[OpenDocString] kdeconnect-kde (cpp)
smshelper.cpp
QObject *SmsHelper::singletonProvider(QQmlEngine *engine, QJSEngine *scriptEngine)
{
    Q_UNUSED(engine);
    Q_UNUSED(scriptEngine);

    return new SmsHelper();
}
This returns a pointer to a SmsHelper object, and sets the engine to be unused.
bool SmsHelper::isPhoneNumberMatchCanonicalized(const QString &canonicalPhone1, const QString &canonicalPhone2)
{
    if (canonicalPhone1.isEmpty() || canonicalPhone2.isEmpty()) {
        // The empty string is not a valid phone number so does not match anything
        return false;
    }

    // To decide if a phone number matches:
    // 1. Are they similar lengths? If two numbers are very different, probably one is junk data and should be ignored
    // 2. Is one a superset of the other? Phone number digits get more specific the further towards the end of the string,
    //    so if one phone number ends with the other, it is probably just a more-complete version of the same thing
    const QString &longerNumber = canonicalPhone1.length() >= canonicalPhone2.length() ? canonicalPhone1 : canonicalPhone2;
    const QString &shorterNumber = canonicalPhone1.length() < canonicalPhone2.length() ? canonicalPhone1 : canonicalPhone2;

    const CountryCode &country = determineCountryCode(longerNumber);

    const bool shorterNumberIsShortCode = isShortCode(shorterNumber, country);
    const bool longerNumberIsShortCode = isShortCode(longerNumber, country);

    if ((shorterNumberIsShortCode && !longerNumberIsShortCode) || (!shorterNumberIsShortCode && longerNumberIsShortCode)) {
        // If only one of the numbers is a short code, they clearly do not match
        return false;
    }

    bool matchingPhoneNumber = longerNumber.endsWith(shorterNumber);

    return matchingPhoneNumber;
}
This function checks if the first string is a valid phone number and if it matches, it checks if the two strings are empty, it returns false. If the first string is empty, it returns false. If the first string is a short code, it checks if the second is a superset of the first number, and if it is a superset of the second, it is a more specific version of the first number, and if it is a superset of the second, it is probably a junk data. If it is a superset of the first number, the second is a number of the same type, it returns true. If the first number is a superset of the second number, the second is a complete version of the same as the first number, the third is a superset of the second number, and if the first is a superset of the second, the second is a more specific version of the first number, the second is a superset of the second. The third is a number of the second is a junk data, the second is ignored.
bool SmsHelper::isPhoneNumberMatch(const QString &phone1, const QString &phone2)
{
    const QString &canonicalPhone1 = canonicalizePhoneNumber(phone1);
    const QString &canonicalPhone2 = canonicalizePhoneNumber(phone2);

    return isPhoneNumberMatchCanonicalized(canonicalPhone1, canonicalPhone2);
}
Returns true if the phone number is matched by resolving the canonical form of the first phone number and comparing the two phone numbers. The function first creates the canonical form of the first phone number, the function retrieves the canonical form of the second phone number, and if it is the first phone number match, the function retrieves the canonical form of the second phone number, and returns it. Finally, the function retrieves the canonical form of the second phone number, which can be used to verify if the first phone number is matched. If the first phone number is not matched, the second phone number is matched by checking if the two phone numbers are canonicalized. Finally, the function returns true on the first successful match. The function returns false on the second phone number. Finally, the first phone number is considered to match, the second phone number match. Finally, the function returns the result of the function.
bool SmsHelper::isShortCode(const QString &phoneNumber, const SmsHelper::CountryCode &country)
{
    // Regardless of which country this number belongs to, a number of length less than 6 is a "short code"
    if (phoneNumber.length() <= 6) {
        return true;
    }
    if (country == CountryCode::Australia && phoneNumber.length() == 8 && phoneNumber.startsWith(QStringLiteral("19"))) {
        return true;
    }
    if (country == CountryCode::CzechRepublic && phoneNumber.length() <= 9) {
        // This entry of the Wikipedia article is fairly poorly written, so it is not clear whether a
        // short code with length 7 should start with a 9. Leave it like this for now, upgrade as
        // we get more information
        return true;
    }
    return false;
}
This checks if the phone number is a short code. It is based on the length of the number, it is based on the country type, and if it is based on the length of 8 and 9 characters. It is clear if the country is a short code.
SmsHelper::CountryCode SmsHelper::determineCountryCode(const QString &canonicalNumber)
{
    // This is going to fall apart if someone has not entered a country code into their contact book
    // or if Android decides it can't be bothered to report the country code, but probably we will
    // be fine anyway
    if (canonicalNumber.startsWith(QStringLiteral("41"))) {
        return CountryCode::Australia;
    }
    if (canonicalNumber.startsWith(QStringLiteral("420"))) {
        return CountryCode::CzechRepublic;
    }

    // The only countries I care about for the current implementation are Australia and CzechRepublic
    // If we need to deal with further countries, we should probably find a library
    return CountryCode::Other;
}
If the canonical number starts with 41 or 420, it returns the country code of the Australia country. If the canonical number starts with 420, it returns the code of the CzechRepublic country. If the canonical number starts with 420, it returns the code of the Australia country. If the canonical number starts with 41, it returns the code of the CzechRepublic country.
QString SmsHelper::canonicalizePhoneNumber(const QString &phoneNumber)
{
    static const QRegularExpression leadingZeroes(QStringLiteral("^0*"));

    QString toReturn(phoneNumber);
    toReturn = toReturn.remove(QStringLiteral(" "));
    toReturn = toReturn.remove(QStringLiteral("-"));
    toReturn = toReturn.remove(QStringLiteral("("));
    toReturn = toReturn.remove(QStringLiteral(")"));
    toReturn = toReturn.remove(QStringLiteral("+"));
    toReturn = toReturn.remove(leadingZeroes);

    if (toReturn.isEmpty()) {
        // If we have stripped away everything, assume this is a special number (and already canonicalized)
        return phoneNumber;
    }
    return toReturn;
}
This removes all leading zeros from the phone number, and returns it.
bool SmsHelper::isAddressValid(const QString &address)
{
    QString canonicalizedNumber = canonicalizePhoneNumber(address);

    // This regular expression matches a wide range of international Phone numbers, minimum of 3 digits and maximum upto 15 digits
    static const QRegularExpression validNumberPattern(QStringLiteral("^(\\d{3,15})$"));
    if (validNumberPattern.match(canonicalizedNumber).hasMatch()) {
        return true;
    } else {
        static const QRegularExpression emailPattern(QStringLiteral("^[\\w\\.]*@[\\w\\.]*$"));
        if (emailPattern.match(address).hasMatch()) {
            return true;
        }
    }
    return false;
}
This implements checking if the address is valid. It first constructs the canonicalized number, then checks if it matches the regular expression. If the canonicalized number is invalid, it checks if the address contains at least one valid number. If the address contains at least one email address, it checks if it contains exactly one valid email address.
QList> SmsHelper::getAllPersons()
{
    static PersonsCache s_cache;
    QList> personDataList;

    for (int rowIndex = 0; rowIndex < s_cache.count(); rowIndex++) {
        const auto person = s_cache.personAt(rowIndex);
        personDataList.append(person);
    }
    return personDataList;
}
This retrieves all people from the cache.
QSharedPointer SmsHelper::lookupPersonByAddress(const QString &address)
{
    const QString &canonicalAddress = SmsHelper::canonicalizePhoneNumber(address);
    QList> personDataList = getAllPersons();

    for (const auto &person : personDataList) {
        const QStringList &allEmails = person->allEmails();

        for (const QString &email : allEmails) {
            // Although we are nominally an SMS messaging app, it is possible to send messages to phone numbers using email -> sms bridges
            if (address == email) {
                return person;
            }
        }

        // TODO: Either upgrade KPeople with an allPhoneNumbers method
        const QVariantList allPhoneNumbers = person->contactCustomProperty(QStringLiteral("all-phoneNumber")).toList();
        for (const QVariant &rawPhoneNumber : allPhoneNumbers) {
            const QString &phoneNumber = SmsHelper::canonicalizePhoneNumber(rawPhoneNumber.toString());
            bool matchingPhoneNumber = SmsHelper::isPhoneNumberMatchCanonicalized(canonicalAddress, phoneNumber);

            if (matchingPhoneNumber) {
                // qCDebug(KDECONNECT_SMS_CONVERSATIONS_LIST_MODEL) << "Matched" << address << "to" << person->name();
                return person;
            }
        }
    }

    return nullptr;
}
This function returns a QSharedPointer object that represents the person of the given address. First, the function gets the person list of all people, and if it finds one, it checks if the address is an email address, if it is a phone number. Next, it iterates over the person list of all people and checks if the address is a phone number. If the address is not a phone number, the function gets the phone number from the all-phoneNumber list of the person using the contactCustomProperty function. If the person is found, the function returns the person object. The function then returns the object that matches the address, if it is a person in the list, and if the address is a phone number, the function returns null.
QIcon SmsHelper::combineIcons(const QList &icons)
{
    QIcon icon;
    if (icons.size() == 0) {
        // We have no icon :(
        // Once we are using the generic icon from KPeople for unknown contacts, this should never happen
    } else if (icons.size() == 1) {
        icon = icons.first();
    } else {
        // Cook an icon by combining the available icons
        // Barring better information, use the size of the first icon as the size for the final icon
        QSize size = icons.first().size();
        QPixmap canvas(size);
        canvas.fill(Qt::transparent);
        QPainter painter(&canvas);

        QSize halfSize = size / 2;

        QRect topLeftQuadrant(QPoint(0, 0), halfSize);
        QRect topRightQuadrant(topLeftQuadrant.topRight(), halfSize);
        QRect bottomLeftQuadrant(topLeftQuadrant.bottomLeft(), halfSize);
        QRect bottomRightQuadrant(topLeftQuadrant.bottomRight(), halfSize);

        if (icons.size() == 2) {
            painter.drawPixmap(topLeftQuadrant, icons[0]);
            painter.drawPixmap(bottomRightQuadrant, icons[1]);
        } else if (icons.size() == 3) {
            QRect topMiddle(QPoint(halfSize.width() / 2, 0), halfSize);
            painter.drawPixmap(topMiddle, icons[0]);
            painter.drawPixmap(bottomLeftQuadrant, icons[1]);
            painter.drawPixmap(bottomRightQuadrant, icons[2]);
        } else {
            // Four or more
            painter.drawPixmap(topLeftQuadrant, icons[0]);
            painter.drawPixmap(topRightQuadrant, icons[1]);
            painter.drawPixmap(bottomLeftQuadrant, icons[2]);
            painter.drawPixmap(bottomRightQuadrant, icons[3]);
        }

        icon = canvas;
    }
    return icon;
}
This method creates a icon from a list of icons. It takes the list of icons to combine, and creates a canvas of the same size.
QString SmsHelper::getTitleForAddresses(const QList &addresses)
{
    QStringList titleParts;
    for (const ConversationAddress &address : addresses) {
        const auto personData = SmsHelper::lookupPersonByAddress(address.address());

        if (personData) {
            titleParts.append(personData->name());
        } else {
            titleParts.append(address.address());
        }
    }

    // It might be nice to alphabetize before combining so that the names don't move around randomly
    // (based on how the data came to us from Android)
    return titleParts.join(QLatin1String(", "));
}
It returns a title string from the given addresses.
QIcon SmsHelper::getIconForAddresses(const QList &addresses)
{
    QList icons;
    for (const ConversationAddress &address : addresses) {
        const auto personData = SmsHelper::lookupPersonByAddress(address.address());
        static const QIcon defaultIcon = QIcon::fromTheme(QStringLiteral("im-user"));
        static const QPixmap defaultAvatar = defaultIcon.pixmap(defaultIcon.actualSize(QSize(32, 32)));
        QPixmap avatar;
        if (personData) {
            const QVariant pic = personData->contactCustomProperty(QStringLiteral("picture"));
            if (pic.canConvert()) {
                avatar = QPixmap::fromImage(pic.value());
            }
            if (avatar.isNull()) {
                icons.append(defaultAvatar);
            } else {
                icons.append(avatar);
            }
        } else {
            icons.append(defaultAvatar);
        }
    }

    // It might be nice to alphabetize by contact before combining so that the pictures don't move
    // around randomly (based on how the data came to us from Android)
    return combineIcons(icons);
}
It returns a QIcon object from an array of addresses. It looks for a person with the given address, and if it exists, it retrieves the avatar for the person from the google google google google google google google google google google google google google google google google google google google google google google google google google google google google google google google google google google google google google google google google google google google google google google google google google google google google google google google google google google google google google google google google google google google google google google google google google google google api, and returns it. If the person has a picture, it returns the default avatar for the person. If the person has no avatar, the default avatar is the empty pixmap.
void SmsHelper::copyToClipboard(const QString &text)
{
    QGuiApplication::clipboard()->setText(text);
}
Copies the given text to the clipboard.
SmsCharCount SmsHelper::getCharCount(const QString &message)
{
    const int remainingWhenEmpty = 160;
    const int septetsInSingleSms = 160;
    const int septetsInSingleConcatSms = 153;
    const int charsInSingleUcs2Sms = 70;
    const int charsInSingleConcatUcs2Sms = 67;

    SmsCharCount count;
    bool enc7bit = true; // 7-bit is used when true, UCS-2 if false
    quint32 septets = 0; // GSM encoding character count (characters in extension are counted as 2 chars)
    int length = message.length();

    // Count characters and detect encoding
    for (int i = 0; i < length; i++) {
        QChar ch = message[i];

        if (isInGsmAlphabet(ch)) {
            septets++;
        } else if (isInGsmAlphabetExtension(ch)) {
            septets += 2;
        } else {
            enc7bit = false;
            break;
        }
    }

    if (length == 0) {
        count.bitsPerChar = 7;
        count.octets = 0;
        count.remaining = remainingWhenEmpty;
        count.messages = 1;
    } else if (enc7bit) {
        count.bitsPerChar = 7;
        count.octets = (septets * 7 + 6) / 8;
        if (septets > septetsInSingleSms) {
            count.messages = (septetsInSingleConcatSms - 1 + septets) / septetsInSingleConcatSms;
            count.remaining = (septetsInSingleConcatSms * count.messages - septets) % septetsInSingleConcatSms;
        } else {
            count.messages = 1;
            count.remaining = (septetsInSingleSms - septets) % septetsInSingleSms;
        }
    } else {
        count.bitsPerChar = 16;
        count.octets = length * 2; // QString should be in UTF-16
        if (length > charsInSingleUcs2Sms) {
            count.messages = (charsInSingleConcatUcs2Sms - 1 + length) / charsInSingleConcatUcs2Sms;
            count.remaining = (charsInSingleConcatUcs2Sms * count.messages - length) % charsInSingleConcatUcs2Sms;
        } else {
            count.messages = 1;
            count.remaining = (charsInSingleUcs2Sms - length) % charsInSingleUcs2Sms;
        }
    }

    return count;
}
This method returns a SmsCharCount object, based on the number of characters in the message, and the number of septets in the first ascii character, and the number of characters in the second ascii character, and the number of characters in the third ascii character, and the encoding is 7bit. If the length is 0, the function returns 0.
bool SmsHelper::isInGsmAlphabet(const QChar &ch)
{
    wchar_t unicode = ch.unicode();

    if ((unicode & ~0x7f) == 0) { // If the character is ASCII
        // use map
        return gsm_ascii_map[unicode];
    } else {
        switch (unicode) {
        case 0xa1: // “¡”
        case 0xa7: // “§”
        case 0xbf: // “¿”
        case 0xa4: // “¤”
        case 0xa3: // “£”
        case 0xa5: // “¥”
        case 0xe0: // “à”
        case 0xe5: // “å”
        case 0xc5: // “Å”
        case 0xe4: // “ä”
        case 0xc4: // “Ä”
        case 0xe6: // “æ”
        case 0xc6: // “Æ”
        case 0xc7: // “Ç”
        case 0xe9: // “é”
        case 0xc9: // “É”
        case 0xe8: // “è”
        case 0xec: // “ì”
        case 0xf1: // “ñ”
        case 0xd1: // “Ñ”
        case 0xf2: // “ò”
        case 0xf6: // “ö”
        case 0xd6: // “Ö”
        case 0xf8: // “ø”
        case 0xd8: // “Ø”
        case 0xdf: // “ß”
        case 0xf9: // “ù”
        case 0xfc: // “ü”
        case 0xdc: // “Ü”
        case 0x393: // “Γ”
        case 0x394: // “Δ”
        case 0x398: // “Θ”
        case 0x39b: // “Λ”
        case 0x39e: // “Ξ”
        case 0x3a0: // “Π”
        case 0x3a3: // “Σ”
        case 0x3a6: // “Φ”
        case 0x3a8: // “Ψ”
        case 0x3a9: // “Ω”
            return true;
        }
    }
    return false;
}

