blob: f97d0d61cd21d6d6c85e41ac0248b57952f872ff [file] [log] [blame]
Sébastien Blin1f915762020-08-03 13:27:42 -04001/*
2 * Copyright (C) 2015-2020 by Savoir-faire Linux
3 * Author: Edric Ladent Milaret <edric.ladent-milaret@savoirfairelinux.com>
4 * Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>
5 * Author: Isa Nanic <isa.nanic@savoirfairelinux.com>
6 * Author: Mingrui Zhang <mingrui.zhang@savoirfairelinux.com>
agsantos655d8e22020-08-10 17:36:47 -04007 * Author: Aline Gondim Santos <aline.gondimsantos@savoirfairelinux.com>
Sébastien Blin1f915762020-08-03 13:27:42 -04008 *
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 3 of the License, or
12 * (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program. If not, see <http://www.gnu.org/licenses/>.
21 */
22
23#pragma once
24
25#include "version.h"
26
27#include <string>
28
29#include <QClipboard>
30#include <QCryptographicHash>
31#include <QDir>
32#include <QApplication>
33#include <QImage>
34#include <QItemDelegate>
35#include <QLabel>
36#include <QListWidget>
37#include <QMessageBox>
38#include <QQmlEngine>
39#include <QSettings>
40#include <QStackedWidget>
41#include <QStandardPaths>
42#include <QString>
43#include <QTextDocument>
44#include <QtGlobal>
45#include <QPainterPath>
46
47#ifdef Q_OS_WIN
48#include <ciso646>
49#include <windows.h>
50#undef ERROR
51#else
52#define LPCWSTR char *
53#endif
54
55#include "api/account.h"
56#include "api/contact.h"
57#include "api/contactmodel.h"
58#include "api/conversationmodel.h"
59
60static const QSize IMAGE_SIZE{128, 128};
61static float CURRENT_SCALING_RATIO{1.0};
62
63#ifdef BETA
64static constexpr bool isBeta = true;
65#else
66static constexpr bool isBeta = false;
67#endif
68
69namespace Utils {
70
71/*
72 * Qml type register.
73 */
74#define QML_REGISTERSINGLETONTYPE(T, MAJ, MIN) \
75 qmlRegisterSingletonType<T>("net.jami.Models", \
76 MAJ, \
77 MIN, \
78 #T, \
79 [](QQmlEngine *e, QJSEngine *se) -> QObject * { \
80 Q_UNUSED(e); \
81 Q_UNUSED(se); \
82 T *obj = new T(); \
83 return obj; \
84 });
85#define QML_REGISTERSINGLETONTYPE_WITH_INSTANCE(T, MAJ, MIN) \
86 qmlRegisterSingletonType<T>("net.jami.Models", \
87 MAJ, \
88 MIN, \
89 #T, \
90 [](QQmlEngine *e, QJSEngine *se) -> QObject * { \
91 Q_UNUSED(e); \
92 Q_UNUSED(se); \
93 return &(T::instance()); \
94 });
95
96#define QML_REGISTERSINGLETONTYPE_URL(URL, T, MAJ, MIN) \
97 qmlRegisterSingletonType(QUrl(URL), "net.jami.Models", MAJ, MIN, #T);
98
99#define QML_REGISTERTYPE(T, MAJ, MIN) qmlRegisterType<T>("net.jami.Models", MAJ, MIN, #T);
100
101#define QML_REGISTERNAMESPACE(T, NAME, MAJ, MIN) \
102 qmlRegisterUncreatableMetaObject(T, "net.jami.Models", MAJ, MIN, NAME, "")
103
104#define QML_REGISTERUNCREATABLE(T, MAJ, MIN) \
105 qmlRegisterUncreatableType<T>("net.jami.Models", \
106 MAJ, \
107 MIN, \
108 #T, \
109 "Don't try to add to a qml definition of " #T);
110
111#define QML_REGISTERUNCREATABLE_IN_NAMESPACE(T, NAMESPACE, MAJ, MIN) \
112 qmlRegisterUncreatableType<NAMESPACE::T>("net.jami.Models", \
113 MAJ, \
114 MIN, \
115 #T, \
116 "Don't try to add to a qml definition of " #T);
117
118/*
119 * System.
120 */
121bool CreateStartupLink(const std::wstring &wstrAppName);
122void DeleteStartupLink(const std::wstring &wstrAppName);
123bool CreateLink(LPCWSTR lpszPathObj, LPCWSTR lpszPathLink);
124bool CheckStartupLink(const std::wstring &wstrAppName);
125void removeOldVersions();
126const char *WinGetEnv(const char *name);
127QString GetRingtonePath();
128QString GenGUID();
129QString GetISODate();
130void InvokeMailto(const QString &subject,
131 const QString &body,
132 const QString &attachement = QString());
133void setStackWidget(QStackedWidget *stack, QWidget *widget);
134void showSystemNotification(QWidget *widget,
135 const QString &message,
136 long delay = 5000,
137 const QString &triggeredAccountId = "");
138void showSystemNotification(QWidget *widget,
139 const QString &sender,
140 const QString &message,
141 long delay = 5000,
142 const QString &triggeredAccountId = "");
143QSize getRealSize(QScreen *screen);
144void forceDeleteAsync(const QString &path);
145QString getChangeLog();
146QString getProjectCredits();
147float getCurrentScalingRatio();
148void setCurrentScalingRatio(float ratio);
149
150/*
151 * Updates.
152 */
153void cleanUpdateFiles();
154void checkForUpdates(bool withUI, QWidget *parent = nullptr);
155void applyUpdates(bool updateToBeta, QWidget *parent = nullptr);
156
157/*
158 * Names.
159 */
160QString bestIdForConversation(const lrc::api::conversation::Info &conv,
161 const lrc::api::ConversationModel &model);
162QString bestIdForAccount(const lrc::api::account::Info &account);
163QString bestNameForAccount(const lrc::api::account::Info &account);
164QString bestIdForContact(const lrc::api::contact::Info &contact);
165QString bestNameForContact(const lrc::api::contact::Info &contact);
166QString bestNameForConversation(const lrc::api::conversation::Info &conv,
167 const lrc::api::ConversationModel &model);
168/*
169 * Returns empty string if only infoHash is available.
170 */
171QString secondBestNameForAccount(const lrc::api::account::Info &account);
172lrc::api::profile::Type profileType(const lrc::api::conversation::Info &conv,
173 const lrc::api::ConversationModel &model);
174
175/*
176 * Interactions.
177 */
178std::string formatTimeString(const std::time_t &timestamp);
179bool isInteractionGenerated(const lrc::api::interaction::Type &interaction);
180bool isContactValid(const QString &contactUid, const lrc::api::ConversationModel &model);
181bool getReplyMessageBox(QWidget *widget, const QString &title, const QString &text);
182
183/*
184 * Image.
185 */
186QString getContactImageString(const QString &accountId, const QString &uid);
187QImage getCirclePhoto(const QImage original, int sizePhoto);
188QImage conversationPhoto(const QString &convUid,
189 const lrc::api::account::Info &accountInfo,
190 bool filtered = false);
191QColor getAvatarColor(const QString &canonicalUri);
192QImage fallbackAvatar(const QSize size,
193 const QString &canonicalUriStr,
194 const QString &letterStr = QString());
195QImage fallbackAvatar(const QSize size, const std::string &alias, const std::string &uri);
196QByteArray QImageToByteArray(QImage image);
197QByteArray QByteArrayFromFile(const QString &filename);
198QPixmap generateTintedPixmap(const QString &filename, QColor color);
199QPixmap generateTintedPixmap(const QPixmap &pix, QColor color);
200QImage scaleAndFrame(const QImage photo, const QSize &size = IMAGE_SIZE);
201QImage accountPhoto(const lrc::api::account::Info &accountInfo, const QSize &size = IMAGE_SIZE);
202QImage cropImage(const QImage &img);
203QPixmap pixmapFromSvg(const QString &svg_resource, const QSize &size);
204QImage setupQRCode(QString ringID, int margin);
205
206/*
207 * Rounded corner.
208 */
209template<typename T>
210void
211fillRoundRectPath(QPainter &painter,
212 const T &brushType,
213 const QRect &rectToDraw,
214 qreal cornerRadius,
215 int xTransFormOffset = 0,
216 int yTransFormOffset = 0)
217{
218 QBrush brush(brushType);
219 brush.setTransform(QTransform::fromTranslate(rectToDraw.x() + xTransFormOffset,
220 rectToDraw.y() + yTransFormOffset));
221 QPainterPath painterPath;
222 painterPath.addRoundRect(rectToDraw, cornerRadius);
223 painter.fillPath(painterPath, brush);
224}
225
226/*
227 * Time.
228 */
229QString formattedTime(int seconds);
230
231/*
232 * Byte to human readable size.
233 */
234QString humanFileSize(qint64 fileSize);
235
236/*
237 * Device plug or unplug enum.
238 */
239enum class DevicePlugStatus { Plugged, Unplugged, Unchanged };
240
241class OneShotDisconnectConnection : public QObject
242{
243 Q_OBJECT
244
245public:
246 explicit OneShotDisconnectConnection(const QObject *sender,
247 const char *signal,
248 QMetaObject::Connection *connection,
249 QObject *parent = nullptr)
250 : QObject(parent)
251 {
252 connection_ = connection;
253 disconnectConnection_ = new QMetaObject::Connection;
254 *disconnectConnection_ = QObject::connect(sender,
255 signal,
256 this,
257 SLOT(slotOneShotDisconnectConnection()));
258 }
259 ~OneShotDisconnectConnection()
260 {
261 if (!connection_) {
262 delete connection_;
263 }
264 if (!disconnectConnection_) {
265 delete disconnectConnection_;
266 }
267 if (!this) {
268 delete this;
269 }
270 }
271
272public slots:
273 void
274 slotOneShotDisconnectConnection()
275 {
276 if (connection_) {
277 QObject::disconnect(*connection_);
278 delete connection_;
279 }
280 if (disconnectConnection_) {
281 QObject::disconnect(*disconnectConnection_);
282 delete disconnectConnection_;
283 }
284 delete this;
285 }
286
287private:
288 QMetaObject::Connection *connection_;
289 QMetaObject::Connection *disconnectConnection_;
290};
291
292template<typename Func1, typename Func2>
293void
294oneShotConnect(const typename QtPrivate::FunctionPointer<Func1>::Object *sender,
295 Func1 signal,
296 Func2 slot)
297{
298 QMetaObject::Connection *const connection = new QMetaObject::Connection;
299 *connection = QObject::connect(sender, signal, slot);
300 QMetaObject::Connection *const disconnectConnection = new QMetaObject::Connection;
301 *disconnectConnection = QObject::connect(sender, signal, [connection, disconnectConnection] {
302 if (connection) {
303 QObject::disconnect(*connection);
304 delete connection;
305 }
306 if (disconnectConnection) {
307 QObject::disconnect(*disconnectConnection);
308 delete disconnectConnection;
309 }
310 });
311}
312
313template<typename Func1, typename Func2>
314void
315oneShotConnect(const typename QtPrivate::FunctionPointer<Func1>::Object *sender,
316 Func1 signal,
317 const typename QtPrivate::FunctionPointer<Func2>::Object *receiver,
318 Func2 slot)
319{
320 QMetaObject::Connection *const connection = new QMetaObject::Connection;
321 *connection = QObject::connect(sender, signal, receiver, slot);
322 QMetaObject::Connection *const disconnectConnection = new QMetaObject::Connection;
323 *disconnectConnection = QObject::connect(sender, signal, [connection, disconnectConnection] {
324 if (connection) {
325 QObject::disconnect(*connection);
326 delete connection;
327 }
328 if (disconnectConnection) {
329 QObject::disconnect(*disconnectConnection);
330 delete disconnectConnection;
331 }
332 });
333}
334
335inline void
336oneShotConnect(const QObject *sender, const char *signal, const QObject *receiver, const char *slot)
337{
338 QMetaObject::Connection *const connection = new QMetaObject::Connection;
339 *connection = QObject::connect(sender, signal, receiver, slot);
340 OneShotDisconnectConnection *disconnectConnection = new OneShotDisconnectConnection(sender,
341 signal,
342 connection);
343 Q_UNUSED(disconnectConnection)
344}
345
346template<class T>
347class Blocker
348{
349 T *blocked;
350 bool previous;
351
352public:
353 Blocker(T *blocked)
354 : blocked(blocked)
355 , previous(blocked->blockSignals(true))
356 {}
357 ~Blocker() { blocked->blockSignals(previous); }
358 T *
359 operator->()
360 {
361 return blocked;
362 }
363};
364
365template<class T>
366inline Blocker<T>
367whileBlocking(T *blocked)
368{
369 return Blocker<T>(blocked);
370}
371
372template<typename T>
373void
374setElidedText(T *object,
375 const QString &text,
376 Qt::TextElideMode mode = Qt::ElideMiddle,
377 int padding = 32)
378{
379 QFontMetrics metrics(object->font());
380 QString clippedText = metrics.elidedText(text, mode, object->width() - padding);
381 object->setText(clippedText);
382}
383
384template<typename E>
385constexpr inline
386 typename std::enable_if<std::is_enum<E>::value, typename std::underlying_type<E>::type>::type
387 toUnderlyingValue(E e) noexcept
388{
389 return static_cast<typename std::underlying_type<E>::type>(e);
390}
391
392template<typename E, typename T>
393constexpr inline
394 typename std::enable_if<std::is_enum<E>::value && std::is_integral<T>::value, E>::type
395 toEnum(T value) noexcept
396{
397 return static_cast<E>(value);
398}
399} // namespace Utils
400
401class UtilsAdapter : public QObject
402{
403 Q_OBJECT
404public:
405 explicit UtilsAdapter(QObject *parent = nullptr)
406 : QObject(parent)
407 {
408 clipboard_ = QApplication::clipboard();
409 }
410 ~UtilsAdapter() {}
411
412 ///Singleton
413 static UtilsAdapter &instance();
414
415 Q_INVOKABLE const QString
416 getChangeLog()
417 {
418 return Utils::getChangeLog();
419 }
420
421 Q_INVOKABLE const QString
422 getProjectCredits()
423 {
424 return Utils::getProjectCredits();
425 }
426
427 Q_INVOKABLE const QString
428 getVersionStr()
429 {
430 return QString(VERSION_STRING);
431 }
432
433 Q_INVOKABLE void
434 setText(QString text)
435 {
436 clipboard_->setText(text, QClipboard::Clipboard);
437 }
438
439 Q_INVOKABLE const QString
440 qStringFromFile(const QString &filename)
441 {
442 return Utils::QByteArrayFromFile(filename);
443 }
444
445 Q_INVOKABLE const QString
446 getStyleSheet(const QString &name, const QString &source)
447 {
448 auto simplifiedCSS = source.simplified().replace("'", "\"");
449 QString s = QString::fromLatin1("(function() {"
450 " var node = document.createElement('style');"
451 " node.id = '%1';"
452 " node.innerHTML = '%2';"
453 " document.head.appendChild(node);"
454 "})()")
455 .arg(name)
456 .arg(simplifiedCSS);
457 return s;
458 }
459
460 Q_INVOKABLE const QString
461 getCachePath()
462 {
463 QDir dataDir(QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation));
464 dataDir.cdUp();
465 return dataDir.absolutePath() + "/jami";
466 }
467 Q_INVOKABLE bool
468 createStartupLink()
469 {
470 return Utils::CreateStartupLink(L"Jami");
471 }
472 Q_INVOKABLE QString
473 GetRingtonePath()
474 {
475 return Utils::GetRingtonePath();
476 }
477 Q_INVOKABLE bool
478 checkStartupLink()
479 {
480 return Utils::CheckStartupLink(L"Jami");
481 }
482
483 Q_INVOKABLE const QString
484 getContactImageString(const QString &accountId, const QString &uid)
485 {
486 return Utils::getContactImageString(accountId, uid);
487 }
488
489 Q_INVOKABLE void removeConversation(const QString &accountId,
490 const QString &uid,
491 bool banContact = false);
492 Q_INVOKABLE void clearConversationHistory(const QString &accountId, const QString &uid);
493 Q_INVOKABLE void setConversationFilter(const QString &filter);
494 Q_INVOKABLE int getTotalUnreadMessages();
495 Q_INVOKABLE int getTotalPendingRequest();
496 Q_INVOKABLE const QString getBestName(const QString &accountId, const QString &uid);
497 Q_INVOKABLE const QString getBestId(const QString &accountId, const QString &uid);
498
499 Q_INVOKABLE const QString getCurrAccId();
500 Q_INVOKABLE const QStringList getCurrAccList();
501 Q_INVOKABLE int getAccountListSize();
502 Q_INVOKABLE void setCurrentCall(const QString &accountId, const QString &convUid);
503 Q_INVOKABLE void startPreviewing(bool force);
504 Q_INVOKABLE void stopPreviewing();
505 Q_INVOKABLE bool hasVideoCall();
506 Q_INVOKABLE const QString getCallId(const QString &accountId, const QString &convUid);
507 Q_INVOKABLE QString getStringUTF8(QString string);
508 Q_INVOKABLE bool validateRegNameForm(const QString &regName);
509 Q_INVOKABLE QString getRecordQualityString(int value);
510 Q_INVOKABLE QString getCurrentPath();
511 Q_INVOKABLE QString
512 stringSimplifier(QString input)
513 {
514 return input.simplified();
515 }
516
517 Q_INVOKABLE QString
518 toNativeSeparators(QString inputDir)
519 {
520 return QDir::toNativeSeparators(inputDir);
521 }
522
523 Q_INVOKABLE QString
524 toFileInfoName(QString inputFileName)
525 {
526 QFileInfo fi(inputFileName);
527 return fi.fileName();
528 }
529
530 Q_INVOKABLE QString
531 toFileAbsolutepath(QString inputFileName)
532 {
533 QFileInfo fi(inputFileName);
534 return fi.absolutePath();
535 }
536
537 Q_INVOKABLE QString
538 getAbsPath(QString path)
539 {
540#ifdef Q_OS_WIN
541 return path.replace("file:///", "").replace("\n", "").replace("\r", "");
542#else
543 return path.replace("file:///", "/").replace("\n", "").replace("\r", "");
544#endif
545 }
546
547 Q_INVOKABLE QString
548 getCroppedImageBase64FromFile(QString fileName, int size)
549 {
550 auto image = Utils::cropImage(QImage(fileName));
551 auto croppedImage = image.scaled(size,
552 size,
553 Qt::KeepAspectRatioByExpanding,
554 Qt::SmoothTransformation);
555 return QString::fromLatin1(Utils::QImageToByteArray(croppedImage).toBase64().data());
556 }
557
agsantos655d8e22020-08-10 17:36:47 -0400558 Q_INVOKABLE bool checkShowPluginsButton();
559
Sébastien Blin1f915762020-08-03 13:27:42 -0400560private:
561 QClipboard *clipboard_;
562};
Sébastien Blin1f915762020-08-03 13:27:42 -0400563Q_DECLARE_METATYPE(UtilsAdapter *)