Files
jami-client-qt/src/utils.cpp
Ming Rui Zhang fcc2f41f6b ringtones: use qt API to get ringtones path and remove redundant folder
Use qt applicationDirPath to get ringtones path to avoid the complexity
of using windows API, and remove redundant ringtones folder.

Gitlab: #41
Change-Id: I5ae95531fb0e7627cdddb61334420bae3f37848b
2020-08-28 15:36:41 -04:00

1086 lines
31 KiB
C++

/*
* Copyright (C) 2015-2020 by Savoir-faire Linux
* Author: Edric Ladent Milaret <edric.ladent-milaret@savoirfairelinux.com>
* Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>
* Author: Isa Nanic <isa.nanic@savoirfairelinux.com
* Author: Mingrui Zhang <mingrui.zhang@savoirfairelinux.com>
* Author: Aline Gondim Santos <aline.gondimsantos@savoirfairelinux.com>
*
* 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 3 of the License, or
* (at your option) any later version.
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "utils.h"
#ifdef Q_OS_WIN
#include <lmcons.h>
#include <shlguid.h>
#include <shlobj.h>
#include <shlwapi.h>
#include <shobjidl.h>
#include <windows.h>
#endif
#include "globalsystemtray.h"
#include "jamiavatartheme.h"
#include "lrcinstance.h"
#include "pixbufmanipulator.h"
#include "version.h"
#include <globalinstances.h>
#include <qrencode.h>
#include <QApplication>
#include <QBitmap>
#include <QErrorMessage>
#include <QFile>
#include <QMessageBox>
#include <QObject>
#include <QPainter>
#include <QPropertyAnimation>
#include <QScreen>
#include <QStackedWidget>
#include <QSvgRenderer>
#include <QTranslator>
#include <QtConcurrent/QtConcurrent>
bool
Utils::CreateStartupLink(const std::wstring &wstrAppName)
{
#ifdef Q_OS_WIN
TCHAR szPath[MAX_PATH];
GetModuleFileName(NULL, szPath, MAX_PATH);
std::wstring programPath(szPath);
TCHAR startupPath[MAX_PATH];
SHGetFolderPathW(NULL, CSIDL_STARTUP, NULL, 0, startupPath);
std::wstring linkPath(startupPath);
linkPath += std::wstring(TEXT("\\") + wstrAppName + TEXT(".lnk"));
return Utils::CreateLink(programPath.c_str(), linkPath.c_str());
#else
return true;
#endif
}
bool
Utils::CreateLink(LPCWSTR lpszPathObj, LPCWSTR lpszPathLink)
{
#ifdef Q_OS_WIN
HRESULT hres;
IShellLink *psl;
hres = CoCreateInstance(CLSID_ShellLink,
NULL,
CLSCTX_INPROC_SERVER,
IID_IShellLink,
(LPVOID *) &psl);
if (SUCCEEDED(hres)) {
IPersistFile *ppf;
psl->SetPath(lpszPathObj);
psl->SetArguments(TEXT("--minimized"));
hres = psl->QueryInterface(IID_IPersistFile, (LPVOID *) &ppf);
if (SUCCEEDED(hres)) {
hres = ppf->Save(lpszPathLink, TRUE);
ppf->Release();
}
psl->Release();
}
return hres;
#else
Q_UNUSED(lpszPathObj)
Q_UNUSED(lpszPathLink)
return true;
#endif
}
void
Utils::DeleteStartupLink(const std::wstring &wstrAppName)
{
#ifdef Q_OS_WIN
TCHAR startupPath[MAX_PATH];
SHGetFolderPathW(NULL, CSIDL_STARTUP, NULL, 0, startupPath);
std::wstring linkPath(startupPath);
linkPath += std::wstring(TEXT("\\") + wstrAppName + TEXT(".lnk"));
DeleteFile(linkPath.c_str());
#endif
}
bool
Utils::CheckStartupLink(const std::wstring &wstrAppName)
{
#ifdef Q_OS_WIN
TCHAR startupPath[MAX_PATH];
SHGetFolderPathW(NULL, CSIDL_STARTUP, NULL, 0, startupPath);
std::wstring linkPath(startupPath);
linkPath += std::wstring(TEXT("\\") + wstrAppName + TEXT(".lnk"));
return PathFileExists(linkPath.c_str());
#else
return true;
#endif
}
const char *
Utils::WinGetEnv(const char *name)
{
#ifdef Q_OS_WIN
const DWORD buffSize = 65535;
static char buffer[buffSize];
if (GetEnvironmentVariableA(name, buffer, buffSize)) {
return buffer;
} else {
return 0;
}
#else
return 0;
#endif
}
void
Utils::removeOldVersions()
{
#ifdef Q_OS_WIN
/*
* As per: https://git.jami.net/savoirfairelinux/ring-client-windows/issues/429
* NB: As only the 64-bit version of this application is distributed, we will only
* remove 1. the configuration reg keys for Ring-x64, 2. the startup links for Ring,
* 3. the winsparkle reg keys. The NSIS uninstall reg keys for Jami-x64 are removed
* by the MSI installer.
* Uninstallation of Ring, either 32 or 64 bit, is left to the user.
* The current version of Jami will attempt to kill Ring.exe upon start if a startup
* link is found.
*/
QString node64 = "HKEY_LOCAL_MACHINE\\SOFTWARE\\WOW6432Node";
QString hkcuSoftwareKey = "HKEY_CURRENT_USER\\Software\\";
QString uninstKey = "\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\";
QString company = "Savoir-Faire Linux";
/*
* 1. Configuration reg keys for Ring-x64.
*/
QSettings(hkcuSoftwareKey + "jami.net\\Ring", QSettings::NativeFormat).remove("");
QSettings(hkcuSoftwareKey + "ring.cx", QSettings::NativeFormat).remove("");
/*
* 2. Unset Ring as a startup application.
*/
if (Utils::CheckStartupLink(TEXT("Ring"))) {
qDebug() << "Found startup link for Ring. Removing it and killing Ring.exe.";
Utils::DeleteStartupLink(TEXT("Ring"));
QProcess::execute("taskkill /im Ring.exe /f");
}
/*
* 3. Remove registry entries for winsparkle(both Jami-x64 and Ring-x64).
*/
QSettings(hkcuSoftwareKey + company, QSettings::NativeFormat).remove("");
#else
return;
#endif
}
QString
Utils::GetRingtonePath()
{
#ifdef Q_OS_WIN
return QCoreApplication::applicationDirPath() + "\\ringtones\\default.opus";
#else
return QString("/usr/local");
#endif
}
QString
Utils::GenGUID()
{
#ifdef Q_OS_WIN
GUID gidReference;
wchar_t *str;
HRESULT hCreateGuid = CoCreateGuid(&gidReference);
if (hCreateGuid == S_OK) {
StringFromCLSID(gidReference, &str);
auto gStr = QString::fromWCharArray(str);
return gStr.remove("{").remove("}").toLower();
} else
return QString();
#else
return QString("");
#endif
}
QString
Utils::GetISODate()
{
#ifdef Q_OS_WIN
SYSTEMTIME lt;
GetSystemTime(&lt);
return QString("%1-%2-%3T%4:%5:%6Z")
.arg(lt.wYear)
.arg(lt.wMonth, 2, 10, QChar('0'))
.arg(lt.wDay, 2, 10, QChar('0'))
.arg(lt.wHour, 2, 10, QChar('0'))
.arg(lt.wMinute, 2, 10, QChar('0'))
.arg(lt.wSecond, 2, 10, QChar('0'));
#else
return QString();
#endif
}
void
Utils::InvokeMailto(const QString &subject, const QString &body, const QString &attachement)
{
#ifdef Q_OS_WIN
HKEY hKey;
LONG lRes = RegOpenKeyExW(HKEY_CLASSES_ROOT, L"mailto", 0, KEY_READ, &hKey);
if (lRes != ERROR_FILE_NOT_FOUND) {
auto addr = QString("mailto:?subject=%1&body=%2").arg(subject).arg(body);
if (not attachement.isEmpty())
addr += QString("&attachement=%1").arg(attachement);
ShellExecute(nullptr, L"open", addr.toStdWString().c_str(), NULL, NULL, SW_SHOWNORMAL);
} else {
QErrorMessage errorMessage;
errorMessage.showMessage(QObject::tr("No default mail client found"));
}
#endif
}
QString
Utils::getContactImageString(const QString &accountId, const QString &uid)
{
return QString::fromLatin1(
Utils::QImageToByteArray(
Utils::conversationPhoto(uid, LRCInstance::getAccountInfo(accountId)))
.toBase64()
.data());
}
QImage
Utils::getCirclePhoto(const QImage original, int sizePhoto)
{
QImage target(sizePhoto, sizePhoto, QImage::Format_ARGB32_Premultiplied);
target.fill(Qt::transparent);
QPainter painter(&target);
painter.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform);
painter.setBrush(QBrush(Qt::white));
auto scaledPhoto = original
.scaled(sizePhoto,
sizePhoto,
Qt::KeepAspectRatioByExpanding,
Qt::SmoothTransformation)
.convertToFormat(QImage::Format_ARGB32_Premultiplied);
int margin = 0;
if (scaledPhoto.width() > sizePhoto) {
margin = (scaledPhoto.width() - sizePhoto) / 2;
}
painter.drawEllipse(0, 0, sizePhoto, sizePhoto);
painter.setCompositionMode(QPainter::CompositionMode_SourceIn);
painter.drawImage(0, 0, scaledPhoto, margin, 0);
return target;
}
void
Utils::setStackWidget(QStackedWidget *stack, QWidget *widget)
{
if (stack->indexOf(widget) != -1 && stack->currentWidget() != widget) {
stack->setCurrentWidget(widget);
}
}
void
Utils::showSystemNotification(QWidget *widget,
const QString &message,
long delay,
const QString &triggeredAccountId)
{
QSettings settings("jami.net", "Jami");
if (settings.value(SettingsKey::enableNotifications).toBool()) {
GlobalSystemTray::instance().setTriggeredAccountId(triggeredAccountId);
GlobalSystemTray::instance().showMessage(message, "", QIcon(":images/jami.png"));
QApplication::alert(widget, delay);
}
}
void
Utils::showSystemNotification(QWidget *widget,
const QString &sender,
const QString &message,
long delay,
const QString &triggeredAccountId)
{
QSettings settings("jami.net", "Jami");
if (settings.value(SettingsKey::enableNotifications).toBool()) {
GlobalSystemTray::instance().setTriggeredAccountId(triggeredAccountId);
GlobalSystemTray::instance().showMessage(sender, message, QIcon(":images/jami.png"));
QApplication::alert(widget, delay);
}
}
QSize
Utils::getRealSize(QScreen *screen)
{
#ifdef Q_OS_WIN
DEVMODE dmThisScreen;
ZeroMemory(&dmThisScreen, sizeof(dmThisScreen));
EnumDisplaySettings((const wchar_t *) screen->name().utf16(),
ENUM_CURRENT_SETTINGS,
(DEVMODE *) &dmThisScreen);
return QSize(dmThisScreen.dmPelsWidth, dmThisScreen.dmPelsHeight);
#else
return {};
#endif
}
void
Utils::forceDeleteAsync(const QString &path)
{
/*
* Keep deleting file until the process holding it let go,
* or the file itself does not exist anymore.
*/
QtConcurrent::run([path] {
QFile file(path);
if (!QFile::exists(path))
return;
int retries{0};
while (!file.remove() && retries < 5) {
qDebug().noquote() << "\n" << file.errorString() << "\n";
QThread::msleep(10);
++retries;
}
});
}
UtilsAdapter &
UtilsAdapter::instance()
{
static auto instance = new UtilsAdapter;
return *instance;
}
QString
Utils::getChangeLog()
{
QString logs;
QFile changeLogFile(":/changelog.html");
if (!changeLogFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
qDebug().noquote() << " Change log file failed to load";
return {};
}
QTextStream in(&changeLogFile);
in.setCodec("UTF-8");
while (!in.atEnd()) {
logs += in.readLine();
}
return logs;
}
QString
Utils::getProjectCredits()
{
QString credits;
QFile projectCreditsFile(":/projectcredits.html");
if (!projectCreditsFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
qDebug().noquote() << " Project Credits failed to load";
return {};
}
QTextStream in(&projectCreditsFile);
in.setCodec("UTF-8");
while (!in.atEnd()) {
QString currentLine = in.readLine();
if (credits.isEmpty()) {
credits += "<h3 align=\"center\" style=\" margin-top:0px; "
+ QString("margin-bottom:0px; margin-left:0px; margin-right:0px; ")
+ "-qt-block-indent:0; text-indent:0px;\"><span style=\" font-weight:600;\">"
+ UtilsAdapter::tr("Created by:") + "</span></h3>";
} else if (currentLine.contains("Marianne Forget")) {
credits
+= "<h3 align=\"center\" style=\" margin-top:0px; margin-bottom:0px; "
+ QString(
"margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">")
+ "<span style=\" font-weight:600;\">" + UtilsAdapter::tr("Artwork by:")
+ "</span></h3>";
}
credits += currentLine;
}
credits += "<p align=\"center\" style=\" margin-top:0px; margin-bottom:0px; "
+ QString(
"margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">")
+ UtilsAdapter::tr("Based on the SFLPhone project") + "</p>";
return credits;
}
void
Utils::cleanUpdateFiles()
{
/*
* Delete all logs and msi in the %TEMP% directory before launching.
*/
QString dir = QString(Utils::WinGetEnv("TEMP"));
QDir log_dir(dir, {"jami*.log"});
for (const QString &filename : log_dir.entryList()) {
log_dir.remove(filename);
}
QDir msi_dir(dir, {"jami*.msi"});
for (const QString &filename : msi_dir.entryList()) {
msi_dir.remove(filename);
}
QDir version_dir(dir, {"version"});
for (const QString &filename : version_dir.entryList()) {
version_dir.remove(filename);
}
}
void
Utils::checkForUpdates(bool withUI, QWidget *parent)
{
Q_UNUSED(withUI)
Q_UNUSED(parent)
/*
* TODO: check update logic.
*/
}
void
Utils::applyUpdates(bool updateToBeta, QWidget *parent)
{
Q_UNUSED(updateToBeta)
Q_UNUSED(parent)
/*
* TODO: update logic.
*/
}
inline QString
removeEndlines(const QString &str)
{
QString trimmed(str);
trimmed.remove(QChar('\n'));
trimmed.remove(QChar('\r'));
return trimmed;
}
QString
Utils::bestIdForConversation(const lrc::api::conversation::Info &conv,
const lrc::api::ConversationModel &model)
{
auto contact = model.owner.contactModel->getContact(conv.participants[0]);
if (!contact.registeredName.isEmpty()) {
return removeEndlines(contact.registeredName);
}
return removeEndlines(contact.profileInfo.uri);
}
QString
Utils::bestIdForAccount(const lrc::api::account::Info &account)
{
if (!account.registeredName.isEmpty()) {
return removeEndlines(account.registeredName);
}
return removeEndlines(account.profileInfo.uri);
}
QString
Utils::bestNameForAccount(const lrc::api::account::Info &account)
{
if (account.profileInfo.alias.isEmpty()) {
return bestIdForAccount(account);
}
return account.profileInfo.alias;
}
QString
Utils::bestIdForContact(const lrc::api::contact::Info &contact)
{
if (!contact.registeredName.isEmpty()) {
return removeEndlines(contact.registeredName);
}
return removeEndlines(contact.profileInfo.uri);
}
QString
Utils::bestNameForContact(const lrc::api::contact::Info &contact)
{
auto alias = removeEndlines(contact.profileInfo.alias);
if (alias.length() == 0) {
return bestIdForContact(contact);
}
return alias;
}
QString
Utils::bestNameForConversation(const lrc::api::conversation::Info &conv,
const lrc::api::ConversationModel &model)
{
try {
auto contact = model.owner.contactModel->getContact(conv.participants[0]);
auto alias = removeEndlines(contact.profileInfo.alias);
if (alias.length() == 0) {
return bestIdForConversation(conv, model);
}
return alias;
} catch (...) {
}
return {};
}
/*
* Returns empty string if only infoHash is available, second best identifier otherwise.
*/
QString
Utils::secondBestNameForAccount(const lrc::api::account::Info &account)
{
auto alias = removeEndlines(account.profileInfo.alias);
auto registeredName = removeEndlines(account.registeredName);
auto infoHash = account.profileInfo.uri;
if (!alias.length() == 0) {
/*
* If alias exists.
*/
if (!registeredName.length() == 0) {
/*
* If registeredName exists.
*/
return registeredName;
} else {
return infoHash;
}
} else {
if (!registeredName.length() == 0) {
/*
* If registeredName exists.
*/
return infoHash;
} else {
return "";
}
}
}
lrc::api::profile::Type
Utils::profileType(const lrc::api::conversation::Info &conv,
const lrc::api::ConversationModel &model)
{
try {
auto contact = model.owner.contactModel->getContact(conv.participants[0]);
return contact.profileInfo.type;
} catch (...) {
return lrc::api::profile::Type::INVALID;
}
}
std::string
Utils::formatTimeString(const std::time_t &timestamp)
{
std::time_t now = std::time(nullptr);
char interactionDay[64];
char nowDay[64];
std::strftime(interactionDay, sizeof(interactionDay), "%D", std::localtime(&timestamp));
std::strftime(nowDay, sizeof(nowDay), "%D", std::localtime(&now));
if (std::string(interactionDay) == std::string(nowDay)) {
char interactionTime[64];
std::strftime(interactionTime, sizeof(interactionTime), "%R", std::localtime(&timestamp));
return interactionTime;
} else {
return interactionDay;
}
}
bool
Utils::isInteractionGenerated(const lrc::api::interaction::Type &type)
{
return type == lrc::api::interaction::Type::CALL
|| type == lrc::api::interaction::Type::CONTACT;
}
bool
Utils::isContactValid(const QString &contactUid, const lrc::api::ConversationModel &model)
{
const auto contact = model.owner.contactModel->getContact(contactUid);
return (contact.profileInfo.type == lrc::api::profile::Type::PENDING
|| contact.profileInfo.type == lrc::api::profile::Type::TEMPORARY
|| contact.profileInfo.type == lrc::api::profile::Type::RING
|| contact.profileInfo.type == lrc::api::profile::Type::SIP)
&& !contact.profileInfo.uri.isEmpty();
}
bool
Utils::getReplyMessageBox(QWidget *widget, const QString &title, const QString &text)
{
if (QMessageBox::question(widget, title, text, QMessageBox::Yes | QMessageBox::No)
== QMessageBox::Yes)
return true;
return false;
}
QImage
Utils::conversationPhoto(const QString &convUid,
const lrc::api::account::Info &accountInfo,
bool filtered)
{
auto* convModel = LRCInstance::getCurrentConversationModel();
const auto convInfo = convModel->getConversationForUID(convUid);
if (!convInfo.uid.isEmpty()) {
return GlobalInstances::pixmapManipulator()
.decorationRole(convInfo, accountInfo)
.value<QImage>();
}
return QImage();
}
QColor
Utils::getAvatarColor(const QString &canonicalUri)
{
if (canonicalUri.isEmpty()) {
return JamiAvatarTheme::defaultAvatarColor_;
}
auto h = QString(
QCryptographicHash::hash(canonicalUri.toLocal8Bit(), QCryptographicHash::Md5).toHex());
if (h.isEmpty() || h.isNull()) {
return JamiAvatarTheme::defaultAvatarColor_;
}
auto colorIndex = std::string("0123456789abcdef").find(h.at(0).toLatin1());
return JamiAvatarTheme::avatarColors_[colorIndex];
}
/* Generate a QImage representing a dummy user avatar, when user doesn't provide it.
* Current rendering is a flat colored circle with a centered letter.
* The color of the letter is computed from the circle color to be visible whaterver be the circle color.
*/
QImage
Utils::fallbackAvatar(const QSize size, const QString &canonicalUriStr, const QString &letterStr)
{
/*
* We start with a transparent avatar.
*/
QImage avatar(size, QImage::Format_ARGB32);
avatar.fill(Qt::transparent);
/*
* We pick a color based on the passed character.
*/
QColor avColor = getAvatarColor(canonicalUriStr);
/*
* We draw a circle with this color.
*/
QPainter painter(&avatar);
painter.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform);
painter.setPen(Qt::transparent);
painter.setBrush(avColor.lighter(110));
painter.drawEllipse(avatar.rect());
/*
* If a letter was passed, then we paint a letter in the circle,
* otherwise we draw the default avatar icon.
*/
QString letterStrCleaned(letterStr);
letterStrCleaned.remove(QRegExp("[\\n\\t\\r]"));
if (!letterStr.isEmpty()) {
auto unicode = letterStr.toUcs4().at(0);
if (unicode >= 0x1F000 && unicode <= 0x1FFFF) {
/*
* Is Emoticon.
*/
auto letter = QString::fromUcs4(&unicode, 1);
QFont font(QStringLiteral("Segoe UI Emoji"), avatar.height() / 2.66667, QFont::Medium);
painter.setFont(font);
QRect emojiRect(avatar.rect());
emojiRect.moveTop(-6);
painter.drawText(emojiRect, letter, QTextOption(Qt::AlignCenter));
} else if (unicode >= 0x0000 && unicode <= 0x00FF) {
/*
* Is Basic Latin.
*/
auto letter = letterStr.at(0).toUpper();
QFont font("Arial", avatar.height() / 2.66667, QFont::Medium);
painter.setFont(font);
painter.setPen(Qt::white);
painter.drawText(avatar.rect(), QString(letter), QTextOption(Qt::AlignCenter));
} else {
auto letter = QString::fromUcs4(&unicode, 1);
QFont font("Arial", avatar.height() / 2.66667, QFont::Medium);
painter.setFont(font);
painter.setPen(Qt::white);
painter.drawText(avatar.rect(), QString(letter), QTextOption(Qt::AlignCenter));
}
} else {
QRect overlayRect = avatar.rect();
qreal margin = (0.05 * overlayRect.width());
overlayRect.moveLeft(overlayRect.left() + margin * 0.5);
overlayRect.moveTop(overlayRect.top() + margin * 0.5);
overlayRect.setWidth(overlayRect.width() - margin);
overlayRect.setHeight(overlayRect.height() - margin);
painter.drawPixmap(overlayRect, QPixmap(":/images/default_avatar_overlay.svg"));
}
return avatar;
}
QImage
Utils::fallbackAvatar(const QSize size, const std::string &alias, const std::string &uri)
{
return fallbackAvatar(size, QString::fromStdString(uri), QString::fromStdString(alias));
}
QByteArray
Utils::QImageToByteArray(QImage image)
{
QByteArray ba;
QBuffer buffer(&ba);
buffer.open(QIODevice::WriteOnly);
image.save(&buffer, "PNG");
return ba;
}
QImage
Utils::cropImage(const QImage &img)
{
QRect rect;
auto w = img.width();
auto h = img.height();
if (w > h) {
return img.copy({(w - h) / 2, 0, h, h});
}
return img.copy({0, (h - w) / 2, w, w});
}
QPixmap
Utils::pixmapFromSvg(const QString &svg_resource, const QSize &size)
{
QSvgRenderer svgRenderer(svg_resource);
QPixmap pixmap(size);
pixmap.fill(Qt::transparent);
QPainter pixPainter(&pixmap);
svgRenderer.render(&pixPainter);
return pixmap;
}
QImage
Utils::setupQRCode(QString ringID, int margin)
{
auto rcode = QRcode_encodeString(ringID.toStdString().c_str(),
0, // Let the version be decided by libqrencode
QR_ECLEVEL_L, // Lowest level of error correction
QR_MODE_8, // 8-bit data mode
1);
if (not rcode) {
qWarning() << "Failed to generate QR code: " << strerror(errno);
return QImage();
}
int qrwidth = rcode->width + margin * 2;
QImage result(QSize(qrwidth, qrwidth), QImage::Format_Mono);
QPainter painter;
painter.begin(&result);
painter.setClipRect(QRect(0, 0, qrwidth, qrwidth));
painter.setPen(QPen(Qt::black, 0.1, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin));
painter.setBrush(Qt::black);
painter.fillRect(QRect(0, 0, qrwidth, qrwidth), Qt::white);
unsigned char *p;
p = rcode->data;
for (int y = 0; y < rcode->width; y++) {
unsigned char *row = (p + (y * rcode->width));
for (int x = 0; x < rcode->width; x++) {
if (*(row + x) & 0x1) {
painter.drawRect(margin + x, margin + y, 1, 1);
}
}
}
painter.end();
QRcode_free(rcode);
return result;
}
float
Utils::getCurrentScalingRatio()
{
return CURRENT_SCALING_RATIO;
}
void
Utils::setCurrentScalingRatio(float ratio)
{
CURRENT_SCALING_RATIO = ratio;
}
QString
Utils::formattedTime(int duration)
{
if (duration == 0)
return {};
std::string formattedString;
auto minutes = duration / 60;
auto seconds = duration % 60;
if (minutes > 0) {
formattedString += std::to_string(minutes) + ":";
if (formattedString.length() == 2) {
formattedString = "0" + formattedString;
}
} else {
formattedString += "00:";
}
if (seconds < 10)
formattedString += "0";
formattedString += std::to_string(seconds);
return QString::fromStdString(formattedString);
}
QByteArray
Utils::QByteArrayFromFile(const QString &filename)
{
QFile file(filename);
if (file.open(QIODevice::ReadOnly)) {
return file.readAll();
} else {
qDebug() << "can't open file";
return QByteArray();
}
}
QPixmap
Utils::generateTintedPixmap(const QString &filename, QColor color)
{
QPixmap px(filename);
QImage tmpImage = px.toImage();
for (int y = 0; y < tmpImage.height(); y++) {
for (int x = 0; x < tmpImage.width(); x++) {
color.setAlpha(tmpImage.pixelColor(x, y).alpha());
tmpImage.setPixelColor(x, y, color);
}
}
return QPixmap::fromImage(tmpImage);
}
QPixmap
Utils::generateTintedPixmap(const QPixmap &pix, QColor color)
{
QPixmap px = pix;
QImage tmpImage = px.toImage();
for (int y = 0; y < tmpImage.height(); y++) {
for (int x = 0; x < tmpImage.width(); x++) {
color.setAlpha(tmpImage.pixelColor(x, y).alpha());
tmpImage.setPixelColor(x, y, color);
}
}
return QPixmap::fromImage(tmpImage);
}
QImage
Utils::scaleAndFrame(const QImage photo, const QSize &size)
{
return photo.scaled(size, Qt::KeepAspectRatio, Qt::SmoothTransformation);
}
QImage
Utils::accountPhoto(const lrc::api::account::Info &accountInfo, const QSize &size)
{
QImage photo;
if (!accountInfo.profileInfo.avatar.isEmpty()) {
QByteArray ba = accountInfo.profileInfo.avatar.toLocal8Bit();
photo = GlobalInstances::pixmapManipulator().personPhoto(ba, nullptr).value<QImage>();
} else {
auto bestId = bestIdForAccount(accountInfo);
auto bestName = bestNameForAccount(accountInfo);
QString letterStr = bestId == bestName ? QString() : bestName;
QString prefix = accountInfo.profileInfo.type == lrc::api::profile::Type::RING ? "ring:"
: "sip:";
photo = fallbackAvatar(size, prefix + accountInfo.profileInfo.uri, letterStr);
}
return scaleAndFrame(photo, size);
}
QString
Utils::humanFileSize(qint64 fileSize)
{
float fileSizeF = static_cast<float>(fileSize);
float thresh = 1024;
if (abs(fileSizeF) < thresh) {
return QString::number(fileSizeF) + " B";
}
QString units[] = {"kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"};
int unit_position = -1;
do {
fileSizeF /= thresh;
++unit_position;
} while (abs(fileSizeF) >= thresh && unit_position < units->size() - 1);
/*
* Round up to two decimal.
*/
fileSizeF = roundf(fileSizeF * 100) / 100;
return QString::number(fileSizeF) + " " + units[unit_position];
}
const QString
UtilsAdapter::getBestName(const QString &accountId, const QString &uid)
{
auto* convModel = LRCInstance::getAccountInfo(accountId).conversationModel.get();
return Utils::bestNameForConversation(convModel->getConversationForUID(uid), *convModel);
}
const QString
UtilsAdapter::getBestId(const QString &accountId, const QString &uid)
{
auto* convModel = LRCInstance::getAccountInfo(accountId).conversationModel.get();
return Utils::bestIdForConversation(convModel->getConversationForUID(uid), *convModel);
}
int
UtilsAdapter::getTotalUnreadMessages()
{
int totalUnreadMessages{0};
if (LRCInstance::getCurrentAccountInfo().profileInfo.type != lrc::api::profile::Type::SIP) {
auto* convModel = LRCInstance::getCurrentConversationModel();
auto ringConversations = convModel->getFilteredConversations(lrc::api::profile::Type::RING);
std::for_each(ringConversations.begin(),
ringConversations.end(),
[&totalUnreadMessages](const auto &conversation) {
totalUnreadMessages += conversation.unreadMessages;
});
}
return totalUnreadMessages;
}
int
UtilsAdapter::getTotalPendingRequest()
{
auto &accountInfo = LRCInstance::getCurrentAccountInfo();
return accountInfo.contactModel->pendingRequestCount();
}
void
UtilsAdapter::setConversationFilter(const QString &filter)
{
LRCInstance::getCurrentConversationModel()->setFilter(filter);
}
void
UtilsAdapter::clearConversationHistory(const QString &accountId, const QString &uid)
{
LRCInstance::getAccountInfo(accountId).conversationModel->clearHistory(uid);
}
void
UtilsAdapter::removeConversation(const QString &accountId, const QString &uid, bool banContact)
{
LRCInstance::getAccountInfo(accountId).conversationModel->removeConversation(uid, banContact);
}
const QString
UtilsAdapter::getCurrAccId()
{
return LRCInstance::getCurrAccId();
}
const QStringList
UtilsAdapter::getCurrAccList()
{
return LRCInstance::accountModel().getAccountList();
}
int
UtilsAdapter::getAccountListSize()
{
return getCurrAccList().size();
}
void
UtilsAdapter::setCurrentCall(const QString &accountId, const QString &convUid)
{
auto &accInfo = LRCInstance::getAccountInfo(accountId);
const auto convInfo = accInfo.conversationModel->getConversationForUID(convUid);
accInfo.callModel->setCurrentCall(convInfo.callId);
}
void
UtilsAdapter::startPreviewing(bool force)
{
LRCInstance::renderer()->startPreviewing(force);
}
void
UtilsAdapter::stopPreviewing()
{
if (!LRCInstance::hasVideoCall()) {
LRCInstance::renderer()->stopPreviewing();
}
}
bool
UtilsAdapter::hasVideoCall()
{
return LRCInstance::hasVideoCall();
}
const QString
UtilsAdapter::getCallId(const QString &accountId, const QString &convUid)
{
auto &accInfo = LRCInstance::getAccountInfo(accountId);
const auto convInfo = accInfo.conversationModel->getConversationForUID(convUid);
if (convInfo.uid.isEmpty()) {
return "";
}
auto call = LRCInstance::getCallInfoForConversation(convInfo, false);
if (!call) {
return "";
}
return call->id;
}
// returns true if name is valid registered name
bool
UtilsAdapter::validateRegNameForm(const QString &regName)
{
QRegularExpression regExp(" ");
if (regName.size() > 2 && !regName.contains(regExp)) {
return true;
} else {
return false;
}
}
QString
UtilsAdapter::getStringUTF8(QString string)
{
return string.toUtf8();
}
QString
UtilsAdapter::getRecordQualityString(int value)
{
return value ? QString::number(static_cast<float>(value) / 100, 'f', 1) + " Mbps" : "Default";
}
QString
UtilsAdapter::getCurrentPath()
{
return QDir::currentPath();
}
bool
UtilsAdapter::checkShowPluginsButton()
{
return LRCInstance::pluginModel().getPluginsEnabled()
&& (LRCInstance::pluginModel().listLoadedPlugins().size() > 0);
}