bool SmsHelper::isInGsmAlphabetExtension(const QChar &ch)
{
    wchar_t unicode = ch.unicode();
    switch (unicode) {
    case '{':
    case '}':
    case '|':
    case '\\':
    case '^':
This implements the isInGsmAlphabet function, which checks if the character is in the GSM alphabet. It allows the caller to determine if the character is in the ASCII format. It allows the caller to determine if the character is in the GSM alphabet. It allows the caller to determine if the character is in the GSM alphabet. It allows the caller to determine if the character is in the GSM alphabet. It allows the caller to determine if the character is in the GSM alphabet. It allows the caller to determine if the character is in the extension format.
ase '[':
    case ']':
    case '~':
    case 0x20ac: // Euro sign
        return true;
    }
    return false;
}

quint64 SmsHelper::totalMessageSize(const QList &urls, const QString &text)
{
    quint64 totalSize = text.size();
    for (QUrl url : urls) {
        QFileInfo fileInfo(url.toLocalFile()
This returns the total size of a list of urls and a text.
totalSize += fileInfo.size();
    }

    return totalSize;
}

QIcon SmsHelper::getThumbnailForAttachment(const Attachment &attachment)
{
    static const QMimeDatabase mimeDatabase;
    const QByteArray rawData = QByteArray::fromBase64(attachment.base64En
This returns the total size of the image represented by the given attachment. It also returns the size of the file in bytes.
dedFile().toUtf8());

    if (attachment.mimeType().startsWith(QStringLiteral("image")) || attachment.mimeType().startsWith(QStringLiteral("video"))) {
        QPixmap preview;
        preview.loadFromData(rawData);
        return QIcon(preview);
    } else {
        const QMimeType mimeType = mimeDatabase.mimeTypeForData(rawData);
        const QIcon mimeIcon = QIcon::fromTheme(mimeType.iconName());
        if (mimeIcon.isNull()) {
            // I am not sure if QIcon::isNull will actually tell us what we care about but I don't
            // know how to trigger the case where we need to use genericIconName instead of iconName
            return QIcon::fromTheme(mimeType.genericIconName());
        } else {
            return mimeIcon;
        }
    }
}
It returns a QIcon object from the mime type, if it exists.