client-qml: add initial commit
Change-Id: I32bfdd2a618aa7ac6181da2697e241667b010aab
diff --git a/src/utils.h b/src/utils.h
new file mode 100644
index 0000000..64db451
--- /dev/null
+++ b/src/utils.h
@@ -0,0 +1,562 @@
+/*
+ * 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>
+ *
+ * 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/>.
+ */
+
+#pragma once
+
+#include "version.h"
+
+#include <string>
+
+#include <QClipboard>
+#include <QCryptographicHash>
+#include <QDir>
+#include <QApplication>
+#include <QImage>
+#include <QItemDelegate>
+#include <QLabel>
+#include <QListWidget>
+#include <QMessageBox>
+#include <QQmlEngine>
+#include <QSettings>
+#include <QStackedWidget>
+#include <QStandardPaths>
+#include <QString>
+#include <QTextDocument>
+#include <QtGlobal>
+#include <QPainterPath>
+
+#ifdef Q_OS_WIN
+#include <ciso646>
+#include <windows.h>
+#undef ERROR
+#else
+#define LPCWSTR char *
+#endif
+
+#include "api/account.h"
+#include "api/contact.h"
+#include "api/contactmodel.h"
+#include "api/conversationmodel.h"
+
+static const QSize IMAGE_SIZE{128, 128};
+static float CURRENT_SCALING_RATIO{1.0};
+
+#ifdef BETA
+static constexpr bool isBeta = true;
+#else
+static constexpr bool isBeta = false;
+#endif
+
+namespace Utils {
+
+/*
+ * Qml type register.
+ */
+#define QML_REGISTERSINGLETONTYPE(T, MAJ, MIN) \
+ qmlRegisterSingletonType<T>("net.jami.Models", \
+ MAJ, \
+ MIN, \
+ #T, \
+ [](QQmlEngine *e, QJSEngine *se) -> QObject * { \
+ Q_UNUSED(e); \
+ Q_UNUSED(se); \
+ T *obj = new T(); \
+ return obj; \
+ });
+#define QML_REGISTERSINGLETONTYPE_WITH_INSTANCE(T, MAJ, MIN) \
+ qmlRegisterSingletonType<T>("net.jami.Models", \
+ MAJ, \
+ MIN, \
+ #T, \
+ [](QQmlEngine *e, QJSEngine *se) -> QObject * { \
+ Q_UNUSED(e); \
+ Q_UNUSED(se); \
+ return &(T::instance()); \
+ });
+
+#define QML_REGISTERSINGLETONTYPE_URL(URL, T, MAJ, MIN) \
+ qmlRegisterSingletonType(QUrl(URL), "net.jami.Models", MAJ, MIN, #T);
+
+#define QML_REGISTERTYPE(T, MAJ, MIN) qmlRegisterType<T>("net.jami.Models", MAJ, MIN, #T);
+
+#define QML_REGISTERNAMESPACE(T, NAME, MAJ, MIN) \
+ qmlRegisterUncreatableMetaObject(T, "net.jami.Models", MAJ, MIN, NAME, "")
+
+#define QML_REGISTERUNCREATABLE(T, MAJ, MIN) \
+ qmlRegisterUncreatableType<T>("net.jami.Models", \
+ MAJ, \
+ MIN, \
+ #T, \
+ "Don't try to add to a qml definition of " #T);
+
+#define QML_REGISTERUNCREATABLE_IN_NAMESPACE(T, NAMESPACE, MAJ, MIN) \
+ qmlRegisterUncreatableType<NAMESPACE::T>("net.jami.Models", \
+ MAJ, \
+ MIN, \
+ #T, \
+ "Don't try to add to a qml definition of " #T);
+
+/*
+ * System.
+ */
+bool CreateStartupLink(const std::wstring &wstrAppName);
+void DeleteStartupLink(const std::wstring &wstrAppName);
+bool CreateLink(LPCWSTR lpszPathObj, LPCWSTR lpszPathLink);
+bool CheckStartupLink(const std::wstring &wstrAppName);
+void removeOldVersions();
+const char *WinGetEnv(const char *name);
+QString GetRingtonePath();
+QString GenGUID();
+QString GetISODate();
+void InvokeMailto(const QString &subject,
+ const QString &body,
+ const QString &attachement = QString());
+void setStackWidget(QStackedWidget *stack, QWidget *widget);
+void showSystemNotification(QWidget *widget,
+ const QString &message,
+ long delay = 5000,
+ const QString &triggeredAccountId = "");
+void showSystemNotification(QWidget *widget,
+ const QString &sender,
+ const QString &message,
+ long delay = 5000,
+ const QString &triggeredAccountId = "");
+QSize getRealSize(QScreen *screen);
+void forceDeleteAsync(const QString &path);
+QString getChangeLog();
+QString getProjectCredits();
+float getCurrentScalingRatio();
+void setCurrentScalingRatio(float ratio);
+
+/*
+ * Updates.
+ */
+void cleanUpdateFiles();
+void checkForUpdates(bool withUI, QWidget *parent = nullptr);
+void applyUpdates(bool updateToBeta, QWidget *parent = nullptr);
+
+/*
+ * Names.
+ */
+QString bestIdForConversation(const lrc::api::conversation::Info &conv,
+ const lrc::api::ConversationModel &model);
+QString bestIdForAccount(const lrc::api::account::Info &account);
+QString bestNameForAccount(const lrc::api::account::Info &account);
+QString bestIdForContact(const lrc::api::contact::Info &contact);
+QString bestNameForContact(const lrc::api::contact::Info &contact);
+QString bestNameForConversation(const lrc::api::conversation::Info &conv,
+ const lrc::api::ConversationModel &model);
+/*
+ * Returns empty string if only infoHash is available.
+ */
+QString secondBestNameForAccount(const lrc::api::account::Info &account);
+lrc::api::profile::Type profileType(const lrc::api::conversation::Info &conv,
+ const lrc::api::ConversationModel &model);
+
+/*
+ * Interactions.
+ */
+std::string formatTimeString(const std::time_t ×tamp);
+bool isInteractionGenerated(const lrc::api::interaction::Type &interaction);
+bool isContactValid(const QString &contactUid, const lrc::api::ConversationModel &model);
+bool getReplyMessageBox(QWidget *widget, const QString &title, const QString &text);
+
+/*
+ * Image.
+ */
+QString getContactImageString(const QString &accountId, const QString &uid);
+QImage getCirclePhoto(const QImage original, int sizePhoto);
+QImage conversationPhoto(const QString &convUid,
+ const lrc::api::account::Info &accountInfo,
+ bool filtered = false);
+QColor getAvatarColor(const QString &canonicalUri);
+QImage fallbackAvatar(const QSize size,
+ const QString &canonicalUriStr,
+ const QString &letterStr = QString());
+QImage fallbackAvatar(const QSize size, const std::string &alias, const std::string &uri);
+QByteArray QImageToByteArray(QImage image);
+QByteArray QByteArrayFromFile(const QString &filename);
+QPixmap generateTintedPixmap(const QString &filename, QColor color);
+QPixmap generateTintedPixmap(const QPixmap &pix, QColor color);
+QImage scaleAndFrame(const QImage photo, const QSize &size = IMAGE_SIZE);
+QImage accountPhoto(const lrc::api::account::Info &accountInfo, const QSize &size = IMAGE_SIZE);
+QImage cropImage(const QImage &img);
+QPixmap pixmapFromSvg(const QString &svg_resource, const QSize &size);
+QImage setupQRCode(QString ringID, int margin);
+
+/*
+ * Rounded corner.
+ */
+template<typename T>
+void
+fillRoundRectPath(QPainter &painter,
+ const T &brushType,
+ const QRect &rectToDraw,
+ qreal cornerRadius,
+ int xTransFormOffset = 0,
+ int yTransFormOffset = 0)
+{
+ QBrush brush(brushType);
+ brush.setTransform(QTransform::fromTranslate(rectToDraw.x() + xTransFormOffset,
+ rectToDraw.y() + yTransFormOffset));
+ QPainterPath painterPath;
+ painterPath.addRoundRect(rectToDraw, cornerRadius);
+ painter.fillPath(painterPath, brush);
+}
+
+/*
+ * Time.
+ */
+QString formattedTime(int seconds);
+
+/*
+ * Byte to human readable size.
+ */
+QString humanFileSize(qint64 fileSize);
+
+/*
+ * Device plug or unplug enum.
+ */
+enum class DevicePlugStatus { Plugged, Unplugged, Unchanged };
+
+class OneShotDisconnectConnection : public QObject
+{
+ Q_OBJECT
+
+public:
+ explicit OneShotDisconnectConnection(const QObject *sender,
+ const char *signal,
+ QMetaObject::Connection *connection,
+ QObject *parent = nullptr)
+ : QObject(parent)
+ {
+ connection_ = connection;
+ disconnectConnection_ = new QMetaObject::Connection;
+ *disconnectConnection_ = QObject::connect(sender,
+ signal,
+ this,
+ SLOT(slotOneShotDisconnectConnection()));
+ }
+ ~OneShotDisconnectConnection()
+ {
+ if (!connection_) {
+ delete connection_;
+ }
+ if (!disconnectConnection_) {
+ delete disconnectConnection_;
+ }
+ if (!this) {
+ delete this;
+ }
+ }
+
+public slots:
+ void
+ slotOneShotDisconnectConnection()
+ {
+ if (connection_) {
+ QObject::disconnect(*connection_);
+ delete connection_;
+ }
+ if (disconnectConnection_) {
+ QObject::disconnect(*disconnectConnection_);
+ delete disconnectConnection_;
+ }
+ delete this;
+ }
+
+private:
+ QMetaObject::Connection *connection_;
+ QMetaObject::Connection *disconnectConnection_;
+};
+
+template<typename Func1, typename Func2>
+void
+oneShotConnect(const typename QtPrivate::FunctionPointer<Func1>::Object *sender,
+ Func1 signal,
+ Func2 slot)
+{
+ QMetaObject::Connection *const connection = new QMetaObject::Connection;
+ *connection = QObject::connect(sender, signal, slot);
+ QMetaObject::Connection *const disconnectConnection = new QMetaObject::Connection;
+ *disconnectConnection = QObject::connect(sender, signal, [connection, disconnectConnection] {
+ if (connection) {
+ QObject::disconnect(*connection);
+ delete connection;
+ }
+ if (disconnectConnection) {
+ QObject::disconnect(*disconnectConnection);
+ delete disconnectConnection;
+ }
+ });
+}
+
+template<typename Func1, typename Func2>
+void
+oneShotConnect(const typename QtPrivate::FunctionPointer<Func1>::Object *sender,
+ Func1 signal,
+ const typename QtPrivate::FunctionPointer<Func2>::Object *receiver,
+ Func2 slot)
+{
+ QMetaObject::Connection *const connection = new QMetaObject::Connection;
+ *connection = QObject::connect(sender, signal, receiver, slot);
+ QMetaObject::Connection *const disconnectConnection = new QMetaObject::Connection;
+ *disconnectConnection = QObject::connect(sender, signal, [connection, disconnectConnection] {
+ if (connection) {
+ QObject::disconnect(*connection);
+ delete connection;
+ }
+ if (disconnectConnection) {
+ QObject::disconnect(*disconnectConnection);
+ delete disconnectConnection;
+ }
+ });
+}
+
+inline void
+oneShotConnect(const QObject *sender, const char *signal, const QObject *receiver, const char *slot)
+{
+ QMetaObject::Connection *const connection = new QMetaObject::Connection;
+ *connection = QObject::connect(sender, signal, receiver, slot);
+ OneShotDisconnectConnection *disconnectConnection = new OneShotDisconnectConnection(sender,
+ signal,
+ connection);
+ Q_UNUSED(disconnectConnection)
+}
+
+template<class T>
+class Blocker
+{
+ T *blocked;
+ bool previous;
+
+public:
+ Blocker(T *blocked)
+ : blocked(blocked)
+ , previous(blocked->blockSignals(true))
+ {}
+ ~Blocker() { blocked->blockSignals(previous); }
+ T *
+ operator->()
+ {
+ return blocked;
+ }
+};
+
+template<class T>
+inline Blocker<T>
+whileBlocking(T *blocked)
+{
+ return Blocker<T>(blocked);
+}
+
+template<typename T>
+void
+setElidedText(T *object,
+ const QString &text,
+ Qt::TextElideMode mode = Qt::ElideMiddle,
+ int padding = 32)
+{
+ QFontMetrics metrics(object->font());
+ QString clippedText = metrics.elidedText(text, mode, object->width() - padding);
+ object->setText(clippedText);
+}
+
+template<typename E>
+constexpr inline
+ typename std::enable_if<std::is_enum<E>::value, typename std::underlying_type<E>::type>::type
+ toUnderlyingValue(E e) noexcept
+{
+ return static_cast<typename std::underlying_type<E>::type>(e);
+}
+
+template<typename E, typename T>
+constexpr inline
+ typename std::enable_if<std::is_enum<E>::value && std::is_integral<T>::value, E>::type
+ toEnum(T value) noexcept
+{
+ return static_cast<E>(value);
+}
+} // namespace Utils
+
+class UtilsAdapter : public QObject
+{
+ Q_OBJECT
+public:
+ explicit UtilsAdapter(QObject *parent = nullptr)
+ : QObject(parent)
+ {
+ clipboard_ = QApplication::clipboard();
+ }
+ ~UtilsAdapter() {}
+
+ ///Singleton
+ static UtilsAdapter &instance();
+
+ Q_INVOKABLE const QString
+ getChangeLog()
+ {
+ return Utils::getChangeLog();
+ }
+
+ Q_INVOKABLE const QString
+ getProjectCredits()
+ {
+ return Utils::getProjectCredits();
+ }
+
+ Q_INVOKABLE const QString
+ getVersionStr()
+ {
+ return QString(VERSION_STRING);
+ }
+
+ Q_INVOKABLE void
+ setText(QString text)
+ {
+ clipboard_->setText(text, QClipboard::Clipboard);
+ }
+
+ Q_INVOKABLE const QString
+ qStringFromFile(const QString &filename)
+ {
+ return Utils::QByteArrayFromFile(filename);
+ }
+
+ Q_INVOKABLE const QString
+ getStyleSheet(const QString &name, const QString &source)
+ {
+ auto simplifiedCSS = source.simplified().replace("'", "\"");
+ QString s = QString::fromLatin1("(function() {"
+ " var node = document.createElement('style');"
+ " node.id = '%1';"
+ " node.innerHTML = '%2';"
+ " document.head.appendChild(node);"
+ "})()")
+ .arg(name)
+ .arg(simplifiedCSS);
+ return s;
+ }
+
+ Q_INVOKABLE const QString
+ getCachePath()
+ {
+ QDir dataDir(QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation));
+ dataDir.cdUp();
+ return dataDir.absolutePath() + "/jami";
+ }
+ Q_INVOKABLE bool
+ createStartupLink()
+ {
+ return Utils::CreateStartupLink(L"Jami");
+ }
+ Q_INVOKABLE QString
+ GetRingtonePath()
+ {
+ return Utils::GetRingtonePath();
+ }
+ Q_INVOKABLE bool
+ checkStartupLink()
+ {
+ return Utils::CheckStartupLink(L"Jami");
+ }
+
+ Q_INVOKABLE const QString
+ getContactImageString(const QString &accountId, const QString &uid)
+ {
+ return Utils::getContactImageString(accountId, uid);
+ }
+
+ Q_INVOKABLE void removeConversation(const QString &accountId,
+ const QString &uid,
+ bool banContact = false);
+ Q_INVOKABLE void clearConversationHistory(const QString &accountId, const QString &uid);
+ Q_INVOKABLE void setConversationFilter(const QString &filter);
+ Q_INVOKABLE int getTotalUnreadMessages();
+ Q_INVOKABLE int getTotalPendingRequest();
+ Q_INVOKABLE const QString getBestName(const QString &accountId, const QString &uid);
+ Q_INVOKABLE const QString getBestId(const QString &accountId, const QString &uid);
+
+ Q_INVOKABLE const QString getCurrAccId();
+ Q_INVOKABLE const QStringList getCurrAccList();
+ Q_INVOKABLE int getAccountListSize();
+ Q_INVOKABLE void setCurrentCall(const QString &accountId, const QString &convUid);
+ Q_INVOKABLE void startPreviewing(bool force);
+ Q_INVOKABLE void stopPreviewing();
+ Q_INVOKABLE bool hasVideoCall();
+ Q_INVOKABLE const QString getCallId(const QString &accountId, const QString &convUid);
+ Q_INVOKABLE QString getStringUTF8(QString string);
+ Q_INVOKABLE bool validateRegNameForm(const QString ®Name);
+ Q_INVOKABLE QString getRecordQualityString(int value);
+ Q_INVOKABLE QString getCurrentPath();
+ Q_INVOKABLE QString
+ stringSimplifier(QString input)
+ {
+ return input.simplified();
+ }
+
+ Q_INVOKABLE QString
+ toNativeSeparators(QString inputDir)
+ {
+ return QDir::toNativeSeparators(inputDir);
+ }
+
+ Q_INVOKABLE QString
+ toFileInfoName(QString inputFileName)
+ {
+ QFileInfo fi(inputFileName);
+ return fi.fileName();
+ }
+
+ Q_INVOKABLE QString
+ toFileAbsolutepath(QString inputFileName)
+ {
+ QFileInfo fi(inputFileName);
+ return fi.absolutePath();
+ }
+
+ Q_INVOKABLE QString
+ getAbsPath(QString path)
+ {
+#ifdef Q_OS_WIN
+ return path.replace("file:///", "").replace("\n", "").replace("\r", "");
+#else
+ return path.replace("file:///", "/").replace("\n", "").replace("\r", "");
+#endif
+ }
+
+ Q_INVOKABLE QString
+ getCroppedImageBase64FromFile(QString fileName, int size)
+ {
+ auto image = Utils::cropImage(QImage(fileName));
+ auto croppedImage = image.scaled(size,
+ size,
+ Qt::KeepAspectRatioByExpanding,
+ Qt::SmoothTransformation);
+ return QString::fromLatin1(Utils::QImageToByteArray(croppedImage).toBase64().data());
+ }
+
+private:
+ QClipboard *clipboard_;
+};
+#if QT_VERSION >= QT_VERSION_CHECK(5, 8, 0)
+Q_DECLARE_METATYPE(UtilsAdapter *)
+#endif