From 7c32056918281032e5e27062b67eaea4fb2eef51 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Mon, 23 Sep 2019 15:09:15 +0300
Subject: [PATCH 001/281] working on file upload

---
 core/account.h            |   1 +
 core/networkaccess.cpp    | 391 +++++++++++++++++++++++++-------------
 core/networkaccess.h      |  21 +-
 core/squawk.cpp           |   8 +-
 core/squawk.h             |   3 +
 core/storage.cpp          |  29 ++-
 core/storage.h            |   1 +
 main.cpp                  |   5 +-
 ui/squawk.cpp             |   8 +-
 ui/squawk.h               |   2 +
 ui/widgets/conversation.h |   1 +
 11 files changed, 326 insertions(+), 144 deletions(-)

diff --git a/core/account.h b/core/account.h
index 2dda75c..f3dd4c6 100644
--- a/core/account.h
+++ b/core/account.h
@@ -30,6 +30,7 @@
 #include <QXmppClient.h>
 #include <QXmppBookmarkManager.h>
 #include <QXmppBookmarkSet.h>
+#include <QXmppHttpUploadIq.h>
 #include "../global.h"
 #include "contact.h"
 #include "conference.h"
diff --git a/core/networkaccess.cpp b/core/networkaccess.cpp
index 002f9d7..4863535 100644
--- a/core/networkaccess.cpp
+++ b/core/networkaccess.cpp
@@ -23,7 +23,8 @@ Core::NetworkAccess::NetworkAccess(QObject* parent):
     running(false),
     manager(0),
     files("files"),
-    downloads()
+    downloads(),
+    uploads()
 {
 }
 
@@ -34,9 +35,9 @@ Core::NetworkAccess::~NetworkAccess()
 
 void Core::NetworkAccess::fileLocalPathRequest(const QString& messageId, const QString& url)
 {
-    std::map<QString, Download*>::iterator itr = downloads.find(url);
+    std::map<QString, Transfer*>::iterator itr = downloads.find(url);
     if (itr != downloads.end()) {
-        Download* dwn = itr->second;
+        Transfer* dwn = itr->second;
         std::set<QString>::const_iterator mItr = dwn->messages.find(messageId);
         if (mItr == dwn->messages.end()) {
             dwn->messages.insert(messageId);
@@ -63,9 +64,9 @@ void Core::NetworkAccess::fileLocalPathRequest(const QString& messageId, const Q
 
 void Core::NetworkAccess::downladFileRequest(const QString& messageId, const QString& url)
 {
-    std::map<QString, Download*>::iterator itr = downloads.find(url);
+    std::map<QString, Transfer*>::iterator itr = downloads.find(url);
     if (itr != downloads.end()) {
-        Download* dwn = itr->second;
+        Transfer* dwn = itr->second;
         std::set<QString>::const_iterator mItr = dwn->messages.find(messageId);
         if (mItr == dwn->messages.end()) {
             dwn->messages.insert(messageId);
@@ -85,7 +86,7 @@ void Core::NetworkAccess::downladFileRequest(const QString& messageId, const QSt
             startDownload(messageId, url);
         } catch (Archive::Unknown e) {
             qDebug() << "Error requesting file path:" << e.what();
-            startDownload(messageId, url);
+            emit downloadFileError(messageId, QString("Database error: ") + e.what());
         }
     }
 }
@@ -107,7 +108,7 @@ void Core::NetworkAccess::stop()
         manager = 0;
         running = false;
         
-        for (std::map<QString, Download*>::const_iterator itr = downloads.begin(), end = downloads.end(); itr != end; ++itr) {
+        for (std::map<QString, Transfer*>::const_iterator itr = downloads.begin(), end = downloads.end(); itr != end; ++itr) {
             itr->second->success = false;
             itr->second->reply->abort();        //assuming it's gonna call onRequestFinished slot
         }
@@ -118,11 +119,11 @@ void Core::NetworkAccess::onDownloadProgress(qint64 bytesReceived, qint64 bytesT
 {
     QNetworkReply* rpl = static_cast<QNetworkReply*>(sender());
     QString url = rpl->url().toString();
-    std::map<QString, Download*>::const_iterator itr = downloads.find(url);
+    std::map<QString, Transfer*>::const_iterator itr = downloads.find(url);
     if (itr == downloads.end()) {
         qDebug() << "an error downloading" << url << ": the request had some progress but seems like noone is waiting for it, skipping";
     } else {
-        Download* dwn = itr->second;
+        Transfer* dwn = itr->second;
         qreal received = bytesReceived;
         qreal total = bytesTotal;
         qreal progress = received/total;
@@ -133,132 +134,18 @@ void Core::NetworkAccess::onDownloadProgress(qint64 bytesReceived, qint64 bytesT
     }
 }
 
-void Core::NetworkAccess::onRequestError(QNetworkReply::NetworkError code)
+void Core::NetworkAccess::onDownloadError(QNetworkReply::NetworkError code)
 {
     QNetworkReply* rpl = static_cast<QNetworkReply*>(sender());
     QString url = rpl->url().toString();
-    std::map<QString, Download*>::const_iterator itr = downloads.find(url);
+    std::map<QString, Transfer*>::const_iterator itr = downloads.find(url);
     if (itr == downloads.end()) {
         qDebug() << "an error downloading" << url << ": the request is reporting an error but seems like noone is waiting for it, skipping";
     } else {
-        QString errorText;
-        switch (code) {
-            case QNetworkReply::NoError:
-                //this never is supposed to happen
-                break;
-
-        // network layer errors [relating to the destination server] (1-99):
-            case QNetworkReply::ConnectionRefusedError:
-                errorText = "Connection refused";
-                break;
-            case QNetworkReply::RemoteHostClosedError:
-                errorText = "Remote server closed the connection";
-                break;
-            case QNetworkReply::HostNotFoundError:
-                errorText = "Remote host is not found";
-                break;
-            case QNetworkReply::TimeoutError:
-                errorText = "Connection was closed because it timed out";
-                break;
-            case QNetworkReply::OperationCanceledError:
-                //this means I closed it myself by abort() or close(), don't think I need to notify here
-                break;
-            case QNetworkReply::SslHandshakeFailedError:
-                errorText = "Security error";           //TODO need to handle sslErrors signal to get a better description here
-                break;
-            case QNetworkReply::TemporaryNetworkFailureError:
-                //this means the connection is lost by opened route, but it's going to be resumed, not sure I need to notify
-                break;
-            case QNetworkReply::NetworkSessionFailedError:
-                errorText = "Outgoing connection problem";
-                break;
-            case QNetworkReply::BackgroundRequestNotAllowedError:
-                errorText = "Background request is not allowed";
-                break;
-            case QNetworkReply::TooManyRedirectsError:
-                errorText = "The request was  redirected too many times";
-                break;
-            case QNetworkReply::InsecureRedirectError:
-                errorText = "The request was redirected to insecure connection";
-                break;
-            case QNetworkReply::UnknownNetworkError:
-                errorText = "Unknown network error";
-                break;
-
-        // proxy errors (101-199):
-            case QNetworkReply::ProxyConnectionRefusedError:
-                errorText = "The connection to the proxy server was refused";
-                break;
-            case QNetworkReply::ProxyConnectionClosedError:
-                errorText = "Proxy server closed the connection";
-                break;
-            case QNetworkReply::ProxyNotFoundError:
-                errorText = "Proxy host was not found";
-                break;
-            case QNetworkReply::ProxyTimeoutError:
-                errorText = "Connection to the proxy server was closed because it timed out";
-                break;
-            case QNetworkReply::ProxyAuthenticationRequiredError:
-                errorText = "Couldn't connect to proxy server, authentication is required";
-                break;
-            case QNetworkReply::UnknownProxyError:
-                errorText = "Unknown proxy error";
-                break;
-
-        // content errors (201-299):
-            case QNetworkReply::ContentAccessDenied:
-                errorText = "The access to file is denied";
-                break;
-            case QNetworkReply::ContentOperationNotPermittedError:
-                errorText = "The operation over requesting file is not permitted";
-                break;
-            case QNetworkReply::ContentNotFoundError:
-                errorText = "The file was not found";
-                break;
-            case QNetworkReply::AuthenticationRequiredError:
-                errorText = "Couldn't access the file, authentication is required";
-                break;
-            case QNetworkReply::ContentReSendError:
-                errorText = "Sending error, one more attempt will probably solve this problem";
-                break;
-            case QNetworkReply::ContentConflictError:
-                errorText = "The request could not be completed due to a conflict with the current state of the resource";
-                break;
-            case QNetworkReply::ContentGoneError:
-                errorText = "The requested resource is no longer available at the server";
-                break;
-            case QNetworkReply::UnknownContentError:
-                errorText = "Unknown content error";
-                break;
-
-        // protocol errors
-            case QNetworkReply::ProtocolUnknownError:
-                errorText = "Unknown protocol error";
-                break;
-            case QNetworkReply::ProtocolInvalidOperationError:
-                errorText = "Requested operation is not permitted in this protocol";
-                break;
-            case QNetworkReply::ProtocolFailure:
-                errorText = "Low level protocol error";
-                break;
-
-        // Server side errors (401-499)
-            case QNetworkReply::InternalServerError:
-                errorText = "Internal server error";
-                break;
-            case QNetworkReply::OperationNotImplementedError:
-                errorText = "Server doesn't support requested operation";
-                break;
-            case QNetworkReply::ServiceUnavailableError:
-                errorText = "The server is not available for this operation right now";
-                break;
-            case QNetworkReply::UnknownServerError:
-                errorText = "Unknown server error";
-                break;
-        }
+        QString errorText = getErrorText(code);
         if (errorText.size() > 0) {
             itr->second->success = false;
-            Download* dwn = itr->second;
+            Transfer* dwn = itr->second;
             for (std::set<QString>::const_iterator mItr = dwn->messages.begin(), end = dwn->messages.end(); mItr != end; ++mItr) {
                 emit downloadFileError(*mItr, errorText);
             }
@@ -266,16 +153,137 @@ void Core::NetworkAccess::onRequestError(QNetworkReply::NetworkError code)
     }
 }
 
-void Core::NetworkAccess::onRequestFinished()
+QString Core::NetworkAccess::getErrorText(QNetworkReply::NetworkError code)
+{
+    QString errorText("");
+    switch (code) {
+        case QNetworkReply::NoError:
+            //this never is supposed to happen
+            break;
+
+    // network layer errors [relating to the destination server] (1-99):
+        case QNetworkReply::ConnectionRefusedError:
+            errorText = "Connection refused";
+            break;
+        case QNetworkReply::RemoteHostClosedError:
+            errorText = "Remote server closed the connection";
+            break;
+        case QNetworkReply::HostNotFoundError:
+            errorText = "Remote host is not found";
+            break;
+        case QNetworkReply::TimeoutError:
+            errorText = "Connection was closed because it timed out";
+            break;
+        case QNetworkReply::OperationCanceledError:
+            //this means I closed it myself by abort() or close(), don't think I need to notify here
+            break;
+        case QNetworkReply::SslHandshakeFailedError:
+            errorText = "Security error";           //TODO need to handle sslErrors signal to get a better description here
+            break;
+        case QNetworkReply::TemporaryNetworkFailureError:
+            //this means the connection is lost by opened route, but it's going to be resumed, not sure I need to notify
+            break;
+        case QNetworkReply::NetworkSessionFailedError:
+            errorText = "Outgoing connection problem";
+            break;
+        case QNetworkReply::BackgroundRequestNotAllowedError:
+            errorText = "Background request is not allowed";
+            break;
+        case QNetworkReply::TooManyRedirectsError:
+            errorText = "The request was  redirected too many times";
+            break;
+        case QNetworkReply::InsecureRedirectError:
+            errorText = "The request was redirected to insecure connection";
+            break;
+        case QNetworkReply::UnknownNetworkError:
+            errorText = "Unknown network error";
+            break;
+
+    // proxy errors (101-199):
+        case QNetworkReply::ProxyConnectionRefusedError:
+            errorText = "The connection to the proxy server was refused";
+            break;
+        case QNetworkReply::ProxyConnectionClosedError:
+            errorText = "Proxy server closed the connection";
+            break;
+        case QNetworkReply::ProxyNotFoundError:
+            errorText = "Proxy host was not found";
+            break;
+        case QNetworkReply::ProxyTimeoutError:
+            errorText = "Connection to the proxy server was closed because it timed out";
+            break;
+        case QNetworkReply::ProxyAuthenticationRequiredError:
+            errorText = "Couldn't connect to proxy server, authentication is required";
+            break;
+        case QNetworkReply::UnknownProxyError:
+            errorText = "Unknown proxy error";
+            break;
+
+    // content errors (201-299):
+        case QNetworkReply::ContentAccessDenied:
+            errorText = "The access to file is denied";
+            break;
+        case QNetworkReply::ContentOperationNotPermittedError:
+            errorText = "The operation over requesting file is not permitted";
+            break;
+        case QNetworkReply::ContentNotFoundError:
+            errorText = "The file was not found";
+            break;
+        case QNetworkReply::AuthenticationRequiredError:
+            errorText = "Couldn't access the file, authentication is required";
+            break;
+        case QNetworkReply::ContentReSendError:
+            errorText = "Sending error, one more attempt will probably solve this problem";
+            break;
+        case QNetworkReply::ContentConflictError:
+            errorText = "The request could not be completed due to a conflict with the current state of the resource";
+            break;
+        case QNetworkReply::ContentGoneError:
+            errorText = "The requested resource is no longer available at the server";
+            break;
+        case QNetworkReply::UnknownContentError:
+            errorText = "Unknown content error";
+            break;
+
+    // protocol errors
+        case QNetworkReply::ProtocolUnknownError:
+            errorText = "Unknown protocol error";
+            break;
+        case QNetworkReply::ProtocolInvalidOperationError:
+            errorText = "Requested operation is not permitted in this protocol";
+            break;
+        case QNetworkReply::ProtocolFailure:
+            errorText = "Low level protocol error";
+            break;
+
+    // Server side errors (401-499)
+        case QNetworkReply::InternalServerError:
+            errorText = "Internal server error";
+            break;
+        case QNetworkReply::OperationNotImplementedError:
+            errorText = "Server doesn't support requested operation";
+            break;
+        case QNetworkReply::ServiceUnavailableError:
+            errorText = "The server is not available for this operation right now";
+            break;
+        case QNetworkReply::UnknownServerError:
+            errorText = "Unknown server error";
+            break;
+    }
+    return errorText;
+}
+
+
+void Core::NetworkAccess::onDownloadFinished()
 {
     QString path("");
     QNetworkReply* rpl = static_cast<QNetworkReply*>(sender());
     QString url = rpl->url().toString();
-    std::map<QString, Download*>::const_iterator itr = downloads.find(url);
+    std::map<QString, Transfer*>::const_iterator itr = downloads.find(url);
     if (itr == downloads.end()) {
         qDebug() << "an error downloading" << url << ": the request is done but seems like noone is waiting for it, skipping";
     } else {
-        Download* dwn = itr->second;
+        Transfer* dwn = itr->second;
         if (dwn->success) {
             qDebug() << "download success for" << url;
             QStringList hops = url.split("/");
@@ -320,13 +328,130 @@ void Core::NetworkAccess::onRequestFinished()
 
 void Core::NetworkAccess::startDownload(const QString& messageId, const QString& url)
 {
-    Download* dwn = new Download({{messageId}, 0, 0, true});
+    Transfer* dwn = new Transfer({{messageId}, 0, 0, true, "", 0});
     QNetworkRequest req(url);
     dwn->reply = manager->get(req);
     connect(dwn->reply, SIGNAL(downloadProgress(qint64, qint64)), SLOT(onDownloadProgress(qint64, qint64)));
-    connect(dwn->reply, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(onRequestError(QNetworkReply::NetworkError)));
-    connect(dwn->reply, SIGNAL(finished()), SLOT(onRequestFinished()));
+    connect(dwn->reply, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(onDownloadError(QNetworkReply::NetworkError)));
+    connect(dwn->reply, SIGNAL(finished()), SLOT(onDownloadFinished()));
     downloads.insert(std::make_pair(url, dwn));
     emit downloadFileProgress(messageId, 0);
 }
 
+void Core::NetworkAccess::onUploadError(QNetworkReply::NetworkError code)
+{
+    QNetworkReply* rpl = static_cast<QNetworkReply*>(sender());
+    QString url = rpl->url().toString();
+    std::map<QString, Transfer*>::const_iterator itr = uploads.find(url);
+    if (itr == uploads.end()) {
+        qDebug() << "an error uploading" << url << ": the request is reporting an error but seems like noone is waiting for it, skipping";
+    } else {
+        QString errorText = getErrorText(code);
+        if (errorText.size() > 0) {
+            itr->second->success = false;
+            Transfer* upl = itr->second;
+            for (std::set<QString>::const_iterator mItr = upl->messages.begin(), end = upl->messages.end(); mItr != end; ++mItr) {
+                emit uploadFileError(*mItr, errorText);
+            }
+        }
+    }
+}
+
+void Core::NetworkAccess::onUploadFinished()
+{
+    QNetworkReply* rpl = static_cast<QNetworkReply*>(sender());
+    QString url = rpl->url().toString();
+    std::map<QString, Transfer*>::const_iterator itr = uploads.find(url);
+    if (itr == downloads.end()) {
+        qDebug() << "an error uploading" << url << ": the request is done but seems like noone is waiting for it, skipping";
+    } else {
+        Transfer* upl = itr->second;
+        if (upl->success) {
+            qDebug() << "upload success for" << url;
+            files.addRecord(url, upl->path);
+        
+            for (std::set<QString>::const_iterator mItr = upl->messages.begin(), end = upl->messages.end(); mItr != end; ++mItr) {
+                emit fileLocalPathResponse(*mItr, upl->path);
+            }
+        }
+        
+        upl->reply->deleteLater();
+        upl->file->close();
+        upl->file->deleteLater();
+        delete upl;
+        uploads.erase(itr);
+    }
+}
+
+void Core::NetworkAccess::onUploadProgress(qint64 bytesReceived, qint64 bytesTotal)
+{
+    QNetworkReply* rpl = static_cast<QNetworkReply*>(sender());
+    QString url = rpl->url().toString();
+    std::map<QString, Transfer*>::const_iterator itr = uploads.find(url);
+    if (itr == uploads.end()) {
+        qDebug() << "an error downloading" << url << ": the request had some progress but seems like noone is waiting for it, skipping";
+    } else {
+        Transfer* upl = itr->second;
+        qreal received = bytesReceived;
+        qreal total = bytesTotal;
+        qreal progress = received/total;
+        upl->progress = progress;
+        for (std::set<QString>::const_iterator mItr = upl->messages.begin(), end = upl->messages.end(); mItr != end; ++mItr) {
+            emit uploadFileProgress(*mItr, progress);
+        }
+    }
+}
+
+void Core::NetworkAccess::startUpload(const QString& messageId, const QString& url, const QString& path)
+{
+    Transfer* upl = new Transfer({{messageId}, 0, 0, true, path, 0});
+    QNetworkRequest req(url);
+    QFile* file = new QFile(path);
+    if (file->open(QIODevice::ReadOnly)) {
+        upl->reply = manager->put(req, file);
+        
+        connect(upl->reply, SIGNAL(uploadProgress(qint64, qint64)), SLOT(onUploadProgress(qint64, qint64)));
+        connect(upl->reply, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(onUploadError(QNetworkReply::NetworkError)));
+        connect(upl->reply, SIGNAL(finished()), SLOT(onUploadFinished()));
+        uploads.insert(std::make_pair(url, upl));
+        emit downloadFileProgress(messageId, 0);
+    } else {
+        qDebug() << "couldn't upload file" << path;
+        emit uploadFileError(messageId, "Error opening file");
+        delete file;
+    }
+}
+
+void Core::NetworkAccess::uploadFileRequest(const QString& messageId, const QString& url, const QString& path)
+{
+    std::map<QString, Transfer*>::iterator itr = uploads.find(url);
+    if (itr != uploads.end()) {
+        Transfer* upl = itr->second;
+        std::set<QString>::const_iterator mItr = upl->messages.find(messageId);
+        if (mItr == upl->messages.end()) {
+            upl->messages.insert(messageId);
+        }
+        emit uploadFileProgress(messageId, upl->progress);
+    } else {
+        try {
+            QString ePath = files.getRecord(url);
+            if (ePath == path) {
+                emit fileLocalPathResponse(messageId, path);
+            } else {
+                files.changeRecord(url, path);
+            }
+            QFileInfo info(path);
+            if (info.exists() && info.isFile()) {
+                emit fileLocalPathResponse(messageId, path);
+            } else {
+                files.removeRecord(url);
+                startDownload(messageId, url);
+            }
+        } catch (Archive::NotFound e) {
+            startUpload(messageId, url, path);
+        } catch (Archive::Unknown e) {
+            qDebug() << "Error requesting file path on upload:" << e.what();
+            emit uploadFileError(messageId, QString("Database error: ") + e.what());
+        }
+    }
+}
diff --git a/core/networkaccess.h b/core/networkaccess.h
index 526cf2d..de3f2bc 100644
--- a/core/networkaccess.h
+++ b/core/networkaccess.h
@@ -39,7 +39,7 @@ namespace Core {
 class NetworkAccess : public QObject
 {
     Q_OBJECT
-    struct Download;
+    struct Transfer;
 public:
     NetworkAccess(QObject* parent = nullptr);
     virtual ~NetworkAccess();
@@ -51,30 +51,41 @@ signals:
     void fileLocalPathResponse(const QString& messageId, const QString& path);
     void downloadFileProgress(const QString& messageId, qreal value);
     void downloadFileError(const QString& messageId, const QString& path);
+    void uploadFileProgress(const QString& messageId, qreal value);
+    void uploadFileError(const QString& messageId, const QString& path);
     
 public slots:
     void fileLocalPathRequest(const QString& messageId, const QString& url);
     void downladFileRequest(const QString& messageId, const QString& url);
+    void uploadFileRequest(const QString& messageId, const QString& url, const QString& path);
     
 private:
     void startDownload(const QString& messageId, const QString& url);
+    void startUpload(const QString& messageId, const QString& url, const QString& path);
+    QString getErrorText(QNetworkReply::NetworkError code);
     
 private slots:
     void onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal);
-    void onRequestError(QNetworkReply::NetworkError code);
-    void onRequestFinished();
+    void onDownloadError(QNetworkReply::NetworkError code);
+    void onDownloadFinished();
+    void onUploadProgress(qint64 bytesReceived, qint64 bytesTotal);
+    void onUploadError(QNetworkReply::NetworkError code);
+    void onUploadFinished();
     
 private:
     bool running;
     QNetworkAccessManager* manager;
     Storage files;
-    std::map<QString, Download*> downloads;
+    std::map<QString, Transfer*> downloads;
+    std::map<QString, Transfer*> uploads;
     
-    struct Download {
+    struct Transfer {
         std::set<QString> messages;
         qreal progress;
         QNetworkReply* reply;
         bool success;
+        QString path;
+        QFile* file;
     };
 };
 
diff --git a/core/squawk.cpp b/core/squawk.cpp
index bf9550f..50275a5 100644
--- a/core/squawk.cpp
+++ b/core/squawk.cpp
@@ -31,6 +31,8 @@ Core::Squawk::Squawk(QObject* parent):
     connect(&network, SIGNAL(fileLocalPathResponse(const QString&, const QString&)), this, SIGNAL(fileLocalPathResponse(const QString&, const QString&)));
     connect(&network, SIGNAL(downloadFileProgress(const QString&, qreal)), this, SIGNAL(downloadFileProgress(const QString&, qreal)));
     connect(&network, SIGNAL(downloadFileError(const QString&, const QString&)), this, SIGNAL(downloadFileError(const QString&, const QString&)));
+    connect(&network, SIGNAL(uploadFileProgress(const QString&, qreal)), this, SIGNAL(uploadFileProgress(const QString&, qreal)));
+    connect(&network, SIGNAL(uploadFileError(const QString&, const QString&)), this, SIGNAL(uploadFileError(const QString&, const QString&)));
 }
 
 Core::Squawk::~Squawk()
@@ -276,6 +278,11 @@ void Core::Squawk::sendMessage(const QString& account, const Shared::Message& da
     itr->second->sendMessage(data);
 }
 
+void Core::Squawk::sendMessage(const QString& account, const Shared::Message& data, const QString& path)
+{
+    
+}
+
 void Core::Squawk::requestArchive(const QString& account, const QString& jid, int count, const QString& before)
 {
     AccountsMap::const_iterator itr = amap.find(account);
@@ -368,7 +375,6 @@ void Core::Squawk::removeAccountRequest(const QString& name)
     acc->deleteLater();
 }
 
-
 void Core::Squawk::subscribeContact(const QString& account, const QString& jid, const QString& reason)
 {
     AccountsMap::const_iterator itr = amap.find(account);
diff --git a/core/squawk.h b/core/squawk.h
index 89f4c2d..7403ba8 100644
--- a/core/squawk.h
+++ b/core/squawk.h
@@ -65,6 +65,8 @@ signals:
     void fileLocalPathResponse(const QString& messageId, const QString& path);
     void downloadFileError(const QString& messageId, const QString& error);
     void downloadFileProgress(const QString& messageId, qreal value);
+    void uploadFileError(const QString& messageId, const QString& error);
+    void uploadFileProgress(const QString& messageId, qreal value);
     
 public slots:
     void start();
@@ -76,6 +78,7 @@ public slots:
     void disconnectAccount(const QString& account);
     void changeState(int state);
     void sendMessage(const QString& account, const Shared::Message& data);
+    void sendMessage(const QString& account, const Shared::Message& data, const QString& path);
     void requestArchive(const QString& account, const QString& jid, int count, const QString& before);
     void subscribeContact(const QString& account, const QString& jid, const QString& reason);
     void unsubscribeContact(const QString& account, const QString& jid, const QString& reason);
diff --git a/core/storage.cpp b/core/storage.cpp
index 8f6c17c..7ff0ef7 100644
--- a/core/storage.cpp
+++ b/core/storage.cpp
@@ -73,7 +73,7 @@ void Core::Storage::close()
 void Core::Storage::addRecord(const QString& key, const QString& value)
 {
     if (!opened) {
-        throw Archive::Closed("addElement", name.toStdString());
+        throw Archive::Closed("addRecord", name.toStdString());
     }
     const std::string& id = key.toStdString();
     const std::string& val = value.toStdString();
@@ -99,6 +99,33 @@ void Core::Storage::addRecord(const QString& key, const QString& value)
     }
 }
 
+void Core::Storage::changeRecord(const QString& key, const QString& value)
+{
+    if (!opened) {
+        throw Archive::Closed("changeRecord", name.toStdString());
+    }
+    const std::string& id = key.toStdString();
+    const std::string& val = value.toStdString();
+    
+    MDB_val lmdbKey, lmdbData;
+    lmdbKey.mv_size = id.size();
+    lmdbKey.mv_data = (char*)id.c_str();
+    lmdbData.mv_size = val.size();
+    lmdbData.mv_data = (char*)val.c_str();
+    MDB_txn *txn;
+    mdb_txn_begin(environment, NULL, 0, &txn);
+    int rc;
+    rc = mdb_put(txn, base, &lmdbKey, &lmdbData, 0);
+    if (rc != 0) {
+        mdb_txn_abort(txn);
+        if (rc) {
+            throw Archive::Unknown(name.toStdString(), mdb_strerror(rc));
+        }
+    } else {
+        mdb_txn_commit(txn);
+    }
+}
+
 QString Core::Storage::getRecord(const QString& key) const
 {
     if (!opened) {
diff --git a/core/storage.h b/core/storage.h
index 9fe64ca..d2abfde 100644
--- a/core/storage.h
+++ b/core/storage.h
@@ -39,6 +39,7 @@ public:
     void close();
     
     void addRecord(const QString& key, const QString& value);
+    void changeRecord(const QString& key, const QString& value);
     void removeRecord(const QString& key);
     QString getRecord(const QString& key) const;
     
diff --git a/main.cpp b/main.cpp
index d0fba26..085a01f 100644
--- a/main.cpp
+++ b/main.cpp
@@ -70,6 +70,8 @@ int main(int argc, char *argv[])
     QObject::connect(&w, SIGNAL(disconnectAccount(const QString&)), squawk, SLOT(disconnectAccount(const QString&)));
     QObject::connect(&w, SIGNAL(changeState(int)), squawk, SLOT(changeState(int)));
     QObject::connect(&w, SIGNAL(sendMessage(const QString&, const Shared::Message&)), squawk, SLOT(sendMessage(const QString&, const Shared::Message&)));
+    QObject::connect(&w, SIGNAL(sendMessage(const QString&, const Shared::Message&, const QString&)), 
+                     squawk, SLOT(sendMessage(const QString&, const Shared::Message&, const QString&)));
     QObject::connect(&w, SIGNAL(requestArchive(const QString&, const QString&, int, const QString&)), 
                      squawk, SLOT(requestArchive(const QString&, const QString&, int, const QString&)));
     QObject::connect(&w, SIGNAL(subscribeContact(const QString&, const QString&, const QString&)), 
@@ -125,9 +127,6 @@ int main(int argc, char *argv[])
     QObject::connect(squawk, SIGNAL(downloadFileProgress(const QString&, qreal)), &w, SLOT(downloadFileProgress(const QString&, qreal)));
     QObject::connect(squawk, SIGNAL(downloadFileError(const QString&, const QString&)), &w, SLOT(downloadFileError(const QString&, const QString&)));
     
-    
-    //qDebug() << QStandardPaths::writableLocation(QStandardPaths::CacheLocation);
-    
     coreThread->start();
 
     int result = app.exec();
diff --git a/ui/squawk.cpp b/ui/squawk.cpp
index f30a8bb..599c909 100644
--- a/ui/squawk.cpp
+++ b/ui/squawk.cpp
@@ -285,6 +285,7 @@ void Squawk::onRosterItemDoubleClicked(const QModelIndex& item)
                     
                     connect(conv, SIGNAL(destroyed(QObject*)), this, SLOT(onConversationClosed(QObject*)));
                     connect(conv, SIGNAL(sendMessage(const Shared::Message&)), this, SLOT(onConversationMessage(const Shared::Message&)));
+                    connect(conv, SIGNAL(sendMessage(const Shared::Message&, const QString&)), this, SLOT(onConversationMessage(const Shared::Message&, const QString&)));
                     connect(conv, SIGNAL(requestArchive(const QString&)), this, SLOT(onConversationRequestArchive(const QString&)));
                     connect(conv, SIGNAL(requestLocalFile(const QString&, const QString&)), this, SLOT(onConversationRequestLocalFile(const QString&, const QString&)));
                     connect(conv, SIGNAL(downloadFile(const QString&, const QString&)), this, SLOT(onConversationDownloadFile(const QString&, const QString&)));
@@ -468,10 +469,15 @@ void Squawk::notify(const QString& account, const Shared::Message& msg)
 void Squawk::onConversationMessage(const Shared::Message& msg)
 {
     Conversation* conv = static_cast<Conversation*>(sender());
-    
     emit sendMessage(conv->getAccount(), msg);
 }
 
+void Squawk::onConversationMessage(const Shared::Message& msg, const QString& path)
+{
+    Conversation* conv = static_cast<Conversation*>(sender());
+    emit sendMessage(conv->getAccount(), msg, path);
+}
+
 void Squawk::onConversationRequestArchive(const QString& before)
 {
     Conversation* conv = static_cast<Conversation*>(sender());
diff --git a/ui/squawk.h b/ui/squawk.h
index 99a6be7..2e92b28 100644
--- a/ui/squawk.h
+++ b/ui/squawk.h
@@ -57,6 +57,7 @@ signals:
     void disconnectAccount(const QString&);
     void changeState(int state);
     void sendMessage(const QString& account, const Shared::Message& data);
+    void sendMessage(const QString& account, const Shared::Message& data, const QString& path);
     void requestArchive(const QString& account, const QString& jid, int count, const QString& before);
     void subscribeContact(const QString& account, const QString& jid, const QString& reason);
     void unsubscribeContact(const QString& account, const QString& jid, const QString& reason);
@@ -121,6 +122,7 @@ private slots:
     void onComboboxActivated(int index);
     void onRosterItemDoubleClicked(const QModelIndex& item);
     void onConversationMessage(const Shared::Message& msg);
+    void onConversationMessage(const Shared::Message& msg, const QString& path);
     void onConversationRequestArchive(const QString& before);
     void onRosterContextMenu(const QPoint& point);
     void onConversationShown();
diff --git a/ui/widgets/conversation.h b/ui/widgets/conversation.h
index 5bc2d33..cb738b9 100644
--- a/ui/widgets/conversation.h
+++ b/ui/widgets/conversation.h
@@ -80,6 +80,7 @@ public:
     
 signals:
     void sendMessage(const Shared::Message& message);
+    void sendMessage(const Shared::Message& message, const QString& path);
     void requestArchive(const QString& before);
     void shown();
     void requestLocalFile(const QString& messageId, const QString& url);

From 323a549eca11dd34a1f2b0359634ef41394ee32a Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Thu, 10 Oct 2019 14:45:21 +0300
Subject: [PATCH 002/281] got rid of organization name, made building with
 system qxmpp by default

---
 CMakeLists.txt               | 10 +++++-----
 external/qxmpp               |  2 +-
 main.cpp                     |  8 ++++----
 packaging/Archlinux/PKGBUILD | 14 +++++++-------
 4 files changed, 17 insertions(+), 17 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index ab73444..28d32d2 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -39,15 +39,15 @@ qt5_add_resources(RCC resources/resources.qrc)
 add_executable(squawk ${squawk_SRC} ${RCC})
 target_link_libraries(squawk Qt5::Widgets)
 
-add_subdirectory(ui)
-add_subdirectory(core)
-
-option(SYSTEM_QXMPP "Use system qxmpp lib" OFF) 
+option(SYSTEM_QXMPP "Use system qxmpp lib" ON) 
 
 if(NOT SYSTEM_QXMPP)
     add_subdirectory(external/qxmpp)
 endif()
 
+add_subdirectory(ui)
+add_subdirectory(core)
+
 target_link_libraries(squawk squawkUI)
 target_link_libraries(squawk squawkCORE)
 target_link_libraries(squawk uuid)
@@ -56,7 +56,7 @@ add_dependencies(${CMAKE_PROJECT_NAME} translations)
 
 # Install the executable
 install(TARGETS squawk DESTINATION ${CMAKE_INSTALL_BINDIR})
-install(FILES ${QM_FILES} DESTINATION ${CMAKE_INSTALL_DATADIR}/Macaw/Squawk/l10n)
+install(FILES ${QM_FILES} DESTINATION ${CMAKE_INSTALL_DATADIR}/squawk/l10n)
 install(FILES squawk.svg DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/scalable/apps)
 install(FILES squawk48.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/48x48/apps RENAME squawk.png)
 install(FILES squawk64.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/64x64/apps RENAME squawk.png)
diff --git a/external/qxmpp b/external/qxmpp
index e6eb0b7..b18a57d 160000
--- a/external/qxmpp
+++ b/external/qxmpp
@@ -1 +1 @@
-Subproject commit e6eb0b78d0cb17fccd5ddb60966ba2a0a2d2b593
+Subproject commit b18a57daa33f0fefa5f4c63aa7f448b48d302e0d
diff --git a/main.cpp b/main.cpp
index dac0e91..6fcf6c9 100644
--- a/main.cpp
+++ b/main.cpp
@@ -36,10 +36,9 @@ int main(int argc, char *argv[])
     QApplication app(argc, argv);
     SignalCatcher sc(&app);
     
-    QCoreApplication::setOrganizationName("Macaw");
-    QCoreApplication::setOrganizationDomain("macaw.me");
-    QCoreApplication::setApplicationName("Squawk");
-    QCoreApplication::setApplicationVersion("0.0.5");
+    QApplication::setApplicationName("squawk");
+    QApplication::setApplicationDisplayName("Squawk");
+    QApplication::setApplicationVersion("0.0.5");
     
     QTranslator qtTranslator;
     qtTranslator.load("qt_" + QLocale::system().name(), QLibraryInfo::location(QLibraryInfo::TranslationsPath));
@@ -49,6 +48,7 @@ int main(int argc, char *argv[])
     QStringList shares = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation);
     bool found = false;
     for (QString share : shares) {
+        qDebug() << share;
         found = myappTranslator.load(QLocale(), QLatin1String("squawk"), ".", share + "/l10n");
         if (found) {
             break;
diff --git a/packaging/Archlinux/PKGBUILD b/packaging/Archlinux/PKGBUILD
index 316b8e9..bd979b5 100644
--- a/packaging/Archlinux/PKGBUILD
+++ b/packaging/Archlinux/PKGBUILD
@@ -5,15 +5,15 @@ pkgrel=1
 pkgdesc="An XMPP desktop messenger, written on qt"
 arch=('i686' 'x86_64')
 url="https://git.macaw.me/blue/squawk"
-license=('GPLv3')
-depends=('qt5-base' 'qt5-svg' 'lmdb' 'qxmpp>=1.0.0' 'libutil-linux')
-makedepends=('cmake>=3.3' 'imagemagick')
-source=("https://git.macaw.me/blue/squawk/archive/master.tar.gz")
-md5sums=('SKIP')
+license=('GPL3')
+depends=('hicolor-icon-theme' 'desktop-file-utils' 'lmdb' 'qxmpp>=1.0.0')
+makedepends=('cmake>=3.3' 'imagemagick' 'qt5-tools')
+source=("$pkgname-$pkgver.tar.gz")
+sha256sums=('12bfc517574387257a82143d8970ec0d8d434ccd32f7ac400355ed5fa18192ab')
 build() {
         cd "$srcdir/squawk"
-        cmake . -D SYSTEM_QXMPP:BOOL=True -D CMAKE_INSTALL_PREFIX=/usr -G Ninja
-        cmake --build .
+        cmake . -D CMAKE_INSTALL_PREFIX=/usr 
+        cmake --build . -j $nproc
 }
 package() {
         cd "$srcdir/squawk"

From c678a790e53d0125d2dba31bd760ca9c3ce0c8d1 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Mon, 14 Oct 2019 23:18:51 +0300
Subject: [PATCH 003/281] some more methods for archive to store avatar states

---
 core/CMakeLists.txt |   2 +-
 core/archive.cpp    | 188 ++++++++++++++++++++++++++++++++------------
 core/archive.h      |  12 ++-
 core/rosteritem.cpp |  15 +++-
 core/rosteritem.h   |   5 ++
 main.cpp            |   1 -
 6 files changed, 168 insertions(+), 55 deletions(-)

diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt
index 46bf97a..c541ba2 100644
--- a/core/CMakeLists.txt
+++ b/core/CMakeLists.txt
@@ -3,7 +3,7 @@ project(squawkCORE)
 
 set(CMAKE_AUTOMOC ON)
 
-find_package(Qt5Widgets CONFIG REQUIRED)
+find_package(Qt5Core CONFIG REQUIRED)
 find_package(Qt5Network CONFIG REQUIRED)
 
 set(squawkCORE_SRC
diff --git a/core/archive.cpp b/core/archive.cpp
index 00139ac..295b04c 100644
--- a/core/archive.cpp
+++ b/core/archive.cpp
@@ -32,7 +32,10 @@ Core::Archive::Archive(const QString& p_jid, QObject* parent):
     environment(),
     main(),
     order(),
-    stats()
+    stats(),
+    hasAvatar(false),
+    avatarHash(),
+    avatarType()
 {
 }
 
@@ -66,7 +69,26 @@ void Core::Archive::open(const QString& account)
         mdb_dbi_open(txn, "order", MDB_CREATE | MDB_INTEGERKEY, &order);
         mdb_dbi_open(txn, "stats", MDB_CREATE, &stats);
         mdb_txn_commit(txn);
-        fromTheBeginning = _isFromTheBeginning();
+        
+        mdb_txn_begin(environment, NULL, 0, &txn);
+        try {
+            fromTheBeginning = getStatBoolValue("beginning", txn);
+        } catch (NotFound e) {
+            fromTheBeginning = false;
+        }
+        try {
+            hasAvatar = getStatBoolValue("hasAvatar", txn);
+        } catch (NotFound e) {
+            hasAvatar = false;
+        }
+        if (hasAvatar) {
+            avatarHash = getStatStringValue("avatarHash", txn).c_str();
+            avatarType = getStatStringValue("avatarType", txn).c_str();
+        } else {
+            avatarHash = "";
+            avatarType = "";
+        }
+        mdb_txn_abort(txn);
         opened = true;
     }
 }
@@ -396,40 +418,6 @@ std::list<Shared::Message> Core::Archive::getBefore(int count, const QString& id
     return res;
 }
 
-bool Core::Archive::_isFromTheBeginning()
-{
-    std::string strKey = "beginning";
-    
-    MDB_val lmdbKey, lmdbData;
-    lmdbKey.mv_size = strKey.size();
-    lmdbKey.mv_data = (char*)strKey.c_str();
-    
-    MDB_txn *txn;
-    int rc;
-    mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn);
-    rc = mdb_get(txn, stats, &lmdbKey, &lmdbData);
-    if (rc == MDB_NOTFOUND) {
-        mdb_txn_abort(txn);
-        return false;
-    } else if (rc) {
-        qDebug() <<"isFromTheBeginning error: " << mdb_strerror(rc);
-        mdb_txn_abort(txn);
-        throw NotFound(strKey, jid.toStdString());
-    } else {
-        uint8_t value = *(uint8_t*)(lmdbData.mv_data);
-        bool is;
-        if (value == 144) {
-            is = false;
-        } else if (value == 72) {
-            is = true;
-        } else {
-            qDebug() <<"isFromTheBeginning error: stored value doesn't match any magic number, the answer is most probably wrong";
-        }
-        mdb_txn_abort(txn);
-        return is;
-    }
-}
-
 bool Core::Archive::isFromTheBeginning()
 {
     if (!opened) {
@@ -445,26 +433,15 @@ void Core::Archive::setFromTheBeginning(bool is)
     }
     if (fromTheBeginning != is) {
         fromTheBeginning = is;
-        const std::string& id = "beginning";
-        uint8_t value = 144;
-        if (is) {
-            value = 72;
-        }
         
-        MDB_val lmdbKey, lmdbData;
-        lmdbKey.mv_size = id.size();
-        lmdbKey.mv_data = (char*)id.c_str();
-        lmdbData.mv_size = sizeof value;
-        lmdbData.mv_data = &value;
         MDB_txn *txn;
         mdb_txn_begin(environment, NULL, 0, &txn);
-        int rc;
-        rc = mdb_put(txn, stats, &lmdbKey, &lmdbData, 0);
-        if (rc != 0) {
-            qDebug() << "Couldn't store beginning key into stat database:" << mdb_strerror(rc);
+        bool success = setStatValue("beginning", is, txn);
+        if (success != 0) {
             mdb_txn_abort(txn);
+        } else {
+            mdb_txn_commit(txn);
         }
-        mdb_txn_commit(txn);
     }
 }
 
@@ -508,3 +485,112 @@ void Core::Archive::printKeys()
     mdb_cursor_close(cursor);
     mdb_txn_abort(txn);
 }
+
+bool Core::Archive::getStatBoolValue(const std::string& id, MDB_txn* txn)
+{
+    MDB_val lmdbKey, lmdbData;
+    lmdbKey.mv_size = id.size();
+    lmdbKey.mv_data = (char*)id.c_str();
+    
+    int rc;
+    rc = mdb_get(txn, stats, &lmdbKey, &lmdbData);
+    if (rc == MDB_NOTFOUND) {
+        throw NotFound(id, jid.toStdString());
+    } else if (rc) {
+        qDebug() << "error retrieving" << id.c_str() << "from stats db of" << jid << mdb_strerror(rc);
+        throw 15;            //TODO proper exception
+    } else {
+        uint8_t value = *(uint8_t*)(lmdbData.mv_data);
+        bool is;
+        if (value == 144) {
+            is = false;
+        } else if (value == 72) {
+            is = true;
+        } else {
+            qDebug() << "error retrieving boolean stat" << id.c_str() << ": stored value doesn't match any magic number, the answer is most probably wrong";
+            throw NotFound(id, jid.toStdString());
+        }
+        return is;
+    }
+}
+
+std::string Core::Archive::getStatStringValue(const std::string& id, MDB_txn* txn)
+{
+    MDB_cursor* cursor;
+    MDB_val lmdbKey, lmdbData;
+    lmdbKey.mv_size = id.size();
+    lmdbKey.mv_data = (char*)id.c_str();
+    
+    int rc;
+    rc = mdb_get(txn, stats, &lmdbKey, &lmdbData);
+    if (rc == MDB_NOTFOUND) {
+        throw NotFound(id, jid.toStdString());
+    } else if (rc) {
+        qDebug() << "error retrieving" << id.c_str() << "from stats db of" << jid << mdb_strerror(rc);
+        throw 15;            //TODO proper exception
+    } else {
+        std::string value((char*)lmdbData.mv_data, lmdbData.mv_size);
+        return value;
+    }
+}
+
+bool Core::Archive::setStatValue(const std::string& id, bool value, MDB_txn* txn)
+{
+    uint8_t binvalue = 144;
+    if (value) {
+        binvalue = 72;
+    }
+    MDB_val lmdbKey, lmdbData;
+    lmdbKey.mv_size = id.size();
+    lmdbKey.mv_data = (char*)id.c_str();
+    lmdbData.mv_size = sizeof binvalue;
+    lmdbData.mv_data = &binvalue;
+    int rc = mdb_put(txn, stats, &lmdbKey, &lmdbData, 0);
+    if (rc != 0) {
+        qDebug() << "Couldn't store" << id.c_str() << "key into stat database:" << mdb_strerror(rc);
+        return false;
+    }
+    return true;
+}
+
+bool Core::Archive::setStatValue(const std::string& id, const std::string& value, MDB_txn* txn)
+{
+    MDB_val lmdbKey, lmdbData;
+    lmdbKey.mv_size = id.size();
+    lmdbKey.mv_data = (char*)id.c_str();
+    lmdbData.mv_size = value.size();
+    lmdbData.mv_data = (char*)value.c_str();
+    int rc = mdb_put(txn, stats, &lmdbKey, &lmdbData, 0);
+    if (rc != 0) {
+        qDebug() << "Couldn't store" << id.c_str() << "key into stat database:" << mdb_strerror(rc);
+        return false;
+    }
+    return true;
+}
+
+bool Core::Archive::getHasAvatar() const
+{
+    if (!opened) {
+        throw Closed("getHasAvatar", jid.toStdString());
+    }
+    
+    return hasAvatar;
+}
+
+QString Core::Archive::getAvatarHash() const
+{
+    if (!opened) {
+        throw Closed("getAvatarHash", jid.toStdString());
+    }
+    
+    return avatarHash;
+}
+
+QString Core::Archive::getAvatarType() const
+{
+    if (!opened) {
+        throw Closed("getAvatarType", jid.toStdString());
+    }
+    
+    return avatarType;
+}
diff --git a/core/archive.h b/core/archive.h
index 58a5f8d..45a7599 100644
--- a/core/archive.h
+++ b/core/archive.h
@@ -49,6 +49,9 @@ public:
     std::list<Shared::Message> getBefore(int count, const QString& id);
     bool isFromTheBeginning();
     void setFromTheBeginning(bool is);
+    bool getHasAvatar() const;
+    QString getAvatarHash() const;
+    QString getAvatarType() const;
     
 public:
     const QString jid;
@@ -131,8 +134,15 @@ private:
     MDB_dbi main;
     MDB_dbi order;
     MDB_dbi stats;
+    bool hasAvatar;
+    QString avatarHash;
+    QString avatarType;
     
-    bool _isFromTheBeginning();
+    bool getStatBoolValue(const std::string& id, MDB_txn* txn);
+    std::string getStatStringValue(const std::string& id, MDB_txn* txn);
+    
+    bool setStatValue(const std::string& id, bool value, MDB_txn* txn);
+    bool setStatValue(const std::string& id, const std::string& value, MDB_txn* txn);
     void printOrder();
     void printKeys();
 };
diff --git a/core/rosteritem.cpp b/core/rosteritem.cpp
index be6ceee..50b3b83 100644
--- a/core/rosteritem.cpp
+++ b/core/rosteritem.cpp
@@ -20,9 +20,10 @@
 
 #include <QDebug>
 
-Core::RosterItem::RosterItem(const QString& pJid, const QString& account, QObject* parent):
+Core::RosterItem::RosterItem(const QString& pJid, const QString& pAccount, QObject* parent):
     QObject(parent),
     jid(pJid),
+    account(pAccount),
     name(),
     archiveState(empty),
     archive(new Archive(jid)),
@@ -331,3 +332,15 @@ bool Core::RosterItem::isMuc() const
 {
     return muc;
 }
+
+QString Core::RosterItem::avatarHash() const
+{
+    return archive->getAvatarHash();
+}
+
+QString Core::RosterItem::avatarPath() const
+{
+    QString path(QStandardPaths::writableLocation(QStandardPaths::CacheLocation));
+    path += "/" + account + "/" + jid + "/avatar." + archive->getAvatarType();
+    return path;
+}
diff --git a/core/rosteritem.h b/core/rosteritem.h
index e4284af..a2bc929 100644
--- a/core/rosteritem.h
+++ b/core/rosteritem.h
@@ -21,6 +21,7 @@
 
 #include <QObject>
 #include <QString>
+#include <QStandardPaths>
 
 #include <list>
 
@@ -58,6 +59,9 @@ public:
     void flushMessagesToArchive(bool finished, const QString& firstId, const QString& lastId);
     void requestHistory(int count, const QString& before);
     void requestFromEmpty(int count, const QString& before);
+    bool hasAvatar() const;
+    QString avatarHash() const;
+    QString avatarPath() const;
     
 signals:
     void nameChanged(const QString& name);
@@ -67,6 +71,7 @@ signals:
     
 public:
     const QString jid;
+    const QString account;
     
 protected:
     QString name;
diff --git a/main.cpp b/main.cpp
index 6fcf6c9..0b8c9a4 100644
--- a/main.cpp
+++ b/main.cpp
@@ -48,7 +48,6 @@ int main(int argc, char *argv[])
     QStringList shares = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation);
     bool found = false;
     for (QString share : shares) {
-        qDebug() << share;
         found = myappTranslator.load(QLocale(), QLatin1String("squawk"), ".", share + "/l10n");
         if (found) {
             break;

From 64e33b6139540af7688bfac30ec76f9c1088e9ac Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Tue, 15 Oct 2019 22:25:40 +0300
Subject: [PATCH 004/281] receiving avatars, generating missing avatars,
 storing state of avatars, global color palette

---
 core/CMakeLists.txt |   4 ++
 core/account.cpp    | 159 +++++++++++++++++++++++++++++++++-----------
 core/account.h      |   6 ++
 core/archive.cpp    | 135 ++++++++++++++++++++++++++++++++++++-
 core/archive.h      |   8 +++
 core/rosteritem.cpp |  48 +++++++++++++
 core/rosteritem.h   |   7 ++
 global.h            |  46 +++++++++++++
 8 files changed, 375 insertions(+), 38 deletions(-)

diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt
index c541ba2..2c0374d 100644
--- a/core/CMakeLists.txt
+++ b/core/CMakeLists.txt
@@ -4,7 +4,9 @@ project(squawkCORE)
 set(CMAKE_AUTOMOC ON)
 
 find_package(Qt5Core CONFIG REQUIRED)
+find_package(Qt5Gui CONFIG REQUIRED)
 find_package(Qt5Network CONFIG REQUIRED)
+find_package(Qt5Xml CONFIG REQUIRED)
 
 set(squawkCORE_SRC
     squawk.cpp
@@ -30,5 +32,7 @@ endif()
 # Use the Widgets module from Qt 5.
 target_link_libraries(squawkCORE Qt5::Core)
 target_link_libraries(squawkCORE Qt5::Network)
+target_link_libraries(squawkCORE Qt5::Gui)
+target_link_libraries(squawkCORE Qt5::Xml)
 target_link_libraries(squawkCORE qxmpp)
 target_link_libraries(squawkCORE lmdb)
diff --git a/core/account.cpp b/core/account.cpp
index bacbb6d..bc7428c 100644
--- a/core/account.cpp
+++ b/core/account.cpp
@@ -48,37 +48,39 @@ Account::Account(const QString& p_login, const QString& p_server, const QString&
     config.setPassword(p_password);
     config.setAutoAcceptSubscriptions(true);
     
-    QObject::connect(&client, SIGNAL(connected()), this, SLOT(onClientConnected()));
-    QObject::connect(&client, SIGNAL(disconnected()), this, SLOT(onClientDisconnected()));
-    QObject::connect(&client, SIGNAL(presenceReceived(const QXmppPresence&)), this, SLOT(onPresenceReceived(const QXmppPresence&)));
-    QObject::connect(&client, SIGNAL(messageReceived(const QXmppMessage&)), this, SLOT(onMessageReceived(const QXmppMessage&)));
-    QObject::connect(&client, SIGNAL(error(QXmppClient::Error)), this, SLOT(onClientError(QXmppClient::Error)));
+    QObject::connect(&client, &QXmppClient::connected, this, &Account::onClientConnected);
+    QObject::connect(&client, &QXmppClient::disconnected, this, &Account::onClientDisconnected);
+    QObject::connect(&client, &QXmppClient::presenceReceived, this, &Account::onPresenceReceived);
+    QObject::connect(&client, &QXmppClient::messageReceived, this, &Account::onMessageReceived);
+    QObject::connect(&client, &QXmppClient::error, this, &Account::onClientError);
     
     QXmppRosterManager& rm = client.rosterManager();
     
-    QObject::connect(&rm, SIGNAL(rosterReceived()), this, SLOT(onRosterReceived()));
-    QObject::connect(&rm, SIGNAL(itemAdded(const QString&)), this, SLOT(onRosterItemAdded(const QString&)));
-    QObject::connect(&rm, SIGNAL(itemRemoved(const QString&)), this, SLOT(onRosterItemRemoved(const QString&)));
-    QObject::connect(&rm, SIGNAL(itemChanged(const QString&)), this, SLOT(onRosterItemChanged(const QString&)));
-    //QObject::connect(&rm, SIGNAL(presenceChanged(const QString&, const QString&)), this, SLOT(onRosterPresenceChanged(const QString&, const QString&)));
+    QObject::connect(&rm, &QXmppRosterManager::rosterReceived, this, &Account::onRosterReceived);
+    QObject::connect(&rm, &QXmppRosterManager::itemAdded, this, &Account::onRosterItemAdded);
+    QObject::connect(&rm, &QXmppRosterManager::itemRemoved, this, &Account::onRosterItemRemoved);
+    QObject::connect(&rm, &QXmppRosterManager::itemChanged, this, &Account::onRosterItemChanged);
+    //QObject::connect(&rm, &QXmppRosterManager::presenceChanged, this, &Account::onRosterPresenceChanged);
     
     client.addExtension(cm);
     
-    QObject::connect(cm, SIGNAL(messageReceived(const QXmppMessage&)), this, SLOT(onCarbonMessageReceived(const QXmppMessage&)));
-    QObject::connect(cm, SIGNAL(messageSent(const QXmppMessage&)), this, SLOT(onCarbonMessageSent(const QXmppMessage&)));
+    QObject::connect(cm, &QXmppCarbonManager::messageReceived, this, &Account::onCarbonMessageReceived);
+    QObject::connect(cm, &QXmppCarbonManager::messageSent, this, &Account::onCarbonMessageSent);
     
     client.addExtension(am);
     
-    QObject::connect(am, SIGNAL(logMessage(QXmppLogger::MessageType, const QString&)), this, SLOT(onMamLog(QXmppLogger::MessageType, const QString)));
-    QObject::connect(am, SIGNAL(archivedMessageReceived(const QString&, const QXmppMessage&)), this, SLOT(onMamMessageReceived(const QString&, const QXmppMessage&)));
-    QObject::connect(am, SIGNAL(resultsRecieved(const QString&, const QXmppResultSetReply&, bool)), 
-                     this, SLOT(onMamResultsReceived(const QString&, const QXmppResultSetReply&, bool)));
+    QObject::connect(am, &QXmppMamManager::logMessage, this, &Account::onMamLog);
+    QObject::connect(am, &QXmppMamManager::archivedMessageReceived, this, &Account::onMamMessageReceived);
+    QObject::connect(am, &QXmppMamManager::resultsRecieved, this, &Account::onMamResultsReceived);
     
     client.addExtension(mm);
-    QObject::connect(mm, SIGNAL(roomAdded(QXmppMucRoom*)), this, SLOT(onMucRoomAdded(QXmppMucRoom*)));
+    QObject::connect(mm, &QXmppMucManager::roomAdded, this, &Account::onMucRoomAdded);
     
     client.addExtension(bm);
-    QObject::connect(bm, SIGNAL(bookmarksReceived(const QXmppBookmarkSet&)), this, SLOT(bookmarksReceived(const QXmppBookmarkSet&)));
+    QObject::connect(bm, &QXmppBookmarkManager::bookmarksReceived, this, &Account::bookmarksReceived);
+    
+    QXmppVCardManager& vm = client.vCardManager();
+    QObject::connect(&vm, &QXmppVCardManager::vCardReceived, this, &Account::onVCardReceived);
 }
 
 Account::~Account()
@@ -279,6 +281,19 @@ void Core::Account::addedAccount(const QString& jid)
             {"name", re.name()},
             {"state", state}
         });
+        
+        if (contact->hasAvatar()) {
+            if (contact->isAvatarAutoGenerated()) {
+                cData.insert("avatarType", static_cast<uint>(Shared::Avatar::valid));
+            } else {
+                cData.insert("avatarType", static_cast<uint>(Shared::Avatar::autocreated));
+            }
+            cData.insert("avatarPath", contact->avatarPath());
+        } else {
+            cData.insert("avatarType", static_cast<uint>(Shared::Avatar::empty));
+            client.vCardManager().requestVCard(jid);
+            pendingVCardRequests.insert(jid);
+        }
         int grCount = 0;
         for (QSet<QString>::const_iterator itr = gr.begin(), end = gr.end(); itr != end; ++itr) {
             const QString& groupName = *itr;
@@ -296,36 +311,32 @@ void Core::Account::addedAccount(const QString& jid)
 
 void Core::Account::handleNewRosterItem(Core::RosterItem* contact)
 {
-    
-    QObject::connect(contact, SIGNAL(needHistory(const QString&, const QString&, const QDateTime&)), this, SLOT(onContactNeedHistory(const QString&, const QString&, const QDateTime&)));
-    QObject::connect(contact, SIGNAL(historyResponse(const std::list<Shared::Message>&)), this, SLOT(onContactHistoryResponse(const std::list<Shared::Message>&)));
-    QObject::connect(contact, SIGNAL(nameChanged(const QString&)), this, SLOT(onContactNameChanged(const QString&)));
+    QObject::connect(contact, &RosterItem::needHistory, this, &Account::onContactNeedHistory);
+    QObject::connect(contact, &RosterItem::historyResponse, this, &Account::onContactHistoryResponse);
+    QObject::connect(contact, &RosterItem::nameChanged, this, &Account::onContactNameChanged);
+    QObject::connect(contact, &RosterItem::avatarChanged, this, &Account::onContactAvatarChanged);
 }
 
 void Core::Account::handleNewContact(Core::Contact* contact)
 {
     handleNewRosterItem(contact);
-    QObject::connect(contact, SIGNAL(groupAdded(const QString&)), this, SLOT(onContactGroupAdded(const QString&)));
-    QObject::connect(contact, SIGNAL(groupRemoved(const QString&)), this, SLOT(onContactGroupRemoved(const QString&)));
-    QObject::connect(contact, SIGNAL(subscriptionStateChanged(Shared::SubscriptionState)), 
-                     this, SLOT(onContactSubscriptionStateChanged(Shared::SubscriptionState)));
+    QObject::connect(contact, &Contact::groupAdded, this, &Account::onContactGroupAdded);
+    QObject::connect(contact, &Contact::groupRemoved, this, &Account::onContactGroupRemoved);
+    QObject::connect(contact, &Contact::subscriptionStateChanged, this, &Account::onContactSubscriptionStateChanged);
 }
 
 void Core::Account::handleNewConference(Core::Conference* contact)
 {
     handleNewRosterItem(contact);
-    QObject::connect(contact, SIGNAL(nickChanged(const QString&)), this, SLOT(onMucNickNameChanged(const QString&)));
-    QObject::connect(contact, SIGNAL(subjectChanged(const QString&)), this, SLOT(onMucSubjectChanged(const QString&)));
-    QObject::connect(contact, SIGNAL(joinedChanged(bool)), this, SLOT(onMucJoinedChanged(bool)));
-    QObject::connect(contact, SIGNAL(autoJoinChanged(bool)), this, SLOT(onMucAutoJoinChanged(bool)));
-    QObject::connect(contact, SIGNAL(addParticipant(const QString&, const QMap<QString, QVariant>&)), 
-                     this, SLOT(onMucAddParticipant(const QString&, const QMap<QString, QVariant>&)));
-    QObject::connect(contact, SIGNAL(changeParticipant(const QString&, const QMap<QString, QVariant>&)), 
-                     this, SLOT(onMucChangeParticipant(const QString&, const QMap<QString, QVariant>&)));
-    QObject::connect(contact, SIGNAL(removeParticipant(const QString&)), this, SLOT(onMucRemoveParticipant(const QString&)));
+    QObject::connect(contact, &Conference::nickChanged, this, &Account::onMucNickNameChanged);
+    QObject::connect(contact, &Conference::subjectChanged, this, &Account::onMucSubjectChanged);
+    QObject::connect(contact, &Conference::joinedChanged, this, &Account::onMucJoinedChanged);
+    QObject::connect(contact, &Conference::autoJoinChanged, this, &Account::onMucAutoJoinChanged);
+    QObject::connect(contact, &Conference::addParticipant, this, &Account::onMucAddParticipant);
+    QObject::connect(contact, &Conference::changeParticipant, this, &Account::onMucChangeParticipant);
+    QObject::connect(contact, &Conference::removeParticipant, this, &Account::onMucRemoveParticipant);
 }
 
-
 void Core::Account::onPresenceReceived(const QXmppPresence& presence)
 {
     QString id = presence.from();
@@ -341,11 +352,45 @@ void Core::Account::onPresenceReceived(const QXmppPresence& presence)
         } else {
             qDebug() << "Received a presence for another resource of my " << name << " account, skipping";
         }
+    } else {
+        if (pendingVCardRequests.find(jid) == pendingVCardRequests.end()) {
+            std::map<QString, Contact*>::const_iterator itr = contacts.find(jid);
+            if (itr != contacts.end()) {
+                Contact* cnt = itr->second;
+                switch (presence.vCardUpdateType()) {
+                    case QXmppPresence::VCardUpdateNone:            //this presence has nothing to do with photo
+                        break;
+                    case QXmppPresence::VCardUpdateNotReady:        //let's say the photo didn't change here
+                        break;
+                    case QXmppPresence::VCardUpdateNoPhoto:         //there is no photo, need to drop if any
+                        if (!cnt->hasAvatar() || (cnt->hasAvatar() && !cnt->isAvatarAutoGenerated())) {
+                            cnt->setAutoGeneratedAvatar();
+                        }
+                            break;
+                    case QXmppPresence::VCardUpdateValidPhoto:      //there is a photo, need to load
+                        if (cnt->hasAvatar()) {
+                            if (cnt->isAvatarAutoGenerated()) {
+                                client.vCardManager().requestVCard(jid);
+                                pendingVCardRequests.insert(jid);
+                            } else {
+                                if (cnt->avatarHash() != presence.photoHash()) {
+                                    client.vCardManager().requestVCard(jid);
+                                    pendingVCardRequests.insert(jid);
+                                }
+                            }
+                        } else {
+                            client.vCardManager().requestVCard(jid);
+                            pendingVCardRequests.insert(jid);
+                        }
+                        break;
+                }
+            }
+        }
     }
     
     switch (presence.type()) {
         case QXmppPresence::Error:
-            qDebug() << "An error reported by presence from " << id;
+            qDebug() << "An error reported by presence from" << id << presence.error().text();
             break;
         case QXmppPresence::Available:{
             QDateTime lastInteraction = presence.lastUserInteraction();
@@ -1211,3 +1256,43 @@ void Core::Account::renameContactRequest(const QString& jid, const QString& newN
         rm.renameItem(jid, newName);
     }
 }
+
+void Core::Account::onVCardReceived(const QXmppVCardIq& card)
+{
+    QString jid = card.from();
+    pendingVCardRequests.erase(jid);
+    RosterItem* item = 0;
+    
+    std::map<QString, Contact*>::const_iterator contItr = contacts.find(jid);
+    if (contItr == contacts.end()) {
+        std::map<QString, Conference*>::const_iterator confItr = conferences.find(jid);
+        if (confItr == conferences.end()) {
+            qDebug() << "received vCard" << jid << "doesn't belong to any of known contacts or conferences, skipping";
+            return;
+        } else {
+            item = confItr->second;
+        }
+    } else {
+        item = contItr->second;
+    }
+    
+    QByteArray ava = card.photo();
+    if (ava.size() > 0) {
+        item->setAvatar(ava);
+    } else {
+        item->setAutoGeneratedAvatar();
+    }
+}
+
+void Core::Account::onContactAvatarChanged(Shared::Avatar type, const QString& path)
+{
+    RosterItem* item = static_cast<RosterItem*>(sender());
+    QMap<QString, QVariant> cData({
+        {"avatarType", static_cast<uint>(type)}
+    });
+    if (type != Shared::Avatar::empty) {
+        cData.insert("avatarPath", path);
+    }
+    
+    emit changeContact(item->jid, cData);
+}
diff --git a/core/account.h b/core/account.h
index 21f35d3..c1af059 100644
--- a/core/account.h
+++ b/core/account.h
@@ -30,6 +30,8 @@
 #include <QXmppClient.h>
 #include <QXmppBookmarkManager.h>
 #include <QXmppBookmarkSet.h>
+#include <QXmppVCardIq.h>
+#include <QXmppVCardManager.h>
 #include "../global.h"
 #include "contact.h"
 #include "conference.h"
@@ -119,6 +121,7 @@ private:
     
     std::map<QString, QString> queuedContacts;
     std::set<QString> outOfRosterContacts;
+    std::set<QString> pendingVCardRequests;
     
 private slots:
     void onClientConnected();
@@ -157,8 +160,11 @@ private slots:
     void onContactSubscriptionStateChanged(Shared::SubscriptionState state);
     void onContactHistoryResponse(const std::list<Shared::Message>& list);
     void onContactNeedHistory(const QString& before, const QString& after, const QDateTime& at);
+    void onContactAvatarChanged(Shared::Avatar, const QString& path);
     
     void onMamLog(QXmppLogger::MessageType type, const QString &msg);
+    
+    void onVCardReceived(const QXmppVCardIq& card);
   
 private:
     void addedAccount(const QString &bareJid);
diff --git a/core/archive.cpp b/core/archive.cpp
index 295b04c..5900df2 100644
--- a/core/archive.cpp
+++ b/core/archive.cpp
@@ -34,6 +34,7 @@ Core::Archive::Archive(const QString& p_jid, QObject* parent):
     order(),
     stats(),
     hasAvatar(false),
+    avatarAutoGenerated(false),
     avatarHash(),
     avatarType()
 {
@@ -82,13 +83,36 @@ void Core::Archive::open(const QString& account)
             hasAvatar = false;
         }
         if (hasAvatar) {
-            avatarHash = getStatStringValue("avatarHash", txn).c_str();
+            try {
+                avatarAutoGenerated = getStatBoolValue("avatarAutoGenerated", txn);
+            } catch (NotFound e) {
+                avatarAutoGenerated = false;
+            }
+            
             avatarType = getStatStringValue("avatarType", txn).c_str();
+            if (avatarAutoGenerated) {
+                avatarHash = "";
+            } else {
+                avatarHash = getStatStringValue("avatarHash", txn).c_str();
+            }
         } else {
+            avatarAutoGenerated = false;
             avatarHash = "";
             avatarType = "";
         }
         mdb_txn_abort(txn);
+        
+        if (hasAvatar) {
+            QFile ava(path + "/avatar." + avatarType);
+            if (!ava.exists()) {
+                bool success = dropAvatar();
+                if (!success) {
+                    qDebug() << "error opening archive" << jid << "for account" << account 
+                    << ". There is supposed to be avatar but the file doesn't exist, couldn't even drop it, it surely will lead to an error";
+                }
+            }
+        }
+        
         opened = true;
     }
 }
@@ -577,6 +601,15 @@ bool Core::Archive::getHasAvatar() const
     return hasAvatar;
 }
 
+bool Core::Archive::getAutoAvatar() const
+{
+    if (!opened) {
+        throw Closed("getAutoAvatar", jid.toStdString());
+    }
+    
+    return avatarAutoGenerated;
+}
+
 QString Core::Archive::getAvatarHash() const
 {
     if (!opened) {
@@ -594,3 +627,103 @@ QString Core::Archive::getAvatarType() const
     
     return avatarType;
 }
+
+bool Core::Archive::dropAvatar()
+{
+    MDB_txn *txn;
+    mdb_txn_begin(environment, NULL, 0, &txn);
+    bool success = setStatValue("hasAvatar", false, txn);
+    success = success && setStatValue("avatarAutoGenerated", false, txn);
+    success = success && setStatValue("avatarHash", "", txn);
+    success = success && setStatValue("avatarType", "", txn);
+    if (!success) {
+        mdb_txn_abort(txn);
+        return false;
+    } else {
+        hasAvatar = false;
+        avatarAutoGenerated = false;
+        avatarHash = "";
+        avatarType = "";
+        mdb_txn_commit(txn);
+        return true;
+    }
+}
+
+bool Core::Archive::setAvatar(const QByteArray& data, bool generated)
+{
+    if (!opened) {
+        throw Closed("setAvatar", jid.toStdString());
+    }
+    
+    if (data.size() == 0) {
+        if (!hasAvatar) {
+            return false;
+        } else {
+            return dropAvatar();
+        }
+    } else {
+        const char* cep;
+        mdb_env_get_path(environment, &cep);
+        QString currentPath(cep);
+        bool needToRemoveOld = false;
+        QCryptographicHash hash(QCryptographicHash::Sha1);
+        hash.addData(data);
+        QString newHash(hash.result());
+        if (hasAvatar) {
+            if (!generated && !avatarAutoGenerated && avatarHash == newHash) {
+                return false;
+            }
+            QFile oldAvatar(currentPath + "/avatar." + avatarType);
+            if (oldAvatar.exists()) {
+                if (oldAvatar.rename(currentPath + "/avatar." + avatarType + ".bak")) {
+                    needToRemoveOld = true;
+                } else {
+                    qDebug() << "Can't change avatar: couldn't get rid of the old avatar" << oldAvatar.fileName();
+                    return false;
+                }
+            }
+        }
+        QMimeDatabase db;
+        QMimeType type = db.mimeTypeForData(data);
+        QString ext = type.preferredSuffix();
+        QFile newAvatar(currentPath + "/avatar." + ext);
+        if (newAvatar.open(QFile::WriteOnly)) {
+            newAvatar.write(data);
+            newAvatar.close();
+            
+            MDB_txn *txn;
+            mdb_txn_begin(environment, NULL, 0, &txn);
+            bool success = setStatValue("hasAvatar", true, txn);
+            success = success && setStatValue("avatarAutoGenerated", generated, txn);
+            success = success && setStatValue("avatarHash", newHash.toStdString(), txn);
+            success = success && setStatValue("avatarType", ext.toStdString(), txn);
+            if (!success) {
+                qDebug() << "Can't change avatar: couldn't store changes to database for" << newAvatar.fileName() << "rolling back to the previous state";
+                if (needToRemoveOld) {
+                    QFile oldAvatar(currentPath + "/avatar." + avatarType + ".bak");
+                    oldAvatar.rename(currentPath + "/avatar." + avatarType);
+                }
+                mdb_txn_abort(txn);
+                return false;
+            } else {
+                hasAvatar = true;
+                avatarAutoGenerated = generated;
+                avatarHash = newHash;
+                avatarType = ext;
+                mdb_txn_commit(txn);
+                if (needToRemoveOld) {
+                    QFile oldAvatar(currentPath + "/avatar." + avatarType + ".bak");
+                    oldAvatar.remove();
+                }
+                return true;
+            }
+        } else {
+            qDebug() << "Can't change avatar: cant open file to write" << newAvatar.fileName() << "rolling back to the previous state";
+            if (needToRemoveOld) {
+                QFile oldAvatar(currentPath + "/avatar." + avatarType + ".bak");
+                oldAvatar.rename(currentPath + "/avatar." + avatarType);
+            }
+            return false;
+        }
+    }
+}
diff --git a/core/archive.h b/core/archive.h
index 45a7599..e94fac8 100644
--- a/core/archive.h
+++ b/core/archive.h
@@ -20,6 +20,10 @@
 #define CORE_ARCHIVE_H
 
 #include <QObject>
+#include <QCryptographicHash>
+#include <QMimeDatabase>
+#include <QMimeType>
+
 #include "../global.h"
 #include <lmdb.h>
 #include "../exception.h"
@@ -50,8 +54,10 @@ public:
     bool isFromTheBeginning();
     void setFromTheBeginning(bool is);
     bool getHasAvatar() const;
+    bool getAutoAvatar() const;
     QString getAvatarHash() const;
     QString getAvatarType() const;
+    bool setAvatar(const QByteArray& data, bool generated = false);
     
 public:
     const QString jid;
@@ -135,6 +141,7 @@ private:
     MDB_dbi order;
     MDB_dbi stats;
     bool hasAvatar;
+    bool avatarAutoGenerated;
     QString avatarHash;
     QString avatarType;
     
@@ -145,6 +152,7 @@ private:
     bool setStatValue(const std::string& id, const std::string& value, MDB_txn* txn);
     void printOrder();
     void printKeys();
+    bool dropAvatar();
 };
 
 }
diff --git a/core/rosteritem.cpp b/core/rosteritem.cpp
index 50b3b83..f144901 100644
--- a/core/rosteritem.cpp
+++ b/core/rosteritem.cpp
@@ -338,9 +338,57 @@ QString Core::RosterItem::avatarHash() const
     return archive->getAvatarHash();
 }
 
+bool Core::RosterItem::isAvatarAutoGenerated() const
+{
+    return archive->getAutoAvatar();
+}
+
 QString Core::RosterItem::avatarPath() const
 {
     QString path(QStandardPaths::writableLocation(QStandardPaths::CacheLocation));
     path += "/" + account + "/" + jid + "/avatar." + archive->getAvatarType();
     return path;
 }
+
+bool Core::RosterItem::hasAvatar() const
+{
+    return archive->getHasAvatar();
+}
+
+void Core::RosterItem::setAvatar(const QByteArray& data)
+{
+    if (archive->setAvatar(data, false)) {
+        if (archive->getHasAvatar()) {
+            emit avatarChanged(Shared::Avatar::empty, "");
+        } else {
+            emit avatarChanged(Shared::Avatar::valid, avatarPath());
+        }
+    }
+}
+
+void Core::RosterItem::setAutoGeneratedAvatar()
+{
+    QImage image(96, 96, QImage::Format_ARGB32_Premultiplied);
+    QPainter painter(&image);
+    quint8 colorIndex = rand() % Shared::colorPalette.size();
+    const QColor& bg = Shared::colorPalette[colorIndex];
+    painter.fillRect(image.rect(), bg);
+    QFont f;
+    f.setBold(true);
+    f.setPixelSize(72);
+    painter.setFont(f);
+    if (bg.lightnessF() > 0.5) {
+        painter.setPen(Qt::black);
+    } else {
+        painter.setPen(Qt::white);
+    }
+    painter.drawText(image.rect(), Qt::AlignCenter | Qt::AlignVCenter, jid.at(0).toUpper());
+    QByteArray arr;
+    QBuffer stream(&arr);
+    stream.open(QBuffer::WriteOnly);
+    image.save(&stream, "PNG");
+    stream.close();
+    if (archive->setAvatar(arr, true)) {
+        emit avatarChanged(Shared::Avatar::autocreated, avatarPath());
+    }
+}
diff --git a/core/rosteritem.h b/core/rosteritem.h
index a2bc929..c0a490e 100644
--- a/core/rosteritem.h
+++ b/core/rosteritem.h
@@ -22,6 +22,9 @@
 #include <QObject>
 #include <QString>
 #include <QStandardPaths>
+#include <QImage>
+#include <QPainter>
+#include <QBuffer>
 
 #include <list>
 
@@ -60,14 +63,18 @@ public:
     void requestHistory(int count, const QString& before);
     void requestFromEmpty(int count, const QString& before);
     bool hasAvatar() const;
+    bool isAvatarAutoGenerated() const;
     QString avatarHash() const;
     QString avatarPath() const;
+    void setAvatar(const QByteArray& data);
+    void setAutoGeneratedAvatar();
     
 signals:
     void nameChanged(const QString& name);
     void subscriptionStateChanged(Shared::SubscriptionState state);
     void historyResponse(const std::list<Shared::Message>& messages);
     void needHistory(const QString& before, const QString& after, const QDateTime& afterTime = QDateTime());
+    void avatarChanged(Shared::Avatar, const QString& path);
     
 public:
     const QString jid;
diff --git a/global.h b/global.h
index 77f89bf..ab84655 100644
--- a/global.h
+++ b/global.h
@@ -24,6 +24,7 @@
 #include <deque>
 #include <QDateTime>
 #include <QDataStream>
+#include <QColor>
 
 namespace Shared {
     
@@ -69,6 +70,12 @@ enum class Role {
     moderator 
 };
 
+enum class Avatar {
+    empty,
+    autocreated,
+    valid
+};
+
 static const Availability availabilityHighest = offline;
 static const Availability availabilityLowest = online;
 
@@ -102,6 +109,45 @@ static const std::deque<QString> affiliationNames = {"Unspecified", "Outcast", "
 static const std::deque<QString> roleNames = {"Unspecified", "Nobody", "Visitor", "Participant", "Moderator"};
 QString generateUUID();
 
+static const std::vector<QColor> colorPalette = {
+    QColor(244, 27, 63),
+    QColor(21, 104, 156),
+    QColor(38, 156, 98),
+    QColor(247, 103, 101),
+    QColor(121, 37, 117),
+    QColor(242, 202, 33),
+    QColor(168, 22, 63),
+    QColor(35, 100, 52),
+    QColor(52, 161, 152),
+    QColor(239, 53, 111),
+    QColor(237, 234, 36),
+    QColor(153, 148, 194),
+    QColor(211, 102, 151),
+    QColor(194, 63, 118),
+    QColor(249, 149, 51),
+    QColor(244, 206, 109),
+    QColor(121, 105, 153),
+    QColor(244, 199, 30),
+    QColor(28, 112, 28),
+    QColor(172, 18, 20),
+    QColor(25, 66, 110),
+    QColor(25, 149, 104),
+    QColor(214, 148, 0),
+    QColor(203, 47, 57),
+    QColor(4, 54, 84),
+    QColor(116, 161, 97),
+    QColor(50, 68, 52),
+    QColor(237, 179, 20),
+    QColor(69, 114, 147),
+    QColor(242, 212, 31),
+    QColor(248, 19, 20),
+    QColor(84, 102, 84),
+    QColor(25, 53, 122),
+    QColor(91, 91, 109),
+    QColor(17, 17, 80),
+    QColor(54, 54, 94)
+};
+
 class Message {
 public:
     enum Type {

From dc1ec1c9d4a0d7135298ecc8ad8015bd2853bf1c Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Wed, 16 Oct 2019 22:38:35 +0300
Subject: [PATCH 005/281] receiving account owner vCard, displaying avatars in
 roster

---
 CMakeLists.txt        |   2 +-
 core/account.cpp      | 202 +++++++++++++++++++++++++++++++++++++-----
 core/account.h        |  15 +++-
 core/squawk.cpp       |  66 +++++++-------
 core/squawk.h         |   4 +-
 ui/models/account.cpp |  19 +++-
 ui/models/account.h   |   4 +
 ui/models/contact.cpp |  60 ++++++++++++-
 ui/models/contact.h   |   7 ++
 ui/models/item.cpp    |   2 +-
 ui/models/roster.cpp  |  31 ++++++-
 ui/squawk.cpp         |   4 +
 ui/squawk.ui          |   6 +-
 13 files changed, 358 insertions(+), 64 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 28d32d2..5010adf 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.0)
 project(squawk)
 
 set(CMAKE_INCLUDE_CURRENT_DIR ON)
-set(CMAKE_CXX_STANDARD 11)
+set(CMAKE_CXX_STANDARD 14)
 
 set(CMAKE_AUTOMOC ON)
 set(CMAKE_AUTOUIC ON)
diff --git a/core/account.cpp b/core/account.cpp
index bc7428c..a14e60f 100644
--- a/core/account.cpp
+++ b/core/account.cpp
@@ -41,7 +41,10 @@ Account::Account(const QString& p_login, const QString& p_server, const QString&
     maxReconnectTimes(0),
     reconnectTimes(0),
     queuedContacts(),
-    outOfRosterContacts()
+    outOfRosterContacts(),
+    avatarHash(),
+    avatarType(),
+    ownVCardRequestInProgress(false)
 {
     config.setUser(p_login);
     config.setDomain(p_server);
@@ -81,6 +84,52 @@ Account::Account(const QString& p_login, const QString& p_server, const QString&
     
     QXmppVCardManager& vm = client.vCardManager();
     QObject::connect(&vm, &QXmppVCardManager::vCardReceived, this, &Account::onVCardReceived);
+    //QObject::connect(&vm, &QXmppVCardManager::clientVCardReceived, this, &Account::onOwnVCardReceived); //for some reason it doesn't work, launching from common handler
+    
+    QString path(QStandardPaths::writableLocation(QStandardPaths::CacheLocation));
+    path += "/" + name;
+    QDir dir(path);
+    
+    if (!dir.exists()) {
+        bool res = dir.mkpath(path);
+        if (!res) {
+            qDebug() << "Couldn't create a cache directory for account" << name;
+            throw 22;
+        }
+    }
+    
+    QFile* avatar = new QFile(path + "/avatar.png");
+    QString type = "png";
+    if (!avatar->exists()) {
+        delete avatar;
+        avatar = new QFile(path + "/avatar.jpg");
+        QString type = "jpg";
+        if (!avatar->exists()) {
+            delete avatar;
+            avatar = new QFile(path + "/avatar.jpeg");
+            QString type = "jpeg";
+            if (!avatar->exists()) {
+                delete avatar;
+                avatar = new QFile(path + "/avatar.gif");
+                QString type = "gif";
+            }
+        }
+    }
+    
+    if (avatar->exists()) {
+        if (avatar->open(QFile::ReadOnly)) {
+            QCryptographicHash sha1(QCryptographicHash::Sha1);
+            sha1.addData(avatar);
+            avatarHash = sha1.result();
+            avatarType = type;
+        }
+    }
+    if (avatarType.size() != 0) {
+        presence.setVCardUpdateType(QXmppPresence::VCardUpdateValidPhoto);
+        presence.setPhotoHash(avatarHash.toUtf8());
+    } else {
+        presence.setVCardUpdateType(QXmppPresence::VCardUpdateNotReady);
+    }
 }
 
 Account::~Account()
@@ -191,6 +240,9 @@ QString Core::Account::getServer() const
 
 void Core::Account::onRosterReceived()
 {
+    client.vCardManager().requestClientVCard();         //TODO need to make sure server actually supports vCards
+    ownVCardRequestInProgress = true;
+    
     QXmppRosterManager& rm = client.rosterManager();
     QStringList bj = rm.getRosterBareJids();
     for (int i = 0; i < bj.size(); ++i) {
@@ -283,14 +335,15 @@ void Core::Account::addedAccount(const QString& jid)
         });
         
         if (contact->hasAvatar()) {
-            if (contact->isAvatarAutoGenerated()) {
-                cData.insert("avatarType", static_cast<uint>(Shared::Avatar::valid));
+            if (!contact->isAvatarAutoGenerated()) {
+                cData.insert("avatarState", static_cast<uint>(Shared::Avatar::valid));
             } else {
-                cData.insert("avatarType", static_cast<uint>(Shared::Avatar::autocreated));
+                cData.insert("avatarState", static_cast<uint>(Shared::Avatar::autocreated));
             }
             cData.insert("avatarPath", contact->avatarPath());
         } else {
-            cData.insert("avatarType", static_cast<uint>(Shared::Avatar::empty));
+            cData.insert("avatarState", static_cast<uint>(Shared::Avatar::empty));
+            cData.insert("avatarPath", "");
             client.vCardManager().requestVCard(jid);
             pendingVCardRequests.insert(jid);
         }
@@ -337,9 +390,9 @@ void Core::Account::handleNewConference(Core::Conference* contact)
     QObject::connect(contact, &Conference::removeParticipant, this, &Account::onMucRemoveParticipant);
 }
 
-void Core::Account::onPresenceReceived(const QXmppPresence& presence)
+void Core::Account::onPresenceReceived(const QXmppPresence& p_presence)
 {
-    QString id = presence.from();
+    QString id = p_presence.from();
     QStringList comps = id.split("/");
     QString jid = comps.front();
     QString resource = comps.back();
@@ -348,16 +401,35 @@ void Core::Account::onPresenceReceived(const QXmppPresence& presence)
     
     if (jid == myJid) {
         if (resource == getResource()) {
-            emit availabilityChanged(presence.availableStatusType());
+            emit availabilityChanged(p_presence.availableStatusType());
         } else {
-            qDebug() << "Received a presence for another resource of my " << name << " account, skipping";
+            if (!ownVCardRequestInProgress) {
+                switch (p_presence.vCardUpdateType()) {
+                    case QXmppPresence::VCardUpdateNone:            //this presence has nothing to do with photo
+                        break;
+                    case QXmppPresence::VCardUpdateNotReady:        //let's say the photo didn't change here
+                        break;
+                    case QXmppPresence::VCardUpdateNoPhoto:         //there is no photo, need to drop if any
+                        if (avatarType.size() > 0) {
+                            client.vCardManager().requestClientVCard();
+                            ownVCardRequestInProgress = true;
+                        }
+                        break;
+                    case QXmppPresence::VCardUpdateValidPhoto:      //there is a photo, need to load
+                        if (avatarHash != p_presence.photoHash()) {
+                            client.vCardManager().requestClientVCard();
+                            ownVCardRequestInProgress = true;
+                        }
+                        break;
+                }
+            }
         }
     } else {
         if (pendingVCardRequests.find(jid) == pendingVCardRequests.end()) {
             std::map<QString, Contact*>::const_iterator itr = contacts.find(jid);
             if (itr != contacts.end()) {
                 Contact* cnt = itr->second;
-                switch (presence.vCardUpdateType()) {
+                switch (p_presence.vCardUpdateType()) {
                     case QXmppPresence::VCardUpdateNone:            //this presence has nothing to do with photo
                         break;
                     case QXmppPresence::VCardUpdateNotReady:        //let's say the photo didn't change here
@@ -373,7 +445,7 @@ void Core::Account::onPresenceReceived(const QXmppPresence& presence)
                                 client.vCardManager().requestVCard(jid);
                                 pendingVCardRequests.insert(jid);
                             } else {
-                                if (cnt->avatarHash() != presence.photoHash()) {
+                                if (cnt->avatarHash() != p_presence.photoHash()) {
                                     client.vCardManager().requestVCard(jid);
                                     pendingVCardRequests.insert(jid);
                                 }
@@ -388,19 +460,19 @@ void Core::Account::onPresenceReceived(const QXmppPresence& presence)
         }
     }
     
-    switch (presence.type()) {
+    switch (p_presence.type()) {
         case QXmppPresence::Error:
-            qDebug() << "An error reported by presence from" << id << presence.error().text();
+            qDebug() << "An error reported by presence from" << id << p_presence.error().text();
             break;
         case QXmppPresence::Available:{
-            QDateTime lastInteraction = presence.lastUserInteraction();
+            QDateTime lastInteraction = p_presence.lastUserInteraction();
             if (!lastInteraction.isValid()) {
                 lastInteraction = QDateTime::currentDateTime();
             }
             emit addPresence(jid, resource, {
                 {"lastActivity", lastInteraction},
-                {"availability", presence.availableStatusType()},           //TODO check and handle invisible
-                {"status", presence.statusText()}
+                {"availability", p_presence.availableStatusType()},           //TODO check and handle invisible
+                {"status", p_presence.statusText()}
             });
         }
             break;
@@ -1267,7 +1339,11 @@ void Core::Account::onVCardReceived(const QXmppVCardIq& card)
     if (contItr == contacts.end()) {
         std::map<QString, Conference*>::const_iterator confItr = conferences.find(jid);
         if (confItr == conferences.end()) {
-            qDebug() << "received vCard" << jid << "doesn't belong to any of known contacts or conferences, skipping";
+            if (jid == getLogin() + "@" + getServer()) {
+                onOwnVCardReceived(card);
+            } else {
+                qDebug() << "received vCard" << jid << "doesn't belong to any of known contacts or conferences, skipping";
+            }
             return;
         } else {
             item = confItr->second;
@@ -1284,15 +1360,99 @@ void Core::Account::onVCardReceived(const QXmppVCardIq& card)
     }
 }
 
+void Core::Account::onOwnVCardReceived(const QXmppVCardIq& card)
+{
+    QByteArray ava = card.photo();
+    bool changed = false;
+    QString path = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/" + name + "/";
+    if (ava.size() > 0) {
+        QCryptographicHash sha1(QCryptographicHash::Sha1);
+        sha1.addData(ava);
+        QString newHash(sha1.result());
+        QMimeDatabase db;
+        QMimeType newType = db.mimeTypeForData(ava);
+        if (avatarType.size() > 0) {
+            if (avatarHash != newHash) {
+                QString oldPath = path + "avatar." + avatarType;
+                QFile oldAvatar(oldPath);
+                bool oldToRemove = false;
+                if (oldAvatar.exists()) {
+                    if (oldAvatar.rename(oldPath + ".bak")) {
+                        oldToRemove = true;
+                    } else {
+                        qDebug() << "Received new avatar for account" << name << "but can't get rid of the old one, doing nothing";
+                    }
+                }
+                QFile newAvatar(path + "avatar." + newType.preferredSuffix());
+                if (newAvatar.open(QFile::WriteOnly)) {
+                    newAvatar.write(ava);
+                    newAvatar.close();
+                    avatarHash = newHash;
+                    avatarType = newType.preferredSuffix();
+                    changed = true;
+                } else {
+                    qDebug() << "Received new avatar for account" << name << "but can't save it";
+                    if (oldToRemove) {
+                        qDebug() << "rolling back to the old avatar";
+                        if (!oldAvatar.rename(oldPath)) {
+                            qDebug() << "Couldn't roll back to the old avatar in account" << name;
+                        }
+                    }
+                }
+            }
+        } else {
+            QFile newAvatar(path + "avatar." + newType.preferredSuffix());
+            if (newAvatar.open(QFile::WriteOnly)) {
+                newAvatar.write(ava);
+                newAvatar.close();
+                avatarHash = newHash;
+                avatarType = newType.preferredSuffix();
+                changed = true;
+            } else {
+                qDebug() << "Received new avatar for account" << name << "but can't save it";
+            }
+        }
+    } else {
+        if (avatarType.size() > 0) {
+            QFile oldAvatar(path + "avatar." + avatarType);
+            if (!oldAvatar.remove()) {
+                qDebug() << "Received vCard for account" << name << "without avatar, but can't get rid of the file, doing nothing";
+            } else {
+                changed = true;
+            }
+        }
+    }
+    
+    if (changed) {
+        QMap<QString, QVariant> change;
+        if (avatarType.size() > 0) {
+            presence.setPhotoHash(avatarHash.toUtf8());
+            presence.setVCardUpdateType(QXmppPresence::VCardUpdateValidPhoto);
+            change.insert("avatarPath", path + "avatar." + avatarType);
+        } else {
+            presence.setPhotoHash("");
+            presence.setVCardUpdateType(QXmppPresence::VCardUpdateNoPhoto);
+            change.insert("avatarPath", "");
+        }
+        client.setClientPresence(presence);
+    }
+    
+    ownVCardRequestInProgress = false;
+}
+
+QString Core::Account::getAvatarPath() const
+{
+    return QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/" + name + "/" + "avatar." + avatarType;
+}
+
 void Core::Account::onContactAvatarChanged(Shared::Avatar type, const QString& path)
 {
     RosterItem* item = static_cast<RosterItem*>(sender());
     QMap<QString, QVariant> cData({
-        {"avatarType", static_cast<uint>(type)}
+        {"avatarState", static_cast<uint>(type)},
+        {"avatarPath", path}
     });
-    if (type != Shared::Avatar::empty) {
-        cData.insert("avatarPath", path);
-    }
     
     emit changeContact(item->jid, cData);
 }
+
diff --git a/core/account.h b/core/account.h
index c1af059..f9d8469 100644
--- a/core/account.h
+++ b/core/account.h
@@ -19,7 +19,13 @@
 #ifndef CORE_ACCOUNT_H
 #define CORE_ACCOUNT_H
 
-#include <QtCore/QObject>
+#include <QObject>
+#include <QCryptographicHash>
+#include <QFile>
+#include <QMimeDatabase>
+#include <QStandardPaths>
+#include <QDir>
+
 #include <map>
 #include <set>
 
@@ -56,6 +62,7 @@ public:
     QString getServer() const;
     QString getPassword() const;
     QString getResource() const;
+    QString getAvatarPath() const;
     Shared::Availability getAvailability() const;
     
     void setName(const QString& p_name);
@@ -82,6 +89,7 @@ public:
     void addRoomRequest(const QString& jid, const QString& nick, const QString& password, bool autoJoin);
     
 signals:
+    void changed(const QMap<QString, QVariant>& data);
     void connectionStateChanged(int);
     void availabilityChanged(int);
     void addGroup(const QString& name);
@@ -123,6 +131,10 @@ private:
     std::set<QString> outOfRosterContacts;
     std::set<QString> pendingVCardRequests;
     
+    QString avatarHash;
+    QString avatarType;
+    bool ownVCardRequestInProgress;
+    
 private slots:
     void onClientConnected();
     void onClientDisconnected();
@@ -165,6 +177,7 @@ private slots:
     void onMamLog(QXmppLogger::MessageType type, const QString &msg);
     
     void onVCardReceived(const QXmppVCardIq& card);
+    void onOwnVCardReceived(const QXmppVCardIq& card);
   
 private:
     void addedAccount(const QString &bareJid);
diff --git a/core/squawk.cpp b/core/squawk.cpp
index e624a8b..61b624d 100644
--- a/core/squawk.cpp
+++ b/core/squawk.cpp
@@ -28,9 +28,9 @@ Core::Squawk::Squawk(QObject* parent):
     amap(),
     network()
 {
-    connect(&network, SIGNAL(fileLocalPathResponse(const QString&, const QString&)), this, SIGNAL(fileLocalPathResponse(const QString&, const QString&)));
-    connect(&network, SIGNAL(downloadFileProgress(const QString&, qreal)), this, SIGNAL(downloadFileProgress(const QString&, qreal)));
-    connect(&network, SIGNAL(downloadFileError(const QString&, const QString&)), this, SIGNAL(downloadFileError(const QString&, const QString&)));
+    connect(&network, &NetworkAccess::fileLocalPathResponse, this, &Squawk::fileLocalPathResponse);
+    connect(&network, &NetworkAccess::downloadFileProgress, this, &Squawk::downloadFileProgress);
+    connect(&network, &NetworkAccess::downloadFileError, this, &Squawk::downloadFileError);
 }
 
 Core::Squawk::~Squawk()
@@ -110,36 +110,28 @@ void Core::Squawk::addAccount(const QString& login, const QString& server, const
     accounts.push_back(acc);
     amap.insert(std::make_pair(name, acc));
     
-    connect(acc, SIGNAL(connectionStateChanged(int)), this, SLOT(onAccountConnectionStateChanged(int)));
-    connect(acc, SIGNAL(error(const QString&)), this, SLOT(onAccountError(const QString&)));
-    connect(acc, SIGNAL(availabilityChanged(int)), this, SLOT(onAccountAvailabilityChanged(int)));
-    connect(acc, SIGNAL(addContact(const QString&, const QString&, const QMap<QString, QVariant>&)), 
-            this, SLOT(onAccountAddContact(const QString&, const QString&, const QMap<QString, QVariant>&)));
-    connect(acc, SIGNAL(addGroup(const QString&)), this, SLOT(onAccountAddGroup(const QString&)));
-    connect(acc, SIGNAL(removeGroup(const QString&)), this, SLOT(onAccountRemoveGroup(const QString&)));
-    connect(acc, SIGNAL(removeContact(const QString&)), this, SLOT(onAccountRemoveContact(const QString&)));
-    connect(acc, SIGNAL(removeContact(const QString&, const QString&)), this, SLOT(onAccountRemoveContact(const QString&, const QString&)));
-    connect(acc, SIGNAL(changeContact(const QString&, const QMap<QString, QVariant>&)), 
-            this, SLOT(onAccountChangeContact(const QString&, const QMap<QString, QVariant>&)));
-    connect(acc, SIGNAL(addPresence(const QString&, const QString&, const QMap<QString, QVariant>&)), 
-            this, SLOT(onAccountAddPresence(const QString&, const QString&, const QMap<QString, QVariant>&)));
-    connect(acc, SIGNAL(removePresence(const QString&, const QString&)), this, SLOT(onAccountRemovePresence(const QString&, const QString&)));
-    connect(acc, SIGNAL(message(const Shared::Message&)), this, SLOT(onAccountMessage(const Shared::Message&)));
-    connect(acc, SIGNAL(responseArchive(const QString&, const std::list<Shared::Message>&)), 
-            this, SLOT(onAccountResponseArchive(const QString&, const std::list<Shared::Message>&)));
+    connect(acc, &Account::connectionStateChanged, this, &Squawk::onAccountConnectionStateChanged);
+    connect(acc, &Account::changed, this, &Squawk::onAccountChanged);
+    connect(acc, &Account::error, this, &Squawk::onAccountError);
+    connect(acc, &Account::availabilityChanged, this, &Squawk::onAccountAvailabilityChanged);
+    connect(acc, &Account::addContact, this, &Squawk::onAccountAddContact);
+    connect(acc, &Account::addGroup, this, &Squawk::onAccountAddGroup);
+    connect(acc, &Account::removeGroup, this, &Squawk::onAccountRemoveGroup);
+    connect(acc, qOverload<const QString&, const QString&>(&Account::removeContact), this, qOverload<const QString&, const QString&>(&Squawk::onAccountRemoveContact));
+    connect(acc, qOverload<const QString&>(&Account::removeContact), this, qOverload<const QString&>(&Squawk::onAccountRemoveContact));
+    connect(acc, &Account::changeContact, this, &Squawk::onAccountChangeContact);
+    connect(acc, &Account::addPresence, this, &Squawk::onAccountAddPresence);
+    connect(acc, &Account::removePresence, this, &Squawk::onAccountRemovePresence);
+    connect(acc, &Account::message, this, &Squawk::onAccountMessage);
+    connect(acc, &Account::responseArchive, this, &Squawk::onAccountResponseArchive);
+
+    connect(acc, &Account::addRoom, this, &Squawk::onAccountAddRoom);
+    connect(acc, &Account::changeRoom, this, &Squawk::onAccountChangeRoom);
+    connect(acc, &Account::removeRoom, this, &Squawk::onAccountRemoveRoom);
     
-    connect(acc, SIGNAL(addRoom(const QString&, const QMap<QString, QVariant>&)), 
-            this, SLOT(onAccountAddRoom(const QString&, const QMap<QString, QVariant>&)));
-    connect(acc, SIGNAL(changeRoom(const QString&, const QMap<QString, QVariant>&)), 
-            this, SLOT(onAccountChangeRoom(const QString&, const QMap<QString, QVariant>&)));
-    connect(acc, SIGNAL(removeRoom(const QString&)), this, SLOT(onAccountRemoveRoom(const QString&)));
-    
-    connect(acc, SIGNAL(addRoomParticipant(const QString&, const QString&, const QMap<QString, QVariant>&)), 
-            this, SLOT(onAccountAddRoomPresence(const QString&, const QString&, const QMap<QString, QVariant>&)));
-    connect(acc, SIGNAL(changeRoomParticipant(const QString&, const QString&, const QMap<QString, QVariant>&)), 
-            this, SLOT(onAccountChangeRoomPresence(const QString&, const QString&, const QMap<QString, QVariant>&)));
-    connect(acc, SIGNAL(removeRoomParticipant(const QString&, const QString&)), 
-            this, SLOT(onAccountRemoveRoomPresence(const QString&, const QString&)));
+    connect(acc, &Account::addRoomParticipant, this, &Squawk::onAccountAddRoomPresence);
+    connect(acc, &Account::changeRoomParticipant, this, &Squawk::onAccountChangeRoomPresence);
+    connect(acc, &Account::removeRoomParticipant, this, &Squawk::onAccountRemoveRoomPresence);
     
     
     QMap<QString, QVariant> map = {
@@ -150,8 +142,10 @@ void Core::Squawk::addAccount(const QString& login, const QString& server, const
         {"resource", resource},
         {"state", Shared::disconnected},
         {"offline", Shared::offline},
-        {"error", ""}
+        {"error", ""},
+        {"avatarPath", acc->getAvatarPath()}
     };
+    
     emit newAccount(map);
 }
 
@@ -263,6 +257,12 @@ void Core::Squawk::onAccountAvailabilityChanged(int state)
     emit changeAccount(acc->getName(), {{"availability", state}});
 }
 
+void Core::Squawk::onAccountChanged(const QMap<QString, QVariant>& data)
+{    
+    Account* acc = static_cast<Account*>(sender());
+    emit changeAccount(acc->getName(), data);
+}
+
 void Core::Squawk::onAccountMessage(const Shared::Message& data)
 {
     Account* acc = static_cast<Account*>(sender());
diff --git a/core/squawk.h b/core/squawk.h
index d6b5d2a..9435ef9 100644
--- a/core/squawk.h
+++ b/core/squawk.h
@@ -23,7 +23,8 @@
 #include <QString>
 #include <QVariant>
 #include <QMap>
-#include <deque>
+#include <QtGlobal>
+
 #include <deque>
 
 #include "account.h"
@@ -106,6 +107,7 @@ private:
 private slots:
     void onAccountConnectionStateChanged(int state);
     void onAccountAvailabilityChanged(int state);
+    void onAccountChanged(const QMap<QString, QVariant>& data);
     void onAccountAddGroup(const QString& name);
     void onAccountError(const QString& text);
     void onAccountRemoveGroup(const QString& name);
diff --git a/ui/models/account.cpp b/ui/models/account.cpp
index b8758c2..9a30db7 100644
--- a/ui/models/account.cpp
+++ b/ui/models/account.cpp
@@ -26,6 +26,7 @@ Models::Account::Account(const QMap<QString, QVariant>& data, Models::Item* pare
     server(data.value("server").toString()),
     resource(data.value("resource").toString()),
     error(data.value("error").toString()),
+    avatarPath(data.value("avatarPath").toString()),
     state(Shared::disconnected),
     availability(Shared::offline)
 {
@@ -162,6 +163,8 @@ QVariant Models::Account::data(int column) const
             return QCoreApplication::translate("Global", Shared::availabilityNames[availability].toLatin1());
         case 7:
             return resource;
+        case 8:
+            return avatarPath;
         default:
             return QVariant();
     }
@@ -169,7 +172,7 @@ QVariant Models::Account::data(int column) const
 
 int Models::Account::columnCount() const
 {
-    return 8;
+    return 9;
 }
 
 void Models::Account::update(const QString& field, const QVariant& value)
@@ -190,6 +193,8 @@ void Models::Account::update(const QString& field, const QVariant& value)
         setResource(value.toString());
     } else if (field == "error") {
         setError(value.toString());
+    } else if (field == "avatarPath") {
+        setAvatarPath(value.toString());
     }
 }
 
@@ -224,3 +229,15 @@ void Models::Account::toOfflineState()
     setAvailability(Shared::offline);
     Item::toOfflineState();
 }
+
+QString Models::Account::getAvatarPath()
+{
+    return avatarPath;
+}
+
+void Models::Account::setAvatarPath(const QString& path)
+{
+    avatarPath = path;
+    changed(8);             //it's uncoditional because the path doesn't change when one avatar of the same type replaces another, sha1 sums checks are on the backend
+}
+
diff --git a/ui/models/account.h b/ui/models/account.h
index fbdf204..8be7c45 100644
--- a/ui/models/account.h
+++ b/ui/models/account.h
@@ -50,6 +50,9 @@ namespace Models {
         void setError(const QString& p_resource);
         QString getError() const;
         
+        void setAvatarPath(const QString& path);
+        QString getAvatarPath();
+        
         void setAvailability(Shared::Availability p_avail);
         void setAvailability(unsigned int p_avail);
         Shared::Availability getAvailability() const;
@@ -67,6 +70,7 @@ namespace Models {
         QString server;
         QString resource;
         QString error;
+        QString avatarPath;
         Shared::ConnectionState state;
         Shared::Availability availability;
         
diff --git a/ui/models/contact.cpp b/ui/models/contact.cpp
index 5f4ba21..9b5b436 100644
--- a/ui/models/contact.cpp
+++ b/ui/models/contact.cpp
@@ -25,14 +25,26 @@ Models::Contact::Contact(const QString& p_jid ,const QMap<QString, QVariant> &da
     jid(p_jid),
     availability(Shared::offline),
     state(Shared::none),
+    avatarState(Shared::Avatar::empty),
     presences(),
     messages(),
-    childMessages(0)
+    childMessages(0),
+    status(),
+    avatarPath()
 {
     QMap<QString, QVariant>::const_iterator itr = data.find("state");
     if (itr != data.end()) {
         setState(itr.value().toUInt());
     }
+    
+    itr = data.find("avatarState");
+    if (itr != data.end()) {
+        setAvatarState(itr.value().toUInt());
+    }
+    itr = data.find("avatarPath");
+    if (itr != data.end()) {
+        setAvatarPath(itr.value().toString());
+    }
 }
 
 Models::Contact::~Contact()
@@ -100,7 +112,7 @@ void Models::Contact::setStatus(const QString& p_state)
 
 int Models::Contact::columnCount() const
 {
-    return 6;
+    return 8;
 }
 
 QVariant Models::Contact::data(int column) const
@@ -118,6 +130,10 @@ QVariant Models::Contact::data(int column) const
             return getMessagesCount();
         case 5:
             return getStatus();
+        case 6:
+            return static_cast<quint8>(getAvatarState());
+        case 7:
+            return getAvatarPath();
         default:
             return QVariant();
     }
@@ -142,6 +158,10 @@ void Models::Contact::update(const QString& field, const QVariant& value)
         setAvailability(value.toUInt());
     } else if (field == "state") {
         setState(value.toUInt());
+    } else if (field == "avatarState") {
+        setAvatarState(value.toUInt());
+    } else if (field == "avatarPath") {
+        setAvatarPath(value.toString());
     }
 }
 
@@ -348,3 +368,39 @@ Models::Contact::Contact(const Models::Contact& other):
     
     refresh();
 }
+
+QString Models::Contact::getAvatarPath() const
+{
+    return avatarPath;
+}
+
+Shared::Avatar Models::Contact::getAvatarState() const
+{
+    return avatarState;
+}
+
+void Models::Contact::setAvatarPath(const QString& path)
+{
+    if (path != avatarPath) {
+        avatarPath = path;
+        changed(7);
+    }
+}
+
+void Models::Contact::setAvatarState(Shared::Avatar p_state)
+{
+    if (avatarState != p_state) {
+        avatarState = p_state;
+        changed(6);
+    }
+}
+
+void Models::Contact::setAvatarState(unsigned int p_state)
+{
+    if (p_state <= static_cast<quint8>(Shared::Avatar::valid)) {
+        Shared::Avatar state = static_cast<Shared::Avatar>(p_state);
+        setAvatarState(state);
+    } else {
+        qDebug() << "An attempt to set invalid avatar state" << p_state << "to the contact" << jid << ", skipping";
+    }
+}
diff --git a/ui/models/contact.h b/ui/models/contact.h
index 6f0a5fc..fda893f 100644
--- a/ui/models/contact.h
+++ b/ui/models/contact.h
@@ -40,6 +40,8 @@ public:
     QString getJid() const;
     Shared::Availability getAvailability() const;
     Shared::SubscriptionState getState() const;
+    Shared::Avatar getAvatarState() const;
+    QString getAvatarPath() const;
     QIcon getStatusIcon(bool big = false) const;
     
     int columnCount() const override;
@@ -75,6 +77,9 @@ protected:
     void setAvailability(unsigned int p_state);
     void setState(Shared::SubscriptionState p_state);
     void setState(unsigned int p_state);
+    void setAvatarState(Shared::Avatar p_state);
+    void setAvatarState(unsigned int p_state);
+    void setAvatarPath(const QString& path);
     void setJid(const QString p_jid);
     void setStatus(const QString& p_state);
     
@@ -82,10 +87,12 @@ private:
     QString jid;
     Shared::Availability availability;
     Shared::SubscriptionState state;
+    Shared::Avatar avatarState;
     QMap<QString, Presence*> presences;
     Messages messages;
     unsigned int childMessages;
     QString status;
+    QString avatarPath;
 };
 
 }
diff --git a/ui/models/item.cpp b/ui/models/item.cpp
index 7c0cb96..7ab877a 100644
--- a/ui/models/item.cpp
+++ b/ui/models/item.cpp
@@ -141,7 +141,7 @@ const Models::Item * Models::Item::parentItemConst() const
 
 int Models::Item::columnCount() const
 {
-    return 1;
+    return 2;
 }
 
 QString Models::Item::getName() const
diff --git a/ui/models/roster.cpp b/ui/models/roster.cpp
index f252583..5ea5b2e 100644
--- a/ui/models/roster.cpp
+++ b/ui/models/roster.cpp
@@ -68,6 +68,9 @@ QVariant Models::Roster::data (const QModelIndex& index, int role) const
     switch (role) {
         case Qt::DisplayRole:
         {
+            if (index.column() != 0) {
+                break;
+            }
             switch (item->type) {
                 case Item::group: {
                     Group* gr = static_cast<Group*>(item);
@@ -91,26 +94,50 @@ QVariant Models::Roster::data (const QModelIndex& index, int role) const
         case Qt::DecorationRole:
             switch (item->type) {
                 case Item::account: {
+                    quint8 col = index.column();
                     Account* acc = static_cast<Account*>(item);
-                    result = acc->getStatusIcon(false);
+                    if (col == 0) {
+                        result = acc->getStatusIcon(false);
+                    } else if (col == 1) {
+                        QString path = acc->getAvatarPath();
+                        if (path.size() > 0) {
+                            result = QIcon(path);
+                        }
+                    }
                 }
                     break;
                 case Item::contact: {
                     Contact* contact = static_cast<Contact*>(item);
-                    result = contact->getStatusIcon(false);
+                    quint8 col = index.column();
+                    if (col == 0) {
+                        result = contact->getStatusIcon(false);
+                    } else if (col == 1) {
+                        if (contact->getAvatarState() != Shared::Avatar::empty) {
+                            result = QIcon(contact->getAvatarPath());
+                        }
+                    }
                 }
                     break;
                 case Item::presence: {
+                    if (index.column() != 0) {
+                        break;
+                    }
                     Presence* presence = static_cast<Presence*>(item);
                     result = presence->getStatusIcon(false);
                 }
                     break;
                 case Item::room: {
+                    if (index.column() != 0) {
+                        break;
+                    }
                     Room* room = static_cast<Room*>(item);
                     result = room->getStatusIcon(false);
                 }
                     break;
                 case Item::participant: {
+                    if (index.column() != 0) {
+                        break;
+                    }
                     Participant* p = static_cast<Participant*>(item);
                     result = p->getStatusIcon(false);
                 }
diff --git a/ui/squawk.cpp b/ui/squawk.cpp
index e382ebd..455812f 100644
--- a/ui/squawk.cpp
+++ b/ui/squawk.cpp
@@ -34,6 +34,10 @@ Squawk::Squawk(QWidget *parent) :
     m_ui->setupUi(this);
     m_ui->roster->setModel(&rosterModel);
     m_ui->roster->setContextMenuPolicy(Qt::CustomContextMenu);
+    m_ui->roster->setColumnWidth(1, 20);
+    m_ui->roster->setIconSize(QSize(20, 20));
+    m_ui->roster->header()->setStretchLastSection(false);
+    m_ui->roster->header()->setSectionResizeMode(0, QHeaderView::Stretch);
     
     for (unsigned int i = Shared::availabilityLowest; i < Shared::availabilityHighest + 1; ++i) {
         Shared::Availability av = static_cast<Shared::Availability>(i);
diff --git a/ui/squawk.ui b/ui/squawk.ui
index 642ded2..04cf999 100644
--- a/ui/squawk.ui
+++ b/ui/squawk.ui
@@ -51,6 +51,9 @@
       <property name="frameShadow">
        <enum>QFrame::Sunken</enum>
       </property>
+      <property name="uniformRowHeights">
+       <bool>true</bool>
+      </property>
       <property name="expandsOnDoubleClick">
        <bool>false</bool>
       </property>
@@ -122,7 +125,8 @@
     <bool>false</bool>
    </property>
    <property name="icon">
-    <iconset theme="resource-group-new"/>
+    <iconset theme="resource-group-new">
+     <normaloff>.</normaloff>.</iconset>
    </property>
    <property name="text">
     <string>Add conference</string>

From 46e74ad5e8cf5f842bbf082a7542dbfff7728baa Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Thu, 17 Oct 2019 23:54:27 +0300
Subject: [PATCH 006/281] initial vCard class, signal-slots refactoring

---
 global.cpp |  57 +++++++++++++++++++++++++++
 global.h   |  72 ++++++++++++++++++++++++++++++++++
 main.cpp   | 113 +++++++++++++++++++++++------------------------------
 3 files changed, 177 insertions(+), 65 deletions(-)

diff --git a/global.cpp b/global.cpp
index 8ab74d8..e9156fd 100644
--- a/global.cpp
+++ b/global.cpp
@@ -296,6 +296,63 @@ bool Shared::Message::storable() const
     return id.size() > 0 && (body.size() > 0 || oob.size()) > 0;
 }
 
+Shared::VCard::Contact::Contact(Shared::VCard::Contact::Role p_role, bool p_prefered):
+    role(p_role),
+    prefered(p_prefered)
+{}
+
+Shared::VCard::Email::Email(const QString& addr, Shared::VCard::Contact::Role p_role, bool p_prefered):
+    Contact(p_role, p_prefered),
+    address(addr)
+{}
+
+Shared::VCard::Phone::Phone(const QString& nmbr, Shared::VCard::Phone::Type p_type, Shared::VCard::Contact::Role p_role, bool p_prefered):
+    Contact(p_role, p_prefered),
+    number(nmbr),
+    type(p_type)
+{}
+
+Shared::VCard::Address::Address(const QString& zCode, const QString& cntry, const QString& rgn, const QString& lclty, const QString& strt, const QString& ext, Shared::VCard::Contact::Role p_role, bool p_prefered):
+    Contact(p_role, p_prefered),
+    zipCode(zCode),
+    country(cntry),
+    region(rgn),
+    locality(lclty),
+    street(strt),
+    external(ext)
+{}
+
+Shared::VCard::VCard():
+    firstName(),
+    middleName(),
+    lastName(),
+    nickName(),
+    description(),
+    birthday(),
+    photoType(Avatar::empty),
+    photoPath(),
+    receivingTime(QDateTime::currentDateTime()),
+    emails(),
+    phones(),
+    addresses()
+{}
+
+Shared::VCard::VCard(const QDateTime& creationTime):
+    firstName(),
+    middleName(),
+    lastName(),
+    nickName(),
+    description(),
+    birthday(),
+    photoType(Avatar::empty),
+    photoPath(),
+    receivingTime(creationTime),
+    emails(),
+    phones(),
+    addresses()
+{
+}
+
 QIcon Shared::availabilityIcon(Shared::Availability av, bool big)
 {
     const std::deque<QString>& fallback = QApplication::palette().window().color().lightnessF() > 0.5 ? 
diff --git a/global.h b/global.h
index ab84655..e0acd91 100644
--- a/global.h
+++ b/global.h
@@ -215,6 +215,78 @@ private:
     QString oob;
 };
 
+class VCard {
+    class Contact {
+    public:
+        enum Role {
+            none,
+            home,
+            work
+        };
+        
+        Contact(Role p_role = none, bool p_prefered = false);
+        
+        Role role;
+        bool prefered;
+    };
+public:
+    class Email : public Contact {
+    public:
+        Email(const QString& address, Role p_role = none, bool p_prefered = false);
+        
+        QString address;
+    };
+    class Phone : public Contact {
+        enum Type {
+            fax,
+            pager,
+            voice,
+            cell,
+            video,
+            modem
+        };
+        Phone(const QString& number, Type p_type = voice, Role p_role = none, bool p_prefered = false);
+        
+        QString number;
+        Type type;
+    };
+    class Address : public Contact {
+        Address(
+            const QString& zCode = "", 
+            const QString& cntry = "", 
+            const QString& rgn = "", 
+            const QString& lclty = "", 
+            const QString& strt = "", 
+            const QString& ext = "", 
+            Role p_role = none, 
+            bool p_prefered = false
+        );
+        
+        QString zipCode;
+        QString country;
+        QString region;
+        QString locality;
+        QString street;
+        QString external;
+    };
+    VCard();
+    VCard(const QDateTime& creationTime);
+    
+private:
+    QString firstName;
+    QString middleName;
+    QString lastName;
+    QString nickName;
+    QString description;
+    QDate birthday;
+    Avatar photoType;
+    QString photoPath;
+    QDateTime receivingTime;
+    std::deque<Email> emails;
+    std::deque<Phone> phones;
+    std::deque<Address> addresses;
+};
+
 static const std::deque<QString> fallbackAvailabilityThemeIconsLightBig = {
     ":images/fallback/light/big/online.svg",
     ":images/fallback/light/big/away.svg",
diff --git a/main.cpp b/main.cpp
index 0b8c9a4..83db32a 100644
--- a/main.cpp
+++ b/main.cpp
@@ -30,6 +30,7 @@
 int main(int argc, char *argv[])
 {
     qRegisterMetaType<Shared::Message>("Shared::Message");
+    qRegisterMetaType<Shared::VCard>("Shared::VCard");
     qRegisterMetaType<std::list<Shared::Message>>("std::list<Shared::Message>");
     qRegisterMetaType<QSet<QString>>("QSet<QString>");
     
@@ -78,76 +79,58 @@ int main(int argc, char *argv[])
     QThread* coreThread = new QThread();
     squawk->moveToThread(coreThread);
     
-    QObject::connect(coreThread, SIGNAL(started()), squawk, SLOT(start()));
-    QObject::connect(&app, SIGNAL(aboutToQuit()), squawk, SLOT(stop()));
-    QObject::connect(squawk, SIGNAL(quit()), coreThread, SLOT(quit()));
-    QObject::connect(coreThread, SIGNAL(finished()), squawk, SLOT(deleteLater()));
+    QObject::connect(coreThread, &QThread::started, squawk, &Core::Squawk::start);
+    QObject::connect(&app, &QApplication::aboutToQuit, squawk, &Core::Squawk::stop);
+    QObject::connect(squawk, &Core::Squawk::quit, coreThread, &QThread::quit);
+    QObject::connect(coreThread, &QThread::finished, squawk, &Core::Squawk::deleteLater);
     
-    QObject::connect(&w, SIGNAL(newAccountRequest(const QMap<QString, QVariant>&)), squawk, SLOT(newAccountRequest(const QMap<QString, QVariant>&)));
-    QObject::connect(&w, SIGNAL(modifyAccountRequest(const QString&, const QMap<QString, QVariant>&)), 
-                     squawk, SLOT(modifyAccountRequest(const QString&, const QMap<QString, QVariant>&)));
-    QObject::connect(&w, SIGNAL(removeAccountRequest(const QString&)), squawk, SLOT(removeAccountRequest(const QString&)));
-    QObject::connect(&w, SIGNAL(connectAccount(const QString&)), squawk, SLOT(connectAccount(const QString&)));
-    QObject::connect(&w, SIGNAL(disconnectAccount(const QString&)), squawk, SLOT(disconnectAccount(const QString&)));
-    QObject::connect(&w, SIGNAL(changeState(int)), squawk, SLOT(changeState(int)));
-    QObject::connect(&w, SIGNAL(sendMessage(const QString&, const Shared::Message&)), squawk, SLOT(sendMessage(const QString&, const Shared::Message&)));
-    QObject::connect(&w, SIGNAL(requestArchive(const QString&, const QString&, int, const QString&)), 
-                     squawk, SLOT(requestArchive(const QString&, const QString&, int, const QString&)));
-    QObject::connect(&w, SIGNAL(subscribeContact(const QString&, const QString&, const QString&)), 
-                     squawk, SLOT(subscribeContact(const QString&, const QString&, const QString&)));
-    QObject::connect(&w, SIGNAL(unsubscribeContact(const QString&, const QString&, const QString&)), 
-                     squawk, SLOT(unsubscribeContact(const QString&, const QString&, const QString&)));
-    QObject::connect(&w, SIGNAL(addContactRequest(const QString&, const QString&, const QString&, const QSet<QString>&)), 
-                     squawk, SLOT(addContactRequest(const QString&, const QString&, const QString&, const QSet<QString>&)));
-    QObject::connect(&w, SIGNAL(removeContactRequest(const QString&, const QString&)), 
-                     squawk, SLOT(removeContactRequest(const QString&, const QString&)));
-    QObject::connect(&w, SIGNAL(setRoomJoined(const QString&, const QString&, bool)), squawk, SLOT(setRoomJoined(const QString&, const QString&, bool)));
-    QObject::connect(&w, SIGNAL(setRoomAutoJoin(const QString&, const QString&, bool)), squawk, SLOT(setRoomAutoJoin(const QString&, const QString&, bool)));
-    
-    QObject::connect(&w, SIGNAL(removeRoomRequest(const QString&, const QString&)), 
-                     squawk, SLOT(removeRoomRequest(const QString&, const QString&)));
-    QObject::connect(&w, SIGNAL(addRoomRequest(const QString&, const QString&, const QString&, const QString&, bool)), 
-                     squawk, SLOT(addRoomRequest(const QString&, const QString&, const QString&, const QString&, bool)));
-    QObject::connect(&w, SIGNAL(fileLocalPathRequest(const QString&, const QString&)), squawk, SLOT(fileLocalPathRequest(const QString&, const QString&)));
-    QObject::connect(&w, SIGNAL(downloadFileRequest(const QString&, const QString&)), squawk, SLOT(downloadFileRequest(const QString&, const QString&)));
+    QObject::connect(&w, &Squawk::newAccountRequest, squawk, &Core::Squawk::newAccountRequest);
+    QObject::connect(&w, &Squawk::modifyAccountRequest, squawk, &Core::Squawk::modifyAccountRequest);
+    QObject::connect(&w, &Squawk::removeAccountRequest, squawk, &Core::Squawk::removeAccountRequest);
+    QObject::connect(&w, &Squawk::connectAccount, squawk, &Core::Squawk::connectAccount);
+    QObject::connect(&w, &Squawk::disconnectAccount, squawk, &Core::Squawk::disconnectAccount);
+    QObject::connect(&w, &Squawk::changeState, squawk, &Core::Squawk::changeState);
+    QObject::connect(&w, &Squawk::sendMessage, squawk, &Core::Squawk::sendMessage);
+    QObject::connect(&w, &Squawk::requestArchive, squawk, &Core::Squawk::requestArchive);
+    QObject::connect(&w, &Squawk::subscribeContact, squawk, &Core::Squawk::subscribeContact);
+    QObject::connect(&w, &Squawk::unsubscribeContact, squawk, &Core::Squawk::unsubscribeContact);
+    QObject::connect(&w, &Squawk::addContactRequest, squawk, &Core::Squawk::addContactRequest);
+    QObject::connect(&w, &Squawk::removeContactRequest, squawk, &Core::Squawk::removeContactRequest);
+    QObject::connect(&w, &Squawk::setRoomJoined, squawk, &Core::Squawk::setRoomJoined);
+    QObject::connect(&w, &Squawk::setRoomAutoJoin, squawk, &Core::Squawk::setRoomAutoJoin);
+    QObject::connect(&w, &Squawk::removeRoomRequest, squawk, &Core::Squawk::removeRoomRequest);
+    QObject::connect(&w, &Squawk::addRoomRequest, squawk, &Core::Squawk::addRoomRequest);
+    QObject::connect(&w, &Squawk::fileLocalPathRequest, squawk, &Core::Squawk::fileLocalPathRequest);
+    QObject::connect(&w, &Squawk::downloadFileRequest, squawk, &Core::Squawk::downloadFileRequest);
     QObject::connect(&w, &Squawk::addContactToGroupRequest, squawk, &Core::Squawk::addContactToGroupRequest);
     QObject::connect(&w, &Squawk::removeContactFromGroupRequest, squawk, &Core::Squawk::removeContactFromGroupRequest);
     QObject::connect(&w, &Squawk::renameContactRequest, squawk, &Core::Squawk::renameContactRequest);
     
-    QObject::connect(squawk, SIGNAL(newAccount(const QMap<QString, QVariant>&)), &w, SLOT(newAccount(const QMap<QString, QVariant>&)));
-    QObject::connect(squawk, SIGNAL(addContact(const QString&, const QString&, const QString&, const QMap<QString, QVariant>&)), 
-                     &w, SLOT(addContact(const QString&, const QString&, const QString&, const QMap<QString, QVariant>&)));
-    QObject::connect(squawk, SIGNAL(changeAccount(const QString&, const QMap<QString, QVariant>&)), 
-                     &w, SLOT(changeAccount(const QString&, const QMap<QString, QVariant>&)));
-    QObject::connect(squawk, SIGNAL(removeAccount(const QString&)), &w, SLOT(removeAccount(const QString&)));
-    QObject::connect(squawk, SIGNAL(addGroup(const QString&, const QString&)), &w, SLOT(addGroup(const QString&, const QString&)));
-    QObject::connect(squawk, SIGNAL(removeGroup(const QString&, const QString&)), &w, SLOT(removeGroup(const QString&, const QString&)));
-    QObject::connect(squawk, SIGNAL(removeContact(const QString&, const QString&)), &w, SLOT(removeContact(const QString&, const QString&)));
-    QObject::connect(squawk, SIGNAL(removeContact(const QString&, const QString&, const QString&)), &w, SLOT(removeContact(const QString&, const QString&, const QString&)));
-    QObject::connect(squawk, SIGNAL(changeContact(const QString&, const QString&, const QMap<QString, QVariant>&)), 
-                     &w, SLOT(changeContact(const QString&, const QString&, const QMap<QString, QVariant>&)));
-    QObject::connect(squawk, SIGNAL(addPresence(const QString&, const QString&, const QString&, const QMap<QString, QVariant>&)), 
-                     &w, SLOT(addPresence(const QString&, const QString&, const QString&, const QMap<QString, QVariant>&)));
-    QObject::connect(squawk, SIGNAL(removePresence(const QString&, const QString&, const QString&)), &w, SLOT(removePresence(const QString&, const QString&, const QString&)));
-    QObject::connect(squawk, SIGNAL(stateChanged(int)), &w, SLOT(stateChanged(int)));
-    QObject::connect(squawk, SIGNAL(accountMessage(const QString&, const Shared::Message&)), &w, SLOT(accountMessage(const QString&, const Shared::Message&)));
-    QObject::connect(squawk, SIGNAL(responseArchive(const QString&, const QString&, const std::list<Shared::Message>&)), 
-                     &w, SLOT(responseArchive(const QString&, const QString&, const std::list<Shared::Message>&)));
-    
-    QObject::connect(squawk, SIGNAL(addRoom(const QString&, const QString&, const QMap<QString, QVariant>&)), 
-                     &w, SLOT(addRoom(const QString&, const QString&, const QMap<QString, QVariant>&)));
-    QObject::connect(squawk, SIGNAL(changeRoom(const QString&, const QString&, const QMap<QString, QVariant>&)), 
-                     &w, SLOT(changeRoom(const QString&, const QString&, const QMap<QString, QVariant>&)));
-    QObject::connect(squawk, SIGNAL(removeRoom(const QString&, const QString&)), &w, SLOT(removeRoom(const QString&, const QString&)));
-    QObject::connect(squawk, SIGNAL(addRoomParticipant(const QString&, const QString&, const QString&, const QMap<QString, QVariant>&)), 
-                     &w, SLOT(addRoomParticipant(const QString&, const QString&, const QString&, const QMap<QString, QVariant>&)));
-    QObject::connect(squawk, SIGNAL(changeRoomParticipant(const QString&, const QString&, const QString&, const QMap<QString, QVariant>&)), 
-                     &w, SLOT(changeRoomParticipant(const QString&, const QString&, const QString&, const QMap<QString, QVariant>&)));
-    QObject::connect(squawk, SIGNAL(removeRoomParticipant(const QString&, const QString&, const QString&)), 
-                     &w, SLOT(removeRoomParticipant(const QString&, const QString&, const QString&)));
-    QObject::connect(squawk, SIGNAL(fileLocalPathResponse(const QString&, const QString&)), &w, SLOT(fileLocalPathResponse(const QString&, const QString&)));
-    QObject::connect(squawk, SIGNAL(downloadFileProgress(const QString&, qreal)), &w, SLOT(downloadFileProgress(const QString&, qreal)));
-    QObject::connect(squawk, SIGNAL(downloadFileError(const QString&, const QString&)), &w, SLOT(downloadFileError(const QString&, const QString&)));
+    QObject::connect(squawk, &Core::Squawk::newAccount, &w, &Squawk::newAccount);
+    QObject::connect(squawk, &Core::Squawk::addContact, &w, &Squawk::addContact);
+    QObject::connect(squawk, &Core::Squawk::changeAccount, &w, &Squawk::changeAccount);
+    QObject::connect(squawk, &Core::Squawk::removeAccount, &w, &Squawk::removeAccount);
+    QObject::connect(squawk, &Core::Squawk::addGroup, &w, &Squawk::addGroup);
+    QObject::connect(squawk, &Core::Squawk::removeGroup, &w, &Squawk::removeGroup);
+    QObject::connect(squawk, qOverload<const QString&, const QString&>(&Core::Squawk::removeContact), 
+                     &w, qOverload<const QString&, const QString&>(&Squawk::removeContact));
+    QObject::connect(squawk, qOverload<const QString&, const QString&, const QString&>(&Core::Squawk::removeContact), 
+                     &w, qOverload<const QString&, const QString&, const QString&>(&Squawk::removeContact));
+    QObject::connect(squawk, &Core::Squawk::changeContact, &w, &Squawk::changeContact);
+    QObject::connect(squawk, &Core::Squawk::addPresence, &w, &Squawk::addPresence);
+    QObject::connect(squawk, &Core::Squawk::removePresence, &w, &Squawk::removePresence);
+    QObject::connect(squawk, &Core::Squawk::stateChanged, &w, &Squawk::stateChanged);
+    QObject::connect(squawk, &Core::Squawk::accountMessage, &w, &Squawk::accountMessage);
+    QObject::connect(squawk, &Core::Squawk::responseArchive, &w, &Squawk::responseArchive);
+    QObject::connect(squawk, &Core::Squawk::addRoom, &w, &Squawk::addRoom);
+    QObject::connect(squawk, &Core::Squawk::changeRoom, &w, &Squawk::changeRoom);
+    QObject::connect(squawk, &Core::Squawk::removeRoom, &w, &Squawk::removeRoom);
+    QObject::connect(squawk, &Core::Squawk::addRoomParticipant, &w, &Squawk::addRoomParticipant);
+    QObject::connect(squawk, &Core::Squawk::changeRoomParticipant, &w, &Squawk::changeRoomParticipant);
+    QObject::connect(squawk, &Core::Squawk::removeRoomParticipant, &w, &Squawk::removeRoomParticipant);
+    QObject::connect(squawk, &Core::Squawk::fileLocalPathResponse, &w, &Squawk::fileLocalPathResponse);
+    QObject::connect(squawk, &Core::Squawk::downloadFileProgress, &w, &Squawk::downloadFileProgress);
+    QObject::connect(squawk, &Core::Squawk::downloadFileError, &w, &Squawk::downloadFileError);
     
     
     //qDebug() << QStandardPaths::writableLocation(QStandardPaths::CacheLocation);

From d050cd82dde47e1b74c6bcd4b34cd87fbb4afb35 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Sat, 19 Oct 2019 22:34:25 +0300
Subject: [PATCH 007/281] some lines to vCard object

---
 core/account.cpp | 38 +++++++++++++++++++
 core/account.h   |  2 +
 global.cpp       | 96 ++++++++++++++++++++++++++++++++++++++++++++++++
 global.h         | 17 +++++++++
 4 files changed, 153 insertions(+)

diff --git a/core/account.cpp b/core/account.cpp
index a14e60f..1da14a3 100644
--- a/core/account.cpp
+++ b/core/account.cpp
@@ -1341,6 +1341,7 @@ void Core::Account::onVCardReceived(const QXmppVCardIq& card)
         if (confItr == conferences.end()) {
             if (jid == getLogin() + "@" + getServer()) {
                 onOwnVCardReceived(card);
+                
             } else {
                 qDebug() << "received vCard" << jid << "doesn't belong to any of known contacts or conferences, skipping";
             }
@@ -1353,11 +1354,32 @@ void Core::Account::onVCardReceived(const QXmppVCardIq& card)
     }
     
     QByteArray ava = card.photo();
+    
     if (ava.size() > 0) {
         item->setAvatar(ava);
     } else {
         item->setAutoGeneratedAvatar();
     }
+    
+    Shared::VCard vCard;
+    vCard.setFirstName(card.firstName());
+    vCard.setMiddleName(card.middleName());
+    vCard.setLastName(card.lastName());
+    vCard.setBirthday(card.birthday());
+    vCard.setNickName(card.nickName());
+    vCard.setDescription(card.description());
+    if (item->hasAvatar()) {
+        if (item->isAvatarAutoGenerated()) {
+            vCard.setAvatarType(Shared::Avatar::valid);
+        } else {
+            vCard.setAvatarType(Shared::Avatar::autocreated);
+        }
+        vCard.setAvatarPath(item->avatarPath());
+    } else {
+        vCard.setAvatarType(Shared::Avatar::empty);
+    }
+    
+    emit receivedVCard(jid, vCard);
 }
 
 void Core::Account::onOwnVCardReceived(const QXmppVCardIq& card)
@@ -1438,6 +1460,22 @@ void Core::Account::onOwnVCardReceived(const QXmppVCardIq& card)
     }
     
     ownVCardRequestInProgress = false;
+    
+    Shared::VCard vCard;
+    vCard.setFirstName(card.firstName());
+    vCard.setMiddleName(card.middleName());
+    vCard.setLastName(card.lastName());
+    vCard.setBirthday(card.birthday());
+    vCard.setNickName(card.nickName());
+    vCard.setDescription(card.description());
+    if (avatarType.size() > 0) {
+        vCard.setAvatarType(Shared::Avatar::valid);
+        vCard.setAvatarPath(path + "avatar." + avatarType);
+    } else {
+        vCard.setAvatarType(Shared::Avatar::empty);
+    }
+    
+    emit receivedVCard(getLogin() + "@" + getServer(), vCard);
 }
 
 QString Core::Account::getAvatarPath() const
diff --git a/core/account.h b/core/account.h
index f9d8469..b5b8c46 100644
--- a/core/account.h
+++ b/core/account.h
@@ -87,6 +87,7 @@ public:
     void setRoomAutoJoin(const QString& jid, bool joined);
     void removeRoomRequest(const QString& jid);
     void addRoomRequest(const QString& jid, const QString& nick, const QString& password, bool autoJoin);
+    void requestVCard(const QString& jid);
     
 signals:
     void changed(const QMap<QString, QVariant>& data);
@@ -109,6 +110,7 @@ signals:
     void addRoomParticipant(const QString& jid, const QString& nickName, const QMap<QString, QVariant>& data);
     void changeRoomParticipant(const QString& jid, const QString& nickName, const QMap<QString, QVariant>& data);
     void removeRoomParticipant(const QString& jid, const QString& nickName);
+    void receivedVCard(const QString& jid, const Shared::VCard& card);
     
 private:
     QString name;
diff --git a/global.cpp b/global.cpp
index e9156fd..fa63539 100644
--- a/global.cpp
+++ b/global.cpp
@@ -353,6 +353,102 @@ Shared::VCard::VCard(const QDateTime& creationTime):
 {
 }
 
+QString Shared::VCard::getAvatarPath() const
+{
+    return photoPath;
+}
+
+Shared::Avatar Shared::VCard::getAvatarType() const
+{
+    return photoType;
+}
+
+QDate Shared::VCard::getBirthday() const
+{
+    return birthday;
+}
+
+QString Shared::VCard::getDescription() const
+{
+    return description;
+}
+
+QString Shared::VCard::getFirstName() const
+{
+    return firstName;
+}
+
+QString Shared::VCard::getLastName() const
+{
+    return lastName;
+}
+
+QString Shared::VCard::getMiddleName() const
+{
+    return middleName;
+}
+
+QString Shared::VCard::getNickName() const
+{
+    return nickName;
+}
+
+void Shared::VCard::setAvatarPath(const QString& path)
+{
+    if (path != photoPath) {
+        photoPath = path;
+    }
+}
+
+void Shared::VCard::setAvatarType(Shared::Avatar type)
+{
+    if (photoType != type) {
+        photoType = type;
+    }
+}
+
+void Shared::VCard::setBirthday(const QDate& date)
+{
+    if (date.isValid() && birthday != date) {
+        birthday = date;
+    }
+}
+
+void Shared::VCard::setDescription(const QString& descr)
+{
+    if (description != descr) {
+        description = descr;
+    }
+}
+
+void Shared::VCard::setFirstName(const QString& first)
+{
+    if (firstName != first) {
+        firstName = first;
+    }
+}
+
+void Shared::VCard::setLastName(const QString& last)
+{
+    if (lastName != last) {
+        lastName = last;
+    }
+}
+
+void Shared::VCard::setMiddleName(const QString& middle)
+{
+    if (middleName != middle) {
+        middleName = middle;
+    }
+}
+
+void Shared::VCard::setNickName(const QString& nick)
+{
+    if (nickName != nick) {
+        nickName = nick;
+    }
+}
+
 QIcon Shared::availabilityIcon(Shared::Availability av, bool big)
 {
     const std::deque<QString>& fallback = QApplication::palette().window().color().lightnessF() > 0.5 ? 
diff --git a/global.h b/global.h
index e0acd91..b20e616 100644
--- a/global.h
+++ b/global.h
@@ -272,6 +272,23 @@ public:
     VCard();
     VCard(const QDateTime& creationTime);
     
+    QString getFirstName() const;
+    void setFirstName(const QString& first);
+    QString getMiddleName() const;
+    void setMiddleName(const QString& middle);
+    QString getLastName() const;
+    void setLastName(const QString& last);
+    QString getNickName() const;
+    void setNickName(const QString& nick);
+    QString getDescription() const;
+    void setDescription(const QString& descr);
+    QDate getBirthday() const;
+    void setBirthday(const QDate& date);
+    Avatar getAvatarType() const;
+    void setAvatarType(Avatar type);
+    QString getAvatarPath() const;
+    void setAvatarPath(const QString& path);
+    
 private:
     QString firstName;
     QString middleName;

From e7be046e9f5af7eba6f33bc77a75db5c53ee0991 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Sun, 20 Oct 2019 22:39:11 +0300
Subject: [PATCH 008/281] vcard ui file

---
 ui/CMakeLists.txt    |   1 +
 ui/widgets/vcard.cpp |  31 ++
 ui/widgets/vcard.h   |  44 +++
 ui/widgets/vcard.ui  | 737 +++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 813 insertions(+)
 create mode 100644 ui/widgets/vcard.cpp
 create mode 100644 ui/widgets/vcard.h
 create mode 100644 ui/widgets/vcard.ui

diff --git a/ui/CMakeLists.txt b/ui/CMakeLists.txt
index 982bb9a..0f6680a 100644
--- a/ui/CMakeLists.txt
+++ b/ui/CMakeLists.txt
@@ -29,6 +29,7 @@ set(squawkUI_SRC
   widgets/accounts.cpp
   widgets/account.cpp
   widgets/joinconference.cpp
+  widgets/vcard.cpp
   utils/messageline.cpp
   utils//message.cpp
   utils/resizer.cpp
diff --git a/ui/widgets/vcard.cpp b/ui/widgets/vcard.cpp
new file mode 100644
index 0000000..f2db0e9
--- /dev/null
+++ b/ui/widgets/vcard.cpp
@@ -0,0 +1,31 @@
+/*
+ * Squawk messenger. 
+ * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+ * 
+ * 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 "vcard.h"
+#include "ui_vcard.h"
+
+VCard::VCard(QWidget* parent):
+    QWidget(parent),
+    m_ui(new Ui::VCard())
+{
+    m_ui->setupUi(this);
+}
+
+VCard::~VCard()
+{
+}
diff --git a/ui/widgets/vcard.h b/ui/widgets/vcard.h
new file mode 100644
index 0000000..c9fe19d
--- /dev/null
+++ b/ui/widgets/vcard.h
@@ -0,0 +1,44 @@
+/*
+ * Squawk messenger. 
+ * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+ * 
+ * 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/>.
+ */
+
+#ifndef VCARD_H
+#define VCARD_H
+
+#include <QWidget>
+#include <QScopedPointer>
+
+namespace Ui
+{
+class VCard;
+}
+
+/**
+ * @todo write docs
+ */
+class VCard : public QWidget
+{
+    Q_OBJECT
+public:
+    VCard(QWidget* parent = nullptr);
+    ~VCard();
+    
+private:
+    QScopedPointer<Ui::VCard> m_ui;
+};
+
+#endif // VCARD_H
diff --git a/ui/widgets/vcard.ui b/ui/widgets/vcard.ui
new file mode 100644
index 0000000..5658d2c
--- /dev/null
+++ b/ui/widgets/vcard.ui
@@ -0,0 +1,737 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>VCard</class>
+ <widget class="QWidget" name="VCard">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>434</width>
+    <height>534</height>
+   </rect>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout_2">
+   <property name="spacing">
+    <number>6</number>
+   </property>
+   <property name="leftMargin">
+    <number>6</number>
+   </property>
+   <property name="topMargin">
+    <number>6</number>
+   </property>
+   <property name="rightMargin">
+    <number>6</number>
+   </property>
+   <property name="bottomMargin">
+    <number>6</number>
+   </property>
+   <item>
+    <widget class="QTabWidget" name="tabWidget">
+     <property name="focusPolicy">
+      <enum>Qt::TabFocus</enum>
+     </property>
+     <property name="tabPosition">
+      <enum>QTabWidget::North</enum>
+     </property>
+     <property name="tabShape">
+      <enum>QTabWidget::Rounded</enum>
+     </property>
+     <property name="currentIndex">
+      <number>0</number>
+     </property>
+     <property name="elideMode">
+      <enum>Qt::ElideNone</enum>
+     </property>
+     <property name="documentMode">
+      <bool>true</bool>
+     </property>
+     <property name="tabBarAutoHide">
+      <bool>false</bool>
+     </property>
+     <widget class="QWidget" name="General" native="true">
+      <attribute name="title">
+       <string>General</string>
+      </attribute>
+      <layout class="QGridLayout" name="gridLayout_2" columnstretch="1,2,2,1">
+       <property name="leftMargin">
+        <number>0</number>
+       </property>
+       <property name="topMargin">
+        <number>6</number>
+       </property>
+       <property name="rightMargin">
+        <number>0</number>
+       </property>
+       <property name="bottomMargin">
+        <number>0</number>
+       </property>
+       <property name="horizontalSpacing">
+        <number>6</number>
+       </property>
+       <item row="1" column="1" colspan="2">
+        <widget class="QLabel" name="personalHeading">
+         <property name="sizePolicy">
+          <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+           <horstretch>0</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
+         <property name="styleSheet">
+          <string notr="true"/>
+         </property>
+         <property name="text">
+          <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:16pt; font-weight:600;&quot;&gt;Personal information&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+         </property>
+         <property name="alignment">
+          <set>Qt::AlignCenter</set>
+         </property>
+        </widget>
+       </item>
+       <item row="3" column="1" colspan="2">
+        <widget class="Line" name="personalLine">
+         <property name="orientation">
+          <enum>Qt::Horizontal</enum>
+         </property>
+        </widget>
+       </item>
+       <item row="2" column="1">
+        <layout class="QFormLayout" name="personalForm">
+         <property name="formAlignment">
+          <set>Qt::AlignHCenter|Qt::AlignTop</set>
+         </property>
+         <item row="1" column="1">
+          <widget class="QLineEdit" name="middleName">
+           <property name="minimumSize">
+            <size>
+             <width>150</width>
+             <height>0</height>
+            </size>
+           </property>
+           <property name="maximumSize">
+            <size>
+             <width>300</width>
+             <height>16777215</height>
+            </size>
+           </property>
+          </widget>
+         </item>
+         <item row="0" column="1">
+          <widget class="QLineEdit" name="firstName">
+           <property name="minimumSize">
+            <size>
+             <width>150</width>
+             <height>0</height>
+            </size>
+           </property>
+           <property name="maximumSize">
+            <size>
+             <width>300</width>
+             <height>16777215</height>
+            </size>
+           </property>
+          </widget>
+         </item>
+         <item row="1" column="0">
+          <widget class="QLabel" name="middleNameLabel">
+           <property name="text">
+            <string>Middle name</string>
+           </property>
+           <property name="buddy">
+            <cstring>middleName</cstring>
+           </property>
+          </widget>
+         </item>
+         <item row="0" column="0">
+          <widget class="QLabel" name="firstNameLabel">
+           <property name="text">
+            <string>First name</string>
+           </property>
+           <property name="buddy">
+            <cstring>firstName</cstring>
+           </property>
+          </widget>
+         </item>
+         <item row="2" column="0">
+          <widget class="QLabel" name="lastNameLabel">
+           <property name="text">
+            <string>Last name</string>
+           </property>
+           <property name="buddy">
+            <cstring>lastName</cstring>
+           </property>
+          </widget>
+         </item>
+         <item row="2" column="1">
+          <widget class="QLineEdit" name="lastName">
+           <property name="minimumSize">
+            <size>
+             <width>150</width>
+             <height>0</height>
+            </size>
+           </property>
+           <property name="maximumSize">
+            <size>
+             <width>300</width>
+             <height>16777215</height>
+            </size>
+           </property>
+          </widget>
+         </item>
+         <item row="3" column="0">
+          <widget class="QLabel" name="nickNameLabel">
+           <property name="text">
+            <string>Nick name</string>
+           </property>
+           <property name="buddy">
+            <cstring>nickName</cstring>
+           </property>
+          </widget>
+         </item>
+         <item row="3" column="1">
+          <widget class="QLineEdit" name="nickName">
+           <property name="minimumSize">
+            <size>
+             <width>150</width>
+             <height>0</height>
+            </size>
+           </property>
+           <property name="maximumSize">
+            <size>
+             <width>300</width>
+             <height>16777215</height>
+            </size>
+           </property>
+          </widget>
+         </item>
+         <item row="4" column="0">
+          <widget class="QLabel" name="birthdayLabel">
+           <property name="text">
+            <string>Birthday</string>
+           </property>
+           <property name="buddy">
+            <cstring>birthday</cstring>
+           </property>
+          </widget>
+         </item>
+         <item row="4" column="1">
+          <widget class="QDateEdit" name="birthday"/>
+         </item>
+        </layout>
+       </item>
+       <item row="2" column="2">
+        <widget class="QFrame" name="avatarFrame">
+         <property name="sizePolicy">
+          <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+           <horstretch>0</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
+         <property name="minimumSize">
+          <size>
+           <width>0</width>
+           <height>0</height>
+          </size>
+         </property>
+         <property name="sizeIncrement">
+          <size>
+           <width>0</width>
+           <height>0</height>
+          </size>
+         </property>
+         <property name="frameShape">
+          <enum>QFrame::StyledPanel</enum>
+         </property>
+         <property name="frameShadow">
+          <enum>QFrame::Raised</enum>
+         </property>
+         <layout class="QVBoxLayout" name="verticalLayout"/>
+        </widget>
+       </item>
+       <item row="2" column="3" rowspan="7">
+        <spacer name="generalRightHSpacer">
+         <property name="orientation">
+          <enum>Qt::Horizontal</enum>
+         </property>
+         <property name="sizeHint" stdset="0">
+          <size>
+           <width>40</width>
+           <height>20</height>
+          </size>
+         </property>
+        </spacer>
+       </item>
+       <item row="8" column="1" colspan="2">
+        <spacer name="verticalSpacer">
+         <property name="orientation">
+          <enum>Qt::Vertical</enum>
+         </property>
+         <property name="sizeHint" stdset="0">
+          <size>
+           <width>20</width>
+           <height>40</height>
+          </size>
+         </property>
+        </spacer>
+       </item>
+       <item row="2" column="0" rowspan="7">
+        <spacer name="generalLeftHSpacer">
+         <property name="orientation">
+          <enum>Qt::Horizontal</enum>
+         </property>
+         <property name="sizeHint" stdset="0">
+          <size>
+           <width>40</width>
+           <height>20</height>
+          </size>
+         </property>
+        </spacer>
+       </item>
+       <item row="5" column="1" colspan="2">
+        <widget class="QLabel" name="organizationHeading">
+         <property name="sizePolicy">
+          <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+           <horstretch>0</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
+         <property name="styleSheet">
+          <string notr="true"/>
+         </property>
+         <property name="text">
+          <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:16pt; font-weight:600;&quot;&gt;Organization&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+         </property>
+         <property name="alignment">
+          <set>Qt::AlignCenter</set>
+         </property>
+        </widget>
+       </item>
+       <item row="6" column="1" colspan="2">
+        <layout class="QFormLayout" name="organizationForm">
+         <property name="formAlignment">
+          <set>Qt::AlignHCenter|Qt::AlignTop</set>
+         </property>
+         <item row="0" column="0">
+          <widget class="QLabel" name="organizationNameLabel">
+           <property name="text">
+            <string>Name</string>
+           </property>
+           <property name="buddy">
+            <cstring>organizationName</cstring>
+           </property>
+          </widget>
+         </item>
+         <item row="0" column="1">
+          <widget class="QLineEdit" name="organizationName">
+           <property name="minimumSize">
+            <size>
+             <width>150</width>
+             <height>0</height>
+            </size>
+           </property>
+           <property name="maximumSize">
+            <size>
+             <width>300</width>
+             <height>16777215</height>
+            </size>
+           </property>
+          </widget>
+         </item>
+         <item row="1" column="0">
+          <widget class="QLabel" name="organizationDepartmentLabel">
+           <property name="text">
+            <string>Department</string>
+           </property>
+           <property name="buddy">
+            <cstring>organizationDepartment</cstring>
+           </property>
+          </widget>
+         </item>
+         <item row="1" column="1">
+          <widget class="QLineEdit" name="organizationDepartment">
+           <property name="minimumSize">
+            <size>
+             <width>150</width>
+             <height>0</height>
+            </size>
+           </property>
+           <property name="maximumSize">
+            <size>
+             <width>300</width>
+             <height>16777215</height>
+            </size>
+           </property>
+          </widget>
+         </item>
+         <item row="2" column="0">
+          <widget class="QLabel" name="roleLabel">
+           <property name="text">
+            <string>Role</string>
+           </property>
+           <property name="buddy">
+            <cstring>organizationRole</cstring>
+           </property>
+          </widget>
+         </item>
+         <item row="2" column="1">
+          <widget class="QLineEdit" name="organizationRole">
+           <property name="minimumSize">
+            <size>
+             <width>150</width>
+             <height>0</height>
+            </size>
+           </property>
+           <property name="maximumSize">
+            <size>
+             <width>300</width>
+             <height>16777215</height>
+            </size>
+           </property>
+          </widget>
+         </item>
+         <item row="3" column="0">
+          <widget class="QLabel" name="organizationTitleLabel">
+           <property name="text">
+            <string>Title</string>
+           </property>
+           <property name="buddy">
+            <cstring>organizationTitle</cstring>
+           </property>
+          </widget>
+         </item>
+         <item row="3" column="1">
+          <widget class="QLineEdit" name="organizationTitle">
+           <property name="minimumSize">
+            <size>
+             <width>150</width>
+             <height>0</height>
+            </size>
+           </property>
+           <property name="maximumSize">
+            <size>
+             <width>300</width>
+             <height>16777215</height>
+            </size>
+           </property>
+          </widget>
+         </item>
+        </layout>
+       </item>
+       <item row="7" column="1" colspan="2">
+        <widget class="Line" name="organizationLine">
+         <property name="orientation">
+          <enum>Qt::Horizontal</enum>
+         </property>
+        </widget>
+       </item>
+       <item row="0" column="0" colspan="4">
+        <widget class="QLabel" name="generalHeading">
+         <property name="text">
+          <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:24pt; font-weight:600;&quot;&gt;General&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+         </property>
+        </widget>
+       </item>
+      </layout>
+     </widget>
+     <widget class="QWidget" name="Contact" native="true">
+      <attribute name="title">
+       <string>Contact</string>
+      </attribute>
+      <layout class="QVBoxLayout" name="verticalLayout_7">
+       <property name="spacing">
+        <number>0</number>
+       </property>
+       <property name="leftMargin">
+        <number>0</number>
+       </property>
+       <property name="topMargin">
+        <number>6</number>
+       </property>
+       <property name="rightMargin">
+        <number>0</number>
+       </property>
+       <property name="bottomMargin">
+        <number>0</number>
+       </property>
+       <item>
+        <widget class="QLabel" name="contactHeading">
+         <property name="text">
+          <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:24pt; font-weight:600;&quot;&gt;Contact&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+         </property>
+        </widget>
+       </item>
+       <item>
+        <widget class="QScrollArea" name="scrollArea">
+         <property name="frameShape">
+          <enum>QFrame::NoFrame</enum>
+         </property>
+         <property name="frameShadow">
+          <enum>QFrame::Plain</enum>
+         </property>
+         <property name="lineWidth">
+          <number>0</number>
+         </property>
+         <property name="widgetResizable">
+          <bool>true</bool>
+         </property>
+         <widget class="QWidget" name="contactScrollArea">
+          <property name="geometry">
+           <rect>
+            <x>0</x>
+            <y>0</y>
+            <width>422</width>
+            <height>410</height>
+           </rect>
+          </property>
+          <layout class="QGridLayout" name="gridLayout_3" columnstretch="1,4,1">
+           <item row="10" column="1">
+            <widget class="Line" name="addressesLine">
+             <property name="orientation">
+              <enum>Qt::Horizontal</enum>
+             </property>
+            </widget>
+           </item>
+           <item row="3" column="1">
+            <layout class="QVBoxLayout" name="emailsLayout">
+             <item>
+              <widget class="QLabel" name="emptyEmailsPlaceholder">
+               <property name="text">
+                <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-style:italic;&quot;&gt;User has no contact e-mail addresses&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+               </property>
+               <property name="alignment">
+                <set>Qt::AlignCenter</set>
+               </property>
+              </widget>
+             </item>
+            </layout>
+           </item>
+           <item row="2" column="1">
+            <widget class="QLabel" name="emailsHeading">
+             <property name="text">
+              <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:16pt; font-weight:600;&quot;&gt;E-Mail addresses&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+             </property>
+             <property name="alignment">
+              <set>Qt::AlignCenter</set>
+             </property>
+            </widget>
+           </item>
+           <item row="0" column="1">
+            <layout class="QFormLayout" name="contactForm">
+             <property name="formAlignment">
+              <set>Qt::AlignHCenter|Qt::AlignTop</set>
+             </property>
+             <item row="0" column="1">
+              <widget class="QLineEdit" name="jabberID">
+               <property name="minimumSize">
+                <size>
+                 <width>150</width>
+                 <height>0</height>
+                </size>
+               </property>
+               <property name="maximumSize">
+                <size>
+                 <width>300</width>
+                 <height>16777215</height>
+                </size>
+               </property>
+              </widget>
+             </item>
+             <item row="0" column="0">
+              <widget class="QLabel" name="jabberIDLabel">
+               <property name="text">
+                <string>JabberID</string>
+               </property>
+               <property name="buddy">
+                <cstring>jabberID</cstring>
+               </property>
+              </widget>
+             </item>
+            </layout>
+           </item>
+           <item row="7" column="1">
+            <widget class="Line" name="phonesLine">
+             <property name="orientation">
+              <enum>Qt::Horizontal</enum>
+             </property>
+            </widget>
+           </item>
+           <item row="1" column="1">
+            <widget class="Line" name="contactFormLine">
+             <property name="orientation">
+              <enum>Qt::Horizontal</enum>
+             </property>
+            </widget>
+           </item>
+           <item row="4" column="1">
+            <widget class="Line" name="emailsLine">
+             <property name="orientation">
+              <enum>Qt::Horizontal</enum>
+             </property>
+            </widget>
+           </item>
+           <item row="11" column="1">
+            <spacer name="contactBottomSpacer">
+             <property name="orientation">
+              <enum>Qt::Vertical</enum>
+             </property>
+             <property name="sizeHint" stdset="0">
+              <size>
+               <width>20</width>
+               <height>40</height>
+              </size>
+             </property>
+            </spacer>
+           </item>
+           <item row="0" column="0" rowspan="12">
+            <spacer name="contactLeftSpacer">
+             <property name="orientation">
+              <enum>Qt::Horizontal</enum>
+             </property>
+             <property name="sizeHint" stdset="0">
+              <size>
+               <width>40</width>
+               <height>20</height>
+              </size>
+             </property>
+            </spacer>
+           </item>
+           <item row="6" column="1">
+            <layout class="QVBoxLayout" name="phonesLayout">
+             <item>
+              <widget class="QLabel" name="emptyPhonesPlaceholder">
+               <property name="text">
+                <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-style:italic;&quot;&gt;User has no contact e-mail addresses&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+               </property>
+               <property name="alignment">
+                <set>Qt::AlignCenter</set>
+               </property>
+              </widget>
+             </item>
+            </layout>
+           </item>
+           <item row="8" column="1">
+            <widget class="QLabel" name="addressesHeading">
+             <property name="text">
+              <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:16pt; font-weight:600;&quot;&gt;Addresses&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+             </property>
+             <property name="alignment">
+              <set>Qt::AlignCenter</set>
+             </property>
+            </widget>
+           </item>
+           <item row="9" column="1">
+            <layout class="QVBoxLayout" name="addressesLayout">
+             <item>
+              <widget class="QLabel" name="EmptyAddressesPlaceholder">
+               <property name="text">
+                <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-style:italic;&quot;&gt;User has no contact e-mail addresses&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+               </property>
+               <property name="alignment">
+                <set>Qt::AlignCenter</set>
+               </property>
+              </widget>
+             </item>
+            </layout>
+           </item>
+           <item row="5" column="1">
+            <widget class="QLabel" name="phenesHeading">
+             <property name="text">
+              <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:16pt; font-weight:600;&quot;&gt;Phone numbers&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+             </property>
+             <property name="alignment">
+              <set>Qt::AlignCenter</set>
+             </property>
+            </widget>
+           </item>
+           <item row="0" column="2" rowspan="12">
+            <spacer name="contactRightSpacer">
+             <property name="orientation">
+              <enum>Qt::Horizontal</enum>
+             </property>
+             <property name="sizeHint" stdset="0">
+              <size>
+               <width>40</width>
+               <height>20</height>
+              </size>
+             </property>
+            </spacer>
+           </item>
+          </layout>
+         </widget>
+        </widget>
+       </item>
+      </layout>
+     </widget>
+     <widget class="QWidget" name="Description">
+      <attribute name="title">
+       <string>Description</string>
+      </attribute>
+      <layout class="QGridLayout" name="gridLayout">
+       <property name="leftMargin">
+        <number>0</number>
+       </property>
+       <property name="topMargin">
+        <number>6</number>
+       </property>
+       <property name="rightMargin">
+        <number>0</number>
+       </property>
+       <property name="bottomMargin">
+        <number>0</number>
+       </property>
+       <property name="horizontalSpacing">
+        <number>6</number>
+       </property>
+       <item row="0" column="0">
+        <widget class="QLabel" name="descriptionHeading">
+         <property name="text">
+          <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:24pt; font-weight:600;&quot;&gt;Description&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+         </property>
+        </widget>
+       </item>
+       <item row="1" column="0">
+        <widget class="QTextEdit" name="description">
+         <property name="frameShape">
+          <enum>QFrame::StyledPanel</enum>
+         </property>
+         <property name="textInteractionFlags">
+          <set>Qt::LinksAccessibleByMouse|Qt::TextEditable|Qt::TextEditorInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
+         </property>
+        </widget>
+       </item>
+      </layout>
+     </widget>
+    </widget>
+   </item>
+   <item>
+    <widget class="QDialogButtonBox" name="buttonBox">
+     <property name="standardButtons">
+      <set>QDialogButtonBox::Close|QDialogButtonBox::Save</set>
+     </property>
+     <property name="centerButtons">
+      <bool>false</bool>
+     </property>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <tabstops>
+  <tabstop>firstName</tabstop>
+  <tabstop>middleName</tabstop>
+  <tabstop>lastName</tabstop>
+  <tabstop>nickName</tabstop>
+  <tabstop>birthday</tabstop>
+  <tabstop>organizationName</tabstop>
+  <tabstop>organizationDepartment</tabstop>
+  <tabstop>organizationRole</tabstop>
+  <tabstop>organizationTitle</tabstop>
+  <tabstop>scrollArea</tabstop>
+  <tabstop>jabberID</tabstop>
+  <tabstop>description</tabstop>
+  <tabstop>tabWidget</tabstop>
+ </tabstops>
+ <resources>
+  <include location="../../resources/resources.qrc"/>
+ </resources>
+ <connections/>
+</ui>

From c4d22c9c1429e799cbebff8004baa7c82df94ac4 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Mon, 21 Oct 2019 18:02:41 +0300
Subject: [PATCH 009/281] some methods to cVard

---
 ui/utils/image.cpp   | 37 ++++++++++++++++++-------
 ui/utils/image.h     | 28 ++++++-------------
 ui/widgets/vcard.cpp | 65 ++++++++++++++++++++++++++++++++++++++++++--
 ui/widgets/vcard.h   | 14 +++++++++-
 ui/widgets/vcard.ui  | 33 ++++++++++++++++++----
 5 files changed, 139 insertions(+), 38 deletions(-)

diff --git a/ui/utils/image.cpp b/ui/utils/image.cpp
index 0abccf1..6a2ce1c 100644
--- a/ui/utils/image.cpp
+++ b/ui/utils/image.cpp
@@ -19,19 +19,13 @@
 #include <QDebug>
 #include "image.h"
 
-Image::Image(const QString& path, QWidget* parent):
+Image::Image(const QString& path, quint16 p_minWidth, QWidget* parent):
     QLabel(parent),
     pixmap(path),
-    aspectRatio(0)
+    aspectRatio(0),
+    minWidth(p_minWidth)
 {
-    
-    qreal height = pixmap.height();
-    qreal width = pixmap.width();
-    aspectRatio = width / height;
-    setPixmap(pixmap);
     setScaledContents(true);
-    setMinimumHeight(50 / aspectRatio);
-    setMinimumWidth(50);
 }
 
 Image::~Image()
@@ -42,7 +36,6 @@ Image::~Image()
 int Image::heightForWidth(int width) const
 {
     int height = width / aspectRatio;
-    //qDebug() << height << width << aspectRatio;
     return height;
 }
 
@@ -50,3 +43,27 @@ bool Image::hasHeightForWidth() const
 {
     return true;
 }
+
+void Image::recalculateAspectRatio()
+{
+    qreal height = pixmap.height();
+    qreal width = pixmap.width();
+    aspectRatio = width / height;
+    setPixmap(pixmap);
+    setMinimumHeight(minWidth / aspectRatio);
+    setMinimumWidth(minWidth);
+}
+
+void Image::setMinWidth(quint16 p_minWidth)
+{
+    if (minWidth != p_minWidth) {
+        minWidth = p_minWidth;
+        recalculateAspectRatio();
+    }
+}
+
+void Image::setPath(const QString& path)
+{
+    pixmap = QPixmap(path);
+    recalculateAspectRatio();
+}
diff --git a/ui/utils/image.h b/ui/utils/image.h
index 7d61202..a583f94 100644
--- a/ui/utils/image.h
+++ b/ui/utils/image.h
@@ -28,34 +28,22 @@
 class Image : public QLabel
 {
 public:
-    /**
-     * Default constructor
-     */
-    Image(const QString& path, QWidget* parent = nullptr);
+    Image(const QString& path, quint16 minWidth = 50, QWidget* parent = nullptr);
 
-    /**
-     * Destructor
-     */
     ~Image();
 
-    /**
-     * @todo write docs
-     *
-     * @param  TODO
-     * @return TODO
-     */
     int heightForWidth(int width) const override;
-
-    /**
-     * @todo write docs
-     *
-     * @return TODO
-     */
-    virtual bool hasHeightForWidth() const;
+    bool hasHeightForWidth() const override;
+    void setPath(const QString& path);
+    void setMinWidth(quint16 minWidth);
     
 private:
     QPixmap pixmap;
     qreal aspectRatio;
+    quint16 minWidth;
+    
+private:
+    void recalculateAspectRatio();
 };
 
 #endif // IMAGE_H
diff --git a/ui/widgets/vcard.cpp b/ui/widgets/vcard.cpp
index f2db0e9..7072ca1 100644
--- a/ui/widgets/vcard.cpp
+++ b/ui/widgets/vcard.cpp
@@ -19,13 +19,74 @@
 #include "vcard.h"
 #include "ui_vcard.h"
 
-VCard::VCard(QWidget* parent):
+VCard::VCard(bool edit, QWidget* parent):
     QWidget(parent),
-    m_ui(new Ui::VCard())
+    m_ui(new Ui::VCard()),
+    avatar(":/images/logo.svg", 64)
 {
     m_ui->setupUi(this);
+    
+    if (edit) {
+        
+    } else {
+        m_ui->buttonBox->hide();
+        m_ui->firstName->setReadOnly(true);
+        m_ui->middleName->setReadOnly(true);
+        m_ui->lastName->setReadOnly(true);
+        m_ui->nickName->setReadOnly(true);
+        m_ui->birthday->setReadOnly(true);
+        m_ui->organizationName->setReadOnly(true);
+        m_ui->organizationDepartment->setReadOnly(true);
+        m_ui->organizationTitle->setReadOnly(true);
+        m_ui->organizationRole->setReadOnly(true);
+        m_ui->description->setReadOnly(true);
+        m_ui->jabberID->setReadOnly(true);
+    }
+    
+    connect(m_ui->buttonBox, &QDialogButtonBox::accepted, this, &VCard::onButtonBoxAccepted);
+    connect(m_ui->buttonBox, &QDialogButtonBox::rejected, m_ui->buttonBox, &QDialogButtonBox::deleteLater);
 }
 
 VCard::~VCard()
 {
 }
+
+void VCard::setVCard(const QString& jid, const Shared::VCard& card)
+{
+    m_ui->jabberID->setText(jid);
+    m_ui->firstName->setText(card.getFirstName());
+    m_ui->middleName->setText(card.getMiddleName());
+    m_ui->lastName->setText(card.getLastName());
+    m_ui->nickName->setText(card.getNickName());
+    m_ui->birthday->setDate(card.getBirthday());
+    //m_ui->organizationName->setText(card.get());
+    //m_ui->organizationDepartment->setText(card.get());
+    //m_ui->organizationTitle->setText(card.get());
+    //m_ui->organizationRole->setText(card.get());
+    m_ui->description->setText(card.getDescription());
+    
+    QString path;
+    switch (card.getAvatarType()) {
+        case Shared::Avatar::empty:
+            path = QApplication::palette().window().color().lightnessF() > 0.5 ? ":/images/fallback/dark/big/user.svg" : ":/images/fallback/light/big/user.svg";
+            break;
+        case Shared::Avatar::autocreated:
+        case Shared::Avatar::valid:
+            path = card.getAvatarPath();
+            break;
+    }
+    avatar.setPath(path);
+}
+
+void VCard::onButtonBoxAccepted()
+{
+    Shared::VCard card;
+    card.setFirstName(m_ui->firstName->text());
+    card.setMiddleName(m_ui->middleName->text());
+    card.setLastName(m_ui->lastName->text());
+    card.setNickName(m_ui->nickName->text());
+    card.setBirthday(m_ui->birthday->date());
+    card.setDescription(m_ui->description->toPlainText());
+    
+    emit saveVCard(m_ui->jabberID->text(), card);
+}
diff --git a/ui/widgets/vcard.h b/ui/widgets/vcard.h
index c9fe19d..dc14172 100644
--- a/ui/widgets/vcard.h
+++ b/ui/widgets/vcard.h
@@ -22,6 +22,9 @@
 #include <QWidget>
 #include <QScopedPointer>
 
+#include "../../global.h"
+#include "../utils/image.h"
+
 namespace Ui
 {
 class VCard;
@@ -34,11 +37,20 @@ class VCard : public QWidget
 {
     Q_OBJECT
 public:
-    VCard(QWidget* parent = nullptr);
+    VCard(bool edit = false, QWidget* parent = nullptr);
     ~VCard();
     
+    void setVCard(const QString& jid, const Shared::VCard& card);
+    
+signals:
+    void saveVCard(const QString& jid, const Shared::VCard& card);
+    
+private slots:
+    void onButtonBoxAccepted();
+    
 private:
     QScopedPointer<Ui::VCard> m_ui;
+    Image avatar;
 };
 
 #endif // VCARD_H
diff --git a/ui/widgets/vcard.ui b/ui/widgets/vcard.ui
index 5658d2c..2ebae41 100644
--- a/ui/widgets/vcard.ui
+++ b/ui/widgets/vcard.ui
@@ -6,7 +6,7 @@
    <rect>
     <x>0</x>
     <y>0</y>
-    <width>434</width>
+    <width>544</width>
     <height>534</height>
    </rect>
   </property>
@@ -49,7 +49,7 @@
      <property name="tabBarAutoHide">
       <bool>false</bool>
      </property>
-     <widget class="QWidget" name="General" native="true">
+     <widget class="QWidget" name="General">
       <attribute name="title">
        <string>General</string>
       </attribute>
@@ -433,7 +433,7 @@
        </item>
       </layout>
      </widget>
-     <widget class="QWidget" name="Contact" native="true">
+     <widget class="QWidget" name="Contact">
       <attribute name="title">
        <string>Contact</string>
       </attribute>
@@ -479,7 +479,7 @@
            <rect>
             <x>0</x>
             <y>0</y>
-            <width>422</width>
+            <width>532</width>
             <height>410</height>
            </rect>
           </property>
@@ -539,13 +539,36 @@
              <item row="0" column="0">
               <widget class="QLabel" name="jabberIDLabel">
                <property name="text">
-                <string>JabberID</string>
+                <string>Jabber ID</string>
                </property>
                <property name="buddy">
                 <cstring>jabberID</cstring>
                </property>
               </widget>
              </item>
+             <item row="1" column="1">
+              <widget class="QLineEdit" name="lineEdit">
+               <property name="minimumSize">
+                <size>
+                 <width>150</width>
+                 <height>0</height>
+                </size>
+               </property>
+               <property name="maximumSize">
+                <size>
+                 <width>300</width>
+                 <height>16777215</height>
+                </size>
+               </property>
+              </widget>
+             </item>
+             <item row="1" column="0">
+              <widget class="QLabel" name="urlLabel">
+               <property name="text">
+                <string>Web site</string>
+               </property>
+              </widget>
+             </item>
             </layout>
            </item>
            <item row="7" column="1">

From 2a37f36b8394508a03b574aa7e222abea0f92142 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Tue, 22 Oct 2019 18:13:56 +0300
Subject: [PATCH 010/281] first primitive vcard in graphic interface

---
 core/account.cpp      | 26 +++++++++-----
 core/squawk.cpp       | 17 +++++++--
 core/squawk.h         |  2 ++
 main.cpp              |  2 ++
 ui/models/account.cpp |  9 +++++
 ui/models/account.h   |  3 ++
 ui/squawk.cpp         | 63 ++++++++++++++++++++++++++++++--
 ui/squawk.h           |  6 ++++
 ui/utils/image.cpp    | 22 ++++++++++++
 ui/utils/image.h      |  4 +++
 ui/widgets/vcard.cpp  | 39 +++++++++++++++-----
 ui/widgets/vcard.h    |  6 ++--
 ui/widgets/vcard.ui   | 84 +++++++++++++++++--------------------------
 13 files changed, 208 insertions(+), 75 deletions(-)

diff --git a/core/account.cpp b/core/account.cpp
index 1da14a3..04d2947 100644
--- a/core/account.cpp
+++ b/core/account.cpp
@@ -344,8 +344,7 @@ void Core::Account::addedAccount(const QString& jid)
         } else {
             cData.insert("avatarState", static_cast<uint>(Shared::Avatar::empty));
             cData.insert("avatarPath", "");
-            client.vCardManager().requestVCard(jid);
-            pendingVCardRequests.insert(jid);
+            requestVCard(jid);
         }
         int grCount = 0;
         for (QSet<QString>::const_iterator itr = gr.begin(), end = gr.end(); itr != end; ++itr) {
@@ -442,17 +441,14 @@ void Core::Account::onPresenceReceived(const QXmppPresence& p_presence)
                     case QXmppPresence::VCardUpdateValidPhoto:      //there is a photo, need to load
                         if (cnt->hasAvatar()) {
                             if (cnt->isAvatarAutoGenerated()) {
-                                client.vCardManager().requestVCard(jid);
-                                pendingVCardRequests.insert(jid);
+                                requestVCard(jid);
                             } else {
                                 if (cnt->avatarHash() != p_presence.photoHash()) {
-                                    client.vCardManager().requestVCard(jid);
-                                    pendingVCardRequests.insert(jid);
+                                    requestVCard(jid);
                                 }
                             }
                         } else {
-                            client.vCardManager().requestVCard(jid);
-                            pendingVCardRequests.insert(jid);
+                            requestVCard(jid);
                         }
                         break;
                 }
@@ -1494,3 +1490,17 @@ void Core::Account::onContactAvatarChanged(Shared::Avatar type, const QString& p
     emit changeContact(item->jid, cData);
 }
 
+void Core::Account::requestVCard(const QString& jid)
+{
+    if (pendingVCardRequests.find(jid) == pendingVCardRequests.end()) {
+        if (jid == getLogin() + "@" + getServer()) {
+            if (!ownVCardRequestInProgress) {
+                client.vCardManager().requestClientVCard();
+                ownVCardRequestInProgress = true;
+            }
+        } else {
+            client.vCardManager().requestVCard(jid);
+            pendingVCardRequests.insert(jid);
+        }
+    }
+}
diff --git a/core/squawk.cpp b/core/squawk.cpp
index 61b624d..35977a4 100644
--- a/core/squawk.cpp
+++ b/core/squawk.cpp
@@ -133,6 +133,7 @@ void Core::Squawk::addAccount(const QString& login, const QString& server, const
     connect(acc, &Account::changeRoomParticipant, this, &Squawk::onAccountChangeRoomPresence);
     connect(acc, &Account::removeRoomParticipant, this, &Squawk::onAccountRemoveRoomPresence);
     
+    connect(acc, &Account::receivedVCard, this, &Squawk::responseVCard);
     
     QMap<QString, QVariant> map = {
         {"login", login},
@@ -507,7 +508,7 @@ void Core::Squawk::addContactToGroupRequest(const QString& account, const QStrin
 {
     AccountsMap::const_iterator itr = amap.find(account);
     if (itr == amap.end()) {
-        qDebug() << "An attempt to add contact" << jid << "of existing account" << account << "to the group" << groupName << ", skipping";
+        qDebug() << "An attempt to add contact" << jid << "of non existing account" << account << "to the group" << groupName << ", skipping";
         return;
     }
     itr->second->addContactToGroupRequest(jid, groupName);
@@ -517,7 +518,7 @@ void Core::Squawk::removeContactFromGroupRequest(const QString& account, const Q
 {
     AccountsMap::const_iterator itr = amap.find(account);
     if (itr == amap.end()) {
-        qDebug() << "An attempt to add contact" << jid << "of existing account" << account << "to the group" << groupName << ", skipping";
+        qDebug() << "An attempt to add contact" << jid << "of non existing account" << account << "to the group" << groupName << ", skipping";
         return;
     }
     itr->second->removeContactFromGroupRequest(jid, groupName);
@@ -527,8 +528,18 @@ void Core::Squawk::renameContactRequest(const QString& account, const QString& j
 {
     AccountsMap::const_iterator itr = amap.find(account);
     if (itr == amap.end()) {
-        qDebug() << "An attempt to rename contact" << jid << "of existing account" << account << ", skipping";
+        qDebug() << "An attempt to rename contact" << jid << "of non existing account" << account << ", skipping";
         return;
     }
     itr->second->renameContactRequest(jid, newName);
 }
+
+void Core::Squawk::requestVCard(const QString& account, const QString& jid)
+{
+    AccountsMap::const_iterator itr = amap.find(account);
+    if (itr == amap.end()) {
+        qDebug() << "An attempt to request" << jid << "vcard of non existing account" << account << ", skipping";
+        return;
+    }
+    itr->second->requestVCard(jid);
+}
diff --git a/core/squawk.h b/core/squawk.h
index 9435ef9..9176f28 100644
--- a/core/squawk.h
+++ b/core/squawk.h
@@ -66,6 +66,7 @@ signals:
     void fileLocalPathResponse(const QString& messageId, const QString& path);
     void downloadFileError(const QString& messageId, const QString& error);
     void downloadFileProgress(const QString& messageId, qreal value);
+    void responseVCard(const QString& jid, const Shared::VCard& card);
     
 public slots:
     void start();
@@ -91,6 +92,7 @@ public slots:
     void removeRoomRequest(const QString& account, const QString& jid);
     void fileLocalPathRequest(const QString& messageId, const QString& url);
     void downloadFileRequest(const QString& messageId, const QString& url);
+    void requestVCard(const QString& account, const QString& jid);
     
 private:
     typedef std::deque<Account*> Accounts;
diff --git a/main.cpp b/main.cpp
index 83db32a..49a9875 100644
--- a/main.cpp
+++ b/main.cpp
@@ -105,6 +105,7 @@ int main(int argc, char *argv[])
     QObject::connect(&w, &Squawk::addContactToGroupRequest, squawk, &Core::Squawk::addContactToGroupRequest);
     QObject::connect(&w, &Squawk::removeContactFromGroupRequest, squawk, &Core::Squawk::removeContactFromGroupRequest);
     QObject::connect(&w, &Squawk::renameContactRequest, squawk, &Core::Squawk::renameContactRequest);
+    QObject::connect(&w, &Squawk::requestVCard, squawk, &Core::Squawk::requestVCard);
     
     QObject::connect(squawk, &Core::Squawk::newAccount, &w, &Squawk::newAccount);
     QObject::connect(squawk, &Core::Squawk::addContact, &w, &Squawk::addContact);
@@ -131,6 +132,7 @@ int main(int argc, char *argv[])
     QObject::connect(squawk, &Core::Squawk::fileLocalPathResponse, &w, &Squawk::fileLocalPathResponse);
     QObject::connect(squawk, &Core::Squawk::downloadFileProgress, &w, &Squawk::downloadFileProgress);
     QObject::connect(squawk, &Core::Squawk::downloadFileError, &w, &Squawk::downloadFileError);
+    QObject::connect(squawk, &Core::Squawk::responseVCard, &w, &Squawk::responseVCard);
     
     
     //qDebug() << QStandardPaths::writableLocation(QStandardPaths::CacheLocation);
diff --git a/ui/models/account.cpp b/ui/models/account.cpp
index 9a30db7..eeb8731 100644
--- a/ui/models/account.cpp
+++ b/ui/models/account.cpp
@@ -241,3 +241,12 @@ void Models::Account::setAvatarPath(const QString& path)
     changed(8);             //it's uncoditional because the path doesn't change when one avatar of the same type replaces another, sha1 sums checks are on the backend
 }
 
+QString Models::Account::getBareJid() const
+{
+    return login + "@" + server;
+}
+
+QString Models::Account::getFullJid() const
+{
+    return getBareJid() + "/" + resource;
+}
diff --git a/ui/models/account.h b/ui/models/account.h
index 8be7c45..e114699 100644
--- a/ui/models/account.h
+++ b/ui/models/account.h
@@ -64,6 +64,9 @@ namespace Models {
         
         void update(const QString& field, const QVariant& value);
         
+        QString getBareJid() const;
+        QString getFullJid() const;
+        
     private:
         QString login;
         QString password;
diff --git a/ui/squawk.cpp b/ui/squawk.cpp
index 455812f..051e691 100644
--- a/ui/squawk.cpp
+++ b/ui/squawk.cpp
@@ -29,7 +29,9 @@ Squawk::Squawk(QWidget *parent) :
     rosterModel(),
     conversations(),
     contextMenu(new QMenu()),
-    dbus("org.freedesktop.Notifications", "/org/freedesktop/Notifications", "org.freedesktop.Notifications", QDBusConnection::sessionBus())
+    dbus("org.freedesktop.Notifications", "/org/freedesktop/Notifications", "org.freedesktop.Notifications", QDBusConnection::sessionBus()),
+    requestedFiles(),
+    vCards()
 {
     m_ui->setupUi(this);
     m_ui->roster->setModel(&rosterModel);
@@ -136,12 +138,19 @@ void Squawk::closeEvent(QCloseEvent* event)
     if (accounts != 0) {
         accounts->close();
     }
+    
     for (Conversations::const_iterator itr = conversations.begin(), end = conversations.end(); itr != end; ++itr) {
-        disconnect(itr->second, SIGNAL(destroyed(QObject*)), this, SLOT(onConversationClosed(QObject*)));
+        disconnect(itr->second, &Conversation::destroyed, this, &Squawk::onConversationClosed);
         itr->second->close();
     }
     conversations.clear();
     
+    for (std::map<QString, VCard*>::const_iterator itr = vCards.begin(), end = vCards.end(); itr != end; ++itr) {
+        disconnect(itr->second, &VCard::destroyed, this, &Squawk::onVCardClosed);
+        itr->second->close();
+    }
+    vCards.clear();
+    
     QMainWindow::closeEvent(event);
 }
 
@@ -542,6 +551,10 @@ void Squawk::onRosterContextMenu(const QPoint& point)
                     });
                 }
                 
+                QAction* card = contextMenu->addAction(Shared::icon("user-properties"), tr("VCard"));
+                card->setEnabled(active);
+                connect(card, &QAction::triggered, std::bind(&Squawk::onActivateVCard, this, name, acc->getBareJid(), true));
+                
                 QAction* remove = contextMenu->addAction(Shared::icon("edit-delete"), tr("Remove"));
                 remove->setEnabled(active);
                 connect(remove, &QAction::triggered, [this, name]() {
@@ -636,6 +649,10 @@ void Squawk::onRosterContextMenu(const QPoint& point)
                 });
                 
                 
+                QAction* card = contextMenu->addAction(Shared::icon("user-properties"), tr("VCard"));
+                card->setEnabled(active);
+                connect(card, &QAction::triggered, std::bind(&Squawk::onActivateVCard, this, accName, cnt->getJid(), false));
+                
                 QAction* remove = contextMenu->addAction(Shared::icon("edit-delete"), tr("Remove"));
                 remove->setEnabled(active);
                 connect(remove, &QAction::triggered, [this, cnt]() {
@@ -721,3 +738,45 @@ void Squawk::removeRoomParticipant(const QString& account, const QString& jid, c
 {
     rosterModel.removeRoomParticipant(account, jid, name);
 }
+
+void Squawk::responseVCard(const QString& jid, const Shared::VCard& card)
+{
+    std::map<QString, VCard*>::const_iterator itr = vCards.find(jid);
+    if (itr != vCards.end()) {
+        itr->second->setVCard(card);
+    }
+}
+
+void Squawk::onVCardClosed()
+{
+    VCard* vCard = static_cast<VCard*>(sender());
+    
+    std::map<QString, VCard*>::const_iterator itr = vCards.find(vCard->getJid());
+    if (itr == vCards.end()) {
+        qDebug() << "VCard has been closed but can not be found among other opened vCards, application is most probably going to crash";
+        return;
+    }
+    vCards.erase(itr);
+}
+
+void Squawk::onActivateVCard(const QString& account, const QString& jid, bool edition)
+{
+    std::map<QString, VCard*>::const_iterator itr = vCards.find(jid);
+    VCard* card;
+    Models::Contact::Messages deque;
+    if (itr != vCards.end()) {
+        card = itr->second;
+    } else {
+        card = new VCard(jid, edition);
+        card->setAttribute(Qt::WA_DeleteOnClose);
+        vCards.insert(std::make_pair(jid, card));
+        
+        connect(card, &VCard::destroyed, this, &Squawk::onVCardClosed);
+    }
+    
+    card->show();
+    card->raise();
+    card->activateWindow();
+    
+    emit requestVCard(account, jid);
+}
diff --git a/ui/squawk.h b/ui/squawk.h
index c4d7f6f..7308882 100644
--- a/ui/squawk.h
+++ b/ui/squawk.h
@@ -33,6 +33,7 @@
 #include "widgets/room.h"
 #include "widgets/newcontact.h"
 #include "widgets/joinconference.h"
+#include "widgets/vcard.h"
 #include "models/roster.h"
 
 #include "../global.h"
@@ -71,6 +72,7 @@ signals:
     void removeRoomRequest(const QString& account, const QString& jid);
     void fileLocalPathRequest(const QString& messageId, const QString& url);
     void downloadFileRequest(const QString& messageId, const QString& url);
+    void requestVCard(const QString& account, const QString& jid);
     
 public slots:
     void newAccount(const QMap<QString, QVariant>& account);
@@ -96,6 +98,7 @@ public slots:
     void fileLocalPathResponse(const QString& messageId, const QString& path);
     void downloadFileError(const QString& messageId, const QString& error);
     void downloadFileProgress(const QString& messageId, qreal value);
+    void responseVCard(const QString& jid, const Shared::VCard& card);
     
 private:
     typedef std::map<Models::Roster::ElId, Conversation*> Conversations;
@@ -107,6 +110,7 @@ private:
     QMenu* contextMenu;
     QDBusInterface dbus;
     std::map<QString, std::set<Models::Roster::ElId>> requestedFiles;
+    std::map<QString, VCard*> vCards;
     
 protected:
     void closeEvent(QCloseEvent * event) override;
@@ -121,6 +125,8 @@ private slots:
     void onAccountsSizeChanged(unsigned int size);
     void onAccountsClosed(QObject* parent = 0);
     void onConversationClosed(QObject* parent = 0);
+    void onVCardClosed();
+    void onActivateVCard(const QString& account, const QString& jid, bool edition = false);
     void onComboboxActivated(int index);
     void onRosterItemDoubleClicked(const QModelIndex& item);
     void onConversationMessage(const Shared::Message& msg);
diff --git a/ui/utils/image.cpp b/ui/utils/image.cpp
index 6a2ce1c..dca8153 100644
--- a/ui/utils/image.cpp
+++ b/ui/utils/image.cpp
@@ -26,6 +26,17 @@ Image::Image(const QString& path, quint16 p_minWidth, QWidget* parent):
     minWidth(p_minWidth)
 {
     setScaledContents(true);
+    recalculateAspectRatio();
+}
+
+Image::Image(const QString& path, quint16 width, quint16 height, quint16 p_minWidth, QWidget* parent):
+    QLabel(parent),
+    pixmap(QIcon(path).pixmap(QSize(width, height))),
+    aspectRatio(0),
+    minWidth(p_minWidth)
+{
+    setScaledContents(true);
+    recalculateAspectRatio();
 }
 
 Image::~Image()
@@ -39,6 +50,11 @@ int Image::heightForWidth(int width) const
     return height;
 }
 
+int Image::widthForHeight(int height) const
+{
+    return height * aspectRatio;
+}
+
 bool Image::hasHeightForWidth() const
 {
     return true;
@@ -67,3 +83,9 @@ void Image::setPath(const QString& path)
     pixmap = QPixmap(path);
     recalculateAspectRatio();
 }
+
+void Image::setPath(const QString& path, quint16 width, quint16 height)
+{
+    pixmap = QPixmap(QIcon(path).pixmap(QSize(width, height)));
+    recalculateAspectRatio();
+}
diff --git a/ui/utils/image.h b/ui/utils/image.h
index a583f94..82071ca 100644
--- a/ui/utils/image.h
+++ b/ui/utils/image.h
@@ -21,6 +21,7 @@
 
 #include <QLabel>
 #include <QPixmap>
+#include <QIcon>
 
 /**
  * @todo write docs
@@ -29,12 +30,15 @@ class Image : public QLabel
 {
 public:
     Image(const QString& path, quint16 minWidth = 50, QWidget* parent = nullptr);
+    Image(const QString& path, quint16 width, quint16 height, quint16 minWidth = 50, QWidget* parent = nullptr);
 
     ~Image();
 
     int heightForWidth(int width) const override;
+    int widthForHeight(int height) const;
     bool hasHeightForWidth() const override;
     void setPath(const QString& path);
+    void setPath(const QString& path, quint16 width, quint16 height);
     void setMinWidth(quint16 minWidth);
     
 private:
diff --git a/ui/widgets/vcard.cpp b/ui/widgets/vcard.cpp
index 7072ca1..e19538a 100644
--- a/ui/widgets/vcard.cpp
+++ b/ui/widgets/vcard.cpp
@@ -19,12 +19,19 @@
 #include "vcard.h"
 #include "ui_vcard.h"
 
-VCard::VCard(bool edit, QWidget* parent):
+VCard::VCard(const QString& jid, bool edit, QWidget* parent):
     QWidget(parent),
     m_ui(new Ui::VCard()),
-    avatar(":/images/logo.svg", 64)
+    avatar(QApplication::palette().window().color().lightnessF() > 0.5 ? ":/images/fallback/dark/big/user.svg" : ":/images/fallback/light/big/user.svg", 256, 256, 256)
 {
     m_ui->setupUi(this);
+    m_ui->jabberID->setText(jid);
+    m_ui->jabberID->setReadOnly(true);
+    QGridLayout* general = static_cast<QGridLayout*>(m_ui->General->layout());
+    general->addWidget(&avatar, 2, 2, 1, 1);
+    avatar.setFrameShape(QFrame::StyledPanel);
+    avatar.setFrameShadow(QFrame::Sunken);
+    avatar.setMargin(6);
     
     if (edit) {
         
@@ -40,11 +47,15 @@ VCard::VCard(bool edit, QWidget* parent):
         m_ui->organizationTitle->setReadOnly(true);
         m_ui->organizationRole->setReadOnly(true);
         m_ui->description->setReadOnly(true);
-        m_ui->jabberID->setReadOnly(true);
+        m_ui->url->setReadOnly(true);
     }
     
     connect(m_ui->buttonBox, &QDialogButtonBox::accepted, this, &VCard::onButtonBoxAccepted);
     connect(m_ui->buttonBox, &QDialogButtonBox::rejected, m_ui->buttonBox, &QDialogButtonBox::deleteLater);
+    
+    int height = m_ui->personalForm->minimumSize().height();
+    avatar.setMaximumSize(avatar.widthForHeight(height), height);
+    avatar.setMinimumSize(avatar.widthForHeight(height), height);
 }
 
 VCard::~VCard()
@@ -54,6 +65,11 @@ VCard::~VCard()
 void VCard::setVCard(const QString& jid, const Shared::VCard& card)
 {
     m_ui->jabberID->setText(jid);
+    setVCard(card);
+}
+
+void VCard::setVCard(const Shared::VCard& card)
+{
     m_ui->firstName->setText(card.getFirstName());
     m_ui->middleName->setText(card.getMiddleName());
     m_ui->lastName->setText(card.getLastName());
@@ -65,17 +81,24 @@ void VCard::setVCard(const QString& jid, const Shared::VCard& card)
     //m_ui->organizationRole->setText(card.get());
     m_ui->description->setText(card.getDescription());
     
-    QString path;
     switch (card.getAvatarType()) {
         case Shared::Avatar::empty:
-            path = QApplication::palette().window().color().lightnessF() > 0.5 ? ":/images/fallback/dark/big/user.svg" : ":/images/fallback/light/big/user.svg";
+            avatar.setPath(QApplication::palette().window().color().lightnessF() > 0.5 ? ":/images/fallback/dark/big/user.svg" : ":/images/fallback/light/big/user.svg", 256, 256);
             break;
         case Shared::Avatar::autocreated:
         case Shared::Avatar::valid:
-            path = card.getAvatarPath();
+            avatar.setPath(card.getAvatarPath());
             break;
     }
-    avatar.setPath(path);
+    
+    int height = m_ui->personalForm->minimumSize().height();
+    avatar.setMaximumSize(avatar.widthForHeight(height), height);
+    avatar.setMinimumSize(avatar.widthForHeight(height), height);
+}
+
+QString VCard::getJid() const
+{
+    return m_ui->jabberID->text();
 }
 
 void VCard::onButtonBoxAccepted()
@@ -88,5 +111,5 @@ void VCard::onButtonBoxAccepted()
     card.setBirthday(m_ui->birthday->date());
     card.setDescription(m_ui->description->toPlainText());
     
-    emit saveVCard(m_ui->jabberID->text(), card);
+    emit saveVCard(card);
 }
diff --git a/ui/widgets/vcard.h b/ui/widgets/vcard.h
index dc14172..f4c81be 100644
--- a/ui/widgets/vcard.h
+++ b/ui/widgets/vcard.h
@@ -37,13 +37,15 @@ class VCard : public QWidget
 {
     Q_OBJECT
 public:
-    VCard(bool edit = false, QWidget* parent = nullptr);
+    VCard(const QString& jid, bool edit = false, QWidget* parent = nullptr);
     ~VCard();
     
+    void setVCard(const Shared::VCard& card);
     void setVCard(const QString& jid, const Shared::VCard& card);
+    QString getJid() const;
     
 signals:
-    void saveVCard(const QString& jid, const Shared::VCard& card);
+    void saveVCard(const Shared::VCard& card);
     
 private slots:
     void onButtonBoxAccepted();
diff --git a/ui/widgets/vcard.ui b/ui/widgets/vcard.ui
index 2ebae41..179e235 100644
--- a/ui/widgets/vcard.ui
+++ b/ui/widgets/vcard.ui
@@ -6,7 +6,7 @@
    <rect>
     <x>0</x>
     <y>0</y>
-    <width>544</width>
+    <width>560</width>
     <height>534</height>
    </rect>
   </property>
@@ -80,6 +80,12 @@
          <property name="styleSheet">
           <string notr="true"/>
          </property>
+         <property name="frameShape">
+          <enum>QFrame::NoFrame</enum>
+         </property>
+         <property name="frameShadow">
+          <enum>QFrame::Plain</enum>
+         </property>
          <property name="text">
           <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:16pt; font-weight:600;&quot;&gt;Personal information&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
          </property>
@@ -97,6 +103,9 @@
        </item>
        <item row="2" column="1">
         <layout class="QFormLayout" name="personalForm">
+         <property name="sizeConstraint">
+          <enum>QLayout::SetDefaultConstraint</enum>
+         </property>
          <property name="formAlignment">
           <set>Qt::AlignHCenter|Qt::AlignTop</set>
          </property>
@@ -104,13 +113,13 @@
           <widget class="QLineEdit" name="middleName">
            <property name="minimumSize">
             <size>
-             <width>150</width>
+             <width>200</width>
              <height>0</height>
             </size>
            </property>
            <property name="maximumSize">
             <size>
-             <width>300</width>
+             <width>350</width>
              <height>16777215</height>
             </size>
            </property>
@@ -120,13 +129,13 @@
           <widget class="QLineEdit" name="firstName">
            <property name="minimumSize">
             <size>
-             <width>150</width>
+             <width>200</width>
              <height>0</height>
             </size>
            </property>
            <property name="maximumSize">
             <size>
-             <width>300</width>
+             <width>350</width>
              <height>16777215</height>
             </size>
            </property>
@@ -166,13 +175,13 @@
           <widget class="QLineEdit" name="lastName">
            <property name="minimumSize">
             <size>
-             <width>150</width>
+             <width>200</width>
              <height>0</height>
             </size>
            </property>
            <property name="maximumSize">
             <size>
-             <width>300</width>
+             <width>350</width>
              <height>16777215</height>
             </size>
            </property>
@@ -192,13 +201,13 @@
           <widget class="QLineEdit" name="nickName">
            <property name="minimumSize">
             <size>
-             <width>150</width>
+             <width>200</width>
              <height>0</height>
             </size>
            </property>
            <property name="maximumSize">
             <size>
-             <width>300</width>
+             <width>350</width>
              <height>16777215</height>
             </size>
            </property>
@@ -219,35 +228,6 @@
          </item>
         </layout>
        </item>
-       <item row="2" column="2">
-        <widget class="QFrame" name="avatarFrame">
-         <property name="sizePolicy">
-          <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
-           <horstretch>0</horstretch>
-           <verstretch>0</verstretch>
-          </sizepolicy>
-         </property>
-         <property name="minimumSize">
-          <size>
-           <width>0</width>
-           <height>0</height>
-          </size>
-         </property>
-         <property name="sizeIncrement">
-          <size>
-           <width>0</width>
-           <height>0</height>
-          </size>
-         </property>
-         <property name="frameShape">
-          <enum>QFrame::StyledPanel</enum>
-         </property>
-         <property name="frameShadow">
-          <enum>QFrame::Raised</enum>
-         </property>
-         <layout class="QVBoxLayout" name="verticalLayout"/>
-        </widget>
-       </item>
        <item row="2" column="3" rowspan="7">
         <spacer name="generalRightHSpacer">
          <property name="orientation">
@@ -314,7 +294,7 @@
          <item row="0" column="0">
           <widget class="QLabel" name="organizationNameLabel">
            <property name="text">
-            <string>Name</string>
+            <string>Organization name</string>
            </property>
            <property name="buddy">
             <cstring>organizationName</cstring>
@@ -325,13 +305,13 @@
           <widget class="QLineEdit" name="organizationName">
            <property name="minimumSize">
             <size>
-             <width>150</width>
+             <width>200</width>
              <height>0</height>
             </size>
            </property>
            <property name="maximumSize">
             <size>
-             <width>300</width>
+             <width>350</width>
              <height>16777215</height>
             </size>
            </property>
@@ -340,7 +320,7 @@
          <item row="1" column="0">
           <widget class="QLabel" name="organizationDepartmentLabel">
            <property name="text">
-            <string>Department</string>
+            <string>Unit / Department</string>
            </property>
            <property name="buddy">
             <cstring>organizationDepartment</cstring>
@@ -351,13 +331,13 @@
           <widget class="QLineEdit" name="organizationDepartment">
            <property name="minimumSize">
             <size>
-             <width>150</width>
+             <width>200</width>
              <height>0</height>
             </size>
            </property>
            <property name="maximumSize">
             <size>
-             <width>300</width>
+             <width>350</width>
              <height>16777215</height>
             </size>
            </property>
@@ -366,7 +346,7 @@
          <item row="2" column="0">
           <widget class="QLabel" name="roleLabel">
            <property name="text">
-            <string>Role</string>
+            <string>Role / Profession</string>
            </property>
            <property name="buddy">
             <cstring>organizationRole</cstring>
@@ -377,13 +357,13 @@
           <widget class="QLineEdit" name="organizationRole">
            <property name="minimumSize">
             <size>
-             <width>150</width>
+             <width>200</width>
              <height>0</height>
             </size>
            </property>
            <property name="maximumSize">
             <size>
-             <width>300</width>
+             <width>350</width>
              <height>16777215</height>
             </size>
            </property>
@@ -392,7 +372,7 @@
          <item row="3" column="0">
           <widget class="QLabel" name="organizationTitleLabel">
            <property name="text">
-            <string>Title</string>
+            <string>Job title</string>
            </property>
            <property name="buddy">
             <cstring>organizationTitle</cstring>
@@ -403,13 +383,13 @@
           <widget class="QLineEdit" name="organizationTitle">
            <property name="minimumSize">
             <size>
-             <width>150</width>
+             <width>200</width>
              <height>0</height>
             </size>
            </property>
            <property name="maximumSize">
             <size>
-             <width>300</width>
+             <width>350</width>
              <height>16777215</height>
             </size>
            </property>
@@ -479,7 +459,7 @@
            <rect>
             <x>0</x>
             <y>0</y>
-            <width>532</width>
+            <width>548</width>
             <height>410</height>
            </rect>
           </property>
@@ -547,7 +527,7 @@
               </widget>
              </item>
              <item row="1" column="1">
-              <widget class="QLineEdit" name="lineEdit">
+              <widget class="QLineEdit" name="url">
                <property name="minimumSize">
                 <size>
                  <width>150</width>

From 652381b06762b5d1387dcc27efdea2a9ff2604bd Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Wed, 23 Oct 2019 17:49:56 +0300
Subject: [PATCH 011/281] changing avatar in local vcard, no uploading yet

---
 ui/models/roster.cpp        |  20 +++---
 ui/squawk.cpp               |  16 +++++
 ui/squawk.h                 |   2 +
 ui/utils/image.cpp          |  16 -----
 ui/utils/image.h            |   3 -
 ui/widgets/conversation.cpp |   6 +-
 ui/widgets/vcard.cpp        | 135 ++++++++++++++++++++++++++++++------
 ui/widgets/vcard.h          |  23 +++++-
 ui/widgets/vcard.ui         |  63 +++++++++++++++--
 9 files changed, 221 insertions(+), 63 deletions(-)

diff --git a/ui/models/roster.cpp b/ui/models/roster.cpp
index 5ea5b2e..33998dc 100644
--- a/ui/models/roster.cpp
+++ b/ui/models/roster.cpp
@@ -29,17 +29,14 @@ Models::Roster::Roster(QObject* parent):
     groups(),
     contacts()
 {
-    connect(accountsModel, 
-            SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&, const QVector<int>&)), 
-            this, 
-            SLOT(onAccountDataChanged(const QModelIndex&, const QModelIndex&, const QVector<int>&)));
-    connect(root, SIGNAL(childChanged(Models::Item*, int, int)), this, SLOT(onChildChanged(Models::Item*, int, int)));
-    connect(root, SIGNAL(childIsAboutToBeInserted(Item*, int, int)), this, SLOT(onChildIsAboutToBeInserted(Item*, int, int)));
-    connect(root, SIGNAL(childInserted()), this, SLOT(onChildInserted()));
-    connect(root, SIGNAL(childIsAboutToBeRemoved(Item*, int, int)), this, SLOT(onChildIsAboutToBeRemoved(Item*, int, int)));
-    connect(root, SIGNAL(childRemoved()), this, SLOT(onChildRemoved()));
-    connect(root, SIGNAL(childIsAboutToBeMoved(Item*, int, int, Item*, int)), this, SLOT(onChildIsAboutToBeMoved(Item*, int, int, Item*, int)));
-    connect(root, SIGNAL(childMoved()), this, SLOT(onChildMoved()));
+    connect(accountsModel, &Accounts::dataChanged, this, &Roster::onAccountDataChanged);
+    connect(root, &Item::childChanged, this, &Roster::onChildChanged);
+    connect(root, &Item::childIsAboutToBeInserted, this,  &Roster::onChildIsAboutToBeInserted);
+    connect(root, &Item::childInserted, this, &Roster::onChildInserted);
+    connect(root, &Item::childIsAboutToBeRemoved, this, &Roster::onChildIsAboutToBeRemoved);
+    connect(root, &Item::childRemoved, this, &Roster::onChildRemoved);
+    connect(root, &Item::childIsAboutToBeMoved, this, &Roster::onChildIsAboutToBeMoved);
+    connect(root, &Item::childMoved, this, &Roster::onChildMoved);
 }
 
 Models::Roster::~Roster()
@@ -69,6 +66,7 @@ QVariant Models::Roster::data (const QModelIndex& index, int role) const
         case Qt::DisplayRole:
         {
             if (index.column() != 0) {
+                result = "";
                 break;
             }
             switch (item->type) {
diff --git a/ui/squawk.cpp b/ui/squawk.cpp
index 051e691..b5cb84c 100644
--- a/ui/squawk.cpp
+++ b/ui/squawk.cpp
@@ -56,6 +56,8 @@ Squawk::Squawk(QWidget *parent) :
     
     connect(rosterModel.accountsModel, SIGNAL(sizeChanged(unsigned int)), this, SLOT(onAccountsSizeChanged(unsigned int)));
     //m_ui->mainToolBar->addWidget(m_ui->comboBox);
+    
+    setWindowTitle(tr("Contact list"));
 }
 
 Squawk::~Squawk() {
@@ -768,10 +770,16 @@ void Squawk::onActivateVCard(const QString& account, const QString& jid, bool ed
         card = itr->second;
     } else {
         card = new VCard(jid, edition);
+        if (edition) {
+            card->setWindowTitle(tr("%1 account card").arg(account));
+        } else {
+            card->setWindowTitle(tr("%1 contact card").arg(jid));
+        }
         card->setAttribute(Qt::WA_DeleteOnClose);
         vCards.insert(std::make_pair(jid, card));
         
         connect(card, &VCard::destroyed, this, &Squawk::onVCardClosed);
+        connect(card, &VCard::saveVCard, std::bind( &Squawk::onVCardSave, this, std::placeholders::_1, account));
     }
     
     card->show();
@@ -780,3 +788,11 @@ void Squawk::onActivateVCard(const QString& account, const QString& jid, bool ed
     
     emit requestVCard(account, jid);
 }
+
+void Squawk::onVCardSave(const Shared::VCard& card, const QString& account)
+{
+    VCard* widget = static_cast<VCard*>(sender());
+    emit uploadVCard(account, card);
+    
+    widget->deleteLater();
+}
diff --git a/ui/squawk.h b/ui/squawk.h
index 7308882..bf6582f 100644
--- a/ui/squawk.h
+++ b/ui/squawk.h
@@ -73,6 +73,7 @@ signals:
     void fileLocalPathRequest(const QString& messageId, const QString& url);
     void downloadFileRequest(const QString& messageId, const QString& url);
     void requestVCard(const QString& account, const QString& jid);
+    void uploadVCard(const QString& account, const Shared::VCard& card);
     
 public slots:
     void newAccount(const QMap<QString, QVariant>& account);
@@ -126,6 +127,7 @@ private slots:
     void onAccountsClosed(QObject* parent = 0);
     void onConversationClosed(QObject* parent = 0);
     void onVCardClosed();
+    void onVCardSave(const Shared::VCard& card, const QString& account);
     void onActivateVCard(const QString& account, const QString& jid, bool edition = false);
     void onComboboxActivated(int index);
     void onRosterItemDoubleClicked(const QModelIndex& item);
diff --git a/ui/utils/image.cpp b/ui/utils/image.cpp
index dca8153..1d09709 100644
--- a/ui/utils/image.cpp
+++ b/ui/utils/image.cpp
@@ -29,16 +29,6 @@ Image::Image(const QString& path, quint16 p_minWidth, QWidget* parent):
     recalculateAspectRatio();
 }
 
-Image::Image(const QString& path, quint16 width, quint16 height, quint16 p_minWidth, QWidget* parent):
-    QLabel(parent),
-    pixmap(QIcon(path).pixmap(QSize(width, height))),
-    aspectRatio(0),
-    minWidth(p_minWidth)
-{
-    setScaledContents(true);
-    recalculateAspectRatio();
-}
-
 Image::~Image()
 {
 
@@ -83,9 +73,3 @@ void Image::setPath(const QString& path)
     pixmap = QPixmap(path);
     recalculateAspectRatio();
 }
-
-void Image::setPath(const QString& path, quint16 width, quint16 height)
-{
-    pixmap = QPixmap(QIcon(path).pixmap(QSize(width, height)));
-    recalculateAspectRatio();
-}
diff --git a/ui/utils/image.h b/ui/utils/image.h
index 82071ca..883ddf4 100644
--- a/ui/utils/image.h
+++ b/ui/utils/image.h
@@ -21,7 +21,6 @@
 
 #include <QLabel>
 #include <QPixmap>
-#include <QIcon>
 
 /**
  * @todo write docs
@@ -30,7 +29,6 @@ class Image : public QLabel
 {
 public:
     Image(const QString& path, quint16 minWidth = 50, QWidget* parent = nullptr);
-    Image(const QString& path, quint16 width, quint16 height, quint16 minWidth = 50, QWidget* parent = nullptr);
 
     ~Image();
 
@@ -38,7 +36,6 @@ public:
     int widthForHeight(int height) const;
     bool hasHeightForWidth() const override;
     void setPath(const QString& path);
-    void setPath(const QString& path, quint16 width, quint16 height);
     void setMinWidth(quint16 minWidth);
     
 private:
diff --git a/ui/widgets/conversation.cpp b/ui/widgets/conversation.cpp
index d661a5c..c281258 100644
--- a/ui/widgets/conversation.cpp
+++ b/ui/widgets/conversation.cpp
@@ -257,11 +257,11 @@ void Conversation::showEvent(QShowEvent* event)
 
 void Conversation::onAttach()
 {
-    QFileDialog* d = new QFileDialog(this, "Chose a file to send");
+    QFileDialog* d = new QFileDialog(this, tr("Chose a file to send"));
     d->setFileMode(QFileDialog::ExistingFile);
     
-    connect(d, SIGNAL(accepted()), this, SLOT(onFileSelected()));
-    connect(d, SIGNAL(rejected()), d, SLOT(deleteLater()));
+    connect(d, &QFileDialog::accepted, this, &Conversation::onFileSelected);
+    connect(d, &QFileDialog::rejected, d, &QFileDialog::deleteLater);
     
     d->show();
 }
diff --git a/ui/widgets/vcard.cpp b/ui/widgets/vcard.cpp
index e19538a..552e078 100644
--- a/ui/widgets/vcard.cpp
+++ b/ui/widgets/vcard.cpp
@@ -19,22 +19,37 @@
 #include "vcard.h"
 #include "ui_vcard.h"
 
+#include <QDebug>
+
+const std::set<QString> VCard::supportedTypes = {"image/jpeg", "image/png"};
+
 VCard::VCard(const QString& jid, bool edit, QWidget* parent):
     QWidget(parent),
     m_ui(new Ui::VCard()),
-    avatar(QApplication::palette().window().color().lightnessF() > 0.5 ? ":/images/fallback/dark/big/user.svg" : ":/images/fallback/light/big/user.svg", 256, 256, 256)
+    avatarButtonMargins(),
+    avatarMenu(nullptr),
+    editable(edit),
+    currentAvatarType(Shared::Avatar::empty),
+    currentAvatarPath("")
 {
     m_ui->setupUi(this);
     m_ui->jabberID->setText(jid);
     m_ui->jabberID->setReadOnly(true);
-    QGridLayout* general = static_cast<QGridLayout*>(m_ui->General->layout());
-    general->addWidget(&avatar, 2, 2, 1, 1);
-    avatar.setFrameShape(QFrame::StyledPanel);
-    avatar.setFrameShadow(QFrame::Sunken);
-    avatar.setMargin(6);
+    
+    QAction* setAvatar = m_ui->actionSetAvatar;
+    QAction* clearAvatar = m_ui->actionClearAvatar;
+    
+    connect(setAvatar, &QAction::triggered, this, &VCard::onSetAvatar);
+    connect(clearAvatar, &QAction::triggered, this, &VCard::onClearAvatar);
+    
+    setAvatar->setEnabled(true);
+    clearAvatar->setEnabled(false);
     
     if (edit) {
-        
+        avatarMenu = new QMenu();
+        m_ui->avatarButton->setMenu(avatarMenu);
+        avatarMenu->addAction(setAvatar);
+        avatarMenu->addAction(clearAvatar);
     } else {
         m_ui->buttonBox->hide();
         m_ui->firstName->setReadOnly(true);
@@ -53,13 +68,17 @@ VCard::VCard(const QString& jid, bool edit, QWidget* parent):
     connect(m_ui->buttonBox, &QDialogButtonBox::accepted, this, &VCard::onButtonBoxAccepted);
     connect(m_ui->buttonBox, &QDialogButtonBox::rejected, m_ui->buttonBox, &QDialogButtonBox::deleteLater);
     
-    int height = m_ui->personalForm->minimumSize().height();
-    avatar.setMaximumSize(avatar.widthForHeight(height), height);
-    avatar.setMinimumSize(avatar.widthForHeight(height), height);
+    avatarButtonMargins = m_ui->avatarButton->size();
+    
+    int height = m_ui->personalForm->minimumSize().height() - avatarButtonMargins.height();
+    m_ui->avatarButton->setIconSize(QSize(height, height));
 }
 
 VCard::~VCard()
 {
+    if (editable) {
+        avatarMenu->deleteLater();
+    }
 }
 
 void VCard::setVCard(const QString& jid, const Shared::VCard& card)
@@ -80,20 +99,10 @@ void VCard::setVCard(const Shared::VCard& card)
     //m_ui->organizationTitle->setText(card.get());
     //m_ui->organizationRole->setText(card.get());
     m_ui->description->setText(card.getDescription());
+    currentAvatarType = card.getAvatarType();
+    currentAvatarPath = card.getAvatarPath();
     
-    switch (card.getAvatarType()) {
-        case Shared::Avatar::empty:
-            avatar.setPath(QApplication::palette().window().color().lightnessF() > 0.5 ? ":/images/fallback/dark/big/user.svg" : ":/images/fallback/light/big/user.svg", 256, 256);
-            break;
-        case Shared::Avatar::autocreated:
-        case Shared::Avatar::valid:
-            avatar.setPath(card.getAvatarPath());
-            break;
-    }
-    
-    int height = m_ui->personalForm->minimumSize().height();
-    avatar.setMaximumSize(avatar.widthForHeight(height), height);
-    avatar.setMinimumSize(avatar.widthForHeight(height), height);
+    updateAvatar();
 }
 
 QString VCard::getJid() const
@@ -110,6 +119,86 @@ void VCard::onButtonBoxAccepted()
     card.setNickName(m_ui->nickName->text());
     card.setBirthday(m_ui->birthday->date());
     card.setDescription(m_ui->description->toPlainText());
+    card.setAvatarPath(currentAvatarPath);
+    card.setAvatarType(currentAvatarType);
     
     emit saveVCard(card);
 }
+
+void VCard::onClearAvatar()
+{
+    currentAvatarType = Shared::Avatar::empty;
+    currentAvatarPath = "";
+    
+    updateAvatar();
+}
+
+void VCard::onSetAvatar()
+{
+    QFileDialog* d = new QFileDialog(this, tr("Chose your new avatar"));
+    d->setFileMode(QFileDialog::ExistingFile);
+    d->setNameFilter(tr("Images (*.png *.jpg *.jpeg)"));
+    
+    connect(d, &QFileDialog::accepted, this, &VCard::onAvatarSelected);
+    connect(d, &QFileDialog::rejected, d, &QFileDialog::deleteLater);
+    
+    d->show();
+}
+
+void VCard::updateAvatar()
+{
+    int height = m_ui->personalForm->minimumSize().height() - avatarButtonMargins.height();
+    switch (currentAvatarType) {
+        case Shared::Avatar::empty:
+            m_ui->avatarButton->setIcon(Shared::icon("user", true));
+            m_ui->avatarButton->setIconSize(QSize(height, height));
+            m_ui->actionClearAvatar->setEnabled(false);
+            break;
+        case Shared::Avatar::autocreated:
+        case Shared::Avatar::valid:
+            QPixmap pixmap(currentAvatarPath);
+            qreal h = pixmap.height();
+            qreal w = pixmap.width();
+            qreal aspectRatio = w / h;
+            m_ui->avatarButton->setIconSize(QSize(height * aspectRatio, height));
+            m_ui->avatarButton->setIcon(QIcon(currentAvatarPath));
+            m_ui->actionClearAvatar->setEnabled(true);
+            break;
+    }
+}
+
+void VCard::onAvatarSelected()
+{
+    QFileDialog* d = static_cast<QFileDialog*>(sender());
+    QMimeDatabase db;
+    QString path = d->selectedFiles().front();
+    QMimeType type = db.mimeTypeForFile(path);
+    d->deleteLater();
+    
+    if (supportedTypes.find(type.name()) == supportedTypes.end()) {
+        qDebug() << "Selected for avatar file is not supported, skipping";
+    } else {
+        QImage src(path);
+        QImage dst;
+        if (src.width() > 160 || src.height() > 160) {
+            dst = src.scaled(160, 160, Qt::KeepAspectRatio);
+        }
+        QString path = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/" + m_ui->jabberID->text() + ".temp." + type.preferredSuffix();
+        QFile oldTemp(path);
+        if (oldTemp.exists()) {
+            if (!oldTemp.remove()) {
+                qDebug() << "Error removing old temp avatar" << path;
+                return;
+            }
+        }
+        bool success = dst.save(path);
+        if (success) {
+            currentAvatarPath = path;
+            currentAvatarType = Shared::Avatar::valid;
+            
+            updateAvatar();
+        } else {
+            qDebug() << "couldn't save avatar" << path << ", skipping";
+        }
+    }
+}
diff --git a/ui/widgets/vcard.h b/ui/widgets/vcard.h
index f4c81be..afba227 100644
--- a/ui/widgets/vcard.h
+++ b/ui/widgets/vcard.h
@@ -21,9 +21,16 @@
 
 #include <QWidget>
 #include <QScopedPointer>
+#include <QPixmap>
+#include <QMenu>
+#include <QFileDialog>
+#include <QMimeDatabase>
+#include <QImage>
+#include <QStandardPaths>
+
+#include <set>
 
 #include "../../global.h"
-#include "../utils/image.h"
 
 namespace Ui
 {
@@ -49,10 +56,22 @@ signals:
     
 private slots:
     void onButtonBoxAccepted();
+    void onClearAvatar();
+    void onSetAvatar();
+    void onAvatarSelected();
     
 private:
     QScopedPointer<Ui::VCard> m_ui;
-    Image avatar;
+    QSize avatarButtonMargins;
+    QMenu* avatarMenu;
+    bool editable;
+    Shared::Avatar currentAvatarType;
+    QString currentAvatarPath;
+    
+    static const std::set<QString> supportedTypes;
+    
+private:
+    void updateAvatar();
 };
 
 #endif // VCARD_H
diff --git a/ui/widgets/vcard.ui b/ui/widgets/vcard.ui
index 179e235..406ca9a 100644
--- a/ui/widgets/vcard.ui
+++ b/ui/widgets/vcard.ui
@@ -6,8 +6,8 @@
    <rect>
     <x>0</x>
     <y>0</y>
-    <width>560</width>
-    <height>534</height>
+    <width>537</width>
+    <height>539</height>
    </rect>
   </property>
   <layout class="QVBoxLayout" name="verticalLayout_2">
@@ -411,6 +411,43 @@
          </property>
         </widget>
        </item>
+       <item row="2" column="2">
+        <widget class="QToolButton" name="avatarButton">
+         <property name="sizePolicy">
+          <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+           <horstretch>0</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
+         <property name="minimumSize">
+          <size>
+           <width>0</width>
+           <height>0</height>
+          </size>
+         </property>
+         <property name="text">
+          <string/>
+         </property>
+         <property name="icon">
+          <iconset theme="user"/>
+         </property>
+         <property name="iconSize">
+          <size>
+           <width>0</width>
+           <height>0</height>
+          </size>
+         </property>
+         <property name="popupMode">
+          <enum>QToolButton::InstantPopup</enum>
+         </property>
+         <property name="toolButtonStyle">
+          <enum>Qt::ToolButtonIconOnly</enum>
+         </property>
+         <property name="arrowType">
+          <enum>Qt::NoArrow</enum>
+         </property>
+        </widget>
+       </item>
       </layout>
      </widget>
      <widget class="QWidget" name="Contact">
@@ -459,8 +496,8 @@
            <rect>
             <x>0</x>
             <y>0</y>
-            <width>548</width>
-            <height>410</height>
+            <width>525</width>
+            <height>415</height>
            </rect>
           </property>
           <layout class="QGridLayout" name="gridLayout_3" columnstretch="1,4,1">
@@ -625,7 +662,7 @@
            <item row="9" column="1">
             <layout class="QVBoxLayout" name="addressesLayout">
              <item>
-              <widget class="QLabel" name="EmptyAddressesPlaceholder">
+              <widget class="QLabel" name="emptyAddressesPlaceholder">
                <property name="text">
                 <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-style:italic;&quot;&gt;User has no contact e-mail addresses&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
                </property>
@@ -717,6 +754,22 @@
     </widget>
    </item>
   </layout>
+  <action name="actionSetAvatar">
+   <property name="icon">
+    <iconset theme="photo"/>
+   </property>
+   <property name="text">
+    <string>Set avatar</string>
+   </property>
+  </action>
+  <action name="actionClearAvatar">
+   <property name="icon">
+    <iconset theme="edit-clear-all"/>
+   </property>
+   <property name="text">
+    <string>Clear avatar</string>
+   </property>
+  </action>
  </widget>
  <tabstops>
   <tabstop>firstName</tabstop>

From 36c71968bc46e0230e688cc897d72bd54a1964fe Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Thu, 24 Oct 2019 12:42:38 +0300
Subject: [PATCH 012/281] basic avatar/vcard changes uploads and roster
 reaction

---
 core/account.cpp     | 88 +++++++++++++++++++++++++++++++++++++++-----
 core/account.h       |  1 +
 core/squawk.cpp      | 10 +++++
 core/squawk.h        |  1 +
 global.h             |  1 +
 main.cpp             |  1 +
 ui/models/roster.cpp |  4 +-
 ui/widgets/vcard.cpp |  2 +-
 8 files changed, 97 insertions(+), 11 deletions(-)

diff --git a/core/account.cpp b/core/account.cpp
index 04d2947..a6b0bde 100644
--- a/core/account.cpp
+++ b/core/account.cpp
@@ -103,15 +103,15 @@ Account::Account(const QString& p_login, const QString& p_server, const QString&
     if (!avatar->exists()) {
         delete avatar;
         avatar = new QFile(path + "/avatar.jpg");
-        QString type = "jpg";
+        type = "jpg";
         if (!avatar->exists()) {
             delete avatar;
             avatar = new QFile(path + "/avatar.jpeg");
-            QString type = "jpeg";
+            type = "jpeg";
             if (!avatar->exists()) {
                 delete avatar;
                 avatar = new QFile(path + "/avatar.gif");
-                QString type = "gif";
+                type = "gif";
             }
         }
     }
@@ -1337,7 +1337,6 @@ void Core::Account::onVCardReceived(const QXmppVCardIq& card)
         if (confItr == conferences.end()) {
             if (jid == getLogin() + "@" + getServer()) {
                 onOwnVCardReceived(card);
-                
             } else {
                 qDebug() << "received vCard" << jid << "doesn't belong to any of known contacts or conferences, skipping";
             }
@@ -1375,13 +1374,18 @@ void Core::Account::onVCardReceived(const QXmppVCardIq& card)
         vCard.setAvatarType(Shared::Avatar::empty);
     }
     
+    QMap<QString, QVariant> cd = {
+        {"avatarState", static_cast<quint8>(vCard.getAvatarType())},
+        {"avatarPath", vCard.getAvatarPath()}
+    };
+    emit changeContact(jid, cd);
     emit receivedVCard(jid, vCard);
 }
 
 void Core::Account::onOwnVCardReceived(const QXmppVCardIq& card)
 {
     QByteArray ava = card.photo();
-    bool changed = false;
+    bool avaChanged = false;
     QString path = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/" + name + "/";
     if (ava.size() > 0) {
         QCryptographicHash sha1(QCryptographicHash::Sha1);
@@ -1407,7 +1411,7 @@ void Core::Account::onOwnVCardReceived(const QXmppVCardIq& card)
                     newAvatar.close();
                     avatarHash = newHash;
                     avatarType = newType.preferredSuffix();
-                    changed = true;
+                    avaChanged = true;
                 } else {
                     qDebug() << "Received new avatar for account" << name << "but can't save it";
                     if (oldToRemove) {
@@ -1425,7 +1429,7 @@ void Core::Account::onOwnVCardReceived(const QXmppVCardIq& card)
                 newAvatar.close();
                 avatarHash = newHash;
                 avatarType = newType.preferredSuffix();
-                changed = true;
+                avaChanged = true;
             } else {
                 qDebug() << "Received new avatar for account" << name << "but can't save it";
             }
@@ -1436,12 +1440,14 @@ void Core::Account::onOwnVCardReceived(const QXmppVCardIq& card)
             if (!oldAvatar.remove()) {
                 qDebug() << "Received vCard for account" << name << "without avatar, but can't get rid of the file, doing nothing";
             } else {
-                changed = true;
+                avatarType = "";
+                avatarHash = "";
+                avaChanged = true;
             }
         }
     }
     
-    if (changed) {
+    if (avaChanged) {
         QMap<QString, QVariant> change;
         if (avatarType.size() > 0) {
             presence.setPhotoHash(avatarHash.toUtf8());
@@ -1453,6 +1459,7 @@ void Core::Account::onOwnVCardReceived(const QXmppVCardIq& card)
             change.insert("avatarPath", "");
         }
         client.setClientPresence(presence);
+        emit changed(change);
     }
     
     ownVCardRequestInProgress = false;
@@ -1504,3 +1511,66 @@ void Core::Account::requestVCard(const QString& jid)
         }
     }
 }
+
+void Core::Account::uploadVCard(const Shared::VCard& card)
+{
+    QXmppVCardIq iq;
+    iq.setFirstName(card.getFirstName());
+    iq.setMiddleName(card.getMiddleName());
+    iq.setLastName(card.getLastName());
+    iq.setNickName(card.getNickName());
+    iq.setBirthday(card.getBirthday());
+    iq.setDescription(card.getDescription());
+    
+    bool avatarChanged = false;
+    if (card.getAvatarType() == Shared::Avatar::empty) {
+        if (avatarType.size() > 0) {
+            avatarChanged = true;
+        }
+    } else {
+        QString newPath = card.getAvatarPath();
+        QString oldPath = getAvatarPath();
+        QByteArray data;
+        QString type;
+        if (newPath != oldPath) {
+            QFile avatar(newPath);
+            if (!avatar.open(QFile::ReadOnly)) {
+                qDebug() << "An attempt to upload new vCard to account" << name 
+                << "but it wasn't possible to read file" << newPath 
+                << "which was supposed to be new avatar, uploading old avatar";
+                if (avatarType.size() > 0) {
+                    QFile oA(oldPath);
+                    if (!oA.open(QFile::ReadOnly)) {
+                        qDebug() << "Couldn't read old avatar of account" << name << ", uploading empty avatar";
+                        avatarChanged = true;
+                    } else {
+                        data = oA.readAll();
+                    }
+                }
+            } else {
+                data = avatar.readAll();
+                avatarChanged = true;
+            }
+        } else {
+            if (avatarType.size() > 0) {
+                QFile oA(oldPath);
+                if (!oA.open(QFile::ReadOnly)) {
+                    qDebug() << "Couldn't read old avatar of account" << name << ", uploading empty avatar";
+                    avatarChanged = true;
+                } else {
+                    data = oA.readAll();
+                }
+            }
+        }
+        
+        if (data.size() > 0) {
+            QMimeDatabase db;
+            type = db.mimeTypeForData(data).name();
+            iq.setPhoto(data);
+            iq.setPhotoType(type);
+        }
+    }
+    
+    client.vCardManager().setClientVCard(iq);
+    onOwnVCardReceived(iq);
+}
diff --git a/core/account.h b/core/account.h
index b5b8c46..371a561 100644
--- a/core/account.h
+++ b/core/account.h
@@ -88,6 +88,7 @@ public:
     void removeRoomRequest(const QString& jid);
     void addRoomRequest(const QString& jid, const QString& nick, const QString& password, bool autoJoin);
     void requestVCard(const QString& jid);
+    void uploadVCard(const Shared::VCard& card);
     
 signals:
     void changed(const QMap<QString, QVariant>& data);
diff --git a/core/squawk.cpp b/core/squawk.cpp
index 35977a4..9f421c9 100644
--- a/core/squawk.cpp
+++ b/core/squawk.cpp
@@ -543,3 +543,13 @@ void Core::Squawk::requestVCard(const QString& account, const QString& jid)
     }
     itr->second->requestVCard(jid);
 }
+
+void Core::Squawk::uploadVCard(const QString& account, const Shared::VCard& card)
+{
+    AccountsMap::const_iterator itr = amap.find(account);
+    if (itr == amap.end()) {
+        qDebug() << "An attempt to upload vcard to non existing account" << account << ", skipping";
+        return;
+    }
+    itr->second->uploadVCard(card);
+}
diff --git a/core/squawk.h b/core/squawk.h
index 9176f28..88ea860 100644
--- a/core/squawk.h
+++ b/core/squawk.h
@@ -93,6 +93,7 @@ public slots:
     void fileLocalPathRequest(const QString& messageId, const QString& url);
     void downloadFileRequest(const QString& messageId, const QString& url);
     void requestVCard(const QString& account, const QString& jid);
+    void uploadVCard(const QString& account, const Shared::VCard& card);
     
 private:
     typedef std::deque<Account*> Accounts;
diff --git a/global.h b/global.h
index b20e616..0daa20b 100644
--- a/global.h
+++ b/global.h
@@ -437,6 +437,7 @@ static const std::map<QString, std::pair<QString, QString>> icons = {
     {"view-refresh", {"view-refresh", "view-refresh"}},
     {"send", {"document-send", "send"}},
     {"clean", {"edit-clear-all", "clean"}},
+    {"user", {"user", "user"}},
 };
 
 };
diff --git a/main.cpp b/main.cpp
index 49a9875..1c455bc 100644
--- a/main.cpp
+++ b/main.cpp
@@ -106,6 +106,7 @@ int main(int argc, char *argv[])
     QObject::connect(&w, &Squawk::removeContactFromGroupRequest, squawk, &Core::Squawk::removeContactFromGroupRequest);
     QObject::connect(&w, &Squawk::renameContactRequest, squawk, &Core::Squawk::renameContactRequest);
     QObject::connect(&w, &Squawk::requestVCard, squawk, &Core::Squawk::requestVCard);
+    QObject::connect(&w, &Squawk::uploadVCard, squawk, &Core::Squawk::uploadVCard);
     
     QObject::connect(squawk, &Core::Squawk::newAccount, &w, &Squawk::newAccount);
     QObject::connect(squawk, &Core::Squawk::addContact, &w, &Squawk::addContact);
diff --git a/ui/models/roster.cpp b/ui/models/roster.cpp
index 33998dc..6e49104 100644
--- a/ui/models/roster.cpp
+++ b/ui/models/roster.cpp
@@ -98,6 +98,7 @@ QVariant Models::Roster::data (const QModelIndex& index, int role) const
                         result = acc->getStatusIcon(false);
                     } else if (col == 1) {
                         QString path = acc->getAvatarPath();
+                        
                         if (path.size() > 0) {
                             result = QIcon(path);
                         }
@@ -641,7 +642,8 @@ void Models::Roster::removeContact(const QString& account, const QString& jid, c
 void Models::Roster::onChildChanged(Models::Item* item, int row, int col)
 {
     QModelIndex index = createIndex(row, 0, item);
-    emit dataChanged(index, index);
+    QModelIndex index2 = createIndex(row, 1, item);
+    emit dataChanged(index, index2);
 }
 
 void Models::Roster::onChildIsAboutToBeInserted(Models::Item* parent, int first, int last)
diff --git a/ui/widgets/vcard.cpp b/ui/widgets/vcard.cpp
index 552e078..1da6c2e 100644
--- a/ui/widgets/vcard.cpp
+++ b/ui/widgets/vcard.cpp
@@ -66,7 +66,7 @@ VCard::VCard(const QString& jid, bool edit, QWidget* parent):
     }
     
     connect(m_ui->buttonBox, &QDialogButtonBox::accepted, this, &VCard::onButtonBoxAccepted);
-    connect(m_ui->buttonBox, &QDialogButtonBox::rejected, m_ui->buttonBox, &QDialogButtonBox::deleteLater);
+    connect(m_ui->buttonBox, &QDialogButtonBox::rejected, this, &VCard::close);
     
     avatarButtonMargins = m_ui->avatarButton->size();
     

From 566fc1f2fb5ee25cba97288c191c71430bf4848c Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Fri, 25 Oct 2019 16:38:48 +0300
Subject: [PATCH 013/281] new fallback icons, new fields in vCard, fix about
 recreation new avatar on each request

---
 core/account.cpp                              |  29 +-
 global.cpp                                    |  84 ++++
 global.h                                      |  22 +
 .../images/fallback/dark/big/edit-rename.svg  |  14 +
 .../images/fallback/dark/big/group-new.svg    |  14 +
 resources/images/fallback/dark/big/group.svg  |  14 +
 .../fallback/dark/big/user-properties.svg     |  14 +
 .../fallback/dark/small/edit-rename.svg       |  13 +
 .../images/fallback/dark/small/group-new.svg  |  13 +
 .../images/fallback/dark/small/group.svg      |  13 +
 .../fallback/dark/small/user-properties.svg   |  13 +
 .../images/fallback/light/big/edit-rename.svg |  14 +
 .../images/fallback/light/big/group-new.svg   |  14 +
 resources/images/fallback/light/big/group.svg |  14 +
 .../fallback/light/big/user-properties.svg    |  14 +
 .../fallback/light/small/edit-rename.svg      |  13 +
 .../images/fallback/light/small/group-new.svg |  13 +
 .../images/fallback/light/small/group.svg     |  13 +
 .../fallback/light/small/user-properties.svg  |  13 +
 resources/resources.qrc                       |  16 +
 ui/models/roster.cpp                          |  20 +
 ui/models/roster.h                            |   1 +
 ui/squawk.cpp                                 |  11 +-
 ui/widgets/vcard.cpp                          |  17 +-
 ui/widgets/vcard.ui                           | 418 ++++++++++--------
 25 files changed, 629 insertions(+), 205 deletions(-)
 create mode 100644 resources/images/fallback/dark/big/edit-rename.svg
 create mode 100644 resources/images/fallback/dark/big/group-new.svg
 create mode 100644 resources/images/fallback/dark/big/group.svg
 create mode 100644 resources/images/fallback/dark/big/user-properties.svg
 create mode 100644 resources/images/fallback/dark/small/edit-rename.svg
 create mode 100644 resources/images/fallback/dark/small/group-new.svg
 create mode 100644 resources/images/fallback/dark/small/group.svg
 create mode 100644 resources/images/fallback/dark/small/user-properties.svg
 create mode 100644 resources/images/fallback/light/big/edit-rename.svg
 create mode 100644 resources/images/fallback/light/big/group-new.svg
 create mode 100644 resources/images/fallback/light/big/group.svg
 create mode 100644 resources/images/fallback/light/big/user-properties.svg
 create mode 100644 resources/images/fallback/light/small/edit-rename.svg
 create mode 100644 resources/images/fallback/light/small/group-new.svg
 create mode 100644 resources/images/fallback/light/small/group.svg
 create mode 100644 resources/images/fallback/light/small/user-properties.svg

diff --git a/core/account.cpp b/core/account.cpp
index a6b0bde..f3e4156 100644
--- a/core/account.cpp
+++ b/core/account.cpp
@@ -1353,18 +1353,28 @@ void Core::Account::onVCardReceived(const QXmppVCardIq& card)
     if (ava.size() > 0) {
         item->setAvatar(ava);
     } else {
-        item->setAutoGeneratedAvatar();
+        if (!item->hasAvatar() || !item->isAvatarAutoGenerated()) {
+            item->setAutoGeneratedAvatar();
+        }
     }
     
     Shared::VCard vCard;
+    vCard.setFullName(card.fullName());
     vCard.setFirstName(card.firstName());
     vCard.setMiddleName(card.middleName());
     vCard.setLastName(card.lastName());
     vCard.setBirthday(card.birthday());
     vCard.setNickName(card.nickName());
     vCard.setDescription(card.description());
+    vCard.setUrl(card.url());
+    QXmppVCardOrganization org = card.organization();
+    vCard.setOrgName(org.organization());
+    vCard.setOrgRole(org.role());
+    vCard.setOrgUnit(org.unit());
+    vCard.setOrgTitle(org.title());
+    
     if (item->hasAvatar()) {
-        if (item->isAvatarAutoGenerated()) {
+        if (!item->isAvatarAutoGenerated()) {
             vCard.setAvatarType(Shared::Avatar::valid);
         } else {
             vCard.setAvatarType(Shared::Avatar::autocreated);
@@ -1465,12 +1475,19 @@ void Core::Account::onOwnVCardReceived(const QXmppVCardIq& card)
     ownVCardRequestInProgress = false;
     
     Shared::VCard vCard;
+    vCard.setFullName(card.fullName());
     vCard.setFirstName(card.firstName());
     vCard.setMiddleName(card.middleName());
     vCard.setLastName(card.lastName());
     vCard.setBirthday(card.birthday());
     vCard.setNickName(card.nickName());
     vCard.setDescription(card.description());
+    vCard.setUrl(card.url());
+    QXmppVCardOrganization org = card.organization();
+    vCard.setOrgName(org.organization());
+    vCard.setOrgRole(org.role());
+    vCard.setOrgUnit(org.unit());
+    vCard.setOrgTitle(org.title());
     if (avatarType.size() > 0) {
         vCard.setAvatarType(Shared::Avatar::valid);
         vCard.setAvatarPath(path + "avatar." + avatarType);
@@ -1515,12 +1532,20 @@ void Core::Account::requestVCard(const QString& jid)
 void Core::Account::uploadVCard(const Shared::VCard& card)
 {
     QXmppVCardIq iq;
+    iq.setFullName(card.getFullName());
     iq.setFirstName(card.getFirstName());
     iq.setMiddleName(card.getMiddleName());
     iq.setLastName(card.getLastName());
     iq.setNickName(card.getNickName());
     iq.setBirthday(card.getBirthday());
     iq.setDescription(card.getDescription());
+    iq.setUrl(card.getUrl());
+    QXmppVCardOrganization org;
+    org.setOrganization(card.getOrgName());
+    org.setUnit(card.getOrgUnit());
+    org.setRole(card.getOrgRole());
+    org.setTitle(card.getOrgTitle());
+    iq.setOrganization(org);
     
     bool avatarChanged = false;
     if (card.getAvatarType() == Shared::Avatar::empty) {
diff --git a/global.cpp b/global.cpp
index fa63539..efadd6c 100644
--- a/global.cpp
+++ b/global.cpp
@@ -323,11 +323,17 @@ Shared::VCard::Address::Address(const QString& zCode, const QString& cntry, cons
 {}
 
 Shared::VCard::VCard():
+    fullName(),
     firstName(),
     middleName(),
     lastName(),
     nickName(),
     description(),
+    url(),
+    organizationName(),
+    organizationUnit(),
+    organizationRole(),
+    jobTitle(),
     birthday(),
     photoType(Avatar::empty),
     photoPath(),
@@ -338,11 +344,17 @@ Shared::VCard::VCard():
 {}
 
 Shared::VCard::VCard(const QDateTime& creationTime):
+    fullName(),
     firstName(),
     middleName(),
     lastName(),
     nickName(),
     description(),
+    url(),
+    organizationName(),
+    organizationUnit(),
+    organizationRole(),
+    jobTitle(),
     birthday(),
     photoType(Avatar::empty),
     photoPath(),
@@ -449,6 +461,78 @@ void Shared::VCard::setNickName(const QString& nick)
     }
 }
 
+QString Shared::VCard::getFullName() const
+{
+    return fullName;
+}
+
+QString Shared::VCard::getUrl() const
+{
+    return url;
+}
+
+void Shared::VCard::setFullName(const QString& name)
+{
+    if (fullName != name) {
+        fullName = name;
+    }
+}
+
+void Shared::VCard::setUrl(const QString& u)
+{
+    if (url != u) {
+        url = u;
+    }
+}
+
+QString Shared::VCard::getOrgName() const
+{
+    return organizationName;
+}
+
+QString Shared::VCard::getOrgRole() const
+{
+    return organizationRole;
+}
+
+QString Shared::VCard::getOrgTitle() const
+{
+    return jobTitle;
+}
+
+QString Shared::VCard::getOrgUnit() const
+{
+    return organizationUnit;
+}
+
+void Shared::VCard::setOrgName(const QString& name)
+{
+    if (organizationName != name) {
+        organizationName = name;
+    }
+}
+
+void Shared::VCard::setOrgRole(const QString& role)
+{
+    if (organizationRole != role) {
+        organizationRole = role;
+    }
+}
+
+void Shared::VCard::setOrgTitle(const QString& title)
+{
+    if (jobTitle != title) {
+        jobTitle = title;
+    }
+}
+
+void Shared::VCard::setOrgUnit(const QString& unit)
+{
+    if (organizationUnit != unit) {
+        organizationUnit = unit;
+    }
+}
+
 QIcon Shared::availabilityIcon(Shared::Availability av, bool big)
 {
     const std::deque<QString>& fallback = QApplication::palette().window().color().lightnessF() > 0.5 ? 
diff --git a/global.h b/global.h
index 0daa20b..1261d1f 100644
--- a/global.h
+++ b/global.h
@@ -272,6 +272,8 @@ public:
     VCard();
     VCard(const QDateTime& creationTime);
     
+    QString getFullName() const;
+    void setFullName(const QString& name);
     QString getFirstName() const;
     void setFirstName(const QString& first);
     QString getMiddleName() const;
@@ -282,19 +284,35 @@ public:
     void setNickName(const QString& nick);
     QString getDescription() const;
     void setDescription(const QString& descr);
+    QString getUrl() const;
+    void setUrl(const QString& u);
     QDate getBirthday() const;
     void setBirthday(const QDate& date);
     Avatar getAvatarType() const;
     void setAvatarType(Avatar type);
     QString getAvatarPath() const;
     void setAvatarPath(const QString& path);
+    QString getOrgName() const;
+    void setOrgName(const QString& name);
+    QString getOrgUnit() const;
+    void setOrgUnit(const QString& unit);
+    QString getOrgRole() const;
+    void setOrgRole(const QString& role);
+    QString getOrgTitle() const;
+    void setOrgTitle(const QString& title);
     
 private:
+    QString fullName;
     QString firstName;
     QString middleName;
     QString lastName;
     QString nickName;
     QString description;
+    QString url;
+    QString organizationName;
+    QString organizationUnit;
+    QString organizationRole;
+    QString jobTitle;
     QDate birthday;
     Avatar photoType;
     QString photoPath;
@@ -438,6 +456,10 @@ static const std::map<QString, std::pair<QString, QString>> icons = {
     {"send", {"document-send", "send"}},
     {"clean", {"edit-clear-all", "clean"}},
     {"user", {"user", "user"}},
+    {"user-properties", {"user-properties", "user-properties"}},
+    {"edit-rename", {"edit-rename", "edit-rename"}},
+    {"group", {"group", "group"}},
+    {"group-new", {"resurce-group-new", "group-new"}},
 };
 
 };
diff --git a/resources/images/fallback/dark/big/edit-rename.svg b/resources/images/fallback/dark/big/edit-rename.svg
new file mode 100644
index 0000000..8075a3b
--- /dev/null
+++ b/resources/images/fallback/dark/big/edit-rename.svg
@@ -0,0 +1,14 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
+  <defs id="defs3051">
+    <style type="text/css" id="current-color-scheme">
+      .ColorScheme-Text {
+        color:#232629;
+      }
+      </style>
+  </defs>
+ <path 
+    style="fill:currentColor;fill-opacity:1;stroke:none" 
+   d="m14.996094 3l-11.992188 11.992188h-.003906v4.00781h1 2 1.00781v-.003906l11.992188-11.992188-.001953-.001953.001953-.001953-4-4-.001953.001953-.001953-.001953m-1.998047 3.412109l2.589844 2.589844-7.587891 7.587891v-1.589844h-1-1v-1-.589844l6.998047-6.998047m-7.998047 7.998047v1.589844h1 1v1 .589844l-.410156.410156h-1.589844l-1-1v-1.589844l1-1" 
+    class="ColorScheme-Text"
+    />
+</svg>
diff --git a/resources/images/fallback/dark/big/group-new.svg b/resources/images/fallback/dark/big/group-new.svg
new file mode 100644
index 0000000..a28270a
--- /dev/null
+++ b/resources/images/fallback/dark/big/group-new.svg
@@ -0,0 +1,14 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
+  <defs id="defs3051">
+    <style type="text/css" id="current-color-scheme">
+      .ColorScheme-Text {
+        color:#232629;
+      }
+      </style>
+  </defs>
+ <path 
+    style="fill:currentColor;fill-opacity:1;stroke:none" 
+    d="M 14 3 C 12.338 3 11 4.338 11 6 C 11 7.662 12.338 9 14 9 C 15.662 9 17 7.662 17 6 C 17 4.338 15.662 3 14 3 z M 8 6 C 6.3380003 6 5 7.3380003 5 9 C 5 10.662 6.3380003 12 8 12 C 9.6619997 12 11 10.662 11 9 C 11 7.3380003 9.6619997 6 8 6 z M 14 10 C 13.353654 10 12.744006 10.134157 12.181641 10.361328 L 12.636719 11.275391 C 13.064535 11.114885 13.514485 11 14 11 C 15.482985 11 16.758385 11.807292 17.449219 13 L 18.580078 13 C 17.810617 11.232833 16.056835 10 14 10 z M 8 13 C 5.2299834 13 3 15.229983 3 18 L 3 19 L 13 19 L 13 18 C 13 15.229983 10.770017 13 8 13 z M 8 14 C 10.216017 14 12 15.783983 12 18 L 4 18 C 4 15.783983 5.7839834 14 8 14 z M 16 14 L 16 16 L 14 16 L 14 17 L 16 17 L 16 19 L 17 19 L 17 17 L 19 17 L 19 16 L 17 16 L 17 14 L 16 14 z "
+    class="ColorScheme-Text"
+    />  
+</svg>
diff --git a/resources/images/fallback/dark/big/group.svg b/resources/images/fallback/dark/big/group.svg
new file mode 100644
index 0000000..0b9c379
--- /dev/null
+++ b/resources/images/fallback/dark/big/group.svg
@@ -0,0 +1,14 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
+  <defs id="defs3051">
+    <style type="text/css" id="current-color-scheme">
+      .ColorScheme-Text {
+        color:#232629;
+      }
+      </style>
+  </defs>
+ <path 
+    style="fill:currentColor;fill-opacity:1;stroke:none" 
+    d="M 14 3 C 12.338 3 11 4.338 11 6 C 11 7.662 12.338 9 14 9 C 15.662 9 17 7.662 17 6 C 17 4.338 15.662 3 14 3 z M 8 6 C 6.338 6 5 7.338 5 9 C 5 10.662 6.338 12 8 12 C 9.662 12 11 10.662 11 9 C 11 7.338 9.662 6 8 6 z M 14 10 C 13.353654 10 12.744006 10.134157 12.181641 10.361328 L 12.636719 11.275391 C 13.064535 11.114885 13.514485 11 14 11 C 16.21602 11 18 12.78398 18 15 L 14.5 15 L 15 16 L 19 16 L 19 15 C 19 12.22998 16.77002 10 14 10 z M 8 13 C 5.229983 13 3 15.229983 3 18 L 3 19 L 13 19 L 13 18 C 13 15.229983 10.770017 13 8 13 z M 8 14 C 10.216017 14 12 15.783983 12 18 L 4 18 C 4 15.783983 5.783983 14 8 14 z "
+	class="ColorScheme-Text"
+    />  
+</svg>
diff --git a/resources/images/fallback/dark/big/user-properties.svg b/resources/images/fallback/dark/big/user-properties.svg
new file mode 100644
index 0000000..afec990
--- /dev/null
+++ b/resources/images/fallback/dark/big/user-properties.svg
@@ -0,0 +1,14 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
+  <defs id="defs3051">
+    <style type="text/css" id="current-color-scheme">
+      .ColorScheme-Text {
+        color:#232629;
+      }
+      </style>
+  </defs>
+ <path 
+    style="fill:currentColor;fill-opacity:1;stroke:none" 
+    d="M 10 3 L 10 4 L 19 4 L 19 3 L 10 3 z M 8 6 C 6.338 6 5 7.338 5 9 C 5 10.662 6.338 12 8 12 C 9.662 12 11 10.662 11 9 C 11 7.338 9.662 6 8 6 z M 12 8 L 12 9 L 19 9 L 19 8 L 12 8 z M 8 13 C 5.229983 13 3 15.229983 3 18 L 3 19 L 13 19 L 13 18 C 13 15.229983 10.770017 13 8 13 z M 13 13 L 13 14 L 19 14 L 19 13 L 13 13 z M 8 14 C 10.216017 14 12 15.783983 12 18 L 4 18 C 4 15.783983 5.783983 14 8 14 z "
+	class="ColorScheme-Text"
+    />  
+</svg>
diff --git a/resources/images/fallback/dark/small/edit-rename.svg b/resources/images/fallback/dark/small/edit-rename.svg
new file mode 100644
index 0000000..18ccc58
--- /dev/null
+++ b/resources/images/fallback/dark/small/edit-rename.svg
@@ -0,0 +1,13 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
+  <defs id="defs3051">
+    <style type="text/css" id="current-color-scheme">
+      .ColorScheme-Text {
+        color:#232629;
+      }
+      </style>
+  </defs>
+ <path style="fill:currentColor;fill-opacity:1;stroke:none" 
+     d="M 10.398438 2 L 5.2871094 7.1113281 L 2 10.398438 L 2 14 L 5.6015625 14 L 14 5.6015625 L 10.398438 2 z M 8.3496094 5.4902344 L 10.509766 7.6503906 L 7.3359375 10.826172 L 7.3359375 10.150391 L 6.3222656 10.171875 L 5.2871094 10.171875 L 5.2871094 9.1367188 L 5.2871094 8.5507812 L 6.7285156 7.1113281 L 8.3496094 5.4902344 z M 4.2734375 9.5644531 L 4.2734375 11.185547 L 5.3085938 11.185547 L 6.3007812 11.185547 L 6.3222656 11.837891 L 5.2421875 12.919922 L 3.8007812 12.919922 L 3.0800781 12.199219 L 3.0800781 10.757812 L 4.2734375 9.5644531 z "
+     class="ColorScheme-Text"
+     />
+</svg>
diff --git a/resources/images/fallback/dark/small/group-new.svg b/resources/images/fallback/dark/small/group-new.svg
new file mode 100644
index 0000000..848af85
--- /dev/null
+++ b/resources/images/fallback/dark/small/group-new.svg
@@ -0,0 +1,13 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
+  <defs id="defs3051">
+    <style type="text/css" id="current-color-scheme">
+      .ColorScheme-Text {
+        color:#232629;
+      }
+      </style>
+  </defs>
+ <path style="fill:currentColor;fill-opacity:1;stroke:none" 
+     d="M 11.5 2 A 2.5 2.5 0 0 0 9 4.5 A 2.5 2.5 0 0 0 11.5 7 A 2.5 2.5 0 0 0 14 4.5 A 2.5 2.5 0 0 0 11.5 2 z M 5.5 4 A 2.5 2.5 0 0 0 3 6.5 A 2.5 2.5 0 0 0 5.5 9 A 2.5 2.5 0 0 0 8 6.5 A 2.5 2.5 0 0 0 5.5 4 z M 11 8.0722656 A 3.4999979 4 0 0 0 8.4921875 10.273438 A 4.5 5 0 0 1 9.0917969 11 L 9.2929688 11 A 2.5 3 0 0 1 11 9.1074219 L 11 8.0722656 z M 12 9 L 12 11 L 10 11 L 10 12 L 12 12 L 12 14 L 13 14 L 13 12 L 15 12 L 15 11 L 13 11 L 13 9 L 12 9 z M 5.5 10 A 3.4999979 4 0 0 0 2 14 L 3 14 L 8 14 L 9 14 A 3.4999979 4 0 0 0 5.5 10 z M 5.5 11 A 2.5 3 0 0 1 7.8535156 13 L 3.1464844 13 A 2.5 3 0 0 1 5.5 11 z "
+     class="ColorScheme-Text"
+     />
+</svg>
diff --git a/resources/images/fallback/dark/small/group.svg b/resources/images/fallback/dark/small/group.svg
new file mode 100644
index 0000000..7ca4c26
--- /dev/null
+++ b/resources/images/fallback/dark/small/group.svg
@@ -0,0 +1,13 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
+  <defs id="defs3051">
+    <style type="text/css" id="current-color-scheme">
+      .ColorScheme-Text {
+        color:#232629;
+      }
+      </style>
+  </defs>
+ <path style="fill:currentColor;fill-opacity:1;stroke:none" 
+     d="M 11.5 2 A 2.5 2.5 0 0 0 9 4.5 A 2.5 2.5 0 0 0 11.5 7 A 2.5 2.5 0 0 0 14 4.5 A 2.5 2.5 0 0 0 11.5 2 z M 5.5 4 A 2.5 2.5 0 0 0 3 6.5 A 2.5 2.5 0 0 0 5.5 9 A 2.5 2.5 0 0 0 8 6.5 A 2.5 2.5 0 0 0 5.5 4 z M 11.646484 8 A 3.4999979 4 0 0 0 8.4921875 10.273438 A 4.5 5 0 0 1 9.6171875 12 L 14.146484 12 L 15.146484 12 A 3.4999979 4 0 0 0 11.646484 8 z M 11.646484 9 A 2.5 3 0 0 1 14 11 L 9.2929688 11 A 2.5 3 0 0 1 11.646484 9 z M 5.5 10 A 3.4999979 4 0 0 0 2 14 L 3 14 L 8 14 L 9 14 A 3.4999979 4 0 0 0 5.5 10 z M 5.5 11 A 2.5 3 0 0 1 7.8535156 13 L 3.1464844 13 A 2.5 3 0 0 1 5.5 11 z "
+     class="ColorScheme-Text"
+     />
+</svg>
diff --git a/resources/images/fallback/dark/small/user-properties.svg b/resources/images/fallback/dark/small/user-properties.svg
new file mode 100644
index 0000000..fa4c9e0
--- /dev/null
+++ b/resources/images/fallback/dark/small/user-properties.svg
@@ -0,0 +1,13 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
+  <defs id="defs3051">
+    <style type="text/css" id="current-color-scheme">
+      .ColorScheme-Text {
+        color:#232629;
+      }
+      </style>
+  </defs>
+ <path 
+     style="fill:currentColor;fill-opacity:1;stroke:none" 
+     d="M 9 2 L 9 3 L 14 3 L 14 2 L 9 2 z M 5.5 4 A 2.5 2.5 0 0 0 3 6.5 A 2.5 2.5 0 0 0 5.5 9 A 2.5 2.5 0 0 0 8 6.5 A 2.5 2.5 0 0 0 5.5 4 z M 9 5 L 9 6 L 14 6 L 14 5 L 9 5 z M 9 8 L 9 9 L 14 9 L 14 8 L 9 8 z M 5.5 10 A 3.499998 4 0 0 0 2 14 L 9 14 A 3.499998 4 0 0 0 5.5 10 z M 5.5 11 A 2.5 3 0 0 1 7.8535156 13 L 3.1464844 13 A 2.5 3 0 0 1 5.5 11 z "
+     class="ColorScheme-Text"/>
+</svg>
diff --git a/resources/images/fallback/light/big/edit-rename.svg b/resources/images/fallback/light/big/edit-rename.svg
new file mode 100644
index 0000000..0c3d22c
--- /dev/null
+++ b/resources/images/fallback/light/big/edit-rename.svg
@@ -0,0 +1,14 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
+  <defs id="defs3051">
+    <style type="text/css" id="current-color-scheme">
+      .ColorScheme-Text {
+        color:#eff0f1;
+      }
+      </style>
+  </defs>
+ <path 
+    style="fill:currentColor;fill-opacity:1;stroke:none" 
+   d="m14.996094 3l-11.992188 11.992188h-.003906v4.00781h1 2 1.00781v-.003906l11.992188-11.992188-.001953-.001953.001953-.001953-4-4-.001953.001953-.001953-.001953m-1.998047 3.412109l2.589844 2.589844-7.587891 7.587891v-1.589844h-1-1v-1-.589844l6.998047-6.998047m-7.998047 7.998047v1.589844h1 1v1 .589844l-.410156.410156h-1.589844l-1-1v-1.589844l1-1" 
+    class="ColorScheme-Text"
+    />
+</svg>
diff --git a/resources/images/fallback/light/big/group-new.svg b/resources/images/fallback/light/big/group-new.svg
new file mode 100644
index 0000000..9c8b823
--- /dev/null
+++ b/resources/images/fallback/light/big/group-new.svg
@@ -0,0 +1,14 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
+  <defs id="defs3051">
+    <style type="text/css" id="current-color-scheme">
+      .ColorScheme-Text {
+        color:#eff0f1;
+      }
+      </style>
+  </defs>
+ <path 
+    style="fill:currentColor;fill-opacity:1;stroke:none" 
+    d="M 14 3 C 12.338 3 11 4.338 11 6 C 11 7.662 12.338 9 14 9 C 15.662 9 17 7.662 17 6 C 17 4.338 15.662 3 14 3 z M 8 6 C 6.3380003 6 5 7.3380003 5 9 C 5 10.662 6.3380003 12 8 12 C 9.6619997 12 11 10.662 11 9 C 11 7.3380003 9.6619997 6 8 6 z M 14 10 C 13.353654 10 12.744006 10.134157 12.181641 10.361328 L 12.636719 11.275391 C 13.064535 11.114885 13.514485 11 14 11 C 15.482985 11 16.758385 11.807292 17.449219 13 L 18.580078 13 C 17.810617 11.232833 16.056835 10 14 10 z M 8 13 C 5.2299834 13 3 15.229983 3 18 L 3 19 L 13 19 L 13 18 C 13 15.229983 10.770017 13 8 13 z M 8 14 C 10.216017 14 12 15.783983 12 18 L 4 18 C 4 15.783983 5.7839834 14 8 14 z M 16 14 L 16 16 L 14 16 L 14 17 L 16 17 L 16 19 L 17 19 L 17 17 L 19 17 L 19 16 L 17 16 L 17 14 L 16 14 z "
+    class="ColorScheme-Text"
+    />  
+</svg>
diff --git a/resources/images/fallback/light/big/group.svg b/resources/images/fallback/light/big/group.svg
new file mode 100644
index 0000000..ef4758b
--- /dev/null
+++ b/resources/images/fallback/light/big/group.svg
@@ -0,0 +1,14 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
+  <defs id="defs3051">
+    <style type="text/css" id="current-color-scheme">
+      .ColorScheme-Text {
+        color:#eff0f1;
+      }
+      </style>
+  </defs>
+ <path 
+    style="fill:currentColor;fill-opacity:1;stroke:none" 
+    d="M 14 3 C 12.338 3 11 4.338 11 6 C 11 7.662 12.338 9 14 9 C 15.662 9 17 7.662 17 6 C 17 4.338 15.662 3 14 3 z M 8 6 C 6.338 6 5 7.338 5 9 C 5 10.662 6.338 12 8 12 C 9.662 12 11 10.662 11 9 C 11 7.338 9.662 6 8 6 z M 14 10 C 13.353654 10 12.744006 10.134157 12.181641 10.361328 L 12.636719 11.275391 C 13.064535 11.114885 13.514485 11 14 11 C 16.21602 11 18 12.78398 18 15 L 14.5 15 L 15 16 L 19 16 L 19 15 C 19 12.22998 16.77002 10 14 10 z M 8 13 C 5.229983 13 3 15.229983 3 18 L 3 19 L 13 19 L 13 18 C 13 15.229983 10.770017 13 8 13 z M 8 14 C 10.216017 14 12 15.783983 12 18 L 4 18 C 4 15.783983 5.783983 14 8 14 z "
+	class="ColorScheme-Text"
+    />  
+</svg>
diff --git a/resources/images/fallback/light/big/user-properties.svg b/resources/images/fallback/light/big/user-properties.svg
new file mode 100644
index 0000000..9a14b93
--- /dev/null
+++ b/resources/images/fallback/light/big/user-properties.svg
@@ -0,0 +1,14 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
+  <defs id="defs3051">
+    <style type="text/css" id="current-color-scheme">
+      .ColorScheme-Text {
+        color:#eff0f1;
+      }
+      </style>
+  </defs>
+ <path 
+    style="fill:currentColor;fill-opacity:1;stroke:none" 
+    d="M 10 3 L 10 4 L 19 4 L 19 3 L 10 3 z M 8 6 C 6.338 6 5 7.338 5 9 C 5 10.662 6.338 12 8 12 C 9.662 12 11 10.662 11 9 C 11 7.338 9.662 6 8 6 z M 12 8 L 12 9 L 19 9 L 19 8 L 12 8 z M 8 13 C 5.229983 13 3 15.229983 3 18 L 3 19 L 13 19 L 13 18 C 13 15.229983 10.770017 13 8 13 z M 13 13 L 13 14 L 19 14 L 19 13 L 13 13 z M 8 14 C 10.216017 14 12 15.783983 12 18 L 4 18 C 4 15.783983 5.783983 14 8 14 z "
+	class="ColorScheme-Text"
+    />  
+</svg>
diff --git a/resources/images/fallback/light/small/edit-rename.svg b/resources/images/fallback/light/small/edit-rename.svg
new file mode 100644
index 0000000..6a84496
--- /dev/null
+++ b/resources/images/fallback/light/small/edit-rename.svg
@@ -0,0 +1,13 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
+  <defs id="defs3051">
+    <style type="text/css" id="current-color-scheme">
+      .ColorScheme-Text {
+        color:#eff0f1;
+      }
+      </style>
+  </defs>
+ <path style="fill:currentColor;fill-opacity:1;stroke:none" 
+     d="M 10.398438 2 L 5.2871094 7.1113281 L 2 10.398438 L 2 14 L 5.6015625 14 L 14 5.6015625 L 10.398438 2 z M 8.3496094 5.4902344 L 10.509766 7.6503906 L 7.3359375 10.826172 L 7.3359375 10.150391 L 6.3222656 10.171875 L 5.2871094 10.171875 L 5.2871094 9.1367188 L 5.2871094 8.5507812 L 6.7285156 7.1113281 L 8.3496094 5.4902344 z M 4.2734375 9.5644531 L 4.2734375 11.185547 L 5.3085938 11.185547 L 6.3007812 11.185547 L 6.3222656 11.837891 L 5.2421875 12.919922 L 3.8007812 12.919922 L 3.0800781 12.199219 L 3.0800781 10.757812 L 4.2734375 9.5644531 z "
+     class="ColorScheme-Text"
+     />
+</svg>
diff --git a/resources/images/fallback/light/small/group-new.svg b/resources/images/fallback/light/small/group-new.svg
new file mode 100644
index 0000000..43b465a
--- /dev/null
+++ b/resources/images/fallback/light/small/group-new.svg
@@ -0,0 +1,13 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
+  <defs id="defs3051">
+    <style type="text/css" id="current-color-scheme">
+      .ColorScheme-Text {
+        color:#eff0f1;
+      }
+      </style>
+  </defs>
+ <path style="fill:currentColor;fill-opacity:1;stroke:none" 
+     d="M 11.5 2 A 2.5 2.5 0 0 0 9 4.5 A 2.5 2.5 0 0 0 11.5 7 A 2.5 2.5 0 0 0 14 4.5 A 2.5 2.5 0 0 0 11.5 2 z M 5.5 4 A 2.5 2.5 0 0 0 3 6.5 A 2.5 2.5 0 0 0 5.5 9 A 2.5 2.5 0 0 0 8 6.5 A 2.5 2.5 0 0 0 5.5 4 z M 11 8.0722656 A 3.4999979 4 0 0 0 8.4921875 10.273438 A 4.5 5 0 0 1 9.0917969 11 L 9.2929688 11 A 2.5 3 0 0 1 11 9.1074219 L 11 8.0722656 z M 12 9 L 12 11 L 10 11 L 10 12 L 12 12 L 12 14 L 13 14 L 13 12 L 15 12 L 15 11 L 13 11 L 13 9 L 12 9 z M 5.5 10 A 3.4999979 4 0 0 0 2 14 L 3 14 L 8 14 L 9 14 A 3.4999979 4 0 0 0 5.5 10 z M 5.5 11 A 2.5 3 0 0 1 7.8535156 13 L 3.1464844 13 A 2.5 3 0 0 1 5.5 11 z "
+     class="ColorScheme-Text"
+     />
+</svg>
diff --git a/resources/images/fallback/light/small/group.svg b/resources/images/fallback/light/small/group.svg
new file mode 100644
index 0000000..dfefc94
--- /dev/null
+++ b/resources/images/fallback/light/small/group.svg
@@ -0,0 +1,13 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
+  <defs id="defs3051">
+    <style type="text/css" id="current-color-scheme">
+      .ColorScheme-Text {
+        color:#eff0f1;
+      }
+      </style>
+  </defs>
+ <path style="fill:currentColor;fill-opacity:1;stroke:none" 
+     d="M 11.5 2 A 2.5 2.5 0 0 0 9 4.5 A 2.5 2.5 0 0 0 11.5 7 A 2.5 2.5 0 0 0 14 4.5 A 2.5 2.5 0 0 0 11.5 2 z M 5.5 4 A 2.5 2.5 0 0 0 3 6.5 A 2.5 2.5 0 0 0 5.5 9 A 2.5 2.5 0 0 0 8 6.5 A 2.5 2.5 0 0 0 5.5 4 z M 11.646484 8 A 3.4999979 4 0 0 0 8.4921875 10.273438 A 4.5 5 0 0 1 9.6171875 12 L 14.146484 12 L 15.146484 12 A 3.4999979 4 0 0 0 11.646484 8 z M 11.646484 9 A 2.5 3 0 0 1 14 11 L 9.2929688 11 A 2.5 3 0 0 1 11.646484 9 z M 5.5 10 A 3.4999979 4 0 0 0 2 14 L 3 14 L 8 14 L 9 14 A 3.4999979 4 0 0 0 5.5 10 z M 5.5 11 A 2.5 3 0 0 1 7.8535156 13 L 3.1464844 13 A 2.5 3 0 0 1 5.5 11 z "
+     class="ColorScheme-Text"
+     />
+</svg>
diff --git a/resources/images/fallback/light/small/user-properties.svg b/resources/images/fallback/light/small/user-properties.svg
new file mode 100644
index 0000000..2a0bebd
--- /dev/null
+++ b/resources/images/fallback/light/small/user-properties.svg
@@ -0,0 +1,13 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
+  <defs id="defs3051">
+    <style type="text/css" id="current-color-scheme">
+      .ColorScheme-Text {
+        color:#eff0f1;
+      }
+      </style>
+  </defs>
+ <path 
+     style="fill:currentColor;fill-opacity:1;stroke:none" 
+     d="M 9 2 L 9 3 L 14 3 L 14 2 L 9 2 z M 5.5 4 A 2.5 2.5 0 0 0 3 6.5 A 2.5 2.5 0 0 0 5.5 9 A 2.5 2.5 0 0 0 8 6.5 A 2.5 2.5 0 0 0 5.5 4 z M 9 5 L 9 6 L 14 6 L 14 5 L 9 5 z M 9 8 L 9 9 L 14 9 L 14 8 L 9 8 z M 5.5 10 A 3.499998 4 0 0 0 2 14 L 9 14 A 3.499998 4 0 0 0 5.5 10 z M 5.5 11 A 2.5 3 0 0 1 7.8535156 13 L 3.1464844 13 A 2.5 3 0 0 1 5.5 11 z "
+     class="ColorScheme-Text"/>
+</svg>
diff --git a/resources/resources.qrc b/resources/resources.qrc
index 244db04..3cfaa84 100644
--- a/resources/resources.qrc
+++ b/resources/resources.qrc
@@ -32,6 +32,10 @@
     <file>images/fallback/dark/big/clean.svg</file>
     <file>images/fallback/dark/big/send.svg</file>
     <file>images/fallback/dark/big/mail-attachment.svg</file>
+    <file>images/fallback/dark/big/group.svg</file>
+    <file>images/fallback/dark/big/group-new.svg</file>
+    <file>images/fallback/dark/big/edit-rename.svg</file>
+    <file>images/fallback/dark/big/user-properties.svg</file>
     
     
     <file>images/fallback/dark/small/absent.svg</file>
@@ -64,6 +68,10 @@
     <file>images/fallback/dark/small/clean.svg</file>
     <file>images/fallback/dark/small/send.svg</file>
     <file>images/fallback/dark/small/mail-attachment.svg</file>
+    <file>images/fallback/dark/small/group.svg</file>
+    <file>images/fallback/dark/small/group-new.svg</file>
+    <file>images/fallback/dark/small/edit-rename.svg</file>
+    <file>images/fallback/dark/small/user-properties.svg</file>
     
     
     <file>images/fallback/light/big/absent.svg</file>
@@ -96,6 +104,10 @@
     <file>images/fallback/light/big/clean.svg</file>
     <file>images/fallback/light/big/send.svg</file>
     <file>images/fallback/light/big/mail-attachment.svg</file>
+    <file>images/fallback/light/big/group.svg</file>
+    <file>images/fallback/light/big/group-new.svg</file>
+    <file>images/fallback/light/big/edit-rename.svg</file>
+    <file>images/fallback/light/big/user-properties.svg</file>
     
     
     <file>images/fallback/light/small/absent.svg</file>
@@ -128,5 +140,9 @@
     <file>images/fallback/light/small/clean.svg</file>
     <file>images/fallback/light/small/send.svg</file>
     <file>images/fallback/light/small/mail-attachment.svg</file>
+    <file>images/fallback/light/small/group.svg</file>
+    <file>images/fallback/light/small/group-new.svg</file>
+    <file>images/fallback/light/small/edit-rename.svg</file>
+    <file>images/fallback/light/small/user-properties.svg</file>
 </qresource>
 </RCC>
diff --git a/ui/models/roster.cpp b/ui/models/roster.cpp
index 6e49104..e124db7 100644
--- a/ui/models/roster.cpp
+++ b/ui/models/roster.cpp
@@ -934,3 +934,23 @@ bool Models::Roster::groupHasContact(const QString& account, const QString& grou
         return gr->hasContact(contact);
     }
 }
+
+QString Models::Roster::getContactIconPath(const QString& account, const QString& jid)
+{
+    ElId id(account, jid);
+    std::multimap<ElId, Contact*>::const_iterator cItr = contacts.find(id);
+    QString path = "";
+    if (cItr == contacts.end()) {
+        std::map<ElId, Room*>::const_iterator rItr = rooms.find(id);
+        if (rItr == rooms.end()) {
+            qDebug() << "An attempt to get an icon path of non existing contact" << account << ":" << jid << ", returning empty value";
+        } else {
+            //path = rItr->second->getRoomName();
+        }
+    } else {
+        if (cItr->second->getAvatarState() != Shared::Avatar::empty) {
+            path = cItr->second->getAvatarPath();
+        }
+    }
+    return path;
+}
diff --git a/ui/models/roster.h b/ui/models/roster.h
index 30fb884..40c978d 100644
--- a/ui/models/roster.h
+++ b/ui/models/roster.h
@@ -73,6 +73,7 @@ public:
     
     std::deque<QString> groupList(const QString& account) const;
     bool groupHasContact(const QString& account, const QString& group, const QString& contactJID) const;
+    QString getContactIconPath(const QString& account, const QString& jid);
     
     Accounts* accountsModel;
     
diff --git a/ui/squawk.cpp b/ui/squawk.cpp
index b5cb84c..b228ac8 100644
--- a/ui/squawk.cpp
+++ b/ui/squawk.cpp
@@ -464,11 +464,16 @@ void Squawk::accountMessage(const QString& account, const Shared::Message& data)
 
 void Squawk::notify(const QString& account, const Shared::Message& msg)
 {
-    QString name = QString(rosterModel.getContactName(account, msg.getPenPalJid()));;
+    QString name = QString(rosterModel.getContactName(account, msg.getPenPalJid()));
+    QString path = QString(rosterModel.getContactIconPath(account, msg.getPenPalJid()));
     QVariantList args;
     args << QString(QCoreApplication::applicationName());
     args << QVariant(QVariant::UInt);   //TODO some normal id
-    args << QString("mail-message");    //TODO icon
+    if (path.size() > 0) {
+        args << path;
+    } else {
+        args << QString("mail-message");
+    }
     if (msg.getType() == Shared::Message::groupChat) {
         args << msg.getFromResource() + " from " + name;
     } else {
@@ -635,7 +640,7 @@ void Squawk::onRosterContextMenu(const QPoint& point)
                         }
                     });
                 }
-                QAction* newGroup = groupsMenu->addAction(Shared::icon("resource-group-new"), tr("New group"));
+                QAction* newGroup = groupsMenu->addAction(Shared::icon("group-new"), tr("New group"));
                 newGroup->setEnabled(active);
                 connect(newGroup, &QAction::triggered, [this, accName, cntJID]() {
                     QInputDialog* dialog = new QInputDialog(this);
diff --git a/ui/widgets/vcard.cpp b/ui/widgets/vcard.cpp
index 1da6c2e..c20b4bf 100644
--- a/ui/widgets/vcard.cpp
+++ b/ui/widgets/vcard.cpp
@@ -52,6 +52,7 @@ VCard::VCard(const QString& jid, bool edit, QWidget* parent):
         avatarMenu->addAction(clearAvatar);
     } else {
         m_ui->buttonBox->hide();
+        m_ui->fullName->setReadOnly(true);
         m_ui->firstName->setReadOnly(true);
         m_ui->middleName->setReadOnly(true);
         m_ui->lastName->setReadOnly(true);
@@ -89,16 +90,18 @@ void VCard::setVCard(const QString& jid, const Shared::VCard& card)
 
 void VCard::setVCard(const Shared::VCard& card)
 {
+    m_ui->fullName->setText(card.getFullName());
     m_ui->firstName->setText(card.getFirstName());
     m_ui->middleName->setText(card.getMiddleName());
     m_ui->lastName->setText(card.getLastName());
     m_ui->nickName->setText(card.getNickName());
     m_ui->birthday->setDate(card.getBirthday());
-    //m_ui->organizationName->setText(card.get());
-    //m_ui->organizationDepartment->setText(card.get());
-    //m_ui->organizationTitle->setText(card.get());
-    //m_ui->organizationRole->setText(card.get());
+    m_ui->organizationName->setText(card.getOrgName());
+    m_ui->organizationDepartment->setText(card.getOrgUnit());
+    m_ui->organizationTitle->setText(card.getOrgTitle());
+    m_ui->organizationRole->setText(card.getOrgRole());
     m_ui->description->setText(card.getDescription());
+    m_ui->url->setText(card.getUrl());
     currentAvatarType = card.getAvatarType();
     currentAvatarPath = card.getAvatarPath();
     
@@ -113,12 +116,18 @@ QString VCard::getJid() const
 void VCard::onButtonBoxAccepted()
 {
     Shared::VCard card;
+    card.setFullName(m_ui->fullName->text());
     card.setFirstName(m_ui->firstName->text());
     card.setMiddleName(m_ui->middleName->text());
     card.setLastName(m_ui->lastName->text());
     card.setNickName(m_ui->nickName->text());
     card.setBirthday(m_ui->birthday->date());
     card.setDescription(m_ui->description->toPlainText());
+    card.setUrl(m_ui->url->text());
+    card.setOrgName(m_ui->organizationName->text());
+    card.setOrgUnit(m_ui->organizationDepartment->text());
+    card.setOrgRole(m_ui->organizationRole->text());
+    card.setOrgTitle(m_ui->organizationTitle->text());
     card.setAvatarPath(currentAvatarPath);
     card.setAvatarType(currentAvatarType);
     
diff --git a/ui/widgets/vcard.ui b/ui/widgets/vcard.ui
index 406ca9a..9eabf0b 100644
--- a/ui/widgets/vcard.ui
+++ b/ui/widgets/vcard.ui
@@ -6,8 +6,8 @@
    <rect>
     <x>0</x>
     <y>0</y>
-    <width>537</width>
-    <height>539</height>
+    <width>594</width>
+    <height>595</height>
    </rect>
   </property>
   <layout class="QVBoxLayout" name="verticalLayout_2">
@@ -94,14 +94,177 @@
          </property>
         </widget>
        </item>
-       <item row="3" column="1" colspan="2">
+       <item row="4" column="1" colspan="2">
         <widget class="Line" name="personalLine">
          <property name="orientation">
           <enum>Qt::Horizontal</enum>
          </property>
         </widget>
        </item>
-       <item row="2" column="1">
+       <item row="3" column="0" rowspan="7">
+        <spacer name="generalLeftHSpacer">
+         <property name="orientation">
+          <enum>Qt::Horizontal</enum>
+         </property>
+         <property name="sizeHint" stdset="0">
+          <size>
+           <width>40</width>
+           <height>20</height>
+          </size>
+         </property>
+        </spacer>
+       </item>
+       <item row="0" column="0" colspan="4">
+        <widget class="QLabel" name="generalHeading">
+         <property name="text">
+          <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:24pt; font-weight:600;&quot;&gt;General&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+         </property>
+        </widget>
+       </item>
+       <item row="3" column="3" rowspan="7">
+        <spacer name="generalRightHSpacer">
+         <property name="orientation">
+          <enum>Qt::Horizontal</enum>
+         </property>
+         <property name="sizeHint" stdset="0">
+          <size>
+           <width>40</width>
+           <height>20</height>
+          </size>
+         </property>
+        </spacer>
+       </item>
+       <item row="6" column="1" colspan="2">
+        <widget class="QLabel" name="organizationHeading">
+         <property name="sizePolicy">
+          <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+           <horstretch>0</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
+         <property name="styleSheet">
+          <string notr="true"/>
+         </property>
+         <property name="text">
+          <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:16pt; font-weight:600;&quot;&gt;Organization&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+         </property>
+         <property name="alignment">
+          <set>Qt::AlignCenter</set>
+         </property>
+        </widget>
+       </item>
+       <item row="7" column="1" colspan="2">
+        <layout class="QFormLayout" name="organizationForm">
+         <property name="formAlignment">
+          <set>Qt::AlignHCenter|Qt::AlignTop</set>
+         </property>
+         <item row="0" column="0">
+          <widget class="QLabel" name="organizationNameLabel">
+           <property name="text">
+            <string>Organization name</string>
+           </property>
+           <property name="buddy">
+            <cstring>organizationName</cstring>
+           </property>
+          </widget>
+         </item>
+         <item row="0" column="1">
+          <widget class="QLineEdit" name="organizationName">
+           <property name="minimumSize">
+            <size>
+             <width>200</width>
+             <height>0</height>
+            </size>
+           </property>
+           <property name="maximumSize">
+            <size>
+             <width>350</width>
+             <height>16777215</height>
+            </size>
+           </property>
+          </widget>
+         </item>
+         <item row="1" column="0">
+          <widget class="QLabel" name="organizationDepartmentLabel">
+           <property name="text">
+            <string>Unit / Department</string>
+           </property>
+           <property name="buddy">
+            <cstring>organizationDepartment</cstring>
+           </property>
+          </widget>
+         </item>
+         <item row="1" column="1">
+          <widget class="QLineEdit" name="organizationDepartment">
+           <property name="minimumSize">
+            <size>
+             <width>200</width>
+             <height>0</height>
+            </size>
+           </property>
+           <property name="maximumSize">
+            <size>
+             <width>350</width>
+             <height>16777215</height>
+            </size>
+           </property>
+          </widget>
+         </item>
+         <item row="2" column="0">
+          <widget class="QLabel" name="roleLabel">
+           <property name="text">
+            <string>Role / Profession</string>
+           </property>
+           <property name="buddy">
+            <cstring>organizationRole</cstring>
+           </property>
+          </widget>
+         </item>
+         <item row="2" column="1">
+          <widget class="QLineEdit" name="organizationRole">
+           <property name="minimumSize">
+            <size>
+             <width>200</width>
+             <height>0</height>
+            </size>
+           </property>
+           <property name="maximumSize">
+            <size>
+             <width>350</width>
+             <height>16777215</height>
+            </size>
+           </property>
+          </widget>
+         </item>
+         <item row="3" column="0">
+          <widget class="QLabel" name="organizationTitleLabel">
+           <property name="text">
+            <string>Job title</string>
+           </property>
+           <property name="buddy">
+            <cstring>organizationTitle</cstring>
+           </property>
+          </widget>
+         </item>
+         <item row="3" column="1">
+          <widget class="QLineEdit" name="organizationTitle">
+           <property name="minimumSize">
+            <size>
+             <width>200</width>
+             <height>0</height>
+            </size>
+           </property>
+           <property name="maximumSize">
+            <size>
+             <width>350</width>
+             <height>16777215</height>
+            </size>
+           </property>
+          </widget>
+         </item>
+        </layout>
+       </item>
+       <item row="3" column="1">
         <layout class="QFormLayout" name="personalForm">
          <property name="sizeConstraint">
           <enum>QLayout::SetDefaultConstraint</enum>
@@ -228,190 +391,7 @@
          </item>
         </layout>
        </item>
-       <item row="2" column="3" rowspan="7">
-        <spacer name="generalRightHSpacer">
-         <property name="orientation">
-          <enum>Qt::Horizontal</enum>
-         </property>
-         <property name="sizeHint" stdset="0">
-          <size>
-           <width>40</width>
-           <height>20</height>
-          </size>
-         </property>
-        </spacer>
-       </item>
-       <item row="8" column="1" colspan="2">
-        <spacer name="verticalSpacer">
-         <property name="orientation">
-          <enum>Qt::Vertical</enum>
-         </property>
-         <property name="sizeHint" stdset="0">
-          <size>
-           <width>20</width>
-           <height>40</height>
-          </size>
-         </property>
-        </spacer>
-       </item>
-       <item row="2" column="0" rowspan="7">
-        <spacer name="generalLeftHSpacer">
-         <property name="orientation">
-          <enum>Qt::Horizontal</enum>
-         </property>
-         <property name="sizeHint" stdset="0">
-          <size>
-           <width>40</width>
-           <height>20</height>
-          </size>
-         </property>
-        </spacer>
-       </item>
-       <item row="5" column="1" colspan="2">
-        <widget class="QLabel" name="organizationHeading">
-         <property name="sizePolicy">
-          <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
-           <horstretch>0</horstretch>
-           <verstretch>0</verstretch>
-          </sizepolicy>
-         </property>
-         <property name="styleSheet">
-          <string notr="true"/>
-         </property>
-         <property name="text">
-          <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:16pt; font-weight:600;&quot;&gt;Organization&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
-         </property>
-         <property name="alignment">
-          <set>Qt::AlignCenter</set>
-         </property>
-        </widget>
-       </item>
-       <item row="6" column="1" colspan="2">
-        <layout class="QFormLayout" name="organizationForm">
-         <property name="formAlignment">
-          <set>Qt::AlignHCenter|Qt::AlignTop</set>
-         </property>
-         <item row="0" column="0">
-          <widget class="QLabel" name="organizationNameLabel">
-           <property name="text">
-            <string>Organization name</string>
-           </property>
-           <property name="buddy">
-            <cstring>organizationName</cstring>
-           </property>
-          </widget>
-         </item>
-         <item row="0" column="1">
-          <widget class="QLineEdit" name="organizationName">
-           <property name="minimumSize">
-            <size>
-             <width>200</width>
-             <height>0</height>
-            </size>
-           </property>
-           <property name="maximumSize">
-            <size>
-             <width>350</width>
-             <height>16777215</height>
-            </size>
-           </property>
-          </widget>
-         </item>
-         <item row="1" column="0">
-          <widget class="QLabel" name="organizationDepartmentLabel">
-           <property name="text">
-            <string>Unit / Department</string>
-           </property>
-           <property name="buddy">
-            <cstring>organizationDepartment</cstring>
-           </property>
-          </widget>
-         </item>
-         <item row="1" column="1">
-          <widget class="QLineEdit" name="organizationDepartment">
-           <property name="minimumSize">
-            <size>
-             <width>200</width>
-             <height>0</height>
-            </size>
-           </property>
-           <property name="maximumSize">
-            <size>
-             <width>350</width>
-             <height>16777215</height>
-            </size>
-           </property>
-          </widget>
-         </item>
-         <item row="2" column="0">
-          <widget class="QLabel" name="roleLabel">
-           <property name="text">
-            <string>Role / Profession</string>
-           </property>
-           <property name="buddy">
-            <cstring>organizationRole</cstring>
-           </property>
-          </widget>
-         </item>
-         <item row="2" column="1">
-          <widget class="QLineEdit" name="organizationRole">
-           <property name="minimumSize">
-            <size>
-             <width>200</width>
-             <height>0</height>
-            </size>
-           </property>
-           <property name="maximumSize">
-            <size>
-             <width>350</width>
-             <height>16777215</height>
-            </size>
-           </property>
-          </widget>
-         </item>
-         <item row="3" column="0">
-          <widget class="QLabel" name="organizationTitleLabel">
-           <property name="text">
-            <string>Job title</string>
-           </property>
-           <property name="buddy">
-            <cstring>organizationTitle</cstring>
-           </property>
-          </widget>
-         </item>
-         <item row="3" column="1">
-          <widget class="QLineEdit" name="organizationTitle">
-           <property name="minimumSize">
-            <size>
-             <width>200</width>
-             <height>0</height>
-            </size>
-           </property>
-           <property name="maximumSize">
-            <size>
-             <width>350</width>
-             <height>16777215</height>
-            </size>
-           </property>
-          </widget>
-         </item>
-        </layout>
-       </item>
-       <item row="7" column="1" colspan="2">
-        <widget class="Line" name="organizationLine">
-         <property name="orientation">
-          <enum>Qt::Horizontal</enum>
-         </property>
-        </widget>
-       </item>
-       <item row="0" column="0" colspan="4">
-        <widget class="QLabel" name="generalHeading">
-         <property name="text">
-          <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:24pt; font-weight:600;&quot;&gt;General&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
-         </property>
-        </widget>
-       </item>
-       <item row="2" column="2">
+       <item row="3" column="2">
         <widget class="QToolButton" name="avatarButton">
          <property name="sizePolicy">
           <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
@@ -429,7 +409,8 @@
           <string/>
          </property>
          <property name="icon">
-          <iconset theme="user"/>
+          <iconset theme="user">
+           <normaloff>.</normaloff>.</iconset>
          </property>
          <property name="iconSize">
           <size>
@@ -448,6 +429,43 @@
          </property>
         </widget>
        </item>
+       <item row="8" column="1" colspan="2">
+        <widget class="Line" name="organizationLine">
+         <property name="orientation">
+          <enum>Qt::Horizontal</enum>
+         </property>
+        </widget>
+       </item>
+       <item row="2" column="1" colspan="2">
+        <layout class="QFormLayout" name="commonForm">
+         <item row="0" column="1">
+          <widget class="QLineEdit" name="fullName"/>
+         </item>
+         <item row="0" column="0">
+          <widget class="QLabel" name="fullNameLabel">
+           <property name="text">
+            <string>Full name</string>
+           </property>
+           <property name="buddy">
+            <cstring>fullName</cstring>
+           </property>
+          </widget>
+         </item>
+        </layout>
+       </item>
+       <item row="9" column="1" colspan="2">
+        <spacer name="verticalSpacer">
+         <property name="orientation">
+          <enum>Qt::Vertical</enum>
+         </property>
+         <property name="sizeHint" stdset="0">
+          <size>
+           <width>20</width>
+           <height>40</height>
+          </size>
+         </property>
+        </spacer>
+       </item>
       </layout>
      </widget>
      <widget class="QWidget" name="Contact">
@@ -496,8 +514,8 @@
            <rect>
             <x>0</x>
             <y>0</y>
-            <width>525</width>
-            <height>415</height>
+            <width>582</width>
+            <height>471</height>
            </rect>
           </property>
           <layout class="QGridLayout" name="gridLayout_3" columnstretch="1,4,1">
@@ -584,6 +602,9 @@
                <property name="text">
                 <string>Web site</string>
                </property>
+               <property name="buddy">
+                <cstring>url</cstring>
+               </property>
               </widget>
              </item>
             </layout>
@@ -756,7 +777,8 @@
   </layout>
   <action name="actionSetAvatar">
    <property name="icon">
-    <iconset theme="photo"/>
+    <iconset theme="photo">
+     <normaloff>.</normaloff>.</iconset>
    </property>
    <property name="text">
     <string>Set avatar</string>
@@ -764,7 +786,8 @@
   </action>
   <action name="actionClearAvatar">
    <property name="icon">
-    <iconset theme="edit-clear-all"/>
+    <iconset theme="edit-clear-all">
+     <normaloff>.</normaloff>.</iconset>
    </property>
    <property name="text">
     <string>Clear avatar</string>
@@ -772,19 +795,22 @@
   </action>
  </widget>
  <tabstops>
+  <tabstop>fullName</tabstop>
   <tabstop>firstName</tabstop>
   <tabstop>middleName</tabstop>
   <tabstop>lastName</tabstop>
   <tabstop>nickName</tabstop>
   <tabstop>birthday</tabstop>
+  <tabstop>avatarButton</tabstop>
   <tabstop>organizationName</tabstop>
   <tabstop>organizationDepartment</tabstop>
   <tabstop>organizationRole</tabstop>
   <tabstop>organizationTitle</tabstop>
-  <tabstop>scrollArea</tabstop>
-  <tabstop>jabberID</tabstop>
-  <tabstop>description</tabstop>
   <tabstop>tabWidget</tabstop>
+  <tabstop>jabberID</tabstop>
+  <tabstop>url</tabstop>
+  <tabstop>description</tabstop>
+  <tabstop>scrollArea</tabstop>
  </tabstops>
  <resources>
   <include location="../../resources/resources.qrc"/>

From f3515f1104285a5f551b5f1357d573187da8ae38 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Wed, 30 Oct 2019 16:47:21 +0300
Subject: [PATCH 014/281] refactored progress

---
 ui/CMakeLists.txt        |  1 +
 ui/utils/messageline.cpp | 41 +++-----------------
 ui/utils/messageline.h   | 14 +------
 ui/utils/progress.cpp    | 84 ++++++++++++++++++++++++++++++++++++++++
 ui/utils/progress.h      | 57 +++++++++++++++++++++++++++
 5 files changed, 149 insertions(+), 48 deletions(-)
 create mode 100644 ui/utils/progress.cpp
 create mode 100644 ui/utils/progress.h

diff --git a/ui/CMakeLists.txt b/ui/CMakeLists.txt
index 0f6680a..59a0a4f 100644
--- a/ui/CMakeLists.txt
+++ b/ui/CMakeLists.txt
@@ -36,6 +36,7 @@ set(squawkUI_SRC
   utils/image.cpp
   utils/flowlayout.cpp
   utils/badge.cpp
+  utils/progress.cpp
 )
 
 # Tell CMake to create the helloworld executable
diff --git a/ui/utils/messageline.cpp b/ui/utils/messageline.cpp
index 0560344..57894e8 100644
--- a/ui/utils/messageline.cpp
+++ b/ui/utils/messageline.cpp
@@ -31,35 +31,11 @@ MessageLine::MessageLine(bool p_room, QWidget* parent):
     palNames(),
     views(),
     room(p_room),
-    busyPixmap(new QGraphicsPixmapItem(Shared::icon("view-refresh", true).pixmap(70))),
-    busyScene(),
-    busyLabel(&busyScene),
-    busyLayout(),
     busyShown(false),
-    rotation()
+    progress()
 {
     setBackgroundRole(QPalette::Base);
     layout->addStretch();
-    
-    busyScene.addItem(busyPixmap);
-    busyLayout.addStretch();
-    busyLayout.addWidget(&busyLabel);
-    busyLayout.addStretch();
-    busyLabel.setMaximumSize(70, 70);
-    busyLabel.setMinimumSize(70, 70);
-    busyLabel.setSceneRect(0, 0, 70, 70);
-    busyLabel.setFrameStyle(0);
-    busyLabel.setContentsMargins(0, 0, 0, 0);
-    busyLabel.setInteractive(false);
-    busyPixmap->setTransformOriginPoint(35, 35);
-    busyPixmap->setTransformationMode(Qt::SmoothTransformation);
-    busyPixmap->setOffset(0, 0);;
-    
-    rotation.setDuration(500);
-    rotation.setStartValue(0.0f);
-    rotation.setEndValue(180.0f);
-    rotation.setLoopCount(-1);
-    connect(&rotation, SIGNAL(valueChanged(const QVariant&)), this, SLOT(onAnimationValueChanged(const QVariant&)));
 }
 
 MessageLine::~MessageLine()
@@ -201,28 +177,21 @@ QString MessageLine::firstMessageId() const
 void MessageLine::showBusyIndicator()
 {
     if (!busyShown)  {
-        layout->insertLayout(0, &busyLayout);
+        layout->insertWidget(0, &progress);
+        progress.start();
         busyShown = true;
-        rotation.start();
-        busyLabel.show();
     }
 }
 
 void MessageLine::hideBusyIndicator()
 {
     if (busyShown) {
-        busyLabel.hide();
-        rotation.stop();
-        layout->removeItem(&busyLayout);
+        progress.stop();
+        layout->removeWidget(&progress);
         busyShown = false;
     }
 }
 
-void MessageLine::onAnimationValueChanged(const QVariant& value)
-{
-    busyPixmap->setRotation(value.toReal());
-}
-
 void MessageLine::responseDownloadProgress(const QString& messageId, qreal progress)
 {
     Index::const_iterator itr = messageIndex.find(messageId);
diff --git a/ui/utils/messageline.h b/ui/utils/messageline.h
index 3d7fb56..67280e4 100644
--- a/ui/utils/messageline.h
+++ b/ui/utils/messageline.h
@@ -25,13 +25,10 @@
 #include <QLabel>
 #include <QResizeEvent>
 #include <QIcon>
-#include <QGraphicsScene>
-#include <QGraphicsView>
-#include <QGraphicsPixmapItem>
-#include <QVariantAnimation>
 
 #include "../../global.h"
 #include "message.h"
+#include "progress.h"
 
 class MessageLine : public QWidget
 {
@@ -85,15 +82,8 @@ private:
     std::map<QString, QString> palNames;
     std::deque<QHBoxLayout*> views;
     bool room;
-    QGraphicsPixmapItem* busyPixmap;
-    QGraphicsScene busyScene;
-    QGraphicsView busyLabel;
-    QHBoxLayout busyLayout;
     bool busyShown;
-    QVariantAnimation rotation;
-    
-private slots:
-    void onAnimationValueChanged(const QVariant& value);
+    Progress progress;
 };
 
 #endif // MESSAGELINE_H
diff --git a/ui/utils/progress.cpp b/ui/utils/progress.cpp
new file mode 100644
index 0000000..9886270
--- /dev/null
+++ b/ui/utils/progress.cpp
@@ -0,0 +1,84 @@
+/*
+ * Squawk messenger. 
+ * Copyright (C) 2019 Yury Gubich <blue@macaw.me>
+ * 
+ * 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 "progress.h"
+
+Progress::Progress(quint16 p_size, QWidget* parent):
+    QWidget(parent),
+    pixmap(new QGraphicsPixmapItem(Shared::icon("view-refresh", true).pixmap(p_size))),
+    scene(),
+    label(&scene),
+    progress(false),
+    animation(),
+    size(p_size)
+{
+    scene.addItem(pixmap);
+    label.setMaximumSize(size, size);
+    label.setMinimumSize(size, size);
+    label.setSceneRect(0, 0, size, size);
+    label.setFrameStyle(0);
+    label.setContentsMargins(0, 0, 0, 0);
+    label.setInteractive(false);
+    pixmap->setTransformOriginPoint(size / 2, size / 2);
+    pixmap->setTransformationMode(Qt::SmoothTransformation);
+    pixmap->setOffset(0, 0);;
+    
+    animation.setDuration(500);
+    animation.setStartValue(0.0f);
+    animation.setEndValue(180.0f);
+    animation.setLoopCount(-1);
+    connect(&animation, &QVariantAnimation::valueChanged, this, &Progress::onValueChanged);
+    
+    QGridLayout* layout = new QGridLayout();
+    setLayout(layout);
+    layout->setMargin(0);
+    layout->setVerticalSpacing(0);
+    layout->setHorizontalSpacing(0);
+    
+    setContentsMargins(0, 0, 0, 0);
+    
+    layout->addWidget(&label, 0, 0, 1, 1);
+    label.hide();
+}
+
+Progress::~Progress()
+{
+}
+
+void Progress::onValueChanged(const QVariant& value)
+{
+    pixmap->setRotation(value.toReal());
+}
+
+void Progress::start()
+{
+    if (!progress) {
+        label.show();
+        animation.start();
+        progress = true;
+    }
+}
+
+void Progress::stop()
+{
+    if (progress) {
+        label.hide();
+        animation.stop();
+        progress = false;
+    }
+}
diff --git a/ui/utils/progress.h b/ui/utils/progress.h
new file mode 100644
index 0000000..c6501aa
--- /dev/null
+++ b/ui/utils/progress.h
@@ -0,0 +1,57 @@
+/*
+ * Squawk messenger. 
+ * Copyright (C) 2019 Yury Gubich <blue@macaw.me>
+ * 
+ * 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/>.
+ */
+
+#ifndef PROGRESS_H
+#define PROGRESS_H
+
+#include <QWidget>
+#include <QGraphicsScene>
+#include <QIcon>
+#include <QGraphicsView>
+#include <QGraphicsPixmapItem>
+#include <QVariantAnimation>
+#include <QGridLayout>
+
+#include "../../global.h"
+
+/**
+ * @todo write docs
+ */
+class Progress : public QWidget
+{
+    Q_OBJECT
+public:
+    Progress(quint16 p_size = 70, QWidget* parent = nullptr);
+    ~Progress();
+    
+    void start();
+    void stop();
+    
+private slots:
+    void onValueChanged(const QVariant& value);
+    
+private:
+    QGraphicsPixmapItem* pixmap;
+    QGraphicsScene scene;
+    QGraphicsView label;
+    bool progress;
+    QVariantAnimation animation;
+    quint16 size;
+};
+
+#endif // PROGRESS_H

From e924715a57b0ea3b075560baa5adff84d3c2e938 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Thu, 31 Oct 2019 17:01:48 +0300
Subject: [PATCH 015/281] progress for VCard widget, date of receiving

---
 global.cpp            |    5 +
 global.h              |    1 +
 ui/squawk.cpp         |    2 +
 ui/utils/message.cpp  |    1 -
 ui/utils/progress.cpp |    3 +-
 ui/widgets/vcard.cpp  |   40 +-
 ui/widgets/vcard.h    |    9 +
 ui/widgets/vcard.ui   | 1501 +++++++++++++++++++++--------------------
 8 files changed, 832 insertions(+), 730 deletions(-)

diff --git a/global.cpp b/global.cpp
index efadd6c..9beb7a1 100644
--- a/global.cpp
+++ b/global.cpp
@@ -533,6 +533,11 @@ void Shared::VCard::setOrgUnit(const QString& unit)
     }
 }
 
+QDateTime Shared::VCard::getReceivingTime() const
+{
+    return receivingTime;
+}
+
 QIcon Shared::availabilityIcon(Shared::Availability av, bool big)
 {
     const std::deque<QString>& fallback = QApplication::palette().window().color().lightnessF() > 0.5 ? 
diff --git a/global.h b/global.h
index 1261d1f..b046efa 100644
--- a/global.h
+++ b/global.h
@@ -300,6 +300,7 @@ public:
     void setOrgRole(const QString& role);
     QString getOrgTitle() const;
     void setOrgTitle(const QString& title);
+    QDateTime getReceivingTime() const;
     
 private:
     QString fullName;
diff --git a/ui/squawk.cpp b/ui/squawk.cpp
index b228ac8..5ddd525 100644
--- a/ui/squawk.cpp
+++ b/ui/squawk.cpp
@@ -751,6 +751,7 @@ void Squawk::responseVCard(const QString& jid, const Shared::VCard& card)
     std::map<QString, VCard*>::const_iterator itr = vCards.find(jid);
     if (itr != vCards.end()) {
         itr->second->setVCard(card);
+        itr->second->hideProgress();
     }
 }
 
@@ -790,6 +791,7 @@ void Squawk::onActivateVCard(const QString& account, const QString& jid, bool ed
     card->show();
     card->raise();
     card->activateWindow();
+    card->showProgress(tr("Downloading vCard"));
     
     emit requestVCard(account, jid);
 }
diff --git a/ui/utils/message.cpp b/ui/utils/message.cpp
index 4c8debe..2498d84 100644
--- a/ui/utils/message.cpp
+++ b/ui/utils/message.cpp
@@ -63,7 +63,6 @@ Message::Message(const Shared::Message& source, bool outgoing, const QString& p_
     dFont.setItalic(true);
     dFont.setPointSize(dFont.pointSize() - 2);
     date->setFont(dFont);
-    date->setForegroundRole(QPalette::ToolTipText);
     
     QFont f;
     f.setBold(true);
diff --git a/ui/utils/progress.cpp b/ui/utils/progress.cpp
index 9886270..95eafa2 100644
--- a/ui/utils/progress.cpp
+++ b/ui/utils/progress.cpp
@@ -34,9 +34,10 @@ Progress::Progress(quint16 p_size, QWidget* parent):
     label.setFrameStyle(0);
     label.setContentsMargins(0, 0, 0, 0);
     label.setInteractive(false);
+    label.setStyleSheet("background: transparent");
     pixmap->setTransformOriginPoint(size / 2, size / 2);
     pixmap->setTransformationMode(Qt::SmoothTransformation);
-    pixmap->setOffset(0, 0);;
+    pixmap->setOffset(0, 0);
     
     animation.setDuration(500);
     animation.setStartValue(0.0f);
diff --git a/ui/widgets/vcard.cpp b/ui/widgets/vcard.cpp
index c20b4bf..4584638 100644
--- a/ui/widgets/vcard.cpp
+++ b/ui/widgets/vcard.cpp
@@ -30,7 +30,10 @@ VCard::VCard(const QString& jid, bool edit, QWidget* parent):
     avatarMenu(nullptr),
     editable(edit),
     currentAvatarType(Shared::Avatar::empty),
-    currentAvatarPath("")
+    currentAvatarPath(""),
+    progress(new Progress(100)),
+    progressLabel(new QLabel()),
+    overlay(new QWidget())
 {
     m_ui->setupUi(this);
     m_ui->jabberID->setText(jid);
@@ -50,6 +53,7 @@ VCard::VCard(const QString& jid, bool edit, QWidget* parent):
         m_ui->avatarButton->setMenu(avatarMenu);
         avatarMenu->addAction(setAvatar);
         avatarMenu->addAction(clearAvatar);
+        m_ui->title->setText(tr("Your card"));
     } else {
         m_ui->buttonBox->hide();
         m_ui->fullName->setReadOnly(true);
@@ -64,6 +68,7 @@ VCard::VCard(const QString& jid, bool edit, QWidget* parent):
         m_ui->organizationRole->setReadOnly(true);
         m_ui->description->setReadOnly(true);
         m_ui->url->setReadOnly(true);
+        m_ui->title->setText(tr("Contact %1 card").arg(jid));
     }
     
     connect(m_ui->buttonBox, &QDialogButtonBox::accepted, this, &VCard::onButtonBoxAccepted);
@@ -73,6 +78,23 @@ VCard::VCard(const QString& jid, bool edit, QWidget* parent):
     
     int height = m_ui->personalForm->minimumSize().height() - avatarButtonMargins.height();
     m_ui->avatarButton->setIconSize(QSize(height, height));
+    
+    QGridLayout* gr = static_cast<QGridLayout*>(layout());
+    gr->addWidget(overlay, 0, 0, 4, 1);
+    QVBoxLayout* nl = new QVBoxLayout();
+    QGraphicsOpacityEffect* opacity = new QGraphicsOpacityEffect();
+    opacity->setOpacity(0.8);
+    overlay->setLayout(nl);
+    overlay->setBackgroundRole(QPalette::Base);
+    overlay->setAutoFillBackground(true);
+    overlay->setGraphicsEffect(opacity);
+    progressLabel->setAlignment(Qt::AlignCenter);
+    progressLabel->setStyleSheet("font: 16pt");
+    nl->addStretch();
+    nl->addWidget(progress);
+    nl->addWidget(progressLabel);
+    nl->addStretch();
+    overlay->hide();
 }
 
 VCard::~VCard()
@@ -102,6 +124,9 @@ void VCard::setVCard(const Shared::VCard& card)
     m_ui->organizationRole->setText(card.getOrgRole());
     m_ui->description->setText(card.getDescription());
     m_ui->url->setText(card.getUrl());
+    
+    QDateTime receivingTime = card.getReceivingTime();
+    m_ui->receivingTimeLabel->setText(tr("Received %1 at %2").arg(receivingTime.date().toString()).arg(receivingTime.time().toString()));
     currentAvatarType = card.getAvatarType();
     currentAvatarPath = card.getAvatarPath();
     
@@ -211,3 +236,16 @@ void VCard::onAvatarSelected()
         }
     }
 }
+
+void VCard::showProgress(const QString& line)
+{
+    progressLabel->setText(line);
+    overlay->show();
+    progress->start();
+}
+
+void VCard::hideProgress()
+{
+    overlay->hide();
+    progress->stop();
+}
diff --git a/ui/widgets/vcard.h b/ui/widgets/vcard.h
index afba227..4831734 100644
--- a/ui/widgets/vcard.h
+++ b/ui/widgets/vcard.h
@@ -27,10 +27,14 @@
 #include <QMimeDatabase>
 #include <QImage>
 #include <QStandardPaths>
+#include <QLabel>
+#include <QGraphicsOpacityEffect>
+#include <QVBoxLayout>
 
 #include <set>
 
 #include "../../global.h"
+#include "../utils/progress.h"
 
 namespace Ui
 {
@@ -50,6 +54,8 @@ public:
     void setVCard(const Shared::VCard& card);
     void setVCard(const QString& jid, const Shared::VCard& card);
     QString getJid() const;
+    void showProgress(const QString& = "");
+    void hideProgress();
     
 signals:
     void saveVCard(const Shared::VCard& card);
@@ -67,6 +73,9 @@ private:
     bool editable;
     Shared::Avatar currentAvatarType;
     QString currentAvatarPath;
+    Progress* progress;
+    QLabel* progressLabel;
+    QWidget* overlay;
     
     static const std::set<QString> supportedTypes;
     
diff --git a/ui/widgets/vcard.ui b/ui/widgets/vcard.ui
index 9eabf0b..a582d3c 100644
--- a/ui/widgets/vcard.ui
+++ b/ui/widgets/vcard.ui
@@ -2,777 +2,825 @@
 <ui version="4.0">
  <class>VCard</class>
  <widget class="QWidget" name="VCard">
+  <property name="enabled">
+   <bool>true</bool>
+  </property>
   <property name="geometry">
    <rect>
     <x>0</x>
     <y>0</y>
     <width>594</width>
-    <height>595</height>
+    <height>651</height>
    </rect>
   </property>
-  <layout class="QVBoxLayout" name="verticalLayout_2">
-   <property name="spacing">
-    <number>6</number>
-   </property>
+  <layout class="QGridLayout" name="gridLayout_4">
    <property name="leftMargin">
-    <number>6</number>
+    <number>0</number>
    </property>
    <property name="topMargin">
-    <number>6</number>
+    <number>0</number>
    </property>
    <property name="rightMargin">
-    <number>6</number>
+    <number>0</number>
    </property>
    <property name="bottomMargin">
-    <number>6</number>
+    <number>0</number>
    </property>
-   <item>
-    <widget class="QTabWidget" name="tabWidget">
-     <property name="focusPolicy">
-      <enum>Qt::TabFocus</enum>
+   <property name="spacing">
+    <number>0</number>
+   </property>
+   <item row="0" column="0">
+    <layout class="QVBoxLayout" name="verticalLayout">
+     <property name="leftMargin">
+      <number>6</number>
      </property>
-     <property name="tabPosition">
-      <enum>QTabWidget::North</enum>
+     <property name="topMargin">
+      <number>6</number>
      </property>
-     <property name="tabShape">
-      <enum>QTabWidget::Rounded</enum>
+     <property name="rightMargin">
+      <number>6</number>
      </property>
-     <property name="currentIndex">
-      <number>0</number>
+     <property name="bottomMargin">
+      <number>6</number>
      </property>
-     <property name="elideMode">
-      <enum>Qt::ElideNone</enum>
-     </property>
-     <property name="documentMode">
-      <bool>true</bool>
-     </property>
-     <property name="tabBarAutoHide">
-      <bool>false</bool>
-     </property>
-     <widget class="QWidget" name="General">
-      <attribute name="title">
-       <string>General</string>
-      </attribute>
-      <layout class="QGridLayout" name="gridLayout_2" columnstretch="1,2,2,1">
-       <property name="leftMargin">
+     <item>
+      <widget class="QLabel" name="title">
+       <property name="styleSheet">
+        <string notr="true">font: 16pt </string>
+       </property>
+       <property name="text">
+        <string notr="true">Contact john@dow.org card</string>
+       </property>
+       <property name="alignment">
+        <set>Qt::AlignCenter</set>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QLabel" name="receivingTimeLabel">
+       <property name="styleSheet">
+        <string notr="true">font: italic 8pt;</string>
+       </property>
+       <property name="text">
+        <string>Received 12.07.2007 at 17.35</string>
+       </property>
+       <property name="alignment">
+        <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QTabWidget" name="tabWidget">
+       <property name="enabled">
+        <bool>true</bool>
+       </property>
+       <property name="focusPolicy">
+        <enum>Qt::TabFocus</enum>
+       </property>
+       <property name="tabPosition">
+        <enum>QTabWidget::North</enum>
+       </property>
+       <property name="tabShape">
+        <enum>QTabWidget::Rounded</enum>
+       </property>
+       <property name="currentIndex">
         <number>0</number>
        </property>
-       <property name="topMargin">
-        <number>6</number>
+       <property name="elideMode">
+        <enum>Qt::ElideNone</enum>
        </property>
-       <property name="rightMargin">
-        <number>0</number>
+       <property name="documentMode">
+        <bool>true</bool>
        </property>
-       <property name="bottomMargin">
-        <number>0</number>
+       <property name="tabBarAutoHide">
+        <bool>false</bool>
        </property>
-       <property name="horizontalSpacing">
-        <number>6</number>
-       </property>
-       <item row="1" column="1" colspan="2">
-        <widget class="QLabel" name="personalHeading">
-         <property name="sizePolicy">
-          <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
-           <horstretch>0</horstretch>
-           <verstretch>0</verstretch>
-          </sizepolicy>
-         </property>
-         <property name="styleSheet">
-          <string notr="true"/>
-         </property>
-         <property name="frameShape">
-          <enum>QFrame::NoFrame</enum>
-         </property>
-         <property name="frameShadow">
-          <enum>QFrame::Plain</enum>
-         </property>
-         <property name="text">
-          <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:16pt; font-weight:600;&quot;&gt;Personal information&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
-         </property>
-         <property name="alignment">
-          <set>Qt::AlignCenter</set>
-         </property>
-        </widget>
-       </item>
-       <item row="4" column="1" colspan="2">
-        <widget class="Line" name="personalLine">
-         <property name="orientation">
-          <enum>Qt::Horizontal</enum>
-         </property>
-        </widget>
-       </item>
-       <item row="3" column="0" rowspan="7">
-        <spacer name="generalLeftHSpacer">
-         <property name="orientation">
-          <enum>Qt::Horizontal</enum>
-         </property>
-         <property name="sizeHint" stdset="0">
-          <size>
-           <width>40</width>
-           <height>20</height>
-          </size>
-         </property>
-        </spacer>
-       </item>
-       <item row="0" column="0" colspan="4">
-        <widget class="QLabel" name="generalHeading">
-         <property name="text">
-          <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:24pt; font-weight:600;&quot;&gt;General&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
-         </property>
-        </widget>
-       </item>
-       <item row="3" column="3" rowspan="7">
-        <spacer name="generalRightHSpacer">
-         <property name="orientation">
-          <enum>Qt::Horizontal</enum>
-         </property>
-         <property name="sizeHint" stdset="0">
-          <size>
-           <width>40</width>
-           <height>20</height>
-          </size>
-         </property>
-        </spacer>
-       </item>
-       <item row="6" column="1" colspan="2">
-        <widget class="QLabel" name="organizationHeading">
-         <property name="sizePolicy">
-          <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
-           <horstretch>0</horstretch>
-           <verstretch>0</verstretch>
-          </sizepolicy>
-         </property>
-         <property name="styleSheet">
-          <string notr="true"/>
-         </property>
-         <property name="text">
-          <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:16pt; font-weight:600;&quot;&gt;Organization&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
-         </property>
-         <property name="alignment">
-          <set>Qt::AlignCenter</set>
-         </property>
-        </widget>
-       </item>
-       <item row="7" column="1" colspan="2">
-        <layout class="QFormLayout" name="organizationForm">
-         <property name="formAlignment">
-          <set>Qt::AlignHCenter|Qt::AlignTop</set>
-         </property>
-         <item row="0" column="0">
-          <widget class="QLabel" name="organizationNameLabel">
-           <property name="text">
-            <string>Organization name</string>
-           </property>
-           <property name="buddy">
-            <cstring>organizationName</cstring>
-           </property>
-          </widget>
-         </item>
-         <item row="0" column="1">
-          <widget class="QLineEdit" name="organizationName">
-           <property name="minimumSize">
-            <size>
-             <width>200</width>
-             <height>0</height>
-            </size>
-           </property>
-           <property name="maximumSize">
-            <size>
-             <width>350</width>
-             <height>16777215</height>
-            </size>
-           </property>
-          </widget>
-         </item>
-         <item row="1" column="0">
-          <widget class="QLabel" name="organizationDepartmentLabel">
-           <property name="text">
-            <string>Unit / Department</string>
-           </property>
-           <property name="buddy">
-            <cstring>organizationDepartment</cstring>
-           </property>
-          </widget>
-         </item>
-         <item row="1" column="1">
-          <widget class="QLineEdit" name="organizationDepartment">
-           <property name="minimumSize">
-            <size>
-             <width>200</width>
-             <height>0</height>
-            </size>
-           </property>
-           <property name="maximumSize">
-            <size>
-             <width>350</width>
-             <height>16777215</height>
-            </size>
-           </property>
-          </widget>
-         </item>
-         <item row="2" column="0">
-          <widget class="QLabel" name="roleLabel">
-           <property name="text">
-            <string>Role / Profession</string>
-           </property>
-           <property name="buddy">
-            <cstring>organizationRole</cstring>
-           </property>
-          </widget>
-         </item>
-         <item row="2" column="1">
-          <widget class="QLineEdit" name="organizationRole">
-           <property name="minimumSize">
-            <size>
-             <width>200</width>
-             <height>0</height>
-            </size>
-           </property>
-           <property name="maximumSize">
-            <size>
-             <width>350</width>
-             <height>16777215</height>
-            </size>
-           </property>
-          </widget>
-         </item>
-         <item row="3" column="0">
-          <widget class="QLabel" name="organizationTitleLabel">
-           <property name="text">
-            <string>Job title</string>
-           </property>
-           <property name="buddy">
-            <cstring>organizationTitle</cstring>
-           </property>
-          </widget>
-         </item>
-         <item row="3" column="1">
-          <widget class="QLineEdit" name="organizationTitle">
-           <property name="minimumSize">
-            <size>
-             <width>200</width>
-             <height>0</height>
-            </size>
-           </property>
-           <property name="maximumSize">
-            <size>
-             <width>350</width>
-             <height>16777215</height>
-            </size>
-           </property>
-          </widget>
-         </item>
-        </layout>
-       </item>
-       <item row="3" column="1">
-        <layout class="QFormLayout" name="personalForm">
-         <property name="sizeConstraint">
-          <enum>QLayout::SetDefaultConstraint</enum>
-         </property>
-         <property name="formAlignment">
-          <set>Qt::AlignHCenter|Qt::AlignTop</set>
-         </property>
-         <item row="1" column="1">
-          <widget class="QLineEdit" name="middleName">
-           <property name="minimumSize">
-            <size>
-             <width>200</width>
-             <height>0</height>
-            </size>
-           </property>
-           <property name="maximumSize">
-            <size>
-             <width>350</width>
-             <height>16777215</height>
-            </size>
-           </property>
-          </widget>
-         </item>
-         <item row="0" column="1">
-          <widget class="QLineEdit" name="firstName">
-           <property name="minimumSize">
-            <size>
-             <width>200</width>
-             <height>0</height>
-            </size>
-           </property>
-           <property name="maximumSize">
-            <size>
-             <width>350</width>
-             <height>16777215</height>
-            </size>
-           </property>
-          </widget>
-         </item>
-         <item row="1" column="0">
-          <widget class="QLabel" name="middleNameLabel">
-           <property name="text">
-            <string>Middle name</string>
-           </property>
-           <property name="buddy">
-            <cstring>middleName</cstring>
-           </property>
-          </widget>
-         </item>
-         <item row="0" column="0">
-          <widget class="QLabel" name="firstNameLabel">
-           <property name="text">
-            <string>First name</string>
-           </property>
-           <property name="buddy">
-            <cstring>firstName</cstring>
-           </property>
-          </widget>
-         </item>
-         <item row="2" column="0">
-          <widget class="QLabel" name="lastNameLabel">
-           <property name="text">
-            <string>Last name</string>
-           </property>
-           <property name="buddy">
-            <cstring>lastName</cstring>
-           </property>
-          </widget>
-         </item>
-         <item row="2" column="1">
-          <widget class="QLineEdit" name="lastName">
-           <property name="minimumSize">
-            <size>
-             <width>200</width>
-             <height>0</height>
-            </size>
-           </property>
-           <property name="maximumSize">
-            <size>
-             <width>350</width>
-             <height>16777215</height>
-            </size>
-           </property>
-          </widget>
-         </item>
-         <item row="3" column="0">
-          <widget class="QLabel" name="nickNameLabel">
-           <property name="text">
-            <string>Nick name</string>
-           </property>
-           <property name="buddy">
-            <cstring>nickName</cstring>
-           </property>
-          </widget>
-         </item>
-         <item row="3" column="1">
-          <widget class="QLineEdit" name="nickName">
-           <property name="minimumSize">
-            <size>
-             <width>200</width>
-             <height>0</height>
-            </size>
-           </property>
-           <property name="maximumSize">
-            <size>
-             <width>350</width>
-             <height>16777215</height>
-            </size>
-           </property>
-          </widget>
-         </item>
-         <item row="4" column="0">
-          <widget class="QLabel" name="birthdayLabel">
-           <property name="text">
-            <string>Birthday</string>
-           </property>
-           <property name="buddy">
-            <cstring>birthday</cstring>
-           </property>
-          </widget>
-         </item>
-         <item row="4" column="1">
-          <widget class="QDateEdit" name="birthday"/>
-         </item>
-        </layout>
-       </item>
-       <item row="3" column="2">
-        <widget class="QToolButton" name="avatarButton">
-         <property name="sizePolicy">
-          <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
-           <horstretch>0</horstretch>
-           <verstretch>0</verstretch>
-          </sizepolicy>
-         </property>
-         <property name="minimumSize">
-          <size>
-           <width>0</width>
-           <height>0</height>
-          </size>
-         </property>
-         <property name="text">
-          <string/>
-         </property>
-         <property name="icon">
-          <iconset theme="user">
-           <normaloff>.</normaloff>.</iconset>
-         </property>
-         <property name="iconSize">
-          <size>
-           <width>0</width>
-           <height>0</height>
-          </size>
-         </property>
-         <property name="popupMode">
-          <enum>QToolButton::InstantPopup</enum>
-         </property>
-         <property name="toolButtonStyle">
-          <enum>Qt::ToolButtonIconOnly</enum>
-         </property>
-         <property name="arrowType">
-          <enum>Qt::NoArrow</enum>
-         </property>
-        </widget>
-       </item>
-       <item row="8" column="1" colspan="2">
-        <widget class="Line" name="organizationLine">
-         <property name="orientation">
-          <enum>Qt::Horizontal</enum>
-         </property>
-        </widget>
-       </item>
-       <item row="2" column="1" colspan="2">
-        <layout class="QFormLayout" name="commonForm">
-         <item row="0" column="1">
-          <widget class="QLineEdit" name="fullName"/>
-         </item>
-         <item row="0" column="0">
-          <widget class="QLabel" name="fullNameLabel">
-           <property name="text">
-            <string>Full name</string>
-           </property>
-           <property name="buddy">
-            <cstring>fullName</cstring>
-           </property>
-          </widget>
-         </item>
-        </layout>
-       </item>
-       <item row="9" column="1" colspan="2">
-        <spacer name="verticalSpacer">
-         <property name="orientation">
-          <enum>Qt::Vertical</enum>
-         </property>
-         <property name="sizeHint" stdset="0">
-          <size>
-           <width>20</width>
-           <height>40</height>
-          </size>
-         </property>
-        </spacer>
-       </item>
-      </layout>
-     </widget>
-     <widget class="QWidget" name="Contact">
-      <attribute name="title">
-       <string>Contact</string>
-      </attribute>
-      <layout class="QVBoxLayout" name="verticalLayout_7">
-       <property name="spacing">
-        <number>0</number>
-       </property>
-       <property name="leftMargin">
-        <number>0</number>
-       </property>
-       <property name="topMargin">
-        <number>6</number>
-       </property>
-       <property name="rightMargin">
-        <number>0</number>
-       </property>
-       <property name="bottomMargin">
-        <number>0</number>
-       </property>
-       <item>
-        <widget class="QLabel" name="contactHeading">
-         <property name="text">
-          <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:24pt; font-weight:600;&quot;&gt;Contact&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
-         </property>
-        </widget>
-       </item>
-       <item>
-        <widget class="QScrollArea" name="scrollArea">
-         <property name="frameShape">
-          <enum>QFrame::NoFrame</enum>
-         </property>
-         <property name="frameShadow">
-          <enum>QFrame::Plain</enum>
-         </property>
-         <property name="lineWidth">
+       <widget class="QWidget" name="General">
+        <attribute name="title">
+         <string>General</string>
+        </attribute>
+        <layout class="QGridLayout" name="gridLayout_2" columnstretch="1,2,2,1">
+         <property name="leftMargin">
           <number>0</number>
          </property>
-         <property name="widgetResizable">
-          <bool>true</bool>
+         <property name="topMargin">
+          <number>6</number>
          </property>
-         <widget class="QWidget" name="contactScrollArea">
-          <property name="geometry">
-           <rect>
-            <x>0</x>
-            <y>0</y>
-            <width>582</width>
-            <height>471</height>
-           </rect>
-          </property>
-          <layout class="QGridLayout" name="gridLayout_3" columnstretch="1,4,1">
-           <item row="10" column="1">
-            <widget class="Line" name="addressesLine">
-             <property name="orientation">
-              <enum>Qt::Horizontal</enum>
+         <property name="rightMargin">
+          <number>0</number>
+         </property>
+         <property name="bottomMargin">
+          <number>0</number>
+         </property>
+         <property name="horizontalSpacing">
+          <number>6</number>
+         </property>
+         <item row="6" column="1" colspan="2">
+          <widget class="QLabel" name="organizationHeading">
+           <property name="sizePolicy">
+            <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+             <horstretch>0</horstretch>
+             <verstretch>0</verstretch>
+            </sizepolicy>
+           </property>
+           <property name="styleSheet">
+            <string notr="true"/>
+           </property>
+           <property name="text">
+            <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:16pt; font-weight:600;&quot;&gt;Organization&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+           </property>
+           <property name="alignment">
+            <set>Qt::AlignCenter</set>
+           </property>
+          </widget>
+         </item>
+         <item row="3" column="1">
+          <layout class="QFormLayout" name="personalForm">
+           <property name="sizeConstraint">
+            <enum>QLayout::SetDefaultConstraint</enum>
+           </property>
+           <property name="formAlignment">
+            <set>Qt::AlignHCenter|Qt::AlignTop</set>
+           </property>
+           <item row="1" column="1">
+            <widget class="QLineEdit" name="middleName">
+             <property name="minimumSize">
+              <size>
+               <width>200</width>
+               <height>0</height>
+              </size>
              </property>
-            </widget>
-           </item>
-           <item row="3" column="1">
-            <layout class="QVBoxLayout" name="emailsLayout">
-             <item>
-              <widget class="QLabel" name="emptyEmailsPlaceholder">
-               <property name="text">
-                <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-style:italic;&quot;&gt;User has no contact e-mail addresses&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
-               </property>
-               <property name="alignment">
-                <set>Qt::AlignCenter</set>
-               </property>
-              </widget>
-             </item>
-            </layout>
-           </item>
-           <item row="2" column="1">
-            <widget class="QLabel" name="emailsHeading">
-             <property name="text">
-              <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:16pt; font-weight:600;&quot;&gt;E-Mail addresses&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
-             </property>
-             <property name="alignment">
-              <set>Qt::AlignCenter</set>
+             <property name="maximumSize">
+              <size>
+               <width>350</width>
+               <height>16777215</height>
+              </size>
              </property>
             </widget>
            </item>
            <item row="0" column="1">
-            <layout class="QFormLayout" name="contactForm">
-             <property name="formAlignment">
-              <set>Qt::AlignHCenter|Qt::AlignTop</set>
+            <widget class="QLineEdit" name="firstName">
+             <property name="minimumSize">
+              <size>
+               <width>200</width>
+               <height>0</height>
+              </size>
              </property>
-             <item row="0" column="1">
-              <widget class="QLineEdit" name="jabberID">
-               <property name="minimumSize">
-                <size>
-                 <width>150</width>
-                 <height>0</height>
-                </size>
-               </property>
-               <property name="maximumSize">
-                <size>
-                 <width>300</width>
-                 <height>16777215</height>
-                </size>
-               </property>
-              </widget>
-             </item>
-             <item row="0" column="0">
-              <widget class="QLabel" name="jabberIDLabel">
-               <property name="text">
-                <string>Jabber ID</string>
-               </property>
-               <property name="buddy">
-                <cstring>jabberID</cstring>
-               </property>
-              </widget>
-             </item>
-             <item row="1" column="1">
-              <widget class="QLineEdit" name="url">
-               <property name="minimumSize">
-                <size>
-                 <width>150</width>
-                 <height>0</height>
-                </size>
-               </property>
-               <property name="maximumSize">
-                <size>
-                 <width>300</width>
-                 <height>16777215</height>
-                </size>
-               </property>
-              </widget>
-             </item>
-             <item row="1" column="0">
-              <widget class="QLabel" name="urlLabel">
-               <property name="text">
-                <string>Web site</string>
-               </property>
-               <property name="buddy">
-                <cstring>url</cstring>
-               </property>
-              </widget>
-             </item>
-            </layout>
-           </item>
-           <item row="7" column="1">
-            <widget class="Line" name="phonesLine">
-             <property name="orientation">
-              <enum>Qt::Horizontal</enum>
+             <property name="maximumSize">
+              <size>
+               <width>350</width>
+               <height>16777215</height>
+              </size>
              </property>
             </widget>
            </item>
-           <item row="1" column="1">
-            <widget class="Line" name="contactFormLine">
-             <property name="orientation">
-              <enum>Qt::Horizontal</enum>
+           <item row="1" column="0">
+            <widget class="QLabel" name="middleNameLabel">
+             <property name="text">
+              <string>Middle name</string>
+             </property>
+             <property name="buddy">
+              <cstring>middleName</cstring>
+             </property>
+            </widget>
+           </item>
+           <item row="0" column="0">
+            <widget class="QLabel" name="firstNameLabel">
+             <property name="text">
+              <string>First name</string>
+             </property>
+             <property name="buddy">
+              <cstring>firstName</cstring>
+             </property>
+            </widget>
+           </item>
+           <item row="2" column="0">
+            <widget class="QLabel" name="lastNameLabel">
+             <property name="text">
+              <string>Last name</string>
+             </property>
+             <property name="buddy">
+              <cstring>lastName</cstring>
+             </property>
+            </widget>
+           </item>
+           <item row="2" column="1">
+            <widget class="QLineEdit" name="lastName">
+             <property name="minimumSize">
+              <size>
+               <width>200</width>
+               <height>0</height>
+              </size>
+             </property>
+             <property name="maximumSize">
+              <size>
+               <width>350</width>
+               <height>16777215</height>
+              </size>
+             </property>
+            </widget>
+           </item>
+           <item row="3" column="0">
+            <widget class="QLabel" name="nickNameLabel">
+             <property name="text">
+              <string>Nick name</string>
+             </property>
+             <property name="buddy">
+              <cstring>nickName</cstring>
+             </property>
+            </widget>
+           </item>
+           <item row="3" column="1">
+            <widget class="QLineEdit" name="nickName">
+             <property name="minimumSize">
+              <size>
+               <width>200</width>
+               <height>0</height>
+              </size>
+             </property>
+             <property name="maximumSize">
+              <size>
+               <width>350</width>
+               <height>16777215</height>
+              </size>
+             </property>
+            </widget>
+           </item>
+           <item row="4" column="0">
+            <widget class="QLabel" name="birthdayLabel">
+             <property name="text">
+              <string>Birthday</string>
+             </property>
+             <property name="buddy">
+              <cstring>birthday</cstring>
              </property>
             </widget>
            </item>
            <item row="4" column="1">
-            <widget class="Line" name="emailsLine">
-             <property name="orientation">
-              <enum>Qt::Horizontal</enum>
-             </property>
-            </widget>
-           </item>
-           <item row="11" column="1">
-            <spacer name="contactBottomSpacer">
-             <property name="orientation">
-              <enum>Qt::Vertical</enum>
-             </property>
-             <property name="sizeHint" stdset="0">
-              <size>
-               <width>20</width>
-               <height>40</height>
-              </size>
-             </property>
-            </spacer>
-           </item>
-           <item row="0" column="0" rowspan="12">
-            <spacer name="contactLeftSpacer">
-             <property name="orientation">
-              <enum>Qt::Horizontal</enum>
-             </property>
-             <property name="sizeHint" stdset="0">
-              <size>
-               <width>40</width>
-               <height>20</height>
-              </size>
-             </property>
-            </spacer>
-           </item>
-           <item row="6" column="1">
-            <layout class="QVBoxLayout" name="phonesLayout">
-             <item>
-              <widget class="QLabel" name="emptyPhonesPlaceholder">
-               <property name="text">
-                <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-style:italic;&quot;&gt;User has no contact e-mail addresses&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
-               </property>
-               <property name="alignment">
-                <set>Qt::AlignCenter</set>
-               </property>
-              </widget>
-             </item>
-            </layout>
-           </item>
-           <item row="8" column="1">
-            <widget class="QLabel" name="addressesHeading">
-             <property name="text">
-              <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:16pt; font-weight:600;&quot;&gt;Addresses&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
-             </property>
-             <property name="alignment">
-              <set>Qt::AlignCenter</set>
-             </property>
-            </widget>
-           </item>
-           <item row="9" column="1">
-            <layout class="QVBoxLayout" name="addressesLayout">
-             <item>
-              <widget class="QLabel" name="emptyAddressesPlaceholder">
-               <property name="text">
-                <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-style:italic;&quot;&gt;User has no contact e-mail addresses&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
-               </property>
-               <property name="alignment">
-                <set>Qt::AlignCenter</set>
-               </property>
-              </widget>
-             </item>
-            </layout>
-           </item>
-           <item row="5" column="1">
-            <widget class="QLabel" name="phenesHeading">
-             <property name="text">
-              <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:16pt; font-weight:600;&quot;&gt;Phone numbers&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
-             </property>
-             <property name="alignment">
-              <set>Qt::AlignCenter</set>
-             </property>
-            </widget>
-           </item>
-           <item row="0" column="2" rowspan="12">
-            <spacer name="contactRightSpacer">
-             <property name="orientation">
-              <enum>Qt::Horizontal</enum>
-             </property>
-             <property name="sizeHint" stdset="0">
-              <size>
-               <width>40</width>
-               <height>20</height>
-              </size>
-             </property>
-            </spacer>
+            <widget class="QDateEdit" name="birthday"/>
            </item>
           </layout>
-         </widget>
-        </widget>
-       </item>
-      </layout>
-     </widget>
-     <widget class="QWidget" name="Description">
-      <attribute name="title">
-       <string>Description</string>
-      </attribute>
-      <layout class="QGridLayout" name="gridLayout">
-       <property name="leftMargin">
-        <number>0</number>
-       </property>
-       <property name="topMargin">
-        <number>6</number>
-       </property>
-       <property name="rightMargin">
-        <number>0</number>
-       </property>
-       <property name="bottomMargin">
-        <number>0</number>
-       </property>
-       <property name="horizontalSpacing">
-        <number>6</number>
-       </property>
-       <item row="0" column="0">
-        <widget class="QLabel" name="descriptionHeading">
-         <property name="text">
-          <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:24pt; font-weight:600;&quot;&gt;Description&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+         </item>
+         <item row="7" column="1" colspan="2">
+          <layout class="QFormLayout" name="organizationForm">
+           <property name="formAlignment">
+            <set>Qt::AlignHCenter|Qt::AlignTop</set>
+           </property>
+           <item row="0" column="0">
+            <widget class="QLabel" name="organizationNameLabel">
+             <property name="text">
+              <string>Organization name</string>
+             </property>
+             <property name="buddy">
+              <cstring>organizationName</cstring>
+             </property>
+            </widget>
+           </item>
+           <item row="0" column="1">
+            <widget class="QLineEdit" name="organizationName">
+             <property name="minimumSize">
+              <size>
+               <width>200</width>
+               <height>0</height>
+              </size>
+             </property>
+             <property name="maximumSize">
+              <size>
+               <width>350</width>
+               <height>16777215</height>
+              </size>
+             </property>
+            </widget>
+           </item>
+           <item row="1" column="0">
+            <widget class="QLabel" name="organizationDepartmentLabel">
+             <property name="text">
+              <string>Unit / Department</string>
+             </property>
+             <property name="buddy">
+              <cstring>organizationDepartment</cstring>
+             </property>
+            </widget>
+           </item>
+           <item row="1" column="1">
+            <widget class="QLineEdit" name="organizationDepartment">
+             <property name="minimumSize">
+              <size>
+               <width>200</width>
+               <height>0</height>
+              </size>
+             </property>
+             <property name="maximumSize">
+              <size>
+               <width>350</width>
+               <height>16777215</height>
+              </size>
+             </property>
+            </widget>
+           </item>
+           <item row="2" column="0">
+            <widget class="QLabel" name="roleLabel">
+             <property name="text">
+              <string>Role / Profession</string>
+             </property>
+             <property name="buddy">
+              <cstring>organizationRole</cstring>
+             </property>
+            </widget>
+           </item>
+           <item row="2" column="1">
+            <widget class="QLineEdit" name="organizationRole">
+             <property name="minimumSize">
+              <size>
+               <width>200</width>
+               <height>0</height>
+              </size>
+             </property>
+             <property name="maximumSize">
+              <size>
+               <width>350</width>
+               <height>16777215</height>
+              </size>
+             </property>
+            </widget>
+           </item>
+           <item row="3" column="0">
+            <widget class="QLabel" name="organizationTitleLabel">
+             <property name="text">
+              <string>Job title</string>
+             </property>
+             <property name="buddy">
+              <cstring>organizationTitle</cstring>
+             </property>
+            </widget>
+           </item>
+           <item row="3" column="1">
+            <widget class="QLineEdit" name="organizationTitle">
+             <property name="minimumSize">
+              <size>
+               <width>200</width>
+               <height>0</height>
+              </size>
+             </property>
+             <property name="maximumSize">
+              <size>
+               <width>350</width>
+               <height>16777215</height>
+              </size>
+             </property>
+            </widget>
+           </item>
+          </layout>
+         </item>
+         <item row="8" column="1" colspan="2">
+          <widget class="Line" name="organizationLine">
+           <property name="orientation">
+            <enum>Qt::Horizontal</enum>
+           </property>
+          </widget>
+         </item>
+         <item row="2" column="1" colspan="2">
+          <layout class="QFormLayout" name="commonForm">
+           <item row="0" column="1">
+            <widget class="QLineEdit" name="fullName"/>
+           </item>
+           <item row="0" column="0">
+            <widget class="QLabel" name="fullNameLabel">
+             <property name="text">
+              <string>Full name</string>
+             </property>
+             <property name="buddy">
+              <cstring>fullName</cstring>
+             </property>
+            </widget>
+           </item>
+          </layout>
+         </item>
+         <item row="3" column="0" rowspan="7">
+          <spacer name="generalLeftHSpacer">
+           <property name="orientation">
+            <enum>Qt::Horizontal</enum>
+           </property>
+           <property name="sizeHint" stdset="0">
+            <size>
+             <width>40</width>
+             <height>20</height>
+            </size>
+           </property>
+          </spacer>
+         </item>
+         <item row="4" column="1" colspan="2">
+          <widget class="Line" name="personalLine">
+           <property name="orientation">
+            <enum>Qt::Horizontal</enum>
+           </property>
+          </widget>
+         </item>
+         <item row="0" column="0" colspan="4">
+          <widget class="QLabel" name="generalHeading">
+           <property name="text">
+            <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:24pt; font-weight:600;&quot;&gt;General&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+           </property>
+          </widget>
+         </item>
+         <item row="3" column="3" rowspan="7">
+          <spacer name="generalRightHSpacer">
+           <property name="orientation">
+            <enum>Qt::Horizontal</enum>
+           </property>
+           <property name="sizeHint" stdset="0">
+            <size>
+             <width>40</width>
+             <height>20</height>
+            </size>
+           </property>
+          </spacer>
+         </item>
+         <item row="1" column="1" colspan="2">
+          <widget class="QLabel" name="personalHeading">
+           <property name="sizePolicy">
+            <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+             <horstretch>0</horstretch>
+             <verstretch>0</verstretch>
+            </sizepolicy>
+           </property>
+           <property name="styleSheet">
+            <string notr="true"/>
+           </property>
+           <property name="frameShape">
+            <enum>QFrame::NoFrame</enum>
+           </property>
+           <property name="frameShadow">
+            <enum>QFrame::Plain</enum>
+           </property>
+           <property name="text">
+            <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:16pt; font-weight:600;&quot;&gt;Personal information&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+           </property>
+           <property name="alignment">
+            <set>Qt::AlignCenter</set>
+           </property>
+          </widget>
+         </item>
+         <item row="3" column="2">
+          <widget class="QToolButton" name="avatarButton">
+           <property name="sizePolicy">
+            <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+             <horstretch>0</horstretch>
+             <verstretch>0</verstretch>
+            </sizepolicy>
+           </property>
+           <property name="minimumSize">
+            <size>
+             <width>0</width>
+             <height>0</height>
+            </size>
+           </property>
+           <property name="text">
+            <string/>
+           </property>
+           <property name="icon">
+            <iconset theme="user">
+             <normaloff>.</normaloff>.</iconset>
+           </property>
+           <property name="iconSize">
+            <size>
+             <width>0</width>
+             <height>0</height>
+            </size>
+           </property>
+           <property name="popupMode">
+            <enum>QToolButton::InstantPopup</enum>
+           </property>
+           <property name="toolButtonStyle">
+            <enum>Qt::ToolButtonIconOnly</enum>
+           </property>
+           <property name="arrowType">
+            <enum>Qt::NoArrow</enum>
+           </property>
+          </widget>
+         </item>
+         <item row="9" column="1" colspan="2">
+          <spacer name="verticalSpacer">
+           <property name="orientation">
+            <enum>Qt::Vertical</enum>
+           </property>
+           <property name="sizeHint" stdset="0">
+            <size>
+             <width>20</width>
+             <height>40</height>
+            </size>
+           </property>
+          </spacer>
+         </item>
+        </layout>
+       </widget>
+       <widget class="QWidget" name="Contact">
+        <attribute name="title">
+         <string>Contact</string>
+        </attribute>
+        <layout class="QVBoxLayout" name="verticalLayout_7">
+         <property name="spacing">
+          <number>0</number>
          </property>
-        </widget>
-       </item>
-       <item row="1" column="0">
-        <widget class="QTextEdit" name="description">
-         <property name="frameShape">
-          <enum>QFrame::StyledPanel</enum>
+         <property name="leftMargin">
+          <number>0</number>
          </property>
-         <property name="textInteractionFlags">
-          <set>Qt::LinksAccessibleByMouse|Qt::TextEditable|Qt::TextEditorInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
+         <property name="topMargin">
+          <number>6</number>
          </property>
-        </widget>
-       </item>
-      </layout>
-     </widget>
-    </widget>
-   </item>
-   <item>
-    <widget class="QDialogButtonBox" name="buttonBox">
-     <property name="standardButtons">
-      <set>QDialogButtonBox::Close|QDialogButtonBox::Save</set>
-     </property>
-     <property name="centerButtons">
-      <bool>false</bool>
-     </property>
-    </widget>
+         <property name="rightMargin">
+          <number>0</number>
+         </property>
+         <property name="bottomMargin">
+          <number>0</number>
+         </property>
+         <item>
+          <widget class="QLabel" name="contactHeading">
+           <property name="text">
+            <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:24pt; font-weight:600;&quot;&gt;Contact&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <widget class="QScrollArea" name="scrollArea">
+           <property name="frameShape">
+            <enum>QFrame::NoFrame</enum>
+           </property>
+           <property name="frameShadow">
+            <enum>QFrame::Plain</enum>
+           </property>
+           <property name="lineWidth">
+            <number>0</number>
+           </property>
+           <property name="widgetResizable">
+            <bool>true</bool>
+           </property>
+           <widget class="QWidget" name="contactScrollArea">
+            <property name="geometry">
+             <rect>
+              <x>0</x>
+              <y>0</y>
+              <width>575</width>
+              <height>475</height>
+             </rect>
+            </property>
+            <layout class="QGridLayout" name="gridLayout_3" columnstretch="1,4,1">
+             <item row="10" column="1">
+              <widget class="Line" name="addressesLine">
+               <property name="orientation">
+                <enum>Qt::Horizontal</enum>
+               </property>
+              </widget>
+             </item>
+             <item row="3" column="1">
+              <layout class="QVBoxLayout" name="emailsLayout">
+               <item>
+                <widget class="QLabel" name="emptyEmailsPlaceholder">
+                 <property name="text">
+                  <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-style:italic;&quot;&gt;User has no contact e-mail addresses&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+                 </property>
+                 <property name="alignment">
+                  <set>Qt::AlignCenter</set>
+                 </property>
+                </widget>
+               </item>
+              </layout>
+             </item>
+             <item row="2" column="1">
+              <widget class="QLabel" name="emailsHeading">
+               <property name="text">
+                <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:16pt; font-weight:600;&quot;&gt;E-Mail addresses&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+               </property>
+               <property name="alignment">
+                <set>Qt::AlignCenter</set>
+               </property>
+              </widget>
+             </item>
+             <item row="0" column="1">
+              <layout class="QFormLayout" name="contactForm">
+               <property name="formAlignment">
+                <set>Qt::AlignHCenter|Qt::AlignTop</set>
+               </property>
+               <item row="0" column="1">
+                <widget class="QLineEdit" name="jabberID">
+                 <property name="minimumSize">
+                  <size>
+                   <width>150</width>
+                   <height>0</height>
+                  </size>
+                 </property>
+                 <property name="maximumSize">
+                  <size>
+                   <width>300</width>
+                   <height>16777215</height>
+                  </size>
+                 </property>
+                </widget>
+               </item>
+               <item row="0" column="0">
+                <widget class="QLabel" name="jabberIDLabel">
+                 <property name="text">
+                  <string>Jabber ID</string>
+                 </property>
+                 <property name="buddy">
+                  <cstring>jabberID</cstring>
+                 </property>
+                </widget>
+               </item>
+               <item row="1" column="1">
+                <widget class="QLineEdit" name="url">
+                 <property name="minimumSize">
+                  <size>
+                   <width>150</width>
+                   <height>0</height>
+                  </size>
+                 </property>
+                 <property name="maximumSize">
+                  <size>
+                   <width>300</width>
+                   <height>16777215</height>
+                  </size>
+                 </property>
+                </widget>
+               </item>
+               <item row="1" column="0">
+                <widget class="QLabel" name="urlLabel">
+                 <property name="text">
+                  <string>Web site</string>
+                 </property>
+                 <property name="buddy">
+                  <cstring>url</cstring>
+                 </property>
+                </widget>
+               </item>
+              </layout>
+             </item>
+             <item row="7" column="1">
+              <widget class="Line" name="phonesLine">
+               <property name="orientation">
+                <enum>Qt::Horizontal</enum>
+               </property>
+              </widget>
+             </item>
+             <item row="1" column="1">
+              <widget class="Line" name="contactFormLine">
+               <property name="orientation">
+                <enum>Qt::Horizontal</enum>
+               </property>
+              </widget>
+             </item>
+             <item row="4" column="1">
+              <widget class="Line" name="emailsLine">
+               <property name="orientation">
+                <enum>Qt::Horizontal</enum>
+               </property>
+              </widget>
+             </item>
+             <item row="11" column="1">
+              <spacer name="contactBottomSpacer">
+               <property name="orientation">
+                <enum>Qt::Vertical</enum>
+               </property>
+               <property name="sizeHint" stdset="0">
+                <size>
+                 <width>20</width>
+                 <height>40</height>
+                </size>
+               </property>
+              </spacer>
+             </item>
+             <item row="0" column="0" rowspan="12">
+              <spacer name="contactLeftSpacer">
+               <property name="orientation">
+                <enum>Qt::Horizontal</enum>
+               </property>
+               <property name="sizeHint" stdset="0">
+                <size>
+                 <width>40</width>
+                 <height>20</height>
+                </size>
+               </property>
+              </spacer>
+             </item>
+             <item row="6" column="1">
+              <layout class="QVBoxLayout" name="phonesLayout">
+               <item>
+                <widget class="QLabel" name="emptyPhonesPlaceholder">
+                 <property name="text">
+                  <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-style:italic;&quot;&gt;User has no contact e-mail addresses&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+                 </property>
+                 <property name="alignment">
+                  <set>Qt::AlignCenter</set>
+                 </property>
+                </widget>
+               </item>
+              </layout>
+             </item>
+             <item row="8" column="1">
+              <widget class="QLabel" name="addressesHeading">
+               <property name="text">
+                <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:16pt; font-weight:600;&quot;&gt;Addresses&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+               </property>
+               <property name="alignment">
+                <set>Qt::AlignCenter</set>
+               </property>
+              </widget>
+             </item>
+             <item row="9" column="1">
+              <layout class="QVBoxLayout" name="addressesLayout">
+               <item>
+                <widget class="QLabel" name="emptyAddressesPlaceholder">
+                 <property name="text">
+                  <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-style:italic;&quot;&gt;User has no contact e-mail addresses&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+                 </property>
+                 <property name="alignment">
+                  <set>Qt::AlignCenter</set>
+                 </property>
+                </widget>
+               </item>
+              </layout>
+             </item>
+             <item row="5" column="1">
+              <widget class="QLabel" name="phenesHeading">
+               <property name="text">
+                <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:16pt; font-weight:600;&quot;&gt;Phone numbers&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+               </property>
+               <property name="alignment">
+                <set>Qt::AlignCenter</set>
+               </property>
+              </widget>
+             </item>
+             <item row="0" column="2" rowspan="12">
+              <spacer name="contactRightSpacer">
+               <property name="orientation">
+                <enum>Qt::Horizontal</enum>
+               </property>
+               <property name="sizeHint" stdset="0">
+                <size>
+                 <width>40</width>
+                 <height>20</height>
+                </size>
+               </property>
+              </spacer>
+             </item>
+            </layout>
+           </widget>
+          </widget>
+         </item>
+        </layout>
+       </widget>
+       <widget class="QWidget" name="Description">
+        <attribute name="title">
+         <string>Description</string>
+        </attribute>
+        <layout class="QGridLayout" name="gridLayout">
+         <property name="leftMargin">
+          <number>0</number>
+         </property>
+         <property name="topMargin">
+          <number>6</number>
+         </property>
+         <property name="rightMargin">
+          <number>0</number>
+         </property>
+         <property name="bottomMargin">
+          <number>0</number>
+         </property>
+         <property name="horizontalSpacing">
+          <number>6</number>
+         </property>
+         <item row="0" column="0">
+          <widget class="QLabel" name="descriptionHeading">
+           <property name="text">
+            <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:24pt; font-weight:600;&quot;&gt;Description&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+           </property>
+          </widget>
+         </item>
+         <item row="1" column="0">
+          <widget class="QTextEdit" name="description">
+           <property name="frameShape">
+            <enum>QFrame::StyledPanel</enum>
+           </property>
+           <property name="textInteractionFlags">
+            <set>Qt::LinksAccessibleByMouse|Qt::TextEditable|Qt::TextEditorInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
+           </property>
+          </widget>
+         </item>
+        </layout>
+       </widget>
+      </widget>
+     </item>
+     <item>
+      <widget class="QDialogButtonBox" name="buttonBox">
+       <property name="standardButtons">
+        <set>QDialogButtonBox::Close|QDialogButtonBox::Save</set>
+       </property>
+       <property name="centerButtons">
+        <bool>false</bool>
+       </property>
+      </widget>
+     </item>
+    </layout>
    </item>
   </layout>
   <action name="actionSetAvatar">
@@ -806,7 +854,6 @@
   <tabstop>organizationDepartment</tabstop>
   <tabstop>organizationRole</tabstop>
   <tabstop>organizationTitle</tabstop>
-  <tabstop>tabWidget</tabstop>
   <tabstop>jabberID</tabstop>
   <tabstop>url</tabstop>
   <tabstop>description</tabstop>

From c067835b006bcbd259978e1662b5228f30afee12 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Fri, 1 Nov 2019 18:06:03 +0300
Subject: [PATCH 016/281] button to add addresses phones and emails, yet dummy

---
 ui/widgets/vcard.cpp |   4 +
 ui/widgets/vcard.ui  | 218 ++++++++++++++++++++++++++-----------------
 2 files changed, 137 insertions(+), 85 deletions(-)

diff --git a/ui/widgets/vcard.cpp b/ui/widgets/vcard.cpp
index 4584638..b092ef8 100644
--- a/ui/widgets/vcard.cpp
+++ b/ui/widgets/vcard.cpp
@@ -69,6 +69,10 @@ VCard::VCard(const QString& jid, bool edit, QWidget* parent):
         m_ui->description->setReadOnly(true);
         m_ui->url->setReadOnly(true);
         m_ui->title->setText(tr("Contact %1 card").arg(jid));
+        
+        m_ui->addAddressButton->hide();
+        m_ui->addPhoneButton->hide();
+        m_ui->addEmailButton->hide();
     }
     
     connect(m_ui->buttonBox, &QDialogButtonBox::accepted, this, &VCard::onButtonBoxAccepted);
diff --git a/ui/widgets/vcard.ui b/ui/widgets/vcard.ui
index a582d3c..fbd0b82 100644
--- a/ui/widgets/vcard.ui
+++ b/ui/widgets/vcard.ui
@@ -9,8 +9,8 @@
    <rect>
     <x>0</x>
     <y>0</y>
-    <width>594</width>
-    <height>651</height>
+    <width>526</width>
+    <height>662</height>
    </rect>
   </property>
   <layout class="QGridLayout" name="gridLayout_4">
@@ -560,22 +560,31 @@
              <rect>
               <x>0</x>
               <y>0</y>
-              <width>575</width>
-              <height>475</height>
+              <width>514</width>
+              <height>488</height>
              </rect>
             </property>
-            <layout class="QGridLayout" name="gridLayout_3" columnstretch="1,4,1">
-             <item row="10" column="1">
-              <widget class="Line" name="addressesLine">
-               <property name="orientation">
-                <enum>Qt::Horizontal</enum>
+            <layout class="QGridLayout" name="gridLayout_3" columnstretch="1,2,2,1">
+             <item row="2" column="2">
+              <widget class="QPushButton" name="addEmailButton">
+               <property name="sizePolicy">
+                <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+                 <horstretch>0</horstretch>
+                 <verstretch>0</verstretch>
+                </sizepolicy>
+               </property>
+               <property name="text">
+                <string/>
+               </property>
+               <property name="icon">
+                <iconset theme="list-add"/>
                </property>
               </widget>
              </item>
-             <item row="3" column="1">
-              <layout class="QVBoxLayout" name="emailsLayout">
+             <item row="6" column="1" colspan="2">
+              <layout class="QVBoxLayout" name="phonesLayout">
                <item>
-                <widget class="QLabel" name="emptyEmailsPlaceholder">
+                <widget class="QLabel" name="emptyPhonesPlaceholder">
                  <property name="text">
                   <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-style:italic;&quot;&gt;User has no contact e-mail addresses&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
                  </property>
@@ -586,17 +595,34 @@
                </item>
               </layout>
              </item>
-             <item row="2" column="1">
-              <widget class="QLabel" name="emailsHeading">
-               <property name="text">
-                <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:16pt; font-weight:600;&quot;&gt;E-Mail addresses&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+             <item row="11" column="1" colspan="2">
+              <spacer name="contactBottomSpacer">
+               <property name="orientation">
+                <enum>Qt::Vertical</enum>
                </property>
-               <property name="alignment">
-                <set>Qt::AlignCenter</set>
+               <property name="sizeHint" stdset="0">
+                <size>
+                 <width>20</width>
+                 <height>40</height>
+                </size>
                </property>
-              </widget>
+              </spacer>
              </item>
-             <item row="0" column="1">
+             <item row="9" column="1" colspan="2">
+              <layout class="QVBoxLayout" name="addressesLayout">
+               <item>
+                <widget class="QLabel" name="emptyAddressesPlaceholder">
+                 <property name="text">
+                  <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-style:italic;&quot;&gt;User has no contact e-mail addresses&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+                 </property>
+                 <property name="alignment">
+                  <set>Qt::AlignCenter</set>
+                 </property>
+                </widget>
+               </item>
+              </layout>
+             </item>
+             <item row="0" column="1" colspan="2">
               <layout class="QFormLayout" name="contactForm">
                <property name="formAlignment">
                 <set>Qt::AlignHCenter|Qt::AlignTop</set>
@@ -655,39 +681,109 @@
                </item>
               </layout>
              </item>
-             <item row="7" column="1">
-              <widget class="Line" name="phonesLine">
-               <property name="orientation">
-                <enum>Qt::Horizontal</enum>
+             <item row="8" column="1">
+              <widget class="QLabel" name="addressesHeading">
+               <property name="text">
+                <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:16pt; font-weight:600;&quot;&gt;Addresses&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+               </property>
+               <property name="alignment">
+                <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
                </property>
               </widget>
              </item>
-             <item row="1" column="1">
+             <item row="1" column="1" colspan="2">
               <widget class="Line" name="contactFormLine">
                <property name="orientation">
                 <enum>Qt::Horizontal</enum>
                </property>
               </widget>
              </item>
-             <item row="4" column="1">
+             <item row="8" column="2">
+              <widget class="QPushButton" name="addAddressButton">
+               <property name="sizePolicy">
+                <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+                 <horstretch>0</horstretch>
+                 <verstretch>0</verstretch>
+                </sizepolicy>
+               </property>
+               <property name="text">
+                <string/>
+               </property>
+               <property name="icon">
+                <iconset theme="list-add"/>
+               </property>
+              </widget>
+             </item>
+             <item row="10" column="1" colspan="2">
+              <widget class="Line" name="addressesLine">
+               <property name="orientation">
+                <enum>Qt::Horizontal</enum>
+               </property>
+              </widget>
+             </item>
+             <item row="5" column="2">
+              <widget class="QPushButton" name="addPhoneButton">
+               <property name="sizePolicy">
+                <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+                 <horstretch>0</horstretch>
+                 <verstretch>0</verstretch>
+                </sizepolicy>
+               </property>
+               <property name="text">
+                <string/>
+               </property>
+               <property name="icon">
+                <iconset theme="list-add"/>
+               </property>
+              </widget>
+             </item>
+             <item row="2" column="1">
+              <widget class="QLabel" name="emailsHeading">
+               <property name="text">
+                <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:16pt; font-weight:600;&quot;&gt;E-Mail addresses&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+               </property>
+               <property name="alignment">
+                <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
+               </property>
+              </widget>
+             </item>
+             <item row="5" column="1">
+              <widget class="QLabel" name="phenesHeading">
+               <property name="text">
+                <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:16pt; font-weight:600;&quot;&gt;Phone numbers&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+               </property>
+               <property name="alignment">
+                <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
+               </property>
+              </widget>
+             </item>
+             <item row="4" column="1" colspan="2">
               <widget class="Line" name="emailsLine">
                <property name="orientation">
                 <enum>Qt::Horizontal</enum>
                </property>
               </widget>
              </item>
-             <item row="11" column="1">
-              <spacer name="contactBottomSpacer">
+             <item row="3" column="1" colspan="2">
+              <layout class="QVBoxLayout" name="emailsLayout">
+               <item>
+                <widget class="QLabel" name="emptyEmailsPlaceholder">
+                 <property name="text">
+                  <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-style:italic;&quot;&gt;User has no contact e-mail addresses&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+                 </property>
+                 <property name="alignment">
+                  <set>Qt::AlignCenter</set>
+                 </property>
+                </widget>
+               </item>
+              </layout>
+             </item>
+             <item row="7" column="1" colspan="2">
+              <widget class="Line" name="phonesLine">
                <property name="orientation">
-                <enum>Qt::Vertical</enum>
+                <enum>Qt::Horizontal</enum>
                </property>
-               <property name="sizeHint" stdset="0">
-                <size>
-                 <width>20</width>
-                 <height>40</height>
-                </size>
-               </property>
-              </spacer>
+              </widget>
              </item>
              <item row="0" column="0" rowspan="12">
               <spacer name="contactLeftSpacer">
@@ -702,55 +798,7 @@
                </property>
               </spacer>
              </item>
-             <item row="6" column="1">
-              <layout class="QVBoxLayout" name="phonesLayout">
-               <item>
-                <widget class="QLabel" name="emptyPhonesPlaceholder">
-                 <property name="text">
-                  <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-style:italic;&quot;&gt;User has no contact e-mail addresses&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
-                 </property>
-                 <property name="alignment">
-                  <set>Qt::AlignCenter</set>
-                 </property>
-                </widget>
-               </item>
-              </layout>
-             </item>
-             <item row="8" column="1">
-              <widget class="QLabel" name="addressesHeading">
-               <property name="text">
-                <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:16pt; font-weight:600;&quot;&gt;Addresses&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
-               </property>
-               <property name="alignment">
-                <set>Qt::AlignCenter</set>
-               </property>
-              </widget>
-             </item>
-             <item row="9" column="1">
-              <layout class="QVBoxLayout" name="addressesLayout">
-               <item>
-                <widget class="QLabel" name="emptyAddressesPlaceholder">
-                 <property name="text">
-                  <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-style:italic;&quot;&gt;User has no contact e-mail addresses&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
-                 </property>
-                 <property name="alignment">
-                  <set>Qt::AlignCenter</set>
-                 </property>
-                </widget>
-               </item>
-              </layout>
-             </item>
-             <item row="5" column="1">
-              <widget class="QLabel" name="phenesHeading">
-               <property name="text">
-                <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:16pt; font-weight:600;&quot;&gt;Phone numbers&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
-               </property>
-               <property name="alignment">
-                <set>Qt::AlignCenter</set>
-               </property>
-              </widget>
-             </item>
-             <item row="0" column="2" rowspan="12">
+             <item row="0" column="3" rowspan="12">
               <spacer name="contactRightSpacer">
                <property name="orientation">
                 <enum>Qt::Horizontal</enum>

From 9d491e9e931333df42e62fd41aac9449f05ba033 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Sat, 2 Nov 2019 23:50:25 +0300
Subject: [PATCH 017/281] deprecation fix about qxmpp, beginning of adding
 email addresses in VCards

---
 CMakeLists.txt                   |   1 +
 core/account.cpp                 |  62 +++----
 core/account.h                   |   2 +
 global.cpp                       |   2 +
 global.h                         |   1 +
 ui/CMakeLists.txt                |  12 +-
 ui/squawk.cpp                    |  14 +-
 ui/squawk.h                      |   4 +-
 ui/utils/comboboxdelegate.cpp    |  64 +++++++
 ui/utils/comboboxdelegate.h      |  47 +++++
 ui/widgets/CMakeLists.txt        |  29 +++
 ui/widgets/vcard/CMakeLists.txt  |  21 +++
 ui/widgets/vcard/emailsmodel.cpp | 145 +++++++++++++++
 ui/widgets/vcard/emailsmodel.h   |  57 ++++++
 ui/widgets/{ => vcard}/vcard.cpp |  67 ++++++-
 ui/widgets/{ => vcard}/vcard.h   |  17 +-
 ui/widgets/{ => vcard}/vcard.ui  | 292 +++++++++++++------------------
 17 files changed, 610 insertions(+), 227 deletions(-)
 create mode 100644 ui/utils/comboboxdelegate.cpp
 create mode 100644 ui/utils/comboboxdelegate.h
 create mode 100644 ui/widgets/CMakeLists.txt
 create mode 100644 ui/widgets/vcard/CMakeLists.txt
 create mode 100644 ui/widgets/vcard/emailsmodel.cpp
 create mode 100644 ui/widgets/vcard/emailsmodel.h
 rename ui/widgets/{ => vcard}/vcard.cpp (82%)
 rename ui/widgets/{ => vcard}/vcard.h (82%)
 rename ui/widgets/{ => vcard}/vcard.ui (87%)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 5010adf..f39d0e5 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -9,6 +9,7 @@ set(CMAKE_AUTOUIC ON)
 set(CMAKE_AUTORCC ON)
 
 include(GNUInstallDirs)
+include_directories(.)
 
 find_package(Qt5Widgets CONFIG REQUIRED)
 find_package(Qt5LinguistTools)
diff --git a/core/account.cpp b/core/account.cpp
index f3e4156..ff8c334 100644
--- a/core/account.cpp
+++ b/core/account.cpp
@@ -36,6 +36,8 @@ Account::Account(const QString& p_login, const QString& p_server, const QString&
     am(new QXmppMamManager()),
     mm(new QXmppMucManager()),
     bm(new QXmppBookmarkManager()),
+    rm(client.findExtension<QXmppRosterManager>()),
+    vm(client.findExtension<QXmppVCardManager>()),
     contacts(),
     conferences(),
     maxReconnectTimes(0),
@@ -57,12 +59,10 @@ Account::Account(const QString& p_login, const QString& p_server, const QString&
     QObject::connect(&client, &QXmppClient::messageReceived, this, &Account::onMessageReceived);
     QObject::connect(&client, &QXmppClient::error, this, &Account::onClientError);
     
-    QXmppRosterManager& rm = client.rosterManager();
-    
-    QObject::connect(&rm, &QXmppRosterManager::rosterReceived, this, &Account::onRosterReceived);
-    QObject::connect(&rm, &QXmppRosterManager::itemAdded, this, &Account::onRosterItemAdded);
-    QObject::connect(&rm, &QXmppRosterManager::itemRemoved, this, &Account::onRosterItemRemoved);
-    QObject::connect(&rm, &QXmppRosterManager::itemChanged, this, &Account::onRosterItemChanged);
+    QObject::connect(rm, &QXmppRosterManager::rosterReceived, this, &Account::onRosterReceived);
+    QObject::connect(rm, &QXmppRosterManager::itemAdded, this, &Account::onRosterItemAdded);
+    QObject::connect(rm, &QXmppRosterManager::itemRemoved, this, &Account::onRosterItemRemoved);
+    QObject::connect(rm, &QXmppRosterManager::itemChanged, this, &Account::onRosterItemChanged);
     //QObject::connect(&rm, &QXmppRosterManager::presenceChanged, this, &Account::onRosterPresenceChanged);
     
     client.addExtension(cm);
@@ -82,8 +82,7 @@ Account::Account(const QString& p_login, const QString& p_server, const QString&
     client.addExtension(bm);
     QObject::connect(bm, &QXmppBookmarkManager::bookmarksReceived, this, &Account::bookmarksReceived);
     
-    QXmppVCardManager& vm = client.vCardManager();
-    QObject::connect(&vm, &QXmppVCardManager::vCardReceived, this, &Account::onVCardReceived);
+    QObject::connect(vm, &QXmppVCardManager::vCardReceived, this, &Account::onVCardReceived);
     //QObject::connect(&vm, &QXmppVCardManager::clientVCardReceived, this, &Account::onOwnVCardReceived); //for some reason it doesn't work, launching from common handler
     
     QString path(QStandardPaths::writableLocation(QStandardPaths::CacheLocation));
@@ -240,11 +239,10 @@ QString Core::Account::getServer() const
 
 void Core::Account::onRosterReceived()
 {
-    client.vCardManager().requestClientVCard();         //TODO need to make sure server actually supports vCards
+    vm->requestClientVCard();         //TODO need to make sure server actually supports vCards
     ownVCardRequestInProgress = true;
     
-    QXmppRosterManager& rm = client.rosterManager();
-    QStringList bj = rm.getRosterBareJids();
+    QStringList bj = rm->getRosterBareJids();
     for (int i = 0; i < bj.size(); ++i) {
         const QString& jid = bj[i];
         addedAccount(jid);
@@ -264,8 +262,7 @@ void Core::Account::onRosterItemAdded(const QString& bareJid)
     addedAccount(bareJid);
     std::map<QString, QString>::const_iterator itr = queuedContacts.find(bareJid);
     if (itr != queuedContacts.end()) {
-        QXmppRosterManager& rm = client.rosterManager();
-        rm.subscribe(bareJid, itr->second);
+        rm->subscribe(bareJid, itr->second);
         queuedContacts.erase(itr);
     }
 }
@@ -278,8 +275,7 @@ void Core::Account::onRosterItemChanged(const QString& bareJid)
         return;
     }
     Contact* contact = itr->second;
-    QXmppRosterManager& rm = client.rosterManager();
-    QXmppRosterIq::Item re = rm.getRosterEntry(bareJid);
+    QXmppRosterIq::Item re = rm->getRosterEntry(bareJid);
     
     Shared::SubscriptionState state = castSubscriptionState(re.subscriptionType());
 
@@ -308,9 +304,8 @@ void Core::Account::onRosterItemRemoved(const QString& bareJid)
 
 void Core::Account::addedAccount(const QString& jid)
 {
-    QXmppRosterManager& rm = client.rosterManager();
     std::map<QString, Contact*>::const_iterator itr = contacts.find(jid);
-    QXmppRosterIq::Item re = rm.getRosterEntry(jid);
+    QXmppRosterIq::Item re = rm->getRosterEntry(jid);
     Contact* contact;
     bool newContact = false;
     if (itr == contacts.end()) {
@@ -410,13 +405,13 @@ void Core::Account::onPresenceReceived(const QXmppPresence& p_presence)
                         break;
                     case QXmppPresence::VCardUpdateNoPhoto:         //there is no photo, need to drop if any
                         if (avatarType.size() > 0) {
-                            client.vCardManager().requestClientVCard();
+                            vm->requestClientVCard();
                             ownVCardRequestInProgress = true;
                         }
                         break;
                     case QXmppPresence::VCardUpdateValidPhoto:      //there is a photo, need to load
                         if (avatarHash != p_presence.photoHash()) {
-                            client.vCardManager().requestClientVCard();
+                            vm->requestClientVCard();
                             ownVCardRequestInProgress = true;
                         }
                         break;
@@ -493,7 +488,7 @@ void Core::Account::onRosterPresenceChanged(const QString& bareJid, const QStrin
 {
     //not used for now;
     qDebug() << "presence changed for " << bareJid << " resource " << resource;
-    const QXmppPresence& presence = client.rosterManager().getPresence(bareJid, resource);
+    const QXmppPresence& presence = rm->getPresence(bareJid, resource);
 }
 
 void Core::Account::setLogin(const QString& p_login)
@@ -1053,8 +1048,7 @@ void Core::Account::onClientError(QXmppClient::Error err)
 void Core::Account::subscribeToContact(const QString& jid, const QString& reason)
 {
     if (state == Shared::connected) {
-        QXmppRosterManager& rm = client.rosterManager();
-        rm.subscribe(jid, reason);
+        rm->subscribe(jid, reason);
     } else {
         qDebug() << "An attempt to subscribe account " << name << " to contact " << jid << " but the account is not in the connected state, skipping";
     }
@@ -1063,8 +1057,7 @@ void Core::Account::subscribeToContact(const QString& jid, const QString& reason
 void Core::Account::unsubscribeFromContact(const QString& jid, const QString& reason)
 {
     if (state == Shared::connected) {
-        QXmppRosterManager& rm = client.rosterManager();
-        rm.unsubscribe(jid, reason);
+        rm->unsubscribe(jid, reason);
     } else {
         qDebug() << "An attempt to unsubscribe account " << name << " from contact " << jid << " but the account is not in the connected state, skipping";
     }
@@ -1078,8 +1071,7 @@ void Core::Account::removeContactRequest(const QString& jid)
             outOfRosterContacts.erase(itr);
             onRosterItemRemoved(jid);
         } else {
-            QXmppRosterManager& rm = client.rosterManager();
-            rm.removeItem(jid);
+            rm->removeItem(jid);
         }
     } else {
         qDebug() << "An attempt to remove contact " << jid << " from account " << name << " but the account is not in the connected state, skipping";
@@ -1095,8 +1087,7 @@ void Core::Account::addContactRequest(const QString& jid, const QString& name, c
             qDebug() << "An attempt to add contact " << jid << " to account " << name << " but the account is already queued for adding, skipping";
         } else {
             queuedContacts.insert(std::make_pair(jid, ""));     //TODO need to add reason here;
-            QXmppRosterManager& rm = client.rosterManager();
-            rm.addItem(jid, name, groups);
+            rm->addItem(jid, name, groups);
         }
     } else {
         qDebug() << "An attempt to add contact " << jid << " to account " << name << " but the account is not in the connected state, skipping";
@@ -1273,8 +1264,7 @@ void Core::Account::addContactToGroupRequest(const QString& jid, const QString&
     if (itr == contacts.end()) {
         qDebug() << "An attempt to add non existing contact" << jid << "of account" << name << "to the group" << groupName << ", skipping";
     } else {
-        QXmppRosterManager& rm = client.rosterManager();
-        QXmppRosterIq::Item item = rm.getRosterEntry(jid);
+        QXmppRosterIq::Item item = rm->getRosterEntry(jid);
         QSet<QString> groups = item.groups();
         if (groups.find(groupName) == groups.end()) {           //TODO need to change it, I guess that sort of code is better in qxmpp lib
             groups.insert(groupName);
@@ -1296,8 +1286,7 @@ void Core::Account::removeContactFromGroupRequest(const QString& jid, const QStr
     if (itr == contacts.end()) {
         qDebug() << "An attempt to remove non existing contact" << jid << "of account" << name << "from the group" << groupName << ", skipping";
     } else {
-        QXmppRosterManager& rm = client.rosterManager();
-        QXmppRosterIq::Item item = rm.getRosterEntry(jid);
+        QXmppRosterIq::Item item = rm->getRosterEntry(jid);
         QSet<QString> groups = item.groups();
         QSet<QString>::const_iterator gItr = groups.find(groupName);
         if (gItr != groups.end()) {
@@ -1320,8 +1309,7 @@ void Core::Account::renameContactRequest(const QString& jid, const QString& newN
     if (itr == contacts.end()) {
         qDebug() << "An attempt to rename non existing contact" << jid << "of account" << name << ", skipping";
     } else {
-        QXmppRosterManager& rm = client.rosterManager();
-        rm.renameItem(jid, newName);
+        rm->renameItem(jid, newName);
     }
 }
 
@@ -1519,11 +1507,11 @@ void Core::Account::requestVCard(const QString& jid)
     if (pendingVCardRequests.find(jid) == pendingVCardRequests.end()) {
         if (jid == getLogin() + "@" + getServer()) {
             if (!ownVCardRequestInProgress) {
-                client.vCardManager().requestClientVCard();
+                vm->requestClientVCard();
                 ownVCardRequestInProgress = true;
             }
         } else {
-            client.vCardManager().requestVCard(jid);
+            vm->requestVCard(jid);
             pendingVCardRequests.insert(jid);
         }
     }
@@ -1596,6 +1584,6 @@ void Core::Account::uploadVCard(const Shared::VCard& card)
         }
     }
     
-    client.vCardManager().setClientVCard(iq);
+    vm->setClientVCard(iq);
     onOwnVCardReceived(iq);
 }
diff --git a/core/account.h b/core/account.h
index 371a561..d6444d3 100644
--- a/core/account.h
+++ b/core/account.h
@@ -125,6 +125,8 @@ private:
     QXmppMamManager* am;
     QXmppMucManager* mm;
     QXmppBookmarkManager* bm;
+    QXmppRosterManager* rm;
+    QXmppVCardManager* vm;
     std::map<QString, Contact*> contacts;
     std::map<QString, Conference*> conferences;
     unsigned int maxReconnectTimes;
diff --git a/global.cpp b/global.cpp
index 9beb7a1..e3ffb11 100644
--- a/global.cpp
+++ b/global.cpp
@@ -538,6 +538,8 @@ QDateTime Shared::VCard::getReceivingTime() const
     return receivingTime;
 }
 
+const std::deque<QString>Shared::VCard::Contact::roleNames = {"Not specified", "Personal", "Business"};
+
 QIcon Shared::availabilityIcon(Shared::Availability av, bool big)
 {
     const std::deque<QString>& fallback = QApplication::palette().window().color().lightnessF() > 0.5 ? 
diff --git a/global.h b/global.h
index b046efa..c33bd38 100644
--- a/global.h
+++ b/global.h
@@ -223,6 +223,7 @@ class VCard {
             home,
             work
         };
+        static const std::deque<QString> roleNames;
         
         Contact(Role p_role = none, bool p_prefered = false);
         
diff --git a/ui/CMakeLists.txt b/ui/CMakeLists.txt
index 59a0a4f..50d5304 100644
--- a/ui/CMakeLists.txt
+++ b/ui/CMakeLists.txt
@@ -10,6 +10,8 @@ set(CMAKE_AUTOUIC ON)
 find_package(Qt5Widgets CONFIG REQUIRED)
 find_package(Qt5DBus CONFIG REQUIRED)
 
+add_subdirectory(widgets)
+
 set(squawkUI_SRC
   squawk.cpp
   models/accounts.cpp
@@ -22,14 +24,6 @@ set(squawkUI_SRC
   models/room.cpp
   models/abstractparticipant.cpp
   models/participant.cpp
-  widgets/conversation.cpp
-  widgets/chat.cpp
-  widgets/room.cpp
-  widgets/newcontact.cpp
-  widgets/accounts.cpp
-  widgets/account.cpp
-  widgets/joinconference.cpp
-  widgets/vcard.cpp
   utils/messageline.cpp
   utils//message.cpp
   utils/resizer.cpp
@@ -37,11 +31,13 @@ set(squawkUI_SRC
   utils/flowlayout.cpp
   utils/badge.cpp
   utils/progress.cpp
+  utils/comboboxdelegate.cpp
 )
 
 # Tell CMake to create the helloworld executable
 add_library(squawkUI ${squawkUI_SRC})
 
 # Use the Widgets module from Qt 5.
+target_link_libraries(squawkUI squawkWidgets)
 target_link_libraries(squawkUI Qt5::Widgets)
 target_link_libraries(squawkUI Qt5::DBus)
diff --git a/ui/squawk.cpp b/ui/squawk.cpp
index 5ddd525..0c27fc6 100644
--- a/ui/squawk.cpp
+++ b/ui/squawk.cpp
@@ -47,14 +47,14 @@ Squawk::Squawk(QWidget *parent) :
     }
     m_ui->comboBox->setCurrentIndex(Shared::offline);
     
-    connect(m_ui->actionAccounts, SIGNAL(triggered()), this, SLOT(onAccounts()));
-    connect(m_ui->actionAddContact, SIGNAL(triggered()), this, SLOT(onNewContact()));
-    connect(m_ui->actionAddConference, SIGNAL(triggered()), this, SLOT(onNewConference()));
-    connect(m_ui->comboBox, SIGNAL(activated(int)), this, SLOT(onComboboxActivated(int)));
-    connect(m_ui->roster, SIGNAL(doubleClicked(const QModelIndex&)), this, SLOT(onRosterItemDoubleClicked(const QModelIndex&)));
-    connect(m_ui->roster, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(onRosterContextMenu(const QPoint&)));
+    connect(m_ui->actionAccounts, &QAction::triggered, this, &Squawk::onAccounts);
+    connect(m_ui->actionAddContact, &QAction::triggered, this, &Squawk::onNewContact);
+    connect(m_ui->actionAddConference, &QAction::triggered, this, &Squawk::onNewConference);
+    connect(m_ui->comboBox, qOverload<int>(&QComboBox::activated), this, &Squawk::onComboboxActivated);
+    connect(m_ui->roster, &QTreeView::doubleClicked, this, &Squawk::onRosterItemDoubleClicked);
+    connect(m_ui->roster, &QTreeView::customContextMenuRequested, this, &Squawk::onRosterContextMenu);
     
-    connect(rosterModel.accountsModel, SIGNAL(sizeChanged(unsigned int)), this, SLOT(onAccountsSizeChanged(unsigned int)));
+    connect(rosterModel.accountsModel, &Models::Accounts::sizeChanged, this, &Squawk::onAccountsSizeChanged);
     //m_ui->mainToolBar->addWidget(m_ui->comboBox);
     
     setWindowTitle(tr("Contact list"));
diff --git a/ui/squawk.h b/ui/squawk.h
index bf6582f..819d255 100644
--- a/ui/squawk.h
+++ b/ui/squawk.h
@@ -33,10 +33,10 @@
 #include "widgets/room.h"
 #include "widgets/newcontact.h"
 #include "widgets/joinconference.h"
-#include "widgets/vcard.h"
 #include "models/roster.h"
+#include "widgets/vcard/vcard.h"
 
-#include "../global.h"
+#include "global.h"
 
 namespace Ui {
 class Squawk;
diff --git a/ui/utils/comboboxdelegate.cpp b/ui/utils/comboboxdelegate.cpp
new file mode 100644
index 0000000..824d1cf
--- /dev/null
+++ b/ui/utils/comboboxdelegate.cpp
@@ -0,0 +1,64 @@
+/*
+ * Squawk messenger. 
+ * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+ * 
+ * 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 "comboboxdelegate.h"
+
+ComboboxDelegate::ComboboxDelegate(QObject *parent):
+    QStyledItemDelegate(parent),
+    entries()
+{
+}
+
+
+ComboboxDelegate::~ComboboxDelegate()
+{
+}
+
+
+QWidget* ComboboxDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
+{
+    QComboBox *cb = new QComboBox(parent);
+    
+    for (const std::pair<QString, QIcon> pair : entries) {
+        cb->addItem(pair.second, pair.first);
+    }
+    
+    return cb;
+}
+
+
+void ComboboxDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
+{
+    QComboBox *cb = static_cast<QComboBox*>(editor);
+    int currentIndex = index.data(Qt::EditRole).toInt();
+    if (currentIndex >= 0) {
+        cb->setCurrentIndex(currentIndex);
+    }
+}
+
+
+void ComboboxDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
+{
+    QComboBox *cb = static_cast<QComboBox *>(editor);
+    model->setData(index, cb->currentIndex(), Qt::EditRole);
+}
+
+void ComboboxDelegate::addEntry(const QString& title, const QIcon& icon)
+{
+    entries.emplace_back(title, icon);
+}
diff --git a/ui/utils/comboboxdelegate.h b/ui/utils/comboboxdelegate.h
new file mode 100644
index 0000000..1d23a5c
--- /dev/null
+++ b/ui/utils/comboboxdelegate.h
@@ -0,0 +1,47 @@
+/*
+ * Squawk messenger. 
+ * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+ * 
+ * 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/>.
+ */
+
+#ifndef COMBOBOXDELEGATE_H
+#define COMBOBOXDELEGATE_H
+
+#include <QStyledItemDelegate>
+#include <QComboBox>
+
+#include <deque>
+
+/**
+ * @todo write docs
+ */
+class ComboboxDelegate : public QStyledItemDelegate
+{
+    Q_OBJECT
+public:
+    ComboboxDelegate(QObject *parent = nullptr);
+    ~ComboboxDelegate();
+    
+    QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
+    void setEditorData(QWidget *editor, const QModelIndex &index) const override;
+    void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override;
+    
+    void addEntry(const QString& title, const QIcon& icon = QIcon());
+    
+private:
+    std::deque<std::pair<QString, QIcon>> entries;
+};
+
+#endif // COMBOBOXDELEGATE_H
diff --git a/ui/widgets/CMakeLists.txt b/ui/widgets/CMakeLists.txt
new file mode 100644
index 0000000..29e2dbc
--- /dev/null
+++ b/ui/widgets/CMakeLists.txt
@@ -0,0 +1,29 @@
+cmake_minimum_required(VERSION 3.0)
+project(squawkWidgets)
+
+# Instruct CMake to run moc automatically when needed.
+set(CMAKE_AUTOMOC ON)
+# Instruct CMake to create code from Qt designer ui files
+set(CMAKE_AUTOUIC ON)
+
+# Find the QtWidgets library
+find_package(Qt5Widgets CONFIG REQUIRED)
+
+add_subdirectory(vcard)
+
+set(squawkWidgets_SRC
+  conversation.cpp
+  chat.cpp
+  room.cpp
+  newcontact.cpp
+  accounts.cpp
+  account.cpp
+  joinconference.cpp
+)
+
+# Tell CMake to create the helloworld executable
+add_library(squawkWidgets ${squawkWidgets_SRC})
+
+# Use the Widgets module from Qt 5.
+target_link_libraries(squawkWidgets vCardUI)
+target_link_libraries(squawkWidgets Qt5::Widgets)
diff --git a/ui/widgets/vcard/CMakeLists.txt b/ui/widgets/vcard/CMakeLists.txt
new file mode 100644
index 0000000..4af3d68
--- /dev/null
+++ b/ui/widgets/vcard/CMakeLists.txt
@@ -0,0 +1,21 @@
+cmake_minimum_required(VERSION 3.0)
+project(vCardUI)
+
+# Instruct CMake to run moc automatically when needed.
+set(CMAKE_AUTOMOC ON)
+# Instruct CMake to create code from Qt designer ui files
+set(CMAKE_AUTOUIC ON)
+
+# Find the QtWidgets library
+find_package(Qt5Widgets CONFIG REQUIRED)
+
+set(vCardUI_SRC
+  vcard.cpp
+  emailsmodel.cpp
+)
+
+# Tell CMake to create the helloworld executable
+add_library(vCardUI ${vCardUI_SRC})
+
+# Use the Widgets module from Qt 5.
+target_link_libraries(vCardUI Qt5::Widgets)
diff --git a/ui/widgets/vcard/emailsmodel.cpp b/ui/widgets/vcard/emailsmodel.cpp
new file mode 100644
index 0000000..4a5024f
--- /dev/null
+++ b/ui/widgets/vcard/emailsmodel.cpp
@@ -0,0 +1,145 @@
+/*
+ * Squawk messenger. 
+ * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+ * 
+ * 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 "emailsmodel.h"
+
+UI::VCard::EMailsModel::EMailsModel(bool p_edit, QObject* parent):
+    QAbstractTableModel(parent),
+    edit(p_edit),
+    deque()
+{
+}
+
+int UI::VCard::EMailsModel::columnCount(const QModelIndex& parent) const
+{
+    return 3;
+}
+
+int UI::VCard::EMailsModel::rowCount(const QModelIndex& parent) const
+{
+    return deque.size();
+}
+
+QVariant UI::VCard::EMailsModel::data(const QModelIndex& index, int role) const
+{
+    if (index.isValid()) {
+        int col = index.column();
+        switch (col) {
+            case 0:
+                switch (role) {
+                    case Qt::DisplayRole:
+                        return deque[index.row()].address;
+                    default:
+                        return QVariant();
+                }
+                break;
+            case 1:
+                switch (role) {
+                    case Qt::DisplayRole:
+                        return tr(Shared::VCard::Email::roleNames[deque[index.row()].role].toStdString().c_str());
+                    case Qt::EditRole: 
+                        return deque[index.row()].role;
+                    default:
+                        return QVariant();
+                }
+                break;
+            case 2:
+                switch (role) {
+                    case Qt::DisplayRole:
+                        return QVariant();
+                    case Qt::DecorationRole:
+                        if (deque[index.row()].prefered) {
+                            return Shared::icon("favorite", false);
+                        }
+                        return QVariant();
+                    default:
+                        return QVariant();
+                }
+                break;
+            default:
+                return QVariant();
+        }
+    }
+    return QVariant();
+}
+
+Qt::ItemFlags UI::VCard::EMailsModel::flags(const QModelIndex& index) const
+{
+    Qt::ItemFlags f = QAbstractTableModel::flags(index);
+    if (edit && index.column() != 2) {
+        f = Qt::ItemIsEditable | f;
+    }
+    return  f;
+}
+
+bool UI::VCard::EMailsModel::setData(const QModelIndex& index, const QVariant& value, int role)
+{
+    if (role == Qt::EditRole && checkIndex(index)) {
+        Shared::VCard::Email& item = deque[index.row()];
+        switch (index.column()) {
+            case 0:
+                item.address = value.toString();
+                return true;
+            case 1: {
+                quint8 newRole = value.toUInt();
+                if (newRole > Shared::VCard::Email::work) {
+                    return false;
+                }
+                item.role = static_cast<Shared::VCard::Email::Role>(newRole);
+                return true;
+            }
+            case 2: {
+                bool newDef = value.toBool();
+                if (newDef != item.prefered) {
+                    if (newDef) {
+                        dropPrefered();
+                    }
+                    item.prefered = newDef;
+                    return true;
+                }
+            }
+        }
+        return true;
+    }
+    return false;
+}
+
+
+bool UI::VCard::EMailsModel::dropPrefered()
+{
+    bool dropped = false;
+    int i = 0;
+    for (Shared::VCard::Email& email : deque) {
+        if (email.prefered) {
+            email.prefered = false;
+            QModelIndex ci = createIndex(i, 2, &email);
+            emit dataChanged(ci, ci);
+            dropped = true;
+        }
+        ++i;
+    }
+    return dropped;
+}
+
+QModelIndex UI::VCard::EMailsModel::addNewEmptyLine()
+{
+    beginInsertRows(QModelIndex(), deque.size(), deque.size());
+    deque.emplace_back("");
+    endInsertRows();
+    return createIndex(deque.size() - 1, 0, &(deque.back()));
+}
diff --git a/ui/widgets/vcard/emailsmodel.h b/ui/widgets/vcard/emailsmodel.h
new file mode 100644
index 0000000..ddb0a57
--- /dev/null
+++ b/ui/widgets/vcard/emailsmodel.h
@@ -0,0 +1,57 @@
+/*
+ * Squawk messenger. 
+ * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+ * 
+ * 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/>.
+ */
+
+#ifndef UI_VCARD_EMAILSMODEL_H
+#define UI_VCARD_EMAILSMODEL_H
+
+#include <QAbstractTableModel>
+#include <QIcon>
+
+#include <deque>
+
+#include "global.h"
+
+namespace UI {
+namespace VCard {
+
+class EMailsModel : public QAbstractTableModel
+{
+    Q_OBJECT
+public:
+    EMailsModel(bool edit = false, QObject *parent = nullptr);
+    
+    int rowCount(const QModelIndex& parent = QModelIndex()) const override;
+    int columnCount(const QModelIndex& parent = QModelIndex()) const override;
+    QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
+    bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
+    Qt::ItemFlags flags(const QModelIndex &index) const override;
+    
+public slots:
+    QModelIndex addNewEmptyLine();
+    
+private:
+    bool edit;
+    std::deque<Shared::VCard::Email> deque;
+    
+private:
+    bool dropPrefered();
+};
+
+}}
+
+#endif // UI_VCARD_EMAILSMODEL_H
diff --git a/ui/widgets/vcard.cpp b/ui/widgets/vcard/vcard.cpp
similarity index 82%
rename from ui/widgets/vcard.cpp
rename to ui/widgets/vcard/vcard.cpp
index b092ef8..3f7778e 100644
--- a/ui/widgets/vcard.cpp
+++ b/ui/widgets/vcard/vcard.cpp
@@ -33,7 +33,10 @@ VCard::VCard(const QString& jid, bool edit, QWidget* parent):
     currentAvatarPath(""),
     progress(new Progress(100)),
     progressLabel(new QLabel()),
-    overlay(new QWidget())
+    overlay(new QWidget()),
+    contextMenu(new QMenu()),
+    emails(edit),
+    roleDelegate(new ComboboxDelegate())
 {
     m_ui->setupUi(this);
     m_ui->jabberID->setText(jid);
@@ -48,6 +51,18 @@ VCard::VCard(const QString& jid, bool edit, QWidget* parent):
     setAvatar->setEnabled(true);
     clearAvatar->setEnabled(false);
     
+    roleDelegate->addEntry(tr(Shared::VCard::Email::roleNames[0].toStdString().c_str()));
+    roleDelegate->addEntry(tr(Shared::VCard::Email::roleNames[1].toStdString().c_str()));
+    roleDelegate->addEntry(tr(Shared::VCard::Email::roleNames[2].toStdString().c_str()));
+    
+    m_ui->emailsView->setContextMenuPolicy(Qt::CustomContextMenu);
+    m_ui->emailsView->setModel(&emails);
+    m_ui->emailsView->setItemDelegateForColumn(1, roleDelegate);
+    m_ui->emailsView->horizontalHeader()->setStretchLastSection(false);
+    m_ui->emailsView->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch);
+    
+    connect(m_ui->emailsView, &QWidget::customContextMenuRequested, this, &VCard::onContextMenu);
+    
     if (edit) {
         avatarMenu = new QMenu();
         m_ui->avatarButton->setMenu(avatarMenu);
@@ -69,10 +84,6 @@ VCard::VCard(const QString& jid, bool edit, QWidget* parent):
         m_ui->description->setReadOnly(true);
         m_ui->url->setReadOnly(true);
         m_ui->title->setText(tr("Contact %1 card").arg(jid));
-        
-        m_ui->addAddressButton->hide();
-        m_ui->addPhoneButton->hide();
-        m_ui->addEmailButton->hide();
     }
     
     connect(m_ui->buttonBox, &QDialogButtonBox::accepted, this, &VCard::onButtonBoxAccepted);
@@ -106,6 +117,9 @@ VCard::~VCard()
     if (editable) {
         avatarMenu->deleteLater();
     }
+    
+    roleDelegate->deleteLater();
+    contextMenu->deleteLater();
 }
 
 void VCard::setVCard(const QString& jid, const Shared::VCard& card)
@@ -253,3 +267,46 @@ void VCard::hideProgress()
     overlay->hide();
     progress->stop();
 }
+
+void VCard::onContextMenu(const QPoint& point)
+{
+    contextMenu->clear();
+    bool hasMenu = false;
+    QAbstractItemView* snd = static_cast<QAbstractItemView*>(sender());
+    if (snd == m_ui->emailsView) {
+        if (editable) {
+            hasMenu = true;
+            QAction* add = contextMenu->addAction(Shared::icon("list-add"), tr("Add email address"));
+            connect(add, &QAction::triggered, this, &VCard::onAddEmail);
+        }
+    }
+    
+    if (hasMenu) {
+        contextMenu->popup(snd->viewport()->mapToGlobal(point));
+    }
+}
+
+void VCard::onAddEmail()
+{
+    QModelIndex index = emails.addNewEmptyLine();
+    m_ui->emailsView->setCurrentIndex(index);
+    m_ui->emailsView->edit(index);
+}
+
+void VCard::onAddAddress()
+{
+    
+}
+void VCard::onAddPhone()
+{
+}
+void VCard::onRemoveAddress()
+{
+}
+void VCard::onRemoveEmail()
+{
+}
+
+void VCard::onRemovePhone()
+{
+}
diff --git a/ui/widgets/vcard.h b/ui/widgets/vcard/vcard.h
similarity index 82%
rename from ui/widgets/vcard.h
rename to ui/widgets/vcard/vcard.h
index 4831734..e150ae9 100644
--- a/ui/widgets/vcard.h
+++ b/ui/widgets/vcard/vcard.h
@@ -30,11 +30,14 @@
 #include <QLabel>
 #include <QGraphicsOpacityEffect>
 #include <QVBoxLayout>
+#include <QMenu>
 
 #include <set>
 
-#include "../../global.h"
-#include "../utils/progress.h"
+#include "global.h"
+#include "emailsmodel.h"
+#include "ui/utils/progress.h"
+#include "ui/utils/comboboxdelegate.h"
 
 namespace Ui
 {
@@ -65,6 +68,13 @@ private slots:
     void onClearAvatar();
     void onSetAvatar();
     void onAvatarSelected();
+    void onAddAddress();
+    void onRemoveAddress();
+    void onAddEmail();
+    void onRemoveEmail();
+    void onAddPhone();
+    void onRemovePhone();
+    void onContextMenu(const QPoint& point);
     
 private:
     QScopedPointer<Ui::VCard> m_ui;
@@ -76,6 +86,9 @@ private:
     Progress* progress;
     QLabel* progressLabel;
     QWidget* overlay;
+    QMenu* contextMenu;
+    UI::VCard::EMailsModel emails;
+    ComboboxDelegate* roleDelegate;
     
     static const std::set<QString> supportedTypes;
     
diff --git a/ui/widgets/vcard.ui b/ui/widgets/vcard/vcard.ui
similarity index 87%
rename from ui/widgets/vcard.ui
rename to ui/widgets/vcard/vcard.ui
index fbd0b82..a4381b3 100644
--- a/ui/widgets/vcard.ui
+++ b/ui/widgets/vcard/vcard.ui
@@ -9,8 +9,8 @@
    <rect>
     <x>0</x>
     <y>0</y>
-    <width>526</width>
-    <height>662</height>
+    <width>578</width>
+    <height>671</height>
    </rect>
   </property>
   <layout class="QGridLayout" name="gridLayout_4">
@@ -84,7 +84,7 @@
         <enum>QTabWidget::Rounded</enum>
        </property>
        <property name="currentIndex">
-        <number>0</number>
+        <number>1</number>
        </property>
        <property name="elideMode">
         <enum>Qt::ElideNone</enum>
@@ -560,42 +560,72 @@
              <rect>
               <x>0</x>
               <y>0</y>
-              <width>514</width>
-              <height>488</height>
+              <width>566</width>
+              <height>497</height>
              </rect>
             </property>
-            <layout class="QGridLayout" name="gridLayout_3" columnstretch="1,2,2,1">
-             <item row="2" column="2">
-              <widget class="QPushButton" name="addEmailButton">
-               <property name="sizePolicy">
-                <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
-                 <horstretch>0</horstretch>
-                 <verstretch>0</verstretch>
-                </sizepolicy>
-               </property>
-               <property name="text">
-                <string/>
-               </property>
-               <property name="icon">
-                <iconset theme="list-add"/>
+            <layout class="QGridLayout" name="gridLayout_3" columnstretch="1,3,1,0">
+             <item row="7" column="1">
+              <widget class="Line" name="phonesLine">
+               <property name="orientation">
+                <enum>Qt::Horizontal</enum>
                </property>
               </widget>
              </item>
-             <item row="6" column="1" colspan="2">
-              <layout class="QVBoxLayout" name="phonesLayout">
-               <item>
-                <widget class="QLabel" name="emptyPhonesPlaceholder">
-                 <property name="text">
-                  <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-style:italic;&quot;&gt;User has no contact e-mail addresses&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
-                 </property>
-                 <property name="alignment">
-                  <set>Qt::AlignCenter</set>
-                 </property>
-                </widget>
-               </item>
-              </layout>
+             <item row="3" column="1">
+              <widget class="QTableView" name="emailsView">
+               <property name="selectionMode">
+                <enum>QAbstractItemView::ExtendedSelection</enum>
+               </property>
+               <property name="selectionBehavior">
+                <enum>QAbstractItemView::SelectRows</enum>
+               </property>
+               <property name="showGrid">
+                <bool>false</bool>
+               </property>
+               <attribute name="horizontalHeaderVisible">
+                <bool>false</bool>
+               </attribute>
+               <attribute name="verticalHeaderVisible">
+                <bool>false</bool>
+               </attribute>
+              </widget>
              </item>
-             <item row="11" column="1" colspan="2">
+             <item row="10" column="1">
+              <widget class="Line" name="addressesLine">
+               <property name="orientation">
+                <enum>Qt::Horizontal</enum>
+               </property>
+              </widget>
+             </item>
+             <item row="0" column="0" rowspan="12">
+              <spacer name="contactLeftSpacer">
+               <property name="orientation">
+                <enum>Qt::Horizontal</enum>
+               </property>
+               <property name="sizeHint" stdset="0">
+                <size>
+                 <width>40</width>
+                 <height>20</height>
+                </size>
+               </property>
+              </spacer>
+             </item>
+             <item row="1" column="1">
+              <widget class="Line" name="contactFormLine">
+               <property name="orientation">
+                <enum>Qt::Horizontal</enum>
+               </property>
+              </widget>
+             </item>
+             <item row="4" column="1">
+              <widget class="Line" name="emailsLine">
+               <property name="orientation">
+                <enum>Qt::Horizontal</enum>
+               </property>
+              </widget>
+             </item>
+             <item row="11" column="1">
               <spacer name="contactBottomSpacer">
                <property name="orientation">
                 <enum>Qt::Vertical</enum>
@@ -608,21 +638,43 @@
                </property>
               </spacer>
              </item>
-             <item row="9" column="1" colspan="2">
-              <layout class="QVBoxLayout" name="addressesLayout">
-               <item>
-                <widget class="QLabel" name="emptyAddressesPlaceholder">
-                 <property name="text">
-                  <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-style:italic;&quot;&gt;User has no contact e-mail addresses&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
-                 </property>
-                 <property name="alignment">
-                  <set>Qt::AlignCenter</set>
-                 </property>
-                </widget>
-               </item>
-              </layout>
+             <item row="8" column="1">
+              <widget class="QLabel" name="addressesHeading">
+               <property name="text">
+                <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:16pt; font-weight:600;&quot;&gt;Addresses&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+               </property>
+               <property name="alignment">
+                <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
+               </property>
+              </widget>
              </item>
-             <item row="0" column="1" colspan="2">
+             <item row="6" column="1">
+              <widget class="QTableView" name="phonesView">
+               <property name="selectionBehavior">
+                <enum>QAbstractItemView::SelectRows</enum>
+               </property>
+               <property name="showGrid">
+                <bool>false</bool>
+               </property>
+               <attribute name="horizontalHeaderVisible">
+                <bool>false</bool>
+               </attribute>
+               <attribute name="verticalHeaderVisible">
+                <bool>false</bool>
+               </attribute>
+              </widget>
+             </item>
+             <item row="2" column="1">
+              <widget class="QLabel" name="emailsHeading">
+               <property name="text">
+                <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:16pt; font-weight:600;&quot;&gt;E-Mail addresses&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+               </property>
+               <property name="alignment">
+                <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
+               </property>
+              </widget>
+             </item>
+             <item row="0" column="1">
               <layout class="QFormLayout" name="contactForm">
                <property name="formAlignment">
                 <set>Qt::AlignHCenter|Qt::AlignTop</set>
@@ -681,124 +733,7 @@
                </item>
               </layout>
              </item>
-             <item row="8" column="1">
-              <widget class="QLabel" name="addressesHeading">
-               <property name="text">
-                <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:16pt; font-weight:600;&quot;&gt;Addresses&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
-               </property>
-               <property name="alignment">
-                <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
-               </property>
-              </widget>
-             </item>
-             <item row="1" column="1" colspan="2">
-              <widget class="Line" name="contactFormLine">
-               <property name="orientation">
-                <enum>Qt::Horizontal</enum>
-               </property>
-              </widget>
-             </item>
-             <item row="8" column="2">
-              <widget class="QPushButton" name="addAddressButton">
-               <property name="sizePolicy">
-                <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
-                 <horstretch>0</horstretch>
-                 <verstretch>0</verstretch>
-                </sizepolicy>
-               </property>
-               <property name="text">
-                <string/>
-               </property>
-               <property name="icon">
-                <iconset theme="list-add"/>
-               </property>
-              </widget>
-             </item>
-             <item row="10" column="1" colspan="2">
-              <widget class="Line" name="addressesLine">
-               <property name="orientation">
-                <enum>Qt::Horizontal</enum>
-               </property>
-              </widget>
-             </item>
-             <item row="5" column="2">
-              <widget class="QPushButton" name="addPhoneButton">
-               <property name="sizePolicy">
-                <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
-                 <horstretch>0</horstretch>
-                 <verstretch>0</verstretch>
-                </sizepolicy>
-               </property>
-               <property name="text">
-                <string/>
-               </property>
-               <property name="icon">
-                <iconset theme="list-add"/>
-               </property>
-              </widget>
-             </item>
-             <item row="2" column="1">
-              <widget class="QLabel" name="emailsHeading">
-               <property name="text">
-                <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:16pt; font-weight:600;&quot;&gt;E-Mail addresses&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
-               </property>
-               <property name="alignment">
-                <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
-               </property>
-              </widget>
-             </item>
-             <item row="5" column="1">
-              <widget class="QLabel" name="phenesHeading">
-               <property name="text">
-                <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:16pt; font-weight:600;&quot;&gt;Phone numbers&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
-               </property>
-               <property name="alignment">
-                <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
-               </property>
-              </widget>
-             </item>
-             <item row="4" column="1" colspan="2">
-              <widget class="Line" name="emailsLine">
-               <property name="orientation">
-                <enum>Qt::Horizontal</enum>
-               </property>
-              </widget>
-             </item>
-             <item row="3" column="1" colspan="2">
-              <layout class="QVBoxLayout" name="emailsLayout">
-               <item>
-                <widget class="QLabel" name="emptyEmailsPlaceholder">
-                 <property name="text">
-                  <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-style:italic;&quot;&gt;User has no contact e-mail addresses&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
-                 </property>
-                 <property name="alignment">
-                  <set>Qt::AlignCenter</set>
-                 </property>
-                </widget>
-               </item>
-              </layout>
-             </item>
-             <item row="7" column="1" colspan="2">
-              <widget class="Line" name="phonesLine">
-               <property name="orientation">
-                <enum>Qt::Horizontal</enum>
-               </property>
-              </widget>
-             </item>
-             <item row="0" column="0" rowspan="12">
-              <spacer name="contactLeftSpacer">
-               <property name="orientation">
-                <enum>Qt::Horizontal</enum>
-               </property>
-               <property name="sizeHint" stdset="0">
-                <size>
-                 <width>40</width>
-                 <height>20</height>
-                </size>
-               </property>
-              </spacer>
-             </item>
-             <item row="0" column="3" rowspan="12">
+             <item row="0" column="2" rowspan="12">
               <spacer name="contactRightSpacer">
                <property name="orientation">
                 <enum>Qt::Horizontal</enum>
@@ -811,6 +746,32 @@
                </property>
               </spacer>
              </item>
+             <item row="5" column="1">
+              <widget class="QLabel" name="phenesHeading">
+               <property name="text">
+                <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:16pt; font-weight:600;&quot;&gt;Phone numbers&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+               </property>
+               <property name="alignment">
+                <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
+               </property>
+              </widget>
+             </item>
+             <item row="9" column="1">
+              <widget class="QTableView" name="addressesView">
+               <property name="selectionBehavior">
+                <enum>QAbstractItemView::SelectRows</enum>
+               </property>
+               <property name="showGrid">
+                <bool>false</bool>
+               </property>
+               <attribute name="horizontalHeaderVisible">
+                <bool>false</bool>
+               </attribute>
+               <attribute name="verticalHeaderVisible">
+                <bool>false</bool>
+               </attribute>
+              </widget>
+             </item>
             </layout>
            </widget>
           </widget>
@@ -905,7 +866,6 @@
   <tabstop>jabberID</tabstop>
   <tabstop>url</tabstop>
   <tabstop>description</tabstop>
-  <tabstop>scrollArea</tabstop>
  </tabstops>
  <resources>
   <include location="../../resources/resources.qrc"/>

From 0b57e6a77fc0f83b11ccde1d3785797406cfb16d Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Sun, 3 Nov 2019 21:46:40 +0300
Subject: [PATCH 018/281] Refactoring of signal/slots connection to new qt
 syntax

---
 core/conference.cpp         | 18 ++++++++---------
 core/networkaccess.cpp      |  6 +++---
 signalcatcher.cpp           |  2 +-
 ui/models/accounts.cpp      |  4 ++--
 ui/models/contact.cpp       |  8 ++++----
 ui/models/group.cpp         |  4 ++--
 ui/models/item.cpp          | 28 +++++++++++++-------------
 ui/models/room.cpp          |  1 -
 ui/squawk.cpp               | 40 ++++++++++++++++++-------------------
 ui/utils/badge.cpp          |  2 +-
 ui/utils/message.cpp        |  2 +-
 ui/utils/messageline.cpp    |  2 +-
 ui/widgets/accounts.cpp     | 21 ++++++++++---------
 ui/widgets/chat.cpp         |  2 +-
 ui/widgets/conversation.cpp | 22 ++++++++++----------
 ui/widgets/room.cpp         |  2 +-
 16 files changed, 81 insertions(+), 83 deletions(-)

diff --git a/core/conference.cpp b/core/conference.cpp
index 1305419..56a1e8f 100644
--- a/core/conference.cpp
+++ b/core/conference.cpp
@@ -30,15 +30,15 @@ Core::Conference::Conference(const QString& p_jid, const QString& p_account, boo
     muc = true;
     name = p_name;
     
-    connect(room, SIGNAL(joined()), this, SLOT(onRoomJoined()));
-    connect(room, SIGNAL(left()), this, SLOT(onRoomLeft()));
-    connect(room, SIGNAL(nameChanged(const QString&)), this, SLOT(onRoomNameChanged(const QString&)));
-    connect(room, SIGNAL(subjectChanged(const QString&)), this, SLOT(onRoomSubjectChanged(const QString&)));
-    connect(room, SIGNAL(participantAdded(const QString&)), this, SLOT(onRoomParticipantAdded(const QString&)));
-    connect(room, SIGNAL(participantChanged(const QString&)), this, SLOT(onRoomParticipantChanged(const QString&)));
-    connect(room, SIGNAL(participantRemoved(const QString&)), this, SLOT(onRoomParticipantRemoved(const QString&)));
-    connect(room, SIGNAL(nickNameChanged(const QString&)), this, SLOT(onRoomNickNameChanged(const QString&)));
-    connect(room, SIGNAL(error(const QXmppStanza::Error&)), this, SLOT(onRoomError(const QXmppStanza::Error&)));
+    connect(room, &QXmppMucRoom::joined, this, &Conference::onRoomJoined);
+    connect(room, &QXmppMucRoom::left, this, &Conference::onRoomLeft);
+    connect(room, &QXmppMucRoom::nameChanged, this, &Conference::onRoomNameChanged);
+    connect(room, &QXmppMucRoom::subjectChanged, this, &Conference::onRoomSubjectChanged);
+    connect(room, &QXmppMucRoom::participantAdded, this, &Conference::onRoomParticipantAdded);
+    connect(room, &QXmppMucRoom::participantChanged, this, &Conference::onRoomParticipantChanged);
+    connect(room, &QXmppMucRoom::participantRemoved, this, &Conference::onRoomParticipantRemoved);
+    connect(room, &QXmppMucRoom::nickNameChanged, this, &Conference::onRoomNickNameChanged);
+    connect(room, &QXmppMucRoom::error, this, &Conference::onRoomError);
     
     room->setNickName(nick);
     if (autoJoin) {
diff --git a/core/networkaccess.cpp b/core/networkaccess.cpp
index 002f9d7..090f4e7 100644
--- a/core/networkaccess.cpp
+++ b/core/networkaccess.cpp
@@ -323,9 +323,9 @@ void Core::NetworkAccess::startDownload(const QString& messageId, const QString&
     Download* dwn = new Download({{messageId}, 0, 0, true});
     QNetworkRequest req(url);
     dwn->reply = manager->get(req);
-    connect(dwn->reply, SIGNAL(downloadProgress(qint64, qint64)), SLOT(onDownloadProgress(qint64, qint64)));
-    connect(dwn->reply, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(onRequestError(QNetworkReply::NetworkError)));
-    connect(dwn->reply, SIGNAL(finished()), SLOT(onRequestFinished()));
+    connect(dwn->reply, &QNetworkReply::downloadProgress, this, &NetworkAccess::onDownloadProgress);
+    connect(dwn->reply, qOverload<QNetworkReply::NetworkError>(&QNetworkReply::error), this, &NetworkAccess::onRequestError);
+    connect(dwn->reply, &QNetworkReply::finished, this, &NetworkAccess::onRequestFinished);
     downloads.insert(std::make_pair(url, dwn));
     emit downloadFileProgress(messageId, 0);
 }
diff --git a/signalcatcher.cpp b/signalcatcher.cpp
index 3460d0e..9ac3aae 100644
--- a/signalcatcher.cpp
+++ b/signalcatcher.cpp
@@ -38,7 +38,7 @@ SignalCatcher::SignalCatcher(QCoreApplication *p_app, QObject *parent):
     }
     
     snInt = new QSocketNotifier(sigintFd[1], QSocketNotifier::Read, this);
-    connect(snInt, SIGNAL(activated(int)), this, SLOT(handleSigInt()));
+    connect(snInt, &QSocketNotifier::activated, this, &SignalCatcher::handleSigInt);
 }
 
 SignalCatcher::~SignalCatcher()
diff --git a/ui/models/accounts.cpp b/ui/models/accounts.cpp
index b7f16ef..79ed159 100644
--- a/ui/models/accounts.cpp
+++ b/ui/models/accounts.cpp
@@ -89,7 +89,7 @@ void Models::Accounts::addAccount(Account* account)
     }
     
     accs.insert(before, account);
-    connect(account, SIGNAL(childChanged(Models::Item*, int, int)), this, SLOT(onAccountChanged(Models::Item*, int, int)));
+    connect(account, &Account::childChanged, this, &Accounts::onAccountChanged);
     endInsertRows();
     
     emit sizeChanged(accs.size());
@@ -143,7 +143,7 @@ void Models::Accounts::removeAccount(int index)
 {
     Account* account = accs[index];
     beginRemoveRows(QModelIndex(), index, index);
-    disconnect(account, SIGNAL(childChanged(Models::Item*, int, int)), this, SLOT(onAccountChanged(Models::Item*, int, int)));
+    disconnect(account, &Account::childChanged, this, &Accounts::onAccountChanged);
     accs.erase(accs.begin() + index);
     endRemoveRows();
     
diff --git a/ui/models/contact.cpp b/ui/models/contact.cpp
index 9b5b436..eee6b4e 100644
--- a/ui/models/contact.cpp
+++ b/ui/models/contact.cpp
@@ -229,7 +229,7 @@ void Models::Contact::refresh()
 void Models::Contact::_removeChild(int index)
 {
     Item* child = childItems[index];
-    disconnect(child, SIGNAL(childChanged(Models::Item*, int, int)), this, SLOT(refresh()));
+    disconnect(child, &Item::childChanged, this, &Contact::refresh);
     Item::_removeChild(index);
     refresh();
 }
@@ -237,7 +237,7 @@ void Models::Contact::_removeChild(int index)
 void Models::Contact::appendChild(Models::Item* child)
 {
     Item::appendChild(child);
-    connect(child, SIGNAL(childChanged(Models::Item*, int, int)), this, SLOT(refresh()));
+    connect(child, &Item::childChanged, this, &Contact::refresh);
     refresh();
 }
 
@@ -324,7 +324,7 @@ void Models::Contact::toOfflineState()
     emit childIsAboutToBeRemoved(this, 0, childItems.size());
     for (int i = 0; i < childItems.size(); ++i) {
         Item* item = childItems[i];
-        disconnect(item, SIGNAL(childChanged(Models::Item*, int, int)), this, SLOT(refresh()));
+        disconnect(item, &Item::childChanged, this, &Contact::refresh);
         Item::_removeChild(i);
         item->deleteLater();
     }
@@ -363,7 +363,7 @@ Models::Contact::Contact(const Models::Contact& other):
         Presence* pCopy = new Presence(*pres);
         presences.insert(pCopy->getName(), pCopy);
         Item::appendChild(pCopy);
-        connect(pCopy, SIGNAL(childChanged(Models::Item*, int, int)), this, SLOT(refresh()));
+        connect(pCopy, &Item::childChanged, this, &Contact::refresh);
     }
     
     refresh();
diff --git a/ui/models/group.cpp b/ui/models/group.cpp
index d857710..5e4411e 100644
--- a/ui/models/group.cpp
+++ b/ui/models/group.cpp
@@ -32,7 +32,7 @@ Models::Group::~Group()
 void Models::Group::appendChild(Models::Item* child)
 {
     Item::appendChild(child);
-    connect(child, SIGNAL(childChanged(Models::Item*, int, int)), this, SLOT(refresh()));
+    connect(child, &Item::childChanged, this, &Group::refresh);
     changed(1);
     refresh();
 }
@@ -59,7 +59,7 @@ QVariant Models::Group::data(int column) const
 void Models::Group::_removeChild(int index)
 {
     Item* child = childItems[index];
-    disconnect(child, SIGNAL(childChanged(Models::Item*, int, int)), this, SLOT(refresh()));
+    disconnect(child, &Item::childChanged, this, &Group::refresh);
     Item::_removeChild(index);
     changed(1);
     refresh();
diff --git a/ui/models/item.cpp b/ui/models/item.cpp
index 7ab877a..f90f5e6 100644
--- a/ui/models/item.cpp
+++ b/ui/models/item.cpp
@@ -88,13 +88,13 @@ void Models::Item::appendChild(Models::Item* child)
     childItems.insert(before, child);
     child->parent = this;
     
-    QObject::connect(child, SIGNAL(childChanged(Models::Item*, int, int)), this, SIGNAL(childChanged(Models::Item*, int, int)));
-    QObject::connect(child, SIGNAL(childIsAboutToBeInserted(Item*, int, int)), this, SIGNAL(childIsAboutToBeInserted(Item*, int, int)));
-    QObject::connect(child, SIGNAL(childInserted()), this, SIGNAL(childInserted()));
-    QObject::connect(child, SIGNAL(childIsAboutToBeRemoved(Item*, int, int)), this, SIGNAL(childIsAboutToBeRemoved(Item*, int, int)));
-    QObject::connect(child, SIGNAL(childRemoved()), this, SIGNAL(childRemoved()));
-    QObject::connect(child, SIGNAL(childIsAboutToBeMoved(Item*, int, int, Item*, int)), this, SIGNAL(childIsAboutToBeMoved(Item*, int, int, Item*, int)));
-    QObject::connect(child, SIGNAL(childMoved()), this, SIGNAL(childMoved()));
+    QObject::connect(child, &Item::childChanged, this, &Item::childChanged);
+    QObject::connect(child, &Item::childIsAboutToBeInserted, this, &Item::childIsAboutToBeInserted);
+    QObject::connect(child, &Item::childInserted, this, &Item::childInserted);
+    QObject::connect(child, &Item::childIsAboutToBeRemoved, this, &Item::childIsAboutToBeRemoved);
+    QObject::connect(child, &Item::childRemoved, this, &Item::childRemoved);
+    QObject::connect(child, &Item::childIsAboutToBeMoved, this, &Item::childIsAboutToBeMoved);
+    QObject::connect(child, &Item::childMoved, this, &Item::childMoved);
     
     if (moving) {
         emit childMoved();
@@ -168,13 +168,13 @@ void Models::Item::_removeChild(int index)
 {
     Item* child = childItems[index];
     
-    QObject::disconnect(child, SIGNAL(childChanged(Models::Item*, int, int)), this, SLOT(onChildChanged(Models::Item*, int, int)));
-    QObject::disconnect(child, SIGNAL(childIsAboutToBeInserted(Item*, int, int)), this, SIGNAL(childIsAboutToBeInserted(Item*, int, int)));
-    QObject::disconnect(child, SIGNAL(childInserted()), this, SIGNAL(childInserted()));
-    QObject::disconnect(child, SIGNAL(childIsAboutToBeRemoved(Item*, int, int)), this, SIGNAL(childIsAboutToBeRemoved(Item*, int, int)));
-    QObject::disconnect(child, SIGNAL(childRemoved()), this, SIGNAL(childRemoved()));
-    QObject::disconnect(child, SIGNAL(childIsAboutToBeMoved(Item*, int, int, Item*, int)), this, SIGNAL(childIsAboutToBeMoved(Item*, int, int, Item*, int)));
-    QObject::disconnect(child, SIGNAL(childMoved()), this, SIGNAL(childMoved()));
+    QObject::disconnect(child, &Item::childChanged, this, &Item::childChanged);
+    QObject::disconnect(child, &Item::childIsAboutToBeInserted, this, &Item::childIsAboutToBeInserted);
+    QObject::disconnect(child, &Item::childInserted, this, &Item::childInserted);
+    QObject::disconnect(child, &Item::childIsAboutToBeRemoved, this, &Item::childIsAboutToBeRemoved);
+    QObject::disconnect(child, &Item::childRemoved, this, &Item::childRemoved);
+    QObject::disconnect(child, &Item::childIsAboutToBeMoved, this, &Item::childIsAboutToBeMoved);
+    QObject::disconnect(child, &Item::childMoved, this, &Item::childMoved);
     
     childItems.erase(childItems.begin() + index);
     child->parent = 0;
diff --git a/ui/models/room.cpp b/ui/models/room.cpp
index 6addead..204b2b5 100644
--- a/ui/models/room.cpp
+++ b/ui/models/room.cpp
@@ -238,7 +238,6 @@ void Models::Room::toOfflineState()
     emit childIsAboutToBeRemoved(this, 0, childItems.size());
     for (int i = 0; i < childItems.size(); ++i) {
         Item* item = childItems[i];
-        disconnect(item, SIGNAL(childChanged(Models::Item*, int, int)), this, SLOT(refresh()));
         Item::_removeChild(i);
         item->deleteLater();
     }
diff --git a/ui/squawk.cpp b/ui/squawk.cpp
index 0c27fc6..396eeb6 100644
--- a/ui/squawk.cpp
+++ b/ui/squawk.cpp
@@ -69,12 +69,12 @@ void Squawk::onAccounts()
     if (accounts == 0) {
         accounts = new Accounts(rosterModel.accountsModel, this);
         accounts->setAttribute(Qt::WA_DeleteOnClose);
-        connect(accounts, SIGNAL(destroyed(QObject*)), this, SLOT(onAccountsClosed(QObject*)));
-        connect(accounts, SIGNAL(newAccount(const QMap<QString, QVariant>&)), this, SIGNAL(newAccountRequest(const QMap<QString, QVariant>&)));
-        connect(accounts, SIGNAL(changeAccount(const QString&, const QMap<QString, QVariant>&)), this, SIGNAL(modifyAccountRequest(const QString&, const QMap<QString, QVariant>&)));
-        connect(accounts, SIGNAL(connectAccount(const QString&)), this, SIGNAL(connectAccount(const QString&)));
-        connect(accounts, SIGNAL(disconnectAccount(const QString&)), this, SIGNAL(disconnectAccount(const QString&)));
-        connect(accounts, SIGNAL(removeAccount(const QString&)), this, SIGNAL(removeAccountRequest(const QString&)));
+        connect(accounts, &Accounts::destroyed, this, &Squawk::onAccountsClosed);
+        connect(accounts, &Accounts::newAccount, this, &Squawk::newAccountRequest);
+        connect(accounts, &Accounts::changeAccount, this, &Squawk::modifyAccountRequest);
+        connect(accounts, &Accounts::connectAccount, this, &Squawk::connectAccount);
+        connect(accounts, &Accounts::disconnectAccount, this, &Squawk::disconnectAccount);
+        connect(accounts, &Accounts::removeAccount, this, &Squawk::removeAccountRequest);
         
         accounts->show();
     } else {
@@ -99,8 +99,8 @@ void Squawk::onNewContact()
 {
     NewContact* nc = new NewContact(rosterModel.accountsModel, this);
     
-    connect(nc, SIGNAL(accepted()), this, SLOT(onNewContactAccepted()));
-    connect(nc, SIGNAL(rejected()), nc, SLOT(deleteLater()));
+    connect(nc, &NewContact::accepted, this, &Squawk::onNewContactAccepted);
+    connect(nc, &NewContact::rejected, nc, &NewContact::deleteLater);
     
     nc->exec();
 }
@@ -109,8 +109,8 @@ void Squawk::onNewConference()
 {
     JoinConference* jc = new JoinConference(rosterModel.accountsModel, this);
     
-    connect(jc, SIGNAL(accepted()), this, SLOT(onJoinConferenceAccepted()));
-    connect(jc, SIGNAL(rejected()), jc, SLOT(deleteLater()));
+    connect(jc, &JoinConference::accepted, this, &Squawk::onJoinConferenceAccepted);
+    connect(jc, &JoinConference::rejected, jc, &JoinConference::deleteLater);
     
     jc->exec();
 }
@@ -299,12 +299,12 @@ void Squawk::onRosterItemDoubleClicked(const QModelIndex& item)
                 if (created) {
                     conv->setAttribute(Qt::WA_DeleteOnClose);
                     
-                    connect(conv, SIGNAL(destroyed(QObject*)), this, SLOT(onConversationClosed(QObject*)));
-                    connect(conv, SIGNAL(sendMessage(const Shared::Message&)), this, SLOT(onConversationMessage(const Shared::Message&)));
-                    connect(conv, SIGNAL(requestArchive(const QString&)), this, SLOT(onConversationRequestArchive(const QString&)));
-                    connect(conv, SIGNAL(requestLocalFile(const QString&, const QString&)), this, SLOT(onConversationRequestLocalFile(const QString&, const QString&)));
-                    connect(conv, SIGNAL(downloadFile(const QString&, const QString&)), this, SLOT(onConversationDownloadFile(const QString&, const QString&)));
-                    connect(conv, SIGNAL(shown()), this, SLOT(onConversationShown()));
+                    connect(conv, &Conversation::destroyed, this, &Squawk::onConversationClosed);
+                    connect(conv, &Conversation::sendMessage, this, &Squawk::onConversationMessage);
+                    connect(conv, &Conversation::requestArchive, this, &Squawk::onConversationRequestArchive);
+                    connect(conv, &Conversation::requestLocalFile, this, &Squawk::onConversationRequestLocalFile);
+                    connect(conv, &Conversation::downloadFile, this, &Squawk::onConversationDownloadFile);
+                    connect(conv, &Conversation::shown, this, &Squawk::onConversationShown);
                     
                     conversations.insert(std::make_pair(*id, conv));
                     
@@ -517,10 +517,10 @@ void Squawk::removeAccount(const QString& account)
             Conversations::const_iterator lItr = itr;
             ++itr;
             Conversation* conv = lItr->second;
-            disconnect(conv, SIGNAL(destroyed(QObject*)), this, SLOT(onConversationClosed(QObject*)));
-            disconnect(conv, SIGNAL(sendMessage(const Shared::Message&)), this, SLOT(onConversationMessage(const Shared::Message&)));
-            disconnect(conv, SIGNAL(requestArchive(const QString&)), this, SLOT(onConversationRequestArchive(const QString&)));
-            disconnect(conv, SIGNAL(shown()), this, SLOT(onConversationShown()));
+            disconnect(conv, &Conversation::destroyed, this, &Squawk::onConversationClosed);
+            disconnect(conv, &Conversation::sendMessage, this, &Squawk::onConversationMessage);
+            disconnect(conv, &Conversation::requestArchive, this, &Squawk::onConversationRequestArchive);
+            disconnect(conv, &Conversation::shown, this, &Squawk::onConversationShown);
             conv->close();
             conversations.erase(lItr);
         } else {
diff --git a/ui/utils/badge.cpp b/ui/utils/badge.cpp
index 94277f2..ef15bd2 100644
--- a/ui/utils/badge.cpp
+++ b/ui/utils/badge.cpp
@@ -42,7 +42,7 @@ Badge::Badge(const QString& p_id, const QString& p_text, const QIcon& icon, QWid
     
     layout->setContentsMargins(2, 2, 2, 2);
     
-    connect(closeButton, SIGNAL(clicked()), this, SIGNAL(close()));
+    connect(closeButton, &QPushButton::clicked, this, &Badge::close);
 }
 
 Badge::~Badge()
diff --git a/ui/utils/message.cpp b/ui/utils/message.cpp
index 2498d84..951037a 100644
--- a/ui/utils/message.cpp
+++ b/ui/utils/message.cpp
@@ -125,7 +125,7 @@ void Message::addDownloadDialog()
             fileComment->setText(tr("%1 is offering you to download a file").arg(sender->text()));
         }
         fileComment->show();
-        connect(downloadButton, SIGNAL(clicked()), this, SLOT(onDownload()));
+        connect(downloadButton, &QPushButton::clicked, this, &Message::onDownload);
         bodyLayout->insertWidget(2, fileComment);
         bodyLayout->insertWidget(3, downloadButton);
         hasDownloadButton = true;
diff --git a/ui/utils/messageline.cpp b/ui/utils/messageline.cpp
index 57894e8..06efa85 100644
--- a/ui/utils/messageline.cpp
+++ b/ui/utils/messageline.cpp
@@ -127,7 +127,7 @@ MessageLine::Position MessageLine::message(const Shared::Message& msg)
     
     if (msg.hasOutOfBandUrl()) {\
         emit requestLocalFile(msg.getId(), msg.getOutOfBandUrl());
-        connect(message, SIGNAL(downloadFile(const QString&, const QString&)), this, SIGNAL(downloadFile(const QString&, const QString&)));
+        connect(message, &Message::downloadFile, this, &MessageLine::downloadFile);
     }
     
     return res;
diff --git a/ui/widgets/accounts.cpp b/ui/widgets/accounts.cpp
index cb526cf..62e9ed3 100644
--- a/ui/widgets/accounts.cpp
+++ b/ui/widgets/accounts.cpp
@@ -29,14 +29,13 @@ Accounts::Accounts(Models::Accounts* p_model, QWidget *parent) :
 {
     m_ui->setupUi(this);
     
-    connect(m_ui->addButton, SIGNAL(clicked(bool)), this, SLOT(onAddButton(bool)));
-    connect(m_ui->editButton, SIGNAL(clicked(bool)), this, SLOT(onEditButton(bool)));
-    connect(m_ui->connectButton, SIGNAL(clicked(bool)), this, SLOT(onConnectButton(bool)));
-    connect(m_ui->deleteButton, SIGNAL(clicked(bool)), this, SLOT(onDeleteButton(bool)));
+    connect(m_ui->addButton, &QPushButton::clicked, this, &Accounts::onAddButton);
+    connect(m_ui->editButton, &QPushButton::clicked, this, &Accounts::onEditButton);
+    connect(m_ui->connectButton, &QPushButton::clicked, this, &Accounts::onConnectButton);
+    connect(m_ui->deleteButton, &QPushButton::clicked, this, &Accounts::onDeleteButton);
     m_ui->tableView->setModel(model);
-    connect(m_ui->tableView->selectionModel(), SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)),
-            this, SLOT(onSelectionChanged(const QItemSelection&, const QItemSelection&)));
-    connect(p_model, SIGNAL(changed()), this, SLOT(updateConnectButton()));
+    connect(m_ui->tableView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &Accounts::onSelectionChanged);
+    connect(p_model, &Models::Accounts::changed, this, &Accounts::updateConnectButton);
 }
 
 Accounts::~Accounts() = default;
@@ -44,8 +43,8 @@ Accounts::~Accounts() = default;
 void Accounts::onAddButton(bool clicked)
 {
     Account* acc = new Account();
-    connect(acc, SIGNAL(accepted()), this, SLOT(onAccountAccepted()));
-    connect(acc, SIGNAL(rejected()), this, SLOT(onAccountRejected()));
+    connect(acc, &Account::accepted, this, &Accounts::onAccountAccepted);
+    connect(acc, &Account::rejected, this, &Accounts::onAccountRejected);
     acc->exec();
 }
 
@@ -84,8 +83,8 @@ void Accounts::onEditButton(bool clicked)
         {"resource", mAcc->getResource()}
     });
     acc->lockId();
-    connect(acc, SIGNAL(accepted()), this, SLOT(onAccountAccepted()));
-    connect(acc, SIGNAL(rejected()), this, SLOT(onAccountRejected()));
+    connect(acc, &Account::accepted, this, &Accounts::onAccountAccepted);
+    connect(acc, &Account::rejected, this, &Accounts::onAccountRejected);
     editing = true;
     acc->exec();
 }
diff --git a/ui/widgets/chat.cpp b/ui/widgets/chat.cpp
index 5e0b390..c876679 100644
--- a/ui/widgets/chat.cpp
+++ b/ui/widgets/chat.cpp
@@ -26,7 +26,7 @@ Chat::Chat(Models::Contact* p_contact, QWidget* parent):
     updateState();
     setStatus(p_contact->getStatus());
     
-    connect(contact, SIGNAL(childChanged(Models::Item*, int, int)), this, SLOT(onContactChanged(Models::Item*, int, int)));
+    connect(contact, &Models::Contact::childChanged, this, &Chat::onContactChanged);
     
     line->setMyName(p_contact->getAccountName());
 }
diff --git a/ui/widgets/conversation.cpp b/ui/widgets/conversation.cpp
index c281258..36e7b6e 100644
--- a/ui/widgets/conversation.cpp
+++ b/ui/widgets/conversation.cpp
@@ -59,15 +59,15 @@ Conversation::Conversation(bool muc, const QString& mJid, const QString mRes, co
     statusIcon = m_ui->statusIcon;
     statusLabel = m_ui->statusLabel;
     
-    connect(&ker, SIGNAL(enterPressed()), this, SLOT(onEnterPressed()));
-    connect(&res, SIGNAL(resized()), this, SLOT(onScrollResize()));
-    connect(&vis, SIGNAL(shown()), this, SLOT(onScrollResize()));
-    connect(&vis, SIGNAL(hidden()), this, SLOT(onScrollResize()));
-    connect(m_ui->sendButton, SIGNAL(clicked(bool)), this, SLOT(onEnterPressed()));
-    connect(line, SIGNAL(resize(int)), this, SLOT(onMessagesResize(int)));
-    connect(line, SIGNAL(downloadFile(const QString&, const QString&)), this, SIGNAL(downloadFile(const QString&, const QString&)));
-    connect(line, SIGNAL(requestLocalFile(const QString&, const QString&)), this, SIGNAL(requestLocalFile(const QString&, const QString&)));
-    connect(m_ui->attachButton, SIGNAL(clicked(bool)), this, SLOT(onAttach()));
+    connect(&ker, &KeyEnterReceiver::enterPressed, this, &Conversation::onEnterPressed);
+    connect(&res, &Resizer::resized, this, &Conversation::onScrollResize);
+    connect(&vis, &VisibilityCatcher::shown, this, &Conversation::onScrollResize);
+    connect(&vis, &VisibilityCatcher::hidden, this, &Conversation::onScrollResize);
+    connect(m_ui->sendButton, &QPushButton::clicked, this, &Conversation::onEnterPressed);
+    connect(line, &MessageLine::resize, this, &Conversation::onMessagesResize);
+    connect(line, &MessageLine::downloadFile, this, &Conversation::downloadFile);
+    connect(line, &MessageLine::requestLocalFile, this, &Conversation::requestLocalFile);
+    connect(m_ui->attachButton, &QPushButton::clicked, this, &Conversation::onAttach);
     
     m_ui->messageEditor->installEventFilter(&ker);
     
@@ -76,7 +76,7 @@ Conversation::Conversation(bool muc, const QString& mJid, const QString mRes, co
     vs->installEventFilter(&vis);
     vs->setBackgroundRole(QPalette::Base);
     vs->setAutoFillBackground(true);
-    connect(vs, SIGNAL(valueChanged(int)), this, SLOT(onSliderValueChanged(int)));
+    connect(vs, &QScrollBar::valueChanged, this, &Conversation::onSliderValueChanged);
     m_ui->scrollArea->installEventFilter(&res);
     
     applyVisualEffects();
@@ -317,7 +317,7 @@ void Conversation::addAttachedFile(const QString& path)
     
     Badge* badge = new Badge(path, info.fileName(), QIcon::fromTheme(type.iconName()));
     
-    connect(badge, SIGNAL(close()), this, SLOT(onBadgeClose()));
+    connect(badge, &Badge::close, this, &Conversation::onBadgeClose);
     filesToAttach.push_back(badge);                                                         //TODO neet to check if there are any duplicated ids
     filesLayout->addWidget(badge);
     if (filesLayout->count() == 1) {
diff --git a/ui/widgets/room.cpp b/ui/widgets/room.cpp
index b1f9c9c..98fc97d 100644
--- a/ui/widgets/room.cpp
+++ b/ui/widgets/room.cpp
@@ -26,7 +26,7 @@ Room::Room(Models::Room* p_room, QWidget* parent):
     line->setMyName(room->getNick());
     setStatus(room->getSubject());
     
-    connect(room, SIGNAL(childChanged(Models::Item*, int, int)), this, SLOT(onRoomChanged(Models::Item*, int, int)));
+    connect(room, &Models::Room::childChanged, this, &Room::onRoomChanged);
 }
 
 Room::~Room()

From 5bbacad84a82f55ccf678ce38573c1bb0104431e Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Mon, 4 Nov 2019 18:22:39 +0300
Subject: [PATCH 019/281] VCard: email list now displays and stores on server
 vcard

---
 core/account.cpp                 | 56 ++++++++++++++++++++++++++++++++
 global.cpp                       | 30 +++++++++++++++++
 global.h                         |  6 ++++
 ui/utils/comboboxdelegate.cpp    | 23 ++++++++++++-
 ui/utils/comboboxdelegate.h      | 10 ++++++
 ui/widgets/vcard/emailsmodel.cpp | 55 +++++++++++++++++++++++++++++++
 ui/widgets/vcard/emailsmodel.h   |  6 ++++
 ui/widgets/vcard/vcard.cpp       | 46 +++++++++++++++++++++++++-
 ui/widgets/vcard/vcard.ui        | 10 +++---
 9 files changed, 235 insertions(+), 7 deletions(-)

diff --git a/core/account.cpp b/core/account.cpp
index ff8c334..ed6da44 100644
--- a/core/account.cpp
+++ b/core/account.cpp
@@ -1361,6 +1361,23 @@ void Core::Account::onVCardReceived(const QXmppVCardIq& card)
     vCard.setOrgUnit(org.unit());
     vCard.setOrgTitle(org.title());
     
+    QList<QXmppVCardEmail> emails = card.emails();
+    std::deque<Shared::VCard::Email>& myEmails = vCard.getEmails();
+    for (const QXmppVCardEmail& em : emails) {
+        Shared::VCard::Email mEm(em.address());
+        QXmppVCardEmail::Type et = em.type();
+        if (et & QXmppVCardEmail::Preferred) {
+            mEm.prefered = true;
+        }
+        if (et & QXmppVCardEmail::Home && !(et & QXmppVCardEmail::Work)) {
+            mEm.role = Shared::VCard::Email::home;
+        } else if (!(et & QXmppVCardEmail::Home) && et & QXmppVCardEmail::Work) {
+            mEm.role = Shared::VCard::Email::work;
+        }
+        
+        myEmails.emplace_back(mEm);
+    }
+    
     if (item->hasAvatar()) {
         if (!item->isAvatarAutoGenerated()) {
             vCard.setAvatarType(Shared::Avatar::valid);
@@ -1483,6 +1500,24 @@ void Core::Account::onOwnVCardReceived(const QXmppVCardIq& card)
         vCard.setAvatarType(Shared::Avatar::empty);
     }
     
+    
+    QList<QXmppVCardEmail> emails = card.emails();
+    std::deque<Shared::VCard::Email>& myEmails = vCard.getEmails();
+    for (const QXmppVCardEmail& em : emails) {
+        Shared::VCard::Email mEm(em.address());
+        QXmppVCardEmail::Type et = em.type();
+        if (et & QXmppVCardEmail::Preferred) {
+            mEm.prefered = true;
+        }
+        if (et & QXmppVCardEmail::Home && !(et & QXmppVCardEmail::Work)) {
+            mEm.role = Shared::VCard::Email::home;
+        } else if (!(et & QXmppVCardEmail::Home) && et & QXmppVCardEmail::Work) {
+            mEm.role = Shared::VCard::Email::work;
+        }
+        
+        myEmails.emplace_back(mEm);
+    }
+    
     emit receivedVCard(getLogin() + "@" + getServer(), vCard);
 }
 
@@ -1535,6 +1570,27 @@ void Core::Account::uploadVCard(const Shared::VCard& card)
     org.setTitle(card.getOrgTitle());
     iq.setOrganization(org);
     
+    const std::deque<Shared::VCard::Email>& myEmails = card.getEmails();
+    QList<QXmppVCardEmail> emails;
+    for (const Shared::VCard::Email& mEm : myEmails) {
+        QXmppVCardEmail em;
+        QXmppVCardEmail::Type t = QXmppVCardEmail::Internet;
+        if (mEm.prefered) {
+            t = t | QXmppVCardEmail::Preferred;
+        }
+        if (mEm.role == Shared::VCard::Email::home) {
+            t = t | QXmppVCardEmail::Home;
+        } else if (mEm.role == Shared::VCard::Email::work) {
+            t = t | QXmppVCardEmail::Work;
+        }
+        em.setType(t);
+        em.setAddress(mEm.address);
+        
+        emails.push_back(em);
+    }
+    
+    iq.setEmails(emails);
+    
     bool avatarChanged = false;
     if (card.getAvatarType() == Shared::Avatar::empty) {
         if (avatarType.size() > 0) {
diff --git a/global.cpp b/global.cpp
index e3ffb11..e223a45 100644
--- a/global.cpp
+++ b/global.cpp
@@ -538,6 +538,36 @@ QDateTime Shared::VCard::getReceivingTime() const
     return receivingTime;
 }
 
+std::deque<Shared::VCard::Email> & Shared::VCard::getEmails()
+{
+    return emails;
+}
+
+std::deque<Shared::VCard::Address> & Shared::VCard::getAddresses()
+{
+    return addresses;
+}
+
+std::deque<Shared::VCard::Phone> & Shared::VCard::getPhones()
+{
+    return phones;
+}
+
+const std::deque<Shared::VCard::Email> & Shared::VCard::getEmails() const
+{
+    return emails;
+}
+
+const std::deque<Shared::VCard::Address> & Shared::VCard::getAddresses() const
+{
+    return addresses;
+}
+
+const std::deque<Shared::VCard::Phone> & Shared::VCard::getPhones() const
+{
+    return phones;
+}
+
 const std::deque<QString>Shared::VCard::Contact::roleNames = {"Not specified", "Personal", "Business"};
 
 QIcon Shared::availabilityIcon(Shared::Availability av, bool big)
diff --git a/global.h b/global.h
index c33bd38..34d892c 100644
--- a/global.h
+++ b/global.h
@@ -302,6 +302,12 @@ public:
     QString getOrgTitle() const;
     void setOrgTitle(const QString& title);
     QDateTime getReceivingTime() const;
+    std::deque<Email>& getEmails();
+    const std::deque<Email>& getEmails() const;
+    std::deque<Phone>& getPhones();
+    const std::deque<Phone>& getPhones() const;
+    std::deque<Address>& getAddresses();
+    const std::deque<Address>& getAddresses() const;
     
 private:
     QString fullName;
diff --git a/ui/utils/comboboxdelegate.cpp b/ui/utils/comboboxdelegate.cpp
index 824d1cf..7153405 100644
--- a/ui/utils/comboboxdelegate.cpp
+++ b/ui/utils/comboboxdelegate.cpp
@@ -15,18 +15,21 @@
  * 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 "QTimer"
 
 #include "comboboxdelegate.h"
 
 ComboboxDelegate::ComboboxDelegate(QObject *parent):
     QStyledItemDelegate(parent),
-    entries()
+    entries(),
+    ff(new FocusFilter())
 {
 }
 
 
 ComboboxDelegate::~ComboboxDelegate()
 {
+    delete ff;
 }
 
 
@@ -48,6 +51,7 @@ void ComboboxDelegate::setEditorData(QWidget *editor, const QModelIndex &index)
     int currentIndex = index.data(Qt::EditRole).toInt();
     if (currentIndex >= 0) {
         cb->setCurrentIndex(currentIndex);
+        cb->installEventFilter(ff);
     }
 }
 
@@ -62,3 +66,20 @@ void ComboboxDelegate::addEntry(const QString& title, const QIcon& icon)
 {
     entries.emplace_back(title, icon);
 }
+
+bool ComboboxDelegate::FocusFilter::eventFilter(QObject* src, QEvent* evt)
+{
+    if (evt->type() == QEvent::FocusIn) {
+        QComboBox* cb = static_cast<QComboBox*>(src);
+        cb->removeEventFilter(this);
+        QTimer* timer = new QTimer;                                 //TODO that is ridiculous! I refuse to believe there is no better way than that one!
+        QObject::connect(timer, &QTimer::timeout, [timer, cb]() {
+            cb->showPopup();
+            timer->deleteLater();
+        });
+        
+        timer->setSingleShot(true);
+        timer->start(100);
+    }
+    return QObject::eventFilter(src, evt);
+}
diff --git a/ui/utils/comboboxdelegate.h b/ui/utils/comboboxdelegate.h
index 1d23a5c..a5d79e4 100644
--- a/ui/utils/comboboxdelegate.h
+++ b/ui/utils/comboboxdelegate.h
@@ -21,6 +21,7 @@
 
 #include <QStyledItemDelegate>
 #include <QComboBox>
+#include <QFocusEvent>
 
 #include <deque>
 
@@ -30,6 +31,12 @@
 class ComboboxDelegate : public QStyledItemDelegate
 {
     Q_OBJECT
+    
+    class FocusFilter : public QObject {
+    public:
+        bool eventFilter(QObject *src, QEvent *evt) override;
+    };
+    
 public:
     ComboboxDelegate(QObject *parent = nullptr);
     ~ComboboxDelegate();
@@ -42,6 +49,9 @@ public:
     
 private:
     std::deque<std::pair<QString, QIcon>> entries;
+    FocusFilter* ff;
 };
 
+
+
 #endif // COMBOBOXDELEGATE_H
diff --git a/ui/widgets/vcard/emailsmodel.cpp b/ui/widgets/vcard/emailsmodel.cpp
index 4a5024f..2babaad 100644
--- a/ui/widgets/vcard/emailsmodel.cpp
+++ b/ui/widgets/vcard/emailsmodel.cpp
@@ -43,6 +43,7 @@ QVariant UI::VCard::EMailsModel::data(const QModelIndex& index, int role) const
             case 0:
                 switch (role) {
                     case Qt::DisplayRole:
+                    case Qt::EditRole:
                         return deque[index.row()].address;
                     default:
                         return QVariant();
@@ -143,3 +144,57 @@ QModelIndex UI::VCard::EMailsModel::addNewEmptyLine()
     endInsertRows();
     return createIndex(deque.size() - 1, 0, &(deque.back()));
 }
+
+bool UI::VCard::EMailsModel::isPreferred(int row) const
+{
+    if (row < deque.size()) {
+        return deque[row].prefered;
+    } else {
+        return false;
+    }
+}
+
+void UI::VCard::EMailsModel::removeLines(int index, int count)
+{
+    if (index < deque.size()) {
+        int maxCount = deque.size() - index;
+        if (count > maxCount) {
+            count = maxCount;
+        }
+        
+        if (count > 0) {
+            beginRemoveRows(QModelIndex(), index, index + count - 1);
+            std::deque<Shared::VCard::Email>::const_iterator itr = deque.begin() + index;
+            std::deque<Shared::VCard::Email>::const_iterator end = itr + count;
+            deque.erase(itr, end);
+            endRemoveRows();
+        }
+    }
+}
+
+void UI::VCard::EMailsModel::getEmails(std::deque<Shared::VCard::Email>& emails) const
+{
+    for (const Shared::VCard::Email& my : deque) {
+        emails.emplace_back(my);
+    }
+}
+
+void UI::VCard::EMailsModel::setEmails(const std::deque<Shared::VCard::Email>& emails)
+{
+    if (deque.size() > 0) {
+        removeLines(0, deque.size());
+    }
+    
+    if (emails.size() > 0) {
+        beginInsertRows(QModelIndex(), 0, emails.size() - 1);
+        for (const Shared::VCard::Email& comming : emails) {
+            deque.emplace_back(comming);
+        }
+        endInsertRows();
+    }
+}
+
+void UI::VCard::EMailsModel::revertPreferred(int row)
+{
+    setData(createIndex(row, 2), !isPreferred(row));
+}
diff --git a/ui/widgets/vcard/emailsmodel.h b/ui/widgets/vcard/emailsmodel.h
index ddb0a57..10a610f 100644
--- a/ui/widgets/vcard/emailsmodel.h
+++ b/ui/widgets/vcard/emailsmodel.h
@@ -40,9 +40,15 @@ public:
     QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
     bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
     Qt::ItemFlags flags(const QModelIndex &index) const override;
+    bool isPreferred(int row) const;
+    
+    void removeLines(int index, int count);
+    void setEmails(const std::deque<Shared::VCard::Email>& emails);
+    void getEmails(std::deque<Shared::VCard::Email>& emails) const;
     
 public slots:
     QModelIndex addNewEmptyLine();
+    void revertPreferred(int row);
     
 private:
     bool edit;
diff --git a/ui/widgets/vcard/vcard.cpp b/ui/widgets/vcard/vcard.cpp
index 3f7778e..850830d 100644
--- a/ui/widgets/vcard/vcard.cpp
+++ b/ui/widgets/vcard/vcard.cpp
@@ -21,6 +21,8 @@
 
 #include <QDebug>
 
+#include <algorithm>
+
 const std::set<QString> VCard::supportedTypes = {"image/jpeg", "image/png"};
 
 VCard::VCard(const QString& jid, bool edit, QWidget* parent):
@@ -58,6 +60,7 @@ VCard::VCard(const QString& jid, bool edit, QWidget* parent):
     m_ui->emailsView->setContextMenuPolicy(Qt::CustomContextMenu);
     m_ui->emailsView->setModel(&emails);
     m_ui->emailsView->setItemDelegateForColumn(1, roleDelegate);
+    m_ui->emailsView->setColumnWidth(2, 30);
     m_ui->emailsView->horizontalHeader()->setStretchLastSection(false);
     m_ui->emailsView->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch);
     
@@ -68,7 +71,7 @@ VCard::VCard(const QString& jid, bool edit, QWidget* parent):
         m_ui->avatarButton->setMenu(avatarMenu);
         avatarMenu->addAction(setAvatar);
         avatarMenu->addAction(clearAvatar);
-        m_ui->title->setText(tr("Your card"));
+        m_ui->title->setText(tr("Account %1 card").arg(jid));
     } else {
         m_ui->buttonBox->hide();
         m_ui->fullName->setReadOnly(true);
@@ -149,6 +152,9 @@ void VCard::setVCard(const Shared::VCard& card)
     currentAvatarPath = card.getAvatarPath();
     
     updateAvatar();
+    
+    const std::deque<Shared::VCard::Email>& ems = card.getEmails();
+    emails.setEmails(ems);
 }
 
 QString VCard::getJid() const
@@ -174,6 +180,8 @@ void VCard::onButtonBoxAccepted()
     card.setAvatarPath(currentAvatarPath);
     card.setAvatarType(currentAvatarType);
     
+    emails.getEmails(card.getEmails());
+    
     emit saveVCard(card);
 }
 
@@ -278,6 +286,25 @@ void VCard::onContextMenu(const QPoint& point)
             hasMenu = true;
             QAction* add = contextMenu->addAction(Shared::icon("list-add"), tr("Add email address"));
             connect(add, &QAction::triggered, this, &VCard::onAddEmail);
+            
+            QItemSelectionModel* sm = m_ui->emailsView->selectionModel();
+            int selectionSize = sm->selectedRows().size();
+            
+            if (selectionSize > 0) {
+                if (selectionSize == 1) {
+                    int row = sm->selectedRows().at(0).row();
+                    if (emails.isPreferred(row)) {
+                        QAction* rev = contextMenu->addAction(Shared::icon("view-media-favorite"), tr("Unset this email as preferred"));
+                        connect(rev, &QAction::triggered, std::bind(&UI::VCard::EMailsModel::revertPreferred, &emails, row));
+                    } else {
+                        QAction* rev = contextMenu->addAction(Shared::icon("favorite"), tr("Set this email as preferred"));
+                        connect(rev, &QAction::triggered, std::bind(&UI::VCard::EMailsModel::revertPreferred, &emails, row));
+                    }
+                }
+                
+                QAction* del = contextMenu->addAction(Shared::icon("remove"), tr("Remove selected email addresses"));
+                connect(del, &QAction::triggered, this, &VCard::onRemoveEmail);
+            }
         }
     }
     
@@ -305,6 +332,23 @@ void VCard::onRemoveAddress()
 }
 void VCard::onRemoveEmail()
 {
+    QItemSelection selection(m_ui->emailsView->selectionModel()->selection());
+    
+    QList<int> rows;
+    for (const QModelIndex& index : selection.indexes()) {
+        rows.append(index.row());
+    }
+    
+    std::sort(rows.begin(), rows.end());
+    
+    int prev = -1;
+    for (int i = rows.count() - 1; i >= 0; i -= 1) {
+        int current = rows[i];
+        if (current != prev) {
+            emails.removeLines(current, 1);
+            prev = current;
+        }
+    }
 }
 
 void VCard::onRemovePhone()
diff --git a/ui/widgets/vcard/vcard.ui b/ui/widgets/vcard/vcard.ui
index a4381b3..e08bf49 100644
--- a/ui/widgets/vcard/vcard.ui
+++ b/ui/widgets/vcard/vcard.ui
@@ -84,7 +84,7 @@
         <enum>QTabWidget::Rounded</enum>
        </property>
        <property name="currentIndex">
-        <number>1</number>
+        <number>0</number>
        </property>
        <property name="elideMode">
         <enum>Qt::ElideNone</enum>
@@ -564,7 +564,7 @@
               <height>497</height>
              </rect>
             </property>
-            <layout class="QGridLayout" name="gridLayout_3" columnstretch="1,3,1,0">
+            <layout class="QGridLayout" name="gridLayout_3" columnstretch="1,3,1">
              <item row="7" column="1">
               <widget class="Line" name="phonesLine">
                <property name="orientation">
@@ -644,7 +644,7 @@
                 <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:16pt; font-weight:600;&quot;&gt;Addresses&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
                </property>
                <property name="alignment">
-                <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
+                <set>Qt::AlignCenter</set>
                </property>
               </widget>
              </item>
@@ -670,7 +670,7 @@
                 <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:16pt; font-weight:600;&quot;&gt;E-Mail addresses&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
                </property>
                <property name="alignment">
-                <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
+                <set>Qt::AlignCenter</set>
                </property>
               </widget>
              </item>
@@ -752,7 +752,7 @@
                 <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:16pt; font-weight:600;&quot;&gt;Phone numbers&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
                </property>
                <property name="alignment">
-                <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
+                <set>Qt::AlignCenter</set>
                </property>
               </widget>
              </item>

From c1c1de1b7bacc9ee9661021fef306fcd027e51c6 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Tue, 5 Nov 2019 21:55:21 +0300
Subject: [PATCH 020/281] backend adapter for vCard phones list

---
 core/CMakeLists.txt              |   1 +
 core/account.cpp                 | 100 +-----------
 core/account.h                   |   2 +
 core/adapterFuctions.cpp         | 269 +++++++++++++++++++++++++++++++
 global.h                         |   5 +-
 ui/widgets/vcard/emailsmodel.cpp |   2 +-
 6 files changed, 281 insertions(+), 98 deletions(-)
 create mode 100644 core/adapterFuctions.cpp

diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt
index 2c0374d..fab36f2 100644
--- a/core/CMakeLists.txt
+++ b/core/CMakeLists.txt
@@ -17,6 +17,7 @@ set(squawkCORE_SRC
     conference.cpp
     storage.cpp
     networkaccess.cpp
+    adapterFuctions.cpp
 )
 
 # Tell CMake to create the helloworld executable
diff --git a/core/account.cpp b/core/account.cpp
index ed6da44..e3dd29f 100644
--- a/core/account.cpp
+++ b/core/account.cpp
@@ -1347,36 +1347,7 @@ void Core::Account::onVCardReceived(const QXmppVCardIq& card)
     }
     
     Shared::VCard vCard;
-    vCard.setFullName(card.fullName());
-    vCard.setFirstName(card.firstName());
-    vCard.setMiddleName(card.middleName());
-    vCard.setLastName(card.lastName());
-    vCard.setBirthday(card.birthday());
-    vCard.setNickName(card.nickName());
-    vCard.setDescription(card.description());
-    vCard.setUrl(card.url());
-    QXmppVCardOrganization org = card.organization();
-    vCard.setOrgName(org.organization());
-    vCard.setOrgRole(org.role());
-    vCard.setOrgUnit(org.unit());
-    vCard.setOrgTitle(org.title());
-    
-    QList<QXmppVCardEmail> emails = card.emails();
-    std::deque<Shared::VCard::Email>& myEmails = vCard.getEmails();
-    for (const QXmppVCardEmail& em : emails) {
-        Shared::VCard::Email mEm(em.address());
-        QXmppVCardEmail::Type et = em.type();
-        if (et & QXmppVCardEmail::Preferred) {
-            mEm.prefered = true;
-        }
-        if (et & QXmppVCardEmail::Home && !(et & QXmppVCardEmail::Work)) {
-            mEm.role = Shared::VCard::Email::home;
-        } else if (!(et & QXmppVCardEmail::Home) && et & QXmppVCardEmail::Work) {
-            mEm.role = Shared::VCard::Email::work;
-        }
-        
-        myEmails.emplace_back(mEm);
-    }
+    initializeVCard(vCard, card);
     
     if (item->hasAvatar()) {
         if (!item->isAvatarAutoGenerated()) {
@@ -1480,19 +1451,8 @@ void Core::Account::onOwnVCardReceived(const QXmppVCardIq& card)
     ownVCardRequestInProgress = false;
     
     Shared::VCard vCard;
-    vCard.setFullName(card.fullName());
-    vCard.setFirstName(card.firstName());
-    vCard.setMiddleName(card.middleName());
-    vCard.setLastName(card.lastName());
-    vCard.setBirthday(card.birthday());
-    vCard.setNickName(card.nickName());
-    vCard.setDescription(card.description());
-    vCard.setUrl(card.url());
-    QXmppVCardOrganization org = card.organization();
-    vCard.setOrgName(org.organization());
-    vCard.setOrgRole(org.role());
-    vCard.setOrgUnit(org.unit());
-    vCard.setOrgTitle(org.title());
+    initializeVCard(vCard, card);
+    
     if (avatarType.size() > 0) {
         vCard.setAvatarType(Shared::Avatar::valid);
         vCard.setAvatarPath(path + "avatar." + avatarType);
@@ -1500,24 +1460,6 @@ void Core::Account::onOwnVCardReceived(const QXmppVCardIq& card)
         vCard.setAvatarType(Shared::Avatar::empty);
     }
     
-    
-    QList<QXmppVCardEmail> emails = card.emails();
-    std::deque<Shared::VCard::Email>& myEmails = vCard.getEmails();
-    for (const QXmppVCardEmail& em : emails) {
-        Shared::VCard::Email mEm(em.address());
-        QXmppVCardEmail::Type et = em.type();
-        if (et & QXmppVCardEmail::Preferred) {
-            mEm.prefered = true;
-        }
-        if (et & QXmppVCardEmail::Home && !(et & QXmppVCardEmail::Work)) {
-            mEm.role = Shared::VCard::Email::home;
-        } else if (!(et & QXmppVCardEmail::Home) && et & QXmppVCardEmail::Work) {
-            mEm.role = Shared::VCard::Email::work;
-        }
-        
-        myEmails.emplace_back(mEm);
-    }
-    
     emit receivedVCard(getLogin() + "@" + getServer(), vCard);
 }
 
@@ -1555,41 +1497,7 @@ void Core::Account::requestVCard(const QString& jid)
 void Core::Account::uploadVCard(const Shared::VCard& card)
 {
     QXmppVCardIq iq;
-    iq.setFullName(card.getFullName());
-    iq.setFirstName(card.getFirstName());
-    iq.setMiddleName(card.getMiddleName());
-    iq.setLastName(card.getLastName());
-    iq.setNickName(card.getNickName());
-    iq.setBirthday(card.getBirthday());
-    iq.setDescription(card.getDescription());
-    iq.setUrl(card.getUrl());
-    QXmppVCardOrganization org;
-    org.setOrganization(card.getOrgName());
-    org.setUnit(card.getOrgUnit());
-    org.setRole(card.getOrgRole());
-    org.setTitle(card.getOrgTitle());
-    iq.setOrganization(org);
-    
-    const std::deque<Shared::VCard::Email>& myEmails = card.getEmails();
-    QList<QXmppVCardEmail> emails;
-    for (const Shared::VCard::Email& mEm : myEmails) {
-        QXmppVCardEmail em;
-        QXmppVCardEmail::Type t = QXmppVCardEmail::Internet;
-        if (mEm.prefered) {
-            t = t | QXmppVCardEmail::Preferred;
-        }
-        if (mEm.role == Shared::VCard::Email::home) {
-            t = t | QXmppVCardEmail::Home;
-        } else if (mEm.role == Shared::VCard::Email::work) {
-            t = t | QXmppVCardEmail::Work;
-        }
-        em.setType(t);
-        em.setAddress(mEm.address);
-        
-        emails.push_back(em);
-    }
-    
-    iq.setEmails(emails);
+    initializeQXmppVCard(iq, card);
     
     bool avatarChanged = false;
     if (card.getAvatarType() == Shared::Avatar::empty) {
diff --git a/core/account.h b/core/account.h
index d6444d3..a5f07bd 100644
--- a/core/account.h
+++ b/core/account.h
@@ -202,6 +202,8 @@ private:
     
 };
 
+void initializeVCard(Shared::VCard& vCard, const QXmppVCardIq& card);
+void initializeQXmppVCard(QXmppVCardIq& card, const Shared::VCard& vCard);
 }
 
 
diff --git a/core/adapterFuctions.cpp b/core/adapterFuctions.cpp
new file mode 100644
index 0000000..0693c34
--- /dev/null
+++ b/core/adapterFuctions.cpp
@@ -0,0 +1,269 @@
+/*
+ * Squawk messenger. 
+ * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+ *
+ * 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/>.
+ */
+#ifndef CORE_ADAPTER_FUNCTIONS_H
+#define CORE_ADAPTER_FUNCTIONS_H
+
+#include "account.h"
+
+void Core::initializeVCard(Shared::VCard& vCard, const QXmppVCardIq& card)
+{
+    vCard.setFullName(card.fullName());
+    vCard.setFirstName(card.firstName());
+    vCard.setMiddleName(card.middleName());
+    vCard.setLastName(card.lastName());
+    vCard.setBirthday(card.birthday());
+    vCard.setNickName(card.nickName());
+    vCard.setDescription(card.description());
+    vCard.setUrl(card.url());
+    QXmppVCardOrganization org = card.organization();
+    vCard.setOrgName(org.organization());
+    vCard.setOrgRole(org.role());
+    vCard.setOrgUnit(org.unit());
+    vCard.setOrgTitle(org.title());
+    
+    QList<QXmppVCardEmail> emails = card.emails();
+    std::deque<Shared::VCard::Email>& myEmails = vCard.getEmails();
+    for (const QXmppVCardEmail& em : emails) {
+        QXmppVCardEmail::Type et = em.type();
+        bool prefered = false;
+        bool accounted = false;
+        if (et & QXmppVCardEmail::Preferred) {
+            prefered = true;
+        }
+        if (et & QXmppVCardEmail::Home) {
+            myEmails.emplace_back(em.address(), Shared::VCard::Email::home, prefered);
+            accounted = true;
+        } 
+        if (et & QXmppVCardEmail::Work) {
+            myEmails.emplace_back(em.address(), Shared::VCard::Email::work, prefered);
+            accounted = true;
+        }
+        if (!accounted) {
+            myEmails.emplace_back(em.address(), Shared::VCard::Email::none, prefered);
+        }
+        
+    }
+    
+    QList<QXmppVCardPhone> phones = card.phones();
+    std::deque<Shared::VCard::Phone>& myPhones = vCard.getPhones();
+    for (const QXmppVCardPhone& ph : phones) {
+        Shared::VCard::Phone mPh(ph.number());
+        QXmppVCardPhone::Type pt = ph.type();
+        bool prefered = false;
+        bool accounted = false;
+        if (pt & QXmppVCardPhone::Preferred) {
+            prefered = true;
+        }
+        
+        bool home = false;
+        bool work = false;
+        
+        if (pt & QXmppVCardPhone::Home) {
+            home = true;
+        }
+        if (pt & QXmppVCardPhone::Work) {
+            work = true;
+        }
+        
+        
+        if (pt & QXmppVCardPhone::Fax) {
+            if (home || work) {
+                if (home) {
+                    myPhones.emplace_back(ph.number(), Shared::VCard::Phone::fax, Shared::VCard::Phone::home, prefered);
+                }
+                if (work) {
+                    myPhones.emplace_back(ph.number(), Shared::VCard::Phone::fax, Shared::VCard::Phone::work, prefered);
+                }
+            } else {
+                myPhones.emplace_back(ph.number(), Shared::VCard::Phone::fax, Shared::VCard::Phone::none, prefered);
+            }
+            accounted = true;
+        } 
+        if (pt & QXmppVCardPhone::Voice) {
+            if (home || work) {
+                if (home) {
+                    myPhones.emplace_back(ph.number(), Shared::VCard::Phone::voice, Shared::VCard::Phone::home, prefered);
+                }
+                if (work) {
+                    myPhones.emplace_back(ph.number(), Shared::VCard::Phone::voice, Shared::VCard::Phone::work, prefered);
+                }
+            } else {
+                myPhones.emplace_back(ph.number(), Shared::VCard::Phone::voice, Shared::VCard::Phone::none, prefered);
+            }
+            accounted = true;
+        } 
+        if (pt & QXmppVCardPhone::Pager) {
+            if (home || work) {
+                if (home) {
+                    myPhones.emplace_back(ph.number(), Shared::VCard::Phone::pager, Shared::VCard::Phone::home, prefered);
+                }
+                if (work) {
+                    myPhones.emplace_back(ph.number(), Shared::VCard::Phone::pager, Shared::VCard::Phone::work, prefered);
+                }
+            } else {
+                myPhones.emplace_back(ph.number(), Shared::VCard::Phone::pager, Shared::VCard::Phone::none, prefered);
+            }
+            accounted = true;
+        } 
+        if (pt & QXmppVCardPhone::Cell) {
+            if (home || work) {
+                if (home) {
+                    myPhones.emplace_back(ph.number(), Shared::VCard::Phone::cell, Shared::VCard::Phone::home, prefered);
+                }
+                if (work) {
+                    myPhones.emplace_back(ph.number(), Shared::VCard::Phone::cell, Shared::VCard::Phone::work, prefered);
+                }
+            } else {
+                myPhones.emplace_back(ph.number(), Shared::VCard::Phone::cell, Shared::VCard::Phone::none, prefered);
+            }
+            accounted = true;
+        } 
+        if (pt & QXmppVCardPhone::Video) {
+            if (home || work) {
+                if (home) {
+                    myPhones.emplace_back(ph.number(), Shared::VCard::Phone::video, Shared::VCard::Phone::home, prefered);
+                }
+                if (work) {
+                    myPhones.emplace_back(ph.number(), Shared::VCard::Phone::video, Shared::VCard::Phone::work, prefered);
+                }
+            } else {
+                myPhones.emplace_back(ph.number(), Shared::VCard::Phone::video, Shared::VCard::Phone::none, prefered);
+            }
+            accounted = true;
+        } 
+        if (pt & QXmppVCardPhone::Modem) {
+            if (home || work) {
+                if (home) {
+                    myPhones.emplace_back(ph.number(), Shared::VCard::Phone::modem, Shared::VCard::Phone::home, prefered);
+                }
+                if (work) {
+                    myPhones.emplace_back(ph.number(), Shared::VCard::Phone::modem, Shared::VCard::Phone::work, prefered);
+                }
+            } else {
+                myPhones.emplace_back(ph.number(), Shared::VCard::Phone::modem, Shared::VCard::Phone::none, prefered);
+            }
+            accounted = true;
+        } 
+        if (!accounted) {
+            if (home || work) {
+                if (home) {
+                    myPhones.emplace_back(ph.number(), Shared::VCard::Phone::other, Shared::VCard::Phone::home, prefered);
+                }
+                if (work) {
+                    myPhones.emplace_back(ph.number(), Shared::VCard::Phone::other, Shared::VCard::Phone::work, prefered);
+                }
+            } else {
+                myPhones.emplace_back(ph.number(), Shared::VCard::Phone::other, Shared::VCard::Phone::none, prefered);
+            }
+        }
+    }
+}
+
+void Core::initializeQXmppVCard(QXmppVCardIq& iq, const Shared::VCard& card) {
+    iq.setFullName(card.getFullName());
+    iq.setFirstName(card.getFirstName());
+    iq.setMiddleName(card.getMiddleName());
+    iq.setLastName(card.getLastName());
+    iq.setNickName(card.getNickName());
+    iq.setBirthday(card.getBirthday());
+    iq.setDescription(card.getDescription());
+    iq.setUrl(card.getUrl());
+    QXmppVCardOrganization org;
+    org.setOrganization(card.getOrgName());
+    org.setUnit(card.getOrgUnit());
+    org.setRole(card.getOrgRole());
+    org.setTitle(card.getOrgTitle());
+    iq.setOrganization(org);
+    
+    const std::deque<Shared::VCard::Email>& myEmails = card.getEmails();
+    QList<QXmppVCardEmail> emails;
+    for (const Shared::VCard::Email& mEm : myEmails) {
+        QXmppVCardEmail em;
+        QXmppVCardEmail::Type t = QXmppVCardEmail::Internet;
+        if (mEm.prefered) {
+            t = t | QXmppVCardEmail::Preferred;
+        }
+        if (mEm.role == Shared::VCard::Email::home) {
+            t = t | QXmppVCardEmail::Home;
+        } else if (mEm.role == Shared::VCard::Email::work) {
+            t = t | QXmppVCardEmail::Work;
+        }
+        em.setType(t);
+        em.setAddress(mEm.address);
+        
+        emails.push_back(em);
+    }
+    
+    std::map<QString, QXmppVCardPhone> phones;
+    QList<QXmppVCardPhone> phs;
+    const std::deque<Shared::VCard::Phone>& myPhones = card.getPhones();
+    for (const Shared::VCard::Phone& mPh : myPhones) {
+        std::map<QString, QXmppVCardPhone>::iterator itr = phones.find(mPh.number);
+        if (itr == phones.end()) {
+            itr = phones.emplace(mPh.number, QXmppVCardPhone()).first;
+        }
+        QXmppVCardPhone& phone = itr->second;
+        
+        switch (mPh.type) {
+            case Shared::VCard::Phone::fax:
+                phone.setType(phone.type() | QXmppVCardPhone::Fax);
+                break;
+            case Shared::VCard::Phone::pager:
+                phone.setType(phone.type() | QXmppVCardPhone::Pager);
+                break;
+            case Shared::VCard::Phone::voice:
+                phone.setType(phone.type() | QXmppVCardPhone::Voice);
+                break;
+            case Shared::VCard::Phone::cell:
+                phone.setType(phone.type() | QXmppVCardPhone::Cell);
+                break;
+            case Shared::VCard::Phone::video:
+                phone.setType(phone.type() | QXmppVCardPhone::Video);
+                break;
+            case Shared::VCard::Phone::modem:
+                phone.setType(phone.type() | QXmppVCardPhone::Modem);
+                break;
+            case Shared::VCard::Phone::other:
+                phone.setType(phone.type() | QXmppVCardPhone::PCS);     //loss of information, but I don't even know what the heck is this type of phone!
+                break;
+        }
+        
+        switch (mPh.role) {
+            case Shared::VCard::Phone::home:
+                phone.setType(phone.type() | QXmppVCardPhone::Home);
+                break;
+            case Shared::VCard::Phone::work:
+                phone.setType(phone.type() | QXmppVCardPhone::Work);
+                break;
+            default:
+                break;
+        }
+        
+        if (mPh.prefered) {
+            phone.setType(phone.type() | QXmppVCardPhone::Preferred);
+        }
+    }
+    for (const std::pair<QString, QXmppVCardPhone>& phone : phones) {
+        phs.push_back(phone.second);
+    }
+    
+    iq.setEmails(emails);
+    iq.setPhones(phs);
+}
+
+#endif // CORE_ADAPTER_FUNCTIONS_H
diff --git a/global.h b/global.h
index 34d892c..72c8406 100644
--- a/global.h
+++ b/global.h
@@ -238,13 +238,15 @@ public:
         QString address;
     };
     class Phone : public Contact {
+    public:
         enum Type {
             fax,
             pager,
             voice,
             cell,
             video,
-            modem
+            modem,
+            other
         };
         Phone(const QString& number, Type p_type = voice, Role p_role = none, bool p_prefered = false);
         
@@ -252,6 +254,7 @@ public:
         Type type;
     };
     class Address : public Contact {
+    public:
         Address(
             const QString& zCode = "", 
             const QString& cntry = "", 
diff --git a/ui/widgets/vcard/emailsmodel.cpp b/ui/widgets/vcard/emailsmodel.cpp
index 2babaad..7e3a646 100644
--- a/ui/widgets/vcard/emailsmodel.cpp
+++ b/ui/widgets/vcard/emailsmodel.cpp
@@ -108,7 +108,7 @@ bool UI::VCard::EMailsModel::setData(const QModelIndex& index, const QVariant& v
                 bool newDef = value.toBool();
                 if (newDef != item.prefered) {
                     if (newDef) {
-                        dropPrefered();
+                        //dropPrefered();
                     }
                     item.prefered = newDef;
                     return true;

From 2c13f0d77c47aa871676fa35e50ea54e6a8859e9 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Wed, 6 Nov 2019 18:14:49 +0300
Subject: [PATCH 021/281] phones now also saveble

---
 core/adapterFuctions.cpp         |   1 +
 global.cpp                       |   1 +
 global.h                         |   1 +
 ui/widgets/vcard/CMakeLists.txt  |   1 +
 ui/widgets/vcard/emailsmodel.cpp |   5 +
 ui/widgets/vcard/emailsmodel.h   |   1 +
 ui/widgets/vcard/phonesmodel.cpp | 222 +++++++++++++++++++++++++++++++
 ui/widgets/vcard/phonesmodel.h   |  65 +++++++++
 ui/widgets/vcard/vcard.cpp       | 112 +++++++++++++++-
 ui/widgets/vcard/vcard.h         |   7 +
 10 files changed, 413 insertions(+), 3 deletions(-)
 create mode 100644 ui/widgets/vcard/phonesmodel.cpp
 create mode 100644 ui/widgets/vcard/phonesmodel.h

diff --git a/core/adapterFuctions.cpp b/core/adapterFuctions.cpp
index 0693c34..0279533 100644
--- a/core/adapterFuctions.cpp
+++ b/core/adapterFuctions.cpp
@@ -218,6 +218,7 @@ void Core::initializeQXmppVCard(QXmppVCardIq& iq, const Shared::VCard& card) {
             itr = phones.emplace(mPh.number, QXmppVCardPhone()).first;
         }
         QXmppVCardPhone& phone = itr->second;
+        phone.setNumber(mPh.number);
         
         switch (mPh.type) {
             case Shared::VCard::Phone::fax:
diff --git a/global.cpp b/global.cpp
index e223a45..d054150 100644
--- a/global.cpp
+++ b/global.cpp
@@ -569,6 +569,7 @@ const std::deque<Shared::VCard::Phone> & Shared::VCard::getPhones() const
 }
 
 const std::deque<QString>Shared::VCard::Contact::roleNames = {"Not specified", "Personal", "Business"};
+const std::deque<QString>Shared::VCard::Phone::typeNames = {"Fax", "Pager", "Voice", "Cell", "Video", "Modem", "Other"};
 
 QIcon Shared::availabilityIcon(Shared::Availability av, bool big)
 {
diff --git a/global.h b/global.h
index 72c8406..ccb7317 100644
--- a/global.h
+++ b/global.h
@@ -248,6 +248,7 @@ public:
             modem,
             other
         };
+        static const std::deque<QString> typeNames;
         Phone(const QString& number, Type p_type = voice, Role p_role = none, bool p_prefered = false);
         
         QString number;
diff --git a/ui/widgets/vcard/CMakeLists.txt b/ui/widgets/vcard/CMakeLists.txt
index 4af3d68..4d2ee15 100644
--- a/ui/widgets/vcard/CMakeLists.txt
+++ b/ui/widgets/vcard/CMakeLists.txt
@@ -12,6 +12,7 @@ find_package(Qt5Widgets CONFIG REQUIRED)
 set(vCardUI_SRC
   vcard.cpp
   emailsmodel.cpp
+  phonesmodel.cpp
 )
 
 # Tell CMake to create the helloworld executable
diff --git a/ui/widgets/vcard/emailsmodel.cpp b/ui/widgets/vcard/emailsmodel.cpp
index 7e3a646..18838ee 100644
--- a/ui/widgets/vcard/emailsmodel.cpp
+++ b/ui/widgets/vcard/emailsmodel.cpp
@@ -198,3 +198,8 @@ void UI::VCard::EMailsModel::revertPreferred(int row)
 {
     setData(createIndex(row, 2), !isPreferred(row));
 }
+
+QString UI::VCard::EMailsModel::getEmail(int row) const
+{
+    return deque[row].address;
+}
diff --git a/ui/widgets/vcard/emailsmodel.h b/ui/widgets/vcard/emailsmodel.h
index 10a610f..358536f 100644
--- a/ui/widgets/vcard/emailsmodel.h
+++ b/ui/widgets/vcard/emailsmodel.h
@@ -45,6 +45,7 @@ public:
     void removeLines(int index, int count);
     void setEmails(const std::deque<Shared::VCard::Email>& emails);
     void getEmails(std::deque<Shared::VCard::Email>& emails) const;
+    QString getEmail(int row) const;
     
 public slots:
     QModelIndex addNewEmptyLine();
diff --git a/ui/widgets/vcard/phonesmodel.cpp b/ui/widgets/vcard/phonesmodel.cpp
new file mode 100644
index 0000000..4371dff
--- /dev/null
+++ b/ui/widgets/vcard/phonesmodel.cpp
@@ -0,0 +1,222 @@
+/*
+ * Squawk messenger. 
+ * Copyright (C) 2019 Yury Gubich <blue@macaw.me>
+ * 
+ * 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 "phonesmodel.h"
+
+UI::VCard::PhonesModel::PhonesModel(bool p_edit, QObject* parent):
+    QAbstractTableModel(parent),
+    edit(p_edit),
+    deque()
+{
+}
+
+int UI::VCard::PhonesModel::columnCount(const QModelIndex& parent) const
+{
+    return 4;
+}
+
+int UI::VCard::PhonesModel::rowCount(const QModelIndex& parent) const
+{
+    return deque.size();
+}
+
+QVariant UI::VCard::PhonesModel::data(const QModelIndex& index, int role) const
+{
+    if (index.isValid()) {
+        int col = index.column();
+        switch (col) {
+            case 0:
+                switch (role) {
+                    case Qt::DisplayRole:
+                    case Qt::EditRole:
+                        return deque[index.row()].number;
+                    default:
+                        return QVariant();
+                }
+                break;
+            case 1:
+                switch (role) {
+                    case Qt::DisplayRole:
+                        return tr(Shared::VCard::Phone::roleNames[deque[index.row()].role].toStdString().c_str());
+                    case Qt::EditRole: 
+                        return deque[index.row()].role;
+                    default:
+                        return QVariant();
+                }
+                break;
+            case 2:
+                switch (role) {
+                    case Qt::DisplayRole:
+                        return tr(Shared::VCard::Phone::typeNames[deque[index.row()].type].toStdString().c_str());
+                    case Qt::EditRole: 
+                        return deque[index.row()].type;
+                    default:
+                        return QVariant();
+                }
+                break;
+            case 3:
+                switch (role) {
+                    case Qt::DisplayRole:
+                        return QVariant();
+                    case Qt::DecorationRole:
+                        if (deque[index.row()].prefered) {
+                            return Shared::icon("favorite", false);
+                        }
+                        return QVariant();
+                    default:
+                        return QVariant();
+                }
+                break;
+            default:
+                return QVariant();
+        }
+    }
+    return QVariant();
+}
+
+QModelIndex UI::VCard::PhonesModel::addNewEmptyLine()
+{
+    beginInsertRows(QModelIndex(), deque.size(), deque.size());
+    deque.emplace_back("", Shared::VCard::Phone::other);
+    endInsertRows();
+    return createIndex(deque.size() - 1, 0, &(deque.back()));
+}
+
+Qt::ItemFlags UI::VCard::PhonesModel::flags(const QModelIndex& index) const
+{
+    Qt::ItemFlags f = QAbstractTableModel::flags(index);
+    if (edit && index.column() != 3) {
+        f = Qt::ItemIsEditable | f;
+    }
+    return  f;
+}
+
+bool UI::VCard::PhonesModel::dropPrefered()
+{
+    bool dropped = false;
+    int i = 0;
+    for (Shared::VCard::Phone& phone : deque) {
+        if (phone.prefered) {
+            phone.prefered = false;
+            QModelIndex ci = createIndex(i, 2, &phone);
+            emit dataChanged(ci, ci);
+            dropped = true;
+        }
+        ++i;
+    }
+    return dropped;
+}
+
+void UI::VCard::PhonesModel::getPhones(std::deque<Shared::VCard::Phone>& phones) const
+{
+    for (const Shared::VCard::Phone& my : deque) {
+        phones.emplace_back(my);
+    }
+}
+
+bool UI::VCard::PhonesModel::isPreferred(int row) const
+{
+    if (row < deque.size()) {
+        return deque[row].prefered;
+    } else {
+        return false;
+    }
+}
+
+void UI::VCard::PhonesModel::removeLines(int index, int count)
+{
+    if (index < deque.size()) {
+        int maxCount = deque.size() - index;
+        if (count > maxCount) {
+            count = maxCount;
+        }
+        
+        if (count > 0) {
+            beginRemoveRows(QModelIndex(), index, index + count - 1);
+            std::deque<Shared::VCard::Phone>::const_iterator itr = deque.begin() + index;
+            std::deque<Shared::VCard::Phone>::const_iterator end = itr + count;
+            deque.erase(itr, end);
+            endRemoveRows();
+        }
+    }
+}
+
+void UI::VCard::PhonesModel::revertPreferred(int row)
+{
+    setData(createIndex(row, 3), !isPreferred(row));
+}
+
+bool UI::VCard::PhonesModel::setData(const QModelIndex& index, const QVariant& value, int role)
+{
+    if (role == Qt::EditRole && checkIndex(index)) {
+        Shared::VCard::Phone& item = deque[index.row()];
+        switch (index.column()) {
+            case 0:
+                item.number = value.toString();
+                return true;
+            case 1: {
+                quint8 newRole = value.toUInt();
+                if (newRole > Shared::VCard::Phone::work) {
+                    return false;
+                }
+                item.role = static_cast<Shared::VCard::Phone::Role>(newRole);
+                return true;
+            }
+            case 2: {
+                quint8 newType = value.toUInt();
+                if (newType > Shared::VCard::Phone::other) {
+                    return false;
+                }
+                item.type = static_cast<Shared::VCard::Phone::Type>(newType);
+                return true;
+            }
+            case 3: {
+                bool newDef = value.toBool();
+                if (newDef != item.prefered) {
+                    if (newDef) {
+                        //dropPrefered();
+                    }
+                    item.prefered = newDef;
+                    return true;
+                }
+            }
+        }
+        return true;
+    }
+    return false;
+}
+
+void UI::VCard::PhonesModel::setPhones(const std::deque<Shared::VCard::Phone>& phones)
+{
+    if (deque.size() > 0) {
+        removeLines(0, deque.size());
+    }
+    
+    if (phones.size() > 0) {
+        beginInsertRows(QModelIndex(), 0, phones.size() - 1);
+        for (const Shared::VCard::Phone& comming : phones) {
+            deque.emplace_back(comming);
+        }
+        endInsertRows();
+    }
+}
+
+QString UI::VCard::PhonesModel::getPhone(int row) const
+{
+    return deque[row].number;
+}
diff --git a/ui/widgets/vcard/phonesmodel.h b/ui/widgets/vcard/phonesmodel.h
new file mode 100644
index 0000000..bf847d2
--- /dev/null
+++ b/ui/widgets/vcard/phonesmodel.h
@@ -0,0 +1,65 @@
+/*
+ * Squawk messenger. 
+ * Copyright (C) 2019 Yury Gubich <blue@macaw.me>
+ * 
+ * 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/>.
+ */
+
+#ifndef UI_VCARD_PHONESMODEL_H
+#define UI_VCARD_PHONESMODEL_H
+
+#include <QAbstractTableModel>
+#include <QIcon>
+
+#include "global.h"
+
+namespace UI {
+namespace VCard {
+
+/**
+ * @todo write docs
+ */
+class PhonesModel : public QAbstractTableModel
+{
+    Q_OBJECT
+public:
+    PhonesModel(bool edit = false, QObject *parent = nullptr);
+    
+    QVariant data(const QModelIndex& index, int role) const override;
+    int columnCount(const QModelIndex& parent) const override;
+    int rowCount(const QModelIndex& parent) const override;
+    bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
+    Qt::ItemFlags flags(const QModelIndex &index) const override;
+    bool isPreferred(int row) const;
+    
+    void removeLines(int index, int count);
+    void setPhones(const std::deque<Shared::VCard::Phone>& phones);
+    void getPhones(std::deque<Shared::VCard::Phone>& phones) const;
+    QString getPhone(int row) const;
+    
+public slots:
+    QModelIndex addNewEmptyLine();
+    void revertPreferred(int row);
+    
+private:
+    bool edit;
+    std::deque<Shared::VCard::Phone> deque;
+    
+private:
+    bool dropPrefered();
+};
+
+}}
+
+#endif // UI_VCARD_PHONESMODEL_H
diff --git a/ui/widgets/vcard/vcard.cpp b/ui/widgets/vcard/vcard.cpp
index 850830d..d66d6cd 100644
--- a/ui/widgets/vcard/vcard.cpp
+++ b/ui/widgets/vcard/vcard.cpp
@@ -38,7 +38,9 @@ VCard::VCard(const QString& jid, bool edit, QWidget* parent):
     overlay(new QWidget()),
     contextMenu(new QMenu()),
     emails(edit),
-    roleDelegate(new ComboboxDelegate())
+    phones(edit),
+    roleDelegate(new ComboboxDelegate()),
+    phoneTypeDelegate(new ComboboxDelegate())
 {
     m_ui->setupUi(this);
     m_ui->jabberID->setText(jid);
@@ -57,13 +59,30 @@ VCard::VCard(const QString& jid, bool edit, QWidget* parent):
     roleDelegate->addEntry(tr(Shared::VCard::Email::roleNames[1].toStdString().c_str()));
     roleDelegate->addEntry(tr(Shared::VCard::Email::roleNames[2].toStdString().c_str()));
     
+    phoneTypeDelegate->addEntry(tr(Shared::VCard::Phone::typeNames[0].toStdString().c_str()));
+    phoneTypeDelegate->addEntry(tr(Shared::VCard::Phone::typeNames[1].toStdString().c_str()));
+    phoneTypeDelegate->addEntry(tr(Shared::VCard::Phone::typeNames[2].toStdString().c_str()));
+    phoneTypeDelegate->addEntry(tr(Shared::VCard::Phone::typeNames[3].toStdString().c_str()));
+    phoneTypeDelegate->addEntry(tr(Shared::VCard::Phone::typeNames[4].toStdString().c_str()));
+    phoneTypeDelegate->addEntry(tr(Shared::VCard::Phone::typeNames[5].toStdString().c_str()));
+    phoneTypeDelegate->addEntry(tr(Shared::VCard::Phone::typeNames[6].toStdString().c_str()));
+    
     m_ui->emailsView->setContextMenuPolicy(Qt::CustomContextMenu);
     m_ui->emailsView->setModel(&emails);
     m_ui->emailsView->setItemDelegateForColumn(1, roleDelegate);
-    m_ui->emailsView->setColumnWidth(2, 30);
+    m_ui->emailsView->setColumnWidth(2, 25);
     m_ui->emailsView->horizontalHeader()->setStretchLastSection(false);
     m_ui->emailsView->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch);
     
+    m_ui->phonesView->setContextMenuPolicy(Qt::CustomContextMenu);
+    m_ui->phonesView->setModel(&phones);
+    m_ui->phonesView->setItemDelegateForColumn(1, roleDelegate);
+    m_ui->phonesView->setItemDelegateForColumn(2, phoneTypeDelegate);
+    m_ui->phonesView->setColumnWidth(3, 25);
+    m_ui->phonesView->horizontalHeader()->setStretchLastSection(false);
+    m_ui->phonesView->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch);
+    
+    connect(m_ui->phonesView, &QWidget::customContextMenuRequested, this, &VCard::onContextMenu);
     connect(m_ui->emailsView, &QWidget::customContextMenuRequested, this, &VCard::onContextMenu);
     
     if (edit) {
@@ -121,6 +140,7 @@ VCard::~VCard()
         avatarMenu->deleteLater();
     }
     
+    phoneTypeDelegate->deleteLater();
     roleDelegate->deleteLater();
     contextMenu->deleteLater();
 }
@@ -154,7 +174,9 @@ void VCard::setVCard(const Shared::VCard& card)
     updateAvatar();
     
     const std::deque<Shared::VCard::Email>& ems = card.getEmails();
+    const std::deque<Shared::VCard::Phone>& phs = card.getPhones();
     emails.setEmails(ems);
+    phones.setPhones(phs);
 }
 
 QString VCard::getJid() const
@@ -181,6 +203,7 @@ void VCard::onButtonBoxAccepted()
     card.setAvatarType(currentAvatarType);
     
     emails.getEmails(card.getEmails());
+    phones.getPhones(card.getPhones());
     
     emit saveVCard(card);
 }
@@ -282,8 +305,8 @@ void VCard::onContextMenu(const QPoint& point)
     bool hasMenu = false;
     QAbstractItemView* snd = static_cast<QAbstractItemView*>(sender());
     if (snd == m_ui->emailsView) {
+        hasMenu = true;
         if (editable) {
-            hasMenu = true;
             QAction* add = contextMenu->addAction(Shared::icon("list-add"), tr("Add email address"));
             connect(add, &QAction::triggered, this, &VCard::onAddEmail);
             
@@ -306,6 +329,37 @@ void VCard::onContextMenu(const QPoint& point)
                 connect(del, &QAction::triggered, this, &VCard::onRemoveEmail);
             }
         }
+        
+        QAction* cp = contextMenu->addAction(Shared::icon("copy"), tr("Copy selected emails to clipboard"));
+        connect(cp, &QAction::triggered, this, &VCard::onCopyEmail);
+    } else if (snd == m_ui->phonesView) {
+        hasMenu = true;
+        if (editable) {
+            QAction* add = contextMenu->addAction(Shared::icon("list-add"), tr("Add phone number"));
+            connect(add, &QAction::triggered, this, &VCard::onAddPhone);
+            
+            QItemSelectionModel* sm = m_ui->phonesView->selectionModel();
+            int selectionSize = sm->selectedRows().size();
+            
+            if (selectionSize > 0) {
+                if (selectionSize == 1) {
+                    int row = sm->selectedRows().at(0).row();
+                    if (phones.isPreferred(row)) {
+                        QAction* rev = contextMenu->addAction(Shared::icon("view-media-favorite"), tr("Unset this phone as preferred"));
+                        connect(rev, &QAction::triggered, std::bind(&UI::VCard::PhonesModel::revertPreferred, &phones, row));
+                    } else {
+                        QAction* rev = contextMenu->addAction(Shared::icon("favorite"), tr("Set this phone as preferred"));
+                        connect(rev, &QAction::triggered, std::bind(&UI::VCard::PhonesModel::revertPreferred, &phones, row));
+                    }
+                }
+                
+                QAction* del = contextMenu->addAction(Shared::icon("remove"), tr("Remove selected phone numbers"));
+                connect(del, &QAction::triggered, this, &VCard::onRemovePhone);
+            }
+        }
+        
+        QAction* cp = contextMenu->addAction(Shared::icon("copy"), tr("Copy selected phones to clipboard"));
+        connect(cp, &QAction::triggered, this, &VCard::onCopyPhone);
     }
     
     if (hasMenu) {
@@ -326,6 +380,9 @@ void VCard::onAddAddress()
 }
 void VCard::onAddPhone()
 {
+    QModelIndex index = phones.addNewEmptyLine();
+    m_ui->phonesView->setCurrentIndex(index);
+    m_ui->phonesView->edit(index);
 }
 void VCard::onRemoveAddress()
 {
@@ -353,4 +410,53 @@ void VCard::onRemoveEmail()
 
 void VCard::onRemovePhone()
 {
+    QItemSelection selection(m_ui->phonesView->selectionModel()->selection());
+    
+    QList<int> rows;
+    for (const QModelIndex& index : selection.indexes()) {
+        rows.append(index.row());
+    }
+    
+    std::sort(rows.begin(), rows.end());
+    
+    int prev = -1;
+    for (int i = rows.count() - 1; i >= 0; i -= 1) {
+        int current = rows[i];
+        if (current != prev) {
+            phones.removeLines(current, 1);
+            prev = current;
+        }
+    }
+}
+
+void VCard::onCopyEmail()
+{
+    QItemSelection selection(m_ui->emailsView->selectionModel()->selection());
+    
+    QList<QString> addrs;
+    for (const QModelIndex& index : selection.indexes()) {
+        addrs.push_back(emails.getEmail(index.row()));
+    }
+    
+    QString list = addrs.join("\n");
+    
+    qDebug() << list;
+    QClipboard* cb = QApplication::clipboard();
+    cb->setText(list);
+}
+
+void VCard::onCopyPhone()
+{
+    QItemSelection selection(m_ui->phonesView->selectionModel()->selection());
+    
+    QList<QString> phs;
+    for (const QModelIndex& index : selection.indexes()) {
+        phs.push_back(phones.getPhone(index.row()));
+    }
+    
+    QString list = phs.join("\n");
+    
+    qDebug() << list;
+    QClipboard* cb = QApplication::clipboard();
+    cb->setText(list);
 }
diff --git a/ui/widgets/vcard/vcard.h b/ui/widgets/vcard/vcard.h
index e150ae9..8346fc3 100644
--- a/ui/widgets/vcard/vcard.h
+++ b/ui/widgets/vcard/vcard.h
@@ -31,11 +31,14 @@
 #include <QGraphicsOpacityEffect>
 #include <QVBoxLayout>
 #include <QMenu>
+#include <QApplication>
+#include <QClipboard>
 
 #include <set>
 
 #include "global.h"
 #include "emailsmodel.h"
+#include "phonesmodel.h"
 #include "ui/utils/progress.h"
 #include "ui/utils/comboboxdelegate.h"
 
@@ -71,8 +74,10 @@ private slots:
     void onAddAddress();
     void onRemoveAddress();
     void onAddEmail();
+    void onCopyEmail();
     void onRemoveEmail();
     void onAddPhone();
+    void onCopyPhone();
     void onRemovePhone();
     void onContextMenu(const QPoint& point);
     
@@ -88,7 +93,9 @@ private:
     QWidget* overlay;
     QMenu* contextMenu;
     UI::VCard::EMailsModel emails;
+    UI::VCard::PhonesModel phones;
     ComboboxDelegate* roleDelegate;
+    ComboboxDelegate* phoneTypeDelegate;
     
     static const std::set<QString> supportedTypes;
     

From dfa4d10c36d3180bc72bde24db06ea543cfff21a Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Thu, 7 Nov 2019 14:17:46 +0300
Subject: [PATCH 022/281] some VCard polishing, missing icons and translations

---
 core/adapterFuctions.cpp                      | 231 +++++-----
 global.h                                      |   6 +-
 resources/images/fallback/dark/big/add.svg    |  14 +
 resources/images/fallback/dark/big/copy.svg   |  14 +
 .../images/fallback/dark/big/favorite.svg     |  14 +
 .../images/fallback/dark/big/unfavorite.svg   |  14 +
 resources/images/fallback/dark/small/add.svg  |  13 +
 resources/images/fallback/dark/small/copy.svg |  13 +
 .../images/fallback/dark/small/favorite.svg   |  14 +
 .../images/fallback/dark/small/unfavorite.svg |  14 +
 resources/images/fallback/light/big/add.svg   |  14 +
 resources/images/fallback/light/big/copy.svg  |  14 +
 .../images/fallback/light/big/favorite.svg    |  14 +
 .../images/fallback/light/big/unfavorite.svg  |  14 +
 resources/images/fallback/light/small/add.svg |  13 +
 .../images/fallback/light/small/copy.svg      |  13 +
 .../images/fallback/light/small/favorite.svg  |  14 +
 .../fallback/light/small/unfavorite.svg       |  14 +
 resources/resources.qrc                       |  16 +
 translations/squawk.ru.ts                     | 415 +++++++++++++-----
 ui/widgets/vcard/emailsmodel.cpp              |   2 +-
 ui/widgets/vcard/phonesmodel.cpp              |   4 +-
 ui/widgets/vcard/vcard.cpp                    |  40 +-
 ui/widgets/vcard/vcard.ui                     |  40 +-
 24 files changed, 727 insertions(+), 247 deletions(-)
 create mode 100644 resources/images/fallback/dark/big/add.svg
 create mode 100644 resources/images/fallback/dark/big/copy.svg
 create mode 100644 resources/images/fallback/dark/big/favorite.svg
 create mode 100644 resources/images/fallback/dark/big/unfavorite.svg
 create mode 100644 resources/images/fallback/dark/small/add.svg
 create mode 100644 resources/images/fallback/dark/small/copy.svg
 create mode 100644 resources/images/fallback/dark/small/favorite.svg
 create mode 100644 resources/images/fallback/dark/small/unfavorite.svg
 create mode 100644 resources/images/fallback/light/big/add.svg
 create mode 100644 resources/images/fallback/light/big/copy.svg
 create mode 100644 resources/images/fallback/light/big/favorite.svg
 create mode 100644 resources/images/fallback/light/big/unfavorite.svg
 create mode 100644 resources/images/fallback/light/small/add.svg
 create mode 100644 resources/images/fallback/light/small/copy.svg
 create mode 100644 resources/images/fallback/light/small/favorite.svg
 create mode 100644 resources/images/fallback/light/small/unfavorite.svg

diff --git a/core/adapterFuctions.cpp b/core/adapterFuctions.cpp
index 0279533..e2559d8 100644
--- a/core/adapterFuctions.cpp
+++ b/core/adapterFuctions.cpp
@@ -39,22 +39,25 @@ void Core::initializeVCard(Shared::VCard& vCard, const QXmppVCardIq& card)
     QList<QXmppVCardEmail> emails = card.emails();
     std::deque<Shared::VCard::Email>& myEmails = vCard.getEmails();
     for (const QXmppVCardEmail& em : emails) {
-        QXmppVCardEmail::Type et = em.type();
-        bool prefered = false;
-        bool accounted = false;
-        if (et & QXmppVCardEmail::Preferred) {
-            prefered = true;
-        }
-        if (et & QXmppVCardEmail::Home) {
-            myEmails.emplace_back(em.address(), Shared::VCard::Email::home, prefered);
-            accounted = true;
-        } 
-        if (et & QXmppVCardEmail::Work) {
-            myEmails.emplace_back(em.address(), Shared::VCard::Email::work, prefered);
-            accounted = true;
-        }
-        if (!accounted) {
-            myEmails.emplace_back(em.address(), Shared::VCard::Email::none, prefered);
+        QString addr = em.address();
+        if (addr.size() != 0) {
+            QXmppVCardEmail::Type et = em.type();
+            bool prefered = false;
+            bool accounted = false;
+            if (et & QXmppVCardEmail::Preferred) {
+                prefered = true;
+            }
+            if (et & QXmppVCardEmail::Home) {
+                myEmails.emplace_back(addr, Shared::VCard::Email::home, prefered);
+                accounted = true;
+            } 
+            if (et & QXmppVCardEmail::Work) {
+                myEmails.emplace_back(addr, Shared::VCard::Email::work, prefered);
+                accounted = true;
+            }
+            if (!accounted) {
+                myEmails.emplace_back(addr, Shared::VCard::Email::none, prefered);
+            }
         }
         
     }
@@ -62,113 +65,115 @@ void Core::initializeVCard(Shared::VCard& vCard, const QXmppVCardIq& card)
     QList<QXmppVCardPhone> phones = card.phones();
     std::deque<Shared::VCard::Phone>& myPhones = vCard.getPhones();
     for (const QXmppVCardPhone& ph : phones) {
-        Shared::VCard::Phone mPh(ph.number());
-        QXmppVCardPhone::Type pt = ph.type();
-        bool prefered = false;
-        bool accounted = false;
-        if (pt & QXmppVCardPhone::Preferred) {
-            prefered = true;
-        }
-        
-        bool home = false;
-        bool work = false;
-        
-        if (pt & QXmppVCardPhone::Home) {
-            home = true;
-        }
-        if (pt & QXmppVCardPhone::Work) {
-            work = true;
-        }
-        
-        
-        if (pt & QXmppVCardPhone::Fax) {
-            if (home || work) {
-                if (home) {
-                    myPhones.emplace_back(ph.number(), Shared::VCard::Phone::fax, Shared::VCard::Phone::home, prefered);
-                }
-                if (work) {
-                    myPhones.emplace_back(ph.number(), Shared::VCard::Phone::fax, Shared::VCard::Phone::work, prefered);
-                }
-            } else {
-                myPhones.emplace_back(ph.number(), Shared::VCard::Phone::fax, Shared::VCard::Phone::none, prefered);
+        QString num = ph.number();
+        if (num.size() != 0) {
+            QXmppVCardPhone::Type pt = ph.type();
+            bool prefered = false;
+            bool accounted = false;
+            if (pt & QXmppVCardPhone::Preferred) {
+                prefered = true;
             }
-            accounted = true;
-        } 
-        if (pt & QXmppVCardPhone::Voice) {
-            if (home || work) {
-                if (home) {
-                    myPhones.emplace_back(ph.number(), Shared::VCard::Phone::voice, Shared::VCard::Phone::home, prefered);
-                }
-                if (work) {
-                    myPhones.emplace_back(ph.number(), Shared::VCard::Phone::voice, Shared::VCard::Phone::work, prefered);
-                }
-            } else {
-                myPhones.emplace_back(ph.number(), Shared::VCard::Phone::voice, Shared::VCard::Phone::none, prefered);
+            
+            bool home = false;
+            bool work = false;
+            
+            if (pt & QXmppVCardPhone::Home) {
+                home = true;
             }
-            accounted = true;
-        } 
-        if (pt & QXmppVCardPhone::Pager) {
-            if (home || work) {
-                if (home) {
-                    myPhones.emplace_back(ph.number(), Shared::VCard::Phone::pager, Shared::VCard::Phone::home, prefered);
-                }
-                if (work) {
-                    myPhones.emplace_back(ph.number(), Shared::VCard::Phone::pager, Shared::VCard::Phone::work, prefered);
-                }
-            } else {
-                myPhones.emplace_back(ph.number(), Shared::VCard::Phone::pager, Shared::VCard::Phone::none, prefered);
+            if (pt & QXmppVCardPhone::Work) {
+                work = true;
             }
-            accounted = true;
-        } 
-        if (pt & QXmppVCardPhone::Cell) {
-            if (home || work) {
-                if (home) {
-                    myPhones.emplace_back(ph.number(), Shared::VCard::Phone::cell, Shared::VCard::Phone::home, prefered);
+            
+            
+            if (pt & QXmppVCardPhone::Fax) {
+                if (home || work) {
+                    if (home) {
+                        myPhones.emplace_back(num, Shared::VCard::Phone::fax, Shared::VCard::Phone::home, prefered);
+                    }
+                    if (work) {
+                        myPhones.emplace_back(num, Shared::VCard::Phone::fax, Shared::VCard::Phone::work, prefered);
+                    }
+                } else {
+                    myPhones.emplace_back(num, Shared::VCard::Phone::fax, Shared::VCard::Phone::none, prefered);
                 }
-                if (work) {
-                    myPhones.emplace_back(ph.number(), Shared::VCard::Phone::cell, Shared::VCard::Phone::work, prefered);
+                accounted = true;
+            } 
+            if (pt & QXmppVCardPhone::Voice) {
+                if (home || work) {
+                    if (home) {
+                        myPhones.emplace_back(num, Shared::VCard::Phone::voice, Shared::VCard::Phone::home, prefered);
+                    }
+                    if (work) {
+                        myPhones.emplace_back(num, Shared::VCard::Phone::voice, Shared::VCard::Phone::work, prefered);
+                    }
+                } else {
+                    myPhones.emplace_back(num, Shared::VCard::Phone::voice, Shared::VCard::Phone::none, prefered);
                 }
-            } else {
-                myPhones.emplace_back(ph.number(), Shared::VCard::Phone::cell, Shared::VCard::Phone::none, prefered);
-            }
-            accounted = true;
-        } 
-        if (pt & QXmppVCardPhone::Video) {
-            if (home || work) {
-                if (home) {
-                    myPhones.emplace_back(ph.number(), Shared::VCard::Phone::video, Shared::VCard::Phone::home, prefered);
+                accounted = true;
+            } 
+            if (pt & QXmppVCardPhone::Pager) {
+                if (home || work) {
+                    if (home) {
+                        myPhones.emplace_back(num, Shared::VCard::Phone::pager, Shared::VCard::Phone::home, prefered);
+                    }
+                    if (work) {
+                        myPhones.emplace_back(num, Shared::VCard::Phone::pager, Shared::VCard::Phone::work, prefered);
+                    }
+                } else {
+                    myPhones.emplace_back(num, Shared::VCard::Phone::pager, Shared::VCard::Phone::none, prefered);
                 }
-                if (work) {
-                    myPhones.emplace_back(ph.number(), Shared::VCard::Phone::video, Shared::VCard::Phone::work, prefered);
+                accounted = true;
+            } 
+            if (pt & QXmppVCardPhone::Cell) {
+                if (home || work) {
+                    if (home) {
+                        myPhones.emplace_back(num, Shared::VCard::Phone::cell, Shared::VCard::Phone::home, prefered);
+                    }
+                    if (work) {
+                        myPhones.emplace_back(num, Shared::VCard::Phone::cell, Shared::VCard::Phone::work, prefered);
+                    }
+                } else {
+                    myPhones.emplace_back(num, Shared::VCard::Phone::cell, Shared::VCard::Phone::none, prefered);
                 }
-            } else {
-                myPhones.emplace_back(ph.number(), Shared::VCard::Phone::video, Shared::VCard::Phone::none, prefered);
-            }
-            accounted = true;
-        } 
-        if (pt & QXmppVCardPhone::Modem) {
-            if (home || work) {
-                if (home) {
-                    myPhones.emplace_back(ph.number(), Shared::VCard::Phone::modem, Shared::VCard::Phone::home, prefered);
+                accounted = true;
+            } 
+            if (pt & QXmppVCardPhone::Video) {
+                if (home || work) {
+                    if (home) {
+                        myPhones.emplace_back(num, Shared::VCard::Phone::video, Shared::VCard::Phone::home, prefered);
+                    }
+                    if (work) {
+                        myPhones.emplace_back(num, Shared::VCard::Phone::video, Shared::VCard::Phone::work, prefered);
+                    }
+                } else {
+                    myPhones.emplace_back(num, Shared::VCard::Phone::video, Shared::VCard::Phone::none, prefered);
                 }
-                if (work) {
-                    myPhones.emplace_back(ph.number(), Shared::VCard::Phone::modem, Shared::VCard::Phone::work, prefered);
+                accounted = true;
+            } 
+            if (pt & QXmppVCardPhone::Modem) {
+                if (home || work) {
+                    if (home) {
+                        myPhones.emplace_back(num, Shared::VCard::Phone::modem, Shared::VCard::Phone::home, prefered);
+                    }
+                    if (work) {
+                        myPhones.emplace_back(num, Shared::VCard::Phone::modem, Shared::VCard::Phone::work, prefered);
+                    }
+                } else {
+                    myPhones.emplace_back(num, Shared::VCard::Phone::modem, Shared::VCard::Phone::none, prefered);
                 }
-            } else {
-                myPhones.emplace_back(ph.number(), Shared::VCard::Phone::modem, Shared::VCard::Phone::none, prefered);
-            }
-            accounted = true;
-        } 
-        if (!accounted) {
-            if (home || work) {
-                if (home) {
-                    myPhones.emplace_back(ph.number(), Shared::VCard::Phone::other, Shared::VCard::Phone::home, prefered);
+                accounted = true;
+            } 
+            if (!accounted) {
+                if (home || work) {
+                    if (home) {
+                        myPhones.emplace_back(num, Shared::VCard::Phone::other, Shared::VCard::Phone::home, prefered);
+                    }
+                    if (work) {
+                        myPhones.emplace_back(num, Shared::VCard::Phone::other, Shared::VCard::Phone::work, prefered);
+                    }
+                } else {
+                    myPhones.emplace_back(num, Shared::VCard::Phone::other, Shared::VCard::Phone::none, prefered);
                 }
-                if (work) {
-                    myPhones.emplace_back(ph.number(), Shared::VCard::Phone::other, Shared::VCard::Phone::work, prefered);
-                }
-            } else {
-                myPhones.emplace_back(ph.number(), Shared::VCard::Phone::other, Shared::VCard::Phone::none, prefered);
             }
         }
     }
diff --git a/global.h b/global.h
index ccb7317..d5d206e 100644
--- a/global.h
+++ b/global.h
@@ -457,7 +457,9 @@ static const std::map<QString, std::pair<QString, QString>> icons = {
     {"state-ok", {"state-ok", "state-ok"}}, 
     {"state-error", {"state-error", "state-error"}},
     
+    {"edit-copy", {"edit-copy", "copy"}},
     {"edit-delete", {"edit-delete", "edit-delete"}},
+    {"edit-rename", {"edit-rename", "edit-rename"}},
     {"mail-message", {"mail-message", "mail-message"}},
     {"mail-attachment", {"mail-attachment", "mail-attachment"}},
     {"network-connect", {"network-connect", "network-connect"}},
@@ -469,9 +471,11 @@ static const std::map<QString, std::pair<QString, QString>> icons = {
     {"clean", {"edit-clear-all", "clean"}},
     {"user", {"user", "user"}},
     {"user-properties", {"user-properties", "user-properties"}},
-    {"edit-rename", {"edit-rename", "edit-rename"}},
     {"group", {"group", "group"}},
     {"group-new", {"resurce-group-new", "group-new"}},
+    {"favorite", {"favorite", "favorite"}},
+    {"unfavorite", {"draw-star", "unfavorite"}},
+    {"list-add", {"list-add", "add"}},
 };
 
 };
diff --git a/resources/images/fallback/dark/big/add.svg b/resources/images/fallback/dark/big/add.svg
new file mode 100644
index 0000000..f9bae3c
--- /dev/null
+++ b/resources/images/fallback/dark/big/add.svg
@@ -0,0 +1,14 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
+  <defs id="defs3051">
+    <style type="text/css" id="current-color-scheme">
+      .ColorScheme-Text {
+        color:#232629;
+      }
+      </style>
+  </defs>
+ <path 
+    style="fill:currentColor;fill-opacity:1;stroke:none" 
+    d="M 10 4 L 10 11 L 3 11 L 3 12 L 10 12 L 10 19 L 11 19 L 11 12 L 18 12 L 18 11 L 11 11 L 11 4 L 10 4 z "
+    class="ColorScheme-Text"
+    />  
+</svg>
diff --git a/resources/images/fallback/dark/big/copy.svg b/resources/images/fallback/dark/big/copy.svg
new file mode 100644
index 0000000..7470e5f
--- /dev/null
+++ b/resources/images/fallback/dark/big/copy.svg
@@ -0,0 +1,14 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
+  <defs id="defs3051">
+    <style type="text/css" id="current-color-scheme">
+      .ColorScheme-Text {
+        color:#232629;
+      }
+      </style>
+  </defs>
+ <path 
+    style="fill:currentColor;fill-opacity:1;stroke:none" 
+	d="m4 3v1 13h1 2 1v1 1h6l4-4v-1-7-1h-2v-3h-1-10-1m1 1h10v2h-7v1 9h-1-2v-12m4 3h8v7h-3-1v1 3h-4v-11"
+    class="ColorScheme-Text"
+    />  
+</svg>
diff --git a/resources/images/fallback/dark/big/favorite.svg b/resources/images/fallback/dark/big/favorite.svg
new file mode 100644
index 0000000..5751db6
--- /dev/null
+++ b/resources/images/fallback/dark/big/favorite.svg
@@ -0,0 +1,14 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
+  <defs id="defs3051">
+    <style type="text/css" id="current-color-scheme">
+      .ColorScheme-Text {
+        color:#232629;
+      }
+      </style>
+  </defs>
+ <path 
+    style="fill:currentColor;fill-opacity:1;stroke:none" 
+    d="M 11,3 8.523,8.266 3,9.11 7,13.21 6.055,19 11,16.266 15.945,19 15,13.21 19,9.11 13.477,8.266 11,3 Z"
+    class="ColorScheme-Text"
+    />  
+</svg>
diff --git a/resources/images/fallback/dark/big/unfavorite.svg b/resources/images/fallback/dark/big/unfavorite.svg
new file mode 100644
index 0000000..3d128ea
--- /dev/null
+++ b/resources/images/fallback/dark/big/unfavorite.svg
@@ -0,0 +1,14 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
+  <defs id="defs3051">
+    <style type="text/css" id="current-color-scheme">
+      .ColorScheme-Text {
+        color:#232629;
+      }
+      </style>
+  </defs>
+ <path 
+    style="fill:currentColor;fill-opacity:1;stroke:none" 
+	d="M 12,4 9.5273438,9.2675781 4,10.111328 8,14.210938 7.0566406,20 12,17.267578 16.943359,20 16,14.210938 20,10.111328 14.472656,9.2675781 12,4 Z M 12,6 13.853516,9.9492188 18,10.583984 15,13.658203 15.708984,18 12,15.949219 8.2910156,18 9,13.658203 6,10.583984 10.146484,9.9492188 12,6 Z" transform="translate(-.99999-.99999)"
+    class="ColorScheme-Text"
+    />  
+</svg>
diff --git a/resources/images/fallback/dark/small/add.svg b/resources/images/fallback/dark/small/add.svg
new file mode 100644
index 0000000..8779062
--- /dev/null
+++ b/resources/images/fallback/dark/small/add.svg
@@ -0,0 +1,13 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
+  <defs id="defs3051">
+    <style type="text/css" id="current-color-scheme">
+      .ColorScheme-Text {
+        color:#232629;
+      }
+      </style>
+  </defs>
+ <path style="fill:currentColor;fill-opacity:1;stroke:none" 
+     d="M 7 3.0058594 L 7 8 L 2 8 L 2 8.9980469 L 7 8.9980469 L 7 14.007812 L 8 14.007812 L 8 8.9980469 L 13 8.9980469 L 13 8 L 8 8 L 8 3.0058594 L 7 3.0058594 z "
+     class="ColorScheme-Text"
+     />
+</svg>
diff --git a/resources/images/fallback/dark/small/copy.svg b/resources/images/fallback/dark/small/copy.svg
new file mode 100644
index 0000000..061d734
--- /dev/null
+++ b/resources/images/fallback/dark/small/copy.svg
@@ -0,0 +1,13 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
+  <defs id="defs3051">
+    <style type="text/css" id="current-color-scheme">
+      .ColorScheme-Text {
+        color:#232629;
+      }
+      </style>
+  </defs>
+ <path style="fill:currentColor;fill-opacity:1;stroke:none" 
+     d="M 3 2 L 3 13 L 6 13 L 6 14 L 11 14 L 14 11 L 14 4 L 13 4 L 13 2 L 3.7851562 2 L 3 2 z M 4 3 L 12 3 L 12 4 L 6 4 L 6 12 L 4 12 L 4 3 z M 7 5 L 13 5 L 13 10 L 10 10 L 10 13 L 7 13 L 7 5 z "
+     class="ColorScheme-Text"
+     />
+</svg>
diff --git a/resources/images/fallback/dark/small/favorite.svg b/resources/images/fallback/dark/small/favorite.svg
new file mode 100644
index 0000000..5751db6
--- /dev/null
+++ b/resources/images/fallback/dark/small/favorite.svg
@@ -0,0 +1,14 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
+  <defs id="defs3051">
+    <style type="text/css" id="current-color-scheme">
+      .ColorScheme-Text {
+        color:#232629;
+      }
+      </style>
+  </defs>
+ <path 
+    style="fill:currentColor;fill-opacity:1;stroke:none" 
+    d="M 11,3 8.523,8.266 3,9.11 7,13.21 6.055,19 11,16.266 15.945,19 15,13.21 19,9.11 13.477,8.266 11,3 Z"
+    class="ColorScheme-Text"
+    />  
+</svg>
diff --git a/resources/images/fallback/dark/small/unfavorite.svg b/resources/images/fallback/dark/small/unfavorite.svg
new file mode 100644
index 0000000..3d128ea
--- /dev/null
+++ b/resources/images/fallback/dark/small/unfavorite.svg
@@ -0,0 +1,14 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
+  <defs id="defs3051">
+    <style type="text/css" id="current-color-scheme">
+      .ColorScheme-Text {
+        color:#232629;
+      }
+      </style>
+  </defs>
+ <path 
+    style="fill:currentColor;fill-opacity:1;stroke:none" 
+	d="M 12,4 9.5273438,9.2675781 4,10.111328 8,14.210938 7.0566406,20 12,17.267578 16.943359,20 16,14.210938 20,10.111328 14.472656,9.2675781 12,4 Z M 12,6 13.853516,9.9492188 18,10.583984 15,13.658203 15.708984,18 12,15.949219 8.2910156,18 9,13.658203 6,10.583984 10.146484,9.9492188 12,6 Z" transform="translate(-.99999-.99999)"
+    class="ColorScheme-Text"
+    />  
+</svg>
diff --git a/resources/images/fallback/light/big/add.svg b/resources/images/fallback/light/big/add.svg
new file mode 100644
index 0000000..0d6166a
--- /dev/null
+++ b/resources/images/fallback/light/big/add.svg
@@ -0,0 +1,14 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
+  <defs id="defs3051">
+    <style type="text/css" id="current-color-scheme">
+      .ColorScheme-Text {
+        color:#eff0f1;
+      }
+      </style>
+  </defs>
+ <path 
+    style="fill:currentColor;fill-opacity:1;stroke:none" 
+    d="M 10 4 L 10 11 L 3 11 L 3 12 L 10 12 L 10 19 L 11 19 L 11 12 L 18 12 L 18 11 L 11 11 L 11 4 L 10 4 z "
+    class="ColorScheme-Text"
+    />  
+</svg>
diff --git a/resources/images/fallback/light/big/copy.svg b/resources/images/fallback/light/big/copy.svg
new file mode 100644
index 0000000..427de29
--- /dev/null
+++ b/resources/images/fallback/light/big/copy.svg
@@ -0,0 +1,14 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
+  <defs id="defs3051">
+    <style type="text/css" id="current-color-scheme">
+      .ColorScheme-Text {
+        color:#eff0f1;
+      }
+      </style>
+  </defs>
+ <path 
+    style="fill:currentColor;fill-opacity:1;stroke:none" 
+	d="m4 3v1 13h1 2 1v1 1h6l4-4v-1-7-1h-2v-3h-1-10-1m1 1h10v2h-7v1 9h-1-2v-12m4 3h8v7h-3-1v1 3h-4v-11"
+    class="ColorScheme-Text"
+    />  
+</svg>
diff --git a/resources/images/fallback/light/big/favorite.svg b/resources/images/fallback/light/big/favorite.svg
new file mode 100644
index 0000000..f6025c6
--- /dev/null
+++ b/resources/images/fallback/light/big/favorite.svg
@@ -0,0 +1,14 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
+  <defs id="defs3051">
+    <style type="text/css" id="current-color-scheme">
+      .ColorScheme-Text {
+        color:#eff0f1;
+      }
+      </style>
+  </defs>
+ <path 
+    style="fill:currentColor;fill-opacity:1;stroke:none" 
+    d="M 11,3 8.523,8.266 3,9.11 7,13.21 6.055,19 11,16.266 15.945,19 15,13.21 19,9.11 13.477,8.266 11,3 Z"
+    class="ColorScheme-Text"
+    />  
+</svg>
diff --git a/resources/images/fallback/light/big/unfavorite.svg b/resources/images/fallback/light/big/unfavorite.svg
new file mode 100644
index 0000000..5eef7a3
--- /dev/null
+++ b/resources/images/fallback/light/big/unfavorite.svg
@@ -0,0 +1,14 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
+  <defs id="defs3051">
+    <style type="text/css" id="current-color-scheme">
+      .ColorScheme-Text {
+        color:#eff0f1;
+      }
+      </style>
+  </defs>
+ <path 
+    style="fill:currentColor;fill-opacity:1;stroke:none" 
+	d="M 12,4 9.5273438,9.2675781 4,10.111328 8,14.210938 7.0566406,20 12,17.267578 16.943359,20 16,14.210938 20,10.111328 14.472656,9.2675781 12,4 Z M 12,6 13.853516,9.9492188 18,10.583984 15,13.658203 15.708984,18 12,15.949219 8.2910156,18 9,13.658203 6,10.583984 10.146484,9.9492188 12,6 Z" transform="translate(-.99999-.99999)"
+    class="ColorScheme-Text"
+    />  
+</svg>
diff --git a/resources/images/fallback/light/small/add.svg b/resources/images/fallback/light/small/add.svg
new file mode 100644
index 0000000..f05db06
--- /dev/null
+++ b/resources/images/fallback/light/small/add.svg
@@ -0,0 +1,13 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
+  <defs id="defs3051">
+    <style type="text/css" id="current-color-scheme">
+      .ColorScheme-Text {
+        color:#eff0f1;
+      }
+      </style>
+  </defs>
+ <path style="fill:currentColor;fill-opacity:1;stroke:none" 
+     d="M 7 3.0058594 L 7 8 L 2 8 L 2 8.9980469 L 7 8.9980469 L 7 14.007812 L 8 14.007812 L 8 8.9980469 L 13 8.9980469 L 13 8 L 8 8 L 8 3.0058594 L 7 3.0058594 z "
+     class="ColorScheme-Text"
+     />
+</svg>
diff --git a/resources/images/fallback/light/small/copy.svg b/resources/images/fallback/light/small/copy.svg
new file mode 100644
index 0000000..c557cef
--- /dev/null
+++ b/resources/images/fallback/light/small/copy.svg
@@ -0,0 +1,13 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
+  <defs id="defs3051">
+    <style type="text/css" id="current-color-scheme">
+      .ColorScheme-Text {
+        color:#eff0f1;
+      }
+      </style>
+  </defs>
+ <path style="fill:currentColor;fill-opacity:1;stroke:none" 
+     d="M 3 2 L 3 13 L 6 13 L 6 14 L 11 14 L 14 11 L 14 4 L 13 4 L 13 2 L 3.7851562 2 L 3 2 z M 4 3 L 12 3 L 12 4 L 6 4 L 6 12 L 4 12 L 4 3 z M 7 5 L 13 5 L 13 10 L 10 10 L 10 13 L 7 13 L 7 5 z "
+     class="ColorScheme-Text"
+     />
+</svg>
diff --git a/resources/images/fallback/light/small/favorite.svg b/resources/images/fallback/light/small/favorite.svg
new file mode 100644
index 0000000..f6025c6
--- /dev/null
+++ b/resources/images/fallback/light/small/favorite.svg
@@ -0,0 +1,14 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
+  <defs id="defs3051">
+    <style type="text/css" id="current-color-scheme">
+      .ColorScheme-Text {
+        color:#eff0f1;
+      }
+      </style>
+  </defs>
+ <path 
+    style="fill:currentColor;fill-opacity:1;stroke:none" 
+    d="M 11,3 8.523,8.266 3,9.11 7,13.21 6.055,19 11,16.266 15.945,19 15,13.21 19,9.11 13.477,8.266 11,3 Z"
+    class="ColorScheme-Text"
+    />  
+</svg>
diff --git a/resources/images/fallback/light/small/unfavorite.svg b/resources/images/fallback/light/small/unfavorite.svg
new file mode 100644
index 0000000..5eef7a3
--- /dev/null
+++ b/resources/images/fallback/light/small/unfavorite.svg
@@ -0,0 +1,14 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
+  <defs id="defs3051">
+    <style type="text/css" id="current-color-scheme">
+      .ColorScheme-Text {
+        color:#eff0f1;
+      }
+      </style>
+  </defs>
+ <path 
+    style="fill:currentColor;fill-opacity:1;stroke:none" 
+	d="M 12,4 9.5273438,9.2675781 4,10.111328 8,14.210938 7.0566406,20 12,17.267578 16.943359,20 16,14.210938 20,10.111328 14.472656,9.2675781 12,4 Z M 12,6 13.853516,9.9492188 18,10.583984 15,13.658203 15.708984,18 12,15.949219 8.2910156,18 9,13.658203 6,10.583984 10.146484,9.9492188 12,6 Z" transform="translate(-.99999-.99999)"
+    class="ColorScheme-Text"
+    />  
+</svg>
diff --git a/resources/resources.qrc b/resources/resources.qrc
index 3cfaa84..4fb3e5b 100644
--- a/resources/resources.qrc
+++ b/resources/resources.qrc
@@ -36,6 +36,10 @@
     <file>images/fallback/dark/big/group-new.svg</file>
     <file>images/fallback/dark/big/edit-rename.svg</file>
     <file>images/fallback/dark/big/user-properties.svg</file>
+    <file>images/fallback/dark/big/copy.svg</file>
+    <file>images/fallback/dark/big/favorite.svg</file>
+    <file>images/fallback/dark/big/unfavorite.svg</file>
+    <file>images/fallback/dark/big/add.svg</file>
     
     
     <file>images/fallback/dark/small/absent.svg</file>
@@ -72,6 +76,10 @@
     <file>images/fallback/dark/small/group-new.svg</file>
     <file>images/fallback/dark/small/edit-rename.svg</file>
     <file>images/fallback/dark/small/user-properties.svg</file>
+    <file>images/fallback/dark/small/copy.svg</file>
+    <file>images/fallback/dark/small/favorite.svg</file>
+    <file>images/fallback/dark/small/unfavorite.svg</file>
+    <file>images/fallback/dark/small/add.svg</file>
     
     
     <file>images/fallback/light/big/absent.svg</file>
@@ -108,6 +116,10 @@
     <file>images/fallback/light/big/group-new.svg</file>
     <file>images/fallback/light/big/edit-rename.svg</file>
     <file>images/fallback/light/big/user-properties.svg</file>
+    <file>images/fallback/light/big/copy.svg</file>
+    <file>images/fallback/light/big/favorite.svg</file>
+    <file>images/fallback/light/big/unfavorite.svg</file>
+    <file>images/fallback/light/big/add.svg</file>
     
     
     <file>images/fallback/light/small/absent.svg</file>
@@ -144,5 +156,9 @@
     <file>images/fallback/light/small/group-new.svg</file>
     <file>images/fallback/light/small/edit-rename.svg</file>
     <file>images/fallback/light/small/user-properties.svg</file>
+    <file>images/fallback/light/small/copy.svg</file>
+    <file>images/fallback/light/small/favorite.svg</file>
+    <file>images/fallback/light/small/unfavorite.svg</file>
+    <file>images/fallback/light/small/add.svg</file>
 </qresource>
 </RCC>
diff --git a/translations/squawk.ru.ts b/translations/squawk.ru.ts
index bb1fdef..e7bae69 100644
--- a/translations/squawk.ru.ts
+++ b/translations/squawk.ru.ts
@@ -5,92 +5,77 @@
     <name>Account</name>
     <message>
         <location filename="../ui/widgets/account.ui" line="14"/>
-        <location filename="../build/ui/squawkUI_autogen/include/ui_account.h" line="127"/>
         <source>Account</source>
         <translatorcomment>Заголовок окна</translatorcomment>
         <translation>Учетная запись</translation>
     </message>
     <message>
         <location filename="../ui/widgets/account.ui" line="40"/>
-        <location filename="../build/ui/squawkUI_autogen/include/ui_account.h" line="129"/>
         <source>Your account login</source>
         <translation>Имя пользователя Вашей учетной записи</translation>
     </message>
     <message>
         <location filename="../ui/widgets/account.ui" line="43"/>
-        <location filename="../build/ui/squawkUI_autogen/include/ui_account.h" line="131"/>
         <source>john_smith1987</source>
         <translation>ivan_ivanov1987</translation>
     </message>
     <message>
         <location filename="../ui/widgets/account.ui" line="50"/>
-        <location filename="../build/ui/squawkUI_autogen/include/ui_account.h" line="132"/>
         <source>Server</source>
         <translation>Сервер</translation>
     </message>
     <message>
         <location filename="../ui/widgets/account.ui" line="57"/>
-        <location filename="../build/ui/squawkUI_autogen/include/ui_account.h" line="134"/>
         <source>A server address of your account. Like 404.city or macaw.me</source>
         <translation>Адресс сервера вашей учетной записи (выглядит как 404.city или macaw.me)</translation>
     </message>
     <message>
         <location filename="../ui/widgets/account.ui" line="60"/>
-        <location filename="../build/ui/squawkUI_autogen/include/ui_account.h" line="136"/>
         <source>macaw.me</source>
         <translation>macaw.me</translation>
     </message>
     <message>
         <location filename="../ui/widgets/account.ui" line="67"/>
-        <location filename="../build/ui/squawkUI_autogen/include/ui_account.h" line="137"/>
         <source>Login</source>
         <translation>Имя учетной записи</translation>
     </message>
     <message>
         <location filename="../ui/widgets/account.ui" line="74"/>
-        <location filename="../build/ui/squawkUI_autogen/include/ui_account.h" line="138"/>
         <source>Password</source>
         <translation>Пароль</translation>
     </message>
     <message>
         <location filename="../ui/widgets/account.ui" line="81"/>
-        <location filename="../build/ui/squawkUI_autogen/include/ui_account.h" line="140"/>
         <source>Password of your account</source>
         <translation>Пароль вашей учетной записи</translation>
     </message>
     <message>
         <location filename="../ui/widgets/account.ui" line="103"/>
-        <location filename="../build/ui/squawkUI_autogen/include/ui_account.h" line="145"/>
         <source>Name</source>
         <translation>Имя</translation>
     </message>
     <message>
         <location filename="../ui/widgets/account.ui" line="110"/>
-        <location filename="../build/ui/squawkUI_autogen/include/ui_account.h" line="147"/>
         <source>Just a name how would you call this account, doesn&apos;t affect anything</source>
         <translation>Просто имя, то как Вы называете свою учетную запись, может быть любым</translation>
     </message>
     <message>
         <location filename="../ui/widgets/account.ui" line="113"/>
-        <location filename="../build/ui/squawkUI_autogen/include/ui_account.h" line="149"/>
         <source>John</source>
         <translation>Иван</translation>
     </message>
     <message>
         <location filename="../ui/widgets/account.ui" line="120"/>
-        <location filename="../build/ui/squawkUI_autogen/include/ui_account.h" line="150"/>
         <source>Resource</source>
         <translation>Ресурс</translation>
     </message>
     <message>
         <location filename="../ui/widgets/account.ui" line="127"/>
-        <location filename="../build/ui/squawkUI_autogen/include/ui_account.h" line="152"/>
         <source>A resource name like &quot;Home&quot; or &quot;Work&quot;</source>
         <translation>Имя этой программы для ваших контактов, может быть &quot;Home&quot; или &quot;Phone&quot;</translation>
     </message>
     <message>
         <location filename="../ui/widgets/account.ui" line="130"/>
-        <location filename="../build/ui/squawkUI_autogen/include/ui_account.h" line="154"/>
         <source>QXmpp</source>
         <translatorcomment>Ресурс по умолчанию</translatorcomment>
         <translation>QXmpp</translation>
@@ -100,44 +85,38 @@
     <name>Accounts</name>
     <message>
         <location filename="../ui/widgets/accounts.ui" line="14"/>
-        <location filename="../build/ui/squawkUI_autogen/include/ui_accounts.h" line="108"/>
         <source>Accounts</source>
         <translation>Учетные записи</translation>
     </message>
     <message>
         <location filename="../ui/widgets/accounts.ui" line="45"/>
-        <location filename="../build/ui/squawkUI_autogen/include/ui_accounts.h" line="109"/>
         <source>Delete</source>
         <translation>Удалить</translation>
     </message>
     <message>
         <location filename="../ui/widgets/accounts.ui" line="86"/>
-        <location filename="../build/ui/squawkUI_autogen/include/ui_accounts.h" line="110"/>
         <source>Add</source>
         <translation>Добавить</translation>
     </message>
     <message>
         <location filename="../ui/widgets/accounts.ui" line="96"/>
-        <location filename="../build/ui/squawkUI_autogen/include/ui_accounts.h" line="111"/>
         <source>Edit</source>
         <translation>Редактировать</translation>
     </message>
     <message>
         <location filename="../ui/widgets/accounts.ui" line="106"/>
-        <location filename="../build/ui/squawkUI_autogen/include/ui_accounts.h" line="112"/>
         <source>Change password</source>
         <translation>Изменить пароль</translation>
     </message>
     <message>
         <location filename="../ui/widgets/accounts.ui" line="129"/>
-        <location filename="../build/ui/squawkUI_autogen/include/ui_accounts.h" line="113"/>
-        <location filename="../ui/widgets/accounts.cpp" line="126"/>
-        <location filename="../ui/widgets/accounts.cpp" line="129"/>
+        <location filename="../ui/widgets/accounts.cpp" line="125"/>
+        <location filename="../ui/widgets/accounts.cpp" line="128"/>
         <source>Connect</source>
         <translation>Подключить</translation>
     </message>
     <message>
-        <location filename="../ui/widgets/accounts.cpp" line="123"/>
+        <location filename="../ui/widgets/accounts.cpp" line="122"/>
         <source>Disconnect</source>
         <translation>Отключить</translation>
     </message>
@@ -146,10 +125,14 @@
     <name>Conversation</name>
     <message>
         <location filename="../ui/widgets/conversation.ui" line="449"/>
-        <location filename="../build/ui/squawkUI_autogen/include/ui_conversation.h" line="324"/>
         <source>Type your message here...</source>
         <translation>Введите сообщение...</translation>
     </message>
+    <message>
+        <location filename="../ui/widgets/conversation.cpp" line="260"/>
+        <source>Chose a file to send</source>
+        <translation>Выберите файл для отправки</translation>
+    </message>
 </context>
 <context>
     <name>Global</name>
@@ -253,67 +236,97 @@
         <source>Moderator</source>
         <translation>Модератор</translation>
     </message>
+    <message>
+        <source>Not specified</source>
+        <translation>Не указан</translation>
+    </message>
+    <message>
+        <source>Personal</source>
+        <translation>Личный</translation>
+    </message>
+    <message>
+        <source>Business</source>
+        <translation>Рабочий</translation>
+    </message>
+    <message>
+        <source>Fax</source>
+        <translation>Факс</translation>
+    </message>
+    <message>
+        <source>Pager</source>
+        <translation>Пэйджер</translation>
+    </message>
+    <message>
+        <source>Voice</source>
+        <translation>Стационарный</translation>
+    </message>
+    <message>
+        <source>Cell</source>
+        <translation>Мобильный</translation>
+    </message>
+    <message>
+        <source>Video</source>
+        <translation>Видеофон</translation>
+    </message>
+    <message>
+        <source>Modem</source>
+        <translation>Модем</translation>
+    </message>
+    <message>
+        <source>Other</source>
+        <translation>Другой</translation>
+    </message>
 </context>
 <context>
     <name>JoinConference</name>
     <message>
         <location filename="../ui/widgets/joinconference.ui" line="14"/>
-        <location filename="../build/ui/squawkUI_autogen/include/ui_joinconference.h" line="116"/>
         <source>Join new conference</source>
         <translatorcomment>Заголовок окна</translatorcomment>
         <translation>Присоединиться к новой беседе</translation>
     </message>
     <message>
         <location filename="../ui/widgets/joinconference.ui" line="22"/>
-        <location filename="../build/ui/squawkUI_autogen/include/ui_joinconference.h" line="117"/>
         <source>JID</source>
         <translation>JID</translation>
     </message>
     <message>
         <location filename="../ui/widgets/joinconference.ui" line="29"/>
-        <location filename="../build/ui/squawkUI_autogen/include/ui_joinconference.h" line="119"/>
         <source>Room JID</source>
         <translation>Jabber-идентификатор беседы</translation>
     </message>
     <message>
         <location filename="../ui/widgets/joinconference.ui" line="32"/>
-        <location filename="../build/ui/squawkUI_autogen/include/ui_joinconference.h" line="121"/>
         <source>identifier@conference.server.org</source>
         <translation>identifier@conference.server.org</translation>
     </message>
     <message>
         <location filename="../ui/widgets/joinconference.ui" line="39"/>
-        <location filename="../build/ui/squawkUI_autogen/include/ui_joinconference.h" line="122"/>
         <source>Account</source>
         <translation>Учетная запись</translation>
     </message>
     <message>
         <location filename="../ui/widgets/joinconference.ui" line="49"/>
-        <location filename="../build/ui/squawkUI_autogen/include/ui_joinconference.h" line="123"/>
         <source>Join on login</source>
         <translation>Автовход</translation>
     </message>
     <message>
         <location filename="../ui/widgets/joinconference.ui" line="56"/>
-        <location filename="../build/ui/squawkUI_autogen/include/ui_joinconference.h" line="125"/>
         <source>If checked Squawk will try to join this conference on login</source>
         <translation>Если стоит галочка Squawk автоматически присоединится к этой беседе при подключении</translation>
     </message>
     <message>
         <location filename="../ui/widgets/joinconference.ui" line="66"/>
-        <location filename="../build/ui/squawkUI_autogen/include/ui_joinconference.h" line="128"/>
         <source>Nick name</source>
         <translation>Псевдоним</translation>
     </message>
     <message>
         <location filename="../ui/widgets/joinconference.ui" line="73"/>
-        <location filename="../build/ui/squawkUI_autogen/include/ui_joinconference.h" line="130"/>
         <source>Your nick name for that conference. If you leave this field empty your account name will be used as a nick name</source>
         <translation>Ваш псевдоним в этой беседе, если оставите это поле пустым - будет использовано имя Вашей учетной записи</translation>
     </message>
     <message>
         <location filename="../ui/widgets/joinconference.ui" line="76"/>
-        <location filename="../build/ui/squawkUI_autogen/include/ui_joinconference.h" line="132"/>
         <source>John</source>
         <translation>Ivan</translation>
     </message>
@@ -321,24 +334,24 @@
 <context>
     <name>Message</name>
     <message>
-        <location filename="../ui/utils/message.cpp" line="120"/>
+        <location filename="../ui/utils/message.cpp" line="119"/>
         <source>Download</source>
         <translation>Скачать</translation>
     </message>
     <message>
-        <location filename="../ui/utils/message.cpp" line="124"/>
+        <location filename="../ui/utils/message.cpp" line="123"/>
         <source>Error downloading file: %1
 You can try again</source>
         <translation>Ошибка загрузки файла: %1
 Вы можете попробовать снова</translation>
     </message>
     <message>
-        <location filename="../ui/utils/message.cpp" line="126"/>
+        <location filename="../ui/utils/message.cpp" line="125"/>
         <source>%1 is offering you to download a file</source>
         <translation>%1 предлагает Вам скачать файл</translation>
     </message>
     <message>
-        <location filename="../ui/utils/message.cpp" line="191"/>
+        <location filename="../ui/utils/message.cpp" line="190"/>
         <source>Open</source>
         <translation>Открыть</translation>
     </message>
@@ -388,67 +401,67 @@ You can try again</source>
 <context>
     <name>Models::Roster</name>
     <message>
-        <location filename="../ui/models/roster.cpp" line="79"/>
+        <location filename="../ui/models/roster.cpp" line="80"/>
         <source>New messages</source>
         <translation>Есть непрочитанные сообщения</translation>
     </message>
     <message>
-        <location filename="../ui/models/roster.cpp" line="152"/>
         <location filename="../ui/models/roster.cpp" line="178"/>
-        <location filename="../ui/models/roster.cpp" line="215"/>
-        <location filename="../ui/models/roster.cpp" line="227"/>
+        <location filename="../ui/models/roster.cpp" line="204"/>
+        <location filename="../ui/models/roster.cpp" line="241"/>
+        <location filename="../ui/models/roster.cpp" line="253"/>
         <source>New messages: </source>
         <translation>Новых сообщений: </translation>
     </message>
     <message>
-        <location filename="../ui/models/roster.cpp" line="154"/>
+        <location filename="../ui/models/roster.cpp" line="180"/>
         <source>Jabber ID: </source>
         <translation>Идентификатор: </translation>
     </message>
     <message>
-        <location filename="../ui/models/roster.cpp" line="158"/>
-        <location filename="../ui/models/roster.cpp" line="181"/>
-        <location filename="../ui/models/roster.cpp" line="194"/>
+        <location filename="../ui/models/roster.cpp" line="184"/>
+        <location filename="../ui/models/roster.cpp" line="207"/>
+        <location filename="../ui/models/roster.cpp" line="220"/>
         <source>Availability: </source>
         <translation>Доступность: </translation>
     </message>
     <message>
-        <location filename="../ui/models/roster.cpp" line="162"/>
-        <location filename="../ui/models/roster.cpp" line="184"/>
-        <location filename="../ui/models/roster.cpp" line="197"/>
+        <location filename="../ui/models/roster.cpp" line="188"/>
+        <location filename="../ui/models/roster.cpp" line="210"/>
+        <location filename="../ui/models/roster.cpp" line="223"/>
         <source>Status: </source>
         <translation>Статус: </translation>
     </message>
     <message>
-        <location filename="../ui/models/roster.cpp" line="165"/>
-        <location filename="../ui/models/roster.cpp" line="167"/>
-        <location filename="../ui/models/roster.cpp" line="229"/>
+        <location filename="../ui/models/roster.cpp" line="191"/>
+        <location filename="../ui/models/roster.cpp" line="193"/>
+        <location filename="../ui/models/roster.cpp" line="255"/>
         <source>Subscription: </source>
         <translation>Подписка: </translation>
     </message>
     <message>
-        <location filename="../ui/models/roster.cpp" line="200"/>
+        <location filename="../ui/models/roster.cpp" line="226"/>
         <source>Affiliation: </source>
         <translatorcomment>Я правда не знаю, как это объяснить, не то что перевести</translatorcomment>
         <translation>Причастность: </translation>
     </message>
     <message>
-        <location filename="../ui/models/roster.cpp" line="203"/>
+        <location filename="../ui/models/roster.cpp" line="229"/>
         <source>Role: </source>
         <translation>Роль: </translation>
     </message>
     <message>
-        <location filename="../ui/models/roster.cpp" line="217"/>
+        <location filename="../ui/models/roster.cpp" line="243"/>
         <source>Online contacts: </source>
         <translation>Контакстов в сети: </translation>
     </message>
     <message>
-        <location filename="../ui/models/roster.cpp" line="218"/>
+        <location filename="../ui/models/roster.cpp" line="244"/>
         <source>Total contacts: </source>
         <translation>Всего контактов: </translation>
     </message>
     <message>
-        <location filename="../ui/models/roster.cpp" line="231"/>
+        <location filename="../ui/models/roster.cpp" line="257"/>
         <source>Members: </source>
         <translation>Участников: </translation>
     </message>
@@ -457,57 +470,48 @@ You can try again</source>
     <name>NewContact</name>
     <message>
         <location filename="../ui/widgets/newcontact.ui" line="14"/>
-        <location filename="../build/ui/squawkUI_autogen/include/ui_newcontact.h" line="103"/>
         <source>Add new contact</source>
         <translatorcomment>Заголовок окна</translatorcomment>
         <translation>Добавление нового контакта</translation>
     </message>
     <message>
         <location filename="../ui/widgets/newcontact.ui" line="22"/>
-        <location filename="../build/ui/squawkUI_autogen/include/ui_newcontact.h" line="104"/>
         <source>Account</source>
         <translation>Учетная запись</translation>
     </message>
     <message>
         <location filename="../ui/widgets/newcontact.ui" line="29"/>
-        <location filename="../build/ui/squawkUI_autogen/include/ui_newcontact.h" line="106"/>
         <source>An account that is going to have new contact</source>
         <translation>Учетная запись для которой будет добавлен контакт</translation>
     </message>
     <message>
         <location filename="../ui/widgets/newcontact.ui" line="36"/>
-        <location filename="../build/ui/squawkUI_autogen/include/ui_newcontact.h" line="108"/>
         <source>JID</source>
         <translation>JID</translation>
     </message>
     <message>
         <location filename="../ui/widgets/newcontact.ui" line="43"/>
-        <location filename="../build/ui/squawkUI_autogen/include/ui_newcontact.h" line="110"/>
         <source>Jabber id of your new contact</source>
         <translation>Jabber-идентификатор нового контакта</translation>
     </message>
     <message>
         <location filename="../ui/widgets/newcontact.ui" line="46"/>
-        <location filename="../build/ui/squawkUI_autogen/include/ui_newcontact.h" line="112"/>
         <source>name@server.dmn</source>
         <translatorcomment>Placeholder поля ввода JID</translatorcomment>
         <translation>name@server.dmn</translation>
     </message>
     <message>
         <location filename="../ui/widgets/newcontact.ui" line="53"/>
-        <location filename="../build/ui/squawkUI_autogen/include/ui_newcontact.h" line="113"/>
         <source>Name</source>
         <translation>Имя</translation>
     </message>
     <message>
         <location filename="../ui/widgets/newcontact.ui" line="60"/>
-        <location filename="../build/ui/squawkUI_autogen/include/ui_newcontact.h" line="115"/>
         <source>The way this new contact will be labeled in your roster (optional)</source>
         <translation>То, как будет подписан контакт в вашем списке контактов (не обязательно)</translation>
     </message>
     <message>
         <location filename="../ui/widgets/newcontact.ui" line="63"/>
-        <location filename="../build/ui/squawkUI_autogen/include/ui_newcontact.h" line="117"/>
         <source>John Smith</source>
         <translation>Иван Иванов</translation>
     </message>
@@ -516,87 +520,91 @@ You can try again</source>
     <name>Squawk</name>
     <message>
         <location filename="../ui/squawk.ui" line="14"/>
-        <location filename="../build/ui/squawkUI_autogen/include/ui_squawk.h" line="130"/>
         <source>squawk</source>
         <translation>Squawk</translation>
     </message>
     <message>
-        <location filename="../ui/squawk.ui" line="75"/>
-        <location filename="../build/ui/squawkUI_autogen/include/ui_squawk.h" line="136"/>
+        <location filename="../ui/squawk.ui" line="78"/>
         <source>Settings</source>
         <translation>Настройки</translation>
     </message>
     <message>
-        <location filename="../ui/squawk.ui" line="81"/>
-        <location filename="../build/ui/squawkUI_autogen/include/ui_squawk.h" line="137"/>
+        <location filename="../ui/squawk.ui" line="84"/>
         <source>Squawk</source>
         <translation>Squawk</translation>
     </message>
     <message>
-        <location filename="../ui/squawk.ui" line="96"/>
-        <location filename="../build/ui/squawkUI_autogen/include/ui_squawk.h" line="131"/>
+        <location filename="../ui/squawk.ui" line="99"/>
         <source>Accounts</source>
         <translation>Учетные записи</translation>
     </message>
     <message>
-        <location filename="../ui/squawk.ui" line="105"/>
-        <location filename="../build/ui/squawkUI_autogen/include/ui_squawk.h" line="132"/>
+        <location filename="../ui/squawk.ui" line="108"/>
         <source>Quit</source>
         <translation>Выйти</translation>
     </message>
     <message>
-        <location filename="../ui/squawk.ui" line="117"/>
-        <location filename="../build/ui/squawkUI_autogen/include/ui_squawk.h" line="133"/>
+        <location filename="../ui/squawk.ui" line="120"/>
         <source>Add contact</source>
         <translation>Добавить контакт</translation>
     </message>
     <message>
-        <location filename="../ui/squawk.ui" line="128"/>
-        <location filename="../build/ui/squawkUI_autogen/include/ui_squawk.h" line="134"/>
+        <location filename="../ui/squawk.ui" line="132"/>
         <source>Add conference</source>
         <translation>Присоединиться к беседе</translation>
     </message>
     <message>
-        <location filename="../ui/squawk.cpp" line="529"/>
+        <location filename="../ui/squawk.cpp" line="60"/>
+        <source>Contact list</source>
+        <translation>Список контактов</translation>
+    </message>
+    <message>
+        <location filename="../ui/squawk.cpp" line="549"/>
         <source>Disconnect</source>
         <translation>Отключить</translation>
     </message>
     <message>
-        <location filename="../ui/squawk.cpp" line="535"/>
+        <location filename="../ui/squawk.cpp" line="555"/>
         <source>Connect</source>
         <translation>Подключить</translation>
     </message>
     <message>
-        <location filename="../ui/squawk.cpp" line="541"/>
-        <location filename="../ui/squawk.cpp" line="635"/>
-        <location filename="../ui/squawk.cpp" line="675"/>
+        <location filename="../ui/squawk.cpp" line="561"/>
+        <location filename="../ui/squawk.cpp" line="659"/>
+        <source>VCard</source>
+        <translation>Карточка</translation>
+    </message>
+    <message>
+        <location filename="../ui/squawk.cpp" line="565"/>
+        <location filename="../ui/squawk.cpp" line="663"/>
+        <location filename="../ui/squawk.cpp" line="703"/>
         <source>Remove</source>
         <translation>Удалить</translation>
     </message>
     <message>
-        <location filename="../ui/squawk.cpp" line="553"/>
+        <location filename="../ui/squawk.cpp" line="577"/>
         <source>Open dialog</source>
         <translation>Открыть диалог</translation>
     </message>
     <message>
-        <location filename="../ui/squawk.cpp" line="563"/>
-        <location filename="../ui/squawk.cpp" line="656"/>
+        <location filename="../ui/squawk.cpp" line="587"/>
+        <location filename="../ui/squawk.cpp" line="684"/>
         <source>Unsubscribe</source>
         <translation>Отписаться</translation>
     </message>
     <message>
-        <location filename="../ui/squawk.cpp" line="573"/>
-        <location filename="../ui/squawk.cpp" line="665"/>
+        <location filename="../ui/squawk.cpp" line="597"/>
+        <location filename="../ui/squawk.cpp" line="693"/>
         <source>Subscribe</source>
         <translation>Подписаться</translation>
     </message>
     <message>
-        <location filename="../ui/squawk.cpp" line="584"/>
+        <location filename="../ui/squawk.cpp" line="608"/>
         <source>Rename</source>
         <translation>Переименовать</translation>
     </message>
     <message>
-        <location filename="../ui/squawk.cpp" line="597"/>
+        <location filename="../ui/squawk.cpp" line="621"/>
         <source>Input new name for %1
 or leave it empty for the contact 
 to be displayed as %1</source>
@@ -606,34 +614,245 @@ to be displayed as %1</source>
 %1</translation>
     </message>
     <message>
-        <location filename="../ui/squawk.cpp" line="598"/>
+        <location filename="../ui/squawk.cpp" line="622"/>
         <source>Renaming %1</source>
         <translation>Назначение имени контакту %1</translation>
     </message>
     <message>
-        <location filename="../ui/squawk.cpp" line="604"/>
+        <location filename="../ui/squawk.cpp" line="628"/>
         <source>Groups</source>
         <translation>Группы</translation>
     </message>
     <message>
-        <location filename="../ui/squawk.cpp" line="619"/>
+        <location filename="../ui/squawk.cpp" line="643"/>
         <source>New group</source>
         <translation>Создать новую группу</translation>
     </message>
     <message>
-        <location filename="../ui/squawk.cpp" line="629"/>
+        <location filename="../ui/squawk.cpp" line="653"/>
         <source>New group name</source>
         <translation>Имя группы</translation>
     </message>
     <message>
-        <location filename="../ui/squawk.cpp" line="630"/>
+        <location filename="../ui/squawk.cpp" line="654"/>
         <source>Add %1 to a new group</source>
         <translation>Добавление %1 в новую группу</translation>
     </message>
     <message>
-        <location filename="../ui/squawk.cpp" line="647"/>
+        <location filename="../ui/squawk.cpp" line="675"/>
         <source>Open conversation</source>
         <translation>Открыть окно беседы</translation>
     </message>
+    <message>
+        <location filename="../ui/squawk.cpp" line="780"/>
+        <source>%1 account card</source>
+        <translation>Карточка учетной записи %1</translation>
+    </message>
+    <message>
+        <location filename="../ui/squawk.cpp" line="782"/>
+        <source>%1 contact card</source>
+        <translation>Карточка контакта %1</translation>
+    </message>
+    <message>
+        <location filename="../ui/squawk.cpp" line="794"/>
+        <source>Downloading vCard</source>
+        <translation>Получение карточки</translation>
+    </message>
+</context>
+<context>
+    <name>VCard</name>
+    <message>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="65"/>
+        <source>Received 12.07.2007 at 17.35</source>
+        <translation>Не обновлялось</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="100"/>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="425"/>
+        <source>General</source>
+        <translation>Общее</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="130"/>
+        <source>Organization</source>
+        <translation>Место работы</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="180"/>
+        <source>Middle name</source>
+        <translation>Среднее имя</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="190"/>
+        <source>First name</source>
+        <translation>Имя</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="200"/>
+        <source>Last name</source>
+        <translation>Фамилия</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="226"/>
+        <source>Nick name</source>
+        <translation>Псевдоним</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="252"/>
+        <source>Birthday</source>
+        <translation>Дата рождения</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="272"/>
+        <source>Organization name</source>
+        <translation>Название организации</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="298"/>
+        <source>Unit / Department</source>
+        <translation>Отдел</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="324"/>
+        <source>Role / Profession</source>
+        <translation>Профессия</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="350"/>
+        <source>Job title</source>
+        <translation>Наименование должности</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="390"/>
+        <source>Full name</source>
+        <translation>Полное имя</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="460"/>
+        <source>Personal information</source>
+        <translation>Личная информация</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="653"/>
+        <source>Addresses</source>
+        <translation>Адреса</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="682"/>
+        <source>E-Mail addresses</source>
+        <translation>Адреса электронной почты</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="767"/>
+        <source>Phone numbers</source>
+        <translation>Номера телефонов</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="522"/>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="546"/>
+        <source>Contact</source>
+        <translation>Контактная информация</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="713"/>
+        <source>Jabber ID</source>
+        <translation>Jabber ID</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="739"/>
+        <source>Web site</source>
+        <translation>Веб сайт</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="798"/>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="822"/>
+        <source>Description</source>
+        <translation>Описание</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="859"/>
+        <source>Set avatar</source>
+        <translation>Установить иконку</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="868"/>
+        <source>Clear avatar</source>
+        <translation>Убрать иконку</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/vcard/vcard.cpp" line="93"/>
+        <source>Account %1 card</source>
+        <translation>Карточка учетной записи %1</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/vcard/vcard.cpp" line="108"/>
+        <source>Contact %1 card</source>
+        <translation>Карточка контакта %1</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/vcard/vcard.cpp" line="170"/>
+        <source>Received %1 at %2</source>
+        <translation>Получено %1 в %2</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/vcard/vcard.cpp" line="221"/>
+        <source>Chose your new avatar</source>
+        <translation>Выберите новую иконку</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/vcard/vcard.cpp" line="223"/>
+        <source>Images (*.png *.jpg *.jpeg)</source>
+        <translation>Изображения (*.png *.jpg *.jpeg)</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/vcard/vcard.cpp" line="310"/>
+        <source>Add email address</source>
+        <translation>Добавить адрес электронной почты</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/vcard/vcard.cpp" line="320"/>
+        <source>Unset this email as preferred</source>
+        <translation>Убрать отметку &quot;предпочтительный&quot; с этого адреса</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/vcard/vcard.cpp" line="323"/>
+        <source>Set this email as preferred</source>
+        <translation>Отметить этот адрес как &quot;предпочтительный&quot;</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/vcard/vcard.cpp" line="328"/>
+        <source>Remove selected email addresses</source>
+        <translation>Удалить выбранные адреса</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/vcard/vcard.cpp" line="333"/>
+        <source>Copy selected emails to clipboard</source>
+        <translation>Скопировать выбранные адреса в буфер обмена</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/vcard/vcard.cpp" line="338"/>
+        <source>Add phone number</source>
+        <translation>Добавить номер телефона</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/vcard/vcard.cpp" line="348"/>
+        <source>Unset this phone as preferred</source>
+        <translation>Убрать отметку &quot;предпочтительный&quot; с этого номера</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/vcard/vcard.cpp" line="351"/>
+        <source>Set this phone as preferred</source>
+        <translation>Отметить этот номер как &quot;предпочтительный&quot;</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/vcard/vcard.cpp" line="356"/>
+        <source>Remove selected phone numbers</source>
+        <translation>Удалить выбранные телефонные номера</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/vcard/vcard.cpp" line="361"/>
+        <source>Copy selected phones to clipboard</source>
+        <translation>Скопировать выбранные телефонные номера в буфер обмена</translation>
+    </message>
 </context>
 </TS>
diff --git a/ui/widgets/vcard/emailsmodel.cpp b/ui/widgets/vcard/emailsmodel.cpp
index 18838ee..4044322 100644
--- a/ui/widgets/vcard/emailsmodel.cpp
+++ b/ui/widgets/vcard/emailsmodel.cpp
@@ -52,7 +52,7 @@ QVariant UI::VCard::EMailsModel::data(const QModelIndex& index, int role) const
             case 1:
                 switch (role) {
                     case Qt::DisplayRole:
-                        return tr(Shared::VCard::Email::roleNames[deque[index.row()].role].toStdString().c_str());
+                        return QCoreApplication::translate("Global", Shared::VCard::Email::roleNames[deque[index.row()].role].toStdString().c_str());
                     case Qt::EditRole: 
                         return deque[index.row()].role;
                     default:
diff --git a/ui/widgets/vcard/phonesmodel.cpp b/ui/widgets/vcard/phonesmodel.cpp
index 4371dff..df9cad6 100644
--- a/ui/widgets/vcard/phonesmodel.cpp
+++ b/ui/widgets/vcard/phonesmodel.cpp
@@ -52,7 +52,7 @@ QVariant UI::VCard::PhonesModel::data(const QModelIndex& index, int role) const
             case 1:
                 switch (role) {
                     case Qt::DisplayRole:
-                        return tr(Shared::VCard::Phone::roleNames[deque[index.row()].role].toStdString().c_str());
+                        return QCoreApplication::translate("Global", Shared::VCard::Phone::roleNames[deque[index.row()].role].toStdString().c_str());
                     case Qt::EditRole: 
                         return deque[index.row()].role;
                     default:
@@ -62,7 +62,7 @@ QVariant UI::VCard::PhonesModel::data(const QModelIndex& index, int role) const
             case 2:
                 switch (role) {
                     case Qt::DisplayRole:
-                        return tr(Shared::VCard::Phone::typeNames[deque[index.row()].type].toStdString().c_str());
+                        return QCoreApplication::translate("Global", Shared::VCard::Phone::typeNames[deque[index.row()].type].toStdString().c_str());
                     case Qt::EditRole: 
                         return deque[index.row()].type;
                     default:
diff --git a/ui/widgets/vcard/vcard.cpp b/ui/widgets/vcard/vcard.cpp
index d66d6cd..44b5aa3 100644
--- a/ui/widgets/vcard/vcard.cpp
+++ b/ui/widgets/vcard/vcard.cpp
@@ -55,17 +55,17 @@ VCard::VCard(const QString& jid, bool edit, QWidget* parent):
     setAvatar->setEnabled(true);
     clearAvatar->setEnabled(false);
     
-    roleDelegate->addEntry(tr(Shared::VCard::Email::roleNames[0].toStdString().c_str()));
-    roleDelegate->addEntry(tr(Shared::VCard::Email::roleNames[1].toStdString().c_str()));
-    roleDelegate->addEntry(tr(Shared::VCard::Email::roleNames[2].toStdString().c_str()));
+    roleDelegate->addEntry(QCoreApplication::translate("Global", Shared::VCard::Email::roleNames[0].toStdString().c_str()));
+    roleDelegate->addEntry(QCoreApplication::translate("Global", Shared::VCard::Email::roleNames[1].toStdString().c_str()));
+    roleDelegate->addEntry(QCoreApplication::translate("Global", Shared::VCard::Email::roleNames[2].toStdString().c_str()));
     
-    phoneTypeDelegate->addEntry(tr(Shared::VCard::Phone::typeNames[0].toStdString().c_str()));
-    phoneTypeDelegate->addEntry(tr(Shared::VCard::Phone::typeNames[1].toStdString().c_str()));
-    phoneTypeDelegate->addEntry(tr(Shared::VCard::Phone::typeNames[2].toStdString().c_str()));
-    phoneTypeDelegate->addEntry(tr(Shared::VCard::Phone::typeNames[3].toStdString().c_str()));
-    phoneTypeDelegate->addEntry(tr(Shared::VCard::Phone::typeNames[4].toStdString().c_str()));
-    phoneTypeDelegate->addEntry(tr(Shared::VCard::Phone::typeNames[5].toStdString().c_str()));
-    phoneTypeDelegate->addEntry(tr(Shared::VCard::Phone::typeNames[6].toStdString().c_str()));
+    phoneTypeDelegate->addEntry(QCoreApplication::translate("Global", Shared::VCard::Phone::typeNames[0].toStdString().c_str()));
+    phoneTypeDelegate->addEntry(QCoreApplication::translate("Global", Shared::VCard::Phone::typeNames[1].toStdString().c_str()));
+    phoneTypeDelegate->addEntry(QCoreApplication::translate("Global", Shared::VCard::Phone::typeNames[2].toStdString().c_str()));
+    phoneTypeDelegate->addEntry(QCoreApplication::translate("Global", Shared::VCard::Phone::typeNames[3].toStdString().c_str()));
+    phoneTypeDelegate->addEntry(QCoreApplication::translate("Global", Shared::VCard::Phone::typeNames[4].toStdString().c_str()));
+    phoneTypeDelegate->addEntry(QCoreApplication::translate("Global", Shared::VCard::Phone::typeNames[5].toStdString().c_str()));
+    phoneTypeDelegate->addEntry(QCoreApplication::translate("Global", Shared::VCard::Phone::typeNames[6].toStdString().c_str()));
     
     m_ui->emailsView->setContextMenuPolicy(Qt::CustomContextMenu);
     m_ui->emailsView->setModel(&emails);
@@ -317,7 +317,7 @@ void VCard::onContextMenu(const QPoint& point)
                 if (selectionSize == 1) {
                     int row = sm->selectedRows().at(0).row();
                     if (emails.isPreferred(row)) {
-                        QAction* rev = contextMenu->addAction(Shared::icon("view-media-favorite"), tr("Unset this email as preferred"));
+                        QAction* rev = contextMenu->addAction(Shared::icon("unfavorite"), tr("Unset this email as preferred"));
                         connect(rev, &QAction::triggered, std::bind(&UI::VCard::EMailsModel::revertPreferred, &emails, row));
                     } else {
                         QAction* rev = contextMenu->addAction(Shared::icon("favorite"), tr("Set this email as preferred"));
@@ -325,12 +325,12 @@ void VCard::onContextMenu(const QPoint& point)
                     }
                 }
                 
-                QAction* del = contextMenu->addAction(Shared::icon("remove"), tr("Remove selected email addresses"));
+                QAction* del = contextMenu->addAction(Shared::icon("edit-delete"), tr("Remove selected email addresses"));
                 connect(del, &QAction::triggered, this, &VCard::onRemoveEmail);
             }
         }
         
-        QAction* cp = contextMenu->addAction(Shared::icon("copy"), tr("Copy selected emails to clipboard"));
+        QAction* cp = contextMenu->addAction(Shared::icon("edit-copy"), tr("Copy selected emails to clipboard"));
         connect(cp, &QAction::triggered, this, &VCard::onCopyEmail);
     } else if (snd == m_ui->phonesView) {
         hasMenu = true;
@@ -353,12 +353,12 @@ void VCard::onContextMenu(const QPoint& point)
                     }
                 }
                 
-                QAction* del = contextMenu->addAction(Shared::icon("remove"), tr("Remove selected phone numbers"));
+                QAction* del = contextMenu->addAction(Shared::icon("edit-delete"), tr("Remove selected phone numbers"));
                 connect(del, &QAction::triggered, this, &VCard::onRemovePhone);
             }
         }
         
-        QAction* cp = contextMenu->addAction(Shared::icon("copy"), tr("Copy selected phones to clipboard"));
+        QAction* cp = contextMenu->addAction(Shared::icon("edit-copy"), tr("Copy selected phones to clipboard"));
         connect(cp, &QAction::triggered, this, &VCard::onCopyPhone);
     }
     
@@ -431,32 +431,30 @@ void VCard::onRemovePhone()
 
 void VCard::onCopyEmail()
 {
-    QItemSelection selection(m_ui->emailsView->selectionModel()->selection());
+    QList<QModelIndex> selection(m_ui->emailsView->selectionModel()->selectedRows());
     
     QList<QString> addrs;
-    for (const QModelIndex& index : selection.indexes()) {
+    for (const QModelIndex& index : selection) {
         addrs.push_back(emails.getEmail(index.row()));
     }
     
     QString list = addrs.join("\n");
     
-    qDebug() << list;
     QClipboard* cb = QApplication::clipboard();
     cb->setText(list);
 }
 
 void VCard::onCopyPhone()
 {
-    QItemSelection selection(m_ui->phonesView->selectionModel()->selection());
+    QList<QModelIndex> selection(m_ui->phonesView->selectionModel()->selectedRows());
     
     QList<QString> phs;
-    for (const QModelIndex& index : selection.indexes()) {
+    for (const QModelIndex& index : selection) {
         phs.push_back(phones.getPhone(index.row()));
     }
     
     QString list = phs.join("\n");
     
-    qDebug() << list;
     QClipboard* cb = QApplication::clipboard();
     cb->setText(list);
 }
diff --git a/ui/widgets/vcard/vcard.ui b/ui/widgets/vcard/vcard.ui
index e08bf49..26db8f9 100644
--- a/ui/widgets/vcard/vcard.ui
+++ b/ui/widgets/vcard/vcard.ui
@@ -124,10 +124,10 @@
             </sizepolicy>
            </property>
            <property name="styleSheet">
-            <string notr="true"/>
+            <string notr="true">font: 600 16pt;</string>
            </property>
            <property name="text">
-            <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:16pt; font-weight:600;&quot;&gt;Organization&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+            <string>Organization</string>
            </property>
            <property name="alignment">
             <set>Qt::AlignCenter</set>
@@ -418,8 +418,11 @@
          </item>
          <item row="0" column="0" colspan="4">
           <widget class="QLabel" name="generalHeading">
+           <property name="styleSheet">
+            <string notr="true">font: 600 24pt ;</string>
+           </property>
            <property name="text">
-            <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:24pt; font-weight:600;&quot;&gt;General&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+            <string>General</string>
            </property>
           </widget>
          </item>
@@ -445,7 +448,7 @@
             </sizepolicy>
            </property>
            <property name="styleSheet">
-            <string notr="true"/>
+            <string notr="true">font: 600 16pt;</string>
            </property>
            <property name="frameShape">
             <enum>QFrame::NoFrame</enum>
@@ -454,7 +457,7 @@
             <enum>QFrame::Plain</enum>
            </property>
            <property name="text">
-            <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:16pt; font-weight:600;&quot;&gt;Personal information&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+            <string>Personal information</string>
            </property>
            <property name="alignment">
             <set>Qt::AlignCenter</set>
@@ -536,8 +539,11 @@
          </property>
          <item>
           <widget class="QLabel" name="contactHeading">
+           <property name="styleSheet">
+            <string notr="true">font: 600 24pt ;</string>
+           </property>
            <property name="text">
-            <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:24pt; font-weight:600;&quot;&gt;Contact&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+            <string>Contact</string>
            </property>
           </widget>
          </item>
@@ -561,7 +567,7 @@
               <x>0</x>
               <y>0</y>
               <width>566</width>
-              <height>497</height>
+              <height>498</height>
              </rect>
             </property>
             <layout class="QGridLayout" name="gridLayout_3" columnstretch="1,3,1">
@@ -640,8 +646,11 @@
              </item>
              <item row="8" column="1">
               <widget class="QLabel" name="addressesHeading">
+               <property name="styleSheet">
+                <string notr="true">font: 600 16pt;</string>
+               </property>
                <property name="text">
-                <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:16pt; font-weight:600;&quot;&gt;Addresses&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+                <string>Addresses</string>
                </property>
                <property name="alignment">
                 <set>Qt::AlignCenter</set>
@@ -666,8 +675,11 @@
              </item>
              <item row="2" column="1">
               <widget class="QLabel" name="emailsHeading">
+               <property name="styleSheet">
+                <string notr="true">font: 600 16pt;</string>
+               </property>
                <property name="text">
-                <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:16pt; font-weight:600;&quot;&gt;E-Mail addresses&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+                <string>E-Mail addresses</string>
                </property>
                <property name="alignment">
                 <set>Qt::AlignCenter</set>
@@ -748,8 +760,11 @@
              </item>
              <item row="5" column="1">
               <widget class="QLabel" name="phenesHeading">
+               <property name="styleSheet">
+                <string notr="true">font: 600 16pt;</string>
+               </property>
                <property name="text">
-                <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:16pt; font-weight:600;&quot;&gt;Phone numbers&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+                <string>Phone numbers</string>
                </property>
                <property name="alignment">
                 <set>Qt::AlignCenter</set>
@@ -800,8 +815,11 @@
          </property>
          <item row="0" column="0">
           <widget class="QLabel" name="descriptionHeading">
+           <property name="styleSheet">
+            <string notr="true">font: 600 24pt ;</string>
+           </property>
            <property name="text">
-            <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:24pt; font-weight:600;&quot;&gt;Description&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+            <string>Description</string>
            </property>
           </widget>
          </item>

From 3f710e26d01b3254b01346fdb08de0de84796669 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Sat, 9 Nov 2019 17:04:27 +0300
Subject: [PATCH 023/281] some more work about uploading, not done yet

---
 core/account.cpp       | 93 +++++++++++++++++++++++++++++++++++++++++-
 core/account.h         | 19 +++++++--
 core/networkaccess.cpp | 54 +++++++++++++++++++-----
 core/networkaccess.h   |  6 +++
 core/squawk.cpp        |  8 +++-
 5 files changed, 164 insertions(+), 16 deletions(-)

diff --git a/core/account.cpp b/core/account.cpp
index e3dd29f..49746b7 100644
--- a/core/account.cpp
+++ b/core/account.cpp
@@ -23,7 +23,7 @@
 
 using namespace Core;
 
-Account::Account(const QString& p_login, const QString& p_server, const QString& p_password, const QString& p_name, QObject* parent):
+Account::Account(const QString& p_login, const QString& p_server, const QString& p_password, const QString& p_name, NetworkAccess* p_net, QObject* parent):
     QObject(parent),
     name(p_name),
     achiveQueries(),
@@ -38,15 +38,19 @@ Account::Account(const QString& p_login, const QString& p_server, const QString&
     bm(new QXmppBookmarkManager()),
     rm(client.findExtension<QXmppRosterManager>()),
     vm(client.findExtension<QXmppVCardManager>()),
+    um(new QXmppUploadRequestManager()),
     contacts(),
     conferences(),
     maxReconnectTimes(0),
     reconnectTimes(0),
     queuedContacts(),
     outOfRosterContacts(),
+    pendingMessages(),
+    uploadingSlotsQueue(),
     avatarHash(),
     avatarType(),
-    ownVCardRequestInProgress(false)
+    ownVCardRequestInProgress(false),
+    network(p_net)
 {
     config.setUser(p_login);
     config.setDomain(p_server);
@@ -85,6 +89,10 @@ Account::Account(const QString& p_login, const QString& p_server, const QString&
     QObject::connect(vm, &QXmppVCardManager::vCardReceived, this, &Account::onVCardReceived);
     //QObject::connect(&vm, &QXmppVCardManager::clientVCardReceived, this, &Account::onOwnVCardReceived); //for some reason it doesn't work, launching from common handler
     
+    client.addExtension(um);
+    QObject::connect(um, &QXmppUploadRequestManager::slotReceived, this, &Account::onUploadSlotReceived);
+    QObject::connect(um, &QXmppUploadRequestManager::requestFailed, this, &Account::onUploadSlotRequestFailed);
+    
     QString path(QStandardPaths::writableLocation(QStandardPaths::CacheLocation));
     path += "/" + name;
     QDir dir(path);
@@ -141,6 +149,7 @@ Account::~Account()
         delete itr->second;
     }
     
+    delete um;
     delete bm;
     delete mm;
     delete am;
@@ -623,6 +632,44 @@ void Core::Account::sendMessage(const Shared::Message& data)
     }
 }
 
+void Core::Account::sendMessage(const Shared::Message& data, const QString& path)
+{
+    if (state == Shared::connected) {
+        QString url = network->getFileRemoteUrl(path);
+        if (url.size() != 0) {
+            sendMessageWithLocalUploadedFile(data, url);
+        } else {
+            if (network->isUploading(path, data.getId())) {
+                pendingMessages.emplace(data.getId(), data);
+            } else {
+                if (um->serviceFound()) {
+                    QFileInfo file(path);
+                    if (file.exists() && file.isReadable()) {
+                        uploadingSlotsQueue.emplace_back(path, data);
+                        if (uploadingSlotsQueue.size() == 1) {
+                            um->requestUploadSlot(file);
+                        }
+                    } else {
+                        qDebug() << "Requested upload slot in account" << name << "for file" << path << "but the file doesn't exist or is not readable";
+                    }
+                } else {
+                    qDebug() << "Requested upload slot in account" << name << "for file" << path << "but upload manager didn't discover any upload services";
+                }
+            }
+        }
+    } else {
+        qDebug() << "An attempt to send message with not connected account " << name << ", skipping";
+    }
+}
+
+void Core::Account::sendMessageWithLocalUploadedFile(Shared::Message msg, const QString& url)
+{
+    msg.setOutOfBandUrl(url);
+    sendMessage(msg);
+    //TODO removal/progress update
+}
+
+
 void Core::Account::onCarbonMessageReceived(const QXmppMessage& msg)
 {
     handleChatMessage(msg, false, true);
@@ -1551,3 +1598,45 @@ void Core::Account::uploadVCard(const Shared::VCard& card)
     vm->setClientVCard(iq);
     onOwnVCardReceived(iq);
 }
+
+void Core::Account::onUploadSlotReceived(const QXmppHttpUploadSlotIq& slot)
+{
+    if (uploadingSlotsQueue.size() == 0) {
+        qDebug() << "HTTP Upload manager of account" << name << "reports about success requesting upload slot, but none was requested";
+    } else {
+        const std::pair<QString, Shared::Message>& pair = uploadingSlotsQueue.front();
+        const QString& mId = pair.second.getId();
+        network->uploadFile(mId, pair.first, slot.putUrl(), slot.getUrl(), slot.putHeaders());
+        pendingMessages.emplace(mId, pair.second);
+        uploadingSlotsQueue.pop_front();
+        
+        if (uploadingSlotsQueue.size() > 0) {
+            um->requestUploadSlot(uploadingSlotsQueue.front().first);
+        }
+    }
+}
+
+void Core::Account::onUploadSlotRequestFailed(const QXmppHttpUploadRequestIq& request)
+{
+    if (uploadingSlotsQueue.size() == 0) {
+        qDebug() << "HTTP Upload manager of account" << name << "reports about an error requesting upload slot, but none was requested";
+        qDebug() << request.error().text();
+    } else {
+        const std::pair<QString, Shared::Message>& pair = uploadingSlotsQueue.front();
+        qDebug() << "Error requesting upload slot for file" << pair.first << "in account" << name << ":" << request.error().text();
+        
+        if (uploadingSlotsQueue.size() > 0) {
+            um->requestUploadSlot(uploadingSlotsQueue.front().first);
+        }
+        uploadingSlotsQueue.pop_front();
+    }
+}
+
+void Core::Account::onFileUploaded(const QString& messageId, const QString& url)
+{
+    std::map<QString, Shared::Message>::const_iterator itr = pendingMessages.find(messageId);
+    if (itr != pendingMessages.end()) {
+        sendMessageWithLocalUploadedFile(itr->second, url);
+        pendingMessages.erase(itr);
+    }
+}
diff --git a/core/account.h b/core/account.h
index 262a334..8f22913 100644
--- a/core/account.h
+++ b/core/account.h
@@ -36,12 +36,14 @@
 #include <QXmppClient.h>
 #include <QXmppBookmarkManager.h>
 #include <QXmppBookmarkSet.h>
+#include <QXmppUploadRequestManager.h>
 #include <QXmppHttpUploadIq.h>
 #include <QXmppVCardIq.h>
 #include <QXmppVCardManager.h>
-#include "../global.h"
+#include "global.h"
 #include "contact.h"
 #include "conference.h"
+#include "networkaccess.h"
 
 namespace Core
 {
@@ -50,7 +52,7 @@ class Account : public QObject
 {
     Q_OBJECT
 public:
-    Account(const QString& p_login, const QString& p_server, const QString& p_password, const QString& p_name, QObject* parent = 0);
+    Account(const QString& p_login, const QString& p_server, const QString& p_password, const QString& p_name, NetworkAccess* p_net, QObject* parent = 0);
     ~Account();
     
     void connect();
@@ -74,6 +76,7 @@ public:
     void setAvailability(Shared::Availability avail);
     QString getFullJid() const;
     void sendMessage(const Shared::Message& data);
+    void sendMessage(const Shared::Message& data, const QString& path);
     void requestArchive(const QString& jid, int count, const QString& before);
     void setReconnectTimes(unsigned int times);
     void subscribeToContact(const QString& jid, const QString& reason);
@@ -113,6 +116,7 @@ signals:
     void changeRoomParticipant(const QString& jid, const QString& nickName, const QMap<QString, QVariant>& data);
     void removeRoomParticipant(const QString& jid, const QString& nickName);
     void receivedVCard(const QString& jid, const Shared::VCard& card);
+    void uploadFile(const QFileInfo& file, const QUrl& set, const QUrl& get, QMap<QString, QString> headers);
     
 private:
     QString name;
@@ -128,6 +132,7 @@ private:
     QXmppBookmarkManager* bm;
     QXmppRosterManager* rm;
     QXmppVCardManager* vm;
+    QXmppUploadRequestManager* um;
     std::map<QString, Contact*> contacts;
     std::map<QString, Conference*> conferences;
     unsigned int maxReconnectTimes;
@@ -136,10 +141,13 @@ private:
     std::map<QString, QString> queuedContacts;
     std::set<QString> outOfRosterContacts;
     std::set<QString> pendingVCardRequests;
+    std::map<QString, Shared::Message> pendingMessages;
+    std::deque<std::pair<QString, Shared::Message>> uploadingSlotsQueue;
     
     QString avatarHash;
     QString avatarType;
     bool ownVCardRequestInProgress;
+    NetworkAccess* network;
     
 private slots:
     void onClientConnected();
@@ -184,6 +192,11 @@ private slots:
     
     void onVCardReceived(const QXmppVCardIq& card);
     void onOwnVCardReceived(const QXmppVCardIq& card);
+    
+    void onUploadSlotReceived(const QXmppHttpUploadSlotIq& slot);
+    void onUploadSlotRequestFailed(const QXmppHttpUploadRequestIq& request);
+    void onFileUploaded(const QString& messageId, const QString& url);
+    void onFileUploadError(const QString& messageId, const QString& path);
   
 private:
     void addedAccount(const QString &bareJid);
@@ -200,7 +213,7 @@ private:
     void logMessage(const QXmppMessage& msg, const QString& reason = "Message wasn't handled: ");
     void storeConferences();
     void clearConferences();
-    
+    void sendMessageWithLocalUploadedFile(Shared::Message msg, const QString& url);
 };
 
 void initializeVCard(Shared::VCard& vCard, const QXmppVCardIq& card);
diff --git a/core/networkaccess.cpp b/core/networkaccess.cpp
index 095cf41..ea726f2 100644
--- a/core/networkaccess.cpp
+++ b/core/networkaccess.cpp
@@ -121,7 +121,7 @@ void Core::NetworkAccess::onDownloadProgress(qint64 bytesReceived, qint64 bytesT
     QString url = rpl->url().toString();
     std::map<QString, Transfer*>::const_iterator itr = downloads.find(url);
     if (itr == downloads.end()) {
-        qDebug() << "an error downloading" << url << ": the request had some progress but seems like noone is waiting for it, skipping";
+        qDebug() << "an error downloading" << url << ": the request had some progress but seems like no one is waiting for it, skipping";
     } else {
         Transfer* dwn = itr->second;
         qreal received = bytesReceived;
@@ -140,7 +140,7 @@ void Core::NetworkAccess::onDownloadError(QNetworkReply::NetworkError code)
     QString url = rpl->url().toString();
     std::map<QString, Transfer*>::const_iterator itr = downloads.find(url);
     if (itr == downloads.end()) {
-        qDebug() << "an error downloading" << url << ": the request is reporting an error but seems like noone is waiting for it, skipping";
+        qDebug() << "an error downloading" << url << ": the request is reporting an error but seems like no one is waiting for it, skipping";
     } else {
         QString errorText = getErrorText(code);
         if (errorText.size() > 0) {
@@ -328,7 +328,7 @@ void Core::NetworkAccess::onDownloadFinished()
 
 void Core::NetworkAccess::startDownload(const QString& messageId, const QString& url)
 {
-    Transfer* dwn = new Transfer({{messageId}, 0, 0, true, "", 0});
+    Transfer* dwn = new Transfer({{messageId}, 0, 0, true, "", url, 0});
     QNetworkRequest req(url);
     dwn->reply = manager->get(req);
     connect(dwn->reply, &QNetworkReply::downloadProgress, this, &NetworkAccess::onDownloadProgress);
@@ -363,15 +363,16 @@ void Core::NetworkAccess::onUploadFinished()
     QString url = rpl->url().toString();
     std::map<QString, Transfer*>::const_iterator itr = uploads.find(url);
     if (itr == downloads.end()) {
-        qDebug() << "an error uploading" << url << ": the request is done but seems like noone is waiting for it, skipping";
+        qDebug() << "an error uploading" << url << ": the request is done but seems like no one is waiting for it, skipping";
     } else {
         Transfer* upl = itr->second;
         if (upl->success) {
             qDebug() << "upload success for" << url;
-            files.addRecord(url, upl->path);
+            files.addRecord(upl->url, upl->path);
         
             for (std::set<QString>::const_iterator mItr = upl->messages.begin(), end = upl->messages.end(); mItr != end; ++mItr) {
                 emit fileLocalPathResponse(*mItr, upl->path);
+                emit uploadFileComplete(*mItr, upl->url);
             }
         }
         
@@ -389,7 +390,7 @@ void Core::NetworkAccess::onUploadProgress(qint64 bytesReceived, qint64 bytesTot
     QString url = rpl->url().toString();
     std::map<QString, Transfer*>::const_iterator itr = uploads.find(url);
     if (itr == uploads.end()) {
-        qDebug() << "an error downloading" << url << ": the request had some progress but seems like noone is waiting for it, skipping";
+        qDebug() << "an error downloading" << url << ": the request had some progress but seems like no one is waiting for it, skipping";
     } else {
         Transfer* upl = itr->second;
         qreal received = bytesReceived;
@@ -404,15 +405,15 @@ void Core::NetworkAccess::onUploadProgress(qint64 bytesReceived, qint64 bytesTot
 
 void Core::NetworkAccess::startUpload(const QString& messageId, const QString& url, const QString& path)
 {
-    Transfer* upl = new Transfer({{messageId}, 0, 0, true, path, 0});
+    Transfer* upl = new Transfer({{messageId}, 0, 0, true, path, url, 0});
     QNetworkRequest req(url);
     QFile* file = new QFile(path);
     if (file->open(QIODevice::ReadOnly)) {
         upl->reply = manager->put(req, file);
         
-        connect(upl->reply, SIGNAL(uploadProgress(qint64, qint64)), SLOT(onUploadProgress(qint64, qint64)));
-        connect(upl->reply, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(onUploadError(QNetworkReply::NetworkError)));
-        connect(upl->reply, SIGNAL(finished()), SLOT(onUploadFinished()));
+        connect(upl->reply, &QNetworkReply::uploadProgress, this, &NetworkAccess::onUploadProgress);
+        connect(upl->reply, qOverload<QNetworkReply::NetworkError>(&QNetworkReply::error), this, &NetworkAccess::onUploadError);
+        connect(upl->reply, &QNetworkReply::finished, this, &NetworkAccess::onUploadFinished);
         uploads.insert(std::make_pair(url, upl));
         emit downloadFileProgress(messageId, 0);
     } else {
@@ -455,3 +456,36 @@ void Core::NetworkAccess::uploadFileRequest(const QString& messageId, const QStr
         }
     }
 }
+
+QString Core::NetworkAccess::getFileRemoteUrl(const QString& path)
+{
+    return "";  //TODO this is a way not to upload some file more then 1 time, here I'm supposed to return that file GET url
+}
+
+bool Core::NetworkAccess::isUploading(const QString& path, const QString& messageId)
+{
+    return false; //TODO this is a way to avoid parallel uploading of the same files by different chats
+                    //   message is is supposed to be added to the uploading messageids list 
+                    //   the result should be true if there was an uploading file with this path
+                    //   message id can be empty, then it's just to check and not to add
+}
+
+void Core::NetworkAccess::uploadFile(const QString& messageId, const QString& path, const QUrl& put, const QUrl& get, const QMap<QString, QString> headers)
+{
+    Transfer* upl = new Transfer({{messageId}, 0, 0, true, path, get.toString(), 0});
+    QNetworkRequest req(put);
+    QFile* file = new QFile(path);
+    if (file->open(QIODevice::ReadOnly)) {
+        upl->reply = manager->put(req, file);
+        
+        connect(upl->reply, &QNetworkReply::uploadProgress, this, &NetworkAccess::onUploadProgress);
+        connect(upl->reply, qOverload<QNetworkReply::NetworkError>(&QNetworkReply::error), this, &NetworkAccess::onUploadError);
+        connect(upl->reply, &QNetworkReply::finished, this, &NetworkAccess::onUploadFinished);
+        uploads.insert(std::make_pair(put.toString(), upl));
+        emit downloadFileProgress(messageId, 0);
+    } else {
+        qDebug() << "couldn't upload file" << path;
+        emit uploadFileError(messageId, "Error opening file");
+        delete file;
+    }
+}
diff --git a/core/networkaccess.h b/core/networkaccess.h
index de3f2bc..824b1af 100644
--- a/core/networkaccess.h
+++ b/core/networkaccess.h
@@ -47,12 +47,17 @@ public:
     void start();
     void stop();
     
+    QString getFileRemoteUrl(const QString& path);
+    bool isUploading(const QString& path, const QString& messageId = "");
+    void uploadFile(const QString& messageId, const QString& path, const QUrl& put, const QUrl& get, const QMap<QString, QString> headers);
+    
 signals:
     void fileLocalPathResponse(const QString& messageId, const QString& path);
     void downloadFileProgress(const QString& messageId, qreal value);
     void downloadFileError(const QString& messageId, const QString& path);
     void uploadFileProgress(const QString& messageId, qreal value);
     void uploadFileError(const QString& messageId, const QString& path);
+    void uploadFileComplete(const QString& messageId, const QString& url);
     
 public slots:
     void fileLocalPathRequest(const QString& messageId, const QString& url);
@@ -85,6 +90,7 @@ private:
         QNetworkReply* reply;
         bool success;
         QString path;
+        QString url;
         QFile* file;
     };
 };
diff --git a/core/squawk.cpp b/core/squawk.cpp
index 1679eab..1842367 100644
--- a/core/squawk.cpp
+++ b/core/squawk.cpp
@@ -106,7 +106,7 @@ void Core::Squawk::addAccount(const QString& login, const QString& server, const
     QSettings settings;
     unsigned int reconnects = settings.value("reconnects", 2).toUInt();
     
-    Account* acc = new Account(login, server, password, name);
+    Account* acc = new Account(login, server, password, name, &network);
     acc->setResource(resource);
     acc->setReconnectTimes(reconnects);
     accounts.push_back(acc);
@@ -285,7 +285,13 @@ void Core::Squawk::sendMessage(const QString& account, const Shared::Message& da
 
 void Core::Squawk::sendMessage(const QString& account, const Shared::Message& data, const QString& path)
 {
+    AccountsMap::const_iterator itr = amap.find(account);
+    if (itr == amap.end()) {
+        qDebug("An attempt to send a message with non existing account, skipping");
+        return;
+    }
     
+    itr->second->sendMessage(data, path);
 }
 
 void Core::Squawk::requestArchive(const QString& account, const QString& jid, int count, const QString& before)

From a6e48599aad6b342c6353ba829af1b086a3b8938 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Mon, 11 Nov 2019 18:19:54 +0300
Subject: [PATCH 024/281] some progress on upload

---
 core/account.cpp         | 14 ++++++++++
 core/account.h           |  2 +-
 core/networkaccess.cpp   | 27 ++++++++++++------
 main.cpp                 |  2 ++
 ui/squawk.h              |  2 ++
 ui/utils/message.cpp     | 42 +++++++++++-----------------
 ui/utils/message.h       | 19 +++++--------
 ui/utils/messageline.cpp | 60 +++++++++++++++++++++++++++++++---------
 ui/utils/messageline.h   | 12 ++++++--
 9 files changed, 117 insertions(+), 63 deletions(-)

diff --git a/core/account.cpp b/core/account.cpp
index 49746b7..f7b1665 100644
--- a/core/account.cpp
+++ b/core/account.cpp
@@ -93,6 +93,9 @@ Account::Account(const QString& p_login, const QString& p_server, const QString&
     QObject::connect(um, &QXmppUploadRequestManager::slotReceived, this, &Account::onUploadSlotReceived);
     QObject::connect(um, &QXmppUploadRequestManager::requestFailed, this, &Account::onUploadSlotRequestFailed);
     
+    QObject::connect(network, &NetworkAccess::uploadFileComplete, this, &Account::onFileUploaded);
+    QObject::connect(network, &NetworkAccess::uploadFileError, this, &Account::onFileUploadError);
+    
     QString path(QStandardPaths::writableLocation(QStandardPaths::CacheLocation));
     path += "/" + name;
     QDir dir(path);
@@ -141,6 +144,9 @@ Account::Account(const QString& p_login, const QString& p_server, const QString&
 
 Account::~Account()
 {
+    QObject::disconnect(network, &NetworkAccess::uploadFileComplete, this, &Account::onFileUploaded);
+    QObject::disconnect(network, &NetworkAccess::uploadFileError, this, &Account::onFileUploadError);
+    
     for (std::map<QString, Contact*>::const_iterator itr = contacts.begin(), end = contacts.end(); itr != end; ++itr) {
         delete itr->second;
     }
@@ -1640,3 +1646,11 @@ void Core::Account::onFileUploaded(const QString& messageId, const QString& url)
         pendingMessages.erase(itr);
     }
 }
+
+void Core::Account::onFileUploadError(const QString& messageId, const QString& errMsg)
+{
+    std::map<QString, Shared::Message>::const_iterator itr = pendingMessages.find(messageId);
+    if (itr != pendingMessages.end()) {
+        pendingMessages.erase(itr);
+    }
+}
diff --git a/core/account.h b/core/account.h
index 8f22913..c8419d1 100644
--- a/core/account.h
+++ b/core/account.h
@@ -196,7 +196,7 @@ private slots:
     void onUploadSlotReceived(const QXmppHttpUploadSlotIq& slot);
     void onUploadSlotRequestFailed(const QXmppHttpUploadRequestIq& request);
     void onFileUploaded(const QString& messageId, const QString& url);
-    void onFileUploadError(const QString& messageId, const QString& path);
+    void onFileUploadError(const QString& messageId, const QString& errMsg);
   
 private:
     void addedAccount(const QString &bareJid);
diff --git a/core/networkaccess.cpp b/core/networkaccess.cpp
index ea726f2..2a41909 100644
--- a/core/networkaccess.cpp
+++ b/core/networkaccess.cpp
@@ -437,16 +437,22 @@ void Core::NetworkAccess::uploadFileRequest(const QString& messageId, const QStr
         try {
             QString ePath = files.getRecord(url);
             if (ePath == path) {
-                emit fileLocalPathResponse(messageId, path);
+                QFileInfo info(path);
+                if (info.exists() && info.isFile()) {
+                    emit fileLocalPathResponse(messageId, path);
+                } else {
+                    files.removeRecord(url);
+                    startUpload(messageId, url, path);
+                }
             } else {
-                files.changeRecord(url, path);
-            }
-            QFileInfo info(path);
-            if (info.exists() && info.isFile()) {
-                emit fileLocalPathResponse(messageId, path);
-            } else {
-                files.removeRecord(url);
-                startDownload(messageId, url);
+                QFileInfo info(path);
+                if (info.exists() && info.isFile()) {
+                    files.changeRecord(url, path);
+                    emit fileLocalPathResponse(messageId, path);
+                } else {
+                    files.removeRecord(url);
+                    startUpload(messageId, url, path);
+                }
             }
         } catch (Archive::NotFound e) {
             startUpload(messageId, url, path);
@@ -474,6 +480,9 @@ void Core::NetworkAccess::uploadFile(const QString& messageId, const QString& pa
 {
     Transfer* upl = new Transfer({{messageId}, 0, 0, true, path, get.toString(), 0});
     QNetworkRequest req(put);
+    for (QMap<QString, QString>::const_iterator itr = headers.begin(), end = headers.end(); itr != end; itr++) {
+        req.setRawHeader(itr.key().toUtf8(), itr.value().toUtf8());
+    }
     QFile* file = new QFile(path);
     if (file->open(QIODevice::ReadOnly)) {
         upl->reply = manager->put(req, file);
diff --git a/main.cpp b/main.cpp
index a66230f..76b1984 100644
--- a/main.cpp
+++ b/main.cpp
@@ -131,6 +131,8 @@ int main(int argc, char *argv[])
     QObject::connect(squawk, &Core::Squawk::fileLocalPathResponse, &w, &Squawk::fileLocalPathResponse);
     QObject::connect(squawk, &Core::Squawk::downloadFileProgress, &w, &Squawk::downloadFileProgress);
     QObject::connect(squawk, &Core::Squawk::downloadFileError, &w, &Squawk::downloadFileError);
+    QObject::connect(squawk, &Core::Squawk::uploadFileProgress, &w, &Squawk::uploadFileProgress);
+    QObject::connect(squawk, &Core::Squawk::uploadFileError, &w, &Squawk::uploadFileError);
     QObject::connect(squawk, &Core::Squawk::responseVCard, &w, &Squawk::responseVCard);
     
     coreThread->start();
diff --git a/ui/squawk.h b/ui/squawk.h
index 70f0d5f..cc43992 100644
--- a/ui/squawk.h
+++ b/ui/squawk.h
@@ -100,6 +100,8 @@ public slots:
     void fileLocalPathResponse(const QString& messageId, const QString& path);
     void downloadFileError(const QString& messageId, const QString& error);
     void downloadFileProgress(const QString& messageId, qreal value);
+    void uploadFileError(const QString& messageId, const QString& error);
+    void uploadFileProgress(const QString& messageId, qreal value);
     void responseVCard(const QString& jid, const Shared::VCard& card);
     
 private:
diff --git a/ui/utils/message.cpp b/ui/utils/message.cpp
index 951037a..4bb9f46 100644
--- a/ui/utils/message.cpp
+++ b/ui/utils/message.cpp
@@ -38,12 +38,10 @@ Message::Message(const Shared::Message& source, bool outgoing, const QString& p_
     file(0),
     progress(0),
     fileComment(new QLabel()),
-    errorText(""),
     hasDownloadButton(false),
     hasProgress(false),
     hasFile(false),
-    commentAdded(false),
-    errorDownloadingFile(false)
+    commentAdded(false)
 {
     body->setBackgroundRole(QPalette::AlternateBase);
     body->setAutoFillBackground(true);
@@ -101,12 +99,17 @@ QString Message::getId() const
     return msg.getId();
 }
 
+QString Message::getFileUrl() const
+{
+    return msg.getOutOfBandUrl();
+}
+
 void Message::setSender(const QString& p_sender)
 {
     sender->setText(p_sender);
 }
 
-void Message::addDownloadDialog()
+void Message::addButton(const QIcon& icon, const QString& buttonText, const QString& comment)
 {
     hideFile();
     hideProgress();
@@ -116,16 +119,14 @@ void Message::addDownloadDialog()
             text->setText("");
             text->hide();
         }
-        downloadButton = new QPushButton(QIcon::fromTheme("download"), tr("Download"));
+        downloadButton = new QPushButton(icon, buttonText);
         downloadButton->setToolTip("<a href=\"" + msg.getOutOfBandUrl() + "\">" + msg.getOutOfBandUrl() + "</a>");
-        if (errorDownloadingFile) {
+        if (comment.size() != 0) {
             fileComment->setWordWrap(true);
-            fileComment->setText(tr("Error downloading file: %1\nYou can try again").arg(QCoreApplication::translate("NetworkErrors", errorText.toLatin1())));
-        } else {
-            fileComment->setText(tr("%1 is offering you to download a file").arg(sender->text()));
+            fileComment->setText(comment);
+            fileComment->show();
         }
-        fileComment->show();
-        connect(downloadButton, &QPushButton::clicked, this, &Message::onDownload);
+        connect(downloadButton, &QPushButton::clicked, this, &Message::downloadFile);
         bodyLayout->insertWidget(2, fileComment);
         bodyLayout->insertWidget(3, downloadButton);
         hasDownloadButton = true;
@@ -133,12 +134,7 @@ void Message::addDownloadDialog()
     }
 }
 
-void Message::onDownload()
-{
-    emit downloadFile(msg.getId(), msg.getOutOfBandUrl());
-}
-
-void Message::setProgress(qreal value)
+void Message::setProgress(qreal value, const QString& label)
 {
     hideFile();
     hideDownload();
@@ -150,7 +146,9 @@ void Message::setProgress(qreal value)
         }
         progress = new QProgressBar();
         progress->setRange(0, 100);
-        fileComment->setText("Downloading...");
+        if (label.size() != 0) {
+            fileComment->setText(label);
+        }
         fileComment->show();
         bodyLayout->insertWidget(2, progress);
         bodyLayout->insertWidget(3, fileComment);
@@ -214,7 +212,6 @@ void Message::hideDownload()
         downloadButton->deleteLater();
         downloadButton = 0;
         hasDownloadButton = false;
-        errorDownloadingFile = false;
     }
 }
 
@@ -235,10 +232,3 @@ void Message::hideProgress()
         hasProgress = false;;
     }
 }
-
-void Message::showError(const QString& error)
-{
-    errorDownloadingFile = true;
-    errorText = error;
-    addDownloadDialog();
-}
diff --git a/ui/utils/message.h b/ui/utils/message.h
index 8a89268..22375a1 100644
--- a/ui/utils/message.h
+++ b/ui/utils/message.h
@@ -30,9 +30,9 @@
 #include <QDesktopServices>
 #include <QUrl>
 
-#include "../../global.h"
-#include "../utils/resizer.h"
-#include "../utils/image.h"
+#include "global.h"
+#include "resizer.h"
+#include "image.h"
 
 /**
  * @todo write docs
@@ -46,14 +46,14 @@ public:
     
     void setSender(const QString& sender);
     QString getId() const;
+    QString getFileUrl() const;
     
-    void addDownloadDialog();
+    void addButton(const QIcon& icon, const QString& buttonText, const QString& comment = "");
     void showFile(const QString& path);
-    void showError(const QString& error);
-    void setProgress(qreal value);
+    void setProgress(qreal value, const QString& label = "");
     
 signals:
-    void downloadFile(const QString& messageId, const QString& url);
+    void downloadFile();
     
 private:
     Shared::Message msg;
@@ -67,15 +67,10 @@ private:
     QLabel* file;
     QProgressBar* progress;
     QLabel* fileComment;
-    QString errorText;
     bool hasDownloadButton;
     bool hasProgress;
     bool hasFile;
     bool commentAdded;
-    bool errorDownloadingFile;
-    
-private slots:
-    void onDownload();
   
 private:
     void hideDownload();
diff --git a/ui/utils/messageline.cpp b/ui/utils/messageline.cpp
index 06efa85..60c31f9 100644
--- a/ui/utils/messageline.cpp
+++ b/ui/utils/messageline.cpp
@@ -26,10 +26,13 @@ MessageLine::MessageLine(bool p_room, QWidget* parent):
     messageOrder(),
     myMessages(),
     palMessages(),
+    uploadPaths(),
     layout(new QVBoxLayout(this)),
     myName(),
     palNames(),
     views(),
+    uploading(),
+    downloading(),
     room(p_room),
     busyShown(false),
     progress()
@@ -125,14 +128,27 @@ MessageLine::Position MessageLine::message(const Shared::Message& msg)
         layout->insertLayout(index, message);
     }
     
-    if (msg.hasOutOfBandUrl()) {\
+    if (msg.hasOutOfBandUrl()) {
         emit requestLocalFile(msg.getId(), msg.getOutOfBandUrl());
-        connect(message, &Message::downloadFile, this, &MessageLine::downloadFile);
+        connect(message, &Message::downloadFile, this, &MessageLine::onDownload);
     }
     
     return res;
 }
 
+void MessageLine::onDownload()
+{
+    Message* msg = static_cast<Message*>(sender());
+    QString messageId = msg->getId();
+    Index::const_iterator itr = downloading.find(messageId);
+    if (itr == downloading.end()) {
+        downloading.insert(std::make_pair(messageId, msg));
+        emit downloadFile(messageId, msg->getFileUrl());
+    } else {
+        qDebug() << "An attempt to initiate download for already downloading file" << msg->getFileUrl() << ", skipping";
+    }
+}
+
 void MessageLine::setMyName(const QString& name)
 {
     myName = name;
@@ -192,13 +208,18 @@ void MessageLine::hideBusyIndicator()
     }
 }
 
-void MessageLine::responseDownloadProgress(const QString& messageId, qreal progress)
+void MessageLine::fileProgress(const QString& messageId, qreal progress)
 {
-    Index::const_iterator itr = messageIndex.find(messageId);
-    if (itr == messageIndex.end()) {
-        
+    Index::const_iterator itr = downloading.find(messageId);
+    if (itr == downloading.end()) {
+        Index::const_iterator itr = uploading.find(messageId);
+        if (itr == uploading.end()) {
+            //TODO may be some logging, that's not normal
+        } else {
+            itr->second->setProgress(progress, tr("Uploading..."));
+        }
     } else {
-        itr->second->setProgress(progress);
+        itr->second->setProgress(progress, tr("Downloading..."));
     }
 }
 
@@ -211,18 +232,31 @@ void MessageLine::responseLocalFile(const QString& messageId, const QString& pat
         if (path.size() > 0) {
             itr->second->showFile(path);
         } else {
-            itr->second->addDownloadDialog();
+            itr->second->addButton(QIcon::fromTheme("download"), tr("Download"), tr("Push the button to daownload the file"));
         }
     }
 }
 
-void MessageLine::downloadError(const QString& messageId, const QString& error)
+void MessageLine::fileError(const QString& messageId, const QString& error)
 {
-    Index::const_iterator itr = messageIndex.find(messageId);
-    if (itr == messageIndex.end()) {
-        
+    Index::const_iterator itr = downloading.find(messageId);
+    if (itr == downloading.end()) {
+        Index::const_iterator itr = uploading.find(messageId);
+        if (itr == uploading.end()) {
+            //TODO may be some logging, that's not normal
+        } else {
+            //itr->second->showError(error);
+            //itr->second->addDownloadDialog();
+        }
     } else {
-        itr->second->showError(error);
+        itr->second->addButton(QIcon::fromTheme("download"), tr("Download"), 
+                               tr("Error downloading file: %1\nYou can try again").arg(QCoreApplication::translate("NetworkErrors", error.toLatin1())));
     }
 }
 
+void MessageLine::appendMessageWithUpload(const Shared::Message& msg, const QString& path)
+{
+    message(msg);
+    
+}
+
diff --git a/ui/utils/messageline.h b/ui/utils/messageline.h
index 67280e4..5bdb733 100644
--- a/ui/utils/messageline.h
+++ b/ui/utils/messageline.h
@@ -50,17 +50,22 @@ public:
     void showBusyIndicator();
     void hideBusyIndicator();
     void responseLocalFile(const QString& messageId, const QString& path);
-    void downloadError(const QString& messageId, const QString& error);
-    void responseDownloadProgress(const QString& messageId, qreal progress);
+    void fileError(const QString& messageId, const QString& error);
+    void fileProgress(const QString& messageId, qreal progress);
+    void appendMessageWithUpload(const Shared::Message& message, const QString& path);
     
 signals:
     void resize(int amount);
     void downloadFile(const QString& messageId, const QString& url);
+    void uploadFile(const Shared::Message& msg, const QString& path);
     void requestLocalFile(const QString& messageId, const QString& url);
     
 protected:
     void resizeEvent(QResizeEvent * event) override;
     
+protected:
+    void onDownload();
+    
 private:
     struct Comparator {
         bool operator()(const Shared::Message& a, const Shared::Message& b) const {
@@ -76,11 +81,14 @@ private:
     Order messageOrder;
     Index myMessages;
     std::map<QString, Index> palMessages;
+    std::map<QString, QString> uploadPaths;
     QVBoxLayout* layout;
     
     QString myName;
     std::map<QString, QString> palNames;
     std::deque<QHBoxLayout*> views;
+    Index uploading;
+    Index downloading;
     bool room;
     bool busyShown;
     Progress progress;

From 166a7ac83addcf8bfa20c19c86bfaa2d52f714f1 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Tue, 12 Nov 2019 16:38:01 +0300
Subject: [PATCH 025/281] first working prototype of file upload

---
 core/account.cpp            |  21 ++++++
 core/account.h              |   4 ++
 core/networkaccess.cpp      |   4 +-
 external/qxmpp              |   2 +-
 main.cpp                    |  13 ++--
 ui/squawk.cpp               |  15 +++--
 ui/squawk.h                 |   6 +-
 ui/utils/message.cpp        |  86 +++++++++++++-----------
 ui/utils/message.h          |  16 +++--
 ui/utils/messageline.cpp    | 129 +++++++++++++++++++++++++++++-------
 ui/utils/messageline.h      |   3 +-
 ui/widgets/conversation.cpp |  38 +++++++++--
 ui/widgets/conversation.h   |   4 +-
 13 files changed, 244 insertions(+), 97 deletions(-)

diff --git a/core/account.cpp b/core/account.cpp
index f7b1665..c238a23 100644
--- a/core/account.cpp
+++ b/core/account.cpp
@@ -39,6 +39,7 @@ Account::Account(const QString& p_login, const QString& p_server, const QString&
     rm(client.findExtension<QXmppRosterManager>()),
     vm(client.findExtension<QXmppVCardManager>()),
     um(new QXmppUploadRequestManager()),
+    dm(client.findExtension<QXmppDiscoveryManager>()),
     contacts(),
     conferences(),
     maxReconnectTimes(0),
@@ -93,6 +94,9 @@ Account::Account(const QString& p_login, const QString& p_server, const QString&
     QObject::connect(um, &QXmppUploadRequestManager::slotReceived, this, &Account::onUploadSlotReceived);
     QObject::connect(um, &QXmppUploadRequestManager::requestFailed, this, &Account::onUploadSlotRequestFailed);
     
+    QObject::connect(dm, &QXmppDiscoveryManager::itemsReceived, this, &Account::onDiscoveryItemsReceived);
+    QObject::connect(dm, &QXmppDiscoveryManager::infoReceived, this, &Account::onDiscoveryInfoReceived);
+    
     QObject::connect(network, &NetworkAccess::uploadFileComplete, this, &Account::onFileUploaded);
     QObject::connect(network, &NetworkAccess::uploadFileError, this, &Account::onFileUploadError);
     
@@ -196,6 +200,7 @@ void Core::Account::onClientConnected()
         reconnectTimes = maxReconnectTimes;
         state = Shared::connected;
         cm->setCarbonsEnabled(true);
+        dm->requestItems(getServer());
         emit connectionStateChanged(state);
     } else {
         qDebug() << "Something weird had happened - xmpp client reported about successful connection but account wasn't in" << state << "state";
@@ -613,6 +618,7 @@ void Core::Account::sendMessage(const Shared::Message& data)
         QXmppMessage msg(data.getFrom(), data.getTo(), data.getBody(), data.getThread());
         msg.setId(data.getId());
         msg.setType(static_cast<QXmppMessage::Type>(data.getType()));       //it is safe here, my type is compatible
+        msg.setOutOfBandUrl(data.getOutOfBandUrl());
         
         RosterItem* ri = 0;
         std::map<QString, Contact*>::const_iterator itr = contacts.find(data.getPenPalJid());
@@ -671,6 +677,9 @@ void Core::Account::sendMessage(const Shared::Message& data, const QString& path
 void Core::Account::sendMessageWithLocalUploadedFile(Shared::Message msg, const QString& url)
 {
     msg.setOutOfBandUrl(url);
+    if (msg.getBody().size() == 0) {
+        msg.setBody(url);
+    }
     sendMessage(msg);
     //TODO removal/progress update
 }
@@ -1654,3 +1663,15 @@ void Core::Account::onFileUploadError(const QString& messageId, const QString& e
         pendingMessages.erase(itr);
     }
 }
+
+void Core::Account::onDiscoveryItemsReceived(const QXmppDiscoveryIq& items)
+{
+    for (QXmppDiscoveryIq::Item item : items.items()) {
+        dm->requestInfo(item.jid());
+    }
+}
+
+void Core::Account::onDiscoveryInfoReceived(const QXmppDiscoveryIq& info)
+{
+
+}
diff --git a/core/account.h b/core/account.h
index c8419d1..38631af 100644
--- a/core/account.h
+++ b/core/account.h
@@ -31,6 +31,7 @@
 
 #include <QXmppRosterManager.h>
 #include <QXmppCarbonManager.h>
+#include <QXmppDiscoveryManager.h>
 #include <QXmppMamManager.h>
 #include <QXmppMucManager.h>
 #include <QXmppClient.h>
@@ -133,6 +134,7 @@ private:
     QXmppRosterManager* rm;
     QXmppVCardManager* vm;
     QXmppUploadRequestManager* um;
+    QXmppDiscoveryManager* dm;
     std::map<QString, Contact*> contacts;
     std::map<QString, Conference*> conferences;
     unsigned int maxReconnectTimes;
@@ -197,6 +199,8 @@ private slots:
     void onUploadSlotRequestFailed(const QXmppHttpUploadRequestIq& request);
     void onFileUploaded(const QString& messageId, const QString& url);
     void onFileUploadError(const QString& messageId, const QString& errMsg);
+    void onDiscoveryItemsReceived (const QXmppDiscoveryIq& items);
+    void onDiscoveryInfoReceived (const QXmppDiscoveryIq& info);
   
 private:
     void addedAccount(const QString &bareJid);
diff --git a/core/networkaccess.cpp b/core/networkaccess.cpp
index 2a41909..6e1689d 100644
--- a/core/networkaccess.cpp
+++ b/core/networkaccess.cpp
@@ -478,12 +478,12 @@ bool Core::NetworkAccess::isUploading(const QString& path, const QString& messag
 
 void Core::NetworkAccess::uploadFile(const QString& messageId, const QString& path, const QUrl& put, const QUrl& get, const QMap<QString, QString> headers)
 {
-    Transfer* upl = new Transfer({{messageId}, 0, 0, true, path, get.toString(), 0});
+    QFile* file = new QFile(path);
+    Transfer* upl = new Transfer({{messageId}, 0, 0, true, path, get.toString(), file});
     QNetworkRequest req(put);
     for (QMap<QString, QString>::const_iterator itr = headers.begin(), end = headers.end(); itr != end; itr++) {
         req.setRawHeader(itr.key().toUtf8(), itr.value().toUtf8());
     }
-    QFile* file = new QFile(path);
     if (file->open(QIODevice::ReadOnly)) {
         upl->reply = manager->put(req, file);
         
diff --git a/external/qxmpp b/external/qxmpp
index b18a57d..f8c546c 160000
--- a/external/qxmpp
+++ b/external/qxmpp
@@ -1 +1 @@
-Subproject commit b18a57daa33f0fefa5f4c63aa7f448b48d302e0d
+Subproject commit f8c546c5b701c53d708a38a951fcc734eaee7940
diff --git a/main.cpp b/main.cpp
index 76b1984..cc5e895 100644
--- a/main.cpp
+++ b/main.cpp
@@ -79,6 +79,11 @@ int main(int argc, char *argv[])
     QThread* coreThread = new QThread();
     squawk->moveToThread(coreThread);
     
+    QObject::connect(coreThread, &QThread::started, squawk, &Core::Squawk::start);
+    QObject::connect(&app, &QApplication::aboutToQuit, squawk, &Core::Squawk::stop);
+    QObject::connect(squawk, &Core::Squawk::quit, coreThread, &QThread::quit);
+    QObject::connect(coreThread, &QThread::finished, squawk, &Core::Squawk::deleteLater);
+    
     QObject::connect(&w, &Squawk::newAccountRequest, squawk, &Core::Squawk::newAccountRequest);
     QObject::connect(&w, &Squawk::modifyAccountRequest, squawk, &Core::Squawk::modifyAccountRequest);
     QObject::connect(&w, &Squawk::removeAccountRequest, squawk, &Core::Squawk::removeAccountRequest);
@@ -129,10 +134,10 @@ int main(int argc, char *argv[])
     QObject::connect(squawk, &Core::Squawk::changeRoomParticipant, &w, &Squawk::changeRoomParticipant);
     QObject::connect(squawk, &Core::Squawk::removeRoomParticipant, &w, &Squawk::removeRoomParticipant);
     QObject::connect(squawk, &Core::Squawk::fileLocalPathResponse, &w, &Squawk::fileLocalPathResponse);
-    QObject::connect(squawk, &Core::Squawk::downloadFileProgress, &w, &Squawk::downloadFileProgress);
-    QObject::connect(squawk, &Core::Squawk::downloadFileError, &w, &Squawk::downloadFileError);
-    QObject::connect(squawk, &Core::Squawk::uploadFileProgress, &w, &Squawk::uploadFileProgress);
-    QObject::connect(squawk, &Core::Squawk::uploadFileError, &w, &Squawk::uploadFileError);
+    QObject::connect(squawk, &Core::Squawk::downloadFileProgress, &w, &Squawk::fileProgress);
+    QObject::connect(squawk, &Core::Squawk::downloadFileError, &w, &Squawk::fileError);
+    QObject::connect(squawk, &Core::Squawk::uploadFileProgress, &w, &Squawk::fileProgress);
+    QObject::connect(squawk, &Core::Squawk::uploadFileError, &w, &Squawk::fileError);
     QObject::connect(squawk, &Core::Squawk::responseVCard, &w, &Squawk::responseVCard);
     
     coreThread->start();
diff --git a/ui/squawk.cpp b/ui/squawk.cpp
index 3221a4c..095f639 100644
--- a/ui/squawk.cpp
+++ b/ui/squawk.cpp
@@ -369,11 +369,11 @@ void Squawk::onConversationDownloadFile(const QString& messageId, const QString&
     }
 }
 
-void Squawk::downloadFileProgress(const QString& messageId, qreal value)
+void Squawk::fileProgress(const QString& messageId, qreal value)
 {
     std::map<QString, std::set<Models::Roster::ElId>>::const_iterator itr = requestedFiles.find(messageId);
     if (itr == requestedFiles.end()) {
-        qDebug() << "downloadFileProgress in UI Squawk but there is nobody waiting for that id" << messageId << ", skipping";
+        qDebug() << "fileProgress in UI Squawk but there is nobody waiting for that id" << messageId << ", skipping";
         return;
     } else {
         const std::set<Models::Roster::ElId>& convs = itr->second;
@@ -381,17 +381,17 @@ void Squawk::downloadFileProgress(const QString& messageId, qreal value)
             const Models::Roster::ElId& id = *cItr;
             Conversations::const_iterator c = conversations.find(id);
             if (c != conversations.end()) {
-                c->second->responseDownloadProgress(messageId, value);
+                c->second->responseFileProgress(messageId, value);
             }
         }
     }
 }
 
-void Squawk::downloadFileError(const QString& messageId, const QString& error)
+void Squawk::fileError(const QString& messageId, const QString& error)
 {
     std::map<QString, std::set<Models::Roster::ElId>>::const_iterator itr = requestedFiles.find(messageId);
     if (itr == requestedFiles.end()) {
-        qDebug() << "downloadFileError in UI Squawk but there is nobody waiting for that id" << messageId << ", skipping";
+        qDebug() << "fileError in UI Squawk but there is nobody waiting for that id" << messageId << ", skipping";
         return;
     } else {
         const std::set<Models::Roster::ElId>& convs = itr->second;
@@ -399,7 +399,7 @@ void Squawk::downloadFileError(const QString& messageId, const QString& error)
             const Models::Roster::ElId& id = *cItr;
             Conversations::const_iterator c = conversations.find(id);
             if (c != conversations.end()) {
-                c->second->downloadError(messageId, error);
+                c->second->fileError(messageId, error);
             }
         }
         requestedFiles.erase(itr);
@@ -497,6 +497,9 @@ void Squawk::onConversationMessage(const Shared::Message& msg)
 void Squawk::onConversationMessage(const Shared::Message& msg, const QString& path)
 {
     Conversation* conv = static_cast<Conversation*>(sender());
+    std::map<QString, std::set<Models::Roster::ElId>>::iterator itr = requestedFiles.insert(std::make_pair(msg.getId(), std::set<Models::Roster::ElId>())).first;
+    itr->second.insert(Models::Roster::ElId(conv->getAccount(), conv->getJid()));
+    
     emit sendMessage(conv->getAccount(), msg, path);
 }
 
diff --git a/ui/squawk.h b/ui/squawk.h
index cc43992..b464fd2 100644
--- a/ui/squawk.h
+++ b/ui/squawk.h
@@ -98,10 +98,8 @@ public slots:
     void changeRoomParticipant(const QString& account, const QString& jid, const QString& name, const QMap<QString, QVariant>& data);
     void removeRoomParticipant(const QString& account, const QString& jid, const QString& name);
     void fileLocalPathResponse(const QString& messageId, const QString& path);
-    void downloadFileError(const QString& messageId, const QString& error);
-    void downloadFileProgress(const QString& messageId, qreal value);
-    void uploadFileError(const QString& messageId, const QString& error);
-    void uploadFileProgress(const QString& messageId, qreal value);
+    void fileError(const QString& messageId, const QString& error);
+    void fileProgress(const QString& messageId, qreal value);
     void responseVCard(const QString& jid, const Shared::VCard& card);
     
 private:
diff --git a/ui/utils/message.cpp b/ui/utils/message.cpp
index 4bb9f46..d826436 100644
--- a/ui/utils/message.cpp
+++ b/ui/utils/message.cpp
@@ -34,11 +34,11 @@ Message::Message(const Shared::Message& source, bool outgoing, const QString& p_
     sender(new QLabel(p_sender)),
     text(new QLabel()),
     shadow(new QGraphicsDropShadowEffect()),
-    downloadButton(0),
+    button(0),
     file(0),
     progress(0),
     fileComment(new QLabel()),
-    hasDownloadButton(false),
+    hasButton(false),
     hasProgress(false),
     hasFile(false),
     commentAdded(false)
@@ -77,13 +77,13 @@ Message::Message(const Shared::Message& source, bool outgoing, const QString& p_
     body->setGraphicsEffect(shadow);
     
     if (outgoing) {
-        addWidget(body);
-        addStretch();
-    } else {
         sender->setAlignment(Qt::AlignRight);
         date->setAlignment(Qt::AlignRight);
         addStretch();
         addWidget(body);
+    } else {
+        addWidget(body);
+        addStretch();
     }
 }
 
@@ -109,35 +109,28 @@ void Message::setSender(const QString& p_sender)
     sender->setText(p_sender);
 }
 
-void Message::addButton(const QIcon& icon, const QString& buttonText, const QString& comment)
+void Message::addButton(const QIcon& icon, const QString& buttonText)
 {
     hideFile();
     hideProgress();
-    if (!hasDownloadButton) {
+    if (!hasButton) {
         hideComment();
         if (msg.getBody() == msg.getOutOfBandUrl()) {
             text->setText("");
             text->hide();
         }
-        downloadButton = new QPushButton(icon, buttonText);
-        downloadButton->setToolTip("<a href=\"" + msg.getOutOfBandUrl() + "\">" + msg.getOutOfBandUrl() + "</a>");
-        if (comment.size() != 0) {
-            fileComment->setWordWrap(true);
-            fileComment->setText(comment);
-            fileComment->show();
-        }
-        connect(downloadButton, &QPushButton::clicked, this, &Message::downloadFile);
-        bodyLayout->insertWidget(2, fileComment);
-        bodyLayout->insertWidget(3, downloadButton);
-        hasDownloadButton = true;
-        commentAdded = true;
+        button = new QPushButton(icon, buttonText);
+        button->setToolTip("<a href=\"" + msg.getOutOfBandUrl() + "\">" + msg.getOutOfBandUrl() + "</a>");
+        connect(button, &QPushButton::clicked, this, &Message::buttonClicked);
+        bodyLayout->insertWidget(2, button);
+        hasButton = true;
     }
 }
 
-void Message::setProgress(qreal value, const QString& label)
+void Message::setProgress(qreal value)
 {
     hideFile();
-    hideDownload();
+    hideButton();
     if (!hasProgress) {
         hideComment();
         if (msg.getBody() == msg.getOutOfBandUrl()) {
@@ -146,21 +139,15 @@ void Message::setProgress(qreal value, const QString& label)
         }
         progress = new QProgressBar();
         progress->setRange(0, 100);
-        if (label.size() != 0) {
-            fileComment->setText(label);
-        }
-        fileComment->show();
         bodyLayout->insertWidget(2, progress);
-        bodyLayout->insertWidget(3, fileComment);
         hasProgress = true;
-        commentAdded = true;
     }
     progress->setValue(value * 100);
 }
 
 void Message::showFile(const QString& path)
 {
-    hideDownload();
+    hideButton();
     hideProgress();
     if (!hasFile) {
         hideComment();
@@ -173,16 +160,13 @@ void Message::showFile(const QString& path)
         QStringList parts = type.name().split("/");
         QString big = parts.front();
         QFileInfo info(path);
-        fileComment = new QLabel();
         if (big == "image") {
             file = new Image(path);
         } else {
             file = new QLabel();
             file->setPixmap(QIcon::fromTheme(type.iconName()).pixmap(50));
             file->setAlignment(Qt::AlignCenter);
-            fileComment->setText(info.fileName());
-            fileComment->setWordWrap(true);
-            fileComment->show();
+            showComment(info.fileName(), true);
         }
         file->setContextMenuPolicy(Qt::ActionsContextMenu);
         QAction* openAction = new QAction(QIcon::fromTheme("document-new-from-template"), tr("Open"), file);
@@ -191,9 +175,7 @@ void Message::showFile(const QString& path)
         });
         file->addAction(openAction);
         bodyLayout->insertWidget(2, file);
-        bodyLayout->insertWidget(3, fileComment);
         hasFile = true;
-        commentAdded = true;
     }
 }
 
@@ -206,12 +188,12 @@ void Message::hideComment()
     }
 }
 
-void Message::hideDownload()
+void Message::hideButton()
 {
-    if (hasDownloadButton) {
-        downloadButton->deleteLater();
-        downloadButton = 0;
-        hasDownloadButton = false;
+    if (hasButton) {
+        button->deleteLater();
+        button = 0;
+        hasButton = false;
     }
 }
 
@@ -232,3 +214,29 @@ void Message::hideProgress()
         hasProgress = false;;
     }
 }
+void Message::showComment(const QString& comment, bool wordWrap)
+{
+    if (!commentAdded) {
+        int index = 2;
+        if (hasFile) {
+            index++;
+        }
+        if (hasButton) {
+            index++;
+        }
+        if (hasProgress) {
+            index++;
+        }
+        bodyLayout->insertWidget(index, fileComment);
+        fileComment->show();
+        commentAdded = true;
+    }
+    fileComment->setWordWrap(wordWrap);
+    fileComment->setText(comment);
+}
+
+const Shared::Message & Message::getMessage() const
+{
+    return msg;
+}
+
diff --git a/ui/utils/message.h b/ui/utils/message.h
index 22375a1..4147178 100644
--- a/ui/utils/message.h
+++ b/ui/utils/message.h
@@ -47,13 +47,16 @@ public:
     void setSender(const QString& sender);
     QString getId() const;
     QString getFileUrl() const;
+    const Shared::Message& getMessage() const;
     
-    void addButton(const QIcon& icon, const QString& buttonText, const QString& comment = "");
+    void addButton(const QIcon& icon, const QString& buttonText);
+    void showComment(const QString& comment, bool wordWrap = false);
+    void hideComment();
     void showFile(const QString& path);
-    void setProgress(qreal value, const QString& label = "");
+    void setProgress(qreal value);
     
 signals:
-    void downloadFile();
+    void buttonClicked();
     
 private:
     Shared::Message msg;
@@ -63,20 +66,19 @@ private:
     QLabel* sender;
     QLabel* text;
     QGraphicsDropShadowEffect* shadow;
-    QPushButton* downloadButton;
+    QPushButton* button;
     QLabel* file;
     QProgressBar* progress;
     QLabel* fileComment;
-    bool hasDownloadButton;
+    bool hasButton;
     bool hasProgress;
     bool hasFile;
     bool commentAdded;
   
 private:
-    void hideDownload();
+    void hideButton();
     void hideProgress();
     void hideFile();
-    void hideComment();
 };
 
 #endif // MESSAGE_H
diff --git a/ui/utils/messageline.cpp b/ui/utils/messageline.cpp
index 60c31f9..c17baca 100644
--- a/ui/utils/messageline.cpp
+++ b/ui/utils/messageline.cpp
@@ -30,7 +30,6 @@ MessageLine::MessageLine(bool p_room, QWidget* parent):
     layout(new QVBoxLayout(this)),
     myName(),
     palNames(),
-    views(),
     uploading(),
     downloading(),
     room(p_room),
@@ -63,15 +62,15 @@ MessageLine::Position MessageLine::message(const Shared::Message& msg)
     if (room) {
         if (msg.getFromResource() == myName) {
             sender = myName;
-            outgoing = false;
+            outgoing = true;
         } else {
             sender = msg.getFromResource();
-            outgoing = true;
+            outgoing = false;
         }
     } else {
         if (msg.getOutgoing()) {
             sender = myName;
-            outgoing = false;
+            outgoing = true;
         } else {
             QString jid = msg.getFromJid();
             std::map<QString, QString>::iterator itr = palNames.find(jid);
@@ -80,7 +79,7 @@ MessageLine::Position MessageLine::message(const Shared::Message& msg)
             } else {
                 sender = jid;
             }
-            outgoing = true;
+            outgoing = false;
         }
     }
     
@@ -93,6 +92,8 @@ MessageLine::Position MessageLine::message(const Shared::Message& msg)
         return invalid;
     }
     if (outgoing) {
+        myMessages.insert(std::make_pair(id, message));
+    } else {
         if (room) {
             
         } else {
@@ -103,8 +104,6 @@ MessageLine::Position MessageLine::message(const Shared::Message& msg)
             }
             pItr->second.insert(std::make_pair(id, message));
         }
-    } else {
-        myMessages.insert(std::make_pair(id, message));
     }
     messageIndex.insert(std::make_pair(id, message));
     int index = std::distance<Order::const_iterator>(messageOrder.begin(), result.first);   //need to make with binary indexed tree
@@ -130,7 +129,7 @@ MessageLine::Position MessageLine::message(const Shared::Message& msg)
     
     if (msg.hasOutOfBandUrl()) {
         emit requestLocalFile(msg.getId(), msg.getOutOfBandUrl());
-        connect(message, &Message::downloadFile, this, &MessageLine::onDownload);
+        connect(message, &Message::buttonClicked, this, &MessageLine::onDownload);
     }
     
     return res;
@@ -143,6 +142,8 @@ void MessageLine::onDownload()
     Index::const_iterator itr = downloading.find(messageId);
     if (itr == downloading.end()) {
         downloading.insert(std::make_pair(messageId, msg));
+        msg->setProgress(0);
+        msg->showComment(tr("Downloading..."));
         emit downloadFile(messageId, msg->getFileUrl());
     } else {
         qDebug() << "An attempt to initiate download for already downloading file" << msg->getFileUrl() << ", skipping";
@@ -210,16 +211,11 @@ void MessageLine::hideBusyIndicator()
 
 void MessageLine::fileProgress(const QString& messageId, qreal progress)
 {
-    Index::const_iterator itr = downloading.find(messageId);
-    if (itr == downloading.end()) {
-        Index::const_iterator itr = uploading.find(messageId);
-        if (itr == uploading.end()) {
-            //TODO may be some logging, that's not normal
-        } else {
-            itr->second->setProgress(progress, tr("Uploading..."));
-        }
+    Index::const_iterator itr = messageIndex.find(messageId);
+    if (itr == messageIndex.end()) {
+        //TODO may be some logging, that's not normal
     } else {
-        itr->second->setProgress(progress, tr("Downloading..."));
+        itr->second->setProgress(progress);
     }
 }
 
@@ -229,14 +225,88 @@ void MessageLine::responseLocalFile(const QString& messageId, const QString& pat
     if (itr == messageIndex.end()) {
         
     } else {
+        Index::const_iterator uItr = uploading.find(messageId);
         if (path.size() > 0) {
-            itr->second->showFile(path);
+            Index::const_iterator dItr = downloading.find(messageId);
+            if (dItr != downloading.end()) {
+                downloading.erase(dItr);
+                itr->second->showFile(path);
+            } else {
+                if (uItr != uploading.end()) {
+                    uploading.erase(uItr);
+                    std::map<QString, QString>::const_iterator muItr = uploadPaths.find(messageId);
+                    if (muItr != uploadPaths.end()) {
+                        uploadPaths.erase(muItr);
+                    }
+                    if (room) {
+                        removeMessage(messageId);
+                    } else {
+                        Shared::Message msg = itr->second->getMessage();
+                        removeMessage(messageId);
+                        msg.setCurrentTime();
+                        message(msg);
+                        itr = messageIndex.find(messageId);
+                        itr->second->showFile(path);
+                    }
+                } else {
+                    itr->second->showFile(path); //then it is already cached file
+                }
+            }
         } else {
-            itr->second->addButton(QIcon::fromTheme("download"), tr("Download"), tr("Push the button to daownload the file"));
+            if (uItr != uploading.end()) {
+                itr->second->addButton(QIcon::fromTheme("download"), tr("Download"));
+                itr->second->showComment(tr("Push the button to daownload the file"));
+            } else {
+                qDebug() << "An unhandled state for file uploading - empty path";
+            }
         }
     }
 }
 
+void MessageLine::removeMessage(const QString& messageId)
+{
+    Index::const_iterator itr = messageIndex.find(messageId);
+    if (itr != messageIndex.end()) {
+        Message* ui = itr->second;
+        const Shared::Message& msg = ui->getMessage();
+        messageIndex.erase(itr);
+        Order::const_iterator oItr = messageOrder.find(msg.getTime());
+        if (oItr != messageOrder.end()) {
+            messageOrder.erase(oItr);
+        } else {
+            qDebug() << "An attempt to remove message from messageLine, but it wasn't found in order";
+        }
+        if (msg.getOutgoing()) {
+            Index::const_iterator mItr = myMessages.find(messageId);
+            if (mItr != myMessages.end()) {
+                myMessages.erase(mItr);
+            } else {
+                qDebug() << "Error removing message: it seems to be outgoing yet it wasn't found in outgoing messages";
+            }
+        } else {
+            if (room) {
+            
+            } else {
+                QString jid = msg.getFromJid();
+                std::map<QString, Index>::iterator pItr = palMessages.find(jid);
+                if (pItr != palMessages.end()) {
+                    Index& pMsgs = pItr->second;
+                    Index::const_iterator pmitr = pMsgs.find(messageId);
+                    if (pmitr != pMsgs.end()) {
+                        pMsgs.erase(pmitr);
+                    } else {
+                        qDebug() << "Error removing message: it seems to be incoming yet it wasn't found among messages from that penpal";
+                    }
+                }
+            }
+        }
+        ui->deleteLater();
+        qDebug() << "message" << messageId << "has been removed";
+    } else {
+        qDebug() << "An attempt to remove non existing message from messageLine";
+    }
+}
+
 void MessageLine::fileError(const QString& messageId, const QString& error)
 {
     Index::const_iterator itr = downloading.find(messageId);
@@ -245,18 +315,29 @@ void MessageLine::fileError(const QString& messageId, const QString& error)
         if (itr == uploading.end()) {
             //TODO may be some logging, that's not normal
         } else {
-            //itr->second->showError(error);
-            //itr->second->addDownloadDialog();
+            itr->second->showComment(tr("Error uploading file: %1\nYou can try again").arg(QCoreApplication::translate("NetworkErrors", error.toLatin1())), true);
+            itr->second->addButton(QIcon::fromTheme("upload"), tr("Upload"));
         }
     } else {
-        itr->second->addButton(QIcon::fromTheme("download"), tr("Download"), 
-                               tr("Error downloading file: %1\nYou can try again").arg(QCoreApplication::translate("NetworkErrors", error.toLatin1())));
+        itr->second->addButton(QIcon::fromTheme("download"), tr("Download"));
+        itr->second->showComment(tr("Error downloading file: %1\nYou can try again").arg(QCoreApplication::translate("NetworkErrors", error.toLatin1())), true);
     }
 }
 
 void MessageLine::appendMessageWithUpload(const Shared::Message& msg, const QString& path)
 {
     message(msg);
-    
+    QString id = msg.getId();
+    Message* ui = messageIndex.find(id)->second;
+    connect(ui, &Message::buttonClicked, this, &MessageLine::onUpload);     //this is in case of retry;
+    ui->setProgress(0);
+    ui->showComment("Uploading...");
+    uploading.insert(std::make_pair(id, ui));
+    uploadPaths.insert(std::make_pair(id, path));
+    emit uploadFile(msg, path);
 }
 
+void MessageLine::onUpload()
+{
+    //TODO retry
+}
diff --git a/ui/utils/messageline.h b/ui/utils/messageline.h
index 5bdb733..601604b 100644
--- a/ui/utils/messageline.h
+++ b/ui/utils/messageline.h
@@ -53,6 +53,7 @@ public:
     void fileError(const QString& messageId, const QString& error);
     void fileProgress(const QString& messageId, qreal progress);
     void appendMessageWithUpload(const Shared::Message& message, const QString& path);
+    void removeMessage(const QString& messageId);
     
 signals:
     void resize(int amount);
@@ -65,6 +66,7 @@ protected:
     
 protected:
     void onDownload();
+    void onUpload();
     
 private:
     struct Comparator {
@@ -86,7 +88,6 @@ private:
     
     QString myName;
     std::map<QString, QString> palNames;
-    std::deque<QHBoxLayout*> views;
     Index uploading;
     Index downloading;
     bool room;
diff --git a/ui/widgets/conversation.cpp b/ui/widgets/conversation.cpp
index 36e7b6e..b18d3b2 100644
--- a/ui/widgets/conversation.cpp
+++ b/ui/widgets/conversation.cpp
@@ -66,6 +66,7 @@ Conversation::Conversation(bool muc, const QString& mJid, const QString mRes, co
     connect(m_ui->sendButton, &QPushButton::clicked, this, &Conversation::onEnterPressed);
     connect(line, &MessageLine::resize, this, &Conversation::onMessagesResize);
     connect(line, &MessageLine::downloadFile, this, &Conversation::downloadFile);
+    connect(line, &MessageLine::uploadFile, this, qOverload<const Shared::Message&, const QString&>(&Conversation::sendMessage));
     connect(line, &MessageLine::requestLocalFile, this, &Conversation::requestLocalFile);
     connect(m_ui->attachButton, &QPushButton::clicked, this, &Conversation::onAttach);
     
@@ -180,9 +181,32 @@ void Conversation::onEnterPressed()
 {
     QString body(m_ui->messageEditor->toPlainText());
     
-    if (body.size() > 0) {
-        m_ui->messageEditor->clear();
-        handleSendMessage(body);
+    if (filesToAttach.size() > 0) {
+        for (Badge* badge : filesToAttach) {
+            Shared::Message msg;
+            if (isMuc) {
+                msg.setType(Shared::Message::groupChat);
+            } else {
+                msg.setType(Shared::Message::chat);
+                msg.setToResource(activePalResource);
+            }
+            msg.setFromJid(myJid);
+            msg.setFromResource(myResource);
+            msg.setToJid(palJid);
+            msg.setOutgoing(true);
+            msg.generateRandomId();
+            msg.setCurrentTime();
+            if (body.size() > 0) {
+                msg.setBody(body);
+            }
+            line->appendMessageWithUpload(msg, badge->id);
+        }
+        clearAttachedFiles();
+    } else {
+        if (body.size() > 0) {
+            m_ui->messageEditor->clear();
+            handleSendMessage(body);
+        }
     }
 }
 
@@ -294,14 +318,14 @@ void Conversation::onScrollResize()
     }
 }
 
-void Conversation::responseDownloadProgress(const QString& messageId, qreal progress)
+void Conversation::responseFileProgress(const QString& messageId, qreal progress)
 {
-    line->responseDownloadProgress(messageId, progress);
+    line->fileProgress(messageId, progress);
 }
 
-void Conversation::downloadError(const QString& messageId, const QString& error)
+void Conversation::fileError(const QString& messageId, const QString& error)
 {
-    line->downloadError(messageId, error);
+    line->fileError(messageId, error);
 }
 
 void Conversation::responseLocalFile(const QString& messageId, const QString& path)
diff --git a/ui/widgets/conversation.h b/ui/widgets/conversation.h
index cb738b9..386e20a 100644
--- a/ui/widgets/conversation.h
+++ b/ui/widgets/conversation.h
@@ -75,8 +75,8 @@ public:
     void responseArchive(const std::list<Shared::Message> list);
     void showEvent(QShowEvent * event) override;
     void responseLocalFile(const QString& messageId, const QString& path);
-    void downloadError(const QString& messageId, const QString& error);
-    void responseDownloadProgress(const QString& messageId, qreal progress);
+    void fileError(const QString& messageId, const QString& error);
+    void responseFileProgress(const QString& messageId, qreal progress);
     
 signals:
     void sendMessage(const Shared::Message& message);

From 326eef864b2d9ca863d327c355d8631eb587db25 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Thu, 14 Nov 2019 14:43:43 +0300
Subject: [PATCH 026/281] some fixes about uploading, some error handling

---
 core/account.cpp            |   4 +
 core/account.h              |   1 +
 core/squawk.cpp             |   2 +
 translations/squawk.ru.ts   | 176 +++++++++++++++++++++++++++++-------
 ui/utils/message.cpp        |   4 +-
 ui/utils/message.h          |   2 +-
 ui/utils/messageline.cpp    |  10 +-
 ui/utils/resizer.cpp        |   3 +-
 ui/utils/resizer.h          |   3 +-
 ui/widgets/conversation.cpp |  46 +++++++---
 ui/widgets/conversation.h   |   5 +-
 11 files changed, 200 insertions(+), 56 deletions(-)

diff --git a/core/account.cpp b/core/account.cpp
index c238a23..492ebdc 100644
--- a/core/account.cpp
+++ b/core/account.cpp
@@ -662,14 +662,17 @@ void Core::Account::sendMessage(const Shared::Message& data, const QString& path
                             um->requestUploadSlot(file);
                         }
                     } else {
+                        emit onFileUploadError(data.getId(), "Uploading file dissapeared or your system user has no permission to read it");
                         qDebug() << "Requested upload slot in account" << name << "for file" << path << "but the file doesn't exist or is not readable";
                     }
                 } else {
+                    emit onFileUploadError(data.getId(), "Your server doesn't support file upload service, or it's prohibited for your account");
                     qDebug() << "Requested upload slot in account" << name << "for file" << path << "but upload manager didn't discover any upload services";
                 }
             }
         }
     } else {
+        emit onFileUploadError(data.getId(), "Account is offline or reconnecting");
         qDebug() << "An attempt to send message with not connected account " << name << ", skipping";
     }
 }
@@ -1639,6 +1642,7 @@ void Core::Account::onUploadSlotRequestFailed(const QXmppHttpUploadRequestIq& re
     } else {
         const std::pair<QString, Shared::Message>& pair = uploadingSlotsQueue.front();
         qDebug() << "Error requesting upload slot for file" << pair.first << "in account" << name << ":" << request.error().text();
+        emit uploadFileError(pair.second.getId(), "Error requesting slot to upload file: " + request.error().text());
         
         if (uploadingSlotsQueue.size() > 0) {
             um->requestUploadSlot(uploadingSlotsQueue.front().first);
diff --git a/core/account.h b/core/account.h
index 38631af..ff77455 100644
--- a/core/account.h
+++ b/core/account.h
@@ -118,6 +118,7 @@ signals:
     void removeRoomParticipant(const QString& jid, const QString& nickName);
     void receivedVCard(const QString& jid, const Shared::VCard& card);
     void uploadFile(const QFileInfo& file, const QUrl& set, const QUrl& get, QMap<QString, QString> headers);
+    void uploadFileError(const QString& messageId, const QString& error);
     
 private:
     QString name;
diff --git a/core/squawk.cpp b/core/squawk.cpp
index 1842367..7ca41e3 100644
--- a/core/squawk.cpp
+++ b/core/squawk.cpp
@@ -137,6 +137,8 @@ void Core::Squawk::addAccount(const QString& login, const QString& server, const
     
     connect(acc, &Account::receivedVCard, this, &Squawk::responseVCard);
     
+    connect(acc, &Account::uploadFileError, this, &Squawk::uploadFileError);
+    
     QMap<QString, QVariant> map = {
         {"login", login},
         {"server", server},
diff --git a/translations/squawk.ru.ts b/translations/squawk.ru.ts
index e7bae69..412b6d8 100644
--- a/translations/squawk.ru.ts
+++ b/translations/squawk.ru.ts
@@ -5,77 +5,92 @@
     <name>Account</name>
     <message>
         <location filename="../ui/widgets/account.ui" line="14"/>
+        <location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_account.h" line="127"/>
         <source>Account</source>
         <translatorcomment>Заголовок окна</translatorcomment>
         <translation>Учетная запись</translation>
     </message>
     <message>
         <location filename="../ui/widgets/account.ui" line="40"/>
+        <location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_account.h" line="129"/>
         <source>Your account login</source>
         <translation>Имя пользователя Вашей учетной записи</translation>
     </message>
     <message>
         <location filename="../ui/widgets/account.ui" line="43"/>
+        <location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_account.h" line="131"/>
         <source>john_smith1987</source>
         <translation>ivan_ivanov1987</translation>
     </message>
     <message>
         <location filename="../ui/widgets/account.ui" line="50"/>
+        <location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_account.h" line="132"/>
         <source>Server</source>
         <translation>Сервер</translation>
     </message>
     <message>
         <location filename="../ui/widgets/account.ui" line="57"/>
+        <location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_account.h" line="134"/>
         <source>A server address of your account. Like 404.city or macaw.me</source>
         <translation>Адресс сервера вашей учетной записи (выглядит как 404.city или macaw.me)</translation>
     </message>
     <message>
         <location filename="../ui/widgets/account.ui" line="60"/>
+        <location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_account.h" line="136"/>
         <source>macaw.me</source>
         <translation>macaw.me</translation>
     </message>
     <message>
         <location filename="../ui/widgets/account.ui" line="67"/>
+        <location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_account.h" line="137"/>
         <source>Login</source>
         <translation>Имя учетной записи</translation>
     </message>
     <message>
         <location filename="../ui/widgets/account.ui" line="74"/>
+        <location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_account.h" line="138"/>
         <source>Password</source>
         <translation>Пароль</translation>
     </message>
     <message>
         <location filename="../ui/widgets/account.ui" line="81"/>
+        <location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_account.h" line="140"/>
         <source>Password of your account</source>
         <translation>Пароль вашей учетной записи</translation>
     </message>
     <message>
         <location filename="../ui/widgets/account.ui" line="103"/>
+        <location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_account.h" line="145"/>
         <source>Name</source>
         <translation>Имя</translation>
     </message>
     <message>
         <location filename="../ui/widgets/account.ui" line="110"/>
+        <location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_account.h" line="147"/>
         <source>Just a name how would you call this account, doesn&apos;t affect anything</source>
         <translation>Просто имя, то как Вы называете свою учетную запись, может быть любым</translation>
     </message>
     <message>
         <location filename="../ui/widgets/account.ui" line="113"/>
+        <location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_account.h" line="149"/>
         <source>John</source>
         <translation>Иван</translation>
     </message>
     <message>
         <location filename="../ui/widgets/account.ui" line="120"/>
+        <location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_account.h" line="150"/>
         <source>Resource</source>
         <translation>Ресурс</translation>
     </message>
     <message>
         <location filename="../ui/widgets/account.ui" line="127"/>
+        <location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_account.h" line="152"/>
         <source>A resource name like &quot;Home&quot; or &quot;Work&quot;</source>
         <translation>Имя этой программы для ваших контактов, может быть &quot;Home&quot; или &quot;Phone&quot;</translation>
     </message>
     <message>
         <location filename="../ui/widgets/account.ui" line="130"/>
+        <location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_account.h" line="154"/>
         <source>QXmpp</source>
         <translatorcomment>Ресурс по умолчанию</translatorcomment>
         <translation>QXmpp</translation>
@@ -85,31 +100,37 @@
     <name>Accounts</name>
     <message>
         <location filename="../ui/widgets/accounts.ui" line="14"/>
+        <location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_accounts.h" line="108"/>
         <source>Accounts</source>
         <translation>Учетные записи</translation>
     </message>
     <message>
         <location filename="../ui/widgets/accounts.ui" line="45"/>
+        <location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_accounts.h" line="109"/>
         <source>Delete</source>
         <translation>Удалить</translation>
     </message>
     <message>
         <location filename="../ui/widgets/accounts.ui" line="86"/>
+        <location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_accounts.h" line="110"/>
         <source>Add</source>
         <translation>Добавить</translation>
     </message>
     <message>
         <location filename="../ui/widgets/accounts.ui" line="96"/>
+        <location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_accounts.h" line="111"/>
         <source>Edit</source>
         <translation>Редактировать</translation>
     </message>
     <message>
         <location filename="../ui/widgets/accounts.ui" line="106"/>
+        <location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_accounts.h" line="112"/>
         <source>Change password</source>
         <translation>Изменить пароль</translation>
     </message>
     <message>
         <location filename="../ui/widgets/accounts.ui" line="129"/>
+        <location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_accounts.h" line="113"/>
         <location filename="../ui/widgets/accounts.cpp" line="125"/>
         <location filename="../ui/widgets/accounts.cpp" line="128"/>
         <source>Connect</source>
@@ -125,11 +146,12 @@
     <name>Conversation</name>
     <message>
         <location filename="../ui/widgets/conversation.ui" line="449"/>
+        <location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_conversation.h" line="324"/>
         <source>Type your message here...</source>
         <translation>Введите сообщение...</translation>
     </message>
     <message>
-        <location filename="../ui/widgets/conversation.cpp" line="260"/>
+        <location filename="../ui/widgets/conversation.cpp" line="284"/>
         <source>Chose a file to send</source>
         <translation>Выберите файл для отправки</translation>
     </message>
@@ -281,52 +303,62 @@
     <name>JoinConference</name>
     <message>
         <location filename="../ui/widgets/joinconference.ui" line="14"/>
+        <location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_joinconference.h" line="116"/>
         <source>Join new conference</source>
         <translatorcomment>Заголовок окна</translatorcomment>
         <translation>Присоединиться к новой беседе</translation>
     </message>
     <message>
         <location filename="../ui/widgets/joinconference.ui" line="22"/>
+        <location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_joinconference.h" line="117"/>
         <source>JID</source>
         <translation>JID</translation>
     </message>
     <message>
         <location filename="../ui/widgets/joinconference.ui" line="29"/>
+        <location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_joinconference.h" line="119"/>
         <source>Room JID</source>
         <translation>Jabber-идентификатор беседы</translation>
     </message>
     <message>
         <location filename="../ui/widgets/joinconference.ui" line="32"/>
+        <location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_joinconference.h" line="121"/>
         <source>identifier@conference.server.org</source>
         <translation>identifier@conference.server.org</translation>
     </message>
     <message>
         <location filename="../ui/widgets/joinconference.ui" line="39"/>
+        <location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_joinconference.h" line="122"/>
         <source>Account</source>
         <translation>Учетная запись</translation>
     </message>
     <message>
         <location filename="../ui/widgets/joinconference.ui" line="49"/>
+        <location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_joinconference.h" line="123"/>
         <source>Join on login</source>
         <translation>Автовход</translation>
     </message>
     <message>
         <location filename="../ui/widgets/joinconference.ui" line="56"/>
+        <location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_joinconference.h" line="125"/>
         <source>If checked Squawk will try to join this conference on login</source>
         <translation>Если стоит галочка Squawk автоматически присоединится к этой беседе при подключении</translation>
     </message>
     <message>
         <location filename="../ui/widgets/joinconference.ui" line="66"/>
+        <location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_joinconference.h" line="128"/>
         <source>Nick name</source>
         <translation>Псевдоним</translation>
     </message>
     <message>
         <location filename="../ui/widgets/joinconference.ui" line="73"/>
+        <location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_joinconference.h" line="130"/>
         <source>Your nick name for that conference. If you leave this field empty your account name will be used as a nick name</source>
         <translation>Ваш псевдоним в этой беседе, если оставите это поле пустым - будет использовано имя Вашей учетной записи</translation>
     </message>
     <message>
         <location filename="../ui/widgets/joinconference.ui" line="76"/>
+        <location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_joinconference.h" line="132"/>
         <source>John</source>
         <translation>Ivan</translation>
     </message>
@@ -334,26 +366,58 @@
 <context>
     <name>Message</name>
     <message>
-        <location filename="../ui/utils/message.cpp" line="119"/>
         <source>Download</source>
         <translation>Скачать</translation>
     </message>
     <message>
-        <location filename="../ui/utils/message.cpp" line="123"/>
+        <location filename="../ui/utils/message.cpp" line="172"/>
+        <source>Open</source>
+        <translation>Открыть</translation>
+    </message>
+</context>
+<context>
+    <name>MessageLine</name>
+    <message>
+        <location filename="../ui/utils/messageline.cpp" line="146"/>
+        <source>Downloading...</source>
+        <translation>Скачивается...</translation>
+    </message>
+    <message>
+        <location filename="../ui/utils/messageline.cpp" line="258"/>
+        <location filename="../ui/utils/messageline.cpp" line="324"/>
+        <source>Download</source>
+        <translation>Скачать</translation>
+    </message>
+    <message>
+        <location filename="../ui/utils/messageline.cpp" line="259"/>
+        <source>Push the button to daownload the file</source>
+        <translation>Нажмите на кнопку что бы загрузить файл</translation>
+    </message>
+    <message>
+        <location filename="../ui/utils/messageline.cpp" line="319"/>
+        <source>Error uploading file: %1
+You can try again</source>
+        <translation>Ошибка загрузки файла на сервер:
+%1
+Для того, что бы попробовать снова нажмите на кнопку</translation>
+    </message>
+    <message>
+        <location filename="../ui/utils/messageline.cpp" line="320"/>
+        <source>Upload</source>
+        <translation>Загрузить</translation>
+    </message>
+    <message>
+        <location filename="../ui/utils/messageline.cpp" line="325"/>
         <source>Error downloading file: %1
 You can try again</source>
-        <translation>Ошибка загрузки файла: %1
+        <translation>Ошибка скачивания файла: 
+%1
 Вы можете попробовать снова</translation>
     </message>
     <message>
-        <location filename="../ui/utils/message.cpp" line="125"/>
-        <source>%1 is offering you to download a file</source>
-        <translation>%1 предлагает Вам скачать файл</translation>
-    </message>
-    <message>
-        <location filename="../ui/utils/message.cpp" line="190"/>
-        <source>Open</source>
-        <translation>Открыть</translation>
+        <location filename="../ui/utils/messageline.cpp" line="336"/>
+        <source>Uploading...</source>
+        <translation>Загружается...</translation>
     </message>
 </context>
 <context>
@@ -470,48 +534,57 @@ You can try again</source>
     <name>NewContact</name>
     <message>
         <location filename="../ui/widgets/newcontact.ui" line="14"/>
+        <location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_newcontact.h" line="103"/>
         <source>Add new contact</source>
         <translatorcomment>Заголовок окна</translatorcomment>
         <translation>Добавление нового контакта</translation>
     </message>
     <message>
         <location filename="../ui/widgets/newcontact.ui" line="22"/>
+        <location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_newcontact.h" line="104"/>
         <source>Account</source>
         <translation>Учетная запись</translation>
     </message>
     <message>
         <location filename="../ui/widgets/newcontact.ui" line="29"/>
+        <location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_newcontact.h" line="106"/>
         <source>An account that is going to have new contact</source>
         <translation>Учетная запись для которой будет добавлен контакт</translation>
     </message>
     <message>
         <location filename="../ui/widgets/newcontact.ui" line="36"/>
+        <location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_newcontact.h" line="108"/>
         <source>JID</source>
         <translation>JID</translation>
     </message>
     <message>
         <location filename="../ui/widgets/newcontact.ui" line="43"/>
+        <location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_newcontact.h" line="110"/>
         <source>Jabber id of your new contact</source>
         <translation>Jabber-идентификатор нового контакта</translation>
     </message>
     <message>
         <location filename="../ui/widgets/newcontact.ui" line="46"/>
+        <location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_newcontact.h" line="112"/>
         <source>name@server.dmn</source>
         <translatorcomment>Placeholder поля ввода JID</translatorcomment>
         <translation>name@server.dmn</translation>
     </message>
     <message>
         <location filename="../ui/widgets/newcontact.ui" line="53"/>
+        <location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_newcontact.h" line="113"/>
         <source>Name</source>
         <translation>Имя</translation>
     </message>
     <message>
         <location filename="../ui/widgets/newcontact.ui" line="60"/>
+        <location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_newcontact.h" line="115"/>
         <source>The way this new contact will be labeled in your roster (optional)</source>
         <translation>То, как будет подписан контакт в вашем списке контактов (не обязательно)</translation>
     </message>
     <message>
         <location filename="../ui/widgets/newcontact.ui" line="63"/>
+        <location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_newcontact.h" line="117"/>
         <source>John Smith</source>
         <translation>Иван Иванов</translation>
     </message>
@@ -520,36 +593,43 @@ You can try again</source>
     <name>Squawk</name>
     <message>
         <location filename="../ui/squawk.ui" line="14"/>
+        <location filename="../build/ui/squawkUI_autogen/include/ui_squawk.h" line="137"/>
         <source>squawk</source>
         <translation>Squawk</translation>
     </message>
     <message>
         <location filename="../ui/squawk.ui" line="78"/>
+        <location filename="../build/ui/squawkUI_autogen/include/ui_squawk.h" line="143"/>
         <source>Settings</source>
         <translation>Настройки</translation>
     </message>
     <message>
         <location filename="../ui/squawk.ui" line="84"/>
+        <location filename="../build/ui/squawkUI_autogen/include/ui_squawk.h" line="144"/>
         <source>Squawk</source>
         <translation>Squawk</translation>
     </message>
     <message>
         <location filename="../ui/squawk.ui" line="99"/>
+        <location filename="../build/ui/squawkUI_autogen/include/ui_squawk.h" line="138"/>
         <source>Accounts</source>
         <translation>Учетные записи</translation>
     </message>
     <message>
         <location filename="../ui/squawk.ui" line="108"/>
+        <location filename="../build/ui/squawkUI_autogen/include/ui_squawk.h" line="139"/>
         <source>Quit</source>
         <translation>Выйти</translation>
     </message>
     <message>
         <location filename="../ui/squawk.ui" line="120"/>
+        <location filename="../build/ui/squawkUI_autogen/include/ui_squawk.h" line="140"/>
         <source>Add contact</source>
         <translation>Добавить контакт</translation>
     </message>
     <message>
         <location filename="../ui/squawk.ui" line="132"/>
+        <location filename="../build/ui/squawkUI_autogen/include/ui_squawk.h" line="141"/>
         <source>Add conference</source>
         <translation>Присоединиться к беседе</translation>
     </message>
@@ -559,52 +639,52 @@ You can try again</source>
         <translation>Список контактов</translation>
     </message>
     <message>
-        <location filename="../ui/squawk.cpp" line="549"/>
+        <location filename="../ui/squawk.cpp" line="558"/>
         <source>Disconnect</source>
         <translation>Отключить</translation>
     </message>
     <message>
-        <location filename="../ui/squawk.cpp" line="555"/>
+        <location filename="../ui/squawk.cpp" line="564"/>
         <source>Connect</source>
         <translation>Подключить</translation>
     </message>
     <message>
-        <location filename="../ui/squawk.cpp" line="561"/>
-        <location filename="../ui/squawk.cpp" line="659"/>
+        <location filename="../ui/squawk.cpp" line="570"/>
+        <location filename="../ui/squawk.cpp" line="668"/>
         <source>VCard</source>
         <translation>Карточка</translation>
     </message>
     <message>
-        <location filename="../ui/squawk.cpp" line="565"/>
-        <location filename="../ui/squawk.cpp" line="663"/>
-        <location filename="../ui/squawk.cpp" line="703"/>
+        <location filename="../ui/squawk.cpp" line="574"/>
+        <location filename="../ui/squawk.cpp" line="672"/>
+        <location filename="../ui/squawk.cpp" line="712"/>
         <source>Remove</source>
         <translation>Удалить</translation>
     </message>
     <message>
-        <location filename="../ui/squawk.cpp" line="577"/>
+        <location filename="../ui/squawk.cpp" line="586"/>
         <source>Open dialog</source>
         <translation>Открыть диалог</translation>
     </message>
     <message>
-        <location filename="../ui/squawk.cpp" line="587"/>
-        <location filename="../ui/squawk.cpp" line="684"/>
+        <location filename="../ui/squawk.cpp" line="596"/>
+        <location filename="../ui/squawk.cpp" line="693"/>
         <source>Unsubscribe</source>
         <translation>Отписаться</translation>
     </message>
     <message>
-        <location filename="../ui/squawk.cpp" line="597"/>
-        <location filename="../ui/squawk.cpp" line="693"/>
+        <location filename="../ui/squawk.cpp" line="606"/>
+        <location filename="../ui/squawk.cpp" line="702"/>
         <source>Subscribe</source>
         <translation>Подписаться</translation>
     </message>
     <message>
-        <location filename="../ui/squawk.cpp" line="608"/>
+        <location filename="../ui/squawk.cpp" line="617"/>
         <source>Rename</source>
         <translation>Переименовать</translation>
     </message>
     <message>
-        <location filename="../ui/squawk.cpp" line="621"/>
+        <location filename="../ui/squawk.cpp" line="630"/>
         <source>Input new name for %1
 or leave it empty for the contact 
 to be displayed as %1</source>
@@ -614,47 +694,47 @@ to be displayed as %1</source>
 %1</translation>
     </message>
     <message>
-        <location filename="../ui/squawk.cpp" line="622"/>
+        <location filename="../ui/squawk.cpp" line="631"/>
         <source>Renaming %1</source>
         <translation>Назначение имени контакту %1</translation>
     </message>
     <message>
-        <location filename="../ui/squawk.cpp" line="628"/>
+        <location filename="../ui/squawk.cpp" line="637"/>
         <source>Groups</source>
         <translation>Группы</translation>
     </message>
     <message>
-        <location filename="../ui/squawk.cpp" line="643"/>
+        <location filename="../ui/squawk.cpp" line="652"/>
         <source>New group</source>
         <translation>Создать новую группу</translation>
     </message>
     <message>
-        <location filename="../ui/squawk.cpp" line="653"/>
+        <location filename="../ui/squawk.cpp" line="662"/>
         <source>New group name</source>
         <translation>Имя группы</translation>
     </message>
     <message>
-        <location filename="../ui/squawk.cpp" line="654"/>
+        <location filename="../ui/squawk.cpp" line="663"/>
         <source>Add %1 to a new group</source>
         <translation>Добавление %1 в новую группу</translation>
     </message>
     <message>
-        <location filename="../ui/squawk.cpp" line="675"/>
+        <location filename="../ui/squawk.cpp" line="684"/>
         <source>Open conversation</source>
         <translation>Открыть окно беседы</translation>
     </message>
     <message>
-        <location filename="../ui/squawk.cpp" line="780"/>
+        <location filename="../ui/squawk.cpp" line="789"/>
         <source>%1 account card</source>
         <translation>Карточка учетной записи %1</translation>
     </message>
     <message>
-        <location filename="../ui/squawk.cpp" line="782"/>
+        <location filename="../ui/squawk.cpp" line="791"/>
         <source>%1 contact card</source>
         <translation>Карточка контакта %1</translation>
     </message>
     <message>
-        <location filename="../ui/squawk.cpp" line="794"/>
+        <location filename="../ui/squawk.cpp" line="803"/>
         <source>Downloading vCard</source>
         <translation>Получение карточки</translation>
     </message>
@@ -663,119 +743,145 @@ to be displayed as %1</source>
     <name>VCard</name>
     <message>
         <location filename="../ui/widgets/vcard/vcard.ui" line="65"/>
+        <location filename="../build/ui/widgets/vcard/vCardUI_autogen/include/ui_vcard.h" line="612"/>
         <source>Received 12.07.2007 at 17.35</source>
         <translation>Не обновлялось</translation>
     </message>
     <message>
         <location filename="../ui/widgets/vcard/vcard.ui" line="100"/>
         <location filename="../ui/widgets/vcard/vcard.ui" line="425"/>
+        <location filename="../build/ui/widgets/vcard/vCardUI_autogen/include/ui_vcard.h" line="624"/>
+        <location filename="../build/ui/widgets/vcard/vCardUI_autogen/include/ui_vcard.h" line="627"/>
         <source>General</source>
         <translation>Общее</translation>
     </message>
     <message>
         <location filename="../ui/widgets/vcard/vcard.ui" line="130"/>
+        <location filename="../build/ui/widgets/vcard/vCardUI_autogen/include/ui_vcard.h" line="613"/>
         <source>Organization</source>
         <translation>Место работы</translation>
     </message>
     <message>
         <location filename="../ui/widgets/vcard/vcard.ui" line="180"/>
+        <location filename="../build/ui/widgets/vcard/vCardUI_autogen/include/ui_vcard.h" line="614"/>
         <source>Middle name</source>
         <translation>Среднее имя</translation>
     </message>
     <message>
         <location filename="../ui/widgets/vcard/vcard.ui" line="190"/>
+        <location filename="../build/ui/widgets/vcard/vCardUI_autogen/include/ui_vcard.h" line="615"/>
         <source>First name</source>
         <translation>Имя</translation>
     </message>
     <message>
         <location filename="../ui/widgets/vcard/vcard.ui" line="200"/>
+        <location filename="../build/ui/widgets/vcard/vCardUI_autogen/include/ui_vcard.h" line="616"/>
         <source>Last name</source>
         <translation>Фамилия</translation>
     </message>
     <message>
         <location filename="../ui/widgets/vcard/vcard.ui" line="226"/>
+        <location filename="../build/ui/widgets/vcard/vCardUI_autogen/include/ui_vcard.h" line="617"/>
         <source>Nick name</source>
         <translation>Псевдоним</translation>
     </message>
     <message>
         <location filename="../ui/widgets/vcard/vcard.ui" line="252"/>
+        <location filename="../build/ui/widgets/vcard/vCardUI_autogen/include/ui_vcard.h" line="618"/>
         <source>Birthday</source>
         <translation>Дата рождения</translation>
     </message>
     <message>
         <location filename="../ui/widgets/vcard/vcard.ui" line="272"/>
+        <location filename="../build/ui/widgets/vcard/vCardUI_autogen/include/ui_vcard.h" line="619"/>
         <source>Organization name</source>
         <translation>Название организации</translation>
     </message>
     <message>
         <location filename="../ui/widgets/vcard/vcard.ui" line="298"/>
+        <location filename="../build/ui/widgets/vcard/vCardUI_autogen/include/ui_vcard.h" line="620"/>
         <source>Unit / Department</source>
         <translation>Отдел</translation>
     </message>
     <message>
         <location filename="../ui/widgets/vcard/vcard.ui" line="324"/>
+        <location filename="../build/ui/widgets/vcard/vCardUI_autogen/include/ui_vcard.h" line="621"/>
         <source>Role / Profession</source>
         <translation>Профессия</translation>
     </message>
     <message>
         <location filename="../ui/widgets/vcard/vcard.ui" line="350"/>
+        <location filename="../build/ui/widgets/vcard/vCardUI_autogen/include/ui_vcard.h" line="622"/>
         <source>Job title</source>
         <translation>Наименование должности</translation>
     </message>
     <message>
         <location filename="../ui/widgets/vcard/vcard.ui" line="390"/>
+        <location filename="../build/ui/widgets/vcard/vCardUI_autogen/include/ui_vcard.h" line="623"/>
         <source>Full name</source>
         <translation>Полное имя</translation>
     </message>
     <message>
         <location filename="../ui/widgets/vcard/vcard.ui" line="460"/>
+        <location filename="../build/ui/widgets/vcard/vCardUI_autogen/include/ui_vcard.h" line="625"/>
         <source>Personal information</source>
         <translation>Личная информация</translation>
     </message>
     <message>
         <location filename="../ui/widgets/vcard/vcard.ui" line="653"/>
+        <location filename="../build/ui/widgets/vcard/vCardUI_autogen/include/ui_vcard.h" line="629"/>
         <source>Addresses</source>
         <translation>Адреса</translation>
     </message>
     <message>
         <location filename="../ui/widgets/vcard/vcard.ui" line="682"/>
+        <location filename="../build/ui/widgets/vcard/vCardUI_autogen/include/ui_vcard.h" line="630"/>
         <source>E-Mail addresses</source>
         <translation>Адреса электронной почты</translation>
     </message>
     <message>
         <location filename="../ui/widgets/vcard/vcard.ui" line="767"/>
+        <location filename="../build/ui/widgets/vcard/vCardUI_autogen/include/ui_vcard.h" line="633"/>
         <source>Phone numbers</source>
         <translation>Номера телефонов</translation>
     </message>
     <message>
         <location filename="../ui/widgets/vcard/vcard.ui" line="522"/>
         <location filename="../ui/widgets/vcard/vcard.ui" line="546"/>
+        <location filename="../build/ui/widgets/vcard/vCardUI_autogen/include/ui_vcard.h" line="628"/>
+        <location filename="../build/ui/widgets/vcard/vCardUI_autogen/include/ui_vcard.h" line="634"/>
         <source>Contact</source>
         <translation>Контактная информация</translation>
     </message>
     <message>
         <location filename="../ui/widgets/vcard/vcard.ui" line="713"/>
+        <location filename="../build/ui/widgets/vcard/vCardUI_autogen/include/ui_vcard.h" line="631"/>
         <source>Jabber ID</source>
         <translation>Jabber ID</translation>
     </message>
     <message>
         <location filename="../ui/widgets/vcard/vcard.ui" line="739"/>
+        <location filename="../build/ui/widgets/vcard/vCardUI_autogen/include/ui_vcard.h" line="632"/>
         <source>Web site</source>
         <translation>Веб сайт</translation>
     </message>
     <message>
         <location filename="../ui/widgets/vcard/vcard.ui" line="798"/>
         <location filename="../ui/widgets/vcard/vcard.ui" line="822"/>
+        <location filename="../build/ui/widgets/vcard/vCardUI_autogen/include/ui_vcard.h" line="635"/>
+        <location filename="../build/ui/widgets/vcard/vCardUI_autogen/include/ui_vcard.h" line="636"/>
         <source>Description</source>
         <translation>Описание</translation>
     </message>
     <message>
         <location filename="../ui/widgets/vcard/vcard.ui" line="859"/>
+        <location filename="../build/ui/widgets/vcard/vCardUI_autogen/include/ui_vcard.h" line="610"/>
         <source>Set avatar</source>
         <translation>Установить иконку</translation>
     </message>
     <message>
         <location filename="../ui/widgets/vcard/vcard.ui" line="868"/>
+        <location filename="../build/ui/widgets/vcard/vCardUI_autogen/include/ui_vcard.h" line="611"/>
         <source>Clear avatar</source>
         <translation>Убрать иконку</translation>
     </message>
diff --git a/ui/utils/message.cpp b/ui/utils/message.cpp
index d826436..db517e9 100644
--- a/ui/utils/message.cpp
+++ b/ui/utils/message.cpp
@@ -109,7 +109,7 @@ void Message::setSender(const QString& p_sender)
     sender->setText(p_sender);
 }
 
-void Message::addButton(const QIcon& icon, const QString& buttonText)
+void Message::addButton(const QIcon& icon, const QString& buttonText, const QString& tooltip)
 {
     hideFile();
     hideProgress();
@@ -120,7 +120,7 @@ void Message::addButton(const QIcon& icon, const QString& buttonText)
             text->hide();
         }
         button = new QPushButton(icon, buttonText);
-        button->setToolTip("<a href=\"" + msg.getOutOfBandUrl() + "\">" + msg.getOutOfBandUrl() + "</a>");
+        button->setToolTip(tooltip);
         connect(button, &QPushButton::clicked, this, &Message::buttonClicked);
         bodyLayout->insertWidget(2, button);
         hasButton = true;
diff --git a/ui/utils/message.h b/ui/utils/message.h
index 4147178..6bea433 100644
--- a/ui/utils/message.h
+++ b/ui/utils/message.h
@@ -49,7 +49,7 @@ public:
     QString getFileUrl() const;
     const Shared::Message& getMessage() const;
     
-    void addButton(const QIcon& icon, const QString& buttonText);
+    void addButton(const QIcon& icon, const QString& buttonText, const QString& tooltip = "");
     void showComment(const QString& comment, bool wordWrap = false);
     void hideComment();
     void showFile(const QString& path);
diff --git a/ui/utils/messageline.cpp b/ui/utils/messageline.cpp
index c17baca..dc3eeda 100644
--- a/ui/utils/messageline.cpp
+++ b/ui/utils/messageline.cpp
@@ -253,8 +253,9 @@ void MessageLine::responseLocalFile(const QString& messageId, const QString& pat
                 }
             }
         } else {
-            if (uItr != uploading.end()) {
-                itr->second->addButton(QIcon::fromTheme("download"), tr("Download"));
+            if (uItr == uploading.end()) {
+                const Shared::Message& msg = itr->second->getMessage();
+                itr->second->addButton(QIcon::fromTheme("download"), tr("Download"), "<a href=\"" + msg.getOutOfBandUrl() + "\">" + msg.getOutOfBandUrl() + "</a>");
                 itr->second->showComment(tr("Push the button to daownload the file"));
             } else {
                 qDebug() << "An unhandled state for file uploading - empty path";
@@ -319,7 +320,8 @@ void MessageLine::fileError(const QString& messageId, const QString& error)
             itr->second->addButton(QIcon::fromTheme("upload"), tr("Upload"));
         }
     } else {
-        itr->second->addButton(QIcon::fromTheme("download"), tr("Download"));
+        const Shared::Message& msg = itr->second->getMessage();
+        itr->second->addButton(QIcon::fromTheme("download"), tr("Download"), "<a href=\"" + msg.getOutOfBandUrl() + "\">" + msg.getOutOfBandUrl() + "</a>");
         itr->second->showComment(tr("Error downloading file: %1\nYou can try again").arg(QCoreApplication::translate("NetworkErrors", error.toLatin1())), true);
     }
 }
@@ -331,7 +333,7 @@ void MessageLine::appendMessageWithUpload(const Shared::Message& msg, const QStr
     Message* ui = messageIndex.find(id)->second;
     connect(ui, &Message::buttonClicked, this, &MessageLine::onUpload);     //this is in case of retry;
     ui->setProgress(0);
-    ui->showComment("Uploading...");
+    ui->showComment(tr("Uploading..."));
     uploading.insert(std::make_pair(id, ui));
     uploadPaths.insert(std::make_pair(id, path));
     emit uploadFile(msg, path);
diff --git a/ui/utils/resizer.cpp b/ui/utils/resizer.cpp
index 45a21e8..8691400 100644
--- a/ui/utils/resizer.cpp
+++ b/ui/utils/resizer.cpp
@@ -27,7 +27,8 @@ QObject(parent)
 bool Resizer::eventFilter(QObject* obj, QEvent* event)
 {
     if (event->type() == QEvent::Resize) {
-        emit resized();
+        QResizeEvent* ev = static_cast<QResizeEvent*>(event);
+        emit resized(ev->oldSize(), ev->size());
     }
     
     return false;
diff --git a/ui/utils/resizer.h b/ui/utils/resizer.h
index 85fc97e..735b2fb 100644
--- a/ui/utils/resizer.h
+++ b/ui/utils/resizer.h
@@ -22,6 +22,7 @@
 #include <QObject>
 #include <QWidget>
 #include <QEvent>
+#include <QResizeEvent>
 
 /**
  * @todo write docs
@@ -35,7 +36,7 @@ protected:
     bool eventFilter(QObject* obj, QEvent* event) override;
     
 signals:
-    void resized();
+    void resized(const QSize& oldSize, const QSize& newSize);
 };
 
 #endif // RESIZER_H
diff --git a/ui/widgets/conversation.cpp b/ui/widgets/conversation.cpp
index b18d3b2..07a160f 100644
--- a/ui/widgets/conversation.cpp
+++ b/ui/widgets/conversation.cpp
@@ -36,7 +36,8 @@ Conversation::Conversation(bool muc, const QString& mJid, const QString mRes, co
     line(new MessageLine(muc)),
     m_ui(new Ui::Conversation()),
     ker(),
-    res(),
+    scrollResizeCatcher(),
+    attachResizeCatcher(),
     vis(),
     thread(),
     statusIcon(0),
@@ -60,7 +61,8 @@ Conversation::Conversation(bool muc, const QString& mJid, const QString mRes, co
     statusLabel = m_ui->statusLabel;
     
     connect(&ker, &KeyEnterReceiver::enterPressed, this, &Conversation::onEnterPressed);
-    connect(&res, &Resizer::resized, this, &Conversation::onScrollResize);
+    connect(&scrollResizeCatcher, &Resizer::resized, this, &Conversation::onScrollResize);
+    connect(&attachResizeCatcher, &Resizer::resized, this, &Conversation::onAttachResize);
     connect(&vis, &VisibilityCatcher::shown, this, &Conversation::onScrollResize);
     connect(&vis, &VisibilityCatcher::hidden, this, &Conversation::onScrollResize);
     connect(m_ui->sendButton, &QPushButton::clicked, this, &Conversation::onEnterPressed);
@@ -69,6 +71,7 @@ Conversation::Conversation(bool muc, const QString& mJid, const QString mRes, co
     connect(line, &MessageLine::uploadFile, this, qOverload<const Shared::Message&, const QString&>(&Conversation::sendMessage));
     connect(line, &MessageLine::requestLocalFile, this, &Conversation::requestLocalFile);
     connect(m_ui->attachButton, &QPushButton::clicked, this, &Conversation::onAttach);
+    connect(m_ui->clearButton, &QPushButton::clicked, this, &Conversation::onClearButton);
     
     m_ui->messageEditor->installEventFilter(&ker);
     
@@ -78,7 +81,8 @@ Conversation::Conversation(bool muc, const QString& mJid, const QString mRes, co
     vs->setBackgroundRole(QPalette::Base);
     vs->setAutoFillBackground(true);
     connect(vs, &QScrollBar::valueChanged, this, &Conversation::onSliderValueChanged);
-    m_ui->scrollArea->installEventFilter(&res);
+    m_ui->scrollArea->installEventFilter(&scrollResizeCatcher);
+    m_ui->filesPanel->installEventFilter(&attachResizeCatcher);
     
     applyVisualEffects();
 }
@@ -181,6 +185,10 @@ void Conversation::onEnterPressed()
 {
     QString body(m_ui->messageEditor->toPlainText());
     
+    if (body.size() > 0) {
+        m_ui->messageEditor->clear();
+        handleSendMessage(body);
+    }
     if (filesToAttach.size() > 0) {
         for (Badge* badge : filesToAttach) {
             Shared::Message msg;
@@ -196,17 +204,9 @@ void Conversation::onEnterPressed()
             msg.setOutgoing(true);
             msg.generateRandomId();
             msg.setCurrentTime();
-            if (body.size() > 0) {
-                msg.setBody(body);
-            }
             line->appendMessageWithUpload(msg, badge->id);
         }
         clearAttachedFiles();
-    } else {
-        if (body.size() > 0) {
-            m_ui->messageEditor->clear();
-            handleSendMessage(body);
-        }
     }
 }
 
@@ -376,6 +376,30 @@ void Conversation::clearAttachedFiles()
     filesLayout->setContentsMargins(0, 0, 0, 0);
 }
 
+void Conversation::onClearButton()
+{
+    clearAttachedFiles();
+    m_ui->messageEditor->clear();
+}
+
+void Conversation::onAttachResize(const QSize& oldSize, const QSize& newSize)
+{
+    int oh = oldSize.height();
+    int nh = newSize.height();
+    
+    int d = oh - nh;
+    
+    if (d != 0) {
+        QList<int> cs = m_ui->splitter->sizes();
+        cs.first() += d;
+        cs.last() -=d;
+        
+        m_ui->splitter->setSizes(cs);
+        m_ui->scrollArea->verticalScrollBar()->setValue(m_ui->scrollArea->verticalScrollBar()->maximum());
+    }
+}
+
+
 bool VisibilityCatcher::eventFilter(QObject* obj, QEvent* event)
 {
     if (event->type() == QEvent::Show) {
diff --git a/ui/widgets/conversation.h b/ui/widgets/conversation.h
index 386e20a..cf9585b 100644
--- a/ui/widgets/conversation.h
+++ b/ui/widgets/conversation.h
@@ -102,7 +102,9 @@ protected slots:
     void onAttach();
     void onFileSelected();
     void onScrollResize();
+    void onAttachResize(const QSize& oldSize, const QSize& newSize);
     void onBadgeClose();
+    void onClearButton();
     
 public:
     const bool isMuc;
@@ -121,7 +123,8 @@ protected:
     MessageLine* line;
     QScopedPointer<Ui::Conversation> m_ui;
     KeyEnterReceiver ker;
-    Resizer res;
+    Resizer scrollResizeCatcher;
+    Resizer attachResizeCatcher;
     VisibilityCatcher vis;
     QString thread;
     QLabel* statusIcon;

From ae3a1c97e3605d9b4638d2d80ef641482f446b04 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Fri, 15 Nov 2019 16:30:29 +0300
Subject: [PATCH 027/281] uploading message destruction bug, optimisations for
 release warnings for debug, packaging, readme

---
 CMakeLists.txt               |  9 +++++++
 README.md                    | 46 +++++++++++++++++++++++++++++-------
 main.cpp                     |  2 +-
 packaging/Archlinux/PKGBUILD |  8 +++----
 ui/utils/message.cpp         |  1 +
 ui/utils/messageline.cpp     | 45 +++++++++++++++++++----------------
 ui/utils/messageline.h       |  4 ++--
 7 files changed, 80 insertions(+), 35 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index f39d0e5..59582a1 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -14,6 +14,15 @@ include_directories(.)
 find_package(Qt5Widgets CONFIG REQUIRED)
 find_package(Qt5LinguistTools)
 
+if(NOT CMAKE_BUILD_TYPE)
+  set(CMAKE_BUILD_TYPE Debug)
+endif()
+
+set(CMAKE_CXX_FLAGS_DEBUG "-g -Wall -Wextra")
+set(CMAKE_CXX_FLAGS_RELEASE "-O3")
+message("Build type: ${CMAKE_BUILD_TYPE}")
+
+
 set(squawk_SRC
   main.cpp
   global.cpp
diff --git a/README.md b/README.md
index 2f7beca..1473ddc 100644
--- a/README.md
+++ b/README.md
@@ -8,22 +8,52 @@ A compact XMPP desktop messenger
 - uuid _(usually included in some other package, for example it's ***libutil-linux*** in archlinux)_
 - lmdb
 - CMake 3.0 or higher
+- qxmpp 1.1.0 or higher
+
+### Getting
+
+The easiest way to get the Squawk is to install it from AUR (if you use Archlinux like distribution)
+
+Here is the [link](https://aur.archlinux.org/packages/squawk/) for the AUR package
+
+You can also install it from console if you use some AUR wrapper. Here what it's going to look like with *pacaur*
+
+```
+$ pacaur -S squawk
+```
 
 ### Building
 
+You can also clone the repo and build it from source
+
 Squawk requires Qt with SSL enabled. It uses CMake as build system.
 
-Squawk uses upstream version of QXmpp library so first we need to pull it
+There are two ways to build, it depends whether you have qxmpp installed in your system
+
+#### Building with system qxmpp
+
+Here is what you do
+
 ```
-git submodule update --init --recursive
+$ git clone https://git.macaw.me/blue/squawk
+$ cd squawk
+$ mkdir build
+$ cd build
+$ cmake ..
+$ cmake --build .
 ```
-Then create a folder for the build, go there and build the project using CMake
- 
+
+#### Building with bundled qxmpp
+
+Here is what you do
+
 ```
-mkdir build
-cd build
-cmake ..
-cmake --build .
+$ git clone --recurse-submodules https://git.macaw.me/blue/squawk
+$ cd squawk
+$ mkdir build
+$ cd build
+$ cmake .. -D SYSTEM_QXMPP=False
+$ cmake --build .
 ```
 
 ## License
diff --git a/main.cpp b/main.cpp
index cc5e895..ce23acc 100644
--- a/main.cpp
+++ b/main.cpp
@@ -39,7 +39,7 @@ int main(int argc, char *argv[])
     
     QApplication::setApplicationName("squawk");
     QApplication::setApplicationDisplayName("Squawk");
-    QApplication::setApplicationVersion("0.0.5");
+    QApplication::setApplicationVersion("0.1.1");
     
     QTranslator qtTranslator;
     qtTranslator.load("qt_" + QLocale::system().name(), QLibraryInfo::location(QLibraryInfo::TranslationsPath));
diff --git a/packaging/Archlinux/PKGBUILD b/packaging/Archlinux/PKGBUILD
index bd979b5..aec6d05 100644
--- a/packaging/Archlinux/PKGBUILD
+++ b/packaging/Archlinux/PKGBUILD
@@ -1,18 +1,18 @@
 # Maintainer: Yury Gubich <blue@macaw.me>
 pkgname=squawk
-pkgver=0.0.5
+pkgver=0.1.1
 pkgrel=1
 pkgdesc="An XMPP desktop messenger, written on qt"
 arch=('i686' 'x86_64')
 url="https://git.macaw.me/blue/squawk"
 license=('GPL3')
-depends=('hicolor-icon-theme' 'desktop-file-utils' 'lmdb' 'qxmpp>=1.0.0')
+depends=('hicolor-icon-theme' 'desktop-file-utils' 'lmdb' 'qxmpp>=1.1.0')
 makedepends=('cmake>=3.3' 'imagemagick' 'qt5-tools')
 source=("$pkgname-$pkgver.tar.gz")
-sha256sums=('12bfc517574387257a82143d8970ec0d8d434ccd32f7ac400355ed5fa18192ab')
+sha256sums=('d0448f2fdb321e31a40c08b77adc951bafe8d1c271f70d6ffc80fb17cef670cf')
 build() {
         cd "$srcdir/squawk"
-        cmake . -D CMAKE_INSTALL_PREFIX=/usr 
+        cmake . -D CMAKE_INSTALL_PREFIX=/usr -D CMAKE_BUILD_TYPE=Release
         cmake --build . -j $nproc
 }
 package() {
diff --git a/ui/utils/message.cpp b/ui/utils/message.cpp
index db517e9..eb4b608 100644
--- a/ui/utils/message.cpp
+++ b/ui/utils/message.cpp
@@ -92,6 +92,7 @@ Message::~Message()
     if (!commentAdded) {
         delete fileComment;
     }
+    delete body;
 }
 
 QString Message::getId() const
diff --git a/ui/utils/messageline.cpp b/ui/utils/messageline.cpp
index dc3eeda..befef70 100644
--- a/ui/utils/messageline.cpp
+++ b/ui/utils/messageline.cpp
@@ -47,7 +47,7 @@ MessageLine::~MessageLine()
     }
 }
 
-MessageLine::Position MessageLine::message(const Shared::Message& msg)
+MessageLine::Position MessageLine::message(const Shared::Message& msg, bool forceOutgoing)
 {
     QString id = msg.getId();
     Index::iterator itr = messageIndex.find(id);
@@ -59,27 +59,32 @@ MessageLine::Position MessageLine::message(const Shared::Message& msg)
     QString sender;
     bool outgoing;
     
-    if (room) {
-        if (msg.getFromResource() == myName) {
-            sender = myName;
-            outgoing = true;
-        } else {
-            sender = msg.getFromResource();
-            outgoing = false;
-        }
+    if (forceOutgoing) {
+        sender = myName;
+        outgoing = true;
     } else {
-        if (msg.getOutgoing()) {
-            sender = myName;
-            outgoing = true;
-        } else {
-            QString jid = msg.getFromJid();
-            std::map<QString, QString>::iterator itr = palNames.find(jid);
-            if (itr != palNames.end()) {
-                sender = itr->second;
+        if (room) {
+            if (msg.getFromResource() == myName) {
+                sender = myName;
+                outgoing = true;
             } else {
-                sender = jid;
+                sender = msg.getFromResource();
+                outgoing = false;
+            }
+        } else {
+            if (msg.getOutgoing()) {
+                sender = myName;
+                outgoing = true;
+            } else {
+                QString jid = msg.getFromJid();
+                std::map<QString, QString>::iterator itr = palNames.find(jid);
+                if (itr != palNames.end()) {
+                    sender = itr->second;
+                } else {
+                    sender = jid;
+                }
+                outgoing = false;
             }
-            outgoing = false;
         }
     }
     
@@ -328,7 +333,7 @@ void MessageLine::fileError(const QString& messageId, const QString& error)
 
 void MessageLine::appendMessageWithUpload(const Shared::Message& msg, const QString& path)
 {
-    message(msg);
+    message(msg, true);
     QString id = msg.getId();
     Message* ui = messageIndex.find(id)->second;
     connect(ui, &Message::buttonClicked, this, &MessageLine::onUpload);     //this is in case of retry;
diff --git a/ui/utils/messageline.h b/ui/utils/messageline.h
index 601604b..56f0a5e 100644
--- a/ui/utils/messageline.h
+++ b/ui/utils/messageline.h
@@ -43,7 +43,7 @@ public:
     MessageLine(bool p_room, QWidget* parent = 0);
     ~MessageLine();
     
-    Position message(const Shared::Message& msg);
+    Position message(const Shared::Message& msg, bool forceOutgoing = false);
     void setMyName(const QString& name);
     void setPalName(const QString& jid, const QString& name);
     QString firstMessageId() const;
@@ -52,7 +52,7 @@ public:
     void responseLocalFile(const QString& messageId, const QString& path);
     void fileError(const QString& messageId, const QString& error);
     void fileProgress(const QString& messageId, qreal progress);
-    void appendMessageWithUpload(const Shared::Message& message, const QString& path);
+    void appendMessageWithUpload(const Shared::Message& msg, const QString& path);
     void removeMessage(const QString& messageId);
     
 signals:

From 0cb25a25cf100822d044e33290ca32f4fa47049e Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Sun, 17 Nov 2019 13:24:12 +0300
Subject: [PATCH 028/281] uploading multiple messages fix, some warnings fix

---
 core/account.cpp                 | 10 +++-------
 core/archive.cpp                 | 24 ++++++++++--------------
 core/networkaccess.cpp           | 12 ++++++------
 core/rosteritem.cpp              | 13 +++++++------
 core/squawk.cpp                  |  2 +-
 ui/models/contact.cpp            |  2 +-
 ui/models/room.cpp               |  2 +-
 ui/models/roster.cpp             |  2 +-
 ui/utils/messageline.cpp         |  2 +-
 ui/widgets/conversation.cpp      |  2 ++
 ui/widgets/vcard/emailsmodel.cpp | 10 +++++-----
 ui/widgets/vcard/emailsmodel.h   |  8 ++++----
 ui/widgets/vcard/phonesmodel.cpp | 10 +++++-----
 ui/widgets/vcard/phonesmodel.h   |  8 ++++----
 14 files changed, 51 insertions(+), 56 deletions(-)

diff --git a/core/account.cpp b/core/account.cpp
index 492ebdc..6fb26a0 100644
--- a/core/account.cpp
+++ b/core/account.cpp
@@ -1103,6 +1103,8 @@ void Core::Account::onClientError(QXmppClient::Error err)
         case QXmppClient::KeepAliveError:
             errorText = "Client keep alive error";
             break;
+        case QXmppClient::NoError:
+            break;                      //not exactly sure what to do here
     }
     
     qDebug() << errorType << errorText;
@@ -1565,11 +1567,7 @@ void Core::Account::uploadVCard(const Shared::VCard& card)
     initializeQXmppVCard(iq, card);
     
     bool avatarChanged = false;
-    if (card.getAvatarType() == Shared::Avatar::empty) {
-        if (avatarType.size() > 0) {
-            avatarChanged = true;
-        }
-    } else {
+    if (card.getAvatarType() != Shared::Avatar::empty) {
         QString newPath = card.getAvatarPath();
         QString oldPath = getAvatarPath();
         QByteArray data;
@@ -1584,7 +1582,6 @@ void Core::Account::uploadVCard(const Shared::VCard& card)
                     QFile oA(oldPath);
                     if (!oA.open(QFile::ReadOnly)) {
                         qDebug() << "Couldn't read old avatar of account" << name << ", uploading empty avatar";
-                        avatarChanged = true;
                     } else {
                         data = oA.readAll();
                     }
@@ -1598,7 +1595,6 @@ void Core::Account::uploadVCard(const Shared::VCard& card)
                 QFile oA(oldPath);
                 if (!oA.open(QFile::ReadOnly)) {
                     qDebug() << "Couldn't read old avatar of account" << name << ", uploading empty avatar";
-                    avatarChanged = true;
                 } else {
                     data = oA.readAll();
                 }
diff --git a/core/archive.cpp b/core/archive.cpp
index 5900df2..9b7c9eb 100644
--- a/core/archive.cpp
+++ b/core/archive.cpp
@@ -74,18 +74,18 @@ void Core::Archive::open(const QString& account)
         mdb_txn_begin(environment, NULL, 0, &txn);
         try {
             fromTheBeginning = getStatBoolValue("beginning", txn);
-        } catch (NotFound e) {
+        } catch (const NotFound& e) {
             fromTheBeginning = false;
         }
         try {
             hasAvatar = getStatBoolValue("hasAvatar", txn);
-        } catch (NotFound e) {
+        } catch (const NotFound& e) {
             hasAvatar = false;
         }
         if (hasAvatar) {
             try {
                 avatarAutoGenerated = getStatBoolValue("avatarAutoGenerated", txn);
-            } catch (NotFound e) {
+            } catch (const NotFound& e) {
                 avatarAutoGenerated = false;
             }
             
@@ -346,8 +346,7 @@ long unsigned int Core::Archive::size() const
         throw Closed("size", jid.toStdString());
     }
     MDB_txn *txn;
-    int rc;
-    rc = mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn);
+    mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn);
     MDB_stat stat;
     mdb_stat(txn, order, &stat);
     mdb_txn_abort(txn);
@@ -473,13 +472,12 @@ void Core::Archive::printOrder()
 {
     qDebug() << "Printing order";
     MDB_txn *txn;
-    int rc;
-    rc = mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn);
+    mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn);
     MDB_cursor* cursor;
-    rc = mdb_cursor_open(txn, order, &cursor);
+    mdb_cursor_open(txn, order, &cursor);
     MDB_val lmdbKey, lmdbData;
     
-    rc = mdb_cursor_get(cursor, &lmdbKey, &lmdbData, MDB_FIRST);
+    mdb_cursor_get(cursor, &lmdbKey, &lmdbData, MDB_FIRST);
     
     do {
         std::string sId((char*)lmdbData.mv_data, lmdbData.mv_size);
@@ -493,13 +491,12 @@ void Core::Archive::printOrder()
 void Core::Archive::printKeys()
 {
     MDB_txn *txn;
-    int rc;
-    rc = mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn);
+    mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn);
     MDB_cursor* cursor;
-    rc = mdb_cursor_open(txn, main, &cursor);
+    mdb_cursor_open(txn, main, &cursor);
     MDB_val lmdbKey, lmdbData;
     
-    rc = mdb_cursor_get(cursor, &lmdbKey, &lmdbData, MDB_FIRST);
+    mdb_cursor_get(cursor, &lmdbKey, &lmdbData, MDB_FIRST);
     
     do {
         std::string sId((char*)lmdbKey.mv_data, lmdbKey.mv_size);
@@ -540,7 +537,6 @@ bool Core::Archive::getStatBoolValue(const std::string& id, MDB_txn* txn)
 
 std::string Core::Archive::getStatStringValue(const std::string& id, MDB_txn* txn)
 {
-    MDB_cursor* cursor;
     MDB_val lmdbKey, lmdbData;
     lmdbKey.mv_size = id.size();
     lmdbKey.mv_data = (char*)id.c_str();
diff --git a/core/networkaccess.cpp b/core/networkaccess.cpp
index 6e1689d..4ee4baa 100644
--- a/core/networkaccess.cpp
+++ b/core/networkaccess.cpp
@@ -53,9 +53,9 @@ void Core::NetworkAccess::fileLocalPathRequest(const QString& messageId, const Q
                 files.removeRecord(url);
                 emit fileLocalPathResponse(messageId, "");
             }
-        } catch (Archive::NotFound e) {
+        } catch (const Archive::NotFound& e) {
             emit fileLocalPathResponse(messageId, "");
-        } catch (Archive::Unknown e) {
+        } catch (const Archive::Unknown& e) {
             qDebug() << "Error requesting file path:" << e.what();
             emit fileLocalPathResponse(messageId, "");
         }
@@ -82,9 +82,9 @@ void Core::NetworkAccess::downladFileRequest(const QString& messageId, const QSt
                 files.removeRecord(url);
                 startDownload(messageId, url);
             }
-        } catch (Archive::NotFound e) {
+        } catch (const Archive::NotFound& e) {
             startDownload(messageId, url);
-        } catch (Archive::Unknown e) {
+        } catch (const Archive::Unknown& e) {
             qDebug() << "Error requesting file path:" << e.what();
             emit downloadFileError(messageId, QString("Database error: ") + e.what());
         }
@@ -454,9 +454,9 @@ void Core::NetworkAccess::uploadFileRequest(const QString& messageId, const QStr
                     startUpload(messageId, url, path);
                 }
             }
-        } catch (Archive::NotFound e) {
+        } catch (const Archive::NotFound& e) {
             startUpload(messageId, url, path);
-        } catch (Archive::Unknown e) {
+        } catch (const Archive::Unknown& e) {
             qDebug() << "Error requesting file path on upload:" << e.what();
             emit uploadFileError(messageId, QString("Database error: ") + e.what());
         }
diff --git a/core/rosteritem.cpp b/core/rosteritem.cpp
index f144901..ccc3072 100644
--- a/core/rosteritem.cpp
+++ b/core/rosteritem.cpp
@@ -141,11 +141,11 @@ void Core::RosterItem::performRequest(int count, const QString& before)
                     std::list<Shared::Message> arc = archive->getBefore(requestedCount - responseCache.size(), lBefore);
                     responseCache.insert(responseCache.begin(), arc.begin(), arc.end());
                     found = true;
-                } catch (Archive::NotFound e) {
+                } catch (const Archive::NotFound& e) {
                     requestCache.emplace_back(requestedCount, before);
                     requestedCount = -1;
                     emit needHistory(archive->oldestId(), "");
-                } catch (Archive::Empty e) {
+                } catch (const Archive::Empty& e) {
                     requestCache.emplace_back(requestedCount, before);
                     requestedCount = -1;
                     emit needHistory(archive->oldestId(), "");
@@ -171,9 +171,9 @@ void Core::RosterItem::performRequest(int count, const QString& before)
             try {
                 std::list<Shared::Message> arc = archive->getBefore(requestedCount - responseCache.size(), before);
                 responseCache.insert(responseCache.begin(), arc.begin(), arc.end());
-            } catch (Archive::NotFound e) {
+            } catch (const Archive::NotFound& e) {
                 qDebug("requesting id hasn't been found in archive, skipping");
-            } catch (Archive::Empty e) {
+            } catch (const Archive::Empty& e) {
                 qDebug("requesting id hasn't been found in archive, skipping");
             }
             nextRequest();
@@ -254,6 +254,7 @@ void Core::RosterItem::flushMessagesToArchive(bool finished, const QString& firs
         case empty:
             wasEmpty = true;
             archiveState = end;
+            [[fallthrough]];
         case end:
             added += archive->addElements(appendCache);
             appendCache.clear();
@@ -274,9 +275,9 @@ void Core::RosterItem::flushMessagesToArchive(bool finished, const QString& firs
                     std::list<Shared::Message> arc = archive->getBefore(requestedCount - responseCache.size(), before);
                     responseCache.insert(responseCache.begin(), arc.begin(), arc.end());
                     found = true;
-                } catch (Archive::NotFound e) {
+                } catch (const Archive::NotFound& e) {
                     
-                } catch (Archive::Empty e) {
+                } catch (const Archive::Empty& e) {
                     
                 }
                 if (!found || requestedCount > responseCache.size()) {
diff --git a/core/squawk.cpp b/core/squawk.cpp
index 7ca41e3..3ccb224 100644
--- a/core/squawk.cpp
+++ b/core/squawk.cpp
@@ -51,7 +51,7 @@ void Core::Squawk::stop()
     QSettings settings;
     settings.beginGroup("core");
     settings.beginWriteArray("accounts");
-    for (int i = 0; i < accounts.size(); ++i) {
+    for (std::deque<Account*>::size_type i = 0; i < accounts.size(); ++i) {
         settings.setArrayIndex(i);
         Account* acc = accounts[i];
         settings.setValue("name", acc->getName());
diff --git a/ui/models/contact.cpp b/ui/models/contact.cpp
index eee6b4e..cde8941 100644
--- a/ui/models/contact.cpp
+++ b/ui/models/contact.cpp
@@ -322,7 +322,7 @@ void Models::Contact::getMessages(Models::Contact::Messages& container) const
 void Models::Contact::toOfflineState()
 {
     emit childIsAboutToBeRemoved(this, 0, childItems.size());
-    for (int i = 0; i < childItems.size(); ++i) {
+    for (std::deque<Item*>::size_type i = 0; i < childItems.size(); ++i) {
         Item* item = childItems[i];
         disconnect(item, &Item::childChanged, this, &Contact::refresh);
         Item::_removeChild(i);
diff --git a/ui/models/room.cpp b/ui/models/room.cpp
index 204b2b5..a251f4d 100644
--- a/ui/models/room.cpp
+++ b/ui/models/room.cpp
@@ -236,7 +236,7 @@ void Models::Room::getMessages(Models::Room::Messages& container) const
 void Models::Room::toOfflineState()
 {
     emit childIsAboutToBeRemoved(this, 0, childItems.size());
-    for (int i = 0; i < childItems.size(); ++i) {
+    for (std::deque<Item*>::size_type i = 0; i < childItems.size(); ++i) {
         Item* item = childItems[i];
         Item::_removeChild(i);
         item->deleteLater();
diff --git a/ui/models/roster.cpp b/ui/models/roster.cpp
index e124db7..2f226f2 100644
--- a/ui/models/roster.cpp
+++ b/ui/models/roster.cpp
@@ -518,7 +518,7 @@ void Models::Roster::removeGroup(const QString& account, const QString& name)
     
     if (toInsert.size() > 0) {
         Account* acc = accounts.find("account")->second;
-        for (int i = 0; i < toInsert.size(); ++i) {
+        for (std::deque<Contact*>::size_type i = 0; i < toInsert.size(); ++i) {
             Contact* cont = toInsert[i];
             acc->appendChild(cont);             //TODO optimisation
         }
diff --git a/ui/utils/messageline.cpp b/ui/utils/messageline.cpp
index befef70..6f2cb8d 100644
--- a/ui/utils/messageline.cpp
+++ b/ui/utils/messageline.cpp
@@ -111,7 +111,7 @@ MessageLine::Position MessageLine::message(const Shared::Message& msg, bool forc
         }
     }
     messageIndex.insert(std::make_pair(id, message));
-    int index = std::distance<Order::const_iterator>(messageOrder.begin(), result.first);   //need to make with binary indexed tree
+    unsigned long index = std::distance<Order::const_iterator>(messageOrder.begin(), result.first);   //need to make with binary indexed tree
     Position res = invalid;
     if (index == 0) {
         res = beggining;
diff --git a/ui/widgets/conversation.cpp b/ui/widgets/conversation.cpp
index 07a160f..d9e1039 100644
--- a/ui/widgets/conversation.cpp
+++ b/ui/widgets/conversation.cpp
@@ -24,6 +24,7 @@
 #include <QGraphicsDropShadowEffect>
 #include <QFileDialog>
 #include <QMimeDatabase>
+#include <unistd.h>
 
 Conversation::Conversation(bool muc, const QString& mJid, const QString mRes, const QString pJid, const QString pRes, const QString& acc, QWidget* parent):
     QWidget(parent),
@@ -205,6 +206,7 @@ void Conversation::onEnterPressed()
             msg.generateRandomId();
             msg.setCurrentTime();
             line->appendMessageWithUpload(msg, badge->id);
+            usleep(1000);       //this is required for the messages not to have equal time when appending into messageline
         }
         clearAttachedFiles();
     }
diff --git a/ui/widgets/vcard/emailsmodel.cpp b/ui/widgets/vcard/emailsmodel.cpp
index 4044322..9723672 100644
--- a/ui/widgets/vcard/emailsmodel.cpp
+++ b/ui/widgets/vcard/emailsmodel.cpp
@@ -145,7 +145,7 @@ QModelIndex UI::VCard::EMailsModel::addNewEmptyLine()
     return createIndex(deque.size() - 1, 0, &(deque.back()));
 }
 
-bool UI::VCard::EMailsModel::isPreferred(int row) const
+bool UI::VCard::EMailsModel::isPreferred(quint32 row) const
 {
     if (row < deque.size()) {
         return deque[row].prefered;
@@ -154,10 +154,10 @@ bool UI::VCard::EMailsModel::isPreferred(int row) const
     }
 }
 
-void UI::VCard::EMailsModel::removeLines(int index, int count)
+void UI::VCard::EMailsModel::removeLines(quint32 index, quint32 count)
 {
     if (index < deque.size()) {
-        int maxCount = deque.size() - index;
+        quint32 maxCount = deque.size() - index;
         if (count > maxCount) {
             count = maxCount;
         }
@@ -194,12 +194,12 @@ void UI::VCard::EMailsModel::setEmails(const std::deque<Shared::VCard::Email>& e
     }
 }
 
-void UI::VCard::EMailsModel::revertPreferred(int row)
+void UI::VCard::EMailsModel::revertPreferred(quint32 row)
 {
     setData(createIndex(row, 2), !isPreferred(row));
 }
 
-QString UI::VCard::EMailsModel::getEmail(int row) const
+QString UI::VCard::EMailsModel::getEmail(quint32 row) const
 {
     return deque[row].address;
 }
diff --git a/ui/widgets/vcard/emailsmodel.h b/ui/widgets/vcard/emailsmodel.h
index 358536f..3ee3a02 100644
--- a/ui/widgets/vcard/emailsmodel.h
+++ b/ui/widgets/vcard/emailsmodel.h
@@ -40,16 +40,16 @@ public:
     QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
     bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
     Qt::ItemFlags flags(const QModelIndex &index) const override;
-    bool isPreferred(int row) const;
+    bool isPreferred(quint32 row) const;
     
-    void removeLines(int index, int count);
+    void removeLines(quint32 index, quint32 count);
     void setEmails(const std::deque<Shared::VCard::Email>& emails);
     void getEmails(std::deque<Shared::VCard::Email>& emails) const;
-    QString getEmail(int row) const;
+    QString getEmail(quint32 row) const;
     
 public slots:
     QModelIndex addNewEmptyLine();
-    void revertPreferred(int row);
+    void revertPreferred(quint32 row);
     
 private:
     bool edit;
diff --git a/ui/widgets/vcard/phonesmodel.cpp b/ui/widgets/vcard/phonesmodel.cpp
index df9cad6..ee0ff75 100644
--- a/ui/widgets/vcard/phonesmodel.cpp
+++ b/ui/widgets/vcard/phonesmodel.cpp
@@ -129,7 +129,7 @@ void UI::VCard::PhonesModel::getPhones(std::deque<Shared::VCard::Phone>& phones)
     }
 }
 
-bool UI::VCard::PhonesModel::isPreferred(int row) const
+bool UI::VCard::PhonesModel::isPreferred(quint32 row) const
 {
     if (row < deque.size()) {
         return deque[row].prefered;
@@ -138,10 +138,10 @@ bool UI::VCard::PhonesModel::isPreferred(int row) const
     }
 }
 
-void UI::VCard::PhonesModel::removeLines(int index, int count)
+void UI::VCard::PhonesModel::removeLines(quint32 index, quint32 count)
 {
     if (index < deque.size()) {
-        int maxCount = deque.size() - index;
+        quint32 maxCount = deque.size() - index;
         if (count > maxCount) {
             count = maxCount;
         }
@@ -156,7 +156,7 @@ void UI::VCard::PhonesModel::removeLines(int index, int count)
     }
 }
 
-void UI::VCard::PhonesModel::revertPreferred(int row)
+void UI::VCard::PhonesModel::revertPreferred(quint32 row)
 {
     setData(createIndex(row, 3), !isPreferred(row));
 }
@@ -216,7 +216,7 @@ void UI::VCard::PhonesModel::setPhones(const std::deque<Shared::VCard::Phone>& p
     }
 }
 
-QString UI::VCard::PhonesModel::getPhone(int row) const
+QString UI::VCard::PhonesModel::getPhone(quint32 row) const
 {
     return deque[row].number;
 }
diff --git a/ui/widgets/vcard/phonesmodel.h b/ui/widgets/vcard/phonesmodel.h
index bf847d2..f799f82 100644
--- a/ui/widgets/vcard/phonesmodel.h
+++ b/ui/widgets/vcard/phonesmodel.h
@@ -41,16 +41,16 @@ public:
     int rowCount(const QModelIndex& parent) const override;
     bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
     Qt::ItemFlags flags(const QModelIndex &index) const override;
-    bool isPreferred(int row) const;
+    bool isPreferred(quint32 row) const;
     
-    void removeLines(int index, int count);
+    void removeLines(quint32 index, quint32 count);
     void setPhones(const std::deque<Shared::VCard::Phone>& phones);
     void getPhones(std::deque<Shared::VCard::Phone>& phones) const;
-    QString getPhone(int row) const;
+    QString getPhone(quint32 row) const;
     
 public slots:
     QModelIndex addNewEmptyLine();
-    void revertPreferred(int row);
+    void revertPreferred(quint32 row);
     
 private:
     bool edit;

From 0c33d81c597ddfd43d6fd9b7b5abc70910ee2696 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Fri, 6 Dec 2019 11:00:09 +0300
Subject: [PATCH 029/281] insignifican fixes about urls parsing

---
 core/networkaccess.cpp | 2 +-
 ui/utils/message.cpp   | 6 ++++--
 2 files changed, 5 insertions(+), 3 deletions(-)

diff --git a/core/networkaccess.cpp b/core/networkaccess.cpp
index 6e1689d..2973a6c 100644
--- a/core/networkaccess.cpp
+++ b/core/networkaccess.cpp
@@ -289,7 +289,7 @@ void Core::NetworkAccess::onDownloadFinished()
             QStringList hops = url.split("/");
             QString fileName = hops.back();
             QStringList parts = fileName.split(".");
-            path = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation);
+            path = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation) + "/";
             QString suffix("");
             QString realName = parts.front();
             for (QStringList::const_iterator sItr = (parts.begin()++), sEnd = parts.end(); sItr != sEnd; ++sItr) {
diff --git a/ui/utils/message.cpp b/ui/utils/message.cpp
index eb4b608..1dc35b7 100644
--- a/ui/utils/message.cpp
+++ b/ui/utils/message.cpp
@@ -20,10 +20,11 @@
 #include <QMimeDatabase>
 #include <QPixmap>
 #include <QFileInfo>
+#include <QRegularExpression>
 #include "message.h"
 
-const QRegExp urlReg("(?!<img\\ssrc=\")((?:https?|ftp)://\\S+)");
-const QRegExp imgReg("((?:https?|ftp)://\\S+\\.(?:jpg|jpeg|png|svg|gif))");
+const QRegularExpression urlReg("(?<!<a\\shref=['\"])(?<!<img\\ssrc=['\"])((?:https?|ftp)://\\S+)");       //finds all hypertext references which are not wrapped in a or img tags
+const QRegularExpression imgReg("((?:https?|ftp)://\\S+\\.(?:jpg|jpeg|png|svg|gif))");
 
 Message::Message(const Shared::Message& source, bool outgoing, const QString& p_sender, QWidget* parent):
     QHBoxLayout(parent),
@@ -49,6 +50,7 @@ Message::Message(const Shared::Message& source, bool outgoing, const QString& p_
     QString bd = msg.getBody();
     //bd.replace(imgReg, "<img src=\"\\1\"/>");
     bd.replace(urlReg, "<a href=\"\\1\">\\1</a>");
+    text->setTextFormat(Qt::RichText);
     text->setText(bd);;
     text->setTextInteractionFlags(text->textInteractionFlags() | Qt::TextSelectableByMouse | Qt::LinksAccessibleByMouse);
     text->setWordWrap(true);

From fe279556894a5c68bf2a92871e66552e8b3fea53 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Sun, 17 Nov 2019 13:24:12 +0300
Subject: [PATCH 030/281] uploading multiple messages fix, some warnings fix

---
 core/account.cpp                 | 10 +++-------
 core/archive.cpp                 | 24 ++++++++++--------------
 core/networkaccess.cpp           | 12 ++++++------
 core/rosteritem.cpp              | 13 +++++++------
 core/squawk.cpp                  |  2 +-
 ui/models/contact.cpp            |  2 +-
 ui/models/room.cpp               |  2 +-
 ui/models/roster.cpp             |  2 +-
 ui/utils/messageline.cpp         |  2 +-
 ui/widgets/conversation.cpp      |  2 ++
 ui/widgets/vcard/emailsmodel.cpp | 10 +++++-----
 ui/widgets/vcard/emailsmodel.h   |  8 ++++----
 ui/widgets/vcard/phonesmodel.cpp | 10 +++++-----
 ui/widgets/vcard/phonesmodel.h   |  8 ++++----
 14 files changed, 51 insertions(+), 56 deletions(-)

diff --git a/core/account.cpp b/core/account.cpp
index 492ebdc..6fb26a0 100644
--- a/core/account.cpp
+++ b/core/account.cpp
@@ -1103,6 +1103,8 @@ void Core::Account::onClientError(QXmppClient::Error err)
         case QXmppClient::KeepAliveError:
             errorText = "Client keep alive error";
             break;
+        case QXmppClient::NoError:
+            break;                      //not exactly sure what to do here
     }
     
     qDebug() << errorType << errorText;
@@ -1565,11 +1567,7 @@ void Core::Account::uploadVCard(const Shared::VCard& card)
     initializeQXmppVCard(iq, card);
     
     bool avatarChanged = false;
-    if (card.getAvatarType() == Shared::Avatar::empty) {
-        if (avatarType.size() > 0) {
-            avatarChanged = true;
-        }
-    } else {
+    if (card.getAvatarType() != Shared::Avatar::empty) {
         QString newPath = card.getAvatarPath();
         QString oldPath = getAvatarPath();
         QByteArray data;
@@ -1584,7 +1582,6 @@ void Core::Account::uploadVCard(const Shared::VCard& card)
                     QFile oA(oldPath);
                     if (!oA.open(QFile::ReadOnly)) {
                         qDebug() << "Couldn't read old avatar of account" << name << ", uploading empty avatar";
-                        avatarChanged = true;
                     } else {
                         data = oA.readAll();
                     }
@@ -1598,7 +1595,6 @@ void Core::Account::uploadVCard(const Shared::VCard& card)
                 QFile oA(oldPath);
                 if (!oA.open(QFile::ReadOnly)) {
                     qDebug() << "Couldn't read old avatar of account" << name << ", uploading empty avatar";
-                    avatarChanged = true;
                 } else {
                     data = oA.readAll();
                 }
diff --git a/core/archive.cpp b/core/archive.cpp
index 5900df2..9b7c9eb 100644
--- a/core/archive.cpp
+++ b/core/archive.cpp
@@ -74,18 +74,18 @@ void Core::Archive::open(const QString& account)
         mdb_txn_begin(environment, NULL, 0, &txn);
         try {
             fromTheBeginning = getStatBoolValue("beginning", txn);
-        } catch (NotFound e) {
+        } catch (const NotFound& e) {
             fromTheBeginning = false;
         }
         try {
             hasAvatar = getStatBoolValue("hasAvatar", txn);
-        } catch (NotFound e) {
+        } catch (const NotFound& e) {
             hasAvatar = false;
         }
         if (hasAvatar) {
             try {
                 avatarAutoGenerated = getStatBoolValue("avatarAutoGenerated", txn);
-            } catch (NotFound e) {
+            } catch (const NotFound& e) {
                 avatarAutoGenerated = false;
             }
             
@@ -346,8 +346,7 @@ long unsigned int Core::Archive::size() const
         throw Closed("size", jid.toStdString());
     }
     MDB_txn *txn;
-    int rc;
-    rc = mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn);
+    mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn);
     MDB_stat stat;
     mdb_stat(txn, order, &stat);
     mdb_txn_abort(txn);
@@ -473,13 +472,12 @@ void Core::Archive::printOrder()
 {
     qDebug() << "Printing order";
     MDB_txn *txn;
-    int rc;
-    rc = mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn);
+    mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn);
     MDB_cursor* cursor;
-    rc = mdb_cursor_open(txn, order, &cursor);
+    mdb_cursor_open(txn, order, &cursor);
     MDB_val lmdbKey, lmdbData;
     
-    rc = mdb_cursor_get(cursor, &lmdbKey, &lmdbData, MDB_FIRST);
+    mdb_cursor_get(cursor, &lmdbKey, &lmdbData, MDB_FIRST);
     
     do {
         std::string sId((char*)lmdbData.mv_data, lmdbData.mv_size);
@@ -493,13 +491,12 @@ void Core::Archive::printOrder()
 void Core::Archive::printKeys()
 {
     MDB_txn *txn;
-    int rc;
-    rc = mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn);
+    mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn);
     MDB_cursor* cursor;
-    rc = mdb_cursor_open(txn, main, &cursor);
+    mdb_cursor_open(txn, main, &cursor);
     MDB_val lmdbKey, lmdbData;
     
-    rc = mdb_cursor_get(cursor, &lmdbKey, &lmdbData, MDB_FIRST);
+    mdb_cursor_get(cursor, &lmdbKey, &lmdbData, MDB_FIRST);
     
     do {
         std::string sId((char*)lmdbKey.mv_data, lmdbKey.mv_size);
@@ -540,7 +537,6 @@ bool Core::Archive::getStatBoolValue(const std::string& id, MDB_txn* txn)
 
 std::string Core::Archive::getStatStringValue(const std::string& id, MDB_txn* txn)
 {
-    MDB_cursor* cursor;
     MDB_val lmdbKey, lmdbData;
     lmdbKey.mv_size = id.size();
     lmdbKey.mv_data = (char*)id.c_str();
diff --git a/core/networkaccess.cpp b/core/networkaccess.cpp
index 2973a6c..a27ab8f 100644
--- a/core/networkaccess.cpp
+++ b/core/networkaccess.cpp
@@ -53,9 +53,9 @@ void Core::NetworkAccess::fileLocalPathRequest(const QString& messageId, const Q
                 files.removeRecord(url);
                 emit fileLocalPathResponse(messageId, "");
             }
-        } catch (Archive::NotFound e) {
+        } catch (const Archive::NotFound& e) {
             emit fileLocalPathResponse(messageId, "");
-        } catch (Archive::Unknown e) {
+        } catch (const Archive::Unknown& e) {
             qDebug() << "Error requesting file path:" << e.what();
             emit fileLocalPathResponse(messageId, "");
         }
@@ -82,9 +82,9 @@ void Core::NetworkAccess::downladFileRequest(const QString& messageId, const QSt
                 files.removeRecord(url);
                 startDownload(messageId, url);
             }
-        } catch (Archive::NotFound e) {
+        } catch (const Archive::NotFound& e) {
             startDownload(messageId, url);
-        } catch (Archive::Unknown e) {
+        } catch (const Archive::Unknown& e) {
             qDebug() << "Error requesting file path:" << e.what();
             emit downloadFileError(messageId, QString("Database error: ") + e.what());
         }
@@ -454,9 +454,9 @@ void Core::NetworkAccess::uploadFileRequest(const QString& messageId, const QStr
                     startUpload(messageId, url, path);
                 }
             }
-        } catch (Archive::NotFound e) {
+        } catch (const Archive::NotFound& e) {
             startUpload(messageId, url, path);
-        } catch (Archive::Unknown e) {
+        } catch (const Archive::Unknown& e) {
             qDebug() << "Error requesting file path on upload:" << e.what();
             emit uploadFileError(messageId, QString("Database error: ") + e.what());
         }
diff --git a/core/rosteritem.cpp b/core/rosteritem.cpp
index f144901..ccc3072 100644
--- a/core/rosteritem.cpp
+++ b/core/rosteritem.cpp
@@ -141,11 +141,11 @@ void Core::RosterItem::performRequest(int count, const QString& before)
                     std::list<Shared::Message> arc = archive->getBefore(requestedCount - responseCache.size(), lBefore);
                     responseCache.insert(responseCache.begin(), arc.begin(), arc.end());
                     found = true;
-                } catch (Archive::NotFound e) {
+                } catch (const Archive::NotFound& e) {
                     requestCache.emplace_back(requestedCount, before);
                     requestedCount = -1;
                     emit needHistory(archive->oldestId(), "");
-                } catch (Archive::Empty e) {
+                } catch (const Archive::Empty& e) {
                     requestCache.emplace_back(requestedCount, before);
                     requestedCount = -1;
                     emit needHistory(archive->oldestId(), "");
@@ -171,9 +171,9 @@ void Core::RosterItem::performRequest(int count, const QString& before)
             try {
                 std::list<Shared::Message> arc = archive->getBefore(requestedCount - responseCache.size(), before);
                 responseCache.insert(responseCache.begin(), arc.begin(), arc.end());
-            } catch (Archive::NotFound e) {
+            } catch (const Archive::NotFound& e) {
                 qDebug("requesting id hasn't been found in archive, skipping");
-            } catch (Archive::Empty e) {
+            } catch (const Archive::Empty& e) {
                 qDebug("requesting id hasn't been found in archive, skipping");
             }
             nextRequest();
@@ -254,6 +254,7 @@ void Core::RosterItem::flushMessagesToArchive(bool finished, const QString& firs
         case empty:
             wasEmpty = true;
             archiveState = end;
+            [[fallthrough]];
         case end:
             added += archive->addElements(appendCache);
             appendCache.clear();
@@ -274,9 +275,9 @@ void Core::RosterItem::flushMessagesToArchive(bool finished, const QString& firs
                     std::list<Shared::Message> arc = archive->getBefore(requestedCount - responseCache.size(), before);
                     responseCache.insert(responseCache.begin(), arc.begin(), arc.end());
                     found = true;
-                } catch (Archive::NotFound e) {
+                } catch (const Archive::NotFound& e) {
                     
-                } catch (Archive::Empty e) {
+                } catch (const Archive::Empty& e) {
                     
                 }
                 if (!found || requestedCount > responseCache.size()) {
diff --git a/core/squawk.cpp b/core/squawk.cpp
index 7ca41e3..3ccb224 100644
--- a/core/squawk.cpp
+++ b/core/squawk.cpp
@@ -51,7 +51,7 @@ void Core::Squawk::stop()
     QSettings settings;
     settings.beginGroup("core");
     settings.beginWriteArray("accounts");
-    for (int i = 0; i < accounts.size(); ++i) {
+    for (std::deque<Account*>::size_type i = 0; i < accounts.size(); ++i) {
         settings.setArrayIndex(i);
         Account* acc = accounts[i];
         settings.setValue("name", acc->getName());
diff --git a/ui/models/contact.cpp b/ui/models/contact.cpp
index eee6b4e..cde8941 100644
--- a/ui/models/contact.cpp
+++ b/ui/models/contact.cpp
@@ -322,7 +322,7 @@ void Models::Contact::getMessages(Models::Contact::Messages& container) const
 void Models::Contact::toOfflineState()
 {
     emit childIsAboutToBeRemoved(this, 0, childItems.size());
-    for (int i = 0; i < childItems.size(); ++i) {
+    for (std::deque<Item*>::size_type i = 0; i < childItems.size(); ++i) {
         Item* item = childItems[i];
         disconnect(item, &Item::childChanged, this, &Contact::refresh);
         Item::_removeChild(i);
diff --git a/ui/models/room.cpp b/ui/models/room.cpp
index 204b2b5..a251f4d 100644
--- a/ui/models/room.cpp
+++ b/ui/models/room.cpp
@@ -236,7 +236,7 @@ void Models::Room::getMessages(Models::Room::Messages& container) const
 void Models::Room::toOfflineState()
 {
     emit childIsAboutToBeRemoved(this, 0, childItems.size());
-    for (int i = 0; i < childItems.size(); ++i) {
+    for (std::deque<Item*>::size_type i = 0; i < childItems.size(); ++i) {
         Item* item = childItems[i];
         Item::_removeChild(i);
         item->deleteLater();
diff --git a/ui/models/roster.cpp b/ui/models/roster.cpp
index e124db7..2f226f2 100644
--- a/ui/models/roster.cpp
+++ b/ui/models/roster.cpp
@@ -518,7 +518,7 @@ void Models::Roster::removeGroup(const QString& account, const QString& name)
     
     if (toInsert.size() > 0) {
         Account* acc = accounts.find("account")->second;
-        for (int i = 0; i < toInsert.size(); ++i) {
+        for (std::deque<Contact*>::size_type i = 0; i < toInsert.size(); ++i) {
             Contact* cont = toInsert[i];
             acc->appendChild(cont);             //TODO optimisation
         }
diff --git a/ui/utils/messageline.cpp b/ui/utils/messageline.cpp
index befef70..6f2cb8d 100644
--- a/ui/utils/messageline.cpp
+++ b/ui/utils/messageline.cpp
@@ -111,7 +111,7 @@ MessageLine::Position MessageLine::message(const Shared::Message& msg, bool forc
         }
     }
     messageIndex.insert(std::make_pair(id, message));
-    int index = std::distance<Order::const_iterator>(messageOrder.begin(), result.first);   //need to make with binary indexed tree
+    unsigned long index = std::distance<Order::const_iterator>(messageOrder.begin(), result.first);   //need to make with binary indexed tree
     Position res = invalid;
     if (index == 0) {
         res = beggining;
diff --git a/ui/widgets/conversation.cpp b/ui/widgets/conversation.cpp
index 07a160f..d9e1039 100644
--- a/ui/widgets/conversation.cpp
+++ b/ui/widgets/conversation.cpp
@@ -24,6 +24,7 @@
 #include <QGraphicsDropShadowEffect>
 #include <QFileDialog>
 #include <QMimeDatabase>
+#include <unistd.h>
 
 Conversation::Conversation(bool muc, const QString& mJid, const QString mRes, const QString pJid, const QString pRes, const QString& acc, QWidget* parent):
     QWidget(parent),
@@ -205,6 +206,7 @@ void Conversation::onEnterPressed()
             msg.generateRandomId();
             msg.setCurrentTime();
             line->appendMessageWithUpload(msg, badge->id);
+            usleep(1000);       //this is required for the messages not to have equal time when appending into messageline
         }
         clearAttachedFiles();
     }
diff --git a/ui/widgets/vcard/emailsmodel.cpp b/ui/widgets/vcard/emailsmodel.cpp
index 4044322..9723672 100644
--- a/ui/widgets/vcard/emailsmodel.cpp
+++ b/ui/widgets/vcard/emailsmodel.cpp
@@ -145,7 +145,7 @@ QModelIndex UI::VCard::EMailsModel::addNewEmptyLine()
     return createIndex(deque.size() - 1, 0, &(deque.back()));
 }
 
-bool UI::VCard::EMailsModel::isPreferred(int row) const
+bool UI::VCard::EMailsModel::isPreferred(quint32 row) const
 {
     if (row < deque.size()) {
         return deque[row].prefered;
@@ -154,10 +154,10 @@ bool UI::VCard::EMailsModel::isPreferred(int row) const
     }
 }
 
-void UI::VCard::EMailsModel::removeLines(int index, int count)
+void UI::VCard::EMailsModel::removeLines(quint32 index, quint32 count)
 {
     if (index < deque.size()) {
-        int maxCount = deque.size() - index;
+        quint32 maxCount = deque.size() - index;
         if (count > maxCount) {
             count = maxCount;
         }
@@ -194,12 +194,12 @@ void UI::VCard::EMailsModel::setEmails(const std::deque<Shared::VCard::Email>& e
     }
 }
 
-void UI::VCard::EMailsModel::revertPreferred(int row)
+void UI::VCard::EMailsModel::revertPreferred(quint32 row)
 {
     setData(createIndex(row, 2), !isPreferred(row));
 }
 
-QString UI::VCard::EMailsModel::getEmail(int row) const
+QString UI::VCard::EMailsModel::getEmail(quint32 row) const
 {
     return deque[row].address;
 }
diff --git a/ui/widgets/vcard/emailsmodel.h b/ui/widgets/vcard/emailsmodel.h
index 358536f..3ee3a02 100644
--- a/ui/widgets/vcard/emailsmodel.h
+++ b/ui/widgets/vcard/emailsmodel.h
@@ -40,16 +40,16 @@ public:
     QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
     bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
     Qt::ItemFlags flags(const QModelIndex &index) const override;
-    bool isPreferred(int row) const;
+    bool isPreferred(quint32 row) const;
     
-    void removeLines(int index, int count);
+    void removeLines(quint32 index, quint32 count);
     void setEmails(const std::deque<Shared::VCard::Email>& emails);
     void getEmails(std::deque<Shared::VCard::Email>& emails) const;
-    QString getEmail(int row) const;
+    QString getEmail(quint32 row) const;
     
 public slots:
     QModelIndex addNewEmptyLine();
-    void revertPreferred(int row);
+    void revertPreferred(quint32 row);
     
 private:
     bool edit;
diff --git a/ui/widgets/vcard/phonesmodel.cpp b/ui/widgets/vcard/phonesmodel.cpp
index df9cad6..ee0ff75 100644
--- a/ui/widgets/vcard/phonesmodel.cpp
+++ b/ui/widgets/vcard/phonesmodel.cpp
@@ -129,7 +129,7 @@ void UI::VCard::PhonesModel::getPhones(std::deque<Shared::VCard::Phone>& phones)
     }
 }
 
-bool UI::VCard::PhonesModel::isPreferred(int row) const
+bool UI::VCard::PhonesModel::isPreferred(quint32 row) const
 {
     if (row < deque.size()) {
         return deque[row].prefered;
@@ -138,10 +138,10 @@ bool UI::VCard::PhonesModel::isPreferred(int row) const
     }
 }
 
-void UI::VCard::PhonesModel::removeLines(int index, int count)
+void UI::VCard::PhonesModel::removeLines(quint32 index, quint32 count)
 {
     if (index < deque.size()) {
-        int maxCount = deque.size() - index;
+        quint32 maxCount = deque.size() - index;
         if (count > maxCount) {
             count = maxCount;
         }
@@ -156,7 +156,7 @@ void UI::VCard::PhonesModel::removeLines(int index, int count)
     }
 }
 
-void UI::VCard::PhonesModel::revertPreferred(int row)
+void UI::VCard::PhonesModel::revertPreferred(quint32 row)
 {
     setData(createIndex(row, 3), !isPreferred(row));
 }
@@ -216,7 +216,7 @@ void UI::VCard::PhonesModel::setPhones(const std::deque<Shared::VCard::Phone>& p
     }
 }
 
-QString UI::VCard::PhonesModel::getPhone(int row) const
+QString UI::VCard::PhonesModel::getPhone(quint32 row) const
 {
     return deque[row].number;
 }
diff --git a/ui/widgets/vcard/phonesmodel.h b/ui/widgets/vcard/phonesmodel.h
index bf847d2..f799f82 100644
--- a/ui/widgets/vcard/phonesmodel.h
+++ b/ui/widgets/vcard/phonesmodel.h
@@ -41,16 +41,16 @@ public:
     int rowCount(const QModelIndex& parent) const override;
     bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
     Qt::ItemFlags flags(const QModelIndex &index) const override;
-    bool isPreferred(int row) const;
+    bool isPreferred(quint32 row) const;
     
-    void removeLines(int index, int count);
+    void removeLines(quint32 index, quint32 count);
     void setPhones(const std::deque<Shared::VCard::Phone>& phones);
     void getPhones(std::deque<Shared::VCard::Phone>& phones) const;
-    QString getPhone(int row) const;
+    QString getPhone(quint32 row) const;
     
 public slots:
     QModelIndex addNewEmptyLine();
-    void revertPreferred(int row);
+    void revertPreferred(quint32 row);
     
 private:
     bool edit;

From 867c3a18e90a961b6d7443e529c0fc9b9b592c98 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Tue, 17 Dec 2019 19:54:53 +0300
Subject: [PATCH 031/281] avatars for mucs, some tooltip tweaks

---
 core/account.cpp      | 42 ++++++++++++++++++++++++++-------
 ui/models/contact.cpp |  2 +-
 ui/models/contact.h   |  2 +-
 ui/models/room.cpp    | 55 +++++++++++++++++++++++++++++++++++++++++++
 ui/models/room.h      |  9 ++++++-
 ui/models/roster.cpp  | 19 ++++++++++-----
 6 files changed, 111 insertions(+), 18 deletions(-)

diff --git a/core/account.cpp b/core/account.cpp
index 6fb26a0..89ce95c 100644
--- a/core/account.cpp
+++ b/core/account.cpp
@@ -440,25 +440,34 @@ void Core::Account::onPresenceReceived(const QXmppPresence& p_presence)
         }
     } else {
         if (pendingVCardRequests.find(jid) == pendingVCardRequests.end()) {
+            RosterItem* item = 0;
             std::map<QString, Contact*>::const_iterator itr = contacts.find(jid);
             if (itr != contacts.end()) {
-                Contact* cnt = itr->second;
+                item = itr->second;
+            } else {
+                std::map<QString, Conference*>::const_iterator citr = conferences.find(jid);
+                if (citr != conferences.end()) {
+                    item = citr->second;
+                }
+            }
+            
+            if (item != 0) {
                 switch (p_presence.vCardUpdateType()) {
                     case QXmppPresence::VCardUpdateNone:            //this presence has nothing to do with photo
                         break;
                     case QXmppPresence::VCardUpdateNotReady:        //let's say the photo didn't change here
                         break;
                     case QXmppPresence::VCardUpdateNoPhoto:         //there is no photo, need to drop if any
-                        if (!cnt->hasAvatar() || (cnt->hasAvatar() && !cnt->isAvatarAutoGenerated())) {
-                            cnt->setAutoGeneratedAvatar();
+                        if (!item->hasAvatar() || (item->hasAvatar() && !item->isAvatarAutoGenerated())) {
+                            item->setAutoGeneratedAvatar();
                         }
-                            break;
+                        break;
                     case QXmppPresence::VCardUpdateValidPhoto:      //there is a photo, need to load
-                        if (cnt->hasAvatar()) {
-                            if (cnt->isAvatarAutoGenerated()) {
+                        if (item->hasAvatar()) {
+                            if (item->isAvatarAutoGenerated()) {
                                 requestVCard(jid);
                             } else {
-                                if (cnt->avatarHash() != p_presence.photoHash()) {
+                                if (item->avatarHash() != p_presence.photoHash()) {
                                     requestVCard(jid);
                                 }
                             }
@@ -1317,12 +1326,27 @@ void Core::Account::addNewRoom(const QString& jid, const QString& nick, const QS
     
     handleNewConference(conf);
     
-    emit addRoom(jid, {
+    QMap<QString, QVariant> cData = {
         {"autoJoin", conf->getAutoJoin()},
         {"joined", conf->getJoined()},
         {"nick", conf->getNick()},
         {"name", conf->getName()}
-    });
+    };
+    
+    if (conf->hasAvatar()) {
+        if (!conf->isAvatarAutoGenerated()) {
+            cData.insert("avatarState", static_cast<uint>(Shared::Avatar::valid));
+        } else {
+            cData.insert("avatarState", static_cast<uint>(Shared::Avatar::autocreated));
+        }
+        cData.insert("avatarPath", conf->avatarPath());
+    } else {
+        cData.insert("avatarState", static_cast<uint>(Shared::Avatar::empty));
+        cData.insert("avatarPath", "");
+        requestVCard(jid);
+    }
+    
+    emit addRoom(jid, cData);
 }
 
 void Core::Account::addContactToGroupRequest(const QString& jid, const QString& groupName)
diff --git a/ui/models/contact.cpp b/ui/models/contact.cpp
index cde8941..a5390fb 100644
--- a/ui/models/contact.cpp
+++ b/ui/models/contact.cpp
@@ -258,7 +258,7 @@ QIcon Models::Contact::getStatusIcon(bool big) const
 {
     if (getMessagesCount() > 0) {
         return Shared::icon("mail-message", big);
-    } else if (state == Shared::both) {
+    } else if (state == Shared::both || state == Shared::to) {
         return Shared::availabilityIcon(availability, big);;
     } else {
         return Shared::subscriptionStateIcon(state, big);
diff --git a/ui/models/contact.h b/ui/models/contact.h
index fda893f..f8ac346 100644
--- a/ui/models/contact.h
+++ b/ui/models/contact.h
@@ -21,7 +21,7 @@
 
 #include "item.h"
 #include "presence.h"
-#include "../../global.h"
+#include "global.h"
 #include <QMap>
 #include <QIcon>
 #include <deque>
diff --git a/ui/models/room.cpp b/ui/models/room.cpp
index a251f4d..72eafd2 100644
--- a/ui/models/room.cpp
+++ b/ui/models/room.cpp
@@ -27,6 +27,8 @@ Models::Room::Room(const QString& p_jid, const QMap<QString, QVariant>& data, Mo
     jid(p_jid),
     nick(""),
     subject(""),
+    avatarState(Shared::Avatar::empty),
+    avatarPath(""),
     messages(),
     participants()
 {
@@ -49,6 +51,15 @@ Models::Room::Room(const QString& p_jid, const QMap<QString, QVariant>& data, Mo
     if (itr != data.end()) {
         setSubject(itr.value().toString());
     }
+    
+    itr = data.find("avatarState");
+    if (itr != data.end()) {
+        setAvatarState(itr.value().toUInt());
+    }
+    itr = data.find("avatarPath");
+    if (itr != data.end()) {
+        setAvatarPath(itr.value().toString());
+    }
 }
 
 Models::Room::~Room()
@@ -111,6 +122,10 @@ QVariant Models::Room::data(int column) const
             return getMessagesCount();
         case 6:
             return getSubject();
+        case 7:
+            return static_cast<quint8>(getAvatarState());
+        case 8:
+            return getAvatarPath();
         default:
             return QVariant();
     }
@@ -165,6 +180,10 @@ void Models::Room::update(const QString& field, const QVariant& value)
         setNick(value.toString());
     } else if (field == "subject") {
         setSubject(value.toString());
+    } else if (field == "avatarState") {
+        setAvatarState(value.toUInt());
+    } else if (field == "avatarPath") {
+        setAvatarPath(value.toString());
     }
 }
 
@@ -318,3 +337,39 @@ bool Models::Room::columnInvolvedInDisplay(int col)
 {
     return Item::columnInvolvedInDisplay(col) && col == 1;
 }
+
+QString Models::Room::getAvatarPath() const
+{
+    return avatarPath;
+}
+
+Shared::Avatar Models::Room::getAvatarState() const
+{
+    return avatarState;
+}
+
+void Models::Room::setAvatarPath(const QString& path)
+{
+    if (avatarPath != path) {
+        avatarPath = path;
+        changed(8);
+    }
+}
+
+void Models::Room::setAvatarState(Shared::Avatar p_state)
+{
+    if (avatarState != p_state) {
+        avatarState = p_state;
+        changed(7);
+    }
+}
+
+void Models::Room::setAvatarState(unsigned int p_state)
+{
+    if (p_state <= static_cast<quint8>(Shared::Avatar::valid)) {
+        Shared::Avatar state = static_cast<Shared::Avatar>(p_state);
+        setAvatarState(state);
+    } else {
+        qDebug() << "An attempt to set invalid avatar state" << p_state << "to the room" << jid << ", skipping";
+    }
+}
diff --git a/ui/models/room.h b/ui/models/room.h
index 71ac221..3a6ebee 100644
--- a/ui/models/room.h
+++ b/ui/models/room.h
@@ -21,7 +21,7 @@
 
 #include "item.h"
 #include "participant.h"
-#include "../../global.h"
+#include "global.h"
 
 namespace Models {
 
@@ -69,12 +69,17 @@ public:
     
     void toOfflineState() override;
     QString getDisplayedName() const override;
+    Shared::Avatar getAvatarState() const;
+    QString getAvatarPath() const;
     
 private:
     void handleParticipantUpdate(std::map<QString, Participant*>::const_iterator itr, const QMap<QString, QVariant>& data);
     
 protected:
     bool columnInvolvedInDisplay(int col) override;
+    void setAvatarState(Shared::Avatar p_state);
+    void setAvatarState(unsigned int p_state);
+    void setAvatarPath(const QString& path);
     
 private:
     bool autoJoin;
@@ -82,6 +87,8 @@ private:
     QString jid;
     QString nick;
     QString subject;
+    Shared::Avatar avatarState;
+    QString avatarPath;
     Messages messages;
     std::map<QString, Participant*> participants;
 
diff --git a/ui/models/roster.cpp b/ui/models/roster.cpp
index 2f226f2..8e006a9 100644
--- a/ui/models/roster.cpp
+++ b/ui/models/roster.cpp
@@ -66,7 +66,6 @@ QVariant Models::Roster::data (const QModelIndex& index, int role) const
         case Qt::DisplayRole:
         {
             if (index.column() != 0) {
-                result = "";
                 break;
             }
             switch (item->type) {
@@ -126,11 +125,17 @@ QVariant Models::Roster::data (const QModelIndex& index, int role) const
                 }
                     break;
                 case Item::room: {
-                    if (index.column() != 0) {
-                        break;
-                    }
+                    quint8 col = index.column();
                     Room* room = static_cast<Room*>(item);
-                    result = room->getStatusIcon(false);
+                    if (col == 0) {
+                        result = room->getStatusIcon(false);
+                    } else if (col == 1) {
+                        QString path = room->getAvatarPath();
+                        
+                        if (path.size() > 0) {
+                            result = QIcon(path);
+                        }
+                    }
                 }
                     break;
                 case Item::participant: {
@@ -179,7 +184,7 @@ QVariant Models::Roster::data (const QModelIndex& index, int role) const
                     }
                     str += tr("Jabber ID: ") + contact->getJid() + "\n";
                     Shared::SubscriptionState ss = contact->getState();
-                    if (ss == Shared::both) {
+                    if (ss == Shared::both || ss == Shared::to) {
                         Shared::Availability av = contact->getAvailability();
                         str += tr("Availability: ") + QCoreApplication::translate("Global", Shared::availabilityNames[av].toLatin1());
                         if (av != Shared::offline) {
@@ -252,6 +257,8 @@ QVariant Models::Roster::data (const QModelIndex& index, int role) const
                     if (count > 0) {
                         str += tr("New messages: ") + std::to_string(count).c_str() + "\n";
                     }
+                    
+                    str += tr("Jabber ID: ") + rm->getJid() + "\n";
                     str += tr("Subscription: ") + rm->getStatusText();
                     if (rm->getJoined()) {
                         str += QString("\n") + tr("Members: ") + std::to_string(rm->childCount()).c_str();

From f13b43d38b0392c3ef88ab50dc6e4bf07da55a65 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Fri, 20 Dec 2019 18:41:20 +0300
Subject: [PATCH 032/281] some bug fixes, initial work on avatars in dialog
 windows

---
 core/networkaccess.cpp      |  8 +++++---
 global.cpp                  | 17 +++++++++++++++-
 global.h                    |  1 +
 ui/models/roster.cpp        |  5 +++++
 ui/models/roster.h          |  1 +
 ui/squawk.cpp               |  5 +++--
 ui/utils/image.cpp          | 40 ++++++++++++++++++++++++++++++-------
 ui/utils/image.h            |  6 +++++-
 ui/utils/message.cpp        | 16 ++++++++++++++-
 ui/utils/message.h          |  4 +++-
 ui/utils/messageline.cpp    | 17 +++++++++++++++-
 ui/utils/messageline.h      |  2 ++
 ui/widgets/chat.cpp         |  9 +++------
 ui/widgets/chat.h           |  2 +-
 ui/widgets/conversation.cpp | 14 ++++++-------
 ui/widgets/conversation.h   | 11 +++++-----
 ui/widgets/room.cpp         |  7 +++----
 ui/widgets/room.h           |  2 +-
 18 files changed, 125 insertions(+), 42 deletions(-)

diff --git a/core/networkaccess.cpp b/core/networkaccess.cpp
index a27ab8f..3e9d2c0 100644
--- a/core/networkaccess.cpp
+++ b/core/networkaccess.cpp
@@ -291,15 +291,17 @@ void Core::NetworkAccess::onDownloadFinished()
             QStringList parts = fileName.split(".");
             path = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation) + "/";
             QString suffix("");
-            QString realName = parts.front();
-            for (QStringList::const_iterator sItr = (parts.begin()++), sEnd = parts.end(); sItr != sEnd; ++sItr) {
+            QStringList::const_iterator sItr = parts.begin();
+            QString realName = *sItr;
+            ++sItr;
+            for (QStringList::const_iterator sEnd = parts.end(); sItr != sEnd; ++sItr) {
                 suffix += "." + (*sItr);
             }
             QString postfix("");
             QFileInfo proposedName(path + realName + postfix + suffix);
             int counter = 0;
             while (proposedName.exists()) {
-                suffix = QString("(") + std::to_string(++counter).c_str() + ")";
+                postfix = QString("(") + std::to_string(++counter).c_str() + ")";
                 proposedName = QFileInfo(path + realName + postfix + suffix);
             }
             
diff --git a/global.cpp b/global.cpp
index d054150..5756bb0 100644
--- a/global.cpp
+++ b/global.cpp
@@ -622,9 +622,24 @@ QIcon Shared::icon(const QString& name, bool big)
         const QString& prefix = QApplication::palette().window().color().lightnessF() > 0.5 ? 
             big ? db : ds:
             big ? lb : ls;
-        return QIcon::fromTheme(itr->second.first, QIcon(prefix + itr->second.second));
+        return QIcon::fromTheme(itr->second.first, QIcon(prefix + itr->second.second + ".svg"));
     } else {
         qDebug() << "Icon" << name << "not found";
         return QIcon::fromTheme(name);
     }
 }
+
+
+QString Shared::iconPath(const QString& name, bool big)
+{
+    QString result = "";
+    std::map<QString, std::pair<QString, QString>>::const_iterator itr = icons.find(name);
+    if (itr != icons.end()) {
+        const QString& prefix = QApplication::palette().window().color().lightnessF() > 0.5 ? 
+            big ? db : ds:
+            big ? lb : ls;
+        result = prefix + itr->second.second + ".svg";
+    }
+    
+    return result;
+}
diff --git a/global.h b/global.h
index d5d206e..521cd26 100644
--- a/global.h
+++ b/global.h
@@ -438,6 +438,7 @@ QIcon availabilityIcon(Availability av, bool big = false);
 QIcon subscriptionStateIcon(SubscriptionState ss, bool big = false);
 QIcon connectionStateIcon(ConnectionState cs, bool big = false);
 QIcon icon(const QString& name, bool big = false);
+QString iconPath(const QString& name, bool big = false);
 
 static const std::map<QString, std::pair<QString, QString>> icons = {
     {"user-online", {"user-online", "online"}},
diff --git a/ui/models/roster.cpp b/ui/models/roster.cpp
index 8e006a9..903a3a8 100644
--- a/ui/models/roster.cpp
+++ b/ui/models/roster.cpp
@@ -961,3 +961,8 @@ QString Models::Roster::getContactIconPath(const QString& account, const QString
     }
     return path;
 }
+
+Models::Account * Models::Roster::getAccount(const QString& name)
+{
+    return accounts.find(name)->second;
+}
diff --git a/ui/models/roster.h b/ui/models/roster.h
index 40c978d..07ab495 100644
--- a/ui/models/roster.h
+++ b/ui/models/roster.h
@@ -74,6 +74,7 @@ public:
     std::deque<QString> groupList(const QString& account) const;
     bool groupHasContact(const QString& account, const QString& group, const QString& contactJID) const;
     QString getContactIconPath(const QString& account, const QString& jid);
+    Account* getAccount(const QString& name);
     
     Accounts* accountsModel;
     
diff --git a/ui/squawk.cpp b/ui/squawk.cpp
index 095f639..167b8ed 100644
--- a/ui/squawk.cpp
+++ b/ui/squawk.cpp
@@ -276,6 +276,7 @@ void Squawk::onRosterItemDoubleClicked(const QModelIndex& item)
         
         if (id != 0) {
             Conversations::const_iterator itr = conversations.find(*id);
+            Models::Account* acc = rosterModel.getAccount(id->account);
             Conversation* conv = 0;
             bool created = false;
             Models::Contact::Messages deque;
@@ -283,11 +284,11 @@ void Squawk::onRosterItemDoubleClicked(const QModelIndex& item)
                 conv = itr->second;
             } else if (contact != 0) {
                 created = true;
-                conv = new Chat(contact);
+                conv = new Chat(acc, contact);
                 contact->getMessages(deque);
             } else if (room != 0) {
                 created = true;
-                conv = new Room(room);
+                conv = new Room(acc, room);
                 room->getMessages(deque);
                 
                 if (!room->getJoined()) {
diff --git a/ui/utils/image.cpp b/ui/utils/image.cpp
index 1d09709..13ac400 100644
--- a/ui/utils/image.cpp
+++ b/ui/utils/image.cpp
@@ -19,12 +19,17 @@
 #include <QDebug>
 #include "image.h"
 
-Image::Image(const QString& path, quint16 p_minWidth, QWidget* parent):
+Image::Image(const QString& p_path, quint16 p_minWidth, QWidget* parent):
     QLabel(parent),
-    pixmap(path),
+    pixmap(p_path),
+    path(p_path),
     aspectRatio(0),
-    minWidth(p_minWidth)
+    minWidth(p_minWidth),
+    svgMode(false)
 {
+    if (path.contains(".svg")) {
+        svgMode = true;
+    }
     setScaledContents(true);
     recalculateAspectRatio();
 }
@@ -55,7 +60,9 @@ void Image::recalculateAspectRatio()
     qreal height = pixmap.height();
     qreal width = pixmap.width();
     aspectRatio = width / height;
-    setPixmap(pixmap);
+    if (!svgMode) {
+        setPixmap(pixmap);
+    }
     setMinimumHeight(minWidth / aspectRatio);
     setMinimumWidth(minWidth);
 }
@@ -68,8 +75,27 @@ void Image::setMinWidth(quint16 p_minWidth)
     }
 }
 
-void Image::setPath(const QString& path)
+void Image::setPath(const QString& p_path)
 {
-    pixmap = QPixmap(path);
-    recalculateAspectRatio();
+    if (path != p_path) {
+        path = p_path;
+        if (path.contains(".svg")) {
+            svgMode = true;
+        }
+        pixmap = QPixmap(path);
+        recalculateAspectRatio();
+    }
 }
+
+void Image::resizeEvent(QResizeEvent* event)
+{
+    if (svgMode) {
+        QIcon ico;
+        QSize size = event->size();
+        ico.addFile(path, size);
+        setPixmap(ico.pixmap(size));
+    }
+    
+    QLabel::resizeEvent(event);
+}
+
diff --git a/ui/utils/image.h b/ui/utils/image.h
index 883ddf4..4f524bb 100644
--- a/ui/utils/image.h
+++ b/ui/utils/image.h
@@ -21,6 +21,8 @@
 
 #include <QLabel>
 #include <QPixmap>
+#include <QResizeEvent>
+#include <QIcon>
 
 /**
  * @todo write docs
@@ -29,7 +31,6 @@ class Image : public QLabel
 {
 public:
     Image(const QString& path, quint16 minWidth = 50, QWidget* parent = nullptr);
-
     ~Image();
 
     int heightForWidth(int width) const override;
@@ -40,11 +41,14 @@ public:
     
 private:
     QPixmap pixmap;
+    QString path;
     qreal aspectRatio;
     quint16 minWidth;
+    bool svgMode;
     
 private:
     void recalculateAspectRatio();
+    void resizeEvent(QResizeEvent * event) override;
 };
 
 #endif // IMAGE_H
diff --git a/ui/utils/message.cpp b/ui/utils/message.cpp
index 1dc35b7..d3d9f77 100644
--- a/ui/utils/message.cpp
+++ b/ui/utils/message.cpp
@@ -26,7 +26,7 @@
 const QRegularExpression urlReg("(?<!<a\\shref=['\"])(?<!<img\\ssrc=['\"])((?:https?|ftp)://\\S+)");       //finds all hypertext references which are not wrapped in a or img tags
 const QRegularExpression imgReg("((?:https?|ftp)://\\S+\\.(?:jpg|jpeg|png|svg|gif))");
 
-Message::Message(const Shared::Message& source, bool outgoing, const QString& p_sender, QWidget* parent):
+Message::Message(const Shared::Message& source, bool outgoing, const QString& p_sender, const QString& avatarPath, QWidget* parent):
     QHBoxLayout(parent),
     msg(source),
     body(new QWidget()),
@@ -39,6 +39,7 @@ Message::Message(const Shared::Message& source, bool outgoing, const QString& p_
     file(0),
     progress(0),
     fileComment(new QLabel()),
+    avatar(new Image(avatarPath.size() == 0 ? Shared::iconPath("user", true) : avatarPath, 60)),
     hasButton(false),
     hasProgress(false),
     hasFile(false),
@@ -77,13 +78,21 @@ Message::Message(const Shared::Message& source, bool outgoing, const QString& p_
     shadow->setYOffset(1);
     shadow->setColor(Qt::black);
     body->setGraphicsEffect(shadow);
+    avatar->setMaximumHeight(60);
+    avatar->setMaximumWidth(60);
+    QVBoxLayout* aLay = new QVBoxLayout();
+    aLay->addWidget(avatar);
+    aLay->addStretch();
+    
     
     if (outgoing) {
         sender->setAlignment(Qt::AlignRight);
         date->setAlignment(Qt::AlignRight);
         addStretch();
         addWidget(body);
+        addItem(aLay);
     } else {
+        addItem(aLay);
         addWidget(body);
         addStretch();
     }
@@ -95,6 +104,7 @@ Message::~Message()
         delete fileComment;
     }
     delete body;
+    delete avatar;
 }
 
 QString Message::getId() const
@@ -243,3 +253,7 @@ const Shared::Message & Message::getMessage() const
     return msg;
 }
 
+void Message::setAvatarPath(const QString& p_path)
+{
+    avatar->setPath(p_path);
+}
diff --git a/ui/utils/message.h b/ui/utils/message.h
index 6bea433..537f40b 100644
--- a/ui/utils/message.h
+++ b/ui/utils/message.h
@@ -41,7 +41,7 @@ class Message : public QHBoxLayout
 {
     Q_OBJECT
 public:
-    Message(const Shared::Message& source, bool outgoing, const QString& sender, QWidget* parent = nullptr);
+    Message(const Shared::Message& source, bool outgoing, const QString& sender, const QString& avatarPath = "", QWidget* parent = nullptr);
     ~Message();
     
     void setSender(const QString& sender);
@@ -54,6 +54,7 @@ public:
     void hideComment();
     void showFile(const QString& path);
     void setProgress(qreal value);
+    void setAvatarPath(const QString& p_path);
     
 signals:
     void buttonClicked();
@@ -70,6 +71,7 @@ private:
     QLabel* file;
     QProgressBar* progress;
     QLabel* fileComment;
+    Image* avatar;
     bool hasButton;
     bool hasProgress;
     bool hasFile;
diff --git a/ui/utils/messageline.cpp b/ui/utils/messageline.cpp
index 6f2cb8d..e7d190d 100644
--- a/ui/utils/messageline.cpp
+++ b/ui/utils/messageline.cpp
@@ -29,6 +29,7 @@ MessageLine::MessageLine(bool p_room, QWidget* parent):
     uploadPaths(),
     layout(new QVBoxLayout(this)),
     myName(),
+    myAvatarPath(),
     palNames(),
     uploading(),
     downloading(),
@@ -57,15 +58,18 @@ MessageLine::Position MessageLine::message(const Shared::Message& msg, bool forc
     }
     
     QString sender;
+    QString aPath;
     bool outgoing;
     
     if (forceOutgoing) {
         sender = myName;
+        aPath = myAvatarPath;
         outgoing = true;
     } else {
         if (room) {
             if (msg.getFromResource() == myName) {
                 sender = myName;
+                aPath = myAvatarPath;
                 outgoing = true;
             } else {
                 sender = msg.getFromResource();
@@ -74,6 +78,7 @@ MessageLine::Position MessageLine::message(const Shared::Message& msg, bool forc
         } else {
             if (msg.getOutgoing()) {
                 sender = myName;
+                aPath = myAvatarPath;
                 outgoing = true;
             } else {
                 QString jid = msg.getFromJid();
@@ -88,7 +93,7 @@ MessageLine::Position MessageLine::message(const Shared::Message& msg, bool forc
         }
     }
     
-    Message* message = new Message(msg, outgoing, sender);
+    Message* message = new Message(msg, outgoing, sender, aPath);
     
     std::pair<Order::const_iterator, bool> result = messageOrder.insert(std::make_pair(msg.getTime(), message));
     if (!result.second) {
@@ -348,3 +353,13 @@ void MessageLine::onUpload()
 {
     //TODO retry
 }
+
+void MessageLine::setMyAvatarPath(const QString& p_path)
+{
+    if (myAvatarPath != p_path) {
+        myAvatarPath = p_path;
+        for (std::pair<QString, Message*> pair : myMessages) {
+            pair.second->setAvatarPath(myAvatarPath);
+        }
+    }
+}
diff --git a/ui/utils/messageline.h b/ui/utils/messageline.h
index 56f0a5e..4ef226c 100644
--- a/ui/utils/messageline.h
+++ b/ui/utils/messageline.h
@@ -54,6 +54,7 @@ public:
     void fileProgress(const QString& messageId, qreal progress);
     void appendMessageWithUpload(const Shared::Message& msg, const QString& path);
     void removeMessage(const QString& messageId);
+    void setMyAvatarPath(const QString& p_path);
     
 signals:
     void resize(int amount);
@@ -87,6 +88,7 @@ private:
     QVBoxLayout* layout;
     
     QString myName;
+    QString myAvatarPath;
     std::map<QString, QString> palNames;
     Index uploading;
     Index downloading;
diff --git a/ui/widgets/chat.cpp b/ui/widgets/chat.cpp
index c876679..43a4144 100644
--- a/ui/widgets/chat.cpp
+++ b/ui/widgets/chat.cpp
@@ -18,8 +18,8 @@
 
 #include "chat.h"
 
-Chat::Chat(Models::Contact* p_contact, QWidget* parent):
-    Conversation(false, p_contact->getAccountJid(), p_contact->getAccountResource(), p_contact->getJid(), "", p_contact->getAccountName(), parent),
+Chat::Chat(Models::Account* acc, Models::Contact* p_contact, QWidget* parent):
+    Conversation(false, acc, p_contact->getJid(), "", parent),
     contact(p_contact)
 {
     setName(p_contact->getContactName());
@@ -27,8 +27,6 @@ Chat::Chat(Models::Contact* p_contact, QWidget* parent):
     setStatus(p_contact->getStatus());
     
     connect(contact, &Models::Contact::childChanged, this, &Chat::onContactChanged);
-    
-    line->setMyName(p_contact->getAccountName());
 }
 
 Chat::~Chat()
@@ -62,8 +60,7 @@ void Chat::updateState()
 void Chat::handleSendMessage(const QString& text)
 {
     Shared::Message msg(Shared::Message::chat);
-    msg.setFromJid(myJid);
-    msg.setFromResource(myResource);
+    msg.setFrom(account->getFullJid());
     msg.setToJid(palJid);
     msg.setToResource(activePalResource);
     msg.setBody(text);
diff --git a/ui/widgets/chat.h b/ui/widgets/chat.h
index 174bdf3..d36cbbf 100644
--- a/ui/widgets/chat.h
+++ b/ui/widgets/chat.h
@@ -30,7 +30,7 @@ class Chat : public Conversation
 {
     Q_OBJECT
 public:
-    Chat(Models::Contact* p_contact, QWidget* parent = 0);
+    Chat(Models::Account* acc, Models::Contact* p_contact, QWidget* parent = 0);
     ~Chat();
     
     void addMessage(const Shared::Message & data) override;
diff --git a/ui/widgets/conversation.cpp b/ui/widgets/conversation.cpp
index d9e1039..52e4b4c 100644
--- a/ui/widgets/conversation.cpp
+++ b/ui/widgets/conversation.cpp
@@ -26,14 +26,12 @@
 #include <QMimeDatabase>
 #include <unistd.h>
 
-Conversation::Conversation(bool muc, const QString& mJid, const QString mRes, const QString pJid, const QString pRes, const QString& acc, QWidget* parent):
+Conversation::Conversation(bool muc, Models::Account* acc, const QString pJid, const QString pRes, QWidget* parent):
     QWidget(parent),
     isMuc(muc),
-    myJid(mJid),
-    myResource(mRes),
+    account(acc),
     palJid(pJid),
     activePalResource(pRes),
-    account(acc),
     line(new MessageLine(muc)),
     m_ui(new Ui::Conversation()),
     ker(),
@@ -85,6 +83,9 @@ Conversation::Conversation(bool muc, const QString& mJid, const QString mRes, co
     m_ui->scrollArea->installEventFilter(&scrollResizeCatcher);
     m_ui->filesPanel->installEventFilter(&attachResizeCatcher);
     
+    line->setMyAvatarPath(acc->getAvatarPath());
+    line->setMyName(acc->getName());
+    
     applyVisualEffects();
 }
 
@@ -124,7 +125,7 @@ void Conversation::setName(const QString& name)
 
 QString Conversation::getAccount() const
 {
-    return account;
+    return account->getName();
 }
 
 QString Conversation::getJid() const
@@ -199,8 +200,7 @@ void Conversation::onEnterPressed()
                 msg.setType(Shared::Message::chat);
                 msg.setToResource(activePalResource);
             }
-            msg.setFromJid(myJid);
-            msg.setFromResource(myResource);
+            msg.setFrom(account->getFullJid());
             msg.setToJid(palJid);
             msg.setOutgoing(true);
             msg.generateRandomId();
diff --git a/ui/widgets/conversation.h b/ui/widgets/conversation.h
index cf9585b..594801a 100644
--- a/ui/widgets/conversation.h
+++ b/ui/widgets/conversation.h
@@ -21,8 +21,9 @@
 
 #include <QWidget>
 #include <QScopedPointer>
-#include "../../global.h"
-#include "../../order.h"
+#include "global.h"
+#include "order.h"
+#include "../models/account.h"
 #include "../utils/messageline.h"
 #include "../utils/resizer.h"
 #include "../utils/flowlayout.h"
@@ -63,7 +64,7 @@ class Conversation : public QWidget
 {
     Q_OBJECT
 public:
-    Conversation(bool muc, const QString& mJid, const QString mRes, const QString pJid, const QString pRes, const QString& acc, QWidget* parent = 0);
+    Conversation(bool muc, Models::Account* acc, const QString pJid, const QString pRes, QWidget* parent = 0);
     ~Conversation();
     
     QString getJid() const;
@@ -115,11 +116,9 @@ protected:
         keep,
         down
     };
-    QString myJid;
-    QString myResource;
+    Models::Account* account;
     QString palJid;
     QString activePalResource;
-    QString account;
     MessageLine* line;
     QScopedPointer<Ui::Conversation> m_ui;
     KeyEnterReceiver ker;
diff --git a/ui/widgets/room.cpp b/ui/widgets/room.cpp
index 98fc97d..0e0ecc6 100644
--- a/ui/widgets/room.cpp
+++ b/ui/widgets/room.cpp
@@ -18,8 +18,8 @@
 
 #include "room.h"
 
-Room::Room(Models::Room* p_room, QWidget* parent):
-    Conversation(true, p_room->getAccountJid(), p_room->getAccountResource(), p_room->getJid(), "", p_room->getAccountName(), parent),
+Room::Room(Models::Account* acc, Models::Room* p_room, QWidget* parent):
+    Conversation(true, acc, p_room->getJid(), "", parent),
     room(p_room)
 {
     setName(p_room->getName());
@@ -36,8 +36,7 @@ Room::~Room()
 void Room::handleSendMessage(const QString& text)
 {
     Shared::Message msg(Shared::Message::groupChat);
-    msg.setFromJid(myJid);
-    msg.setFromResource(myResource);
+    msg.setFrom(account->getFullJid());
     msg.setToJid(palJid);
     //msg.setToResource(activePalResource);
     msg.setBody(text);
diff --git a/ui/widgets/room.h b/ui/widgets/room.h
index 52d19c6..bf08615 100644
--- a/ui/widgets/room.h
+++ b/ui/widgets/room.h
@@ -29,7 +29,7 @@ class Room : public Conversation
 {
     Q_OBJECT
 public:
-    Room(Models::Room* p_room, QWidget* parent = 0);
+    Room(Models::Account* acc, Models::Room* p_room, QWidget* parent = 0);
     ~Room();
     
     bool autoJoined() const;

From dd62f84acc127b133d487274fa508c728999f431 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Mon, 23 Dec 2019 09:28:23 +0300
Subject: [PATCH 033/281] pal avatars in one on one dialogs

---
 ui/utils/message.cpp        |  6 +++++-
 ui/utils/messageline.cpp    | 39 +++++++++++++++++++++++++++++++++++++
 ui/utils/messageline.h      |  3 +++
 ui/widgets/chat.cpp         | 14 +++++++++++++
 ui/widgets/chat.h           |  1 +
 ui/widgets/conversation.cpp |  6 +++++-
 ui/widgets/conversation.h   |  1 +
 ui/widgets/conversation.ui  | 18 +++++++++++++++++
 ui/widgets/room.cpp         |  1 +
 9 files changed, 87 insertions(+), 2 deletions(-)

diff --git a/ui/utils/message.cpp b/ui/utils/message.cpp
index d3d9f77..ecb54f8 100644
--- a/ui/utils/message.cpp
+++ b/ui/utils/message.cpp
@@ -255,5 +255,9 @@ const Shared::Message & Message::getMessage() const
 
 void Message::setAvatarPath(const QString& p_path)
 {
-    avatar->setPath(p_path);
+    if (p_path.size() == 0) {
+        avatar->setPath(Shared::iconPath("user", true));
+    } else {
+        avatar->setPath(p_path);
+    }
 }
diff --git a/ui/utils/messageline.cpp b/ui/utils/messageline.cpp
index e7d190d..d692308 100644
--- a/ui/utils/messageline.cpp
+++ b/ui/utils/messageline.cpp
@@ -27,6 +27,7 @@ MessageLine::MessageLine(bool p_room, QWidget* parent):
     myMessages(),
     palMessages(),
     uploadPaths(),
+    palAvatars(),
     layout(new QVBoxLayout(this)),
     myName(),
     myAvatarPath(),
@@ -88,6 +89,12 @@ MessageLine::Position MessageLine::message(const Shared::Message& msg, bool forc
                 } else {
                     sender = jid;
                 }
+                
+                std::map<QString, QString>::iterator aItr = palAvatars.find(jid);
+                if (aItr != palAvatars.end()) {
+                    aPath = aItr->second;
+                }
+                
                 outgoing = false;
             }
         }
@@ -185,6 +192,38 @@ void MessageLine::setPalName(const QString& jid, const QString& name)
     }
 }
 
+void MessageLine::setPalAvatar(const QString& jid, const QString& path)
+{
+    std::map<QString, QString>::iterator itr = palAvatars.find(jid);
+    if (itr == palAvatars.end()) {
+        palAvatars.insert(std::make_pair(jid, path));
+    } else {
+        itr->second = path;
+    }
+    
+    std::map<QString, Index>::iterator pItr = palMessages.find(jid);
+    if (pItr != palMessages.end()) {
+        for (Index::const_iterator itr = pItr->second.begin(), end = pItr->second.end(); itr != end; ++itr) {
+            itr->second->setAvatarPath(path);
+        }
+    }
+}
+
+void MessageLine::dropPalAvatar(const QString& jid)
+{
+    std::map<QString, QString>::iterator itr = palAvatars.find(jid);
+    if (itr != palNames.end()) {
+        palNames.erase(itr);
+        
+        std::map<QString, Index>::iterator pItr = palMessages.find(jid);
+        if (pItr != palMessages.end()) {
+            for (Index::const_iterator itr = pItr->second.begin(), end = pItr->second.end(); itr != end; ++itr) {
+                itr->second->setAvatarPath("");
+            }
+        }
+    }
+}
+
 void MessageLine::resizeEvent(QResizeEvent* event)
 {
     QWidget::resizeEvent(event);
diff --git a/ui/utils/messageline.h b/ui/utils/messageline.h
index 4ef226c..707ff39 100644
--- a/ui/utils/messageline.h
+++ b/ui/utils/messageline.h
@@ -55,6 +55,8 @@ public:
     void appendMessageWithUpload(const Shared::Message& msg, const QString& path);
     void removeMessage(const QString& messageId);
     void setMyAvatarPath(const QString& p_path);
+    void setPalAvatar(const QString& jid, const QString& path);
+    void dropPalAvatar(const QString& jid);
     
 signals:
     void resize(int amount);
@@ -85,6 +87,7 @@ private:
     Index myMessages;
     std::map<QString, Index> palMessages;
     std::map<QString, QString> uploadPaths;
+    std::map<QString, QString> palAvatars;
     QVBoxLayout* layout;
     
     QString myName;
diff --git a/ui/widgets/chat.cpp b/ui/widgets/chat.cpp
index 43a4144..b8f49ba 100644
--- a/ui/widgets/chat.cpp
+++ b/ui/widgets/chat.cpp
@@ -25,6 +25,7 @@ Chat::Chat(Models::Account* acc, Models::Contact* p_contact, QWidget* parent):
     setName(p_contact->getContactName());
     updateState();
     setStatus(p_contact->getStatus());
+    setAvatar(p_contact->getAvatarPath());
     
     connect(contact, &Models::Contact::childChanged, this, &Chat::onContactChanged);
 }
@@ -46,6 +47,9 @@ void Chat::onContactChanged(Models::Item* item, int row, int col)
             case 5:
                 setStatus(contact->getStatus());
                 break;
+            case 7:
+                setAvatar(contact->getAvatarPath());
+                break;
         }
     }
 }
@@ -89,3 +93,13 @@ void Chat::setName(const QString& name)
     line->setPalName(getJid(), name);
 }
 
+void Chat::setAvatar(const QString& path)
+{
+    Conversation::setAvatar(path);
+    
+    if (path.size() == 0) {
+        line->dropPalAvatar(contact->getJid());
+    } else {
+        line->setPalAvatar(contact->getJid(), path);
+    }
+}
diff --git a/ui/widgets/chat.h b/ui/widgets/chat.h
index d36cbbf..5c0eb63 100644
--- a/ui/widgets/chat.h
+++ b/ui/widgets/chat.h
@@ -34,6 +34,7 @@ public:
     ~Chat();
     
     void addMessage(const Shared::Message & data) override;
+    void setAvatar(const QString& path) override;
 
 protected slots:
     void onContactChanged(Models::Item* item, int row, int col);
diff --git a/ui/widgets/conversation.cpp b/ui/widgets/conversation.cpp
index 52e4b4c..89a2a7f 100644
--- a/ui/widgets/conversation.cpp
+++ b/ui/widgets/conversation.cpp
@@ -384,6 +384,11 @@ void Conversation::onClearButton()
     m_ui->messageEditor->clear();
 }
 
+void Conversation::setAvatar(const QString& path)
+{
+    m_ui->avatar->setPixmap(path.size() == 0 ? Shared::iconPath("user", true) : path);
+}
+
 void Conversation::onAttachResize(const QSize& oldSize, const QSize& newSize)
 {
     int oh = oldSize.height();
@@ -419,4 +424,3 @@ VisibilityCatcher::VisibilityCatcher(QWidget* parent):
 QObject(parent)
 {
 }
-
diff --git a/ui/widgets/conversation.h b/ui/widgets/conversation.h
index 594801a..5e68965 100644
--- a/ui/widgets/conversation.h
+++ b/ui/widgets/conversation.h
@@ -78,6 +78,7 @@ public:
     void responseLocalFile(const QString& messageId, const QString& path);
     void fileError(const QString& messageId, const QString& error);
     void responseFileProgress(const QString& messageId, qreal progress);
+    virtual void setAvatar(const QString& path);
     
 signals:
     void sendMessage(const Shared::Message& message);
diff --git a/ui/widgets/conversation.ui b/ui/widgets/conversation.ui
index 0eb7ae8..acdc80b 100644
--- a/ui/widgets/conversation.ui
+++ b/ui/widgets/conversation.ui
@@ -71,6 +71,12 @@
        </property>
        <item>
         <widget class="QWidget" name="widget_3" native="true">
+         <property name="maximumSize">
+          <size>
+           <width>16777215</width>
+           <height>100</height>
+          </size>
+         </property>
          <property name="autoFillBackground">
           <bool>true</bool>
          </property>
@@ -92,6 +98,9 @@
             <property name="text">
              <string/>
             </property>
+            <property name="scaledContents">
+             <bool>true</bool>
+            </property>
            </widget>
           </item>
           <item>
@@ -140,9 +149,18 @@
           </item>
           <item>
            <widget class="QLabel" name="avatar">
+            <property name="maximumSize">
+             <size>
+              <width>60</width>
+              <height>60</height>
+             </size>
+            </property>
             <property name="text">
              <string/>
             </property>
+            <property name="scaledContents">
+             <bool>true</bool>
+            </property>
            </widget>
           </item>
          </layout>
diff --git a/ui/widgets/room.cpp b/ui/widgets/room.cpp
index 0e0ecc6..49c422c 100644
--- a/ui/widgets/room.cpp
+++ b/ui/widgets/room.cpp
@@ -25,6 +25,7 @@ Room::Room(Models::Account* acc, Models::Room* p_room, QWidget* parent):
     setName(p_room->getName());
     line->setMyName(room->getNick());
     setStatus(room->getSubject());
+    setAvatar(room->getAvatarPath());
     
     connect(room, &Models::Room::childChanged, this, &Room::onRoomChanged);
 }

From 0bcfd779b802715f4d8fe6fe7b8a516722eb3cdf Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Tue, 24 Dec 2019 11:42:46 +0300
Subject: [PATCH 034/281] some bugs and corner cases

---
 core/account.cpp            |  6 +++++-
 ui/utils/messageline.cpp    |  4 ++--
 ui/widgets/conversation.cpp |  6 +++++-
 ui/widgets/conversation.ui  | 34 ++++++++++++++++++++++++++++++++--
 4 files changed, 44 insertions(+), 6 deletions(-)

diff --git a/core/account.cpp b/core/account.cpp
index 89ce95c..9811dd2 100644
--- a/core/account.cpp
+++ b/core/account.cpp
@@ -1556,7 +1556,11 @@ void Core::Account::onOwnVCardReceived(const QXmppVCardIq& card)
 
 QString Core::Account::getAvatarPath() const
 {
-    return QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/" + name + "/" + "avatar." + avatarType;
+    if (avatarType.size() == 0) {
+        return "";
+    } else {
+        return QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/" + name + "/" + "avatar." + avatarType;
+    }
 }
 
 void Core::Account::onContactAvatarChanged(Shared::Avatar type, const QString& path)
diff --git a/ui/utils/messageline.cpp b/ui/utils/messageline.cpp
index d692308..013d94c 100644
--- a/ui/utils/messageline.cpp
+++ b/ui/utils/messageline.cpp
@@ -212,8 +212,8 @@ void MessageLine::setPalAvatar(const QString& jid, const QString& path)
 void MessageLine::dropPalAvatar(const QString& jid)
 {
     std::map<QString, QString>::iterator itr = palAvatars.find(jid);
-    if (itr != palNames.end()) {
-        palNames.erase(itr);
+    if (itr != palAvatars.end()) {
+        palAvatars.erase(itr);
         
         std::map<QString, Index>::iterator pItr = palMessages.find(jid);
         if (pItr != palMessages.end()) {
diff --git a/ui/widgets/conversation.cpp b/ui/widgets/conversation.cpp
index 89a2a7f..ef06dd6 100644
--- a/ui/widgets/conversation.cpp
+++ b/ui/widgets/conversation.cpp
@@ -386,7 +386,11 @@ void Conversation::onClearButton()
 
 void Conversation::setAvatar(const QString& path)
 {
-    m_ui->avatar->setPixmap(path.size() == 0 ? Shared::iconPath("user", true) : path);
+    if (path.size() == 0) {
+        m_ui->avatar->setPixmap(Shared::icon("user", true).pixmap(QSize(50, 50)));
+    } else {
+        m_ui->avatar->setPixmap(path);
+    }
 }
 
 void Conversation::onAttachResize(const QSize& oldSize, const QSize& newSize)
diff --git a/ui/widgets/conversation.ui b/ui/widgets/conversation.ui
index acdc80b..75ca7c5 100644
--- a/ui/widgets/conversation.ui
+++ b/ui/widgets/conversation.ui
@@ -7,7 +7,7 @@
     <x>0</x>
     <y>0</y>
     <width>572</width>
-    <height>485</height>
+    <height>484</height>
    </rect>
   </property>
   <layout class="QHBoxLayout" name="horizontalLayout">
@@ -95,11 +95,26 @@
           </property>
           <item>
            <widget class="QLabel" name="statusIcon">
+            <property name="minimumSize">
+             <size>
+              <width>0</width>
+              <height>0</height>
+             </size>
+            </property>
+            <property name="maximumSize">
+             <size>
+              <width>16777215</width>
+              <height>16777215</height>
+             </size>
+            </property>
             <property name="text">
              <string/>
             </property>
             <property name="scaledContents">
-             <bool>true</bool>
+             <bool>false</bool>
+            </property>
+            <property name="alignment">
+             <set>Qt::AlignCenter</set>
             </property>
            </widget>
           </item>
@@ -149,18 +164,33 @@
           </item>
           <item>
            <widget class="QLabel" name="avatar">
+            <property name="sizePolicy">
+             <sizepolicy hsizetype="Minimum" vsizetype="Minimum">
+              <horstretch>0</horstretch>
+              <verstretch>0</verstretch>
+             </sizepolicy>
+            </property>
             <property name="maximumSize">
              <size>
               <width>60</width>
               <height>60</height>
              </size>
             </property>
+            <property name="baseSize">
+             <size>
+              <width>50</width>
+              <height>50</height>
+             </size>
+            </property>
             <property name="text">
              <string/>
             </property>
             <property name="scaledContents">
              <bool>true</bool>
             </property>
+            <property name="margin">
+             <number>5</number>
+            </property>
            </widget>
           </item>
          </layout>

From 3e594c7e13cd7c73dbaaaeffab39f90cb0cddda3 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Wed, 25 Dec 2019 13:24:20 +0300
Subject: [PATCH 035/281] connectivity, roster position size and state,
 expanded anccounts and groups restoration with the settings

---
 main.cpp             |   7 ++-
 ui/models/roster.cpp |  29 ++++++++--
 ui/models/roster.h   |   2 +
 ui/squawk.cpp        | 122 +++++++++++++++++++++++++++++++++++++++++++
 ui/squawk.h          |   6 +++
 5 files changed, 162 insertions(+), 4 deletions(-)

diff --git a/main.cpp b/main.cpp
index ce23acc..a38852b 100644
--- a/main.cpp
+++ b/main.cpp
@@ -39,7 +39,7 @@ int main(int argc, char *argv[])
     
     QApplication::setApplicationName("squawk");
     QApplication::setApplicationDisplayName("Squawk");
-    QApplication::setApplicationVersion("0.1.1");
+    QApplication::setApplicationVersion("0.1.2");
     
     QTranslator qtTranslator;
     qtTranslator.load("qt_" + QLocale::system().name(), QLibraryInfo::location(QLibraryInfo::TranslationsPath));
@@ -81,6 +81,7 @@ int main(int argc, char *argv[])
     
     QObject::connect(coreThread, &QThread::started, squawk, &Core::Squawk::start);
     QObject::connect(&app, &QApplication::aboutToQuit, squawk, &Core::Squawk::stop);
+    QObject::connect(&app, &QApplication::aboutToQuit, &w, &QMainWindow::close);
     QObject::connect(squawk, &Core::Squawk::quit, coreThread, &QThread::quit);
     QObject::connect(coreThread, &QThread::finished, squawk, &Core::Squawk::deleteLater);
     
@@ -141,10 +142,14 @@ int main(int argc, char *argv[])
     QObject::connect(squawk, &Core::Squawk::responseVCard, &w, &Squawk::responseVCard);
     
     coreThread->start();
+    w.readSettings();
 
     int result = app.exec();
+    
+    w.writeSettings();
     coreThread->wait(500);      //TODO hate doing that but settings for some reason don't get saved to the disk
     
+    
     return result;
 }
 
diff --git a/ui/models/roster.cpp b/ui/models/roster.cpp
index 903a3a8..97aa192 100644
--- a/ui/models/roster.cpp
+++ b/ui/models/roster.cpp
@@ -310,9 +310,6 @@ Qt::ItemFlags Models::Roster::flags(const QModelIndex& index) const
 int Models::Roster::rowCount (const QModelIndex& parent) const
 {
     Item *parentItem;
-    if (parent.column() > 0) {
-        return 0;
-    }
     
     if (!parent.isValid()) {
         parentItem = root;
@@ -966,3 +963,29 @@ Models::Account * Models::Roster::getAccount(const QString& name)
 {
     return accounts.find(name)->second;
 }
+
+QModelIndex Models::Roster::getAccountIndex(const QString& name)
+{
+    std::map<QString, Account*>::const_iterator itr = accounts.find(name);
+    if (itr == accounts.end()) {
+        return QModelIndex();
+    } else {
+        return index(itr->second->row(), 0, QModelIndex());
+    }
+}
+
+QModelIndex Models::Roster::getGroupIndex(const QString& account, const QString& name)
+{
+    std::map<QString, Account*>::const_iterator itr = accounts.find(account);
+    if (itr == accounts.end()) {
+        return QModelIndex();
+    } else {
+        std::map<ElId, Group*>::const_iterator gItr = groups.find({account, name});
+        if (gItr == groups.end()) {
+            return QModelIndex();
+        } else {
+            QModelIndex accIndex = index(itr->second->row(), 0, QModelIndex());
+            return index(gItr->second->row(), 0, accIndex);
+        }
+    }
+}
diff --git a/ui/models/roster.h b/ui/models/roster.h
index 07ab495..a2ffdc2 100644
--- a/ui/models/roster.h
+++ b/ui/models/roster.h
@@ -75,6 +75,8 @@ public:
     bool groupHasContact(const QString& account, const QString& group, const QString& contactJID) const;
     QString getContactIconPath(const QString& account, const QString& jid);
     Account* getAccount(const QString& name);
+    QModelIndex getAccountIndex(const QString& name);
+    QModelIndex getGroupIndex(const QString& account, const QString& name);
     
     Accounts* accountsModel;
     
diff --git a/ui/squawk.cpp b/ui/squawk.cpp
index 167b8ed..ce648ba 100644
--- a/ui/squawk.cpp
+++ b/ui/squawk.cpp
@@ -53,6 +53,7 @@ Squawk::Squawk(QWidget *parent) :
     connect(m_ui->comboBox, qOverload<int>(&QComboBox::activated), this, &Squawk::onComboboxActivated);
     connect(m_ui->roster, &QTreeView::doubleClicked, this, &Squawk::onRosterItemDoubleClicked);
     connect(m_ui->roster, &QTreeView::customContextMenuRequested, this, &Squawk::onRosterContextMenu);
+    connect(m_ui->roster, &QTreeView::collapsed, this, &Squawk::onItemCollepsed);
     
     connect(rosterModel.accountsModel, &Models::Accounts::sizeChanged, this, &Squawk::onAccountsSizeChanged);
     //m_ui->mainToolBar->addWidget(m_ui->comboBox);
@@ -205,11 +206,40 @@ void Squawk::changeAccount(const QString& account, const QMap<QString, QVariant>
 void Squawk::addContact(const QString& account, const QString& jid, const QString& group, const QMap<QString, QVariant>& data)
 {
     rosterModel.addContact(account, jid, group, data);
+        
+    QSettings settings;
+    settings.beginGroup("ui");
+    settings.beginGroup("roster");
+    settings.beginGroup(account);
+    if (settings.value("expanded", false).toBool()) {
+        QModelIndex ind = rosterModel.getAccountIndex(account);
+        qDebug() << "expanding account " << ind.data();
+        m_ui->roster->expand(ind);
+    }
+    settings.endGroup();
+    settings.endGroup();
+    settings.endGroup();
 }
 
 void Squawk::addGroup(const QString& account, const QString& name)
 {
     rosterModel.addGroup(account, name);
+    
+    QSettings settings;
+    settings.beginGroup("ui");
+    settings.beginGroup("roster");
+    settings.beginGroup(account);
+    if (settings.value("expanded", false).toBool()) {
+        QModelIndex ind = rosterModel.getAccountIndex(account);
+        qDebug() << "expanding account " << ind.data();
+        m_ui->roster->expand(ind);
+        if (settings.value(name + "/expanded", false).toBool()) {
+            m_ui->roster->expand(rosterModel.getGroupIndex(account, name));
+        }
+    }
+    settings.endGroup();
+    settings.endGroup();
+    settings.endGroup();
 }
 
 void Squawk::removeGroup(const QString& account, const QString& name)
@@ -813,3 +843,95 @@ void Squawk::onVCardSave(const Shared::VCard& card, const QString& account)
     
     widget->deleteLater();
 }
+
+void Squawk::readSettings()
+{
+    QSettings settings;
+    settings.beginGroup("ui");
+    settings.beginGroup("window");
+    if (settings.contains("geometry")) {
+        restoreGeometry(settings.value("geometry").toByteArray());
+    }
+    if (settings.contains("state")) {
+        restoreState(settings.value("state").toByteArray());
+    }
+    settings.endGroup();
+    
+    if (settings.contains("availability")) {
+        int avail = settings.value("availability").toInt();
+        m_ui->comboBox->setCurrentIndex(avail);
+        emit stateChanged(avail);
+        
+        int size = settings.beginReadArray("connectedAccounts");
+        for (int i = 0; i < size; ++i) {
+            settings.setArrayIndex(i);
+            emit connectAccount(settings.value("name").toString());     //TODO  this is actually not needed, stateChanged event already connects everything you have
+        }                                                               //      need to fix that
+        settings.endArray();
+    }
+    
+    settings.endGroup();
+}
+
+void Squawk::writeSettings()
+{
+    QSettings settings;
+    settings.beginGroup("ui");
+    settings.beginGroup("window");
+    settings.setValue("geometry", saveGeometry());
+    settings.setValue("state", saveState());
+    settings.endGroup();
+    
+    settings.setValue("availability", m_ui->comboBox->currentIndex());
+    settings.beginWriteArray("connectedAccounts");
+    int size = rosterModel.accountsModel->rowCount(QModelIndex());
+    for (int i = 0; i < size; ++i) {
+        Models::Account* acc = rosterModel.accountsModel->getAccount(i);
+        if (acc->getState() != Shared::disconnected) {
+            settings.setArrayIndex(i);
+            settings.setValue("name", acc->getName());
+        }
+    }
+    settings.endArray();
+    
+    settings.remove("roster");
+    settings.beginGroup("roster");
+    for (int i = 0; i < size; ++i) {
+        QModelIndex acc = rosterModel.index(i, 0, QModelIndex());
+        Models::Account* account = rosterModel.accountsModel->getAccount(i);
+        QString accName = account->getName();
+        settings.beginGroup(accName);
+        
+        settings.setValue("expanded", m_ui->roster->isExpanded(acc));
+        std::deque<QString> groups = rosterModel.groupList(accName);
+        for (const QString& groupName : groups) {
+            settings.beginGroup(groupName);
+            QModelIndex gIndex = rosterModel.getGroupIndex(accName, groupName);
+            settings.setValue("expanded", m_ui->roster->isExpanded(gIndex));
+            settings.endGroup();
+        }
+        
+        settings.endGroup();
+    }
+    settings.endGroup();
+    settings.endGroup();
+}
+
+void Squawk::onItemCollepsed(const QModelIndex& index)
+{
+    QSettings settings;
+    Models::Item* item = static_cast<Models::Item*>(index.internalPointer());
+    switch (item->type) {
+        case Models::Item::account:
+            settings.setValue("ui/roster/" + item->getName() + "/expanded", false);
+            break;
+        case Models::Item::group: {
+            QModelIndex accInd = rosterModel.parent(index);
+            Models::Account* account = rosterModel.accountsModel->getAccount(accInd.row());
+            settings.setValue("ui/roster/" + account->getName() + "/" + item->getName() + "/expanded", false);
+        }
+            break;
+        default:
+            break;
+    }
+}
diff --git a/ui/squawk.h b/ui/squawk.h
index b464fd2..f0b21c7 100644
--- a/ui/squawk.h
+++ b/ui/squawk.h
@@ -23,6 +23,8 @@
 #include <QScopedPointer>
 #include <QCloseEvent>
 #include <QtDBus/QDBusInterface>
+#include <QSettings>
+
 #include <deque>
 #include <map>
 #include <set>
@@ -50,6 +52,9 @@ public:
     explicit Squawk(QWidget *parent = nullptr);
     ~Squawk() override;
     
+    void readSettings();
+    void writeSettings();
+    
 signals:
     void newAccountRequest(const QMap<QString, QVariant>&);
     void modifyAccountRequest(const QString&, const QMap<QString, QVariant>&);
@@ -101,6 +106,7 @@ public slots:
     void fileError(const QString& messageId, const QString& error);
     void fileProgress(const QString& messageId, qreal value);
     void responseVCard(const QString& jid, const Shared::VCard& card);
+    void onItemCollepsed(const QModelIndex& index);
     
 private:
     typedef std::map<Models::Roster::ElId, Conversation*> Conversations;

From efc90e18c3bd47cb4e088ebc6a65fabdd8418b5a Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Wed, 25 Dec 2019 14:11:29 +0300
Subject: [PATCH 036/281] verion bump

---
 packaging/Archlinux/PKGBUILD | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/packaging/Archlinux/PKGBUILD b/packaging/Archlinux/PKGBUILD
index aec6d05..923a7a0 100644
--- a/packaging/Archlinux/PKGBUILD
+++ b/packaging/Archlinux/PKGBUILD
@@ -1,6 +1,6 @@
 # Maintainer: Yury Gubich <blue@macaw.me>
 pkgname=squawk
-pkgver=0.1.1
+pkgver=0.1.2
 pkgrel=1
 pkgdesc="An XMPP desktop messenger, written on qt"
 arch=('i686' 'x86_64')
@@ -9,7 +9,7 @@ license=('GPL3')
 depends=('hicolor-icon-theme' 'desktop-file-utils' 'lmdb' 'qxmpp>=1.1.0')
 makedepends=('cmake>=3.3' 'imagemagick' 'qt5-tools')
 source=("$pkgname-$pkgver.tar.gz")
-sha256sums=('d0448f2fdb321e31a40c08b77adc951bafe8d1c271f70d6ffc80fb17cef670cf')
+sha256sums=('9f89f41e52047c0e687a0a1b766331c19747c11f8f329e540402eaddbca4b677')
 build() {
         cd "$srcdir/squawk"
         cmake . -D CMAKE_INSTALL_PREFIX=/usr -D CMAKE_BUILD_TYPE=Release

From 55703c200778c8729a2f1fe912d5b80352e97ed0 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Mon, 30 Dec 2019 23:22:04 +0300
Subject: [PATCH 037/281] muc participant avatars

---
 core/account.cpp          |  92 ++++-----------
 core/account.h            |   4 +-
 core/archive.cpp          | 234 +++++++++++++++++++++-----------------
 core/archive.h            |  48 ++++++--
 core/conference.cpp       | 111 +++++++++++++++++-
 core/conference.h         |   4 +
 core/contact.cpp          |  30 +++++
 core/contact.h            |   1 +
 core/rosteritem.cpp       |  96 ++++++++++++----
 core/rosteritem.h         |  16 ++-
 ui/models/participant.cpp |  55 ++++++++-
 ui/models/participant.h   |  10 ++
 ui/models/roster.cpp      |  14 ++-
 ui/squawk.cpp             |   1 -
 ui/utils/message.cpp      |  11 +-
 15 files changed, 506 insertions(+), 221 deletions(-)

diff --git a/core/account.cpp b/core/account.cpp
index 9811dd2..a7f4801 100644
--- a/core/account.cpp
+++ b/core/account.cpp
@@ -349,13 +349,15 @@ void Core::Account::addedAccount(const QString& jid)
             {"state", state}
         });
         
-        if (contact->hasAvatar()) {
-            if (!contact->isAvatarAutoGenerated()) {
+        Archive::AvatarInfo info;
+        bool hasAvatar = contact->readAvatarInfo(info);
+        if (hasAvatar) {
+            if (info.autogenerated) {
                 cData.insert("avatarState", static_cast<uint>(Shared::Avatar::valid));
             } else {
                 cData.insert("avatarState", static_cast<uint>(Shared::Avatar::autocreated));
             }
-            cData.insert("avatarPath", contact->avatarPath());
+            cData.insert("avatarPath", contact->avatarPath() + "." + info.type);
         } else {
             cData.insert("avatarState", static_cast<uint>(Shared::Avatar::empty));
             cData.insert("avatarPath", "");
@@ -382,6 +384,7 @@ void Core::Account::handleNewRosterItem(Core::RosterItem* contact)
     QObject::connect(contact, &RosterItem::historyResponse, this, &Account::onContactHistoryResponse);
     QObject::connect(contact, &RosterItem::nameChanged, this, &Account::onContactNameChanged);
     QObject::connect(contact, &RosterItem::avatarChanged, this, &Account::onContactAvatarChanged);
+    QObject::connect(contact, &RosterItem::requestVCard, this, &Account::requestVCard);
 }
 
 void Core::Account::handleNewContact(Core::Contact* contact)
@@ -440,42 +443,9 @@ void Core::Account::onPresenceReceived(const QXmppPresence& p_presence)
         }
     } else {
         if (pendingVCardRequests.find(jid) == pendingVCardRequests.end()) {
-            RosterItem* item = 0;
             std::map<QString, Contact*>::const_iterator itr = contacts.find(jid);
             if (itr != contacts.end()) {
-                item = itr->second;
-            } else {
-                std::map<QString, Conference*>::const_iterator citr = conferences.find(jid);
-                if (citr != conferences.end()) {
-                    item = citr->second;
-                }
-            }
-            
-            if (item != 0) {
-                switch (p_presence.vCardUpdateType()) {
-                    case QXmppPresence::VCardUpdateNone:            //this presence has nothing to do with photo
-                        break;
-                    case QXmppPresence::VCardUpdateNotReady:        //let's say the photo didn't change here
-                        break;
-                    case QXmppPresence::VCardUpdateNoPhoto:         //there is no photo, need to drop if any
-                        if (!item->hasAvatar() || (item->hasAvatar() && !item->isAvatarAutoGenerated())) {
-                            item->setAutoGeneratedAvatar();
-                        }
-                        break;
-                    case QXmppPresence::VCardUpdateValidPhoto:      //there is a photo, need to load
-                        if (item->hasAvatar()) {
-                            if (item->isAvatarAutoGenerated()) {
-                                requestVCard(jid);
-                            } else {
-                                if (item->avatarHash() != p_presence.photoHash()) {
-                                    requestVCard(jid);
-                                }
-                            }
-                        } else {
-                            requestVCard(jid);
-                        }
-                        break;
-                }
+                itr->second->handlePresence(p_presence);
             }
         }
     }
@@ -1333,13 +1303,15 @@ void Core::Account::addNewRoom(const QString& jid, const QString& nick, const QS
         {"name", conf->getName()}
     };
     
-    if (conf->hasAvatar()) {
-        if (!conf->isAvatarAutoGenerated()) {
+    Archive::AvatarInfo info;
+    bool hasAvatar = conf->readAvatarInfo(info);
+    if (hasAvatar) {
+        if (info.autogenerated) {
             cData.insert("avatarState", static_cast<uint>(Shared::Avatar::valid));
         } else {
             cData.insert("avatarState", static_cast<uint>(Shared::Avatar::autocreated));
         }
-        cData.insert("avatarPath", conf->avatarPath());
+        cData.insert("avatarPath", conf->avatarPath() + "." + info.type);
     } else {
         cData.insert("avatarState", static_cast<uint>(Shared::Avatar::empty));
         cData.insert("avatarPath", "");
@@ -1406,8 +1378,14 @@ void Core::Account::renameContactRequest(const QString& jid, const QString& newN
 
 void Core::Account::onVCardReceived(const QXmppVCardIq& card)
 {
-    QString jid = card.from();
-    pendingVCardRequests.erase(jid);
+    QString id = card.from();
+    QStringList comps = id.split("/");
+    QString jid = comps.front();
+    QString resource("");
+    if (comps.size() > 1) {
+        resource = comps.back();
+    }
+    pendingVCardRequests.erase(id);
     RosterItem* item = 0;
     
     std::map<QString, Contact*>::const_iterator contItr = contacts.find(jid);
@@ -1427,35 +1405,8 @@ void Core::Account::onVCardReceived(const QXmppVCardIq& card)
         item = contItr->second;
     }
     
-    QByteArray ava = card.photo();
+    Shared::VCard vCard = item->handleResponseVCard(card, resource);
     
-    if (ava.size() > 0) {
-        item->setAvatar(ava);
-    } else {
-        if (!item->hasAvatar() || !item->isAvatarAutoGenerated()) {
-            item->setAutoGeneratedAvatar();
-        }
-    }
-    
-    Shared::VCard vCard;
-    initializeVCard(vCard, card);
-    
-    if (item->hasAvatar()) {
-        if (!item->isAvatarAutoGenerated()) {
-            vCard.setAvatarType(Shared::Avatar::valid);
-        } else {
-            vCard.setAvatarType(Shared::Avatar::autocreated);
-        }
-        vCard.setAvatarPath(item->avatarPath());
-    } else {
-        vCard.setAvatarType(Shared::Avatar::empty);
-    }
-    
-    QMap<QString, QVariant> cd = {
-        {"avatarState", static_cast<quint8>(vCard.getAvatarType())},
-        {"avatarPath", vCard.getAvatarPath()}
-    };
-    emit changeContact(jid, cd);
     emit receivedVCard(jid, vCard);
 }
 
@@ -1577,6 +1528,7 @@ void Core::Account::onContactAvatarChanged(Shared::Avatar type, const QString& p
 void Core::Account::requestVCard(const QString& jid)
 {
     if (pendingVCardRequests.find(jid) == pendingVCardRequests.end()) {
+        qDebug() << "requesting vCard" << jid;
         if (jid == getLogin() + "@" + getServer()) {
             if (!ownVCardRequestInProgress) {
                 vm->requestClientVCard();
diff --git a/core/account.h b/core/account.h
index ff77455..8978800 100644
--- a/core/account.h
+++ b/core/account.h
@@ -92,9 +92,11 @@ public:
     void setRoomAutoJoin(const QString& jid, bool joined);
     void removeRoomRequest(const QString& jid);
     void addRoomRequest(const QString& jid, const QString& nick, const QString& password, bool autoJoin);
-    void requestVCard(const QString& jid);
     void uploadVCard(const Shared::VCard& card);
     
+public slots:
+    void requestVCard(const QString& jid);
+    
 signals:
     void changed(const QMap<QString, QVariant>& data);
     void connectionStateChanged(int);
diff --git a/core/archive.cpp b/core/archive.cpp
index 9b7c9eb..0d3e7c4 100644
--- a/core/archive.cpp
+++ b/core/archive.cpp
@@ -33,10 +33,7 @@ Core::Archive::Archive(const QString& p_jid, QObject* parent):
     main(),
     order(),
     stats(),
-    hasAvatar(false),
-    avatarAutoGenerated(false),
-    avatarHash(),
-    avatarType()
+    avatars()
 {
 }
 
@@ -60,7 +57,7 @@ void Core::Archive::open(const QString& account)
             }
         }
         
-        mdb_env_set_maxdbs(environment, 4);
+        mdb_env_set_maxdbs(environment, 5);
         mdb_env_set_mapsize(environment, 512UL * 1024UL * 1024UL);
         mdb_env_open(environment, path.toStdString().c_str(), 0, 0664);
         
@@ -69,43 +66,25 @@ void Core::Archive::open(const QString& account)
         mdb_dbi_open(txn, "main", MDB_CREATE, &main);
         mdb_dbi_open(txn, "order", MDB_CREATE | MDB_INTEGERKEY, &order);
         mdb_dbi_open(txn, "stats", MDB_CREATE, &stats);
+        mdb_dbi_open(txn, "avatars", MDB_CREATE, &avatars);
         mdb_txn_commit(txn);
         
-        mdb_txn_begin(environment, NULL, 0, &txn);
+        mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn);
         try {
             fromTheBeginning = getStatBoolValue("beginning", txn);
         } catch (const NotFound& e) {
             fromTheBeginning = false;
         }
-        try {
-            hasAvatar = getStatBoolValue("hasAvatar", txn);
-        } catch (const NotFound& e) {
-            hasAvatar = false;
-        }
-        if (hasAvatar) {
-            try {
-                avatarAutoGenerated = getStatBoolValue("avatarAutoGenerated", txn);
-            } catch (const NotFound& e) {
-                avatarAutoGenerated = false;
-            }
-            
-            avatarType = getStatStringValue("avatarType", txn).c_str();
-            if (avatarAutoGenerated) {
-                avatarHash = "";
-            } else {
-                avatarHash = getStatStringValue("avatarHash", txn).c_str();
-            }
-        } else {
-            avatarAutoGenerated = false;
-            avatarHash = "";
-            avatarType = "";
-        }
+        
+        std::string sJid = jid.toStdString();
+        AvatarInfo info;
+        bool hasAvatar = readAvatarInfo(info, sJid, txn);
         mdb_txn_abort(txn);
         
         if (hasAvatar) {
-            QFile ava(path + "/avatar." + avatarType);
+            QFile ava(path + "/" + sJid.c_str() + "." + info.type);
             if (!ava.exists()) {
-                bool success = dropAvatar();
+                bool success = dropAvatar(sJid);
                 if (!success) {
                     qDebug() << "error opening archive" << jid << "for account" << account 
                     << ". There is supposed to be avatar but the file doesn't exist, couldn't even drop it, it surely will lead to an error";
@@ -120,6 +99,7 @@ void Core::Archive::open(const QString& account)
 void Core::Archive::close()
 {
     if (opened) {
+        mdb_dbi_close(environment, avatars);
         mdb_dbi_close(environment, stats);
         mdb_dbi_close(environment, order);
         mdb_dbi_close(environment, main);
@@ -518,8 +498,9 @@ bool Core::Archive::getStatBoolValue(const std::string& id, MDB_txn* txn)
     if (rc == MDB_NOTFOUND) {
         throw NotFound(id, jid.toStdString());
     } else if (rc) {
-        qDebug() << "error retrieving" << id.c_str() << "from stats db of" << jid << mdb_strerror(rc);
-        throw 15;            //TODO proper exception
+        std::string err(mdb_strerror(rc));
+        qDebug() << "error retrieving" << id.c_str() << "from stats db of" << jid << err.c_str();
+        throw Unknown(jid.toStdString(), err);
     } else {
         uint8_t value = *(uint8_t*)(lmdbData.mv_data);
         bool is;
@@ -546,8 +527,9 @@ std::string Core::Archive::getStatStringValue(const std::string& id, MDB_txn* tx
     if (rc == MDB_NOTFOUND) {
         throw NotFound(id, jid.toStdString());
     } else if (rc) {
-        qDebug() << "error retrieving" << id.c_str() << "from stats db of" << jid << mdb_strerror(rc);
-        throw 15;            //TODO proper exception
+        std::string err(mdb_strerror(rc));
+        qDebug() << "error retrieving" << id.c_str() << "from stats db of" << jid << err.c_str();
+        throw Unknown(jid.toStdString(), err);
     } else {
         std::string value((char*)lmdbData.mv_data, lmdbData.mv_size);
         return value;
@@ -588,74 +570,38 @@ bool Core::Archive::setStatValue(const std::string& id, const std::string& value
     return true;
 }
 
-bool Core::Archive::getHasAvatar() const
-{
-    if (!opened) {
-        throw Closed("getHasAvatar", jid.toStdString());
-    }
-    
-    return hasAvatar;
-}
-
-bool Core::Archive::getAutoAvatar() const
-{
-    if (!opened) {
-        throw Closed("getAutoAvatar", jid.toStdString());
-    }
-    
-    return avatarAutoGenerated;
-}
-
-QString Core::Archive::getAvatarHash() const
-{
-    if (!opened) {
-        throw Closed("getAvatarHash", jid.toStdString());
-    }
-    
-    return avatarHash;
-}
-
-QString Core::Archive::getAvatarType() const
-{
-    if (!opened) {
-        throw Closed("getAvatarType", jid.toStdString());
-    }
-    
-    return avatarType;
-}
-
-bool Core::Archive::dropAvatar()
+bool Core::Archive::dropAvatar(const std::string& resource)
 {
     MDB_txn *txn;
+    MDB_val lmdbKey;
     mdb_txn_begin(environment, NULL, 0, &txn);
-    bool success = setStatValue("hasAvatar", false, txn);
-    success = success && setStatValue("avatarAutoGenerated", false, txn);
-    success = success && setStatValue("avatarHash", "", txn);
-    success = success && setStatValue("avatarType", "", txn);
-    if (!success) {
+    lmdbKey.mv_size = resource.size();
+    lmdbKey.mv_data = (char*)resource.c_str();
+    int rc = mdb_del(txn, avatars, &lmdbKey, NULL);
+    if (rc != 0) {
         mdb_txn_abort(txn);
         return false;
     } else {
-        hasAvatar = false;
-        avatarAutoGenerated = false;
-        avatarHash = "";
-        avatarType = "";
         mdb_txn_commit(txn);
         return true;
     }
 }
 
-bool Core::Archive::setAvatar(const QByteArray& data, bool generated)
+bool Core::Archive::setAvatar(const QByteArray& data, bool generated, const QString& resource)
 {
     if (!opened) {
         throw Closed("setAvatar", jid.toStdString());
     }
     
+    AvatarInfo oldInfo;
+    bool hasAvatar = readAvatarInfo(oldInfo, resource);
+    std::string res = resource.size() == 0 ? jid.toStdString() : resource.toStdString();
+    
     if (data.size() == 0) {
         if (!hasAvatar) {
             return false;
         } else {
-            return dropAvatar();
+            return dropAvatar(res);
         }
     } else {
         const char* cep;
@@ -664,14 +610,14 @@ bool Core::Archive::setAvatar(const QByteArray& data, bool generated)
         bool needToRemoveOld = false;
         QCryptographicHash hash(QCryptographicHash::Sha1);
         hash.addData(data);
-        QString newHash(hash.result());
+        QByteArray newHash(hash.result());
         if (hasAvatar) {
-            if (!generated && !avatarAutoGenerated && avatarHash == newHash) {
+            if (!generated && !oldInfo.autogenerated && oldInfo.hash == newHash) {
                 return false;
             }
-            QFile oldAvatar(currentPath + "/avatar." + avatarType);
+            QFile oldAvatar(currentPath + "/" + res.c_str() + "." + oldInfo.type);
             if (oldAvatar.exists()) {
-                if (oldAvatar.rename(currentPath + "/avatar." + avatarType + ".bak")) {
+                if (oldAvatar.rename(currentPath + "/" + res.c_str() + "." + oldInfo.type + ".bak")) {
                     needToRemoveOld = true;
                 } else {
                     qDebug() << "Can't change avatar: couldn't get rid of the old avatar" << oldAvatar.fileName();
@@ -682,33 +628,36 @@ bool Core::Archive::setAvatar(const QByteArray& data, bool generated)
         QMimeDatabase db;
         QMimeType type = db.mimeTypeForData(data);
         QString ext = type.preferredSuffix();
-        QFile newAvatar(currentPath + "/avatar." + ext);
+        QFile newAvatar(currentPath + "/" + res.c_str() + "." + ext);
         if (newAvatar.open(QFile::WriteOnly)) {
             newAvatar.write(data);
             newAvatar.close();
             
             MDB_txn *txn;
             mdb_txn_begin(environment, NULL, 0, &txn);
-            bool success = setStatValue("hasAvatar", true, txn);
-            success = success && setStatValue("avatarAutoGenerated", generated, txn);
-            success = success && setStatValue("avatarHash", newHash.toStdString(), txn);
-            success = success && setStatValue("avatarType", ext.toStdString(), txn);
-            if (!success) {
+            
+            MDB_val lmdbKey, lmdbData;
+            QByteArray value;
+            AvatarInfo newInfo(ext, newHash, generated);
+            newInfo.serialize(&value);
+            lmdbKey.mv_size = res.size();
+            lmdbKey.mv_data = (char*)res.c_str();
+            lmdbData.mv_size = value.size();
+            lmdbData.mv_data = value.data();
+            int rc = mdb_put(txn, avatars, &lmdbKey, &lmdbData, 0);
+            
+            if (rc != 0) {
                 qDebug() << "Can't change avatar: couldn't store changes to database for" << newAvatar.fileName() << "rolling back to the previous state";
                 if (needToRemoveOld) {
-                    QFile oldAvatar(currentPath + "/avatar." + avatarType + ".bak");
-                    oldAvatar.rename(currentPath + "/avatar." + avatarType);
+                    QFile oldAvatar(currentPath + "/" + res.c_str() + "." + oldInfo.type + ".bak");
+                    oldAvatar.rename(currentPath + "/" + res.c_str() + "." + oldInfo.type);
                 }
                 mdb_txn_abort(txn);
                 return false;
             } else {
-                hasAvatar = true;
-                avatarAutoGenerated = generated;
-                avatarHash = newHash;
-                avatarType = ext;
                 mdb_txn_commit(txn);
                 if (needToRemoveOld) {
-                    QFile oldAvatar(currentPath + "/avatar." + avatarType + ".bak");
+                    QFile oldAvatar(currentPath + "/" + res.c_str() + "." + oldInfo.type + ".bak");
                     oldAvatar.remove();
                 }
                 return true;
@@ -716,10 +665,91 @@ bool Core::Archive::setAvatar(const QByteArray& data, bool generated)
         } else {
             qDebug() << "Can't change avatar: cant open file to write" << newAvatar.fileName() << "rolling back to the previous state";
             if (needToRemoveOld) {
-                QFile oldAvatar(currentPath + "/avatar." + avatarType + ".bak");
-                oldAvatar.rename(currentPath + "/avatar." + avatarType);
+                QFile oldAvatar(currentPath + "/" + res.c_str() + "." + oldInfo.type + ".bak");
+                oldAvatar.rename(currentPath + "/" + res.c_str() + "." + oldInfo.type);
             }
             return false;
         }
     }
 }
+
+bool Core::Archive::readAvatarInfo(Core::Archive::AvatarInfo& target, const QString& resource) const
+{
+    if (!opened) {
+        throw Closed("readAvatarInfo", jid.toStdString());
+    }
+    std::string res = resource.size() == 0 ? jid.toStdString() : resource.toStdString();
+    
+    MDB_txn *txn;
+    mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn);
+    
+    try {
+        bool success = readAvatarInfo(target, res, txn);
+        mdb_txn_abort(txn);
+        return success;
+    } catch (const std::exception& e) {
+        mdb_txn_abort(txn);
+        throw e;
+    }
+    
+}
+
+bool Core::Archive::readAvatarInfo(Core::Archive::AvatarInfo& target, const std::string& res, MDB_txn* txn) const
+{
+    MDB_val lmdbKey, lmdbData;
+    lmdbKey.mv_size = res.size();
+    lmdbKey.mv_data = (char*)res.c_str();
+    
+    int rc;
+    rc = mdb_get(txn, avatars, &lmdbKey, &lmdbData);
+    if (rc == MDB_NOTFOUND) {
+        return false;
+    } else if (rc) {
+        std::string err(mdb_strerror(rc));
+        qDebug() << "error reading avatar info for" << res.c_str() << "resource of" << jid << err.c_str();
+        throw Unknown(jid.toStdString(), err);
+    } else {
+        target.deserialize((char*)lmdbData.mv_data, lmdbData.mv_size);
+        return true;
+    }
+}
+
+Core::Archive::AvatarInfo Core::Archive::getAvatarInfo(const QString& resource) const
+{
+    if (!opened) {
+        throw Closed("readAvatarInfo", jid.toStdString());
+    }
+    
+    AvatarInfo info;
+    bool success = readAvatarInfo(info, resource);
+    if (success) {
+        return info;
+    } else {
+        throw NoAvatar(jid.toStdString(), resource.toStdString());
+    }
+}
+
+Core::Archive::AvatarInfo::AvatarInfo():
+type(),
+hash(),
+autogenerated(false) {}
+
+Core::Archive::AvatarInfo::AvatarInfo(const QString& p_type, const QByteArray& p_hash, bool p_autogenerated):
+type(p_type),
+hash(p_hash),
+autogenerated(p_autogenerated) {}
+
+void Core::Archive::AvatarInfo::deserialize(char* pointer, uint32_t size)
+{
+    QByteArray data = QByteArray::fromRawData(pointer, size);
+    QDataStream in(&data, QIODevice::ReadOnly);
+    
+    in >> type >> hash >> autogenerated;
+}
+
+void Core::Archive::AvatarInfo::serialize(QByteArray* ba) const
+{
+    QDataStream out(ba, QIODevice::WriteOnly);
+    
+    out << type << hash << autogenerated;
+}
diff --git a/core/archive.h b/core/archive.h
index e94fac8..da4a96f 100644
--- a/core/archive.h
+++ b/core/archive.h
@@ -35,6 +35,8 @@ class Archive : public QObject
 {
     Q_OBJECT
 public:
+    class AvatarInfo;
+    
     Archive(const QString& jid, QObject* parent = 0);
     ~Archive();
     
@@ -53,11 +55,9 @@ public:
     std::list<Shared::Message> getBefore(int count, const QString& id);
     bool isFromTheBeginning();
     void setFromTheBeginning(bool is);
-    bool getHasAvatar() const;
-    bool getAutoAvatar() const;
-    QString getAvatarHash() const;
-    QString getAvatarType() const;
-    bool setAvatar(const QByteArray& data, bool generated = false);
+    bool setAvatar(const QByteArray& data, bool generated = false, const QString& resource = "");
+    AvatarInfo getAvatarInfo(const QString& resource = "") const;
+    bool readAvatarInfo(AvatarInfo& target, const QString& resource = "") const;
     
 public:
     const QString jid;
@@ -121,6 +121,22 @@ public:
         std::string key;
     };
     
+    class NoAvatar: 
+    public Utils::Exception
+    {
+    public:
+        NoAvatar(const std::string& el, const std::string& res):Exception(), element(el), resource(res){
+            if (resource.size() == 0) {
+                resource = "for himself";
+            }
+        }
+        
+        std::string getMessage() const{return "Element " + element + " has no avatar for " + resource ;}
+    private:
+        std::string element;
+        std::string resource;
+    };
+    
     class Unknown:
     public Utils::Exception
     {
@@ -133,6 +149,20 @@ public:
         std::string msg;
     };
     
+    
+    class AvatarInfo {
+    public:
+        AvatarInfo();
+        AvatarInfo(const QString& type, const QByteArray& hash, bool autogenerated);
+        
+        void deserialize(char* pointer, uint32_t size);
+        void serialize(QByteArray* ba) const;
+        
+        QString type;
+        QByteArray hash;
+        bool autogenerated;
+    };
+    
 private:
     bool opened;
     bool fromTheBeginning;
@@ -140,19 +170,17 @@ private:
     MDB_dbi main;
     MDB_dbi order;
     MDB_dbi stats;
-    bool hasAvatar;
-    bool avatarAutoGenerated;
-    QString avatarHash;
-    QString avatarType;
+    MDB_dbi avatars;
     
     bool getStatBoolValue(const std::string& id, MDB_txn* txn);
     std::string getStatStringValue(const std::string& id, MDB_txn* txn);
     
     bool setStatValue(const std::string& id, bool value, MDB_txn* txn);
     bool setStatValue(const std::string& id, const std::string& value, MDB_txn* txn);
+    bool readAvatarInfo(AvatarInfo& target, const std::string& res, MDB_txn* txn) const;
     void printOrder();
     void printKeys();
-    bool dropAvatar();
+    bool dropAvatar(const std::string& resource);
 };
 
 }
diff --git a/core/conference.cpp b/core/conference.cpp
index 56a1e8f..2d10273 100644
--- a/core/conference.cpp
+++ b/core/conference.cpp
@@ -143,14 +143,31 @@ void Core::Conference::onRoomParticipantAdded(const QString& p_name)
             lastInteraction = QDateTime::currentDateTime();
         }
         QXmppMucItem mi = pres.mucItem();
+        Archive::AvatarInfo info;
+        bool hasAvatar = readAvatarInfo(info, resource);
         
-        emit addParticipant(resource, {
+        QMap<QString, QVariant> cData = {
             {"lastActivity", lastInteraction},
             {"availability", pres.availableStatusType()},
             {"status", pres.statusText()},
             {"affiliation", mi.affiliation()},
             {"role", mi.role()}
-        });
+        };
+        
+        if (hasAvatar) {
+            if (info.autogenerated) {
+                cData.insert("avatarState", static_cast<uint>(Shared::Avatar::valid));
+            } else {
+                cData.insert("avatarState", static_cast<uint>(Shared::Avatar::autocreated));
+            }
+            cData.insert("avatarPath", avatarPath(resource) + "." + info.type);
+        } else {
+            cData.insert("avatarState", static_cast<uint>(Shared::Avatar::empty));
+            cData.insert("avatarPath", "");
+            requestVCard(p_name);
+        }
+        
+        emit addParticipant(resource, cData);
     }
 }
 
@@ -167,6 +184,7 @@ void Core::Conference::onRoomParticipantChanged(const QString& p_name)
             lastInteraction = QDateTime::currentDateTime();
         }
         QXmppMucItem mi = pres.mucItem();
+        handlePresence(pres);
         
         emit changeParticipant(resource, {
             {"lastActivity", lastInteraction},
@@ -202,3 +220,92 @@ void Core::Conference::onRoomSubjectChanged(const QString& p_name)
 {
     emit subjectChanged(p_name);
 }
+
+void Core::Conference::handlePresence(const QXmppPresence& pres)
+{
+    QString id = pres.from();
+    QStringList comps = id.split("/");
+    QString jid = comps.front();
+    QString resource("");
+    if (comps.size() > 1) {
+        resource = comps.back();
+    }
+    
+    switch (pres.vCardUpdateType()) {
+        case QXmppPresence::VCardUpdateNone:            //this presence has nothing to do with photo
+            break;
+        case QXmppPresence::VCardUpdateNotReady:        //let's say the photo didn't change here
+            break;
+        case QXmppPresence::VCardUpdateNoPhoto: {       //there is no photo, need to drop if any
+            Archive::AvatarInfo info;
+            bool hasAvatar = readAvatarInfo(info, resource);
+            if (!hasAvatar || !info.autogenerated) {
+                setAutoGeneratedAvatar(resource);
+            }
+        }         
+        break;
+        case QXmppPresence::VCardUpdateValidPhoto:{     //there is a photo, need to load
+            Archive::AvatarInfo info;
+            bool hasAvatar = readAvatarInfo(info, resource);
+            if (hasAvatar) {
+                if (info.autogenerated || info.hash != pres.photoHash()) {
+                    emit requestVCard(id);
+                }
+            } else {
+                emit requestVCard(id);
+            }
+            break;
+        }      
+    }
+}
+
+bool Core::Conference::setAutoGeneratedAvatar(const QString& resource)
+{
+    bool result = RosterItem::setAutoGeneratedAvatar(resource);
+    if (result && resource.size() != 0) {
+        emit changeParticipant(resource, {
+            {"avatarState", static_cast<uint>(Shared::Avatar::autocreated)},
+            {"availability", avatarPath(resource) + ".png"}
+        });
+    }
+    
+    return result;
+}
+
+bool Core::Conference::setAvatar(const QByteArray& data, const QString& resource)
+{
+    bool result = RosterItem::setAvatar(data, resource);
+    if (result && resource.size() != 0) {
+        if (data.size() > 0) {
+            QMimeDatabase db;
+            QMimeType type = db.mimeTypeForData(data);
+            QString ext = type.preferredSuffix();
+            emit changeParticipant(resource, {
+                {"avatarState", static_cast<uint>(Shared::Avatar::autocreated)},
+                {"avatarPath", avatarPath(resource) + "." + ext}
+            });
+        } else {
+            emit changeParticipant(resource, {
+                {"avatarState", static_cast<uint>(Shared::Avatar::empty)},
+                {"avatarPath", ""}
+            });
+        }
+        
+    }
+    
+    return result;
+}
+
+Shared::VCard Core::Conference::handleResponseVCard(const QXmppVCardIq& card, const QString &resource)
+{
+    Shared::VCard result = RosterItem::handleResponseVCard(card, resource);
+    
+    if (resource.size() > 0) {
+        emit changeParticipant(resource, {
+            {"avatarState", static_cast<uint>(result.getAvatarType())},
+            {"avatarPath", result.getAvatarPath()}
+        });
+    }
+    
+    return result;
+}
diff --git a/core/conference.h b/core/conference.h
index 71829b7..c00c472 100644
--- a/core/conference.h
+++ b/core/conference.h
@@ -44,6 +44,10 @@ public:
     
     bool getAutoJoin();
     void setAutoJoin(bool p_autoJoin);
+    void handlePresence(const QXmppPresence & pres) override;
+    bool setAutoGeneratedAvatar(const QString& resource = "") override;
+    bool setAvatar(const QByteArray &data, const QString &resource = "") override;
+    Shared::VCard handleResponseVCard(const QXmppVCardIq & card, const QString &resource) override;
     
 signals:
     void nickChanged(const QString& nick);
diff --git a/core/contact.cpp b/core/contact.cpp
index ba0737e..711c505 100644
--- a/core/contact.cpp
+++ b/core/contact.cpp
@@ -68,3 +68,33 @@ void Core::Contact::setSubscriptionState(Shared::SubscriptionState state)
         emit subscriptionStateChanged(subscriptionState);
     }
 }
+
+void Core::Contact::handlePresence(const QXmppPresence& pres)
+{
+    switch (pres.vCardUpdateType()) {
+        case QXmppPresence::VCardUpdateNone:            //this presence has nothing to do with photo
+            break;
+        case QXmppPresence::VCardUpdateNotReady:        //let's say the photo didn't change here
+            break;
+        case QXmppPresence::VCardUpdateNoPhoto: {       //there is no photo, need to drop if any
+            Archive::AvatarInfo info;
+            bool hasAvatar = readAvatarInfo(info);
+            if (!hasAvatar || !info.autogenerated) {
+                setAutoGeneratedAvatar();
+            }
+        }         
+        break;
+        case QXmppPresence::VCardUpdateValidPhoto:{     //there is a photo, need to load
+            Archive::AvatarInfo info;
+            bool hasAvatar = readAvatarInfo(info);
+            if (hasAvatar) {
+                if (info.autogenerated || info.hash != pres.photoHash()) {
+                    emit requestVCard(jid);
+                }
+            } else {
+                emit requestVCard(jid);
+            }
+            break;
+        }      
+    }
+}
diff --git a/core/contact.h b/core/contact.h
index d4eb4a1..aa81010 100644
--- a/core/contact.h
+++ b/core/contact.h
@@ -38,6 +38,7 @@ public:
     
     void setSubscriptionState(Shared::SubscriptionState state);
     Shared::SubscriptionState getSubscriptionState() const;
+    void handlePresence(const QXmppPresence & pres) override;
 
 signals:
     void groupAdded(const QString& name);
diff --git a/core/rosteritem.cpp b/core/rosteritem.cpp
index ccc3072..a8d48a4 100644
--- a/core/rosteritem.cpp
+++ b/core/rosteritem.cpp
@@ -17,6 +17,7 @@
  */
 
 #include "rosteritem.h"
+#include "account.h"
 
 #include <QDebug>
 
@@ -334,40 +335,30 @@ bool Core::RosterItem::isMuc() const
     return muc;
 }
 
-QString Core::RosterItem::avatarHash() const
-{
-    return archive->getAvatarHash();
-}
-
-bool Core::RosterItem::isAvatarAutoGenerated() const
-{
-    return archive->getAutoAvatar();
-}
-
-QString Core::RosterItem::avatarPath() const
+QString Core::RosterItem::avatarPath(const QString& resource) const
 {
     QString path(QStandardPaths::writableLocation(QStandardPaths::CacheLocation));
-    path += "/" + account + "/" + jid + "/avatar." + archive->getAvatarType();
+    path += "/" + account + "/" + jid + "/" + (resource.size() == 0 ? jid : resource);
     return path;
 }
 
-bool Core::RosterItem::hasAvatar() const
+bool Core::RosterItem::setAvatar(const QByteArray& data, const QString& resource)
 {
-    return archive->getHasAvatar();
-}
-
-void Core::RosterItem::setAvatar(const QByteArray& data)
-{
-    if (archive->setAvatar(data, false)) {
-        if (archive->getHasAvatar()) {
+    bool result = archive->setAvatar(data, false, resource);
+    if (resource.size() == 0 && result) {
+        if (data.size() == 0) {
             emit avatarChanged(Shared::Avatar::empty, "");
         } else {
-            emit avatarChanged(Shared::Avatar::valid, avatarPath());
+            QMimeDatabase db;
+            QMimeType type = db.mimeTypeForData(data);
+            QString ext = type.preferredSuffix();
+            emit avatarChanged(Shared::Avatar::valid, avatarPath(resource) + "." + ext);
         }
     }
+    return result;
 }
 
-void Core::RosterItem::setAutoGeneratedAvatar()
+bool Core::RosterItem::setAutoGeneratedAvatar(const QString& resource)
 {
     QImage image(96, 96, QImage::Format_ARGB32_Premultiplied);
     QPainter painter(&image);
@@ -383,13 +374,68 @@ void Core::RosterItem::setAutoGeneratedAvatar()
     } else {
         painter.setPen(Qt::white);
     }
-    painter.drawText(image.rect(), Qt::AlignCenter | Qt::AlignVCenter, jid.at(0).toUpper());
+    painter.drawText(image.rect(), Qt::AlignCenter | Qt::AlignVCenter, resource.size() == 0 ? jid.at(0).toUpper() : resource.at(0).toUpper());
     QByteArray arr;
     QBuffer stream(&arr);
     stream.open(QBuffer::WriteOnly);
     image.save(&stream, "PNG");
     stream.close();
-    if (archive->setAvatar(arr, true)) {
-        emit avatarChanged(Shared::Avatar::autocreated, avatarPath());
+    bool result = archive->setAvatar(arr, true, resource);
+    if (resource.size() == 0 && result) {
+        emit avatarChanged(Shared::Avatar::autocreated, avatarPath(resource) + ".png");
     }
+    return result;
 }
+
+bool Core::RosterItem::readAvatarInfo(Archive::AvatarInfo& target, const QString& resource) const
+{
+    return archive->readAvatarInfo(target, resource);
+}
+
+Shared::VCard Core::RosterItem::handleResponseVCard(const QXmppVCardIq& card, const QString& resource)
+{
+    Archive::AvatarInfo info;
+    bool hasAvatar = readAvatarInfo(info, resource);
+    
+    QByteArray ava = card.photo();
+    Shared::VCard vCard;
+    initializeVCard(vCard, card);
+    Shared::Avatar type = Shared::Avatar::empty;
+    QString path = "";
+    
+    if (ava.size() > 0) {
+        bool changed = setAvatar(ava, resource);
+        if (changed) {
+            type = Shared::Avatar::valid;
+            QMimeDatabase db;
+            QMimeType type = db.mimeTypeForData(ava);
+            QString ext = type.preferredSuffix();
+            path = avatarPath(resource) + "." + ext;
+        } else if (hasAvatar) {
+            if (info.autogenerated) {
+                type = Shared::Avatar::autocreated;
+                path = avatarPath(resource) + ".png";
+            } else {
+                type = Shared::Avatar::valid;
+                path = avatarPath(resource) + "." + info.type;
+            }
+        }
+    } else {
+        if (!hasAvatar || !info.autogenerated) {
+            setAutoGeneratedAvatar(resource);
+            type = Shared::Avatar::autocreated;
+            path = avatarPath(resource) + ".png";
+        }
+    }
+    
+    
+    vCard.setAvatarType(type);
+    vCard.setAvatarPath(path);
+    
+    if (resource.size() == 0) {
+        emit avatarChanged(vCard.getAvatarType(), vCard.getAvatarPath());
+    }
+    
+    return vCard;
+}
+
diff --git a/core/rosteritem.h b/core/rosteritem.h
index c0a490e..ec8a622 100644
--- a/core/rosteritem.h
+++ b/core/rosteritem.h
@@ -28,6 +28,9 @@
 
 #include <list>
 
+#include <QXmppVCardIq.h>
+#include <QXmppPresence.h>
+
 #include "../global.h"
 #include "archive.h"
 
@@ -62,12 +65,12 @@ public:
     void flushMessagesToArchive(bool finished, const QString& firstId, const QString& lastId);
     void requestHistory(int count, const QString& before);
     void requestFromEmpty(int count, const QString& before);
-    bool hasAvatar() const;
-    bool isAvatarAutoGenerated() const;
-    QString avatarHash() const;
-    QString avatarPath() const;
-    void setAvatar(const QByteArray& data);
-    void setAutoGeneratedAvatar();
+    QString avatarPath(const QString& resource = "") const;
+    bool readAvatarInfo(Archive::AvatarInfo& target, const QString& resource = "") const;
+    virtual bool setAvatar(const QByteArray& data, const QString& resource = "");
+    virtual bool setAutoGeneratedAvatar(const QString& resource = "");
+    virtual Shared::VCard handleResponseVCard(const QXmppVCardIq& card, const QString& resource);
+    virtual void handlePresence(const QXmppPresence& pres) = 0;
     
 signals:
     void nameChanged(const QString& name);
@@ -75,6 +78,7 @@ signals:
     void historyResponse(const std::list<Shared::Message>& messages);
     void needHistory(const QString& before, const QString& after, const QDateTime& afterTime = QDateTime());
     void avatarChanged(Shared::Avatar, const QString& path);
+    void requestVCard(const QString& jid);
     
 public:
     const QString jid;
diff --git a/ui/models/participant.cpp b/ui/models/participant.cpp
index 16f5cb7..3939888 100644
--- a/ui/models/participant.cpp
+++ b/ui/models/participant.cpp
@@ -34,6 +34,15 @@ Models::Participant::Participant(const QMap<QString, QVariant>& data, Models::It
     if (itr != data.end()) {
         setRole(itr.value().toUInt());
     }
+    
+    itr = data.find("avatarState");
+    if (itr != data.end()) {
+        setAvatarState(itr.value().toUInt());
+    }
+    itr = data.find("avatarPath");
+    if (itr != data.end()) {
+        setAvatarPath(itr.value().toString());
+    }
 }
 
 Models::Participant::~Participant()
@@ -42,7 +51,7 @@ Models::Participant::~Participant()
 
 int Models::Participant::columnCount() const
 {
-    return 6;
+    return 8;
 }
 
 QVariant Models::Participant::data(int column) const
@@ -52,6 +61,10 @@ QVariant Models::Participant::data(int column) const
             return static_cast<uint8_t>(affiliation);
         case 5:
             return static_cast<uint8_t>(role);
+        case 6:
+            return static_cast<quint8>(getAvatarState());
+        case 7:
+            return getAvatarPath();
         default:
             return AbstractParticipant::data(column);
     }
@@ -63,6 +76,10 @@ void Models::Participant::update(const QString& key, const QVariant& value)
         setAffiliation(value.toUInt());
     } else if (key == "role") {
         setRole(value.toUInt());
+    } else if (key == "avatarState") {
+        setAvatarState(value.toUInt());
+    } else if (key == "avatarPath") {
+        setAvatarPath(value.toString());
     } else {
         AbstractParticipant::update(key, value);
     }
@@ -113,3 +130,39 @@ void Models::Participant::setRole(unsigned int p_role)
         qDebug() << "An attempt to set wrong role" << p_role << "to the room participant" << name;
     }
 }
+
+QString Models::Participant::getAvatarPath() const
+{
+    return avatarPath;
+}
+
+Shared::Avatar Models::Participant::getAvatarState() const
+{
+    return avatarState;
+}
+
+void Models::Participant::setAvatarPath(const QString& path)
+{
+    if (avatarPath != path) {
+        avatarPath = path;
+        changed(7);
+    }
+}
+
+void Models::Participant::setAvatarState(Shared::Avatar p_state)
+{
+    if (avatarState != p_state) {
+        avatarState = p_state;
+        changed(6);
+    }
+}
+
+void Models::Participant::setAvatarState(unsigned int p_state)
+{
+    if (p_state <= static_cast<quint8>(Shared::Avatar::valid)) {
+        Shared::Avatar state = static_cast<Shared::Avatar>(p_state);
+        setAvatarState(state);
+    } else {
+        qDebug() << "An attempt to set invalid avatar state" << p_state << "to the room participant" << name << ", skipping";
+    }
+}
diff --git a/ui/models/participant.h b/ui/models/participant.h
index 9c6ddbc..a93cb6d 100644
--- a/ui/models/participant.h
+++ b/ui/models/participant.h
@@ -41,10 +41,20 @@ public:
     Shared::Role getRole() const;
     void setRole(Shared::Role p_role);
     void setRole(unsigned int role);
+    
+    Shared::Avatar getAvatarState() const;
+    QString getAvatarPath() const;
 
+protected:
+    void setAvatarState(Shared::Avatar p_state);
+    void setAvatarState(unsigned int p_state);
+    void setAvatarPath(const QString& path);
+    
 private:
     Shared::Affiliation affiliation;
     Shared::Role role;
+    Shared::Avatar avatarState;
+    QString avatarPath;
 };
 
 }
diff --git a/ui/models/roster.cpp b/ui/models/roster.cpp
index 97aa192..63bd290 100644
--- a/ui/models/roster.cpp
+++ b/ui/models/roster.cpp
@@ -66,6 +66,7 @@ QVariant Models::Roster::data (const QModelIndex& index, int role) const
         case Qt::DisplayRole:
         {
             if (index.column() != 0) {
+                result = "";
                 break;
             }
             switch (item->type) {
@@ -139,11 +140,20 @@ QVariant Models::Roster::data (const QModelIndex& index, int role) const
                 }
                     break;
                 case Item::participant: {
+                    quint8 col = index.column();
+                    Participant* p = static_cast<Participant*>(item);
+                    if (col == 0) {
+                        result = p->getStatusIcon(false);
+                    } else if (col == 1) {
+                        QString path = p->getAvatarPath();
+                        if (path.size() > 0) {
+                            result = QIcon(path);
+                        }
+                    }
                     if (index.column() != 0) {
                         break;
                     }
-                    Participant* p = static_cast<Participant*>(item);
-                    result = p->getStatusIcon(false);
+                    
                 }
                     break;
                 default:
diff --git a/ui/squawk.cpp b/ui/squawk.cpp
index ce648ba..915c42c 100644
--- a/ui/squawk.cpp
+++ b/ui/squawk.cpp
@@ -213,7 +213,6 @@ void Squawk::addContact(const QString& account, const QString& jid, const QStrin
     settings.beginGroup(account);
     if (settings.value("expanded", false).toBool()) {
         QModelIndex ind = rosterModel.getAccountIndex(account);
-        qDebug() << "expanding account " << ind.data();
         m_ui->roster->expand(ind);
     }
     settings.endGroup();
diff --git a/ui/utils/message.cpp b/ui/utils/message.cpp
index ecb54f8..479a3a0 100644
--- a/ui/utils/message.cpp
+++ b/ui/utils/message.cpp
@@ -23,7 +23,16 @@
 #include <QRegularExpression>
 #include "message.h"
 
-const QRegularExpression urlReg("(?<!<a\\shref=['\"])(?<!<img\\ssrc=['\"])((?:https?|ftp)://\\S+)");       //finds all hypertext references which are not wrapped in a or img tags
+const QRegularExpression urlReg("(?<!<a\\shref=['\"])(?<!<img\\ssrc=['\"])("
+                                "(?:https?|ftp):\\/\\/"
+                                    "\\w+"
+                                    "(?:"
+                                        "[\\w\\.\\/\\:\\;\\?\\&\\=\\@\\%\\#\\+]?"
+                                        "(?:"
+                                            "\\([\\w\\.\\/\\:\\;\\?\\&\\=\\@\\%\\#\\+]+\\)"
+                                        ")?"
+                                    ")*"
+                                ")");
 const QRegularExpression imgReg("((?:https?|ftp)://\\S+\\.(?:jpg|jpeg|png|svg|gif))");
 
 Message::Message(const Shared::Message& source, bool outgoing, const QString& p_sender, const QString& avatarPath, QWidget* parent):

From 52efc2b1a4e5cd14d5795b4730e3748ccb697cf7 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Tue, 31 Dec 2019 21:14:12 +0300
Subject: [PATCH 038/281] now we have avatars in muc chats

---
 ui/models/room.cpp       | 23 +++++++++++++++++++++++
 ui/models/room.h         |  6 ++++++
 ui/models/roster.cpp     |  4 ++--
 ui/models/roster.h       |  2 +-
 ui/squawk.cpp            | 14 ++++++++++----
 ui/utils/messageline.cpp | 18 ++++++++++++------
 ui/widgets/room.cpp      | 38 ++++++++++++++++++++++++++++++++++++++
 ui/widgets/room.h        |  2 ++
 8 files changed, 94 insertions(+), 13 deletions(-)

diff --git a/ui/models/room.cpp b/ui/models/room.cpp
index 72eafd2..55e64ca 100644
--- a/ui/models/room.cpp
+++ b/ui/models/room.cpp
@@ -276,6 +276,7 @@ void Models::Room::addParticipant(const QString& p_name, const QMap<QString, QVa
         part->setName(p_name);
         participants.insert(std::make_pair(p_name, part));
         appendChild(part);
+        emit participantJoined(*part);
     }
 }
 
@@ -299,6 +300,7 @@ void Models::Room::removeParticipant(const QString& p_name)
         participants.erase(itr);
         removeChild(p->row());
         p->deleteLater();
+        emit participantLeft(p_name);
     }
 }
 
@@ -373,3 +375,24 @@ void Models::Room::setAvatarState(unsigned int p_state)
         qDebug() << "An attempt to set invalid avatar state" << p_state << "to the room" << jid << ", skipping";
     }
 }
+
+std::map<QString, const Models::Participant &> Models::Room::getParticipants() const
+{
+    std::map<QString, const Models::Participant&> result;
+    
+    for (std::pair<QString, Models::Participant*> pair : participants) {
+        result.emplace(pair.first, *(pair.second));
+    }
+    
+    return result;
+}
+
+QString Models::Room::getParticipantIconPath(const QString& name) const
+{
+    std::map<QString, Models::Participant*>::const_iterator itr = participants.find(name);
+    if (itr == participants.end()) {
+        return "";
+    }
+    
+    return itr->second->getAvatarPath();
+}
diff --git a/ui/models/room.h b/ui/models/room.h
index 3a6ebee..4c3b9d9 100644
--- a/ui/models/room.h
+++ b/ui/models/room.h
@@ -71,6 +71,12 @@ public:
     QString getDisplayedName() const override;
     Shared::Avatar getAvatarState() const;
     QString getAvatarPath() const;
+    std::map<QString, const Participant&> getParticipants() const;
+    QString getParticipantIconPath(const QString& name) const;
+    
+signals:
+    void participantJoined(const Participant& participant);
+    void participantLeft(const QString& name);
     
 private:
     void handleParticipantUpdate(std::map<QString, Participant*>::const_iterator itr, const QMap<QString, QVariant>& data);
diff --git a/ui/models/roster.cpp b/ui/models/roster.cpp
index 63bd290..44b4ac8 100644
--- a/ui/models/roster.cpp
+++ b/ui/models/roster.cpp
@@ -949,7 +949,7 @@ bool Models::Roster::groupHasContact(const QString& account, const QString& grou
     }
 }
 
-QString Models::Roster::getContactIconPath(const QString& account, const QString& jid)
+QString Models::Roster::getContactIconPath(const QString& account, const QString& jid, const QString& resource)
 {
     ElId id(account, jid);
     std::multimap<ElId, Contact*>::const_iterator cItr = contacts.find(id);
@@ -959,7 +959,7 @@ QString Models::Roster::getContactIconPath(const QString& account, const QString
         if (rItr == rooms.end()) {
             qDebug() << "An attempt to get an icon path of non existing contact" << account << ":" << jid << ", returning empty value";
         } else {
-            //path = rItr->second->getRoomName();
+            path = rItr->second->getParticipantIconPath(resource);
         }
     } else {
         if (cItr->second->getAvatarState() != Shared::Avatar::empty) {
diff --git a/ui/models/roster.h b/ui/models/roster.h
index a2ffdc2..542d0b7 100644
--- a/ui/models/roster.h
+++ b/ui/models/roster.h
@@ -73,7 +73,7 @@ public:
     
     std::deque<QString> groupList(const QString& account) const;
     bool groupHasContact(const QString& account, const QString& group, const QString& contactJID) const;
-    QString getContactIconPath(const QString& account, const QString& jid);
+    QString getContactIconPath(const QString& account, const QString& jid, const QString& resource);
     Account* getAccount(const QString& name);
     QModelIndex getAccountIndex(const QString& name);
     QModelIndex getGroupIndex(const QString& account, const QString& name);
diff --git a/ui/squawk.cpp b/ui/squawk.cpp
index 915c42c..0f2ce5d 100644
--- a/ui/squawk.cpp
+++ b/ui/squawk.cpp
@@ -230,7 +230,6 @@ void Squawk::addGroup(const QString& account, const QString& name)
     settings.beginGroup(account);
     if (settings.value("expanded", false).toBool()) {
         QModelIndex ind = rosterModel.getAccountIndex(account);
-        qDebug() << "expanding account " << ind.data();
         m_ui->roster->expand(ind);
         if (settings.value(name + "/expanded", false).toBool()) {
             m_ui->roster->expand(rosterModel.getGroupIndex(account, name));
@@ -497,21 +496,28 @@ void Squawk::accountMessage(const QString& account, const Shared::Message& data)
 void Squawk::notify(const QString& account, const Shared::Message& msg)
 {
     QString name = QString(rosterModel.getContactName(account, msg.getPenPalJid()));
-    QString path = QString(rosterModel.getContactIconPath(account, msg.getPenPalJid()));
+    QString path = QString(rosterModel.getContactIconPath(account, msg.getPenPalJid(), msg.getPenPalResource()));
     QVariantList args;
     args << QString(QCoreApplication::applicationName());
     args << QVariant(QVariant::UInt);   //TODO some normal id
     if (path.size() > 0) {
         args << path;
     } else {
-        args << QString("mail-message");
+        args << QString("mail-message");    //TODO should here better be unknown user icon?
     }
     if (msg.getType() == Shared::Message::groupChat) {
         args << msg.getFromResource() + " from " + name;
     } else {
         args << name;
     }
-    args << QString(msg.getBody());
+    
+    QString body(msg.getBody());
+    QString oob(msg.getOutOfBandUrl());
+    if (body == oob) {
+        body = tr("Attached file");
+    }
+    
+    args << body;
     args << QStringList();
     args << QVariantMap();
     args << 3000;
diff --git a/ui/utils/messageline.cpp b/ui/utils/messageline.cpp
index 013d94c..ba95259 100644
--- a/ui/utils/messageline.cpp
+++ b/ui/utils/messageline.cpp
@@ -74,6 +74,10 @@ MessageLine::Position MessageLine::message(const Shared::Message& msg, bool forc
                 outgoing = true;
             } else {
                 sender = msg.getFromResource();
+                std::map<QString, QString>::iterator aItr = palAvatars.find(sender);
+                if (aItr != palAvatars.end()) {
+                    aPath = aItr->second;
+                }
                 outgoing = false;
             }
         } else {
@@ -111,16 +115,18 @@ MessageLine::Position MessageLine::message(const Shared::Message& msg, bool forc
     if (outgoing) {
         myMessages.insert(std::make_pair(id, message));
     } else {
+        QString senderId;
         if (room) {
-            
+            senderId = sender;
         } else {
             QString jid = msg.getFromJid();
-            std::map<QString, Index>::iterator pItr = palMessages.find(jid);
-            if (pItr == palMessages.end()) {
-                pItr = palMessages.insert(std::make_pair(jid, Index())).first;
-            }
-            pItr->second.insert(std::make_pair(id, message));
         }
+        
+        std::map<QString, Index>::iterator pItr = palMessages.find(senderId);
+        if (pItr == palMessages.end()) {
+            pItr = palMessages.insert(std::make_pair(senderId, Index())).first;
+        }
+        pItr->second.insert(std::make_pair(id, message));
     }
     messageIndex.insert(std::make_pair(id, message));
     unsigned long index = std::distance<Order::const_iterator>(messageOrder.begin(), result.first);   //need to make with binary indexed tree
diff --git a/ui/widgets/room.cpp b/ui/widgets/room.cpp
index 49c422c..f495432 100644
--- a/ui/widgets/room.cpp
+++ b/ui/widgets/room.cpp
@@ -28,6 +28,16 @@ Room::Room(Models::Account* acc, Models::Room* p_room, QWidget* parent):
     setAvatar(room->getAvatarPath());
     
     connect(room, &Models::Room::childChanged, this, &Room::onRoomChanged);
+    connect(room, &Models::Room::participantJoined, this, &Room::onParticipantJoined);
+    connect(room, &Models::Room::participantLeft, this, &Room::onParticipantLeft);
+    
+    std::map<QString, const Models::Participant&> members = room->getParticipants();
+    for (std::pair<QString, const Models::Participant&> pair : members) {
+        QString aPath = pair.second.getAvatarPath();
+        if (aPath.size() > 0) {
+            line->setPalAvatar(pair.first, aPath);
+        }
+    }
 }
 
 Room::~Room()
@@ -62,6 +72,34 @@ void Room::onRoomChanged(Models::Item* item, int row, int col)
             case 6:
                 setStatus(room->getSubject());
                 break;
+            case 8:
+                setAvatar(room->getAvatarPath());
+                break;
+        }
+    } else {
+        switch (col) {
+            case 7: {
+                Models::Participant* mem = static_cast<Models::Participant*>(item);
+                QString aPath = mem->getAvatarPath();
+                if (aPath.size() > 0) {
+                    line->setPalAvatar(mem->getName(), aPath);
+                } else {
+                    line->dropPalAvatar(mem->getName());
+                }
+            }
         }
     }
 }
+
+void Room::onParticipantJoined(const Models::Participant& participant)
+{
+    QString aPath = participant.getAvatarPath();
+    if (aPath.size() > 0) {
+        line->setPalAvatar(participant.getName(), aPath);
+    }
+}
+
+void Room::onParticipantLeft(const QString& name)
+{
+    line->dropPalAvatar(name);
+}
diff --git a/ui/widgets/room.h b/ui/widgets/room.h
index bf08615..2cf7831 100644
--- a/ui/widgets/room.h
+++ b/ui/widgets/room.h
@@ -36,6 +36,8 @@ public:
     
 protected slots:
     void onRoomChanged(Models::Item* item, int row, int col);
+    void onParticipantJoined(const Models::Participant& participant);
+    void onParticipantLeft(const QString& name);
     
 protected:
     void handleSendMessage(const QString & text) override;

From 5a59d54b182b0a93f55cd0f15df75d05ed9b2d5a Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Thu, 2 Jan 2020 15:04:49 +0300
Subject: [PATCH 039/281] first thoughts on message states

---
 global.cpp | 37 +++++++++++++++++++++++++++++++++----
 global.h   | 14 ++++++++++++++
 2 files changed, 47 insertions(+), 4 deletions(-)

diff --git a/global.cpp b/global.cpp
index 5756bb0..223cf10 100644
--- a/global.cpp
+++ b/global.cpp
@@ -34,7 +34,9 @@ Shared::Message::Message(Shared::Message::Type p_type):
     thread(),
     type(p_type),
     outgoing(false),
-    forwarded(false)
+    forwarded(false),
+    state(State::delivered),
+    edited(false)
 {
 }
 
@@ -49,7 +51,9 @@ Shared::Message::Message():
     thread(),
     type(Message::normal),
     outgoing(false),
-    forwarded(false)
+    forwarded(false),
+    state(State::delivered),
+    edited(false)
 {
 }
 
@@ -161,6 +165,16 @@ QString Shared::Message::getPenPalResource() const
     }
 }
 
+Shared::Message::State Shared::Message::getState() const
+{
+    return state;
+}
+
+bool Shared::Message::getEdited() const
+{
+    return edited;
+}
+
 void Shared::Message::setFromJid(const QString& from)
 {
     jFrom = from;
@@ -226,6 +240,16 @@ void Shared::Message::setType(Shared::Message::Type t)
     type = t;
 }
 
+void Shared::Message::setState(Shared::Message::State p_state)
+{
+    state = p_state;
+}
+
+void Shared::Message::setEdited(bool p_edited)
+{
+    edited = p_edited;
+}
+
 void Shared::Message::serialize(QDataStream& data) const
 {
     data << jFrom;
@@ -236,11 +260,12 @@ void Shared::Message::serialize(QDataStream& data) const
     data << body;
     data << time;
     data << thread;
-    quint8 t = type;
-    data << t;
+    data << (quint8)type;
     data << outgoing;
     data << forwarded;
     data << oob;
+    data << (quint8)state;
+    data << edited;
 }
 
 void Shared::Message::deserialize(QDataStream& data)
@@ -259,6 +284,10 @@ void Shared::Message::deserialize(QDataStream& data)
     data >> outgoing;
     data >> forwarded;
     data >> oob;
+    quint8 s;
+    data >> s;
+    state = static_cast<State>(s);
+    data >> edited;
 }
 
 QString Shared::generateUUID()
diff --git a/global.h b/global.h
index 521cd26..f194862 100644
--- a/global.h
+++ b/global.h
@@ -157,6 +157,14 @@ public:
         groupChat,
         headline
     };
+    
+    enum class State {
+        pending,
+        sent,
+        delivered,
+        error
+    };
+    
     Message(Type p_type);
     Message();
 
@@ -175,6 +183,8 @@ public:
     void setType(Type t);
     void setCurrentTime();
     void setOutOfBandUrl(const QString& url);
+    void setState(State p_state);
+    void setEdited(bool p_edited);
     
     QString getFrom() const;
     QString getFromJid() const;
@@ -192,6 +202,8 @@ public:
     bool hasOutOfBandUrl() const;
     bool storable() const;
     QString getOutOfBandUrl() const;
+    State getState() const;
+    bool getEdited() const;
     
     QString getPenPalJid() const;
     QString getPenPalResource() const;
@@ -213,6 +225,8 @@ private:
     bool outgoing;
     bool forwarded;
     QString oob;
+    State state;
+    bool edited;
 };
 
 class VCard {

From 5e7c247bb46768b619e7657b3e45494f1cb044d9 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Thu, 2 Jan 2020 17:18:03 +0300
Subject: [PATCH 040/281] badges and screenshots in readme

---
 README.md | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/README.md b/README.md
index 1473ddc..e8d0d10 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,10 @@
-# Sqwawk
+# Squawk - a compact XMPP desktop messenger
 
-A compact XMPP desktop messenger
+[![AUR license](https://img.shields.io/aur/license/squawk?style=flat-square)](https://git.macaw.me/blue/squawk/raw/branch/master/LICENSE.md)
+[![AUR version](https://img.shields.io/aur/version/squawk?style=flat-square)](https://aur.archlinux.org/packages/squawk/)
+[![Donations](http://img.shields.io/liberapay/patrons/macaw.me.svg?logo=liberapay?style=flat-square)](https://liberapay.com/macaw.me)
+
+![Squawk screenshot](https://macaw.me/images/squawk/0.1.2.png)
 
 ### Prerequisites
 

From 0a4c4aa04217233ddedd721dedf808f7c3c81f9b Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Thu, 2 Jan 2020 17:19:00 +0300
Subject: [PATCH 041/281] badges and screenshots in readme typo

---
 README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/README.md b/README.md
index e8d0d10..f8455a0 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
 
 [![AUR license](https://img.shields.io/aur/license/squawk?style=flat-square)](https://git.macaw.me/blue/squawk/raw/branch/master/LICENSE.md)
 [![AUR version](https://img.shields.io/aur/version/squawk?style=flat-square)](https://aur.archlinux.org/packages/squawk/)
-[![Donations](http://img.shields.io/liberapay/patrons/macaw.me.svg?logo=liberapay?style=flat-square)](https://liberapay.com/macaw.me)
+[![Donations](http://img.shields.io/liberapay/patrons/macaw.me.svg?logo=liberapay&style=flat-square)](https://liberapay.com/macaw.me)
 
 ![Squawk screenshot](https://macaw.me/images/squawk/0.1.2.png)
 

From ad1977f05f3fe3a1b158aa08d54a1039a6853a62 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Thu, 2 Jan 2020 17:22:51 +0300
Subject: [PATCH 042/281] badges and screenshots in readme typo

---
 README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/README.md b/README.md
index f8455a0..2ff8eb9 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
 
 [![AUR license](https://img.shields.io/aur/license/squawk?style=flat-square)](https://git.macaw.me/blue/squawk/raw/branch/master/LICENSE.md)
 [![AUR version](https://img.shields.io/aur/version/squawk?style=flat-square)](https://aur.archlinux.org/packages/squawk/)
-[![Donations](http://img.shields.io/liberapay/patrons/macaw.me.svg?logo=liberapay&style=flat-square)](https://liberapay.com/macaw.me)
+[![Liberapay patrons](https://img.shields.io/liberapay/patrons/macaw.me?logo=liberapay&style=flat-square)](https://liberapay.com/macaw.me)
 
 ![Squawk screenshot](https://macaw.me/images/squawk/0.1.2.png)
 

From 13a197004750c054fc34c94cff107a8d67efba01 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Tue, 7 Jan 2020 12:26:07 +0300
Subject: [PATCH 043/281] a method for setting states to messages

---
 core/archive.cpp | 104 +++++++++++++++++++++++++++++++----------------
 core/archive.h   |   2 +
 2 files changed, 72 insertions(+), 34 deletions(-)

diff --git a/core/archive.cpp b/core/archive.cpp
index 0d3e7c4..15d8011 100644
--- a/core/archive.cpp
+++ b/core/archive.cpp
@@ -173,28 +173,74 @@ Shared::Message Core::Archive::getElement(const QString& id)
         throw Closed("getElement", jid.toStdString());
     }
     
-    std::string strKey = id.toStdString();
-    
-    MDB_val lmdbKey, lmdbData;
-    lmdbKey.mv_size = strKey.size();
-    lmdbKey.mv_data = (char*)strKey.c_str();
-    
     MDB_txn *txn;
-    int rc;
     mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn);
-    rc = mdb_get(txn, main, &lmdbKey, &lmdbData);
-    if (rc) {
-        qDebug() <<"Get error: " << mdb_strerror(rc);
+    
+    try {
+        Shared::Message msg = getMessage(id.toStdString(), txn);
         mdb_txn_abort(txn);
-        throw NotFound(id.toStdString(), jid.toStdString());
-    } else {
+        return msg;
+    } catch (...) {
+        mdb_txn_abort(txn);
+        throw;
+    }
+}
+
+Shared::Message Core::Archive::getMessage(const std::string& id, MDB_txn* txn)
+{
+    MDB_val lmdbKey, lmdbData;
+    lmdbKey.mv_size = id.size();
+    lmdbKey.mv_data = (char*)id.c_str();
+    int rc = mdb_get(txn, main, &lmdbKey, &lmdbData);
+    
+    if (rc == 0) {
         QByteArray ba((char*)lmdbData.mv_data, lmdbData.mv_size);
         QDataStream ds(&ba, QIODevice::ReadOnly);
         
         Shared::Message msg;
         msg.deserialize(ds);
-        mdb_txn_abort(txn);
+        
         return msg;
+    } else if (rc == MDB_NOTFOUND) {
+        throw NotFound(id, jid.toStdString());
+    } else {
+        throw Unknown(jid.toStdString(), mdb_strerror(rc));
+    }
+}
+
+void Core::Archive::setMessageState(const QString& id, Shared::Message::State state)
+{
+    if (!opened) {
+        throw Closed("setMessageState", jid.toStdString());
+    }
+    
+    MDB_txn *txn;
+    mdb_txn_begin(environment, NULL, 0, &txn);
+    
+    std::string strId(id.toStdString());
+    try {
+        Shared::Message msg = getMessage(strId, txn);
+        msg.setState(state);
+        
+        MDB_val lmdbKey, lmdbData;
+        QByteArray ba;
+        QDataStream ds(&ba, QIODevice::WriteOnly);
+        msg.serialize(ds);
+        lmdbKey.mv_size = strId.size();
+        lmdbKey.mv_data = (char*)strId.c_str();
+        lmdbData.mv_size = ba.size();
+        lmdbData.mv_data = (uint8_t*)ba.data();
+        int rc = mdb_put(txn, main, &lmdbKey, &lmdbData, 0);
+        if (rc == 0) {
+            rc = mdb_txn_commit(txn);
+        } else {
+            mdb_txn_abort(txn);
+            throw Unknown(jid.toStdString(), mdb_strerror(rc));
+        }
+        
+    } catch (...) {
+        mdb_txn_abort(txn);
+        throw;
     }
 }
 
@@ -344,8 +390,8 @@ std::list<Shared::Message> Core::Archive::getBefore(int count, const QString& id
     MDB_val lmdbKey, lmdbData;
     int rc;
     rc = mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn);
+    rc = mdb_cursor_open(txn, order, &cursor);
     if (id == "") {
-        rc = mdb_cursor_open(txn, order, &cursor);
         rc = mdb_cursor_get(cursor, &lmdbKey, &lmdbData, MDB_LAST);
         if (rc) {
             qDebug() << "Error getting before" << mdb_strerror(rc) << ", id:" << id;
@@ -356,40 +402,29 @@ std::list<Shared::Message> Core::Archive::getBefore(int count, const QString& id
         }
     } else {
         std::string stdId(id.toStdString());
-        lmdbKey.mv_size = stdId.size();
-        lmdbKey.mv_data = (char*)stdId.c_str();
-        rc = mdb_get(txn, main, &lmdbKey, &lmdbData);
-        if (rc) {
-            qDebug() <<"Error getting before: no reference message" << mdb_strerror(rc) << ", id:" << id;
-            mdb_txn_abort(txn);
-            throw NotFound(stdId, jid.toStdString());
-        } else {
-            QByteArray ba((char*)lmdbData.mv_data, lmdbData.mv_size);
-            QDataStream ds(&ba, QIODevice::ReadOnly);
-            
-            Shared::Message msg;
-            msg.deserialize(ds);
+        try {
+            Shared::Message msg = getMessage(stdId, txn);
             quint64 stamp = msg.getTime().toMSecsSinceEpoch();
             lmdbKey.mv_data = (quint8*)&stamp;
             lmdbKey.mv_size = 8;
             
-            rc = mdb_cursor_open(txn, order, &cursor);
             rc = mdb_cursor_get(cursor, &lmdbKey, &lmdbData, MDB_SET);
             
             if (rc) {
                 qDebug() << "Error getting before: couldn't set " << mdb_strerror(rc);
-                mdb_cursor_close(cursor);
-                mdb_txn_abort(txn);
                 throw NotFound(stdId, jid.toStdString());
             } else {
                 rc = mdb_cursor_get(cursor, &lmdbKey, &lmdbData, MDB_PREV);
                 if (rc) {
                     qDebug() << "Error getting before, couldn't prev " << mdb_strerror(rc);
-                    mdb_cursor_close(cursor);
-                    mdb_txn_abort(txn);
                     throw NotFound(stdId, jid.toStdString());
                 }
             }
+            
+        } catch (...) {
+            mdb_cursor_close(cursor);
+            mdb_txn_abort(txn);
+            throw;
         }
     }
     
@@ -401,6 +436,7 @@ std::list<Shared::Message> Core::Archive::getBefore(int count, const QString& id
         if (rc) {
             qDebug() <<"Get error: " << mdb_strerror(rc);
             std::string sId((char*)lmdbData.mv_data, lmdbData.mv_size);
+            mdb_cursor_close(cursor);
             mdb_txn_abort(txn);
             throw NotFound(sId, jid.toStdString());
         } else {
@@ -687,9 +723,9 @@ bool Core::Archive::readAvatarInfo(Core::Archive::AvatarInfo& target, const QStr
         bool success = readAvatarInfo(target, res, txn);
         mdb_txn_abort(txn);
         return success;
-    } catch (const std::exception& e) {
+    } catch (...) {
         mdb_txn_abort(txn);
-        throw e;
+        throw;
     }
     
 }
diff --git a/core/archive.h b/core/archive.h
index da4a96f..36dbdbd 100644
--- a/core/archive.h
+++ b/core/archive.h
@@ -46,6 +46,7 @@ public:
     bool addElement(const Shared::Message& message);
     unsigned int addElements(const std::list<Shared::Message>& messages);
     Shared::Message getElement(const QString& id);
+    void setMessageState(const QString& id, Shared::Message::State state);
     Shared::Message oldest();
     QString oldestId();
     Shared::Message newest();
@@ -181,6 +182,7 @@ private:
     void printOrder();
     void printKeys();
     bool dropAvatar(const std::string& resource);
+    Shared::Message getMessage(const std::string& id, MDB_txn* txn);
 };
 
 }

From 626227db938e679042f31e4567e829e41d8afb1e Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Thu, 16 Jan 2020 11:52:54 +0300
Subject: [PATCH 044/281] file comment fix, avatar dropping bug fix, url
 detection bug fix

---
 core/rosteritem.cpp  | 4 ++--
 ui/utils/message.cpp | 5 +++--
 2 files changed, 5 insertions(+), 4 deletions(-)

diff --git a/core/rosteritem.cpp b/core/rosteritem.cpp
index a8d48a4..b9801d9 100644
--- a/core/rosteritem.cpp
+++ b/core/rosteritem.cpp
@@ -423,9 +423,9 @@ Shared::VCard Core::RosterItem::handleResponseVCard(const QXmppVCardIq& card, co
     } else {
         if (!hasAvatar || !info.autogenerated) {
             setAutoGeneratedAvatar(resource);
-            type = Shared::Avatar::autocreated;
-            path = avatarPath(resource) + ".png";
         }
+        type = Shared::Avatar::autocreated;
+        path = avatarPath(resource) + ".png";
     }
     
     
diff --git a/ui/utils/message.cpp b/ui/utils/message.cpp
index 479a3a0..1725a9b 100644
--- a/ui/utils/message.cpp
+++ b/ui/utils/message.cpp
@@ -27,9 +27,9 @@ const QRegularExpression urlReg("(?<!<a\\shref=['\"])(?<!<img\\ssrc=['\"])("
                                 "(?:https?|ftp):\\/\\/"
                                     "\\w+"
                                     "(?:"
-                                        "[\\w\\.\\/\\:\\;\\?\\&\\=\\@\\%\\#\\+]?"
+                                        "[\\w\\.\\/\\:\\;\\?\\&\\=\\@\\%\\#\\+\\-]?"
                                         "(?:"
-                                            "\\([\\w\\.\\/\\:\\;\\?\\&\\=\\@\\%\\#\\+]+\\)"
+                                            "\\([\\w\\.\\/\\:\\;\\?\\&\\=\\@\\%\\#\\+\\-]+\\)"
                                         ")?"
                                     ")*"
                                 ")");
@@ -207,6 +207,7 @@ void Message::hideComment()
         bodyLayout->removeWidget(fileComment);
         fileComment->hide();
         fileComment->setWordWrap(false);
+        commentAdded = false;
     }
 }
 

From 565449f17626a2de3398c9fbf5ed61ea78133b3a Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Sat, 25 Jan 2020 11:10:24 +0300
Subject: [PATCH 045/281] bug with selection, some right padding to the avatars

---
 ui/models/roster.cpp | 1 -
 ui/squawk.cpp        | 2 +-
 ui/squawk.ui         | 8 +++++++-
 3 files changed, 8 insertions(+), 3 deletions(-)

diff --git a/ui/models/roster.cpp b/ui/models/roster.cpp
index 44b4ac8..7f4cfc5 100644
--- a/ui/models/roster.cpp
+++ b/ui/models/roster.cpp
@@ -66,7 +66,6 @@ QVariant Models::Roster::data (const QModelIndex& index, int role) const
         case Qt::DisplayRole:
         {
             if (index.column() != 0) {
-                result = "";
                 break;
             }
             switch (item->type) {
diff --git a/ui/squawk.cpp b/ui/squawk.cpp
index 0f2ce5d..722f2c7 100644
--- a/ui/squawk.cpp
+++ b/ui/squawk.cpp
@@ -36,7 +36,7 @@ Squawk::Squawk(QWidget *parent) :
     m_ui->setupUi(this);
     m_ui->roster->setModel(&rosterModel);
     m_ui->roster->setContextMenuPolicy(Qt::CustomContextMenu);
-    m_ui->roster->setColumnWidth(1, 20);
+    m_ui->roster->setColumnWidth(1, 30);
     m_ui->roster->setIconSize(QSize(20, 20));
     m_ui->roster->header()->setStretchLastSection(false);
     m_ui->roster->header()->setSectionResizeMode(0, QHeaderView::Stretch);
diff --git a/ui/squawk.ui b/ui/squawk.ui
index 04cf999..e09cd19 100644
--- a/ui/squawk.ui
+++ b/ui/squawk.ui
@@ -54,6 +54,12 @@
       <property name="uniformRowHeights">
        <bool>true</bool>
       </property>
+      <property name="animated">
+       <bool>true</bool>
+      </property>
+      <property name="allColumnsShowFocus">
+       <bool>true</bool>
+      </property>
       <property name="expandsOnDoubleClick">
        <bool>false</bool>
       </property>
@@ -70,7 +76,7 @@
      <x>0</x>
      <y>0</y>
      <width>385</width>
-     <height>27</height>
+     <height>22</height>
     </rect>
    </property>
    <widget class="QMenu" name="menuSettings">

From a4136ff9fefc3586218d88d5f7755a51baf26d07 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Mon, 27 Jan 2020 18:07:38 +0300
Subject: [PATCH 046/281] progress spinner fix, new lines in messages now
 display again

---
 ui/utils/message.cpp  | 1 +
 ui/utils/progress.cpp | 4 ++--
 2 files changed, 3 insertions(+), 2 deletions(-)

diff --git a/ui/utils/message.cpp b/ui/utils/message.cpp
index 1725a9b..c20930f 100644
--- a/ui/utils/message.cpp
+++ b/ui/utils/message.cpp
@@ -60,6 +60,7 @@ Message::Message(const Shared::Message& source, bool outgoing, const QString& p_
     QString bd = msg.getBody();
     //bd.replace(imgReg, "<img src=\"\\1\"/>");
     bd.replace(urlReg, "<a href=\"\\1\">\\1</a>");
+    bd.replace("\n", "<br>");
     text->setTextFormat(Qt::RichText);
     text->setText(bd);;
     text->setTextInteractionFlags(text->textInteractionFlags() | Qt::TextSelectableByMouse | Qt::LinksAccessibleByMouse);
diff --git a/ui/utils/progress.cpp b/ui/utils/progress.cpp
index 95eafa2..a028822 100644
--- a/ui/utils/progress.cpp
+++ b/ui/utils/progress.cpp
@@ -39,9 +39,9 @@ Progress::Progress(quint16 p_size, QWidget* parent):
     pixmap->setTransformationMode(Qt::SmoothTransformation);
     pixmap->setOffset(0, 0);
     
-    animation.setDuration(500);
+    animation.setDuration(1000);
     animation.setStartValue(0.0f);
-    animation.setEndValue(180.0f);
+    animation.setEndValue(360.0f);
     animation.setLoopCount(-1);
     connect(&animation, &QVariantAnimation::valueChanged, this, &Progress::onValueChanged);
     

From ed56cca2e708e5fabcc2c5616ae10f9098c00fe1 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Tue, 4 Feb 2020 18:14:51 +0300
Subject: [PATCH 047/281] some visual tweaks, moving on message delivery
 statuses

---
 global.h                    |  4 ++++
 translations/squawk.ru.ts   | 12 +++++++++++
 ui/utils/message.cpp        | 40 +++++++++++++++++++++++++------------
 ui/utils/message.h          |  4 +++-
 ui/utils/messageline.cpp    |  7 +++++--
 ui/widgets/conversation.cpp | 14 +++++++++----
 ui/widgets/conversation.h   |  1 +
 7 files changed, 62 insertions(+), 20 deletions(-)

diff --git a/global.h b/global.h
index f194862..c815149 100644
--- a/global.h
+++ b/global.h
@@ -107,6 +107,10 @@ static const std::deque<QString> subscriptionStateNames = {"None", "From", "To",
 
 static const std::deque<QString> affiliationNames = {"Unspecified", "Outcast", "Nobody", "Member", "Admin", "Owner"};
 static const std::deque<QString> roleNames = {"Unspecified", "Nobody", "Visitor", "Participant", "Moderator"};
+
+static const std::deque<QString> messageStateNames = {"Pending", "Sent", "Delivered", "Error"};
+static const std::deque<QString> messageStateThemeIcons = {"state-offline", "state-sync", "state-ok", "state-error"};
+
 QString generateUUID();
 
 static const std::vector<QColor> colorPalette = {
diff --git a/translations/squawk.ru.ts b/translations/squawk.ru.ts
index 412b6d8..4fe02e3 100644
--- a/translations/squawk.ru.ts
+++ b/translations/squawk.ru.ts
@@ -298,6 +298,18 @@
         <source>Other</source>
         <translation>Другой</translation>
     </message>
+    <message>
+        <source>Pending</source>
+        <translation>В процессе</translation>
+    </message>
+    <message>
+        <source>Sent</source>
+        <translation>Отправлено</translation>
+    </message>
+    <message>
+        <source>Delivered</source>
+        <translation>Доставлено</translation>
+    </message>
 </context>
 <context>
     <name>JoinConference</name>
diff --git a/ui/utils/message.cpp b/ui/utils/message.cpp
index c20930f..ac8be9c 100644
--- a/ui/utils/message.cpp
+++ b/ui/utils/message.cpp
@@ -36,10 +36,12 @@ const QRegularExpression urlReg("(?<!<a\\shref=['\"])(?<!<img\\ssrc=['\"])("
 const QRegularExpression imgReg("((?:https?|ftp)://\\S+\\.(?:jpg|jpeg|png|svg|gif))");
 
 Message::Message(const Shared::Message& source, bool outgoing, const QString& p_sender, const QString& avatarPath, QWidget* parent):
-    QHBoxLayout(parent),
+    QWidget(parent),
     msg(source),
     body(new QWidget()),
+    statusBar(new QWidget()),
     bodyLayout(new QVBoxLayout(body)),
+    layout(new QHBoxLayout(this)),
     date(new QLabel(msg.getTime().toLocalTime().toString())),
     sender(new QLabel(p_sender)),
     text(new QLabel()),
@@ -54,15 +56,17 @@ Message::Message(const Shared::Message& source, bool outgoing, const QString& p_
     hasFile(false),
     commentAdded(false)
 {
+    setContentsMargins(0, 0, 0, 0);
+    layout->setContentsMargins(10, 5, 10, 5);
     body->setBackgroundRole(QPalette::AlternateBase);
     body->setAutoFillBackground(true);
     
     QString bd = msg.getBody();
     //bd.replace(imgReg, "<img src=\"\\1\"/>");
     bd.replace(urlReg, "<a href=\"\\1\">\\1</a>");
-    bd.replace("\n", "<br>");
+    //bd.replace("\n", "<br>");
     text->setTextFormat(Qt::RichText);
-    text->setText(bd);;
+    text->setText("<p style=\"white-space: pre-wrap;\">" + bd + "</p>");;
     text->setTextInteractionFlags(text->textInteractionFlags() | Qt::TextSelectableByMouse | Qt::LinksAccessibleByMouse);
     text->setWordWrap(true);
     text->setOpenExternalLinks(true);
@@ -81,7 +85,6 @@ Message::Message(const Shared::Message& source, bool outgoing, const QString& p_
     
     bodyLayout->addWidget(sender);
     bodyLayout->addWidget(text);
-    bodyLayout->addWidget(date);
     
     shadow->setBlurRadius(10);
     shadow->setXOffset(1);
@@ -90,22 +93,33 @@ Message::Message(const Shared::Message& source, bool outgoing, const QString& p_
     body->setGraphicsEffect(shadow);
     avatar->setMaximumHeight(60);
     avatar->setMaximumWidth(60);
-    QVBoxLayout* aLay = new QVBoxLayout();
-    aLay->addWidget(avatar);
-    aLay->addStretch();
     
+    statusBar->setContentsMargins(0, 0, 0, 0);
+    QHBoxLayout* statusLay = new QHBoxLayout();
+    statusLay->setContentsMargins(0, 0, 0, 0);
+    statusBar->setLayout(statusLay);
     
     if (outgoing) {
         sender->setAlignment(Qt::AlignRight);
         date->setAlignment(Qt::AlignRight);
-        addStretch();
-        addWidget(body);
-        addItem(aLay);
+        QIcon q(Shared::icon(Shared::messageStateThemeIcons[static_cast<uint8_t>(source.getState())]));
+        QLabel* statusIcon = new QLabel();
+        statusIcon->setToolTip(QCoreApplication::translate("Global", Shared::messageStateNames[static_cast<uint8_t>(source.getState())].toLatin1()));
+        statusIcon->setPixmap(q.pixmap(12, 12));
+        statusLay->addWidget(statusIcon);
+        statusLay->addWidget(date);
+        layout->addStretch();
+        layout->addWidget(body);
+        layout->addWidget(avatar);
     } else {
-        addItem(aLay);
-        addWidget(body);
-        addStretch();
+        layout->addWidget(avatar);
+        layout->addWidget(body);
+        layout->addStretch();
+        statusLay->addWidget(date);
     }
+    
+    bodyLayout->addWidget(statusBar);
+    layout->setAlignment(avatar, Qt::AlignTop);
 }
 
 Message::~Message()
diff --git a/ui/utils/message.h b/ui/utils/message.h
index 537f40b..b9d76ee 100644
--- a/ui/utils/message.h
+++ b/ui/utils/message.h
@@ -37,7 +37,7 @@
 /**
  * @todo write docs
  */
-class Message : public QHBoxLayout
+class Message : public QWidget
 {
     Q_OBJECT
 public:
@@ -62,7 +62,9 @@ signals:
 private:
     Shared::Message msg;
     QWidget* body;
+    QWidget* statusBar;
     QVBoxLayout* bodyLayout;
+    QHBoxLayout* layout;
     QLabel* date;
     QLabel* sender;
     QLabel* text;
diff --git a/ui/utils/messageline.cpp b/ui/utils/messageline.cpp
index ba95259..c436c57 100644
--- a/ui/utils/messageline.cpp
+++ b/ui/utils/messageline.cpp
@@ -38,7 +38,10 @@ MessageLine::MessageLine(bool p_room, QWidget* parent):
     busyShown(false),
     progress()
 {
+    setContentsMargins(0, 0, 0, 0);
+    layout->setContentsMargins(0, 0, 0, 0);
     setBackgroundRole(QPalette::Base);
+    layout->setSpacing(0);
     layout->addStretch();
 }
 
@@ -145,9 +148,9 @@ MessageLine::Position MessageLine::message(const Shared::Message& msg, bool forc
     
         
     if (res == end) {
-        layout->addLayout(message);
+        layout->addWidget(message);
     } else {
-        layout->insertLayout(index, message);
+        layout->insertWidget(index + 1, message);
     }
     
     if (msg.hasOutOfBandUrl()) {
diff --git a/ui/widgets/conversation.cpp b/ui/widgets/conversation.cpp
index ef06dd6..6b3613f 100644
--- a/ui/widgets/conversation.cpp
+++ b/ui/widgets/conversation.cpp
@@ -46,7 +46,8 @@ Conversation::Conversation(bool muc, Models::Account* acc, const QString pJid, c
     scroll(down),
     manualSliderChange(false),
     requestingHistory(false),
-    everShown(false)
+    everShown(false),
+    tsb(QApplication::style()->styleHint(QStyle::SH_ScrollBar_Transient) == 1)
 {
     m_ui->setupUi(this);
     
@@ -77,8 +78,12 @@ Conversation::Conversation(bool muc, Models::Account* acc, const QString pJid, c
     QScrollBar* vs = m_ui->scrollArea->verticalScrollBar();
     m_ui->scrollArea->setWidget(line);
     vs->installEventFilter(&vis);
-    vs->setBackgroundRole(QPalette::Base);
-    vs->setAutoFillBackground(true);
+    
+    if (!tsb) {
+        vs->setBackgroundRole(QPalette::Base);
+        vs->setAutoFillBackground(true);
+    }
+    
     connect(vs, &QScrollBar::valueChanged, this, &Conversation::onSliderValueChanged);
     m_ui->scrollArea->installEventFilter(&scrollResizeCatcher);
     m_ui->filesPanel->installEventFilter(&attachResizeCatcher);
@@ -313,8 +318,9 @@ void Conversation::onScrollResize()
     if (everShown) {
         int size = m_ui->scrollArea->width();
         QScrollBar* bar = m_ui->scrollArea->verticalScrollBar();
-        if (bar->isVisible()) {
+        if (bar->isVisible() && !tsb) {
             size -= bar->width();
+            
         }
         line->setMaximumWidth(size);
     }
diff --git a/ui/widgets/conversation.h b/ui/widgets/conversation.h
index 5e68965..311db65 100644
--- a/ui/widgets/conversation.h
+++ b/ui/widgets/conversation.h
@@ -135,6 +135,7 @@ protected:
     bool manualSliderChange;
     bool requestingHistory;
     bool everShown;
+    bool tsb;           //transient scroll bars
 };
 
 #endif // CONVERSATION_H

From 6d1b83d0f888d06940045d1a40754ddc7b03f8b7 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Sat, 8 Feb 2020 14:44:15 +0300
Subject: [PATCH 048/281] ui logick of changing message, not connected yet

---
 global.cpp                  | 25 ++++++++++++++
 global.h                    |  2 ++
 ui/models/contact.cpp       | 21 ++++++++++++
 ui/models/contact.h         |  1 +
 ui/models/presence.cpp      | 13 ++++++++
 ui/models/presence.h        |  1 +
 ui/models/room.cpp          | 10 ++++++
 ui/models/room.h            |  1 +
 ui/models/roster.cpp        | 24 ++++++++++++--
 ui/models/roster.h          |  4 ++-
 ui/squawk.cpp               | 20 ++++++++++--
 ui/squawk.h                 |  3 +-
 ui/utils/message.cpp        | 65 ++++++++++++++++++++++++++++++++++---
 ui/utils/message.h          | 10 ++++++
 ui/utils/messageline.cpp    | 44 ++++++++++++++++++++++++-
 ui/utils/messageline.h      |  3 +-
 ui/widgets/conversation.cpp |  5 +++
 ui/widgets/conversation.h   | 13 +++++---
 18 files changed, 246 insertions(+), 19 deletions(-)

diff --git a/global.cpp b/global.cpp
index 223cf10..6ff87c5 100644
--- a/global.cpp
+++ b/global.cpp
@@ -290,6 +290,31 @@ void Shared::Message::deserialize(QDataStream& data)
     data >> edited;
 }
 
+bool Shared::Message::change(const QMap<QString, QVariant>& data)
+{
+    QMap<QString, QVariant>::const_iterator itr = data.find("state");
+    if (itr != data.end()) {
+        setState(static_cast<State>(itr.value().toUInt()));
+    }
+    
+    bool idChanged = false;
+    itr = data.find("id");
+    if (itr != data.end()) {
+        QString newId = itr.value().toString();
+        if (id != newId) {
+            setId(newId);
+            idChanged = true;
+        }
+    }
+    itr = data.find("body");
+    if (itr != data.end()) {
+        setBody(itr.value().toString());
+        setEdited(true);
+    }
+    
+    return idChanged;
+}
+
 QString Shared::generateUUID()
 {
     uuid_t uuid;
diff --git a/global.h b/global.h
index c815149..29b7969 100644
--- a/global.h
+++ b/global.h
@@ -20,6 +20,7 @@
 #define GLOBAL_H
 
 #include <QString>
+#include <QMap>
 #include <QCoreApplication>
 #include <deque>
 #include <QDateTime>
@@ -189,6 +190,7 @@ public:
     void setOutOfBandUrl(const QString& url);
     void setState(State p_state);
     void setEdited(bool p_edited);
+    bool change(const QMap<QString, QVariant>& data);
     
     QString getFrom() const;
     QString getFromJid() const;
diff --git a/ui/models/contact.cpp b/ui/models/contact.cpp
index a5390fb..de9d312 100644
--- a/ui/models/contact.cpp
+++ b/ui/models/contact.cpp
@@ -290,6 +290,27 @@ void Models::Contact::addMessage(const Shared::Message& data)
     }
 }
 
+void Models::Contact::changeMessage(const QString& id, const QMap<QString, QVariant>& data)
+{
+
+    bool found = false;
+    for (Shared::Message& msg : messages) {
+        if (msg.getId() == id) {
+            msg.change(data);
+            found = true;
+            break;
+        }
+    }
+    if (!found) {
+        for (Presence* pr : presences) {
+            found = pr->changeMessage(id, data);
+            if (found) {
+                break;
+            }
+        }
+    }
+}
+
 unsigned int Models::Contact::getMessagesCount() const
 {
     return messages.size() + childMessages;
diff --git a/ui/models/contact.h b/ui/models/contact.h
index f8ac346..cffc53c 100644
--- a/ui/models/contact.h
+++ b/ui/models/contact.h
@@ -57,6 +57,7 @@ public:
     QString getStatus() const;
     
     void addMessage(const Shared::Message& data);
+    void changeMessage(const QString& id, const QMap<QString, QVariant>& data);
     unsigned int getMessagesCount() const;
     void dropMessages();
     void getMessages(Messages& container) const;
diff --git a/ui/models/presence.cpp b/ui/models/presence.cpp
index 36b07d2..bf2ef12 100644
--- a/ui/models/presence.cpp
+++ b/ui/models/presence.cpp
@@ -61,6 +61,19 @@ void Models::Presence::addMessage(const Shared::Message& data)
     changed(4);
 }
 
+bool Models::Presence::changeMessage(const QString& id, const QMap<QString, QVariant>& data)
+{
+    bool found = false;
+    for (Shared::Message& msg : messages) {
+        if (msg.getId() == id) {
+            msg.change(data);
+            found = true;
+            break;
+        }
+    }
+    return found;
+}
+
 void Models::Presence::dropMessages()
 {
     if (messages.size() > 0) {
diff --git a/ui/models/presence.h b/ui/models/presence.h
index 8371be7..5073e7a 100644
--- a/ui/models/presence.h
+++ b/ui/models/presence.h
@@ -43,6 +43,7 @@ public:
     unsigned int getMessagesCount() const;
     void dropMessages();
     void addMessage(const Shared::Message& data);
+    bool changeMessage(const QString& id, const QMap<QString, QVariant>& data);
     
     void getMessages(Messages& container) const;
 
diff --git a/ui/models/room.cpp b/ui/models/room.cpp
index 55e64ca..afcf8e9 100644
--- a/ui/models/room.cpp
+++ b/ui/models/room.cpp
@@ -236,6 +236,16 @@ void Models::Room::addMessage(const Shared::Message& data)
     changed(5);
 }
 
+void Models::Room::changeMessage(const QString& id, const QMap<QString, QVariant>& data)
+{
+    for (Shared::Message& msg : messages) {
+        if (msg.getId() == id) {
+            msg.change(data);
+            break;
+        }
+    }
+}
+
 void Models::Room::dropMessages()
 {
     if (messages.size() > 0) {
diff --git a/ui/models/room.h b/ui/models/room.h
index 4c3b9d9..6a87a83 100644
--- a/ui/models/room.h
+++ b/ui/models/room.h
@@ -59,6 +59,7 @@ public:
     void update(const QString& field, const QVariant& value);
     
     void addMessage(const Shared::Message& data);
+    void changeMessage(const QString& id, const QMap<QString, QVariant>& data);
     unsigned int getMessagesCount() const;
     void dropMessages();
     void getMessages(Messages& container) const;
diff --git a/ui/models/roster.cpp b/ui/models/roster.cpp
index 7f4cfc5..23e39af 100644
--- a/ui/models/roster.cpp
+++ b/ui/models/roster.cpp
@@ -549,14 +549,34 @@ void Models::Roster::changeContact(const QString& account, const QString& jid, c
     
     for (; cBeg != cEnd; ++cBeg) {
         for (QMap<QString, QVariant>::const_iterator itr = data.begin(), end = data.end(); itr != end; ++itr) {
-            cBeg->second->update(itr.key(), itr.value());;
+            cBeg->second->update(itr.key(), itr.value());
         }
     }
     
     std::map<ElId, Room*>::iterator rItr = rooms.find(id);
     if (rItr != rooms.end()) {
         for (QMap<QString, QVariant>::const_iterator itr = data.begin(), end = data.end(); itr != end; ++itr) {
-            rItr->second->update(itr.key(), itr.value());;
+            rItr->second->update(itr.key(), itr.value());
+        }
+    }
+}
+
+void Models::Roster::changeMessage(const QString& account, const QString& jid, const QString& id, const QMap<QString, QVariant>& data)
+{
+    ElId elid(account, jid);
+    std::multimap<ElId, Contact*>::iterator cBeg = contacts.lower_bound(elid);
+    std::multimap<ElId, Contact*>::iterator cEnd = contacts.upper_bound(elid);
+    
+    for (; cBeg != cEnd; ++cBeg) {
+        for (QMap<QString, QVariant>::const_iterator itr = data.begin(), end = data.end(); itr != end; ++itr) {
+            cBeg->second->changeMessage(id, data);
+        }
+    }
+    
+    std::map<ElId, Room*>::iterator rItr = rooms.find(elid);
+    if (rItr != rooms.end()) {
+        for (QMap<QString, QVariant>::const_iterator itr = data.begin(), end = data.end(); itr != end; ++itr) {
+            rItr->second->changeMessage(id, data);
         }
     }
 }
diff --git a/ui/models/roster.h b/ui/models/roster.h
index 542d0b7..dde6370 100644
--- a/ui/models/roster.h
+++ b/ui/models/roster.h
@@ -23,7 +23,8 @@
 #include <deque>
 #include <map>
 #include <QVector>
-#include "../../global.h"
+
+#include "global.h"
 #include "accounts.h"
 #include "item.h"
 #include "account.h"
@@ -54,6 +55,7 @@ public:
     void addPresence(const QString& account, const QString& jid, const QString& name, const QMap<QString, QVariant>& data);
     void removePresence(const QString& account, const QString& jid, const QString& name);
     void addMessage(const QString& account, const Shared::Message& data);
+    void changeMessage(const QString& account, const QString& jid, const QString& id, const QMap<QString, QVariant>& data);
     void dropMessages(const QString& account, const QString& jid);
     void addRoom(const QString& account, const QString jid, const QMap<QString, QVariant>& data);
     void changeRoom(const QString& account, const QString jid, const QMap<QString, QVariant>& data);
diff --git a/ui/squawk.cpp b/ui/squawk.cpp
index 722f2c7..fd9b6ff 100644
--- a/ui/squawk.cpp
+++ b/ui/squawk.cpp
@@ -480,9 +480,9 @@ void Squawk::accountMessage(const QString& account, const Shared::Message& data)
         QApplication::alert(conv);
         if (conv->isMinimized()) {
             rosterModel.addMessage(account, data);
-            if (!data.getForwarded()) {
-                notify(account, data);
-            }
+        }
+        if (!conv->isVisible() && !data.getForwarded()) {
+            notify(account, data);
         }
     } else {
         rosterModel.addMessage(account, data);
@@ -493,6 +493,20 @@ void Squawk::accountMessage(const QString& account, const Shared::Message& data)
     }
 }
 
+void Squawk::changeMessage(const QString& account, const QString& jid, const QString& id, const QMap<QString, QVariant>& data)
+{
+    Conversations::iterator itr = conversations.find({account, jid});
+    if (itr != conversations.end()) {
+        Conversation* conv = itr->second;
+        conv->changeMessage(id, data);
+        if (conv->isMinimized()) {
+            rosterModel.changeMessage(account, jid, id, data);
+        }
+    } else {
+        rosterModel.changeMessage(account, jid, id, data);
+    }
+}
+
 void Squawk::notify(const QString& account, const Shared::Message& msg)
 {
     QString name = QString(rosterModel.getContactName(account, msg.getPenPalJid()));
diff --git a/ui/squawk.h b/ui/squawk.h
index f0b21c7..a3fbcba 100644
--- a/ui/squawk.h
+++ b/ui/squawk.h
@@ -106,7 +106,7 @@ public slots:
     void fileError(const QString& messageId, const QString& error);
     void fileProgress(const QString& messageId, qreal value);
     void responseVCard(const QString& jid, const Shared::VCard& card);
-    void onItemCollepsed(const QModelIndex& index);
+    void changeMessage(const QString& account, const QString& jid, const QString& id, const QMap<QString, QVariant>& data);
     
 private:
     typedef std::map<Models::Roster::ElId, Conversation*> Conversations;
@@ -145,6 +145,7 @@ private slots:
     void onConversationShown();
     void onConversationRequestLocalFile(const QString& messageId, const QString& url);
     void onConversationDownloadFile(const QString& messageId, const QString& url);
+    void onItemCollepsed(const QModelIndex& index);
     
 };
 
diff --git a/ui/utils/message.cpp b/ui/utils/message.cpp
index ac8be9c..f3f162b 100644
--- a/ui/utils/message.cpp
+++ b/ui/utils/message.cpp
@@ -35,8 +35,9 @@ const QRegularExpression urlReg("(?<!<a\\shref=['\"])(?<!<img\\ssrc=['\"])("
                                 ")");
 const QRegularExpression imgReg("((?:https?|ftp)://\\S+\\.(?:jpg|jpeg|png|svg|gif))");
 
-Message::Message(const Shared::Message& source, bool outgoing, const QString& p_sender, const QString& avatarPath, QWidget* parent):
+Message::Message(const Shared::Message& source, bool p_outgoing, const QString& p_sender, const QString& avatarPath, QWidget* parent):
     QWidget(parent),
+    outgoing(p_outgoing),
     msg(source),
     body(new QWidget()),
     statusBar(new QWidget()),
@@ -50,11 +51,15 @@ Message::Message(const Shared::Message& source, bool outgoing, const QString& p_
     file(0),
     progress(0),
     fileComment(new QLabel()),
+    statusIcon(0),
+    editedLabel(0),
     avatar(new Image(avatarPath.size() == 0 ? Shared::iconPath("user", true) : avatarPath, 60)),
     hasButton(false),
     hasProgress(false),
     hasFile(false),
-    commentAdded(false)
+    commentAdded(false),
+    hasStatusIcon(false),
+    hasEditedLabel(false)
 {
     setContentsMargins(0, 0, 0, 0);
     layout->setContentsMargins(10, 5, 10, 5);
@@ -102,8 +107,8 @@ Message::Message(const Shared::Message& source, bool outgoing, const QString& p_
     if (outgoing) {
         sender->setAlignment(Qt::AlignRight);
         date->setAlignment(Qt::AlignRight);
+        statusIcon = new QLabel();
         QIcon q(Shared::icon(Shared::messageStateThemeIcons[static_cast<uint8_t>(source.getState())]));
-        QLabel* statusIcon = new QLabel();
         statusIcon->setToolTip(QCoreApplication::translate("Global", Shared::messageStateNames[static_cast<uint8_t>(source.getState())].toLatin1()));
         statusIcon->setPixmap(q.pixmap(12, 12));
         statusLay->addWidget(statusIcon);
@@ -111,6 +116,7 @@ Message::Message(const Shared::Message& source, bool outgoing, const QString& p_
         layout->addStretch();
         layout->addWidget(body);
         layout->addWidget(avatar);
+        hasStatusIcon = true;
     } else {
         layout->addWidget(avatar);
         layout->addWidget(body);
@@ -127,8 +133,8 @@ Message::~Message()
     if (!commentAdded) {
         delete fileComment;
     }
-    delete body;
-    delete avatar;
+    //delete body;  //not sure if I should delete it here, it's probably already owned by the infrastructure and gonna die with the rest of the widget
+    //delete avatar;
 }
 
 QString Message::getId() const
@@ -136,6 +142,16 @@ QString Message::getId() const
     return msg.getId();
 }
 
+QString Message::getSenderJid() const
+{
+    return msg.getFromJid();
+}
+
+QString Message::getSenderResource() const
+{
+    return msg.getFromResource();
+}
+
 QString Message::getFileUrl() const
 {
     return msg.getOutOfBandUrl();
@@ -286,3 +302,42 @@ void Message::setAvatarPath(const QString& p_path)
         avatar->setPath(p_path);
     }
 }
+
+bool Message::change(const QMap<QString, QVariant>& data)
+{
+    bool idChanged = msg.change(data);
+    
+    QString bd = msg.getBody();
+    //bd.replace(imgReg, "<img src=\"\\1\"/>");
+    bd.replace(urlReg, "<a href=\"\\1\">\\1</a>");
+    text->setText(bd);
+    if (bd.size() > 0) {
+        text->show();
+    } else {
+        text->hide();
+    }
+    if (msg.getEdited()) {
+        if (!hasEditedLabel) {
+            editedLabel = new QLabel();
+            QFont eFont = editedLabel->font();
+            eFont.setItalic(true);
+            eFont.setPointSize(eFont.pointSize() - 2);
+            editedLabel->setFont(eFont);
+            hasEditedLabel = true;
+            QHBoxLayout* statusLay = static_cast<QHBoxLayout*>(statusBar->layout());
+            if (hasStatusIcon) {
+                statusLay->insertWidget(1, editedLabel);
+            } else {
+                statusLay->insertWidget(0, editedLabel);
+            }
+        }
+    }
+    if (hasStatusIcon) {
+        QIcon q(Shared::icon(Shared::messageStateThemeIcons[static_cast<uint8_t>(msg.getState())]));
+        statusIcon->setToolTip(QCoreApplication::translate("Global", Shared::messageStateNames[static_cast<uint8_t>(msg.getState())].toLatin1()));
+        statusIcon->setPixmap(q.pixmap(12, 12));
+    }
+    
+    
+    return idChanged;
+}
diff --git a/ui/utils/message.h b/ui/utils/message.h
index b9d76ee..b041b87 100644
--- a/ui/utils/message.h
+++ b/ui/utils/message.h
@@ -29,6 +29,7 @@
 #include <QAction>
 #include <QDesktopServices>
 #include <QUrl>
+#include <QMap>
 
 #include "global.h"
 #include "resizer.h"
@@ -46,6 +47,8 @@ public:
     
     void setSender(const QString& sender);
     QString getId() const;
+    QString getSenderJid() const;
+    QString getSenderResource() const;
     QString getFileUrl() const;
     const Shared::Message& getMessage() const;
     
@@ -55,6 +58,9 @@ public:
     void showFile(const QString& path);
     void setProgress(qreal value);
     void setAvatarPath(const QString& p_path);
+    bool change(const QMap<QString, QVariant>& data);
+    
+    bool const outgoing;
     
 signals:
     void buttonClicked();
@@ -73,11 +79,15 @@ private:
     QLabel* file;
     QProgressBar* progress;
     QLabel* fileComment;
+    QLabel* statusIcon;
+    QLabel* editedLabel;
     Image* avatar;
     bool hasButton;
     bool hasProgress;
     bool hasFile;
     bool commentAdded;
+    bool hasStatusIcon;
+    bool hasEditedLabel;
   
 private:
     void hideButton();
diff --git a/ui/utils/messageline.cpp b/ui/utils/messageline.cpp
index c436c57..d941b2e 100644
--- a/ui/utils/messageline.cpp
+++ b/ui/utils/messageline.cpp
@@ -122,7 +122,7 @@ MessageLine::Position MessageLine::message(const Shared::Message& msg, bool forc
         if (room) {
             senderId = sender;
         } else {
-            QString jid = msg.getFromJid();
+            senderId = msg.getFromJid();
         }
         
         std::map<QString, Index>::iterator pItr = palMessages.find(senderId);
@@ -161,6 +161,48 @@ MessageLine::Position MessageLine::message(const Shared::Message& msg, bool forc
     return res;
 }
 
+void MessageLine::changeMessage(const QString& id, const QMap<QString, QVariant>& data)
+{
+    Index::const_iterator itr = messageIndex.find(id);
+    if (itr != messageIndex.end()) {
+        Message* msg = itr->second;
+        if (msg->change(data)) {                    //if ID changed (stanza in replace of another)
+            QString newId = msg->getId();           //need to updated IDs of that message in all maps
+            messageIndex.erase(itr);
+            messageIndex.insert(std::make_pair(newId, msg));
+            if (msg->outgoing) {
+                QString senderId;
+                if (room) {
+                    senderId = msg->getSenderResource();
+                } else {
+                    senderId = msg->getSenderJid();
+                }
+                
+                std::map<QString, Index>::iterator pItr = palMessages.find(senderId);
+                if (pItr != palMessages.end()) {
+                    Index::const_iterator sItr = pItr->second.find(id);
+                    if (sItr != pItr->second.end()) {
+                        pItr->second.erase(sItr);
+                        pItr->second.insert(std::make_pair(newId, msg));
+                    } else {
+                        qDebug() << "Was trying to replace message in open conversations, couldn't find it among pal's messages, probably an error"; 
+                    }
+                } else {
+                    qDebug() << "Was trying to replace message in open conversations, couldn't find pal messages, probably an error"; 
+                }
+            } else {
+                Index::const_iterator mItr = myMessages.find(id);
+                if (mItr != myMessages.end()) {
+                    myMessages.erase(mItr);
+                    myMessages.insert(std::make_pair(newId, msg));
+                } else {
+                    qDebug() << "Was trying to replace message in open conversations, couldn't find it among my messages, probably an error"; 
+                }
+            }
+        }
+    }
+}
+
 void MessageLine::onDownload()
 {
     Message* msg = static_cast<Message*>(sender());
diff --git a/ui/utils/messageline.h b/ui/utils/messageline.h
index 707ff39..16eea21 100644
--- a/ui/utils/messageline.h
+++ b/ui/utils/messageline.h
@@ -26,7 +26,7 @@
 #include <QResizeEvent>
 #include <QIcon>
 
-#include "../../global.h"
+#include "global.h"
 #include "message.h"
 #include "progress.h"
 
@@ -57,6 +57,7 @@ public:
     void setMyAvatarPath(const QString& p_path);
     void setPalAvatar(const QString& jid, const QString& path);
     void dropPalAvatar(const QString& jid);
+    void changeMessage(const QString& id, const QMap<QString, QVariant>& data);
     
 signals:
     void resize(int amount);
diff --git a/ui/widgets/conversation.cpp b/ui/widgets/conversation.cpp
index 6b3613f..0749daa 100644
--- a/ui/widgets/conversation.cpp
+++ b/ui/widgets/conversation.cpp
@@ -149,6 +149,11 @@ void Conversation::addMessage(const Shared::Message& data)
     }
 }
 
+void Conversation::changeMessage(const QString& id, const QMap<QString, QVariant>& data)
+{
+    line->changeMessage(id, data);
+}
+
 KeyEnterReceiver::KeyEnterReceiver(QObject* parent): QObject(parent), ownEvent(false) {}
 
 bool KeyEnterReceiver::eventFilter(QObject* obj, QEvent* event)
diff --git a/ui/widgets/conversation.h b/ui/widgets/conversation.h
index 311db65..d9f6dda 100644
--- a/ui/widgets/conversation.h
+++ b/ui/widgets/conversation.h
@@ -21,13 +21,15 @@
 
 #include <QWidget>
 #include <QScopedPointer>
+#include <QMap>
+
 #include "global.h"
 #include "order.h"
-#include "../models/account.h"
-#include "../utils/messageline.h"
-#include "../utils/resizer.h"
-#include "../utils/flowlayout.h"
-#include "../utils/badge.h"
+#include "ui/models/account.h"
+#include "ui/utils/messageline.h"
+#include "ui/utils/resizer.h"
+#include "ui/utils/flowlayout.h"
+#include "ui/utils/badge.h"
 
 namespace Ui
 {
@@ -79,6 +81,7 @@ public:
     void fileError(const QString& messageId, const QString& error);
     void responseFileProgress(const QString& messageId, qreal progress);
     virtual void setAvatar(const QString& path);
+    void changeMessage(const QString& id, const QMap<QString, QVariant>& data);
     
 signals:
     void sendMessage(const Shared::Message& message);

From 91cc851bfc4ff16e94f06b692a0f9d8c43ad7596 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Wed, 25 Mar 2020 18:28:36 +0300
Subject: [PATCH 049/281] delivery statuses now actually mean something for MUC
 messages

---
 core/account.cpp    | 24 +++++++++++++++---------
 core/account.h      |  2 ++
 core/rosteritem.cpp | 40 ++++++++++++++++++++++++++++++++++++++++
 core/rosteritem.h   |  2 ++
 core/squawk.cpp     | 10 ++++++++++
 core/squawk.h       |  2 ++
 main.cpp            |  1 +
 ui/widgets/room.cpp |  5 ++++-
 8 files changed, 76 insertions(+), 10 deletions(-)

diff --git a/core/account.cpp b/core/account.cpp
index a7f4801..b615c83 100644
--- a/core/account.cpp
+++ b/core/account.cpp
@@ -51,7 +51,8 @@ Account::Account(const QString& p_login, const QString& p_server, const QString&
     avatarHash(),
     avatarType(),
     ownVCardRequestInProgress(false),
-    network(p_net)
+    network(p_net),
+    pendingStateMessages()
 {
     config.setUser(p_login);
     config.setDomain(p_server);
@@ -594,7 +595,7 @@ QString Core::Account::getFullJid() const
 void Core::Account::sendMessage(const Shared::Message& data)
 {
     if (state == Shared::connected) {
-        QXmppMessage msg(data.getFrom(), data.getTo(), data.getBody(), data.getThread());
+        QXmppMessage msg(getFullJid(), data.getTo(), data.getBody(), data.getThread());
         msg.setId(data.getId());
         msg.setType(static_cast<QXmppMessage::Type>(data.getType()));       //it is safe here, my type is compatible
         msg.setOutOfBandUrl(data.getOutOfBandUrl());
@@ -611,9 +612,8 @@ void Core::Account::sendMessage(const Shared::Message& data)
         }
         
         if (ri != 0) {
-            if (!ri->isMuc()) {
-                ri->appendMessageToArchive(data);
-            }
+            ri->appendMessageToArchive(data);
+            pendingStateMessages.insert(std::make_pair(data.getId(), data.getPenPalJid()));
         }
         
         client.sendPacket(msg);
@@ -731,11 +731,17 @@ bool Core::Account::handleGroupMessage(const QXmppMessage& msg, bool outgoing, b
         } else {
             return false;
         }
-        cnt->appendMessageToArchive(sMsg);
         
-        QDateTime fiveMinsAgo = QDateTime::currentDateTime().addSecs(-300);
-        if (sMsg.getTime() > fiveMinsAgo) {     //otherwise it's considered a delayed delivery, most probably MUC history receipt
-            emit message(sMsg);
+        std::map<QString, QString>::const_iterator pItr = pendingStateMessages.find(id);
+        if (pItr != pendingStateMessages.end()) {
+            cnt->changeMessageState(id, Shared::Message::State::delivered);
+            emit changeMessage(jid, id, {{"state", static_cast<uint>(Shared::Message::State::delivered)}});
+        } else {
+            cnt->appendMessageToArchive(sMsg);
+            QDateTime minAgo = QDateTime::currentDateTime().addSecs(-60);
+            if (sMsg.getTime() > minAgo) {     //otherwise it's considered a delayed delivery, most probably MUC history receipt
+                emit message(sMsg);
+            }
         }
         
         if (!forwarded && !outgoing) {
diff --git a/core/account.h b/core/account.h
index 8978800..dcf32d3 100644
--- a/core/account.h
+++ b/core/account.h
@@ -113,6 +113,7 @@ signals:
     void addPresence(const QString& jid, const QString& name, const QMap<QString, QVariant>& data);
     void removePresence(const QString& jid, const QString& name);
     void message(const Shared::Message& data);
+    void changeMessage(const QString& jid, const QString& id, const QMap<QString, QVariant>& data);
     void responseArchive(const QString& jid, const std::list<Shared::Message>& list);
     void error(const QString& text);
     void addRoomParticipant(const QString& jid, const QString& nickName, const QMap<QString, QVariant>& data);
@@ -153,6 +154,7 @@ private:
     QString avatarType;
     bool ownVCardRequestInProgress;
     NetworkAccess* network;
+    std::map<QString, QString> pendingStateMessages;
     
 private slots:
     void onClientConnected();
diff --git a/core/rosteritem.cpp b/core/rosteritem.cpp
index b9801d9..712e4a5 100644
--- a/core/rosteritem.cpp
+++ b/core/rosteritem.cpp
@@ -221,6 +221,46 @@ void Core::RosterItem::appendMessageToArchive(const Shared::Message& msg)
     }
 }
 
+void Core::RosterItem::changeMessageState(const QString& id, Shared::Message::State newState) 
+{
+    bool found = false;
+    for (Shared::Message& msg : appendCache) {
+        if (msg.getId() == id) {
+            msg.setState(newState);
+            found = true;
+            break;
+        }
+    }
+    
+    if (!found) {
+        for (Shared::Message& msg : hisoryCache) {
+            if (msg.getId() == id) {
+                msg.setState(newState);
+                found = true;
+                break;
+            }
+        }
+    }
+    
+    if (!found) {
+        try {
+            archive->setMessageState(id, newState);
+            found = true;
+        } catch (const Archive::NotFound& e) {
+            qDebug() << "An attempt to change state to the message" << id << "but it couldn't be found";
+        }
+    }
+    
+    if (found) {
+        for (Shared::Message& msg : responseCache) {
+            if (msg.getId() == id) {
+                msg.setState(newState);
+                break;
+            }
+        }
+    }
+}
+
 void Core::RosterItem::flushMessagesToArchive(bool finished, const QString& firstId, const QString& lastId)
 {
     unsigned int added(0);
diff --git a/core/rosteritem.h b/core/rosteritem.h
index ec8a622..f18d42f 100644
--- a/core/rosteritem.h
+++ b/core/rosteritem.h
@@ -72,6 +72,8 @@ public:
     virtual Shared::VCard handleResponseVCard(const QXmppVCardIq& card, const QString& resource);
     virtual void handlePresence(const QXmppPresence& pres) = 0;
     
+    void changeMessageState(const QString& id, Shared::Message::State newState);
+    
 signals:
     void nameChanged(const QString& name);
     void subscriptionStateChanged(Shared::SubscriptionState state);
diff --git a/core/squawk.cpp b/core/squawk.cpp
index 3ccb224..75cceec 100644
--- a/core/squawk.cpp
+++ b/core/squawk.cpp
@@ -125,6 +125,7 @@ void Core::Squawk::addAccount(const QString& login, const QString& server, const
     connect(acc, &Account::addPresence, this, &Squawk::onAccountAddPresence);
     connect(acc, &Account::removePresence, this, &Squawk::onAccountRemovePresence);
     connect(acc, &Account::message, this, &Squawk::onAccountMessage);
+    connect(acc, &Account::changeMessage, this, &Squawk::onAccountChangeMessage);
     connect(acc, &Account::responseArchive, this, &Squawk::onAccountResponseArchive);
 
     connect(acc, &Account::addRoom, this, &Squawk::onAccountAddRoom);
@@ -488,6 +489,14 @@ void Core::Squawk::onAccountRemoveRoomPresence(const QString& jid, const QString
     emit removeRoomParticipant(acc->getName(), jid, nick);
 }
 
+
+
+void Core::Squawk::onAccountChangeMessage(const QString& jid, const QString& id, const QMap<QString, QVariant>& data)
+{
+    Account* acc = static_cast<Account*>(sender());
+    emit changeMessage(acc->getName(), jid, id, data);
+}
+
 void Core::Squawk::removeRoomRequest(const QString& account, const QString& jid)
 {
     AccountsMap::const_iterator itr = amap.find(account);
@@ -567,3 +576,4 @@ void Core::Squawk::uploadVCard(const QString& account, const Shared::VCard& card
     }
     itr->second->uploadVCard(card);
 }
+
diff --git a/core/squawk.h b/core/squawk.h
index e2a6b74..76f5170 100644
--- a/core/squawk.h
+++ b/core/squawk.h
@@ -69,6 +69,7 @@ signals:
     void uploadFileError(const QString& messageId, const QString& error);
     void uploadFileProgress(const QString& messageId, qreal value);
     void responseVCard(const QString& jid, const Shared::VCard& card);
+    void changeMessage(const QString& account, const QString& jid, const QString& id, const QMap<QString, QVariant>& data);
     
 public slots:
     void start();
@@ -131,6 +132,7 @@ private slots:
     void onAccountAddRoomPresence(const QString& jid, const QString& nick, const QMap<QString, QVariant>& data);
     void onAccountChangeRoomPresence(const QString& jid, const QString& nick, const QMap<QString, QVariant>& data);
     void onAccountRemoveRoomPresence(const QString& jid, const QString& nick);
+    void onAccountChangeMessage(const QString& jid, const QString& id, const QMap<QString, QVariant>& data);
 };
 
 }
diff --git a/main.cpp b/main.cpp
index a38852b..f3a410c 100644
--- a/main.cpp
+++ b/main.cpp
@@ -127,6 +127,7 @@ int main(int argc, char *argv[])
     QObject::connect(squawk, &Core::Squawk::removePresence, &w, &Squawk::removePresence);
     QObject::connect(squawk, &Core::Squawk::stateChanged, &w, &Squawk::stateChanged);
     QObject::connect(squawk, &Core::Squawk::accountMessage, &w, &Squawk::accountMessage);
+    QObject::connect(squawk, &Core::Squawk::changeMessage, &w, &Squawk::changeMessage);
     QObject::connect(squawk, &Core::Squawk::responseArchive, &w, &Squawk::responseArchive);
     QObject::connect(squawk, &Core::Squawk::addRoom, &w, &Squawk::addRoom);
     QObject::connect(squawk, &Core::Squawk::changeRoom, &w, &Squawk::changeRoom);
diff --git a/ui/widgets/room.cpp b/ui/widgets/room.cpp
index f495432..6d5340f 100644
--- a/ui/widgets/room.cpp
+++ b/ui/widgets/room.cpp
@@ -47,13 +47,16 @@ Room::~Room()
 void Room::handleSendMessage(const QString& text)
 {
     Shared::Message msg(Shared::Message::groupChat);
-    msg.setFrom(account->getFullJid());
+    msg.setFromJid(room->getJid());
+    msg.setFromResource(room->getNick());
     msg.setToJid(palJid);
     //msg.setToResource(activePalResource);
     msg.setBody(text);
     msg.setOutgoing(true);
     msg.generateRandomId();
     msg.setCurrentTime();
+    msg.setState(Shared::Message::State::pending);
+    addMessage(msg);
     emit sendMessage(msg);
 }
 

From 57d6e3adab4713498da754cb0ca7e459e58b6f9b Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Thu, 26 Mar 2020 18:08:44 +0300
Subject: [PATCH 050/281] Message error handling as state, errorText to store,
 fake ID for message without

---
 core/account.cpp     | 109 ++++++++++++++++++++++---------------------
 core/account.h       |   1 +
 global.cpp           |  29 ++++++++++++
 global.h             |   3 ++
 ui/utils/message.cpp |  24 +++++++---
 ui/utils/message.h   |   1 +
 6 files changed, 109 insertions(+), 58 deletions(-)

diff --git a/core/account.cpp b/core/account.cpp
index b615c83..1dc0d55 100644
--- a/core/account.cpp
+++ b/core/account.cpp
@@ -554,8 +554,26 @@ void Core::Account::onMessageReceived(const QXmppMessage& msg)
         case QXmppMessage::GroupChat:
             handled = handleGroupMessage(msg);
             break;
-        case QXmppMessage::Error:
-            qDebug() << "received a message with type \"Error\", not sure what to do with it now, skipping";
+        case QXmppMessage::Error: {
+                QString id = msg.id();
+                std::map<QString, QString>::const_iterator itr = pendingStateMessages.find(id);
+                if (itr != pendingStateMessages.end()) {
+                    QString jid = itr->second;
+                    RosterItem* cnt = getRosterItem(jid);
+                    if (cnt != 0) {
+                        cnt->changeMessageState(id, Shared::Message::State::error);
+                    }
+                    ;
+                    emit changeMessage(jid, id, {
+                        {"state", static_cast<uint>(Shared::Message::State::error)},
+                        {"errorText", msg.error().text()}
+                    });
+                    pendingStateMessages.erase(itr);
+                    handled = true;
+                } else {
+                    qDebug() << "received a message with type \"Error\", not sure what to do with it now, skipping";
+                }
+            }
             break;
         case QXmppMessage::Headline:
             qDebug() << "received a message with type \"Headline\", not sure what to do with it now, skipping";
@@ -735,12 +753,15 @@ bool Core::Account::handleGroupMessage(const QXmppMessage& msg, bool outgoing, b
         std::map<QString, QString>::const_iterator pItr = pendingStateMessages.find(id);
         if (pItr != pendingStateMessages.end()) {
             cnt->changeMessageState(id, Shared::Message::State::delivered);
+            pendingStateMessages.erase(pItr);
             emit changeMessage(jid, id, {{"state", static_cast<uint>(Shared::Message::State::delivered)}});
         } else {
             cnt->appendMessageToArchive(sMsg);
             QDateTime minAgo = QDateTime::currentDateTime().addSecs(-60);
             if (sMsg.getTime() > minAgo) {     //otherwise it's considered a delayed delivery, most probably MUC history receipt
                 emit message(sMsg);
+            } else {
+                //qDebug() << "Delayed delivery: ";
             }
         }
         
@@ -761,7 +782,12 @@ bool Core::Account::handleGroupMessage(const QXmppMessage& msg, bool outgoing, b
 void Core::Account::initializeMessage(Shared::Message& target, const QXmppMessage& source, bool outgoing, bool forwarded, bool guessing) const
 {
     const QDateTime& time(source.stamp());
-    target.setId(source.id());
+    QString id = source.id();
+    if (id.size() == 0) {
+        target.generateRandomId();
+    } else {
+        target.setId(id);
+    }
     target.setFrom(source.from());
     target.setTo(source.to());
     target.setBody(source.body());
@@ -787,17 +813,7 @@ void Core::Account::onMamMessageReceived(const QString& queryId, const QXmppMess
     if (msg.id().size() > 0 && (msg.body().size() > 0 || msg.outOfBandUrl().size() > 0)) {
         std::map<QString, QString>::const_iterator itr = achiveQueries.find(queryId);
         QString jid = itr->second;
-        RosterItem* item = 0;
-        std::map<QString, Contact*>::const_iterator citr = contacts.find(jid);
-
-        if (citr != contacts.end()) {
-            item = citr->second;
-        } else {
-            std::map<QString, Conference*>::const_iterator coitr = conferences.find(jid);
-            if (coitr != conferences.end()) {
-                item = coitr->second;
-            }
-        }
+        RosterItem* item = getRosterItem(jid);
         
         Shared::Message sMsg(Shared::Message::chat);
         initializeMessage(sMsg, msg, false, true, true);
@@ -808,21 +824,27 @@ void Core::Account::onMamMessageReceived(const QString& queryId, const QXmppMess
     //handleChatMessage(msg, false, true, true);
 }
 
+Core::RosterItem * Core::Account::getRosterItem(const QString& jid)
+{
+    RosterItem* item = 0;
+    std::map<QString, Contact*>::const_iterator citr = contacts.find(jid);
+    if (citr != contacts.end()) {
+        item = citr->second;
+    } else {
+        std::map<QString, Conference*>::const_iterator coitr = conferences.find(jid);
+        if (coitr != conferences.end()) {
+            item = coitr->second;
+        }
+    }
+    
+    return item;
+}
+
+
 void Core::Account::requestArchive(const QString& jid, int count, const QString& before)
 {
     qDebug() << "An archive request for " << jid << ", before " << before;
-    RosterItem* contact = 0;
-    std::map<QString, Contact*>::const_iterator itr = contacts.find(jid);
-    bool gr = false;
-    if (itr != contacts.end()) {
-        contact = itr->second;
-    } else {
-        std::map<QString, Conference*>::const_iterator citr = conferences.find(jid);
-        if (citr != conferences.end()) {
-            contact = citr->second;
-            gr = true;
-        }
-    }
+    RosterItem* contact = getRosterItem(jid);
     
     if (contact == 0) {
         qDebug() << "An attempt to request archive for" << jid << "in account" << name << ", but the contact with such id wasn't found, skipping";
@@ -834,7 +856,7 @@ void Core::Account::requestArchive(const QString& jid, int count, const QString&
         QXmppMessage msg(getFullJid(), jid, "", "");
         QString last = Shared::generateUUID();
         msg.setId(last);
-        if (gr) {
+        if (contact->isMuc()) {
             msg.setType(QXmppMessage::GroupChat);
         } else {
             msg.setType(QXmppMessage::Chat);
@@ -883,18 +905,9 @@ void Core::Account::onMamResultsReceived(const QString& queryId, const QXmppResu
 {
     std::map<QString, QString>::const_iterator itr = achiveQueries.find(queryId);
     QString jid = itr->second;
-    RosterItem* ri = 0;
-    
     achiveQueries.erase(itr);
-    std::map<QString, Contact*>::const_iterator citr = contacts.find(jid);
-    if (citr != contacts.end()) {
-        ri = citr->second;
-    } else {
-        std::map<QString, Conference*>::const_iterator coitr = conferences.find(jid);
-        if (coitr != conferences.end()) {
-            ri = coitr->second;
-        }
-    }
+    
+    RosterItem* ri = getRosterItem(jid);
     
     if (ri != 0) {
         qDebug() << "Flushing messages for" << jid;
@@ -1392,23 +1405,15 @@ void Core::Account::onVCardReceived(const QXmppVCardIq& card)
         resource = comps.back();
     }
     pendingVCardRequests.erase(id);
-    RosterItem* item = 0;
+    RosterItem* item = getRosterItem(jid);
     
-    std::map<QString, Contact*>::const_iterator contItr = contacts.find(jid);
-    if (contItr == contacts.end()) {
-        std::map<QString, Conference*>::const_iterator confItr = conferences.find(jid);
-        if (confItr == conferences.end()) {
-            if (jid == getLogin() + "@" + getServer()) {
-                onOwnVCardReceived(card);
-            } else {
-                qDebug() << "received vCard" << jid << "doesn't belong to any of known contacts or conferences, skipping";
-            }
-            return;
+    if (item == 0) {
+        if (jid == getLogin() + "@" + getServer()) {
+            onOwnVCardReceived(card);
         } else {
-            item = confItr->second;
+            qDebug() << "received vCard" << jid << "doesn't belong to any of known contacts or conferences, skipping";
         }
-    } else {
-        item = contItr->second;
+        return;
     }
     
     Shared::VCard vCard = item->handleResponseVCard(card, resource);
diff --git a/core/account.h b/core/account.h
index dcf32d3..69e17a7 100644
--- a/core/account.h
+++ b/core/account.h
@@ -223,6 +223,7 @@ private:
     void storeConferences();
     void clearConferences();
     void sendMessageWithLocalUploadedFile(Shared::Message msg, const QString& url);
+    RosterItem* getRosterItem(const QString& jid);
 };
 
 void initializeVCard(Shared::VCard& vCard, const QXmppVCardIq& card);
diff --git a/global.cpp b/global.cpp
index 6ff87c5..4cdfa8d 100644
--- a/global.cpp
+++ b/global.cpp
@@ -147,6 +147,11 @@ QString Shared::Message::getToResource() const
     return rTo;
 }
 
+QString Shared::Message::getErrorText() const
+{
+    return errorText;
+}
+
 QString Shared::Message::getPenPalJid() const
 {
     if (outgoing) {
@@ -195,6 +200,13 @@ void Shared::Message::setToResource(const QString& to)
     rTo = to;
 }
 
+void Shared::Message::setErrorText(const QString& err)
+{
+    if (state == State::error) {
+        errorText = err;
+    }
+}
+
 bool Shared::Message::getOutgoing() const
 {
     return outgoing;
@@ -243,6 +255,10 @@ void Shared::Message::setType(Shared::Message::Type t)
 void Shared::Message::setState(Shared::Message::State p_state)
 {
     state = p_state;
+    
+    if (state != State::error) {
+        errorText = "";
+    }
 }
 
 void Shared::Message::setEdited(bool p_edited)
@@ -266,6 +282,9 @@ void Shared::Message::serialize(QDataStream& data) const
     data << oob;
     data << (quint8)state;
     data << edited;
+    if (state == State::error) {
+        data << errorText;
+    }
 }
 
 void Shared::Message::deserialize(QDataStream& data)
@@ -288,6 +307,9 @@ void Shared::Message::deserialize(QDataStream& data)
     data >> s;
     state = static_cast<State>(s);
     data >> edited;
+    if (state == State::error) {
+        data >> errorText;
+    }
 }
 
 bool Shared::Message::change(const QMap<QString, QVariant>& data)
@@ -297,6 +319,13 @@ bool Shared::Message::change(const QMap<QString, QVariant>& data)
         setState(static_cast<State>(itr.value().toUInt()));
     }
     
+    if (state == State::error) {
+        itr = data.find("errorText");
+        if (itr != data.end()) {
+            setErrorText(itr.value().toString());
+        }
+    }
+    
     bool idChanged = false;
     itr = data.find("id");
     if (itr != data.end()) {
diff --git a/global.h b/global.h
index 29b7969..e0a0796 100644
--- a/global.h
+++ b/global.h
@@ -190,6 +190,7 @@ public:
     void setOutOfBandUrl(const QString& url);
     void setState(State p_state);
     void setEdited(bool p_edited);
+    void setErrorText(const QString& err);
     bool change(const QMap<QString, QVariant>& data);
     
     QString getFrom() const;
@@ -210,6 +211,7 @@ public:
     QString getOutOfBandUrl() const;
     State getState() const;
     bool getEdited() const;
+    QString getErrorText() const;
     
     QString getPenPalJid() const;
     QString getPenPalResource() const;
@@ -233,6 +235,7 @@ private:
     QString oob;
     State state;
     bool edited;
+    QString errorText;
 };
 
 class VCard {
diff --git a/ui/utils/message.cpp b/ui/utils/message.cpp
index f3f162b..26fbd99 100644
--- a/ui/utils/message.cpp
+++ b/ui/utils/message.cpp
@@ -108,9 +108,7 @@ Message::Message(const Shared::Message& source, bool p_outgoing, const QString&
         sender->setAlignment(Qt::AlignRight);
         date->setAlignment(Qt::AlignRight);
         statusIcon = new QLabel();
-        QIcon q(Shared::icon(Shared::messageStateThemeIcons[static_cast<uint8_t>(source.getState())]));
-        statusIcon->setToolTip(QCoreApplication::translate("Global", Shared::messageStateNames[static_cast<uint8_t>(source.getState())].toLatin1()));
-        statusIcon->setPixmap(q.pixmap(12, 12));
+        setState();
         statusLay->addWidget(statusIcon);
         statusLay->addWidget(date);
         layout->addStretch();
@@ -333,11 +331,25 @@ bool Message::change(const QMap<QString, QVariant>& data)
         }
     }
     if (hasStatusIcon) {
-        QIcon q(Shared::icon(Shared::messageStateThemeIcons[static_cast<uint8_t>(msg.getState())]));
-        statusIcon->setToolTip(QCoreApplication::translate("Global", Shared::messageStateNames[static_cast<uint8_t>(msg.getState())].toLatin1()));
-        statusIcon->setPixmap(q.pixmap(12, 12));
+        setState();
     }
     
     
     return idChanged;
 }
+
+void Message::setState()
+{
+    Shared::Message::State state = msg.getState();
+    QIcon q(Shared::icon(Shared::messageStateThemeIcons[static_cast<uint8_t>(state)]));
+    QString tt = QCoreApplication::translate("Global", Shared::messageStateNames[static_cast<uint8_t>(state)].toLatin1());
+    if (state == Shared::Message::State::error) {
+        QString errText = msg.getErrorText();
+        if (errText.size() > 0) {
+            tt += ": " + errText;
+        }
+    }
+    statusIcon->setToolTip(tt);
+    statusIcon->setPixmap(q.pixmap(12, 12));
+}
+
diff --git a/ui/utils/message.h b/ui/utils/message.h
index b041b87..baac728 100644
--- a/ui/utils/message.h
+++ b/ui/utils/message.h
@@ -93,6 +93,7 @@ private:
     void hideButton();
     void hideProgress();
     void hideFile();
+    void setState();
 };
 
 #endif // MESSAGE_H

From fe1ae8567a1a490c115683f0fe967c45f17e6bae Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Fri, 27 Mar 2020 23:59:30 +0300
Subject: [PATCH 051/281] Message receipt manager not takes care of the message
 reception, switched on embedded reconnect

---
 core/account.cpp    | 114 ++++++++++++++++++++++++++++----------------
 core/account.h      |   7 ++-
 core/archive.cpp    |  29 +++++++++--
 core/archive.h      |   6 +--
 core/rosteritem.cpp |  10 ++--
 core/rosteritem.h   |   2 +-
 ui/widgets/chat.cpp |   1 +
 7 files changed, 113 insertions(+), 56 deletions(-)

diff --git a/core/account.cpp b/core/account.cpp
index 1dc0d55..6e7d782 100644
--- a/core/account.cpp
+++ b/core/account.cpp
@@ -40,6 +40,7 @@ Account::Account(const QString& p_login, const QString& p_server, const QString&
     vm(client.findExtension<QXmppVCardManager>()),
     um(new QXmppUploadRequestManager()),
     dm(client.findExtension<QXmppDiscoveryManager>()),
+    rcpm(new QXmppMessageReceiptManager()),
     contacts(),
     conferences(),
     maxReconnectTimes(0),
@@ -58,6 +59,7 @@ Account::Account(const QString& p_login, const QString& p_server, const QString&
     config.setDomain(p_server);
     config.setPassword(p_password);
     config.setAutoAcceptSubscriptions(true);
+    config.setAutoReconnectionEnabled(false);
     
     QObject::connect(&client, &QXmppClient::connected, this, &Account::onClientConnected);
     QObject::connect(&client, &QXmppClient::disconnected, this, &Account::onClientDisconnected);
@@ -101,6 +103,10 @@ Account::Account(const QString& p_login, const QString& p_server, const QString&
     QObject::connect(network, &NetworkAccess::uploadFileComplete, this, &Account::onFileUploaded);
     QObject::connect(network, &NetworkAccess::uploadFileError, this, &Account::onFileUploadError);
     
+    client.addExtension(rcpm);
+    QObject::connect(rcpm, &QXmppMessageReceiptManager::messageDelivered, this, &Account::onReceiptReceived);
+    
+    
     QString path(QStandardPaths::writableLocation(QStandardPaths::CacheLocation));
     path += "/" + name;
     QDir dir(path);
@@ -160,6 +166,8 @@ Account::~Account()
         delete itr->second;
     }
     
+    delete rcpm;
+    delete dm;
     delete um;
     delete bm;
     delete mm;
@@ -200,8 +208,8 @@ void Core::Account::onClientConnected()
     if (state == Shared::connecting) {
         reconnectTimes = maxReconnectTimes;
         state = Shared::connected;
-        cm->setCarbonsEnabled(true);
         dm->requestItems(getServer());
+        dm->requestInfo(getServer());
         emit connectionStateChanged(state);
     } else {
         qDebug() << "Something weird had happened - xmpp client reported about successful connection but account wasn't in" << state << "state";
@@ -560,14 +568,15 @@ void Core::Account::onMessageReceived(const QXmppMessage& msg)
                 if (itr != pendingStateMessages.end()) {
                     QString jid = itr->second;
                     RosterItem* cnt = getRosterItem(jid);
-                    if (cnt != 0) {
-                        cnt->changeMessageState(id, Shared::Message::State::error);
-                    }
-                    ;
-                    emit changeMessage(jid, id, {
+                    QMap<QString, QVariant> cData = {
                         {"state", static_cast<uint>(Shared::Message::State::error)},
                         {"errorText", msg.error().text()}
-                    });
+                    };
+                    if (cnt != 0) {
+                        cnt->changeMessage(id, cData);
+                    }
+                    ;
+                    emit changeMessage(jid, id, cData);
                     pendingStateMessages.erase(itr);
                     handled = true;
                 } else {
@@ -610,35 +619,43 @@ QString Core::Account::getFullJid() const
     return getLogin() + "@" + getServer() + "/" + getResource();
 }
 
-void Core::Account::sendMessage(const Shared::Message& data)
+void Core::Account::sendMessage(Shared::Message data)
 {
+    QString jid = data.getPenPalJid();
+    QString id = data.getId();
+    RosterItem* ri = getRosterItem(jid);
     if (state == Shared::connected) {
         QXmppMessage msg(getFullJid(), data.getTo(), data.getBody(), data.getThread());
-        msg.setId(data.getId());
+        msg.setId(id);
         msg.setType(static_cast<QXmppMessage::Type>(data.getType()));       //it is safe here, my type is compatible
         msg.setOutOfBandUrl(data.getOutOfBandUrl());
+        msg.setReceiptRequested(true);
         
-        RosterItem* ri = 0;
-        std::map<QString, Contact*>::const_iterator itr = contacts.find(data.getPenPalJid());
-        if (itr != contacts.end()) {
-            ri = itr->second;
+        bool sent = client.sendPacket(msg);
+        
+        if (sent) {
+            data.setState(Shared::Message::State::sent);
         } else {
-            std::map<QString, Conference*>::const_iterator ritr = conferences.find(data.getPenPalJid());
-            if (ritr != conferences.end()) {
-                ri = ritr->second;
-            }
+            data.setState(Shared::Message::State::error);
+            data.setErrorText("Couldn't send message via QXMPP library check out logs");
         }
         
         if (ri != 0) {
             ri->appendMessageToArchive(data);
-            pendingStateMessages.insert(std::make_pair(data.getId(), data.getPenPalJid()));
+            if (sent) {
+                pendingStateMessages.insert(std::make_pair(id, jid));
+            }
         }
         
-        client.sendPacket(msg);
-        
     } else {
-        qDebug() << "An attempt to send message with not connected account " << name << ", skipping";
+        data.setState(Shared::Message::State::error);
+        data.setErrorText("You are is offline or reconnecting");
     }
+    
+    emit changeMessage(jid, id, {
+        {"state", static_cast<uint>(data.getState())},
+        {"errorText", data.getErrorText()}
+    });
 }
 
 void Core::Account::sendMessage(const Shared::Message& data, const QString& path)
@@ -659,7 +676,7 @@ void Core::Account::sendMessage(const Shared::Message& data, const QString& path
                             um->requestUploadSlot(file);
                         }
                     } else {
-                        emit onFileUploadError(data.getId(), "Uploading file dissapeared or your system user has no permission to read it");
+                        emit onFileUploadError(data.getId(), "Uploading file no longer exists or your system user has no permission to read it");
                         qDebug() << "Requested upload slot in account" << name << "for file" << path << "but the file doesn't exist or is not readable";
                     }
                 } else {
@@ -717,18 +734,17 @@ bool Core::Account::handleChatMessage(const QXmppMessage& msg, bool outgoing, bo
             }));
             handleNewContact(cnt);
         }
+        if (outgoing) {
+            if (forwarded) {
+                sMsg.setState(Shared::Message::State::sent);
+            }
+        } else {
+            sMsg.setState(Shared::Message::State::delivered);
+        }
         cnt->appendMessageToArchive(sMsg);
         
         emit message(sMsg);
         
-        if (!forwarded && !outgoing) {
-            if (msg.isReceiptRequested() && id.size() > 0) {
-                QXmppMessage receipt(getFullJid(), msg.from(), "");
-                receipt.setReceiptId(id);
-                client.sendPacket(receipt);
-            }
-        }
-        
         return true;
     }
     return false;
@@ -752,9 +768,10 @@ bool Core::Account::handleGroupMessage(const QXmppMessage& msg, bool outgoing, b
         
         std::map<QString, QString>::const_iterator pItr = pendingStateMessages.find(id);
         if (pItr != pendingStateMessages.end()) {
-            cnt->changeMessageState(id, Shared::Message::State::delivered);
+            QMap<QString, QVariant> cData = {{"state", static_cast<uint>(Shared::Message::State::delivered)}};
+            cnt->changeMessage(id, cData);
             pendingStateMessages.erase(pItr);
-            emit changeMessage(jid, id, {{"state", static_cast<uint>(Shared::Message::State::delivered)}});
+            emit changeMessage(jid, id, cData);
         } else {
             cnt->appendMessageToArchive(sMsg);
             QDateTime minAgo = QDateTime::currentDateTime().addSecs(-60);
@@ -765,14 +782,6 @@ bool Core::Account::handleGroupMessage(const QXmppMessage& msg, bool outgoing, b
             }
         }
         
-        if (!forwarded && !outgoing) {
-            if (msg.isReceiptRequested() && id.size() > 0) {
-                QXmppMessage receipt(getFullJid(), msg.from(), "");
-                receipt.setReceiptId(id);
-                client.sendPacket(receipt);
-            }
-        }
-        
         return true;
     }
     return false;
@@ -1658,11 +1667,32 @@ void Core::Account::onFileUploadError(const QString& messageId, const QString& e
 void Core::Account::onDiscoveryItemsReceived(const QXmppDiscoveryIq& items)
 {
     for (QXmppDiscoveryIq::Item item : items.items()) {
-        dm->requestInfo(item.jid());
+        if (item.jid() != getServer()) {
+            dm->requestInfo(item.jid());
+        }
     }
 }
 
 void Core::Account::onDiscoveryInfoReceived(const QXmppDiscoveryIq& info)
 {
-
+    if (info.from() == getServer()) {
+        if (info.features().contains("urn:xmpp:carbons:2")) {
+            cm->setCarbonsEnabled(true);
+        }
+    }
 }
+
+void Core::Account::onReceiptReceived(const QString& jid, const QString& id)
+{
+    std::map<QString, QString>::const_iterator itr = pendingStateMessages.find(id);
+    if (itr != pendingStateMessages.end()) {
+        QMap<QString, QVariant> cData = {{"state", static_cast<uint>(Shared::Message::State::delivered)}};
+        RosterItem* ri = getRosterItem(itr->second);
+        if (ri != 0) {
+            ri->changeMessage(id, cData);
+        }
+        pendingStateMessages.erase(itr);
+        emit changeMessage(itr->second, id, cData);
+    }
+}
+
diff --git a/core/account.h b/core/account.h
index 69e17a7..02ae2ab 100644
--- a/core/account.h
+++ b/core/account.h
@@ -41,6 +41,8 @@
 #include <QXmppHttpUploadIq.h>
 #include <QXmppVCardIq.h>
 #include <QXmppVCardManager.h>
+#include <QXmppMessageReceiptManager.h>
+
 #include "global.h"
 #include "contact.h"
 #include "conference.h"
@@ -76,7 +78,7 @@ public:
     void setResource(const QString& p_resource);
     void setAvailability(Shared::Availability avail);
     QString getFullJid() const;
-    void sendMessage(const Shared::Message& data);
+    void sendMessage(Shared::Message data);
     void sendMessage(const Shared::Message& data, const QString& path);
     void requestArchive(const QString& jid, int count, const QString& before);
     void setReconnectTimes(unsigned int times);
@@ -139,6 +141,7 @@ private:
     QXmppVCardManager* vm;
     QXmppUploadRequestManager* um;
     QXmppDiscoveryManager* dm;
+    QXmppMessageReceiptManager* rcpm;
     std::map<QString, Contact*> contacts;
     std::map<QString, Conference*> conferences;
     unsigned int maxReconnectTimes;
@@ -206,6 +209,8 @@ private slots:
     void onFileUploadError(const QString& messageId, const QString& errMsg);
     void onDiscoveryItemsReceived (const QXmppDiscoveryIq& items);
     void onDiscoveryInfoReceived (const QXmppDiscoveryIq& info);
+    
+    void onReceiptReceived(const QString& jid, const QString &id);
   
 private:
     void addedAccount(const QString &bareJid);
diff --git a/core/archive.cpp b/core/archive.cpp
index 15d8011..3afd706 100644
--- a/core/archive.cpp
+++ b/core/archive.cpp
@@ -208,7 +208,7 @@ Shared::Message Core::Archive::getMessage(const std::string& id, MDB_txn* txn)
     }
 }
 
-void Core::Archive::setMessageState(const QString& id, Shared::Message::State state)
+void Core::Archive::changeMessage(const QString& id, const QMap<QString, QVariant>& data)
 {
     if (!opened) {
         throw Closed("setMessageState", jid.toStdString());
@@ -220,21 +220,42 @@ void Core::Archive::setMessageState(const QString& id, Shared::Message::State st
     std::string strId(id.toStdString());
     try {
         Shared::Message msg = getMessage(strId, txn);
-        msg.setState(state);
+        QDateTime oTime = msg.getTime();
+        bool idChange = msg.change(data);
         
         MDB_val lmdbKey, lmdbData;
         QByteArray ba;
         QDataStream ds(&ba, QIODevice::WriteOnly);
         msg.serialize(ds);
+        
         lmdbKey.mv_size = strId.size();
         lmdbKey.mv_data = (char*)strId.c_str();
+        int rc;
+        if (idChange) {
+            rc = mdb_del(txn, main, &lmdbKey, &lmdbData);
+            if (rc == 0) {
+                strId = msg.getId().toStdString();
+                lmdbKey.mv_size = strId.size();
+                lmdbKey.mv_data = (char*)strId.c_str();
+                
+                
+                quint64 stamp = oTime.toMSecsSinceEpoch();
+                lmdbData.mv_data = (quint8*)&stamp;
+                lmdbData.mv_size = 8;
+                rc = mdb_put(txn, order, &lmdbData, &lmdbKey, 0);
+                if (rc != 0) {
+                    throw Unknown(jid.toStdString(), mdb_strerror(rc));
+                }
+            } else {
+                throw Unknown(jid.toStdString(), mdb_strerror(rc));
+            }
+        }
         lmdbData.mv_size = ba.size();
         lmdbData.mv_data = (uint8_t*)ba.data();
-        int rc = mdb_put(txn, main, &lmdbKey, &lmdbData, 0);
+        rc = mdb_put(txn, main, &lmdbKey, &lmdbData, 0);
         if (rc == 0) {
             rc = mdb_txn_commit(txn);
         } else {
-            mdb_txn_abort(txn);
             throw Unknown(jid.toStdString(), mdb_strerror(rc));
         }
         
diff --git a/core/archive.h b/core/archive.h
index 36dbdbd..15c91b9 100644
--- a/core/archive.h
+++ b/core/archive.h
@@ -24,9 +24,9 @@
 #include <QMimeDatabase>
 #include <QMimeType>
 
-#include "../global.h"
+#include "global.h"
+#include "exception.h"
 #include <lmdb.h>
-#include "../exception.h"
 #include <list>
 
 namespace Core {
@@ -46,7 +46,7 @@ public:
     bool addElement(const Shared::Message& message);
     unsigned int addElements(const std::list<Shared::Message>& messages);
     Shared::Message getElement(const QString& id);
-    void setMessageState(const QString& id, Shared::Message::State state);
+    void changeMessage(const QString& id, const QMap<QString, QVariant>& data);
     Shared::Message oldest();
     QString oldestId();
     Shared::Message newest();
diff --git a/core/rosteritem.cpp b/core/rosteritem.cpp
index 712e4a5..4d1cca2 100644
--- a/core/rosteritem.cpp
+++ b/core/rosteritem.cpp
@@ -221,12 +221,12 @@ void Core::RosterItem::appendMessageToArchive(const Shared::Message& msg)
     }
 }
 
-void Core::RosterItem::changeMessageState(const QString& id, Shared::Message::State newState) 
+void Core::RosterItem::changeMessage(const QString& id, const QMap<QString, QVariant>& data)
 {
     bool found = false;
     for (Shared::Message& msg : appendCache) {
         if (msg.getId() == id) {
-            msg.setState(newState);
+            msg.change(data);
             found = true;
             break;
         }
@@ -235,7 +235,7 @@ void Core::RosterItem::changeMessageState(const QString& id, Shared::Message::St
     if (!found) {
         for (Shared::Message& msg : hisoryCache) {
             if (msg.getId() == id) {
-                msg.setState(newState);
+                msg.change(data);
                 found = true;
                 break;
             }
@@ -244,7 +244,7 @@ void Core::RosterItem::changeMessageState(const QString& id, Shared::Message::St
     
     if (!found) {
         try {
-            archive->setMessageState(id, newState);
+            archive->changeMessage(id, data);
             found = true;
         } catch (const Archive::NotFound& e) {
             qDebug() << "An attempt to change state to the message" << id << "but it couldn't be found";
@@ -254,7 +254,7 @@ void Core::RosterItem::changeMessageState(const QString& id, Shared::Message::St
     if (found) {
         for (Shared::Message& msg : responseCache) {
             if (msg.getId() == id) {
-                msg.setState(newState);
+                msg.change(data);
                 break;
             }
         }
diff --git a/core/rosteritem.h b/core/rosteritem.h
index f18d42f..f9aa8be 100644
--- a/core/rosteritem.h
+++ b/core/rosteritem.h
@@ -72,7 +72,7 @@ public:
     virtual Shared::VCard handleResponseVCard(const QXmppVCardIq& card, const QString& resource);
     virtual void handlePresence(const QXmppPresence& pres) = 0;
     
-    void changeMessageState(const QString& id, Shared::Message::State newState);
+    void changeMessage(const QString& id, const QMap<QString, QVariant>& data);
     
 signals:
     void nameChanged(const QString& name);
diff --git a/ui/widgets/chat.cpp b/ui/widgets/chat.cpp
index b8f49ba..41c79be 100644
--- a/ui/widgets/chat.cpp
+++ b/ui/widgets/chat.cpp
@@ -71,6 +71,7 @@ void Chat::handleSendMessage(const QString& text)
     msg.setOutgoing(true);
     msg.generateRandomId();
     msg.setCurrentTime();
+    msg.setState(Shared::Message::State::pending);
     addMessage(msg);
     emit sendMessage(msg);
 }

From ff2c9831cff51e11c12732d3532a51a5b251ad29 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Sat, 28 Mar 2020 17:05:49 +0300
Subject: [PATCH 052/281] corrected messages now are supposed to display
 correctly

---
 core/account.cpp     | 46 ++++++++++++++++-----
 core/archive.cpp     | 97 +++++++++++++++++++++++++++++---------------
 core/archive.h       |  2 +
 core/rosteritem.cpp  | 35 +++++++++++++++-
 core/rosteritem.h    |  4 +-
 global.cpp           | 43 ++++++++++++++++++--
 global.h             |  5 +++
 ui/utils/message.cpp | 32 ++++++++-------
 ui/utils/message.h   |  1 +
 9 files changed, 202 insertions(+), 63 deletions(-)

diff --git a/core/account.cpp b/core/account.cpp
index 6e7d782..724bfa1 100644
--- a/core/account.cpp
+++ b/core/account.cpp
@@ -716,7 +716,6 @@ bool Core::Account::handleChatMessage(const QXmppMessage& msg, bool outgoing, bo
 {
     const QString& body(msg.body());
     if (body.size() != 0) {
-        const QString& id(msg.id());
         Shared::Message sMsg(Shared::Message::chat);
         initializeMessage(sMsg, msg, outgoing, forwarded, guessing);
         QString jid = sMsg.getPenPalJid();
@@ -741,9 +740,18 @@ bool Core::Account::handleChatMessage(const QXmppMessage& msg, bool outgoing, bo
         } else {
             sMsg.setState(Shared::Message::State::delivered);
         }
-        cnt->appendMessageToArchive(sMsg);
-        
-        emit message(sMsg);
+        QString oId = msg.replaceId();
+        if (oId.size() > 0) {
+            QMap<QString, QVariant> cData = {
+                {"body", sMsg.getBody()},
+                {"stamp", sMsg.getTime()}
+            };
+            cnt->correctMessageInArchive(oId, sMsg);
+            emit changeMessage(jid, oId, cData);
+        } else {
+            cnt->appendMessageToArchive(sMsg);
+            emit message(sMsg);
+        }
         
         return true;
     }
@@ -773,12 +781,22 @@ bool Core::Account::handleGroupMessage(const QXmppMessage& msg, bool outgoing, b
             pendingStateMessages.erase(pItr);
             emit changeMessage(jid, id, cData);
         } else {
-            cnt->appendMessageToArchive(sMsg);
-            QDateTime minAgo = QDateTime::currentDateTime().addSecs(-60);
-            if (sMsg.getTime() > minAgo) {     //otherwise it's considered a delayed delivery, most probably MUC history receipt
-                emit message(sMsg);
+            QString oId = msg.replaceId();
+            if (oId.size() > 0) {
+                QMap<QString, QVariant> cData = {
+                    {"body", sMsg.getBody()},
+                    {"stamp", sMsg.getTime()}
+                };
+                cnt->correctMessageInArchive(oId, sMsg);
+                emit changeMessage(jid, oId, cData);
             } else {
-                //qDebug() << "Delayed delivery: ";
+                cnt->appendMessageToArchive(sMsg);
+                QDateTime minAgo = QDateTime::currentDateTime().addSecs(-60);
+                if (sMsg.getTime() > minAgo) {     //otherwise it's considered a delayed delivery, most probably MUC history receipt
+                    emit message(sMsg);
+                } else {
+                    //qDebug() << "Delayed delivery: ";
+                }
             }
         }
         
@@ -824,10 +842,16 @@ void Core::Account::onMamMessageReceived(const QString& queryId, const QXmppMess
         QString jid = itr->second;
         RosterItem* item = getRosterItem(jid);
         
-        Shared::Message sMsg(Shared::Message::chat);
+        Shared::Message sMsg(static_cast<Shared::Message::Type>(msg.type()));
         initializeMessage(sMsg, msg, false, true, true);
+        sMsg.setState(Shared::Message::State::sent);
         
-        item->addMessageToArchive(sMsg);
+        QString oId = msg.replaceId();
+        if (oId.size() > 0) {
+            item->correctMessageInArchive(oId, sMsg);
+        } else {
+            item->addMessageToArchive(sMsg);
+        }
     } 
     
     //handleChatMessage(msg, false, true, true);
diff --git a/core/archive.cpp b/core/archive.cpp
index 3afd706..bd159ca 100644
--- a/core/archive.cpp
+++ b/core/archive.cpp
@@ -267,8 +267,7 @@ void Core::Archive::changeMessage(const QString& id, const QMap<QString, QVarian
 
 Shared::Message Core::Archive::newest()
 {
-    QString id = newestId();
-    return getElement(id);
+    return edge(true);
 }
 
 QString Core::Archive::newestId()
@@ -276,25 +275,8 @@ QString Core::Archive::newestId()
     if (!opened) {
         throw Closed("newestId", jid.toStdString());
     }
-    MDB_txn *txn;
-    int rc;
-    rc = mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn);
-    MDB_cursor* cursor;
-    rc = mdb_cursor_open(txn, order, &cursor);
-    MDB_val lmdbKey, lmdbData;
-    
-    rc = mdb_cursor_get(cursor, &lmdbKey, &lmdbData, MDB_LAST);
-    if (rc) {
-        qDebug() << "Error geting newestId " << mdb_strerror(rc);
-        mdb_cursor_close(cursor);
-        mdb_txn_abort(txn);
-        throw Empty(jid.toStdString());
-    } else {
-        std::string sId((char*)lmdbData.mv_data, lmdbData.mv_size);
-        mdb_cursor_close(cursor);
-        mdb_txn_abort(txn);
-        return sId.c_str();
-    }
+    Shared::Message msg = newest();
+    return msg.getId();
 }
 
 QString Core::Archive::oldestId()
@@ -302,30 +284,79 @@ QString Core::Archive::oldestId()
     if (!opened) {
         throw Closed("oldestId", jid.toStdString());
     }
+    Shared::Message msg = oldest();
+    return msg.getId();
+}
+
+Shared::Message Core::Archive::oldest() 
+{
+    return edge(false);
+}
+
+Shared::Message Core::Archive::edge(bool end)
+{
+    QString name;
+    MDB_cursor_op begin;
+    MDB_cursor_op iteration;
+    if (end) {
+        name = "newest";
+        begin = MDB_LAST;
+        iteration = MDB_PREV;
+    } else {
+        name = "oldest";
+        begin = MDB_FIRST;
+        iteration = MDB_NEXT;
+    }
+    
+    
+    if (!opened) {
+        throw Closed(name.toStdString(), jid.toStdString());
+    }
+    
     MDB_txn *txn;
+    MDB_cursor* cursor;
+    MDB_val lmdbKey, lmdbData;
     int rc;
     rc = mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn);
-    MDB_cursor* cursor;
     rc = mdb_cursor_open(txn, order, &cursor);
-    MDB_val lmdbKey, lmdbData;
+    rc = mdb_cursor_get(cursor, &lmdbKey, &lmdbData, begin);
+    
+    Shared::Message msg = getStoredMessage(txn, cursor, iteration, &lmdbKey, &lmdbData, rc);
+    
+    mdb_cursor_close(cursor);
+    mdb_txn_abort(txn);
     
-    rc = mdb_cursor_get(cursor, &lmdbKey, &lmdbData, MDB_FIRST);
     if (rc) {
-        qDebug() << "Error geting oldestId " << mdb_strerror(rc);
-        mdb_cursor_close(cursor);
-        mdb_txn_abort(txn);
+        qDebug() << "Error geting" << name << "message" << mdb_strerror(rc);
         throw Empty(jid.toStdString());
     } else {
-        std::string sId((char*)lmdbData.mv_data, lmdbData.mv_size);
-        mdb_cursor_close(cursor);
-        mdb_txn_abort(txn);
-        return sId.c_str();
+        return msg;
     }
 }
 
-Shared::Message Core::Archive::oldest()
+Shared::Message Core::Archive::getStoredMessage(MDB_txn *txn, MDB_cursor* cursor, MDB_cursor_op op, MDB_val* key, MDB_val* value, int& rc)
 {
-    return getElement(oldestId());
+    Shared::Message msg;
+    std::string sId;
+    while (true) {
+        if (rc) {
+            break;
+        }
+        sId = std::string((char*)value->mv_data, value->mv_size);
+        
+        try {
+            msg = getMessage(sId, txn);
+            if (msg.serverStored()) {
+                break;
+            } else {
+                rc = mdb_cursor_get(cursor, key, value, op);
+            }
+        } catch (...) {
+            break;
+        }
+    }
+    
+    return msg;
 }
 
 unsigned int Core::Archive::addElements(const std::list<Shared::Message>& messages)
diff --git a/core/archive.h b/core/archive.h
index 15c91b9..4d61f95 100644
--- a/core/archive.h
+++ b/core/archive.h
@@ -183,6 +183,8 @@ private:
     void printKeys();
     bool dropAvatar(const std::string& resource);
     Shared::Message getMessage(const std::string& id, MDB_txn* txn);
+    Shared::Message getStoredMessage(MDB_txn *txn, MDB_cursor* cursor, MDB_cursor_op op, MDB_val* key, MDB_val* value, int& rc);
+    Shared::Message edge(bool end);
 };
 
 }
diff --git a/core/rosteritem.cpp b/core/rosteritem.cpp
index 4d1cca2..59aa4a7 100644
--- a/core/rosteritem.cpp
+++ b/core/rosteritem.cpp
@@ -35,6 +35,7 @@ Core::RosterItem::RosterItem(const QString& pJid, const QString& pAccount, QObje
     appendCache(),
     responseCache(),
     requestCache(),
+    toCorrect(),
     muc(false)
 {
     archive->open(account);
@@ -75,6 +76,36 @@ void Core::RosterItem::addMessageToArchive(const Shared::Message& msg)
 {
     if (msg.storable()) {
         hisoryCache.push_back(msg);
+        std::map<QString, Shared::Message>::iterator itr = toCorrect.find(msg.getId());
+        if (itr != toCorrect.end()) {
+            hisoryCache.back().change({
+                {"body", itr->second.getBody()},
+                {"stamp", itr->second.getTime()}
+            });
+            toCorrect.erase(itr);
+        }
+    }
+}
+
+void Core::RosterItem::correctMessageInArchive(const QString& originalId, const Shared::Message& msg)
+{
+    if (msg.storable()) {
+        QDateTime thisTime = msg.getTime();
+        std::map<QString, Shared::Message>::iterator itr = toCorrect.find(originalId);
+        if (itr != toCorrect.end()) {
+            if (itr->second.getTime() < thisTime) {
+                itr->second = msg;
+            }
+            return;
+        }
+        
+        bool found = changeMessage(originalId, {
+            {"body", msg.getBody()},
+            {"stamp", thisTime}
+        });
+        if (!found) {
+            toCorrect.insert(std::make_pair(originalId, msg));
+        }
     }
 }
 
@@ -221,7 +252,7 @@ void Core::RosterItem::appendMessageToArchive(const Shared::Message& msg)
     }
 }
 
-void Core::RosterItem::changeMessage(const QString& id, const QMap<QString, QVariant>& data)
+bool Core::RosterItem::changeMessage(const QString& id, const QMap<QString, QVariant>& data)
 {
     bool found = false;
     for (Shared::Message& msg : appendCache) {
@@ -259,6 +290,8 @@ void Core::RosterItem::changeMessage(const QString& id, const QMap<QString, QVar
             }
         }
     }
+    
+    return found;
 }
 
 void Core::RosterItem::flushMessagesToArchive(bool finished, const QString& firstId, const QString& lastId)
diff --git a/core/rosteritem.h b/core/rosteritem.h
index f9aa8be..b6c60cd 100644
--- a/core/rosteritem.h
+++ b/core/rosteritem.h
@@ -61,6 +61,7 @@ public:
     bool isMuc() const;
     
     void addMessageToArchive(const Shared::Message& msg);
+    void correctMessageInArchive(const QString& originalId, const Shared::Message& msg);
     void appendMessageToArchive(const Shared::Message& msg);
     void flushMessagesToArchive(bool finished, const QString& firstId, const QString& lastId);
     void requestHistory(int count, const QString& before);
@@ -72,7 +73,7 @@ public:
     virtual Shared::VCard handleResponseVCard(const QXmppVCardIq& card, const QString& resource);
     virtual void handlePresence(const QXmppPresence& pres) = 0;
     
-    void changeMessage(const QString& id, const QMap<QString, QVariant>& data);
+    bool changeMessage(const QString& id, const QMap<QString, QVariant>& data);
     
 signals:
     void nameChanged(const QString& name);
@@ -98,6 +99,7 @@ protected:
     std::list<Shared::Message> appendCache;
     std::list<Shared::Message> responseCache;
     std::list<std::pair<int, QString>> requestCache;
+    std::map<QString, Shared::Message> toCorrect;
     bool muc;
 
 private:
diff --git a/global.cpp b/global.cpp
index 4cdfa8d..ed75abc 100644
--- a/global.cpp
+++ b/global.cpp
@@ -53,7 +53,10 @@ Shared::Message::Message():
     outgoing(false),
     forwarded(false),
     state(State::delivered),
-    edited(false)
+    edited(false),
+    errorText(),
+    originalMessage(),
+    lastModified()
 {
 }
 
@@ -242,6 +245,16 @@ void Shared::Message::setThread(const QString& p_body)
     thread = p_body;
 }
 
+QDateTime Shared::Message::getLastModified() const
+{
+    return lastModified;
+}
+
+QString Shared::Message::getOriginalBody() const
+{
+    return originalMessage;
+}
+
 Shared::Message::Type Shared::Message::getType() const
 {
     return type;
@@ -261,6 +274,11 @@ void Shared::Message::setState(Shared::Message::State p_state)
     }
 }
 
+bool Shared::Message::serverStored() const
+{
+    return state == State::delivered || state == State::sent;
+}
+
 void Shared::Message::setEdited(bool p_edited)
 {
     edited = p_edited;
@@ -285,6 +303,10 @@ void Shared::Message::serialize(QDataStream& data) const
     if (state == State::error) {
         data << errorText;
     }
+    if (edited) {
+        data << originalMessage;
+        data << lastModified;
+    }
 }
 
 void Shared::Message::deserialize(QDataStream& data)
@@ -310,6 +332,10 @@ void Shared::Message::deserialize(QDataStream& data)
     if (state == State::error) {
         data >> errorText;
     }
+    if (edited) {
+        data >> originalMessage;
+        data >> lastModified;
+    }
 }
 
 bool Shared::Message::change(const QMap<QString, QVariant>& data)
@@ -337,8 +363,19 @@ bool Shared::Message::change(const QMap<QString, QVariant>& data)
     }
     itr = data.find("body");
     if (itr != data.end()) {
-        setBody(itr.value().toString());
-        setEdited(true);
+        QMap<QString, QVariant>::const_iterator dItr = data.find("stamp");
+        QDateTime correctionDate;
+        if (dItr != data.end()) {
+            correctionDate = dItr.value().toDateTime();
+        } else {
+            correctionDate = QDateTime::currentDateTime();      //in case there is no information about time of this correction it's applied
+        }
+        if (!edited || lastModified < correctionDate) {
+            originalMessage = body;
+            lastModified = correctionDate;
+            setBody(itr.value().toString());
+            setEdited(true);
+        }
     }
     
     return idChanged;
diff --git a/global.h b/global.h
index e0a0796..aa5911a 100644
--- a/global.h
+++ b/global.h
@@ -216,6 +216,9 @@ public:
     QString getPenPalJid() const;
     QString getPenPalResource() const;
     void generateRandomId();
+    bool serverStored() const;
+    QDateTime getLastModified() const;
+    QString getOriginalBody() const;
     
     void serialize(QDataStream& data) const;
     void deserialize(QDataStream& data);
@@ -236,6 +239,8 @@ private:
     State state;
     bool edited;
     QString errorText;
+    QString originalMessage;
+    QDateTime lastModified;
 };
 
 class VCard {
diff --git a/ui/utils/message.cpp b/ui/utils/message.cpp
index 26fbd99..eb69ca0 100644
--- a/ui/utils/message.cpp
+++ b/ui/utils/message.cpp
@@ -121,6 +121,9 @@ Message::Message(const Shared::Message& source, bool p_outgoing, const QString&
         layout->addStretch();
         statusLay->addWidget(date);
     }
+    if (msg.getEdited()) {
+        setEdited();
+    }
     
     bodyLayout->addWidget(statusBar);
     layout->setAlignment(avatar, Qt::AlignTop);
@@ -315,20 +318,7 @@ bool Message::change(const QMap<QString, QVariant>& data)
         text->hide();
     }
     if (msg.getEdited()) {
-        if (!hasEditedLabel) {
-            editedLabel = new QLabel();
-            QFont eFont = editedLabel->font();
-            eFont.setItalic(true);
-            eFont.setPointSize(eFont.pointSize() - 2);
-            editedLabel->setFont(eFont);
-            hasEditedLabel = true;
-            QHBoxLayout* statusLay = static_cast<QHBoxLayout*>(statusBar->layout());
-            if (hasStatusIcon) {
-                statusLay->insertWidget(1, editedLabel);
-            } else {
-                statusLay->insertWidget(0, editedLabel);
-            }
-        }
+        setEdited();
     }
     if (hasStatusIcon) {
         setState();
@@ -338,6 +328,20 @@ bool Message::change(const QMap<QString, QVariant>& data)
     return idChanged;
 }
 
+void Message::setEdited()
+{
+    if (!hasEditedLabel) {
+        editedLabel = new QLabel();
+        hasEditedLabel = true;
+        QIcon q(Shared::icon("edit-rename"));
+        editedLabel->setPixmap(q.pixmap(12, 12));
+        QHBoxLayout* statusLay = static_cast<QHBoxLayout*>(statusBar->layout());
+        statusLay->insertWidget(1, editedLabel);
+    }
+    editedLabel->setToolTip("Last time edited: " + msg.getLastModified().toLocalTime().toString() 
+    + "\nOriginal message: " + msg.getOriginalBody());
+}
+
 void Message::setState()
 {
     Shared::Message::State state = msg.getState();
diff --git a/ui/utils/message.h b/ui/utils/message.h
index baac728..a885d5b 100644
--- a/ui/utils/message.h
+++ b/ui/utils/message.h
@@ -94,6 +94,7 @@ private:
     void hideProgress();
     void hideFile();
     void setState();
+    void setEdited();
 };
 
 #endif // MESSAGE_H

From c793f566471607896311326c086d3d8bccafcd2e Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Tue, 31 Mar 2020 00:17:10 +0300
Subject: [PATCH 053/281] a little bit of restyling, now the integration with
 transparent themes is supposed to be better

---
 core/account.cpp              |   4 +-
 core/conference.cpp           |   4 +-
 global.cpp                    |   6 +-
 ui/CMakeLists.txt             |   1 +
 ui/models/contact.cpp         |   2 +-
 ui/squawk.cpp                 |   3 +
 ui/utils/dropshadoweffect.cpp | 701 +++++++++++++++++++++++++++++
 ui/utils/dropshadoweffect.h   |  93 ++++
 ui/utils/messageline.cpp      |   1 -
 ui/widgets/conversation.cpp   |  57 +--
 ui/widgets/conversation.h     |   3 +-
 ui/widgets/conversation.ui    | 812 ++++++++++++++++------------------
 12 files changed, 1192 insertions(+), 495 deletions(-)
 create mode 100644 ui/utils/dropshadoweffect.cpp
 create mode 100644 ui/utils/dropshadoweffect.h

diff --git a/core/account.cpp b/core/account.cpp
index 724bfa1..3acdeae 100644
--- a/core/account.cpp
+++ b/core/account.cpp
@@ -466,7 +466,7 @@ void Core::Account::onPresenceReceived(const QXmppPresence& p_presence)
         case QXmppPresence::Available:{
             QDateTime lastInteraction = p_presence.lastUserInteraction();
             if (!lastInteraction.isValid()) {
-                lastInteraction = QDateTime::currentDateTime();
+                lastInteraction = QDateTime::currentDateTimeUtc();
             }
             emit addPresence(jid, resource, {
                 {"lastActivity", lastInteraction},
@@ -791,7 +791,7 @@ bool Core::Account::handleGroupMessage(const QXmppMessage& msg, bool outgoing, b
                 emit changeMessage(jid, oId, cData);
             } else {
                 cnt->appendMessageToArchive(sMsg);
-                QDateTime minAgo = QDateTime::currentDateTime().addSecs(-60);
+                QDateTime minAgo = QDateTime::currentDateTimeUtc().addSecs(-60);
                 if (sMsg.getTime() > minAgo) {     //otherwise it's considered a delayed delivery, most probably MUC history receipt
                     emit message(sMsg);
                 } else {
diff --git a/core/conference.cpp b/core/conference.cpp
index 2d10273..f0b3b7d 100644
--- a/core/conference.cpp
+++ b/core/conference.cpp
@@ -140,7 +140,7 @@ void Core::Conference::onRoomParticipantAdded(const QString& p_name)
         QXmppPresence pres = room->participantPresence(p_name);
         QDateTime lastInteraction = pres.lastUserInteraction();
         if (!lastInteraction.isValid()) {
-            lastInteraction = QDateTime::currentDateTime();
+            lastInteraction = QDateTime::currentDateTimeUtc();
         }
         QXmppMucItem mi = pres.mucItem();
         Archive::AvatarInfo info;
@@ -181,7 +181,7 @@ void Core::Conference::onRoomParticipantChanged(const QString& p_name)
         QXmppPresence pres = room->participantPresence(p_name);
         QDateTime lastInteraction = pres.lastUserInteraction();
         if (!lastInteraction.isValid()) {
-            lastInteraction = QDateTime::currentDateTime();
+            lastInteraction = QDateTime::currentDateTimeUtc();
         }
         QXmppMucItem mi = pres.mucItem();
         handlePresence(pres);
diff --git a/global.cpp b/global.cpp
index ed75abc..9ece2ba 100644
--- a/global.cpp
+++ b/global.cpp
@@ -368,7 +368,7 @@ bool Shared::Message::change(const QMap<QString, QVariant>& data)
         if (dItr != data.end()) {
             correctionDate = dItr.value().toDateTime();
         } else {
-            correctionDate = QDateTime::currentDateTime();      //in case there is no information about time of this correction it's applied
+            correctionDate = QDateTime::currentDateTimeUtc();      //in case there is no information about time of this correction it's applied
         }
         if (!edited || lastModified < correctionDate) {
             originalMessage = body;
@@ -393,7 +393,7 @@ QString Shared::generateUUID()
 
 void Shared::Message::setCurrentTime()
 {
-    time = QDateTime::currentDateTime();
+    time = QDateTime::currentDateTimeUtc();
 }
 
 QString Shared::Message::getOutOfBandUrl() const
@@ -457,7 +457,7 @@ Shared::VCard::VCard():
     birthday(),
     photoType(Avatar::empty),
     photoPath(),
-    receivingTime(QDateTime::currentDateTime()),
+    receivingTime(QDateTime::currentDateTimeUtc()),
     emails(),
     phones(),
     addresses()
diff --git a/ui/CMakeLists.txt b/ui/CMakeLists.txt
index 50d5304..4e47abe 100644
--- a/ui/CMakeLists.txt
+++ b/ui/CMakeLists.txt
@@ -32,6 +32,7 @@ set(squawkUI_SRC
   utils/badge.cpp
   utils/progress.cpp
   utils/comboboxdelegate.cpp
+  utils/dropshadoweffect.cpp
 )
 
 # Tell CMake to create the helloworld executable
diff --git a/ui/models/contact.cpp b/ui/models/contact.cpp
index de9d312..b968ce4 100644
--- a/ui/models/contact.cpp
+++ b/ui/models/contact.cpp
@@ -277,7 +277,7 @@ void Models::Contact::addMessage(const Shared::Message& data)
             Presence* pr = new Presence({});
             pr->setName(res);
             pr->setAvailability(Shared::invisible);
-            pr->setLastActivity(QDateTime::currentDateTime());
+            pr->setLastActivity(QDateTime::currentDateTimeUtc());
             presences.insert(res, pr);
             appendChild(pr);
             pr->addMessage(data);
diff --git a/ui/squawk.cpp b/ui/squawk.cpp
index fd9b6ff..601e999 100644
--- a/ui/squawk.cpp
+++ b/ui/squawk.cpp
@@ -59,6 +59,9 @@ Squawk::Squawk(QWidget *parent) :
     //m_ui->mainToolBar->addWidget(m_ui->comboBox);
     
     setWindowTitle(tr("Contact list"));
+    if (testAttribute(Qt::WA_TranslucentBackground)) {
+        m_ui->roster->viewport()->setAutoFillBackground(false);
+    }
 }
 
 Squawk::~Squawk() {
diff --git a/ui/utils/dropshadoweffect.cpp b/ui/utils/dropshadoweffect.cpp
new file mode 100644
index 0000000..91a0258
--- /dev/null
+++ b/ui/utils/dropshadoweffect.cpp
@@ -0,0 +1,701 @@
+/*
+ * Squawk messenger. 
+ * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+ * 
+ * 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 "dropshadoweffect.h"
+#include "QtMath"
+
+static const int tileSize = 32;
+template <class T>
+static
+inline void qt_memrotate90_tiled(const T *src, int w, int h, int sstride, T *dest, int dstride)
+{
+    sstride /= sizeof(T);
+    dstride /= sizeof(T);
+    const int pack = sizeof(quint32) / sizeof(T);
+    const int unaligned =
+    qMin(uint((quintptr(dest) & (sizeof(quint32)-1)) / sizeof(T)), uint(h));
+    const int restX = w % tileSize;
+    const int restY = (h - unaligned) % tileSize;
+    const int unoptimizedY = restY % pack;
+    const int numTilesX = w / tileSize + (restX > 0);
+    const int numTilesY = (h - unaligned) / tileSize + (restY >= pack);
+    for (int tx = 0; tx < numTilesX; ++tx) {
+        const int startx = w - tx * tileSize - 1;
+        const int stopx = qMax(startx - tileSize, 0);
+        if (unaligned) {
+            for (int x = startx; x >= stopx; --x) {
+                T *d = dest + (w - x - 1) * dstride;
+                for (int y = 0; y < unaligned; ++y) {
+                    *d++ = src[y * sstride + x];
+                }
+            }
+        }
+        for (int ty = 0; ty < numTilesY; ++ty) {
+            const int starty = ty * tileSize + unaligned;
+            const int stopy = qMin(starty + tileSize, h - unoptimizedY);
+            for (int x = startx; x >= stopx; --x) {
+                quint32 *d = reinterpret_cast<quint32*>(dest + (w - x - 1) * dstride + starty);
+                for (int y = starty; y < stopy; y += pack) {
+                    quint32 c = src[y * sstride + x];
+                    for (int i = 1; i < pack; ++i) {
+                        const int shift = (sizeof(T) * 8 * i);
+                        const T color = src[(y + i) * sstride + x];
+                        c |= color << shift;
+                    }
+                    *d++ = c;
+                }
+            }
+        }
+        if (unoptimizedY) {
+            const int starty = h - unoptimizedY;
+            for (int x = startx; x >= stopx; --x) {
+                T *d = dest + (w - x - 1) * dstride + starty;
+                for (int y = starty; y < h; ++y) {
+                    *d++ = src[y * sstride + x];
+                }
+            }
+        }
+    }
+}
+template <class T>
+static
+inline void qt_memrotate90_tiled_unpacked(const T *src, int w, int h, int sstride, T *dest,
+                                          int dstride)
+{
+    const int numTilesX = (w + tileSize - 1) / tileSize;
+    const int numTilesY = (h + tileSize - 1) / tileSize;
+    for (int tx = 0; tx < numTilesX; ++tx) {
+        const int startx = w - tx * tileSize - 1;
+        const int stopx = qMax(startx - tileSize, 0);
+        for (int ty = 0; ty < numTilesY; ++ty) {
+            const int starty = ty * tileSize;
+            const int stopy = qMin(starty + tileSize, h);
+            for (int x = startx; x >= stopx; --x) {
+                T *d = (T *)((char*)dest + (w - x - 1) * dstride) + starty;
+                const char *s = (const char*)(src + x) + starty * sstride;
+                for (int y = starty; y < stopy; ++y) {
+                    *d++ = *(const T *)(s);
+                    s += sstride;
+                }
+            }
+        }
+    }
+}
+template <class T>
+static
+inline void qt_memrotate270_tiled(const T *src, int w, int h, int sstride, T *dest, int dstride)
+{
+    sstride /= sizeof(T);
+    dstride /= sizeof(T);
+    const int pack = sizeof(quint32) / sizeof(T);
+    const int unaligned =
+    qMin(uint((quintptr(dest) & (sizeof(quint32)-1)) / sizeof(T)), uint(h));
+    const int restX = w % tileSize;
+    const int restY = (h - unaligned) % tileSize;
+    const int unoptimizedY = restY % pack;
+    const int numTilesX = w / tileSize + (restX > 0);
+    const int numTilesY = (h - unaligned) / tileSize + (restY >= pack);
+    for (int tx = 0; tx < numTilesX; ++tx) {
+        const int startx = tx * tileSize;
+        const int stopx = qMin(startx + tileSize, w);
+        if (unaligned) {
+            for (int x = startx; x < stopx; ++x) {
+                T *d = dest + x * dstride;
+                for (int y = h - 1; y >= h - unaligned; --y) {
+                    *d++ = src[y * sstride + x];
+                }
+            }
+        }
+        for (int ty = 0; ty < numTilesY; ++ty) {
+            const int starty = h - 1 - unaligned - ty * tileSize;
+            const int stopy = qMax(starty - tileSize, unoptimizedY);
+            for (int x = startx; x < stopx; ++x) {
+                quint32 *d = reinterpret_cast<quint32*>(dest + x * dstride
+                + h - 1 - starty);
+                for (int y = starty; y >= stopy; y -= pack) {
+                    quint32 c = src[y * sstride + x];
+                    for (int i = 1; i < pack; ++i) {
+                        const int shift = (sizeof(T) * 8 * i);
+                        const T color = src[(y - i) * sstride + x];
+                        c |= color << shift;
+                    }
+                    *d++ = c;
+                }
+            }
+        }
+        if (unoptimizedY) {
+            const int starty = unoptimizedY - 1;
+            for (int x = startx; x < stopx; ++x) {
+                T *d = dest + x * dstride + h - 1 - starty;
+                for (int y = starty; y >= 0; --y) {
+                    *d++ = src[y * sstride + x];
+                }
+            }
+        }
+    }
+}
+template <class T>
+static
+inline void qt_memrotate270_tiled_unpacked(const T *src, int w, int h, int sstride, T *dest,
+                                           int dstride)
+{
+    const int numTilesX = (w + tileSize - 1) / tileSize;
+    const int numTilesY = (h + tileSize - 1) / tileSize;
+    for (int tx = 0; tx < numTilesX; ++tx) {
+        const int startx = tx * tileSize;
+        const int stopx = qMin(startx + tileSize, w);
+        for (int ty = 0; ty < numTilesY; ++ty) {
+            const int starty = h - 1 - ty * tileSize;
+            const int stopy = qMax(starty - tileSize, 0);
+            for (int x = startx; x < stopx; ++x) {
+                T *d = (T*)((char*)dest + x * dstride) + h - 1 - starty;
+                const char *s = (const char*)(src + x) + starty * sstride;
+                for (int y = starty; y >= stopy; --y) {
+                    *d++ = *(const T*)s;
+                    s -= sstride;
+                }
+            }
+        }
+    }
+}
+template <class T>
+static
+inline void qt_memrotate90_template(const T *src, int srcWidth, int srcHeight, int srcStride,
+                                    T *dest, int dstStride)
+{
+    #if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
+    // packed algorithm assumes little endian and that sizeof(quint32)/sizeof(T) is an integer
+    if (sizeof(quint32) % sizeof(T) == 0)
+        qt_memrotate90_tiled<T>(src, srcWidth, srcHeight, srcStride, dest, dstStride);
+    else
+        #endif
+        qt_memrotate90_tiled_unpacked<T>(src, srcWidth, srcHeight, srcStride, dest, dstStride);
+}
+template <>
+inline void qt_memrotate90_template<quint32>(const quint32 *src, int w, int h, int sstride, quint32 *dest, int dstride)
+{
+    // packed algorithm doesn't have any benefit for quint32
+    qt_memrotate90_tiled_unpacked(src, w, h, sstride, dest, dstride);
+}
+template <>
+inline void qt_memrotate90_template<quint64>(const quint64 *src, int w, int h, int sstride, quint64 *dest, int dstride)
+{
+    qt_memrotate90_tiled_unpacked(src, w, h, sstride, dest, dstride);
+}
+template <class T>
+static
+inline void qt_memrotate180_template(const T *src, int w, int h, int sstride, T *dest, int dstride)
+{
+    const char *s = (const char*)(src) + (h - 1) * sstride;
+    for (int dy = 0; dy < h; ++dy) {
+        T *d = reinterpret_cast<T*>((char *)(dest) + dy * dstride);
+        src = reinterpret_cast<const T*>(s);
+        for (int dx = 0; dx < w; ++dx) {
+            d[dx] = src[w - 1 - dx];
+        }
+        s -= sstride;
+    }
+}
+template <class T>
+static
+inline void qt_memrotate270_template(const T *src, int srcWidth, int srcHeight, int srcStride,
+                                     T *dest, int dstStride)
+{
+    #if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
+    // packed algorithm assumes little endian and that sizeof(quint32)/sizeof(T) is an integer
+    if (sizeof(quint32) % sizeof(T) == 0)
+        qt_memrotate270_tiled<T>(src, srcWidth, srcHeight, srcStride, dest, dstStride);
+    else
+        #endif
+        qt_memrotate270_tiled_unpacked<T>(src, srcWidth, srcHeight, srcStride, dest, dstStride);
+}
+template <>
+inline void qt_memrotate270_template<quint32>(const quint32 *src, int w, int h, int sstride, quint32 *dest, int dstride)
+{
+    // packed algorithm doesn't have any benefit for quint32
+    qt_memrotate270_tiled_unpacked(src, w, h, sstride, dest, dstride);
+}
+template <>
+inline void qt_memrotate270_template<quint64>(const quint64 *src, int w, int h, int sstride, quint64 *dest, int dstride)
+{
+    qt_memrotate270_tiled_unpacked(src, w, h, sstride, dest, dstride);
+}
+#define QT_IMPL_MEMROTATE(type)                                     \
+void qt_memrotate90(const type *src, int w, int h, int sstride, \
+type *dest, int dstride)           \
+{                                                                   \
+    qt_memrotate90_template(src, w, h, sstride, dest, dstride);     \
+}                                                                   \
+void qt_memrotate180(const type *src, int w, int h, int sstride, \
+type *dest, int dstride)          \
+{                                                                   \
+    qt_memrotate180_template(src, w, h, sstride, dest, dstride);    \
+}                                                                   \
+void qt_memrotate270(const type *src, int w, int h, int sstride, \
+type *dest, int dstride)          \
+{                                                                   \
+    qt_memrotate270_template(src, w, h, sstride, dest, dstride);    \
+}
+#define QT_IMPL_SIMPLE_MEMROTATE(type)                              \
+void qt_memrotate90(const type *src, int w, int h, int sstride,  \
+type *dest, int dstride)           \
+{                                                                   \
+    qt_memrotate90_tiled_unpacked(src, w, h, sstride, dest, dstride); \
+}                                                                   \
+void qt_memrotate180(const type *src, int w, int h, int sstride, \
+type *dest, int dstride)          \
+{                                                                   \
+    qt_memrotate180_template(src, w, h, sstride, dest, dstride);    \
+}                                                                   \
+void qt_memrotate270(const type *src, int w, int h, int sstride, \
+type *dest, int dstride)          \
+{                                                                   \
+    qt_memrotate270_tiled_unpacked(src, w, h, sstride, dest, dstride); \
+}
+QT_IMPL_MEMROTATE(quint64)
+QT_IMPL_MEMROTATE(quint32)
+QT_IMPL_MEMROTATE(quint16)
+QT_IMPL_MEMROTATE(quint8)
+void qt_memrotate90_8(const uchar *srcPixels, int w, int h, int sbpl, uchar *destPixels, int dbpl)
+{
+    qt_memrotate90(srcPixels, w, h, sbpl, destPixels, dbpl);
+}
+void qt_memrotate180_8(const uchar *srcPixels, int w, int h, int sbpl, uchar *destPixels, int dbpl)
+{
+    qt_memrotate180(srcPixels, w, h, sbpl, destPixels, dbpl);
+}
+void qt_memrotate270_8(const uchar *srcPixels, int w, int h, int sbpl, uchar *destPixels, int dbpl)
+{
+    qt_memrotate270(srcPixels, w, h, sbpl, destPixels, dbpl);
+}
+void qt_memrotate90_16(const uchar *srcPixels, int w, int h, int sbpl, uchar *destPixels, int dbpl)
+{
+    qt_memrotate90((const ushort *)srcPixels, w, h, sbpl, (ushort *)destPixels, dbpl);
+}
+void qt_memrotate180_16(const uchar *srcPixels, int w, int h, int sbpl, uchar *destPixels, int dbpl)
+{
+    qt_memrotate180((const ushort *)srcPixels, w, h, sbpl, (ushort *)destPixels, dbpl);
+}
+void qt_memrotate270_16(const uchar *srcPixels, int w, int h, int sbpl, uchar *destPixels, int dbpl)
+{
+    qt_memrotate270((const ushort *)srcPixels, w, h, sbpl, (ushort *)destPixels, dbpl);
+}
+void qt_memrotate90_32(const uchar *srcPixels, int w, int h, int sbpl, uchar *destPixels, int dbpl)
+{
+    qt_memrotate90((const uint *)srcPixels, w, h, sbpl, (uint *)destPixels, dbpl);
+}
+void qt_memrotate180_32(const uchar *srcPixels, int w, int h, int sbpl, uchar *destPixels, int dbpl)
+{
+    qt_memrotate180((const uint *)srcPixels, w, h, sbpl, (uint *)destPixels, dbpl);
+}
+void qt_memrotate270_32(const uchar *srcPixels, int w, int h, int sbpl, uchar *destPixels, int dbpl)
+{
+    qt_memrotate270((const uint *)srcPixels, w, h, sbpl, (uint *)destPixels, dbpl);
+}
+void qt_memrotate90_64(const uchar *srcPixels, int w, int h, int sbpl, uchar *destPixels, int dbpl)
+{
+    qt_memrotate90((const quint64 *)srcPixels, w, h, sbpl, (quint64 *)destPixels, dbpl);
+}
+void qt_memrotate180_64(const uchar *srcPixels, int w, int h, int sbpl, uchar *destPixels, int dbpl)
+{
+    qt_memrotate180((const quint64 *)srcPixels, w, h, sbpl, (quint64 *)destPixels, dbpl);
+}
+void qt_memrotate270_64(const uchar *srcPixels, int w, int h, int sbpl, uchar *destPixels, int dbpl)
+{
+    qt_memrotate270((const quint64 *)srcPixels, w, h, sbpl, (quint64 *)destPixels, dbpl);
+}
+
+#define AVG(a,b)  ( ((((a)^(b)) & 0xfefefefeUL) >> 1) + ((a)&(b)) )
+#define AVG16(a,b)  ( ((((a)^(b)) & 0xf7deUL) >> 1) + ((a)&(b)) )
+const int alphaIndex = (QSysInfo::ByteOrder == QSysInfo::BigEndian ? 0 : 3);
+
+QImage qt_halfScaled(const QImage &source)
+{
+    if (source.width() < 2 || source.height() < 2)
+        return QImage();
+    
+    QImage srcImage = source;
+    
+    if (source.format() == QImage::Format_Indexed8 || source.format() == QImage::Format_Grayscale8) {
+        // assumes grayscale
+        QImage dest(source.width() / 2, source.height() / 2, srcImage.format());
+        dest.setDevicePixelRatio(source.devicePixelRatioF());
+        
+        const uchar *src = reinterpret_cast<const uchar*>(const_cast<const QImage &>(srcImage).bits());
+        qsizetype sx = srcImage.bytesPerLine();
+        qsizetype sx2 = sx << 1;
+        
+        uchar *dst = reinterpret_cast<uchar*>(dest.bits());
+        qsizetype dx = dest.bytesPerLine();
+        int ww = dest.width();
+        int hh = dest.height();
+        
+        for (int y = hh; y; --y, dst += dx, src += sx2) {
+            const uchar *p1 = src;
+            const uchar *p2 = src + sx;
+            uchar *q = dst;
+            for (int x = ww; x; --x, ++q, p1 += 2, p2 += 2)
+                *q = ((int(p1[0]) + int(p1[1]) + int(p2[0]) + int(p2[1])) + 2) >> 2;
+        }
+        
+        return dest;
+    } else if (source.format() == QImage::Format_ARGB8565_Premultiplied) {
+        QImage dest(source.width() / 2, source.height() / 2, srcImage.format());
+        dest.setDevicePixelRatio(source.devicePixelRatioF());
+        
+        const uchar *src = reinterpret_cast<const uchar*>(const_cast<const QImage &>(srcImage).bits());
+        qsizetype sx = srcImage.bytesPerLine();
+        qsizetype sx2 = sx << 1;
+        
+        uchar *dst = reinterpret_cast<uchar*>(dest.bits());
+        qsizetype dx = dest.bytesPerLine();
+        int ww = dest.width();
+        int hh = dest.height();
+        
+        for (int y = hh; y; --y, dst += dx, src += sx2) {
+            const uchar *p1 = src;
+            const uchar *p2 = src + sx;
+            uchar *q = dst;
+            for (int x = ww; x; --x, q += 3, p1 += 6, p2 += 6) {
+                // alpha
+                q[0] = AVG(AVG(p1[0], p1[3]), AVG(p2[0], p2[3]));
+                // rgb
+                const quint16 p16_1 = (p1[2] << 8) | p1[1];
+                const quint16 p16_2 = (p1[5] << 8) | p1[4];
+                const quint16 p16_3 = (p2[2] << 8) | p2[1];
+                const quint16 p16_4 = (p2[5] << 8) | p2[4];
+                const quint16 result = AVG16(AVG16(p16_1, p16_2), AVG16(p16_3, p16_4));
+                q[1] = result & 0xff;
+                q[2] = result >> 8;
+            }
+        }
+        
+        return dest;
+    } else if (source.format() != QImage::Format_ARGB32_Premultiplied
+        && source.format() != QImage::Format_RGB32)
+    {
+        srcImage = source.convertToFormat(QImage::Format_ARGB32_Premultiplied);
+    }
+    
+    QImage dest(source.width() / 2, source.height() / 2, srcImage.format());
+    dest.setDevicePixelRatio(source.devicePixelRatioF());
+    
+    const quint32 *src = reinterpret_cast<const quint32*>(const_cast<const QImage &>(srcImage).bits());
+    qsizetype sx = srcImage.bytesPerLine() >> 2;
+    qsizetype sx2 = sx << 1;
+    
+    quint32 *dst = reinterpret_cast<quint32*>(dest.bits());
+    qsizetype dx = dest.bytesPerLine() >> 2;
+    int ww = dest.width();
+    int hh = dest.height();
+    
+    for (int y = hh; y; --y, dst += dx, src += sx2) {
+        const quint32 *p1 = src;
+        const quint32 *p2 = src + sx;
+        quint32 *q = dst;
+        for (int x = ww; x; --x, q++, p1 += 2, p2 += 2)
+            *q = AVG(AVG(p1[0], p1[1]), AVG(p2[0], p2[1]));
+    }
+    
+    return dest;
+}
+
+template <int shift>
+inline int qt_static_shift(int value)
+{
+    if (shift == 0)
+        return value;
+    else if (shift > 0)
+        return value << (uint(shift) & 0x1f);
+    else
+        return value >> (uint(-shift) & 0x1f);
+}
+
+template<int aprec, int zprec>
+inline void qt_blurinner(uchar *bptr, int &zR, int &zG, int &zB, int &zA, int alpha)
+{
+    QRgb *pixel = (QRgb *)bptr;
+    
+    #define Z_MASK (0xff << zprec)
+    const int A_zprec = qt_static_shift<zprec - 24>(*pixel) & Z_MASK;
+    const int R_zprec = qt_static_shift<zprec - 16>(*pixel) & Z_MASK;
+    const int G_zprec = qt_static_shift<zprec - 8>(*pixel)  & Z_MASK;
+    const int B_zprec = qt_static_shift<zprec>(*pixel)      & Z_MASK;
+    #undef Z_MASK
+    
+    const int zR_zprec = zR >> aprec;
+    const int zG_zprec = zG >> aprec;
+    const int zB_zprec = zB >> aprec;
+    const int zA_zprec = zA >> aprec;
+    
+    zR += alpha * (R_zprec - zR_zprec);
+    zG += alpha * (G_zprec - zG_zprec);
+    zB += alpha * (B_zprec - zB_zprec);
+    zA += alpha * (A_zprec - zA_zprec);
+    
+    #define ZA_MASK (0xff << (zprec + aprec))
+    *pixel =
+    qt_static_shift<24 - zprec - aprec>(zA & ZA_MASK)
+    | qt_static_shift<16 - zprec - aprec>(zR & ZA_MASK)
+    | qt_static_shift<8 - zprec - aprec>(zG & ZA_MASK)
+    | qt_static_shift<-zprec - aprec>(zB & ZA_MASK);
+    #undef ZA_MASK
+}
+
+template<int aprec, int zprec>
+inline void qt_blurinner_alphaOnly(uchar *bptr, int &z, int alpha)
+{
+    const int A_zprec = int(*(bptr)) << zprec;
+    const int z_zprec = z >> aprec;
+    z += alpha * (A_zprec - z_zprec);
+    *(bptr) = z >> (zprec + aprec);
+}
+
+template<int aprec, int zprec, bool alphaOnly>
+inline void qt_blurrow(QImage & im, int line, int alpha)
+{
+    uchar *bptr = im.scanLine(line);
+    
+    int zR = 0, zG = 0, zB = 0, zA = 0;
+    
+    if (alphaOnly && im.format() != QImage::Format_Indexed8)
+        bptr += alphaIndex;
+    
+    const int stride = im.depth() >> 3;
+    const int im_width = im.width();
+    for (int index = 0; index < im_width; ++index) {
+        if (alphaOnly)
+            qt_blurinner_alphaOnly<aprec, zprec>(bptr, zA, alpha);
+        else
+            qt_blurinner<aprec, zprec>(bptr, zR, zG, zB, zA, alpha);
+        bptr += stride;
+    }
+    
+    bptr -= stride;
+    
+    for (int index = im_width - 2; index >= 0; --index) {
+        bptr -= stride;
+        if (alphaOnly)
+            qt_blurinner_alphaOnly<aprec, zprec>(bptr, zA, alpha);
+        else
+            qt_blurinner<aprec, zprec>(bptr, zR, zG, zB, zA, alpha);
+    }
+}
+
+template <int aprec, int zprec, bool alphaOnly>
+void expblur(QImage &img, qreal radius, bool improvedQuality = false, int transposed = 0)
+{
+    // halve the radius if we're using two passes
+    if (improvedQuality)
+        radius *= qreal(0.5);
+    
+    Q_ASSERT(img.format() == QImage::Format_ARGB32_Premultiplied
+    || img.format() == QImage::Format_RGB32
+    || img.format() == QImage::Format_Indexed8
+    || img.format() == QImage::Format_Grayscale8);
+    
+    // choose the alpha such that pixels at radius distance from a fully
+    // saturated pixel will have an alpha component of no greater than
+    // the cutOffIntensity
+    const qreal cutOffIntensity = 2;
+    int alpha = radius <= qreal(1e-5)
+    ? ((1 << aprec)-1)
+    : qRound((1<<aprec)*(1 - qPow(cutOffIntensity * (1 / qreal(255)), 1 / radius)));
+    
+    int img_height = img.height();
+    for (int row = 0; row < img_height; ++row) {
+        for (int i = 0; i <= int(improvedQuality); ++i)
+            qt_blurrow<aprec, zprec, alphaOnly>(img, row, alpha);
+    }
+    
+    QImage temp(img.height(), img.width(), img.format());
+    temp.setDevicePixelRatio(img.devicePixelRatioF());
+    if (transposed >= 0) {
+        if (img.depth() == 8) {
+            qt_memrotate270(reinterpret_cast<const quint8*>(img.bits()),
+                            img.width(), img.height(), img.bytesPerLine(),
+                            reinterpret_cast<quint8*>(temp.bits()),
+                            temp.bytesPerLine());
+        } else {
+            qt_memrotate270(reinterpret_cast<const quint32*>(img.bits()),
+                            img.width(), img.height(), img.bytesPerLine(),
+                            reinterpret_cast<quint32*>(temp.bits()),
+                            temp.bytesPerLine());
+        }
+    } else {
+        if (img.depth() == 8) {
+            qt_memrotate90(reinterpret_cast<const quint8*>(img.bits()),
+                           img.width(), img.height(), img.bytesPerLine(),
+                           reinterpret_cast<quint8*>(temp.bits()),
+                           temp.bytesPerLine());
+        } else {
+            qt_memrotate90(reinterpret_cast<const quint32*>(img.bits()),
+                           img.width(), img.height(), img.bytesPerLine(),
+                           reinterpret_cast<quint32*>(temp.bits()),
+                           temp.bytesPerLine());
+        }
+    }
+    
+    img_height = temp.height();
+    for (int row = 0; row < img_height; ++row) {
+        for (int i = 0; i <= int(improvedQuality); ++i)
+            qt_blurrow<aprec, zprec, alphaOnly>(temp, row, alpha);
+    }
+    
+    if (transposed == 0) {
+        if (img.depth() == 8) {
+            qt_memrotate90(reinterpret_cast<const quint8*>(temp.bits()),
+                           temp.width(), temp.height(), temp.bytesPerLine(),
+                           reinterpret_cast<quint8*>(img.bits()),
+                           img.bytesPerLine());
+        } else {
+            qt_memrotate90(reinterpret_cast<const quint32*>(temp.bits()),
+                           temp.width(), temp.height(), temp.bytesPerLine(),
+                           reinterpret_cast<quint32*>(img.bits()),
+                           img.bytesPerLine());
+        }
+    } else {
+        img = temp;
+    }
+}
+
+PixmapFilter::PixmapFilter(QObject* parent):QObject(parent) {}
+PixmapFilter::~PixmapFilter(){}
+QRectF PixmapFilter::boundingRectFor(const QRectF &rect) const {return rect;}
+
+PixmapDropShadowFilter::PixmapDropShadowFilter(QObject *parent):
+    PixmapFilter(parent),
+    mColor(63, 63, 63, 180),
+    mRadius(1),
+    mThickness(2),
+    top(true),
+    right(true),
+    bottom(true),
+    left(true){}
+
+PixmapDropShadowFilter::~PixmapDropShadowFilter() {}
+qreal PixmapDropShadowFilter::blurRadius() const {return mRadius;}
+void PixmapDropShadowFilter::setBlurRadius(qreal radius) {mRadius = radius;}
+QColor PixmapDropShadowFilter::color() const {return mColor;}
+void PixmapDropShadowFilter::setColor(const QColor &color) {mColor = color;}
+qreal PixmapDropShadowFilter::thickness() const {return mThickness;}
+void PixmapDropShadowFilter::setThickness(qreal thickness) {mThickness = thickness;}
+void PixmapDropShadowFilter::setFrame(bool ptop, bool pright, bool pbottom, bool pleft)
+{
+    top = ptop;
+    right = pright;
+    bottom = pbottom;
+    left = pleft;
+}
+
+void DropShadowEffect::setThickness(qreal thickness)
+{
+    if (filter.thickness() == thickness)
+        return;
+    
+    filter.setThickness(thickness);
+    update();
+}
+
+
+void PixmapDropShadowFilter::draw(QPainter *p, const QPointF &pos, const QPixmap &px, const QRectF &src) const
+{
+    if (px.isNull())
+        return;
+    
+    QImage tmp({px.width(), px.height() + int(mThickness)}, QImage::Format_ARGB32_Premultiplied);
+    tmp.setDevicePixelRatio(px.devicePixelRatioF());
+    tmp.fill(0);
+    QPainter tmpPainter(&tmp);
+    tmpPainter.setCompositionMode(QPainter::CompositionMode_Source);
+    if (top) {
+        QRectF shadow(0, 0, px.width(), mThickness);
+        tmpPainter.fillRect(shadow, mColor);
+    }
+    if (right) {
+        QRectF shadow(px.width() - mThickness, 0, mThickness, px.height());
+        tmpPainter.fillRect(shadow, mColor);
+    }
+    if (bottom) {
+        QRectF shadow(0, px.height() - mThickness, px.width(), mThickness * 2); //i have no idea why, but it leaves some unpainted stripe without some spare space 
+        tmpPainter.fillRect(shadow, mColor);
+    }
+    if (left) {
+        QRectF shadow(0, 0, mThickness, px.height());
+        tmpPainter.fillRect(shadow, mColor);
+    }
+    
+    expblur<12, 10, false>(tmp, mRadius, false, 0);
+    tmpPainter.end();
+    
+    // Draw the actual pixmap...
+    p->drawPixmap(pos, px, src);
+    
+    // draw the blurred drop shadow...
+    p->drawImage(pos, tmp);
+}
+
+qreal DropShadowEffect::blurRadius() const {return filter.blurRadius();}
+void DropShadowEffect::setBlurRadius(qreal blurRadius)
+{
+    if (qFuzzyCompare(filter.blurRadius(), blurRadius))
+        return;
+    
+    filter.setBlurRadius(blurRadius);
+    updateBoundingRect();
+    emit blurRadiusChanged(blurRadius);
+}
+
+void DropShadowEffect::setFrame(bool top, bool right, bool bottom, bool left)
+{
+    filter.setFrame(top, right, bottom, left);
+    update();
+}
+
+
+QColor DropShadowEffect::color() const {return filter.color();}
+void DropShadowEffect::setColor(const QColor &color)
+{
+    if (filter.color() == color)
+        return;
+    
+    filter.setColor(color);
+    update();
+    emit colorChanged(color);
+}
+
+void DropShadowEffect::draw(QPainter* painter)
+{
+    if (filter.blurRadius() <= 0 && filter.thickness() == 0) {
+        drawSource(painter);
+        return;
+    }
+    
+    PixmapPadMode mode = PadToEffectiveBoundingRect;
+    
+    // Draw pixmap in device coordinates to avoid pixmap scaling.
+    QPoint offset;
+    const QPixmap pixmap = sourcePixmap(Qt::DeviceCoordinates, &offset, mode);
+    if (pixmap.isNull())
+        return;
+    
+    QTransform restoreTransform = painter->worldTransform();
+    painter->setWorldTransform(QTransform());
+    filter.draw(painter, offset, pixmap);
+    painter->setWorldTransform(restoreTransform);
+}
diff --git a/ui/utils/dropshadoweffect.h b/ui/utils/dropshadoweffect.h
new file mode 100644
index 0000000..b2768b7
--- /dev/null
+++ b/ui/utils/dropshadoweffect.h
@@ -0,0 +1,93 @@
+/*
+ * Squawk messenger. 
+ * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+ * 
+ * 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/>.
+ */
+
+#ifndef DROPSHADOWEFFECT_H
+#define DROPSHADOWEFFECT_H
+
+#include <QGraphicsEffect>
+#include <QPainter>
+#include <QPointF>
+#include <QColor>
+
+class PixmapFilter : public QObject
+{
+    Q_OBJECT
+public:
+    PixmapFilter(QObject *parent = nullptr);
+    virtual ~PixmapFilter() = 0;
+    
+    virtual QRectF boundingRectFor(const QRectF &rect) const;
+    virtual void draw(QPainter *painter, const QPointF &p, const QPixmap &src, const QRectF &srcRect = QRectF()) const = 0;
+};
+
+class PixmapDropShadowFilter : public PixmapFilter
+{
+    Q_OBJECT
+    
+public:
+    PixmapDropShadowFilter(QObject *parent = nullptr);
+    ~PixmapDropShadowFilter();
+    
+    void draw(QPainter *p, const QPointF &pos, const QPixmap &px, const QRectF &src = QRectF()) const override;
+    
+    qreal blurRadius() const;
+    void setBlurRadius(qreal radius);
+    
+    QColor color() const;
+    void setColor(const QColor &color);
+    
+    qreal thickness() const;
+    void setThickness(qreal thickness);
+    void setFrame(bool top, bool right, bool bottom, bool left);
+    
+protected:
+    QColor mColor;
+    qreal mRadius;
+    qreal mThickness;
+    bool top;
+    bool right;
+    bool bottom;
+    bool left;
+};
+
+class DropShadowEffect : public QGraphicsEffect
+{
+    Q_OBJECT
+public:
+    qreal blurRadius() const;
+    QColor color() const;
+    void setFrame(bool top, bool right, bool bottom, bool left);
+    void setThickness(qreal thickness);
+    
+signals:
+    void blurRadiusChanged(qreal blurRadius);
+    void colorChanged(const QColor &color);
+    
+public slots:
+    void setBlurRadius(qreal blurRadius);
+    void setColor(const QColor &color);
+    
+protected:
+    void draw(QPainter * painter) override;
+    
+protected:
+    PixmapDropShadowFilter filter;
+    
+};
+
+#endif // DROPSHADOWEFFECT_H
diff --git a/ui/utils/messageline.cpp b/ui/utils/messageline.cpp
index d941b2e..2077863 100644
--- a/ui/utils/messageline.cpp
+++ b/ui/utils/messageline.cpp
@@ -40,7 +40,6 @@ MessageLine::MessageLine(bool p_room, QWidget* parent):
 {
     setContentsMargins(0, 0, 0, 0);
     layout->setContentsMargins(0, 0, 0, 0);
-    setBackgroundRole(QPalette::Base);
     layout->setSpacing(0);
     layout->addStretch();
 }
diff --git a/ui/widgets/conversation.cpp b/ui/widgets/conversation.cpp
index 0749daa..340fd82 100644
--- a/ui/widgets/conversation.cpp
+++ b/ui/widgets/conversation.cpp
@@ -18,13 +18,15 @@
 
 #include "conversation.h"
 #include "ui_conversation.h"
+#include "ui/utils/dropshadoweffect.h"
+
 #include <QDebug>
 #include <QScrollBar>
 #include <QTimer>
-#include <QGraphicsDropShadowEffect>
 #include <QFileDialog>
 #include <QMimeDatabase>
 #include <unistd.h>
+#include <QAbstractTextDocumentLayout>
 
 Conversation::Conversation(bool muc, Models::Account* acc, const QString pJid, const QString pRes, QWidget* parent):
     QWidget(parent),
@@ -36,7 +38,6 @@ Conversation::Conversation(bool muc, Models::Account* acc, const QString pJid, c
     m_ui(new Ui::Conversation()),
     ker(),
     scrollResizeCatcher(),
-    attachResizeCatcher(),
     vis(),
     thread(),
     statusIcon(0),
@@ -54,15 +55,11 @@ Conversation::Conversation(bool muc, Models::Account* acc, const QString pJid, c
     filesLayout = new FlowLayout(m_ui->filesPanel, 0);
     m_ui->filesPanel->setLayout(filesLayout);
     
-    m_ui->splitter->setSizes({300, 0});
-    m_ui->splitter->setStretchFactor(1, 0);
-    
     statusIcon = m_ui->statusIcon;
     statusLabel = m_ui->statusLabel;
     
     connect(&ker, &KeyEnterReceiver::enterPressed, this, &Conversation::onEnterPressed);
     connect(&scrollResizeCatcher, &Resizer::resized, this, &Conversation::onScrollResize);
-    connect(&attachResizeCatcher, &Resizer::resized, this, &Conversation::onAttachResize);
     connect(&vis, &VisibilityCatcher::shown, this, &Conversation::onScrollResize);
     connect(&vis, &VisibilityCatcher::hidden, this, &Conversation::onScrollResize);
     connect(m_ui->sendButton, &QPushButton::clicked, this, &Conversation::onEnterPressed);
@@ -72,6 +69,8 @@ Conversation::Conversation(bool muc, Models::Account* acc, const QString pJid, c
     connect(line, &MessageLine::requestLocalFile, this, &Conversation::requestLocalFile);
     connect(m_ui->attachButton, &QPushButton::clicked, this, &Conversation::onAttach);
     connect(m_ui->clearButton, &QPushButton::clicked, this, &Conversation::onClearButton);
+    connect(m_ui->messageEditor->document()->documentLayout(), &QAbstractTextDocumentLayout::documentSizeChanged, 
+            this, &Conversation::onTextEditDocSizeChanged);
     
     m_ui->messageEditor->installEventFilter(&ker);
     
@@ -79,14 +78,13 @@ Conversation::Conversation(bool muc, Models::Account* acc, const QString pJid, c
     m_ui->scrollArea->setWidget(line);
     vs->installEventFilter(&vis);
     
-    if (!tsb) {
-        vs->setBackgroundRole(QPalette::Base);
-        vs->setAutoFillBackground(true);
+    if (testAttribute(Qt::WA_TranslucentBackground)) {
+        m_ui->scrollArea->setAutoFillBackground(false);
+        m_ui->scrollArea->viewport()->setAutoFillBackground(false);
     }
     
     connect(vs, &QScrollBar::valueChanged, this, &Conversation::onSliderValueChanged);
     m_ui->scrollArea->installEventFilter(&scrollResizeCatcher);
-    m_ui->filesPanel->installEventFilter(&attachResizeCatcher);
     
     line->setMyAvatarPath(acc->getAvatarPath());
     line->setMyName(acc->getName());
@@ -100,26 +98,12 @@ Conversation::~Conversation()
 
 void Conversation::applyVisualEffects()
 {
-    QGraphicsDropShadowEffect *e1 = new QGraphicsDropShadowEffect;
+    DropShadowEffect *e1 = new DropShadowEffect;
     e1->setBlurRadius(10);
-    e1->setXOffset(0);
-    e1->setYOffset(-2);
     e1->setColor(Qt::black);
-    m_ui->bl->setGraphicsEffect(e1);
-    
-    QGraphicsDropShadowEffect *e2 = new QGraphicsDropShadowEffect;
-    e2->setBlurRadius(7);
-    e2->setXOffset(0);
-    e2->setYOffset(2);
-    e2->setColor(Qt::black);
-    m_ui->ul->setGraphicsEffect(e2);
-    
-    QGraphicsDropShadowEffect *e3 = new QGraphicsDropShadowEffect;
-    e3->setBlurRadius(10);
-    e3->setXOffset(0);
-    e3->setYOffset(2);
-    e3->setColor(Qt::black);
-    m_ui->ut->setGraphicsEffect(e3);
+    e1->setThickness(1);
+    e1->setFrame(true, false, true, false);
+    m_ui->scrollArea->setGraphicsEffect(e1);
 }
 
 void Conversation::setName(const QString& name)
@@ -404,21 +388,9 @@ void Conversation::setAvatar(const QString& path)
     }
 }
 
-void Conversation::onAttachResize(const QSize& oldSize, const QSize& newSize)
+void Conversation::onTextEditDocSizeChanged(const QSizeF& size)
 {
-    int oh = oldSize.height();
-    int nh = newSize.height();
-    
-    int d = oh - nh;
-    
-    if (d != 0) {
-        QList<int> cs = m_ui->splitter->sizes();
-        cs.first() += d;
-        cs.last() -=d;
-        
-        m_ui->splitter->setSizes(cs);
-        m_ui->scrollArea->verticalScrollBar()->setValue(m_ui->scrollArea->verticalScrollBar()->maximum());
-    }
+    m_ui->messageEditor->setMaximumHeight(int(size.height()));
 }
 
 
@@ -439,3 +411,4 @@ VisibilityCatcher::VisibilityCatcher(QWidget* parent):
 QObject(parent)
 {
 }
+
diff --git a/ui/widgets/conversation.h b/ui/widgets/conversation.h
index d9f6dda..64d7341 100644
--- a/ui/widgets/conversation.h
+++ b/ui/widgets/conversation.h
@@ -107,9 +107,9 @@ protected slots:
     void onAttach();
     void onFileSelected();
     void onScrollResize();
-    void onAttachResize(const QSize& oldSize, const QSize& newSize);
     void onBadgeClose();
     void onClearButton();
+    void onTextEditDocSizeChanged(const QSizeF& size);
     
 public:
     const bool isMuc;
@@ -127,7 +127,6 @@ protected:
     QScopedPointer<Ui::Conversation> m_ui;
     KeyEnterReceiver ker;
     Resizer scrollResizeCatcher;
-    Resizer attachResizeCatcher;
     VisibilityCatcher vis;
     QString thread;
     QLabel* statusIcon;
diff --git a/ui/widgets/conversation.ui b/ui/widgets/conversation.ui
index 75ca7c5..4ff8b34 100644
--- a/ui/widgets/conversation.ui
+++ b/ui/widgets/conversation.ui
@@ -6,11 +6,11 @@
    <rect>
     <x>0</x>
     <y>0</y>
-    <width>572</width>
-    <height>484</height>
+    <width>520</width>
+    <height>658</height>
    </rect>
   </property>
-  <layout class="QHBoxLayout" name="horizontalLayout">
+  <layout class="QVBoxLayout" name="horizontalLayout">
    <property name="spacing">
     <number>0</number>
    </property>
@@ -27,321 +27,192 @@
     <number>0</number>
    </property>
    <item>
-    <widget class="QSplitter" name="splitter">
+    <widget class="QWidget" name="widget" native="true">
      <property name="sizePolicy">
-      <sizepolicy hsizetype="Preferred" vsizetype="Expanding">
+      <sizepolicy hsizetype="Preferred" vsizetype="MinimumExpanding">
        <horstretch>0</horstretch>
        <verstretch>0</verstretch>
       </sizepolicy>
      </property>
-     <property name="frameShape">
-      <enum>QFrame::NoFrame</enum>
-     </property>
-     <property name="frameShadow">
-      <enum>QFrame::Plain</enum>
-     </property>
-     <property name="orientation">
-      <enum>Qt::Vertical</enum>
-     </property>
-     <property name="childrenCollapsible">
-      <bool>false</bool>
-     </property>
-     <widget class="QWidget" name="widget" native="true">
-      <property name="sizePolicy">
-       <sizepolicy hsizetype="Preferred" vsizetype="Expanding">
-        <horstretch>0</horstretch>
-        <verstretch>0</verstretch>
-       </sizepolicy>
+     <layout class="QVBoxLayout" name="verticalLayout">
+      <property name="spacing">
+       <number>0</number>
       </property>
-      <layout class="QVBoxLayout" name="verticalLayout">
-       <property name="spacing">
-        <number>0</number>
-       </property>
-       <property name="leftMargin">
-        <number>0</number>
-       </property>
-       <property name="topMargin">
-        <number>0</number>
-       </property>
-       <property name="rightMargin">
-        <number>0</number>
-       </property>
-       <property name="bottomMargin">
-        <number>0</number>
-       </property>
-       <item>
-        <widget class="QWidget" name="widget_3" native="true">
-         <property name="maximumSize">
-          <size>
-           <width>16777215</width>
-           <height>100</height>
-          </size>
-         </property>
-         <property name="autoFillBackground">
-          <bool>true</bool>
-         </property>
-         <layout class="QHBoxLayout" name="horizontalLayout_3">
-          <property name="leftMargin">
-           <number>0</number>
-          </property>
-          <property name="topMargin">
-           <number>0</number>
-          </property>
-          <property name="rightMargin">
-           <number>0</number>
-          </property>
-          <property name="bottomMargin">
-           <number>0</number>
-          </property>
-          <item>
-           <widget class="QLabel" name="statusIcon">
-            <property name="minimumSize">
-             <size>
-              <width>0</width>
-              <height>0</height>
-             </size>
-            </property>
-            <property name="maximumSize">
-             <size>
-              <width>16777215</width>
-              <height>16777215</height>
-             </size>
-            </property>
-            <property name="text">
-             <string/>
-            </property>
-            <property name="scaledContents">
-             <bool>false</bool>
-            </property>
-            <property name="alignment">
-             <set>Qt::AlignCenter</set>
-            </property>
-           </widget>
-          </item>
-          <item>
-           <layout class="QVBoxLayout" name="verticalLayout_3">
-            <property name="spacing">
-             <number>0</number>
-            </property>
-            <property name="leftMargin">
-             <number>0</number>
-            </property>
-            <item>
-             <widget class="QLabel" name="nameLabel">
-              <property name="text">
-               <string/>
-              </property>
-             </widget>
-            </item>
-            <item>
-             <widget class="QLabel" name="statusLabel">
-              <property name="text">
-               <string/>
-              </property>
-             </widget>
-            </item>
-           </layout>
-          </item>
-          <item>
-           <spacer name="horizontalSpacer_2">
-            <property name="orientation">
-             <enum>Qt::Horizontal</enum>
-            </property>
-            <property name="sizeHint" stdset="0">
-             <size>
-              <width>40</width>
-              <height>20</height>
-             </size>
-            </property>
-           </spacer>
-          </item>
-          <item>
-           <widget class="QLabel" name="historyStatus">
-            <property name="text">
-             <string/>
-            </property>
-           </widget>
-          </item>
-          <item>
-           <widget class="QLabel" name="avatar">
-            <property name="sizePolicy">
-             <sizepolicy hsizetype="Minimum" vsizetype="Minimum">
-              <horstretch>0</horstretch>
-              <verstretch>0</verstretch>
-             </sizepolicy>
-            </property>
-            <property name="maximumSize">
-             <size>
-              <width>60</width>
-              <height>60</height>
-             </size>
-            </property>
-            <property name="baseSize">
-             <size>
-              <width>50</width>
-              <height>50</height>
-             </size>
-            </property>
-            <property name="text">
-             <string/>
-            </property>
-            <property name="scaledContents">
-             <bool>true</bool>
-            </property>
-            <property name="margin">
-             <number>5</number>
-            </property>
-           </widget>
-          </item>
-         </layout>
-        </widget>
-       </item>
-       <item>
-        <widget class="QWidget" name="ul" native="true">
-         <property name="sizePolicy">
-          <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
-           <horstretch>0</horstretch>
-           <verstretch>0</verstretch>
-          </sizepolicy>
-         </property>
-         <property name="minimumSize">
-          <size>
-           <width>0</width>
-           <height>2</height>
-          </size>
-         </property>
-         <property name="baseSize">
-          <size>
-           <width>0</width>
-           <height>2</height>
-          </size>
-         </property>
-         <property name="autoFillBackground">
-          <bool>true</bool>
-         </property>
-        </widget>
-       </item>
-       <item>
-        <widget class="QScrollArea" name="scrollArea">
-         <property name="autoFillBackground">
-          <bool>true</bool>
-         </property>
-         <property name="frameShape">
-          <enum>QFrame::NoFrame</enum>
-         </property>
-         <property name="lineWidth">
+      <property name="leftMargin">
+       <number>0</number>
+      </property>
+      <property name="topMargin">
+       <number>0</number>
+      </property>
+      <property name="rightMargin">
+       <number>0</number>
+      </property>
+      <property name="bottomMargin">
+       <number>0</number>
+      </property>
+      <item>
+       <widget class="QWidget" name="widget_3" native="true">
+        <property name="maximumSize">
+         <size>
+          <width>16777215</width>
+          <height>100</height>
+         </size>
+        </property>
+        <property name="autoFillBackground">
+         <bool>false</bool>
+        </property>
+        <layout class="QHBoxLayout" name="horizontalLayout_3">
+         <property name="leftMargin">
           <number>0</number>
          </property>
-         <property name="midLineWidth">
+         <property name="topMargin">
           <number>0</number>
          </property>
-         <property name="horizontalScrollBarPolicy">
-          <enum>Qt::ScrollBarAlwaysOff</enum>
+         <property name="rightMargin">
+          <number>0</number>
          </property>
-         <property name="sizeAdjustPolicy">
-          <enum>QAbstractScrollArea::AdjustIgnored</enum>
+         <property name="bottomMargin">
+          <number>0</number>
          </property>
-         <property name="widgetResizable">
-          <bool>true</bool>
-         </property>
-         <widget class="QWidget" name="scrollAreaWidgetContents">
-          <property name="geometry">
-           <rect>
-            <x>0</x>
-            <y>0</y>
-            <width>572</width>
-            <height>118</height>
-           </rect>
-          </property>
-          <layout class="QHBoxLayout" name="horizontalLayout_2">
+         <item>
+          <widget class="QLabel" name="statusIcon">
+           <property name="minimumSize">
+            <size>
+             <width>0</width>
+             <height>0</height>
+            </size>
+           </property>
+           <property name="maximumSize">
+            <size>
+             <width>16777215</width>
+             <height>16777215</height>
+            </size>
+           </property>
+           <property name="text">
+            <string/>
+           </property>
+           <property name="scaledContents">
+            <bool>false</bool>
+           </property>
+           <property name="alignment">
+            <set>Qt::AlignCenter</set>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <layout class="QVBoxLayout" name="verticalLayout_3">
            <property name="spacing">
             <number>0</number>
            </property>
            <property name="leftMargin">
             <number>0</number>
            </property>
-           <property name="topMargin">
-            <number>0</number>
-           </property>
-           <property name="rightMargin">
-            <number>0</number>
-           </property>
-           <property name="bottomMargin">
-            <number>0</number>
-           </property>
+           <item>
+            <widget class="QLabel" name="nameLabel">
+             <property name="text">
+              <string/>
+             </property>
+            </widget>
+           </item>
+           <item>
+            <widget class="QLabel" name="statusLabel">
+             <property name="text">
+              <string/>
+             </property>
+            </widget>
+           </item>
           </layout>
-         </widget>
-        </widget>
-       </item>
-       <item>
-        <widget class="QWidget" name="bl" native="true">
-         <property name="sizePolicy">
-          <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
-           <horstretch>0</horstretch>
-           <verstretch>0</verstretch>
-          </sizepolicy>
+         </item>
+         <item>
+          <spacer name="horizontalSpacer_2">
+           <property name="orientation">
+            <enum>Qt::Horizontal</enum>
+           </property>
+           <property name="sizeHint" stdset="0">
+            <size>
+             <width>40</width>
+             <height>20</height>
+            </size>
+           </property>
+          </spacer>
+         </item>
+         <item>
+          <widget class="QLabel" name="historyStatus">
+           <property name="text">
+            <string/>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <widget class="QLabel" name="avatar">
+           <property name="sizePolicy">
+            <sizepolicy hsizetype="Minimum" vsizetype="Minimum">
+             <horstretch>0</horstretch>
+             <verstretch>0</verstretch>
+            </sizepolicy>
+           </property>
+           <property name="maximumSize">
+            <size>
+             <width>60</width>
+             <height>60</height>
+            </size>
+           </property>
+           <property name="baseSize">
+            <size>
+             <width>50</width>
+             <height>50</height>
+            </size>
+           </property>
+           <property name="text">
+            <string/>
+           </property>
+           <property name="scaledContents">
+            <bool>true</bool>
+           </property>
+           <property name="margin">
+            <number>5</number>
+           </property>
+          </widget>
+         </item>
+        </layout>
+       </widget>
+      </item>
+      <item>
+       <widget class="QScrollArea" name="scrollArea">
+        <property name="autoFillBackground">
+         <bool>true</bool>
+        </property>
+        <property name="frameShape">
+         <enum>QFrame::NoFrame</enum>
+        </property>
+        <property name="lineWidth">
+         <number>0</number>
+        </property>
+        <property name="midLineWidth">
+         <number>0</number>
+        </property>
+        <property name="horizontalScrollBarPolicy">
+         <enum>Qt::ScrollBarAlwaysOff</enum>
+        </property>
+        <property name="sizeAdjustPolicy">
+         <enum>QAbstractScrollArea::AdjustIgnored</enum>
+        </property>
+        <property name="widgetResizable">
+         <bool>true</bool>
+        </property>
+        <widget class="QWidget" name="scrollAreaWidgetContents">
+         <property name="geometry">
+          <rect>
+           <x>0</x>
+           <y>0</y>
+           <width>520</width>
+           <height>389</height>
+          </rect>
          </property>
-         <property name="minimumSize">
-          <size>
-           <width>0</width>
-           <height>2</height>
-          </size>
-         </property>
-         <property name="maximumSize">
-          <size>
-           <width>16777215</width>
-           <height>2</height>
-          </size>
-         </property>
-         <property name="baseSize">
-          <size>
-           <width>0</width>
-           <height>2</height>
-          </size>
-         </property>
-         <property name="autoFillBackground">
-          <bool>true</bool>
-         </property>
-        </widget>
-       </item>
-      </layout>
-      <zorder>scrollArea</zorder>
-      <zorder>bl</zorder>
-      <zorder>ul</zorder>
-      <zorder>widget_3</zorder>
-     </widget>
-     <widget class="QWidget" name="widget_2" native="true">
-      <property name="sizePolicy">
-       <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
-        <horstretch>0</horstretch>
-        <verstretch>0</verstretch>
-       </sizepolicy>
-      </property>
-      <property name="autoFillBackground">
-       <bool>true</bool>
-      </property>
-      <layout class="QVBoxLayout" name="verticalLayout_2">
-       <property name="spacing">
-        <number>0</number>
-       </property>
-       <property name="leftMargin">
-        <number>0</number>
-       </property>
-       <property name="topMargin">
-        <number>0</number>
-       </property>
-       <property name="rightMargin">
-        <number>0</number>
-       </property>
-       <property name="bottomMargin">
-        <number>0</number>
-       </property>
-       <item>
-        <widget class="QWidget" name="panel" native="true">
-         <property name="autoFillBackground">
-          <bool>true</bool>
-         </property>
-         <layout class="QHBoxLayout" name="horizontalLayout_4">
+         <layout class="QHBoxLayout" name="horizontalLayout_2">
+          <property name="spacing">
+           <number>0</number>
+          </property>
           <property name="leftMargin">
            <number>0</number>
           </property>
@@ -354,156 +225,213 @@
           <property name="bottomMargin">
            <number>0</number>
           </property>
-          <item>
-           <widget class="QPushButton" name="smilesButton">
-            <property name="text">
-             <string/>
-            </property>
-            <property name="icon">
-             <iconset theme="smiley-shape">
-              <normaloff>.</normaloff>.</iconset>
-            </property>
-            <property name="flat">
-             <bool>true</bool>
-            </property>
-           </widget>
-          </item>
-          <item>
-           <spacer name="horizontalSpacer">
-            <property name="orientation">
-             <enum>Qt::Horizontal</enum>
-            </property>
-            <property name="sizeHint" stdset="0">
-             <size>
-              <width>40</width>
-              <height>20</height>
-             </size>
-            </property>
-           </spacer>
-          </item>
-          <item>
-           <widget class="QPushButton" name="attachButton">
-            <property name="text">
-             <string/>
-            </property>
-            <property name="icon">
-             <iconset theme="mail-attachment-symbolic">
-              <normaloff>.</normaloff>.</iconset>
-            </property>
-            <property name="flat">
-             <bool>true</bool>
-            </property>
-           </widget>
-          </item>
-          <item>
-           <widget class="QPushButton" name="clearButton">
-            <property name="text">
-             <string/>
-            </property>
-            <property name="icon">
-             <iconset theme="edit-clear-all">
-              <normaloff>.</normaloff>.</iconset>
-            </property>
-            <property name="flat">
-             <bool>true</bool>
-            </property>
-           </widget>
-          </item>
-          <item>
-           <widget class="QPushButton" name="sendButton">
-            <property name="sizePolicy">
-             <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
-              <horstretch>0</horstretch>
-              <verstretch>0</verstretch>
-             </sizepolicy>
-            </property>
-            <property name="text">
-             <string/>
-            </property>
-            <property name="icon">
-             <iconset theme="document-send">
-              <normaloff>.</normaloff>.</iconset>
-            </property>
-            <property name="flat">
-             <bool>true</bool>
-            </property>
-           </widget>
-          </item>
          </layout>
         </widget>
-       </item>
-       <item>
-        <widget class="QWidget" name="filesPanel" native="true">
-         <property name="autoFillBackground">
-          <bool>true</bool>
+       </widget>
+      </item>
+     </layout>
+     <zorder>scrollArea</zorder>
+     <zorder>widget_3</zorder>
+    </widget>
+   </item>
+   <item>
+    <widget class="QWidget" name="widget_2" native="true">
+     <property name="sizePolicy">
+      <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+       <horstretch>0</horstretch>
+       <verstretch>0</verstretch>
+      </sizepolicy>
+     </property>
+     <property name="autoFillBackground">
+      <bool>false</bool>
+     </property>
+     <layout class="QVBoxLayout" name="verticalLayout_2">
+      <property name="spacing">
+       <number>0</number>
+      </property>
+      <property name="sizeConstraint">
+       <enum>QLayout::SetDefaultConstraint</enum>
+      </property>
+      <property name="leftMargin">
+       <number>0</number>
+      </property>
+      <property name="topMargin">
+       <number>0</number>
+      </property>
+      <property name="rightMargin">
+       <number>0</number>
+      </property>
+      <property name="bottomMargin">
+       <number>0</number>
+      </property>
+      <item>
+       <widget class="QWidget" name="panel" native="true">
+        <property name="autoFillBackground">
+         <bool>false</bool>
+        </property>
+        <layout class="QHBoxLayout" name="horizontalLayout_4">
+         <property name="leftMargin">
+          <number>0</number>
          </property>
-        </widget>
-       </item>
-       <item>
-        <widget class="QWidget" name="ut" native="true">
-         <property name="sizePolicy">
-          <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
-           <horstretch>0</horstretch>
-           <verstretch>0</verstretch>
-          </sizepolicy>
+         <property name="topMargin">
+          <number>0</number>
          </property>
-         <property name="minimumSize">
-          <size>
-           <width>0</width>
-           <height>2</height>
-          </size>
+         <property name="rightMargin">
+          <number>0</number>
          </property>
-         <property name="maximumSize">
-          <size>
-           <width>16777215</width>
-           <height>2</height>
-          </size>
+         <property name="bottomMargin">
+          <number>0</number>
          </property>
-         <property name="baseSize">
-          <size>
-           <width>0</width>
-           <height>2</height>
-          </size>
-         </property>
-         <property name="autoFillBackground">
-          <bool>true</bool>
-         </property>
-        </widget>
-       </item>
-       <item>
-        <widget class="QPlainTextEdit" name="messageEditor">
-         <property name="sizePolicy">
-          <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
-           <horstretch>0</horstretch>
-           <verstretch>0</verstretch>
-          </sizepolicy>
-         </property>
-         <property name="minimumSize">
-          <size>
-           <width>0</width>
-           <height>30</height>
-          </size>
-         </property>
-         <property name="baseSize">
-          <size>
-           <width>0</width>
-           <height>30</height>
-          </size>
-         </property>
-         <property name="frameShape">
-          <enum>QFrame::NoFrame</enum>
-         </property>
-         <property name="placeholderText">
-          <string>Type your message here...</string>
-         </property>
-        </widget>
-       </item>
-      </layout>
-      <zorder>messageEditor</zorder>
-      <zorder>ut</zorder>
-      <zorder>filesPanel</zorder>
-      <zorder>panel</zorder>
-     </widget>
+         <item>
+          <widget class="QPushButton" name="smilesButton">
+           <property name="text">
+            <string/>
+           </property>
+           <property name="icon">
+            <iconset theme="smiley-shape">
+             <normaloff>.</normaloff>.</iconset>
+           </property>
+           <property name="flat">
+            <bool>true</bool>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <spacer name="horizontalSpacer">
+           <property name="orientation">
+            <enum>Qt::Horizontal</enum>
+           </property>
+           <property name="sizeHint" stdset="0">
+            <size>
+             <width>40</width>
+             <height>20</height>
+            </size>
+           </property>
+          </spacer>
+         </item>
+         <item>
+          <widget class="QPushButton" name="attachButton">
+           <property name="text">
+            <string/>
+           </property>
+           <property name="icon">
+            <iconset theme="mail-attachment-symbolic">
+             <normaloff>.</normaloff>.</iconset>
+           </property>
+           <property name="flat">
+            <bool>true</bool>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <widget class="QPushButton" name="clearButton">
+           <property name="text">
+            <string/>
+           </property>
+           <property name="icon">
+            <iconset theme="edit-clear-all">
+             <normaloff>.</normaloff>.</iconset>
+           </property>
+           <property name="flat">
+            <bool>true</bool>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <widget class="QPushButton" name="sendButton">
+           <property name="sizePolicy">
+            <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
+             <horstretch>0</horstretch>
+             <verstretch>0</verstretch>
+            </sizepolicy>
+           </property>
+           <property name="text">
+            <string/>
+           </property>
+           <property name="icon">
+            <iconset theme="document-send">
+             <normaloff>.</normaloff>.</iconset>
+           </property>
+           <property name="flat">
+            <bool>true</bool>
+           </property>
+          </widget>
+         </item>
+        </layout>
+       </widget>
+      </item>
+      <item>
+       <widget class="QWidget" name="filesPanel" native="true">
+        <property name="autoFillBackground">
+         <bool>false</bool>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="QTextEdit" name="messageEditor">
+        <property name="enabled">
+         <bool>true</bool>
+        </property>
+        <property name="sizePolicy">
+         <sizepolicy hsizetype="Expanding" vsizetype="Maximum">
+          <horstretch>0</horstretch>
+          <verstretch>0</verstretch>
+         </sizepolicy>
+        </property>
+        <property name="minimumSize">
+         <size>
+          <width>0</width>
+          <height>30</height>
+         </size>
+        </property>
+        <property name="maximumSize">
+         <size>
+          <width>16777215</width>
+          <height>16777215</height>
+         </size>
+        </property>
+        <property name="baseSize">
+         <size>
+          <width>0</width>
+          <height>30</height>
+         </size>
+        </property>
+        <property name="autoFillBackground">
+         <bool>false</bool>
+        </property>
+        <property name="styleSheet">
+         <string notr="true">QTextEdit {
+background-color: transparent
+}</string>
+        </property>
+        <property name="frameShape">
+         <enum>QFrame::NoFrame</enum>
+        </property>
+        <property name="verticalScrollBarPolicy">
+         <enum>Qt::ScrollBarAsNeeded</enum>
+        </property>
+        <property name="sizeAdjustPolicy">
+         <enum>QAbstractScrollArea::AdjustIgnored</enum>
+        </property>
+        <property name="html">
+         <string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Liberation Sans'; font-size:10pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+        </property>
+        <property name="acceptRichText">
+         <bool>false</bool>
+        </property>
+        <property name="placeholderText">
+         <string>Type your message here...</string>
+        </property>
+       </widget>
+      </item>
+     </layout>
+     <zorder>messageEditor</zorder>
+     <zorder>filesPanel</zorder>
+     <zorder>panel</zorder>
     </widget>
    </item>
   </layout>

From b309100f99b82a1618495aae2276710b4a7c4393 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Tue, 31 Mar 2020 18:56:49 +0300
Subject: [PATCH 054/281] 0.1.3 release

---
 README.md                    | 2 +-
 external/qxmpp               | 2 +-
 main.cpp                     | 2 +-
 packaging/Archlinux/PKGBUILD | 6 +++---
 ui/widgets/conversation.cpp  | 4 +++-
 5 files changed, 9 insertions(+), 7 deletions(-)

diff --git a/README.md b/README.md
index 2ff8eb9..1366c96 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@
 [![AUR version](https://img.shields.io/aur/version/squawk?style=flat-square)](https://aur.archlinux.org/packages/squawk/)
 [![Liberapay patrons](https://img.shields.io/liberapay/patrons/macaw.me?logo=liberapay&style=flat-square)](https://liberapay.com/macaw.me)
 
-![Squawk screenshot](https://macaw.me/images/squawk/0.1.2.png)
+![Squawk screenshot](https://macaw.me/images/squawk/0.1.3.png)
 
 ### Prerequisites
 
diff --git a/external/qxmpp b/external/qxmpp
index f8c546c..067c774 160000
--- a/external/qxmpp
+++ b/external/qxmpp
@@ -1 +1 @@
-Subproject commit f8c546c5b701c53d708a38a951fcc734eaee7940
+Subproject commit 067c7743b1c72d055a749a7611efd2f9026fe784
diff --git a/main.cpp b/main.cpp
index f3a410c..c546b57 100644
--- a/main.cpp
+++ b/main.cpp
@@ -39,7 +39,7 @@ int main(int argc, char *argv[])
     
     QApplication::setApplicationName("squawk");
     QApplication::setApplicationDisplayName("Squawk");
-    QApplication::setApplicationVersion("0.1.2");
+    QApplication::setApplicationVersion("0.1.3");
     
     QTranslator qtTranslator;
     qtTranslator.load("qt_" + QLocale::system().name(), QLibraryInfo::location(QLibraryInfo::TranslationsPath));
diff --git a/packaging/Archlinux/PKGBUILD b/packaging/Archlinux/PKGBUILD
index 923a7a0..400bc8c 100644
--- a/packaging/Archlinux/PKGBUILD
+++ b/packaging/Archlinux/PKGBUILD
@@ -1,15 +1,15 @@
 # Maintainer: Yury Gubich <blue@macaw.me>
 pkgname=squawk
-pkgver=0.1.2
+pkgver=0.1.3
 pkgrel=1
-pkgdesc="An XMPP desktop messenger, written on qt"
+pkgdesc="An XMPP desktop messenger, written on pure c++ (qt)"
 arch=('i686' 'x86_64')
 url="https://git.macaw.me/blue/squawk"
 license=('GPL3')
 depends=('hicolor-icon-theme' 'desktop-file-utils' 'lmdb' 'qxmpp>=1.1.0')
 makedepends=('cmake>=3.3' 'imagemagick' 'qt5-tools')
 source=("$pkgname-$pkgver.tar.gz")
-sha256sums=('9f89f41e52047c0e687a0a1b766331c19747c11f8f329e540402eaddbca4b677')
+sha256sums=('adb172bb7d5b81bd9b83b192481a79ac985877e81604f401b3f2a08613b359bc')
 build() {
         cd "$srcdir/squawk"
         cmake . -D CMAKE_INSTALL_PREFIX=/usr -D CMAKE_BUILD_TYPE=Release
diff --git a/ui/widgets/conversation.cpp b/ui/widgets/conversation.cpp
index 340fd82..952973d 100644
--- a/ui/widgets/conversation.cpp
+++ b/ui/widgets/conversation.cpp
@@ -78,9 +78,11 @@ Conversation::Conversation(bool muc, Models::Account* acc, const QString pJid, c
     m_ui->scrollArea->setWidget(line);
     vs->installEventFilter(&vis);
     
+    line->setAutoFillBackground(false);
     if (testAttribute(Qt::WA_TranslucentBackground)) {
         m_ui->scrollArea->setAutoFillBackground(false);
-        m_ui->scrollArea->viewport()->setAutoFillBackground(false);
+    } else {
+        m_ui->scrollArea->setBackgroundRole(QPalette::Base);
     }
     
     connect(vs, &QScrollBar::valueChanged, this, &Conversation::onSliderValueChanged);

From ddfb3419cc0db6c0fd899fb44b9f1271901adf26 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Sat, 4 Apr 2020 01:28:15 +0300
Subject: [PATCH 055/281] Shared namespace refactoring, enum class refactoring,
 going offline related crash fix

---
 CMakeLists.txt                    |  20 +-
 core/account.cpp                  |  68 +--
 core/account.h                    |   6 +-
 core/archive.h                    |   2 +-
 core/contact.cpp                  |   2 +-
 core/rosteritem.h                 |   4 +-
 core/squawk.cpp                   |  33 +-
 core/squawk.h                     |  12 +-
 global.cpp                        | 765 ------------------------------
 global.h                          | 512 --------------------
 main.cpp                          |   5 +
 shared.h                          |  29 ++
 shared/enums.h                    | 114 +++++
 shared/global.cpp                 | 170 +++++++
 shared/global.h                   |  64 +++
 shared/icons.cpp                  |  96 ++++
 shared/icons.h                    | 176 +++++++
 shared/message.cpp                | 399 ++++++++++++++++
 shared/message.h                  | 125 +++++
 shared/utils.cpp                  |  29 ++
 shared/utils.h                    |  72 +++
 shared/vcard.cpp                  | 288 +++++++++++
 shared/vcard.h                    | 152 ++++++
 translations/squawk.ru.ts         | 266 ++---------
 ui/models/abstractparticipant.cpp |  12 +-
 ui/models/abstractparticipant.h   |   6 +-
 ui/models/account.cpp             |  32 +-
 ui/models/account.h               |   5 +-
 ui/models/accounts.cpp            |   2 +-
 ui/models/contact.cpp             |  30 +-
 ui/models/contact.h               |   6 +-
 ui/models/group.cpp               |   2 +-
 ui/models/item.cpp                |   4 +-
 ui/models/item.h                  |   2 +-
 ui/models/presence.cpp            |   1 +
 ui/models/presence.h              |   4 +-
 ui/models/room.cpp                |  10 +-
 ui/models/room.h                  |   3 +-
 ui/models/roster.cpp              |  24 +-
 ui/models/roster.h                |   3 +-
 ui/squawk.cpp                     |  41 +-
 ui/squawk.h                       |   6 +-
 ui/utils/message.cpp              |   4 +-
 ui/utils/message.h                |   4 +-
 ui/utils/messageline.cpp          |   1 +
 ui/utils/messageline.h            |   2 +-
 ui/utils/progress.cpp             |   2 +
 ui/utils/progress.h               |   2 -
 ui/widgets/accounts.cpp           |   2 +-
 ui/widgets/chat.cpp               |   2 +-
 ui/widgets/chat.h                 |   4 +-
 ui/widgets/conversation.cpp       |   2 +
 ui/widgets/conversation.h         |   2 +-
 ui/widgets/vcard/emailsmodel.cpp  |   3 +
 ui/widgets/vcard/emailsmodel.h    |   2 +-
 ui/widgets/vcard/phonesmodel.cpp  |   3 +
 ui/widgets/vcard/phonesmodel.h    |   2 +-
 ui/widgets/vcard/vcard.cpp        |   2 +-
 ui/widgets/vcard/vcard.h          |   2 +-
 59 files changed, 1948 insertions(+), 1695 deletions(-)
 delete mode 100644 global.cpp
 delete mode 100644 global.h
 create mode 100644 shared.h
 create mode 100644 shared/enums.h
 create mode 100644 shared/global.cpp
 create mode 100644 shared/global.h
 create mode 100644 shared/icons.cpp
 create mode 100644 shared/icons.h
 create mode 100644 shared/message.cpp
 create mode 100644 shared/message.h
 create mode 100644 shared/utils.cpp
 create mode 100644 shared/utils.h
 create mode 100644 shared/vcard.cpp
 create mode 100644 shared/vcard.h

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 59582a1..3e9968c 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -25,9 +25,25 @@ message("Build type: ${CMAKE_BUILD_TYPE}")
 
 set(squawk_SRC
   main.cpp
-  global.cpp
   exception.cpp
   signalcatcher.cpp
+  shared/global.cpp
+  shared/utils.cpp
+  shared/message.cpp
+  shared/vcard.cpp
+  shared/icons.cpp
+)
+
+set(squawk_HEAD
+  exception.h
+  signalcatcher.h
+  shared.h
+  shared/enums.h
+  shared/message.h
+  shared/global.h
+  shared/utils.h
+  shared/vcard.h
+  shared/icons.h
 )
 
 configure_file(resources/images/logo.svg squawk.svg COPYONLY)
@@ -46,7 +62,7 @@ add_custom_target(translations ALL DEPENDS ${QM_FILES})
 
 qt5_add_resources(RCC resources/resources.qrc)
 
-add_executable(squawk ${squawk_SRC} ${RCC})
+add_executable(squawk ${squawk_SRC} ${squawk_HEAD} ${RCC})
 target_link_libraries(squawk Qt5::Widgets)
 
 option(SYSTEM_QXMPP "Use system qxmpp lib" ON) 
diff --git a/core/account.cpp b/core/account.cpp
index 3acdeae..37a8c16 100644
--- a/core/account.cpp
+++ b/core/account.cpp
@@ -30,7 +30,7 @@ Account::Account(const QString& p_login, const QString& p_server, const QString&
     client(),
     config(),
     presence(),
-    state(Shared::disconnected),
+    state(Shared::ConnectionState::disconnected),
     groups(),
     cm(new QXmppCarbonManager()),
     am(new QXmppMamManager()),
@@ -182,9 +182,9 @@ Shared::ConnectionState Core::Account::getState() const
 
 void Core::Account::connect()
 {
-    if (state == Shared::disconnected) {
+    if (state == Shared::ConnectionState::disconnected) {
         reconnectTimes = maxReconnectTimes;
-        state = Shared::connecting;
+        state = Shared::ConnectionState::connecting;
         client.connectToServer(config, presence);
         emit connectionStateChanged(state);
     } else {
@@ -195,19 +195,19 @@ void Core::Account::connect()
 void Core::Account::disconnect()
 {
     reconnectTimes = 0;
-    if (state != Shared::disconnected) {
+    if (state != Shared::ConnectionState::disconnected) {
         clearConferences();
         client.disconnectFromServer();
-        state = Shared::disconnected;
+        state = Shared::ConnectionState::disconnected;
         emit connectionStateChanged(state);
     }
 }
 
 void Core::Account::onClientConnected()
 {
-    if (state == Shared::connecting) {
+    if (state == Shared::ConnectionState::connecting) {
         reconnectTimes = maxReconnectTimes;
-        state = Shared::connected;
+        state = Shared::ConnectionState::connected;
         dm->requestItems(getServer());
         dm->requestInfo(getServer());
         emit connectionStateChanged(state);
@@ -219,16 +219,16 @@ void Core::Account::onClientConnected()
 void Core::Account::onClientDisconnected()
 {
     clearConferences();
-    if (state != Shared::disconnected) {
+    if (state != Shared::ConnectionState::disconnected) {
         if (reconnectTimes > 0) {
             qDebug() << "Account" << name << "is reconnecting for" << reconnectTimes << "more times";
             --reconnectTimes;
-            state = Shared::connecting;
+            state = Shared::ConnectionState::connecting;
             client.connectToServer(config, presence);
             emit connectionStateChanged(state);
         } else {
             qDebug() << "Account" << name << "has been disconnected";
-            state = Shared::disconnected;
+            state = Shared::ConnectionState::disconnected;
             emit connectionStateChanged(state);
         }
     } else {
@@ -238,7 +238,7 @@ void Core::Account::onClientDisconnected()
 
 void Core::Account::reconnect()
 {
-    if (state == Shared::connected) {
+    if (state == Shared::ConnectionState::connected) {
         ++reconnectTimes;
         client.disconnectFromServer();
     } else {
@@ -281,7 +281,7 @@ void Core::Account::onRosterReceived()
 void Core::Account::setReconnectTimes(unsigned int times)
 {
     maxReconnectTimes = times;
-    if (state == Shared::connected) {
+    if (state == Shared::ConnectionState::connected) {
         reconnectTimes = times;
     }
 }
@@ -355,7 +355,7 @@ void Core::Account::addedAccount(const QString& jid)
     if (newContact) {
         QMap<QString, QVariant> cData({
             {"name", re.name()},
-            {"state", state}
+            {"state", QVariant::fromValue(state)}
         });
         
         Archive::AvatarInfo info;
@@ -427,7 +427,7 @@ void Core::Account::onPresenceReceived(const QXmppPresence& p_presence)
     
     if (jid == myJid) {
         if (resource == getResource()) {
-            emit availabilityChanged(p_presence.availableStatusType());
+            emit availabilityChanged(static_cast<Shared::Availability>(p_presence.availableStatusType()));
         } else {
             if (!ownVCardRequestInProgress) {
                 switch (p_presence.vCardUpdateType()) {
@@ -521,21 +521,25 @@ void Core::Account::setServer(const QString& p_server)
 
 Shared::Availability Core::Account::getAvailability() const
 {
-    if (state == Shared::connected) {
+    if (state == Shared::ConnectionState::connected) {
         QXmppPresence::AvailableStatusType pres = presence.availableStatusType();
         return static_cast<Shared::Availability>(pres);         //they are compatible;
     } else {
-        return Shared::offline;
+        return Shared::Availability::offline;
     }
 }
 
 void Core::Account::setAvailability(Shared::Availability avail)
 {
-    QXmppPresence::AvailableStatusType pres = static_cast<QXmppPresence::AvailableStatusType>(avail);
-    
-    presence.setAvailableStatusType(pres);
-    if (state != Shared::disconnected) {        //TODO not sure how to do here - changing state may cause connection or disconnection
-        client.setClientPresence(presence);
+    if (avail == Shared::Availability::offline) {
+        disconnect();               //TODO not sure how to do here - changing state may cause connection or disconnection
+    } else {
+        QXmppPresence::AvailableStatusType pres = static_cast<QXmppPresence::AvailableStatusType>(avail);
+        
+        presence.setAvailableStatusType(pres);
+        if (state != Shared::ConnectionState::disconnected) {
+            client.setClientPresence(presence);
+        }
     }
 }
 
@@ -624,7 +628,7 @@ void Core::Account::sendMessage(Shared::Message data)
     QString jid = data.getPenPalJid();
     QString id = data.getId();
     RosterItem* ri = getRosterItem(jid);
-    if (state == Shared::connected) {
+    if (state == Shared::ConnectionState::connected) {
         QXmppMessage msg(getFullJid(), data.getTo(), data.getBody(), data.getThread());
         msg.setId(id);
         msg.setType(static_cast<QXmppMessage::Type>(data.getType()));       //it is safe here, my type is compatible
@@ -660,7 +664,7 @@ void Core::Account::sendMessage(Shared::Message data)
 
 void Core::Account::sendMessage(const Shared::Message& data, const QString& path)
 {
-    if (state == Shared::connected) {
+    if (state == Shared::ConnectionState::connected) {
         QString url = network->getFileRemoteUrl(path);
         if (url.size() != 0) {
             sendMessageWithLocalUploadedFile(data, url);
@@ -727,9 +731,9 @@ bool Core::Account::handleChatMessage(const QXmppMessage& msg, bool outgoing, bo
             cnt = new Contact(jid, name);
             contacts.insert(std::make_pair(jid, cnt));
             outOfRosterContacts.insert(jid);
-            cnt->setSubscriptionState(Shared::unknown);
+            cnt->setSubscriptionState(Shared::SubscriptionState::unknown);
             emit addContact(jid, "", QMap<QString, QVariant>({
-                {"state", Shared::unknown}
+                {"state", QVariant::fromValue(Shared::SubscriptionState::unknown)}
             }));
             handleNewContact(cnt);
         }
@@ -963,7 +967,7 @@ void Core::Account::onContactGroupAdded(const QString& group)
     
     QMap<QString, QVariant> cData({
         {"name", contact->getName()},
-        {"state", contact->getSubscriptionState()}
+        {"state", QVariant::fromValue(contact->getSubscriptionState())}
     });
     addToGroup(contact->jid, group);
     emit addContact(contact->jid, group, cData);
@@ -993,7 +997,7 @@ void Core::Account::onContactSubscriptionStateChanged(Shared::SubscriptionState
 {
     Contact* contact = static_cast<Contact*>(sender());
     QMap<QString, QVariant> cData({
-        {"state", cstate},
+        {"state", QVariant::fromValue(cstate)},
     });
     emit changeContact(contact->jid, cData);
 }
@@ -1031,7 +1035,7 @@ Shared::SubscriptionState Core::Account::castSubscriptionState(QXmppRosterIq::It
 {
     Shared::SubscriptionState state;
     if (qs == QXmppRosterIq::Item::NotSet) {
-        state = Shared::unknown;
+        state = Shared::SubscriptionState::unknown;
     } else {
         state = static_cast<Shared::SubscriptionState>(qs);
     }
@@ -1145,7 +1149,7 @@ void Core::Account::onClientError(QXmppClient::Error err)
 
 void Core::Account::subscribeToContact(const QString& jid, const QString& reason)
 {
-    if (state == Shared::connected) {
+    if (state == Shared::ConnectionState::connected) {
         rm->subscribe(jid, reason);
     } else {
         qDebug() << "An attempt to subscribe account " << name << " to contact " << jid << " but the account is not in the connected state, skipping";
@@ -1154,7 +1158,7 @@ void Core::Account::subscribeToContact(const QString& jid, const QString& reason
 
 void Core::Account::unsubscribeFromContact(const QString& jid, const QString& reason)
 {
-    if (state == Shared::connected) {
+    if (state == Shared::ConnectionState::connected) {
         rm->unsubscribe(jid, reason);
     } else {
         qDebug() << "An attempt to unsubscribe account " << name << " from contact " << jid << " but the account is not in the connected state, skipping";
@@ -1163,7 +1167,7 @@ void Core::Account::unsubscribeFromContact(const QString& jid, const QString& re
 
 void Core::Account::removeContactRequest(const QString& jid)
 {
-    if (state == Shared::connected) {
+    if (state == Shared::ConnectionState::connected) {
         std::set<QString>::const_iterator itr = outOfRosterContacts.find(jid);
         if (itr != outOfRosterContacts.end()) {
             outOfRosterContacts.erase(itr);
@@ -1179,7 +1183,7 @@ void Core::Account::removeContactRequest(const QString& jid)
 
 void Core::Account::addContactRequest(const QString& jid, const QString& name, const QSet<QString>& groups)
 {
-    if (state == Shared::connected) {
+    if (state == Shared::ConnectionState::connected) {
         std::map<QString, QString>::const_iterator itr = queuedContacts.find(jid);
         if (itr != queuedContacts.end()) {
             qDebug() << "An attempt to add contact " << jid << " to account " << name << " but the account is already queued for adding, skipping";
diff --git a/core/account.h b/core/account.h
index 02ae2ab..1688fc9 100644
--- a/core/account.h
+++ b/core/account.h
@@ -43,7 +43,7 @@
 #include <QXmppVCardManager.h>
 #include <QXmppMessageReceiptManager.h>
 
-#include "global.h"
+#include "shared.h"
 #include "contact.h"
 #include "conference.h"
 #include "networkaccess.h"
@@ -101,8 +101,8 @@ public slots:
     
 signals:
     void changed(const QMap<QString, QVariant>& data);
-    void connectionStateChanged(int);
-    void availabilityChanged(int);
+    void connectionStateChanged(Shared::ConnectionState);
+    void availabilityChanged(Shared::Availability);
     void addGroup(const QString& name);
     void removeGroup(const QString& name);
     void addRoom(const QString& jid, const QMap<QString, QVariant>& data);
diff --git a/core/archive.h b/core/archive.h
index 4d61f95..6facf68 100644
--- a/core/archive.h
+++ b/core/archive.h
@@ -24,7 +24,7 @@
 #include <QMimeDatabase>
 #include <QMimeType>
 
-#include "global.h"
+#include "shared/message.h"
 #include "exception.h"
 #include <lmdb.h>
 #include <list>
diff --git a/core/contact.cpp b/core/contact.cpp
index 711c505..0471a5c 100644
--- a/core/contact.cpp
+++ b/core/contact.cpp
@@ -22,7 +22,7 @@
 Core::Contact::Contact(const QString& pJid, const QString& account, QObject* parent):
     RosterItem(pJid, account, parent),
     groups(),
-    subscriptionState(Shared::unknown)
+    subscriptionState(Shared::SubscriptionState::unknown)
 {
 }
 
diff --git a/core/rosteritem.h b/core/rosteritem.h
index b6c60cd..387ebfc 100644
--- a/core/rosteritem.h
+++ b/core/rosteritem.h
@@ -31,7 +31,9 @@
 #include <QXmppVCardIq.h>
 #include <QXmppPresence.h>
 
-#include "../global.h"
+#include "shared/enums.h"
+#include "shared/message.h"
+#include "shared/vcard.h"
 #include "archive.h"
 
 namespace Core {
diff --git a/core/squawk.cpp b/core/squawk.cpp
index 75cceec..387c685 100644
--- a/core/squawk.cpp
+++ b/core/squawk.cpp
@@ -146,8 +146,8 @@ void Core::Squawk::addAccount(const QString& login, const QString& server, const
         {"name", name},
         {"password", password},
         {"resource", resource},
-        {"state", Shared::disconnected},
-        {"offline", Shared::offline},
+        {"state", QVariant::fromValue(Shared::ConnectionState::disconnected)},
+        {"offline", QVariant::fromValue(Shared::Availability::offline)},
         {"error", ""},
         {"avatarPath", acc->getAvatarPath()}
     };
@@ -155,14 +155,11 @@ void Core::Squawk::addAccount(const QString& login, const QString& server, const
     emit newAccount(map);
 }
 
-void Core::Squawk::changeState(int p_state)
+void Core::Squawk::changeState(Shared::Availability p_state)
 {
-    Shared::Availability avail;
-    if (p_state < Shared::availabilityLowest && p_state > Shared::availabilityHighest) {
-        qDebug("An attempt to set invalid availability to Squawk core, skipping");
+    if (state != p_state) {
+        state = p_state;
     }
-    avail = static_cast<Shared::Availability>(p_state);
-    state = avail;
     
     for (std::deque<Account*>::iterator itr = accounts.begin(), end = accounts.end(); itr != end; ++itr) {
         (*itr)->setAvailability(state);
@@ -190,20 +187,20 @@ void Core::Squawk::disconnectAccount(const QString& account)
     itr->second->disconnect();
 }
 
-void Core::Squawk::onAccountConnectionStateChanged(int state)
+void Core::Squawk::onAccountConnectionStateChanged(Shared::ConnectionState p_state)
 {
     Account* acc = static_cast<Account*>(sender());
-    emit changeAccount(acc->getName(), {{"state", state}});
+    emit changeAccount(acc->getName(), {{"state", QVariant::fromValue(p_state)}});
     
-    if (state == Shared::disconnected) {
+    if (p_state == Shared::ConnectionState::disconnected) {
         bool equals = true;
         for (Accounts::const_iterator itr = accounts.begin(), end = accounts.end(); itr != end; itr++) {
-            if ((*itr)->getState() != Shared::disconnected) {
+            if ((*itr)->getState() != Shared::ConnectionState::disconnected) {
                 equals = false;
             }
         }
-        if (equals) {
-            state = Shared::offline;
+        if (equals && state != Shared::Availability::offline) {
+            state = Shared::Availability::offline;
             emit stateChanged(state);
         }
     }
@@ -257,10 +254,10 @@ void Core::Squawk::onAccountRemovePresence(const QString& jid, const QString& na
     emit removePresence(acc->getName(), jid, name);
 }
 
-void Core::Squawk::onAccountAvailabilityChanged(int state)
+void Core::Squawk::onAccountAvailabilityChanged(Shared::Availability state)
 {
     Account* acc = static_cast<Account*>(sender());
-    emit changeAccount(acc->getName(), {{"availability", state}});
+    emit changeAccount(acc->getName(), {{"availability", QVariant::fromValue(state)}});
 }
 
 void Core::Squawk::onAccountChanged(const QMap<QString, QVariant>& data)
@@ -324,7 +321,7 @@ void Core::Squawk::modifyAccountRequest(const QString& name, const QMap<QString,
     Core::Account* acc = itr->second;
     Shared::ConnectionState st = acc->getState();
     
-    if (st != Shared::disconnected) {
+    if (st != Shared::ConnectionState::disconnected) {
         acc->reconnect();
     }
     
@@ -367,7 +364,7 @@ void Core::Squawk::removeAccountRequest(const QString& name)
     }
     
     Account* acc = itr->second;
-    if (acc->getState() != Shared::disconnected) {
+    if (acc->getState() != Shared::ConnectionState::disconnected) {
         acc->disconnect();
     }
     
diff --git a/core/squawk.h b/core/squawk.h
index 76f5170..29e5b8c 100644
--- a/core/squawk.h
+++ b/core/squawk.h
@@ -28,7 +28,9 @@
 #include <deque>
 
 #include "account.h"
-#include "../global.h"
+#include "shared/enums.h"
+#include "shared/message.h"
+#include "shared/global.h"
 #include "networkaccess.h"
 
 namespace Core
@@ -54,7 +56,7 @@ signals:
     void changeContact(const QString& account, const QString& jid, const QMap<QString, QVariant>& data);
     void addPresence(const QString& account, const QString& jid, const QString& name, const QMap<QString, QVariant>& data);
     void removePresence(const QString& account, const QString& jid, const QString& name);
-    void stateChanged(int state);
+    void stateChanged(Shared::Availability state);
     void accountMessage(const QString& account, const Shared::Message& data);
     void responseArchive(const QString& account, const QString& jid, const std::list<Shared::Message>& list);
     void addRoom(const QString& account, const QString jid, const QMap<QString, QVariant>& data);
@@ -79,7 +81,7 @@ public slots:
     void removeAccountRequest(const QString& name);
     void connectAccount(const QString& account);
     void disconnectAccount(const QString& account);
-    void changeState(int state);
+    void changeState(Shared::Availability state);
     void sendMessage(const QString& account, const Shared::Message& data);
     void sendMessage(const QString& account, const Shared::Message& data, const QString& path);
     void requestArchive(const QString& account, const QString& jid, int count, const QString& before);
@@ -112,8 +114,8 @@ private:
     void addAccount(const QString& login, const QString& server, const QString& password, const QString& name, const QString& resource);
     
 private slots:
-    void onAccountConnectionStateChanged(int state);
-    void onAccountAvailabilityChanged(int state);
+    void onAccountConnectionStateChanged(Shared::ConnectionState state);
+    void onAccountAvailabilityChanged(Shared::Availability state);
     void onAccountChanged(const QMap<QString, QVariant>& data);
     void onAccountAddGroup(const QString& name);
     void onAccountError(const QString& text);
diff --git a/global.cpp b/global.cpp
deleted file mode 100644
index 9ece2ba..0000000
--- a/global.cpp
+++ /dev/null
@@ -1,765 +0,0 @@
-/*
- * Squawk messenger. 
- * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
- *
- * 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 "global.h"
-#include <uuid/uuid.h>
-#include <QApplication>
-#include <QPalette>
-#include <QIcon>
-#include <QDebug>
-
-Shared::Message::Message(Shared::Message::Type p_type):
-    jFrom(),
-    rFrom(),
-    jTo(),
-    rTo(),
-    id(),
-    body(),
-    time(),
-    thread(),
-    type(p_type),
-    outgoing(false),
-    forwarded(false),
-    state(State::delivered),
-    edited(false)
-{
-}
-
-Shared::Message::Message():
-    jFrom(),
-    rFrom(),
-    jTo(),
-    rTo(),
-    id(),
-    body(),
-    time(),
-    thread(),
-    type(Message::normal),
-    outgoing(false),
-    forwarded(false),
-    state(State::delivered),
-    edited(false),
-    errorText(),
-    originalMessage(),
-    lastModified()
-{
-}
-
-QString Shared::Message::getBody() const
-{
-    return body;
-}
-
-QString Shared::Message::getFrom() const
-{
-    QString from = jFrom;
-    if (rFrom.size() > 0) {
-        from += "/" + rFrom;
-    }
-    return from;
-}
-
-QString Shared::Message::getTo() const
-{
-    QString to = jTo;
-    if (rTo.size() > 0) {
-        to += "/" + rTo;
-    }
-    return to;
-}
-
-QString Shared::Message::getId() const
-{
-    return id;
-}
-
-QDateTime Shared::Message::getTime() const
-{
-    return time;
-}
-
-void Shared::Message::setBody(const QString& p_body)
-{
-    body = p_body;
-}
-
-void Shared::Message::setFrom(const QString& from)
-{
-    QStringList list = from.split("/");
-    if (list.size() == 1) {
-        jFrom = from;
-    } else {
-        jFrom = list.front();
-        rFrom = list.back();
-    }
-}
-
-void Shared::Message::setTo(const QString& to)
-{
-    QStringList list = to.split("/");
-    if (list.size() == 1) {
-        jTo = to;
-    } else {
-        jTo = list.front();
-        rTo = list.back();
-    }
-}
-
-void Shared::Message::setId(const QString& p_id)
-{
-    id = p_id;
-}
-
-void Shared::Message::setTime(const QDateTime& p_time)
-{
-    time = p_time;
-}
-
-QString Shared::Message::getFromJid() const
-{
-    return jFrom;
-}
-
-QString Shared::Message::getFromResource() const
-{
-    return rFrom;
-}
-
-QString Shared::Message::getToJid() const
-{
-    return jTo;
-}
-
-QString Shared::Message::getToResource() const
-{
-    return rTo;
-}
-
-QString Shared::Message::getErrorText() const
-{
-    return errorText;
-}
-
-QString Shared::Message::getPenPalJid() const
-{
-    if (outgoing) {
-        return jTo;
-    } else {
-        return jFrom;
-    }
-}
-
-QString Shared::Message::getPenPalResource() const
-{
-    if (outgoing) {
-        return rTo;
-    } else {
-        return rFrom;
-    }
-}
-
-Shared::Message::State Shared::Message::getState() const
-{
-    return state;
-}
-
-bool Shared::Message::getEdited() const
-{
-    return edited;
-}
-
-void Shared::Message::setFromJid(const QString& from)
-{
-    jFrom = from;
-}
-
-void Shared::Message::setFromResource(const QString& from)
-{
-    rFrom = from;
-}
-
-void Shared::Message::setToJid(const QString& to)
-{
-    jTo = to;
-}
-
-void Shared::Message::setToResource(const QString& to)
-{
-    rTo = to;
-}
-
-void Shared::Message::setErrorText(const QString& err)
-{
-    if (state == State::error) {
-        errorText = err;
-    }
-}
-
-bool Shared::Message::getOutgoing() const
-{
-    return outgoing;
-}
-
-void Shared::Message::setOutgoing(bool og)
-{
-    outgoing = og;
-}
-
-bool Shared::Message::getForwarded() const
-{
-    return forwarded;
-}
-
-void Shared::Message::generateRandomId()
-{
-    id = generateUUID();
-}
-
-QString Shared::Message::getThread() const
-{
-    return thread;
-}
-
-void Shared::Message::setForwarded(bool fwd)
-{
-    forwarded = fwd;
-}
-
-void Shared::Message::setThread(const QString& p_body)
-{
-    thread = p_body;
-}
-
-QDateTime Shared::Message::getLastModified() const
-{
-    return lastModified;
-}
-
-QString Shared::Message::getOriginalBody() const
-{
-    return originalMessage;
-}
-
-Shared::Message::Type Shared::Message::getType() const
-{
-    return type;
-}
-
-void Shared::Message::setType(Shared::Message::Type t)
-{
-    type = t;
-}
-
-void Shared::Message::setState(Shared::Message::State p_state)
-{
-    state = p_state;
-    
-    if (state != State::error) {
-        errorText = "";
-    }
-}
-
-bool Shared::Message::serverStored() const
-{
-    return state == State::delivered || state == State::sent;
-}
-
-void Shared::Message::setEdited(bool p_edited)
-{
-    edited = p_edited;
-}
-
-void Shared::Message::serialize(QDataStream& data) const
-{
-    data << jFrom;
-    data << rFrom;
-    data << jTo;
-    data << rTo;
-    data << id;
-    data << body;
-    data << time;
-    data << thread;
-    data << (quint8)type;
-    data << outgoing;
-    data << forwarded;
-    data << oob;
-    data << (quint8)state;
-    data << edited;
-    if (state == State::error) {
-        data << errorText;
-    }
-    if (edited) {
-        data << originalMessage;
-        data << lastModified;
-    }
-}
-
-void Shared::Message::deserialize(QDataStream& data)
-{
-    data >> jFrom;
-    data >> rFrom;
-    data >> jTo;
-    data >> rTo;
-    data >> id;
-    data >> body;
-    data >> time;
-    data >> thread;
-    quint8 t;
-    data >> t;
-    type = static_cast<Type>(t);
-    data >> outgoing;
-    data >> forwarded;
-    data >> oob;
-    quint8 s;
-    data >> s;
-    state = static_cast<State>(s);
-    data >> edited;
-    if (state == State::error) {
-        data >> errorText;
-    }
-    if (edited) {
-        data >> originalMessage;
-        data >> lastModified;
-    }
-}
-
-bool Shared::Message::change(const QMap<QString, QVariant>& data)
-{
-    QMap<QString, QVariant>::const_iterator itr = data.find("state");
-    if (itr != data.end()) {
-        setState(static_cast<State>(itr.value().toUInt()));
-    }
-    
-    if (state == State::error) {
-        itr = data.find("errorText");
-        if (itr != data.end()) {
-            setErrorText(itr.value().toString());
-        }
-    }
-    
-    bool idChanged = false;
-    itr = data.find("id");
-    if (itr != data.end()) {
-        QString newId = itr.value().toString();
-        if (id != newId) {
-            setId(newId);
-            idChanged = true;
-        }
-    }
-    itr = data.find("body");
-    if (itr != data.end()) {
-        QMap<QString, QVariant>::const_iterator dItr = data.find("stamp");
-        QDateTime correctionDate;
-        if (dItr != data.end()) {
-            correctionDate = dItr.value().toDateTime();
-        } else {
-            correctionDate = QDateTime::currentDateTimeUtc();      //in case there is no information about time of this correction it's applied
-        }
-        if (!edited || lastModified < correctionDate) {
-            originalMessage = body;
-            lastModified = correctionDate;
-            setBody(itr.value().toString());
-            setEdited(true);
-        }
-    }
-    
-    return idChanged;
-}
-
-QString Shared::generateUUID()
-{
-    uuid_t uuid;
-    uuid_generate(uuid);
-    
-    char uuid_str[36];
-    uuid_unparse_lower(uuid, uuid_str);
-    return uuid_str;
-}
-
-void Shared::Message::setCurrentTime()
-{
-    time = QDateTime::currentDateTimeUtc();
-}
-
-QString Shared::Message::getOutOfBandUrl() const
-{
-    return oob;
-}
-
-bool Shared::Message::hasOutOfBandUrl() const
-{
-    return oob.size() > 0;
-}
-
-void Shared::Message::setOutOfBandUrl(const QString& url)
-{
-    oob = url;
-}
-
-bool Shared::Message::storable() const
-{
-    return id.size() > 0 && (body.size() > 0 || oob.size()) > 0;
-}
-
-Shared::VCard::Contact::Contact(Shared::VCard::Contact::Role p_role, bool p_prefered):
-    role(p_role),
-    prefered(p_prefered)
-{}
-
-Shared::VCard::Email::Email(const QString& addr, Shared::VCard::Contact::Role p_role, bool p_prefered):
-    Contact(p_role, p_prefered),
-    address(addr)
-{}
-
-Shared::VCard::Phone::Phone(const QString& nmbr, Shared::VCard::Phone::Type p_type, Shared::VCard::Contact::Role p_role, bool p_prefered):
-    Contact(p_role, p_prefered),
-    number(nmbr),
-    type(p_type)
-{}
-
-Shared::VCard::Address::Address(const QString& zCode, const QString& cntry, const QString& rgn, const QString& lclty, const QString& strt, const QString& ext, Shared::VCard::Contact::Role p_role, bool p_prefered):
-    Contact(p_role, p_prefered),
-    zipCode(zCode),
-    country(cntry),
-    region(rgn),
-    locality(lclty),
-    street(strt),
-    external(ext)
-{}
-
-Shared::VCard::VCard():
-    fullName(),
-    firstName(),
-    middleName(),
-    lastName(),
-    nickName(),
-    description(),
-    url(),
-    organizationName(),
-    organizationUnit(),
-    organizationRole(),
-    jobTitle(),
-    birthday(),
-    photoType(Avatar::empty),
-    photoPath(),
-    receivingTime(QDateTime::currentDateTimeUtc()),
-    emails(),
-    phones(),
-    addresses()
-{}
-
-Shared::VCard::VCard(const QDateTime& creationTime):
-    fullName(),
-    firstName(),
-    middleName(),
-    lastName(),
-    nickName(),
-    description(),
-    url(),
-    organizationName(),
-    organizationUnit(),
-    organizationRole(),
-    jobTitle(),
-    birthday(),
-    photoType(Avatar::empty),
-    photoPath(),
-    receivingTime(creationTime),
-    emails(),
-    phones(),
-    addresses()
-{
-}
-
-QString Shared::VCard::getAvatarPath() const
-{
-    return photoPath;
-}
-
-Shared::Avatar Shared::VCard::getAvatarType() const
-{
-    return photoType;
-}
-
-QDate Shared::VCard::getBirthday() const
-{
-    return birthday;
-}
-
-QString Shared::VCard::getDescription() const
-{
-    return description;
-}
-
-QString Shared::VCard::getFirstName() const
-{
-    return firstName;
-}
-
-QString Shared::VCard::getLastName() const
-{
-    return lastName;
-}
-
-QString Shared::VCard::getMiddleName() const
-{
-    return middleName;
-}
-
-QString Shared::VCard::getNickName() const
-{
-    return nickName;
-}
-
-void Shared::VCard::setAvatarPath(const QString& path)
-{
-    if (path != photoPath) {
-        photoPath = path;
-    }
-}
-
-void Shared::VCard::setAvatarType(Shared::Avatar type)
-{
-    if (photoType != type) {
-        photoType = type;
-    }
-}
-
-void Shared::VCard::setBirthday(const QDate& date)
-{
-    if (date.isValid() && birthday != date) {
-        birthday = date;
-    }
-}
-
-void Shared::VCard::setDescription(const QString& descr)
-{
-    if (description != descr) {
-        description = descr;
-    }
-}
-
-void Shared::VCard::setFirstName(const QString& first)
-{
-    if (firstName != first) {
-        firstName = first;
-    }
-}
-
-void Shared::VCard::setLastName(const QString& last)
-{
-    if (lastName != last) {
-        lastName = last;
-    }
-}
-
-void Shared::VCard::setMiddleName(const QString& middle)
-{
-    if (middleName != middle) {
-        middleName = middle;
-    }
-}
-
-void Shared::VCard::setNickName(const QString& nick)
-{
-    if (nickName != nick) {
-        nickName = nick;
-    }
-}
-
-QString Shared::VCard::getFullName() const
-{
-    return fullName;
-}
-
-QString Shared::VCard::getUrl() const
-{
-    return url;
-}
-
-void Shared::VCard::setFullName(const QString& name)
-{
-    if (fullName != name) {
-        fullName = name;
-    }
-}
-
-void Shared::VCard::setUrl(const QString& u)
-{
-    if (url != u) {
-        url = u;
-    }
-}
-
-QString Shared::VCard::getOrgName() const
-{
-    return organizationName;
-}
-
-QString Shared::VCard::getOrgRole() const
-{
-    return organizationRole;
-}
-
-QString Shared::VCard::getOrgTitle() const
-{
-    return jobTitle;
-}
-
-QString Shared::VCard::getOrgUnit() const
-{
-    return organizationUnit;
-}
-
-void Shared::VCard::setOrgName(const QString& name)
-{
-    if (organizationName != name) {
-        organizationName = name;
-    }
-}
-
-void Shared::VCard::setOrgRole(const QString& role)
-{
-    if (organizationRole != role) {
-        organizationRole = role;
-    }
-}
-
-void Shared::VCard::setOrgTitle(const QString& title)
-{
-    if (jobTitle != title) {
-        jobTitle = title;
-    }
-}
-
-void Shared::VCard::setOrgUnit(const QString& unit)
-{
-    if (organizationUnit != unit) {
-        organizationUnit = unit;
-    }
-}
-
-QDateTime Shared::VCard::getReceivingTime() const
-{
-    return receivingTime;
-}
-
-std::deque<Shared::VCard::Email> & Shared::VCard::getEmails()
-{
-    return emails;
-}
-
-std::deque<Shared::VCard::Address> & Shared::VCard::getAddresses()
-{
-    return addresses;
-}
-
-std::deque<Shared::VCard::Phone> & Shared::VCard::getPhones()
-{
-    return phones;
-}
-
-const std::deque<Shared::VCard::Email> & Shared::VCard::getEmails() const
-{
-    return emails;
-}
-
-const std::deque<Shared::VCard::Address> & Shared::VCard::getAddresses() const
-{
-    return addresses;
-}
-
-const std::deque<Shared::VCard::Phone> & Shared::VCard::getPhones() const
-{
-    return phones;
-}
-
-const std::deque<QString>Shared::VCard::Contact::roleNames = {"Not specified", "Personal", "Business"};
-const std::deque<QString>Shared::VCard::Phone::typeNames = {"Fax", "Pager", "Voice", "Cell", "Video", "Modem", "Other"};
-
-QIcon Shared::availabilityIcon(Shared::Availability av, bool big)
-{
-    const std::deque<QString>& fallback = QApplication::palette().window().color().lightnessF() > 0.5 ? 
-    big ? 
-    Shared::fallbackAvailabilityThemeIconsDarkBig:
-    Shared::fallbackAvailabilityThemeIconsDarkSmall:
-    big ? 
-    Shared::fallbackAvailabilityThemeIconsLightBig:
-    Shared::fallbackAvailabilityThemeIconsLightSmall;
-    
-    return QIcon::fromTheme(availabilityThemeIcons[av], QIcon(fallback[av]));
-}
-
-QIcon Shared::subscriptionStateIcon(Shared::SubscriptionState ss, bool big)
-{
-    const std::deque<QString>& fallback = QApplication::palette().window().color().lightnessF() > 0.5 ? 
-    big ? 
-    Shared::fallbackSubscriptionStateThemeIconsDarkBig:
-    Shared::fallbackSubscriptionStateThemeIconsDarkSmall:
-    big ? 
-    Shared::fallbackSubscriptionStateThemeIconsLightBig:
-    Shared::fallbackSubscriptionStateThemeIconsLightSmall;
-    
-    return QIcon::fromTheme(subscriptionStateThemeIcons[ss], QIcon(fallback[ss]));
-}
-
-QIcon Shared::connectionStateIcon(Shared::ConnectionState cs, bool big)
-{
-    const std::deque<QString>& fallback = QApplication::palette().window().color().lightnessF() > 0.5 ? 
-    big ? 
-    Shared::fallbackConnectionStateThemeIconsDarkBig:
-    Shared::fallbackConnectionStateThemeIconsDarkSmall:
-    big ? 
-    Shared::fallbackConnectionStateThemeIconsLightBig:
-    Shared::fallbackConnectionStateThemeIconsLightSmall;
-    
-    return QIcon::fromTheme(connectionStateThemeIcons[cs], QIcon(fallback[cs]));
-}
-
-static const QString ds = ":images/fallback/dark/small/";
-static const QString db = ":images/fallback/dark/big/";
-static const QString ls = ":images/fallback/light/small/";
-static const QString lb = ":images/fallback/light/big/";
-
-QIcon Shared::icon(const QString& name, bool big)
-{
-    std::map<QString, std::pair<QString, QString>>::const_iterator itr = icons.find(name);
-    if (itr != icons.end()) {
-        const QString& prefix = QApplication::palette().window().color().lightnessF() > 0.5 ? 
-            big ? db : ds:
-            big ? lb : ls;
-        return QIcon::fromTheme(itr->second.first, QIcon(prefix + itr->second.second + ".svg"));
-    } else {
-        qDebug() << "Icon" << name << "not found";
-        return QIcon::fromTheme(name);
-    }
-}
-
-
-QString Shared::iconPath(const QString& name, bool big)
-{
-    QString result = "";
-    std::map<QString, std::pair<QString, QString>>::const_iterator itr = icons.find(name);
-    if (itr != icons.end()) {
-        const QString& prefix = QApplication::palette().window().color().lightnessF() > 0.5 ? 
-            big ? db : ds:
-            big ? lb : ls;
-        result = prefix + itr->second.second + ".svg";
-    }
-    
-    return result;
-}
diff --git a/global.h b/global.h
deleted file mode 100644
index aa5911a..0000000
--- a/global.h
+++ /dev/null
@@ -1,512 +0,0 @@
-/*
- * Squawk messenger. 
- * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
- *
- * 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/>.
- */
-
-#ifndef GLOBAL_H
-#define GLOBAL_H
-
-#include <QString>
-#include <QMap>
-#include <QCoreApplication>
-#include <deque>
-#include <QDateTime>
-#include <QDataStream>
-#include <QColor>
-
-namespace Shared {
-    
-enum ConnectionState {
-    disconnected,
-    connecting,
-    connected,
-    error
-};
-
-enum Availability {
-    online,
-    away,
-    extendedAway,
-    busy,
-    chatty,
-    invisible,
-    offline
-};
-
-enum SubscriptionState {
-    none,
-    from,
-    to,
-    both,
-    unknown
-};
-
-enum class Affiliation {
-    unspecified, 
-    outcast, 
-    nobody, 
-    member, 
-    admin, 
-    owner 
-};
-
-enum class Role { 
-    unspecified, 
-    nobody, 
-    visitor, 
-    participant, 
-    moderator 
-};
-
-enum class Avatar {
-    empty,
-    autocreated,
-    valid
-};
-
-static const Availability availabilityHighest = offline;
-static const Availability availabilityLowest = online;
-
-static const SubscriptionState subscriptionStateHighest = unknown;
-static const SubscriptionState subscriptionStateLowest = none;
-
-static const Affiliation affiliationHighest = Affiliation::owner;
-static const Affiliation affiliationLowest = Affiliation::unspecified;
-
-static const Role roleHighest = Role::moderator;
-static const Role roleLowest = Role::unspecified;
-
-static const std::deque<QString> connectionStateNames = {"Disconnected", "Connecting", "Connected", "Error"};
-static const std::deque<QString> connectionStateThemeIcons = {"state-offline", "state-sync", "state-ok", "state-error"};
-
-static const std::deque<QString> availabilityThemeIcons = {
-    "user-online",
-    "user-away",
-    "user-away-extended",
-    "user-busy",
-    "chatty",
-    "user-invisible",
-    "user-offline"
-};
-static const std::deque<QString> availabilityNames = {"Online", "Away", "Absent", "Busy", "Chatty", "Invisible", "Offline"};
-
-static const std::deque<QString> subscriptionStateThemeIcons = {"edit-none", "arrow-down-double", "arrow-up-double", "dialog-ok", "question"};
-static const std::deque<QString> subscriptionStateNames = {"None", "From", "To", "Both", "Unknown"};
-
-static const std::deque<QString> affiliationNames = {"Unspecified", "Outcast", "Nobody", "Member", "Admin", "Owner"};
-static const std::deque<QString> roleNames = {"Unspecified", "Nobody", "Visitor", "Participant", "Moderator"};
-
-static const std::deque<QString> messageStateNames = {"Pending", "Sent", "Delivered", "Error"};
-static const std::deque<QString> messageStateThemeIcons = {"state-offline", "state-sync", "state-ok", "state-error"};
-
-QString generateUUID();
-
-static const std::vector<QColor> colorPalette = {
-    QColor(244, 27, 63),
-    QColor(21, 104, 156),
-    QColor(38, 156, 98),
-    QColor(247, 103, 101),
-    QColor(121, 37, 117),
-    QColor(242, 202, 33),
-    QColor(168, 22, 63),
-    QColor(35, 100, 52),
-    QColor(52, 161, 152),
-    QColor(239, 53, 111),
-    QColor(237, 234, 36),
-    QColor(153, 148, 194),
-    QColor(211, 102, 151),
-    QColor(194, 63, 118),
-    QColor(249, 149, 51),
-    QColor(244, 206, 109),
-    QColor(121, 105, 153),
-    QColor(244, 199, 30),
-    QColor(28, 112, 28),
-    QColor(172, 18, 20),
-    QColor(25, 66, 110),
-    QColor(25, 149, 104),
-    QColor(214, 148, 0),
-    QColor(203, 47, 57),
-    QColor(4, 54, 84),
-    QColor(116, 161, 97),
-    QColor(50, 68, 52),
-    QColor(237, 179, 20),
-    QColor(69, 114, 147),
-    QColor(242, 212, 31),
-    QColor(248, 19, 20),
-    QColor(84, 102, 84),
-    QColor(25, 53, 122),
-    QColor(91, 91, 109),
-    QColor(17, 17, 80),
-    QColor(54, 54, 94)
-};
-
-class Message {
-public:
-    enum Type {
-        error,
-        normal,
-        chat,
-        groupChat,
-        headline
-    };
-    
-    enum class State {
-        pending,
-        sent,
-        delivered,
-        error
-    };
-    
-    Message(Type p_type);
-    Message();
-
-    void setFrom(const QString& from);
-    void setFromResource(const QString& from);
-    void setFromJid(const QString& from);
-    void setTo(const QString& to);
-    void setToResource(const QString& to);
-    void setToJid(const QString& to);
-    void setTime(const QDateTime& p_time);
-    void setId(const QString& p_id);
-    void setBody(const QString& p_body);
-    void setThread(const QString& p_body);
-    void setOutgoing(bool og);
-    void setForwarded(bool fwd);
-    void setType(Type t);
-    void setCurrentTime();
-    void setOutOfBandUrl(const QString& url);
-    void setState(State p_state);
-    void setEdited(bool p_edited);
-    void setErrorText(const QString& err);
-    bool change(const QMap<QString, QVariant>& data);
-    
-    QString getFrom() const;
-    QString getFromJid() const;
-    QString getFromResource() const;
-    QString getTo() const;
-    QString getToJid() const;
-    QString getToResource() const;
-    QDateTime getTime() const;
-    QString getId() const;
-    QString getBody() const;
-    QString getThread() const;
-    bool getOutgoing() const;
-    bool getForwarded() const;
-    Type getType() const;
-    bool hasOutOfBandUrl() const;
-    bool storable() const;
-    QString getOutOfBandUrl() const;
-    State getState() const;
-    bool getEdited() const;
-    QString getErrorText() const;
-    
-    QString getPenPalJid() const;
-    QString getPenPalResource() const;
-    void generateRandomId();
-    bool serverStored() const;
-    QDateTime getLastModified() const;
-    QString getOriginalBody() const;
-    
-    void serialize(QDataStream& data) const;
-    void deserialize(QDataStream& data);
-    
-private:
-    QString jFrom;
-    QString rFrom;
-    QString jTo;
-    QString rTo;
-    QString id;
-    QString body;
-    QDateTime time;
-    QString thread;
-    Type type;
-    bool outgoing;
-    bool forwarded;
-    QString oob;
-    State state;
-    bool edited;
-    QString errorText;
-    QString originalMessage;
-    QDateTime lastModified;
-};
-
-class VCard {
-    class Contact {
-    public:
-        enum Role {
-            none,
-            home,
-            work
-        };
-        static const std::deque<QString> roleNames;
-        
-        Contact(Role p_role = none, bool p_prefered = false);
-        
-        Role role;
-        bool prefered;
-    };
-public:
-    class Email : public Contact {
-    public:
-        Email(const QString& address, Role p_role = none, bool p_prefered = false);
-        
-        QString address;
-    };
-    class Phone : public Contact {
-    public:
-        enum Type {
-            fax,
-            pager,
-            voice,
-            cell,
-            video,
-            modem,
-            other
-        };
-        static const std::deque<QString> typeNames;
-        Phone(const QString& number, Type p_type = voice, Role p_role = none, bool p_prefered = false);
-        
-        QString number;
-        Type type;
-    };
-    class Address : public Contact {
-    public:
-        Address(
-            const QString& zCode = "", 
-            const QString& cntry = "", 
-            const QString& rgn = "", 
-            const QString& lclty = "", 
-            const QString& strt = "", 
-            const QString& ext = "", 
-            Role p_role = none, 
-            bool p_prefered = false
-        );
-        
-        QString zipCode;
-        QString country;
-        QString region;
-        QString locality;
-        QString street;
-        QString external;
-    };
-    VCard();
-    VCard(const QDateTime& creationTime);
-    
-    QString getFullName() const;
-    void setFullName(const QString& name);
-    QString getFirstName() const;
-    void setFirstName(const QString& first);
-    QString getMiddleName() const;
-    void setMiddleName(const QString& middle);
-    QString getLastName() const;
-    void setLastName(const QString& last);
-    QString getNickName() const;
-    void setNickName(const QString& nick);
-    QString getDescription() const;
-    void setDescription(const QString& descr);
-    QString getUrl() const;
-    void setUrl(const QString& u);
-    QDate getBirthday() const;
-    void setBirthday(const QDate& date);
-    Avatar getAvatarType() const;
-    void setAvatarType(Avatar type);
-    QString getAvatarPath() const;
-    void setAvatarPath(const QString& path);
-    QString getOrgName() const;
-    void setOrgName(const QString& name);
-    QString getOrgUnit() const;
-    void setOrgUnit(const QString& unit);
-    QString getOrgRole() const;
-    void setOrgRole(const QString& role);
-    QString getOrgTitle() const;
-    void setOrgTitle(const QString& title);
-    QDateTime getReceivingTime() const;
-    std::deque<Email>& getEmails();
-    const std::deque<Email>& getEmails() const;
-    std::deque<Phone>& getPhones();
-    const std::deque<Phone>& getPhones() const;
-    std::deque<Address>& getAddresses();
-    const std::deque<Address>& getAddresses() const;
-    
-private:
-    QString fullName;
-    QString firstName;
-    QString middleName;
-    QString lastName;
-    QString nickName;
-    QString description;
-    QString url;
-    QString organizationName;
-    QString organizationUnit;
-    QString organizationRole;
-    QString jobTitle;
-    QDate birthday;
-    Avatar photoType;
-    QString photoPath;
-    QDateTime receivingTime;
-    std::deque<Email> emails;
-    std::deque<Phone> phones;
-    std::deque<Address> addresses;
-};
-
-static const std::deque<QString> fallbackAvailabilityThemeIconsLightBig = {
-    ":images/fallback/light/big/online.svg",
-    ":images/fallback/light/big/away.svg",
-    ":images/fallback/light/big/absent.svg",
-    ":images/fallback/light/big/busy.svg",
-    ":images/fallback/light/big/chatty.svg",
-    ":images/fallback/light/big/invisible.svg",
-    ":images/fallback/light/big/offline.svg"
-};
-
-static const std::deque<QString> fallbackSubscriptionStateThemeIconsLightBig = {
-    ":images/fallback/light/big/edit-none.svg",
-    ":images/fallback/light/big/arrow-down-double.svg",
-    ":images/fallback/light/big/arrow-up-double.svg",
-    ":images/fallback/light/big/dialog-ok.svg",
-    ":images/fallback/light/big/question.svg"
-};
-
-static const std::deque<QString> fallbackConnectionStateThemeIconsLightBig = {
-    ":images/fallback/light/big/state-offline.svg",
-    ":images/fallback/light/big/state-sync.svg",
-    ":images/fallback/light/big/state-ok.svg",
-    ":images/fallback/light/big/state-error.svg"
-};
-
-static const std::deque<QString> fallbackAvailabilityThemeIconsLightSmall = {
-    ":images/fallback/light/small/online.svg",
-    ":images/fallback/light/small/away.svg",
-    ":images/fallback/light/small/absent.svg",
-    ":images/fallback/light/small/busy.svg",
-    ":images/fallback/light/small/chatty.svg",
-    ":images/fallback/light/small/invisible.svg",
-    ":images/fallback/light/small/offline.svg"
-};
-
-static const std::deque<QString> fallbackSubscriptionStateThemeIconsLightSmall = {
-    ":images/fallback/light/small/edit-none.svg",
-    ":images/fallback/light/small/arrow-down-double.svg",
-    ":images/fallback/light/small/arrow-up-double.svg",
-    ":images/fallback/light/small/dialog-ok.svg",
-    ":images/fallback/light/small/question.svg"
-};
-
-static const std::deque<QString> fallbackConnectionStateThemeIconsLightSmall = {
-    ":images/fallback/light/small/state-offline.svg",
-    ":images/fallback/light/small/state-sync.svg",
-    ":images/fallback/light/small/state-ok.svg",
-    ":images/fallback/light/small/state-error.svg"
-};
-
-static const std::deque<QString> fallbackAvailabilityThemeIconsDarkBig = {
-    ":images/fallback/dark/big/online.svg",
-    ":images/fallback/dark/big/away.svg",
-    ":images/fallback/dark/big/absent.svg",
-    ":images/fallback/dark/big/busy.svg",
-    ":images/fallback/dark/big/chatty.svg",
-    ":images/fallback/dark/big/invisible.svg",
-    ":images/fallback/dark/big/offline.svg"
-};
-
-static const std::deque<QString> fallbackSubscriptionStateThemeIconsDarkBig = {
-    ":images/fallback/dark/big/edit-none.svg",
-    ":images/fallback/dark/big/arrow-down-double.svg",
-    ":images/fallback/dark/big/arrow-up-double.svg",
-    ":images/fallback/dark/big/dialog-ok.svg",
-    ":images/fallback/dark/big/question.svg"
-};
-
-static const std::deque<QString> fallbackConnectionStateThemeIconsDarkBig = {
-    ":images/fallback/dark/big/state-offline.svg",
-    ":images/fallback/dark/big/state-sync.svg",
-    ":images/fallback/dark/big/state-ok.svg",
-    ":images/fallback/dark/big/state-error.svg"
-};
-
-static const std::deque<QString> fallbackAvailabilityThemeIconsDarkSmall = {
-    ":images/fallback/dark/small/online.svg",
-    ":images/fallback/dark/small/away.svg",
-    ":images/fallback/dark/small/absent.svg",
-    ":images/fallback/dark/small/busy.svg",
-    ":images/fallback/dark/small/chatty.svg",
-    ":images/fallback/dark/small/invisible.svg",
-    ":images/fallback/dark/small/offline.svg"
-};
-
-static const std::deque<QString> fallbackSubscriptionStateThemeIconsDarkSmall = {
-    ":images/fallback/dark/small/edit-none.svg",
-    ":images/fallback/dark/small/arrow-down-double.svg",
-    ":images/fallback/dark/small/arrow-up-double.svg",
-    ":images/fallback/dark/small/dialog-ok.svg",
-    ":images/fallback/dark/small/question.svg"
-};
-
-static const std::deque<QString> fallbackConnectionStateThemeIconsDarkSmall = {
-    ":images/fallback/dark/small/state-offline.svg",
-    ":images/fallback/dark/small/state-sync.svg",
-    ":images/fallback/dark/small/state-ok.svg",
-    ":images/fallback/dark/small/state-error.svg"
-};
-
-QIcon availabilityIcon(Availability av, bool big = false);
-QIcon subscriptionStateIcon(SubscriptionState ss, bool big = false);
-QIcon connectionStateIcon(ConnectionState cs, bool big = false);
-QIcon icon(const QString& name, bool big = false);
-QString iconPath(const QString& name, bool big = false);
-
-static const std::map<QString, std::pair<QString, QString>> icons = {
-    {"user-online", {"user-online", "online"}},
-    {"user-away", {"user-away", "away"}},
-    {"user-away-extended", {"user-away-extended", "absent"}},
-    {"user-busy", {"user-busy", "busy"}},
-    {"user-chatty", {"chatty", "chatty"}},
-    {"user-invisible", {"user-invisible", "invisible"}},
-    {"user-offline", {"offline", "offline"}},
-    {"edit-none", {"edit-none", "edit-none"}}, 
-    {"arrow-down-double", {"arrow-down-double", "arrow-down-double"}}, 
-    {"arrow-up-double", {"arrow-up-double", "arrow-up-double"}}, 
-    {"dialog-ok", {"dialog-ok", "dialog-ok"}}, 
-    {"question", {"question", "question"}},
-    {"state-offline", {"state-offline", "state-offline"}}, 
-    {"state-sync", {"state-sync", "state-sync"}}, 
-    {"state-ok", {"state-ok", "state-ok"}}, 
-    {"state-error", {"state-error", "state-error"}},
-    
-    {"edit-copy", {"edit-copy", "copy"}},
-    {"edit-delete", {"edit-delete", "edit-delete"}},
-    {"edit-rename", {"edit-rename", "edit-rename"}},
-    {"mail-message", {"mail-message", "mail-message"}},
-    {"mail-attachment", {"mail-attachment", "mail-attachment"}},
-    {"network-connect", {"network-connect", "network-connect"}},
-    {"network-disconnect", {"network-disconnect", "network-disconnect"}},
-    {"news-subscribe", {"news-subscribe", "news-subscribe"}},
-    {"news-unsubscribe", {"news-unsubscribe", "news-unsubscribe"}},
-    {"view-refresh", {"view-refresh", "view-refresh"}},
-    {"send", {"document-send", "send"}},
-    {"clean", {"edit-clear-all", "clean"}},
-    {"user", {"user", "user"}},
-    {"user-properties", {"user-properties", "user-properties"}},
-    {"group", {"group", "group"}},
-    {"group-new", {"resurce-group-new", "group-new"}},
-    {"favorite", {"favorite", "favorite"}},
-    {"unfavorite", {"draw-star", "unfavorite"}},
-    {"list-add", {"list-add", "add"}},
-};
-
-};
-
-#endif // GLOBAL_H
diff --git a/main.cpp b/main.cpp
index c546b57..25de512 100644
--- a/main.cpp
+++ b/main.cpp
@@ -19,6 +19,7 @@
 #include "ui/squawk.h"
 #include "core/squawk.h"
 #include "signalcatcher.h"
+#include "shared/global.h"
 #include <QtWidgets/QApplication>
 #include <QtCore/QThread>
 #include <QtCore/QObject>
@@ -33,6 +34,8 @@ int main(int argc, char *argv[])
     qRegisterMetaType<Shared::VCard>("Shared::VCard");
     qRegisterMetaType<std::list<Shared::Message>>("std::list<Shared::Message>");
     qRegisterMetaType<QSet<QString>>("QSet<QString>");
+    qRegisterMetaType<Shared::ConnectionState>("Shared::ConnectionState");
+    qRegisterMetaType<Shared::Availability>("Shared::Availability");
     
     QApplication app(argc, argv);
     SignalCatcher sc(&app);
@@ -72,6 +75,8 @@ int main(int argc, char *argv[])
     icon.addFile(":images/logo.svg", QSize(512, 512));
     QApplication::setWindowIcon(icon);
     
+    new Shared::Global();        //translates enums
+    
     Squawk w;
     w.show();
     
diff --git a/shared.h b/shared.h
new file mode 100644
index 0000000..83bcd76
--- /dev/null
+++ b/shared.h
@@ -0,0 +1,29 @@
+/*
+ * Squawk messenger. 
+ * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+ *
+ * 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/>.
+ */
+
+#ifndef SHARED_H
+#define SHARED_H
+
+#include "shared/enums.h"
+#include "shared/utils.h"
+#include "shared/icons.h"
+#include "shared/message.h"
+#include "shared/vcard.h"
+#include "shared/global.h"
+
+#endif // SHARED_H
diff --git a/shared/enums.h b/shared/enums.h
new file mode 100644
index 0000000..158695c
--- /dev/null
+++ b/shared/enums.h
@@ -0,0 +1,114 @@
+/*
+ * Squawk messenger. 
+ * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+ *
+ * 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/>.
+ */
+
+#ifndef SHARED_ENUMS_H
+#define SHARED_ENUMS_H
+
+#include <deque>
+
+#include <QString>
+#include <QObject>
+
+namespace Shared {
+Q_NAMESPACE
+    
+enum class ConnectionState {
+    disconnected,
+    connecting,
+    connected,
+    error
+};
+Q_ENUM_NS(ConnectionState)
+static const std::deque<QString> connectionStateThemeIcons = {"state-offline", "state-sync", "state-ok", "state-error"};
+static const ConnectionState connectionStateHighest = ConnectionState::error;
+static const ConnectionState connectionStateLowest = ConnectionState::disconnected;
+
+enum class Availability {
+    online,
+    away,
+    extendedAway,
+    busy,
+    chatty,
+    invisible,
+    offline
+};
+Q_ENUM_NS(Availability)
+static const Availability availabilityHighest = Availability::offline;
+static const Availability availabilityLowest = Availability::online;
+static const std::deque<QString> availabilityThemeIcons = {
+    "user-online",
+    "user-away",
+    "user-away-extended",
+    "user-busy",
+    "chatty",
+    "user-invisible",
+    "user-offline"
+};
+static const std::deque<QString> availabilityNames = {"Online", "Away", "Absent", "Busy", "Chatty", "Invisible", "Offline"};
+
+enum class SubscriptionState {
+    none,
+    from,
+    to,
+    both,
+    unknown
+};
+Q_ENUM_NS(SubscriptionState)
+static const SubscriptionState subscriptionStateHighest = SubscriptionState::unknown;
+static const SubscriptionState subscriptionStateLowest = SubscriptionState::none;
+static const std::deque<QString> subscriptionStateThemeIcons = {"edit-none", "arrow-down-double", "arrow-up-double", "dialog-ok", "question"};
+static const std::deque<QString> subscriptionStateNames = {"None", "From", "To", "Both", "Unknown"};
+
+enum class Affiliation {
+    unspecified, 
+    outcast, 
+    nobody, 
+    member, 
+    admin, 
+    owner 
+};
+Q_ENUM_NS(Affiliation)
+static const Affiliation affiliationHighest = Affiliation::owner;
+static const Affiliation affiliationLowest = Affiliation::unspecified;
+static const std::deque<QString> affiliationNames = {"Unspecified", "Outcast", "Nobody", "Member", "Admin", "Owner"};
+
+enum class Role { 
+    unspecified, 
+    nobody, 
+    visitor, 
+    participant, 
+    moderator 
+};
+Q_ENUM_NS(Role)
+static const Role roleHighest = Role::moderator;
+static const Role roleLowest = Role::unspecified;
+static const std::deque<QString> roleNames = {"Unspecified", "Nobody", "Visitor", "Participant", "Moderator"};
+
+enum class Avatar {
+    empty,
+    autocreated,
+    valid
+};
+Q_ENUM_NS(Avatar)
+
+
+static const std::deque<QString> messageStateNames = {"Pending", "Sent", "Delivered", "Error"};
+static const std::deque<QString> messageStateThemeIcons = {"state-offline", "state-sync", "state-ok", "state-error"};
+
+}
+#endif // SHARED_ENUMS_H
diff --git a/shared/global.cpp b/shared/global.cpp
new file mode 100644
index 0000000..4d3399a
--- /dev/null
+++ b/shared/global.cpp
@@ -0,0 +1,170 @@
+/*
+ * Squawk messenger. 
+ * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+ * 
+ * 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 "global.h"
+
+#include "enums.h"
+
+Shared::Global* Shared::Global::instance = 0;
+
+Shared::Global::Global():
+    availability({
+        tr("Online"), 
+        tr("Away"), 
+        tr("Absent"), 
+        tr("Busy"), 
+        tr("Chatty"), 
+        tr("Invisible"), 
+        tr("Offline")
+    }),
+    connectionState({
+        tr("Disconnected"), 
+        tr("Connecting"), 
+        tr("Connected"), 
+        tr("Error")
+    }),
+    subscriptionState({
+        tr("None"), 
+        tr("From"), 
+        tr("To"), 
+        tr("Both"), 
+        tr("Unknown")
+    }),
+    affiliation({
+        tr("Unspecified"), 
+        tr("Outcast"), 
+        tr("Nobody"), 
+        tr("Member"), 
+        tr("Admin"), 
+        tr("Owner")
+    }),
+    role({
+        tr("Unspecified"), 
+        tr("Nobody"), 
+        tr("Visitor"),
+        tr("Participant"), 
+        tr("Moderator")
+    }),
+    messageState({
+        tr("Pending"), 
+        tr("Sent"), 
+        tr("Delivered"), 
+        tr("Error")
+    })
+{
+    if (instance != 0) {
+        throw 551;
+    }
+    
+    instance = this;
+}
+
+Shared::Global * Shared::Global::getInstance()
+{
+    return instance;
+}
+
+QString Shared::Global::getName(Message::State rl)
+{
+    return instance->messageState[int(rl)];
+}
+
+QString Shared::Global::getName(Shared::Affiliation af)
+{
+    return instance->affiliation[int(af)];
+}
+
+QString Shared::Global::getName(Shared::Availability av)
+{
+    return instance->availability[int(av)];
+}
+
+QString Shared::Global::getName(Shared::ConnectionState cs)
+{
+    return instance->connectionState[int(cs)];
+}
+
+QString Shared::Global::getName(Shared::Role rl)
+{
+    return instance->role[int(rl)];
+}
+
+QString Shared::Global::getName(Shared::SubscriptionState ss)
+{
+    return instance->subscriptionState[int(ss)];
+}
+
+template<> 
+Shared::Availability Shared::Global::fromInt(int src)
+{
+    if (src < static_cast<int>(Shared::availabilityLowest) && src > static_cast<int>(Shared::availabilityHighest)) {
+        qDebug("An attempt to set invalid availability to Squawk core, skipping");
+    }
+    
+    return static_cast<Shared::Availability>(src);
+}
+
+template<> 
+Shared::Availability Shared::Global::fromInt(unsigned int src)
+{
+    if (src < static_cast<int>(Shared::availabilityLowest) && src > static_cast<int>(Shared::availabilityHighest)) {
+        qDebug("An attempt to set invalid availability to Squawk core, skipping");
+    }
+    
+    return static_cast<Shared::Availability>(src);
+}
+
+template<> 
+Shared::ConnectionState Shared::Global::fromInt(int src)
+{
+    if (src < static_cast<int>(Shared::connectionStateLowest) && src > static_cast<int>(Shared::connectionStateHighest)) {
+        qDebug("An attempt to set invalid availability to Squawk core, skipping");
+    }
+    
+    return static_cast<Shared::ConnectionState>(src);
+}
+
+template<> 
+Shared::ConnectionState Shared::Global::fromInt(unsigned int src)
+{
+    if (src < static_cast<int>(Shared::connectionStateLowest) && src > static_cast<int>(Shared::connectionStateHighest)) {
+        qDebug("An attempt to set invalid availability to Squawk core, skipping");
+    }
+    
+    return static_cast<Shared::ConnectionState>(src);
+}
+
+template<> 
+Shared::SubscriptionState Shared::Global::fromInt(int src)
+{
+    if (src < static_cast<int>(Shared::subscriptionStateLowest) && src > static_cast<int>(Shared::subscriptionStateHighest)) {
+        qDebug("An attempt to set invalid availability to Squawk core, skipping");
+    }
+    
+    return static_cast<Shared::SubscriptionState>(src);
+}
+
+template<> 
+Shared::SubscriptionState Shared::Global::fromInt(unsigned int src)
+{
+    if (src < static_cast<int>(Shared::subscriptionStateLowest) && src > static_cast<int>(Shared::subscriptionStateHighest)) {
+        qDebug("An attempt to set invalid availability to Squawk core, skipping");
+    }
+    
+    return static_cast<Shared::SubscriptionState>(src);
+}
diff --git a/shared/global.h b/shared/global.h
new file mode 100644
index 0000000..ef61611
--- /dev/null
+++ b/shared/global.h
@@ -0,0 +1,64 @@
+/*
+ * Squawk messenger. 
+ * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+ * 
+ * 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/>.
+ */
+
+#ifndef SHARED_GLOBAL_H
+#define SHARED_GLOBAL_H
+
+#include "enums.h"
+#include "message.h"
+
+#include <map>
+
+#include <QCoreApplication>
+#include <QDebug>
+
+namespace Shared {
+    
+    class Global {
+        Q_DECLARE_TR_FUNCTIONS(Global)
+        
+    public:
+        Global();
+        
+        static Global* getInstance();
+        static QString getName(Availability av);
+        static QString getName(ConnectionState cs);
+        static QString getName(SubscriptionState ss);
+        static QString getName(Affiliation af);
+        static QString getName(Role rl);
+        static QString getName(Message::State rl);
+        
+        const std::deque<QString> availability;
+        const std::deque<QString> connectionState;
+        const std::deque<QString> subscriptionState;
+        const std::deque<QString> affiliation;
+        const std::deque<QString> role;
+        const std::deque<QString> messageState;
+        
+        template<typename T>
+        static T fromInt(int src);
+        
+        template<typename T>
+        static T fromInt(unsigned int src);
+        
+    private:
+        static Global* instance;
+    };
+}
+
+#endif // SHARED_GLOBAL_H
diff --git a/shared/icons.cpp b/shared/icons.cpp
new file mode 100644
index 0000000..f3a4dcc
--- /dev/null
+++ b/shared/icons.cpp
@@ -0,0 +1,96 @@
+/*
+ * Squawk messenger. 
+ * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+ * 
+ * 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 "icons.h"
+
+#include <QApplication>
+#include <QPalette>
+#include <QDebug>
+
+QIcon Shared::availabilityIcon(Shared::Availability av, bool big)
+{
+    const std::deque<QString>& fallback = QApplication::palette().window().color().lightnessF() > 0.5 ? 
+    big ? 
+    Shared::fallbackAvailabilityThemeIconsDarkBig:
+    Shared::fallbackAvailabilityThemeIconsDarkSmall:
+    big ? 
+    Shared::fallbackAvailabilityThemeIconsLightBig:
+    Shared::fallbackAvailabilityThemeIconsLightSmall;
+    
+    return QIcon::fromTheme(availabilityThemeIcons[static_cast<int>(av)], QIcon(fallback[static_cast<int>(av)]));
+}
+
+QIcon Shared::subscriptionStateIcon(Shared::SubscriptionState ss, bool big)
+{
+    const std::deque<QString>& fallback = QApplication::palette().window().color().lightnessF() > 0.5 ? 
+    big ? 
+    Shared::fallbackSubscriptionStateThemeIconsDarkBig:
+    Shared::fallbackSubscriptionStateThemeIconsDarkSmall:
+    big ? 
+    Shared::fallbackSubscriptionStateThemeIconsLightBig:
+    Shared::fallbackSubscriptionStateThemeIconsLightSmall;
+    
+    return QIcon::fromTheme(subscriptionStateThemeIcons[static_cast<int>(ss)], QIcon(fallback[static_cast<int>(ss)]));
+}
+
+QIcon Shared::connectionStateIcon(Shared::ConnectionState cs, bool big)
+{
+    const std::deque<QString>& fallback = QApplication::palette().window().color().lightnessF() > 0.5 ? 
+    big ? 
+    Shared::fallbackConnectionStateThemeIconsDarkBig:
+    Shared::fallbackConnectionStateThemeIconsDarkSmall:
+    big ? 
+    Shared::fallbackConnectionStateThemeIconsLightBig:
+    Shared::fallbackConnectionStateThemeIconsLightSmall;
+    
+    return QIcon::fromTheme(connectionStateThemeIcons[static_cast<int>(cs)], QIcon(fallback[static_cast<int>(cs)]));
+}
+
+static const QString ds = ":images/fallback/dark/small/";
+static const QString db = ":images/fallback/dark/big/";
+static const QString ls = ":images/fallback/light/small/";
+static const QString lb = ":images/fallback/light/big/";
+
+QIcon Shared::icon(const QString& name, bool big)
+{
+    std::map<QString, std::pair<QString, QString>>::const_iterator itr = icons.find(name);
+    if (itr != icons.end()) {
+        const QString& prefix = QApplication::palette().window().color().lightnessF() > 0.5 ? 
+        big ? db : ds:
+        big ? lb : ls;
+        return QIcon::fromTheme(itr->second.first, QIcon(prefix + itr->second.second + ".svg"));
+    } else {
+        qDebug() << "Icon" << name << "not found";
+        return QIcon::fromTheme(name);
+    }
+}
+
+
+QString Shared::iconPath(const QString& name, bool big)
+{
+    QString result = "";
+    std::map<QString, std::pair<QString, QString>>::const_iterator itr = icons.find(name);
+    if (itr != icons.end()) {
+        const QString& prefix = QApplication::palette().window().color().lightnessF() > 0.5 ? 
+        big ? db : ds:
+        big ? lb : ls;
+        result = prefix + itr->second.second + ".svg";
+    }
+    
+    return result;
+}
diff --git a/shared/icons.h b/shared/icons.h
new file mode 100644
index 0000000..48ecc37
--- /dev/null
+++ b/shared/icons.h
@@ -0,0 +1,176 @@
+/*
+ * Squawk messenger. 
+ * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+ * 
+ * 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/>.
+ */
+
+#ifndef SHARED_ICONS_H
+#define SHARED_ICONS_H
+
+#include <QIcon>
+
+#include <map>
+
+#include "enums.h"
+
+namespace Shared {
+
+static const std::deque<QString> fallbackAvailabilityThemeIconsLightBig = {
+    ":images/fallback/light/big/online.svg",
+    ":images/fallback/light/big/away.svg",
+    ":images/fallback/light/big/absent.svg",
+    ":images/fallback/light/big/busy.svg",
+    ":images/fallback/light/big/chatty.svg",
+    ":images/fallback/light/big/invisible.svg",
+    ":images/fallback/light/big/offline.svg"
+};
+
+static const std::deque<QString> fallbackSubscriptionStateThemeIconsLightBig = {
+    ":images/fallback/light/big/edit-none.svg",
+    ":images/fallback/light/big/arrow-down-double.svg",
+    ":images/fallback/light/big/arrow-up-double.svg",
+    ":images/fallback/light/big/dialog-ok.svg",
+    ":images/fallback/light/big/question.svg"
+};
+
+static const std::deque<QString> fallbackConnectionStateThemeIconsLightBig = {
+    ":images/fallback/light/big/state-offline.svg",
+    ":images/fallback/light/big/state-sync.svg",
+    ":images/fallback/light/big/state-ok.svg",
+    ":images/fallback/light/big/state-error.svg"
+};
+
+static const std::deque<QString> fallbackAvailabilityThemeIconsLightSmall = {
+    ":images/fallback/light/small/online.svg",
+    ":images/fallback/light/small/away.svg",
+    ":images/fallback/light/small/absent.svg",
+    ":images/fallback/light/small/busy.svg",
+    ":images/fallback/light/small/chatty.svg",
+    ":images/fallback/light/small/invisible.svg",
+    ":images/fallback/light/small/offline.svg"
+};
+
+static const std::deque<QString> fallbackSubscriptionStateThemeIconsLightSmall = {
+    ":images/fallback/light/small/edit-none.svg",
+    ":images/fallback/light/small/arrow-down-double.svg",
+    ":images/fallback/light/small/arrow-up-double.svg",
+    ":images/fallback/light/small/dialog-ok.svg",
+    ":images/fallback/light/small/question.svg"
+};
+
+static const std::deque<QString> fallbackConnectionStateThemeIconsLightSmall = {
+    ":images/fallback/light/small/state-offline.svg",
+    ":images/fallback/light/small/state-sync.svg",
+    ":images/fallback/light/small/state-ok.svg",
+    ":images/fallback/light/small/state-error.svg"
+};
+
+static const std::deque<QString> fallbackAvailabilityThemeIconsDarkBig = {
+    ":images/fallback/dark/big/online.svg",
+    ":images/fallback/dark/big/away.svg",
+    ":images/fallback/dark/big/absent.svg",
+    ":images/fallback/dark/big/busy.svg",
+    ":images/fallback/dark/big/chatty.svg",
+    ":images/fallback/dark/big/invisible.svg",
+    ":images/fallback/dark/big/offline.svg"
+};
+
+static const std::deque<QString> fallbackSubscriptionStateThemeIconsDarkBig = {
+    ":images/fallback/dark/big/edit-none.svg",
+    ":images/fallback/dark/big/arrow-down-double.svg",
+    ":images/fallback/dark/big/arrow-up-double.svg",
+    ":images/fallback/dark/big/dialog-ok.svg",
+    ":images/fallback/dark/big/question.svg"
+};
+
+static const std::deque<QString> fallbackConnectionStateThemeIconsDarkBig = {
+    ":images/fallback/dark/big/state-offline.svg",
+    ":images/fallback/dark/big/state-sync.svg",
+    ":images/fallback/dark/big/state-ok.svg",
+    ":images/fallback/dark/big/state-error.svg"
+};
+
+static const std::deque<QString> fallbackAvailabilityThemeIconsDarkSmall = {
+    ":images/fallback/dark/small/online.svg",
+    ":images/fallback/dark/small/away.svg",
+    ":images/fallback/dark/small/absent.svg",
+    ":images/fallback/dark/small/busy.svg",
+    ":images/fallback/dark/small/chatty.svg",
+    ":images/fallback/dark/small/invisible.svg",
+    ":images/fallback/dark/small/offline.svg"
+};
+
+static const std::deque<QString> fallbackSubscriptionStateThemeIconsDarkSmall = {
+    ":images/fallback/dark/small/edit-none.svg",
+    ":images/fallback/dark/small/arrow-down-double.svg",
+    ":images/fallback/dark/small/arrow-up-double.svg",
+    ":images/fallback/dark/small/dialog-ok.svg",
+    ":images/fallback/dark/small/question.svg"
+};
+
+static const std::deque<QString> fallbackConnectionStateThemeIconsDarkSmall = {
+    ":images/fallback/dark/small/state-offline.svg",
+    ":images/fallback/dark/small/state-sync.svg",
+    ":images/fallback/dark/small/state-ok.svg",
+    ":images/fallback/dark/small/state-error.svg"
+};
+    
+QIcon availabilityIcon(Availability av, bool big = false);
+QIcon subscriptionStateIcon(SubscriptionState ss, bool big = false);
+QIcon connectionStateIcon(ConnectionState cs, bool big = false);
+QIcon icon(const QString& name, bool big = false);
+QString iconPath(const QString& name, bool big = false);
+
+static const std::map<QString, std::pair<QString, QString>> icons = {
+    {"user-online", {"user-online", "online"}},
+    {"user-away", {"user-away", "away"}},
+    {"user-away-extended", {"user-away-extended", "absent"}},
+    {"user-busy", {"user-busy", "busy"}},
+    {"user-chatty", {"chatty", "chatty"}},
+    {"user-invisible", {"user-invisible", "invisible"}},
+    {"user-offline", {"offline", "offline"}},
+    {"edit-none", {"edit-none", "edit-none"}}, 
+    {"arrow-down-double", {"arrow-down-double", "arrow-down-double"}}, 
+    {"arrow-up-double", {"arrow-up-double", "arrow-up-double"}}, 
+    {"dialog-ok", {"dialog-ok", "dialog-ok"}}, 
+    {"question", {"question", "question"}},
+    {"state-offline", {"state-offline", "state-offline"}}, 
+    {"state-sync", {"state-sync", "state-sync"}}, 
+    {"state-ok", {"state-ok", "state-ok"}}, 
+    {"state-error", {"state-error", "state-error"}},
+    
+    {"edit-copy", {"edit-copy", "copy"}},
+    {"edit-delete", {"edit-delete", "edit-delete"}},
+    {"edit-rename", {"edit-rename", "edit-rename"}},
+    {"mail-message", {"mail-message", "mail-message"}},
+    {"mail-attachment", {"mail-attachment", "mail-attachment"}},
+    {"network-connect", {"network-connect", "network-connect"}},
+    {"network-disconnect", {"network-disconnect", "network-disconnect"}},
+    {"news-subscribe", {"news-subscribe", "news-subscribe"}},
+    {"news-unsubscribe", {"news-unsubscribe", "news-unsubscribe"}},
+    {"view-refresh", {"view-refresh", "view-refresh"}},
+    {"send", {"document-send", "send"}},
+    {"clean", {"edit-clear-all", "clean"}},
+    {"user", {"user", "user"}},
+    {"user-properties", {"user-properties", "user-properties"}},
+    {"group", {"group", "group"}},
+    {"group-new", {"resurce-group-new", "group-new"}},
+    {"favorite", {"favorite", "favorite"}},
+    {"unfavorite", {"draw-star", "unfavorite"}},
+    {"list-add", {"list-add", "add"}},
+};
+}
+
+#endif // SHARED_ICONS_H
diff --git a/shared/message.cpp b/shared/message.cpp
new file mode 100644
index 0000000..7df0f28
--- /dev/null
+++ b/shared/message.cpp
@@ -0,0 +1,399 @@
+/*
+ * Squawk messenger. 
+ * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+ * 
+ * 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 "message.h"
+#include "utils.h"
+
+Shared::Message::Message(Shared::Message::Type p_type):
+    jFrom(),
+    rFrom(),
+    jTo(),
+    rTo(),
+    id(),
+    body(),
+    time(),
+    thread(),
+    type(p_type),
+    outgoing(false),
+    forwarded(false),
+    state(State::delivered),
+    edited(false) {}
+
+Shared::Message::Message():
+    jFrom(),
+    rFrom(),
+    jTo(),
+    rTo(),
+    id(),
+    body(),
+    time(),
+    thread(),
+    type(Message::normal),
+    outgoing(false),
+    forwarded(false),
+    state(State::delivered),
+    edited(false),
+    errorText(),
+    originalMessage(),
+    lastModified() {}
+
+QString Shared::Message::getBody() const
+{
+    return body;
+}
+
+QString Shared::Message::getFrom() const
+{
+    QString from = jFrom;
+    if (rFrom.size() > 0) {
+        from += "/" + rFrom;
+    }
+    return from;
+}
+
+QString Shared::Message::getTo() const
+{
+    QString to = jTo;
+    if (rTo.size() > 0) {
+        to += "/" + rTo;
+    }
+    return to;
+}
+
+QString Shared::Message::getId() const
+{
+    return id;
+}
+
+QDateTime Shared::Message::getTime() const
+{
+    return time;
+}
+
+void Shared::Message::setBody(const QString& p_body)
+{
+    body = p_body;
+}
+
+void Shared::Message::setFrom(const QString& from)
+{
+    QStringList list = from.split("/");
+    if (list.size() == 1) {
+        jFrom = from;
+    } else {
+        jFrom = list.front();
+        rFrom = list.back();
+    }
+}
+
+void Shared::Message::setTo(const QString& to)
+{
+    QStringList list = to.split("/");
+    if (list.size() == 1) {
+        jTo = to;
+    } else {
+        jTo = list.front();
+        rTo = list.back();
+    }
+}
+
+void Shared::Message::setId(const QString& p_id)
+{
+    id = p_id;
+}
+
+void Shared::Message::setTime(const QDateTime& p_time)
+{
+    time = p_time;
+}
+
+QString Shared::Message::getFromJid() const
+{
+    return jFrom;
+}
+
+QString Shared::Message::getFromResource() const
+{
+    return rFrom;
+}
+
+QString Shared::Message::getToJid() const
+{
+    return jTo;
+}
+
+QString Shared::Message::getToResource() const
+{
+    return rTo;
+}
+
+QString Shared::Message::getErrorText() const
+{
+    return errorText;
+}
+
+QString Shared::Message::getPenPalJid() const
+{
+    if (outgoing) {
+        return jTo;
+    } else {
+        return jFrom;
+    }
+}
+
+QString Shared::Message::getPenPalResource() const
+{
+    if (outgoing) {
+        return rTo;
+    } else {
+        return rFrom;
+    }
+}
+
+Shared::Message::State Shared::Message::getState() const
+{
+    return state;
+}
+
+bool Shared::Message::getEdited() const
+{
+    return edited;
+}
+
+void Shared::Message::setFromJid(const QString& from)
+{
+    jFrom = from;
+}
+
+void Shared::Message::setFromResource(const QString& from)
+{
+    rFrom = from;
+}
+
+void Shared::Message::setToJid(const QString& to)
+{
+    jTo = to;
+}
+
+void Shared::Message::setToResource(const QString& to)
+{
+    rTo = to;
+}
+
+void Shared::Message::setErrorText(const QString& err)
+{
+    if (state == State::error) {
+        errorText = err;
+    }
+}
+
+bool Shared::Message::getOutgoing() const
+{
+    return outgoing;
+}
+
+void Shared::Message::setOutgoing(bool og)
+{
+    outgoing = og;
+}
+
+bool Shared::Message::getForwarded() const
+{
+    return forwarded;
+}
+
+void Shared::Message::generateRandomId()
+{
+    id = generateUUID();
+}
+
+QString Shared::Message::getThread() const
+{
+    return thread;
+}
+
+void Shared::Message::setForwarded(bool fwd)
+{
+    forwarded = fwd;
+}
+
+void Shared::Message::setThread(const QString& p_body)
+{
+    thread = p_body;
+}
+
+QDateTime Shared::Message::getLastModified() const
+{
+    return lastModified;
+}
+
+QString Shared::Message::getOriginalBody() const
+{
+    return originalMessage;
+}
+
+Shared::Message::Type Shared::Message::getType() const
+{
+    return type;
+}
+
+void Shared::Message::setType(Shared::Message::Type t)
+{
+    type = t;
+}
+
+void Shared::Message::setState(Shared::Message::State p_state)
+{
+    state = p_state;
+    
+    if (state != State::error) {
+        errorText = "";
+    }
+}
+
+bool Shared::Message::serverStored() const
+{
+    return state == State::delivered || state == State::sent;
+}
+
+void Shared::Message::setEdited(bool p_edited)
+{
+    edited = p_edited;
+}
+
+void Shared::Message::serialize(QDataStream& data) const
+{
+    data << jFrom;
+    data << rFrom;
+    data << jTo;
+    data << rTo;
+    data << id;
+    data << body;
+    data << time;
+    data << thread;
+    data << (quint8)type;
+    data << outgoing;
+    data << forwarded;
+    data << oob;
+    data << (quint8)state;
+    data << edited;
+    if (state == State::error) {
+        data << errorText;
+    }
+    if (edited) {
+        data << originalMessage;
+        data << lastModified;
+    }
+}
+
+void Shared::Message::deserialize(QDataStream& data)
+{
+    data >> jFrom;
+    data >> rFrom;
+    data >> jTo;
+    data >> rTo;
+    data >> id;
+    data >> body;
+    data >> time;
+    data >> thread;
+    quint8 t;
+    data >> t;
+    type = static_cast<Type>(t);
+    data >> outgoing;
+    data >> forwarded;
+    data >> oob;
+    quint8 s;
+    data >> s;
+    state = static_cast<State>(s);
+    data >> edited;
+    if (state == State::error) {
+        data >> errorText;
+    }
+    if (edited) {
+        data >> originalMessage;
+        data >> lastModified;
+    }
+}
+
+bool Shared::Message::change(const QMap<QString, QVariant>& data)
+{
+    QMap<QString, QVariant>::const_iterator itr = data.find("state");
+    if (itr != data.end()) {
+        setState(static_cast<State>(itr.value().toUInt()));
+    }
+    
+    if (state == State::error) {
+        itr = data.find("errorText");
+        if (itr != data.end()) {
+            setErrorText(itr.value().toString());
+        }
+    }
+    
+    bool idChanged = false;
+    itr = data.find("id");
+    if (itr != data.end()) {
+        QString newId = itr.value().toString();
+        if (id != newId) {
+            setId(newId);
+            idChanged = true;
+        }
+    }
+    itr = data.find("body");
+    if (itr != data.end()) {
+        QMap<QString, QVariant>::const_iterator dItr = data.find("stamp");
+        QDateTime correctionDate;
+        if (dItr != data.end()) {
+            correctionDate = dItr.value().toDateTime();
+        } else {
+            correctionDate = QDateTime::currentDateTimeUtc();      //in case there is no information about time of this correction it's applied
+        }
+        if (!edited || lastModified < correctionDate) {
+            originalMessage = body;
+            lastModified = correctionDate;
+            setBody(itr.value().toString());
+            setEdited(true);
+        }
+    }
+    
+    return idChanged;
+}
+
+void Shared::Message::setCurrentTime()
+{
+    time = QDateTime::currentDateTimeUtc();
+}
+
+QString Shared::Message::getOutOfBandUrl() const
+{
+    return oob;
+}
+
+bool Shared::Message::hasOutOfBandUrl() const
+{
+    return oob.size() > 0;
+}
+
+void Shared::Message::setOutOfBandUrl(const QString& url)
+{
+    oob = url;
+}
+
+bool Shared::Message::storable() const
+{
+    return id.size() > 0 && (body.size() > 0 || oob.size()) > 0;
+}
diff --git a/shared/message.h b/shared/message.h
new file mode 100644
index 0000000..98ef206
--- /dev/null
+++ b/shared/message.h
@@ -0,0 +1,125 @@
+/*
+ * Squawk messenger. 
+ * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+ * 
+ * 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 <QString>
+#include <QDateTime>
+#include <QVariant>
+#include <QMap>
+#include <QDataStream>
+
+#ifndef SHAPER_MESSAGE_H
+#define SHAPER_MESSAGE_H
+
+namespace Shared {
+
+/**
+ * @todo write docs
+ */
+class Message {
+public:
+    enum Type {
+        error,
+        normal,
+        chat,
+        groupChat,
+        headline
+    };
+    
+    enum class State {
+        pending,
+        sent,
+        delivered,
+        error
+    };
+    
+    Message(Type p_type);
+    Message();
+    
+    void setFrom(const QString& from);
+    void setFromResource(const QString& from);
+    void setFromJid(const QString& from);
+    void setTo(const QString& to);
+    void setToResource(const QString& to);
+    void setToJid(const QString& to);
+    void setTime(const QDateTime& p_time);
+    void setId(const QString& p_id);
+    void setBody(const QString& p_body);
+    void setThread(const QString& p_body);
+    void setOutgoing(bool og);
+    void setForwarded(bool fwd);
+    void setType(Type t);
+    void setCurrentTime();
+    void setOutOfBandUrl(const QString& url);
+    void setState(State p_state);
+    void setEdited(bool p_edited);
+    void setErrorText(const QString& err);
+    bool change(const QMap<QString, QVariant>& data);
+    
+    QString getFrom() const;
+    QString getFromJid() const;
+    QString getFromResource() const;
+    QString getTo() const;
+    QString getToJid() const;
+    QString getToResource() const;
+    QDateTime getTime() const;
+    QString getId() const;
+    QString getBody() const;
+    QString getThread() const;
+    bool getOutgoing() const;
+    bool getForwarded() const;
+    Type getType() const;
+    bool hasOutOfBandUrl() const;
+    bool storable() const;
+    QString getOutOfBandUrl() const;
+    State getState() const;
+    bool getEdited() const;
+    QString getErrorText() const;
+    
+    QString getPenPalJid() const;
+    QString getPenPalResource() const;
+    void generateRandomId();
+    bool serverStored() const;
+    QDateTime getLastModified() const;
+    QString getOriginalBody() const;
+    
+    void serialize(QDataStream& data) const;
+    void deserialize(QDataStream& data);
+    
+private:
+    QString jFrom;
+    QString rFrom;
+    QString jTo;
+    QString rTo;
+    QString id;
+    QString body;
+    QDateTime time;
+    QString thread;
+    Type type;
+    bool outgoing;
+    bool forwarded;
+    QString oob;
+    State state;
+    bool edited;
+    QString errorText;
+    QString originalMessage;
+    QDateTime lastModified;
+};
+
+}
+
+#endif // SHAPER_MESSAGE_H
diff --git a/shared/utils.cpp b/shared/utils.cpp
new file mode 100644
index 0000000..776c052
--- /dev/null
+++ b/shared/utils.cpp
@@ -0,0 +1,29 @@
+/*
+ * Squawk messenger. 
+ * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+ * 
+ * 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"
+
+QString Shared::generateUUID()
+{
+    uuid_t uuid;
+    uuid_generate(uuid);
+    
+    char uuid_str[36];
+    uuid_unparse_lower(uuid, uuid_str);
+    return uuid_str;
+}
diff --git a/shared/utils.h b/shared/utils.h
new file mode 100644
index 0000000..218b53a
--- /dev/null
+++ b/shared/utils.h
@@ -0,0 +1,72 @@
+/*
+ * Squawk messenger. 
+ * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+ * 
+ * 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/>.
+ */
+
+#ifndef SHARED_UTILS_H
+#define SHARED_UTILS_H
+
+#include <QString>
+#include <QColor>
+
+#include <uuid/uuid.h>
+#include <vector>
+
+namespace Shared {
+
+QString generateUUID();
+
+static const std::vector<QColor> colorPalette = {
+    QColor(244, 27, 63),
+    QColor(21, 104, 156),
+    QColor(38, 156, 98),
+    QColor(247, 103, 101),
+    QColor(121, 37, 117),
+    QColor(242, 202, 33),
+    QColor(168, 22, 63),
+    QColor(35, 100, 52),
+    QColor(52, 161, 152),
+    QColor(239, 53, 111),
+    QColor(237, 234, 36),
+    QColor(153, 148, 194),
+    QColor(211, 102, 151),
+    QColor(194, 63, 118),
+    QColor(249, 149, 51),
+    QColor(244, 206, 109),
+    QColor(121, 105, 153),
+    QColor(244, 199, 30),
+    QColor(28, 112, 28),
+    QColor(172, 18, 20),
+    QColor(25, 66, 110),
+    QColor(25, 149, 104),
+    QColor(214, 148, 0),
+    QColor(203, 47, 57),
+    QColor(4, 54, 84),
+    QColor(116, 161, 97),
+    QColor(50, 68, 52),
+    QColor(237, 179, 20),
+    QColor(69, 114, 147),
+    QColor(242, 212, 31),
+    QColor(248, 19, 20),
+    QColor(84, 102, 84),
+    QColor(25, 53, 122),
+    QColor(91, 91, 109),
+    QColor(17, 17, 80),
+    QColor(54, 54, 94)
+};
+}
+
+#endif // SHARED_UTILS_H
diff --git a/shared/vcard.cpp b/shared/vcard.cpp
new file mode 100644
index 0000000..6b996e2
--- /dev/null
+++ b/shared/vcard.cpp
@@ -0,0 +1,288 @@
+/*
+ * Squawk messenger. 
+ * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+ * 
+ * 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 "vcard.h"
+
+Shared::VCard::Contact::Contact(Shared::VCard::Contact::Role p_role, bool p_prefered):
+    role(p_role),
+    prefered(p_prefered) {}
+
+Shared::VCard::Email::Email(const QString& addr, Shared::VCard::Contact::Role p_role, bool p_prefered):
+    Contact(p_role, p_prefered),
+    address(addr) {}
+
+Shared::VCard::Phone::Phone(const QString& nmbr, Shared::VCard::Phone::Type p_type, Shared::VCard::Contact::Role p_role, bool p_prefered):
+    Contact(p_role, p_prefered),
+    number(nmbr),
+    type(p_type) {}
+
+Shared::VCard::Address::Address(const QString& zCode, const QString& cntry, const QString& rgn, const QString& lclty, const QString& strt, const QString& ext, Shared::VCard::Contact::Role p_role, bool p_prefered):
+    Contact(p_role, p_prefered),
+    zipCode(zCode),
+    country(cntry),
+    region(rgn),
+    locality(lclty),
+    street(strt),
+    external(ext) {}
+
+Shared::VCard::VCard():
+    fullName(),
+    firstName(),
+    middleName(),
+    lastName(),
+    nickName(),
+    description(),
+    url(),
+    organizationName(),
+    organizationUnit(),
+    organizationRole(),
+    jobTitle(),
+    birthday(),
+    photoType(Avatar::empty),
+    photoPath(),
+    receivingTime(QDateTime::currentDateTimeUtc()),
+    emails(),
+    phones(),
+    addresses() {}
+
+Shared::VCard::VCard(const QDateTime& creationTime):
+    fullName(),
+    firstName(),
+    middleName(),
+    lastName(),
+    nickName(),
+    description(),
+    url(),
+    organizationName(),
+    organizationUnit(),
+    organizationRole(),
+    jobTitle(),
+    birthday(),
+    photoType(Avatar::empty),
+    photoPath(),
+    receivingTime(creationTime),
+    emails(),
+    phones(),
+    addresses() {}
+
+QString Shared::VCard::getAvatarPath() const
+{
+    return photoPath;
+}
+
+Shared::Avatar Shared::VCard::getAvatarType() const
+{
+    return photoType;
+}
+
+QDate Shared::VCard::getBirthday() const
+{
+    return birthday;
+}
+
+QString Shared::VCard::getDescription() const
+{
+    return description;
+}
+
+QString Shared::VCard::getFirstName() const
+{
+    return firstName;
+}
+
+QString Shared::VCard::getLastName() const
+{
+    return lastName;
+}
+
+QString Shared::VCard::getMiddleName() const
+{
+    return middleName;
+}
+
+QString Shared::VCard::getNickName() const
+{
+    return nickName;
+}
+
+void Shared::VCard::setAvatarPath(const QString& path)
+{
+    if (path != photoPath) {
+        photoPath = path;
+    }
+}
+
+void Shared::VCard::setAvatarType(Shared::Avatar type)
+{
+    if (photoType != type) {
+        photoType = type;
+    }
+}
+
+void Shared::VCard::setBirthday(const QDate& date)
+{
+    if (date.isValid() && birthday != date) {
+        birthday = date;
+    }
+}
+
+void Shared::VCard::setDescription(const QString& descr)
+{
+    if (description != descr) {
+        description = descr;
+    }
+}
+
+void Shared::VCard::setFirstName(const QString& first)
+{
+    if (firstName != first) {
+        firstName = first;
+    }
+}
+
+void Shared::VCard::setLastName(const QString& last)
+{
+    if (lastName != last) {
+        lastName = last;
+    }
+}
+
+void Shared::VCard::setMiddleName(const QString& middle)
+{
+    if (middleName != middle) {
+        middleName = middle;
+    }
+}
+
+void Shared::VCard::setNickName(const QString& nick)
+{
+    if (nickName != nick) {
+        nickName = nick;
+    }
+}
+
+QString Shared::VCard::getFullName() const
+{
+    return fullName;
+}
+
+QString Shared::VCard::getUrl() const
+{
+    return url;
+}
+
+void Shared::VCard::setFullName(const QString& name)
+{
+    if (fullName != name) {
+        fullName = name;
+    }
+}
+
+void Shared::VCard::setUrl(const QString& u)
+{
+    if (url != u) {
+        url = u;
+    }
+}
+
+QString Shared::VCard::getOrgName() const
+{
+    return organizationName;
+}
+
+QString Shared::VCard::getOrgRole() const
+{
+    return organizationRole;
+}
+
+QString Shared::VCard::getOrgTitle() const
+{
+    return jobTitle;
+}
+
+QString Shared::VCard::getOrgUnit() const
+{
+    return organizationUnit;
+}
+
+void Shared::VCard::setOrgName(const QString& name)
+{
+    if (organizationName != name) {
+        organizationName = name;
+    }
+}
+
+void Shared::VCard::setOrgRole(const QString& role)
+{
+    if (organizationRole != role) {
+        organizationRole = role;
+    }
+}
+
+void Shared::VCard::setOrgTitle(const QString& title)
+{
+    if (jobTitle != title) {
+        jobTitle = title;
+    }
+}
+
+void Shared::VCard::setOrgUnit(const QString& unit)
+{
+    if (organizationUnit != unit) {
+        organizationUnit = unit;
+    }
+}
+
+QDateTime Shared::VCard::getReceivingTime() const
+{
+    return receivingTime;
+}
+
+std::deque<Shared::VCard::Email> & Shared::VCard::getEmails()
+{
+    return emails;
+}
+
+std::deque<Shared::VCard::Address> & Shared::VCard::getAddresses()
+{
+    return addresses;
+}
+
+std::deque<Shared::VCard::Phone> & Shared::VCard::getPhones()
+{
+    return phones;
+}
+
+const std::deque<Shared::VCard::Email> & Shared::VCard::getEmails() const
+{
+    return emails;
+}
+
+const std::deque<Shared::VCard::Address> & Shared::VCard::getAddresses() const
+{
+    return addresses;
+}
+
+const std::deque<Shared::VCard::Phone> & Shared::VCard::getPhones() const
+{
+    return phones;
+}
+
+const std::deque<QString>Shared::VCard::Contact::roleNames = {"Not specified", "Personal", "Business"};
+const std::deque<QString>Shared::VCard::Phone::typeNames = {"Fax", "Pager", "Voice", "Cell", "Video", "Modem", "Other"};
+
diff --git a/shared/vcard.h b/shared/vcard.h
new file mode 100644
index 0000000..68e5de3
--- /dev/null
+++ b/shared/vcard.h
@@ -0,0 +1,152 @@
+/*
+ * Squawk messenger. 
+ * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+ * 
+ * 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/>.
+ */
+
+#ifndef SHARED_VCARD_H
+#define SHARED_VCARD_H
+
+#include <QString>
+#include <QDateTime>
+
+#include <deque>
+
+#include "enums.h"
+
+namespace Shared {
+
+class VCard {
+    class Contact {
+    public:
+        enum Role {
+            none,
+            home,
+            work
+        };
+        static const std::deque<QString> roleNames;
+        
+        Contact(Role p_role = none, bool p_prefered = false);
+        
+        Role role;
+        bool prefered;
+    };
+public:
+    class Email : public Contact {
+    public:
+        Email(const QString& address, Role p_role = none, bool p_prefered = false);
+        
+        QString address;
+    };
+    class Phone : public Contact {
+    public:
+        enum Type {
+            fax,
+            pager,
+            voice,
+            cell,
+            video,
+            modem,
+            other
+        };
+        static const std::deque<QString> typeNames;
+        Phone(const QString& number, Type p_type = voice, Role p_role = none, bool p_prefered = false);
+        
+        QString number;
+        Type type;
+    };
+    class Address : public Contact {
+    public:
+        Address(
+            const QString& zCode = "", 
+            const QString& cntry = "", 
+            const QString& rgn = "", 
+            const QString& lclty = "", 
+            const QString& strt = "", 
+            const QString& ext = "", 
+            Role p_role = none, 
+            bool p_prefered = false
+        );
+        
+        QString zipCode;
+        QString country;
+        QString region;
+        QString locality;
+        QString street;
+        QString external;
+    };
+    VCard();
+    VCard(const QDateTime& creationTime);
+    
+    QString getFullName() const;
+    void setFullName(const QString& name);
+    QString getFirstName() const;
+    void setFirstName(const QString& first);
+    QString getMiddleName() const;
+    void setMiddleName(const QString& middle);
+    QString getLastName() const;
+    void setLastName(const QString& last);
+    QString getNickName() const;
+    void setNickName(const QString& nick);
+    QString getDescription() const;
+    void setDescription(const QString& descr);
+    QString getUrl() const;
+    void setUrl(const QString& u);
+    QDate getBirthday() const;
+    void setBirthday(const QDate& date);
+    Avatar getAvatarType() const;
+    void setAvatarType(Avatar type);
+    QString getAvatarPath() const;
+    void setAvatarPath(const QString& path);
+    QString getOrgName() const;
+    void setOrgName(const QString& name);
+    QString getOrgUnit() const;
+    void setOrgUnit(const QString& unit);
+    QString getOrgRole() const;
+    void setOrgRole(const QString& role);
+    QString getOrgTitle() const;
+    void setOrgTitle(const QString& title);
+    QDateTime getReceivingTime() const;
+    std::deque<Email>& getEmails();
+    const std::deque<Email>& getEmails() const;
+    std::deque<Phone>& getPhones();
+    const std::deque<Phone>& getPhones() const;
+    std::deque<Address>& getAddresses();
+    const std::deque<Address>& getAddresses() const;
+    
+private:
+    QString fullName;
+    QString firstName;
+    QString middleName;
+    QString lastName;
+    QString nickName;
+    QString description;
+    QString url;
+    QString organizationName;
+    QString organizationUnit;
+    QString organizationRole;
+    QString jobTitle;
+    QDate birthday;
+    Avatar photoType;
+    QString photoPath;
+    QDateTime receivingTime;
+    std::deque<Email> emails;
+    std::deque<Phone> phones;
+    std::deque<Address> addresses;
+};
+
+}
+
+#endif // SHARED_VCARD_H
diff --git a/translations/squawk.ru.ts b/translations/squawk.ru.ts
index 4fe02e3..37686fb 100644
--- a/translations/squawk.ru.ts
+++ b/translations/squawk.ru.ts
@@ -4,93 +4,63 @@
 <context>
     <name>Account</name>
     <message>
-        <location filename="../ui/widgets/account.ui" line="14"/>
-        <location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_account.h" line="127"/>
         <source>Account</source>
         <translatorcomment>Заголовок окна</translatorcomment>
         <translation>Учетная запись</translation>
     </message>
     <message>
-        <location filename="../ui/widgets/account.ui" line="40"/>
-        <location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_account.h" line="129"/>
         <source>Your account login</source>
         <translation>Имя пользователя Вашей учетной записи</translation>
     </message>
     <message>
-        <location filename="../ui/widgets/account.ui" line="43"/>
-        <location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_account.h" line="131"/>
         <source>john_smith1987</source>
         <translation>ivan_ivanov1987</translation>
     </message>
     <message>
-        <location filename="../ui/widgets/account.ui" line="50"/>
-        <location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_account.h" line="132"/>
         <source>Server</source>
         <translation>Сервер</translation>
     </message>
     <message>
-        <location filename="../ui/widgets/account.ui" line="57"/>
-        <location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_account.h" line="134"/>
         <source>A server address of your account. Like 404.city or macaw.me</source>
         <translation>Адресс сервера вашей учетной записи (выглядит как 404.city или macaw.me)</translation>
     </message>
     <message>
-        <location filename="../ui/widgets/account.ui" line="60"/>
-        <location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_account.h" line="136"/>
         <source>macaw.me</source>
         <translation>macaw.me</translation>
     </message>
     <message>
-        <location filename="../ui/widgets/account.ui" line="67"/>
-        <location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_account.h" line="137"/>
         <source>Login</source>
         <translation>Имя учетной записи</translation>
     </message>
     <message>
-        <location filename="../ui/widgets/account.ui" line="74"/>
-        <location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_account.h" line="138"/>
         <source>Password</source>
         <translation>Пароль</translation>
     </message>
     <message>
-        <location filename="../ui/widgets/account.ui" line="81"/>
-        <location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_account.h" line="140"/>
         <source>Password of your account</source>
         <translation>Пароль вашей учетной записи</translation>
     </message>
     <message>
-        <location filename="../ui/widgets/account.ui" line="103"/>
-        <location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_account.h" line="145"/>
         <source>Name</source>
         <translation>Имя</translation>
     </message>
     <message>
-        <location filename="../ui/widgets/account.ui" line="110"/>
-        <location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_account.h" line="147"/>
         <source>Just a name how would you call this account, doesn&apos;t affect anything</source>
         <translation>Просто имя, то как Вы называете свою учетную запись, может быть любым</translation>
     </message>
     <message>
-        <location filename="../ui/widgets/account.ui" line="113"/>
-        <location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_account.h" line="149"/>
         <source>John</source>
         <translation>Иван</translation>
     </message>
     <message>
-        <location filename="../ui/widgets/account.ui" line="120"/>
-        <location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_account.h" line="150"/>
         <source>Resource</source>
         <translation>Ресурс</translation>
     </message>
     <message>
-        <location filename="../ui/widgets/account.ui" line="127"/>
-        <location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_account.h" line="152"/>
         <source>A resource name like &quot;Home&quot; or &quot;Work&quot;</source>
         <translation>Имя этой программы для ваших контактов, может быть &quot;Home&quot; или &quot;Phone&quot;</translation>
     </message>
     <message>
-        <location filename="../ui/widgets/account.ui" line="130"/>
-        <location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_account.h" line="154"/>
         <source>QXmpp</source>
         <translatorcomment>Ресурс по умолчанию</translatorcomment>
         <translation>QXmpp</translation>
@@ -99,45 +69,30 @@
 <context>
     <name>Accounts</name>
     <message>
-        <location filename="../ui/widgets/accounts.ui" line="14"/>
-        <location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_accounts.h" line="108"/>
         <source>Accounts</source>
         <translation>Учетные записи</translation>
     </message>
     <message>
-        <location filename="../ui/widgets/accounts.ui" line="45"/>
-        <location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_accounts.h" line="109"/>
         <source>Delete</source>
         <translation>Удалить</translation>
     </message>
     <message>
-        <location filename="../ui/widgets/accounts.ui" line="86"/>
-        <location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_accounts.h" line="110"/>
         <source>Add</source>
         <translation>Добавить</translation>
     </message>
     <message>
-        <location filename="../ui/widgets/accounts.ui" line="96"/>
-        <location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_accounts.h" line="111"/>
         <source>Edit</source>
         <translation>Редактировать</translation>
     </message>
     <message>
-        <location filename="../ui/widgets/accounts.ui" line="106"/>
-        <location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_accounts.h" line="112"/>
         <source>Change password</source>
         <translation>Изменить пароль</translation>
     </message>
     <message>
-        <location filename="../ui/widgets/accounts.ui" line="129"/>
-        <location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_accounts.h" line="113"/>
-        <location filename="../ui/widgets/accounts.cpp" line="125"/>
-        <location filename="../ui/widgets/accounts.cpp" line="128"/>
         <source>Connect</source>
         <translation>Подключить</translation>
     </message>
     <message>
-        <location filename="../ui/widgets/accounts.cpp" line="122"/>
         <source>Disconnect</source>
         <translation>Отключить</translation>
     </message>
@@ -145,16 +100,21 @@
 <context>
     <name>Conversation</name>
     <message>
-        <location filename="../ui/widgets/conversation.ui" line="449"/>
-        <location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_conversation.h" line="324"/>
         <source>Type your message here...</source>
         <translation>Введите сообщение...</translation>
     </message>
     <message>
-        <location filename="../ui/widgets/conversation.cpp" line="284"/>
         <source>Chose a file to send</source>
         <translation>Выберите файл для отправки</translation>
     </message>
+    <message>
+        <source>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;Liberation Sans&apos;; font-size:10pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation></translation>
+    </message>
 </context>
 <context>
     <name>Global</name>
@@ -260,43 +220,43 @@
     </message>
     <message>
         <source>Not specified</source>
-        <translation>Не указан</translation>
+        <translation type="vanished">Не указан</translation>
     </message>
     <message>
         <source>Personal</source>
-        <translation>Личный</translation>
+        <translation type="vanished">Личный</translation>
     </message>
     <message>
         <source>Business</source>
-        <translation>Рабочий</translation>
+        <translation type="vanished">Рабочий</translation>
     </message>
     <message>
         <source>Fax</source>
-        <translation>Факс</translation>
+        <translation type="vanished">Факс</translation>
     </message>
     <message>
         <source>Pager</source>
-        <translation>Пэйджер</translation>
+        <translation type="vanished">Пэйджер</translation>
     </message>
     <message>
         <source>Voice</source>
-        <translation>Стационарный</translation>
+        <translation type="vanished">Стационарный</translation>
     </message>
     <message>
         <source>Cell</source>
-        <translation>Мобильный</translation>
+        <translation type="vanished">Мобильный</translation>
     </message>
     <message>
         <source>Video</source>
-        <translation>Видеофон</translation>
+        <translation type="vanished">Видеофон</translation>
     </message>
     <message>
         <source>Modem</source>
-        <translation>Модем</translation>
+        <translation type="vanished">Модем</translation>
     </message>
     <message>
         <source>Other</source>
-        <translation>Другой</translation>
+        <translation type="vanished">Другой</translation>
     </message>
     <message>
         <source>Pending</source>
@@ -314,63 +274,43 @@
 <context>
     <name>JoinConference</name>
     <message>
-        <location filename="../ui/widgets/joinconference.ui" line="14"/>
-        <location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_joinconference.h" line="116"/>
         <source>Join new conference</source>
         <translatorcomment>Заголовок окна</translatorcomment>
         <translation>Присоединиться к новой беседе</translation>
     </message>
     <message>
-        <location filename="../ui/widgets/joinconference.ui" line="22"/>
-        <location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_joinconference.h" line="117"/>
         <source>JID</source>
         <translation>JID</translation>
     </message>
     <message>
-        <location filename="../ui/widgets/joinconference.ui" line="29"/>
-        <location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_joinconference.h" line="119"/>
         <source>Room JID</source>
         <translation>Jabber-идентификатор беседы</translation>
     </message>
     <message>
-        <location filename="../ui/widgets/joinconference.ui" line="32"/>
-        <location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_joinconference.h" line="121"/>
         <source>identifier@conference.server.org</source>
         <translation>identifier@conference.server.org</translation>
     </message>
     <message>
-        <location filename="../ui/widgets/joinconference.ui" line="39"/>
-        <location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_joinconference.h" line="122"/>
         <source>Account</source>
         <translation>Учетная запись</translation>
     </message>
     <message>
-        <location filename="../ui/widgets/joinconference.ui" line="49"/>
-        <location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_joinconference.h" line="123"/>
         <source>Join on login</source>
         <translation>Автовход</translation>
     </message>
     <message>
-        <location filename="../ui/widgets/joinconference.ui" line="56"/>
-        <location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_joinconference.h" line="125"/>
         <source>If checked Squawk will try to join this conference on login</source>
         <translation>Если стоит галочка Squawk автоматически присоединится к этой беседе при подключении</translation>
     </message>
     <message>
-        <location filename="../ui/widgets/joinconference.ui" line="66"/>
-        <location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_joinconference.h" line="128"/>
         <source>Nick name</source>
         <translation>Псевдоним</translation>
     </message>
     <message>
-        <location filename="../ui/widgets/joinconference.ui" line="73"/>
-        <location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_joinconference.h" line="130"/>
         <source>Your nick name for that conference. If you leave this field empty your account name will be used as a nick name</source>
         <translation>Ваш псевдоним в этой беседе, если оставите это поле пустым - будет использовано имя Вашей учетной записи</translation>
     </message>
     <message>
-        <location filename="../ui/widgets/joinconference.ui" line="76"/>
-        <location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_joinconference.h" line="132"/>
         <source>John</source>
         <translation>Ivan</translation>
     </message>
@@ -379,10 +319,9 @@
     <name>Message</name>
     <message>
         <source>Download</source>
-        <translation>Скачать</translation>
+        <translation type="vanished">Скачать</translation>
     </message>
     <message>
-        <location filename="../ui/utils/message.cpp" line="172"/>
         <source>Open</source>
         <translation>Открыть</translation>
     </message>
@@ -390,23 +329,18 @@
 <context>
     <name>MessageLine</name>
     <message>
-        <location filename="../ui/utils/messageline.cpp" line="146"/>
         <source>Downloading...</source>
         <translation>Скачивается...</translation>
     </message>
     <message>
-        <location filename="../ui/utils/messageline.cpp" line="258"/>
-        <location filename="../ui/utils/messageline.cpp" line="324"/>
         <source>Download</source>
         <translation>Скачать</translation>
     </message>
     <message>
-        <location filename="../ui/utils/messageline.cpp" line="259"/>
         <source>Push the button to daownload the file</source>
         <translation>Нажмите на кнопку что бы загрузить файл</translation>
     </message>
     <message>
-        <location filename="../ui/utils/messageline.cpp" line="319"/>
         <source>Error uploading file: %1
 You can try again</source>
         <translation>Ошибка загрузки файла на сервер:
@@ -414,12 +348,10 @@ You can try again</source>
 Для того, что бы попробовать снова нажмите на кнопку</translation>
     </message>
     <message>
-        <location filename="../ui/utils/messageline.cpp" line="320"/>
         <source>Upload</source>
         <translation>Загрузить</translation>
     </message>
     <message>
-        <location filename="../ui/utils/messageline.cpp" line="325"/>
         <source>Error downloading file: %1
 You can try again</source>
         <translation>Ошибка скачивания файла: 
@@ -427,7 +359,6 @@ You can try again</source>
 Вы можете попробовать снова</translation>
     </message>
     <message>
-        <location filename="../ui/utils/messageline.cpp" line="336"/>
         <source>Uploading...</source>
         <translation>Загружается...</translation>
     </message>
@@ -436,40 +367,36 @@ You can try again</source>
     <name>Models::Accounts</name>
     <message>
         <source>Name</source>
-        <translation>Имя</translation>
+        <translation type="vanished">Имя</translation>
     </message>
     <message>
         <source>Server</source>
-        <translation>Сервер</translation>
+        <translation type="vanished">Сервер</translation>
     </message>
     <message>
         <source>State</source>
-        <translation>Состояние</translation>
+        <translation type="vanished">Состояние</translation>
     </message>
     <message>
         <source>Error</source>
-        <translation>Ошибка</translation>
+        <translation type="vanished">Ошибка</translation>
     </message>
 </context>
 <context>
     <name>Models::Room</name>
     <message>
-        <location filename="../ui/models/room.cpp" line="196"/>
         <source>Subscribed</source>
         <translation>Вы состоите в беседе</translation>
     </message>
     <message>
-        <location filename="../ui/models/room.cpp" line="198"/>
         <source>Temporarily unsubscribed</source>
         <translation>Вы временно не состоите в беседе</translation>
     </message>
     <message>
-        <location filename="../ui/models/room.cpp" line="202"/>
         <source>Temporarily subscribed</source>
         <translation>Вы временно состоите в беседе</translation>
     </message>
     <message>
-        <location filename="../ui/models/room.cpp" line="204"/>
         <source>Unsubscribed</source>
         <translation>Вы не состоите в беседе</translation>
     </message>
@@ -477,67 +404,47 @@ You can try again</source>
 <context>
     <name>Models::Roster</name>
     <message>
-        <location filename="../ui/models/roster.cpp" line="80"/>
         <source>New messages</source>
         <translation>Есть непрочитанные сообщения</translation>
     </message>
     <message>
-        <location filename="../ui/models/roster.cpp" line="178"/>
-        <location filename="../ui/models/roster.cpp" line="204"/>
-        <location filename="../ui/models/roster.cpp" line="241"/>
-        <location filename="../ui/models/roster.cpp" line="253"/>
         <source>New messages: </source>
         <translation>Новых сообщений: </translation>
     </message>
     <message>
-        <location filename="../ui/models/roster.cpp" line="180"/>
         <source>Jabber ID: </source>
         <translation>Идентификатор: </translation>
     </message>
     <message>
-        <location filename="../ui/models/roster.cpp" line="184"/>
-        <location filename="../ui/models/roster.cpp" line="207"/>
-        <location filename="../ui/models/roster.cpp" line="220"/>
         <source>Availability: </source>
         <translation>Доступность: </translation>
     </message>
     <message>
-        <location filename="../ui/models/roster.cpp" line="188"/>
-        <location filename="../ui/models/roster.cpp" line="210"/>
-        <location filename="../ui/models/roster.cpp" line="223"/>
         <source>Status: </source>
         <translation>Статус: </translation>
     </message>
     <message>
-        <location filename="../ui/models/roster.cpp" line="191"/>
-        <location filename="../ui/models/roster.cpp" line="193"/>
-        <location filename="../ui/models/roster.cpp" line="255"/>
         <source>Subscription: </source>
         <translation>Подписка: </translation>
     </message>
     <message>
-        <location filename="../ui/models/roster.cpp" line="226"/>
         <source>Affiliation: </source>
         <translatorcomment>Я правда не знаю, как это объяснить, не то что перевести</translatorcomment>
         <translation>Причастность: </translation>
     </message>
     <message>
-        <location filename="../ui/models/roster.cpp" line="229"/>
         <source>Role: </source>
         <translation>Роль: </translation>
     </message>
     <message>
-        <location filename="../ui/models/roster.cpp" line="243"/>
         <source>Online contacts: </source>
         <translation>Контакстов в сети: </translation>
     </message>
     <message>
-        <location filename="../ui/models/roster.cpp" line="244"/>
         <source>Total contacts: </source>
         <translation>Всего контактов: </translation>
     </message>
     <message>
-        <location filename="../ui/models/roster.cpp" line="257"/>
         <source>Members: </source>
         <translation>Участников: </translation>
     </message>
@@ -545,58 +452,40 @@ You can try again</source>
 <context>
     <name>NewContact</name>
     <message>
-        <location filename="../ui/widgets/newcontact.ui" line="14"/>
-        <location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_newcontact.h" line="103"/>
         <source>Add new contact</source>
         <translatorcomment>Заголовок окна</translatorcomment>
         <translation>Добавление нового контакта</translation>
     </message>
     <message>
-        <location filename="../ui/widgets/newcontact.ui" line="22"/>
-        <location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_newcontact.h" line="104"/>
         <source>Account</source>
         <translation>Учетная запись</translation>
     </message>
     <message>
-        <location filename="../ui/widgets/newcontact.ui" line="29"/>
-        <location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_newcontact.h" line="106"/>
         <source>An account that is going to have new contact</source>
         <translation>Учетная запись для которой будет добавлен контакт</translation>
     </message>
     <message>
-        <location filename="../ui/widgets/newcontact.ui" line="36"/>
-        <location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_newcontact.h" line="108"/>
         <source>JID</source>
         <translation>JID</translation>
     </message>
     <message>
-        <location filename="../ui/widgets/newcontact.ui" line="43"/>
-        <location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_newcontact.h" line="110"/>
         <source>Jabber id of your new contact</source>
         <translation>Jabber-идентификатор нового контакта</translation>
     </message>
     <message>
-        <location filename="../ui/widgets/newcontact.ui" line="46"/>
-        <location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_newcontact.h" line="112"/>
         <source>name@server.dmn</source>
         <translatorcomment>Placeholder поля ввода JID</translatorcomment>
         <translation>name@server.dmn</translation>
     </message>
     <message>
-        <location filename="../ui/widgets/newcontact.ui" line="53"/>
-        <location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_newcontact.h" line="113"/>
         <source>Name</source>
         <translation>Имя</translation>
     </message>
     <message>
-        <location filename="../ui/widgets/newcontact.ui" line="60"/>
-        <location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_newcontact.h" line="115"/>
         <source>The way this new contact will be labeled in your roster (optional)</source>
         <translation>То, как будет подписан контакт в вашем списке контактов (не обязательно)</translation>
     </message>
     <message>
-        <location filename="../ui/widgets/newcontact.ui" line="63"/>
-        <location filename="../build/ui/widgets/squawkWidgets_autogen/include/ui_newcontact.h" line="117"/>
         <source>John Smith</source>
         <translation>Иван Иванов</translation>
     </message>
@@ -604,99 +493,70 @@ You can try again</source>
 <context>
     <name>Squawk</name>
     <message>
-        <location filename="../ui/squawk.ui" line="14"/>
-        <location filename="../build/ui/squawkUI_autogen/include/ui_squawk.h" line="137"/>
         <source>squawk</source>
         <translation>Squawk</translation>
     </message>
     <message>
-        <location filename="../ui/squawk.ui" line="78"/>
-        <location filename="../build/ui/squawkUI_autogen/include/ui_squawk.h" line="143"/>
         <source>Settings</source>
         <translation>Настройки</translation>
     </message>
     <message>
-        <location filename="../ui/squawk.ui" line="84"/>
-        <location filename="../build/ui/squawkUI_autogen/include/ui_squawk.h" line="144"/>
         <source>Squawk</source>
         <translation>Squawk</translation>
     </message>
     <message>
-        <location filename="../ui/squawk.ui" line="99"/>
-        <location filename="../build/ui/squawkUI_autogen/include/ui_squawk.h" line="138"/>
         <source>Accounts</source>
         <translation>Учетные записи</translation>
     </message>
     <message>
-        <location filename="../ui/squawk.ui" line="108"/>
-        <location filename="../build/ui/squawkUI_autogen/include/ui_squawk.h" line="139"/>
         <source>Quit</source>
         <translation>Выйти</translation>
     </message>
     <message>
-        <location filename="../ui/squawk.ui" line="120"/>
-        <location filename="../build/ui/squawkUI_autogen/include/ui_squawk.h" line="140"/>
         <source>Add contact</source>
         <translation>Добавить контакт</translation>
     </message>
     <message>
-        <location filename="../ui/squawk.ui" line="132"/>
-        <location filename="../build/ui/squawkUI_autogen/include/ui_squawk.h" line="141"/>
         <source>Add conference</source>
         <translation>Присоединиться к беседе</translation>
     </message>
     <message>
-        <location filename="../ui/squawk.cpp" line="60"/>
         <source>Contact list</source>
         <translation>Список контактов</translation>
     </message>
     <message>
-        <location filename="../ui/squawk.cpp" line="558"/>
         <source>Disconnect</source>
         <translation>Отключить</translation>
     </message>
     <message>
-        <location filename="../ui/squawk.cpp" line="564"/>
         <source>Connect</source>
         <translation>Подключить</translation>
     </message>
     <message>
-        <location filename="../ui/squawk.cpp" line="570"/>
-        <location filename="../ui/squawk.cpp" line="668"/>
         <source>VCard</source>
         <translation>Карточка</translation>
     </message>
     <message>
-        <location filename="../ui/squawk.cpp" line="574"/>
-        <location filename="../ui/squawk.cpp" line="672"/>
-        <location filename="../ui/squawk.cpp" line="712"/>
         <source>Remove</source>
         <translation>Удалить</translation>
     </message>
     <message>
-        <location filename="../ui/squawk.cpp" line="586"/>
         <source>Open dialog</source>
         <translation>Открыть диалог</translation>
     </message>
     <message>
-        <location filename="../ui/squawk.cpp" line="596"/>
-        <location filename="../ui/squawk.cpp" line="693"/>
         <source>Unsubscribe</source>
         <translation>Отписаться</translation>
     </message>
     <message>
-        <location filename="../ui/squawk.cpp" line="606"/>
-        <location filename="../ui/squawk.cpp" line="702"/>
         <source>Subscribe</source>
         <translation>Подписаться</translation>
     </message>
     <message>
-        <location filename="../ui/squawk.cpp" line="617"/>
         <source>Rename</source>
         <translation>Переименовать</translation>
     </message>
     <message>
-        <location filename="../ui/squawk.cpp" line="630"/>
         <source>Input new name for %1
 or leave it empty for the contact 
 to be displayed as %1</source>
@@ -706,269 +566,197 @@ to be displayed as %1</source>
 %1</translation>
     </message>
     <message>
-        <location filename="../ui/squawk.cpp" line="631"/>
         <source>Renaming %1</source>
         <translation>Назначение имени контакту %1</translation>
     </message>
     <message>
-        <location filename="../ui/squawk.cpp" line="637"/>
         <source>Groups</source>
         <translation>Группы</translation>
     </message>
     <message>
-        <location filename="../ui/squawk.cpp" line="652"/>
         <source>New group</source>
         <translation>Создать новую группу</translation>
     </message>
     <message>
-        <location filename="../ui/squawk.cpp" line="662"/>
         <source>New group name</source>
         <translation>Имя группы</translation>
     </message>
     <message>
-        <location filename="../ui/squawk.cpp" line="663"/>
         <source>Add %1 to a new group</source>
         <translation>Добавление %1 в новую группу</translation>
     </message>
     <message>
-        <location filename="../ui/squawk.cpp" line="684"/>
         <source>Open conversation</source>
         <translation>Открыть окно беседы</translation>
     </message>
     <message>
-        <location filename="../ui/squawk.cpp" line="789"/>
         <source>%1 account card</source>
         <translation>Карточка учетной записи %1</translation>
     </message>
     <message>
-        <location filename="../ui/squawk.cpp" line="791"/>
         <source>%1 contact card</source>
         <translation>Карточка контакта %1</translation>
     </message>
     <message>
-        <location filename="../ui/squawk.cpp" line="803"/>
         <source>Downloading vCard</source>
         <translation>Получение карточки</translation>
     </message>
+    <message>
+        <source>Attached file</source>
+        <translation>Прикрепленный файл</translation>
+    </message>
 </context>
 <context>
     <name>VCard</name>
     <message>
-        <location filename="../ui/widgets/vcard/vcard.ui" line="65"/>
-        <location filename="../build/ui/widgets/vcard/vCardUI_autogen/include/ui_vcard.h" line="612"/>
         <source>Received 12.07.2007 at 17.35</source>
         <translation>Не обновлялось</translation>
     </message>
     <message>
-        <location filename="../ui/widgets/vcard/vcard.ui" line="100"/>
-        <location filename="../ui/widgets/vcard/vcard.ui" line="425"/>
-        <location filename="../build/ui/widgets/vcard/vCardUI_autogen/include/ui_vcard.h" line="624"/>
-        <location filename="../build/ui/widgets/vcard/vCardUI_autogen/include/ui_vcard.h" line="627"/>
         <source>General</source>
         <translation>Общее</translation>
     </message>
     <message>
-        <location filename="../ui/widgets/vcard/vcard.ui" line="130"/>
-        <location filename="../build/ui/widgets/vcard/vCardUI_autogen/include/ui_vcard.h" line="613"/>
         <source>Organization</source>
         <translation>Место работы</translation>
     </message>
     <message>
-        <location filename="../ui/widgets/vcard/vcard.ui" line="180"/>
-        <location filename="../build/ui/widgets/vcard/vCardUI_autogen/include/ui_vcard.h" line="614"/>
         <source>Middle name</source>
         <translation>Среднее имя</translation>
     </message>
     <message>
-        <location filename="../ui/widgets/vcard/vcard.ui" line="190"/>
-        <location filename="../build/ui/widgets/vcard/vCardUI_autogen/include/ui_vcard.h" line="615"/>
         <source>First name</source>
         <translation>Имя</translation>
     </message>
     <message>
-        <location filename="../ui/widgets/vcard/vcard.ui" line="200"/>
-        <location filename="../build/ui/widgets/vcard/vCardUI_autogen/include/ui_vcard.h" line="616"/>
         <source>Last name</source>
         <translation>Фамилия</translation>
     </message>
     <message>
-        <location filename="../ui/widgets/vcard/vcard.ui" line="226"/>
-        <location filename="../build/ui/widgets/vcard/vCardUI_autogen/include/ui_vcard.h" line="617"/>
         <source>Nick name</source>
         <translation>Псевдоним</translation>
     </message>
     <message>
-        <location filename="../ui/widgets/vcard/vcard.ui" line="252"/>
-        <location filename="../build/ui/widgets/vcard/vCardUI_autogen/include/ui_vcard.h" line="618"/>
         <source>Birthday</source>
         <translation>Дата рождения</translation>
     </message>
     <message>
-        <location filename="../ui/widgets/vcard/vcard.ui" line="272"/>
-        <location filename="../build/ui/widgets/vcard/vCardUI_autogen/include/ui_vcard.h" line="619"/>
         <source>Organization name</source>
         <translation>Название организации</translation>
     </message>
     <message>
-        <location filename="../ui/widgets/vcard/vcard.ui" line="298"/>
-        <location filename="../build/ui/widgets/vcard/vCardUI_autogen/include/ui_vcard.h" line="620"/>
         <source>Unit / Department</source>
         <translation>Отдел</translation>
     </message>
     <message>
-        <location filename="../ui/widgets/vcard/vcard.ui" line="324"/>
-        <location filename="../build/ui/widgets/vcard/vCardUI_autogen/include/ui_vcard.h" line="621"/>
         <source>Role / Profession</source>
         <translation>Профессия</translation>
     </message>
     <message>
-        <location filename="../ui/widgets/vcard/vcard.ui" line="350"/>
-        <location filename="../build/ui/widgets/vcard/vCardUI_autogen/include/ui_vcard.h" line="622"/>
         <source>Job title</source>
         <translation>Наименование должности</translation>
     </message>
     <message>
-        <location filename="../ui/widgets/vcard/vcard.ui" line="390"/>
-        <location filename="../build/ui/widgets/vcard/vCardUI_autogen/include/ui_vcard.h" line="623"/>
         <source>Full name</source>
         <translation>Полное имя</translation>
     </message>
     <message>
-        <location filename="../ui/widgets/vcard/vcard.ui" line="460"/>
-        <location filename="../build/ui/widgets/vcard/vCardUI_autogen/include/ui_vcard.h" line="625"/>
         <source>Personal information</source>
         <translation>Личная информация</translation>
     </message>
     <message>
-        <location filename="../ui/widgets/vcard/vcard.ui" line="653"/>
-        <location filename="../build/ui/widgets/vcard/vCardUI_autogen/include/ui_vcard.h" line="629"/>
         <source>Addresses</source>
         <translation>Адреса</translation>
     </message>
     <message>
-        <location filename="../ui/widgets/vcard/vcard.ui" line="682"/>
-        <location filename="../build/ui/widgets/vcard/vCardUI_autogen/include/ui_vcard.h" line="630"/>
         <source>E-Mail addresses</source>
         <translation>Адреса электронной почты</translation>
     </message>
     <message>
-        <location filename="../ui/widgets/vcard/vcard.ui" line="767"/>
-        <location filename="../build/ui/widgets/vcard/vCardUI_autogen/include/ui_vcard.h" line="633"/>
         <source>Phone numbers</source>
         <translation>Номера телефонов</translation>
     </message>
     <message>
-        <location filename="../ui/widgets/vcard/vcard.ui" line="522"/>
-        <location filename="../ui/widgets/vcard/vcard.ui" line="546"/>
-        <location filename="../build/ui/widgets/vcard/vCardUI_autogen/include/ui_vcard.h" line="628"/>
-        <location filename="../build/ui/widgets/vcard/vCardUI_autogen/include/ui_vcard.h" line="634"/>
         <source>Contact</source>
         <translation>Контактная информация</translation>
     </message>
     <message>
-        <location filename="../ui/widgets/vcard/vcard.ui" line="713"/>
-        <location filename="../build/ui/widgets/vcard/vCardUI_autogen/include/ui_vcard.h" line="631"/>
         <source>Jabber ID</source>
         <translation>Jabber ID</translation>
     </message>
     <message>
-        <location filename="../ui/widgets/vcard/vcard.ui" line="739"/>
-        <location filename="../build/ui/widgets/vcard/vCardUI_autogen/include/ui_vcard.h" line="632"/>
         <source>Web site</source>
         <translation>Веб сайт</translation>
     </message>
     <message>
-        <location filename="../ui/widgets/vcard/vcard.ui" line="798"/>
-        <location filename="../ui/widgets/vcard/vcard.ui" line="822"/>
-        <location filename="../build/ui/widgets/vcard/vCardUI_autogen/include/ui_vcard.h" line="635"/>
-        <location filename="../build/ui/widgets/vcard/vCardUI_autogen/include/ui_vcard.h" line="636"/>
         <source>Description</source>
         <translation>Описание</translation>
     </message>
     <message>
-        <location filename="../ui/widgets/vcard/vcard.ui" line="859"/>
-        <location filename="../build/ui/widgets/vcard/vCardUI_autogen/include/ui_vcard.h" line="610"/>
         <source>Set avatar</source>
         <translation>Установить иконку</translation>
     </message>
     <message>
-        <location filename="../ui/widgets/vcard/vcard.ui" line="868"/>
-        <location filename="../build/ui/widgets/vcard/vCardUI_autogen/include/ui_vcard.h" line="611"/>
         <source>Clear avatar</source>
         <translation>Убрать иконку</translation>
     </message>
     <message>
-        <location filename="../ui/widgets/vcard/vcard.cpp" line="93"/>
         <source>Account %1 card</source>
         <translation>Карточка учетной записи %1</translation>
     </message>
     <message>
-        <location filename="../ui/widgets/vcard/vcard.cpp" line="108"/>
         <source>Contact %1 card</source>
         <translation>Карточка контакта %1</translation>
     </message>
     <message>
-        <location filename="../ui/widgets/vcard/vcard.cpp" line="170"/>
         <source>Received %1 at %2</source>
         <translation>Получено %1 в %2</translation>
     </message>
     <message>
-        <location filename="../ui/widgets/vcard/vcard.cpp" line="221"/>
         <source>Chose your new avatar</source>
         <translation>Выберите новую иконку</translation>
     </message>
     <message>
-        <location filename="../ui/widgets/vcard/vcard.cpp" line="223"/>
         <source>Images (*.png *.jpg *.jpeg)</source>
         <translation>Изображения (*.png *.jpg *.jpeg)</translation>
     </message>
     <message>
-        <location filename="../ui/widgets/vcard/vcard.cpp" line="310"/>
         <source>Add email address</source>
         <translation>Добавить адрес электронной почты</translation>
     </message>
     <message>
-        <location filename="../ui/widgets/vcard/vcard.cpp" line="320"/>
         <source>Unset this email as preferred</source>
         <translation>Убрать отметку &quot;предпочтительный&quot; с этого адреса</translation>
     </message>
     <message>
-        <location filename="../ui/widgets/vcard/vcard.cpp" line="323"/>
         <source>Set this email as preferred</source>
         <translation>Отметить этот адрес как &quot;предпочтительный&quot;</translation>
     </message>
     <message>
-        <location filename="../ui/widgets/vcard/vcard.cpp" line="328"/>
         <source>Remove selected email addresses</source>
         <translation>Удалить выбранные адреса</translation>
     </message>
     <message>
-        <location filename="../ui/widgets/vcard/vcard.cpp" line="333"/>
         <source>Copy selected emails to clipboard</source>
         <translation>Скопировать выбранные адреса в буфер обмена</translation>
     </message>
     <message>
-        <location filename="../ui/widgets/vcard/vcard.cpp" line="338"/>
         <source>Add phone number</source>
         <translation>Добавить номер телефона</translation>
     </message>
     <message>
-        <location filename="../ui/widgets/vcard/vcard.cpp" line="348"/>
         <source>Unset this phone as preferred</source>
         <translation>Убрать отметку &quot;предпочтительный&quot; с этого номера</translation>
     </message>
     <message>
-        <location filename="../ui/widgets/vcard/vcard.cpp" line="351"/>
         <source>Set this phone as preferred</source>
         <translation>Отметить этот номер как &quot;предпочтительный&quot;</translation>
     </message>
     <message>
-        <location filename="../ui/widgets/vcard/vcard.cpp" line="356"/>
         <source>Remove selected phone numbers</source>
         <translation>Удалить выбранные телефонные номера</translation>
     </message>
     <message>
-        <location filename="../ui/widgets/vcard/vcard.cpp" line="361"/>
         <source>Copy selected phones to clipboard</source>
         <translation>Скопировать выбранные телефонные номера в буфер обмена</translation>
     </message>
diff --git a/ui/models/abstractparticipant.cpp b/ui/models/abstractparticipant.cpp
index 53d8b2d..029527d 100644
--- a/ui/models/abstractparticipant.cpp
+++ b/ui/models/abstractparticipant.cpp
@@ -22,7 +22,7 @@ using namespace Models;
 
 Models::AbstractParticipant::AbstractParticipant(Models::Item::Type p_type, const QMap<QString, QVariant>& data, Models::Item* parentItem):
     Item(p_type, data, parentItem),
-    availability(Shared::offline),
+    availability(Shared::Availability::offline),
     lastActivity(data.value("lastActivity").toDateTime()),
     status(data.value("status").toString())
 {
@@ -58,7 +58,7 @@ QVariant Models::AbstractParticipant::data(int column) const
         case 1:
             return lastActivity;
         case 2:
-            return availability;
+            return QVariant::fromValue(availability);
         case 3:
             return status;
         default:
@@ -91,15 +91,9 @@ void Models::AbstractParticipant::setAvailability(Shared::Availability p_avail)
 
 void Models::AbstractParticipant::setAvailability(unsigned int avail)
 {
-    if (avail <= Shared::availabilityHighest) {
-        Shared::Availability state = static_cast<Shared::Availability>(avail);
-        setAvailability(state);
-    } else {
-        qDebug("An attempt to set wrong state to the contact");
-    }
+    setAvailability(Shared::Global::fromInt<Shared::Availability>(avail));
 }
 
-
 void Models::AbstractParticipant::setLastActivity(const QDateTime& p_time)
 {
     if (lastActivity != p_time) {
diff --git a/ui/models/abstractparticipant.h b/ui/models/abstractparticipant.h
index 86d5629..cb20788 100644
--- a/ui/models/abstractparticipant.h
+++ b/ui/models/abstractparticipant.h
@@ -21,8 +21,12 @@
 
 
 #include "item.h"
-#include "../../global.h"
+#include "shared/enums.h"
+#include "shared/icons.h"
+#include "shared/global.h"
+
 #include <QIcon>
+#include <QDateTime>
 
 namespace Models {
 
diff --git a/ui/models/account.cpp b/ui/models/account.cpp
index eeb8731..8d9774e 100644
--- a/ui/models/account.cpp
+++ b/ui/models/account.cpp
@@ -27,8 +27,8 @@ Models::Account::Account(const QMap<QString, QVariant>& data, Models::Item* pare
     resource(data.value("resource").toString()),
     error(data.value("error").toString()),
     avatarPath(data.value("avatarPath").toString()),
-    state(Shared::disconnected),
-    availability(Shared::offline)
+    state(Shared::ConnectionState::disconnected),
+    availability(Shared::Availability::offline)
 {
     QMap<QString, QVariant>::const_iterator sItr = data.find("state");
     if (sItr != data.end()) {
@@ -49,7 +49,7 @@ void Models::Account::setState(Shared::ConnectionState p_state)
     if (state != p_state) {
         state = p_state;
         changed(2);
-        if (state == Shared::disconnected) {
+        if (state == Shared::ConnectionState::disconnected) {
             toOfflineState();
         }
     }
@@ -57,22 +57,12 @@ void Models::Account::setState(Shared::ConnectionState p_state)
 
 void Models::Account::setAvailability(unsigned int p_state)
 {
-    if (p_state <= Shared::availabilityHighest) {
-        Shared::Availability state = static_cast<Shared::Availability>(p_state);
-        setAvailability(state);
-    } else {
-        qDebug() << "An attempt to set invalid availability " << p_state << " to the account " << name;
-    }
+    setAvailability(Shared::Global::fromInt<Shared::Availability>(p_state));
 }
 
 void Models::Account::setState(unsigned int p_state)
 {
-    if (p_state <= Shared::subscriptionStateHighest) {
-        Shared::ConnectionState state = static_cast<Shared::ConnectionState>(p_state);
-        setState(state);
-    } else {
-        qDebug() << "An attempt to set invalid subscription state " << p_state << " to the account " << name;
-    }
+    setState(Shared::Global::fromInt<Shared::ConnectionState>(p_state));
 }
 
 Shared::Availability Models::Account::getAvailability() const
@@ -90,10 +80,10 @@ void Models::Account::setAvailability(Shared::Availability p_avail)
 
 QIcon Models::Account::getStatusIcon(bool big) const
 {
-    if (state == Shared::connected) {
+    if (state == Shared::ConnectionState::connected) {
         return Shared::availabilityIcon(availability, big);
-    } else if (state == Shared::disconnected) {
-        return Shared::availabilityIcon(Shared::offline, big);
+    } else if (state == Shared::ConnectionState::disconnected) {
+        return Shared::availabilityIcon(Shared::Availability::offline, big);
     } else {
         return Shared::connectionStateIcon(state);
     }
@@ -152,7 +142,7 @@ QVariant Models::Account::data(int column) const
         case 1:
             return server;
         case 2:
-            return QCoreApplication::translate("Global", Shared::connectionStateNames[state].toLatin1());
+            return Shared::Global::getName(state);
         case 3:
             return error;
         case 4:
@@ -160,7 +150,7 @@ QVariant Models::Account::data(int column) const
         case 5:
             return password;
         case 6:
-            return QCoreApplication::translate("Global", Shared::availabilityNames[availability].toLatin1());
+            return Shared::Global::getName(availability);
         case 7:
             return resource;
         case 8:
@@ -226,7 +216,7 @@ void Models::Account::setError(const QString& p_resource)
 
 void Models::Account::toOfflineState()
 {
-    setAvailability(Shared::offline);
+    setAvailability(Shared::Availability::offline);
     Item::toOfflineState();
 }
 
diff --git a/ui/models/account.h b/ui/models/account.h
index e114699..428b049 100644
--- a/ui/models/account.h
+++ b/ui/models/account.h
@@ -19,7 +19,10 @@
 #ifndef MODELS_ACCOUNT_H
 #define MODELS_ACCOUNT_H
 
-#include "../../global.h"
+#include "shared/enums.h"
+#include "shared/utils.h"
+#include "shared/icons.h"
+#include "shared/global.h"
 #include "item.h"
 #include <QVariant>
 #include <QIcon>
diff --git a/ui/models/accounts.cpp b/ui/models/accounts.cpp
index 79ed159..f5ffce8 100644
--- a/ui/models/accounts.cpp
+++ b/ui/models/accounts.cpp
@@ -17,7 +17,7 @@
  */
 
 #include "accounts.h"
-#include "../../global.h"
+#include "shared/icons.h"
 
 #include <QIcon>
 #include <QDebug>
diff --git a/ui/models/contact.cpp b/ui/models/contact.cpp
index b968ce4..9cd011a 100644
--- a/ui/models/contact.cpp
+++ b/ui/models/contact.cpp
@@ -23,8 +23,8 @@
 Models::Contact::Contact(const QString& p_jid ,const QMap<QString, QVariant> &data, Item *parentItem):
     Item(Item::contact, data, parentItem),
     jid(p_jid),
-    availability(Shared::offline),
-    state(Shared::none),
+    availability(Shared::Availability::offline),
+    state(Shared::SubscriptionState::none),
     avatarState(Shared::Avatar::empty),
     presences(),
     messages(),
@@ -66,22 +66,12 @@ void Models::Contact::setJid(const QString p_jid)
 
 void Models::Contact::setAvailability(unsigned int p_state)
 {
-    if (p_state <= Shared::availabilityHighest) {
-        Shared::Availability state = static_cast<Shared::Availability>(p_state);
-        setAvailability(state);
-    } else {
-        qDebug() << "An attempt to set invalid availability " << p_state << " to the contact " << jid;
-    }
+    setAvailability(Shared::Global::fromInt<Shared::Availability>(p_state));
 }
 
 void Models::Contact::setState(unsigned int p_state)
 {
-    if (p_state <= Shared::subscriptionStateHighest) {
-        Shared::SubscriptionState state = static_cast<Shared::SubscriptionState>(p_state);
-        setState(state);
-    } else {
-        qDebug() << "An attempt to set invalid subscription state " << p_state << " to the contact " << jid;
-    }
+    setState(Shared::Global::fromInt<Shared::SubscriptionState>(p_state));
 }
 
 Shared::Availability Models::Contact::getAvailability() const
@@ -123,15 +113,15 @@ QVariant Models::Contact::data(int column) const
         case 1:
             return jid;
         case 2:
-            return state;
+            return QVariant::fromValue(state);
         case 3:
-            return availability;
+            return QVariant::fromValue(availability);
         case 4:
             return getMessagesCount();
         case 5:
             return getStatus();
         case 6:
-            return static_cast<quint8>(getAvatarState());
+            return QVariant::fromValue(getAvatarState());
         case 7:
             return getAvatarPath();
         default:
@@ -216,7 +206,7 @@ void Models::Contact::refresh()
         setAvailability(presence->getAvailability());
         setStatus(presence->getStatus());
     } else {
-        setAvailability(Shared::offline);
+        setAvailability(Shared::Availability::offline);
         setStatus("");
     }
     
@@ -258,7 +248,7 @@ QIcon Models::Contact::getStatusIcon(bool big) const
 {
     if (getMessagesCount() > 0) {
         return Shared::icon("mail-message", big);
-    } else if (state == Shared::both || state == Shared::to) {
+    } else if (state == Shared::SubscriptionState::both || state == Shared::SubscriptionState::to) {
         return Shared::availabilityIcon(availability, big);;
     } else {
         return Shared::subscriptionStateIcon(state, big);
@@ -276,7 +266,7 @@ void Models::Contact::addMessage(const Shared::Message& data)
             // the only issue is to find out when the sender is gone offline
             Presence* pr = new Presence({});
             pr->setName(res);
-            pr->setAvailability(Shared::invisible);
+            pr->setAvailability(Shared::Availability::invisible);
             pr->setLastActivity(QDateTime::currentDateTimeUtc());
             presences.insert(res, pr);
             appendChild(pr);
diff --git a/ui/models/contact.h b/ui/models/contact.h
index cffc53c..eb2be6b 100644
--- a/ui/models/contact.h
+++ b/ui/models/contact.h
@@ -21,7 +21,11 @@
 
 #include "item.h"
 #include "presence.h"
-#include "global.h"
+#include "shared/enums.h"
+#include "shared/message.h"
+#include "shared/icons.h"
+#include "shared/global.h"
+
 #include <QMap>
 #include <QIcon>
 #include <deque>
diff --git a/ui/models/group.cpp b/ui/models/group.cpp
index 5e4411e..76dc03e 100644
--- a/ui/models/group.cpp
+++ b/ui/models/group.cpp
@@ -96,7 +96,7 @@ unsigned int Models::Group::getOnlineContacts() const
     
     for (std::deque<Models::Item*>::const_iterator itr = childItems.begin(), end = childItems.end(); itr != end; ++itr) {
         Models::Contact* cnt = static_cast<Models::Contact*>(*itr);
-        if (cnt->getAvailability() != Shared::offline) {
+        if (cnt->getAvailability() != Shared::Availability::offline) {
             ++amount;
         }
     }
diff --git a/ui/models/item.cpp b/ui/models/item.cpp
index f90f5e6..1d0437f 100644
--- a/ui/models/item.cpp
+++ b/ui/models/item.cpp
@@ -238,7 +238,7 @@ Shared::Availability Models::Item::getAccountAvailability() const
 {
     const Account* acc = static_cast<const Account*>(getParentAccount());
     if (acc == 0) {
-        return Shared::offline;
+        return Shared::Availability::offline;
     }
     return acc->getAvailability();
 }
@@ -247,7 +247,7 @@ Shared::ConnectionState Models::Item::getAccountConnectionState() const
 {
     const Account* acc = static_cast<const Account*>(getParentAccount());
     if (acc == 0) {
-        return Shared::disconnected;
+        return Shared::ConnectionState::disconnected;
     }
     return acc->getState();
 }
diff --git a/ui/models/item.h b/ui/models/item.h
index 20f6f89..a936e34 100644
--- a/ui/models/item.h
+++ b/ui/models/item.h
@@ -25,7 +25,7 @@
 
 #include <deque>
 
-#include "../../global.h"
+#include "shared/enums.h"
 
 namespace Models {
 
diff --git a/ui/models/presence.cpp b/ui/models/presence.cpp
index bf2ef12..bf931e9 100644
--- a/ui/models/presence.cpp
+++ b/ui/models/presence.cpp
@@ -17,6 +17,7 @@
  */
 
 #include "presence.h"
+#include "shared/icons.h"
 
 Models::Presence::Presence(const QMap<QString, QVariant>& data, Item* parentItem):
     AbstractParticipant(Item::presence, data, parentItem),
diff --git a/ui/models/presence.h b/ui/models/presence.h
index 5073e7a..fc430f0 100644
--- a/ui/models/presence.h
+++ b/ui/models/presence.h
@@ -20,7 +20,9 @@
 #define MODELS_PRESENCE_H
 
 #include "abstractparticipant.h"
-#include "../../global.h"
+#include "shared/enums.h"
+#include "shared/message.h"
+
 #include <QDateTime>
 #include <QIcon>
 
diff --git a/ui/models/room.cpp b/ui/models/room.cpp
index afcf8e9..be92d41 100644
--- a/ui/models/room.cpp
+++ b/ui/models/room.cpp
@@ -17,6 +17,8 @@
  */
 
 #include "room.h"
+#include "shared/icons.h"
+
 #include <QIcon>
 #include <QDebug>
 
@@ -194,15 +196,15 @@ QIcon Models::Room::getStatusIcon(bool big) const
     } else {
         if (autoJoin) {
             if (joined) {
-                return Shared::connectionStateIcon(Shared::connected, big);
+                return Shared::connectionStateIcon(Shared::ConnectionState::connected, big);
             } else {
-                return Shared::connectionStateIcon(Shared::disconnected, big);
+                return Shared::connectionStateIcon(Shared::ConnectionState::disconnected, big);
             }
         } else {
             if (joined) {
-                return Shared::connectionStateIcon(Shared::connecting, big);
+                return Shared::connectionStateIcon(Shared::ConnectionState::connecting, big);
             } else {
-                return Shared::connectionStateIcon(Shared::error, big);
+                return Shared::connectionStateIcon(Shared::ConnectionState::error, big);
             }
         }
     }
diff --git a/ui/models/room.h b/ui/models/room.h
index 6a87a83..382b6e9 100644
--- a/ui/models/room.h
+++ b/ui/models/room.h
@@ -21,7 +21,8 @@
 
 #include "item.h"
 #include "participant.h"
-#include "global.h"
+#include "shared/enums.h"
+#include "shared/message.h"
 
 namespace Models {
 
diff --git a/ui/models/roster.cpp b/ui/models/roster.cpp
index 23e39af..609715f 100644
--- a/ui/models/roster.cpp
+++ b/ui/models/roster.cpp
@@ -181,7 +181,7 @@ QVariant Models::Roster::data (const QModelIndex& index, int role) const
             switch (item->type) {
                 case Item::account: {
                     Account* acc = static_cast<Account*>(item);
-                    result = QCoreApplication::translate("Global", Shared::availabilityNames[acc->getAvailability()].toLatin1());
+                    result = Shared::Global::getName(acc->getAvailability());
                 }
                     break;
                 case Item::contact: {
@@ -193,18 +193,18 @@ QVariant Models::Roster::data (const QModelIndex& index, int role) const
                     }
                     str += tr("Jabber ID: ") + contact->getJid() + "\n";
                     Shared::SubscriptionState ss = contact->getState();
-                    if (ss == Shared::both || ss == Shared::to) {
+                    if (ss == Shared::SubscriptionState::both || ss == Shared::SubscriptionState::to) {
                         Shared::Availability av = contact->getAvailability();
-                        str += tr("Availability: ") + QCoreApplication::translate("Global", Shared::availabilityNames[av].toLatin1());
-                        if (av != Shared::offline) {
+                        str += tr("Availability: ") + Shared::Global::getName(av);
+                        if (av != Shared::Availability::offline) {
                             QString s = contact->getStatus();
                             if (s.size() > 0) {
                                 str += "\n" + tr("Status: ") + s;
                             }
                         }
-                        str += "\n" + tr("Subscription: ") + QCoreApplication::translate("Global", Shared::subscriptionStateNames[ss].toLatin1());
+                        str += "\n" + tr("Subscription: ") + Shared::Global::getName(ss);
                     } else {
-                        str += tr("Subscription: ") + QCoreApplication::translate("Global", Shared::subscriptionStateNames[ss].toLatin1());
+                        str += tr("Subscription: ") + Shared::Global::getName(ss);
                     }
                     
                     result = str;
@@ -218,7 +218,7 @@ QVariant Models::Roster::data (const QModelIndex& index, int role) const
                         str += tr("New messages: ") + std::to_string(mc).c_str() + "\n";
                     }
                     Shared::Availability av = contact->getAvailability();
-                    str += tr("Availability: ") + QCoreApplication::translate("Global", Shared::availabilityNames[av].toLatin1());
+                    str += tr("Availability: ") + Shared::Global::getName(av);
                     QString s = contact->getStatus();
                     if (s.size() > 0) {
                         str += "\n" + tr("Status: ") + s;
@@ -231,18 +231,14 @@ QVariant Models::Roster::data (const QModelIndex& index, int role) const
                     Participant* p = static_cast<Participant*>(item);
                     QString str("");
                     Shared::Availability av = p->getAvailability();
-                    str += tr("Availability: ") + QCoreApplication::translate("Global", Shared::availabilityNames[av].toLatin1()) + "\n";
+                    str += tr("Availability: ") + Shared::Global::getName(av) + "\n";
                     QString s = p->getStatus();
                     if (s.size() > 0) {
                         str += tr("Status: ") + s + "\n";
                     }
                     
-                    str += tr("Affiliation: ") + 
-                    QCoreApplication::translate("Global", 
-                                                Shared::affiliationNames[static_cast<unsigned int>(p->getAffiliation())].toLatin1()) + "\n";
-                    str += tr("Role: ") + 
-                    QCoreApplication::translate("Global", 
-                                                Shared::roleNames[static_cast<unsigned int>(p->getRole())].toLatin1());
+                    str += tr("Affiliation: ") + Shared::Global::getName(p->getAffiliation()) + "\n";
+                    str += tr("Role: ") + Shared::Global::getName(p->getRole());
                     
                     result = str;
                 }
diff --git a/ui/models/roster.h b/ui/models/roster.h
index dde6370..50c6532 100644
--- a/ui/models/roster.h
+++ b/ui/models/roster.h
@@ -24,7 +24,8 @@
 #include <map>
 #include <QVector>
 
-#include "global.h"
+#include "shared/message.h"
+#include "shared/global.h"
 #include "accounts.h"
 #include "item.h"
 #include "account.h"
diff --git a/ui/squawk.cpp b/ui/squawk.cpp
index 601e999..ad002c3 100644
--- a/ui/squawk.cpp
+++ b/ui/squawk.cpp
@@ -41,11 +41,11 @@ Squawk::Squawk(QWidget *parent) :
     m_ui->roster->header()->setStretchLastSection(false);
     m_ui->roster->header()->setSectionResizeMode(0, QHeaderView::Stretch);
     
-    for (unsigned int i = Shared::availabilityLowest; i < Shared::availabilityHighest + 1; ++i) {
+    for (int i = static_cast<int>(Shared::availabilityLowest); i < static_cast<int>(Shared::availabilityHighest) + 1; ++i) {
         Shared::Availability av = static_cast<Shared::Availability>(i);
-        m_ui->comboBox->addItem(Shared::availabilityIcon(av), QCoreApplication::translate("Global", Shared::availabilityNames[av].toLatin1()));
+        m_ui->comboBox->addItem(Shared::availabilityIcon(av), Shared::Global::getName(av));
     }
-    m_ui->comboBox->setCurrentIndex(Shared::offline);
+    m_ui->comboBox->setCurrentIndex(static_cast<int>(Shared::Availability::offline));
     
     connect(m_ui->actionAccounts, &QAction::triggered, this, &Squawk::onAccounts);
     connect(m_ui->actionAddContact, &QAction::triggered, this, &Squawk::onNewContact);
@@ -173,25 +173,26 @@ void Squawk::newAccount(const QMap<QString, QVariant>& account)
 
 void Squawk::onComboboxActivated(int index)
 {
-    if (index != Shared::offline) {
+    Shared::Availability av = Shared::Global::fromInt<Shared::Availability>(index);
+    if (av != Shared::Availability::offline) {
         int size = rosterModel.accountsModel->rowCount(QModelIndex());
         if (size > 0) {
-            emit changeState(index);
+            emit changeState(av);
             for (int i = 0; i < size; ++i) {
                 Models::Account* acc = rosterModel.accountsModel->getAccount(i);
-                if (acc->getState() == Shared::disconnected) {
+                if (acc->getState() == Shared::ConnectionState::disconnected) {
                     emit connectAccount(acc->getName());
                 }
             }
         } else {
-            m_ui->comboBox->setCurrentIndex(Shared::offline);
+            m_ui->comboBox->setCurrentIndex(static_cast<int>(Shared::Availability::offline));
         }
     } else {
-        emit changeState(index);
+        emit changeState(av);
         int size = rosterModel.accountsModel->rowCount(QModelIndex());
         for (int i = 0; i != size; ++i) {
             Models::Account* acc = rosterModel.accountsModel->getAccount(i);
-            if (acc->getState() != Shared::disconnected) {
+            if (acc->getState() != Shared::ConnectionState::disconnected) {
                 emit disconnectAccount(acc->getName());
             }
         }
@@ -273,9 +274,9 @@ void Squawk::removePresence(const QString& account, const QString& jid, const QS
     rosterModel.removePresence(account, jid, name);
 }
 
-void Squawk::stateChanged(int state)
+void Squawk::stateChanged(Shared::Availability state)
 {
-    m_ui->comboBox->setCurrentIndex(state);
+    m_ui->comboBox->setCurrentIndex(static_cast<int>(state));
 }
 
 void Squawk::onRosterItemDoubleClicked(const QModelIndex& item)
@@ -600,14 +601,14 @@ void Squawk::onRosterContextMenu(const QPoint& point)
     
         contextMenu->clear();
         bool hasMenu = false;
-        bool active = item->getAccountConnectionState() == Shared::connected;
+        bool active = item->getAccountConnectionState() == Shared::ConnectionState::connected;
         switch (item->type) {
             case Models::Item::account: {
                 Models::Account* acc = static_cast<Models::Account*>(item);
                 hasMenu = true;
                 QString name = acc->getName();
                 
-                if (acc->getState() != Shared::disconnected) {
+                if (acc->getState() != Shared::ConnectionState::disconnected) {
                     QAction* con = contextMenu->addAction(Shared::icon("network-disconnect"), tr("Disconnect"));
                     con->setEnabled(active);
                     connect(con, &QAction::triggered, [this, name]() {
@@ -644,8 +645,8 @@ void Squawk::onRosterContextMenu(const QPoint& point)
                 
                 Shared::SubscriptionState state = cnt->getState();
                 switch (state) {
-                    case Shared::both:
-                    case Shared::to: {
+                    case Shared::SubscriptionState::both:
+                    case Shared::SubscriptionState::to: {
                         QAction* unsub = contextMenu->addAction(Shared::icon("news-unsubscribe"), tr("Unsubscribe"));
                         unsub->setEnabled(active);
                         connect(unsub, &QAction::triggered, [this, cnt]() {
@@ -653,9 +654,9 @@ void Squawk::onRosterContextMenu(const QPoint& point)
                         });
                     }
                     break;
-                    case Shared::from:
-                    case Shared::unknown:
-                    case Shared::none: {
+                    case Shared::SubscriptionState::from:
+                    case Shared::SubscriptionState::unknown:
+                    case Shared::SubscriptionState::none: {
                         QAction* sub = contextMenu->addAction(Shared::icon("news-subscribe"), tr("Subscribe"));
                         sub->setEnabled(active);
                         connect(sub, &QAction::triggered, [this, cnt]() {
@@ -882,7 +883,7 @@ void Squawk::readSettings()
     if (settings.contains("availability")) {
         int avail = settings.value("availability").toInt();
         m_ui->comboBox->setCurrentIndex(avail);
-        emit stateChanged(avail);
+        emit stateChanged(Shared::Global::fromInt<Shared::Availability>(avail));
         
         int size = settings.beginReadArray("connectedAccounts");
         for (int i = 0; i < size; ++i) {
@@ -909,7 +910,7 @@ void Squawk::writeSettings()
     int size = rosterModel.accountsModel->rowCount(QModelIndex());
     for (int i = 0; i < size; ++i) {
         Models::Account* acc = rosterModel.accountsModel->getAccount(i);
-        if (acc->getState() != Shared::disconnected) {
+        if (acc->getState() != Shared::ConnectionState::disconnected) {
             settings.setArrayIndex(i);
             settings.setValue("name", acc->getName());
         }
diff --git a/ui/squawk.h b/ui/squawk.h
index a3fbcba..adfff1d 100644
--- a/ui/squawk.h
+++ b/ui/squawk.h
@@ -38,7 +38,7 @@
 #include "models/roster.h"
 #include "widgets/vcard/vcard.h"
 
-#include "global.h"
+#include "shared.h"
 
 namespace Ui {
 class Squawk;
@@ -61,7 +61,7 @@ signals:
     void removeAccountRequest(const QString&);
     void connectAccount(const QString&);
     void disconnectAccount(const QString&);
-    void changeState(int state);
+    void changeState(Shared::Availability state);
     void sendMessage(const QString& account, const Shared::Message& data);
     void sendMessage(const QString& account, const Shared::Message& data, const QString& path);
     void requestArchive(const QString& account, const QString& jid, int count, const QString& before);
@@ -93,7 +93,7 @@ public slots:
     void changeContact(const QString& account, const QString& jid, const QMap<QString, QVariant>& data);
     void addPresence(const QString& account, const QString& jid, const QString& name, const QMap<QString, QVariant>& data);
     void removePresence(const QString& account, const QString& jid, const QString& name);
-    void stateChanged(int state);
+    void stateChanged(Shared::Availability state);
     void accountMessage(const QString& account, const Shared::Message& data);
     void responseArchive(const QString& account, const QString& jid, const std::list<Shared::Message>& list);
     void addRoom(const QString& account, const QString jid, const QMap<QString, QVariant>& data);
diff --git a/ui/utils/message.cpp b/ui/utils/message.cpp
index eb69ca0..c5149ef 100644
--- a/ui/utils/message.cpp
+++ b/ui/utils/message.cpp
@@ -16,12 +16,12 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
+#include "message.h"
 #include <QDebug>
 #include <QMimeDatabase>
 #include <QPixmap>
 #include <QFileInfo>
 #include <QRegularExpression>
-#include "message.h"
 
 const QRegularExpression urlReg("(?<!<a\\shref=['\"])(?<!<img\\ssrc=['\"])("
                                 "(?:https?|ftp):\\/\\/"
@@ -346,7 +346,7 @@ void Message::setState()
 {
     Shared::Message::State state = msg.getState();
     QIcon q(Shared::icon(Shared::messageStateThemeIcons[static_cast<uint8_t>(state)]));
-    QString tt = QCoreApplication::translate("Global", Shared::messageStateNames[static_cast<uint8_t>(state)].toLatin1());
+    QString tt = Shared::Global::getName(state);
     if (state == Shared::Message::State::error) {
         QString errText = msg.getErrorText();
         if (errText.size() > 0) {
diff --git a/ui/utils/message.h b/ui/utils/message.h
index a885d5b..fc3f178 100644
--- a/ui/utils/message.h
+++ b/ui/utils/message.h
@@ -31,7 +31,9 @@
 #include <QUrl>
 #include <QMap>
 
-#include "global.h"
+#include "shared/message.h"
+#include "shared/icons.h"
+#include "shared/global.h"
 #include "resizer.h"
 #include "image.h"
 
diff --git a/ui/utils/messageline.cpp b/ui/utils/messageline.cpp
index 2077863..deb9a45 100644
--- a/ui/utils/messageline.cpp
+++ b/ui/utils/messageline.cpp
@@ -18,6 +18,7 @@
 
 #include "messageline.h"
 #include <QDebug>
+#include <QCoreApplication>
 #include <cmath>
 
 MessageLine::MessageLine(bool p_room, QWidget* parent):
diff --git a/ui/utils/messageline.h b/ui/utils/messageline.h
index 16eea21..277d429 100644
--- a/ui/utils/messageline.h
+++ b/ui/utils/messageline.h
@@ -26,7 +26,7 @@
 #include <QResizeEvent>
 #include <QIcon>
 
-#include "global.h"
+#include "shared/message.h"
 #include "message.h"
 #include "progress.h"
 
diff --git a/ui/utils/progress.cpp b/ui/utils/progress.cpp
index a028822..73e4d02 100644
--- a/ui/utils/progress.cpp
+++ b/ui/utils/progress.cpp
@@ -18,6 +18,8 @@
 
 #include "progress.h"
 
+#include "shared/icons.h"
+
 Progress::Progress(quint16 p_size, QWidget* parent):
     QWidget(parent),
     pixmap(new QGraphicsPixmapItem(Shared::icon("view-refresh", true).pixmap(p_size))),
diff --git a/ui/utils/progress.h b/ui/utils/progress.h
index c6501aa..bbfef40 100644
--- a/ui/utils/progress.h
+++ b/ui/utils/progress.h
@@ -27,8 +27,6 @@
 #include <QVariantAnimation>
 #include <QGridLayout>
 
-#include "../../global.h"
-
 /**
  * @todo write docs
  */
diff --git a/ui/widgets/accounts.cpp b/ui/widgets/accounts.cpp
index 62e9ed3..e6c3da1 100644
--- a/ui/widgets/accounts.cpp
+++ b/ui/widgets/accounts.cpp
@@ -115,7 +115,7 @@ void Accounts::updateConnectButton()
         bool allConnected = true;
         for (int i = 0; i < selectionSize && allConnected; ++i) {
             const Models::Account* mAcc = model->getAccount(sm->selectedRows().at(i).row());
-            allConnected = mAcc->getState() == Shared::connected;
+            allConnected = mAcc->getState() == Shared::ConnectionState::connected;
         }
         if (allConnected) {
             toDisconnect = true;
diff --git a/ui/widgets/chat.cpp b/ui/widgets/chat.cpp
index 41c79be..c9a41ad 100644
--- a/ui/widgets/chat.cpp
+++ b/ui/widgets/chat.cpp
@@ -58,7 +58,7 @@ void Chat::updateState()
 {
     Shared::Availability av = contact->getAvailability();
     statusIcon->setPixmap(Shared::availabilityIcon(av, true).pixmap(40));
-    statusIcon->setToolTip(QCoreApplication::translate("Global", Shared::availabilityNames[av].toLatin1()));
+    statusIcon->setToolTip(Shared::Global::getName(av));
 }
 
 void Chat::handleSendMessage(const QString& text)
diff --git a/ui/widgets/chat.h b/ui/widgets/chat.h
index 5c0eb63..66ac53a 100644
--- a/ui/widgets/chat.h
+++ b/ui/widgets/chat.h
@@ -20,7 +20,9 @@
 #define CHAT_H
 
 #include "conversation.h"
-#include "../models/contact.h"
+#include "ui/models/contact.h"
+#include "shared/icons.h"
+#include "shared/global.h"
 
 namespace Ui
 {
diff --git a/ui/widgets/conversation.cpp b/ui/widgets/conversation.cpp
index 952973d..13cb881 100644
--- a/ui/widgets/conversation.cpp
+++ b/ui/widgets/conversation.cpp
@@ -19,6 +19,7 @@
 #include "conversation.h"
 #include "ui_conversation.h"
 #include "ui/utils/dropshadoweffect.h"
+#include "shared/icons.h"
 
 #include <QDebug>
 #include <QScrollBar>
@@ -27,6 +28,7 @@
 #include <QMimeDatabase>
 #include <unistd.h>
 #include <QAbstractTextDocumentLayout>
+#include <QCoreApplication>
 
 Conversation::Conversation(bool muc, Models::Account* acc, const QString pJid, const QString pRes, QWidget* parent):
     QWidget(parent),
diff --git a/ui/widgets/conversation.h b/ui/widgets/conversation.h
index 64d7341..6ce8cad 100644
--- a/ui/widgets/conversation.h
+++ b/ui/widgets/conversation.h
@@ -23,7 +23,7 @@
 #include <QScopedPointer>
 #include <QMap>
 
-#include "global.h"
+#include "shared/message.h"
 #include "order.h"
 #include "ui/models/account.h"
 #include "ui/utils/messageline.h"
diff --git a/ui/widgets/vcard/emailsmodel.cpp b/ui/widgets/vcard/emailsmodel.cpp
index 9723672..994fcc3 100644
--- a/ui/widgets/vcard/emailsmodel.cpp
+++ b/ui/widgets/vcard/emailsmodel.cpp
@@ -18,6 +18,9 @@
 
 #include "emailsmodel.h"
 
+#include "shared/icons.h"
+#include <QCoreApplication>
+
 UI::VCard::EMailsModel::EMailsModel(bool p_edit, QObject* parent):
     QAbstractTableModel(parent),
     edit(p_edit),
diff --git a/ui/widgets/vcard/emailsmodel.h b/ui/widgets/vcard/emailsmodel.h
index 3ee3a02..bafbe9f 100644
--- a/ui/widgets/vcard/emailsmodel.h
+++ b/ui/widgets/vcard/emailsmodel.h
@@ -24,7 +24,7 @@
 
 #include <deque>
 
-#include "global.h"
+#include "shared/vcard.h"
 
 namespace UI {
 namespace VCard {
diff --git a/ui/widgets/vcard/phonesmodel.cpp b/ui/widgets/vcard/phonesmodel.cpp
index ee0ff75..d3cace8 100644
--- a/ui/widgets/vcard/phonesmodel.cpp
+++ b/ui/widgets/vcard/phonesmodel.cpp
@@ -18,6 +18,9 @@
 
 #include "phonesmodel.h"
 
+#include "shared/icons.h"
+#include <QCoreApplication>
+
 UI::VCard::PhonesModel::PhonesModel(bool p_edit, QObject* parent):
     QAbstractTableModel(parent),
     edit(p_edit),
diff --git a/ui/widgets/vcard/phonesmodel.h b/ui/widgets/vcard/phonesmodel.h
index f799f82..32d08b6 100644
--- a/ui/widgets/vcard/phonesmodel.h
+++ b/ui/widgets/vcard/phonesmodel.h
@@ -22,7 +22,7 @@
 #include <QAbstractTableModel>
 #include <QIcon>
 
-#include "global.h"
+#include "shared/vcard.h"
 
 namespace UI {
 namespace VCard {
diff --git a/ui/widgets/vcard/vcard.cpp b/ui/widgets/vcard/vcard.cpp
index 44b5aa3..e3aa1b9 100644
--- a/ui/widgets/vcard/vcard.cpp
+++ b/ui/widgets/vcard/vcard.cpp
@@ -18,7 +18,7 @@
 
 #include "vcard.h"
 #include "ui_vcard.h"
-
+#include "shared/icons.h"
 #include <QDebug>
 
 #include <algorithm>
diff --git a/ui/widgets/vcard/vcard.h b/ui/widgets/vcard/vcard.h
index 8346fc3..4d579e1 100644
--- a/ui/widgets/vcard/vcard.h
+++ b/ui/widgets/vcard/vcard.h
@@ -36,7 +36,7 @@
 
 #include <set>
 
-#include "global.h"
+#include "shared/vcard.h"
 #include "emailsmodel.h"
 #include "phonesmodel.h"
 #include "ui/utils/progress.h"

From 3477226367ea44ce5e9ebddd8d29952706b749d0 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Sat, 4 Apr 2020 19:40:32 +0300
Subject: [PATCH 056/281] first moves to safe pasword storing, preparing the
 structure

---
 core/account.cpp          | 13 +++++-
 core/account.h            | 11 ++++-
 core/squawk.cpp           | 88 ++++++++++++++++++++++++++----------
 core/squawk.h             | 26 +++++++++--
 main.cpp                  |  2 +-
 shared/enums.h            | 37 ++++++++-------
 shared/global.cpp         | 94 ++++++++++++++-------------------------
 shared/global.h           | 16 +++++++
 shared/message.h          |  2 +
 translations/squawk.ru.ts | 20 +++++++++
 ui/models/account.cpp     | 28 +++++++++++-
 ui/models/account.h       |  7 ++-
 ui/models/participant.cpp | 29 +++---------
 ui/models/participant.h   |  1 +
 ui/squawk.cpp             |  4 +-
 ui/squawk.h               |  2 +-
 ui/widgets/account.cpp    | 15 +++++--
 ui/widgets/account.h      |  4 +-
 ui/widgets/account.ui     | 18 ++++++--
 ui/widgets/accounts.cpp   | 14 +++---
 ui/widgets/accounts.h     | 10 ++---
 21 files changed, 288 insertions(+), 153 deletions(-)

diff --git a/core/account.cpp b/core/account.cpp
index 37a8c16..ec92e96 100644
--- a/core/account.cpp
+++ b/core/account.cpp
@@ -53,7 +53,8 @@ Account::Account(const QString& p_login, const QString& p_server, const QString&
     avatarType(),
     ownVCardRequestInProgress(false),
     network(p_net),
-    pendingStateMessages()
+    pendingStateMessages(),
+    passwordType(Shared::AccountPassword::plain)
 {
     config.setUser(p_login);
     config.setDomain(p_server);
@@ -266,6 +267,11 @@ QString Core::Account::getServer() const
     return config.domain();
 }
 
+Shared::AccountPassword Core::Account::getPasswordType() const
+{
+    return passwordType;
+}
+
 void Core::Account::onRosterReceived()
 {
     vm->requestClientVCard();         //TODO need to make sure server actually supports vCards
@@ -296,6 +302,11 @@ void Core::Account::onRosterItemAdded(const QString& bareJid)
     }
 }
 
+void Core::Account::setPasswordType(Shared::AccountPassword pt)
+{
+    passwordType = pt;
+}
+
 void Core::Account::onRosterItemChanged(const QString& bareJid)
 {
     std::map<QString, Contact*>::const_iterator itr = contacts.find(bareJid);
diff --git a/core/account.h b/core/account.h
index 1688fc9..386835c 100644
--- a/core/account.h
+++ b/core/account.h
@@ -55,7 +55,13 @@ class Account : public QObject
 {
     Q_OBJECT
 public:
-    Account(const QString& p_login, const QString& p_server, const QString& p_password, const QString& p_name, NetworkAccess* p_net, QObject* parent = 0);
+    Account(
+        const QString& p_login, 
+        const QString& p_server, 
+        const QString& p_password, 
+        const QString& p_name, 
+        NetworkAccess* p_net, 
+        QObject* parent = 0);
     ~Account();
     
     void connect();
@@ -70,6 +76,7 @@ public:
     QString getResource() const;
     QString getAvatarPath() const;
     Shared::Availability getAvailability() const;
+    Shared::AccountPassword getPasswordType() const;
     
     void setName(const QString& p_name);
     void setLogin(const QString& p_login);
@@ -77,6 +84,7 @@ public:
     void setPassword(const QString& p_password);
     void setResource(const QString& p_resource);
     void setAvailability(Shared::Availability avail);
+    void setPasswordType(Shared::AccountPassword pt);
     QString getFullJid() const;
     void sendMessage(Shared::Message data);
     void sendMessage(const Shared::Message& data, const QString& path);
@@ -158,6 +166,7 @@ private:
     bool ownVCardRequestInProgress;
     NetworkAccess* network;
     std::map<QString, QString> pendingStateMessages;
+    Shared::AccountPassword passwordType;
     
 private slots:
     void onClientConnected();
diff --git a/core/squawk.cpp b/core/squawk.cpp
index 387c685..e8dd1ae 100644
--- a/core/squawk.cpp
+++ b/core/squawk.cpp
@@ -26,7 +26,8 @@ Core::Squawk::Squawk(QObject* parent):
     QObject(parent),
     accounts(),
     amap(),
-    network()
+    network(),
+    waitingForAccounts(0)
 {
     connect(&network, &NetworkAccess::fileLocalPathResponse, this, &Squawk::fileLocalPathResponse);
     connect(&network, &NetworkAccess::downloadFileProgress, this, &Squawk::downloadFileProgress);
@@ -72,21 +73,7 @@ void Core::Squawk::start()
 {
     qDebug("Starting squawk core..");
     
-    QSettings settings;
-    settings.beginGroup("core");
-    int size = settings.beginReadArray("accounts");
-    for (int i = 0; i < size; ++i) {
-        settings.setArrayIndex(i);
-        addAccount(
-            settings.value("login").toString(), 
-            settings.value("server").toString(), 
-            settings.value("password").toString(), 
-            settings.value("name").toString(), 
-            settings.value("resource").toString()
-        );
-    }
-    settings.endArray();
-    settings.endGroup();
+    readSettings();
     network.start();
 }
 
@@ -98,10 +85,17 @@ void Core::Squawk::newAccountRequest(const QMap<QString, QVariant>& map)
     QString password = map.value("password").toString();
     QString resource = map.value("resource").toString();
     
-    addAccount(login, server, password, name, resource);
+    addAccount(login, server, password, name, resource, Shared::AccountPassword::plain);
 }
 
-void Core::Squawk::addAccount(const QString& login, const QString& server, const QString& password, const QString& name, const QString& resource)
+void Core::Squawk::addAccount(
+    const QString& login, 
+    const QString& server, 
+    const QString& password, 
+    const QString& name, 
+    const QString& resource,                          
+    Shared::AccountPassword passwordType
+)
 {
     QSettings settings;
     unsigned int reconnects = settings.value("reconnects", 2).toUInt();
@@ -109,6 +103,7 @@ void Core::Squawk::addAccount(const QString& login, const QString& server, const
     Account* acc = new Account(login, server, password, name, &network);
     acc->setResource(resource);
     acc->setReconnectTimes(reconnects);
+    acc->setPasswordType(passwordType);
     accounts.push_back(acc);
     amap.insert(std::make_pair(name, acc));
     
@@ -119,8 +114,10 @@ void Core::Squawk::addAccount(const QString& login, const QString& server, const
     connect(acc, &Account::addContact, this, &Squawk::onAccountAddContact);
     connect(acc, &Account::addGroup, this, &Squawk::onAccountAddGroup);
     connect(acc, &Account::removeGroup, this, &Squawk::onAccountRemoveGroup);
-    connect(acc, qOverload<const QString&, const QString&>(&Account::removeContact), this, qOverload<const QString&, const QString&>(&Squawk::onAccountRemoveContact));
-    connect(acc, qOverload<const QString&>(&Account::removeContact), this, qOverload<const QString&>(&Squawk::onAccountRemoveContact));
+    connect(acc, qOverload<const QString&, const QString&>(&Account::removeContact), 
+            this, qOverload<const QString&, const QString&>(&Squawk::onAccountRemoveContact));
+    connect(acc, qOverload<const QString&>(&Account::removeContact), 
+            this, qOverload<const QString&>(&Squawk::onAccountRemoveContact));
     connect(acc, &Account::changeContact, this, &Squawk::onAccountChangeContact);
     connect(acc, &Account::addPresence, this, &Squawk::onAccountAddPresence);
     connect(acc, &Account::removePresence, this, &Squawk::onAccountRemovePresence);
@@ -149,7 +146,8 @@ void Core::Squawk::addAccount(const QString& login, const QString& server, const
         {"state", QVariant::fromValue(Shared::ConnectionState::disconnected)},
         {"offline", QVariant::fromValue(Shared::Availability::offline)},
         {"error", ""},
-        {"avatarPath", acc->getAvatarPath()}
+        {"avatarPath", acc->getAvatarPath()},
+        {"passwordType", QVariant::fromValue(passwordType)}
     };
     
     emit newAccount(map);
@@ -486,8 +484,6 @@ void Core::Squawk::onAccountRemoveRoomPresence(const QString& jid, const QString
     emit removeRoomParticipant(acc->getName(), jid, nick);
 }
 
-
-
 void Core::Squawk::onAccountChangeMessage(const QString& jid, const QString& id, const QMap<QString, QVariant>& data)
 {
     Account* acc = static_cast<Account*>(sender());
@@ -574,3 +570,49 @@ void Core::Squawk::uploadVCard(const QString& account, const Shared::VCard& card
     itr->second->uploadVCard(card);
 }
 
+void Core::Squawk::readSettings()
+{
+    QSettings settings;
+    settings.beginGroup("core");
+    int size = settings.beginReadArray("accounts");
+    waitingForAccounts = size;
+    for (int i = 0; i < size; ++i) {
+        settings.setArrayIndex(i);
+        parseAccount(
+            settings.value("login").toString(), 
+            settings.value("server").toString(), 
+            settings.value("password", "").toString(), 
+            settings.value("name").toString(), 
+            settings.value("resource").toString(),
+            Shared::Global::fromInt<Shared::AccountPassword>(settings.value("passwordType", static_cast<int>(Shared::AccountPassword::plain)).toInt())
+        );
+    }
+    settings.endArray();
+    settings.endGroup();
+}
+
+void Core::Squawk::accountReady()
+{
+    --waitingForAccounts;
+    
+    if (waitingForAccounts == 0) {
+        emit ready();
+    }
+}
+
+void Core::Squawk::parseAccount(
+    const QString& login, 
+    const QString& server, 
+    const QString& password, 
+    const QString& name, 
+    const QString& resource, 
+    Shared::AccountPassword passwordType
+)
+{
+    switch (passwordType) {
+        case Shared::AccountPassword::plain:
+            addAccount(login, server, password, name, resource, passwordType);
+            accountReady();
+            break;
+    }
+}
diff --git a/core/squawk.h b/core/squawk.h
index 29e5b8c..6d5f7d9 100644
--- a/core/squawk.h
+++ b/core/squawk.h
@@ -45,6 +45,7 @@ public:
 
 signals:
     void quit();
+    void ready();
     void newAccount(const QMap<QString, QVariant>&);
     void changeAccount(const QString& account, const QMap<QString, QVariant>& data);
     void removeAccount(const QString& account);
@@ -109,11 +110,18 @@ private:
     AccountsMap amap;
     Shared::Availability state;
     NetworkAccess network;
-    
-private:
-    void addAccount(const QString& login, const QString& server, const QString& password, const QString& name, const QString& resource);
+    uint8_t waitingForAccounts;
     
 private slots:
+    void addAccount(
+        const QString& login, 
+        const QString& server, 
+        const QString& password, 
+        const QString& name, 
+        const QString& resource, 
+        Shared::AccountPassword passwordType
+    );
+    
     void onAccountConnectionStateChanged(Shared::ConnectionState state);
     void onAccountAvailabilityChanged(Shared::Availability state);
     void onAccountChanged(const QMap<QString, QVariant>& data);
@@ -135,6 +143,18 @@ private slots:
     void onAccountChangeRoomPresence(const QString& jid, const QString& nick, const QMap<QString, QVariant>& data);
     void onAccountRemoveRoomPresence(const QString& jid, const QString& nick);
     void onAccountChangeMessage(const QString& jid, const QString& id, const QMap<QString, QVariant>& data);
+    
+private:
+    void readSettings();
+    void accountReady();
+    void parseAccount(
+        const QString& login, 
+        const QString& server, 
+        const QString& password, 
+        const QString& name, 
+        const QString& resource, 
+        Shared::AccountPassword passwordType
+    );
 };
 
 }
diff --git a/main.cpp b/main.cpp
index 25de512..eeb7138 100644
--- a/main.cpp
+++ b/main.cpp
@@ -146,9 +146,9 @@ int main(int argc, char *argv[])
     QObject::connect(squawk, &Core::Squawk::uploadFileProgress, &w, &Squawk::fileProgress);
     QObject::connect(squawk, &Core::Squawk::uploadFileError, &w, &Squawk::fileError);
     QObject::connect(squawk, &Core::Squawk::responseVCard, &w, &Squawk::responseVCard);
+    QObject::connect(squawk, &Core::Squawk::ready, &w, &Squawk::readSettings);
     
     coreThread->start();
-    w.readSettings();
 
     int result = app.exec();
     
diff --git a/shared/enums.h b/shared/enums.h
index 158695c..bf55377 100644
--- a/shared/enums.h
+++ b/shared/enums.h
@@ -35,8 +35,8 @@ enum class ConnectionState {
 };
 Q_ENUM_NS(ConnectionState)
 static const std::deque<QString> connectionStateThemeIcons = {"state-offline", "state-sync", "state-ok", "state-error"};
-static const ConnectionState connectionStateHighest = ConnectionState::error;
-static const ConnectionState connectionStateLowest = ConnectionState::disconnected;
+static const ConnectionState ConnectionStateHighest = ConnectionState::error;
+static const ConnectionState ConnectionStateLowest = ConnectionState::disconnected;
 
 enum class Availability {
     online,
@@ -48,8 +48,8 @@ enum class Availability {
     offline
 };
 Q_ENUM_NS(Availability)
-static const Availability availabilityHighest = Availability::offline;
-static const Availability availabilityLowest = Availability::online;
+static const Availability AvailabilityHighest = Availability::offline;
+static const Availability AvailabilityLowest = Availability::online;
 static const std::deque<QString> availabilityThemeIcons = {
     "user-online",
     "user-away",
@@ -59,7 +59,6 @@ static const std::deque<QString> availabilityThemeIcons = {
     "user-invisible",
     "user-offline"
 };
-static const std::deque<QString> availabilityNames = {"Online", "Away", "Absent", "Busy", "Chatty", "Invisible", "Offline"};
 
 enum class SubscriptionState {
     none,
@@ -69,10 +68,9 @@ enum class SubscriptionState {
     unknown
 };
 Q_ENUM_NS(SubscriptionState)
-static const SubscriptionState subscriptionStateHighest = SubscriptionState::unknown;
-static const SubscriptionState subscriptionStateLowest = SubscriptionState::none;
+static const SubscriptionState SubscriptionStateHighest = SubscriptionState::unknown;
+static const SubscriptionState SubscriptionStateLowest = SubscriptionState::none;
 static const std::deque<QString> subscriptionStateThemeIcons = {"edit-none", "arrow-down-double", "arrow-up-double", "dialog-ok", "question"};
-static const std::deque<QString> subscriptionStateNames = {"None", "From", "To", "Both", "Unknown"};
 
 enum class Affiliation {
     unspecified, 
@@ -83,9 +81,8 @@ enum class Affiliation {
     owner 
 };
 Q_ENUM_NS(Affiliation)
-static const Affiliation affiliationHighest = Affiliation::owner;
-static const Affiliation affiliationLowest = Affiliation::unspecified;
-static const std::deque<QString> affiliationNames = {"Unspecified", "Outcast", "Nobody", "Member", "Admin", "Owner"};
+static const Affiliation AffiliationHighest = Affiliation::owner;
+static const Affiliation AffiliationLowest = Affiliation::unspecified;
 
 enum class Role { 
     unspecified, 
@@ -95,9 +92,8 @@ enum class Role {
     moderator 
 };
 Q_ENUM_NS(Role)
-static const Role roleHighest = Role::moderator;
-static const Role roleLowest = Role::unspecified;
-static const std::deque<QString> roleNames = {"Unspecified", "Nobody", "Visitor", "Participant", "Moderator"};
+static const Role RoleHighest = Role::moderator;
+static const Role RoleLowest = Role::unspecified;
 
 enum class Avatar {
     empty,
@@ -105,10 +101,21 @@ enum class Avatar {
     valid
 };
 Q_ENUM_NS(Avatar)
+static const Avatar AvatarHighest = Avatar::valid; 
+static const Avatar AvatarLowest = Avatar::empty; 
 
 
-static const std::deque<QString> messageStateNames = {"Pending", "Sent", "Delivered", "Error"};
 static const std::deque<QString> messageStateThemeIcons = {"state-offline", "state-sync", "state-ok", "state-error"};
 
+enum class AccountPassword {
+    plain,
+    jammed,
+    kwallet,
+    alwaysAsk
+};
+Q_ENUM_NS(AccountPassword)
+static const AccountPassword AccountPasswordHighest = AccountPassword::alwaysAsk;
+static const AccountPassword AccountPasswordLowest = AccountPassword::plain;
+
 }
 #endif // SHARED_ENUMS_H
diff --git a/shared/global.cpp b/shared/global.cpp
index 4d3399a..81bee3f 100644
--- a/shared/global.cpp
+++ b/shared/global.cpp
@@ -65,6 +65,12 @@ Shared::Global::Global():
         tr("Sent"), 
         tr("Delivered"), 
         tr("Error")
+    }),
+    accountPassword({
+        tr("Plain"),
+        tr("Jammed"),
+        tr("KWallet"),
+        tr("Always Ask")
     })
 {
     if (instance != 0) {
@@ -81,90 +87,56 @@ Shared::Global * Shared::Global::getInstance()
 
 QString Shared::Global::getName(Message::State rl)
 {
-    return instance->messageState[int(rl)];
+    return instance->messageState[static_cast<int>(rl)];
 }
 
 QString Shared::Global::getName(Shared::Affiliation af)
 {
-    return instance->affiliation[int(af)];
+    return instance->affiliation[static_cast<int>(af)];
 }
 
 QString Shared::Global::getName(Shared::Availability av)
 {
-    return instance->availability[int(av)];
+    return instance->availability[static_cast<int>(av)];
 }
 
 QString Shared::Global::getName(Shared::ConnectionState cs)
 {
-    return instance->connectionState[int(cs)];
+    return instance->connectionState[static_cast<int>(cs)];
 }
 
 QString Shared::Global::getName(Shared::Role rl)
 {
-    return instance->role[int(rl)];
+    return instance->role[static_cast<int>(rl)];
 }
 
 QString Shared::Global::getName(Shared::SubscriptionState ss)
 {
-    return instance->subscriptionState[int(ss)];
+    return instance->subscriptionState[static_cast<int>(ss)];
 }
 
-template<> 
-Shared::Availability Shared::Global::fromInt(int src)
+QString Shared::Global::getName(Shared::AccountPassword ap)
 {
-    if (src < static_cast<int>(Shared::availabilityLowest) && src > static_cast<int>(Shared::availabilityHighest)) {
-        qDebug("An attempt to set invalid availability to Squawk core, skipping");
-    }
-    
-    return static_cast<Shared::Availability>(src);
+    return instance->accountPassword[static_cast<int>(ap)];
 }
 
-template<> 
-Shared::Availability Shared::Global::fromInt(unsigned int src)
-{
-    if (src < static_cast<int>(Shared::availabilityLowest) && src > static_cast<int>(Shared::availabilityHighest)) {
-        qDebug("An attempt to set invalid availability to Squawk core, skipping");
-    }
-    
-    return static_cast<Shared::Availability>(src);
-}
+#define FROM_INT_INPL(Enum)                                                                 \
+template<>                                                                                  \
+Enum Shared::Global::fromInt(int src)                                                       \
+{                                                                                           \
+    if (src < static_cast<int>(Enum##Lowest) && src > static_cast<int>(Enum##Highest)) {    \
+        throw EnumOutOfRange(#Enum);                                                        \
+    }                                                                                       \
+    return static_cast<Enum>(src);                                                          \
+}                                                                                           \
+template<>                                                                                  \
+Enum Shared::Global::fromInt(unsigned int src) {return fromInt<Enum>(static_cast<int>(src));}
 
-template<> 
-Shared::ConnectionState Shared::Global::fromInt(int src)
-{
-    if (src < static_cast<int>(Shared::connectionStateLowest) && src > static_cast<int>(Shared::connectionStateHighest)) {
-        qDebug("An attempt to set invalid availability to Squawk core, skipping");
-    }
-    
-    return static_cast<Shared::ConnectionState>(src);
-}
-
-template<> 
-Shared::ConnectionState Shared::Global::fromInt(unsigned int src)
-{
-    if (src < static_cast<int>(Shared::connectionStateLowest) && src > static_cast<int>(Shared::connectionStateHighest)) {
-        qDebug("An attempt to set invalid availability to Squawk core, skipping");
-    }
-    
-    return static_cast<Shared::ConnectionState>(src);
-}
-
-template<> 
-Shared::SubscriptionState Shared::Global::fromInt(int src)
-{
-    if (src < static_cast<int>(Shared::subscriptionStateLowest) && src > static_cast<int>(Shared::subscriptionStateHighest)) {
-        qDebug("An attempt to set invalid availability to Squawk core, skipping");
-    }
-    
-    return static_cast<Shared::SubscriptionState>(src);
-}
-
-template<> 
-Shared::SubscriptionState Shared::Global::fromInt(unsigned int src)
-{
-    if (src < static_cast<int>(Shared::subscriptionStateLowest) && src > static_cast<int>(Shared::subscriptionStateHighest)) {
-        qDebug("An attempt to set invalid availability to Squawk core, skipping");
-    }
-    
-    return static_cast<Shared::SubscriptionState>(src);
-}
+FROM_INT_INPL(Shared::Message::State)
+FROM_INT_INPL(Shared::Affiliation)
+FROM_INT_INPL(Shared::ConnectionState)
+FROM_INT_INPL(Shared::Role)
+FROM_INT_INPL(Shared::SubscriptionState)
+FROM_INT_INPL(Shared::AccountPassword)
+FROM_INT_INPL(Shared::Avatar)
+FROM_INT_INPL(Shared::Availability)
diff --git a/shared/global.h b/shared/global.h
index ef61611..3ea6147 100644
--- a/shared/global.h
+++ b/shared/global.h
@@ -21,6 +21,7 @@
 
 #include "enums.h"
 #include "message.h"
+#include "exception.h"
 
 #include <map>
 
@@ -42,6 +43,7 @@ namespace Shared {
         static QString getName(Affiliation af);
         static QString getName(Role rl);
         static QString getName(Message::State rl);
+        static QString getName(AccountPassword ap);
         
         const std::deque<QString> availability;
         const std::deque<QString> connectionState;
@@ -49,6 +51,7 @@ namespace Shared {
         const std::deque<QString> affiliation;
         const std::deque<QString> role;
         const std::deque<QString> messageState;
+        const std::deque<QString> accountPassword;
         
         template<typename T>
         static T fromInt(int src);
@@ -56,6 +59,19 @@ namespace Shared {
         template<typename T>
         static T fromInt(unsigned int src);
         
+        class EnumOutOfRange: 
+        public Utils::Exception
+        {
+        public:
+            EnumOutOfRange(const std::string& p_name):Exception(), name(p_name) {}
+            
+            std::string getMessage() const{
+                return "An attempt to get enum " + name + " from integer out of range of that enum";
+            }
+        private:
+            std::string name;
+        };
+        
     private:
         static Global* instance;
     };
diff --git a/shared/message.h b/shared/message.h
index 98ef206..4a0d661 100644
--- a/shared/message.h
+++ b/shared/message.h
@@ -46,6 +46,8 @@ public:
         delivered,
         error
     };
+    static const State StateHighest = State::error;
+    static const State StateLowest = State::pending;
     
     Message(Type p_type);
     Message();
diff --git a/translations/squawk.ru.ts b/translations/squawk.ru.ts
index 37686fb..8a733f2 100644
--- a/translations/squawk.ru.ts
+++ b/translations/squawk.ru.ts
@@ -65,6 +65,10 @@
         <translatorcomment>Ресурс по умолчанию</translatorcomment>
         <translation>QXmpp</translation>
     </message>
+    <message>
+        <source>Password storage</source>
+        <translation type="unfinished"></translation>
+    </message>
 </context>
 <context>
     <name>Accounts</name>
@@ -270,6 +274,22 @@ p, li { white-space: pre-wrap; }
         <source>Delivered</source>
         <translation>Доставлено</translation>
     </message>
+    <message>
+        <source>Plain</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Jammed</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>KWallet</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Always Ask</source>
+        <translation type="unfinished"></translation>
+    </message>
 </context>
 <context>
     <name>JoinConference</name>
diff --git a/ui/models/account.cpp b/ui/models/account.cpp
index 8d9774e..c581439 100644
--- a/ui/models/account.cpp
+++ b/ui/models/account.cpp
@@ -28,7 +28,8 @@ Models::Account::Account(const QMap<QString, QVariant>& data, Models::Item* pare
     error(data.value("error").toString()),
     avatarPath(data.value("avatarPath").toString()),
     state(Shared::ConnectionState::disconnected),
-    availability(Shared::Availability::offline)
+    availability(Shared::Availability::offline),
+    passwordType(Shared::AccountPassword::plain)
 {
     QMap<QString, QVariant>::const_iterator sItr = data.find("state");
     if (sItr != data.end()) {
@@ -155,6 +156,8 @@ QVariant Models::Account::data(int column) const
             return resource;
         case 8:
             return avatarPath;
+        case 9:
+            return Shared::Global::getName(passwordType);
         default:
             return QVariant();
     }
@@ -162,7 +165,7 @@ QVariant Models::Account::data(int column) const
 
 int Models::Account::columnCount() const
 {
-    return 9;
+    return 10;
 }
 
 void Models::Account::update(const QString& field, const QVariant& value)
@@ -185,6 +188,8 @@ void Models::Account::update(const QString& field, const QVariant& value)
         setError(value.toString());
     } else if (field == "avatarPath") {
         setAvatarPath(value.toString());
+    } else if (field == "passwordType") {
+        setPasswordType(value.toUInt());
     }
 }
 
@@ -240,3 +245,22 @@ QString Models::Account::getFullJid() const
 {
     return getBareJid() + "/" + resource;
 }
+
+Shared::AccountPassword Models::Account::getPasswordType() const
+{
+    return passwordType;
+}
+
+void Models::Account::setPasswordType(Shared::AccountPassword pt)
+{
+    if (passwordType != pt) {
+        passwordType = pt;
+        changed(9);
+    }
+}
+
+void Models::Account::setPasswordType(unsigned int pt)
+{
+    setPasswordType(Shared::Global::fromInt<Shared::AccountPassword>(pt));
+}
+
diff --git a/ui/models/account.h b/ui/models/account.h
index 428b049..d2bb79f 100644
--- a/ui/models/account.h
+++ b/ui/models/account.h
@@ -19,11 +19,11 @@
 #ifndef MODELS_ACCOUNT_H
 #define MODELS_ACCOUNT_H
 
+#include "item.h"
 #include "shared/enums.h"
 #include "shared/utils.h"
 #include "shared/icons.h"
 #include "shared/global.h"
-#include "item.h"
 #include <QVariant>
 #include <QIcon>
 
@@ -60,6 +60,10 @@ namespace Models {
         void setAvailability(unsigned int p_avail);
         Shared::Availability getAvailability() const;
         
+        void setPasswordType(Shared::AccountPassword pt);
+        void setPasswordType(unsigned int pt);
+        Shared::AccountPassword getPasswordType() const;
+        
         QIcon getStatusIcon(bool big = false) const;
         
         QVariant data(int column) const override;
@@ -79,6 +83,7 @@ namespace Models {
         QString avatarPath;
         Shared::ConnectionState state;
         Shared::Availability availability;
+        Shared::AccountPassword passwordType;
         
     protected slots:
         void toOfflineState() override;
diff --git a/ui/models/participant.cpp b/ui/models/participant.cpp
index 3939888..dc42c07 100644
--- a/ui/models/participant.cpp
+++ b/ui/models/participant.cpp
@@ -58,11 +58,11 @@ QVariant Models::Participant::data(int column) const
 {
     switch (column) {
         case 4:
-            return static_cast<uint8_t>(affiliation);
+            return QVariant::fromValue(affiliation);
         case 5:
-            return static_cast<uint8_t>(role);
+            return QVariant::fromValue(role);
         case 6:
-            return static_cast<quint8>(getAvatarState());
+            return QVariant::fromValue(getAvatarState());
         case 7:
             return getAvatarPath();
         default:
@@ -100,12 +100,7 @@ void Models::Participant::setAffiliation(Shared::Affiliation p_aff)
 
 void Models::Participant::setAffiliation(unsigned int aff)
 {
-    if (aff <= static_cast<uint8_t>(Shared::affiliationHighest)) {
-        Shared::Affiliation affil = static_cast<Shared::Affiliation>(aff);
-        setAffiliation(affil);
-    } else {
-        qDebug() << "An attempt to set wrong affiliation" << aff << "to the room participant" << name;
-    }
+    setAffiliation(Shared::Global::fromInt<Shared::Affiliation>(aff));
 }
 
 Shared::Role Models::Participant::getRole() const
@@ -123,12 +118,7 @@ void Models::Participant::setRole(Shared::Role p_role)
 
 void Models::Participant::setRole(unsigned int p_role)
 {
-    if (p_role <= static_cast<uint8_t>(Shared::roleHighest)) {
-        Shared::Role r = static_cast<Shared::Role>(p_role);
-        setRole(r);
-    } else {
-        qDebug() << "An attempt to set wrong role" << p_role << "to the room participant" << name;
-    }
+    setRole(Shared::Global::fromInt<Shared::Role>(p_role));
 }
 
 QString Models::Participant::getAvatarPath() const
@@ -158,11 +148,4 @@ void Models::Participant::setAvatarState(Shared::Avatar p_state)
 }
 
 void Models::Participant::setAvatarState(unsigned int p_state)
-{
-    if (p_state <= static_cast<quint8>(Shared::Avatar::valid)) {
-        Shared::Avatar state = static_cast<Shared::Avatar>(p_state);
-        setAvatarState(state);
-    } else {
-        qDebug() << "An attempt to set invalid avatar state" << p_state << "to the room participant" << name << ", skipping";
-    }
-}
+{setAvatarState(Shared::Global::fromInt<Shared::Avatar>(p_state));}
diff --git a/ui/models/participant.h b/ui/models/participant.h
index a93cb6d..6666d84 100644
--- a/ui/models/participant.h
+++ b/ui/models/participant.h
@@ -20,6 +20,7 @@
 #define MODELS_PARTICIPANT_H
 
 #include "abstractparticipant.h"
+#include "shared/global.h"
 
 namespace Models {
 
diff --git a/ui/squawk.cpp b/ui/squawk.cpp
index ad002c3..6822dd2 100644
--- a/ui/squawk.cpp
+++ b/ui/squawk.cpp
@@ -41,7 +41,7 @@ Squawk::Squawk(QWidget *parent) :
     m_ui->roster->header()->setStretchLastSection(false);
     m_ui->roster->header()->setSectionResizeMode(0, QHeaderView::Stretch);
     
-    for (int i = static_cast<int>(Shared::availabilityLowest); i < static_cast<int>(Shared::availabilityHighest) + 1; ++i) {
+    for (int i = static_cast<int>(Shared::AvailabilityLowest); i < static_cast<int>(Shared::AvailabilityHighest) + 1; ++i) {
         Shared::Availability av = static_cast<Shared::Availability>(i);
         m_ui->comboBox->addItem(Shared::availabilityIcon(av), Shared::Global::getName(av));
     }
@@ -71,7 +71,7 @@ Squawk::~Squawk() {
 void Squawk::onAccounts()
 {
     if (accounts == 0) {
-        accounts = new Accounts(rosterModel.accountsModel, this);
+        accounts = new Accounts(rosterModel.accountsModel);
         accounts->setAttribute(Qt::WA_DeleteOnClose);
         connect(accounts, &Accounts::destroyed, this, &Squawk::onAccountsClosed);
         connect(accounts, &Accounts::newAccount, this, &Squawk::newAccountRequest);
diff --git a/ui/squawk.h b/ui/squawk.h
index adfff1d..64459ab 100644
--- a/ui/squawk.h
+++ b/ui/squawk.h
@@ -52,7 +52,6 @@ public:
     explicit Squawk(QWidget *parent = nullptr);
     ~Squawk() override;
     
-    void readSettings();
     void writeSettings();
     
 signals:
@@ -82,6 +81,7 @@ signals:
     void uploadVCard(const QString& account, const Shared::VCard& card);
     
 public slots:
+    void readSettings();
     void newAccount(const QMap<QString, QVariant>& account);
     void changeAccount(const QString& account, const QMap<QString, QVariant>& data);
     void removeAccount(const QString& account);
diff --git a/ui/widgets/account.cpp b/ui/widgets/account.cpp
index d42cca8..d417d4f 100644
--- a/ui/widgets/account.cpp
+++ b/ui/widgets/account.cpp
@@ -19,10 +19,17 @@
 #include "account.h"
 #include "ui_account.h"
 
-Account::Account()
-    : m_ui ( new Ui::Account )
+Account::Account(): 
+    QDialog(),
+    m_ui(new Ui::Account)
 {
-    m_ui->setupUi ( this );
+    m_ui->setupUi (this);
+    
+    for (int i = static_cast<int>(Shared::AccountPasswordLowest); i < static_cast<int>(Shared::AccountPasswordHighest) + 1; ++i) {
+        Shared::AccountPassword ap = static_cast<Shared::AccountPassword>(i);
+        m_ui->passwordType->addItem(Shared::Global::getName(ap));
+    }
+    m_ui->passwordType->setCurrentIndex(static_cast<int>(Shared::AccountPassword::plain));
 }
 
 Account::~Account()
@@ -37,6 +44,7 @@ QMap<QString, QVariant> Account::value() const
     map["server"] = m_ui->server->text();
     map["name"] = m_ui->name->text();
     map["resource"] = m_ui->resource->text();
+    map["passwordType"] = m_ui->passwordType->currentIndex();
     
     return map;
 }
@@ -53,4 +61,5 @@ void Account::setData(const QMap<QString, QVariant>& data)
     m_ui->server->setText(data.value("server").toString());
     m_ui->name->setText(data.value("name").toString());
     m_ui->resource->setText(data.value("resource").toString());
+    m_ui->passwordType->setCurrentIndex(data.value("passwordType").toInt());
 }
diff --git a/ui/widgets/account.h b/ui/widgets/account.h
index 2f41430..9732224 100644
--- a/ui/widgets/account.h
+++ b/ui/widgets/account.h
@@ -19,12 +19,14 @@
 #ifndef ACCOUNT_H
 #define ACCOUNT_H
 
-#include <QScopedPointer>
 #include <QDialog>
+#include <QScopedPointer>
 #include <QMap>
 #include <QString>
 #include <QVariant>
 
+#include "shared/global.h"
+
 namespace Ui
 {
 class Account;
diff --git a/ui/widgets/account.ui b/ui/widgets/account.ui
index bfd0926..28cb389 100644
--- a/ui/widgets/account.ui
+++ b/ui/widgets/account.ui
@@ -6,8 +6,8 @@
    <rect>
     <x>0</x>
     <y>0</y>
-    <width>395</width>
-    <height>272</height>
+    <width>438</width>
+    <height>342</height>
    </rect>
   </property>
   <property name="windowTitle">
@@ -114,14 +114,14 @@
        </property>
       </widget>
      </item>
-     <item row="4" column="0">
+     <item row="5" column="0">
       <widget class="QLabel" name="label_5">
        <property name="text">
         <string>Resource</string>
        </property>
       </widget>
      </item>
-     <item row="4" column="1">
+     <item row="5" column="1">
       <widget class="QLineEdit" name="resource">
        <property name="toolTip">
         <string>A resource name like &quot;Home&quot; or &quot;Work&quot;</string>
@@ -131,6 +131,16 @@
        </property>
       </widget>
      </item>
+     <item row="4" column="0">
+      <widget class="QLabel" name="label_6">
+       <property name="text">
+        <string>Password storage</string>
+       </property>
+      </widget>
+     </item>
+     <item row="4" column="1">
+      <widget class="QComboBox" name="passwordType"/>
+     </item>
     </layout>
    </item>
    <item>
diff --git a/ui/widgets/accounts.cpp b/ui/widgets/accounts.cpp
index e6c3da1..626915e 100644
--- a/ui/widgets/accounts.cpp
+++ b/ui/widgets/accounts.cpp
@@ -22,6 +22,7 @@
 #include <QDebug>
 
 Accounts::Accounts(Models::Accounts* p_model, QWidget *parent) :
+    QWidget(parent),
     m_ui(new Ui::Accounts),
     model(p_model),
     editing(false),
@@ -40,7 +41,7 @@ Accounts::Accounts(Models::Accounts* p_model, QWidget *parent) :
 
 Accounts::~Accounts() = default;
 
-void Accounts::onAddButton(bool clicked)
+void Accounts::onAddButton()
 {
     Account* acc = new Account();
     connect(acc, &Account::accepted, this, &Accounts::onAccountAccepted);
@@ -70,7 +71,7 @@ void Accounts::onAccountRejected()
     editing = false;
 }
 
-void Accounts::onEditButton(bool clicked)
+void Accounts::onEditButton()
 {
     Account* acc = new Account();
     
@@ -80,7 +81,8 @@ void Accounts::onEditButton(bool clicked)
         {"password", mAcc->getPassword()},
         {"server", mAcc->getServer()},
         {"name", mAcc->getName()},
-        {"resource", mAcc->getResource()}
+        {"resource", mAcc->getResource()},
+        {"passwordType", QVariant::fromValue(mAcc->getPasswordType())}
     });
     acc->lockId();
     connect(acc, &Account::accepted, this, &Accounts::onAccountAccepted);
@@ -89,7 +91,7 @@ void Accounts::onEditButton(bool clicked)
     acc->exec();
 }
 
-void Accounts::onSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected)
+void Accounts::onSelectionChanged()
 {
     int selectionSize = m_ui->tableView->selectionModel()->selection().size();
     if (selectionSize == 0) {
@@ -131,7 +133,7 @@ void Accounts::updateConnectButton()
     }
 }
 
-void Accounts::onConnectButton(bool clicked)
+void Accounts::onConnectButton()
 {
     QItemSelectionModel* sm = m_ui->tableView->selectionModel();
     int selectionSize = sm->selection().size();
@@ -145,7 +147,7 @@ void Accounts::onConnectButton(bool clicked)
     }
 }
 
-void Accounts::onDeleteButton(bool clicked)
+void Accounts::onDeleteButton()
 {
     QItemSelectionModel* sm = m_ui->tableView->selectionModel();
     int selectionSize = sm->selection().size();
diff --git a/ui/widgets/accounts.h b/ui/widgets/accounts.h
index 31dc9ee..9fd0b57 100644
--- a/ui/widgets/accounts.h
+++ b/ui/widgets/accounts.h
@@ -46,13 +46,13 @@ signals:
     void removeAccount(const QString&);
     
 private slots:
-    void onAddButton(bool clicked = 0);
-    void onEditButton(bool clicked = 0);
-    void onConnectButton(bool clicked = 0);
-    void onDeleteButton(bool clicked = 0);
+    void onAddButton();
+    void onEditButton();
+    void onConnectButton();
+    void onDeleteButton();
     void onAccountAccepted();
     void onAccountRejected();
-    void onSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected);
+    void onSelectionChanged();
     void updateConnectButton();
     
 private:

From 95f0d4008a12842fc2651f56575c782b12f51806 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Sun, 5 Apr 2020 16:25:27 +0300
Subject: [PATCH 057/281] minor fix about updating muc avatars

---
 core/account.cpp    |  6 +++---
 core/conference.cpp | 50 +++++++++++++++++++++++++++++++++------------
 core/rosteritem.cpp |  1 -
 3 files changed, 40 insertions(+), 17 deletions(-)

diff --git a/core/account.cpp b/core/account.cpp
index ec92e96..2f8238a 100644
--- a/core/account.cpp
+++ b/core/account.cpp
@@ -1374,13 +1374,13 @@ void Core::Account::addNewRoom(const QString& jid, const QString& nick, const QS
     bool hasAvatar = conf->readAvatarInfo(info);
     if (hasAvatar) {
         if (info.autogenerated) {
-            cData.insert("avatarState", static_cast<uint>(Shared::Avatar::valid));
+            cData.insert("avatarState", QVariant::fromValue(Shared::Avatar::autocreated));
         } else {
-            cData.insert("avatarState", static_cast<uint>(Shared::Avatar::autocreated));
+            cData.insert("avatarState", QVariant::fromValue(Shared::Avatar::valid));
         }
         cData.insert("avatarPath", conf->avatarPath() + "." + info.type);
     } else {
-        cData.insert("avatarState", static_cast<uint>(Shared::Avatar::empty));
+        cData.insert("avatarState", QVariant::fromValue(Shared::Avatar::empty));
         cData.insert("avatarPath", "");
         requestVCard(jid);
     }
diff --git a/core/conference.cpp b/core/conference.cpp
index f0b3b7d..d745227 100644
--- a/core/conference.cpp
+++ b/core/conference.cpp
@@ -134,17 +134,20 @@ void Core::Conference::onRoomParticipantAdded(const QString& p_name)
 {
     QStringList comps = p_name.split("/");
     QString resource = comps.back();
+    QXmppPresence pres = room->participantPresence(p_name);
+    QXmppMucItem mi = pres.mucItem();
     if (resource == jid) {
-        qDebug() << "Room" << jid << "is reporting of adding itself to the list participants. Not sure what to do with that yet, skipping";
-    } else {
-        QXmppPresence pres = room->participantPresence(p_name);
+        resource = "";
+    } 
+    
+    Archive::AvatarInfo info;
+    bool hasAvatar = readAvatarInfo(info, resource);
+    
+    if (resource.size() > 0) {
         QDateTime lastInteraction = pres.lastUserInteraction();
         if (!lastInteraction.isValid()) {
             lastInteraction = QDateTime::currentDateTimeUtc();
         }
-        QXmppMucItem mi = pres.mucItem();
-        Archive::AvatarInfo info;
-        bool hasAvatar = readAvatarInfo(info, resource);
         
         QMap<QString, QVariant> cData = {
             {"lastActivity", lastInteraction},
@@ -169,22 +172,43 @@ void Core::Conference::onRoomParticipantAdded(const QString& p_name)
         
         emit addParticipant(resource, cData);
     }
+    
+    switch (pres.vCardUpdateType()) {
+        case QXmppPresence::VCardUpdateNone:            //this presence has nothing to do with photo
+            break;
+        case QXmppPresence::VCardUpdateNotReady:        //let's say the photo didn't change here
+            break;
+        case QXmppPresence::VCardUpdateNoPhoto: {       //there is no photo, need to drop if any
+            if (!hasAvatar || !info.autogenerated) {
+                setAutoGeneratedAvatar(resource);
+            }
+        }         
+        break;
+        case QXmppPresence::VCardUpdateValidPhoto:{     //there is a photo, need to load
+            if (hasAvatar) {
+                if (info.autogenerated || info.hash != pres.photoHash()) {
+                    emit requestVCard(p_name);
+                }
+            } else {
+                emit requestVCard(p_name);
+            }
+            break;
+        }      
+    }
 }
 
 void Core::Conference::onRoomParticipantChanged(const QString& p_name)
 {
     QStringList comps = p_name.split("/");
     QString resource = comps.back();
-    if (resource == jid) {
-        qDebug() << "Room" << jid << "is reporting of changing his own presence. Not sure what to do with that yet, skipping";
-    } else {
-        QXmppPresence pres = room->participantPresence(p_name);
+    QXmppPresence pres = room->participantPresence(p_name);
+    QXmppMucItem mi = pres.mucItem();
+    handlePresence(pres);
+    if (resource != jid) {
         QDateTime lastInteraction = pres.lastUserInteraction();
         if (!lastInteraction.isValid()) {
             lastInteraction = QDateTime::currentDateTimeUtc();
         }
-        QXmppMucItem mi = pres.mucItem();
-        handlePresence(pres);
         
         emit changeParticipant(resource, {
             {"lastActivity", lastInteraction},
@@ -265,7 +289,7 @@ bool Core::Conference::setAutoGeneratedAvatar(const QString& resource)
     if (result && resource.size() != 0) {
         emit changeParticipant(resource, {
             {"avatarState", static_cast<uint>(Shared::Avatar::autocreated)},
-            {"availability", avatarPath(resource) + ".png"}
+            {"avatarPath", avatarPath(resource) + ".png"}
         });
     }
     
diff --git a/core/rosteritem.cpp b/core/rosteritem.cpp
index 59aa4a7..59b84f8 100644
--- a/core/rosteritem.cpp
+++ b/core/rosteritem.cpp
@@ -501,7 +501,6 @@ Shared::VCard Core::RosterItem::handleResponseVCard(const QXmppVCardIq& card, co
         path = avatarPath(resource) + ".png";
     }
     
-    
     vCard.setAvatarType(type);
     vCard.setAvatarPath(path);
     

From 7ce27d1c11d550fe4f423569eb3c2f8a4d6f0ddb Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Tue, 7 Apr 2020 23:33:03 +0300
Subject: [PATCH 058/281] pasword storing options: jammed an alwaysAsk,
 external lib for password jamming, changelog

---
 CHANGELOG.md                         |  74 ++++++++
 CMakeLists.txt                       |   2 +
 core/CMakeLists.txt                  |   1 +
 core/squawk.cpp                      |  75 +++++++-
 core/squawk.h                        |   5 +
 external/simpleCrypt/CMakeLists.txt  |  16 ++
 external/simpleCrypt/simplecrypt.cpp | 252 +++++++++++++++++++++++++++
 external/simpleCrypt/simplecrypt.h   | 225 ++++++++++++++++++++++++
 main.cpp                             |   2 +
 shared/enums.h                       |   6 +-
 shared/global.cpp                    |   4 +-
 ui/models/account.cpp                |   4 +
 ui/squawk.cpp                        |  70 ++++++--
 ui/squawk.h                          |  10 ++
 14 files changed, 728 insertions(+), 18 deletions(-)
 create mode 100644 CHANGELOG.md
 create mode 100644 external/simpleCrypt/CMakeLists.txt
 create mode 100644 external/simpleCrypt/simplecrypt.cpp
 create mode 100644 external/simpleCrypt/simplecrypt.h

diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..d281fb8
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,74 @@
+# Changelog
+
+## Squawk 0.1.4 (UNRELEASED)
+------------------------------
+### New features
+- several ways to manage your account password:
+  - store it in plain text with the config (like it always was)
+  - store it in config jammed (local hashing with the constant seed, not secure at all but might look like it is)
+  - ask the account password on each program launch
+  
+### Bug fixes
+- never updating MUC avatars now update
+- going offline related segfault fix
+
+
+## Squawk 0.1.3 (Mar 31, 2020)
+------------------------------
+### New features
+- delivery states for the messages
+- delivery receipts now work for real
+- avatars in conferences
+- edited messages now display correctly
+- restyling to get better look with different desktop themes
+
+### Bug fixes
+- clickable links now detects better
+- fixed lost messages that came with no ID
+- avatar related fixes
+- message carbons now get turned on only if the server supports them
+- progress spinner fix
+- files in dialog now have better comment
+
+
+## Squawk 0.1.2 (Dec 25, 2019)
+------------------------------
+### New features
+- icons in roster for conferences
+- pal avatar in dialog window
+- avatars next to every message in dialog windows (not in conferences yet)
+- roster window position and size now are stored in config
+- expanded accounts and groups are stored in config
+- availability (from top combobox) now is stored in config
+
+### Bug fixes
+- segfault when sending more then one attached file
+- wrong path and name of saving file
+- wrong message syntax when attaching file and writing text in the save message
+- problem with links highlighting in dialog
+- project building fixes
+
+
+## Squawk 0.1.1 (Nov 16, 2019)
+------------------------------
+A lot of bug fixes, memory leaks fixes
+### New features
+- exchange files via HTTP File Upload
+- download VCards of your contacts
+- upload your VCard with information about your contact phones email addresses, names, career information and avatar
+- avatars of your contacts in roster and in notifications
+
+
+## Squawk 0.0.5 (Oct 10, 2019)
+------------------------------
+### Features
+- chat directly
+- have multiple accounts
+- add contacts
+- remove contacts
+- assign contact to different groups
+- chat in MUCs
+- join MUCs, leave them, keep them subscribed or unsubscribed
+- download attachmets
+- local history
+- desktop notifications of new messages
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 3e9968c..aac3e75 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -74,6 +74,8 @@ endif()
 add_subdirectory(ui)
 add_subdirectory(core)
 
+add_subdirectory(external/simpleCrypt)
+
 target_link_libraries(squawk squawkUI)
 target_link_libraries(squawk squawkCORE)
 target_link_libraries(squawk uuid)
diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt
index fab36f2..ad37198 100644
--- a/core/CMakeLists.txt
+++ b/core/CMakeLists.txt
@@ -37,3 +37,4 @@ target_link_libraries(squawkCORE Qt5::Gui)
 target_link_libraries(squawkCORE Qt5::Xml)
 target_link_libraries(squawkCORE qxmpp)
 target_link_libraries(squawkCORE lmdb)
+target_link_libraries(squawkCORE simpleCrypt)
diff --git a/core/squawk.cpp b/core/squawk.cpp
index e8dd1ae..8c70cb8 100644
--- a/core/squawk.cpp
+++ b/core/squawk.cpp
@@ -52,14 +52,31 @@ void Core::Squawk::stop()
     QSettings settings;
     settings.beginGroup("core");
     settings.beginWriteArray("accounts");
+    SimpleCrypt crypto(passwordHash);
     for (std::deque<Account*>::size_type i = 0; i < accounts.size(); ++i) {
         settings.setArrayIndex(i);
         Account* acc = accounts[i];
+        
+        Shared::AccountPassword ap = acc->getPasswordType();
+        QString password;
+        
+        switch (ap) {
+            case Shared::AccountPassword::plain:
+                password = acc->getPassword();
+                break;
+            case Shared::AccountPassword::jammed:
+                password = crypto.encryptToString(acc->getPassword());
+                break;
+            default:
+                break;
+        }
+        
         settings.setValue("name", acc->getName());
         settings.setValue("server", acc->getServer());
         settings.setValue("login", acc->getLogin());
-        settings.setValue("password", acc->getPassword());
+        settings.setValue("password", password);
         settings.setValue("resource", acc->getResource());
+        settings.setValue("passwordType", static_cast<int>(ap));
     }
     settings.endArray();
     settings.endGroup();
@@ -318,12 +335,37 @@ void Core::Squawk::modifyAccountRequest(const QString& name, const QMap<QString,
     
     Core::Account* acc = itr->second;
     Shared::ConnectionState st = acc->getState();
+    QMap<QString, QVariant>::const_iterator mItr;
+    bool needToReconnect = false;
     
-    if (st != Shared::ConnectionState::disconnected) {
+    mItr = map.find("login");
+    if (mItr != map.end()) {
+        needToReconnect = acc->getLogin() != mItr->toString();
+    }
+    
+    if (!needToReconnect) {
+        mItr = map.find("password");
+        if (mItr != map.end()) {
+            needToReconnect = acc->getPassword() != mItr->toString();
+        }
+    }
+    if (!needToReconnect) {
+        mItr = map.find("server");
+        if (mItr != map.end()) {
+            needToReconnect = acc->getServer() != mItr->toString();
+        }
+    }
+    if (!needToReconnect) {
+        mItr = map.find("resource");
+        if (mItr != map.end()) {
+            needToReconnect = acc->getResource() != mItr->toString();
+        }
+    }
+    
+    if (needToReconnect && st != Shared::ConnectionState::disconnected) {
         acc->reconnect();
     }
     
-    QMap<QString, QVariant>::const_iterator mItr;
     mItr = map.find("login");
     if (mItr != map.end()) {
         acc->setLogin(mItr->toString());
@@ -344,6 +386,11 @@ void Core::Squawk::modifyAccountRequest(const QString& name, const QMap<QString,
         acc->setServer(mItr->toString());
     }
     
+    mItr = map.find("passwordType");
+    if (mItr != map.end()) {
+        acc->setPasswordType(Shared::Global::fromInt<Shared::AccountPassword>(mItr->toInt()));
+    }
+    
     emit changeAccount(name, map);
 }
 
@@ -570,6 +617,17 @@ void Core::Squawk::uploadVCard(const QString& account, const Shared::VCard& card
     itr->second->uploadVCard(card);
 }
 
+void Core::Squawk::responsePassword(const QString& account, const QString& password)
+{
+    AccountsMap::const_iterator itr = amap.find(account);
+    if (itr == amap.end()) {
+        qDebug() << "An attempt to set password to non existing account" << account << ", skipping";
+        return;
+    }
+    itr->second->setPassword(password);
+    accountReady();
+}
+
 void Core::Squawk::readSettings()
 {
     QSettings settings;
@@ -614,5 +672,16 @@ void Core::Squawk::parseAccount(
             addAccount(login, server, password, name, resource, passwordType);
             accountReady();
             break;
+        case Shared::AccountPassword::jammed: {
+            SimpleCrypt crypto(passwordHash);
+            QString decrypted = crypto.decryptToString(password);
+            addAccount(login, server, decrypted, name, resource, passwordType);
+            accountReady();
+        }
+            break;
+        case Shared::AccountPassword::alwaysAsk: 
+            addAccount(login, server, QString(), name, resource, passwordType);
+            emit requestPassword(name);
+            break;
     }
 }
diff --git a/core/squawk.h b/core/squawk.h
index 6d5f7d9..f96f7e8 100644
--- a/core/squawk.h
+++ b/core/squawk.h
@@ -32,6 +32,7 @@
 #include "shared/message.h"
 #include "shared/global.h"
 #include "networkaccess.h"
+#include "external/simpleCrypt/simplecrypt.h"
 
 namespace Core
 {
@@ -73,6 +74,7 @@ signals:
     void uploadFileProgress(const QString& messageId, qreal value);
     void responseVCard(const QString& jid, const Shared::VCard& card);
     void changeMessage(const QString& account, const QString& jid, const QString& id, const QMap<QString, QVariant>& data);
+    void requestPassword(const QString& account);
     
 public slots:
     void start();
@@ -101,6 +103,7 @@ public slots:
     void downloadFileRequest(const QString& messageId, const QString& url);
     void requestVCard(const QString& account, const QString& jid);
     void uploadVCard(const QString& account, const Shared::VCard& card);
+    void responsePassword(const QString& account, const QString& password);
     
 private:
     typedef std::deque<Account*> Accounts;
@@ -155,6 +158,8 @@ private:
         const QString& resource, 
         Shared::AccountPassword passwordType
     );
+    
+    static const quint64 passwordHash = 0x08d054225ac4871d;
 };
 
 }
diff --git a/external/simpleCrypt/CMakeLists.txt b/external/simpleCrypt/CMakeLists.txt
new file mode 100644
index 0000000..bdb62c6
--- /dev/null
+++ b/external/simpleCrypt/CMakeLists.txt
@@ -0,0 +1,16 @@
+cmake_minimum_required(VERSION 3.0)
+project(simplecrypt)
+
+set(CMAKE_AUTOMOC ON)
+
+find_package(Qt5Core CONFIG REQUIRED)
+
+set(simplecrypt_SRC
+    simplecrypt.cpp
+)
+
+# Tell CMake to create the helloworld executable
+add_library(simpleCrypt ${simplecrypt_SRC})
+
+# Use the Widgets module from Qt 5.
+target_link_libraries(simpleCrypt Qt5::Core)
diff --git a/external/simpleCrypt/simplecrypt.cpp b/external/simpleCrypt/simplecrypt.cpp
new file mode 100644
index 0000000..9bbec90
--- /dev/null
+++ b/external/simpleCrypt/simplecrypt.cpp
@@ -0,0 +1,252 @@
+/*
+ Copyright (c) 2011, Andre Somers
+ All rights reserved.
+ 
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ * Neither the name of the Rathenau Instituut, Andre Somers nor the
+ names of its contributors may be used to endorse or promote products
+ derived from this software without specific prior written permission.
+ 
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL ANDRE SOMERS BE LIABLE FOR ANY
+ DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ LOSS OF USE, DATA, OR #######; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#include "simplecrypt.h"
+#include <QByteArray>
+#include <QtDebug>
+#include <QtGlobal>
+#include <QDateTime>
+#include <QCryptographicHash>
+#include <QDataStream>
+
+SimpleCrypt::SimpleCrypt():
+m_key(0),
+m_compressionMode(CompressionAuto),
+m_protectionMode(ProtectionChecksum),
+m_lastError(ErrorNoError)
+{
+    qsrand(uint(QDateTime::currentMSecsSinceEpoch() & 0xFFFF));
+}
+
+SimpleCrypt::SimpleCrypt(quint64 key):
+m_key(key),
+m_compressionMode(CompressionAuto),
+m_protectionMode(ProtectionChecksum),
+m_lastError(ErrorNoError)
+{
+    qsrand(uint(QDateTime::currentMSecsSinceEpoch() & 0xFFFF));
+    splitKey();
+}
+
+void SimpleCrypt::setKey(quint64 key)
+{
+    m_key = key;
+    splitKey();
+}
+
+void SimpleCrypt::splitKey()
+{
+    m_keyParts.clear();
+    m_keyParts.resize(8);
+    for (int i=0;i<8;i++) {
+        quint64 part = m_key;
+        for (int j=i; j>0; j--)
+            part = part >> 8;
+        part = part & 0xff;
+        m_keyParts[i] = static_cast<char>(part);
+    }
+}
+
+QByteArray SimpleCrypt::encryptToByteArray(const QString& plaintext)
+{
+    QByteArray plaintextArray = plaintext.toUtf8();
+    return encryptToByteArray(plaintextArray);
+}
+
+QByteArray SimpleCrypt::encryptToByteArray(QByteArray plaintext)
+{
+    if (m_keyParts.isEmpty()) {
+        qWarning() << "No key set.";
+        m_lastError = ErrorNoKeySet;
+        return QByteArray();
+    }
+    
+    
+    QByteArray ba = plaintext;
+    
+    CryptoFlags flags = CryptoFlagNone;
+    if (m_compressionMode == CompressionAlways) {
+        ba = qCompress(ba, 9); //maximum compression
+        flags |= CryptoFlagCompression;
+    } else if (m_compressionMode == CompressionAuto) {
+        QByteArray compressed = qCompress(ba, 9);
+        if (compressed.count() < ba.count()) {
+            ba = compressed;
+            flags |= CryptoFlagCompression;
+        }
+    }
+    
+    QByteArray integrityProtection;
+    if (m_protectionMode == ProtectionChecksum) {
+        flags |= CryptoFlagChecksum;
+        QDataStream s(&integrityProtection, QIODevice::WriteOnly);
+        s << qChecksum(ba.constData(), ba.length());
+    } else if (m_protectionMode == ProtectionHash) {
+        flags |= CryptoFlagHash;
+        QCryptographicHash hash(QCryptographicHash::Sha1);
+        hash.addData(ba);
+        
+        integrityProtection += hash.result();
+    }
+    
+    //prepend a random char to the string
+    char randomChar = char(qrand() & 0xFF);
+    ba = randomChar + integrityProtection + ba;
+    
+    int pos(0);
+    char lastChar(0);
+    
+    int cnt = ba.count();
+    
+    while (pos < cnt) {
+        ba[pos] = ba.at(pos) ^ m_keyParts.at(pos % 8) ^ lastChar;
+        lastChar = ba.at(pos);
+        ++pos;
+    }
+    
+    QByteArray resultArray;
+    resultArray.append(char(0x03));  //version for future updates to algorithm
+    resultArray.append(char(flags)); //encryption flags
+    resultArray.append(ba);
+    
+    m_lastError = ErrorNoError;
+    return resultArray;
+}
+
+QString SimpleCrypt::encryptToString(const QString& plaintext)
+{
+    QByteArray plaintextArray = plaintext.toUtf8();
+    QByteArray cypher = encryptToByteArray(plaintextArray);
+    QString cypherString = QString::fromLatin1(cypher.toBase64());
+    return cypherString;
+}
+
+QString SimpleCrypt::encryptToString(QByteArray plaintext)
+{
+    QByteArray cypher = encryptToByteArray(plaintext);
+    QString cypherString = QString::fromLatin1(cypher.toBase64());
+    return cypherString;
+}
+
+QString SimpleCrypt::decryptToString(const QString &cyphertext)
+{
+    QByteArray cyphertextArray = QByteArray::fromBase64(cyphertext.toLatin1());
+    QByteArray plaintextArray = decryptToByteArray(cyphertextArray);
+    QString plaintext = QString::fromUtf8(plaintextArray, plaintextArray.size());
+    
+    return plaintext;
+}
+
+QString SimpleCrypt::decryptToString(QByteArray cypher)
+{
+    QByteArray ba = decryptToByteArray(cypher);
+    QString plaintext = QString::fromUtf8(ba, ba.size());
+    
+    return plaintext;
+}
+
+QByteArray SimpleCrypt::decryptToByteArray(const QString& cyphertext)
+{
+    QByteArray cyphertextArray = QByteArray::fromBase64(cyphertext.toLatin1());
+    QByteArray ba = decryptToByteArray(cyphertextArray);
+    
+    return ba;
+}
+
+QByteArray SimpleCrypt::decryptToByteArray(QByteArray cypher)
+{
+    if (m_keyParts.isEmpty()) {
+        qWarning() << "No key set.";
+        m_lastError = ErrorNoKeySet;
+        return QByteArray();
+    }
+    
+    QByteArray ba = cypher;
+    
+    if( cypher.count() < 3 )
+        return QByteArray();
+    
+    char version = ba.at(0);
+    
+    if (version !=3) {  //we only work with version 3
+        m_lastError = ErrorUnknownVersion;
+        qWarning() << "Invalid version or not a cyphertext.";
+        return QByteArray();
+    }
+    
+    CryptoFlags flags = CryptoFlags(ba.at(1));
+    
+    ba = ba.mid(2);
+    int pos(0);
+    int cnt(ba.count());
+    char lastChar = 0;
+    
+    while (pos < cnt) {
+        char currentChar = ba[pos];
+        ba[pos] = ba.at(pos) ^ lastChar ^ m_keyParts.at(pos % 8);
+        lastChar = currentChar;
+        ++pos;
+    }
+    
+    ba = ba.mid(1); //chop off the random number at the start
+    
+    bool integrityOk(true);
+    if (flags.testFlag(CryptoFlagChecksum)) {
+        if (ba.length() < 2) {
+            m_lastError = ErrorIntegrityFailed;
+            return QByteArray();
+        }
+        quint16 storedChecksum;
+        {
+            QDataStream s(&ba, QIODevice::ReadOnly);
+            s >> storedChecksum;
+        }
+        ba = ba.mid(2);
+        quint16 checksum = qChecksum(ba.constData(), ba.length());
+        integrityOk = (checksum == storedChecksum);
+    } else if (flags.testFlag(CryptoFlagHash)) {
+        if (ba.length() < 20) {
+            m_lastError = ErrorIntegrityFailed;
+            return QByteArray();
+        }
+        QByteArray storedHash = ba.left(20);
+        ba = ba.mid(20);
+        QCryptographicHash hash(QCryptographicHash::Sha1);
+        hash.addData(ba);
+        integrityOk = (hash.result() == storedHash);
+    }
+    
+    if (!integrityOk) {
+        m_lastError = ErrorIntegrityFailed;
+        return QByteArray();
+    }
+    
+    if (flags.testFlag(CryptoFlagCompression))
+        ba = qUncompress(ba);
+    
+    m_lastError = ErrorNoError;
+    return ba;
+}
diff --git a/external/simpleCrypt/simplecrypt.h b/external/simpleCrypt/simplecrypt.h
new file mode 100644
index 0000000..2a5906a
--- /dev/null
+++ b/external/simpleCrypt/simplecrypt.h
@@ -0,0 +1,225 @@
+/*
+ Copyright (c) 2011, Andre Somers
+ All rights reserved.
+ 
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ * Neither the name of the Rathenau Instituut, Andre Somers nor the
+ names of its contributors may be used to endorse or promote products
+ derived from this software without specific prior written permission.
+ 
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL ANDRE SOMERS BE LIABLE FOR ANY
+ DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ LOSS OF USE, DATA, OR #######; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef SIMPLECRYPT_H
+#define SIMPLECRYPT_H
+#include <QString>
+#include <QVector>
+#include <QFlags>
+
+/**
+ @ short Simple encrypt*ion and decryption of strings and byte arrays
+ 
+ This class provides a simple implementation of encryption and decryption
+ of strings and byte arrays.
+ 
+ @warning The encryption provided by this class is NOT strong encryption. It may
+ help to shield things from curious eyes, but it will NOT stand up to someone
+ determined to break the encryption. Don't say you were not warned.
+ 
+ The class uses a 64 bit key. Simply create an instance of the class, set the key,
+ and use the encryptToString() method to calculate an encrypted version of the input string.
+ To decrypt that string again, use an instance of SimpleCrypt initialized with
+ the same key, and call the decryptToString() method with the encrypted string. If the key
+ matches, the decrypted version of the string will be returned again.
+ 
+ If you do not provide a key, or if something else is wrong, the encryption and
+ decryption function will return an empty string or will return a string containing nonsense.
+ lastError() will return a value indicating if the method was succesful, and if not, why not.
+ 
+ SimpleCrypt is prepared for the case that the encryption and decryption
+ algorithm is changed in a later version, by prepending a version identifier to the cypertext.
+ */
+class SimpleCrypt
+{
+public:
+    /**
+     CompressionMode describes if compression will be applied to the data to be
+     encrypted.
+     */
+    enum CompressionMode {
+        CompressionAuto,    /*!< Only apply compression if that results in a shorter plaintext. */
+        CompressionAlways,  /*!< Always apply compression. Note that for short inputs, a compression may result in longer data */
+        CompressionNever    /*!< Never apply compression. */
+    };
+    /**
+     IntegrityProtectionMode describes measures taken to make it possible to detect problems with the data
+     or wrong decryption keys.
+     
+     Measures involve adding a checksum or a cryptograhpic hash to the data to be encrypted. This
+     increases the length of the resulting cypertext, but makes it possible to check if the plaintext
+     appears to be valid after decryption.
+     */
+    enum IntegrityProtectionMode {
+        ProtectionNone,    /*!< The integerity of the encrypted data is not protected. It is not really possible to detect a wrong key, for instance. */
+        ProtectionChecksum,/*!< A simple checksum is used to verify that the data is in order. If not, an empty string is returned. */
+        ProtectionHash     /*!< A cryptographic hash is used to verify the integrity of the data. This method produces a much stronger, but longer check */
+    };
+    /**
+     Error describes t*he type of error that occured.
+     */
+    enum Error {
+        ErrorNoError,         /*!< No error occurred. */
+        ErrorNoKeySet,        /*!< No key was set. You can not encrypt or decrypt without a valid key. */
+        ErrorUnknownVersion,  /*!< The version of this data is unknown, or the data is otherwise not valid. */
+        ErrorIntegrityFailed, /*!< The integrity check of the data failed. Perhaps the wrong key was used. */
+    };
+    
+    /**
+     Constructor.     *
+     
+     Constructs a SimpleCrypt instance without a valid key set on it.
+     */
+    SimpleCrypt();
+    /**
+     Constructor.     *
+     
+     Constructs a SimpleCrypt instance and initializes it with the given @arg key.
+     */
+    explicit SimpleCrypt(quint64 key);
+    
+    /**
+     ( Re-) initializes* the key with the given @arg key.
+     */
+    void setKey(quint64 key);
+    /**
+     Returns true if SimpleCrypt has been initialized with a key.
+     */
+    bool hasKey() const {return !m_keyParts.isEmpty();}
+    
+    /**
+     Sets the compress*ion mode to use when encrypting data. The default mode is Auto.
+     
+     Note that decryption is not influenced by this mode, as the decryption recognizes
+     what mode was used when encrypting.
+     */
+    void setCompressionMode(CompressionMode mode) {m_compressionMode = mode;}
+    /**
+     Returns the CompressionMode that is currently in use.
+     */
+    CompressionMode compressionMode() const {return m_compressionMode;}
+    
+    /**
+     Sets the integrity mode to use when encrypting data. The default mode is Checksum.
+     
+     Note that decryption is not influenced by this mode, as the decryption recognizes
+     what mode was used when encrypting.
+     */
+    void setIntegrityProtectionMode(IntegrityProtectionMode mode) {m_protectionMode = mode;}
+    /**
+     Returns the IntegrityProtectionMode that is currently in use.
+     */
+    IntegrityProtectionMode integrityProtectionMode() const {return m_protectionMode;}
+    
+    /**
+     Returns the last *error that occurred.
+     */
+    Error lastError() const {return m_lastError;}
+    
+    /**
+     Encrypts the @arg* plaintext string with the key the class was initialized with, and returns
+     a cyphertext the result. The result is a base64 encoded version of the binary array that is the
+     actual result of the string, so it can be stored easily in a text format.
+     */
+    QString encryptToString(const QString& plaintext) ;
+    /**
+     Encrypts the @arg* plaintext QByteArray with the key the class was initialized with, and returns
+     a cyphertext the result. The result is a base64 encoded version of the binary array that is the
+     actual result of the encryption, so it can be stored easily in a text format.
+     */
+    QString encryptToString(QByteArray plaintext) ;
+    /**
+     Encrypts the @arg* plaintext string with the key the class was initialized with, and returns
+     a binary cyphertext in a QByteArray the result.
+     
+     This method returns a byte array, that is useable for storing a binary format. If you need
+     a string you can store in a text file, use encryptToString() instead.
+     */
+    QByteArray encryptToByteArray(const QString& plaintext) ;
+    /**
+     Encrypts the @arg* plaintext QByteArray with the key the class was initialized with, and returns
+     a binary cyphertext in a QByteArray the result.
+     
+     This method returns a byte array, that is useable for storing a binary format. If you need
+     a string you can store in a text file, use encryptToString() instead.
+     */
+    QByteArray encryptToByteArray(QByteArray plaintext) ;
+    
+    /**
+     Decrypts a cypher*text string encrypted with this class with the set key back to the
+     plain text version.
+     
+     If an error occured, such as non-matching keys between encryption and decryption,
+     an empty string or a string containing nonsense may be returned.
+     */
+    QString decryptToString(const QString& cyphertext) ;
+    /**
+     Decrypts a cypher*text string encrypted with this class with the set key back to the
+     plain text version.
+     
+     If an error occured, such as non-matching keys between encryption and decryption,
+     an empty string or a string containing nonsense may be returned.
+     */
+    QByteArray decryptToByteArray(const QString& cyphertext) ;
+    /**
+     Decrypts a cypher*text binary encrypted with this class with the set key back to the
+     plain text version.
+     
+     If an error occured, such as non-matching keys between encryption and decryption,
+     an empty string or a string containing nonsense may be returned.
+     */
+    QString decryptToString(QByteArray cypher) ;
+    /**
+     Decrypts a cypher*text binary encrypted with this class with the set key back to the
+     plain text version.
+     
+     If an error occured, such as non-matching keys between encryption and decryption,
+     an empty string or a string containing nonsense may be returned.
+     */
+    QByteArray decryptToByteArray(QByteArray cypher) ;
+    
+    //enum to describe options that have been used for the encryption. Currently only one, but
+    //that only leaves room for future extensions like adding a cryptographic hash...
+    enum CryptoFlag{CryptoFlagNone = 0,
+        CryptoFlagCompression = 0x01,
+        CryptoFlagChecksum = 0x02,
+        CryptoFlagHash = 0x04
+    };
+    Q_DECLARE_FLAGS(CryptoFlags, CryptoFlag);
+private:
+    
+    void splitKey();
+    
+    quint64 m_key;
+    QVector<char> m_keyParts;
+    CompressionMode m_compressionMode;
+    IntegrityProtectionMode m_protectionMode;
+    Error m_lastError;
+};
+Q_DECLARE_OPERATORS_FOR_FLAGS(SimpleCrypt::CryptoFlags)
+
+#endif // SimpleCrypt_H
diff --git a/main.cpp b/main.cpp
index eeb7138..b8be72c 100644
--- a/main.cpp
+++ b/main.cpp
@@ -116,6 +116,7 @@ int main(int argc, char *argv[])
     QObject::connect(&w, &Squawk::renameContactRequest, squawk, &Core::Squawk::renameContactRequest);
     QObject::connect(&w, &Squawk::requestVCard, squawk, &Core::Squawk::requestVCard);
     QObject::connect(&w, &Squawk::uploadVCard, squawk, &Core::Squawk::uploadVCard);
+    QObject::connect(&w, &Squawk::responsePassword, squawk, &Core::Squawk::responsePassword);
     
     QObject::connect(squawk, &Core::Squawk::newAccount, &w, &Squawk::newAccount);
     QObject::connect(squawk, &Core::Squawk::addContact, &w, &Squawk::addContact);
@@ -146,6 +147,7 @@ int main(int argc, char *argv[])
     QObject::connect(squawk, &Core::Squawk::uploadFileProgress, &w, &Squawk::fileProgress);
     QObject::connect(squawk, &Core::Squawk::uploadFileError, &w, &Squawk::fileError);
     QObject::connect(squawk, &Core::Squawk::responseVCard, &w, &Squawk::responseVCard);
+    QObject::connect(squawk, &Core::Squawk::requestPassword, &w, &Squawk::requestPassword);
     QObject::connect(squawk, &Core::Squawk::ready, &w, &Squawk::readSettings);
     
     coreThread->start();
diff --git a/shared/enums.h b/shared/enums.h
index bf55377..cb41443 100644
--- a/shared/enums.h
+++ b/shared/enums.h
@@ -110,11 +110,11 @@ static const std::deque<QString> messageStateThemeIcons = {"state-offline", "sta
 enum class AccountPassword {
     plain,
     jammed,
-    kwallet,
-    alwaysAsk
+    alwaysAsk,
+    kwallet
 };
 Q_ENUM_NS(AccountPassword)
-static const AccountPassword AccountPasswordHighest = AccountPassword::alwaysAsk;
+static const AccountPassword AccountPasswordHighest = AccountPassword::kwallet;
 static const AccountPassword AccountPasswordLowest = AccountPassword::plain;
 
 }
diff --git a/shared/global.cpp b/shared/global.cpp
index 81bee3f..c974eaf 100644
--- a/shared/global.cpp
+++ b/shared/global.cpp
@@ -69,8 +69,8 @@ Shared::Global::Global():
     accountPassword({
         tr("Plain"),
         tr("Jammed"),
-        tr("KWallet"),
-        tr("Always Ask")
+        tr("Always Ask"),
+        tr("KWallet")
     })
 {
     if (instance != 0) {
diff --git a/ui/models/account.cpp b/ui/models/account.cpp
index c581439..a60315c 100644
--- a/ui/models/account.cpp
+++ b/ui/models/account.cpp
@@ -39,6 +39,10 @@ Models::Account::Account(const QMap<QString, QVariant>& data, Models::Item* pare
     if (aItr != data.end()) {
         setAvailability(aItr.value().toUInt());
     }
+    QMap<QString, QVariant>::const_iterator pItr = data.find("passwordType");
+    if (pItr != data.end()) {
+        setPasswordType(pItr.value().toUInt());
+    }
 }
 
 Models::Account::~Account()
diff --git a/ui/squawk.cpp b/ui/squawk.cpp
index 6822dd2..3a11c1a 100644
--- a/ui/squawk.cpp
+++ b/ui/squawk.cpp
@@ -20,7 +20,6 @@
 #include "ui_squawk.h"
 #include <QDebug>
 #include <QIcon>
-#include <QInputDialog>
 
 Squawk::Squawk(QWidget *parent) :
     QMainWindow(parent),
@@ -31,7 +30,9 @@ Squawk::Squawk(QWidget *parent) :
     contextMenu(new QMenu()),
     dbus("org.freedesktop.Notifications", "/org/freedesktop/Notifications", "org.freedesktop.Notifications", QDBusConnection::sessionBus()),
     requestedFiles(),
-    vCards()
+    vCards(),
+    requestedAccountsForPasswords(),
+    prompt(0)
 {
     m_ui->setupUi(this);
     m_ui->roster->setModel(&rosterModel);
@@ -62,6 +63,18 @@ Squawk::Squawk(QWidget *parent) :
     if (testAttribute(Qt::WA_TranslucentBackground)) {
         m_ui->roster->viewport()->setAutoFillBackground(false);
     }
+    
+    QSettings settings;
+    settings.beginGroup("ui");
+    settings.beginGroup("window");
+    if (settings.contains("geometry")) {
+        restoreGeometry(settings.value("geometry").toByteArray());
+    }
+    if (settings.contains("state")) {
+        restoreState(settings.value("state").toByteArray());
+    }
+    settings.endGroup();
+    settings.endGroup();
 }
 
 Squawk::~Squawk() {
@@ -871,14 +884,6 @@ void Squawk::readSettings()
 {
     QSettings settings;
     settings.beginGroup("ui");
-    settings.beginGroup("window");
-    if (settings.contains("geometry")) {
-        restoreGeometry(settings.value("geometry").toByteArray());
-    }
-    if (settings.contains("state")) {
-        restoreState(settings.value("state").toByteArray());
-    }
-    settings.endGroup();
     
     if (settings.contains("availability")) {
         int avail = settings.value("availability").toInt();
@@ -958,3 +963,48 @@ void Squawk::onItemCollepsed(const QModelIndex& index)
             break;
     }
 }
+
+void Squawk::requestPassword(const QString& account)
+{
+    requestedAccountsForPasswords.push_back(account);
+    checkNextAccountForPassword();
+}
+
+void Squawk::checkNextAccountForPassword()
+{
+    if (prompt == 0 && requestedAccountsForPasswords.size() > 0) {
+        prompt = new QInputDialog(this);
+        QString accName = requestedAccountsForPasswords.front();
+        connect(prompt, &QDialog::accepted, this, &Squawk::onPasswordPromptAccepted);
+        connect(prompt, &QDialog::rejected, this, &Squawk::onPasswordPromptRejected);
+        prompt->setInputMode(QInputDialog::TextInput);
+        prompt->setTextEchoMode(QLineEdit::Password);
+        prompt->setLabelText(tr("Input the password for account %1").arg(accName));
+        prompt->setWindowTitle(tr("Password for account %1").arg(accName));
+        prompt->setTextValue("");
+        prompt->exec();
+    }
+}
+
+void Squawk::onPasswordPromptAccepted()
+{
+    emit responsePassword(requestedAccountsForPasswords.front(), prompt->textValue());
+    onPasswordPromptDone();
+}
+
+void Squawk::onPasswordPromptDone()
+{
+    prompt->deleteLater();
+    prompt = 0;
+    requestedAccountsForPasswords.pop_front();
+    checkNextAccountForPassword();
+}
+
+void Squawk::onPasswordPromptRejected()
+{
+    //for now it's the same on reject and on accept, but one day I'm gonna make 
+    //"Asking for the password again on the authentication failure" feature
+    //and here I'll be able to break the circle of password requests
+    emit responsePassword(requestedAccountsForPasswords.front(), prompt->textValue());
+    onPasswordPromptDone();
+}
diff --git a/ui/squawk.h b/ui/squawk.h
index 64459ab..d5bde9c 100644
--- a/ui/squawk.h
+++ b/ui/squawk.h
@@ -24,6 +24,7 @@
 #include <QCloseEvent>
 #include <QtDBus/QDBusInterface>
 #include <QSettings>
+#include <QInputDialog>
 
 #include <deque>
 #include <map>
@@ -79,6 +80,7 @@ signals:
     void downloadFileRequest(const QString& messageId, const QString& url);
     void requestVCard(const QString& account, const QString& jid);
     void uploadVCard(const QString& account, const Shared::VCard& card);
+    void responsePassword(const QString& account, const QString& password);
     
 public slots:
     void readSettings();
@@ -107,6 +109,7 @@ public slots:
     void fileProgress(const QString& messageId, qreal value);
     void responseVCard(const QString& jid, const Shared::VCard& card);
     void changeMessage(const QString& account, const QString& jid, const QString& id, const QMap<QString, QVariant>& data);
+    void requestPassword(const QString& account);
     
 private:
     typedef std::map<Models::Roster::ElId, Conversation*> Conversations;
@@ -119,6 +122,8 @@ private:
     QDBusInterface dbus;
     std::map<QString, std::set<Models::Roster::ElId>> requestedFiles;
     std::map<QString, VCard*> vCards;
+    std::deque<QString> requestedAccountsForPasswords;
+    QInputDialog* prompt;
     
 protected:
     void closeEvent(QCloseEvent * event) override;
@@ -146,7 +151,12 @@ private slots:
     void onConversationRequestLocalFile(const QString& messageId, const QString& url);
     void onConversationDownloadFile(const QString& messageId, const QString& url);
     void onItemCollepsed(const QModelIndex& index);
+    void onPasswordPromptAccepted();
+    void onPasswordPromptRejected();
     
+private:
+    void checkNextAccountForPassword();
+    void onPasswordPromptDone();
 };
 
 #endif // SQUAWK_H

From 543538fc5667e304ca4788e24b70c4e7add15d1e Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Fri, 10 Apr 2020 01:48:08 +0300
Subject: [PATCH 059/281] first working prototype of dynamically loaded kwallet
 storage

---
 CHANGELOG.md                                  |   5 -
 core/CMakeLists.txt                           |   4 +-
 core/passwordStorageEngines/CMakeLists.txt    |  36 +++
 core/passwordStorageEngines/kwallet.cpp       | 227 ++++++++++++++++++
 core/passwordStorageEngines/kwallet.h         | 112 +++++++++
 .../wrappers/kwallet.cpp                      |  33 +++
 core/squawk.cpp                               |  77 +++++-
 core/squawk.h                                 |   7 +
 8 files changed, 484 insertions(+), 17 deletions(-)
 create mode 100644 core/passwordStorageEngines/CMakeLists.txt
 create mode 100644 core/passwordStorageEngines/kwallet.cpp
 create mode 100644 core/passwordStorageEngines/kwallet.h
 create mode 100644 core/passwordStorageEngines/wrappers/kwallet.cpp

diff --git a/CHANGELOG.md b/CHANGELOG.md
index d281fb8..b993919 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,7 +1,6 @@
 # Changelog
 
 ## Squawk 0.1.4 (UNRELEASED)
-------------------------------
 ### New features
 - several ways to manage your account password:
   - store it in plain text with the config (like it always was)
@@ -14,7 +13,6 @@
 
 
 ## Squawk 0.1.3 (Mar 31, 2020)
-------------------------------
 ### New features
 - delivery states for the messages
 - delivery receipts now work for real
@@ -32,7 +30,6 @@
 
 
 ## Squawk 0.1.2 (Dec 25, 2019)
-------------------------------
 ### New features
 - icons in roster for conferences
 - pal avatar in dialog window
@@ -50,7 +47,6 @@
 
 
 ## Squawk 0.1.1 (Nov 16, 2019)
-------------------------------
 A lot of bug fixes, memory leaks fixes
 ### New features
 - exchange files via HTTP File Upload
@@ -60,7 +56,6 @@ A lot of bug fixes, memory leaks fixes
 
 
 ## Squawk 0.0.5 (Oct 10, 2019)
-------------------------------
 ### Features
 - chat directly
 - have multiple accounts
diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt
index ad37198..32d61b9 100644
--- a/core/CMakeLists.txt
+++ b/core/CMakeLists.txt
@@ -23,13 +23,14 @@ set(squawkCORE_SRC
 # Tell CMake to create the helloworld executable
 add_library(squawkCORE ${squawkCORE_SRC})
 
+add_subdirectory(passwordStorageEngines)
+
 if(SYSTEM_QXMPP)
     find_package(QXmpp CONFIG REQUIRED)
     get_target_property(QXMPP_INTERFACE_INCLUDE_DIRECTORIES QXmpp::QXmpp INTERFACE_INCLUDE_DIRECTORIES)
     target_include_directories(squawkCORE PUBLIC ${QXMPP_INTERFACE_INCLUDE_DIRECTORIES})
 endif()
 
-
 # Use the Widgets module from Qt 5.
 target_link_libraries(squawkCORE Qt5::Core)
 target_link_libraries(squawkCORE Qt5::Network)
@@ -38,3 +39,4 @@ target_link_libraries(squawkCORE Qt5::Xml)
 target_link_libraries(squawkCORE qxmpp)
 target_link_libraries(squawkCORE lmdb)
 target_link_libraries(squawkCORE simpleCrypt)
+target_link_libraries(squawkCORE kwalletPSE)
diff --git a/core/passwordStorageEngines/CMakeLists.txt b/core/passwordStorageEngines/CMakeLists.txt
new file mode 100644
index 0000000..9527238
--- /dev/null
+++ b/core/passwordStorageEngines/CMakeLists.txt
@@ -0,0 +1,36 @@
+cmake_minimum_required(VERSION 3.0)
+project(pse)
+
+set(CMAKE_AUTOMOC ON)
+
+find_package(Qt5Core CONFIG REQUIRED)
+find_package(Qt5Gui CONFIG REQUIRED)
+find_package(KF5Wallet CONFIG REQUIRED)
+
+get_target_property(KWALLET_INTERFACE_INCLUDE_DIRECTORIES KF5::Wallet INTERFACE_INCLUDE_DIRECTORIES)
+get_target_property(Qt5GUI_INTERFACE_INCLUDE_DIRECTORIES Qt5::Gui INTERFACE_INCLUDE_DIRECTORIES)
+
+set(kwalletPSE_SRC
+    kwallet.cpp
+)
+
+add_library(kwalletPSE ${kwalletPSE_SRC})
+
+target_include_directories(kwalletPSE PUBLIC ${KWALLET_INTERFACE_INCLUDE_DIRECTORIES})
+target_include_directories(kwalletPSE PUBLIC ${Qt5GUI_INTERFACE_INCLUDE_DIRECTORIES})
+
+target_link_libraries(kwalletPSE Qt5::Core)
+
+set(kwalletW_SRC
+    wrappers/kwallet.cpp
+)
+
+add_library(kwalletWrapper SHARED ${kwalletW_SRC})
+
+target_include_directories(kwalletWrapper PUBLIC ${KWALLET_INTERFACE_INCLUDE_DIRECTORIES})
+target_include_directories(kwalletWrapper PUBLIC ${Qt5GUI_INTERFACE_INCLUDE_DIRECTORIES})
+
+target_link_libraries(kwalletWrapper KF5::Wallet)
+target_link_libraries(kwalletWrapper Qt5::Core)
+
+install(TARGETS kwalletWrapper DESTINATION ${CMAKE_INSTALL_LIBDIR})
diff --git a/core/passwordStorageEngines/kwallet.cpp b/core/passwordStorageEngines/kwallet.cpp
new file mode 100644
index 0000000..ddfb466
--- /dev/null
+++ b/core/passwordStorageEngines/kwallet.cpp
@@ -0,0 +1,227 @@
+/*
+ * Squawk messenger. 
+ * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+ * 
+ * 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 "kwallet.h"
+
+Core::PSE::KWallet::OpenWallet Core::PSE::KWallet::openWallet = 0;
+Core::PSE::KWallet::NetworkWallet Core::PSE::KWallet::networkWallet = 0;
+Core::PSE::KWallet::DeleteWallet Core::PSE::KWallet::deleteWallet = 0;
+Core::PSE::KWallet::ReadPassword Core::PSE::KWallet::readPassword = 0;
+Core::PSE::KWallet::WritePassword Core::PSE::KWallet::writePassword = 0;
+Core::PSE::KWallet::HasFolder Core::PSE::KWallet::hasFolder = 0;
+Core::PSE::KWallet::CreateFolder Core::PSE::KWallet::createFolder = 0;
+Core::PSE::KWallet::SetFolder Core::PSE::KWallet::setFolder = 0;
+
+Core::PSE::KWallet::SupportState Core::PSE::KWallet::sState = Core::PSE::KWallet::initial;
+QLibrary Core::PSE::KWallet::lib("./libkwalletWrapper.so");
+
+Core::PSE::KWallet::KWallet():
+    QObject(),
+    cState(disconnected),
+    everError(false),
+    wallet(0),
+    readRequest(),
+    writeRequest()
+{
+    if (sState == initial) {
+        lib.load();
+        
+        if (lib.isLoaded()) {
+            openWallet = (OpenWallet) lib.resolve("openWallet");
+            networkWallet = (NetworkWallet) lib.resolve("networkWallet");
+            deleteWallet = (DeleteWallet) lib.resolve("deleteWallet");
+            readPassword = (ReadPassword) lib.resolve("readPassword");
+            writePassword = (WritePassword) lib.resolve("writePassword");
+            hasFolder = (HasFolder) lib.resolve("hasFolder");
+            createFolder = (CreateFolder) lib.resolve("createFolder");
+            setFolder = (SetFolder) lib.resolve("setFolder");
+            
+            if (openWallet 
+                && networkWallet
+                && deleteWallet
+                && readPassword
+                && writePassword
+            ) {
+                sState = success;
+            } else {
+                sState = failure;
+            }
+        } else {
+            sState = failure;
+        }
+    }
+}
+
+Core::PSE::KWallet::~KWallet()
+{
+    close();
+}
+
+void Core::PSE::KWallet::open()
+{
+    if (sState == success) {
+        if (cState == disconnected) {
+            wallet = openWallet(networkWallet(), 0, ::KWallet::Wallet::Asynchronous);
+            if (wallet) {
+                cState = connecting;
+                connect(wallet, SIGNAL(walletOpened(bool)), this, SLOT(onWalletOpened(bool)));
+                connect(wallet, SIGNAL(walletClosed()), this, SLOT(onWalletClosed()));
+            } else {
+                everError = true;
+                emit opened(false);
+            }
+        }
+    }
+}
+
+Core::PSE::KWallet::ConnectionState Core::PSE::KWallet::connectionState()
+{
+    return cState;
+}
+
+void Core::PSE::KWallet::close()
+{
+    if (sState == success) {
+        if (cState != disconnected) {
+            deleteWallet(wallet);
+            wallet = 0;
+        }
+        rejectPending();
+    }
+}
+
+void Core::PSE::KWallet::onWalletClosed()
+{
+    cState = disconnected;
+    deleteWallet(wallet);
+    wallet = 0;
+    emit closed();
+    rejectPending();
+}
+
+void Core::PSE::KWallet::onWalletOpened(bool success)
+{
+    emit opened(success);
+    if (success) {
+        QString appName = QCoreApplication::applicationName();
+        if (!hasFolder(wallet, appName)) {
+            createFolder(wallet, appName);
+        }
+        setFolder(wallet, appName);
+        cState = connected;
+        readPending();
+    } else {
+        everError = true;
+        cState = disconnected;
+        deleteWallet(wallet);
+        wallet = 0;
+        rejectPending();
+    }
+}
+
+Core::PSE::KWallet::SupportState Core::PSE::KWallet::supportState()
+{
+    return sState;
+}
+
+bool Core::PSE::KWallet::everHadError() const
+{
+    return everError;
+}
+
+void Core::PSE::KWallet::resetEverHadError()
+{
+    everError = false;
+}
+
+void Core::PSE::KWallet::requestReadPassword(const QString& login, bool askAgain)
+{
+    if (sState == success) {
+        readRequest.insert(login);
+        readSwitch(askAgain);
+    }
+}
+
+void Core::PSE::KWallet::requestWritePassword(const QString& login, const QString& password, bool askAgain)
+{
+    if (sState == success) {
+        std::map<QString, QString>::iterator itr = writeRequest.find(login);
+        if (itr == writeRequest.end()) {
+            writeRequest.insert(std::make_pair(login, password));
+        } else {
+            itr->second = password;
+        }
+        readSwitch(askAgain);
+    }
+}
+
+void Core::PSE::KWallet::readSwitch(bool askAgain)
+{
+    switch (cState) {
+        case connected:
+            readPending();
+            break;
+        case connecting:
+            break;
+        case disconnected:
+            if (!everError || askAgain) {
+                open();
+            }
+            break;
+    }
+}
+
+void Core::PSE::KWallet::rejectPending()
+{
+    writeRequest.clear();
+    std::set<QString>::const_iterator i = readRequest.begin();
+    while (i != readRequest.end()) {
+        emit rejectPassword(*i);
+        readRequest.erase(i);
+        i = readRequest.begin();
+    }
+}
+
+void Core::PSE::KWallet::readPending()
+{
+    std::map<QString, QString>::const_iterator itr = writeRequest.begin();
+    while (itr != writeRequest.end()) {
+        int result = writePassword(wallet, itr->first, itr->second);
+        if (result == 0) {
+            qDebug() << "Successfully saved password for user" << itr->first;
+        } else {
+            qDebug() << "Error writing password for user" << itr->first << ":" << result;
+        }
+        writeRequest.erase(itr);
+        itr = writeRequest.begin();
+    }
+    
+    std::set<QString>::const_iterator i = readRequest.begin();
+    while (i != readRequest.end()) {
+        QString password;
+        int result = readPassword(wallet, *i, password);
+        if (result == 0 && password.size() > 0) {       //even though it's written that the error is supposed to be returned in case there were no password
+            emit responsePassword(*i, password);        //it doesn't do so. I assume empty password as a lack of password in KWallet
+        } else {
+            emit rejectPassword(*i);
+        }
+        readRequest.erase(i);
+        i = readRequest.begin();
+    }
+}
+
diff --git a/core/passwordStorageEngines/kwallet.h b/core/passwordStorageEngines/kwallet.h
new file mode 100644
index 0000000..8ac52d2
--- /dev/null
+++ b/core/passwordStorageEngines/kwallet.h
@@ -0,0 +1,112 @@
+/*
+ * Squawk messenger. 
+ * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+ * 
+ * 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/>.
+ */
+
+#ifndef CORE_PSE_KWALLET_H
+#define CORE_PSE_KWALLET_H
+
+#include <QObject>
+#include <QLibrary>
+#include <QDebug>
+#include <QCoreApplication>
+
+#include <map>
+#include <set>
+
+#include <KF5/KWallet/KWallet>
+
+namespace Core {
+namespace PSE {
+
+/**
+ * @todo write docs
+ */
+class KWallet : public QObject
+{
+    Q_OBJECT
+public:
+    enum SupportState {
+        initial,
+        success,
+        failure
+    };
+    enum ConnectionState {
+        disconnected,
+        connecting,
+        connected
+    };
+    
+    KWallet();
+    ~KWallet();
+    
+    static SupportState supportState();
+    void open();
+    void close();
+    ConnectionState connectionState();
+    bool everHadError() const;
+    void resetEverHadError();
+    void requestReadPassword(const QString& login, bool askAgain = false);
+    void requestWritePassword(const QString& login, const QString& password, bool askAgain = false);
+    
+signals:
+    void opened(bool success);
+    void closed();
+    void responsePassword(const QString& login, const QString& password);
+    void rejectPassword(const QString& login);
+    
+private slots:
+    void onWalletOpened(bool success);
+    void onWalletClosed();
+    
+private:
+    void readSwitch(bool askAgain);
+    void readPending();
+    void rejectPending();
+    
+private:
+    typedef ::KWallet::Wallet* (*OpenWallet)(const QString &, WId, ::KWallet::Wallet::OpenType);
+    typedef const char* (*NetworkWallet)();
+    typedef void (*DeleteWallet)(::KWallet::Wallet*);
+    typedef int (*ReadPassword)(::KWallet::Wallet*, const QString&, QString&);
+    typedef int (*WritePassword)(::KWallet::Wallet*, const QString&, const QString&);
+    typedef bool (*HasFolder)(::KWallet::Wallet* w, const QString &f);
+    typedef bool (*CreateFolder)(::KWallet::Wallet* w, const QString &f);
+    typedef bool (*SetFolder)(::KWallet::Wallet* w, const QString &f);
+    
+    static OpenWallet openWallet;
+    static NetworkWallet networkWallet;
+    static DeleteWallet deleteWallet;
+    static ReadPassword readPassword;
+    static WritePassword writePassword;
+    static HasFolder hasFolder;
+    static CreateFolder createFolder;
+    static SetFolder setFolder;
+    
+    static SupportState sState;
+    static QLibrary lib;
+    
+    ConnectionState cState;
+    bool everError;
+    ::KWallet::Wallet* wallet;
+    
+    std::set<QString> readRequest;
+    std::map<QString, QString> writeRequest;
+};
+
+}}
+
+#endif // CORE_PSE_KWALLET_H
diff --git a/core/passwordStorageEngines/wrappers/kwallet.cpp b/core/passwordStorageEngines/wrappers/kwallet.cpp
new file mode 100644
index 0000000..39a2eaf
--- /dev/null
+++ b/core/passwordStorageEngines/wrappers/kwallet.cpp
@@ -0,0 +1,33 @@
+#include <KF5/KWallet/KWallet>
+
+extern "C" KWallet::Wallet* openWallet(const QString &name, WId w, KWallet::Wallet::OpenType ot = KWallet::Wallet::Synchronous) {
+    return KWallet::Wallet::openWallet(name, w, ot);
+}
+
+extern "C" void deleteWallet(KWallet::Wallet* w) {
+    w->deleteLater();
+}
+
+extern "C" const char* networkWallet() {
+    return KWallet::Wallet::NetworkWallet().toStdString().c_str();
+}
+
+extern "C" int readPassword(KWallet::Wallet* w, const QString &key, QString &value) {
+    return w->readPassword(key, value);
+}
+
+extern "C" int writePassword(KWallet::Wallet* w, const QString &key, const QString &value) {
+    return w->writePassword(key, value);
+}
+
+extern "C" bool hasFolder(KWallet::Wallet* w, const QString &f) {
+    return w->hasFolder(f);
+}
+
+extern "C" bool createFolder(KWallet::Wallet* w, const QString &f) {
+    return w->createFolder(f);
+}
+
+extern "C" bool setFolder(KWallet::Wallet* w, const QString &f) {
+    return w->setFolder(f);
+}
diff --git a/core/squawk.cpp b/core/squawk.cpp
index 8c70cb8..abd8977 100644
--- a/core/squawk.cpp
+++ b/core/squawk.cpp
@@ -27,13 +27,22 @@ Core::Squawk::Squawk(QObject* parent):
     accounts(),
     amap(),
     network(),
-    waitingForAccounts(0)
+    waitingForAccounts(0),
+    kwallet()
 {
     connect(&network, &NetworkAccess::fileLocalPathResponse, this, &Squawk::fileLocalPathResponse);
     connect(&network, &NetworkAccess::downloadFileProgress, this, &Squawk::downloadFileProgress);
     connect(&network, &NetworkAccess::downloadFileError, this, &Squawk::downloadFileError);
     connect(&network, &NetworkAccess::uploadFileProgress, this, &Squawk::uploadFileProgress);
     connect(&network, &NetworkAccess::uploadFileError, this, &Squawk::uploadFileError);
+    
+    
+    if (kwallet.supportState() == PSE::KWallet::success) {
+        qDebug() << "KWallet support detected";
+        connect(&kwallet, &PSE::KWallet::opened, this, &Squawk::onWalletOpened);
+        connect(&kwallet, &PSE::KWallet::rejectPassword, this, &Squawk::onWalletRejectPassword);
+        connect(&kwallet, &PSE::KWallet::responsePassword, this, &Squawk::onWalletResponsePassword);
+    }
 }
 
 Core::Squawk::~Squawk()
@@ -45,6 +54,11 @@ Core::Squawk::~Squawk()
     }
 }
 
+void Core::Squawk::onWalletOpened(bool success)
+{
+    qDebug() << "KWallet opened: " << success;
+}
+
 void Core::Squawk::stop()
 {
     qDebug("Stopping squawk core..");
@@ -207,17 +221,27 @@ void Core::Squawk::onAccountConnectionStateChanged(Shared::ConnectionState p_sta
     Account* acc = static_cast<Account*>(sender());
     emit changeAccount(acc->getName(), {{"state", QVariant::fromValue(p_state)}});
     
-    if (p_state == Shared::ConnectionState::disconnected) {
-        bool equals = true;
-        for (Accounts::const_iterator itr = accounts.begin(), end = accounts.end(); itr != end; itr++) {
-            if ((*itr)->getState() != Shared::ConnectionState::disconnected) {
-                equals = false;
+    switch (p_state) {
+        case Shared::ConnectionState::disconnected: {
+                bool equals = true;
+                for (Accounts::const_iterator itr = accounts.begin(), end = accounts.end(); itr != end; itr++) {
+                    if ((*itr)->getState() != Shared::ConnectionState::disconnected) {
+                        equals = false;
+                    }
+                }
+                if (equals && state != Shared::Availability::offline) {
+                    state = Shared::Availability::offline;
+                    emit stateChanged(state);
+                }
             }
-        }
-        if (equals && state != Shared::Availability::offline) {
-            state = Shared::Availability::offline;
-            emit stateChanged(state);
-        }
+            break;
+        case Shared::ConnectionState::connected:
+            if (acc->getPasswordType() == Shared::AccountPassword::kwallet && kwallet.supportState() == PSE::KWallet::success) {
+                kwallet.requestWritePassword(acc->getName(), acc->getPassword(), true);
+            }
+            break;
+        default:
+            break;
     }
 }
 
@@ -391,6 +415,13 @@ void Core::Squawk::modifyAccountRequest(const QString& name, const QMap<QString,
         acc->setPasswordType(Shared::Global::fromInt<Shared::AccountPassword>(mItr->toInt()));
     }
     
+    if (acc->getPasswordType() == Shared::AccountPassword::kwallet 
+        && kwallet.supportState() == PSE::KWallet::success 
+        && !needToReconnect
+    ) {
+        kwallet.requestWritePassword(acc->getName(), acc->getPassword(), true);
+    }
+    
     emit changeAccount(name, map);
 }
 
@@ -683,5 +714,29 @@ void Core::Squawk::parseAccount(
             addAccount(login, server, QString(), name, resource, passwordType);
             emit requestPassword(name);
             break;
+        case Shared::AccountPassword::kwallet: {
+            addAccount(login, server, QString(), name, resource, passwordType);
+            if (kwallet.supportState() == PSE::KWallet::success) {
+                kwallet.requestReadPassword(name);
+            } else {
+                emit requestPassword(name);
+            }
+        }
     }
 }
+
+void Core::Squawk::onWalletRejectPassword(const QString& login)
+{
+    emit requestPassword(login);
+}
+
+void Core::Squawk::onWalletResponsePassword(const QString& login, const QString& password)
+{
+    AccountsMap::const_iterator itr = amap.find(login);
+    if (itr == amap.end()) {
+        qDebug() << "An attempt to set password to non existing account" << login << ", skipping";
+        return;
+    }
+    itr->second->setPassword(password);
+    accountReady();
+}
diff --git a/core/squawk.h b/core/squawk.h
index f96f7e8..b1fa492 100644
--- a/core/squawk.h
+++ b/core/squawk.h
@@ -34,6 +34,8 @@
 #include "networkaccess.h"
 #include "external/simpleCrypt/simplecrypt.h"
 
+#include "passwordStorageEngines/kwallet.h"
+
 namespace Core
 {
 class Squawk : public QObject
@@ -114,6 +116,7 @@ private:
     Shared::Availability state;
     NetworkAccess network;
     uint8_t waitingForAccounts;
+    PSE::KWallet kwallet;
     
 private slots:
     void addAccount(
@@ -147,6 +150,10 @@ private slots:
     void onAccountRemoveRoomPresence(const QString& jid, const QString& nick);
     void onAccountChangeMessage(const QString& jid, const QString& id, const QMap<QString, QVariant>& data);
     
+    void onWalletOpened(bool success);
+    void onWalletResponsePassword(const QString& login, const QString& password);
+    void onWalletRejectPassword(const QString& login);
+    
 private:
     void readSettings();
     void accountReady();

From b95028e33e2f8aaea255379a559fd7e93b14b838 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Sat, 11 Apr 2020 01:15:08 +0300
Subject: [PATCH 060/281] testing, ability to build without kwallet,
 translations, disabling unsupported storage types in combobox

---
 CHANGELOG.md                               |   1 +
 CMakeLists.txt                             |  30 +++-
 README.md                                  |  10 ++
 core/CMakeLists.txt                        |   8 +-
 core/passwordStorageEngines/CMakeLists.txt |  50 +++---
 core/passwordStorageEngines/kwallet.cpp    |   7 +-
 core/squawk.cpp                            |  20 ++-
 core/squawk.h                              |   5 +
 shared/global.cpp                          | 101 +++++++----
 shared/global.h                            |   9 +
 translations/squawk.ru.ts                  | 196 +++++++++++----------
 ui/widgets/account.cpp                     |  16 +-
 ui/widgets/account.h                       |   4 +
 ui/widgets/account.ui                      |  20 ++-
 ui/widgets/accounts.cpp                    |   1 +
 15 files changed, 315 insertions(+), 163 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index b993919..11177e8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,7 @@
   - store it in plain text with the config (like it always was)
   - store it in config jammed (local hashing with the constant seed, not secure at all but might look like it is)
   - ask the account password on each program launch
+  - store it in KWallet which is dynamically loaded
   
 ### Bug fixes
 - never updating MUC avatars now update
diff --git a/CMakeLists.txt b/CMakeLists.txt
index aac3e75..20ae092 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -62,15 +62,39 @@ add_custom_target(translations ALL DEPENDS ${QM_FILES})
 
 qt5_add_resources(RCC resources/resources.qrc)
 
-add_executable(squawk ${squawk_SRC} ${squawk_HEAD} ${RCC})
-target_link_libraries(squawk Qt5::Widgets)
-
 option(SYSTEM_QXMPP "Use system qxmpp lib" ON) 
+option(WITH_KWALLET "Build KWallet support module" ON) 
+
+if (SYSTEM_QXMPP) 
+    find_package(QXmpp CONFIG)
+    
+    if (NOT QXmpp_FOUND)
+        set(SYSTEM_QXMPP OFF)
+        message("QXmpp package wasn't found, trying to build with bundled QXmpp")
+    else()
+        message("Building with system QXmpp")
+    endif()
+endif()
 
 if(NOT SYSTEM_QXMPP)
     add_subdirectory(external/qxmpp)
 endif()
 
+if (WITH_KWALLET)
+    find_package(KF5Wallet CONFIG)
+    
+    if (NOT KF5Wallet_FOUND)
+        set(WITH_KWALLET OFF)
+        message("KWallet package wasn't found, KWallet support module wouldn't be built")
+    else()
+        add_definitions(-DWITH_KWALLET)
+        message("Building with support of KWallet")
+    endif()
+endif()
+
+add_executable(squawk ${squawk_SRC} ${squawk_HEAD} ${RCC})
+target_link_libraries(squawk Qt5::Widgets)
+
 add_subdirectory(ui)
 add_subdirectory(core)
 
diff --git a/README.md b/README.md
index 1366c96..c820ccd 100644
--- a/README.md
+++ b/README.md
@@ -60,6 +60,16 @@ $ cmake .. -D SYSTEM_QXMPP=False
 $ cmake --build .
 ```
 
+### List of keys
+
+Here is the list of keys you can pass to configuration phase of `cmake ..`. 
+- `CMAKE_BUILD_TYPE` - `Debug` just builds showing all warnings, `Release` builds with no warnings and applies optimizations (default is `Debug`)
+- `SYSTEM_QXMPP` - `True` tries to link against `qxmpp` installed in the system, `False` builds bundled `qxmpp` library (default is `True`)
+- `WITH_KWALLET` - `True` builds the `KWallet` capability module if `KWallet` is installed and if not goes to `False`. `False` disables `KWallet` support (default is `True`)
+
+
+Each key is supposed to be passed like that 
+
 ## License
 
 This project is licensed under the GPLv3 License - see the [LICENSE.md](LICENSE.md) file for details
diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt
index 32d61b9..c9c573b 100644
--- a/core/CMakeLists.txt
+++ b/core/CMakeLists.txt
@@ -20,13 +20,13 @@ set(squawkCORE_SRC
     adapterFuctions.cpp
 )
 
+add_subdirectory(passwordStorageEngines)
+
 # Tell CMake to create the helloworld executable
 add_library(squawkCORE ${squawkCORE_SRC})
 
-add_subdirectory(passwordStorageEngines)
 
 if(SYSTEM_QXMPP)
-    find_package(QXmpp CONFIG REQUIRED)
     get_target_property(QXMPP_INTERFACE_INCLUDE_DIRECTORIES QXmpp::QXmpp INTERFACE_INCLUDE_DIRECTORIES)
     target_include_directories(squawkCORE PUBLIC ${QXMPP_INTERFACE_INCLUDE_DIRECTORIES})
 endif()
@@ -39,4 +39,6 @@ target_link_libraries(squawkCORE Qt5::Xml)
 target_link_libraries(squawkCORE qxmpp)
 target_link_libraries(squawkCORE lmdb)
 target_link_libraries(squawkCORE simpleCrypt)
-target_link_libraries(squawkCORE kwalletPSE)
+if (WITH_KWALLET)
+    target_link_libraries(squawkCORE kwalletPSE)
+endif()
diff --git a/core/passwordStorageEngines/CMakeLists.txt b/core/passwordStorageEngines/CMakeLists.txt
index 9527238..36f67b1 100644
--- a/core/passwordStorageEngines/CMakeLists.txt
+++ b/core/passwordStorageEngines/CMakeLists.txt
@@ -1,36 +1,42 @@
 cmake_minimum_required(VERSION 3.0)
 project(pse)
 
-set(CMAKE_AUTOMOC ON)
 
-find_package(Qt5Core CONFIG REQUIRED)
-find_package(Qt5Gui CONFIG REQUIRED)
-find_package(KF5Wallet CONFIG REQUIRED)
+if (WITH_KWALLET) 
+    set(CMAKE_AUTOMOC ON)
 
-get_target_property(KWALLET_INTERFACE_INCLUDE_DIRECTORIES KF5::Wallet INTERFACE_INCLUDE_DIRECTORIES)
-get_target_property(Qt5GUI_INTERFACE_INCLUDE_DIRECTORIES Qt5::Gui INTERFACE_INCLUDE_DIRECTORIES)
+    find_package(Qt5Core CONFIG REQUIRED)
+    find_package(Qt5Gui CONFIG REQUIRED)
 
-set(kwalletPSE_SRC
-    kwallet.cpp
-)
+    get_target_property(KWALLET_INTERFACE_INCLUDE_DIRECTORIES KF5::Wallet INTERFACE_INCLUDE_DIRECTORIES)
+    get_target_property(Qt5GUI_INTERFACE_INCLUDE_DIRECTORIES Qt5::Gui INTERFACE_INCLUDE_DIRECTORIES)
 
-add_library(kwalletPSE ${kwalletPSE_SRC})
+    set(kwalletPSE_SRC
+        kwallet.cpp
+    )
+    
+    add_library(kwalletPSE ${kwalletPSE_SRC})
+    
+    target_include_directories(kwalletPSE PUBLIC ${KWALLET_INTERFACE_INCLUDE_DIRECTORIES})
+    target_include_directories(kwalletPSE PUBLIC ${Qt5GUI_INTERFACE_INCLUDE_DIRECTORIES})
 
-target_include_directories(kwalletPSE PUBLIC ${KWALLET_INTERFACE_INCLUDE_DIRECTORIES})
-target_include_directories(kwalletPSE PUBLIC ${Qt5GUI_INTERFACE_INCLUDE_DIRECTORIES})
+    target_link_libraries(kwalletPSE Qt5::Core)
 
-target_link_libraries(kwalletPSE Qt5::Core)
+    set(kwalletW_SRC
+        wrappers/kwallet.cpp
+    )
 
-set(kwalletW_SRC
-    wrappers/kwallet.cpp
-)
+    add_library(kwalletWrapper SHARED ${kwalletW_SRC})
+
+    target_include_directories(kwalletWrapper PUBLIC ${KWALLET_INTERFACE_INCLUDE_DIRECTORIES})
+    target_include_directories(kwalletWrapper PUBLIC ${Qt5GUI_INTERFACE_INCLUDE_DIRECTORIES})
+
+    target_link_libraries(kwalletWrapper KF5::Wallet)
+    target_link_libraries(kwalletWrapper Qt5::Core)
+
+    install(TARGETS kwalletWrapper DESTINATION ${CMAKE_INSTALL_LIBDIR})
+endif()
 
-add_library(kwalletWrapper SHARED ${kwalletW_SRC})
 
-target_include_directories(kwalletWrapper PUBLIC ${KWALLET_INTERFACE_INCLUDE_DIRECTORIES})
-target_include_directories(kwalletWrapper PUBLIC ${Qt5GUI_INTERFACE_INCLUDE_DIRECTORIES})
 
-target_link_libraries(kwalletWrapper KF5::Wallet)
-target_link_libraries(kwalletWrapper Qt5::Core)
 
-install(TARGETS kwalletWrapper DESTINATION ${CMAKE_INSTALL_LIBDIR})
diff --git a/core/passwordStorageEngines/kwallet.cpp b/core/passwordStorageEngines/kwallet.cpp
index ddfb466..fe05a2c 100644
--- a/core/passwordStorageEngines/kwallet.cpp
+++ b/core/passwordStorageEngines/kwallet.cpp
@@ -28,7 +28,7 @@ Core::PSE::KWallet::CreateFolder Core::PSE::KWallet::createFolder = 0;
 Core::PSE::KWallet::SetFolder Core::PSE::KWallet::setFolder = 0;
 
 Core::PSE::KWallet::SupportState Core::PSE::KWallet::sState = Core::PSE::KWallet::initial;
-QLibrary Core::PSE::KWallet::lib("./libkwalletWrapper.so");
+QLibrary Core::PSE::KWallet::lib("kwalletWrapper");
 
 Core::PSE::KWallet::KWallet():
     QObject(),
@@ -41,6 +41,11 @@ Core::PSE::KWallet::KWallet():
     if (sState == initial) {
         lib.load();
         
+        if (!lib.isLoaded()) {      //fallback from the build directory
+            lib.setFileName("./core/passwordStorageEngines/libkwalletWrapper.so");
+            lib.load();
+        }
+        
         if (lib.isLoaded()) {
             openWallet = (OpenWallet) lib.resolve("openWallet");
             networkWallet = (NetworkWallet) lib.resolve("networkWallet");
diff --git a/core/squawk.cpp b/core/squawk.cpp
index abd8977..8a486c0 100644
--- a/core/squawk.cpp
+++ b/core/squawk.cpp
@@ -27,8 +27,10 @@ Core::Squawk::Squawk(QObject* parent):
     accounts(),
     amap(),
     network(),
-    waitingForAccounts(0),
-    kwallet()
+    waitingForAccounts(0)
+#ifdef WITH_KWALLET
+    ,kwallet()
+#endif
 {
     connect(&network, &NetworkAccess::fileLocalPathResponse, this, &Squawk::fileLocalPathResponse);
     connect(&network, &NetworkAccess::downloadFileProgress, this, &Squawk::downloadFileProgress);
@@ -36,13 +38,15 @@ Core::Squawk::Squawk(QObject* parent):
     connect(&network, &NetworkAccess::uploadFileProgress, this, &Squawk::uploadFileProgress);
     connect(&network, &NetworkAccess::uploadFileError, this, &Squawk::uploadFileError);
     
-    
+#ifdef WITH_KWALLET
     if (kwallet.supportState() == PSE::KWallet::success) {
-        qDebug() << "KWallet support detected";
         connect(&kwallet, &PSE::KWallet::opened, this, &Squawk::onWalletOpened);
         connect(&kwallet, &PSE::KWallet::rejectPassword, this, &Squawk::onWalletRejectPassword);
         connect(&kwallet, &PSE::KWallet::responsePassword, this, &Squawk::onWalletResponsePassword);
+        
+        Shared::Global::setSupported("KWallet", true);
     }
+#endif
 }
 
 Core::Squawk::~Squawk()
@@ -236,9 +240,11 @@ void Core::Squawk::onAccountConnectionStateChanged(Shared::ConnectionState p_sta
             }
             break;
         case Shared::ConnectionState::connected:
+#ifdef WITH_KWALLET
             if (acc->getPasswordType() == Shared::AccountPassword::kwallet && kwallet.supportState() == PSE::KWallet::success) {
                 kwallet.requestWritePassword(acc->getName(), acc->getPassword(), true);
             }
+#endif
             break;
         default:
             break;
@@ -415,12 +421,14 @@ void Core::Squawk::modifyAccountRequest(const QString& name, const QMap<QString,
         acc->setPasswordType(Shared::Global::fromInt<Shared::AccountPassword>(mItr->toInt()));
     }
     
+#ifdef WITH_KWALLET
     if (acc->getPasswordType() == Shared::AccountPassword::kwallet 
         && kwallet.supportState() == PSE::KWallet::success 
         && !needToReconnect
     ) {
         kwallet.requestWritePassword(acc->getName(), acc->getPassword(), true);
     }
+#endif
     
     emit changeAccount(name, map);
 }
@@ -716,11 +724,15 @@ void Core::Squawk::parseAccount(
             break;
         case Shared::AccountPassword::kwallet: {
             addAccount(login, server, QString(), name, resource, passwordType);
+#ifdef WITH_KWALLET
             if (kwallet.supportState() == PSE::KWallet::success) {
                 kwallet.requestReadPassword(name);
             } else {
+#endif
                 emit requestPassword(name);
+#ifdef WITH_KWALLET
             }
+#endif
         }
     }
 }
diff --git a/core/squawk.h b/core/squawk.h
index b1fa492..31812d2 100644
--- a/core/squawk.h
+++ b/core/squawk.h
@@ -34,7 +34,9 @@
 #include "networkaccess.h"
 #include "external/simpleCrypt/simplecrypt.h"
 
+#ifdef WITH_KWALLET
 #include "passwordStorageEngines/kwallet.h"
+#endif
 
 namespace Core
 {
@@ -116,7 +118,10 @@ private:
     Shared::Availability state;
     NetworkAccess network;
     uint8_t waitingForAccounts;
+
+#ifdef WITH_KWALLET
     PSE::KWallet kwallet;
+#endif
     
 private slots:
     void addAccount(
diff --git a/shared/global.cpp b/shared/global.cpp
index c974eaf..c8e5cf2 100644
--- a/shared/global.cpp
+++ b/shared/global.cpp
@@ -24,53 +24,62 @@ Shared::Global* Shared::Global::instance = 0;
 
 Shared::Global::Global():
     availability({
-        tr("Online"), 
-        tr("Away"), 
-        tr("Absent"), 
-        tr("Busy"), 
-        tr("Chatty"), 
-        tr("Invisible"), 
-        tr("Offline")
+        tr("Online", "Availability"), 
+        tr("Away", "Availability"), 
+        tr("Absent", "Availability"), 
+        tr("Busy", "Availability"), 
+        tr("Chatty", "Availability"), 
+        tr("Invisible", "Availability"), 
+        tr("Offline", "Availability")
     }),
     connectionState({
-        tr("Disconnected"), 
-        tr("Connecting"), 
-        tr("Connected"), 
-        tr("Error")
+        tr("Disconnected", "ConnectionState"), 
+        tr("Connecting", "ConnectionState"), 
+        tr("Connected", "ConnectionState"), 
+        tr("Error", "ConnectionState")
     }),
     subscriptionState({
-        tr("None"), 
-        tr("From"), 
-        tr("To"), 
-        tr("Both"), 
-        tr("Unknown")
+        tr("None", "SubscriptionState"), 
+        tr("From", "SubscriptionState"), 
+        tr("To", "SubscriptionState"), 
+        tr("Both", "SubscriptionState"), 
+        tr("Unknown", "SubscriptionState")
     }),
     affiliation({
-        tr("Unspecified"), 
-        tr("Outcast"), 
-        tr("Nobody"), 
-        tr("Member"), 
-        tr("Admin"), 
-        tr("Owner")
+        tr("Unspecified", "Affiliation"), 
+        tr("Outcast", "Affiliation"), 
+        tr("Nobody", "Affiliation"), 
+        tr("Member", "Affiliation"), 
+        tr("Admin", "Affiliation"), 
+        tr("Owner", "Affiliation")
     }),
     role({
-        tr("Unspecified"), 
-        tr("Nobody"), 
-        tr("Visitor"),
-        tr("Participant"), 
-        tr("Moderator")
+        tr("Unspecified", "Role"), 
+        tr("Nobody", "Role"), 
+        tr("Visitor", "Role"),
+        tr("Participant", "Role"), 
+        tr("Moderator", "Role")
     }),
     messageState({
-        tr("Pending"), 
-        tr("Sent"), 
-        tr("Delivered"), 
-        tr("Error")
+        tr("Pending", "MessageState"), 
+        tr("Sent", "MessageState"), 
+        tr("Delivered", "MessageState"), 
+        tr("Error", "MessageState")
     }),
     accountPassword({
-        tr("Plain"),
-        tr("Jammed"),
-        tr("Always Ask"),
-        tr("KWallet")
+        tr("Plain", "AccountPassword"),
+        tr("Jammed", "AccountPassword"),
+        tr("Always Ask", "AccountPassword"),
+        tr("KWallet", "AccountPassword")
+    }),
+    accountPasswordDescription({
+        tr("Your password is going to be stored in config file in plain text", "AccountPasswordDescription"),
+        tr("Your password is going to be stored in config file but jammed with constant encryption key you can find in program source code. It might look like encryption but it's not", "AccountPasswordDescription"),
+        tr("Squawk is going to query you for the password on every start of the program", "AccountPasswordDescription"),
+        tr("Your password is going to be stored in KDE wallet storage (KWallet). You're going to be queried for permissions", "AccountPasswordDescription")
+    }),
+    pluginSupport({
+        {"KWallet", false}
     })
 {
     if (instance != 0) {
@@ -120,6 +129,28 @@ QString Shared::Global::getName(Shared::AccountPassword ap)
     return instance->accountPassword[static_cast<int>(ap)];
 }
 
+void Shared::Global::setSupported(const QString& pluginName, bool support)
+{
+    std::map<QString, bool>::iterator itr = instance->pluginSupport.find(pluginName);
+    if (itr != instance->pluginSupport.end()) {
+        itr->second = support;
+    }
+}
+
+bool Shared::Global::supported(const QString& pluginName)
+{
+    std::map<QString, bool>::iterator itr = instance->pluginSupport.find(pluginName);
+    if (itr != instance->pluginSupport.end()) {
+        return itr->second;
+    }
+    return false;
+}
+
+QString Shared::Global::getDescription(Shared::AccountPassword ap)
+{
+    return instance->accountPasswordDescription[static_cast<int>(ap)];
+}
+
 #define FROM_INT_INPL(Enum)                                                                 \
 template<>                                                                                  \
 Enum Shared::Global::fromInt(int src)                                                       \
diff --git a/shared/global.h b/shared/global.h
index 3ea6147..481ac01 100644
--- a/shared/global.h
+++ b/shared/global.h
@@ -45,6 +45,8 @@ namespace Shared {
         static QString getName(Message::State rl);
         static QString getName(AccountPassword ap);
         
+        static QString getDescription(AccountPassword ap);
+        
         const std::deque<QString> availability;
         const std::deque<QString> connectionState;
         const std::deque<QString> subscriptionState;
@@ -53,6 +55,11 @@ namespace Shared {
         const std::deque<QString> messageState;
         const std::deque<QString> accountPassword;
         
+        const std::deque<QString> accountPasswordDescription;
+        
+        static bool supported(const QString& pluginName);
+        static void setSupported(const QString& pluginName, bool support);
+        
         template<typename T>
         static T fromInt(int src);
         
@@ -74,6 +81,8 @@ namespace Shared {
         
     private:
         static Global* instance;
+        
+        std::map<QString, bool> pluginSupport;
     };
 }
 
diff --git a/translations/squawk.ru.ts b/translations/squawk.ru.ts
index 8a733f2..32efc46 100644
--- a/translations/squawk.ru.ts
+++ b/translations/squawk.ru.ts
@@ -67,7 +67,7 @@
     </message>
     <message>
         <source>Password storage</source>
-        <translation type="unfinished"></translation>
+        <translation>Хранение пароля</translation>
     </message>
 </context>
 <context>
@@ -122,173 +122,200 @@ p, li { white-space: pre-wrap; }
 </context>
 <context>
     <name>Global</name>
-    <message>
-        <source>Disconnected</source>
-        <translation>Отключен</translation>
-    </message>
-    <message>
-        <source>Connecting</source>
-        <translation>Подключается</translation>
-    </message>
-    <message>
-        <source>Connected</source>
-        <translation>Подключен</translation>
-    </message>
-    <message>
-        <source>Error</source>
-        <translation>Ошибка</translation>
-    </message>
     <message>
         <source>Online</source>
+        <comment>Availability</comment>
         <translation>В сети</translation>
     </message>
     <message>
         <source>Away</source>
+        <comment>Availability</comment>
         <translation>Отошел</translation>
     </message>
-    <message>
-        <source>Busy</source>
-        <translation>Занят</translation>
-    </message>
     <message>
         <source>Absent</source>
+        <comment>Availability</comment>
         <translation>Недоступен</translation>
     </message>
+    <message>
+        <source>Busy</source>
+        <comment>Availability</comment>
+        <translation>Занят</translation>
+    </message>
     <message>
         <source>Chatty</source>
+        <comment>Availability</comment>
         <translation>Готов поболтать</translation>
     </message>
     <message>
         <source>Invisible</source>
+        <comment>Availability</comment>
         <translation>Невидимый</translation>
     </message>
     <message>
         <source>Offline</source>
+        <comment>Availability</comment>
         <translation>Отключен</translation>
     </message>
+    <message>
+        <source>Disconnected</source>
+        <comment>ConnectionState</comment>
+        <translation>Отключен</translation>
+    </message>
+    <message>
+        <source>Connecting</source>
+        <comment>ConnectionState</comment>
+        <translation>Подключается</translation>
+    </message>
+    <message>
+        <source>Connected</source>
+        <comment>ConnectionState</comment>
+        <translation>Подключен</translation>
+    </message>
+    <message>
+        <source>Error</source>
+        <comment>ConnectionState</comment>
+        <translation>Ошибка</translation>
+    </message>
     <message>
         <source>None</source>
+        <comment>SubscriptionState</comment>
         <translation>Нет</translation>
     </message>
     <message>
         <source>From</source>
+        <comment>SubscriptionState</comment>
         <translation>Входящая</translation>
     </message>
     <message>
         <source>To</source>
+        <comment>SubscriptionState</comment>
         <translation>Исходящая</translation>
     </message>
     <message>
         <source>Both</source>
+        <comment>SubscriptionState</comment>
         <translation>Взаимная</translation>
     </message>
     <message>
         <source>Unknown</source>
+        <comment>SubscriptionState</comment>
         <translation>Неизвестно</translation>
     </message>
     <message>
         <source>Unspecified</source>
+        <comment>Affiliation</comment>
         <translation>Не назначено</translation>
     </message>
     <message>
         <source>Outcast</source>
+        <comment>Affiliation</comment>
         <translation>Изгой</translation>
     </message>
     <message>
         <source>Nobody</source>
+        <comment>Affiliation</comment>
         <translation>Никто</translation>
     </message>
     <message>
         <source>Member</source>
+        <comment>Affiliation</comment>
         <translation>Участник</translation>
     </message>
     <message>
         <source>Admin</source>
+        <comment>Affiliation</comment>
         <translation>Администратор</translation>
     </message>
     <message>
         <source>Owner</source>
+        <comment>Affiliation</comment>
         <translation>Владелец</translation>
     </message>
+    <message>
+        <source>Unspecified</source>
+        <comment>Role</comment>
+        <translation>Не назначено</translation>
+    </message>
+    <message>
+        <source>Nobody</source>
+        <comment>Role</comment>
+        <translation>Никто</translation>
+    </message>
     <message>
         <source>Visitor</source>
+        <comment>Role</comment>
         <translation>Гость</translation>
     </message>
     <message>
         <source>Participant</source>
+        <comment>Role</comment>
         <translation>Участник</translation>
     </message>
     <message>
         <source>Moderator</source>
+        <comment>Role</comment>
         <translation>Модератор</translation>
     </message>
-    <message>
-        <source>Not specified</source>
-        <translation type="vanished">Не указан</translation>
-    </message>
-    <message>
-        <source>Personal</source>
-        <translation type="vanished">Личный</translation>
-    </message>
-    <message>
-        <source>Business</source>
-        <translation type="vanished">Рабочий</translation>
-    </message>
-    <message>
-        <source>Fax</source>
-        <translation type="vanished">Факс</translation>
-    </message>
-    <message>
-        <source>Pager</source>
-        <translation type="vanished">Пэйджер</translation>
-    </message>
-    <message>
-        <source>Voice</source>
-        <translation type="vanished">Стационарный</translation>
-    </message>
-    <message>
-        <source>Cell</source>
-        <translation type="vanished">Мобильный</translation>
-    </message>
-    <message>
-        <source>Video</source>
-        <translation type="vanished">Видеофон</translation>
-    </message>
-    <message>
-        <source>Modem</source>
-        <translation type="vanished">Модем</translation>
-    </message>
-    <message>
-        <source>Other</source>
-        <translation type="vanished">Другой</translation>
-    </message>
     <message>
         <source>Pending</source>
-        <translation>В процессе</translation>
+        <comment>MessageState</comment>
+        <translation>В процессе отправки</translation>
     </message>
     <message>
         <source>Sent</source>
+        <comment>MessageState</comment>
         <translation>Отправлено</translation>
     </message>
     <message>
         <source>Delivered</source>
+        <comment>MessageState</comment>
         <translation>Доставлено</translation>
     </message>
+    <message>
+        <source>Error</source>
+        <comment>MessageState</comment>
+        <translation>Ошибка</translation>
+    </message>
     <message>
         <source>Plain</source>
-        <translation type="unfinished"></translation>
+        <comment>AccountPassword</comment>
+        <translation>Открытый текст</translation>
     </message>
     <message>
         <source>Jammed</source>
-        <translation type="unfinished"></translation>
-    </message>
-    <message>
-        <source>KWallet</source>
-        <translation type="unfinished"></translation>
+        <comment>AccountPassword</comment>
+        <translation>Обфусцированный</translation>
     </message>
     <message>
         <source>Always Ask</source>
-        <translation type="unfinished"></translation>
+        <comment>AccountPassword</comment>
+        <translation>Всегда спрашивать</translation>
+    </message>
+    <message>
+        <source>KWallet</source>
+        <comment>AccountPassword</comment>
+        <translation>KWallet</translation>
+    </message>
+    <message>
+        <source>Your password is going to be stored in config file but jammed with constant encryption key you can find in program source code. It might look like encryption but it&apos;s not</source>
+        <comment>AccountPasswordDescription</comment>
+        <translation>Ваш пароль будет храниться в обфусцированном виде в конфигурационном файле. Обфускация производится с помощью постоянного числа, которое можно найти в исходном коде программы. Это может и выглядит как шифрование но им не является</translation>
+    </message>
+    <message>
+        <source>Squawk is going to query you for the password on every start of the program</source>
+        <comment>AccountPasswordDescription</comment>
+        <translation>Squawk будет спрашивать пароль от этой учетной записи каждый раз при запуске</translation>
+    </message>
+    <message>
+        <source>Your password is going to be stored in config file in plain text</source>
+        <comment>AccountPasswordDescription</comment>
+        <translation>Ваш пароль будет храниться в конфигурационном файле открытым текстром</translation>
+    </message>
+    <message>
+        <source>Your password is going to be stored in KDE wallet storage (KWallet). You&apos;re going to be queried for permissions</source>
+        <comment>AccountPasswordDescription</comment>
+        <translation>Ваш пароль будет храниться в бумажнике KDE (KWallet). В первый раз программа попросит разрешения для доступа к бумажнику</translation>
     </message>
 </context>
 <context>
@@ -337,10 +364,6 @@ p, li { white-space: pre-wrap; }
 </context>
 <context>
     <name>Message</name>
-    <message>
-        <source>Download</source>
-        <translation type="vanished">Скачать</translation>
-    </message>
     <message>
         <source>Open</source>
         <translation>Открыть</translation>
@@ -383,25 +406,6 @@ You can try again</source>
         <translation>Загружается...</translation>
     </message>
 </context>
-<context>
-    <name>Models::Accounts</name>
-    <message>
-        <source>Name</source>
-        <translation type="vanished">Имя</translation>
-    </message>
-    <message>
-        <source>Server</source>
-        <translation type="vanished">Сервер</translation>
-    </message>
-    <message>
-        <source>State</source>
-        <translation type="vanished">Состояние</translation>
-    </message>
-    <message>
-        <source>Error</source>
-        <translation type="vanished">Ошибка</translation>
-    </message>
-</context>
 <context>
     <name>Models::Room</name>
     <message>
@@ -625,6 +629,14 @@ to be displayed as %1</source>
         <source>Attached file</source>
         <translation>Прикрепленный файл</translation>
     </message>
+    <message>
+        <source>Input the password for account %1</source>
+        <translation>Введите пароль для учетной записи %1</translation>
+    </message>
+    <message>
+        <source>Password for account %1</source>
+        <translation>Пароль для учетной записи %1</translation>
+    </message>
 </context>
 <context>
     <name>VCard</name>
diff --git a/ui/widgets/account.cpp b/ui/widgets/account.cpp
index d417d4f..ba3af6b 100644
--- a/ui/widgets/account.cpp
+++ b/ui/widgets/account.cpp
@@ -23,13 +23,21 @@ Account::Account():
     QDialog(),
     m_ui(new Ui::Account)
 {
-    m_ui->setupUi (this);
+    m_ui->setupUi(this);
+    
+    connect(m_ui->passwordType, qOverload<int>(&QComboBox::currentIndexChanged), this, &Account::onComboboxChange);
     
     for (int i = static_cast<int>(Shared::AccountPasswordLowest); i < static_cast<int>(Shared::AccountPasswordHighest) + 1; ++i) {
         Shared::AccountPassword ap = static_cast<Shared::AccountPassword>(i);
         m_ui->passwordType->addItem(Shared::Global::getName(ap));
     }
     m_ui->passwordType->setCurrentIndex(static_cast<int>(Shared::AccountPassword::plain));
+    
+    if (!Shared::Global::supported("KWallet")) {
+        QStandardItemModel *model = static_cast<QStandardItemModel*>(m_ui->passwordType->model());
+        QStandardItem *item = model->item(static_cast<int>(Shared::AccountPassword::kwallet));
+        item->setFlags(item->flags() & ~Qt::ItemIsEnabled);
+    }
 }
 
 Account::~Account()
@@ -63,3 +71,9 @@ void Account::setData(const QMap<QString, QVariant>& data)
     m_ui->resource->setText(data.value("resource").toString());
     m_ui->passwordType->setCurrentIndex(data.value("passwordType").toInt());
 }
+
+void Account::onComboboxChange(int index)
+{
+    QString description = Shared::Global::getDescription(Shared::Global::fromInt<Shared::AccountPassword>(index));
+    m_ui->comment->setText(description);
+}
diff --git a/ui/widgets/account.h b/ui/widgets/account.h
index 9732224..cbe8b9c 100644
--- a/ui/widgets/account.h
+++ b/ui/widgets/account.h
@@ -24,6 +24,7 @@
 #include <QMap>
 #include <QString>
 #include <QVariant>
+#include <QStandardItemModel>
 
 #include "shared/global.h"
 
@@ -44,6 +45,9 @@ public:
     void setData(const QMap<QString, QVariant>& data);
     void lockId();
 
+private slots:
+    void onComboboxChange(int index);
+    
 private:
     QScopedPointer<Ui::Account> m_ui;
 };
diff --git a/ui/widgets/account.ui b/ui/widgets/account.ui
index 28cb389..a1879bc 100644
--- a/ui/widgets/account.ui
+++ b/ui/widgets/account.ui
@@ -114,14 +114,14 @@
        </property>
       </widget>
      </item>
-     <item row="5" column="0">
+     <item row="6" column="0">
       <widget class="QLabel" name="label_5">
        <property name="text">
         <string>Resource</string>
        </property>
       </widget>
      </item>
-     <item row="5" column="1">
+     <item row="6" column="1">
       <widget class="QLineEdit" name="resource">
        <property name="toolTip">
         <string>A resource name like &quot;Home&quot; or &quot;Work&quot;</string>
@@ -141,6 +141,22 @@
      <item row="4" column="1">
       <widget class="QComboBox" name="passwordType"/>
      </item>
+     <item row="5" column="1">
+      <widget class="QLabel" name="comment">
+       <property name="sizePolicy">
+        <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+         <horstretch>0</horstretch>
+         <verstretch>0</verstretch>
+        </sizepolicy>
+       </property>
+       <property name="text">
+        <string/>
+       </property>
+       <property name="wordWrap">
+        <bool>true</bool>
+       </property>
+      </widget>
+     </item>
     </layout>
    </item>
    <item>
diff --git a/ui/widgets/accounts.cpp b/ui/widgets/accounts.cpp
index 626915e..7f4a135 100644
--- a/ui/widgets/accounts.cpp
+++ b/ui/widgets/accounts.cpp
@@ -37,6 +37,7 @@ Accounts::Accounts(Models::Accounts* p_model, QWidget *parent) :
     m_ui->tableView->setModel(model);
     connect(m_ui->tableView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &Accounts::onSelectionChanged);
     connect(p_model, &Models::Accounts::changed, this, &Accounts::updateConnectButton);
+    connect(m_ui->tableView, &QTableView::doubleClicked, this, &Accounts::onEditButton);
 }
 
 Accounts::~Accounts() = default;

From a77dfd191ab5dcf47169b3a6eee33e83afad0f38 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Sat, 11 Apr 2020 23:00:15 +0300
Subject: [PATCH 061/281] single window mode

---
 CHANGELOG.md                               |   1 +
 README.md                                  |   3 -
 core/passwordStorageEngines/CMakeLists.txt |   5 -
 ui/models/roster.cpp                       |  10 +
 ui/models/roster.h                         |   2 +
 ui/squawk.cpp                              | 212 ++++++++++++++++++---
 ui/squawk.h                                |   3 +
 ui/squawk.ui                               | 148 ++++++++++----
 ui/utils/messageline.cpp                   |   8 +-
 ui/utils/messageline.h                     |   1 +
 ui/widgets/conversation.cpp                |  14 ++
 ui/widgets/conversation.h                  |   4 +
 ui/widgets/conversation.ui                 |   8 +-
 13 files changed, 345 insertions(+), 74 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 11177e8..2fb98c7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,7 @@
 
 ## Squawk 0.1.4 (UNRELEASED)
 ### New features
+- message line now is in the same window with roster (new window dialog is still able to opened on double click)
 - several ways to manage your account password:
   - store it in plain text with the config (like it always was)
   - store it in config jammed (local hashing with the constant seed, not secure at all but might look like it is)
diff --git a/README.md b/README.md
index c820ccd..7d55eb5 100644
--- a/README.md
+++ b/README.md
@@ -67,9 +67,6 @@ Here is the list of keys you can pass to configuration phase of `cmake ..`.
 - `SYSTEM_QXMPP` - `True` tries to link against `qxmpp` installed in the system, `False` builds bundled `qxmpp` library (default is `True`)
 - `WITH_KWALLET` - `True` builds the `KWallet` capability module if `KWallet` is installed and if not goes to `False`. `False` disables `KWallet` support (default is `True`)
 
-
-Each key is supposed to be passed like that 
-
 ## License
 
 This project is licensed under the GPLv3 License - see the [LICENSE.md](LICENSE.md) file for details
diff --git a/core/passwordStorageEngines/CMakeLists.txt b/core/passwordStorageEngines/CMakeLists.txt
index 36f67b1..e824f77 100644
--- a/core/passwordStorageEngines/CMakeLists.txt
+++ b/core/passwordStorageEngines/CMakeLists.txt
@@ -1,7 +1,6 @@
 cmake_minimum_required(VERSION 3.0)
 project(pse)
 
-
 if (WITH_KWALLET) 
     set(CMAKE_AUTOMOC ON)
 
@@ -36,7 +35,3 @@ if (WITH_KWALLET)
 
     install(TARGETS kwalletWrapper DESTINATION ${CMAKE_INSTALL_LIBDIR})
 endif()
-
-
-
-
diff --git a/ui/models/roster.cpp b/ui/models/roster.cpp
index 609715f..42ea9f8 100644
--- a/ui/models/roster.cpp
+++ b/ui/models/roster.cpp
@@ -386,6 +386,16 @@ bool Models::Roster::ElId::operator <(const Models::Roster::ElId& other) const
     }
 }
 
+bool Models::Roster::ElId::operator!=(const Models::Roster::ElId& other) const
+{
+    return !(operator == (other));
+}
+
+bool Models::Roster::ElId::operator==(const Models::Roster::ElId& other) const
+{
+    return (account == other.account) && (name == other.name);
+}
+
 void Models::Roster::onAccountDataChanged(const QModelIndex& tl, const QModelIndex& br, const QVector<int>& roles)
 {
     if (tl.column() == 0) {
diff --git a/ui/models/roster.h b/ui/models/roster.h
index 50c6532..34c343c 100644
--- a/ui/models/roster.h
+++ b/ui/models/roster.h
@@ -109,6 +109,8 @@ public:
         const QString name;
         
         bool operator < (const ElId& other) const;
+        bool operator == (const ElId& other) const;
+        bool operator != (const ElId& other) const;
     };
 };
 
diff --git a/ui/squawk.cpp b/ui/squawk.cpp
index 3a11c1a..120123a 100644
--- a/ui/squawk.cpp
+++ b/ui/squawk.cpp
@@ -32,7 +32,8 @@ Squawk::Squawk(QWidget *parent) :
     requestedFiles(),
     vCards(),
     requestedAccountsForPasswords(),
-    prompt(0)
+    prompt(0),
+    currentConversation(0)
 {
     m_ui->setupUi(this);
     m_ui->roster->setModel(&rosterModel);
@@ -55,6 +56,7 @@ Squawk::Squawk(QWidget *parent) :
     connect(m_ui->roster, &QTreeView::doubleClicked, this, &Squawk::onRosterItemDoubleClicked);
     connect(m_ui->roster, &QTreeView::customContextMenuRequested, this, &Squawk::onRosterContextMenu);
     connect(m_ui->roster, &QTreeView::collapsed, this, &Squawk::onItemCollepsed);
+    connect(m_ui->roster->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &Squawk::onRosterSelectionChanged);
     
     connect(rosterModel.accountsModel, &Models::Accounts::sizeChanged, this, &Squawk::onAccountsSizeChanged);
     //m_ui->mainToolBar->addWidget(m_ui->comboBox);
@@ -74,6 +76,10 @@ Squawk::Squawk(QWidget *parent) :
         restoreState(settings.value("state").toByteArray());
     }
     settings.endGroup();
+    
+    if (settings.contains("splitter")) {
+        m_ui->splitter->restoreState(settings.value("splitter").toByteArray());
+    }
     settings.endGroup();
 }
 
@@ -344,16 +350,7 @@ void Squawk::onRosterItemDoubleClicked(const QModelIndex& item)
             if (conv != 0) {
                 if (created) {
                     conv->setAttribute(Qt::WA_DeleteOnClose);
-                    
-                    connect(conv, &Conversation::destroyed, this, &Squawk::onConversationClosed);
-                    connect(conv, qOverload<const Shared::Message&>(&Conversation::sendMessage), this, qOverload<const Shared::Message&>(&Squawk::onConversationMessage));
-                    connect(conv, qOverload<const Shared::Message&, const QString&>(&Conversation::sendMessage), 
-                            this, qOverload<const Shared::Message&, const QString&>(&Squawk::onConversationMessage));
-                    connect(conv, &Conversation::requestArchive, this, &Squawk::onConversationRequestArchive);
-                    connect(conv, &Conversation::requestLocalFile, this, &Squawk::onConversationRequestLocalFile);
-                    connect(conv, &Conversation::downloadFile, this, &Squawk::onConversationDownloadFile);
-                    connect(conv, &Conversation::shown, this, &Squawk::onConversationShown);
-                    
+                    subscribeConversation(conv);
                     conversations.insert(std::make_pair(*id, conv));
                     
                     if (created) {
@@ -372,6 +369,7 @@ void Squawk::onRosterItemDoubleClicked(const QModelIndex& item)
                 }
             }
             
+            delete id;
         }
     }
 }
@@ -387,9 +385,8 @@ void Squawk::onConversationClosed(QObject* parent)
     Conversation* conv = static_cast<Conversation*>(sender());
     Models::Roster::ElId id(conv->getAccount(), conv->getJid());
     Conversations::const_iterator itr = conversations.find(id);
-    if (itr == conversations.end()) {
-        qDebug() << "Conversation has been closed but can not be found among other opened conversations, application is most probably going to crash";
-        return;
+    if (itr != conversations.end()) {
+        conversations.erase(itr);
     }
     if (conv->isMuc) {
         Room* room = static_cast<Room*>(conv);
@@ -397,7 +394,6 @@ void Squawk::onConversationClosed(QObject* parent)
             emit setRoomJoined(id.account, id.name, false);
         }
     }
-    conversations.erase(itr);
 }
 
 void Squawk::onConversationDownloadFile(const QString& messageId, const QString& url)
@@ -429,6 +425,9 @@ void Squawk::fileProgress(const QString& messageId, qreal value)
             if (c != conversations.end()) {
                 c->second->responseFileProgress(messageId, value);
             }
+            if (currentConversation != 0 && currentConversation->getId() == id) {
+                currentConversation->responseFileProgress(messageId, value);
+            }
         }
     }
 }
@@ -447,6 +446,9 @@ void Squawk::fileError(const QString& messageId, const QString& error)
             if (c != conversations.end()) {
                 c->second->fileError(messageId, error);
             }
+            if (currentConversation != 0 && currentConversation->getId() == id) {
+                currentConversation->fileError(messageId, error);
+            }
         }
         requestedFiles.erase(itr);
     }
@@ -466,6 +468,9 @@ void Squawk::fileLocalPathResponse(const QString& messageId, const QString& path
             if (c != conversations.end()) {
                 c->second->responseLocalFile(messageId, path);
             }
+            if (currentConversation != 0 && currentConversation->getId() == id) {
+                currentConversation->responseLocalFile(messageId, path);
+            }
         }
         
         requestedFiles.erase(itr);
@@ -490,18 +495,33 @@ void Squawk::onConversationRequestLocalFile(const QString& messageId, const QStr
 void Squawk::accountMessage(const QString& account, const Shared::Message& data)
 {
     const QString& from = data.getPenPalJid();
-    Conversations::iterator itr = conversations.find({account, from});
+    Models::Roster::ElId id({account, from});
+    Conversations::iterator itr = conversations.find(id);
+    bool found = false;
+    
+    if (currentConversation != 0 && currentConversation->getId() == id) {
+        currentConversation->addMessage(data);
+        QApplication::alert(this);
+        if (!isVisible() && !data.getForwarded()) {
+            notify(account, data);
+        }
+        found = true;
+    }
+    
     if (itr != conversations.end()) {
         Conversation* conv = itr->second;
         conv->addMessage(data);
         QApplication::alert(conv);
-        if (conv->isMinimized()) {
+        if (!found && conv->isMinimized()) {
             rosterModel.addMessage(account, data);
         }
         if (!conv->isVisible() && !data.getForwarded()) {
             notify(account, data);
         }
-    } else {
+        found = true;
+    }
+    
+    if (!found) {
         rosterModel.addMessage(account, data);
         if (!data.getForwarded()) {
             QApplication::alert(this);
@@ -512,14 +532,26 @@ void Squawk::accountMessage(const QString& account, const Shared::Message& data)
 
 void Squawk::changeMessage(const QString& account, const QString& jid, const QString& id, const QMap<QString, QVariant>& data)
 {
-    Conversations::iterator itr = conversations.find({account, jid});
+    Models::Roster::ElId eid({account, jid});
+    bool found = false;
+    
+    if (currentConversation != 0 && currentConversation->getId() == eid) {
+        currentConversation->changeMessage(id, data);
+        QApplication::alert(this);
+        found = true;
+    }
+    
+    Conversations::iterator itr = conversations.find(eid);
     if (itr != conversations.end()) {
         Conversation* conv = itr->second;
         conv->changeMessage(id, data);
-        if (conv->isMinimized()) {
+        if (!found && conv->isMinimized()) {
             rosterModel.changeMessage(account, jid, id, data);
         }
-    } else {
+        found = true;
+    } 
+    
+    if (!found) {
         rosterModel.changeMessage(account, jid, id, data);
     }
 }
@@ -559,13 +591,37 @@ void Squawk::onConversationMessage(const Shared::Message& msg)
 {
     Conversation* conv = static_cast<Conversation*>(sender());
     emit sendMessage(conv->getAccount(), msg);
+    Models::Roster::ElId id = conv->getId();
+    
+    if (currentConversation != 0 && currentConversation->getId() == id) {
+        if (conv == currentConversation) {
+            Conversations::iterator itr = conversations.find(id);
+            if (itr != conversations.end()) {
+                itr->second->addMessage(msg);
+            }
+        } else {
+            currentConversation->addMessage(msg);
+        }
+    }
 }
 
 void Squawk::onConversationMessage(const Shared::Message& msg, const QString& path)
 {
     Conversation* conv = static_cast<Conversation*>(sender());
+    Models::Roster::ElId id = conv->getId();
     std::map<QString, std::set<Models::Roster::ElId>>::iterator itr = requestedFiles.insert(std::make_pair(msg.getId(), std::set<Models::Roster::ElId>())).first;
-    itr->second.insert(Models::Roster::ElId(conv->getAccount(), conv->getJid()));
+    itr->second.insert(id);
+    
+    if (currentConversation != 0 && currentConversation->getId() == id) {
+        if (conv == currentConversation) {
+            Conversations::iterator itr = conversations.find(id);
+            if (itr != conversations.end()) {
+                itr->second->appendMessageWithUpload(msg, path);
+            }
+        } else {
+            currentConversation->appendMessageWithUpload(msg, path);
+        }
+    }
     
     emit sendMessage(conv->getAccount(), msg, path);
 }
@@ -580,6 +636,10 @@ void Squawk::responseArchive(const QString& account, const QString& jid, const s
 {
     Models::Roster::ElId id(account, jid);
     
+    if (currentConversation != 0 && currentConversation->getId() == id) {
+        currentConversation->responseArchive(list);
+    }
+    
     Conversations::const_iterator itr = conversations.find(id);
     if (itr != conversations.end()) {
         itr->second->responseArchive(list);
@@ -603,6 +663,13 @@ void Squawk::removeAccount(const QString& account)
             ++itr;
         }
     }
+    
+    if (currentConversation != 0 && currentConversation->getAccount() == account) {
+        currentConversation->deleteLater();
+        currentConversation = 0;
+        m_ui->filler->show();
+    }
+    
     rosterModel.removeAccount(account);
 }
 
@@ -761,7 +828,9 @@ void Squawk::onRosterContextMenu(const QPoint& point)
                     unsub->setEnabled(active);
                     connect(unsub, &QAction::triggered, [this, id]() {
                         emit setRoomAutoJoin(id.account, id.name, false);
-                        if (conversations.find(id) == conversations.end()) {    //to leave the room if it's not opened in a conversation window
+                        if (conversations.find(id) == conversations.end()
+                            && (currentConversation == 0 || currentConversation->getId() != id)
+                        ) {    //to leave the room if it's not opened in a conversation window
                             emit setRoomJoined(id.account, id.name, false);
                         }
                     });
@@ -770,7 +839,9 @@ void Squawk::onRosterContextMenu(const QPoint& point)
                     unsub->setEnabled(active);
                     connect(unsub, &QAction::triggered, [this, id]() {
                         emit setRoomAutoJoin(id.account, id.name, true);
-                        if (conversations.find(id) == conversations.end()) {    //to join the room if it's not already joined
+                        if (conversations.find(id) == conversations.end()
+                            && (currentConversation == 0 || currentConversation->getId() != id)
+                        ) {    //to join the room if it's not already joined
                             emit setRoomJoined(id.account, id.name, true);
                         }
                     });
@@ -897,7 +968,6 @@ void Squawk::readSettings()
         }                                                               //      need to fix that
         settings.endArray();
     }
-    
     settings.endGroup();
 }
 
@@ -910,6 +980,8 @@ void Squawk::writeSettings()
     settings.setValue("state", saveState());
     settings.endGroup();
     
+    settings.setValue("splitter", m_ui->splitter->saveState());
+    
     settings.setValue("availability", m_ui->comboBox->currentIndex());
     settings.beginWriteArray("connectedAccounts");
     int size = rosterModel.accountsModel->rowCount(QModelIndex());
@@ -1008,3 +1080,93 @@ void Squawk::onPasswordPromptRejected()
     emit responsePassword(requestedAccountsForPasswords.front(), prompt->textValue());
     onPasswordPromptDone();
 }
+
+void Squawk::subscribeConversation(Conversation* conv)
+{
+    connect(conv, &Conversation::destroyed, this, &Squawk::onConversationClosed);
+    connect(conv, qOverload<const Shared::Message&>(&Conversation::sendMessage), this, qOverload<const Shared::Message&>(&Squawk::onConversationMessage));
+    connect(conv, qOverload<const Shared::Message&, const QString&>(&Conversation::sendMessage), 
+            this, qOverload<const Shared::Message&, const QString&>(&Squawk::onConversationMessage));
+    connect(conv, &Conversation::requestArchive, this, &Squawk::onConversationRequestArchive);
+    connect(conv, &Conversation::requestLocalFile, this, &Squawk::onConversationRequestLocalFile);
+    connect(conv, &Conversation::downloadFile, this, &Squawk::onConversationDownloadFile);
+    connect(conv, &Conversation::shown, this, &Squawk::onConversationShown);
+}
+
+void Squawk::onRosterSelectionChanged(const QModelIndex& current, const QModelIndex& previous)
+{
+    if (current.isValid()) {
+        Models::Item* node = static_cast<Models::Item*>(current.internalPointer());
+        Models::Contact* contact = 0;
+        Models::Room* room = 0;
+        QString res;
+        Models::Roster::ElId* id = 0;
+        switch (node->type) {
+            case Models::Item::contact:
+                contact = static_cast<Models::Contact*>(node);
+                id = new Models::Roster::ElId(contact->getAccountName(), contact->getJid());
+                break;
+            case Models::Item::presence:
+                contact = static_cast<Models::Contact*>(node->parentItem());
+                id = new Models::Roster::ElId(contact->getAccountName(), contact->getJid());
+                res = node->getName();
+                break;
+            case Models::Item::room:
+                room = static_cast<Models::Room*>(node);
+                id = new Models::Roster::ElId(room->getAccountName(), room->getJid());
+                break;
+            default:
+                break;
+        }
+        
+        if (id != 0) {
+            if (currentConversation != 0) {
+                if (currentConversation->getJid() == id->name) {
+                    if (contact != 0) {
+                        currentConversation->setPalResource(res);
+                    }
+                } else {
+                    currentConversation->deleteLater();
+                }
+            } else {
+                m_ui->filler->hide();
+            }
+            
+            Models::Account* acc = rosterModel.getAccount(id->account);
+            Models::Contact::Messages deque;
+            if (contact != 0) {
+                currentConversation = new Chat(acc, contact);
+                contact->getMessages(deque);
+            } else if (room != 0) {
+                currentConversation = new Room(acc, room);
+                room->getMessages(deque);
+                
+                if (!room->getJoined()) {
+                    emit setRoomJoined(id->account, id->name, true);
+                }
+            }
+            if (!testAttribute(Qt::WA_TranslucentBackground)) {
+                currentConversation->setFeedFrames(true, false, true, true);
+            }
+            
+            subscribeConversation(currentConversation);
+            for (Models::Contact::Messages::const_iterator itr = deque.begin(), end = deque.end(); itr != end; ++itr) {
+                currentConversation->addMessage(*itr);
+            }
+            
+            if (res.size() > 0) {
+                currentConversation->setPalResource(res);
+            }
+            
+            m_ui->splitter->insertWidget(1, currentConversation);
+            
+            delete id;
+        } else {
+            if (currentConversation != 0) {
+                currentConversation->deleteLater();
+                currentConversation = 0;
+                m_ui->filler->show();
+            }
+        }
+    }
+}
diff --git a/ui/squawk.h b/ui/squawk.h
index d5bde9c..5b3d7cd 100644
--- a/ui/squawk.h
+++ b/ui/squawk.h
@@ -124,6 +124,7 @@ private:
     std::map<QString, VCard*> vCards;
     std::deque<QString> requestedAccountsForPasswords;
     QInputDialog* prompt;
+    Conversation* currentConversation;
     
 protected:
     void closeEvent(QCloseEvent * event) override;
@@ -153,10 +154,12 @@ private slots:
     void onItemCollepsed(const QModelIndex& index);
     void onPasswordPromptAccepted();
     void onPasswordPromptRejected();
+    void onRosterSelectionChanged(const QModelIndex& current, const QModelIndex& previous);
     
 private:
     void checkNextAccountForPassword();
     void onPasswordPromptDone();
+    void subscribeConversation(Conversation* conv);
 };
 
 #endif // SQUAWK_H
diff --git a/ui/squawk.ui b/ui/squawk.ui
index e09cd19..6c50024 100644
--- a/ui/squawk.ui
+++ b/ui/squawk.ui
@@ -6,8 +6,8 @@
    <rect>
     <x>0</x>
     <y>0</y>
-    <width>385</width>
-    <height>508</height>
+    <width>718</width>
+    <height>720</height>
    </rect>
   </property>
   <property name="windowTitle">
@@ -17,7 +17,7 @@
    <enum>Qt::ToolButtonFollowStyle</enum>
   </property>
   <widget class="QWidget" name="centralWidget">
-   <layout class="QGridLayout" name="gridLayout">
+   <layout class="QVBoxLayout" name="verticalLayout">
     <property name="leftMargin">
      <number>0</number>
     </property>
@@ -30,42 +30,112 @@
     <property name="bottomMargin">
      <number>0</number>
     </property>
-    <item row="0" column="0">
-     <widget class="QComboBox" name="comboBox">
-      <property name="editable">
+    <item>
+     <widget class="QSplitter" name="splitter">
+      <property name="orientation">
+       <enum>Qt::Horizontal</enum>
+      </property>
+      <property name="handleWidth">
+       <number>1</number>
+      </property>
+      <property name="childrenCollapsible">
        <bool>false</bool>
       </property>
-      <property name="currentText">
-       <string/>
-      </property>
-      <property name="currentIndex">
-       <number>-1</number>
-      </property>
-     </widget>
-    </item>
-    <item row="1" column="0">
-     <widget class="QTreeView" name="roster">
-      <property name="frameShape">
-       <enum>QFrame::NoFrame</enum>
-      </property>
-      <property name="frameShadow">
-       <enum>QFrame::Sunken</enum>
-      </property>
-      <property name="uniformRowHeights">
-       <bool>true</bool>
-      </property>
-      <property name="animated">
-       <bool>true</bool>
-      </property>
-      <property name="allColumnsShowFocus">
-       <bool>true</bool>
-      </property>
-      <property name="expandsOnDoubleClick">
-       <bool>false</bool>
-      </property>
-      <attribute name="headerVisible">
-       <bool>false</bool>
-      </attribute>
+      <widget class="QWidget" name="widget" native="true">
+       <property name="sizePolicy">
+        <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+         <horstretch>0</horstretch>
+         <verstretch>0</verstretch>
+        </sizepolicy>
+       </property>
+       <property name="minimumSize">
+        <size>
+         <width>200</width>
+         <height>0</height>
+        </size>
+       </property>
+       <property name="maximumSize">
+        <size>
+         <width>400</width>
+         <height>16777215</height>
+        </size>
+       </property>
+       <layout class="QGridLayout" name="gridLayout_2">
+        <property name="leftMargin">
+         <number>0</number>
+        </property>
+        <property name="topMargin">
+         <number>0</number>
+        </property>
+        <property name="rightMargin">
+         <number>0</number>
+        </property>
+        <property name="bottomMargin">
+         <number>0</number>
+        </property>
+        <item row="2" column="1">
+         <widget class="QTreeView" name="roster">
+          <property name="frameShape">
+           <enum>QFrame::NoFrame</enum>
+          </property>
+          <property name="frameShadow">
+           <enum>QFrame::Sunken</enum>
+          </property>
+          <property name="uniformRowHeights">
+           <bool>true</bool>
+          </property>
+          <property name="animated">
+           <bool>true</bool>
+          </property>
+          <property name="allColumnsShowFocus">
+           <bool>true</bool>
+          </property>
+          <property name="expandsOnDoubleClick">
+           <bool>false</bool>
+          </property>
+          <attribute name="headerVisible">
+           <bool>false</bool>
+          </attribute>
+         </widget>
+        </item>
+        <item row="1" column="1">
+         <widget class="QComboBox" name="comboBox">
+          <property name="editable">
+           <bool>false</bool>
+          </property>
+          <property name="currentText">
+           <string/>
+          </property>
+          <property name="currentIndex">
+           <number>-1</number>
+          </property>
+         </widget>
+        </item>
+       </layout>
+      </widget>
+      <widget class="QWidget" name="filler" native="true">
+       <property name="sizePolicy">
+        <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+         <horstretch>2</horstretch>
+         <verstretch>0</verstretch>
+        </sizepolicy>
+       </property>
+       <layout class="QGridLayout" name="gridLayout">
+        <item row="0" column="0">
+         <widget class="QLabel" name="label">
+          <property name="text">
+           <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:26pt;&quot;&gt;Please select a contact to start chatting&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+          </property>
+          <property name="alignment">
+           <set>Qt::AlignCenter</set>
+          </property>
+          <property name="wordWrap">
+           <bool>true</bool>
+          </property>
+         </widget>
+        </item>
+       </layout>
+      </widget>
      </widget>
     </item>
    </layout>
@@ -75,8 +145,8 @@
     <rect>
      <x>0</x>
      <y>0</y>
-     <width>385</width>
-     <height>22</height>
+     <width>718</width>
+     <height>27</height>
     </rect>
    </property>
    <widget class="QMenu" name="menuSettings">
diff --git a/ui/utils/messageline.cpp b/ui/utils/messageline.cpp
index deb9a45..ecd10a1 100644
--- a/ui/utils/messageline.cpp
+++ b/ui/utils/messageline.cpp
@@ -427,6 +427,12 @@ void MessageLine::fileError(const QString& messageId, const QString& error)
 }
 
 void MessageLine::appendMessageWithUpload(const Shared::Message& msg, const QString& path)
+{
+    appendMessageWithUploadNoSiganl(msg, path);
+    emit uploadFile(msg, path);
+}
+
+void MessageLine::appendMessageWithUploadNoSiganl(const Shared::Message& msg, const QString& path)
 {
     message(msg, true);
     QString id = msg.getId();
@@ -436,9 +442,9 @@ void MessageLine::appendMessageWithUpload(const Shared::Message& msg, const QStr
     ui->showComment(tr("Uploading..."));
     uploading.insert(std::make_pair(id, ui));
     uploadPaths.insert(std::make_pair(id, path));
-    emit uploadFile(msg, path);
 }
 
+
 void MessageLine::onUpload()
 {
     //TODO retry
diff --git a/ui/utils/messageline.h b/ui/utils/messageline.h
index 277d429..104dc72 100644
--- a/ui/utils/messageline.h
+++ b/ui/utils/messageline.h
@@ -53,6 +53,7 @@ public:
     void fileError(const QString& messageId, const QString& error);
     void fileProgress(const QString& messageId, qreal progress);
     void appendMessageWithUpload(const Shared::Message& msg, const QString& path);
+    void appendMessageWithUploadNoSiganl(const Shared::Message& msg, const QString& path);
     void removeMessage(const QString& messageId);
     void setMyAvatarPath(const QString& p_path);
     void setPalAvatar(const QString& jid, const QString& path);
diff --git a/ui/widgets/conversation.cpp b/ui/widgets/conversation.cpp
index 13cb881..d413506 100644
--- a/ui/widgets/conversation.cpp
+++ b/ui/widgets/conversation.cpp
@@ -210,6 +210,11 @@ void Conversation::onEnterPressed()
     }
 }
 
+void Conversation::appendMessageWithUpload(const Shared::Message& data, const QString& path)
+{
+    line->appendMessageWithUploadNoSiganl(data, path);
+}
+
 void Conversation::onMessagesResize(int amount)
 {
     manualSliderChange = true;
@@ -334,6 +339,11 @@ void Conversation::responseLocalFile(const QString& messageId, const QString& pa
     line->responseLocalFile(messageId, path);
 }
 
+Models::Roster::ElId Conversation::getId() const
+{
+    return {getAccount(), getJid()};
+}
+
 void Conversation::addAttachedFile(const QString& path)
 {
     QMimeDatabase db;
@@ -397,6 +407,10 @@ void Conversation::onTextEditDocSizeChanged(const QSizeF& size)
     m_ui->messageEditor->setMaximumHeight(int(size.height()));
 }
 
+void Conversation::setFeedFrames(bool top, bool right, bool bottom, bool left)
+{
+    static_cast<DropShadowEffect*>(m_ui->scrollArea->graphicsEffect())->setFrame(top, right, bottom, left);
+}
 
 bool VisibilityCatcher::eventFilter(QObject* obj, QEvent* event)
 {
diff --git a/ui/widgets/conversation.h b/ui/widgets/conversation.h
index 6ce8cad..075bc31 100644
--- a/ui/widgets/conversation.h
+++ b/ui/widgets/conversation.h
@@ -26,6 +26,7 @@
 #include "shared/message.h"
 #include "order.h"
 #include "ui/models/account.h"
+#include "ui/models/roster.h"
 #include "ui/utils/messageline.h"
 #include "ui/utils/resizer.h"
 #include "ui/utils/flowlayout.h"
@@ -72,6 +73,7 @@ public:
     QString getJid() const;
     QString getAccount() const;
     QString getPalResource() const;
+    Models::Roster::ElId getId() const;
     virtual void addMessage(const Shared::Message& data);
     
     void setPalResource(const QString& res);
@@ -82,6 +84,8 @@ public:
     void responseFileProgress(const QString& messageId, qreal progress);
     virtual void setAvatar(const QString& path);
     void changeMessage(const QString& id, const QMap<QString, QVariant>& data);
+    void setFeedFrames(bool top, bool right, bool bottom, bool left);
+    virtual void appendMessageWithUpload(const Shared::Message& data, const QString& path);
     
 signals:
     void sendMessage(const Shared::Message& message);
diff --git a/ui/widgets/conversation.ui b/ui/widgets/conversation.ui
index 4ff8b34..9b1321e 100644
--- a/ui/widgets/conversation.ui
+++ b/ui/widgets/conversation.ui
@@ -10,6 +10,12 @@
     <height>658</height>
    </rect>
   </property>
+  <property name="sizePolicy">
+   <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+    <horstretch>2</horstretch>
+    <verstretch>0</verstretch>
+   </sizepolicy>
+  </property>
   <layout class="QVBoxLayout" name="horizontalLayout">
    <property name="spacing">
     <number>0</number>
@@ -206,7 +212,7 @@
            <x>0</x>
            <y>0</y>
            <width>520</width>
-           <height>389</height>
+           <height>392</height>
           </rect>
          </property>
          <layout class="QHBoxLayout" name="horizontalLayout_2">

From 29c7d31c895e9a3c7009f06c0930ffeb2deb0af2 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Sun, 12 Apr 2020 18:55:05 +0300
Subject: [PATCH 062/281] context menu now doesn't select items, just
 temporarily, statuses and messahes html wrapping fix

---
 CHANGELOG.md                |  8 ++++---
 shared/utils.cpp            | 19 ++++++++++++++++
 shared/utils.h              |  2 ++
 ui/squawk.cpp               | 44 +++++++++++++++++++++++++++++++++----
 ui/squawk.h                 |  2 ++
 ui/squawk.ui                |  6 +++++
 ui/utils/message.cpp        | 29 ++++++------------------
 ui/utils/message.h          |  1 +
 ui/widgets/conversation.cpp |  3 +--
 ui/widgets/conversation.h   |  2 ++
 ui/widgets/conversation.ui  | 20 ++++++++++++++++-
 11 files changed, 104 insertions(+), 32 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2fb98c7..c8c5e75 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,16 +2,18 @@
 
 ## Squawk 0.1.4 (UNRELEASED)
 ### New features
-- message line now is in the same window with roster (new window dialog is still able to opened on double click)
-- several ways to manage your account password:
+- message line now is in the same window with roster (new window dialog is still able to opened on context menu)
+- several new ways to manage your account password:
   - store it in plain text with the config (like it always was)
   - store it in config jammed (local hashing with the constant seed, not secure at all but might look like it is)
   - ask the account password on each program launch
   - store it in KWallet which is dynamically loaded
   
 ### Bug fixes
-- never updating MUC avatars now update
+- never updating MUC avatars now get updated
 - going offline related segfault fix
+- statuses now behave better: they wrap if they don't fit, you can select them, you can follow links from there
+- messages and statuses don't loose content if you use < ore > symbols
 
 
 ## Squawk 0.1.3 (Mar 31, 2020)
diff --git a/shared/utils.cpp b/shared/utils.cpp
index 776c052..06247f8 100644
--- a/shared/utils.cpp
+++ b/shared/utils.cpp
@@ -27,3 +27,22 @@ QString Shared::generateUUID()
     uuid_unparse_lower(uuid, uuid_str);
     return uuid_str;
 }
+
+
+static const QRegularExpression urlReg("(?<!<a\\shref=['\"])(?<!<img\\ssrc=['\"])("
+                                "(?:https?|ftp):\\/\\/"
+                                    "\\w+"
+                                    "(?:"
+                                        "[\\w\\.\\/\\:\\;\\?\\&\\=\\@\\%\\#\\+\\-]?"
+                                        "(?:"
+                                            "\\([\\w\\.\\/\\:\\;\\?\\&\\=\\@\\%\\#\\+\\-]+\\)"
+                                        ")?"
+                                    ")*"
+                                ")");
+
+QString Shared::processMessageBody(const QString& msg)
+{
+    QString processed = msg.toHtmlEscaped();
+    processed.replace(urlReg, "<a href=\"\\1\">\\1</a>");
+    return "<p style=\"white-space: pre-wrap;\">" + processed + "</p>";
+}
diff --git a/shared/utils.h b/shared/utils.h
index 218b53a..e9e3d29 100644
--- a/shared/utils.h
+++ b/shared/utils.h
@@ -21,6 +21,7 @@
 
 #include <QString>
 #include <QColor>
+#include <QRegularExpression>
 
 #include <uuid/uuid.h>
 #include <vector>
@@ -28,6 +29,7 @@
 namespace Shared {
 
 QString generateUUID();
+QString processMessageBody(const QString& msg);
 
 static const std::vector<QColor> colorPalette = {
     QColor(244, 27, 63),
diff --git a/ui/squawk.cpp b/ui/squawk.cpp
index 120123a..3e1eb9e 100644
--- a/ui/squawk.cpp
+++ b/ui/squawk.cpp
@@ -33,7 +33,8 @@ Squawk::Squawk(QWidget *parent) :
     vCards(),
     requestedAccountsForPasswords(),
     prompt(0),
-    currentConversation(0)
+    currentConversation(0),
+    restoreSelection()
 {
     m_ui->setupUi(this);
     m_ui->roster->setModel(&rosterModel);
@@ -53,12 +54,13 @@ Squawk::Squawk(QWidget *parent) :
     connect(m_ui->actionAddContact, &QAction::triggered, this, &Squawk::onNewContact);
     connect(m_ui->actionAddConference, &QAction::triggered, this, &Squawk::onNewConference);
     connect(m_ui->comboBox, qOverload<int>(&QComboBox::activated), this, &Squawk::onComboboxActivated);
-    connect(m_ui->roster, &QTreeView::doubleClicked, this, &Squawk::onRosterItemDoubleClicked);
+    //connect(m_ui->roster, &QTreeView::doubleClicked, this, &Squawk::onRosterItemDoubleClicked);
     connect(m_ui->roster, &QTreeView::customContextMenuRequested, this, &Squawk::onRosterContextMenu);
     connect(m_ui->roster, &QTreeView::collapsed, this, &Squawk::onItemCollepsed);
     connect(m_ui->roster->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &Squawk::onRosterSelectionChanged);
     
     connect(rosterModel.accountsModel, &Models::Accounts::sizeChanged, this, &Squawk::onAccountsSizeChanged);
+    connect(contextMenu, &QMenu::aboutToHide, this, &Squawk::onContextAboutToHide);
     //m_ui->mainToolBar->addWidget(m_ui->comboBox);
     
     setWindowTitle(tr("Contact list"));
@@ -1094,13 +1096,19 @@ void Squawk::subscribeConversation(Conversation* conv)
 }
 
 void Squawk::onRosterSelectionChanged(const QModelIndex& current, const QModelIndex& previous)
-{
+{   
+    if (restoreSelection.isValid() && restoreSelection == current) {
+        restoreSelection = QModelIndex();
+        return;
+    }
+    
     if (current.isValid()) {
         Models::Item* node = static_cast<Models::Item*>(current.internalPointer());
         Models::Contact* contact = 0;
         Models::Room* room = 0;
         QString res;
         Models::Roster::ElId* id = 0;
+        bool hasContext = true;
         switch (node->type) {
             case Models::Item::contact:
                 contact = static_cast<Models::Contact*>(node);
@@ -1110,21 +1118,38 @@ void Squawk::onRosterSelectionChanged(const QModelIndex& current, const QModelIn
                 contact = static_cast<Models::Contact*>(node->parentItem());
                 id = new Models::Roster::ElId(contact->getAccountName(), contact->getJid());
                 res = node->getName();
+                hasContext = false;
                 break;
             case Models::Item::room:
                 room = static_cast<Models::Room*>(node);
                 id = new Models::Roster::ElId(room->getAccountName(), room->getJid());
                 break;
+            case Models::Item::participant:
+                room = static_cast<Models::Room*>(node->parentItem());
+                id = new Models::Roster::ElId(room->getAccountName(), room->getJid());
+                hasContext = false;
+                break;
+            case Models::Item::group:
+                hasContext = false;
             default:
                 break;
         }
         
+        if (hasContext && QGuiApplication::mouseButtons() & Qt::RightButton) {
+            if (id != 0) {
+                delete id;
+            }
+            restoreSelection = previous;
+            return;
+        }
+        
         if (id != 0) {
             if (currentConversation != 0) {
-                if (currentConversation->getJid() == id->name) {
+                if (currentConversation->getId() == *id) {
                     if (contact != 0) {
                         currentConversation->setPalResource(res);
                     }
+                    return;
                 } else {
                     currentConversation->deleteLater();
                 }
@@ -1168,5 +1193,16 @@ void Squawk::onRosterSelectionChanged(const QModelIndex& current, const QModelIn
                 m_ui->filler->show();
             }
         }
+    } else {
+        if (currentConversation != 0) {
+            currentConversation->deleteLater();
+            currentConversation = 0;
+            m_ui->filler->show();
+        }
     }
 }
+
+void Squawk::onContextAboutToHide()
+{
+    m_ui->roster->selectionModel()->setCurrentIndex(restoreSelection, QItemSelectionModel::ClearAndSelect);
+}
diff --git a/ui/squawk.h b/ui/squawk.h
index 5b3d7cd..28a2f17 100644
--- a/ui/squawk.h
+++ b/ui/squawk.h
@@ -125,6 +125,7 @@ private:
     std::deque<QString> requestedAccountsForPasswords;
     QInputDialog* prompt;
     Conversation* currentConversation;
+    QModelIndex restoreSelection;
     
 protected:
     void closeEvent(QCloseEvent * event) override;
@@ -155,6 +156,7 @@ private slots:
     void onPasswordPromptAccepted();
     void onPasswordPromptRejected();
     void onRosterSelectionChanged(const QModelIndex& current, const QModelIndex& previous);
+    void onContextAboutToHide();
     
 private:
     void checkNextAccountForPassword();
diff --git a/ui/squawk.ui b/ui/squawk.ui
index 6c50024..19fdc4f 100644
--- a/ui/squawk.ui
+++ b/ui/squawk.ui
@@ -81,6 +81,12 @@
           <property name="frameShadow">
            <enum>QFrame::Sunken</enum>
           </property>
+          <property name="editTriggers">
+           <set>QAbstractItemView::NoEditTriggers</set>
+          </property>
+          <property name="showDropIndicator" stdset="0">
+           <bool>false</bool>
+          </property>
           <property name="uniformRowHeights">
            <bool>true</bool>
           </property>
diff --git a/ui/utils/message.cpp b/ui/utils/message.cpp
index c5149ef..7a004bb 100644
--- a/ui/utils/message.cpp
+++ b/ui/utils/message.cpp
@@ -23,18 +23,6 @@
 #include <QFileInfo>
 #include <QRegularExpression>
 
-const QRegularExpression urlReg("(?<!<a\\shref=['\"])(?<!<img\\ssrc=['\"])("
-                                "(?:https?|ftp):\\/\\/"
-                                    "\\w+"
-                                    "(?:"
-                                        "[\\w\\.\\/\\:\\;\\?\\&\\=\\@\\%\\#\\+\\-]?"
-                                        "(?:"
-                                            "\\([\\w\\.\\/\\:\\;\\?\\&\\=\\@\\%\\#\\+\\-]+\\)"
-                                        ")?"
-                                    ")*"
-                                ")");
-const QRegularExpression imgReg("((?:https?|ftp)://\\S+\\.(?:jpg|jpeg|png|svg|gif))");
-
 Message::Message(const Shared::Message& source, bool p_outgoing, const QString& p_sender, const QString& avatarPath, QWidget* parent):
     QWidget(parent),
     outgoing(p_outgoing),
@@ -66,12 +54,9 @@ Message::Message(const Shared::Message& source, bool p_outgoing, const QString&
     body->setBackgroundRole(QPalette::AlternateBase);
     body->setAutoFillBackground(true);
     
-    QString bd = msg.getBody();
-    //bd.replace(imgReg, "<img src=\"\\1\"/>");
-    bd.replace(urlReg, "<a href=\"\\1\">\\1</a>");
-    //bd.replace("\n", "<br>");
+    QString bd = Shared::processMessageBody(msg.getBody());
     text->setTextFormat(Qt::RichText);
-    text->setText("<p style=\"white-space: pre-wrap;\">" + bd + "</p>");;
+    text->setText(bd);;
     text->setTextInteractionFlags(text->textInteractionFlags() | Qt::TextSelectableByMouse | Qt::LinksAccessibleByMouse);
     text->setWordWrap(true);
     text->setOpenExternalLinks(true);
@@ -308,13 +293,13 @@ bool Message::change(const QMap<QString, QVariant>& data)
 {
     bool idChanged = msg.change(data);
     
-    QString bd = msg.getBody();
-    //bd.replace(imgReg, "<img src=\"\\1\"/>");
-    bd.replace(urlReg, "<a href=\"\\1\">\\1</a>");
-    text->setText(bd);
-    if (bd.size() > 0) {
+    QString body = msg.getBody();
+    QString bd = Shared::processMessageBody(body);
+    if (body.size() > 0) {
+        text->setText(bd);
         text->show();
     } else {
+        text->setText(body);
         text->hide();
     }
     if (msg.getEdited()) {
diff --git a/ui/utils/message.h b/ui/utils/message.h
index fc3f178..eef93a1 100644
--- a/ui/utils/message.h
+++ b/ui/utils/message.h
@@ -34,6 +34,7 @@
 #include "shared/message.h"
 #include "shared/icons.h"
 #include "shared/global.h"
+#include "shared/utils.h"
 #include "resizer.h"
 #include "image.h"
 
diff --git a/ui/widgets/conversation.cpp b/ui/widgets/conversation.cpp
index d413506..50f7dcf 100644
--- a/ui/widgets/conversation.cpp
+++ b/ui/widgets/conversation.cpp
@@ -19,7 +19,6 @@
 #include "conversation.h"
 #include "ui_conversation.h"
 #include "ui/utils/dropshadoweffect.h"
-#include "shared/icons.h"
 
 #include <QDebug>
 #include <QScrollBar>
@@ -308,7 +307,7 @@ void Conversation::onFileSelected()
 
 void Conversation::setStatus(const QString& status)
 {
-    statusLabel->setText(status);
+    statusLabel->setText(Shared::processMessageBody(status));
 }
 
 void Conversation::onScrollResize()
diff --git a/ui/widgets/conversation.h b/ui/widgets/conversation.h
index 075bc31..f64ae54 100644
--- a/ui/widgets/conversation.h
+++ b/ui/widgets/conversation.h
@@ -31,6 +31,8 @@
 #include "ui/utils/resizer.h"
 #include "ui/utils/flowlayout.h"
 #include "ui/utils/badge.h"
+#include "shared/icons.h"
+#include "shared/utils.h"
 
 namespace Ui
 {
diff --git a/ui/widgets/conversation.ui b/ui/widgets/conversation.ui
index 9b1321e..d73875d 100644
--- a/ui/widgets/conversation.ui
+++ b/ui/widgets/conversation.ui
@@ -122,9 +122,24 @@
            </item>
            <item>
             <widget class="QLabel" name="statusLabel">
+             <property name="sizePolicy">
+              <sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
+               <horstretch>0</horstretch>
+               <verstretch>0</verstretch>
+              </sizepolicy>
+             </property>
              <property name="text">
               <string/>
              </property>
+             <property name="wordWrap">
+              <bool>true</bool>
+             </property>
+             <property name="openExternalLinks">
+              <bool>true</bool>
+             </property>
+             <property name="textInteractionFlags">
+              <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
+             </property>
             </widget>
            </item>
           </layout>
@@ -134,9 +149,12 @@
            <property name="orientation">
             <enum>Qt::Horizontal</enum>
            </property>
+           <property name="sizeType">
+            <enum>QSizePolicy::Preferred</enum>
+           </property>
            <property name="sizeHint" stdset="0">
             <size>
-             <width>40</width>
+             <width>0</width>
              <height>20</height>
             </size>
            </property>

From 21c7d65027a8c4ff8c9aa29d0f71bdb89c54fa80 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Mon, 13 Apr 2020 22:57:23 +0300
Subject: [PATCH 063/281] offline avatars in mucs

---
 CHANGELOG.md                |  1 +
 core/account.cpp            |  7 ++++-
 core/archive.cpp            | 33 +++++++++++++++++++--
 core/archive.h              |  3 +-
 core/conference.cpp         | 58 ++++++++++++++++++++++++++----------
 core/conference.h           | 15 ++++++++--
 core/rosteritem.cpp         | 35 +++++++++++++---------
 core/rosteritem.h           |  8 +++--
 shared/global.cpp           |  1 +
 shared/global.h             |  4 +++
 ui/models/room.cpp          | 27 ++++++++++++++++-
 ui/models/room.h            |  2 ++
 ui/utils/messageline.cpp    | 59 +++++++++++++++++++++++++++++++++----
 ui/utils/messageline.h      |  3 ++
 ui/widgets/conversation.cpp | 10 +++++++
 ui/widgets/room.cpp         |  4 ++-
 16 files changed, 225 insertions(+), 45 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index c8c5e75..e528bd5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,6 +14,7 @@
 - going offline related segfault fix
 - statuses now behave better: they wrap if they don't fit, you can select them, you can follow links from there
 - messages and statuses don't loose content if you use < ore > symbols
+- now avatars of those who are not in the MUC right now but was also display next to the message
 
 
 ## Squawk 0.1.3 (Mar 31, 2020)
diff --git a/core/account.cpp b/core/account.cpp
index 2f8238a..4c12761 100644
--- a/core/account.cpp
+++ b/core/account.cpp
@@ -1063,6 +1063,7 @@ void Core::Account::onContactHistoryResponse(const std::list<Shared::Message>& l
 
 void Core::Account::onClientError(QXmppClient::Error err)
 {
+    qDebug() << "Error";
     QString errorText;
     QString errorType;
     switch (err) {
@@ -1140,6 +1141,9 @@ void Core::Account::onClientError(QXmppClient::Error err)
                 case QXmppStanza::Error::UnexpectedRequest:
                     errorText = "Unexpected request";
                     break;
+                case QXmppStanza::Error::PolicyViolation:
+                    errorText = "Policy violation";
+                    break;
             }
          
             errorType = "Client stream error";
@@ -1367,7 +1371,8 @@ void Core::Account::addNewRoom(const QString& jid, const QString& nick, const QS
         {"autoJoin", conf->getAutoJoin()},
         {"joined", conf->getJoined()},
         {"nick", conf->getNick()},
-        {"name", conf->getName()}
+        {"name", conf->getName()},
+        {"avatars", conf->getAllAvatars()}
     };
     
     Archive::AvatarInfo info;
diff --git a/core/archive.cpp b/core/archive.cpp
index bd159ca..50acf81 100644
--- a/core/archive.cpp
+++ b/core/archive.cpp
@@ -675,7 +675,7 @@ bool Core::Archive::dropAvatar(const std::string& resource)
     }
 }
 
-bool Core::Archive::setAvatar(const QByteArray& data, bool generated, const QString& resource)
+bool Core::Archive::setAvatar(const QByteArray& data, AvatarInfo& newInfo, bool generated, const QString& resource)
 {
     if (!opened) {
         throw Closed("setAvatar", jid.toStdString());
@@ -726,7 +726,9 @@ bool Core::Archive::setAvatar(const QByteArray& data, bool generated, const QStr
             
             MDB_val lmdbKey, lmdbData;
             QByteArray value;
-            AvatarInfo newInfo(ext, newHash, generated);
+            newInfo.type = ext;
+            newInfo.hash = newHash;
+            newInfo.autogenerated = generated;
             newInfo.serialize(&value);
             lmdbKey.mv_size = res.size();
             lmdbKey.mv_data = (char*)res.c_str();
@@ -802,6 +804,33 @@ bool Core::Archive::readAvatarInfo(Core::Archive::AvatarInfo& target, const std:
     }
 }
 
+void Core::Archive::readAllResourcesAvatars(std::map<QString, AvatarInfo>& data) const
+{
+    if (!opened) {
+        throw Closed("readAllResourcesAvatars", jid.toStdString());
+    }
+    
+    int rc;
+    MDB_val lmdbKey, lmdbData;
+    MDB_txn *txn;
+    MDB_cursor* cursor;
+    mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn);
+    mdb_cursor_open(txn, avatars, &cursor);
+    rc = mdb_cursor_get(cursor, &lmdbKey, &lmdbData, MDB_FIRST);
+    
+    do {
+        std::string sId((char*)lmdbKey.mv_data, lmdbKey.mv_size);
+        QString res(sId.c_str());
+        if (res != jid) {
+            data.emplace(res, AvatarInfo());
+            data[res].deserialize((char*)lmdbData.mv_data, lmdbData.mv_size);
+        }
+    } while (mdb_cursor_get(cursor, &lmdbKey, &lmdbData, MDB_NEXT) == 0);
+    
+    mdb_cursor_close(cursor);
+    mdb_txn_abort(txn);
+}
+
 Core::Archive::AvatarInfo Core::Archive::getAvatarInfo(const QString& resource) const
 {
     if (!opened) {
diff --git a/core/archive.h b/core/archive.h
index 6facf68..ef6ca23 100644
--- a/core/archive.h
+++ b/core/archive.h
@@ -56,9 +56,10 @@ public:
     std::list<Shared::Message> getBefore(int count, const QString& id);
     bool isFromTheBeginning();
     void setFromTheBeginning(bool is);
-    bool setAvatar(const QByteArray& data, bool generated = false, const QString& resource = "");
+    bool setAvatar(const QByteArray& data, AvatarInfo& info, bool generated = false, const QString& resource = "");
     AvatarInfo getAvatarInfo(const QString& resource = "") const;
     bool readAvatarInfo(AvatarInfo& target, const QString& resource = "") const;
+    void readAllResourcesAvatars(std::map<QString, AvatarInfo>& data) const;
     
 public:
     const QString jid;
diff --git a/core/conference.cpp b/core/conference.cpp
index d745227..cda19fd 100644
--- a/core/conference.cpp
+++ b/core/conference.cpp
@@ -25,7 +25,8 @@ Core::Conference::Conference(const QString& p_jid, const QString& p_account, boo
     nick(p_nick),
     room(p_room),
     joined(false),
-    autoJoin(p_autoJoin)
+    autoJoin(p_autoJoin),
+    exParticipants()
 {
     muc = true;
     name = p_name;
@@ -44,6 +45,8 @@ Core::Conference::Conference(const QString& p_jid, const QString& p_account, boo
     if (autoJoin) {
         room->join();
     }
+    
+    archive->readAllResourcesAvatars(exParticipants);
 }
 
 Core::Conference::~Conference()
@@ -140,8 +143,8 @@ void Core::Conference::onRoomParticipantAdded(const QString& p_name)
         resource = "";
     } 
     
-    Archive::AvatarInfo info;
-    bool hasAvatar = readAvatarInfo(info, resource);
+    std::map<QString, Archive::AvatarInfo>::const_iterator itr = exParticipants.find(resource);
+    bool hasAvatar = itr != exParticipants.end();
     
     if (resource.size() > 0) {
         QDateTime lastInteraction = pres.lastUserInteraction();
@@ -158,12 +161,12 @@ void Core::Conference::onRoomParticipantAdded(const QString& p_name)
         };
         
         if (hasAvatar) {
-            if (info.autogenerated) {
+            if (itr->second.autogenerated) {
                 cData.insert("avatarState", static_cast<uint>(Shared::Avatar::valid));
             } else {
                 cData.insert("avatarState", static_cast<uint>(Shared::Avatar::autocreated));
             }
-            cData.insert("avatarPath", avatarPath(resource) + "." + info.type);
+            cData.insert("avatarPath", avatarPath(resource) + "." + itr->second.type);
         } else {
             cData.insert("avatarState", static_cast<uint>(Shared::Avatar::empty));
             cData.insert("avatarPath", "");
@@ -179,14 +182,14 @@ void Core::Conference::onRoomParticipantAdded(const QString& p_name)
         case QXmppPresence::VCardUpdateNotReady:        //let's say the photo didn't change here
             break;
         case QXmppPresence::VCardUpdateNoPhoto: {       //there is no photo, need to drop if any
-            if (!hasAvatar || !info.autogenerated) {
+            if (!hasAvatar || !itr->second.autogenerated) {
                 setAutoGeneratedAvatar(resource);
             }
         }         
         break;
         case QXmppPresence::VCardUpdateValidPhoto:{     //there is a photo, need to load
             if (hasAvatar) {
-                if (info.autogenerated || info.hash != pres.photoHash()) {
+                if (itr->second.autogenerated || itr->second.hash != pres.photoHash()) {
                     emit requestVCard(p_name);
                 }
             } else {
@@ -285,30 +288,46 @@ void Core::Conference::handlePresence(const QXmppPresence& pres)
 
 bool Core::Conference::setAutoGeneratedAvatar(const QString& resource)
 {
-    bool result = RosterItem::setAutoGeneratedAvatar(resource);
+    Archive::AvatarInfo newInfo;
+    bool result = RosterItem::setAutoGeneratedAvatar(newInfo, resource);
     if (result && resource.size() != 0) {
+        std::map<QString, Archive::AvatarInfo>::iterator itr = exParticipants.find(resource);
+        if (itr == exParticipants.end()) {
+            exParticipants.insert(std::make_pair(resource, newInfo));
+        } else {
+            itr->second = newInfo;
+        }
         emit changeParticipant(resource, {
             {"avatarState", static_cast<uint>(Shared::Avatar::autocreated)},
-            {"avatarPath", avatarPath(resource) + ".png"}
+            {"avatarPath", avatarPath(resource) + "." + newInfo.type}
         });
     }
     
     return result;
 }
 
-bool Core::Conference::setAvatar(const QByteArray& data, const QString& resource)
+bool Core::Conference::setAvatar(const QByteArray& data, Archive::AvatarInfo& info, const QString& resource)
 {
-    bool result = RosterItem::setAvatar(data, resource);
+    bool result = RosterItem::setAvatar(data, info, resource);
     if (result && resource.size() != 0) {
         if (data.size() > 0) {
-            QMimeDatabase db;
-            QMimeType type = db.mimeTypeForData(data);
-            QString ext = type.preferredSuffix();
+            std::map<QString, Archive::AvatarInfo>::iterator itr = exParticipants.find(resource);
+            if (itr == exParticipants.end()) {
+                exParticipants.insert(std::make_pair(resource, info));
+            } else {
+                itr->second = info;
+            }
+            
             emit changeParticipant(resource, {
                 {"avatarState", static_cast<uint>(Shared::Avatar::autocreated)},
-                {"avatarPath", avatarPath(resource) + "." + ext}
+                {"avatarPath", avatarPath(resource) + "." + info.type}
             });
         } else {
+            std::map<QString, Archive::AvatarInfo>::iterator itr = exParticipants.find(resource);
+            if (itr != exParticipants.end()) {
+                exParticipants.erase(itr);
+            }
+            
             emit changeParticipant(resource, {
                 {"avatarState", static_cast<uint>(Shared::Avatar::empty)},
                 {"avatarPath", ""}
@@ -333,3 +352,12 @@ Shared::VCard Core::Conference::handleResponseVCard(const QXmppVCardIq& card, co
     
     return result;
 }
+
+QMap<QString, QVariant> Core::Conference::getAllAvatars() const
+{
+    QMap<QString, QVariant> result;
+    for (const std::pair<QString, Archive::AvatarInfo>& pair : exParticipants) {
+        result.insert(pair.first, avatarPath(pair.first) + "." + pair.second.type);
+    }
+    return result;
+}
diff --git a/core/conference.h b/core/conference.h
index c00c472..4e0e463 100644
--- a/core/conference.h
+++ b/core/conference.h
@@ -19,9 +19,15 @@
 #ifndef CORE_CONFERENCE_H
 #define CORE_CONFERENCE_H
 
-#include "rosteritem.h"
+#include <QDir>
+
 #include <QXmppMucManager.h>
 
+#include <set>
+
+#include "rosteritem.h"
+#include "shared/global.h"
+
 namespace Core
 {
 
@@ -46,8 +52,8 @@ public:
     void setAutoJoin(bool p_autoJoin);
     void handlePresence(const QXmppPresence & pres) override;
     bool setAutoGeneratedAvatar(const QString& resource = "") override;
-    bool setAvatar(const QByteArray &data, const QString &resource = "") override;
     Shared::VCard handleResponseVCard(const QXmppVCardIq & card, const QString &resource) override;
+    QMap<QString, QVariant> getAllAvatars() const;
     
 signals:
     void nickChanged(const QString& nick);
@@ -58,11 +64,16 @@ signals:
     void changeParticipant(const QString& name, const QMap<QString, QVariant>& data);
     void removeParticipant(const QString& name);
     
+protected:
+    bool setAvatar(const QByteArray &data, Archive::AvatarInfo& info, const QString &resource = "") override;
+    
 private:
     QString nick;
     QXmppMucRoom* room;
     bool joined;
     bool autoJoin;
+    std::map<QString, Archive::AvatarInfo> exParticipants;
+    static const std::set<QString> supportedList;
     
 private slots:
     void onRoomJoined();
diff --git a/core/rosteritem.cpp b/core/rosteritem.cpp
index 59b84f8..c25b339 100644
--- a/core/rosteritem.cpp
+++ b/core/rosteritem.cpp
@@ -410,28 +410,37 @@ bool Core::RosterItem::isMuc() const
 
 QString Core::RosterItem::avatarPath(const QString& resource) const
 {
-    QString path(QStandardPaths::writableLocation(QStandardPaths::CacheLocation));
-    path += "/" + account + "/" + jid + "/" + (resource.size() == 0 ? jid : resource);
+    QString path = folderPath() + "/" + (resource.size() == 0 ? jid : resource);
     return path;
 }
 
-bool Core::RosterItem::setAvatar(const QByteArray& data, const QString& resource)
+QString Core::RosterItem::folderPath() const
 {
-    bool result = archive->setAvatar(data, false, resource);
+    QString path(QStandardPaths::writableLocation(QStandardPaths::CacheLocation));
+    path += "/" + account + "/" + jid;
+    return path;
+}
+
+bool Core::RosterItem::setAvatar(const QByteArray& data, Archive::AvatarInfo& info, const QString& resource)
+{
+    bool result = archive->setAvatar(data, info, false, resource);
     if (resource.size() == 0 && result) {
         if (data.size() == 0) {
             emit avatarChanged(Shared::Avatar::empty, "");
         } else {
-            QMimeDatabase db;
-            QMimeType type = db.mimeTypeForData(data);
-            QString ext = type.preferredSuffix();
-            emit avatarChanged(Shared::Avatar::valid, avatarPath(resource) + "." + ext);
+            emit avatarChanged(Shared::Avatar::valid, avatarPath(resource) + "." + info.type);
         }
     }
     return result;
 }
 
 bool Core::RosterItem::setAutoGeneratedAvatar(const QString& resource)
+{
+    Archive::AvatarInfo info;
+    return setAutoGeneratedAvatar(info, resource);
+}
+
+bool Core::RosterItem::setAutoGeneratedAvatar(Archive::AvatarInfo& info, const QString& resource)
 {
     QImage image(96, 96, QImage::Format_ARGB32_Premultiplied);
     QPainter painter(&image);
@@ -453,7 +462,7 @@ bool Core::RosterItem::setAutoGeneratedAvatar(const QString& resource)
     stream.open(QBuffer::WriteOnly);
     image.save(&stream, "PNG");
     stream.close();
-    bool result = archive->setAvatar(arr, true, resource);
+    bool result = archive->setAvatar(arr, info, true, resource);
     if (resource.size() == 0 && result) {
         emit avatarChanged(Shared::Avatar::autocreated, avatarPath(resource) + ".png");
     }
@@ -468,6 +477,7 @@ bool Core::RosterItem::readAvatarInfo(Archive::AvatarInfo& target, const QString
 Shared::VCard Core::RosterItem::handleResponseVCard(const QXmppVCardIq& card, const QString& resource)
 {
     Archive::AvatarInfo info;
+    Archive::AvatarInfo newInfo;
     bool hasAvatar = readAvatarInfo(info, resource);
     
     QByteArray ava = card.photo();
@@ -477,13 +487,10 @@ Shared::VCard Core::RosterItem::handleResponseVCard(const QXmppVCardIq& card, co
     QString path = "";
     
     if (ava.size() > 0) {
-        bool changed = setAvatar(ava, resource);
+        bool changed = setAvatar(ava, newInfo, resource);
         if (changed) {
             type = Shared::Avatar::valid;
-            QMimeDatabase db;
-            QMimeType type = db.mimeTypeForData(ava);
-            QString ext = type.preferredSuffix();
-            path = avatarPath(resource) + "." + ext;
+            path = avatarPath(resource) + "." + newInfo.type;
         } else if (hasAvatar) {
             if (info.autogenerated) {
                 type = Shared::Avatar::autocreated;
diff --git a/core/rosteritem.h b/core/rosteritem.h
index 387ebfc..47470b1 100644
--- a/core/rosteritem.h
+++ b/core/rosteritem.h
@@ -69,8 +69,8 @@ public:
     void requestHistory(int count, const QString& before);
     void requestFromEmpty(int count, const QString& before);
     QString avatarPath(const QString& resource = "") const;
+    QString folderPath() const;
     bool readAvatarInfo(Archive::AvatarInfo& target, const QString& resource = "") const;
-    virtual bool setAvatar(const QByteArray& data, const QString& resource = "");
     virtual bool setAutoGeneratedAvatar(const QString& resource = "");
     virtual Shared::VCard handleResponseVCard(const QXmppVCardIq& card, const QString& resource);
     virtual void handlePresence(const QXmppPresence& pres) = 0;
@@ -89,6 +89,10 @@ public:
     const QString jid;
     const QString account;
     
+protected:
+    virtual bool setAvatar(const QByteArray& data, Archive::AvatarInfo& info, const QString& resource = "");
+    virtual bool setAutoGeneratedAvatar(Archive::AvatarInfo& info, const QString& resource = "");
+    
 protected:
     QString name;
     ArchiveState archiveState;
@@ -103,7 +107,7 @@ protected:
     std::list<std::pair<int, QString>> requestCache;
     std::map<QString, Shared::Message> toCorrect;
     bool muc;
-
+    
 private:
     void nextRequest();
     void performRequest(int count, const QString& before);
diff --git a/shared/global.cpp b/shared/global.cpp
index c8e5cf2..a6b7b60 100644
--- a/shared/global.cpp
+++ b/shared/global.cpp
@@ -21,6 +21,7 @@
 #include "enums.h"
 
 Shared::Global* Shared::Global::instance = 0;
+const std::set<QString> Shared::Global::supportedImagesExts = {"png", "jpg", "webp", "jpeg", "gif", "svg"};
 
 Shared::Global::Global():
     availability({
diff --git a/shared/global.h b/shared/global.h
index 481ac01..54e1584 100644
--- a/shared/global.h
+++ b/shared/global.h
@@ -24,6 +24,8 @@
 #include "exception.h"
 
 #include <map>
+#include <set>
+#include <deque>
 
 #include <QCoreApplication>
 #include <QDebug>
@@ -60,6 +62,8 @@ namespace Shared {
         static bool supported(const QString& pluginName);
         static void setSupported(const QString& pluginName, bool support);
         
+        static const std::set<QString> supportedImagesExts;
+        
         template<typename T>
         static T fromInt(int src);
         
diff --git a/ui/models/room.cpp b/ui/models/room.cpp
index be92d41..cc19d2c 100644
--- a/ui/models/room.cpp
+++ b/ui/models/room.cpp
@@ -32,7 +32,8 @@ Models::Room::Room(const QString& p_jid, const QMap<QString, QVariant>& data, Mo
     avatarState(Shared::Avatar::empty),
     avatarPath(""),
     messages(),
-    participants()
+    participants(),
+    exParticipantAvatars()
 {
     QMap<QString, QVariant>::const_iterator itr = data.find("autoJoin");
     if (itr != data.end()) {
@@ -62,6 +63,15 @@ Models::Room::Room(const QString& p_jid, const QMap<QString, QVariant>& data, Mo
     if (itr != data.end()) {
         setAvatarPath(itr.value().toString());
     }
+    
+    
+    itr = data.find("avatars");
+    if (itr != data.end()) {
+        QMap<QString, QVariant> avs = itr.value().toMap();
+        for (QMap<QString, QVariant>::const_iterator itr = avs.begin(), end = avs.end(); itr != end; ++itr) {
+            exParticipantAvatars.insert(std::make_pair(itr.key(), itr.value().toString()));
+        }
+    }
 }
 
 Models::Room::~Room()
@@ -284,6 +294,11 @@ void Models::Room::addParticipant(const QString& p_name, const QMap<QString, QVa
         qDebug() << "An attempt to add already existing participant" << p_name << "to the room" << name << ", updating instead";
         handleParticipantUpdate(itr, data);
     } else {
+        std::map<QString, QString>::const_iterator eitr = exParticipantAvatars.find(name);
+        if (eitr != exParticipantAvatars.end()) {
+            exParticipantAvatars.erase(eitr);
+        }
+        
         Participant* part = new Participant(data);
         part->setName(p_name);
         participants.insert(std::make_pair(p_name, part));
@@ -311,6 +326,11 @@ void Models::Room::removeParticipant(const QString& p_name)
         Participant* p = itr->second;
         participants.erase(itr);
         removeChild(p->row());
+        
+        if (p->getAvatarState() != Shared::Avatar::empty) {
+            exParticipantAvatars.insert(std::make_pair(p_name, p->getAvatarPath()));
+        }
+        
         p->deleteLater();
         emit participantLeft(p_name);
     }
@@ -408,3 +428,8 @@ QString Models::Room::getParticipantIconPath(const QString& name) const
     
     return itr->second->getAvatarPath();
 }
+
+std::map<QString, QString> Models::Room::getExParticipantAvatars() const
+{
+    return exParticipantAvatars;
+}
diff --git a/ui/models/room.h b/ui/models/room.h
index 382b6e9..9ea70bf 100644
--- a/ui/models/room.h
+++ b/ui/models/room.h
@@ -75,6 +75,7 @@ public:
     QString getAvatarPath() const;
     std::map<QString, const Participant&> getParticipants() const;
     QString getParticipantIconPath(const QString& name) const;
+    std::map<QString, QString> getExParticipantAvatars() const;
     
 signals:
     void participantJoined(const Participant& participant);
@@ -99,6 +100,7 @@ private:
     QString avatarPath;
     Messages messages;
     std::map<QString, Participant*> participants;
+    std::map<QString, QString> exParticipantAvatars;
 
 };
 
diff --git a/ui/utils/messageline.cpp b/ui/utils/messageline.cpp
index ecd10a1..0ef5f07 100644
--- a/ui/utils/messageline.cpp
+++ b/ui/utils/messageline.cpp
@@ -29,6 +29,7 @@ MessageLine::MessageLine(bool p_room, QWidget* parent):
     palMessages(),
     uploadPaths(),
     palAvatars(),
+    exPalAvatars(),
     layout(new QVBoxLayout(this)),
     myName(),
     myAvatarPath(),
@@ -80,6 +81,11 @@ MessageLine::Position MessageLine::message(const Shared::Message& msg, bool forc
                 std::map<QString, QString>::iterator aItr = palAvatars.find(sender);
                 if (aItr != palAvatars.end()) {
                     aPath = aItr->second;
+                } else {
+                    aItr = exPalAvatars.find(sender);
+                    if (aItr != exPalAvatars.end()) {
+                        aPath = aItr->second;
+                    }
                 }
                 outgoing = false;
             }
@@ -248,6 +254,11 @@ void MessageLine::setPalAvatar(const QString& jid, const QString& path)
     std::map<QString, QString>::iterator itr = palAvatars.find(jid);
     if (itr == palAvatars.end()) {
         palAvatars.insert(std::make_pair(jid, path));
+        
+        std::map<QString, QString>::const_iterator eitr = exPalAvatars.find(jid);
+        if (eitr != exPalAvatars.end()) {
+            exPalAvatars.erase(eitr);
+        }
     } else {
         itr->second = path;
     }
@@ -265,16 +276,36 @@ void MessageLine::dropPalAvatar(const QString& jid)
     std::map<QString, QString>::iterator itr = palAvatars.find(jid);
     if (itr != palAvatars.end()) {
         palAvatars.erase(itr);
-        
-        std::map<QString, Index>::iterator pItr = palMessages.find(jid);
-        if (pItr != palMessages.end()) {
-            for (Index::const_iterator itr = pItr->second.begin(), end = pItr->second.end(); itr != end; ++itr) {
-                itr->second->setAvatarPath("");
-            }
+    }
+    
+    std::map<QString, QString>::const_iterator eitr = exPalAvatars.find(jid);
+    if (eitr != exPalAvatars.end()) {
+        exPalAvatars.erase(eitr);
+    }
+    
+    std::map<QString, Index>::iterator pItr = palMessages.find(jid);
+    if (pItr != palMessages.end()) {
+        for (Index::const_iterator itr = pItr->second.begin(), end = pItr->second.end(); itr != end; ++itr) {
+            itr->second->setAvatarPath("");
         }
     }
 }
 
+void MessageLine::movePalAvatarToEx(const QString& name)
+{
+    std::map<QString, QString>::iterator itr = palAvatars.find(name);
+    if (itr != palAvatars.end()) {
+        std::map<QString, QString>::iterator eitr = exPalAvatars.find(name);
+        if (eitr != exPalAvatars.end()) {
+            eitr->second = itr->second;
+        } else {
+            exPalAvatars.insert(std::make_pair(name, itr->second));
+        }
+        
+        palAvatars.erase(itr);
+    }
+}
+
 void MessageLine::resizeEvent(QResizeEvent* event)
 {
     QWidget::resizeEvent(event);
@@ -459,3 +490,19 @@ void MessageLine::setMyAvatarPath(const QString& p_path)
         }
     }
 }
+
+void MessageLine::setExPalAvatars(const std::map<QString, QString>& data)
+{
+    exPalAvatars = data;
+    
+    for (const std::pair<QString, Index>& pair : palMessages) {
+        if (palAvatars.find(pair.first) == palAvatars.end()) {
+            std::map<QString, QString>::const_iterator eitr = exPalAvatars.find(pair.first);
+            if (eitr != exPalAvatars.end()) {
+                for (const std::pair<QString, Message*>& mp : pair.second) {
+                    mp.second->setAvatarPath(eitr->second);
+                }
+            }
+        }
+    }
+}
diff --git a/ui/utils/messageline.h b/ui/utils/messageline.h
index 104dc72..a0a7b6c 100644
--- a/ui/utils/messageline.h
+++ b/ui/utils/messageline.h
@@ -59,6 +59,8 @@ public:
     void setPalAvatar(const QString& jid, const QString& path);
     void dropPalAvatar(const QString& jid);
     void changeMessage(const QString& id, const QMap<QString, QVariant>& data);
+    void setExPalAvatars(const std::map<QString, QString>& data);
+    void movePalAvatarToEx(const QString& name);
     
 signals:
     void resize(int amount);
@@ -90,6 +92,7 @@ private:
     std::map<QString, Index> palMessages;
     std::map<QString, QString> uploadPaths;
     std::map<QString, QString> palAvatars;
+    std::map<QString, QString> exPalAvatars;
     QVBoxLayout* layout;
     
     QString myName;
diff --git a/ui/widgets/conversation.cpp b/ui/widgets/conversation.cpp
index 50f7dcf..e677bc8 100644
--- a/ui/widgets/conversation.cpp
+++ b/ui/widgets/conversation.cpp
@@ -92,6 +92,16 @@ Conversation::Conversation(bool muc, Models::Account* acc, const QString pJid, c
     line->setMyAvatarPath(acc->getAvatarPath());
     line->setMyName(acc->getName());
     
+    QFont nf = m_ui->nameLabel->font();
+    nf.setBold(true);
+    nf.setPointSize(nf.pointSize() + 2);
+    m_ui->nameLabel->setFont(nf);
+    
+    QFont sf = statusLabel->font();
+    sf.setItalic(true);
+    sf.setPointSize(sf.pointSize() - 2);
+    statusLabel->setFont(sf);
+    
     applyVisualEffects();
 }
 
diff --git a/ui/widgets/room.cpp b/ui/widgets/room.cpp
index 6d5340f..66ae5f7 100644
--- a/ui/widgets/room.cpp
+++ b/ui/widgets/room.cpp
@@ -38,6 +38,8 @@ Room::Room(Models::Account* acc, Models::Room* p_room, QWidget* parent):
             line->setPalAvatar(pair.first, aPath);
         }
     }
+    
+    line->setExPalAvatars(room->getExParticipantAvatars());
 }
 
 Room::~Room()
@@ -104,5 +106,5 @@ void Room::onParticipantJoined(const Models::Participant& participant)
 
 void Room::onParticipantLeft(const QString& name)
 {
-    line->dropPalAvatar(name);
+    line->movePalAvatarToEx(name);
 }

From cb44b12a7ec9b303294eb863e10eedd19159b909 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Tue, 14 Apr 2020 19:30:33 +0300
Subject: [PATCH 064/281] 0.1.4 kwallet optimisation related fix, DnD files
 into convs, visual fixes

---
 CHANGELOG.md                                  |  4 +-
 CMakeLists.txt                                | 12 +--
 README.md                                     |  3 +-
 core/passwordStorageEngines/kwallet.cpp       | 10 +-
 core/passwordStorageEngines/kwallet.h         |  2 +-
 .../wrappers/kwallet.cpp                      |  4 +-
 main.cpp                                      |  2 +-
 order.h                                       |  2 +-
 packaging/Archlinux/PKGBUILD                  |  6 +-
 shared/utils.cpp                              |  4 +-
 translations/squawk.ru.ts                     |  8 ++
 ui/squawk.cpp                                 |  9 +-
 ui/squawk.h                                   |  1 +
 ui/squawk.ui                                  | 12 ++-
 ui/widgets/conversation.cpp                   | 91 ++++++++++++++++---
 ui/widgets/conversation.h                     |  6 ++
 ui/widgets/conversation.ui                    | 27 ++++--
 ui/widgets/vcard/vcard.cpp                    |  6 +-
 18 files changed, 162 insertions(+), 47 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index e528bd5..c328fb5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,6 @@
 # Changelog
 
-## Squawk 0.1.4 (UNRELEASED)
+## Squawk 0.1.4 (Apr 14, 2020)
 ### New features
 - message line now is in the same window with roster (new window dialog is still able to opened on context menu)
 - several new ways to manage your account password:
@@ -8,6 +8,7 @@
   - store it in config jammed (local hashing with the constant seed, not secure at all but might look like it is)
   - ask the account password on each program launch
   - store it in KWallet which is dynamically loaded
+- dragging into conversation now attach files
   
 ### Bug fixes
 - never updating MUC avatars now get updated
@@ -15,6 +16,7 @@
 - statuses now behave better: they wrap if they don't fit, you can select them, you can follow links from there
 - messages and statuses don't loose content if you use < ore > symbols
 - now avatars of those who are not in the MUC right now but was also display next to the message
+- fix crash on attempt to attach the same file for the second time
 
 
 ## Squawk 0.1.3 (Mar 31, 2020)
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 20ae092..771481f 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -109,9 +109,9 @@ add_dependencies(${CMAKE_PROJECT_NAME} translations)
 # Install the executable
 install(TARGETS squawk DESTINATION ${CMAKE_INSTALL_BINDIR})
 install(FILES ${QM_FILES} DESTINATION ${CMAKE_INSTALL_DATADIR}/squawk/l10n)
-install(FILES squawk.svg DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/scalable/apps)
-install(FILES squawk48.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/48x48/apps RENAME squawk.png)
-install(FILES squawk64.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/64x64/apps RENAME squawk.png)
-install(FILES squawk128.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/128x128/apps RENAME squawk.png)
-install(FILES squawk256.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/256x256/apps RENAME squawk.png)
-install(FILES squawk.desktop DESTINATION ${CMAKE_INSTALL_DATADIR}/applications)
+install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk.svg DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/scalable/apps)
+install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk48.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/48x48/apps RENAME squawk.png)
+install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk64.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/64x64/apps RENAME squawk.png)
+install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk128.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/128x128/apps RENAME squawk.png)
+install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk256.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/256x256/apps RENAME squawk.png)
+install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk.desktop DESTINATION ${CMAKE_INSTALL_DATADIR}/applications)
diff --git a/README.md b/README.md
index 7d55eb5..30c6473 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@
 [![AUR version](https://img.shields.io/aur/version/squawk?style=flat-square)](https://aur.archlinux.org/packages/squawk/)
 [![Liberapay patrons](https://img.shields.io/liberapay/patrons/macaw.me?logo=liberapay&style=flat-square)](https://liberapay.com/macaw.me)
 
-![Squawk screenshot](https://macaw.me/images/squawk/0.1.3.png)
+![Squawk screenshot](https://macaw.me/images/squawk/0.1.4.png)
 
 ### Prerequisites
 
@@ -13,6 +13,7 @@
 - lmdb
 - CMake 3.0 or higher
 - qxmpp 1.1.0 or higher
+- kwallet (optional)
 
 ### Getting
 
diff --git a/core/passwordStorageEngines/kwallet.cpp b/core/passwordStorageEngines/kwallet.cpp
index fe05a2c..0dfe071 100644
--- a/core/passwordStorageEngines/kwallet.cpp
+++ b/core/passwordStorageEngines/kwallet.cpp
@@ -41,11 +41,6 @@ Core::PSE::KWallet::KWallet():
     if (sState == initial) {
         lib.load();
         
-        if (!lib.isLoaded()) {      //fallback from the build directory
-            lib.setFileName("./core/passwordStorageEngines/libkwalletWrapper.so");
-            lib.load();
-        }
-        
         if (lib.isLoaded()) {
             openWallet = (OpenWallet) lib.resolve("openWallet");
             networkWallet = (NetworkWallet) lib.resolve("networkWallet");
@@ -81,7 +76,9 @@ void Core::PSE::KWallet::open()
 {
     if (sState == success) {
         if (cState == disconnected) {
-            wallet = openWallet(networkWallet(), 0, ::KWallet::Wallet::Asynchronous);
+            QString name;
+            networkWallet(name);
+            wallet = openWallet(name, 0, ::KWallet::Wallet::Asynchronous);
             if (wallet) {
                 cState = connecting;
                 connect(wallet, SIGNAL(walletOpened(bool)), this, SLOT(onWalletOpened(bool)));
@@ -89,6 +86,7 @@ void Core::PSE::KWallet::open()
             } else {
                 everError = true;
                 emit opened(false);
+                rejectPending();
             }
         }
     }
diff --git a/core/passwordStorageEngines/kwallet.h b/core/passwordStorageEngines/kwallet.h
index 8ac52d2..28475d2 100644
--- a/core/passwordStorageEngines/kwallet.h
+++ b/core/passwordStorageEngines/kwallet.h
@@ -79,7 +79,7 @@ private:
     
 private:
     typedef ::KWallet::Wallet* (*OpenWallet)(const QString &, WId, ::KWallet::Wallet::OpenType);
-    typedef const char* (*NetworkWallet)();
+    typedef void (*NetworkWallet)(QString&);
     typedef void (*DeleteWallet)(::KWallet::Wallet*);
     typedef int (*ReadPassword)(::KWallet::Wallet*, const QString&, QString&);
     typedef int (*WritePassword)(::KWallet::Wallet*, const QString&, const QString&);
diff --git a/core/passwordStorageEngines/wrappers/kwallet.cpp b/core/passwordStorageEngines/wrappers/kwallet.cpp
index 39a2eaf..f5e7cb5 100644
--- a/core/passwordStorageEngines/wrappers/kwallet.cpp
+++ b/core/passwordStorageEngines/wrappers/kwallet.cpp
@@ -8,8 +8,8 @@ extern "C" void deleteWallet(KWallet::Wallet* w) {
     w->deleteLater();
 }
 
-extern "C" const char* networkWallet() {
-    return KWallet::Wallet::NetworkWallet().toStdString().c_str();
+extern "C" void networkWallet(QString& str) {
+    str = KWallet::Wallet::NetworkWallet();
 }
 
 extern "C" int readPassword(KWallet::Wallet* w, const QString &key, QString &value) {
diff --git a/main.cpp b/main.cpp
index b8be72c..d272d3d 100644
--- a/main.cpp
+++ b/main.cpp
@@ -42,7 +42,7 @@ int main(int argc, char *argv[])
     
     QApplication::setApplicationName("squawk");
     QApplication::setApplicationDisplayName("Squawk");
-    QApplication::setApplicationVersion("0.1.3");
+    QApplication::setApplicationVersion("0.1.4");
     
     QTranslator qtTranslator;
     qtTranslator.load("qt_" + QLocale::system().name(), QLibraryInfo::location(QLibraryInfo::TranslationsPath));
diff --git a/order.h b/order.h
index 1821e15..fa9379b 100644
--- a/order.h
+++ b/order.h
@@ -29,7 +29,7 @@ namespace W
     template <typename data_type, typename comparator = std::less<data_type>>
     class Order
     {
-        
+    public:
         class Duplicates: 
             public Utils::Exception
         {
diff --git a/packaging/Archlinux/PKGBUILD b/packaging/Archlinux/PKGBUILD
index 400bc8c..9f8ef73 100644
--- a/packaging/Archlinux/PKGBUILD
+++ b/packaging/Archlinux/PKGBUILD
@@ -1,6 +1,6 @@
 # Maintainer: Yury Gubich <blue@macaw.me>
 pkgname=squawk
-pkgver=0.1.3
+pkgver=0.1.4
 pkgrel=1
 pkgdesc="An XMPP desktop messenger, written on pure c++ (qt)"
 arch=('i686' 'x86_64')
@@ -8,8 +8,10 @@ url="https://git.macaw.me/blue/squawk"
 license=('GPL3')
 depends=('hicolor-icon-theme' 'desktop-file-utils' 'lmdb' 'qxmpp>=1.1.0')
 makedepends=('cmake>=3.3' 'imagemagick' 'qt5-tools')
+optdepends=('kwallet: secure password storage (requires rebuild)')
+
 source=("$pkgname-$pkgver.tar.gz")
-sha256sums=('adb172bb7d5b81bd9b83b192481a79ac985877e81604f401b3f2a08613b359bc')
+sha256sums=('3b290381eaf15a35d24a58a36c29eee375a4ea77b606124982a063d7ecf98870')
 build() {
         cd "$srcdir/squawk"
         cmake . -D CMAKE_INSTALL_PREFIX=/usr -D CMAKE_BUILD_TYPE=Release
diff --git a/shared/utils.cpp b/shared/utils.cpp
index 06247f8..924be85 100644
--- a/shared/utils.cpp
+++ b/shared/utils.cpp
@@ -33,9 +33,9 @@ static const QRegularExpression urlReg("(?<!<a\\shref=['\"])(?<!<img\\ssrc=['\"]
                                 "(?:https?|ftp):\\/\\/"
                                     "\\w+"
                                     "(?:"
-                                        "[\\w\\.\\/\\:\\;\\?\\&\\=\\@\\%\\#\\+\\-]?"
+                                        "[\\w\\.\\,\\/\\:\\;\\?\\&\\=\\@\\%\\#\\+\\-]?"
                                         "(?:"
-                                            "\\([\\w\\.\\/\\:\\;\\?\\&\\=\\@\\%\\#\\+\\-]+\\)"
+                                            "\\([\\w\\.\\,\\/\\:\\;\\?\\&\\=\\@\\%\\#\\+\\-]+\\)"
                                         ")?"
                                     ")*"
                                 ")");
diff --git a/translations/squawk.ru.ts b/translations/squawk.ru.ts
index 32efc46..c425d52 100644
--- a/translations/squawk.ru.ts
+++ b/translations/squawk.ru.ts
@@ -119,6 +119,10 @@ p, li { white-space: pre-wrap; }
 &lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
         <translation></translation>
     </message>
+    <message>
+        <source>Drop files here to attach them to your message</source>
+        <translation>Бросьте файлы сюда для того что бы прикрепить их к сообщению</translation>
+    </message>
 </context>
 <context>
     <name>Global</name>
@@ -637,6 +641,10 @@ to be displayed as %1</source>
         <source>Password for account %1</source>
         <translation>Пароль для учетной записи %1</translation>
     </message>
+    <message>
+        <source>Please select a contact to start chatting</source>
+        <translation>Выберите контакт или группу что бы начать переписку</translation>
+    </message>
 </context>
 <context>
     <name>VCard</name>
diff --git a/ui/squawk.cpp b/ui/squawk.cpp
index 3e1eb9e..a808dff 100644
--- a/ui/squawk.cpp
+++ b/ui/squawk.cpp
@@ -34,7 +34,8 @@ Squawk::Squawk(QWidget *parent) :
     requestedAccountsForPasswords(),
     prompt(0),
     currentConversation(0),
-    restoreSelection()
+    restoreSelection(),
+    needToRestore(false)
 {
     m_ui->setupUi(this);
     m_ui->roster->setModel(&rosterModel);
@@ -1139,6 +1140,7 @@ void Squawk::onRosterSelectionChanged(const QModelIndex& current, const QModelIn
             if (id != 0) {
                 delete id;
             }
+            needToRestore = true;
             restoreSelection = previous;
             return;
         }
@@ -1204,5 +1206,8 @@ void Squawk::onRosterSelectionChanged(const QModelIndex& current, const QModelIn
 
 void Squawk::onContextAboutToHide()
 {
-    m_ui->roster->selectionModel()->setCurrentIndex(restoreSelection, QItemSelectionModel::ClearAndSelect);
+    if (needToRestore) {
+        needToRestore = false;
+        m_ui->roster->selectionModel()->setCurrentIndex(restoreSelection, QItemSelectionModel::ClearAndSelect);
+    }
 }
diff --git a/ui/squawk.h b/ui/squawk.h
index 28a2f17..a6a27c0 100644
--- a/ui/squawk.h
+++ b/ui/squawk.h
@@ -126,6 +126,7 @@ private:
     QInputDialog* prompt;
     Conversation* currentConversation;
     QModelIndex restoreSelection;
+    bool needToRestore;
     
 protected:
     void closeEvent(QCloseEvent * event) override;
diff --git a/ui/squawk.ui b/ui/squawk.ui
index 19fdc4f..f6cb300 100644
--- a/ui/squawk.ui
+++ b/ui/squawk.ui
@@ -129,8 +129,18 @@
        <layout class="QGridLayout" name="gridLayout">
         <item row="0" column="0">
          <widget class="QLabel" name="label">
+          <property name="font">
+           <font>
+            <pointsize>26</pointsize>
+            <weight>75</weight>
+            <bold>true</bold>
+           </font>
+          </property>
           <property name="text">
-           <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:26pt;&quot;&gt;Please select a contact to start chatting&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+           <string>Please select a contact to start chatting</string>
+          </property>
+          <property name="textFormat">
+           <enum>Qt::PlainText</enum>
           </property>
           <property name="alignment">
            <set>Qt::AlignCenter</set>
diff --git a/ui/widgets/conversation.cpp b/ui/widgets/conversation.cpp
index e677bc8..ec772a6 100644
--- a/ui/widgets/conversation.cpp
+++ b/ui/widgets/conversation.cpp
@@ -44,6 +44,7 @@ Conversation::Conversation(bool muc, Models::Account* acc, const QString pJid, c
     statusIcon(0),
     statusLabel(0),
     filesLayout(0),
+    overlay(new QWidget()),
     filesToAttach(),
     scroll(down),
     manualSliderChange(false),
@@ -92,15 +93,26 @@ Conversation::Conversation(bool muc, Models::Account* acc, const QString pJid, c
     line->setMyAvatarPath(acc->getAvatarPath());
     line->setMyName(acc->getName());
     
-    QFont nf = m_ui->nameLabel->font();
-    nf.setBold(true);
-    nf.setPointSize(nf.pointSize() + 2);
-    m_ui->nameLabel->setFont(nf);
-    
-    QFont sf = statusLabel->font();
-    sf.setItalic(true);
-    sf.setPointSize(sf.pointSize() - 2);
-    statusLabel->setFont(sf);
+    QGridLayout* gr = static_cast<QGridLayout*>(layout());
+    QLabel* progressLabel = new QLabel(tr("Drop files here to attach them to your message"));
+    gr->addWidget(overlay, 0, 0, 2, 1);
+    QVBoxLayout* nl = new QVBoxLayout();
+    QGraphicsOpacityEffect* opacity = new QGraphicsOpacityEffect();
+    opacity->setOpacity(0.8);
+    overlay->setLayout(nl);
+    overlay->setBackgroundRole(QPalette::Base);
+    overlay->setAutoFillBackground(true);
+    overlay->setGraphicsEffect(opacity);
+    progressLabel->setAlignment(Qt::AlignCenter);
+    QFont pf = progressLabel->font();
+    pf.setBold(true);
+    pf.setPointSize(26);
+    progressLabel->setWordWrap(true);
+    progressLabel->setFont(pf);
+    nl->addStretch();
+    nl->addWidget(progressLabel);
+    nl->addStretch();
+    overlay->hide();
     
     applyVisualEffects();
 }
@@ -362,10 +374,16 @@ void Conversation::addAttachedFile(const QString& path)
     Badge* badge = new Badge(path, info.fileName(), QIcon::fromTheme(type.iconName()));
     
     connect(badge, &Badge::close, this, &Conversation::onBadgeClose);
-    filesToAttach.push_back(badge);                                                         //TODO neet to check if there are any duplicated ids
-    filesLayout->addWidget(badge);
-    if (filesLayout->count() == 1) {
-        filesLayout->setContentsMargins(3, 3, 3, 3);
+    try {
+        filesToAttach.push_back(badge);
+        filesLayout->addWidget(badge);
+        if (filesLayout->count() == 1) {
+            filesLayout->setContentsMargins(3, 3, 3, 3);
+        }
+    } catch (const W::Order<Badge*, Badge::Comparator>::Duplicates& e) {
+        delete badge;
+    } catch (...) {
+        throw;
     }
 }
 
@@ -421,6 +439,53 @@ void Conversation::setFeedFrames(bool top, bool right, bool bottom, bool left)
     static_cast<DropShadowEffect*>(m_ui->scrollArea->graphicsEffect())->setFrame(top, right, bottom, left);
 }
 
+void Conversation::dragEnterEvent(QDragEnterEvent* event)
+{
+    bool accept = false;
+    if (event->mimeData()->hasUrls()) {
+        QList<QUrl> list = event->mimeData()->urls();
+        for (const QUrl& url : list) {
+            if (url.isLocalFile()) {
+                QFileInfo info(url.toLocalFile());
+                if (info.isReadable() && info.isFile()) {
+                    accept = true;
+                    break;
+                }
+            }
+        }
+    }
+    if (accept) {
+        event->acceptProposedAction();
+        overlay->show();
+    }
+}
+
+void Conversation::dragLeaveEvent(QDragLeaveEvent* event)
+{
+    overlay->hide();
+}
+
+void Conversation::dropEvent(QDropEvent* event)
+{
+    bool accept = false;
+    if (event->mimeData()->hasUrls()) {
+        QList<QUrl> list = event->mimeData()->urls();
+        for (const QUrl& url : list) {
+            if (url.isLocalFile()) {
+                QFileInfo info(url.toLocalFile());
+                if (info.isReadable() && info.isFile()) {
+                    addAttachedFile(info.canonicalFilePath());
+                    accept = true;
+                }
+            }
+        }
+    }
+    if (accept) {
+        event->acceptProposedAction();
+    }
+    overlay->hide();
+}
+
 bool VisibilityCatcher::eventFilter(QObject* obj, QEvent* event)
 {
     if (event->type() == QEvent::Show) {
diff --git a/ui/widgets/conversation.h b/ui/widgets/conversation.h
index f64ae54..27ce074 100644
--- a/ui/widgets/conversation.h
+++ b/ui/widgets/conversation.h
@@ -22,6 +22,8 @@
 #include <QWidget>
 #include <QScopedPointer>
 #include <QMap>
+#include <QMimeData>
+#include <QFileInfo>
 
 #include "shared/message.h"
 #include "order.h"
@@ -105,6 +107,9 @@ protected:
     void addAttachedFile(const QString& path);
     void removeAttachedFile(Badge* badge);
     void clearAttachedFiles();
+    void dragEnterEvent(QDragEnterEvent* event) override;
+    void dragLeaveEvent(QDragLeaveEvent* event) override;
+    void dropEvent(QDropEvent* event) override;
     
 protected slots:
     void onEnterPressed();
@@ -138,6 +143,7 @@ protected:
     QLabel* statusIcon;
     QLabel* statusLabel;
     FlowLayout* filesLayout;
+    QWidget* overlay;
     W::Order<Badge*, Badge::Comparator> filesToAttach;
     Scroll scroll;
     bool manualSliderChange;
diff --git a/ui/widgets/conversation.ui b/ui/widgets/conversation.ui
index d73875d..7093bcb 100644
--- a/ui/widgets/conversation.ui
+++ b/ui/widgets/conversation.ui
@@ -16,10 +16,10 @@
     <verstretch>0</verstretch>
    </sizepolicy>
   </property>
-  <layout class="QVBoxLayout" name="horizontalLayout">
-   <property name="spacing">
-    <number>0</number>
-   </property>
+  <property name="acceptDrops">
+   <bool>true</bool>
+  </property>
+  <layout class="QGridLayout" name="gridLayout">
    <property name="leftMargin">
     <number>0</number>
    </property>
@@ -32,7 +32,7 @@
    <property name="bottomMargin">
     <number>0</number>
    </property>
-   <item>
+   <item row="0" column="0">
     <widget class="QWidget" name="widget" native="true">
      <property name="sizePolicy">
       <sizepolicy hsizetype="Preferred" vsizetype="MinimumExpanding">
@@ -115,6 +115,13 @@
            </property>
            <item>
             <widget class="QLabel" name="nameLabel">
+             <property name="font">
+              <font>
+               <pointsize>12</pointsize>
+               <weight>75</weight>
+               <bold>true</bold>
+              </font>
+             </property>
              <property name="text">
               <string/>
              </property>
@@ -128,6 +135,12 @@
                <verstretch>0</verstretch>
               </sizepolicy>
              </property>
+             <property name="font">
+              <font>
+               <pointsize>8</pointsize>
+               <italic>true</italic>
+              </font>
+             </property>
              <property name="text">
               <string/>
              </property>
@@ -230,7 +243,7 @@
            <x>0</x>
            <y>0</y>
            <width>520</width>
-           <height>392</height>
+           <height>385</height>
           </rect>
          </property>
          <layout class="QHBoxLayout" name="horizontalLayout_2">
@@ -258,7 +271,7 @@
      <zorder>widget_3</zorder>
     </widget>
    </item>
-   <item>
+   <item row="1" column="0">
     <widget class="QWidget" name="widget_2" native="true">
      <property name="sizePolicy">
       <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
diff --git a/ui/widgets/vcard/vcard.cpp b/ui/widgets/vcard/vcard.cpp
index e3aa1b9..f1cfe3b 100644
--- a/ui/widgets/vcard/vcard.cpp
+++ b/ui/widgets/vcard/vcard.cpp
@@ -126,7 +126,11 @@ VCard::VCard(const QString& jid, bool edit, QWidget* parent):
     overlay->setAutoFillBackground(true);
     overlay->setGraphicsEffect(opacity);
     progressLabel->setAlignment(Qt::AlignCenter);
-    progressLabel->setStyleSheet("font: 16pt");
+    QFont pf = progressLabel->font();
+    pf.setBold(true);
+    pf.setPointSize(26);
+    progressLabel->setFont(pf);
+    progressLabel->setWordWrap(true);
     nl->addStretch();
     nl->addWidget(progress);
     nl->addWidget(progressLabel);

From 6657dc79ce12ab2a7dc5617bd9978eb5b9d4eaa8 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Tue, 14 Apr 2020 22:40:32 +0300
Subject: [PATCH 065/281] qxmpp bump, ifdef to assemble on lower versions

---
 core/account.cpp | 2 ++
 external/qxmpp   | 2 +-
 2 files changed, 3 insertions(+), 1 deletion(-)

diff --git a/core/account.cpp b/core/account.cpp
index 4c12761..ffa993b 100644
--- a/core/account.cpp
+++ b/core/account.cpp
@@ -1141,9 +1141,11 @@ void Core::Account::onClientError(QXmppClient::Error err)
                 case QXmppStanza::Error::UnexpectedRequest:
                     errorText = "Unexpected request";
                     break;
+#if (QXMPP_VERSION) >= QT_VERSION_CHECK(1, 1, 0)
                 case QXmppStanza::Error::PolicyViolation:
                     errorText = "Policy violation";
                     break;
+#endif
             }
          
             errorType = "Client stream error";
diff --git a/external/qxmpp b/external/qxmpp
index 067c774..fe83e9c 160000
--- a/external/qxmpp
+++ b/external/qxmpp
@@ -1 +1 @@
-Subproject commit 067c7743b1c72d055a749a7611efd2f9026fe784
+Subproject commit fe83e9c3d42c3becf682e2b5ecfc9d77b24c614f

From b50ce146b8d22d2c13814d4af145dca47a774d3c Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Wed, 15 Apr 2020 16:48:49 +0300
Subject: [PATCH 066/281] attaching messages fix, bad alloc fix

---
 core/account.cpp            |  1 +
 core/archive.cpp            | 19 ++++++++++---------
 ui/widgets/chat.cpp         | 13 ++++---------
 ui/widgets/chat.h           |  2 +-
 ui/widgets/conversation.cpp | 28 +++++++++++++++-------------
 ui/widgets/conversation.h   |  2 +-
 ui/widgets/room.cpp         | 14 ++++----------
 ui/widgets/room.h           |  2 +-
 8 files changed, 37 insertions(+), 44 deletions(-)

diff --git a/core/account.cpp b/core/account.cpp
index ffa993b..4c251f7 100644
--- a/core/account.cpp
+++ b/core/account.cpp
@@ -1377,6 +1377,7 @@ void Core::Account::addNewRoom(const QString& jid, const QString& nick, const QS
         {"avatars", conf->getAllAvatars()}
     };
     
+    
     Archive::AvatarInfo info;
     bool hasAvatar = conf->readAvatarInfo(info);
     if (hasAvatar) {
diff --git a/core/archive.cpp b/core/archive.cpp
index 50acf81..179f33e 100644
--- a/core/archive.cpp
+++ b/core/archive.cpp
@@ -817,15 +817,16 @@ void Core::Archive::readAllResourcesAvatars(std::map<QString, AvatarInfo>& data)
     mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn);
     mdb_cursor_open(txn, avatars, &cursor);
     rc = mdb_cursor_get(cursor, &lmdbKey, &lmdbData, MDB_FIRST);
-    
-    do {
-        std::string sId((char*)lmdbKey.mv_data, lmdbKey.mv_size);
-        QString res(sId.c_str());
-        if (res != jid) {
-            data.emplace(res, AvatarInfo());
-            data[res].deserialize((char*)lmdbData.mv_data, lmdbData.mv_size);
-        }
-    } while (mdb_cursor_get(cursor, &lmdbKey, &lmdbData, MDB_NEXT) == 0);
+    if (rc == 0) {                                                  //the db might be empty yet
+        do {
+            std::string sId((char*)lmdbKey.mv_data, lmdbKey.mv_size);
+            QString res(sId.c_str());
+            if (res != jid) {
+                data.emplace(res, AvatarInfo());
+                data[res].deserialize((char*)lmdbData.mv_data, lmdbData.mv_size);
+            }
+        } while (mdb_cursor_get(cursor, &lmdbKey, &lmdbData, MDB_NEXT) == 0);
+    }
     
     mdb_cursor_close(cursor);
     mdb_txn_abort(txn);
diff --git a/ui/widgets/chat.cpp b/ui/widgets/chat.cpp
index c9a41ad..acbcac1 100644
--- a/ui/widgets/chat.cpp
+++ b/ui/widgets/chat.cpp
@@ -61,19 +61,14 @@ void Chat::updateState()
     statusIcon->setToolTip(Shared::Global::getName(av));
 }
 
-void Chat::handleSendMessage(const QString& text)
+Shared::Message Chat::createMessage() const
 {
-    Shared::Message msg(Shared::Message::chat);
+    Shared::Message msg = Conversation::createMessage();
+    msg.setType(Shared::Message::chat);
     msg.setFrom(account->getFullJid());
     msg.setToJid(palJid);
     msg.setToResource(activePalResource);
-    msg.setBody(text);
-    msg.setOutgoing(true);
-    msg.generateRandomId();
-    msg.setCurrentTime();
-    msg.setState(Shared::Message::State::pending);
-    addMessage(msg);
-    emit sendMessage(msg);
+    return msg;
 }
 
 void Chat::addMessage(const Shared::Message& data)
diff --git a/ui/widgets/chat.h b/ui/widgets/chat.h
index 66ac53a..f05b0fa 100644
--- a/ui/widgets/chat.h
+++ b/ui/widgets/chat.h
@@ -43,7 +43,7 @@ protected slots:
     
 protected:
     void setName(const QString & name) override;
-    void handleSendMessage(const QString & text) override;
+    Shared::Message createMessage() const override;
     
 private:
     void updateState();
diff --git a/ui/widgets/conversation.cpp b/ui/widgets/conversation.cpp
index ec772a6..60b0210 100644
--- a/ui/widgets/conversation.cpp
+++ b/ui/widgets/conversation.cpp
@@ -208,22 +208,14 @@ void Conversation::onEnterPressed()
     
     if (body.size() > 0) {
         m_ui->messageEditor->clear();
-        handleSendMessage(body);
+        Shared::Message msg = createMessage();
+        msg.setBody(body);
+        addMessage(msg);
+        emit sendMessage(msg);
     }
     if (filesToAttach.size() > 0) {
         for (Badge* badge : filesToAttach) {
-            Shared::Message msg;
-            if (isMuc) {
-                msg.setType(Shared::Message::groupChat);
-            } else {
-                msg.setType(Shared::Message::chat);
-                msg.setToResource(activePalResource);
-            }
-            msg.setFrom(account->getFullJid());
-            msg.setToJid(palJid);
-            msg.setOutgoing(true);
-            msg.generateRandomId();
-            msg.setCurrentTime();
+            Shared::Message msg = createMessage();
             line->appendMessageWithUpload(msg, badge->id);
             usleep(1000);       //this is required for the messages not to have equal time when appending into messageline
         }
@@ -486,6 +478,16 @@ void Conversation::dropEvent(QDropEvent* event)
     overlay->hide();
 }
 
+Shared::Message Conversation::createMessage() const
+{
+    Shared::Message msg;
+    msg.setOutgoing(true);
+    msg.generateRandomId();
+    msg.setCurrentTime();
+    msg.setState(Shared::Message::State::pending);
+    return msg;
+}
+
 bool VisibilityCatcher::eventFilter(QObject* obj, QEvent* event)
 {
     if (event->type() == QEvent::Show) {
diff --git a/ui/widgets/conversation.h b/ui/widgets/conversation.h
index 27ce074..e5ac53a 100644
--- a/ui/widgets/conversation.h
+++ b/ui/widgets/conversation.h
@@ -102,7 +102,7 @@ signals:
 protected:
     virtual void setName(const QString& name);
     void applyVisualEffects();
-    virtual void handleSendMessage(const QString& text) = 0;
+    virtual Shared::Message createMessage() const;
     void setStatus(const QString& status);
     void addAttachedFile(const QString& path);
     void removeAttachedFile(Badge* badge);
diff --git a/ui/widgets/room.cpp b/ui/widgets/room.cpp
index 66ae5f7..5bc73b2 100644
--- a/ui/widgets/room.cpp
+++ b/ui/widgets/room.cpp
@@ -46,20 +46,14 @@ Room::~Room()
 {
 }
 
-void Room::handleSendMessage(const QString& text)
+Shared::Message Room::createMessage() const
 {
-    Shared::Message msg(Shared::Message::groupChat);
+    Shared::Message msg = Conversation::createMessage();
+    msg.setType(Shared::Message::groupChat);
     msg.setFromJid(room->getJid());
     msg.setFromResource(room->getNick());
     msg.setToJid(palJid);
-    //msg.setToResource(activePalResource);
-    msg.setBody(text);
-    msg.setOutgoing(true);
-    msg.generateRandomId();
-    msg.setCurrentTime();
-    msg.setState(Shared::Message::State::pending);
-    addMessage(msg);
-    emit sendMessage(msg);
+    return msg;
 }
 
 bool Room::autoJoined() const
diff --git a/ui/widgets/room.h b/ui/widgets/room.h
index 2cf7831..3f74e4e 100644
--- a/ui/widgets/room.h
+++ b/ui/widgets/room.h
@@ -40,7 +40,7 @@ protected slots:
     void onParticipantLeft(const QString& name);
     
 protected:
-    void handleSendMessage(const QString & text) override;
+    Shared::Message createMessage() const override;
     
 private:
     Models::Room* room;

From a8698cc94f934b3ecf0621fdab225eed7c61f5a9 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Wed, 15 Apr 2020 20:27:38 +0300
Subject: [PATCH 067/281] minor bugfixes

---
 CHANGELOG.md              |  8 ++++++++
 core/account.cpp          |  2 +-
 translations/squawk.ru.ts | 12 ++++--------
 ui/squawk.cpp             |  2 +-
 ui/utils/messageline.cpp  | 18 +++++++-----------
 5 files changed, 21 insertions(+), 21 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index c328fb5..d61aaf2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,13 @@
 # Changelog
 
+## Squawk 0.1.5 (UNRELEASED)
+### Bug fixes
+- error with sending attached files to the conference
+- error with building on lower versions of QXmpp
+- error on the first access to the conference database
+- quit now actually quits the app
+
+
 ## Squawk 0.1.4 (Apr 14, 2020)
 ### New features
 - message line now is in the same window with roster (new window dialog is still able to opened on context menu)
diff --git a/core/account.cpp b/core/account.cpp
index 4c251f7..9ef85ee 100644
--- a/core/account.cpp
+++ b/core/account.cpp
@@ -1141,7 +1141,7 @@ void Core::Account::onClientError(QXmppClient::Error err)
                 case QXmppStanza::Error::UnexpectedRequest:
                     errorText = "Unexpected request";
                     break;
-#if (QXMPP_VERSION) >= QT_VERSION_CHECK(1, 1, 0)
+#if (QXMPP_VERSION) >= QT_VERSION_CHECK(1, 3, 0)
                 case QXmppStanza::Error::PolicyViolation:
                     errorText = "Policy violation";
                     break;
diff --git a/translations/squawk.ru.ts b/translations/squawk.ru.ts
index c425d52..e3b4d52 100644
--- a/translations/squawk.ru.ts
+++ b/translations/squawk.ru.ts
@@ -383,10 +383,6 @@ p, li { white-space: pre-wrap; }
         <source>Download</source>
         <translation>Скачать</translation>
     </message>
-    <message>
-        <source>Push the button to daownload the file</source>
-        <translation>Нажмите на кнопку что бы загрузить файл</translation>
-    </message>
     <message>
         <source>Error uploading file: %1
 You can try again</source>
@@ -409,6 +405,10 @@ You can try again</source>
         <source>Uploading...</source>
         <translation>Загружается...</translation>
     </message>
+    <message>
+        <source>Push the button to download the file</source>
+        <translation>Нажмите на кнопку что бы загрузить файл</translation>
+    </message>
 </context>
 <context>
     <name>Models::Room</name>
@@ -548,10 +548,6 @@ You can try again</source>
         <source>Add conference</source>
         <translation>Присоединиться к беседе</translation>
     </message>
-    <message>
-        <source>Contact list</source>
-        <translation>Список контактов</translation>
-    </message>
     <message>
         <source>Disconnect</source>
         <translation>Отключить</translation>
diff --git a/ui/squawk.cpp b/ui/squawk.cpp
index a808dff..26d69e0 100644
--- a/ui/squawk.cpp
+++ b/ui/squawk.cpp
@@ -54,6 +54,7 @@ Squawk::Squawk(QWidget *parent) :
     connect(m_ui->actionAccounts, &QAction::triggered, this, &Squawk::onAccounts);
     connect(m_ui->actionAddContact, &QAction::triggered, this, &Squawk::onNewContact);
     connect(m_ui->actionAddConference, &QAction::triggered, this, &Squawk::onNewConference);
+    connect(m_ui->actionQuit, &QAction::triggered, this, &Squawk::close);
     connect(m_ui->comboBox, qOverload<int>(&QComboBox::activated), this, &Squawk::onComboboxActivated);
     //connect(m_ui->roster, &QTreeView::doubleClicked, this, &Squawk::onRosterItemDoubleClicked);
     connect(m_ui->roster, &QTreeView::customContextMenuRequested, this, &Squawk::onRosterContextMenu);
@@ -64,7 +65,6 @@ Squawk::Squawk(QWidget *parent) :
     connect(contextMenu, &QMenu::aboutToHide, this, &Squawk::onContextAboutToHide);
     //m_ui->mainToolBar->addWidget(m_ui->comboBox);
     
-    setWindowTitle(tr("Contact list"));
     if (testAttribute(Qt::WA_TranslucentBackground)) {
         m_ui->roster->viewport()->setAutoFillBackground(false);
     }
diff --git a/ui/utils/messageline.cpp b/ui/utils/messageline.cpp
index 0ef5f07..fec0037 100644
--- a/ui/utils/messageline.cpp
+++ b/ui/utils/messageline.cpp
@@ -369,16 +369,12 @@ void MessageLine::responseLocalFile(const QString& messageId, const QString& pat
                     if (muItr != uploadPaths.end()) {
                         uploadPaths.erase(muItr);
                     }
-                    if (room) {
-                        removeMessage(messageId);
-                    } else {
-                        Shared::Message msg = itr->second->getMessage();
-                        removeMessage(messageId);
-                        msg.setCurrentTime();
-                        message(msg);
-                        itr = messageIndex.find(messageId);
-                        itr->second->showFile(path);
-                    }
+                    Shared::Message msg = itr->second->getMessage();
+                    removeMessage(messageId);
+                    msg.setCurrentTime();
+                    message(msg);
+                    itr = messageIndex.find(messageId);
+                    itr->second->showFile(path);
                 } else {
                     itr->second->showFile(path); //then it is already cached file
                 }
@@ -387,7 +383,7 @@ void MessageLine::responseLocalFile(const QString& messageId, const QString& pat
             if (uItr == uploading.end()) {
                 const Shared::Message& msg = itr->second->getMessage();
                 itr->second->addButton(QIcon::fromTheme("download"), tr("Download"), "<a href=\"" + msg.getOutOfBandUrl() + "\">" + msg.getOutOfBandUrl() + "</a>");
-                itr->second->showComment(tr("Push the button to daownload the file"));
+                itr->second->showComment(tr("Push the button to download the file"));
             } else {
                 qDebug() << "An unhandled state for file uploading - empty path";
             }

From 494afcf2b569eb440747c194b7ac4bb18c92bb7e Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Thu, 16 Apr 2020 23:23:02 +0300
Subject: [PATCH 068/281] initial class reference

---
 ui/CMakeLists.txt       |  1 +
 ui/models/item.cpp      |  3 +-
 ui/models/item.h        | 12 ++++--
 ui/models/reference.cpp | 93 +++++++++++++++++++++++++++++++++++++++++
 ui/models/reference.h   | 54 ++++++++++++++++++++++++
 ui/models/roster.cpp    |  4 +-
 6 files changed, 161 insertions(+), 6 deletions(-)
 create mode 100644 ui/models/reference.cpp
 create mode 100644 ui/models/reference.h

diff --git a/ui/CMakeLists.txt b/ui/CMakeLists.txt
index 4e47abe..52913a8 100644
--- a/ui/CMakeLists.txt
+++ b/ui/CMakeLists.txt
@@ -24,6 +24,7 @@ set(squawkUI_SRC
   models/room.cpp
   models/abstractparticipant.cpp
   models/participant.cpp
+  models/reference.cpp
   utils/messageline.cpp
   utils//message.cpp
   utils/resizer.cpp
diff --git a/ui/models/item.cpp b/ui/models/item.cpp
index 1d0437f..b653702 100644
--- a/ui/models/item.cpp
+++ b/ui/models/item.cpp
@@ -26,7 +26,8 @@ Models::Item::Item(Type p_type, const QMap<QString, QVariant> &p_data, Item *p_p
     type(p_type),
     name(""),
     childItems(),
-    parent(p_parent)
+    parent(p_parent),
+    references()
 {
     QMap<QString, QVariant>::const_iterator itr = p_data.find("name");
     if (itr != p_data.end()) {
diff --git a/ui/models/item.h b/ui/models/item.h
index a936e34..77f2824 100644
--- a/ui/models/item.h
+++ b/ui/models/item.h
@@ -26,10 +26,12 @@
 #include <deque>
 
 #include "shared/enums.h"
-
 namespace Models {
 
+class Reference;
+    
 class Item : public QObject{
+    friend class Reference;
     Q_OBJECT
     public:
         enum Type {
@@ -39,7 +41,8 @@ class Item : public QObject{
             room,
             presence,
             participant,
-            root
+            root,
+            reference
         };
         
         explicit Item(Type p_type, const QMap<QString, QVariant> &data, Item *parentItem = 0);
@@ -62,8 +65,8 @@ class Item : public QObject{
         QString getName() const;
         void setName(const QString& name);
         
-        Item *child(int row);
-        int childCount() const;
+        virtual Item *child(int row);
+        virtual int childCount() const;
         virtual int columnCount() const;
         virtual QVariant data(int column) const;
         int row() const;
@@ -92,6 +95,7 @@ class Item : public QObject{
         QString name;
         std::deque<Item*> childItems;
         Item* parent;
+        std::deque<Item*> references;
         
     protected slots:
         virtual void toOfflineState();
diff --git a/ui/models/reference.cpp b/ui/models/reference.cpp
new file mode 100644
index 0000000..4ead77f
--- /dev/null
+++ b/ui/models/reference.cpp
@@ -0,0 +1,93 @@
+/*
+ * Squawk messenger. 
+ * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+ * 
+ * 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 "reference.h"
+
+using namespace Models;
+
+Models::Reference::Reference(Models::Item* original, Models::Item* parent):
+    Models::Item(reference, {}, parent),
+    original(original)
+{
+    original->references.push_back(this);
+    
+    connect(original, &Item::childChanged, this, &Item::childChanged);
+    connect(original, &Item::childIsAboutToBeInserted, this, &Item::childIsAboutToBeInserted);
+    connect(original, &Item::childInserted, this, &Item::childInserted);
+    connect(original, &Item::childIsAboutToBeRemoved, this, &Item::childIsAboutToBeRemoved);
+    connect(original, &Item::childRemoved, this, &Item::childRemoved);
+    connect(original, &Item::childIsAboutToBeMoved, this, &Item::childIsAboutToBeMoved);
+    connect(original, &Item::childMoved, this, &Item::childMoved);
+}
+
+Models::Reference::~Reference()
+{
+    disconnect(original, &Item::childIsAboutToBeInserted, this, &Item::childIsAboutToBeInserted);
+    disconnect(original, &Item::childInserted, this, &Item::childInserted);
+    disconnect(original, &Item::childIsAboutToBeRemoved, this, &Item::childIsAboutToBeRemoved);
+    disconnect(original, &Item::childRemoved, this, &Item::childRemoved);
+    disconnect(original, &Item::childIsAboutToBeMoved, this, &Item::childIsAboutToBeMoved);
+    disconnect(original, &Item::childMoved, this, &Item::childMoved);
+    
+    for (std::deque<Item*>::const_iterator itr = original->references.begin(), end = original->references.end(); itr != end; itr++) {
+        if (*itr == this) {
+            original->references.erase(itr);
+            break;
+        }
+    }
+}
+
+int Models::Reference::columnCount() const
+{
+    return original->columnCount();
+}
+
+QVariant Models::Reference::data(int column) const
+{
+    return original->data(column);
+}
+
+QString Models::Reference::getDisplayedName() const
+{
+    return original->getDisplayedName();
+}
+
+Models::Item * Models::Reference::dereference()
+{
+    return original;
+}
+
+const Models::Item * Models::Reference::dereferenceConst() const
+{
+    return original;
+}
+
+void Models::Reference::appendChild(Models::Item* child)
+{
+    original->appendChild(child);
+}
+
+void Models::Reference::removeChild(int index)
+{
+    original->removeChild(index);
+}
+
+void Models::Reference::toOfflineState()
+{
+    original->toOfflineState();
+}
diff --git a/ui/models/reference.h b/ui/models/reference.h
new file mode 100644
index 0000000..84d0b07
--- /dev/null
+++ b/ui/models/reference.h
@@ -0,0 +1,54 @@
+/*
+ * Squawk messenger. 
+ * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+ * 
+ * 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/>.
+ */
+
+#ifndef MODELS_REFERENCE_H
+#define MODELS_REFERENCE_H
+
+#include "item.h"
+
+namespace Models {
+
+/**
+ * @todo write docs
+ */
+class Reference : public Models::Item
+{
+    Q_OBJECT
+public:
+    Reference(Models::Item* original, Models::Item* parent);
+    ~Reference();
+    
+    int columnCount() const override;
+    QVariant data(int column) const override;
+    QString getDisplayedName() const override;
+    void appendChild(Models::Item * child) override;
+    void removeChild(int index) override;
+    Item* dereference();
+    const Item* dereferenceConst() const;
+    
+protected slots:
+    void toOfflineState() override;
+    
+private:
+    Models::Item* original;
+    
+};
+
+}
+
+#endif // MODELS_REFERENCE_H
diff --git a/ui/models/roster.cpp b/ui/models/roster.cpp
index 42ea9f8..f1e6efa 100644
--- a/ui/models/roster.cpp
+++ b/ui/models/roster.cpp
@@ -480,7 +480,9 @@ void Models::Roster::addContact(const QString& account, const QString& jid, cons
             if (item->type == Item::contact) {
                 Contact* ca = static_cast<Contact*>(item);
                 if (ca->getJid() == jid) {
-                    qDebug() << "An attempt to add a already existing contact " << jid << " to the group " << group << ", contact will be moved from ungrouped contacts of " << account;
+                    qDebug()    << "An attempt to add a already existing contact " << jid 
+                                << " to the group " << group 
+                                << ", contact will be moved from ungrouped contacts of " << account;
                     
                     parent->appendChild(ca);
                     return;

From 83a2e6af8586396912c4fd22294f5ba56e0ed18d Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Sat, 18 Apr 2020 02:17:47 +0300
Subject: [PATCH 069/281] first prototype

---
 ui/models/account.cpp   |   3 +-
 ui/models/account.h     |   3 +
 ui/models/contact.cpp   |  15 ++-
 ui/models/contact.h     |   5 +-
 ui/models/group.cpp     |  14 +-
 ui/models/group.h       |   4 +-
 ui/models/item.cpp      | 117 ++++++++++++----
 ui/models/item.h        |  13 +-
 ui/models/reference.cpp | 118 +++++++++++++---
 ui/models/reference.h   |  16 ++-
 ui/models/roster.cpp    | 288 +++++++++++++++++-----------------------
 ui/models/roster.h      |   3 +-
 ui/squawk.cpp           |  10 +-
 13 files changed, 365 insertions(+), 244 deletions(-)

diff --git a/ui/models/account.cpp b/ui/models/account.cpp
index a60315c..00dd6b2 100644
--- a/ui/models/account.cpp
+++ b/ui/models/account.cpp
@@ -17,6 +17,8 @@
  */
 
 #include "account.h"
+#include "contact.h"
+#include "reference.h"
 #include <QDebug>
 
 Models::Account::Account(const QMap<QString, QVariant>& data, Models::Item* parentItem):
@@ -267,4 +269,3 @@ void Models::Account::setPasswordType(unsigned int pt)
 {
     setPasswordType(Shared::Global::fromInt<Shared::AccountPassword>(pt));
 }
-
diff --git a/ui/models/account.h b/ui/models/account.h
index d2bb79f..2563382 100644
--- a/ui/models/account.h
+++ b/ui/models/account.h
@@ -28,6 +28,9 @@
 #include <QIcon>
 
 namespace Models {
+    class Contact;
+    class Reference;
+    
     class Account : public Item {
         Q_OBJECT
     public:
diff --git a/ui/models/contact.cpp b/ui/models/contact.cpp
index 9cd011a..cc3caba 100644
--- a/ui/models/contact.cpp
+++ b/ui/models/contact.cpp
@@ -17,10 +17,11 @@
  */
 
 #include "contact.h"
+#include "account.h"
 
 #include <QDebug>
 
-Models::Contact::Contact(const QString& p_jid ,const QMap<QString, QVariant> &data, Item *parentItem):
+Models::Contact::Contact(const Account* acc, const QString& p_jid ,const QMap<QString, QVariant> &data, Item *parentItem):
     Item(Item::contact, data, parentItem),
     jid(p_jid),
     availability(Shared::Availability::offline),
@@ -30,7 +31,8 @@ Models::Contact::Contact(const QString& p_jid ,const QMap<QString, QVariant> &da
     messages(),
     childMessages(0),
     status(),
-    avatarPath()
+    avatarPath(),
+    account(acc)
 {
     QMap<QString, QVariant>::const_iterator itr = data.find("state");
     if (itr != data.end()) {
@@ -368,7 +370,8 @@ Models::Contact::Contact(const Models::Contact& other):
     state(other.state),
     presences(),
     messages(other.messages),
-    childMessages(0)
+    childMessages(0),
+    account(other.account)
 {
     for (const Presence* pres : other.presences) {
         Presence* pCopy = new Presence(*pres);
@@ -415,3 +418,9 @@ void Models::Contact::setAvatarState(unsigned int p_state)
         qDebug() << "An attempt to set invalid avatar state" << p_state << "to the contact" << jid << ", skipping";
     }
 }
+
+const Models::Account * Models::Contact::getParentAccount() const
+{
+    return account;
+}
+
diff --git a/ui/models/contact.h b/ui/models/contact.h
index eb2be6b..39418ae 100644
--- a/ui/models/contact.h
+++ b/ui/models/contact.h
@@ -31,13 +31,14 @@
 #include <deque>
 
 namespace Models {
+class Account;
     
 class Contact : public Item
 {
     Q_OBJECT
 public:
     typedef std::deque<Shared::Message> Messages;
-    Contact(const QString& p_jid, const QMap<QString, QVariant> &data, Item *parentItem = 0);
+    Contact(const Account* acc, const QString& p_jid, const QMap<QString, QVariant> &data, Item *parentItem = 0);
     Contact(const Contact& other);
     ~Contact();
     
@@ -72,6 +73,7 @@ public:
 protected:
     void _removeChild(int index) override;
     bool columnInvolvedInDisplay(int col) override;
+    const Account* getParentAccount() const override;
     
 protected slots:
     void refresh();
@@ -98,6 +100,7 @@ private:
     unsigned int childMessages;
     QString status;
     QString avatarPath;
+    const Account* account;
 };
 
 }
diff --git a/ui/models/group.cpp b/ui/models/group.cpp
index 76dc03e..5cf411b 100644
--- a/ui/models/group.cpp
+++ b/ui/models/group.cpp
@@ -18,6 +18,7 @@
 
 #include "group.h"
 #include "contact.h"
+#include "reference.h"
 
 Models::Group::Group(const QMap<QString, QVariant>& data, Models::Item* parentItem):
     Item(group, data, parentItem),
@@ -103,16 +104,3 @@ unsigned int Models::Group::getOnlineContacts() const
     
     return amount;
 }
-
-bool Models::Group::hasContact(const QString& jid) const
-{
-    for (Models::Item* item : childItems) {
-        if (item->type == Item::contact) {
-            const Contact* cnt = static_cast<const Contact*>(item);
-            if (cnt->getJid() == jid) {
-                return true;
-            }
-        }
-    }
-    return false;
-}
diff --git a/ui/models/group.h b/ui/models/group.h
index c2f4bfe..7343b63 100644
--- a/ui/models/group.h
+++ b/ui/models/group.h
@@ -23,6 +23,8 @@
 
 namespace Models {
 
+class Contact;
+    
 class Group : public Models::Item
 {
     Q_OBJECT
@@ -36,8 +38,6 @@ public:
     
     unsigned int getUnreadMessages() const;
     unsigned int getOnlineContacts() const;
-    
-    bool hasContact(const QString& jid) const;
 
 protected:
     void _removeChild(int index) override;
diff --git a/ui/models/item.cpp b/ui/models/item.cpp
index b653702..566c26a 100644
--- a/ui/models/item.cpp
+++ b/ui/models/item.cpp
@@ -18,6 +18,8 @@
 
 #include "item.h"
 #include "account.h"
+#include "reference.h"
+#include "contact.h"
 
 #include <QDebug>
 
@@ -46,11 +48,16 @@ Models::Item::Item(const Models::Item& other):
 
 Models::Item::~Item()
 {
-    std::deque<Item*>::const_iterator itr = childItems.begin();
-    std::deque<Item*>::const_iterator end = childItems.end();
+    for (Reference* ref : references) {
+        Item* parent = ref->parentItem();
+        if (parent != nullptr) {
+            parent->removeChild(ref->row());
+        }
+        delete ref;
+    }
     
-    for (;itr != end; ++itr) {
-        delete (*itr);
+    for (Item* child : childItems) {
+        delete child;
     }
 }
 
@@ -67,18 +74,26 @@ void Models::Item::appendChild(Models::Item* child)
     bool moving = false;
     int newRow = 0;
     std::deque<Item*>::const_iterator before = childItems.begin();
+    Type ct = child->type;
+    if (ct == reference) {
+        ct = static_cast<Reference*>(child)->dereference()->type;
+    }
     while (before != childItems.end()) {
         Item* bfr = *before;
-        if (bfr->type > child->type) {
+        Type bt = bfr->type;
+        if (bt == reference) {
+            bt = static_cast<Reference*>(bfr)->dereference()->type;
+        }
+        if (bt > ct) {
             break;
-        } else if (bfr->type == child->type && bfr->getDisplayedName() > child->getDisplayedName()) {
+        } else if (bt == ct && bfr->getDisplayedName() > child->getDisplayedName()) {
             break;
         }
         newRow++;
         before++;
     }
     
-    if (child->parent != 0) {
+    if (child->parent != nullptr) {
         int oldRow = child->row();
         moving = true;
         emit childIsAboutToBeMoved(child->parent, oldRow, oldRow, this, newRow);
@@ -116,7 +131,7 @@ int Models::Item::childCount() const
 
 int Models::Item::row() const
 {
-    if (parent != 0) {
+    if (parent != nullptr) {
         std::deque<Item*>::const_iterator itr = parent->childItems.begin();
         std::deque<Item*>::const_iterator end = parent->childItems.end();
         
@@ -127,7 +142,7 @@ int Models::Item::row() const
         }
     }
     
-    return 0;       //TODO not sure how it helps, i copy-pasted it from the example
+    return -1;       //TODO not sure how it helps
 }
 
 Models::Item * Models::Item::parentItem()
@@ -178,13 +193,13 @@ void Models::Item::_removeChild(int index)
     QObject::disconnect(child, &Item::childMoved, this, &Item::childMoved);
     
     childItems.erase(childItems.begin() + index);
-    child->parent = 0;
+    child->parent = nullptr;
 }
 
 
 void Models::Item::changed(int col)
 {
-    if (parent != 0) {
+    if (parent != nullptr) {
         emit childChanged(this, row(), col);
     }
 }
@@ -197,21 +212,21 @@ void Models::Item::toOfflineState()
     }
 }
 
-const Models::Item * Models::Item::getParentAccount() const
+const Models::Account * Models::Item::getParentAccount() const
 {
     const Item* p = this;
     
-    while (p != 0 && p->type != Item::account) {
+    while (p != nullptr && p->type != Item::account) {
         p = p->parentItemConst();
     }
     
-    return p;
+    return static_cast<const Account*>(p);
 }
 
 QString Models::Item::getAccountJid() const
 {
-    const Account* acc = static_cast<const Account*>(getParentAccount());
-    if (acc == 0) {
+    const Account* acc = getParentAccount();
+    if (acc == nullptr) {
         return "";
     }
     return acc->getLogin() + "@" + acc->getServer();
@@ -219,8 +234,8 @@ QString Models::Item::getAccountJid() const
 
 QString Models::Item::getAccountResource() const
 {
-    const Account* acc = static_cast<const Account*>(getParentAccount());
-    if (acc == 0) {
+    const Account* acc = getParentAccount();
+    if (acc == nullptr) {
         return "";
     }
     return acc->getResource();
@@ -228,8 +243,8 @@ QString Models::Item::getAccountResource() const
 
 QString Models::Item::getAccountName() const
 {
-    const Account* acc = static_cast<const Account*>(getParentAccount());
-    if (acc == 0) {
+    const Account* acc = getParentAccount();
+    if (acc == nullptr) {
         return "";
     }
     return acc->getName();
@@ -237,8 +252,8 @@ QString Models::Item::getAccountName() const
 
 Shared::Availability Models::Item::getAccountAvailability() const
 {
-    const Account* acc = static_cast<const Account*>(getParentAccount());
-    if (acc == 0) {
+    const Account* acc = getParentAccount();
+    if (acc == nullptr) {
         return Shared::Availability::offline;
     }
     return acc->getAvailability();
@@ -246,8 +261,8 @@ Shared::Availability Models::Item::getAccountAvailability() const
 
 Shared::ConnectionState Models::Item::getAccountConnectionState() const
 {
-    const Account* acc = static_cast<const Account*>(getParentAccount());
-    if (acc == 0) {
+    const Account* acc = getParentAccount();
+    if (acc == nullptr) {
         return Shared::ConnectionState::disconnected;
     }
     return acc->getState();
@@ -261,15 +276,24 @@ QString Models::Item::getDisplayedName() const
 void Models::Item::onChildChanged(Models::Item* item, int row, int col)
 {
     Item* parent = item->parentItem();
-    if (parent != 0 && parent == this) {
+    if (parent != nullptr && parent == this) {
         if (item->columnInvolvedInDisplay(col)) {
             int newRow = 0;
             std::deque<Item*>::const_iterator before = childItems.begin();
+            
+            Type ct = item->type;
+            if (ct == reference) {
+                ct = static_cast<Reference*>(item)->dereference()->type;
+            }
             while (before != childItems.end()) {
                 Item* bfr = *before;
-                if (bfr->type > item->type) {
+                Type bt = bfr->type;
+                if (bt == reference) {
+                    bt = static_cast<Reference*>(bfr)->dereference()->type;
+                }
+                if (bt > ct) {
                     break;
-                } else if (bfr->type == item->type && bfr->getDisplayedName() > item->getDisplayedName()) {
+                } else if (bt == ct && bfr->getDisplayedName() > item->getDisplayedName()) {
                     break;
                 }
                 newRow++;
@@ -293,3 +317,42 @@ bool Models::Item::columnInvolvedInDisplay(int col)
 {
     return col == 0;
 }
+
+void Models::Item::addReference(Models::Reference* ref)
+{
+    references.insert(ref);
+}
+
+void Models::Item::removeReference(Models::Reference* ref)
+{
+    std::set<Reference*>::const_iterator itr = references.find(ref);
+    if (itr != references.end()) {
+        references.erase(itr);
+    }
+}
+
+int Models::Item::getContact(const QString& jid) const
+{
+    int index = -1;
+    for (std::deque<Item*>::size_type i = 0; i < childItems.size(); ++i) {
+        const Models::Item* item = childItems[i];
+        const Contact* cnt = nullptr;
+        if (item->type == Item::reference) {
+            item = static_cast<const Reference*>(item)->dereferenceConst();
+        }
+        
+        if (item->type == Item::contact) {
+            cnt = static_cast<const Contact*>(item);
+            if (cnt->getJid() == jid) {
+                index = i;
+                break;
+            }
+        }
+    }
+    return index;
+}
+
+std::set<Models::Reference *>::size_type Models::Item::referencesCount() const
+{
+    return references.size();
+}
diff --git a/ui/models/item.h b/ui/models/item.h
index 77f2824..76f1780 100644
--- a/ui/models/item.h
+++ b/ui/models/item.h
@@ -24,11 +24,14 @@
 #include <QVariant>
 
 #include <deque>
+#include <set>
 
 #include "shared/enums.h"
 namespace Models {
 
 class Reference;
+class Contact;
+class Account;
     
 class Item : public QObject{
     friend class Reference;
@@ -82,11 +85,17 @@ class Item : public QObject{
         
         const Type type;
         
+        int getContact(const QString& jid) const;
+        std::set<Reference*>::size_type referencesCount() const;
+        
+        void addReference(Reference* ref);
+        void removeReference(Reference* ref);
+        
     protected:
         virtual void changed(int col);
         virtual void _removeChild(int index);
         virtual bool columnInvolvedInDisplay(int col);
-        const Item* getParentAccount() const;
+        virtual const Account* getParentAccount() const;
         
     protected slots:
         void onChildChanged(Models::Item* item, int row, int col);
@@ -95,7 +104,7 @@ class Item : public QObject{
         QString name;
         std::deque<Item*> childItems;
         Item* parent;
-        std::deque<Item*> references;
+        std::set<Reference*> references;
         
     protected slots:
         virtual void toOfflineState();
diff --git a/ui/models/reference.cpp b/ui/models/reference.cpp
index 4ead77f..26d6b94 100644
--- a/ui/models/reference.cpp
+++ b/ui/models/reference.cpp
@@ -17,39 +17,37 @@
  */
 
 #include "reference.h"
+#include <QDebug>
 
 using namespace Models;
 
 Models::Reference::Reference(Models::Item* original, Models::Item* parent):
     Models::Item(reference, {}, parent),
-    original(original)
+    original(original),
+    ax(-1),
+    bx(-1),
+    cx(-1),
+    c(false)
 {
-    original->references.push_back(this);
+    connect(original, &Item::childChanged, this, &Reference::onChildChanged);
+    connect(original, &Item::childIsAboutToBeInserted, this, &Reference::onChildIsAboutToBeInserted);
+    connect(original, &Item::childInserted, this, &Reference::onChildInserted);
+    connect(original, &Item::childIsAboutToBeRemoved, this, &Reference::onChildIsAboutToBeRemoved);
+    connect(original, &Item::childRemoved, this, &Reference::onChildRemoved);
+    connect(original, &Item::childIsAboutToBeMoved, this, &Reference::onChildIsAboutToBeMoved);
+    connect(original, &Item::childMoved, this, &Reference::onChildMoved);
     
-    connect(original, &Item::childChanged, this, &Item::childChanged);
-    connect(original, &Item::childIsAboutToBeInserted, this, &Item::childIsAboutToBeInserted);
-    connect(original, &Item::childInserted, this, &Item::childInserted);
-    connect(original, &Item::childIsAboutToBeRemoved, this, &Item::childIsAboutToBeRemoved);
-    connect(original, &Item::childRemoved, this, &Item::childRemoved);
-    connect(original, &Item::childIsAboutToBeMoved, this, &Item::childIsAboutToBeMoved);
-    connect(original, &Item::childMoved, this, &Item::childMoved);
+    original->addReference(this);
 }
 
 Models::Reference::~Reference()
 {
-    disconnect(original, &Item::childIsAboutToBeInserted, this, &Item::childIsAboutToBeInserted);
-    disconnect(original, &Item::childInserted, this, &Item::childInserted);
-    disconnect(original, &Item::childIsAboutToBeRemoved, this, &Item::childIsAboutToBeRemoved);
-    disconnect(original, &Item::childRemoved, this, &Item::childRemoved);
-    disconnect(original, &Item::childIsAboutToBeMoved, this, &Item::childIsAboutToBeMoved);
-    disconnect(original, &Item::childMoved, this, &Item::childMoved);
-    
-    for (std::deque<Item*>::const_iterator itr = original->references.begin(), end = original->references.end(); itr != end; itr++) {
-        if (*itr == this) {
-            original->references.erase(itr);
-            break;
-        }
-    }
+    disconnect(original, &Item::childIsAboutToBeInserted, this, &Reference::onChildIsAboutToBeInserted);
+    disconnect(original, &Item::childInserted, this, &Reference::onChildInserted);
+    disconnect(original, &Item::childIsAboutToBeRemoved, this, &Reference::onChildIsAboutToBeRemoved);
+    disconnect(original, &Item::childRemoved, this, &Reference::onChildRemoved);
+    disconnect(original, &Item::childIsAboutToBeMoved, this, &Reference::onChildIsAboutToBeMoved);
+    disconnect(original, &Item::childMoved, this, &Reference::onChildMoved);
 }
 
 int Models::Reference::columnCount() const
@@ -91,3 +89,79 @@ void Models::Reference::toOfflineState()
 {
     original->toOfflineState();
 }
+
+void Models::Reference::onChildChanged(Models::Item* item, int row, int col)
+{
+    if (item == original) {
+        emit childChanged(this, row, col);
+    }
+}
+
+void Models::Reference::onChildIsAboutToBeInserted(Models::Item* parent, int first, int last)
+{
+    if (parent == original) {
+        ax = first;
+        bx = last;
+    }
+}
+
+void Models::Reference::onChildInserted()
+{
+    if (ax != -1 && bx != -1) {
+        for (int i = ax; i <= bx; ++i) {
+            Reference* ref = new Reference(original->child(i));
+            Item::appendChild(ref);
+        }
+        ax = -1;
+        bx = -1;
+    }
+}
+
+void Models::Reference::onChildIsAboutToBeRemoved(Models::Item* parent, int first, int last)
+{
+    if (parent == original) {
+        for (int i = first; i <= last; ++i) {
+            Reference* ref = static_cast<Reference*>(childItems[i]);
+            Item* orig = original->child(i);
+            orig->removeReference(ref);
+            Item::removeChild(i);
+            delete ref;
+        }
+    }
+}
+
+void Models::Reference::onChildRemoved()
+{
+}
+
+void Models::Reference::onChildIsAboutToBeMoved(Models::Item* source, int first, int last, Models::Item* destination, int newIndex)
+{
+    if (destination == original) {
+        if (source != original) {
+            c = true;
+            ax = first;
+            bx = last;
+            cx = newIndex;
+            emit childIsAboutToBeMoved(source, first, last, destination, newIndex);
+        } else {
+            ax = newIndex;
+            bx = newIndex + last - first + 1;
+        }
+    }
+}
+
+void Models::Reference::onChildMoved()
+{
+    if (c) {
+        std::deque<Item*>::const_iterator beg = childItems.begin() + ax;
+        std::deque<Item*>::const_iterator end = childItems.begin() + bx + 1;
+        std::deque<Item*>::const_iterator tgt = childItems.begin() + cx;
+        std::deque<Item*> temp;
+        temp.insert(temp.end(), beg, end);
+        childItems.erase(beg, end);
+        childItems.insert(tgt, temp.begin(), temp.end());
+        emit childMoved();
+    } else {
+        onChildInserted();
+    }
+}
diff --git a/ui/models/reference.h b/ui/models/reference.h
index 84d0b07..8ec5352 100644
--- a/ui/models/reference.h
+++ b/ui/models/reference.h
@@ -30,7 +30,7 @@ class Reference : public Models::Item
 {
     Q_OBJECT
 public:
-    Reference(Models::Item* original, Models::Item* parent);
+    Reference(Models::Item* original, Models::Item* parent = 0);
     ~Reference();
     
     int columnCount() const override;
@@ -44,9 +44,21 @@ public:
 protected slots:
     void toOfflineState() override;
     
+private slots:
+    void onChildChanged(Models::Item* item, int row, int col);
+    void onChildIsAboutToBeInserted(Item* parent, int first, int last);
+    void onChildInserted();
+    void onChildIsAboutToBeRemoved(Item* parent, int first, int last);
+    void onChildRemoved();
+    void onChildIsAboutToBeMoved(Item* source, int first, int last, Item* destination, int newIndex);
+    void onChildMoved();
+    
 private:
     Models::Item* original;
-    
+    int ax;
+    int bx;
+    int cx;
+    bool c;
 };
 
 }
diff --git a/ui/models/roster.cpp b/ui/models/roster.cpp
index f1e6efa..91f1f74 100644
--- a/ui/models/roster.cpp
+++ b/ui/models/roster.cpp
@@ -62,6 +62,9 @@ QVariant Models::Roster::data (const QModelIndex& index, int role) const
     QVariant result;
     
     Item *item = static_cast<Item*>(index.internalPointer());
+    if (item->type == Item::reference) {
+        item = static_cast<Reference*>(item)->dereference();
+    }
     switch (role) {
         case Qt::DisplayRole:
         {
@@ -430,7 +433,8 @@ void Models::Roster::addContact(const QString& account, const QString& jid, cons
 {
     Item* parent;
     Account* acc;
-    Contact* sample = 0;
+    Contact* contact;
+    Reference* ref = 0;
     ElId id(account, jid);
     
     {
@@ -442,13 +446,18 @@ void Models::Roster::addContact(const QString& account, const QString& jid, cons
         acc = itr->second;
     }
     
-    for (std::multimap<ElId, Contact*>::iterator itr = contacts.lower_bound(id), eItr = contacts.upper_bound(id); itr != eItr; ++itr) {
-        sample = itr->second;                   //need to find if this contact is already added somewhere
-        break;                                  //so one iteration is enough
+    {
+        std::map<ElId, Contact*>::iterator itr = contacts.find(id);
+        if (itr == contacts.end()) {
+            contact = new Contact(acc, jid, data);
+            contacts.insert(std::make_pair(id, contact));
+        } else {
+            contact = itr->second;
+        }
     }
     
-    if (group == "") {       //this means this contact is already added somewhere and there is no sense to add it ungrouped
-        if (sample != 0) {
+    if (group == "") {
+        if (acc->getContact(jid) != -1) {
             qDebug() << "An attempt to add a contact" << jid << "to the ungrouped contact set of account" << account << "for the second time, skipping";
             return;
         } else {
@@ -464,41 +473,26 @@ void Models::Roster::addContact(const QString& account, const QString& jid, cons
         
         parent = itr->second;
         
-        for (int i = 0; i < parent->childCount(); ++i) {        //checking if the contact is already added to that group
-            Item* item = parent->child(i);
-            if (item->type == Item::contact) {
-                Contact* ca = static_cast<Contact*>(item);
-                if (ca->getJid() == jid) {
-                    qDebug() << "An attempt to add a contact" << jid << "to the group" << group << "for the second time, skipping";
-                    return;
-                }
-            }
+        if (parent->getContact(jid) != -1) {
+            qDebug() << "An attempt to add a contact" << jid << "to the group" << group << "for the second time, skipping";
+            return;
         }
         
-        for (int i = 0; i < acc->childCount(); ++i) {           //checking if that contact is among ugrouped
-            Item* item = acc->child(i);
-            if (item->type == Item::contact) {
-                Contact* ca = static_cast<Contact*>(item);
-                if (ca->getJid() == jid) {
-                    qDebug()    << "An attempt to add a already existing contact " << jid 
-                                << " to the group " << group 
-                                << ", contact will be moved from ungrouped contacts of " << account;
-                    
-                    parent->appendChild(ca);
-                    return;
-                }
-            }
+        int refIndex = acc->getContact(jid);
+        if (refIndex != -1) {           //checking if that contact is among ugrouped
+            qDebug()    << "An attempt to add a already existing contact " << jid 
+                        << " to the group " << group 
+                        << ", contact will be moved from ungrouped contacts of " << account;
+            ref = static_cast<Reference*>(acc->child(refIndex));
+            acc->removeChild(refIndex);
         }
         
     }
-    Contact* contact;
-    if (sample == 0) {
-        contact = new Contact(jid, data);
-    } else {
-        contact = sample->copy();
+    
+    if (ref == 0) {
+        ref = new Reference(contact);
     }
-    contacts.insert(std::make_pair(id, contact));
-    parent->appendChild(contact);
+    parent->appendChild(ref);
 }
 
 void Models::Roster::removeGroup(const QString& account, const QString& name)
@@ -515,33 +509,24 @@ void Models::Roster::removeGroup(const QString& account, const QString& name)
     
     parent->removeChild(row);
     
-    std::deque<Contact*> toInsert;
+    std::deque<Reference*> toInsert;
     for (int i = 0; item->childCount() > 0; ++i) {
-        Contact* cont = static_cast<Contact*>(item->child(0));
+        Reference* ref = static_cast<Reference*>(item->child(0));
         item->removeChild(0);
         
-        std::multimap<ElId, Contact*>::iterator cBeg = contacts.lower_bound({account, cont->getJid()});
-        std::multimap<ElId, Contact*>::iterator cEnd = contacts.upper_bound({account, cont->getJid()});
-        
-        int count = std::distance(cBeg, cEnd);
-        if (count > 1) {
-            for (; cBeg != cEnd; ++count, ++cBeg) {
-                if (cBeg->second == cont) {
-                    delete cont;
-                    contacts.erase(cBeg);
-                    break;
-                }
-            }
+        Contact* cont = static_cast<Contact*>(ref->dereference());
+        if (cont->referencesCount() == 1) {
+            toInsert.push_back(ref);
         } else {
-            toInsert.push_back(cont);
+            cont->removeReference(ref);
+            delete ref;
         }
     }
     
     if (toInsert.size() > 0) {
         Account* acc = accounts.find("account")->second;
         for (std::deque<Contact*>::size_type i = 0; i < toInsert.size(); ++i) {
-            Contact* cont = toInsert[i];
-            acc->appendChild(cont);             //TODO optimisation
+            acc->appendChild(toInsert[i]);             //TODO optimisation
         }
     }
     
@@ -552,19 +537,18 @@ void Models::Roster::removeGroup(const QString& account, const QString& name)
 void Models::Roster::changeContact(const QString& account, const QString& jid, const QMap<QString, QVariant>& data)
 {
     ElId id(account, jid);
-    std::multimap<ElId, Contact*>::iterator cBeg = contacts.lower_bound(id);
-    std::multimap<ElId, Contact*>::iterator cEnd = contacts.upper_bound(id);
+    std::map<ElId, Contact*>::iterator cItr = contacts.find(id);
     
-    for (; cBeg != cEnd; ++cBeg) {
+    if (cItr != contacts.end()) {
         for (QMap<QString, QVariant>::const_iterator itr = data.begin(), end = data.end(); itr != end; ++itr) {
-            cBeg->second->update(itr.key(), itr.value());
+            cItr->second->update(itr.key(), itr.value());
         }
-    }
-    
-    std::map<ElId, Room*>::iterator rItr = rooms.find(id);
-    if (rItr != rooms.end()) {
-        for (QMap<QString, QVariant>::const_iterator itr = data.begin(), end = data.end(); itr != end; ++itr) {
-            rItr->second->update(itr.key(), itr.value());
+    } else {
+        std::map<ElId, Room*>::iterator rItr = rooms.find(id);
+        if (rItr != rooms.end()) {
+            for (QMap<QString, QVariant>::const_iterator itr = data.begin(), end = data.end(); itr != end; ++itr) {
+                rItr->second->update(itr.key(), itr.value());
+            }
         }
     }
 }
@@ -572,18 +556,13 @@ void Models::Roster::changeContact(const QString& account, const QString& jid, c
 void Models::Roster::changeMessage(const QString& account, const QString& jid, const QString& id, const QMap<QString, QVariant>& data)
 {
     ElId elid(account, jid);
-    std::multimap<ElId, Contact*>::iterator cBeg = contacts.lower_bound(elid);
-    std::multimap<ElId, Contact*>::iterator cEnd = contacts.upper_bound(elid);
+    std::map<ElId, Contact*>::iterator cItr = contacts.find(elid);
     
-    for (; cBeg != cEnd; ++cBeg) {
-        for (QMap<QString, QVariant>::const_iterator itr = data.begin(), end = data.end(); itr != end; ++itr) {
-            cBeg->second->changeMessage(id, data);
-        }
-    }
-    
-    std::map<ElId, Room*>::iterator rItr = rooms.find(elid);
-    if (rItr != rooms.end()) {
-        for (QMap<QString, QVariant>::const_iterator itr = data.begin(), end = data.end(); itr != end; ++itr) {
+    if (cItr != contacts.end()) {
+        cItr->second->changeMessage(id, data);
+    } else {
+        std::map<ElId, Room*>::iterator rItr = rooms.find(elid);
+        if (rItr != rooms.end()) {
             rItr->second->changeMessage(id, data);
         }
     }
@@ -592,25 +571,26 @@ void Models::Roster::changeMessage(const QString& account, const QString& jid, c
 void Models::Roster::removeContact(const QString& account, const QString& jid)
 {
     ElId id(account, jid);
-    std::multimap<ElId, Contact*>::iterator cBeg = contacts.lower_bound(id);
-    std::multimap<ElId, Contact*>::iterator cEnd = contacts.upper_bound(id);
-    std::multimap<ElId, Contact*>::iterator cpBeg = cBeg;
+    std::map<ElId, Contact*>::iterator itr = contacts.find(id);
     
-    QSet<QString> toRemove;
-    for (; cBeg != cEnd; ++cBeg) {
-        Contact* contact = cBeg->second;
-        Item* parent = contact->parentItem();
-        if (parent->type == Item::group && parent->childCount() == 1) {
-            toRemove.insert(parent->getName());
+    if (itr != contacts.end()) {
+        Contact* contact = itr->second;
+        
+        contacts.erase(itr);
+        delete contact;
+        
+        std::set<ElId> toRemove;
+        for (std::pair<ElId, Group*> pair : groups) {
+            if (pair.second->childCount() == 0) {
+                toRemove.insert(pair.first);
+            }
         }
         
-        parent->removeChild(contact->row());
-        contact->deleteLater();
-    }
-    contacts.erase(cpBeg, cEnd);
-    
-    for (QSet<QString>::const_iterator itr = toRemove.begin(), end = toRemove.end(); itr != end; ++itr) {
-        removeGroup(account, *itr);
+        for (const ElId& elId : toRemove) {
+            removeGroup(elId.account, elId.name);
+        }
+    } else {
+        qDebug() << "An attempt to remove contact " << jid << " from account " << account <<" which doesn't exist there, skipping";
     }
 }
 
@@ -619,6 +599,12 @@ void Models::Roster::removeContact(const QString& account, const QString& jid, c
     ElId contactId(account, jid);
     ElId groupId(account, group);
     
+    std::map<ElId, Contact*>::iterator cItr = contacts.find(contactId);
+    if (cItr == contacts.end()) {
+        qDebug() << "An attempt to remove non existing contact " << jid << " from group " << group << " of account " << account <<", skipping";
+        return;
+    }
+    
     std::map<ElId, Group*>::iterator gItr = groups.find(groupId);
     if (gItr == groups.end()) {
         qDebug() << "An attempt to remove contact " << jid << " from non existing group " << group << " of account " << account <<", skipping";
@@ -626,57 +612,26 @@ void Models::Roster::removeContact(const QString& account, const QString& jid, c
     }
     Account* acc = accounts.find(account)->second;  //I assume the account is found, otherwise there will be no groups with that ElId;
     Group* gr = gItr->second;
-    Contact* cont = 0;
+    Contact* cont = cItr->second;
     
-    unsigned int entries(0);
-    unsigned int ungroupped(0);
-    for (std::multimap<ElId, Contact*>::iterator cBeg = contacts.lower_bound(contactId), cEnd = contacts.upper_bound(contactId); cBeg != cEnd; ++cBeg) {
-        ++entries;
-        Contact* elem = cBeg->second;
-        if (elem->parentItem() == acc) {
-            ++ungroupped;
-        }
+    int contRow = gr->getContact(jid);
+    if (contRow == -1) {
+        qDebug() << "An attempt to remove contact " << jid << " of account " << account << " from group " << group  <<", but there is no such contact in that group, skipping";
+        return;
+    }
+    Reference* ref = static_cast<Reference*>(gr->child(contRow));
+    gr->removeChild(contRow);
+    
+    if (cont->referencesCount() == 1) {
+        qDebug() << "An attempt to remove last instance of contact" << jid << "from the group" << group << ", contact will be moved to ungrouped contacts of" << account;
+        acc->appendChild(ref);
+    } else {
+        cont->removeReference(ref);
+        delete ref;
     }
     
-    if (ungroupped == 0 && entries == 1) {
-        for (std::multimap<ElId, Contact*>::iterator cBeg = contacts.lower_bound(contactId), cEnd = contacts.upper_bound(contactId); cBeg != cEnd; ++cBeg) {
-            if (cBeg->second->parentItem() == gr) {
-                cont = cBeg->second;
-                break;
-            }
-        }
-        
-        if (cont == 0) {
-            qDebug() << "An attempt to remove contact " << jid << " of account " << account << " from group " << group  <<", but there is no such contact in that group, skipping";
-            return;
-        }
-        
-        qDebug() << "An attempt to remove last instance of contact" << jid << "from the group" << group << ", contact will be moved to ungrouped contacts of" << account;
-        acc->appendChild(cont);
-        
-        if (gr->childCount() == 0) {
-            removeGroup(account, group);
-        }
-    } else {
-        for (std::multimap<ElId, Contact*>::iterator cBeg = contacts.lower_bound(contactId), cEnd = contacts.upper_bound(contactId); cBeg != cEnd; ++cBeg) {
-            if (cBeg->second->parentItem() == gr) {
-                cont = cBeg->second;
-                contacts.erase(cBeg);
-                break;
-            }
-        }
-        
-        if (cont == 0) {
-            qDebug() << "An attempt to remove contact" << jid << "of account" << account << "from group" << group  <<", but there is no such contact in that group, skipping";
-            return;
-        }
-        
-        gr->removeChild(cont->row());
-        cont->deleteLater();
-        
-        if (gr->childCount() == 0) {
-            removeGroup(account, group);
-        }
+    if (gr->childCount() == 0) {
+        removeGroup(account, group);
     }
 }
 
@@ -738,50 +693,46 @@ void Models::Roster::onChildRemoved()
 void Models::Roster::addPresence(const QString& account, const QString& jid, const QString& name, const QMap<QString, QVariant>& data)
 {
     ElId contactId(account, jid);
-    std::multimap<ElId, Contact*>::iterator cBeg = contacts.lower_bound(contactId);
-    std::multimap<ElId, Contact*>::iterator cEnd = contacts.upper_bound(contactId);
-    for (;cBeg != cEnd; ++cBeg) {
-        cBeg->second->addPresence(name, data);
+    std::map<ElId, Contact*>::iterator itr = contacts.find(contactId);
+    if (itr != contacts.end()) {
+        itr->second->addPresence(name, data);
     }
 }
 
 void Models::Roster::removePresence(const QString& account, const QString& jid, const QString& name)
 {
     ElId contactId(account, jid);
-    std::multimap<ElId, Contact*>::iterator cBeg = contacts.lower_bound(contactId);
-    std::multimap<ElId, Contact*>::iterator cEnd = contacts.upper_bound(contactId);
-    for (;cBeg != cEnd; ++cBeg) {
-        cBeg->second->removePresence(name);
+    std::map<ElId, Contact*>::iterator itr = contacts.find(contactId);
+    if (itr != contacts.end()) {
+        itr->second->removePresence(name);
     }
 }
 
 void Models::Roster::addMessage(const QString& account, const Shared::Message& data)
 {
     ElId id(account, data.getPenPalJid());
-    
-    std::multimap<ElId, Contact*>::iterator cBeg = contacts.lower_bound(id);
-    std::multimap<ElId, Contact*>::iterator cEnd = contacts.upper_bound(id);
-    
-    for (;cBeg != cEnd; ++cBeg) {
-        cBeg->second->addMessage(data);
-    }
-    
-    std::map<ElId, Room*>::const_iterator rItr = rooms.find(id);
-    if (rItr != rooms.end()) {
-        rItr->second->addMessage(data);
+    std::map<ElId, Contact*>::iterator itr = contacts.find(id);
+    if (itr != contacts.end()) {
+        itr->second->addMessage(data);
+    } else {
+        std::map<ElId, Room*>::const_iterator rItr = rooms.find(id);
+        if (rItr != rooms.end()) {
+            rItr->second->addMessage(data);
+        }
     }
 }
 
 void Models::Roster::dropMessages(const QString& account, const QString& jid)
 {
     ElId id(account, jid);
-    for (std::multimap<ElId, Contact*>::iterator cBeg = contacts.lower_bound(id), cEnd = contacts.upper_bound(id) ;cBeg != cEnd; ++cBeg) {
-        cBeg->second->dropMessages();
-    }
-    
-    std::map<ElId, Room*>::const_iterator rItr = rooms.find(id);
-    if (rItr != rooms.end()) {
-        rItr->second->dropMessages();
+    std::map<ElId, Contact*>::iterator itr = contacts.find(id);
+    if (itr != contacts.end()) {
+        itr->second->dropMessages();
+    } else {
+        std::map<ElId, Room*>::const_iterator rItr = rooms.find(id);
+        if (rItr != rooms.end()) {
+            rItr->second->dropMessages();
+        }
     }
 }
 
@@ -799,10 +750,10 @@ void Models::Roster::removeAccount(const QString& account)
     accountsModel->removeAccount(index);
     accounts.erase(itr);
     
-    std::multimap<ElId, Contact*>::const_iterator cItr = contacts.begin();
+    std::map<ElId, Contact*>::const_iterator cItr = contacts.begin();
     while (cItr != contacts.end()) {
         if (cItr->first.account == account) {
-            std::multimap<ElId, Contact*>::const_iterator lItr = cItr;
+            std::map<ElId, Contact*>::const_iterator lItr = cItr;
             ++cItr;
             contacts.erase(lItr);
         } else {
@@ -838,7 +789,7 @@ void Models::Roster::removeAccount(const QString& account)
 QString Models::Roster::getContactName(const QString& account, const QString& jid)
 {
     ElId id(account, jid);
-    std::multimap<ElId, Contact*>::const_iterator cItr = contacts.find(id);
+    std::map<ElId, Contact*>::const_iterator cItr = contacts.find(id);
     QString name = "";
     if (cItr == contacts.end()) {
         std::map<ElId, Room*>::const_iterator rItr = rooms.find(id);
@@ -971,15 +922,14 @@ bool Models::Roster::groupHasContact(const QString& account, const QString& grou
     if (gItr == groups.end()) {
         return false;
     } else {
-        const Group* gr = gItr->second;
-        return gr->hasContact(contact);
+        return gItr->second->getContact(contact) != -1;
     }
 }
 
 QString Models::Roster::getContactIconPath(const QString& account, const QString& jid, const QString& resource)
 {
     ElId id(account, jid);
-    std::multimap<ElId, Contact*>::const_iterator cItr = contacts.find(id);
+    std::map<ElId, Contact*>::const_iterator cItr = contacts.find(id);
     QString path = "";
     if (cItr == contacts.end()) {
         std::map<ElId, Room*>::const_iterator rItr = rooms.find(id);
diff --git a/ui/models/roster.h b/ui/models/roster.h
index 34c343c..d866b6d 100644
--- a/ui/models/roster.h
+++ b/ui/models/roster.h
@@ -32,6 +32,7 @@
 #include "contact.h"
 #include "group.h"
 #include "room.h"
+#include "reference.h"
 
 namespace Models
 {
@@ -87,7 +88,7 @@ private:
     Item* root;
     std::map<QString, Account*> accounts;
     std::map<ElId, Group*> groups;
-    std::multimap<ElId, Contact*> contacts;
+    std::map<ElId, Contact*> contacts;
     std::map<ElId, Room*> rooms;
     
 private slots:
diff --git a/ui/squawk.cpp b/ui/squawk.cpp
index 26d69e0..41634ad 100644
--- a/ui/squawk.cpp
+++ b/ui/squawk.cpp
@@ -305,6 +305,9 @@ void Squawk::onRosterItemDoubleClicked(const QModelIndex& item)
 {
     if (item.isValid()) {
         Models::Item* node = static_cast<Models::Item*>(item.internalPointer());
+        if (node->type == Models::Item::reference) {
+            node = static_cast<Models::Reference*>(node)->dereference();
+        }
         Models::Contact* contact = 0;
         Models::Room* room = 0;
         QString res;
@@ -681,7 +684,9 @@ void Squawk::onRosterContextMenu(const QPoint& point)
     QModelIndex index = m_ui->roster->indexAt(point);
     if (index.isValid()) {
         Models::Item* item = static_cast<Models::Item*>(index.internalPointer());
-    
+        if (item->type == Models::Item::reference) {
+            item = static_cast<Models::Reference*>(item)->dereference();
+        }
         contextMenu->clear();
         bool hasMenu = false;
         bool active = item->getAccountConnectionState() == Shared::ConnectionState::connected;
@@ -1105,6 +1110,9 @@ void Squawk::onRosterSelectionChanged(const QModelIndex& current, const QModelIn
     
     if (current.isValid()) {
         Models::Item* node = static_cast<Models::Item*>(current.internalPointer());
+        if (node->type == Models::Item::reference) {
+            node = static_cast<Models::Reference*>(node)->dereference();
+        }
         Models::Contact* contact = 0;
         Models::Room* room = 0;
         QString res;

From 9c855553c5ace9bc20964dc1121f90bdd820ba66 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Sat, 18 Apr 2020 15:02:01 +0300
Subject: [PATCH 070/281] referencing seems to be working now

---
 ui/models/contact.cpp   | 27 +++++++++++++++------------
 ui/models/contact.h     |  2 +-
 ui/models/group.cpp     | 28 +++++++++++++++++++---------
 ui/models/group.h       |  2 +-
 ui/models/item.cpp      | 24 ++++++++++++++++++++----
 ui/models/item.h        | 13 ++++++-------
 ui/models/reference.cpp | 20 +++++++++++++++-----
 ui/models/roster.cpp    |  2 --
 8 files changed, 77 insertions(+), 41 deletions(-)

diff --git a/ui/models/contact.cpp b/ui/models/contact.cpp
index cc3caba..57744d8 100644
--- a/ui/models/contact.cpp
+++ b/ui/models/contact.cpp
@@ -226,9 +226,9 @@ void Models::Contact::_removeChild(int index)
     refresh();
 }
 
-void Models::Contact::appendChild(Models::Item* child)
+void Models::Contact::_appendChild(Models::Item* child)
 {
-    Item::appendChild(child);
+    Item::_appendChild(child);
     connect(child, &Item::childChanged, this, &Contact::refresh);
     refresh();
 }
@@ -334,17 +334,20 @@ void Models::Contact::getMessages(Models::Contact::Messages& container) const
 
 void Models::Contact::toOfflineState()
 {
-    emit childIsAboutToBeRemoved(this, 0, childItems.size());
-    for (std::deque<Item*>::size_type i = 0; i < childItems.size(); ++i) {
-        Item* item = childItems[i];
-        disconnect(item, &Item::childChanged, this, &Contact::refresh);
-        Item::_removeChild(i);
-        item->deleteLater();
+    std::deque<Item*>::size_type size = childItems.size();
+    if (size > 0) {
+        emit childIsAboutToBeRemoved(this, 0, size - 1);
+        for (std::deque<Item*>::size_type i = 0; i < size; ++i) {
+            Item* item = childItems[0];
+            disconnect(item, &Item::childChanged, this, &Contact::refresh);
+            Item::_removeChild(0);
+            item->deleteLater();
+        }
+        childItems.clear();
+        presences.clear();
+        emit childRemoved();
+        refresh();
     }
-    childItems.clear();
-    presences.clear();
-    emit childRemoved();
-    refresh();
 }
 
 QString Models::Contact::getDisplayedName() const
diff --git a/ui/models/contact.h b/ui/models/contact.h
index 39418ae..c8c99b5 100644
--- a/ui/models/contact.h
+++ b/ui/models/contact.h
@@ -57,7 +57,6 @@ public:
     void addPresence(const QString& name, const QMap<QString, QVariant>& data);
     void removePresence(const QString& name);
     
-    void appendChild(Models::Item * child) override;
     QString getContactName() const;
     QString getStatus() const;
     
@@ -72,6 +71,7 @@ public:
     
 protected:
     void _removeChild(int index) override;
+    void _appendChild(Models::Item * child) override;
     bool columnInvolvedInDisplay(int col) override;
     const Account* getParentAccount() const override;
     
diff --git a/ui/models/group.cpp b/ui/models/group.cpp
index 5cf411b..013ff13 100644
--- a/ui/models/group.cpp
+++ b/ui/models/group.cpp
@@ -30,9 +30,9 @@ Models::Group::~Group()
 {
 }
 
-void Models::Group::appendChild(Models::Item* child)
+void Models::Group::_appendChild(Models::Item* child)
 {
-    Item::appendChild(child);
+    Item::_appendChild(child);
     connect(child, &Item::childChanged, this, &Group::refresh);
     changed(1);
     refresh();
@@ -83,9 +83,14 @@ void Models::Group::refresh()
 {
     unsigned int newAmount(0);
     
-    for (std::deque<Models::Item*>::const_iterator itr = childItems.begin(), end = childItems.end(); itr != end; ++itr) {
-        Models::Contact* cnt = static_cast<Models::Contact*>(*itr);
-        newAmount += cnt->getMessagesCount();
+    for (Models::Item* item : childItems) {
+        if (item->type == reference) {
+            item = static_cast<Reference*>(item)->dereference();
+        }
+        if (item->type == contact) {
+            Models::Contact* cnt = static_cast<Models::Contact*>(item);
+            newAmount += cnt->getMessagesCount();
+        }
     }
     
     setUnreadMessages(newAmount);
@@ -95,10 +100,15 @@ unsigned int Models::Group::getOnlineContacts() const
 {
     unsigned int amount(0);
     
-    for (std::deque<Models::Item*>::const_iterator itr = childItems.begin(), end = childItems.end(); itr != end; ++itr) {
-        Models::Contact* cnt = static_cast<Models::Contact*>(*itr);
-        if (cnt->getAvailability() != Shared::Availability::offline) {
-            ++amount;
+    for (Models::Item* item : childItems) {
+        if (item->type == reference) {
+            item = static_cast<Reference*>(item)->dereference();
+        }
+        if (item->type == contact) {
+            Models::Contact* cnt = static_cast<Models::Contact*>(item);
+            if (cnt->getAvailability() != Shared::Availability::offline) {
+                ++amount;
+            }
         }
     }
     
diff --git a/ui/models/group.h b/ui/models/group.h
index 7343b63..5a0baaf 100644
--- a/ui/models/group.h
+++ b/ui/models/group.h
@@ -32,7 +32,6 @@ public:
     Group(const QMap<QString, QVariant> &data, Item *parentItem = 0);
     ~Group();
     
-    void appendChild(Models::Item* child) override;
     int columnCount() const override;
     QVariant data(int column) const override;
     
@@ -41,6 +40,7 @@ public:
 
 protected:
     void _removeChild(int index) override;
+    void _appendChild(Models::Item* child) override;
     void setUnreadMessages(unsigned int amount);
     
 private slots:
diff --git a/ui/models/item.cpp b/ui/models/item.cpp
index 566c26a..c5d6e2a 100644
--- a/ui/models/item.cpp
+++ b/ui/models/item.cpp
@@ -29,7 +29,9 @@ Models::Item::Item(Type p_type, const QMap<QString, QVariant> &p_data, Item *p_p
     name(""),
     childItems(),
     parent(p_parent),
-    references()
+    references(),
+    destroyingByParent(false),
+    destroyingByOriginal(false)
 {
     QMap<QString, QVariant>::const_iterator itr = p_data.find("name");
     if (itr != p_data.end()) {
@@ -48,15 +50,24 @@ Models::Item::Item(const Models::Item& other):
 
 Models::Item::~Item()
 {
-    for (Reference* ref : references) {
-        Item* parent = ref->parentItem();
+    if (!destroyingByParent) {
+        Item* parent = parentItem();
         if (parent != nullptr) {
-            parent->removeChild(ref->row());
+            if (parent->type == reference) {
+                parent->Item::removeChild(row());
+            } else {
+                parent->removeChild(row());
+            }
         }
+    }
+    
+    for (Reference* ref : references) {
+        ref->destroyingByOriginal = true;
         delete ref;
     }
     
     for (Item* child : childItems) {
+        child->destroyingByParent = true;
         delete child;
     }
 }
@@ -70,6 +81,11 @@ void Models::Item::setName(const QString& p_name)
 }
 
 void Models::Item::appendChild(Models::Item* child)
+{
+    _appendChild(child);
+}
+
+void Models::Item::_appendChild(Models::Item* child)
 {
     bool moving = false;
     int newRow = 0;
diff --git a/ui/models/item.h b/ui/models/item.h
index 76f1780..4f3e29a 100644
--- a/ui/models/item.h
+++ b/ui/models/item.h
@@ -88,27 +88,26 @@ class Item : public QObject{
         int getContact(const QString& jid) const;
         std::set<Reference*>::size_type referencesCount() const;
         
-        void addReference(Reference* ref);
-        void removeReference(Reference* ref);
-        
     protected:
         virtual void changed(int col);
         virtual void _removeChild(int index);
+        virtual void _appendChild(Item *child);
         virtual bool columnInvolvedInDisplay(int col);
         virtual const Account* getParentAccount() const;
+        void addReference(Reference* ref);
+        void removeReference(Reference* ref);
         
     protected slots:
         void onChildChanged(Models::Item* item, int row, int col);
+        virtual void toOfflineState();
         
     protected:
         QString name;
         std::deque<Item*> childItems;
         Item* parent;
         std::set<Reference*> references;
-        
-    protected slots:
-        virtual void toOfflineState();
-        
+        bool destroyingByParent;
+        bool destroyingByOriginal;
     };
 
 }
diff --git a/ui/models/reference.cpp b/ui/models/reference.cpp
index 26d6b94..cb8efad 100644
--- a/ui/models/reference.cpp
+++ b/ui/models/reference.cpp
@@ -29,7 +29,7 @@ Models::Reference::Reference(Models::Item* original, Models::Item* parent):
     cx(-1),
     c(false)
 {
-    connect(original, &Item::childChanged, this, &Reference::onChildChanged);
+    connect(original, &Item::childChanged, this, &Reference::onChildChanged, Qt::QueuedConnection);
     connect(original, &Item::childIsAboutToBeInserted, this, &Reference::onChildIsAboutToBeInserted);
     connect(original, &Item::childInserted, this, &Reference::onChildInserted);
     connect(original, &Item::childIsAboutToBeRemoved, this, &Reference::onChildIsAboutToBeRemoved);
@@ -38,10 +38,20 @@ Models::Reference::Reference(Models::Item* original, Models::Item* parent):
     connect(original, &Item::childMoved, this, &Reference::onChildMoved);
     
     original->addReference(this);
+    
+    for (int i = 0; i < original->childCount(); i++) {
+        Reference* ref = new Reference(original->child(i));
+        Item::appendChild(ref);
+    }
 }
 
 Models::Reference::~Reference()
 {
+    if (!destroyingByOriginal) {
+        original->removeReference(this);
+    }
+    
+    disconnect(original, &Item::childChanged, this, &Reference::onChildChanged);
     disconnect(original, &Item::childIsAboutToBeInserted, this, &Reference::onChildIsAboutToBeInserted);
     disconnect(original, &Item::childInserted, this, &Reference::onChildInserted);
     disconnect(original, &Item::childIsAboutToBeRemoved, this, &Reference::onChildIsAboutToBeRemoved);
@@ -120,13 +130,13 @@ void Models::Reference::onChildInserted()
 void Models::Reference::onChildIsAboutToBeRemoved(Models::Item* parent, int first, int last)
 {
     if (parent == original) {
+        emit childIsAboutToBeRemoved(this, first, last);
         for (int i = first; i <= last; ++i) {
-            Reference* ref = static_cast<Reference*>(childItems[i]);
-            Item* orig = original->child(i);
-            orig->removeReference(ref);
-            Item::removeChild(i);
+            Reference* ref = static_cast<Reference*>(childItems[first]);
+            Item::_removeChild(first);
             delete ref;
         }
+        childRemoved();
     }
 }
 
diff --git a/ui/models/roster.cpp b/ui/models/roster.cpp
index 91f1f74..75555ae 100644
--- a/ui/models/roster.cpp
+++ b/ui/models/roster.cpp
@@ -518,7 +518,6 @@ void Models::Roster::removeGroup(const QString& account, const QString& name)
         if (cont->referencesCount() == 1) {
             toInsert.push_back(ref);
         } else {
-            cont->removeReference(ref);
             delete ref;
         }
     }
@@ -626,7 +625,6 @@ void Models::Roster::removeContact(const QString& account, const QString& jid, c
         qDebug() << "An attempt to remove last instance of contact" << jid << "from the group" << group << ", contact will be moved to ungrouped contacts of" << account;
         acc->appendChild(ref);
     } else {
-        cont->removeReference(ref);
         delete ref;
     }
     

From 55ae5858b58412f23a30dc078848e61eea709aa2 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Sun, 19 Apr 2020 16:13:15 +0300
Subject: [PATCH 071/281] some workaround about disconnection segfault

---
 core/account.cpp    | 64 ++++++++++++++++++++++++++++++---------------
 core/account.h      |  1 +
 core/rosteritem.cpp | 10 +++++++
 core/rosteritem.h   |  1 +
 4 files changed, 55 insertions(+), 21 deletions(-)

diff --git a/core/account.cpp b/core/account.cpp
index 9ef85ee..90c5b42 100644
--- a/core/account.cpp
+++ b/core/account.cpp
@@ -219,6 +219,8 @@ void Core::Account::onClientConnected()
 
 void Core::Account::onClientDisconnected()
 {
+    cancelHistoryRequests();
+    pendingVCardRequests.clear();
     clearConferences();
     if (state != Shared::ConnectionState::disconnected) {
         if (reconnectTimes > 0) {
@@ -854,18 +856,20 @@ void Core::Account::onMamMessageReceived(const QString& queryId, const QXmppMess
 {
     if (msg.id().size() > 0 && (msg.body().size() > 0 || msg.outOfBandUrl().size() > 0)) {
         std::map<QString, QString>::const_iterator itr = achiveQueries.find(queryId);
-        QString jid = itr->second;
-        RosterItem* item = getRosterItem(jid);
-        
-        Shared::Message sMsg(static_cast<Shared::Message::Type>(msg.type()));
-        initializeMessage(sMsg, msg, false, true, true);
-        sMsg.setState(Shared::Message::State::sent);
-        
-        QString oId = msg.replaceId();
-        if (oId.size() > 0) {
-            item->correctMessageInArchive(oId, sMsg);
-        } else {
-            item->addMessageToArchive(sMsg);
+        if (itr != achiveQueries.end()) {
+            QString jid = itr->second;
+            RosterItem* item = getRosterItem(jid);
+            
+            Shared::Message sMsg(static_cast<Shared::Message::Type>(msg.type()));
+            initializeMessage(sMsg, msg, false, true, true);
+            sMsg.setState(Shared::Message::State::sent);
+            
+            QString oId = msg.replaceId();
+            if (oId.size() > 0) {
+                item->correctMessageInArchive(oId, sMsg);
+            } else {
+                item->addMessageToArchive(sMsg);
+            }
         }
     } 
     
@@ -896,10 +900,15 @@ void Core::Account::requestArchive(const QString& jid, int count, const QString&
     
     if (contact == 0) {
         qDebug() << "An attempt to request archive for" << jid << "in account" << name << ", but the contact with such id wasn't found, skipping";
-        emit responseArchive(contact->jid, std::list<Shared::Message>());
+        emit responseArchive(jid, std::list<Shared::Message>());
         return;
     }
     
+    if (state != Shared::ConnectionState::connected) {
+        qDebug() << "An attempt to request archive for" << jid << "in account" << name << ", but the account is not online, skipping";
+        emit responseArchive(contact->jid, std::list<Shared::Message>());
+    }
+    
     if (contact->getArchiveState() == RosterItem::empty && before.size() == 0) {
         QXmppMessage msg(getFullJid(), jid, "", "");
         QString last = Shared::generateUUID();
@@ -952,14 +961,16 @@ void Core::Account::onContactNeedHistory(const QString& before, const QString& a
 void Core::Account::onMamResultsReceived(const QString& queryId, const QXmppResultSetReply& resultSetReply, bool complete)
 {
     std::map<QString, QString>::const_iterator itr = achiveQueries.find(queryId);
-    QString jid = itr->second;
-    achiveQueries.erase(itr);
-    
-    RosterItem* ri = getRosterItem(jid);
-    
-    if (ri != 0) {
-        qDebug() << "Flushing messages for" << jid;
-        ri->flushMessagesToArchive(complete, resultSetReply.first(), resultSetReply.last());
+    if (itr != achiveQueries.end()) {
+        QString jid = itr->second;
+        achiveQueries.erase(itr);
+        
+        RosterItem* ri = getRosterItem(jid);
+        
+        if (ri != 0) {
+            qDebug() << "Flushing messages for" << jid;
+            ri->flushMessagesToArchive(complete, resultSetReply.first(), resultSetReply.last());
+        }
     }
 }
 
@@ -1743,3 +1754,14 @@ void Core::Account::onReceiptReceived(const QString& jid, const QString& id)
     }
 }
 
+void Core::Account::cancelHistoryRequests()
+{
+    for (const std::pair<QString, Conference*>& pair : conferences) {
+        pair.second->clearArchiveRequests();
+    }
+    for (const std::pair<QString, Contact*>& pair : contacts) {
+        pair.second->clearArchiveRequests();
+    }
+    achiveQueries.clear();
+}
+
diff --git a/core/account.h b/core/account.h
index 386835c..b706916 100644
--- a/core/account.h
+++ b/core/account.h
@@ -236,6 +236,7 @@ private:
     void logMessage(const QXmppMessage& msg, const QString& reason = "Message wasn't handled: ");
     void storeConferences();
     void clearConferences();
+    void cancelHistoryRequests();
     void sendMessageWithLocalUploadedFile(Shared::Message msg, const QString& url);
     RosterItem* getRosterItem(const QString& jid);
 };
diff --git a/core/rosteritem.cpp b/core/rosteritem.cpp
index c25b339..8260eec 100644
--- a/core/rosteritem.cpp
+++ b/core/rosteritem.cpp
@@ -518,3 +518,13 @@ Shared::VCard Core::RosterItem::handleResponseVCard(const QXmppVCardIq& card, co
     return vCard;
 }
 
+void Core::RosterItem::clearArchiveRequests()
+{
+    syncronizing = false;
+    requestedCount = 0;
+    requestedBefore = "";
+    hisoryCache.clear();
+    responseCache.clear();
+    appendCache.clear();
+    requestCache.clear();
+}
diff --git a/core/rosteritem.h b/core/rosteritem.h
index 47470b1..cecd2e4 100644
--- a/core/rosteritem.h
+++ b/core/rosteritem.h
@@ -76,6 +76,7 @@ public:
     virtual void handlePresence(const QXmppPresence& pres) = 0;
     
     bool changeMessage(const QString& id, const QMap<QString, QVariant>& data);
+    void clearArchiveRequests();
     
 signals:
     void nameChanged(const QString& name);

From 9ca4aa29d466905cf453ad137398518c968f1c4c Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Tue, 28 Apr 2020 23:35:52 +0300
Subject: [PATCH 072/281] started account refactoring

---
 core/CMakeLists.txt              |   1 +
 core/account.cpp                 | 383 +++---------------------------
 core/account.h                   |  24 +-
 core/handlers/messagehandler.cpp | 390 +++++++++++++++++++++++++++++++
 core/handlers/messagehandler.h   |  76 ++++++
 5 files changed, 501 insertions(+), 373 deletions(-)
 create mode 100644 core/handlers/messagehandler.cpp
 create mode 100644 core/handlers/messagehandler.h

diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt
index c9c573b..64319c2 100644
--- a/core/CMakeLists.txt
+++ b/core/CMakeLists.txt
@@ -18,6 +18,7 @@ set(squawkCORE_SRC
     storage.cpp
     networkaccess.cpp
     adapterFuctions.cpp
+    handlers/messagehandler.cpp
 )
 
 add_subdirectory(passwordStorageEngines)
diff --git a/core/account.cpp b/core/account.cpp
index 90c5b42..c9e08eb 100644
--- a/core/account.cpp
+++ b/core/account.cpp
@@ -53,19 +53,19 @@ Account::Account(const QString& p_login, const QString& p_server, const QString&
     avatarType(),
     ownVCardRequestInProgress(false),
     network(p_net),
-    pendingStateMessages(),
-    passwordType(Shared::AccountPassword::plain)
+    passwordType(Shared::AccountPassword::plain),
+    mh(new MessageHandler(this))
 {
     config.setUser(p_login);
     config.setDomain(p_server);
     config.setPassword(p_password);
     config.setAutoAcceptSubscriptions(true);
-    config.setAutoReconnectionEnabled(false);
+    //config.setAutoReconnectionEnabled(false);
     
     QObject::connect(&client, &QXmppClient::connected, this, &Account::onClientConnected);
     QObject::connect(&client, &QXmppClient::disconnected, this, &Account::onClientDisconnected);
     QObject::connect(&client, &QXmppClient::presenceReceived, this, &Account::onPresenceReceived);
-    QObject::connect(&client, &QXmppClient::messageReceived, this, &Account::onMessageReceived);
+    QObject::connect(&client, &QXmppClient::messageReceived, mh, &MessageHandler::onMessageReceived);
     QObject::connect(&client, &QXmppClient::error, this, &Account::onClientError);
     
     QObject::connect(rm, &QXmppRosterManager::rosterReceived, this, &Account::onRosterReceived);
@@ -76,8 +76,8 @@ Account::Account(const QString& p_login, const QString& p_server, const QString&
     
     client.addExtension(cm);
     
-    QObject::connect(cm, &QXmppCarbonManager::messageReceived, this, &Account::onCarbonMessageReceived);
-    QObject::connect(cm, &QXmppCarbonManager::messageSent, this, &Account::onCarbonMessageSent);
+    QObject::connect(cm, &QXmppCarbonManager::messageReceived, mh, &MessageHandler::onCarbonMessageReceived);
+    QObject::connect(cm, &QXmppCarbonManager::messageSent, mh, &MessageHandler::onCarbonMessageSent);
     
     client.addExtension(am);
     
@@ -95,17 +95,17 @@ Account::Account(const QString& p_login, const QString& p_server, const QString&
     //QObject::connect(&vm, &QXmppVCardManager::clientVCardReceived, this, &Account::onOwnVCardReceived); //for some reason it doesn't work, launching from common handler
     
     client.addExtension(um);
-    QObject::connect(um, &QXmppUploadRequestManager::slotReceived, this, &Account::onUploadSlotReceived);
-    QObject::connect(um, &QXmppUploadRequestManager::requestFailed, this, &Account::onUploadSlotRequestFailed);
+    QObject::connect(um, &QXmppUploadRequestManager::slotReceived, mh, &MessageHandler::onUploadSlotReceived);
+    QObject::connect(um, &QXmppUploadRequestManager::requestFailed, mh, &MessageHandler::onUploadSlotRequestFailed);
     
     QObject::connect(dm, &QXmppDiscoveryManager::itemsReceived, this, &Account::onDiscoveryItemsReceived);
     QObject::connect(dm, &QXmppDiscoveryManager::infoReceived, this, &Account::onDiscoveryInfoReceived);
     
-    QObject::connect(network, &NetworkAccess::uploadFileComplete, this, &Account::onFileUploaded);
-    QObject::connect(network, &NetworkAccess::uploadFileError, this, &Account::onFileUploadError);
+    QObject::connect(network, &NetworkAccess::uploadFileComplete, mh, &MessageHandler::onFileUploaded);
+    QObject::connect(network, &NetworkAccess::uploadFileError, mh, &MessageHandler::onFileUploadError);
     
     client.addExtension(rcpm);
-    QObject::connect(rcpm, &QXmppMessageReceiptManager::messageDelivered, this, &Account::onReceiptReceived);
+    QObject::connect(rcpm, &QXmppMessageReceiptManager::messageDelivered, mh, &MessageHandler::onReceiptReceived);
     
     
     QString path(QStandardPaths::writableLocation(QStandardPaths::CacheLocation));
@@ -156,8 +156,8 @@ Account::Account(const QString& p_login, const QString& p_server, const QString&
 
 Account::~Account()
 {
-    QObject::disconnect(network, &NetworkAccess::uploadFileComplete, this, &Account::onFileUploaded);
-    QObject::disconnect(network, &NetworkAccess::uploadFileError, this, &Account::onFileUploadError);
+    QObject::disconnect(network, &NetworkAccess::uploadFileComplete, mh, &MessageHandler::onFileUploaded);
+    QObject::disconnect(network, &NetworkAccess::uploadFileError, mh, &MessageHandler::onFileUploadError);
     
     for (std::map<QString, Contact*>::const_iterator itr = contacts.begin(), end = contacts.end(); itr != end; ++itr) {
         delete itr->second;
@@ -566,290 +566,19 @@ void Core::Account::setResource(const QString& p_resource)
     config.setResource(p_resource);
 }
 
-void Core::Account::onMessageReceived(const QXmppMessage& msg)
-{
-    bool handled = false;
-    switch (msg.type()) {
-        case QXmppMessage::Normal:
-            qDebug() << "received a message with type \"Normal\", not sure what to do with it now, skipping";
-            break;
-        case QXmppMessage::Chat:
-            handled = handleChatMessage(msg);
-            break;
-        case QXmppMessage::GroupChat:
-            handled = handleGroupMessage(msg);
-            break;
-        case QXmppMessage::Error: {
-                QString id = msg.id();
-                std::map<QString, QString>::const_iterator itr = pendingStateMessages.find(id);
-                if (itr != pendingStateMessages.end()) {
-                    QString jid = itr->second;
-                    RosterItem* cnt = getRosterItem(jid);
-                    QMap<QString, QVariant> cData = {
-                        {"state", static_cast<uint>(Shared::Message::State::error)},
-                        {"errorText", msg.error().text()}
-                    };
-                    if (cnt != 0) {
-                        cnt->changeMessage(id, cData);
-                    }
-                    ;
-                    emit changeMessage(jid, id, cData);
-                    pendingStateMessages.erase(itr);
-                    handled = true;
-                } else {
-                    qDebug() << "received a message with type \"Error\", not sure what to do with it now, skipping";
-                }
-            }
-            break;
-        case QXmppMessage::Headline:
-            qDebug() << "received a message with type \"Headline\", not sure what to do with it now, skipping";
-            break;
-    }
-    if (!handled) {
-        logMessage(msg);
-    }
-}
-
-void Core::Account::logMessage(const QXmppMessage& msg, const QString& reason)
-{
-    qDebug() << reason;
-        qDebug() << "- from: " << msg.from();
-        qDebug() << "- to: " << msg.to();
-        qDebug() << "- body: " << msg.body();
-        qDebug() << "- type: " << msg.type();
-        qDebug() << "- state: " << msg.state();
-        qDebug() << "- stamp: " << msg.stamp();
-        qDebug() << "- id: " << msg.id();
-        qDebug() << "- outOfBandUrl: " << msg.outOfBandUrl();
-        qDebug() << "- isAttentionRequested: " << msg.isAttentionRequested();
-        qDebug() << "- isReceiptRequested: " << msg.isReceiptRequested();
-        qDebug() << "- receiptId: " << msg.receiptId();
-        qDebug() << "- subject: " << msg.subject();
-        qDebug() << "- thread: " << msg.thread();
-        qDebug() << "- isMarkable: " << msg.isMarkable();
-        qDebug() << "==============================";
-}
-
-
 QString Core::Account::getFullJid() const
 {
     return getLogin() + "@" + getServer() + "/" + getResource();
 }
 
-void Core::Account::sendMessage(Shared::Message data)
+void Core::Account::sendMessage(const Shared::Message& data)
 {
-    QString jid = data.getPenPalJid();
-    QString id = data.getId();
-    RosterItem* ri = getRosterItem(jid);
-    if (state == Shared::ConnectionState::connected) {
-        QXmppMessage msg(getFullJid(), data.getTo(), data.getBody(), data.getThread());
-        msg.setId(id);
-        msg.setType(static_cast<QXmppMessage::Type>(data.getType()));       //it is safe here, my type is compatible
-        msg.setOutOfBandUrl(data.getOutOfBandUrl());
-        msg.setReceiptRequested(true);
-        
-        bool sent = client.sendPacket(msg);
-        
-        if (sent) {
-            data.setState(Shared::Message::State::sent);
-        } else {
-            data.setState(Shared::Message::State::error);
-            data.setErrorText("Couldn't send message via QXMPP library check out logs");
-        }
-        
-        if (ri != 0) {
-            ri->appendMessageToArchive(data);
-            if (sent) {
-                pendingStateMessages.insert(std::make_pair(id, jid));
-            }
-        }
-        
-    } else {
-        data.setState(Shared::Message::State::error);
-        data.setErrorText("You are is offline or reconnecting");
-    }
-    
-    emit changeMessage(jid, id, {
-        {"state", static_cast<uint>(data.getState())},
-        {"errorText", data.getErrorText()}
-    });
+    mh->sendMessage(data);
 }
 
 void Core::Account::sendMessage(const Shared::Message& data, const QString& path)
 {
-    if (state == Shared::ConnectionState::connected) {
-        QString url = network->getFileRemoteUrl(path);
-        if (url.size() != 0) {
-            sendMessageWithLocalUploadedFile(data, url);
-        } else {
-            if (network->isUploading(path, data.getId())) {
-                pendingMessages.emplace(data.getId(), data);
-            } else {
-                if (um->serviceFound()) {
-                    QFileInfo file(path);
-                    if (file.exists() && file.isReadable()) {
-                        uploadingSlotsQueue.emplace_back(path, data);
-                        if (uploadingSlotsQueue.size() == 1) {
-                            um->requestUploadSlot(file);
-                        }
-                    } else {
-                        emit onFileUploadError(data.getId(), "Uploading file no longer exists or your system user has no permission to read it");
-                        qDebug() << "Requested upload slot in account" << name << "for file" << path << "but the file doesn't exist or is not readable";
-                    }
-                } else {
-                    emit onFileUploadError(data.getId(), "Your server doesn't support file upload service, or it's prohibited for your account");
-                    qDebug() << "Requested upload slot in account" << name << "for file" << path << "but upload manager didn't discover any upload services";
-                }
-            }
-        }
-    } else {
-        emit onFileUploadError(data.getId(), "Account is offline or reconnecting");
-        qDebug() << "An attempt to send message with not connected account " << name << ", skipping";
-    }
-}
-
-void Core::Account::sendMessageWithLocalUploadedFile(Shared::Message msg, const QString& url)
-{
-    msg.setOutOfBandUrl(url);
-    if (msg.getBody().size() == 0) {
-        msg.setBody(url);
-    }
-    sendMessage(msg);
-    //TODO removal/progress update
-}
-
-
-void Core::Account::onCarbonMessageReceived(const QXmppMessage& msg)
-{
-    handleChatMessage(msg, false, true);
-}
-
-void Core::Account::onCarbonMessageSent(const QXmppMessage& msg)
-{
-    handleChatMessage(msg, true, true);
-}
-
-bool Core::Account::handleChatMessage(const QXmppMessage& msg, bool outgoing, bool forwarded, bool guessing)
-{
-    const QString& body(msg.body());
-    if (body.size() != 0) {
-        Shared::Message sMsg(Shared::Message::chat);
-        initializeMessage(sMsg, msg, outgoing, forwarded, guessing);
-        QString jid = sMsg.getPenPalJid();
-        std::map<QString, Contact*>::const_iterator itr = contacts.find(jid);
-        Contact* cnt;
-        if (itr != contacts.end()) {
-            cnt = itr->second;
-        } else {
-            cnt = new Contact(jid, name);
-            contacts.insert(std::make_pair(jid, cnt));
-            outOfRosterContacts.insert(jid);
-            cnt->setSubscriptionState(Shared::SubscriptionState::unknown);
-            emit addContact(jid, "", QMap<QString, QVariant>({
-                {"state", QVariant::fromValue(Shared::SubscriptionState::unknown)}
-            }));
-            handleNewContact(cnt);
-        }
-        if (outgoing) {
-            if (forwarded) {
-                sMsg.setState(Shared::Message::State::sent);
-            }
-        } else {
-            sMsg.setState(Shared::Message::State::delivered);
-        }
-        QString oId = msg.replaceId();
-        if (oId.size() > 0) {
-            QMap<QString, QVariant> cData = {
-                {"body", sMsg.getBody()},
-                {"stamp", sMsg.getTime()}
-            };
-            cnt->correctMessageInArchive(oId, sMsg);
-            emit changeMessage(jid, oId, cData);
-        } else {
-            cnt->appendMessageToArchive(sMsg);
-            emit message(sMsg);
-        }
-        
-        return true;
-    }
-    return false;
-}
-
-bool Core::Account::handleGroupMessage(const QXmppMessage& msg, bool outgoing, bool forwarded, bool guessing)
-{
-    const QString& body(msg.body());
-    if (body.size() != 0) {
-        const QString& id(msg.id());
-        Shared::Message sMsg(Shared::Message::groupChat);
-        initializeMessage(sMsg, msg, outgoing, forwarded, guessing);
-        QString jid = sMsg.getPenPalJid();
-        std::map<QString, Conference*>::const_iterator itr = conferences.find(jid);
-        Conference* cnt;
-        if (itr != conferences.end()) {
-            cnt = itr->second;
-        } else {
-            return false;
-        }
-        
-        std::map<QString, QString>::const_iterator pItr = pendingStateMessages.find(id);
-        if (pItr != pendingStateMessages.end()) {
-            QMap<QString, QVariant> cData = {{"state", static_cast<uint>(Shared::Message::State::delivered)}};
-            cnt->changeMessage(id, cData);
-            pendingStateMessages.erase(pItr);
-            emit changeMessage(jid, id, cData);
-        } else {
-            QString oId = msg.replaceId();
-            if (oId.size() > 0) {
-                QMap<QString, QVariant> cData = {
-                    {"body", sMsg.getBody()},
-                    {"stamp", sMsg.getTime()}
-                };
-                cnt->correctMessageInArchive(oId, sMsg);
-                emit changeMessage(jid, oId, cData);
-            } else {
-                cnt->appendMessageToArchive(sMsg);
-                QDateTime minAgo = QDateTime::currentDateTimeUtc().addSecs(-60);
-                if (sMsg.getTime() > minAgo) {     //otherwise it's considered a delayed delivery, most probably MUC history receipt
-                    emit message(sMsg);
-                } else {
-                    //qDebug() << "Delayed delivery: ";
-                }
-            }
-        }
-        
-        return true;
-    }
-    return false;
-}
-
-
-void Core::Account::initializeMessage(Shared::Message& target, const QXmppMessage& source, bool outgoing, bool forwarded, bool guessing) const
-{
-    const QDateTime& time(source.stamp());
-    QString id = source.id();
-    if (id.size() == 0) {
-        target.generateRandomId();
-    } else {
-        target.setId(id);
-    }
-    target.setFrom(source.from());
-    target.setTo(source.to());
-    target.setBody(source.body());
-    target.setForwarded(forwarded);
-    target.setOutOfBandUrl(source.outOfBandUrl());
-    if (guessing) {
-        if (target.getFromJid() == getLogin() + "@" + getServer()) {
-            outgoing = true;
-        } else {
-            outgoing = false;
-        }
-    }
-    target.setOutgoing(outgoing);
-    if (time.isValid()) {
-        target.setTime(time);
-    } else {
-        target.setCurrentTime();
-    }
+    mh->sendMessage(data, path);
 }
 
 void Core::Account::onMamMessageReceived(const QString& queryId, const QXmppMessage& msg)
@@ -860,8 +589,16 @@ void Core::Account::onMamMessageReceived(const QString& queryId, const QXmppMess
             QString jid = itr->second;
             RosterItem* item = getRosterItem(jid);
             
+            qDebug() << "archive for" << jid;
+            qDebug() << "id:" << msg.id();
+            qDebug() << "oid:" << msg.originId();
+            qDebug() << "sid:" << msg.stanzaId();
+            qDebug() << "rid:" << msg.replaceId();
+            qDebug() << "============================";
+            
+            
             Shared::Message sMsg(static_cast<Shared::Message::Type>(msg.type()));
-            initializeMessage(sMsg, msg, false, true, true);
+            mh->initializeMessage(sMsg, msg, false, true, true);
             sMsg.setState(Shared::Message::State::sent);
             
             QString oId = msg.replaceId();
@@ -892,7 +629,6 @@ Core::RosterItem * Core::Account::getRosterItem(const QString& jid)
     return item;
 }
 
-
 void Core::Account::requestArchive(const QString& jid, int count, const QString& before)
 {
     qDebug() << "An archive request for " << jid << ", before " << before;
@@ -1671,57 +1407,6 @@ void Core::Account::uploadVCard(const Shared::VCard& card)
     onOwnVCardReceived(iq);
 }
 
-void Core::Account::onUploadSlotReceived(const QXmppHttpUploadSlotIq& slot)
-{
-    if (uploadingSlotsQueue.size() == 0) {
-        qDebug() << "HTTP Upload manager of account" << name << "reports about success requesting upload slot, but none was requested";
-    } else {
-        const std::pair<QString, Shared::Message>& pair = uploadingSlotsQueue.front();
-        const QString& mId = pair.second.getId();
-        network->uploadFile(mId, pair.first, slot.putUrl(), slot.getUrl(), slot.putHeaders());
-        pendingMessages.emplace(mId, pair.second);
-        uploadingSlotsQueue.pop_front();
-        
-        if (uploadingSlotsQueue.size() > 0) {
-            um->requestUploadSlot(uploadingSlotsQueue.front().first);
-        }
-    }
-}
-
-void Core::Account::onUploadSlotRequestFailed(const QXmppHttpUploadRequestIq& request)
-{
-    if (uploadingSlotsQueue.size() == 0) {
-        qDebug() << "HTTP Upload manager of account" << name << "reports about an error requesting upload slot, but none was requested";
-        qDebug() << request.error().text();
-    } else {
-        const std::pair<QString, Shared::Message>& pair = uploadingSlotsQueue.front();
-        qDebug() << "Error requesting upload slot for file" << pair.first << "in account" << name << ":" << request.error().text();
-        emit uploadFileError(pair.second.getId(), "Error requesting slot to upload file: " + request.error().text());
-        
-        if (uploadingSlotsQueue.size() > 0) {
-            um->requestUploadSlot(uploadingSlotsQueue.front().first);
-        }
-        uploadingSlotsQueue.pop_front();
-    }
-}
-
-void Core::Account::onFileUploaded(const QString& messageId, const QString& url)
-{
-    std::map<QString, Shared::Message>::const_iterator itr = pendingMessages.find(messageId);
-    if (itr != pendingMessages.end()) {
-        sendMessageWithLocalUploadedFile(itr->second, url);
-        pendingMessages.erase(itr);
-    }
-}
-
-void Core::Account::onFileUploadError(const QString& messageId, const QString& errMsg)
-{
-    std::map<QString, Shared::Message>::const_iterator itr = pendingMessages.find(messageId);
-    if (itr != pendingMessages.end()) {
-        pendingMessages.erase(itr);
-    }
-}
-
 void Core::Account::onDiscoveryItemsReceived(const QXmppDiscoveryIq& items)
 {
     for (QXmppDiscoveryIq::Item item : items.items()) {
@@ -1740,26 +1425,12 @@ void Core::Account::onDiscoveryInfoReceived(const QXmppDiscoveryIq& info)
     }
 }
 
-void Core::Account::onReceiptReceived(const QString& jid, const QString& id)
-{
-    std::map<QString, QString>::const_iterator itr = pendingStateMessages.find(id);
-    if (itr != pendingStateMessages.end()) {
-        QMap<QString, QVariant> cData = {{"state", static_cast<uint>(Shared::Message::State::delivered)}};
-        RosterItem* ri = getRosterItem(itr->second);
-        if (ri != 0) {
-            ri->changeMessage(id, cData);
-        }
-        pendingStateMessages.erase(itr);
-        emit changeMessage(itr->second, id, cData);
-    }
-}
-
 void Core::Account::cancelHistoryRequests()
 {
-    for (const std::pair<QString, Conference*>& pair : conferences) {
+    for (const std::pair<const QString, Conference*>& pair : conferences) {
         pair.second->clearArchiveRequests();
     }
-    for (const std::pair<QString, Contact*>& pair : contacts) {
+    for (const std::pair<const QString, Contact*>& pair : contacts) {
         pair.second->clearArchiveRequests();
     }
     achiveQueries.clear();
diff --git a/core/account.h b/core/account.h
index b706916..b0d5ee0 100644
--- a/core/account.h
+++ b/core/account.h
@@ -38,7 +38,6 @@
 #include <QXmppBookmarkManager.h>
 #include <QXmppBookmarkSet.h>
 #include <QXmppUploadRequestManager.h>
-#include <QXmppHttpUploadIq.h>
 #include <QXmppVCardIq.h>
 #include <QXmppVCardManager.h>
 #include <QXmppMessageReceiptManager.h>
@@ -48,12 +47,15 @@
 #include "conference.h"
 #include "networkaccess.h"
 
+#include "handlers/messagehandler.h"
+
 namespace Core
 {
 
 class Account : public QObject
 {
     Q_OBJECT
+    friend class MessageHandler;
 public:
     Account(
         const QString& p_login, 
@@ -86,7 +88,7 @@ public:
     void setAvailability(Shared::Availability avail);
     void setPasswordType(Shared::AccountPassword pt);
     QString getFullJid() const;
-    void sendMessage(Shared::Message data);
+    void sendMessage(const Shared::Message& data);
     void sendMessage(const Shared::Message& data, const QString& path);
     void requestArchive(const QString& jid, int count, const QString& before);
     void setReconnectTimes(unsigned int times);
@@ -165,9 +167,10 @@ private:
     QString avatarType;
     bool ownVCardRequestInProgress;
     NetworkAccess* network;
-    std::map<QString, QString> pendingStateMessages;
     Shared::AccountPassword passwordType;
     
+    MessageHandler* mh;
+    
 private slots:
     void onClientConnected();
     void onClientDisconnected();
@@ -180,10 +183,6 @@ private slots:
     void onRosterPresenceChanged(const QString& bareJid, const QString& resource);
     
     void onPresenceReceived(const QXmppPresence& presence);
-    void onMessageReceived(const QXmppMessage& message);
-    
-    void onCarbonMessageReceived(const QXmppMessage& message);
-    void onCarbonMessageSent(const QXmppMessage& message);
     
     void onMamMessageReceived(const QString& bareJid, const QXmppMessage& message);
     void onMamResultsReceived(const QString &queryId, const QXmppResultSetReply &resultSetReply, bool complete);
@@ -211,15 +210,9 @@ private slots:
     
     void onVCardReceived(const QXmppVCardIq& card);
     void onOwnVCardReceived(const QXmppVCardIq& card);
-    
-    void onUploadSlotReceived(const QXmppHttpUploadSlotIq& slot);
-    void onUploadSlotRequestFailed(const QXmppHttpUploadRequestIq& request);
-    void onFileUploaded(const QString& messageId, const QString& url);
-    void onFileUploadError(const QString& messageId, const QString& errMsg);
+
     void onDiscoveryItemsReceived (const QXmppDiscoveryIq& items);
     void onDiscoveryInfoReceived (const QXmppDiscoveryIq& info);
-    
-    void onReceiptReceived(const QString& jid, const QString &id);
   
 private:
     void addedAccount(const QString &bareJid);
@@ -231,13 +224,10 @@ private:
     void addNewRoom(const QString& jid, const QString& nick, const QString& roomName, bool autoJoin);
     void addToGroup(const QString& jid, const QString& group);
     void removeFromGroup(const QString& jid, const QString& group);
-    void initializeMessage(Shared::Message& target, const QXmppMessage& source, bool outgoing = false, bool forwarded = false, bool guessing = false) const;
     Shared::SubscriptionState castSubscriptionState(QXmppRosterIq::Item::SubscriptionType qs) const;
-    void logMessage(const QXmppMessage& msg, const QString& reason = "Message wasn't handled: ");
     void storeConferences();
     void clearConferences();
     void cancelHistoryRequests();
-    void sendMessageWithLocalUploadedFile(Shared::Message msg, const QString& url);
     RosterItem* getRosterItem(const QString& jid);
 };
 
diff --git a/core/handlers/messagehandler.cpp b/core/handlers/messagehandler.cpp
new file mode 100644
index 0000000..aa02e78
--- /dev/null
+++ b/core/handlers/messagehandler.cpp
@@ -0,0 +1,390 @@
+/*
+ * Squawk messenger. 
+ * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+ * 
+ * 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 "messagehandler.h"
+#include "core/account.h"
+
+Core::MessageHandler::MessageHandler(Core::Account* account):
+    QObject(),
+    acc(account),
+    pendingStateMessages(),
+    pendingMessages(),
+    uploadingSlotsQueue()
+{
+}
+
+void Core::MessageHandler::onMessageReceived(const QXmppMessage& msg)
+{
+    bool handled = false;
+    switch (msg.type()) {
+        case QXmppMessage::Normal:
+            qDebug() << "received a message with type \"Normal\", not sure what to do with it now, skipping";
+            break;
+        case QXmppMessage::Chat:
+            handled = handleChatMessage(msg);
+            break;
+        case QXmppMessage::GroupChat:
+            handled = handleGroupMessage(msg);
+            break;
+        case QXmppMessage::Error: {
+            QString id = msg.id();
+            std::map<QString, QString>::const_iterator itr = pendingStateMessages.find(id);
+            if (itr != pendingStateMessages.end()) {
+                QString jid = itr->second;
+                RosterItem* cnt = acc->getRosterItem(jid);
+                QMap<QString, QVariant> cData = {
+                    {"state", static_cast<uint>(Shared::Message::State::error)},
+                    {"errorText", msg.error().text()}
+                };
+                if (cnt != 0) {
+                    cnt->changeMessage(id, cData);
+                }
+                ;
+                emit acc->changeMessage(jid, id, cData);
+                pendingStateMessages.erase(itr);
+                handled = true;
+            } else {
+                qDebug() << "received a message with type \"Error\", not sure what to do with it now, skipping";
+            }
+        }
+        break;
+        case QXmppMessage::Headline:
+            qDebug() << "received a message with type \"Headline\", not sure what to do with it now, skipping";
+            break;
+    }
+    if (!handled) {
+        logMessage(msg);
+    }
+}
+
+bool Core::MessageHandler::handleChatMessage(const QXmppMessage& msg, bool outgoing, bool forwarded, bool guessing)
+{
+    const QString& body(msg.body());
+    if (body.size() != 0) {
+        Shared::Message sMsg(Shared::Message::chat);
+        initializeMessage(sMsg, msg, outgoing, forwarded, guessing);
+        QString jid = sMsg.getPenPalJid();
+        std::map<QString, Contact*>::const_iterator itr = acc->contacts.find(jid);
+        Contact* cnt;
+        if (itr != acc->contacts.end()) {
+            cnt = itr->second;
+        } else {
+            cnt = new Contact(jid, acc->name);
+            acc->contacts.insert(std::make_pair(jid, cnt));
+            acc->outOfRosterContacts.insert(jid);
+            cnt->setSubscriptionState(Shared::SubscriptionState::unknown);
+            emit acc->addContact(jid, "", QMap<QString, QVariant>({
+                {"state", QVariant::fromValue(Shared::SubscriptionState::unknown)}
+            }));
+            acc->handleNewContact(cnt);
+        }
+        if (outgoing) {
+            if (forwarded) {
+                sMsg.setState(Shared::Message::State::sent);
+            }
+        } else {
+            sMsg.setState(Shared::Message::State::delivered);
+        }
+        QString oId = msg.replaceId();
+        if (oId.size() > 0) {
+            QMap<QString, QVariant> cData = {
+                {"body", sMsg.getBody()},
+                {"stamp", sMsg.getTime()}
+            };
+            cnt->correctMessageInArchive(oId, sMsg);
+            emit acc->changeMessage(jid, oId, cData);
+        } else {
+            cnt->appendMessageToArchive(sMsg);
+            emit acc->message(sMsg);
+        }
+        
+        return true;
+    }
+    return false;
+}
+
+bool Core::MessageHandler::handleGroupMessage(const QXmppMessage& msg, bool outgoing, bool forwarded, bool guessing)
+{
+    const QString& body(msg.body());
+    if (body.size() != 0) {
+        QString id = msg.id();
+        
+        Shared::Message sMsg(Shared::Message::groupChat);
+        initializeMessage(sMsg, msg, outgoing, forwarded, guessing);
+        QString jid = sMsg.getPenPalJid();
+        std::map<QString, Conference*>::const_iterator itr = acc->conferences.find(jid);
+        Conference* cnt;
+        if (itr != acc->conferences.end()) {
+            cnt = itr->second;
+        } else {
+            return false;
+        }
+        
+        std::map<QString, QString>::const_iterator pItr = pendingStateMessages.find(id);
+        if (pItr != pendingStateMessages.end()) {
+            QMap<QString, QVariant> cData = {{"state", static_cast<uint>(Shared::Message::State::delivered)}};
+            cnt->changeMessage(id, cData);
+            pendingStateMessages.erase(pItr);
+            emit acc->changeMessage(jid, id, cData);
+        } else {
+            QString oId = msg.replaceId();
+            if (oId.size() > 0) {
+                QMap<QString, QVariant> cData = {
+                    {"body", sMsg.getBody()},
+                    {"stamp", sMsg.getTime()}
+                };
+                cnt->correctMessageInArchive(oId, sMsg);
+                emit acc->changeMessage(jid, oId, cData);
+            } else {
+                cnt->appendMessageToArchive(sMsg);
+                QDateTime minAgo = QDateTime::currentDateTimeUtc().addSecs(-60);
+                if (sMsg.getTime() > minAgo) {     //otherwise it's considered a delayed delivery, most probably MUC history receipt
+                    emit acc->message(sMsg);
+                } else {
+                    //qDebug() << "Delayed delivery: ";
+                }
+            }
+        }
+        
+        return true;
+    }
+    return false;
+}
+
+
+void Core::MessageHandler::initializeMessage(Shared::Message& target, const QXmppMessage& source, bool outgoing, bool forwarded, bool guessing) const
+{
+    const QDateTime& time(source.stamp());
+    QString id;
+#if (QXMPP_VERSION) >= QT_VERSION_CHECK(1, 3, 0)
+    id = source.originId();
+    if (id.size() == 0) {
+        id = source.id();
+    }
+    if (id.size() == 0) {
+        id = source.stanzaId();
+    }
+#else
+    id = source.id();
+#endif
+    if (id.size() == 0) {
+        target.generateRandomId();
+    } else {
+        target.setId(id);
+    }
+    target.setFrom(source.from());
+    target.setTo(source.to());
+    target.setBody(source.body());
+    target.setForwarded(forwarded);
+    target.setOutOfBandUrl(source.outOfBandUrl());
+    if (guessing) {
+        if (target.getFromJid() == acc->getLogin() + "@" + acc->getServer()) {
+            outgoing = true;
+        } else {
+            outgoing = false;
+        }
+    }
+    target.setOutgoing(outgoing);
+    if (time.isValid()) {
+        target.setTime(time);
+    } else {
+        target.setCurrentTime();
+    }
+}
+
+void Core::MessageHandler::logMessage(const QXmppMessage& msg, const QString& reason)
+{
+    qDebug() << reason;
+    qDebug() << "- from: " << msg.from();
+    qDebug() << "- to: " << msg.to();
+    qDebug() << "- body: " << msg.body();
+    qDebug() << "- type: " << msg.type();
+    qDebug() << "- state: " << msg.state();
+    qDebug() << "- stamp: " << msg.stamp();
+    qDebug() << "- id: " << msg.id();
+    qDebug() << "- outOfBandUrl: " << msg.outOfBandUrl();
+    qDebug() << "- isAttentionRequested: " << msg.isAttentionRequested();
+    qDebug() << "- isReceiptRequested: " << msg.isReceiptRequested();
+    qDebug() << "- receiptId: " << msg.receiptId();
+    qDebug() << "- subject: " << msg.subject();
+    qDebug() << "- thread: " << msg.thread();
+    qDebug() << "- isMarkable: " << msg.isMarkable();
+    qDebug() << "==============================";
+}
+
+void Core::MessageHandler::onCarbonMessageReceived(const QXmppMessage& msg)
+{
+    handleChatMessage(msg, false, true);
+}
+
+void Core::MessageHandler::onCarbonMessageSent(const QXmppMessage& msg)
+{
+    handleChatMessage(msg, true, true);
+}
+
+void Core::MessageHandler::onReceiptReceived(const QString& jid, const QString& id)
+{
+    std::map<QString, QString>::const_iterator itr = pendingStateMessages.find(id);
+    if (itr != pendingStateMessages.end()) {
+        QMap<QString, QVariant> cData = {{"state", static_cast<uint>(Shared::Message::State::delivered)}};
+        RosterItem* ri = acc->getRosterItem(itr->second);
+        if (ri != 0) {
+            ri->changeMessage(id, cData);
+        }
+        pendingStateMessages.erase(itr);
+        emit acc->changeMessage(itr->second, id, cData);
+    }
+}
+
+void Core::MessageHandler::sendMessage(Shared::Message data)
+{
+    QString jid = data.getPenPalJid();
+    QString id = data.getId();
+    RosterItem* ri = acc->getRosterItem(jid);
+    if (acc->state == Shared::ConnectionState::connected) {
+        QXmppMessage msg(acc->getFullJid(), data.getTo(), data.getBody(), data.getThread());
+        
+#if (QXMPP_VERSION) >= QT_VERSION_CHECK(1, 3, 0)
+        msg.setOriginId(id);
+#endif
+        msg.setId(id);
+        msg.setType(static_cast<QXmppMessage::Type>(data.getType()));       //it is safe here, my type is compatible
+        msg.setOutOfBandUrl(data.getOutOfBandUrl());
+        msg.setReceiptRequested(true);
+        
+        bool sent = acc->client.sendPacket(msg);
+        
+        if (sent) {
+            data.setState(Shared::Message::State::sent);
+        } else {
+            data.setState(Shared::Message::State::error);
+            data.setErrorText("Couldn't send message via QXMPP library check out logs");
+        }
+        
+        if (ri != 0) {
+            ri->appendMessageToArchive(data);
+            if (sent) {
+                pendingStateMessages.insert(std::make_pair(id, jid));
+            }
+        }
+        
+    } else {
+        data.setState(Shared::Message::State::error);
+        data.setErrorText("You are is offline or reconnecting");
+    }
+    
+    emit acc->changeMessage(jid, id, {
+        {"state", static_cast<uint>(data.getState())},
+        {"errorText", data.getErrorText()}
+    });
+}
+
+void Core::MessageHandler::sendMessage(const Shared::Message& data, const QString& path)
+{
+    if (acc->state == Shared::ConnectionState::connected) {
+        QString url = acc->network->getFileRemoteUrl(path);
+        if (url.size() != 0) {
+            sendMessageWithLocalUploadedFile(data, url);
+        } else {
+            if (acc->network->isUploading(path, data.getId())) {
+                pendingMessages.emplace(data.getId(), data);
+            } else {
+                if (acc->um->serviceFound()) {
+                    QFileInfo file(path);
+                    if (file.exists() && file.isReadable()) {
+                        uploadingSlotsQueue.emplace_back(path, data);
+                        if (uploadingSlotsQueue.size() == 1) {
+                            acc->um->requestUploadSlot(file);
+                        }
+                    } else {
+                        onFileUploadError(data.getId(), "Uploading file no longer exists or your system user has no permission to read it");
+                        qDebug() << "Requested upload slot in account" << acc->name << "for file" << path << "but the file doesn't exist or is not readable";
+                    }
+                } else {
+                    onFileUploadError(data.getId(), "Your server doesn't support file upload service, or it's prohibited for your account");
+                    qDebug() << "Requested upload slot in account" << acc->name << "for file" << path << "but upload manager didn't discover any upload services";
+                }
+            }
+        }
+    } else {
+        onFileUploadError(data.getId(), "Account is offline or reconnecting");
+        qDebug() << "An attempt to send message with not connected account " << acc->name << ", skipping";
+    }
+}
+
+
+void Core::MessageHandler::onUploadSlotReceived(const QXmppHttpUploadSlotIq& slot)
+{
+    if (uploadingSlotsQueue.size() == 0) {
+        qDebug() << "HTTP Upload manager of account" << acc->name << "reports about success requesting upload slot, but none was requested";
+    } else {
+        const std::pair<QString, Shared::Message>& pair = uploadingSlotsQueue.front();
+        const QString& mId = pair.second.getId();
+        acc->network->uploadFile(mId, pair.first, slot.putUrl(), slot.getUrl(), slot.putHeaders());
+        pendingMessages.emplace(mId, pair.second);
+        uploadingSlotsQueue.pop_front();
+        
+        if (uploadingSlotsQueue.size() > 0) {
+            acc->um->requestUploadSlot(uploadingSlotsQueue.front().first);
+        }
+    }
+}
+
+void Core::MessageHandler::onUploadSlotRequestFailed(const QXmppHttpUploadRequestIq& request)
+{
+    if (uploadingSlotsQueue.size() == 0) {
+        qDebug() << "HTTP Upload manager of account" << acc->name << "reports about an error requesting upload slot, but none was requested";
+        qDebug() << request.error().text();
+    } else {
+        const std::pair<QString, Shared::Message>& pair = uploadingSlotsQueue.front();
+        qDebug() << "Error requesting upload slot for file" << pair.first << "in account" << acc->name << ":" << request.error().text();
+        emit acc->uploadFileError(pair.second.getId(), "Error requesting slot to upload file: " + request.error().text());
+        
+        if (uploadingSlotsQueue.size() > 0) {
+            acc->um->requestUploadSlot(uploadingSlotsQueue.front().first);
+        }
+        uploadingSlotsQueue.pop_front();
+    }
+}
+
+void Core::MessageHandler::onFileUploaded(const QString& messageId, const QString& url)
+{
+    std::map<QString, Shared::Message>::const_iterator itr = pendingMessages.find(messageId);
+    if (itr != pendingMessages.end()) {
+        sendMessageWithLocalUploadedFile(itr->second, url);
+        pendingMessages.erase(itr);
+    }
+}
+
+void Core::MessageHandler::onFileUploadError(const QString& messageId, const QString& errMsg)
+{
+    std::map<QString, Shared::Message>::const_iterator itr = pendingMessages.find(messageId);
+    if (itr != pendingMessages.end()) {
+        pendingMessages.erase(itr);
+    }
+}
+
+void Core::MessageHandler::sendMessageWithLocalUploadedFile(Shared::Message msg, const QString& url)
+{
+    msg.setOutOfBandUrl(url);
+    if (msg.getBody().size() == 0) {
+        msg.setBody(url);
+    }
+    sendMessage(msg);
+    //TODO removal/progress update
+}
diff --git a/core/handlers/messagehandler.h b/core/handlers/messagehandler.h
new file mode 100644
index 0000000..54b8331
--- /dev/null
+++ b/core/handlers/messagehandler.h
@@ -0,0 +1,76 @@
+/*
+ * Squawk messenger. 
+ * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+ * 
+ * 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/>.
+ */
+
+#ifndef CORE_MESSAGEHANDLER_H
+#define CORE_MESSAGEHANDLER_H
+
+#include <QObject>
+
+#include <deque>
+#include <map>
+
+#include <QXmppMessage.h>
+#include <QXmppHttpUploadIq.h>
+
+#include "shared/message.h"
+
+namespace Core {
+
+/**
+ * @todo write docs
+ */
+
+class Account;
+
+class MessageHandler : public QObject
+{
+    Q_OBJECT
+public:
+    MessageHandler(Account* account);
+    
+public:
+    void sendMessage(Shared::Message data);
+    void sendMessage(const Shared::Message& data, const QString& path);
+    void initializeMessage(Shared::Message& target, const QXmppMessage& source, bool outgoing = false, bool forwarded = false, bool guessing = false) const;
+    
+public slots:
+    void onMessageReceived(const QXmppMessage& message);
+    void onCarbonMessageReceived(const QXmppMessage& message);
+    void onCarbonMessageSent(const QXmppMessage& message);
+    void onReceiptReceived(const QString& jid, const QString& id);    
+    void onUploadSlotReceived(const QXmppHttpUploadSlotIq& slot);
+    void onUploadSlotRequestFailed(const QXmppHttpUploadRequestIq& request);
+    void onFileUploaded(const QString& messageId, const QString& url);
+    void onFileUploadError(const QString& messageId, const QString& errMsg);
+    
+private:
+    bool handleChatMessage(const QXmppMessage& msg, bool outgoing = false, bool forwarded = false, bool guessing = false);
+    bool handleGroupMessage(const QXmppMessage& msg, bool outgoing = false, bool forwarded = false, bool guessing = false);
+    void logMessage(const QXmppMessage& msg, const QString& reason = "Message wasn't handled: ");
+    void sendMessageWithLocalUploadedFile(Shared::Message msg, const QString& url);
+    
+private:
+    Account* acc;
+    std::map<QString, QString> pendingStateMessages;
+    std::map<QString, Shared::Message> pendingMessages;
+    std::deque<std::pair<QString, Shared::Message>> uploadingSlotsQueue;
+};
+
+}
+
+#endif // CORE_MESSAGEHANDLER_H

From 6b65910ded18b02022beabee60c0cf1b07af7655 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Thu, 21 May 2020 18:42:40 +0300
Subject: [PATCH 073/281] stanzaId based muc archive request, account object
 refactoring

---
 core/account.cpp                 | 178 +++++++++++++++----------------
 core/account.h                   |  18 ++--
 core/archive.cpp                 | 111 +++++++++++++++++--
 core/archive.h                   |  13 ++-
 core/handlers/messagehandler.cpp |  22 ++--
 core/rosteritem.cpp              |  64 +++++------
 core/rosteritem.h                |   2 +-
 core/squawk.cpp                  |   4 +-
 shared/message.cpp               |  41 ++++++-
 shared/message.h                 |   3 +
 10 files changed, 287 insertions(+), 169 deletions(-)

diff --git a/core/account.cpp b/core/account.cpp
index c9e08eb..0f654fa 100644
--- a/core/account.cpp
+++ b/core/account.cpp
@@ -19,7 +19,6 @@
 #include "account.h"
 #include <QXmppMessage.h>
 #include <QDateTime>
-#include <QTimer>
 
 using namespace Core;
 
@@ -43,8 +42,8 @@ Account::Account(const QString& p_login, const QString& p_server, const QString&
     rcpm(new QXmppMessageReceiptManager()),
     contacts(),
     conferences(),
-    maxReconnectTimes(0),
-    reconnectTimes(0),
+    reconnectScheduled(false),
+    reconnectTimer(new QTimer),
     queuedContacts(),
     outOfRosterContacts(),
     pendingMessages(),
@@ -62,8 +61,7 @@ Account::Account(const QString& p_login, const QString& p_server, const QString&
     config.setAutoAcceptSubscriptions(true);
     //config.setAutoReconnectionEnabled(false);
     
-    QObject::connect(&client, &QXmppClient::connected, this, &Account::onClientConnected);
-    QObject::connect(&client, &QXmppClient::disconnected, this, &Account::onClientDisconnected);
+    QObject::connect(&client, &QXmppClient::stateChanged, this, &Account::onClientStateChange);
     QObject::connect(&client, &QXmppClient::presenceReceived, this, &Account::onPresenceReceived);
     QObject::connect(&client, &QXmppClient::messageReceived, mh, &MessageHandler::onMessageReceived);
     QObject::connect(&client, &QXmppClient::error, this, &Account::onClientError);
@@ -152,10 +150,26 @@ Account::Account(const QString& p_login, const QString& p_server, const QString&
     } else {
         presence.setVCardUpdateType(QXmppPresence::VCardUpdateNotReady);
     }
+    
+    reconnectTimer->setSingleShot(true);
+    QObject::connect(reconnectTimer, &QTimer::timeout, this, &Account::connect);
+    
+//     QXmppLogger* logger = new QXmppLogger(this);
+//     logger->setLoggingType(QXmppLogger::SignalLogging);
+//     client.setLogger(logger);
+//     
+//     QObject::connect(logger, &QXmppLogger::message, this, [](QXmppLogger::MessageType type, const QString& text){
+//         qDebug() << text;
+//     });
 }
 
 Account::~Account()
 {
+    if (reconnectScheduled) {
+        reconnectScheduled = false;
+        reconnectTimer->stop();
+    }
+    
     QObject::disconnect(network, &NetworkAccess::uploadFileComplete, mh, &MessageHandler::onFileUploaded);
     QObject::disconnect(network, &NetworkAccess::uploadFileError, mh, &MessageHandler::onFileUploadError);
     
@@ -167,6 +181,7 @@ Account::~Account()
         delete itr->second;
     }
     
+    delete reconnectTimer;
     delete rcpm;
     delete dm;
     delete um;
@@ -184,10 +199,8 @@ Shared::ConnectionState Core::Account::getState() const
 void Core::Account::connect()
 {
     if (state == Shared::ConnectionState::disconnected) {
-        reconnectTimes = maxReconnectTimes;
-        state = Shared::ConnectionState::connecting;
+        qDebug() << presence.availableStatusType();
         client.connectToServer(config, presence);
-        emit connectionStateChanged(state);
     } else {
         qDebug("An attempt to connect an account which is already connected, skipping");
     }
@@ -195,54 +208,63 @@ void Core::Account::connect()
 
 void Core::Account::disconnect()
 {
-    reconnectTimes = 0;
+    if (reconnectScheduled) {
+        reconnectScheduled = false;
+        reconnectTimer->stop();
+    }
     if (state != Shared::ConnectionState::disconnected) {
         clearConferences();
         client.disconnectFromServer();
-        state = Shared::ConnectionState::disconnected;
-        emit connectionStateChanged(state);
     }
 }
 
-void Core::Account::onClientConnected()
+void Core::Account::onClientStateChange(QXmppClient::State st)
 {
-    if (state == Shared::ConnectionState::connecting) {
-        reconnectTimes = maxReconnectTimes;
-        state = Shared::ConnectionState::connected;
-        dm->requestItems(getServer());
-        dm->requestInfo(getServer());
-        emit connectionStateChanged(state);
-    } else {
-        qDebug() << "Something weird had happened - xmpp client reported about successful connection but account wasn't in" << state << "state";
-    }
-}
-
-void Core::Account::onClientDisconnected()
-{
-    cancelHistoryRequests();
-    pendingVCardRequests.clear();
-    clearConferences();
-    if (state != Shared::ConnectionState::disconnected) {
-        if (reconnectTimes > 0) {
-            qDebug() << "Account" << name << "is reconnecting for" << reconnectTimes << "more times";
-            --reconnectTimes;
-            state = Shared::ConnectionState::connecting;
-            client.connectToServer(config, presence);
-            emit connectionStateChanged(state);
-        } else {
-            qDebug() << "Account" << name << "has been disconnected";
-            state = Shared::ConnectionState::disconnected;
-            emit connectionStateChanged(state);
+    switch (st) {
+        case QXmppClient::ConnectedState: {
+            if (state != Shared::ConnectionState::connected) {
+                if (client.isActive()) {
+                    Shared::ConnectionState os = state;
+                    state = Shared::ConnectionState::connected;
+                    if (os == Shared::ConnectionState::connecting) {
+                        dm->requestItems(getServer());
+                        dm->requestInfo(getServer());
+                    }
+                    emit connectionStateChanged(state);
+                }
+            } else {
+                qDebug()    << "Something weird happened - xmpp client of account" << name
+                            << "reported about successful connection but account was in" << state << "state";
+            }
         }
-    } else {
-        //qDebug("Something weird had happened - xmpp client reported about being disconnection but account was already in disconnected state");
+            break;
+        case QXmppClient::ConnectingState: {
+            if (state != Shared::ConnectionState::connecting) {
+                state = Shared::ConnectionState::connecting;
+                emit connectionStateChanged(state);
+            }
+        }
+            break;
+        case QXmppClient::DisconnectedState: {
+            cancelHistoryRequests();
+            pendingVCardRequests.clear();
+            if (state != Shared::ConnectionState::disconnected) {
+                state = Shared::ConnectionState::disconnected;
+                emit connectionStateChanged(state);
+            } else {
+                qDebug()    << "Something weird happened - xmpp client of account" << name
+                            << "reported about disconnection but account was in" << state << "state";
+            }
+        }
+            break;
     }
 }
 
 void Core::Account::reconnect()
 {
-    if (state == Shared::ConnectionState::connected) {
-        ++reconnectTimes;
+    if (state == Shared::ConnectionState::connected && !reconnectScheduled) {
+        reconnectScheduled = true;
+        reconnectTimer->start(500);
         client.disconnectFromServer();
     } else {
         qDebug() << "An attempt to reconnect account" << getName() << "which was not connected";
@@ -286,14 +308,6 @@ void Core::Account::onRosterReceived()
     }
 }
 
-void Core::Account::setReconnectTimes(unsigned int times)
-{
-    maxReconnectTimes = times;
-    if (state == Shared::ConnectionState::connected) {
-        reconnectTimes = times;
-    }
-}
-
 void Core::Account::onRosterItemAdded(const QString& bareJid)
 {
     addedAccount(bareJid);
@@ -589,14 +603,6 @@ void Core::Account::onMamMessageReceived(const QString& queryId, const QXmppMess
             QString jid = itr->second;
             RosterItem* item = getRosterItem(jid);
             
-            qDebug() << "archive for" << jid;
-            qDebug() << "id:" << msg.id();
-            qDebug() << "oid:" << msg.originId();
-            qDebug() << "sid:" << msg.stanzaId();
-            qDebug() << "rid:" << msg.replaceId();
-            qDebug() << "============================";
-            
-            
             Shared::Message sMsg(static_cast<Shared::Message::Type>(msg.type()));
             mh->initializeMessage(sMsg, msg, false, true, true);
             sMsg.setState(Shared::Message::State::sent);
@@ -645,49 +651,35 @@ void Core::Account::requestArchive(const QString& jid, int count, const QString&
         emit responseArchive(contact->jid, std::list<Shared::Message>());
     }
     
-    if (contact->getArchiveState() == RosterItem::empty && before.size() == 0) {
-        QXmppMessage msg(getFullJid(), jid, "", "");
-        QString last = Shared::generateUUID();
-        msg.setId(last);
-        if (contact->isMuc()) {
-            msg.setType(QXmppMessage::GroupChat);
-        } else {
-            msg.setType(QXmppMessage::Chat);
-        }
-        msg.setState(QXmppMessage::Active);
-        client.sendPacket(msg);
-        QTimer* timer = new QTimer;
-        QObject::connect(timer, &QTimer::timeout, [timer, contact, count, last](){
-            contact->requestFromEmpty(count, last);
-            timer->deleteLater();
-        });
-        
-        timer->setSingleShot(true);
-        timer->start(1000);
-    } else {
-        contact->requestHistory(count, before);
-    }
+    contact->requestHistory(count, before);
 }
 
 void Core::Account::onContactNeedHistory(const QString& before, const QString& after, const QDateTime& at)
 {
     RosterItem* contact = static_cast<RosterItem*>(sender());
-    QString to = "";
+
+    QString to = contact->jid;
     QXmppResultSetQuery query;
-    query.setMax(100);
-    if (before.size() > 0) {
-        query.setBefore(before);
-    }
     QDateTime start;
-    if (after.size() > 0) {     //there is some strange behavior of ejabberd server returning empty result set
-        if (at.isValid()) {     //there can be some useful information about it here https://github.com/processone/ejabberd/issues/2924
-            start = at;
-        } else {
-            query.setAfter(after);
+    query.setMax(100);
+    
+    if (contact->getArchiveState() == RosterItem::empty) {
+        query.setBefore(before);
+        qDebug() << "Requesting remote history from empty for" << contact->jid;
+    } else {
+        if (before.size() > 0) {
+            query.setBefore(before);
         }
+        if (after.size() > 0) {     //there is some strange behavior of ejabberd server returning empty result set
+            if (at.isValid()) {     //there can be some useful information about it here https://github.com/processone/ejabberd/issues/2924
+                start = at;
+            } else {
+                query.setAfter(after);
+            }
+        }
+        qDebug() << "Remote query for" << contact->jid << "from" << after << ", to" << before;
     }
     
-    qDebug() << "Remote query from" << after << ", to" << before;
     
     QString q = am->retrieveArchivedMessages(to, "", contact->jid, start, QDateTime(), query);
     achiveQueries.insert(std::make_pair(q, contact->jid));
@@ -704,7 +696,7 @@ void Core::Account::onMamResultsReceived(const QString& queryId, const QXmppResu
         RosterItem* ri = getRosterItem(jid);
         
         if (ri != 0) {
-            qDebug() << "Flushing messages for" << jid;
+            qDebug() << "Flushing messages for" << jid << ", complete:" << complete;
             ri->flushMessagesToArchive(complete, resultSetReply.first(), resultSetReply.last());
         }
     }
diff --git a/core/account.h b/core/account.h
index b0d5ee0..d31d066 100644
--- a/core/account.h
+++ b/core/account.h
@@ -25,6 +25,7 @@
 #include <QMimeDatabase>
 #include <QStandardPaths>
 #include <QDir>
+#include <QTimer>
 
 #include <map>
 #include <set>
@@ -66,10 +67,6 @@ public:
         QObject* parent = 0);
     ~Account();
     
-    void connect();
-    void disconnect();
-    void reconnect();
-    
     Shared::ConnectionState getState() const;
     QString getName() const;
     QString getLogin() const;
@@ -91,7 +88,6 @@ public:
     void sendMessage(const Shared::Message& data);
     void sendMessage(const Shared::Message& data, const QString& path);
     void requestArchive(const QString& jid, int count, const QString& before);
-    void setReconnectTimes(unsigned int times);
     void subscribeToContact(const QString& jid, const QString& reason);
     void unsubscribeFromContact(const QString& jid, const QString& reason);
     void removeContactRequest(const QString& jid);
@@ -107,6 +103,9 @@ public:
     void uploadVCard(const Shared::VCard& card);
     
 public slots:
+    void connect();
+    void disconnect();
+    void reconnect();
     void requestVCard(const QString& jid);
     
 signals:
@@ -154,8 +153,8 @@ private:
     QXmppMessageReceiptManager* rcpm;
     std::map<QString, Contact*> contacts;
     std::map<QString, Conference*> conferences;
-    unsigned int maxReconnectTimes;
-    unsigned int reconnectTimes;
+    bool reconnectScheduled;
+    QTimer* reconnectTimer;
     
     std::map<QString, QString> queuedContacts;
     std::set<QString> outOfRosterContacts;
@@ -172,8 +171,7 @@ private:
     MessageHandler* mh;
     
 private slots:
-    void onClientConnected();
-    void onClientDisconnected();
+    void onClientStateChange(QXmppClient::State state);
     void onClientError(QXmppClient::Error err);
     
     void onRosterReceived();
@@ -219,8 +217,6 @@ private:
     void handleNewContact(Contact* contact);
     void handleNewRosterItem(RosterItem* contact);
     void handleNewConference(Conference* contact);
-    bool handleChatMessage(const QXmppMessage& msg, bool outgoing = false, bool forwarded = false, bool guessing = false);
-    bool handleGroupMessage(const QXmppMessage& msg, bool outgoing = false, bool forwarded = false, bool guessing = false);
     void addNewRoom(const QString& jid, const QString& nick, const QString& roomName, bool autoJoin);
     void addToGroup(const QString& jid, const QString& group);
     void removeFromGroup(const QString& jid, const QString& group);
diff --git a/core/archive.cpp b/core/archive.cpp
index 179f33e..628723d 100644
--- a/core/archive.cpp
+++ b/core/archive.cpp
@@ -67,6 +67,7 @@ void Core::Archive::open(const QString& account)
         mdb_dbi_open(txn, "order", MDB_CREATE | MDB_INTEGERKEY, &order);
         mdb_dbi_open(txn, "stats", MDB_CREATE, &stats);
         mdb_dbi_open(txn, "avatars", MDB_CREATE, &avatars);
+        mdb_dbi_open(txn, "sid", MDB_CREATE, &sid);
         mdb_txn_commit(txn);
         
         mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn);
@@ -99,6 +100,7 @@ void Core::Archive::open(const QString& account)
 void Core::Archive::close()
 {
     if (opened) {
+        mdb_dbi_close(environment, sid);
         mdb_dbi_close(environment, avatars);
         mdb_dbi_close(environment, stats);
         mdb_dbi_close(environment, order);
@@ -139,12 +141,36 @@ bool Core::Archive::addElement(const Shared::Message& message)
             mdb_txn_abort(txn);
             return false;
         } else {
-            rc = mdb_txn_commit(txn);
-            if (rc) {
-                qDebug() << "A transaction error: " << mdb_strerror(rc);
-                return false;
+            if (message.getStanzaId().size() > 0) {
+                const std::string& szid = message.getStanzaId().toStdString();
+                
+                lmdbKey.mv_size = szid.size();
+                lmdbKey.mv_data = (char*)szid.c_str();
+                lmdbData.mv_size = id.size();
+                lmdbData.mv_data = (uint8_t*)id.data();
+                rc = mdb_put(txn, sid, &lmdbKey, &lmdbData, MDB_NOOVERWRITE);
+                
+                if (rc) {
+                    qDebug() << "An element stanzaId to id pair couldn't be inserted into the archive" << mdb_strerror(rc);
+                    mdb_txn_abort(txn);
+                    return false;
+                } else {
+                    rc = mdb_txn_commit(txn);
+                    if (rc) {
+                        qDebug() << "A transaction error: " << mdb_strerror(rc);
+                        return false;
+                    }
+                    return true;
+                }
+                
+            } else {
+                rc = mdb_txn_commit(txn);
+                if (rc) {
+                    qDebug() << "A transaction error: " << mdb_strerror(rc);
+                    return false;
+                }
+                return true;
             }
-            return true;
         }
     } else {
         qDebug() << "An element couldn't been added to the archive, skipping" << mdb_strerror(rc);
@@ -164,10 +190,12 @@ void Core::Archive::clear()
     mdb_drop(txn, main, 0);
     mdb_drop(txn, order, 0);
     mdb_drop(txn, stats, 0);
+    mdb_drop(txn, avatars, 0);
+    mdb_drop(txn, sid, 0);
     mdb_txn_commit(txn);
 }
 
-Shared::Message Core::Archive::getElement(const QString& id)
+Shared::Message Core::Archive::getElement(const QString& id) const
 {
     if (!opened) {
         throw Closed("getElement", jid.toStdString());
@@ -186,7 +214,7 @@ Shared::Message Core::Archive::getElement(const QString& id)
     }
 }
 
-Shared::Message Core::Archive::getMessage(const std::string& id, MDB_txn* txn)
+Shared::Message Core::Archive::getMessage(const std::string& id, MDB_txn* txn) const
 {
     MDB_val lmdbKey, lmdbData;
     lmdbKey.mv_size = id.size();
@@ -220,6 +248,7 @@ void Core::Archive::changeMessage(const QString& id, const QMap<QString, QVarian
     std::string strId(id.toStdString());
     try {
         Shared::Message msg = getMessage(strId, txn);
+        bool hadStanzaId = msg.getStanzaId().size() > 0;
         QDateTime oTime = msg.getTime();
         bool idChange = msg.change(data);
         
@@ -250,6 +279,19 @@ void Core::Archive::changeMessage(const QString& id, const QMap<QString, QVarian
                 throw Unknown(jid.toStdString(), mdb_strerror(rc));
             }
         }
+        
+        if (msg.getStanzaId().size() > 0 && (idChange || !hadStanzaId)) {
+            const std::string& szid = msg.getStanzaId().toStdString();
+            
+            lmdbData.mv_size = szid.size();
+            lmdbData.mv_data = (char*)szid.c_str();
+            rc = mdb_put(txn, sid, &lmdbData, &lmdbKey, 0);
+            
+            if (rc != 0) {
+                throw Unknown(jid.toStdString(), mdb_strerror(rc));
+            }
+        };
+        
         lmdbData.mv_size = ba.size();
         lmdbData.mv_data = (uint8_t*)ba.data();
         rc = mdb_put(txn, main, &lmdbKey, &lmdbData, 0);
@@ -395,7 +437,20 @@ unsigned int Core::Archive::addElements(const std::list<Shared::Message>& messag
             if (rc) {
                 qDebug() << "An element couldn't be inserted into the index, aborting the transaction" << mdb_strerror(rc);
             } else {
-                //qDebug() << "element added with id" << message.getId() << "stamp" << message.getTime();
+                if (message.getStanzaId().size() > 0) {
+                    const std::string& szid = message.getStanzaId().toStdString();
+                    
+                    lmdbKey.mv_size = szid.size();
+                    lmdbKey.mv_data = (char*)szid.c_str();
+                    lmdbData.mv_size = id.size();
+                    lmdbData.mv_data = (uint8_t*)id.data();
+                    rc = mdb_put(txn, sid, &lmdbKey, &lmdbData, MDB_NOOVERWRITE);
+                    
+                    if (rc) {
+                        qDebug() << "During bulk add an element stanzaId to id pair couldn't be inserted into the archive, continuing without stanzaId" << mdb_strerror(rc);
+                    }
+                    
+                }
                 success++;
             }
         } else {
@@ -536,6 +591,46 @@ void Core::Archive::setFromTheBeginning(bool is)
     }
 }
 
+QString Core::Archive::idByStanzaId(const QString& stanzaId) const
+{
+    if (!opened) {
+        throw Closed("idByStanzaId", jid.toStdString());
+    }
+    QString id;
+    std::string ssid = stanzaId.toStdString();
+    
+    MDB_txn *txn;
+    MDB_val lmdbKey, lmdbData;
+    lmdbKey.mv_size = ssid.size();
+    lmdbKey.mv_data = (char*)ssid.c_str();
+    mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn);
+    int rc = mdb_get(txn, sid, &lmdbKey, &lmdbData);
+    if (rc == 0) {
+        id = QString::fromStdString(std::string((char*)lmdbData.mv_data, lmdbData.mv_size));
+    }
+    mdb_txn_abort(txn);
+    
+    return id;
+}
+
+QString Core::Archive::stanzaIdById(const QString& id) const
+{
+    if (!opened) {
+        throw Closed("stanzaIdById", jid.toStdString());
+    }
+    
+    try {
+        Shared::Message msg = getElement(id);
+        return msg.getStanzaId();
+    } catch (const NotFound& e) {
+        return QString();
+    } catch (const Empty& e) {
+        return QString();
+    } catch (...) {
+        throw;
+    }
+}
+
 void Core::Archive::printOrder()
 {
     qDebug() << "Printing order";
diff --git a/core/archive.h b/core/archive.h
index ef6ca23..b71a8be 100644
--- a/core/archive.h
+++ b/core/archive.h
@@ -45,7 +45,7 @@ public:
     
     bool addElement(const Shared::Message& message);
     unsigned int addElements(const std::list<Shared::Message>& messages);
-    Shared::Message getElement(const QString& id);
+    Shared::Message getElement(const QString& id) const;
     void changeMessage(const QString& id, const QMap<QString, QVariant>& data);
     Shared::Message oldest();
     QString oldestId();
@@ -60,6 +60,8 @@ public:
     AvatarInfo getAvatarInfo(const QString& resource = "") const;
     bool readAvatarInfo(AvatarInfo& target, const QString& resource = "") const;
     void readAllResourcesAvatars(std::map<QString, AvatarInfo>& data) const;
+    QString idByStanzaId(const QString& stanzaId) const;
+    QString stanzaIdById(const QString& id) const;
     
 public:
     const QString jid;
@@ -169,10 +171,11 @@ private:
     bool opened;
     bool fromTheBeginning;
     MDB_env* environment;
-    MDB_dbi main;
-    MDB_dbi order;
+    MDB_dbi main;           //id to message
+    MDB_dbi order;          //time to id
     MDB_dbi stats;
-    MDB_dbi avatars;
+    MDB_dbi avatars;        
+    MDB_dbi sid;            //stanzaId to id
     
     bool getStatBoolValue(const std::string& id, MDB_txn* txn);
     std::string getStatStringValue(const std::string& id, MDB_txn* txn);
@@ -183,7 +186,7 @@ private:
     void printOrder();
     void printKeys();
     bool dropAvatar(const std::string& resource);
-    Shared::Message getMessage(const std::string& id, MDB_txn* txn);
+    Shared::Message getMessage(const std::string& id, MDB_txn* txn) const;
     Shared::Message getStoredMessage(MDB_txn *txn, MDB_cursor* cursor, MDB_cursor_op op, MDB_val* key, MDB_val* value, int& rc);
     Shared::Message edge(bool end);
 };
diff --git a/core/handlers/messagehandler.cpp b/core/handlers/messagehandler.cpp
index aa02e78..ae06694 100644
--- a/core/handlers/messagehandler.cpp
+++ b/core/handlers/messagehandler.cpp
@@ -162,7 +162,7 @@ bool Core::MessageHandler::handleGroupMessage(const QXmppMessage& msg, bool outg
         }
         
         return true;
-    }
+    } 
     return false;
 }
 
@@ -176,16 +176,13 @@ void Core::MessageHandler::initializeMessage(Shared::Message& target, const QXmp
     if (id.size() == 0) {
         id = source.id();
     }
-    if (id.size() == 0) {
-        id = source.stanzaId();
-    }
+    target.setStanzaId(source.stanzaId());
 #else
     id = source.id();
 #endif
-    if (id.size() == 0) {
-        target.generateRandomId();
-    } else {
-        target.setId(id);
+    target.setId(id);
+    if (target.getId().size() == 0) {
+        target.generateRandomId();          //TODO out of desperation, I need at least a random ID
     }
     target.setFrom(source.from());
     target.setTo(source.to());
@@ -217,13 +214,10 @@ void Core::MessageHandler::logMessage(const QXmppMessage& msg, const QString& re
     qDebug() << "- state: " << msg.state();
     qDebug() << "- stamp: " << msg.stamp();
     qDebug() << "- id: " << msg.id();
+#if (QXMPP_VERSION) >= QT_VERSION_CHECK(1, 3, 0)
+    qDebug() << "- stanzaId: " << msg.stanzaId();
+#endif
     qDebug() << "- outOfBandUrl: " << msg.outOfBandUrl();
-    qDebug() << "- isAttentionRequested: " << msg.isAttentionRequested();
-    qDebug() << "- isReceiptRequested: " << msg.isReceiptRequested();
-    qDebug() << "- receiptId: " << msg.receiptId();
-    qDebug() << "- subject: " << msg.subject();
-    qDebug() << "- thread: " << msg.thread();
-    qDebug() << "- isMarkable: " << msg.isMarkable();
     qDebug() << "==============================";
 }
 
diff --git a/core/rosteritem.cpp b/core/rosteritem.cpp
index 8260eec..9163994 100644
--- a/core/rosteritem.cpp
+++ b/core/rosteritem.cpp
@@ -157,7 +157,7 @@ void Core::RosterItem::performRequest(int count, const QString& before)
                 requestedCount = -1;
             }
             Shared::Message msg = archive->newest();
-            emit needHistory("", msg.getId(), msg.getTime());
+            emit needHistory("", getId(msg), msg.getTime());
         }
             break;
         case end: 
@@ -176,27 +176,37 @@ void Core::RosterItem::performRequest(int count, const QString& before)
                 } catch (const Archive::NotFound& e) {
                     requestCache.emplace_back(requestedCount, before);
                     requestedCount = -1;
-                    emit needHistory(archive->oldestId(), "");
+                    emit needHistory(getId(archive->oldest()), "");
                 } catch (const Archive::Empty& e) {
                     requestCache.emplace_back(requestedCount, before);
                     requestedCount = -1;
-                    emit needHistory(archive->oldestId(), "");
+                    emit needHistory(getId(archive->oldest()), "");
                 }
                 
                 if (found) {
                     int rSize = responseCache.size();
                     if (rSize < count) {
                         if (rSize != 0) {
-                            emit needHistory(responseCache.front().getId(), "");
+                            emit needHistory(getId(responseCache.front()), "");
                         } else {
-                            emit needHistory(before, "");
+                            QString bf;
+                            if (muc) {
+                                bf = archive->stanzaIdById(before);
+                                if (bf.size() < 0) {
+                                    qDebug() << "Didn't find stanzaId for id requesting history for" << jid << ", falling back to requesting by id";
+                                    bf = before;
+                                }
+                            } else {
+                                bf = before;
+                            }
+                            emit needHistory(bf, "");
                         }
                     } else {
                         nextRequest();
                     }
                 }
             } else {
-                emit needHistory(archive->oldestId(), "");
+                emit needHistory(getId(archive->oldest()), "");
             }
             break;
         case complete:
@@ -213,10 +223,20 @@ void Core::RosterItem::performRequest(int count, const QString& before)
     }
 }
 
+QString Core::RosterItem::getId(const Shared::Message& msg)
+{
+    QString id;
+    if (muc) {
+        id = msg.getStanzaId();
+    } else {
+        id = msg.getId();
+    }
+    return id;
+}
+
 void Core::RosterItem::appendMessageToArchive(const Shared::Message& msg)
 {
-    const QString& id = msg.getId(); 
-    if (id.size() > 0) {
+    if (msg.getId().size() > 0) {
         if (msg.storable()) {
             switch (archiveState) {
                 case empty:
@@ -224,13 +244,13 @@ void Core::RosterItem::appendMessageToArchive(const Shared::Message& msg)
                         archiveState = end;
                     }
                     if (!syncronizing) {
-                        requestHistory(-1, id);
+                        requestHistory(-1, getId(msg));
                     }
                     break;
                 case beginning:
                     appendCache.push_back(msg);
                     if (!syncronizing) {
-                        requestHistory(-1, id);
+                        requestHistory(-1, getId(msg));
                     }
                     break;
                 case end:
@@ -239,7 +259,7 @@ void Core::RosterItem::appendMessageToArchive(const Shared::Message& msg)
                 case chunk:
                     appendCache.push_back(msg);
                     if (!syncronizing) {
-                        requestHistory(-1, id);
+                        requestHistory(-1, getId(msg));
                     }
                     break;
                 case complete:
@@ -247,7 +267,7 @@ void Core::RosterItem::appendMessageToArchive(const Shared::Message& msg)
                     break;
             }
         } else if (!syncronizing && archiveState == empty) {
-            requestHistory(-1, id);
+            requestHistory(-1, getId(msg));
         }
     }
 }
@@ -377,26 +397,6 @@ void Core::RosterItem::flushMessagesToArchive(bool finished, const QString& firs
     }
 }
 
-void Core::RosterItem::requestFromEmpty(int count, const QString& before)
-{
-    if (syncronizing) {
-        qDebug("perform from empty didn't work, another request queued");
-    } else {
-        if (archiveState != empty) {
-            qDebug("perform from empty didn't work, the state is not empty");
-            requestHistory(count, before);
-        } else {
-            syncronizing = true;
-            requestedCount = count;
-            requestedBefore = "";
-            hisoryCache.clear();
-            responseCache.clear();
-            
-            emit needHistory(before, "");
-        }
-    }
-}
-
 QString Core::RosterItem::getServer() const
 {
     QStringList lst = jid.split("@");
diff --git a/core/rosteritem.h b/core/rosteritem.h
index cecd2e4..f38e189 100644
--- a/core/rosteritem.h
+++ b/core/rosteritem.h
@@ -67,7 +67,6 @@ public:
     void appendMessageToArchive(const Shared::Message& msg);
     void flushMessagesToArchive(bool finished, const QString& firstId, const QString& lastId);
     void requestHistory(int count, const QString& before);
-    void requestFromEmpty(int count, const QString& before);
     QString avatarPath(const QString& resource = "") const;
     QString folderPath() const;
     bool readAvatarInfo(Archive::AvatarInfo& target, const QString& resource = "") const;
@@ -112,6 +111,7 @@ protected:
 private:
     void nextRequest();
     void performRequest(int count, const QString& before);
+    QString getId(const Shared::Message& msg);
 };
 
 }
diff --git a/core/squawk.cpp b/core/squawk.cpp
index 8a486c0..9bb2f14 100644
--- a/core/squawk.cpp
+++ b/core/squawk.cpp
@@ -133,11 +133,9 @@ void Core::Squawk::addAccount(
 )
 {
     QSettings settings;
-    unsigned int reconnects = settings.value("reconnects", 2).toUInt();
     
     Account* acc = new Account(login, server, password, name, &network);
     acc->setResource(resource);
-    acc->setReconnectTimes(reconnects);
     acc->setPasswordType(passwordType);
     accounts.push_back(acc);
     amap.insert(std::make_pair(name, acc));
@@ -664,6 +662,7 @@ void Core::Squawk::responsePassword(const QString& account, const QString& passw
         return;
     }
     itr->second->setPassword(password);
+    emit changeAccount(account, {{"password", password}});
     accountReady();
 }
 
@@ -750,5 +749,6 @@ void Core::Squawk::onWalletResponsePassword(const QString& login, const QString&
         return;
     }
     itr->second->setPassword(password);
+    emit changeAccount(login, {{"password", password}});
     accountReady();
 }
diff --git a/shared/message.cpp b/shared/message.cpp
index 7df0f28..fad10dc 100644
--- a/shared/message.cpp
+++ b/shared/message.cpp
@@ -32,7 +32,12 @@ Shared::Message::Message(Shared::Message::Type p_type):
     outgoing(false),
     forwarded(false),
     state(State::delivered),
-    edited(false) {}
+    edited(false),
+    errorText(),
+    originalMessage(),
+    lastModified(),
+    stanzaId()
+    {}
 
 Shared::Message::Message():
     jFrom(),
@@ -50,7 +55,9 @@ Shared::Message::Message():
     edited(false),
     errorText(),
     originalMessage(),
-    lastModified() {}
+    lastModified(),
+    stanzaId()
+    {}
 
 QString Shared::Message::getBody() const
 {
@@ -77,7 +84,11 @@ QString Shared::Message::getTo() const
 
 QString Shared::Message::getId() const
 {
-    return id;
+    if (id.size() > 0) {
+        return id;
+    } else {
+        return stanzaId;
+    }
 }
 
 QDateTime Shared::Message::getTime() const
@@ -299,6 +310,7 @@ void Shared::Message::serialize(QDataStream& data) const
         data << originalMessage;
         data << lastModified;
     }
+    data << stanzaId;
 }
 
 void Shared::Message::deserialize(QDataStream& data)
@@ -328,6 +340,7 @@ void Shared::Message::deserialize(QDataStream& data)
         data >> originalMessage;
         data >> lastModified;
     }
+    data >> stanzaId;
 }
 
 bool Shared::Message::change(const QMap<QString, QVariant>& data)
@@ -353,6 +366,18 @@ bool Shared::Message::change(const QMap<QString, QVariant>& data)
             idChanged = true;
         }
     }
+    
+    itr = data.find("stanzaId");
+    if (itr != data.end()) {
+        QString newId = itr.value().toString();
+        if (stanzaId != newId) {
+            setStanzaId(newId);
+            if (id.size() == 0) {
+                idChanged = true;
+            }
+        }
+    }
+    
     itr = data.find("body");
     if (itr != data.end()) {
         QMap<QString, QVariant>::const_iterator dItr = data.find("stamp");
@@ -397,3 +422,13 @@ bool Shared::Message::storable() const
 {
     return id.size() > 0 && (body.size() > 0 || oob.size()) > 0;
 }
+
+void Shared::Message::setStanzaId(const QString& sid)
+{
+    stanzaId = sid;
+}
+
+QString Shared::Message::getStanzaId() const
+{
+    return stanzaId;
+}
diff --git a/shared/message.h b/shared/message.h
index 4a0d661..d84053f 100644
--- a/shared/message.h
+++ b/shared/message.h
@@ -71,6 +71,7 @@ public:
     void setEdited(bool p_edited);
     void setErrorText(const QString& err);
     bool change(const QMap<QString, QVariant>& data);
+    void setStanzaId(const QString& sid);
     
     QString getFrom() const;
     QString getFromJid() const;
@@ -98,6 +99,7 @@ public:
     bool serverStored() const;
     QDateTime getLastModified() const;
     QString getOriginalBody() const;
+    QString getStanzaId() const;
     
     void serialize(QDataStream& data) const;
     void deserialize(QDataStream& data);
@@ -120,6 +122,7 @@ private:
     QString errorText;
     QString originalMessage;
     QDateTime lastModified;
+    QString stanzaId;
 };
 
 }

From 20bcae5ab2d4f49faaf1bcfc425cb58229efd68e Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Fri, 22 May 2020 19:28:26 +0300
Subject: [PATCH 074/281] finally history works in mucs

---
 core/account.cpp | 11 +++++++++--
 1 file changed, 9 insertions(+), 2 deletions(-)

diff --git a/core/account.cpp b/core/account.cpp
index 0f654fa..2d70876 100644
--- a/core/account.cpp
+++ b/core/account.cpp
@@ -658,7 +658,8 @@ void Core::Account::onContactNeedHistory(const QString& before, const QString& a
 {
     RosterItem* contact = static_cast<RosterItem*>(sender());
 
-    QString to = contact->jid;
+    QString to;
+    QString with;
     QXmppResultSetQuery query;
     QDateTime start;
     query.setMax(100);
@@ -680,8 +681,14 @@ void Core::Account::onContactNeedHistory(const QString& before, const QString& a
         qDebug() << "Remote query for" << contact->jid << "from" << after << ", to" << before;
     }
     
+    if (contact->isMuc()) {
+        to = contact->jid;
+    } else {
+        with = contact->jid;
+    }
     
-    QString q = am->retrieveArchivedMessages(to, "", contact->jid, start, QDateTime(), query);
+    
+    QString q = am->retrieveArchivedMessages(to, "", with, start, QDateTime(), query);
     achiveQueries.insert(std::make_pair(q, contact->jid));
 }
 

From 87426ee20f2e15c24c6a6c0f1a798452d5dac806 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Mon, 15 Jun 2020 00:23:43 +0300
Subject: [PATCH 075/281] account refactoring

---
 core/CMakeLists.txt                  |   1 +
 core/account.cpp                     | 709 +++------------------------
 core/account.h                       |  51 +-
 core/handlers/messagehandler.cpp     |  29 +-
 core/handlers/messagehandler.h       |   2 +-
 core/handlers/rosterhandler.cpp      | 583 ++++++++++++++++++++++
 core/handlers/rosterhandler.h        | 115 +++++
 external/simpleCrypt/simplecrypt.cpp |   8 +-
 external/simpleCrypt/simplecrypt.h   |   1 +
 9 files changed, 797 insertions(+), 702 deletions(-)
 create mode 100644 core/handlers/rosterhandler.cpp
 create mode 100644 core/handlers/rosterhandler.h

diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt
index 64319c2..b74a055 100644
--- a/core/CMakeLists.txt
+++ b/core/CMakeLists.txt
@@ -19,6 +19,7 @@ set(squawkCORE_SRC
     networkaccess.cpp
     adapterFuctions.cpp
     handlers/messagehandler.cpp
+    handlers/rosterhandler.cpp
 )
 
 add_subdirectory(passwordStorageEngines)
diff --git a/core/account.cpp b/core/account.cpp
index 2d70876..59a05fd 100644
--- a/core/account.cpp
+++ b/core/account.cpp
@@ -30,7 +30,6 @@ Account::Account(const QString& p_login, const QString& p_server, const QString&
     config(),
     presence(),
     state(Shared::ConnectionState::disconnected),
-    groups(),
     cm(new QXmppCarbonManager()),
     am(new QXmppMamManager()),
     mm(new QXmppMucManager()),
@@ -40,20 +39,15 @@ Account::Account(const QString& p_login, const QString& p_server, const QString&
     um(new QXmppUploadRequestManager()),
     dm(client.findExtension<QXmppDiscoveryManager>()),
     rcpm(new QXmppMessageReceiptManager()),
-    contacts(),
-    conferences(),
     reconnectScheduled(false),
     reconnectTimer(new QTimer),
-    queuedContacts(),
-    outOfRosterContacts(),
-    pendingMessages(),
-    uploadingSlotsQueue(),
     avatarHash(),
     avatarType(),
     ownVCardRequestInProgress(false),
     network(p_net),
     passwordType(Shared::AccountPassword::plain),
-    mh(new MessageHandler(this))
+    mh(new MessageHandler(this)),
+    rh(new RosterHandler(this))
 {
     config.setUser(p_login);
     config.setDomain(p_server);
@@ -66,12 +60,6 @@ Account::Account(const QString& p_login, const QString& p_server, const QString&
     QObject::connect(&client, &QXmppClient::messageReceived, mh, &MessageHandler::onMessageReceived);
     QObject::connect(&client, &QXmppClient::error, this, &Account::onClientError);
     
-    QObject::connect(rm, &QXmppRosterManager::rosterReceived, this, &Account::onRosterReceived);
-    QObject::connect(rm, &QXmppRosterManager::itemAdded, this, &Account::onRosterItemAdded);
-    QObject::connect(rm, &QXmppRosterManager::itemRemoved, this, &Account::onRosterItemRemoved);
-    QObject::connect(rm, &QXmppRosterManager::itemChanged, this, &Account::onRosterItemChanged);
-    //QObject::connect(&rm, &QXmppRosterManager::presenceChanged, this, &Account::onRosterPresenceChanged);
-    
     client.addExtension(cm);
     
     QObject::connect(cm, &QXmppCarbonManager::messageReceived, mh, &MessageHandler::onCarbonMessageReceived);
@@ -84,10 +72,7 @@ Account::Account(const QString& p_login, const QString& p_server, const QString&
     QObject::connect(am, &QXmppMamManager::resultsRecieved, this, &Account::onMamResultsReceived);
     
     client.addExtension(mm);
-    QObject::connect(mm, &QXmppMucManager::roomAdded, this, &Account::onMucRoomAdded);
-    
     client.addExtension(bm);
-    QObject::connect(bm, &QXmppBookmarkManager::bookmarksReceived, this, &Account::bookmarksReceived);
     
     QObject::connect(vm, &QXmppVCardManager::vCardReceived, this, &Account::onVCardReceived);
     //QObject::connect(&vm, &QXmppVCardManager::clientVCardReceived, this, &Account::onOwnVCardReceived); //for some reason it doesn't work, launching from common handler
@@ -173,13 +158,8 @@ Account::~Account()
     QObject::disconnect(network, &NetworkAccess::uploadFileComplete, mh, &MessageHandler::onFileUploaded);
     QObject::disconnect(network, &NetworkAccess::uploadFileError, mh, &MessageHandler::onFileUploadError);
     
-    for (std::map<QString, Contact*>::const_iterator itr = contacts.begin(), end = contacts.end(); itr != end; ++itr) {
-        delete itr->second;
-    }
-    
-    for (std::map<QString, Conference*>::const_iterator itr = conferences.begin(), end = conferences.end(); itr != end; ++itr) {
-        delete itr->second;
-    }
+    delete mh;
+    delete rh;
     
     delete reconnectTimer;
     delete rcpm;
@@ -213,7 +193,7 @@ void Core::Account::disconnect()
         reconnectTimer->stop();
     }
     if (state != Shared::ConnectionState::disconnected) {
-        clearConferences();
+        rh->clearConferences();
         client.disconnectFromServer();
     }
 }
@@ -271,176 +251,58 @@ void Core::Account::reconnect()
     }
 }
 
-QString Core::Account::getName() const
-{
-    return name;
-}
+QString Core::Account::getName() const {
+    return name;}
 
-QString Core::Account::getLogin() const
-{
-    return config.user();
-}
+QString Core::Account::getLogin() const {
+    return config.user();}
 
-QString Core::Account::getPassword() const
-{
-    return config.password();
-}
+QString Core::Account::getPassword() const {
+    return config.password();}
 
-QString Core::Account::getServer() const
-{
-    return config.domain();
-}
+QString Core::Account::getServer() const {
+    return config.domain();}
 
-Shared::AccountPassword Core::Account::getPasswordType() const
-{
-    return passwordType;
-}
+Shared::AccountPassword Core::Account::getPasswordType() const {
+    return passwordType;}
 
-void Core::Account::onRosterReceived()
-{
-    vm->requestClientVCard();         //TODO need to make sure server actually supports vCards
-    ownVCardRequestInProgress = true;
-    
-    QStringList bj = rm->getRosterBareJids();
-    for (int i = 0; i < bj.size(); ++i) {
-        const QString& jid = bj[i];
-        addedAccount(jid);
-    }
-}
+void Core::Account::setPasswordType(Shared::AccountPassword pt) {
+    passwordType = pt; }
 
-void Core::Account::onRosterItemAdded(const QString& bareJid)
-{
-    addedAccount(bareJid);
-    std::map<QString, QString>::const_iterator itr = queuedContacts.find(bareJid);
-    if (itr != queuedContacts.end()) {
-        rm->subscribe(bareJid, itr->second);
-        queuedContacts.erase(itr);
-    }
-}
+void Core::Account::setLogin(const QString& p_login) {
+    config.setUser(p_login);}
 
-void Core::Account::setPasswordType(Shared::AccountPassword pt)
-{
-    passwordType = pt;
-}
+void Core::Account::setName(const QString& p_name) {
+    name = p_name;}
 
-void Core::Account::onRosterItemChanged(const QString& bareJid)
-{
-    std::map<QString, Contact*>::const_iterator itr = contacts.find(bareJid);
-    if (itr == contacts.end()) {
-        qDebug() << "An attempt to change non existing contact" << bareJid << "from account" << name << ", skipping";
-        return;
-    }
-    Contact* contact = itr->second;
-    QXmppRosterIq::Item re = rm->getRosterEntry(bareJid);
-    
-    Shared::SubscriptionState state = castSubscriptionState(re.subscriptionType());
+void Core::Account::setPassword(const QString& p_password) {
+    config.setPassword(p_password);}
 
-    contact->setGroups(re.groups());
-    contact->setSubscriptionState(state);
-    contact->setName(re.name());
-}
+void Core::Account::setServer(const QString& p_server) {
+    config.setDomain(p_server);}
 
-void Core::Account::onRosterItemRemoved(const QString& bareJid)
+Shared::Availability Core::Account::getAvailability() const
 {
-    std::map<QString, Contact*>::const_iterator itr = contacts.find(bareJid);
-    if (itr == contacts.end()) {
-        qDebug() << "An attempt to remove non existing contact" << bareJid << "from account" << name << ", skipping";
-        return;
-    }
-    Contact* contact = itr->second;
-    contacts.erase(itr);
-    QSet<QString> cGroups = contact->getGroups();
-    for (QSet<QString>::const_iterator itr = cGroups.begin(), end = cGroups.end(); itr != end; ++itr) {
-        removeFromGroup(bareJid, *itr);
-    }
-    emit removeContact(bareJid);
-    
-    contact->deleteLater();
-}
-
-void Core::Account::addedAccount(const QString& jid)
-{
-    std::map<QString, Contact*>::const_iterator itr = contacts.find(jid);
-    QXmppRosterIq::Item re = rm->getRosterEntry(jid);
-    Contact* contact;
-    bool newContact = false;
-    if (itr == contacts.end()) {
-        newContact = true;
-        contact = new Contact(jid, name);
-        contacts.insert(std::make_pair(jid, contact));
-        
+    if (state == Shared::ConnectionState::connected) {
+        QXmppPresence::AvailableStatusType pres = presence.availableStatusType();
+        return static_cast<Shared::Availability>(pres);         //they are compatible;
     } else {
-        contact = itr->second;
-    }
-    
-    QSet<QString> gr = re.groups();
-    Shared::SubscriptionState state = castSubscriptionState(re.subscriptionType());
-    contact->setGroups(gr);
-    contact->setSubscriptionState(state);
-    contact->setName(re.name());
-    
-    if (newContact) {
-        QMap<QString, QVariant> cData({
-            {"name", re.name()},
-            {"state", QVariant::fromValue(state)}
-        });
-        
-        Archive::AvatarInfo info;
-        bool hasAvatar = contact->readAvatarInfo(info);
-        if (hasAvatar) {
-            if (info.autogenerated) {
-                cData.insert("avatarState", static_cast<uint>(Shared::Avatar::valid));
-            } else {
-                cData.insert("avatarState", static_cast<uint>(Shared::Avatar::autocreated));
-            }
-            cData.insert("avatarPath", contact->avatarPath() + "." + info.type);
-        } else {
-            cData.insert("avatarState", static_cast<uint>(Shared::Avatar::empty));
-            cData.insert("avatarPath", "");
-            requestVCard(jid);
-        }
-        int grCount = 0;
-        for (QSet<QString>::const_iterator itr = gr.begin(), end = gr.end(); itr != end; ++itr) {
-            const QString& groupName = *itr;
-            addToGroup(jid, groupName);
-            emit addContact(jid, groupName, cData);
-            grCount++;
-        }
-        
-        if (grCount == 0) {
-            emit addContact(jid, "", cData);
-        }
-        handleNewContact(contact);
+        return Shared::Availability::offline;
     }
 }
 
-void Core::Account::handleNewRosterItem(Core::RosterItem* contact)
+void Core::Account::setAvailability(Shared::Availability avail)
 {
-    QObject::connect(contact, &RosterItem::needHistory, this, &Account::onContactNeedHistory);
-    QObject::connect(contact, &RosterItem::historyResponse, this, &Account::onContactHistoryResponse);
-    QObject::connect(contact, &RosterItem::nameChanged, this, &Account::onContactNameChanged);
-    QObject::connect(contact, &RosterItem::avatarChanged, this, &Account::onContactAvatarChanged);
-    QObject::connect(contact, &RosterItem::requestVCard, this, &Account::requestVCard);
-}
-
-void Core::Account::handleNewContact(Core::Contact* contact)
-{
-    handleNewRosterItem(contact);
-    QObject::connect(contact, &Contact::groupAdded, this, &Account::onContactGroupAdded);
-    QObject::connect(contact, &Contact::groupRemoved, this, &Account::onContactGroupRemoved);
-    QObject::connect(contact, &Contact::subscriptionStateChanged, this, &Account::onContactSubscriptionStateChanged);
-}
-
-void Core::Account::handleNewConference(Core::Conference* contact)
-{
-    handleNewRosterItem(contact);
-    QObject::connect(contact, &Conference::nickChanged, this, &Account::onMucNickNameChanged);
-    QObject::connect(contact, &Conference::subjectChanged, this, &Account::onMucSubjectChanged);
-    QObject::connect(contact, &Conference::joinedChanged, this, &Account::onMucJoinedChanged);
-    QObject::connect(contact, &Conference::autoJoinChanged, this, &Account::onMucAutoJoinChanged);
-    QObject::connect(contact, &Conference::addParticipant, this, &Account::onMucAddParticipant);
-    QObject::connect(contact, &Conference::changeParticipant, this, &Account::onMucChangeParticipant);
-    QObject::connect(contact, &Conference::removeParticipant, this, &Account::onMucRemoveParticipant);
+    if (avail == Shared::Availability::offline) {
+        disconnect();               //TODO not sure how to do here - changing state may cause connection or disconnection
+    } else {
+        QXmppPresence::AvailableStatusType pres = static_cast<QXmppPresence::AvailableStatusType>(avail);
+        
+        presence.setAvailableStatusType(pres);
+        if (state != Shared::ConnectionState::disconnected) {
+            client.setClientPresence(presence);
+        }
+    }
 }
 
 void Core::Account::onPresenceReceived(const QXmppPresence& p_presence)
@@ -478,11 +340,9 @@ void Core::Account::onPresenceReceived(const QXmppPresence& p_presence)
             }
         }
     } else {
-        if (pendingVCardRequests.find(jid) == pendingVCardRequests.end()) {
-            std::map<QString, Contact*>::const_iterator itr = contacts.find(jid);
-            if (itr != contacts.end()) {
-                itr->second->handlePresence(p_presence);
-            }
+        RosterItem* item = rh->getRosterItem(jid);
+        if (item != 0) {
+            item->handlePresence(p_presence);
         }
     }
     
@@ -501,7 +361,7 @@ void Core::Account::onPresenceReceived(const QXmppPresence& p_presence)
                 {"status", p_presence.statusText()}
             });
         }
-            break;
+        break;
         case QXmppPresence::Unavailable:
             emit removePresence(jid, resource);
             break;
@@ -519,81 +379,20 @@ void Core::Account::onPresenceReceived(const QXmppPresence& p_presence)
     }
 }
 
-void Core::Account::onRosterPresenceChanged(const QString& bareJid, const QString& resource)
-{
-    //not used for now;
-    qDebug() << "presence changed for " << bareJid << " resource " << resource;
-    const QXmppPresence& presence = rm->getPresence(bareJid, resource);
-}
+QString Core::Account::getResource() const {
+    return config.resource();}
 
-void Core::Account::setLogin(const QString& p_login)
-{
-    config.setUser(p_login);
-}
+void Core::Account::setResource(const QString& p_resource) {
+    config.setResource(p_resource);}
 
-void Core::Account::setName(const QString& p_name)
-{
-    name = p_name;
-}
+QString Core::Account::getFullJid() const {
+    return getLogin() + "@" + getServer() + "/" + getResource();}
 
-void Core::Account::setPassword(const QString& p_password)
-{
-    config.setPassword(p_password);
-}
+void Core::Account::sendMessage(const Shared::Message& data) {
+    mh->sendMessage(data);}
 
-void Core::Account::setServer(const QString& p_server)
-{
-    config.setDomain(p_server);
-}
-
-Shared::Availability Core::Account::getAvailability() const
-{
-    if (state == Shared::ConnectionState::connected) {
-        QXmppPresence::AvailableStatusType pres = presence.availableStatusType();
-        return static_cast<Shared::Availability>(pres);         //they are compatible;
-    } else {
-        return Shared::Availability::offline;
-    }
-}
-
-void Core::Account::setAvailability(Shared::Availability avail)
-{
-    if (avail == Shared::Availability::offline) {
-        disconnect();               //TODO not sure how to do here - changing state may cause connection or disconnection
-    } else {
-        QXmppPresence::AvailableStatusType pres = static_cast<QXmppPresence::AvailableStatusType>(avail);
-        
-        presence.setAvailableStatusType(pres);
-        if (state != Shared::ConnectionState::disconnected) {
-            client.setClientPresence(presence);
-        }
-    }
-}
-
-QString Core::Account::getResource() const
-{
-    return config.resource();
-}
-
-void Core::Account::setResource(const QString& p_resource)
-{
-    config.setResource(p_resource);
-}
-
-QString Core::Account::getFullJid() const
-{
-    return getLogin() + "@" + getServer() + "/" + getResource();
-}
-
-void Core::Account::sendMessage(const Shared::Message& data)
-{
-    mh->sendMessage(data);
-}
-
-void Core::Account::sendMessage(const Shared::Message& data, const QString& path)
-{
-    mh->sendMessage(data, path);
-}
+void Core::Account::sendMessage(const Shared::Message& data, const QString& path) {
+    mh->sendMessage(data, path);}
 
 void Core::Account::onMamMessageReceived(const QString& queryId, const QXmppMessage& msg)
 {
@@ -601,7 +400,7 @@ void Core::Account::onMamMessageReceived(const QString& queryId, const QXmppMess
         std::map<QString, QString>::const_iterator itr = achiveQueries.find(queryId);
         if (itr != achiveQueries.end()) {
             QString jid = itr->second;
-            RosterItem* item = getRosterItem(jid);
+            RosterItem* item = rh->getRosterItem(jid);
             
             Shared::Message sMsg(static_cast<Shared::Message::Type>(msg.type()));
             mh->initializeMessage(sMsg, msg, false, true, true);
@@ -614,31 +413,13 @@ void Core::Account::onMamMessageReceived(const QString& queryId, const QXmppMess
                 item->addMessageToArchive(sMsg);
             }
         }
-    } 
-    
-    //handleChatMessage(msg, false, true, true);
-}
-
-Core::RosterItem * Core::Account::getRosterItem(const QString& jid)
-{
-    RosterItem* item = 0;
-    std::map<QString, Contact*>::const_iterator citr = contacts.find(jid);
-    if (citr != contacts.end()) {
-        item = citr->second;
-    } else {
-        std::map<QString, Conference*>::const_iterator coitr = conferences.find(jid);
-        if (coitr != conferences.end()) {
-            item = coitr->second;
-        }
     }
-    
-    return item;
 }
 
 void Core::Account::requestArchive(const QString& jid, int count, const QString& before)
 {
     qDebug() << "An archive request for " << jid << ", before " << before;
-    RosterItem* contact = getRosterItem(jid);
+    RosterItem* contact = rh->getRosterItem(jid);
     
     if (contact == 0) {
         qDebug() << "An attempt to request archive for" << jid << "in account" << name << ", but the contact with such id wasn't found, skipping";
@@ -687,12 +468,10 @@ void Core::Account::onContactNeedHistory(const QString& before, const QString& a
         with = contact->jid;
     }
     
-    
     QString q = am->retrieveArchivedMessages(to, "", with, start, QDateTime(), query);
     achiveQueries.insert(std::make_pair(q, contact->jid));
 }
 
-
 void Core::Account::onMamResultsReceived(const QString& queryId, const QXmppResultSetReply& resultSetReply, bool complete)
 {
     std::map<QString, QString>::const_iterator itr = achiveQueries.find(queryId);
@@ -700,7 +479,7 @@ void Core::Account::onMamResultsReceived(const QString& queryId, const QXmppResu
         QString jid = itr->second;
         achiveQueries.erase(itr);
         
-        RosterItem* ri = getRosterItem(jid);
+        RosterItem* ri = rh->getRosterItem(jid);
         
         if (ri != 0) {
             qDebug() << "Flushing messages for" << jid << ", complete:" << complete;
@@ -715,98 +494,6 @@ void Core::Account::onMamLog(QXmppLogger::MessageType type, const QString& msg)
     qDebug() << msg;
 }
 
-void Core::Account::onContactGroupAdded(const QString& group)
-{
-    Contact* contact = static_cast<Contact*>(sender());
-    if (contact->groupsCount() == 1) {
-        // not sure i need to handle it here, the situation with grouped and ungrouped contacts handled on the client anyway
-    }
-    
-    QMap<QString, QVariant> cData({
-        {"name", contact->getName()},
-        {"state", QVariant::fromValue(contact->getSubscriptionState())}
-    });
-    addToGroup(contact->jid, group);
-    emit addContact(contact->jid, group, cData);
-}
-
-void Core::Account::onContactGroupRemoved(const QString& group)
-{
-    Contact* contact = static_cast<Contact*>(sender());
-    if (contact->groupsCount() == 0) {
-        // not sure i need to handle it here, the situation with grouped and ungrouped contacts handled on the client anyway
-    }
-    
-    emit removeContact(contact->jid, group);
-    removeFromGroup(contact->jid, group);
-}
-
-void Core::Account::onContactNameChanged(const QString& cname)
-{
-    Contact* contact = static_cast<Contact*>(sender());
-    QMap<QString, QVariant> cData({
-        {"name", cname},
-    });
-    emit changeContact(contact->jid, cData);
-}
-
-void Core::Account::onContactSubscriptionStateChanged(Shared::SubscriptionState cstate)
-{
-    Contact* contact = static_cast<Contact*>(sender());
-    QMap<QString, QVariant> cData({
-        {"state", QVariant::fromValue(cstate)},
-    });
-    emit changeContact(contact->jid, cData);
-}
-
-void Core::Account::addToGroup(const QString& jid, const QString& group)
-{
-    std::map<QString, std::set<QString>>::iterator gItr = groups.find(group);
-    if (gItr == groups.end()) {
-        gItr = groups.insert(std::make_pair(group, std::set<QString>())).first;
-        emit addGroup(group);
-    }
-    gItr->second.insert(jid);
-}
-
-void Core::Account::removeFromGroup(const QString& jid, const QString& group)
-{
-    QSet<QString> toRemove;
-    std::map<QString, std::set<QString>>::iterator itr = groups.find(group);
-    if (itr == groups.end()) {
-        qDebug() << "An attempt to remove contact" << jid << "of account" << name << "from non existing group" << group << ", skipping";
-        return;
-    }
-    std::set<QString> contacts = itr->second;
-    std::set<QString>::const_iterator cItr = contacts.find(jid);
-    if (cItr != contacts.end()) {
-        contacts.erase(cItr);
-        if (contacts.size() == 0) {
-            emit removeGroup(group);
-            groups.erase(group);
-        }
-    }
-}
-
-Shared::SubscriptionState Core::Account::castSubscriptionState(QXmppRosterIq::Item::SubscriptionType qs) const
-{
-    Shared::SubscriptionState state;
-    if (qs == QXmppRosterIq::Item::NotSet) {
-        state = Shared::SubscriptionState::unknown;
-    } else {
-        state = static_cast<Shared::SubscriptionState>(qs);
-    }
-    return state;
-}
-
-void Core::Account::onContactHistoryResponse(const std::list<Shared::Message>& list)
-{
-    RosterItem* contact = static_cast<RosterItem*>(sender());
-    
-    qDebug() << "Collected history for contact " << contact->jid << list.size() << "elements";
-    emit responseArchive(contact->jid, list);
-}
-
 void Core::Account::onClientError(QXmppClient::Error err)
 {
     qDebug() << "Error";
@@ -909,7 +596,6 @@ void Core::Account::onClientError(QXmppClient::Error err)
     emit error(errorText);
 }
 
-
 void Core::Account::subscribeToContact(const QString& jid, const QString& reason)
 {
     if (state == Shared::ConnectionState::connected) {
@@ -928,269 +614,50 @@ void Core::Account::unsubscribeFromContact(const QString& jid, const QString& re
     }
 }
 
-void Core::Account::removeContactRequest(const QString& jid)
-{
-    if (state == Shared::ConnectionState::connected) {
-        std::set<QString>::const_iterator itr = outOfRosterContacts.find(jid);
-        if (itr != outOfRosterContacts.end()) {
-            outOfRosterContacts.erase(itr);
-            onRosterItemRemoved(jid);
-        } else {
-            rm->removeItem(jid);
-        }
-    } else {
-        qDebug() << "An attempt to remove contact " << jid << " from account " << name << " but the account is not in the connected state, skipping";
-    }
-}
+void Core::Account::removeContactRequest(const QString& jid) {
+    rh->removeContactRequest(jid);}
 
-
-void Core::Account::addContactRequest(const QString& jid, const QString& name, const QSet<QString>& groups)
-{
-    if (state == Shared::ConnectionState::connected) {
-        std::map<QString, QString>::const_iterator itr = queuedContacts.find(jid);
-        if (itr != queuedContacts.end()) {
-            qDebug() << "An attempt to add contact " << jid << " to account " << name << " but the account is already queued for adding, skipping";
-        } else {
-            queuedContacts.insert(std::make_pair(jid, ""));     //TODO need to add reason here;
-            rm->addItem(jid, name, groups);
-        }
-    } else {
-        qDebug() << "An attempt to add contact " << jid << " to account " << name << " but the account is not in the connected state, skipping";
-    }
-}
-
-void Core::Account::onMucRoomAdded(QXmppMucRoom* room)
-{
-    qDebug() << "room" << room->jid() << "added with name" << room->name() << ", account" << getName() << "joined:" << room->isJoined(); 
-}
-
-void Core::Account::bookmarksReceived(const QXmppBookmarkSet& bookmarks)
-{
-    QList<QXmppBookmarkConference> confs = bookmarks.conferences();
-    for (QList<QXmppBookmarkConference>::const_iterator itr = confs.begin(), end = confs.end(); itr != end; ++itr) {
-        const QXmppBookmarkConference& c = *itr;
-        
-        QString jid = c.jid();
-        std::map<QString, Conference*>::const_iterator cItr = conferences.find(jid);
-        if (cItr == conferences.end()) {
-            addNewRoom(jid, c.nickName(), c.name(), c.autoJoin());
-        } else {
-            qDebug() << "Received a bookmark to a MUC " << jid << " which is already booked by another bookmark, skipping";
-        }
-    }
-}
-
-void Core::Account::onMucJoinedChanged(bool joined)
-{
-    Conference* room = static_cast<Conference*>(sender());
-    emit changeRoom(room->jid, {
-            {"joined", joined}
-        });
-}
-
-void Core::Account::onMucAutoJoinChanged(bool autoJoin)
-{
-    storeConferences();
-    Conference* room = static_cast<Conference*>(sender());
-    emit changeRoom(room->jid, {
-            {"autoJoin", autoJoin}
-        });
-}
-
-void Core::Account::onMucNickNameChanged(const QString& nickName)
-{
-    storeConferences();
-    Conference* room = static_cast<Conference*>(sender());
-    emit changeRoom(room->jid, {
-            {"nick", nickName}
-        });
-}
+void Core::Account::addContactRequest(const QString& jid, const QString& name, const QSet<QString>& groups) {
+    rh->addContactRequest(jid, name, groups);}
 
 void Core::Account::setRoomAutoJoin(const QString& jid, bool joined)
 {
-    std::map<QString, Conference*>::const_iterator cItr = conferences.find(jid);
-    if (cItr == conferences.end()) {
+    Conference* conf = rh->getConference(jid);
+    if (conf == 0) {
         qDebug() << "An attempt to set auto join to the non existing room" << jid << "of the account" << getName() << ", skipping";
         return;
     }
     
-    cItr->second->setAutoJoin(joined);
+    conf->setAutoJoin(joined);
 }
 
 void Core::Account::setRoomJoined(const QString& jid, bool joined)
 {
-    std::map<QString, Conference*>::const_iterator cItr = conferences.find(jid);
-    if (cItr == conferences.end()) {
+    Conference* conf = rh->getConference(jid);
+    if (conf == 0) {
         qDebug() << "An attempt to set joined to the non existing room" << jid << "of the account" << getName() << ", skipping";
         return;
     }
     
-    cItr->second->setJoined(joined);
+    conf->setJoined(joined);
 }
 
-void Core::Account::onMucAddParticipant(const QString& nickName, const QMap<QString, QVariant>& data)
-{
-    Conference* room = static_cast<Conference*>(sender());
-    emit addRoomParticipant(room->jid, nickName, data);
-}
+void Core::Account::removeRoomRequest(const QString& jid){
+    rh->removeRoomRequest(jid);}
 
-void Core::Account::onMucChangeParticipant(const QString& nickName, const QMap<QString, QVariant>& data)
-{
-    Conference* room = static_cast<Conference*>(sender());
-    emit changeRoomParticipant(room->jid, nickName, data);
-}
+void Core::Account::addRoomRequest(const QString& jid, const QString& nick, const QString& password, bool autoJoin) {
+    rh->addRoomRequest(jid, nick, password, autoJoin);}
 
-void Core::Account::onMucRemoveParticipant(const QString& nickName)
-{
-    Conference* room = static_cast<Conference*>(sender());
-    emit removeRoomParticipant(room->jid, nickName);
-}
+void Core::Account::addContactToGroupRequest(const QString& jid, const QString& groupName) {
+    rh->addContactToGroupRequest(jid, groupName);}
 
-void Core::Account::onMucSubjectChanged(const QString& subject)
-{
-    Conference* room = static_cast<Conference*>(sender());
-    emit changeRoom(room->jid, {
-        {"subject", subject}
-    });
-}
-
-void Core::Account::storeConferences()
-{
-    QXmppBookmarkSet bms = bm->bookmarks();
-    QList<QXmppBookmarkConference> confs;
-    for (std::map<QString, Conference*>::const_iterator itr = conferences.begin(), end = conferences.end(); itr != end; ++itr) {
-        Conference* conference = itr->second;
-        QXmppBookmarkConference conf;
-        conf.setJid(conference->jid);
-        conf.setName(conference->getName());
-        conf.setNickName(conference->getNick());
-        conf.setAutoJoin(conference->getAutoJoin());
-        confs.push_back(conf);
-    }
-    bms.setConferences(confs);
-    bm->setBookmarks(bms);
-}
-
-void Core::Account::clearConferences()
-{
-    for (std::map<QString, Conference*>::const_iterator itr = conferences.begin(), end = conferences.end(); itr != end; itr++) {
-        itr->second->deleteLater();
-        emit removeRoom(itr->first);
-    }
-    conferences.clear();
-}
-
-void Core::Account::removeRoomRequest(const QString& jid)
-{
-    std::map<QString, Conference*>::const_iterator itr = conferences.find(jid);
-    if (itr == conferences.end()) {
-        qDebug() << "An attempt to remove non existing room" << jid << "from account" << name << ", skipping";
-    }
-    itr->second->deleteLater();
-    conferences.erase(itr);
-    emit removeRoom(jid);
-    storeConferences();
-}
-
-void Core::Account::addRoomRequest(const QString& jid, const QString& nick, const QString& password, bool autoJoin)
-{
-    std::map<QString, Conference*>::const_iterator cItr = conferences.find(jid);
-    if (cItr == conferences.end()) {
-        addNewRoom(jid, nick, "", autoJoin);
-        storeConferences();
-    } else {
-        qDebug() << "An attempt to add a MUC " << jid << " which is already present in the rester, skipping";
-    }
-}
-
-void Core::Account::addNewRoom(const QString& jid, const QString& nick, const QString& roomName, bool autoJoin)
-{
-    QXmppMucRoom* room = mm->addRoom(jid);
-    QString lNick = nick;
-    if (lNick.size() == 0) {
-        lNick = getName();
-    }
-    Conference* conf = new Conference(jid, getName(), autoJoin, roomName, lNick, room);
-    conferences.insert(std::make_pair(jid, conf));
-    
-    handleNewConference(conf);
-    
-    QMap<QString, QVariant> cData = {
-        {"autoJoin", conf->getAutoJoin()},
-        {"joined", conf->getJoined()},
-        {"nick", conf->getNick()},
-        {"name", conf->getName()},
-        {"avatars", conf->getAllAvatars()}
-    };
-    
-    
-    Archive::AvatarInfo info;
-    bool hasAvatar = conf->readAvatarInfo(info);
-    if (hasAvatar) {
-        if (info.autogenerated) {
-            cData.insert("avatarState", QVariant::fromValue(Shared::Avatar::autocreated));
-        } else {
-            cData.insert("avatarState", QVariant::fromValue(Shared::Avatar::valid));
-        }
-        cData.insert("avatarPath", conf->avatarPath() + "." + info.type);
-    } else {
-        cData.insert("avatarState", QVariant::fromValue(Shared::Avatar::empty));
-        cData.insert("avatarPath", "");
-        requestVCard(jid);
-    }
-    
-    emit addRoom(jid, cData);
-}
-
-void Core::Account::addContactToGroupRequest(const QString& jid, const QString& groupName)
-{
-    std::map<QString, Contact*>::const_iterator itr = contacts.find(jid);
-    if (itr == contacts.end()) {
-        qDebug() << "An attempt to add non existing contact" << jid << "of account" << name << "to the group" << groupName << ", skipping";
-    } else {
-        QXmppRosterIq::Item item = rm->getRosterEntry(jid);
-        QSet<QString> groups = item.groups();
-        if (groups.find(groupName) == groups.end()) {           //TODO need to change it, I guess that sort of code is better in qxmpp lib
-            groups.insert(groupName);
-            item.setGroups(groups);
-            
-            QXmppRosterIq iq;
-            iq.setType(QXmppIq::Set);
-            iq.addItem(item);
-            client.sendPacket(iq);
-        } else {
-            qDebug() << "An attempt to add contact" << jid << "of account" << name << "to the group" << groupName << "but it's already in that group, skipping";
-        }
-    }
-}
-
-void Core::Account::removeContactFromGroupRequest(const QString& jid, const QString& groupName)
-{
-    std::map<QString, Contact*>::const_iterator itr = contacts.find(jid);
-    if (itr == contacts.end()) {
-        qDebug() << "An attempt to remove non existing contact" << jid << "of account" << name << "from the group" << groupName << ", skipping";
-    } else {
-        QXmppRosterIq::Item item = rm->getRosterEntry(jid);
-        QSet<QString> groups = item.groups();
-        QSet<QString>::const_iterator gItr = groups.find(groupName);
-        if (gItr != groups.end()) {
-            groups.erase(gItr);
-            item.setGroups(groups);
-            
-            QXmppRosterIq iq;
-            iq.setType(QXmppIq::Set);
-            iq.addItem(item);
-            client.sendPacket(iq);
-        } else {
-            qDebug() << "An attempt to remove contact" << jid << "of account" << name << "from the group" << groupName << "but it's not in that group, skipping";
-        }
-    }
-}
+void Core::Account::removeContactFromGroupRequest(const QString& jid, const QString& groupName) {
+    rh->removeContactFromGroupRequest(jid, groupName);}
 
 void Core::Account::renameContactRequest(const QString& jid, const QString& newName)
 {
-    std::map<QString, Contact*>::const_iterator itr = contacts.find(jid);
-    if (itr == contacts.end()) {
+    Contact* cnt = rh->getContact(jid);
+    if (cnt == 0) {
         qDebug() << "An attempt to rename non existing contact" << jid << "of account" << name << ", skipping";
     } else {
         rm->renameItem(jid, newName);
@@ -1207,7 +674,7 @@ void Core::Account::onVCardReceived(const QXmppVCardIq& card)
         resource = comps.back();
     }
     pendingVCardRequests.erase(id);
-    RosterItem* item = getRosterItem(jid);
+    RosterItem* item = rh->getRosterItem(jid);
     
     if (item == 0) {
         if (jid == getLogin() + "@" + getServer()) {
@@ -1327,17 +794,6 @@ QString Core::Account::getAvatarPath() const
     }
 }
 
-void Core::Account::onContactAvatarChanged(Shared::Avatar type, const QString& path)
-{
-    RosterItem* item = static_cast<RosterItem*>(sender());
-    QMap<QString, QVariant> cData({
-        {"avatarState", static_cast<uint>(type)},
-        {"avatarPath", path}
-    });
-    
-    emit changeContact(item->jid, cData);
-}
-
 void Core::Account::requestVCard(const QString& jid)
 {
     if (pendingVCardRequests.find(jid) == pendingVCardRequests.end()) {
@@ -1426,12 +882,7 @@ void Core::Account::onDiscoveryInfoReceived(const QXmppDiscoveryIq& info)
 
 void Core::Account::cancelHistoryRequests()
 {
-    for (const std::pair<const QString, Conference*>& pair : conferences) {
-        pair.second->clearArchiveRequests();
-    }
-    for (const std::pair<const QString, Contact*>& pair : contacts) {
-        pair.second->clearArchiveRequests();
-    }
+    rh->cancelHistoryRequests();
     achiveQueries.clear();
 }
 
diff --git a/core/account.h b/core/account.h
index d31d066..9af4d7b 100644
--- a/core/account.h
+++ b/core/account.h
@@ -49,6 +49,7 @@
 #include "networkaccess.h"
 
 #include "handlers/messagehandler.h"
+#include "handlers/rosterhandler.h"
 
 namespace Core
 {
@@ -57,6 +58,7 @@ class Account : public QObject
 {
     Q_OBJECT
     friend class MessageHandler;
+    friend class RosterHandler;
 public:
     Account(
         const QString& p_login, 
@@ -141,7 +143,6 @@ private:
     QXmppConfiguration config;
     QXmppPresence presence;
     Shared::ConnectionState state;
-    std::map<QString, std::set<QString>> groups;
     QXmppCarbonManager* cm;
     QXmppMamManager* am;
     QXmppMucManager* mm;
@@ -151,16 +152,10 @@ private:
     QXmppUploadRequestManager* um;
     QXmppDiscoveryManager* dm;
     QXmppMessageReceiptManager* rcpm;
-    std::map<QString, Contact*> contacts;
-    std::map<QString, Conference*> conferences;
     bool reconnectScheduled;
     QTimer* reconnectTimer;
     
-    std::map<QString, QString> queuedContacts;
-    std::set<QString> outOfRosterContacts;
     std::set<QString> pendingVCardRequests;
-    std::map<QString, Shared::Message> pendingMessages;
-    std::deque<std::pair<QString, Shared::Message>> uploadingSlotsQueue;
     
     QString avatarHash;
     QString avatarType;
@@ -169,41 +164,18 @@ private:
     Shared::AccountPassword passwordType;
     
     MessageHandler* mh;
+    RosterHandler* rh;
     
 private slots:
     void onClientStateChange(QXmppClient::State state);
     void onClientError(QXmppClient::Error err);
     
-    void onRosterReceived();
-    void onRosterItemAdded(const QString& bareJid);
-    void onRosterItemChanged(const QString& bareJid);
-    void onRosterItemRemoved(const QString& bareJid);
-    void onRosterPresenceChanged(const QString& bareJid, const QString& resource);
-    
     void onPresenceReceived(const QXmppPresence& presence);
-    
+    void onContactNeedHistory(const QString& before, const QString& after, const QDateTime& at);
+
     void onMamMessageReceived(const QString& bareJid, const QXmppMessage& message);
     void onMamResultsReceived(const QString &queryId, const QXmppResultSetReply &resultSetReply, bool complete);
-    
-    void onMucRoomAdded(QXmppMucRoom* room);
-    void onMucJoinedChanged(bool joined);
-    void onMucAutoJoinChanged(bool autoJoin);
-    void onMucNickNameChanged(const QString& nickName);
-    void onMucSubjectChanged(const QString& subject);
-    void onMucAddParticipant(const QString& nickName, const QMap<QString, QVariant>& data);
-    void onMucChangeParticipant(const QString& nickName, const QMap<QString, QVariant>& data);
-    void onMucRemoveParticipant(const QString& nickName);
-    
-    void bookmarksReceived(const QXmppBookmarkSet& bookmarks);
-    
-    void onContactGroupAdded(const QString& group);
-    void onContactGroupRemoved(const QString& group);
-    void onContactNameChanged(const QString& name);
-    void onContactSubscriptionStateChanged(Shared::SubscriptionState state);
-    void onContactHistoryResponse(const std::list<Shared::Message>& list);
-    void onContactNeedHistory(const QString& before, const QString& after, const QDateTime& at);
-    void onContactAvatarChanged(Shared::Avatar, const QString& path);
-    
+
     void onMamLog(QXmppLogger::MessageType type, const QString &msg);
     
     void onVCardReceived(const QXmppVCardIq& card);
@@ -213,18 +185,7 @@ private slots:
     void onDiscoveryInfoReceived (const QXmppDiscoveryIq& info);
   
 private:
-    void addedAccount(const QString &bareJid);
-    void handleNewContact(Contact* contact);
-    void handleNewRosterItem(RosterItem* contact);
-    void handleNewConference(Conference* contact);
-    void addNewRoom(const QString& jid, const QString& nick, const QString& roomName, bool autoJoin);
-    void addToGroup(const QString& jid, const QString& group);
-    void removeFromGroup(const QString& jid, const QString& group);
-    Shared::SubscriptionState castSubscriptionState(QXmppRosterIq::Item::SubscriptionType qs) const;
-    void storeConferences();
-    void clearConferences();
     void cancelHistoryRequests();
-    RosterItem* getRosterItem(const QString& jid);
 };
 
 void initializeVCard(Shared::VCard& vCard, const QXmppVCardIq& card);
diff --git a/core/handlers/messagehandler.cpp b/core/handlers/messagehandler.cpp
index ae06694..0f0e09d 100644
--- a/core/handlers/messagehandler.cpp
+++ b/core/handlers/messagehandler.cpp
@@ -46,7 +46,7 @@ void Core::MessageHandler::onMessageReceived(const QXmppMessage& msg)
             std::map<QString, QString>::const_iterator itr = pendingStateMessages.find(id);
             if (itr != pendingStateMessages.end()) {
                 QString jid = itr->second;
-                RosterItem* cnt = acc->getRosterItem(jid);
+                RosterItem* cnt = acc->rh->getRosterItem(jid);
                 QMap<QString, QVariant> cData = {
                     {"state", static_cast<uint>(Shared::Message::State::error)},
                     {"errorText", msg.error().text()}
@@ -79,19 +79,9 @@ bool Core::MessageHandler::handleChatMessage(const QXmppMessage& msg, bool outgo
         Shared::Message sMsg(Shared::Message::chat);
         initializeMessage(sMsg, msg, outgoing, forwarded, guessing);
         QString jid = sMsg.getPenPalJid();
-        std::map<QString, Contact*>::const_iterator itr = acc->contacts.find(jid);
-        Contact* cnt;
-        if (itr != acc->contacts.end()) {
-            cnt = itr->second;
-        } else {
-            cnt = new Contact(jid, acc->name);
-            acc->contacts.insert(std::make_pair(jid, cnt));
-            acc->outOfRosterContacts.insert(jid);
-            cnt->setSubscriptionState(Shared::SubscriptionState::unknown);
-            emit acc->addContact(jid, "", QMap<QString, QVariant>({
-                {"state", QVariant::fromValue(Shared::SubscriptionState::unknown)}
-            }));
-            acc->handleNewContact(cnt);
+        Contact* cnt = acc->rh->getContact(jid);
+        if (cnt == 0) {
+            cnt = acc->rh->addOutOfRosterContact(jid);
         }
         if (outgoing) {
             if (forwarded) {
@@ -127,11 +117,8 @@ bool Core::MessageHandler::handleGroupMessage(const QXmppMessage& msg, bool outg
         Shared::Message sMsg(Shared::Message::groupChat);
         initializeMessage(sMsg, msg, outgoing, forwarded, guessing);
         QString jid = sMsg.getPenPalJid();
-        std::map<QString, Conference*>::const_iterator itr = acc->conferences.find(jid);
-        Conference* cnt;
-        if (itr != acc->conferences.end()) {
-            cnt = itr->second;
-        } else {
+        Conference* cnt = acc->rh->getConference(jid);
+        if (cnt == 0) {
             return false;
         }
         
@@ -236,7 +223,7 @@ void Core::MessageHandler::onReceiptReceived(const QString& jid, const QString&
     std::map<QString, QString>::const_iterator itr = pendingStateMessages.find(id);
     if (itr != pendingStateMessages.end()) {
         QMap<QString, QVariant> cData = {{"state", static_cast<uint>(Shared::Message::State::delivered)}};
-        RosterItem* ri = acc->getRosterItem(itr->second);
+        RosterItem* ri = acc->rh->getRosterItem(itr->second);
         if (ri != 0) {
             ri->changeMessage(id, cData);
         }
@@ -249,7 +236,7 @@ void Core::MessageHandler::sendMessage(Shared::Message data)
 {
     QString jid = data.getPenPalJid();
     QString id = data.getId();
-    RosterItem* ri = acc->getRosterItem(jid);
+    RosterItem* ri = acc->rh->getRosterItem(jid);
     if (acc->state == Shared::ConnectionState::connected) {
         QXmppMessage msg(acc->getFullJid(), data.getTo(), data.getBody(), data.getThread());
         
diff --git a/core/handlers/messagehandler.h b/core/handlers/messagehandler.h
index 54b8331..be1545f 100644
--- a/core/handlers/messagehandler.h
+++ b/core/handlers/messagehandler.h
@@ -27,7 +27,7 @@
 #include <QXmppMessage.h>
 #include <QXmppHttpUploadIq.h>
 
-#include "shared/message.h"
+#include <shared/message.h>
 
 namespace Core {
 
diff --git a/core/handlers/rosterhandler.cpp b/core/handlers/rosterhandler.cpp
new file mode 100644
index 0000000..ea1812d
--- /dev/null
+++ b/core/handlers/rosterhandler.cpp
@@ -0,0 +1,583 @@
+/*
+ * Squawk messenger. 
+ * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+ * 
+ * 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 "rosterhandler.h"
+#include "core/account.h"
+
+Core::RosterHandler::RosterHandler(Core::Account* account):
+    QObject(),
+    acc(account),
+    contacts(),
+    conferences(),
+    groups(),
+    queuedContacts(),
+    outOfRosterContacts()
+{
+    connect(acc->rm, &QXmppRosterManager::rosterReceived, this, &RosterHandler::onRosterReceived);
+    connect(acc->rm, &QXmppRosterManager::itemAdded, this, &RosterHandler::onRosterItemAdded);
+    connect(acc->rm, &QXmppRosterManager::itemRemoved, this, &RosterHandler::onRosterItemRemoved);
+    connect(acc->rm, &QXmppRosterManager::itemChanged, this, &RosterHandler::onRosterItemChanged);
+    
+    
+    connect(acc->mm, &QXmppMucManager::roomAdded, this, &RosterHandler::onMucRoomAdded);
+    connect(acc->bm, &QXmppBookmarkManager::bookmarksReceived, this, &RosterHandler::bookmarksReceived);
+}
+
+Core::RosterHandler::~RosterHandler()
+{
+    for (std::map<QString, Contact*>::const_iterator itr = contacts.begin(), end = contacts.end(); itr != end; ++itr) {
+        delete itr->second;
+    }
+    
+    for (std::map<QString, Conference*>::const_iterator itr = conferences.begin(), end = conferences.end(); itr != end; ++itr) {
+        delete itr->second;
+    }
+}
+
+void Core::RosterHandler::onRosterReceived()
+{
+    acc->vm->requestClientVCard();         //TODO need to make sure server actually supports vCards
+    acc->ownVCardRequestInProgress = true;
+    
+    QStringList bj = acc->rm->getRosterBareJids();
+    for (int i = 0; i < bj.size(); ++i) {
+        const QString& jid = bj[i];
+        addedAccount(jid);
+    }
+}
+
+void Core::RosterHandler::onRosterItemAdded(const QString& bareJid)
+{
+    addedAccount(bareJid);
+    std::map<QString, QString>::const_iterator itr = queuedContacts.find(bareJid);
+    if (itr != queuedContacts.end()) {
+        acc->rm->subscribe(bareJid, itr->second);
+        queuedContacts.erase(itr);
+    }
+}
+
+void Core::RosterHandler::addedAccount(const QString& jid)
+{
+    std::map<QString, Contact*>::const_iterator itr = contacts.find(jid);
+    QXmppRosterIq::Item re = acc->rm->getRosterEntry(jid);
+    Contact* contact;
+    bool newContact = false;
+    if (itr == contacts.end()) {
+        newContact = true;
+        contact = new Contact(jid, acc->name);
+        contacts.insert(std::make_pair(jid, contact));
+        
+    } else {
+        contact = itr->second;
+    }
+    
+    QSet<QString> gr = re.groups();
+    Shared::SubscriptionState state = castSubscriptionState(re.subscriptionType());
+    contact->setGroups(gr);
+    contact->setSubscriptionState(state);
+    contact->setName(re.name());
+    
+    if (newContact) {
+        QMap<QString, QVariant> cData({
+            {"name", re.name()},
+            {"state", QVariant::fromValue(state)}
+        });
+        
+        careAboutAvatar(contact, cData);
+        int grCount = 0;
+        for (QSet<QString>::const_iterator itr = gr.begin(), end = gr.end(); itr != end; ++itr) {
+            const QString& groupName = *itr;
+            addToGroup(jid, groupName);
+            emit acc->addContact(jid, groupName, cData);
+            grCount++;
+        }
+        
+        if (grCount == 0) {
+            emit acc->addContact(jid, "", cData);
+        }
+        handleNewContact(contact);
+    }
+}
+
+void Core::RosterHandler::addNewRoom(const QString& jid, const QString& nick, const QString& roomName, bool autoJoin)
+{
+    QXmppMucRoom* room = acc->mm->addRoom(jid);
+    QString lNick = nick;
+    if (lNick.size() == 0) {
+        lNick = acc->getName();
+    }
+    Conference* conf = new Conference(jid, acc->getName(), autoJoin, roomName, lNick, room);
+    conferences.insert(std::make_pair(jid, conf));
+    
+    handleNewConference(conf);
+    
+    QMap<QString, QVariant> cData = {
+        {"autoJoin", conf->getAutoJoin()},
+        {"joined", conf->getJoined()},
+        {"nick", conf->getNick()},
+        {"name", conf->getName()},
+        {"avatars", conf->getAllAvatars()}
+    };
+    careAboutAvatar(conf, cData);
+    emit acc->addRoom(jid, cData);
+}
+
+void Core::RosterHandler::careAboutAvatar(Core::RosterItem* item, QMap<QString, QVariant>& data)
+{
+    Archive::AvatarInfo info;
+    bool hasAvatar = item->readAvatarInfo(info);
+    if (hasAvatar) {
+        if (info.autogenerated) {
+            data.insert("avatarState", QVariant::fromValue(Shared::Avatar::autocreated));
+        } else {
+            data.insert("avatarState", QVariant::fromValue(Shared::Avatar::valid));
+        }
+        data.insert("avatarPath", item->avatarPath() + "." + info.type);
+    } else {
+        data.insert("avatarState", QVariant::fromValue(Shared::Avatar::empty));
+        data.insert("avatarPath", "");
+        acc->requestVCard(item->jid);
+    }
+}
+
+void Core::RosterHandler::addContactRequest(const QString& jid, const QString& name, const QSet<QString>& groups)
+{
+    if (acc->state == Shared::ConnectionState::connected) {
+        std::map<QString, QString>::const_iterator itr = queuedContacts.find(jid);
+        if (itr != queuedContacts.end()) {
+            qDebug() << "An attempt to add contact " << jid << " to account " << acc->name << " but the account is already queued for adding, skipping";
+        } else {
+            queuedContacts.insert(std::make_pair(jid, ""));     //TODO need to add reason here;
+            acc->rm->addItem(jid, name, groups);
+        }
+    } else {
+        qDebug() << "An attempt to add contact " << jid << " to account " << acc->name << " but the account is not in the connected state, skipping";
+    }
+}
+
+void Core::RosterHandler::removeContactRequest(const QString& jid)
+{
+    if (acc->state == Shared::ConnectionState::connected) {
+        std::set<QString>::const_iterator itr = outOfRosterContacts.find(jid);
+        if (itr != outOfRosterContacts.end()) {
+            outOfRosterContacts.erase(itr);
+            onRosterItemRemoved(jid);
+        } else {
+            acc->rm->removeItem(jid);
+        }
+    } else {
+        qDebug() << "An attempt to remove contact " << jid << " from account " << acc->name << " but the account is not in the connected state, skipping";
+    }
+}
+
+void Core::RosterHandler::handleNewRosterItem(Core::RosterItem* contact)
+{
+    connect(contact, &RosterItem::needHistory, this->acc, &Account::onContactNeedHistory);
+    connect(contact, &RosterItem::historyResponse, this, &RosterHandler::onContactHistoryResponse);
+    connect(contact, &RosterItem::nameChanged, this, &RosterHandler::onContactNameChanged);
+    connect(contact, &RosterItem::avatarChanged, this, &RosterHandler::onContactAvatarChanged);
+    connect(contact, &RosterItem::requestVCard, this->acc, &Account::requestVCard);
+}
+
+void Core::RosterHandler::handleNewContact(Core::Contact* contact)
+{
+    handleNewRosterItem(contact);
+    connect(contact, &Contact::groupAdded, this, &RosterHandler::onContactGroupAdded);
+    connect(contact, &Contact::groupRemoved, this, &RosterHandler::onContactGroupRemoved);
+    connect(contact, &Contact::subscriptionStateChanged, this, &RosterHandler::onContactSubscriptionStateChanged);
+}
+
+void Core::RosterHandler::handleNewConference(Core::Conference* contact)
+{
+    handleNewRosterItem(contact);
+    connect(contact, &Conference::nickChanged, this, &RosterHandler::onMucNickNameChanged);
+    connect(contact, &Conference::subjectChanged, this, &RosterHandler::onMucSubjectChanged);
+    connect(contact, &Conference::joinedChanged, this, &RosterHandler::onMucJoinedChanged);
+    connect(contact, &Conference::autoJoinChanged, this, &RosterHandler::onMucAutoJoinChanged);
+    connect(contact, &Conference::addParticipant, this, &RosterHandler::onMucAddParticipant);
+    connect(contact, &Conference::changeParticipant, this, &RosterHandler::onMucChangeParticipant);
+    connect(contact, &Conference::removeParticipant, this, &RosterHandler::onMucRemoveParticipant);
+}
+
+void Core::RosterHandler::onMucAddParticipant(const QString& nickName, const QMap<QString, QVariant>& data)
+{
+    Conference* room = static_cast<Conference*>(sender());
+    emit acc->addRoomParticipant(room->jid, nickName, data);
+}
+
+void Core::RosterHandler::onMucChangeParticipant(const QString& nickName, const QMap<QString, QVariant>& data)
+{
+    Conference* room = static_cast<Conference*>(sender());
+    emit acc->changeRoomParticipant(room->jid, nickName, data);
+}
+
+void Core::RosterHandler::onMucRemoveParticipant(const QString& nickName)
+{
+    Conference* room = static_cast<Conference*>(sender());
+    emit acc->removeRoomParticipant(room->jid, nickName);
+}
+
+void Core::RosterHandler::onMucSubjectChanged(const QString& subject)
+{
+    Conference* room = static_cast<Conference*>(sender());
+    emit acc->changeRoom(room->jid, {
+        {"subject", subject}
+    });
+}
+
+void Core::RosterHandler::onContactGroupAdded(const QString& group)
+{
+    Contact* contact = static_cast<Contact*>(sender());
+    if (contact->groupsCount() == 1) {
+        // not sure i need to handle it here, the situation with grouped and ungrouped contacts handled on the client anyway
+    }
+    
+    QMap<QString, QVariant> cData({
+        {"name", contact->getName()},
+        {"state", QVariant::fromValue(contact->getSubscriptionState())}
+    });
+    addToGroup(contact->jid, group);
+    emit acc->addContact(contact->jid, group, cData);
+}
+
+void Core::RosterHandler::onContactGroupRemoved(const QString& group)
+{
+    Contact* contact = static_cast<Contact*>(sender());
+    if (contact->groupsCount() == 0) {
+        // not sure i need to handle it here, the situation with grouped and ungrouped contacts handled on the client anyway
+    }
+    
+    emit acc->removeContact(contact->jid, group);
+    removeFromGroup(contact->jid, group);
+}
+
+void Core::RosterHandler::onContactNameChanged(const QString& cname)
+{
+    Contact* contact = static_cast<Contact*>(sender());
+    QMap<QString, QVariant> cData({
+        {"name", cname},
+    });
+    emit acc->changeContact(contact->jid, cData);
+}
+
+void Core::RosterHandler::onContactSubscriptionStateChanged(Shared::SubscriptionState cstate)
+{
+    Contact* contact = static_cast<Contact*>(sender());
+    QMap<QString, QVariant> cData({
+        {"state", QVariant::fromValue(cstate)},
+    });
+    emit acc->changeContact(contact->jid, cData);
+}
+
+void Core::RosterHandler::addToGroup(const QString& jid, const QString& group)
+{
+    std::map<QString, std::set<QString>>::iterator gItr = groups.find(group);
+    if (gItr == groups.end()) {
+        gItr = groups.insert(std::make_pair(group, std::set<QString>())).first;
+        emit acc->addGroup(group);
+    }
+    gItr->second.insert(jid);
+}
+
+void Core::RosterHandler::removeFromGroup(const QString& jid, const QString& group)
+{
+    QSet<QString> toRemove;
+    std::map<QString, std::set<QString>>::iterator itr = groups.find(group);
+    if (itr == groups.end()) {
+        qDebug() << "An attempt to remove contact" << jid << "of account" << acc->name << "from non existing group" << group << ", skipping";
+        return;
+    }
+    std::set<QString> contacts = itr->second;
+    std::set<QString>::const_iterator cItr = contacts.find(jid);
+    if (cItr != contacts.end()) {
+        contacts.erase(cItr);
+        if (contacts.size() == 0) {
+            emit acc->removeGroup(group);
+            groups.erase(group);
+        }
+    }
+}
+
+void Core::RosterHandler::onContactHistoryResponse(const std::list<Shared::Message>& list)
+{
+    RosterItem* contact = static_cast<RosterItem*>(sender());
+    
+    qDebug() << "Collected history for contact " << contact->jid << list.size() << "elements";
+    emit acc->responseArchive(contact->jid, list);
+}
+
+Core::RosterItem * Core::RosterHandler::getRosterItem(const QString& jid)
+{
+    RosterItem* item = 0;
+    std::map<QString, Contact*>::const_iterator citr = contacts.find(jid);
+    if (citr != contacts.end()) {
+        item = citr->second;
+    } else {
+        std::map<QString, Conference*>::const_iterator coitr = conferences.find(jid);
+        if (coitr != conferences.end()) {
+            item = coitr->second;
+        }
+    }
+    return item;
+}
+
+Core::Conference * Core::RosterHandler::getConference(const QString& jid)
+{
+    Conference* item = 0;
+    std::map<QString, Conference*>::const_iterator coitr = conferences.find(jid);
+    if (coitr != conferences.end()) {
+        item = coitr->second;
+    }
+    return item;
+}
+
+Core::Contact * Core::RosterHandler::getContact(const QString& jid)
+{
+    Contact* item = 0;
+    std::map<QString, Contact*>::const_iterator citr = contacts.find(jid);
+    if (citr != contacts.end()) {
+        item = citr->second;
+    }
+    return item;
+}
+
+Core::Contact * Core::RosterHandler::addOutOfRosterContact(const QString& jid)
+{
+    Contact* cnt = new Contact(jid, acc->name);
+    contacts.insert(std::make_pair(jid, cnt));
+    outOfRosterContacts.insert(jid);
+    cnt->setSubscriptionState(Shared::SubscriptionState::unknown);
+    emit acc->addContact(jid, "", QMap<QString, QVariant>({
+        {"state", QVariant::fromValue(Shared::SubscriptionState::unknown)}
+    }));
+    handleNewContact(cnt);
+    return cnt;
+}
+
+void Core::RosterHandler::onRosterItemChanged(const QString& bareJid)
+{
+    std::map<QString, Contact*>::const_iterator itr = contacts.find(bareJid);
+    if (itr == contacts.end()) {
+        qDebug() << "An attempt to change non existing contact" << bareJid << "from account" << acc->name << ", skipping";
+        return;
+    }
+    Contact* contact = itr->second;
+    QXmppRosterIq::Item re = acc->rm->getRosterEntry(bareJid);
+    
+    Shared::SubscriptionState state = castSubscriptionState(re.subscriptionType());
+    
+    contact->setGroups(re.groups());
+    contact->setSubscriptionState(state);
+    contact->setName(re.name());
+}
+
+void Core::RosterHandler::onRosterItemRemoved(const QString& bareJid)
+{
+    std::map<QString, Contact*>::const_iterator itr = contacts.find(bareJid);
+    if (itr == contacts.end()) {
+        qDebug() << "An attempt to remove non existing contact" << bareJid << "from account" << acc->name << ", skipping";
+        return;
+    }
+    Contact* contact = itr->second;
+    contacts.erase(itr);
+    QSet<QString> cGroups = contact->getGroups();
+    for (QSet<QString>::const_iterator itr = cGroups.begin(), end = cGroups.end(); itr != end; ++itr) {
+        removeFromGroup(bareJid, *itr);
+    }
+    emit acc->removeContact(bareJid);
+    
+    contact->deleteLater();
+}
+
+void Core::RosterHandler::onMucRoomAdded(QXmppMucRoom* room)
+{
+    qDebug()    << "room" << room->jid() << "added with name" << room->name() 
+                << ", account" << acc->getName() << "joined:" << room->isJoined(); 
+}
+
+void Core::RosterHandler::bookmarksReceived(const QXmppBookmarkSet& bookmarks)
+{
+    QList<QXmppBookmarkConference> confs = bookmarks.conferences();
+    for (QList<QXmppBookmarkConference>::const_iterator itr = confs.begin(), end = confs.end(); itr != end; ++itr) {
+        const QXmppBookmarkConference& c = *itr;
+        
+        QString jid = c.jid();
+        std::map<QString, Conference*>::const_iterator cItr = conferences.find(jid);
+        if (cItr == conferences.end()) {
+            addNewRoom(jid, c.nickName(), c.name(), c.autoJoin());
+        } else {
+            qDebug() << "Received a bookmark to a MUC " << jid << " which is already booked by another bookmark, skipping";
+        }
+    }
+}
+
+void Core::RosterHandler::onMucJoinedChanged(bool joined)
+{
+    Conference* room = static_cast<Conference*>(sender());
+    emit acc->changeRoom(room->jid, {
+        {"joined", joined}
+    });
+}
+
+void Core::RosterHandler::onMucAutoJoinChanged(bool autoJoin)
+{
+    storeConferences();
+    Conference* room = static_cast<Conference*>(sender());
+    emit acc->changeRoom(room->jid, {
+        {"autoJoin", autoJoin}
+    });
+}
+
+void Core::RosterHandler::onMucNickNameChanged(const QString& nickName)
+{
+    storeConferences();
+    Conference* room = static_cast<Conference*>(sender());
+    emit acc->changeRoom(room->jid, {
+        {"nick", nickName}
+    });
+}
+
+Shared::SubscriptionState Core::RosterHandler::castSubscriptionState(QXmppRosterIq::Item::SubscriptionType qs)
+{
+    Shared::SubscriptionState state;
+    if (qs == QXmppRosterIq::Item::NotSet) {
+        state = Shared::SubscriptionState::unknown;
+    } else {
+        state = static_cast<Shared::SubscriptionState>(qs);
+    }
+    return state;
+}
+
+void Core::RosterHandler::storeConferences()
+{
+    QXmppBookmarkSet bms = acc->bm->bookmarks();
+    QList<QXmppBookmarkConference> confs;
+    for (std::map<QString, Conference*>::const_iterator itr = conferences.begin(), end = conferences.end(); itr != end; ++itr) {
+        Conference* conference = itr->second;
+        QXmppBookmarkConference conf;
+        conf.setJid(conference->jid);
+        conf.setName(conference->getName());
+        conf.setNickName(conference->getNick());
+        conf.setAutoJoin(conference->getAutoJoin());
+        confs.push_back(conf);
+    }
+    bms.setConferences(confs);
+    acc->bm->setBookmarks(bms);
+}
+
+void Core::RosterHandler::clearConferences()
+{
+    for (std::map<QString, Conference*>::const_iterator itr = conferences.begin(), end = conferences.end(); itr != end; itr++) {
+        itr->second->deleteLater();
+        emit acc->removeRoom(itr->first);
+    }
+    conferences.clear();
+}
+
+void Core::RosterHandler::removeRoomRequest(const QString& jid)
+{
+    std::map<QString, Conference*>::const_iterator itr = conferences.find(jid);
+    if (itr == conferences.end()) {
+        qDebug() << "An attempt to remove non existing room" << jid << "from account" << acc->name << ", skipping";
+    }
+    itr->second->deleteLater();
+    conferences.erase(itr);
+    emit acc->removeRoom(jid);
+    storeConferences();
+}
+
+void Core::RosterHandler::addRoomRequest(const QString& jid, const QString& nick, const QString& password, bool autoJoin)
+{
+    std::map<QString, Conference*>::const_iterator cItr = conferences.find(jid);
+    if (cItr == conferences.end()) {
+        addNewRoom(jid, nick, "", autoJoin);
+        storeConferences();
+    } else {
+        qDebug() << "An attempt to add a MUC " << jid << " which is already present in the rester, skipping";
+    }
+}
+
+void Core::RosterHandler::addContactToGroupRequest(const QString& jid, const QString& groupName)
+{
+    std::map<QString, Contact*>::const_iterator itr = contacts.find(jid);
+    if (itr == contacts.end()) {
+        qDebug()    << "An attempt to add non existing contact" << jid << "of account" 
+                    << acc->name << "to the group" << groupName << ", skipping";
+    } else {
+        QXmppRosterIq::Item item = acc->rm->getRosterEntry(jid);
+        QSet<QString> groups = item.groups();
+        if (groups.find(groupName) == groups.end()) {           //TODO need to change it, I guess that sort of code is better in qxmpp lib
+            groups.insert(groupName);
+            item.setGroups(groups);
+            
+            QXmppRosterIq iq;
+            iq.setType(QXmppIq::Set);
+            iq.addItem(item);
+            acc->client.sendPacket(iq);
+        } else {
+            qDebug()    << "An attempt to add contact" << jid << "of account" 
+                        << acc->name << "to the group" << groupName << "but it's already in that group, skipping";
+        }
+    }
+}
+
+void Core::RosterHandler::removeContactFromGroupRequest(const QString& jid, const QString& groupName)
+{
+    std::map<QString, Contact*>::const_iterator itr = contacts.find(jid);
+    if (itr == contacts.end()) {
+        qDebug()    << "An attempt to remove non existing contact" << jid << "of account" 
+                    << acc->name << "from the group" << groupName << ", skipping";
+    } else {
+        QXmppRosterIq::Item item = acc->rm->getRosterEntry(jid);
+        QSet<QString> groups = item.groups();
+        QSet<QString>::const_iterator gItr = groups.find(groupName);
+        if (gItr != groups.end()) {
+            groups.erase(gItr);
+            item.setGroups(groups);
+            
+            QXmppRosterIq iq;
+            iq.setType(QXmppIq::Set);
+            iq.addItem(item);
+            acc->client.sendPacket(iq);
+        } else {
+            qDebug()    << "An attempt to remove contact" << jid << "of account" 
+                        << acc->name << "from the group" << groupName << "but it's not in that group, skipping";
+        }
+    }
+}
+
+void Core::RosterHandler::onContactAvatarChanged(Shared::Avatar type, const QString& path)
+{
+    RosterItem* item = static_cast<RosterItem*>(sender());
+    QMap<QString, QVariant> cData({
+        {"avatarState", static_cast<uint>(type)},
+        {"avatarPath", path}
+    });
+    
+    emit acc->changeContact(item->jid, cData);
+}
+
+void Core::RosterHandler::cancelHistoryRequests()
+{
+    for (const std::pair<const QString, Conference*>& pair : conferences) {
+        pair.second->clearArchiveRequests();
+    }
+    for (const std::pair<const QString, Contact*>& pair : contacts) {
+        pair.second->clearArchiveRequests();
+    }
+}
diff --git a/core/handlers/rosterhandler.h b/core/handlers/rosterhandler.h
new file mode 100644
index 0000000..c8eeafa
--- /dev/null
+++ b/core/handlers/rosterhandler.h
@@ -0,0 +1,115 @@
+/*
+ * Squawk messenger. 
+ * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+ * 
+ * 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/>.
+ */
+
+#ifndef CORE_ROSTERHANDLER_H
+#define CORE_ROSTERHANDLER_H
+
+#include <QObject>
+#include <QSet>
+#include <QString>
+#include <QDateTime>
+
+#include <list>
+#include <map>
+#include <set>
+
+#include <QXmppBookmarkSet.h>
+#include <QXmppMucManager.h>
+#include <QXmppRosterIq.h>
+
+#include <shared/message.h>
+#include <core/contact.h>
+#include <core/conference.h>
+
+namespace Core {
+
+    
+class Account;
+    
+class RosterHandler : public QObject
+{
+    Q_OBJECT
+public:
+    RosterHandler(Account* account);
+    ~RosterHandler();
+    
+    void addContactRequest(const QString& jid, const QString& name, const QSet<QString>& groups);
+    void removeContactRequest(const QString& jid);
+    void addContactToGroupRequest(const QString& jid, const QString& groupName);
+    void removeContactFromGroupRequest(const QString& jid, const QString& groupName);
+    
+    void removeRoomRequest(const QString& jid);
+    void addRoomRequest(const QString& jid, const QString& nick, const QString& password, bool autoJoin);
+    void cancelHistoryRequests();
+    
+    Core::Contact* getContact(const QString& jid);
+    Core::Conference* getConference(const QString& jid);
+    Core::RosterItem* getRosterItem(const QString& jid);
+    Core::Contact* addOutOfRosterContact(const QString& jid);
+    
+    void storeConferences();
+    void clearConferences();
+    
+private slots:
+    void onRosterReceived();
+    void onRosterItemAdded(const QString& bareJid);
+    void onRosterItemChanged(const QString& bareJid);
+    void onRosterItemRemoved(const QString& bareJid);
+    
+    void onMucRoomAdded(QXmppMucRoom* room);
+    void onMucJoinedChanged(bool joined);
+    void onMucAutoJoinChanged(bool autoJoin);
+    void onMucNickNameChanged(const QString& nickName);
+    void onMucSubjectChanged(const QString& subject);
+    void onMucAddParticipant(const QString& nickName, const QMap<QString, QVariant>& data);
+    void onMucChangeParticipant(const QString& nickName, const QMap<QString, QVariant>& data);
+    void onMucRemoveParticipant(const QString& nickName);
+    
+    void bookmarksReceived(const QXmppBookmarkSet& bookmarks);
+    
+    void onContactGroupAdded(const QString& group);
+    void onContactGroupRemoved(const QString& group);
+    void onContactNameChanged(const QString& name);
+    void onContactSubscriptionStateChanged(Shared::SubscriptionState state);
+    void onContactHistoryResponse(const std::list<Shared::Message>& list);
+    void onContactAvatarChanged(Shared::Avatar, const QString& path);
+    
+private:
+    void addNewRoom(const QString& jid, const QString& nick, const QString& roomName, bool autoJoin);
+    void addToGroup(const QString& jid, const QString& group);
+    void removeFromGroup(const QString& jid, const QString& group);
+    void addedAccount(const QString &bareJid);
+    void handleNewRosterItem(Core::RosterItem* contact);
+    void handleNewContact(Core::Contact* contact);
+    void handleNewConference(Core::Conference* contact);
+    void careAboutAvatar(Core::RosterItem* item, QMap<QString, QVariant>& data);
+    
+    static Shared::SubscriptionState castSubscriptionState(QXmppRosterIq::Item::SubscriptionType qs);
+    
+private:
+    Account* acc;
+    std::map<QString, Contact*> contacts;
+    std::map<QString, Conference*> conferences;
+    std::map<QString, std::set<QString>> groups;
+    std::map<QString, QString> queuedContacts;
+    std::set<QString> outOfRosterContacts;
+};
+
+}
+
+#endif // CORE_ROSTERHANDLER_H
diff --git a/external/simpleCrypt/simplecrypt.cpp b/external/simpleCrypt/simplecrypt.cpp
index 9bbec90..19a96be 100644
--- a/external/simpleCrypt/simplecrypt.cpp
+++ b/external/simpleCrypt/simplecrypt.cpp
@@ -36,10 +36,7 @@ SimpleCrypt::SimpleCrypt():
 m_key(0),
 m_compressionMode(CompressionAuto),
 m_protectionMode(ProtectionChecksum),
-m_lastError(ErrorNoError)
-{
-    qsrand(uint(QDateTime::currentMSecsSinceEpoch() & 0xFFFF));
-}
+m_lastError(ErrorNoError) {}
 
 SimpleCrypt::SimpleCrypt(quint64 key):
 m_key(key),
@@ -47,7 +44,6 @@ m_compressionMode(CompressionAuto),
 m_protectionMode(ProtectionChecksum),
 m_lastError(ErrorNoError)
 {
-    qsrand(uint(QDateTime::currentMSecsSinceEpoch() & 0xFFFF));
     splitKey();
 }
 
@@ -113,7 +109,7 @@ QByteArray SimpleCrypt::encryptToByteArray(QByteArray plaintext)
     }
     
     //prepend a random char to the string
-    char randomChar = char(qrand() & 0xFF);
+    char randomChar = char(QRandomGenerator::global()->generate() & 0xFF);
     ba = randomChar + integrityProtection + ba;
     
     int pos(0);
diff --git a/external/simpleCrypt/simplecrypt.h b/external/simpleCrypt/simplecrypt.h
index 2a5906a..d75bdc2 100644
--- a/external/simpleCrypt/simplecrypt.h
+++ b/external/simpleCrypt/simplecrypt.h
@@ -30,6 +30,7 @@
 #include <QString>
 #include <QVector>
 #include <QFlags>
+#include <QRandomGenerator>
 
 /**
  @ short Simple encrypt*ion and decryption of strings and byte arrays

From 0dcfc5eedc580e134206df0910cd1d7ad1589a01 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Sun, 21 Jun 2020 01:26:30 +0300
Subject: [PATCH 076/281] some better cleanup and restore state on connect
 disconnect, workaround for that wired undefined condition error on every
 other reconnection

---
 core/account.cpp                | 42 ++++++++++++++++++++++++---------
 core/account.h                  |  3 ++-
 core/handlers/rosterhandler.cpp | 10 ++++++--
 core/handlers/rosterhandler.h   |  2 +-
 core/rosteritem.cpp             | 19 ++++++++++++++-
 core/rosteritem.h               |  1 +
 6 files changed, 61 insertions(+), 16 deletions(-)

diff --git a/core/account.cpp b/core/account.cpp
index 59a05fd..5ed8873 100644
--- a/core/account.cpp
+++ b/core/account.cpp
@@ -25,7 +25,7 @@ using namespace Core;
 Account::Account(const QString& p_login, const QString& p_server, const QString& p_password, const QString& p_name, NetworkAccess* p_net, QObject* parent):
     QObject(parent),
     name(p_name),
-    achiveQueries(),
+    archiveQueries(),
     client(),
     config(),
     presence(),
@@ -137,7 +137,7 @@ Account::Account(const QString& p_login, const QString& p_server, const QString&
     }
     
     reconnectTimer->setSingleShot(true);
-    QObject::connect(reconnectTimer, &QTimer::timeout, this, &Account::connect);
+    QObject::connect(reconnectTimer, &QTimer::timeout, this, &Account::onReconnectTimer);
     
 //     QXmppLogger* logger = new QXmppLogger(this);
 //     logger->setLoggingType(QXmppLogger::SignalLogging);
@@ -178,6 +178,10 @@ Shared::ConnectionState Core::Account::getState() const
 
 void Core::Account::connect()
 {
+    if (reconnectScheduled) {
+        reconnectScheduled = false;
+        reconnectTimer->stop();
+    }
     if (state == Shared::ConnectionState::disconnected) {
         qDebug() << presence.availableStatusType();
         client.connectToServer(config, presence);
@@ -186,6 +190,14 @@ void Core::Account::connect()
     }
 }
 
+void Core::Account::onReconnectTimer()
+{
+    if (reconnectScheduled) {
+        reconnectScheduled = false;
+        connect();
+    }
+}
+
 void Core::Account::disconnect()
 {
     if (reconnectScheduled) {
@@ -193,7 +205,7 @@ void Core::Account::disconnect()
         reconnectTimer->stop();
     }
     if (state != Shared::ConnectionState::disconnected) {
-        rh->clearConferences();
+        //rh->clearConferences();
         client.disconnectFromServer();
     }
 }
@@ -397,8 +409,8 @@ void Core::Account::sendMessage(const Shared::Message& data, const QString& path
 void Core::Account::onMamMessageReceived(const QString& queryId, const QXmppMessage& msg)
 {
     if (msg.id().size() > 0 && (msg.body().size() > 0 || msg.outOfBandUrl().size() > 0)) {
-        std::map<QString, QString>::const_iterator itr = achiveQueries.find(queryId);
-        if (itr != achiveQueries.end()) {
+        std::map<QString, QString>::const_iterator itr = archiveQueries.find(queryId);
+        if (itr != archiveQueries.end()) {
             QString jid = itr->second;
             RosterItem* item = rh->getRosterItem(jid);
             
@@ -469,15 +481,15 @@ void Core::Account::onContactNeedHistory(const QString& before, const QString& a
     }
     
     QString q = am->retrieveArchivedMessages(to, "", with, start, QDateTime(), query);
-    achiveQueries.insert(std::make_pair(q, contact->jid));
+    archiveQueries.insert(std::make_pair(q, contact->jid));
 }
 
 void Core::Account::onMamResultsReceived(const QString& queryId, const QXmppResultSetReply& resultSetReply, bool complete)
 {
-    std::map<QString, QString>::const_iterator itr = achiveQueries.find(queryId);
-    if (itr != achiveQueries.end()) {
+    std::map<QString, QString>::const_iterator itr = archiveQueries.find(queryId);
+    if (itr != archiveQueries.end()) {
         QString jid = itr->second;
-        achiveQueries.erase(itr);
+        archiveQueries.erase(itr);
         
         RosterItem* ri = rh->getRosterItem(jid);
         
@@ -570,6 +582,8 @@ void Core::Account::onClientError(QXmppClient::Error err)
                     break;
                 case QXmppStanza::Error::UndefinedCondition:
                     errorText = "Undefined condition";
+                    reconnectScheduled = true;
+                    reconnectTimer->start(500);     //let's reconnect here just for now, it seems to be something broken in QXMPP
                     break;
                 case QXmppStanza::Error::UnexpectedRequest:
                     errorText = "Unexpected request";
@@ -882,7 +896,13 @@ void Core::Account::onDiscoveryInfoReceived(const QXmppDiscoveryIq& info)
 
 void Core::Account::cancelHistoryRequests()
 {
-    rh->cancelHistoryRequests();
-    achiveQueries.clear();
+    rh->handleOffline();
+    archiveQueries.clear();
+    pendingVCardRequests.clear();
+    Shared::VCard vCard;                    //just to show, that there is now more pending request
+    for (const QString& jid : pendingVCardRequests) {
+        emit receivedVCard(jid, vCard);     //need to show it better in the future, like with an error
+    }
+    ownVCardRequestInProgress = false;
 }
 
diff --git a/core/account.h b/core/account.h
index 9af4d7b..d7ca113 100644
--- a/core/account.h
+++ b/core/account.h
@@ -138,7 +138,7 @@ signals:
     
 private:
     QString name;
-    std::map<QString, QString> achiveQueries;
+    std::map<QString, QString> archiveQueries;
     QXmppClient client;
     QXmppConfiguration config;
     QXmppPresence presence;
@@ -186,6 +186,7 @@ private slots:
   
 private:
     void cancelHistoryRequests();
+    void onReconnectTimer();
 };
 
 void initializeVCard(Shared::VCard& vCard, const QXmppVCardIq& card);
diff --git a/core/handlers/rosterhandler.cpp b/core/handlers/rosterhandler.cpp
index ea1812d..44b2bda 100644
--- a/core/handlers/rosterhandler.cpp
+++ b/core/handlers/rosterhandler.cpp
@@ -421,7 +421,11 @@ void Core::RosterHandler::bookmarksReceived(const QXmppBookmarkSet& bookmarks)
         if (cItr == conferences.end()) {
             addNewRoom(jid, c.nickName(), c.name(), c.autoJoin());
         } else {
-            qDebug() << "Received a bookmark to a MUC " << jid << " which is already booked by another bookmark, skipping";
+            if (c.autoJoin()) {
+                cItr->second->setJoined(true);
+            } else {
+                cItr->second->setAutoJoin(false);
+            }
         }
     }
 }
@@ -572,12 +576,14 @@ void Core::RosterHandler::onContactAvatarChanged(Shared::Avatar type, const QStr
     emit acc->changeContact(item->jid, cData);
 }
 
-void Core::RosterHandler::cancelHistoryRequests()
+void Core::RosterHandler::handleOffline()
 {
     for (const std::pair<const QString, Conference*>& pair : conferences) {
         pair.second->clearArchiveRequests();
+        pair.second->downgradeDatabaseState();
     }
     for (const std::pair<const QString, Contact*>& pair : contacts) {
         pair.second->clearArchiveRequests();
+        pair.second->downgradeDatabaseState();
     }
 }
diff --git a/core/handlers/rosterhandler.h b/core/handlers/rosterhandler.h
index c8eeafa..c01f396 100644
--- a/core/handlers/rosterhandler.h
+++ b/core/handlers/rosterhandler.h
@@ -55,7 +55,7 @@ public:
     
     void removeRoomRequest(const QString& jid);
     void addRoomRequest(const QString& jid, const QString& nick, const QString& password, bool autoJoin);
-    void cancelHistoryRequests();
+    void handleOffline();
     
     Core::Contact* getContact(const QString& jid);
     Core::Conference* getConference(const QString& jid);
diff --git a/core/rosteritem.cpp b/core/rosteritem.cpp
index 9163994..5275993 100644
--- a/core/rosteritem.cpp
+++ b/core/rosteritem.cpp
@@ -523,8 +523,25 @@ void Core::RosterItem::clearArchiveRequests()
     syncronizing = false;
     requestedCount = 0;
     requestedBefore = "";
+    for (const std::pair<int, QString>& pair : requestCache) {
+        if (pair.first != -1) {
+            emit historyResponse(responseCache);        //just to notify those who still waits with whatever happened to be left in caches yet
+        }
+        responseCache.clear();
+    }
     hisoryCache.clear();
-    responseCache.clear();
+    responseCache.clear();  //in case the cycle never runned
     appendCache.clear();
     requestCache.clear();
 }
+
+void Core::RosterItem::downgradeDatabaseState()
+{
+    if (archiveState == ArchiveState::complete) {
+        archiveState = ArchiveState::beginning;
+    }
+    
+    if (archiveState == ArchiveState::end) {
+        archiveState = ArchiveState::chunk;
+    }
+}
diff --git a/core/rosteritem.h b/core/rosteritem.h
index f38e189..4113b37 100644
--- a/core/rosteritem.h
+++ b/core/rosteritem.h
@@ -76,6 +76,7 @@ public:
     
     bool changeMessage(const QString& id, const QMap<QString, QVariant>& data);
     void clearArchiveRequests();
+    void downgradeDatabaseState();
     
 signals:
     void nameChanged(const QString& name);

From 480c78cf61a46f69fa7b92d6fbcfa681d3ab811e Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Sun, 26 Jul 2020 22:41:30 +0300
Subject: [PATCH 077/281] non lower cased jids error handled

---
 CHANGELOG.md                    |  2 +
 core/account.cpp                |  4 +-
 core/handlers/rosterhandler.cpp | 88 ++++++++++++++++++---------------
 shared/message.cpp              | 12 ++---
 4 files changed, 59 insertions(+), 47 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index d61aaf2..e1f9543 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,8 @@
 - error with building on lower versions of QXmpp
 - error on the first access to the conference database
 - quit now actually quits the app
+- history in MUC now works properly and requests messages from server
+- error with handling non lower cased JIDs
 
 
 ## Squawk 0.1.4 (Apr 14, 2020)
diff --git a/core/account.cpp b/core/account.cpp
index 5ed8873..0c8c272 100644
--- a/core/account.cpp
+++ b/core/account.cpp
@@ -321,7 +321,7 @@ void Core::Account::onPresenceReceived(const QXmppPresence& p_presence)
 {
     QString id = p_presence.from();
     QStringList comps = id.split("/");
-    QString jid = comps.front();
+    QString jid = comps.front().toLower();
     QString resource = comps.back();
     
     QString myJid = getLogin() + "@" + getServer();
@@ -682,7 +682,7 @@ void Core::Account::onVCardReceived(const QXmppVCardIq& card)
 {
     QString id = card.from();
     QStringList comps = id.split("/");
-    QString jid = comps.front();
+    QString jid = comps.front().toLower();
     QString resource("");
     if (comps.size() > 1) {
         resource = comps.back();
diff --git a/core/handlers/rosterhandler.cpp b/core/handlers/rosterhandler.cpp
index 44b2bda..82ca8c3 100644
--- a/core/handlers/rosterhandler.cpp
+++ b/core/handlers/rosterhandler.cpp
@@ -57,16 +57,17 @@ void Core::RosterHandler::onRosterReceived()
     QStringList bj = acc->rm->getRosterBareJids();
     for (int i = 0; i < bj.size(); ++i) {
         const QString& jid = bj[i];
-        addedAccount(jid);
+        addedAccount(jid.toLower());
     }
 }
 
 void Core::RosterHandler::onRosterItemAdded(const QString& bareJid)
 {
-    addedAccount(bareJid);
-    std::map<QString, QString>::const_iterator itr = queuedContacts.find(bareJid);
+    QString lcJid = bareJid.toLower();
+    addedAccount(lcJid);
+    std::map<QString, QString>::const_iterator itr = queuedContacts.find(lcJid);
     if (itr != queuedContacts.end()) {
-        acc->rm->subscribe(bareJid, itr->second);
+        acc->rm->subscribe(lcJid, itr->second);
         queuedContacts.erase(itr);
     }
 }
@@ -172,16 +173,17 @@ void Core::RosterHandler::addContactRequest(const QString& jid, const QString& n
 
 void Core::RosterHandler::removeContactRequest(const QString& jid)
 {
+    QString lcJid = jid.toLower();
     if (acc->state == Shared::ConnectionState::connected) {
-        std::set<QString>::const_iterator itr = outOfRosterContacts.find(jid);
+        std::set<QString>::const_iterator itr = outOfRosterContacts.find(lcJid);
         if (itr != outOfRosterContacts.end()) {
             outOfRosterContacts.erase(itr);
-            onRosterItemRemoved(jid);
+            onRosterItemRemoved(lcJid);
         } else {
-            acc->rm->removeItem(jid);
+            acc->rm->removeItem(lcJid);
         }
     } else {
-        qDebug() << "An attempt to remove contact " << jid << " from account " << acc->name << " but the account is not in the connected state, skipping";
+        qDebug() << "An attempt to remove contact " << lcJid << " from account " << acc->name << " but the account is not in the connected state, skipping";
     }
 }
 
@@ -291,7 +293,7 @@ void Core::RosterHandler::addToGroup(const QString& jid, const QString& group)
         gItr = groups.insert(std::make_pair(group, std::set<QString>())).first;
         emit acc->addGroup(group);
     }
-    gItr->second.insert(jid);
+    gItr->second.insert(jid.toLower());
 }
 
 void Core::RosterHandler::removeFromGroup(const QString& jid, const QString& group)
@@ -303,7 +305,7 @@ void Core::RosterHandler::removeFromGroup(const QString& jid, const QString& gro
         return;
     }
     std::set<QString> contacts = itr->second;
-    std::set<QString>::const_iterator cItr = contacts.find(jid);
+    std::set<QString>::const_iterator cItr = contacts.find(jid.toLower());
     if (cItr != contacts.end()) {
         contacts.erase(cItr);
         if (contacts.size() == 0) {
@@ -324,11 +326,12 @@ void Core::RosterHandler::onContactHistoryResponse(const std::list<Shared::Messa
 Core::RosterItem * Core::RosterHandler::getRosterItem(const QString& jid)
 {
     RosterItem* item = 0;
-    std::map<QString, Contact*>::const_iterator citr = contacts.find(jid);
+    QString lcJid = jid.toLower();
+    std::map<QString, Contact*>::const_iterator citr = contacts.find(lcJid);
     if (citr != contacts.end()) {
         item = citr->second;
     } else {
-        std::map<QString, Conference*>::const_iterator coitr = conferences.find(jid);
+        std::map<QString, Conference*>::const_iterator coitr = conferences.find(lcJid);
         if (coitr != conferences.end()) {
             item = coitr->second;
         }
@@ -339,7 +342,7 @@ Core::RosterItem * Core::RosterHandler::getRosterItem(const QString& jid)
 Core::Conference * Core::RosterHandler::getConference(const QString& jid)
 {
     Conference* item = 0;
-    std::map<QString, Conference*>::const_iterator coitr = conferences.find(jid);
+    std::map<QString, Conference*>::const_iterator coitr = conferences.find(jid.toLower());
     if (coitr != conferences.end()) {
         item = coitr->second;
     }
@@ -349,7 +352,7 @@ Core::Conference * Core::RosterHandler::getConference(const QString& jid)
 Core::Contact * Core::RosterHandler::getContact(const QString& jid)
 {
     Contact* item = 0;
-    std::map<QString, Contact*>::const_iterator citr = contacts.find(jid);
+    std::map<QString, Contact*>::const_iterator citr = contacts.find(jid.toLower());
     if (citr != contacts.end()) {
         item = citr->second;
     }
@@ -358,11 +361,12 @@ Core::Contact * Core::RosterHandler::getContact(const QString& jid)
 
 Core::Contact * Core::RosterHandler::addOutOfRosterContact(const QString& jid)
 {
-    Contact* cnt = new Contact(jid, acc->name);
-    contacts.insert(std::make_pair(jid, cnt));
-    outOfRosterContacts.insert(jid);
+    QString lcJid = jid.toLower();
+    Contact* cnt = new Contact(lcJid, acc->name);
+    contacts.insert(std::make_pair(lcJid, cnt));
+    outOfRosterContacts.insert(lcJid);
     cnt->setSubscriptionState(Shared::SubscriptionState::unknown);
-    emit acc->addContact(jid, "", QMap<QString, QVariant>({
+    emit acc->addContact(lcJid, "", QMap<QString, QVariant>({
         {"state", QVariant::fromValue(Shared::SubscriptionState::unknown)}
     }));
     handleNewContact(cnt);
@@ -371,13 +375,14 @@ Core::Contact * Core::RosterHandler::addOutOfRosterContact(const QString& jid)
 
 void Core::RosterHandler::onRosterItemChanged(const QString& bareJid)
 {
-    std::map<QString, Contact*>::const_iterator itr = contacts.find(bareJid);
+    QString lcJid = bareJid.toLower();
+    std::map<QString, Contact*>::const_iterator itr = contacts.find(lcJid);
     if (itr == contacts.end()) {
-        qDebug() << "An attempt to change non existing contact" << bareJid << "from account" << acc->name << ", skipping";
+        qDebug() << "An attempt to change non existing contact" << lcJid << "from account" << acc->name << ", skipping";
         return;
     }
     Contact* contact = itr->second;
-    QXmppRosterIq::Item re = acc->rm->getRosterEntry(bareJid);
+    QXmppRosterIq::Item re = acc->rm->getRosterEntry(lcJid);
     
     Shared::SubscriptionState state = castSubscriptionState(re.subscriptionType());
     
@@ -388,18 +393,19 @@ void Core::RosterHandler::onRosterItemChanged(const QString& bareJid)
 
 void Core::RosterHandler::onRosterItemRemoved(const QString& bareJid)
 {
-    std::map<QString, Contact*>::const_iterator itr = contacts.find(bareJid);
+    QString lcJid = bareJid.toLower();
+    std::map<QString, Contact*>::const_iterator itr = contacts.find(lcJid);
     if (itr == contacts.end()) {
-        qDebug() << "An attempt to remove non existing contact" << bareJid << "from account" << acc->name << ", skipping";
+        qDebug() << "An attempt to remove non existing contact" << lcJid << "from account" << acc->name << ", skipping";
         return;
     }
     Contact* contact = itr->second;
     contacts.erase(itr);
     QSet<QString> cGroups = contact->getGroups();
     for (QSet<QString>::const_iterator itr = cGroups.begin(), end = cGroups.end(); itr != end; ++itr) {
-        removeFromGroup(bareJid, *itr);
+        removeFromGroup(lcJid, *itr);
     }
-    emit acc->removeContact(bareJid);
+    emit acc->removeContact(lcJid);
     
     contact->deleteLater();
 }
@@ -416,7 +422,7 @@ void Core::RosterHandler::bookmarksReceived(const QXmppBookmarkSet& bookmarks)
     for (QList<QXmppBookmarkConference>::const_iterator itr = confs.begin(), end = confs.end(); itr != end; ++itr) {
         const QXmppBookmarkConference& c = *itr;
         
-        QString jid = c.jid();
+        QString jid = c.jid().toLower();
         std::map<QString, Conference*>::const_iterator cItr = conferences.find(jid);
         if (cItr == conferences.end()) {
             addNewRoom(jid, c.nickName(), c.name(), c.autoJoin());
@@ -495,35 +501,38 @@ void Core::RosterHandler::clearConferences()
 
 void Core::RosterHandler::removeRoomRequest(const QString& jid)
 {
-    std::map<QString, Conference*>::const_iterator itr = conferences.find(jid);
+    QString lcJid = jid.toLower();
+    std::map<QString, Conference*>::const_iterator itr = conferences.find(lcJid);
     if (itr == conferences.end()) {
-        qDebug() << "An attempt to remove non existing room" << jid << "from account" << acc->name << ", skipping";
+        qDebug() << "An attempt to remove non existing room" << lcJid << "from account" << acc->name << ", skipping";
     }
     itr->second->deleteLater();
     conferences.erase(itr);
-    emit acc->removeRoom(jid);
+    emit acc->removeRoom(lcJid);
     storeConferences();
 }
 
 void Core::RosterHandler::addRoomRequest(const QString& jid, const QString& nick, const QString& password, bool autoJoin)
 {
-    std::map<QString, Conference*>::const_iterator cItr = conferences.find(jid);
+    QString lcJid = jid.toLower();
+    std::map<QString, Conference*>::const_iterator cItr = conferences.find(lcJid);
     if (cItr == conferences.end()) {
-        addNewRoom(jid, nick, "", autoJoin);
+        addNewRoom(lcJid, nick, "", autoJoin);
         storeConferences();
     } else {
-        qDebug() << "An attempt to add a MUC " << jid << " which is already present in the rester, skipping";
+        qDebug() << "An attempt to add a MUC " << lcJid << " which is already present in the rester, skipping";
     }
 }
 
 void Core::RosterHandler::addContactToGroupRequest(const QString& jid, const QString& groupName)
 {
-    std::map<QString, Contact*>::const_iterator itr = contacts.find(jid);
+    QString lcJid = jid.toLower();
+    std::map<QString, Contact*>::const_iterator itr = contacts.find(lcJid);
     if (itr == contacts.end()) {
-        qDebug()    << "An attempt to add non existing contact" << jid << "of account" 
+        qDebug()    << "An attempt to add non existing contact" << lcJid << "of account" 
                     << acc->name << "to the group" << groupName << ", skipping";
     } else {
-        QXmppRosterIq::Item item = acc->rm->getRosterEntry(jid);
+        QXmppRosterIq::Item item = acc->rm->getRosterEntry(lcJid);
         QSet<QString> groups = item.groups();
         if (groups.find(groupName) == groups.end()) {           //TODO need to change it, I guess that sort of code is better in qxmpp lib
             groups.insert(groupName);
@@ -542,12 +551,13 @@ void Core::RosterHandler::addContactToGroupRequest(const QString& jid, const QSt
 
 void Core::RosterHandler::removeContactFromGroupRequest(const QString& jid, const QString& groupName)
 {
-    std::map<QString, Contact*>::const_iterator itr = contacts.find(jid);
+    QString lcJid = jid.toLower();
+    std::map<QString, Contact*>::const_iterator itr = contacts.find(lcJid);
     if (itr == contacts.end()) {
-        qDebug()    << "An attempt to remove non existing contact" << jid << "of account" 
+        qDebug()    << "An attempt to remove non existing contact" << lcJid << "of account" 
                     << acc->name << "from the group" << groupName << ", skipping";
     } else {
-        QXmppRosterIq::Item item = acc->rm->getRosterEntry(jid);
+        QXmppRosterIq::Item item = acc->rm->getRosterEntry(lcJid);
         QSet<QString> groups = item.groups();
         QSet<QString>::const_iterator gItr = groups.find(groupName);
         if (gItr != groups.end()) {
@@ -559,7 +569,7 @@ void Core::RosterHandler::removeContactFromGroupRequest(const QString& jid, cons
             iq.addItem(item);
             acc->client.sendPacket(iq);
         } else {
-            qDebug()    << "An attempt to remove contact" << jid << "of account" 
+            qDebug()    << "An attempt to remove contact" << lcJid << "of account" 
                         << acc->name << "from the group" << groupName << "but it's not in that group, skipping";
         }
     }
diff --git a/shared/message.cpp b/shared/message.cpp
index fad10dc..af4f9e0 100644
--- a/shared/message.cpp
+++ b/shared/message.cpp
@@ -105,9 +105,9 @@ void Shared::Message::setFrom(const QString& from)
 {
     QStringList list = from.split("/");
     if (list.size() == 1) {
-        jFrom = from;
+        jFrom = from.toLower();
     } else {
-        jFrom = list.front();
+        jFrom = list.front().toLower();
         rFrom = list.back();
     }
 }
@@ -116,9 +116,9 @@ void Shared::Message::setTo(const QString& to)
 {
     QStringList list = to.split("/");
     if (list.size() == 1) {
-        jTo = to;
+        jTo = to.toLower();
     } else {
-        jTo = list.front();
+        jTo = list.front().toLower();
         rTo = list.back();
     }
 }
@@ -188,7 +188,7 @@ bool Shared::Message::getEdited() const
 
 void Shared::Message::setFromJid(const QString& from)
 {
-    jFrom = from;
+    jFrom = from.toLower();
 }
 
 void Shared::Message::setFromResource(const QString& from)
@@ -198,7 +198,7 @@ void Shared::Message::setFromResource(const QString& from)
 
 void Shared::Message::setToJid(const QString& to)
 {
-    jTo = to;
+    jTo = to.toLower();
 }
 
 void Shared::Message::setToResource(const QString& to)

From a543eb1aef21b27e67060623e97f2e99ade9e87f Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Wed, 29 Jul 2020 23:26:56 +0300
Subject: [PATCH 078/281] 0.1.5

---
 CHANGELOG.md                 | 3 ++-
 core/networkaccess.cpp       | 6 +++---
 main.cpp                     | 2 +-
 packaging/Archlinux/PKGBUILD | 4 ++--
 signalcatcher.cpp            | 4 ++--
 ui/models/roster.cpp         | 2 +-
 ui/utils/flowlayout.cpp      | 2 +-
 7 files changed, 12 insertions(+), 11 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index e1f9543..5c1d31c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,6 @@
 # Changelog
 
-## Squawk 0.1.5 (UNRELEASED)
+## Squawk 0.1.5 (Jul 29, 2020)
 ### Bug fixes
 - error with sending attached files to the conference
 - error with building on lower versions of QXmpp
@@ -8,6 +8,7 @@
 - quit now actually quits the app
 - history in MUC now works properly and requests messages from server
 - error with handling non lower cased JIDs
+- some workaround upon reconnection
 
 
 ## Squawk 0.1.4 (Apr 14, 2020)
diff --git a/core/networkaccess.cpp b/core/networkaccess.cpp
index 3e9d2c0..8c120d8 100644
--- a/core/networkaccess.cpp
+++ b/core/networkaccess.cpp
@@ -334,7 +334,7 @@ void Core::NetworkAccess::startDownload(const QString& messageId, const QString&
     QNetworkRequest req(url);
     dwn->reply = manager->get(req);
     connect(dwn->reply, &QNetworkReply::downloadProgress, this, &NetworkAccess::onDownloadProgress);
-    connect(dwn->reply, qOverload<QNetworkReply::NetworkError>(&QNetworkReply::error), this, &NetworkAccess::onDownloadError);
+    connect(dwn->reply, qOverload<QNetworkReply::NetworkError>(&QNetworkReply::errorOccurred), this, &NetworkAccess::onDownloadError);
     connect(dwn->reply, &QNetworkReply::finished, this, &NetworkAccess::onDownloadFinished);
     downloads.insert(std::make_pair(url, dwn));
     emit downloadFileProgress(messageId, 0);
@@ -414,7 +414,7 @@ void Core::NetworkAccess::startUpload(const QString& messageId, const QString& u
         upl->reply = manager->put(req, file);
         
         connect(upl->reply, &QNetworkReply::uploadProgress, this, &NetworkAccess::onUploadProgress);
-        connect(upl->reply, qOverload<QNetworkReply::NetworkError>(&QNetworkReply::error), this, &NetworkAccess::onUploadError);
+        connect(upl->reply, qOverload<QNetworkReply::NetworkError>(&QNetworkReply::errorOccurred), this, &NetworkAccess::onUploadError);
         connect(upl->reply, &QNetworkReply::finished, this, &NetworkAccess::onUploadFinished);
         uploads.insert(std::make_pair(url, upl));
         emit downloadFileProgress(messageId, 0);
@@ -490,7 +490,7 @@ void Core::NetworkAccess::uploadFile(const QString& messageId, const QString& pa
         upl->reply = manager->put(req, file);
         
         connect(upl->reply, &QNetworkReply::uploadProgress, this, &NetworkAccess::onUploadProgress);
-        connect(upl->reply, qOverload<QNetworkReply::NetworkError>(&QNetworkReply::error), this, &NetworkAccess::onUploadError);
+        connect(upl->reply, qOverload<QNetworkReply::NetworkError>(&QNetworkReply::errorOccurred), this, &NetworkAccess::onUploadError);
         connect(upl->reply, &QNetworkReply::finished, this, &NetworkAccess::onUploadFinished);
         uploads.insert(std::make_pair(put.toString(), upl));
         emit downloadFileProgress(messageId, 0);
diff --git a/main.cpp b/main.cpp
index d272d3d..4c4b3ea 100644
--- a/main.cpp
+++ b/main.cpp
@@ -42,7 +42,7 @@ int main(int argc, char *argv[])
     
     QApplication::setApplicationName("squawk");
     QApplication::setApplicationDisplayName("Squawk");
-    QApplication::setApplicationVersion("0.1.4");
+    QApplication::setApplicationVersion("0.1.5");
     
     QTranslator qtTranslator;
     qtTranslator.load("qt_" + QLocale::system().name(), QLibraryInfo::location(QLibraryInfo::TranslationsPath));
diff --git a/packaging/Archlinux/PKGBUILD b/packaging/Archlinux/PKGBUILD
index 9f8ef73..20fea99 100644
--- a/packaging/Archlinux/PKGBUILD
+++ b/packaging/Archlinux/PKGBUILD
@@ -1,6 +1,6 @@
 # Maintainer: Yury Gubich <blue@macaw.me>
 pkgname=squawk
-pkgver=0.1.4
+pkgver=0.1.5
 pkgrel=1
 pkgdesc="An XMPP desktop messenger, written on pure c++ (qt)"
 arch=('i686' 'x86_64')
@@ -11,7 +11,7 @@ makedepends=('cmake>=3.3' 'imagemagick' 'qt5-tools')
 optdepends=('kwallet: secure password storage (requires rebuild)')
 
 source=("$pkgname-$pkgver.tar.gz")
-sha256sums=('3b290381eaf15a35d24a58a36c29eee375a4ea77b606124982a063d7ecf98870')
+sha256sums=('e1a4c88be9f0481d2aa21078faf42fd0e9d66b490b6d8af82827d441cb58df25')
 build() {
         cd "$srcdir/squawk"
         cmake . -D CMAKE_INSTALL_PREFIX=/usr -D CMAKE_BUILD_TYPE=Release
diff --git a/signalcatcher.cpp b/signalcatcher.cpp
index 9ac3aae..dcdcb88 100644
--- a/signalcatcher.cpp
+++ b/signalcatcher.cpp
@@ -48,7 +48,7 @@ void SignalCatcher::handleSigInt()
 {
     snInt->setEnabled(false);
     char tmp;
-    ::read(sigintFd[1], &tmp, sizeof(tmp));
+    ssize_t s = ::read(sigintFd[1], &tmp, sizeof(tmp));
 
     app->quit();
 
@@ -58,7 +58,7 @@ void SignalCatcher::handleSigInt()
 void SignalCatcher::intSignalHandler(int unused)
 {
     char a = 1;
-    ::write(sigintFd[0], &a, sizeof(a));
+    ssize_t s = ::write(sigintFd[0], &a, sizeof(a));
 }
 
 int SignalCatcher::setup_unix_signal_handlers()
diff --git a/ui/models/roster.cpp b/ui/models/roster.cpp
index 75555ae..ac90a50 100644
--- a/ui/models/roster.cpp
+++ b/ui/models/roster.cpp
@@ -308,7 +308,7 @@ void Models::Roster::updateAccount(const QString& account, const QString& field,
 Qt::ItemFlags Models::Roster::flags(const QModelIndex& index) const
 {
     if (!index.isValid()) {
-        return 0;
+        return Qt::ItemFlags();
     }
     
     return QAbstractItemModel::flags(index);
diff --git a/ui/utils/flowlayout.cpp b/ui/utils/flowlayout.cpp
index ed53f51..ad7715e 100644
--- a/ui/utils/flowlayout.cpp
+++ b/ui/utils/flowlayout.cpp
@@ -84,7 +84,7 @@ QLayoutItem *FlowLayout::takeAt(int index)
 
 Qt::Orientations FlowLayout::expandingDirections() const
 {
-    return 0;
+    return Qt::Orientations();
 }
 
 bool FlowLayout::hasHeightForWidth() const

From 5f64321c2abb23c08368e44ff69944196de1d4db Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Tue, 4 Aug 2020 17:52:16 +0300
Subject: [PATCH 079/281] fix compilation on older qt versions

---
 core/networkaccess.cpp | 13 +++++++++++++
 1 file changed, 13 insertions(+)

diff --git a/core/networkaccess.cpp b/core/networkaccess.cpp
index 8c120d8..2d66a70 100644
--- a/core/networkaccess.cpp
+++ b/core/networkaccess.cpp
@@ -334,7 +334,11 @@ void Core::NetworkAccess::startDownload(const QString& messageId, const QString&
     QNetworkRequest req(url);
     dwn->reply = manager->get(req);
     connect(dwn->reply, &QNetworkReply::downloadProgress, this, &NetworkAccess::onDownloadProgress);
+#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
     connect(dwn->reply, qOverload<QNetworkReply::NetworkError>(&QNetworkReply::errorOccurred), this, &NetworkAccess::onDownloadError);
+#else
+    connect(dwn->reply, qOverload<QNetworkReply::NetworkError>(&QNetworkReply::error), this, &NetworkAccess::onDownloadError);
+#endif
     connect(dwn->reply, &QNetworkReply::finished, this, &NetworkAccess::onDownloadFinished);
     downloads.insert(std::make_pair(url, dwn));
     emit downloadFileProgress(messageId, 0);
@@ -414,7 +418,12 @@ void Core::NetworkAccess::startUpload(const QString& messageId, const QString& u
         upl->reply = manager->put(req, file);
         
         connect(upl->reply, &QNetworkReply::uploadProgress, this, &NetworkAccess::onUploadProgress);
+#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
         connect(upl->reply, qOverload<QNetworkReply::NetworkError>(&QNetworkReply::errorOccurred), this, &NetworkAccess::onUploadError);
+#else
+        connect(upl->reply, qOverload<QNetworkReply::NetworkError>(&QNetworkReply::error), this, &NetworkAccess::onUploadError);
+#endif
+        
         connect(upl->reply, &QNetworkReply::finished, this, &NetworkAccess::onUploadFinished);
         uploads.insert(std::make_pair(url, upl));
         emit downloadFileProgress(messageId, 0);
@@ -490,7 +499,11 @@ void Core::NetworkAccess::uploadFile(const QString& messageId, const QString& pa
         upl->reply = manager->put(req, file);
         
         connect(upl->reply, &QNetworkReply::uploadProgress, this, &NetworkAccess::onUploadProgress);
+#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
         connect(upl->reply, qOverload<QNetworkReply::NetworkError>(&QNetworkReply::errorOccurred), this, &NetworkAccess::onUploadError);
+#else
+        connect(upl->reply, qOverload<QNetworkReply::NetworkError>(&QNetworkReply::error), this, &NetworkAccess::onUploadError);
+#endif
         connect(upl->reply, &QNetworkReply::finished, this, &NetworkAccess::onUploadFinished);
         uploads.insert(std::make_pair(put.toString(), upl));
         emit downloadFileProgress(messageId, 0);

From 3a120c773a9c1db16c544303b24331cd27e8326a Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Sat, 8 Aug 2020 02:33:03 +0300
Subject: [PATCH 080/281] reconnection issues

---
 CHANGELOG.md                |  8 ++++++
 core/account.cpp            | 11 +++++---
 core/account.h              |  2 +-
 core/squawk.cpp             | 54 ++++++++++++++++++++++---------------
 ui/models/item.cpp          |  5 ++--
 ui/widgets/conversation.cpp | 16 +++++++++++
 ui/widgets/conversation.h   |  1 +
 7 files changed, 67 insertions(+), 30 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5c1d31c..3e1ebd4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,13 @@
 # Changelog
 
+## Squawk 0.2.0 (Unreleased)
+### Bug fixes
+- carbon copies switches on again after reconnection
+- requesting the history of the current chat after reconnection
+- global availability (in drop down list) gets restored after reconnection
+- status icon in active chat changes when presence of the pen pal changes
+
+
 ## Squawk 0.1.5 (Jul 29, 2020)
 ### Bug fixes
 - error with sending attached files to the conference
diff --git a/core/account.cpp b/core/account.cpp
index 0c8c272..094fd3c 100644
--- a/core/account.cpp
+++ b/core/account.cpp
@@ -183,7 +183,6 @@ void Core::Account::connect()
         reconnectTimer->stop();
     }
     if (state == Shared::ConnectionState::disconnected) {
-        qDebug() << presence.availableStatusType();
         client.connectToServer(config, presence);
     } else {
         qDebug("An attempt to connect an account which is already connected, skipping");
@@ -219,6 +218,7 @@ void Core::Account::onClientStateChange(QXmppClient::State st)
                     Shared::ConnectionState os = state;
                     state = Shared::ConnectionState::connected;
                     if (os == Shared::ConnectionState::connecting) {
+                        qDebug() << "running service discovery for account" << name;
                         dm->requestItems(getServer());
                         dm->requestInfo(getServer());
                     }
@@ -238,9 +238,8 @@ void Core::Account::onClientStateChange(QXmppClient::State st)
         }
             break;
         case QXmppClient::DisconnectedState: {
-            cancelHistoryRequests();
-            pendingVCardRequests.clear();
             if (state != Shared::ConnectionState::disconnected) {
+                handleDisconnection();
                 state = Shared::ConnectionState::disconnected;
                 emit connectionStateChanged(state);
             } else {
@@ -887,15 +886,18 @@ void Core::Account::onDiscoveryItemsReceived(const QXmppDiscoveryIq& items)
 
 void Core::Account::onDiscoveryInfoReceived(const QXmppDiscoveryIq& info)
 {
+    qDebug() << "Discovery info received for account" << name;
     if (info.from() == getServer()) {
         if (info.features().contains("urn:xmpp:carbons:2")) {
+            qDebug() << "Enabling carbon copies for account" << name;
             cm->setCarbonsEnabled(true);
         }
     }
 }
 
-void Core::Account::cancelHistoryRequests()
+void Core::Account::handleDisconnection()
 {
+    cm->setCarbonsEnabled(false);
     rh->handleOffline();
     archiveQueries.clear();
     pendingVCardRequests.clear();
@@ -903,6 +905,7 @@ void Core::Account::cancelHistoryRequests()
     for (const QString& jid : pendingVCardRequests) {
         emit receivedVCard(jid, vCard);     //need to show it better in the future, like with an error
     }
+    pendingVCardRequests.clear();
     ownVCardRequestInProgress = false;
 }
 
diff --git a/core/account.h b/core/account.h
index d7ca113..49c7ca9 100644
--- a/core/account.h
+++ b/core/account.h
@@ -185,7 +185,7 @@ private slots:
     void onDiscoveryInfoReceived (const QXmppDiscoveryIq& info);
   
 private:
-    void cancelHistoryRequests();
+    void handleDisconnection();
     void onReconnectTimer();
 };
 
diff --git a/core/squawk.cpp b/core/squawk.cpp
index 9bb2f14..1689d71 100644
--- a/core/squawk.cpp
+++ b/core/squawk.cpp
@@ -223,30 +223,40 @@ void Core::Squawk::onAccountConnectionStateChanged(Shared::ConnectionState p_sta
     Account* acc = static_cast<Account*>(sender());
     emit changeAccount(acc->getName(), {{"state", QVariant::fromValue(p_state)}});
     
-    switch (p_state) {
-        case Shared::ConnectionState::disconnected: {
-                bool equals = true;
-                for (Accounts::const_iterator itr = accounts.begin(), end = accounts.end(); itr != end; itr++) {
-                    if ((*itr)->getState() != Shared::ConnectionState::disconnected) {
-                        equals = false;
-                    }
-                }
-                if (equals && state != Shared::Availability::offline) {
-                    state = Shared::Availability::offline;
-                    emit stateChanged(state);
-                }
-            }
-            break;
-        case Shared::ConnectionState::connected:
 #ifdef WITH_KWALLET
-            if (acc->getPasswordType() == Shared::AccountPassword::kwallet && kwallet.supportState() == PSE::KWallet::success) {
-                kwallet.requestWritePassword(acc->getName(), acc->getPassword(), true);
-            }
-#endif
-            break;
-        default:
-            break;
+    if (p_state == Shared::ConnectionState::connected) {
+        if (acc->getPasswordType() == Shared::AccountPassword::kwallet && kwallet.supportState() == PSE::KWallet::success) {
+            kwallet.requestWritePassword(acc->getName(), acc->getPassword(), true);
+        }
     }
+#endif
+    
+    Accounts::const_iterator itr = accounts.begin();
+    bool es = true;
+    bool ea = true;
+    Shared::ConnectionState cs = (*itr)->getState();
+    Shared::Availability av = (*itr)->getAvailability();
+    itr++;
+    for (Accounts::const_iterator end = accounts.end(); itr != end; itr++) {
+        Account* item = *itr;
+        if (item->getState() != cs) {
+            es = false;
+        }
+        if (item->getAvailability() != av) {
+            ea = false;
+        }
+    }
+    
+    if (es) {
+        if (cs == Shared::ConnectionState::disconnected) {
+            state = Shared::Availability::offline;
+            emit stateChanged(state);
+        } else if (ea) {
+            state = av;
+            emit stateChanged(state);
+        }
+    }
+    
 }
 
 void Core::Squawk::onAccountAddContact(const QString& jid, const QString& group, const QMap<QString, QVariant>& data)
diff --git a/ui/models/item.cpp b/ui/models/item.cpp
index c5d6e2a..e006ad0 100644
--- a/ui/models/item.cpp
+++ b/ui/models/item.cpp
@@ -215,9 +215,8 @@ void Models::Item::_removeChild(int index)
 
 void Models::Item::changed(int col)
 {
-    if (parent != nullptr) {
-        emit childChanged(this, row(), col);
-    }
+    
+    emit childChanged(this, row(), col);
 }
 
 void Models::Item::toOfflineState()
diff --git a/ui/widgets/conversation.cpp b/ui/widgets/conversation.cpp
index 60b0210..fd87d9f 100644
--- a/ui/widgets/conversation.cpp
+++ b/ui/widgets/conversation.cpp
@@ -54,6 +54,8 @@ Conversation::Conversation(bool muc, Models::Account* acc, const QString pJid, c
 {
     m_ui->setupUi(this);
     
+    connect(acc, &Models::Account::childChanged, this, &Conversation::onAccountChanged);
+    
     filesLayout = new FlowLayout(m_ui->filesPanel, 0);
     m_ui->filesPanel->setLayout(filesLayout);
     
@@ -121,6 +123,20 @@ Conversation::~Conversation()
 {
 }
 
+void Conversation::onAccountChanged(Models::Item* item, int row, int col)
+{
+    if (item == account) {
+        if (col == 2 && account->getState() == Shared::ConnectionState::connected) {
+            if (!requestingHistory) {
+                requestingHistory = true;
+                line->showBusyIndicator();
+                emit requestArchive("");
+                scroll = down;
+            }
+        }
+    }
+}
+
 void Conversation::applyVisualEffects()
 {
     DropShadowEffect *e1 = new DropShadowEffect;
diff --git a/ui/widgets/conversation.h b/ui/widgets/conversation.h
index e5ac53a..ea87607 100644
--- a/ui/widgets/conversation.h
+++ b/ui/widgets/conversation.h
@@ -121,6 +121,7 @@ protected slots:
     void onBadgeClose();
     void onClearButton();
     void onTextEditDocSizeChanged(const QSizeF& size);
+    void onAccountChanged(Models::Item* item, int row, int col);
     
 public:
     const bool isMuc;

From ef1a9846bf7a0b8b160b52e58c4e80078f782d9b Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Sun, 9 Aug 2020 19:28:03 +0300
Subject: [PATCH 081/281] found a way to avoid requesting all the MUCs history
 alltogether on startup

---
 CHANGELOG.md        |  3 +++
 core/archive.cpp    | 20 ++++++++++++++++++++
 core/archive.h      |  1 +
 core/rosteritem.cpp | 16 ++++++++++------
 4 files changed, 34 insertions(+), 6 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3e1ebd4..06c4ce1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,9 @@
 - global availability (in drop down list) gets restored after reconnection
 - status icon in active chat changes when presence of the pen pal changes
 
+### Improvements
+- slightly reduced the traffic on the startup by not requesting history of all MUCs
+
 
 ## Squawk 0.1.5 (Jul 29, 2020)
 ### Bug fixes
diff --git a/core/archive.cpp b/core/archive.cpp
index 628723d..a1f8b76 100644
--- a/core/archive.cpp
+++ b/core/archive.cpp
@@ -214,6 +214,26 @@ Shared::Message Core::Archive::getElement(const QString& id) const
     }
 }
 
+bool Core::Archive::hasElement(const QString& id) const
+{
+    if (!opened) {
+        throw Closed("hasElement", jid.toStdString());
+    }
+    
+    MDB_txn *txn;
+    mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn);
+    
+    bool has;
+    MDB_val lmdbKey, lmdbData;
+    lmdbKey.mv_size = id.size();
+    lmdbKey.mv_data = (char*)id.toStdString().c_str();
+    int rc = mdb_get(txn, main, &lmdbKey, &lmdbData);
+    has = rc == 0;
+    mdb_txn_abort(txn);
+    
+    return has;
+}
+
 Shared::Message Core::Archive::getMessage(const std::string& id, MDB_txn* txn) const
 {
     MDB_val lmdbKey, lmdbData;
diff --git a/core/archive.h b/core/archive.h
index b71a8be..dd7a167 100644
--- a/core/archive.h
+++ b/core/archive.h
@@ -46,6 +46,7 @@ public:
     bool addElement(const Shared::Message& message);
     unsigned int addElements(const std::list<Shared::Message>& messages);
     Shared::Message getElement(const QString& id) const;
+    bool hasElement(const QString& id) const;
     void changeMessage(const QString& id, const QMap<QString, QVariant>& data);
     Shared::Message oldest();
     QString oldestId();
diff --git a/core/rosteritem.cpp b/core/rosteritem.cpp
index 5275993..32b70f4 100644
--- a/core/rosteritem.cpp
+++ b/core/rosteritem.cpp
@@ -248,18 +248,22 @@ void Core::RosterItem::appendMessageToArchive(const Shared::Message& msg)
                     }
                     break;
                 case beginning:
-                    appendCache.push_back(msg);
-                    if (!syncronizing) {
-                        requestHistory(-1, getId(msg));
+                    if (!archive->hasElement(msg.getId())) {
+                        appendCache.push_back(msg);
+                        if (!syncronizing) {
+                            requestHistory(-1, getId(msg));
+                        }
                     }
                     break;
                 case end:
                     archive->addElement(msg);
                     break;
                 case chunk:
-                    appendCache.push_back(msg);
-                    if (!syncronizing) {
-                        requestHistory(-1, getId(msg));
+                    if (!archive->hasElement(msg.getId())) {
+                        appendCache.push_back(msg);
+                        if (!syncronizing) {
+                            requestHistory(-1, getId(msg));
+                        }
                     }
                     break;
                 case complete:

From 38159eafeb67d3991879d7822d66e1f594449806 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Wed, 12 Aug 2020 01:49:51 +0300
Subject: [PATCH 082/281] first compiling prototype, doesnt work yet

---
 ui/CMakeLists.txt           |   6 ++
 ui/models/contact.cpp       | 195 +----------------------------------
 ui/models/contact.h         |  33 +-----
 ui/models/element.cpp       | 151 +++++++++++++++++++++++++++
 ui/models/element.h         |  70 +++++++++++++
 ui/models/messagefeed.cpp   | 145 ++++++++++++++++++++++++++
 ui/models/messagefeed.h     | 100 ++++++++++++++++++
 ui/models/presence.cpp      |  71 +------------
 ui/models/presence.h        |  15 ---
 ui/models/room.cpp          | 123 +---------------------
 ui/models/room.h            |  30 +-----
 ui/models/roster.cpp        |  48 +++++----
 ui/models/roster.h          |   6 +-
 ui/squawk.cpp               |  62 ++---------
 ui/squawk.h                 |   3 +-
 ui/widgets/chat.cpp         |  42 +++-----
 ui/widgets/chat.h           |   4 +-
 ui/widgets/conversation.cpp | 198 +++++++++++++++++-------------------
 ui/widgets/conversation.h   |   4 +-
 ui/widgets/conversation.ui  |  61 ++++-------
 ui/widgets/room.cpp         |  29 +-----
 21 files changed, 662 insertions(+), 734 deletions(-)
 create mode 100644 ui/models/element.cpp
 create mode 100644 ui/models/element.h
 create mode 100644 ui/models/messagefeed.cpp
 create mode 100644 ui/models/messagefeed.h

diff --git a/ui/CMakeLists.txt b/ui/CMakeLists.txt
index 52913a8..0ed806f 100644
--- a/ui/CMakeLists.txt
+++ b/ui/CMakeLists.txt
@@ -9,6 +9,10 @@ set(CMAKE_AUTOUIC ON)
 # Find the QtWidgets library
 find_package(Qt5Widgets CONFIG REQUIRED)
 find_package(Qt5DBus CONFIG REQUIRED)
+find_package(Boost 1.36.0 CONFIG REQUIRED)
+if(Boost_FOUND)
+  include_directories(${Boost_INCLUDE_DIRS})
+endif()
 
 add_subdirectory(widgets)
 
@@ -25,6 +29,8 @@ set(squawkUI_SRC
   models/abstractparticipant.cpp
   models/participant.cpp
   models/reference.cpp
+  models/messagefeed.cpp
+  models/element.cpp
   utils/messageline.cpp
   utils//message.cpp
   utils/resizer.cpp
diff --git a/ui/models/contact.cpp b/ui/models/contact.cpp
index 57744d8..d54fccf 100644
--- a/ui/models/contact.cpp
+++ b/ui/models/contact.cpp
@@ -17,55 +17,26 @@
  */
 
 #include "contact.h"
-#include "account.h"
 
 #include <QDebug>
 
 Models::Contact::Contact(const Account* acc, const QString& p_jid ,const QMap<QString, QVariant> &data, Item *parentItem):
-    Item(Item::contact, data, parentItem),
-    jid(p_jid),
+    Element(Item::contact, acc, p_jid, data, parentItem),
     availability(Shared::Availability::offline),
     state(Shared::SubscriptionState::none),
-    avatarState(Shared::Avatar::empty),
     presences(),
-    messages(),
-    childMessages(0),
-    status(),
-    avatarPath(),
-    account(acc)
+    status()
 {
     QMap<QString, QVariant>::const_iterator itr = data.find("state");
     if (itr != data.end()) {
         setState(itr.value().toUInt());
     }
-    
-    itr = data.find("avatarState");
-    if (itr != data.end()) {
-        setAvatarState(itr.value().toUInt());
-    }
-    itr = data.find("avatarPath");
-    if (itr != data.end()) {
-        setAvatarPath(itr.value().toString());
-    }
 }
 
 Models::Contact::~Contact()
 {
 }
 
-QString Models::Contact::getJid() const
-{
-    return jid;
-}
-
-void Models::Contact::setJid(const QString p_jid)
-{
-    if (jid != p_jid) {
-        jid = p_jid;
-        changed(1);
-    }
-}
-
 void Models::Contact::setAvailability(unsigned int p_state)
 {
     setAvailability(Shared::Global::fromInt<Shared::Availability>(p_state));
@@ -144,16 +115,12 @@ void Models::Contact::update(const QString& field, const QVariant& value)
 {
     if (field == "name") {
         setName(value.toString());
-    } else if (field == "jid") {
-        setJid(value.toString());
     } else if (field == "availability") {
         setAvailability(value.toUInt());
     } else if (field == "state") {
         setState(value.toUInt());
-    } else if (field == "avatarState") {
-        setAvatarState(value.toUInt());
-    } else if (field == "avatarPath") {
-        setAvatarPath(value.toString());
+    } else {
+        Element::update(field, value);
     }
 }
 
@@ -192,11 +159,9 @@ void Models::Contact::refresh()
 {
     QDateTime lastActivity;
     Presence* presence = 0;
-    unsigned int count = 0;
     for (QMap<QString, Presence*>::iterator itr = presences.begin(), end = presences.end(); itr != end; ++itr) {
         Presence* pr = itr.value();
         QDateTime la = pr->getLastActivity();
-        count += pr->getMessagesCount();
         
         if (la > lastActivity) {
             lastActivity = la;
@@ -211,11 +176,6 @@ void Models::Contact::refresh()
         setAvailability(Shared::Availability::offline);
         setStatus("");
     }
-    
-    if (childMessages != count) {
-        childMessages = count;
-        changed(4);
-    }
 }
 
 void Models::Contact::_removeChild(int index)
@@ -257,81 +217,6 @@ QIcon Models::Contact::getStatusIcon(bool big) const
     }
 }
 
-void Models::Contact::addMessage(const Shared::Message& data)
-{
-    const QString& res = data.getPenPalResource();
-    if (res.size() > 0) {
-        QMap<QString, Presence*>::iterator itr = presences.find(res);
-        if (itr == presences.end()) {
-            // this is actually the place when I can spot someone's invisible presence, and there is nothing criminal in it, cuz the sender sent us a message
-            // therefore he have revealed himself
-            // the only issue is to find out when the sender is gone offline
-            Presence* pr = new Presence({});
-            pr->setName(res);
-            pr->setAvailability(Shared::Availability::invisible);
-            pr->setLastActivity(QDateTime::currentDateTimeUtc());
-            presences.insert(res, pr);
-            appendChild(pr);
-            pr->addMessage(data);
-            return;
-        }
-        itr.value()->addMessage(data);
-    } else {
-        messages.emplace_back(data);
-        changed(4);
-    }
-}
-
-void Models::Contact::changeMessage(const QString& id, const QMap<QString, QVariant>& data)
-{
-
-    bool found = false;
-    for (Shared::Message& msg : messages) {
-        if (msg.getId() == id) {
-            msg.change(data);
-            found = true;
-            break;
-        }
-    }
-    if (!found) {
-        for (Presence* pr : presences) {
-            found = pr->changeMessage(id, data);
-            if (found) {
-                break;
-            }
-        }
-    }
-}
-
-unsigned int Models::Contact::getMessagesCount() const
-{
-    return messages.size() + childMessages;
-}
-
-void Models::Contact::dropMessages()
-{
-    if (messages.size() > 0) {
-        messages.clear();
-        changed(4);
-    }
-    
-    for (QMap<QString, Presence*>::iterator itr = presences.begin(), end = presences.end(); itr != end; ++itr) {
-        itr.value()->dropMessages();
-    }
-}
-
-void Models::Contact::getMessages(Models::Contact::Messages& container) const
-{
-    for (Messages::const_iterator itr = messages.begin(), end = messages.end(); itr != end; ++itr) {
-        const Shared::Message& msg = *itr;
-        container.push_back(msg);
-    }
-    
-    for (QMap<QString, Presence*>::const_iterator itr = presences.begin(), end = presences.end(); itr != end; ++itr) {
-        itr.value()->getMessages(container);
-    }
-}
-
 void Models::Contact::toOfflineState()
 {
     std::deque<Item*>::size_type size = childItems.size();
@@ -355,75 +240,3 @@ QString Models::Contact::getDisplayedName() const
     return getContactName();
 }
 
-bool Models::Contact::columnInvolvedInDisplay(int col)
-{
-    return Item::columnInvolvedInDisplay(col) && col == 1;
-}
-
-Models::Contact * Models::Contact::copy() const
-{
-    Contact* cnt = new Contact(*this);
-    return cnt;
-}
-
-Models::Contact::Contact(const Models::Contact& other):
-    Item(other),
-    jid(other.jid),
-    availability(other.availability),
-    state(other.state),
-    presences(),
-    messages(other.messages),
-    childMessages(0),
-    account(other.account)
-{
-    for (const Presence* pres : other.presences) {
-        Presence* pCopy = new Presence(*pres);
-        presences.insert(pCopy->getName(), pCopy);
-        Item::appendChild(pCopy);
-        connect(pCopy, &Item::childChanged, this, &Contact::refresh);
-    }
-    
-    refresh();
-}
-
-QString Models::Contact::getAvatarPath() const
-{
-    return avatarPath;
-}
-
-Shared::Avatar Models::Contact::getAvatarState() const
-{
-    return avatarState;
-}
-
-void Models::Contact::setAvatarPath(const QString& path)
-{
-    if (path != avatarPath) {
-        avatarPath = path;
-        changed(7);
-    }
-}
-
-void Models::Contact::setAvatarState(Shared::Avatar p_state)
-{
-    if (avatarState != p_state) {
-        avatarState = p_state;
-        changed(6);
-    }
-}
-
-void Models::Contact::setAvatarState(unsigned int p_state)
-{
-    if (p_state <= static_cast<quint8>(Shared::Avatar::valid)) {
-        Shared::Avatar state = static_cast<Shared::Avatar>(p_state);
-        setAvatarState(state);
-    } else {
-        qDebug() << "An attempt to set invalid avatar state" << p_state << "to the contact" << jid << ", skipping";
-    }
-}
-
-const Models::Account * Models::Contact::getParentAccount() const
-{
-    return account;
-}
-
diff --git a/ui/models/contact.h b/ui/models/contact.h
index c8c99b5..7e76f5b 100644
--- a/ui/models/contact.h
+++ b/ui/models/contact.h
@@ -19,7 +19,7 @@
 #ifndef MODELS_CONTACT_H
 #define MODELS_CONTACT_H
 
-#include "item.h"
+#include "element.h"
 #include "presence.h"
 #include "shared/enums.h"
 #include "shared/message.h"
@@ -31,49 +31,34 @@
 #include <deque>
 
 namespace Models {
-class Account;
     
-class Contact : public Item
+class Contact : public Element
 {
     Q_OBJECT
 public:
-    typedef std::deque<Shared::Message> Messages;
     Contact(const Account* acc, const QString& p_jid, const QMap<QString, QVariant> &data, Item *parentItem = 0);
-    Contact(const Contact& other);
     ~Contact();
     
-    QString getJid() const;
     Shared::Availability getAvailability() const;
     Shared::SubscriptionState getState() const;
-    Shared::Avatar getAvatarState() const;
-    QString getAvatarPath() const;
+
     QIcon getStatusIcon(bool big = false) const;
     
     int columnCount() const override;
     QVariant data(int column) const override;
         
-    void update(const QString& field, const QVariant& value);
+    void update(const QString& field, const QVariant& value) override;
     
     void addPresence(const QString& name, const QMap<QString, QVariant>& data);
     void removePresence(const QString& name);
     
     QString getContactName() const;
     QString getStatus() const;
-    
-    void addMessage(const Shared::Message& data);
-    void changeMessage(const QString& id, const QMap<QString, QVariant>& data);
-    unsigned int getMessagesCount() const;
-    void dropMessages();
-    void getMessages(Messages& container) const;
     QString getDisplayedName() const override;
     
-    Contact* copy() const;
-    
 protected:
     void _removeChild(int index) override;
     void _appendChild(Models::Item * child) override;
-    bool columnInvolvedInDisplay(int col) override;
-    const Account* getParentAccount() const override;
     
 protected slots:
     void refresh();
@@ -84,23 +69,13 @@ protected:
     void setAvailability(unsigned int p_state);
     void setState(Shared::SubscriptionState p_state);
     void setState(unsigned int p_state);
-    void setAvatarState(Shared::Avatar p_state);
-    void setAvatarState(unsigned int p_state);
-    void setAvatarPath(const QString& path);
-    void setJid(const QString p_jid);
     void setStatus(const QString& p_state);
     
 private:
-    QString jid;
     Shared::Availability availability;
     Shared::SubscriptionState state;
-    Shared::Avatar avatarState;
     QMap<QString, Presence*> presences;
-    Messages messages;
-    unsigned int childMessages;
     QString status;
-    QString avatarPath;
-    const Account* account;
 };
 
 }
diff --git a/ui/models/element.cpp b/ui/models/element.cpp
new file mode 100644
index 0000000..98a54a5
--- /dev/null
+++ b/ui/models/element.cpp
@@ -0,0 +1,151 @@
+/*
+ * Squawk messenger. 
+ * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+ * 
+ * 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 "element.h"
+#include "account.h"
+
+#include <QDebug>
+
+Models::Element::Element(Type p_type, const Models::Account* acc, const QString& p_jid, const QMap<QString, QVariant>& data, Models::Item* parentItem):
+    Item(p_type, data, parentItem),
+    jid(p_jid),
+    avatarPath(),
+    avatarState(Shared::Avatar::empty),
+    account(acc),
+    feed(new MessageFeed())
+{
+    connect(feed, &MessageFeed::requestArchive, this, &Element::requestArchive);
+    
+    QMap<QString, QVariant>::const_iterator itr = data.find("avatarState");
+    if (itr != data.end()) {
+        setAvatarState(itr.value().toUInt());
+    }
+    itr = data.find("avatarPath");
+    if (itr != data.end()) {
+        setAvatarPath(itr.value().toString());
+    }
+}
+
+Models::Element::~Element()
+{
+    delete feed;
+}
+
+
+QString Models::Element::getJid() const
+{
+    return jid;
+}
+
+void Models::Element::setJid(const QString& p_jid)
+{
+    if (jid != p_jid) {
+        jid = p_jid;
+        changed(1);
+    }
+}
+
+void Models::Element::update(const QString& field, const QVariant& value)
+{
+    if (field == "jid") {
+        setJid(value.toString());
+    } else if (field == "avatarState") {
+        setAvatarState(value.toUInt());
+    } else if (field == "avatarPath") {
+        setAvatarPath(value.toString());
+    }
+}
+
+QString Models::Element::getAvatarPath() const
+{
+    return avatarPath;
+}
+
+Shared::Avatar Models::Element::getAvatarState() const
+{
+    return avatarState;
+}
+
+void Models::Element::setAvatarPath(const QString& path)
+{
+    if (path != avatarPath) {
+        avatarPath = path;
+        if (type == contact) {
+            changed(7);
+        } else if (type == room) {
+            changed(8);
+        }
+    }
+}
+
+void Models::Element::setAvatarState(Shared::Avatar p_state)
+{
+    if (avatarState != p_state) {
+        avatarState = p_state;
+        if (type == contact) {
+            changed(6);
+        } else if (type == room) {
+            changed(7);
+        }
+    }
+}
+
+void Models::Element::setAvatarState(unsigned int p_state)
+{
+    if (p_state <= static_cast<quint8>(Shared::Avatar::valid)) {
+        Shared::Avatar state = static_cast<Shared::Avatar>(p_state);
+        setAvatarState(state);
+    } else {
+        qDebug() << "An attempt to set invalid avatar state" << p_state << "to the element" << jid << ", skipping";
+    }
+}
+
+bool Models::Element::columnInvolvedInDisplay(int col)
+{
+    return Item::columnInvolvedInDisplay(col) && col == 1;
+}
+
+const Models::Account * Models::Element::getParentAccount() const
+{
+    return account;
+}
+
+unsigned int Models::Element::getMessagesCount() const
+{
+    return feed->unreadMessagesCount();
+}
+
+void Models::Element::addMessage(const Shared::Message& data)
+{
+    feed->addMessage(data);
+    if (type == contact) {
+        changed(4);
+    } else if (type == room) {
+        changed(5);
+    }
+}
+
+void Models::Element::changeMessage(const QString& id, const QMap<QString, QVariant>& data)
+{
+    
+}
+
+void Models::Element::responseArchive(const std::list<Shared::Message> list)
+{
+    feed->responseArchive(list);
+}
diff --git a/ui/models/element.h b/ui/models/element.h
new file mode 100644
index 0000000..29c4e76
--- /dev/null
+++ b/ui/models/element.h
@@ -0,0 +1,70 @@
+/*
+ * Squawk messenger. 
+ * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+ * 
+ * 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/>.
+ */
+
+#ifndef ELEMENT_H
+#define ELEMENT_H
+
+#include "item.h"
+#include "messagefeed.h"
+
+namespace Models {
+    
+class Element : public Item
+{
+    Q_OBJECT
+protected:
+    Element(Type p_type, const Account* acc, const QString& p_jid, const QMap<QString, QVariant> &data, Item *parentItem = 0);
+    ~Element();
+    
+public:
+    QString getJid() const;
+    Shared::Avatar getAvatarState() const;
+    QString getAvatarPath() const;
+    
+    virtual void update(const QString& field, const QVariant& value);
+    
+    void addMessage(const Shared::Message& data);
+    void changeMessage(const QString& id, const QMap<QString, QVariant>& data);
+    unsigned int getMessagesCount() const;
+    void responseArchive(const std::list<Shared::Message> list);
+    
+signals:
+    void requestArchive(const QString& before);
+    
+protected:
+    void setJid(const QString& p_jid);
+    void setAvatarState(Shared::Avatar p_state);
+    void setAvatarState(unsigned int p_state);
+    void setAvatarPath(const QString& path);
+    bool columnInvolvedInDisplay(int col) override;
+    const Account* getParentAccount() const override;
+    
+protected:
+    QString jid;
+    QString avatarPath;
+    Shared::Avatar avatarState;
+    
+    const Account* account;
+
+public:
+    MessageFeed* feed;
+};
+
+}
+
+#endif // ELEMENT_H
diff --git a/ui/models/messagefeed.cpp b/ui/models/messagefeed.cpp
new file mode 100644
index 0000000..bd86b67
--- /dev/null
+++ b/ui/models/messagefeed.cpp
@@ -0,0 +1,145 @@
+/*
+ * Squawk messenger. 
+ * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+ * 
+ * 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 "messagefeed.h"
+
+#include <QDebug>
+
+MessageFeed::MessageFeed(QObject* parent):
+    QAbstractListModel(parent),
+    storage(),
+    indexById(storage.get<id>()),
+    indexByTime(storage.get<time>()),
+    syncState(incomplete)
+{
+}
+
+MessageFeed::~MessageFeed()
+{
+    for (Shared::Message* message : storage) {
+        delete message;
+    }
+}
+
+void MessageFeed::addMessage(const Shared::Message& msg)
+{
+    QString id = msg.getId();
+    StorageById::const_iterator itr = indexById.find(id);
+    if (itr != indexById.end()) {
+        qDebug() << "received more then one message with the same id, skipping yet the new one";
+        return;
+    }
+    
+    Shared::Message* copy = new Shared::Message(msg);
+    StorageByTime::const_iterator tItr = indexByTime.upper_bound(msg.getTime());
+    int position;
+    if (tItr == indexByTime.end()) {
+        position = storage.size();
+    } else {
+        position = indexByTime.rank(tItr);
+    }
+    beginInsertRows(QModelIndex(), position, position);
+    storage.insert(copy);
+    endInsertRows();
+}
+
+void MessageFeed::changeMessage(const QString& id, const Shared::Message& msg)
+{
+}
+
+void MessageFeed::removeMessage(const QString& id)
+{
+}
+
+QVariant MessageFeed::data(const QModelIndex& index, int role) const
+{
+    int i = index.row();
+    if (syncState == syncing) {
+        --i;
+    }
+    QVariant answer;
+    switch (role) {
+        case Qt::DisplayRole: {
+            if (i == -1) {
+                return "Loading...";
+            }
+            
+            StorageByTime::const_iterator itr = indexByTime.nth(i);
+            if (itr != indexByTime.end()) {
+                const Shared::Message* msg = *itr;
+                answer = msg->getFrom() + ": " + msg->getBody();
+            }
+        }
+            break;
+        default:
+            break;
+    }
+    
+    return answer;
+}
+
+int MessageFeed::rowCount(const QModelIndex& parent) const
+{
+    int count = storage.size();
+    if (syncState == syncing) {
+        count++;
+    }
+    return count;
+}
+
+unsigned int MessageFeed::unreadMessagesCount() const
+{
+    return storage.size(); //let's say they are all new for now =)
+}
+
+bool MessageFeed::canFetchMore(const QModelIndex& parent) const
+{
+    return syncState == incomplete;
+}
+
+void MessageFeed::fetchMore(const QModelIndex& parent)
+{
+    if (syncState == incomplete) {
+        beginInsertRows(QModelIndex(), 0, 0);
+        syncState = syncing;
+        endInsertRows();
+        
+        if (storage.size() == 0) {
+            emit requestArchive("");
+        } else {
+            emit requestArchive((*indexByTime.nth(0))->getId());
+        }
+    }
+}
+
+void MessageFeed::responseArchive(const std::list<Shared::Message> list)
+{
+    if (syncState == syncing) {
+        beginRemoveRows(QModelIndex(), 0, 0);
+        syncState = incomplete;
+        endRemoveRows();
+    }
+    
+    beginInsertRows(QModelIndex(), 0, list.size() - 1);
+    for (const Shared::Message& msg : list) {
+        Shared::Message* copy = new Shared::Message(msg);
+        storage.insert(copy);
+    }
+    endInsertRows();
+}
+
diff --git a/ui/models/messagefeed.h b/ui/models/messagefeed.h
new file mode 100644
index 0000000..7ca72f9
--- /dev/null
+++ b/ui/models/messagefeed.h
@@ -0,0 +1,100 @@
+/*
+ * Squawk messenger. 
+ * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+ * 
+ * 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/>.
+ */
+
+#ifndef MESSAGEFEED_H
+#define MESSAGEFEED_H
+
+#include <QAbstractListModel>
+#include <QDateTime>
+#include <QString>
+
+#include <boost/multi_index_container.hpp>
+#include <boost/multi_index/ordered_index.hpp>
+#include <boost/multi_index/ranked_index.hpp>
+#include <boost/multi_index/mem_fun.hpp>
+
+#include <shared/message.h>
+
+
+class MessageFeed : public QAbstractListModel
+{
+    Q_OBJECT
+public:
+    MessageFeed(QObject *parent = nullptr);
+    ~MessageFeed();
+    
+    void addMessage(const Shared::Message& msg);
+    void changeMessage(const QString& id, const Shared::Message& msg);
+    void removeMessage(const QString& id);
+    
+    QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const override;
+    int rowCount(const QModelIndex& parent = QModelIndex()) const override;
+    
+    bool canFetchMore(const QModelIndex & parent) const override;
+    void fetchMore(const QModelIndex & parent) override;
+    void responseArchive(const std::list<Shared::Message> list);
+    
+    unsigned int unreadMessagesCount() const;
+    
+signals:
+    void requestArchive(const QString& before);
+    
+private:
+    enum SyncState {
+        incomplete,
+        syncing,
+        complete
+    };
+    //tags
+    struct id {};
+    struct time {};
+    
+    typedef boost::multi_index_container<
+        Shared::Message*,
+        boost::multi_index::indexed_by<
+            boost::multi_index::ordered_unique<
+                boost::multi_index::tag<id>,
+                boost::multi_index::const_mem_fun<
+                    Shared::Message,
+                    QString,
+                    &Shared::Message::getId
+                >
+            >,
+            boost::multi_index::ranked_non_unique<
+                boost::multi_index::tag<time>,
+                boost::multi_index::const_mem_fun<
+                    Shared::Message,
+                    QDateTime,
+                    &Shared::Message::getTime
+                >
+            >
+        >
+    > Storage; 
+    
+    typedef Storage::index<id>::type StorageById;
+    typedef Storage::index<time>::type StorageByTime;
+    Storage storage;
+    StorageById& indexById;
+    StorageByTime& indexByTime;
+    
+    SyncState syncState;
+    
+
+};
+
+#endif // MESSAGEFEED_H
diff --git a/ui/models/presence.cpp b/ui/models/presence.cpp
index bf931e9..8ba7c47 100644
--- a/ui/models/presence.cpp
+++ b/ui/models/presence.cpp
@@ -20,82 +20,15 @@
 #include "shared/icons.h"
 
 Models::Presence::Presence(const QMap<QString, QVariant>& data, Item* parentItem):
-    AbstractParticipant(Item::presence, data, parentItem),
-    messages()
+    AbstractParticipant(Item::presence, data, parentItem)
 {
 }
 
-Models::Presence::Presence(const Models::Presence& other):
-    AbstractParticipant(other),
-    messages(other.messages)
-{
-}
-
-
 Models::Presence::~Presence()
 {
 }
 
 int Models::Presence::columnCount() const
 {
-    return 5;
-}
-
-QVariant Models::Presence::data(int column) const
-{
-    switch (column) {
-        case 4:
-            return getMessagesCount();
-        default:
-            return AbstractParticipant::data(column);
-    }
-}
-
-unsigned int Models::Presence::getMessagesCount() const
-{
-    return messages.size();
-}
-
-void Models::Presence::addMessage(const Shared::Message& data)
-{
-    messages.emplace_back(data);
-    changed(4);
-}
-
-bool Models::Presence::changeMessage(const QString& id, const QMap<QString, QVariant>& data)
-{
-    bool found = false;
-    for (Shared::Message& msg : messages) {
-        if (msg.getId() == id) {
-            msg.change(data);
-            found = true;
-            break;
-        }
-    }
-    return found;
-}
-
-void Models::Presence::dropMessages()
-{
-    if (messages.size() > 0) {
-        messages.clear();
-        changed(4);
-    }
-}
-
-QIcon Models::Presence::getStatusIcon(bool big) const
-{
-    if (getMessagesCount() > 0) {
-        return Shared::icon("mail-message", big);
-    } else {
-        return AbstractParticipant::getStatusIcon();
-    }
-}
-
-void Models::Presence::getMessages(Models::Presence::Messages& container) const
-{
-    for (Messages::const_iterator itr = messages.begin(), end = messages.end(); itr != end; ++itr) {
-        const Shared::Message& msg = *itr;
-        container.push_back(msg);
-    }
+    return 4;
 }
diff --git a/ui/models/presence.h b/ui/models/presence.h
index fc430f0..fb1a31c 100644
--- a/ui/models/presence.h
+++ b/ui/models/presence.h
@@ -32,25 +32,10 @@ class Presence : public Models::AbstractParticipant
 {
     Q_OBJECT
 public:
-    typedef std::deque<Shared::Message> Messages;
     explicit Presence(const QMap<QString, QVariant> &data, Item *parentItem = 0);
-    Presence(const Presence& other);
     ~Presence();
     
     int columnCount() const override;
-    QVariant data(int column) const override;
-    
-    QIcon getStatusIcon(bool big = false) const override;
-    
-    unsigned int getMessagesCount() const;
-    void dropMessages();
-    void addMessage(const Shared::Message& data);
-    bool changeMessage(const QString& id, const QMap<QString, QVariant>& data);
-    
-    void getMessages(Messages& container) const;
-
-private:
-    Messages messages;
 };
 
 }
diff --git a/ui/models/room.cpp b/ui/models/room.cpp
index cc19d2c..7f83b3f 100644
--- a/ui/models/room.cpp
+++ b/ui/models/room.cpp
@@ -22,16 +22,12 @@
 #include <QIcon>
 #include <QDebug>
 
-Models::Room::Room(const QString& p_jid, const QMap<QString, QVariant>& data, Models::Item* parentItem):
-    Item(room, data, parentItem),
+Models::Room::Room(const Account* acc, const QString& p_jid, const QMap<QString, QVariant>& data, Models::Item* parentItem):
+    Element(room, acc, p_jid, data, parentItem),
     autoJoin(false),
     joined(false),
-    jid(p_jid),
     nick(""),
     subject(""),
-    avatarState(Shared::Avatar::empty),
-    avatarPath(""),
-    messages(),
     participants(),
     exParticipantAvatars()
 {
@@ -55,16 +51,6 @@ Models::Room::Room(const QString& p_jid, const QMap<QString, QVariant>& data, Mo
         setSubject(itr.value().toString());
     }
     
-    itr = data.find("avatarState");
-    if (itr != data.end()) {
-        setAvatarState(itr.value().toUInt());
-    }
-    itr = data.find("avatarPath");
-    if (itr != data.end()) {
-        setAvatarPath(itr.value().toString());
-    }
-    
-    
     itr = data.find("avatars");
     if (itr != data.end()) {
         QMap<QString, QVariant> avs = itr.value().toMap();
@@ -78,21 +64,11 @@ Models::Room::~Room()
 {
 }
 
-unsigned int Models::Room::getUnreadMessagesCount() const
-{
-    return messages.size();
-}
-
 int Models::Room::columnCount() const
 {
     return 7;
 }
 
-QString Models::Room::getJid() const
-{
-    return jid;
-}
-
 bool Models::Room::getAutoJoin() const
 {
     return autoJoin;
@@ -151,14 +127,6 @@ void Models::Room::setAutoJoin(bool p_autoJoin)
     }
 }
 
-void Models::Room::setJid(const QString& p_jid)
-{
-    if (jid != p_jid) {
-        jid = p_jid;
-        changed(1);
-    }
-}
-
 void Models::Room::setJoined(bool p_joined)
 {
     if (joined != p_joined) {
@@ -182,8 +150,6 @@ void Models::Room::update(const QString& field, const QVariant& value)
 {
     if (field == "name") {
         setName(value.toString());
-    } else if (field == "jid") {
-        setJid(value.toString());
     } else if (field == "joined") {
         setJoined(value.toBool());
     } else if (field == "autoJoin") {
@@ -192,16 +158,14 @@ void Models::Room::update(const QString& field, const QVariant& value)
         setNick(value.toString());
     } else if (field == "subject") {
         setSubject(value.toString());
-    } else if (field == "avatarState") {
-        setAvatarState(value.toUInt());
-    } else if (field == "avatarPath") {
-        setAvatarPath(value.toString());
+    } else {
+        Element::update(field, value);
     }
 }
 
 QIcon Models::Room::getStatusIcon(bool big) const
 {
-    if (messages.size() > 0) {
+    if (getMessagesCount() > 0) {
         return Shared::icon("mail-message", big);
     } else {
         if (autoJoin) {
@@ -237,42 +201,6 @@ QString Models::Room::getStatusText() const
     }
 }
 
-unsigned int Models::Room::getMessagesCount() const
-{
-    return messages.size();
-}
-
-void Models::Room::addMessage(const Shared::Message& data)
-{
-    messages.emplace_back(data);
-    changed(5);
-}
-
-void Models::Room::changeMessage(const QString& id, const QMap<QString, QVariant>& data)
-{
-    for (Shared::Message& msg : messages) {
-        if (msg.getId() == id) {
-            msg.change(data);
-            break;
-        }
-    }
-}
-
-void Models::Room::dropMessages()
-{
-    if (messages.size() > 0) {
-        messages.clear();
-        changed(5);
-    }
-}
-
-void Models::Room::getMessages(Models::Room::Messages& container) const
-{
-    for (Messages::const_iterator itr = messages.begin(), end = messages.end(); itr != end; ++itr) {
-        const Shared::Message& msg = *itr;
-        container.push_back(msg);
-    }
-}
 
 void Models::Room::toOfflineState()
 {
@@ -367,47 +295,6 @@ QString Models::Room::getDisplayedName() const
     return getRoomName();
 }
 
-bool Models::Room::columnInvolvedInDisplay(int col)
-{
-    return Item::columnInvolvedInDisplay(col) && col == 1;
-}
-
-QString Models::Room::getAvatarPath() const
-{
-    return avatarPath;
-}
-
-Shared::Avatar Models::Room::getAvatarState() const
-{
-    return avatarState;
-}
-
-void Models::Room::setAvatarPath(const QString& path)
-{
-    if (avatarPath != path) {
-        avatarPath = path;
-        changed(8);
-    }
-}
-
-void Models::Room::setAvatarState(Shared::Avatar p_state)
-{
-    if (avatarState != p_state) {
-        avatarState = p_state;
-        changed(7);
-    }
-}
-
-void Models::Room::setAvatarState(unsigned int p_state)
-{
-    if (p_state <= static_cast<quint8>(Shared::Avatar::valid)) {
-        Shared::Avatar state = static_cast<Shared::Avatar>(p_state);
-        setAvatarState(state);
-    } else {
-        qDebug() << "An attempt to set invalid avatar state" << p_state << "to the room" << jid << ", skipping";
-    }
-}
-
 std::map<QString, const Models::Participant &> Models::Room::getParticipants() const
 {
     std::map<QString, const Models::Participant&> result;
diff --git a/ui/models/room.h b/ui/models/room.h
index 9ea70bf..a51a537 100644
--- a/ui/models/room.h
+++ b/ui/models/room.h
@@ -19,7 +19,7 @@
 #ifndef MODELS_ROOM_H
 #define MODELS_ROOM_H
 
-#include "item.h"
+#include "element.h"
 #include "participant.h"
 #include "shared/enums.h"
 #include "shared/message.h"
@@ -29,21 +29,18 @@ namespace Models {
 /**
  * @todo write docs
  */
-class Room : public Models::Item
+class Room : public Element
 {
     Q_OBJECT
 public:
-    typedef std::deque<Shared::Message> Messages;
-    Room(const QString& p_jid, const QMap<QString, QVariant> &data, Item *parentItem = 0);
+    Room(const Account* acc, const QString& p_jid, const QMap<QString, QVariant> &data, Item *parentItem = 0);
     ~Room();
     
     int columnCount() const override;
     QVariant data(int column) const override;
     
-    unsigned int getUnreadMessagesCount() const;
     bool getJoined() const;
     bool getAutoJoin() const;
-    QString getJid() const;
     QString getNick() const;
     QString getRoomName() const;
     QString getSubject() const;
@@ -53,17 +50,10 @@ public:
     
     void setJoined(bool p_joined);
     void setAutoJoin(bool p_autoJoin);
-    void setJid(const QString& p_jid);
     void setNick(const QString& p_nick);
     void setSubject(const QString& sub);
     
-    void update(const QString& field, const QVariant& value);
-    
-    void addMessage(const Shared::Message& data);
-    void changeMessage(const QString& id, const QMap<QString, QVariant>& data);
-    unsigned int getMessagesCount() const;
-    void dropMessages();
-    void getMessages(Messages& container) const;
+    void update(const QString& field, const QVariant& value) override;
     
     void addParticipant(const QString& name, const QMap<QString, QVariant>& data);
     void changeParticipant(const QString& name, const QMap<QString, QVariant>& data);
@@ -71,8 +61,6 @@ public:
     
     void toOfflineState() override;
     QString getDisplayedName() const override;
-    Shared::Avatar getAvatarState() const;
-    QString getAvatarPath() const;
     std::map<QString, const Participant&> getParticipants() const;
     QString getParticipantIconPath(const QString& name) const;
     std::map<QString, QString> getExParticipantAvatars() const;
@@ -84,24 +72,14 @@ signals:
 private:
     void handleParticipantUpdate(std::map<QString, Participant*>::const_iterator itr, const QMap<QString, QVariant>& data);
     
-protected:
-    bool columnInvolvedInDisplay(int col) override;
-    void setAvatarState(Shared::Avatar p_state);
-    void setAvatarState(unsigned int p_state);
-    void setAvatarPath(const QString& path);
-    
 private:
     bool autoJoin;
     bool joined;
     QString jid;
     QString nick;
     QString subject;
-    Shared::Avatar avatarState;
-    QString avatarPath;
-    Messages messages;
     std::map<QString, Participant*> participants;
     std::map<QString, QString> exParticipantAvatars;
-
 };
 
 }
diff --git a/ui/models/roster.cpp b/ui/models/roster.cpp
index ac90a50..461fbaa 100644
--- a/ui/models/roster.cpp
+++ b/ui/models/roster.cpp
@@ -215,11 +215,7 @@ QVariant Models::Roster::data (const QModelIndex& index, int role) const
                     break;
                 case Item::presence: {
                     Presence* contact = static_cast<Presence*>(item);
-                    QString str("");
-                    int mc = contact->getMessagesCount();
-                    if (mc > 0) {
-                        str += tr("New messages: ") + std::to_string(mc).c_str() + "\n";
-                    }
+                    QString str;
                     Shared::Availability av = contact->getAvailability();
                     str += tr("Availability: ") + Shared::Global::getName(av);
                     QString s = contact->getStatus();
@@ -232,7 +228,7 @@ QVariant Models::Roster::data (const QModelIndex& index, int role) const
                     break;
                 case Item::participant: {
                     Participant* p = static_cast<Participant*>(item);
-                    QString str("");
+                    QString str;
                     Shared::Availability av = p->getAvailability();
                     str += tr("Availability: ") + Shared::Global::getName(av) + "\n";
                     QString s = p->getStatus();
@@ -260,7 +256,7 @@ QVariant Models::Roster::data (const QModelIndex& index, int role) const
                     break;
                 case Item::room: {
                     Room* rm = static_cast<Room*>(item);
-                    unsigned int count = rm->getUnreadMessagesCount();
+                    unsigned int count = rm->getMessagesCount();
                     QString str("");
                     if (count > 0) {
                         str += tr("New messages: ") + std::to_string(count).c_str() + "\n";
@@ -450,6 +446,7 @@ void Models::Roster::addContact(const QString& account, const QString& jid, cons
         std::map<ElId, Contact*>::iterator itr = contacts.find(id);
         if (itr == contacts.end()) {
             contact = new Contact(acc, jid, data);
+            connect(contact, &Contact::requestArchive, this, &Roster::onElementRequestArchive);
             contacts.insert(std::make_pair(id, contact));
         } else {
             contact = itr->second;
@@ -720,20 +717,6 @@ void Models::Roster::addMessage(const QString& account, const Shared::Message& d
     }
 }
 
-void Models::Roster::dropMessages(const QString& account, const QString& jid)
-{
-    ElId id(account, jid);
-    std::map<ElId, Contact*>::iterator itr = contacts.find(id);
-    if (itr != contacts.end()) {
-        itr->second->dropMessages();
-    } else {
-        std::map<ElId, Room*>::const_iterator rItr = rooms.find(id);
-        if (rItr != rooms.end()) {
-            rItr->second->dropMessages();
-        }
-    }
-}
-
 void Models::Roster::removeAccount(const QString& account)
 {
     std::map<QString, Account*>::const_iterator itr = accounts.find(account);
@@ -821,7 +804,8 @@ void Models::Roster::addRoom(const QString& account, const QString jid, const QM
         return;
     }
     
-    Room* room = new Room(jid, data);
+    Room* room = new Room(acc, jid, data);
+    connect(room, &Contact::requestArchive, this, &Roster::onElementRequestArchive);
     rooms.insert(std::make_pair(id, room));
     acc->appendChild(room);
 }
@@ -974,3 +958,23 @@ QModelIndex Models::Roster::getGroupIndex(const QString& account, const QString&
         }
     }
 }
+
+void Models::Roster::onElementRequestArchive(const QString& before)
+{
+    Element* el = static_cast<Element*>(sender());
+    emit requestArchive(el->getAccountName(), el->getJid(), before);
+}
+
+void Models::Roster::responseArchive(const QString& account, const QString& jid, const std::list<Shared::Message>& list)
+{
+    ElId id(account, jid);
+    std::map<ElId, Contact*>::iterator itr = contacts.find(id);
+    if (itr != contacts.end()) {
+        itr->second->responseArchive(list);
+    } else {
+        std::map<ElId, Room*>::const_iterator rItr = rooms.find(id);
+        if (rItr != rooms.end()) {
+            rItr->second->responseArchive(list);
+        }
+    }
+}
diff --git a/ui/models/roster.h b/ui/models/roster.h
index d866b6d..ac72617 100644
--- a/ui/models/roster.h
+++ b/ui/models/roster.h
@@ -58,7 +58,6 @@ public:
     void removePresence(const QString& account, const QString& jid, const QString& name);
     void addMessage(const QString& account, const Shared::Message& data);
     void changeMessage(const QString& account, const QString& jid, const QString& id, const QMap<QString, QVariant>& data);
-    void dropMessages(const QString& account, const QString& jid);
     void addRoom(const QString& account, const QString jid, const QMap<QString, QVariant>& data);
     void changeRoom(const QString& account, const QString jid, const QMap<QString, QVariant>& data);
     void removeRoom(const QString& account, const QString jid);
@@ -81,9 +80,13 @@ public:
     Account* getAccount(const QString& name);
     QModelIndex getAccountIndex(const QString& name);
     QModelIndex getGroupIndex(const QString& account, const QString& name);
+    void responseArchive(const QString& account, const QString& jid, const std::list<Shared::Message>& list);
     
     Accounts* accountsModel;
     
+signals:
+    void requestArchive(const QString& account, const QString& jid, const QString& before);
+    
 private:
     Item* root;
     std::map<QString, Account*> accounts;
@@ -100,6 +103,7 @@ private slots:
     void onChildRemoved();
     void onChildIsAboutToBeMoved(Item* source, int first, int last, Item* destination, int newIndex);
     void onChildMoved();
+    void onElementRequestArchive(const QString& before);
     
 public:
     class ElId {
diff --git a/ui/squawk.cpp b/ui/squawk.cpp
index 41634ad..37150d2 100644
--- a/ui/squawk.cpp
+++ b/ui/squawk.cpp
@@ -62,6 +62,7 @@ Squawk::Squawk(QWidget *parent) :
     connect(m_ui->roster->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &Squawk::onRosterSelectionChanged);
     
     connect(rosterModel.accountsModel, &Models::Accounts::sizeChanged, this, &Squawk::onAccountsSizeChanged);
+    connect(&rosterModel, &Models::Roster::requestArchive, this, &Squawk::onConversationRequestArchive);
     connect(contextMenu, &QMenu::aboutToHide, this, &Squawk::onContextAboutToHide);
     //m_ui->mainToolBar->addWidget(m_ui->comboBox);
     
@@ -336,17 +337,14 @@ void Squawk::onRosterItemDoubleClicked(const QModelIndex& item)
             Models::Account* acc = rosterModel.getAccount(id->account);
             Conversation* conv = 0;
             bool created = false;
-            Models::Contact::Messages deque;
             if (itr != conversations.end()) {
                 conv = itr->second;
             } else if (contact != 0) {
                 created = true;
                 conv = new Chat(acc, contact);
-                contact->getMessages(deque);
             } else if (room != 0) {
                 created = true;
                 conv = new Room(acc, room);
-                room->getMessages(deque);
                 
                 if (!room->getJoined()) {
                     emit setRoomJoined(id->account, id->name, true);
@@ -358,12 +356,6 @@ void Squawk::onRosterItemDoubleClicked(const QModelIndex& item)
                     conv->setAttribute(Qt::WA_DeleteOnClose);
                     subscribeConversation(conv);
                     conversations.insert(std::make_pair(*id, conv));
-                    
-                    if (created) {
-                        for (Models::Contact::Messages::const_iterator itr = deque.begin(), end = deque.end(); itr != end; ++itr) {
-                            conv->addMessage(*itr);
-                        }
-                    }
                 }
                 
                 conv->show();
@@ -380,12 +372,6 @@ void Squawk::onRosterItemDoubleClicked(const QModelIndex& item)
     }
 }
 
-void Squawk::onConversationShown()
-{
-    Conversation* conv = static_cast<Conversation*>(sender());
-    rosterModel.dropMessages(conv->getAccount(), conv->getJid());
-}
-
 void Squawk::onConversationClosed(QObject* parent)
 {
     Conversation* conv = static_cast<Conversation*>(sender());
@@ -505,8 +491,9 @@ void Squawk::accountMessage(const QString& account, const Shared::Message& data)
     Conversations::iterator itr = conversations.find(id);
     bool found = false;
     
+    rosterModel.addMessage(account, data);
+    
     if (currentConversation != 0 && currentConversation->getId() == id) {
-        currentConversation->addMessage(data);
         QApplication::alert(this);
         if (!isVisible() && !data.getForwarded()) {
             notify(account, data);
@@ -516,11 +503,7 @@ void Squawk::accountMessage(const QString& account, const Shared::Message& data)
     
     if (itr != conversations.end()) {
         Conversation* conv = itr->second;
-        conv->addMessage(data);
         QApplication::alert(conv);
-        if (!found && conv->isMinimized()) {
-            rosterModel.addMessage(account, data);
-        }
         if (!conv->isVisible() && !data.getForwarded()) {
             notify(account, data);
         }
@@ -528,7 +511,6 @@ void Squawk::accountMessage(const QString& account, const Shared::Message& data)
     }
     
     if (!found) {
-        rosterModel.addMessage(account, data);
         if (!data.getForwarded()) {
             QApplication::alert(this);
             notify(account, data);
@@ -599,16 +581,7 @@ void Squawk::onConversationMessage(const Shared::Message& msg)
     emit sendMessage(conv->getAccount(), msg);
     Models::Roster::ElId id = conv->getId();
     
-    if (currentConversation != 0 && currentConversation->getId() == id) {
-        if (conv == currentConversation) {
-            Conversations::iterator itr = conversations.find(id);
-            if (itr != conversations.end()) {
-                itr->second->addMessage(msg);
-            }
-        } else {
-            currentConversation->addMessage(msg);
-        }
-    }
+    rosterModel.addMessage(conv->getAccount(), msg);
 }
 
 void Squawk::onConversationMessage(const Shared::Message& msg, const QString& path)
@@ -632,24 +605,14 @@ void Squawk::onConversationMessage(const Shared::Message& msg, const QString& pa
     emit sendMessage(conv->getAccount(), msg, path);
 }
 
-void Squawk::onConversationRequestArchive(const QString& before)
+void Squawk::onConversationRequestArchive(const QString& account, const QString& jid, const QString& before)
 {
-    Conversation* conv = static_cast<Conversation*>(sender());
-    requestArchive(conv->getAccount(), conv->getJid(), 20, before); //TODO amount as a settings value
+    emit requestArchive(account, jid, 20, before); //TODO amount as a settings value
 }
 
 void Squawk::responseArchive(const QString& account, const QString& jid, const std::list<Shared::Message>& list)
 {
-    Models::Roster::ElId id(account, jid);
-    
-    if (currentConversation != 0 && currentConversation->getId() == id) {
-        currentConversation->responseArchive(list);
-    }
-    
-    Conversations::const_iterator itr = conversations.find(id);
-    if (itr != conversations.end()) {
-        itr->second->responseArchive(list);
-    }
+    rosterModel.responseArchive(account, jid, list);
 }
 
 void Squawk::removeAccount(const QString& account)
@@ -661,8 +624,6 @@ void Squawk::removeAccount(const QString& account)
             ++itr;
             Conversation* conv = lItr->second;
             disconnect(conv, &Conversation::destroyed, this, &Squawk::onConversationClosed);
-            disconnect(conv, &Conversation::requestArchive, this, &Squawk::onConversationRequestArchive);
-            disconnect(conv, &Conversation::shown, this, &Squawk::onConversationShown);
             conv->close();
             conversations.erase(lItr);
         } else {
@@ -926,7 +887,6 @@ void Squawk::onActivateVCard(const QString& account, const QString& jid, bool ed
 {
     std::map<QString, VCard*>::const_iterator itr = vCards.find(jid);
     VCard* card;
-    Models::Contact::Messages deque;
     if (itr != vCards.end()) {
         card = itr->second;
     } else {
@@ -1095,10 +1055,8 @@ void Squawk::subscribeConversation(Conversation* conv)
     connect(conv, qOverload<const Shared::Message&>(&Conversation::sendMessage), this, qOverload<const Shared::Message&>(&Squawk::onConversationMessage));
     connect(conv, qOverload<const Shared::Message&, const QString&>(&Conversation::sendMessage), 
             this, qOverload<const Shared::Message&, const QString&>(&Squawk::onConversationMessage));
-    connect(conv, &Conversation::requestArchive, this, &Squawk::onConversationRequestArchive);
     connect(conv, &Conversation::requestLocalFile, this, &Squawk::onConversationRequestLocalFile);
     connect(conv, &Conversation::downloadFile, this, &Squawk::onConversationDownloadFile);
-    connect(conv, &Conversation::shown, this, &Squawk::onConversationShown);
 }
 
 void Squawk::onRosterSelectionChanged(const QModelIndex& current, const QModelIndex& previous)
@@ -1168,13 +1126,10 @@ void Squawk::onRosterSelectionChanged(const QModelIndex& current, const QModelIn
             }
             
             Models::Account* acc = rosterModel.getAccount(id->account);
-            Models::Contact::Messages deque;
             if (contact != 0) {
                 currentConversation = new Chat(acc, contact);
-                contact->getMessages(deque);
             } else if (room != 0) {
                 currentConversation = new Room(acc, room);
-                room->getMessages(deque);
                 
                 if (!room->getJoined()) {
                     emit setRoomJoined(id->account, id->name, true);
@@ -1185,9 +1140,6 @@ void Squawk::onRosterSelectionChanged(const QModelIndex& current, const QModelIn
             }
             
             subscribeConversation(currentConversation);
-            for (Models::Contact::Messages::const_iterator itr = deque.begin(), end = deque.end(); itr != end; ++itr) {
-                currentConversation->addMessage(*itr);
-            }
             
             if (res.size() > 0) {
                 currentConversation->setPalResource(res);
diff --git a/ui/squawk.h b/ui/squawk.h
index a6a27c0..67013cc 100644
--- a/ui/squawk.h
+++ b/ui/squawk.h
@@ -148,9 +148,8 @@ private slots:
     void onRosterItemDoubleClicked(const QModelIndex& item);
     void onConversationMessage(const Shared::Message& msg);
     void onConversationMessage(const Shared::Message& msg, const QString& path);
-    void onConversationRequestArchive(const QString& before);
+    void onConversationRequestArchive(const QString& account, const QString& jid, const QString& before);
     void onRosterContextMenu(const QPoint& point);
-    void onConversationShown();
     void onConversationRequestLocalFile(const QString& messageId, const QString& url);
     void onConversationDownloadFile(const QString& messageId, const QString& url);
     void onItemCollepsed(const QModelIndex& index);
diff --git a/ui/widgets/chat.cpp b/ui/widgets/chat.cpp
index acbcac1..379b01e 100644
--- a/ui/widgets/chat.cpp
+++ b/ui/widgets/chat.cpp
@@ -28,6 +28,8 @@ Chat::Chat(Models::Account* acc, Models::Contact* p_contact, QWidget* parent):
     setAvatar(p_contact->getAvatarPath());
     
     connect(contact, &Models::Contact::childChanged, this, &Chat::onContactChanged);
+    
+    feed->setModel(p_contact->feed);
 }
 
 Chat::~Chat()
@@ -71,31 +73,15 @@ Shared::Message Chat::createMessage() const
     return msg;
 }
 
-void Chat::addMessage(const Shared::Message& data)
-{
-    Conversation::addMessage(data);
-    
-    if (!data.getOutgoing()) {                          //TODO need to check if that was the last message
-        const QString& res = data.getPenPalResource();
-        if (res.size() > 0) {
-            setPalResource(res);
-        }
-    }
-}
-
-void Chat::setName(const QString& name)
-{
-    Conversation::setName(name);
-    line->setPalName(getJid(), name);
-}
-
-void Chat::setAvatar(const QString& path)
-{
-    Conversation::setAvatar(path);
-    
-    if (path.size() == 0) {
-        line->dropPalAvatar(contact->getJid());
-    } else {
-        line->setPalAvatar(contact->getJid(), path);
-    }
-}
+// TODO
+// void Chat::addMessage(const Shared::Message& data)
+// {
+//     Conversation::addMessage(data);
+//     
+//     if (!data.getOutgoing()) {                          //TODO need to check if that was the last message
+//         const QString& res = data.getPenPalResource();
+//         if (res.size() > 0) {
+//             setPalResource(res);
+//         }
+//     }
+// }
diff --git a/ui/widgets/chat.h b/ui/widgets/chat.h
index f05b0fa..c0be972 100644
--- a/ui/widgets/chat.h
+++ b/ui/widgets/chat.h
@@ -35,14 +35,12 @@ public:
     Chat(Models::Account* acc, Models::Contact* p_contact, QWidget* parent = 0);
     ~Chat();
     
-    void addMessage(const Shared::Message & data) override;
-    void setAvatar(const QString& path) override;
+    //void addMessage(const Shared::Message & data) override;
 
 protected slots:
     void onContactChanged(Models::Item* item, int row, int col);
     
 protected:
-    void setName(const QString & name) override;
     Shared::Message createMessage() const override;
     
 private:
diff --git a/ui/widgets/conversation.cpp b/ui/widgets/conversation.cpp
index fd87d9f..fced226 100644
--- a/ui/widgets/conversation.cpp
+++ b/ui/widgets/conversation.cpp
@@ -35,7 +35,6 @@ Conversation::Conversation(bool muc, Models::Account* acc, const QString pJid, c
     account(acc),
     palJid(pJid),
     activePalResource(pRes),
-    line(new MessageLine(muc)),
     m_ui(new Ui::Conversation()),
     ker(),
     scrollResizeCatcher(),
@@ -46,6 +45,7 @@ Conversation::Conversation(bool muc, Models::Account* acc, const QString pJid, c
     filesLayout(0),
     overlay(new QWidget()),
     filesToAttach(),
+    feed(0),
     scroll(down),
     manualSliderChange(false),
     requestingHistory(false),
@@ -53,6 +53,7 @@ Conversation::Conversation(bool muc, Models::Account* acc, const QString pJid, c
     tsb(QApplication::style()->styleHint(QStyle::SH_ScrollBar_Transient) == 1)
 {
     m_ui->setupUi(this);
+    feed = m_ui->feed;
     
     connect(acc, &Models::Account::childChanged, this, &Conversation::onAccountChanged);
     
@@ -67,10 +68,10 @@ Conversation::Conversation(bool muc, Models::Account* acc, const QString pJid, c
     connect(&vis, &VisibilityCatcher::shown, this, &Conversation::onScrollResize);
     connect(&vis, &VisibilityCatcher::hidden, this, &Conversation::onScrollResize);
     connect(m_ui->sendButton, &QPushButton::clicked, this, &Conversation::onEnterPressed);
-    connect(line, &MessageLine::resize, this, &Conversation::onMessagesResize);
-    connect(line, &MessageLine::downloadFile, this, &Conversation::downloadFile);
-    connect(line, &MessageLine::uploadFile, this, qOverload<const Shared::Message&, const QString&>(&Conversation::sendMessage));
-    connect(line, &MessageLine::requestLocalFile, this, &Conversation::requestLocalFile);
+    //connect(line, &MessageLine::resize, this, &Conversation::onMessagesResize);
+    //connect(line, &MessageLine::downloadFile, this, &Conversation::downloadFile);
+    //connect(line, &MessageLine::uploadFile, this, qOverload<const Shared::Message&, const QString&>(&Conversation::sendMessage));
+    //connect(line, &MessageLine::requestLocalFile, this, &Conversation::requestLocalFile);
     connect(m_ui->attachButton, &QPushButton::clicked, this, &Conversation::onAttach);
     connect(m_ui->clearButton, &QPushButton::clicked, this, &Conversation::onClearButton);
     connect(m_ui->messageEditor->document()->documentLayout(), &QAbstractTextDocumentLayout::documentSizeChanged, 
@@ -78,22 +79,22 @@ Conversation::Conversation(bool muc, Models::Account* acc, const QString pJid, c
     
     m_ui->messageEditor->installEventFilter(&ker);
     
-    QScrollBar* vs = m_ui->scrollArea->verticalScrollBar();
-    m_ui->scrollArea->setWidget(line);
-    vs->installEventFilter(&vis);
+    //QScrollBar* vs = m_ui->scrollArea->verticalScrollBar();
+    //m_ui->scrollArea->setWidget(line);
+    //vs->installEventFilter(&vis);
     
-    line->setAutoFillBackground(false);
-    if (testAttribute(Qt::WA_TranslucentBackground)) {
-        m_ui->scrollArea->setAutoFillBackground(false);
-    } else {
-        m_ui->scrollArea->setBackgroundRole(QPalette::Base);
-    }
+    //line->setAutoFillBackground(false);
+    //if (testAttribute(Qt::WA_TranslucentBackground)) {
+        //m_ui->scrollArea->setAutoFillBackground(false);
+    //} else {
+        //m_ui->scrollArea->setBackgroundRole(QPalette::Base);
+    //}
     
-    connect(vs, &QScrollBar::valueChanged, this, &Conversation::onSliderValueChanged);
-    m_ui->scrollArea->installEventFilter(&scrollResizeCatcher);
+    //connect(vs, &QScrollBar::valueChanged, this, &Conversation::onSliderValueChanged);
+    //m_ui->scrollArea->installEventFilter(&scrollResizeCatcher);
     
-    line->setMyAvatarPath(acc->getAvatarPath());
-    line->setMyName(acc->getName());
+    //line->setMyAvatarPath(acc->getAvatarPath());
+    //line->setMyName(acc->getName());
     
     QGridLayout* gr = static_cast<QGridLayout*>(layout());
     QLabel* progressLabel = new QLabel(tr("Drop files here to attach them to your message"));
@@ -129,9 +130,9 @@ void Conversation::onAccountChanged(Models::Item* item, int row, int col)
         if (col == 2 && account->getState() == Shared::ConnectionState::connected) {
             if (!requestingHistory) {
                 requestingHistory = true;
-                line->showBusyIndicator();
-                emit requestArchive("");
-                scroll = down;
+                //line->showBusyIndicator();
+                //emit requestArchive("");
+                //scroll = down;
             }
         }
     }
@@ -139,12 +140,12 @@ void Conversation::onAccountChanged(Models::Item* item, int row, int col)
 
 void Conversation::applyVisualEffects()
 {
-    DropShadowEffect *e1 = new DropShadowEffect;
-    e1->setBlurRadius(10);
-    e1->setColor(Qt::black);
-    e1->setThickness(1);
-    e1->setFrame(true, false, true, false);
-    m_ui->scrollArea->setGraphicsEffect(e1);
+//     DropShadowEffect *e1 = new DropShadowEffect;
+//     e1->setBlurRadius(10);
+//     e1->setColor(Qt::black);
+//     e1->setThickness(1);
+//     e1->setFrame(true, false, true, false);
+//     m_ui->scrollArea->setGraphicsEffect(e1);
 }
 
 void Conversation::setName(const QString& name)
@@ -163,20 +164,9 @@ QString Conversation::getJid() const
     return palJid;
 }
 
-void Conversation::addMessage(const Shared::Message& data)
-{
-    int pos = m_ui->scrollArea->verticalScrollBar()->sliderPosition();
-    int max = m_ui->scrollArea->verticalScrollBar()->maximum();
-    
-    MessageLine::Position place = line->message(data);
-    if (place == MessageLine::invalid) {
-        return;
-    }
-}
-
 void Conversation::changeMessage(const QString& id, const QMap<QString, QVariant>& data)
 {
-    line->changeMessage(id, data);
+//     line->changeMessage(id, data);
 }
 
 KeyEnterReceiver::KeyEnterReceiver(QObject* parent): QObject(parent), ownEvent(false) {}
@@ -226,86 +216,86 @@ void Conversation::onEnterPressed()
         m_ui->messageEditor->clear();
         Shared::Message msg = createMessage();
         msg.setBody(body);
-        addMessage(msg);
+        //addMessage(msg);
         emit sendMessage(msg);
     }
     if (filesToAttach.size() > 0) {
-        for (Badge* badge : filesToAttach) {
-            Shared::Message msg = createMessage();
-            line->appendMessageWithUpload(msg, badge->id);
-            usleep(1000);       //this is required for the messages not to have equal time when appending into messageline
-        }
-        clearAttachedFiles();
+//         for (Badge* badge : filesToAttach) {
+//             Shared::Message msg = createMessage();
+//             line->appendMessageWithUpload(msg, badge->id);
+//             usleep(1000);       //this is required for the messages not to have equal time when appending into messageline
+//         }
+//         clearAttachedFiles();
     }
 }
 
 void Conversation::appendMessageWithUpload(const Shared::Message& data, const QString& path)
 {
-    line->appendMessageWithUploadNoSiganl(data, path);
+//     line->appendMessageWithUploadNoSiganl(data, path);
 }
 
 void Conversation::onMessagesResize(int amount)
 {
-    manualSliderChange = true;
-    switch (scroll) {
-        case down:
-            m_ui->scrollArea->verticalScrollBar()->setValue(m_ui->scrollArea->verticalScrollBar()->maximum());
-            break;
-        case keep: {
-            int max = m_ui->scrollArea->verticalScrollBar()->maximum();
-            int value = m_ui->scrollArea->verticalScrollBar()->value() + amount;
-            m_ui->scrollArea->verticalScrollBar()->setValue(value);
-            
-            if (value == max) {
-                scroll = down;
-            } else {
-                scroll = nothing;
-            }
-        }
-            break;
-        default:
-            break;
-    }
-    manualSliderChange = false;
+//     manualSliderChange = true;
+//     switch (scroll) {
+//         case down:
+//             m_ui->scrollArea->verticalScrollBar()->setValue(m_ui->scrollArea->verticalScrollBar()->maximum());
+//             break;
+//         case keep: {
+//             int max = m_ui->scrollArea->verticalScrollBar()->maximum();
+//             int value = m_ui->scrollArea->verticalScrollBar()->value() + amount;
+//             m_ui->scrollArea->verticalScrollBar()->setValue(value);
+//             
+//             if (value == max) {
+//                 scroll = down;
+//             } else {
+//                 scroll = nothing;
+//             }
+//         }
+//             break;
+//         default:
+//             break;
+//     }
+//     manualSliderChange = false;
 }
 
 void Conversation::onSliderValueChanged(int value)
 {
-    if (!manualSliderChange) {
-        if (value == m_ui->scrollArea->verticalScrollBar()->maximum()) {
-            scroll = down;
-        } else {
-            if (!requestingHistory && value == 0) {
-                requestingHistory = true;
-                line->showBusyIndicator();
-                emit requestArchive(line->firstMessageId());
-                scroll = keep;
-            } else {
-                scroll = nothing;
-            }
-        }
-    }
+//     if (!manualSliderChange) {
+//         if (value == m_ui->scrollArea->verticalScrollBar()->maximum()) {
+//             scroll = down;
+//         } else {
+//             if (!requestingHistory && value == 0) {
+//                 requestingHistory = true;
+//                  line->showBusyIndicator();
+//                  emit requestArchive(line->firstMessageId());
+//                 scroll = keep;
+//             } else {
+//                 scroll = nothing;
+//             }
+//         }
+//     }
 }
 
 void Conversation::responseArchive(const std::list<Shared::Message> list)
 {
-    requestingHistory = false;
-    scroll = keep;
-    
-    line->hideBusyIndicator();
-    for (std::list<Shared::Message>::const_iterator itr = list.begin(), end = list.end(); itr != end; ++itr) {
-        addMessage(*itr);
-    }
+//     requestingHistory = false;
+//     scroll = keep;
+//     
+//     line->hideBusyIndicator();
+//     for (std::list<Shared::Message>::const_iterator itr = list.begin(), end = list.end(); itr != end; ++itr) {
+//         addMessage(*itr);
+//     }
 }
 
 void Conversation::showEvent(QShowEvent* event)
 {
     if (!everShown) {
         everShown = true;
-        line->showBusyIndicator();
+//         line->showBusyIndicator();
         requestingHistory = true;
         scroll = keep;
-        emit requestArchive(line->firstMessageId());
+        emit requestArchive("");
     }
     emit shown();
     
@@ -342,30 +332,30 @@ void Conversation::setStatus(const QString& status)
 
 void Conversation::onScrollResize()
 {
-    if (everShown) {
-        int size = m_ui->scrollArea->width();
-        QScrollBar* bar = m_ui->scrollArea->verticalScrollBar();
-        if (bar->isVisible() && !tsb) {
-            size -= bar->width();
-            
-        }
-        line->setMaximumWidth(size);
-    }
+//     if (everShown) {
+//         int size = m_ui->scrollArea->width();
+//         QScrollBar* bar = m_ui->scrollArea->verticalScrollBar();
+//         if (bar->isVisible() && !tsb) {
+//             size -= bar->width();
+//             
+//         }
+//          line->setMaximumWidth(size);
+//     }
 }
 
 void Conversation::responseFileProgress(const QString& messageId, qreal progress)
 {
-    line->fileProgress(messageId, progress);
+//     line->fileProgress(messageId, progress);
 }
 
 void Conversation::fileError(const QString& messageId, const QString& error)
 {
-    line->fileError(messageId, error);
+//     line->fileError(messageId, error);
 }
 
 void Conversation::responseLocalFile(const QString& messageId, const QString& path)
 {
-    line->responseLocalFile(messageId, path);
+//     line->responseLocalFile(messageId, path);
 }
 
 Models::Roster::ElId Conversation::getId() const
@@ -444,7 +434,7 @@ void Conversation::onTextEditDocSizeChanged(const QSizeF& size)
 
 void Conversation::setFeedFrames(bool top, bool right, bool bottom, bool left)
 {
-    static_cast<DropShadowEffect*>(m_ui->scrollArea->graphicsEffect())->setFrame(top, right, bottom, left);
+    //static_cast<DropShadowEffect*>(m_ui->scrollArea->graphicsEffect())->setFrame(top, right, bottom, left);
 }
 
 void Conversation::dragEnterEvent(QDragEnterEvent* event)
diff --git a/ui/widgets/conversation.h b/ui/widgets/conversation.h
index ea87607..f870e76 100644
--- a/ui/widgets/conversation.h
+++ b/ui/widgets/conversation.h
@@ -24,6 +24,7 @@
 #include <QMap>
 #include <QMimeData>
 #include <QFileInfo>
+#include <QListView>
 
 #include "shared/message.h"
 #include "order.h"
@@ -78,7 +79,6 @@ public:
     QString getAccount() const;
     QString getPalResource() const;
     Models::Roster::ElId getId() const;
-    virtual void addMessage(const Shared::Message& data);
     
     void setPalResource(const QString& res);
     void responseArchive(const std::list<Shared::Message> list);
@@ -135,7 +135,6 @@ protected:
     Models::Account* account;
     QString palJid;
     QString activePalResource;
-    MessageLine* line;
     QScopedPointer<Ui::Conversation> m_ui;
     KeyEnterReceiver ker;
     Resizer scrollResizeCatcher;
@@ -146,6 +145,7 @@ protected:
     FlowLayout* filesLayout;
     QWidget* overlay;
     W::Order<Badge*, Badge::Comparator> filesToAttach;
+    QListView* feed;
     Scroll scroll;
     bool manualSliderChange;
     bool requestingHistory;
diff --git a/ui/widgets/conversation.ui b/ui/widgets/conversation.ui
index 7093bcb..898f0ff 100644
--- a/ui/widgets/conversation.ui
+++ b/ui/widgets/conversation.ui
@@ -215,60 +215,37 @@
        </widget>
       </item>
       <item>
-       <widget class="QScrollArea" name="scrollArea">
-        <property name="autoFillBackground">
-         <bool>true</bool>
-        </property>
+       <widget class="QListView" name="feed">
         <property name="frameShape">
          <enum>QFrame::NoFrame</enum>
         </property>
-        <property name="lineWidth">
-         <number>0</number>
-        </property>
-        <property name="midLineWidth">
-         <number>0</number>
-        </property>
         <property name="horizontalScrollBarPolicy">
          <enum>Qt::ScrollBarAlwaysOff</enum>
         </property>
-        <property name="sizeAdjustPolicy">
-         <enum>QAbstractScrollArea::AdjustIgnored</enum>
+        <property name="editTriggers">
+         <set>QAbstractItemView::NoEditTriggers</set>
         </property>
-        <property name="widgetResizable">
+        <property name="showDropIndicator" stdset="0">
+         <bool>false</bool>
+        </property>
+        <property name="selectionMode">
+         <enum>QAbstractItemView::NoSelection</enum>
+        </property>
+        <property name="verticalScrollMode">
+         <enum>QAbstractItemView::ScrollPerPixel</enum>
+        </property>
+        <property name="horizontalScrollMode">
+         <enum>QAbstractItemView::ScrollPerItem</enum>
+        </property>
+        <property name="isWrapping" stdset="0">
+         <bool>false</bool>
+        </property>
+        <property name="wordWrap">
          <bool>true</bool>
         </property>
-        <widget class="QWidget" name="scrollAreaWidgetContents">
-         <property name="geometry">
-          <rect>
-           <x>0</x>
-           <y>0</y>
-           <width>520</width>
-           <height>385</height>
-          </rect>
-         </property>
-         <layout class="QHBoxLayout" name="horizontalLayout_2">
-          <property name="spacing">
-           <number>0</number>
-          </property>
-          <property name="leftMargin">
-           <number>0</number>
-          </property>
-          <property name="topMargin">
-           <number>0</number>
-          </property>
-          <property name="rightMargin">
-           <number>0</number>
-          </property>
-          <property name="bottomMargin">
-           <number>0</number>
-          </property>
-         </layout>
-        </widget>
        </widget>
       </item>
      </layout>
-     <zorder>scrollArea</zorder>
-     <zorder>widget_3</zorder>
     </widget>
    </item>
    <item row="1" column="0">
diff --git a/ui/widgets/room.cpp b/ui/widgets/room.cpp
index 5bc73b2..8e3b813 100644
--- a/ui/widgets/room.cpp
+++ b/ui/widgets/room.cpp
@@ -23,7 +23,6 @@ Room::Room(Models::Account* acc, Models::Room* p_room, QWidget* parent):
     room(p_room)
 {
     setName(p_room->getName());
-    line->setMyName(room->getNick());
     setStatus(room->getSubject());
     setAvatar(room->getAvatarPath());
     
@@ -31,15 +30,7 @@ Room::Room(Models::Account* acc, Models::Room* p_room, QWidget* parent):
     connect(room, &Models::Room::participantJoined, this, &Room::onParticipantJoined);
     connect(room, &Models::Room::participantLeft, this, &Room::onParticipantLeft);
     
-    std::map<QString, const Models::Participant&> members = room->getParticipants();
-    for (std::pair<QString, const Models::Participant&> pair : members) {
-        QString aPath = pair.second.getAvatarPath();
-        if (aPath.size() > 0) {
-            line->setPalAvatar(pair.first, aPath);
-        }
-    }
-    
-    line->setExPalAvatars(room->getExParticipantAvatars());
+    feed->setModel(p_room->feed);
 }
 
 Room::~Room()
@@ -75,30 +66,14 @@ void Room::onRoomChanged(Models::Item* item, int row, int col)
                 setAvatar(room->getAvatarPath());
                 break;
         }
-    } else {
-        switch (col) {
-            case 7: {
-                Models::Participant* mem = static_cast<Models::Participant*>(item);
-                QString aPath = mem->getAvatarPath();
-                if (aPath.size() > 0) {
-                    line->setPalAvatar(mem->getName(), aPath);
-                } else {
-                    line->dropPalAvatar(mem->getName());
-                }
-            }
-        }
     }
 }
 
 void Room::onParticipantJoined(const Models::Participant& participant)
 {
-    QString aPath = participant.getAvatarPath();
-    if (aPath.size() > 0) {
-        line->setPalAvatar(participant.getName(), aPath);
-    }
+    
 }
 
 void Room::onParticipantLeft(const QString& name)
 {
-    line->movePalAvatarToEx(name);
 }

From 4e6bd04b022a454519737fd382a97e4fb882cf13 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Wed, 12 Aug 2020 19:55:01 +0300
Subject: [PATCH 083/281] experimenting with qml

---
 CMakeLists.txt              |  1 +
 main.cpp                    |  5 +++
 resources/qml/feed.qml      | 79 +++++++++++++++++++++++++++++++++++++
 resources/resources.qrc     |  2 +
 ui/CMakeLists.txt           |  3 +-
 ui/models/messagefeed.cpp   | 74 ++++++++++++++++++++++++----------
 ui/models/messagefeed.h     | 15 ++++++-
 ui/widgets/CMakeLists.txt   |  7 +++-
 ui/widgets/chat.cpp         |  4 +-
 ui/widgets/conversation.cpp | 13 ++++--
 ui/widgets/conversation.h   |  7 ++--
 ui/widgets/conversation.ui  | 31 ---------------
 ui/widgets/room.cpp         |  4 +-
 13 files changed, 176 insertions(+), 69 deletions(-)
 create mode 100644 resources/qml/feed.qml

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 771481f..47599dc 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -12,6 +12,7 @@ include(GNUInstallDirs)
 include_directories(.)
 
 find_package(Qt5Widgets CONFIG REQUIRED)
+find_package(Qt5QuickCompiler CONFIG REQUIRED)
 find_package(Qt5LinguistTools)
 
 if(NOT CMAKE_BUILD_TYPE)
diff --git a/main.cpp b/main.cpp
index 4c4b3ea..ee65d0a 100644
--- a/main.cpp
+++ b/main.cpp
@@ -77,6 +77,11 @@ int main(int argc, char *argv[])
     
     new Shared::Global();        //translates enums
     
+    // QtQuickControls2 Style
+    if (qEnvironmentVariableIsEmpty("QT_QUICK_CONTROLS_STYLE")) {
+        qputenv("QT_QUICK_CONTROLS_STYLE", "Material");
+    }
+    
     Squawk w;
     w.show();
     
diff --git a/resources/qml/feed.qml b/resources/qml/feed.qml
new file mode 100644
index 0000000..3fc0ba8
--- /dev/null
+++ b/resources/qml/feed.qml
@@ -0,0 +1,79 @@
+import QtQuick 2.14
+import QtQuick.Controls 2.14
+import QtQuick.Layouts 1.14
+
+ListView {
+    id: list
+    verticalLayoutDirection: ListView.BottomToTop
+    
+    required model
+    
+    delegate: RowLayout {
+        id: root
+        width: ListView.view.width
+        
+        // placeholder
+        Item {
+            Layout.preferredWidth: root.layoutDirection === Qt.LeftToRight ? 5 : 10
+        }
+        
+//         Avatar {
+//             id: avatar
+//             visible: !sentByMe
+//             avatarUrl: root.avatarUrl
+//             Layout.alignment: Qt.AlignHCenter | Qt.AlignTop
+//             name: root.senderName
+//             Layout.preferredHeight: Kirigami.Units.gridUnit * 2.2
+//             Layout.preferredWidth: Kirigami.Units.gridUnit * 2.2
+//         }
+        
+        Item {
+            Layout.preferredWidth: content.width + 16
+            Layout.preferredHeight: content.height + 16
+            
+            Rectangle {
+                id: messageBubble
+                anchors.fill: parent
+                
+                color: "blue"
+            }
+            
+            ColumnLayout {
+                id: content
+                spacing: 5
+                anchors.centerIn: parent
+                
+                Label {
+                    text: model.sender
+                }
+                
+                Label {
+                    text: model.text
+                    wrapMode: Text.Wrap
+                    Layout.maximumWidth: root.width
+                }
+                
+                // message meta data: date, deliveryState
+                RowLayout {
+                    Layout.bottomMargin: -4
+                    
+                    Label {
+                        id: dateLabel
+                        text: model.date
+                    }
+                    
+//                     Icon {
+//                         source: "edit-symbolic"
+//                         visible: model.correction
+//                         Layout.preferredHeight: 10
+//                         Layout.preferredWidth: 10
+//                     }
+                }
+            }
+        }
+        
+        Item {
+            Layout.fillWidth: true
+        }
+    }
+}
diff --git a/resources/resources.qrc b/resources/resources.qrc
index 4fb3e5b..179b251 100644
--- a/resources/resources.qrc
+++ b/resources/resources.qrc
@@ -160,5 +160,7 @@
     <file>images/fallback/light/small/favorite.svg</file>
     <file>images/fallback/light/small/unfavorite.svg</file>
     <file>images/fallback/light/small/add.svg</file>
+    
+    <file>qml/feed.qml</file>
 </qresource>
 </RCC>
diff --git a/ui/CMakeLists.txt b/ui/CMakeLists.txt
index 0ed806f..4fada80 100644
--- a/ui/CMakeLists.txt
+++ b/ui/CMakeLists.txt
@@ -7,8 +7,7 @@ set(CMAKE_AUTOMOC ON)
 set(CMAKE_AUTOUIC ON)
 
 # Find the QtWidgets library
-find_package(Qt5Widgets CONFIG REQUIRED)
-find_package(Qt5DBus CONFIG REQUIRED)
+find_package(Qt5 CONFIG REQUIRED COMPONENTS Widgets DBus Core)
 find_package(Boost 1.36.0 CONFIG REQUIRED)
 if(Boost_FOUND)
   include_directories(${Boost_INCLUDE_DIRS})
diff --git a/ui/models/messagefeed.cpp b/ui/models/messagefeed.cpp
index bd86b67..a097693 100644
--- a/ui/models/messagefeed.cpp
+++ b/ui/models/messagefeed.cpp
@@ -20,6 +20,15 @@
 
 #include <QDebug>
 
+const QHash<int, QByteArray> MessageFeed::roles = {
+    {Text, "text"},
+    {Sender, "sender"},
+    {Date, "date"},
+    {DeliveryState, "deliveryState"},
+    {Correction, "correction"},
+    {SentByMe,"sentByMe"}
+};
+
 MessageFeed::MessageFeed(QObject* parent):
     QAbstractListModel(parent),
     storage(),
@@ -69,25 +78,43 @@ void MessageFeed::removeMessage(const QString& id)
 QVariant MessageFeed::data(const QModelIndex& index, int role) const
 {
     int i = index.row();
-    if (syncState == syncing) {
-        --i;
-    }
     QVariant answer;
-    switch (role) {
-        case Qt::DisplayRole: {
-            if (i == -1) {
-                return "Loading...";
-            }
-            
-            StorageByTime::const_iterator itr = indexByTime.nth(i);
-            if (itr != indexByTime.end()) {
-                const Shared::Message* msg = *itr;
-                answer = msg->getFrom() + ": " + msg->getBody();
-            }
+    
+    StorageByTime::const_iterator itr = indexByTime.nth(i);
+    if (itr != indexByTime.end()) {
+        const Shared::Message* msg = *itr;
+        
+        switch (role) {
+            case Text: 
+                answer = msg->getBody();
+                break;
+            case Sender: 
+                answer = msg->getFrom();
+                break;
+            case Date: 
+                answer = msg->getTime();
+                break;
+            case DeliveryState: 
+                answer = static_cast<unsigned int>(msg->getState());
+                break;
+            case Correction: 
+                answer = msg->getEdited();
+                break;
+            case SentByMe: 
+                answer = msg->getOutgoing();
+                break;
+            default:
+                break;
+        }
+    } else {
+        switch (role) {
+            case Text: 
+                answer = "loading...";
+                break;
+            default:
+                answer = "";
+                break;
         }
-            break;
-        default:
-            break;
     }
     
     return answer;
@@ -115,27 +142,28 @@ bool MessageFeed::canFetchMore(const QModelIndex& parent) const
 void MessageFeed::fetchMore(const QModelIndex& parent)
 {
     if (syncState == incomplete) {
-        beginInsertRows(QModelIndex(), 0, 0);
+        beginInsertRows(QModelIndex(), storage.size(), storage.size());
         syncState = syncing;
         endInsertRows();
         
         if (storage.size() == 0) {
             emit requestArchive("");
         } else {
-            emit requestArchive((*indexByTime.nth(0))->getId());
+            emit requestArchive((*indexByTime.rbegin())->getId());
         }
     }
 }
 
 void MessageFeed::responseArchive(const std::list<Shared::Message> list)
 {
+    Storage::size_type size = storage.size();
     if (syncState == syncing) {
-        beginRemoveRows(QModelIndex(), 0, 0);
+        beginRemoveRows(QModelIndex(), size, size);
         syncState = incomplete;
         endRemoveRows();
     }
     
-    beginInsertRows(QModelIndex(), 0, list.size() - 1);
+    beginInsertRows(QModelIndex(), size, size + list.size() - 1);
     for (const Shared::Message& msg : list) {
         Shared::Message* copy = new Shared::Message(msg);
         storage.insert(copy);
@@ -143,3 +171,7 @@ void MessageFeed::responseArchive(const std::list<Shared::Message> list)
     endInsertRows();
 }
 
+QHash<int, QByteArray> MessageFeed::roleNames() const
+{
+    return roles;
+}
diff --git a/ui/models/messagefeed.h b/ui/models/messagefeed.h
index 7ca72f9..1aeb476 100644
--- a/ui/models/messagefeed.h
+++ b/ui/models/messagefeed.h
@@ -47,6 +47,8 @@ public:
     
     bool canFetchMore(const QModelIndex & parent) const override;
     void fetchMore(const QModelIndex & parent) override;
+    QHash<int, QByteArray> roleNames() const override;
+    
     void responseArchive(const std::list<Shared::Message> list);
     
     unsigned int unreadMessagesCount() const;
@@ -54,6 +56,15 @@ public:
 signals:
     void requestArchive(const QString& before);
     
+public:
+    enum MessageRoles {
+        Text = Qt::UserRole + 1,
+        Sender,
+        Date,
+        DeliveryState,
+        Correction,
+        SentByMe
+    };
 private:
     enum SyncState {
         incomplete,
@@ -81,7 +92,8 @@ private:
                     Shared::Message,
                     QDateTime,
                     &Shared::Message::getTime
-                >
+                >,
+                std::greater<QDateTime>
             >
         >
     > Storage; 
@@ -94,6 +106,7 @@ private:
     
     SyncState syncState;
     
+    static const QHash<int, QByteArray> roles;
 
 };
 
diff --git a/ui/widgets/CMakeLists.txt b/ui/widgets/CMakeLists.txt
index 29e2dbc..0932100 100644
--- a/ui/widgets/CMakeLists.txt
+++ b/ui/widgets/CMakeLists.txt
@@ -7,7 +7,7 @@ set(CMAKE_AUTOMOC ON)
 set(CMAKE_AUTOUIC ON)
 
 # Find the QtWidgets library
-find_package(Qt5Widgets CONFIG REQUIRED)
+find_package(Qt5Widgets CONFIG REQUIRED COMPONENTS Widgets Quick Qml QuickControls2 Core)
 
 add_subdirectory(vcard)
 
@@ -21,9 +21,12 @@ set(squawkWidgets_SRC
   joinconference.cpp
 )
 
-# Tell CMake to create the helloworld executable
 add_library(squawkWidgets ${squawkWidgets_SRC})
 
 # Use the Widgets module from Qt 5.
 target_link_libraries(squawkWidgets vCardUI)
 target_link_libraries(squawkWidgets Qt5::Widgets)
+target_link_libraries(squawkWidgets Qt5::Qml)
+target_link_libraries(squawkWidgets Qt5::QuickControls2)
+
+qt5_use_modules(squawkWidgets Quick Qml QuickControls2 Core Widgets)
diff --git a/ui/widgets/chat.cpp b/ui/widgets/chat.cpp
index 379b01e..1b20e86 100644
--- a/ui/widgets/chat.cpp
+++ b/ui/widgets/chat.cpp
@@ -19,7 +19,7 @@
 #include "chat.h"
 
 Chat::Chat(Models::Account* acc, Models::Contact* p_contact, QWidget* parent):
-    Conversation(false, acc, p_contact->getJid(), "", parent),
+    Conversation(false, acc, p_contact, p_contact->getJid(), "", parent),
     contact(p_contact)
 {
     setName(p_contact->getContactName());
@@ -28,8 +28,6 @@ Chat::Chat(Models::Account* acc, Models::Contact* p_contact, QWidget* parent):
     setAvatar(p_contact->getAvatarPath());
     
     connect(contact, &Models::Contact::childChanged, this, &Chat::onContactChanged);
-    
-    feed->setModel(p_contact->feed);
 }
 
 Chat::~Chat()
diff --git a/ui/widgets/conversation.cpp b/ui/widgets/conversation.cpp
index fced226..6643865 100644
--- a/ui/widgets/conversation.cpp
+++ b/ui/widgets/conversation.cpp
@@ -29,7 +29,7 @@
 #include <QAbstractTextDocumentLayout>
 #include <QCoreApplication>
 
-Conversation::Conversation(bool muc, Models::Account* acc, const QString pJid, const QString pRes, QWidget* parent):
+Conversation::Conversation(bool muc, Models::Account* acc, Models::Element* el, const QString pJid, const QString pRes, QWidget* parent):
     QWidget(parent),
     isMuc(muc),
     account(acc),
@@ -45,7 +45,7 @@ Conversation::Conversation(bool muc, Models::Account* acc, const QString pJid, c
     filesLayout(0),
     overlay(new QWidget()),
     filesToAttach(),
-    feed(0),
+    feed(new QQuickView()),
     scroll(down),
     manualSliderChange(false),
     requestingHistory(false),
@@ -53,7 +53,14 @@ Conversation::Conversation(bool muc, Models::Account* acc, const QString pJid, c
     tsb(QApplication::style()->styleHint(QStyle::SH_ScrollBar_Transient) == 1)
 {
     m_ui->setupUi(this);
-    feed = m_ui->feed;
+    
+    feed->setColor(QWidget::palette().color(QPalette::Base));
+    feed->setInitialProperties({{"model", QVariant::fromValue(el->feed)}});
+    feed->setResizeMode(QQuickView::SizeRootObjectToView);
+    feed->setSource(QUrl("qrc:/qml/feed.qml"));
+    QWidget *container = QWidget::createWindowContainer(feed, this);
+    container->setAutoFillBackground(false);
+    m_ui->widget->layout()->addWidget(container);
     
     connect(acc, &Models::Account::childChanged, this, &Conversation::onAccountChanged);
     
diff --git a/ui/widgets/conversation.h b/ui/widgets/conversation.h
index f870e76..b0e1b79 100644
--- a/ui/widgets/conversation.h
+++ b/ui/widgets/conversation.h
@@ -24,7 +24,7 @@
 #include <QMap>
 #include <QMimeData>
 #include <QFileInfo>
-#include <QListView>
+#include <QQuickView>
 
 #include "shared/message.h"
 #include "order.h"
@@ -72,7 +72,7 @@ class Conversation : public QWidget
 {
     Q_OBJECT
 public:
-    Conversation(bool muc, Models::Account* acc, const QString pJid, const QString pRes, QWidget* parent = 0);
+    Conversation(bool muc, Models::Account* acc, Models::Element* el, const QString pJid, const QString pRes, QWidget* parent = 0);
     ~Conversation();
     
     QString getJid() const;
@@ -133,6 +133,7 @@ protected:
         down
     };
     Models::Account* account;
+    Models::Element* element;
     QString palJid;
     QString activePalResource;
     QScopedPointer<Ui::Conversation> m_ui;
@@ -145,7 +146,7 @@ protected:
     FlowLayout* filesLayout;
     QWidget* overlay;
     W::Order<Badge*, Badge::Comparator> filesToAttach;
-    QListView* feed;
+    QQuickView* feed;
     Scroll scroll;
     bool manualSliderChange;
     bool requestingHistory;
diff --git a/ui/widgets/conversation.ui b/ui/widgets/conversation.ui
index 898f0ff..bb38666 100644
--- a/ui/widgets/conversation.ui
+++ b/ui/widgets/conversation.ui
@@ -214,37 +214,6 @@
         </layout>
        </widget>
       </item>
-      <item>
-       <widget class="QListView" name="feed">
-        <property name="frameShape">
-         <enum>QFrame::NoFrame</enum>
-        </property>
-        <property name="horizontalScrollBarPolicy">
-         <enum>Qt::ScrollBarAlwaysOff</enum>
-        </property>
-        <property name="editTriggers">
-         <set>QAbstractItemView::NoEditTriggers</set>
-        </property>
-        <property name="showDropIndicator" stdset="0">
-         <bool>false</bool>
-        </property>
-        <property name="selectionMode">
-         <enum>QAbstractItemView::NoSelection</enum>
-        </property>
-        <property name="verticalScrollMode">
-         <enum>QAbstractItemView::ScrollPerPixel</enum>
-        </property>
-        <property name="horizontalScrollMode">
-         <enum>QAbstractItemView::ScrollPerItem</enum>
-        </property>
-        <property name="isWrapping" stdset="0">
-         <bool>false</bool>
-        </property>
-        <property name="wordWrap">
-         <bool>true</bool>
-        </property>
-       </widget>
-      </item>
      </layout>
     </widget>
    </item>
diff --git a/ui/widgets/room.cpp b/ui/widgets/room.cpp
index 8e3b813..91d7255 100644
--- a/ui/widgets/room.cpp
+++ b/ui/widgets/room.cpp
@@ -19,7 +19,7 @@
 #include "room.h"
 
 Room::Room(Models::Account* acc, Models::Room* p_room, QWidget* parent):
-    Conversation(true, acc, p_room->getJid(), "", parent),
+    Conversation(true, acc, p_room, p_room->getJid(), "", parent),
     room(p_room)
 {
     setName(p_room->getName());
@@ -29,8 +29,6 @@ Room::Room(Models::Account* acc, Models::Room* p_room, QWidget* parent):
     connect(room, &Models::Room::childChanged, this, &Room::onRoomChanged);
     connect(room, &Models::Room::participantJoined, this, &Room::onParticipantJoined);
     connect(room, &Models::Room::participantLeft, this, &Room::onParticipantLeft);
-    
-    feed->setModel(p_room->feed);
 }
 
 Room::~Room()

From e54cff0f0c1ce5e3c744f2813c53886c03c14c39 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Sun, 16 Aug 2020 00:48:28 +0300
Subject: [PATCH 084/281] changed my mind, gonna implement the feed on qt
 instead of qml, first tries, nothing working yet

---
 main.cpp                    |   5 --
 resources/qml/feed.qml      |  79 -----------------------
 resources/resources.qrc     |   2 -
 ui/CMakeLists.txt           |   1 +
 ui/models/messagefeed.cpp   |   2 +
 ui/utils/feedview.cpp       | 125 ++++++++++++++++++++++++++++++++++++
 ui/utils/feedview.h         |  65 +++++++++++++++++++
 ui/widgets/CMakeLists.txt   |   6 +-
 ui/widgets/conversation.cpp |  11 +---
 ui/widgets/conversation.h   |   4 +-
 10 files changed, 200 insertions(+), 100 deletions(-)
 delete mode 100644 resources/qml/feed.qml
 create mode 100644 ui/utils/feedview.cpp
 create mode 100644 ui/utils/feedview.h

diff --git a/main.cpp b/main.cpp
index ee65d0a..4c4b3ea 100644
--- a/main.cpp
+++ b/main.cpp
@@ -77,11 +77,6 @@ int main(int argc, char *argv[])
     
     new Shared::Global();        //translates enums
     
-    // QtQuickControls2 Style
-    if (qEnvironmentVariableIsEmpty("QT_QUICK_CONTROLS_STYLE")) {
-        qputenv("QT_QUICK_CONTROLS_STYLE", "Material");
-    }
-    
     Squawk w;
     w.show();
     
diff --git a/resources/qml/feed.qml b/resources/qml/feed.qml
deleted file mode 100644
index 3fc0ba8..0000000
--- a/resources/qml/feed.qml
+++ /dev/null
@@ -1,79 +0,0 @@
-import QtQuick 2.14
-import QtQuick.Controls 2.14
-import QtQuick.Layouts 1.14
-
-ListView {
-    id: list
-    verticalLayoutDirection: ListView.BottomToTop
-    
-    required model
-    
-    delegate: RowLayout {
-        id: root
-        width: ListView.view.width
-        
-        // placeholder
-        Item {
-            Layout.preferredWidth: root.layoutDirection === Qt.LeftToRight ? 5 : 10
-        }
-        
-//         Avatar {
-//             id: avatar
-//             visible: !sentByMe
-//             avatarUrl: root.avatarUrl
-//             Layout.alignment: Qt.AlignHCenter | Qt.AlignTop
-//             name: root.senderName
-//             Layout.preferredHeight: Kirigami.Units.gridUnit * 2.2
-//             Layout.preferredWidth: Kirigami.Units.gridUnit * 2.2
-//         }
-        
-        Item {
-            Layout.preferredWidth: content.width + 16
-            Layout.preferredHeight: content.height + 16
-            
-            Rectangle {
-                id: messageBubble
-                anchors.fill: parent
-                
-                color: "blue"
-            }
-            
-            ColumnLayout {
-                id: content
-                spacing: 5
-                anchors.centerIn: parent
-                
-                Label {
-                    text: model.sender
-                }
-                
-                Label {
-                    text: model.text
-                    wrapMode: Text.Wrap
-                    Layout.maximumWidth: root.width
-                }
-                
-                // message meta data: date, deliveryState
-                RowLayout {
-                    Layout.bottomMargin: -4
-                    
-                    Label {
-                        id: dateLabel
-                        text: model.date
-                    }
-                    
-//                     Icon {
-//                         source: "edit-symbolic"
-//                         visible: model.correction
-//                         Layout.preferredHeight: 10
-//                         Layout.preferredWidth: 10
-//                     }
-                }
-            }
-        }
-        
-        Item {
-            Layout.fillWidth: true
-        }
-    }
-}
diff --git a/resources/resources.qrc b/resources/resources.qrc
index 179b251..4fb3e5b 100644
--- a/resources/resources.qrc
+++ b/resources/resources.qrc
@@ -160,7 +160,5 @@
     <file>images/fallback/light/small/favorite.svg</file>
     <file>images/fallback/light/small/unfavorite.svg</file>
     <file>images/fallback/light/small/add.svg</file>
-    
-    <file>qml/feed.qml</file>
 </qresource>
 </RCC>
diff --git a/ui/CMakeLists.txt b/ui/CMakeLists.txt
index 4fada80..23f5f39 100644
--- a/ui/CMakeLists.txt
+++ b/ui/CMakeLists.txt
@@ -39,6 +39,7 @@ set(squawkUI_SRC
   utils/progress.cpp
   utils/comboboxdelegate.cpp
   utils/dropshadoweffect.cpp
+  utils/feedview.cpp
 )
 
 # Tell CMake to create the helloworld executable
diff --git a/ui/models/messagefeed.cpp b/ui/models/messagefeed.cpp
index a097693..f7e6b4b 100644
--- a/ui/models/messagefeed.cpp
+++ b/ui/models/messagefeed.cpp
@@ -85,6 +85,7 @@ QVariant MessageFeed::data(const QModelIndex& index, int role) const
         const Shared::Message* msg = *itr;
         
         switch (role) {
+            case Qt::DisplayRole:
             case Text: 
                 answer = msg->getBody();
                 break;
@@ -108,6 +109,7 @@ QVariant MessageFeed::data(const QModelIndex& index, int role) const
         }
     } else {
         switch (role) {
+            case Qt::DisplayRole:
             case Text: 
                 answer = "loading...";
                 break;
diff --git a/ui/utils/feedview.cpp b/ui/utils/feedview.cpp
new file mode 100644
index 0000000..fa2adbb
--- /dev/null
+++ b/ui/utils/feedview.cpp
@@ -0,0 +1,125 @@
+/*
+ * Squawk messenger. 
+ * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+ * 
+ * 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 "feedview.h"
+
+#include <QPaintEvent>
+#include <QPainter>
+#include <QDebug>
+
+FeedView::FeedView(QWidget* parent):
+    QAbstractItemView(parent),
+    hints()
+{
+
+}
+
+FeedView::~FeedView()
+{
+}
+
+QModelIndex FeedView::indexAt(const QPoint& point) const
+{
+    return QModelIndex();
+}
+
+void FeedView::scrollTo(const QModelIndex& index, QAbstractItemView::ScrollHint hint)
+{
+}
+
+QRect FeedView::visualRect(const QModelIndex& index) const
+{
+    if (!index.isValid() || index.row() >= hints.size()) {
+        return QRect();
+    } else {
+        const Hint& hint = hints.at(index.row());
+        const QWidget* vp = viewport();
+        return QRect(0, vp->height() - hint.height - hint.offset, vp->width(), hint.height);
+    }
+}
+
+int FeedView::horizontalOffset() const
+{
+    return 0;
+}
+
+bool FeedView::isIndexHidden(const QModelIndex& index) const
+{
+    return true;
+}
+
+QModelIndex FeedView::moveCursor(QAbstractItemView::CursorAction cursorAction, Qt::KeyboardModifiers modifiers)
+{
+    return QModelIndex();
+}
+
+void FeedView::setSelection(const QRect& rect, QItemSelectionModel::SelectionFlags command)
+{
+}
+
+int FeedView::verticalOffset() const
+{
+    return 0;
+}
+
+QRegion FeedView::visualRegionForSelection(const QItemSelection& selection) const
+{
+    return QRegion();
+}
+
+void FeedView::rowsInserted(const QModelIndex& parent, int start, int end)
+{
+    scheduleDelayedItemsLayout();
+    QAbstractItemView::rowsInserted(parent, start, end);
+}
+
+void FeedView::updateGeometries()
+{
+    qDebug() << "updateGeometries";
+    QAbstractItemView::updateGeometries();
+    const QAbstractItemModel* m = model();
+    QStyleOptionViewItem option = viewOptions();
+    uint32_t previousOffset = 0;
+    
+    hints.clear();
+    for (int i = 0, size = m->rowCount(); i < size; ++i) {
+        QModelIndex index = m->index(i, 0, QModelIndex());
+        int height = itemDelegate(index)->sizeHint(option, index).height();
+        hints.emplace_back(Hint({
+            false,
+            previousOffset,
+            static_cast<uint32_t>(height)
+        }));
+        previousOffset += height;
+    }
+}
+
+void FeedView::paintEvent(QPaintEvent* event)
+{
+    qDebug() << "paint";
+    const QAbstractItemModel* m = model();
+    QRect zone = event->rect().translated(horizontalOffset(), -verticalOffset());
+    QPainter painter(viewport());
+    QStyleOptionViewItem option = viewOptions();
+    
+    for (int i = 0, size = m->rowCount(); i < size; ++i) {
+        QModelIndex index = m->index(i, 0, QModelIndex());
+        option.rect = visualRect(index);
+        itemDelegate(index)->paint(&painter, option, index);
+    }
+}
diff --git a/ui/utils/feedview.h b/ui/utils/feedview.h
new file mode 100644
index 0000000..8cba4f7
--- /dev/null
+++ b/ui/utils/feedview.h
@@ -0,0 +1,65 @@
+/*
+ * Squawk messenger. 
+ * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+ * 
+ * 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/>.
+ */
+
+#ifndef FEEDVIEW_H
+#define FEEDVIEW_H
+
+#include <QAbstractItemView>
+
+#include <deque>
+
+#include <ui/models/messagefeed.h>
+
+/**
+ * @todo write docs
+ */
+class FeedView : public QAbstractItemView
+{
+    Q_OBJECT
+public:
+    FeedView(QWidget* parent = nullptr);
+    ~FeedView();
+    
+    QModelIndex indexAt(const QPoint & point) const override;
+    void scrollTo(const QModelIndex & index, QAbstractItemView::ScrollHint hint) override;
+    QRect visualRect(const QModelIndex & index) const override;
+    bool isIndexHidden(const QModelIndex & index) const override;
+    QModelIndex moveCursor(QAbstractItemView::CursorAction cursorAction, Qt::KeyboardModifiers modifiers) override;
+    void setSelection(const QRect & rect, QItemSelectionModel::SelectionFlags command) override;
+    QRegion visualRegionForSelection(const QItemSelection & selection) const override;
+    
+protected slots:
+    void rowsInserted(const QModelIndex & parent, int start, int end) override;
+    
+protected:
+    int verticalOffset() const override;
+    int horizontalOffset() const override;
+    void paintEvent(QPaintEvent * event) override;
+    void updateGeometries() override;
+    
+private:
+    struct Hint {
+        bool dirty;
+        uint32_t offset;
+        uint32_t height;
+    };
+    std::deque<Hint> hints;
+    
+};
+
+#endif //FEEDVIEW_H
diff --git a/ui/widgets/CMakeLists.txt b/ui/widgets/CMakeLists.txt
index 0932100..0a21f04 100644
--- a/ui/widgets/CMakeLists.txt
+++ b/ui/widgets/CMakeLists.txt
@@ -7,7 +7,7 @@ set(CMAKE_AUTOMOC ON)
 set(CMAKE_AUTOUIC ON)
 
 # Find the QtWidgets library
-find_package(Qt5Widgets CONFIG REQUIRED COMPONENTS Widgets Quick Qml QuickControls2 Core)
+find_package(Qt5Widgets CONFIG REQUIRED COMPONENTS Widgets Core)
 
 add_subdirectory(vcard)
 
@@ -26,7 +26,5 @@ add_library(squawkWidgets ${squawkWidgets_SRC})
 # Use the Widgets module from Qt 5.
 target_link_libraries(squawkWidgets vCardUI)
 target_link_libraries(squawkWidgets Qt5::Widgets)
-target_link_libraries(squawkWidgets Qt5::Qml)
-target_link_libraries(squawkWidgets Qt5::QuickControls2)
 
-qt5_use_modules(squawkWidgets Quick Qml QuickControls2 Core Widgets)
+qt5_use_modules(squawkWidgets Core Widgets)
diff --git a/ui/widgets/conversation.cpp b/ui/widgets/conversation.cpp
index 6643865..0326ed2 100644
--- a/ui/widgets/conversation.cpp
+++ b/ui/widgets/conversation.cpp
@@ -45,7 +45,7 @@ Conversation::Conversation(bool muc, Models::Account* acc, Models::Element* el,
     filesLayout(0),
     overlay(new QWidget()),
     filesToAttach(),
-    feed(new QQuickView()),
+    feed(new FeedView()),
     scroll(down),
     manualSliderChange(false),
     requestingHistory(false),
@@ -54,13 +54,8 @@ Conversation::Conversation(bool muc, Models::Account* acc, Models::Element* el,
 {
     m_ui->setupUi(this);
     
-    feed->setColor(QWidget::palette().color(QPalette::Base));
-    feed->setInitialProperties({{"model", QVariant::fromValue(el->feed)}});
-    feed->setResizeMode(QQuickView::SizeRootObjectToView);
-    feed->setSource(QUrl("qrc:/qml/feed.qml"));
-    QWidget *container = QWidget::createWindowContainer(feed, this);
-    container->setAutoFillBackground(false);
-    m_ui->widget->layout()->addWidget(container);
+    feed->setModel(el->feed);
+    m_ui->widget->layout()->addWidget(feed);
     
     connect(acc, &Models::Account::childChanged, this, &Conversation::onAccountChanged);
     
diff --git a/ui/widgets/conversation.h b/ui/widgets/conversation.h
index b0e1b79..bc5863e 100644
--- a/ui/widgets/conversation.h
+++ b/ui/widgets/conversation.h
@@ -24,7 +24,6 @@
 #include <QMap>
 #include <QMimeData>
 #include <QFileInfo>
-#include <QQuickView>
 
 #include "shared/message.h"
 #include "order.h"
@@ -34,6 +33,7 @@
 #include "ui/utils/resizer.h"
 #include "ui/utils/flowlayout.h"
 #include "ui/utils/badge.h"
+#include "ui/utils/feedview.h"
 #include "shared/icons.h"
 #include "shared/utils.h"
 
@@ -146,7 +146,7 @@ protected:
     FlowLayout* filesLayout;
     QWidget* overlay;
     W::Order<Badge*, Badge::Comparator> filesToAttach;
-    QQuickView* feed;
+    FeedView* feed;
     Scroll scroll;
     bool manualSliderChange;
     bool requestingHistory;

From e1eea2f3a244892e2a921c62575bc3aa29cac49f Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Mon, 17 Aug 2020 13:27:14 +0300
Subject: [PATCH 085/281] made the first prototype, scrolling and word wrapping
 seems to be working

---
 ui/utils/feedview.cpp | 145 ++++++++++++++++++++++++++++++++++++------
 ui/utils/feedview.h   |   5 ++
 2 files changed, 132 insertions(+), 18 deletions(-)

diff --git a/ui/utils/feedview.cpp b/ui/utils/feedview.cpp
index fa2adbb..efdf2cd 100644
--- a/ui/utils/feedview.cpp
+++ b/ui/utils/feedview.cpp
@@ -20,13 +20,19 @@
 
 #include <QPaintEvent>
 #include <QPainter>
+#include <QScrollBar>
 #include <QDebug>
 
+constexpr int maxMessageHeight = 10000;
+constexpr int approximateSingleMessageHeight = 20;
+
 FeedView::FeedView(QWidget* parent):
     QAbstractItemView(parent),
-    hints()
+    hints(),
+    vo(0)
 {
-
+    horizontalScrollBar()->setRange(0, 0);
+    verticalScrollBar()->setSingleStep(approximateSingleMessageHeight);
 }
 
 FeedView::~FeedView()
@@ -35,6 +41,17 @@ FeedView::~FeedView()
 
 QModelIndex FeedView::indexAt(const QPoint& point) const
 {
+    int32_t totalHeight = viewport()->height() + vo;
+    if (point.y() <= totalHeight) {                      //if it's bigger - someone wants to know the index below the feed beginning, it's invalid
+        uint32_t y = totalHeight - point.y();
+        
+        for (std::deque<Hint>::size_type i = 0; i < hints.size(); ++i) {
+            if (y > hints[i].offset) {
+                return model()->index(i - 1, 0, rootIndex());
+            }
+        }
+    }
+    
     return QModelIndex();
 }
 
@@ -45,11 +62,12 @@ void FeedView::scrollTo(const QModelIndex& index, QAbstractItemView::ScrollHint
 QRect FeedView::visualRect(const QModelIndex& index) const
 {
     if (!index.isValid() || index.row() >= hints.size()) {
+        qDebug() << "visualRect for" << index.row();
         return QRect();
     } else {
         const Hint& hint = hints.at(index.row());
         const QWidget* vp = viewport();
-        return QRect(0, vp->height() - hint.height - hint.offset, vp->width(), hint.height);
+        return QRect(0, vp->height() - hint.height - hint.offset + vo, vp->width(), hint.height);
     }
 }
 
@@ -60,7 +78,7 @@ int FeedView::horizontalOffset() const
 
 bool FeedView::isIndexHidden(const QModelIndex& index) const
 {
-    return true;
+    return false;
 }
 
 QModelIndex FeedView::moveCursor(QAbstractItemView::CursorAction cursorAction, Qt::KeyboardModifiers modifiers)
@@ -74,7 +92,7 @@ void FeedView::setSelection(const QRect& rect, QItemSelectionModel::SelectionFla
 
 int FeedView::verticalOffset() const
 {
-    return 0;
+    return vo;
 }
 
 QRegion FeedView::visualRegionForSelection(const QItemSelection& selection) const
@@ -84,22 +102,80 @@ QRegion FeedView::visualRegionForSelection(const QItemSelection& selection) cons
 
 void FeedView::rowsInserted(const QModelIndex& parent, int start, int end)
 {
-    scheduleDelayedItemsLayout();
+    updateGeometries();
     QAbstractItemView::rowsInserted(parent, start, end);
 }
 
 void FeedView::updateGeometries()
 {
     qDebug() << "updateGeometries";
-    QAbstractItemView::updateGeometries();
-    const QAbstractItemModel* m = model();
-    QStyleOptionViewItem option = viewOptions();
-    uint32_t previousOffset = 0;
+    QScrollBar* bar = verticalScrollBar();
     
-    hints.clear();
+    QAbstractItemView::updateGeometries();
+    
+    const QStyle* st = style();
+    const QAbstractItemModel* m = model();
+    QRect layoutBounds = QRect(QPoint(), maximumViewportSize());
+    QStyleOptionViewItem option = viewOptions();
+    option.rect.setHeight(maxMessageHeight);
+    option.rect.setWidth(layoutBounds.width());
+    int frameAroundContents = 0;
+    int verticalScrollBarExtent = st->pixelMetric(QStyle::PM_ScrollBarExtent, 0, bar);
+    
+    bool layedOut = false;
+    if (verticalScrollBarExtent != 0 && verticalScrollBarPolicy() == Qt::ScrollBarAsNeeded && m->rowCount() * approximateSingleMessageHeight < layoutBounds.height()) {
+        hints.clear();
+        layedOut = tryToCalculateGeometriesWithNoScrollbars(option, m, layoutBounds.height());
+    }
+    
+    if (layedOut) {
+        bar->setRange(0, 0);
+    } else {
+        int verticalMargin = 0;
+        if (st->styleHint(QStyle::SH_ScrollView_FrameOnlyAroundContents)) {
+            frameAroundContents = st->pixelMetric(QStyle::PM_DefaultFrameWidth) * 2;
+        }
+        
+        if (verticalScrollBarPolicy() == Qt::ScrollBarAsNeeded) {
+            verticalMargin = verticalScrollBarExtent + frameAroundContents;
+        }
+        
+        layoutBounds.adjust(0, 0, -verticalMargin, 0);
+        
+        option.features |= QStyleOptionViewItem::WrapText;
+        option.rect.setWidth(layoutBounds.width());
+        
+        hints.clear();
+        uint32_t previousOffset = 0;
+        for (int i = 0, size = m->rowCount(); i < size; ++i) {
+            QModelIndex index = m->index(i, 0, rootIndex());
+            int height = itemDelegate(index)->sizeHint(option, index).height();
+            hints.emplace_back(Hint({
+                false,
+                previousOffset,
+                static_cast<uint32_t>(height)
+            }));
+            previousOffset += height;
+        }
+        
+        bar->setRange(0, previousOffset - layoutBounds.height());
+        bar->setPageStep(layoutBounds.height());
+        bar->setValue(previousOffset - layoutBounds.height() - vo);
+    }
+}
+
+bool FeedView::tryToCalculateGeometriesWithNoScrollbars(const QStyleOptionViewItem& option, const QAbstractItemModel* m, uint32_t totalHeight)
+{
+    uint32_t previousOffset = 0;
+    bool success = true;
     for (int i = 0, size = m->rowCount(); i < size; ++i) {
-        QModelIndex index = m->index(i, 0, QModelIndex());
+        QModelIndex index = m->index(i, 0, rootIndex());
         int height = itemDelegate(index)->sizeHint(option, index).height();
+        
+        if (previousOffset + height > totalHeight) {
+            success = false;
+            break;
+        }
         hints.emplace_back(Hint({
             false,
             previousOffset,
@@ -107,19 +183,52 @@ void FeedView::updateGeometries()
         }));
         previousOffset += height;
     }
+    
+    return success;
 }
 
+
 void FeedView::paintEvent(QPaintEvent* event)
 {
-    qDebug() << "paint";
+    qDebug() << "paint" << event->rect();
     const QAbstractItemModel* m = model();
-    QRect zone = event->rect().translated(horizontalOffset(), -verticalOffset());
-    QPainter painter(viewport());
-    QStyleOptionViewItem option = viewOptions();
+    QWidget* vp = viewport();
+    QRect zone = event->rect().translated(0, -vo);
+    uint32_t vph = vp->height(); 
+    int32_t y1 = zone.y();
+    int32_t y2 = y1 + zone.height();
     
-    for (int i = 0, size = m->rowCount(); i < size; ++i) {
-        QModelIndex index = m->index(i, 0, QModelIndex());
+    bool inZone = false;
+    std::deque<QModelIndex> toRener;
+    for (std::deque<Hint>::size_type i = 0; i < hints.size(); ++i) {
+        const Hint& hint = hints[i];
+        int32_t relativeY1 = vph - hint.offset - hint.height;
+        if (!inZone) {
+            if (y2 > relativeY1) {
+                inZone = true;
+            }
+        }
+        if (inZone) {
+            toRener.emplace_back(m->index(i, 0, rootIndex()));
+        }
+        if (y1 > relativeY1) {
+            break;
+        }
+    }
+    
+    QPainter painter(vp);
+    QStyleOptionViewItem option = viewOptions();
+    option.features = QStyleOptionViewItem::WrapText;
+    
+    for (const QModelIndex& index : toRener) {
         option.rect = visualRect(index);
         itemDelegate(index)->paint(&painter, option, index);
     }
 }
+
+void FeedView::verticalScrollbarValueChanged(int value)
+{
+    vo = verticalScrollBar()->maximum() - value;
+    
+    QAbstractItemView::verticalScrollbarValueChanged(vo);
+}
diff --git a/ui/utils/feedview.h b/ui/utils/feedview.h
index 8cba4f7..423725e 100644
--- a/ui/utils/feedview.h
+++ b/ui/utils/feedview.h
@@ -45,6 +45,7 @@ public:
     
 protected slots:
     void rowsInserted(const QModelIndex & parent, int start, int end) override;
+    void verticalScrollbarValueChanged(int value) override;
     
 protected:
     int verticalOffset() const override;
@@ -52,6 +53,9 @@ protected:
     void paintEvent(QPaintEvent * event) override;
     void updateGeometries() override;
     
+private:
+    bool tryToCalculateGeometriesWithNoScrollbars(const QStyleOptionViewItem& option, const QAbstractItemModel* model, uint32_t totalHeight);
+    
 private:
     struct Hint {
         bool dirty;
@@ -59,6 +63,7 @@ private:
         uint32_t height;
     };
     std::deque<Hint> hints;
+    int vo;
     
 };
 

From e0ef1ef7974ed483ea0a3e4d8096f439f3d9ca4b Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Fri, 21 Aug 2020 00:32:30 +0300
Subject: [PATCH 086/281] Some basic message painting

---
 ui/CMakeLists.txt            |   1 +
 ui/models/account.cpp        |   2 +-
 ui/models/account.h          |   2 +-
 ui/models/element.cpp        |   7 +-
 ui/models/element.h          |   1 +
 ui/models/item.cpp           |   9 +++
 ui/models/item.h             |   1 +
 ui/models/messagefeed.cpp    | 102 +++++++++++++++++----------
 ui/models/messagefeed.h      |  16 ++++-
 ui/models/room.cpp           |   7 +-
 ui/utils/feedview.cpp        |   5 ++
 ui/utils/feedview.h          |   2 +
 ui/utils/messagedelegate.cpp | 130 +++++++++++++++++++++++++++++++++++
 ui/utils/messagedelegate.h   |  50 ++++++++++++++
 ui/widgets/conversation.cpp  |   3 +
 ui/widgets/conversation.h    |   2 +
 16 files changed, 296 insertions(+), 44 deletions(-)
 create mode 100644 ui/utils/messagedelegate.cpp
 create mode 100644 ui/utils/messagedelegate.h

diff --git a/ui/CMakeLists.txt b/ui/CMakeLists.txt
index 23f5f39..c4a8aa6 100644
--- a/ui/CMakeLists.txt
+++ b/ui/CMakeLists.txt
@@ -40,6 +40,7 @@ set(squawkUI_SRC
   utils/comboboxdelegate.cpp
   utils/dropshadoweffect.cpp
   utils/feedview.cpp
+  utils/messagedelegate.cpp
 )
 
 # Tell CMake to create the helloworld executable
diff --git a/ui/models/account.cpp b/ui/models/account.cpp
index 00dd6b2..f8d0c37 100644
--- a/ui/models/account.cpp
+++ b/ui/models/account.cpp
@@ -231,7 +231,7 @@ void Models::Account::toOfflineState()
     Item::toOfflineState();
 }
 
-QString Models::Account::getAvatarPath()
+QString Models::Account::getAvatarPath() const
 {
     return avatarPath;
 }
diff --git a/ui/models/account.h b/ui/models/account.h
index 2563382..686d4da 100644
--- a/ui/models/account.h
+++ b/ui/models/account.h
@@ -57,7 +57,7 @@ namespace Models {
         QString getError() const;
         
         void setAvatarPath(const QString& path);
-        QString getAvatarPath();
+        QString getAvatarPath() const;
         
         void setAvailability(Shared::Availability p_avail);
         void setAvailability(unsigned int p_avail);
diff --git a/ui/models/element.cpp b/ui/models/element.cpp
index 98a54a5..88d990c 100644
--- a/ui/models/element.cpp
+++ b/ui/models/element.cpp
@@ -27,7 +27,7 @@ Models::Element::Element(Type p_type, const Models::Account* acc, const QString&
     avatarPath(),
     avatarState(Shared::Avatar::empty),
     account(acc),
-    feed(new MessageFeed())
+    feed(new MessageFeed(this))
 {
     connect(feed, &MessageFeed::requestArchive, this, &Element::requestArchive);
     
@@ -149,3 +149,8 @@ void Models::Element::responseArchive(const std::list<Shared::Message> list)
 {
     feed->responseArchive(list);
 }
+
+bool Models::Element::isRoom() const
+{
+    return type != contact;
+}
diff --git a/ui/models/element.h b/ui/models/element.h
index 29c4e76..41cb642 100644
--- a/ui/models/element.h
+++ b/ui/models/element.h
@@ -42,6 +42,7 @@ public:
     void changeMessage(const QString& id, const QMap<QString, QVariant>& data);
     unsigned int getMessagesCount() const;
     void responseArchive(const std::list<Shared::Message> list);
+    bool isRoom() const;
     
 signals:
     void requestArchive(const QString& before);
diff --git a/ui/models/item.cpp b/ui/models/item.cpp
index e006ad0..4a88dd2 100644
--- a/ui/models/item.cpp
+++ b/ui/models/item.cpp
@@ -283,6 +283,15 @@ Shared::ConnectionState Models::Item::getAccountConnectionState() const
     return acc->getState();
 }
 
+QString Models::Item::getAccountAvatarPath() const
+{
+    const Account* acc = getParentAccount();
+    if (acc == nullptr) {
+        return "";
+    }
+    return acc->getAvatarPath();
+}
+
 QString Models::Item::getDisplayedName() const
 {
     return name;
diff --git a/ui/models/item.h b/ui/models/item.h
index 4f3e29a..4661479 100644
--- a/ui/models/item.h
+++ b/ui/models/item.h
@@ -80,6 +80,7 @@ class Item : public QObject{
         QString getAccountName() const;
         QString getAccountJid() const;
         QString getAccountResource() const;
+        QString getAccountAvatarPath() const;
         Shared::ConnectionState getAccountConnectionState() const;
         Shared::Availability getAccountAvailability() const;
         
diff --git a/ui/models/messagefeed.cpp b/ui/models/messagefeed.cpp
index f7e6b4b..5226ed3 100644
--- a/ui/models/messagefeed.cpp
+++ b/ui/models/messagefeed.cpp
@@ -17,35 +17,39 @@
  */
 
 #include "messagefeed.h"
+#include "element.h"
+#include "room.h"
 
 #include <QDebug>
 
-const QHash<int, QByteArray> MessageFeed::roles = {
+const QHash<int, QByteArray> Models::MessageFeed::roles = {
     {Text, "text"},
     {Sender, "sender"},
     {Date, "date"},
     {DeliveryState, "deliveryState"},
     {Correction, "correction"},
-    {SentByMe,"sentByMe"}
+    {SentByMe,"sentByMe"},
+    {Avatar, "avatar"}
 };
 
-MessageFeed::MessageFeed(QObject* parent):
+Models::MessageFeed::MessageFeed(const Element* ri, QObject* parent):
     QAbstractListModel(parent),
     storage(),
     indexById(storage.get<id>()),
     indexByTime(storage.get<time>()),
+    rosterItem(ri),
     syncState(incomplete)
 {
 }
 
-MessageFeed::~MessageFeed()
+Models::MessageFeed::~MessageFeed()
 {
     for (Shared::Message* message : storage) {
         delete message;
     }
 }
 
-void MessageFeed::addMessage(const Shared::Message& msg)
+void Models::MessageFeed::addMessage(const Shared::Message& msg)
 {
     QString id = msg.getId();
     StorageById::const_iterator itr = indexById.find(id);
@@ -67,15 +71,15 @@ void MessageFeed::addMessage(const Shared::Message& msg)
     endInsertRows();
 }
 
-void MessageFeed::changeMessage(const QString& id, const Shared::Message& msg)
+void Models::MessageFeed::changeMessage(const QString& id, const Shared::Message& msg)
 {
 }
 
-void MessageFeed::removeMessage(const QString& id)
+void Models::MessageFeed::removeMessage(const QString& id)
 {
 }
 
-QVariant MessageFeed::data(const QModelIndex& index, int role) const
+QVariant Models::MessageFeed::data(const QModelIndex& index, int role) const
 {
     int i = index.row();
     QVariant answer;
@@ -90,7 +94,19 @@ QVariant MessageFeed::data(const QModelIndex& index, int role) const
                 answer = msg->getBody();
                 break;
             case Sender: 
-                answer = msg->getFrom();
+                if (rosterItem->isRoom()) {
+                    if (sentByMe(*msg)) {
+                        answer = rosterItem->getDisplayedName();
+                    } else {
+                        answer = msg->getFromResource();
+                    }
+                } else {
+                    if (sentByMe(*msg)) {
+                        answer = rosterItem->getAccountName();
+                    } else {
+                        answer = rosterItem->getDisplayedName();
+                    }
+                }
                 break;
             case Date: 
                 answer = msg->getTime();
@@ -102,51 +118,55 @@ QVariant MessageFeed::data(const QModelIndex& index, int role) const
                 answer = msg->getEdited();
                 break;
             case SentByMe: 
-                answer = msg->getOutgoing();
+                answer = sentByMe(*msg);
+                break;
+            case Avatar: {
+                QString path;
+                if (sentByMe(*msg)) {
+                    path = rosterItem->getAccountAvatarPath();
+                } else if (!rosterItem->isRoom()) {
+                    if (rosterItem->getAvatarState() != Shared::Avatar::empty) {
+                        path = rosterItem->getAvatarPath();
+                    }
+                } else {
+                    const Room* room = static_cast<const Room*>(rosterItem);
+                    path = room->getParticipantIconPath(msg->getFromResource());
+                }
+                
+                if (path.size() == 0) {
+                    answer = Shared::iconPath("user", true);
+                } else {
+                    answer = path;
+                }
+            }
                 break;
             default:
                 break;
         }
-    } else {
-        switch (role) {
-            case Qt::DisplayRole:
-            case Text: 
-                answer = "loading...";
-                break;
-            default:
-                answer = "";
-                break;
-        }
     }
     
     return answer;
 }
 
-int MessageFeed::rowCount(const QModelIndex& parent) const
+int Models::MessageFeed::rowCount(const QModelIndex& parent) const
 {
-    int count = storage.size();
-    if (syncState == syncing) {
-        count++;
-    }
-    return count;
+    return storage.size();
 }
 
-unsigned int MessageFeed::unreadMessagesCount() const
+unsigned int Models::MessageFeed::unreadMessagesCount() const
 {
     return storage.size(); //let's say they are all new for now =)
 }
 
-bool MessageFeed::canFetchMore(const QModelIndex& parent) const
+bool Models::MessageFeed::canFetchMore(const QModelIndex& parent) const
 {
     return syncState == incomplete;
 }
 
-void MessageFeed::fetchMore(const QModelIndex& parent)
+void Models::MessageFeed::fetchMore(const QModelIndex& parent)
 {
     if (syncState == incomplete) {
-        beginInsertRows(QModelIndex(), storage.size(), storage.size());
-        syncState = syncing;
-        endInsertRows();
+        emit requestStateChange(true);
         
         if (storage.size() == 0) {
             emit requestArchive("");
@@ -156,13 +176,11 @@ void MessageFeed::fetchMore(const QModelIndex& parent)
     }
 }
 
-void MessageFeed::responseArchive(const std::list<Shared::Message> list)
+void Models::MessageFeed::responseArchive(const std::list<Shared::Message> list)
 {
     Storage::size_type size = storage.size();
     if (syncState == syncing) {
-        beginRemoveRows(QModelIndex(), size, size);
-        syncState = incomplete;
-        endRemoveRows();
+        emit requestStateChange(false);
     }
     
     beginInsertRows(QModelIndex(), size, size + list.size() - 1);
@@ -173,7 +191,17 @@ void MessageFeed::responseArchive(const std::list<Shared::Message> list)
     endInsertRows();
 }
 
-QHash<int, QByteArray> MessageFeed::roleNames() const
+QHash<int, QByteArray> Models::MessageFeed::roleNames() const
 {
     return roles;
 }
+
+bool Models::MessageFeed::sentByMe(const Shared::Message& msg) const
+{
+    if (rosterItem->isRoom()) {
+        const Room* room = static_cast<const Room*>(rosterItem);
+        return room->getNick().toLower() == msg.getFromResource().toLower();
+    } else {
+        return msg.getOutgoing();
+    }
+}
diff --git a/ui/models/messagefeed.h b/ui/models/messagefeed.h
index 1aeb476..0be29a3 100644
--- a/ui/models/messagefeed.h
+++ b/ui/models/messagefeed.h
@@ -29,13 +29,17 @@
 #include <boost/multi_index/mem_fun.hpp>
 
 #include <shared/message.h>
+#include <shared/icons.h>
 
 
+namespace Models {
+    class Element;
+    
 class MessageFeed : public QAbstractListModel
 {
     Q_OBJECT
 public:
-    MessageFeed(QObject *parent = nullptr);
+    MessageFeed(const Element* rosterItem, QObject *parent = nullptr);
     ~MessageFeed();
     
     void addMessage(const Shared::Message& msg);
@@ -55,6 +59,10 @@ public:
     
 signals:
     void requestArchive(const QString& before);
+    void requestStateChange(bool requesting);
+    
+protected:
+    bool sentByMe(const Shared::Message& msg) const;
     
 public:
     enum MessageRoles {
@@ -63,7 +71,8 @@ public:
         Date,
         DeliveryState,
         Correction,
-        SentByMe
+        SentByMe,
+        Avatar
     };
 private:
     enum SyncState {
@@ -104,10 +113,11 @@ private:
     StorageById& indexById;
     StorageByTime& indexByTime;
     
+    const Element* rosterItem;
     SyncState syncState;
     
     static const QHash<int, QByteArray> roles;
-
+};
 };
 
 #endif // MESSAGEFEED_H
diff --git a/ui/models/room.cpp b/ui/models/room.cpp
index 7f83b3f..a6a36d0 100644
--- a/ui/models/room.cpp
+++ b/ui/models/room.cpp
@@ -310,7 +310,12 @@ QString Models::Room::getParticipantIconPath(const QString& name) const
 {
     std::map<QString, Models::Participant*>::const_iterator itr = participants.find(name);
     if (itr == participants.end()) {
-        return "";
+        std::map<QString, QString>::const_iterator eitr = exParticipantAvatars.find(name);
+        if (eitr != exParticipantAvatars.end()) {
+            return eitr->second;
+        } else {
+            return "";
+        }
     }
     
     return itr->second->getAvatarPath();
diff --git a/ui/utils/feedview.cpp b/ui/utils/feedview.cpp
index efdf2cd..afa86a3 100644
--- a/ui/utils/feedview.cpp
+++ b/ui/utils/feedview.cpp
@@ -232,3 +232,8 @@ void FeedView::verticalScrollbarValueChanged(int value)
     
     QAbstractItemView::verticalScrollbarValueChanged(vo);
 }
+
+QFont FeedView::getFont() const
+{
+    return viewOptions().font;
+}
diff --git a/ui/utils/feedview.h b/ui/utils/feedview.h
index 423725e..d084130 100644
--- a/ui/utils/feedview.h
+++ b/ui/utils/feedview.h
@@ -43,6 +43,8 @@ public:
     void setSelection(const QRect & rect, QItemSelectionModel::SelectionFlags command) override;
     QRegion visualRegionForSelection(const QItemSelection & selection) const override;
     
+    QFont getFont() const;
+    
 protected slots:
     void rowsInserted(const QModelIndex & parent, int start, int end) override;
     void verticalScrollbarValueChanged(int value) override;
diff --git a/ui/utils/messagedelegate.cpp b/ui/utils/messagedelegate.cpp
new file mode 100644
index 0000000..315cae0
--- /dev/null
+++ b/ui/utils/messagedelegate.cpp
@@ -0,0 +1,130 @@
+/*
+ * Squawk messenger. 
+ * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+ * 
+ * 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 <QPainter>
+#include <QApplication>
+#include "messagedelegate.h"
+#include "ui/models/messagefeed.h"
+
+constexpr int avatarHeight = 50;
+constexpr int margin = 6;
+
+MessageDelegate::MessageDelegate(QObject* parent):
+QStyledItemDelegate(parent),
+bodyFont(),
+nickFont(),
+dateFont(),
+bodyMetrics(bodyFont),
+nickMetrics(nickFont),
+dateMetrics(dateFont)
+{
+}
+
+MessageDelegate::~MessageDelegate()
+{
+}
+
+void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
+{
+    bool sentByMe = false;
+    QVariant sbm = index.data(Models::MessageFeed::SentByMe);
+    if (sbm.isValid()) {
+        sentByMe = sbm.toBool();
+    }
+    painter->save();
+    painter->setRenderHint(QPainter::Antialiasing, true);
+    QIcon icon(index.data(Models::MessageFeed::Avatar).toString());
+    
+    if (sentByMe) {
+        painter->drawPixmap(option.rect.width() - avatarHeight - margin,  option.rect.y() + margin / 2, icon.pixmap(avatarHeight, avatarHeight));
+    } else {
+        painter->drawPixmap(margin, option.rect.y() + margin / 2, icon.pixmap(avatarHeight, avatarHeight));
+    }
+    
+    QStyleOptionViewItem opt = option;
+    QRect messageRect = option.rect.adjusted(margin, margin / 2, -(avatarHeight + 2 * margin), -margin / 2);
+    if (!sentByMe) {
+        opt.displayAlignment = Qt::AlignLeft | Qt::AlignTop;
+        messageRect.adjust(avatarHeight + margin, 0, avatarHeight + margin, 0);
+    } else {
+        opt.displayAlignment = Qt::AlignRight | Qt::AlignTop;
+    }
+    opt.rect = messageRect;
+    
+    QRect rect;
+    painter->setFont(nickFont);
+    painter->drawText(opt.rect, opt.displayAlignment, index.data(Models::MessageFeed::Sender).toString(), &rect);
+    
+    opt.rect.adjust(0, rect.height(), 0, 0);
+    painter->setFont(bodyFont);
+    painter->drawText(opt.rect, opt.displayAlignment | Qt::TextWordWrap, index.data(Models::MessageFeed::Text).toString(), &rect);
+    
+    opt.rect.adjust(0, rect.height(), 0, 0);
+    painter->setFont(dateFont);
+    QColor q = painter->pen().color();
+    q.setAlpha(180);
+    painter->setPen(q);
+    painter->drawText(opt.rect, opt.displayAlignment, index.data(Models::MessageFeed::Date).toDateTime().toLocalTime().toString(), &rect);
+    
+    painter->restore();
+}
+
+QSize MessageDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const
+{
+    QRect messageRect = option.rect.adjusted(0, margin / 2, -(avatarHeight + 3 * margin), -margin);
+    QStyleOptionViewItem opt = option;
+    opt.rect = messageRect;
+    QSize messageSize = bodyMetrics.boundingRect(messageRect, Qt::TextWordWrap, index.data(Models::MessageFeed::Text).toString()).size();
+    
+    messageSize.rheight() += nickMetrics.lineSpacing();
+    messageSize.rheight() += dateMetrics.height();
+    
+    if (messageSize.height() < avatarHeight) {
+        messageSize.setHeight(avatarHeight);
+    }
+    
+    messageSize.rheight() += margin;
+    
+    return messageSize;
+}
+
+void MessageDelegate::initializeFonts(const QFont& font)
+{
+    bodyFont = font;
+    nickFont = font;
+    dateFont = font;
+    
+    nickFont.setBold(true);
+    dateFont.setItalic(true);
+    float dps = dateFont.pointSizeF();
+    if (dps != -1) {
+        dateFont.setPointSizeF(dps * 0.7);
+    } else {
+        dateFont.setPointSize(dateFont.pointSize() - 2);
+    }
+    
+    bodyMetrics = QFontMetrics(bodyFont);
+    nickMetrics = QFontMetrics(nickFont);
+    dateMetrics = QFontMetrics(dateFont);
+}
+
+
+// void MessageDelegate::setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const
+// {
+//     
+// }
diff --git a/ui/utils/messagedelegate.h b/ui/utils/messagedelegate.h
new file mode 100644
index 0000000..0ed8463
--- /dev/null
+++ b/ui/utils/messagedelegate.h
@@ -0,0 +1,50 @@
+/*
+ * Squawk messenger. 
+ * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+ * 
+ * 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/>.
+ */
+
+#ifndef MESSAGEDELEGATE_H
+#define MESSAGEDELEGATE_H
+
+#include <QStyledItemDelegate>
+#include <QFont>
+#include <QFontMetrics>
+
+#include "shared/icons.h"
+
+class MessageDelegate : public QStyledItemDelegate
+{
+    Q_OBJECT
+public:
+    MessageDelegate(QObject *parent = nullptr);
+    ~MessageDelegate();
+    
+    void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override;
+    QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const override;
+    //void setModelData(QWidget * editor, QAbstractItemModel * model, const QModelIndex & index) const override;
+    
+    void initializeFonts(const QFont& font);
+    
+private:
+    QFont bodyFont;
+    QFont nickFont;
+    QFont dateFont;
+    QFontMetrics bodyMetrics;
+    QFontMetrics nickMetrics;
+    QFontMetrics dateMetrics;
+};
+
+#endif // MESSAGEDELEGATE_H
diff --git a/ui/widgets/conversation.cpp b/ui/widgets/conversation.cpp
index 0326ed2..b31b59d 100644
--- a/ui/widgets/conversation.cpp
+++ b/ui/widgets/conversation.cpp
@@ -46,6 +46,7 @@ Conversation::Conversation(bool muc, Models::Account* acc, Models::Element* el,
     overlay(new QWidget()),
     filesToAttach(),
     feed(new FeedView()),
+    delegate(new MessageDelegate()),
     scroll(down),
     manualSliderChange(false),
     requestingHistory(false),
@@ -54,6 +55,8 @@ Conversation::Conversation(bool muc, Models::Account* acc, Models::Element* el,
 {
     m_ui->setupUi(this);
     
+    feed->setItemDelegate(delegate);
+    delegate->initializeFonts(feed->getFont());
     feed->setModel(el->feed);
     m_ui->widget->layout()->addWidget(feed);
     
diff --git a/ui/widgets/conversation.h b/ui/widgets/conversation.h
index bc5863e..2331e34 100644
--- a/ui/widgets/conversation.h
+++ b/ui/widgets/conversation.h
@@ -34,6 +34,7 @@
 #include "ui/utils/flowlayout.h"
 #include "ui/utils/badge.h"
 #include "ui/utils/feedview.h"
+#include "ui/utils/messagedelegate.h"
 #include "shared/icons.h"
 #include "shared/utils.h"
 
@@ -147,6 +148,7 @@ protected:
     QWidget* overlay;
     W::Order<Badge*, Badge::Comparator> filesToAttach;
     FeedView* feed;
+    MessageDelegate* delegate;
     Scroll scroll;
     bool manualSliderChange;
     bool requestingHistory;

From 270a32db9e827ab13ae518d385cf96dfe3d67b41 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Fri, 21 Aug 2020 23:57:48 +0300
Subject: [PATCH 087/281] achive from the beginning memorizing bugfix,
 limitation of the requests in the model

---
 core/account.cpp                | 14 ++++++++++++--
 core/account.h                  |  3 ++-
 core/archive.cpp                |  9 +++++----
 core/handlers/rosterhandler.cpp | 10 +---------
 core/handlers/rosterhandler.h   |  1 -
 core/rosteritem.cpp             | 17 +++++++++++++++--
 core/rosteritem.h               |  2 +-
 core/squawk.cpp                 |  4 ++--
 core/squawk.h                   |  4 ++--
 ui/models/element.cpp           |  4 ++--
 ui/models/element.h             |  2 +-
 ui/models/messagefeed.cpp       | 15 +++++++++++----
 ui/models/messagefeed.h         |  2 +-
 ui/models/roster.cpp            |  6 +++---
 ui/models/roster.h              |  2 +-
 ui/squawk.cpp                   |  4 ++--
 ui/squawk.h                     |  2 +-
 17 files changed, 62 insertions(+), 39 deletions(-)

diff --git a/core/account.cpp b/core/account.cpp
index 094fd3c..21fe9e7 100644
--- a/core/account.cpp
+++ b/core/account.cpp
@@ -434,13 +434,13 @@ void Core::Account::requestArchive(const QString& jid, int count, const QString&
     
     if (contact == 0) {
         qDebug() << "An attempt to request archive for" << jid << "in account" << name << ", but the contact with such id wasn't found, skipping";
-        emit responseArchive(jid, std::list<Shared::Message>());
+        emit responseArchive(jid, std::list<Shared::Message>(), true);
         return;
     }
     
     if (state != Shared::ConnectionState::connected) {
         qDebug() << "An attempt to request archive for" << jid << "in account" << name << ", but the account is not online, skipping";
-        emit responseArchive(contact->jid, std::list<Shared::Message>());
+        emit responseArchive(contact->jid, std::list<Shared::Message>(), false);
     }
     
     contact->requestHistory(count, before);
@@ -909,3 +909,13 @@ void Core::Account::handleDisconnection()
     ownVCardRequestInProgress = false;
 }
 
+void Core::Account::onContactHistoryResponse(const std::list<Shared::Message>& list, bool last)
+{
+    RosterItem* contact = static_cast<RosterItem*>(sender());
+    
+    qDebug() << "Collected history for contact " << contact->jid << list.size() << "elements";
+    if (last) {
+        qDebug() << "The response contains the first accounted message";
+    }
+    emit responseArchive(contact->jid, list, last);
+}
diff --git a/core/account.h b/core/account.h
index 49c7ca9..7b6b50d 100644
--- a/core/account.h
+++ b/core/account.h
@@ -127,7 +127,7 @@ signals:
     void removePresence(const QString& jid, const QString& name);
     void message(const Shared::Message& data);
     void changeMessage(const QString& jid, const QString& id, const QMap<QString, QVariant>& data);
-    void responseArchive(const QString& jid, const std::list<Shared::Message>& list);
+    void responseArchive(const QString& jid, const std::list<Shared::Message>& list, bool last);
     void error(const QString& text);
     void addRoomParticipant(const QString& jid, const QString& nickName, const QMap<QString, QVariant>& data);
     void changeRoomParticipant(const QString& jid, const QString& nickName, const QMap<QString, QVariant>& data);
@@ -183,6 +183,7 @@ private slots:
 
     void onDiscoveryItemsReceived (const QXmppDiscoveryIq& items);
     void onDiscoveryInfoReceived (const QXmppDiscoveryIq& info);
+    void onContactHistoryResponse(const std::list<Shared::Message>& list, bool last);
   
 private:
     void handleDisconnection();
diff --git a/core/archive.cpp b/core/archive.cpp
index a1f8b76..f18201b 100644
--- a/core/archive.cpp
+++ b/core/archive.cpp
@@ -502,8 +502,9 @@ long unsigned int Core::Archive::size() const
     mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn);
     MDB_stat stat;
     mdb_stat(txn, order, &stat);
+    size_t amount = stat.ms_entries;
     mdb_txn_abort(txn);
-    return stat.ms_entries;
+    return amount;
 }
 
 std::list<Shared::Message> Core::Archive::getBefore(int count, const QString& id)
@@ -603,10 +604,10 @@ void Core::Archive::setFromTheBeginning(bool is)
         MDB_txn *txn;
         mdb_txn_begin(environment, NULL, 0, &txn);
         bool success = setStatValue("beginning", is, txn);
-        if (success != 0) {
-            mdb_txn_abort(txn);
-        } else {
+        if (success) {
             mdb_txn_commit(txn);
+        } else {
+            mdb_txn_abort(txn);
         }
     }
 }
diff --git a/core/handlers/rosterhandler.cpp b/core/handlers/rosterhandler.cpp
index 82ca8c3..ce5f1b7 100644
--- a/core/handlers/rosterhandler.cpp
+++ b/core/handlers/rosterhandler.cpp
@@ -190,7 +190,7 @@ void Core::RosterHandler::removeContactRequest(const QString& jid)
 void Core::RosterHandler::handleNewRosterItem(Core::RosterItem* contact)
 {
     connect(contact, &RosterItem::needHistory, this->acc, &Account::onContactNeedHistory);
-    connect(contact, &RosterItem::historyResponse, this, &RosterHandler::onContactHistoryResponse);
+    connect(contact, &RosterItem::historyResponse, this->acc, &Account::onContactHistoryResponse);
     connect(contact, &RosterItem::nameChanged, this, &RosterHandler::onContactNameChanged);
     connect(contact, &RosterItem::avatarChanged, this, &RosterHandler::onContactAvatarChanged);
     connect(contact, &RosterItem::requestVCard, this->acc, &Account::requestVCard);
@@ -315,14 +315,6 @@ void Core::RosterHandler::removeFromGroup(const QString& jid, const QString& gro
     }
 }
 
-void Core::RosterHandler::onContactHistoryResponse(const std::list<Shared::Message>& list)
-{
-    RosterItem* contact = static_cast<RosterItem*>(sender());
-    
-    qDebug() << "Collected history for contact " << contact->jid << list.size() << "elements";
-    emit acc->responseArchive(contact->jid, list);
-}
-
 Core::RosterItem * Core::RosterHandler::getRosterItem(const QString& jid)
 {
     RosterItem* item = 0;
diff --git a/core/handlers/rosterhandler.h b/core/handlers/rosterhandler.h
index c01f396..b1dfc45 100644
--- a/core/handlers/rosterhandler.h
+++ b/core/handlers/rosterhandler.h
@@ -86,7 +86,6 @@ private slots:
     void onContactGroupRemoved(const QString& group);
     void onContactNameChanged(const QString& name);
     void onContactSubscriptionStateChanged(Shared::SubscriptionState state);
-    void onContactHistoryResponse(const std::list<Shared::Message>& list);
     void onContactAvatarChanged(Shared::Avatar, const QString& path);
     
 private:
diff --git a/core/rosteritem.cpp b/core/rosteritem.cpp
index 32b70f4..1baa61f 100644
--- a/core/rosteritem.cpp
+++ b/core/rosteritem.cpp
@@ -122,7 +122,20 @@ void Core::RosterItem::nextRequest()
 {
     if (syncronizing) {
         if (requestedCount != -1) {
-            emit historyResponse(responseCache);
+            bool last = false;
+            if (archiveState == beginning || archiveState == complete) {
+                QString firstId = archive->oldestId();
+                if (responseCache.size() == 0) {
+                    if (requestedBefore == firstId) {
+                        last = true;
+                    }
+                } else {
+                    if (responseCache.front().getId() == firstId) {
+                        last = true;
+                    }
+                }
+            }
+            emit historyResponse(responseCache, last);
         }
     }
     if (requestCache.size() > 0) {
@@ -529,7 +542,7 @@ void Core::RosterItem::clearArchiveRequests()
     requestedBefore = "";
     for (const std::pair<int, QString>& pair : requestCache) {
         if (pair.first != -1) {
-            emit historyResponse(responseCache);        //just to notify those who still waits with whatever happened to be left in caches yet
+            emit historyResponse(responseCache, false);        //just to notify those who still waits with whatever happened to be left in caches yet
         }
         responseCache.clear();
     }
diff --git a/core/rosteritem.h b/core/rosteritem.h
index 4113b37..e744cac 100644
--- a/core/rosteritem.h
+++ b/core/rosteritem.h
@@ -81,7 +81,7 @@ public:
 signals:
     void nameChanged(const QString& name);
     void subscriptionStateChanged(Shared::SubscriptionState state);
-    void historyResponse(const std::list<Shared::Message>& messages);
+    void historyResponse(const std::list<Shared::Message>& messages, bool last);
     void needHistory(const QString& before, const QString& after, const QDateTime& afterTime = QDateTime());
     void avatarChanged(Shared::Avatar, const QString& path);
     void requestVCard(const QString& jid);
diff --git a/core/squawk.cpp b/core/squawk.cpp
index 1689d71..9116e47 100644
--- a/core/squawk.cpp
+++ b/core/squawk.cpp
@@ -357,10 +357,10 @@ void Core::Squawk::requestArchive(const QString& account, const QString& jid, in
     itr->second->requestArchive(jid, count, before);
 }
 
-void Core::Squawk::onAccountResponseArchive(const QString& jid, const std::list<Shared::Message>& list)
+void Core::Squawk::onAccountResponseArchive(const QString& jid, const std::list<Shared::Message>& list, bool last)
 {
     Account* acc = static_cast<Account*>(sender());
-    emit responseArchive(acc->getName(), jid, list);
+    emit responseArchive(acc->getName(), jid, list, last);
 }
 
 void Core::Squawk::modifyAccountRequest(const QString& name, const QMap<QString, QVariant>& map)
diff --git a/core/squawk.h b/core/squawk.h
index 31812d2..aa84f59 100644
--- a/core/squawk.h
+++ b/core/squawk.h
@@ -64,7 +64,7 @@ signals:
     void removePresence(const QString& account, const QString& jid, const QString& name);
     void stateChanged(Shared::Availability state);
     void accountMessage(const QString& account, const Shared::Message& data);
-    void responseArchive(const QString& account, const QString& jid, const std::list<Shared::Message>& list);
+    void responseArchive(const QString& account, const QString& jid, const std::list<Shared::Message>& list, bool last);
     void addRoom(const QString& account, const QString jid, const QMap<QString, QVariant>& data);
     void changeRoom(const QString& account, const QString jid, const QMap<QString, QVariant>& data);
     void removeRoom(const QString& account, const QString jid);
@@ -146,7 +146,7 @@ private slots:
     void onAccountAddPresence(const QString& jid, const QString& name, const QMap<QString, QVariant>& data);
     void onAccountRemovePresence(const QString& jid, const QString& name);
     void onAccountMessage(const Shared::Message& data);
-    void onAccountResponseArchive(const QString& jid, const std::list<Shared::Message>& list);
+    void onAccountResponseArchive(const QString& jid, const std::list<Shared::Message>& list, bool last);
     void onAccountAddRoom(const QString jid, const QMap<QString, QVariant>& data);
     void onAccountChangeRoom(const QString jid, const QMap<QString, QVariant>& data);
     void onAccountRemoveRoom(const QString jid);
diff --git a/ui/models/element.cpp b/ui/models/element.cpp
index 88d990c..20df389 100644
--- a/ui/models/element.cpp
+++ b/ui/models/element.cpp
@@ -145,9 +145,9 @@ void Models::Element::changeMessage(const QString& id, const QMap<QString, QVari
     
 }
 
-void Models::Element::responseArchive(const std::list<Shared::Message> list)
+void Models::Element::responseArchive(const std::list<Shared::Message> list, bool last)
 {
-    feed->responseArchive(list);
+    feed->responseArchive(list, last);
 }
 
 bool Models::Element::isRoom() const
diff --git a/ui/models/element.h b/ui/models/element.h
index 41cb642..047a645 100644
--- a/ui/models/element.h
+++ b/ui/models/element.h
@@ -41,7 +41,7 @@ public:
     void addMessage(const Shared::Message& data);
     void changeMessage(const QString& id, const QMap<QString, QVariant>& data);
     unsigned int getMessagesCount() const;
-    void responseArchive(const std::list<Shared::Message> list);
+    void responseArchive(const std::list<Shared::Message> list, bool last);
     bool isRoom() const;
     
 signals:
diff --git a/ui/models/messagefeed.cpp b/ui/models/messagefeed.cpp
index 5226ed3..689d0d6 100644
--- a/ui/models/messagefeed.cpp
+++ b/ui/models/messagefeed.cpp
@@ -166,6 +166,7 @@ bool Models::MessageFeed::canFetchMore(const QModelIndex& parent) const
 void Models::MessageFeed::fetchMore(const QModelIndex& parent)
 {
     if (syncState == incomplete) {
+        syncState = syncing;
         emit requestStateChange(true);
         
         if (storage.size() == 0) {
@@ -176,12 +177,9 @@ void Models::MessageFeed::fetchMore(const QModelIndex& parent)
     }
 }
 
-void Models::MessageFeed::responseArchive(const std::list<Shared::Message> list)
+void Models::MessageFeed::responseArchive(const std::list<Shared::Message> list, bool last)
 {
     Storage::size_type size = storage.size();
-    if (syncState == syncing) {
-        emit requestStateChange(false);
-    }
     
     beginInsertRows(QModelIndex(), size, size + list.size() - 1);
     for (const Shared::Message& msg : list) {
@@ -189,6 +187,15 @@ void Models::MessageFeed::responseArchive(const std::list<Shared::Message> list)
         storage.insert(copy);
     }
     endInsertRows();
+    
+    if (syncState == syncing) {
+        if (last) {
+            syncState = complete;
+        } else {
+            syncState = incomplete;
+        }
+        emit requestStateChange(false);
+    }
 }
 
 QHash<int, QByteArray> Models::MessageFeed::roleNames() const
diff --git a/ui/models/messagefeed.h b/ui/models/messagefeed.h
index 0be29a3..e8031ff 100644
--- a/ui/models/messagefeed.h
+++ b/ui/models/messagefeed.h
@@ -53,7 +53,7 @@ public:
     void fetchMore(const QModelIndex & parent) override;
     QHash<int, QByteArray> roleNames() const override;
     
-    void responseArchive(const std::list<Shared::Message> list);
+    void responseArchive(const std::list<Shared::Message> list, bool last);
     
     unsigned int unreadMessagesCount() const;
     
diff --git a/ui/models/roster.cpp b/ui/models/roster.cpp
index 461fbaa..95515b3 100644
--- a/ui/models/roster.cpp
+++ b/ui/models/roster.cpp
@@ -965,16 +965,16 @@ void Models::Roster::onElementRequestArchive(const QString& before)
     emit requestArchive(el->getAccountName(), el->getJid(), before);
 }
 
-void Models::Roster::responseArchive(const QString& account, const QString& jid, const std::list<Shared::Message>& list)
+void Models::Roster::responseArchive(const QString& account, const QString& jid, const std::list<Shared::Message>& list, bool last)
 {
     ElId id(account, jid);
     std::map<ElId, Contact*>::iterator itr = contacts.find(id);
     if (itr != contacts.end()) {
-        itr->second->responseArchive(list);
+        itr->second->responseArchive(list, last);
     } else {
         std::map<ElId, Room*>::const_iterator rItr = rooms.find(id);
         if (rItr != rooms.end()) {
-            rItr->second->responseArchive(list);
+            rItr->second->responseArchive(list, last);
         }
     }
 }
diff --git a/ui/models/roster.h b/ui/models/roster.h
index ac72617..f43d9a9 100644
--- a/ui/models/roster.h
+++ b/ui/models/roster.h
@@ -80,7 +80,7 @@ public:
     Account* getAccount(const QString& name);
     QModelIndex getAccountIndex(const QString& name);
     QModelIndex getGroupIndex(const QString& account, const QString& name);
-    void responseArchive(const QString& account, const QString& jid, const std::list<Shared::Message>& list);
+    void responseArchive(const QString& account, const QString& jid, const std::list<Shared::Message>& list, bool last);
     
     Accounts* accountsModel;
     
diff --git a/ui/squawk.cpp b/ui/squawk.cpp
index 37150d2..1709dd6 100644
--- a/ui/squawk.cpp
+++ b/ui/squawk.cpp
@@ -610,9 +610,9 @@ void Squawk::onConversationRequestArchive(const QString& account, const QString&
     emit requestArchive(account, jid, 20, before); //TODO amount as a settings value
 }
 
-void Squawk::responseArchive(const QString& account, const QString& jid, const std::list<Shared::Message>& list)
+void Squawk::responseArchive(const QString& account, const QString& jid, const std::list<Shared::Message>& list, bool last)
 {
-    rosterModel.responseArchive(account, jid, list);
+    rosterModel.responseArchive(account, jid, list, last);
 }
 
 void Squawk::removeAccount(const QString& account)
diff --git a/ui/squawk.h b/ui/squawk.h
index 67013cc..a0d776d 100644
--- a/ui/squawk.h
+++ b/ui/squawk.h
@@ -97,7 +97,7 @@ public slots:
     void removePresence(const QString& account, const QString& jid, const QString& name);
     void stateChanged(Shared::Availability state);
     void accountMessage(const QString& account, const Shared::Message& data);
-    void responseArchive(const QString& account, const QString& jid, const std::list<Shared::Message>& list);
+    void responseArchive(const QString& account, const QString& jid, const std::list<Shared::Message>& list, bool last);
     void addRoom(const QString& account, const QString jid, const QMap<QString, QVariant>& data);
     void changeRoom(const QString& account, const QString jid, const QMap<QString, QVariant>& data);
     void removeRoom(const QString& account, const QString jid);

From 15342f3c53854201b301751b24eae08b74697c7d Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Fri, 8 Jan 2021 00:50:12 +0300
Subject: [PATCH 088/281] self nick in the chat fix, hovering message feature

---
 CMakeLists.txt               |  2 +-
 ui/models/messagefeed.cpp    | 47 +++++++++++++++++++++++++++++-------
 ui/models/messagefeed.h      | 31 +++++++++++++++++++++++-
 ui/utils/feedview.cpp        | 29 ++++++++++++++++------
 ui/utils/feedview.h          |  3 +++
 ui/utils/messagedelegate.cpp | 47 +++++++++++++++++++++++++++---------
 ui/utils/messagedelegate.h   |  1 +
 7 files changed, 130 insertions(+), 30 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 47599dc..f02df03 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.0)
+cmake_minimum_required(VERSION 3.4)
 project(squawk)
 
 set(CMAKE_INCLUDE_CURRENT_DIR ON)
diff --git a/ui/models/messagefeed.cpp b/ui/models/messagefeed.cpp
index 689d0d6..ae17c7b 100644
--- a/ui/models/messagefeed.cpp
+++ b/ui/models/messagefeed.cpp
@@ -29,7 +29,9 @@ const QHash<int, QByteArray> Models::MessageFeed::roles = {
     {DeliveryState, "deliveryState"},
     {Correction, "correction"},
     {SentByMe,"sentByMe"},
-    {Avatar, "avatar"}
+    {Avatar, "avatar"},
+    {Attach, "attach"},
+    {Bulk, "bulk"}
 };
 
 Models::MessageFeed::MessageFeed(const Element* ri, QObject* parent):
@@ -94,15 +96,11 @@ QVariant Models::MessageFeed::data(const QModelIndex& index, int role) const
                 answer = msg->getBody();
                 break;
             case Sender: 
-                if (rosterItem->isRoom()) {
-                    if (sentByMe(*msg)) {
-                        answer = rosterItem->getDisplayedName();
-                    } else {
-                        answer = msg->getFromResource();
-                    }
+                if (sentByMe(*msg)) {
+                    answer = rosterItem->getAccountName();
                 } else {
-                    if (sentByMe(*msg)) {
-                        answer = rosterItem->getAccountName();
+                    if (rosterItem->isRoom()) {
+                        answer = msg->getFromResource();
                     } else {
                         answer = rosterItem->getDisplayedName();
                     }
@@ -139,7 +137,38 @@ QVariant Models::MessageFeed::data(const QModelIndex& index, int role) const
                     answer = path;
                 }
             }
+            case Attach:
+                
                 break;
+            case Bulk: {
+                FeedItem item;
+                item.sentByMe = sentByMe(*msg);
+                item.date = msg->getTime();
+                item.state = msg->getState();
+                item.correction = msg->getEdited();
+                item.text = msg->getBody();
+                item.avatar.clear();
+                if (item.sentByMe) {
+                    item.sender = rosterItem->getAccountName();
+                    item.avatar = rosterItem->getAccountAvatarPath();
+                } else {
+                    if (rosterItem->isRoom()) {
+                        item.sender = msg->getFromResource();
+                        const Room* room = static_cast<const Room*>(rosterItem);
+                        item.avatar = room->getParticipantIconPath(msg->getFromResource());
+                    } else {
+                        item.sender = rosterItem->getDisplayedName();
+                        if (rosterItem->getAvatarState() != Shared::Avatar::empty) {
+                            item.avatar = rosterItem->getAvatarPath();
+                        }
+                    }
+                }
+                
+                if (item.avatar.size() == 0) {
+                    item.avatar = Shared::iconPath("user", true);
+                }
+                answer.setValue(item);
+            }
             default:
                 break;
         }
diff --git a/ui/models/messagefeed.h b/ui/models/messagefeed.h
index e8031ff..af4bd6a 100644
--- a/ui/models/messagefeed.h
+++ b/ui/models/messagefeed.h
@@ -72,7 +72,17 @@ public:
         DeliveryState,
         Correction,
         SentByMe,
-        Avatar
+        Avatar,
+        Attach,
+        Bulk
+    };
+    
+    enum Attachment {
+        none,
+        remote,
+        downloading,
+        uploading,
+        ready
     };
 private:
     enum SyncState {
@@ -80,6 +90,12 @@ private:
         syncing,
         complete
     };
+    struct Attach {
+        Attachment state;
+        qreal progress;
+        QString localPath;
+    };
+    
     //tags
     struct id {};
     struct time {};
@@ -118,6 +134,19 @@ private:
     
     static const QHash<int, QByteArray> roles;
 };
+
+struct FeedItem {
+    QString text;
+    QString sender;
+    QString avatar;
+    bool sentByMe;
+    bool correction;
+    QDateTime date;
+    Shared::Message::State state;
+    MessageFeed::Attachment attach;
+};
 };
 
+Q_DECLARE_METATYPE(Models::FeedItem);
+
 #endif // MESSAGEFEED_H
diff --git a/ui/utils/feedview.cpp b/ui/utils/feedview.cpp
index afa86a3..69c6ce9 100644
--- a/ui/utils/feedview.cpp
+++ b/ui/utils/feedview.cpp
@@ -33,6 +33,9 @@ FeedView::FeedView(QWidget* parent):
 {
     horizontalScrollBar()->setRange(0, 0);
     verticalScrollBar()->setSingleStep(approximateSingleMessageHeight);
+    setMouseTracking(true);
+    setSelectionBehavior(SelectItems);
+//     viewport()->setAttribute(Qt::WA_Hover, true);
 }
 
 FeedView::~FeedView()
@@ -41,14 +44,12 @@ FeedView::~FeedView()
 
 QModelIndex FeedView::indexAt(const QPoint& point) const
 {
-    int32_t totalHeight = viewport()->height() + vo;
-    if (point.y() <= totalHeight) {                      //if it's bigger - someone wants to know the index below the feed beginning, it's invalid
-        uint32_t y = totalHeight - point.y();
-        
-        for (std::deque<Hint>::size_type i = 0; i < hints.size(); ++i) {
-            if (y > hints[i].offset) {
-                return model()->index(i - 1, 0, rootIndex());
-            }
+    int32_t vh = viewport()->height();
+    uint32_t y = vh - point.y() + vo;
+    
+    for (std::deque<Hint>::size_type i = 0; i < hints.size(); ++i) {
+        if (hints[i].offset >= y) {
+            return model()->index(i - 1, 0, rootIndex());
         }
     }
     
@@ -219,9 +220,11 @@ void FeedView::paintEvent(QPaintEvent* event)
     QPainter painter(vp);
     QStyleOptionViewItem option = viewOptions();
     option.features = QStyleOptionViewItem::WrapText;
+    QPoint cursor = vp->mapFromGlobal(QCursor::pos());
     
     for (const QModelIndex& index : toRener) {
         option.rect = visualRect(index);
+        option.state.setFlag(QStyle::State_MouseOver, option.rect.contains(cursor));
         itemDelegate(index)->paint(&painter, option, index);
     }
 }
@@ -233,6 +236,16 @@ void FeedView::verticalScrollbarValueChanged(int value)
     QAbstractItemView::verticalScrollbarValueChanged(vo);
 }
 
+void FeedView::mouseMoveEvent(QMouseEvent* event)
+{
+    if (!isVisible()) {
+        return;
+    }
+    
+    QAbstractItemView::mouseMoveEvent(event);
+}
+
+
 QFont FeedView::getFont() const
 {
     return viewOptions().font;
diff --git a/ui/utils/feedview.h b/ui/utils/feedview.h
index d084130..50f46e4 100644
--- a/ui/utils/feedview.h
+++ b/ui/utils/feedview.h
@@ -45,6 +45,8 @@ public:
     
     QFont getFont() const;
     
+public slots:
+    
 protected slots:
     void rowsInserted(const QModelIndex & parent, int start, int end) override;
     void verticalScrollbarValueChanged(int value) override;
@@ -54,6 +56,7 @@ protected:
     int horizontalOffset() const override;
     void paintEvent(QPaintEvent * event) override;
     void updateGeometries() override;
+    void mouseMoveEvent(QMouseEvent * event) override;
     
 private:
     bool tryToCalculateGeometriesWithNoScrollbars(const QStyleOptionViewItem& option, const QAbstractItemModel* model, uint32_t totalHeight);
diff --git a/ui/utils/messagedelegate.cpp b/ui/utils/messagedelegate.cpp
index 315cae0..089557b 100644
--- a/ui/utils/messagedelegate.cpp
+++ b/ui/utils/messagedelegate.cpp
@@ -16,8 +16,10 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
+#include <QDebug>
 #include <QPainter>
 #include <QApplication>
+
 #include "messagedelegate.h"
 #include "ui/models/messagefeed.h"
 
@@ -41,16 +43,21 @@ MessageDelegate::~MessageDelegate()
 
 void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
 {
-    bool sentByMe = false;
-    QVariant sbm = index.data(Models::MessageFeed::SentByMe);
-    if (sbm.isValid()) {
-        sentByMe = sbm.toBool();
+    QVariant vi = index.data(Models::MessageFeed::Bulk);
+    if (!vi.isValid()) {
+        return;
     }
+    Models::FeedItem data = qvariant_cast<Models::FeedItem>(vi);
     painter->save();
     painter->setRenderHint(QPainter::Antialiasing, true);
-    QIcon icon(index.data(Models::MessageFeed::Avatar).toString());
     
-    if (sentByMe) {
+    if (option.state & QStyle::State_MouseOver) {
+        painter->fillRect(option.rect, option.palette.brush(QPalette::Inactive, QPalette::Highlight));
+    }
+    
+    QIcon icon(data.avatar);
+    
+    if (data.sentByMe) {
         painter->drawPixmap(option.rect.width() - avatarHeight - margin,  option.rect.y() + margin / 2, icon.pixmap(avatarHeight, avatarHeight));
     } else {
         painter->drawPixmap(margin, option.rect.y() + margin / 2, icon.pixmap(avatarHeight, avatarHeight));
@@ -58,7 +65,7 @@ void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio
     
     QStyleOptionViewItem opt = option;
     QRect messageRect = option.rect.adjusted(margin, margin / 2, -(avatarHeight + 2 * margin), -margin / 2);
-    if (!sentByMe) {
+    if (!data.sentByMe) {
         opt.displayAlignment = Qt::AlignLeft | Qt::AlignTop;
         messageRect.adjust(avatarHeight + margin, 0, avatarHeight + margin, 0);
     } else {
@@ -66,27 +73,39 @@ void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio
     }
     opt.rect = messageRect;
     
+    QSize messageSize = bodyMetrics.boundingRect(messageRect, Qt::TextWordWrap, data.text).size();
+    messageSize.rheight() += nickMetrics.lineSpacing();
+    messageSize.rheight() += dateMetrics.height();
+    if (messageSize.width() < opt.rect.width()) {
+        QSize senderSize = nickMetrics.boundingRect(messageRect, 0, data.sender).size();
+        if (senderSize.width() > messageSize.width()) {
+            messageSize.setWidth(senderSize.width());
+        }
+    } else {
+        messageSize.setWidth(opt.rect.width());
+    }
+    
     QRect rect;
     painter->setFont(nickFont);
-    painter->drawText(opt.rect, opt.displayAlignment, index.data(Models::MessageFeed::Sender).toString(), &rect);
+    painter->drawText(opt.rect, opt.displayAlignment, data.sender, &rect);
     
     opt.rect.adjust(0, rect.height(), 0, 0);
     painter->setFont(bodyFont);
-    painter->drawText(opt.rect, opt.displayAlignment | Qt::TextWordWrap, index.data(Models::MessageFeed::Text).toString(), &rect);
+    painter->drawText(opt.rect, opt.displayAlignment | Qt::TextWordWrap, data.text, &rect);
     
     opt.rect.adjust(0, rect.height(), 0, 0);
     painter->setFont(dateFont);
     QColor q = painter->pen().color();
     q.setAlpha(180);
     painter->setPen(q);
-    painter->drawText(opt.rect, opt.displayAlignment, index.data(Models::MessageFeed::Date).toDateTime().toLocalTime().toString(), &rect);
+    painter->drawText(opt.rect, opt.displayAlignment, data.date.toLocalTime().toString(), &rect);
     
     painter->restore();
 }
 
 QSize MessageDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const
 {
-    QRect messageRect = option.rect.adjusted(0, margin / 2, -(avatarHeight + 3 * margin), -margin);
+    QRect messageRect = option.rect.adjusted(0, margin / 2, -(avatarHeight + 3 * margin), -margin / 2);
     QStyleOptionViewItem opt = option;
     opt.rect = messageRect;
     QSize messageSize = bodyMetrics.boundingRect(messageRect, Qt::TextWordWrap, index.data(Models::MessageFeed::Text).toString()).size();
@@ -123,6 +142,12 @@ void MessageDelegate::initializeFonts(const QFont& font)
     dateMetrics = QFontMetrics(dateFont);
 }
 
+bool MessageDelegate::editorEvent(QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& index)
+{
+    //qDebug() << event->type();
+    return QStyledItemDelegate::editorEvent(event, model, option, index);
+}
+
 
 // void MessageDelegate::setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const
 // {
diff --git a/ui/utils/messagedelegate.h b/ui/utils/messagedelegate.h
index 0ed8463..4daa0a2 100644
--- a/ui/utils/messagedelegate.h
+++ b/ui/utils/messagedelegate.h
@@ -37,6 +37,7 @@ public:
     //void setModelData(QWidget * editor, QAbstractItemModel * model, const QModelIndex & index) const override;
     
     void initializeFonts(const QFont& font);
+    bool editorEvent(QEvent * event, QAbstractItemModel * model, const QStyleOptionViewItem & option, const QModelIndex & index) override;
     
 private:
     QFont bodyFont;

From ff4124d1f09d15ba37f18f875c329ac9d71b784d Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Tue, 12 Jan 2021 20:10:24 +0300
Subject: [PATCH 089/281] Resolved the bug about crash with an empty history
 chat

---
 core/rosteritem.cpp       |  2 ++
 ui/models/messagefeed.cpp |  7 ++++++-
 ui/models/messagefeed.h   | 29 ++++++++++++++++-------------
 3 files changed, 24 insertions(+), 14 deletions(-)

diff --git a/core/rosteritem.cpp b/core/rosteritem.cpp
index 1baa61f..d51ae8b 100644
--- a/core/rosteritem.cpp
+++ b/core/rosteritem.cpp
@@ -134,6 +134,8 @@ void Core::RosterItem::nextRequest()
                         last = true;
                     }
                 }
+            } else if (archiveState == empty && responseCache.size() == 0) {
+                last = true;
             }
             emit historyResponse(responseCache, last);
         }
diff --git a/ui/models/messagefeed.cpp b/ui/models/messagefeed.cpp
index ae17c7b..79d3755 100644
--- a/ui/models/messagefeed.cpp
+++ b/ui/models/messagefeed.cpp
@@ -137,8 +137,12 @@ QVariant Models::MessageFeed::data(const QModelIndex& index, int role) const
                     answer = path;
                 }
             }
-            case Attach:
+                break;
+            case Attach: {
+                ::Models::Attach att;
                 
+                answer.setValue(att);
+            }
                 break;
             case Bulk: {
                 FeedItem item;
@@ -169,6 +173,7 @@ QVariant Models::MessageFeed::data(const QModelIndex& index, int role) const
                 }
                 answer.setValue(item);
             }
+                break;
             default:
                 break;
         }
diff --git a/ui/models/messagefeed.h b/ui/models/messagefeed.h
index af4bd6a..78d54ad 100644
--- a/ui/models/messagefeed.h
+++ b/ui/models/messagefeed.h
@@ -77,24 +77,12 @@ public:
         Bulk
     };
     
-    enum Attachment {
-        none,
-        remote,
-        downloading,
-        uploading,
-        ready
-    };
 private:
     enum SyncState {
         incomplete,
         syncing,
         complete
     };
-    struct Attach {
-        Attachment state;
-        qreal progress;
-        QString localPath;
-    };
     
     //tags
     struct id {};
@@ -135,6 +123,20 @@ private:
     static const QHash<int, QByteArray> roles;
 };
 
+enum Attachment {
+    none,
+    remote,
+    downloading,
+    uploading,
+    ready
+};
+
+struct Attach {
+    Attachment state;
+    qreal progress;
+    QString localPath;
+};
+
 struct FeedItem {
     QString text;
     QString sender;
@@ -143,10 +145,11 @@ struct FeedItem {
     bool correction;
     QDateTime date;
     Shared::Message::State state;
-    MessageFeed::Attachment attach;
+    Attach attach;
 };
 };
 
+Q_DECLARE_METATYPE(Models::Attach);
 Q_DECLARE_METATYPE(Models::FeedItem);
 
 #endif // MESSAGEFEED_H

From 00ffbac6b0760af4c617300198f17f1c3d354e4c Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Thu, 14 Jan 2021 14:22:02 +0300
Subject: [PATCH 090/281] initial attempt to paint buttons in the messagefeed

---
 core/rosteritem.cpp          |  4 +++
 shared/message.cpp           | 28 +++++++++++++++--
 shared/message.h             |  9 ++++--
 ui/models/messagefeed.cpp    | 48 ++++++++++++++++++++++++----
 ui/models/messagefeed.h      | 18 ++++++++---
 ui/utils/feedview.cpp        |  2 +-
 ui/utils/messagedelegate.cpp | 61 +++++++++++++++++++++++++++++++++++-
 ui/utils/messagedelegate.h   |  4 +++
 8 files changed, 156 insertions(+), 18 deletions(-)

diff --git a/core/rosteritem.cpp b/core/rosteritem.cpp
index d51ae8b..5014ddd 100644
--- a/core/rosteritem.cpp
+++ b/core/rosteritem.cpp
@@ -375,6 +375,10 @@ void Core::RosterItem::flushMessagesToArchive(bool finished, const QString& firs
                 archiveState = complete;
                 archive->setFromTheBeginning(true);
             }
+            if (added == 0 && wasEmpty) {
+                archiveState = empty;
+                break;
+            }
             if (requestedCount != -1) {
                 QString before;
                 if (responseCache.size() > 0) {
diff --git a/shared/message.cpp b/shared/message.cpp
index af4f9e0..3f23d59 100644
--- a/shared/message.cpp
+++ b/shared/message.cpp
@@ -36,7 +36,8 @@ Shared::Message::Message(Shared::Message::Type p_type):
     errorText(),
     originalMessage(),
     lastModified(),
-    stanzaId()
+    stanzaId(),
+    attachPath()
     {}
 
 Shared::Message::Message():
@@ -56,7 +57,8 @@ Shared::Message::Message():
     errorText(),
     originalMessage(),
     lastModified(),
-    stanzaId()
+    stanzaId(),
+    attachPath()
     {}
 
 QString Shared::Message::getBody() const
@@ -311,6 +313,7 @@ void Shared::Message::serialize(QDataStream& data) const
         data << lastModified;
     }
     data << stanzaId;
+    data << attachPath;
 }
 
 void Shared::Message::deserialize(QDataStream& data)
@@ -341,6 +344,7 @@ void Shared::Message::deserialize(QDataStream& data)
         data >> lastModified;
     }
     data >> stanzaId;
+    data >> attachPath;
 }
 
 bool Shared::Message::change(const QMap<QString, QVariant>& data)
@@ -350,6 +354,16 @@ bool Shared::Message::change(const QMap<QString, QVariant>& data)
         setState(static_cast<State>(itr.value().toUInt()));
     }
     
+    itr = data.find("outOfBandUrl");
+    if (itr != data.end()) {
+        setOutOfBandUrl(itr.value().toString());
+    }
+    
+    itr = data.find("attachPath");
+    if (itr != data.end()) {
+        setAttachPath(itr.value().toString());
+    }
+    
     if (state == State::error) {
         itr = data.find("errorText");
         if (itr != data.end()) {
@@ -432,3 +446,13 @@ QString Shared::Message::getStanzaId() const
 {
     return stanzaId;
 }
+
+QString Shared::Message::getAttachPath() const
+{
+    return attachPath;
+}
+
+void Shared::Message::setAttachPath(const QString& path)
+{
+    attachPath = path;
+}
diff --git a/shared/message.h b/shared/message.h
index d84053f..2082101 100644
--- a/shared/message.h
+++ b/shared/message.h
@@ -16,15 +16,15 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
+#ifndef SHAPER_MESSAGE_H
+#define SHAPER_MESSAGE_H
+
 #include <QString>
 #include <QDateTime>
 #include <QVariant>
 #include <QMap>
 #include <QDataStream>
 
-#ifndef SHAPER_MESSAGE_H
-#define SHAPER_MESSAGE_H
-
 namespace Shared {
 
 /**
@@ -72,6 +72,7 @@ public:
     void setErrorText(const QString& err);
     bool change(const QMap<QString, QVariant>& data);
     void setStanzaId(const QString& sid);
+    void setAttachPath(const QString& path);
     
     QString getFrom() const;
     QString getFromJid() const;
@@ -100,6 +101,7 @@ public:
     QDateTime getLastModified() const;
     QString getOriginalBody() const;
     QString getStanzaId() const;
+    QString getAttachPath() const;
     
     void serialize(QDataStream& data) const;
     void deserialize(QDataStream& data);
@@ -123,6 +125,7 @@ private:
     QString originalMessage;
     QDateTime lastModified;
     QString stanzaId;
+    QString attachPath;
 };
 
 }
diff --git a/ui/models/messagefeed.cpp b/ui/models/messagefeed.cpp
index 79d3755..a5a6b15 100644
--- a/ui/models/messagefeed.cpp
+++ b/ui/models/messagefeed.cpp
@@ -40,7 +40,9 @@ Models::MessageFeed::MessageFeed(const Element* ri, QObject* parent):
     indexById(storage.get<id>()),
     indexByTime(storage.get<time>()),
     rosterItem(ri),
-    syncState(incomplete)
+    syncState(incomplete),
+    uploads(),
+    downloads()
 {
 }
 
@@ -138,11 +140,8 @@ QVariant Models::MessageFeed::data(const QModelIndex& index, int role) const
                 }
             }
                 break;
-            case Attach: {
-                ::Models::Attach att;
-                
-                answer.setValue(att);
-            }
+            case Attach: 
+                answer.setValue(fillAttach(*msg));
                 break;
             case Bulk: {
                 FeedItem item;
@@ -171,6 +170,7 @@ QVariant Models::MessageFeed::data(const QModelIndex& index, int role) const
                 if (item.avatar.size() == 0) {
                     item.avatar = Shared::iconPath("user", true);
                 }
+                item.attach = fillAttach(*msg);
                 answer.setValue(item);
             }
                 break;
@@ -246,3 +246,39 @@ bool Models::MessageFeed::sentByMe(const Shared::Message& msg) const
         return msg.getOutgoing();
     }
 }
+
+Models::Attachment Models::MessageFeed::fillAttach(const Shared::Message& msg) const
+{
+    ::Models::Attachment att;
+    
+    att.localPath = msg.getAttachPath();
+    att.remotePath = msg.getOutOfBandUrl();
+    
+    if (att.remotePath.size() == 0) {
+        if (att.localPath.size() == 0) {
+            att.state = none;
+        } else {
+            Progress::const_iterator itr = uploads.find(msg.getId());
+            if (itr == uploads.end()) {
+                att.state = local;
+            } else {
+                att.state = uploading;
+                att.progress = itr->second;
+            }
+        }
+    } else {
+        if (att.localPath.size() == 0) {
+            Progress::const_iterator itr = downloads.find(msg.getId());
+            if (itr == downloads.end()) {
+                att.state = remote;
+            } else {
+                att.state = downloading;
+                att.progress = itr->second;
+            }
+        } else {
+            att.state = ready;
+        }
+    }
+    
+    return att;
+}
diff --git a/ui/models/messagefeed.h b/ui/models/messagefeed.h
index 78d54ad..d0e7599 100644
--- a/ui/models/messagefeed.h
+++ b/ui/models/messagefeed.h
@@ -34,6 +34,7 @@
 
 namespace Models {
     class Element;
+    struct Attachment;
     
 class MessageFeed : public QAbstractListModel
 {
@@ -63,6 +64,7 @@ signals:
     
 protected:
     bool sentByMe(const Shared::Message& msg) const;
+    Attachment fillAttach(const Shared::Message& msg) const;
     
 public:
     enum MessageRoles {
@@ -120,21 +122,27 @@ private:
     const Element* rosterItem;
     SyncState syncState;
     
+    typedef std::map<QString, qreal> Progress;
+    Progress uploads;
+    Progress downloads;
+    
     static const QHash<int, QByteArray> roles;
 };
 
-enum Attachment {
+enum AttachmentType {
     none,
     remote,
+    local,
     downloading,
     uploading,
     ready
 };
 
-struct Attach {
-    Attachment state;
+struct Attachment {
+    AttachmentType state;
     qreal progress;
     QString localPath;
+    QString remotePath;
 };
 
 struct FeedItem {
@@ -145,11 +153,11 @@ struct FeedItem {
     bool correction;
     QDateTime date;
     Shared::Message::State state;
-    Attach attach;
+    Attachment attach;
 };
 };
 
-Q_DECLARE_METATYPE(Models::Attach);
+Q_DECLARE_METATYPE(Models::Attachment);
 Q_DECLARE_METATYPE(Models::FeedItem);
 
 #endif // MESSAGEFEED_H
diff --git a/ui/utils/feedview.cpp b/ui/utils/feedview.cpp
index 69c6ce9..47cbc63 100644
--- a/ui/utils/feedview.cpp
+++ b/ui/utils/feedview.cpp
@@ -191,7 +191,7 @@ bool FeedView::tryToCalculateGeometriesWithNoScrollbars(const QStyleOptionViewIt
 
 void FeedView::paintEvent(QPaintEvent* event)
 {
-    qDebug() << "paint" << event->rect();
+    //qDebug() << "paint" << event->rect();
     const QAbstractItemModel* m = model();
     QWidget* vp = viewport();
     QRect zone = event->rect().translated(0, -vo);
diff --git a/ui/utils/messagedelegate.cpp b/ui/utils/messagedelegate.cpp
index 089557b..52f9f35 100644
--- a/ui/utils/messagedelegate.cpp
+++ b/ui/utils/messagedelegate.cpp
@@ -33,12 +33,24 @@ nickFont(),
 dateFont(),
 bodyMetrics(bodyFont),
 nickMetrics(nickFont),
-dateMetrics(dateFont)
+dateMetrics(dateFont),
+downloadButton(new QPushButton()),
+uploadButton(new QPushButton())
 {
+    downloadButton->setText(tr("Download"));
+    uploadButton->setText(tr("Retry"));
+    
+    //this is done for the buttons to calculate their acual size for we are going to use them further
+    downloadButton->setAttribute(Qt::WA_DontShowOnScreen);
+    uploadButton->setAttribute(Qt::WA_DontShowOnScreen);
+    downloadButton->show();
+    uploadButton->show();
 }
 
 MessageDelegate::~MessageDelegate()
 {
+    delete uploadButton;
+    delete downloadButton;
 }
 
 void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
@@ -89,7 +101,38 @@ void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio
     painter->setFont(nickFont);
     painter->drawText(opt.rect, opt.displayAlignment, data.sender, &rect);
     
+    
     opt.rect.adjust(0, rect.height(), 0, 0);
+    painter->save();
+    switch (data.attach.state) {
+        case Models::none:
+            break;
+        case Models::uploading:
+        case Models::downloading:
+            break;
+        case Models::remote:
+            if (data.sentByMe) {
+                painter->translate(option.rect.width() - avatarHeight - margin * 2 - downloadButton->width(), opt.rect.top());
+            } else {
+                painter->translate(opt.rect.topLeft());
+            }
+            downloadButton->render(painter, QPoint(), QRegion(), QWidget::DrawChildren);
+            opt.rect.adjust(0, downloadButton->height(), 0, 0);
+            break;
+        case Models::local:
+            if (data.sentByMe) {
+                painter->translate(option.rect.width() - avatarHeight - margin * 2 - uploadButton->width(), opt.rect.top());
+            } else {
+                painter->translate(opt.rect.topLeft());
+            }
+            uploadButton->render(painter, QPoint(), QRegion(), QWidget::DrawChildren);
+            opt.rect.adjust(0, uploadButton->height(), 0, 0);
+            break;
+        case Models::ready:
+            break;
+    }
+    painter->restore();
+    
     painter->setFont(bodyFont);
     painter->drawText(opt.rect, opt.displayAlignment | Qt::TextWordWrap, data.text, &rect);
     
@@ -108,8 +151,24 @@ QSize MessageDelegate::sizeHint(const QStyleOptionViewItem& option, const QModel
     QRect messageRect = option.rect.adjusted(0, margin / 2, -(avatarHeight + 3 * margin), -margin / 2);
     QStyleOptionViewItem opt = option;
     opt.rect = messageRect;
+    QVariant va = index.data(Models::MessageFeed::Attach);
+    Models::Attachment attach = qvariant_cast<Models::Attachment>(va);
     QSize messageSize = bodyMetrics.boundingRect(messageRect, Qt::TextWordWrap, index.data(Models::MessageFeed::Text).toString()).size();
     
+    switch (attach.state) {
+        case Models::none:
+            break;
+        case Models::uploading:
+        case Models::downloading:
+            break;
+        case Models::remote:
+        case Models::local:
+            messageSize.rheight() += downloadButton->height();
+            break;
+        case Models::ready:
+            break;
+    }
+    
     messageSize.rheight() += nickMetrics.lineSpacing();
     messageSize.rheight() += dateMetrics.height();
     
diff --git a/ui/utils/messagedelegate.h b/ui/utils/messagedelegate.h
index 4daa0a2..396ff43 100644
--- a/ui/utils/messagedelegate.h
+++ b/ui/utils/messagedelegate.h
@@ -22,6 +22,7 @@
 #include <QStyledItemDelegate>
 #include <QFont>
 #include <QFontMetrics>
+#include <QPushButton>
 
 #include "shared/icons.h"
 
@@ -46,6 +47,9 @@ private:
     QFontMetrics bodyMetrics;
     QFontMetrics nickMetrics;
     QFontMetrics dateMetrics;
+    
+    QPushButton* downloadButton;
+    QPushButton* uploadButton;
 };
 
 #endif // MESSAGEDELEGATE_H

From b3c6860e25f4f4726a8eda5b18a5dd79df0aa426 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Tue, 2 Feb 2021 01:55:15 +0300
Subject: [PATCH 091/281] seems like i have found solution how to properly
 render buttons

---
 ui/models/messagefeed.cpp    |  20 ++++-
 ui/models/messagefeed.h      |   2 +
 ui/utils/feedview.cpp        |  37 ++++++++-
 ui/utils/feedview.h          |   3 +
 ui/utils/messagedelegate.cpp | 146 +++++++++++++++++++++++++++--------
 ui/utils/messagedelegate.h   |  27 ++++++-
 ui/widgets/conversation.cpp  |   2 +-
 7 files changed, 196 insertions(+), 41 deletions(-)

diff --git a/ui/models/messagefeed.cpp b/ui/models/messagefeed.cpp
index a5a6b15..78c216f 100644
--- a/ui/models/messagefeed.cpp
+++ b/ui/models/messagefeed.cpp
@@ -31,6 +31,7 @@ const QHash<int, QByteArray> Models::MessageFeed::roles = {
     {SentByMe,"sentByMe"},
     {Avatar, "avatar"},
     {Attach, "attach"},
+    {Id, "id"},
     {Bulk, "bulk"}
 };
 
@@ -94,8 +95,12 @@ QVariant Models::MessageFeed::data(const QModelIndex& index, int role) const
         
         switch (role) {
             case Qt::DisplayRole:
-            case Text: 
-                answer = msg->getBody();
+            case Text: {
+                QString body = msg->getBody();
+                if (body != msg->getOutOfBandUrl()) {
+                    answer = body;
+                }
+            }
                 break;
             case Sender: 
                 if (sentByMe(*msg)) {
@@ -143,13 +148,22 @@ QVariant Models::MessageFeed::data(const QModelIndex& index, int role) const
             case Attach: 
                 answer.setValue(fillAttach(*msg));
                 break;
+            case Id: 
+                answer.setValue(msg->getId());
+                break;
             case Bulk: {
                 FeedItem item;
+                item.id = msg->getId();
                 item.sentByMe = sentByMe(*msg);
                 item.date = msg->getTime();
                 item.state = msg->getState();
                 item.correction = msg->getEdited();
-                item.text = msg->getBody();
+                
+                QString body = msg->getBody();
+                if (body != msg->getOutOfBandUrl()) {
+                    item.text = body;
+                }
+                
                 item.avatar.clear();
                 if (item.sentByMe) {
                     item.sender = rosterItem->getAccountName();
diff --git a/ui/models/messagefeed.h b/ui/models/messagefeed.h
index d0e7599..bc403c1 100644
--- a/ui/models/messagefeed.h
+++ b/ui/models/messagefeed.h
@@ -76,6 +76,7 @@ public:
         SentByMe,
         Avatar,
         Attach,
+        Id,
         Bulk
     };
     
@@ -146,6 +147,7 @@ struct Attachment {
 };
 
 struct FeedItem {
+    QString id;
     QString text;
     QString sender;
     QString avatar;
diff --git a/ui/utils/feedview.cpp b/ui/utils/feedview.cpp
index 47cbc63..21f2956 100644
--- a/ui/utils/feedview.cpp
+++ b/ui/utils/feedview.cpp
@@ -23,13 +23,17 @@
 #include <QScrollBar>
 #include <QDebug>
 
+#include "messagedelegate.h"
+
 constexpr int maxMessageHeight = 10000;
 constexpr int approximateSingleMessageHeight = 20;
 
 FeedView::FeedView(QWidget* parent):
     QAbstractItemView(parent),
     hints(),
-    vo(0)
+    vo(0),
+    specialDelegate(false),
+    clearWidgetsMode(false)
 {
     horizontalScrollBar()->setRange(0, 0);
     verticalScrollBar()->setSingleStep(approximateSingleMessageHeight);
@@ -163,6 +167,10 @@ void FeedView::updateGeometries()
         bar->setPageStep(layoutBounds.height());
         bar->setValue(previousOffset - layoutBounds.height() - vo);
     }
+    
+    if (specialDelegate) {
+        clearWidgetsMode = true;
+    }
 }
 
 bool FeedView::tryToCalculateGeometriesWithNoScrollbars(const QStyleOptionViewItem& option, const QAbstractItemModel* m, uint32_t totalHeight)
@@ -222,17 +230,32 @@ void FeedView::paintEvent(QPaintEvent* event)
     option.features = QStyleOptionViewItem::WrapText;
     QPoint cursor = vp->mapFromGlobal(QCursor::pos());
     
+    if (clearWidgetsMode && specialDelegate) {
+        MessageDelegate* del = dynamic_cast<MessageDelegate*>(itemDelegate());
+        del->beginClearWidgets();
+    }
+    
     for (const QModelIndex& index : toRener) {
         option.rect = visualRect(index);
         option.state.setFlag(QStyle::State_MouseOver, option.rect.contains(cursor));
         itemDelegate(index)->paint(&painter, option, index);
     }
+    
+    if (clearWidgetsMode && specialDelegate) {
+        MessageDelegate* del = dynamic_cast<MessageDelegate*>(itemDelegate());
+        del->endClearWidgets();
+        clearWidgetsMode = false;
+    }
 }
 
 void FeedView::verticalScrollbarValueChanged(int value)
 {
     vo = verticalScrollBar()->maximum() - value;
     
+    if (specialDelegate) {
+        clearWidgetsMode = true;
+    }
+    
     QAbstractItemView::verticalScrollbarValueChanged(vo);
 }
 
@@ -250,3 +273,15 @@ QFont FeedView::getFont() const
 {
     return viewOptions().font;
 }
+
+void FeedView::setItemDelegate(QAbstractItemDelegate* delegate)
+{
+    QAbstractItemView::setItemDelegate(delegate);
+    
+    MessageDelegate* del = dynamic_cast<MessageDelegate*>(delegate);
+    if (del) {
+        specialDelegate = true;
+    } else {
+        specialDelegate = false;
+    }
+}
diff --git a/ui/utils/feedview.h b/ui/utils/feedview.h
index 50f46e4..0256a4d 100644
--- a/ui/utils/feedview.h
+++ b/ui/utils/feedview.h
@@ -42,6 +42,7 @@ public:
     QModelIndex moveCursor(QAbstractItemView::CursorAction cursorAction, Qt::KeyboardModifiers modifiers) override;
     void setSelection(const QRect & rect, QItemSelectionModel::SelectionFlags command) override;
     QRegion visualRegionForSelection(const QItemSelection & selection) const override;
+    void setItemDelegate(QAbstractItemDelegate* delegate);
     
     QFont getFont() const;
     
@@ -69,6 +70,8 @@ private:
     };
     std::deque<Hint> hints;
     int vo;
+    bool specialDelegate;
+    bool clearWidgetsMode;
     
 };
 
diff --git a/ui/utils/messagedelegate.cpp b/ui/utils/messagedelegate.cpp
index 52f9f35..5aebebe 100644
--- a/ui/utils/messagedelegate.cpp
+++ b/ui/utils/messagedelegate.cpp
@@ -19,6 +19,7 @@
 #include <QDebug>
 #include <QPainter>
 #include <QApplication>
+#include <QMouseEvent>
 
 #include "messagedelegate.h"
 #include "ui/models/messagefeed.h"
@@ -34,23 +35,23 @@ dateFont(),
 bodyMetrics(bodyFont),
 nickMetrics(nickFont),
 dateMetrics(dateFont),
-downloadButton(new QPushButton()),
-uploadButton(new QPushButton())
+buttonHeight(0),
+buttons(new std::map<QString, FeedButton*>()),
+idsToKeep(new std::set<QString>()),
+clearingWidgets(false)
 {
-    downloadButton->setText(tr("Download"));
-    uploadButton->setText(tr("Retry"));
-    
-    //this is done for the buttons to calculate their acual size for we are going to use them further
-    downloadButton->setAttribute(Qt::WA_DontShowOnScreen);
-    uploadButton->setAttribute(Qt::WA_DontShowOnScreen);
-    downloadButton->show();
-    uploadButton->show();
+    QPushButton btn;
+    buttonHeight = btn.sizeHint().height();
 }
 
 MessageDelegate::~MessageDelegate()
 {
-    delete uploadButton;
-    delete downloadButton;
+    for (const std::pair<const QString, FeedButton*>& pair: *buttons){
+        delete pair.second;
+    }
+    
+    delete idsToKeep;
+    delete buttons;
 }
 
 void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
@@ -85,7 +86,10 @@ void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio
     }
     opt.rect = messageRect;
     
-    QSize messageSize = bodyMetrics.boundingRect(messageRect, Qt::TextWordWrap, data.text).size();
+    QSize messageSize(0, 0);
+    if (data.text.size() > 0) {
+        messageSize = bodyMetrics.boundingRect(messageRect, Qt::TextWordWrap, data.text).size();
+    }
     messageSize.rheight() += nickMetrics.lineSpacing();
     messageSize.rheight() += dateMetrics.height();
     if (messageSize.width() < opt.rect.width()) {
@@ -111,32 +115,19 @@ void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio
         case Models::downloading:
             break;
         case Models::remote:
-            if (data.sentByMe) {
-                painter->translate(option.rect.width() - avatarHeight - margin * 2 - downloadButton->width(), opt.rect.top());
-            } else {
-                painter->translate(opt.rect.topLeft());
-            }
-            downloadButton->render(painter, QPoint(), QRegion(), QWidget::DrawChildren);
-            opt.rect.adjust(0, downloadButton->height(), 0, 0);
-            break;
         case Models::local:
-            if (data.sentByMe) {
-                painter->translate(option.rect.width() - avatarHeight - margin * 2 - uploadButton->width(), opt.rect.top());
-            } else {
-                painter->translate(opt.rect.topLeft());
-            }
-            uploadButton->render(painter, QPoint(), QRegion(), QWidget::DrawChildren);
-            opt.rect.adjust(0, uploadButton->height(), 0, 0);
+            paintButton(getButton(data), painter, data.sentByMe, opt);
             break;
         case Models::ready:
             break;
     }
     painter->restore();
     
-    painter->setFont(bodyFont);
-    painter->drawText(opt.rect, opt.displayAlignment | Qt::TextWordWrap, data.text, &rect);
-    
-    opt.rect.adjust(0, rect.height(), 0, 0);
+    if (data.text.size() > 0) {
+        painter->setFont(bodyFont);
+        painter->drawText(opt.rect, opt.displayAlignment | Qt::TextWordWrap, data.text, &rect);
+        opt.rect.adjust(0, rect.height(), 0, 0);
+    }
     painter->setFont(dateFont);
     QColor q = painter->pen().color();
     q.setAlpha(180);
@@ -144,6 +135,10 @@ void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio
     painter->drawText(opt.rect, opt.displayAlignment, data.date.toLocalTime().toString(), &rect);
     
     painter->restore();
+    
+    if (clearingWidgets) {
+        idsToKeep->insert(data.id);
+    }
 }
 
 QSize MessageDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const
@@ -153,7 +148,11 @@ QSize MessageDelegate::sizeHint(const QStyleOptionViewItem& option, const QModel
     opt.rect = messageRect;
     QVariant va = index.data(Models::MessageFeed::Attach);
     Models::Attachment attach = qvariant_cast<Models::Attachment>(va);
-    QSize messageSize = bodyMetrics.boundingRect(messageRect, Qt::TextWordWrap, index.data(Models::MessageFeed::Text).toString()).size();
+    QString body = index.data(Models::MessageFeed::Text).toString();
+    QSize messageSize(0, 0);
+    if (body.size() > 0) {
+        messageSize = bodyMetrics.boundingRect(messageRect, Qt::TextWordWrap, body).size();
+    }
     
     switch (attach.state) {
         case Models::none:
@@ -163,7 +162,7 @@ QSize MessageDelegate::sizeHint(const QStyleOptionViewItem& option, const QModel
             break;
         case Models::remote:
         case Models::local:
-            messageSize.rheight() += downloadButton->height();
+            messageSize.rheight() += buttonHeight;
             break;
         case Models::ready:
             break;
@@ -204,9 +203,88 @@ void MessageDelegate::initializeFonts(const QFont& font)
 bool MessageDelegate::editorEvent(QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& index)
 {
     //qDebug() << event->type();
+    
+    
     return QStyledItemDelegate::editorEvent(event, model, option, index);
 }
 
+void MessageDelegate::paintButton(QPushButton* btn, QPainter* painter, bool sentByMe, QStyleOptionViewItem& option) const
+{
+    QPoint start;
+    if (sentByMe) {
+        start = {option.rect.width() - btn->width(), option.rect.top()};
+    } else {
+        start = option.rect.topLeft();
+    }
+    
+    QWidget* vp = static_cast<QWidget*>(painter->device());
+    btn->setParent(vp);
+    btn->move(start);
+    btn->show();
+    
+    option.rect.adjust(0, buttonHeight, 0, 0);
+}
+
+
+QPushButton * MessageDelegate::getButton(const Models::FeedItem& data) const
+{
+    std::map<QString, FeedButton*>::const_iterator itr = buttons->find(data.id);
+    FeedButton* result = 0;
+    if (itr != buttons->end()) {
+        if (
+            (data.attach.state == Models::remote && itr->second->download) ||
+            (data.attach.state == Models::local && !itr->second->download)
+        ) {
+            result = itr->second;
+        } else {
+            delete itr->second;
+            buttons->erase(itr);
+        }
+    }
+    
+    if (result == 0) {
+        result = new FeedButton();
+        result->messageId = data.id;
+        if (data.attach.state == Models::remote) {
+            result->setText(QCoreApplication::translate("MessageLine", "Download"));
+            result->download = true;
+        } else {
+            result->setText(QCoreApplication::translate("MessageLine", "Upload"));
+            result->download = false;
+        }
+        buttons->insert(std::make_pair(data.id, result));
+    }
+    
+    return result;
+}
+
+
+void MessageDelegate::beginClearWidgets()
+{
+    idsToKeep->clear();
+    clearingWidgets = true;
+}
+
+void MessageDelegate::endClearWidgets()
+{
+    if (clearingWidgets) {
+        std::set<QString> toRemove;
+        for (const std::pair<const QString, FeedButton*>& pair: *buttons){
+            if (idsToKeep->find(pair.first) == idsToKeep->end()) {
+                delete pair.second;
+                toRemove.insert(pair.first);
+            }
+        }
+        
+        for (const QString& key : toRemove) {
+            buttons->erase(key);
+        }
+        
+        idsToKeep->clear();
+        clearingWidgets = false;
+    }
+}
+
 
 // void MessageDelegate::setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const
 // {
diff --git a/ui/utils/messagedelegate.h b/ui/utils/messagedelegate.h
index 396ff43..b71163c 100644
--- a/ui/utils/messagedelegate.h
+++ b/ui/utils/messagedelegate.h
@@ -19,13 +19,21 @@
 #ifndef MESSAGEDELEGATE_H
 #define MESSAGEDELEGATE_H
 
+#include <map>
+#include <set>
+
 #include <QStyledItemDelegate>
+#include <QStyleOptionButton>
 #include <QFont>
 #include <QFontMetrics>
 #include <QPushButton>
 
 #include "shared/icons.h"
 
+namespace Models {
+    struct FeedItem;
+};
+
 class MessageDelegate : public QStyledItemDelegate
 {
     Q_OBJECT
@@ -39,8 +47,20 @@ public:
     
     void initializeFonts(const QFont& font);
     bool editorEvent(QEvent * event, QAbstractItemModel * model, const QStyleOptionViewItem & option, const QModelIndex & index) override;
+    void endClearWidgets();
+    void beginClearWidgets();
+    
+protected:
+    void paintButton(QPushButton* btn, QPainter* painter, bool sentByMe, QStyleOptionViewItem& option) const;
+    QPushButton* getButton(const Models::FeedItem& data) const;
     
 private:
+    class FeedButton : public QPushButton {
+    public:
+        QString messageId;
+        bool download;
+    };
+    
     QFont bodyFont;
     QFont nickFont;
     QFont dateFont;
@@ -48,8 +68,11 @@ private:
     QFontMetrics nickMetrics;
     QFontMetrics dateMetrics;
     
-    QPushButton* downloadButton;
-    QPushButton* uploadButton;
+    int buttonHeight;
+    
+    std::map<QString, FeedButton*>* buttons;
+    std::set<QString>* idsToKeep;
+    bool clearingWidgets;
 };
 
 #endif // MESSAGEDELEGATE_H
diff --git a/ui/widgets/conversation.cpp b/ui/widgets/conversation.cpp
index b31b59d..e10058d 100644
--- a/ui/widgets/conversation.cpp
+++ b/ui/widgets/conversation.cpp
@@ -46,7 +46,7 @@ Conversation::Conversation(bool muc, Models::Account* acc, Models::Element* el,
     overlay(new QWidget()),
     filesToAttach(),
     feed(new FeedView()),
-    delegate(new MessageDelegate()),
+    delegate(new MessageDelegate(this)),
     scroll(down),
     manualSliderChange(false),
     requestingHistory(false),

From ebe5addfb5438ef3a3a73b95b04ac42b3ded2eec Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Sat, 6 Feb 2021 14:02:42 +0300
Subject: [PATCH 092/281] just proxying button event from feed view delegate to
 the feed model

---
 ui/models/messagefeed.cpp    | 10 ++++++++++
 ui/models/messagefeed.h      |  2 ++
 ui/utils/feedview.cpp        | 37 ++++++++++++++++++++++++++++++++++--
 ui/utils/feedview.h          |  3 +++
 ui/utils/messagedelegate.cpp |  6 ++++++
 ui/utils/messagedelegate.h   |  6 ++++++
 6 files changed, 62 insertions(+), 2 deletions(-)

diff --git a/ui/models/messagefeed.cpp b/ui/models/messagefeed.cpp
index 78c216f..7d167cd 100644
--- a/ui/models/messagefeed.cpp
+++ b/ui/models/messagefeed.cpp
@@ -296,3 +296,13 @@ Models::Attachment Models::MessageFeed::fillAttach(const Shared::Message& msg) c
     
     return att;
 }
+
+void Models::MessageFeed::downloadAttachment(const QString& messageId)
+{
+    qDebug() << "request to download attachment of the message" << messageId;
+}
+
+void Models::MessageFeed::uploadAttachment(const QString& messageId)
+{
+    qDebug() << "request to upload attachment of the message" << messageId;
+}
diff --git a/ui/models/messagefeed.h b/ui/models/messagefeed.h
index bc403c1..9a58c45 100644
--- a/ui/models/messagefeed.h
+++ b/ui/models/messagefeed.h
@@ -55,6 +55,8 @@ public:
     QHash<int, QByteArray> roleNames() const override;
     
     void responseArchive(const std::list<Shared::Message> list, bool last);
+    void downloadAttachment(const QString& messageId);
+    void uploadAttachment(const QString& messageId);
     
     unsigned int unreadMessagesCount() const;
     
diff --git a/ui/utils/feedview.cpp b/ui/utils/feedview.cpp
index 21f2956..15f6fb3 100644
--- a/ui/utils/feedview.cpp
+++ b/ui/utils/feedview.cpp
@@ -24,6 +24,7 @@
 #include <QDebug>
 
 #include "messagedelegate.h"
+#include "ui/models/messagefeed.h"
 
 constexpr int maxMessageHeight = 10000;
 constexpr int approximateSingleMessageHeight = 20;
@@ -33,6 +34,7 @@ FeedView::FeedView(QWidget* parent):
     hints(),
     vo(0),
     specialDelegate(false),
+    specialModel(false),
     clearWidgetsMode(false)
 {
     horizontalScrollBar()->setRange(0, 0);
@@ -231,7 +233,7 @@ void FeedView::paintEvent(QPaintEvent* event)
     QPoint cursor = vp->mapFromGlobal(QCursor::pos());
     
     if (clearWidgetsMode && specialDelegate) {
-        MessageDelegate* del = dynamic_cast<MessageDelegate*>(itemDelegate());
+        MessageDelegate* del = static_cast<MessageDelegate*>(itemDelegate());
         del->beginClearWidgets();
     }
     
@@ -242,7 +244,7 @@ void FeedView::paintEvent(QPaintEvent* event)
     }
     
     if (clearWidgetsMode && specialDelegate) {
-        MessageDelegate* del = dynamic_cast<MessageDelegate*>(itemDelegate());
+        MessageDelegate* del = static_cast<MessageDelegate*>(itemDelegate());
         del->endClearWidgets();
         clearWidgetsMode = false;
     }
@@ -276,12 +278,43 @@ QFont FeedView::getFont() const
 
 void FeedView::setItemDelegate(QAbstractItemDelegate* delegate)
 {
+    if (specialDelegate) {
+        MessageDelegate* del = static_cast<MessageDelegate*>(itemDelegate());
+        disconnect(del, &MessageDelegate::buttonPushed, this, &FeedView::onMessageButtonPushed);
+    }
+    
     QAbstractItemView::setItemDelegate(delegate);
     
     MessageDelegate* del = dynamic_cast<MessageDelegate*>(delegate);
     if (del) {
         specialDelegate = true;
+        connect(del, &MessageDelegate::buttonPushed, this, &FeedView::onMessageButtonPushed);
     } else {
         specialDelegate = false;
     }
 }
+
+void FeedView::setModel(QAbstractItemModel* model)
+{
+    QAbstractItemView::setModel(model);
+    
+    Models::MessageFeed* feed = dynamic_cast<Models::MessageFeed*>(model);
+    if (feed) {
+        specialModel = true;
+    } else {
+        specialModel = false;
+    }
+}
+
+void FeedView::onMessageButtonPushed(const QString& messageId, bool download)
+{
+    if (specialModel) {
+        Models::MessageFeed* feed = static_cast<Models::MessageFeed*>(model());
+        
+        if (download) {
+            feed->downloadAttachment(messageId);
+        } else {
+            feed->uploadAttachment(messageId);
+        }
+    }
+}
diff --git a/ui/utils/feedview.h b/ui/utils/feedview.h
index 0256a4d..6d16ea3 100644
--- a/ui/utils/feedview.h
+++ b/ui/utils/feedview.h
@@ -43,6 +43,7 @@ public:
     void setSelection(const QRect & rect, QItemSelectionModel::SelectionFlags command) override;
     QRegion visualRegionForSelection(const QItemSelection & selection) const override;
     void setItemDelegate(QAbstractItemDelegate* delegate);
+    void setModel(QAbstractItemModel * model) override;
     
     QFont getFont() const;
     
@@ -51,6 +52,7 @@ public slots:
 protected slots:
     void rowsInserted(const QModelIndex & parent, int start, int end) override;
     void verticalScrollbarValueChanged(int value) override;
+    void onMessageButtonPushed(const QString& messageId, bool download);
     
 protected:
     int verticalOffset() const override;
@@ -71,6 +73,7 @@ private:
     std::deque<Hint> hints;
     int vo;
     bool specialDelegate;
+    bool specialModel;
     bool clearWidgetsMode;
     
 };
diff --git a/ui/utils/messagedelegate.cpp b/ui/utils/messagedelegate.cpp
index 5aebebe..038b0af 100644
--- a/ui/utils/messagedelegate.cpp
+++ b/ui/utils/messagedelegate.cpp
@@ -253,6 +253,7 @@ QPushButton * MessageDelegate::getButton(const Models::FeedItem& data) const
             result->download = false;
         }
         buttons->insert(std::make_pair(data.id, result));
+        connect(result, &QPushButton::clicked, this, &MessageDelegate::onButtonPushed);
     }
     
     return result;
@@ -285,6 +286,11 @@ void MessageDelegate::endClearWidgets()
     }
 }
 
+void MessageDelegate::onButtonPushed() const
+{
+    FeedButton* btn = static_cast<FeedButton*>(sender());
+    emit buttonPushed(btn->messageId, btn->download);
+}
 
 // void MessageDelegate::setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const
 // {
diff --git a/ui/utils/messagedelegate.h b/ui/utils/messagedelegate.h
index b71163c..69ffb84 100644
--- a/ui/utils/messagedelegate.h
+++ b/ui/utils/messagedelegate.h
@@ -50,10 +50,16 @@ public:
     void endClearWidgets();
     void beginClearWidgets();
     
+signals:
+    void buttonPushed(const QString& messageId, bool download) const;
+    
 protected:
     void paintButton(QPushButton* btn, QPainter* painter, bool sentByMe, QStyleOptionViewItem& option) const;
     QPushButton* getButton(const Models::FeedItem& data) const;
     
+protected slots:
+    void onButtonPushed() const;
+    
 private:
     class FeedButton : public QPushButton {
     public:

From 85555da81f05684ab87ab8c352161cc479daae11 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Sun, 7 Feb 2021 20:02:11 +0300
Subject: [PATCH 093/281] just a temp one

---
 core/account.cpp                 |   3 -
 core/account.h                   |   1 -
 core/handlers/messagehandler.cpp |  16 ++++-
 core/handlers/messagehandler.h   |   5 +-
 core/squawk.cpp                  |  11 ----
 core/squawk.h                    |   1 -
 main.cpp                         |   5 +-
 ui/squawk.cpp                    |  31 +++------
 ui/squawk.h                      |   2 -
 ui/widgets/conversation.cpp      | 110 ++-----------------------------
 ui/widgets/conversation.h        |  21 ------
 11 files changed, 32 insertions(+), 174 deletions(-)

diff --git a/core/account.cpp b/core/account.cpp
index 21fe9e7..8452688 100644
--- a/core/account.cpp
+++ b/core/account.cpp
@@ -402,9 +402,6 @@ QString Core::Account::getFullJid() const {
 void Core::Account::sendMessage(const Shared::Message& data) {
     mh->sendMessage(data);}
 
-void Core::Account::sendMessage(const Shared::Message& data, const QString& path) {
-    mh->sendMessage(data, path);}
-
 void Core::Account::onMamMessageReceived(const QString& queryId, const QXmppMessage& msg)
 {
     if (msg.id().size() > 0 && (msg.body().size() > 0 || msg.outOfBandUrl().size() > 0)) {
diff --git a/core/account.h b/core/account.h
index 7b6b50d..1a46e24 100644
--- a/core/account.h
+++ b/core/account.h
@@ -88,7 +88,6 @@ public:
     void setPasswordType(Shared::AccountPassword pt);
     QString getFullJid() const;
     void sendMessage(const Shared::Message& data);
-    void sendMessage(const Shared::Message& data, const QString& path);
     void requestArchive(const QString& jid, int count, const QString& before);
     void subscribeToContact(const QString& jid, const QString& reason);
     void unsubscribeFromContact(const QString& jid, const QString& reason);
diff --git a/core/handlers/messagehandler.cpp b/core/handlers/messagehandler.cpp
index 0f0e09d..a236f1e 100644
--- a/core/handlers/messagehandler.cpp
+++ b/core/handlers/messagehandler.cpp
@@ -232,7 +232,16 @@ void Core::MessageHandler::onReceiptReceived(const QString& jid, const QString&
     }
 }
 
-void Core::MessageHandler::sendMessage(Shared::Message data)
+void Core::MessageHandler::sendMessage(const Shared::Message& data)
+{
+    if (data.getOutOfBandUrl().size() == 0 && data.getAttachPath().size() > 0) {
+        prepareUpload(data);
+    } else {
+        performSending(data);
+    }
+}
+
+void Core::MessageHandler::performSending(Shared::Message data)
 {
     QString jid = data.getPenPalJid();
     QString id = data.getId();
@@ -275,9 +284,10 @@ void Core::MessageHandler::sendMessage(Shared::Message data)
     });
 }
 
-void Core::MessageHandler::sendMessage(const Shared::Message& data, const QString& path)
+void Core::MessageHandler::prepareUpload(const Shared::Message& data)
 {
     if (acc->state == Shared::ConnectionState::connected) {
+        QString path = data.getAttachPath();
         QString url = acc->network->getFileRemoteUrl(path);
         if (url.size() != 0) {
             sendMessageWithLocalUploadedFile(data, url);
@@ -366,6 +376,6 @@ void Core::MessageHandler::sendMessageWithLocalUploadedFile(Shared::Message msg,
     if (msg.getBody().size() == 0) {
         msg.setBody(url);
     }
-    sendMessage(msg);
+    performSending(msg);
     //TODO removal/progress update
 }
diff --git a/core/handlers/messagehandler.h b/core/handlers/messagehandler.h
index be1545f..0b53cc2 100644
--- a/core/handlers/messagehandler.h
+++ b/core/handlers/messagehandler.h
@@ -44,8 +44,7 @@ public:
     MessageHandler(Account* account);
     
 public:
-    void sendMessage(Shared::Message data);
-    void sendMessage(const Shared::Message& data, const QString& path);
+    void sendMessage(const Shared::Message& data);
     void initializeMessage(Shared::Message& target, const QXmppMessage& source, bool outgoing = false, bool forwarded = false, bool guessing = false) const;
     
 public slots:
@@ -63,6 +62,8 @@ private:
     bool handleGroupMessage(const QXmppMessage& msg, bool outgoing = false, bool forwarded = false, bool guessing = false);
     void logMessage(const QXmppMessage& msg, const QString& reason = "Message wasn't handled: ");
     void sendMessageWithLocalUploadedFile(Shared::Message msg, const QString& url);
+    void performSending(Shared::Message data);
+    void prepareUpload(const Shared::Message& data);
     
 private:
     Account* acc;
diff --git a/core/squawk.cpp b/core/squawk.cpp
index 9116e47..c637c96 100644
--- a/core/squawk.cpp
+++ b/core/squawk.cpp
@@ -336,17 +336,6 @@ void Core::Squawk::sendMessage(const QString& account, const Shared::Message& da
     itr->second->sendMessage(data);
 }
 
-void Core::Squawk::sendMessage(const QString& account, const Shared::Message& data, const QString& path)
-{
-    AccountsMap::const_iterator itr = amap.find(account);
-    if (itr == amap.end()) {
-        qDebug("An attempt to send a message with non existing account, skipping");
-        return;
-    }
-    
-    itr->second->sendMessage(data, path);
-}
-
 void Core::Squawk::requestArchive(const QString& account, const QString& jid, int count, const QString& before)
 {
     AccountsMap::const_iterator itr = amap.find(account);
diff --git a/core/squawk.h b/core/squawk.h
index aa84f59..3aac06a 100644
--- a/core/squawk.h
+++ b/core/squawk.h
@@ -90,7 +90,6 @@ public slots:
     void disconnectAccount(const QString& account);
     void changeState(Shared::Availability state);
     void sendMessage(const QString& account, const Shared::Message& data);
-    void sendMessage(const QString& account, const Shared::Message& data, const QString& path);
     void requestArchive(const QString& account, const QString& jid, int count, const QString& before);
     void subscribeContact(const QString& account, const QString& jid, const QString& reason);
     void unsubscribeContact(const QString& account, const QString& jid, const QString& reason);
diff --git a/main.cpp b/main.cpp
index 4c4b3ea..0373af8 100644
--- a/main.cpp
+++ b/main.cpp
@@ -96,10 +96,7 @@ int main(int argc, char *argv[])
     QObject::connect(&w, &Squawk::connectAccount, squawk, &Core::Squawk::connectAccount);
     QObject::connect(&w, &Squawk::disconnectAccount, squawk, &Core::Squawk::disconnectAccount);
     QObject::connect(&w, &Squawk::changeState, squawk, &Core::Squawk::changeState);
-    QObject::connect(&w, qOverload<const QString&, const Shared::Message&>(&Squawk::sendMessage), 
-                     squawk, qOverload<const QString&, const Shared::Message&>(&Core::Squawk::sendMessage));
-    QObject::connect(&w, qOverload<const QString&, const Shared::Message&, const QString&>(&Squawk::sendMessage), 
-                     squawk, qOverload<const QString&, const Shared::Message&, const QString&>(&Core::Squawk::sendMessage));
+    QObject::connect(&w, &Squawk::sendMessage, squawk,&Core::Squawk::sendMessage);
     QObject::connect(&w, &Squawk::requestArchive, squawk, &Core::Squawk::requestArchive);
     QObject::connect(&w, &Squawk::subscribeContact, squawk, &Core::Squawk::subscribeContact);
     QObject::connect(&w, &Squawk::unsubscribeContact, squawk, &Core::Squawk::unsubscribeContact);
diff --git a/ui/squawk.cpp b/ui/squawk.cpp
index 1709dd6..52e1d9f 100644
--- a/ui/squawk.cpp
+++ b/ui/squawk.cpp
@@ -578,31 +578,20 @@ void Squawk::notify(const QString& account, const Shared::Message& msg)
 void Squawk::onConversationMessage(const Shared::Message& msg)
 {
     Conversation* conv = static_cast<Conversation*>(sender());
-    emit sendMessage(conv->getAccount(), msg);
     Models::Roster::ElId id = conv->getId();
     
     rosterModel.addMessage(conv->getAccount(), msg);
-}
-
-void Squawk::onConversationMessage(const Shared::Message& msg, const QString& path)
-{
-    Conversation* conv = static_cast<Conversation*>(sender());
-    Models::Roster::ElId id = conv->getId();
-    std::map<QString, std::set<Models::Roster::ElId>>::iterator itr = requestedFiles.insert(std::make_pair(msg.getId(), std::set<Models::Roster::ElId>())).first;
-    itr->second.insert(id);
     
-    if (currentConversation != 0 && currentConversation->getId() == id) {
-        if (conv == currentConversation) {
-            Conversations::iterator itr = conversations.find(id);
-            if (itr != conversations.end()) {
-                itr->second->appendMessageWithUpload(msg, path);
-            }
-        } else {
-            currentConversation->appendMessageWithUpload(msg, path);
-        }
+    QString ap = msg.getAttachPath();
+    QString oob = msg.getOutOfBandUrl();
+    if ((ap.size() > 0 && oob.size() == 0) || (ap.size() == 0 && oob.size() > 0)) {
+        std::map<QString, std::set<Models::Roster::ElId>>::iterator itr = requestedFiles.insert(std::make_pair(msg.getId(), std::set<Models::Roster::ElId>())).first;
+        itr->second.insert(id);
+        
+        //TODO can also start downloading here if someone attached the message with the remote url
     }
     
-    emit sendMessage(conv->getAccount(), msg, path);
+    emit sendMessage(conv->getAccount(), msg);
 }
 
 void Squawk::onConversationRequestArchive(const QString& account, const QString& jid, const QString& before)
@@ -1052,9 +1041,7 @@ void Squawk::onPasswordPromptRejected()
 void Squawk::subscribeConversation(Conversation* conv)
 {
     connect(conv, &Conversation::destroyed, this, &Squawk::onConversationClosed);
-    connect(conv, qOverload<const Shared::Message&>(&Conversation::sendMessage), this, qOverload<const Shared::Message&>(&Squawk::onConversationMessage));
-    connect(conv, qOverload<const Shared::Message&, const QString&>(&Conversation::sendMessage), 
-            this, qOverload<const Shared::Message&, const QString&>(&Squawk::onConversationMessage));
+    connect(conv, &Conversation::sendMessage, this, &Squawk::onConversationMessage);
     connect(conv, &Conversation::requestLocalFile, this, &Squawk::onConversationRequestLocalFile);
     connect(conv, &Conversation::downloadFile, this, &Squawk::onConversationDownloadFile);
 }
diff --git a/ui/squawk.h b/ui/squawk.h
index a0d776d..26f7753 100644
--- a/ui/squawk.h
+++ b/ui/squawk.h
@@ -63,7 +63,6 @@ signals:
     void disconnectAccount(const QString&);
     void changeState(Shared::Availability state);
     void sendMessage(const QString& account, const Shared::Message& data);
-    void sendMessage(const QString& account, const Shared::Message& data, const QString& path);
     void requestArchive(const QString& account, const QString& jid, int count, const QString& before);
     void subscribeContact(const QString& account, const QString& jid, const QString& reason);
     void unsubscribeContact(const QString& account, const QString& jid, const QString& reason);
@@ -147,7 +146,6 @@ private slots:
     void onComboboxActivated(int index);
     void onRosterItemDoubleClicked(const QModelIndex& item);
     void onConversationMessage(const Shared::Message& msg);
-    void onConversationMessage(const Shared::Message& msg, const QString& path);
     void onConversationRequestArchive(const QString& account, const QString& jid, const QString& before);
     void onRosterContextMenu(const QPoint& point);
     void onConversationRequestLocalFile(const QString& messageId, const QString& url);
diff --git a/ui/widgets/conversation.cpp b/ui/widgets/conversation.cpp
index e10058d..017b9ba 100644
--- a/ui/widgets/conversation.cpp
+++ b/ui/widgets/conversation.cpp
@@ -37,8 +37,6 @@ Conversation::Conversation(bool muc, Models::Account* acc, Models::Element* el,
     activePalResource(pRes),
     m_ui(new Ui::Conversation()),
     ker(),
-    scrollResizeCatcher(),
-    vis(),
     thread(),
     statusIcon(0),
     statusLabel(0),
@@ -69,11 +67,7 @@ Conversation::Conversation(bool muc, Models::Account* acc, Models::Element* el,
     statusLabel = m_ui->statusLabel;
     
     connect(&ker, &KeyEnterReceiver::enterPressed, this, &Conversation::onEnterPressed);
-    connect(&scrollResizeCatcher, &Resizer::resized, this, &Conversation::onScrollResize);
-    connect(&vis, &VisibilityCatcher::shown, this, &Conversation::onScrollResize);
-    connect(&vis, &VisibilityCatcher::hidden, this, &Conversation::onScrollResize);
     connect(m_ui->sendButton, &QPushButton::clicked, this, &Conversation::onEnterPressed);
-    //connect(line, &MessageLine::resize, this, &Conversation::onMessagesResize);
     //connect(line, &MessageLine::downloadFile, this, &Conversation::downloadFile);
     //connect(line, &MessageLine::uploadFile, this, qOverload<const Shared::Message&, const QString&>(&Conversation::sendMessage));
     //connect(line, &MessageLine::requestLocalFile, this, &Conversation::requestLocalFile);
@@ -95,7 +89,6 @@ Conversation::Conversation(bool muc, Models::Account* acc, Models::Element* el,
         //m_ui->scrollArea->setBackgroundRole(QPalette::Base);
     //}
     
-    //connect(vs, &QScrollBar::valueChanged, this, &Conversation::onSliderValueChanged);
     //m_ui->scrollArea->installEventFilter(&scrollResizeCatcher);
     
     //line->setMyAvatarPath(acc->getAvatarPath());
@@ -221,78 +214,18 @@ void Conversation::onEnterPressed()
         m_ui->messageEditor->clear();
         Shared::Message msg = createMessage();
         msg.setBody(body);
-        //addMessage(msg);
         emit sendMessage(msg);
     }
     if (filesToAttach.size() > 0) {
-//         for (Badge* badge : filesToAttach) {
-//             Shared::Message msg = createMessage();
-//             line->appendMessageWithUpload(msg, badge->id);
-//             usleep(1000);       //this is required for the messages not to have equal time when appending into messageline
-//         }
-//         clearAttachedFiles();
+        for (Badge* badge : filesToAttach) {
+            Shared::Message msg = createMessage();
+            msg.setAttachPath(badge->id);
+            emit sendMessage(msg);
+        }
+         clearAttachedFiles();
     }
 }
 
-void Conversation::appendMessageWithUpload(const Shared::Message& data, const QString& path)
-{
-//     line->appendMessageWithUploadNoSiganl(data, path);
-}
-
-void Conversation::onMessagesResize(int amount)
-{
-//     manualSliderChange = true;
-//     switch (scroll) {
-//         case down:
-//             m_ui->scrollArea->verticalScrollBar()->setValue(m_ui->scrollArea->verticalScrollBar()->maximum());
-//             break;
-//         case keep: {
-//             int max = m_ui->scrollArea->verticalScrollBar()->maximum();
-//             int value = m_ui->scrollArea->verticalScrollBar()->value() + amount;
-//             m_ui->scrollArea->verticalScrollBar()->setValue(value);
-//             
-//             if (value == max) {
-//                 scroll = down;
-//             } else {
-//                 scroll = nothing;
-//             }
-//         }
-//             break;
-//         default:
-//             break;
-//     }
-//     manualSliderChange = false;
-}
-
-void Conversation::onSliderValueChanged(int value)
-{
-//     if (!manualSliderChange) {
-//         if (value == m_ui->scrollArea->verticalScrollBar()->maximum()) {
-//             scroll = down;
-//         } else {
-//             if (!requestingHistory && value == 0) {
-//                 requestingHistory = true;
-//                  line->showBusyIndicator();
-//                  emit requestArchive(line->firstMessageId());
-//                 scroll = keep;
-//             } else {
-//                 scroll = nothing;
-//             }
-//         }
-//     }
-}
-
-void Conversation::responseArchive(const std::list<Shared::Message> list)
-{
-//     requestingHistory = false;
-//     scroll = keep;
-//     
-//     line->hideBusyIndicator();
-//     for (std::list<Shared::Message>::const_iterator itr = list.begin(), end = list.end(); itr != end; ++itr) {
-//         addMessage(*itr);
-//     }
-}
-
 void Conversation::showEvent(QShowEvent* event)
 {
     if (!everShown) {
@@ -335,19 +268,6 @@ void Conversation::setStatus(const QString& status)
     statusLabel->setText(Shared::processMessageBody(status));
 }
 
-void Conversation::onScrollResize()
-{
-//     if (everShown) {
-//         int size = m_ui->scrollArea->width();
-//         QScrollBar* bar = m_ui->scrollArea->verticalScrollBar();
-//         if (bar->isVisible() && !tsb) {
-//             size -= bar->width();
-//             
-//         }
-//          line->setMaximumWidth(size);
-//     }
-}
-
 void Conversation::responseFileProgress(const QString& messageId, qreal progress)
 {
 //     line->fileProgress(messageId, progress);
@@ -499,21 +419,3 @@ Shared::Message Conversation::createMessage() const
     return msg;
 }
 
-bool VisibilityCatcher::eventFilter(QObject* obj, QEvent* event)
-{
-    if (event->type() == QEvent::Show) {
-        emit shown();
-    }
-    
-    if (event->type() == QEvent::Hide) {
-        emit hidden();
-    }
-    
-    return false;
-}
-
-VisibilityCatcher::VisibilityCatcher(QWidget* parent):
-QObject(parent)
-{
-}
-
diff --git a/ui/widgets/conversation.h b/ui/widgets/conversation.h
index 2331e34..ac1ffcd 100644
--- a/ui/widgets/conversation.h
+++ b/ui/widgets/conversation.h
@@ -30,7 +30,6 @@
 #include "ui/models/account.h"
 #include "ui/models/roster.h"
 #include "ui/utils/messageline.h"
-#include "ui/utils/resizer.h"
 #include "ui/utils/flowlayout.h"
 #include "ui/utils/badge.h"
 #include "ui/utils/feedview.h"
@@ -56,19 +55,6 @@ signals:
     void enterPressed();
 };
 
-class VisibilityCatcher : public QObject {
-    Q_OBJECT
-public:
-    VisibilityCatcher(QWidget* parent = nullptr);
-    
-protected:
-    bool eventFilter(QObject* obj, QEvent* event) override;
-
-signals:
-    void hidden();
-    void shown();
-};
-
 class Conversation : public QWidget
 {
     Q_OBJECT
@@ -82,7 +68,6 @@ public:
     Models::Roster::ElId getId() const;
     
     void setPalResource(const QString& res);
-    void responseArchive(const std::list<Shared::Message> list);
     void showEvent(QShowEvent * event) override;
     void responseLocalFile(const QString& messageId, const QString& path);
     void fileError(const QString& messageId, const QString& error);
@@ -90,11 +75,9 @@ public:
     virtual void setAvatar(const QString& path);
     void changeMessage(const QString& id, const QMap<QString, QVariant>& data);
     void setFeedFrames(bool top, bool right, bool bottom, bool left);
-    virtual void appendMessageWithUpload(const Shared::Message& data, const QString& path);
     
 signals:
     void sendMessage(const Shared::Message& message);
-    void sendMessage(const Shared::Message& message, const QString& path);
     void requestArchive(const QString& before);
     void shown();
     void requestLocalFile(const QString& messageId, const QString& url);
@@ -114,8 +97,6 @@ protected:
     
 protected slots:
     void onEnterPressed();
-    void onMessagesResize(int amount);
-    void onSliderValueChanged(int value);
     void onAttach();
     void onFileSelected();
     void onScrollResize();
@@ -139,8 +120,6 @@ protected:
     QString activePalResource;
     QScopedPointer<Ui::Conversation> m_ui;
     KeyEnterReceiver ker;
-    Resizer scrollResizeCatcher;
-    VisibilityCatcher vis;
     QString thread;
     QLabel* statusIcon;
     QLabel* statusLabel;

From a0348b8fd2a0eb34e7bc575f6da842e30fbca767 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Sat, 27 Feb 2021 15:21:27 +0300
Subject: [PATCH 094/281] file progress events delivery methonds

---
 ui/models/element.cpp     |  7 +++++++
 ui/models/element.h       |  2 ++
 ui/models/messagefeed.cpp | 38 +++++++++++++++++++++++++++++++++-
 ui/models/messagefeed.h   |  3 +++
 ui/models/roster.cpp      | 43 ++++++++++++++++++++++++++++++++++++++-
 ui/models/roster.h        |  4 ++++
 ui/squawk.cpp             | 19 ++---------------
 7 files changed, 97 insertions(+), 19 deletions(-)

diff --git a/ui/models/element.cpp b/ui/models/element.cpp
index 20df389..7b0e70f 100644
--- a/ui/models/element.cpp
+++ b/ui/models/element.cpp
@@ -30,6 +30,7 @@ Models::Element::Element(Type p_type, const Models::Account* acc, const QString&
     feed(new MessageFeed(this))
 {
     connect(feed, &MessageFeed::requestArchive, this, &Element::requestArchive);
+    connect(feed, &MessageFeed::fileLocalPathRequest, this, &Element::fileLocalPathRequest);
     
     QMap<QString, QVariant>::const_iterator itr = data.find("avatarState");
     if (itr != data.end()) {
@@ -154,3 +155,9 @@ bool Models::Element::isRoom() const
 {
     return type != contact;
 }
+
+void Models::Element::fileProgress(const QString& messageId, qreal value)
+{
+    feed->fileProgress(messageId, value);
+}
+
diff --git a/ui/models/element.h b/ui/models/element.h
index 047a645..f537f9b 100644
--- a/ui/models/element.h
+++ b/ui/models/element.h
@@ -43,9 +43,11 @@ public:
     unsigned int getMessagesCount() const;
     void responseArchive(const std::list<Shared::Message> list, bool last);
     bool isRoom() const;
+    void fileProgress(const QString& messageId, qreal value);
     
 signals:
     void requestArchive(const QString& before);
+    void fileLocalPathRequest(const QString& messageId, const QString& url);
     
 protected:
     void setJid(const QString& p_jid);
diff --git a/ui/models/messagefeed.cpp b/ui/models/messagefeed.cpp
index 7d167cd..55451fb 100644
--- a/ui/models/messagefeed.cpp
+++ b/ui/models/messagefeed.cpp
@@ -299,10 +299,46 @@ Models::Attachment Models::MessageFeed::fillAttach(const Shared::Message& msg) c
 
 void Models::MessageFeed::downloadAttachment(const QString& messageId)
 {
-    qDebug() << "request to download attachment of the message" << messageId;
+    QModelIndex ind = modelIndexById(messageId);
+    if (ind.isValid()) {
+        std::pair<Progress::iterator, bool> progressPair = downloads.insert(std::make_pair(messageId, 0));
+        if (!progressPair.second) {     //Only to take action if we weren't already downloading it
+            Shared::Message* msg = static_cast<Shared::Message*>(ind.internalPointer());
+            emit dataChanged(ind, ind);
+            emit fileLocalPathRequest(messageId, msg->getOutOfBandUrl());
+        } else {
+            qDebug() << "Attachment download for message with id" << messageId << "is already in progress, skipping";
+        }
+    } else {
+        qDebug() << "An attempt to download an attachment for the message that doesn't exist. ID:" << messageId;
+    }
 }
 
 void Models::MessageFeed::uploadAttachment(const QString& messageId)
 {
     qDebug() << "request to upload attachment of the message" << messageId;
 }
+
+void Models::MessageFeed::fileProgress(const QString& messageId, qreal value)
+{
+    Progress::iterator itr = downloads.find(messageId);
+    if (itr != downloads.end()) {
+        itr->second = value;
+        QModelIndex ind = modelIndexById(messageId);
+        emit dataChanged(ind, ind);
+    }
+}
+
+QModelIndex Models::MessageFeed::modelIndexById(const QString& id) const
+{
+    StorageById::const_iterator itr = indexById.find(id);
+    if (itr != indexById.end()) {
+        Shared::Message* msg = *itr;
+        StorageByTime::const_iterator tItr = indexByTime.upper_bound(msg->getTime());
+        int position = indexByTime.rank(tItr);
+        return createIndex(position, 0, msg);
+    } else {
+        return QModelIndex();
+    }
+}
+
diff --git a/ui/models/messagefeed.h b/ui/models/messagefeed.h
index 9a58c45..e8995d5 100644
--- a/ui/models/messagefeed.h
+++ b/ui/models/messagefeed.h
@@ -59,14 +59,17 @@ public:
     void uploadAttachment(const QString& messageId);
     
     unsigned int unreadMessagesCount() const;
+    void fileProgress(const QString& messageId, qreal value);
     
 signals:
     void requestArchive(const QString& before);
     void requestStateChange(bool requesting);
+    void fileLocalPathRequest(const QString& messageId, const QString& url);
     
 protected:
     bool sentByMe(const Shared::Message& msg) const;
     Attachment fillAttach(const Shared::Message& msg) const;
+    QModelIndex modelIndexById(const QString& id) const;
     
 public:
     enum MessageRoles {
diff --git a/ui/models/roster.cpp b/ui/models/roster.cpp
index 95515b3..a7bc74e 100644
--- a/ui/models/roster.cpp
+++ b/ui/models/roster.cpp
@@ -27,7 +27,8 @@ Models::Roster::Roster(QObject* parent):
     root(new Item(Item::root, {{"name", "root"}})),
     accounts(),
     groups(),
-    contacts()
+    contacts(),
+    requestedFiles()
 {
     connect(accountsModel, &Accounts::dataChanged, this, &Roster::onAccountDataChanged);
     connect(root, &Item::childChanged, this, &Roster::onChildChanged);
@@ -447,6 +448,7 @@ void Models::Roster::addContact(const QString& account, const QString& jid, cons
         if (itr == contacts.end()) {
             contact = new Contact(acc, jid, data);
             connect(contact, &Contact::requestArchive, this, &Roster::onElementRequestArchive);
+            connect(contact, &Contact::fileLocalPathRequest, this, &Roster::onElementFileLocalPathRequest);
             contacts.insert(std::make_pair(id, contact));
         } else {
             contact = itr->second;
@@ -806,6 +808,7 @@ void Models::Roster::addRoom(const QString& account, const QString jid, const QM
     
     Room* room = new Room(acc, jid, data);
     connect(room, &Contact::requestArchive, this, &Roster::onElementRequestArchive);
+    connect(room, &Contact::fileLocalPathRequest, this, &Roster::onElementFileLocalPathRequest);
     rooms.insert(std::make_pair(id, room));
     acc->appendChild(room);
 }
@@ -978,3 +981,41 @@ void Models::Roster::responseArchive(const QString& account, const QString& jid,
         }
     }
 }
+
+void Models::Roster::onElementFileLocalPathRequest(const QString& messageId, const QString& url)
+{
+    Element* el = static_cast<Element*>(sender());
+    std::map<QString, std::set<Models::Roster::ElId>>::iterator itr = requestedFiles.find(messageId);
+    bool created = false;
+    if (itr == requestedFiles.end()) {
+        itr = requestedFiles.insert(std::make_pair(messageId, std::set<Models::Roster::ElId>())).first;
+        created = true;
+    }
+    itr->second.insert(Models::Roster::ElId(el->getAccountName(), el->getJid()));
+    if (created) {
+        emit fileLocalPathRequest(messageId, url);
+    }
+}
+
+void Models::Roster::fileProgress(const QString& messageId, qreal value)
+{
+    std::map<QString, std::set<Models::Roster::ElId>>::const_iterator itr = requestedFiles.find(messageId);
+    if (itr == requestedFiles.end()) {
+        qDebug() << "fileProgress in UI but there is nobody waiting for that id:" << messageId << ", skipping";
+        return;
+    } else {
+        const std::set<Models::Roster::ElId>& convs = itr->second;
+        for (const Models::Roster::ElId& id : convs) {
+            std::map<ElId, Contact*>::const_iterator cItr = contacts.find(id);
+            if (cItr != contacts.end()) {
+                cItr->second->fileProgress(messageId, value);
+            } else {
+                std::map<ElId, Room*>::const_iterator rItr = rooms.find(id);
+                if (rItr != rooms.end()) {
+                    rItr->second->fileProgress(messageId, value);
+                }
+            }
+        }
+    }
+}
+
diff --git a/ui/models/roster.h b/ui/models/roster.h
index f43d9a9..1f398d8 100644
--- a/ui/models/roster.h
+++ b/ui/models/roster.h
@@ -81,11 +81,13 @@ public:
     QModelIndex getAccountIndex(const QString& name);
     QModelIndex getGroupIndex(const QString& account, const QString& name);
     void responseArchive(const QString& account, const QString& jid, const std::list<Shared::Message>& list, bool last);
+    void fileProgress(const QString& messageId, qreal value);
     
     Accounts* accountsModel;
     
 signals:
     void requestArchive(const QString& account, const QString& jid, const QString& before);
+    void fileLocalPathRequest(const QString& messageId, const QString& url);
     
 private:
     Item* root;
@@ -93,6 +95,7 @@ private:
     std::map<ElId, Group*> groups;
     std::map<ElId, Contact*> contacts;
     std::map<ElId, Room*> rooms;
+    std::map<QString, std::set<Models::Roster::ElId>> requestedFiles;
     
 private slots:
     void onAccountDataChanged(const QModelIndex& tl, const QModelIndex& br, const QVector<int>& roles);
@@ -104,6 +107,7 @@ private slots:
     void onChildIsAboutToBeMoved(Item* source, int first, int last, Item* destination, int newIndex);
     void onChildMoved();
     void onElementRequestArchive(const QString& before);
+    void onElementFileLocalPathRequest(const QString& messageId, const QString& url);
     
 public:
     class ElId {
diff --git a/ui/squawk.cpp b/ui/squawk.cpp
index 52e1d9f..55bfe63 100644
--- a/ui/squawk.cpp
+++ b/ui/squawk.cpp
@@ -63,6 +63,7 @@ Squawk::Squawk(QWidget *parent) :
     
     connect(rosterModel.accountsModel, &Models::Accounts::sizeChanged, this, &Squawk::onAccountsSizeChanged);
     connect(&rosterModel, &Models::Roster::requestArchive, this, &Squawk::onConversationRequestArchive);
+    connect(&rosterModel, &Models::Roster::fileLocalPathRequest, this, &Squawk::fileLocalPathRequest);
     connect(contextMenu, &QMenu::aboutToHide, this, &Squawk::onContextAboutToHide);
     //m_ui->mainToolBar->addWidget(m_ui->comboBox);
     
@@ -405,23 +406,7 @@ void Squawk::onConversationDownloadFile(const QString& messageId, const QString&
 
 void Squawk::fileProgress(const QString& messageId, qreal value)
 {
-    std::map<QString, std::set<Models::Roster::ElId>>::const_iterator itr = requestedFiles.find(messageId);
-    if (itr == requestedFiles.end()) {
-        qDebug() << "fileProgress in UI Squawk but there is nobody waiting for that id" << messageId << ", skipping";
-        return;
-    } else {
-        const std::set<Models::Roster::ElId>& convs = itr->second;
-        for (std::set<Models::Roster::ElId>::const_iterator cItr = convs.begin(), cEnd = convs.end(); cItr != cEnd; ++cItr) {
-            const Models::Roster::ElId& id = *cItr;
-            Conversations::const_iterator c = conversations.find(id);
-            if (c != conversations.end()) {
-                c->second->responseFileProgress(messageId, value);
-            }
-            if (currentConversation != 0 && currentConversation->getId() == id) {
-                currentConversation->responseFileProgress(messageId, value);
-            }
-        }
-    }
+    rosterModel.fileProgress(messageId, value);
 }
 
 void Squawk::fileError(const QString& messageId, const QString& error)

From 50bb3f5fd77b94824ded06f421f7859a50ae408e Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Mon, 22 Mar 2021 21:04:26 +0300
Subject: [PATCH 095/281] started progress bars, changed gcc standard to 17

---
 CMakeLists.txt               |  2 +-
 ui/models/messagefeed.cpp    |  2 +-
 ui/squawk.cpp                |  2 +
 ui/utils/messagedelegate.cpp | 91 ++++++++++++++++++++++++++++++++++--
 ui/utils/messagedelegate.h   |  6 +++
 ui/widgets/conversation.h    |  1 -
 6 files changed, 96 insertions(+), 8 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index f02df03..7418a7a 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.4)
 project(squawk)
 
 set(CMAKE_INCLUDE_CURRENT_DIR ON)
-set(CMAKE_CXX_STANDARD 14)
+set(CMAKE_CXX_STANDARD 17)
 
 set(CMAKE_AUTOMOC ON)
 set(CMAKE_AUTOUIC ON)
diff --git a/ui/models/messagefeed.cpp b/ui/models/messagefeed.cpp
index 55451fb..01b1a9d 100644
--- a/ui/models/messagefeed.cpp
+++ b/ui/models/messagefeed.cpp
@@ -302,7 +302,7 @@ void Models::MessageFeed::downloadAttachment(const QString& messageId)
     QModelIndex ind = modelIndexById(messageId);
     if (ind.isValid()) {
         std::pair<Progress::iterator, bool> progressPair = downloads.insert(std::make_pair(messageId, 0));
-        if (!progressPair.second) {     //Only to take action if we weren't already downloading it
+        if (progressPair.second) {     //Only to take action if we weren't already downloading it
             Shared::Message* msg = static_cast<Shared::Message*>(ind.internalPointer());
             emit dataChanged(ind, ind);
             emit fileLocalPathRequest(messageId, msg->getOutOfBandUrl());
diff --git a/ui/squawk.cpp b/ui/squawk.cpp
index 55bfe63..6b8416f 100644
--- a/ui/squawk.cpp
+++ b/ui/squawk.cpp
@@ -431,6 +431,8 @@ void Squawk::fileError(const QString& messageId, const QString& error)
     }
 }
 
+
+//TODO! Need to make it look like a standard message change event!
 void Squawk::fileLocalPathResponse(const QString& messageId, const QString& path)
 {
     std::map<QString, std::set<Models::Roster::ElId>>::const_iterator itr = requestedFiles.find(messageId);
diff --git a/ui/utils/messagedelegate.cpp b/ui/utils/messagedelegate.cpp
index 038b0af..ff5b1fb 100644
--- a/ui/utils/messagedelegate.cpp
+++ b/ui/utils/messagedelegate.cpp
@@ -36,12 +36,17 @@ bodyMetrics(bodyFont),
 nickMetrics(nickFont),
 dateMetrics(dateFont),
 buttonHeight(0),
+barHeight(0),
 buttons(new std::map<QString, FeedButton*>()),
+bars(new std::map<QString, QProgressBar*>()),
 idsToKeep(new std::set<QString>()),
 clearingWidgets(false)
 {
     QPushButton btn;
     buttonHeight = btn.sizeHint().height();
+    
+    QProgressBar bar;
+    barHeight = bar.sizeHint().height();
 }
 
 MessageDelegate::~MessageDelegate()
@@ -50,8 +55,13 @@ MessageDelegate::~MessageDelegate()
         delete pair.second;
     }
     
+    for (const std::pair<const QString, QProgressBar*>& pair: *bars){
+        delete pair.second;
+    }
+    
     delete idsToKeep;
     delete buttons;
+    delete bars;
 }
 
 void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
@@ -110,9 +120,11 @@ void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio
     painter->save();
     switch (data.attach.state) {
         case Models::none:
-            break;
+            clearHelperWidget(data);        //i can't imagine the situation where it's gonna be needed
+            break;                          //but it's a possible performance problem
         case Models::uploading:
         case Models::downloading:
+            paintBar(getBar(data), painter, data.sentByMe, opt);
             break;
         case Models::remote:
         case Models::local:
@@ -159,6 +171,7 @@ QSize MessageDelegate::sizeHint(const QStyleOptionViewItem& option, const QModel
             break;
         case Models::uploading:
         case Models::downloading:
+            messageSize.rheight() += barHeight;
             break;
         case Models::remote:
         case Models::local:
@@ -225,6 +238,18 @@ void MessageDelegate::paintButton(QPushButton* btn, QPainter* painter, bool sent
     option.rect.adjust(0, buttonHeight, 0, 0);
 }
 
+void MessageDelegate::paintBar(QProgressBar* bar, QPainter* painter, bool sentByMe, QStyleOptionViewItem& option) const
+{
+    QPoint start = option.rect.topLeft();
+    
+    QWidget* vp = static_cast<QWidget*>(painter->device());
+    bar->setParent(vp);
+    bar->move(start);
+    bar->resize(option.rect.width(), barHeight);
+    bar->show();
+    
+    option.rect.adjust(0, barHeight, 0, 0);
+}
 
 QPushButton * MessageDelegate::getButton(const Models::FeedItem& data) const
 {
@@ -240,6 +265,12 @@ QPushButton * MessageDelegate::getButton(const Models::FeedItem& data) const
             delete itr->second;
             buttons->erase(itr);
         }
+    } else {
+        std::map<QString, QProgressBar*>::const_iterator barItr = bars->find(data.id);
+        if (barItr != bars->end()) {
+            delete barItr->second;
+            bars->erase(barItr);
+        }
     }
     
     if (result == 0) {
@@ -259,6 +290,30 @@ QPushButton * MessageDelegate::getButton(const Models::FeedItem& data) const
     return result;
 }
 
+QProgressBar * MessageDelegate::getBar(const Models::FeedItem& data) const
+{
+    std::map<QString, QProgressBar*>::const_iterator barItr = bars->find(data.id);
+    QProgressBar* result = 0;
+    if (barItr != bars->end()) {
+        result = barItr->second;
+    } else {
+        std::map<QString, FeedButton*>::const_iterator itr = buttons->find(data.id);
+        if (itr != buttons->end()) {
+            delete itr->second;
+            buttons->erase(itr);
+        }
+    }
+    
+    if (result == 0) {
+        result = new QProgressBar();
+        bars->insert(std::make_pair(data.id, result));
+    }
+    
+    result->setValue(data.attach.progress);
+    
+    return result;
+}
+
 
 void MessageDelegate::beginClearWidgets()
 {
@@ -269,17 +324,27 @@ void MessageDelegate::beginClearWidgets()
 void MessageDelegate::endClearWidgets()
 {
     if (clearingWidgets) {
-        std::set<QString> toRemove;
-        for (const std::pair<const QString, FeedButton*>& pair: *buttons){
+        std::set<QString> toRemoveButtons;
+        std::set<QString> toRemoveBars;
+        for (const std::pair<const QString, FeedButton*>& pair: *buttons) {
             if (idsToKeep->find(pair.first) == idsToKeep->end()) {
                 delete pair.second;
-                toRemove.insert(pair.first);
+                toRemoveButtons.insert(pair.first);
+            }
+        }
+        for (const std::pair<const QString, QProgressBar*>& pair: *bars) {
+            if (idsToKeep->find(pair.first) == idsToKeep->end()) {
+                delete pair.second;
+                toRemoveBars.insert(pair.first);
             }
         }
         
-        for (const QString& key : toRemove) {
+        for (const QString& key : toRemoveButtons) {
             buttons->erase(key);
         }
+        for (const QString& key : toRemoveBars) {
+            bars->erase(key);
+        }
         
         idsToKeep->clear();
         clearingWidgets = false;
@@ -292,6 +357,22 @@ void MessageDelegate::onButtonPushed() const
     emit buttonPushed(btn->messageId, btn->download);
 }
 
+void MessageDelegate::clearHelperWidget(const Models::FeedItem& data) const
+{
+    std::map<QString, FeedButton*>::const_iterator itr = buttons->find(data.id);
+    if (itr != buttons->end()) {
+        delete itr->second;
+        buttons->erase(itr);
+    } else {
+        std::map<QString, QProgressBar*>::const_iterator barItr = bars->find(data.id);
+        if (barItr != bars->end()) {
+            delete barItr->second;
+            bars->erase(barItr);
+        }
+    }
+}
+
+
 // void MessageDelegate::setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const
 // {
 //     
diff --git a/ui/utils/messagedelegate.h b/ui/utils/messagedelegate.h
index 69ffb84..42c8ed5 100644
--- a/ui/utils/messagedelegate.h
+++ b/ui/utils/messagedelegate.h
@@ -27,6 +27,7 @@
 #include <QFont>
 #include <QFontMetrics>
 #include <QPushButton>
+#include <QProgressBar>
 
 #include "shared/icons.h"
 
@@ -55,7 +56,10 @@ signals:
     
 protected:
     void paintButton(QPushButton* btn, QPainter* painter, bool sentByMe, QStyleOptionViewItem& option) const;
+    void paintBar(QProgressBar* bar, QPainter* painter, bool sentByMe, QStyleOptionViewItem& option) const;
     QPushButton* getButton(const Models::FeedItem& data) const;
+    QProgressBar* getBar(const Models::FeedItem& data) const;
+    void clearHelperWidget(const Models::FeedItem& data) const;
     
 protected slots:
     void onButtonPushed() const;
@@ -75,8 +79,10 @@ private:
     QFontMetrics dateMetrics;
     
     int buttonHeight;
+    int barHeight;
     
     std::map<QString, FeedButton*>* buttons;
+    std::map<QString, QProgressBar*>* bars;
     std::set<QString>* idsToKeep;
     bool clearingWidgets;
 };
diff --git a/ui/widgets/conversation.h b/ui/widgets/conversation.h
index ac1ffcd..7d10aff 100644
--- a/ui/widgets/conversation.h
+++ b/ui/widgets/conversation.h
@@ -99,7 +99,6 @@ protected slots:
     void onEnterPressed();
     void onAttach();
     void onFileSelected();
-    void onScrollResize();
     void onBadgeClose();
     void onClearButton();
     void onTextEditDocSizeChanged(const QSizeF& size);

From 8f914c02a7692ba5839ab906a6a1ab353e481143 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Tue, 13 Apr 2021 16:27:31 +0300
Subject: [PATCH 096/281] temp url storage commit

---
 core/CMakeLists.txt  |   2 +-
 core/networkaccess.h |   4 +-
 core/squawk.cpp      |   9 +-
 core/squawk.h        |   3 +-
 core/urlstorage.cpp  | 435 +++++++++++++++++++++++++++++++++++++++++++
 core/urlstorage.h    | 102 ++++++++++
 6 files changed, 549 insertions(+), 6 deletions(-)
 create mode 100644 core/urlstorage.cpp
 create mode 100644 core/urlstorage.h

diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt
index b74a055..2e832e2 100644
--- a/core/CMakeLists.txt
+++ b/core/CMakeLists.txt
@@ -15,7 +15,7 @@ set(squawkCORE_SRC
     rosteritem.cpp
     contact.cpp
     conference.cpp
-    storage.cpp
+    urlstorage.cpp
     networkaccess.cpp
     adapterFuctions.cpp
     handlers/messagehandler.cpp
diff --git a/core/networkaccess.h b/core/networkaccess.h
index 824b1af..c931393 100644
--- a/core/networkaccess.h
+++ b/core/networkaccess.h
@@ -29,7 +29,7 @@
 
 #include <set>
 
-#include "storage.h"
+#include "urlstorage.h"
 
 namespace Core {
 
@@ -80,7 +80,7 @@ private slots:
 private:
     bool running;
     QNetworkAccessManager* manager;
-    Storage files;
+    UrlStorage storage;
     std::map<QString, Transfer*> downloads;
     std::map<QString, Transfer*> uploads;
     
diff --git a/core/squawk.cpp b/core/squawk.cpp
index c637c96..a64d418 100644
--- a/core/squawk.cpp
+++ b/core/squawk.cpp
@@ -32,7 +32,7 @@ Core::Squawk::Squawk(QObject* parent):
     ,kwallet()
 #endif
 {
-    connect(&network, &NetworkAccess::fileLocalPathResponse, this, &Squawk::fileLocalPathResponse);
+    connect(&network, &NetworkAccess::fileLocalPathResponse, this, &Squawk::onNetworkAccessfileLocalPathResponse);
     connect(&network, &NetworkAccess::downloadFileProgress, this, &Squawk::downloadFileProgress);
     connect(&network, &NetworkAccess::downloadFileError, this, &Squawk::downloadFileError);
     connect(&network, &NetworkAccess::uploadFileProgress, this, &Squawk::uploadFileProgress);
@@ -677,7 +677,7 @@ void Core::Squawk::readSettings()
             settings.value("login").toString(), 
             settings.value("server").toString(), 
             settings.value("password", "").toString(), 
-            settings.value("name").toString(), 
+            settings.value("name").toString(),
             settings.value("resource").toString(),
             Shared::Global::fromInt<Shared::AccountPassword>(settings.value("passwordType", static_cast<int>(Shared::AccountPassword::plain)).toInt())
         );
@@ -751,3 +751,8 @@ void Core::Squawk::onWalletResponsePassword(const QString& login, const QString&
     emit changeAccount(login, {{"password", password}});
     accountReady();
 }
+
+void Core::Squawk::onNetworkAccessfileLocalPathResponse(const QString& messageId, const QString& path)
+{
+    
+}
diff --git a/core/squawk.h b/core/squawk.h
index 3aac06a..4b6fbea 100644
--- a/core/squawk.h
+++ b/core/squawk.h
@@ -71,7 +71,6 @@ signals:
     void addRoomParticipant(const QString& account, const QString& jid, const QString& name, const QMap<QString, QVariant>& data);
     void changeRoomParticipant(const QString& account, const QString& jid, const QString& name, const QMap<QString, QVariant>& data);
     void removeRoomParticipant(const QString& account, const QString& jid, const QString& name);
-    void fileLocalPathResponse(const QString& messageId, const QString& path);
     void downloadFileError(const QString& messageId, const QString& error);
     void downloadFileProgress(const QString& messageId, qreal value);
     void uploadFileError(const QString& messageId, const QString& error);
@@ -158,6 +157,8 @@ private slots:
     void onWalletResponsePassword(const QString& login, const QString& password);
     void onWalletRejectPassword(const QString& login);
     
+    void onNetworkAccessfileLocalPathResponse(const QString& messageId, const QString& path);
+    
 private:
     void readSettings();
     void accountReady();
diff --git a/core/urlstorage.cpp b/core/urlstorage.cpp
new file mode 100644
index 0000000..a8a9179
--- /dev/null
+++ b/core/urlstorage.cpp
@@ -0,0 +1,435 @@
+/*
+ * Squawk messenger. 
+ * Copyright (C) 2019 Yury Gubich <blue@macaw.me>
+ * 
+ * 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 <QStandardPaths>
+#include <QDir>
+#include <QDebug>
+
+#include "urlstorage.h"
+
+Core::UrlStorage::UrlStorage(const QString& p_name):
+    name(p_name),
+    opened(false),
+    environment(),
+    base(),
+    map()
+{
+}
+
+Core::UrlStorage::~UrlStorage()
+{
+    close();
+}
+
+void Core::UrlStorage::open()
+{
+    if (!opened) {
+        mdb_env_create(&environment);
+        QString path(QStandardPaths::writableLocation(QStandardPaths::CacheLocation));
+        path += "/" + name;
+        QDir cache(path);
+        
+        if (!cache.exists()) {
+            bool res = cache.mkpath(path);
+            if (!res) {
+                throw Archive::Directory(path.toStdString());
+            }
+        }
+        
+        mdb_env_set_maxdbs(environment, 2);
+        mdb_env_set_mapsize(environment, 10UL * 1024UL * 1024UL);
+        mdb_env_open(environment, path.toStdString().c_str(), 0, 0664);
+        
+        MDB_txn *txn;
+        mdb_txn_begin(environment, NULL, 0, &txn);
+        mdb_dbi_open(txn, "base", MDB_CREATE, &base);
+        mdb_dbi_open(txn, "map", MDB_CREATE, &map);
+        mdb_txn_commit(txn);
+        opened = true;
+    }
+}
+
+void Core::UrlStorage::close()
+{
+    if (opened) {
+        mdb_dbi_close(environment, map);
+        mdb_dbi_close(environment, base);
+        mdb_env_close(environment);
+        opened = false;
+    }
+}
+
+void Core::UrlStorage::writeInfo(const QString& key, const Core::UrlStorage::UrlInfo& info, bool overwrite)
+{
+    MDB_txn *txn;
+    mdb_txn_begin(environment, NULL, 0, &txn);
+    
+    try {
+        writeInfo(key, info, txn, overwrite);
+        mdb_txn_commit(txn);
+    } catch (...) {
+        mdb_txn_abort(txn);
+        throw;
+    }
+}
+
+void Core::UrlStorage::writeInfo(const QString& key, const Core::UrlStorage::UrlInfo& info, MDB_txn* txn, bool overwrite)
+{
+    QByteArray ba;
+    QDataStream ds(&ba, QIODevice::WriteOnly);
+    info.serialize(ds);
+    
+    const std::string& id = key.toStdString();
+    MDB_val lmdbKey, lmdbData;
+    lmdbKey.mv_size = id.size();
+    lmdbKey.mv_data = (char*)id.c_str();
+    lmdbData.mv_size = ba.size();
+    lmdbData.mv_data = (uint8_t*)ba.data();
+    
+    int rc;
+    rc = mdb_put(txn, base, &lmdbKey, &lmdbData, overwrite ? 0 : MDB_NOOVERWRITE);
+    
+    if (rc != 0) {
+        if (rc == MDB_KEYEXIST) {
+            if (!overwrite) {
+                throw Archive::Exist(name.toStdString(), id);
+            }
+        } else {
+            throw Archive::Unknown(name.toStdString(), mdb_strerror(rc));
+        }
+    }
+    
+    if (info.hasPath()) {
+        std::string sp = info.getPath().toStdString();
+        lmdbData.mv_size = sp.size();
+        lmdbData.mv_data = (char*)sp.c_str();
+        rc = mdb_put(txn, map, &lmdbData, &lmdbKey, 0);
+        if (rc != 0) {
+            throw Archive::Unknown(name.toStdString(), mdb_strerror(rc));
+        }
+    }
+}
+
+void Core::UrlStorage::readInfo(const QString& key, Core::UrlStorage::UrlInfo& info, MDB_txn* txn)
+{
+    const std::string& id = key.toStdString();
+    MDB_val lmdbKey, lmdbData;
+    lmdbKey.mv_size = id.size();
+    lmdbKey.mv_data = (char*)id.c_str();
+    int rc = mdb_get(txn, base, &lmdbKey, &lmdbData);
+    
+    if (rc == 0) {
+        QByteArray ba((char*)lmdbData.mv_data, lmdbData.mv_size);
+        QDataStream ds(&ba, QIODevice::ReadOnly);
+        
+        info.deserialize(ds);
+    } else if (rc == MDB_NOTFOUND) {
+        throw Archive::NotFound(id, name.toStdString());
+    } else {
+        throw Archive::Unknown(name.toStdString(), mdb_strerror(rc));
+    }
+}
+
+void Core::UrlStorage::readInfo(const QString& key, Core::UrlStorage::UrlInfo& info)
+{
+    MDB_txn *txn;
+    mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn);
+    
+    try {
+        readInfo(key, info, txn);
+        mdb_txn_commit(txn);
+    } catch (...) {
+        mdb_txn_abort(txn);
+        throw;
+    }
+}
+
+void Core::UrlStorage::addFile(const QString& url)
+{
+    if (!opened) {
+        throw Archive::Closed("addFile(no message, no path)", name.toStdString());
+    }
+    
+    UrlInfo info;
+    writeInfo(url, info);
+}
+
+void Core::UrlStorage::addFile(const QString& url, const QString& path)
+{
+    if (!opened) {
+        throw Archive::Closed("addFile(no message, with path)", name.toStdString());
+    }
+    
+    UrlInfo info(path);
+    writeInfo(url, info);
+}
+
+void Core::UrlStorage::addFile(const QString& url, const QString& account, const QString& jid, const QString& id)
+{
+    if (!opened) {
+        throw Archive::Closed("addFile(with message, no path)", name.toStdString());
+    }
+    
+    UrlInfo info;
+    info.addMessage(account, jid, id);
+    writeInfo(url, info);
+}
+
+void Core::UrlStorage::addFile(const QString& url, const QString& path, const QString& account, const QString& jid, const QString& id)
+{
+    if (!opened) {
+        throw Archive::Closed("addFile(with message, with path)", name.toStdString());
+    }
+    
+    UrlInfo info(path);
+    info.addMessage(account, jid, id);
+    writeInfo(url, info);
+}
+
+QString Core::UrlStorage::addMessageAndCheckForPath(const QString& url, const QString& account, const QString& jid, const QString& id)
+{
+    QString path;
+    
+    MDB_txn *txn;
+    mdb_txn_begin(environment, NULL, 0, &txn);
+    UrlInfo info;
+    
+    try {
+        readInfo(url, info, txn);
+        path = info.getPath();
+        info.addMessage(account, jid, id);
+        try {
+            writeInfo(url, info, txn, true);
+            mdb_txn_commit(txn);
+        } catch (...) {
+            mdb_txn_abort(txn);
+            throw;
+        }
+    } catch (const Archive::NotFound& e) {
+        info.addMessage(account, jid, id);
+        try {
+            writeInfo(url, info, txn, true);
+            mdb_txn_commit(txn);
+        } catch (...) {
+            mdb_txn_abort(txn);
+            throw;
+        }
+    } catch (...) {
+        mdb_txn_abort(txn);
+        throw;
+    }
+    
+    return path;
+}
+
+std::list<Core::UrlStorage::MessageInfo> Core::UrlStorage::setPath(const QString& url, const QString& path)
+{
+    std::list<MessageInfo> list;
+    
+    MDB_txn *txn;
+    mdb_txn_begin(environment, NULL, 0, &txn);
+    UrlInfo info;
+    
+    try {
+        readInfo(url, info, txn);
+        info.setPath(path);
+        info.getMessages(list);
+        try {
+            writeInfo(url, info, txn, true);
+            mdb_txn_commit(txn);
+        } catch (...) {
+            mdb_txn_abort(txn);
+            throw;
+        }
+    } catch (const Archive::NotFound& e) {
+        info.setPath(path);
+        try {
+            writeInfo(url, info, txn, true);
+            mdb_txn_commit(txn);
+        } catch (...) {
+            mdb_txn_abort(txn);
+            throw;
+        }
+    } catch (...) {
+        mdb_txn_abort(txn);
+        throw;
+    }
+    
+    return list;
+}
+
+std::list<Core::UrlStorage::MessageInfo> Core::UrlStorage::removeFile(const QString& url)
+{
+    std::list<MessageInfo> list;
+    
+    MDB_txn *txn;
+    mdb_txn_begin(environment, NULL, 0, &txn);
+    UrlInfo info;
+    
+    try {
+        std::string id = url.toStdString();
+        readInfo(url, info, txn);
+        info.getMessages(list);
+        
+        MDB_val lmdbKey;
+        lmdbKey.mv_size = id.size();
+        lmdbKey.mv_data = (char*)id.c_str();
+        int rc = mdb_del(txn, base, &lmdbKey, NULL);
+        if (rc != 0) {
+            throw Archive::Unknown(name.toStdString(), mdb_strerror(rc));
+        }
+        
+        if (info.hasPath()) {
+            std::string path = info.getPath().toStdString();
+            lmdbKey.mv_size = path.size();
+            lmdbKey.mv_data = (char*)path.c_str();
+            
+            int rc = mdb_del(txn, map, &lmdbKey, NULL);
+            if (rc != 0) {
+                throw Archive::Unknown(name.toStdString(), mdb_strerror(rc));
+            }
+        }
+        mdb_txn_commit(txn);
+    } catch (...) {
+        mdb_txn_abort(txn);
+        throw;
+    }
+    
+    return list;
+}
+
+std::list<Core::UrlStorage::MessageInfo> Core::UrlStorage::deletedFile(const QString& path)
+{
+    std::list<MessageInfo> list;
+    
+    MDB_txn *txn;
+    mdb_txn_begin(environment, NULL, 0, &txn);
+    
+    try {
+        std::string spath = path.toStdString();
+        
+        MDB_val lmdbKey, lmdbData;
+        lmdbKey.mv_size = spath.size();
+        lmdbKey.mv_data = (char*)spath.c_str();
+        
+        QString url;
+        int rc = mdb_get(txn, map, &lmdbKey, &lmdbData);
+        
+        if (rc == 0) {
+            std::string surl((char*)lmdbData.mv_data, lmdbData.mv_size);
+            url = QString(surl.c_str());
+        } else if (rc == MDB_NOTFOUND) {
+            qDebug() << "Have been asked to remove file" << path << ", which isn't in the database, skipping";
+            return list;
+        } else {
+            throw Archive::Unknown(name.toStdString(), mdb_strerror(rc));
+        }
+        
+        UrlInfo info;
+        std::string id = url.toStdString();
+        readInfo(url, info, txn);
+        info.getMessages(list);
+        info.setPath(QString());
+        writeInfo(url, info, txn, true);
+        
+        rc = mdb_del(txn, map, &lmdbKey, NULL);
+        if (rc != 0) {
+            throw Archive::Unknown(name.toStdString(), mdb_strerror(rc));
+        }
+        
+        mdb_txn_commit(txn);
+    } catch (...) {
+        mdb_txn_abort(txn);
+        throw;
+    }
+    
+    return list;
+}
+
+
+Core::UrlStorage::UrlInfo::UrlInfo():
+    localPath(),
+    messages() {}
+
+Core::UrlStorage::UrlInfo::UrlInfo(const QString& path):
+    localPath(path),
+    messages() {}
+ 
+Core::UrlStorage::UrlInfo::~UrlInfo() {}
+ 
+void Core::UrlStorage::UrlInfo::addMessage(const QString& acc, const QString& jid, const QString& id)
+{
+    messages.emplace_back(acc, jid, id);
+}
+
+void Core::UrlStorage::UrlInfo::serialize(QDataStream& data) const
+{
+    data << localPath;
+    std::list<MessageInfo>::size_type size = messages.size();
+    data << quint32(size);
+    for (const MessageInfo& info : messages) {
+        data << info.account;
+        data << info.jid;
+        data << info.messageId;
+    }
+}
+
+void Core::UrlStorage::UrlInfo::deserialize(QDataStream& data)
+{
+    data >> localPath;
+    quint32 size;
+    data >> size;
+    for (quint32 i = 0; i < size; ++i) {
+        messages.emplace_back();
+        MessageInfo& info = messages.back();
+        data >> info.account;
+        data >> info.jid;
+        data >> info.messageId;
+    }
+}
+
+void Core::UrlStorage::UrlInfo::getMessages(std::list<MessageInfo>& container) const
+{
+    std::copy(messages.begin(), messages.end(), container.end());
+}
+
+QString Core::UrlStorage::UrlInfo::getPath() const
+{
+    return localPath;
+}
+
+bool Core::UrlStorage::UrlInfo::hasPath() const
+{
+    return localPath.size() > 0;
+}
+
+
+void Core::UrlStorage::UrlInfo::setPath(const QString& path)
+{
+    localPath = path;
+}
+
+Core::UrlStorage::MessageInfo::MessageInfo():
+    account(),
+    jid(),
+    messageId() {}
+
+Core::UrlStorage::MessageInfo::MessageInfo(const QString& acc, const QString& j, const QString& id):
+    account(acc),
+    jid(j),
+    messageId(id) {}
diff --git a/core/urlstorage.h b/core/urlstorage.h
new file mode 100644
index 0000000..17adfdd
--- /dev/null
+++ b/core/urlstorage.h
@@ -0,0 +1,102 @@
+/*
+ * Squawk messenger. 
+ * Copyright (C) 2019 Yury Gubich <blue@macaw.me>
+ * 
+ * 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/>.
+ */
+
+#ifndef CORE_URLSTORAGE_H
+#define CORE_URLSTORAGE_H
+
+#include <QString>
+#include <QDataStream>
+#include <lmdb.h>
+#include <list>
+
+#include "archive.h"
+
+namespace Core {
+
+/**
+ * @todo write docs
+ */
+class UrlStorage
+{
+    class UrlInfo;
+public:
+    struct MessageInfo {
+        MessageInfo();
+        MessageInfo(const QString& acc, const QString& j, const QString& id);
+        
+        QString account;
+        QString jid;
+        QString messageId;
+    };
+    
+    UrlStorage(const QString& name);
+    ~UrlStorage();
+    
+    void open();
+    void close();
+    
+    void addFile(const QString& url);
+    void addFile(const QString& url, const QString& path);
+    void addFile(const QString& url, const QString& account, const QString& jid, const QString& id);
+    void addFile(const QString& url, const QString& path, const QString& account, const QString& jid, const QString& id);
+    std::list<MessageInfo> removeFile(const QString& url);      //removes entry like it never was in the database, returns affected message infos
+    std::list<MessageInfo> deletedFile(const QString& path);    //empties the localPath of the entry, returns affected message infos
+    std::list<MessageInfo> setPath(const QString& url, const QString& path);
+    QString addMessageAndCheckForPath(const QString& url, const QString& account, const QString& jid, const QString& id);
+    
+private:
+    QString name;
+    bool opened;
+    MDB_env* environment;
+    MDB_dbi base;
+    MDB_dbi map;
+    
+private:
+    void writeInfo(const QString& key, const UrlInfo& info, bool overwrite = false);
+    void writeInfo(const QString& key, const UrlInfo& info, MDB_txn* txn, bool overwrite = false);
+    void readInfo(const QString& key, UrlInfo& info);
+    void readInfo(const QString& key, UrlInfo& info, MDB_txn* txn);
+    
+private:
+    class UrlInfo {
+    public:
+        UrlInfo(const QString& path);
+        UrlInfo();
+        ~UrlInfo();
+        
+        void serialize(QDataStream& data) const;
+        void deserialize(QDataStream& data);
+        
+        QString getPath() const;
+        bool hasPath() const;
+        void setPath(const QString& path);
+        
+        void addMessage(const QString& acc, const QString& jid, const QString& id);
+        void getMessages(std::list<MessageInfo>& container) const;
+        
+    private:
+        QString localPath;
+        std::list<MessageInfo> messages;
+    };
+    
+    
+};
+
+}
+
+#endif // CORE_URLSTORAGE_H

From 3a7735b19200552b219bc02f6f998e5f3702a8df Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Sun, 18 Apr 2021 15:49:20 +0300
Subject: [PATCH 097/281] First steps on the new idea of file up/downloading

---
 CMakeLists.txt                   |   2 +
 core/account.cpp                 |  12 +-
 core/account.h                   |   2 +-
 core/handlers/messagehandler.cpp |  78 ++++++--
 core/handlers/messagehandler.h   |   7 +-
 core/networkaccess.cpp           | 334 +++++++++++++++----------------
 core/networkaccess.h             |  30 +--
 core/squawk.cpp                  |  25 +--
 core/squawk.h                    |  31 ++-
 core/urlstorage.cpp              | 191 +++++++++++-------
 core/urlstorage.h                |  27 ++-
 main.cpp                         |  15 +-
 shared.h                         |   1 +
 shared/messageinfo.cpp           |  45 +++++
 shared/messageinfo.h             |  43 ++++
 ui/models/element.cpp            |  15 +-
 ui/models/element.h              |   6 +-
 ui/models/messagefeed.cpp        |  26 ++-
 ui/models/messagefeed.h          |   8 +-
 ui/models/roster.cpp             | 136 ++++++-------
 ui/models/roster.h               |  23 ++-
 ui/squawk.cpp                    | 103 ++--------
 ui/squawk.h                      |  15 +-
 23 files changed, 650 insertions(+), 525 deletions(-)
 create mode 100644 shared/messageinfo.cpp
 create mode 100644 shared/messageinfo.h

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 7418a7a..0db5693 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -33,6 +33,7 @@ set(squawk_SRC
   shared/message.cpp
   shared/vcard.cpp
   shared/icons.cpp
+  shared/messageinfo.cpp
 )
 
 set(squawk_HEAD
@@ -45,6 +46,7 @@ set(squawk_HEAD
   shared/utils.h
   shared/vcard.h
   shared/icons.h
+  shared/messageinfo.h
 )
 
 configure_file(resources/images/logo.svg squawk.svg COPYONLY)
diff --git a/core/account.cpp b/core/account.cpp
index 8452688..da7f25c 100644
--- a/core/account.cpp
+++ b/core/account.cpp
@@ -84,8 +84,9 @@ Account::Account(const QString& p_login, const QString& p_server, const QString&
     QObject::connect(dm, &QXmppDiscoveryManager::itemsReceived, this, &Account::onDiscoveryItemsReceived);
     QObject::connect(dm, &QXmppDiscoveryManager::infoReceived, this, &Account::onDiscoveryInfoReceived);
     
-    QObject::connect(network, &NetworkAccess::uploadFileComplete, mh, &MessageHandler::onFileUploaded);
-    QObject::connect(network, &NetworkAccess::uploadFileError, mh, &MessageHandler::onFileUploadError);
+    QObject::connect(network, &NetworkAccess::uploadFileComplete, mh, &MessageHandler::onUploadFileComplete);
+    QObject::connect(network, &NetworkAccess::downloadFileComplete, mh, &MessageHandler::onDownloadFileComplete);
+    QObject::connect(network, &NetworkAccess::loadFileError, mh, &MessageHandler::onLoadFileError);
     
     client.addExtension(rcpm);
     QObject::connect(rcpm, &QXmppMessageReceiptManager::messageDelivered, mh, &MessageHandler::onReceiptReceived);
@@ -155,8 +156,9 @@ Account::~Account()
         reconnectTimer->stop();
     }
     
-    QObject::disconnect(network, &NetworkAccess::uploadFileComplete, mh, &MessageHandler::onFileUploaded);
-    QObject::disconnect(network, &NetworkAccess::uploadFileError, mh, &MessageHandler::onFileUploadError);
+    QObject::disconnect(network, &NetworkAccess::uploadFileComplete, mh, &MessageHandler::onUploadFileComplete);
+    QObject::disconnect(network, &NetworkAccess::downloadFileComplete, mh, &MessageHandler::onDownloadFileComplete);
+    QObject::disconnect(network, &NetworkAccess::loadFileError, mh, &MessageHandler::onLoadFileError);
     
     delete mh;
     delete rh;
@@ -549,9 +551,11 @@ void Core::Account::onClientError(QXmppClient::Error err)
                 case QXmppStanza::Error::NotAuthorized:
                     errorText = "Authentication error";
                     break;
+#if (QXMPP_VERSION) < QT_VERSION_CHECK(1, 3, 0)
                 case QXmppStanza::Error::PaymentRequired:
                     errorText = "Payment is required";
                     break;
+#endif
                 case QXmppStanza::Error::RecipientUnavailable:
                     errorText = "Recipient is unavailable";
                     break;
diff --git a/core/account.h b/core/account.h
index 1a46e24..8b0839b 100644
--- a/core/account.h
+++ b/core/account.h
@@ -133,7 +133,7 @@ signals:
     void removeRoomParticipant(const QString& jid, const QString& nickName);
     void receivedVCard(const QString& jid, const Shared::VCard& card);
     void uploadFile(const QFileInfo& file, const QUrl& set, const QUrl& get, QMap<QString, QString> headers);
-    void uploadFileError(const QString& messageId, const QString& error);
+    void uploadFileError(const QString& jid, const QString& messageId, const QString& error);
     
 private:
     QString name;
diff --git a/core/handlers/messagehandler.cpp b/core/handlers/messagehandler.cpp
index a236f1e..51282eb 100644
--- a/core/handlers/messagehandler.cpp
+++ b/core/handlers/messagehandler.cpp
@@ -168,14 +168,16 @@ void Core::MessageHandler::initializeMessage(Shared::Message& target, const QXmp
     id = source.id();
 #endif
     target.setId(id);
-    if (target.getId().size() == 0) {
+    QString messageId = target.getId();
+    if (messageId.size() == 0) {
         target.generateRandomId();          //TODO out of desperation, I need at least a random ID
+        messageId = target.getId();
     }
     target.setFrom(source.from());
     target.setTo(source.to());
     target.setBody(source.body());
     target.setForwarded(forwarded);
-    target.setOutOfBandUrl(source.outOfBandUrl());
+    
     if (guessing) {
         if (target.getFromJid() == acc->getLogin() + "@" + acc->getServer()) {
             outgoing = true;
@@ -189,6 +191,12 @@ void Core::MessageHandler::initializeMessage(Shared::Message& target, const QXmp
     } else {
         target.setCurrentTime();
     }
+    
+    QString oob = source.outOfBandUrl();
+    if (oob.size() > 0) {
+        target.setAttachPath(acc->network->addMessageAndCheckForPath(oob, acc->getName(), target.getPenPalJid(), messageId));
+    }
+    target.setOutOfBandUrl(oob);
 }
 
 void Core::MessageHandler::logMessage(const QXmppMessage& msg, const QString& reason)
@@ -292,7 +300,7 @@ void Core::MessageHandler::prepareUpload(const Shared::Message& data)
         if (url.size() != 0) {
             sendMessageWithLocalUploadedFile(data, url);
         } else {
-            if (acc->network->isUploading(path, data.getId())) {
+            if (acc->network->checkAndAddToUploading(acc->getName(), data.getPenPalJid(), data.getId(), path)) {
                 pendingMessages.emplace(data.getId(), data);
             } else {
                 if (acc->um->serviceFound()) {
@@ -303,17 +311,17 @@ void Core::MessageHandler::prepareUpload(const Shared::Message& data)
                             acc->um->requestUploadSlot(file);
                         }
                     } else {
-                        onFileUploadError(data.getId(), "Uploading file no longer exists or your system user has no permission to read it");
+                        handleUploadError(data.getPenPalJid(), data.getId(), "Uploading file no longer exists or your system user has no permission to read it");
                         qDebug() << "Requested upload slot in account" << acc->name << "for file" << path << "but the file doesn't exist or is not readable";
                     }
                 } else {
-                    onFileUploadError(data.getId(), "Your server doesn't support file upload service, or it's prohibited for your account");
+                    handleUploadError(data.getPenPalJid(), data.getId(), "Your server doesn't support file upload service, or it's prohibited for your account");
                     qDebug() << "Requested upload slot in account" << acc->name << "for file" << path << "but upload manager didn't discover any upload services";
                 }
             }
         }
     } else {
-        onFileUploadError(data.getId(), "Account is offline or reconnecting");
+        handleUploadError(data.getPenPalJid(), data.getId(), "Account is offline or reconnecting");
         qDebug() << "An attempt to send message with not connected account " << acc->name << ", skipping";
     }
 }
@@ -326,10 +334,10 @@ void Core::MessageHandler::onUploadSlotReceived(const QXmppHttpUploadSlotIq& slo
     } else {
         const std::pair<QString, Shared::Message>& pair = uploadingSlotsQueue.front();
         const QString& mId = pair.second.getId();
-        acc->network->uploadFile(mId, pair.first, slot.putUrl(), slot.getUrl(), slot.putHeaders());
+        acc->network->uploadFile({acc->name, pair.second.getPenPalJid(), mId}, pair.first, slot.putUrl(), slot.getUrl(), slot.putHeaders());
         pendingMessages.emplace(mId, pair.second);
-        uploadingSlotsQueue.pop_front();
         
+        uploadingSlotsQueue.pop_front();
         if (uploadingSlotsQueue.size() > 0) {
             acc->um->requestUploadSlot(uploadingSlotsQueue.front().first);
         }
@@ -338,35 +346,69 @@ void Core::MessageHandler::onUploadSlotReceived(const QXmppHttpUploadSlotIq& slo
 
 void Core::MessageHandler::onUploadSlotRequestFailed(const QXmppHttpUploadRequestIq& request)
 {
+    QString err(request.error().text());
     if (uploadingSlotsQueue.size() == 0) {
         qDebug() << "HTTP Upload manager of account" << acc->name << "reports about an error requesting upload slot, but none was requested";
-        qDebug() << request.error().text();
+        qDebug() << err;
     } else {
         const std::pair<QString, Shared::Message>& pair = uploadingSlotsQueue.front();
-        qDebug() << "Error requesting upload slot for file" << pair.first << "in account" << acc->name << ":" << request.error().text();
-        emit acc->uploadFileError(pair.second.getId(), "Error requesting slot to upload file: " + request.error().text());
+        qDebug() << "Error requesting upload slot for file" << pair.first << "in account" << acc->name << ":" << err;
+        emit acc->uploadFileError(pair.second.getPenPalJid(), pair.second.getId(), "Error requesting slot to upload file: " + err);
         
+        uploadingSlotsQueue.pop_front();
         if (uploadingSlotsQueue.size() > 0) {
             acc->um->requestUploadSlot(uploadingSlotsQueue.front().first);
         }
-        uploadingSlotsQueue.pop_front();
     }
 }
 
-void Core::MessageHandler::onFileUploaded(const QString& messageId, const QString& url)
+void Core::MessageHandler::onDownloadFileComplete(const std::list<Shared::MessageInfo>& msgs, const QString& path)
 {
-    std::map<QString, Shared::Message>::const_iterator itr = pendingMessages.find(messageId);
-    if (itr != pendingMessages.end()) {
-        sendMessageWithLocalUploadedFile(itr->second, url);
-        pendingMessages.erase(itr);
+    QMap<QString, QVariant> cData = {
+        {"attachPath", path}
+    };
+    for (const Shared::MessageInfo& info : msgs) {
+        if (info.account == acc->getName()) {
+            Contact* cnt = acc->rh->getContact(info.jid);
+            if (cnt != 0) {
+                if (cnt->changeMessage(info.messageId, cData)) {
+                    emit acc->changeMessage(info.jid, info.messageId, cData);
+                }
+            }
+        }
     }
 }
 
-void Core::MessageHandler::onFileUploadError(const QString& messageId, const QString& errMsg)
+void Core::MessageHandler::onLoadFileError(const std::list<Shared::MessageInfo>& msgs, const QString& text, bool up)
+{
+    if (up) {
+        for (const Shared::MessageInfo& info : msgs) {
+            if (info.account == acc->getName()) {
+                handleUploadError(info.jid, info.messageId, text);
+            }
+        }
+    }
+}
+
+void Core::MessageHandler::handleUploadError(const QString& jid, const QString& messageId, const QString& errorText)
 {
     std::map<QString, Shared::Message>::const_iterator itr = pendingMessages.find(messageId);
     if (itr != pendingMessages.end()) {
         pendingMessages.erase(itr);
+        //TODO move the storage of pending messages to the database and change them there
+    }
+}
+
+void Core::MessageHandler::onUploadFileComplete(const std::list<Shared::MessageInfo>& msgs, const QString& path)
+{
+    for (const Shared::MessageInfo& info : msgs) {
+        if (info.account == acc->getName()) {
+            std::map<QString, Shared::Message>::const_iterator itr = pendingMessages.find(info.messageId);
+            if (itr != pendingMessages.end()) {
+                sendMessageWithLocalUploadedFile(itr->second, path);
+                pendingMessages.erase(itr);
+            }
+        }
     }
 }
 
diff --git a/core/handlers/messagehandler.h b/core/handlers/messagehandler.h
index 0b53cc2..8893921 100644
--- a/core/handlers/messagehandler.h
+++ b/core/handlers/messagehandler.h
@@ -28,6 +28,7 @@
 #include <QXmppHttpUploadIq.h>
 
 #include <shared/message.h>
+#include <shared/messageinfo.h>
 
 namespace Core {
 
@@ -54,8 +55,9 @@ public slots:
     void onReceiptReceived(const QString& jid, const QString& id);    
     void onUploadSlotReceived(const QXmppHttpUploadSlotIq& slot);
     void onUploadSlotRequestFailed(const QXmppHttpUploadRequestIq& request);
-    void onFileUploaded(const QString& messageId, const QString& url);
-    void onFileUploadError(const QString& messageId, const QString& errMsg);
+    void onDownloadFileComplete(const std::list<Shared::MessageInfo>& msgs, const QString& path);
+    void onUploadFileComplete(const std::list<Shared::MessageInfo>& msgs, const QString& path);
+    void onLoadFileError(const std::list<Shared::MessageInfo>& msgs, const QString& path, bool up);
     
 private:
     bool handleChatMessage(const QXmppMessage& msg, bool outgoing = false, bool forwarded = false, bool guessing = false);
@@ -64,6 +66,7 @@ private:
     void sendMessageWithLocalUploadedFile(Shared::Message msg, const QString& url);
     void performSending(Shared::Message data);
     void prepareUpload(const Shared::Message& data);
+    void handleUploadError(const QString& jid, const QString& messageId, const QString& errorText);
     
 private:
     Account* acc;
diff --git a/core/networkaccess.cpp b/core/networkaccess.cpp
index 2d66a70..60d7cb9 100644
--- a/core/networkaccess.cpp
+++ b/core/networkaccess.cpp
@@ -16,13 +16,17 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
+
+#include <QtWidgets/QApplication>
+#include <QtCore/QDir>
+
 #include "networkaccess.h"
 
 Core::NetworkAccess::NetworkAccess(QObject* parent):
     QObject(parent),
     running(false),
     manager(0),
-    files("files"),
+    storage("fileURLStorage"),
     downloads(),
     uploads()
 {
@@ -33,60 +37,31 @@ Core::NetworkAccess::~NetworkAccess()
     stop();
 }
 
-void Core::NetworkAccess::fileLocalPathRequest(const QString& messageId, const QString& url)
+void Core::NetworkAccess::downladFile(const QString& url)
 {
     std::map<QString, Transfer*>::iterator itr = downloads.find(url);
     if (itr != downloads.end()) {
-        Transfer* dwn = itr->second;
-        std::set<QString>::const_iterator mItr = dwn->messages.find(messageId);
-        if (mItr == dwn->messages.end()) {
-            dwn->messages.insert(messageId);
-        }
-        emit downloadFileProgress(messageId, dwn->progress);
+        qDebug() << "NetworkAccess received a request to download a file" << url << ", but the file is currently downloading, skipping";
     } else {
         try {
-            QString path = files.getRecord(url);
-            QFileInfo info(path);
-            if (info.exists() && info.isFile()) {
-                emit fileLocalPathResponse(messageId, path);
+            std::pair<QString, std::list<Shared::MessageInfo>> p = storage.getPath(url);
+            if (p.first.size() > 0) {
+                QFileInfo info(p.first);
+                if (info.exists() && info.isFile()) {
+                    emit downloadFileComplete(p.second, p.first);
+                } else {
+                    startDownload(p.second, url);
+                }
             } else {
-                files.removeRecord(url);
-                emit fileLocalPathResponse(messageId, "");
+                startDownload(p.second, url);
             }
         } catch (const Archive::NotFound& e) {
-            emit fileLocalPathResponse(messageId, "");
+            qDebug() << "NetworkAccess received a request to download a file" << url << ", but there is now record of which message uses that file, downloading anyway";
+            storage.addFile(url);
+            startDownload(std::list<Shared::MessageInfo>(), url);
         } catch (const Archive::Unknown& e) {
             qDebug() << "Error requesting file path:" << e.what();
-            emit fileLocalPathResponse(messageId, "");
-        }
-    }
-}
-
-void Core::NetworkAccess::downladFileRequest(const QString& messageId, const QString& url)
-{
-    std::map<QString, Transfer*>::iterator itr = downloads.find(url);
-    if (itr != downloads.end()) {
-        Transfer* dwn = itr->second;
-        std::set<QString>::const_iterator mItr = dwn->messages.find(messageId);
-        if (mItr == dwn->messages.end()) {
-            dwn->messages.insert(messageId);
-        }
-        emit downloadFileProgress(messageId, dwn->progress);
-    } else {
-        try {
-            QString path = files.getRecord(url);
-            QFileInfo info(path);
-            if (info.exists() && info.isFile()) {
-                emit fileLocalPathResponse(messageId, path);
-            } else {
-                files.removeRecord(url);
-                startDownload(messageId, url);
-            }
-        } catch (const Archive::NotFound& e) {
-            startDownload(messageId, url);
-        } catch (const Archive::Unknown& e) {
-            qDebug() << "Error requesting file path:" << e.what();
-            emit downloadFileError(messageId, QString("Database error: ") + e.what());
+            emit loadFileError(std::list<Shared::MessageInfo>(), QString("Database error: ") + e.what(), false);
         }
     }
 }
@@ -95,7 +70,7 @@ void Core::NetworkAccess::start()
 {
     if (!running) {
         manager = new QNetworkAccessManager();
-        files.open();
+        storage.open();
         running = true;
     }
 }
@@ -103,7 +78,7 @@ void Core::NetworkAccess::start()
 void Core::NetworkAccess::stop()
 {
     if (running) {
-        files.close();
+        storage.close();
         manager->deleteLater();
         manager = 0;
         running = false;
@@ -128,9 +103,7 @@ void Core::NetworkAccess::onDownloadProgress(qint64 bytesReceived, qint64 bytesT
         qreal total = bytesTotal;
         qreal progress = received/total;
         dwn->progress = progress;
-        for (std::set<QString>::const_iterator mItr = dwn->messages.begin(), end = dwn->messages.end(); mItr != end; ++mItr) {
-            emit downloadFileProgress(*mItr, progress);
-        }
+        emit loadFileProgress(dwn->messages, progress, false);
     }
 }
 
@@ -146,9 +119,7 @@ void Core::NetworkAccess::onDownloadError(QNetworkReply::NetworkError code)
         if (errorText.size() > 0) {
             itr->second->success = false;
             Transfer* dwn = itr->second;
-            for (std::set<QString>::const_iterator mItr = dwn->messages.begin(), end = dwn->messages.end(); mItr != end; ++mItr) {
-                emit downloadFileError(*mItr, errorText);
-            }
+            emit loadFileError(dwn->messages, errorText, false);
         }
     }
 }
@@ -276,61 +247,54 @@ QString Core::NetworkAccess::getErrorText(QNetworkReply::NetworkError code)
 
 void Core::NetworkAccess::onDownloadFinished()
 {
-    QString path("");
     QNetworkReply* rpl = static_cast<QNetworkReply*>(sender());
     QString url = rpl->url().toString();
     std::map<QString, Transfer*>::const_iterator itr = downloads.find(url);
     if (itr == downloads.end()) {
-        qDebug() << "an error downloading" << url << ": the request is done but seems like noone is waiting for it, skipping";
+        qDebug() << "an error downloading" << url << ": the request is done but there is no record of it being downloaded, ignoring";
     } else {
         Transfer* dwn = itr->second;
         if (dwn->success) {
             qDebug() << "download success for" << url;
             QStringList hops = url.split("/");
             QString fileName = hops.back();
-            QStringList parts = fileName.split(".");
-            path = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation) + "/";
-            QString suffix("");
-            QStringList::const_iterator sItr = parts.begin();
-            QString realName = *sItr;
-            ++sItr;
-            for (QStringList::const_iterator sEnd = parts.end(); sItr != sEnd; ++sItr) {
-                suffix += "." + (*sItr);
+            QString jid;
+            if (dwn->messages.size() > 0) {
+                jid = dwn->messages.front().jid;
             }
-            QString postfix("");
-            QFileInfo proposedName(path + realName + postfix + suffix);
-            int counter = 0;
-            while (proposedName.exists()) {
-                postfix = QString("(") + std::to_string(++counter).c_str() + ")";
-                proposedName = QFileInfo(path + realName + postfix + suffix);
+            QString path = prepareDirectory(jid);
+            if (path.size() > 0) {
+                path = checkFileName(fileName, path);
+                
+                QFile file(path);
+                if (file.open(QIODevice::WriteOnly)) {
+                    file.write(dwn->reply->readAll());
+                    file.close();
+                    storage.setPath(url, path);
+                    qDebug() << "file" << path << "was successfully downloaded";
+                } else {
+                    qDebug() << "couldn't save file" << path;
+                    path = QString();
+                }
             }
             
-            path = proposedName.absoluteFilePath();
-            QFile file(path);
-            if (file.open(QIODevice::WriteOnly)) {
-                file.write(dwn->reply->readAll());
-                file.close();
-                files.addRecord(url, path);
-                qDebug() << "file" << path << "was successfully downloaded";
+            if (path.size() > 0) {
+                emit downloadFileComplete(dwn->messages, path);
             } else {
-                qDebug() << "couldn't save file" << path;
-                path = "";
+                //TODO do I need to handle the failure here or it's already being handled in error?
+                //emit loadFileError(dwn->messages, path, false);
             }
         }
         
-        for (std::set<QString>::const_iterator mItr = dwn->messages.begin(), end = dwn->messages.end(); mItr != end; ++mItr) {
-            emit fileLocalPathResponse(*mItr, path);
-        }
-        
         dwn->reply->deleteLater();
         delete dwn;
         downloads.erase(itr);
     }
 }
 
-void Core::NetworkAccess::startDownload(const QString& messageId, const QString& url)
+void Core::NetworkAccess::startDownload(const std::list<Shared::MessageInfo>& msgs, const QString& url)
 {
-    Transfer* dwn = new Transfer({{messageId}, 0, 0, true, "", url, 0});
+    Transfer* dwn = new Transfer({msgs, 0, 0, true, "", url, 0});
     QNetworkRequest req(url);
     dwn->reply = manager->get(req);
     connect(dwn->reply, &QNetworkReply::downloadProgress, this, &NetworkAccess::onDownloadProgress);
@@ -341,7 +305,7 @@ void Core::NetworkAccess::startDownload(const QString& messageId, const QString&
 #endif
     connect(dwn->reply, &QNetworkReply::finished, this, &NetworkAccess::onDownloadFinished);
     downloads.insert(std::make_pair(url, dwn));
-    emit downloadFileProgress(messageId, 0);
+    emit loadFileProgress(dwn->messages, 0, false);
 }
 
 void Core::NetworkAccess::onUploadError(QNetworkReply::NetworkError code)
@@ -350,16 +314,16 @@ void Core::NetworkAccess::onUploadError(QNetworkReply::NetworkError code)
     QString url = rpl->url().toString();
     std::map<QString, Transfer*>::const_iterator itr = uploads.find(url);
     if (itr == uploads.end()) {
-        qDebug() << "an error uploading" << url << ": the request is reporting an error but seems like noone is waiting for it, skipping";
+        qDebug() << "an error uploading" << url << ": the request is reporting an error but there is no record of it being uploading, ignoring";
     } else {
         QString errorText = getErrorText(code);
         if (errorText.size() > 0) {
             itr->second->success = false;
             Transfer* upl = itr->second;
-            for (std::set<QString>::const_iterator mItr = upl->messages.begin(), end = upl->messages.end(); mItr != end; ++mItr) {
-                emit uploadFileError(*mItr, errorText);
-            }
+            emit loadFileError(upl->messages, errorText, true);
         }
+        
+        //TODO deletion?
     }
 }
 
@@ -369,17 +333,14 @@ void Core::NetworkAccess::onUploadFinished()
     QString url = rpl->url().toString();
     std::map<QString, Transfer*>::const_iterator itr = uploads.find(url);
     if (itr == downloads.end()) {
-        qDebug() << "an error uploading" << url << ": the request is done but seems like no one is waiting for it, skipping";
+        qDebug() << "an error uploading" << url << ": the request is done there is no record of it being uploading, ignoring";
     } else {
         Transfer* upl = itr->second;
         if (upl->success) {
             qDebug() << "upload success for" << url;
-            files.addRecord(upl->url, upl->path);
-        
-            for (std::set<QString>::const_iterator mItr = upl->messages.begin(), end = upl->messages.end(); mItr != end; ++mItr) {
-                emit fileLocalPathResponse(*mItr, upl->path);
-                emit uploadFileComplete(*mItr, upl->url);
-            }
+            
+            storage.addFile(upl->messages, upl->url, upl->path);
+            emit uploadFileComplete(upl->messages, upl->url);
         }
         
         upl->reply->deleteLater();
@@ -403,94 +364,29 @@ void Core::NetworkAccess::onUploadProgress(qint64 bytesReceived, qint64 bytesTot
         qreal total = bytesTotal;
         qreal progress = received/total;
         upl->progress = progress;
-        for (std::set<QString>::const_iterator mItr = upl->messages.begin(), end = upl->messages.end(); mItr != end; ++mItr) {
-            emit uploadFileProgress(*mItr, progress);
-        }
-    }
-}
-
-void Core::NetworkAccess::startUpload(const QString& messageId, const QString& url, const QString& path)
-{
-    Transfer* upl = new Transfer({{messageId}, 0, 0, true, path, url, 0});
-    QNetworkRequest req(url);
-    QFile* file = new QFile(path);
-    if (file->open(QIODevice::ReadOnly)) {
-        upl->reply = manager->put(req, file);
-        
-        connect(upl->reply, &QNetworkReply::uploadProgress, this, &NetworkAccess::onUploadProgress);
-#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
-        connect(upl->reply, qOverload<QNetworkReply::NetworkError>(&QNetworkReply::errorOccurred), this, &NetworkAccess::onUploadError);
-#else
-        connect(upl->reply, qOverload<QNetworkReply::NetworkError>(&QNetworkReply::error), this, &NetworkAccess::onUploadError);
-#endif
-        
-        connect(upl->reply, &QNetworkReply::finished, this, &NetworkAccess::onUploadFinished);
-        uploads.insert(std::make_pair(url, upl));
-        emit downloadFileProgress(messageId, 0);
-    } else {
-        qDebug() << "couldn't upload file" << path;
-        emit uploadFileError(messageId, "Error opening file");
-        delete file;
-    }
-}
-
-void Core::NetworkAccess::uploadFileRequest(const QString& messageId, const QString& url, const QString& path)
-{
-    std::map<QString, Transfer*>::iterator itr = uploads.find(url);
-    if (itr != uploads.end()) {
-        Transfer* upl = itr->second;
-        std::set<QString>::const_iterator mItr = upl->messages.find(messageId);
-        if (mItr == upl->messages.end()) {
-            upl->messages.insert(messageId);
-        }
-        emit uploadFileProgress(messageId, upl->progress);
-    } else {
-        try {
-            QString ePath = files.getRecord(url);
-            if (ePath == path) {
-                QFileInfo info(path);
-                if (info.exists() && info.isFile()) {
-                    emit fileLocalPathResponse(messageId, path);
-                } else {
-                    files.removeRecord(url);
-                    startUpload(messageId, url, path);
-                }
-            } else {
-                QFileInfo info(path);
-                if (info.exists() && info.isFile()) {
-                    files.changeRecord(url, path);
-                    emit fileLocalPathResponse(messageId, path);
-                } else {
-                    files.removeRecord(url);
-                    startUpload(messageId, url, path);
-                }
-            }
-        } catch (const Archive::NotFound& e) {
-            startUpload(messageId, url, path);
-        } catch (const Archive::Unknown& e) {
-            qDebug() << "Error requesting file path on upload:" << e.what();
-            emit uploadFileError(messageId, QString("Database error: ") + e.what());
-        }
+        emit loadFileProgress(upl->messages, progress, true);
     }
 }
 
 QString Core::NetworkAccess::getFileRemoteUrl(const QString& path)
 {
-    return "";  //TODO this is a way not to upload some file more then 1 time, here I'm supposed to return that file GET url
+    QString p;
+    
+    try {
+        QString p = storage.getUrl(path);
+    } catch (const Archive::NotFound& err) {
+        
+    } catch (...) {
+        throw;
+    }
+    
+    return p;
 }
 
-bool Core::NetworkAccess::isUploading(const QString& path, const QString& messageId)
-{
-    return false; //TODO this is a way to avoid parallel uploading of the same files by different chats
-                    //   message is is supposed to be added to the uploading messageids list 
-                    //   the result should be true if there was an uploading file with this path
-                    //   message id can be empty, then it's just to check and not to add
-}
-
-void Core::NetworkAccess::uploadFile(const QString& messageId, const QString& path, const QUrl& put, const QUrl& get, const QMap<QString, QString> headers)
+void Core::NetworkAccess::uploadFile(const Shared::MessageInfo& info, const QString& path, const QUrl& put, const QUrl& get, const QMap<QString, QString> headers)
 {
     QFile* file = new QFile(path);
-    Transfer* upl = new Transfer({{messageId}, 0, 0, true, path, get.toString(), file});
+    Transfer* upl = new Transfer({{info}, 0, 0, true, path, get.toString(), file});
     QNetworkRequest req(put);
     for (QMap<QString, QString>::const_iterator itr = headers.begin(), end = headers.end(); itr != end; itr++) {
         req.setRawHeader(itr.key().toUtf8(), itr.value().toUtf8());
@@ -506,10 +402,94 @@ void Core::NetworkAccess::uploadFile(const QString& messageId, const QString& pa
 #endif
         connect(upl->reply, &QNetworkReply::finished, this, &NetworkAccess::onUploadFinished);
         uploads.insert(std::make_pair(put.toString(), upl));
-        emit downloadFileProgress(messageId, 0);
+        emit loadFileProgress(upl->messages, 0, true);
     } else {
         qDebug() << "couldn't upload file" << path;
-        emit uploadFileError(messageId, "Error opening file");
+        emit loadFileError(upl->messages, "Error opening file", true);
         delete file;
+        delete upl;
     }
 }
+
+void Core::NetworkAccess::registerFile(const QString& url, const QString& account, const QString& jid, const QString& id)
+{
+    storage.addFile(url, account, jid, id);
+    std::map<QString, Transfer*>::iterator itr = downloads.find(url);
+    if (itr != downloads.end()) {
+        itr->second->messages.emplace_back(account, jid, id);   //TODO notification is going to happen the next tick, is that okay?
+    }
+}
+
+void Core::NetworkAccess::registerFile(const QString& url, const QString& path, const QString& account, const QString& jid, const QString& id)
+{
+    storage.addFile(url, path, account, jid, id);
+}
+
+bool Core::NetworkAccess::checkAndAddToUploading(const QString& acc, const QString& jid, const QString id, const QString path)
+{
+    for (const std::pair<const QString, Transfer*>& pair : uploads) {
+        Transfer* info = pair.second;
+        if (pair.second->path == path) {
+            std::list<Shared::MessageInfo>& messages = info->messages;
+            bool dup = false;
+            for (const Shared::MessageInfo& info : messages) {
+                if (info.account == acc && info.jid == jid && info.messageId == id) {
+                    dup = true;
+                    break;
+                }
+            }
+            if (!dup) {
+                info->messages.emplace_back(acc, jid, id);   //TODO notification is going to happen the next tick, is that okay?
+                return true;
+            }
+        }
+    }
+    
+    return false;
+}
+
+QString Core::NetworkAccess::prepareDirectory(const QString& jid)
+{
+    QString path = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation);
+    path += "/" + QApplication::applicationName();
+    if (jid.size() > 0) {
+        path += "/" + jid;
+    }
+    QDir location(path);
+    
+    if (!location.exists()) {
+        bool res = location.mkpath(path);
+        if (!res) {
+            return "";
+        } else {
+            return path;
+        }
+    }
+    return path;
+}
+
+QString Core::NetworkAccess::checkFileName(const QString& name, const QString& path)
+{
+    QStringList parts = name.split(".");
+    QString suffix("");
+    QStringList::const_iterator sItr = parts.begin();
+    QString realName = *sItr;
+    ++sItr;
+    for (QStringList::const_iterator sEnd = parts.end(); sItr != sEnd; ++sItr) {
+        suffix += "." + (*sItr);
+    }
+    QString postfix("");
+    QFileInfo proposedName(path + realName + suffix);
+    int counter = 0;
+    while (proposedName.exists()) {
+        QString count = QString("(") + std::to_string(++counter).c_str() + ")";
+        proposedName = QFileInfo(path + realName + count + suffix);
+    }
+    
+    return proposedName.absoluteFilePath();
+}
+
+QString Core::NetworkAccess::addMessageAndCheckForPath(const QString& url, const QString& account, const QString& jid, const QString& id)
+{
+    return storage.addMessageAndCheckForPath(url, account, jid, id);
+}
diff --git a/core/networkaccess.h b/core/networkaccess.h
index c931393..a116e6d 100644
--- a/core/networkaccess.h
+++ b/core/networkaccess.h
@@ -36,6 +36,8 @@ namespace Core {
 /**
  * @todo write docs
  */
+
+//TODO Need to describe how to get rid of records when file is no longer reachable;
 class NetworkAccess : public QObject
 {
     Q_OBJECT
@@ -48,26 +50,26 @@ public:
     void stop();
     
     QString getFileRemoteUrl(const QString& path);
-    bool isUploading(const QString& path, const QString& messageId = "");
-    void uploadFile(const QString& messageId, const QString& path, const QUrl& put, const QUrl& get, const QMap<QString, QString> headers);
+    QString addMessageAndCheckForPath(const QString& url, const QString& account, const QString& jid, const QString& id);
+    void uploadFile(const Shared::MessageInfo& info, const QString& path, const QUrl& put, const QUrl& get, const QMap<QString, QString> headers);
+    bool checkAndAddToUploading(const QString& acc, const QString& jid, const QString id, const QString path);
     
 signals:
-    void fileLocalPathResponse(const QString& messageId, const QString& path);
-    void downloadFileProgress(const QString& messageId, qreal value);
-    void downloadFileError(const QString& messageId, const QString& path);
-    void uploadFileProgress(const QString& messageId, qreal value);
-    void uploadFileError(const QString& messageId, const QString& path);
-    void uploadFileComplete(const QString& messageId, const QString& url);
+    void loadFileProgress(const std::list<Shared::MessageInfo>& msgs, qreal value, bool up);
+    void loadFileError(const std::list<Shared::MessageInfo>& msgs, const QString& text, bool up);
+    void uploadFileComplete(const std::list<Shared::MessageInfo>& msgs, const QString& url);
+    void downloadFileComplete(const std::list<Shared::MessageInfo>& msgs, const QString& path);
     
 public slots:
-    void fileLocalPathRequest(const QString& messageId, const QString& url);
-    void downladFileRequest(const QString& messageId, const QString& url);
-    void uploadFileRequest(const QString& messageId, const QString& url, const QString& path);
+    void downladFile(const QString& url);
+    void registerFile(const QString& url, const QString& account, const QString& jid, const QString& id);
+    void registerFile(const QString& url, const QString& path, const QString& account, const QString& jid, const QString& id);
     
 private:
-    void startDownload(const QString& messageId, const QString& url);
-    void startUpload(const QString& messageId, const QString& url, const QString& path);
+    void startDownload(const std::list<Shared::MessageInfo>& msgs, const QString& url);
     QString getErrorText(QNetworkReply::NetworkError code);
+    QString prepareDirectory(const QString& jid);
+    QString checkFileName(const QString& name, const QString& path);
     
 private slots:
     void onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal);
@@ -85,7 +87,7 @@ private:
     std::map<QString, Transfer*> uploads;
     
     struct Transfer {
-        std::set<QString> messages;
+        std::list<Shared::MessageInfo> messages;
         qreal progress;
         QNetworkReply* reply;
         bool success;
diff --git a/core/squawk.cpp b/core/squawk.cpp
index a64d418..83fedb6 100644
--- a/core/squawk.cpp
+++ b/core/squawk.cpp
@@ -32,11 +32,10 @@ Core::Squawk::Squawk(QObject* parent):
     ,kwallet()
 #endif
 {
-    connect(&network, &NetworkAccess::fileLocalPathResponse, this, &Squawk::onNetworkAccessfileLocalPathResponse);
-    connect(&network, &NetworkAccess::downloadFileProgress, this, &Squawk::downloadFileProgress);
-    connect(&network, &NetworkAccess::downloadFileError, this, &Squawk::downloadFileError);
-    connect(&network, &NetworkAccess::uploadFileProgress, this, &Squawk::uploadFileProgress);
-    connect(&network, &NetworkAccess::uploadFileError, this, &Squawk::uploadFileError);
+    connect(&network, &NetworkAccess::loadFileProgress, this, &Squawk::fileProgress);
+    connect(&network, &NetworkAccess::loadFileError, this, &Squawk::fileError);
+    connect(&network, &NetworkAccess::downloadFileComplete, this, &Squawk::fileDownloadComplete);
+    connect(&network, &NetworkAccess::uploadFileComplete, this, &Squawk::fileUploadComplete);
     
 #ifdef WITH_KWALLET
     if (kwallet.supportState() == PSE::KWallet::success) {
@@ -168,7 +167,7 @@ void Core::Squawk::addAccount(
     
     connect(acc, &Account::receivedVCard, this, &Squawk::responseVCard);
     
-    connect(acc, &Account::uploadFileError, this, &Squawk::uploadFileError);
+    connect(acc, &Account::uploadFileError, this, &Squawk::onAccountUploadFileError);
     
     QMap<QString, QVariant> map = {
         {"login", login},
@@ -593,14 +592,9 @@ void Core::Squawk::addRoomRequest(const QString& account, const QString& jid, co
     itr->second->addRoomRequest(jid, nick, password, autoJoin);
 }
 
-void Core::Squawk::fileLocalPathRequest(const QString& messageId, const QString& url)
+void Core::Squawk::fileDownloadRequest(const QString& url)
 {
-    network.fileLocalPathRequest(messageId, url);
-}
-
-void Core::Squawk::downloadFileRequest(const QString& messageId, const QString& url)
-{
-    network.downladFileRequest(messageId, url);
+    network.downladFile(url);
 }
 
 void Core::Squawk::addContactToGroupRequest(const QString& account, const QString& jid, const QString& groupName)
@@ -752,7 +746,8 @@ void Core::Squawk::onWalletResponsePassword(const QString& login, const QString&
     accountReady();
 }
 
-void Core::Squawk::onNetworkAccessfileLocalPathResponse(const QString& messageId, const QString& path)
+void Core::Squawk::onAccountUploadFileError(const QString& jid, const QString id, const QString& errorText)
 {
-    
+    Account* acc = static_cast<Account*>(sender());
+    emit fileError({{acc->getName(), jid, id}}, errorText, true);
 }
diff --git a/core/squawk.h b/core/squawk.h
index 4b6fbea..36301d8 100644
--- a/core/squawk.h
+++ b/core/squawk.h
@@ -51,30 +51,39 @@ public:
 signals:
     void quit();
     void ready();
+    
     void newAccount(const QMap<QString, QVariant>&);
     void changeAccount(const QString& account, const QMap<QString, QVariant>& data);
     void removeAccount(const QString& account);
+    
     void addGroup(const QString& account, const QString& name);
     void removeGroup(const QString& account, const QString& name);
+    
     void addContact(const QString& account, const QString& jid, const QString& group, const QMap<QString, QVariant>& data);
     void removeContact(const QString& account, const QString& jid);
     void removeContact(const QString& account, const QString& jid, const QString& group);
     void changeContact(const QString& account, const QString& jid, const QMap<QString, QVariant>& data);
+    
     void addPresence(const QString& account, const QString& jid, const QString& name, const QMap<QString, QVariant>& data);
     void removePresence(const QString& account, const QString& jid, const QString& name);
+    
     void stateChanged(Shared::Availability state);
+    
     void accountMessage(const QString& account, const Shared::Message& data);
     void responseArchive(const QString& account, const QString& jid, const std::list<Shared::Message>& list, bool last);
+    
     void addRoom(const QString& account, const QString jid, const QMap<QString, QVariant>& data);
     void changeRoom(const QString& account, const QString jid, const QMap<QString, QVariant>& data);
     void removeRoom(const QString& account, const QString jid);
     void addRoomParticipant(const QString& account, const QString& jid, const QString& name, const QMap<QString, QVariant>& data);
     void changeRoomParticipant(const QString& account, const QString& jid, const QString& name, const QMap<QString, QVariant>& data);
     void removeRoomParticipant(const QString& account, const QString& jid, const QString& name);
-    void downloadFileError(const QString& messageId, const QString& error);
-    void downloadFileProgress(const QString& messageId, qreal value);
-    void uploadFileError(const QString& messageId, const QString& error);
-    void uploadFileProgress(const QString& messageId, qreal value);
+    
+    void fileError(const std::list<Shared::MessageInfo> msgs, const QString& error, bool up);
+    void fileProgress(const std::list<Shared::MessageInfo> msgs, qreal value, bool up);
+    void fileDownloadComplete(const std::list<Shared::MessageInfo> msgs, const QString& path);
+    void fileUploadComplete(const std::list<Shared::MessageInfo> msgs, const QString& path);
+    
     void responseVCard(const QString& jid, const Shared::VCard& card);
     void changeMessage(const QString& account, const QString& jid, const QString& id, const QMap<QString, QVariant>& data);
     void requestPassword(const QString& account);
@@ -82,14 +91,18 @@ signals:
 public slots:
     void start();
     void stop();
+    
     void newAccountRequest(const QMap<QString, QVariant>& map);
     void modifyAccountRequest(const QString& name, const QMap<QString, QVariant>& map);
     void removeAccountRequest(const QString& name);
     void connectAccount(const QString& account);
     void disconnectAccount(const QString& account);
+    
     void changeState(Shared::Availability state);
+    
     void sendMessage(const QString& account, const Shared::Message& data);
     void requestArchive(const QString& account, const QString& jid, int count, const QString& before);
+    
     void subscribeContact(const QString& account, const QString& jid, const QString& reason);
     void unsubscribeContact(const QString& account, const QString& jid, const QString& reason);
     void addContactToGroupRequest(const QString& account, const QString& jid, const QString& groupName);
@@ -97,12 +110,14 @@ public slots:
     void removeContactRequest(const QString& account, const QString& jid);
     void renameContactRequest(const QString& account, const QString& jid, const QString& newName);
     void addContactRequest(const QString& account, const QString& jid, const QString& name, const QSet<QString>& groups);
+    
     void setRoomJoined(const QString& account, const QString& jid, bool joined);
     void setRoomAutoJoin(const QString& account, const QString& jid, bool joined);
     void addRoomRequest(const QString& account, const QString& jid, const QString& nick, const QString& password, bool autoJoin);
     void removeRoomRequest(const QString& account, const QString& jid);
-    void fileLocalPathRequest(const QString& messageId, const QString& url);
-    void downloadFileRequest(const QString& messageId, const QString& url);
+    
+    void fileDownloadRequest(const QString& url);
+    
     void requestVCard(const QString& account, const QString& jid);
     void uploadVCard(const QString& account, const Shared::VCard& card);
     void responsePassword(const QString& account, const QString& password);
@@ -153,12 +168,12 @@ private slots:
     void onAccountRemoveRoomPresence(const QString& jid, const QString& nick);
     void onAccountChangeMessage(const QString& jid, const QString& id, const QMap<QString, QVariant>& data);
     
+    void onAccountUploadFileError(const QString& jid, const QString id, const QString& errorText);
+    
     void onWalletOpened(bool success);
     void onWalletResponsePassword(const QString& login, const QString& password);
     void onWalletRejectPassword(const QString& login);
     
-    void onNetworkAccessfileLocalPathResponse(const QString& messageId, const QString& path);
-    
 private:
     void readSettings();
     void accountReady();
diff --git a/core/urlstorage.cpp b/core/urlstorage.cpp
index a8a9179..1ce7957 100644
--- a/core/urlstorage.cpp
+++ b/core/urlstorage.cpp
@@ -165,8 +165,7 @@ void Core::UrlStorage::addFile(const QString& url)
         throw Archive::Closed("addFile(no message, no path)", name.toStdString());
     }
     
-    UrlInfo info;
-    writeInfo(url, info);
+    addToInfo(url, "", "", "");
 }
 
 void Core::UrlStorage::addFile(const QString& url, const QString& path)
@@ -175,8 +174,7 @@ void Core::UrlStorage::addFile(const QString& url, const QString& path)
         throw Archive::Closed("addFile(no message, with path)", name.toStdString());
     }
     
-    UrlInfo info(path);
-    writeInfo(url, info);
+    addToInfo(url, "", "", "", path);
 }
 
 void Core::UrlStorage::addFile(const QString& url, const QString& account, const QString& jid, const QString& id)
@@ -185,9 +183,7 @@ void Core::UrlStorage::addFile(const QString& url, const QString& account, const
         throw Archive::Closed("addFile(with message, no path)", name.toStdString());
     }
     
-    UrlInfo info;
-    info.addMessage(account, jid, id);
-    writeInfo(url, info);
+    addToInfo(url, account, jid, id);
 }
 
 void Core::UrlStorage::addFile(const QString& url, const QString& path, const QString& account, const QString& jid, const QString& id)
@@ -196,50 +192,74 @@ void Core::UrlStorage::addFile(const QString& url, const QString& path, const QS
         throw Archive::Closed("addFile(with message, with path)", name.toStdString());
     }
     
-    UrlInfo info(path);
-    info.addMessage(account, jid, id);
-    writeInfo(url, info);
+    addToInfo(url, account, jid, id, path);
+}
+
+void Core::UrlStorage::addFile(const std::list<Shared::MessageInfo>& msgs, const QString& url, const QString& path)
+{
+    if (!opened) {
+        throw Archive::Closed("addFile(with list)", name.toStdString());
+    }
+    
+    UrlInfo info (path, msgs);
+    writeInfo(url, info, true);;
 }
 
 QString Core::UrlStorage::addMessageAndCheckForPath(const QString& url, const QString& account, const QString& jid, const QString& id)
 {
-    QString path;
+    if (!opened) {
+        throw Archive::Closed("addMessageAndCheckForPath", name.toStdString());
+    }
     
+    return addToInfo(url, account, jid, id).getPath();
+}
+
+Core::UrlStorage::UrlInfo Core::UrlStorage::addToInfo(const QString& url, const QString& account, const QString& jid, const QString& id, const QString& path)
+{
+    UrlInfo info;
     MDB_txn *txn;
     mdb_txn_begin(environment, NULL, 0, &txn);
-    UrlInfo info;
     
     try {
         readInfo(url, info, txn);
-        path = info.getPath();
-        info.addMessage(account, jid, id);
-        try {
-            writeInfo(url, info, txn, true);
-            mdb_txn_commit(txn);
-        } catch (...) {
-            mdb_txn_abort(txn);
-            throw;
-        }
     } catch (const Archive::NotFound& e) {
-        info.addMessage(account, jid, id);
-        try {
-            writeInfo(url, info, txn, true);
-            mdb_txn_commit(txn);
-        } catch (...) {
-            mdb_txn_abort(txn);
-            throw;
-        }
+        
     } catch (...) {
         mdb_txn_abort(txn);
         throw;
     }
     
-    return path;
+    bool pathChange = false;
+    bool listChange = false;
+    if (path != "-s") {
+        if (info.getPath() != path) {
+            info.setPath(path);
+            pathChange = true;
+        }
+    }
+    
+    if (account.size() > 0 && jid.size() > 0 && id.size() > 0) {
+        listChange = info.addMessage(account, jid, id);
+    }
+    
+    if (pathChange || listChange) {
+        try {
+            writeInfo(url, info, txn, true);
+            mdb_txn_commit(txn);
+        } catch (...) {
+            mdb_txn_abort(txn);
+            throw;
+        }
+    } else {
+        mdb_txn_abort(txn);
+    }
+    
+    return info;
 }
 
-std::list<Core::UrlStorage::MessageInfo> Core::UrlStorage::setPath(const QString& url, const QString& path)
+std::list<Shared::MessageInfo> Core::UrlStorage::setPath(const QString& url, const QString& path)
 {
-    std::list<MessageInfo> list;
+    std::list<Shared::MessageInfo> list;
     
     MDB_txn *txn;
     mdb_txn_begin(environment, NULL, 0, &txn);
@@ -247,24 +267,17 @@ std::list<Core::UrlStorage::MessageInfo> Core::UrlStorage::setPath(const QString
     
     try {
         readInfo(url, info, txn);
-        info.setPath(path);
         info.getMessages(list);
-        try {
-            writeInfo(url, info, txn, true);
-            mdb_txn_commit(txn);
-        } catch (...) {
-            mdb_txn_abort(txn);
-            throw;
-        }
     } catch (const Archive::NotFound& e) {
-        info.setPath(path);
-        try {
-            writeInfo(url, info, txn, true);
-            mdb_txn_commit(txn);
-        } catch (...) {
-            mdb_txn_abort(txn);
-            throw;
-        }
+    } catch (...) {
+        mdb_txn_abort(txn);
+        throw;
+    }
+    
+    info.setPath(path);
+    try {
+        writeInfo(url, info, txn, true);
+        mdb_txn_commit(txn);
     } catch (...) {
         mdb_txn_abort(txn);
         throw;
@@ -273,9 +286,9 @@ std::list<Core::UrlStorage::MessageInfo> Core::UrlStorage::setPath(const QString
     return list;
 }
 
-std::list<Core::UrlStorage::MessageInfo> Core::UrlStorage::removeFile(const QString& url)
+std::list<Shared::MessageInfo> Core::UrlStorage::removeFile(const QString& url)
 {
-    std::list<MessageInfo> list;
+    std::list<Shared::MessageInfo> list;
     
     MDB_txn *txn;
     mdb_txn_begin(environment, NULL, 0, &txn);
@@ -313,9 +326,9 @@ std::list<Core::UrlStorage::MessageInfo> Core::UrlStorage::removeFile(const QStr
     return list;
 }
 
-std::list<Core::UrlStorage::MessageInfo> Core::UrlStorage::deletedFile(const QString& path)
+std::list<Shared::MessageInfo> Core::UrlStorage::deletedFile(const QString& path)
 {
-    std::list<MessageInfo> list;
+    std::list<Shared::MessageInfo> list;
     
     MDB_txn *txn;
     mdb_txn_begin(environment, NULL, 0, &txn);
@@ -362,6 +375,46 @@ std::list<Core::UrlStorage::MessageInfo> Core::UrlStorage::deletedFile(const QSt
 }
 
 
+QString Core::UrlStorage::getUrl(const QString& path)
+{
+    std::list<Shared::MessageInfo> list;
+    
+    MDB_txn *txn;
+    mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn);
+    
+    std::string spath = path.toStdString();
+    
+    MDB_val lmdbKey, lmdbData;
+    lmdbKey.mv_size = spath.size();
+    lmdbKey.mv_data = (char*)spath.c_str();
+    
+    QString url;
+    int rc = mdb_get(txn, map, &lmdbKey, &lmdbData);
+    
+    if (rc == 0) {
+        std::string surl((char*)lmdbData.mv_data, lmdbData.mv_size);
+        url = QString(surl.c_str());
+        
+        mdb_txn_abort(txn);
+        return url;
+    } else if (rc == MDB_NOTFOUND) {
+        mdb_txn_abort(txn);
+        throw Archive::NotFound(spath, name.toStdString());
+    } else {
+        mdb_txn_abort(txn);
+        throw Archive::Unknown(name.toStdString(), mdb_strerror(rc));
+    }
+}
+
+std::pair<QString, std::list<Shared::MessageInfo>> Core::UrlStorage::getPath(const QString& url)
+{
+    UrlInfo info;
+    readInfo(url, info);
+    std::list<Shared::MessageInfo> container;
+    info.getMessages(container);
+    return std::make_pair(info.getPath(), container);
+}
+
 Core::UrlStorage::UrlInfo::UrlInfo():
     localPath(),
     messages() {}
@@ -370,19 +423,29 @@ Core::UrlStorage::UrlInfo::UrlInfo(const QString& path):
     localPath(path),
     messages() {}
  
+Core::UrlStorage::UrlInfo::UrlInfo(const QString& path, const std::list<Shared::MessageInfo>& msgs):
+    localPath(path),
+    messages(msgs) {}
+ 
 Core::UrlStorage::UrlInfo::~UrlInfo() {}
  
-void Core::UrlStorage::UrlInfo::addMessage(const QString& acc, const QString& jid, const QString& id)
+bool Core::UrlStorage::UrlInfo::addMessage(const QString& acc, const QString& jid, const QString& id)
 {
+    for (const Shared::MessageInfo& info : messages) {
+        if (info.account == acc && info.jid == jid && info.messageId == id) {
+            return false;
+        }
+    }
     messages.emplace_back(acc, jid, id);
+    return true;
 }
 
 void Core::UrlStorage::UrlInfo::serialize(QDataStream& data) const
 {
     data << localPath;
-    std::list<MessageInfo>::size_type size = messages.size();
+    std::list<Shared::MessageInfo>::size_type size = messages.size();
     data << quint32(size);
-    for (const MessageInfo& info : messages) {
+    for (const Shared::MessageInfo& info : messages) {
         data << info.account;
         data << info.jid;
         data << info.messageId;
@@ -396,16 +459,18 @@ void Core::UrlStorage::UrlInfo::deserialize(QDataStream& data)
     data >> size;
     for (quint32 i = 0; i < size; ++i) {
         messages.emplace_back();
-        MessageInfo& info = messages.back();
+        Shared::MessageInfo& info = messages.back();
         data >> info.account;
         data >> info.jid;
         data >> info.messageId;
     }
 }
 
-void Core::UrlStorage::UrlInfo::getMessages(std::list<MessageInfo>& container) const
+void Core::UrlStorage::UrlInfo::getMessages(std::list<Shared::MessageInfo>& container) const
 {
-    std::copy(messages.begin(), messages.end(), container.end());
+    for (const Shared::MessageInfo& info : messages) {
+        container.emplace_back(info);
+    }
 }
 
 QString Core::UrlStorage::UrlInfo::getPath() const
@@ -423,13 +488,3 @@ void Core::UrlStorage::UrlInfo::setPath(const QString& path)
 {
     localPath = path;
 }
-
-Core::UrlStorage::MessageInfo::MessageInfo():
-    account(),
-    jid(),
-    messageId() {}
-
-Core::UrlStorage::MessageInfo::MessageInfo(const QString& acc, const QString& j, const QString& id):
-    account(acc),
-    jid(j),
-    messageId(id) {}
diff --git a/core/urlstorage.h b/core/urlstorage.h
index 17adfdd..3dc5c21 100644
--- a/core/urlstorage.h
+++ b/core/urlstorage.h
@@ -25,6 +25,7 @@
 #include <list>
 
 #include "archive.h"
+#include <shared/messageinfo.h>
 
 namespace Core {
 
@@ -35,15 +36,6 @@ class UrlStorage
 {
     class UrlInfo;
 public:
-    struct MessageInfo {
-        MessageInfo();
-        MessageInfo(const QString& acc, const QString& j, const QString& id);
-        
-        QString account;
-        QString jid;
-        QString messageId;
-    };
-    
     UrlStorage(const QString& name);
     ~UrlStorage();
     
@@ -54,10 +46,13 @@ public:
     void addFile(const QString& url, const QString& path);
     void addFile(const QString& url, const QString& account, const QString& jid, const QString& id);
     void addFile(const QString& url, const QString& path, const QString& account, const QString& jid, const QString& id);
-    std::list<MessageInfo> removeFile(const QString& url);      //removes entry like it never was in the database, returns affected message infos
-    std::list<MessageInfo> deletedFile(const QString& path);    //empties the localPath of the entry, returns affected message infos
-    std::list<MessageInfo> setPath(const QString& url, const QString& path);
+    void addFile(const std::list<Shared::MessageInfo>& msgs, const QString& url, const QString& path);      //this one overwrites all that was
+    std::list<Shared::MessageInfo> removeFile(const QString& url);      //removes entry like it never was in the database, returns affected message infos
+    std::list<Shared::MessageInfo> deletedFile(const QString& path);    //empties the localPath of the entry, returns affected message infos
+    std::list<Shared::MessageInfo> setPath(const QString& url, const QString& path);
+    QString getUrl(const QString& path);
     QString addMessageAndCheckForPath(const QString& url, const QString& account, const QString& jid, const QString& id);
+    std::pair<QString, std::list<Shared::MessageInfo>> getPath(const QString& url);
     
 private:
     QString name;
@@ -71,11 +66,13 @@ private:
     void writeInfo(const QString& key, const UrlInfo& info, MDB_txn* txn, bool overwrite = false);
     void readInfo(const QString& key, UrlInfo& info);
     void readInfo(const QString& key, UrlInfo& info, MDB_txn* txn);
+    UrlInfo addToInfo(const QString& url, const QString& account, const QString& jid, const QString& id, const QString& path = "-s");
     
 private:
     class UrlInfo {
     public:
         UrlInfo(const QString& path);
+        UrlInfo(const QString& path, const std::list<Shared::MessageInfo>& msgs);
         UrlInfo();
         ~UrlInfo();
         
@@ -86,12 +83,12 @@ private:
         bool hasPath() const;
         void setPath(const QString& path);
         
-        void addMessage(const QString& acc, const QString& jid, const QString& id);
-        void getMessages(std::list<MessageInfo>& container) const;
+        bool addMessage(const QString& acc, const QString& jid, const QString& id);
+        void getMessages(std::list<Shared::MessageInfo>& container) const;
         
     private:
         QString localPath;
-        std::list<MessageInfo> messages;
+        std::list<Shared::MessageInfo> messages;
     };
     
     
diff --git a/main.cpp b/main.cpp
index 0373af8..45232cf 100644
--- a/main.cpp
+++ b/main.cpp
@@ -20,6 +20,7 @@
 #include "core/squawk.h"
 #include "signalcatcher.h"
 #include "shared/global.h"
+#include "shared/messageinfo.h"
 #include <QtWidgets/QApplication>
 #include <QtCore/QThread>
 #include <QtCore/QObject>
@@ -31,8 +32,10 @@
 int main(int argc, char *argv[])
 {
     qRegisterMetaType<Shared::Message>("Shared::Message");
+    qRegisterMetaType<Shared::MessageInfo>("Shared::MessageInfo");
     qRegisterMetaType<Shared::VCard>("Shared::VCard");
     qRegisterMetaType<std::list<Shared::Message>>("std::list<Shared::Message>");
+    qRegisterMetaType<std::list<Shared::MessageInfo>>("std::list<Shared::MessageInfo>");
     qRegisterMetaType<QSet<QString>>("QSet<QString>");
     qRegisterMetaType<Shared::ConnectionState>("Shared::ConnectionState");
     qRegisterMetaType<Shared::Availability>("Shared::Availability");
@@ -106,8 +109,7 @@ int main(int argc, char *argv[])
     QObject::connect(&w, &Squawk::setRoomAutoJoin, squawk, &Core::Squawk::setRoomAutoJoin);
     QObject::connect(&w, &Squawk::removeRoomRequest, squawk, &Core::Squawk::removeRoomRequest);
     QObject::connect(&w, &Squawk::addRoomRequest, squawk, &Core::Squawk::addRoomRequest);
-    QObject::connect(&w, &Squawk::fileLocalPathRequest, squawk, &Core::Squawk::fileLocalPathRequest);
-    QObject::connect(&w, &Squawk::downloadFileRequest, squawk, &Core::Squawk::downloadFileRequest);
+    QObject::connect(&w, &Squawk::fileDownloadRequest, squawk, &Core::Squawk::fileDownloadRequest);
     QObject::connect(&w, &Squawk::addContactToGroupRequest, squawk, &Core::Squawk::addContactToGroupRequest);
     QObject::connect(&w, &Squawk::removeContactFromGroupRequest, squawk, &Core::Squawk::removeContactFromGroupRequest);
     QObject::connect(&w, &Squawk::renameContactRequest, squawk, &Core::Squawk::renameContactRequest);
@@ -138,11 +140,10 @@ int main(int argc, char *argv[])
     QObject::connect(squawk, &Core::Squawk::addRoomParticipant, &w, &Squawk::addRoomParticipant);
     QObject::connect(squawk, &Core::Squawk::changeRoomParticipant, &w, &Squawk::changeRoomParticipant);
     QObject::connect(squawk, &Core::Squawk::removeRoomParticipant, &w, &Squawk::removeRoomParticipant);
-    QObject::connect(squawk, &Core::Squawk::fileLocalPathResponse, &w, &Squawk::fileLocalPathResponse);
-    QObject::connect(squawk, &Core::Squawk::downloadFileProgress, &w, &Squawk::fileProgress);
-    QObject::connect(squawk, &Core::Squawk::downloadFileError, &w, &Squawk::fileError);
-    QObject::connect(squawk, &Core::Squawk::uploadFileProgress, &w, &Squawk::fileProgress);
-    QObject::connect(squawk, &Core::Squawk::uploadFileError, &w, &Squawk::fileError);
+    QObject::connect(squawk, &Core::Squawk::fileDownloadComplete, &w, &Squawk::fileDownloadComplete);
+    QObject::connect(squawk, &Core::Squawk::fileUploadComplete, &w, &Squawk::fileUploadComplete);
+    QObject::connect(squawk, &Core::Squawk::fileProgress, &w, &Squawk::fileProgress);
+    QObject::connect(squawk, &Core::Squawk::fileError, &w, &Squawk::fileError);
     QObject::connect(squawk, &Core::Squawk::responseVCard, &w, &Squawk::responseVCard);
     QObject::connect(squawk, &Core::Squawk::requestPassword, &w, &Squawk::requestPassword);
     QObject::connect(squawk, &Core::Squawk::ready, &w, &Squawk::readSettings);
diff --git a/shared.h b/shared.h
index 83bcd76..3925ce2 100644
--- a/shared.h
+++ b/shared.h
@@ -25,5 +25,6 @@
 #include "shared/message.h"
 #include "shared/vcard.h"
 #include "shared/global.h"
+#include "shared/messageinfo.h"
 
 #endif // SHARED_H
diff --git a/shared/messageinfo.cpp b/shared/messageinfo.cpp
new file mode 100644
index 0000000..7502a6e
--- /dev/null
+++ b/shared/messageinfo.cpp
@@ -0,0 +1,45 @@
+/*
+ * Squawk messenger. 
+ * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+ * 
+ * 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 "messageinfo.h"
+
+using namespace Shared;
+
+Shared::MessageInfo::MessageInfo():
+    account(),
+    jid(),
+    messageId() {}
+
+Shared::MessageInfo::MessageInfo(const QString& acc, const QString& j, const QString& id):
+    account(acc),
+    jid(j),
+    messageId(id) {}
+
+Shared::MessageInfo::MessageInfo(const Shared::MessageInfo& other):
+    account(other.account),
+    jid(other.jid),
+    messageId(other.messageId) {}
+
+Shared::MessageInfo & Shared::MessageInfo::operator=(const Shared::MessageInfo& other)
+{
+    account = other.account;
+    jid = other.jid;
+    messageId = other.messageId;
+    
+    return *this;
+}
diff --git a/shared/messageinfo.h b/shared/messageinfo.h
new file mode 100644
index 0000000..942d88c
--- /dev/null
+++ b/shared/messageinfo.h
@@ -0,0 +1,43 @@
+/*
+ * Squawk messenger. 
+ * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+ * 
+ * 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/>.
+ */
+
+#ifndef SHARED_MESSAGEINFO_H
+#define SHARED_MESSAGEINFO_H
+
+#include <QString>
+
+namespace Shared {
+
+/**
+ * @todo write docs
+ */
+struct MessageInfo {
+    MessageInfo();
+    MessageInfo(const QString& acc, const QString& j, const QString& id);
+    MessageInfo(const MessageInfo& other);
+    
+    QString account;
+    QString jid;
+    QString messageId;
+    
+    MessageInfo& operator=(const MessageInfo& other);
+};
+
+}
+
+#endif // SHARED_MESSAGEINFO_H
diff --git a/ui/models/element.cpp b/ui/models/element.cpp
index 7b0e70f..d372e8d 100644
--- a/ui/models/element.cpp
+++ b/ui/models/element.cpp
@@ -30,7 +30,7 @@ Models::Element::Element(Type p_type, const Models::Account* acc, const QString&
     feed(new MessageFeed(this))
 {
     connect(feed, &MessageFeed::requestArchive, this, &Element::requestArchive);
-    connect(feed, &MessageFeed::fileLocalPathRequest, this, &Element::fileLocalPathRequest);
+    connect(feed, &MessageFeed::fileDownloadRequest, this, &Element::fileDownloadRequest);
     
     QMap<QString, QVariant>::const_iterator itr = data.find("avatarState");
     if (itr != data.end()) {
@@ -156,8 +156,17 @@ bool Models::Element::isRoom() const
     return type != contact;
 }
 
-void Models::Element::fileProgress(const QString& messageId, qreal value)
+void Models::Element::fileProgress(const QString& messageId, qreal value, bool up)
 {
-    feed->fileProgress(messageId, value);
+    feed->fileProgress(messageId, value, up);
 }
 
+void Models::Element::fileComplete(const QString& messageId, bool up)
+{
+    feed->fileComplete(messageId, up);
+}
+
+void Models::Element::fileError(const QString& messageId, const QString& error, bool up)
+{
+    feed->fileError(messageId, error, up);
+}
diff --git a/ui/models/element.h b/ui/models/element.h
index f537f9b..db6cda1 100644
--- a/ui/models/element.h
+++ b/ui/models/element.h
@@ -43,11 +43,13 @@ public:
     unsigned int getMessagesCount() const;
     void responseArchive(const std::list<Shared::Message> list, bool last);
     bool isRoom() const;
-    void fileProgress(const QString& messageId, qreal value);
+    void fileProgress(const QString& messageId, qreal value, bool up);
+    void fileError(const QString& messageId, const QString& error, bool up);
+    void fileComplete(const QString& messageId, bool up);
     
 signals:
     void requestArchive(const QString& before);
-    void fileLocalPathRequest(const QString& messageId, const QString& url);
+    void fileDownloadRequest(const QString& url);
     
 protected:
     void setJid(const QString& p_jid);
diff --git a/ui/models/messagefeed.cpp b/ui/models/messagefeed.cpp
index 01b1a9d..99951d5 100644
--- a/ui/models/messagefeed.cpp
+++ b/ui/models/messagefeed.cpp
@@ -305,7 +305,7 @@ void Models::MessageFeed::downloadAttachment(const QString& messageId)
         if (progressPair.second) {     //Only to take action if we weren't already downloading it
             Shared::Message* msg = static_cast<Shared::Message*>(ind.internalPointer());
             emit dataChanged(ind, ind);
-            emit fileLocalPathRequest(messageId, msg->getOutOfBandUrl());
+            emit fileDownloadRequest(msg->getOutOfBandUrl());
         } else {
             qDebug() << "Attachment download for message with id" << messageId << "is already in progress, skipping";
         }
@@ -319,16 +319,34 @@ void Models::MessageFeed::uploadAttachment(const QString& messageId)
     qDebug() << "request to upload attachment of the message" << messageId;
 }
 
-void Models::MessageFeed::fileProgress(const QString& messageId, qreal value)
+void Models::MessageFeed::fileProgress(const QString& messageId, qreal value, bool up)
 {
-    Progress::iterator itr = downloads.find(messageId);
-    if (itr != downloads.end()) {
+    Progress* pr = 0;
+    if (up) {
+        pr = &uploads;
+    } else {
+        pr = &downloads;
+    }
+    
+    Progress::iterator itr = pr->find(messageId);
+    if (itr != pr->end()) {
         itr->second = value;
         QModelIndex ind = modelIndexById(messageId);
         emit dataChanged(ind, ind);
     }
 }
 
+void Models::MessageFeed::fileComplete(const QString& messageId, bool up)
+{
+    //TODO
+}
+
+void Models::MessageFeed::fileError(const QString& messageId, const QString& error, bool up)
+{
+    //TODO
+}
+
+
 QModelIndex Models::MessageFeed::modelIndexById(const QString& id) const
 {
     StorageById::const_iterator itr = indexById.find(id);
diff --git a/ui/models/messagefeed.h b/ui/models/messagefeed.h
index e8995d5..f4c2c28 100644
--- a/ui/models/messagefeed.h
+++ b/ui/models/messagefeed.h
@@ -59,12 +59,14 @@ public:
     void uploadAttachment(const QString& messageId);
     
     unsigned int unreadMessagesCount() const;
-    void fileProgress(const QString& messageId, qreal value);
+    void fileProgress(const QString& messageId, qreal value, bool up);
+    void fileError(const QString& messageId, const QString& error, bool up);
+    void fileComplete(const QString& messageId, bool up);
     
 signals:
     void requestArchive(const QString& before);
     void requestStateChange(bool requesting);
-    void fileLocalPathRequest(const QString& messageId, const QString& url);
+    void fileDownloadRequest(const QString& url);
     
 protected:
     bool sentByMe(const Shared::Message& msg) const;
@@ -141,6 +143,8 @@ enum AttachmentType {
     local,
     downloading,
     uploading,
+    errorDownload,
+    errorUpload,
     ready
 };
 
diff --git a/ui/models/roster.cpp b/ui/models/roster.cpp
index a7bc74e..5e3a1ac 100644
--- a/ui/models/roster.cpp
+++ b/ui/models/roster.cpp
@@ -27,8 +27,7 @@ Models::Roster::Roster(QObject* parent):
     root(new Item(Item::root, {{"name", "root"}})),
     accounts(),
     groups(),
-    contacts(),
-    requestedFiles()
+    contacts()
 {
     connect(accountsModel, &Accounts::dataChanged, this, &Roster::onAccountDataChanged);
     connect(root, &Item::childChanged, this, &Roster::onChildChanged);
@@ -448,7 +447,7 @@ void Models::Roster::addContact(const QString& account, const QString& jid, cons
         if (itr == contacts.end()) {
             contact = new Contact(acc, jid, data);
             connect(contact, &Contact::requestArchive, this, &Roster::onElementRequestArchive);
-            connect(contact, &Contact::fileLocalPathRequest, this, &Roster::onElementFileLocalPathRequest);
+            connect(contact, &Contact::fileDownloadRequest, this, &Roster::fileDownloadRequest);
             contacts.insert(std::make_pair(id, contact));
         } else {
             contact = itr->second;
@@ -534,35 +533,19 @@ void Models::Roster::removeGroup(const QString& account, const QString& name)
 
 void Models::Roster::changeContact(const QString& account, const QString& jid, const QMap<QString, QVariant>& data)
 {
-    ElId id(account, jid);
-    std::map<ElId, Contact*>::iterator cItr = contacts.find(id);
-    
-    if (cItr != contacts.end()) {
+    Element* el = getElement({account, jid});
+    if (el != NULL) {
         for (QMap<QString, QVariant>::const_iterator itr = data.begin(), end = data.end(); itr != end; ++itr) {
-            cItr->second->update(itr.key(), itr.value());
-        }
-    } else {
-        std::map<ElId, Room*>::iterator rItr = rooms.find(id);
-        if (rItr != rooms.end()) {
-            for (QMap<QString, QVariant>::const_iterator itr = data.begin(), end = data.end(); itr != end; ++itr) {
-                rItr->second->update(itr.key(), itr.value());
-            }
+            el->update(itr.key(), itr.value());
         }
     }
 }
 
 void Models::Roster::changeMessage(const QString& account, const QString& jid, const QString& id, const QMap<QString, QVariant>& data)
 {
-    ElId elid(account, jid);
-    std::map<ElId, Contact*>::iterator cItr = contacts.find(elid);
-    
-    if (cItr != contacts.end()) {
-        cItr->second->changeMessage(id, data);
-    } else {
-        std::map<ElId, Room*>::iterator rItr = rooms.find(elid);
-        if (rItr != rooms.end()) {
-            rItr->second->changeMessage(id, data);
-        }
+    Element* el = getElement({account, jid});
+    if (el != NULL) {
+        el->changeMessage(id, data);
     }
 }
 
@@ -626,7 +609,6 @@ void Models::Roster::removeContact(const QString& account, const QString& jid, c
     } else {
         delete ref;
     }
-    
     if (gr->childCount() == 0) {
         removeGroup(account, group);
     }
@@ -707,15 +689,9 @@ void Models::Roster::removePresence(const QString& account, const QString& jid,
 
 void Models::Roster::addMessage(const QString& account, const Shared::Message& data)
 {
-    ElId id(account, data.getPenPalJid());
-    std::map<ElId, Contact*>::iterator itr = contacts.find(id);
-    if (itr != contacts.end()) {
-        itr->second->addMessage(data);
-    } else {
-        std::map<ElId, Room*>::const_iterator rItr = rooms.find(id);
-        if (rItr != rooms.end()) {
-            rItr->second->addMessage(data);
-        }
+    Element* el = getElement({account, data.getPenPalJid()});
+    if (el != NULL) {
+        el->addMessage(data);
     }
 }
 
@@ -808,7 +784,7 @@ void Models::Roster::addRoom(const QString& account, const QString jid, const QM
     
     Room* room = new Room(acc, jid, data);
     connect(room, &Contact::requestArchive, this, &Roster::onElementRequestArchive);
-    connect(room, &Contact::fileLocalPathRequest, this, &Roster::onElementFileLocalPathRequest);
+    connect(room, &Contact::fileDownloadRequest, this, &Roster::fileDownloadRequest);
     rooms.insert(std::make_pair(id, room));
     acc->appendChild(room);
 }
@@ -971,51 +947,55 @@ void Models::Roster::onElementRequestArchive(const QString& before)
 void Models::Roster::responseArchive(const QString& account, const QString& jid, const std::list<Shared::Message>& list, bool last)
 {
     ElId id(account, jid);
-    std::map<ElId, Contact*>::iterator itr = contacts.find(id);
-    if (itr != contacts.end()) {
-        itr->second->responseArchive(list, last);
+    Element* el = getElement(id);
+    if (el != NULL) {
+        el->responseArchive(list, last);
+    }
+}
+
+void Models::Roster::fileProgress(const std::list<Shared::MessageInfo>& msgs, qreal value, bool up)
+{
+    for (const Shared::MessageInfo& info : msgs) {
+        Element* el = getElement({info.account, info.jid});
+        if (el != NULL) {
+            el->fileProgress(info.messageId, value, up);
+        }
+    }
+}
+
+void Models::Roster::fileComplete(const std::list<Shared::MessageInfo>& msgs, bool up)
+{
+    for (const Shared::MessageInfo& info : msgs) {
+        Element* el = getElement({info.account, info.jid});
+        if (el != NULL) {
+            el->fileComplete(info.messageId, up);
+        }
+    }
+}
+
+void Models::Roster::fileError(const std::list<Shared::MessageInfo>& msgs, const QString& err, bool up)
+{
+    for (const Shared::MessageInfo& info : msgs) {
+        Element* el = getElement({info.account, info.jid});
+        if (el != NULL) {
+            el->fileError(info.messageId, err, up);
+        }
+    }
+}
+
+Models::Element * Models::Roster::getElement(const Models::Roster::ElId& id)
+{
+    std::map<ElId, Contact*>::iterator cItr = contacts.find(id);
+    
+    if (cItr != contacts.end()) {
+        return cItr->second;
     } else {
-        std::map<ElId, Room*>::const_iterator rItr = rooms.find(id);
+        std::map<ElId, Room*>::iterator rItr = rooms.find(id);
         if (rItr != rooms.end()) {
-            rItr->second->responseArchive(list, last);
-        }
-    }
-}
-
-void Models::Roster::onElementFileLocalPathRequest(const QString& messageId, const QString& url)
-{
-    Element* el = static_cast<Element*>(sender());
-    std::map<QString, std::set<Models::Roster::ElId>>::iterator itr = requestedFiles.find(messageId);
-    bool created = false;
-    if (itr == requestedFiles.end()) {
-        itr = requestedFiles.insert(std::make_pair(messageId, std::set<Models::Roster::ElId>())).first;
-        created = true;
-    }
-    itr->second.insert(Models::Roster::ElId(el->getAccountName(), el->getJid()));
-    if (created) {
-        emit fileLocalPathRequest(messageId, url);
-    }
-}
-
-void Models::Roster::fileProgress(const QString& messageId, qreal value)
-{
-    std::map<QString, std::set<Models::Roster::ElId>>::const_iterator itr = requestedFiles.find(messageId);
-    if (itr == requestedFiles.end()) {
-        qDebug() << "fileProgress in UI but there is nobody waiting for that id:" << messageId << ", skipping";
-        return;
-    } else {
-        const std::set<Models::Roster::ElId>& convs = itr->second;
-        for (const Models::Roster::ElId& id : convs) {
-            std::map<ElId, Contact*>::const_iterator cItr = contacts.find(id);
-            if (cItr != contacts.end()) {
-                cItr->second->fileProgress(messageId, value);
-            } else {
-                std::map<ElId, Room*>::const_iterator rItr = rooms.find(id);
-                if (rItr != rooms.end()) {
-                    rItr->second->fileProgress(messageId, value);
-                }
-            }
+            return rItr->second;
         }
     }
+    
+    return NULL;
 }
 
diff --git a/ui/models/roster.h b/ui/models/roster.h
index 1f398d8..775d8de 100644
--- a/ui/models/roster.h
+++ b/ui/models/roster.h
@@ -26,6 +26,7 @@
 
 #include "shared/message.h"
 #include "shared/global.h"
+#include "shared/messageinfo.h"
 #include "accounts.h"
 #include "item.h"
 #include "account.h"
@@ -81,21 +82,19 @@ public:
     QModelIndex getAccountIndex(const QString& name);
     QModelIndex getGroupIndex(const QString& account, const QString& name);
     void responseArchive(const QString& account, const QString& jid, const std::list<Shared::Message>& list, bool last);
-    void fileProgress(const QString& messageId, qreal value);
+    
+    void fileProgress(const std::list<Shared::MessageInfo>& msgs, qreal value, bool up);
+    void fileError(const std::list<Shared::MessageInfo>& msgs, const QString& err, bool up);
+    void fileComplete(const std::list<Shared::MessageInfo>& msgs, bool up);
     
     Accounts* accountsModel;
     
 signals:
     void requestArchive(const QString& account, const QString& jid, const QString& before);
-    void fileLocalPathRequest(const QString& messageId, const QString& url);
+    void fileDownloadRequest(const QString& url);
     
 private:
-    Item* root;
-    std::map<QString, Account*> accounts;
-    std::map<ElId, Group*> groups;
-    std::map<ElId, Contact*> contacts;
-    std::map<ElId, Room*> rooms;
-    std::map<QString, std::set<Models::Roster::ElId>> requestedFiles;
+    Element* getElement(const ElId& id);
     
 private slots:
     void onAccountDataChanged(const QModelIndex& tl, const QModelIndex& br, const QVector<int>& roles);
@@ -107,7 +106,13 @@ private slots:
     void onChildIsAboutToBeMoved(Item* source, int first, int last, Item* destination, int newIndex);
     void onChildMoved();
     void onElementRequestArchive(const QString& before);
-    void onElementFileLocalPathRequest(const QString& messageId, const QString& url);
+    
+private:
+    Item* root;
+    std::map<QString, Account*> accounts;
+    std::map<ElId, Group*> groups;
+    std::map<ElId, Contact*> contacts;
+    std::map<ElId, Room*> rooms;
     
 public:
     class ElId {
diff --git a/ui/squawk.cpp b/ui/squawk.cpp
index 6b8416f..22c1051 100644
--- a/ui/squawk.cpp
+++ b/ui/squawk.cpp
@@ -29,7 +29,6 @@ Squawk::Squawk(QWidget *parent) :
     conversations(),
     contextMenu(new QMenu()),
     dbus("org.freedesktop.Notifications", "/org/freedesktop/Notifications", "org.freedesktop.Notifications", QDBusConnection::sessionBus()),
-    requestedFiles(),
     vCards(),
     requestedAccountsForPasswords(),
     prompt(0),
@@ -62,8 +61,8 @@ Squawk::Squawk(QWidget *parent) :
     connect(m_ui->roster->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &Squawk::onRosterSelectionChanged);
     
     connect(rosterModel.accountsModel, &Models::Accounts::sizeChanged, this, &Squawk::onAccountsSizeChanged);
-    connect(&rosterModel, &Models::Roster::requestArchive, this, &Squawk::onConversationRequestArchive);
-    connect(&rosterModel, &Models::Roster::fileLocalPathRequest, this, &Squawk::fileLocalPathRequest);
+    connect(&rosterModel, &Models::Roster::requestArchive, this, &Squawk::onRequestArchive);
+    connect(&rosterModel, &Models::Roster::fileDownloadRequest, this, &Squawk::fileDownloadRequest);
     connect(contextMenu, &QMenu::aboutToHide, this, &Squawk::onContextAboutToHide);
     //m_ui->mainToolBar->addWidget(m_ui->comboBox);
     
@@ -389,86 +388,24 @@ void Squawk::onConversationClosed(QObject* parent)
     }
 }
 
-void Squawk::onConversationDownloadFile(const QString& messageId, const QString& url)
+void Squawk::fileProgress(const std::list<Shared::MessageInfo> msgs, qreal value, bool up)
 {
-    Conversation* conv = static_cast<Conversation*>(sender());
-    std::map<QString, std::set<Models::Roster::ElId>>::iterator itr = requestedFiles.find(messageId);
-    bool created = false;
-    if (itr == requestedFiles.end()) {
-        itr = requestedFiles.insert(std::make_pair(messageId, std::set<Models::Roster::ElId>())).first;
-        created = true;
-    }
-    itr->second.insert(Models::Roster::ElId(conv->getAccount(), conv->getJid()));
-    if (created) {
-        emit downloadFileRequest(messageId, url);
-    }
+    rosterModel.fileProgress(msgs, value, up);
 }
 
-void Squawk::fileProgress(const QString& messageId, qreal value)
+void Squawk::fileDownloadComplete(const std::list<Shared::MessageInfo> msgs, const QString& path)
 {
-    rosterModel.fileProgress(messageId, value);
+    rosterModel.fileComplete(msgs, false);
 }
 
-void Squawk::fileError(const QString& messageId, const QString& error)
+void Squawk::fileError(const std::list<Shared::MessageInfo> msgs, const QString& error, bool up)
 {
-    std::map<QString, std::set<Models::Roster::ElId>>::const_iterator itr = requestedFiles.find(messageId);
-    if (itr == requestedFiles.end()) {
-        qDebug() << "fileError in UI Squawk but there is nobody waiting for that id" << messageId << ", skipping";
-        return;
-    } else {
-        const std::set<Models::Roster::ElId>& convs = itr->second;
-        for (std::set<Models::Roster::ElId>::const_iterator cItr = convs.begin(), cEnd = convs.end(); cItr != cEnd; ++cItr) {
-            const Models::Roster::ElId& id = *cItr;
-            Conversations::const_iterator c = conversations.find(id);
-            if (c != conversations.end()) {
-                c->second->fileError(messageId, error);
-            }
-            if (currentConversation != 0 && currentConversation->getId() == id) {
-                currentConversation->fileError(messageId, error);
-            }
-        }
-        requestedFiles.erase(itr);
-    }
+    rosterModel.fileError(msgs, error, up);
 }
 
-
-//TODO! Need to make it look like a standard message change event!
-void Squawk::fileLocalPathResponse(const QString& messageId, const QString& path)
+void Squawk::fileUploadComplete(const std::list<Shared::MessageInfo> msgs, const QString& path)
 {
-    std::map<QString, std::set<Models::Roster::ElId>>::const_iterator itr = requestedFiles.find(messageId);
-    if (itr == requestedFiles.end()) {
-        qDebug() << "fileLocalPathResponse in UI Squawk but there is nobody waiting for that path, skipping";
-        return;
-    } else {
-        const std::set<Models::Roster::ElId>& convs = itr->second;
-        for (std::set<Models::Roster::ElId>::const_iterator cItr = convs.begin(), cEnd = convs.end(); cItr != cEnd; ++cItr) {
-            const Models::Roster::ElId& id = *cItr;
-            Conversations::const_iterator c = conversations.find(id);
-            if (c != conversations.end()) {
-                c->second->responseLocalFile(messageId, path);
-            }
-            if (currentConversation != 0 && currentConversation->getId() == id) {
-                currentConversation->responseLocalFile(messageId, path);
-            }
-        }
-        
-        requestedFiles.erase(itr);
-    }
-}
-
-void Squawk::onConversationRequestLocalFile(const QString& messageId, const QString& url)
-{
-    Conversation* conv = static_cast<Conversation*>(sender());
-    std::map<QString, std::set<Models::Roster::ElId>>::iterator itr = requestedFiles.find(messageId);
-    bool created = false;
-    if (itr == requestedFiles.end()) {
-        itr = requestedFiles.insert(std::make_pair(messageId, std::set<Models::Roster::ElId>())).first;
-        created = true;
-    }
-    itr->second.insert(Models::Roster::ElId(conv->getAccount(), conv->getJid()));
-    if (created) {
-        emit fileLocalPathRequest(messageId, url);
-    }
+    rosterModel.fileComplete(msgs, true);
 }
 
 void Squawk::accountMessage(const QString& account, const Shared::Message& data)
@@ -565,23 +502,13 @@ void Squawk::notify(const QString& account, const Shared::Message& msg)
 void Squawk::onConversationMessage(const Shared::Message& msg)
 {
     Conversation* conv = static_cast<Conversation*>(sender());
-    Models::Roster::ElId id = conv->getId();
+    QString acc = conv->getAccount();
     
-    rosterModel.addMessage(conv->getAccount(), msg);
-    
-    QString ap = msg.getAttachPath();
-    QString oob = msg.getOutOfBandUrl();
-    if ((ap.size() > 0 && oob.size() == 0) || (ap.size() == 0 && oob.size() > 0)) {
-        std::map<QString, std::set<Models::Roster::ElId>>::iterator itr = requestedFiles.insert(std::make_pair(msg.getId(), std::set<Models::Roster::ElId>())).first;
-        itr->second.insert(id);
-        
-        //TODO can also start downloading here if someone attached the message with the remote url
-    }
-    
-    emit sendMessage(conv->getAccount(), msg);
+    rosterModel.addMessage(acc, msg);
+    emit sendMessage(acc, msg);
 }
 
-void Squawk::onConversationRequestArchive(const QString& account, const QString& jid, const QString& before)
+void Squawk::onRequestArchive(const QString& account, const QString& jid, const QString& before)
 {
     emit requestArchive(account, jid, 20, before); //TODO amount as a settings value
 }
@@ -1029,8 +956,6 @@ void Squawk::subscribeConversation(Conversation* conv)
 {
     connect(conv, &Conversation::destroyed, this, &Squawk::onConversationClosed);
     connect(conv, &Conversation::sendMessage, this, &Squawk::onConversationMessage);
-    connect(conv, &Conversation::requestLocalFile, this, &Squawk::onConversationRequestLocalFile);
-    connect(conv, &Conversation::downloadFile, this, &Squawk::onConversationDownloadFile);
 }
 
 void Squawk::onRosterSelectionChanged(const QModelIndex& current, const QModelIndex& previous)
diff --git a/ui/squawk.h b/ui/squawk.h
index 26f7753..ada13fc 100644
--- a/ui/squawk.h
+++ b/ui/squawk.h
@@ -75,8 +75,7 @@ signals:
     void setRoomAutoJoin(const QString& account, const QString& jid, bool joined);
     void addRoomRequest(const QString& account, const QString& jid, const QString& nick, const QString& password, bool autoJoin);
     void removeRoomRequest(const QString& account, const QString& jid);
-    void fileLocalPathRequest(const QString& messageId, const QString& url);
-    void downloadFileRequest(const QString& messageId, const QString& url);
+    void fileDownloadRequest(const QString& url);
     void requestVCard(const QString& account, const QString& jid);
     void uploadVCard(const QString& account, const Shared::VCard& card);
     void responsePassword(const QString& account, const QString& password);
@@ -103,9 +102,10 @@ public slots:
     void addRoomParticipant(const QString& account, const QString& jid, const QString& name, const QMap<QString, QVariant>& data);
     void changeRoomParticipant(const QString& account, const QString& jid, const QString& name, const QMap<QString, QVariant>& data);
     void removeRoomParticipant(const QString& account, const QString& jid, const QString& name);
-    void fileLocalPathResponse(const QString& messageId, const QString& path);
-    void fileError(const QString& messageId, const QString& error);
-    void fileProgress(const QString& messageId, qreal value);
+    void fileError(const std::list<Shared::MessageInfo> msgs, const QString& error, bool up);
+    void fileProgress(const std::list<Shared::MessageInfo> msgs, qreal value, bool up);
+    void fileDownloadComplete(const std::list<Shared::MessageInfo> msgs, const QString& path);
+    void fileUploadComplete(const std::list<Shared::MessageInfo> msgs, const QString& path);
     void responseVCard(const QString& jid, const Shared::VCard& card);
     void changeMessage(const QString& account, const QString& jid, const QString& id, const QMap<QString, QVariant>& data);
     void requestPassword(const QString& account);
@@ -119,7 +119,6 @@ private:
     Conversations conversations;
     QMenu* contextMenu;
     QDBusInterface dbus;
-    std::map<QString, std::set<Models::Roster::ElId>> requestedFiles;
     std::map<QString, VCard*> vCards;
     std::deque<QString> requestedAccountsForPasswords;
     QInputDialog* prompt;
@@ -146,10 +145,8 @@ private slots:
     void onComboboxActivated(int index);
     void onRosterItemDoubleClicked(const QModelIndex& item);
     void onConversationMessage(const Shared::Message& msg);
-    void onConversationRequestArchive(const QString& account, const QString& jid, const QString& before);
+    void onRequestArchive(const QString& account, const QString& jid, const QString& before);
     void onRosterContextMenu(const QPoint& point);
-    void onConversationRequestLocalFile(const QString& messageId, const QString& url);
-    void onConversationDownloadFile(const QString& messageId, const QString& url);
     void onItemCollepsed(const QModelIndex& index);
     void onPasswordPromptAccepted();
     void onPasswordPromptRejected();

From 48e498be251760e34def4f42a28940136c72ee80 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Tue, 20 Apr 2021 00:49:24 +0300
Subject: [PATCH 098/281] some debug, message changing in messageFeed

---
 core/networkaccess.cpp        |  4 +--
 shared/message.cpp            | 20 +++++++++++
 shared/message.h              | 12 +++++++
 ui/models/element.cpp         |  2 +-
 ui/models/messagefeed.cpp     | 68 +++++++++++++++++++++++++++++++----
 ui/models/messagefeed.h       |  3 +-
 ui/squawk.cpp                 | 23 +-----------
 ui/utils/comboboxdelegate.cpp |  2 +-
 ui/utils/messagedelegate.cpp  |  6 +++-
 ui/utils/messagedelegate.h    |  2 ++
 10 files changed, 107 insertions(+), 35 deletions(-)

diff --git a/core/networkaccess.cpp b/core/networkaccess.cpp
index 60d7cb9..f178068 100644
--- a/core/networkaccess.cpp
+++ b/core/networkaccess.cpp
@@ -479,11 +479,11 @@ QString Core::NetworkAccess::checkFileName(const QString& name, const QString& p
         suffix += "." + (*sItr);
     }
     QString postfix("");
-    QFileInfo proposedName(path + realName + suffix);
+    QFileInfo proposedName(path + "/" + realName + suffix);
     int counter = 0;
     while (proposedName.exists()) {
         QString count = QString("(") + std::to_string(++counter).c_str() + ")";
-        proposedName = QFileInfo(path + realName + count + suffix);
+        proposedName = QFileInfo(path + "/" + realName + count + suffix);
     }
     
     return proposedName.absoluteFilePath();
diff --git a/shared/message.cpp b/shared/message.cpp
index 3f23d59..6728bbe 100644
--- a/shared/message.cpp
+++ b/shared/message.cpp
@@ -456,3 +456,23 @@ void Shared::Message::setAttachPath(const QString& path)
 {
     attachPath = path;
 }
+
+Shared::Message::Change::Change(const QMap<QString, QVariant>& _data):
+    data(_data),
+    idModified(false) {}
+    
+void Shared::Message::Change::operator()(Shared::Message& msg)
+{
+    idModified = msg.change(data);
+}
+
+void Shared::Message::Change::operator()(Shared::Message* msg)
+{
+    idModified = msg->change(data);
+}
+
+bool Shared::Message::Change::hasIdBeenModified() const
+{
+    return idModified;
+}
+
diff --git a/shared/message.h b/shared/message.h
index 2082101..c2766b3 100644
--- a/shared/message.h
+++ b/shared/message.h
@@ -49,6 +49,18 @@ public:
     static const State StateHighest = State::error;
     static const State StateLowest = State::pending;
     
+    struct Change       //change functor, stores in idModified if ID has been modified during change
+    {
+        Change(const QMap<QString, QVariant>& _data);
+        void operator() (Message& msg);
+        void operator() (Message* msg);
+        bool hasIdBeenModified() const;
+        
+    private:
+        const QMap<QString, QVariant>& data;
+        bool idModified;
+    };
+    
     Message(Type p_type);
     Message();
     
diff --git a/ui/models/element.cpp b/ui/models/element.cpp
index d372e8d..659a30f 100644
--- a/ui/models/element.cpp
+++ b/ui/models/element.cpp
@@ -143,7 +143,7 @@ void Models::Element::addMessage(const Shared::Message& data)
 
 void Models::Element::changeMessage(const QString& id, const QMap<QString, QVariant>& data)
 {
-    
+    feed->changeMessage(id, data);
 }
 
 void Models::Element::responseArchive(const std::list<Shared::Message> list, bool last)
diff --git a/ui/models/messagefeed.cpp b/ui/models/messagefeed.cpp
index 99951d5..7c82900 100644
--- a/ui/models/messagefeed.cpp
+++ b/ui/models/messagefeed.cpp
@@ -76,8 +76,44 @@ void Models::MessageFeed::addMessage(const Shared::Message& msg)
     endInsertRows();
 }
 
-void Models::MessageFeed::changeMessage(const QString& id, const Shared::Message& msg)
+void Models::MessageFeed::changeMessage(const QString& id, const QMap<QString, QVariant>& data)
 {
+    StorageById::iterator itr = indexById.find(id);
+    if (itr == indexById.end()) {
+        qDebug() << "received a command to change a message, but the message couldn't be found, skipping";
+        return;
+    }
+    
+    Shared::Message* msg = *itr;
+    QModelIndex index = modelIndexByTime(id, msg->getTime());
+    Shared::Message::Change functor(data);
+    bool success = indexById.modify(itr, functor);
+    if (!success) {
+        qDebug() << "received a command to change a message, but something went wrong modifying message in the feed, throwing error";
+        throw 872;
+    }
+    
+    if (functor.hasIdBeenModified()) {
+        
+    }
+    
+    //change message is a final event in download/upload event train
+    //only after changeMessage we can consider the download is done
+    Progress::const_iterator dItr = downloads.find(id);
+    if (dItr != downloads.end()) {
+        if (dItr->second == 1) {
+            downloads.erase(dItr);
+        }
+    } else {
+        dItr = uploads.find(id);
+        if (dItr != uploads.end()) {
+            if (dItr->second == 1) {
+                uploads.erase(dItr);
+            }
+        }
+    }
+    
+    emit dataChanged(index, index);
 }
 
 void Models::MessageFeed::removeMessage(const QString& id)
@@ -338,7 +374,7 @@ void Models::MessageFeed::fileProgress(const QString& messageId, qreal value, bo
 
 void Models::MessageFeed::fileComplete(const QString& messageId, bool up)
 {
-    //TODO
+    fileProgress(messageId, 1, up);
 }
 
 void Models::MessageFeed::fileError(const QString& messageId, const QString& error, bool up)
@@ -352,11 +388,29 @@ QModelIndex Models::MessageFeed::modelIndexById(const QString& id) const
     StorageById::const_iterator itr = indexById.find(id);
     if (itr != indexById.end()) {
         Shared::Message* msg = *itr;
-        StorageByTime::const_iterator tItr = indexByTime.upper_bound(msg->getTime());
-        int position = indexByTime.rank(tItr);
-        return createIndex(position, 0, msg);
-    } else {
-        return QModelIndex();
+        return modelIndexByTime(id, msg->getTime());
     }
+    
+    return QModelIndex();
 }
 
+QModelIndex Models::MessageFeed::modelIndexByTime(const QString& id, const QDateTime& time) const
+{
+    StorageByTime::const_iterator tItr = indexByTime.upper_bound(time);
+    StorageByTime::const_iterator tBeg = indexByTime.begin();
+    bool found = false;
+    while (tItr != tBeg) {
+        if (id == (*tItr)->getId()) {
+            found = true;
+            break;
+        }
+        --tItr;
+    }
+    
+    if (found) {
+        int position = indexByTime.rank(tItr);
+        return createIndex(position, 0, *tItr);
+    }
+    
+    return QModelIndex();
+}
diff --git a/ui/models/messagefeed.h b/ui/models/messagefeed.h
index f4c2c28..e8fb712 100644
--- a/ui/models/messagefeed.h
+++ b/ui/models/messagefeed.h
@@ -44,7 +44,7 @@ public:
     ~MessageFeed();
     
     void addMessage(const Shared::Message& msg);
-    void changeMessage(const QString& id, const Shared::Message& msg);
+    void changeMessage(const QString& id, const QMap<QString, QVariant>& data);
     void removeMessage(const QString& id);
     
     QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const override;
@@ -72,6 +72,7 @@ protected:
     bool sentByMe(const Shared::Message& msg) const;
     Attachment fillAttach(const Shared::Message& msg) const;
     QModelIndex modelIndexById(const QString& id) const;
+    QModelIndex modelIndexByTime(const QString& id, const QDateTime& time) const;
     
 public:
     enum MessageRoles {
diff --git a/ui/squawk.cpp b/ui/squawk.cpp
index 22c1051..05f8c87 100644
--- a/ui/squawk.cpp
+++ b/ui/squawk.cpp
@@ -444,28 +444,7 @@ void Squawk::accountMessage(const QString& account, const Shared::Message& data)
 
 void Squawk::changeMessage(const QString& account, const QString& jid, const QString& id, const QMap<QString, QVariant>& data)
 {
-    Models::Roster::ElId eid({account, jid});
-    bool found = false;
-    
-    if (currentConversation != 0 && currentConversation->getId() == eid) {
-        currentConversation->changeMessage(id, data);
-        QApplication::alert(this);
-        found = true;
-    }
-    
-    Conversations::iterator itr = conversations.find(eid);
-    if (itr != conversations.end()) {
-        Conversation* conv = itr->second;
-        conv->changeMessage(id, data);
-        if (!found && conv->isMinimized()) {
-            rosterModel.changeMessage(account, jid, id, data);
-        }
-        found = true;
-    } 
-    
-    if (!found) {
-        rosterModel.changeMessage(account, jid, id, data);
-    }
+    rosterModel.changeMessage(account, jid, id, data);
 }
 
 void Squawk::notify(const QString& account, const Shared::Message& msg)
diff --git a/ui/utils/comboboxdelegate.cpp b/ui/utils/comboboxdelegate.cpp
index 7153405..4c96c79 100644
--- a/ui/utils/comboboxdelegate.cpp
+++ b/ui/utils/comboboxdelegate.cpp
@@ -37,7 +37,7 @@ QWidget* ComboboxDelegate::createEditor(QWidget *parent, const QStyleOptionViewI
 {
     QComboBox *cb = new QComboBox(parent);
     
-    for (const std::pair<QString, QIcon> pair : entries) {
+    for (const std::pair<QString, QIcon>& pair : entries) {
         cb->addItem(pair.second, pair.first);
     }
     
diff --git a/ui/utils/messagedelegate.cpp b/ui/utils/messagedelegate.cpp
index ff5b1fb..83eaa42 100644
--- a/ui/utils/messagedelegate.cpp
+++ b/ui/utils/messagedelegate.cpp
@@ -179,6 +179,9 @@ QSize MessageDelegate::sizeHint(const QStyleOptionViewItem& option, const QModel
             break;
         case Models::ready:
             break;
+        case Models::errorDownload:
+        case Models::errorUpload:
+            break;
     }
     
     messageSize.rheight() += nickMetrics.lineSpacing();
@@ -306,10 +309,11 @@ QProgressBar * MessageDelegate::getBar(const Models::FeedItem& data) const
     
     if (result == 0) {
         result = new QProgressBar();
+        result->setRange(0, 100);
         bars->insert(std::make_pair(data.id, result));
     }
     
-    result->setValue(data.attach.progress);
+    result->setValue(data.attach.progress * 100);
     
     return result;
 }
diff --git a/ui/utils/messagedelegate.h b/ui/utils/messagedelegate.h
index 42c8ed5..56f9ebf 100644
--- a/ui/utils/messagedelegate.h
+++ b/ui/utils/messagedelegate.h
@@ -85,6 +85,8 @@ private:
     std::map<QString, QProgressBar*>* bars;
     std::set<QString>* idsToKeep;
     bool clearingWidgets;
+    
+    
 };
 
 #endif // MESSAGEDELEGATE_H

From 0e937199b0187f97ba178309c867db39509cfe5a Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Wed, 21 Apr 2021 00:56:47 +0300
Subject: [PATCH 099/281] debug and actual first way to display pictures in
 messageFeed

---
 shared/global.cpp            | 31 ++++++++++++++++++-
 shared/global.h              | 21 +++++++++++++
 ui/models/messagefeed.cpp    |  2 +-
 ui/utils/feedview.cpp        |  7 +++++
 ui/utils/feedview.h          |  1 +
 ui/utils/messagedelegate.cpp | 59 +++++++++++++++++++++++++++++++++---
 ui/utils/messagedelegate.h   |  5 ++-
 7 files changed, 118 insertions(+), 8 deletions(-)

diff --git a/shared/global.cpp b/shared/global.cpp
index a6b7b60..981dd60 100644
--- a/shared/global.cpp
+++ b/shared/global.cpp
@@ -81,7 +81,8 @@ Shared::Global::Global():
     }),
     pluginSupport({
         {"KWallet", false}
-    })
+    }),
+    fileCache()
 {
     if (instance != 0) {
         throw 551;
@@ -90,6 +91,34 @@ Shared::Global::Global():
     instance = this;
 }
 
+Shared::Global::FileInfo Shared::Global::getFileInfo(const QString& path)
+{
+    std::map<QString, FileInfo>::const_iterator itr = instance->fileCache.find(path);
+    if (itr == instance->fileCache.end()) {
+        QMimeDatabase db;
+        QMimeType type = db.mimeTypeForFile(path);
+        QStringList parts = type.name().split("/");
+        QString big = parts.front();
+        QFileInfo info(path);
+        
+        FileInfo::Preview p = FileInfo::Preview::none;
+        QSize size;
+        if (big == "image") {
+            if (parts.back() == "gif") {
+                //TODO need to consider GIF as a movie
+            }
+            p = FileInfo::Preview::picture;
+            QImage img(path);
+            size = img.size();
+        }
+        
+        itr = instance->fileCache.insert(std::make_pair(path, FileInfo({info.fileName(), size, type, p}))).first;
+    } 
+    
+    return itr->second;
+}
+
+
 Shared::Global * Shared::Global::getInstance()
 {
     return instance;
diff --git a/shared/global.h b/shared/global.h
index 54e1584..bb9e5b7 100644
--- a/shared/global.h
+++ b/shared/global.h
@@ -29,6 +29,11 @@
 
 #include <QCoreApplication>
 #include <QDebug>
+#include <QMimeType>
+#include <QMimeDatabase>
+#include <QFileInfo>
+#include <QImage>
+#include <QSize>
 
 namespace Shared {
     
@@ -36,6 +41,19 @@ namespace Shared {
         Q_DECLARE_TR_FUNCTIONS(Global)
         
     public:
+        struct FileInfo {
+            enum class Preview {
+                none,
+                picture,
+                movie
+            };
+            
+            QString name;
+            QSize size;
+            QMimeType mime;
+            Preview preview;
+        };
+        
         Global();
         
         static Global* getInstance();
@@ -64,6 +82,8 @@ namespace Shared {
         
         static const std::set<QString> supportedImagesExts;
         
+        static FileInfo getFileInfo(const QString& path);
+        
         template<typename T>
         static T fromInt(int src);
         
@@ -87,6 +107,7 @@ namespace Shared {
         static Global* instance;
         
         std::map<QString, bool> pluginSupport;
+        std::map<QString, FileInfo> fileCache;
     };
 }
 
diff --git a/ui/models/messagefeed.cpp b/ui/models/messagefeed.cpp
index 7c82900..2345816 100644
--- a/ui/models/messagefeed.cpp
+++ b/ui/models/messagefeed.cpp
@@ -407,7 +407,7 @@ QModelIndex Models::MessageFeed::modelIndexByTime(const QString& id, const QDate
         --tItr;
     }
     
-    if (found) {
+    if (found || id == (*tItr)->getId()) {
         int position = indexByTime.rank(tItr);
         return createIndex(position, 0, *tItr);
     }
diff --git a/ui/utils/feedview.cpp b/ui/utils/feedview.cpp
index 15f6fb3..7155d95 100644
--- a/ui/utils/feedview.cpp
+++ b/ui/utils/feedview.cpp
@@ -113,6 +113,13 @@ void FeedView::rowsInserted(const QModelIndex& parent, int start, int end)
     QAbstractItemView::rowsInserted(parent, start, end);
 }
 
+void FeedView::dataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight, const QVector<int>& roles)
+{
+    //TODO make optimisations! There are some roles but not all that change geometry!
+    updateGeometries();
+    QAbstractItemView::dataChanged(topLeft, bottomRight, roles);
+}
+
 void FeedView::updateGeometries()
 {
     qDebug() << "updateGeometries";
diff --git a/ui/utils/feedview.h b/ui/utils/feedview.h
index 6d16ea3..a0b9252 100644
--- a/ui/utils/feedview.h
+++ b/ui/utils/feedview.h
@@ -52,6 +52,7 @@ public slots:
 protected slots:
     void rowsInserted(const QModelIndex & parent, int start, int end) override;
     void verticalScrollbarValueChanged(int value) override;
+    void dataChanged(const QModelIndex & topLeft, const QModelIndex & bottomRight, const QVector<int> & roles) override;
     void onMessageButtonPushed(const QString& messageId, bool download);
     
 protected:
diff --git a/ui/utils/messagedelegate.cpp b/ui/utils/messagedelegate.cpp
index 83eaa42..6d53141 100644
--- a/ui/utils/messagedelegate.cpp
+++ b/ui/utils/messagedelegate.cpp
@@ -115,7 +115,6 @@ void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio
     painter->setFont(nickFont);
     painter->drawText(opt.rect, opt.displayAlignment, data.sender, &rect);
     
-    
     opt.rect.adjust(0, rect.height(), 0, 0);
     painter->save();
     switch (data.attach.state) {
@@ -131,6 +130,11 @@ void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio
             paintButton(getButton(data), painter, data.sentByMe, opt);
             break;
         case Models::ready:
+            clearHelperWidget(data);
+            paintPreview(data, painter, opt);
+            break;
+        case Models::errorDownload:
+        case Models::errorUpload:
             break;
     }
     painter->restore();
@@ -178,6 +182,7 @@ QSize MessageDelegate::sizeHint(const QStyleOptionViewItem& option, const QModel
             messageSize.rheight() += buttonHeight;
             break;
         case Models::ready:
+            messageSize.rheight() += calculateAttachSize(attach.localPath, messageRect).height();
             break;
         case Models::errorDownload:
         case Models::errorUpload:
@@ -245,15 +250,41 @@ void MessageDelegate::paintBar(QProgressBar* bar, QPainter* painter, bool sentBy
 {
     QPoint start = option.rect.topLeft();
     
-    QWidget* vp = static_cast<QWidget*>(painter->device());
-    bar->setParent(vp);
-    bar->move(start);
+    //QWidget* vp = static_cast<QWidget*>(painter->device());
+    
+//     if (bar->parent() != vp) {
+//         bar->setParent(vp);
+//     }
+//     bar->move(start);
     bar->resize(option.rect.width(), barHeight);
-    bar->show();
+    //     bar->show();      
+    
+    painter->translate(start);
+    bar->render(painter, QPoint(), QRegion(), QWidget::DrawChildren);
     
     option.rect.adjust(0, barHeight, 0, 0);
 }
 
+void MessageDelegate::paintPreview(const Models::FeedItem& data, QPainter* painter, QStyleOptionViewItem& option) const
+{
+    Shared::Global::FileInfo info = Shared::Global::getFileInfo(data.attach.localPath);
+    if (info.preview == Shared::Global::FileInfo::Preview::picture) {
+        QSize size = constrainAttachSize(info.size, option.rect.size());
+        
+        QPoint start;
+        if (data.sentByMe) {
+            start = {option.rect.width() - size.width(), option.rect.top()};
+            start.rx() += margin;
+        } else {
+            start = option.rect.topLeft();
+        }
+        QImage img(data.attach.localPath);
+        painter->drawImage(QRect(start, size), img);
+        
+        option.rect.adjust(0, size.height(), 0, 0);
+    }
+}
+
 QPushButton * MessageDelegate::getButton(const Models::FeedItem& data) const
 {
     std::map<QString, FeedButton*>::const_iterator itr = buttons->find(data.id);
@@ -376,6 +407,24 @@ void MessageDelegate::clearHelperWidget(const Models::FeedItem& data) const
     }
 }
 
+QSize MessageDelegate::calculateAttachSize(const QString& path, const QRect& bounds) const
+{
+    Shared::Global::FileInfo info = Shared::Global::getFileInfo(path);
+    
+    return constrainAttachSize(info.size, bounds.size());
+}
+
+QSize MessageDelegate::constrainAttachSize(QSize src, QSize bounds) const
+{
+    bounds.setHeight(500);
+    
+    if (src.width() > bounds.width() || src.height() > bounds.height()) {
+        src.scale(bounds, Qt::KeepAspectRatio);
+    }
+    
+    return src;
+}
+
 
 // void MessageDelegate::setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const
 // {
diff --git a/ui/utils/messagedelegate.h b/ui/utils/messagedelegate.h
index 56f9ebf..cbad6cd 100644
--- a/ui/utils/messagedelegate.h
+++ b/ui/utils/messagedelegate.h
@@ -30,6 +30,7 @@
 #include <QProgressBar>
 
 #include "shared/icons.h"
+#include "shared/global.h"
 
 namespace Models {
     struct FeedItem;
@@ -57,9 +58,12 @@ signals:
 protected:
     void paintButton(QPushButton* btn, QPainter* painter, bool sentByMe, QStyleOptionViewItem& option) const;
     void paintBar(QProgressBar* bar, QPainter* painter, bool sentByMe, QStyleOptionViewItem& option) const;
+    void paintPreview(const Models::FeedItem& data, QPainter* painter, QStyleOptionViewItem& option) const;
     QPushButton* getButton(const Models::FeedItem& data) const;
     QProgressBar* getBar(const Models::FeedItem& data) const;
     void clearHelperWidget(const Models::FeedItem& data) const;
+    QSize calculateAttachSize(const QString& path, const QRect& bounds) const;
+    QSize constrainAttachSize(QSize src, QSize bounds) const;
     
 protected slots:
     void onButtonPushed() const;
@@ -86,7 +90,6 @@ private:
     std::set<QString>* idsToKeep;
     bool clearingWidgets;
     
-    
 };
 
 #endif // MESSAGEDELEGATE_H

From d936c0302d5f23c422822406a21a140402606b7f Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Fri, 23 Apr 2021 01:41:32 +0300
Subject: [PATCH 100/281] bug with downloads in group chats, status icons in
 messages, visuals, feedView optimisations

---
 core/handlers/messagehandler.cpp |  2 +-
 ui/models/messagefeed.cpp        | 63 ++++++++++++++++++++++++++++++--
 ui/models/messagefeed.h          |  3 ++
 ui/utils/feedview.cpp            | 18 ++++++++-
 ui/utils/feedview.h              |  3 ++
 ui/utils/messagedelegate.cpp     | 44 +++++++++++++++++-----
 6 files changed, 116 insertions(+), 17 deletions(-)

diff --git a/core/handlers/messagehandler.cpp b/core/handlers/messagehandler.cpp
index 51282eb..7644982 100644
--- a/core/handlers/messagehandler.cpp
+++ b/core/handlers/messagehandler.cpp
@@ -369,7 +369,7 @@ void Core::MessageHandler::onDownloadFileComplete(const std::list<Shared::Messag
     };
     for (const Shared::MessageInfo& info : msgs) {
         if (info.account == acc->getName()) {
-            Contact* cnt = acc->rh->getContact(info.jid);
+            RosterItem* cnt = acc->rh->getRosterItem(info.jid);
             if (cnt != 0) {
                 if (cnt->changeMessage(info.messageId, cData)) {
                     emit acc->changeMessage(info.jid, info.messageId, cData);
diff --git a/ui/models/messagefeed.cpp b/ui/models/messagefeed.cpp
index 2345816..a591657 100644
--- a/ui/models/messagefeed.cpp
+++ b/ui/models/messagefeed.cpp
@@ -32,6 +32,7 @@ const QHash<int, QByteArray> Models::MessageFeed::roles = {
     {Avatar, "avatar"},
     {Attach, "attach"},
     {Id, "id"},
+    {Error, "error"},
     {Bulk, "bulk"}
 };
 
@@ -85,6 +86,7 @@ void Models::MessageFeed::changeMessage(const QString& id, const QMap<QString, Q
     }
     
     Shared::Message* msg = *itr;
+    QVector<int> changeRoles = detectChanges(*msg, data);
     QModelIndex index = modelIndexByTime(id, msg->getTime());
     Shared::Message::Change functor(data);
     bool success = indexById.modify(itr, functor);
@@ -94,7 +96,7 @@ void Models::MessageFeed::changeMessage(const QString& id, const QMap<QString, Q
     }
     
     if (functor.hasIdBeenModified()) {
-        
+        changeRoles.push_back(MessageRoles::Id);
     }
     
     //change message is a final event in download/upload event train
@@ -113,7 +115,55 @@ void Models::MessageFeed::changeMessage(const QString& id, const QMap<QString, Q
         }
     }
     
-    emit dataChanged(index, index);
+    emit dataChanged(index, index, changeRoles);
+}
+
+QVector<int> Models::MessageFeed::detectChanges(const Shared::Message& msg, const QMap<QString, QVariant>& data) const
+{
+    QVector<int> roles;
+    Shared::Message::State state = msg.getState();
+    QMap<QString, QVariant>::const_iterator itr = data.find("state");
+    if (itr != data.end() && static_cast<Shared::Message::State>(itr.value().toUInt()) != state) {
+        roles.push_back(MessageRoles::DeliveryState);
+    }
+    
+    itr = data.find("outOfBandUrl");
+    bool att = false;
+    if (itr != data.end() && itr.value().toString() != msg.getOutOfBandUrl()) {
+        roles.push_back(MessageRoles::Attach);
+        att = true;
+    }
+    
+    if (!att) {
+        itr = data.find("attachPath");
+        if (itr != data.end() && itr.value().toString() != msg.getAttachPath()) {
+            roles.push_back(MessageRoles::Attach);
+        }
+    }
+    
+    if (state == Shared::Message::State::error) {
+        itr = data.find("errorText");
+        if (itr != data.end()) {
+            roles.push_back(MessageRoles::Error);
+        }
+    }
+    
+    itr = data.find("body");
+    if (itr != data.end()) {
+        QMap<QString, QVariant>::const_iterator dItr = data.find("stamp");
+        QDateTime correctionDate;
+        if (dItr != data.end()) {
+            correctionDate = dItr.value().toDateTime();
+        } else {
+            correctionDate = QDateTime::currentDateTimeUtc();      //in case there is no information about time of this correction it's applied
+        }
+        if (!msg.getEdited() || msg.getLastModified() < correctionDate) {
+            roles.push_back(MessageRoles::Text);
+            roles.push_back(MessageRoles::Correction);
+        }
+    }
+    
+    return roles;
 }
 
 void Models::MessageFeed::removeMessage(const QString& id)
@@ -187,12 +237,17 @@ QVariant Models::MessageFeed::data(const QModelIndex& index, int role) const
             case Id: 
                 answer.setValue(msg->getId());
                 break;
+                break;
+            case Error: 
+                answer.setValue(msg->getErrorText());
+                break;
             case Bulk: {
                 FeedItem item;
                 item.id = msg->getId();
                 item.sentByMe = sentByMe(*msg);
                 item.date = msg->getTime();
                 item.state = msg->getState();
+                item.error = msg->getErrorText();
                 item.correction = msg->getEdited();
                 
                 QString body = msg->getBody();
@@ -340,7 +395,7 @@ void Models::MessageFeed::downloadAttachment(const QString& messageId)
         std::pair<Progress::iterator, bool> progressPair = downloads.insert(std::make_pair(messageId, 0));
         if (progressPair.second) {     //Only to take action if we weren't already downloading it
             Shared::Message* msg = static_cast<Shared::Message*>(ind.internalPointer());
-            emit dataChanged(ind, ind);
+            emit dataChanged(ind, ind, {MessageRoles::Attach});
             emit fileDownloadRequest(msg->getOutOfBandUrl());
         } else {
             qDebug() << "Attachment download for message with id" << messageId << "is already in progress, skipping";
@@ -368,7 +423,7 @@ void Models::MessageFeed::fileProgress(const QString& messageId, qreal value, bo
     if (itr != pr->end()) {
         itr->second = value;
         QModelIndex ind = modelIndexById(messageId);
-        emit dataChanged(ind, ind);
+        emit dataChanged(ind, ind);                     //the type of the attach didn't change, so, there is no need to relayout, there is no role in event
     }
 }
 
diff --git a/ui/models/messagefeed.h b/ui/models/messagefeed.h
index e8fb712..f5b27b2 100644
--- a/ui/models/messagefeed.h
+++ b/ui/models/messagefeed.h
@@ -73,6 +73,7 @@ protected:
     Attachment fillAttach(const Shared::Message& msg) const;
     QModelIndex modelIndexById(const QString& id) const;
     QModelIndex modelIndexByTime(const QString& id, const QDateTime& time) const;
+    QVector<int> detectChanges(const Shared::Message& msg, const QMap<QString, QVariant>& data) const;
     
 public:
     enum MessageRoles {
@@ -85,6 +86,7 @@ public:
         Avatar,
         Attach,
         Id,
+        Error,
         Bulk
     };
     
@@ -161,6 +163,7 @@ struct FeedItem {
     QString text;
     QString sender;
     QString avatar;
+    QString error;
     bool sentByMe;
     bool correction;
     QDateTime date;
diff --git a/ui/utils/feedview.cpp b/ui/utils/feedview.cpp
index 7155d95..3f3b4b7 100644
--- a/ui/utils/feedview.cpp
+++ b/ui/utils/feedview.cpp
@@ -29,6 +29,14 @@
 constexpr int maxMessageHeight = 10000;
 constexpr int approximateSingleMessageHeight = 20;
 
+const std::set<int> FeedView::geometryChangingRoles = {
+    Models::MessageFeed::Attach,
+    Models::MessageFeed::Text,
+    Models::MessageFeed::Id,
+    Models::MessageFeed::Error
+    
+};
+
 FeedView::FeedView(QWidget* parent):
     QAbstractItemView(parent),
     hints(),
@@ -115,8 +123,14 @@ void FeedView::rowsInserted(const QModelIndex& parent, int start, int end)
 
 void FeedView::dataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight, const QVector<int>& roles)
 {
-    //TODO make optimisations! There are some roles but not all that change geometry!
-    updateGeometries();
+    if (specialDelegate) {
+        for (int role : roles) {
+            if (geometryChangingRoles.count(role) != 0) {
+                updateGeometries();                         //to recalculate layout only if there are some geometry changing modifications
+                break;
+            }
+        }
+    }
     QAbstractItemView::dataChanged(topLeft, bottomRight, roles);
 }
 
diff --git a/ui/utils/feedview.h b/ui/utils/feedview.h
index a0b9252..05e3025 100644
--- a/ui/utils/feedview.h
+++ b/ui/utils/feedview.h
@@ -22,6 +22,7 @@
 #include <QAbstractItemView>
 
 #include <deque>
+#include <set>
 
 #include <ui/models/messagefeed.h>
 
@@ -77,6 +78,8 @@ private:
     bool specialModel;
     bool clearWidgetsMode;
     
+    static const std::set<int> geometryChangingRoles;
+    
 };
 
 #endif //FEEDVIEW_H
diff --git a/ui/utils/messagedelegate.cpp b/ui/utils/messagedelegate.cpp
index 6d53141..02aca8f 100644
--- a/ui/utils/messagedelegate.cpp
+++ b/ui/utils/messagedelegate.cpp
@@ -26,6 +26,8 @@
 
 constexpr int avatarHeight = 50;
 constexpr int margin = 6;
+constexpr int textMargin = 2;
+constexpr int statusIconSize = 16;
 
 MessageDelegate::MessageDelegate(QObject* parent):
 QStyledItemDelegate(parent),
@@ -115,7 +117,7 @@ void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio
     painter->setFont(nickFont);
     painter->drawText(opt.rect, opt.displayAlignment, data.sender, &rect);
     
-    opt.rect.adjust(0, rect.height(), 0, 0);
+    opt.rect.adjust(0, rect.height() + textMargin, 0, 0);
     painter->save();
     switch (data.attach.state) {
         case Models::none:
@@ -139,16 +141,28 @@ void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio
     }
     painter->restore();
     
+    int messageLeft = 10000; //TODO
     if (data.text.size() > 0) {
         painter->setFont(bodyFont);
         painter->drawText(opt.rect, opt.displayAlignment | Qt::TextWordWrap, data.text, &rect);
-        opt.rect.adjust(0, rect.height(), 0, 0);
+        opt.rect.adjust(0, rect.height() + textMargin, 0, 0);
+        messageLeft = rect.x();
     }
     painter->setFont(dateFont);
     QColor q = painter->pen().color();
     q.setAlpha(180);
     painter->setPen(q);
     painter->drawText(opt.rect, opt.displayAlignment, data.date.toLocalTime().toString(), &rect);
+    if (data.sentByMe) {
+        if (messageLeft > rect.x() - statusIconSize - margin) {
+            messageLeft = rect.x() - statusIconSize - margin;
+        }
+        QIcon q(Shared::icon(Shared::messageStateThemeIcons[static_cast<uint8_t>(data.state)]));
+        if (data.state == Shared::Message::State::error) {
+            //TODO handle error tooltip
+        }
+        painter->drawPixmap(messageLeft, opt.rect.y(), q.pixmap(statusIconSize, statusIconSize));
+    }
     
     painter->restore();
     
@@ -168,6 +182,7 @@ QSize MessageDelegate::sizeHint(const QStyleOptionViewItem& option, const QModel
     QSize messageSize(0, 0);
     if (body.size() > 0) {
         messageSize = bodyMetrics.boundingRect(messageRect, Qt::TextWordWrap, body).size();
+        messageSize.rheight() += textMargin;
     }
     
     switch (attach.state) {
@@ -175,14 +190,14 @@ QSize MessageDelegate::sizeHint(const QStyleOptionViewItem& option, const QModel
             break;
         case Models::uploading:
         case Models::downloading:
-            messageSize.rheight() += barHeight;
+            messageSize.rheight() += barHeight + textMargin;
             break;
         case Models::remote:
         case Models::local:
-            messageSize.rheight() += buttonHeight;
+            messageSize.rheight() += buttonHeight + textMargin;
             break;
         case Models::ready:
-            messageSize.rheight() += calculateAttachSize(attach.localPath, messageRect).height();
+            messageSize.rheight() += calculateAttachSize(attach.localPath, messageRect).height() + textMargin;
             break;
         case Models::errorDownload:
         case Models::errorUpload:
@@ -190,7 +205,8 @@ QSize MessageDelegate::sizeHint(const QStyleOptionViewItem& option, const QModel
     }
     
     messageSize.rheight() += nickMetrics.lineSpacing();
-    messageSize.rheight() += dateMetrics.height();
+    messageSize.rheight() += textMargin;
+    messageSize.rheight() += dateMetrics.height() > statusIconSize ? dateMetrics.height() : statusIconSize;
     
     if (messageSize.height() < avatarHeight) {
         messageSize.setHeight(avatarHeight);
@@ -208,10 +224,18 @@ void MessageDelegate::initializeFonts(const QFont& font)
     dateFont = font;
     
     nickFont.setBold(true);
+    
+    float ndps = nickFont.pointSizeF();
+    if (ndps != -1) {
+        nickFont.setPointSizeF(ndps * 1.2);
+    } else {
+        nickFont.setPointSize(nickFont.pointSize() + 2);
+    }
+    
     dateFont.setItalic(true);
     float dps = dateFont.pointSizeF();
     if (dps != -1) {
-        dateFont.setPointSizeF(dps * 0.7);
+        dateFont.setPointSizeF(dps * 0.8);
     } else {
         dateFont.setPointSize(dateFont.pointSize() - 2);
     }
@@ -243,7 +267,7 @@ void MessageDelegate::paintButton(QPushButton* btn, QPainter* painter, bool sent
     btn->move(start);
     btn->show();
     
-    option.rect.adjust(0, buttonHeight, 0, 0);
+    option.rect.adjust(0, buttonHeight + textMargin, 0, 0);
 }
 
 void MessageDelegate::paintBar(QProgressBar* bar, QPainter* painter, bool sentByMe, QStyleOptionViewItem& option) const
@@ -262,7 +286,7 @@ void MessageDelegate::paintBar(QProgressBar* bar, QPainter* painter, bool sentBy
     painter->translate(start);
     bar->render(painter, QPoint(), QRegion(), QWidget::DrawChildren);
     
-    option.rect.adjust(0, barHeight, 0, 0);
+    option.rect.adjust(0, barHeight + textMargin, 0, 0);
 }
 
 void MessageDelegate::paintPreview(const Models::FeedItem& data, QPainter* painter, QStyleOptionViewItem& option) const
@@ -281,7 +305,7 @@ void MessageDelegate::paintPreview(const Models::FeedItem& data, QPainter* paint
         QImage img(data.attach.localPath);
         painter->drawImage(QRect(start, size), img);
         
-        option.rect.adjust(0, size.height(), 0, 0);
+        option.rect.adjust(0, size.height() + textMargin, 0, 0);
     }
 }
 

From 8310708c922aee3b8b3c6eb610737735f0eebc21 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Fri, 23 Apr 2021 14:53:48 +0300
Subject: [PATCH 101/281] First attemtps to upload files, debug, reused of once
 uploaded or downloaded files

---
 core/handlers/messagehandler.cpp | 18 +++++++++----
 core/networkaccess.cpp           |  2 +-
 shared/message.h                 |  1 +
 ui/models/messagefeed.cpp        | 45 +++++++++++++++++++++++---------
 ui/models/messagefeed.h          | 17 +++++++-----
 ui/widgets/conversation.cpp      |  7 ++---
 ui/widgets/conversation.h        |  1 -
 7 files changed, 59 insertions(+), 32 deletions(-)

diff --git a/core/handlers/messagehandler.cpp b/core/handlers/messagehandler.cpp
index 7644982..171a424 100644
--- a/core/handlers/messagehandler.cpp
+++ b/core/handlers/messagehandler.cpp
@@ -253,7 +253,9 @@ void Core::MessageHandler::performSending(Shared::Message data)
 {
     QString jid = data.getPenPalJid();
     QString id = data.getId();
+    QString oob = data.getOutOfBandUrl();
     RosterItem* ri = acc->rh->getRosterItem(jid);
+    QMap<QString, QVariant> changes;
     if (acc->state == Shared::ConnectionState::connected) {
         QXmppMessage msg(acc->getFullJid(), data.getTo(), data.getBody(), data.getThread());
         
@@ -262,7 +264,7 @@ void Core::MessageHandler::performSending(Shared::Message data)
 #endif
         msg.setId(id);
         msg.setType(static_cast<QXmppMessage::Type>(data.getType()));       //it is safe here, my type is compatible
-        msg.setOutOfBandUrl(data.getOutOfBandUrl());
+        msg.setOutOfBandUrl(oob);
         msg.setReceiptRequested(true);
         
         bool sent = acc->client.sendPacket(msg);
@@ -286,10 +288,16 @@ void Core::MessageHandler::performSending(Shared::Message data)
         data.setErrorText("You are is offline or reconnecting");
     }
     
-    emit acc->changeMessage(jid, id, {
-        {"state", static_cast<uint>(data.getState())},
-        {"errorText", data.getErrorText()}
-    });
+    Shared::Message::State mstate = data.getState();
+    changes.insert("state", static_cast<uint>(mstate));
+    if (mstate == Shared::Message::State::error) {
+        changes.insert("errorText", data.getErrorText());
+    }
+    if (oob.size() > 0) {
+        changes.insert("outOfBandUrl", oob);
+    }
+    
+    emit acc->changeMessage(jid, id, changes);
 }
 
 void Core::MessageHandler::prepareUpload(const Shared::Message& data)
diff --git a/core/networkaccess.cpp b/core/networkaccess.cpp
index f178068..d771dc6 100644
--- a/core/networkaccess.cpp
+++ b/core/networkaccess.cpp
@@ -373,7 +373,7 @@ QString Core::NetworkAccess::getFileRemoteUrl(const QString& path)
     QString p;
     
     try {
-        QString p = storage.getUrl(path);
+        p = storage.getUrl(path);
     } catch (const Archive::NotFound& err) {
         
     } catch (...) {
diff --git a/shared/message.h b/shared/message.h
index c2766b3..aa91af6 100644
--- a/shared/message.h
+++ b/shared/message.h
@@ -46,6 +46,7 @@ public:
         delivered,
         error
     };
+    
     static const State StateHighest = State::error;
     static const State StateLowest = State::pending;
     
diff --git a/ui/models/messagefeed.cpp b/ui/models/messagefeed.cpp
index a591657..c3a7923 100644
--- a/ui/models/messagefeed.cpp
+++ b/ui/models/messagefeed.cpp
@@ -86,7 +86,7 @@ void Models::MessageFeed::changeMessage(const QString& id, const QMap<QString, Q
     }
     
     Shared::Message* msg = *itr;
-    QVector<int> changeRoles = detectChanges(*msg, data);
+    std::set<MessageRoles> changeRoles = detectChanges(*msg, data);
     QModelIndex index = modelIndexByTime(id, msg->getTime());
     Shared::Message::Change functor(data);
     bool success = indexById.modify(itr, functor);
@@ -96,55 +96,69 @@ void Models::MessageFeed::changeMessage(const QString& id, const QMap<QString, Q
     }
     
     if (functor.hasIdBeenModified()) {
-        changeRoles.push_back(MessageRoles::Id);
+        changeRoles.insert(MessageRoles::Id);
     }
     
     //change message is a final event in download/upload event train
     //only after changeMessage we can consider the download is done
     Progress::const_iterator dItr = downloads.find(id);
+    bool attachOrError = changeRoles.count(MessageRoles::Attach) > 0 || changeRoles.count(MessageRoles::Error);
     if (dItr != downloads.end()) {
-        if (dItr->second == 1) {
+        if (attachOrError) {
             downloads.erase(dItr);
+        } else if (changeRoles.count(MessageRoles::Id) > 0) {
+            qreal progress = dItr->second;
+            downloads.erase(dItr);
+            downloads.insert(std::make_pair(msg->getId(), progress));
         }
     } else {
         dItr = uploads.find(id);
         if (dItr != uploads.end()) {
-            if (dItr->second == 1) {
+            if (attachOrError) {
                 uploads.erase(dItr);
+            } else if (changeRoles.count(MessageRoles::Id) > 0) {
+                qreal progress = dItr->second;
+                uploads.erase(dItr);
+                uploads.insert(std::make_pair(msg->getId(), progress));
             }
         }
     }
     
-    emit dataChanged(index, index, changeRoles);
+    QVector<int> cr;
+    for (MessageRoles role : changeRoles) {
+        cr.push_back(role);
+    }
+    
+    emit dataChanged(index, index, cr);
 }
 
-QVector<int> Models::MessageFeed::detectChanges(const Shared::Message& msg, const QMap<QString, QVariant>& data) const
+std::set<Models::MessageFeed::MessageRoles> Models::MessageFeed::detectChanges(const Shared::Message& msg, const QMap<QString, QVariant>& data) const
 {
-    QVector<int> roles;
+    std::set<MessageRoles> roles;
     Shared::Message::State state = msg.getState();
     QMap<QString, QVariant>::const_iterator itr = data.find("state");
     if (itr != data.end() && static_cast<Shared::Message::State>(itr.value().toUInt()) != state) {
-        roles.push_back(MessageRoles::DeliveryState);
+        roles.insert(MessageRoles::DeliveryState);
     }
     
     itr = data.find("outOfBandUrl");
     bool att = false;
     if (itr != data.end() && itr.value().toString() != msg.getOutOfBandUrl()) {
-        roles.push_back(MessageRoles::Attach);
+        roles.insert(MessageRoles::Attach);
         att = true;
     }
     
     if (!att) {
         itr = data.find("attachPath");
         if (itr != data.end() && itr.value().toString() != msg.getAttachPath()) {
-            roles.push_back(MessageRoles::Attach);
+            roles.insert(MessageRoles::Attach);
         }
     }
     
     if (state == Shared::Message::State::error) {
         itr = data.find("errorText");
         if (itr != data.end()) {
-            roles.push_back(MessageRoles::Error);
+            roles.insert(MessageRoles::Error);
         }
     }
     
@@ -158,8 +172,8 @@ QVector<int> Models::MessageFeed::detectChanges(const Shared::Message& msg, cons
             correctionDate = QDateTime::currentDateTimeUtc();      //in case there is no information about time of this correction it's applied
         }
         if (!msg.getEdited() || msg.getLastModified() < correctionDate) {
-            roles.push_back(MessageRoles::Text);
-            roles.push_back(MessageRoles::Correction);
+            roles.insert(MessageRoles::Text);
+            roles.insert(MessageRoles::Correction);
         }
     }
     
@@ -410,6 +424,11 @@ void Models::MessageFeed::uploadAttachment(const QString& messageId)
     qDebug() << "request to upload attachment of the message" << messageId;
 }
 
+bool Models::MessageFeed::registerUpload(const QString& messageId)
+{
+    return uploads.insert(std::make_pair(messageId, 0)).second;
+}
+
 void Models::MessageFeed::fileProgress(const QString& messageId, qreal value, bool up)
 {
     Progress* pr = 0;
diff --git a/ui/models/messagefeed.h b/ui/models/messagefeed.h
index f5b27b2..c86df33 100644
--- a/ui/models/messagefeed.h
+++ b/ui/models/messagefeed.h
@@ -23,6 +23,8 @@
 #include <QDateTime>
 #include <QString>
 
+#include <set>
+
 #include <boost/multi_index_container.hpp>
 #include <boost/multi_index/ordered_index.hpp>
 #include <boost/multi_index/ranked_index.hpp>
@@ -57,6 +59,7 @@ public:
     void responseArchive(const std::list<Shared::Message> list, bool last);
     void downloadAttachment(const QString& messageId);
     void uploadAttachment(const QString& messageId);
+    bool registerUpload(const QString& messageId);
     
     unsigned int unreadMessagesCount() const;
     void fileProgress(const QString& messageId, qreal value, bool up);
@@ -68,13 +71,6 @@ signals:
     void requestStateChange(bool requesting);
     void fileDownloadRequest(const QString& url);
     
-protected:
-    bool sentByMe(const Shared::Message& msg) const;
-    Attachment fillAttach(const Shared::Message& msg) const;
-    QModelIndex modelIndexById(const QString& id) const;
-    QModelIndex modelIndexByTime(const QString& id, const QDateTime& time) const;
-    QVector<int> detectChanges(const Shared::Message& msg, const QMap<QString, QVariant>& data) const;
-    
 public:
     enum MessageRoles {
         Text = Qt::UserRole + 1,
@@ -90,6 +86,13 @@ public:
         Bulk
     };
     
+protected:
+    bool sentByMe(const Shared::Message& msg) const;
+    Attachment fillAttach(const Shared::Message& msg) const;
+    QModelIndex modelIndexById(const QString& id) const;
+    QModelIndex modelIndexByTime(const QString& id, const QDateTime& time) const;
+    std::set<MessageRoles> detectChanges(const Shared::Message& msg, const QMap<QString, QVariant>& data) const;
+    
 private:
     enum SyncState {
         incomplete,
diff --git a/ui/widgets/conversation.cpp b/ui/widgets/conversation.cpp
index 017b9ba..d6e3c9a 100644
--- a/ui/widgets/conversation.cpp
+++ b/ui/widgets/conversation.cpp
@@ -33,6 +33,7 @@ Conversation::Conversation(bool muc, Models::Account* acc, Models::Element* el,
     QWidget(parent),
     isMuc(muc),
     account(acc),
+    element(el),
     palJid(pJid),
     activePalResource(pRes),
     m_ui(new Ui::Conversation()),
@@ -162,11 +163,6 @@ QString Conversation::getJid() const
     return palJid;
 }
 
-void Conversation::changeMessage(const QString& id, const QMap<QString, QVariant>& data)
-{
-//     line->changeMessage(id, data);
-}
-
 KeyEnterReceiver::KeyEnterReceiver(QObject* parent): QObject(parent), ownEvent(false) {}
 
 bool KeyEnterReceiver::eventFilter(QObject* obj, QEvent* event)
@@ -220,6 +216,7 @@ void Conversation::onEnterPressed()
         for (Badge* badge : filesToAttach) {
             Shared::Message msg = createMessage();
             msg.setAttachPath(badge->id);
+            element->feed->registerUpload(msg.getId());
             emit sendMessage(msg);
         }
          clearAttachedFiles();
diff --git a/ui/widgets/conversation.h b/ui/widgets/conversation.h
index 7d10aff..b506e39 100644
--- a/ui/widgets/conversation.h
+++ b/ui/widgets/conversation.h
@@ -73,7 +73,6 @@ public:
     void fileError(const QString& messageId, const QString& error);
     void responseFileProgress(const QString& messageId, qreal progress);
     virtual void setAvatar(const QString& path);
-    void changeMessage(const QString& id, const QMap<QString, QVariant>& data);
     void setFeedFrames(bool top, bool right, bool bottom, bool left);
     
 signals:

From 4c5efad9dce74c1612162c5e876a2a00feba4b44 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Mon, 26 Apr 2021 19:37:36 +0300
Subject: [PATCH 102/281] CMake build error, status icon text tooltip

---
 ui/CMakeLists.txt            |  5 ++--
 ui/utils/feedview.cpp        |  6 ++--
 ui/utils/messagedelegate.cpp | 56 +++++++++++++++++++++++++++++++-----
 ui/utils/messagedelegate.h   |  3 ++
 4 files changed, 59 insertions(+), 11 deletions(-)

diff --git a/ui/CMakeLists.txt b/ui/CMakeLists.txt
index c4a8aa6..d6e29d3 100644
--- a/ui/CMakeLists.txt
+++ b/ui/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.0)
+cmake_minimum_required(VERSION 3.3)
 project(squawkUI)
 
 # Instruct CMake to run moc automatically when needed.
@@ -8,7 +8,8 @@ set(CMAKE_AUTOUIC ON)
 
 # Find the QtWidgets library
 find_package(Qt5 CONFIG REQUIRED COMPONENTS Widgets DBus Core)
-find_package(Boost 1.36.0 CONFIG REQUIRED)
+find_package(Boost 1.36.0 CONFIG REQUIRED COMPONENTS
+             date_time filesystem iostreams)
 if(Boost_FOUND)
   include_directories(${Boost_INCLUDE_DIRS})
 endif()
diff --git a/ui/utils/feedview.cpp b/ui/utils/feedview.cpp
index 3f3b4b7..550b8d4 100644
--- a/ui/utils/feedview.cpp
+++ b/ui/utils/feedview.cpp
@@ -253,9 +253,11 @@ void FeedView::paintEvent(QPaintEvent* event)
     option.features = QStyleOptionViewItem::WrapText;
     QPoint cursor = vp->mapFromGlobal(QCursor::pos());
     
-    if (clearWidgetsMode && specialDelegate) {
+    if (specialDelegate) {
         MessageDelegate* del = static_cast<MessageDelegate*>(itemDelegate());
-        del->beginClearWidgets();
+        if (clearWidgetsMode) {
+            del->beginClearWidgets();
+        }
     }
     
     for (const QModelIndex& index : toRener) {
diff --git a/ui/utils/messagedelegate.cpp b/ui/utils/messagedelegate.cpp
index 02aca8f..910db72 100644
--- a/ui/utils/messagedelegate.cpp
+++ b/ui/utils/messagedelegate.cpp
@@ -28,6 +28,7 @@ constexpr int avatarHeight = 50;
 constexpr int margin = 6;
 constexpr int textMargin = 2;
 constexpr int statusIconSize = 16;
+constexpr int maxAttachmentHeight = 500;
 
 MessageDelegate::MessageDelegate(QObject* parent):
 QStyledItemDelegate(parent),
@@ -41,6 +42,7 @@ buttonHeight(0),
 barHeight(0),
 buttons(new std::map<QString, FeedButton*>()),
 bars(new std::map<QString, QProgressBar*>()),
+statusIcons(new std::map<QString, QLabel*>()),
 idsToKeep(new std::set<QString>()),
 clearingWidgets(false)
 {
@@ -61,6 +63,10 @@ MessageDelegate::~MessageDelegate()
         delete pair.second;
     }
     
+    for (const std::pair<const QString, QLabel*>& pair: *statusIcons){
+        delete pair.second;
+    }
+    
     delete idsToKeep;
     delete buttons;
     delete bars;
@@ -116,7 +122,6 @@ void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio
     QRect rect;
     painter->setFont(nickFont);
     painter->drawText(opt.rect, opt.displayAlignment, data.sender, &rect);
-    
     opt.rect.adjust(0, rect.height() + textMargin, 0, 0);
     painter->save();
     switch (data.attach.state) {
@@ -157,11 +162,13 @@ void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio
         if (messageLeft > rect.x() - statusIconSize - margin) {
             messageLeft = rect.x() - statusIconSize - margin;
         }
-        QIcon q(Shared::icon(Shared::messageStateThemeIcons[static_cast<uint8_t>(data.state)]));
-        if (data.state == Shared::Message::State::error) {
-            //TODO handle error tooltip
-        }
-        painter->drawPixmap(messageLeft, opt.rect.y(), q.pixmap(statusIconSize, statusIconSize));
+        QLabel* statusIcon = getStatusIcon(data);
+        
+        QWidget* vp = static_cast<QWidget*>(painter->device());
+        statusIcon->setParent(vp);
+        statusIcon->move(messageLeft, opt.rect.y());
+        statusIcon->show();
+        opt.rect.adjust(0, statusIconSize + textMargin, 0, 0);
     }
     
     painter->restore();
@@ -373,6 +380,31 @@ QProgressBar * MessageDelegate::getBar(const Models::FeedItem& data) const
     return result;
 }
 
+QLabel * MessageDelegate::getStatusIcon(const Models::FeedItem& data) const
+{
+    std::map<QString, QLabel*>::const_iterator itr = statusIcons->find(data.id);
+    QLabel* result = 0;
+    
+    if (itr != statusIcons->end()) {
+        result = itr->second;
+    } else {
+        result = new QLabel();
+        statusIcons->insert(std::make_pair(data.id, result));
+    }
+    
+    QIcon q(Shared::icon(Shared::messageStateThemeIcons[static_cast<uint8_t>(data.state)]));
+    QString tt = Shared::Global::getName(data.state);
+    if (data.state == Shared::Message::State::error) {
+        if (data.error > 0) {
+            tt += ": " + data.error;
+        }
+    }
+    
+    result->setToolTip(tt);
+    result->setPixmap(q.pixmap(statusIconSize));
+    
+    return result;
+}
 
 void MessageDelegate::beginClearWidgets()
 {
@@ -385,6 +417,7 @@ void MessageDelegate::endClearWidgets()
     if (clearingWidgets) {
         std::set<QString> toRemoveButtons;
         std::set<QString> toRemoveBars;
+        std::set<QString> toRemoveIcons;
         for (const std::pair<const QString, FeedButton*>& pair: *buttons) {
             if (idsToKeep->find(pair.first) == idsToKeep->end()) {
                 delete pair.second;
@@ -397,6 +430,12 @@ void MessageDelegate::endClearWidgets()
                 toRemoveBars.insert(pair.first);
             }
         }
+        for (const std::pair<const QString, QLabel*>& pair: *statusIcons) {
+            if (idsToKeep->find(pair.first) == idsToKeep->end()) {
+                delete pair.second;
+                toRemoveIcons.insert(pair.first);
+            }
+        }
         
         for (const QString& key : toRemoveButtons) {
             buttons->erase(key);
@@ -404,6 +443,9 @@ void MessageDelegate::endClearWidgets()
         for (const QString& key : toRemoveBars) {
             bars->erase(key);
         }
+        for (const QString& key : toRemoveIcons) {
+            statusIcons->erase(key);
+        }
         
         idsToKeep->clear();
         clearingWidgets = false;
@@ -440,7 +482,7 @@ QSize MessageDelegate::calculateAttachSize(const QString& path, const QRect& bou
 
 QSize MessageDelegate::constrainAttachSize(QSize src, QSize bounds) const
 {
-    bounds.setHeight(500);
+    bounds.setHeight(maxAttachmentHeight);
     
     if (src.width() > bounds.width() || src.height() > bounds.height()) {
         src.scale(bounds, Qt::KeepAspectRatio);
diff --git a/ui/utils/messagedelegate.h b/ui/utils/messagedelegate.h
index cbad6cd..ed42e2f 100644
--- a/ui/utils/messagedelegate.h
+++ b/ui/utils/messagedelegate.h
@@ -28,6 +28,7 @@
 #include <QFontMetrics>
 #include <QPushButton>
 #include <QProgressBar>
+#include <QLabel>
 
 #include "shared/icons.h"
 #include "shared/global.h"
@@ -61,6 +62,7 @@ protected:
     void paintPreview(const Models::FeedItem& data, QPainter* painter, QStyleOptionViewItem& option) const;
     QPushButton* getButton(const Models::FeedItem& data) const;
     QProgressBar* getBar(const Models::FeedItem& data) const;
+    QLabel* getStatusIcon(const Models::FeedItem& data) const;
     void clearHelperWidget(const Models::FeedItem& data) const;
     QSize calculateAttachSize(const QString& path, const QRect& bounds) const;
     QSize constrainAttachSize(QSize src, QSize bounds) const;
@@ -87,6 +89,7 @@ private:
     
     std::map<QString, FeedButton*>* buttons;
     std::map<QString, QProgressBar*>* bars;
+    std::map<QString, QLabel*>* statusIcons;
     std::set<QString>* idsToKeep;
     bool clearingWidgets;
     

From b44873d5877f2a84345a217e1dc9024ec4afa97a Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Tue, 27 Apr 2021 22:29:15 +0300
Subject: [PATCH 103/281] pal resourse sticking, notifications of unread
 messages, new message icons

---
 ui/models/element.cpp       | 21 ++++++++++---
 ui/models/element.h         |  5 +++
 ui/models/messagefeed.cpp   | 38 +++++++++++++++++++++--
 ui/models/messagefeed.h     |  9 ++++++
 ui/models/roster.cpp        |  2 ++
 ui/models/roster.h          |  1 +
 ui/squawk.cpp               | 37 +++++-----------------
 ui/squawk.h                 |  4 +++
 ui/widgets/chat.cpp         | 23 +++++++-------
 ui/widgets/chat.h           |  3 +-
 ui/widgets/conversation.cpp | 62 ++++++++++++++-----------------------
 ui/widgets/conversation.h   |  9 ++----
 12 files changed, 119 insertions(+), 95 deletions(-)

diff --git a/ui/models/element.cpp b/ui/models/element.cpp
index 659a30f..4bdc3d7 100644
--- a/ui/models/element.cpp
+++ b/ui/models/element.cpp
@@ -31,6 +31,8 @@ Models::Element::Element(Type p_type, const Models::Account* acc, const QString&
 {
     connect(feed, &MessageFeed::requestArchive, this, &Element::requestArchive);
     connect(feed, &MessageFeed::fileDownloadRequest, this, &Element::fileDownloadRequest);
+    connect(feed, &MessageFeed::unreadMessagesCountChanged, this, &Element::onFeedUnreadMessagesCountChanged);
+    connect(feed, &MessageFeed::unnoticedMessage, this, &Element::onFeedUnnoticedMessage);
     
     QMap<QString, QVariant>::const_iterator itr = data.find("avatarState");
     if (itr != data.end()) {
@@ -134,11 +136,6 @@ unsigned int Models::Element::getMessagesCount() const
 void Models::Element::addMessage(const Shared::Message& data)
 {
     feed->addMessage(data);
-    if (type == contact) {
-        changed(4);
-    } else if (type == room) {
-        changed(5);
-    }
 }
 
 void Models::Element::changeMessage(const QString& id, const QMap<QString, QVariant>& data)
@@ -170,3 +167,17 @@ void Models::Element::fileError(const QString& messageId, const QString& error,
 {
     feed->fileError(messageId, error, up);
 }
+
+void Models::Element::onFeedUnreadMessagesCountChanged()
+{
+    if (type == contact) {
+        changed(4);
+    } else if (type == room) {
+        changed(5);
+    }
+}
+
+void Models::Element::onFeedUnnoticedMessage(const Shared::Message& msg)
+{
+    emit unnoticedMessage(getAccountName(), msg);
+}
diff --git a/ui/models/element.h b/ui/models/element.h
index db6cda1..1818405 100644
--- a/ui/models/element.h
+++ b/ui/models/element.h
@@ -50,6 +50,7 @@ public:
 signals:
     void requestArchive(const QString& before);
     void fileDownloadRequest(const QString& url);
+    void unnoticedMessage(const QString& account, const Shared::Message& msg);
     
 protected:
     void setJid(const QString& p_jid);
@@ -59,6 +60,10 @@ protected:
     bool columnInvolvedInDisplay(int col) override;
     const Account* getParentAccount() const override;
     
+protected slots:
+    void onFeedUnreadMessagesCountChanged();
+    void onFeedUnnoticedMessage(const Shared::Message& msg);
+    
 protected:
     QString jid;
     QString avatarPath;
diff --git a/ui/models/messagefeed.cpp b/ui/models/messagefeed.cpp
index c3a7923..07dfe0a 100644
--- a/ui/models/messagefeed.cpp
+++ b/ui/models/messagefeed.cpp
@@ -44,12 +44,16 @@ Models::MessageFeed::MessageFeed(const Element* ri, QObject* parent):
     rosterItem(ri),
     syncState(incomplete),
     uploads(),
-    downloads()
+    downloads(),
+    unreadMessages(new std::set<QString>()),
+    observersAmount(0)
 {
 }
 
 Models::MessageFeed::~MessageFeed()
 {
+    delete unreadMessages;
+    
     for (Shared::Message* message : storage) {
         delete message;
     }
@@ -75,6 +79,14 @@ void Models::MessageFeed::addMessage(const Shared::Message& msg)
     beginInsertRows(QModelIndex(), position, position);
     storage.insert(copy);
     endInsertRows();
+    
+    emit newMessage(msg);
+    
+    if (observersAmount == 0 && !msg.getForwarded()) {      //not to notify when the message is delivered by the carbon copy
+        unreadMessages->insert(msg.getId());                //cuz it could be my own one or the one I read on another device
+        emit unreadMessagesCountChanged();
+        emit unnoticedMessage(msg);
+    }
 }
 
 void Models::MessageFeed::changeMessage(const QString& id, const QMap<QString, QVariant>& data)
@@ -97,6 +109,11 @@ void Models::MessageFeed::changeMessage(const QString& id, const QMap<QString, Q
     
     if (functor.hasIdBeenModified()) {
         changeRoles.insert(MessageRoles::Id);
+        std::set<QString>::const_iterator umi = unreadMessages->find(id);
+        if (umi != unreadMessages->end()) {
+            unreadMessages->erase(umi);
+            unreadMessages->insert(msg->getId());
+        }
     }
     
     //change message is a final event in download/upload event train
@@ -258,6 +275,13 @@ QVariant Models::MessageFeed::data(const QModelIndex& index, int role) const
             case Bulk: {
                 FeedItem item;
                 item.id = msg->getId();
+                
+                std::set<QString>::const_iterator umi = unreadMessages->find(item.id);
+                if (umi != unreadMessages->end()) {
+                    unreadMessages->erase(umi);
+                    emit unreadMessagesCount();
+                }
+                
                 item.sentByMe = sentByMe(*msg);
                 item.date = msg->getTime();
                 item.state = msg->getState();
@@ -308,7 +332,7 @@ int Models::MessageFeed::rowCount(const QModelIndex& parent) const
 
 unsigned int Models::MessageFeed::unreadMessagesCount() const
 {
-    return storage.size(); //let's say they are all new for now =)
+    return unreadMessages->size();
 }
 
 bool Models::MessageFeed::canFetchMore(const QModelIndex& parent) const
@@ -456,6 +480,16 @@ void Models::MessageFeed::fileError(const QString& messageId, const QString& err
     //TODO
 }
 
+void Models::MessageFeed::incrementObservers()
+{
+    ++observersAmount;
+}
+
+void Models::MessageFeed::decrementObservers()
+{
+    --observersAmount;
+}
+
 
 QModelIndex Models::MessageFeed::modelIndexById(const QString& id) const
 {
diff --git a/ui/models/messagefeed.h b/ui/models/messagefeed.h
index c86df33..1b7bc43 100644
--- a/ui/models/messagefeed.h
+++ b/ui/models/messagefeed.h
@@ -66,10 +66,16 @@ public:
     void fileError(const QString& messageId, const QString& error, bool up);
     void fileComplete(const QString& messageId, bool up);
     
+    void incrementObservers();
+    void decrementObservers();
+    
 signals:
     void requestArchive(const QString& before);
     void requestStateChange(bool requesting);
     void fileDownloadRequest(const QString& url);
+    void unreadMessagesCountChanged();
+    void newMessage(const Shared::Message& msg);
+    void unnoticedMessage(const Shared::Message& msg);
     
 public:
     enum MessageRoles {
@@ -140,6 +146,9 @@ private:
     Progress uploads;
     Progress downloads;
     
+    std::set<QString>* unreadMessages;
+    uint16_t observersAmount;
+    
     static const QHash<int, QByteArray> roles;
 };
 
diff --git a/ui/models/roster.cpp b/ui/models/roster.cpp
index 5e3a1ac..eb02942 100644
--- a/ui/models/roster.cpp
+++ b/ui/models/roster.cpp
@@ -448,6 +448,7 @@ void Models::Roster::addContact(const QString& account, const QString& jid, cons
             contact = new Contact(acc, jid, data);
             connect(contact, &Contact::requestArchive, this, &Roster::onElementRequestArchive);
             connect(contact, &Contact::fileDownloadRequest, this, &Roster::fileDownloadRequest);
+            connect(contact, &Contact::unnoticedMessage, this, &Roster::unnoticedMessage);
             contacts.insert(std::make_pair(id, contact));
         } else {
             contact = itr->second;
@@ -785,6 +786,7 @@ void Models::Roster::addRoom(const QString& account, const QString jid, const QM
     Room* room = new Room(acc, jid, data);
     connect(room, &Contact::requestArchive, this, &Roster::onElementRequestArchive);
     connect(room, &Contact::fileDownloadRequest, this, &Roster::fileDownloadRequest);
+    connect(room, &Contact::unnoticedMessage, this, &Roster::unnoticedMessage);
     rooms.insert(std::make_pair(id, room));
     acc->appendChild(room);
 }
diff --git a/ui/models/roster.h b/ui/models/roster.h
index 775d8de..10da0fb 100644
--- a/ui/models/roster.h
+++ b/ui/models/roster.h
@@ -92,6 +92,7 @@ public:
 signals:
     void requestArchive(const QString& account, const QString& jid, const QString& before);
     void fileDownloadRequest(const QString& url);
+    void unnoticedMessage(const QString& account, const Shared::Message& msg);
     
 private:
     Element* getElement(const ElId& id);
diff --git a/ui/squawk.cpp b/ui/squawk.cpp
index 05f8c87..d7085aa 100644
--- a/ui/squawk.cpp
+++ b/ui/squawk.cpp
@@ -59,6 +59,7 @@ Squawk::Squawk(QWidget *parent) :
     connect(m_ui->roster, &QTreeView::customContextMenuRequested, this, &Squawk::onRosterContextMenu);
     connect(m_ui->roster, &QTreeView::collapsed, this, &Squawk::onItemCollepsed);
     connect(m_ui->roster->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &Squawk::onRosterSelectionChanged);
+    connect(&rosterModel, &Models::Roster::unnoticedMessage, this, &Squawk::onUnnoticedMessage);
     
     connect(rosterModel.accountsModel, &Models::Accounts::sizeChanged, this, &Squawk::onAccountsSizeChanged);
     connect(&rosterModel, &Models::Roster::requestArchive, this, &Squawk::onRequestArchive);
@@ -410,36 +411,13 @@ void Squawk::fileUploadComplete(const std::list<Shared::MessageInfo> msgs, const
 
 void Squawk::accountMessage(const QString& account, const Shared::Message& data)
 {
-    const QString& from = data.getPenPalJid();
-    Models::Roster::ElId id({account, from});
-    Conversations::iterator itr = conversations.find(id);
-    bool found = false;
-    
     rosterModel.addMessage(account, data);
-    
-    if (currentConversation != 0 && currentConversation->getId() == id) {
-        QApplication::alert(this);
-        if (!isVisible() && !data.getForwarded()) {
-            notify(account, data);
-        }
-        found = true;
-    }
-    
-    if (itr != conversations.end()) {
-        Conversation* conv = itr->second;
-        QApplication::alert(conv);
-        if (!conv->isVisible() && !data.getForwarded()) {
-            notify(account, data);
-        }
-        found = true;
-    }
-    
-    if (!found) {
-        if (!data.getForwarded()) {
-            QApplication::alert(this);
-            notify(account, data);
-        }
-    }
+}
+
+void Squawk::onUnnoticedMessage(const QString& account, const Shared::Message& msg)
+{
+    notify(account, msg);             //Telegram does this way - notifies even if the app is visible
+    QApplication::alert(this);
 }
 
 void Squawk::changeMessage(const QString& account, const QString& jid, const QString& id, const QMap<QString, QVariant>& data)
@@ -935,6 +913,7 @@ void Squawk::subscribeConversation(Conversation* conv)
 {
     connect(conv, &Conversation::destroyed, this, &Squawk::onConversationClosed);
     connect(conv, &Conversation::sendMessage, this, &Squawk::onConversationMessage);
+    connect(conv, &Conversation::notifyableMessage, this, &Squawk::notify);
 }
 
 void Squawk::onRosterSelectionChanged(const QModelIndex& current, const QModelIndex& previous)
diff --git a/ui/squawk.h b/ui/squawk.h
index ada13fc..cda7c8c 100644
--- a/ui/squawk.h
+++ b/ui/squawk.h
@@ -128,6 +128,8 @@ private:
     
 protected:
     void closeEvent(QCloseEvent * event) override;
+    
+protected slots:
     void notify(const QString& account, const Shared::Message& msg);
     
 private slots:
@@ -153,6 +155,8 @@ private slots:
     void onRosterSelectionChanged(const QModelIndex& current, const QModelIndex& previous);
     void onContextAboutToHide();
     
+    void onUnnoticedMessage(const QString& account, const Shared::Message& msg);
+    
 private:
     void checkNextAccountForPassword();
     void onPasswordPromptDone();
diff --git a/ui/widgets/chat.cpp b/ui/widgets/chat.cpp
index 1b20e86..052d83d 100644
--- a/ui/widgets/chat.cpp
+++ b/ui/widgets/chat.cpp
@@ -71,15 +71,14 @@ Shared::Message Chat::createMessage() const
     return msg;
 }
 
-// TODO
-// void Chat::addMessage(const Shared::Message& data)
-// {
-//     Conversation::addMessage(data);
-//     
-//     if (!data.getOutgoing()) {                          //TODO need to check if that was the last message
-//         const QString& res = data.getPenPalResource();
-//         if (res.size() > 0) {
-//             setPalResource(res);
-//         }
-//     }
-// }
+void Chat::onMessage(const Shared::Message& data)
+{
+    Conversation::onMessage(data);
+    
+    if (!data.getOutgoing()) {
+        const QString& res = data.getPenPalResource();
+        if (res.size() > 0) {
+            setPalResource(res);
+        }
+    }
+}
diff --git a/ui/widgets/chat.h b/ui/widgets/chat.h
index c0be972..78e6bec 100644
--- a/ui/widgets/chat.h
+++ b/ui/widgets/chat.h
@@ -34,14 +34,13 @@ class Chat : public Conversation
 public:
     Chat(Models::Account* acc, Models::Contact* p_contact, QWidget* parent = 0);
     ~Chat();
-    
-    //void addMessage(const Shared::Message & data) override;
 
 protected slots:
     void onContactChanged(Models::Item* item, int row, int col);
     
 protected:
     Shared::Message createMessage() const override;
+    void onMessage(const Shared::Message& msg) override;
     
 private:
     void updateState();
diff --git a/ui/widgets/conversation.cpp b/ui/widgets/conversation.cpp
index d6e3c9a..7e4b138 100644
--- a/ui/widgets/conversation.cpp
+++ b/ui/widgets/conversation.cpp
@@ -48,8 +48,6 @@ Conversation::Conversation(bool muc, Models::Account* acc, Models::Element* el,
     delegate(new MessageDelegate(this)),
     scroll(down),
     manualSliderChange(false),
-    requestingHistory(false),
-    everShown(false),
     tsb(QApplication::style()->styleHint(QStyle::SH_ScrollBar_Transient) == 1)
 {
     m_ui->setupUi(this);
@@ -57,8 +55,11 @@ Conversation::Conversation(bool muc, Models::Account* acc, Models::Element* el,
     feed->setItemDelegate(delegate);
     delegate->initializeFonts(feed->getFont());
     feed->setModel(el->feed);
+    el->feed->incrementObservers();
     m_ui->widget->layout()->addWidget(feed);
     
+    connect(el->feed, &Models::MessageFeed::newMessage, this, &Conversation::onFeedMessage);
+    
     connect(acc, &Models::Account::childChanged, this, &Conversation::onAccountChanged);
     
     filesLayout = new FlowLayout(m_ui->filesPanel, 0);
@@ -69,9 +70,6 @@ Conversation::Conversation(bool muc, Models::Account* acc, Models::Element* el,
     
     connect(&ker, &KeyEnterReceiver::enterPressed, this, &Conversation::onEnterPressed);
     connect(m_ui->sendButton, &QPushButton::clicked, this, &Conversation::onEnterPressed);
-    //connect(line, &MessageLine::downloadFile, this, &Conversation::downloadFile);
-    //connect(line, &MessageLine::uploadFile, this, qOverload<const Shared::Message&, const QString&>(&Conversation::sendMessage));
-    //connect(line, &MessageLine::requestLocalFile, this, &Conversation::requestLocalFile);
     connect(m_ui->attachButton, &QPushButton::clicked, this, &Conversation::onAttach);
     connect(m_ui->clearButton, &QPushButton::clicked, this, &Conversation::onClearButton);
     connect(m_ui->messageEditor->document()->documentLayout(), &QAbstractTextDocumentLayout::documentSizeChanged, 
@@ -121,18 +119,19 @@ Conversation::Conversation(bool muc, Models::Account* acc, Models::Element* el,
 
 Conversation::~Conversation()
 {
+    element->feed->decrementObservers();
 }
 
 void Conversation::onAccountChanged(Models::Item* item, int row, int col)
 {
     if (item == account) {
-        if (col == 2 && account->getState() == Shared::ConnectionState::connected) {
-            if (!requestingHistory) {
-                requestingHistory = true;
+        if (col == 2 && account->getState() == Shared::ConnectionState::connected) {        //to request the history when we're back online after reconnect
+            //if (!requestingHistory) {
+                //requestingHistory = true;
                 //line->showBusyIndicator();
                 //emit requestArchive("");
                 //scroll = down;
-            }
+            //}
         }
     }
 }
@@ -223,21 +222,6 @@ void Conversation::onEnterPressed()
     }
 }
 
-void Conversation::showEvent(QShowEvent* event)
-{
-    if (!everShown) {
-        everShown = true;
-//         line->showBusyIndicator();
-        requestingHistory = true;
-        scroll = keep;
-        emit requestArchive("");
-    }
-    emit shown();
-    
-    QWidget::showEvent(event);
-    
-}
-
 void Conversation::onAttach()
 {
     QFileDialog* d = new QFileDialog(this, tr("Chose a file to send"));
@@ -265,21 +249,6 @@ void Conversation::setStatus(const QString& status)
     statusLabel->setText(Shared::processMessageBody(status));
 }
 
-void Conversation::responseFileProgress(const QString& messageId, qreal progress)
-{
-//     line->fileProgress(messageId, progress);
-}
-
-void Conversation::fileError(const QString& messageId, const QString& error)
-{
-//     line->fileError(messageId, error);
-}
-
-void Conversation::responseLocalFile(const QString& messageId, const QString& path)
-{
-//     line->responseLocalFile(messageId, path);
-}
-
 Models::Roster::ElId Conversation::getId() const
 {
     return {getAccount(), getJid()};
@@ -416,3 +385,18 @@ Shared::Message Conversation::createMessage() const
     return msg;
 }
 
+void Conversation::onFeedMessage(const Shared::Message& msg)
+{
+    this->onMessage(msg);
+}
+
+void Conversation::onMessage(const Shared::Message& msg)
+{
+    qDebug() << window()->windowState();
+    if (!msg.getForwarded()) {
+        QApplication::alert(this);
+        if (window()->windowState().testFlag(Qt::WindowMinimized)) {
+            emit notifyableMessage(getAccount(), msg);
+        }
+    }
+}
diff --git a/ui/widgets/conversation.h b/ui/widgets/conversation.h
index b506e39..1c81d31 100644
--- a/ui/widgets/conversation.h
+++ b/ui/widgets/conversation.h
@@ -68,10 +68,6 @@ public:
     Models::Roster::ElId getId() const;
     
     void setPalResource(const QString& res);
-    void showEvent(QShowEvent * event) override;
-    void responseLocalFile(const QString& messageId, const QString& path);
-    void fileError(const QString& messageId, const QString& error);
-    void responseFileProgress(const QString& messageId, qreal progress);
     virtual void setAvatar(const QString& path);
     void setFeedFrames(bool top, bool right, bool bottom, bool left);
     
@@ -81,6 +77,7 @@ signals:
     void shown();
     void requestLocalFile(const QString& messageId, const QString& url);
     void downloadFile(const QString& messageId, const QString& url);
+    void notifyableMessage(const QString& account, const Shared::Message& msg);
     
 protected:
     virtual void setName(const QString& name);
@@ -93,6 +90,7 @@ protected:
     void dragEnterEvent(QDragEnterEvent* event) override;
     void dragLeaveEvent(QDragLeaveEvent* event) override;
     void dropEvent(QDropEvent* event) override;
+    virtual void onMessage(const Shared::Message& msg);
     
 protected slots:
     void onEnterPressed();
@@ -102,6 +100,7 @@ protected slots:
     void onClearButton();
     void onTextEditDocSizeChanged(const QSizeF& size);
     void onAccountChanged(Models::Item* item, int row, int col);
+    void onFeedMessage(const Shared::Message& msg);
     
 public:
     const bool isMuc;
@@ -128,8 +127,6 @@ protected:
     MessageDelegate* delegate;
     Scroll scroll;
     bool manualSliderChange;
-    bool requestingHistory;
-    bool everShown;
     bool tsb;           //transient scroll bars
 };
 

From 50190f3eac1c013864d3b9ae9980aa07ae92f0db Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Wed, 28 Apr 2021 23:26:19 +0300
Subject: [PATCH 104/281] handled a case when user removes downloaded file,
 minor optimizations on message changing

---
 core/account.cpp                 |  3 ++
 core/account.h                   |  1 +
 core/handlers/messagehandler.cpp | 15 +++++++
 core/handlers/messagehandler.h   |  1 +
 core/networkaccess.cpp           |  5 +++
 core/networkaccess.h             |  1 +
 core/squawk.cpp                  | 17 ++++++++
 core/squawk.h                    |  1 +
 core/urlstorage.cpp              |  1 +
 main.cpp                         |  1 +
 shared/message.cpp               | 27 +++++++-----
 ui/models/element.cpp            |  1 +
 ui/models/element.h              |  1 +
 ui/models/messagefeed.cpp        | 75 ++++++++++++++++++++------------
 ui/models/messagefeed.h          |  2 +
 ui/models/roster.cpp             |  2 +
 ui/models/roster.h               |  1 +
 ui/squawk.cpp                    |  1 +
 ui/squawk.h                      |  1 +
 ui/utils/feedview.cpp            | 11 +++++
 ui/utils/feedview.h              |  1 +
 ui/utils/messagedelegate.cpp     |  6 ++-
 ui/utils/messagedelegate.h       |  1 +
 23 files changed, 136 insertions(+), 40 deletions(-)

diff --git a/core/account.cpp b/core/account.cpp
index da7f25c..5ce29ee 100644
--- a/core/account.cpp
+++ b/core/account.cpp
@@ -920,3 +920,6 @@ void Core::Account::onContactHistoryResponse(const std::list<Shared::Message>& l
     }
     emit responseArchive(contact->jid, list, last);
 }
+
+void Core::Account::requestChangeMessage(const QString& jid, const QString& messageId, const QMap<QString, QVariant>& data){
+    mh->requestChangeMessage(jid, messageId, data);}
diff --git a/core/account.h b/core/account.h
index 8b0839b..ce3b754 100644
--- a/core/account.h
+++ b/core/account.h
@@ -96,6 +96,7 @@ public:
     void addContactToGroupRequest(const QString& jid, const QString& groupName);
     void removeContactFromGroupRequest(const QString& jid, const QString& groupName);
     void renameContactRequest(const QString& jid, const QString& newName);
+    void requestChangeMessage(const QString& jid, const QString& messageId, const QMap<QString, QVariant>& data);
     
     void setRoomJoined(const QString& jid, bool joined);
     void setRoomAutoJoin(const QString& jid, bool joined);
diff --git a/core/handlers/messagehandler.cpp b/core/handlers/messagehandler.cpp
index 171a424..0bb84be 100644
--- a/core/handlers/messagehandler.cpp
+++ b/core/handlers/messagehandler.cpp
@@ -429,3 +429,18 @@ void Core::MessageHandler::sendMessageWithLocalUploadedFile(Shared::Message msg,
     performSending(msg);
     //TODO removal/progress update
 }
+
+void Core::MessageHandler::requestChangeMessage(const QString& jid, const QString& messageId, const QMap<QString, QVariant>& data)
+{
+    RosterItem* cnt = acc->rh->getRosterItem(jid);
+    if (cnt != 0) {
+        QMap<QString, QVariant>::const_iterator itr = data.find("attachPath");
+        if (data.size() == 1 && itr != data.end()) {
+            cnt->changeMessage(messageId, data);
+            emit acc->changeMessage(jid, messageId, data);
+        } else {
+            qDebug() << "A request to change message" << messageId << "of conversation" << jid << "with following data" << data;
+            qDebug() << "nothing but the changing of the local path is supported yet in this method, skipping";
+        }
+    }
+}
diff --git a/core/handlers/messagehandler.h b/core/handlers/messagehandler.h
index 8893921..28fc783 100644
--- a/core/handlers/messagehandler.h
+++ b/core/handlers/messagehandler.h
@@ -58,6 +58,7 @@ public slots:
     void onDownloadFileComplete(const std::list<Shared::MessageInfo>& msgs, const QString& path);
     void onUploadFileComplete(const std::list<Shared::MessageInfo>& msgs, const QString& path);
     void onLoadFileError(const std::list<Shared::MessageInfo>& msgs, const QString& path, bool up);
+    void requestChangeMessage(const QString& jid, const QString& messageId, const QMap<QString, QVariant>& data);
     
 private:
     bool handleChatMessage(const QXmppMessage& msg, bool outgoing = false, bool forwarded = false, bool guessing = false);
diff --git a/core/networkaccess.cpp b/core/networkaccess.cpp
index d771dc6..eece379 100644
--- a/core/networkaccess.cpp
+++ b/core/networkaccess.cpp
@@ -493,3 +493,8 @@ QString Core::NetworkAccess::addMessageAndCheckForPath(const QString& url, const
 {
     return storage.addMessageAndCheckForPath(url, account, jid, id);
 }
+
+std::list<Shared::MessageInfo> Core::NetworkAccess::reportPathInvalid(const QString& path)
+{
+    return storage.deletedFile(path);
+}
diff --git a/core/networkaccess.h b/core/networkaccess.h
index a116e6d..5b9eae2 100644
--- a/core/networkaccess.h
+++ b/core/networkaccess.h
@@ -53,6 +53,7 @@ public:
     QString addMessageAndCheckForPath(const QString& url, const QString& account, const QString& jid, const QString& id);
     void uploadFile(const Shared::MessageInfo& info, const QString& path, const QUrl& put, const QUrl& get, const QMap<QString, QString> headers);
     bool checkAndAddToUploading(const QString& acc, const QString& jid, const QString id, const QString path);
+    std::list<Shared::MessageInfo> reportPathInvalid(const QString& path);
     
 signals:
     void loadFileProgress(const std::list<Shared::MessageInfo>& msgs, qreal value, bool up);
diff --git a/core/squawk.cpp b/core/squawk.cpp
index 83fedb6..411d4ab 100644
--- a/core/squawk.cpp
+++ b/core/squawk.cpp
@@ -751,3 +751,20 @@ void Core::Squawk::onAccountUploadFileError(const QString& jid, const QString id
     Account* acc = static_cast<Account*>(sender());
     emit fileError({{acc->getName(), jid, id}}, errorText, true);
 }
+
+void Core::Squawk::onLocalPathInvalid(const QString& path)
+{
+    std::list<Shared::MessageInfo> list = network.reportPathInvalid(path);
+    
+    QMap<QString, QVariant> data({
+        {"attachPath", ""}
+    });
+    for (const Shared::MessageInfo& info : list) {
+        AccountsMap::const_iterator itr = amap.find(info.account);
+        if (itr != amap.end()) {
+            itr->second->requestChangeMessage(info.jid, info.messageId, data);
+        } else {
+            qDebug() << "Reacting on failure to reach file" << path << "there was an attempt to change message in account" << info.account << "which doesn't exist, skipping";
+        }
+    }
+}
diff --git a/core/squawk.h b/core/squawk.h
index 36301d8..25fdbda 100644
--- a/core/squawk.h
+++ b/core/squawk.h
@@ -121,6 +121,7 @@ public slots:
     void requestVCard(const QString& account, const QString& jid);
     void uploadVCard(const QString& account, const Shared::VCard& card);
     void responsePassword(const QString& account, const QString& password);
+    void onLocalPathInvalid(const QString& path);
     
 private:
     typedef std::deque<Account*> Accounts;
diff --git a/core/urlstorage.cpp b/core/urlstorage.cpp
index 1ce7957..f59ff62 100644
--- a/core/urlstorage.cpp
+++ b/core/urlstorage.cpp
@@ -348,6 +348,7 @@ std::list<Shared::MessageInfo> Core::UrlStorage::deletedFile(const QString& path
             url = QString(surl.c_str());
         } else if (rc == MDB_NOTFOUND) {
             qDebug() << "Have been asked to remove file" << path << ", which isn't in the database, skipping";
+            mdb_txn_abort(txn);
             return list;
         } else {
             throw Archive::Unknown(name.toStdString(), mdb_strerror(rc));
diff --git a/main.cpp b/main.cpp
index 45232cf..210dd70 100644
--- a/main.cpp
+++ b/main.cpp
@@ -116,6 +116,7 @@ int main(int argc, char *argv[])
     QObject::connect(&w, &Squawk::requestVCard, squawk, &Core::Squawk::requestVCard);
     QObject::connect(&w, &Squawk::uploadVCard, squawk, &Core::Squawk::uploadVCard);
     QObject::connect(&w, &Squawk::responsePassword, squawk, &Core::Squawk::responsePassword);
+    QObject::connect(&w, &Squawk::localPathInvalid, squawk, &Core::Squawk::onLocalPathInvalid);
     
     QObject::connect(squawk, &Core::Squawk::newAccount, &w, &Squawk::newAccount);
     QObject::connect(squawk, &Core::Squawk::addContact, &w, &Squawk::addContact);
diff --git a/shared/message.cpp b/shared/message.cpp
index 6728bbe..e63b7d2 100644
--- a/shared/message.cpp
+++ b/shared/message.cpp
@@ -394,18 +394,21 @@ bool Shared::Message::change(const QMap<QString, QVariant>& data)
     
     itr = data.find("body");
     if (itr != data.end()) {
-        QMap<QString, QVariant>::const_iterator dItr = data.find("stamp");
-        QDateTime correctionDate;
-        if (dItr != data.end()) {
-            correctionDate = dItr.value().toDateTime();
-        } else {
-            correctionDate = QDateTime::currentDateTimeUtc();      //in case there is no information about time of this correction it's applied
-        }
-        if (!edited || lastModified < correctionDate) {
-            originalMessage = body;
-            lastModified = correctionDate;
-            setBody(itr.value().toString());
-            setEdited(true);
+        QString b = itr.value().toString();
+        if (body != b) {
+            QMap<QString, QVariant>::const_iterator dItr = data.find("stamp");
+            QDateTime correctionDate;
+            if (dItr != data.end()) {
+                correctionDate = dItr.value().toDateTime();
+            } else {
+                correctionDate = QDateTime::currentDateTimeUtc();      //in case there is no information about time of this correction it's applied
+            }
+            if (!edited || lastModified < correctionDate) {
+                originalMessage = body;
+                lastModified = correctionDate;
+                setBody(body);
+                setEdited(true);
+            }
         }
     }
     
diff --git a/ui/models/element.cpp b/ui/models/element.cpp
index 4bdc3d7..4e741a4 100644
--- a/ui/models/element.cpp
+++ b/ui/models/element.cpp
@@ -33,6 +33,7 @@ Models::Element::Element(Type p_type, const Models::Account* acc, const QString&
     connect(feed, &MessageFeed::fileDownloadRequest, this, &Element::fileDownloadRequest);
     connect(feed, &MessageFeed::unreadMessagesCountChanged, this, &Element::onFeedUnreadMessagesCountChanged);
     connect(feed, &MessageFeed::unnoticedMessage, this, &Element::onFeedUnnoticedMessage);
+    connect(feed, &MessageFeed::localPathInvalid, this, &Element::localPathInvalid);
     
     QMap<QString, QVariant>::const_iterator itr = data.find("avatarState");
     if (itr != data.end()) {
diff --git a/ui/models/element.h b/ui/models/element.h
index 1818405..af44791 100644
--- a/ui/models/element.h
+++ b/ui/models/element.h
@@ -51,6 +51,7 @@ signals:
     void requestArchive(const QString& before);
     void fileDownloadRequest(const QString& url);
     void unnoticedMessage(const QString& account, const Shared::Message& msg);
+    void localPathInvalid(const QString& path);
     
 protected:
     void setJid(const QString& p_jid);
diff --git a/ui/models/messagefeed.cpp b/ui/models/messagefeed.cpp
index 07dfe0a..d1024b8 100644
--- a/ui/models/messagefeed.cpp
+++ b/ui/models/messagefeed.cpp
@@ -116,37 +116,39 @@ void Models::MessageFeed::changeMessage(const QString& id, const QMap<QString, Q
         }
     }
     
-    //change message is a final event in download/upload event train
-    //only after changeMessage we can consider the download is done
-    Progress::const_iterator dItr = downloads.find(id);
-    bool attachOrError = changeRoles.count(MessageRoles::Attach) > 0 || changeRoles.count(MessageRoles::Error);
-    if (dItr != downloads.end()) {
-        if (attachOrError) {
-            downloads.erase(dItr);
-        } else if (changeRoles.count(MessageRoles::Id) > 0) {
-            qreal progress = dItr->second;
-            downloads.erase(dItr);
-            downloads.insert(std::make_pair(msg->getId(), progress));
-        }
-    } else {
-        dItr = uploads.find(id);
-        if (dItr != uploads.end()) {
+    if (changeRoles.size() > 0) {
+        //change message is a final event in download/upload event train
+        //only after changeMessage we can consider the download is done
+        Progress::const_iterator dItr = downloads.find(id);
+        bool attachOrError = changeRoles.count(MessageRoles::Attach) > 0 || changeRoles.count(MessageRoles::Error);
+        if (dItr != downloads.end()) {
             if (attachOrError) {
-                uploads.erase(dItr);
+                downloads.erase(dItr);
             } else if (changeRoles.count(MessageRoles::Id) > 0) {
                 qreal progress = dItr->second;
-                uploads.erase(dItr);
-                uploads.insert(std::make_pair(msg->getId(), progress));
+                downloads.erase(dItr);
+                downloads.insert(std::make_pair(msg->getId(), progress));
+            }
+        } else {
+            dItr = uploads.find(id);
+            if (dItr != uploads.end()) {
+                if (attachOrError) {
+                    uploads.erase(dItr);
+                } else if (changeRoles.count(MessageRoles::Id) > 0) {
+                    qreal progress = dItr->second;
+                    uploads.erase(dItr);
+                    uploads.insert(std::make_pair(msg->getId(), progress));
+                }
             }
         }
+        
+        QVector<int> cr;
+        for (MessageRoles role : changeRoles) {
+            cr.push_back(role);
+        }
+        
+        emit dataChanged(index, index, cr);
     }
-    
-    QVector<int> cr;
-    for (MessageRoles role : changeRoles) {
-        cr.push_back(role);
-    }
-    
-    emit dataChanged(index, index, cr);
 }
 
 std::set<Models::MessageFeed::MessageRoles> Models::MessageFeed::detectChanges(const Shared::Message& msg, const QMap<QString, QVariant>& data) const
@@ -174,13 +176,13 @@ std::set<Models::MessageFeed::MessageRoles> Models::MessageFeed::detectChanges(c
     
     if (state == Shared::Message::State::error) {
         itr = data.find("errorText");
-        if (itr != data.end()) {
+        if (itr != data.end() && itr.value().toString() != msg.getErrorText()) {
             roles.insert(MessageRoles::Error);
         }
     }
     
     itr = data.find("body");
-    if (itr != data.end()) {
+    if (itr != data.end() && itr.value().toString() != msg.getBody()) {
         QMap<QString, QVariant>::const_iterator dItr = data.find("stamp");
         QDateTime correctionDate;
         if (dItr != data.end()) {
@@ -522,3 +524,22 @@ QModelIndex Models::MessageFeed::modelIndexByTime(const QString& id, const QDate
     
     return QModelIndex();
 }
+
+void Models::MessageFeed::reportLocalPathInvalid(const QString& messageId)
+{
+    StorageById::iterator itr = indexById.find(messageId);
+    if (itr == indexById.end()) {
+        qDebug() << "received a command to change a message, but the message couldn't be found, skipping";
+        return;
+    }
+    
+    Shared::Message* msg = *itr;
+    
+    emit localPathInvalid(msg->getAttachPath());
+    
+    //gonna change the message in current model right away, to prevent spam on each attemt to draw element
+    QModelIndex index = modelIndexByTime(messageId, msg->getTime());
+    msg->setAttachPath("");
+    
+    emit dataChanged(index, index, {MessageRoles::Attach});
+}
diff --git a/ui/models/messagefeed.h b/ui/models/messagefeed.h
index 1b7bc43..5c5c019 100644
--- a/ui/models/messagefeed.h
+++ b/ui/models/messagefeed.h
@@ -60,6 +60,7 @@ public:
     void downloadAttachment(const QString& messageId);
     void uploadAttachment(const QString& messageId);
     bool registerUpload(const QString& messageId);
+    void reportLocalPathInvalid(const QString& messageId);
     
     unsigned int unreadMessagesCount() const;
     void fileProgress(const QString& messageId, qreal value, bool up);
@@ -76,6 +77,7 @@ signals:
     void unreadMessagesCountChanged();
     void newMessage(const Shared::Message& msg);
     void unnoticedMessage(const Shared::Message& msg);
+    void localPathInvalid(const QString& path);
     
 public:
     enum MessageRoles {
diff --git a/ui/models/roster.cpp b/ui/models/roster.cpp
index eb02942..d70d9d1 100644
--- a/ui/models/roster.cpp
+++ b/ui/models/roster.cpp
@@ -449,6 +449,7 @@ void Models::Roster::addContact(const QString& account, const QString& jid, cons
             connect(contact, &Contact::requestArchive, this, &Roster::onElementRequestArchive);
             connect(contact, &Contact::fileDownloadRequest, this, &Roster::fileDownloadRequest);
             connect(contact, &Contact::unnoticedMessage, this, &Roster::unnoticedMessage);
+            connect(contact, &Contact::localPathInvalid, this, &Roster::localPathInvalid);
             contacts.insert(std::make_pair(id, contact));
         } else {
             contact = itr->second;
@@ -787,6 +788,7 @@ void Models::Roster::addRoom(const QString& account, const QString jid, const QM
     connect(room, &Contact::requestArchive, this, &Roster::onElementRequestArchive);
     connect(room, &Contact::fileDownloadRequest, this, &Roster::fileDownloadRequest);
     connect(room, &Contact::unnoticedMessage, this, &Roster::unnoticedMessage);
+    connect(room, &Contact::localPathInvalid, this, &Roster::localPathInvalid);
     rooms.insert(std::make_pair(id, room));
     acc->appendChild(room);
 }
diff --git a/ui/models/roster.h b/ui/models/roster.h
index 10da0fb..09261cd 100644
--- a/ui/models/roster.h
+++ b/ui/models/roster.h
@@ -93,6 +93,7 @@ signals:
     void requestArchive(const QString& account, const QString& jid, const QString& before);
     void fileDownloadRequest(const QString& url);
     void unnoticedMessage(const QString& account, const Shared::Message& msg);
+    void localPathInvalid(const QString& path);
     
 private:
     Element* getElement(const ElId& id);
diff --git a/ui/squawk.cpp b/ui/squawk.cpp
index d7085aa..fb79592 100644
--- a/ui/squawk.cpp
+++ b/ui/squawk.cpp
@@ -64,6 +64,7 @@ Squawk::Squawk(QWidget *parent) :
     connect(rosterModel.accountsModel, &Models::Accounts::sizeChanged, this, &Squawk::onAccountsSizeChanged);
     connect(&rosterModel, &Models::Roster::requestArchive, this, &Squawk::onRequestArchive);
     connect(&rosterModel, &Models::Roster::fileDownloadRequest, this, &Squawk::fileDownloadRequest);
+    connect(&rosterModel, &Models::Roster::localPathInvalid, this, &Squawk::localPathInvalid);
     connect(contextMenu, &QMenu::aboutToHide, this, &Squawk::onContextAboutToHide);
     //m_ui->mainToolBar->addWidget(m_ui->comboBox);
     
diff --git a/ui/squawk.h b/ui/squawk.h
index cda7c8c..fa92df7 100644
--- a/ui/squawk.h
+++ b/ui/squawk.h
@@ -79,6 +79,7 @@ signals:
     void requestVCard(const QString& account, const QString& jid);
     void uploadVCard(const QString& account, const Shared::VCard& card);
     void responsePassword(const QString& account, const QString& password);
+    void localPathInvalid(const QString& path);
     
 public slots:
     void readSettings();
diff --git a/ui/utils/feedview.cpp b/ui/utils/feedview.cpp
index 550b8d4..05302b0 100644
--- a/ui/utils/feedview.cpp
+++ b/ui/utils/feedview.cpp
@@ -304,6 +304,7 @@ void FeedView::setItemDelegate(QAbstractItemDelegate* delegate)
     if (specialDelegate) {
         MessageDelegate* del = static_cast<MessageDelegate*>(itemDelegate());
         disconnect(del, &MessageDelegate::buttonPushed, this, &FeedView::onMessageButtonPushed);
+        disconnect(del, &MessageDelegate::invalidPath, this, &FeedView::onMessageInvalidPath);
     }
     
     QAbstractItemView::setItemDelegate(delegate);
@@ -312,6 +313,7 @@ void FeedView::setItemDelegate(QAbstractItemDelegate* delegate)
     if (del) {
         specialDelegate = true;
         connect(del, &MessageDelegate::buttonPushed, this, &FeedView::onMessageButtonPushed);
+        connect(del, &MessageDelegate::invalidPath, this, &FeedView::onMessageInvalidPath);
     } else {
         specialDelegate = false;
     }
@@ -341,3 +343,12 @@ void FeedView::onMessageButtonPushed(const QString& messageId, bool download)
         }
     }
 }
+
+void FeedView::onMessageInvalidPath(const QString& messageId)
+{
+    if (specialModel) {
+        Models::MessageFeed* feed = static_cast<Models::MessageFeed*>(model());
+        feed->reportLocalPathInvalid(messageId);
+    }
+}
+
diff --git a/ui/utils/feedview.h b/ui/utils/feedview.h
index 05e3025..8361ec9 100644
--- a/ui/utils/feedview.h
+++ b/ui/utils/feedview.h
@@ -55,6 +55,7 @@ protected slots:
     void verticalScrollbarValueChanged(int value) override;
     void dataChanged(const QModelIndex & topLeft, const QModelIndex & bottomRight, const QVector<int> & roles) override;
     void onMessageButtonPushed(const QString& messageId, bool download);
+    void onMessageInvalidPath(const QString& messageId);
     
 protected:
     int verticalOffset() const override;
diff --git a/ui/utils/messagedelegate.cpp b/ui/utils/messagedelegate.cpp
index 910db72..c98710c 100644
--- a/ui/utils/messagedelegate.cpp
+++ b/ui/utils/messagedelegate.cpp
@@ -310,7 +310,11 @@ void MessageDelegate::paintPreview(const Models::FeedItem& data, QPainter* paint
             start = option.rect.topLeft();
         }
         QImage img(data.attach.localPath);
-        painter->drawImage(QRect(start, size), img);
+        if (img.isNull()) {
+            emit invalidPath(data.id);
+        } else {
+            painter->drawImage(QRect(start, size), img);
+        }
         
         option.rect.adjust(0, size.height() + textMargin, 0, 0);
     }
diff --git a/ui/utils/messagedelegate.h b/ui/utils/messagedelegate.h
index ed42e2f..97822eb 100644
--- a/ui/utils/messagedelegate.h
+++ b/ui/utils/messagedelegate.h
@@ -55,6 +55,7 @@ public:
     
 signals:
     void buttonPushed(const QString& messageId, bool download) const;
+    void invalidPath(const QString& messageId) const;
     
 protected:
     void paintButton(QPushButton* btn, QPainter* painter, bool sentByMe, QStyleOptionViewItem& option) const;

From 0973cb2991b3f8cbc42fd8f8c007729a668b36c7 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Fri, 30 Apr 2021 23:07:00 +0300
Subject: [PATCH 105/281] first lousy attempt to make load indicator in
 feedView

---
 ui/models/messagefeed.cpp |  7 +++++++
 ui/models/messagefeed.h   | 16 ++++++++-------
 ui/utils/feedview.cpp     | 43 +++++++++++++++++++++++++++++++++++----
 ui/utils/feedview.h       |  5 +++++
 4 files changed, 60 insertions(+), 11 deletions(-)

diff --git a/ui/models/messagefeed.cpp b/ui/models/messagefeed.cpp
index d1024b8..09b11cd 100644
--- a/ui/models/messagefeed.cpp
+++ b/ui/models/messagefeed.cpp
@@ -346,6 +346,7 @@ void Models::MessageFeed::fetchMore(const QModelIndex& parent)
 {
     if (syncState == incomplete) {
         syncState = syncing;
+        emit syncStateChange(syncState);
         emit requestStateChange(true);
         
         if (storage.size() == 0) {
@@ -373,6 +374,7 @@ void Models::MessageFeed::responseArchive(const std::list<Shared::Message> list,
         } else {
             syncState = incomplete;
         }
+        emit syncStateChange(syncState);
         emit requestStateChange(false);
     }
 }
@@ -543,3 +545,8 @@ void Models::MessageFeed::reportLocalPathInvalid(const QString& messageId)
     
     emit dataChanged(index, index, {MessageRoles::Attach});
 }
+
+Models::MessageFeed::SyncState Models::MessageFeed::getSyncState() const
+{
+    return syncState;
+}
diff --git a/ui/models/messagefeed.h b/ui/models/messagefeed.h
index 5c5c019..cc833fa 100644
--- a/ui/models/messagefeed.h
+++ b/ui/models/messagefeed.h
@@ -41,7 +41,13 @@ namespace Models {
 class MessageFeed : public QAbstractListModel
 {
     Q_OBJECT
-public:
+public:    
+    enum SyncState {
+        incomplete,
+        syncing,
+        complete
+    };
+    
     MessageFeed(const Element* rosterItem, QObject *parent = nullptr);
     ~MessageFeed();
     
@@ -69,6 +75,7 @@ public:
     
     void incrementObservers();
     void decrementObservers();
+    SyncState getSyncState() const;
     
 signals:
     void requestArchive(const QString& before);
@@ -78,6 +85,7 @@ signals:
     void newMessage(const Shared::Message& msg);
     void unnoticedMessage(const Shared::Message& msg);
     void localPathInvalid(const QString& path);
+    void syncStateChange(SyncState state);
     
 public:
     enum MessageRoles {
@@ -102,12 +110,6 @@ protected:
     std::set<MessageRoles> detectChanges(const Shared::Message& msg, const QMap<QString, QVariant>& data) const;
     
 private:
-    enum SyncState {
-        incomplete,
-        syncing,
-        complete
-    };
-    
     //tags
     struct id {};
     struct time {};
diff --git a/ui/utils/feedview.cpp b/ui/utils/feedview.cpp
index 05302b0..f7b0f9d 100644
--- a/ui/utils/feedview.cpp
+++ b/ui/utils/feedview.cpp
@@ -28,6 +28,7 @@
 
 constexpr int maxMessageHeight = 10000;
 constexpr int approximateSingleMessageHeight = 20;
+constexpr int progressSize = 70;
 
 const std::set<int> FeedView::geometryChangingRoles = {
     Models::MessageFeed::Attach,
@@ -43,13 +44,18 @@ FeedView::FeedView(QWidget* parent):
     vo(0),
     specialDelegate(false),
     specialModel(false),
-    clearWidgetsMode(false)
+    clearWidgetsMode(false),
+    modelState(Models::MessageFeed::complete),
+    progress()
 {
     horizontalScrollBar()->setRange(0, 0);
     verticalScrollBar()->setSingleStep(approximateSingleMessageHeight);
     setMouseTracking(true);
     setSelectionBehavior(SelectItems);
 //     viewport()->setAttribute(Qt::WA_Hover, true);
+    
+    progress.setParent(viewport());
+    progress.resize(progressSize, progressSize);
 }
 
 FeedView::~FeedView()
@@ -293,6 +299,13 @@ void FeedView::mouseMoveEvent(QMouseEvent* event)
     QAbstractItemView::mouseMoveEvent(event);
 }
 
+void FeedView::resizeEvent(QResizeEvent* event)
+{
+    progress.move((width() - progressSize) / 2, 0);
+    
+    QAbstractItemView::resizeEvent(event);
+}
+
 
 QFont FeedView::getFont() const
 {
@@ -319,14 +332,22 @@ void FeedView::setItemDelegate(QAbstractItemDelegate* delegate)
     }
 }
 
-void FeedView::setModel(QAbstractItemModel* model)
+void FeedView::setModel(QAbstractItemModel* p_model)
 {
-    QAbstractItemView::setModel(model);
+    if (specialModel) {
+        Models::MessageFeed* feed = static_cast<Models::MessageFeed*>(model());
+        disconnect(feed, &Models::MessageFeed::syncStateChange, this, &FeedView::onModelSyncStateChange);
+    }
     
-    Models::MessageFeed* feed = dynamic_cast<Models::MessageFeed*>(model);
+    QAbstractItemView::setModel(p_model);
+    
+    Models::MessageFeed* feed = dynamic_cast<Models::MessageFeed*>(p_model);
     if (feed) {
+        onModelSyncStateChange(feed->getSyncState());
         specialModel = true;
+        connect(feed, &Models::MessageFeed::syncStateChange, this, &FeedView::onModelSyncStateChange);
     } else {
+        onModelSyncStateChange(Models::MessageFeed::complete);
         specialModel = false;
     }
 }
@@ -352,3 +373,17 @@ void FeedView::onMessageInvalidPath(const QString& messageId)
     }
 }
 
+void FeedView::onModelSyncStateChange(Models::MessageFeed::SyncState state)
+{
+    if (modelState != state) {
+        modelState = state;
+        
+        if (state == Models::MessageFeed::syncing) {
+            progress.show();
+            progress.start();
+        } else {
+            progress.stop();
+            progress.hide();
+        }
+    }
+}
diff --git a/ui/utils/feedview.h b/ui/utils/feedview.h
index 8361ec9..85ef31e 100644
--- a/ui/utils/feedview.h
+++ b/ui/utils/feedview.h
@@ -25,6 +25,7 @@
 #include <set>
 
 #include <ui/models/messagefeed.h>
+#include "progress.h"
 
 /**
  * @todo write docs
@@ -56,6 +57,7 @@ protected slots:
     void dataChanged(const QModelIndex & topLeft, const QModelIndex & bottomRight, const QVector<int> & roles) override;
     void onMessageButtonPushed(const QString& messageId, bool download);
     void onMessageInvalidPath(const QString& messageId);
+    void onModelSyncStateChange(Models::MessageFeed::SyncState state);
     
 protected:
     int verticalOffset() const override;
@@ -63,6 +65,7 @@ protected:
     void paintEvent(QPaintEvent * event) override;
     void updateGeometries() override;
     void mouseMoveEvent(QMouseEvent * event) override;
+    void resizeEvent(QResizeEvent * event) override;
     
 private:
     bool tryToCalculateGeometriesWithNoScrollbars(const QStyleOptionViewItem& option, const QAbstractItemModel* model, uint32_t totalHeight);
@@ -78,6 +81,8 @@ private:
     bool specialDelegate;
     bool specialModel;
     bool clearWidgetsMode;
+    Models::MessageFeed::SyncState modelState;
+    Progress progress;
     
     static const std::set<int> geometryChangingRoles;
     

From 216dcd29e91c84ca170c316ad5d08d2b9871bfbd Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Sun, 2 May 2021 02:03:08 +0300
Subject: [PATCH 106/281] bug fixing, better progres indicator positioning

---
 ui/utils/feedview.cpp       | 64 ++++++++++++++++++++++++++++---------
 ui/utils/feedview.h         |  1 +
 ui/widgets/conversation.cpp |  1 -
 3 files changed, 50 insertions(+), 16 deletions(-)

diff --git a/ui/utils/feedview.cpp b/ui/utils/feedview.cpp
index f7b0f9d..45cb723 100644
--- a/ui/utils/feedview.cpp
+++ b/ui/utils/feedview.cpp
@@ -68,8 +68,8 @@ QModelIndex FeedView::indexAt(const QPoint& point) const
     uint32_t y = vh - point.y() + vo;
     
     for (std::deque<Hint>::size_type i = 0; i < hints.size(); ++i) {
-        if (hints[i].offset >= y) {
-            return model()->index(i - 1, 0, rootIndex());
+        if (hints[i].offset + hints[i].height >= y) {
+            return model()->index(i, 0, rootIndex());
         }
     }
     
@@ -82,11 +82,12 @@ void FeedView::scrollTo(const QModelIndex& index, QAbstractItemView::ScrollHint
 
 QRect FeedView::visualRect(const QModelIndex& index) const
 {
-    if (!index.isValid() || index.row() >= hints.size()) {
-        qDebug() << "visualRect for" << index.row();
+    unsigned int row = index.row();
+    if (!index.isValid() || row >= hints.size()) {
+        qDebug() << "visualRect for" << row;
         return QRect();
     } else {
-        const Hint& hint = hints.at(index.row());
+        const Hint& hint = hints.at(row);
         const QWidget* vp = viewport();
         return QRect(0, vp->height() - hint.height - hint.offset + vo, vp->width(), hint.height);
     }
@@ -123,8 +124,9 @@ QRegion FeedView::visualRegionForSelection(const QItemSelection& selection) cons
 
 void FeedView::rowsInserted(const QModelIndex& parent, int start, int end)
 {
-    updateGeometries();
     QAbstractItemView::rowsInserted(parent, start, end);
+    
+    scheduleDelayedItemsLayout();
 }
 
 void FeedView::dataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight, const QVector<int>& roles)
@@ -132,7 +134,7 @@ void FeedView::dataChanged(const QModelIndex& topLeft, const QModelIndex& bottom
     if (specialDelegate) {
         for (int role : roles) {
             if (geometryChangingRoles.count(role) != 0) {
-                updateGeometries();                         //to recalculate layout only if there are some geometry changing modifications
+                scheduleDelayedItemsLayout();                         //to recalculate layout only if there are some geometry changing modifications
                 break;
             }
         }
@@ -145,11 +147,9 @@ void FeedView::updateGeometries()
     qDebug() << "updateGeometries";
     QScrollBar* bar = verticalScrollBar();
     
-    QAbstractItemView::updateGeometries();
-    
     const QStyle* st = style();
     const QAbstractItemModel* m = model();
-    QRect layoutBounds = QRect(QPoint(), maximumViewportSize());
+    QSize layoutBounds = maximumViewportSize();
     QStyleOptionViewItem option = viewOptions();
     option.rect.setHeight(maxMessageHeight);
     option.rect.setWidth(layoutBounds.width());
@@ -164,6 +164,7 @@ void FeedView::updateGeometries()
     
     if (layedOut) {
         bar->setRange(0, 0);
+        vo = 0;
     } else {
         int verticalMargin = 0;
         if (st->styleHint(QStyle::SH_ScrollView_FrameOnlyAroundContents)) {
@@ -174,7 +175,7 @@ void FeedView::updateGeometries()
             verticalMargin = verticalScrollBarExtent + frameAroundContents;
         }
         
-        layoutBounds.adjust(0, 0, -verticalMargin, 0);
+        layoutBounds.rwidth() -= verticalMargin;
         
         option.features |= QStyleOptionViewItem::WrapText;
         option.rect.setWidth(layoutBounds.width());
@@ -192,14 +193,24 @@ void FeedView::updateGeometries()
             previousOffset += height;
         }
         
-        bar->setRange(0, previousOffset - layoutBounds.height());
+        int totalHeight = previousOffset - layoutBounds.height();
+        if (modelState != Models::MessageFeed::complete) {
+            totalHeight += progressSize;
+        }
+        vo = qMax(qMin(vo, totalHeight), 0);
+        bar->setRange(0, totalHeight);
         bar->setPageStep(layoutBounds.height());
-        bar->setValue(previousOffset - layoutBounds.height() - vo);
+        bar->setValue(totalHeight - vo);
     }
     
+    positionProgress();
+    
     if (specialDelegate) {
         clearWidgetsMode = true;
     }
+    
+    
+    QAbstractItemView::updateGeometries();
 }
 
 bool FeedView::tryToCalculateGeometriesWithNoScrollbars(const QStyleOptionViewItem& option, const QAbstractItemModel* m, uint32_t totalHeight)
@@ -283,6 +294,8 @@ void FeedView::verticalScrollbarValueChanged(int value)
 {
     vo = verticalScrollBar()->maximum() - value;
     
+    positionProgress();
+    
     if (specialDelegate) {
         clearWidgetsMode = true;
     }
@@ -301,11 +314,24 @@ void FeedView::mouseMoveEvent(QMouseEvent* event)
 
 void FeedView::resizeEvent(QResizeEvent* event)
 {
-    progress.move((width() - progressSize) / 2, 0);
-    
     QAbstractItemView::resizeEvent(event);
+    
+    positionProgress();
 }
 
+void FeedView::positionProgress()
+{
+    QSize layoutBounds = maximumViewportSize();
+    int progressPosition = layoutBounds.height() - progressSize;
+    std::deque<Hint>::size_type size = hints.size();
+    if (size > 0) {
+        const Hint& hint = hints[size - 1];
+        progressPosition -= hint.offset + hint.height;
+    }
+    progressPosition += vo;
+    
+    progress.move((width() - progressSize) / 2, progressPosition);
+}
 
 QFont FeedView::getFont() const
 {
@@ -375,7 +401,11 @@ void FeedView::onMessageInvalidPath(const QString& messageId)
 
 void FeedView::onModelSyncStateChange(Models::MessageFeed::SyncState state)
 {
+    bool needToUpdateGeometry = false;
     if (modelState != state) {
+        if (state == Models::MessageFeed::complete || modelState == Models::MessageFeed::complete) {
+            needToUpdateGeometry = true;
+        }
         modelState = state;
         
         if (state == Models::MessageFeed::syncing) {
@@ -386,4 +416,8 @@ void FeedView::onModelSyncStateChange(Models::MessageFeed::SyncState state)
             progress.hide();
         }
     }
+    
+    if (needToUpdateGeometry) {
+        scheduleDelayedItemsLayout();
+    }
 }
diff --git a/ui/utils/feedview.h b/ui/utils/feedview.h
index 85ef31e..2789464 100644
--- a/ui/utils/feedview.h
+++ b/ui/utils/feedview.h
@@ -69,6 +69,7 @@ protected:
     
 private:
     bool tryToCalculateGeometriesWithNoScrollbars(const QStyleOptionViewItem& option, const QAbstractItemModel* model, uint32_t totalHeight);
+    void positionProgress();
     
 private:
     struct Hint {
diff --git a/ui/widgets/conversation.cpp b/ui/widgets/conversation.cpp
index 7e4b138..b8141c5 100644
--- a/ui/widgets/conversation.cpp
+++ b/ui/widgets/conversation.cpp
@@ -392,7 +392,6 @@ void Conversation::onFeedMessage(const Shared::Message& msg)
 
 void Conversation::onMessage(const Shared::Message& msg)
 {
-    qDebug() << window()->windowState();
     if (!msg.getForwarded()) {
         QApplication::alert(this);
         if (window()->windowState().testFlag(Qt::WindowMinimized)) {

From 05d6761baafae94efd02d6f4ec9eccea6b3f9b66 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Mon, 3 May 2021 03:35:43 +0300
Subject: [PATCH 107/281] a bit of refactor, fix the time to request next
 portion of messages in ui, fancy shadows are back!

---
 ui/CMakeLists.txt             |   4 +-
 ui/utils/dropshadoweffect.cpp | 558 +-------------------------------
 ui/utils/dropshadoweffect.h   |   2 +
 ui/utils/eb.cpp               | 579 ++++++++++++++++++++++++++++++++++
 ui/utils/eb.h                 |  34 ++
 ui/utils/feedview.cpp         |  10 +
 ui/utils/feedview.h           |   3 +
 ui/utils/shadowoverlay.cpp    |  91 ++++++
 ui/utils/shadowoverlay.h      |  58 ++++
 ui/widgets/CMakeLists.txt     |   1 +
 ui/widgets/conversation.cpp   |  74 +++--
 ui/widgets/conversation.h     |  12 +-
 12 files changed, 826 insertions(+), 600 deletions(-)
 create mode 100644 ui/utils/eb.cpp
 create mode 100644 ui/utils/eb.h
 create mode 100644 ui/utils/shadowoverlay.cpp
 create mode 100644 ui/utils/shadowoverlay.h

diff --git a/ui/CMakeLists.txt b/ui/CMakeLists.txt
index d6e29d3..4b53439 100644
--- a/ui/CMakeLists.txt
+++ b/ui/CMakeLists.txt
@@ -32,7 +32,7 @@ set(squawkUI_SRC
   models/messagefeed.cpp
   models/element.cpp
   utils/messageline.cpp
-  utils//message.cpp
+  utils/message.cpp
   utils/resizer.cpp
   utils/image.cpp
   utils/flowlayout.cpp
@@ -42,6 +42,8 @@ set(squawkUI_SRC
   utils/dropshadoweffect.cpp
   utils/feedview.cpp
   utils/messagedelegate.cpp
+  utils/eb.cpp
+  utils/shadowoverlay.cpp
 )
 
 # Tell CMake to create the helloworld executable
diff --git a/ui/utils/dropshadoweffect.cpp b/ui/utils/dropshadoweffect.cpp
index 91a0258..1090fcd 100644
--- a/ui/utils/dropshadoweffect.cpp
+++ b/ui/utils/dropshadoweffect.cpp
@@ -17,562 +17,6 @@
  */
 
 #include "dropshadoweffect.h"
-#include "QtMath"
-
-static const int tileSize = 32;
-template <class T>
-static
-inline void qt_memrotate90_tiled(const T *src, int w, int h, int sstride, T *dest, int dstride)
-{
-    sstride /= sizeof(T);
-    dstride /= sizeof(T);
-    const int pack = sizeof(quint32) / sizeof(T);
-    const int unaligned =
-    qMin(uint((quintptr(dest) & (sizeof(quint32)-1)) / sizeof(T)), uint(h));
-    const int restX = w % tileSize;
-    const int restY = (h - unaligned) % tileSize;
-    const int unoptimizedY = restY % pack;
-    const int numTilesX = w / tileSize + (restX > 0);
-    const int numTilesY = (h - unaligned) / tileSize + (restY >= pack);
-    for (int tx = 0; tx < numTilesX; ++tx) {
-        const int startx = w - tx * tileSize - 1;
-        const int stopx = qMax(startx - tileSize, 0);
-        if (unaligned) {
-            for (int x = startx; x >= stopx; --x) {
-                T *d = dest + (w - x - 1) * dstride;
-                for (int y = 0; y < unaligned; ++y) {
-                    *d++ = src[y * sstride + x];
-                }
-            }
-        }
-        for (int ty = 0; ty < numTilesY; ++ty) {
-            const int starty = ty * tileSize + unaligned;
-            const int stopy = qMin(starty + tileSize, h - unoptimizedY);
-            for (int x = startx; x >= stopx; --x) {
-                quint32 *d = reinterpret_cast<quint32*>(dest + (w - x - 1) * dstride + starty);
-                for (int y = starty; y < stopy; y += pack) {
-                    quint32 c = src[y * sstride + x];
-                    for (int i = 1; i < pack; ++i) {
-                        const int shift = (sizeof(T) * 8 * i);
-                        const T color = src[(y + i) * sstride + x];
-                        c |= color << shift;
-                    }
-                    *d++ = c;
-                }
-            }
-        }
-        if (unoptimizedY) {
-            const int starty = h - unoptimizedY;
-            for (int x = startx; x >= stopx; --x) {
-                T *d = dest + (w - x - 1) * dstride + starty;
-                for (int y = starty; y < h; ++y) {
-                    *d++ = src[y * sstride + x];
-                }
-            }
-        }
-    }
-}
-template <class T>
-static
-inline void qt_memrotate90_tiled_unpacked(const T *src, int w, int h, int sstride, T *dest,
-                                          int dstride)
-{
-    const int numTilesX = (w + tileSize - 1) / tileSize;
-    const int numTilesY = (h + tileSize - 1) / tileSize;
-    for (int tx = 0; tx < numTilesX; ++tx) {
-        const int startx = w - tx * tileSize - 1;
-        const int stopx = qMax(startx - tileSize, 0);
-        for (int ty = 0; ty < numTilesY; ++ty) {
-            const int starty = ty * tileSize;
-            const int stopy = qMin(starty + tileSize, h);
-            for (int x = startx; x >= stopx; --x) {
-                T *d = (T *)((char*)dest + (w - x - 1) * dstride) + starty;
-                const char *s = (const char*)(src + x) + starty * sstride;
-                for (int y = starty; y < stopy; ++y) {
-                    *d++ = *(const T *)(s);
-                    s += sstride;
-                }
-            }
-        }
-    }
-}
-template <class T>
-static
-inline void qt_memrotate270_tiled(const T *src, int w, int h, int sstride, T *dest, int dstride)
-{
-    sstride /= sizeof(T);
-    dstride /= sizeof(T);
-    const int pack = sizeof(quint32) / sizeof(T);
-    const int unaligned =
-    qMin(uint((quintptr(dest) & (sizeof(quint32)-1)) / sizeof(T)), uint(h));
-    const int restX = w % tileSize;
-    const int restY = (h - unaligned) % tileSize;
-    const int unoptimizedY = restY % pack;
-    const int numTilesX = w / tileSize + (restX > 0);
-    const int numTilesY = (h - unaligned) / tileSize + (restY >= pack);
-    for (int tx = 0; tx < numTilesX; ++tx) {
-        const int startx = tx * tileSize;
-        const int stopx = qMin(startx + tileSize, w);
-        if (unaligned) {
-            for (int x = startx; x < stopx; ++x) {
-                T *d = dest + x * dstride;
-                for (int y = h - 1; y >= h - unaligned; --y) {
-                    *d++ = src[y * sstride + x];
-                }
-            }
-        }
-        for (int ty = 0; ty < numTilesY; ++ty) {
-            const int starty = h - 1 - unaligned - ty * tileSize;
-            const int stopy = qMax(starty - tileSize, unoptimizedY);
-            for (int x = startx; x < stopx; ++x) {
-                quint32 *d = reinterpret_cast<quint32*>(dest + x * dstride
-                + h - 1 - starty);
-                for (int y = starty; y >= stopy; y -= pack) {
-                    quint32 c = src[y * sstride + x];
-                    for (int i = 1; i < pack; ++i) {
-                        const int shift = (sizeof(T) * 8 * i);
-                        const T color = src[(y - i) * sstride + x];
-                        c |= color << shift;
-                    }
-                    *d++ = c;
-                }
-            }
-        }
-        if (unoptimizedY) {
-            const int starty = unoptimizedY - 1;
-            for (int x = startx; x < stopx; ++x) {
-                T *d = dest + x * dstride + h - 1 - starty;
-                for (int y = starty; y >= 0; --y) {
-                    *d++ = src[y * sstride + x];
-                }
-            }
-        }
-    }
-}
-template <class T>
-static
-inline void qt_memrotate270_tiled_unpacked(const T *src, int w, int h, int sstride, T *dest,
-                                           int dstride)
-{
-    const int numTilesX = (w + tileSize - 1) / tileSize;
-    const int numTilesY = (h + tileSize - 1) / tileSize;
-    for (int tx = 0; tx < numTilesX; ++tx) {
-        const int startx = tx * tileSize;
-        const int stopx = qMin(startx + tileSize, w);
-        for (int ty = 0; ty < numTilesY; ++ty) {
-            const int starty = h - 1 - ty * tileSize;
-            const int stopy = qMax(starty - tileSize, 0);
-            for (int x = startx; x < stopx; ++x) {
-                T *d = (T*)((char*)dest + x * dstride) + h - 1 - starty;
-                const char *s = (const char*)(src + x) + starty * sstride;
-                for (int y = starty; y >= stopy; --y) {
-                    *d++ = *(const T*)s;
-                    s -= sstride;
-                }
-            }
-        }
-    }
-}
-template <class T>
-static
-inline void qt_memrotate90_template(const T *src, int srcWidth, int srcHeight, int srcStride,
-                                    T *dest, int dstStride)
-{
-    #if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
-    // packed algorithm assumes little endian and that sizeof(quint32)/sizeof(T) is an integer
-    if (sizeof(quint32) % sizeof(T) == 0)
-        qt_memrotate90_tiled<T>(src, srcWidth, srcHeight, srcStride, dest, dstStride);
-    else
-        #endif
-        qt_memrotate90_tiled_unpacked<T>(src, srcWidth, srcHeight, srcStride, dest, dstStride);
-}
-template <>
-inline void qt_memrotate90_template<quint32>(const quint32 *src, int w, int h, int sstride, quint32 *dest, int dstride)
-{
-    // packed algorithm doesn't have any benefit for quint32
-    qt_memrotate90_tiled_unpacked(src, w, h, sstride, dest, dstride);
-}
-template <>
-inline void qt_memrotate90_template<quint64>(const quint64 *src, int w, int h, int sstride, quint64 *dest, int dstride)
-{
-    qt_memrotate90_tiled_unpacked(src, w, h, sstride, dest, dstride);
-}
-template <class T>
-static
-inline void qt_memrotate180_template(const T *src, int w, int h, int sstride, T *dest, int dstride)
-{
-    const char *s = (const char*)(src) + (h - 1) * sstride;
-    for (int dy = 0; dy < h; ++dy) {
-        T *d = reinterpret_cast<T*>((char *)(dest) + dy * dstride);
-        src = reinterpret_cast<const T*>(s);
-        for (int dx = 0; dx < w; ++dx) {
-            d[dx] = src[w - 1 - dx];
-        }
-        s -= sstride;
-    }
-}
-template <class T>
-static
-inline void qt_memrotate270_template(const T *src, int srcWidth, int srcHeight, int srcStride,
-                                     T *dest, int dstStride)
-{
-    #if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
-    // packed algorithm assumes little endian and that sizeof(quint32)/sizeof(T) is an integer
-    if (sizeof(quint32) % sizeof(T) == 0)
-        qt_memrotate270_tiled<T>(src, srcWidth, srcHeight, srcStride, dest, dstStride);
-    else
-        #endif
-        qt_memrotate270_tiled_unpacked<T>(src, srcWidth, srcHeight, srcStride, dest, dstStride);
-}
-template <>
-inline void qt_memrotate270_template<quint32>(const quint32 *src, int w, int h, int sstride, quint32 *dest, int dstride)
-{
-    // packed algorithm doesn't have any benefit for quint32
-    qt_memrotate270_tiled_unpacked(src, w, h, sstride, dest, dstride);
-}
-template <>
-inline void qt_memrotate270_template<quint64>(const quint64 *src, int w, int h, int sstride, quint64 *dest, int dstride)
-{
-    qt_memrotate270_tiled_unpacked(src, w, h, sstride, dest, dstride);
-}
-#define QT_IMPL_MEMROTATE(type)                                     \
-void qt_memrotate90(const type *src, int w, int h, int sstride, \
-type *dest, int dstride)           \
-{                                                                   \
-    qt_memrotate90_template(src, w, h, sstride, dest, dstride);     \
-}                                                                   \
-void qt_memrotate180(const type *src, int w, int h, int sstride, \
-type *dest, int dstride)          \
-{                                                                   \
-    qt_memrotate180_template(src, w, h, sstride, dest, dstride);    \
-}                                                                   \
-void qt_memrotate270(const type *src, int w, int h, int sstride, \
-type *dest, int dstride)          \
-{                                                                   \
-    qt_memrotate270_template(src, w, h, sstride, dest, dstride);    \
-}
-#define QT_IMPL_SIMPLE_MEMROTATE(type)                              \
-void qt_memrotate90(const type *src, int w, int h, int sstride,  \
-type *dest, int dstride)           \
-{                                                                   \
-    qt_memrotate90_tiled_unpacked(src, w, h, sstride, dest, dstride); \
-}                                                                   \
-void qt_memrotate180(const type *src, int w, int h, int sstride, \
-type *dest, int dstride)          \
-{                                                                   \
-    qt_memrotate180_template(src, w, h, sstride, dest, dstride);    \
-}                                                                   \
-void qt_memrotate270(const type *src, int w, int h, int sstride, \
-type *dest, int dstride)          \
-{                                                                   \
-    qt_memrotate270_tiled_unpacked(src, w, h, sstride, dest, dstride); \
-}
-QT_IMPL_MEMROTATE(quint64)
-QT_IMPL_MEMROTATE(quint32)
-QT_IMPL_MEMROTATE(quint16)
-QT_IMPL_MEMROTATE(quint8)
-void qt_memrotate90_8(const uchar *srcPixels, int w, int h, int sbpl, uchar *destPixels, int dbpl)
-{
-    qt_memrotate90(srcPixels, w, h, sbpl, destPixels, dbpl);
-}
-void qt_memrotate180_8(const uchar *srcPixels, int w, int h, int sbpl, uchar *destPixels, int dbpl)
-{
-    qt_memrotate180(srcPixels, w, h, sbpl, destPixels, dbpl);
-}
-void qt_memrotate270_8(const uchar *srcPixels, int w, int h, int sbpl, uchar *destPixels, int dbpl)
-{
-    qt_memrotate270(srcPixels, w, h, sbpl, destPixels, dbpl);
-}
-void qt_memrotate90_16(const uchar *srcPixels, int w, int h, int sbpl, uchar *destPixels, int dbpl)
-{
-    qt_memrotate90((const ushort *)srcPixels, w, h, sbpl, (ushort *)destPixels, dbpl);
-}
-void qt_memrotate180_16(const uchar *srcPixels, int w, int h, int sbpl, uchar *destPixels, int dbpl)
-{
-    qt_memrotate180((const ushort *)srcPixels, w, h, sbpl, (ushort *)destPixels, dbpl);
-}
-void qt_memrotate270_16(const uchar *srcPixels, int w, int h, int sbpl, uchar *destPixels, int dbpl)
-{
-    qt_memrotate270((const ushort *)srcPixels, w, h, sbpl, (ushort *)destPixels, dbpl);
-}
-void qt_memrotate90_32(const uchar *srcPixels, int w, int h, int sbpl, uchar *destPixels, int dbpl)
-{
-    qt_memrotate90((const uint *)srcPixels, w, h, sbpl, (uint *)destPixels, dbpl);
-}
-void qt_memrotate180_32(const uchar *srcPixels, int w, int h, int sbpl, uchar *destPixels, int dbpl)
-{
-    qt_memrotate180((const uint *)srcPixels, w, h, sbpl, (uint *)destPixels, dbpl);
-}
-void qt_memrotate270_32(const uchar *srcPixels, int w, int h, int sbpl, uchar *destPixels, int dbpl)
-{
-    qt_memrotate270((const uint *)srcPixels, w, h, sbpl, (uint *)destPixels, dbpl);
-}
-void qt_memrotate90_64(const uchar *srcPixels, int w, int h, int sbpl, uchar *destPixels, int dbpl)
-{
-    qt_memrotate90((const quint64 *)srcPixels, w, h, sbpl, (quint64 *)destPixels, dbpl);
-}
-void qt_memrotate180_64(const uchar *srcPixels, int w, int h, int sbpl, uchar *destPixels, int dbpl)
-{
-    qt_memrotate180((const quint64 *)srcPixels, w, h, sbpl, (quint64 *)destPixels, dbpl);
-}
-void qt_memrotate270_64(const uchar *srcPixels, int w, int h, int sbpl, uchar *destPixels, int dbpl)
-{
-    qt_memrotate270((const quint64 *)srcPixels, w, h, sbpl, (quint64 *)destPixels, dbpl);
-}
-
-#define AVG(a,b)  ( ((((a)^(b)) & 0xfefefefeUL) >> 1) + ((a)&(b)) )
-#define AVG16(a,b)  ( ((((a)^(b)) & 0xf7deUL) >> 1) + ((a)&(b)) )
-const int alphaIndex = (QSysInfo::ByteOrder == QSysInfo::BigEndian ? 0 : 3);
-
-QImage qt_halfScaled(const QImage &source)
-{
-    if (source.width() < 2 || source.height() < 2)
-        return QImage();
-    
-    QImage srcImage = source;
-    
-    if (source.format() == QImage::Format_Indexed8 || source.format() == QImage::Format_Grayscale8) {
-        // assumes grayscale
-        QImage dest(source.width() / 2, source.height() / 2, srcImage.format());
-        dest.setDevicePixelRatio(source.devicePixelRatioF());
-        
-        const uchar *src = reinterpret_cast<const uchar*>(const_cast<const QImage &>(srcImage).bits());
-        qsizetype sx = srcImage.bytesPerLine();
-        qsizetype sx2 = sx << 1;
-        
-        uchar *dst = reinterpret_cast<uchar*>(dest.bits());
-        qsizetype dx = dest.bytesPerLine();
-        int ww = dest.width();
-        int hh = dest.height();
-        
-        for (int y = hh; y; --y, dst += dx, src += sx2) {
-            const uchar *p1 = src;
-            const uchar *p2 = src + sx;
-            uchar *q = dst;
-            for (int x = ww; x; --x, ++q, p1 += 2, p2 += 2)
-                *q = ((int(p1[0]) + int(p1[1]) + int(p2[0]) + int(p2[1])) + 2) >> 2;
-        }
-        
-        return dest;
-    } else if (source.format() == QImage::Format_ARGB8565_Premultiplied) {
-        QImage dest(source.width() / 2, source.height() / 2, srcImage.format());
-        dest.setDevicePixelRatio(source.devicePixelRatioF());
-        
-        const uchar *src = reinterpret_cast<const uchar*>(const_cast<const QImage &>(srcImage).bits());
-        qsizetype sx = srcImage.bytesPerLine();
-        qsizetype sx2 = sx << 1;
-        
-        uchar *dst = reinterpret_cast<uchar*>(dest.bits());
-        qsizetype dx = dest.bytesPerLine();
-        int ww = dest.width();
-        int hh = dest.height();
-        
-        for (int y = hh; y; --y, dst += dx, src += sx2) {
-            const uchar *p1 = src;
-            const uchar *p2 = src + sx;
-            uchar *q = dst;
-            for (int x = ww; x; --x, q += 3, p1 += 6, p2 += 6) {
-                // alpha
-                q[0] = AVG(AVG(p1[0], p1[3]), AVG(p2[0], p2[3]));
-                // rgb
-                const quint16 p16_1 = (p1[2] << 8) | p1[1];
-                const quint16 p16_2 = (p1[5] << 8) | p1[4];
-                const quint16 p16_3 = (p2[2] << 8) | p2[1];
-                const quint16 p16_4 = (p2[5] << 8) | p2[4];
-                const quint16 result = AVG16(AVG16(p16_1, p16_2), AVG16(p16_3, p16_4));
-                q[1] = result & 0xff;
-                q[2] = result >> 8;
-            }
-        }
-        
-        return dest;
-    } else if (source.format() != QImage::Format_ARGB32_Premultiplied
-        && source.format() != QImage::Format_RGB32)
-    {
-        srcImage = source.convertToFormat(QImage::Format_ARGB32_Premultiplied);
-    }
-    
-    QImage dest(source.width() / 2, source.height() / 2, srcImage.format());
-    dest.setDevicePixelRatio(source.devicePixelRatioF());
-    
-    const quint32 *src = reinterpret_cast<const quint32*>(const_cast<const QImage &>(srcImage).bits());
-    qsizetype sx = srcImage.bytesPerLine() >> 2;
-    qsizetype sx2 = sx << 1;
-    
-    quint32 *dst = reinterpret_cast<quint32*>(dest.bits());
-    qsizetype dx = dest.bytesPerLine() >> 2;
-    int ww = dest.width();
-    int hh = dest.height();
-    
-    for (int y = hh; y; --y, dst += dx, src += sx2) {
-        const quint32 *p1 = src;
-        const quint32 *p2 = src + sx;
-        quint32 *q = dst;
-        for (int x = ww; x; --x, q++, p1 += 2, p2 += 2)
-            *q = AVG(AVG(p1[0], p1[1]), AVG(p2[0], p2[1]));
-    }
-    
-    return dest;
-}
-
-template <int shift>
-inline int qt_static_shift(int value)
-{
-    if (shift == 0)
-        return value;
-    else if (shift > 0)
-        return value << (uint(shift) & 0x1f);
-    else
-        return value >> (uint(-shift) & 0x1f);
-}
-
-template<int aprec, int zprec>
-inline void qt_blurinner(uchar *bptr, int &zR, int &zG, int &zB, int &zA, int alpha)
-{
-    QRgb *pixel = (QRgb *)bptr;
-    
-    #define Z_MASK (0xff << zprec)
-    const int A_zprec = qt_static_shift<zprec - 24>(*pixel) & Z_MASK;
-    const int R_zprec = qt_static_shift<zprec - 16>(*pixel) & Z_MASK;
-    const int G_zprec = qt_static_shift<zprec - 8>(*pixel)  & Z_MASK;
-    const int B_zprec = qt_static_shift<zprec>(*pixel)      & Z_MASK;
-    #undef Z_MASK
-    
-    const int zR_zprec = zR >> aprec;
-    const int zG_zprec = zG >> aprec;
-    const int zB_zprec = zB >> aprec;
-    const int zA_zprec = zA >> aprec;
-    
-    zR += alpha * (R_zprec - zR_zprec);
-    zG += alpha * (G_zprec - zG_zprec);
-    zB += alpha * (B_zprec - zB_zprec);
-    zA += alpha * (A_zprec - zA_zprec);
-    
-    #define ZA_MASK (0xff << (zprec + aprec))
-    *pixel =
-    qt_static_shift<24 - zprec - aprec>(zA & ZA_MASK)
-    | qt_static_shift<16 - zprec - aprec>(zR & ZA_MASK)
-    | qt_static_shift<8 - zprec - aprec>(zG & ZA_MASK)
-    | qt_static_shift<-zprec - aprec>(zB & ZA_MASK);
-    #undef ZA_MASK
-}
-
-template<int aprec, int zprec>
-inline void qt_blurinner_alphaOnly(uchar *bptr, int &z, int alpha)
-{
-    const int A_zprec = int(*(bptr)) << zprec;
-    const int z_zprec = z >> aprec;
-    z += alpha * (A_zprec - z_zprec);
-    *(bptr) = z >> (zprec + aprec);
-}
-
-template<int aprec, int zprec, bool alphaOnly>
-inline void qt_blurrow(QImage & im, int line, int alpha)
-{
-    uchar *bptr = im.scanLine(line);
-    
-    int zR = 0, zG = 0, zB = 0, zA = 0;
-    
-    if (alphaOnly && im.format() != QImage::Format_Indexed8)
-        bptr += alphaIndex;
-    
-    const int stride = im.depth() >> 3;
-    const int im_width = im.width();
-    for (int index = 0; index < im_width; ++index) {
-        if (alphaOnly)
-            qt_blurinner_alphaOnly<aprec, zprec>(bptr, zA, alpha);
-        else
-            qt_blurinner<aprec, zprec>(bptr, zR, zG, zB, zA, alpha);
-        bptr += stride;
-    }
-    
-    bptr -= stride;
-    
-    for (int index = im_width - 2; index >= 0; --index) {
-        bptr -= stride;
-        if (alphaOnly)
-            qt_blurinner_alphaOnly<aprec, zprec>(bptr, zA, alpha);
-        else
-            qt_blurinner<aprec, zprec>(bptr, zR, zG, zB, zA, alpha);
-    }
-}
-
-template <int aprec, int zprec, bool alphaOnly>
-void expblur(QImage &img, qreal radius, bool improvedQuality = false, int transposed = 0)
-{
-    // halve the radius if we're using two passes
-    if (improvedQuality)
-        radius *= qreal(0.5);
-    
-    Q_ASSERT(img.format() == QImage::Format_ARGB32_Premultiplied
-    || img.format() == QImage::Format_RGB32
-    || img.format() == QImage::Format_Indexed8
-    || img.format() == QImage::Format_Grayscale8);
-    
-    // choose the alpha such that pixels at radius distance from a fully
-    // saturated pixel will have an alpha component of no greater than
-    // the cutOffIntensity
-    const qreal cutOffIntensity = 2;
-    int alpha = radius <= qreal(1e-5)
-    ? ((1 << aprec)-1)
-    : qRound((1<<aprec)*(1 - qPow(cutOffIntensity * (1 / qreal(255)), 1 / radius)));
-    
-    int img_height = img.height();
-    for (int row = 0; row < img_height; ++row) {
-        for (int i = 0; i <= int(improvedQuality); ++i)
-            qt_blurrow<aprec, zprec, alphaOnly>(img, row, alpha);
-    }
-    
-    QImage temp(img.height(), img.width(), img.format());
-    temp.setDevicePixelRatio(img.devicePixelRatioF());
-    if (transposed >= 0) {
-        if (img.depth() == 8) {
-            qt_memrotate270(reinterpret_cast<const quint8*>(img.bits()),
-                            img.width(), img.height(), img.bytesPerLine(),
-                            reinterpret_cast<quint8*>(temp.bits()),
-                            temp.bytesPerLine());
-        } else {
-            qt_memrotate270(reinterpret_cast<const quint32*>(img.bits()),
-                            img.width(), img.height(), img.bytesPerLine(),
-                            reinterpret_cast<quint32*>(temp.bits()),
-                            temp.bytesPerLine());
-        }
-    } else {
-        if (img.depth() == 8) {
-            qt_memrotate90(reinterpret_cast<const quint8*>(img.bits()),
-                           img.width(), img.height(), img.bytesPerLine(),
-                           reinterpret_cast<quint8*>(temp.bits()),
-                           temp.bytesPerLine());
-        } else {
-            qt_memrotate90(reinterpret_cast<const quint32*>(img.bits()),
-                           img.width(), img.height(), img.bytesPerLine(),
-                           reinterpret_cast<quint32*>(temp.bits()),
-                           temp.bytesPerLine());
-        }
-    }
-    
-    img_height = temp.height();
-    for (int row = 0; row < img_height; ++row) {
-        for (int i = 0; i <= int(improvedQuality); ++i)
-            qt_blurrow<aprec, zprec, alphaOnly>(temp, row, alpha);
-    }
-    
-    if (transposed == 0) {
-        if (img.depth() == 8) {
-            qt_memrotate90(reinterpret_cast<const quint8*>(temp.bits()),
-                           temp.width(), temp.height(), temp.bytesPerLine(),
-                           reinterpret_cast<quint8*>(img.bits()),
-                           img.bytesPerLine());
-        } else {
-            qt_memrotate90(reinterpret_cast<const quint32*>(temp.bits()),
-                           temp.width(), temp.height(), temp.bytesPerLine(),
-                           reinterpret_cast<quint32*>(img.bits()),
-                           img.bytesPerLine());
-        }
-    } else {
-        img = temp;
-    }
-}
 
 PixmapFilter::PixmapFilter(QObject* parent):QObject(parent) {}
 PixmapFilter::~PixmapFilter(){}
@@ -640,7 +84,7 @@ void PixmapDropShadowFilter::draw(QPainter *p, const QPointF &pos, const QPixmap
         tmpPainter.fillRect(shadow, mColor);
     }
     
-    expblur<12, 10, false>(tmp, mRadius, false, 0);
+    Utils::exponentialblur(tmp, mRadius, false, 0);
     tmpPainter.end();
     
     // Draw the actual pixmap...
diff --git a/ui/utils/dropshadoweffect.h b/ui/utils/dropshadoweffect.h
index b2768b7..f374ce3 100644
--- a/ui/utils/dropshadoweffect.h
+++ b/ui/utils/dropshadoweffect.h
@@ -24,6 +24,8 @@
 #include <QPointF>
 #include <QColor>
 
+#include "eb.h"
+
 class PixmapFilter : public QObject
 {
     Q_OBJECT
diff --git a/ui/utils/eb.cpp b/ui/utils/eb.cpp
new file mode 100644
index 0000000..f44e53b
--- /dev/null
+++ b/ui/utils/eb.cpp
@@ -0,0 +1,579 @@
+/*
+ * Squawk messenger. 
+ * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+ * 
+ * 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 "eb.h"
+
+static const int tileSize = 32;
+template <class T>
+static
+inline void qt_memrotate90_tiled(const T *src, int w, int h, int sstride, T *dest, int dstride)
+{
+    sstride /= sizeof(T);
+    dstride /= sizeof(T);
+    const int pack = sizeof(quint32) / sizeof(T);
+    const int unaligned =
+    qMin(uint((quintptr(dest) & (sizeof(quint32)-1)) / sizeof(T)), uint(h));
+    const int restX = w % tileSize;
+    const int restY = (h - unaligned) % tileSize;
+    const int unoptimizedY = restY % pack;
+    const int numTilesX = w / tileSize + (restX > 0);
+    const int numTilesY = (h - unaligned) / tileSize + (restY >= pack);
+    for (int tx = 0; tx < numTilesX; ++tx) {
+        const int startx = w - tx * tileSize - 1;
+        const int stopx = qMax(startx - tileSize, 0);
+        if (unaligned) {
+            for (int x = startx; x >= stopx; --x) {
+                T *d = dest + (w - x - 1) * dstride;
+                for (int y = 0; y < unaligned; ++y) {
+                    *d++ = src[y * sstride + x];
+                }
+            }
+        }
+        for (int ty = 0; ty < numTilesY; ++ty) {
+            const int starty = ty * tileSize + unaligned;
+            const int stopy = qMin(starty + tileSize, h - unoptimizedY);
+            for (int x = startx; x >= stopx; --x) {
+                quint32 *d = reinterpret_cast<quint32*>(dest + (w - x - 1) * dstride + starty);
+                for (int y = starty; y < stopy; y += pack) {
+                    quint32 c = src[y * sstride + x];
+                    for (int i = 1; i < pack; ++i) {
+                        const int shift = (sizeof(T) * 8 * i);
+                        const T color = src[(y + i) * sstride + x];
+                        c |= color << shift;
+                    }
+                    *d++ = c;
+                }
+            }
+        }
+        if (unoptimizedY) {
+            const int starty = h - unoptimizedY;
+            for (int x = startx; x >= stopx; --x) {
+                T *d = dest + (w - x - 1) * dstride + starty;
+                for (int y = starty; y < h; ++y) {
+                    *d++ = src[y * sstride + x];
+                }
+            }
+        }
+    }
+}
+template <class T>
+static
+inline void qt_memrotate90_tiled_unpacked(const T *src, int w, int h, int sstride, T *dest,
+                                          int dstride)
+{
+    const int numTilesX = (w + tileSize - 1) / tileSize;
+    const int numTilesY = (h + tileSize - 1) / tileSize;
+    for (int tx = 0; tx < numTilesX; ++tx) {
+        const int startx = w - tx * tileSize - 1;
+        const int stopx = qMax(startx - tileSize, 0);
+        for (int ty = 0; ty < numTilesY; ++ty) {
+            const int starty = ty * tileSize;
+            const int stopy = qMin(starty + tileSize, h);
+            for (int x = startx; x >= stopx; --x) {
+                T *d = (T *)((char*)dest + (w - x - 1) * dstride) + starty;
+                const char *s = (const char*)(src + x) + starty * sstride;
+                for (int y = starty; y < stopy; ++y) {
+                    *d++ = *(const T *)(s);
+                    s += sstride;
+                }
+            }
+        }
+    }
+}
+template <class T>
+static
+inline void qt_memrotate270_tiled(const T *src, int w, int h, int sstride, T *dest, int dstride)
+{
+    sstride /= sizeof(T);
+    dstride /= sizeof(T);
+    const int pack = sizeof(quint32) / sizeof(T);
+    const int unaligned =
+    qMin(uint((quintptr(dest) & (sizeof(quint32)-1)) / sizeof(T)), uint(h));
+    const int restX = w % tileSize;
+    const int restY = (h - unaligned) % tileSize;
+    const int unoptimizedY = restY % pack;
+    const int numTilesX = w / tileSize + (restX > 0);
+    const int numTilesY = (h - unaligned) / tileSize + (restY >= pack);
+    for (int tx = 0; tx < numTilesX; ++tx) {
+        const int startx = tx * tileSize;
+        const int stopx = qMin(startx + tileSize, w);
+        if (unaligned) {
+            for (int x = startx; x < stopx; ++x) {
+                T *d = dest + x * dstride;
+                for (int y = h - 1; y >= h - unaligned; --y) {
+                    *d++ = src[y * sstride + x];
+                }
+            }
+        }
+        for (int ty = 0; ty < numTilesY; ++ty) {
+            const int starty = h - 1 - unaligned - ty * tileSize;
+            const int stopy = qMax(starty - tileSize, unoptimizedY);
+            for (int x = startx; x < stopx; ++x) {
+                quint32 *d = reinterpret_cast<quint32*>(dest + x * dstride
+                + h - 1 - starty);
+                for (int y = starty; y >= stopy; y -= pack) {
+                    quint32 c = src[y * sstride + x];
+                    for (int i = 1; i < pack; ++i) {
+                        const int shift = (sizeof(T) * 8 * i);
+                        const T color = src[(y - i) * sstride + x];
+                        c |= color << shift;
+                    }
+                    *d++ = c;
+                }
+            }
+        }
+        if (unoptimizedY) {
+            const int starty = unoptimizedY - 1;
+            for (int x = startx; x < stopx; ++x) {
+                T *d = dest + x * dstride + h - 1 - starty;
+                for (int y = starty; y >= 0; --y) {
+                    *d++ = src[y * sstride + x];
+                }
+            }
+        }
+    }
+}
+template <class T>
+static
+inline void qt_memrotate270_tiled_unpacked(const T *src, int w, int h, int sstride, T *dest,
+                                           int dstride)
+{
+    const int numTilesX = (w + tileSize - 1) / tileSize;
+    const int numTilesY = (h + tileSize - 1) / tileSize;
+    for (int tx = 0; tx < numTilesX; ++tx) {
+        const int startx = tx * tileSize;
+        const int stopx = qMin(startx + tileSize, w);
+        for (int ty = 0; ty < numTilesY; ++ty) {
+            const int starty = h - 1 - ty * tileSize;
+            const int stopy = qMax(starty - tileSize, 0);
+            for (int x = startx; x < stopx; ++x) {
+                T *d = (T*)((char*)dest + x * dstride) + h - 1 - starty;
+                const char *s = (const char*)(src + x) + starty * sstride;
+                for (int y = starty; y >= stopy; --y) {
+                    *d++ = *(const T*)s;
+                    s -= sstride;
+                }
+            }
+        }
+    }
+}
+template <class T>
+static
+inline void qt_memrotate90_template(const T *src, int srcWidth, int srcHeight, int srcStride,
+                                    T *dest, int dstStride)
+{
+    #if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
+    // packed algorithm assumes little endian and that sizeof(quint32)/sizeof(T) is an integer
+    if (sizeof(quint32) % sizeof(T) == 0)
+        qt_memrotate90_tiled<T>(src, srcWidth, srcHeight, srcStride, dest, dstStride);
+    else
+        #endif
+        qt_memrotate90_tiled_unpacked<T>(src, srcWidth, srcHeight, srcStride, dest, dstStride);
+}
+template <>
+inline void qt_memrotate90_template<quint32>(const quint32 *src, int w, int h, int sstride, quint32 *dest, int dstride)
+{
+    // packed algorithm doesn't have any benefit for quint32
+    qt_memrotate90_tiled_unpacked(src, w, h, sstride, dest, dstride);
+}
+template <>
+inline void qt_memrotate90_template<quint64>(const quint64 *src, int w, int h, int sstride, quint64 *dest, int dstride)
+{
+    qt_memrotate90_tiled_unpacked(src, w, h, sstride, dest, dstride);
+}
+template <class T>
+static
+inline void qt_memrotate180_template(const T *src, int w, int h, int sstride, T *dest, int dstride)
+{
+    const char *s = (const char*)(src) + (h - 1) * sstride;
+    for (int dy = 0; dy < h; ++dy) {
+        T *d = reinterpret_cast<T*>((char *)(dest) + dy * dstride);
+        src = reinterpret_cast<const T*>(s);
+        for (int dx = 0; dx < w; ++dx) {
+            d[dx] = src[w - 1 - dx];
+        }
+        s -= sstride;
+    }
+}
+template <class T>
+static
+inline void qt_memrotate270_template(const T *src, int srcWidth, int srcHeight, int srcStride,
+                                     T *dest, int dstStride)
+{
+    #if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
+    // packed algorithm assumes little endian and that sizeof(quint32)/sizeof(T) is an integer
+    if (sizeof(quint32) % sizeof(T) == 0)
+        qt_memrotate270_tiled<T>(src, srcWidth, srcHeight, srcStride, dest, dstStride);
+    else
+        #endif
+        qt_memrotate270_tiled_unpacked<T>(src, srcWidth, srcHeight, srcStride, dest, dstStride);
+}
+template <>
+inline void qt_memrotate270_template<quint32>(const quint32 *src, int w, int h, int sstride, quint32 *dest, int dstride)
+{
+    // packed algorithm doesn't have any benefit for quint32
+    qt_memrotate270_tiled_unpacked(src, w, h, sstride, dest, dstride);
+}
+template <>
+inline void qt_memrotate270_template<quint64>(const quint64 *src, int w, int h, int sstride, quint64 *dest, int dstride)
+{
+    qt_memrotate270_tiled_unpacked(src, w, h, sstride, dest, dstride);
+}
+#define QT_IMPL_MEMROTATE(type)                                     \
+void qt_memrotate90(const type *src, int w, int h, int sstride, \
+type *dest, int dstride)           \
+{                                                                   \
+    qt_memrotate90_template(src, w, h, sstride, dest, dstride);     \
+}                                                                   \
+void qt_memrotate180(const type *src, int w, int h, int sstride, \
+type *dest, int dstride)          \
+{                                                                   \
+    qt_memrotate180_template(src, w, h, sstride, dest, dstride);    \
+}                                                                   \
+void qt_memrotate270(const type *src, int w, int h, int sstride, \
+type *dest, int dstride)          \
+{                                                                   \
+    qt_memrotate270_template(src, w, h, sstride, dest, dstride);    \
+}
+#define QT_IMPL_SIMPLE_MEMROTATE(type)                              \
+void qt_memrotate90(const type *src, int w, int h, int sstride,  \
+type *dest, int dstride)           \
+{                                                                   \
+    qt_memrotate90_tiled_unpacked(src, w, h, sstride, dest, dstride); \
+}                                                                   \
+void qt_memrotate180(const type *src, int w, int h, int sstride, \
+type *dest, int dstride)          \
+{                                                                   \
+    qt_memrotate180_template(src, w, h, sstride, dest, dstride);    \
+}                                                                   \
+void qt_memrotate270(const type *src, int w, int h, int sstride, \
+type *dest, int dstride)          \
+{                                                                   \
+    qt_memrotate270_tiled_unpacked(src, w, h, sstride, dest, dstride); \
+}
+QT_IMPL_MEMROTATE(quint64)
+QT_IMPL_MEMROTATE(quint32)
+QT_IMPL_MEMROTATE(quint16)
+QT_IMPL_MEMROTATE(quint8)
+void qt_memrotate90_8(const uchar *srcPixels, int w, int h, int sbpl, uchar *destPixels, int dbpl)
+{
+    qt_memrotate90(srcPixels, w, h, sbpl, destPixels, dbpl);
+}
+void qt_memrotate180_8(const uchar *srcPixels, int w, int h, int sbpl, uchar *destPixels, int dbpl)
+{
+    qt_memrotate180(srcPixels, w, h, sbpl, destPixels, dbpl);
+}
+void qt_memrotate270_8(const uchar *srcPixels, int w, int h, int sbpl, uchar *destPixels, int dbpl)
+{
+    qt_memrotate270(srcPixels, w, h, sbpl, destPixels, dbpl);
+}
+void qt_memrotate90_16(const uchar *srcPixels, int w, int h, int sbpl, uchar *destPixels, int dbpl)
+{
+    qt_memrotate90((const ushort *)srcPixels, w, h, sbpl, (ushort *)destPixels, dbpl);
+}
+void qt_memrotate180_16(const uchar *srcPixels, int w, int h, int sbpl, uchar *destPixels, int dbpl)
+{
+    qt_memrotate180((const ushort *)srcPixels, w, h, sbpl, (ushort *)destPixels, dbpl);
+}
+void qt_memrotate270_16(const uchar *srcPixels, int w, int h, int sbpl, uchar *destPixels, int dbpl)
+{
+    qt_memrotate270((const ushort *)srcPixels, w, h, sbpl, (ushort *)destPixels, dbpl);
+}
+void qt_memrotate90_32(const uchar *srcPixels, int w, int h, int sbpl, uchar *destPixels, int dbpl)
+{
+    qt_memrotate90((const uint *)srcPixels, w, h, sbpl, (uint *)destPixels, dbpl);
+}
+void qt_memrotate180_32(const uchar *srcPixels, int w, int h, int sbpl, uchar *destPixels, int dbpl)
+{
+    qt_memrotate180((const uint *)srcPixels, w, h, sbpl, (uint *)destPixels, dbpl);
+}
+void qt_memrotate270_32(const uchar *srcPixels, int w, int h, int sbpl, uchar *destPixels, int dbpl)
+{
+    qt_memrotate270((const uint *)srcPixels, w, h, sbpl, (uint *)destPixels, dbpl);
+}
+void qt_memrotate90_64(const uchar *srcPixels, int w, int h, int sbpl, uchar *destPixels, int dbpl)
+{
+    qt_memrotate90((const quint64 *)srcPixels, w, h, sbpl, (quint64 *)destPixels, dbpl);
+}
+void qt_memrotate180_64(const uchar *srcPixels, int w, int h, int sbpl, uchar *destPixels, int dbpl)
+{
+    qt_memrotate180((const quint64 *)srcPixels, w, h, sbpl, (quint64 *)destPixels, dbpl);
+}
+void qt_memrotate270_64(const uchar *srcPixels, int w, int h, int sbpl, uchar *destPixels, int dbpl)
+{
+    qt_memrotate270((const quint64 *)srcPixels, w, h, sbpl, (quint64 *)destPixels, dbpl);
+}
+
+#define AVG(a,b)  ( ((((a)^(b)) & 0xfefefefeUL) >> 1) + ((a)&(b)) )
+#define AVG16(a,b)  ( ((((a)^(b)) & 0xf7deUL) >> 1) + ((a)&(b)) )
+const int alphaIndex = (QSysInfo::ByteOrder == QSysInfo::BigEndian ? 0 : 3);
+
+QImage qt_halfScaled(const QImage &source)
+{
+    if (source.width() < 2 || source.height() < 2)
+        return QImage();
+    
+    QImage srcImage = source;
+    
+    if (source.format() == QImage::Format_Indexed8 || source.format() == QImage::Format_Grayscale8) {
+        // assumes grayscale
+        QImage dest(source.width() / 2, source.height() / 2, srcImage.format());
+        dest.setDevicePixelRatio(source.devicePixelRatioF());
+        
+        const uchar *src = reinterpret_cast<const uchar*>(const_cast<const QImage &>(srcImage).bits());
+        qsizetype sx = srcImage.bytesPerLine();
+        qsizetype sx2 = sx << 1;
+        
+        uchar *dst = reinterpret_cast<uchar*>(dest.bits());
+        qsizetype dx = dest.bytesPerLine();
+        int ww = dest.width();
+        int hh = dest.height();
+        
+        for (int y = hh; y; --y, dst += dx, src += sx2) {
+            const uchar *p1 = src;
+            const uchar *p2 = src + sx;
+            uchar *q = dst;
+            for (int x = ww; x; --x, ++q, p1 += 2, p2 += 2)
+                *q = ((int(p1[0]) + int(p1[1]) + int(p2[0]) + int(p2[1])) + 2) >> 2;
+        }
+        
+        return dest;
+    } else if (source.format() == QImage::Format_ARGB8565_Premultiplied) {
+        QImage dest(source.width() / 2, source.height() / 2, srcImage.format());
+        dest.setDevicePixelRatio(source.devicePixelRatioF());
+        
+        const uchar *src = reinterpret_cast<const uchar*>(const_cast<const QImage &>(srcImage).bits());
+        qsizetype sx = srcImage.bytesPerLine();
+        qsizetype sx2 = sx << 1;
+        
+        uchar *dst = reinterpret_cast<uchar*>(dest.bits());
+        qsizetype dx = dest.bytesPerLine();
+        int ww = dest.width();
+        int hh = dest.height();
+        
+        for (int y = hh; y; --y, dst += dx, src += sx2) {
+            const uchar *p1 = src;
+            const uchar *p2 = src + sx;
+            uchar *q = dst;
+            for (int x = ww; x; --x, q += 3, p1 += 6, p2 += 6) {
+                // alpha
+                q[0] = AVG(AVG(p1[0], p1[3]), AVG(p2[0], p2[3]));
+                // rgb
+                const quint16 p16_1 = (p1[2] << 8) | p1[1];
+                const quint16 p16_2 = (p1[5] << 8) | p1[4];
+                const quint16 p16_3 = (p2[2] << 8) | p2[1];
+                const quint16 p16_4 = (p2[5] << 8) | p2[4];
+                const quint16 result = AVG16(AVG16(p16_1, p16_2), AVG16(p16_3, p16_4));
+                q[1] = result & 0xff;
+                q[2] = result >> 8;
+            }
+        }
+        
+        return dest;
+    } else if (source.format() != QImage::Format_ARGB32_Premultiplied
+        && source.format() != QImage::Format_RGB32)
+    {
+        srcImage = source.convertToFormat(QImage::Format_ARGB32_Premultiplied);
+    }
+    
+    QImage dest(source.width() / 2, source.height() / 2, srcImage.format());
+    dest.setDevicePixelRatio(source.devicePixelRatioF());
+    
+    const quint32 *src = reinterpret_cast<const quint32*>(const_cast<const QImage &>(srcImage).bits());
+    qsizetype sx = srcImage.bytesPerLine() >> 2;
+    qsizetype sx2 = sx << 1;
+    
+    quint32 *dst = reinterpret_cast<quint32*>(dest.bits());
+    qsizetype dx = dest.bytesPerLine() >> 2;
+    int ww = dest.width();
+    int hh = dest.height();
+    
+    for (int y = hh; y; --y, dst += dx, src += sx2) {
+        const quint32 *p1 = src;
+        const quint32 *p2 = src + sx;
+        quint32 *q = dst;
+        for (int x = ww; x; --x, q++, p1 += 2, p2 += 2)
+            *q = AVG(AVG(p1[0], p1[1]), AVG(p2[0], p2[1]));
+    }
+    
+    return dest;
+}
+
+template <int shift>
+inline int qt_static_shift(int value)
+{
+    if (shift == 0)
+        return value;
+    else if (shift > 0)
+        return value << (uint(shift) & 0x1f);
+    else
+        return value >> (uint(-shift) & 0x1f);
+}
+
+template<int aprec, int zprec>
+inline void qt_blurinner(uchar *bptr, int &zR, int &zG, int &zB, int &zA, int alpha)
+{
+    QRgb *pixel = (QRgb *)bptr;
+    
+    #define Z_MASK (0xff << zprec)
+    const int A_zprec = qt_static_shift<zprec - 24>(*pixel) & Z_MASK;
+    const int R_zprec = qt_static_shift<zprec - 16>(*pixel) & Z_MASK;
+    const int G_zprec = qt_static_shift<zprec - 8>(*pixel)  & Z_MASK;
+    const int B_zprec = qt_static_shift<zprec>(*pixel)      & Z_MASK;
+    #undef Z_MASK
+    
+    const int zR_zprec = zR >> aprec;
+    const int zG_zprec = zG >> aprec;
+    const int zB_zprec = zB >> aprec;
+    const int zA_zprec = zA >> aprec;
+    
+    zR += alpha * (R_zprec - zR_zprec);
+    zG += alpha * (G_zprec - zG_zprec);
+    zB += alpha * (B_zprec - zB_zprec);
+    zA += alpha * (A_zprec - zA_zprec);
+    
+    #define ZA_MASK (0xff << (zprec + aprec))
+    *pixel =
+    qt_static_shift<24 - zprec - aprec>(zA & ZA_MASK)
+    | qt_static_shift<16 - zprec - aprec>(zR & ZA_MASK)
+    | qt_static_shift<8 - zprec - aprec>(zG & ZA_MASK)
+    | qt_static_shift<-zprec - aprec>(zB & ZA_MASK);
+    #undef ZA_MASK
+}
+
+template<int aprec, int zprec>
+inline void qt_blurinner_alphaOnly(uchar *bptr, int &z, int alpha)
+{
+    const int A_zprec = int(*(bptr)) << zprec;
+    const int z_zprec = z >> aprec;
+    z += alpha * (A_zprec - z_zprec);
+    *(bptr) = z >> (zprec + aprec);
+}
+
+template<int aprec, int zprec, bool alphaOnly>
+inline void qt_blurrow(QImage & im, int line, int alpha)
+{
+    uchar *bptr = im.scanLine(line);
+    
+    int zR = 0, zG = 0, zB = 0, zA = 0;
+    
+    if (alphaOnly && im.format() != QImage::Format_Indexed8)
+        bptr += alphaIndex;
+    
+    const int stride = im.depth() >> 3;
+    const int im_width = im.width();
+    for (int index = 0; index < im_width; ++index) {
+        if (alphaOnly)
+            qt_blurinner_alphaOnly<aprec, zprec>(bptr, zA, alpha);
+        else
+            qt_blurinner<aprec, zprec>(bptr, zR, zG, zB, zA, alpha);
+        bptr += stride;
+    }
+    
+    bptr -= stride;
+    
+    for (int index = im_width - 2; index >= 0; --index) {
+        bptr -= stride;
+        if (alphaOnly)
+            qt_blurinner_alphaOnly<aprec, zprec>(bptr, zA, alpha);
+        else
+            qt_blurinner<aprec, zprec>(bptr, zR, zG, zB, zA, alpha);
+    }
+}
+
+template <int aprec, int zprec, bool alphaOnly>
+void expblur(QImage &img, qreal radius, bool improvedQuality = false, int transposed = 0)
+{
+    // halve the radius if we're using two passes
+    if (improvedQuality)
+        radius *= qreal(0.5);
+    
+    Q_ASSERT(img.format() == QImage::Format_ARGB32_Premultiplied
+    || img.format() == QImage::Format_RGB32
+    || img.format() == QImage::Format_Indexed8
+    || img.format() == QImage::Format_Grayscale8);
+    
+    // choose the alpha such that pixels at radius distance from a fully
+    // saturated pixel will have an alpha component of no greater than
+    // the cutOffIntensity
+    const qreal cutOffIntensity = 2;
+    int alpha = radius <= qreal(1e-5)
+    ? ((1 << aprec)-1)
+    : qRound((1<<aprec)*(1 - qPow(cutOffIntensity * (1 / qreal(255)), 1 / radius)));
+    
+    int img_height = img.height();
+    for (int row = 0; row < img_height; ++row) {
+        for (int i = 0; i <= int(improvedQuality); ++i)
+            qt_blurrow<aprec, zprec, alphaOnly>(img, row, alpha);
+    }
+    
+    QImage temp(img.height(), img.width(), img.format());
+    temp.setDevicePixelRatio(img.devicePixelRatioF());
+    if (transposed >= 0) {
+        if (img.depth() == 8) {
+            qt_memrotate270(reinterpret_cast<const quint8*>(img.bits()),
+                            img.width(), img.height(), img.bytesPerLine(),
+                            reinterpret_cast<quint8*>(temp.bits()),
+                            temp.bytesPerLine());
+        } else {
+            qt_memrotate270(reinterpret_cast<const quint32*>(img.bits()),
+                            img.width(), img.height(), img.bytesPerLine(),
+                            reinterpret_cast<quint32*>(temp.bits()),
+                            temp.bytesPerLine());
+        }
+    } else {
+        if (img.depth() == 8) {
+            qt_memrotate90(reinterpret_cast<const quint8*>(img.bits()),
+                           img.width(), img.height(), img.bytesPerLine(),
+                           reinterpret_cast<quint8*>(temp.bits()),
+                           temp.bytesPerLine());
+        } else {
+            qt_memrotate90(reinterpret_cast<const quint32*>(img.bits()),
+                           img.width(), img.height(), img.bytesPerLine(),
+                           reinterpret_cast<quint32*>(temp.bits()),
+                           temp.bytesPerLine());
+        }
+    }
+    
+    img_height = temp.height();
+    for (int row = 0; row < img_height; ++row) {
+        for (int i = 0; i <= int(improvedQuality); ++i)
+            qt_blurrow<aprec, zprec, alphaOnly>(temp, row, alpha);
+    }
+    
+    if (transposed == 0) {
+        if (img.depth() == 8) {
+            qt_memrotate90(reinterpret_cast<const quint8*>(temp.bits()),
+                           temp.width(), temp.height(), temp.bytesPerLine(),
+                           reinterpret_cast<quint8*>(img.bits()),
+                           img.bytesPerLine());
+        } else {
+            qt_memrotate90(reinterpret_cast<const quint32*>(temp.bits()),
+                           temp.width(), temp.height(), temp.bytesPerLine(),
+                           reinterpret_cast<quint32*>(img.bits()),
+                           img.bytesPerLine());
+        }
+    } else {
+        img = temp;
+    }
+}
+
+void Utils::exponentialblur(QImage& img, qreal radius, bool improvedQuality, int transposed)
+{
+    expblur<12, 10, false>(img, radius, improvedQuality, transposed);
+}
diff --git a/ui/utils/eb.h b/ui/utils/eb.h
new file mode 100644
index 0000000..665a9ee
--- /dev/null
+++ b/ui/utils/eb.h
@@ -0,0 +1,34 @@
+/*
+ * Squawk messenger. 
+ * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+ * 
+ * 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/>.
+ */
+
+#ifndef EB_H
+#define EB_H
+
+#include <QObject>
+#include <QImage>
+#include <QtMath>
+
+/**
+ * @todo write docs
+ */
+
+namespace Utils {
+    void exponentialblur(QImage &img, qreal radius, bool improvedQuality = false, int transposed = 0);
+};
+
+#endif // EB_H
diff --git a/ui/utils/feedview.cpp b/ui/utils/feedview.cpp
index 45cb723..58bcc45 100644
--- a/ui/utils/feedview.cpp
+++ b/ui/utils/feedview.cpp
@@ -25,6 +25,7 @@
 
 #include "messagedelegate.h"
 #include "ui/models/messagefeed.h"
+#include "eb.h"
 
 constexpr int maxMessageHeight = 10000;
 constexpr int approximateSingleMessageHeight = 20;
@@ -288,6 +289,10 @@ void FeedView::paintEvent(QPaintEvent* event)
         del->endClearWidgets();
         clearWidgetsMode = false;
     }
+    
+    if (event->rect().height() == vp->height()) {
+        // draw the blurred drop shadow...
+    }
 }
 
 void FeedView::verticalScrollbarValueChanged(int value)
@@ -300,6 +305,10 @@ void FeedView::verticalScrollbarValueChanged(int value)
         clearWidgetsMode = true;
     }
     
+    if (modelState == Models::MessageFeed::incomplete && value < progressSize) {
+        model()->fetchMore(rootIndex());
+    }
+    
     QAbstractItemView::verticalScrollbarValueChanged(vo);
 }
 
@@ -317,6 +326,7 @@ void FeedView::resizeEvent(QResizeEvent* event)
     QAbstractItemView::resizeEvent(event);
     
     positionProgress();
+    emit resized();
 }
 
 void FeedView::positionProgress()
diff --git a/ui/utils/feedview.h b/ui/utils/feedview.h
index 2789464..0b7e7d9 100644
--- a/ui/utils/feedview.h
+++ b/ui/utils/feedview.h
@@ -49,6 +49,9 @@ public:
     
     QFont getFont() const;
     
+signals:
+    void resized();
+    
 public slots:
     
 protected slots:
diff --git a/ui/utils/shadowoverlay.cpp b/ui/utils/shadowoverlay.cpp
new file mode 100644
index 0000000..3c28a15
--- /dev/null
+++ b/ui/utils/shadowoverlay.cpp
@@ -0,0 +1,91 @@
+/*
+ * Squawk messenger. 
+ * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+ * 
+ * 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 "shadowoverlay.h"
+
+ShadowOverlay::ShadowOverlay(unsigned int r, unsigned int t, const QColor& c, QWidget* parent):
+    QWidget(parent),
+    top(false),
+    right(false),
+    bottom(false),
+    left(false),
+    thickness(t),
+    radius(r),
+    color(c),
+    shadow(1, 1, QImage::Format_ARGB32_Premultiplied)
+{
+    setAttribute(Qt::WA_NoSystemBackground);
+    setAttribute(Qt::WA_TransparentForMouseEvents);
+}
+
+void ShadowOverlay::paintEvent(QPaintEvent* event)
+{
+    QWidget::paintEvent(event);
+    
+    QPainter painter(this);
+    
+    painter.drawImage(0, 0, shadow);
+}
+
+void ShadowOverlay::resizeEvent(QResizeEvent* event)
+{
+    QWidget::resizeEvent(event);
+    
+    updateImage();
+}
+
+void ShadowOverlay::updateImage()
+{
+    int w = width();
+    int h = height();
+    shadow = QImage({w, h + int(thickness)}, QImage::Format_ARGB32_Premultiplied);
+    shadow.fill(0);
+    
+    QPainter tmpPainter(&shadow);
+    tmpPainter.setCompositionMode(QPainter::CompositionMode_Source);
+    if (top) {
+        QRectF shadow(0, 0, w, thickness);
+        tmpPainter.fillRect(shadow, color);
+    }
+    if (right) {
+        QRectF shadow(w - thickness, 0, thickness, h);
+        tmpPainter.fillRect(shadow, color);
+    }
+    if (bottom) {
+        QRectF shadow(0, h - thickness, w, thickness * 2); //i have no idea why, but it leaves some unpainted stripe without some spare space 
+        tmpPainter.fillRect(shadow, color);
+    }
+    if (left) {
+        QRectF shadow(0, 0, thickness, h);
+        tmpPainter.fillRect(shadow, color);
+    }
+    
+    Utils::exponentialblur(shadow, radius, false, 0);
+    tmpPainter.end();
+}
+
+void ShadowOverlay::setFrames(bool t, bool r, bool b, bool l)
+{
+    top = t;
+    right = r;
+    bottom = b;
+    left = l;
+    
+    updateImage();
+    update();
+}
diff --git a/ui/utils/shadowoverlay.h b/ui/utils/shadowoverlay.h
new file mode 100644
index 0000000..36aa5d5
--- /dev/null
+++ b/ui/utils/shadowoverlay.h
@@ -0,0 +1,58 @@
+/*
+ * Squawk messenger. 
+ * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+ * 
+ * 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/>.
+ */
+
+#ifndef SHADOWOVERLAY_H
+#define SHADOWOVERLAY_H
+
+#include <QWidget>
+#include <QImage>
+#include <QPainter>
+#include <QColor>
+#include <QPaintEvent>
+#include <QResizeEvent>
+
+#include <ui/utils/eb.h>
+
+/**
+ * @todo write docs
+ */
+class ShadowOverlay : public QWidget {
+    
+public:
+    ShadowOverlay(unsigned int radius = 10, unsigned int thickness = 1, const QColor& color = Qt::black, QWidget* parent = nullptr);
+    
+    void setFrames(bool top, bool right, bool bottom, bool left);
+    
+protected:
+    void updateImage();
+    
+    void paintEvent(QPaintEvent * event) override;
+    void resizeEvent(QResizeEvent * event) override;
+    
+private:
+    bool top;
+    bool right;
+    bool bottom;
+    bool left;
+    unsigned int thickness;
+    unsigned int radius;
+    QColor color;
+    QImage shadow;
+};
+
+#endif // SHADOWOVERLAY_H
diff --git a/ui/widgets/CMakeLists.txt b/ui/widgets/CMakeLists.txt
index 0a21f04..830fee6 100644
--- a/ui/widgets/CMakeLists.txt
+++ b/ui/widgets/CMakeLists.txt
@@ -26,5 +26,6 @@ add_library(squawkWidgets ${squawkWidgets_SRC})
 # Use the Widgets module from Qt 5.
 target_link_libraries(squawkWidgets vCardUI)
 target_link_libraries(squawkWidgets Qt5::Widgets)
+target_link_libraries(squawkWidgets squawkUI)
 
 qt5_use_modules(squawkWidgets Core Widgets)
diff --git a/ui/widgets/conversation.cpp b/ui/widgets/conversation.cpp
index b8141c5..39f6837 100644
--- a/ui/widgets/conversation.cpp
+++ b/ui/widgets/conversation.cpp
@@ -46,19 +46,23 @@ Conversation::Conversation(bool muc, Models::Account* acc, Models::Element* el,
     filesToAttach(),
     feed(new FeedView()),
     delegate(new MessageDelegate(this)),
-    scroll(down),
     manualSliderChange(false),
-    tsb(QApplication::style()->styleHint(QStyle::SH_ScrollBar_Transient) == 1)
+    tsb(QApplication::style()->styleHint(QStyle::SH_ScrollBar_Transient) == 1),
+    shadow(10, 1, Qt::black, this)
 {
     m_ui->setupUi(this);
     
+    shadow.setFrames(true, false, true, false);
+    
     feed->setItemDelegate(delegate);
+    feed->setFrameShape(QFrame::NoFrame);
     delegate->initializeFonts(feed->getFont());
     feed->setModel(el->feed);
     el->feed->incrementObservers();
     m_ui->widget->layout()->addWidget(feed);
     
     connect(el->feed, &Models::MessageFeed::newMessage, this, &Conversation::onFeedMessage);
+    connect(feed, &FeedView::resized, this, &Conversation::positionShadow);
     
     connect(acc, &Models::Account::childChanged, this, &Conversation::onAccountChanged);
     
@@ -77,9 +81,6 @@ Conversation::Conversation(bool muc, Models::Account* acc, Models::Element* el,
     
     m_ui->messageEditor->installEventFilter(&ker);
     
-    //QScrollBar* vs = m_ui->scrollArea->verticalScrollBar();
-    //m_ui->scrollArea->setWidget(line);
-    //vs->installEventFilter(&vis);
     
     //line->setAutoFillBackground(false);
     //if (testAttribute(Qt::WA_TranslucentBackground)) {
@@ -93,28 +94,7 @@ Conversation::Conversation(bool muc, Models::Account* acc, Models::Element* el,
     //line->setMyAvatarPath(acc->getAvatarPath());
     //line->setMyName(acc->getName());
     
-    QGridLayout* gr = static_cast<QGridLayout*>(layout());
-    QLabel* progressLabel = new QLabel(tr("Drop files here to attach them to your message"));
-    gr->addWidget(overlay, 0, 0, 2, 1);
-    QVBoxLayout* nl = new QVBoxLayout();
-    QGraphicsOpacityEffect* opacity = new QGraphicsOpacityEffect();
-    opacity->setOpacity(0.8);
-    overlay->setLayout(nl);
-    overlay->setBackgroundRole(QPalette::Base);
-    overlay->setAutoFillBackground(true);
-    overlay->setGraphicsEffect(opacity);
-    progressLabel->setAlignment(Qt::AlignCenter);
-    QFont pf = progressLabel->font();
-    pf.setBold(true);
-    pf.setPointSize(26);
-    progressLabel->setWordWrap(true);
-    progressLabel->setFont(pf);
-    nl->addStretch();
-    nl->addWidget(progressLabel);
-    nl->addStretch();
-    overlay->hide();
-    
-    applyVisualEffects();
+    initializeOverlay();
 }
 
 Conversation::~Conversation()
@@ -136,14 +116,28 @@ void Conversation::onAccountChanged(Models::Item* item, int row, int col)
     }
 }
 
-void Conversation::applyVisualEffects()
+void Conversation::initializeOverlay()
 {
-//     DropShadowEffect *e1 = new DropShadowEffect;
-//     e1->setBlurRadius(10);
-//     e1->setColor(Qt::black);
-//     e1->setThickness(1);
-//     e1->setFrame(true, false, true, false);
-//     m_ui->scrollArea->setGraphicsEffect(e1);
+    QGridLayout* gr = static_cast<QGridLayout*>(layout());
+    QLabel* progressLabel = new QLabel(tr("Drop files here to attach them to your message"));
+    gr->addWidget(overlay, 0, 0, 2, 1);
+    QVBoxLayout* nl = new QVBoxLayout();
+    QGraphicsOpacityEffect* opacity = new QGraphicsOpacityEffect();
+    opacity->setOpacity(0.8);
+    overlay->setLayout(nl);
+    overlay->setBackgroundRole(QPalette::Base);
+    overlay->setAutoFillBackground(true);
+    overlay->setGraphicsEffect(opacity);
+    progressLabel->setAlignment(Qt::AlignCenter);
+    QFont pf = progressLabel->font();
+    pf.setBold(true);
+    pf.setPointSize(26);
+    progressLabel->setWordWrap(true);
+    progressLabel->setFont(pf);
+    nl->addStretch();
+    nl->addWidget(progressLabel);
+    nl->addStretch();
+    overlay->hide();
 }
 
 void Conversation::setName(const QString& name)
@@ -325,7 +319,7 @@ void Conversation::onTextEditDocSizeChanged(const QSizeF& size)
 
 void Conversation::setFeedFrames(bool top, bool right, bool bottom, bool left)
 {
-    //static_cast<DropShadowEffect*>(m_ui->scrollArea->graphicsEffect())->setFrame(top, right, bottom, left);
+    shadow.setFrames(top, right, bottom, left);
 }
 
 void Conversation::dragEnterEvent(QDragEnterEvent* event)
@@ -399,3 +393,13 @@ void Conversation::onMessage(const Shared::Message& msg)
         }
     }
 }
+
+void Conversation::positionShadow()
+{
+    int w = width();
+    int h = feed->height();
+    
+    shadow.resize(w, h);
+    shadow.move(feed->pos());
+    shadow.raise();
+}
diff --git a/ui/widgets/conversation.h b/ui/widgets/conversation.h
index 1c81d31..5f98d8a 100644
--- a/ui/widgets/conversation.h
+++ b/ui/widgets/conversation.h
@@ -34,6 +34,7 @@
 #include "ui/utils/badge.h"
 #include "ui/utils/feedview.h"
 #include "ui/utils/messagedelegate.h"
+#include "ui/utils/shadowoverlay.h"
 #include "shared/icons.h"
 #include "shared/utils.h"
 
@@ -81,7 +82,6 @@ signals:
     
 protected:
     virtual void setName(const QString& name);
-    void applyVisualEffects();
     virtual Shared::Message createMessage() const;
     void setStatus(const QString& status);
     void addAttachedFile(const QString& path);
@@ -90,6 +90,7 @@ protected:
     void dragEnterEvent(QDragEnterEvent* event) override;
     void dragLeaveEvent(QDragLeaveEvent* event) override;
     void dropEvent(QDropEvent* event) override;
+    void initializeOverlay();
     virtual void onMessage(const Shared::Message& msg);
     
 protected slots:
@@ -101,16 +102,12 @@ protected slots:
     void onTextEditDocSizeChanged(const QSizeF& size);
     void onAccountChanged(Models::Item* item, int row, int col);
     void onFeedMessage(const Shared::Message& msg);
+    void positionShadow();
     
 public:
     const bool isMuc;
     
 protected:
-    enum Scroll {
-        nothing,
-        keep,
-        down
-    };
     Models::Account* account;
     Models::Element* element;
     QString palJid;
@@ -125,9 +122,10 @@ protected:
     W::Order<Badge*, Badge::Comparator> filesToAttach;
     FeedView* feed;
     MessageDelegate* delegate;
-    Scroll scroll;
     bool manualSliderChange;
     bool tsb;           //transient scroll bars
+    
+    ShadowOverlay shadow;
 };
 
 #endif // CONVERSATION_H

From f34289399e904982be540623ace0fd2f0baae16d Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Mon, 3 May 2021 14:23:41 +0300
Subject: [PATCH 108/281] text inside of message is selectable again, links are
 clickable, some refactor, some bugfixes

---
 core/rosteritem.cpp                      |   3 +-
 ui/CMakeLists.txt                        |  15 +--
 ui/utils/CMakeLists.txt                  |  32 +++++
 ui/utils/dropshadoweffect.cpp            | 145 -----------------------
 ui/utils/dropshadoweffect.h              |  95 ---------------
 ui/utils/{eb.cpp => exponentialblur.cpp} |   2 +-
 ui/utils/{eb.h => exponentialblur.h}     |   6 +-
 ui/utils/feedview.cpp                    |   2 +-
 ui/utils/messagedelegate.cpp             |  97 ++++++++++-----
 ui/utils/messagedelegate.h               |   3 +
 ui/utils/shadowoverlay.h                 |   2 +-
 ui/widgets/CMakeLists.txt                |   2 +-
 ui/widgets/conversation.cpp              |   1 -
 ui/widgets/conversation.h                |   2 +-
 14 files changed, 115 insertions(+), 292 deletions(-)
 create mode 100644 ui/utils/CMakeLists.txt
 delete mode 100644 ui/utils/dropshadoweffect.cpp
 delete mode 100644 ui/utils/dropshadoweffect.h
 rename ui/utils/{eb.cpp => exponentialblur.cpp} (99%)
 rename ui/utils/{eb.h => exponentialblur.h} (92%)

diff --git a/core/rosteritem.cpp b/core/rosteritem.cpp
index 5014ddd..9b121fb 100644
--- a/core/rosteritem.cpp
+++ b/core/rosteritem.cpp
@@ -377,6 +377,7 @@ void Core::RosterItem::flushMessagesToArchive(bool finished, const QString& firs
             }
             if (added == 0 && wasEmpty) {
                 archiveState = empty;
+                nextRequest();
                 break;
             }
             if (requestedCount != -1) {
@@ -397,7 +398,7 @@ void Core::RosterItem::flushMessagesToArchive(bool finished, const QString& firs
                 } catch (const Archive::Empty& e) {
                     
                 }
-                if (!found || requestedCount > responseCache.size()) {
+                if (!found || requestedCount > int(responseCache.size())) {
                     if (archiveState == complete) {
                         nextRequest();
                     } else {
diff --git a/ui/CMakeLists.txt b/ui/CMakeLists.txt
index 4b53439..00570c9 100644
--- a/ui/CMakeLists.txt
+++ b/ui/CMakeLists.txt
@@ -14,6 +14,7 @@ if(Boost_FOUND)
   include_directories(${Boost_INCLUDE_DIRS})
 endif()
 
+add_subdirectory(utils)
 add_subdirectory(widgets)
 
 set(squawkUI_SRC
@@ -31,19 +32,6 @@ set(squawkUI_SRC
   models/reference.cpp
   models/messagefeed.cpp
   models/element.cpp
-  utils/messageline.cpp
-  utils/message.cpp
-  utils/resizer.cpp
-  utils/image.cpp
-  utils/flowlayout.cpp
-  utils/badge.cpp
-  utils/progress.cpp
-  utils/comboboxdelegate.cpp
-  utils/dropshadoweffect.cpp
-  utils/feedview.cpp
-  utils/messagedelegate.cpp
-  utils/eb.cpp
-  utils/shadowoverlay.cpp
 )
 
 # Tell CMake to create the helloworld executable
@@ -51,5 +39,6 @@ add_library(squawkUI ${squawkUI_SRC})
 
 # Use the Widgets module from Qt 5.
 target_link_libraries(squawkUI squawkWidgets)
+target_link_libraries(squawkUI squawkUIUtils)
 target_link_libraries(squawkUI Qt5::Widgets)
 target_link_libraries(squawkUI Qt5::DBus)
diff --git a/ui/utils/CMakeLists.txt b/ui/utils/CMakeLists.txt
new file mode 100644
index 0000000..e656bde
--- /dev/null
+++ b/ui/utils/CMakeLists.txt
@@ -0,0 +1,32 @@
+cmake_minimum_required(VERSION 3.3)
+project(squawkUIUtils)
+
+# Instruct CMake to run moc automatically when needed.
+set(CMAKE_AUTOMOC ON)
+# Instruct CMake to create code from Qt designer ui files
+set(CMAKE_AUTOUIC ON)
+
+# Find the QtWidgets library
+find_package(Qt5 CONFIG REQUIRED COMPONENTS Widgets Core)
+
+set(squawkUIUtils_SRC
+#  messageline.cpp
+#  message.cpp
+  resizer.cpp
+#  image.cpp
+  flowlayout.cpp
+  badge.cpp
+  progress.cpp
+  comboboxdelegate.cpp
+  feedview.cpp
+  messagedelegate.cpp
+  exponentialblur.cpp
+  shadowoverlay.cpp
+)
+
+# Tell CMake to create the helloworld executable
+add_library(squawkUIUtils ${squawkUIUtils_SRC})
+
+# Use the Widgets module from Qt 5.
+target_link_libraries(squawkUIUtils squawkWidgets)
+target_link_libraries(squawkUIUtils Qt5::Widgets)
diff --git a/ui/utils/dropshadoweffect.cpp b/ui/utils/dropshadoweffect.cpp
deleted file mode 100644
index 1090fcd..0000000
--- a/ui/utils/dropshadoweffect.cpp
+++ /dev/null
@@ -1,145 +0,0 @@
-/*
- * Squawk messenger. 
- * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
- * 
- * 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 "dropshadoweffect.h"
-
-PixmapFilter::PixmapFilter(QObject* parent):QObject(parent) {}
-PixmapFilter::~PixmapFilter(){}
-QRectF PixmapFilter::boundingRectFor(const QRectF &rect) const {return rect;}
-
-PixmapDropShadowFilter::PixmapDropShadowFilter(QObject *parent):
-    PixmapFilter(parent),
-    mColor(63, 63, 63, 180),
-    mRadius(1),
-    mThickness(2),
-    top(true),
-    right(true),
-    bottom(true),
-    left(true){}
-
-PixmapDropShadowFilter::~PixmapDropShadowFilter() {}
-qreal PixmapDropShadowFilter::blurRadius() const {return mRadius;}
-void PixmapDropShadowFilter::setBlurRadius(qreal radius) {mRadius = radius;}
-QColor PixmapDropShadowFilter::color() const {return mColor;}
-void PixmapDropShadowFilter::setColor(const QColor &color) {mColor = color;}
-qreal PixmapDropShadowFilter::thickness() const {return mThickness;}
-void PixmapDropShadowFilter::setThickness(qreal thickness) {mThickness = thickness;}
-void PixmapDropShadowFilter::setFrame(bool ptop, bool pright, bool pbottom, bool pleft)
-{
-    top = ptop;
-    right = pright;
-    bottom = pbottom;
-    left = pleft;
-}
-
-void DropShadowEffect::setThickness(qreal thickness)
-{
-    if (filter.thickness() == thickness)
-        return;
-    
-    filter.setThickness(thickness);
-    update();
-}
-
-
-void PixmapDropShadowFilter::draw(QPainter *p, const QPointF &pos, const QPixmap &px, const QRectF &src) const
-{
-    if (px.isNull())
-        return;
-    
-    QImage tmp({px.width(), px.height() + int(mThickness)}, QImage::Format_ARGB32_Premultiplied);
-    tmp.setDevicePixelRatio(px.devicePixelRatioF());
-    tmp.fill(0);
-    QPainter tmpPainter(&tmp);
-    tmpPainter.setCompositionMode(QPainter::CompositionMode_Source);
-    if (top) {
-        QRectF shadow(0, 0, px.width(), mThickness);
-        tmpPainter.fillRect(shadow, mColor);
-    }
-    if (right) {
-        QRectF shadow(px.width() - mThickness, 0, mThickness, px.height());
-        tmpPainter.fillRect(shadow, mColor);
-    }
-    if (bottom) {
-        QRectF shadow(0, px.height() - mThickness, px.width(), mThickness * 2); //i have no idea why, but it leaves some unpainted stripe without some spare space 
-        tmpPainter.fillRect(shadow, mColor);
-    }
-    if (left) {
-        QRectF shadow(0, 0, mThickness, px.height());
-        tmpPainter.fillRect(shadow, mColor);
-    }
-    
-    Utils::exponentialblur(tmp, mRadius, false, 0);
-    tmpPainter.end();
-    
-    // Draw the actual pixmap...
-    p->drawPixmap(pos, px, src);
-    
-    // draw the blurred drop shadow...
-    p->drawImage(pos, tmp);
-}
-
-qreal DropShadowEffect::blurRadius() const {return filter.blurRadius();}
-void DropShadowEffect::setBlurRadius(qreal blurRadius)
-{
-    if (qFuzzyCompare(filter.blurRadius(), blurRadius))
-        return;
-    
-    filter.setBlurRadius(blurRadius);
-    updateBoundingRect();
-    emit blurRadiusChanged(blurRadius);
-}
-
-void DropShadowEffect::setFrame(bool top, bool right, bool bottom, bool left)
-{
-    filter.setFrame(top, right, bottom, left);
-    update();
-}
-
-
-QColor DropShadowEffect::color() const {return filter.color();}
-void DropShadowEffect::setColor(const QColor &color)
-{
-    if (filter.color() == color)
-        return;
-    
-    filter.setColor(color);
-    update();
-    emit colorChanged(color);
-}
-
-void DropShadowEffect::draw(QPainter* painter)
-{
-    if (filter.blurRadius() <= 0 && filter.thickness() == 0) {
-        drawSource(painter);
-        return;
-    }
-    
-    PixmapPadMode mode = PadToEffectiveBoundingRect;
-    
-    // Draw pixmap in device coordinates to avoid pixmap scaling.
-    QPoint offset;
-    const QPixmap pixmap = sourcePixmap(Qt::DeviceCoordinates, &offset, mode);
-    if (pixmap.isNull())
-        return;
-    
-    QTransform restoreTransform = painter->worldTransform();
-    painter->setWorldTransform(QTransform());
-    filter.draw(painter, offset, pixmap);
-    painter->setWorldTransform(restoreTransform);
-}
diff --git a/ui/utils/dropshadoweffect.h b/ui/utils/dropshadoweffect.h
deleted file mode 100644
index f374ce3..0000000
--- a/ui/utils/dropshadoweffect.h
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Squawk messenger. 
- * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
- * 
- * 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/>.
- */
-
-#ifndef DROPSHADOWEFFECT_H
-#define DROPSHADOWEFFECT_H
-
-#include <QGraphicsEffect>
-#include <QPainter>
-#include <QPointF>
-#include <QColor>
-
-#include "eb.h"
-
-class PixmapFilter : public QObject
-{
-    Q_OBJECT
-public:
-    PixmapFilter(QObject *parent = nullptr);
-    virtual ~PixmapFilter() = 0;
-    
-    virtual QRectF boundingRectFor(const QRectF &rect) const;
-    virtual void draw(QPainter *painter, const QPointF &p, const QPixmap &src, const QRectF &srcRect = QRectF()) const = 0;
-};
-
-class PixmapDropShadowFilter : public PixmapFilter
-{
-    Q_OBJECT
-    
-public:
-    PixmapDropShadowFilter(QObject *parent = nullptr);
-    ~PixmapDropShadowFilter();
-    
-    void draw(QPainter *p, const QPointF &pos, const QPixmap &px, const QRectF &src = QRectF()) const override;
-    
-    qreal blurRadius() const;
-    void setBlurRadius(qreal radius);
-    
-    QColor color() const;
-    void setColor(const QColor &color);
-    
-    qreal thickness() const;
-    void setThickness(qreal thickness);
-    void setFrame(bool top, bool right, bool bottom, bool left);
-    
-protected:
-    QColor mColor;
-    qreal mRadius;
-    qreal mThickness;
-    bool top;
-    bool right;
-    bool bottom;
-    bool left;
-};
-
-class DropShadowEffect : public QGraphicsEffect
-{
-    Q_OBJECT
-public:
-    qreal blurRadius() const;
-    QColor color() const;
-    void setFrame(bool top, bool right, bool bottom, bool left);
-    void setThickness(qreal thickness);
-    
-signals:
-    void blurRadiusChanged(qreal blurRadius);
-    void colorChanged(const QColor &color);
-    
-public slots:
-    void setBlurRadius(qreal blurRadius);
-    void setColor(const QColor &color);
-    
-protected:
-    void draw(QPainter * painter) override;
-    
-protected:
-    PixmapDropShadowFilter filter;
-    
-};
-
-#endif // DROPSHADOWEFFECT_H
diff --git a/ui/utils/eb.cpp b/ui/utils/exponentialblur.cpp
similarity index 99%
rename from ui/utils/eb.cpp
rename to ui/utils/exponentialblur.cpp
index f44e53b..cb222dc 100644
--- a/ui/utils/eb.cpp
+++ b/ui/utils/exponentialblur.cpp
@@ -16,7 +16,7 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#include "eb.h"
+#include "exponentialblur.h"
 
 static const int tileSize = 32;
 template <class T>
diff --git a/ui/utils/eb.h b/ui/utils/exponentialblur.h
similarity index 92%
rename from ui/utils/eb.h
rename to ui/utils/exponentialblur.h
index 665a9ee..0a5df8a 100644
--- a/ui/utils/eb.h
+++ b/ui/utils/exponentialblur.h
@@ -16,8 +16,8 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#ifndef EB_H
-#define EB_H
+#ifndef EXPONENTIALBLUR_H
+#define EXPONENTIALBLUR_H
 
 #include <QObject>
 #include <QImage>
@@ -31,4 +31,4 @@ namespace Utils {
     void exponentialblur(QImage &img, qreal radius, bool improvedQuality = false, int transposed = 0);
 };
 
-#endif // EB_H
+#endif // EXPONENTIALBLUR_H
diff --git a/ui/utils/feedview.cpp b/ui/utils/feedview.cpp
index 58bcc45..226b9a7 100644
--- a/ui/utils/feedview.cpp
+++ b/ui/utils/feedview.cpp
@@ -25,7 +25,6 @@
 
 #include "messagedelegate.h"
 #include "ui/models/messagefeed.h"
-#include "eb.h"
 
 constexpr int maxMessageHeight = 10000;
 constexpr int approximateSingleMessageHeight = 20;
@@ -339,6 +338,7 @@ void FeedView::positionProgress()
         progressPosition -= hint.offset + hint.height;
     }
     progressPosition += vo;
+    progressPosition = qMin(progressPosition, 0);
     
     progress.move((width() - progressSize) / 2, progressPosition);
 }
diff --git a/ui/utils/messagedelegate.cpp b/ui/utils/messagedelegate.cpp
index c98710c..0ea64d8 100644
--- a/ui/utils/messagedelegate.cpp
+++ b/ui/utils/messagedelegate.cpp
@@ -31,20 +31,21 @@ constexpr int statusIconSize = 16;
 constexpr int maxAttachmentHeight = 500;
 
 MessageDelegate::MessageDelegate(QObject* parent):
-QStyledItemDelegate(parent),
-bodyFont(),
-nickFont(),
-dateFont(),
-bodyMetrics(bodyFont),
-nickMetrics(nickFont),
-dateMetrics(dateFont),
-buttonHeight(0),
-barHeight(0),
-buttons(new std::map<QString, FeedButton*>()),
-bars(new std::map<QString, QProgressBar*>()),
-statusIcons(new std::map<QString, QLabel*>()),
-idsToKeep(new std::set<QString>()),
-clearingWidgets(false)
+    QStyledItemDelegate(parent),
+    bodyFont(),
+    nickFont(),
+    dateFont(),
+    bodyMetrics(bodyFont),
+    nickMetrics(nickFont),
+    dateMetrics(dateFont),
+    buttonHeight(0),
+    barHeight(0),
+    buttons(new std::map<QString, FeedButton*>()),
+    bars(new std::map<QString, QProgressBar*>()),
+    statusIcons(new std::map<QString, QLabel*>()),
+    bodies(new std::map<QString, QLabel*>()),
+    idsToKeep(new std::set<QString>()),
+    clearingWidgets(false)
 {
     QPushButton btn;
     buttonHeight = btn.sizeHint().height();
@@ -67,9 +68,14 @@ MessageDelegate::~MessageDelegate()
         delete pair.second;
     }
     
+    for (const std::pair<const QString, QLabel*>& pair: *bodies){
+        delete pair.second;
+    }
+    
     delete idsToKeep;
     delete buttons;
     delete bars;
+    delete bodies;
 }
 
 void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
@@ -105,8 +111,10 @@ void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio
     opt.rect = messageRect;
     
     QSize messageSize(0, 0);
+    QSize bodySize(0, 0);
     if (data.text.size() > 0) {
         messageSize = bodyMetrics.boundingRect(messageRect, Qt::TextWordWrap, data.text).size();
+        bodySize = messageSize;
     }
     messageSize.rheight() += nickMetrics.lineSpacing();
     messageSize.rheight() += dateMetrics.height();
@@ -146,12 +154,21 @@ void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio
     }
     painter->restore();
     
-    int messageLeft = 10000; //TODO
+    int messageLeft = INT16_MAX;
+    QWidget* vp = static_cast<QWidget*>(painter->device());
     if (data.text.size() > 0) {
-        painter->setFont(bodyFont);
-        painter->drawText(opt.rect, opt.displayAlignment | Qt::TextWordWrap, data.text, &rect);
-        opt.rect.adjust(0, rect.height() + textMargin, 0, 0);
-        messageLeft = rect.x();
+        QLabel* body = getBody(data);
+        body->setParent(vp);
+        body->setMaximumWidth(bodySize.width());
+        body->setMinimumWidth(bodySize.width());
+        body->setAlignment(opt.displayAlignment);
+        messageLeft = opt.rect.x();
+        if (data.sentByMe) {
+            messageLeft = opt.rect.topRight().x() - bodySize.width();
+        }
+        body->move(messageLeft, opt.rect.y());
+        body->show();
+        opt.rect.adjust(0, bodySize.height() + textMargin, 0, 0);
     }
     painter->setFont(dateFont);
     QColor q = painter->pen().color();
@@ -164,7 +181,6 @@ void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio
         }
         QLabel* statusIcon = getStatusIcon(data);
         
-        QWidget* vp = static_cast<QWidget*>(painter->device());
         statusIcon->setParent(vp);
         statusIcon->move(messageLeft, opt.rect.y());
         statusIcon->show();
@@ -280,15 +296,7 @@ void MessageDelegate::paintButton(QPushButton* btn, QPainter* painter, bool sent
 void MessageDelegate::paintBar(QProgressBar* bar, QPainter* painter, bool sentByMe, QStyleOptionViewItem& option) const
 {
     QPoint start = option.rect.topLeft();
-    
-    //QWidget* vp = static_cast<QWidget*>(painter->device());
-    
-//     if (bar->parent() != vp) {
-//         bar->setParent(vp);
-//     }
-//     bar->move(start);
-    bar->resize(option.rect.width(), barHeight);
-    //     bar->show();      
+    bar->resize(option.rect.width(), barHeight);   
     
     painter->translate(start);
     bar->render(painter, QPoint(), QRegion(), QWidget::DrawChildren);
@@ -410,6 +418,27 @@ QLabel * MessageDelegate::getStatusIcon(const Models::FeedItem& data) const
     return result;
 }
 
+QLabel * MessageDelegate::getBody(const Models::FeedItem& data) const
+{
+    std::map<QString, QLabel*>::const_iterator itr = bodies->find(data.id);
+    QLabel* result = 0;
+    
+    if (itr != bodies->end()) {
+        result = itr->second;
+    } else {
+        result = new QLabel();
+        result->setFont(bodyFont);
+        result->setWordWrap(true);
+        result->setOpenExternalLinks(true);
+        result->setTextInteractionFlags(result->textInteractionFlags() | Qt::TextSelectableByMouse | Qt::LinksAccessibleByMouse);
+        bodies->insert(std::make_pair(data.id, result));
+    }
+    
+    result->setText(Shared::processMessageBody(data.text));
+    
+    return result;
+}
+
 void MessageDelegate::beginClearWidgets()
 {
     idsToKeep->clear();
@@ -422,6 +451,7 @@ void MessageDelegate::endClearWidgets()
         std::set<QString> toRemoveButtons;
         std::set<QString> toRemoveBars;
         std::set<QString> toRemoveIcons;
+        std::set<QString> toRemoveBodies;
         for (const std::pair<const QString, FeedButton*>& pair: *buttons) {
             if (idsToKeep->find(pair.first) == idsToKeep->end()) {
                 delete pair.second;
@@ -440,6 +470,12 @@ void MessageDelegate::endClearWidgets()
                 toRemoveIcons.insert(pair.first);
             }
         }
+        for (const std::pair<const QString, QLabel*>& pair: *bodies) {
+            if (idsToKeep->find(pair.first) == idsToKeep->end()) {
+                delete pair.second;
+                toRemoveBodies.insert(pair.first);
+            }
+        }
         
         for (const QString& key : toRemoveButtons) {
             buttons->erase(key);
@@ -450,6 +486,9 @@ void MessageDelegate::endClearWidgets()
         for (const QString& key : toRemoveIcons) {
             statusIcons->erase(key);
         }
+        for (const QString& key : toRemoveBodies) {
+            bodies->erase(key);
+        }
         
         idsToKeep->clear();
         clearingWidgets = false;
diff --git a/ui/utils/messagedelegate.h b/ui/utils/messagedelegate.h
index 97822eb..6a257b7 100644
--- a/ui/utils/messagedelegate.h
+++ b/ui/utils/messagedelegate.h
@@ -32,6 +32,7 @@
 
 #include "shared/icons.h"
 #include "shared/global.h"
+#include "shared/utils.h"
 
 namespace Models {
     struct FeedItem;
@@ -64,6 +65,7 @@ protected:
     QPushButton* getButton(const Models::FeedItem& data) const;
     QProgressBar* getBar(const Models::FeedItem& data) const;
     QLabel* getStatusIcon(const Models::FeedItem& data) const;
+    QLabel* getBody(const Models::FeedItem& data) const;
     void clearHelperWidget(const Models::FeedItem& data) const;
     QSize calculateAttachSize(const QString& path, const QRect& bounds) const;
     QSize constrainAttachSize(QSize src, QSize bounds) const;
@@ -91,6 +93,7 @@ private:
     std::map<QString, FeedButton*>* buttons;
     std::map<QString, QProgressBar*>* bars;
     std::map<QString, QLabel*>* statusIcons;
+    std::map<QString, QLabel*>* bodies;
     std::set<QString>* idsToKeep;
     bool clearingWidgets;
     
diff --git a/ui/utils/shadowoverlay.h b/ui/utils/shadowoverlay.h
index 36aa5d5..524115a 100644
--- a/ui/utils/shadowoverlay.h
+++ b/ui/utils/shadowoverlay.h
@@ -26,7 +26,7 @@
 #include <QPaintEvent>
 #include <QResizeEvent>
 
-#include <ui/utils/eb.h>
+#include <ui/utils/exponentialblur.h>
 
 /**
  * @todo write docs
diff --git a/ui/widgets/CMakeLists.txt b/ui/widgets/CMakeLists.txt
index 830fee6..abf238c 100644
--- a/ui/widgets/CMakeLists.txt
+++ b/ui/widgets/CMakeLists.txt
@@ -25,7 +25,7 @@ add_library(squawkWidgets ${squawkWidgets_SRC})
 
 # Use the Widgets module from Qt 5.
 target_link_libraries(squawkWidgets vCardUI)
+target_link_libraries(squawkWidgets squawkUIUtils)
 target_link_libraries(squawkWidgets Qt5::Widgets)
-target_link_libraries(squawkWidgets squawkUI)
 
 qt5_use_modules(squawkWidgets Core Widgets)
diff --git a/ui/widgets/conversation.cpp b/ui/widgets/conversation.cpp
index 39f6837..e678caf 100644
--- a/ui/widgets/conversation.cpp
+++ b/ui/widgets/conversation.cpp
@@ -18,7 +18,6 @@
 
 #include "conversation.h"
 #include "ui_conversation.h"
-#include "ui/utils/dropshadoweffect.h"
 
 #include <QDebug>
 #include <QScrollBar>
diff --git a/ui/widgets/conversation.h b/ui/widgets/conversation.h
index 5f98d8a..eaec954 100644
--- a/ui/widgets/conversation.h
+++ b/ui/widgets/conversation.h
@@ -24,12 +24,12 @@
 #include <QMap>
 #include <QMimeData>
 #include <QFileInfo>
+#include <QGraphicsOpacityEffect>
 
 #include "shared/message.h"
 #include "order.h"
 #include "ui/models/account.h"
 #include "ui/models/roster.h"
-#include "ui/utils/messageline.h"
 #include "ui/utils/flowlayout.h"
 #include "ui/utils/badge.h"
 #include "ui/utils/feedview.h"

From d514db9c4a9fc0497588b438c11eca0d4431c92c Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Tue, 4 May 2021 17:09:41 +0300
Subject: [PATCH 109/281] message context menu began, open and show in folder
 features

---
 shared/utils.cpp            | 37 +++++++++++++++++++++++++++++++++++++
 shared/utils.h              |  6 ++++++
 ui/models/messagefeed.cpp   | 16 ++++++++++++++++
 ui/models/messagefeed.h     |  1 +
 ui/utils/feedview.cpp       | 12 +++++++++---
 ui/widgets/conversation.cpp | 37 ++++++++++++++++++++++++++++++++++---
 ui/widgets/conversation.h   |  5 +++++
 7 files changed, 108 insertions(+), 6 deletions(-)

diff --git a/shared/utils.cpp b/shared/utils.cpp
index 924be85..f5f7fa5 100644
--- a/shared/utils.cpp
+++ b/shared/utils.cpp
@@ -46,3 +46,40 @@ QString Shared::processMessageBody(const QString& msg)
     processed.replace(urlReg, "<a href=\"\\1\">\\1</a>");
     return "<p style=\"white-space: pre-wrap;\">" + processed + "</p>";
 }
+
+static const QStringList query = {"query", "default", "inode/directory"};
+static const QRegularExpression dolphinReg("[Dd]olphin");
+static const QRegularExpression nautilusReg("[Nn]autilus");
+static const QRegularExpression cajaReg("[Cc]aja");
+static const QRegularExpression nemoReg("[Nn]emo");
+static const QRegularExpression konquerorReg("kfmclient");
+
+void Shared::showInDirectory(const QString& path)
+{
+    QFileInfo info = path;
+    if (info.exists()) {
+        QProcess proc;
+        proc.start("xdg-mime", query);
+        proc.waitForFinished();
+        QString output = proc.readLine().simplified();
+        if (output.contains(dolphinReg)) {
+            proc.startDetached("dolphin", QStringList() << "--select" << info.canonicalFilePath());
+        } else if (output.contains(nautilusReg)) {
+            proc.startDetached("nautilus", QStringList() << "--no-desktop" << info.canonicalFilePath());
+        } else if (output.contains(cajaReg)) {
+            proc.startDetached("caja", QStringList() << "--no-desktop" << info.canonicalFilePath());
+        } else if (output.contains(nemoReg)) {
+            proc.startDetached("nemo", QStringList() << "--no-desktop" << info.canonicalFilePath());
+        } else if (output.contains(konquerorReg)) {
+            proc.startDetached("konqueror", QStringList() << "--select" << info.canonicalFilePath());
+        } else {
+            QString folder;
+            if (info.isDir()) {
+                folder = info.canonicalFilePath();
+            } else {
+                folder = info.canonicalPath();
+            }
+            QDesktopServices::openUrl(QUrl::fromLocalFile(folder));
+        }
+    }
+}
diff --git a/shared/utils.h b/shared/utils.h
index e9e3d29..6bbe978 100644
--- a/shared/utils.h
+++ b/shared/utils.h
@@ -20,8 +20,13 @@
 #define SHARED_UTILS_H
 
 #include <QString>
+#include <QStringList>
 #include <QColor>
 #include <QRegularExpression>
+#include <QFileInfo>
+#include <QProcess>
+#include <QDesktopServices>
+#include <QUrl>
 
 #include <uuid/uuid.h>
 #include <vector>
@@ -30,6 +35,7 @@ namespace Shared {
 
 QString generateUUID();
 QString processMessageBody(const QString& msg);
+void showInDirectory(const QString& path);
 
 static const std::vector<QColor> colorPalette = {
     QColor(244, 27, 63),
diff --git a/ui/models/messagefeed.cpp b/ui/models/messagefeed.cpp
index 09b11cd..743e64a 100644
--- a/ui/models/messagefeed.cpp
+++ b/ui/models/messagefeed.cpp
@@ -379,6 +379,22 @@ void Models::MessageFeed::responseArchive(const std::list<Shared::Message> list,
     }
 }
 
+QModelIndex Models::MessageFeed::index(int row, int column, const QModelIndex& parent) const
+{
+    if (!hasIndex(row, column, parent)) {
+        return QModelIndex();
+    }
+    
+    StorageByTime::iterator itr = indexByTime.nth(row);
+    if (itr != indexByTime.end()) {
+        Shared::Message* msg = *itr;
+        
+        return createIndex(row, column, msg);
+    } else {
+        return QModelIndex();
+    }
+}
+
 QHash<int, QByteArray> Models::MessageFeed::roleNames() const
 {
     return roles;
diff --git a/ui/models/messagefeed.h b/ui/models/messagefeed.h
index cc833fa..abf67ee 100644
--- a/ui/models/messagefeed.h
+++ b/ui/models/messagefeed.h
@@ -61,6 +61,7 @@ public:
     bool canFetchMore(const QModelIndex & parent) const override;
     void fetchMore(const QModelIndex & parent) override;
     QHash<int, QByteArray> roleNames() const override;
+    QModelIndex index(int row, int column, const QModelIndex & parent) const override;
     
     void responseArchive(const std::list<Shared::Message> list, bool last);
     void downloadAttachment(const QString& messageId);
diff --git a/ui/utils/feedview.cpp b/ui/utils/feedview.cpp
index 226b9a7..fd9669e 100644
--- a/ui/utils/feedview.cpp
+++ b/ui/utils/feedview.cpp
@@ -68,8 +68,13 @@ QModelIndex FeedView::indexAt(const QPoint& point) const
     uint32_t y = vh - point.y() + vo;
     
     for (std::deque<Hint>::size_type i = 0; i < hints.size(); ++i) {
-        if (hints[i].offset + hints[i].height >= y) {
-            return model()->index(i, 0, rootIndex());
+        const Hint& hint = hints[i];
+        if (y <= hint.offset + hint.height) {
+            if (y > hint.offset) {
+                return model()->index(i, 0, rootIndex());
+            } else {
+                break;
+            }
         }
     }
     
@@ -279,7 +284,8 @@ void FeedView::paintEvent(QPaintEvent* event)
     
     for (const QModelIndex& index : toRener) {
         option.rect = visualRect(index);
-        option.state.setFlag(QStyle::State_MouseOver, option.rect.contains(cursor));
+        bool mouseOver = option.rect.contains(cursor) && vp->rect().contains(cursor);
+        option.state.setFlag(QStyle::State_MouseOver, mouseOver);
         itemDelegate(index)->paint(&painter, option, index);
     }
     
diff --git a/ui/widgets/conversation.cpp b/ui/widgets/conversation.cpp
index e678caf..db6a92e 100644
--- a/ui/widgets/conversation.cpp
+++ b/ui/widgets/conversation.cpp
@@ -47,7 +47,8 @@ Conversation::Conversation(bool muc, Models::Account* acc, Models::Element* el,
     delegate(new MessageDelegate(this)),
     manualSliderChange(false),
     tsb(QApplication::style()->styleHint(QStyle::SH_ScrollBar_Transient) == 1),
-    shadow(10, 1, Qt::black, this)
+    shadow(10, 1, Qt::black, this),
+    contextMenu(new QMenu())
 {
     m_ui->setupUi(this);
     
@@ -55,6 +56,7 @@ Conversation::Conversation(bool muc, Models::Account* acc, Models::Element* el,
     
     feed->setItemDelegate(delegate);
     feed->setFrameShape(QFrame::NoFrame);
+    feed->setContextMenuPolicy(Qt::CustomContextMenu);
     delegate->initializeFonts(feed->getFont());
     feed->setModel(el->feed);
     el->feed->incrementObservers();
@@ -62,6 +64,7 @@ Conversation::Conversation(bool muc, Models::Account* acc, Models::Element* el,
     
     connect(el->feed, &Models::MessageFeed::newMessage, this, &Conversation::onFeedMessage);
     connect(feed, &FeedView::resized, this, &Conversation::positionShadow);
+    connect(feed, &FeedView::customContextMenuRequested, this, &Conversation::onFeedContext);
     
     connect(acc, &Models::Account::childChanged, this, &Conversation::onAccountChanged);
     
@@ -88,8 +91,6 @@ Conversation::Conversation(bool muc, Models::Account* acc, Models::Element* el,
         //m_ui->scrollArea->setBackgroundRole(QPalette::Base);
     //}
     
-    //m_ui->scrollArea->installEventFilter(&scrollResizeCatcher);
-    
     //line->setMyAvatarPath(acc->getAvatarPath());
     //line->setMyName(acc->getName());
     
@@ -98,6 +99,8 @@ Conversation::Conversation(bool muc, Models::Account* acc, Models::Element* el,
 
 Conversation::~Conversation()
 {
+    delete contextMenu;
+    
     element->feed->decrementObservers();
 }
 
@@ -402,3 +405,31 @@ void Conversation::positionShadow()
     shadow.move(feed->pos());
     shadow.raise();
 }
+
+void Conversation::onFeedContext(const QPoint& pos)
+{
+    QModelIndex index = feed->indexAt(pos);
+    if (index.isValid()) {
+        Shared::Message* item = static_cast<Shared::Message*>(index.internalPointer());
+        
+        contextMenu->clear();
+        bool showMenu = false;
+        QString path = item->getAttachPath();
+        if (path.size() > 0) {
+            showMenu = true;
+            QAction* open = contextMenu->addAction(Shared::icon("document-new-from-template"), tr("Open")); 
+            connect(open, &QAction::triggered, [path]() {
+                QDesktopServices::openUrl(QUrl::fromLocalFile(path));
+            });
+            
+            QAction* show = contextMenu->addAction(Shared::icon("document-new-from-template"), tr("Show in folder")); 
+            connect(show, &QAction::triggered, [path]() {
+                Shared::showInDirectory(path);
+            });
+        }
+        
+        if (showMenu) {
+            contextMenu->popup(feed->viewport()->mapToGlobal(pos));
+        }
+    }
+}
diff --git a/ui/widgets/conversation.h b/ui/widgets/conversation.h
index eaec954..690a51c 100644
--- a/ui/widgets/conversation.h
+++ b/ui/widgets/conversation.h
@@ -25,6 +25,9 @@
 #include <QMimeData>
 #include <QFileInfo>
 #include <QGraphicsOpacityEffect>
+#include <QMenu>
+#include <QAction>
+#include <QDesktopServices>
 
 #include "shared/message.h"
 #include "order.h"
@@ -103,6 +106,7 @@ protected slots:
     void onAccountChanged(Models::Item* item, int row, int col);
     void onFeedMessage(const Shared::Message& msg);
     void positionShadow();
+    void onFeedContext(const QPoint &pos);
     
 public:
     const bool isMuc;
@@ -126,6 +130,7 @@ protected:
     bool tsb;           //transient scroll bars
     
     ShadowOverlay shadow;
+    QMenu* contextMenu;
 };
 
 #endif // CONVERSATION_H

From ebf0c64ffb0f349723e1f85032a5cac870d13f1f Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Thu, 6 May 2021 17:44:43 +0300
Subject: [PATCH 110/281] highlight in directory now is optional runtime plugin
 to KIO, several more file managers to fallback, refactor, 2 new icons

---
 CHANGELOG.md                                  |  6 +-
 CMakeLists.txt                                | 16 ++++
 README.md                                     |  1 +
 core/passwordStorageEngines/CMakeLists.txt    |  2 +-
 plugins/CMakeLists.txt                        | 26 ++++++
 plugins/openfilemanagerwindowjob.cpp          |  8 ++
 .../fallback/dark/big/document-preview.svg    | 11 +++
 resources/images/fallback/dark/big/folder.svg | 21 +++++
 .../fallback/dark/small/document-preview.svg  | 12 +++
 .../images/fallback/dark/small/folder.svg     | 13 +++
 .../fallback/light/big/document-preview.svg   | 11 +++
 .../images/fallback/light/big/folder.svg      | 21 +++++
 .../fallback/light/small/document-preview.svg | 12 +++
 .../images/fallback/light/small/folder.svg    | 13 +++
 resources/resources.qrc                       |  8 ++
 shared/global.cpp                             | 86 ++++++++++++++++++-
 shared/global.h                               | 15 ++++
 shared/icons.h                                |  2 +
 shared/utils.cpp                              | 37 --------
 shared/utils.h                                |  9 +-
 ui/utils/CMakeLists.txt                       |  2 +-
 ui/widgets/CMakeLists.txt                     |  2 +-
 ui/widgets/conversation.cpp                   |  6 +-
 23 files changed, 289 insertions(+), 51 deletions(-)
 create mode 100644 plugins/CMakeLists.txt
 create mode 100644 plugins/openfilemanagerwindowjob.cpp
 create mode 100644 resources/images/fallback/dark/big/document-preview.svg
 create mode 100644 resources/images/fallback/dark/big/folder.svg
 create mode 100644 resources/images/fallback/dark/small/document-preview.svg
 create mode 100644 resources/images/fallback/dark/small/folder.svg
 create mode 100644 resources/images/fallback/light/big/document-preview.svg
 create mode 100644 resources/images/fallback/light/big/folder.svg
 create mode 100644 resources/images/fallback/light/small/document-preview.svg
 create mode 100644 resources/images/fallback/light/small/folder.svg

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 06c4ce1..bf90231 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,10 +6,14 @@
 - requesting the history of the current chat after reconnection
 - global availability (in drop down list) gets restored after reconnection
 - status icon in active chat changes when presence of the pen pal changes
+- infinite progress when open the dialogue with something that has no history to show
 
 ### Improvements
 - slightly reduced the traffic on the startup by not requesting history of all MUCs
-
+- completely rewritten message feed, now it works way faster
+- OPTIONAL RUNTIME dependency: "KIO Widgets" that is supposed to allow you to open a file in your default file manager
+- show in folder now is supposed to try it's best to show file in folder, even you don't have KIO installed
+- once uploaded local files don't get second time uploaded - the remote URL is reused
 
 ## Squawk 0.1.5 (Jul 29, 2020)
 ### Bug fixes
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 0db5693..d4caf91 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -67,6 +67,7 @@ qt5_add_resources(RCC resources/resources.qrc)
 
 option(SYSTEM_QXMPP "Use system qxmpp lib" ON) 
 option(WITH_KWALLET "Build KWallet support module" ON) 
+option(WITH_KIO "Build KIO support module" ON) 
 
 if (SYSTEM_QXMPP) 
     find_package(QXmpp CONFIG)
@@ -98,8 +99,21 @@ endif()
 add_executable(squawk ${squawk_SRC} ${squawk_HEAD} ${RCC})
 target_link_libraries(squawk Qt5::Widgets)
 
+if (WITH_KIO)
+    find_package(KF5KIO CONFIG)
+    
+    if (NOT KF5KIO_FOUND)
+        set(WITH_KIO OFF)
+        message("KIO package wasn't found, KIO support modules wouldn't be built")
+    else()
+        add_definitions(-DWITH_KIO)
+        message("Building with support of KIO")
+    endif()
+endif()
+
 add_subdirectory(ui)
 add_subdirectory(core)
+add_subdirectory(plugins)
 
 add_subdirectory(external/simpleCrypt)
 
@@ -107,6 +121,8 @@ target_link_libraries(squawk squawkUI)
 target_link_libraries(squawk squawkCORE)
 target_link_libraries(squawk uuid)
 
+
+
 add_dependencies(${CMAKE_PROJECT_NAME} translations)
 
 # Install the executable
diff --git a/README.md b/README.md
index 30c6473..84c114a 100644
--- a/README.md
+++ b/README.md
@@ -67,6 +67,7 @@ Here is the list of keys you can pass to configuration phase of `cmake ..`.
 - `CMAKE_BUILD_TYPE` - `Debug` just builds showing all warnings, `Release` builds with no warnings and applies optimizations (default is `Debug`)
 - `SYSTEM_QXMPP` - `True` tries to link against `qxmpp` installed in the system, `False` builds bundled `qxmpp` library (default is `True`)
 - `WITH_KWALLET` - `True` builds the `KWallet` capability module if `KWallet` is installed and if not goes to `False`. `False` disables `KWallet` support (default is `True`)
+- `WITH_KIO` - `True` builds the `KIO` capability module if `KIO` is installed and if not goes to `False`. `False` disables `KIO` support (default is `True`)
 
 ## License
 
diff --git a/core/passwordStorageEngines/CMakeLists.txt b/core/passwordStorageEngines/CMakeLists.txt
index e824f77..ec750d9 100644
--- a/core/passwordStorageEngines/CMakeLists.txt
+++ b/core/passwordStorageEngines/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.0)
+cmake_minimum_required(VERSION 3.3)
 project(pse)
 
 if (WITH_KWALLET) 
diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt
new file mode 100644
index 0000000..69a5e94
--- /dev/null
+++ b/plugins/CMakeLists.txt
@@ -0,0 +1,26 @@
+cmake_minimum_required(VERSION 3.3)
+project(plugins)
+
+if (WITH_KIO) 
+    set(CMAKE_AUTOMOC ON)
+    
+    find_package(Qt5Core CONFIG REQUIRED)
+
+    set(openFileManagerWindowJob_SRC
+        openfilemanagerwindowjob.cpp
+    )
+
+    add_library(openFileManagerWindowJob SHARED ${openFileManagerWindowJob_SRC})
+    
+    get_target_property(Qt5CORE_INTERFACE_INCLUDE_DIRECTORIES Qt5::Core INTERFACE_INCLUDE_DIRECTORIES)
+    get_target_property(KIO_WIDGETS_INTERFACE_INCLUDE_DIRECTORIES KF5::KIOWidgets INTERFACE_INCLUDE_DIRECTORIES)
+    get_target_property(CORE_ADDONS_INTERFACE_INCLUDE_DIRECTORIES KF5::CoreAddons INTERFACE_INCLUDE_DIRECTORIES)
+    target_include_directories(openFileManagerWindowJob PUBLIC ${KIO_WIDGETS_INTERFACE_INCLUDE_DIRECTORIES})
+    target_include_directories(openFileManagerWindowJob PUBLIC ${CORE_ADDONS_INTERFACE_INCLUDE_DIRECTORIES})
+    target_include_directories(openFileManagerWindowJob PUBLIC ${Qt5CORE_INTERFACE_INCLUDE_DIRECTORIES})
+    
+    target_link_libraries(openFileManagerWindowJob KF5::KIOWidgets)
+    target_link_libraries(openFileManagerWindowJob Qt5::Core)
+
+    install(TARGETS openFileManagerWindowJob DESTINATION ${CMAKE_INSTALL_LIBDIR})
+endif()
diff --git a/plugins/openfilemanagerwindowjob.cpp b/plugins/openfilemanagerwindowjob.cpp
new file mode 100644
index 0000000..904fbcf
--- /dev/null
+++ b/plugins/openfilemanagerwindowjob.cpp
@@ -0,0 +1,8 @@
+#include <QUrl>
+#include <QObject>
+#include <KIO/OpenFileManagerWindowJob>
+
+extern "C" void highlightInFileManager(const QUrl& url) {
+    KIO::OpenFileManagerWindowJob* job = KIO::highlightInFileManager({url});
+    QObject::connect(job, &KIO::OpenFileManagerWindowJob::result, job, &KIO::OpenFileManagerWindowJob::deleteLater);
+}
diff --git a/resources/images/fallback/dark/big/document-preview.svg b/resources/images/fallback/dark/big/document-preview.svg
new file mode 100644
index 0000000..49a3feb
--- /dev/null
+++ b/resources/images/fallback/dark/big/document-preview.svg
@@ -0,0 +1,11 @@
+<!DOCTYPE svg>
+<svg viewBox="0 0 22 22" version="1.1" xmlns="http://www.w3.org/2000/svg">
+    <defs>
+        <style type="text/css" id="current-color-scheme">
+            .ColorScheme-Text {
+                color:#232629;
+            }
+        </style>
+    </defs>
+    <path class="ColorScheme-Text" style="fill:currentColor; fill-opacity:1; stroke:none" d="M 4 3 L 4 19 L 15 19 L 15 18 L 5 18 L 5 4 L 13 4 L 13 8 L 17 8 L 17 16 L 18 16 L 18 7 L 14 3 L 4 3 Z M 13 11 C 11.338 11 10 12.338 10 14 C 10 15.662 11.338 17 13 17 C 13.6494 17 14.2464 16.7914 14.7363 16.4434 L 17.293 19 L 18 18.293 L 15.4434 15.7363 C 15.7914 15.2464 16 14.6494 16 14 C 16 12.338 14.662 11 13 11 Z M 13 12 C 14.108 12 15 12.892 15 14 C 15 15.108 14.108 16 13 16 C 11.892 16 11 15.108 11 14 C 11 12.892 11.892 12 13 12 Z"/>
+</svg>
diff --git a/resources/images/fallback/dark/big/folder.svg b/resources/images/fallback/dark/big/folder.svg
new file mode 100644
index 0000000..2acb4ab
--- /dev/null
+++ b/resources/images/fallback/dark/big/folder.svg
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg width="32" version="1.1" xmlns="http://www.w3.org/2000/svg" height="32" viewBox="0 0 32 32" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape">
+ <defs id="defs5455">
+  <linearGradient inkscape:collect="always" id="linearGradient4172-5">
+   <stop style="stop-color:#3daee9" id="stop4174-6"/>
+   <stop offset="1" style="stop-color:#6cc1ef" id="stop4176-6"/>
+  </linearGradient>
+  <linearGradient inkscape:collect="always" xlink:href="#linearGradient4172-5" id="linearGradient4342" y1="29" y2="8" x2="0" gradientUnits="userSpaceOnUse"/>
+ </defs>
+ <metadata id="metadata5458"/>
+ <g inkscape:label="Capa 1" inkscape:groupmode="layer" id="layer1" transform="matrix(1 0 0 1 -384.57143 -515.798)">
+  <path inkscape:connector-curvature="0" style="fill:#147eb8;fill-rule:evenodd" id="path4308" d="m 386.57144,518.798 0,7 0,1 28,0 0,-6 -14.00001,0 -2,-2 z"/>
+  <path inkscape:connector-curvature="0" style="fill-opacity:0.235294;fill-rule:evenodd" id="path4306" d="m 397.57143,523.798 -1.99999,1 -9,0 0,1 6.99999,0 3,0 z"/>
+  <path style="fill:url(#linearGradient4342)" id="rect4294" d="M 13 8 L 11 10 L 2 10 L 1 10 L 1 29 L 12 29 L 13 29 L 31 29 L 31 8 L 13 8 z " transform="matrix(1 0 0 1 384.57143 515.798)"/>
+  <path inkscape:connector-curvature="0" style="fill:#ffffff;fill-opacity:0.235294;fill-rule:evenodd" id="path4304" d="m 397.57143,523.798 -2,2 -10,0 0,1 11,0 z"/>
+  <path inkscape:connector-curvature="0" style="fill:#ffffff;fill-opacity:0.235294;fill-rule:evenodd" id="path4310" d="m 398.57143,518.798 1,3 15.00001,0 0,-1 -14.00001,0 z"/>
+  <rect width="30" x="385.57144" y="543.79797" height="1" style="fill-opacity:0.235294" id="rect4292"/>
+ </g>
+</svg>
diff --git a/resources/images/fallback/dark/small/document-preview.svg b/resources/images/fallback/dark/small/document-preview.svg
new file mode 100644
index 0000000..43d19bf
--- /dev/null
+++ b/resources/images/fallback/dark/small/document-preview.svg
@@ -0,0 +1,12 @@
+<!DOCTYPE svg>
+<svg viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg">
+    <defs>
+        <style type="text/css" id="current-color-scheme">
+            .ColorScheme-Text {
+                color:#232629;
+            }
+        </style>
+    </defs>
+    <path class="ColorScheme-Text" style="fill:currentColor; fill-opacity:1; stroke:none" d="M 3 2 L 3 14 L 10 14 L 10 13 L 4 13 L 4 3 L 9 3 L 9 6 L 12 6 L 12 11 L 13 11 L 13 5 L 10 2 L 3 2 Z"/>
+    <path class="ColorScheme-Text" style="fill:currentColor; fill-opacity:1; stroke:none" d="M 8.48828 7 C 7.10757 7 5.98828 8.11929 5.98828 9.5 C 5.98828 10.8807 7.10757 12 8.48828 12 C 8.97811 11.9992 9.45691 11.8546 9.86523 11.584 L 12.2813 14 L 12.9883 13.293 L 10.5723 10.877 C 10.8428 10.4686 10.9875 9.98983 10.9883 9.5 C 10.9883 8.11929 9.86899 7 8.48828 7 Z M 8.48828 8 C 9.31671 8 9.98828 8.67157 9.98828 9.5 C 9.98828 10.3284 9.31671 11 8.48828 11 C 7.65985 11 6.98828 10.3284 6.98828 9.5 C 6.98828 8.67157 7.65985 8 8.48828 8 Z"/>
+</svg>
diff --git a/resources/images/fallback/dark/small/folder.svg b/resources/images/fallback/dark/small/folder.svg
new file mode 100644
index 0000000..1061f4d
--- /dev/null
+++ b/resources/images/fallback/dark/small/folder.svg
@@ -0,0 +1,13 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
+  <defs id="defs3051">
+    <style type="text/css" id="current-color-scheme">
+      .ColorScheme-Text {
+        color:#232629;
+      }
+      </style>
+  </defs>
+ <path style="fill:currentColor;fill-opacity:1;stroke:none" 
+       d="M 2 2 L 2 3 L 2 6 L 2 7 L 2 13 L 2 14 L 14 14 L 14 13 L 14 6 L 14 5 L 14 4 L 9.0078125 4 L 7.0078125 2 L 7 2.0078125 L 7 2 L 3 2 L 2 2 z M 3 3 L 6.5917969 3 L 7.59375 4 L 7 4 L 7 4.0078125 L 6.9921875 4 L 4.9921875 6 L 3 6 L 3 3 z M 3 7 L 13 7 L 13 13 L 3 13 L 3 7 z "
+       class="ColorScheme-Text"
+       />
+</svg>
diff --git a/resources/images/fallback/light/big/document-preview.svg b/resources/images/fallback/light/big/document-preview.svg
new file mode 100644
index 0000000..6f6e346
--- /dev/null
+++ b/resources/images/fallback/light/big/document-preview.svg
@@ -0,0 +1,11 @@
+<!DOCTYPE svg>
+<svg viewBox="0 0 22 22" version="1.1" xmlns="http://www.w3.org/2000/svg">
+    <defs>
+        <style type="text/css" id="current-color-scheme">
+            .ColorScheme-Text {
+                color:#eff0f1;
+            }
+        </style>
+    </defs>
+    <path class="ColorScheme-Text" style="fill:currentColor; fill-opacity:1; stroke:none" d="M 4 3 L 4 19 L 15 19 L 15 18 L 5 18 L 5 4 L 13 4 L 13 8 L 17 8 L 17 16 L 18 16 L 18 7 L 14 3 L 4 3 Z M 13 11 C 11.338 11 10 12.338 10 14 C 10 15.662 11.338 17 13 17 C 13.6494 17 14.2464 16.7914 14.7363 16.4434 L 17.293 19 L 18 18.293 L 15.4434 15.7363 C 15.7914 15.2464 16 14.6494 16 14 C 16 12.338 14.662 11 13 11 Z M 13 12 C 14.108 12 15 12.892 15 14 C 15 15.108 14.108 16 13 16 C 11.892 16 11 15.108 11 14 C 11 12.892 11.892 12 13 12 Z"/>
+</svg>
diff --git a/resources/images/fallback/light/big/folder.svg b/resources/images/fallback/light/big/folder.svg
new file mode 100644
index 0000000..2acb4ab
--- /dev/null
+++ b/resources/images/fallback/light/big/folder.svg
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg width="32" version="1.1" xmlns="http://www.w3.org/2000/svg" height="32" viewBox="0 0 32 32" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape">
+ <defs id="defs5455">
+  <linearGradient inkscape:collect="always" id="linearGradient4172-5">
+   <stop style="stop-color:#3daee9" id="stop4174-6"/>
+   <stop offset="1" style="stop-color:#6cc1ef" id="stop4176-6"/>
+  </linearGradient>
+  <linearGradient inkscape:collect="always" xlink:href="#linearGradient4172-5" id="linearGradient4342" y1="29" y2="8" x2="0" gradientUnits="userSpaceOnUse"/>
+ </defs>
+ <metadata id="metadata5458"/>
+ <g inkscape:label="Capa 1" inkscape:groupmode="layer" id="layer1" transform="matrix(1 0 0 1 -384.57143 -515.798)">
+  <path inkscape:connector-curvature="0" style="fill:#147eb8;fill-rule:evenodd" id="path4308" d="m 386.57144,518.798 0,7 0,1 28,0 0,-6 -14.00001,0 -2,-2 z"/>
+  <path inkscape:connector-curvature="0" style="fill-opacity:0.235294;fill-rule:evenodd" id="path4306" d="m 397.57143,523.798 -1.99999,1 -9,0 0,1 6.99999,0 3,0 z"/>
+  <path style="fill:url(#linearGradient4342)" id="rect4294" d="M 13 8 L 11 10 L 2 10 L 1 10 L 1 29 L 12 29 L 13 29 L 31 29 L 31 8 L 13 8 z " transform="matrix(1 0 0 1 384.57143 515.798)"/>
+  <path inkscape:connector-curvature="0" style="fill:#ffffff;fill-opacity:0.235294;fill-rule:evenodd" id="path4304" d="m 397.57143,523.798 -2,2 -10,0 0,1 11,0 z"/>
+  <path inkscape:connector-curvature="0" style="fill:#ffffff;fill-opacity:0.235294;fill-rule:evenodd" id="path4310" d="m 398.57143,518.798 1,3 15.00001,0 0,-1 -14.00001,0 z"/>
+  <rect width="30" x="385.57144" y="543.79797" height="1" style="fill-opacity:0.235294" id="rect4292"/>
+ </g>
+</svg>
diff --git a/resources/images/fallback/light/small/document-preview.svg b/resources/images/fallback/light/small/document-preview.svg
new file mode 100644
index 0000000..f40fcdf
--- /dev/null
+++ b/resources/images/fallback/light/small/document-preview.svg
@@ -0,0 +1,12 @@
+<!DOCTYPE svg>
+<svg viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg">
+    <defs>
+        <style type="text/css" id="current-color-scheme">
+            .ColorScheme-Text {
+                color:#eff0f1;
+            }
+        </style>
+    </defs>
+    <path class="ColorScheme-Text" style="fill:currentColor; fill-opacity:1; stroke:none" d="M 3 2 L 3 14 L 10 14 L 10 13 L 4 13 L 4 3 L 9 3 L 9 6 L 12 6 L 12 11 L 13 11 L 13 5 L 10 2 L 3 2 Z"/>
+    <path class="ColorScheme-Text" style="fill:currentColor; fill-opacity:1; stroke:none" d="M 8.48828 7 C 7.10757 7 5.98828 8.11929 5.98828 9.5 C 5.98828 10.8807 7.10757 12 8.48828 12 C 8.97811 11.9992 9.45691 11.8546 9.86523 11.584 L 12.2813 14 L 12.9883 13.293 L 10.5723 10.877 C 10.8428 10.4686 10.9875 9.98983 10.9883 9.5 C 10.9883 8.11929 9.86899 7 8.48828 7 Z M 8.48828 8 C 9.31671 8 9.98828 8.67157 9.98828 9.5 C 9.98828 10.3284 9.31671 11 8.48828 11 C 7.65985 11 6.98828 10.3284 6.98828 9.5 C 6.98828 8.67157 7.65985 8 8.48828 8 Z"/>
+</svg>
diff --git a/resources/images/fallback/light/small/folder.svg b/resources/images/fallback/light/small/folder.svg
new file mode 100644
index 0000000..a5f66cd
--- /dev/null
+++ b/resources/images/fallback/light/small/folder.svg
@@ -0,0 +1,13 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
+  <defs id="defs3051">
+    <style type="text/css" id="current-color-scheme">
+      .ColorScheme-Text {
+        color:#eff0f1;
+      }
+      </style>
+  </defs>
+ <path style="fill:currentColor;fill-opacity:1;stroke:none" 
+       d="M 2 2 L 2 3 L 2 6 L 2 7 L 2 13 L 2 14 L 14 14 L 14 13 L 14 6 L 14 5 L 14 4 L 9.0078125 4 L 7.0078125 2 L 7 2.0078125 L 7 2 L 3 2 L 2 2 z M 3 3 L 6.5917969 3 L 7.59375 4 L 7 4 L 7 4.0078125 L 6.9921875 4 L 4.9921875 6 L 3 6 L 3 3 z M 3 7 L 13 7 L 13 13 L 3 13 L 3 7 z "
+       class="ColorScheme-Text"
+       />
+</svg>
diff --git a/resources/resources.qrc b/resources/resources.qrc
index 4fb3e5b..58565fc 100644
--- a/resources/resources.qrc
+++ b/resources/resources.qrc
@@ -40,6 +40,8 @@
     <file>images/fallback/dark/big/favorite.svg</file>
     <file>images/fallback/dark/big/unfavorite.svg</file>
     <file>images/fallback/dark/big/add.svg</file>
+    <file>images/fallback/dark/big/folder.svg</file>
+    <file>images/fallback/dark/big/document-preview.svg</file>
     
     
     <file>images/fallback/dark/small/absent.svg</file>
@@ -80,6 +82,8 @@
     <file>images/fallback/dark/small/favorite.svg</file>
     <file>images/fallback/dark/small/unfavorite.svg</file>
     <file>images/fallback/dark/small/add.svg</file>
+    <file>images/fallback/dark/small/folder.svg</file>
+    <file>images/fallback/dark/small/document-preview.svg</file>
     
     
     <file>images/fallback/light/big/absent.svg</file>
@@ -120,6 +124,8 @@
     <file>images/fallback/light/big/favorite.svg</file>
     <file>images/fallback/light/big/unfavorite.svg</file>
     <file>images/fallback/light/big/add.svg</file>
+    <file>images/fallback/light/big/folder.svg</file>
+    <file>images/fallback/light/big/document-preview.svg</file>
     
     
     <file>images/fallback/light/small/absent.svg</file>
@@ -160,5 +166,7 @@
     <file>images/fallback/light/small/favorite.svg</file>
     <file>images/fallback/light/small/unfavorite.svg</file>
     <file>images/fallback/light/small/add.svg</file>
+    <file>images/fallback/light/small/folder.svg</file>
+    <file>images/fallback/light/small/document-preview.svg</file>
 </qresource>
 </RCC>
diff --git a/shared/global.cpp b/shared/global.cpp
index 981dd60..62843ed 100644
--- a/shared/global.cpp
+++ b/shared/global.cpp
@@ -23,6 +23,11 @@
 Shared::Global* Shared::Global::instance = 0;
 const std::set<QString> Shared::Global::supportedImagesExts = {"png", "jpg", "webp", "jpeg", "gif", "svg"};
 
+#ifdef WITH_KIO
+QLibrary Shared::Global::openFileManagerWindowJob("openFileManagerWindowJob");
+Shared::Global::HighlightInFileManager Shared::Global::hfm = 0;
+#endif
+
 Shared::Global::Global():
     availability({
         tr("Online", "Availability"), 
@@ -80,7 +85,8 @@ Shared::Global::Global():
         tr("Your password is going to be stored in KDE wallet storage (KWallet). You're going to be queried for permissions", "AccountPasswordDescription")
     }),
     pluginSupport({
-        {"KWallet", false}
+        {"KWallet", false},
+        {"openFileManagerWindowJob", false}
     }),
     fileCache()
 {
@@ -89,6 +95,21 @@ Shared::Global::Global():
     }
     
     instance = this;
+    
+#ifdef WITH_KIO
+    openFileManagerWindowJob.load();
+    if (openFileManagerWindowJob.isLoaded()) {
+        hfm = (HighlightInFileManager) openFileManagerWindowJob.resolve("highlightInFileManager");
+        if (hfm) {
+            setSupported("openFileManagerWindowJob", true);
+            qDebug() << "KIO::OpenFileManagerWindow support enabled";
+        } else {
+            qDebug() << "KIO::OpenFileManagerWindow support disabled: couldn't resolve required methods in the library";
+        }
+    } else {
+        qDebug() << "KIO::OpenFileManagerWindow support disabled: couldn't load the library" << openFileManagerWindowJob.errorString();
+    }
+#endif
 }
 
 Shared::Global::FileInfo Shared::Global::getFileInfo(const QString& path)
@@ -181,6 +202,69 @@ QString Shared::Global::getDescription(Shared::AccountPassword ap)
     return instance->accountPasswordDescription[static_cast<int>(ap)];
 }
 
+
+static const QStringList query = {"query", "default", "inode/directory"};
+static const QRegularExpression dolphinReg("[Dd]olphin");
+static const QRegularExpression nautilusReg("[Nn]autilus");
+static const QRegularExpression cajaReg("[Cc]aja");
+static const QRegularExpression nemoReg("[Nn]emo");
+static const QRegularExpression konquerorReg("kfmclient");
+static const QRegularExpression pcmanfmQtReg("pcmanfm-qt");
+static const QRegularExpression pcmanfmReg("pcmanfm");
+static const QRegularExpression thunarReg("thunar");
+
+void Shared::Global::highlightInFileManager(const QString& path)
+{
+#ifdef WITH_KIO
+    if (supported("openFileManagerWindowJob")) {
+        hfm(path);
+        return;
+    } else {
+        qDebug() << "requested to highlight in file manager url" << path << "but it's not supported: KIO plugin isn't loaded, trying fallback";
+    }
+#else
+    qDebug() << "requested to highlight in file manager url" << path << "but it's not supported: squawk wasn't compiled to support it, trying fallback";
+#endif
+    
+    QFileInfo info = path;
+    if (info.exists()) {
+        QProcess proc;
+        proc.start("xdg-mime", query);
+        proc.waitForFinished();
+        QString output = proc.readLine().simplified();
+        
+        QString folder;
+        if (info.isDir()) {
+            folder = info.canonicalFilePath();
+        } else {
+            folder = info.canonicalPath();
+        }
+        
+        if (output.contains(dolphinReg)) {
+            //there is a bug on current (21.04.0) dolphin, it works correct only if you already have dolphin launched
+            proc.startDetached("dolphin", QStringList() << "--select" << info.canonicalFilePath());
+            //KIO::highlightInFileManager({QUrl(info.canonicalFilePath())});
+        } else if (output.contains(nautilusReg)) {
+            proc.startDetached("nautilus", QStringList() << "--select" << info.canonicalFilePath());    //this worked on nautilus
+        } else if (output.contains(cajaReg)) {
+            proc.startDetached("caja", QStringList() << folder);  //caja doesn't seem to support file selection command line, gonna just open directory
+        } else if (output.contains(nemoReg)) {
+            proc.startDetached("nemo", QStringList() << info.canonicalFilePath());      //nemo supports selecting files without keys
+        } else if (output.contains(konquerorReg)) {
+            proc.startDetached("konqueror", QStringList() << "--select" << info.canonicalFilePath());   //this worked on konqueror
+        } else if (output.contains(pcmanfmQtReg)) {
+            proc.startDetached("pcmanfm-qt", QStringList() << folder);   //pcmanfm-qt doesn't seem to support open with selection, gonna just open directory
+        } else if (output.contains(pcmanfmReg)) {
+            proc.startDetached("pcmanfm", QStringList() << folder);   //pcmanfm also doesn't seem to support open with selection, gonna just open directory
+        } else if (output.contains(thunarReg)) {
+            proc.startDetached("thunar", QStringList() << folder);   //thunar doesn't seem to support open with selection, gonna just open directory
+        } else {
+            QDesktopServices::openUrl(QUrl::fromLocalFile(folder));
+        }
+    }
+}
+
+
 #define FROM_INT_INPL(Enum)                                                                 \
 template<>                                                                                  \
 Enum Shared::Global::fromInt(int src)                                                       \
diff --git a/shared/global.h b/shared/global.h
index bb9e5b7..b6bbe37 100644
--- a/shared/global.h
+++ b/shared/global.h
@@ -34,6 +34,12 @@
 #include <QFileInfo>
 #include <QImage>
 #include <QSize>
+#include <QUrl>
+#include <QLibrary>
+#include <QFileInfo>
+#include <QProcess>
+#include <QDesktopServices>
+#include <QRegularExpression>
 
 namespace Shared {
     
@@ -83,6 +89,7 @@ namespace Shared {
         static const std::set<QString> supportedImagesExts;
         
         static FileInfo getFileInfo(const QString& path);
+        static void highlightInFileManager(const QString& path);
         
         template<typename T>
         static T fromInt(int src);
@@ -108,6 +115,14 @@ namespace Shared {
         
         std::map<QString, bool> pluginSupport;
         std::map<QString, FileInfo> fileCache;
+        
+#ifdef WITH_KIO
+        static QLibrary openFileManagerWindowJob;
+        
+        typedef void (*HighlightInFileManager)(const QUrl &);
+        
+        static HighlightInFileManager hfm;
+#endif
     };
 }
 
diff --git a/shared/icons.h b/shared/icons.h
index 48ecc37..540d3e9 100644
--- a/shared/icons.h
+++ b/shared/icons.h
@@ -170,6 +170,8 @@ static const std::map<QString, std::pair<QString, QString>> icons = {
     {"favorite", {"favorite", "favorite"}},
     {"unfavorite", {"draw-star", "unfavorite"}},
     {"list-add", {"list-add", "add"}},
+    {"folder", {"folder", "folder"}},
+    {"document-preview", {"document-preview", "document-preview"}}
 };
 }
 
diff --git a/shared/utils.cpp b/shared/utils.cpp
index f5f7fa5..924be85 100644
--- a/shared/utils.cpp
+++ b/shared/utils.cpp
@@ -46,40 +46,3 @@ QString Shared::processMessageBody(const QString& msg)
     processed.replace(urlReg, "<a href=\"\\1\">\\1</a>");
     return "<p style=\"white-space: pre-wrap;\">" + processed + "</p>";
 }
-
-static const QStringList query = {"query", "default", "inode/directory"};
-static const QRegularExpression dolphinReg("[Dd]olphin");
-static const QRegularExpression nautilusReg("[Nn]autilus");
-static const QRegularExpression cajaReg("[Cc]aja");
-static const QRegularExpression nemoReg("[Nn]emo");
-static const QRegularExpression konquerorReg("kfmclient");
-
-void Shared::showInDirectory(const QString& path)
-{
-    QFileInfo info = path;
-    if (info.exists()) {
-        QProcess proc;
-        proc.start("xdg-mime", query);
-        proc.waitForFinished();
-        QString output = proc.readLine().simplified();
-        if (output.contains(dolphinReg)) {
-            proc.startDetached("dolphin", QStringList() << "--select" << info.canonicalFilePath());
-        } else if (output.contains(nautilusReg)) {
-            proc.startDetached("nautilus", QStringList() << "--no-desktop" << info.canonicalFilePath());
-        } else if (output.contains(cajaReg)) {
-            proc.startDetached("caja", QStringList() << "--no-desktop" << info.canonicalFilePath());
-        } else if (output.contains(nemoReg)) {
-            proc.startDetached("nemo", QStringList() << "--no-desktop" << info.canonicalFilePath());
-        } else if (output.contains(konquerorReg)) {
-            proc.startDetached("konqueror", QStringList() << "--select" << info.canonicalFilePath());
-        } else {
-            QString folder;
-            if (info.isDir()) {
-                folder = info.canonicalFilePath();
-            } else {
-                folder = info.canonicalPath();
-            }
-            QDesktopServices::openUrl(QUrl::fromLocalFile(folder));
-        }
-    }
-}
diff --git a/shared/utils.h b/shared/utils.h
index 6bbe978..a8a17d5 100644
--- a/shared/utils.h
+++ b/shared/utils.h
@@ -23,19 +23,16 @@
 #include <QStringList>
 #include <QColor>
 #include <QRegularExpression>
-#include <QFileInfo>
-#include <QProcess>
-#include <QDesktopServices>
-#include <QUrl>
+
+//#include "KIO/OpenFileManagerWindowJob"
 
 #include <uuid/uuid.h>
-#include <vector>
+#include <vector> 
 
 namespace Shared {
 
 QString generateUUID();
 QString processMessageBody(const QString& msg);
-void showInDirectory(const QString& path);
 
 static const std::vector<QColor> colorPalette = {
     QColor(244, 27, 63),
diff --git a/ui/utils/CMakeLists.txt b/ui/utils/CMakeLists.txt
index e656bde..0c33521 100644
--- a/ui/utils/CMakeLists.txt
+++ b/ui/utils/CMakeLists.txt
@@ -25,7 +25,7 @@ set(squawkUIUtils_SRC
 )
 
 # Tell CMake to create the helloworld executable
-add_library(squawkUIUtils ${squawkUIUtils_SRC})
+add_library(squawkUIUtils STATIC ${squawkUIUtils_SRC})
 
 # Use the Widgets module from Qt 5.
 target_link_libraries(squawkUIUtils squawkWidgets)
diff --git a/ui/widgets/CMakeLists.txt b/ui/widgets/CMakeLists.txt
index abf238c..78b4f1a 100644
--- a/ui/widgets/CMakeLists.txt
+++ b/ui/widgets/CMakeLists.txt
@@ -21,7 +21,7 @@ set(squawkWidgets_SRC
   joinconference.cpp
 )
 
-add_library(squawkWidgets ${squawkWidgets_SRC})
+add_library(squawkWidgets STATIC ${squawkWidgets_SRC})
 
 # Use the Widgets module from Qt 5.
 target_link_libraries(squawkWidgets vCardUI)
diff --git a/ui/widgets/conversation.cpp b/ui/widgets/conversation.cpp
index db6a92e..45ce2c5 100644
--- a/ui/widgets/conversation.cpp
+++ b/ui/widgets/conversation.cpp
@@ -417,14 +417,14 @@ void Conversation::onFeedContext(const QPoint& pos)
         QString path = item->getAttachPath();
         if (path.size() > 0) {
             showMenu = true;
-            QAction* open = contextMenu->addAction(Shared::icon("document-new-from-template"), tr("Open")); 
+            QAction* open = contextMenu->addAction(Shared::icon("document-preview"), tr("Open")); 
             connect(open, &QAction::triggered, [path]() {
                 QDesktopServices::openUrl(QUrl::fromLocalFile(path));
             });
             
-            QAction* show = contextMenu->addAction(Shared::icon("document-new-from-template"), tr("Show in folder")); 
+            QAction* show = contextMenu->addAction(Shared::icon("folder"), tr("Show in folder")); 
             connect(show, &QAction::triggered, [path]() {
-                Shared::showInDirectory(path);
+                Shared::Global::highlightInFileManager(path);
             });
         }
         

From f45319de2524036d6fe375160e1c7bd204be956b Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Fri, 7 May 2021 21:26:02 +0300
Subject: [PATCH 111/281] now instead of storing uploading message in ram I
 store them in database to be able to recover unsent ones on the next statrt.
 Found and fixed bug with spam repaints in feedview because of icons

---
 core/archive.cpp                 |  16 +++--
 core/handlers/messagehandler.cpp | 115 ++++++++++++++++++++-----------
 core/handlers/messagehandler.h   |   9 ++-
 core/rosteritem.cpp              |  17 +++++
 core/rosteritem.h                |   2 +
 shared/message.cpp               |  10 ++-
 ui/models/messagefeed.cpp        |   8 +++
 ui/utils/feedview.cpp            |   4 +-
 ui/utils/messagedelegate.cpp     |  24 ++++---
 9 files changed, 146 insertions(+), 59 deletions(-)

diff --git a/core/archive.cpp b/core/archive.cpp
index f18201b..96a8c0d 100644
--- a/core/archive.cpp
+++ b/core/archive.cpp
@@ -271,6 +271,8 @@ void Core::Archive::changeMessage(const QString& id, const QMap<QString, QVarian
         bool hadStanzaId = msg.getStanzaId().size() > 0;
         QDateTime oTime = msg.getTime();
         bool idChange = msg.change(data);
+        QDateTime nTime = msg.getTime();
+        bool orderChange = oTime != nTime;
         
         MDB_val lmdbKey, lmdbData;
         QByteArray ba;
@@ -280,15 +282,21 @@ void Core::Archive::changeMessage(const QString& id, const QMap<QString, QVarian
         lmdbKey.mv_size = strId.size();
         lmdbKey.mv_data = (char*)strId.c_str();
         int rc;
-        if (idChange) {
-            rc = mdb_del(txn, main, &lmdbKey, &lmdbData);
+        if (idChange || orderChange) {
+            if (idChange) {
+                rc = mdb_del(txn, main, &lmdbKey, &lmdbData);
+            } else {
+                quint64 ostamp = oTime.toMSecsSinceEpoch();
+                lmdbData.mv_data = (quint8*)&ostamp;
+                lmdbData.mv_size = 8;
+                rc = mdb_del(txn, order, &lmdbData, &lmdbKey);
+            }
             if (rc == 0) {
                 strId = msg.getId().toStdString();
                 lmdbKey.mv_size = strId.size();
                 lmdbKey.mv_data = (char*)strId.c_str();
                 
-                
-                quint64 stamp = oTime.toMSecsSinceEpoch();
+                quint64 stamp = nTime.toMSecsSinceEpoch();
                 lmdbData.mv_data = (quint8*)&stamp;
                 lmdbData.mv_size = 8;
                 rc = mdb_put(txn, order, &lmdbData, &lmdbKey, 0);
diff --git a/core/handlers/messagehandler.cpp b/core/handlers/messagehandler.cpp
index 0bb84be..54aff53 100644
--- a/core/handlers/messagehandler.cpp
+++ b/core/handlers/messagehandler.cpp
@@ -23,7 +23,6 @@ Core::MessageHandler::MessageHandler(Core::Account* account):
     QObject(),
     acc(account),
     pendingStateMessages(),
-    pendingMessages(),
     uploadingSlotsQueue()
 {
 }
@@ -249,12 +248,13 @@ void Core::MessageHandler::sendMessage(const Shared::Message& data)
     }
 }
 
-void Core::MessageHandler::performSending(Shared::Message data)
+void Core::MessageHandler::performSending(Shared::Message data, bool newMessage)
 {
     QString jid = data.getPenPalJid();
     QString id = data.getId();
     QString oob = data.getOutOfBandUrl();
     RosterItem* ri = acc->rh->getRosterItem(jid);
+    bool sent = false;
     QMap<QString, QVariant> changes;
     if (acc->state == Shared::ConnectionState::connected) {
         QXmppMessage msg(acc->getFullJid(), data.getTo(), data.getBody(), data.getThread());
@@ -267,20 +267,13 @@ void Core::MessageHandler::performSending(Shared::Message data)
         msg.setOutOfBandUrl(oob);
         msg.setReceiptRequested(true);
         
-        bool sent = acc->client.sendPacket(msg);
+        sent = acc->client.sendPacket(msg);
         
         if (sent) {
             data.setState(Shared::Message::State::sent);
         } else {
             data.setState(Shared::Message::State::error);
-            data.setErrorText("Couldn't send message via QXMPP library check out logs");
-        }
-        
-        if (ri != 0) {
-            ri->appendMessageToArchive(data);
-            if (sent) {
-                pendingStateMessages.insert(std::make_pair(id, jid));
-            }
+            data.setErrorText("Couldn't send message: internal QXMPP library error, probably need to check out the logs");
         }
         
     } else {
@@ -296,6 +289,22 @@ void Core::MessageHandler::performSending(Shared::Message data)
     if (oob.size() > 0) {
         changes.insert("outOfBandUrl", oob);
     }
+    if (!newMessage) {
+        changes.insert("stamp", data.getTime());
+    }
+    
+    if (ri != 0) {
+        if (newMessage) {
+            ri->appendMessageToArchive(data);
+        } else {
+            ri->changeMessage(id, changes);
+        }
+        if (sent) {
+            pendingStateMessages.insert(std::make_pair(id, jid));
+        } else {
+            pendingStateMessages.erase(id);
+        }
+    }
     
     emit acc->changeMessage(jid, id, changes);
 }
@@ -303,27 +312,37 @@ void Core::MessageHandler::performSending(Shared::Message data)
 void Core::MessageHandler::prepareUpload(const Shared::Message& data)
 {
     if (acc->state == Shared::ConnectionState::connected) {
+        QString jid = data.getPenPalJid();
+        QString id = data.getId();
+        RosterItem* ri = acc->rh->getRosterItem(jid);
+        if (!ri) {
+            qDebug() << "An attempt to initialize upload in" << acc->name << "for pal" << jid << "but the object for this pal wasn't found, something went terrebly wrong, skipping send";
+            return;
+        }
         QString path = data.getAttachPath();
         QString url = acc->network->getFileRemoteUrl(path);
         if (url.size() != 0) {
             sendMessageWithLocalUploadedFile(data, url);
         } else {
-            if (acc->network->checkAndAddToUploading(acc->getName(), data.getPenPalJid(), data.getId(), path)) {
-                pendingMessages.emplace(data.getId(), data);
+            if (acc->network->checkAndAddToUploading(acc->getName(), jid, id, path)) {
+                ri->appendMessageToArchive(data);
+                pendingStateMessages.insert(std::make_pair(id, jid));
             } else {
                 if (acc->um->serviceFound()) {
                     QFileInfo file(path);
                     if (file.exists() && file.isReadable()) {
-                        uploadingSlotsQueue.emplace_back(path, data);
+                        ri->appendMessageToArchive(data);
+                        pendingStateMessages.insert(std::make_pair(id, jid));
+                        uploadingSlotsQueue.emplace_back(path, id);
                         if (uploadingSlotsQueue.size() == 1) {
                             acc->um->requestUploadSlot(file);
                         }
                     } else {
-                        handleUploadError(data.getPenPalJid(), data.getId(), "Uploading file no longer exists or your system user has no permission to read it");
+                        handleUploadError(jid, id, "Uploading file no longer exists or your system user has no permission to read it");
                         qDebug() << "Requested upload slot in account" << acc->name << "for file" << path << "but the file doesn't exist or is not readable";
                     }
                 } else {
-                    handleUploadError(data.getPenPalJid(), data.getId(), "Your server doesn't support file upload service, or it's prohibited for your account");
+                    handleUploadError(jid, id, "Your server doesn't support file upload service, or it's prohibited for your account");
                     qDebug() << "Requested upload slot in account" << acc->name << "for file" << path << "but upload manager didn't discover any upload services";
                 }
             }
@@ -340,10 +359,10 @@ void Core::MessageHandler::onUploadSlotReceived(const QXmppHttpUploadSlotIq& slo
     if (uploadingSlotsQueue.size() == 0) {
         qDebug() << "HTTP Upload manager of account" << acc->name << "reports about success requesting upload slot, but none was requested";
     } else {
-        const std::pair<QString, Shared::Message>& pair = uploadingSlotsQueue.front();
-        const QString& mId = pair.second.getId();
-        acc->network->uploadFile({acc->name, pair.second.getPenPalJid(), mId}, pair.first, slot.putUrl(), slot.getUrl(), slot.putHeaders());
-        pendingMessages.emplace(mId, pair.second);
+        const std::pair<QString, QString>& pair = uploadingSlotsQueue.front();
+        const QString& mId = pair.second;
+        QString palJid = pendingStateMessages.at(mId);
+        acc->network->uploadFile({acc->name, palJid, mId}, pair.first, slot.putUrl(), slot.getUrl(), slot.putHeaders());
         
         uploadingSlotsQueue.pop_front();
         if (uploadingSlotsQueue.size() > 0) {
@@ -359,9 +378,9 @@ void Core::MessageHandler::onUploadSlotRequestFailed(const QXmppHttpUploadReques
         qDebug() << "HTTP Upload manager of account" << acc->name << "reports about an error requesting upload slot, but none was requested";
         qDebug() << err;
     } else {
-        const std::pair<QString, Shared::Message>& pair = uploadingSlotsQueue.front();
+        const std::pair<QString, QString>& pair = uploadingSlotsQueue.front();
         qDebug() << "Error requesting upload slot for file" << pair.first << "in account" << acc->name << ":" << err;
-        emit acc->uploadFileError(pair.second.getPenPalJid(), pair.second.getId(), "Error requesting slot to upload file: " + err);
+        handleUploadError(pendingStateMessages.at(pair.second), pair.second, err);
         
         uploadingSlotsQueue.pop_front();
         if (uploadingSlotsQueue.size() > 0) {
@@ -400,47 +419,65 @@ void Core::MessageHandler::onLoadFileError(const std::list<Shared::MessageInfo>&
 
 void Core::MessageHandler::handleUploadError(const QString& jid, const QString& messageId, const QString& errorText)
 {
-    std::map<QString, Shared::Message>::const_iterator itr = pendingMessages.find(messageId);
-    if (itr != pendingMessages.end()) {
-        pendingMessages.erase(itr);
-        //TODO move the storage of pending messages to the database and change them there
-    }
+    emit acc->uploadFileError(jid, messageId, "Error requesting slot to upload file: " + errorText);
+    pendingStateMessages.erase(jid);
+    requestChangeMessage(jid, messageId, {
+        {"state", static_cast<uint>(Shared::Message::State::error)},
+        {"errorText", errorText}
+    });
 }
 
 void Core::MessageHandler::onUploadFileComplete(const std::list<Shared::MessageInfo>& msgs, const QString& path)
 {
     for (const Shared::MessageInfo& info : msgs) {
         if (info.account == acc->getName()) {
-            std::map<QString, Shared::Message>::const_iterator itr = pendingMessages.find(info.messageId);
-            if (itr != pendingMessages.end()) {
-                sendMessageWithLocalUploadedFile(itr->second, path);
-                pendingMessages.erase(itr);
+            RosterItem* ri = acc->rh->getRosterItem(info.jid);
+            if (ri != 0) {
+                Shared::Message msg = ri->getMessage(info.messageId);
+                sendMessageWithLocalUploadedFile(msg, path, false);
+            } else {
+                qDebug() << "A signal received about complete upload to" << acc->name << "for pal" << info.jid << "but the object for this pal wasn't found, something went terrebly wrong, skipping send";
             }
         }
     }
 }
 
-void Core::MessageHandler::sendMessageWithLocalUploadedFile(Shared::Message msg, const QString& url)
+void Core::MessageHandler::sendMessageWithLocalUploadedFile(Shared::Message msg, const QString& url, bool newMessage)
 {
     msg.setOutOfBandUrl(url);
-    if (msg.getBody().size() == 0) {
-        msg.setBody(url);
-    }
-    performSending(msg);
+    if (msg.getBody().size() == 0) {    //not sure why, but most messages do that
+        msg.setBody(url);               //they duplicate oob in body, some of them wouldn't even show an attachment if you don't do that
+    }                                   
+    performSending(msg, newMessage);
     //TODO removal/progress update
 }
 
+static const std::set<QString> allowerToChangeKeys({
+    "attachPath",
+    "outOfBandUrl",
+    "state",
+    "errorText"
+});
+
 void Core::MessageHandler::requestChangeMessage(const QString& jid, const QString& messageId, const QMap<QString, QVariant>& data)
 {
     RosterItem* cnt = acc->rh->getRosterItem(jid);
     if (cnt != 0) {
-        QMap<QString, QVariant>::const_iterator itr = data.find("attachPath");
-        if (data.size() == 1 && itr != data.end()) {
+        bool allSupported = true;
+        QString unsupportedString;
+        for (QMap<QString, QVariant>::const_iterator itr = data.begin(); itr != data.end(); ++itr) {        //I need all this madness 
+            if (allowerToChangeKeys.count(itr.key()) != 1) {                                                //to not allow this method
+                allSupported = false;                                                                       //to make a message to look like if it was edited
+                unsupportedString = itr.key();                                                              //basically I needed to control who exaclty calls this method
+                break;                                                                                      //because the underlying tech assumes that the change is initiated by user
+            }                                                                                               //not by system
+        }
+        if (allSupported) {
             cnt->changeMessage(messageId, data);
             emit acc->changeMessage(jid, messageId, data);
         } else {
             qDebug() << "A request to change message" << messageId << "of conversation" << jid << "with following data" << data;
-            qDebug() << "nothing but the changing of the local path is supported yet in this method, skipping";
+            qDebug() << "only limited set of dataFields are supported yet here, and" << unsupportedString << "isn't one of them, skipping";
         }
     }
 }
diff --git a/core/handlers/messagehandler.h b/core/handlers/messagehandler.h
index 28fc783..9138245 100644
--- a/core/handlers/messagehandler.h
+++ b/core/handlers/messagehandler.h
@@ -64,16 +64,15 @@ private:
     bool handleChatMessage(const QXmppMessage& msg, bool outgoing = false, bool forwarded = false, bool guessing = false);
     bool handleGroupMessage(const QXmppMessage& msg, bool outgoing = false, bool forwarded = false, bool guessing = false);
     void logMessage(const QXmppMessage& msg, const QString& reason = "Message wasn't handled: ");
-    void sendMessageWithLocalUploadedFile(Shared::Message msg, const QString& url);
-    void performSending(Shared::Message data);
+    void sendMessageWithLocalUploadedFile(Shared::Message msg, const QString& url, bool newMessage = true);
+    void performSending(Shared::Message data, bool newMessage = true);
     void prepareUpload(const Shared::Message& data);
     void handleUploadError(const QString& jid, const QString& messageId, const QString& errorText);
     
 private:
     Account* acc;
-    std::map<QString, QString> pendingStateMessages;
-    std::map<QString, Shared::Message> pendingMessages;
-    std::deque<std::pair<QString, Shared::Message>> uploadingSlotsQueue;
+    std::map<QString, QString> pendingStateMessages;        //key is message id, value is JID
+    std::deque<std::pair<QString, QString>> uploadingSlotsQueue;
 };
 
 }
diff --git a/core/rosteritem.cpp b/core/rosteritem.cpp
index 9b121fb..b1951d6 100644
--- a/core/rosteritem.cpp
+++ b/core/rosteritem.cpp
@@ -569,3 +569,20 @@ void Core::RosterItem::downgradeDatabaseState()
         archiveState = ArchiveState::chunk;
     }
 }
+
+Shared::Message Core::RosterItem::getMessage(const QString& id)
+{
+    for (const Shared::Message& msg : appendCache) {
+        if (msg.getId() == id) {
+            return msg;
+        }
+    }
+    
+    for (Shared::Message& msg : hisoryCache) {
+        if (msg.getId() == id) {
+            return msg;
+        }
+    }
+    
+    return archive->getElement(id);
+}
diff --git a/core/rosteritem.h b/core/rosteritem.h
index e744cac..237a46a 100644
--- a/core/rosteritem.h
+++ b/core/rosteritem.h
@@ -78,6 +78,8 @@ public:
     void clearArchiveRequests();
     void downgradeDatabaseState();
     
+    Shared::Message getMessage(const QString& id);
+    
 signals:
     void nameChanged(const QString& name);
     void subscriptionStateChanged(Shared::SubscriptionState state);
diff --git a/shared/message.cpp b/shared/message.cpp
index e63b7d2..e6b47b2 100644
--- a/shared/message.cpp
+++ b/shared/message.cpp
@@ -410,6 +410,14 @@ bool Shared::Message::change(const QMap<QString, QVariant>& data)
                 setEdited(true);
             }
         }
+    } else {
+        QMap<QString, QVariant>::const_iterator dItr = data.find("stamp");
+        if (dItr != data.end()) {
+            QDateTime ntime = dItr.value().toDateTime();
+            if (time != ntime) {
+                setTime(ntime);
+            }
+        }
     }
     
     return idChanged;
@@ -437,7 +445,7 @@ void Shared::Message::setOutOfBandUrl(const QString& url)
 
 bool Shared::Message::storable() const
 {
-    return id.size() > 0 && (body.size() > 0 || oob.size()) > 0;
+    return id.size() > 0 && (body.size() > 0 || oob.size() > 0 || attachPath.size() > 0);
 }
 
 void Shared::Message::setStanzaId(const QString& sid)
diff --git a/ui/models/messagefeed.cpp b/ui/models/messagefeed.cpp
index 743e64a..4187af8 100644
--- a/ui/models/messagefeed.cpp
+++ b/ui/models/messagefeed.cpp
@@ -194,6 +194,14 @@ std::set<Models::MessageFeed::MessageRoles> Models::MessageFeed::detectChanges(c
             roles.insert(MessageRoles::Text);
             roles.insert(MessageRoles::Correction);
         }
+    } else {
+        QMap<QString, QVariant>::const_iterator dItr = data.find("stamp");
+        if (dItr != data.end()) {
+            QDateTime ntime = dItr.value().toDateTime();
+            if (msg.getTime() != ntime) {
+                roles.insert(MessageRoles::Date);
+            }
+        }
     }
     
     return roles;
diff --git a/ui/utils/feedview.cpp b/ui/utils/feedview.cpp
index fd9669e..22ef4c4 100644
--- a/ui/utils/feedview.cpp
+++ b/ui/utils/feedview.cpp
@@ -34,8 +34,8 @@ const std::set<int> FeedView::geometryChangingRoles = {
     Models::MessageFeed::Attach,
     Models::MessageFeed::Text,
     Models::MessageFeed::Id,
-    Models::MessageFeed::Error
-    
+    Models::MessageFeed::Error,
+    Models::MessageFeed::Date
 };
 
 FeedView::FeedView(QWidget* parent):
diff --git a/ui/utils/messagedelegate.cpp b/ui/utils/messagedelegate.cpp
index 0ea64d8..8db024d 100644
--- a/ui/utils/messagedelegate.cpp
+++ b/ui/utils/messagedelegate.cpp
@@ -397,13 +397,6 @@ QLabel * MessageDelegate::getStatusIcon(const Models::FeedItem& data) const
     std::map<QString, QLabel*>::const_iterator itr = statusIcons->find(data.id);
     QLabel* result = 0;
     
-    if (itr != statusIcons->end()) {
-        result = itr->second;
-    } else {
-        result = new QLabel();
-        statusIcons->insert(std::make_pair(data.id, result));
-    }
-    
     QIcon q(Shared::icon(Shared::messageStateThemeIcons[static_cast<uint8_t>(data.state)]));
     QString tt = Shared::Global::getName(data.state);
     if (data.state == Shared::Message::State::error) {
@@ -412,8 +405,23 @@ QLabel * MessageDelegate::getStatusIcon(const Models::FeedItem& data) const
         }
     }
     
+    if (itr != statusIcons->end()) {
+        result = itr->second;
+        if (result->toolTip() != tt) {                      //If i just assign pixmap every time unconditionally
+            result->setPixmap(q.pixmap(statusIconSize));    //it involves into an infinite cycle of repaint
+            result->setToolTip(tt);                         //may be it's better to subclass and store last condition in int?
+        }
+    } else {
+        result = new QLabel();
+        statusIcons->insert(std::make_pair(data.id, result));
+        result->setPixmap(q.pixmap(statusIconSize));
+        result->setToolTip(tt);
+    }
+    
+    
+    
     result->setToolTip(tt);
-    result->setPixmap(q.pixmap(statusIconSize));
+    //result->setText(std::to_string((int)data.state).c_str());
     
     return result;
 }

From ce047db78774778cc83b7c593eb9e0c4bf2848c5 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Sun, 9 May 2021 02:12:17 +0300
Subject: [PATCH 112/281] patches from Vae about making libraries static, and
 about boost, findLMDB CMake script, drop dependency for qtquickcontrols

---
 CMakeLists.txt                             |  1 -
 README.md                                  |  3 +-
 cmake/FindLMDB.cmake                       | 47 ++++++++++++++++++++++
 core/CMakeLists.txt                        |  7 +++-
 core/passwordStorageEngines/CMakeLists.txt |  2 +-
 external/simpleCrypt/CMakeLists.txt        |  2 +-
 ui/CMakeLists.txt                          |  5 +--
 ui/widgets/vcard/CMakeLists.txt            |  2 +-
 8 files changed, 59 insertions(+), 10 deletions(-)
 create mode 100644 cmake/FindLMDB.cmake

diff --git a/CMakeLists.txt b/CMakeLists.txt
index d4caf91..e88fdc8 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -12,7 +12,6 @@ include(GNUInstallDirs)
 include_directories(.)
 
 find_package(Qt5Widgets CONFIG REQUIRED)
-find_package(Qt5QuickCompiler CONFIG REQUIRED)
 find_package(Qt5LinguistTools)
 
 if(NOT CMAKE_BUILD_TYPE)
diff --git a/README.md b/README.md
index 84c114a..f2101d6 100644
--- a/README.md
+++ b/README.md
@@ -13,7 +13,8 @@
 - lmdb
 - CMake 3.0 or higher
 - qxmpp 1.1.0 or higher
-- kwallet (optional)
+- KDE Frameworks: kwallet (optional)
+- KDE Frameworks: KIO (optional)
 
 ### Getting
 
diff --git a/cmake/FindLMDB.cmake b/cmake/FindLMDB.cmake
new file mode 100644
index 0000000..8bf48b4
--- /dev/null
+++ b/cmake/FindLMDB.cmake
@@ -0,0 +1,47 @@
+#This file is taken from here https://gitlab.ralph.or.at/causal-rt/causal-cpp/, it was GPLv3 license
+#Thank you so much, mr. Ralph Alexander Bariz, I hope you don't mind me using your code
+
+# Try to find LMDB headers and library.
+#
+# Usage of this module as follows:
+#
+#     find_package(LMDB)
+#
+# Variables used by this module, they can change the default behaviour and need
+# to be set before calling find_package:
+#
+#  LMDB_ROOT_DIR          Set this variable to the root installation of
+#                            LMDB if the module has problems finding the
+#                            proper installation path.
+#
+# Variables defined by this module:
+#
+#  LMDB_FOUND               System has LMDB library/headers.
+#  LMDB_LIBRARIES           The LMDB library.
+#  LMDB_INCLUDE_DIRS        The location of LMDB headers.
+
+find_path(LMDB_ROOT_DIR
+    NAMES include/lmdb.h
+)
+
+find_library(LMDB_LIBRARIES
+    NAMES lmdb
+    HINTS ${LMDB_ROOT_DIR}/lib
+)
+
+find_path(LMDB_INCLUDE_DIRS
+    NAMES lmdb.h
+    HINTS ${LMDB_ROOT_DIR}/include
+)
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(LMDB DEFAULT_MSG
+    LMDB_LIBRARIES
+    LMDB_INCLUDE_DIRS
+)
+
+mark_as_advanced(
+    LMDB_ROOT_DIR
+    LMDB_LIBRARIES
+    LMDB_INCLUDE_DIRS
+)
diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt
index 2e832e2..f8aa267 100644
--- a/core/CMakeLists.txt
+++ b/core/CMakeLists.txt
@@ -1,12 +1,15 @@
-cmake_minimum_required(VERSION 3.0)
+cmake_minimum_required(VERSION 3.3)
 project(squawkCORE)
 
+set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake")
+
 set(CMAKE_AUTOMOC ON)
 
 find_package(Qt5Core CONFIG REQUIRED)
 find_package(Qt5Gui CONFIG REQUIRED)
 find_package(Qt5Network CONFIG REQUIRED)
 find_package(Qt5Xml CONFIG REQUIRED)
+find_package(LMDB REQUIRED)
 
 set(squawkCORE_SRC
     squawk.cpp
@@ -25,7 +28,7 @@ set(squawkCORE_SRC
 add_subdirectory(passwordStorageEngines)
 
 # Tell CMake to create the helloworld executable
-add_library(squawkCORE ${squawkCORE_SRC})
+add_library(squawkCORE STATIC ${squawkCORE_SRC})
 
 
 if(SYSTEM_QXMPP)
diff --git a/core/passwordStorageEngines/CMakeLists.txt b/core/passwordStorageEngines/CMakeLists.txt
index ec750d9..735c0ad 100644
--- a/core/passwordStorageEngines/CMakeLists.txt
+++ b/core/passwordStorageEngines/CMakeLists.txt
@@ -14,7 +14,7 @@ if (WITH_KWALLET)
         kwallet.cpp
     )
     
-    add_library(kwalletPSE ${kwalletPSE_SRC})
+    add_library(kwalletPSE STATIC ${kwalletPSE_SRC})
     
     target_include_directories(kwalletPSE PUBLIC ${KWALLET_INTERFACE_INCLUDE_DIRECTORIES})
     target_include_directories(kwalletPSE PUBLIC ${Qt5GUI_INTERFACE_INCLUDE_DIRECTORIES})
diff --git a/external/simpleCrypt/CMakeLists.txt b/external/simpleCrypt/CMakeLists.txt
index bdb62c6..88f5d23 100644
--- a/external/simpleCrypt/CMakeLists.txt
+++ b/external/simpleCrypt/CMakeLists.txt
@@ -10,7 +10,7 @@ set(simplecrypt_SRC
 )
 
 # Tell CMake to create the helloworld executable
-add_library(simpleCrypt ${simplecrypt_SRC})
+add_library(simpleCrypt STATIC ${simplecrypt_SRC})
 
 # Use the Widgets module from Qt 5.
 target_link_libraries(simpleCrypt Qt5::Core)
diff --git a/ui/CMakeLists.txt b/ui/CMakeLists.txt
index 00570c9..11b8f3d 100644
--- a/ui/CMakeLists.txt
+++ b/ui/CMakeLists.txt
@@ -8,8 +8,7 @@ set(CMAKE_AUTOUIC ON)
 
 # Find the QtWidgets library
 find_package(Qt5 CONFIG REQUIRED COMPONENTS Widgets DBus Core)
-find_package(Boost 1.36.0 CONFIG REQUIRED COMPONENTS
-             date_time filesystem iostreams)
+find_package(Boost 1.36.0 REQUIRED)
 if(Boost_FOUND)
   include_directories(${Boost_INCLUDE_DIRS})
 endif()
@@ -35,7 +34,7 @@ set(squawkUI_SRC
 )
 
 # Tell CMake to create the helloworld executable
-add_library(squawkUI ${squawkUI_SRC})
+add_library(squawkUI STATIC ${squawkUI_SRC})
 
 # Use the Widgets module from Qt 5.
 target_link_libraries(squawkUI squawkWidgets)
diff --git a/ui/widgets/vcard/CMakeLists.txt b/ui/widgets/vcard/CMakeLists.txt
index 4d2ee15..73b157c 100644
--- a/ui/widgets/vcard/CMakeLists.txt
+++ b/ui/widgets/vcard/CMakeLists.txt
@@ -16,7 +16,7 @@ set(vCardUI_SRC
 )
 
 # Tell CMake to create the helloworld executable
-add_library(vCardUI ${vCardUI_SRC})
+add_library(vCardUI STATIC ${vCardUI_SRC})
 
 # Use the Widgets module from Qt 5.
 target_link_libraries(vCardUI Qt5::Widgets)

From b7b70bc198ce0fcfb1a26db3c60ee3f10332f581 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Tue, 11 May 2021 00:06:40 +0300
Subject: [PATCH 113/281] segfault fix when trying to send something but the
 history isn't loaded yet, icon and for attached files which are not previewed

---
 shared/global.cpp            |  4 +++
 ui/models/messagefeed.cpp    | 29 ++++++++++--------
 ui/utils/messagedelegate.cpp | 59 +++++++++++++++++++++++++-----------
 3 files changed, 61 insertions(+), 31 deletions(-)

diff --git a/shared/global.cpp b/shared/global.cpp
index 62843ed..25a1c87 100644
--- a/shared/global.cpp
+++ b/shared/global.cpp
@@ -112,6 +112,8 @@ Shared::Global::Global():
 #endif
 }
 
+
+static const QSize defaultIconFileInfoHeight(50, 50);
 Shared::Global::FileInfo Shared::Global::getFileInfo(const QString& path)
 {
     std::map<QString, FileInfo>::const_iterator itr = instance->fileCache.find(path);
@@ -131,6 +133,8 @@ Shared::Global::FileInfo Shared::Global::getFileInfo(const QString& path)
             p = FileInfo::Preview::picture;
             QImage img(path);
             size = img.size();
+        } else {
+            size = defaultIconFileInfoHeight;
         }
         
         itr = instance->fileCache.insert(std::make_pair(path, FileInfo({info.fileName(), size, type, p}))).first;
diff --git a/ui/models/messagefeed.cpp b/ui/models/messagefeed.cpp
index 4187af8..d5fb3bc 100644
--- a/ui/models/messagefeed.cpp
+++ b/ui/models/messagefeed.cpp
@@ -532,20 +532,23 @@ QModelIndex Models::MessageFeed::modelIndexById(const QString& id) const
 
 QModelIndex Models::MessageFeed::modelIndexByTime(const QString& id, const QDateTime& time) const
 {
-    StorageByTime::const_iterator tItr = indexByTime.upper_bound(time);
-    StorageByTime::const_iterator tBeg = indexByTime.begin();
-    bool found = false;
-    while (tItr != tBeg) {
-        if (id == (*tItr)->getId()) {
-            found = true;
-            break;
+    if (indexByTime.size() > 0) {
+        StorageByTime::const_iterator tItr = indexByTime.upper_bound(time);
+        StorageByTime::const_iterator tBeg = indexByTime.begin();
+        StorageByTime::const_iterator tEnd = indexByTime.end();
+        bool found = false;
+        while (tItr != tBeg) {
+            if (tItr != tEnd && id == (*tItr)->getId()) {
+                found = true;
+                break;
+            }
+            --tItr;
+        }
+        
+        if (found && tItr != tEnd && id == (*tItr)->getId()) {
+            int position = indexByTime.rank(tItr);
+            return createIndex(position, 0, *tItr);
         }
-        --tItr;
-    }
-    
-    if (found || id == (*tItr)->getId()) {
-        int position = indexByTime.rank(tItr);
-        return createIndex(position, 0, *tItr);
     }
     
     return QModelIndex();
diff --git a/ui/utils/messagedelegate.cpp b/ui/utils/messagedelegate.cpp
index 8db024d..6b459f2 100644
--- a/ui/utils/messagedelegate.cpp
+++ b/ui/utils/messagedelegate.cpp
@@ -307,25 +307,48 @@ void MessageDelegate::paintBar(QProgressBar* bar, QPainter* painter, bool sentBy
 void MessageDelegate::paintPreview(const Models::FeedItem& data, QPainter* painter, QStyleOptionViewItem& option) const
 {
     Shared::Global::FileInfo info = Shared::Global::getFileInfo(data.attach.localPath);
-    if (info.preview == Shared::Global::FileInfo::Preview::picture) {
-        QSize size = constrainAttachSize(info.size, option.rect.size());
-        
-        QPoint start;
-        if (data.sentByMe) {
-            start = {option.rect.width() - size.width(), option.rect.top()};
-            start.rx() += margin;
-        } else {
-            start = option.rect.topLeft();
-        }
-        QImage img(data.attach.localPath);
-        if (img.isNull()) {
-            emit invalidPath(data.id);
-        } else {
-            painter->drawImage(QRect(start, size), img);
-        }
-        
-        option.rect.adjust(0, size.height() + textMargin, 0, 0);
+    QSize size = constrainAttachSize(info.size, option.rect.size());
+    
+    QPoint start;
+    if (data.sentByMe) {
+        start = {option.rect.width() - size.width(), option.rect.top()};
+        start.rx() += margin;
+    } else {
+        start = option.rect.topLeft();
     }
+    QRect rect(start, size);
+    switch (info.preview) {
+        case Shared::Global::FileInfo::Preview::picture: {
+            QImage img(data.attach.localPath);
+            if (img.isNull()) {
+                emit invalidPath(data.id);
+            } else {
+                painter->drawImage(rect, img);
+            }
+        }
+        break;
+        default: {
+            QIcon icon = QIcon::fromTheme(info.mime.iconName());
+            
+            painter->save();
+            
+            painter->setFont(bodyFont);
+            int labelWidth = option.rect.width() - size.width() - margin;
+            QString elidedName = bodyMetrics.elidedText(info.name, Qt::ElideMiddle, labelWidth);
+            QSize nameSize = bodyMetrics.boundingRect(QRect(start, QSize(labelWidth, 0)), 0, elidedName).size();
+            if (data.sentByMe) {
+                start.rx() -= nameSize.width() + margin;
+            }
+            painter->drawPixmap({start, size}, icon.pixmap(info.size));
+            start.rx() += size.width() + margin;
+            start.ry() += nameSize.height() + (size.height() - nameSize.height()) / 2;
+            painter->drawText(start, elidedName);
+    
+            painter->restore();
+        }
+    }
+    
+    option.rect.adjust(0, size.height() + textMargin, 0, 0);
 }
 
 QPushButton * MessageDelegate::getButton(const Models::FeedItem& data) const

From 6e06a1d5bc33046ee5641839f1738256da8d75ec Mon Sep 17 00:00:00 2001
From: vae <vae@programming.socks.town>
Date: Tue, 11 May 2021 20:29:08 +0300
Subject: [PATCH 114/281] build: WIP CMakeLists refactoring

---
 CMakeLists.txt                              | 83 ++++++++++-----------
 core/CMakeLists.txt                         | 73 +++++++-----------
 core/account.h                              |  2 +-
 core/archive.h                              |  2 +-
 core/handlers/CMakeLists.txt                |  6 ++
 main.cpp => core/main.cpp                   | 18 ++---
 core/passwordStorageEngines/CMakeLists.txt  | 48 ++++--------
 signalcatcher.cpp => core/signalcatcher.cpp |  0
 signalcatcher.h => core/signalcatcher.h     |  0
 shared/CMakeLists.txt                       | 19 +++++
 exception.cpp => shared/exception.cpp       |  0
 exception.h => shared/exception.h           |  0
 order.h => shared/order.h                   |  0
 shared.h => shared/shared.h                 | 14 ++--
 ui/CMakeLists.txt                           | 42 +----------
 ui/models/CMakeLists.txt                    | 28 +++++++
 ui/squawk.h                                 |  2 +-
 ui/utils/CMakeLists.txt                     | 58 +++++++-------
 ui/widgets/CMakeLists.txt                   | 50 ++++++-------
 ui/widgets/conversation.h                   |  2 +-
 ui/widgets/vcard/CMakeLists.txt             | 31 +++-----
 21 files changed, 214 insertions(+), 264 deletions(-)
 create mode 100644 core/handlers/CMakeLists.txt
 rename main.cpp => core/main.cpp (98%)
 rename signalcatcher.cpp => core/signalcatcher.cpp (100%)
 rename signalcatcher.h => core/signalcatcher.h (100%)
 create mode 100644 shared/CMakeLists.txt
 rename exception.cpp => shared/exception.cpp (100%)
 rename exception.h => shared/exception.h (100%)
 rename order.h => shared/order.h (100%)
 rename shared.h => shared/shared.h (80%)
 create mode 100644 ui/models/CMakeLists.txt

diff --git a/CMakeLists.txt b/CMakeLists.txt
index e88fdc8..e1b7f0c 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,52 +1,41 @@
 cmake_minimum_required(VERSION 3.4)
-project(squawk)
+project(squawk VERSION 0.1.6 LANGUAGES CXX)
 
-set(CMAKE_INCLUDE_CURRENT_DIR ON)
+cmake_policy(SET CMP0076 NEW)
+cmake_policy(SET CMP0079 NEW)
 set(CMAKE_CXX_STANDARD 17)
 
 set(CMAKE_AUTOMOC ON)
 set(CMAKE_AUTOUIC ON)
 set(CMAKE_AUTORCC ON)
 
-include(GNUInstallDirs)
-include_directories(.)
+add_executable(squawk)
+target_include_directories(squawk PRIVATE ${CMAKE_SOURCE_DIR})
+set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake")
 
-find_package(Qt5Widgets CONFIG REQUIRED)
+include(GNUInstallDirs)
+
+find_package(Qt5Widgets CONFIG REQUIRED COMPONENTS Widgets DBus Core)
 find_package(Qt5LinguistTools)
+find_package(Qt5Core CONFIG REQUIRED)
+find_package(Qt5Gui CONFIG REQUIRED)
+find_package(Qt5Network CONFIG REQUIRED)
+find_package(Qt5Xml CONFIG REQUIRED)
+find_package(LMDB REQUIRED)
 
 if(NOT CMAKE_BUILD_TYPE)
   set(CMAKE_BUILD_TYPE Debug)
 endif()
 
-set(CMAKE_CXX_FLAGS_DEBUG "-g -Wall -Wextra")
-set(CMAKE_CXX_FLAGS_RELEASE "-O3")
 message("Build type: ${CMAKE_BUILD_TYPE}")
 
-
-set(squawk_SRC
-  main.cpp
-  exception.cpp
-  signalcatcher.cpp
-  shared/global.cpp
-  shared/utils.cpp
-  shared/message.cpp
-  shared/vcard.cpp
-  shared/icons.cpp
-  shared/messageinfo.cpp
+target_compile_options(squawk PRIVATE
+  "-Wall;-Wextra"
+  "$<$<CONFIG:DEBUG>:-g>"
+  "$<$<CONFIG:RELEASE>:-O3>"
 )
 
-set(squawk_HEAD
-  exception.h
-  signalcatcher.h
-  shared.h
-  shared/enums.h
-  shared/message.h
-  shared/global.h
-  shared/utils.h
-  shared/vcard.h
-  shared/icons.h
-  shared/messageinfo.h
-)
+add_subdirectory(shared)
 
 configure_file(resources/images/logo.svg squawk.svg COPYONLY)
 execute_process(COMMAND convert -background none -size 48x48 squawk.svg squawk48.png WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
@@ -56,21 +45,22 @@ execute_process(COMMAND convert -background none -size 256x256 squawk.svg squawk
 
 configure_file(packaging/squawk.desktop squawk.desktop COPYONLY)
 
-set(TS_FILES 
+set(TS_FILES
     translations/squawk.ru.ts
 )
 qt5_add_translation(QM_FILES ${TS_FILES})
 add_custom_target(translations ALL DEPENDS ${QM_FILES})
 
+qt5_use_modules(squawk LINK_PUBLIC Core Widgets)
 qt5_add_resources(RCC resources/resources.qrc)
 
-option(SYSTEM_QXMPP "Use system qxmpp lib" ON) 
-option(WITH_KWALLET "Build KWallet support module" ON) 
-option(WITH_KIO "Build KIO support module" ON) 
+option(SYSTEM_QXMPP "Use system qxmpp lib" ON)
+option(WITH_KWALLET "Build KWallet support module" ON)
+option(WITH_KIO "Build KIO support module" ON)
 
-if (SYSTEM_QXMPP) 
+if (SYSTEM_QXMPP)
     find_package(QXmpp CONFIG)
-    
+
     if (NOT QXmpp_FOUND)
         set(SYSTEM_QXMPP OFF)
         message("QXmpp package wasn't found, trying to build with bundled QXmpp")
@@ -85,7 +75,7 @@ endif()
 
 if (WITH_KWALLET)
     find_package(KF5Wallet CONFIG)
-    
+
     if (NOT KF5Wallet_FOUND)
         set(WITH_KWALLET OFF)
         message("KWallet package wasn't found, KWallet support module wouldn't be built")
@@ -95,12 +85,19 @@ if (WITH_KWALLET)
     endif()
 endif()
 
-add_executable(squawk ${squawk_SRC} ${squawk_HEAD} ${RCC})
-target_link_libraries(squawk Qt5::Widgets)
+target_sources(squawk PRIVATE ${RCC})
+target_link_libraries(squawk PRIVATE Qt5::Widgets)
+target_link_libraries(squawk PRIVATE Qt5::DBus)
+target_link_libraries(squawk PRIVATE Qt5::Network)
+target_link_libraries(squawk PRIVATE Qt5::Gui)
+target_link_libraries(squawk PRIVATE Qt5::Xml)
+target_link_libraries(squawk PRIVATE qxmpp)
+target_link_libraries(squawk PRIVATE lmdb)
+target_link_libraries(squawk PRIVATE simpleCrypt)
 
 if (WITH_KIO)
     find_package(KF5KIO CONFIG)
-    
+
     if (NOT KF5KIO_FOUND)
         set(WITH_KIO OFF)
         message("KIO package wasn't found, KIO support modules wouldn't be built")
@@ -116,11 +113,7 @@ add_subdirectory(plugins)
 
 add_subdirectory(external/simpleCrypt)
 
-target_link_libraries(squawk squawkUI)
-target_link_libraries(squawk squawkCORE)
-target_link_libraries(squawk uuid)
-
-
+target_link_libraries(squawk PRIVATE uuid)
 
 add_dependencies(${CMAKE_PROJECT_NAME} translations)
 
diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt
index f8aa267..3454204 100644
--- a/core/CMakeLists.txt
+++ b/core/CMakeLists.txt
@@ -1,49 +1,34 @@
-cmake_minimum_required(VERSION 3.3)
-project(squawkCORE)
-
-set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake")
-
-set(CMAKE_AUTOMOC ON)
-
-find_package(Qt5Core CONFIG REQUIRED)
-find_package(Qt5Gui CONFIG REQUIRED)
-find_package(Qt5Network CONFIG REQUIRED)
-find_package(Qt5Xml CONFIG REQUIRED)
-find_package(LMDB REQUIRED)
-
-set(squawkCORE_SRC
-    squawk.cpp
-    account.cpp
-    archive.cpp
-    rosteritem.cpp
-    contact.cpp
-    conference.cpp
-    urlstorage.cpp
-    networkaccess.cpp
-    adapterFuctions.cpp
-    handlers/messagehandler.cpp
-    handlers/rosterhandler.cpp
-)
+target_sources(squawk PRIVATE
+  account.cpp
+  account.h
+  adapterFuctions.cpp
+  archive.cpp
+  archive.h
+  conference.cpp
+  conference.h
+  contact.cpp
+  contact.h
+  main.cpp
+  networkaccess.cpp
+  networkaccess.h
+  rosteritem.cpp
+  rosteritem.h
+  signalcatcher.cpp
+  signalcatcher.h
+  squawk.cpp
+  squawk.h
+  storage.cpp
+  storage.h
+  urlstorage.cpp
+  urlstorage.h
+  )
 
+add_subdirectory(handlers)
 add_subdirectory(passwordStorageEngines)
 
-# Tell CMake to create the helloworld executable
-add_library(squawkCORE STATIC ${squawkCORE_SRC})
-
-
-if(SYSTEM_QXMPP)
-    get_target_property(QXMPP_INTERFACE_INCLUDE_DIRECTORIES QXmpp::QXmpp INTERFACE_INCLUDE_DIRECTORIES)
-    target_include_directories(squawkCORE PUBLIC ${QXMPP_INTERFACE_INCLUDE_DIRECTORIES})
-endif()
+#if(SYSTEM_QXMPP)
+#    get_target_property(QXMPP_INTERFACE_INCLUDE_DIRECTORIES QXmpp::QXmpp INTERFACE_INCLUDE_DIRECTORIES)
+#    target_include_directories(squawk PRIVATE ${QXMPP_INTERFACE_INCLUDE_DIRECTORIES})
+#endif()
 
 # Use the Widgets module from Qt 5.
-target_link_libraries(squawkCORE Qt5::Core)
-target_link_libraries(squawkCORE Qt5::Network)
-target_link_libraries(squawkCORE Qt5::Gui)
-target_link_libraries(squawkCORE Qt5::Xml)
-target_link_libraries(squawkCORE qxmpp)
-target_link_libraries(squawkCORE lmdb)
-target_link_libraries(squawkCORE simpleCrypt)
-if (WITH_KWALLET)
-    target_link_libraries(squawkCORE kwalletPSE)
-endif()
diff --git a/core/account.h b/core/account.h
index ce3b754..a0db9f9 100644
--- a/core/account.h
+++ b/core/account.h
@@ -43,7 +43,7 @@
 #include <QXmppVCardManager.h>
 #include <QXmppMessageReceiptManager.h>
 
-#include "shared.h"
+#include "shared/shared.h"
 #include "contact.h"
 #include "conference.h"
 #include "networkaccess.h"
diff --git a/core/archive.h b/core/archive.h
index dd7a167..47c62dc 100644
--- a/core/archive.h
+++ b/core/archive.h
@@ -25,7 +25,7 @@
 #include <QMimeType>
 
 #include "shared/message.h"
-#include "exception.h"
+#include "shared/exception.h"
 #include <lmdb.h>
 #include <list>
 
diff --git a/core/handlers/CMakeLists.txt b/core/handlers/CMakeLists.txt
new file mode 100644
index 0000000..ebae4b3
--- /dev/null
+++ b/core/handlers/CMakeLists.txt
@@ -0,0 +1,6 @@
+target_sources(squawk PRIVATE
+  messagehandler.cpp
+  messagehandler.h
+  rosterhandler.cpp
+  rosterhandler.h
+  )
\ No newline at end of file
diff --git a/main.cpp b/core/main.cpp
similarity index 98%
rename from main.cpp
rename to core/main.cpp
index 210dd70..0090424 100644
--- a/main.cpp
+++ b/core/main.cpp
@@ -16,18 +16,18 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#include "ui/squawk.h"
-#include "core/squawk.h"
+#include "../shared/global.h"
+#include "../shared/messageinfo.h"
+#include "../ui/squawk.h"
 #include "signalcatcher.h"
-#include "shared/global.h"
-#include "shared/messageinfo.h"
-#include <QtWidgets/QApplication>
-#include <QtCore/QThread>
-#include <QtCore/QObject>
-#include <QSettings>
-#include <QTranslator>
+#include "squawk.h"
 #include <QLibraryInfo>
+#include <QSettings>
 #include <QStandardPaths>
+#include <QTranslator>
+#include <QtCore/QObject>
+#include <QtCore/QThread>
+#include <QtWidgets/QApplication>
 
 int main(int argc, char *argv[])
 {
diff --git a/core/passwordStorageEngines/CMakeLists.txt b/core/passwordStorageEngines/CMakeLists.txt
index 735c0ad..7275d4f 100644
--- a/core/passwordStorageEngines/CMakeLists.txt
+++ b/core/passwordStorageEngines/CMakeLists.txt
@@ -1,37 +1,21 @@
-cmake_minimum_required(VERSION 3.3)
-project(pse)
+target_sources(squawk PRIVATE
+  wrappers/kwallet.cpp
+  kwallet.cpp
+  kwallet.h
+  )
 
-if (WITH_KWALLET) 
-    set(CMAKE_AUTOMOC ON)
+if (WITH_KWALLET)
+#    get_target_property(KWALLET_INTERFACE_INCLUDE_DIRECTORIES KF5::Wallet INTERFACE_INCLUDE_DIRECTORIES)
+#    get_target_property(Qt5GUI_INTERFACE_INCLUDE_DIRECTORIES Qt5::Gui INTERFACE_INCLUDE_DIRECTORIES)
+#
+#    target_include_directories(squawk PRIVATE ${KWALLET_INTERFACE_INCLUDE_DIRECTORIES})
+#    target_include_directories(squawk PRIVATE ${Qt5GUI_INTERFACE_INCLUDE_DIRECTORIES})
 
-    find_package(Qt5Core CONFIG REQUIRED)
-    find_package(Qt5Gui CONFIG REQUIRED)
+    target_link_libraries(squawk PUBLIC Qt5::Core Qt5::Gui KF5::Wallet)
 
-    get_target_property(KWALLET_INTERFACE_INCLUDE_DIRECTORIES KF5::Wallet INTERFACE_INCLUDE_DIRECTORIES)
-    get_target_property(Qt5GUI_INTERFACE_INCLUDE_DIRECTORIES Qt5::Gui INTERFACE_INCLUDE_DIRECTORIES)
+#    target_include_directories(kwalletWrapper PUBLIC ${KWALLET_INTERFACE_INCLUDE_DIRECTORIES})
+#    target_include_directories(kwalletWrapper PUBLIC ${Qt5GUI_INTERFACE_INCLUDE_DIRECTORIES})
 
-    set(kwalletPSE_SRC
-        kwallet.cpp
-    )
-    
-    add_library(kwalletPSE STATIC ${kwalletPSE_SRC})
-    
-    target_include_directories(kwalletPSE PUBLIC ${KWALLET_INTERFACE_INCLUDE_DIRECTORIES})
-    target_include_directories(kwalletPSE PUBLIC ${Qt5GUI_INTERFACE_INCLUDE_DIRECTORIES})
-
-    target_link_libraries(kwalletPSE Qt5::Core)
-
-    set(kwalletW_SRC
-        wrappers/kwallet.cpp
-    )
-
-    add_library(kwalletWrapper SHARED ${kwalletW_SRC})
-
-    target_include_directories(kwalletWrapper PUBLIC ${KWALLET_INTERFACE_INCLUDE_DIRECTORIES})
-    target_include_directories(kwalletWrapper PUBLIC ${Qt5GUI_INTERFACE_INCLUDE_DIRECTORIES})
-
-    target_link_libraries(kwalletWrapper KF5::Wallet)
-    target_link_libraries(kwalletWrapper Qt5::Core)
-
-    install(TARGETS kwalletWrapper DESTINATION ${CMAKE_INSTALL_LIBDIR})
+#    target_link_libraries(kwalletWrapper KF5::Wallet)
+#    target_link_libraries(kwalletWrapper Qt5::Core)
 endif()
diff --git a/signalcatcher.cpp b/core/signalcatcher.cpp
similarity index 100%
rename from signalcatcher.cpp
rename to core/signalcatcher.cpp
diff --git a/signalcatcher.h b/core/signalcatcher.h
similarity index 100%
rename from signalcatcher.h
rename to core/signalcatcher.h
diff --git a/shared/CMakeLists.txt b/shared/CMakeLists.txt
new file mode 100644
index 0000000..edd769a
--- /dev/null
+++ b/shared/CMakeLists.txt
@@ -0,0 +1,19 @@
+target_sources(squawk PRIVATE
+  ${CMAKE_CURRENT_LIST_DIR}/enums.h
+  ${CMAKE_CURRENT_LIST_DIR}/global.cpp
+  ${CMAKE_CURRENT_LIST_DIR}/global.h
+  ${CMAKE_CURRENT_LIST_DIR}/exception.cpp
+  ${CMAKE_CURRENT_LIST_DIR}/exception.h
+  ${CMAKE_CURRENT_LIST_DIR}/icons.cpp
+  ${CMAKE_CURRENT_LIST_DIR}/icons.h
+  ${CMAKE_CURRENT_LIST_DIR}/message.cpp
+  ${CMAKE_CURRENT_LIST_DIR}/message.h
+  ${CMAKE_CURRENT_LIST_DIR}/messageinfo.cpp
+  ${CMAKE_CURRENT_LIST_DIR}/messageinfo.h
+  ${CMAKE_CURRENT_LIST_DIR}/order.h
+  ${CMAKE_CURRENT_LIST_DIR}/shared.h
+  ${CMAKE_CURRENT_LIST_DIR}/utils.cpp
+  ${CMAKE_CURRENT_LIST_DIR}/utils.h
+  ${CMAKE_CURRENT_LIST_DIR}/vcard.cpp
+  ${CMAKE_CURRENT_LIST_DIR}/vcard.h
+)
\ No newline at end of file
diff --git a/exception.cpp b/shared/exception.cpp
similarity index 100%
rename from exception.cpp
rename to shared/exception.cpp
diff --git a/exception.h b/shared/exception.h
similarity index 100%
rename from exception.h
rename to shared/exception.h
diff --git a/order.h b/shared/order.h
similarity index 100%
rename from order.h
rename to shared/order.h
diff --git a/shared.h b/shared/shared.h
similarity index 80%
rename from shared.h
rename to shared/shared.h
index 3925ce2..1e86c5a 100644
--- a/shared.h
+++ b/shared/shared.h
@@ -19,12 +19,12 @@
 #ifndef SHARED_H
 #define SHARED_H
 
-#include "shared/enums.h"
-#include "shared/utils.h"
-#include "shared/icons.h"
-#include "shared/message.h"
-#include "shared/vcard.h"
-#include "shared/global.h"
-#include "shared/messageinfo.h"
+#include "enums.h"
+#include "global.h"
+#include "icons.h"
+#include "message.h"
+#include "messageinfo.h"
+#include "utils.h"
+#include "vcard.h"
 
 #endif // SHARED_H
diff --git a/ui/CMakeLists.txt b/ui/CMakeLists.txt
index 11b8f3d..36207b6 100644
--- a/ui/CMakeLists.txt
+++ b/ui/CMakeLists.txt
@@ -1,43 +1,5 @@
-cmake_minimum_required(VERSION 3.3)
-project(squawkUI)
-
-# Instruct CMake to run moc automatically when needed.
-set(CMAKE_AUTOMOC ON)
-# Instruct CMake to create code from Qt designer ui files
-set(CMAKE_AUTOUIC ON)
-
-# Find the QtWidgets library
-find_package(Qt5 CONFIG REQUIRED COMPONENTS Widgets DBus Core)
-find_package(Boost 1.36.0 REQUIRED)
-if(Boost_FOUND)
-  include_directories(${Boost_INCLUDE_DIRS})
-endif()
+target_sources(squawk PRIVATE squawk.cpp squawk.h squawk.ui)
 
+add_subdirectory(models)
 add_subdirectory(utils)
 add_subdirectory(widgets)
-
-set(squawkUI_SRC
-  squawk.cpp
-  models/accounts.cpp
-  models/roster.cpp
-  models/item.cpp
-  models/account.cpp
-  models/contact.cpp
-  models/presence.cpp
-  models/group.cpp
-  models/room.cpp
-  models/abstractparticipant.cpp
-  models/participant.cpp
-  models/reference.cpp
-  models/messagefeed.cpp
-  models/element.cpp
-)
-
-# Tell CMake to create the helloworld executable
-add_library(squawkUI STATIC ${squawkUI_SRC})
-
-# Use the Widgets module from Qt 5.
-target_link_libraries(squawkUI squawkWidgets)
-target_link_libraries(squawkUI squawkUIUtils)
-target_link_libraries(squawkUI Qt5::Widgets)
-target_link_libraries(squawkUI Qt5::DBus)
diff --git a/ui/models/CMakeLists.txt b/ui/models/CMakeLists.txt
new file mode 100644
index 0000000..fcd80d9
--- /dev/null
+++ b/ui/models/CMakeLists.txt
@@ -0,0 +1,28 @@
+target_sources(squawk PRIVATE
+  ${CMAKE_CURRENT_LIST_DIR}/abstractparticipant.cpp
+  ${CMAKE_CURRENT_LIST_DIR}/abstractparticipant.h
+  ${CMAKE_CURRENT_LIST_DIR}/account.cpp
+  ${CMAKE_CURRENT_LIST_DIR}/account.h
+  ${CMAKE_CURRENT_LIST_DIR}/accounts.cpp
+  ${CMAKE_CURRENT_LIST_DIR}/accounts.h
+  ${CMAKE_CURRENT_LIST_DIR}/contact.cpp
+  ${CMAKE_CURRENT_LIST_DIR}/contact.h
+  ${CMAKE_CURRENT_LIST_DIR}/element.cpp
+  ${CMAKE_CURRENT_LIST_DIR}/element.h
+  ${CMAKE_CURRENT_LIST_DIR}/group.cpp
+  ${CMAKE_CURRENT_LIST_DIR}/group.h
+  ${CMAKE_CURRENT_LIST_DIR}/item.cpp
+  ${CMAKE_CURRENT_LIST_DIR}/item.h
+  ${CMAKE_CURRENT_LIST_DIR}/messagefeed.cpp
+  ${CMAKE_CURRENT_LIST_DIR}/messagefeed.h
+  ${CMAKE_CURRENT_LIST_DIR}/participant.cpp
+  ${CMAKE_CURRENT_LIST_DIR}/participant.h
+  ${CMAKE_CURRENT_LIST_DIR}/presence.cpp
+  ${CMAKE_CURRENT_LIST_DIR}/presence.h
+  ${CMAKE_CURRENT_LIST_DIR}/reference.cpp
+  ${CMAKE_CURRENT_LIST_DIR}/reference.h
+  ${CMAKE_CURRENT_LIST_DIR}/room.cpp
+  ${CMAKE_CURRENT_LIST_DIR}/room.h
+  ${CMAKE_CURRENT_LIST_DIR}/roster.cpp
+  ${CMAKE_CURRENT_LIST_DIR}/roster.h
+)
\ No newline at end of file
diff --git a/ui/squawk.h b/ui/squawk.h
index fa92df7..15d3f82 100644
--- a/ui/squawk.h
+++ b/ui/squawk.h
@@ -39,7 +39,7 @@
 #include "models/roster.h"
 #include "widgets/vcard/vcard.h"
 
-#include "shared.h"
+#include "shared/shared.h"
 
 namespace Ui {
 class Squawk;
diff --git a/ui/utils/CMakeLists.txt b/ui/utils/CMakeLists.txt
index 0c33521..93eb4c7 100644
--- a/ui/utils/CMakeLists.txt
+++ b/ui/utils/CMakeLists.txt
@@ -1,32 +1,26 @@
-cmake_minimum_required(VERSION 3.3)
-project(squawkUIUtils)
-
-# Instruct CMake to run moc automatically when needed.
-set(CMAKE_AUTOMOC ON)
-# Instruct CMake to create code from Qt designer ui files
-set(CMAKE_AUTOUIC ON)
-
-# Find the QtWidgets library
-find_package(Qt5 CONFIG REQUIRED COMPONENTS Widgets Core)
-
-set(squawkUIUtils_SRC
-#  messageline.cpp
-#  message.cpp
-  resizer.cpp
-#  image.cpp
-  flowlayout.cpp
-  badge.cpp
-  progress.cpp
-  comboboxdelegate.cpp
-  feedview.cpp
-  messagedelegate.cpp
-  exponentialblur.cpp
-  shadowoverlay.cpp
-)
-
-# Tell CMake to create the helloworld executable
-add_library(squawkUIUtils STATIC ${squawkUIUtils_SRC})
-
-# Use the Widgets module from Qt 5.
-target_link_libraries(squawkUIUtils squawkWidgets)
-target_link_libraries(squawkUIUtils Qt5::Widgets)
+target_sources(squawk PRIVATE
+  ${CMAKE_CURRENT_LIST_DIR}/badge.cpp
+  ${CMAKE_CURRENT_LIST_DIR}/badge.h
+  ${CMAKE_CURRENT_LIST_DIR}/comboboxdelegate.cpp
+  ${CMAKE_CURRENT_LIST_DIR}/comboboxdelegate.h
+  ${CMAKE_CURRENT_LIST_DIR}/exponentialblur.cpp
+  ${CMAKE_CURRENT_LIST_DIR}/exponentialblur.h
+  ${CMAKE_CURRENT_LIST_DIR}/feedview.cpp
+  ${CMAKE_CURRENT_LIST_DIR}/feedview.h
+  ${CMAKE_CURRENT_LIST_DIR}/flowlayout.cpp
+  ${CMAKE_CURRENT_LIST_DIR}/flowlayout.h
+  ${CMAKE_CURRENT_LIST_DIR}/image.cpp
+  ${CMAKE_CURRENT_LIST_DIR}/image.h
+  ${CMAKE_CURRENT_LIST_DIR}/message.cpp
+  ${CMAKE_CURRENT_LIST_DIR}/message.h
+  ${CMAKE_CURRENT_LIST_DIR}/messagedelegate.cpp
+  ${CMAKE_CURRENT_LIST_DIR}/messagedelegate.h
+  ${CMAKE_CURRENT_LIST_DIR}/messageline.cpp
+  ${CMAKE_CURRENT_LIST_DIR}/messageline.h
+  ${CMAKE_CURRENT_LIST_DIR}/progress.cpp
+  ${CMAKE_CURRENT_LIST_DIR}/progress.h
+  ${CMAKE_CURRENT_LIST_DIR}/resizer.cpp
+  ${CMAKE_CURRENT_LIST_DIR}/resizer.h
+  ${CMAKE_CURRENT_LIST_DIR}/shadowoverlay.cpp
+  ${CMAKE_CURRENT_LIST_DIR}/shadowoverlay.h
+)
\ No newline at end of file
diff --git a/ui/widgets/CMakeLists.txt b/ui/widgets/CMakeLists.txt
index 78b4f1a..dd1bf95 100644
--- a/ui/widgets/CMakeLists.txt
+++ b/ui/widgets/CMakeLists.txt
@@ -1,31 +1,23 @@
-cmake_minimum_required(VERSION 3.0)
-project(squawkWidgets)
-
-# Instruct CMake to run moc automatically when needed.
-set(CMAKE_AUTOMOC ON)
-# Instruct CMake to create code from Qt designer ui files
-set(CMAKE_AUTOUIC ON)
-
-# Find the QtWidgets library
-find_package(Qt5Widgets CONFIG REQUIRED COMPONENTS Widgets Core)
+target_sources(squawk PRIVATE
+  ${CMAKE_CURRENT_LIST_DIR}/account.cpp
+  ${CMAKE_CURRENT_LIST_DIR}/account.h
+  ${CMAKE_CURRENT_LIST_DIR}/account.ui
+  ${CMAKE_CURRENT_LIST_DIR}/accounts.cpp
+  ${CMAKE_CURRENT_LIST_DIR}/accounts.h
+  ${CMAKE_CURRENT_LIST_DIR}/accounts.ui
+  ${CMAKE_CURRENT_LIST_DIR}/chat.cpp
+  ${CMAKE_CURRENT_LIST_DIR}/chat.h
+  ${CMAKE_CURRENT_LIST_DIR}/conversation.cpp
+  ${CMAKE_CURRENT_LIST_DIR}/conversation.h
+  ${CMAKE_CURRENT_LIST_DIR}/conversation.ui
+  ${CMAKE_CURRENT_LIST_DIR}/joinconference.cpp
+  ${CMAKE_CURRENT_LIST_DIR}/joinconference.h
+  ${CMAKE_CURRENT_LIST_DIR}/joinconference.ui
+  ${CMAKE_CURRENT_LIST_DIR}/newcontact.cpp
+  ${CMAKE_CURRENT_LIST_DIR}/newcontact.h
+  ${CMAKE_CURRENT_LIST_DIR}/newcontact.ui
+  ${CMAKE_CURRENT_LIST_DIR}/room.cpp
+  ${CMAKE_CURRENT_LIST_DIR}/room.h
+  )
 
 add_subdirectory(vcard)
-
-set(squawkWidgets_SRC
-  conversation.cpp
-  chat.cpp
-  room.cpp
-  newcontact.cpp
-  accounts.cpp
-  account.cpp
-  joinconference.cpp
-)
-
-add_library(squawkWidgets STATIC ${squawkWidgets_SRC})
-
-# Use the Widgets module from Qt 5.
-target_link_libraries(squawkWidgets vCardUI)
-target_link_libraries(squawkWidgets squawkUIUtils)
-target_link_libraries(squawkWidgets Qt5::Widgets)
-
-qt5_use_modules(squawkWidgets Core Widgets)
diff --git a/ui/widgets/conversation.h b/ui/widgets/conversation.h
index 690a51c..0b0dcb2 100644
--- a/ui/widgets/conversation.h
+++ b/ui/widgets/conversation.h
@@ -30,7 +30,7 @@
 #include <QDesktopServices>
 
 #include "shared/message.h"
-#include "order.h"
+#include "shared/order.h"
 #include "ui/models/account.h"
 #include "ui/models/roster.h"
 #include "ui/utils/flowlayout.h"
diff --git a/ui/widgets/vcard/CMakeLists.txt b/ui/widgets/vcard/CMakeLists.txt
index 73b157c..c5c53a3 100644
--- a/ui/widgets/vcard/CMakeLists.txt
+++ b/ui/widgets/vcard/CMakeLists.txt
@@ -1,22 +1,9 @@
-cmake_minimum_required(VERSION 3.0)
-project(vCardUI)
-
-# Instruct CMake to run moc automatically when needed.
-set(CMAKE_AUTOMOC ON)
-# Instruct CMake to create code from Qt designer ui files
-set(CMAKE_AUTOUIC ON)
-
-# Find the QtWidgets library
-find_package(Qt5Widgets CONFIG REQUIRED)
-
-set(vCardUI_SRC
-  vcard.cpp
-  emailsmodel.cpp
-  phonesmodel.cpp
-)
-
-# Tell CMake to create the helloworld executable
-add_library(vCardUI STATIC ${vCardUI_SRC})
-
-# Use the Widgets module from Qt 5.
-target_link_libraries(vCardUI Qt5::Widgets)
+target_sources(squawk PRIVATE
+  ${CMAKE_CURRENT_LIST_DIR}/emailsmodel.cpp
+  ${CMAKE_CURRENT_LIST_DIR}/emailsmodel.h
+  ${CMAKE_CURRENT_LIST_DIR}/phonesmodel.cpp
+  ${CMAKE_CURRENT_LIST_DIR}/phonesmodel.h
+  ${CMAKE_CURRENT_LIST_DIR}/vcard.cpp
+  ${CMAKE_CURRENT_LIST_DIR}/vcard.h
+  ${CMAKE_CURRENT_LIST_DIR}/vcard.ui
+  )

From 0038aca1f6a44f91ae503de78ea625c76eaea74f Mon Sep 17 00:00:00 2001
From: vae <vae@programming.socks.town>
Date: Tue, 11 May 2021 21:35:12 +0300
Subject: [PATCH 115/281] build: WIP CMakeLists refactoring continue - add
 FindSignal

---
 CMakeLists.txt                             | 120 +++++++++++----------
 cmake/FindSignal.cmake                     |  15 +++
 core/CMakeLists.txt                        |   7 --
 core/handlers/CMakeLists.txt               |   2 +-
 core/passwordStorageEngines/CMakeLists.txt |  26 ++---
 plugins/CMakeLists.txt                     |  30 +-----
 shared/CMakeLists.txt                      |  36 +++----
 ui/models/CMakeLists.txt                   |  54 +++++-----
 ui/utils/CMakeLists.txt                    |  50 ++++-----
 ui/widgets/CMakeLists.txt                  |  38 +++----
 ui/widgets/vcard/CMakeLists.txt            |  14 +--
 11 files changed, 188 insertions(+), 204 deletions(-)
 create mode 100644 cmake/FindSignal.cmake

diff --git a/CMakeLists.txt b/CMakeLists.txt
index e1b7f0c..bf6e062 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -15,13 +15,77 @@ set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake")
 
 include(GNUInstallDirs)
 
+option(SYSTEM_QXMPP "Use system qxmpp lib" ON)
+option(WITH_KWALLET "Build KWallet support module" ON)
+option(WITH_KIO "Build KIO support module" ON)
+
+# Dependencies
+
+## Qt
 find_package(Qt5Widgets CONFIG REQUIRED COMPONENTS Widgets DBus Core)
-find_package(Qt5LinguistTools)
 find_package(Qt5Core CONFIG REQUIRED)
 find_package(Qt5Gui CONFIG REQUIRED)
 find_package(Qt5Network CONFIG REQUIRED)
 find_package(Qt5Xml CONFIG REQUIRED)
+find_package(Qt5LinguistTools)
+
 find_package(LMDB REQUIRED)
+find_package(Signal REQUIRED)
+
+## QXmpp
+if (SYSTEM_QXMPP)
+  find_package(QXmpp CONFIG)
+
+  if (NOT QXmpp_FOUND)
+    set(SYSTEM_QXMPP OFF)
+    message("QXmpp package wasn't found, trying to build with bundled QXmpp")
+  else()
+    message("Building with system QXmpp")
+  endif()
+endif()
+
+if(NOT SYSTEM_QXMPP)
+  add_subdirectory(external/qxmpp)
+endif()
+
+## KIO
+if (WITH_KIO)
+  find_package(KF5KIO CONFIG)
+
+  if (NOT KF5KIO_FOUND)
+    set(WITH_KIO OFF)
+    message("KIO package wasn't found, KIO support modules wouldn't be built")
+  else()
+    add_definitions(-DWITH_KIO)
+    message("Building with support of KIO")
+  endif()
+endif()
+
+## KWallet
+if (WITH_KWALLET)
+  find_package(KF5Wallet CONFIG)
+
+  if (NOT KF5Wallet_FOUND)
+    set(WITH_KWALLET OFF)
+    message("KWallet package wasn't found, KWallet support module wouldn't be built")
+  else()
+    add_definitions(-DWITH_KWALLET)
+    message("Building with support of KWallet")
+  endif()
+endif()
+
+# Linking
+target_link_libraries(squawk PRIVATE Qt5::Widgets)
+target_link_libraries(squawk PRIVATE Qt5::DBus)
+target_link_libraries(squawk PRIVATE Qt5::Network)
+target_link_libraries(squawk PRIVATE Qt5::Gui)
+target_link_libraries(squawk PRIVATE Qt5::Xml)
+target_link_libraries(squawk PRIVATE qxmpp)
+target_link_libraries(squawk PRIVATE lmdb)
+target_link_libraries(squawk PRIVATE simpleCrypt)
+target_link_libraries(squawk PRIVATE uuid)
+
+# Build type
 
 if(NOT CMAKE_BUILD_TYPE)
   set(CMAKE_BUILD_TYPE Debug)
@@ -51,61 +115,9 @@ set(TS_FILES
 qt5_add_translation(QM_FILES ${TS_FILES})
 add_custom_target(translations ALL DEPENDS ${QM_FILES})
 
-qt5_use_modules(squawk LINK_PUBLIC Core Widgets)
 qt5_add_resources(RCC resources/resources.qrc)
 
-option(SYSTEM_QXMPP "Use system qxmpp lib" ON)
-option(WITH_KWALLET "Build KWallet support module" ON)
-option(WITH_KIO "Build KIO support module" ON)
-
-if (SYSTEM_QXMPP)
-    find_package(QXmpp CONFIG)
-
-    if (NOT QXmpp_FOUND)
-        set(SYSTEM_QXMPP OFF)
-        message("QXmpp package wasn't found, trying to build with bundled QXmpp")
-    else()
-        message("Building with system QXmpp")
-    endif()
-endif()
-
-if(NOT SYSTEM_QXMPP)
-    add_subdirectory(external/qxmpp)
-endif()
-
-if (WITH_KWALLET)
-    find_package(KF5Wallet CONFIG)
-
-    if (NOT KF5Wallet_FOUND)
-        set(WITH_KWALLET OFF)
-        message("KWallet package wasn't found, KWallet support module wouldn't be built")
-    else()
-        add_definitions(-DWITH_KWALLET)
-        message("Building with support of KWallet")
-    endif()
-endif()
-
 target_sources(squawk PRIVATE ${RCC})
-target_link_libraries(squawk PRIVATE Qt5::Widgets)
-target_link_libraries(squawk PRIVATE Qt5::DBus)
-target_link_libraries(squawk PRIVATE Qt5::Network)
-target_link_libraries(squawk PRIVATE Qt5::Gui)
-target_link_libraries(squawk PRIVATE Qt5::Xml)
-target_link_libraries(squawk PRIVATE qxmpp)
-target_link_libraries(squawk PRIVATE lmdb)
-target_link_libraries(squawk PRIVATE simpleCrypt)
-
-if (WITH_KIO)
-    find_package(KF5KIO CONFIG)
-
-    if (NOT KF5KIO_FOUND)
-        set(WITH_KIO OFF)
-        message("KIO package wasn't found, KIO support modules wouldn't be built")
-    else()
-        add_definitions(-DWITH_KIO)
-        message("Building with support of KIO")
-    endif()
-endif()
 
 add_subdirectory(ui)
 add_subdirectory(core)
@@ -113,8 +125,6 @@ add_subdirectory(plugins)
 
 add_subdirectory(external/simpleCrypt)
 
-target_link_libraries(squawk PRIVATE uuid)
-
 add_dependencies(${CMAKE_PROJECT_NAME} translations)
 
 # Install the executable
diff --git a/cmake/FindSignal.cmake b/cmake/FindSignal.cmake
new file mode 100644
index 0000000..752fed7
--- /dev/null
+++ b/cmake/FindSignal.cmake
@@ -0,0 +1,15 @@
+find_path(Signal_INCLUDE_DIR NAMES signal/signal_protocol.h)
+find_library(Signal_LIBRARY signal-protocol-c)
+mark_as_advanced(Signal_INCLUDE_DIR Signal_LIBRARY)
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(Signal REQUIRED_VARS Signal_LIBRARY Signal_INCLUDE_DIR)
+
+if (Signal_FOUND AND NOT TARGET Signal::Signal)
+  add_library(Signal::Signal UNKNOWN IMPORTED)
+  set_target_properties(Signal::Signal PROPERTIES
+    IMPORTED_LINK_INTERFACE_LANGUAGES "C"
+    IMPORTED_LOCATION "${Signal_LIBRARY}"
+    INTERFACE_INCLUDE_DIRECTORIES "${Signal_INCLUDE_DIR}"
+    )
+endif ()
diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt
index 3454204..3b160e2 100644
--- a/core/CMakeLists.txt
+++ b/core/CMakeLists.txt
@@ -25,10 +25,3 @@ target_sources(squawk PRIVATE
 
 add_subdirectory(handlers)
 add_subdirectory(passwordStorageEngines)
-
-#if(SYSTEM_QXMPP)
-#    get_target_property(QXMPP_INTERFACE_INCLUDE_DIRECTORIES QXmpp::QXmpp INTERFACE_INCLUDE_DIRECTORIES)
-#    target_include_directories(squawk PRIVATE ${QXMPP_INTERFACE_INCLUDE_DIRECTORIES})
-#endif()
-
-# Use the Widgets module from Qt 5.
diff --git a/core/handlers/CMakeLists.txt b/core/handlers/CMakeLists.txt
index ebae4b3..6da2ef3 100644
--- a/core/handlers/CMakeLists.txt
+++ b/core/handlers/CMakeLists.txt
@@ -3,4 +3,4 @@ target_sources(squawk PRIVATE
   messagehandler.h
   rosterhandler.cpp
   rosterhandler.h
-  )
\ No newline at end of file
+  )
diff --git a/core/passwordStorageEngines/CMakeLists.txt b/core/passwordStorageEngines/CMakeLists.txt
index 7275d4f..da2834c 100644
--- a/core/passwordStorageEngines/CMakeLists.txt
+++ b/core/passwordStorageEngines/CMakeLists.txt
@@ -1,21 +1,9 @@
-target_sources(squawk PRIVATE
-  wrappers/kwallet.cpp
-  kwallet.cpp
-  kwallet.h
-  )
-
 if (WITH_KWALLET)
-#    get_target_property(KWALLET_INTERFACE_INCLUDE_DIRECTORIES KF5::Wallet INTERFACE_INCLUDE_DIRECTORIES)
-#    get_target_property(Qt5GUI_INTERFACE_INCLUDE_DIRECTORIES Qt5::Gui INTERFACE_INCLUDE_DIRECTORIES)
-#
-#    target_include_directories(squawk PRIVATE ${KWALLET_INTERFACE_INCLUDE_DIRECTORIES})
-#    target_include_directories(squawk PRIVATE ${Qt5GUI_INTERFACE_INCLUDE_DIRECTORIES})
+  target_sources(squawk PRIVATE
+    wrappers/kwallet.cpp
+    kwallet.cpp
+    kwallet.h
+    )
 
-    target_link_libraries(squawk PUBLIC Qt5::Core Qt5::Gui KF5::Wallet)
-
-#    target_include_directories(kwalletWrapper PUBLIC ${KWALLET_INTERFACE_INCLUDE_DIRECTORIES})
-#    target_include_directories(kwalletWrapper PUBLIC ${Qt5GUI_INTERFACE_INCLUDE_DIRECTORIES})
-
-#    target_link_libraries(kwalletWrapper KF5::Wallet)
-#    target_link_libraries(kwalletWrapper Qt5::Core)
-endif()
+  target_link_libraries(squawk PUBLIC KF5::Wallet)
+endif ()
diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt
index 69a5e94..97b3b46 100644
--- a/plugins/CMakeLists.txt
+++ b/plugins/CMakeLists.txt
@@ -1,26 +1,4 @@
-cmake_minimum_required(VERSION 3.3)
-project(plugins)
-
-if (WITH_KIO) 
-    set(CMAKE_AUTOMOC ON)
-    
-    find_package(Qt5Core CONFIG REQUIRED)
-
-    set(openFileManagerWindowJob_SRC
-        openfilemanagerwindowjob.cpp
-    )
-
-    add_library(openFileManagerWindowJob SHARED ${openFileManagerWindowJob_SRC})
-    
-    get_target_property(Qt5CORE_INTERFACE_INCLUDE_DIRECTORIES Qt5::Core INTERFACE_INCLUDE_DIRECTORIES)
-    get_target_property(KIO_WIDGETS_INTERFACE_INCLUDE_DIRECTORIES KF5::KIOWidgets INTERFACE_INCLUDE_DIRECTORIES)
-    get_target_property(CORE_ADDONS_INTERFACE_INCLUDE_DIRECTORIES KF5::CoreAddons INTERFACE_INCLUDE_DIRECTORIES)
-    target_include_directories(openFileManagerWindowJob PUBLIC ${KIO_WIDGETS_INTERFACE_INCLUDE_DIRECTORIES})
-    target_include_directories(openFileManagerWindowJob PUBLIC ${CORE_ADDONS_INTERFACE_INCLUDE_DIRECTORIES})
-    target_include_directories(openFileManagerWindowJob PUBLIC ${Qt5CORE_INTERFACE_INCLUDE_DIRECTORIES})
-    
-    target_link_libraries(openFileManagerWindowJob KF5::KIOWidgets)
-    target_link_libraries(openFileManagerWindowJob Qt5::Core)
-
-    install(TARGETS openFileManagerWindowJob DESTINATION ${CMAKE_INSTALL_LIBDIR})
-endif()
+if (WITH_KIO)
+  target_sources(squawk PRIVATE openfilemanagerwindowjob.cpp)
+  target_link_libraries(squawk PRIVATE KF5::KIOWidgets)
+endif ()
diff --git a/shared/CMakeLists.txt b/shared/CMakeLists.txt
index edd769a..a36b516 100644
--- a/shared/CMakeLists.txt
+++ b/shared/CMakeLists.txt
@@ -1,19 +1,19 @@
 target_sources(squawk PRIVATE
-  ${CMAKE_CURRENT_LIST_DIR}/enums.h
-  ${CMAKE_CURRENT_LIST_DIR}/global.cpp
-  ${CMAKE_CURRENT_LIST_DIR}/global.h
-  ${CMAKE_CURRENT_LIST_DIR}/exception.cpp
-  ${CMAKE_CURRENT_LIST_DIR}/exception.h
-  ${CMAKE_CURRENT_LIST_DIR}/icons.cpp
-  ${CMAKE_CURRENT_LIST_DIR}/icons.h
-  ${CMAKE_CURRENT_LIST_DIR}/message.cpp
-  ${CMAKE_CURRENT_LIST_DIR}/message.h
-  ${CMAKE_CURRENT_LIST_DIR}/messageinfo.cpp
-  ${CMAKE_CURRENT_LIST_DIR}/messageinfo.h
-  ${CMAKE_CURRENT_LIST_DIR}/order.h
-  ${CMAKE_CURRENT_LIST_DIR}/shared.h
-  ${CMAKE_CURRENT_LIST_DIR}/utils.cpp
-  ${CMAKE_CURRENT_LIST_DIR}/utils.h
-  ${CMAKE_CURRENT_LIST_DIR}/vcard.cpp
-  ${CMAKE_CURRENT_LIST_DIR}/vcard.h
-)
\ No newline at end of file
+  enums.h
+  global.cpp
+  global.h
+  exception.cpp
+  exception.h
+  icons.cpp
+  icons.h
+  message.cpp
+  message.h
+  messageinfo.cpp
+  messageinfo.h
+  order.h
+  shared.h
+  utils.cpp
+  utils.h
+  vcard.cpp
+  vcard.h
+  )
diff --git a/ui/models/CMakeLists.txt b/ui/models/CMakeLists.txt
index fcd80d9..98ef1c3 100644
--- a/ui/models/CMakeLists.txt
+++ b/ui/models/CMakeLists.txt
@@ -1,28 +1,28 @@
 target_sources(squawk PRIVATE
-  ${CMAKE_CURRENT_LIST_DIR}/abstractparticipant.cpp
-  ${CMAKE_CURRENT_LIST_DIR}/abstractparticipant.h
-  ${CMAKE_CURRENT_LIST_DIR}/account.cpp
-  ${CMAKE_CURRENT_LIST_DIR}/account.h
-  ${CMAKE_CURRENT_LIST_DIR}/accounts.cpp
-  ${CMAKE_CURRENT_LIST_DIR}/accounts.h
-  ${CMAKE_CURRENT_LIST_DIR}/contact.cpp
-  ${CMAKE_CURRENT_LIST_DIR}/contact.h
-  ${CMAKE_CURRENT_LIST_DIR}/element.cpp
-  ${CMAKE_CURRENT_LIST_DIR}/element.h
-  ${CMAKE_CURRENT_LIST_DIR}/group.cpp
-  ${CMAKE_CURRENT_LIST_DIR}/group.h
-  ${CMAKE_CURRENT_LIST_DIR}/item.cpp
-  ${CMAKE_CURRENT_LIST_DIR}/item.h
-  ${CMAKE_CURRENT_LIST_DIR}/messagefeed.cpp
-  ${CMAKE_CURRENT_LIST_DIR}/messagefeed.h
-  ${CMAKE_CURRENT_LIST_DIR}/participant.cpp
-  ${CMAKE_CURRENT_LIST_DIR}/participant.h
-  ${CMAKE_CURRENT_LIST_DIR}/presence.cpp
-  ${CMAKE_CURRENT_LIST_DIR}/presence.h
-  ${CMAKE_CURRENT_LIST_DIR}/reference.cpp
-  ${CMAKE_CURRENT_LIST_DIR}/reference.h
-  ${CMAKE_CURRENT_LIST_DIR}/room.cpp
-  ${CMAKE_CURRENT_LIST_DIR}/room.h
-  ${CMAKE_CURRENT_LIST_DIR}/roster.cpp
-  ${CMAKE_CURRENT_LIST_DIR}/roster.h
-)
\ No newline at end of file
+  abstractparticipant.cpp
+  abstractparticipant.h
+  account.cpp
+  account.h
+  accounts.cpp
+  accounts.h
+  contact.cpp
+  contact.h
+  element.cpp
+  element.h
+  group.cpp
+  group.h
+  item.cpp
+  item.h
+  messagefeed.cpp
+  messagefeed.h
+  participant.cpp
+  participant.h
+  presence.cpp
+  presence.h
+  reference.cpp
+  reference.h
+  room.cpp
+  room.h
+  roster.cpp
+  roster.h
+  )
\ No newline at end of file
diff --git a/ui/utils/CMakeLists.txt b/ui/utils/CMakeLists.txt
index 93eb4c7..5ad5cb7 100644
--- a/ui/utils/CMakeLists.txt
+++ b/ui/utils/CMakeLists.txt
@@ -1,26 +1,26 @@
 target_sources(squawk PRIVATE
-  ${CMAKE_CURRENT_LIST_DIR}/badge.cpp
-  ${CMAKE_CURRENT_LIST_DIR}/badge.h
-  ${CMAKE_CURRENT_LIST_DIR}/comboboxdelegate.cpp
-  ${CMAKE_CURRENT_LIST_DIR}/comboboxdelegate.h
-  ${CMAKE_CURRENT_LIST_DIR}/exponentialblur.cpp
-  ${CMAKE_CURRENT_LIST_DIR}/exponentialblur.h
-  ${CMAKE_CURRENT_LIST_DIR}/feedview.cpp
-  ${CMAKE_CURRENT_LIST_DIR}/feedview.h
-  ${CMAKE_CURRENT_LIST_DIR}/flowlayout.cpp
-  ${CMAKE_CURRENT_LIST_DIR}/flowlayout.h
-  ${CMAKE_CURRENT_LIST_DIR}/image.cpp
-  ${CMAKE_CURRENT_LIST_DIR}/image.h
-  ${CMAKE_CURRENT_LIST_DIR}/message.cpp
-  ${CMAKE_CURRENT_LIST_DIR}/message.h
-  ${CMAKE_CURRENT_LIST_DIR}/messagedelegate.cpp
-  ${CMAKE_CURRENT_LIST_DIR}/messagedelegate.h
-  ${CMAKE_CURRENT_LIST_DIR}/messageline.cpp
-  ${CMAKE_CURRENT_LIST_DIR}/messageline.h
-  ${CMAKE_CURRENT_LIST_DIR}/progress.cpp
-  ${CMAKE_CURRENT_LIST_DIR}/progress.h
-  ${CMAKE_CURRENT_LIST_DIR}/resizer.cpp
-  ${CMAKE_CURRENT_LIST_DIR}/resizer.h
-  ${CMAKE_CURRENT_LIST_DIR}/shadowoverlay.cpp
-  ${CMAKE_CURRENT_LIST_DIR}/shadowoverlay.h
-)
\ No newline at end of file
+  badge.cpp
+  badge.h
+  comboboxdelegate.cpp
+  comboboxdelegate.h
+  exponentialblur.cpp
+  exponentialblur.h
+  feedview.cpp
+  feedview.h
+  flowlayout.cpp
+  flowlayout.h
+  image.cpp
+  image.h
+  message.cpp
+  message.h
+  messagedelegate.cpp
+  messagedelegate.h
+  messageline.cpp
+  messageline.h
+  progress.cpp
+  progress.h
+  resizer.cpp
+  resizer.h
+  shadowoverlay.cpp
+  shadowoverlay.h
+  )
diff --git a/ui/widgets/CMakeLists.txt b/ui/widgets/CMakeLists.txt
index dd1bf95..0cacf6f 100644
--- a/ui/widgets/CMakeLists.txt
+++ b/ui/widgets/CMakeLists.txt
@@ -1,23 +1,23 @@
 target_sources(squawk PRIVATE
-  ${CMAKE_CURRENT_LIST_DIR}/account.cpp
-  ${CMAKE_CURRENT_LIST_DIR}/account.h
-  ${CMAKE_CURRENT_LIST_DIR}/account.ui
-  ${CMAKE_CURRENT_LIST_DIR}/accounts.cpp
-  ${CMAKE_CURRENT_LIST_DIR}/accounts.h
-  ${CMAKE_CURRENT_LIST_DIR}/accounts.ui
-  ${CMAKE_CURRENT_LIST_DIR}/chat.cpp
-  ${CMAKE_CURRENT_LIST_DIR}/chat.h
-  ${CMAKE_CURRENT_LIST_DIR}/conversation.cpp
-  ${CMAKE_CURRENT_LIST_DIR}/conversation.h
-  ${CMAKE_CURRENT_LIST_DIR}/conversation.ui
-  ${CMAKE_CURRENT_LIST_DIR}/joinconference.cpp
-  ${CMAKE_CURRENT_LIST_DIR}/joinconference.h
-  ${CMAKE_CURRENT_LIST_DIR}/joinconference.ui
-  ${CMAKE_CURRENT_LIST_DIR}/newcontact.cpp
-  ${CMAKE_CURRENT_LIST_DIR}/newcontact.h
-  ${CMAKE_CURRENT_LIST_DIR}/newcontact.ui
-  ${CMAKE_CURRENT_LIST_DIR}/room.cpp
-  ${CMAKE_CURRENT_LIST_DIR}/room.h
+  account.cpp
+  account.h
+  account.ui
+  accounts.cpp
+  accounts.h
+  accounts.ui
+  chat.cpp
+  chat.h
+  conversation.cpp
+  conversation.h
+  conversation.ui
+  joinconference.cpp
+  joinconference.h
+  joinconference.ui
+  newcontact.cpp
+  newcontact.h
+  newcontact.ui
+  room.cpp
+  room.h
   )
 
 add_subdirectory(vcard)
diff --git a/ui/widgets/vcard/CMakeLists.txt b/ui/widgets/vcard/CMakeLists.txt
index c5c53a3..51cbaab 100644
--- a/ui/widgets/vcard/CMakeLists.txt
+++ b/ui/widgets/vcard/CMakeLists.txt
@@ -1,9 +1,9 @@
 target_sources(squawk PRIVATE
-  ${CMAKE_CURRENT_LIST_DIR}/emailsmodel.cpp
-  ${CMAKE_CURRENT_LIST_DIR}/emailsmodel.h
-  ${CMAKE_CURRENT_LIST_DIR}/phonesmodel.cpp
-  ${CMAKE_CURRENT_LIST_DIR}/phonesmodel.h
-  ${CMAKE_CURRENT_LIST_DIR}/vcard.cpp
-  ${CMAKE_CURRENT_LIST_DIR}/vcard.h
-  ${CMAKE_CURRENT_LIST_DIR}/vcard.ui
+  emailsmodel.cpp
+  emailsmodel.h
+  phonesmodel.cpp
+  phonesmodel.h
+  vcard.cpp
+  vcard.h
+  vcard.ui
   )

From 7d2688151c2dcef21acfbff7b2a4911965b855b3 Mon Sep 17 00:00:00 2001
From: vae <vae@programming.socks.town>
Date: Tue, 11 May 2021 22:21:25 +0300
Subject: [PATCH 116/281] build: finish up CMakeLists refactoring

---
 CMakeLists.txt                             | 76 +++++++---------------
 core/passwordStorageEngines/CMakeLists.txt |  2 +-
 external/simpleCrypt/CMakeLists.txt        | 12 +---
 packaging/CMakeLists.txt                   |  3 +
 resources/CMakeLists.txt                   | 14 ++++
 translations/CMakeLists.txt                |  8 +++
 6 files changed, 51 insertions(+), 64 deletions(-)
 create mode 100644 packaging/CMakeLists.txt
 create mode 100644 resources/CMakeLists.txt
 create mode 100644 translations/CMakeLists.txt

diff --git a/CMakeLists.txt b/CMakeLists.txt
index bf6e062..fc6ed1b 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -9,28 +9,19 @@ set(CMAKE_AUTOMOC ON)
 set(CMAKE_AUTOUIC ON)
 set(CMAKE_AUTORCC ON)
 
-add_executable(squawk)
-target_include_directories(squawk PRIVATE ${CMAKE_SOURCE_DIR})
+include(GNUInstallDirs)
 set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake")
 
-include(GNUInstallDirs)
+add_executable(squawk)
+target_include_directories(squawk PRIVATE ${CMAKE_SOURCE_DIR})
 
 option(SYSTEM_QXMPP "Use system qxmpp lib" ON)
 option(WITH_KWALLET "Build KWallet support module" ON)
 option(WITH_KIO "Build KIO support module" ON)
 
 # Dependencies
-
 ## Qt
-find_package(Qt5Widgets CONFIG REQUIRED COMPONENTS Widgets DBus Core)
-find_package(Qt5Core CONFIG REQUIRED)
-find_package(Qt5Gui CONFIG REQUIRED)
-find_package(Qt5Network CONFIG REQUIRED)
-find_package(Qt5Xml CONFIG REQUIRED)
-find_package(Qt5LinguistTools)
-
-find_package(LMDB REQUIRED)
-find_package(Signal REQUIRED)
+find_package(Qt5 COMPONENTS Widgets DBus Gui Xml Network Core REQUIRED)
 
 ## QXmpp
 if (SYSTEM_QXMPP)
@@ -45,7 +36,10 @@ if (SYSTEM_QXMPP)
 endif()
 
 if(NOT SYSTEM_QXMPP)
+  target_link_libraries(squawk PRIVATE qxmpp)
   add_subdirectory(external/qxmpp)
+else()
+  target_link_libraries(squawk PRIVATE QXmpp::QXmpp)
 endif()
 
 ## KIO
@@ -56,7 +50,7 @@ if (WITH_KIO)
     set(WITH_KIO OFF)
     message("KIO package wasn't found, KIO support modules wouldn't be built")
   else()
-    add_definitions(-DWITH_KIO)
+    target_compile_definitions(squawk PRIVATE WITH_KIO)
     message("Building with support of KIO")
   endif()
 endif()
@@ -69,24 +63,24 @@ if (WITH_KWALLET)
     set(WITH_KWALLET OFF)
     message("KWallet package wasn't found, KWallet support module wouldn't be built")
   else()
-    add_definitions(-DWITH_KWALLET)
+    target_compile_definitions(squawk PRIVATE WITH_KWALLET)
     message("Building with support of KWallet")
   endif()
 endif()
 
+## Signal (TODO)
+# find_package(Signal REQUIRED)
+
+## LMDB
+find_package(LMDB REQUIRED)
+
 # Linking
-target_link_libraries(squawk PRIVATE Qt5::Widgets)
-target_link_libraries(squawk PRIVATE Qt5::DBus)
-target_link_libraries(squawk PRIVATE Qt5::Network)
-target_link_libraries(squawk PRIVATE Qt5::Gui)
-target_link_libraries(squawk PRIVATE Qt5::Xml)
-target_link_libraries(squawk PRIVATE qxmpp)
+target_link_libraries(squawk PRIVATE Qt5::Core Qt5::Widgets Qt5::DBus Qt5::Network Qt5::Gui Qt5::Xml)
 target_link_libraries(squawk PRIVATE lmdb)
 target_link_libraries(squawk PRIVATE simpleCrypt)
 target_link_libraries(squawk PRIVATE uuid)
 
 # Build type
-
 if(NOT CMAKE_BUILD_TYPE)
   set(CMAKE_BUILD_TYPE Debug)
 endif()
@@ -99,40 +93,14 @@ target_compile_options(squawk PRIVATE
   "$<$<CONFIG:RELEASE>:-O3>"
 )
 
-add_subdirectory(shared)
-
-configure_file(resources/images/logo.svg squawk.svg COPYONLY)
-execute_process(COMMAND convert -background none -size 48x48 squawk.svg squawk48.png WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
-execute_process(COMMAND convert -background none -size 64x64 squawk.svg squawk64.png WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
-execute_process(COMMAND convert -background none -size 128x128 squawk.svg squawk128.png WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
-execute_process(COMMAND convert -background none -size 256x256 squawk.svg squawk256.png WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
-
-configure_file(packaging/squawk.desktop squawk.desktop COPYONLY)
-
-set(TS_FILES
-    translations/squawk.ru.ts
-)
-qt5_add_translation(QM_FILES ${TS_FILES})
-add_custom_target(translations ALL DEPENDS ${QM_FILES})
-
-qt5_add_resources(RCC resources/resources.qrc)
-
-target_sources(squawk PRIVATE ${RCC})
-
-add_subdirectory(ui)
 add_subdirectory(core)
-add_subdirectory(plugins)
-
 add_subdirectory(external/simpleCrypt)
-
-add_dependencies(${CMAKE_PROJECT_NAME} translations)
+add_subdirectory(packaging)
+add_subdirectory(plugins)
+add_subdirectory(resources)
+add_subdirectory(shared)
+add_subdirectory(translations)
+add_subdirectory(ui)
 
 # Install the executable
 install(TARGETS squawk DESTINATION ${CMAKE_INSTALL_BINDIR})
-install(FILES ${QM_FILES} DESTINATION ${CMAKE_INSTALL_DATADIR}/squawk/l10n)
-install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk.svg DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/scalable/apps)
-install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk48.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/48x48/apps RENAME squawk.png)
-install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk64.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/64x64/apps RENAME squawk.png)
-install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk128.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/128x128/apps RENAME squawk.png)
-install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk256.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/256x256/apps RENAME squawk.png)
-install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk.desktop DESTINATION ${CMAKE_INSTALL_DATADIR}/applications)
diff --git a/core/passwordStorageEngines/CMakeLists.txt b/core/passwordStorageEngines/CMakeLists.txt
index da2834c..7cab516 100644
--- a/core/passwordStorageEngines/CMakeLists.txt
+++ b/core/passwordStorageEngines/CMakeLists.txt
@@ -5,5 +5,5 @@ if (WITH_KWALLET)
     kwallet.h
     )
 
-  target_link_libraries(squawk PUBLIC KF5::Wallet)
+  target_link_libraries(squawk PRIVATE KF5::Wallet)
 endif ()
diff --git a/external/simpleCrypt/CMakeLists.txt b/external/simpleCrypt/CMakeLists.txt
index 88f5d23..274d304 100644
--- a/external/simpleCrypt/CMakeLists.txt
+++ b/external/simpleCrypt/CMakeLists.txt
@@ -1,16 +1,10 @@
 cmake_minimum_required(VERSION 3.0)
-project(simplecrypt)
+project(simplecrypt LANGUAGES CXX)
 
 set(CMAKE_AUTOMOC ON)
 
-find_package(Qt5Core CONFIG REQUIRED)
+find_package(Qt5 COMPONENTS Core REQUIRED)
 
-set(simplecrypt_SRC
-    simplecrypt.cpp
-)
+add_library(simpleCrypt STATIC simplecrypt.cpp simplecrypt.h)
 
-# Tell CMake to create the helloworld executable
-add_library(simpleCrypt STATIC ${simplecrypt_SRC})
-
-# Use the Widgets module from Qt 5.
 target_link_libraries(simpleCrypt Qt5::Core)
diff --git a/packaging/CMakeLists.txt b/packaging/CMakeLists.txt
new file mode 100644
index 0000000..4965b37
--- /dev/null
+++ b/packaging/CMakeLists.txt
@@ -0,0 +1,3 @@
+configure_file(squawk.desktop squawk.desktop COPYONLY)
+
+install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk.desktop DESTINATION ${CMAKE_INSTALL_DATADIR}/applications)
\ No newline at end of file
diff --git a/resources/CMakeLists.txt b/resources/CMakeLists.txt
new file mode 100644
index 0000000..86433f3
--- /dev/null
+++ b/resources/CMakeLists.txt
@@ -0,0 +1,14 @@
+target_sources(squawk PRIVATE resources.qrc)
+
+configure_file(images/logo.svg squawk.svg COPYONLY)
+
+execute_process(COMMAND convert -background none -size 48x48 squawk.svg squawk48.png WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
+execute_process(COMMAND convert -background none -size 64x64 squawk.svg squawk64.png WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
+execute_process(COMMAND convert -background none -size 128x128 squawk.svg squawk128.png WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
+execute_process(COMMAND convert -background none -size 256x256 squawk.svg squawk256.png WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
+
+install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk.svg DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/scalable/apps)
+install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk48.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/48x48/apps RENAME squawk.png)
+install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk64.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/64x64/apps RENAME squawk.png)
+install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk128.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/128x128/apps RENAME squawk.png)
+install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk256.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/256x256/apps RENAME squawk.png)
diff --git a/translations/CMakeLists.txt b/translations/CMakeLists.txt
new file mode 100644
index 0000000..c484000
--- /dev/null
+++ b/translations/CMakeLists.txt
@@ -0,0 +1,8 @@
+find_package(Qt5LinguistTools)
+
+set(TS_FILES squawk.ru.ts)
+qt5_add_translation(QM_FILES ${TS_FILES})
+add_custom_target(translations ALL DEPENDS ${QM_FILES})
+install(FILES ${QM_FILES} DESTINATION ${CMAKE_INSTALL_DATADIR}/squawk/l10n)
+
+add_dependencies(${CMAKE_PROJECT_NAME} translations)
\ No newline at end of file

From a184ecafa31d73a55fd314d0eeff8bd03004f757 Mon Sep 17 00:00:00 2001
From: vae <vae@programming.socks.town>
Date: Tue, 11 May 2021 22:24:55 +0300
Subject: [PATCH 117/281] build: reformat cmake code

---
 CMakeLists.txt       | 30 +++++++++++++++---------------
 cmake/FindLMDB.cmake | 28 ++++++++++++++--------------
 2 files changed, 29 insertions(+), 29 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index fc6ed1b..b9349d9 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -30,17 +30,17 @@ if (SYSTEM_QXMPP)
   if (NOT QXmpp_FOUND)
     set(SYSTEM_QXMPP OFF)
     message("QXmpp package wasn't found, trying to build with bundled QXmpp")
-  else()
+  else ()
     message("Building with system QXmpp")
-  endif()
-endif()
+  endif ()
+endif ()
 
-if(NOT SYSTEM_QXMPP)
+if (NOT SYSTEM_QXMPP)
   target_link_libraries(squawk PRIVATE qxmpp)
   add_subdirectory(external/qxmpp)
-else()
+else ()
   target_link_libraries(squawk PRIVATE QXmpp::QXmpp)
-endif()
+endif ()
 
 ## KIO
 if (WITH_KIO)
@@ -49,11 +49,11 @@ if (WITH_KIO)
   if (NOT KF5KIO_FOUND)
     set(WITH_KIO OFF)
     message("KIO package wasn't found, KIO support modules wouldn't be built")
-  else()
+  else ()
     target_compile_definitions(squawk PRIVATE WITH_KIO)
     message("Building with support of KIO")
-  endif()
-endif()
+  endif ()
+endif ()
 
 ## KWallet
 if (WITH_KWALLET)
@@ -62,11 +62,11 @@ if (WITH_KWALLET)
   if (NOT KF5Wallet_FOUND)
     set(WITH_KWALLET OFF)
     message("KWallet package wasn't found, KWallet support module wouldn't be built")
-  else()
+  else ()
     target_compile_definitions(squawk PRIVATE WITH_KWALLET)
     message("Building with support of KWallet")
-  endif()
-endif()
+  endif ()
+endif ()
 
 ## Signal (TODO)
 # find_package(Signal REQUIRED)
@@ -81,9 +81,9 @@ target_link_libraries(squawk PRIVATE simpleCrypt)
 target_link_libraries(squawk PRIVATE uuid)
 
 # Build type
-if(NOT CMAKE_BUILD_TYPE)
+if (NOT CMAKE_BUILD_TYPE)
   set(CMAKE_BUILD_TYPE Debug)
-endif()
+endif ()
 
 message("Build type: ${CMAKE_BUILD_TYPE}")
 
@@ -91,7 +91,7 @@ target_compile_options(squawk PRIVATE
   "-Wall;-Wextra"
   "$<$<CONFIG:DEBUG>:-g>"
   "$<$<CONFIG:RELEASE>:-O3>"
-)
+  )
 
 add_subdirectory(core)
 add_subdirectory(external/simpleCrypt)
diff --git a/cmake/FindLMDB.cmake b/cmake/FindLMDB.cmake
index 8bf48b4..79788f1 100644
--- a/cmake/FindLMDB.cmake
+++ b/cmake/FindLMDB.cmake
@@ -21,27 +21,27 @@
 #  LMDB_INCLUDE_DIRS        The location of LMDB headers.
 
 find_path(LMDB_ROOT_DIR
-    NAMES include/lmdb.h
-)
+  NAMES include/lmdb.h
+  )
 
 find_library(LMDB_LIBRARIES
-    NAMES lmdb
-    HINTS ${LMDB_ROOT_DIR}/lib
-)
+  NAMES lmdb
+  HINTS ${LMDB_ROOT_DIR}/lib
+  )
 
 find_path(LMDB_INCLUDE_DIRS
-    NAMES lmdb.h
-    HINTS ${LMDB_ROOT_DIR}/include
-)
+  NAMES lmdb.h
+  HINTS ${LMDB_ROOT_DIR}/include
+  )
 
 include(FindPackageHandleStandardArgs)
 find_package_handle_standard_args(LMDB DEFAULT_MSG
-    LMDB_LIBRARIES
-    LMDB_INCLUDE_DIRS
-)
+  LMDB_LIBRARIES
+  LMDB_INCLUDE_DIRS
+  )
 
 mark_as_advanced(
-    LMDB_ROOT_DIR
-    LMDB_LIBRARIES
-    LMDB_INCLUDE_DIRS
+  LMDB_ROOT_DIR
+  LMDB_LIBRARIES
+  LMDB_INCLUDE_DIRS
 )

From 8e99cc29692bd83fb9ca164df445a4cff52dbdc4 Mon Sep 17 00:00:00 2001
From: vae <vae@programming.socks.town>
Date: Wed, 12 May 2021 02:01:02 +0300
Subject: [PATCH 118/281] build: plugins/, passwordStorageEngines/wrappers/ as
 shared libs

---
 core/passwordStorageEngines/CMakeLists.txt          | 4 ++--
 core/passwordStorageEngines/wrappers/CMakeLists.txt | 2 ++
 plugins/CMakeLists.txt                              | 4 ++--
 3 files changed, 6 insertions(+), 4 deletions(-)
 create mode 100644 core/passwordStorageEngines/wrappers/CMakeLists.txt

diff --git a/core/passwordStorageEngines/CMakeLists.txt b/core/passwordStorageEngines/CMakeLists.txt
index 7cab516..4da3873 100644
--- a/core/passwordStorageEngines/CMakeLists.txt
+++ b/core/passwordStorageEngines/CMakeLists.txt
@@ -1,9 +1,9 @@
 if (WITH_KWALLET)
   target_sources(squawk PRIVATE
-    wrappers/kwallet.cpp
     kwallet.cpp
     kwallet.h
     )
 
-  target_link_libraries(squawk PRIVATE KF5::Wallet)
+  add_subdirectory(wrappers)
+  target_include_directories(squawk PRIVATE $<TARGET_PROPERTY:KF5::Wallet,INTERFACE_INCLUDE_DIRECTORIES>)
 endif ()
diff --git a/core/passwordStorageEngines/wrappers/CMakeLists.txt b/core/passwordStorageEngines/wrappers/CMakeLists.txt
new file mode 100644
index 0000000..6d486c0
--- /dev/null
+++ b/core/passwordStorageEngines/wrappers/CMakeLists.txt
@@ -0,0 +1,2 @@
+add_library(kwalletWrapper SHARED kwallet.cpp)
+target_link_libraries(kwalletWrapper PRIVATE KF5::Wallet)
diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt
index 97b3b46..84fc09b 100644
--- a/plugins/CMakeLists.txt
+++ b/plugins/CMakeLists.txt
@@ -1,4 +1,4 @@
 if (WITH_KIO)
-  target_sources(squawk PRIVATE openfilemanagerwindowjob.cpp)
-  target_link_libraries(squawk PRIVATE KF5::KIOWidgets)
+  add_library(openFileManagerWindowJob SHARED openfilemanagerwindowjob.cpp)
+  target_link_libraries(openFileManagerWindowJob PRIVATE KF5::KIOWidgets)
 endif ()

From 4307262f6eb4a85b8996cf3bae91ea63976e1b7d Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Fri, 14 May 2021 22:49:38 +0300
Subject: [PATCH 119/281] basic error download/upload files handling, need more
 testing

---
 core/networkaccess.cpp       |  76 +++++++++++++++-----
 core/networkaccess.h         |   1 +
 ui/models/messagefeed.cpp    | 132 ++++++++++++++++++++++++++++-------
 ui/models/messagefeed.h      |   6 +-
 ui/utils/feedview.cpp        |   9 +--
 ui/utils/feedview.h          |   2 +-
 ui/utils/messagedelegate.cpp |  54 ++++++++------
 ui/utils/messagedelegate.h   |   3 +-
 8 files changed, 206 insertions(+), 77 deletions(-)

diff --git a/core/networkaccess.cpp b/core/networkaccess.cpp
index eece379..69fe812 100644
--- a/core/networkaccess.cpp
+++ b/core/networkaccess.cpp
@@ -70,6 +70,9 @@ void Core::NetworkAccess::start()
 {
     if (!running) {
         manager = new QNetworkAccessManager();
+#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
+        manager->setTransferTimeout();
+#endif
         storage.open();
         running = true;
     }
@@ -99,31 +102,56 @@ void Core::NetworkAccess::onDownloadProgress(qint64 bytesReceived, qint64 bytesT
         qDebug() << "an error downloading" << url << ": the request had some progress but seems like no one is waiting for it, skipping";
     } else {
         Transfer* dwn = itr->second;
-        qreal received = bytesReceived;
-        qreal total = bytesTotal;
-        qreal progress = received/total;
-        dwn->progress = progress;
-        emit loadFileProgress(dwn->messages, progress, false);
+        if (dwn->success) {
+            qreal received = bytesReceived;
+            qreal total = bytesTotal;
+            qreal progress = received/total;
+            dwn->progress = progress;
+            emit loadFileProgress(dwn->messages, progress, false);
+        }
     }
 }
 
 void Core::NetworkAccess::onDownloadError(QNetworkReply::NetworkError code)
 {
+    qDebug() << "DEBUG: DOWNLOAD ERROR";
     QNetworkReply* rpl = static_cast<QNetworkReply*>(sender());
+    qDebug() << rpl->errorString();
     QString url = rpl->url().toString();
     std::map<QString, Transfer*>::const_iterator itr = downloads.find(url);
     if (itr == downloads.end()) {
         qDebug() << "an error downloading" << url << ": the request is reporting an error but seems like no one is waiting for it, skipping";
     } else {
         QString errorText = getErrorText(code);
-        if (errorText.size() > 0) {
+        //if (errorText.size() > 0) {
             itr->second->success = false;
             Transfer* dwn = itr->second;
             emit loadFileError(dwn->messages, errorText, false);
-        }
+        //}
     }
 }
 
+void Core::NetworkAccess::onDownloadSSLError(const QList<QSslError>& errors)
+{
+    qDebug() << "DEBUG: DOWNLOAD SSL ERRORS";
+    for (const QSslError& err : errors) {
+        qDebug() << err.errorString();
+    }
+    QNetworkReply* rpl = static_cast<QNetworkReply*>(sender());
+    QString url = rpl->url().toString();
+    std::map<QString, Transfer*>::const_iterator itr = downloads.find(url);
+    if (itr == downloads.end()) {
+        qDebug() << "an SSL error downloading" << url << ": the request is reporting an error but seems like no one is waiting for it, skipping";
+    } else {
+        //if (errorText.size() > 0) {
+        itr->second->success = false;
+        Transfer* dwn = itr->second;
+        emit loadFileError(dwn->messages, "SSL errors occured", false);
+        //}
+    }
+}
+
+
 QString Core::NetworkAccess::getErrorText(QNetworkReply::NetworkError code)
 {
     QString errorText("");
@@ -146,7 +174,11 @@ QString Core::NetworkAccess::getErrorText(QNetworkReply::NetworkError code)
             errorText = "Connection was closed because it timed out";
             break;
         case QNetworkReply::OperationCanceledError:
-            //this means I closed it myself by abort() or close(), don't think I need to notify here
+            //this means I closed it myself by abort() or close()
+            //I don't call them directory, but this is the error code
+            //Qt returns when it can not resume donwload after the network failure
+            //or when the download is canceled by the timout; 
+            errorText = "Connection lost";
             break;
         case QNetworkReply::SslHandshakeFailedError:
             errorText = "Security error";           //TODO need to handle sslErrors signal to get a better description here
@@ -247,6 +279,7 @@ QString Core::NetworkAccess::getErrorText(QNetworkReply::NetworkError code)
 
 void Core::NetworkAccess::onDownloadFinished()
 {
+    qDebug() << "DEBUG: DOWNLOAD FINISHED";
     QNetworkReply* rpl = static_cast<QNetworkReply*>(sender());
     QString url = rpl->url().toString();
     std::map<QString, Transfer*>::const_iterator itr = downloads.find(url);
@@ -256,11 +289,14 @@ void Core::NetworkAccess::onDownloadFinished()
         Transfer* dwn = itr->second;
         if (dwn->success) {
             qDebug() << "download success for" << url;
+            QString err;
             QStringList hops = url.split("/");
             QString fileName = hops.back();
             QString jid;
             if (dwn->messages.size() > 0) {
                 jid = dwn->messages.front().jid;
+            } else {
+                qDebug() << "An attempt to save the file but it doesn't seem to belong to any message, download is definately going to be broken";
             }
             QString path = prepareDirectory(jid);
             if (path.size() > 0) {
@@ -274,15 +310,16 @@ void Core::NetworkAccess::onDownloadFinished()
                     qDebug() << "file" << path << "was successfully downloaded";
                 } else {
                     qDebug() << "couldn't save file" << path;
-                    path = QString();
+                    err = "Error opening file to write:" + file.errorString();
                 }
+            } else {
+                err = "Couldn't prepare a directory for file";
             }
             
             if (path.size() > 0) {
                 emit downloadFileComplete(dwn->messages, path);
             } else {
-                //TODO do I need to handle the failure here or it's already being handled in error?
-                //emit loadFileError(dwn->messages, path, false);
+                emit loadFileError(dwn->messages, "Error saving file " + url + "; " + err, false);
             }
         }
         
@@ -298,6 +335,7 @@ void Core::NetworkAccess::startDownload(const std::list<Shared::MessageInfo>& ms
     QNetworkRequest req(url);
     dwn->reply = manager->get(req);
     connect(dwn->reply, &QNetworkReply::downloadProgress, this, &NetworkAccess::onDownloadProgress);
+    connect(dwn->reply, &QNetworkReply::sslErrors, this, &NetworkAccess::onDownloadSSLError);
 #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
     connect(dwn->reply, qOverload<QNetworkReply::NetworkError>(&QNetworkReply::errorOccurred), this, &NetworkAccess::onDownloadError);
 #else
@@ -317,11 +355,11 @@ void Core::NetworkAccess::onUploadError(QNetworkReply::NetworkError code)
         qDebug() << "an error uploading" << url << ": the request is reporting an error but there is no record of it being uploading, ignoring";
     } else {
         QString errorText = getErrorText(code);
-        if (errorText.size() > 0) {
+        //if (errorText.size() > 0) {
             itr->second->success = false;
             Transfer* upl = itr->second;
             emit loadFileError(upl->messages, errorText, true);
-        }
+        //}
         
         //TODO deletion?
     }
@@ -360,11 +398,13 @@ void Core::NetworkAccess::onUploadProgress(qint64 bytesReceived, qint64 bytesTot
         qDebug() << "an error downloading" << url << ": the request had some progress but seems like no one is waiting for it, skipping";
     } else {
         Transfer* upl = itr->second;
-        qreal received = bytesReceived;
-        qreal total = bytesTotal;
-        qreal progress = received/total;
-        upl->progress = progress;
-        emit loadFileProgress(upl->messages, progress, true);
+        if (upl->success) {
+            qreal received = bytesReceived;
+            qreal total = bytesTotal;
+            qreal progress = received/total;
+            upl->progress = progress;
+            emit loadFileProgress(upl->messages, progress, true);
+        }
     }
 }
 
diff --git a/core/networkaccess.h b/core/networkaccess.h
index 5b9eae2..75c189c 100644
--- a/core/networkaccess.h
+++ b/core/networkaccess.h
@@ -75,6 +75,7 @@ private:
 private slots:
     void onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal);
     void onDownloadError(QNetworkReply::NetworkError code);
+    void onDownloadSSLError(const QList<QSslError> &errors);
     void onDownloadFinished();
     void onUploadProgress(qint64 bytesReceived, qint64 bytesTotal);
     void onUploadError(QNetworkReply::NetworkError code);
diff --git a/ui/models/messagefeed.cpp b/ui/models/messagefeed.cpp
index d5fb3bc..c64d7ab 100644
--- a/ui/models/messagefeed.cpp
+++ b/ui/models/messagefeed.cpp
@@ -45,6 +45,8 @@ Models::MessageFeed::MessageFeed(const Element* ri, QObject* parent):
     syncState(incomplete),
     uploads(),
     downloads(),
+    failedDownloads(),
+    failedUploads(),
     unreadMessages(new std::set<QString>()),
     observersAmount(0)
 {
@@ -142,6 +144,18 @@ void Models::MessageFeed::changeMessage(const QString& id, const QMap<QString, Q
             }
         }
         
+        Err::const_iterator eitr = failedDownloads.find(id);
+        if (eitr != failedDownloads.end()) {
+            failedDownloads.erase(eitr);
+            changeRoles.insert(MessageRoles::Attach);
+        } else {
+            eitr = failedUploads.find(id);
+            if (eitr != failedUploads.end()) {
+                failedUploads.erase(eitr);
+                changeRoles.insert(MessageRoles::Attach);
+            }
+        }
+        
         QVector<int> cr;
         for (MessageRoles role : changeRoles) {
             cr.push_back(role);
@@ -421,6 +435,7 @@ bool Models::MessageFeed::sentByMe(const Shared::Message& msg) const
 Models::Attachment Models::MessageFeed::fillAttach(const Shared::Message& msg) const
 {
     ::Models::Attachment att;
+    QString id = msg.getId();
     
     att.localPath = msg.getAttachPath();
     att.remotePath = msg.getOutOfBandUrl();
@@ -429,22 +444,34 @@ Models::Attachment Models::MessageFeed::fillAttach(const Shared::Message& msg) c
         if (att.localPath.size() == 0) {
             att.state = none;
         } else {
-            Progress::const_iterator itr = uploads.find(msg.getId());
-            if (itr == uploads.end()) {
-                att.state = local;
+            Err::const_iterator eitr = failedUploads.find(id);
+            if (eitr != failedUploads.end()) {
+                att.state = errorUpload;
+                att.error = eitr->second;
             } else {
-                att.state = uploading;
-                att.progress = itr->second;
+                Progress::const_iterator itr = uploads.find(id);
+                if (itr == uploads.end()) {
+                    att.state = local;
+                } else {
+                    att.state = uploading;
+                    att.progress = itr->second;
+                }
             }
         }
     } else {
         if (att.localPath.size() == 0) {
-            Progress::const_iterator itr = downloads.find(msg.getId());
-            if (itr == downloads.end()) {
-                att.state = remote;
+            Err::const_iterator eitr = failedDownloads.find(id);
+            if (eitr != failedDownloads.end()) {
+                att.state = errorDownload;
+                att.error = eitr->second;
             } else {
-                att.state = downloading;
-                att.progress = itr->second;
+                Progress::const_iterator itr = downloads.find(id);
+                if (itr == downloads.end()) {
+                    att.state = remote;
+                } else {
+                    att.state = downloading;
+                    att.progress = itr->second;
+                }
             }
         } else {
             att.state = ready;
@@ -456,12 +483,19 @@ Models::Attachment Models::MessageFeed::fillAttach(const Shared::Message& msg) c
 
 void Models::MessageFeed::downloadAttachment(const QString& messageId)
 {
+    bool notify = false;
+    Err::const_iterator eitr = failedDownloads.find(messageId);
+    if (eitr != failedDownloads.end()) {
+        failedDownloads.erase(eitr);
+        notify = true;
+    }
+    
     QModelIndex ind = modelIndexById(messageId);
     if (ind.isValid()) {
         std::pair<Progress::iterator, bool> progressPair = downloads.insert(std::make_pair(messageId, 0));
         if (progressPair.second) {     //Only to take action if we weren't already downloading it
             Shared::Message* msg = static_cast<Shared::Message*>(ind.internalPointer());
-            emit dataChanged(ind, ind, {MessageRoles::Attach});
+            notify = true;
             emit fileDownloadRequest(msg->getOutOfBandUrl());
         } else {
             qDebug() << "Attachment download for message with id" << messageId << "is already in progress, skipping";
@@ -469,32 +503,55 @@ void Models::MessageFeed::downloadAttachment(const QString& messageId)
     } else {
         qDebug() << "An attempt to download an attachment for the message that doesn't exist. ID:" << messageId;
     }
-}
-
-void Models::MessageFeed::uploadAttachment(const QString& messageId)
-{
-    qDebug() << "request to upload attachment of the message" << messageId;
+    
+    if (notify) {
+        emit dataChanged(ind, ind, {MessageRoles::Attach});
+    }
 }
 
 bool Models::MessageFeed::registerUpload(const QString& messageId)
 {
-    return uploads.insert(std::make_pair(messageId, 0)).second;
+    bool success = uploads.insert(std::make_pair(messageId, 0)).second; 
+    
+    QVector<int> roles({});
+    Err::const_iterator eitr = failedUploads.find(messageId);
+    if (eitr != failedUploads.end()) {
+        failedUploads.erase(eitr);
+        roles.push_back(MessageRoles::Attach);
+    } else if (success) {
+        roles.push_back(MessageRoles::Attach);
+    }
+    
+    QModelIndex ind = modelIndexById(messageId);
+    emit dataChanged(ind, ind, roles);
+    
+    return success;
 }
 
 void Models::MessageFeed::fileProgress(const QString& messageId, qreal value, bool up)
 {
     Progress* pr = 0;
+    Err* err = 0;
     if (up) {
         pr = &uploads;
+        err = &failedUploads;
     } else {
         pr = &downloads;
+        err = &failedDownloads;
+    }
+    
+    QVector<int> roles({});
+    Err::const_iterator eitr = err->find(messageId);
+    if (eitr != err->end() && value != 1) {         //like I want to clear this state when the download is started anew
+        err->erase(eitr);
+        roles.push_back(MessageRoles::Attach);
     }
     
     Progress::iterator itr = pr->find(messageId);
     if (itr != pr->end()) {
         itr->second = value;
         QModelIndex ind = modelIndexById(messageId);
-        emit dataChanged(ind, ind);                     //the type of the attach didn't change, so, there is no need to relayout, there is no role in event
+        emit dataChanged(ind, ind, roles);
     }
 }
 
@@ -505,7 +562,29 @@ void Models::MessageFeed::fileComplete(const QString& messageId, bool up)
 
 void Models::MessageFeed::fileError(const QString& messageId, const QString& error, bool up)
 {
-    //TODO
+    Err* failed;
+    Progress* loads;
+    if (up) {
+        failed = &failedUploads;
+        loads = &uploads;
+    } else {
+        failed = &failedDownloads;
+        loads = &downloads;
+    }
+    
+    Progress::iterator pitr = loads->find(messageId);
+    if (pitr != loads->end()) {
+        loads->erase(pitr);
+    }
+    
+    std::pair<Err::iterator, bool> pair = failed->insert(std::make_pair(messageId, error));
+    if (!pair.second) {
+        pair.first->second = error;
+    }
+    QModelIndex ind = modelIndexById(messageId);
+    if (ind.isValid()) {
+        emit dataChanged(ind, ind, {MessageRoles::Attach});
+    }
 }
 
 void Models::MessageFeed::incrementObservers()
@@ -533,19 +612,18 @@ QModelIndex Models::MessageFeed::modelIndexById(const QString& id) const
 QModelIndex Models::MessageFeed::modelIndexByTime(const QString& id, const QDateTime& time) const
 {
     if (indexByTime.size() > 0) {
-        StorageByTime::const_iterator tItr = indexByTime.upper_bound(time);
-        StorageByTime::const_iterator tBeg = indexByTime.begin();
-        StorageByTime::const_iterator tEnd = indexByTime.end();
+        StorageByTime::const_iterator tItr = indexByTime.lower_bound(time);
+        StorageByTime::const_iterator tEnd = indexByTime.upper_bound(time);
         bool found = false;
-        while (tItr != tBeg) {
-            if (tItr != tEnd && id == (*tItr)->getId()) {
+        while (tItr != tEnd) {
+            if (id == (*tItr)->getId()) {
                 found = true;
                 break;
             }
-            --tItr;
+            ++tItr;
         }
         
-        if (found && tItr != tEnd && id == (*tItr)->getId()) {
+        if (found) {
             int position = indexByTime.rank(tItr);
             return createIndex(position, 0, *tItr);
         }
@@ -566,7 +644,7 @@ void Models::MessageFeed::reportLocalPathInvalid(const QString& messageId)
     
     emit localPathInvalid(msg->getAttachPath());
     
-    //gonna change the message in current model right away, to prevent spam on each attemt to draw element
+    //gonna change the message in current model right away, to prevent spam on each attempt to draw element
     QModelIndex index = modelIndexByTime(messageId, msg->getTime());
     msg->setAttachPath("");
     
diff --git a/ui/models/messagefeed.h b/ui/models/messagefeed.h
index abf67ee..efb005a 100644
--- a/ui/models/messagefeed.h
+++ b/ui/models/messagefeed.h
@@ -65,7 +65,6 @@ public:
     
     void responseArchive(const std::list<Shared::Message> list, bool last);
     void downloadAttachment(const QString& messageId);
-    void uploadAttachment(const QString& messageId);
     bool registerUpload(const QString& messageId);
     void reportLocalPathInvalid(const QString& messageId);
     
@@ -148,12 +147,16 @@ private:
     SyncState syncState;
     
     typedef std::map<QString, qreal> Progress;
+    typedef std::map<QString, QString> Err;
     Progress uploads;
     Progress downloads;
+    Err failedDownloads;
+    Err failedUploads;
     
     std::set<QString>* unreadMessages;
     uint16_t observersAmount;
     
+    
     static const QHash<int, QByteArray> roles;
 };
 
@@ -173,6 +176,7 @@ struct Attachment {
     qreal progress;
     QString localPath;
     QString remotePath;
+    QString error;
 };
 
 struct FeedItem {
diff --git a/ui/utils/feedview.cpp b/ui/utils/feedview.cpp
index 22ef4c4..5f515aa 100644
--- a/ui/utils/feedview.cpp
+++ b/ui/utils/feedview.cpp
@@ -394,16 +394,11 @@ void FeedView::setModel(QAbstractItemModel* p_model)
     }
 }
 
-void FeedView::onMessageButtonPushed(const QString& messageId, bool download)
+void FeedView::onMessageButtonPushed(const QString& messageId)
 {
     if (specialModel) {
         Models::MessageFeed* feed = static_cast<Models::MessageFeed*>(model());
-        
-        if (download) {
-            feed->downloadAttachment(messageId);
-        } else {
-            feed->uploadAttachment(messageId);
-        }
+        feed->downloadAttachment(messageId);
     }
 }
 
diff --git a/ui/utils/feedview.h b/ui/utils/feedview.h
index 0b7e7d9..f5509fd 100644
--- a/ui/utils/feedview.h
+++ b/ui/utils/feedview.h
@@ -58,7 +58,7 @@ protected slots:
     void rowsInserted(const QModelIndex & parent, int start, int end) override;
     void verticalScrollbarValueChanged(int value) override;
     void dataChanged(const QModelIndex & topLeft, const QModelIndex & bottomRight, const QVector<int> & roles) override;
-    void onMessageButtonPushed(const QString& messageId, bool download);
+    void onMessageButtonPushed(const QString& messageId);
     void onMessageInvalidPath(const QString& messageId);
     void onModelSyncStateChange(Models::MessageFeed::SyncState state);
     
diff --git a/ui/utils/messagedelegate.cpp b/ui/utils/messagedelegate.cpp
index 6b459f2..0381ae3 100644
--- a/ui/utils/messagedelegate.cpp
+++ b/ui/utils/messagedelegate.cpp
@@ -137,19 +137,39 @@ void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio
             clearHelperWidget(data);        //i can't imagine the situation where it's gonna be needed
             break;                          //but it's a possible performance problem
         case Models::uploading:
+            paintPreview(data, painter, opt);
         case Models::downloading:
             paintBar(getBar(data), painter, data.sentByMe, opt);
             break;
         case Models::remote:
-        case Models::local:
             paintButton(getButton(data), painter, data.sentByMe, opt);
             break;
         case Models::ready:
+        case Models::local:
             clearHelperWidget(data);
             paintPreview(data, painter, opt);
             break;
-        case Models::errorDownload:
-        case Models::errorUpload:
+        case Models::errorDownload: {
+            paintButton(getButton(data), painter, data.sentByMe, opt);
+            painter->setFont(dateFont);
+            QColor q = painter->pen().color();
+            q.setAlpha(180);
+            painter->setPen(q);
+            painter->drawText(opt.rect, opt.displayAlignment, data.attach.error, &rect);
+            opt.rect.adjust(0, rect.height() + textMargin, 0, 0);
+        }
+            
+            break;
+        case Models::errorUpload:{
+            clearHelperWidget(data);
+            paintPreview(data, painter, opt);
+            painter->setFont(dateFont);
+            QColor q = painter->pen().color();
+            q.setAlpha(180);
+            painter->setPen(q);
+            painter->drawText(opt.rect, opt.displayAlignment, data.attach.error, &rect);
+            opt.rect.adjust(0, rect.height() + textMargin, 0, 0);
+        }
             break;
     }
     painter->restore();
@@ -212,18 +232,24 @@ QSize MessageDelegate::sizeHint(const QStyleOptionViewItem& option, const QModel
         case Models::none:
             break;
         case Models::uploading:
+            messageSize.rheight() += calculateAttachSize(attach.localPath, messageRect).height() + textMargin;
         case Models::downloading:
             messageSize.rheight() += barHeight + textMargin;
             break;
         case Models::remote:
-        case Models::local:
             messageSize.rheight() += buttonHeight + textMargin;
             break;
         case Models::ready:
+        case Models::local:
             messageSize.rheight() += calculateAttachSize(attach.localPath, messageRect).height() + textMargin;
             break;
         case Models::errorDownload:
+            messageSize.rheight() += buttonHeight + textMargin;
+            messageSize.rheight() += dateMetrics.boundingRect(messageRect, Qt::TextWordWrap, attach.error).size().height() + textMargin;
+            break;
         case Models::errorUpload:
+            messageSize.rheight() += calculateAttachSize(attach.localPath, messageRect).height() + textMargin;
+            messageSize.rheight() += dateMetrics.boundingRect(messageRect, Qt::TextWordWrap, attach.error).size().height() + textMargin;
             break;
     }
     
@@ -356,15 +382,7 @@ QPushButton * MessageDelegate::getButton(const Models::FeedItem& data) const
     std::map<QString, FeedButton*>::const_iterator itr = buttons->find(data.id);
     FeedButton* result = 0;
     if (itr != buttons->end()) {
-        if (
-            (data.attach.state == Models::remote && itr->second->download) ||
-            (data.attach.state == Models::local && !itr->second->download)
-        ) {
-            result = itr->second;
-        } else {
-            delete itr->second;
-            buttons->erase(itr);
-        }
+        result = itr->second;
     } else {
         std::map<QString, QProgressBar*>::const_iterator barItr = bars->find(data.id);
         if (barItr != bars->end()) {
@@ -376,13 +394,7 @@ QPushButton * MessageDelegate::getButton(const Models::FeedItem& data) const
     if (result == 0) {
         result = new FeedButton();
         result->messageId = data.id;
-        if (data.attach.state == Models::remote) {
-            result->setText(QCoreApplication::translate("MessageLine", "Download"));
-            result->download = true;
-        } else {
-            result->setText(QCoreApplication::translate("MessageLine", "Upload"));
-            result->download = false;
-        }
+        result->setText(QCoreApplication::translate("MessageLine", "Download"));
         buttons->insert(std::make_pair(data.id, result));
         connect(result, &QPushButton::clicked, this, &MessageDelegate::onButtonPushed);
     }
@@ -529,7 +541,7 @@ void MessageDelegate::endClearWidgets()
 void MessageDelegate::onButtonPushed() const
 {
     FeedButton* btn = static_cast<FeedButton*>(sender());
-    emit buttonPushed(btn->messageId, btn->download);
+    emit buttonPushed(btn->messageId);
 }
 
 void MessageDelegate::clearHelperWidget(const Models::FeedItem& data) const
diff --git a/ui/utils/messagedelegate.h b/ui/utils/messagedelegate.h
index 6a257b7..3af80e1 100644
--- a/ui/utils/messagedelegate.h
+++ b/ui/utils/messagedelegate.h
@@ -55,7 +55,7 @@ public:
     void beginClearWidgets();
     
 signals:
-    void buttonPushed(const QString& messageId, bool download) const;
+    void buttonPushed(const QString& messageId) const;
     void invalidPath(const QString& messageId) const;
     
 protected:
@@ -77,7 +77,6 @@ private:
     class FeedButton : public QPushButton {
     public:
         QString messageId;
-        bool download;
     };
     
     QFont bodyFont;

From 0d584c5aba04889854838f7e06e70f9ea1e1cd40 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Sun, 16 May 2021 01:07:49 +0300
Subject: [PATCH 120/281] message preview refactor, several bugs about label
 size, animations are now playing in previews

---
 shared/global.cpp                             |  13 +-
 shared/global.h                               |   3 +-
 ui/models/CMakeLists.txt                      |   4 +-
 ui/models/element.h                           |   3 +-
 ui/utils/CMakeLists.txt                       |   8 -
 ui/widgets/CMakeLists.txt                     |   1 +
 ui/widgets/conversation.h                     |  17 +-
 ui/widgets/messageline/CMakeLists.txt         |  14 +
 .../messageline}/feedview.cpp                 |   2 +-
 ui/{utils => widgets/messageline}/feedview.h  |   4 +-
 ui/{utils => widgets/messageline}/message.cpp |   0
 ui/{utils => widgets/messageline}/message.h   |   0
 .../messageline}/messagedelegate.cpp          | 199 ++++--------
 .../messageline}/messagedelegate.h            |   6 +-
 .../messageline}/messagefeed.cpp              |   5 +-
 .../messageline}/messagefeed.h                |   0
 .../messageline}/messageline.cpp              |   0
 .../messageline}/messageline.h                |   0
 ui/widgets/messageline/preview.cpp            | 304 ++++++++++++++++++
 ui/widgets/messageline/preview.h              |  79 +++++
 20 files changed, 498 insertions(+), 164 deletions(-)
 create mode 100644 ui/widgets/messageline/CMakeLists.txt
 rename ui/{utils => widgets/messageline}/feedview.cpp (99%)
 rename ui/{utils => widgets/messageline}/feedview.h (97%)
 rename ui/{utils => widgets/messageline}/message.cpp (100%)
 rename ui/{utils => widgets/messageline}/message.h (100%)
 rename ui/{utils => widgets/messageline}/messagedelegate.cpp (73%)
 rename ui/{utils => widgets/messageline}/messagedelegate.h (94%)
 rename ui/{models => widgets/messageline}/messagefeed.cpp (99%)
 rename ui/{models => widgets/messageline}/messagefeed.h (100%)
 rename ui/{utils => widgets/messageline}/messageline.cpp (100%)
 rename ui/{utils => widgets/messageline}/messageline.h (100%)
 create mode 100644 ui/widgets/messageline/preview.cpp
 create mode 100644 ui/widgets/messageline/preview.h

diff --git a/shared/global.cpp b/shared/global.cpp
index 25a1c87..0330a00 100644
--- a/shared/global.cpp
+++ b/shared/global.cpp
@@ -127,12 +127,19 @@ Shared::Global::FileInfo Shared::Global::getFileInfo(const QString& path)
         FileInfo::Preview p = FileInfo::Preview::none;
         QSize size;
         if (big == "image") {
-            if (parts.back() == "gif") {
-                //TODO need to consider GIF as a movie
+            QMovie mov(path);
+            if (mov.isValid()) {
+                p = FileInfo::Preview::animation;
+            } else {
+                p = FileInfo::Preview::picture;
             }
-            p = FileInfo::Preview::picture;
             QImage img(path);
             size = img.size();
+//         } else if (big == "video") {
+//             p = FileInfo::Preview::movie;
+//             QMovie mov(path);
+//             size = mov.scaledSize();
+//             qDebug() << mov.isValid();
         } else {
             size = defaultIconFileInfoHeight;
         }
diff --git a/shared/global.h b/shared/global.h
index b6bbe37..03cf84d 100644
--- a/shared/global.h
+++ b/shared/global.h
@@ -33,6 +33,7 @@
 #include <QMimeDatabase>
 #include <QFileInfo>
 #include <QImage>
+#include <QMovie>
 #include <QSize>
 #include <QUrl>
 #include <QLibrary>
@@ -51,7 +52,7 @@ namespace Shared {
             enum class Preview {
                 none,
                 picture,
-                movie
+                animation
             };
             
             QString name;
diff --git a/ui/models/CMakeLists.txt b/ui/models/CMakeLists.txt
index 98ef1c3..629db32 100644
--- a/ui/models/CMakeLists.txt
+++ b/ui/models/CMakeLists.txt
@@ -13,8 +13,6 @@ target_sources(squawk PRIVATE
   group.h
   item.cpp
   item.h
-  messagefeed.cpp
-  messagefeed.h
   participant.cpp
   participant.h
   presence.cpp
@@ -25,4 +23,4 @@ target_sources(squawk PRIVATE
   room.h
   roster.cpp
   roster.h
-  )
\ No newline at end of file
+  )
diff --git a/ui/models/element.h b/ui/models/element.h
index af44791..94d67cb 100644
--- a/ui/models/element.h
+++ b/ui/models/element.h
@@ -20,7 +20,8 @@
 #define ELEMENT_H
 
 #include "item.h"
-#include "messagefeed.h"
+
+#include "ui/widgets/messageline/messagefeed.h"
 
 namespace Models {
     
diff --git a/ui/utils/CMakeLists.txt b/ui/utils/CMakeLists.txt
index 5ad5cb7..b46d30d 100644
--- a/ui/utils/CMakeLists.txt
+++ b/ui/utils/CMakeLists.txt
@@ -5,18 +5,10 @@ target_sources(squawk PRIVATE
   comboboxdelegate.h
   exponentialblur.cpp
   exponentialblur.h
-  feedview.cpp
-  feedview.h
   flowlayout.cpp
   flowlayout.h
   image.cpp
   image.h
-  message.cpp
-  message.h
-  messagedelegate.cpp
-  messagedelegate.h
-  messageline.cpp
-  messageline.h
   progress.cpp
   progress.h
   resizer.cpp
diff --git a/ui/widgets/CMakeLists.txt b/ui/widgets/CMakeLists.txt
index 0cacf6f..c7e47e0 100644
--- a/ui/widgets/CMakeLists.txt
+++ b/ui/widgets/CMakeLists.txt
@@ -21,3 +21,4 @@ target_sources(squawk PRIVATE
   )
 
 add_subdirectory(vcard)
+add_subdirectory(messageline)
diff --git a/ui/widgets/conversation.h b/ui/widgets/conversation.h
index 0b0dcb2..3f048fb 100644
--- a/ui/widgets/conversation.h
+++ b/ui/widgets/conversation.h
@@ -31,16 +31,19 @@
 
 #include "shared/message.h"
 #include "shared/order.h"
-#include "ui/models/account.h"
-#include "ui/models/roster.h"
-#include "ui/utils/flowlayout.h"
-#include "ui/utils/badge.h"
-#include "ui/utils/feedview.h"
-#include "ui/utils/messagedelegate.h"
-#include "ui/utils/shadowoverlay.h"
 #include "shared/icons.h"
 #include "shared/utils.h"
 
+#include "ui/models/account.h"
+#include "ui/models/roster.h"
+
+#include "ui/utils/flowlayout.h"
+#include "ui/utils/badge.h"
+#include "ui/utils/shadowoverlay.h"
+
+#include "ui/widgets/messageline/feedview.h"
+#include "ui/widgets/messageline/messagedelegate.h"
+
 namespace Ui
 {
 class Conversation;
diff --git a/ui/widgets/messageline/CMakeLists.txt b/ui/widgets/messageline/CMakeLists.txt
new file mode 100644
index 0000000..7cace9d
--- /dev/null
+++ b/ui/widgets/messageline/CMakeLists.txt
@@ -0,0 +1,14 @@
+target_sources(squawk PRIVATE
+  messagedelegate.cpp
+  messagedelegate.h
+  #messageline.cpp
+  #messageline.h
+  preview.cpp
+  preview.h
+  messagefeed.cpp
+  messagefeed.h
+  feedview.cpp
+  feedview.h
+  #message.cpp
+  #message.h
+  )
diff --git a/ui/utils/feedview.cpp b/ui/widgets/messageline/feedview.cpp
similarity index 99%
rename from ui/utils/feedview.cpp
rename to ui/widgets/messageline/feedview.cpp
index 5f515aa..6d8c180 100644
--- a/ui/utils/feedview.cpp
+++ b/ui/widgets/messageline/feedview.cpp
@@ -24,7 +24,7 @@
 #include <QDebug>
 
 #include "messagedelegate.h"
-#include "ui/models/messagefeed.h"
+#include "messagefeed.h"
 
 constexpr int maxMessageHeight = 10000;
 constexpr int approximateSingleMessageHeight = 20;
diff --git a/ui/utils/feedview.h b/ui/widgets/messageline/feedview.h
similarity index 97%
rename from ui/utils/feedview.h
rename to ui/widgets/messageline/feedview.h
index f5509fd..b20276c 100644
--- a/ui/utils/feedview.h
+++ b/ui/widgets/messageline/feedview.h
@@ -24,8 +24,8 @@
 #include <deque>
 #include <set>
 
-#include <ui/models/messagefeed.h>
-#include "progress.h"
+#include <ui/widgets/messageline/messagefeed.h>
+#include <ui/utils/progress.h>
 
 /**
  * @todo write docs
diff --git a/ui/utils/message.cpp b/ui/widgets/messageline/message.cpp
similarity index 100%
rename from ui/utils/message.cpp
rename to ui/widgets/messageline/message.cpp
diff --git a/ui/utils/message.h b/ui/widgets/messageline/message.h
similarity index 100%
rename from ui/utils/message.h
rename to ui/widgets/messageline/message.h
diff --git a/ui/utils/messagedelegate.cpp b/ui/widgets/messageline/messagedelegate.cpp
similarity index 73%
rename from ui/utils/messagedelegate.cpp
rename to ui/widgets/messageline/messagedelegate.cpp
index 0381ae3..8405964 100644
--- a/ui/utils/messagedelegate.cpp
+++ b/ui/widgets/messageline/messagedelegate.cpp
@@ -22,13 +22,12 @@
 #include <QMouseEvent>
 
 #include "messagedelegate.h"
-#include "ui/models/messagefeed.h"
+#include "messagefeed.h"
 
 constexpr int avatarHeight = 50;
 constexpr int margin = 6;
 constexpr int textMargin = 2;
 constexpr int statusIconSize = 16;
-constexpr int maxAttachmentHeight = 500;
 
 MessageDelegate::MessageDelegate(QObject* parent):
     QStyledItemDelegate(parent),
@@ -44,6 +43,7 @@ MessageDelegate::MessageDelegate(QObject* parent):
     bars(new std::map<QString, QProgressBar*>()),
     statusIcons(new std::map<QString, QLabel*>()),
     bodies(new std::map<QString, QLabel*>()),
+    previews(new std::map<QString, Preview*>()),
     idsToKeep(new std::set<QString>()),
     clearingWidgets(false)
 {
@@ -72,10 +72,15 @@ MessageDelegate::~MessageDelegate()
         delete pair.second;
     }
     
+    for (const std::pair<const QString, Preview*>& pair: *previews){
+        delete pair.second;
+    }
+    
     delete idsToKeep;
     delete buttons;
     delete bars;
     delete bodies;
+    delete previews;
 }
 
 void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
@@ -151,24 +156,14 @@ void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio
             break;
         case Models::errorDownload: {
             paintButton(getButton(data), painter, data.sentByMe, opt);
-            painter->setFont(dateFont);
-            QColor q = painter->pen().color();
-            q.setAlpha(180);
-            painter->setPen(q);
-            painter->drawText(opt.rect, opt.displayAlignment, data.attach.error, &rect);
-            opt.rect.adjust(0, rect.height() + textMargin, 0, 0);
+            paintComment(data, painter, opt);
         }
             
             break;
         case Models::errorUpload:{
             clearHelperWidget(data);
             paintPreview(data, painter, opt);
-            painter->setFont(dateFont);
-            QColor q = painter->pen().color();
-            q.setAlpha(180);
-            painter->setPen(q);
-            painter->drawText(opt.rect, opt.displayAlignment, data.attach.error, &rect);
-            opt.rect.adjust(0, rect.height() + textMargin, 0, 0);
+            paintComment(data, painter, opt);
         }
             break;
     }
@@ -181,6 +176,8 @@ void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio
         body->setParent(vp);
         body->setMaximumWidth(bodySize.width());
         body->setMinimumWidth(bodySize.width());
+        body->setMinimumHeight(bodySize.height());
+        body->setMaximumHeight(bodySize.height());
         body->setAlignment(opt.displayAlignment);
         messageLeft = opt.rect.x();
         if (data.sentByMe) {
@@ -232,7 +229,7 @@ QSize MessageDelegate::sizeHint(const QStyleOptionViewItem& option, const QModel
         case Models::none:
             break;
         case Models::uploading:
-            messageSize.rheight() += calculateAttachSize(attach.localPath, messageRect).height() + textMargin;
+            messageSize.rheight() += Preview::calculateAttachSize(attach.localPath, messageRect).height() + textMargin;
         case Models::downloading:
             messageSize.rheight() += barHeight + textMargin;
             break;
@@ -241,14 +238,14 @@ QSize MessageDelegate::sizeHint(const QStyleOptionViewItem& option, const QModel
             break;
         case Models::ready:
         case Models::local:
-            messageSize.rheight() += calculateAttachSize(attach.localPath, messageRect).height() + textMargin;
+            messageSize.rheight() += Preview::calculateAttachSize(attach.localPath, messageRect).height() + textMargin;
             break;
         case Models::errorDownload:
             messageSize.rheight() += buttonHeight + textMargin;
             messageSize.rheight() += dateMetrics.boundingRect(messageRect, Qt::TextWordWrap, attach.error).size().height() + textMargin;
             break;
         case Models::errorUpload:
-            messageSize.rheight() += calculateAttachSize(attach.localPath, messageRect).height() + textMargin;
+            messageSize.rheight() += Preview::calculateAttachSize(attach.localPath, messageRect).height() + textMargin;
             messageSize.rheight() += dateMetrics.boundingRect(messageRect, Qt::TextWordWrap, attach.error).size().height() + textMargin;
             break;
     }
@@ -319,6 +316,17 @@ void MessageDelegate::paintButton(QPushButton* btn, QPainter* painter, bool sent
     option.rect.adjust(0, buttonHeight + textMargin, 0, 0);
 }
 
+void MessageDelegate::paintComment(const Models::FeedItem& data, QPainter* painter, QStyleOptionViewItem& option) const
+{
+    painter->setFont(dateFont);
+    QColor q = painter->pen().color();
+    q.setAlpha(180);
+    painter->setPen(q);
+    QRect rect;
+    painter->drawText(option.rect, option.displayAlignment, data.attach.error, &rect);
+    option.rect.adjust(0, rect.height() + textMargin, 0, 0);
+}
+
 void MessageDelegate::paintBar(QProgressBar* bar, QPainter* painter, bool sentByMe, QStyleOptionViewItem& option) const
 {
     QPoint start = option.rect.topLeft();
@@ -332,49 +340,20 @@ void MessageDelegate::paintBar(QProgressBar* bar, QPainter* painter, bool sentBy
 
 void MessageDelegate::paintPreview(const Models::FeedItem& data, QPainter* painter, QStyleOptionViewItem& option) const
 {
-    Shared::Global::FileInfo info = Shared::Global::getFileInfo(data.attach.localPath);
-    QSize size = constrainAttachSize(info.size, option.rect.size());
-    
-    QPoint start;
-    if (data.sentByMe) {
-        start = {option.rect.width() - size.width(), option.rect.top()};
-        start.rx() += margin;
+    Preview* preview = 0;
+    std::map<QString, Preview*>::iterator itr = previews->find(data.id);
+
+    QSize size = option.rect.size();
+    if (itr != previews->end()) {
+        preview = itr->second;
+        preview->actualize(data.attach.localPath, size, option.rect.topLeft());
     } else {
-        start = option.rect.topLeft();
-    }
-    QRect rect(start, size);
-    switch (info.preview) {
-        case Shared::Global::FileInfo::Preview::picture: {
-            QImage img(data.attach.localPath);
-            if (img.isNull()) {
-                emit invalidPath(data.id);
-            } else {
-                painter->drawImage(rect, img);
-            }
-        }
-        break;
-        default: {
-            QIcon icon = QIcon::fromTheme(info.mime.iconName());
-            
-            painter->save();
-            
-            painter->setFont(bodyFont);
-            int labelWidth = option.rect.width() - size.width() - margin;
-            QString elidedName = bodyMetrics.elidedText(info.name, Qt::ElideMiddle, labelWidth);
-            QSize nameSize = bodyMetrics.boundingRect(QRect(start, QSize(labelWidth, 0)), 0, elidedName).size();
-            if (data.sentByMe) {
-                start.rx() -= nameSize.width() + margin;
-            }
-            painter->drawPixmap({start, size}, icon.pixmap(info.size));
-            start.rx() += size.width() + margin;
-            start.ry() += nameSize.height() + (size.height() - nameSize.height()) / 2;
-            painter->drawText(start, elidedName);
-    
-            painter->restore();
-        }
+        QWidget* vp = static_cast<QWidget*>(painter->device());
+        preview = new Preview(data.attach.localPath, size, option.rect.topLeft(), data.sentByMe, vp);
+        previews->insert(std::make_pair(data.id, preview));
     }
     
-    option.rect.adjust(0, size.height() + textMargin, 0, 0);
+    option.rect.adjust(0, preview->size().height() + textMargin, 0, 0);
 }
 
 QPushButton * MessageDelegate::getButton(const Models::FeedItem& data) const
@@ -432,6 +411,13 @@ QLabel * MessageDelegate::getStatusIcon(const Models::FeedItem& data) const
     std::map<QString, QLabel*>::const_iterator itr = statusIcons->find(data.id);
     QLabel* result = 0;
     
+    if (itr != statusIcons->end()) {
+        result = itr->second;
+    } else {
+        result = new QLabel();
+        statusIcons->insert(std::make_pair(data.id, result));
+    }
+    
     QIcon q(Shared::icon(Shared::messageStateThemeIcons[static_cast<uint8_t>(data.state)]));
     QString tt = Shared::Global::getName(data.state);
     if (data.state == Shared::Message::State::error) {
@@ -439,25 +425,11 @@ QLabel * MessageDelegate::getStatusIcon(const Models::FeedItem& data) const
             tt += ": " + data.error;
         }
     }
-    
-    if (itr != statusIcons->end()) {
-        result = itr->second;
-        if (result->toolTip() != tt) {                      //If i just assign pixmap every time unconditionally
-            result->setPixmap(q.pixmap(statusIconSize));    //it involves into an infinite cycle of repaint
-            result->setToolTip(tt);                         //may be it's better to subclass and store last condition in int?
-        }
-    } else {
-        result = new QLabel();
-        statusIcons->insert(std::make_pair(data.id, result));
-        result->setPixmap(q.pixmap(statusIconSize));
-        result->setToolTip(tt);
+    if (result->toolTip() != tt) {                      //If i just assign pixmap every time unconditionally
+        result->setPixmap(q.pixmap(statusIconSize));    //it invokes an infinite cycle of repaint
+        result->setToolTip(tt);                         //may be it's better to subclass and store last condition in int?
     }
     
-    
-    
-    result->setToolTip(tt);
-    //result->setText(std::to_string((int)data.state).c_str());
-    
     return result;
 }
 
@@ -488,50 +460,28 @@ void MessageDelegate::beginClearWidgets()
     clearingWidgets = true;
 }
 
+template <typename T>
+void removeElements(std::map<QString, T*>* elements, std::set<QString>* idsToKeep) {
+    std::set<QString> toRemove;
+    for (const std::pair<const QString, T*>& pair: *elements) {
+        if (idsToKeep->find(pair.first) == idsToKeep->end()) {
+            delete pair.second;
+            toRemove.insert(pair.first);
+        }
+    }
+    for (const QString& key : toRemove) {
+        elements->erase(key);
+    }
+}
+
 void MessageDelegate::endClearWidgets()
 {
     if (clearingWidgets) {
-        std::set<QString> toRemoveButtons;
-        std::set<QString> toRemoveBars;
-        std::set<QString> toRemoveIcons;
-        std::set<QString> toRemoveBodies;
-        for (const std::pair<const QString, FeedButton*>& pair: *buttons) {
-            if (idsToKeep->find(pair.first) == idsToKeep->end()) {
-                delete pair.second;
-                toRemoveButtons.insert(pair.first);
-            }
-        }
-        for (const std::pair<const QString, QProgressBar*>& pair: *bars) {
-            if (idsToKeep->find(pair.first) == idsToKeep->end()) {
-                delete pair.second;
-                toRemoveBars.insert(pair.first);
-            }
-        }
-        for (const std::pair<const QString, QLabel*>& pair: *statusIcons) {
-            if (idsToKeep->find(pair.first) == idsToKeep->end()) {
-                delete pair.second;
-                toRemoveIcons.insert(pair.first);
-            }
-        }
-        for (const std::pair<const QString, QLabel*>& pair: *bodies) {
-            if (idsToKeep->find(pair.first) == idsToKeep->end()) {
-                delete pair.second;
-                toRemoveBodies.insert(pair.first);
-            }
-        }
-        
-        for (const QString& key : toRemoveButtons) {
-            buttons->erase(key);
-        }
-        for (const QString& key : toRemoveBars) {
-            bars->erase(key);
-        }
-        for (const QString& key : toRemoveIcons) {
-            statusIcons->erase(key);
-        }
-        for (const QString& key : toRemoveBodies) {
-            bodies->erase(key);
-        }
+        removeElements(buttons, idsToKeep);
+        removeElements(bars, idsToKeep);
+        removeElements(statusIcons, idsToKeep);
+        removeElements(bodies, idsToKeep);
+        removeElements(previews, idsToKeep);
         
         idsToKeep->clear();
         clearingWidgets = false;
@@ -559,25 +509,6 @@ void MessageDelegate::clearHelperWidget(const Models::FeedItem& data) const
     }
 }
 
-QSize MessageDelegate::calculateAttachSize(const QString& path, const QRect& bounds) const
-{
-    Shared::Global::FileInfo info = Shared::Global::getFileInfo(path);
-    
-    return constrainAttachSize(info.size, bounds.size());
-}
-
-QSize MessageDelegate::constrainAttachSize(QSize src, QSize bounds) const
-{
-    bounds.setHeight(maxAttachmentHeight);
-    
-    if (src.width() > bounds.width() || src.height() > bounds.height()) {
-        src.scale(bounds, Qt::KeepAspectRatio);
-    }
-    
-    return src;
-}
-
-
 // void MessageDelegate::setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const
 // {
 //     
diff --git a/ui/utils/messagedelegate.h b/ui/widgets/messageline/messagedelegate.h
similarity index 94%
rename from ui/utils/messagedelegate.h
rename to ui/widgets/messageline/messagedelegate.h
index 3af80e1..5c2989d 100644
--- a/ui/utils/messagedelegate.h
+++ b/ui/widgets/messageline/messagedelegate.h
@@ -34,6 +34,8 @@
 #include "shared/global.h"
 #include "shared/utils.h"
 
+#include "preview.h"
+
 namespace Models {
     struct FeedItem;
 };
@@ -62,13 +64,12 @@ protected:
     void paintButton(QPushButton* btn, QPainter* painter, bool sentByMe, QStyleOptionViewItem& option) const;
     void paintBar(QProgressBar* bar, QPainter* painter, bool sentByMe, QStyleOptionViewItem& option) const;
     void paintPreview(const Models::FeedItem& data, QPainter* painter, QStyleOptionViewItem& option) const;
+    void paintComment(const Models::FeedItem& data, QPainter* painter, QStyleOptionViewItem& option) const;
     QPushButton* getButton(const Models::FeedItem& data) const;
     QProgressBar* getBar(const Models::FeedItem& data) const;
     QLabel* getStatusIcon(const Models::FeedItem& data) const;
     QLabel* getBody(const Models::FeedItem& data) const;
     void clearHelperWidget(const Models::FeedItem& data) const;
-    QSize calculateAttachSize(const QString& path, const QRect& bounds) const;
-    QSize constrainAttachSize(QSize src, QSize bounds) const;
     
 protected slots:
     void onButtonPushed() const;
@@ -93,6 +94,7 @@ private:
     std::map<QString, QProgressBar*>* bars;
     std::map<QString, QLabel*>* statusIcons;
     std::map<QString, QLabel*>* bodies;
+    std::map<QString, Preview*>* previews;
     std::set<QString>* idsToKeep;
     bool clearingWidgets;
     
diff --git a/ui/models/messagefeed.cpp b/ui/widgets/messageline/messagefeed.cpp
similarity index 99%
rename from ui/models/messagefeed.cpp
rename to ui/widgets/messageline/messagefeed.cpp
index c64d7ab..4f22113 100644
--- a/ui/models/messagefeed.cpp
+++ b/ui/widgets/messageline/messagefeed.cpp
@@ -17,8 +17,9 @@
  */
 
 #include "messagefeed.h"
-#include "element.h"
-#include "room.h"
+
+#include <ui/models/element.h>
+#include <ui/models/room.h>
 
 #include <QDebug>
 
diff --git a/ui/models/messagefeed.h b/ui/widgets/messageline/messagefeed.h
similarity index 100%
rename from ui/models/messagefeed.h
rename to ui/widgets/messageline/messagefeed.h
diff --git a/ui/utils/messageline.cpp b/ui/widgets/messageline/messageline.cpp
similarity index 100%
rename from ui/utils/messageline.cpp
rename to ui/widgets/messageline/messageline.cpp
diff --git a/ui/utils/messageline.h b/ui/widgets/messageline/messageline.h
similarity index 100%
rename from ui/utils/messageline.h
rename to ui/widgets/messageline/messageline.h
diff --git a/ui/widgets/messageline/preview.cpp b/ui/widgets/messageline/preview.cpp
new file mode 100644
index 0000000..8c56cbc
--- /dev/null
+++ b/ui/widgets/messageline/preview.cpp
@@ -0,0 +1,304 @@
+/*
+ * Squawk messenger. 
+ * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+ * 
+ * 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 "preview.h"
+
+
+constexpr int margin = 6;
+constexpr int maxAttachmentHeight = 500;
+
+bool Preview::fontInitialized = false;
+QFont Preview::font;
+QFontMetrics Preview::metrics(Preview::font);
+
+Preview::Preview(const QString& pPath, const QSize& pMaxSize, const QPoint& pos, bool pRight, QWidget* pParent):
+    info(Shared::Global::getFileInfo(pPath)),
+    path(pPath),
+    maxSize(pMaxSize),
+    actualSize(constrainAttachSize(info.size, maxSize)),
+    cachedLabelSize(0, 0),
+    position(pos),
+    widget(0),
+    label(0),
+    parent(pParent),
+    movie(0),
+    fileReachable(true),
+    actualPreview(false),
+    right(pRight)
+{
+    if (!fontInitialized) {
+        font.setBold(true);
+        font.setPixelSize(14);
+        metrics = QFontMetrics(font);
+        fontInitialized = true;
+    }
+    
+    initializeElements();
+    if (fileReachable) {
+        positionElements();
+    }
+}
+
+Preview::~Preview()
+{
+    clean();
+}
+
+void Preview::clean()
+{
+    if (fileReachable) {
+        if (info.preview == Shared::Global::FileInfo::Preview::animation) {
+            delete movie;
+        }
+        delete widget;
+        if (!actualPreview) {
+            delete label;
+        } else {
+            actualPreview = false;
+        }
+    } else {
+        fileReachable = true;
+    }
+}
+
+void Preview::actualize(const QString& newPath, const QSize& newSize, const QPoint& newPoint)
+{
+    bool positionChanged = false;
+    bool sizeChanged = false;
+    bool maxSizeChanged = false;
+    
+    if (maxSize != newSize) {
+        maxSize = newSize;
+        maxSizeChanged = true;
+        QSize ns = constrainAttachSize(info.size, maxSize);
+        if (actualSize != ns) {
+            sizeChanged = true;
+            actualSize = ns;
+        }
+    }
+    if (position != newPoint) {
+        position = newPoint;
+        positionChanged = true;
+    }
+    
+    if (!setPath(newPath) && fileReachable) {
+        if (sizeChanged) {
+            applyNewSize();
+            if (maxSizeChanged && !actualPreview) {
+                applyNewMaxSize();
+            }
+        } else if (maxSizeChanged) {
+            applyNewMaxSize();
+        }
+        if (positionChanged || !actualPreview) {
+            positionElements();
+        }
+    }
+}
+
+void Preview::setSize(const QSize& newSize)
+{
+    bool sizeChanged = false;
+    bool maxSizeChanged = false;
+    
+    if (maxSize != newSize) {
+        maxSize = newSize;
+        maxSizeChanged = true;
+        QSize ns = constrainAttachSize(info.size, maxSize);
+        if (actualSize != ns) {
+            sizeChanged = true;
+            actualSize = ns;
+        }
+    }
+    
+    if (fileReachable) {
+        if (sizeChanged) {
+            applyNewSize();
+        }
+        if (maxSizeChanged || !actualPreview) {
+            applyNewMaxSize();
+        }
+    }
+}
+
+void Preview::applyNewSize()
+{
+    switch (info.preview) {
+        case Shared::Global::FileInfo::Preview::picture: {
+            QPixmap img(path);
+            if (img.isNull()) {
+                fileReachable = false;
+            } else {
+                img = img.scaled(actualSize, Qt::KeepAspectRatio);
+                widget->resize(actualSize);
+                widget->setPixmap(img);
+            }
+        }
+            break;
+        case Shared::Global::FileInfo::Preview::animation:{
+            movie->setScaledSize(actualSize);
+            widget->resize(actualSize);
+        }
+            break;
+        default: {
+            QIcon icon = QIcon::fromTheme(info.mime.iconName());
+            widget->setPixmap(icon.pixmap(actualSize));
+            widget->resize(actualSize);
+        }
+    }
+}
+
+void Preview::applyNewMaxSize()
+{
+    switch (info.preview) {
+        case Shared::Global::FileInfo::Preview::picture: 
+        case Shared::Global::FileInfo::Preview::animation: 
+            break;
+        default: {
+            int labelWidth = maxSize.width() - actualSize.width() - margin;
+            QString elidedName = metrics.elidedText(info.name, Qt::ElideMiddle, labelWidth);
+            cachedLabelSize = metrics.size(0, elidedName);
+            label->setText(elidedName);
+            label->resize(cachedLabelSize);
+        }
+    }
+}
+
+
+QSize Preview::size() const
+{
+    if (actualPreview) {
+        return actualSize;
+    } else {
+        return QSize(actualSize.width() + margin + cachedLabelSize.width(), actualSize.height());
+    }
+}
+
+bool Preview::isFileReachable() const
+{
+    return fileReachable;
+}
+
+void Preview::setPosition(const QPoint& newPoint)
+{
+    if (position != newPoint) {
+        position = newPoint;
+        if (fileReachable) {
+            positionElements();
+        }
+    }
+}
+
+bool Preview::setPath(const QString& newPath)
+{
+    if (path != newPath) {
+        path = newPath;
+        info = Shared::Global::getFileInfo(path);
+        actualSize = constrainAttachSize(info.size, maxSize);
+        clean();
+        initializeElements();
+        if (fileReachable) {
+            positionElements();
+        }
+        return true;
+    } else {
+        return false;
+    }
+}
+
+void Preview::initializeElements()
+{
+    switch (info.preview) {
+        case Shared::Global::FileInfo::Preview::picture: {
+            QPixmap img(path);
+            if (img.isNull()) {
+                fileReachable = false;
+            } else {
+                actualPreview = true;
+                img = img.scaled(actualSize, Qt::KeepAspectRatio);
+                widget = new QLabel(parent);
+                widget->setPixmap(img);
+                widget->show();
+            }
+        }
+            break;
+        case Shared::Global::FileInfo::Preview::animation:{
+            movie = new QMovie(path);
+            if (!movie->isValid()) {
+                fileReachable = false;
+                delete movie;
+            } else {
+                actualPreview = true;
+                movie->setScaledSize(actualSize);
+                widget = new QLabel(parent);
+                widget->setMovie(movie);
+                movie->start();
+                widget->show();
+            }
+        }
+            break;
+        default: {
+            QIcon icon = QIcon::fromTheme(info.mime.iconName());
+            widget = new QLabel(parent);
+            widget->setPixmap(icon.pixmap(actualSize));
+            widget->show();
+            
+            label = new QLabel(parent);
+            label->setFont(font);
+            int labelWidth = maxSize.width() - actualSize.width() - margin;
+            QString elidedName = metrics.elidedText(info.name, Qt::ElideMiddle, labelWidth);
+            cachedLabelSize = metrics.size(0, elidedName);
+            label->setText(elidedName);
+            label->show();
+        }
+    }
+}
+
+void Preview::positionElements()
+{
+    int start = position.x();
+    if (right) {
+        start += maxSize.width() - size().width();
+    }
+    widget->move(start, position.y());
+    if (!actualPreview) {
+        int x = start + actualSize.width() + margin;
+        int y = position.y() + (actualSize.height() - cachedLabelSize.height()) / 2;
+        label->move(x, y);
+    }
+}
+
+QSize Preview::calculateAttachSize(const QString& path, const QRect& bounds)
+{
+    Shared::Global::FileInfo info = Shared::Global::getFileInfo(path);
+    
+    return constrainAttachSize(info.size, bounds.size());
+}
+
+QSize Preview::constrainAttachSize(QSize src, QSize bounds)
+{
+    if (bounds.height() > maxAttachmentHeight) {
+        bounds.setHeight(maxAttachmentHeight);
+    }
+    
+    if (src.width() > bounds.width() || src.height() > bounds.height()) {
+        src.scale(bounds, Qt::KeepAspectRatio);
+    }
+    
+    return src;
+}
diff --git a/ui/widgets/messageline/preview.h b/ui/widgets/messageline/preview.h
new file mode 100644
index 0000000..3d560d3
--- /dev/null
+++ b/ui/widgets/messageline/preview.h
@@ -0,0 +1,79 @@
+/*
+ * Squawk messenger. 
+ * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+ * 
+ * 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/>.
+ */
+
+#ifndef PREVIEW_H
+#define PREVIEW_H
+
+#include <QWidget>
+#include <QString>
+#include <QPoint>
+#include <QSize>
+#include <QLabel>
+#include <QIcon>
+#include <QPixmap>
+#include <QMovie>
+#include <QFont>
+#include <QFontMetrics>
+
+#include <shared/global.h>
+
+/**
+ * @todo write docs
+ */
+class Preview {
+public:
+    Preview(const QString& pPath, const QSize& pMaxSize, const QPoint& pos, bool pRight, QWidget* parent);
+    ~Preview();
+    
+    void actualize(const QString& newPath, const QSize& newSize, const QPoint& newPoint);
+    void setPosition(const QPoint& newPoint);
+    void setSize(const QSize& newSize);
+    bool setPath(const QString& newPath);
+    bool isFileReachable() const;
+    QSize size() const;
+    
+    static QSize constrainAttachSize(QSize src, QSize bounds);
+    static QSize calculateAttachSize(const QString& path, const QRect& bounds);
+    static bool fontInitialized;
+    static QFont font;
+    static QFontMetrics metrics;
+    
+private:
+    void initializeElements();
+    void positionElements();
+    void clean();
+    void applyNewSize();
+    void applyNewMaxSize();
+    
+private:
+    Shared::Global::FileInfo info;
+    QString path;
+    QSize maxSize;
+    QSize actualSize;
+    QSize cachedLabelSize;
+    QPoint position;
+    QLabel* widget;
+    QLabel* label;
+    QWidget* parent;
+    QMovie* movie;
+    bool fileReachable;
+    bool actualPreview;
+    bool right;
+};
+
+#endif // PREVIEW_H

From 721f6daa3653a5a59d75a4b11ad3ff8cf5cbe6ad Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Mon, 17 May 2021 00:52:59 +0300
Subject: [PATCH 121/281] fix bug when everything was treated as animation, bug
 with not working group amount of messages, handled the situation when preview
 is painted but the file was lost

---
 ui/models/account.cpp                      | 18 +++++++++++++++---
 ui/models/account.h                        |  4 ++++
 ui/models/contact.cpp                      |  6 ++++++
 ui/models/contact.h                        |  2 ++
 ui/models/reference.cpp                    |  2 ++
 ui/models/roster.cpp                       | 14 ++++++++++++++
 ui/models/roster.h                         |  1 +
 ui/widgets/messageline/messagedelegate.cpp |  4 ++++
 ui/widgets/messageline/messagefeed.cpp     | 14 +++++++++++---
 ui/widgets/messageline/messagefeed.h       |  5 +++--
 10 files changed, 62 insertions(+), 8 deletions(-)

diff --git a/ui/models/account.cpp b/ui/models/account.cpp
index f8d0c37..43cb3ed 100644
--- a/ui/models/account.cpp
+++ b/ui/models/account.cpp
@@ -31,7 +31,8 @@ Models::Account::Account(const QMap<QString, QVariant>& data, Models::Item* pare
     avatarPath(data.value("avatarPath").toString()),
     state(Shared::ConnectionState::disconnected),
     availability(Shared::Availability::offline),
-    passwordType(Shared::AccountPassword::plain)
+    passwordType(Shared::AccountPassword::plain),
+    wasEverConnected(false)
 {
     QMap<QString, QVariant>::const_iterator sItr = data.find("state");
     if (sItr != data.end()) {
@@ -56,8 +57,19 @@ void Models::Account::setState(Shared::ConnectionState p_state)
     if (state != p_state) {
         state = p_state;
         changed(2);
-        if (state == Shared::ConnectionState::disconnected) {
-            toOfflineState();
+        switch (state) {
+            case Shared::ConnectionState::disconnected:
+                toOfflineState();
+                break;
+            case Shared::ConnectionState::connected:
+                if (wasEverConnected) {
+                    emit reconnected();
+                } else {
+                    wasEverConnected = true;
+                }
+                break;
+            default:
+                break;
         }
     }
 }
diff --git a/ui/models/account.h b/ui/models/account.h
index 686d4da..3d2310f 100644
--- a/ui/models/account.h
+++ b/ui/models/account.h
@@ -77,6 +77,9 @@ namespace Models {
         QString getBareJid() const;
         QString getFullJid() const;
         
+    signals:
+        void reconnected();
+        
     private:
         QString login;
         QString password;
@@ -87,6 +90,7 @@ namespace Models {
         Shared::ConnectionState state;
         Shared::Availability availability;
         Shared::AccountPassword passwordType;
+        bool wasEverConnected;
         
     protected slots:
         void toOfflineState() override;
diff --git a/ui/models/contact.cpp b/ui/models/contact.cpp
index d54fccf..a0c70ac 100644
--- a/ui/models/contact.cpp
+++ b/ui/models/contact.cpp
@@ -240,3 +240,9 @@ QString Models::Contact::getDisplayedName() const
     return getContactName();
 }
 
+void Models::Contact::handleRecconnect()
+{
+    if (getMessagesCount() > 0) {
+        feed->requestLatestMessages();
+    }
+}
diff --git a/ui/models/contact.h b/ui/models/contact.h
index 7e76f5b..a8b80a3 100644
--- a/ui/models/contact.h
+++ b/ui/models/contact.h
@@ -56,6 +56,8 @@ public:
     QString getStatus() const;
     QString getDisplayedName() const override;
     
+    void handleRecconnect();        //this is a special method Models::Roster calls when reconnect happens
+    
 protected:
     void _removeChild(int index) override;
     void _appendChild(Models::Item * child) override;
diff --git a/ui/models/reference.cpp b/ui/models/reference.cpp
index cb8efad..1aaea15 100644
--- a/ui/models/reference.cpp
+++ b/ui/models/reference.cpp
@@ -104,6 +104,8 @@ void Models::Reference::onChildChanged(Models::Item* item, int row, int col)
 {
     if (item == original) {
         emit childChanged(this, row, col);
+    } else {
+        emit childChanged(item, row, col);
     }
 }
 
diff --git a/ui/models/roster.cpp b/ui/models/roster.cpp
index d70d9d1..2d5f99f 100644
--- a/ui/models/roster.cpp
+++ b/ui/models/roster.cpp
@@ -48,6 +48,7 @@ Models::Roster::~Roster()
 void Models::Roster::addAccount(const QMap<QString, QVariant>& data)
 {
     Account* acc = new Account(data);
+    connect(acc, &Account::reconnected, this, &Roster::onAccountReconnected);
     root->appendChild(acc);
     accounts.insert(std::make_pair(acc->getName(), acc));
     accountsModel->addAccount(acc);
@@ -744,6 +745,7 @@ void Models::Roster::removeAccount(const QString& account)
         }
     }
     
+    disconnect(acc, &Account::reconnected, this, &Roster::onAccountReconnected);
     acc->deleteLater();
 }
 
@@ -1003,3 +1005,15 @@ Models::Element * Models::Roster::getElement(const Models::Roster::ElId& id)
     return NULL;
 }
 
+void Models::Roster::onAccountReconnected()
+{
+    Account* acc = static_cast<Account*>(sender());
+    
+    QString accName = acc->getName();
+    for (const std::pair<const ElId, Contact*>& pair : contacts) {
+        if (pair.first.account == accName) {
+            pair.second->handleRecconnect();
+        }
+    }
+}
+
diff --git a/ui/models/roster.h b/ui/models/roster.h
index 09261cd..08d5afc 100644
--- a/ui/models/roster.h
+++ b/ui/models/roster.h
@@ -100,6 +100,7 @@ private:
     
 private slots:
     void onAccountDataChanged(const QModelIndex& tl, const QModelIndex& br, const QVector<int>& roles);
+    void onAccountReconnected();
     void onChildChanged(Models::Item* item, int row, int col);
     void onChildIsAboutToBeInserted(Item* parent, int first, int last);
     void onChildInserted();
diff --git a/ui/widgets/messageline/messagedelegate.cpp b/ui/widgets/messageline/messagedelegate.cpp
index 8405964..81018ac 100644
--- a/ui/widgets/messageline/messagedelegate.cpp
+++ b/ui/widgets/messageline/messagedelegate.cpp
@@ -353,6 +353,10 @@ void MessageDelegate::paintPreview(const Models::FeedItem& data, QPainter* paint
         previews->insert(std::make_pair(data.id, preview));
     }
     
+    if (!preview->isFileReachable()) {      //this is the situation when the file preview couldn't be painted because the file was moved 
+        emit invalidPath(data.id);          //or deleted. This signal notifies the model, and the model notifies the core, preview can 
+    }                                       //handle being invalid for as long as I need and can be even become valid again with a new path
+    
     option.rect.adjust(0, preview->size().height() + textMargin, 0, 0);
 }
 
diff --git a/ui/widgets/messageline/messagefeed.cpp b/ui/widgets/messageline/messagefeed.cpp
index 4f22113..9537ea5 100644
--- a/ui/widgets/messageline/messagefeed.cpp
+++ b/ui/widgets/messageline/messagefeed.cpp
@@ -304,7 +304,7 @@ QVariant Models::MessageFeed::data(const QModelIndex& index, int role) const
                 std::set<QString>::const_iterator umi = unreadMessages->find(item.id);
                 if (umi != unreadMessages->end()) {
                     unreadMessages->erase(umi);
-                    emit unreadMessagesCount();
+                    emit unreadMessagesCountChanged();
                 }
                 
                 item.sentByMe = sentByMe(*msg);
@@ -370,7 +370,6 @@ void Models::MessageFeed::fetchMore(const QModelIndex& parent)
     if (syncState == incomplete) {
         syncState = syncing;
         emit syncStateChange(syncState);
-        emit requestStateChange(true);
         
         if (storage.size() == 0) {
             emit requestArchive("");
@@ -398,7 +397,6 @@ void Models::MessageFeed::responseArchive(const std::list<Shared::Message> list,
             syncState = incomplete;
         }
         emit syncStateChange(syncState);
-        emit requestStateChange(false);
     }
 }
 
@@ -656,3 +654,13 @@ Models::MessageFeed::SyncState Models::MessageFeed::getSyncState() const
 {
     return syncState;
 }
+
+void Models::MessageFeed::requestLatestMessages()
+{
+    if (syncState != syncing) {
+        syncState = syncing;
+        emit syncStateChange(syncState);
+        
+        emit requestArchive("");
+    }
+}
diff --git a/ui/widgets/messageline/messagefeed.h b/ui/widgets/messageline/messagefeed.h
index efb005a..b368a3d 100644
--- a/ui/widgets/messageline/messagefeed.h
+++ b/ui/widgets/messageline/messagefeed.h
@@ -77,11 +77,12 @@ public:
     void decrementObservers();
     SyncState getSyncState() const;
     
+    void requestLatestMessages();       //this method is used by Models::Contact to request latest messages after reconnection
+    
 signals:
     void requestArchive(const QString& before);
-    void requestStateChange(bool requesting);
     void fileDownloadRequest(const QString& url);
-    void unreadMessagesCountChanged();
+    void unreadMessagesCountChanged() const;
     void newMessage(const Shared::Message& msg);
     void unnoticedMessage(const Shared::Message& msg);
     void localPathInvalid(const QString& path);

From ddfaa63a24d60521d71c84ef22378ff781e64758 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Mon, 17 May 2021 23:32:44 +0300
Subject: [PATCH 122/281] big image preview optimisations, preview positioning
 fix, memory leaks fix

---
 shared/global.cpp                          |  4 +-
 ui/widgets/messageline/messagedelegate.cpp |  2 +
 ui/widgets/messageline/preview.cpp         | 53 +++++++++++++++-------
 ui/widgets/messageline/preview.h           |  4 +-
 4 files changed, 43 insertions(+), 20 deletions(-)

diff --git a/shared/global.cpp b/shared/global.cpp
index 0330a00..67e74d1 100644
--- a/shared/global.cpp
+++ b/shared/global.cpp
@@ -128,12 +128,12 @@ Shared::Global::FileInfo Shared::Global::getFileInfo(const QString& path)
         QSize size;
         if (big == "image") {
             QMovie mov(path);
-            if (mov.isValid()) {
+            if (mov.isValid() && mov.frameCount() > 1) {
                 p = FileInfo::Preview::animation;
             } else {
                 p = FileInfo::Preview::picture;
             }
-            QImage img(path);
+            QImageReader img(path);
             size = img.size();
 //         } else if (big == "video") {
 //             p = FileInfo::Preview::movie;
diff --git a/ui/widgets/messageline/messagedelegate.cpp b/ui/widgets/messageline/messagedelegate.cpp
index 81018ac..9b46b7a 100644
--- a/ui/widgets/messageline/messagedelegate.cpp
+++ b/ui/widgets/messageline/messagedelegate.cpp
@@ -289,6 +289,8 @@ void MessageDelegate::initializeFonts(const QFont& font)
     bodyMetrics = QFontMetrics(bodyFont);
     nickMetrics = QFontMetrics(nickFont);
     dateMetrics = QFontMetrics(dateFont);
+    
+    Preview::initializeFont(bodyFont);
 }
 
 bool MessageDelegate::editorEvent(QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& index)
diff --git a/ui/widgets/messageline/preview.cpp b/ui/widgets/messageline/preview.cpp
index 8c56cbc..a64c036 100644
--- a/ui/widgets/messageline/preview.cpp
+++ b/ui/widgets/messageline/preview.cpp
@@ -22,7 +22,6 @@
 constexpr int margin = 6;
 constexpr int maxAttachmentHeight = 500;
 
-bool Preview::fontInitialized = false;
 QFont Preview::font;
 QFontMetrics Preview::metrics(Preview::font);
 
@@ -41,12 +40,6 @@ Preview::Preview(const QString& pPath, const QSize& pMaxSize, const QPoint& pos,
     actualPreview(false),
     right(pRight)
 {
-    if (!fontInitialized) {
-        font.setBold(true);
-        font.setPixelSize(14);
-        metrics = QFontMetrics(font);
-        fontInitialized = true;
-    }
     
     initializeElements();
     if (fileReachable) {
@@ -54,6 +47,13 @@ Preview::Preview(const QString& pPath, const QSize& pMaxSize, const QPoint& pos,
     }
 }
 
+void Preview::initializeFont(const QFont& newFont)
+{
+    font = newFont;
+    font.setBold(true);
+    metrics = QFontMetrics(font);
+}
+
 Preview::~Preview()
 {
     clean();
@@ -104,6 +104,9 @@ void Preview::actualize(const QString& newPath, const QSize& newSize, const QPoi
             }
         } else if (maxSizeChanged) {
             applyNewMaxSize();
+            if (right) {
+                positionChanged = true;
+            }
         }
         if (positionChanged || !actualPreview) {
             positionElements();
@@ -132,6 +135,9 @@ void Preview::setSize(const QSize& newSize)
         }
         if (maxSizeChanged || !actualPreview) {
             applyNewMaxSize();
+            if (right) {
+                positionElements();
+            }
         }
     }
 }
@@ -140,13 +146,14 @@ void Preview::applyNewSize()
 {
     switch (info.preview) {
         case Shared::Global::FileInfo::Preview::picture: {
-            QPixmap img(path);
-            if (img.isNull()) {
+            QImageReader img(path);
+            if (!img.canRead()) {
+                delete widget;
                 fileReachable = false;
             } else {
-                img = img.scaled(actualSize, Qt::KeepAspectRatio);
+                img.setScaledSize(actualSize);
                 widget->resize(actualSize);
-                widget->setPixmap(img);
+                widget->setPixmap(QPixmap::fromImage(img.read()));
             }
         }
             break;
@@ -172,7 +179,7 @@ void Preview::applyNewMaxSize()
         default: {
             int labelWidth = maxSize.width() - actualSize.width() - margin;
             QString elidedName = metrics.elidedText(info.name, Qt::ElideMiddle, labelWidth);
-            cachedLabelSize = metrics.size(0, elidedName);
+            cachedLabelSize = metrics.boundingRect(elidedName).size();
             label->setText(elidedName);
             label->resize(cachedLabelSize);
         }
@@ -225,20 +232,23 @@ void Preview::initializeElements()
 {
     switch (info.preview) {
         case Shared::Global::FileInfo::Preview::picture: {
-            QPixmap img(path);
-            if (img.isNull()) {
+            QImageReader img(path);
+            if (!img.canRead()) {
                 fileReachable = false;
             } else {
                 actualPreview = true;
-                img = img.scaled(actualSize, Qt::KeepAspectRatio);
+                img.setScaledSize(actualSize);
                 widget = new QLabel(parent);
-                widget->setPixmap(img);
+                widget->setPixmap(QPixmap::fromImage(img.read()));
                 widget->show();
             }
         }
             break;
         case Shared::Global::FileInfo::Preview::animation:{
             movie = new QMovie(path);
+            QObject::connect(movie, &QMovie::error, 
+                std::bind(&Preview::handleQMovieError, this, std::placeholders::_1)
+            );
             if (!movie->isValid()) {
                 fileReachable = false;
                 delete movie;
@@ -262,7 +272,7 @@ void Preview::initializeElements()
             label->setFont(font);
             int labelWidth = maxSize.width() - actualSize.width() - margin;
             QString elidedName = metrics.elidedText(info.name, Qt::ElideMiddle, labelWidth);
-            cachedLabelSize = metrics.size(0, elidedName);
+            cachedLabelSize = metrics.boundingRect(elidedName).size();
             label->setText(elidedName);
             label->show();
         }
@@ -302,3 +312,12 @@ QSize Preview::constrainAttachSize(QSize src, QSize bounds)
     
     return src;
 }
+
+void Preview::handleQMovieError(QImageReader::ImageReaderError error)
+{
+    if (error == QImageReader::FileNotFoundError) {
+        fileReachable = false;
+        movie->deleteLater();
+        widget->deleteLater();
+    }
+}
diff --git a/ui/widgets/messageline/preview.h b/ui/widgets/messageline/preview.h
index 3d560d3..004ed45 100644
--- a/ui/widgets/messageline/preview.h
+++ b/ui/widgets/messageline/preview.h
@@ -29,6 +29,7 @@
 #include <QMovie>
 #include <QFont>
 #include <QFontMetrics>
+#include <QImageReader>
 
 #include <shared/global.h>
 
@@ -47,9 +48,9 @@ public:
     bool isFileReachable() const;
     QSize size() const;
     
+    static void initializeFont(const QFont& newFont);
     static QSize constrainAttachSize(QSize src, QSize bounds);
     static QSize calculateAttachSize(const QString& path, const QRect& bounds);
-    static bool fontInitialized;
     static QFont font;
     static QFontMetrics metrics;
     
@@ -59,6 +60,7 @@ private:
     void clean();
     void applyNewSize();
     void applyNewMaxSize();
+    void handleQMovieError(QImageReader::ImageReaderError error);
     
 private:
     Shared::Global::FileInfo info;

From 3f1fba4de216b71a7d0966d240126d348a0af70d Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Sun, 23 May 2021 01:03:14 +0300
Subject: [PATCH 123/281] doovers for failed messages, some corner cases fixes
 with handling errors during message sending

---
 core/account.cpp                 |  3 ++
 core/account.h                   |  1 +
 core/archive.cpp                 |  5 +--
 core/handlers/messagehandler.cpp | 56 ++++++++++++++++++++++++--------
 core/handlers/messagehandler.h   |  5 +--
 core/main.cpp                    |  1 +
 core/squawk.cpp                  | 13 +++++++-
 core/squawk.h                    |  1 +
 ui/squawk.cpp                    | 10 ++++++
 ui/squawk.h                      |  2 ++
 ui/widgets/conversation.cpp      | 10 ++++++
 ui/widgets/conversation.h        |  1 +
 12 files changed, 89 insertions(+), 19 deletions(-)

diff --git a/core/account.cpp b/core/account.cpp
index 5ce29ee..6784674 100644
--- a/core/account.cpp
+++ b/core/account.cpp
@@ -923,3 +923,6 @@ void Core::Account::onContactHistoryResponse(const std::list<Shared::Message>& l
 
 void Core::Account::requestChangeMessage(const QString& jid, const QString& messageId, const QMap<QString, QVariant>& data){
     mh->requestChangeMessage(jid, messageId, data);}
+
+void Core::Account::resendMessage(const QString& jid, const QString& id) {
+    mh->resendMessage(jid, id);}
diff --git a/core/account.h b/core/account.h
index a0db9f9..5ba834c 100644
--- a/core/account.h
+++ b/core/account.h
@@ -103,6 +103,7 @@ public:
     void removeRoomRequest(const QString& jid);
     void addRoomRequest(const QString& jid, const QString& nick, const QString& password, bool autoJoin);
     void uploadVCard(const Shared::VCard& card);
+    void resendMessage(const QString& jid, const QString& id);
     
 public slots:
     void connect();
diff --git a/core/archive.cpp b/core/archive.cpp
index 96a8c0d..2582ff9 100644
--- a/core/archive.cpp
+++ b/core/archive.cpp
@@ -308,8 +308,9 @@ void Core::Archive::changeMessage(const QString& id, const QMap<QString, QVarian
             }
         }
         
-        if (msg.getStanzaId().size() > 0 && (idChange || !hadStanzaId)) {
-            const std::string& szid = msg.getStanzaId().toStdString();
+        QString qsid = msg.getStanzaId();
+        if (qsid.size() > 0 && (idChange || !hadStanzaId)) {
+            std::string szid = qsid.toStdString();
             
             lmdbData.mv_size = szid.size();
             lmdbData.mv_data = (char*)szid.c_str();
diff --git a/core/handlers/messagehandler.cpp b/core/handlers/messagehandler.cpp
index 54aff53..33b3458 100644
--- a/core/handlers/messagehandler.cpp
+++ b/core/handlers/messagehandler.cpp
@@ -73,8 +73,7 @@ void Core::MessageHandler::onMessageReceived(const QXmppMessage& msg)
 
 bool Core::MessageHandler::handleChatMessage(const QXmppMessage& msg, bool outgoing, bool forwarded, bool guessing)
 {
-    const QString& body(msg.body());
-    if (body.size() != 0) {
+    if (msg.body().size() != 0 || msg.outOfBandUrl().size() > 0) {
         Shared::Message sMsg(Shared::Message::chat);
         initializeMessage(sMsg, msg, outgoing, forwarded, guessing);
         QString jid = sMsg.getPenPalJid();
@@ -234,17 +233,17 @@ void Core::MessageHandler::onReceiptReceived(const QString& jid, const QString&
         if (ri != 0) {
             ri->changeMessage(id, cData);
         }
-        pendingStateMessages.erase(itr);
         emit acc->changeMessage(itr->second, id, cData);
+        pendingStateMessages.erase(itr);
     }
 }
 
-void Core::MessageHandler::sendMessage(const Shared::Message& data)
+void Core::MessageHandler::sendMessage(const Shared::Message& data, bool newMessage)
 {
     if (data.getOutOfBandUrl().size() == 0 && data.getAttachPath().size() > 0) {
-        prepareUpload(data);
+        prepareUpload(data, newMessage);
     } else {
-        performSending(data);
+        performSending(data, newMessage);
     }
 }
 
@@ -256,6 +255,7 @@ void Core::MessageHandler::performSending(Shared::Message data, bool newMessage)
     RosterItem* ri = acc->rh->getRosterItem(jid);
     bool sent = false;
     QMap<QString, QVariant> changes;
+    QDateTime sendTime = QDateTime::currentDateTimeUtc();
     if (acc->state == Shared::ConnectionState::connected) {
         QXmppMessage msg(acc->getFullJid(), data.getTo(), data.getBody(), data.getThread());
         
@@ -266,8 +266,10 @@ void Core::MessageHandler::performSending(Shared::Message data, bool newMessage)
         msg.setType(static_cast<QXmppMessage::Type>(data.getType()));       //it is safe here, my type is compatible
         msg.setOutOfBandUrl(oob);
         msg.setReceiptRequested(true);
+        msg.setStamp(sendTime);
         
         sent = acc->client.sendPacket(msg);
+        //sent = false;
         
         if (sent) {
             data.setState(Shared::Message::State::sent);
@@ -289,9 +291,10 @@ void Core::MessageHandler::performSending(Shared::Message data, bool newMessage)
     if (oob.size() > 0) {
         changes.insert("outOfBandUrl", oob);
     }
-    if (!newMessage) {
-        changes.insert("stamp", data.getTime());
+    if (newMessage) {
+        data.setTime(sendTime);
     }
+    changes.insert("stamp", sendTime);
     
     if (ri != 0) {
         if (newMessage) {
@@ -309,7 +312,7 @@ void Core::MessageHandler::performSending(Shared::Message data, bool newMessage)
     emit acc->changeMessage(jid, id, changes);
 }
 
-void Core::MessageHandler::prepareUpload(const Shared::Message& data)
+void Core::MessageHandler::prepareUpload(const Shared::Message& data, bool newMessage)
 {
     if (acc->state == Shared::ConnectionState::connected) {
         QString jid = data.getPenPalJid();
@@ -322,16 +325,23 @@ void Core::MessageHandler::prepareUpload(const Shared::Message& data)
         QString path = data.getAttachPath();
         QString url = acc->network->getFileRemoteUrl(path);
         if (url.size() != 0) {
-            sendMessageWithLocalUploadedFile(data, url);
+            sendMessageWithLocalUploadedFile(data, url, newMessage);
         } else {
-            if (acc->network->checkAndAddToUploading(acc->getName(), jid, id, path)) {
+            pendingStateMessages.insert(std::make_pair(id, jid));
+            if (newMessage) {
                 ri->appendMessageToArchive(data);
-                pendingStateMessages.insert(std::make_pair(id, jid));
             } else {
+                QMap<QString, QVariant> changes({
+                    {"state", (uint)Shared::Message::State::pending}
+                });
+                ri->changeMessage(id, changes);
+                emit acc->changeMessage(jid, id, changes);
+            }
+            //this checks if the file is already uploading, and if so it subscribes to it's success, so, i need to do stuff only if the network knows nothing of this file
+            if (!acc->network->checkAndAddToUploading(acc->getName(), jid, id, path)) {
                 if (acc->um->serviceFound()) {
                     QFileInfo file(path);
                     if (file.exists() && file.isReadable()) {
-                        ri->appendMessageToArchive(data);
                         pendingStateMessages.insert(std::make_pair(id, jid));
                         uploadingSlotsQueue.emplace_back(path, id);
                         if (uploadingSlotsQueue.size() == 1) {
@@ -353,7 +363,6 @@ void Core::MessageHandler::prepareUpload(const Shared::Message& data)
     }
 }
 
-
 void Core::MessageHandler::onUploadSlotReceived(const QXmppHttpUploadSlotIq& slot)
 {
     if (uploadingSlotsQueue.size() == 0) {
@@ -481,3 +490,22 @@ void Core::MessageHandler::requestChangeMessage(const QString& jid, const QStrin
         }
     }
 }
+
+void Core::MessageHandler::resendMessage(const QString& jid, const QString& id)
+{
+    RosterItem* cnt = acc->rh->getRosterItem(jid);
+    if (cnt != 0) {
+        try {
+            Shared::Message msg = cnt->getMessage(id);
+            if (msg.getState() == Shared::Message::State::error) {
+                sendMessage(msg, false);
+            } else {
+                qDebug() << "An attempt to resend a message to" << jid << "by account" << acc->getName() << ", but this message seems to have been normally sent, this method was made to retry sending failed to be sent messages, skipping";
+            }
+        } catch (const Archive::NotFound& err) {
+            qDebug() << "An attempt to resend a message to" << jid << "by account" << acc->getName() << ", but this message wasn't found in history, skipping";
+        }
+    } else {
+        qDebug() << "An attempt to resend a message to" << jid << "by account" << acc->getName() << ", but this jid isn't present in account roster, skipping";
+    }
+}
diff --git a/core/handlers/messagehandler.h b/core/handlers/messagehandler.h
index 9138245..4eb9265 100644
--- a/core/handlers/messagehandler.h
+++ b/core/handlers/messagehandler.h
@@ -45,8 +45,9 @@ public:
     MessageHandler(Account* account);
     
 public:
-    void sendMessage(const Shared::Message& data);
+    void sendMessage(const Shared::Message& data, bool newMessage = true);
     void initializeMessage(Shared::Message& target, const QXmppMessage& source, bool outgoing = false, bool forwarded = false, bool guessing = false) const;
+    void resendMessage(const QString& jid, const QString& id);
     
 public slots:
     void onMessageReceived(const QXmppMessage& message);
@@ -66,7 +67,7 @@ private:
     void logMessage(const QXmppMessage& msg, const QString& reason = "Message wasn't handled: ");
     void sendMessageWithLocalUploadedFile(Shared::Message msg, const QString& url, bool newMessage = true);
     void performSending(Shared::Message data, bool newMessage = true);
-    void prepareUpload(const Shared::Message& data);
+    void prepareUpload(const Shared::Message& data, bool newMessage = true);
     void handleUploadError(const QString& jid, const QString& messageId, const QString& errorText);
     
 private:
diff --git a/core/main.cpp b/core/main.cpp
index 0090424..0be020e 100644
--- a/core/main.cpp
+++ b/core/main.cpp
@@ -100,6 +100,7 @@ int main(int argc, char *argv[])
     QObject::connect(&w, &Squawk::disconnectAccount, squawk, &Core::Squawk::disconnectAccount);
     QObject::connect(&w, &Squawk::changeState, squawk, &Core::Squawk::changeState);
     QObject::connect(&w, &Squawk::sendMessage, squawk,&Core::Squawk::sendMessage);
+    QObject::connect(&w, &Squawk::resendMessage, squawk,&Core::Squawk::resendMessage);
     QObject::connect(&w, &Squawk::requestArchive, squawk, &Core::Squawk::requestArchive);
     QObject::connect(&w, &Squawk::subscribeContact, squawk, &Core::Squawk::subscribeContact);
     QObject::connect(&w, &Squawk::unsubscribeContact, squawk, &Core::Squawk::unsubscribeContact);
diff --git a/core/squawk.cpp b/core/squawk.cpp
index 411d4ab..6b8af49 100644
--- a/core/squawk.cpp
+++ b/core/squawk.cpp
@@ -328,13 +328,24 @@ void Core::Squawk::sendMessage(const QString& account, const Shared::Message& da
 {
     AccountsMap::const_iterator itr = amap.find(account);
     if (itr == amap.end()) {
-        qDebug("An attempt to send a message with non existing account, skipping");
+        qDebug() << "An attempt to send a message with non existing account" << account << ", skipping";
         return;
     }
     
     itr->second->sendMessage(data);
 }
 
+void Core::Squawk::resendMessage(const QString& account, const QString& jid, const QString& id)
+{
+    AccountsMap::const_iterator itr = amap.find(account);
+    if (itr == amap.end()) {
+        qDebug() << "An attempt to resend a message with non existing account" << account << ", skipping";
+        return;
+    }
+    
+    itr->second->resendMessage(jid, id);
+}
+
 void Core::Squawk::requestArchive(const QString& account, const QString& jid, int count, const QString& before)
 {
     AccountsMap::const_iterator itr = amap.find(account);
diff --git a/core/squawk.h b/core/squawk.h
index 25fdbda..338eb40 100644
--- a/core/squawk.h
+++ b/core/squawk.h
@@ -101,6 +101,7 @@ public slots:
     void changeState(Shared::Availability state);
     
     void sendMessage(const QString& account, const Shared::Message& data);
+    void resendMessage(const QString& account, const QString& jid, const QString& id);
     void requestArchive(const QString& account, const QString& jid, int count, const QString& before);
     
     void subscribeContact(const QString& account, const QString& jid, const QString& reason);
diff --git a/ui/squawk.cpp b/ui/squawk.cpp
index fb79592..6a0a676 100644
--- a/ui/squawk.cpp
+++ b/ui/squawk.cpp
@@ -466,6 +466,15 @@ void Squawk::onConversationMessage(const Shared::Message& msg)
     emit sendMessage(acc, msg);
 }
 
+void Squawk::onConversationResend(const QString& id)
+{
+    Conversation* conv = static_cast<Conversation*>(sender());
+    QString acc = conv->getAccount();
+    QString jid = conv->getJid();
+    
+    emit resendMessage(acc, jid, id);
+}
+
 void Squawk::onRequestArchive(const QString& account, const QString& jid, const QString& before)
 {
     emit requestArchive(account, jid, 20, before); //TODO amount as a settings value
@@ -914,6 +923,7 @@ void Squawk::subscribeConversation(Conversation* conv)
 {
     connect(conv, &Conversation::destroyed, this, &Squawk::onConversationClosed);
     connect(conv, &Conversation::sendMessage, this, &Squawk::onConversationMessage);
+    connect(conv, &Conversation::resendMessage, this, &Squawk::onConversationResend);
     connect(conv, &Conversation::notifyableMessage, this, &Squawk::notify);
 }
 
diff --git a/ui/squawk.h b/ui/squawk.h
index 15d3f82..28389fa 100644
--- a/ui/squawk.h
+++ b/ui/squawk.h
@@ -63,6 +63,7 @@ signals:
     void disconnectAccount(const QString&);
     void changeState(Shared::Availability state);
     void sendMessage(const QString& account, const Shared::Message& data);
+    void resendMessage(const QString& account, const QString& jid, const QString& id);
     void requestArchive(const QString& account, const QString& jid, int count, const QString& before);
     void subscribeContact(const QString& account, const QString& jid, const QString& reason);
     void unsubscribeContact(const QString& account, const QString& jid, const QString& reason);
@@ -148,6 +149,7 @@ private slots:
     void onComboboxActivated(int index);
     void onRosterItemDoubleClicked(const QModelIndex& item);
     void onConversationMessage(const Shared::Message& msg);
+    void onConversationResend(const QString& id);
     void onRequestArchive(const QString& account, const QString& jid, const QString& before);
     void onRosterContextMenu(const QPoint& point);
     void onItemCollepsed(const QModelIndex& index);
diff --git a/ui/widgets/conversation.cpp b/ui/widgets/conversation.cpp
index 45ce2c5..d003551 100644
--- a/ui/widgets/conversation.cpp
+++ b/ui/widgets/conversation.cpp
@@ -414,6 +414,16 @@ void Conversation::onFeedContext(const QPoint& pos)
         
         contextMenu->clear();
         bool showMenu = false;
+        if (item->getState() == Shared::Message::State::error) {
+            showMenu = true;
+            QString id = item->getId();
+            QAction* resend = contextMenu->addAction(Shared::icon("view-refresh"), tr("Try sending again")); 
+            connect(resend, &QAction::triggered, [this, id]() {
+                element->feed->registerUpload(id);
+                emit resendMessage(id);
+            });
+        }
+        
         QString path = item->getAttachPath();
         if (path.size() > 0) {
             showMenu = true;
diff --git a/ui/widgets/conversation.h b/ui/widgets/conversation.h
index 3f048fb..b0eb745 100644
--- a/ui/widgets/conversation.h
+++ b/ui/widgets/conversation.h
@@ -80,6 +80,7 @@ public:
     
 signals:
     void sendMessage(const Shared::Message& message);
+    void resendMessage(const QString& id);
     void requestArchive(const QString& before);
     void shown();
     void requestLocalFile(const QString& messageId, const QString& url);

From 5f925217fc1f2470ccd04b18936f08b879be81f9 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Tue, 25 May 2021 01:06:05 +0300
Subject: [PATCH 124/281] edit icon next to the message showing if the message
 was edited and what was there a first

---
 ui/widgets/messageline/messagedelegate.cpp | 66 ++++++++++++++++++++--
 ui/widgets/messageline/messagedelegate.h   |  2 +
 ui/widgets/messageline/messagefeed.cpp     | 12 +++-
 ui/widgets/messageline/messagefeed.h       | 11 +++-
 4 files changed, 84 insertions(+), 7 deletions(-)

diff --git a/ui/widgets/messageline/messagedelegate.cpp b/ui/widgets/messageline/messagedelegate.cpp
index 9b46b7a..8728ba3 100644
--- a/ui/widgets/messageline/messagedelegate.cpp
+++ b/ui/widgets/messageline/messagedelegate.cpp
@@ -42,6 +42,7 @@ MessageDelegate::MessageDelegate(QObject* parent):
     buttons(new std::map<QString, FeedButton*>()),
     bars(new std::map<QString, QProgressBar*>()),
     statusIcons(new std::map<QString, QLabel*>()),
+    pencilIcons(new std::map<QString, QLabel*>()),
     bodies(new std::map<QString, QLabel*>()),
     previews(new std::map<QString, Preview*>()),
     idsToKeep(new std::set<QString>()),
@@ -68,6 +69,10 @@ MessageDelegate::~MessageDelegate()
         delete pair.second;
     }
     
+    for (const std::pair<const QString, QLabel*>& pair: *pencilIcons){
+        delete pair.second;
+    }
+    
     for (const std::pair<const QString, QLabel*>& pair: *bodies){
         delete pair.second;
     }
@@ -76,6 +81,8 @@ MessageDelegate::~MessageDelegate()
         delete pair.second;
     }
     
+    delete statusIcons;
+    delete pencilIcons;
     delete idsToKeep;
     delete buttons;
     delete bars;
@@ -128,6 +135,18 @@ void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio
         if (senderSize.width() > messageSize.width()) {
             messageSize.setWidth(senderSize.width());
         }
+        QSize dateSize = dateMetrics.boundingRect(messageRect, 0, data.date.toLocalTime().toString()).size();
+        int addition = 0;
+        
+        if (data.correction.corrected) {
+            addition += margin + statusIconSize;
+        }
+        if (data.sentByMe) {
+            addition += margin + statusIconSize;
+        }
+        if (dateSize.width() + addition > messageSize.width()) {
+            messageSize.setWidth(dateSize.width() + addition);
+        }
     } else {
         messageSize.setWidth(opt.rect.width());
     }
@@ -170,6 +189,7 @@ void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio
     painter->restore();
     
     int messageLeft = INT16_MAX;
+    int messageRight = opt.rect.x() + messageSize.width();
     QWidget* vp = static_cast<QWidget*>(painter->device());
     if (data.text.size() > 0) {
         QLabel* body = getBody(data);
@@ -192,18 +212,35 @@ void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio
     q.setAlpha(180);
     painter->setPen(q);
     painter->drawText(opt.rect, opt.displayAlignment, data.date.toLocalTime().toString(), &rect);
+    int currentY = opt.rect.y();
     if (data.sentByMe) {
-        if (messageLeft > rect.x() - statusIconSize - margin) {
-            messageLeft = rect.x() - statusIconSize - margin;
-        }
         QLabel* statusIcon = getStatusIcon(data);
         
         statusIcon->setParent(vp);
-        statusIcon->move(messageLeft, opt.rect.y());
+        statusIcon->move(opt.rect.topRight().x() - messageSize.width(), currentY);
         statusIcon->show();
+        
         opt.rect.adjust(0, statusIconSize + textMargin, 0, 0);
     }
     
+    if (data.correction.corrected) {
+        QLabel* pencilIcon = getPencilIcon(data);
+        
+        pencilIcon->setParent(vp);
+        if (data.sentByMe) {
+            pencilIcon->move(opt.rect.topRight().x() - messageSize.width() + statusIconSize + margin, currentY);
+        } else {
+            pencilIcon->move(messageRight - statusIconSize - margin, currentY);
+        }
+        pencilIcon->show();
+    } else {
+        std::map<QString, QLabel*>::const_iterator itr = pencilIcons->find(data.id);
+        if (itr != pencilIcons->end()) {
+            delete itr->second;
+            pencilIcons->erase(itr);
+        }
+    }
+    
     painter->restore();
     
     if (clearingWidgets) {
@@ -439,6 +476,26 @@ QLabel * MessageDelegate::getStatusIcon(const Models::FeedItem& data) const
     return result;
 }
 
+QLabel * MessageDelegate::getPencilIcon(const Models::FeedItem& data) const
+{
+    std::map<QString, QLabel*>::const_iterator itr = pencilIcons->find(data.id);
+    QLabel* result = 0;
+    
+    if (itr != pencilIcons->end()) {
+        result = itr->second;
+    } else {
+        result = new QLabel();
+        QIcon icon = Shared::icon("edit-rename");
+        result->setPixmap(icon.pixmap(statusIconSize));
+        pencilIcons->insert(std::make_pair(data.id, result));
+    }
+    
+    result->setToolTip("Last time edited: " + data.correction.lastCorrection.toLocalTime().toString() 
+    + "\nOriginal message: " + data.correction.original);
+    
+    return result;
+}
+
 QLabel * MessageDelegate::getBody(const Models::FeedItem& data) const
 {
     std::map<QString, QLabel*>::const_iterator itr = bodies->find(data.id);
@@ -486,6 +543,7 @@ void MessageDelegate::endClearWidgets()
         removeElements(buttons, idsToKeep);
         removeElements(bars, idsToKeep);
         removeElements(statusIcons, idsToKeep);
+        removeElements(pencilIcons, idsToKeep);
         removeElements(bodies, idsToKeep);
         removeElements(previews, idsToKeep);
         
diff --git a/ui/widgets/messageline/messagedelegate.h b/ui/widgets/messageline/messagedelegate.h
index 5c2989d..7403285 100644
--- a/ui/widgets/messageline/messagedelegate.h
+++ b/ui/widgets/messageline/messagedelegate.h
@@ -68,6 +68,7 @@ protected:
     QPushButton* getButton(const Models::FeedItem& data) const;
     QProgressBar* getBar(const Models::FeedItem& data) const;
     QLabel* getStatusIcon(const Models::FeedItem& data) const;
+    QLabel* getPencilIcon(const Models::FeedItem& data) const;
     QLabel* getBody(const Models::FeedItem& data) const;
     void clearHelperWidget(const Models::FeedItem& data) const;
     
@@ -93,6 +94,7 @@ private:
     std::map<QString, FeedButton*>* buttons;
     std::map<QString, QProgressBar*>* bars;
     std::map<QString, QLabel*>* statusIcons;
+    std::map<QString, QLabel*>* pencilIcons;
     std::map<QString, QLabel*>* bodies;
     std::map<QString, Preview*>* previews;
     std::set<QString>* idsToKeep;
diff --git a/ui/widgets/messageline/messagefeed.cpp b/ui/widgets/messageline/messagefeed.cpp
index 9537ea5..733cf1d 100644
--- a/ui/widgets/messageline/messagefeed.cpp
+++ b/ui/widgets/messageline/messagefeed.cpp
@@ -262,7 +262,7 @@ QVariant Models::MessageFeed::data(const QModelIndex& index, int role) const
                 answer = static_cast<unsigned int>(msg->getState());
                 break;
             case Correction: 
-                answer = msg->getEdited();
+                answer.setValue(fillCorrection(*msg));;
                 break;
             case SentByMe: 
                 answer = sentByMe(*msg);
@@ -311,7 +311,7 @@ QVariant Models::MessageFeed::data(const QModelIndex& index, int role) const
                 item.date = msg->getTime();
                 item.state = msg->getState();
                 item.error = msg->getErrorText();
-                item.correction = msg->getEdited();
+                item.correction = fillCorrection(*msg);
                 
                 QString body = msg->getBody();
                 if (body != msg->getOutOfBandUrl()) {
@@ -480,6 +480,14 @@ Models::Attachment Models::MessageFeed::fillAttach(const Shared::Message& msg) c
     return att;
 }
 
+Models::Edition Models::MessageFeed::fillCorrection(const Shared::Message& msg) const
+{
+    ::Models::Edition ed({msg.getEdited(), msg.getOriginalBody(), msg.getLastModified()});
+    
+    return ed;
+}
+
+
 void Models::MessageFeed::downloadAttachment(const QString& messageId)
 {
     bool notify = false;
diff --git a/ui/widgets/messageline/messagefeed.h b/ui/widgets/messageline/messagefeed.h
index b368a3d..2273b15 100644
--- a/ui/widgets/messageline/messagefeed.h
+++ b/ui/widgets/messageline/messagefeed.h
@@ -37,6 +37,7 @@
 namespace Models {
     class Element;
     struct Attachment;
+    struct Edition;
     
 class MessageFeed : public QAbstractListModel
 {
@@ -106,6 +107,7 @@ public:
 protected:
     bool sentByMe(const Shared::Message& msg) const;
     Attachment fillAttach(const Shared::Message& msg) const;
+    Edition fillCorrection(const Shared::Message& msg) const;
     QModelIndex modelIndexById(const QString& id) const;
     QModelIndex modelIndexByTime(const QString& id, const QDateTime& time) const;
     std::set<MessageRoles> detectChanges(const Shared::Message& msg, const QMap<QString, QVariant>& data) const;
@@ -180,6 +182,12 @@ struct Attachment {
     QString error;
 };
 
+struct Edition {
+    bool corrected;
+    QString original;
+    QDateTime lastCorrection;
+};
+
 struct FeedItem {
     QString id;
     QString text;
@@ -187,7 +195,7 @@ struct FeedItem {
     QString avatar;
     QString error;
     bool sentByMe;
-    bool correction;
+    Edition correction;
     QDateTime date;
     Shared::Message::State state;
     Attachment attach;
@@ -195,6 +203,7 @@ struct FeedItem {
 };
 
 Q_DECLARE_METATYPE(Models::Attachment);
+Q_DECLARE_METATYPE(Models::Edition);
 Q_DECLARE_METATYPE(Models::FeedItem);
 
 #endif // MESSAGEFEED_H

From 87c216b491b6965426682d3f2e880740fd9bc3b2 Mon Sep 17 00:00:00 2001
From: Bruno Fontes <developer@brunofontes.net>
Date: Wed, 21 Jul 2021 19:37:31 -0300
Subject: [PATCH 125/281] Add Brazilian Portuguese translations

---
 packaging/squawk.desktop     |   2 +
 translations/squawk.pt_BR.ts | 795 +++++++++++++++++++++++++++++++++++
 2 files changed, 797 insertions(+)
 create mode 100644 translations/squawk.pt_BR.ts

diff --git a/packaging/squawk.desktop b/packaging/squawk.desktop
index 0395af1..ba0f13c 100644
--- a/packaging/squawk.desktop
+++ b/packaging/squawk.desktop
@@ -5,8 +5,10 @@ Version=1.0
 Name=Squawk
 GenericName=Instant Messenger
 GenericName[ru]=Мгновенные сообщения
+GenericName[pt_BR]=Mensageiro instantâneo
 Comment=XMPP (Jabber) instant messenger client
 Comment[ru]=XMPP (Jabber) клиент обмена мгновенными сообщениями
+Comment[pt_BR]=Cliente de mensagem instantânea XMPP (Jabber)
 Exec=squawk %u
 Icon=squawk
 StartupNotify=true
diff --git a/translations/squawk.pt_BR.ts b/translations/squawk.pt_BR.ts
new file mode 100644
index 0000000..4080e5d
--- /dev/null
+++ b/translations/squawk.pt_BR.ts
@@ -0,0 +1,795 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="pt_BR">
+<context>
+    <name>Account</name>
+    <message>
+        <source>Account</source>
+        <translation>Conta</translation>
+    </message>
+    <message>
+        <source>Your account login</source>
+        <translation>Suas informações de login</translation>
+    </message>
+    <message>
+        <source>john_smith1987</source>
+        <translation>josé_silva1987</translation>
+    </message>
+    <message>
+        <source>Server</source>
+        <translation>Servidor</translation>
+    </message>
+    <message>
+        <source>A server address of your account. Like 404.city or macaw.me</source>
+        <translation>O endereço do servidor da sua conta, como o 404.city ou o macaw.me</translation>
+    </message>
+    <message>
+        <source>macaw.me</source>
+        <translation>macaw.me</translation>
+    </message>
+    <message>
+        <source>Login</source>
+        <translation>Usuário</translation>
+    </message>
+    <message>
+        <source>Password</source>
+        <translation>Senha</translation>
+    </message>
+    <message>
+        <source>Password of your account</source>
+        <translation>Senha da sua conta</translation>
+    </message>
+    <message>
+        <source>Name</source>
+        <translation>Nome</translation>
+    </message>
+    <message>
+        <source>Just a name how would you call this account, doesn&apos;t affect anything</source>
+        <translation>Apenas um nome para identificar esta conta. Não influencia em nada</translation>
+    </message>
+    <message>
+        <source>John</source>
+        <translation>José</translation>
+    </message>
+    <message>
+        <source>Resource</source>
+        <translation>Recurso</translation>
+    </message>
+    <message>
+        <source>A resource name like &quot;Home&quot; or &quot;Work&quot;</source>
+        <translation>Um nome de recurso como  &quot;Casa&quot; ou &quot;Trabalho&quot;</translation>
+    </message>
+    <message>
+        <source>QXmpp</source>
+        <translation>QXmpp</translation>
+    </message>
+    <message>
+        <source>Password storage</source>
+        <translation>Armazenamento de senha</translation>
+    </message>
+</context>
+<context>
+    <name>Accounts</name>
+    <message>
+        <source>Accounts</source>
+        <translation>Contas</translation>
+    </message>
+    <message>
+        <source>Delete</source>
+        <translation>Apagar</translation>
+    </message>
+    <message>
+        <source>Add</source>
+        <translation>Adicionar</translation>
+    </message>
+    <message>
+        <source>Edit</source>
+        <translation>Editar</translation>
+    </message>
+    <message>
+        <source>Change password</source>
+        <translation>Alterar senha</translation>
+    </message>
+    <message>
+        <source>Connect</source>
+        <translation>Conectar</translation>
+    </message>
+    <message>
+        <source>Disconnect</source>
+        <translation>Desconectar</translation>
+    </message>
+</context>
+<context>
+    <name>Conversation</name>
+    <message>
+        <source>Type your message here...</source>
+        <translation>Digite sua mensagem aqui...</translation>
+    </message>
+    <message>
+        <source>Chose a file to send</source>
+        <translation>Escolha um arquivo para enviar</translation>
+    </message>
+    <message>
+        <source>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;Liberation Sans&apos;; font-size:10pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation></translation>
+    </message>
+    <message>
+        <source>Drop files here to attach them to your message</source>
+        <translation>Arraste seus arquivos aqui para anexá-los a sua mensagem</translation>
+    </message>
+</context>
+<context>
+    <name>Global</name>
+    <message>
+        <source>Online</source>
+        <comment>Availability</comment>
+        <translation>Conectado</translation>
+    </message>
+    <message>
+        <source>Away</source>
+        <comment>Availability</comment>
+        <translation>Distante</translation>
+    </message>
+    <message>
+        <source>Absent</source>
+        <comment>Availability</comment>
+        <translation>Ausente</translation>
+    </message>
+    <message>
+        <source>Busy</source>
+        <comment>Availability</comment>
+        <translation>Ocupado</translation>
+    </message>
+    <message>
+        <source>Chatty</source>
+        <comment>Availability</comment>
+        <translation>Tagarela</translation>
+    </message>
+    <message>
+        <source>Invisible</source>
+        <comment>Availability</comment>
+        <translation>Invisível</translation>
+    </message>
+    <message>
+        <source>Offline</source>
+        <comment>Availability</comment>
+        <translation>Desconectado</translation>
+    </message>
+    <message>
+        <source>Disconnected</source>
+        <comment>ConnectionState</comment>
+        <translation>Desconectado</translation>
+    </message>
+    <message>
+        <source>Connecting</source>
+        <comment>ConnectionState</comment>
+        <translation>Connectando</translation>
+    </message>
+    <message>
+        <source>Connected</source>
+        <comment>ConnectionState</comment>
+        <translation>Conectado</translation>
+    </message>
+    <message>
+        <source>Error</source>
+        <comment>ConnectionState</comment>
+        <translation>Erro</translation>
+    </message>
+    <message>
+        <source>None</source>
+        <comment>SubscriptionState</comment>
+        <translation>Nenhum</translation>
+    </message>
+    <message>
+        <source>From</source>
+        <comment>SubscriptionState</comment>
+        <translation>De</translation>
+    </message>
+    <message>
+        <source>To</source>
+        <comment>SubscriptionState</comment>
+        <translation>Para</translation>
+    </message>
+    <message>
+        <source>Both</source>
+        <comment>SubscriptionState</comment>
+        <translation>Ambos</translation>
+    </message>
+    <message>
+        <source>Unknown</source>
+        <comment>SubscriptionState</comment>
+        <translation>Desconhecido</translation>
+    </message>
+    <message>
+        <source>Unspecified</source>
+        <comment>Affiliation</comment>
+        <translation>Não especificado</translation>
+    </message>
+    <message>
+        <source>Outcast</source>
+        <comment>Affiliation</comment>
+        <translation>Rejeitado</translation>
+    </message>
+    <message>
+        <source>Nobody</source>
+        <comment>Affiliation</comment>
+        <translation>Ninguém</translation>
+    </message>
+    <message>
+        <source>Member</source>
+        <comment>Affiliation</comment>
+        <translation>Membro</translation>
+    </message>
+    <message>
+        <source>Admin</source>
+        <comment>Affiliation</comment>
+        <translation>Administrador</translation>
+    </message>
+    <message>
+        <source>Owner</source>
+        <comment>Affiliation</comment>
+        <translation>Dono</translation>
+    </message>
+    <message>
+        <source>Unspecified</source>
+        <comment>Role</comment>
+        <translation>Não especificado</translation>
+    </message>
+    <message>
+        <source>Nobody</source>
+        <comment>Role</comment>
+        <translation>Ninguém</translation>
+    </message>
+    <message>
+        <source>Visitor</source>
+        <comment>Role</comment>
+        <translation>Visitante</translation>
+    </message>
+    <message>
+        <source>Participant</source>
+        <comment>Role</comment>
+        <translation>Participante</translation>
+    </message>
+    <message>
+        <source>Moderator</source>
+        <comment>Role</comment>
+        <translation>Moderador</translation>
+    </message>
+    <message>
+        <source>Pending</source>
+        <comment>MessageState</comment>
+        <translation>Aguardando</translation>
+    </message>
+    <message>
+        <source>Sent</source>
+        <comment>MessageState</comment>
+        <translation>Enviada</translation>
+    </message>
+    <message>
+        <source>Delivered</source>
+        <comment>MessageState</comment>
+        <translation>Entregue</translation>
+    </message>
+    <message>
+        <source>Error</source>
+        <comment>MessageState</comment>
+        <translation>Erro</translation>
+    </message>
+    <message>
+        <source>Plain</source>
+        <comment>AccountPassword</comment>
+        <translation>Texto simples</translation>
+    </message>
+    <message>
+        <source>Jammed</source>
+        <comment>AccountPassword</comment>
+        <translation>Embaralhado</translation>
+    </message>
+    <message>
+        <source>Always Ask</source>
+        <comment>AccountPassword</comment>
+        <translation>Sempre perguntar</translation>
+    </message>
+    <message>
+        <source>KWallet</source>
+        <comment>AccountPassword</comment>
+        <translation>KWallet</translation>
+    </message>
+    <message>
+        <source>Your password is going to be stored in config file but jammed with constant encryption key you can find in program source code. It might look like encryption but it&apos;s not</source>
+        <comment>AccountPasswordDescription</comment>
+        <translation>Sua senha será armazenada em um arquivo de configurações, porém embaralhada com uma chave criptográfica constante que você pode encontrar no código fonte do programa. Parece criptografado, mas não é</translation>
+    </message>
+    <message>
+        <source>Squawk is going to query you for the password on every start of the program</source>
+        <comment>AccountPasswordDescription</comment>
+        <translation>O Squark vai requisitar sua senha a cada vez que você abrir o programa</translation>
+    </message>
+    <message>
+        <source>Your password is going to be stored in config file in plain text</source>
+        <comment>AccountPasswordDescription</comment>
+        <translation>Sua senha será armazenada em um arquivo de configurações em texto simples</translation>
+    </message>
+    <message>
+        <source>Your password is going to be stored in KDE wallet storage (KWallet). You&apos;re going to be queried for permissions</source>
+        <comment>AccountPasswordDescription</comment>
+        <translation>Sua senha será armazenada no KDE wallet (KWallet). Sua autorização será requerida</translation>
+    </message>
+</context>
+<context>
+    <name>JoinConference</name>
+    <message>
+        <source>Join new conference</source>
+        <translatorcomment>Заголовок окна</translatorcomment>
+        <translation>Entrar em uma nova conferência</translation>
+    </message>
+    <message>
+        <source>JID</source>
+        <translation>JID</translation>
+    </message>
+    <message>
+        <source>Room JID</source>
+        <translation>Sala JID</translation>
+    </message>
+    <message>
+        <source>identifier@conference.server.org</source>
+        <translation>identifier@conference.server.org</translation>
+    </message>
+    <message>
+        <source>Account</source>
+        <translation>Conta</translation>
+    </message>
+    <message>
+        <source>Join on login</source>
+        <translation>Entrar ao se conectar</translation>
+    </message>
+    <message>
+        <source>If checked Squawk will try to join this conference on login</source>
+        <translation>Se marcado, o Squawk tentará entrar nesta conferência automaticamente durante o login</translation>
+    </message>
+    <message>
+        <source>Nick name</source>
+        <translation>Apelido</translation>
+    </message>
+    <message>
+        <source>Your nick name for that conference. If you leave this field empty your account name will be used as a nick name</source>
+        <translation>Seu apelido para essa conferência. Se você deixar este campo em branco, seu nome de usuário será usado como apelido</translation>
+    </message>
+    <message>
+        <source>John</source>
+        <translation>José</translation>
+    </message>
+</context>
+<context>
+    <name>Message</name>
+    <message>
+        <source>Open</source>
+        <translation>Abrir</translation>
+    </message>
+</context>
+<context>
+    <name>MessageLine</name>
+    <message>
+        <source>Downloading...</source>
+        <translation>Baixando...</translation>
+    </message>
+    <message>
+        <source>Download</source>
+        <translation>Baixar</translation>
+    </message>
+    <message>
+        <source>Error uploading file: %1
+You can try again</source>
+        <translation>Error fazendo upload do arquivo: %1
+Você pode tentar novamente</translation>
+    </message>
+    <message>
+        <source>Upload</source>
+        <translation>Upload</translation>
+    </message>
+    <message>
+        <source>Error downloading file: %1
+You can try again</source>
+        <translation>Erro baixando arquivo: %1
+Você pode tentar novamente</translation>
+    </message>
+    <message>
+        <source>Uploading...</source>
+        <translation>Fazendo upload...</translation>
+    </message>
+    <message>
+        <source>Push the button to download the file</source>
+        <translation>Pressione o botão para baixar o arquivo</translation>
+    </message>
+</context>
+<context>
+    <name>Models::Room</name>
+    <message>
+        <source>Subscribed</source>
+        <translation>Inscrito</translation>
+    </message>
+    <message>
+        <source>Temporarily unsubscribed</source>
+        <translation>Inscrição temporariamente cancelada</translation>
+    </message>
+    <message>
+        <source>Temporarily subscribed</source>
+        <translation>Temporariamente inscrito</translation>
+    </message>
+    <message>
+        <source>Unsubscribed</source>
+        <translation>Não inscrito</translation>
+    </message>
+</context>
+<context>
+    <name>Models::Roster</name>
+    <message>
+        <source>New messages</source>
+        <translation>Novas mensagens</translation>
+    </message>
+    <message>
+        <source>New messages: </source>
+        <translation>Novas mensagens: </translation>
+    </message>
+    <message>
+        <source>Jabber ID: </source>
+        <translation>ID Jabber: </translation>
+    </message>
+    <message>
+        <source>Availability: </source>
+        <translation>Disponibilidade: </translation>
+    </message>
+    <message>
+        <source>Status: </source>
+        <translation>Status: </translation>
+    </message>
+    <message>
+        <source>Subscription: </source>
+        <translation>Inscrição: </translation>
+    </message>
+    <message>
+        <source>Affiliation: </source>
+        <translatorcomment>Я правда не знаю, как это объяснить, не то что перевести</translatorcomment>
+        <translation>Afiliação: </translation>
+    </message>
+    <message>
+        <source>Role: </source>
+        <translation>Papel: </translation>
+    </message>
+    <message>
+        <source>Online contacts: </source>
+        <translation>Contatos online: </translation>
+    </message>
+    <message>
+        <source>Total contacts: </source>
+        <translation>Contatos totais: </translation>
+    </message>
+    <message>
+        <source>Members: </source>
+        <translation>Membros: </translation>
+    </message>
+</context>
+<context>
+    <name>NewContact</name>
+    <message>
+        <source>Add new contact</source>
+        <translatorcomment>Заголовок окна</translatorcomment>
+        <translation>Adicionar novo contato</translation>
+    </message>
+    <message>
+        <source>Account</source>
+        <translation>Conta</translation>
+    </message>
+    <message>
+        <source>An account that is going to have new contact</source>
+        <translation>A conta que terá um novo contato</translation>
+    </message>
+    <message>
+        <source>JID</source>
+        <translation>JID</translation>
+    </message>
+    <message>
+        <source>Jabber id of your new contact</source>
+        <translation>ID Jabber do seu novo contato</translation>
+    </message>
+    <message>
+        <source>name@server.dmn</source>
+        <translatorcomment>Placeholder поля ввода JID</translatorcomment>
+        <translation>nome@servidor.com.br</translation>
+    </message>
+    <message>
+        <source>Name</source>
+        <translation>Nome</translation>
+    </message>
+    <message>
+        <source>The way this new contact will be labeled in your roster (optional)</source>
+        <translation>A forma com que o novo contato será classificado em sua lista (opcional)</translation>
+    </message>
+    <message>
+        <source>John Smith</source>
+        <translation>José Silva</translation>
+    </message>
+</context>
+<context>
+    <name>Squawk</name>
+    <message>
+        <source>squawk</source>
+        <translation>Squawk</translation>
+    </message>
+    <message>
+        <source>Settings</source>
+        <translation>Configurações</translation>
+    </message>
+    <message>
+        <source>Squawk</source>
+        <translation>Squawk</translation>
+    </message>
+    <message>
+        <source>Accounts</source>
+        <translation>Contas</translation>
+    </message>
+    <message>
+        <source>Quit</source>
+        <translation>Sair</translation>
+    </message>
+    <message>
+        <source>Add contact</source>
+        <translation>Adicionar contato</translation>
+    </message>
+    <message>
+        <source>Add conference</source>
+        <translation>Adicionar conferência</translation>
+    </message>
+    <message>
+        <source>Disconnect</source>
+        <translation>Desconectar</translation>
+    </message>
+    <message>
+        <source>Connect</source>
+        <translation>Conectar</translation>
+    </message>
+    <message>
+        <source>VCard</source>
+        <translation>VCard</translation>
+    </message>
+    <message>
+        <source>Remove</source>
+        <translation>Remover</translation>
+    </message>
+    <message>
+        <source>Open dialog</source>
+        <translation>Abrir caixa de diálogo</translation>
+    </message>
+    <message>
+        <source>Unsubscribe</source>
+        <translation>Cancelar inscrição</translation>
+    </message>
+    <message>
+        <source>Subscribe</source>
+        <translation>Inscrever-se</translation>
+    </message>
+    <message>
+        <source>Rename</source>
+        <translation>Renomear</translation>
+    </message>
+    <message>
+        <source>Input new name for %1
+or leave it empty for the contact 
+to be displayed as %1</source>
+        <translation>Digite um novo nome para %1
+ou o deixe em branco para o contato
+ser exibido com %1</translation>
+    </message>
+    <message>
+        <source>Renaming %1</source>
+        <translation>Renomeando %1</translation>
+    </message>
+    <message>
+        <source>Groups</source>
+        <translation>Grupos</translation>
+    </message>
+    <message>
+        <source>New group</source>
+        <translation>Novo grupo</translation>
+    </message>
+    <message>
+        <source>New group name</source>
+        <translation>Novo nome do grupo</translation>
+    </message>
+    <message>
+        <source>Add %1 to a new group</source>
+        <translation>Adicionar %1 a um novo grupo</translation>
+    </message>
+    <message>
+        <source>Open conversation</source>
+        <translation>Abrir conversa</translation>
+    </message>
+    <message>
+        <source>%1 account card</source>
+        <translation>cartão da conta %1</translation>
+    </message>
+    <message>
+        <source>%1 contact card</source>
+        <translation>cartão de contato %1</translation>
+    </message>
+    <message>
+        <source>Downloading vCard</source>
+        <translation>Baixando vCard</translation>
+    </message>
+    <message>
+        <source>Attached file</source>
+        <translation>Arquivo anexado</translation>
+    </message>
+    <message>
+        <source>Input the password for account %1</source>
+        <translation>Digite a senha para a conta %1</translation>
+    </message>
+    <message>
+        <source>Password for account %1</source>
+        <translation>Senha para a conta %1</translation>
+    </message>
+    <message>
+        <source>Please select a contact to start chatting</source>
+        <translation>Por favor selecione um contato para começar a conversar</translation>
+    </message>
+</context>
+<context>
+    <name>VCard</name>
+    <message>
+        <source>Received 12.07.2007 at 17.35</source>
+        <translation>Recebido 12/07/2007 às 17:35</translation>
+    </message>
+    <message>
+        <source>General</source>
+        <translation>Geral</translation>
+    </message>
+    <message>
+        <source>Organization</source>
+        <translation>Empresa</translation>
+    </message>
+    <message>
+        <source>Middle name</source>
+        <translation>Nome do meio</translation>
+    </message>
+    <message>
+        <source>First name</source>
+        <translation>Primeiro nome</translation>
+    </message>
+    <message>
+        <source>Last name</source>
+        <translation>Sobrenome</translation>
+    </message>
+    <message>
+        <source>Nick name</source>
+        <translation>Apelido</translation>
+    </message>
+    <message>
+        <source>Birthday</source>
+        <translation>Data de aniversário</translation>
+    </message>
+    <message>
+        <source>Organization name</source>
+        <translation>Nome da empresa</translation>
+    </message>
+    <message>
+        <source>Unit / Department</source>
+        <translation>Unidade/Departamento</translation>
+    </message>
+    <message>
+        <source>Role / Profession</source>
+        <translation>Profissão</translation>
+    </message>
+    <message>
+        <source>Job title</source>
+        <translation>Cargo</translation>
+    </message>
+    <message>
+        <source>Full name</source>
+        <translation>Nome completo</translation>
+    </message>
+    <message>
+        <source>Personal information</source>
+        <translation>Informações pessoais</translation>
+    </message>
+    <message>
+        <source>Addresses</source>
+        <translation>Endereços</translation>
+    </message>
+    <message>
+        <source>E-Mail addresses</source>
+        <translation>Endereços de e-mail</translation>
+    </message>
+    <message>
+        <source>Phone numbers</source>
+        <translation>Números de telefone</translation>
+    </message>
+    <message>
+        <source>Contact</source>
+        <translation>Contato</translation>
+    </message>
+    <message>
+        <source>Jabber ID</source>
+        <translation>ID Jabber</translation>
+    </message>
+    <message>
+        <source>Web site</source>
+        <translation>Site web</translation>
+    </message>
+    <message>
+        <source>Description</source>
+        <translation>Descrição</translation>
+    </message>
+    <message>
+        <source>Set avatar</source>
+        <translation>Definir avatar</translation>
+    </message>
+    <message>
+        <source>Clear avatar</source>
+        <translation>Apagar avatar</translation>
+    </message>
+    <message>
+        <source>Account %1 card</source>
+        <translation>Cartão da conta %1</translation>
+    </message>
+    <message>
+        <source>Contact %1 card</source>
+        <translation>Cartão do contato %1</translation>
+    </message>
+    <message>
+        <source>Received %1 at %2</source>
+        <translation>Recebido %1 em %2</translation>
+    </message>
+    <message>
+        <source>Chose your new avatar</source>
+        <translation>Escolha um novo avatar</translation>
+    </message>
+    <message>
+        <source>Images (*.png *.jpg *.jpeg)</source>
+        <translation>Imagens (*.png *.jpg *.jpeg)</translation>
+    </message>
+    <message>
+        <source>Add email address</source>
+        <translation>Adicionar endereço de e-mail</translation>
+    </message>
+    <message>
+        <source>Unset this email as preferred</source>
+        <translation>Desmarcar este e-mail como preferido</translation>
+    </message>
+    <message>
+        <source>Set this email as preferred</source>
+        <translation>Marcar este e-mail como preferido</translation>
+    </message>
+    <message>
+        <source>Remove selected email addresses</source>
+        <translation>Remover endereço de e-mail selecionado</translation>
+    </message>
+    <message>
+        <source>Copy selected emails to clipboard</source>
+        <translation>Copiar endereços de e-mails selecionados para a área de transferência</translation>
+    </message>
+    <message>
+        <source>Add phone number</source>
+        <translation>Adicionar número de telefone</translation>
+    </message>
+    <message>
+        <source>Unset this phone as preferred</source>
+        <translation>Desmarcar este número de telefone como preferido</translation>
+    </message>
+    <message>
+        <source>Set this phone as preferred</source>
+        <translation>Marcar este número de telefone como preferido</translation>
+    </message>
+    <message>
+        <source>Remove selected phone numbers</source>
+        <translation>Remover os números de telefones selecionados</translation>
+    </message>
+    <message>
+        <source>Copy selected phones to clipboard</source>
+        <translation>Copiar os números de telefone selecionados para a área de transferência</translation>
+    </message>
+</context>
+</TS>

From 3f09b8f838d78bd7029a82868d1e8edaa5c957b7 Mon Sep 17 00:00:00 2001
From: Blue <blue@macaw.me>
Date: Wed, 22 Sep 2021 01:17:43 +0300
Subject: [PATCH 126/281] Date dividers between messages from different dates

---
 CMakeLists.txt                             | 10 ++++
 shared/global.cpp                          |  2 +-
 ui/widgets/messageline/feedview.cpp        | 62 +++++++++++++++++++++-
 ui/widgets/messageline/feedview.h          |  3 ++
 ui/widgets/messageline/messagedelegate.cpp |  5 +-
 5 files changed, 78 insertions(+), 4 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index b9349d9..b978c33 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -22,6 +22,16 @@ option(WITH_KIO "Build KIO support module" ON)
 # Dependencies
 ## Qt
 find_package(Qt5 COMPONENTS Widgets DBus Gui Xml Network Core REQUIRED)
+find_package(Boost COMPONENTS)
+
+target_include_directories(squawk PRIVATE ${Boost_INCLUDE_DIRS})
+target_include_directories(squawk PRIVATE ${Qt5_INCLUDE_DIRS})
+target_include_directories(squawk PRIVATE ${Qt5Widgets_INCLUDE_DIRS})
+target_include_directories(squawk PRIVATE ${Qt5DBus_INCLUDE_DIRS})
+target_include_directories(squawk PRIVATE ${Qt5Gui_INCLUDE_DIRS})
+target_include_directories(squawk PRIVATE ${Qt5Xml_INCLUDE_DIRS})
+target_include_directories(squawk PRIVATE ${Qt5Network_INCLUDE_DIRS})
+target_include_directories(squawk PRIVATE ${Qt5Core_INCLUDE_DIRS})
 
 ## QXmpp
 if (SYSTEM_QXMPP)
diff --git a/shared/global.cpp b/shared/global.cpp
index 67e74d1..d6f2169 100644
--- a/shared/global.cpp
+++ b/shared/global.cpp
@@ -95,7 +95,7 @@ Shared::Global::Global():
     }
     
     instance = this;
-    
+
 #ifdef WITH_KIO
     openFileManagerWindowJob.load();
     if (openFileManagerWindowJob.isLoaded()) {
diff --git a/ui/widgets/messageline/feedview.cpp b/ui/widgets/messageline/feedview.cpp
index 6d8c180..7bdfb9e 100644
--- a/ui/widgets/messageline/feedview.cpp
+++ b/ui/widgets/messageline/feedview.cpp
@@ -29,6 +29,7 @@
 constexpr int maxMessageHeight = 10000;
 constexpr int approximateSingleMessageHeight = 20;
 constexpr int progressSize = 70;
+constexpr int dateDeviderMargin = 10;
 
 const std::set<int> FeedView::geometryChangingRoles = {
     Models::MessageFeed::Attach,
@@ -46,7 +47,9 @@ FeedView::FeedView(QWidget* parent):
     specialModel(false),
     clearWidgetsMode(false),
     modelState(Models::MessageFeed::complete),
-    progress()
+    progress(),
+    dividerFont(),
+    dividerMetrics(dividerFont)
 {
     horizontalScrollBar()->setRange(0, 0);
     verticalScrollBar()->setSingleStep(approximateSingleMessageHeight);
@@ -56,6 +59,15 @@ FeedView::FeedView(QWidget* parent):
     
     progress.setParent(viewport());
     progress.resize(progressSize, progressSize);
+
+    dividerFont = getFont();
+    dividerFont.setBold(true);
+    float ndps = dividerFont.pointSizeF();
+    if (ndps != -1) {
+        dividerFont.setPointSizeF(ndps * 1.2);
+    } else {
+        dividerFont.setPointSize(dividerFont.pointSize() + 2);
+    }
 }
 
 FeedView::~FeedView()
@@ -187,8 +199,16 @@ void FeedView::updateGeometries()
         
         hints.clear();
         uint32_t previousOffset = 0;
+        QDateTime lastDate;
         for (int i = 0, size = m->rowCount(); i < size; ++i) {
             QModelIndex index = m->index(i, 0, rootIndex());
+            QDateTime currentDate = index.data(Models::MessageFeed::Date).toDateTime();
+            if (i > 0) {
+                if (currentDate.daysTo(lastDate) > 0) {
+                    previousOffset += dividerMetrics.height() + dateDeviderMargin * 2;
+                }
+            }
+            lastDate = currentDate;
             int height = itemDelegate(index)->sizeHint(option, index).height();
             hints.emplace_back(Hint({
                 false,
@@ -222,8 +242,16 @@ bool FeedView::tryToCalculateGeometriesWithNoScrollbars(const QStyleOptionViewIt
 {
     uint32_t previousOffset = 0;
     bool success = true;
+    QDateTime lastDate;
     for (int i = 0, size = m->rowCount(); i < size; ++i) {
         QModelIndex index = m->index(i, 0, rootIndex());
+        QDateTime currentDate = index.data(Models::MessageFeed::Date).toDateTime();
+        if (i > 0) {
+            if (currentDate.daysTo(lastDate) > 0) {
+                previousOffset += dateDeviderMargin * 2 + dividerMetrics.height();
+            }
+        }
+        lastDate = currentDate;
         int height = itemDelegate(index)->sizeHint(option, index).height();
         
         if (previousOffset + height > totalHeight) {
@@ -266,6 +294,7 @@ void FeedView::paintEvent(QPaintEvent* event)
             toRener.emplace_back(m->index(i, 0, rootIndex()));
         }
         if (y1 > relativeY1) {
+            inZone = false;
             break;
         }
     }
@@ -282,11 +311,32 @@ void FeedView::paintEvent(QPaintEvent* event)
         }
     }
     
+    QDateTime lastDate;
+    bool first = true;
     for (const QModelIndex& index : toRener) {
+        QDateTime currentDate = index.data(Models::MessageFeed::Date).toDateTime();
         option.rect = visualRect(index);
+        if (first) {
+            int ind = index.row() - 1;
+            if (ind > 0) {
+                QDateTime underDate = m->index(ind, 0, rootIndex()).data(Models::MessageFeed::Date).toDateTime();
+                if (currentDate.daysTo(underDate) > 0) {
+                    drawDateDevider(option.rect.bottom(), underDate, painter);
+                }
+            }
+            first = false;
+        }
         bool mouseOver = option.rect.contains(cursor) && vp->rect().contains(cursor);
         option.state.setFlag(QStyle::State_MouseOver, mouseOver);
         itemDelegate(index)->paint(&painter, option, index);
+
+        if (!lastDate.isNull() && currentDate.daysTo(lastDate) > 0) {
+            drawDateDevider(option.rect.bottom(), lastDate, painter);
+        }
+        lastDate = currentDate;
+    }
+    if (!lastDate.isNull() && inZone) {     //if after drawing all messages there is still space
+        drawDateDevider(option.rect.bottom(), lastDate, painter);
     }
     
     if (clearWidgetsMode && specialDelegate) {
@@ -300,6 +350,16 @@ void FeedView::paintEvent(QPaintEvent* event)
     }
 }
 
+void FeedView::drawDateDevider(int top, const QDateTime& date, QPainter& painter)
+{
+    int divisionHeight = dateDeviderMargin * 2 + dividerMetrics.height();
+    QRect r(QPoint(0, top), QSize(viewport()->width(), divisionHeight));
+    painter.save();
+    painter.setFont(dividerFont);
+    painter.drawText(r, Qt::AlignCenter, date.toString("d MMMM"));
+    painter.restore();
+}
+
 void FeedView::verticalScrollbarValueChanged(int value)
 {
     vo = verticalScrollBar()->maximum() - value;
diff --git a/ui/widgets/messageline/feedview.h b/ui/widgets/messageline/feedview.h
index b20276c..5e08946 100644
--- a/ui/widgets/messageline/feedview.h
+++ b/ui/widgets/messageline/feedview.h
@@ -73,6 +73,7 @@ protected:
 private:
     bool tryToCalculateGeometriesWithNoScrollbars(const QStyleOptionViewItem& option, const QAbstractItemModel* model, uint32_t totalHeight);
     void positionProgress();
+    void drawDateDevider(int top, const QDateTime& date, QPainter& painter);
     
 private:
     struct Hint {
@@ -87,6 +88,8 @@ private:
     bool clearWidgetsMode;
     Models::MessageFeed::SyncState modelState;
     Progress progress;
+    QFont dividerFont;
+    QFontMetrics dividerMetrics;
     
     static const std::set<int> geometryChangingRoles;
     
diff --git a/ui/widgets/messageline/messagedelegate.cpp b/ui/widgets/messageline/messagedelegate.cpp
index 8728ba3..649230e 100644
--- a/ui/widgets/messageline/messagedelegate.cpp
+++ b/ui/widgets/messageline/messagedelegate.cpp
@@ -130,12 +130,13 @@ void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio
     }
     messageSize.rheight() += nickMetrics.lineSpacing();
     messageSize.rheight() += dateMetrics.height();
+    QString dateString = data.date.toLocalTime().toString("hh:mm");
     if (messageSize.width() < opt.rect.width()) {
         QSize senderSize = nickMetrics.boundingRect(messageRect, 0, data.sender).size();
         if (senderSize.width() > messageSize.width()) {
             messageSize.setWidth(senderSize.width());
         }
-        QSize dateSize = dateMetrics.boundingRect(messageRect, 0, data.date.toLocalTime().toString()).size();
+        QSize dateSize = dateMetrics.boundingRect(messageRect, 0, dateString).size();
         int addition = 0;
         
         if (data.correction.corrected) {
@@ -211,7 +212,7 @@ void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio
     QColor q = painter->pen().color();
     q.setAlpha(180);
     painter->setPen(q);
-    painter->drawText(opt.rect, opt.displayAlignment, data.date.toLocalTime().toString(), &rect);
+    painter->drawText(opt.rect, opt.displayAlignment, dateString, &rect);
     int currentY = opt.rect.y();
     if (data.sentByMe) {
         QLabel* statusIcon = getStatusIcon(data);

From d89c030e667a2af23777d915c7610faec636d585 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Wed, 22 Sep 2021 23:43:03 +0300
Subject: [PATCH 127/281] translation verification Portugues Brazil
 localization added provided by most welcome Bruno Fontes

---
 translations/CMakeLists.txt  |  7 +++++--
 translations/squawk.pt_BR.ts | 11 ++++++++---
 2 files changed, 13 insertions(+), 5 deletions(-)

diff --git a/translations/CMakeLists.txt b/translations/CMakeLists.txt
index c484000..86d2a8c 100644
--- a/translations/CMakeLists.txt
+++ b/translations/CMakeLists.txt
@@ -1,8 +1,11 @@
 find_package(Qt5LinguistTools)
 
-set(TS_FILES squawk.ru.ts)
+set(TS_FILES
+    squawk.ru.ts
+    squawk.pt_BR.ts
+)
 qt5_add_translation(QM_FILES ${TS_FILES})
 add_custom_target(translations ALL DEPENDS ${QM_FILES})
 install(FILES ${QM_FILES} DESTINATION ${CMAKE_INSTALL_DATADIR}/squawk/l10n)
 
-add_dependencies(${CMAKE_PROJECT_NAME} translations)
\ No newline at end of file
+add_dependencies(${CMAKE_PROJECT_NAME} translations)
diff --git a/translations/squawk.pt_BR.ts b/translations/squawk.pt_BR.ts
index 4080e5d..4330979 100644
--- a/translations/squawk.pt_BR.ts
+++ b/translations/squawk.pt_BR.ts
@@ -5,6 +5,7 @@
     <name>Account</name>
     <message>
         <source>Account</source>
+        <translatorcomment>Window header</translatorcomment>
         <translation>Conta</translation>
     </message>
     <message>
@@ -61,6 +62,7 @@
     </message>
     <message>
         <source>QXmpp</source>
+        <translatorcomment>Default resource</translatorcomment>
         <translation>QXmpp</translation>
     </message>
     <message>
@@ -384,7 +386,8 @@ p, li { white-space: pre-wrap; }
     <message>
         <source>Error uploading file: %1
 You can try again</source>
-        <translation>Error fazendo upload do arquivo: %1
+        <translation>Error fazendo upload do arquivo:
+%1
 Você pode tentar novamente</translation>
     </message>
     <message>
@@ -394,7 +397,8 @@ Você pode tentar novamente</translation>
     <message>
         <source>Error downloading file: %1
 You can try again</source>
-        <translation>Erro baixando arquivo: %1
+        <translation>Erro baixando arquivo:
+%1
 Você pode tentar novamente</translation>
     </message>
     <message>
@@ -582,7 +586,8 @@ or leave it empty for the contact
 to be displayed as %1</source>
         <translation>Digite um novo nome para %1
 ou o deixe em branco para o contato
-ser exibido com %1</translation>
+ser exibido com
+%1</translation>
     </message>
     <message>
         <source>Renaming %1</source>

From 5fbb96618fb475a296e9f00f04b3bc79758efc4c Mon Sep 17 00:00:00 2001
From: shunf4 <shun1048576@gmail.com>
Date: Tue, 5 Oct 2021 12:49:06 +0800
Subject: [PATCH 128/281] adjust CMakeLists.txt, to prepare for win32 and macos
 builds

---
 CMakeLists.txt            | 61 ++++++++++++++++++++++++++++++++++-----
 core/CMakeLists.txt       | 22 +++++++++++++-
 signalcatcher_win32.cpp   | 42 +++++++++++++++++++++++++++
 squawk.rc                 |  1 +
 ui/CMakeLists.txt         |  5 ++++
 ui/widgets/CMakeLists.txt | 10 +++++++
 6 files changed, 132 insertions(+), 9 deletions(-)
 create mode 100644 signalcatcher_win32.cpp
 create mode 100644 squawk.rc

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 771481f..194c88b 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -18,15 +18,17 @@ if(NOT CMAKE_BUILD_TYPE)
   set(CMAKE_BUILD_TYPE Debug)
 endif()
 
-set(CMAKE_CXX_FLAGS_DEBUG "-g -Wall -Wextra")
-set(CMAKE_CXX_FLAGS_RELEASE "-O3")
+if(CMAKE_COMPILER_IS_GNUCXX)
+    set(CMAKE_CXX_FLAGS_DEBUG "-g -Wall -Wextra")
+    set(CMAKE_CXX_FLAGS_RELEASE "-O3")
+endif(CMAKE_COMPILER_IS_GNUCXX)
+
 message("Build type: ${CMAKE_BUILD_TYPE}")
 
 
 set(squawk_SRC
   main.cpp
   exception.cpp
-  signalcatcher.cpp
   shared/global.cpp
   shared/utils.cpp
   shared/message.cpp
@@ -34,6 +36,13 @@ set(squawk_SRC
   shared/icons.cpp
 )
 
+if (WIN32)
+    list(APPEND squawk_SRC signalcatcher_win32.cpp)
+else (WIN32)
+    list(APPEND squawk_SRC signalcatcher.cpp)
+endif (WIN32)
+
+
 set(squawk_HEAD
   exception.h
   signalcatcher.h
@@ -47,10 +56,40 @@ set(squawk_HEAD
 )
 
 configure_file(resources/images/logo.svg squawk.svg COPYONLY)
-execute_process(COMMAND convert -background none -size 48x48 squawk.svg squawk48.png WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
-execute_process(COMMAND convert -background none -size 64x64 squawk.svg squawk64.png WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
-execute_process(COMMAND convert -background none -size 128x128 squawk.svg squawk128.png WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
-execute_process(COMMAND convert -background none -size 256x256 squawk.svg squawk256.png WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
+if (WIN32)
+    set(CONVERT_BIN magick convert)
+else(WIN32)
+    set(CONVERT_BIN convert)
+endif(WIN32)
+
+execute_process(COMMAND ${CONVERT_BIN} -background none -size 48x48 squawk.svg squawk48.png WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
+execute_process(COMMAND ${CONVERT_BIN} -background none -size 64x64 squawk.svg squawk64.png WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
+execute_process(COMMAND ${CONVERT_BIN} -background none -size 128x128 squawk.svg squawk128.png WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
+execute_process(COMMAND ${CONVERT_BIN} -background none -size 256x256 squawk.svg squawk256.png WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
+
+if (WIN32)
+    execute_process(COMMAND ${CONVERT_BIN} squawk48.png squawk64.png squawk256.png squawk.ico WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
+    set(SQUAWK_WIN_RC "${CMAKE_CURRENT_BINARY_DIR}/squawk.rc")
+endif(WIN32)
+
+if (APPLE)
+    file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/icns.iconset")
+    execute_process(COMMAND convert -background none -size 16x16 squawk.svg icns.iconset/icon_16x16.png WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
+    execute_process(COMMAND convert -background none -resize !32x32 squawk.svg "icns.iconset/icon_16x16@2x.png" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
+    execute_process(COMMAND convert -background none -resize !32x32 squawk.svg "icns.iconset/icon_32x32.png" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
+    execute_process(COMMAND convert -background none -resize !64x64 squawk.svg "icns.iconset/icon_32x32@2x.png" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
+    execute_process(COMMAND convert -background none -resize !128x128 squawk.svg "icns.iconset/icon_128x128.png" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
+    execute_process(COMMAND convert -background none -resize !256x256 squawk.svg "icns.iconset/icon_128x128@2x.png" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
+    execute_process(COMMAND convert -background none -resize !256x256 squawk.svg "icns.iconset/icon_256x256.png" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
+    execute_process(COMMAND convert -background none -resize !512x512 squawk.svg "icns.iconset/icon_256x256@2x.png" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
+    execute_process(COMMAND convert -background none -resize !512x512 squawk.svg "icns.iconset/icon_512x512.png" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
+    execute_process(COMMAND convert -background none -resize !1024x1024 squawk.svg "icns.iconset/icon_512x512@2x.png" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
+    execute_process(COMMAND iconutil -c icns "icns.iconset" -o "squawk.icns" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
+    set(MACOSX_BUNDLE_ICON_FILE squawk.icns)
+    set(APP_ICON_MACOSX ${CMAKE_CURRENT_BINARY_DIR}/squawk.icns)
+    set_source_files_properties(${APP_ICON_MACOSX} PROPERTIES
+               MACOSX_PACKAGE_LOCATION "Resources")
+endif (APPLE)
 
 configure_file(packaging/squawk.desktop squawk.desktop COPYONLY)
 
@@ -92,7 +131,13 @@ if (WITH_KWALLET)
     endif()
 endif()
 
-add_executable(squawk ${squawk_SRC} ${squawk_HEAD} ${RCC})
+set(WIN32_FLAG "")
+if (WIN32)
+    if (CMAKE_BUILD_TYPE STREQUAL "Release")
+        set(WIN32_FLAG WIN32)
+    endif()
+endif(WIN32)
+add_executable(squawk ${WIN32_FLAG} ${squawk_SRC} ${squawk_HEAD} ${RCC} ${SQUAWK_WIN_RC} ${APP_ICON_MACOSX})
 target_link_libraries(squawk Qt5::Widgets)
 
 add_subdirectory(ui)
diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt
index b74a055..0377620 100644
--- a/core/CMakeLists.txt
+++ b/core/CMakeLists.txt
@@ -8,6 +8,19 @@ find_package(Qt5Gui CONFIG REQUIRED)
 find_package(Qt5Network CONFIG REQUIRED)
 find_package(Qt5Xml CONFIG REQUIRED)
 
+# Find LMDB from system or ${LMDB_DIR}
+find_path(LMDB_INCLUDE_DIR NAMES lmdb.h PATHS "${LMDB_DIR}/include")
+
+if (UNIX AND NOT APPLE)
+    # Linux
+    find_library(LMDB_LIBRARIES NAMES liblmdb.a PATHS ${LMDB_DIR})
+    set(THREADS_PREFER_PTHREAD_FLAG ON)
+    find_package(Threads REQUIRED)
+else (UNIX AND NOT APPLE)
+    # Windows / macOS: LMDB_DIR has to be specified
+    set(LMDB_LIBRARIES "${LMDB_DIR}/lib/liblmdb.a")
+endif (UNIX AND NOT APPLE)
+
 set(squawkCORE_SRC
     squawk.cpp
     account.cpp
@@ -27,6 +40,7 @@ add_subdirectory(passwordStorageEngines)
 # Tell CMake to create the helloworld executable
 add_library(squawkCORE ${squawkCORE_SRC})
 
+target_include_directories(squawkCORE PUBLIC ${LMDB_INCLUDE_DIR})
 
 if(SYSTEM_QXMPP)
     get_target_property(QXMPP_INTERFACE_INCLUDE_DIRECTORIES QXmpp::QXmpp INTERFACE_INCLUDE_DIRECTORIES)
@@ -39,7 +53,13 @@ target_link_libraries(squawkCORE Qt5::Network)
 target_link_libraries(squawkCORE Qt5::Gui)
 target_link_libraries(squawkCORE Qt5::Xml)
 target_link_libraries(squawkCORE qxmpp)
-target_link_libraries(squawkCORE lmdb)
+
+target_link_libraries(squawkCORE ${LMDB_LIBRARIES})
+if (UNIX AND NOT APPLE)
+    # Linux
+    target_link_libraries(squawkCORE Threads::Threads)
+endif (UNIX AND NOT APPLE)
+
 target_link_libraries(squawkCORE simpleCrypt)
 if (WITH_KWALLET)
     target_link_libraries(squawkCORE kwalletPSE)
diff --git a/signalcatcher_win32.cpp b/signalcatcher_win32.cpp
new file mode 100644
index 0000000..ca7b5a2
--- /dev/null
+++ b/signalcatcher_win32.cpp
@@ -0,0 +1,42 @@
+/*
+ * Squawk messenger.
+ * Copyright (C) 2021  Shunf4 <shun1048576@gmail.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 "signalcatcher.h"
+#include <unistd.h>
+
+SignalCatcher::SignalCatcher(QCoreApplication *p_app, QObject *parent):
+    QObject(parent),
+    app(p_app)
+{
+}
+
+SignalCatcher::~SignalCatcher()
+{}
+
+void SignalCatcher::handleSigInt()
+{
+}
+
+void SignalCatcher::intSignalHandler(int unused)
+{
+}
+
+int SignalCatcher::setup_unix_signal_handlers()
+{
+    return 0;
+}
diff --git a/squawk.rc b/squawk.rc
new file mode 100644
index 0000000..6061f20
--- /dev/null
+++ b/squawk.rc
@@ -0,0 +1 @@
+IDI_ICON1               ICON    "squawk.ico"
diff --git a/ui/CMakeLists.txt b/ui/CMakeLists.txt
index 52913a8..8fd04ff 100644
--- a/ui/CMakeLists.txt
+++ b/ui/CMakeLists.txt
@@ -36,6 +36,11 @@ set(squawkUI_SRC
   utils/dropshadoweffect.cpp
 )
 
+# Add squawk.ui to squawkUI_SRC so that the .ui files are displayed in Qt Creator
+qt5_wrap_ui(squawkUI_SRC
+  squawk.ui
+)
+
 # Tell CMake to create the helloworld executable
 add_library(squawkUI ${squawkUI_SRC})
 
diff --git a/ui/widgets/CMakeLists.txt b/ui/widgets/CMakeLists.txt
index 29e2dbc..ffd661e 100644
--- a/ui/widgets/CMakeLists.txt
+++ b/ui/widgets/CMakeLists.txt
@@ -21,6 +21,16 @@ set(squawkWidgets_SRC
   joinconference.cpp
 )
 
+# Add to squawkUI_SRC so that the .ui files are displayed in Qt Creator
+qt5_wrap_ui(squawkWidgets_SRC
+  account.ui
+  accounts.ui
+  conversation.ui
+  joinconference.ui
+  newcontact.ui
+  vcard/vcard.ui
+)
+
 # Tell CMake to create the helloworld executable
 add_library(squawkWidgets ${squawkWidgets_SRC})
 

From 6764d8ea1104b8cd5787bb87ed3bdc8c5867edf2 Mon Sep 17 00:00:00 2001
From: shunf4 <shun1048576@gmail.com>
Date: Tue, 5 Oct 2021 12:56:36 +0800
Subject: [PATCH 129/281] remove dependency libuuid

---
 CMakeLists.txt   | 1 -
 shared/utils.cpp | 8 ++------
 shared/utils.h   | 1 -
 3 files changed, 2 insertions(+), 8 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 194c88b..290568f 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -147,7 +147,6 @@ add_subdirectory(external/simpleCrypt)
 
 target_link_libraries(squawk squawkUI)
 target_link_libraries(squawk squawkCORE)
-target_link_libraries(squawk uuid)
 
 add_dependencies(${CMAKE_PROJECT_NAME} translations)
 
diff --git a/shared/utils.cpp b/shared/utils.cpp
index 924be85..a7a4ecb 100644
--- a/shared/utils.cpp
+++ b/shared/utils.cpp
@@ -17,15 +17,11 @@
  */
 
 #include "utils.h"
+#include <QUuid>
 
 QString Shared::generateUUID()
 {
-    uuid_t uuid;
-    uuid_generate(uuid);
-    
-    char uuid_str[36];
-    uuid_unparse_lower(uuid, uuid_str);
-    return uuid_str;
+    return QUuid::createUuid().toString();
 }
 
 
diff --git a/shared/utils.h b/shared/utils.h
index e9e3d29..8e1e6dd 100644
--- a/shared/utils.h
+++ b/shared/utils.h
@@ -23,7 +23,6 @@
 #include <QColor>
 #include <QRegularExpression>
 
-#include <uuid/uuid.h>
 #include <vector>
 
 namespace Shared {

From c55b7c6baf932c7f8ac82f2fa5f16658bf6889ea Mon Sep 17 00:00:00 2001
From: shunf4 <shun1048576@gmail.com>
Date: Tue, 5 Oct 2021 15:11:43 +0800
Subject: [PATCH 130/281] update docs for removed uuid dep and LMDB_DIR var

---
 README.md | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/README.md b/README.md
index 30c6473..987ca53 100644
--- a/README.md
+++ b/README.md
@@ -9,7 +9,6 @@
 ### Prerequisites
 
 - QT 5.12 *(lower versions might work but it wasn't tested)*
-- uuid _(usually included in some other package, for example it's ***libutil-linux*** in archlinux)_
 - lmdb
 - CMake 3.0 or higher
 - qxmpp 1.1.0 or higher
@@ -44,7 +43,7 @@ $ git clone https://git.macaw.me/blue/squawk
 $ cd squawk
 $ mkdir build
 $ cd build
-$ cmake ..
+$ cmake .. [-DLMDB_DIR:PATH=/path/to/lmdb]
 $ cmake --build .
 ```
 
@@ -57,7 +56,7 @@ $ git clone --recurse-submodules https://git.macaw.me/blue/squawk
 $ cd squawk
 $ mkdir build
 $ cd build
-$ cmake .. -D SYSTEM_QXMPP=False
+$ cmake .. -D SYSTEM_QXMPP=False [-DLMDB_DIR:PATH=/path/to/lmdb]
 $ cmake --build .
 ```
 

From faa7d396a5665e385470f15ebc7841bcabb03f11 Mon Sep 17 00:00:00 2001
From: shunf4 <shun1048576@gmail.com>
Date: Tue, 5 Oct 2021 16:09:31 +0800
Subject: [PATCH 131/281] add liblmdb.so as possible lmdb lib name

---
 core/CMakeLists.txt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt
index 0377620..306b4f5 100644
--- a/core/CMakeLists.txt
+++ b/core/CMakeLists.txt
@@ -13,7 +13,7 @@ find_path(LMDB_INCLUDE_DIR NAMES lmdb.h PATHS "${LMDB_DIR}/include")
 
 if (UNIX AND NOT APPLE)
     # Linux
-    find_library(LMDB_LIBRARIES NAMES liblmdb.a PATHS ${LMDB_DIR})
+    find_library(sudo rLMDB_LIBRARIES NAMES liblmdb.a liblmdb.so PATHS ${LMDB_DIR})
     set(THREADS_PREFER_PTHREAD_FLAG ON)
     find_package(Threads REQUIRED)
 else (UNIX AND NOT APPLE)

From a1f3c00a5454c7908949625ed4be87bfff966951 Mon Sep 17 00:00:00 2001
From: shunf4 <shun1048576@gmail.com>
Date: Wed, 6 Oct 2021 00:15:25 +0800
Subject: [PATCH 132/281] remove dependency uuid

---
 CMakeLists.txt | 1 -
 1 file changed, 1 deletion(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 5fb09fc..7b71aea 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -94,7 +94,6 @@ find_package(LMDB REQUIRED)
 target_link_libraries(squawk PRIVATE Qt5::Core Qt5::Widgets Qt5::DBus Qt5::Network Qt5::Gui Qt5::Xml)
 target_link_libraries(squawk PRIVATE lmdb)
 target_link_libraries(squawk PRIVATE simpleCrypt)
-target_link_libraries(squawk PRIVATE uuid)
 
 # Build type
 if (NOT CMAKE_BUILD_TYPE)

From 1e37aa762c7aae7400d2aa3c16b7a76050d8aec7 Mon Sep 17 00:00:00 2001
From: shunf4 <shun1048576@gmail.com>
Date: Wed, 6 Oct 2021 00:48:25 +0800
Subject: [PATCH 133/281] generate app bundle for macOS

---
 CMakeLists.txt | 26 ++++++++++++++++++++------
 1 file changed, 20 insertions(+), 6 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 7b71aea..fe4ba01 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -13,13 +13,27 @@ include(GNUInstallDirs)
 set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake")
 
 set(WIN32_FLAG "")
-if (WIN32)
-    if (CMAKE_BUILD_TYPE STREQUAL "Release")
-        set(WIN32_FLAG WIN32)
-    endif()
-endif(WIN32)
-add_executable(squawk ${WIN32_FLAG})
+set(MACOSX_BUNDLE_FLAG "")
+if (CMAKE_BUILD_TYPE STREQUAL "Release")
+  if (WIN32)
+    set(WIN32_FLAG WIN32)
+  endif(WIN32)
+  if (APPLE)
+    set(MACOSX_BUNDLE_FLAG MACOSX_BUNDLE)
+  endif(APPLE)
+endif()
+
+add_executable(squawk ${WIN32_FLAG} ${MACOSX_BUNDLE_FLAG})
 target_include_directories(squawk PRIVATE ${CMAKE_SOURCE_DIR})
+if (CMAKE_BUILD_TYPE STREQUAL "Release")
+  if (APPLE)
+    set_target_properties(squawk PROPERTIES
+      MACOSX_BUNDLE_EXECUTABLE_NAME "Squawk"
+      MACOSX_BUNDLE_ICON_FILE "" # TODO
+      MACOSX_BUNDLE_BUNDLE_NAME "Squawk"
+      MACOSX_BUNDLE_INFO_PLIST ${CMAKE_SOURCE_DIR}/CMake/Info.plist.in)
+  endif(APPLE)
+endif()
 
 option(SYSTEM_QXMPP "Use system qxmpp lib" ON)
 option(WITH_KWALLET "Build KWallet support module" ON)

From d1f108e69dc145579f459f491d7301454ffa0eae Mon Sep 17 00:00:00 2001
From: shunf4 <shun1048576@gmail.com>
Date: Wed, 6 Oct 2021 00:53:50 +0800
Subject: [PATCH 134/281] update docs for win32 build

---
 README.md | 27 +++++++++++++++++++++++++--
 1 file changed, 25 insertions(+), 2 deletions(-)

diff --git a/README.md b/README.md
index 892168c..e94972f 100644
--- a/README.md
+++ b/README.md
@@ -14,6 +14,7 @@
 - qxmpp 1.1.0 or higher
 - KDE Frameworks: kwallet (optional)
 - KDE Frameworks: KIO (optional)
+- Boost
 
 ### Getting
 
@@ -33,6 +34,26 @@ You can also clone the repo and build it from source
 
 Squawk requires Qt with SSL enabled. It uses CMake as build system.
 
+Please check the prerequisites and install them before installation.
+
+#### For Windows (Mingw-w64) build
+
+You need Qt for mingw64 (MinGW 64-bit) platform when installing Qt.
+
+The best way to acquire library `lmdb` and `boost` is through Msys2.
+
+First install Msys2, and then install `mingw-w64-x86_64-lmdb` and `mingw-w64-x86_64-boost` by pacman.
+
+Then you need to provide the cmake cache entry when calling cmake for configuration:
+
+```
+cmake .. -D LMDB_ROOT_DIR:PATH=<Msys2 Mingw64 Root Directory> -D BOOST_ROOT:PATH=<Msys2 Mingw64 Root Directory>
+```
+
+`<Msys2 Mingw64 Root Directory>`: e.g. `C:/msys64/mingw64`.
+
+---
+
 There are two ways to build, it depends whether you have qxmpp installed in your system
 
 #### Building with system qxmpp
@@ -44,7 +65,7 @@ $ git clone https://git.macaw.me/blue/squawk
 $ cd squawk
 $ mkdir build
 $ cd build
-$ cmake .. [-DLMDB_DIR:PATH=/path/to/lmdb]
+$ cmake .. [-D LMDB_ROOT_DIR:PATH=...] [-D BOOST_ROOT:PATH=...]
 $ cmake --build .
 ```
 
@@ -57,10 +78,12 @@ $ git clone --recurse-submodules https://git.macaw.me/blue/squawk
 $ cd squawk
 $ mkdir build
 $ cd build
-$ cmake .. -D SYSTEM_QXMPP=False [-DLMDB_DIR:PATH=/path/to/lmdb]
+$ cmake .. -D SYSTEM_QXMPP=False [-D LMDB_ROOT_DIR:PATH=...] [-D BOOST_ROOT:PATH=...]
 $ cmake --build .
 ```
 
+You can always refer to `appveyor.yml` to see how AppVeyor build squawk.
+
 ### List of keys
 
 Here is the list of keys you can pass to configuration phase of `cmake ..`. 

From 261b34b7125ad00ccb244786fe11bfdf6d0373b3 Mon Sep 17 00:00:00 2001
From: shunf4 <shun1048576@gmail.com>
Date: Wed, 6 Oct 2021 00:55:39 +0800
Subject: [PATCH 135/281] add forgotten cmake/MacOSXBundleInfo.plist.in

---
 cmake/MacOSXBundleInfo.plist.in | 41 +++++++++++++++++++++++++++++++++
 1 file changed, 41 insertions(+)
 create mode 100644 cmake/MacOSXBundleInfo.plist.in

diff --git a/cmake/MacOSXBundleInfo.plist.in b/cmake/MacOSXBundleInfo.plist.in
new file mode 100644
index 0000000..ac4bbec
--- /dev/null
+++ b/cmake/MacOSXBundleInfo.plist.in
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+
+    <key>CFBundleDevelopmentRegion</key>
+    <string>English</string>
+    <key>CFBundleExecutable</key>
+    <string>${MACOSX_BUNDLE_EXECUTABLE_NAME}</string>
+    <key>CFBundleGetInfoString</key>
+    <string>${MACOSX_BUNDLE_INFO_STRING}</string>
+    <key>CFBundleIconFile</key>
+    <string>${MACOSX_BUNDLE_ICON_FILE}</string>
+    <key>CFBundleIdentifier</key>
+    <string>${MACOSX_BUNDLE_GUI_IDENTIFIER}</string>
+    <key>CFBundleInfoDictionaryVersion</key>
+    <string>6.0</string>
+    <key>CFBundleLongVersionString</key>
+    <string>${MACOSX_BUNDLE_LONG_VERSION_STRING}</string>
+    <key>CFBundleName</key>
+    <string>${MACOSX_BUNDLE_BUNDLE_NAME}</string>
+    <key>CFBundlePackageType</key>
+    <string>APPL</string>
+    <key>CFBundleShortVersionString</key>
+    <string>${MACOSX_BUNDLE_SHORT_VERSION_STRING}</string>
+    <key>CFBundleSignature</key>
+    <string>????</string>
+    <key>CFBundleVersion</key>
+    <string>${MACOSX_BUNDLE_BUNDLE_VERSION}</string>
+    <key>CSResourcesFileMapped</key>
+    <true/>
+    <key>NSHumanReadableCopyright</key>
+    <string>${MACOSX_BUNDLE_COPYRIGHT}</string>
+
+    <key>NSPrincipalClass</key>
+    <string>NSApplication</string>
+    <key>NSHighResolutionCapable</key>
+    <string>True</string>
+
+</dict>
+</plist>

From 41d9fa7a1cd4029834eb9eb4e5f84b98abd36d4a Mon Sep 17 00:00:00 2001
From: shunf4 <shun1048576@gmail.com>
Date: Wed, 6 Oct 2021 01:07:47 +0800
Subject: [PATCH 136/281] fix macos bundle build

---
 CMakeLists.txt | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index fe4ba01..96df523 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -29,9 +29,9 @@ if (CMAKE_BUILD_TYPE STREQUAL "Release")
   if (APPLE)
     set_target_properties(squawk PROPERTIES
       MACOSX_BUNDLE_EXECUTABLE_NAME "Squawk"
-      MACOSX_BUNDLE_ICON_FILE "" # TODO
+      MACOSX_BUNDLE_ICON_FILE "${MACOSX_BUNDLE_ICON_FILE}" # TODO
       MACOSX_BUNDLE_BUNDLE_NAME "Squawk"
-      MACOSX_BUNDLE_INFO_PLIST ${CMAKE_SOURCE_DIR}/CMake/Info.plist.in)
+      MACOSX_BUNDLE_INFO_PLIST ${CMAKE_SOURCE_DIR}/cmake/MacOSXBundleInfo.plist.in)
   endif(APPLE)
 endif()
 

From f3153ef1dbcbf95ed4071aa11ff439532abc23e8 Mon Sep 17 00:00:00 2001
From: shunf4 <shun1048576@gmail.com>
Date: Wed, 6 Oct 2021 01:17:30 +0800
Subject: [PATCH 137/281] ci: add appveyor.yml

---
 appveyor.yml | 113 +++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 113 insertions(+)
 create mode 100644 appveyor.yml

diff --git a/appveyor.yml b/appveyor.yml
new file mode 100644
index 0000000..763a751
--- /dev/null
+++ b/appveyor.yml
@@ -0,0 +1,113 @@
+image:
+  - Visual Studio 2019
+  - "Previous Ubuntu1804"
+  - macOS
+
+branches:
+  except:
+    - gh-pages
+
+for:
+-
+  matrix:
+    only:
+      - image: Visual Studio 2019
+
+  environment:
+      QTDIR: C:\Qt\5.15.2\mingw81_64
+      QTTOOLDIR: C:\Qt\Tools\mingw810_64\bin
+      QTNINJADIR: C:\Qt\Tools\Ninja
+
+  install:
+      - set PATH=%QTTOOLDIR%;%QTNINJADIR%;%QTDIR%\bin;%PATH%
+      - git submodule update --init --recursive
+
+  before_build:
+      - choco install --yes zstandard
+      - choco install --yes --version=7.1.0.2 imagemagick.app
+      - set PATH=C:\Program Files\ImageMagick-7.1.0-Q16-HDRI;%PATH%
+
+      - mkdir lmdb
+      - cd lmdb
+      - curl -OL https://mirror.msys2.org/mingw/mingw64/mingw-w64-x86_64-lmdb-0.9.27-1-any.pkg.tar.zst
+      - zstd -d ./mingw-w64-x86_64-lmdb-0.9.27-1-any.pkg.tar.zst
+      - tar -xvf ./mingw-w64-x86_64-lmdb-0.9.27-1-any.pkg.tar
+      - cd ..
+
+      - mkdir boost
+      - cd boost
+      - curl -OL https://repo.msys2.org/mingw/mingw64/mingw-w64-x86_64-boost-1.77.0-2-any.pkg.tar.zst
+      - zstd -d ./mingw-w64-x86_64-boost-1.77.0-2-any.pkg.tar.zst
+      - tar -xvf ./mingw-w64-x86_64-boost-1.77.0-2-any.pkg.tar
+      - cd ..
+
+      - mkdir build
+      - cd build
+      - cmake -GNinja -DCMAKE_BUILD_TYPE:String=Release -DCMAKE_PREFIX_PATH:STRING=%QTDIR% -DLMDB_ROOT_DIR:PATH=C:/projects/squawk/lmdb/mingw64  -DBOOST_ROOT:PATH=C:/projects/squawk/boost/mingw64 ..
+
+  build_script:
+      - cmake --build .
+      - mkdir deploy
+      - cd deploy
+      - copy ..\squawk.exe .\
+      - copy ..\external\qxmpp\src\libqxmpp.dll .\
+      - windeployqt .\squawk.exe
+      - windeployqt .\libqxmpp.dll
+      - cd ..\..
+
+  artifacts:
+      - path: build/deploy/squawk.exe
+        name: Squawk executable (Qt 5.15.2)
+
+      - path: build/deploy
+        name: Squawk deployment
+    
+-
+  matrix:
+    only:
+      - image: macOS
+
+  install:
+      - brew install lmdb imagemagick boost
+      - git submodule update --init --recursive
+
+  before_build:
+      - mkdir build
+      - cd build
+      - cmake -DCMAKE_BUILD_TYPE:String=Release -DCMAKE_PREFIX_PATH:STRING=$HOME/Qt/5.15.2/clang_64 ..
+
+  build_script:
+      - cmake --build .
+
+  artifacts:
+      - path: build/squawk
+        name: Squawk executable (Qt 5.15.2)
+      - path: build/external/qxmpp/src/
+        name: QXMPP
+      - path: build/squawk.app
+        name: Squawk Bundle (Qt 5.15.2)
+
+-
+  matrix:
+    only:
+      - image: "Previous Ubuntu1804"
+
+  install:
+      - ls $HOME/Qt
+      - sudo apt update
+      - sudo apt install -y liblmdb-dev liblmdb0 imagemagick mesa-common-dev libglu1-mesa-dev libboost-all-dev
+      - git submodule update --init --recursive
+
+  before_build:
+      - mkdir build
+      - cd build
+      - cmake -DCMAKE_BUILD_TYPE:String=Release -DCMAKE_PREFIX_PATH:STRING=$HOME/Qt/5.12.10/gcc_64 ..
+
+  build_script:
+      - cmake --build .
+
+  artifacts:
+      - path: build/squawk
+        name: Squawk executable (Qt 5.12)
+      - path: build/external/qxmpp/src/
+        name: QXMPP

From 4f6946a5fc0e68b45d2147c2dac00ada01018f25 Mon Sep 17 00:00:00 2001
From: shunf4 <shun1048576@gmail.com>
Date: Wed, 6 Oct 2021 01:28:08 +0800
Subject: [PATCH 138/281] add LMDB_INCLUDE_DIRS as include dir, fixing win32 ci

---
 CMakeLists.txt | 1 +
 1 file changed, 1 insertion(+)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 96df523..1645ffc 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -52,6 +52,7 @@ target_include_directories(squawk PRIVATE ${Qt5Gui_INCLUDE_DIRS})
 target_include_directories(squawk PRIVATE ${Qt5Xml_INCLUDE_DIRS})
 target_include_directories(squawk PRIVATE ${Qt5Network_INCLUDE_DIRS})
 target_include_directories(squawk PRIVATE ${Qt5Core_INCLUDE_DIRS})
+target_include_directories(squawk PRIVATE ${LMDB_INCLUDE_DIRS})
 
 ## QXmpp
 if (SYSTEM_QXMPP)

From d84a33e144f5d0e93b5ccd15379ed45a67e3d3de Mon Sep 17 00:00:00 2001
From: shunf4 <shun1048576@gmail.com>
Date: Wed, 6 Oct 2021 01:33:58 +0800
Subject: [PATCH 139/281] fix linux ci by finding and linking thread libraries

---
 CMakeLists.txt | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 1645ffc..5209974 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -109,6 +109,12 @@ find_package(LMDB REQUIRED)
 target_link_libraries(squawk PRIVATE Qt5::Core Qt5::Widgets Qt5::DBus Qt5::Network Qt5::Gui Qt5::Xml)
 target_link_libraries(squawk PRIVATE lmdb)
 target_link_libraries(squawk PRIVATE simpleCrypt)
+# Link thread libraries on Linux
+if(UNIX AND NOT APPLE)
+  set(THREADS_PREFER_PTHREAD_FLAG ON)
+  find_package(Threads REQUIRED)
+  target_link_libraries(squawk PRIVATE Threads::Threads)
+endif()
 
 # Build type
 if (NOT CMAKE_BUILD_TYPE)

From 1c0802c8ca6262f6815fd16119a1e5d412c4d534 Mon Sep 17 00:00:00 2001
From: shunf4 <shun1048576@gmail.com>
Date: Wed, 6 Oct 2021 01:51:01 +0800
Subject: [PATCH 140/281] fix win32 ci by place LMDB_INCLUDE_DIRS in core/

---
 CMakeLists.txt      | 1 -
 core/CMakeLists.txt | 2 ++
 2 files changed, 2 insertions(+), 1 deletion(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 5209974..1b2ca7b 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -52,7 +52,6 @@ target_include_directories(squawk PRIVATE ${Qt5Gui_INCLUDE_DIRS})
 target_include_directories(squawk PRIVATE ${Qt5Xml_INCLUDE_DIRS})
 target_include_directories(squawk PRIVATE ${Qt5Network_INCLUDE_DIRS})
 target_include_directories(squawk PRIVATE ${Qt5Core_INCLUDE_DIRS})
-target_include_directories(squawk PRIVATE ${LMDB_INCLUDE_DIRS})
 
 ## QXmpp
 if (SYSTEM_QXMPP)
diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt
index e30cc7e..9369cb7 100644
--- a/core/CMakeLists.txt
+++ b/core/CMakeLists.txt
@@ -28,5 +28,7 @@ target_sources(squawk PRIVATE
   urlstorage.h
   )
 
+target_include_directories(squawk PRIVATE ${LMDB_INCLUDE_DIRS})
+
 add_subdirectory(handlers)
 add_subdirectory(passwordStorageEngines)

From 8c6ac1a21d50cf38be1a796d395998dc79a22385 Mon Sep 17 00:00:00 2001
From: shunf4 <shun1048576@gmail.com>
Date: Wed, 6 Oct 2021 17:32:46 +0800
Subject: [PATCH 141/281] add icns to macos bundle; use macdeployqt post build

---
 CMakeLists.txt           | 18 ++++++++----------
 resources/CMakeLists.txt | 23 +++++++++++++++++++----
 2 files changed, 27 insertions(+), 14 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 1b2ca7b..8632b38 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -25,15 +25,6 @@ endif()
 
 add_executable(squawk ${WIN32_FLAG} ${MACOSX_BUNDLE_FLAG})
 target_include_directories(squawk PRIVATE ${CMAKE_SOURCE_DIR})
-if (CMAKE_BUILD_TYPE STREQUAL "Release")
-  if (APPLE)
-    set_target_properties(squawk PROPERTIES
-      MACOSX_BUNDLE_EXECUTABLE_NAME "Squawk"
-      MACOSX_BUNDLE_ICON_FILE "${MACOSX_BUNDLE_ICON_FILE}" # TODO
-      MACOSX_BUNDLE_BUNDLE_NAME "Squawk"
-      MACOSX_BUNDLE_INFO_PLIST ${CMAKE_SOURCE_DIR}/cmake/MacOSXBundleInfo.plist.in)
-  endif(APPLE)
-endif()
 
 option(SYSTEM_QXMPP "Use system qxmpp lib" ON)
 option(WITH_KWALLET "Build KWallet support module" ON)
@@ -142,4 +133,11 @@ add_subdirectory(ui)
 # Install the executable
 install(TARGETS squawk DESTINATION ${CMAKE_INSTALL_BINDIR})
 
-target_sources(squawk PRIVATE ${SQUAWK_WIN_RC} ${APP_ICON_MACOSX})
+if (CMAKE_BUILD_TYPE STREQUAL "Release")
+  if (APPLE)
+    add_custom_command(TARGET squawk POST_BUILD COMMENT "Running macdeployqt..."
+      COMMAND "${Qt5Widgets_DIR}/../../../bin/macdeployqt" "${CMAKE_CURRENT_BINARY_DIR}/squawk.app"
+    )
+  endif(APPLE)
+endif()
+
diff --git a/resources/CMakeLists.txt b/resources/CMakeLists.txt
index 42cb360..9288650 100644
--- a/resources/CMakeLists.txt
+++ b/resources/CMakeLists.txt
@@ -15,7 +15,9 @@ execute_process(COMMAND ${CONVERT_BIN} -background none -size 256x256 squawk.svg
 
 if (WIN32)
     execute_process(COMMAND ${CONVERT_BIN} squawk48.png squawk64.png squawk256.png squawk.ico WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
-    set(SQUAWK_WIN_RC "${CMAKE_CURRENT_BINARY_DIR}/squawk.rc" PARENT_SCOPE)
+    set(SQUAWK_WIN_RC "${CMAKE_CURRENT_BINARY_DIR}/squawk.rc")
+    set(SQUAWK_WIN_RC "${SQUAWK_WIN_RC}" PARENT_SCOPE)
+    target_sources(squawk PRIVATE ${SQUAWK_WIN_RC})
 endif(WIN32)
 
 if (APPLE)
@@ -31,10 +33,22 @@ if (APPLE)
     execute_process(COMMAND convert -background none -resize !512x512 squawk.svg "icns.iconset/icon_512x512.png" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
     execute_process(COMMAND convert -background none -resize !1024x1024 squawk.svg "icns.iconset/icon_512x512@2x.png" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
     execute_process(COMMAND iconutil -c icns "icns.iconset" -o "squawk.icns" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
-    set(MACOSX_BUNDLE_ICON_FILE squawk.icns PARENT_SCOPE)
-    set(APP_ICON_MACOSX ${CMAKE_CURRENT_BINARY_DIR}/squawk.icns PARENT_SCOPE)
-    set_source_files_properties(${APP_ICON_MACOSX} PROPERTIES
+    set(MACOSX_BUNDLE_ICON_FILE squawk.icns)
+    set(MACOSX_BUNDLE_ICON_FILE ${MACOSX_BUNDLE_ICON_FILE} PARENT_SCOPE)
+    set(APP_ICON_MACOSX ${CMAKE_CURRENT_BINARY_DIR}/squawk.icns)
+    set(APP_ICON_MACOSX ${APP_ICON_MACOSX} PARENT_SCOPE)
+    target_sources(squawk PRIVATE ${APP_ICON_MACOSX})
+    set_source_files_properties(${APP_ICON_MACOSX} TARGET_DIRECTORY squawk PROPERTIES
                MACOSX_PACKAGE_LOCATION "Resources")
+    if (CMAKE_BUILD_TYPE STREQUAL "Release")
+      if (APPLE)
+        set_target_properties(squawk PROPERTIES
+          MACOSX_BUNDLE_EXECUTABLE_NAME "Squawk"
+          MACOSX_BUNDLE_ICON_FILE "${MACOSX_BUNDLE_ICON_FILE}" # TODO
+          MACOSX_BUNDLE_BUNDLE_NAME "Squawk"
+          MACOSX_BUNDLE_INFO_PLIST ${CMAKE_SOURCE_DIR}/cmake/MacOSXBundleInfo.plist.in)
+      endif(APPLE)
+    endif()
 endif (APPLE)
 
 install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk.svg DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/scalable/apps)
@@ -42,3 +56,4 @@ install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk48.png DESTINATION ${CMAKE_INSTA
 install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk64.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/64x64/apps RENAME squawk.png)
 install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk128.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/128x128/apps RENAME squawk.png)
 install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk256.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/256x256/apps RENAME squawk.png)
+

From 8b3752ef476cc79b8095701b172c9d8f6bea1ac9 Mon Sep 17 00:00:00 2001
From: shunf4 <shun1048576@gmail.com>
Date: Wed, 6 Oct 2021 18:12:26 +0800
Subject: [PATCH 142/281] fix ci for macos; adjust ci for linux executable with
 changed rpath

---
 appveyor.yml | 24 ++++++++++++++----------
 1 file changed, 14 insertions(+), 10 deletions(-)

diff --git a/appveyor.yml b/appveyor.yml
index 763a751..9b20f3b 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -1,7 +1,7 @@
 image:
   - Visual Studio 2019
   - "Previous Ubuntu1804"
-  - macOS
+  - macOS-Mojave
 
 branches:
   except:
@@ -60,7 +60,7 @@ for:
         name: Squawk executable (Qt 5.15.2)
 
       - path: build/deploy
-        name: Squawk deployment
+        name: Squawk deployment with Qt Framework
     
 -
   matrix:
@@ -79,13 +79,16 @@ for:
   build_script:
       - cmake --build .
 
+  after_build:
+      - zip -r squawk.app.zip squawk.app
+
   artifacts:
-      - path: build/squawk
+      - path: build/squawk.app/Contents/MacOS/squawk
         name: Squawk executable (Qt 5.15.2)
       - path: build/external/qxmpp/src/
         name: QXMPP
-      - path: build/squawk.app
-        name: Squawk Bundle (Qt 5.15.2)
+      - path: build/squawk.app.zip
+        name: Squawk Bundle with Qt Framework (Qt 5.15.2)
 
 -
   matrix:
@@ -101,13 +104,14 @@ for:
   before_build:
       - mkdir build
       - cd build
-      - cmake -DCMAKE_BUILD_TYPE:String=Release -DCMAKE_PREFIX_PATH:STRING=$HOME/Qt/5.12.10/gcc_64 ..
+      - cmake -DCMAKE_BUILD_TYPE:String=Release -DCMAKE_PREFIX_PATH:STRING=$HOME/Qt/5.12.10/gcc_64 -DCMAKE_BUILD_RPATH="\$ORIGIN" ..
 
   build_script:
       - cmake --build .
 
+  after_build:
+      - zip -r squawk.zip squawk -j external/qxmpp/src/libqxmpp*
+
   artifacts:
-      - path: build/squawk
-        name: Squawk executable (Qt 5.12)
-      - path: build/external/qxmpp/src/
-        name: QXMPP
+      - path: build/squawk.zip
+        name: Squawk executable and libraries (Qt 5.12)

From d0bdb374a04f93644758439c9d8fad1e81415f13 Mon Sep 17 00:00:00 2001
From: shunf4 <shun1048576@gmail.com>
Date: Wed, 6 Oct 2021 18:47:59 +0800
Subject: [PATCH 143/281] add flag -fno-sized-deallocation, eliminating _ZdlPvm

---
 CMakeLists.txt | 1 +
 1 file changed, 1 insertion(+)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 8632b38..da89682 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -118,6 +118,7 @@ target_compile_options(squawk PRIVATE
   "-Wall;-Wextra"
   "$<$<CONFIG:DEBUG>:-g>"
   "$<$<CONFIG:RELEASE>:-O3>"
+  "-fno-sized-deallocation" # for eliminating _ZdlPvm
   )
 endif(CMAKE_COMPILER_IS_GNUCXX)
 

From 67e5f9744ef1fc94e347bf3f3684312c56c98ea6 Mon Sep 17 00:00:00 2001
From: shunf4 <shun1048576@gmail.com>
Date: Wed, 6 Oct 2021 18:50:00 +0800
Subject: [PATCH 144/281] fix ci macos matrix item

---
 appveyor.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/appveyor.yml b/appveyor.yml
index 9b20f3b..30a8125 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -65,7 +65,7 @@ for:
 -
   matrix:
     only:
-      - image: macOS
+      - image: macOS-Mojave
 
   install:
       - brew install lmdb imagemagick boost

From 7db269acb588f4311f927115f2b5f6973fbd871b Mon Sep 17 00:00:00 2001
From: shunf4 <shun1048576@gmail.com>
Date: Wed, 6 Oct 2021 19:15:45 +0800
Subject: [PATCH 145/281] Fixes for Windows

1. On Windows, the lmdb file is immediately allocated at full size. So we have to limit the size.

2. Windows need an organization name for QSettings to work. So an organization name is added for Windows target.
---
 core/archive.cpp | 10 +++++++++-
 core/main.cpp    | 11 ++++++++++-
 2 files changed, 19 insertions(+), 2 deletions(-)

diff --git a/core/archive.cpp b/core/archive.cpp
index 2582ff9..c67f228 100644
--- a/core/archive.cpp
+++ b/core/archive.cpp
@@ -58,7 +58,15 @@ void Core::Archive::open(const QString& account)
         }
         
         mdb_env_set_maxdbs(environment, 5);
-        mdb_env_set_mapsize(environment, 512UL * 1024UL * 1024UL);
+        mdb_env_set_mapsize(environment,
+#ifdef Q_OS_WIN
+                            // On Windows, the file is immediately allocated.
+                            // So we have to limit the size.
+                            80UL * 1024UL * 1024UL
+#else
+                            512UL * 1024UL * 1024UL
+#endif
+        );
         mdb_env_open(environment, path.toStdString().c_str(), 0, 0664);
         
         MDB_txn *txn;
diff --git a/core/main.cpp b/core/main.cpp
index 0be020e..f63d4f8 100644
--- a/core/main.cpp
+++ b/core/main.cpp
@@ -42,7 +42,16 @@ int main(int argc, char *argv[])
     
     QApplication app(argc, argv);
     SignalCatcher sc(&app);
-    
+#ifdef Q_OS_WIN
+    // Windows need an organization name for QSettings to work
+    // https://doc.qt.io/qt-5/qsettings.html#basic-usage
+    {
+        const QString& orgName = QApplication::organizationName();
+        if (orgName.isNull() || orgName.isEmpty()) {
+            QApplication::setOrganizationName("squawk");
+        }
+    }
+#endif
     QApplication::setApplicationName("squawk");
     QApplication::setApplicationDisplayName("Squawk");
     QApplication::setApplicationVersion("0.1.5");

From a53126d8bca5fc9a6a4848c63e7fe5b241b86ba0 Mon Sep 17 00:00:00 2001
From: shunf4 <shun1048576@gmail.com>
Date: Wed, 6 Oct 2021 22:04:29 +0800
Subject: [PATCH 146/281] messages may have the same timestamp, put MDB_DUPSORT
 flag with order db

---
 core/archive.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/core/archive.cpp b/core/archive.cpp
index 2582ff9..bfb4b20 100644
--- a/core/archive.cpp
+++ b/core/archive.cpp
@@ -64,7 +64,7 @@ void Core::Archive::open(const QString& account)
         MDB_txn *txn;
         mdb_txn_begin(environment, NULL, 0, &txn);
         mdb_dbi_open(txn, "main", MDB_CREATE, &main);
-        mdb_dbi_open(txn, "order", MDB_CREATE | MDB_INTEGERKEY, &order);
+        mdb_dbi_open(txn, "order", MDB_CREATE | MDB_INTEGERKEY | MDB_INTEGERDUP | MDB_DUPSORT, &order);
         mdb_dbi_open(txn, "stats", MDB_CREATE, &stats);
         mdb_dbi_open(txn, "avatars", MDB_CREATE, &avatars);
         mdb_dbi_open(txn, "sid", MDB_CREATE, &sid);

From ebeb4089ebf49c1509a0a782cbcc6468643aacb4 Mon Sep 17 00:00:00 2001
From: shunf4 <shun1048576@gmail.com>
Date: Wed, 6 Oct 2021 22:45:10 +0800
Subject: [PATCH 147/281] add fallback icons for buttons

---
 ui/squawk.ui                       | 20 +++++++++++---------
 ui/utils/badge.cpp                 |  7 ++++++-
 ui/widgets/conversation.cpp        |  8 ++++++--
 ui/widgets/conversation.ui         | 20 +++++++++++---------
 ui/widgets/messageline/preview.cpp |  7 +++++++
 ui/widgets/vcard/vcard.ui          | 14 +++++++-------
 6 files changed, 48 insertions(+), 28 deletions(-)

diff --git a/ui/squawk.ui b/ui/squawk.ui
index f6cb300..a4d0258 100644
--- a/ui/squawk.ui
+++ b/ui/squawk.ui
@@ -184,8 +184,8 @@
   </widget>
   <action name="actionAccounts">
    <property name="icon">
-    <iconset theme="system-users">
-     <normaloff>.</normaloff>.</iconset>
+    <iconset theme="system-users" resource="../resources/resources.qrc">
+     <normaloff>:/images/fallback/dark/big/group.svg</normaloff>:/images/fallback/dark/big/group.svg</iconset>
    </property>
    <property name="text">
     <string>Accounts</string>
@@ -193,8 +193,8 @@
   </action>
   <action name="actionQuit">
    <property name="icon">
-    <iconset theme="application-exit">
-     <normaloff>.</normaloff>.</iconset>
+    <iconset theme="application-exit" resource="../resources/resources.qrc">
+     <normaloff>:/images/fallback/dark/big/edit-none.svg</normaloff>:/images/fallback/dark/big/edit-none.svg</iconset>
    </property>
    <property name="text">
     <string>Quit</string>
@@ -205,8 +205,8 @@
     <bool>false</bool>
    </property>
    <property name="icon">
-    <iconset theme="list-add-user">
-     <normaloff>.</normaloff>.</iconset>
+    <iconset theme="list-add-user" resource="../resources/resources.qrc">
+     <normaloff>:/images/fallback/dark/big/add.svg</normaloff>:/images/fallback/dark/big/add.svg</iconset>
    </property>
    <property name="text">
     <string>Add contact</string>
@@ -217,14 +217,16 @@
     <bool>false</bool>
    </property>
    <property name="icon">
-    <iconset theme="resource-group-new">
-     <normaloff>.</normaloff>.</iconset>
+    <iconset theme="resource-group-new" resource="../resources/resources.qrc">
+     <normaloff>:/images/fallback/dark/big/group-new.svg</normaloff>:/images/fallback/dark/big/group-new.svg</iconset>
    </property>
    <property name="text">
     <string>Add conference</string>
    </property>
   </action>
  </widget>
- <resources/>
+ <resources>
+  <include location="../resources/resources.qrc"/>
+ </resources>
  <connections/>
 </ui>
diff --git a/ui/utils/badge.cpp b/ui/utils/badge.cpp
index ef15bd2..7575afc 100644
--- a/ui/utils/badge.cpp
+++ b/ui/utils/badge.cpp
@@ -32,7 +32,12 @@ Badge::Badge(const QString& p_id, const QString& p_text, const QIcon& icon, QWid
     setFrameShadow(QFrame::Raised);
     
     image->setPixmap(icon.pixmap(25, 25));
-    closeButton->setIcon(QIcon::fromTheme("tab-close"));
+    QIcon tabCloseIcon = QIcon::fromTheme("tab-close");
+    if (tabCloseIcon.isNull()) {
+        tabCloseIcon.addFile(QString::fromUtf8(":/images/fallback/dark/big/edit-none.svg"), QSize(), QIcon::Normal, QIcon::Off);
+    }
+    closeButton->setIcon(tabCloseIcon);
+
     closeButton->setMaximumHeight(25);
     closeButton->setMaximumWidth(25);
     
diff --git a/ui/widgets/conversation.cpp b/ui/widgets/conversation.cpp
index d003551..1276ff9 100644
--- a/ui/widgets/conversation.cpp
+++ b/ui/widgets/conversation.cpp
@@ -255,8 +255,12 @@ void Conversation::addAttachedFile(const QString& path)
     QMimeDatabase db;
     QMimeType type = db.mimeTypeForFile(path);
     QFileInfo info(path);
-    
-    Badge* badge = new Badge(path, info.fileName(), QIcon::fromTheme(type.iconName()));
+
+    QIcon fileIcon = QIcon::fromTheme(type.iconName());
+    if (fileIcon.isNull()) {
+        fileIcon.addFile(QString::fromUtf8(":/images/fallback/dark/big/mail-attachment.svg"), QSize(), QIcon::Normal, QIcon::Off);
+    }
+    Badge* badge = new Badge(path, info.fileName(), fileIcon);
     
     connect(badge, &Badge::close, this, &Conversation::onBadgeClose);
     try {
diff --git a/ui/widgets/conversation.ui b/ui/widgets/conversation.ui
index bb38666..483375a 100644
--- a/ui/widgets/conversation.ui
+++ b/ui/widgets/conversation.ui
@@ -271,8 +271,8 @@
             <string/>
            </property>
            <property name="icon">
-            <iconset theme="smiley-shape">
-             <normaloff>.</normaloff>.</iconset>
+            <iconset theme="smiley-shape" resource="../../resources/resources.qrc">
+             <normaloff>:/images/fallback/dark/big/unfavorite.svg</normaloff>:/images/fallback/dark/big/unfavorite.svg</iconset>
            </property>
            <property name="flat">
             <bool>true</bool>
@@ -298,8 +298,8 @@
             <string/>
            </property>
            <property name="icon">
-            <iconset theme="mail-attachment-symbolic">
-             <normaloff>.</normaloff>.</iconset>
+            <iconset theme="mail-attachment-symbolic" resource="../../resources/resources.qrc">
+             <normaloff>:/images/fallback/dark/big/mail-attachment.svg</normaloff>:/images/fallback/dark/big/mail-attachment.svg</iconset>
            </property>
            <property name="flat">
             <bool>true</bool>
@@ -312,8 +312,8 @@
             <string/>
            </property>
            <property name="icon">
-            <iconset theme="edit-clear-all">
-             <normaloff>.</normaloff>.</iconset>
+            <iconset theme="edit-clear-all" resource="../../resources/resources.qrc">
+             <normaloff>:/images/fallback/dark/big/clean.svg</normaloff>:/images/fallback/dark/big/clean.svg</iconset>
            </property>
            <property name="flat">
             <bool>true</bool>
@@ -332,8 +332,8 @@
             <string/>
            </property>
            <property name="icon">
-            <iconset theme="document-send">
-             <normaloff>.</normaloff>.</iconset>
+            <iconset theme="document-send" resource="../../resources/resources.qrc">
+             <normaloff>:/images/fallback/dark/big/send.svg</normaloff>:/images/fallback/dark/big/send.svg</iconset>
            </property>
            <property name="flat">
             <bool>true</bool>
@@ -419,6 +419,8 @@ p, li { white-space: pre-wrap; }
    </item>
   </layout>
  </widget>
- <resources/>
+ <resources>
+  <include location="../../resources/resources.qrc"/>
+ </resources>
  <connections/>
 </ui>
diff --git a/ui/widgets/messageline/preview.cpp b/ui/widgets/messageline/preview.cpp
index a64c036..e54fce6 100644
--- a/ui/widgets/messageline/preview.cpp
+++ b/ui/widgets/messageline/preview.cpp
@@ -164,6 +164,9 @@ void Preview::applyNewSize()
             break;
         default: {
             QIcon icon = QIcon::fromTheme(info.mime.iconName());
+            if (icon.isNull()) {
+                icon.addFile(QString::fromUtf8(":/images/fallback/dark/big/mail-attachment.svg"), QSize(), QIcon::Normal, QIcon::Off);
+            }
             widget->setPixmap(icon.pixmap(actualSize));
             widget->resize(actualSize);
         }
@@ -264,6 +267,10 @@ void Preview::initializeElements()
             break;
         default: {
             QIcon icon = QIcon::fromTheme(info.mime.iconName());
+            if (icon.isNull()) {
+                icon.addFile(QString::fromUtf8(":/images/fallback/dark/big/mail-attachment.svg"), QSize(), QIcon::Normal, QIcon::Off);
+            }
+
             widget = new QLabel(parent);
             widget->setPixmap(icon.pixmap(actualSize));
             widget->show();
diff --git a/ui/widgets/vcard/vcard.ui b/ui/widgets/vcard/vcard.ui
index 26db8f9..b71d262 100644
--- a/ui/widgets/vcard/vcard.ui
+++ b/ui/widgets/vcard/vcard.ui
@@ -482,8 +482,8 @@
             <string/>
            </property>
            <property name="icon">
-            <iconset theme="user">
-             <normaloff>.</normaloff>.</iconset>
+            <iconset theme="user" resource="../../../resources/resources.qrc">
+             <normaloff>:/images/fallback/dark/big/user.svg</normaloff>:/images/fallback/dark/big/user.svg</iconset>
            </property>
            <property name="iconSize">
             <size>
@@ -852,8 +852,8 @@
   </layout>
   <action name="actionSetAvatar">
    <property name="icon">
-    <iconset theme="photo">
-     <normaloff>.</normaloff>.</iconset>
+    <iconset theme="photo" resource="../../../resources/resources.qrc">
+     <normaloff>:/images/fallback/dark/big/edit-rename.svg</normaloff>:/images/fallback/dark/big/edit-rename.svg</iconset>
    </property>
    <property name="text">
     <string>Set avatar</string>
@@ -861,8 +861,8 @@
   </action>
   <action name="actionClearAvatar">
    <property name="icon">
-    <iconset theme="edit-clear-all">
-     <normaloff>.</normaloff>.</iconset>
+    <iconset theme="edit-clear-all" resource="../../../resources/resources.qrc">
+     <normaloff>:/images/fallback/dark/big/clean.svg</normaloff>:/images/fallback/dark/big/clean.svg</iconset>
    </property>
    <property name="text">
     <string>Clear avatar</string>
@@ -886,7 +886,7 @@
   <tabstop>description</tabstop>
  </tabstops>
  <resources>
-  <include location="../../resources/resources.qrc"/>
+  <include location="../../../resources/resources.qrc"/>
  </resources>
  <connections/>
 </ui>

From 5862f1552ba15bd31daf492f21811c5a1b1a4cab Mon Sep 17 00:00:00 2001
From: shunf4 <shun1048576@gmail.com>
Date: Wed, 6 Oct 2021 22:52:20 +0800
Subject: [PATCH 148/281] don't save settings on quit, if readSettings() not
 finished

---
 core/squawk.cpp | 69 ++++++++++++++++++++++++++-----------------------
 core/squawk.h   |  1 +
 2 files changed, 38 insertions(+), 32 deletions(-)

diff --git a/core/squawk.cpp b/core/squawk.cpp
index 6b8af49..d989afc 100644
--- a/core/squawk.cpp
+++ b/core/squawk.cpp
@@ -27,7 +27,8 @@ Core::Squawk::Squawk(QObject* parent):
     accounts(),
     amap(),
     network(),
-    waitingForAccounts(0)
+    waitingForAccounts(0),
+    isInitialized(false)
 #ifdef WITH_KWALLET
     ,kwallet()
 #endif
@@ -66,39 +67,42 @@ void Core::Squawk::stop()
 {
     qDebug("Stopping squawk core..");
     network.stop();
-    QSettings settings;
-    settings.beginGroup("core");
-    settings.beginWriteArray("accounts");
-    SimpleCrypt crypto(passwordHash);
-    for (std::deque<Account*>::size_type i = 0; i < accounts.size(); ++i) {
-        settings.setArrayIndex(i);
-        Account* acc = accounts[i];
-        
-        Shared::AccountPassword ap = acc->getPasswordType();
-        QString password;
-        
-        switch (ap) {
-            case Shared::AccountPassword::plain:
-                password = acc->getPassword();
-                break;
-            case Shared::AccountPassword::jammed:
-                password = crypto.encryptToString(acc->getPassword());
-                break;
-            default:
-                break;
+
+    if (isInitialized) {
+        QSettings settings;
+        settings.beginGroup("core");
+        settings.beginWriteArray("accounts");
+        SimpleCrypt crypto(passwordHash);
+        for (std::deque<Account*>::size_type i = 0; i < accounts.size(); ++i) {
+            settings.setArrayIndex(i);
+            Account* acc = accounts[i];
+
+            Shared::AccountPassword ap = acc->getPasswordType();
+            QString password;
+
+            switch (ap) {
+                case Shared::AccountPassword::plain:
+                    password = acc->getPassword();
+                    break;
+                case Shared::AccountPassword::jammed:
+                    password = crypto.encryptToString(acc->getPassword());
+                    break;
+                default:
+                    break;
+            }
+
+            settings.setValue("name", acc->getName());
+            settings.setValue("server", acc->getServer());
+            settings.setValue("login", acc->getLogin());
+            settings.setValue("password", password);
+            settings.setValue("resource", acc->getResource());
+            settings.setValue("passwordType", static_cast<int>(ap));
         }
-        
-        settings.setValue("name", acc->getName());
-        settings.setValue("server", acc->getServer());
-        settings.setValue("login", acc->getLogin());
-        settings.setValue("password", password);
-        settings.setValue("resource", acc->getResource());
-        settings.setValue("passwordType", static_cast<int>(ap));
+        settings.endArray();
+        settings.endGroup();
+
+        settings.sync();
     }
-    settings.endArray();
-    settings.endGroup();
-    
-    settings.sync();
     
     emit quit();
 }
@@ -108,6 +112,7 @@ void Core::Squawk::start()
     qDebug("Starting squawk core..");
     
     readSettings();
+    isInitialized = true;
     network.start();
 }
 
diff --git a/core/squawk.h b/core/squawk.h
index 338eb40..74b1e1a 100644
--- a/core/squawk.h
+++ b/core/squawk.h
@@ -133,6 +133,7 @@ private:
     Shared::Availability state;
     NetworkAccess network;
     uint8_t waitingForAccounts;
+    bool isInitialized;
 
 #ifdef WITH_KWALLET
     PSE::KWallet kwallet;

From d20fd84d391f7645400c6abb0966097e28a3cc58 Mon Sep 17 00:00:00 2001
From: shunf4 <shun1048576@gmail.com>
Date: Wed, 6 Oct 2021 22:55:23 +0800
Subject: [PATCH 149/281] respect password type when adding account, preventing
 loading bad password

---
 core/squawk.cpp | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/core/squawk.cpp b/core/squawk.cpp
index 6b8af49..34cd694 100644
--- a/core/squawk.cpp
+++ b/core/squawk.cpp
@@ -118,8 +118,9 @@ void Core::Squawk::newAccountRequest(const QMap<QString, QVariant>& map)
     QString server = map.value("server").toString();
     QString password = map.value("password").toString();
     QString resource = map.value("resource").toString();
+    int passwordType = map.value("passwordType").toInt();
     
-    addAccount(login, server, password, name, resource, Shared::AccountPassword::plain);
+    addAccount(login, server, password, name, resource, Shared::Global::fromInt<Shared::AccountPassword>(passwordType));
 }
 
 void Core::Squawk::addAccount(

From a24e8382d1a52db4aa73bd8883055960714684d4 Mon Sep 17 00:00:00 2001
From: shunf4 <shun1048576@gmail.com>
Date: Wed, 6 Oct 2021 23:01:11 +0800
Subject: [PATCH 150/281] correctly retrieve latest archived messages per
 XEP-0313

---
 core/account.cpp | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/core/account.cpp b/core/account.cpp
index 6784674..035299b 100644
--- a/core/account.cpp
+++ b/core/account.cpp
@@ -469,6 +469,14 @@ void Core::Account::onContactNeedHistory(const QString& before, const QString& a
                 query.setAfter(after);
             }
         }
+        if (before.size() == 0 && after.size() == 0) {
+            // https://xmpp.org/extensions/xep-0313.html#sect-idm46556759682304
+            // To request the page at the end of the archive
+            // (i.e. the most recent messages), include just an
+            // empty <before/> element in the RSM part of the query.
+            // As defined by RSM, this will return the last page of the archive.
+            query.setBefore("");
+        }
         qDebug() << "Remote query for" << contact->jid << "from" << after << ", to" << before;
     }
     

From 3a70df21f87f69ce0ea881d47adfb9026779e6d8 Mon Sep 17 00:00:00 2001
From: shunf4 <shun1048576@gmail.com>
Date: Wed, 6 Oct 2021 23:09:18 +0800
Subject: [PATCH 151/281] feat: paste image in chat

---
 ui/widgets/conversation.cpp | 45 ++++++++++++++++++++++++++++++++++++-
 ui/widgets/conversation.h   |  5 +++++
 2 files changed, 49 insertions(+), 1 deletion(-)

diff --git a/ui/widgets/conversation.cpp b/ui/widgets/conversation.cpp
index d003551..f0a6be6 100644
--- a/ui/widgets/conversation.cpp
+++ b/ui/widgets/conversation.cpp
@@ -20,6 +20,7 @@
 #include "ui_conversation.h"
 
 #include <QDebug>
+#include <QClipboard>
 #include <QScrollBar>
 #include <QTimer>
 #include <QFileDialog>
@@ -27,6 +28,9 @@
 #include <unistd.h>
 #include <QAbstractTextDocumentLayout>
 #include <QCoreApplication>
+#include <QTemporaryFile>
+#include <QDir>
+#include <QMenu>
 
 Conversation::Conversation(bool muc, Models::Account* acc, Models::Element* el, const QString pJid, const QString pRes, QWidget* parent):
     QWidget(parent),
@@ -47,6 +51,7 @@ Conversation::Conversation(bool muc, Models::Account* acc, Models::Element* el,
     delegate(new MessageDelegate(this)),
     manualSliderChange(false),
     tsb(QApplication::style()->styleHint(QStyle::SH_ScrollBar_Transient) == 1),
+    pasteImageAction(nullptr),
     shadow(10, 1, Qt::black, this),
     contextMenu(new QMenu())
 {
@@ -75,6 +80,7 @@ Conversation::Conversation(bool muc, Models::Account* acc, Models::Element* el,
     statusLabel = m_ui->statusLabel;
     
     connect(&ker, &KeyEnterReceiver::enterPressed, this, &Conversation::onEnterPressed);
+    connect(&ker, &KeyEnterReceiver::imagePasted, this, &Conversation::onImagePasted);
     connect(m_ui->sendButton, &QPushButton::clicked, this, &Conversation::onEnterPressed);
     connect(m_ui->attachButton, &QPushButton::clicked, this, &Conversation::onAttach);
     connect(m_ui->clearButton, &QPushButton::clicked, this, &Conversation::onClearButton);
@@ -82,7 +88,20 @@ Conversation::Conversation(bool muc, Models::Account* acc, Models::Element* el,
             this, &Conversation::onTextEditDocSizeChanged);
     
     m_ui->messageEditor->installEventFilter(&ker);
-    
+
+    QAction* pasteImageAction = new QAction(tr("Paste Image"), this);
+    connect(pasteImageAction, &QAction::triggered, this, &Conversation::onImagePasted);
+
+    m_ui->messageEditor->setContextMenuPolicy(Qt::CustomContextMenu);
+    connect(m_ui->messageEditor, &QTextEdit::customContextMenuRequested, this, [this, pasteImageAction](const QPoint &pos) {
+        pasteImageAction->setEnabled(Conversation::checkClipboardImage());
+
+        QMenu *editorMenu = m_ui->messageEditor->createStandardContextMenu();
+        editorMenu->addSeparator();
+        editorMenu->addAction(pasteImageAction);
+
+        editorMenu->exec(this->m_ui->messageEditor->mapToGlobal(pos));
+    });
     
     //line->setAutoFillBackground(false);
     //if (testAttribute(Qt::WA_TranslucentBackground)) {
@@ -183,10 +202,20 @@ bool KeyEnterReceiver::eventFilter(QObject* obj, QEvent* event)
                 }
             }
         }
+        if (k == Qt::Key_V && key->modifiers() & Qt::CTRL) {
+            if (Conversation::checkClipboardImage()) {
+                emit imagePasted();
+                return true;
+            }
+        }
     }
     return QObject::eventFilter(obj, event);
 }
 
+bool Conversation::checkClipboardImage() {
+    return !QApplication::clipboard()->image().isNull();
+}
+
 QString Conversation::getPalResource() const
 {
     return activePalResource;
@@ -218,6 +247,20 @@ void Conversation::onEnterPressed()
     }
 }
 
+void Conversation::onImagePasted()
+{
+    QImage image = QApplication::clipboard()->image();
+    if (image.isNull()) {
+        return;
+    }
+    QTemporaryFile *tempFile = new QTemporaryFile(QDir::tempPath() + QStringLiteral("/squawk_img_attach_XXXXXX.png"), QApplication::instance());
+    tempFile->open();
+    image.save(tempFile, "PNG");
+    tempFile->close();
+    qDebug() << "image on paste temp file: " << tempFile->fileName();
+    addAttachedFile(tempFile->fileName());
+}
+
 void Conversation::onAttach()
 {
     QFileDialog* d = new QFileDialog(this, tr("Chose a file to send"));
diff --git a/ui/widgets/conversation.h b/ui/widgets/conversation.h
index b0eb745..5f5d69a 100644
--- a/ui/widgets/conversation.h
+++ b/ui/widgets/conversation.h
@@ -60,6 +60,7 @@ protected:
     
 signals:
     void enterPressed();
+    void imagePasted();
 };
 
 class Conversation : public QWidget
@@ -77,6 +78,7 @@ public:
     void setPalResource(const QString& res);
     virtual void setAvatar(const QString& path);
     void setFeedFrames(bool top, bool right, bool bottom, bool left);
+    static bool checkClipboardImage();
     
 signals:
     void sendMessage(const Shared::Message& message);
@@ -102,6 +104,7 @@ protected:
     
 protected slots:
     void onEnterPressed();
+    void onImagePasted();
     void onAttach();
     void onFileSelected();
     void onBadgeClose();
@@ -133,6 +136,8 @@ protected:
     bool manualSliderChange;
     bool tsb;           //transient scroll bars
     
+    QAction* pasteImageAction;
+
     ShadowOverlay shadow;
     QMenu* contextMenu;
 };

From 50d710de04bfe5f3bffc660a4baae84a10e8b3a1 Mon Sep 17 00:00:00 2001
From: shunf4 <shun1048576@gmail.com>
Date: Wed, 13 Oct 2021 11:35:43 +0800
Subject: [PATCH 152/281] remove ./signalcatcher_win32.cpp

---
 signalcatcher_win32.cpp | 42 -----------------------------------------
 1 file changed, 42 deletions(-)
 delete mode 100644 signalcatcher_win32.cpp

diff --git a/signalcatcher_win32.cpp b/signalcatcher_win32.cpp
deleted file mode 100644
index ca7b5a2..0000000
--- a/signalcatcher_win32.cpp
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Squawk messenger.
- * Copyright (C) 2021  Shunf4 <shun1048576@gmail.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 "signalcatcher.h"
-#include <unistd.h>
-
-SignalCatcher::SignalCatcher(QCoreApplication *p_app, QObject *parent):
-    QObject(parent),
-    app(p_app)
-{
-}
-
-SignalCatcher::~SignalCatcher()
-{}
-
-void SignalCatcher::handleSigInt()
-{
-}
-
-void SignalCatcher::intSignalHandler(int unused)
-{
-}
-
-int SignalCatcher::setup_unix_signal_handlers()
-{
-    return 0;
-}

From 52551c1ce0be0a223e388d339389b7a55b159b6c Mon Sep 17 00:00:00 2001
From: shunf4 <shun1048576@gmail.com>
Date: Wed, 13 Oct 2021 20:06:13 +0800
Subject: [PATCH 153/281] pasteImageAction should be a class member; refactor
 messageEditor's context menu callback into a member function

---
 ui/widgets/conversation.cpp | 27 ++++++++++++++-------------
 ui/widgets/conversation.h   |  1 +
 2 files changed, 15 insertions(+), 13 deletions(-)

diff --git a/ui/widgets/conversation.cpp b/ui/widgets/conversation.cpp
index f0a6be6..3f07a2c 100644
--- a/ui/widgets/conversation.cpp
+++ b/ui/widgets/conversation.cpp
@@ -51,7 +51,7 @@ Conversation::Conversation(bool muc, Models::Account* acc, Models::Element* el,
     delegate(new MessageDelegate(this)),
     manualSliderChange(false),
     tsb(QApplication::style()->styleHint(QStyle::SH_ScrollBar_Transient) == 1),
-    pasteImageAction(nullptr),
+    pasteImageAction(new QAction(tr("Paste Image"), this)),
     shadow(10, 1, Qt::black, this),
     contextMenu(new QMenu())
 {
@@ -88,21 +88,11 @@ Conversation::Conversation(bool muc, Models::Account* acc, Models::Element* el,
             this, &Conversation::onTextEditDocSizeChanged);
     
     m_ui->messageEditor->installEventFilter(&ker);
+    m_ui->messageEditor->setContextMenuPolicy(Qt::CustomContextMenu);
 
-    QAction* pasteImageAction = new QAction(tr("Paste Image"), this);
+    connect(m_ui->messageEditor, &QTextEdit::customContextMenuRequested, this, &Conversation::onMessageEditorContext);
     connect(pasteImageAction, &QAction::triggered, this, &Conversation::onImagePasted);
 
-    m_ui->messageEditor->setContextMenuPolicy(Qt::CustomContextMenu);
-    connect(m_ui->messageEditor, &QTextEdit::customContextMenuRequested, this, [this, pasteImageAction](const QPoint &pos) {
-        pasteImageAction->setEnabled(Conversation::checkClipboardImage());
-
-        QMenu *editorMenu = m_ui->messageEditor->createStandardContextMenu();
-        editorMenu->addSeparator();
-        editorMenu->addAction(pasteImageAction);
-
-        editorMenu->exec(this->m_ui->messageEditor->mapToGlobal(pos));
-    });
-    
     //line->setAutoFillBackground(false);
     //if (testAttribute(Qt::WA_TranslucentBackground)) {
         //m_ui->scrollArea->setAutoFillBackground(false);
@@ -486,3 +476,14 @@ void Conversation::onFeedContext(const QPoint& pos)
         }
     }
 }
+
+void Conversation::onMessageEditorContext(const QPoint& pos)
+{
+    pasteImageAction->setEnabled(Conversation::checkClipboardImage());
+
+    QMenu *editorMenu = m_ui->messageEditor->createStandardContextMenu();
+    editorMenu->addSeparator();
+    editorMenu->addAction(pasteImageAction);
+
+    editorMenu->exec(this->m_ui->messageEditor->mapToGlobal(pos));
+}
diff --git a/ui/widgets/conversation.h b/ui/widgets/conversation.h
index 5f5d69a..6b5b4bb 100644
--- a/ui/widgets/conversation.h
+++ b/ui/widgets/conversation.h
@@ -114,6 +114,7 @@ protected slots:
     void onFeedMessage(const Shared::Message& msg);
     void positionShadow();
     void onFeedContext(const QPoint &pos);
+    void onMessageEditorContext(const QPoint &pos);
     
 public:
     const bool isMuc;

From 39f2f3d975a1ce38144ef1992506416a934e5005 Mon Sep 17 00:00:00 2001
From: shunf4 <shun1048576@gmail.com>
Date: Sat, 16 Oct 2021 00:20:31 +0800
Subject: [PATCH 154/281] feat: copy pasted image file to download folder after
 successful upload

---
 core/networkaccess.cpp      | 27 ++++++++++++++++++++++++++-
 core/networkaccess.h        |  2 +-
 core/squawk.h               |  2 +-
 ui/squawk.cpp               |  2 +-
 ui/squawk.h                 |  2 +-
 ui/widgets/conversation.cpp |  4 ++++
 6 files changed, 34 insertions(+), 5 deletions(-)

diff --git a/core/networkaccess.cpp b/core/networkaccess.cpp
index 69fe812..c2cd65d 100644
--- a/core/networkaccess.cpp
+++ b/core/networkaccess.cpp
@@ -378,7 +378,32 @@ void Core::NetworkAccess::onUploadFinished()
             qDebug() << "upload success for" << url;
             
             storage.addFile(upl->messages, upl->url, upl->path);
-            emit uploadFileComplete(upl->messages, upl->url);
+            emit uploadFileComplete(upl->messages, upl->url, upl->path);
+
+            // Copy file to Download folder if it is a temp file. See Conversation::onImagePasted.
+            if (upl->path.startsWith(QDir::tempPath() + QStringLiteral("/squawk_img_attach_")) && upl->path.endsWith(".png")) {
+                QString err = "";
+                QString downloadDirPath = prepareDirectory(upl->messages.front().jid);
+                if (downloadDirPath.size() > 0) {
+                    QString newPath = downloadDirPath + "/" + upl->path.mid(QDir::tempPath().length() + 1);
+
+                    // Copy {TEMPDIR}/squawk_img_attach_XXXXXX.png to Download folder
+                    bool copyResult = QFile::copy(upl->path, newPath);
+
+                    if (copyResult) {
+                        // Change storage
+                        storage.setPath(upl->url, newPath);
+                    } else {
+                        err = "copying to " + newPath + " failed";
+                    }
+                } else {
+                    err = "Couldn't prepare a directory for file";
+                }
+
+                if (err.size() != 0) {
+                    qDebug() << "failed to copy temporary upload file " << upl->path << " to download folder:" << err;
+                }
+            }
         }
         
         upl->reply->deleteLater();
diff --git a/core/networkaccess.h b/core/networkaccess.h
index 75c189c..89d0633 100644
--- a/core/networkaccess.h
+++ b/core/networkaccess.h
@@ -58,7 +58,7 @@ public:
 signals:
     void loadFileProgress(const std::list<Shared::MessageInfo>& msgs, qreal value, bool up);
     void loadFileError(const std::list<Shared::MessageInfo>& msgs, const QString& text, bool up);
-    void uploadFileComplete(const std::list<Shared::MessageInfo>& msgs, const QString& url);
+    void uploadFileComplete(const std::list<Shared::MessageInfo>& msgs, const QString& url, const QString& path);
     void downloadFileComplete(const std::list<Shared::MessageInfo>& msgs, const QString& path);
     
 public slots:
diff --git a/core/squawk.h b/core/squawk.h
index 338eb40..3715afe 100644
--- a/core/squawk.h
+++ b/core/squawk.h
@@ -82,7 +82,7 @@ signals:
     void fileError(const std::list<Shared::MessageInfo> msgs, const QString& error, bool up);
     void fileProgress(const std::list<Shared::MessageInfo> msgs, qreal value, bool up);
     void fileDownloadComplete(const std::list<Shared::MessageInfo> msgs, const QString& path);
-    void fileUploadComplete(const std::list<Shared::MessageInfo> msgs, const QString& path);
+    void fileUploadComplete(const std::list<Shared::MessageInfo> msgs, const QString& url, const QString& path);
     
     void responseVCard(const QString& jid, const Shared::VCard& card);
     void changeMessage(const QString& account, const QString& jid, const QString& id, const QMap<QString, QVariant>& data);
diff --git a/ui/squawk.cpp b/ui/squawk.cpp
index 6a0a676..406ee45 100644
--- a/ui/squawk.cpp
+++ b/ui/squawk.cpp
@@ -405,7 +405,7 @@ void Squawk::fileError(const std::list<Shared::MessageInfo> msgs, const QString&
     rosterModel.fileError(msgs, error, up);
 }
 
-void Squawk::fileUploadComplete(const std::list<Shared::MessageInfo> msgs, const QString& path)
+void Squawk::fileUploadComplete(const std::list<Shared::MessageInfo> msgs, const QString& url, const QString& path)
 {
     rosterModel.fileComplete(msgs, true);
 }
diff --git a/ui/squawk.h b/ui/squawk.h
index 28389fa..cb93259 100644
--- a/ui/squawk.h
+++ b/ui/squawk.h
@@ -107,7 +107,7 @@ public slots:
     void fileError(const std::list<Shared::MessageInfo> msgs, const QString& error, bool up);
     void fileProgress(const std::list<Shared::MessageInfo> msgs, qreal value, bool up);
     void fileDownloadComplete(const std::list<Shared::MessageInfo> msgs, const QString& path);
-    void fileUploadComplete(const std::list<Shared::MessageInfo> msgs, const QString& path);
+    void fileUploadComplete(const std::list<Shared::MessageInfo> msgs, const QString& url, const QString& path);
     void responseVCard(const QString& jid, const Shared::VCard& card);
     void changeMessage(const QString& account, const QString& jid, const QString& id, const QMap<QString, QVariant>& data);
     void requestPassword(const QString& account);
diff --git a/ui/widgets/conversation.cpp b/ui/widgets/conversation.cpp
index 3f07a2c..fcf28c3 100644
--- a/ui/widgets/conversation.cpp
+++ b/ui/widgets/conversation.cpp
@@ -249,6 +249,10 @@ void Conversation::onImagePasted()
     tempFile->close();
     qDebug() << "image on paste temp file: " << tempFile->fileName();
     addAttachedFile(tempFile->fileName());
+
+    // The file, if successfully uploaded, will be copied to Download folder.
+    // On application closing, this temporary file will be automatically removed by Qt.
+    // See Core::NetworkAccess::onUploadFinished.
 }
 
 void Conversation::onAttach()

From 7130e674c4d105d3ddefa064ffbf0eb2ef630d20 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Wed, 5 Jan 2022 22:29:34 +0300
Subject: [PATCH 155/281] some warnings fixed, new way of drawing avatars in
 message line

---
 core/account.cpp                           |  5 +-
 core/adapterFuctions.cpp                   |  2 +-
 core/conference.cpp                        |  2 +-
 ui/models/accounts.cpp                     |  5 +-
 ui/widgets/messageline/feedview.cpp        | 56 ++++++++++++++++++++--
 ui/widgets/messageline/feedview.h          |  1 +
 ui/widgets/messageline/messagedelegate.cpp | 10 ++--
 7 files changed, 65 insertions(+), 16 deletions(-)

diff --git a/core/account.cpp b/core/account.cpp
index 035299b..a923690 100644
--- a/core/account.cpp
+++ b/core/account.cpp
@@ -601,6 +601,9 @@ void Core::Account::onClientError(QXmppClient::Error err)
                     errorText = "Policy violation";
                     break;
 #endif
+                default:
+                    errorText = "Unknown Error";
+                    break;
             }
          
             errorType = "Client stream error";
@@ -837,7 +840,6 @@ void Core::Account::uploadVCard(const Shared::VCard& card)
     QXmppVCardIq iq;
     initializeQXmppVCard(iq, card);
     
-    bool avatarChanged = false;
     if (card.getAvatarType() != Shared::Avatar::empty) {
         QString newPath = card.getAvatarPath();
         QString oldPath = getAvatarPath();
@@ -859,7 +861,6 @@ void Core::Account::uploadVCard(const Shared::VCard& card)
                 }
             } else {
                 data = avatar.readAll();
-                avatarChanged = true;
             }
         } else {
             if (avatarType.size() > 0) {
diff --git a/core/adapterFuctions.cpp b/core/adapterFuctions.cpp
index e2559d8..3d84dfb 100644
--- a/core/adapterFuctions.cpp
+++ b/core/adapterFuctions.cpp
@@ -264,7 +264,7 @@ void Core::initializeQXmppVCard(QXmppVCardIq& iq, const Shared::VCard& card) {
             phone.setType(phone.type() | QXmppVCardPhone::Preferred);
         }
     }
-    for (const std::pair<QString, QXmppVCardPhone>& phone : phones) {
+    for (const std::pair<const QString, QXmppVCardPhone>& phone : phones) {
         phs.push_back(phone.second);
     }
     
diff --git a/core/conference.cpp b/core/conference.cpp
index cda19fd..55280e2 100644
--- a/core/conference.cpp
+++ b/core/conference.cpp
@@ -356,7 +356,7 @@ Shared::VCard Core::Conference::handleResponseVCard(const QXmppVCardIq& card, co
 QMap<QString, QVariant> Core::Conference::getAllAvatars() const
 {
     QMap<QString, QVariant> result;
-    for (const std::pair<QString, Archive::AvatarInfo>& pair : exParticipants) {
+    for (const std::pair<const QString, Archive::AvatarInfo>& pair : exParticipants) {
         result.insert(pair.first, avatarPath(pair.first) + "." + pair.second.type);
     }
     return result;
diff --git a/ui/models/accounts.cpp b/ui/models/accounts.cpp
index f5ffce8..4343481 100644
--- a/ui/models/accounts.cpp
+++ b/ui/models/accounts.cpp
@@ -97,7 +97,10 @@ void Models::Accounts::addAccount(Account* account)
 
 void Models::Accounts::onAccountChanged(Item* item, int row, int col)
 {
-    if (row < accs.size()) {
+    if (row < 0) {
+        return;
+    }
+    if (static_cast<std::deque<Models::Account*>::size_type>(row) < accs.size()) {
         Account* acc = getAccount(row);
         if (item != acc) {
             return;     //it means the signal is emitted by one of accounts' children, not exactly him, this model has no interest in that
diff --git a/ui/widgets/messageline/feedview.cpp b/ui/widgets/messageline/feedview.cpp
index 7bdfb9e..618ecfb 100644
--- a/ui/widgets/messageline/feedview.cpp
+++ b/ui/widgets/messageline/feedview.cpp
@@ -31,6 +31,10 @@ constexpr int approximateSingleMessageHeight = 20;
 constexpr int progressSize = 70;
 constexpr int dateDeviderMargin = 10;
 
+constexpr int avatarHeight = 50;
+constexpr int margin = 6;
+constexpr int halfMargin = 3;
+
 const std::set<int> FeedView::geometryChangingRoles = {
     Models::MessageFeed::Attach,
     Models::MessageFeed::Text,
@@ -334,6 +338,20 @@ void FeedView::paintEvent(QPaintEvent* event)
             drawDateDevider(option.rect.bottom(), lastDate, painter);
         }
         lastDate = currentDate;
+
+
+        if ((option.rect.y() < 1) || (index.row() == m->rowCount() - 1)) {
+            drawAvatar(index, option, painter);
+        } else {
+            QString mySender = index.data(Models::MessageFeed::Sender).toString();
+            QModelIndex prevIndex = m->index(index.row() + 1, 0, rootIndex());
+            if (
+                (prevIndex.data(Models::MessageFeed::Sender).toString() != mySender) ||
+                (prevIndex.data(Models::MessageFeed::Date).toDateTime().daysTo(currentDate) != 0)
+            ) {
+                drawAvatar(index, option, painter);
+            }
+        }
     }
     if (!lastDate.isNull() && inZone) {     //if after drawing all messages there is still space
         drawDateDevider(option.rect.bottom(), lastDate, painter);
@@ -344,10 +362,6 @@ void FeedView::paintEvent(QPaintEvent* event)
         del->endClearWidgets();
         clearWidgetsMode = false;
     }
-    
-    if (event->rect().height() == vp->height()) {
-        // draw the blurred drop shadow...
-    }
 }
 
 void FeedView::drawDateDevider(int top, const QDateTime& date, QPainter& painter)
@@ -360,6 +374,40 @@ void FeedView::drawDateDevider(int top, const QDateTime& date, QPainter& painter
     painter.restore();
 }
 
+void FeedView::drawAvatar(const QModelIndex& index, const QStyleOptionViewItem& option, QPainter& painter)
+{
+    int currentRow = index.row();
+    int y = option.rect.y();
+    bool firstAttempt = true;
+    QString mySender = index.data(Models::MessageFeed::Sender).toString();
+    QDateTime currentDate = index.data(Models::MessageFeed::Date).toDateTime();
+    QIcon icon(index.data(Models::MessageFeed::Avatar).toString());
+    while (y < 0 && currentRow > 0) {
+        QRect rect;
+        if (firstAttempt) {
+            firstAttempt = false;
+            rect = option.rect;
+        } else {
+            QModelIndex ci = model()->index(currentRow, 0, rootIndex());
+            if (
+                (ci.data(Models::MessageFeed::Sender).toString() != mySender) ||
+                (ci.data(Models::MessageFeed::Date).toDateTime().daysTo(currentDate) != 0)
+            ) {
+                break;
+            }
+            rect = visualRect(ci);
+        }
+        y = std::min(0, rect.bottom() - margin - avatarHeight);
+        --currentRow;
+    }
+    if (index.data(Models::MessageFeed::SentByMe).toBool()) {
+        painter.drawPixmap(option.rect.width() - avatarHeight - margin, y + halfMargin, icon.pixmap(avatarHeight, avatarHeight));
+    } else {
+        painter.drawPixmap(margin, y + halfMargin, icon.pixmap(avatarHeight, avatarHeight));
+    }
+}
+
+
 void FeedView::verticalScrollbarValueChanged(int value)
 {
     vo = verticalScrollBar()->maximum() - value;
diff --git a/ui/widgets/messageline/feedview.h b/ui/widgets/messageline/feedview.h
index 5e08946..e3e57b7 100644
--- a/ui/widgets/messageline/feedview.h
+++ b/ui/widgets/messageline/feedview.h
@@ -74,6 +74,7 @@ private:
     bool tryToCalculateGeometriesWithNoScrollbars(const QStyleOptionViewItem& option, const QAbstractItemModel* model, uint32_t totalHeight);
     void positionProgress();
     void drawDateDevider(int top, const QDateTime& date, QPainter& painter);
+    void drawAvatar(const QModelIndex& index, const QStyleOptionViewItem& option, QPainter& painter);
     
 private:
     struct Hint {
diff --git a/ui/widgets/messageline/messagedelegate.cpp b/ui/widgets/messageline/messagedelegate.cpp
index 649230e..0fe1ed0 100644
--- a/ui/widgets/messageline/messagedelegate.cpp
+++ b/ui/widgets/messageline/messagedelegate.cpp
@@ -104,13 +104,7 @@ void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio
         painter->fillRect(option.rect, option.palette.brush(QPalette::Inactive, QPalette::Highlight));
     }
     
-    QIcon icon(data.avatar);
-    
-    if (data.sentByMe) {
-        painter->drawPixmap(option.rect.width() - avatarHeight - margin,  option.rect.y() + margin / 2, icon.pixmap(avatarHeight, avatarHeight));
-    } else {
-        painter->drawPixmap(margin, option.rect.y() + margin / 2, icon.pixmap(avatarHeight, avatarHeight));
-    }
+
     
     QStyleOptionViewItem opt = option;
     QRect messageRect = option.rect.adjusted(margin, margin / 2, -(avatarHeight + 2 * margin), -margin / 2);
@@ -163,6 +157,7 @@ void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio
             break;                          //but it's a possible performance problem
         case Models::uploading:
             paintPreview(data, painter, opt);
+            [[fallthrough]];
         case Models::downloading:
             paintBar(getBar(data), painter, data.sentByMe, opt);
             break;
@@ -268,6 +263,7 @@ QSize MessageDelegate::sizeHint(const QStyleOptionViewItem& option, const QModel
             break;
         case Models::uploading:
             messageSize.rheight() += Preview::calculateAttachSize(attach.localPath, messageRect).height() + textMargin;
+            [[fallthrough]];
         case Models::downloading:
             messageSize.rheight() += barHeight + textMargin;
             break;

From 9ac0ca10f34046b9774659b772ff97d2b029bc73 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Fri, 7 Jan 2022 17:02:49 +0300
Subject: [PATCH 156/281] avatar painting is returned to delegate; sender names
 now are not painted in every message

---
 ui/widgets/messageline/feedview.cpp        |  48 ---------
 ui/widgets/messageline/feedview.h          |   1 -
 ui/widgets/messageline/messagedelegate.cpp | 113 ++++++++++++++++-----
 ui/widgets/messageline/messagedelegate.h   |   4 +
 4 files changed, 93 insertions(+), 73 deletions(-)

diff --git a/ui/widgets/messageline/feedview.cpp b/ui/widgets/messageline/feedview.cpp
index 618ecfb..1296324 100644
--- a/ui/widgets/messageline/feedview.cpp
+++ b/ui/widgets/messageline/feedview.cpp
@@ -338,20 +338,6 @@ void FeedView::paintEvent(QPaintEvent* event)
             drawDateDevider(option.rect.bottom(), lastDate, painter);
         }
         lastDate = currentDate;
-
-
-        if ((option.rect.y() < 1) || (index.row() == m->rowCount() - 1)) {
-            drawAvatar(index, option, painter);
-        } else {
-            QString mySender = index.data(Models::MessageFeed::Sender).toString();
-            QModelIndex prevIndex = m->index(index.row() + 1, 0, rootIndex());
-            if (
-                (prevIndex.data(Models::MessageFeed::Sender).toString() != mySender) ||
-                (prevIndex.data(Models::MessageFeed::Date).toDateTime().daysTo(currentDate) != 0)
-            ) {
-                drawAvatar(index, option, painter);
-            }
-        }
     }
     if (!lastDate.isNull() && inZone) {     //if after drawing all messages there is still space
         drawDateDevider(option.rect.bottom(), lastDate, painter);
@@ -374,40 +360,6 @@ void FeedView::drawDateDevider(int top, const QDateTime& date, QPainter& painter
     painter.restore();
 }
 
-void FeedView::drawAvatar(const QModelIndex& index, const QStyleOptionViewItem& option, QPainter& painter)
-{
-    int currentRow = index.row();
-    int y = option.rect.y();
-    bool firstAttempt = true;
-    QString mySender = index.data(Models::MessageFeed::Sender).toString();
-    QDateTime currentDate = index.data(Models::MessageFeed::Date).toDateTime();
-    QIcon icon(index.data(Models::MessageFeed::Avatar).toString());
-    while (y < 0 && currentRow > 0) {
-        QRect rect;
-        if (firstAttempt) {
-            firstAttempt = false;
-            rect = option.rect;
-        } else {
-            QModelIndex ci = model()->index(currentRow, 0, rootIndex());
-            if (
-                (ci.data(Models::MessageFeed::Sender).toString() != mySender) ||
-                (ci.data(Models::MessageFeed::Date).toDateTime().daysTo(currentDate) != 0)
-            ) {
-                break;
-            }
-            rect = visualRect(ci);
-        }
-        y = std::min(0, rect.bottom() - margin - avatarHeight);
-        --currentRow;
-    }
-    if (index.data(Models::MessageFeed::SentByMe).toBool()) {
-        painter.drawPixmap(option.rect.width() - avatarHeight - margin, y + halfMargin, icon.pixmap(avatarHeight, avatarHeight));
-    } else {
-        painter.drawPixmap(margin, y + halfMargin, icon.pixmap(avatarHeight, avatarHeight));
-    }
-}
-
-
 void FeedView::verticalScrollbarValueChanged(int value)
 {
     vo = verticalScrollBar()->maximum() - value;
diff --git a/ui/widgets/messageline/feedview.h b/ui/widgets/messageline/feedview.h
index e3e57b7..5e08946 100644
--- a/ui/widgets/messageline/feedview.h
+++ b/ui/widgets/messageline/feedview.h
@@ -74,7 +74,6 @@ private:
     bool tryToCalculateGeometriesWithNoScrollbars(const QStyleOptionViewItem& option, const QAbstractItemModel* model, uint32_t totalHeight);
     void positionProgress();
     void drawDateDevider(int top, const QDateTime& date, QPainter& painter);
-    void drawAvatar(const QModelIndex& index, const QStyleOptionViewItem& option, QPainter& painter);
     
 private:
     struct Hint {
diff --git a/ui/widgets/messageline/messagedelegate.cpp b/ui/widgets/messageline/messagedelegate.cpp
index 0fe1ed0..0cf449a 100644
--- a/ui/widgets/messageline/messagedelegate.cpp
+++ b/ui/widgets/messageline/messagedelegate.cpp
@@ -20,6 +20,7 @@
 #include <QPainter>
 #include <QApplication>
 #include <QMouseEvent>
+#include <QAbstractItemView>
 
 #include "messagedelegate.h"
 #include "messagefeed.h"
@@ -104,7 +105,10 @@ void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio
         painter->fillRect(option.rect, option.palette.brush(QPalette::Inactive, QPalette::Highlight));
     }
     
-
+    bool ntds = needToDrawSender(index, data);
+    if (ntds || option.rect.y() < 1) {
+        paintAvatar(data, index, option, painter);
+    }
     
     QStyleOptionViewItem opt = option;
     QRect messageRect = option.rect.adjusted(margin, margin / 2, -(avatarHeight + 2 * margin), -margin / 2);
@@ -122,13 +126,19 @@ void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio
         messageSize = bodyMetrics.boundingRect(messageRect, Qt::TextWordWrap, data.text).size();
         bodySize = messageSize;
     }
-    messageSize.rheight() += nickMetrics.lineSpacing();
+
+    if (ntds) {
+        messageSize.rheight() += nickMetrics.lineSpacing();
+    }
     messageSize.rheight() += dateMetrics.height();
     QString dateString = data.date.toLocalTime().toString("hh:mm");
+
     if (messageSize.width() < opt.rect.width()) {
-        QSize senderSize = nickMetrics.boundingRect(messageRect, 0, data.sender).size();
-        if (senderSize.width() > messageSize.width()) {
-            messageSize.setWidth(senderSize.width());
+        if (ntds) {
+            QSize senderSize = nickMetrics.boundingRect(messageRect, 0, data.sender).size();
+            if (senderSize.width() > messageSize.width()) {
+                messageSize.setWidth(senderSize.width());
+            }
         }
         QSize dateSize = dateMetrics.boundingRect(messageRect, 0, dateString).size();
         int addition = 0;
@@ -147,9 +157,11 @@ void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio
     }
     
     QRect rect;
-    painter->setFont(nickFont);
-    painter->drawText(opt.rect, opt.displayAlignment, data.sender, &rect);
-    opt.rect.adjust(0, rect.height() + textMargin, 0, 0);
+    if (ntds) {
+        painter->setFont(nickFont);
+        painter->drawText(opt.rect, opt.displayAlignment, data.sender, &rect);
+        opt.rect.adjust(0, rect.height() + textMargin, 0, 0);
+    }
     painter->save();
     switch (data.attach.state) {
         case Models::none:
@@ -244,25 +256,75 @@ void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio
     }
 }
 
+void MessageDelegate::paintAvatar(const Models::FeedItem& data, const QModelIndex& index, const QStyleOptionViewItem& option, QPainter* painter) const
+{
+    int currentRow = index.row();
+    int y = option.rect.y();
+    bool firstAttempt = true;
+    QIcon icon(data.avatar);
+    while (y < 0 && currentRow > 0) {
+        QRect rect;
+        if (firstAttempt) {
+            firstAttempt = false;
+            rect = option.rect;
+        } else {
+            QModelIndex ci = index.siblingAtRow(currentRow);
+            if (
+                (ci.data(Models::MessageFeed::Sender).toString() != data.sender) ||
+                (ci.data(Models::MessageFeed::Date).toDateTime().daysTo(data.date) != 0)
+            ) {
+                break;
+            }
+            //TODO this is really bad, but for now I have no idea how else can I access the view;
+            const QAbstractItemView* view = static_cast<const QAbstractItemView*>(option.styleObject);
+            rect = view->visualRect(ci);
+        }
+        y = std::min(0, rect.bottom() - margin - avatarHeight);
+        --currentRow;
+    }
+    if (data.sentByMe) {
+        painter->drawPixmap(option.rect.width() - avatarHeight - margin, y + margin / 2, icon.pixmap(avatarHeight, avatarHeight));
+    } else {
+        painter->drawPixmap(margin, y + margin / 2, icon.pixmap(avatarHeight, avatarHeight));
+    }
+}
+
+bool MessageDelegate::needToDrawAvatar(const QModelIndex& index, const Models::FeedItem& data, const QStyleOptionViewItem& option) const
+{
+    return (option.rect.y() < 1) || needToDrawSender(index, data);
+}
+
+bool MessageDelegate::needToDrawSender(const QModelIndex& index, const Models::FeedItem& data) const
+{
+    if (index.row() == index.model()->rowCount() - 1) {
+        return true;
+    } else {
+        QModelIndex prevIndex = index.siblingAtRow(index.row() + 1);
+
+        return  (prevIndex.data(Models::MessageFeed::Sender).toString() != data.sender) ||
+                (prevIndex.data(Models::MessageFeed::Date).toDateTime().daysTo(data.date) != 0);
+    }
+}
+
+
 QSize MessageDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const
 {
     QRect messageRect = option.rect.adjusted(0, margin / 2, -(avatarHeight + 3 * margin), -margin / 2);
     QStyleOptionViewItem opt = option;
     opt.rect = messageRect;
-    QVariant va = index.data(Models::MessageFeed::Attach);
-    Models::Attachment attach = qvariant_cast<Models::Attachment>(va);
-    QString body = index.data(Models::MessageFeed::Text).toString();
+    QVariant vi = index.data(Models::MessageFeed::Bulk);
+    Models::FeedItem data = qvariant_cast<Models::FeedItem>(vi);
     QSize messageSize(0, 0);
-    if (body.size() > 0) {
-        messageSize = bodyMetrics.boundingRect(messageRect, Qt::TextWordWrap, body).size();
+    if (data.text.size() > 0) {
+        messageSize = bodyMetrics.boundingRect(messageRect, Qt::TextWordWrap, data.text).size();
         messageSize.rheight() += textMargin;
     }
     
-    switch (attach.state) {
+    switch (data.attach.state) {
         case Models::none:
             break;
         case Models::uploading:
-            messageSize.rheight() += Preview::calculateAttachSize(attach.localPath, messageRect).height() + textMargin;
+            messageSize.rheight() += Preview::calculateAttachSize(data.attach.localPath, messageRect).height() + textMargin;
             [[fallthrough]];
         case Models::downloading:
             messageSize.rheight() += barHeight + textMargin;
@@ -272,25 +334,28 @@ QSize MessageDelegate::sizeHint(const QStyleOptionViewItem& option, const QModel
             break;
         case Models::ready:
         case Models::local:
-            messageSize.rheight() += Preview::calculateAttachSize(attach.localPath, messageRect).height() + textMargin;
+            messageSize.rheight() += Preview::calculateAttachSize(data.attach.localPath, messageRect).height() + textMargin;
             break;
         case Models::errorDownload:
             messageSize.rheight() += buttonHeight + textMargin;
-            messageSize.rheight() += dateMetrics.boundingRect(messageRect, Qt::TextWordWrap, attach.error).size().height() + textMargin;
+            messageSize.rheight() += dateMetrics.boundingRect(messageRect, Qt::TextWordWrap, data.attach.error).size().height() + textMargin;
             break;
         case Models::errorUpload:
-            messageSize.rheight() += Preview::calculateAttachSize(attach.localPath, messageRect).height() + textMargin;
-            messageSize.rheight() += dateMetrics.boundingRect(messageRect, Qt::TextWordWrap, attach.error).size().height() + textMargin;
+            messageSize.rheight() += Preview::calculateAttachSize(data.attach.localPath, messageRect).height() + textMargin;
+            messageSize.rheight() += dateMetrics.boundingRect(messageRect, Qt::TextWordWrap, data.attach.error).size().height() + textMargin;
             break;
     }
     
-    messageSize.rheight() += nickMetrics.lineSpacing();
-    messageSize.rheight() += textMargin;
+    if (needToDrawSender(index, data)) {
+        messageSize.rheight() += nickMetrics.lineSpacing();
+        messageSize.rheight() += textMargin;
+    }
+
     messageSize.rheight() += dateMetrics.height() > statusIconSize ? dateMetrics.height() : statusIconSize;
     
-    if (messageSize.height() < avatarHeight) {
-        messageSize.setHeight(avatarHeight);
-    }
+//     if (messageSize.height() < avatarHeight) {
+//         messageSize.setHeight(avatarHeight);
+//     }
     
     messageSize.rheight() += margin;
     
diff --git a/ui/widgets/messageline/messagedelegate.h b/ui/widgets/messageline/messagedelegate.h
index 7403285..5792e01 100644
--- a/ui/widgets/messageline/messagedelegate.h
+++ b/ui/widgets/messageline/messagedelegate.h
@@ -65,12 +65,16 @@ protected:
     void paintBar(QProgressBar* bar, QPainter* painter, bool sentByMe, QStyleOptionViewItem& option) const;
     void paintPreview(const Models::FeedItem& data, QPainter* painter, QStyleOptionViewItem& option) const;
     void paintComment(const Models::FeedItem& data, QPainter* painter, QStyleOptionViewItem& option) const;
+    void paintAvatar(const Models::FeedItem& data, const QModelIndex& index, const QStyleOptionViewItem& option, QPainter* painter) const;
     QPushButton* getButton(const Models::FeedItem& data) const;
     QProgressBar* getBar(const Models::FeedItem& data) const;
     QLabel* getStatusIcon(const Models::FeedItem& data) const;
     QLabel* getPencilIcon(const Models::FeedItem& data) const;
     QLabel* getBody(const Models::FeedItem& data) const;
     void clearHelperWidget(const Models::FeedItem& data) const;
+
+    bool needToDrawAvatar(const QModelIndex& index, const Models::FeedItem& data, const QStyleOptionViewItem& option) const;
+    bool needToDrawSender(const QModelIndex& index, const Models::FeedItem& data) const;
     
 protected slots:
     void onButtonPushed() const;

From 8a2658e4fcf0f6e8559057d05152982c21a90e41 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Sun, 9 Jan 2022 01:28:29 +0300
Subject: [PATCH 157/281] message bubbles, avatar rounding, roster adjusments

---
 ui/squawk.cpp                              |   6 +-
 ui/squawk.ui                               |  23 +++-
 ui/widgets/conversation.cpp                |  24 +++-
 ui/widgets/conversation.h                  |   8 ++
 ui/widgets/messageline/feedview.cpp        |   4 -
 ui/widgets/messageline/messagedelegate.cpp | 133 +++++++++++++++------
 ui/widgets/messageline/messagedelegate.h   |   9 +-
 7 files changed, 158 insertions(+), 49 deletions(-)

diff --git a/ui/squawk.cpp b/ui/squawk.cpp
index 406ee45..800f02a 100644
--- a/ui/squawk.cpp
+++ b/ui/squawk.cpp
@@ -39,7 +39,11 @@ Squawk::Squawk(QWidget *parent) :
     m_ui->setupUi(this);
     m_ui->roster->setModel(&rosterModel);
     m_ui->roster->setContextMenuPolicy(Qt::CustomContextMenu);
-    m_ui->roster->setColumnWidth(1, 30);
+    if (QApplication::style()->styleHint(QStyle::SH_ScrollBar_Transient) == 1) {
+        m_ui->roster->setColumnWidth(1, 52);
+    } else {
+        m_ui->roster->setColumnWidth(1, 26);
+    }
     m_ui->roster->setIconSize(QSize(20, 20));
     m_ui->roster->header()->setStretchLastSection(false);
     m_ui->roster->header()->setSectionResizeMode(0, QHeaderView::Stretch);
diff --git a/ui/squawk.ui b/ui/squawk.ui
index a4d0258..01ffbba 100644
--- a/ui/squawk.ui
+++ b/ui/squawk.ui
@@ -73,6 +73,9 @@
         <property name="bottomMargin">
          <number>0</number>
         </property>
+        <property name="spacing">
+         <number>0</number>
+        </property>
         <item row="2" column="1">
          <widget class="QTreeView" name="roster">
           <property name="frameShape">
@@ -96,16 +99,31 @@
           <property name="allColumnsShowFocus">
            <bool>true</bool>
           </property>
+          <property name="headerHidden">
+           <bool>true</bool>
+          </property>
           <property name="expandsOnDoubleClick">
            <bool>false</bool>
           </property>
           <attribute name="headerVisible">
            <bool>false</bool>
           </attribute>
+          <attribute name="headerMinimumSectionSize">
+           <number>10</number>
+          </attribute>
+          <attribute name="headerStretchLastSection">
+           <bool>false</bool>
+          </attribute>
          </widget>
         </item>
         <item row="1" column="1">
          <widget class="QComboBox" name="comboBox">
+          <property name="minimumSize">
+           <size>
+            <width>0</width>
+            <height>30</height>
+           </size>
+          </property>
           <property name="editable">
            <bool>false</bool>
           </property>
@@ -115,6 +133,9 @@
           <property name="currentIndex">
            <number>-1</number>
           </property>
+          <property name="frame">
+           <bool>true</bool>
+          </property>
          </widget>
         </item>
        </layout>
@@ -162,7 +183,7 @@
      <x>0</x>
      <y>0</y>
      <width>718</width>
-     <height>27</height>
+     <height>32</height>
     </rect>
    </property>
    <widget class="QMenu" name="menuSettings">
diff --git a/ui/widgets/conversation.cpp b/ui/widgets/conversation.cpp
index ea2f722..69eac19 100644
--- a/ui/widgets/conversation.cpp
+++ b/ui/widgets/conversation.cpp
@@ -25,12 +25,16 @@
 #include <QTimer>
 #include <QFileDialog>
 #include <QMimeDatabase>
-#include <unistd.h>
 #include <QAbstractTextDocumentLayout>
 #include <QCoreApplication>
 #include <QTemporaryFile>
 #include <QDir>
 #include <QMenu>
+#include <QBitmap>
+
+#include <unistd.h>
+
+constexpr QSize avatarSize(50, 50);
 
 Conversation::Conversation(bool muc, Models::Account* acc, Models::Element* el, const QString pJid, const QString pRes, QWidget* parent):
     QWidget(parent),
@@ -348,11 +352,25 @@ void Conversation::onClearButton()
 
 void Conversation::setAvatar(const QString& path)
 {
+    QPixmap pixmap;
     if (path.size() == 0) {
-        m_ui->avatar->setPixmap(Shared::icon("user", true).pixmap(QSize(50, 50)));
+        pixmap = Shared::icon("user", true).pixmap(avatarSize);
     } else {
-        m_ui->avatar->setPixmap(path);
+        pixmap = QPixmap(path).scaled(avatarSize);
     }
+
+
+    QPixmap result(avatarSize);
+    result.fill(Qt::transparent);
+    QPainter painter(&result);
+    painter.setRenderHint(QPainter::Antialiasing);
+    painter.setRenderHint(QPainter::SmoothPixmapTransform);
+    QPainterPath maskPath;
+    maskPath.addEllipse(0, 0, avatarSize.width(), avatarSize.height());
+    painter.setClipPath(maskPath);
+    painter.drawPixmap(0, 0, pixmap);
+
+    m_ui->avatar->setPixmap(result);
 }
 
 void Conversation::onTextEditDocSizeChanged(const QSizeF& size)
diff --git a/ui/widgets/conversation.h b/ui/widgets/conversation.h
index 6b5b4bb..a758b2c 100644
--- a/ui/widgets/conversation.h
+++ b/ui/widgets/conversation.h
@@ -141,6 +141,14 @@ protected:
 
     ShadowOverlay shadow;
     QMenu* contextMenu;
+
+private:
+    static bool painterInitialized;
+    static QPainterPath* avatarMask;
+    static QPixmap* avatarPixmap;
+    static QPainter* avatarPainter;
+
+
 };
 
 #endif // CONVERSATION_H
diff --git a/ui/widgets/messageline/feedview.cpp b/ui/widgets/messageline/feedview.cpp
index 1296324..d83ca6b 100644
--- a/ui/widgets/messageline/feedview.cpp
+++ b/ui/widgets/messageline/feedview.cpp
@@ -31,10 +31,6 @@ constexpr int approximateSingleMessageHeight = 20;
 constexpr int progressSize = 70;
 constexpr int dateDeviderMargin = 10;
 
-constexpr int avatarHeight = 50;
-constexpr int margin = 6;
-constexpr int halfMargin = 3;
-
 const std::set<int> FeedView::geometryChangingRoles = {
     Models::MessageFeed::Attach,
     Models::MessageFeed::Text,
diff --git a/ui/widgets/messageline/messagedelegate.cpp b/ui/widgets/messageline/messagedelegate.cpp
index 0cf449a..5dd0dce 100644
--- a/ui/widgets/messageline/messagedelegate.cpp
+++ b/ui/widgets/messageline/messagedelegate.cpp
@@ -18,6 +18,7 @@
 
 #include <QDebug>
 #include <QPainter>
+#include <QPainterPath>
 #include <QApplication>
 #include <QMouseEvent>
 #include <QAbstractItemView>
@@ -29,6 +30,11 @@ constexpr int avatarHeight = 50;
 constexpr int margin = 6;
 constexpr int textMargin = 2;
 constexpr int statusIconSize = 16;
+constexpr float nickFontMultiplier = 1.1;
+constexpr float dateFontMultiplier = 0.8;
+
+constexpr int bubbleMargin = 6;
+constexpr int bubbleBorderRadius = 3;
 
 MessageDelegate::MessageDelegate(QObject* parent):
     QStyledItemDelegate(parent),
@@ -101,9 +107,10 @@ void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio
     painter->save();
     painter->setRenderHint(QPainter::Antialiasing, true);
     
-    if (option.state & QStyle::State_MouseOver) {
-        painter->fillRect(option.rect, option.palette.brush(QPalette::Inactive, QPalette::Highlight));
-    }
+//     if (option.state & QStyle::State_MouseOver) {
+//         painter->fillRect(option.rect, option.palette.brush(QPalette::Inactive, QPalette::Highlight));
+//     }
+
     
     bool ntds = needToDrawSender(index, data);
     if (ntds || option.rect.y() < 1) {
@@ -118,6 +125,9 @@ void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio
     } else {
         opt.displayAlignment = Qt::AlignRight | Qt::AlignTop;
     }
+
+    QPoint bubbleBegin = messageRect.topLeft();
+    messageRect.adjust(bubbleMargin, bubbleMargin, -bubbleMargin, -bubbleMargin / 2);
     opt.rect = messageRect;
     
     QSize messageSize(0, 0);
@@ -127,9 +137,6 @@ void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio
         bodySize = messageSize;
     }
 
-    if (ntds) {
-        messageSize.rheight() += nickMetrics.lineSpacing();
-    }
     messageSize.rheight() += dateMetrics.height();
     QString dateString = data.date.toLocalTime().toString("hh:mm");
 
@@ -155,46 +162,64 @@ void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio
     } else {
         messageSize.setWidth(opt.rect.width());
     }
-    
-    QRect rect;
-    if (ntds) {
-        painter->setFont(nickFont);
-        painter->drawText(opt.rect, opt.displayAlignment, data.sender, &rect);
-        opt.rect.adjust(0, rect.height() + textMargin, 0, 0);
-    }
+
     painter->save();
+
+    int storedY = opt.rect.y();
+    if (ntds) {
+        opt.rect.adjust(0, nickMetrics.lineSpacing() + textMargin, 0, 0);
+    }
+
+    int attWidth(0);
     switch (data.attach.state) {
         case Models::none:
             clearHelperWidget(data);        //i can't imagine the situation where it's gonna be needed
             break;                          //but it's a possible performance problem
         case Models::uploading:
-            paintPreview(data, painter, opt);
+            attWidth = std::max(paintPreview(data, painter, opt), attWidth);
             [[fallthrough]];
         case Models::downloading:
-            paintBar(getBar(data), painter, data.sentByMe, opt);
+            messageSize.setWidth(opt.rect.width());
+            messageSize.rheight() += barHeight + textMargin + opt.rect.y() - storedY;
+            paintBubble(data, painter, messageSize, opt, bubbleBegin);
+            attWidth = std::max(paintBar(getBar(data), painter, data.sentByMe, opt), attWidth);
             break;
         case Models::remote:
-            paintButton(getButton(data), painter, data.sentByMe, opt);
+            attWidth = std::max(paintButton(getButton(data), painter, data.sentByMe, opt), attWidth);
             break;
         case Models::ready:
         case Models::local:
             clearHelperWidget(data);
-            paintPreview(data, painter, opt);
+            attWidth = std::max(paintPreview(data, painter, opt), attWidth);
             break;
         case Models::errorDownload: {
-            paintButton(getButton(data), painter, data.sentByMe, opt);
-            paintComment(data, painter, opt);
+            attWidth = std::max(paintButton(getButton(data), painter, data.sentByMe, opt), attWidth);
+            attWidth = std::max(paintComment(data, painter, opt), attWidth);
         }
             
             break;
         case Models::errorUpload:{
             clearHelperWidget(data);
-            paintPreview(data, painter, opt);
-            paintComment(data, painter, opt);
+            attWidth = std::max(paintPreview(data, painter, opt), attWidth);
+            attWidth = std::max(paintComment(data, painter, opt), attWidth);
         }
             break;
     }
     painter->restore();
+    if (data.attach.state != Models::uploading && data.attach.state != Models::downloading) {
+        messageSize.rheight() += opt.rect.y() - storedY;
+        messageSize.setWidth(std::max(attWidth, messageSize.width()));
+        paintBubble(data, painter, messageSize, opt, bubbleBegin);
+    }
+
+    QRect rect;
+    if (ntds) {
+        painter->setFont(nickFont);
+        int storedY2 = opt.rect.y();
+        opt.rect.setY(storedY);
+        painter->drawText(opt.rect, opt.displayAlignment, data.sender, &rect);
+        opt.rect.setY(storedY2);
+    }
     
     int messageLeft = INT16_MAX;
     int messageRight = opt.rect.x() + messageSize.width();
@@ -256,6 +281,23 @@ void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio
     }
 }
 
+void MessageDelegate::paintBubble(const Models::FeedItem& data, QPainter* painter, const QSize& messageSize, QStyleOptionViewItem& option, QPoint bubbleBegin) const
+{
+    painter->save();
+    if (data.sentByMe) {
+        bubbleBegin.setX(option.rect.topRight().x() - messageSize.width() - bubbleMargin);
+        painter->setBrush(option.palette.brush(QPalette::Inactive, QPalette::Highlight));
+    } else {
+        painter->setBrush(option.palette.brush(QPalette::Window));
+    }
+    QSize bubbleAddition(2 * bubbleMargin, 1.5 * bubbleMargin);
+    QRect bubble(bubbleBegin, messageSize + bubbleAddition);
+    painter->setPen(Qt::NoPen);
+    painter->drawRoundedRect(bubble, bubbleBorderRadius, bubbleBorderRadius);
+    painter->restore();
+}
+
+
 void MessageDelegate::paintAvatar(const Models::FeedItem& data, const QModelIndex& index, const QStyleOptionViewItem& option, QPainter* painter) const
 {
     int currentRow = index.row();
@@ -282,11 +324,22 @@ void MessageDelegate::paintAvatar(const Models::FeedItem& data, const QModelInde
         y = std::min(0, rect.bottom() - margin - avatarHeight);
         --currentRow;
     }
+
+    QPixmap pixmap = icon.pixmap(avatarHeight, avatarHeight);
+    QPainterPath path;
+    int ax;
+
     if (data.sentByMe) {
-        painter->drawPixmap(option.rect.width() - avatarHeight - margin, y + margin / 2, icon.pixmap(avatarHeight, avatarHeight));
+        ax = option.rect.width() - avatarHeight - margin;
     } else {
-        painter->drawPixmap(margin, y + margin / 2, icon.pixmap(avatarHeight, avatarHeight));
+        ax = margin;
     }
+
+    path.addEllipse(ax, y + margin / 2, avatarHeight, avatarHeight);
+    painter->save();
+    painter->setClipPath(path);
+    painter->drawPixmap(ax, y + margin / 2, pixmap);
+    painter->restore();
 }
 
 bool MessageDelegate::needToDrawAvatar(const QModelIndex& index, const Models::FeedItem& data, const QStyleOptionViewItem& option) const
@@ -309,7 +362,7 @@ bool MessageDelegate::needToDrawSender(const QModelIndex& index, const Models::F
 
 QSize MessageDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const
 {
-    QRect messageRect = option.rect.adjusted(0, margin / 2, -(avatarHeight + 3 * margin), -margin / 2);
+    QRect messageRect = option.rect.adjusted(bubbleMargin, margin / 2 + bubbleMargin, -(avatarHeight + 3 * margin + bubbleMargin), -(margin + bubbleMargin) / 2);
     QStyleOptionViewItem opt = option;
     opt.rect = messageRect;
     QVariant vi = index.data(Models::MessageFeed::Bulk);
@@ -347,10 +400,10 @@ QSize MessageDelegate::sizeHint(const QStyleOptionViewItem& option, const QModel
     }
     
     if (needToDrawSender(index, data)) {
-        messageSize.rheight() += nickMetrics.lineSpacing();
-        messageSize.rheight() += textMargin;
+        messageSize.rheight() += nickMetrics.lineSpacing() + textMargin;
     }
 
+    messageSize.rheight() += bubbleMargin + bubbleMargin / 2;
     messageSize.rheight() += dateMetrics.height() > statusIconSize ? dateMetrics.height() : statusIconSize;
     
 //     if (messageSize.height() < avatarHeight) {
@@ -372,17 +425,17 @@ void MessageDelegate::initializeFonts(const QFont& font)
     
     float ndps = nickFont.pointSizeF();
     if (ndps != -1) {
-        nickFont.setPointSizeF(ndps * 1.2);
+        nickFont.setPointSizeF(ndps * nickFontMultiplier);
     } else {
-        nickFont.setPointSize(nickFont.pointSize() + 2);
+        nickFont.setPointSize(nickFont.pointSize() * nickFontMultiplier);
     }
     
     dateFont.setItalic(true);
     float dps = dateFont.pointSizeF();
     if (dps != -1) {
-        dateFont.setPointSizeF(dps * 0.8);
+        dateFont.setPointSizeF(dps * dateFontMultiplier);
     } else {
-        dateFont.setPointSize(dateFont.pointSize() - 2);
+        dateFont.setPointSize(dateFont.pointSize() * dateFontMultiplier);
     }
     
     bodyMetrics = QFontMetrics(bodyFont);
@@ -400,11 +453,11 @@ bool MessageDelegate::editorEvent(QEvent* event, QAbstractItemModel* model, cons
     return QStyledItemDelegate::editorEvent(event, model, option, index);
 }
 
-void MessageDelegate::paintButton(QPushButton* btn, QPainter* painter, bool sentByMe, QStyleOptionViewItem& option) const
+int MessageDelegate::paintButton(QPushButton* btn, QPainter* painter, bool sentByMe, QStyleOptionViewItem& option) const
 {
     QPoint start;
     if (sentByMe) {
-        start = {option.rect.width() - btn->width(), option.rect.top()};
+        start = {option.rect.x() + option.rect.width() - btn->width(), option.rect.top()};
     } else {
         start = option.rect.topLeft();
     }
@@ -415,9 +468,10 @@ void MessageDelegate::paintButton(QPushButton* btn, QPainter* painter, bool sent
     btn->show();
     
     option.rect.adjust(0, buttonHeight + textMargin, 0, 0);
+    return btn->width();
 }
 
-void MessageDelegate::paintComment(const Models::FeedItem& data, QPainter* painter, QStyleOptionViewItem& option) const
+int MessageDelegate::paintComment(const Models::FeedItem& data, QPainter* painter, QStyleOptionViewItem& option) const
 {
     painter->setFont(dateFont);
     QColor q = painter->pen().color();
@@ -426,9 +480,11 @@ void MessageDelegate::paintComment(const Models::FeedItem& data, QPainter* paint
     QRect rect;
     painter->drawText(option.rect, option.displayAlignment, data.attach.error, &rect);
     option.rect.adjust(0, rect.height() + textMargin, 0, 0);
+
+    return rect.width();
 }
 
-void MessageDelegate::paintBar(QProgressBar* bar, QPainter* painter, bool sentByMe, QStyleOptionViewItem& option) const
+int MessageDelegate::paintBar(QProgressBar* bar, QPainter* painter, bool sentByMe, QStyleOptionViewItem& option) const
 {
     QPoint start = option.rect.topLeft();
     bar->resize(option.rect.width(), barHeight);   
@@ -437,9 +493,11 @@ void MessageDelegate::paintBar(QProgressBar* bar, QPainter* painter, bool sentBy
     bar->render(painter, QPoint(), QRegion(), QWidget::DrawChildren);
     
     option.rect.adjust(0, barHeight + textMargin, 0, 0);
+
+    return option.rect.width();
 }
 
-void MessageDelegate::paintPreview(const Models::FeedItem& data, QPainter* painter, QStyleOptionViewItem& option) const
+int MessageDelegate::paintPreview(const Models::FeedItem& data, QPainter* painter, QStyleOptionViewItem& option) const
 {
     Preview* preview = 0;
     std::map<QString, Preview*>::iterator itr = previews->find(data.id);
@@ -458,7 +516,10 @@ void MessageDelegate::paintPreview(const Models::FeedItem& data, QPainter* paint
         emit invalidPath(data.id);          //or deleted. This signal notifies the model, and the model notifies the core, preview can 
     }                                       //handle being invalid for as long as I need and can be even become valid again with a new path
     
-    option.rect.adjust(0, preview->size().height() + textMargin, 0, 0);
+    QSize pSize(preview->size());
+    option.rect.adjust(0, pSize.height() + textMargin, 0, 0);
+
+    return pSize.width();
 }
 
 QPushButton * MessageDelegate::getButton(const Models::FeedItem& data) const
diff --git a/ui/widgets/messageline/messagedelegate.h b/ui/widgets/messageline/messagedelegate.h
index 5792e01..87a79c9 100644
--- a/ui/widgets/messageline/messagedelegate.h
+++ b/ui/widgets/messageline/messagedelegate.h
@@ -61,11 +61,12 @@ signals:
     void invalidPath(const QString& messageId) const;
     
 protected:
-    void paintButton(QPushButton* btn, QPainter* painter, bool sentByMe, QStyleOptionViewItem& option) const;
-    void paintBar(QProgressBar* bar, QPainter* painter, bool sentByMe, QStyleOptionViewItem& option) const;
-    void paintPreview(const Models::FeedItem& data, QPainter* painter, QStyleOptionViewItem& option) const;
-    void paintComment(const Models::FeedItem& data, QPainter* painter, QStyleOptionViewItem& option) const;
+    int paintButton(QPushButton* btn, QPainter* painter, bool sentByMe, QStyleOptionViewItem& option) const;
+    int paintBar(QProgressBar* bar, QPainter* painter, bool sentByMe, QStyleOptionViewItem& option) const;
+    int paintPreview(const Models::FeedItem& data, QPainter* painter, QStyleOptionViewItem& option) const;
+    int paintComment(const Models::FeedItem& data, QPainter* painter, QStyleOptionViewItem& option) const;
     void paintAvatar(const Models::FeedItem& data, const QModelIndex& index, const QStyleOptionViewItem& option, QPainter* painter) const;
+    void paintBubble(const Models::FeedItem& data, QPainter* painter, const QSize& messageSize, QStyleOptionViewItem& option, QPoint bubbleBegin) const;
     QPushButton* getButton(const Models::FeedItem& data) const;
     QProgressBar* getBar(const Models::FeedItem& data) const;
     QLabel* getStatusIcon(const Models::FeedItem& data) const;

From 4d3ba6b11f5704cf09c404e2307dada7ac665322 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Sun, 9 Jan 2022 17:32:23 +0300
Subject: [PATCH 158/281] 0.2.0 finalization

---
 CHANGELOG.md                               |  13 +-
 README.md                                  |   4 +-
 core/main.cpp                              |   2 +-
 packaging/Archlinux/PKGBUILD               |   4 +-
 shared/global.cpp                          |   2 +-
 shared/global.h                            |   1 +
 ui/widgets/messageline/feedview.cpp        |  58 ++++--
 ui/widgets/messageline/feedview.h          |   3 +
 ui/widgets/messageline/messagedelegate.cpp | 220 ++++++++-------------
 ui/widgets/messageline/messagedelegate.h   |   6 +-
 ui/widgets/messageline/preview.cpp         |  55 ++++--
 ui/widgets/messageline/preview.h           |   5 +-
 12 files changed, 194 insertions(+), 179 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index bf90231..09c382a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,19 +1,28 @@
 # Changelog
 
-## Squawk 0.2.0 (Unreleased)
+## Squawk 0.2.0 (Jan 10, 2022)
 ### Bug fixes
 - carbon copies switches on again after reconnection
 - requesting the history of the current chat after reconnection
 - global availability (in drop down list) gets restored after reconnection
 - status icon in active chat changes when presence of the pen pal changes
 - infinite progress when open the dialogue with something that has no history to show
+- fallback icons for buttons, when no supported theme is installed (shunf4)
+- better handling messages with no id (shunf4)
+- removed dependency: uuid, now it's on Qt (shunf4)
+- better requesting latest history (shunf4)
 
 ### Improvements
 - slightly reduced the traffic on the startup by not requesting history of all MUCs
-- completely rewritten message feed, now it works way faster
+- completely rewritten message feed, now it works way faster and looks cooler
 - OPTIONAL RUNTIME dependency: "KIO Widgets" that is supposed to allow you to open a file in your default file manager
 - show in folder now is supposed to try it's best to show file in folder, even you don't have KIO installed
 - once uploaded local files don't get second time uploaded - the remote URL is reused
+- way better compilation time (vae)
+
+### New features
+- pasting images from clipboard to attachment (shunf4)
+- possible compilation for windows and macOS (shunf4)
 
 ## Squawk 0.1.5 (Jul 29, 2020)
 ### Bug fixes
diff --git a/README.md b/README.md
index e94972f..ff36f3c 100644
--- a/README.md
+++ b/README.md
@@ -4,13 +4,13 @@
 [![AUR version](https://img.shields.io/aur/version/squawk?style=flat-square)](https://aur.archlinux.org/packages/squawk/)
 [![Liberapay patrons](https://img.shields.io/liberapay/patrons/macaw.me?logo=liberapay&style=flat-square)](https://liberapay.com/macaw.me)
 
-![Squawk screenshot](https://macaw.me/images/squawk/0.1.4.png)
+![Squawk screenshot](https://macaw.me/images/squawk/0.2.0.png)
 
 ### Prerequisites
 
 - QT 5.12 *(lower versions might work but it wasn't tested)*
 - lmdb
-- CMake 3.0 or higher
+- CMake 3.3 or higher
 - qxmpp 1.1.0 or higher
 - KDE Frameworks: kwallet (optional)
 - KDE Frameworks: KIO (optional)
diff --git a/core/main.cpp b/core/main.cpp
index f63d4f8..d6eea3e 100644
--- a/core/main.cpp
+++ b/core/main.cpp
@@ -54,7 +54,7 @@ int main(int argc, char *argv[])
 #endif
     QApplication::setApplicationName("squawk");
     QApplication::setApplicationDisplayName("Squawk");
-    QApplication::setApplicationVersion("0.1.5");
+    QApplication::setApplicationVersion("0.2.0");
     
     QTranslator qtTranslator;
     qtTranslator.load("qt_" + QLocale::system().name(), QLibraryInfo::location(QLibraryInfo::TranslationsPath));
diff --git a/packaging/Archlinux/PKGBUILD b/packaging/Archlinux/PKGBUILD
index 20fea99..68e1558 100644
--- a/packaging/Archlinux/PKGBUILD
+++ b/packaging/Archlinux/PKGBUILD
@@ -1,6 +1,6 @@
 # Maintainer: Yury Gubich <blue@macaw.me>
 pkgname=squawk
-pkgver=0.1.5
+pkgver=0.2.0
 pkgrel=1
 pkgdesc="An XMPP desktop messenger, written on pure c++ (qt)"
 arch=('i686' 'x86_64')
@@ -11,7 +11,7 @@ makedepends=('cmake>=3.3' 'imagemagick' 'qt5-tools')
 optdepends=('kwallet: secure password storage (requires rebuild)')
 
 source=("$pkgname-$pkgver.tar.gz")
-sha256sums=('e1a4c88be9f0481d2aa21078faf42fd0e9d66b490b6d8af82827d441cb58df25')
+sha256sums=('8e93d3dbe1fc35cfecb7783af409c6a264244d11609b2241d4fe77d43d068419')
 build() {
         cd "$srcdir/squawk"
         cmake . -D CMAKE_INSTALL_PREFIX=/usr -D CMAKE_BUILD_TYPE=Release
diff --git a/shared/global.cpp b/shared/global.cpp
index d6f2169..5f3b4ed 100644
--- a/shared/global.cpp
+++ b/shared/global.cpp
@@ -144,7 +144,7 @@ Shared::Global::FileInfo Shared::Global::getFileInfo(const QString& path)
             size = defaultIconFileInfoHeight;
         }
         
-        itr = instance->fileCache.insert(std::make_pair(path, FileInfo({info.fileName(), size, type, p}))).first;
+        itr = instance->fileCache.insert(std::make_pair(path, FileInfo({info.absoluteFilePath(), info.fileName(), size, type, p}))).first;
     } 
     
     return itr->second;
diff --git a/shared/global.h b/shared/global.h
index 03cf84d..b1ae59c 100644
--- a/shared/global.h
+++ b/shared/global.h
@@ -55,6 +55,7 @@ namespace Shared {
                 animation
             };
             
+            QString path;
             QString name;
             QSize size;
             QMimeType mime;
diff --git a/ui/widgets/messageline/feedview.cpp b/ui/widgets/messageline/feedview.cpp
index d83ca6b..34f9400 100644
--- a/ui/widgets/messageline/feedview.cpp
+++ b/ui/widgets/messageline/feedview.cpp
@@ -43,6 +43,7 @@ FeedView::FeedView(QWidget* parent):
     QAbstractItemView(parent),
     hints(),
     vo(0),
+    elementMargin(0),
     specialDelegate(false),
     specialModel(false),
     clearWidgetsMode(false),
@@ -106,7 +107,7 @@ QRect FeedView::visualRect(const QModelIndex& index) const
     } else {
         const Hint& hint = hints.at(row);
         const QWidget* vp = viewport();
-        return QRect(0, vp->height() - hint.height - hint.offset + vo, vp->width(), hint.height);
+        return QRect(hint.x, vp->height() - hint.height - hint.offset + vo, hint.width, hint.height);
     }
 }
 
@@ -198,7 +199,7 @@ void FeedView::updateGeometries()
         option.rect.setWidth(layoutBounds.width());
         
         hints.clear();
-        uint32_t previousOffset = 0;
+        uint32_t previousOffset = elementMargin;
         QDateTime lastDate;
         for (int i = 0, size = m->rowCount(); i < size; ++i) {
             QModelIndex index = m->index(i, 0, rootIndex());
@@ -206,19 +207,32 @@ void FeedView::updateGeometries()
             if (i > 0) {
                 if (currentDate.daysTo(lastDate) > 0) {
                     previousOffset += dividerMetrics.height() + dateDeviderMargin * 2;
+                } else {
+                    previousOffset += elementMargin;
                 }
             }
             lastDate = currentDate;
-            int height = itemDelegate(index)->sizeHint(option, index).height();
+            QSize messageSize = itemDelegate(index)->sizeHint(option, index);
+            uint32_t offsetX(0);
+            if (specialDelegate) {
+                if (index.data(Models::MessageFeed::SentByMe).toBool()) {
+                    offsetX = layoutBounds.width() - messageSize.width() - MessageDelegate::avatarHeight - MessageDelegate::margin * 2;
+                } else {
+                    offsetX = MessageDelegate::avatarHeight + MessageDelegate::margin * 2;
+                }
+            }
+
             hints.emplace_back(Hint({
                 false,
                 previousOffset,
-                static_cast<uint32_t>(height)
+                static_cast<uint32_t>(messageSize.height()),
+                static_cast<uint32_t>(messageSize.width()),
+                offsetX
             }));
-            previousOffset += height;
+            previousOffset += messageSize.height();
         }
         
-        int totalHeight = previousOffset - layoutBounds.height();
+        int totalHeight = previousOffset - layoutBounds.height() + dividerMetrics.height() + dateDeviderMargin * 2;
         if (modelState != Models::MessageFeed::complete) {
             totalHeight += progressSize;
         }
@@ -240,7 +254,7 @@ void FeedView::updateGeometries()
 
 bool FeedView::tryToCalculateGeometriesWithNoScrollbars(const QStyleOptionViewItem& option, const QAbstractItemModel* m, uint32_t totalHeight)
 {
-    uint32_t previousOffset = 0;
+    uint32_t previousOffset = elementMargin;
     bool success = true;
     QDateTime lastDate;
     for (int i = 0, size = m->rowCount(); i < size; ++i) {
@@ -249,21 +263,39 @@ bool FeedView::tryToCalculateGeometriesWithNoScrollbars(const QStyleOptionViewIt
         if (i > 0) {
             if (currentDate.daysTo(lastDate) > 0) {
                 previousOffset += dateDeviderMargin * 2 + dividerMetrics.height();
+            } else {
+                previousOffset += elementMargin;
             }
         }
         lastDate = currentDate;
-        int height = itemDelegate(index)->sizeHint(option, index).height();
+        QSize messageSize = itemDelegate(index)->sizeHint(option, index);
         
-        if (previousOffset + height > totalHeight) {
+        if (previousOffset + messageSize.height() + elementMargin > totalHeight) {
             success = false;
             break;
         }
+
+        uint32_t offsetX(0);
+        if (specialDelegate) {
+            if (index.data(Models::MessageFeed::SentByMe).toBool()) {
+                offsetX = option.rect.width() - messageSize.width() - MessageDelegate::avatarHeight - MessageDelegate::margin * 2;
+            } else {
+                offsetX = MessageDelegate::avatarHeight + MessageDelegate::margin * 2;
+            }
+        }
         hints.emplace_back(Hint({
             false,
             previousOffset,
-            static_cast<uint32_t>(height)
+            static_cast<uint32_t>(messageSize.height()),
+            static_cast<uint32_t>(messageSize.width()),
+            offsetX
         }));
-        previousOffset += height;
+        previousOffset += messageSize.height();
+    }
+
+    previousOffset += dateDeviderMargin * 2 + dividerMetrics.height();
+    if (previousOffset > totalHeight) {
+        success = false;
     }
     
     return success;
@@ -336,7 +368,7 @@ void FeedView::paintEvent(QPaintEvent* event)
         lastDate = currentDate;
     }
     if (!lastDate.isNull() && inZone) {     //if after drawing all messages there is still space
-        drawDateDevider(option.rect.bottom(), lastDate, painter);
+        drawDateDevider(option.rect.top() - dateDeviderMargin * 2 - dividerMetrics.height(), lastDate, painter);
     }
     
     if (clearWidgetsMode && specialDelegate) {
@@ -423,10 +455,12 @@ void FeedView::setItemDelegate(QAbstractItemDelegate* delegate)
     MessageDelegate* del = dynamic_cast<MessageDelegate*>(delegate);
     if (del) {
         specialDelegate = true;
+        elementMargin = MessageDelegate::margin;
         connect(del, &MessageDelegate::buttonPushed, this, &FeedView::onMessageButtonPushed);
         connect(del, &MessageDelegate::invalidPath, this, &FeedView::onMessageInvalidPath);
     } else {
         specialDelegate = false;
+        elementMargin = 0;
     }
 }
 
diff --git a/ui/widgets/messageline/feedview.h b/ui/widgets/messageline/feedview.h
index 5e08946..8bcd913 100644
--- a/ui/widgets/messageline/feedview.h
+++ b/ui/widgets/messageline/feedview.h
@@ -80,9 +80,12 @@ private:
         bool dirty;
         uint32_t offset;
         uint32_t height;
+        uint32_t width;
+        uint32_t x;
     };
     std::deque<Hint> hints;
     int vo;
+    int elementMargin;
     bool specialDelegate;
     bool specialModel;
     bool clearWidgetsMode;
diff --git a/ui/widgets/messageline/messagedelegate.cpp b/ui/widgets/messageline/messagedelegate.cpp
index 5dd0dce..d692752 100644
--- a/ui/widgets/messageline/messagedelegate.cpp
+++ b/ui/widgets/messageline/messagedelegate.cpp
@@ -26,8 +26,8 @@
 #include "messagedelegate.h"
 #include "messagefeed.h"
 
-constexpr int avatarHeight = 50;
-constexpr int margin = 6;
+int MessageDelegate::avatarHeight(50);
+int MessageDelegate::margin(6);
 constexpr int textMargin = 2;
 constexpr int statusIconSize = 16;
 constexpr float nickFontMultiplier = 1.1;
@@ -45,6 +45,7 @@ MessageDelegate::MessageDelegate(QObject* parent):
     nickMetrics(nickFont),
     dateMetrics(dateFont),
     buttonHeight(0),
+    buttonWidth(0),
     barHeight(0),
     buttons(new std::map<QString, FeedButton*>()),
     bars(new std::map<QString, QProgressBar*>()),
@@ -55,8 +56,9 @@ MessageDelegate::MessageDelegate(QObject* parent):
     idsToKeep(new std::set<QString>()),
     clearingWidgets(false)
 {
-    QPushButton btn;
+    QPushButton btn(QCoreApplication::translate("MessageLine", "Download"));
     buttonHeight = btn.sizeHint().height();
+    buttonWidth = btn.sizeHint().width();
     
     QProgressBar bar;
     barHeight = bar.sizeHint().height();
@@ -107,141 +109,79 @@ void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio
     painter->save();
     painter->setRenderHint(QPainter::Antialiasing, true);
     
-//     if (option.state & QStyle::State_MouseOver) {
-//         painter->fillRect(option.rect, option.palette.brush(QPalette::Inactive, QPalette::Highlight));
-//     }
-
-    
+    paintBubble(data, painter, option);
     bool ntds = needToDrawSender(index, data);
     if (ntds || option.rect.y() < 1) {
         paintAvatar(data, index, option, painter);
     }
     
     QStyleOptionViewItem opt = option;
-    QRect messageRect = option.rect.adjusted(margin, margin / 2, -(avatarHeight + 2 * margin), -margin / 2);
+    opt.rect = option.rect.adjusted(bubbleMargin, bubbleMargin, -bubbleMargin, -bubbleMargin / 2);
     if (!data.sentByMe) {
         opt.displayAlignment = Qt::AlignLeft | Qt::AlignTop;
-        messageRect.adjust(avatarHeight + margin, 0, avatarHeight + margin, 0);
     } else {
         opt.displayAlignment = Qt::AlignRight | Qt::AlignTop;
     }
 
-    QPoint bubbleBegin = messageRect.topLeft();
-    messageRect.adjust(bubbleMargin, bubbleMargin, -bubbleMargin, -bubbleMargin / 2);
-    opt.rect = messageRect;
-    
-    QSize messageSize(0, 0);
     QSize bodySize(0, 0);
     if (data.text.size() > 0) {
-        messageSize = bodyMetrics.boundingRect(messageRect, Qt::TextWordWrap, data.text).size();
-        bodySize = messageSize;
-    }
-
-    messageSize.rheight() += dateMetrics.height();
-    QString dateString = data.date.toLocalTime().toString("hh:mm");
-
-    if (messageSize.width() < opt.rect.width()) {
-        if (ntds) {
-            QSize senderSize = nickMetrics.boundingRect(messageRect, 0, data.sender).size();
-            if (senderSize.width() > messageSize.width()) {
-                messageSize.setWidth(senderSize.width());
-            }
-        }
-        QSize dateSize = dateMetrics.boundingRect(messageRect, 0, dateString).size();
-        int addition = 0;
-        
-        if (data.correction.corrected) {
-            addition += margin + statusIconSize;
-        }
-        if (data.sentByMe) {
-            addition += margin + statusIconSize;
-        }
-        if (dateSize.width() + addition > messageSize.width()) {
-            messageSize.setWidth(dateSize.width() + addition);
-        }
-    } else {
-        messageSize.setWidth(opt.rect.width());
-    }
-
-    painter->save();
-
-    int storedY = opt.rect.y();
-    if (ntds) {
-        opt.rect.adjust(0, nickMetrics.lineSpacing() + textMargin, 0, 0);
-    }
-
-    int attWidth(0);
-    switch (data.attach.state) {
-        case Models::none:
-            clearHelperWidget(data);        //i can't imagine the situation where it's gonna be needed
-            break;                          //but it's a possible performance problem
-        case Models::uploading:
-            attWidth = std::max(paintPreview(data, painter, opt), attWidth);
-            [[fallthrough]];
-        case Models::downloading:
-            messageSize.setWidth(opt.rect.width());
-            messageSize.rheight() += barHeight + textMargin + opt.rect.y() - storedY;
-            paintBubble(data, painter, messageSize, opt, bubbleBegin);
-            attWidth = std::max(paintBar(getBar(data), painter, data.sentByMe, opt), attWidth);
-            break;
-        case Models::remote:
-            attWidth = std::max(paintButton(getButton(data), painter, data.sentByMe, opt), attWidth);
-            break;
-        case Models::ready:
-        case Models::local:
-            clearHelperWidget(data);
-            attWidth = std::max(paintPreview(data, painter, opt), attWidth);
-            break;
-        case Models::errorDownload: {
-            attWidth = std::max(paintButton(getButton(data), painter, data.sentByMe, opt), attWidth);
-            attWidth = std::max(paintComment(data, painter, opt), attWidth);
-        }
-            
-            break;
-        case Models::errorUpload:{
-            clearHelperWidget(data);
-            attWidth = std::max(paintPreview(data, painter, opt), attWidth);
-            attWidth = std::max(paintComment(data, painter, opt), attWidth);
-        }
-            break;
-    }
-    painter->restore();
-    if (data.attach.state != Models::uploading && data.attach.state != Models::downloading) {
-        messageSize.rheight() += opt.rect.y() - storedY;
-        messageSize.setWidth(std::max(attWidth, messageSize.width()));
-        paintBubble(data, painter, messageSize, opt, bubbleBegin);
+        bodySize = bodyMetrics.boundingRect(opt.rect, Qt::TextWordWrap, data.text).size();
     }
 
     QRect rect;
     if (ntds) {
         painter->setFont(nickFont);
-        int storedY2 = opt.rect.y();
-        opt.rect.setY(storedY);
         painter->drawText(opt.rect, opt.displayAlignment, data.sender, &rect);
-        opt.rect.setY(storedY2);
+        opt.rect.adjust(0, nickMetrics.lineSpacing() + textMargin, 0, 0);
     }
+
+    painter->save();
+    switch (data.attach.state) {
+        case Models::none:
+            clearHelperWidget(data);        //i can't imagine the situation where it's gonna be needed
+            break;                          //but it's a possible performance problem
+        case Models::uploading:
+            paintPreview(data, painter, opt);
+            [[fallthrough]];
+        case Models::downloading:
+            paintBar(getBar(data), painter, data.sentByMe, opt);
+            break;
+        case Models::remote:
+            paintButton(getButton(data), painter, data.sentByMe, opt);
+            break;
+        case Models::ready:
+        case Models::local:
+            clearHelperWidget(data);
+            paintPreview(data, painter, opt);
+            break;
+        case Models::errorDownload: {
+            paintButton(getButton(data), painter, data.sentByMe, opt);
+            paintComment(data, painter, opt);
+        }
+            
+            break;
+        case Models::errorUpload:{
+            clearHelperWidget(data);
+            paintPreview(data, painter, opt);
+            paintComment(data, painter, opt);
+        }
+            break;
+    }
+    painter->restore();
     
-    int messageLeft = INT16_MAX;
-    int messageRight = opt.rect.x() + messageSize.width();
     QWidget* vp = static_cast<QWidget*>(painter->device());
     if (data.text.size() > 0) {
         QLabel* body = getBody(data);
         body->setParent(vp);
-        body->setMaximumWidth(bodySize.width());
-        body->setMinimumWidth(bodySize.width());
-        body->setMinimumHeight(bodySize.height());
-        body->setMaximumHeight(bodySize.height());
-        body->setAlignment(opt.displayAlignment);
-        messageLeft = opt.rect.x();
-        if (data.sentByMe) {
-            messageLeft = opt.rect.topRight().x() - bodySize.width();
-        }
-        body->move(messageLeft, opt.rect.y());
+        body->setMinimumSize(bodySize);
+        body->setMaximumSize(bodySize);
+        body->move(opt.rect.left(), opt.rect.y());
         body->show();
         opt.rect.adjust(0, bodySize.height() + textMargin, 0, 0);
     }
     painter->setFont(dateFont);
     QColor q = painter->pen().color();
+    QString dateString = data.date.toLocalTime().toString("hh:mm");
     q.setAlpha(180);
     painter->setPen(q);
     painter->drawText(opt.rect, opt.displayAlignment, dateString, &rect);
@@ -250,7 +190,7 @@ void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio
         QLabel* statusIcon = getStatusIcon(data);
         
         statusIcon->setParent(vp);
-        statusIcon->move(opt.rect.topRight().x() - messageSize.width(), currentY);
+        statusIcon->move(opt.rect.left(), currentY);
         statusIcon->show();
         
         opt.rect.adjust(0, statusIconSize + textMargin, 0, 0);
@@ -261,9 +201,9 @@ void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio
         
         pencilIcon->setParent(vp);
         if (data.sentByMe) {
-            pencilIcon->move(opt.rect.topRight().x() - messageSize.width() + statusIconSize + margin, currentY);
+            pencilIcon->move(opt.rect.left() + statusIconSize + margin, currentY);
         } else {
-            pencilIcon->move(messageRight - statusIconSize - margin, currentY);
+            pencilIcon->move(opt.rect.right() - statusIconSize - margin, currentY);
         }
         pencilIcon->show();
     } else {
@@ -281,19 +221,16 @@ void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio
     }
 }
 
-void MessageDelegate::paintBubble(const Models::FeedItem& data, QPainter* painter, const QSize& messageSize, QStyleOptionViewItem& option, QPoint bubbleBegin) const
+void MessageDelegate::paintBubble(const Models::FeedItem& data, QPainter* painter, const QStyleOptionViewItem& option) const
 {
     painter->save();
     if (data.sentByMe) {
-        bubbleBegin.setX(option.rect.topRight().x() - messageSize.width() - bubbleMargin);
         painter->setBrush(option.palette.brush(QPalette::Inactive, QPalette::Highlight));
     } else {
         painter->setBrush(option.palette.brush(QPalette::Window));
     }
-    QSize bubbleAddition(2 * bubbleMargin, 1.5 * bubbleMargin);
-    QRect bubble(bubbleBegin, messageSize + bubbleAddition);
     painter->setPen(Qt::NoPen);
-    painter->drawRoundedRect(bubble, bubbleBorderRadius, bubbleBorderRadius);
+    painter->drawRoundedRect(option.rect, bubbleBorderRadius, bubbleBorderRadius);
     painter->restore();
 }
 
@@ -330,7 +267,7 @@ void MessageDelegate::paintAvatar(const Models::FeedItem& data, const QModelInde
     int ax;
 
     if (data.sentByMe) {
-        ax = option.rect.width() - avatarHeight - margin;
+        ax = option.rect.x() + option.rect.width() + margin;
     } else {
         ax = margin;
     }
@@ -381,36 +318,51 @@ QSize MessageDelegate::sizeHint(const QStyleOptionViewItem& option, const QModel
             [[fallthrough]];
         case Models::downloading:
             messageSize.rheight() += barHeight + textMargin;
+            messageSize.setWidth(messageRect.width());
             break;
         case Models::remote:
             messageSize.rheight() += buttonHeight + textMargin;
+            messageSize.setWidth(std::max(messageSize.width(), buttonWidth));
             break;
         case Models::ready:
-        case Models::local:
-            messageSize.rheight() += Preview::calculateAttachSize(data.attach.localPath, messageRect).height() + textMargin;
+        case Models::local: {
+            QSize aSize = Preview::calculateAttachSize(data.attach.localPath, messageRect);
+            messageSize.rheight() += aSize.height() + textMargin;
+            messageSize.setWidth(std::max(messageSize.width(), aSize.width()));
+        }
             break;
-        case Models::errorDownload:
-            messageSize.rheight() += buttonHeight + textMargin;
-            messageSize.rheight() += dateMetrics.boundingRect(messageRect, Qt::TextWordWrap, data.attach.error).size().height() + textMargin;
+        case Models::errorDownload: {
+            QSize commentSize = dateMetrics.boundingRect(messageRect, Qt::TextWordWrap, data.attach.error).size();
+            messageSize.rheight() += commentSize.height() + buttonHeight + textMargin * 2;
+            messageSize.setWidth(std::max(messageSize.width(), std::max(commentSize.width(), buttonWidth)));
+        }
             break;
-        case Models::errorUpload:
-            messageSize.rheight() += Preview::calculateAttachSize(data.attach.localPath, messageRect).height() + textMargin;
-            messageSize.rheight() += dateMetrics.boundingRect(messageRect, Qt::TextWordWrap, data.attach.error).size().height() + textMargin;
+        case Models::errorUpload: {
+            QSize aSize = Preview::calculateAttachSize(data.attach.localPath, messageRect);
+            QSize commentSize = dateMetrics.boundingRect(messageRect, Qt::TextWordWrap, data.attach.error).size();
+            messageSize.rheight() += aSize.height() + commentSize.height() + textMargin * 2;
+            messageSize.setWidth(std::max(messageSize.width(), std::max(commentSize.width(), aSize.width())));
+        }
             break;
     }
     
     if (needToDrawSender(index, data)) {
-        messageSize.rheight() += nickMetrics.lineSpacing() + textMargin;
+        QSize senderSize = nickMetrics.boundingRect(messageRect, 0, data.sender).size();
+        messageSize.rheight() += senderSize.height() + textMargin;
+        messageSize.setWidth(std::max(senderSize.width(), messageSize.width()));
     }
 
-    messageSize.rheight() += bubbleMargin + bubbleMargin / 2;
-    messageSize.rheight() += dateMetrics.height() > statusIconSize ? dateMetrics.height() : statusIconSize;
-    
-//     if (messageSize.height() < avatarHeight) {
-//         messageSize.setHeight(avatarHeight);
-//     }
-    
-    messageSize.rheight() += margin;
+    QString dateString = data.date.toLocalTime().toString("hh:mm");
+    QSize dateSize = dateMetrics.boundingRect(messageRect, 0, dateString).size();
+    messageSize.rheight() += bubbleMargin * 1.5;
+    messageSize.rheight() += dateSize.height() > statusIconSize ? dateSize.height() : statusIconSize;
+
+    int statusWidth = dateSize.width() + statusIconSize + margin;
+    if (data.correction.corrected) {
+        statusWidth += statusIconSize + margin;
+    }
+    messageSize.setWidth(std::max(statusWidth, messageSize.width()));
+    messageSize.rwidth() += 2 * bubbleMargin;
     
     return messageSize;
 }
@@ -508,7 +460,7 @@ int MessageDelegate::paintPreview(const Models::FeedItem& data, QPainter* painte
         preview->actualize(data.attach.localPath, size, option.rect.topLeft());
     } else {
         QWidget* vp = static_cast<QWidget*>(painter->device());
-        preview = new Preview(data.attach.localPath, size, option.rect.topLeft(), data.sentByMe, vp);
+        preview = new Preview(data.attach.localPath, size, option.rect.topLeft(), vp);
         previews->insert(std::make_pair(data.id, preview));
     }
     
diff --git a/ui/widgets/messageline/messagedelegate.h b/ui/widgets/messageline/messagedelegate.h
index 87a79c9..b58a1bb 100644
--- a/ui/widgets/messageline/messagedelegate.h
+++ b/ui/widgets/messageline/messagedelegate.h
@@ -55,6 +55,9 @@ public:
     bool editorEvent(QEvent * event, QAbstractItemModel * model, const QStyleOptionViewItem & option, const QModelIndex & index) override;
     void endClearWidgets();
     void beginClearWidgets();
+
+    static int avatarHeight;
+    static int margin;
     
 signals:
     void buttonPushed(const QString& messageId) const;
@@ -66,7 +69,7 @@ protected:
     int paintPreview(const Models::FeedItem& data, QPainter* painter, QStyleOptionViewItem& option) const;
     int paintComment(const Models::FeedItem& data, QPainter* painter, QStyleOptionViewItem& option) const;
     void paintAvatar(const Models::FeedItem& data, const QModelIndex& index, const QStyleOptionViewItem& option, QPainter* painter) const;
-    void paintBubble(const Models::FeedItem& data, QPainter* painter, const QSize& messageSize, QStyleOptionViewItem& option, QPoint bubbleBegin) const;
+    void paintBubble(const Models::FeedItem& data, QPainter* painter, const QStyleOptionViewItem& option) const;
     QPushButton* getButton(const Models::FeedItem& data) const;
     QProgressBar* getBar(const Models::FeedItem& data) const;
     QLabel* getStatusIcon(const Models::FeedItem& data) const;
@@ -94,6 +97,7 @@ private:
     QFontMetrics dateMetrics;
     
     int buttonHeight;
+    int buttonWidth;
     int barHeight;
     
     std::map<QString, FeedButton*>* buttons;
diff --git a/ui/widgets/messageline/preview.cpp b/ui/widgets/messageline/preview.cpp
index e54fce6..137293f 100644
--- a/ui/widgets/messageline/preview.cpp
+++ b/ui/widgets/messageline/preview.cpp
@@ -25,9 +25,8 @@ constexpr int maxAttachmentHeight = 500;
 QFont Preview::font;
 QFontMetrics Preview::metrics(Preview::font);
 
-Preview::Preview(const QString& pPath, const QSize& pMaxSize, const QPoint& pos, bool pRight, QWidget* pParent):
+Preview::Preview(const QString& pPath, const QSize& pMaxSize, const QPoint& pos, QWidget* pParent):
     info(Shared::Global::getFileInfo(pPath)),
-    path(pPath),
     maxSize(pMaxSize),
     actualSize(constrainAttachSize(info.size, maxSize)),
     cachedLabelSize(0, 0),
@@ -37,8 +36,7 @@ Preview::Preview(const QString& pPath, const QSize& pMaxSize, const QPoint& pos,
     parent(pParent),
     movie(0),
     fileReachable(true),
-    actualPreview(false),
-    right(pRight)
+    actualPreview(false)
 {
     
     initializeElements();
@@ -104,9 +102,6 @@ void Preview::actualize(const QString& newPath, const QSize& newSize, const QPoi
             }
         } else if (maxSizeChanged) {
             applyNewMaxSize();
-            if (right) {
-                positionChanged = true;
-            }
         }
         if (positionChanged || !actualPreview) {
             positionElements();
@@ -135,9 +130,6 @@ void Preview::setSize(const QSize& newSize)
         }
         if (maxSizeChanged || !actualPreview) {
             applyNewMaxSize();
-            if (right) {
-                positionElements();
-            }
         }
     }
 }
@@ -146,7 +138,7 @@ void Preview::applyNewSize()
 {
     switch (info.preview) {
         case Shared::Global::FileInfo::Preview::picture: {
-            QImageReader img(path);
+            QImageReader img(info.path);
             if (!img.canRead()) {
                 delete widget;
                 fileReachable = false;
@@ -216,9 +208,8 @@ void Preview::setPosition(const QPoint& newPoint)
 
 bool Preview::setPath(const QString& newPath)
 {
-    if (path != newPath) {
-        path = newPath;
-        info = Shared::Global::getFileInfo(path);
+    if (info.path != newPath) {
+        info = Shared::Global::getFileInfo(newPath);
         actualSize = constrainAttachSize(info.size, maxSize);
         clean();
         initializeElements();
@@ -235,7 +226,7 @@ void Preview::initializeElements()
 {
     switch (info.preview) {
         case Shared::Global::FileInfo::Preview::picture: {
-            QImageReader img(path);
+            QImageReader img(info.path);
             if (!img.canRead()) {
                 fileReachable = false;
             } else {
@@ -248,7 +239,7 @@ void Preview::initializeElements()
         }
             break;
         case Shared::Global::FileInfo::Preview::animation:{
-            movie = new QMovie(path);
+            movie = new QMovie(info.path);
             QObject::connect(movie, &QMovie::error, 
                 std::bind(&Preview::handleQMovieError, this, std::placeholders::_1)
             );
@@ -289,9 +280,6 @@ void Preview::initializeElements()
 void Preview::positionElements()
 {
     int start = position.x();
-    if (right) {
-        start += maxSize.width() - size().width();
-    }
     widget->move(start, position.y());
     if (!actualPreview) {
         int x = start + actualSize.width() + margin;
@@ -300,11 +288,36 @@ void Preview::positionElements()
     }
 }
 
+bool Preview::canVisualize(const Shared::Global::FileInfo& info)
+{
+    switch (info.preview) {
+        case Shared::Global::FileInfo::Preview::picture: {
+            QImageReader img(info.path);
+            return img.canRead();
+        }
+            break;
+        case Shared::Global::FileInfo::Preview::animation:{
+            QMovie movie(info.path);
+            return movie.isValid();
+        }
+            break;
+        default: {
+            return false;
+        }
+    }
+}
+
 QSize Preview::calculateAttachSize(const QString& path, const QRect& bounds)
 {
     Shared::Global::FileInfo info = Shared::Global::getFileInfo(path);
-    
-    return constrainAttachSize(info.size, bounds.size());
+    QSize constrained = constrainAttachSize(info.size, bounds.size());
+    if (!canVisualize(info)) {
+        int maxLabelWidth = bounds.width() - info.size.width() - margin;
+        QString elidedName = metrics.elidedText(info.name, Qt::ElideMiddle, maxLabelWidth);
+        int labelWidth = metrics.boundingRect(elidedName).size().width();
+        constrained.rwidth() += margin + labelWidth;
+    }
+    return constrained;
 }
 
 QSize Preview::constrainAttachSize(QSize src, QSize bounds)
diff --git a/ui/widgets/messageline/preview.h b/ui/widgets/messageline/preview.h
index 004ed45..5165137 100644
--- a/ui/widgets/messageline/preview.h
+++ b/ui/widgets/messageline/preview.h
@@ -38,7 +38,7 @@
  */
 class Preview {
 public:
-    Preview(const QString& pPath, const QSize& pMaxSize, const QPoint& pos, bool pRight, QWidget* parent);
+    Preview(const QString& pPath, const QSize& pMaxSize, const QPoint& pos, QWidget* parent);
     ~Preview();
     
     void actualize(const QString& newPath, const QSize& newSize, const QPoint& newPoint);
@@ -51,6 +51,7 @@ public:
     static void initializeFont(const QFont& newFont);
     static QSize constrainAttachSize(QSize src, QSize bounds);
     static QSize calculateAttachSize(const QString& path, const QRect& bounds);
+    static bool canVisualize(const Shared::Global::FileInfo& info);
     static QFont font;
     static QFontMetrics metrics;
     
@@ -64,7 +65,6 @@ private:
     
 private:
     Shared::Global::FileInfo info;
-    QString path;
     QSize maxSize;
     QSize actualSize;
     QSize cachedLabelSize;
@@ -75,7 +75,6 @@ private:
     QMovie* movie;
     bool fileReachable;
     bool actualPreview;
-    bool right;
 };
 
 #endif // PREVIEW_H

From 296328f12dd9296a8a8d092b4fcd6f8332a15403 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Tue, 11 Jan 2022 23:50:42 +0300
Subject: [PATCH 159/281] a bit of polish

---
 CMakeLists.txt                      | 21 ++++++++++++++-------
 README.md                           |  5 +++--
 packaging/Archlinux/PKGBUILD        |  4 ++--
 ui/widgets/messageline/feedview.cpp |  8 +++-----
 4 files changed, 22 insertions(+), 16 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index da89682..b104667 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,5 +1,5 @@
 cmake_minimum_required(VERSION 3.4)
-project(squawk VERSION 0.1.6 LANGUAGES CXX)
+project(squawk VERSION 0.2.0 LANGUAGES CXX)
 
 cmake_policy(SET CMP0076 NEW)
 cmake_policy(SET CMP0079 NEW)
@@ -32,6 +32,7 @@ option(WITH_KIO "Build KIO support module" ON)
 
 # Dependencies
 ## Qt
+set(QT_VERSION_MAJOR 5)
 find_package(Qt5 COMPONENTS Widgets DBus Gui Xml Network Core REQUIRED)
 find_package(Boost COMPONENTS)
 
@@ -114,12 +115,18 @@ endif ()
 message("Build type: ${CMAKE_BUILD_TYPE}")
 
 if(CMAKE_COMPILER_IS_GNUCXX)
-target_compile_options(squawk PRIVATE
-  "-Wall;-Wextra"
-  "$<$<CONFIG:DEBUG>:-g>"
-  "$<$<CONFIG:RELEASE>:-O3>"
-  "-fno-sized-deallocation" # for eliminating _ZdlPvm
-  )
+  set (COMPILE_OPTIONS -fno-sized-deallocation)   # for eliminating _ZdlPvm
+  if (CMAKE_BUILD_TYPE STREQUAL "Release")
+    list(APPEND COMPILE_OPTIONS -O3)
+  endif()
+  if (CMAKE_BUILD_TYPE STREQUAL Debug)
+    list(APPEND COMPILE_OPTIONS -g)
+    list(APPEND COMPILE_OPTIONS -Wall)
+    list(APPEND COMPILE_OPTIONS -Wextra)
+  endif()
+
+  message("Compilation options: " ${COMPILE_OPTIONS})
+  target_compile_options(squawk PRIVATE ${COMPILE_OPTIONS})
 endif(CMAKE_COMPILER_IS_GNUCXX)
 
 add_subdirectory(core)
diff --git a/README.md b/README.md
index ff36f3c..0af201f 100644
--- a/README.md
+++ b/README.md
@@ -10,11 +10,12 @@
 
 - QT 5.12 *(lower versions might work but it wasn't tested)*
 - lmdb
-- CMake 3.3 or higher
+- CMake 3.4 or higher
 - qxmpp 1.1.0 or higher
 - KDE Frameworks: kwallet (optional)
 - KDE Frameworks: KIO (optional)
-- Boost
+- Boost (just one little hpp from there)
+- Imagemagick (for compilation, to rasterize an SVG logo)
 
 ### Getting
 
diff --git a/packaging/Archlinux/PKGBUILD b/packaging/Archlinux/PKGBUILD
index 68e1558..0d2aaaa 100644
--- a/packaging/Archlinux/PKGBUILD
+++ b/packaging/Archlinux/PKGBUILD
@@ -7,8 +7,8 @@ arch=('i686' 'x86_64')
 url="https://git.macaw.me/blue/squawk"
 license=('GPL3')
 depends=('hicolor-icon-theme' 'desktop-file-utils' 'lmdb' 'qxmpp>=1.1.0')
-makedepends=('cmake>=3.3' 'imagemagick' 'qt5-tools')
-optdepends=('kwallet: secure password storage (requires rebuild)')
+makedepends=('cmake>=3.3' 'imagemagick' 'qt5-tools' 'boost')
+optdepends=('kwallet: secure password storage (requires rebuild)' 'kio: better show in folder action (requires rebuild)')
 
 source=("$pkgname-$pkgver.tar.gz")
 sha256sums=('8e93d3dbe1fc35cfecb7783af409c6a264244d11609b2241d4fe77d43d068419')
diff --git a/ui/widgets/messageline/feedview.cpp b/ui/widgets/messageline/feedview.cpp
index 34f9400..de7f56f 100644
--- a/ui/widgets/messageline/feedview.cpp
+++ b/ui/widgets/messageline/feedview.cpp
@@ -255,7 +255,6 @@ void FeedView::updateGeometries()
 bool FeedView::tryToCalculateGeometriesWithNoScrollbars(const QStyleOptionViewItem& option, const QAbstractItemModel* m, uint32_t totalHeight)
 {
     uint32_t previousOffset = elementMargin;
-    bool success = true;
     QDateTime lastDate;
     for (int i = 0, size = m->rowCount(); i < size; ++i) {
         QModelIndex index = m->index(i, 0, rootIndex());
@@ -271,8 +270,7 @@ bool FeedView::tryToCalculateGeometriesWithNoScrollbars(const QStyleOptionViewIt
         QSize messageSize = itemDelegate(index)->sizeHint(option, index);
         
         if (previousOffset + messageSize.height() + elementMargin > totalHeight) {
-            success = false;
-            break;
+            return false;
         }
 
         uint32_t offsetX(0);
@@ -295,10 +293,10 @@ bool FeedView::tryToCalculateGeometriesWithNoScrollbars(const QStyleOptionViewIt
 
     previousOffset += dateDeviderMargin * 2 + dividerMetrics.height();
     if (previousOffset > totalHeight) {
-        success = false;
+        return false;
     }
     
-    return success;
+    return true;
 }
 
 

From 62a59eb7a15f466ad737e9dc0ed6850d3d6a3d50 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Sat, 15 Jan 2022 15:36:49 +0300
Subject: [PATCH 160/281] Added logs for Shura to help me to debug a download
 attachment issue

---
 CHANGELOG.md                           | 7 +++++++
 CMakeLists.txt                         | 2 +-
 core/archive.cpp                       | 1 +
 core/handlers/messagehandler.cpp       | 3 +++
 core/main.cpp                          | 2 +-
 ui/widgets/messageline/messagefeed.cpp | 2 +-
 6 files changed, 14 insertions(+), 3 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 09c382a..4052647 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,12 @@
 # Changelog
 
+## Squawk 0.2.1 (UNRELEASED)
+### Bug fixes
+
+### Improvements
+
+### New features
+
 ## Squawk 0.2.0 (Jan 10, 2022)
 ### Bug fixes
 - carbon copies switches on again after reconnection
diff --git a/CMakeLists.txt b/CMakeLists.txt
index b104667..cd9d793 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,5 +1,5 @@
 cmake_minimum_required(VERSION 3.4)
-project(squawk VERSION 0.2.0 LANGUAGES CXX)
+project(squawk VERSION 0.2.1 LANGUAGES CXX)
 
 cmake_policy(SET CMP0076 NEW)
 cmake_policy(SET CMP0079 NEW)
diff --git a/core/archive.cpp b/core/archive.cpp
index 2ca409c..cb65a53 100644
--- a/core/archive.cpp
+++ b/core/archive.cpp
@@ -123,6 +123,7 @@ bool Core::Archive::addElement(const Shared::Message& message)
     if (!opened) {
         throw Closed("addElement", jid.toStdString());
     }
+    qDebug() << "Adding message with id " << message.getId();
     QByteArray ba;
     QDataStream ds(&ba, QIODevice::WriteOnly);
     message.serialize(ds);
diff --git a/core/handlers/messagehandler.cpp b/core/handlers/messagehandler.cpp
index 33b3458..eb840f8 100644
--- a/core/handlers/messagehandler.cpp
+++ b/core/handlers/messagehandler.cpp
@@ -80,6 +80,7 @@ bool Core::MessageHandler::handleChatMessage(const QXmppMessage& msg, bool outgo
         Contact* cnt = acc->rh->getContact(jid);
         if (cnt == 0) {
             cnt = acc->rh->addOutOfRosterContact(jid);
+            qDebug() << "appending message" << sMsg.getId() << "to an out of roster contact";
         }
         if (outgoing) {
             if (forwarded) {
@@ -162,6 +163,7 @@ void Core::MessageHandler::initializeMessage(Shared::Message& target, const QXmp
         id = source.id();
     }
     target.setStanzaId(source.stanzaId());
+    qDebug() << "initializing message with originId:" << source.originId() << ", id:" << source.id() << ", stansaId:" << source.stanzaId();
 #else
     id = source.id();
 #endif
@@ -170,6 +172,7 @@ void Core::MessageHandler::initializeMessage(Shared::Message& target, const QXmp
     if (messageId.size() == 0) {
         target.generateRandomId();          //TODO out of desperation, I need at least a random ID
         messageId = target.getId();
+        qDebug() << "Had do initialize a message with no id, assigning autogenerated" << messageId;
     }
     target.setFrom(source.from());
     target.setTo(source.to());
diff --git a/core/main.cpp b/core/main.cpp
index d6eea3e..7c94a12 100644
--- a/core/main.cpp
+++ b/core/main.cpp
@@ -54,7 +54,7 @@ int main(int argc, char *argv[])
 #endif
     QApplication::setApplicationName("squawk");
     QApplication::setApplicationDisplayName("Squawk");
-    QApplication::setApplicationVersion("0.2.0");
+    QApplication::setApplicationVersion("0.2.1");
     
     QTranslator qtTranslator;
     qtTranslator.load("qt_" + QLocale::system().name(), QLibraryInfo::location(QLibraryInfo::TranslationsPath));
diff --git a/ui/widgets/messageline/messagefeed.cpp b/ui/widgets/messageline/messagefeed.cpp
index 733cf1d..4803dce 100644
--- a/ui/widgets/messageline/messagefeed.cpp
+++ b/ui/widgets/messageline/messagefeed.cpp
@@ -86,7 +86,7 @@ void Models::MessageFeed::addMessage(const Shared::Message& msg)
     emit newMessage(msg);
     
     if (observersAmount == 0 && !msg.getForwarded()) {      //not to notify when the message is delivered by the carbon copy
-        unreadMessages->insert(msg.getId());                //cuz it could be my own one or the one I read on another device
+        unreadMessages->insert(id);                         //cuz it could be my own one or the one I read on another device
         emit unreadMessagesCountChanged();
         emit unnoticedMessage(msg);
     }

From 6bee149e6b13d340483654316a3d391bfa6b29a0 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Sun, 16 Jan 2022 22:54:57 +0300
Subject: [PATCH 161/281] started to work on settings

---
 ui/squawk.cpp                        |  28 ++++++-
 ui/squawk.h                          |   6 +-
 ui/squawk.ui                         |   9 ++
 ui/widgets/CMakeLists.txt            |   1 +
 ui/widgets/settings/CMakeLists.txt   |   7 ++
 ui/widgets/settings/settings.cpp     |  14 ++++
 ui/widgets/settings/settings.h       |  26 ++++++
 ui/widgets/settings/settings.ui      | 119 +++++++++++++++++++++++++++
 ui/widgets/settings/settingslist.cpp |  27 ++++++
 ui/widgets/settings/settingslist.h   |  25 ++++++
 10 files changed, 260 insertions(+), 2 deletions(-)
 create mode 100644 ui/widgets/settings/CMakeLists.txt
 create mode 100644 ui/widgets/settings/settings.cpp
 create mode 100644 ui/widgets/settings/settings.h
 create mode 100644 ui/widgets/settings/settings.ui
 create mode 100644 ui/widgets/settings/settingslist.cpp
 create mode 100644 ui/widgets/settings/settingslist.h

diff --git a/ui/squawk.cpp b/ui/squawk.cpp
index 800f02a..4d22b34 100644
--- a/ui/squawk.cpp
+++ b/ui/squawk.cpp
@@ -25,6 +25,7 @@ Squawk::Squawk(QWidget *parent) :
     QMainWindow(parent),
     m_ui(new Ui::Squawk),
     accounts(0),
+    preferences(0),
     rosterModel(),
     conversations(),
     contextMenu(new QMenu()),
@@ -55,6 +56,7 @@ Squawk::Squawk(QWidget *parent) :
     m_ui->comboBox->setCurrentIndex(static_cast<int>(Shared::Availability::offline));
     
     connect(m_ui->actionAccounts, &QAction::triggered, this, &Squawk::onAccounts);
+    connect(m_ui->actionPreferences, &QAction::triggered, this, &Squawk::onPreferences);
     connect(m_ui->actionAddContact, &QAction::triggered, this, &Squawk::onNewContact);
     connect(m_ui->actionAddConference, &QAction::triggered, this, &Squawk::onNewConference);
     connect(m_ui->actionQuit, &QAction::triggered, this, &Squawk::close);
@@ -117,6 +119,22 @@ void Squawk::onAccounts()
     }
 }
 
+void Squawk::onPreferences()
+{
+    if (preferences == 0) {
+        preferences = new Settings();
+        preferences->setAttribute(Qt::WA_DeleteOnClose);
+        connect(preferences, &Settings::destroyed, this, &Squawk::onPreferencesClosed);
+
+        preferences->show();
+    } else {
+        preferences->show();
+        preferences->raise();
+        preferences->activateWindow();
+    }
+}
+
+
 void Squawk::onAccountsSizeChanged(unsigned int size)
 {
     if (size > 0) {
@@ -173,6 +191,9 @@ void Squawk::closeEvent(QCloseEvent* event)
     if (accounts != 0) {
         accounts->close();
     }
+    if (preferences != 0) {
+        preferences->close();
+    }
     
     for (Conversations::const_iterator itr = conversations.begin(), end = conversations.end(); itr != end; ++itr) {
         disconnect(itr->second, &Conversation::destroyed, this, &Squawk::onConversationClosed);
@@ -190,11 +211,16 @@ void Squawk::closeEvent(QCloseEvent* event)
 }
 
 
-void Squawk::onAccountsClosed(QObject* parent)
+void Squawk::onAccountsClosed()
 {
     accounts = 0;
 }
 
+void Squawk::onPreferencesClosed()
+{
+    preferences = 0;
+}
+
 void Squawk::newAccount(const QMap<QString, QVariant>& account)
 {
     rosterModel.addAccount(account);
diff --git a/ui/squawk.h b/ui/squawk.h
index cb93259..26dc0c9 100644
--- a/ui/squawk.h
+++ b/ui/squawk.h
@@ -38,6 +38,7 @@
 #include "widgets/joinconference.h"
 #include "models/roster.h"
 #include "widgets/vcard/vcard.h"
+#include "widgets/settings/settings.h"
 
 #include "shared/shared.h"
 
@@ -117,6 +118,7 @@ private:
     QScopedPointer<Ui::Squawk> m_ui;
     
     Accounts* accounts;
+    Settings* preferences;
     Models::Roster rosterModel;
     Conversations conversations;
     QMenu* contextMenu;
@@ -136,12 +138,14 @@ protected slots:
     
 private slots:
     void onAccounts();
+    void onPreferences();
     void onNewContact();
     void onNewConference();
     void onNewContactAccepted();
     void onJoinConferenceAccepted();
     void onAccountsSizeChanged(unsigned int size);
-    void onAccountsClosed(QObject* parent = 0);
+    void onAccountsClosed();
+    void onPreferencesClosed();
     void onConversationClosed(QObject* parent = 0);
     void onVCardClosed();
     void onVCardSave(const Shared::VCard& card, const QString& account);
diff --git a/ui/squawk.ui b/ui/squawk.ui
index 01ffbba..840dfee 100644
--- a/ui/squawk.ui
+++ b/ui/squawk.ui
@@ -191,6 +191,7 @@
      <string>Settings</string>
     </property>
     <addaction name="actionAccounts"/>
+    <addaction name="actionPreferences"/>
    </widget>
    <widget class="QMenu" name="menuFile">
     <property name="title">
@@ -245,6 +246,14 @@
     <string>Add conference</string>
    </property>
   </action>
+  <action name="actionPreferences">
+   <property name="icon">
+    <iconset theme="settings-configure"/>
+   </property>
+   <property name="text">
+    <string>Preferences</string>
+   </property>
+  </action>
  </widget>
  <resources>
   <include location="../resources/resources.qrc"/>
diff --git a/ui/widgets/CMakeLists.txt b/ui/widgets/CMakeLists.txt
index c7e47e0..f3a2afe 100644
--- a/ui/widgets/CMakeLists.txt
+++ b/ui/widgets/CMakeLists.txt
@@ -22,3 +22,4 @@ target_sources(squawk PRIVATE
 
 add_subdirectory(vcard)
 add_subdirectory(messageline)
+add_subdirectory(settings)
diff --git a/ui/widgets/settings/CMakeLists.txt b/ui/widgets/settings/CMakeLists.txt
new file mode 100644
index 0000000..9f0fa76
--- /dev/null
+++ b/ui/widgets/settings/CMakeLists.txt
@@ -0,0 +1,7 @@
+target_sources(squawk PRIVATE
+    settingslist.h
+    settingslist.cpp
+    settings.h
+    settings.cpp
+    settings.ui
+)
diff --git a/ui/widgets/settings/settings.cpp b/ui/widgets/settings/settings.cpp
new file mode 100644
index 0000000..0397d0c
--- /dev/null
+++ b/ui/widgets/settings/settings.cpp
@@ -0,0 +1,14 @@
+#include "settings.h"
+#include "ui_settings.h"
+
+Settings::Settings(QWidget* parent):
+    QWidget(parent),
+    m_ui(new Ui::Settings())
+{
+    m_ui->setupUi(this);
+}
+
+Settings::~Settings()
+{
+}
+
diff --git a/ui/widgets/settings/settings.h b/ui/widgets/settings/settings.h
new file mode 100644
index 0000000..61129d8
--- /dev/null
+++ b/ui/widgets/settings/settings.h
@@ -0,0 +1,26 @@
+#ifndef SETTINGS_H
+#define SETTINGS_H
+
+#include <QWidget>
+#include <QScopedPointer>
+
+namespace Ui
+{
+class Settings;
+}
+
+/**
+ * @todo write docs
+ */
+class Settings : public QWidget
+{
+    Q_OBJECT
+public:
+    Settings(QWidget* parent = nullptr);
+    ~Settings();
+
+private:
+    QScopedPointer<Ui::Settings> m_ui;
+};
+
+#endif // SETTINGS_H
diff --git a/ui/widgets/settings/settings.ui b/ui/widgets/settings/settings.ui
new file mode 100644
index 0000000..f5c4680
--- /dev/null
+++ b/ui/widgets/settings/settings.ui
@@ -0,0 +1,119 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>Settings</class>
+ <widget class="QWidget" name="Settings">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>520</width>
+    <height>363</height>
+   </rect>
+  </property>
+  <layout class="QGridLayout" name="gridLayout">
+   <property name="leftMargin">
+    <number>0</number>
+   </property>
+   <property name="topMargin">
+    <number>0</number>
+   </property>
+   <property name="rightMargin">
+    <number>0</number>
+   </property>
+   <property name="bottomMargin">
+    <number>0</number>
+   </property>
+   <property name="spacing">
+    <number>0</number>
+   </property>
+   <item row="0" column="0">
+    <widget class="SettingsList" name="listWidget">
+     <property name="frameShape">
+      <enum>QFrame::NoFrame</enum>
+     </property>
+     <property name="lineWidth">
+      <number>0</number>
+     </property>
+     <property name="horizontalScrollBarPolicy">
+      <enum>Qt::ScrollBarAlwaysOff</enum>
+     </property>
+     <property name="sizeAdjustPolicy">
+      <enum>QAbstractScrollArea::AdjustToContents</enum>
+     </property>
+     <property name="editTriggers">
+      <set>QAbstractItemView::NoEditTriggers</set>
+     </property>
+     <property name="showDropIndicator" stdset="0">
+      <bool>false</bool>
+     </property>
+     <property name="dragDropMode">
+      <enum>QAbstractItemView::NoDragDrop</enum>
+     </property>
+     <property name="verticalScrollMode">
+      <enum>QAbstractItemView::ScrollPerPixel</enum>
+     </property>
+     <property name="movement">
+      <enum>QListView::Static</enum>
+     </property>
+     <property name="flow">
+      <enum>QListView::TopToBottom</enum>
+     </property>
+     <property name="isWrapping" stdset="0">
+      <bool>false</bool>
+     </property>
+     <property name="resizeMode">
+      <enum>QListView::Adjust</enum>
+     </property>
+     <property name="layoutMode">
+      <enum>QListView::Batched</enum>
+     </property>
+     <property name="viewMode">
+      <enum>QListView::IconMode</enum>
+     </property>
+     <property name="uniformItemSizes">
+      <bool>true</bool>
+     </property>
+     <property name="itemAlignment">
+      <set>Qt::AlignHCenter</set>
+     </property>
+     <property name="currentRow">
+      <number>0</number>
+     </property>
+     <item>
+      <property name="text">
+       <string>General</string>
+      </property>
+      <property name="icon">
+       <iconset theme="view-list-symbolic">
+        <normaloff>.</normaloff>.</iconset>
+      </property>
+      <property name="flags">
+       <set>ItemIsSelectable|ItemIsEnabled</set>
+      </property>
+     </item>
+     <item>
+      <property name="text">
+       <string>Appearance</string>
+      </property>
+      <property name="icon">
+       <iconset theme="preferences-desktop-theme">
+        <normaloff>.</normaloff>.</iconset>
+      </property>
+      <property name="flags">
+       <set>ItemIsSelectable|ItemIsEnabled</set>
+      </property>
+     </item>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <customwidgets>
+  <customwidget>
+   <class>SettingsList</class>
+   <extends>QListWidget</extends>
+   <header location="global">ui/widgets/settings/settingslist.h</header>
+  </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/ui/widgets/settings/settingslist.cpp b/ui/widgets/settings/settingslist.cpp
new file mode 100644
index 0000000..1925632
--- /dev/null
+++ b/ui/widgets/settings/settingslist.cpp
@@ -0,0 +1,27 @@
+#include "settingslist.h"
+
+SettingsList::SettingsList(QWidget* parent):
+    QListWidget(parent),
+    lastWidth(0)
+{
+}
+
+SettingsList::~SettingsList()
+{
+}
+
+QStyleOptionViewItem SettingsList::viewOptions() const
+{
+    QStyleOptionViewItem option = QListWidget::viewOptions();
+    if (!iconSize().isValid()) {
+        option.decorationSize.setWidth(lastWidth);
+    }
+    option.rect.setWidth(lastWidth);
+    return option;
+}
+
+void SettingsList::resizeEvent(QResizeEvent* event)
+{
+    lastWidth = event->size().width();
+    QListWidget::resizeEvent(event);
+}
diff --git a/ui/widgets/settings/settingslist.h b/ui/widgets/settings/settingslist.h
new file mode 100644
index 0000000..9621c67
--- /dev/null
+++ b/ui/widgets/settings/settingslist.h
@@ -0,0 +1,25 @@
+#ifndef UI_SETTINGSLIST_H
+#define UI_SETTINGSLIST_H
+
+#include <QListWidget>
+#include <QResizeEvent>
+
+/**
+ * @todo write docs
+ */
+class SettingsList : public QListWidget
+{
+    Q_OBJECT
+public:
+    SettingsList(QWidget* parent = nullptr);
+    ~SettingsList();
+
+protected:
+    QStyleOptionViewItem viewOptions() const override;
+    void resizeEvent(QResizeEvent * event) override;
+
+private:
+    int lastWidth;
+};
+
+#endif // UI_SETTINGSLIST_H

From 841e526e59fd7a12d6835de1bdee8967aaa3b5a1 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Mon, 17 Jan 2022 23:52:07 +0300
Subject: [PATCH 162/281] just some toying with designer

---
 ui/widgets/settings/settings.ui | 64 +++++++++++++++++++++++++++++++--
 1 file changed, 61 insertions(+), 3 deletions(-)

diff --git a/ui/widgets/settings/settings.ui b/ui/widgets/settings/settings.ui
index f5c4680..ca9946e 100644
--- a/ui/widgets/settings/settings.ui
+++ b/ui/widgets/settings/settings.ui
@@ -6,7 +6,7 @@
    <rect>
     <x>0</x>
     <y>0</y>
-    <width>520</width>
+    <width>465</width>
     <height>363</height>
    </rect>
   </property>
@@ -21,13 +21,51 @@
     <number>0</number>
    </property>
    <property name="bottomMargin">
-    <number>0</number>
+    <number>7</number>
    </property>
    <property name="spacing">
     <number>0</number>
    </property>
-   <item row="0" column="0">
+   <item row="2" column="3">
+    <widget class="QPushButton" name="pushButton">
+     <property name="text">
+      <string>Apply</string>
+     </property>
+     <property name="icon">
+      <iconset theme="dialog-ok-apply"/>
+     </property>
+    </widget>
+   </item>
+   <item row="2" column="2">
+    <widget class="QPushButton" name="pushButton_2">
+     <property name="text">
+      <string>Ok</string>
+     </property>
+     <property name="icon">
+      <iconset theme="dialog-ok"/>
+     </property>
+    </widget>
+   </item>
+   <item row="0" column="0" rowspan="3">
     <widget class="SettingsList" name="listWidget">
+     <property name="sizePolicy">
+      <sizepolicy hsizetype="Fixed" vsizetype="Expanding">
+       <horstretch>0</horstretch>
+       <verstretch>0</verstretch>
+      </sizepolicy>
+     </property>
+     <property name="minimumSize">
+      <size>
+       <width>120</width>
+       <height>0</height>
+      </size>
+     </property>
+     <property name="maximumSize">
+      <size>
+       <width>120</width>
+       <height>16777215</height>
+      </size>
+     </property>
      <property name="frameShape">
       <enum>QFrame::NoFrame</enum>
      </property>
@@ -105,6 +143,26 @@
      </item>
     </widget>
    </item>
+   <item row="1" column="1" colspan="3">
+    <widget class="QWidget" name="widget" native="true"/>
+   </item>
+   <item row="0" column="1">
+    <widget class="QLabel" name="label">
+     <property name="text">
+      <string>General</string>
+     </property>
+    </widget>
+   </item>
+   <item row="2" column="4">
+    <widget class="QPushButton" name="pushButton_3">
+     <property name="text">
+      <string>Cancel</string>
+     </property>
+     <property name="icon">
+      <iconset theme="dialog-cancel"/>
+     </property>
+    </widget>
+   </item>
   </layout>
  </widget>
  <customwidgets>

From a8a7ce2538887e6a4875ffa7f68a3f2859bd7dab Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Wed, 19 Jan 2022 23:46:42 +0300
Subject: [PATCH 163/281] some more thoughts about settings widgets

---
 ui/widgets/settings/CMakeLists.txt     |   6 ++
 ui/widgets/settings/pageappearance.cpp |  13 +++
 ui/widgets/settings/pageappearance.h   |  26 +++++
 ui/widgets/settings/pageappearance.ui  |  28 ++++++
 ui/widgets/settings/pagegeneral.cpp    |  13 +++
 ui/widgets/settings/pagegeneral.h      |  26 +++++
 ui/widgets/settings/pagegeneral.ui     |  28 ++++++
 ui/widgets/settings/settings.cpp       |  10 ++
 ui/widgets/settings/settings.h         |   4 +
 ui/widgets/settings/settings.ui        | 127 ++++++++++++++++---------
 ui/widgets/settings/settingslist.cpp   |  11 +++
 ui/widgets/settings/settingslist.h     |   1 +
 12 files changed, 248 insertions(+), 45 deletions(-)
 create mode 100644 ui/widgets/settings/pageappearance.cpp
 create mode 100644 ui/widgets/settings/pageappearance.h
 create mode 100644 ui/widgets/settings/pageappearance.ui
 create mode 100644 ui/widgets/settings/pagegeneral.cpp
 create mode 100644 ui/widgets/settings/pagegeneral.h
 create mode 100644 ui/widgets/settings/pagegeneral.ui

diff --git a/ui/widgets/settings/CMakeLists.txt b/ui/widgets/settings/CMakeLists.txt
index 9f0fa76..e100bfe 100644
--- a/ui/widgets/settings/CMakeLists.txt
+++ b/ui/widgets/settings/CMakeLists.txt
@@ -4,4 +4,10 @@ target_sources(squawk PRIVATE
     settings.h
     settings.cpp
     settings.ui
+    pagegeneral.h
+    pagegeneral.cpp
+    pagegeneral.ui
+    pageappearance.h
+    pageappearance.cpp
+    pageappearance.ui
 )
diff --git a/ui/widgets/settings/pageappearance.cpp b/ui/widgets/settings/pageappearance.cpp
new file mode 100644
index 0000000..725f452
--- /dev/null
+++ b/ui/widgets/settings/pageappearance.cpp
@@ -0,0 +1,13 @@
+#include "pageappearance.h"
+#include "ui_pageappearance.h"
+
+PageAppearance::PageAppearance(QWidget* parent):
+    QWidget(parent),
+    m_ui(new Ui::PageAppearance())
+{
+    m_ui->setupUi(this);
+}
+
+PageAppearance::~PageAppearance()
+{
+}
diff --git a/ui/widgets/settings/pageappearance.h b/ui/widgets/settings/pageappearance.h
new file mode 100644
index 0000000..85d45a1
--- /dev/null
+++ b/ui/widgets/settings/pageappearance.h
@@ -0,0 +1,26 @@
+#ifndef PAGEAPPEARANCE_H
+#define PAGEAPPEARANCE_H
+
+#include <QWidget>
+#include <QScopedPointer>
+
+namespace Ui
+{
+class PageAppearance;
+}
+
+/**
+ * @todo write docs
+ */
+class PageAppearance : public QWidget
+{
+    Q_OBJECT
+public:
+    PageAppearance(QWidget* parent = nullptr);
+    ~PageAppearance();
+
+private:
+    QScopedPointer<Ui::PageAppearance> m_ui;
+};
+
+#endif // PAGEAPPEARANCE_H
diff --git a/ui/widgets/settings/pageappearance.ui b/ui/widgets/settings/pageappearance.ui
new file mode 100644
index 0000000..1199347
--- /dev/null
+++ b/ui/widgets/settings/pageappearance.ui
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>PageAppearance</class>
+ <widget class="QWidget" name="PageAppearance">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>400</width>
+    <height>300</height>
+   </rect>
+  </property>
+  <layout class="QFormLayout" name="formLayout">
+   <item row="0" column="0">
+    <widget class="QLabel" name="themeLabel">
+     <property name="text">
+      <string>Theme</string>
+     </property>
+    </widget>
+   </item>
+   <item row="0" column="1">
+    <widget class="QComboBox" name="themeInput"/>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/ui/widgets/settings/pagegeneral.cpp b/ui/widgets/settings/pagegeneral.cpp
new file mode 100644
index 0000000..e448f80
--- /dev/null
+++ b/ui/widgets/settings/pagegeneral.cpp
@@ -0,0 +1,13 @@
+#include "pagegeneral.h"
+#include "ui_pagegeneral.h"
+
+PageGeneral::PageGeneral(QWidget* parent):
+    QWidget(parent),
+    m_ui(new Ui::PageGeneral())
+{
+    m_ui->setupUi(this);
+}
+
+PageGeneral::~PageGeneral()
+{
+}
diff --git a/ui/widgets/settings/pagegeneral.h b/ui/widgets/settings/pagegeneral.h
new file mode 100644
index 0000000..77c0c3a
--- /dev/null
+++ b/ui/widgets/settings/pagegeneral.h
@@ -0,0 +1,26 @@
+#ifndef PAGEGENERAL_H
+#define PAGEGENERAL_H
+
+#include <QWidget>
+#include <QScopedPointer>
+
+namespace Ui
+{
+class PageGeneral;
+}
+
+/**
+ * @todo write docs
+ */
+class PageGeneral : public QWidget
+{
+    Q_OBJECT
+public:
+    PageGeneral(QWidget* parent = nullptr);
+    ~PageGeneral();
+
+private:
+    QScopedPointer<Ui::PageGeneral> m_ui;
+};
+
+#endif // PAGEGENERAL_H
diff --git a/ui/widgets/settings/pagegeneral.ui b/ui/widgets/settings/pagegeneral.ui
new file mode 100644
index 0000000..9921715
--- /dev/null
+++ b/ui/widgets/settings/pagegeneral.ui
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>PageGeneral</class>
+ <widget class="QWidget" name="PageGeneral">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>400</width>
+    <height>300</height>
+   </rect>
+  </property>
+  <layout class="QFormLayout" name="formLayout">
+   <item row="0" column="0">
+    <widget class="QLabel" name="label">
+     <property name="text">
+      <string>Downloads path</string>
+     </property>
+    </widget>
+   </item>
+   <item row="0" column="1">
+    <widget class="QLineEdit" name="downloadsPathInput"/>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/ui/widgets/settings/settings.cpp b/ui/widgets/settings/settings.cpp
index 0397d0c..cdcf0cc 100644
--- a/ui/widgets/settings/settings.cpp
+++ b/ui/widgets/settings/settings.cpp
@@ -6,9 +6,19 @@ Settings::Settings(QWidget* parent):
     m_ui(new Ui::Settings())
 {
     m_ui->setupUi(this);
+
+    connect(m_ui->list, &QListWidget::currentItemChanged, this, &Settings::onCurrentPageChanged);
 }
 
 Settings::~Settings()
 {
 }
 
+void Settings::onCurrentPageChanged(QListWidgetItem* current)
+{
+    if (current != nullptr) {
+        m_ui->header->setText(current->text());
+
+        m_ui->content->setCurrentIndex(m_ui->list->currentRow());
+    }
+}
diff --git a/ui/widgets/settings/settings.h b/ui/widgets/settings/settings.h
index 61129d8..f961e08 100644
--- a/ui/widgets/settings/settings.h
+++ b/ui/widgets/settings/settings.h
@@ -2,6 +2,7 @@
 #define SETTINGS_H
 
 #include <QWidget>
+#include <QListWidgetItem>
 #include <QScopedPointer>
 
 namespace Ui
@@ -19,6 +20,9 @@ public:
     Settings(QWidget* parent = nullptr);
     ~Settings();
 
+protected slots:
+    void onCurrentPageChanged(QListWidgetItem* current);
+
 private:
     QScopedPointer<Ui::Settings> m_ui;
 };
diff --git a/ui/widgets/settings/settings.ui b/ui/widgets/settings/settings.ui
index ca9946e..fe092dc 100644
--- a/ui/widgets/settings/settings.ui
+++ b/ui/widgets/settings/settings.ui
@@ -6,7 +6,7 @@
    <rect>
     <x>0</x>
     <y>0</y>
-    <width>465</width>
+    <width>502</width>
     <height>363</height>
    </rect>
   </property>
@@ -21,33 +21,13 @@
     <number>0</number>
    </property>
    <property name="bottomMargin">
-    <number>7</number>
+    <number>0</number>
    </property>
    <property name="spacing">
     <number>0</number>
    </property>
-   <item row="2" column="3">
-    <widget class="QPushButton" name="pushButton">
-     <property name="text">
-      <string>Apply</string>
-     </property>
-     <property name="icon">
-      <iconset theme="dialog-ok-apply"/>
-     </property>
-    </widget>
-   </item>
-   <item row="2" column="2">
-    <widget class="QPushButton" name="pushButton_2">
-     <property name="text">
-      <string>Ok</string>
-     </property>
-     <property name="icon">
-      <iconset theme="dialog-ok"/>
-     </property>
-    </widget>
-   </item>
-   <item row="0" column="0" rowspan="3">
-    <widget class="SettingsList" name="listWidget">
+   <item row="0" column="0" rowspan="2">
+    <widget class="SettingsList" name="list">
      <property name="sizePolicy">
       <sizepolicy hsizetype="Fixed" vsizetype="Expanding">
        <horstretch>0</horstretch>
@@ -66,12 +46,12 @@
        <height>16777215</height>
       </size>
      </property>
+     <property name="styleSheet">
+      <string notr="true"/>
+     </property>
      <property name="frameShape">
       <enum>QFrame::NoFrame</enum>
      </property>
-     <property name="lineWidth">
-      <number>0</number>
-     </property>
      <property name="horizontalScrollBarPolicy">
       <enum>Qt::ScrollBarAlwaysOff</enum>
      </property>
@@ -103,7 +83,7 @@
       <enum>QListView::Adjust</enum>
      </property>
      <property name="layoutMode">
-      <enum>QListView::Batched</enum>
+      <enum>QListView::SinglePass</enum>
      </property>
      <property name="viewMode">
       <enum>QListView::IconMode</enum>
@@ -143,24 +123,71 @@
      </item>
     </widget>
    </item>
-   <item row="1" column="1" colspan="3">
-    <widget class="QWidget" name="widget" native="true"/>
-   </item>
-   <item row="0" column="1">
-    <widget class="QLabel" name="label">
-     <property name="text">
-      <string>General</string>
-     </property>
-    </widget>
-   </item>
-   <item row="2" column="4">
-    <widget class="QPushButton" name="pushButton_3">
-     <property name="text">
-      <string>Cancel</string>
-     </property>
-     <property name="icon">
-      <iconset theme="dialog-cancel"/>
+   <item row="0" column="1" rowspan="2" colspan="2">
+    <widget class="QWidget" name="groupper" native="true">
+     <property name="sizePolicy">
+      <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+       <horstretch>0</horstretch>
+       <verstretch>0</verstretch>
+      </sizepolicy>
      </property>
+     <layout class="QGridLayout" name="gridLayout_2">
+      <item row="2" column="2">
+       <widget class="QPushButton" name="applyButton">
+        <property name="text">
+         <string>Apply</string>
+        </property>
+        <property name="icon">
+         <iconset theme="dialog-ok-apply">
+          <normaloff>.</normaloff>.</iconset>
+        </property>
+       </widget>
+      </item>
+      <item row="2" column="3">
+       <widget class="QPushButton" name="cancelButton">
+        <property name="text">
+         <string>Cancel</string>
+        </property>
+        <property name="icon">
+         <iconset theme="dialog-cancel">
+          <normaloff>.</normaloff>.</iconset>
+        </property>
+       </widget>
+      </item>
+      <item row="2" column="1">
+       <widget class="QPushButton" name="okButton">
+        <property name="text">
+         <string>Ok</string>
+        </property>
+        <property name="icon">
+         <iconset theme="dialog-ok">
+          <normaloff>.</normaloff>.</iconset>
+        </property>
+       </widget>
+      </item>
+      <item row="0" column="0" colspan="4">
+       <widget class="QLabel" name="header">
+        <property name="styleSheet">
+         <string notr="true">font-size:  14pt;</string>
+        </property>
+        <property name="text">
+         <string>General</string>
+        </property>
+       </widget>
+      </item>
+      <item row="1" column="0" colspan="4">
+       <widget class="QStackedWidget" name="content">
+        <property name="sizePolicy">
+         <sizepolicy hsizetype="Preferred" vsizetype="MinimumExpanding">
+          <horstretch>0</horstretch>
+          <verstretch>0</verstretch>
+         </sizepolicy>
+        </property>
+        <widget class="PageGeneral" name="General" native="true"/>
+        <widget class="PageAppearance" name="Appearance" native="true"/>
+       </widget>
+      </item>
+     </layout>
     </widget>
    </item>
   </layout>
@@ -171,6 +198,16 @@
    <extends>QListWidget</extends>
    <header location="global">ui/widgets/settings/settingslist.h</header>
   </customwidget>
+  <customwidget>
+   <class>PageGeneral</class>
+   <extends>QWidget</extends>
+   <header location="global">ui/widgets/settings/pagegeneral.h</header>
+  </customwidget>
+  <customwidget>
+   <class>PageAppearance</class>
+   <extends>QWidget</extends>
+   <header location="global">ui/widgets/settings/pageappearance.h</header>
+  </customwidget>
  </customwidgets>
  <resources/>
  <connections/>
diff --git a/ui/widgets/settings/settingslist.cpp b/ui/widgets/settings/settingslist.cpp
index 1925632..3a5e2cb 100644
--- a/ui/widgets/settings/settingslist.cpp
+++ b/ui/widgets/settings/settingslist.cpp
@@ -4,6 +4,7 @@ SettingsList::SettingsList(QWidget* parent):
     QListWidget(parent),
     lastWidth(0)
 {
+
 }
 
 SettingsList::~SettingsList()
@@ -25,3 +26,13 @@ void SettingsList::resizeEvent(QResizeEvent* event)
     lastWidth = event->size().width();
     QListWidget::resizeEvent(event);
 }
+
+QRect SettingsList::visualRect(const QModelIndex& index) const
+{
+    QRect res = QListWidget::visualRect(index);
+    if (index.isValid()) {
+        res.setWidth(lastWidth);
+    }
+    return res;
+}
+
diff --git a/ui/widgets/settings/settingslist.h b/ui/widgets/settings/settingslist.h
index 9621c67..a51fc3a 100644
--- a/ui/widgets/settings/settingslist.h
+++ b/ui/widgets/settings/settingslist.h
@@ -17,6 +17,7 @@ public:
 protected:
     QStyleOptionViewItem viewOptions() const override;
     void resizeEvent(QResizeEvent * event) override;
+    QRect visualRect(const QModelIndex & index) const override;
 
 private:
     int lastWidth;

From c708c33a92153e820cd37f266bf8995682148404 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Fri, 21 Jan 2022 22:02:50 +0300
Subject: [PATCH 164/281] basic theme changing

---
 core/main.cpp                          | 11 ++++++-
 shared/global.cpp                      |  1 +
 shared/global.h                        |  5 +++-
 ui/widgets/settings/pageappearance.cpp | 31 +++++++++++++++++++-
 ui/widgets/settings/pageappearance.h   | 11 +++++++
 ui/widgets/settings/pagegeneral.h      |  4 +++
 ui/widgets/settings/settings.cpp       | 40 +++++++++++++++++++++++++-
 ui/widgets/settings/settings.h         |  9 ++++++
 8 files changed, 108 insertions(+), 4 deletions(-)

diff --git a/core/main.cpp b/core/main.cpp
index 7c94a12..03827d3 100644
--- a/core/main.cpp
+++ b/core/main.cpp
@@ -55,7 +55,7 @@ int main(int argc, char *argv[])
     QApplication::setApplicationName("squawk");
     QApplication::setApplicationDisplayName("Squawk");
     QApplication::setApplicationVersion("0.2.1");
-    
+
     QTranslator qtTranslator;
     qtTranslator.load("qt_" + QLocale::system().name(), QLibraryInfo::location(QLibraryInfo::TranslationsPath));
     app.installTranslator(&qtTranslator);
@@ -88,6 +88,15 @@ int main(int argc, char *argv[])
     QApplication::setWindowIcon(icon);
     
     new Shared::Global();        //translates enums
+
+    QSettings settings;
+    QVariant vtheme = settings.value("theme");
+    if (vtheme.isValid()) {
+        QString theme = vtheme.toString().toLower();
+        if (theme != "system") {
+            QApplication::setStyle(theme);
+        }
+    }
     
     Squawk w;
     w.show();
diff --git a/shared/global.cpp b/shared/global.cpp
index 5f3b4ed..7ee9599 100644
--- a/shared/global.cpp
+++ b/shared/global.cpp
@@ -84,6 +84,7 @@ Shared::Global::Global():
         tr("Squawk is going to query you for the password on every start of the program", "AccountPasswordDescription"),
         tr("Your password is going to be stored in KDE wallet storage (KWallet). You're going to be queried for permissions", "AccountPasswordDescription")
     }),
+    defaultSystemStyle(QApplication::style()->objectName()),
     pluginSupport({
         {"KWallet", false},
         {"openFileManagerWindowJob", false}
diff --git a/shared/global.h b/shared/global.h
index b1ae59c..a87b9bc 100644
--- a/shared/global.h
+++ b/shared/global.h
@@ -27,7 +27,8 @@
 #include <set>
 #include <deque>
 
-#include <QCoreApplication>
+#include <QApplication>
+#include <QStyle>
 #include <QDebug>
 #include <QMimeType>
 #include <QMimeDatabase>
@@ -84,6 +85,8 @@ namespace Shared {
         const std::deque<QString> accountPassword;
         
         const std::deque<QString> accountPasswordDescription;
+
+        const QString defaultSystemStyle;
         
         static bool supported(const QString& pluginName);
         static void setSupported(const QString& pluginName, bool support);
diff --git a/ui/widgets/settings/pageappearance.cpp b/ui/widgets/settings/pageappearance.cpp
index 725f452..2fb8dc8 100644
--- a/ui/widgets/settings/pageappearance.cpp
+++ b/ui/widgets/settings/pageappearance.cpp
@@ -1,13 +1,42 @@
 #include "pageappearance.h"
 #include "ui_pageappearance.h"
 
+#include <QDebug>
+
 PageAppearance::PageAppearance(QWidget* parent):
     QWidget(parent),
-    m_ui(new Ui::PageAppearance())
+    m_ui(new Ui::PageAppearance()),
+    styles()
 {
     m_ui->setupUi(this);
+
+    m_ui->themeInput->addItem(tr("System"));
+    styles.push_back("system");
+    QStringList themes = QStyleFactory::keys();
+    for (const QString& key : themes) {
+        m_ui->themeInput->addItem(key);
+        styles.push_back(key);
+    }
+
+    QSettings settings;
+    QVariant vtheme = settings.value("theme");
+    if (vtheme.isValid()) {
+        QString theme = vtheme.toString();
+        m_ui->themeInput->setCurrentText(theme);
+    } else {
+        m_ui->themeInput->setCurrentText("System");
+    }
+
+    connect(m_ui->themeInput, qOverload<int>(&QComboBox::currentIndexChanged), this, &PageAppearance::onThemeChanged);
 }
 
 PageAppearance::~PageAppearance()
 {
 }
+
+void PageAppearance::onThemeChanged(int index)
+{
+    if (index >= 0) {
+        emit variableModified("theme", styles[index]);
+    }
+}
diff --git a/ui/widgets/settings/pageappearance.h b/ui/widgets/settings/pageappearance.h
index 85d45a1..9cb1830 100644
--- a/ui/widgets/settings/pageappearance.h
+++ b/ui/widgets/settings/pageappearance.h
@@ -3,6 +3,10 @@
 
 #include <QWidget>
 #include <QScopedPointer>
+#include <QStyleFactory>
+#include <QVariant>
+#include <QSettings>
+#include <vector>
 
 namespace Ui
 {
@@ -19,8 +23,15 @@ public:
     PageAppearance(QWidget* parent = nullptr);
     ~PageAppearance();
 
+signals:
+    void variableModified(const QString& key, const QVariant& value);
+
+protected slots:
+    void onThemeChanged(int index);
+
 private:
     QScopedPointer<Ui::PageAppearance> m_ui;
+    std::vector<QString> styles;
 };
 
 #endif // PAGEAPPEARANCE_H
diff --git a/ui/widgets/settings/pagegeneral.h b/ui/widgets/settings/pagegeneral.h
index 77c0c3a..b8c30c5 100644
--- a/ui/widgets/settings/pagegeneral.h
+++ b/ui/widgets/settings/pagegeneral.h
@@ -3,6 +3,7 @@
 
 #include <QWidget>
 #include <QScopedPointer>
+#include <QVariant>
 
 namespace Ui
 {
@@ -19,6 +20,9 @@ public:
     PageGeneral(QWidget* parent = nullptr);
     ~PageGeneral();
 
+signals:
+    void variableModified(const QString& key, const QVariant& value);
+
 private:
     QScopedPointer<Ui::PageGeneral> m_ui;
 };
diff --git a/ui/widgets/settings/settings.cpp b/ui/widgets/settings/settings.cpp
index cdcf0cc..75a7780 100644
--- a/ui/widgets/settings/settings.cpp
+++ b/ui/widgets/settings/settings.cpp
@@ -3,11 +3,19 @@
 
 Settings::Settings(QWidget* parent):
     QWidget(parent),
-    m_ui(new Ui::Settings())
+    m_ui(new Ui::Settings()),
+    modifiedSettings()
 {
     m_ui->setupUi(this);
 
     connect(m_ui->list, &QListWidget::currentItemChanged, this, &Settings::onCurrentPageChanged);
+
+    connect(m_ui->General, &PageGeneral::variableModified, this, &Settings::onVariableModified);
+    connect(m_ui->Appearance, &PageAppearance::variableModified, this, &Settings::onVariableModified);
+
+    connect(m_ui->applyButton, &QPushButton::clicked, this, &Settings::apply);
+    connect(m_ui->okButton, &QPushButton::clicked, this, &Settings::confirm);
+    connect(m_ui->cancelButton, &QPushButton::clicked, this, &Settings::close);
 }
 
 Settings::~Settings()
@@ -22,3 +30,33 @@ void Settings::onCurrentPageChanged(QListWidgetItem* current)
         m_ui->content->setCurrentIndex(m_ui->list->currentRow());
     }
 }
+
+void Settings::onVariableModified(const QString& key, const QVariant& value)
+{
+    modifiedSettings[key] = value;
+}
+
+void Settings::apply()
+{
+    QSettings settings;
+    for (const std::pair<const QString, QVariant>& pair: modifiedSettings) {
+        if (pair.first == "theme") {
+            QString theme = pair.second.toString();
+            if (theme.toLower() == "system") {
+                QApplication::setStyle(Shared::Global::getInstance()->defaultSystemStyle);
+            } else {
+                QApplication::setStyle(theme);
+            }
+        }
+
+        settings.setValue(pair.first, pair.second);
+    }
+
+    modifiedSettings.clear();
+}
+
+void Settings::confirm()
+{
+    apply();
+    close();
+}
diff --git a/ui/widgets/settings/settings.h b/ui/widgets/settings/settings.h
index f961e08..5031139 100644
--- a/ui/widgets/settings/settings.h
+++ b/ui/widgets/settings/settings.h
@@ -4,6 +4,9 @@
 #include <QWidget>
 #include <QListWidgetItem>
 #include <QScopedPointer>
+#include <QSettings>
+
+#include "shared/global.h"
 
 namespace Ui
 {
@@ -20,11 +23,17 @@ public:
     Settings(QWidget* parent = nullptr);
     ~Settings();
 
+public slots:
+    void apply();
+    void confirm();
+
 protected slots:
     void onCurrentPageChanged(QListWidgetItem* current);
+    void onVariableModified(const QString& key, const QVariant& value);
 
 private:
     QScopedPointer<Ui::Settings> m_ui;
+    std::map<QString, QVariant> modifiedSettings;
 };
 
 #endif // SETTINGS_H

From 802e2f11a1a990dc720490ce098c864ee21edf61 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Tue, 25 Jan 2022 23:35:55 +0300
Subject: [PATCH 165/281] may be a bit better quit handling

---
 core/main.cpp                          | 21 ++++++++++++---------
 core/signalcatcher.cpp                 |  2 +-
 core/signalcatcher.h                   |  3 +++
 ui/squawk.cpp                          |  4 ++++
 ui/squawk.h                            |  3 +--
 ui/widgets/settings/pageappearance.cpp | 15 +++++++++++++++
 ui/widgets/settings/pageappearance.ui  | 10 ++++++++++
 7 files changed, 46 insertions(+), 12 deletions(-)

diff --git a/core/main.cpp b/core/main.cpp
index 03827d3..c7bb820 100644
--- a/core/main.cpp
+++ b/core/main.cpp
@@ -105,11 +105,15 @@ int main(int argc, char *argv[])
     QThread* coreThread = new QThread();
     squawk->moveToThread(coreThread);
     
+    QObject::connect(&sc, &SignalCatcher::interrupt, &app, &QApplication::closeAllWindows);
+
     QObject::connect(coreThread, &QThread::started, squawk, &Core::Squawk::start);
-    QObject::connect(&app, &QApplication::aboutToQuit, squawk, &Core::Squawk::stop);
-    QObject::connect(&app, &QApplication::aboutToQuit, &w, &QMainWindow::close);
-    QObject::connect(squawk, &Core::Squawk::quit, coreThread, &QThread::quit);
-    QObject::connect(coreThread, &QThread::finished, squawk, &Core::Squawk::deleteLater);
+    QObject::connect(&app, &QApplication::lastWindowClosed, squawk, &Core::Squawk::stop);
+    QObject::connect(&app, &QApplication::lastWindowClosed, &w, &Squawk::writeSettings);
+    //QObject::connect(&app, &QApplication::aboutToQuit, &w, &QMainWindow::close);
+    QObject::connect(squawk, &Core::Squawk::quit, squawk, &Core::Squawk::deleteLater);
+    QObject::connect(squawk, &Core::Squawk::quit, coreThread, &QThread::quit, Qt::QueuedConnection);
+    QObject::connect(coreThread, &QThread::finished, &app, &QApplication::quit, Qt::QueuedConnection);
     
     QObject::connect(&w, &Squawk::newAccountRequest, squawk, &Core::Squawk::newAccountRequest);
     QObject::connect(&w, &Squawk::modifyAccountRequest, squawk, &Core::Squawk::modifyAccountRequest);
@@ -169,12 +173,11 @@ int main(int argc, char *argv[])
     QObject::connect(squawk, &Core::Squawk::ready, &w, &Squawk::readSettings);
     
     coreThread->start();
-
     int result = app.exec();
-    
-    w.writeSettings();
-    coreThread->wait(500);      //TODO hate doing that but settings for some reason don't get saved to the disk
-    
+
+    if (coreThread->isRunning()) {
+        coreThread->wait();
+    }
     
     return result;
 }
diff --git a/core/signalcatcher.cpp b/core/signalcatcher.cpp
index dcdcb88..046c67e 100644
--- a/core/signalcatcher.cpp
+++ b/core/signalcatcher.cpp
@@ -50,7 +50,7 @@ void SignalCatcher::handleSigInt()
     char tmp;
     ssize_t s = ::read(sigintFd[1], &tmp, sizeof(tmp));
 
-    app->quit();
+    emit interrupt();
 
     snInt->setEnabled(true);
 }
diff --git a/core/signalcatcher.h b/core/signalcatcher.h
index 7d75260..0c8e757 100644
--- a/core/signalcatcher.h
+++ b/core/signalcatcher.h
@@ -33,6 +33,9 @@ public:
     
     static void intSignalHandler(int unused);
     
+signals:
+    void interrupt();
+
 public slots:
     void handleSigInt();
     
diff --git a/ui/squawk.cpp b/ui/squawk.cpp
index 4d22b34..7acda3d 100644
--- a/ui/squawk.cpp
+++ b/ui/squawk.cpp
@@ -883,6 +883,10 @@ void Squawk::writeSettings()
     }
     settings.endGroup();
     settings.endGroup();
+
+    settings.sync();
+
+    qDebug() << "Saved settings";
 }
 
 void Squawk::onItemCollepsed(const QModelIndex& index)
diff --git a/ui/squawk.h b/ui/squawk.h
index 26dc0c9..b419057 100644
--- a/ui/squawk.h
+++ b/ui/squawk.h
@@ -54,8 +54,6 @@ public:
     explicit Squawk(QWidget *parent = nullptr);
     ~Squawk() override;
     
-    void writeSettings();
-    
 signals:
     void newAccountRequest(const QMap<QString, QVariant>&);
     void modifyAccountRequest(const QString&, const QMap<QString, QVariant>&);
@@ -84,6 +82,7 @@ signals:
     void localPathInvalid(const QString& path);
     
 public slots:
+    void writeSettings();
     void readSettings();
     void newAccount(const QMap<QString, QVariant>& account);
     void changeAccount(const QString& account, const QMap<QString, QVariant>& data);
diff --git a/ui/widgets/settings/pageappearance.cpp b/ui/widgets/settings/pageappearance.cpp
index 2fb8dc8..1c558f2 100644
--- a/ui/widgets/settings/pageappearance.cpp
+++ b/ui/widgets/settings/pageappearance.cpp
@@ -2,6 +2,10 @@
 #include "ui_pageappearance.h"
 
 #include <QDebug>
+#include <QStandardPaths>
+#include <QDir>
+
+static const QStringList filters = {"*.colors"};
 
 PageAppearance::PageAppearance(QWidget* parent):
     QWidget(parent),
@@ -28,6 +32,17 @@ PageAppearance::PageAppearance(QWidget* parent):
     }
 
     connect(m_ui->themeInput, qOverload<int>(&QComboBox::currentIndexChanged), this, &PageAppearance::onThemeChanged);
+
+    m_ui->colorInput->addItem(tr("System"));
+    const QStringList dirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, "color-schemes", QStandardPaths::LocateDirectory);
+    QStringList schemeFiles;
+    for (const QString &dir : dirs) {
+        const QStringList fileNames = QDir(dir).entryList(filters);
+        for (const QString &file : fileNames) {
+            m_ui->colorInput->addItem(dir + QDir::separator() + file);
+        }
+    }
+
 }
 
 PageAppearance::~PageAppearance()
diff --git a/ui/widgets/settings/pageappearance.ui b/ui/widgets/settings/pageappearance.ui
index 1199347..afcb3a8 100644
--- a/ui/widgets/settings/pageappearance.ui
+++ b/ui/widgets/settings/pageappearance.ui
@@ -21,6 +21,16 @@
    <item row="0" column="1">
     <widget class="QComboBox" name="themeInput"/>
    </item>
+   <item row="1" column="1">
+    <widget class="QComboBox" name="colorInput"/>
+   </item>
+   <item row="1" column="0">
+    <widget class="QLabel" name="colorLabel">
+     <property name="text">
+      <string>Color scheme</string>
+     </property>
+    </widget>
+   </item>
   </layout>
  </widget>
  <resources/>

From 0ff9f12157a8223de3d324ed1e4e82c5166df624 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Wed, 26 Jan 2022 23:53:44 +0300
Subject: [PATCH 166/281] new optional KDE Frameworks plugin to support system
 color schemes

---
 CMakeLists.txt                         | 19 +++++++++++++
 core/main.cpp                          |  5 +++-
 plugins/CMakeLists.txt                 |  6 ++++
 plugins/colorschemetools.cpp           | 39 ++++++++++++++++++++++++++
 shared/global.cpp                      | 37 +++++++++++++++++++++++-
 shared/global.h                        | 11 ++++++++
 ui/widgets/settings/pageappearance.cpp | 21 ++++++++------
 ui/widgets/settings/pageappearance.h   |  2 ++
 8 files changed, 130 insertions(+), 10 deletions(-)
 create mode 100644 plugins/colorschemetools.cpp

diff --git a/CMakeLists.txt b/CMakeLists.txt
index cd9d793..717cf91 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -29,6 +29,7 @@ target_include_directories(squawk PRIVATE ${CMAKE_SOURCE_DIR})
 option(SYSTEM_QXMPP "Use system qxmpp lib" ON)
 option(WITH_KWALLET "Build KWallet support module" ON)
 option(WITH_KIO "Build KIO support module" ON)
+option(WITH_KCONFIG "Build KConfig support module" ON)
 
 # Dependencies
 ## Qt
@@ -90,6 +91,24 @@ if (WITH_KWALLET)
   endif ()
 endif ()
 
+if (WITH_KCONFIG)
+  find_package(KF5Config CONFIG)
+  if (NOT KF5Config_FOUND)
+    set(WITH_KCONFIG OFF)
+    message("KConfig package wasn't found, KConfig support modules wouldn't be built")
+  else()
+    find_package(KF5ConfigWidgets CONFIG)
+    if (NOT KF5ConfigWidgets_FOUND)
+      set(WITH_KCONFIG OFF)
+      message("KConfigWidgets package wasn't found, KConfigWidgets support modules wouldn't be built")
+    else()
+      target_compile_definitions(squawk PRIVATE WITH_KCONFIG)
+      message("Building with support of KConfig")
+      message("Building with support of KConfigWidgets")
+    endif()
+  endif()
+endif()
+
 ## Signal (TODO)
 # find_package(Signal REQUIRED)
 
diff --git a/core/main.cpp b/core/main.cpp
index c7bb820..03de307 100644
--- a/core/main.cpp
+++ b/core/main.cpp
@@ -176,7 +176,10 @@ int main(int argc, char *argv[])
     int result = app.exec();
 
     if (coreThread->isRunning()) {
-        coreThread->wait();
+        //coreThread->wait();
+        //todo if I uncomment that, the app will no quit if it has reconnected at least once
+        //it feels like a symptom of something badly desinged in the core coreThread
+        //need to investigate;
     }
     
     return result;
diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt
index 84fc09b..5a5a41d 100644
--- a/plugins/CMakeLists.txt
+++ b/plugins/CMakeLists.txt
@@ -2,3 +2,9 @@ if (WITH_KIO)
   add_library(openFileManagerWindowJob SHARED openfilemanagerwindowjob.cpp)
   target_link_libraries(openFileManagerWindowJob PRIVATE KF5::KIOWidgets)
 endif ()
+
+if (WITH_KCONFIG)
+  add_library(colorSchemeTools SHARED colorschemetools.cpp)
+  target_link_libraries(colorSchemeTools PRIVATE KF5::ConfigCore)
+  target_link_libraries(colorSchemeTools PRIVATE KF5::ConfigWidgets)
+endif()
diff --git a/plugins/colorschemetools.cpp b/plugins/colorschemetools.cpp
new file mode 100644
index 0000000..0ad8e24
--- /dev/null
+++ b/plugins/colorschemetools.cpp
@@ -0,0 +1,39 @@
+#include <QIcon>
+#include <QPainter>
+
+#include <KConfigCore/KSharedConfig>
+#include <KConfigWidgets/KColorScheme>
+
+QPixmap createPixmap(int size, const QBrush& window, const QBrush& button, const QBrush& view, const QBrush& selection);
+
+extern "C" QIcon* createPreview(const QString& path) {
+    KSharedConfigPtr schemeConfig = KSharedConfig::openConfig(path);
+    QIcon* result = new QIcon();
+    KColorScheme activeWindow(QPalette::Active, KColorScheme::Window, schemeConfig);
+    KColorScheme activeButton(QPalette::Active, KColorScheme::Button, schemeConfig);
+    KColorScheme activeView(QPalette::Active, KColorScheme::View, schemeConfig);
+    KColorScheme activeSelection(QPalette::Active, KColorScheme::Selection, schemeConfig);
+
+    result->addPixmap(createPixmap(16, activeWindow.background(), activeButton.background(), activeView.background(), activeSelection.background()));
+    result->addPixmap(createPixmap(24, activeWindow.background(), activeButton.background(), activeView.background(), activeSelection.background()));
+
+    return result;
+}
+
+extern "C" void deletePreview(QIcon* icon) {
+    delete icon;
+}
+
+QPixmap createPixmap(int size, const QBrush& window, const QBrush& button, const QBrush& view, const QBrush& selection) {
+    QPixmap pix(size, size);
+    pix.fill(Qt::black);
+    QPainter p;
+    p.begin(&pix);
+    const int itemSize = size / 2 - 1;
+    p.fillRect(1, 1, itemSize, itemSize, window);
+    p.fillRect(1 + itemSize, 1, itemSize, itemSize, button);
+    p.fillRect(1, 1 + itemSize, itemSize, itemSize, view);
+    p.fillRect(1 + itemSize, 1 + itemSize, itemSize, itemSize, selection);
+    p.end();
+    return pix;
+}
diff --git a/shared/global.cpp b/shared/global.cpp
index 7ee9599..7dafed6 100644
--- a/shared/global.cpp
+++ b/shared/global.cpp
@@ -28,6 +28,12 @@ QLibrary Shared::Global::openFileManagerWindowJob("openFileManagerWindowJob");
 Shared::Global::HighlightInFileManager Shared::Global::hfm = 0;
 #endif
 
+#ifdef WITH_KCONFIG
+QLibrary Shared::Global::colorSchemeTools("colorSchemeTools");
+Shared::Global::CreatePreview Shared::Global::createPreview = 0;
+Shared::Global::DeletePreview Shared::Global::deletePreview = 0;
+#endif
+
 Shared::Global::Global():
     availability({
         tr("Online", "Availability"), 
@@ -87,7 +93,8 @@ Shared::Global::Global():
     defaultSystemStyle(QApplication::style()->objectName()),
     pluginSupport({
         {"KWallet", false},
-        {"openFileManagerWindowJob", false}
+        {"openFileManagerWindowJob", false},
+        {"colorSchemeTools", false}
     }),
     fileCache()
 {
@@ -111,6 +118,22 @@ Shared::Global::Global():
         qDebug() << "KIO::OpenFileManagerWindow support disabled: couldn't load the library" << openFileManagerWindowJob.errorString();
     }
 #endif
+
+#ifdef WITH_KCONFIG
+    colorSchemeTools.load();
+    if (colorSchemeTools.isLoaded()) {
+        createPreview = (CreatePreview) colorSchemeTools.resolve("createPreview");
+        deletePreview = (DeletePreview) colorSchemeTools.resolve("deletePreview");
+        if (createPreview && deletePreview) {
+            setSupported("colorSchemeTools", true);
+            qDebug() << "Color Schemes support enabled";
+        } else {
+            qDebug() << "Color Schemes support disabled: couldn't resolve required methods in the library";
+        }
+    } else {
+        qDebug() << "Color Schemes support disabled: couldn't load the library" << colorSchemeTools.errorString();
+    }
+#endif
 }
 
 
@@ -276,6 +299,18 @@ void Shared::Global::highlightInFileManager(const QString& path)
     }
 }
 
+QIcon Shared::Global::createThemePreview(const QString& path)
+{
+    if (supported("colorSchemeTools")) {
+        QIcon* icon = createPreview(path);
+        QIcon localIcon = *icon;
+        deletePreview(icon);
+        return localIcon;
+    } else {
+        return QIcon();
+    }
+}
+
 
 #define FROM_INT_INPL(Enum)                                                                 \
 template<>                                                                                  \
diff --git a/shared/global.h b/shared/global.h
index a87b9bc..bd3c9a6 100644
--- a/shared/global.h
+++ b/shared/global.h
@@ -95,6 +95,7 @@ namespace Shared {
         
         static FileInfo getFileInfo(const QString& path);
         static void highlightInFileManager(const QString& path);
+        static QIcon createThemePreview(const QString& path);
         
         template<typename T>
         static T fromInt(int src);
@@ -128,6 +129,16 @@ namespace Shared {
         
         static HighlightInFileManager hfm;
 #endif
+
+#ifdef WITH_KCONFIG
+        static QLibrary colorSchemeTools;
+
+        typedef QIcon* (*CreatePreview)(const QString&);
+        typedef void (*DeletePreview)(QIcon*);
+
+        static CreatePreview createPreview;
+        static DeletePreview deletePreview;
+#endif
     };
 }
 
diff --git a/ui/widgets/settings/pageappearance.cpp b/ui/widgets/settings/pageappearance.cpp
index 1c558f2..57da7aa 100644
--- a/ui/widgets/settings/pageappearance.cpp
+++ b/ui/widgets/settings/pageappearance.cpp
@@ -33,16 +33,19 @@ PageAppearance::PageAppearance(QWidget* parent):
 
     connect(m_ui->themeInput, qOverload<int>(&QComboBox::currentIndexChanged), this, &PageAppearance::onThemeChanged);
 
-    m_ui->colorInput->addItem(tr("System"));
-    const QStringList dirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, "color-schemes", QStandardPaths::LocateDirectory);
-    QStringList schemeFiles;
-    for (const QString &dir : dirs) {
-        const QStringList fileNames = QDir(dir).entryList(filters);
-        for (const QString &file : fileNames) {
-            m_ui->colorInput->addItem(dir + QDir::separator() + file);
+    if (Shared::Global::supported("colorSchemeTools")) {
+        m_ui->colorInput->addItem(tr("System"));
+        const QStringList dirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, "color-schemes", QStandardPaths::LocateDirectory);
+        QStringList schemeFiles;
+        for (const QString &dir : dirs) {
+            const QStringList fileNames = QDir(dir).entryList(filters);
+            for (const QString &file : fileNames) {
+                m_ui->colorInput->addItem(Shared::Global::createThemePreview(dir + QDir::separator() + file), file);
+            }
         }
+    } else {
+         m_ui->colorInput->setEnabled(false);
     }
-
 }
 
 PageAppearance::~PageAppearance()
@@ -55,3 +58,5 @@ void PageAppearance::onThemeChanged(int index)
         emit variableModified("theme", styles[index]);
     }
 }
+
+
diff --git a/ui/widgets/settings/pageappearance.h b/ui/widgets/settings/pageappearance.h
index 9cb1830..b31f3c1 100644
--- a/ui/widgets/settings/pageappearance.h
+++ b/ui/widgets/settings/pageappearance.h
@@ -8,6 +8,8 @@
 #include <QSettings>
 #include <vector>
 
+#include "shared/global.h"
+
 namespace Ui
 {
 class PageAppearance;

From da19eb86bbb9fa6e52189e44524bf8d16f73b84b Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Thu, 27 Jan 2022 20:44:32 +0300
Subject: [PATCH 167/281] color theme setting is now working

---
 CHANGELOG.md                                  |  5 ++
 README.md                                     |  3 ++
 core/main.cpp                                 | 21 ++++++--
 .../wrappers/CMakeLists.txt                   |  2 +
 packaging/Archlinux/PKGBUILD                  |  5 +-
 plugins/CMakeLists.txt                        |  4 ++
 plugins/colorschemetools.cpp                  | 13 +++++
 shared/global.cpp                             | 39 +++++++++++++-
 shared/global.h                               |  8 +++
 ui/widgets/settings/pageappearance.cpp        | 54 +++++++++++++------
 ui/widgets/settings/pageappearance.h          |  2 +
 ui/widgets/settings/pageappearance.ui         |  8 +--
 ui/widgets/settings/settings.cpp              | 11 ++--
 13 files changed, 142 insertions(+), 33 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4052647..7b1d75a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,10 +2,15 @@
 
 ## Squawk 0.2.1 (UNRELEASED)
 ### Bug fixes
+- build in release mode now no longer spams warnings
+- build now correctly installs all build plugin libs
 
 ### Improvements
 
 ### New features
+- the settings are here! You con config different stuff from there
+- now it's possible to set up different qt styles from settings
+- if you have KConfig nad KConfigWidgets packages installed - you can chose from global color schemes
 
 ## Squawk 0.2.0 (Jan 10, 2022)
 ### Bug fixes
diff --git a/README.md b/README.md
index 0af201f..486d4fe 100644
--- a/README.md
+++ b/README.md
@@ -14,6 +14,8 @@
 - qxmpp 1.1.0 or higher
 - KDE Frameworks: kwallet (optional)
 - KDE Frameworks: KIO (optional)
+- KDE Frameworks: KConfig (optional)
+- KDE Frameworks: KConfigWidgets (optional)
 - Boost (just one little hpp from there)
 - Imagemagick (for compilation, to rasterize an SVG logo)
 
@@ -92,6 +94,7 @@ Here is the list of keys you can pass to configuration phase of `cmake ..`.
 - `SYSTEM_QXMPP` - `True` tries to link against `qxmpp` installed in the system, `False` builds bundled `qxmpp` library (default is `True`)
 - `WITH_KWALLET` - `True` builds the `KWallet` capability module if `KWallet` is installed and if not goes to `False`. `False` disables `KWallet` support (default is `True`)
 - `WITH_KIO` - `True` builds the `KIO` capability module if `KIO` is installed and if not goes to `False`. `False` disables `KIO` support (default is `True`)
+- `WITH_KCONFIG` - `True` builds the `KConfig` and `KConfigWidgets` capability module if such packages are installed and if not goes to `False`. `False` disables `KConfig` and `KConfigWidgets` support (default is `True`)
 
 ## License
 
diff --git a/core/main.cpp b/core/main.cpp
index 03de307..cdfca7e 100644
--- a/core/main.cpp
+++ b/core/main.cpp
@@ -90,13 +90,24 @@ int main(int argc, char *argv[])
     new Shared::Global();        //translates enums
 
     QSettings settings;
-    QVariant vtheme = settings.value("theme");
-    if (vtheme.isValid()) {
-        QString theme = vtheme.toString().toLower();
-        if (theme != "system") {
-            QApplication::setStyle(theme);
+    QVariant vs = settings.value("style");
+    if (vs.isValid()) {
+        QString style = vs.toString().toLower();
+        if (style != "system") {
+            Shared::Global::setStyle(style);
         }
     }
+    if (Shared::Global::supported("colorSchemeTools")) {
+        QVariant vt = settings.value("theme");
+        if (vt.isValid()) {
+            QString theme = vt.toString();
+            if (theme.toLower() != "system") {
+                Shared::Global::setTheme(theme);
+            }
+        }
+    }
+
+
     
     Squawk w;
     w.show();
diff --git a/core/passwordStorageEngines/wrappers/CMakeLists.txt b/core/passwordStorageEngines/wrappers/CMakeLists.txt
index 6d486c0..e8420da 100644
--- a/core/passwordStorageEngines/wrappers/CMakeLists.txt
+++ b/core/passwordStorageEngines/wrappers/CMakeLists.txt
@@ -1,2 +1,4 @@
 add_library(kwalletWrapper SHARED kwallet.cpp)
 target_link_libraries(kwalletWrapper PRIVATE KF5::Wallet)
+
+install(TARGETS kwalletWrapper LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})
diff --git a/packaging/Archlinux/PKGBUILD b/packaging/Archlinux/PKGBUILD
index 0d2aaaa..a8da388 100644
--- a/packaging/Archlinux/PKGBUILD
+++ b/packaging/Archlinux/PKGBUILD
@@ -8,7 +8,10 @@ url="https://git.macaw.me/blue/squawk"
 license=('GPL3')
 depends=('hicolor-icon-theme' 'desktop-file-utils' 'lmdb' 'qxmpp>=1.1.0')
 makedepends=('cmake>=3.3' 'imagemagick' 'qt5-tools' 'boost')
-optdepends=('kwallet: secure password storage (requires rebuild)' 'kio: better show in folder action (requires rebuild)')
+optdepends=('kwallet: secure password storage (requires rebuild)'
+            'kconfig: system themes support (requires rebuild)'
+            'kconfigwidgets: system themes support (requires rebuild)'
+            'kio: better show in folder action (requires rebuild)')
 
 source=("$pkgname-$pkgver.tar.gz")
 sha256sums=('8e93d3dbe1fc35cfecb7783af409c6a264244d11609b2241d4fe77d43d068419')
diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt
index 5a5a41d..388c258 100644
--- a/plugins/CMakeLists.txt
+++ b/plugins/CMakeLists.txt
@@ -1,10 +1,14 @@
 if (WITH_KIO)
   add_library(openFileManagerWindowJob SHARED openfilemanagerwindowjob.cpp)
   target_link_libraries(openFileManagerWindowJob PRIVATE KF5::KIOWidgets)
+
+  install(TARGETS openFileManagerWindowJob LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})
 endif ()
 
 if (WITH_KCONFIG)
   add_library(colorSchemeTools SHARED colorschemetools.cpp)
   target_link_libraries(colorSchemeTools PRIVATE KF5::ConfigCore)
   target_link_libraries(colorSchemeTools PRIVATE KF5::ConfigWidgets)
+
+  install(TARGETS colorSchemeTools LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})
 endif()
diff --git a/plugins/colorschemetools.cpp b/plugins/colorschemetools.cpp
index 0ad8e24..0288b28 100644
--- a/plugins/colorschemetools.cpp
+++ b/plugins/colorschemetools.cpp
@@ -1,7 +1,9 @@
 #include <QIcon>
 #include <QPainter>
+#include <QFileInfo>
 
 #include <KConfigCore/KSharedConfig>
+#include <KConfigCore/KConfigGroup>
 #include <KConfigWidgets/KColorScheme>
 
 QPixmap createPixmap(int size, const QBrush& window, const QBrush& button, const QBrush& view, const QBrush& selection);
@@ -24,6 +26,17 @@ extern "C" void deletePreview(QIcon* icon) {
     delete icon;
 }
 
+extern "C" void colorSchemeName(const QString& path, QString& answer) {
+    KSharedConfigPtr config = KSharedConfig::openConfig(path);
+    KConfigGroup group(config, QStringLiteral("General"));
+    answer = group.readEntry("Name", QFileInfo(path).baseName());
+}
+
+extern "C" void createPalette(const QString& path, QPalette& answer) {
+    KSharedConfigPtr config = KSharedConfig::openConfig(path);
+    answer = KColorScheme::createApplicationPalette(config);
+}
+
 QPixmap createPixmap(int size, const QBrush& window, const QBrush& button, const QBrush& view, const QBrush& selection) {
     QPixmap pix(size, size);
     pix.fill(Qt::black);
diff --git a/shared/global.cpp b/shared/global.cpp
index 7dafed6..122bc79 100644
--- a/shared/global.cpp
+++ b/shared/global.cpp
@@ -32,6 +32,8 @@ Shared::Global::HighlightInFileManager Shared::Global::hfm = 0;
 QLibrary Shared::Global::colorSchemeTools("colorSchemeTools");
 Shared::Global::CreatePreview Shared::Global::createPreview = 0;
 Shared::Global::DeletePreview Shared::Global::deletePreview = 0;
+Shared::Global::ColorSchemeName Shared::Global::colorSchemeName = 0;
+Shared::Global::CreatePalette Shared::Global::createPalette = 0;
 #endif
 
 Shared::Global::Global():
@@ -91,6 +93,7 @@ Shared::Global::Global():
         tr("Your password is going to be stored in KDE wallet storage (KWallet). You're going to be queried for permissions", "AccountPasswordDescription")
     }),
     defaultSystemStyle(QApplication::style()->objectName()),
+    defaultSystemPalette(QApplication::palette()),
     pluginSupport({
         {"KWallet", false},
         {"openFileManagerWindowJob", false},
@@ -124,7 +127,9 @@ Shared::Global::Global():
     if (colorSchemeTools.isLoaded()) {
         createPreview = (CreatePreview) colorSchemeTools.resolve("createPreview");
         deletePreview = (DeletePreview) colorSchemeTools.resolve("deletePreview");
-        if (createPreview && deletePreview) {
+        colorSchemeName = (ColorSchemeName) colorSchemeTools.resolve("colorSchemeName");
+        createPalette = (CreatePalette) colorSchemeTools.resolve("createPalette");
+        if (createPreview && deletePreview && colorSchemeName && createPalette) {
             setSupported("colorSchemeTools", true);
             qDebug() << "Color Schemes support enabled";
         } else {
@@ -311,6 +316,38 @@ QIcon Shared::Global::createThemePreview(const QString& path)
     }
 }
 
+QString Shared::Global::getColorSchemeName(const QString& path)
+{
+    if (supported("colorSchemeTools")) {
+        QString res;
+        colorSchemeName(path, res);
+        return res;
+    } else {
+        return "";
+    }
+}
+
+void Shared::Global::setTheme(const QString& path)
+{
+    if (supported("colorSchemeTools")) {
+        if (path.toLower() == "system") {
+            QApplication::setPalette(getInstance()->defaultSystemPalette);
+        } else {
+            QPalette pallete;
+            createPalette(path, pallete);
+            QApplication::setPalette(pallete);
+        }
+    }
+}
+
+void Shared::Global::setStyle(const QString& style)
+{
+    if (style.toLower() == "system") {
+        QApplication::setStyle(getInstance()->defaultSystemStyle);
+    } else {
+        QApplication::setStyle(style);
+    }
+}
 
 #define FROM_INT_INPL(Enum)                                                                 \
 template<>                                                                                  \
diff --git a/shared/global.h b/shared/global.h
index bd3c9a6..2056639 100644
--- a/shared/global.h
+++ b/shared/global.h
@@ -87,6 +87,7 @@ namespace Shared {
         const std::deque<QString> accountPasswordDescription;
 
         const QString defaultSystemStyle;
+        const QPalette defaultSystemPalette;
         
         static bool supported(const QString& pluginName);
         static void setSupported(const QString& pluginName, bool support);
@@ -96,6 +97,9 @@ namespace Shared {
         static FileInfo getFileInfo(const QString& path);
         static void highlightInFileManager(const QString& path);
         static QIcon createThemePreview(const QString& path);
+        static QString getColorSchemeName(const QString& path);
+        static void setTheme(const QString& path);
+        static void setStyle(const QString& style);
         
         template<typename T>
         static T fromInt(int src);
@@ -135,9 +139,13 @@ namespace Shared {
 
         typedef QIcon* (*CreatePreview)(const QString&);
         typedef void (*DeletePreview)(QIcon*);
+        typedef void (*ColorSchemeName)(const QString&, QString&);
+        typedef void (*CreatePalette)(const QString&, QPalette&);
 
         static CreatePreview createPreview;
         static DeletePreview deletePreview;
+        static ColorSchemeName colorSchemeName;
+        static CreatePalette createPalette;
 #endif
     };
 }
diff --git a/ui/widgets/settings/pageappearance.cpp b/ui/widgets/settings/pageappearance.cpp
index 57da7aa..f2bf53e 100644
--- a/ui/widgets/settings/pageappearance.cpp
+++ b/ui/widgets/settings/pageappearance.cpp
@@ -10,41 +10,59 @@ static const QStringList filters = {"*.colors"};
 PageAppearance::PageAppearance(QWidget* parent):
     QWidget(parent),
     m_ui(new Ui::PageAppearance()),
-    styles()
+    styles(),
+    themes()
 {
     m_ui->setupUi(this);
 
-    m_ui->themeInput->addItem(tr("System"));
+    m_ui->styleInput->addItem(tr("System"));
     styles.push_back("system");
-    QStringList themes = QStyleFactory::keys();
-    for (const QString& key : themes) {
-        m_ui->themeInput->addItem(key);
+    QStringList systemStyles = QStyleFactory::keys();
+    for (const QString& key : systemStyles) {
+        m_ui->styleInput->addItem(key);
         styles.push_back(key);
     }
 
     QSettings settings;
-    QVariant vtheme = settings.value("theme");
-    if (vtheme.isValid()) {
-        QString theme = vtheme.toString();
-        m_ui->themeInput->setCurrentText(theme);
+    QVariant vs = settings.value("style");
+    if (vs.isValid()) {
+        m_ui->styleInput->setCurrentText(vs.toString());
     } else {
-        m_ui->themeInput->setCurrentText("System");
+        m_ui->styleInput->setCurrentText(tr("System"));
     }
 
-    connect(m_ui->themeInput, qOverload<int>(&QComboBox::currentIndexChanged), this, &PageAppearance::onThemeChanged);
+    connect(m_ui->styleInput, qOverload<int>(&QComboBox::currentIndexChanged), this, &PageAppearance::onStyleChanged);
 
     if (Shared::Global::supported("colorSchemeTools")) {
-        m_ui->colorInput->addItem(tr("System"));
+        themes.push_back("system");
+        m_ui->themeInput->addItem(tr("System"));
         const QStringList dirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, "color-schemes", QStandardPaths::LocateDirectory);
         QStringList schemeFiles;
+        QString currentTheme(tr("System"));
+        QString requestedTheme("");
+        QVariant vtheme = settings.value("theme");
+        if (vtheme.isValid()) {
+            requestedTheme = vtheme.toString();
+        }
         for (const QString &dir : dirs) {
             const QStringList fileNames = QDir(dir).entryList(filters);
             for (const QString &file : fileNames) {
-                m_ui->colorInput->addItem(Shared::Global::createThemePreview(dir + QDir::separator() + file), file);
+                QString path = dir + QDir::separator() + file;
+                themes.push_back(path);
+                QString themeName = Shared::Global::getColorSchemeName(path);
+                m_ui->themeInput->addItem(Shared::Global::createThemePreview(path), themeName);
+
+                if (path == requestedTheme) {
+                    currentTheme = themeName;
+                }
             }
         }
+
+        m_ui->themeInput->setCurrentText(currentTheme);
+
+        connect(m_ui->themeInput, qOverload<int>(&QComboBox::currentIndexChanged), this, &PageAppearance::onThemeChanged);
     } else {
-         m_ui->colorInput->setEnabled(false);
+         m_ui->themeInput->setEnabled(false);
     }
 }
 
@@ -55,8 +73,14 @@ PageAppearance::~PageAppearance()
 void PageAppearance::onThemeChanged(int index)
 {
     if (index >= 0) {
-        emit variableModified("theme", styles[index]);
+        emit variableModified("theme", themes[index]);
     }
 }
 
+void PageAppearance::onStyleChanged(int index)
+{
+    if (index >= 0) {
+        emit variableModified("style", styles[index]);
+    }
+}
 
diff --git a/ui/widgets/settings/pageappearance.h b/ui/widgets/settings/pageappearance.h
index b31f3c1..80efd85 100644
--- a/ui/widgets/settings/pageappearance.h
+++ b/ui/widgets/settings/pageappearance.h
@@ -29,11 +29,13 @@ signals:
     void variableModified(const QString& key, const QVariant& value);
 
 protected slots:
+    void onStyleChanged(int index);
     void onThemeChanged(int index);
 
 private:
     QScopedPointer<Ui::PageAppearance> m_ui;
     std::vector<QString> styles;
+    std::vector<QString> themes;
 };
 
 #endif // PAGEAPPEARANCE_H
diff --git a/ui/widgets/settings/pageappearance.ui b/ui/widgets/settings/pageappearance.ui
index afcb3a8..570eefa 100644
--- a/ui/widgets/settings/pageappearance.ui
+++ b/ui/widgets/settings/pageappearance.ui
@@ -12,20 +12,20 @@
   </property>
   <layout class="QFormLayout" name="formLayout">
    <item row="0" column="0">
-    <widget class="QLabel" name="themeLabel">
+    <widget class="QLabel" name="styleLabel">
      <property name="text">
       <string>Theme</string>
      </property>
     </widget>
    </item>
    <item row="0" column="1">
-    <widget class="QComboBox" name="themeInput"/>
+    <widget class="QComboBox" name="styleInput"/>
    </item>
    <item row="1" column="1">
-    <widget class="QComboBox" name="colorInput"/>
+    <widget class="QComboBox" name="themeInput"/>
    </item>
    <item row="1" column="0">
-    <widget class="QLabel" name="colorLabel">
+    <widget class="QLabel" name="themeLabel">
      <property name="text">
       <string>Color scheme</string>
      </property>
diff --git a/ui/widgets/settings/settings.cpp b/ui/widgets/settings/settings.cpp
index 75a7780..3ab38bb 100644
--- a/ui/widgets/settings/settings.cpp
+++ b/ui/widgets/settings/settings.cpp
@@ -40,13 +40,10 @@ void Settings::apply()
 {
     QSettings settings;
     for (const std::pair<const QString, QVariant>& pair: modifiedSettings) {
-        if (pair.first == "theme") {
-            QString theme = pair.second.toString();
-            if (theme.toLower() == "system") {
-                QApplication::setStyle(Shared::Global::getInstance()->defaultSystemStyle);
-            } else {
-                QApplication::setStyle(theme);
-            }
+        if (pair.first == "style") {
+            Shared::Global::setStyle(pair.second.toString());
+        } else if (pair.first == "theme") {
+            Shared::Global::setTheme(pair.second.toString());
         }
 
         settings.setValue(pair.first, pair.second);

From 243edff8bdf13fa04a00c35a05a3bb6a374cbfc3 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Thu, 17 Feb 2022 20:26:15 +0300
Subject: [PATCH 168/281] first thoughts about downloads path changing

---
 core/CMakeLists.txt                 |  1 +
 core/main.cpp                       | 11 ++++++-
 core/networkaccess.cpp              | 13 ++++++--
 core/networkaccess.h                |  3 ++
 core/utils/CMakeLists.txt           |  4 +++
 core/utils/pathcheck.cpp            | 47 +++++++++++++++++++++++++++++
 core/utils/pathcheck.h              | 21 +++++++++++++
 shared/utils.h                      |  2 --
 ui/widgets/settings/pagegeneral.cpp |  3 ++
 ui/widgets/settings/pagegeneral.h   |  1 +
 ui/widgets/settings/pagegeneral.ui  | 26 +++++++++++++---
 11 files changed, 122 insertions(+), 10 deletions(-)
 create mode 100644 core/utils/CMakeLists.txt
 create mode 100644 core/utils/pathcheck.cpp
 create mode 100644 core/utils/pathcheck.h

diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt
index 9369cb7..1836349 100644
--- a/core/CMakeLists.txt
+++ b/core/CMakeLists.txt
@@ -32,3 +32,4 @@ target_include_directories(squawk PRIVATE ${LMDB_INCLUDE_DIRS})
 
 add_subdirectory(handlers)
 add_subdirectory(passwordStorageEngines)
+add_subdirectory(utils)
diff --git a/core/main.cpp b/core/main.cpp
index cdfca7e..570bad4 100644
--- a/core/main.cpp
+++ b/core/main.cpp
@@ -21,6 +21,8 @@
 #include "../ui/squawk.h"
 #include "signalcatcher.h"
 #include "squawk.h"
+#include "utils/pathcheck.h"
+
 #include <QLibraryInfo>
 #include <QSettings>
 #include <QStandardPaths>
@@ -28,6 +30,7 @@
 #include <QtCore/QObject>
 #include <QtCore/QThread>
 #include <QtWidgets/QApplication>
+#include <QDir>
 
 int main(int argc, char *argv[])
 {
@@ -106,7 +109,13 @@ int main(int argc, char *argv[])
             }
         }
     }
-
+    QString path = Utils::downloadsPathCheck();
+    if (path.size() > 0) {
+        settings.setValue("downloadsPath", path);
+    } else {
+        qDebug() << "couldn't initialize directory for downloads, quitting";
+        return -1;
+    }
 
     
     Squawk w;
diff --git a/core/networkaccess.cpp b/core/networkaccess.cpp
index c2cd65d..48d26aa 100644
--- a/core/networkaccess.cpp
+++ b/core/networkaccess.cpp
@@ -28,8 +28,11 @@ Core::NetworkAccess::NetworkAccess(QObject* parent):
     manager(0),
     storage("fileURLStorage"),
     downloads(),
-    uploads()
+    uploads(),
+    currentPath()
 {
+    QSettings settings;
+    currentPath = settings.value("downloadsPath").toString();
 }
 
 Core::NetworkAccess::~NetworkAccess()
@@ -515,8 +518,7 @@ bool Core::NetworkAccess::checkAndAddToUploading(const QString& acc, const QStri
 
 QString Core::NetworkAccess::prepareDirectory(const QString& jid)
 {
-    QString path = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation);
-    path += "/" + QApplication::applicationName();
+    QString path = currentPath;
     if (jid.size() > 0) {
         path += "/" + jid;
     }
@@ -563,3 +565,8 @@ std::list<Shared::MessageInfo> Core::NetworkAccess::reportPathInvalid(const QStr
 {
     return storage.deletedFile(path);
 }
+
+void Core::NetworkAccess::moveFilesDirectory(const QString& newPath)
+{
+
+}
diff --git a/core/networkaccess.h b/core/networkaccess.h
index 89d0633..cf24fc4 100644
--- a/core/networkaccess.h
+++ b/core/networkaccess.h
@@ -26,6 +26,7 @@
 #include <QFileInfo>
 #include <QFile>
 #include <QStandardPaths>
+#include <QSettings>
 
 #include <set>
 
@@ -65,6 +66,7 @@ public slots:
     void downladFile(const QString& url);
     void registerFile(const QString& url, const QString& account, const QString& jid, const QString& id);
     void registerFile(const QString& url, const QString& path, const QString& account, const QString& jid, const QString& id);
+    void moveFilesDirectory(const QString& newPath);
     
 private:
     void startDownload(const std::list<Shared::MessageInfo>& msgs, const QString& url);
@@ -87,6 +89,7 @@ private:
     UrlStorage storage;
     std::map<QString, Transfer*> downloads;
     std::map<QString, Transfer*> uploads;
+    QString currentPath;
     
     struct Transfer {
         std::list<Shared::MessageInfo> messages;
diff --git a/core/utils/CMakeLists.txt b/core/utils/CMakeLists.txt
new file mode 100644
index 0000000..6722da7
--- /dev/null
+++ b/core/utils/CMakeLists.txt
@@ -0,0 +1,4 @@
+target_sources(squawk PRIVATE
+  pathcheck.h
+  pathcheck.cpp
+)
diff --git a/core/utils/pathcheck.cpp b/core/utils/pathcheck.cpp
new file mode 100644
index 0000000..3f1b86a
--- /dev/null
+++ b/core/utils/pathcheck.cpp
@@ -0,0 +1,47 @@
+#include "pathcheck.h"
+
+QString Utils::downloadsPathCheck()
+{
+    QSettings settings;
+    QVariant dpv = settings.value("downloadsPath");
+    QString path;
+    if (!dpv.isValid()) {
+        path = defaultDownloadsPath();
+        qDebug() << "no downloadsPath variable in config, using default" << path;
+        path = getCanonicalWritablePath(path);
+        return path;
+    } else {
+        path = dpv.toString();
+        path = getCanonicalWritablePath(path);
+        if (path.size() == 0) {
+            path = defaultDownloadsPath();
+            qDebug() << "falling back to the default downloads path" << path;
+            path = getCanonicalWritablePath(path);
+        }
+        return path;
+    }
+}
+
+QString Utils::defaultDownloadsPath()
+{
+    return QStandardPaths::writableLocation(QStandardPaths::DownloadLocation) + "/" + QApplication::applicationName();
+}
+
+QString Utils::getCanonicalWritablePath(const QString& path)
+{
+    QDir location(path);
+    if (!location.exists()) {
+        bool res = location.mkpath(location.canonicalPath());
+        if (!res) {
+            qDebug() << "couldn't create directory" << path;
+            return "";
+        }
+    }
+    QFileInfo info(location.canonicalPath());
+    if (info.isWritable()) {
+        return location.canonicalPath();
+    } else {
+        qDebug() << "directory" << path << "is not writable";
+        return "";
+    }
+}
diff --git a/core/utils/pathcheck.h b/core/utils/pathcheck.h
new file mode 100644
index 0000000..8618012
--- /dev/null
+++ b/core/utils/pathcheck.h
@@ -0,0 +1,21 @@
+#ifndef PATHCHECK_H
+#define PATHCHECK_H
+
+#include <QString>
+#include <QStandardPaths>
+#include <QSettings>
+#include <QApplication>
+#include <QDir>
+#include <QFileInfo>
+#include <QDebug>
+
+namespace Utils {
+
+QString downloadsPathCheck();
+QString defaultDownloadsPath();
+
+QString getCanonicalWritablePath(const QString& path);
+
+}
+
+#endif // PATHCHECK_H
diff --git a/shared/utils.h b/shared/utils.h
index 6dcb141..564e2e6 100644
--- a/shared/utils.h
+++ b/shared/utils.h
@@ -24,8 +24,6 @@
 #include <QColor>
 #include <QRegularExpression>
 
-// #include "KIO/OpenFileManagerWindowJob"
-
 #include <vector> 
 
 namespace Shared {
diff --git a/ui/widgets/settings/pagegeneral.cpp b/ui/widgets/settings/pagegeneral.cpp
index e448f80..56cb610 100644
--- a/ui/widgets/settings/pagegeneral.cpp
+++ b/ui/widgets/settings/pagegeneral.cpp
@@ -6,6 +6,9 @@ PageGeneral::PageGeneral(QWidget* parent):
     m_ui(new Ui::PageGeneral())
 {
     m_ui->setupUi(this);
+
+    QSettings settings;
+    m_ui->downloadsPathInput->setText(settings.value("downloadsPath").toString());
 }
 
 PageGeneral::~PageGeneral()
diff --git a/ui/widgets/settings/pagegeneral.h b/ui/widgets/settings/pagegeneral.h
index b8c30c5..cb89bfa 100644
--- a/ui/widgets/settings/pagegeneral.h
+++ b/ui/widgets/settings/pagegeneral.h
@@ -4,6 +4,7 @@
 #include <QWidget>
 #include <QScopedPointer>
 #include <QVariant>
+#include <QSettings>
 
 namespace Ui
 {
diff --git a/ui/widgets/settings/pagegeneral.ui b/ui/widgets/settings/pagegeneral.ui
index 9921715..e010980 100644
--- a/ui/widgets/settings/pagegeneral.ui
+++ b/ui/widgets/settings/pagegeneral.ui
@@ -11,15 +11,33 @@
    </rect>
   </property>
   <layout class="QFormLayout" name="formLayout">
-   <item row="0" column="0">
-    <widget class="QLabel" name="label">
+   <item row="1" column="0">
+    <widget class="QLabel" name="downloadsPathLabel">
      <property name="text">
       <string>Downloads path</string>
      </property>
     </widget>
    </item>
-   <item row="0" column="1">
-    <widget class="QLineEdit" name="downloadsPathInput"/>
+   <item row="1" column="1">
+    <layout class="QHBoxLayout" name="horizontalLayout">
+     <property name="spacing">
+      <number>6</number>
+     </property>
+     <item>
+      <widget class="QLineEdit" name="downloadsPathInput">
+       <property name="enabled">
+        <bool>false</bool>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QPushButton" name="downloadsPathButton">
+       <property name="text">
+        <string>PushButton</string>
+       </property>
+      </widget>
+     </item>
+    </layout>
    </item>
   </layout>
  </widget>

From d8b5ccb2dadb1cb64dd4c4927ea99ecda7e0deeb Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Sat, 19 Feb 2022 00:27:09 +0300
Subject: [PATCH 169/281] downloaded files now stored with squawk:// prefix,
 that way I can move downloads folder without messing up the database

---
 core/CMakeLists.txt                        |  1 -
 core/handlers/messagehandler.cpp           | 13 ++++++-
 core/handlers/messagehandler.h             |  2 +-
 core/main.cpp                              |  4 +-
 core/networkaccess.cpp                     | 45 ++++++++++++++--------
 core/networkaccess.h                       |  1 +
 core/utils/CMakeLists.txt                  |  4 --
 shared/CMakeLists.txt                      |  2 +
 {core/utils => shared}/pathcheck.cpp       | 26 ++++++++-----
 {core/utils => shared}/pathcheck.h         |  6 ++-
 ui/widgets/conversation.cpp                |  2 +-
 ui/widgets/conversation.h                  |  1 +
 ui/widgets/messageline/messagedelegate.cpp | 11 +++---
 ui/widgets/messageline/messagedelegate.h   |  1 +
 14 files changed, 75 insertions(+), 44 deletions(-)
 delete mode 100644 core/utils/CMakeLists.txt
 rename {core/utils => shared}/pathcheck.cpp (59%)
 rename {core/utils => shared}/pathcheck.h (66%)

diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt
index 1836349..9369cb7 100644
--- a/core/CMakeLists.txt
+++ b/core/CMakeLists.txt
@@ -32,4 +32,3 @@ target_include_directories(squawk PRIVATE ${LMDB_INCLUDE_DIRS})
 
 add_subdirectory(handlers)
 add_subdirectory(passwordStorageEngines)
-add_subdirectory(utils)
diff --git a/core/handlers/messagehandler.cpp b/core/handlers/messagehandler.cpp
index eb840f8..fc05a67 100644
--- a/core/handlers/messagehandler.cpp
+++ b/core/handlers/messagehandler.cpp
@@ -298,6 +298,14 @@ void Core::MessageHandler::performSending(Shared::Message data, bool newMessage)
         data.setTime(sendTime);
     }
     changes.insert("stamp", sendTime);
+
+    //sometimes (when the image is pasted with ctrl+v)
+    //I start sending message with one path, then copy it to downloads directory
+    //so, the final path changes. Let's assume it changes always since it costs me close to nothing
+    QString attachPath = data.getAttachPath();
+    if (attachPath.size() > 0) {
+        changes.insert("attachPath", attachPath);
+    }
     
     if (ri != 0) {
         if (newMessage) {
@@ -439,14 +447,15 @@ void Core::MessageHandler::handleUploadError(const QString& jid, const QString&
     });
 }
 
-void Core::MessageHandler::onUploadFileComplete(const std::list<Shared::MessageInfo>& msgs, const QString& path)
+void Core::MessageHandler::onUploadFileComplete(const std::list<Shared::MessageInfo>& msgs, const QString& url, const QString& path)
 {
     for (const Shared::MessageInfo& info : msgs) {
         if (info.account == acc->getName()) {
             RosterItem* ri = acc->rh->getRosterItem(info.jid);
             if (ri != 0) {
                 Shared::Message msg = ri->getMessage(info.messageId);
-                sendMessageWithLocalUploadedFile(msg, path, false);
+                msg.setAttachPath(path);
+                sendMessageWithLocalUploadedFile(msg, url, false);
             } else {
                 qDebug() << "A signal received about complete upload to" << acc->name << "for pal" << info.jid << "but the object for this pal wasn't found, something went terrebly wrong, skipping send";
             }
diff --git a/core/handlers/messagehandler.h b/core/handlers/messagehandler.h
index 4eb9265..b88c46a 100644
--- a/core/handlers/messagehandler.h
+++ b/core/handlers/messagehandler.h
@@ -57,7 +57,7 @@ public slots:
     void onUploadSlotReceived(const QXmppHttpUploadSlotIq& slot);
     void onUploadSlotRequestFailed(const QXmppHttpUploadRequestIq& request);
     void onDownloadFileComplete(const std::list<Shared::MessageInfo>& msgs, const QString& path);
-    void onUploadFileComplete(const std::list<Shared::MessageInfo>& msgs, const QString& path);
+    void onUploadFileComplete(const std::list<Shared::MessageInfo>& msgs, const QString& url, const QString& path);
     void onLoadFileError(const std::list<Shared::MessageInfo>& msgs, const QString& path, bool up);
     void requestChangeMessage(const QString& jid, const QString& messageId, const QMap<QString, QVariant>& data);
     
diff --git a/core/main.cpp b/core/main.cpp
index 570bad4..9a10062 100644
--- a/core/main.cpp
+++ b/core/main.cpp
@@ -18,10 +18,10 @@
 
 #include "../shared/global.h"
 #include "../shared/messageinfo.h"
+#include "../shared/pathcheck.h"
 #include "../ui/squawk.h"
 #include "signalcatcher.h"
 #include "squawk.h"
-#include "utils/pathcheck.h"
 
 #include <QLibraryInfo>
 #include <QSettings>
@@ -109,7 +109,7 @@ int main(int argc, char *argv[])
             }
         }
     }
-    QString path = Utils::downloadsPathCheck();
+    QString path = Shared::downloadsPathCheck();
     if (path.size() > 0) {
         settings.setValue("downloadsPath", path);
     } else {
diff --git a/core/networkaccess.cpp b/core/networkaccess.cpp
index 48d26aa..25fafc8 100644
--- a/core/networkaccess.cpp
+++ b/core/networkaccess.cpp
@@ -305,7 +305,7 @@ void Core::NetworkAccess::onDownloadFinished()
             if (path.size() > 0) {
                 path = checkFileName(fileName, path);
                 
-                QFile file(path);
+                QFile file(Shared::resolvePath(path));
                 if (file.open(QIODevice::WriteOnly)) {
                     file.write(dwn->reply->readAll());
                     file.close();
@@ -379,23 +379,20 @@ void Core::NetworkAccess::onUploadFinished()
         Transfer* upl = itr->second;
         if (upl->success) {
             qDebug() << "upload success for" << url;
-            
-            storage.addFile(upl->messages, upl->url, upl->path);
-            emit uploadFileComplete(upl->messages, upl->url, upl->path);
 
             // Copy file to Download folder if it is a temp file. See Conversation::onImagePasted.
-            if (upl->path.startsWith(QDir::tempPath() + QStringLiteral("/squawk_img_attach_")) && upl->path.endsWith(".png")) {
+            if (upl->path.startsWith(QDir::tempPath() + QDir::separator() + QStringLiteral("squawk_img_attach_")) && upl->path.endsWith(".png")) {
                 QString err = "";
                 QString downloadDirPath = prepareDirectory(upl->messages.front().jid);
                 if (downloadDirPath.size() > 0) {
-                    QString newPath = downloadDirPath + "/" + upl->path.mid(QDir::tempPath().length() + 1);
+                    QString newPath = downloadDirPath + QDir::separator() + upl->path.mid(QDir::tempPath().length() + 1);
 
                     // Copy {TEMPDIR}/squawk_img_attach_XXXXXX.png to Download folder
-                    bool copyResult = QFile::copy(upl->path, newPath);
+                    bool copyResult = QFile::copy(upl->path, Shared::resolvePath(newPath));
 
                     if (copyResult) {
                         // Change storage
-                        storage.setPath(upl->url, newPath);
+                        upl->path = newPath;
                     } else {
                         err = "copying to " + newPath + " failed";
                     }
@@ -407,6 +404,9 @@ void Core::NetworkAccess::onUploadFinished()
                     qDebug() << "failed to copy temporary upload file " << upl->path << " to download folder:" << err;
                 }
             }
+
+            storage.addFile(upl->messages, upl->url, upl->path);
+            emit uploadFileComplete(upl->messages, upl->url, upl->path);
         }
         
         upl->reply->deleteLater();
@@ -519,9 +519,12 @@ bool Core::NetworkAccess::checkAndAddToUploading(const QString& acc, const QStri
 QString Core::NetworkAccess::prepareDirectory(const QString& jid)
 {
     QString path = currentPath;
+    QString addition;
     if (jid.size() > 0) {
-        path += "/" + jid;
+        addition = jid;
+        path += QDir::separator() + jid;
     }
+
     QDir location(path);
     
     if (!location.exists()) {
@@ -529,10 +532,10 @@ QString Core::NetworkAccess::prepareDirectory(const QString& jid)
         if (!res) {
             return "";
         } else {
-            return path;
+            return "squawk://" + addition;
         }
     }
-    return path;
+    return "squawk://" + addition;
 }
 
 QString Core::NetworkAccess::checkFileName(const QString& name, const QString& path)
@@ -546,14 +549,17 @@ QString Core::NetworkAccess::checkFileName(const QString& name, const QString& p
         suffix += "." + (*sItr);
     }
     QString postfix("");
-    QFileInfo proposedName(path + "/" + realName + suffix);
+    QString resolvedPath = Shared::resolvePath(path);
+    QString count("");
+    QFileInfo proposedName(resolvedPath + QDir::separator() + realName + count + suffix);
+
     int counter = 0;
     while (proposedName.exists()) {
-        QString count = QString("(") + std::to_string(++counter).c_str() + ")";
-        proposedName = QFileInfo(path + "/" + realName + count + suffix);
+        count = QString("(") + std::to_string(++counter).c_str() + ")";
+        proposedName = QFileInfo(resolvedPath + QDir::separator() + realName + count + suffix);
     }
-    
-    return proposedName.absoluteFilePath();
+
+    return path + QDir::separator() + realName + count + suffix;
 }
 
 QString Core::NetworkAccess::addMessageAndCheckForPath(const QString& url, const QString& account, const QString& jid, const QString& id)
@@ -568,5 +574,10 @@ std::list<Shared::MessageInfo> Core::NetworkAccess::reportPathInvalid(const QStr
 
 void Core::NetworkAccess::moveFilesDirectory(const QString& newPath)
 {
-
+    QDir dir;
+    if (dir.rename(currentPath, newPath)) {
+        currentPath = newPath;
+    } else {
+        qDebug() << "couldn't move downloads directory, most probably downloads will be broken";
+    }
 }
diff --git a/core/networkaccess.h b/core/networkaccess.h
index cf24fc4..0b7bb7d 100644
--- a/core/networkaccess.h
+++ b/core/networkaccess.h
@@ -31,6 +31,7 @@
 #include <set>
 
 #include "urlstorage.h"
+#include "shared/pathcheck.h"
 
 namespace Core {
 
diff --git a/core/utils/CMakeLists.txt b/core/utils/CMakeLists.txt
deleted file mode 100644
index 6722da7..0000000
--- a/core/utils/CMakeLists.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-target_sources(squawk PRIVATE
-  pathcheck.h
-  pathcheck.cpp
-)
diff --git a/shared/CMakeLists.txt b/shared/CMakeLists.txt
index a36b516..0ab7dbd 100644
--- a/shared/CMakeLists.txt
+++ b/shared/CMakeLists.txt
@@ -16,4 +16,6 @@ target_sources(squawk PRIVATE
   utils.h
   vcard.cpp
   vcard.h
+  pathcheck.cpp
+  pathcheck.h
   )
diff --git a/core/utils/pathcheck.cpp b/shared/pathcheck.cpp
similarity index 59%
rename from core/utils/pathcheck.cpp
rename to shared/pathcheck.cpp
index 3f1b86a..0850742 100644
--- a/core/utils/pathcheck.cpp
+++ b/shared/pathcheck.cpp
@@ -1,6 +1,7 @@
 #include "pathcheck.h"
 
-QString Utils::downloadsPathCheck()
+QRegularExpression squawk("^squawk:\\/\\/");
+QString Shared::downloadsPathCheck()
 {
     QSettings settings;
     QVariant dpv = settings.value("downloadsPath");
@@ -8,40 +9,47 @@ QString Utils::downloadsPathCheck()
     if (!dpv.isValid()) {
         path = defaultDownloadsPath();
         qDebug() << "no downloadsPath variable in config, using default" << path;
-        path = getCanonicalWritablePath(path);
+        path = getAbsoluteWritablePath(path);
         return path;
     } else {
         path = dpv.toString();
-        path = getCanonicalWritablePath(path);
+        path = getAbsoluteWritablePath(path);
         if (path.size() == 0) {
             path = defaultDownloadsPath();
             qDebug() << "falling back to the default downloads path" << path;
-            path = getCanonicalWritablePath(path);
+            path = getAbsoluteWritablePath(path);
         }
         return path;
     }
 }
 
-QString Utils::defaultDownloadsPath()
+QString Shared::defaultDownloadsPath()
 {
     return QStandardPaths::writableLocation(QStandardPaths::DownloadLocation) + "/" + QApplication::applicationName();
 }
 
-QString Utils::getCanonicalWritablePath(const QString& path)
+QString Shared::getAbsoluteWritablePath(const QString& path)
 {
     QDir location(path);
     if (!location.exists()) {
-        bool res = location.mkpath(location.canonicalPath());
+        bool res = location.mkpath(location.absolutePath());
         if (!res) {
             qDebug() << "couldn't create directory" << path;
             return "";
         }
     }
-    QFileInfo info(location.canonicalPath());
+    QFileInfo info(location.absolutePath());
     if (info.isWritable()) {
-        return location.canonicalPath();
+        return location.absolutePath();
     } else {
         qDebug() << "directory" << path << "is not writable";
         return "";
     }
 }
+
+QString Shared::resolvePath(QString path)
+{
+    QSettings settings;
+    QVariant dpv = settings.value("downloadsPath");
+    return path.replace(squawk, dpv.toString() + "/");
+}
diff --git a/core/utils/pathcheck.h b/shared/pathcheck.h
similarity index 66%
rename from core/utils/pathcheck.h
rename to shared/pathcheck.h
index 8618012..6e7cd39 100644
--- a/core/utils/pathcheck.h
+++ b/shared/pathcheck.h
@@ -8,13 +8,15 @@
 #include <QDir>
 #include <QFileInfo>
 #include <QDebug>
+#include <QRegularExpression>
 
-namespace Utils {
+namespace Shared {
 
 QString downloadsPathCheck();
 QString defaultDownloadsPath();
 
-QString getCanonicalWritablePath(const QString& path);
+QString getAbsoluteWritablePath(const QString& path);
+QString resolvePath(QString path);
 
 }
 
diff --git a/ui/widgets/conversation.cpp b/ui/widgets/conversation.cpp
index 69eac19..1c82024 100644
--- a/ui/widgets/conversation.cpp
+++ b/ui/widgets/conversation.cpp
@@ -483,7 +483,7 @@ void Conversation::onFeedContext(const QPoint& pos)
             });
         }
         
-        QString path = item->getAttachPath();
+        QString path = Shared::resolvePath(item->getAttachPath());
         if (path.size() > 0) {
             showMenu = true;
             QAction* open = contextMenu->addAction(Shared::icon("document-preview"), tr("Open")); 
diff --git a/ui/widgets/conversation.h b/ui/widgets/conversation.h
index a758b2c..76a8dd5 100644
--- a/ui/widgets/conversation.h
+++ b/ui/widgets/conversation.h
@@ -33,6 +33,7 @@
 #include "shared/order.h"
 #include "shared/icons.h"
 #include "shared/utils.h"
+#include "shared/pathcheck.h"
 
 #include "ui/models/account.h"
 #include "ui/models/roster.h"
diff --git a/ui/widgets/messageline/messagedelegate.cpp b/ui/widgets/messageline/messagedelegate.cpp
index d692752..22e8dcb 100644
--- a/ui/widgets/messageline/messagedelegate.cpp
+++ b/ui/widgets/messageline/messagedelegate.cpp
@@ -314,7 +314,7 @@ QSize MessageDelegate::sizeHint(const QStyleOptionViewItem& option, const QModel
         case Models::none:
             break;
         case Models::uploading:
-            messageSize.rheight() += Preview::calculateAttachSize(data.attach.localPath, messageRect).height() + textMargin;
+            messageSize.rheight() += Preview::calculateAttachSize(Shared::resolvePath(data.attach.localPath), messageRect).height() + textMargin;
             [[fallthrough]];
         case Models::downloading:
             messageSize.rheight() += barHeight + textMargin;
@@ -326,7 +326,7 @@ QSize MessageDelegate::sizeHint(const QStyleOptionViewItem& option, const QModel
             break;
         case Models::ready:
         case Models::local: {
-            QSize aSize = Preview::calculateAttachSize(data.attach.localPath, messageRect);
+            QSize aSize = Preview::calculateAttachSize(Shared::resolvePath(data.attach.localPath), messageRect);
             messageSize.rheight() += aSize.height() + textMargin;
             messageSize.setWidth(std::max(messageSize.width(), aSize.width()));
         }
@@ -338,7 +338,7 @@ QSize MessageDelegate::sizeHint(const QStyleOptionViewItem& option, const QModel
         }
             break;
         case Models::errorUpload: {
-            QSize aSize = Preview::calculateAttachSize(data.attach.localPath, messageRect);
+            QSize aSize = Preview::calculateAttachSize(Shared::resolvePath(data.attach.localPath), messageRect);
             QSize commentSize = dateMetrics.boundingRect(messageRect, Qt::TextWordWrap, data.attach.error).size();
             messageSize.rheight() += aSize.height() + commentSize.height() + textMargin * 2;
             messageSize.setWidth(std::max(messageSize.width(), std::max(commentSize.width(), aSize.width())));
@@ -455,12 +455,13 @@ int MessageDelegate::paintPreview(const Models::FeedItem& data, QPainter* painte
     std::map<QString, Preview*>::iterator itr = previews->find(data.id);
 
     QSize size = option.rect.size();
+    QString path = Shared::resolvePath(data.attach.localPath);
     if (itr != previews->end()) {
         preview = itr->second;
-        preview->actualize(data.attach.localPath, size, option.rect.topLeft());
+        preview->actualize(path, size, option.rect.topLeft());
     } else {
         QWidget* vp = static_cast<QWidget*>(painter->device());
-        preview = new Preview(data.attach.localPath, size, option.rect.topLeft(), vp);
+        preview = new Preview(path, size, option.rect.topLeft(), vp);
         previews->insert(std::make_pair(data.id, preview));
     }
     
diff --git a/ui/widgets/messageline/messagedelegate.h b/ui/widgets/messageline/messagedelegate.h
index b58a1bb..9225412 100644
--- a/ui/widgets/messageline/messagedelegate.h
+++ b/ui/widgets/messageline/messagedelegate.h
@@ -33,6 +33,7 @@
 #include "shared/icons.h"
 #include "shared/global.h"
 #include "shared/utils.h"
+#include "shared/pathcheck.h"
 
 #include "preview.h"
 

From 73b1b58a966469ec5b28ebc9899fcebf1df567b3 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Sat, 19 Feb 2022 21:31:49 +0300
Subject: [PATCH 170/281] Downloads folder now is movable

---
 core/handlers/messagehandler.cpp    |  6 +++-
 core/handlers/messagehandler.h      |  1 +
 core/main.cpp                       |  1 +
 core/networkaccess.cpp              | 18 +++++++----
 core/squawk.cpp                     |  6 ++++
 core/squawk.h                       |  1 +
 shared/pathcheck.cpp                | 23 +++++++++++++++
 shared/pathcheck.h                  |  3 ++
 ui/squawk.cpp                       |  1 +
 ui/squawk.h                         |  1 +
 ui/widgets/settings/pagegeneral.cpp | 46 ++++++++++++++++++++++++++++-
 ui/widgets/settings/pagegeneral.h   |  7 +++++
 ui/widgets/settings/pagegeneral.ui  |  2 +-
 ui/widgets/settings/settings.cpp    | 12 +++++++-
 ui/widgets/settings/settings.h      |  5 ++++
 ui/widgets/settings/settings.ui     | 20 +++++++++++--
 16 files changed, 141 insertions(+), 12 deletions(-)

diff --git a/core/handlers/messagehandler.cpp b/core/handlers/messagehandler.cpp
index fc05a67..1e89dd6 100644
--- a/core/handlers/messagehandler.cpp
+++ b/core/handlers/messagehandler.cpp
@@ -304,7 +304,11 @@ void Core::MessageHandler::performSending(Shared::Message data, bool newMessage)
     //so, the final path changes. Let's assume it changes always since it costs me close to nothing
     QString attachPath = data.getAttachPath();
     if (attachPath.size() > 0) {
-        changes.insert("attachPath", attachPath);
+        QString squawkified = Shared::squawkifyPath(attachPath);
+        changes.insert("attachPath", squawkified);
+        if (attachPath != squawkified) {
+            data.setAttachPath(squawkified);
+        }
     }
     
     if (ri != 0) {
diff --git a/core/handlers/messagehandler.h b/core/handlers/messagehandler.h
index b88c46a..4f03484 100644
--- a/core/handlers/messagehandler.h
+++ b/core/handlers/messagehandler.h
@@ -29,6 +29,7 @@
 
 #include <shared/message.h>
 #include <shared/messageinfo.h>
+#include <shared/pathcheck.h>
 
 namespace Core {
 
diff --git a/core/main.cpp b/core/main.cpp
index 9a10062..79ca648 100644
--- a/core/main.cpp
+++ b/core/main.cpp
@@ -160,6 +160,7 @@ int main(int argc, char *argv[])
     QObject::connect(&w, &Squawk::uploadVCard, squawk, &Core::Squawk::uploadVCard);
     QObject::connect(&w, &Squawk::responsePassword, squawk, &Core::Squawk::responsePassword);
     QObject::connect(&w, &Squawk::localPathInvalid, squawk, &Core::Squawk::onLocalPathInvalid);
+    QObject::connect(&w, &Squawk::changeDownloadsPath, squawk, &Core::Squawk::changeDownloadsPath);
     
     QObject::connect(squawk, &Core::Squawk::newAccount, &w, &Squawk::newAccount);
     QObject::connect(squawk, &Core::Squawk::addContact, &w, &Squawk::addContact);
diff --git a/core/networkaccess.cpp b/core/networkaccess.cpp
index 25fafc8..7c55e19 100644
--- a/core/networkaccess.cpp
+++ b/core/networkaccess.cpp
@@ -438,10 +438,10 @@ void Core::NetworkAccess::onUploadProgress(qint64 bytesReceived, qint64 bytesTot
 
 QString Core::NetworkAccess::getFileRemoteUrl(const QString& path)
 {
-    QString p;
+    QString p = Shared::squawkifyPath(path);
     
     try {
-        p = storage.getUrl(path);
+        p = storage.getUrl(p);
     } catch (const Archive::NotFound& err) {
         
     } catch (...) {
@@ -574,10 +574,16 @@ std::list<Shared::MessageInfo> Core::NetworkAccess::reportPathInvalid(const QStr
 
 void Core::NetworkAccess::moveFilesDirectory(const QString& newPath)
 {
-    QDir dir;
-    if (dir.rename(currentPath, newPath)) {
-        currentPath = newPath;
-    } else {
+    QDir dir(currentPath);
+    bool success = true;
+    qDebug() << "moving" << currentPath << "to" << newPath;
+    for (QFileInfo fileInfo : dir.entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot | QDir::Hidden | QDir::System)) {
+        QString fileName = fileInfo.fileName();
+        success = dir.rename(fileName, newPath + QDir::separator() + fileName) && success;
+    }
+
+    if (!success) {
         qDebug() << "couldn't move downloads directory, most probably downloads will be broken";
     }
+    currentPath = newPath;
 }
diff --git a/core/squawk.cpp b/core/squawk.cpp
index 69d2eef..9f2b445 100644
--- a/core/squawk.cpp
+++ b/core/squawk.cpp
@@ -785,3 +785,9 @@ void Core::Squawk::onLocalPathInvalid(const QString& path)
         }
     }
 }
+
+void Core::Squawk::changeDownloadsPath(const QString& path)
+{
+    network.moveFilesDirectory(path);
+}
+
diff --git a/core/squawk.h b/core/squawk.h
index 5db9fa8..738a957 100644
--- a/core/squawk.h
+++ b/core/squawk.h
@@ -123,6 +123,7 @@ public slots:
     void uploadVCard(const QString& account, const Shared::VCard& card);
     void responsePassword(const QString& account, const QString& password);
     void onLocalPathInvalid(const QString& path);
+    void changeDownloadsPath(const QString& path);
     
 private:
     typedef std::deque<Account*> Accounts;
diff --git a/shared/pathcheck.cpp b/shared/pathcheck.cpp
index 0850742..1929387 100644
--- a/shared/pathcheck.cpp
+++ b/shared/pathcheck.cpp
@@ -53,3 +53,26 @@ QString Shared::resolvePath(QString path)
     QVariant dpv = settings.value("downloadsPath");
     return path.replace(squawk, dpv.toString() + "/");
 }
+
+QString Shared::squawkifyPath(QString path)
+{
+    QSettings settings;
+    QString current = settings.value("downloadsPath").toString();
+
+    if (path.startsWith(current)) {
+        path.replace(0, current.size() + 1, "squawk://");
+    }
+
+    return path;
+}
+
+bool Shared::isSubdirectoryOfSettings(const QString& path)
+{
+
+    QSettings settings;
+    QDir oldPath(settings.value("downloadsPath").toString());
+    QDir newPath(path);
+
+    return newPath.canonicalPath().startsWith(oldPath.canonicalPath());
+}
+
diff --git a/shared/pathcheck.h b/shared/pathcheck.h
index 6e7cd39..62dcaeb 100644
--- a/shared/pathcheck.h
+++ b/shared/pathcheck.h
@@ -13,10 +13,13 @@
 namespace Shared {
 
 QString downloadsPathCheck();
+QString downloadsPathCheck(QString path);
 QString defaultDownloadsPath();
 
 QString getAbsoluteWritablePath(const QString& path);
 QString resolvePath(QString path);
+QString squawkifyPath(QString path);
+bool isSubdirectoryOfSettings(const QString& path);
 
 }
 
diff --git a/ui/squawk.cpp b/ui/squawk.cpp
index 7acda3d..e24640a 100644
--- a/ui/squawk.cpp
+++ b/ui/squawk.cpp
@@ -125,6 +125,7 @@ void Squawk::onPreferences()
         preferences = new Settings();
         preferences->setAttribute(Qt::WA_DeleteOnClose);
         connect(preferences, &Settings::destroyed, this, &Squawk::onPreferencesClosed);
+        connect(preferences, &Settings::changeDownloadsPath, this, &Squawk::changeDownloadsPath);
 
         preferences->show();
     } else {
diff --git a/ui/squawk.h b/ui/squawk.h
index b419057..7551f66 100644
--- a/ui/squawk.h
+++ b/ui/squawk.h
@@ -80,6 +80,7 @@ signals:
     void uploadVCard(const QString& account, const Shared::VCard& card);
     void responsePassword(const QString& account, const QString& password);
     void localPathInvalid(const QString& path);
+    void changeDownloadsPath(const QString& path);
     
 public slots:
     void writeSettings();
diff --git a/ui/widgets/settings/pagegeneral.cpp b/ui/widgets/settings/pagegeneral.cpp
index 56cb610..a546bd0 100644
--- a/ui/widgets/settings/pagegeneral.cpp
+++ b/ui/widgets/settings/pagegeneral.cpp
@@ -3,14 +3,58 @@
 
 PageGeneral::PageGeneral(QWidget* parent):
     QWidget(parent),
-    m_ui(new Ui::PageGeneral())
+    m_ui(new Ui::PageGeneral()),
+    dialog(nullptr)
 {
     m_ui->setupUi(this);
 
     QSettings settings;
     m_ui->downloadsPathInput->setText(settings.value("downloadsPath").toString());
+    connect(m_ui->downloadsPathButton, &QPushButton::clicked, this, &PageGeneral::onBrowseButtonClicked);
 }
 
 PageGeneral::~PageGeneral()
 {
+    if (dialog != nullptr) {
+        dialog->deleteLater();
+    }
+}
+
+void PageGeneral::onBrowseButtonClicked()
+{
+    if (dialog == nullptr) {
+        QSettings settings;
+        dialog = new QFileDialog(this, tr("Select where downloads folder is going to be"), settings.value("downloadsPath").toString());
+        dialog->setAttribute(Qt::WA_DeleteOnClose);
+        dialog->setAcceptMode(QFileDialog::AcceptSave);                 //I find it the most convinient way
+        dialog->setFileMode(QFileDialog::AnyFile);                      //Otherwise the directory is supposed to be
+        dialog->setOption(QFileDialog::ShowDirsOnly, true);             //selected and not to be navigated
+        dialog->setOption(QFileDialog::DontConfirmOverwrite, true);
+        dialog->setModal(true);
+        connect(dialog, &QFileDialog::accepted, this, &PageGeneral::onDialogAccepted);
+        connect(dialog, &QFileDialog::destroyed, this, &PageGeneral::onDialogDestroyed);
+        dialog->show();
+    } else {
+        dialog->show();
+        dialog->raise();
+        dialog->activateWindow();
+    }
+}
+
+void PageGeneral::onDialogAccepted()
+{
+    QStringList files = dialog->selectedFiles();
+    QString path;
+    if (files.size() > 0) {
+        path = files[0];
+    } else {
+        path = dialog->directory().canonicalPath();
+    }
+    m_ui->downloadsPathInput->setText(path);
+    emit variableModified("downloadsPath", path);
+}
+
+void PageGeneral::onDialogDestroyed()
+{
+    dialog = nullptr;
 }
diff --git a/ui/widgets/settings/pagegeneral.h b/ui/widgets/settings/pagegeneral.h
index cb89bfa..ec00bba 100644
--- a/ui/widgets/settings/pagegeneral.h
+++ b/ui/widgets/settings/pagegeneral.h
@@ -5,6 +5,7 @@
 #include <QScopedPointer>
 #include <QVariant>
 #include <QSettings>
+#include <QFileDialog>
 
 namespace Ui
 {
@@ -24,8 +25,14 @@ public:
 signals:
     void variableModified(const QString& key, const QVariant& value);
 
+private slots:
+    void onBrowseButtonClicked();
+    void onDialogAccepted();
+    void onDialogDestroyed();
+
 private:
     QScopedPointer<Ui::PageGeneral> m_ui;
+    QFileDialog* dialog;
 };
 
 #endif // PAGEGENERAL_H
diff --git a/ui/widgets/settings/pagegeneral.ui b/ui/widgets/settings/pagegeneral.ui
index e010980..e412668 100644
--- a/ui/widgets/settings/pagegeneral.ui
+++ b/ui/widgets/settings/pagegeneral.ui
@@ -33,7 +33,7 @@
      <item>
       <widget class="QPushButton" name="downloadsPathButton">
        <property name="text">
-        <string>PushButton</string>
+        <string>Browse</string>
        </property>
       </widget>
      </item>
diff --git a/ui/widgets/settings/settings.cpp b/ui/widgets/settings/settings.cpp
index 3ab38bb..27401bb 100644
--- a/ui/widgets/settings/settings.cpp
+++ b/ui/widgets/settings/settings.cpp
@@ -42,11 +42,21 @@ void Settings::apply()
     for (const std::pair<const QString, QVariant>& pair: modifiedSettings) {
         if (pair.first == "style") {
             Shared::Global::setStyle(pair.second.toString());
+            settings.setValue(pair.first, pair.second);
         } else if (pair.first == "theme") {
             Shared::Global::setTheme(pair.second.toString());
+            settings.setValue(pair.first, pair.second);
+        } else if (pair.first == "downloadsPath") {
+            QString path = pair.second.toString();
+            if (!Shared::isSubdirectoryOfSettings(path)) {
+                path = Shared::getAbsoluteWritablePath(path);
+                if (path.size() > 0) {
+                    settings.setValue(pair.first, path);
+                    emit changeDownloadsPath(path);
+                }
+            }
         }
 
-        settings.setValue(pair.first, pair.second);
     }
 
     modifiedSettings.clear();
diff --git a/ui/widgets/settings/settings.h b/ui/widgets/settings/settings.h
index 5031139..5a6b37c 100644
--- a/ui/widgets/settings/settings.h
+++ b/ui/widgets/settings/settings.h
@@ -5,8 +5,10 @@
 #include <QListWidgetItem>
 #include <QScopedPointer>
 #include <QSettings>
+#include <QDir>
 
 #include "shared/global.h"
+#include "shared/pathcheck.h"
 
 namespace Ui
 {
@@ -23,6 +25,9 @@ public:
     Settings(QWidget* parent = nullptr);
     ~Settings();
 
+signals:
+    void changeDownloadsPath(const QString& path);
+
 public slots:
     void apply();
     void confirm();
diff --git a/ui/widgets/settings/settings.ui b/ui/widgets/settings/settings.ui
index fe092dc..d97a3f2 100644
--- a/ui/widgets/settings/settings.ui
+++ b/ui/widgets/settings/settings.ui
@@ -10,6 +10,9 @@
     <height>363</height>
    </rect>
   </property>
+  <property name="windowTitle">
+   <string>Preferences</string>
+  </property>
   <layout class="QGridLayout" name="gridLayout">
    <property name="leftMargin">
     <number>0</number>
@@ -183,10 +186,23 @@
           <verstretch>0</verstretch>
          </sizepolicy>
         </property>
-        <widget class="PageGeneral" name="General" native="true"/>
-        <widget class="PageAppearance" name="Appearance" native="true"/>
+        <widget class="PageGeneral" name="General"/>
+        <widget class="PageAppearance" name="Appearance"/>
        </widget>
       </item>
+      <item row="2" column="0">
+       <spacer name="horizontalSpacer">
+        <property name="orientation">
+         <enum>Qt::Horizontal</enum>
+        </property>
+        <property name="sizeHint" stdset="0">
+         <size>
+          <width>40</width>
+          <height>20</height>
+         </size>
+        </property>
+       </spacer>
+      </item>
      </layout>
     </widget>
    </item>

From 0823b35148d690d60c12ec5a19821cfe1f9fbe1b Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Sun, 20 Feb 2022 22:10:09 +0300
Subject: [PATCH 171/281] removed unused old message line files, first thoughts
 on message edition

---
 CHANGELOG.md                           |   2 +
 ui/utils/badge.cpp                     |  81 +++-
 ui/utils/badge.h                       |  10 +
 ui/widgets/conversation.cpp            |   8 +-
 ui/widgets/conversation.h              |   6 +
 ui/widgets/conversation.ui             |  34 +-
 ui/widgets/messageline/CMakeLists.txt  |   4 -
 ui/widgets/messageline/message.cpp     | 344 -----------------
 ui/widgets/messageline/message.h       | 103 -----
 ui/widgets/messageline/messageline.cpp | 504 -------------------------
 ui/widgets/messageline/messageline.h   | 108 ------
 11 files changed, 121 insertions(+), 1083 deletions(-)
 delete mode 100644 ui/widgets/messageline/message.cpp
 delete mode 100644 ui/widgets/messageline/message.h
 delete mode 100644 ui/widgets/messageline/messageline.cpp
 delete mode 100644 ui/widgets/messageline/messageline.h

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7b1d75a..8a6fdfa 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,11 +6,13 @@
 - build now correctly installs all build plugin libs
 
 ### Improvements
+- reduced amount of places where platform specific path separator is used
 
 ### New features
 - the settings are here! You con config different stuff from there
 - now it's possible to set up different qt styles from settings
 - if you have KConfig nad KConfigWidgets packages installed - you can chose from global color schemes
+- it's possible now to chose a folder where squawk is going to store downloaded files
 
 ## Squawk 0.2.0 (Jan 10, 2022)
 ### Bug fixes
diff --git a/ui/utils/badge.cpp b/ui/utils/badge.cpp
index 7575afc..b3b321a 100644
--- a/ui/utils/badge.cpp
+++ b/ui/utils/badge.cpp
@@ -26,32 +26,62 @@ Badge::Badge(const QString& p_id, const QString& p_text, const QIcon& icon, QWid
     closeButton(new QPushButton()),
     layout(new QHBoxLayout(this))
 {
-    setBackgroundRole(QPalette::Base);
-    //setAutoFillBackground(true);
-    setFrameStyle(QFrame::StyledPanel);
-    setFrameShadow(QFrame::Raised);
+    createMandatoryComponents();
     
     image->setPixmap(icon.pixmap(25, 25));
-    QIcon tabCloseIcon = QIcon::fromTheme("tab-close");
-    if (tabCloseIcon.isNull()) {
-        tabCloseIcon.addFile(QString::fromUtf8(":/images/fallback/dark/big/edit-none.svg"), QSize(), QIcon::Normal, QIcon::Off);
-    }
-    closeButton->setIcon(tabCloseIcon);
 
-    closeButton->setMaximumHeight(25);
-    closeButton->setMaximumWidth(25);
-    
     layout->addWidget(image);
     layout->addWidget(text);
     layout->addWidget(closeButton);
-    
-    layout->setContentsMargins(2, 2, 2, 2);
-    
-    connect(closeButton, &QPushButton::clicked, this, &Badge::close);
+}
+
+Badge::Badge(QWidget* parent):
+    QFrame(parent),
+    id(Shared::generateUUID()),
+    image(nullptr),
+    text(nullptr),
+    closeButton(new QPushButton()),
+    layout(new QHBoxLayout(this))
+{
+    createMandatoryComponents();
+
+    layout->addWidget(closeButton);
+}
+
+void Badge::setIcon(const QIcon& icon)
+{
+    if (image == nullptr) {
+        image = new QLabel();
+        image->setPixmap(icon.pixmap(25, 25));
+        layout->insertWidget(0, image);
+    } else {
+        image->setPixmap(icon.pixmap(25, 25));
+    }
+}
+
+void Badge::setText(const QString& p_text)
+{
+    if (text == nullptr) {
+        text = new QLabel(p_text);
+        int index = 0;
+        if (image != nullptr) {
+            index = 1;
+        }
+        layout->insertWidget(index, text);
+    } else {
+        text->setText(p_text);
+    }
 }
 
 Badge::~Badge()
 {
+    if (image != nullptr) {
+        delete image;
+    }
+    if (text != nullptr) {
+        delete text;
+    }
+    delete closeButton;
 }
 
 bool Badge::Comparator::operator()(const Badge* a, const Badge* b) const
@@ -63,3 +93,22 @@ bool Badge::Comparator::operator()(const Badge& a, const Badge& b) const
 {
     return a.id < b.id;
 }
+
+void Badge::createMandatoryComponents()
+{
+    setBackgroundRole(QPalette::Base);
+    //setAutoFillBackground(true);
+    setFrameStyle(QFrame::StyledPanel);
+    setFrameShadow(QFrame::Raised);
+
+    QIcon tabCloseIcon = QIcon::fromTheme("tab-close");
+    if (tabCloseIcon.isNull()) {
+        tabCloseIcon.addFile(QString::fromUtf8(":/images/fallback/dark/big/edit-none.svg"), QSize(), QIcon::Normal, QIcon::Off);
+    }
+    closeButton->setIcon(tabCloseIcon);
+
+    closeButton->setMaximumHeight(25);
+    closeButton->setMaximumWidth(25);
+    layout->setContentsMargins(2, 2, 2, 2);
+    connect(closeButton, &QPushButton::clicked, this, &Badge::close);
+}
diff --git a/ui/utils/badge.h b/ui/utils/badge.h
index 93a7f00..52f4747 100644
--- a/ui/utils/badge.h
+++ b/ui/utils/badge.h
@@ -25,6 +25,8 @@
 #include <QIcon>
 #include <QPushButton>
 
+#include "shared/utils.h"
+
 /**
  * @todo write docs
  */
@@ -33,9 +35,14 @@ class Badge : public QFrame
     Q_OBJECT
 public:
     Badge(const QString& id, const QString& text, const QIcon& icon, QWidget* parent = nullptr);
+    Badge(QWidget* parent = nullptr);
     ~Badge();
     
     const QString id;
+
+public:
+    void setText(const QString& text);
+    void setIcon(const QIcon& icon);
     
 signals:
     void close();
@@ -45,6 +52,9 @@ private:
     QLabel* text;
     QPushButton* closeButton;
     QHBoxLayout* layout;
+
+private:
+    void createMandatoryComponents();
     
 public:
     struct Comparator {
diff --git a/ui/widgets/conversation.cpp b/ui/widgets/conversation.cpp
index 1c82024..07c599e 100644
--- a/ui/widgets/conversation.cpp
+++ b/ui/widgets/conversation.cpp
@@ -57,7 +57,8 @@ Conversation::Conversation(bool muc, Models::Account* acc, Models::Element* el,
     tsb(QApplication::style()->styleHint(QStyle::SH_ScrollBar_Transient) == 1),
     pasteImageAction(new QAction(tr("Paste Image"), this)),
     shadow(10, 1, Qt::black, this),
-    contextMenu(new QMenu())
+    contextMenu(new QMenu()),
+    currentAction(CurrentAction::none)
 {
     m_ui->setupUi(this);
     
@@ -108,6 +109,9 @@ Conversation::Conversation(bool muc, Models::Account* acc, Models::Element* el,
     //line->setMyName(acc->getName());
     
     initializeOverlay();
+
+     m_ui->currentActionBadge->setVisible(false);;
+//     m_ui->currentActionBadge->setText(tr("Editing message..."));
 }
 
 Conversation::~Conversation()
@@ -237,7 +241,7 @@ void Conversation::onEnterPressed()
             element->feed->registerUpload(msg.getId());
             emit sendMessage(msg);
         }
-         clearAttachedFiles();
+        clearAttachedFiles();
     }
 }
 
diff --git a/ui/widgets/conversation.h b/ui/widgets/conversation.h
index 76a8dd5..c43d7a0 100644
--- a/ui/widgets/conversation.h
+++ b/ui/widgets/conversation.h
@@ -121,6 +121,11 @@ public:
     const bool isMuc;
     
 protected:
+    enum class CurrentAction {
+        none,
+        edit
+    };
+
     Models::Account* account;
     Models::Element* element;
     QString palJid;
@@ -142,6 +147,7 @@ protected:
 
     ShadowOverlay shadow;
     QMenu* contextMenu;
+    CurrentAction currentAction;
 
 private:
     static bool painterInitialized;
diff --git a/ui/widgets/conversation.ui b/ui/widgets/conversation.ui
index 483375a..ce9ad66 100644
--- a/ui/widgets/conversation.ui
+++ b/ui/widgets/conversation.ui
@@ -279,6 +279,29 @@
            </property>
           </widget>
          </item>
+         <item>
+          <spacer name="horizontalSpacer_3">
+           <property name="orientation">
+            <enum>Qt::Horizontal</enum>
+           </property>
+           <property name="sizeHint" stdset="0">
+            <size>
+             <width>40</width>
+             <height>20</height>
+            </size>
+           </property>
+          </spacer>
+         </item>
+         <item>
+          <widget class="Badge" name="currentActionBadge">
+           <property name="frameShape">
+            <enum>QFrame::StyledPanel</enum>
+           </property>
+           <property name="frameShadow">
+            <enum>QFrame::Raised</enum>
+           </property>
+          </widget>
+         </item>
          <item>
           <spacer name="horizontalSpacer">
            <property name="orientation">
@@ -400,8 +423,8 @@ background-color: transparent
          <string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
 &lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
 p, li { white-space: pre-wrap; }
-&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Liberation Sans'; font-size:10pt; font-weight:400; font-style:normal;&quot;&gt;
-&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Noto Sans'; font-size:8pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Liberation Sans'; font-size:10pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
         </property>
         <property name="acceptRichText">
          <bool>false</bool>
@@ -419,6 +442,13 @@ p, li { white-space: pre-wrap; }
    </item>
   </layout>
  </widget>
+ <customwidgets>
+  <customwidget>
+   <class>Badge</class>
+   <extends>QFrame</extends>
+   <header location="global">ui/utils/badge.h</header>
+  </customwidget>
+ </customwidgets>
  <resources>
   <include location="../../resources/resources.qrc"/>
  </resources>
diff --git a/ui/widgets/messageline/CMakeLists.txt b/ui/widgets/messageline/CMakeLists.txt
index 7cace9d..7a850da 100644
--- a/ui/widgets/messageline/CMakeLists.txt
+++ b/ui/widgets/messageline/CMakeLists.txt
@@ -1,14 +1,10 @@
 target_sources(squawk PRIVATE
   messagedelegate.cpp
   messagedelegate.h
-  #messageline.cpp
-  #messageline.h
   preview.cpp
   preview.h
   messagefeed.cpp
   messagefeed.h
   feedview.cpp
   feedview.h
-  #message.cpp
-  #message.h
   )
diff --git a/ui/widgets/messageline/message.cpp b/ui/widgets/messageline/message.cpp
deleted file mode 100644
index 7a004bb..0000000
--- a/ui/widgets/messageline/message.cpp
+++ /dev/null
@@ -1,344 +0,0 @@
-/*
- * Squawk messenger. 
- * Copyright (C) 2019 Yury Gubich <blue@macaw.me>
- * 
- * 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 "message.h"
-#include <QDebug>
-#include <QMimeDatabase>
-#include <QPixmap>
-#include <QFileInfo>
-#include <QRegularExpression>
-
-Message::Message(const Shared::Message& source, bool p_outgoing, const QString& p_sender, const QString& avatarPath, QWidget* parent):
-    QWidget(parent),
-    outgoing(p_outgoing),
-    msg(source),
-    body(new QWidget()),
-    statusBar(new QWidget()),
-    bodyLayout(new QVBoxLayout(body)),
-    layout(new QHBoxLayout(this)),
-    date(new QLabel(msg.getTime().toLocalTime().toString())),
-    sender(new QLabel(p_sender)),
-    text(new QLabel()),
-    shadow(new QGraphicsDropShadowEffect()),
-    button(0),
-    file(0),
-    progress(0),
-    fileComment(new QLabel()),
-    statusIcon(0),
-    editedLabel(0),
-    avatar(new Image(avatarPath.size() == 0 ? Shared::iconPath("user", true) : avatarPath, 60)),
-    hasButton(false),
-    hasProgress(false),
-    hasFile(false),
-    commentAdded(false),
-    hasStatusIcon(false),
-    hasEditedLabel(false)
-{
-    setContentsMargins(0, 0, 0, 0);
-    layout->setContentsMargins(10, 5, 10, 5);
-    body->setBackgroundRole(QPalette::AlternateBase);
-    body->setAutoFillBackground(true);
-    
-    QString bd = Shared::processMessageBody(msg.getBody());
-    text->setTextFormat(Qt::RichText);
-    text->setText(bd);;
-    text->setTextInteractionFlags(text->textInteractionFlags() | Qt::TextSelectableByMouse | Qt::LinksAccessibleByMouse);
-    text->setWordWrap(true);
-    text->setOpenExternalLinks(true);
-    if (bd.size() == 0) {
-        text->hide();
-    }
-    
-    QFont dFont = date->font();
-    dFont.setItalic(true);
-    dFont.setPointSize(dFont.pointSize() - 2);
-    date->setFont(dFont);
-    
-    QFont f;
-    f.setBold(true);
-    sender->setFont(f);
-    
-    bodyLayout->addWidget(sender);
-    bodyLayout->addWidget(text);
-    
-    shadow->setBlurRadius(10);
-    shadow->setXOffset(1);
-    shadow->setYOffset(1);
-    shadow->setColor(Qt::black);
-    body->setGraphicsEffect(shadow);
-    avatar->setMaximumHeight(60);
-    avatar->setMaximumWidth(60);
-    
-    statusBar->setContentsMargins(0, 0, 0, 0);
-    QHBoxLayout* statusLay = new QHBoxLayout();
-    statusLay->setContentsMargins(0, 0, 0, 0);
-    statusBar->setLayout(statusLay);
-    
-    if (outgoing) {
-        sender->setAlignment(Qt::AlignRight);
-        date->setAlignment(Qt::AlignRight);
-        statusIcon = new QLabel();
-        setState();
-        statusLay->addWidget(statusIcon);
-        statusLay->addWidget(date);
-        layout->addStretch();
-        layout->addWidget(body);
-        layout->addWidget(avatar);
-        hasStatusIcon = true;
-    } else {
-        layout->addWidget(avatar);
-        layout->addWidget(body);
-        layout->addStretch();
-        statusLay->addWidget(date);
-    }
-    if (msg.getEdited()) {
-        setEdited();
-    }
-    
-    bodyLayout->addWidget(statusBar);
-    layout->setAlignment(avatar, Qt::AlignTop);
-}
-
-Message::~Message()
-{
-    if (!commentAdded) {
-        delete fileComment;
-    }
-    //delete body;  //not sure if I should delete it here, it's probably already owned by the infrastructure and gonna die with the rest of the widget
-    //delete avatar;
-}
-
-QString Message::getId() const
-{
-    return msg.getId();
-}
-
-QString Message::getSenderJid() const
-{
-    return msg.getFromJid();
-}
-
-QString Message::getSenderResource() const
-{
-    return msg.getFromResource();
-}
-
-QString Message::getFileUrl() const
-{
-    return msg.getOutOfBandUrl();
-}
-
-void Message::setSender(const QString& p_sender)
-{
-    sender->setText(p_sender);
-}
-
-void Message::addButton(const QIcon& icon, const QString& buttonText, const QString& tooltip)
-{
-    hideFile();
-    hideProgress();
-    if (!hasButton) {
-        hideComment();
-        if (msg.getBody() == msg.getOutOfBandUrl()) {
-            text->setText("");
-            text->hide();
-        }
-        button = new QPushButton(icon, buttonText);
-        button->setToolTip(tooltip);
-        connect(button, &QPushButton::clicked, this, &Message::buttonClicked);
-        bodyLayout->insertWidget(2, button);
-        hasButton = true;
-    }
-}
-
-void Message::setProgress(qreal value)
-{
-    hideFile();
-    hideButton();
-    if (!hasProgress) {
-        hideComment();
-        if (msg.getBody() == msg.getOutOfBandUrl()) {
-            text->setText("");
-            text->hide();
-        }
-        progress = new QProgressBar();
-        progress->setRange(0, 100);
-        bodyLayout->insertWidget(2, progress);
-        hasProgress = true;
-    }
-    progress->setValue(value * 100);
-}
-
-void Message::showFile(const QString& path)
-{
-    hideButton();
-    hideProgress();
-    if (!hasFile) {
-        hideComment();
-        if (msg.getBody() == msg.getOutOfBandUrl()) {
-            text->setText("");
-            text->hide();
-        }
-        QMimeDatabase db;
-        QMimeType type = db.mimeTypeForFile(path);
-        QStringList parts = type.name().split("/");
-        QString big = parts.front();
-        QFileInfo info(path);
-        if (big == "image") {
-            file = new Image(path);
-        } else {
-            file = new QLabel();
-            file->setPixmap(QIcon::fromTheme(type.iconName()).pixmap(50));
-            file->setAlignment(Qt::AlignCenter);
-            showComment(info.fileName(), true);
-        }
-        file->setContextMenuPolicy(Qt::ActionsContextMenu);
-        QAction* openAction = new QAction(QIcon::fromTheme("document-new-from-template"), tr("Open"), file);
-        connect(openAction, &QAction::triggered, [path]() {             //TODO need to get rid of this shame
-            QDesktopServices::openUrl(QUrl::fromLocalFile(path));
-        });
-        file->addAction(openAction);
-        bodyLayout->insertWidget(2, file);
-        hasFile = true;
-    }
-}
-
-void Message::hideComment()
-{
-    if (commentAdded) {
-        bodyLayout->removeWidget(fileComment);
-        fileComment->hide();
-        fileComment->setWordWrap(false);
-        commentAdded = false;
-    }
-}
-
-void Message::hideButton()
-{
-    if (hasButton) {
-        button->deleteLater();
-        button = 0;
-        hasButton = false;
-    }
-}
-
-void Message::hideFile()
-{
-    if (hasFile) {
-        file->deleteLater();
-        file = 0;
-        hasFile = false;
-    }
-}
-
-void Message::hideProgress()
-{
-    if (hasProgress) {
-        progress->deleteLater();
-        progress = 0;
-        hasProgress = false;;
-    }
-}
-void Message::showComment(const QString& comment, bool wordWrap)
-{
-    if (!commentAdded) {
-        int index = 2;
-        if (hasFile) {
-            index++;
-        }
-        if (hasButton) {
-            index++;
-        }
-        if (hasProgress) {
-            index++;
-        }
-        bodyLayout->insertWidget(index, fileComment);
-        fileComment->show();
-        commentAdded = true;
-    }
-    fileComment->setWordWrap(wordWrap);
-    fileComment->setText(comment);
-}
-
-const Shared::Message & Message::getMessage() const
-{
-    return msg;
-}
-
-void Message::setAvatarPath(const QString& p_path)
-{
-    if (p_path.size() == 0) {
-        avatar->setPath(Shared::iconPath("user", true));
-    } else {
-        avatar->setPath(p_path);
-    }
-}
-
-bool Message::change(const QMap<QString, QVariant>& data)
-{
-    bool idChanged = msg.change(data);
-    
-    QString body = msg.getBody();
-    QString bd = Shared::processMessageBody(body);
-    if (body.size() > 0) {
-        text->setText(bd);
-        text->show();
-    } else {
-        text->setText(body);
-        text->hide();
-    }
-    if (msg.getEdited()) {
-        setEdited();
-    }
-    if (hasStatusIcon) {
-        setState();
-    }
-    
-    
-    return idChanged;
-}
-
-void Message::setEdited()
-{
-    if (!hasEditedLabel) {
-        editedLabel = new QLabel();
-        hasEditedLabel = true;
-        QIcon q(Shared::icon("edit-rename"));
-        editedLabel->setPixmap(q.pixmap(12, 12));
-        QHBoxLayout* statusLay = static_cast<QHBoxLayout*>(statusBar->layout());
-        statusLay->insertWidget(1, editedLabel);
-    }
-    editedLabel->setToolTip("Last time edited: " + msg.getLastModified().toLocalTime().toString() 
-    + "\nOriginal message: " + msg.getOriginalBody());
-}
-
-void Message::setState()
-{
-    Shared::Message::State state = msg.getState();
-    QIcon q(Shared::icon(Shared::messageStateThemeIcons[static_cast<uint8_t>(state)]));
-    QString tt = Shared::Global::getName(state);
-    if (state == Shared::Message::State::error) {
-        QString errText = msg.getErrorText();
-        if (errText.size() > 0) {
-            tt += ": " + errText;
-        }
-    }
-    statusIcon->setToolTip(tt);
-    statusIcon->setPixmap(q.pixmap(12, 12));
-}
-
diff --git a/ui/widgets/messageline/message.h b/ui/widgets/messageline/message.h
deleted file mode 100644
index eef93a1..0000000
--- a/ui/widgets/messageline/message.h
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * Squawk messenger. 
- * Copyright (C) 2019 Yury Gubich <blue@macaw.me>
- * 
- * 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/>.
- */
-
-#ifndef MESSAGE_H
-#define MESSAGE_H
-
-#include <QWidget>
-#include <QHBoxLayout>
-#include <QVBoxLayout>
-#include <QLabel>
-#include <QGraphicsDropShadowEffect>
-#include <QPushButton>
-#include <QProgressBar>
-#include <QAction>
-#include <QDesktopServices>
-#include <QUrl>
-#include <QMap>
-
-#include "shared/message.h"
-#include "shared/icons.h"
-#include "shared/global.h"
-#include "shared/utils.h"
-#include "resizer.h"
-#include "image.h"
-
-/**
- * @todo write docs
- */
-class Message : public QWidget
-{
-    Q_OBJECT
-public:
-    Message(const Shared::Message& source, bool outgoing, const QString& sender, const QString& avatarPath = "", QWidget* parent = nullptr);
-    ~Message();
-    
-    void setSender(const QString& sender);
-    QString getId() const;
-    QString getSenderJid() const;
-    QString getSenderResource() const;
-    QString getFileUrl() const;
-    const Shared::Message& getMessage() const;
-    
-    void addButton(const QIcon& icon, const QString& buttonText, const QString& tooltip = "");
-    void showComment(const QString& comment, bool wordWrap = false);
-    void hideComment();
-    void showFile(const QString& path);
-    void setProgress(qreal value);
-    void setAvatarPath(const QString& p_path);
-    bool change(const QMap<QString, QVariant>& data);
-    
-    bool const outgoing;
-    
-signals:
-    void buttonClicked();
-    
-private:
-    Shared::Message msg;
-    QWidget* body;
-    QWidget* statusBar;
-    QVBoxLayout* bodyLayout;
-    QHBoxLayout* layout;
-    QLabel* date;
-    QLabel* sender;
-    QLabel* text;
-    QGraphicsDropShadowEffect* shadow;
-    QPushButton* button;
-    QLabel* file;
-    QProgressBar* progress;
-    QLabel* fileComment;
-    QLabel* statusIcon;
-    QLabel* editedLabel;
-    Image* avatar;
-    bool hasButton;
-    bool hasProgress;
-    bool hasFile;
-    bool commentAdded;
-    bool hasStatusIcon;
-    bool hasEditedLabel;
-  
-private:
-    void hideButton();
-    void hideProgress();
-    void hideFile();
-    void setState();
-    void setEdited();
-};
-
-#endif // MESSAGE_H
diff --git a/ui/widgets/messageline/messageline.cpp b/ui/widgets/messageline/messageline.cpp
deleted file mode 100644
index fec0037..0000000
--- a/ui/widgets/messageline/messageline.cpp
+++ /dev/null
@@ -1,504 +0,0 @@
-/*
- * Squawk messenger. 
- * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
- *
- * 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 "messageline.h"
-#include <QDebug>
-#include <QCoreApplication>
-#include <cmath>
-
-MessageLine::MessageLine(bool p_room, QWidget* parent):
-    QWidget(parent),
-    messageIndex(),
-    messageOrder(),
-    myMessages(),
-    palMessages(),
-    uploadPaths(),
-    palAvatars(),
-    exPalAvatars(),
-    layout(new QVBoxLayout(this)),
-    myName(),
-    myAvatarPath(),
-    palNames(),
-    uploading(),
-    downloading(),
-    room(p_room),
-    busyShown(false),
-    progress()
-{
-    setContentsMargins(0, 0, 0, 0);
-    layout->setContentsMargins(0, 0, 0, 0);
-    layout->setSpacing(0);
-    layout->addStretch();
-}
-
-MessageLine::~MessageLine()
-{
-    for (Index::const_iterator itr = messageIndex.begin(), end = messageIndex.end(); itr != end; ++itr) {
-        delete itr->second;
-    }
-}
-
-MessageLine::Position MessageLine::message(const Shared::Message& msg, bool forceOutgoing)
-{
-    QString id = msg.getId();
-    Index::iterator itr = messageIndex.find(id);
-    if (itr != messageIndex.end()) {
-        qDebug() << "received more then one message with the same id, skipping yet the new one";
-        return invalid;
-    }
-    
-    QString sender;
-    QString aPath;
-    bool outgoing;
-    
-    if (forceOutgoing) {
-        sender = myName;
-        aPath = myAvatarPath;
-        outgoing = true;
-    } else {
-        if (room) {
-            if (msg.getFromResource() == myName) {
-                sender = myName;
-                aPath = myAvatarPath;
-                outgoing = true;
-            } else {
-                sender = msg.getFromResource();
-                std::map<QString, QString>::iterator aItr = palAvatars.find(sender);
-                if (aItr != palAvatars.end()) {
-                    aPath = aItr->second;
-                } else {
-                    aItr = exPalAvatars.find(sender);
-                    if (aItr != exPalAvatars.end()) {
-                        aPath = aItr->second;
-                    }
-                }
-                outgoing = false;
-            }
-        } else {
-            if (msg.getOutgoing()) {
-                sender = myName;
-                aPath = myAvatarPath;
-                outgoing = true;
-            } else {
-                QString jid = msg.getFromJid();
-                std::map<QString, QString>::iterator itr = palNames.find(jid);
-                if (itr != palNames.end()) {
-                    sender = itr->second;
-                } else {
-                    sender = jid;
-                }
-                
-                std::map<QString, QString>::iterator aItr = palAvatars.find(jid);
-                if (aItr != palAvatars.end()) {
-                    aPath = aItr->second;
-                }
-                
-                outgoing = false;
-            }
-        }
-    }
-    
-    Message* message = new Message(msg, outgoing, sender, aPath);
-    
-    std::pair<Order::const_iterator, bool> result = messageOrder.insert(std::make_pair(msg.getTime(), message));
-    if (!result.second) {
-        qDebug() << "Error appending a message into a message list - seems like the time of that message exactly matches the time of some other message, can't put them in order, skipping yet";
-        delete message;
-        return invalid;
-    }
-    if (outgoing) {
-        myMessages.insert(std::make_pair(id, message));
-    } else {
-        QString senderId;
-        if (room) {
-            senderId = sender;
-        } else {
-            senderId = msg.getFromJid();
-        }
-        
-        std::map<QString, Index>::iterator pItr = palMessages.find(senderId);
-        if (pItr == palMessages.end()) {
-            pItr = palMessages.insert(std::make_pair(senderId, Index())).first;
-        }
-        pItr->second.insert(std::make_pair(id, message));
-    }
-    messageIndex.insert(std::make_pair(id, message));
-    unsigned long index = std::distance<Order::const_iterator>(messageOrder.begin(), result.first);   //need to make with binary indexed tree
-    Position res = invalid;
-    if (index == 0) {
-        res = beggining;
-    } else if (index == messageIndex.size() - 1) {
-        res = end;
-    } else {
-        res = middle;
-    }
-    
-    if (busyShown) {
-        index += 1;
-    }
-    
-        
-    if (res == end) {
-        layout->addWidget(message);
-    } else {
-        layout->insertWidget(index + 1, message);
-    }
-    
-    if (msg.hasOutOfBandUrl()) {
-        emit requestLocalFile(msg.getId(), msg.getOutOfBandUrl());
-        connect(message, &Message::buttonClicked, this, &MessageLine::onDownload);
-    }
-    
-    return res;
-}
-
-void MessageLine::changeMessage(const QString& id, const QMap<QString, QVariant>& data)
-{
-    Index::const_iterator itr = messageIndex.find(id);
-    if (itr != messageIndex.end()) {
-        Message* msg = itr->second;
-        if (msg->change(data)) {                    //if ID changed (stanza in replace of another)
-            QString newId = msg->getId();           //need to updated IDs of that message in all maps
-            messageIndex.erase(itr);
-            messageIndex.insert(std::make_pair(newId, msg));
-            if (msg->outgoing) {
-                QString senderId;
-                if (room) {
-                    senderId = msg->getSenderResource();
-                } else {
-                    senderId = msg->getSenderJid();
-                }
-                
-                std::map<QString, Index>::iterator pItr = palMessages.find(senderId);
-                if (pItr != palMessages.end()) {
-                    Index::const_iterator sItr = pItr->second.find(id);
-                    if (sItr != pItr->second.end()) {
-                        pItr->second.erase(sItr);
-                        pItr->second.insert(std::make_pair(newId, msg));
-                    } else {
-                        qDebug() << "Was trying to replace message in open conversations, couldn't find it among pal's messages, probably an error"; 
-                    }
-                } else {
-                    qDebug() << "Was trying to replace message in open conversations, couldn't find pal messages, probably an error"; 
-                }
-            } else {
-                Index::const_iterator mItr = myMessages.find(id);
-                if (mItr != myMessages.end()) {
-                    myMessages.erase(mItr);
-                    myMessages.insert(std::make_pair(newId, msg));
-                } else {
-                    qDebug() << "Was trying to replace message in open conversations, couldn't find it among my messages, probably an error"; 
-                }
-            }
-        }
-    }
-}
-
-void MessageLine::onDownload()
-{
-    Message* msg = static_cast<Message*>(sender());
-    QString messageId = msg->getId();
-    Index::const_iterator itr = downloading.find(messageId);
-    if (itr == downloading.end()) {
-        downloading.insert(std::make_pair(messageId, msg));
-        msg->setProgress(0);
-        msg->showComment(tr("Downloading..."));
-        emit downloadFile(messageId, msg->getFileUrl());
-    } else {
-        qDebug() << "An attempt to initiate download for already downloading file" << msg->getFileUrl() << ", skipping";
-    }
-}
-
-void MessageLine::setMyName(const QString& name)
-{
-    myName = name;
-    for (Index::const_iterator itr = myMessages.begin(), end = myMessages.end(); itr != end; ++itr) {
-        itr->second->setSender(name);
-    }
-}
-
-void MessageLine::setPalName(const QString& jid, const QString& name)
-{
-    std::map<QString, QString>::iterator itr = palNames.find(jid);
-    if (itr == palNames.end()) {
-        palNames.insert(std::make_pair(jid, name));
-    } else {
-        itr->second = name;
-    }
-    
-    std::map<QString, Index>::iterator pItr = palMessages.find(jid);
-    if (pItr != palMessages.end()) {
-        for (Index::const_iterator itr = pItr->second.begin(), end = pItr->second.end(); itr != end; ++itr) {
-            itr->second->setSender(name);
-        }
-    }
-}
-
-void MessageLine::setPalAvatar(const QString& jid, const QString& path)
-{
-    std::map<QString, QString>::iterator itr = palAvatars.find(jid);
-    if (itr == palAvatars.end()) {
-        palAvatars.insert(std::make_pair(jid, path));
-        
-        std::map<QString, QString>::const_iterator eitr = exPalAvatars.find(jid);
-        if (eitr != exPalAvatars.end()) {
-            exPalAvatars.erase(eitr);
-        }
-    } else {
-        itr->second = path;
-    }
-    
-    std::map<QString, Index>::iterator pItr = palMessages.find(jid);
-    if (pItr != palMessages.end()) {
-        for (Index::const_iterator itr = pItr->second.begin(), end = pItr->second.end(); itr != end; ++itr) {
-            itr->second->setAvatarPath(path);
-        }
-    }
-}
-
-void MessageLine::dropPalAvatar(const QString& jid)
-{
-    std::map<QString, QString>::iterator itr = palAvatars.find(jid);
-    if (itr != palAvatars.end()) {
-        palAvatars.erase(itr);
-    }
-    
-    std::map<QString, QString>::const_iterator eitr = exPalAvatars.find(jid);
-    if (eitr != exPalAvatars.end()) {
-        exPalAvatars.erase(eitr);
-    }
-    
-    std::map<QString, Index>::iterator pItr = palMessages.find(jid);
-    if (pItr != palMessages.end()) {
-        for (Index::const_iterator itr = pItr->second.begin(), end = pItr->second.end(); itr != end; ++itr) {
-            itr->second->setAvatarPath("");
-        }
-    }
-}
-
-void MessageLine::movePalAvatarToEx(const QString& name)
-{
-    std::map<QString, QString>::iterator itr = palAvatars.find(name);
-    if (itr != palAvatars.end()) {
-        std::map<QString, QString>::iterator eitr = exPalAvatars.find(name);
-        if (eitr != exPalAvatars.end()) {
-            eitr->second = itr->second;
-        } else {
-            exPalAvatars.insert(std::make_pair(name, itr->second));
-        }
-        
-        palAvatars.erase(itr);
-    }
-}
-
-void MessageLine::resizeEvent(QResizeEvent* event)
-{
-    QWidget::resizeEvent(event);
-    emit resize(event->size().height() - event->oldSize().height());
-}
-
-
-QString MessageLine::firstMessageId() const
-{
-    if (messageOrder.size() == 0) {
-        return "";
-    } else {
-        return messageOrder.begin()->second->getId();
-    }
-}
-
-void MessageLine::showBusyIndicator()
-{
-    if (!busyShown)  {
-        layout->insertWidget(0, &progress);
-        progress.start();
-        busyShown = true;
-    }
-}
-
-void MessageLine::hideBusyIndicator()
-{
-    if (busyShown) {
-        progress.stop();
-        layout->removeWidget(&progress);
-        busyShown = false;
-    }
-}
-
-void MessageLine::fileProgress(const QString& messageId, qreal progress)
-{
-    Index::const_iterator itr = messageIndex.find(messageId);
-    if (itr == messageIndex.end()) {
-        //TODO may be some logging, that's not normal
-    } else {
-        itr->second->setProgress(progress);
-    }
-}
-
-void MessageLine::responseLocalFile(const QString& messageId, const QString& path)
-{
-    Index::const_iterator itr = messageIndex.find(messageId);
-    if (itr == messageIndex.end()) {
-        
-    } else {
-        Index::const_iterator uItr = uploading.find(messageId);
-        if (path.size() > 0) {
-            Index::const_iterator dItr = downloading.find(messageId);
-            if (dItr != downloading.end()) {
-                downloading.erase(dItr);
-                itr->second->showFile(path);
-            } else {
-                if (uItr != uploading.end()) {
-                    uploading.erase(uItr);
-                    std::map<QString, QString>::const_iterator muItr = uploadPaths.find(messageId);
-                    if (muItr != uploadPaths.end()) {
-                        uploadPaths.erase(muItr);
-                    }
-                    Shared::Message msg = itr->second->getMessage();
-                    removeMessage(messageId);
-                    msg.setCurrentTime();
-                    message(msg);
-                    itr = messageIndex.find(messageId);
-                    itr->second->showFile(path);
-                } else {
-                    itr->second->showFile(path); //then it is already cached file
-                }
-            }
-        } else {
-            if (uItr == uploading.end()) {
-                const Shared::Message& msg = itr->second->getMessage();
-                itr->second->addButton(QIcon::fromTheme("download"), tr("Download"), "<a href=\"" + msg.getOutOfBandUrl() + "\">" + msg.getOutOfBandUrl() + "</a>");
-                itr->second->showComment(tr("Push the button to download the file"));
-            } else {
-                qDebug() << "An unhandled state for file uploading - empty path";
-            }
-        }
-    }
-}
-
-void MessageLine::removeMessage(const QString& messageId)
-{
-    Index::const_iterator itr = messageIndex.find(messageId);
-    if (itr != messageIndex.end()) {
-        Message* ui = itr->second;
-        const Shared::Message& msg = ui->getMessage();
-        messageIndex.erase(itr);
-        Order::const_iterator oItr = messageOrder.find(msg.getTime());
-        if (oItr != messageOrder.end()) {
-            messageOrder.erase(oItr);
-        } else {
-            qDebug() << "An attempt to remove message from messageLine, but it wasn't found in order";
-        }
-        if (msg.getOutgoing()) {
-            Index::const_iterator mItr = myMessages.find(messageId);
-            if (mItr != myMessages.end()) {
-                myMessages.erase(mItr);
-            } else {
-                qDebug() << "Error removing message: it seems to be outgoing yet it wasn't found in outgoing messages";
-            }
-        } else {
-            if (room) {
-            
-            } else {
-                QString jid = msg.getFromJid();
-                std::map<QString, Index>::iterator pItr = palMessages.find(jid);
-                if (pItr != palMessages.end()) {
-                    Index& pMsgs = pItr->second;
-                    Index::const_iterator pmitr = pMsgs.find(messageId);
-                    if (pmitr != pMsgs.end()) {
-                        pMsgs.erase(pmitr);
-                    } else {
-                        qDebug() << "Error removing message: it seems to be incoming yet it wasn't found among messages from that penpal";
-                    }
-                }
-            }
-        }
-        ui->deleteLater();
-        qDebug() << "message" << messageId << "has been removed";
-    } else {
-        qDebug() << "An attempt to remove non existing message from messageLine";
-    }
-}
-
-void MessageLine::fileError(const QString& messageId, const QString& error)
-{
-    Index::const_iterator itr = downloading.find(messageId);
-    if (itr == downloading.end()) {
-        Index::const_iterator itr = uploading.find(messageId);
-        if (itr == uploading.end()) {
-            //TODO may be some logging, that's not normal
-        } else {
-            itr->second->showComment(tr("Error uploading file: %1\nYou can try again").arg(QCoreApplication::translate("NetworkErrors", error.toLatin1())), true);
-            itr->second->addButton(QIcon::fromTheme("upload"), tr("Upload"));
-        }
-    } else {
-        const Shared::Message& msg = itr->second->getMessage();
-        itr->second->addButton(QIcon::fromTheme("download"), tr("Download"), "<a href=\"" + msg.getOutOfBandUrl() + "\">" + msg.getOutOfBandUrl() + "</a>");
-        itr->second->showComment(tr("Error downloading file: %1\nYou can try again").arg(QCoreApplication::translate("NetworkErrors", error.toLatin1())), true);
-    }
-}
-
-void MessageLine::appendMessageWithUpload(const Shared::Message& msg, const QString& path)
-{
-    appendMessageWithUploadNoSiganl(msg, path);
-    emit uploadFile(msg, path);
-}
-
-void MessageLine::appendMessageWithUploadNoSiganl(const Shared::Message& msg, const QString& path)
-{
-    message(msg, true);
-    QString id = msg.getId();
-    Message* ui = messageIndex.find(id)->second;
-    connect(ui, &Message::buttonClicked, this, &MessageLine::onUpload);     //this is in case of retry;
-    ui->setProgress(0);
-    ui->showComment(tr("Uploading..."));
-    uploading.insert(std::make_pair(id, ui));
-    uploadPaths.insert(std::make_pair(id, path));
-}
-
-
-void MessageLine::onUpload()
-{
-    //TODO retry
-}
-
-void MessageLine::setMyAvatarPath(const QString& p_path)
-{
-    if (myAvatarPath != p_path) {
-        myAvatarPath = p_path;
-        for (std::pair<QString, Message*> pair : myMessages) {
-            pair.second->setAvatarPath(myAvatarPath);
-        }
-    }
-}
-
-void MessageLine::setExPalAvatars(const std::map<QString, QString>& data)
-{
-    exPalAvatars = data;
-    
-    for (const std::pair<QString, Index>& pair : palMessages) {
-        if (palAvatars.find(pair.first) == palAvatars.end()) {
-            std::map<QString, QString>::const_iterator eitr = exPalAvatars.find(pair.first);
-            if (eitr != exPalAvatars.end()) {
-                for (const std::pair<QString, Message*>& mp : pair.second) {
-                    mp.second->setAvatarPath(eitr->second);
-                }
-            }
-        }
-    }
-}
diff --git a/ui/widgets/messageline/messageline.h b/ui/widgets/messageline/messageline.h
deleted file mode 100644
index a0a7b6c..0000000
--- a/ui/widgets/messageline/messageline.h
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * Squawk messenger. 
- * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
- *
- * 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/>.
- */
-
-#ifndef MESSAGELINE_H
-#define MESSAGELINE_H
-
-#include <QWidget>
-#include <QVBoxLayout>
-#include <QHBoxLayout>
-#include <QLabel>
-#include <QResizeEvent>
-#include <QIcon>
-
-#include "shared/message.h"
-#include "message.h"
-#include "progress.h"
-
-class MessageLine : public QWidget
-{
-    Q_OBJECT
-public:
-    enum Position {
-        beggining,
-        middle,
-        end,
-        invalid
-    };
-    MessageLine(bool p_room, QWidget* parent = 0);
-    ~MessageLine();
-    
-    Position message(const Shared::Message& msg, bool forceOutgoing = false);
-    void setMyName(const QString& name);
-    void setPalName(const QString& jid, const QString& name);
-    QString firstMessageId() const;
-    void showBusyIndicator();
-    void hideBusyIndicator();
-    void responseLocalFile(const QString& messageId, const QString& path);
-    void fileError(const QString& messageId, const QString& error);
-    void fileProgress(const QString& messageId, qreal progress);
-    void appendMessageWithUpload(const Shared::Message& msg, const QString& path);
-    void appendMessageWithUploadNoSiganl(const Shared::Message& msg, const QString& path);
-    void removeMessage(const QString& messageId);
-    void setMyAvatarPath(const QString& p_path);
-    void setPalAvatar(const QString& jid, const QString& path);
-    void dropPalAvatar(const QString& jid);
-    void changeMessage(const QString& id, const QMap<QString, QVariant>& data);
-    void setExPalAvatars(const std::map<QString, QString>& data);
-    void movePalAvatarToEx(const QString& name);
-    
-signals:
-    void resize(int amount);
-    void downloadFile(const QString& messageId, const QString& url);
-    void uploadFile(const Shared::Message& msg, const QString& path);
-    void requestLocalFile(const QString& messageId, const QString& url);
-    
-protected:
-    void resizeEvent(QResizeEvent * event) override;
-    
-protected:
-    void onDownload();
-    void onUpload();
-    
-private:
-    struct Comparator {
-        bool operator()(const Shared::Message& a, const Shared::Message& b) const {
-            return a.getTime() < b.getTime();
-        }
-        bool operator()(const Shared::Message* a, const Shared::Message* b) const {
-            return a->getTime() < b->getTime();
-        }
-    };
-    typedef std::map<QDateTime, Message*> Order;
-    typedef std::map<QString, Message*> Index;
-    Index messageIndex;
-    Order messageOrder;
-    Index myMessages;
-    std::map<QString, Index> palMessages;
-    std::map<QString, QString> uploadPaths;
-    std::map<QString, QString> palAvatars;
-    std::map<QString, QString> exPalAvatars;
-    QVBoxLayout* layout;
-    
-    QString myName;
-    QString myAvatarPath;
-    std::map<QString, QString> palNames;
-    Index uploading;
-    Index downloading;
-    bool room;
-    bool busyShown;
-    Progress progress;
-};
-
-#endif // MESSAGELINE_H

From bf4a27f35d2761485c201083d210ce3aca11559c Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Sun, 27 Mar 2022 22:05:31 +0300
Subject: [PATCH 172/281] Bug with the edited message fixed, some further work
 on message correction

---
 shared/message.cpp                     |  2 +-
 ui/models/roster.cpp                   |  2 ++
 ui/widgets/conversation.cpp            | 30 ++++++++++++++++++++++++--
 ui/widgets/conversation.h              |  1 +
 ui/widgets/messageline/messagefeed.cpp | 12 +++++++++++
 ui/widgets/messageline/messagefeed.h   | 17 +++++++++++++++
 6 files changed, 61 insertions(+), 3 deletions(-)

diff --git a/shared/message.cpp b/shared/message.cpp
index e6b47b2..f3f6b45 100644
--- a/shared/message.cpp
+++ b/shared/message.cpp
@@ -406,7 +406,7 @@ bool Shared::Message::change(const QMap<QString, QVariant>& data)
             if (!edited || lastModified < correctionDate) {
                 originalMessage = body;
                 lastModified = correctionDate;
-                setBody(body);
+                setBody(b);
                 setEdited(true);
             }
         }
diff --git a/ui/models/roster.cpp b/ui/models/roster.cpp
index 2d5f99f..588fb1d 100644
--- a/ui/models/roster.cpp
+++ b/ui/models/roster.cpp
@@ -549,6 +549,8 @@ void Models::Roster::changeMessage(const QString& account, const QString& jid, c
     Element* el = getElement({account, jid});
     if (el != NULL) {
         el->changeMessage(id, data);
+    } else {
+        qDebug() << "A request to change a message of the contact " << jid << " in the account " << account << " but it wasn't found";
     }
 }
 
diff --git a/ui/widgets/conversation.cpp b/ui/widgets/conversation.cpp
index 07c599e..608faf3 100644
--- a/ui/widgets/conversation.cpp
+++ b/ui/widgets/conversation.cpp
@@ -110,7 +110,7 @@ Conversation::Conversation(bool muc, Models::Account* acc, Models::Element* el,
     
     initializeOverlay();
 
-     m_ui->currentActionBadge->setVisible(false);;
+     m_ui->currentActionBadge->setVisible(false);
 //     m_ui->currentActionBadge->setText(tr("Editing message..."));
 }
 
@@ -476,10 +476,10 @@ void Conversation::onFeedContext(const QPoint& pos)
         Shared::Message* item = static_cast<Shared::Message*>(index.internalPointer());
         
         contextMenu->clear();
+        QString id = item->getId();
         bool showMenu = false;
         if (item->getState() == Shared::Message::State::error) {
             showMenu = true;
-            QString id = item->getId();
             QAction* resend = contextMenu->addAction(Shared::icon("view-refresh"), tr("Try sending again")); 
             connect(resend, &QAction::triggered, [this, id]() {
                 element->feed->registerUpload(id);
@@ -500,6 +500,12 @@ void Conversation::onFeedContext(const QPoint& pos)
                 Shared::Global::highlightInFileManager(path);
             });
         }
+
+        if (item->getOutgoing()) {
+            showMenu = true;
+            QAction* edit = contextMenu->addAction(Shared::icon("edit-rename"), tr("Edit"));
+            connect(edit, &QAction::triggered, this, std::bind(&Conversation::onMessageEditRequested, this, id));
+        }
         
         if (showMenu) {
             contextMenu->popup(feed->viewport()->mapToGlobal(pos));
@@ -517,3 +523,23 @@ void Conversation::onMessageEditorContext(const QPoint& pos)
 
     editorMenu->exec(this->m_ui->messageEditor->mapToGlobal(pos));
 }
+
+void Conversation::onMessageEditRequested(const QString& id)
+{
+    if (currentAction == CurrentAction::edit) {
+        //todo;
+    }
+
+    try {
+        Shared::Message msg = element->feed->getMessage(id);
+
+        m_ui->currentActionBadge->setVisible(true);
+        m_ui->currentActionBadge->setText(tr("Editing message..."));
+        currentAction = CurrentAction::edit;
+        m_ui->messageEditor->setText(msg.getBody());
+
+    } catch (const Models::MessageFeed::NotFound& e) {
+        qDebug() << "The message requested to be edited was not found" << e.getMessage().c_str();
+        qDebug() << "Ignoring";
+    }
+}
diff --git a/ui/widgets/conversation.h b/ui/widgets/conversation.h
index c43d7a0..4bccdfc 100644
--- a/ui/widgets/conversation.h
+++ b/ui/widgets/conversation.h
@@ -116,6 +116,7 @@ protected slots:
     void positionShadow();
     void onFeedContext(const QPoint &pos);
     void onMessageEditorContext(const QPoint &pos);
+    void onMessageEditRequested(const QString& id);
     
 public:
     const bool isMuc;
diff --git a/ui/widgets/messageline/messagefeed.cpp b/ui/widgets/messageline/messagefeed.cpp
index 4803dce..33fbdd4 100644
--- a/ui/widgets/messageline/messagefeed.cpp
+++ b/ui/widgets/messageline/messagefeed.cpp
@@ -224,8 +224,20 @@ std::set<Models::MessageFeed::MessageRoles> Models::MessageFeed::detectChanges(c
 
 void Models::MessageFeed::removeMessage(const QString& id)
 {
+    //todo;
 }
 
+Shared::Message Models::MessageFeed::getMessage(const QString& id)
+{
+    StorageById::iterator itr = indexById.find(id);
+    if (itr == indexById.end()) {
+        throw NotFound(id.toStdString(), rosterItem->getJid().toStdString(), rosterItem->getAccountName().toStdString());
+    }
+
+    return **itr;
+}
+
+
 QVariant Models::MessageFeed::data(const QModelIndex& index, int role) const
 {
     int i = index.row();
diff --git a/ui/widgets/messageline/messagefeed.h b/ui/widgets/messageline/messagefeed.h
index 2273b15..c9701ae 100644
--- a/ui/widgets/messageline/messagefeed.h
+++ b/ui/widgets/messageline/messagefeed.h
@@ -32,6 +32,7 @@
 
 #include <shared/message.h>
 #include <shared/icons.h>
+#include <shared/exception.h>
 
 
 namespace Models {
@@ -55,6 +56,7 @@ public:
     void addMessage(const Shared::Message& msg);
     void changeMessage(const QString& id, const QMap<QString, QVariant>& data);
     void removeMessage(const QString& id);
+    Shared::Message getMessage(const QString& id);
     
     QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const override;
     int rowCount(const QModelIndex& parent = QModelIndex()) const override;
@@ -103,6 +105,21 @@ public:
         Error,
         Bulk
     };
+
+    class NotFound:
+    public Utils::Exception
+    {
+    public:
+        NotFound(const std::string& k, const std::string& j, const std::string& acc):Exception(), key(k), jid(j), account(acc){}
+
+        std::string getMessage() const {
+            return "Message with id " + key + " wasn't found in messageFeed " + account + " of the chat with " + jid;
+        }
+    private:
+        std::string key;
+        std::string jid;
+        std::string account;
+    };
     
 protected:
     bool sentByMe(const Shared::Message& msg) const;

From 788c6ca5567563322d3d5afd7943e672e7cdc0f1 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Mon, 28 Mar 2022 23:25:33 +0300
Subject: [PATCH 173/281] now it's possible to fix your messages

---
 CHANGELOG.md                     |   2 +
 core/account.cpp                 |   4 +
 core/account.h                   |   1 +
 core/handlers/messagehandler.cpp | 192 ++++++++++++++++++++-----------
 core/handlers/messagehandler.h   |   8 +-
 core/main.cpp                    |   1 +
 core/squawk.cpp                  |  11 ++
 core/squawk.h                    |   1 +
 shared/message.cpp               |   4 +-
 ui/squawk.cpp                    |  12 ++
 ui/squawk.h                      |   2 +
 ui/widgets/conversation.cpp      |  44 ++++---
 ui/widgets/conversation.h        |   7 +-
 13 files changed, 204 insertions(+), 85 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8a6fdfa..efb159c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,7 @@
 ### Bug fixes
 - build in release mode now no longer spams warnings
 - build now correctly installs all build plugin libs
+- a bug where the correction message was received, the indication was on but the text didn't actually change
 
 ### Improvements
 - reduced amount of places where platform specific path separator is used
@@ -13,6 +14,7 @@
 - now it's possible to set up different qt styles from settings
 - if you have KConfig nad KConfigWidgets packages installed - you can chose from global color schemes
 - it's possible now to chose a folder where squawk is going to store downloaded files
+- now you can correct your message
 
 ## Squawk 0.2.0 (Jan 10, 2022)
 ### Bug fixes
diff --git a/core/account.cpp b/core/account.cpp
index a923690..91d0f2b 100644
--- a/core/account.cpp
+++ b/core/account.cpp
@@ -935,3 +935,7 @@ void Core::Account::requestChangeMessage(const QString& jid, const QString& mess
 
 void Core::Account::resendMessage(const QString& jid, const QString& id) {
     mh->resendMessage(jid, id);}
+
+void Core::Account::replaceMessage(const QString& originalId, const Shared::Message& data) {
+    mh->sendMessage(data, false, originalId);}
+
diff --git a/core/account.h b/core/account.h
index 5ba834c..664b547 100644
--- a/core/account.h
+++ b/core/account.h
@@ -104,6 +104,7 @@ public:
     void addRoomRequest(const QString& jid, const QString& nick, const QString& password, bool autoJoin);
     void uploadVCard(const Shared::VCard& card);
     void resendMessage(const QString& jid, const QString& id);
+    void replaceMessage(const QString& originalId, const Shared::Message& data);
     
 public slots:
     void connect();
diff --git a/core/handlers/messagehandler.cpp b/core/handlers/messagehandler.cpp
index 1e89dd6..559bee3 100644
--- a/core/handlers/messagehandler.cpp
+++ b/core/handlers/messagehandler.cpp
@@ -41,10 +41,10 @@ void Core::MessageHandler::onMessageReceived(const QXmppMessage& msg)
             handled = handleGroupMessage(msg);
             break;
         case QXmppMessage::Error: {
-            QString id = msg.id();
-            std::map<QString, QString>::const_iterator itr = pendingStateMessages.find(id);
-            if (itr != pendingStateMessages.end()) {
-                QString jid = itr->second;
+            std::tuple<bool, QString, QString> ids = getOriginalPendingMessageId(msg.id());
+            if (std::get<0>(ids)) {
+                QString id = std::get<1>(ids);
+                QString jid = std::get<2>(ids);
                 RosterItem* cnt = acc->rh->getRosterItem(jid);
                 QMap<QString, QVariant> cData = {
                     {"state", static_cast<uint>(Shared::Message::State::error)},
@@ -53,9 +53,7 @@ void Core::MessageHandler::onMessageReceived(const QXmppMessage& msg)
                 if (cnt != 0) {
                     cnt->changeMessage(id, cData);
                 }
-                ;
                 emit acc->changeMessage(jid, id, cData);
-                pendingStateMessages.erase(itr);
                 handled = true;
             } else {
                 qDebug() << "received a message with type \"Error\", not sure what to do with it now, skipping";
@@ -111,7 +109,6 @@ bool Core::MessageHandler::handleGroupMessage(const QXmppMessage& msg, bool outg
 {
     const QString& body(msg.body());
     if (body.size() != 0) {
-        QString id = msg.id();
         
         Shared::Message sMsg(Shared::Message::groupChat);
         initializeMessage(sMsg, msg, outgoing, forwarded, guessing);
@@ -121,12 +118,11 @@ bool Core::MessageHandler::handleGroupMessage(const QXmppMessage& msg, bool outg
             return false;
         }
         
-        std::map<QString, QString>::const_iterator pItr = pendingStateMessages.find(id);
-        if (pItr != pendingStateMessages.end()) {
+        std::tuple<bool, QString, QString> ids = getOriginalPendingMessageId(msg.id());
+        if (std::get<0>(ids)) {
             QMap<QString, QVariant> cData = {{"state", static_cast<uint>(Shared::Message::State::delivered)}};
-            cnt->changeMessage(id, cData);
-            pendingStateMessages.erase(pItr);
-            emit acc->changeMessage(jid, id, cData);
+            cnt->changeMessage(std::get<1>(ids), cData);
+            emit acc->changeMessage(std::get<2>(ids), std::get<1>(ids), cData);
         } else {
             QString oId = msg.replaceId();
             if (oId.size() > 0) {
@@ -227,53 +223,70 @@ void Core::MessageHandler::onCarbonMessageSent(const QXmppMessage& msg)
     handleChatMessage(msg, true, true);
 }
 
-void Core::MessageHandler::onReceiptReceived(const QString& jid, const QString& id)
+std::tuple<bool, QString, QString> Core::MessageHandler::getOriginalPendingMessageId(const QString& id)
 {
+    std::tuple<bool, QString, QString> result({false, "", ""});
     std::map<QString, QString>::const_iterator itr = pendingStateMessages.find(id);
     if (itr != pendingStateMessages.end()) {
-        QMap<QString, QVariant> cData = {{"state", static_cast<uint>(Shared::Message::State::delivered)}};
-        RosterItem* ri = acc->rh->getRosterItem(itr->second);
-        if (ri != 0) {
-            ri->changeMessage(id, cData);
+        std::get<0>(result) = true;
+        std::get<2>(result) = itr->second;
+
+        std::map<QString, QString>::const_iterator itrC = pendingCorrectionMessages.find(id);
+        if (itrC != pendingCorrectionMessages.end()) {
+            std::get<1>(result) = itrC->second;
+            pendingCorrectionMessages.erase(itrC);
+        } else {
+            std::get<1>(result) = itr->first;
         }
-        emit acc->changeMessage(itr->second, id, cData);
+
         pendingStateMessages.erase(itr);
     }
+
+    return result;
 }
 
-void Core::MessageHandler::sendMessage(const Shared::Message& data, bool newMessage)
+void Core::MessageHandler::onReceiptReceived(const QString& jid, const QString& id)
 {
-    if (data.getOutOfBandUrl().size() == 0 && data.getAttachPath().size() > 0) {
-        prepareUpload(data, newMessage);
-    } else {
-        performSending(data, newMessage);
+    std::tuple<bool, QString, QString> ids = getOriginalPendingMessageId(id);
+    if (std::get<0>(ids)) {
+        QMap<QString, QVariant> cData = {{"state", static_cast<uint>(Shared::Message::State::delivered)}};
+        RosterItem* ri = acc->rh->getRosterItem(std::get<2>(ids));
+
+        if (ri != 0) {
+            ri->changeMessage(std::get<1>(ids), cData);
+        }
+        emit acc->changeMessage(std::get<2>(ids), std::get<1>(ids), cData);
     }
 }
 
-void Core::MessageHandler::performSending(Shared::Message data, bool newMessage)
+void Core::MessageHandler::sendMessage(const Shared::Message& data, bool newMessage, QString originalId)
+{
+    if (data.getOutOfBandUrl().size() == 0 && data.getAttachPath().size() > 0) {
+        pendingCorrectionMessages.insert(std::make_pair(data.getId(), originalId));
+        prepareUpload(data, newMessage);
+    } else {
+        performSending(data, originalId, newMessage);
+    }
+}
+
+void Core::MessageHandler::performSending(Shared::Message data, const QString& originalId, bool newMessage)
 {
     QString jid = data.getPenPalJid();
     QString id = data.getId();
-    QString oob = data.getOutOfBandUrl();
+    qDebug() << "Sending message with id:" << id;
+    if (originalId.size() > 0) {
+        qDebug() << "To replace one with id:" << originalId;
+    }
     RosterItem* ri = acc->rh->getRosterItem(jid);
     bool sent = false;
-    QMap<QString, QVariant> changes;
+    if (newMessage && originalId.size() > 0) {
+        newMessage = false;
+    }
     QDateTime sendTime = QDateTime::currentDateTimeUtc();
     if (acc->state == Shared::ConnectionState::connected) {
-        QXmppMessage msg(acc->getFullJid(), data.getTo(), data.getBody(), data.getThread());
-        
-#if (QXMPP_VERSION) >= QT_VERSION_CHECK(1, 3, 0)
-        msg.setOriginId(id);
-#endif
-        msg.setId(id);
-        msg.setType(static_cast<QXmppMessage::Type>(data.getType()));       //it is safe here, my type is compatible
-        msg.setOutOfBandUrl(oob);
-        msg.setReceiptRequested(true);
-        msg.setStamp(sendTime);
+        QXmppMessage msg(createPacket(data, sendTime, originalId));
         
         sent = acc->client.sendPacket(msg);
-        //sent = false;
-        
         if (sent) {
             data.setState(Shared::Message::State::sent);
         } else {
@@ -286,6 +299,39 @@ void Core::MessageHandler::performSending(Shared::Message data, bool newMessage)
         data.setErrorText("You are is offline or reconnecting");
     }
     
+    QMap<QString, QVariant> changes(getChanges(data, sendTime, newMessage, originalId));
+    
+    QString realId;
+    if (originalId.size() > 0) {
+        realId = originalId;
+    } else {
+        realId = id;
+    }
+    if (ri != 0) {
+        if (newMessage) {
+            ri->appendMessageToArchive(data);
+        } else {
+            ri->changeMessage(realId, changes);
+        }
+        if (sent) {
+            pendingStateMessages.insert(std::make_pair(id, jid));
+            if (originalId.size() > 0) {
+                pendingCorrectionMessages.insert(std::make_pair(id, originalId));
+            }
+        } else {
+            pendingStateMessages.erase(id);
+            pendingCorrectionMessages.erase(id);
+        }
+    }
+    
+    emit acc->changeMessage(jid, realId, changes);
+}
+
+QMap<QString, QVariant> Core::MessageHandler::getChanges(Shared::Message& data, const QDateTime& time, bool newMessage, const QString& originalId) const
+{
+    QMap<QString, QVariant> changes;
+
+    QString oob = data.getOutOfBandUrl();
     Shared::Message::State mstate = data.getState();
     changes.insert("state", static_cast<uint>(mstate));
     if (mstate == Shared::Message::State::error) {
@@ -295,9 +341,12 @@ void Core::MessageHandler::performSending(Shared::Message data, bool newMessage)
         changes.insert("outOfBandUrl", oob);
     }
     if (newMessage) {
-        data.setTime(sendTime);
+        data.setTime(time);
     }
-    changes.insert("stamp", sendTime);
+    if (originalId.size() > 0) {
+        changes.insert("body", data.getBody());
+    }
+    changes.insert("stamp", time);
 
     //sometimes (when the image is pasted with ctrl+v)
     //I start sending message with one path, then copy it to downloads directory
@@ -310,21 +359,29 @@ void Core::MessageHandler::performSending(Shared::Message data, bool newMessage)
             data.setAttachPath(squawkified);
         }
     }
-    
-    if (ri != 0) {
-        if (newMessage) {
-            ri->appendMessageToArchive(data);
-        } else {
-            ri->changeMessage(id, changes);
-        }
-        if (sent) {
-            pendingStateMessages.insert(std::make_pair(id, jid));
-        } else {
-            pendingStateMessages.erase(id);
-        }
+
+    return changes;
+}
+
+QXmppMessage Core::MessageHandler::createPacket(const Shared::Message& data, const QDateTime& time, const QString& originalId) const
+{
+    QXmppMessage msg(acc->getFullJid(), data.getTo(), data.getBody(), data.getThread());
+    QString id(data.getId());
+
+    if (originalId.size() > 0) {
+        msg.setReplaceId(originalId);
     }
-    
-    emit acc->changeMessage(jid, id, changes);
+
+#if (QXMPP_VERSION) >= QT_VERSION_CHECK(1, 3, 0)
+    msg.setOriginId(id);
+#endif
+    msg.setId(id);
+    msg.setType(static_cast<QXmppMessage::Type>(data.getType()));       //it is safe here, my type is compatible
+    msg.setOutOfBandUrl(data.getOutOfBandUrl());
+    msg.setReceiptRequested(true);
+    msg.setStamp(time);
+
+    return msg;
 }
 
 void Core::MessageHandler::prepareUpload(const Shared::Message& data, bool newMessage)
@@ -444,7 +501,8 @@ void Core::MessageHandler::onLoadFileError(const std::list<Shared::MessageInfo>&
 void Core::MessageHandler::handleUploadError(const QString& jid, const QString& messageId, const QString& errorText)
 {
     emit acc->uploadFileError(jid, messageId, "Error requesting slot to upload file: " + errorText);
-    pendingStateMessages.erase(jid);
+    pendingStateMessages.erase(messageId);
+    pendingCorrectionMessages.erase(messageId);
     requestChangeMessage(jid, messageId, {
         {"state", static_cast<uint>(Shared::Message::State::error)},
         {"errorText", errorText}
@@ -473,11 +531,11 @@ void Core::MessageHandler::sendMessageWithLocalUploadedFile(Shared::Message msg,
     if (msg.getBody().size() == 0) {    //not sure why, but most messages do that
         msg.setBody(url);               //they duplicate oob in body, some of them wouldn't even show an attachment if you don't do that
     }                                   
-    performSending(msg, newMessage);
+    performSending(msg, pendingCorrectionMessages.at(msg.getId()), newMessage);
     //TODO removal/progress update
 }
 
-static const std::set<QString> allowerToChangeKeys({
+static const std::set<QString> allowedToChangeKeys({
     "attachPath",
     "outOfBandUrl",
     "state",
@@ -490,12 +548,12 @@ void Core::MessageHandler::requestChangeMessage(const QString& jid, const QStrin
     if (cnt != 0) {
         bool allSupported = true;
         QString unsupportedString;
-        for (QMap<QString, QVariant>::const_iterator itr = data.begin(); itr != data.end(); ++itr) {        //I need all this madness 
-            if (allowerToChangeKeys.count(itr.key()) != 1) {                                                //to not allow this method
-                allSupported = false;                                                                       //to make a message to look like if it was edited
-                unsupportedString = itr.key();                                                              //basically I needed to control who exaclty calls this method
-                break;                                                                                      //because the underlying tech assumes that the change is initiated by user
-            }                                                                                               //not by system
+        for (QMap<QString, QVariant>::const_iterator itr = data.begin(); itr != data.end(); ++itr) {    //I need all this madness
+            if (allowedToChangeKeys.count(itr.key()) != 1) {                                            //to not allow this method
+                allSupported = false;                                                                   //to make a message to look like if it was edited
+                unsupportedString = itr.key();                                                          //basically I needed to control who exaclty calls this method
+                break;                                                                                  //because the underlying tech assumes that
+            }                                                                                           //the change is initiated by user, not by system
         }
         if (allSupported) {
             cnt->changeMessage(messageId, data);
@@ -514,7 +572,13 @@ void Core::MessageHandler::resendMessage(const QString& jid, const QString& id)
         try {
             Shared::Message msg = cnt->getMessage(id);
             if (msg.getState() == Shared::Message::State::error) {
-                sendMessage(msg, false);
+                if (msg.getEdited()){
+                    QString originalId = msg.getId();
+                    msg.generateRandomId();
+                    sendMessage(msg, false, originalId);
+                } else {
+                    sendMessage(msg, false);
+                }
             } else {
                 qDebug() << "An attempt to resend a message to" << jid << "by account" << acc->getName() << ", but this message seems to have been normally sent, this method was made to retry sending failed to be sent messages, skipping";
             }
diff --git a/core/handlers/messagehandler.h b/core/handlers/messagehandler.h
index 4f03484..1ab2d0d 100644
--- a/core/handlers/messagehandler.h
+++ b/core/handlers/messagehandler.h
@@ -46,7 +46,7 @@ public:
     MessageHandler(Account* account);
     
 public:
-    void sendMessage(const Shared::Message& data, bool newMessage = true);
+    void sendMessage(const Shared::Message& data, bool newMessage = true, QString originalId = "");
     void initializeMessage(Shared::Message& target, const QXmppMessage& source, bool outgoing = false, bool forwarded = false, bool guessing = false) const;
     void resendMessage(const QString& jid, const QString& id);
     
@@ -67,13 +67,17 @@ private:
     bool handleGroupMessage(const QXmppMessage& msg, bool outgoing = false, bool forwarded = false, bool guessing = false);
     void logMessage(const QXmppMessage& msg, const QString& reason = "Message wasn't handled: ");
     void sendMessageWithLocalUploadedFile(Shared::Message msg, const QString& url, bool newMessage = true);
-    void performSending(Shared::Message data, bool newMessage = true);
+    void performSending(Shared::Message data, const QString& originalId, bool newMessage = true);
     void prepareUpload(const Shared::Message& data, bool newMessage = true);
     void handleUploadError(const QString& jid, const QString& messageId, const QString& errorText);
+    QXmppMessage createPacket(const Shared::Message& data, const QDateTime& time, const QString& originalId) const;
+    QMap<QString, QVariant> getChanges(Shared::Message& data, const QDateTime& time, bool newMessage, const QString& originalId) const;
+    std::tuple<bool, QString, QString> getOriginalPendingMessageId(const QString& id);
     
 private:
     Account* acc;
     std::map<QString, QString> pendingStateMessages;        //key is message id, value is JID
+    std::map<QString, QString> pendingCorrectionMessages;   //key is new mesage, value is originalOne
     std::deque<std::pair<QString, QString>> uploadingSlotsQueue;
 };
 
diff --git a/core/main.cpp b/core/main.cpp
index 79ca648..f842c80 100644
--- a/core/main.cpp
+++ b/core/main.cpp
@@ -142,6 +142,7 @@ int main(int argc, char *argv[])
     QObject::connect(&w, &Squawk::disconnectAccount, squawk, &Core::Squawk::disconnectAccount);
     QObject::connect(&w, &Squawk::changeState, squawk, &Core::Squawk::changeState);
     QObject::connect(&w, &Squawk::sendMessage, squawk,&Core::Squawk::sendMessage);
+    QObject::connect(&w, &Squawk::replaceMessage, squawk,&Core::Squawk::replaceMessage);
     QObject::connect(&w, &Squawk::resendMessage, squawk,&Core::Squawk::resendMessage);
     QObject::connect(&w, &Squawk::requestArchive, squawk, &Core::Squawk::requestArchive);
     QObject::connect(&w, &Squawk::subscribeContact, squawk, &Core::Squawk::subscribeContact);
diff --git a/core/squawk.cpp b/core/squawk.cpp
index 9f2b445..af131d5 100644
--- a/core/squawk.cpp
+++ b/core/squawk.cpp
@@ -341,6 +341,17 @@ void Core::Squawk::sendMessage(const QString& account, const Shared::Message& da
     itr->second->sendMessage(data);
 }
 
+void Core::Squawk::replaceMessage(const QString& account, const QString& originalId, const Shared::Message& data)
+{
+    AccountsMap::const_iterator itr = amap.find(account);
+    if (itr == amap.end()) {
+        qDebug() << "An attempt to replace a message with non existing account" << account << ", skipping";
+        return;
+    }
+
+    itr->second->replaceMessage(originalId, data);
+}
+
 void Core::Squawk::resendMessage(const QString& account, const QString& jid, const QString& id)
 {
     AccountsMap::const_iterator itr = amap.find(account);
diff --git a/core/squawk.h b/core/squawk.h
index 738a957..6cd251f 100644
--- a/core/squawk.h
+++ b/core/squawk.h
@@ -101,6 +101,7 @@ public slots:
     void changeState(Shared::Availability state);
     
     void sendMessage(const QString& account, const Shared::Message& data);
+    void replaceMessage(const QString& account, const QString& originalId, const Shared::Message& data);
     void resendMessage(const QString& account, const QString& jid, const QString& id);
     void requestArchive(const QString& account, const QString& jid, int count, const QString& before);
     
diff --git a/shared/message.cpp b/shared/message.cpp
index f3f6b45..0e1b3c5 100644
--- a/shared/message.cpp
+++ b/shared/message.cpp
@@ -404,7 +404,9 @@ bool Shared::Message::change(const QMap<QString, QVariant>& data)
                 correctionDate = QDateTime::currentDateTimeUtc();      //in case there is no information about time of this correction it's applied
             }
             if (!edited || lastModified < correctionDate) {
-                originalMessage = body;
+                if (!edited) {
+                    originalMessage = body;
+                }
                 lastModified = correctionDate;
                 setBody(b);
                 setEdited(true);
diff --git a/ui/squawk.cpp b/ui/squawk.cpp
index e24640a..3ebb6a5 100644
--- a/ui/squawk.cpp
+++ b/ui/squawk.cpp
@@ -497,6 +497,17 @@ void Squawk::onConversationMessage(const Shared::Message& msg)
     emit sendMessage(acc, msg);
 }
 
+void Squawk::onConversationReplaceMessage(const QString& originalId, const Shared::Message& msg)
+{
+    Conversation* conv = static_cast<Conversation*>(sender());
+    QString acc = conv->getAccount();
+
+    rosterModel.changeMessage(acc, msg.getPenPalJid(), originalId, {
+        {"state", static_cast<uint>(Shared::Message::State::pending)}
+    });
+    emit replaceMessage(acc, originalId, msg);
+}
+
 void Squawk::onConversationResend(const QString& id)
 {
     Conversation* conv = static_cast<Conversation*>(sender());
@@ -958,6 +969,7 @@ void Squawk::subscribeConversation(Conversation* conv)
 {
     connect(conv, &Conversation::destroyed, this, &Squawk::onConversationClosed);
     connect(conv, &Conversation::sendMessage, this, &Squawk::onConversationMessage);
+    connect(conv, &Conversation::replaceMessage, this, &Squawk::onConversationReplaceMessage);
     connect(conv, &Conversation::resendMessage, this, &Squawk::onConversationResend);
     connect(conv, &Conversation::notifyableMessage, this, &Squawk::notify);
 }
diff --git a/ui/squawk.h b/ui/squawk.h
index 7551f66..7bd2e10 100644
--- a/ui/squawk.h
+++ b/ui/squawk.h
@@ -62,6 +62,7 @@ signals:
     void disconnectAccount(const QString&);
     void changeState(Shared::Availability state);
     void sendMessage(const QString& account, const Shared::Message& data);
+    void replaceMessage(const QString& account, const QString& originalId, const Shared::Message& data);
     void resendMessage(const QString& account, const QString& jid, const QString& id);
     void requestArchive(const QString& account, const QString& jid, int count, const QString& before);
     void subscribeContact(const QString& account, const QString& jid, const QString& reason);
@@ -153,6 +154,7 @@ private slots:
     void onComboboxActivated(int index);
     void onRosterItemDoubleClicked(const QModelIndex& item);
     void onConversationMessage(const Shared::Message& msg);
+    void onConversationReplaceMessage(const QString& originalId, const Shared::Message& msg);
     void onConversationResend(const QString& id);
     void onRequestArchive(const QString& account, const QString& jid, const QString& before);
     void onRosterContextMenu(const QPoint& point);
diff --git a/ui/widgets/conversation.cpp b/ui/widgets/conversation.cpp
index 608faf3..02aefb4 100644
--- a/ui/widgets/conversation.cpp
+++ b/ui/widgets/conversation.cpp
@@ -58,7 +58,8 @@ Conversation::Conversation(bool muc, Models::Account* acc, Models::Element* el,
     pasteImageAction(new QAction(tr("Paste Image"), this)),
     shadow(10, 1, Qt::black, this),
     contextMenu(new QMenu()),
-    currentAction(CurrentAction::none)
+    currentAction(CurrentAction::none),
+    currentMessageId()
 {
     m_ui->setupUi(this);
     
@@ -84,11 +85,11 @@ Conversation::Conversation(bool muc, Models::Account* acc, Models::Element* el,
     statusIcon = m_ui->statusIcon;
     statusLabel = m_ui->statusLabel;
     
-    connect(&ker, &KeyEnterReceiver::enterPressed, this, &Conversation::onEnterPressed);
+    connect(&ker, &KeyEnterReceiver::enterPressed, this, qOverload<>(&Conversation::initiateMessageSending));
     connect(&ker, &KeyEnterReceiver::imagePasted, this, &Conversation::onImagePasted);
-    connect(m_ui->sendButton, &QPushButton::clicked, this, &Conversation::onEnterPressed);
+    connect(m_ui->sendButton, &QPushButton::clicked, this, qOverload<>(&Conversation::initiateMessageSending));
     connect(m_ui->attachButton, &QPushButton::clicked, this, &Conversation::onAttach);
-    connect(m_ui->clearButton, &QPushButton::clicked, this, &Conversation::onClearButton);
+    connect(m_ui->clearButton, &QPushButton::clicked, this, &Conversation::clear);
     connect(m_ui->messageEditor->document()->documentLayout(), &QAbstractTextDocumentLayout::documentSizeChanged, 
             this, &Conversation::onTextEditDocSizeChanged);
     
@@ -98,6 +99,9 @@ Conversation::Conversation(bool muc, Models::Account* acc, Models::Element* el,
     connect(m_ui->messageEditor, &QTextEdit::customContextMenuRequested, this, &Conversation::onMessageEditorContext);
     connect(pasteImageAction, &QAction::triggered, this, &Conversation::onImagePasted);
 
+    connect(m_ui->currentActionBadge, &Badge::close, this, &Conversation::clear);
+    m_ui->currentActionBadge->setVisible(false);
+
     //line->setAutoFillBackground(false);
     //if (testAttribute(Qt::WA_TranslucentBackground)) {
         //m_ui->scrollArea->setAutoFillBackground(false);
@@ -109,9 +113,6 @@ Conversation::Conversation(bool muc, Models::Account* acc, Models::Element* el,
     //line->setMyName(acc->getName());
     
     initializeOverlay();
-
-     m_ui->currentActionBadge->setVisible(false);
-//     m_ui->currentActionBadge->setText(tr("Editing message..."));
 }
 
 Conversation::~Conversation()
@@ -224,24 +225,33 @@ void Conversation::setPalResource(const QString& res)
     activePalResource = res;
 }
 
-void Conversation::onEnterPressed()
+void Conversation::initiateMessageSending()
 {
     QString body(m_ui->messageEditor->toPlainText());
     
     if (body.size() > 0) {
-        m_ui->messageEditor->clear();
         Shared::Message msg = createMessage();
         msg.setBody(body);
-        emit sendMessage(msg);
+        initiateMessageSending(msg);
     }
     if (filesToAttach.size() > 0) {
         for (Badge* badge : filesToAttach) {
             Shared::Message msg = createMessage();
             msg.setAttachPath(badge->id);
             element->feed->registerUpload(msg.getId());
-            emit sendMessage(msg);
+            initiateMessageSending(msg);
         }
-        clearAttachedFiles();
+    }
+    clear();
+}
+
+void Conversation::initiateMessageSending(const Shared::Message& msg)
+{
+    if (currentAction == CurrentAction::edit) {
+        emit replaceMessage(currentMessageId, msg);
+        currentAction = CurrentAction::none;
+    } else {
+        emit sendMessage(msg);
     }
 }
 
@@ -348,8 +358,11 @@ void Conversation::clearAttachedFiles()
     filesLayout->setContentsMargins(0, 0, 0, 0);
 }
 
-void Conversation::onClearButton()
+void Conversation::clear()
 {
+    currentMessageId.clear();
+    currentAction = CurrentAction::none;
+    m_ui->currentActionBadge->setVisible(false);
     clearAttachedFiles();
     m_ui->messageEditor->clear();
 }
@@ -526,13 +539,12 @@ void Conversation::onMessageEditorContext(const QPoint& pos)
 
 void Conversation::onMessageEditRequested(const QString& id)
 {
-    if (currentAction == CurrentAction::edit) {
-        //todo;
-    }
+    clear();
 
     try {
         Shared::Message msg = element->feed->getMessage(id);
 
+        currentMessageId = id;
         m_ui->currentActionBadge->setVisible(true);
         m_ui->currentActionBadge->setText(tr("Editing message..."));
         currentAction = CurrentAction::edit;
diff --git a/ui/widgets/conversation.h b/ui/widgets/conversation.h
index 4bccdfc..743df71 100644
--- a/ui/widgets/conversation.h
+++ b/ui/widgets/conversation.h
@@ -83,6 +83,7 @@ public:
     
 signals:
     void sendMessage(const Shared::Message& message);
+    void replaceMessage(const QString& originalId, const Shared::Message& message);
     void resendMessage(const QString& id);
     void requestArchive(const QString& before);
     void shown();
@@ -104,12 +105,13 @@ protected:
     virtual void onMessage(const Shared::Message& msg);
     
 protected slots:
-    void onEnterPressed();
+    void initiateMessageSending();
+    void initiateMessageSending(const Shared::Message& msg);
     void onImagePasted();
     void onAttach();
     void onFileSelected();
     void onBadgeClose();
-    void onClearButton();
+    void clear();
     void onTextEditDocSizeChanged(const QSizeF& size);
     void onAccountChanged(Models::Item* item, int row, int col);
     void onFeedMessage(const Shared::Message& msg);
@@ -149,6 +151,7 @@ protected:
     ShadowOverlay shadow;
     QMenu* contextMenu;
     CurrentAction currentAction;
+    QString currentMessageId;
 
 private:
     static bool painterInitialized;

From 5f6691067a18e503caf84122d59b59409bfbb5b4 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Tue, 29 Mar 2022 19:05:24 +0300
Subject: [PATCH 174/281] minor bugfixes about message body, automatic focus
 and that quirk with font becomming bigger

---
 CHANGELOG.md                               | 3 +++
 ui/widgets/conversation.cpp                | 9 ++++++++-
 ui/widgets/conversation.h                  | 1 +
 ui/widgets/conversation.ui                 | 6 +++++-
 ui/widgets/messageline/messagedelegate.cpp | 1 +
 5 files changed, 18 insertions(+), 2 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index efb159c..83c759e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,9 +5,12 @@
 - build in release mode now no longer spams warnings
 - build now correctly installs all build plugin libs
 - a bug where the correction message was received, the indication was on but the text didn't actually change
+- message body now doesn't intecept context menu from the whole message
+- message input now doesn't increase font when you remove everything from it
 
 ### Improvements
 - reduced amount of places where platform specific path separator is used
+- now message input is automatically focused when you open a dialog or a room
 
 ### New features
 - the settings are here! You con config different stuff from there
diff --git a/ui/widgets/conversation.cpp b/ui/widgets/conversation.cpp
index 02aefb4..da3d187 100644
--- a/ui/widgets/conversation.cpp
+++ b/ui/widgets/conversation.cpp
@@ -94,7 +94,6 @@ Conversation::Conversation(bool muc, Models::Account* acc, Models::Element* el,
             this, &Conversation::onTextEditDocSizeChanged);
     
     m_ui->messageEditor->installEventFilter(&ker);
-    m_ui->messageEditor->setContextMenuPolicy(Qt::CustomContextMenu);
 
     connect(m_ui->messageEditor, &QTextEdit::customContextMenuRequested, this, &Conversation::onMessageEditorContext);
     connect(pasteImageAction, &QAction::triggered, this, &Conversation::onImagePasted);
@@ -555,3 +554,11 @@ void Conversation::onMessageEditRequested(const QString& id)
         qDebug() << "Ignoring";
     }
 }
+
+void Conversation::showEvent(QShowEvent* event)
+{
+    QWidget::showEvent(event);
+
+    emit shown();
+    m_ui->messageEditor->setFocus();
+}
diff --git a/ui/widgets/conversation.h b/ui/widgets/conversation.h
index 743df71..0c44bd9 100644
--- a/ui/widgets/conversation.h
+++ b/ui/widgets/conversation.h
@@ -103,6 +103,7 @@ protected:
     void dropEvent(QDropEvent* event) override;
     void initializeOverlay();
     virtual void onMessage(const Shared::Message& msg);
+    virtual void showEvent(QShowEvent * event) override;
     
 protected slots:
     void initiateMessageSending();
diff --git a/ui/widgets/conversation.ui b/ui/widgets/conversation.ui
index ce9ad66..1f8b483 100644
--- a/ui/widgets/conversation.ui
+++ b/ui/widgets/conversation.ui
@@ -402,6 +402,9 @@
           <height>30</height>
          </size>
         </property>
+        <property name="contextMenuPolicy">
+         <enum>Qt::CustomContextMenu</enum>
+        </property>
         <property name="autoFillBackground">
          <bool>false</bool>
         </property>
@@ -424,7 +427,7 @@ background-color: transparent
 &lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
 p, li { white-space: pre-wrap; }
 &lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Noto Sans'; font-size:8pt; font-weight:400; font-style:normal;&quot;&gt;
-&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Liberation Sans'; font-size:10pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
         </property>
         <property name="acceptRichText">
          <bool>false</bool>
@@ -447,6 +450,7 @@ p, li { white-space: pre-wrap; }
    <class>Badge</class>
    <extends>QFrame</extends>
    <header location="global">ui/utils/badge.h</header>
+   <container>1</container>
   </customwidget>
  </customwidgets>
  <resources>
diff --git a/ui/widgets/messageline/messagedelegate.cpp b/ui/widgets/messageline/messagedelegate.cpp
index 22e8dcb..15a5e46 100644
--- a/ui/widgets/messageline/messagedelegate.cpp
+++ b/ui/widgets/messageline/messagedelegate.cpp
@@ -582,6 +582,7 @@ QLabel * MessageDelegate::getBody(const Models::FeedItem& data) const
     } else {
         result = new QLabel();
         result->setFont(bodyFont);
+        result->setContextMenuPolicy(Qt::NoContextMenu);
         result->setWordWrap(true);
         result->setOpenExternalLinks(true);
         result->setTextInteractionFlags(result->textInteractionFlags() | Qt::TextSelectableByMouse | Qt::LinksAccessibleByMouse);

From 1fcd403dbaa841ceb2f3f70bfad439440810bd30 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Fri, 1 Apr 2022 00:32:22 +0300
Subject: [PATCH 175/281] testing, solved unhandled exception, conditions to
 restrict old message to be edited, license un some files that used to miss
 them

---
 core/handlers/messagehandler.cpp               |  6 +++++-
 core/networkaccess.cpp                         |  2 +-
 .../wrappers/kwallet.cpp                       | 18 ++++++++++++++++++
 plugins/colorschemetools.cpp                   | 18 ++++++++++++++++++
 plugins/openfilemanagerwindowjob.cpp           | 18 ++++++++++++++++++
 shared/exception.cpp                           |  3 ++-
 shared/pathcheck.cpp                           | 18 ++++++++++++++++++
 shared/pathcheck.h                             | 18 ++++++++++++++++++
 ui/widgets/conversation.cpp                    |  3 ++-
 ui/widgets/settings/pageappearance.cpp         | 18 ++++++++++++++++++
 ui/widgets/settings/pageappearance.h           | 18 ++++++++++++++++++
 ui/widgets/settings/pagegeneral.cpp            | 18 ++++++++++++++++++
 ui/widgets/settings/pagegeneral.h              | 18 ++++++++++++++++++
 ui/widgets/settings/settings.cpp               | 18 ++++++++++++++++++
 ui/widgets/settings/settings.h                 | 18 ++++++++++++++++++
 ui/widgets/settings/settingslist.cpp           | 18 ++++++++++++++++++
 ui/widgets/settings/settingslist.h             | 18 ++++++++++++++++++
 17 files changed, 244 insertions(+), 4 deletions(-)

diff --git a/core/handlers/messagehandler.cpp b/core/handlers/messagehandler.cpp
index 559bee3..0555873 100644
--- a/core/handlers/messagehandler.cpp
+++ b/core/handlers/messagehandler.cpp
@@ -233,7 +233,11 @@ std::tuple<bool, QString, QString> Core::MessageHandler::getOriginalPendingMessa
 
         std::map<QString, QString>::const_iterator itrC = pendingCorrectionMessages.find(id);
         if (itrC != pendingCorrectionMessages.end()) {
-            std::get<1>(result) = itrC->second;
+            if (itrC->second.size() > 0) {
+                std::get<1>(result) = itrC->second;
+            } else {
+                std::get<1>(result) = itr->first;
+            }
             pendingCorrectionMessages.erase(itrC);
         } else {
             std::get<1>(result) = itr->first;
diff --git a/core/networkaccess.cpp b/core/networkaccess.cpp
index 7c55e19..22bb7a2 100644
--- a/core/networkaccess.cpp
+++ b/core/networkaccess.cpp
@@ -443,7 +443,7 @@ QString Core::NetworkAccess::getFileRemoteUrl(const QString& path)
     try {
         p = storage.getUrl(p);
     } catch (const Archive::NotFound& err) {
-        
+        p = "";
     } catch (...) {
         throw;
     }
diff --git a/core/passwordStorageEngines/wrappers/kwallet.cpp b/core/passwordStorageEngines/wrappers/kwallet.cpp
index f5e7cb5..d899985 100644
--- a/core/passwordStorageEngines/wrappers/kwallet.cpp
+++ b/core/passwordStorageEngines/wrappers/kwallet.cpp
@@ -1,3 +1,21 @@
+/*
+ * Squawk messenger.
+ * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+ *
+ * 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 <KF5/KWallet/KWallet>
 
 extern "C" KWallet::Wallet* openWallet(const QString &name, WId w, KWallet::Wallet::OpenType ot = KWallet::Wallet::Synchronous) {
diff --git a/plugins/colorschemetools.cpp b/plugins/colorschemetools.cpp
index 0288b28..ea2c23e 100644
--- a/plugins/colorschemetools.cpp
+++ b/plugins/colorschemetools.cpp
@@ -1,3 +1,21 @@
+/*
+ * Squawk messenger.
+ * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+ *
+ * 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 <QIcon>
 #include <QPainter>
 #include <QFileInfo>
diff --git a/plugins/openfilemanagerwindowjob.cpp b/plugins/openfilemanagerwindowjob.cpp
index 904fbcf..4335410 100644
--- a/plugins/openfilemanagerwindowjob.cpp
+++ b/plugins/openfilemanagerwindowjob.cpp
@@ -1,3 +1,21 @@
+/*
+ * Squawk messenger.
+ * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+ *
+ * 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 <QUrl>
 #include <QObject>
 #include <KIO/OpenFileManagerWindowJob>
diff --git a/shared/exception.cpp b/shared/exception.cpp
index 342593c..3dee9b3 100644
--- a/shared/exception.cpp
+++ b/shared/exception.cpp
@@ -28,5 +28,6 @@ Utils::Exception::~Exception()
 
 const char* Utils::Exception::what() const noexcept( true )
 {
-    return getMessage().c_str();
+    std::string* msg = new std::string(getMessage());
+    return msg->c_str();
 }
diff --git a/shared/pathcheck.cpp b/shared/pathcheck.cpp
index 1929387..c32f96c 100644
--- a/shared/pathcheck.cpp
+++ b/shared/pathcheck.cpp
@@ -1,3 +1,21 @@
+/*
+ * Squawk messenger.
+ * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+ *
+ * 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 "pathcheck.h"
 
 QRegularExpression squawk("^squawk:\\/\\/");
diff --git a/shared/pathcheck.h b/shared/pathcheck.h
index 62dcaeb..3ca612b 100644
--- a/shared/pathcheck.h
+++ b/shared/pathcheck.h
@@ -1,3 +1,21 @@
+/*
+ * Squawk messenger.
+ * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+ *
+ * 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/>.
+ */
+
 #ifndef PATHCHECK_H
 #define PATHCHECK_H
 
diff --git a/ui/widgets/conversation.cpp b/ui/widgets/conversation.cpp
index da3d187..ab5f0c5 100644
--- a/ui/widgets/conversation.cpp
+++ b/ui/widgets/conversation.cpp
@@ -513,7 +513,8 @@ void Conversation::onFeedContext(const QPoint& pos)
             });
         }
 
-        if (item->getOutgoing()) {
+        //the only mandatory condition - is for the message to be outgoing, the rest is just a good intention on the server
+        if (item->getOutgoing() && index.row() < 100 && item->getTime().daysTo(QDateTime::currentDateTimeUtc()) < 20) {
             showMenu = true;
             QAction* edit = contextMenu->addAction(Shared::icon("edit-rename"), tr("Edit"));
             connect(edit, &QAction::triggered, this, std::bind(&Conversation::onMessageEditRequested, this, id));
diff --git a/ui/widgets/settings/pageappearance.cpp b/ui/widgets/settings/pageappearance.cpp
index f2bf53e..64d6de4 100644
--- a/ui/widgets/settings/pageappearance.cpp
+++ b/ui/widgets/settings/pageappearance.cpp
@@ -1,3 +1,21 @@
+/*
+ * Squawk messenger.
+ * Copyright (C) 2019 Yury Gubich <blue@macaw.me>
+ *
+ * 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 "pageappearance.h"
 #include "ui_pageappearance.h"
 
diff --git a/ui/widgets/settings/pageappearance.h b/ui/widgets/settings/pageappearance.h
index 80efd85..c182ea2 100644
--- a/ui/widgets/settings/pageappearance.h
+++ b/ui/widgets/settings/pageappearance.h
@@ -1,3 +1,21 @@
+/*
+ * Squawk messenger.
+ * Copyright (C) 2019 Yury Gubich <blue@macaw.me>
+ *
+ * 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/>.
+ */
+
 #ifndef PAGEAPPEARANCE_H
 #define PAGEAPPEARANCE_H
 
diff --git a/ui/widgets/settings/pagegeneral.cpp b/ui/widgets/settings/pagegeneral.cpp
index a546bd0..9ed46a2 100644
--- a/ui/widgets/settings/pagegeneral.cpp
+++ b/ui/widgets/settings/pagegeneral.cpp
@@ -1,3 +1,21 @@
+/*
+ * Squawk messenger.
+ * Copyright (C) 2019 Yury Gubich <blue@macaw.me>
+ *
+ * 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 "pagegeneral.h"
 #include "ui_pagegeneral.h"
 
diff --git a/ui/widgets/settings/pagegeneral.h b/ui/widgets/settings/pagegeneral.h
index ec00bba..7f58d71 100644
--- a/ui/widgets/settings/pagegeneral.h
+++ b/ui/widgets/settings/pagegeneral.h
@@ -1,3 +1,21 @@
+/*
+ * Squawk messenger.
+ * Copyright (C) 2019 Yury Gubich <blue@macaw.me>
+ *
+ * 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/>.
+ */
+
 #ifndef PAGEGENERAL_H
 #define PAGEGENERAL_H
 
diff --git a/ui/widgets/settings/settings.cpp b/ui/widgets/settings/settings.cpp
index 27401bb..cf5e905 100644
--- a/ui/widgets/settings/settings.cpp
+++ b/ui/widgets/settings/settings.cpp
@@ -1,3 +1,21 @@
+/*
+ * Squawk messenger.
+ * Copyright (C) 2019 Yury Gubich <blue@macaw.me>
+ *
+ * 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 "settings.h"
 #include "ui_settings.h"
 
diff --git a/ui/widgets/settings/settings.h b/ui/widgets/settings/settings.h
index 5a6b37c..689e0ce 100644
--- a/ui/widgets/settings/settings.h
+++ b/ui/widgets/settings/settings.h
@@ -1,3 +1,21 @@
+/*
+ * Squawk messenger.
+ * Copyright (C) 2019 Yury Gubich <blue@macaw.me>
+ *
+ * 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/>.
+ */
+
 #ifndef SETTINGS_H
 #define SETTINGS_H
 
diff --git a/ui/widgets/settings/settingslist.cpp b/ui/widgets/settings/settingslist.cpp
index 3a5e2cb..ee2e3ed 100644
--- a/ui/widgets/settings/settingslist.cpp
+++ b/ui/widgets/settings/settingslist.cpp
@@ -1,3 +1,21 @@
+/*
+ * Squawk messenger.
+ * Copyright (C) 2019 Yury Gubich <blue@macaw.me>
+ *
+ * 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 "settingslist.h"
 
 SettingsList::SettingsList(QWidget* parent):
diff --git a/ui/widgets/settings/settingslist.h b/ui/widgets/settings/settingslist.h
index a51fc3a..64c9d57 100644
--- a/ui/widgets/settings/settingslist.h
+++ b/ui/widgets/settings/settingslist.h
@@ -1,3 +1,21 @@
+/*
+ * Squawk messenger.
+ * Copyright (C) 2019 Yury Gubich <blue@macaw.me>
+ *
+ * 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/>.
+ */
+
 #ifndef UI_SETTINGSLIST_H
 #define UI_SETTINGSLIST_H
 

From 62f02c18d785582e493a8aedd9a46413ab14212a Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Sat, 2 Apr 2022 15:34:36 +0300
Subject: [PATCH 176/281] now you can't edit messages with attachments: no
 other client actually allowes that, and if I edit they don't handle it
 properly anyway

---
 ui/widgets/conversation.cpp | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/ui/widgets/conversation.cpp b/ui/widgets/conversation.cpp
index ab5f0c5..4e5e007 100644
--- a/ui/widgets/conversation.cpp
+++ b/ui/widgets/conversation.cpp
@@ -513,8 +513,9 @@ void Conversation::onFeedContext(const QPoint& pos)
             });
         }
 
+        bool hasAttach = item->getAttachPath() > 0 || item->getOutOfBandUrl() > 0;
         //the only mandatory condition - is for the message to be outgoing, the rest is just a good intention on the server
-        if (item->getOutgoing() && index.row() < 100 && item->getTime().daysTo(QDateTime::currentDateTimeUtc()) < 20) {
+        if (item->getOutgoing() && !hasAttach  && index.row() < 100 && item->getTime().daysTo(QDateTime::currentDateTimeUtc()) < 20) {
             showMenu = true;
             QAction* edit = contextMenu->addAction(Shared::icon("edit-rename"), tr("Edit"));
             connect(edit, &QAction::triggered, this, std::bind(&Conversation::onMessageEditRequested, this, id));
@@ -549,6 +550,10 @@ void Conversation::onMessageEditRequested(const QString& id)
         m_ui->currentActionBadge->setText(tr("Editing message..."));
         currentAction = CurrentAction::edit;
         m_ui->messageEditor->setText(msg.getBody());
+        QString path = msg.getAttachPath();
+        if (path.size() > 0) {
+            addAttachedFile(path);
+        }
 
     } catch (const Models::MessageFeed::NotFound& e) {
         qDebug() << "The message requested to be edited was not found" << e.getMessage().c_str();

From 4786388822f74f225e993ac1d0f86cb2b5df9f3b Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Sat, 2 Apr 2022 15:53:23 +0300
Subject: [PATCH 177/281] 0.2.1

---
 CHANGELOG.md                 | 7 ++++---
 packaging/Archlinux/PKGBUILD | 6 +++---
 2 files changed, 7 insertions(+), 6 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 83c759e..a6445ec 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,6 @@
 # Changelog
 
-## Squawk 0.2.1 (UNRELEASED)
+## Squawk 0.2.1 (Apr 02, 2022)
 ### Bug fixes
 - build in release mode now no longer spams warnings
 - build now correctly installs all build plugin libs
@@ -11,12 +11,13 @@
 ### Improvements
 - reduced amount of places where platform specific path separator is used
 - now message input is automatically focused when you open a dialog or a room
+- what() method on unhandled exception now actually tells what happened
 
 ### New features
 - the settings are here! You con config different stuff from there
 - now it's possible to set up different qt styles from settings
-- if you have KConfig nad KConfigWidgets packages installed - you can chose from global color schemes
-- it's possible now to chose a folder where squawk is going to store downloaded files
+- if you have KConfig nad KConfigWidgets packages installed - you can choose from global color schemes
+- it's possible now to choose a folder where squawk is going to store downloaded files
 - now you can correct your message
 
 ## Squawk 0.2.0 (Jan 10, 2022)
diff --git a/packaging/Archlinux/PKGBUILD b/packaging/Archlinux/PKGBUILD
index a8da388..899f058 100644
--- a/packaging/Archlinux/PKGBUILD
+++ b/packaging/Archlinux/PKGBUILD
@@ -1,6 +1,6 @@
 # Maintainer: Yury Gubich <blue@macaw.me>
 pkgname=squawk
-pkgver=0.2.0
+pkgver=0.2.1
 pkgrel=1
 pkgdesc="An XMPP desktop messenger, written on pure c++ (qt)"
 arch=('i686' 'x86_64')
@@ -14,7 +14,7 @@ optdepends=('kwallet: secure password storage (requires rebuild)'
             'kio: better show in folder action (requires rebuild)')
 
 source=("$pkgname-$pkgver.tar.gz")
-sha256sums=('8e93d3dbe1fc35cfecb7783af409c6a264244d11609b2241d4fe77d43d068419')
+sha256sums=('c00dad1e441601acabb5200dc394f53abfc9876f3902a7dd4ad2fee3232ee84d')
 build() {
         cd "$srcdir/squawk"
         cmake . -D CMAKE_INSTALL_PREFIX=/usr -D CMAKE_BUILD_TYPE=Release
@@ -22,5 +22,5 @@ build() {
 }
 package() {
         cd "$srcdir/squawk"
-        DESTDIR="$pkgdir/" cmake --build . --target install 
+        DESTDIR="$pkgdir/" cmake --build . --target install
 }

From 4baa3bccbf80d4b861661d4986b94a4114f9edba Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Sat, 2 Apr 2022 16:09:11 +0300
Subject: [PATCH 178/281] new screenshot

---
 README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/README.md b/README.md
index 486d4fe..5845c46 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@
 [![AUR version](https://img.shields.io/aur/version/squawk?style=flat-square)](https://aur.archlinux.org/packages/squawk/)
 [![Liberapay patrons](https://img.shields.io/liberapay/patrons/macaw.me?logo=liberapay&style=flat-square)](https://liberapay.com/macaw.me)
 
-![Squawk screenshot](https://macaw.me/images/squawk/0.2.0.png)
+![Squawk screenshot](https://macaw.me/images/squawk/0.2.1.png)
 
 ### Prerequisites
 

From 27377e0ec51fad161165649bd0fe92d23f25bbd0 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Sun, 3 Apr 2022 23:53:46 +0300
Subject: [PATCH 179/281] first attempt to make About window

---
 CHANGELOG.md              |   8 ++
 CMakeLists.txt            |   2 +-
 core/main.cpp             |  18 +---
 ui/squawk.cpp             |  98 ++++++++++++--------
 ui/squawk.h               |   4 +
 ui/squawk.ui              |  15 ++-
 ui/widgets/CMakeLists.txt |   3 +
 ui/widgets/about.cpp      |  29 ++++++
 ui/widgets/about.h        |  43 +++++++++
 ui/widgets/about.ui       | 186 ++++++++++++++++++++++++++++++++++++++
 10 files changed, 354 insertions(+), 52 deletions(-)
 create mode 100644 ui/widgets/about.cpp
 create mode 100644 ui/widgets/about.h
 create mode 100644 ui/widgets/about.ui

diff --git a/CHANGELOG.md b/CHANGELOG.md
index a6445ec..f563c85 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,13 @@
 # Changelog
 
+## Squawk 0.2.2 (UNRELEASED)
+### Bug fixes
+
+### Improvements
+
+### New features
+
+
 ## Squawk 0.2.1 (Apr 02, 2022)
 ### Bug fixes
 - build in release mode now no longer spams warnings
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 717cf91..7d0ee7f 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,5 +1,5 @@
 cmake_minimum_required(VERSION 3.4)
-project(squawk VERSION 0.2.1 LANGUAGES CXX)
+project(squawk VERSION 0.2.2 LANGUAGES CXX)
 
 cmake_policy(SET CMP0076 NEW)
 cmake_policy(SET CMP0079 NEW)
diff --git a/core/main.cpp b/core/main.cpp
index f842c80..7d3c3ab 100644
--- a/core/main.cpp
+++ b/core/main.cpp
@@ -45,19 +45,11 @@ int main(int argc, char *argv[])
     
     QApplication app(argc, argv);
     SignalCatcher sc(&app);
-#ifdef Q_OS_WIN
-    // Windows need an organization name for QSettings to work
-    // https://doc.qt.io/qt-5/qsettings.html#basic-usage
-    {
-        const QString& orgName = QApplication::organizationName();
-        if (orgName.isNull() || orgName.isEmpty()) {
-            QApplication::setOrganizationName("squawk");
-        }
-    }
-#endif
+
     QApplication::setApplicationName("squawk");
+    QApplication::setOrganizationName("macaw.me");
     QApplication::setApplicationDisplayName("Squawk");
-    QApplication::setApplicationVersion("0.2.1");
+    QApplication::setApplicationVersion("0.2.2");
 
     QTranslator qtTranslator;
     qtTranslator.load("qt_" + QLocale::system().name(), QLibraryInfo::location(QLibraryInfo::TranslationsPath));
@@ -199,8 +191,8 @@ int main(int argc, char *argv[])
 
     if (coreThread->isRunning()) {
         //coreThread->wait();
-        //todo if I uncomment that, the app will no quit if it has reconnected at least once
-        //it feels like a symptom of something badly desinged in the core coreThread
+        //todo if I uncomment that, the app will not quit if it has reconnected at least once
+        //it feels like a symptom of something badly desinged in the core thread
         //need to investigate;
     }
     
diff --git a/ui/squawk.cpp b/ui/squawk.cpp
index 3ebb6a5..4594c01 100644
--- a/ui/squawk.cpp
+++ b/ui/squawk.cpp
@@ -24,16 +24,17 @@
 Squawk::Squawk(QWidget *parent) :
     QMainWindow(parent),
     m_ui(new Ui::Squawk),
-    accounts(0),
-    preferences(0),
+    accounts(nullptr),
+    preferences(nullptr),
+    about(nullptr),
     rosterModel(),
     conversations(),
     contextMenu(new QMenu()),
     dbus("org.freedesktop.Notifications", "/org/freedesktop/Notifications", "org.freedesktop.Notifications", QDBusConnection::sessionBus()),
     vCards(),
     requestedAccountsForPasswords(),
-    prompt(0),
-    currentConversation(0),
+    prompt(nullptr),
+    currentConversation(nullptr),
     restoreSelection(),
     needToRestore(false)
 {
@@ -72,6 +73,7 @@ Squawk::Squawk(QWidget *parent) :
     connect(&rosterModel, &Models::Roster::fileDownloadRequest, this, &Squawk::fileDownloadRequest);
     connect(&rosterModel, &Models::Roster::localPathInvalid, this, &Squawk::localPathInvalid);
     connect(contextMenu, &QMenu::aboutToHide, this, &Squawk::onContextAboutToHide);
+    connect(m_ui->actionAboutSquawk, &QAction::triggered, this, &Squawk::onAboutSquawkCalled);
     //m_ui->mainToolBar->addWidget(m_ui->comboBox);
     
     if (testAttribute(Qt::WA_TranslucentBackground)) {
@@ -101,7 +103,7 @@ Squawk::~Squawk() {
 
 void Squawk::onAccounts()
 {
-    if (accounts == 0) {
+    if (accounts == nullptr) {
         accounts = new Accounts(rosterModel.accountsModel);
         accounts->setAttribute(Qt::WA_DeleteOnClose);
         connect(accounts, &Accounts::destroyed, this, &Squawk::onAccountsClosed);
@@ -121,7 +123,7 @@ void Squawk::onAccounts()
 
 void Squawk::onPreferences()
 {
-    if (preferences == 0) {
+    if (preferences == nullptr) {
         preferences = new Settings();
         preferences->setAttribute(Qt::WA_DeleteOnClose);
         connect(preferences, &Settings::destroyed, this, &Squawk::onPreferencesClosed);
@@ -189,12 +191,15 @@ void Squawk::onJoinConferenceAccepted()
 
 void Squawk::closeEvent(QCloseEvent* event)
 {
-    if (accounts != 0) {
+    if (accounts != nullptr) {
         accounts->close();
     }
-    if (preferences != 0) {
+    if (preferences != nullptr) {
         preferences->close();
     }
+    if (about != nullptr) {
+        about->close();
+    }
     
     for (Conversations::const_iterator itr = conversations.begin(), end = conversations.end(); itr != end; ++itr) {
         disconnect(itr->second, &Conversation::destroyed, this, &Squawk::onConversationClosed);
@@ -214,12 +219,12 @@ void Squawk::closeEvent(QCloseEvent* event)
 
 void Squawk::onAccountsClosed()
 {
-    accounts = 0;
+    accounts = nullptr;
 }
 
 void Squawk::onPreferencesClosed()
 {
-    preferences = 0;
+    preferences = nullptr;
 }
 
 void Squawk::newAccount(const QMap<QString, QVariant>& account)
@@ -342,10 +347,10 @@ void Squawk::onRosterItemDoubleClicked(const QModelIndex& item)
         if (node->type == Models::Item::reference) {
             node = static_cast<Models::Reference*>(node)->dereference();
         }
-        Models::Contact* contact = 0;
-        Models::Room* room = 0;
+        Models::Contact* contact = nullptr;
+        Models::Room* room = nullptr;
         QString res;
-        Models::Roster::ElId* id = 0;
+        Models::Roster::ElId* id = nullptr;
         switch (node->type) {
             case Models::Item::contact:
                 contact = static_cast<Models::Contact*>(node);
@@ -365,17 +370,17 @@ void Squawk::onRosterItemDoubleClicked(const QModelIndex& item)
                 break;
         }
         
-        if (id != 0) {
+        if (id != nullptr) {
             Conversations::const_iterator itr = conversations.find(*id);
             Models::Account* acc = rosterModel.getAccount(id->account);
-            Conversation* conv = 0;
+            Conversation* conv = nullptr;
             bool created = false;
             if (itr != conversations.end()) {
                 conv = itr->second;
-            } else if (contact != 0) {
+            } else if (contact != nullptr) {
                 created = true;
                 conv = new Chat(acc, contact);
-            } else if (room != 0) {
+            } else if (room != nullptr) {
                 created = true;
                 conv = new Room(acc, room);
                 
@@ -384,7 +389,7 @@ void Squawk::onRosterItemDoubleClicked(const QModelIndex& item)
                 }
             }
             
-            if (conv != 0) {
+            if (conv != nullptr) {
                 if (created) {
                     conv->setAttribute(Qt::WA_DeleteOnClose);
                     subscribeConversation(conv);
@@ -543,9 +548,9 @@ void Squawk::removeAccount(const QString& account)
         }
     }
     
-    if (currentConversation != 0 && currentConversation->getAccount() == account) {
+    if (currentConversation != nullptr && currentConversation->getAccount() == account) {
         currentConversation->deleteLater();
-        currentConversation = 0;
+        currentConversation = nullptr;
         m_ui->filler->show();
     }
     
@@ -710,7 +715,7 @@ void Squawk::onRosterContextMenu(const QPoint& point)
                     connect(unsub, &QAction::triggered, [this, id]() {
                         emit setRoomAutoJoin(id.account, id.name, false);
                         if (conversations.find(id) == conversations.end()
-                            && (currentConversation == 0 || currentConversation->getId() != id)
+                            && (currentConversation == nullptr || currentConversation->getId() != id)
                         ) {    //to leave the room if it's not opened in a conversation window
                             emit setRoomJoined(id.account, id.name, false);
                         }
@@ -721,7 +726,7 @@ void Squawk::onRosterContextMenu(const QPoint& point)
                     connect(unsub, &QAction::triggered, [this, id]() {
                         emit setRoomAutoJoin(id.account, id.name, true);
                         if (conversations.find(id) == conversations.end()
-                            && (currentConversation == 0 || currentConversation->getId() != id)
+                            && (currentConversation == nullptr || currentConversation->getId() != id)
                         ) {    //to join the room if it's not already joined
                             emit setRoomJoined(id.account, id.name, true);
                         }
@@ -928,7 +933,7 @@ void Squawk::requestPassword(const QString& account)
 
 void Squawk::checkNextAccountForPassword()
 {
-    if (prompt == 0 && requestedAccountsForPasswords.size() > 0) {
+    if (prompt == nullptr && requestedAccountsForPasswords.size() > 0) {
         prompt = new QInputDialog(this);
         QString accName = requestedAccountsForPasswords.front();
         connect(prompt, &QDialog::accepted, this, &Squawk::onPasswordPromptAccepted);
@@ -951,7 +956,7 @@ void Squawk::onPasswordPromptAccepted()
 void Squawk::onPasswordPromptDone()
 {
     prompt->deleteLater();
-    prompt = 0;
+    prompt = nullptr;
     requestedAccountsForPasswords.pop_front();
     checkNextAccountForPassword();
 }
@@ -986,10 +991,10 @@ void Squawk::onRosterSelectionChanged(const QModelIndex& current, const QModelIn
         if (node->type == Models::Item::reference) {
             node = static_cast<Models::Reference*>(node)->dereference();
         }
-        Models::Contact* contact = 0;
-        Models::Room* room = 0;
+        Models::Contact* contact = nullptr;
+        Models::Room* room = nullptr;
         QString res;
-        Models::Roster::ElId* id = 0;
+        Models::Roster::ElId* id = nullptr;
         bool hasContext = true;
         switch (node->type) {
             case Models::Item::contact:
@@ -1018,7 +1023,7 @@ void Squawk::onRosterSelectionChanged(const QModelIndex& current, const QModelIn
         }
         
         if (hasContext && QGuiApplication::mouseButtons() & Qt::RightButton) {
-            if (id != 0) {
+            if (id != nullptr) {
                 delete id;
             }
             needToRestore = true;
@@ -1026,10 +1031,10 @@ void Squawk::onRosterSelectionChanged(const QModelIndex& current, const QModelIn
             return;
         }
         
-        if (id != 0) {
-            if (currentConversation != 0) {
+        if (id != nullptr) {
+            if (currentConversation != nullptr) {
                 if (currentConversation->getId() == *id) {
-                    if (contact != 0) {
+                    if (contact != nullptr) {
                         currentConversation->setPalResource(res);
                     }
                     return;
@@ -1041,9 +1046,9 @@ void Squawk::onRosterSelectionChanged(const QModelIndex& current, const QModelIn
             }
             
             Models::Account* acc = rosterModel.getAccount(id->account);
-            if (contact != 0) {
+            if (contact != nullptr) {
                 currentConversation = new Chat(acc, contact);
-            } else if (room != 0) {
+            } else if (room != nullptr) {
                 currentConversation = new Room(acc, room);
                 
                 if (!room->getJoined()) {
@@ -1064,16 +1069,16 @@ void Squawk::onRosterSelectionChanged(const QModelIndex& current, const QModelIn
             
             delete id;
         } else {
-            if (currentConversation != 0) {
+            if (currentConversation != nullptr) {
                 currentConversation->deleteLater();
-                currentConversation = 0;
+                currentConversation = nullptr;
                 m_ui->filler->show();
             }
         }
     } else {
-        if (currentConversation != 0) {
+        if (currentConversation != nullptr) {
             currentConversation->deleteLater();
-            currentConversation = 0;
+            currentConversation = nullptr;
             m_ui->filler->show();
         }
     }
@@ -1086,3 +1091,22 @@ void Squawk::onContextAboutToHide()
         m_ui->roster->selectionModel()->setCurrentIndex(restoreSelection, QItemSelectionModel::ClearAndSelect);
     }
 }
+
+void Squawk::onAboutSquawkCalled()
+{
+    if (about == nullptr) {
+        about = new About();
+        about->setAttribute(Qt::WA_DeleteOnClose);
+        connect(about, &Settings::destroyed, this, &Squawk::onAboutSquawkClosed);
+        about->show();
+    } else {
+        about->raise();
+        about->activateWindow();
+        about->show();
+    }
+}
+
+void Squawk::onAboutSquawkClosed()
+{
+    about = nullptr;
+}
diff --git a/ui/squawk.h b/ui/squawk.h
index 7bd2e10..95c5ce3 100644
--- a/ui/squawk.h
+++ b/ui/squawk.h
@@ -39,6 +39,7 @@
 #include "models/roster.h"
 #include "widgets/vcard/vcard.h"
 #include "widgets/settings/settings.h"
+#include "widgets/about.h"
 
 #include "shared/shared.h"
 
@@ -120,6 +121,7 @@ private:
     
     Accounts* accounts;
     Settings* preferences;
+    About* about;
     Models::Roster rosterModel;
     Conversations conversations;
     QMenu* contextMenu;
@@ -163,6 +165,8 @@ private slots:
     void onPasswordPromptRejected();
     void onRosterSelectionChanged(const QModelIndex& current, const QModelIndex& previous);
     void onContextAboutToHide();
+    void onAboutSquawkCalled();
+    void onAboutSquawkClosed();
     
     void onUnnoticedMessage(const QString& account, const Shared::Message& msg);
     
diff --git a/ui/squawk.ui b/ui/squawk.ui
index 840dfee..a8b0730 100644
--- a/ui/squawk.ui
+++ b/ui/squawk.ui
@@ -201,8 +201,15 @@
     <addaction name="actionAddConference"/>
     <addaction name="actionQuit"/>
    </widget>
+   <widget class="QMenu" name="menuHelp">
+    <property name="title">
+     <string>Help</string>
+    </property>
+    <addaction name="actionAboutSquawk"/>
+   </widget>
    <addaction name="menuFile"/>
    <addaction name="menuSettings"/>
+   <addaction name="menuHelp"/>
   </widget>
   <action name="actionAccounts">
    <property name="icon">
@@ -248,12 +255,18 @@
   </action>
   <action name="actionPreferences">
    <property name="icon">
-    <iconset theme="settings-configure"/>
+    <iconset theme="settings-configure">
+     <normaloff>.</normaloff>.</iconset>
    </property>
    <property name="text">
     <string>Preferences</string>
    </property>
   </action>
+  <action name="actionAboutSquawk">
+   <property name="text">
+    <string>About Squawk</string>
+   </property>
+  </action>
  </widget>
  <resources>
   <include location="../resources/resources.qrc"/>
diff --git a/ui/widgets/CMakeLists.txt b/ui/widgets/CMakeLists.txt
index f3a2afe..7ba83d2 100644
--- a/ui/widgets/CMakeLists.txt
+++ b/ui/widgets/CMakeLists.txt
@@ -18,6 +18,9 @@ target_sources(squawk PRIVATE
   newcontact.ui
   room.cpp
   room.h
+  about.cpp
+  about.h
+  about.ui
   )
 
 add_subdirectory(vcard)
diff --git a/ui/widgets/about.cpp b/ui/widgets/about.cpp
new file mode 100644
index 0000000..4631065
--- /dev/null
+++ b/ui/widgets/about.cpp
@@ -0,0 +1,29 @@
+// Squawk messenger. 
+// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+// 
+// 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 "about.h"
+#include "ui_about.h"
+
+About::About(QWidget* parent):
+    QWidget(parent),
+    m_ui(new Ui::About)
+{
+    m_ui->setupUi(this);
+    m_ui->versionValue->setText(QApplication::applicationVersion());
+    setWindowFlag(Qt::Tool);
+}
+
+About::~About() = default;
diff --git a/ui/widgets/about.h b/ui/widgets/about.h
new file mode 100644
index 0000000..89d879d
--- /dev/null
+++ b/ui/widgets/about.h
@@ -0,0 +1,43 @@
+// Squawk messenger. 
+// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+// 
+// 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/>.
+
+#ifndef ABOUT_H
+#define ABOUT_H
+
+#include <QWidget>
+#include <QScopedPointer>
+#include <QApplication>
+
+namespace Ui
+{
+class About;
+}
+
+/**
+ * @todo write docs
+ */
+class About : public QWidget
+{
+    Q_OBJECT
+public:
+    About(QWidget* parent = nullptr);
+    ~About();
+
+private:
+    QScopedPointer<Ui::About> m_ui;
+};
+
+#endif // ABOUT_H
diff --git a/ui/widgets/about.ui b/ui/widgets/about.ui
new file mode 100644
index 0000000..ab54df5
--- /dev/null
+++ b/ui/widgets/about.ui
@@ -0,0 +1,186 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>About</class>
+ <widget class="QWidget" name="About">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>334</width>
+    <height>321</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>About Squawk</string>
+  </property>
+  <layout class="QGridLayout" name="gridLayout">
+   <property name="verticalSpacing">
+    <number>0</number>
+   </property>
+   <item row="0" column="1" colspan="2">
+    <widget class="QLabel" name="header">
+     <property name="font">
+      <font>
+       <pointsize>12</pointsize>
+      </font>
+     </property>
+     <property name="text">
+      <string>Squawk</string>
+     </property>
+     <property name="alignment">
+      <set>Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft</set>
+     </property>
+    </widget>
+   </item>
+   <item row="0" column="3" rowspan="2">
+    <spacer name="horizontalSpacer">
+     <property name="orientation">
+      <enum>Qt::Horizontal</enum>
+     </property>
+     <property name="sizeHint" stdset="0">
+      <size>
+       <width>40</width>
+       <height>20</height>
+      </size>
+     </property>
+    </spacer>
+   </item>
+   <item row="4" column="0" colspan="4">
+    <widget class="QTabWidget" name="tabs">
+     <property name="currentIndex">
+      <number>0</number>
+     </property>
+     <widget class="QWidget" name="aboutTab">
+      <attribute name="title">
+       <string>About</string>
+      </attribute>
+      <layout class="QVBoxLayout" name="verticalLayout">
+       <item>
+        <spacer name="verticalSpacer_2">
+         <property name="orientation">
+          <enum>Qt::Vertical</enum>
+         </property>
+         <property name="sizeHint" stdset="0">
+          <size>
+           <width>20</width>
+           <height>40</height>
+          </size>
+         </property>
+        </spacer>
+       </item>
+       <item>
+        <widget class="QLabel" name="description">
+         <property name="text">
+          <string>XMPP (jabber) messenger</string>
+         </property>
+        </widget>
+       </item>
+       <item>
+        <widget class="QLabel" name="copyright">
+         <property name="text">
+          <string>(c) 2019 - 2022, Yury Gubich</string>
+         </property>
+        </widget>
+       </item>
+       <item>
+        <widget class="QLabel" name="siteLink">
+         <property name="text">
+          <string>&lt;a href=&quot;https://git.macaw.me/blue/squawk&quot;&gt;Project site&lt;/a&gt;</string>
+         </property>
+         <property name="textFormat">
+          <enum>Qt::RichText</enum>
+         </property>
+         <property name="openExternalLinks">
+          <bool>true</bool>
+         </property>
+        </widget>
+       </item>
+       <item>
+        <widget class="QLabel" name="licenceLink">
+         <property name="text">
+          <string>&lt;a href=&quot;https://git.macaw.me/blue/squawk/src/branch/master/LICENSE.md&quot;&gt;License: GNU General Public License version 3&lt;/a&gt;</string>
+         </property>
+         <property name="textFormat">
+          <enum>Qt::RichText</enum>
+         </property>
+        </widget>
+       </item>
+       <item>
+        <spacer name="verticalSpacer_3">
+         <property name="orientation">
+          <enum>Qt::Vertical</enum>
+         </property>
+         <property name="sizeHint" stdset="0">
+          <size>
+           <width>20</width>
+           <height>40</height>
+          </size>
+         </property>
+        </spacer>
+       </item>
+      </layout>
+     </widget>
+    </widget>
+   </item>
+   <item row="1" column="2">
+    <widget class="QLabel" name="versionValue">
+     <property name="text">
+      <string>0.0.0</string>
+     </property>
+    </widget>
+   </item>
+   <item row="2" column="0" colspan="4">
+    <spacer name="verticalSpacer">
+     <property name="orientation">
+      <enum>Qt::Vertical</enum>
+     </property>
+     <property name="sizeType">
+      <enum>QSizePolicy::Fixed</enum>
+     </property>
+     <property name="sizeHint" stdset="0">
+      <size>
+       <width>20</width>
+       <height>10</height>
+      </size>
+     </property>
+    </spacer>
+   </item>
+   <item row="1" column="1">
+    <widget class="QLabel" name="versionLabel">
+     <property name="text">
+      <string>Version</string>
+     </property>
+     <property name="alignment">
+      <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
+     </property>
+    </widget>
+   </item>
+   <item row="0" column="0" rowspan="2">
+    <widget class="QLabel" name="squawkIcon">
+     <property name="maximumSize">
+      <size>
+       <width>50</width>
+       <height>50</height>
+      </size>
+     </property>
+     <property name="text">
+      <string/>
+     </property>
+     <property name="pixmap">
+      <pixmap resource="../../resources/resources.qrc">:/images/logo.svg</pixmap>
+     </property>
+     <property name="scaledContents">
+      <bool>true</bool>
+     </property>
+     <property name="alignment">
+      <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
+     </property>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <resources>
+  <include location="../../resources/resources.qrc"/>
+ </resources>
+ <connections/>
+</ui>

From 9f746d203b6af51431587c38f81870b83cc3081f Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Mon, 4 Apr 2022 23:49:01 +0300
Subject: [PATCH 180/281] new tab in About: components

---
 ui/widgets/about.cpp |  15 +-
 ui/widgets/about.h   |   1 +
 ui/widgets/about.ui  | 317 +++++++++++++++++++++++++++++++++++++------
 3 files changed, 289 insertions(+), 44 deletions(-)

diff --git a/ui/widgets/about.cpp b/ui/widgets/about.cpp
index 4631065..3366284 100644
--- a/ui/widgets/about.cpp
+++ b/ui/widgets/about.cpp
@@ -16,13 +16,26 @@
 
 #include "about.h"
 #include "ui_about.h"
+#include <QXmppGlobal.h>
+
+static const std::string QXMPP_VERSION_PATCH(std::to_string(QXMPP_VERSION & 0xff));
+static const std::string QXMPP_VERSION_MINOR(std::to_string((QXMPP_VERSION & 0xff00) >> 8));
+static const std::string QXMPP_VERSION_MAJOR(std::to_string(QXMPP_VERSION >> 16));
+static const QString QXMPP_VERSION_STRING = QString::fromStdString(QXMPP_VERSION_MAJOR + "." + QXMPP_VERSION_MINOR + "." + QXMPP_VERSION_PATCH);
 
 About::About(QWidget* parent):
     QWidget(parent),
-    m_ui(new Ui::About)
+    m_ui(new Ui::About),
+    license(nullptr)
 {
     m_ui->setupUi(this);
     m_ui->versionValue->setText(QApplication::applicationVersion());
+    m_ui->qtVersionValue->setText(qVersion());
+    m_ui->qtBuiltAgainstVersion->setText(tr("(built against %1)").arg(QT_VERSION_STR));
+
+    m_ui->qxmppVersionValue->setText(QXmppVersion());
+    m_ui->qxmppBuiltAgainstVersion->setText(tr("(built against %1)").arg(QXMPP_VERSION_STRING));
+
     setWindowFlag(Qt::Tool);
 }
 
diff --git a/ui/widgets/about.h b/ui/widgets/about.h
index 89d879d..e28b362 100644
--- a/ui/widgets/about.h
+++ b/ui/widgets/about.h
@@ -38,6 +38,7 @@ public:
 
 private:
     QScopedPointer<Ui::About> m_ui;
+    QWidget* license;
 };
 
 #endif // ABOUT_H
diff --git a/ui/widgets/about.ui b/ui/widgets/about.ui
index ab54df5..58c136b 100644
--- a/ui/widgets/about.ui
+++ b/ui/widgets/about.ui
@@ -6,8 +6,8 @@
    <rect>
     <x>0</x>
     <y>0</y>
-    <width>334</width>
-    <height>321</height>
+    <width>354</width>
+    <height>349</height>
    </rect>
   </property>
   <property name="windowTitle">
@@ -17,6 +17,22 @@
    <property name="verticalSpacing">
     <number>0</number>
    </property>
+   <item row="2" column="0" colspan="4">
+    <spacer name="verticalSpacer">
+     <property name="orientation">
+      <enum>Qt::Vertical</enum>
+     </property>
+     <property name="sizeType">
+      <enum>QSizePolicy::Fixed</enum>
+     </property>
+     <property name="sizeHint" stdset="0">
+      <size>
+       <width>20</width>
+       <height>10</height>
+      </size>
+     </property>
+    </spacer>
+   </item>
    <item row="0" column="1" colspan="2">
     <widget class="QLabel" name="header">
      <property name="font">
@@ -32,6 +48,28 @@
      </property>
     </widget>
    </item>
+   <item row="0" column="0" rowspan="2">
+    <widget class="QLabel" name="squawkIcon">
+     <property name="maximumSize">
+      <size>
+       <width>50</width>
+       <height>50</height>
+      </size>
+     </property>
+     <property name="text">
+      <string/>
+     </property>
+     <property name="pixmap">
+      <pixmap resource="../../resources/resources.qrc">:/images/logo.svg</pixmap>
+     </property>
+     <property name="scaledContents">
+      <bool>true</bool>
+     </property>
+     <property name="alignment">
+      <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
+     </property>
+    </widget>
+   </item>
    <item row="0" column="3" rowspan="2">
     <spacer name="horizontalSpacer">
      <property name="orientation">
@@ -120,31 +158,239 @@
        </item>
       </layout>
      </widget>
+     <widget class="QScrollArea" name="componentsTab">
+      <property name="frameShape">
+       <enum>QFrame::NoFrame</enum>
+      </property>
+      <property name="horizontalScrollBarPolicy">
+       <enum>Qt::ScrollBarAlwaysOff</enum>
+      </property>
+      <property name="widgetResizable">
+       <bool>true</bool>
+      </property>
+      <attribute name="title">
+       <string>Components</string>
+      </attribute>
+      <widget class="QWidget" name="scrollAreaWidgetContents">
+       <property name="geometry">
+        <rect>
+         <x>0</x>
+         <y>0</y>
+         <width>334</width>
+         <height>240</height>
+        </rect>
+       </property>
+       <layout class="QVBoxLayout" name="verticalLayout_1">
+        <item>
+         <widget class="QWidget" name="widget" native="true">
+          <layout class="QGridLayout" name="gridLayout_2">
+           <property name="leftMargin">
+            <number>0</number>
+           </property>
+           <property name="topMargin">
+            <number>0</number>
+           </property>
+           <property name="rightMargin">
+            <number>0</number>
+           </property>
+           <property name="bottomMargin">
+            <number>0</number>
+           </property>
+           <property name="horizontalSpacing">
+            <number>2</number>
+           </property>
+           <property name="verticalSpacing">
+            <number>0</number>
+           </property>
+           <item row="1" column="0">
+            <widget class="QLabel" name="label_2">
+             <property name="font">
+              <font>
+               <italic>true</italic>
+              </font>
+             </property>
+             <property name="text">
+              <string>Version</string>
+             </property>
+            </widget>
+           </item>
+           <item row="1" column="1">
+            <widget class="QLabel" name="qtVersionValue">
+             <property name="font">
+              <font>
+               <italic>true</italic>
+              </font>
+             </property>
+             <property name="text">
+              <string>0.0.0</string>
+             </property>
+            </widget>
+           </item>
+           <item row="1" column="2">
+            <widget class="QLabel" name="qtBuiltAgainstVersion">
+             <property name="font">
+              <font>
+               <italic>true</italic>
+              </font>
+             </property>
+             <property name="text">
+              <string notr="true">(built against 0.0.0)</string>
+             </property>
+            </widget>
+           </item>
+           <item row="0" column="0" colspan="3">
+            <widget class="QLabel" name="label">
+             <property name="font">
+              <font>
+               <weight>75</weight>
+               <bold>true</bold>
+              </font>
+             </property>
+             <property name="text">
+              <string notr="true">Qt</string>
+             </property>
+            </widget>
+           </item>
+           <item row="0" column="3" rowspan="2">
+            <spacer name="horizontalSpacer_2">
+             <property name="orientation">
+              <enum>Qt::Horizontal</enum>
+             </property>
+             <property name="sizeHint" stdset="0">
+              <size>
+               <width>40</width>
+               <height>20</height>
+              </size>
+             </property>
+            </spacer>
+           </item>
+           <item row="2" column="0" colspan="4">
+            <widget class="QLabel" name="label_3">
+             <property name="text">
+              <string notr="true">&lt;a href=&quot;https://www.qt.io/&quot;&gt;www.qt.io&lt;/a&gt;</string>
+             </property>
+             <property name="openExternalLinks">
+              <bool>true</bool>
+             </property>
+            </widget>
+           </item>
+          </layout>
+         </widget>
+        </item>
+        <item>
+         <widget class="QWidget" name="widget_2" native="true">
+          <layout class="QGridLayout" name="gridLayout_3">
+           <property name="leftMargin">
+            <number>0</number>
+           </property>
+           <property name="topMargin">
+            <number>0</number>
+           </property>
+           <property name="rightMargin">
+            <number>0</number>
+           </property>
+           <property name="bottomMargin">
+            <number>0</number>
+           </property>
+           <property name="horizontalSpacing">
+            <number>2</number>
+           </property>
+           <property name="verticalSpacing">
+            <number>0</number>
+           </property>
+           <item row="1" column="0">
+            <widget class="QLabel" name="label_4">
+             <property name="font">
+              <font>
+               <italic>true</italic>
+              </font>
+             </property>
+             <property name="text">
+              <string>Version</string>
+             </property>
+            </widget>
+           </item>
+           <item row="1" column="1">
+            <widget class="QLabel" name="qxmppVersionValue">
+             <property name="font">
+              <font>
+               <italic>true</italic>
+              </font>
+             </property>
+             <property name="text">
+              <string>0.0.0</string>
+             </property>
+            </widget>
+           </item>
+           <item row="1" column="2">
+            <widget class="QLabel" name="qxmppBuiltAgainstVersion">
+             <property name="font">
+              <font>
+               <italic>true</italic>
+              </font>
+             </property>
+             <property name="text">
+              <string notr="true">(built against 0.0.0)</string>
+             </property>
+            </widget>
+           </item>
+           <item row="0" column="0" colspan="3">
+            <widget class="QLabel" name="label_5">
+             <property name="font">
+              <font>
+               <weight>75</weight>
+               <bold>true</bold>
+              </font>
+             </property>
+             <property name="text">
+              <string notr="true">QXmpp</string>
+             </property>
+            </widget>
+           </item>
+           <item row="0" column="3" rowspan="2">
+            <spacer name="horizontalSpacer_3">
+             <property name="orientation">
+              <enum>Qt::Horizontal</enum>
+             </property>
+             <property name="sizeHint" stdset="0">
+              <size>
+               <width>40</width>
+               <height>20</height>
+              </size>
+             </property>
+            </spacer>
+           </item>
+           <item row="2" column="0" colspan="4">
+            <widget class="QLabel" name="label_6">
+             <property name="text">
+              <string notr="true">&lt;a href=&quot;https://github.com/qxmpp-project/qxmpp&quot;&gt;github.com/qxmpp-project/qxmpp&lt;/a&gt;</string>
+             </property>
+             <property name="openExternalLinks">
+              <bool>true</bool>
+             </property>
+            </widget>
+           </item>
+          </layout>
+         </widget>
+        </item>
+        <item>
+         <spacer name="verticalSpacer_4">
+          <property name="orientation">
+           <enum>Qt::Vertical</enum>
+          </property>
+          <property name="sizeHint" stdset="0">
+           <size>
+            <width>20</width>
+            <height>40</height>
+           </size>
+          </property>
+         </spacer>
+        </item>
+       </layout>
+      </widget>
+     </widget>
     </widget>
    </item>
-   <item row="1" column="2">
-    <widget class="QLabel" name="versionValue">
-     <property name="text">
-      <string>0.0.0</string>
-     </property>
-    </widget>
-   </item>
-   <item row="2" column="0" colspan="4">
-    <spacer name="verticalSpacer">
-     <property name="orientation">
-      <enum>Qt::Vertical</enum>
-     </property>
-     <property name="sizeType">
-      <enum>QSizePolicy::Fixed</enum>
-     </property>
-     <property name="sizeHint" stdset="0">
-      <size>
-       <width>20</width>
-       <height>10</height>
-      </size>
-     </property>
-    </spacer>
-   </item>
    <item row="1" column="1">
     <widget class="QLabel" name="versionLabel">
      <property name="text">
@@ -155,25 +401,10 @@
      </property>
     </widget>
    </item>
-   <item row="0" column="0" rowspan="2">
-    <widget class="QLabel" name="squawkIcon">
-     <property name="maximumSize">
-      <size>
-       <width>50</width>
-       <height>50</height>
-      </size>
-     </property>
+   <item row="1" column="2">
+    <widget class="QLabel" name="versionValue">
      <property name="text">
-      <string/>
-     </property>
-     <property name="pixmap">
-      <pixmap resource="../../resources/resources.qrc">:/images/logo.svg</pixmap>
-     </property>
-     <property name="scaledContents">
-      <bool>true</bool>
-     </property>
-     <property name="alignment">
-      <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
+      <string>0.0.0</string>
      </property>
     </widget>
    </item>

From 1b66fda3181b38bf4864fb1591291e3391aa03fb Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Tue, 5 Apr 2022 22:00:56 +0300
Subject: [PATCH 181/281] License is now can be viewed locally, some
 organization name packaging issies

---
 CMakeLists.txt              |  2 ++
 LICENSE.md                  |  6 ++--
 core/main.cpp               |  2 +-
 translations/CMakeLists.txt |  2 +-
 ui/squawk.cpp               |  3 +-
 ui/widgets/about.cpp        | 68 ++++++++++++++++++++++++++++++++++++-
 ui/widgets/about.h          |  7 ++++
 7 files changed, 82 insertions(+), 8 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 7d0ee7f..85aa98a 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -159,6 +159,8 @@ add_subdirectory(ui)
 
 # Install the executable
 install(TARGETS squawk DESTINATION ${CMAKE_INSTALL_BINDIR})
+install(FILES README.md DESTINATION ${CMAKE_INSTALL_DATADIR}/macaw.me/squawk)
+install(FILES LICENSE.md DESTINATION ${CMAKE_INSTALL_DATADIR}/macaw.me/squawk)
 
 if (CMAKE_BUILD_TYPE STREQUAL "Release")
   if (APPLE)
diff --git a/LICENSE.md b/LICENSE.md
index 85c7c69..32b38d4 100644
--- a/LICENSE.md
+++ b/LICENSE.md
@@ -595,17 +595,17 @@ pointer to where the full notice is found.
 
 	<one line to give the program's name and a brief idea of what it does.>
 	Copyright (C) <year>  <name of author>
-	
+
 	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/>.
 
diff --git a/core/main.cpp b/core/main.cpp
index 7d3c3ab..4fbb1f7 100644
--- a/core/main.cpp
+++ b/core/main.cpp
@@ -54,7 +54,7 @@ int main(int argc, char *argv[])
     QTranslator qtTranslator;
     qtTranslator.load("qt_" + QLocale::system().name(), QLibraryInfo::location(QLibraryInfo::TranslationsPath));
     app.installTranslator(&qtTranslator);
-    
+
     QTranslator myappTranslator;
     QStringList shares = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation);
     bool found = false;
diff --git a/translations/CMakeLists.txt b/translations/CMakeLists.txt
index 86d2a8c..eee4f98 100644
--- a/translations/CMakeLists.txt
+++ b/translations/CMakeLists.txt
@@ -6,6 +6,6 @@ set(TS_FILES
 )
 qt5_add_translation(QM_FILES ${TS_FILES})
 add_custom_target(translations ALL DEPENDS ${QM_FILES})
-install(FILES ${QM_FILES} DESTINATION ${CMAKE_INSTALL_DATADIR}/squawk/l10n)
+install(FILES ${QM_FILES} DESTINATION ${CMAKE_INSTALL_DATADIR}/macaw.me/squawk/l10n)
 
 add_dependencies(${CMAKE_PROJECT_NAME} translations)
diff --git a/ui/squawk.cpp b/ui/squawk.cpp
index 4594c01..4c7320b 100644
--- a/ui/squawk.cpp
+++ b/ui/squawk.cpp
@@ -1098,12 +1098,11 @@ void Squawk::onAboutSquawkCalled()
         about = new About();
         about->setAttribute(Qt::WA_DeleteOnClose);
         connect(about, &Settings::destroyed, this, &Squawk::onAboutSquawkClosed);
-        about->show();
     } else {
         about->raise();
         about->activateWindow();
-        about->show();
     }
+    about->show();
 }
 
 void Squawk::onAboutSquawkClosed()
diff --git a/ui/widgets/about.cpp b/ui/widgets/about.cpp
index 3366284..3782a94 100644
--- a/ui/widgets/about.cpp
+++ b/ui/widgets/about.cpp
@@ -17,6 +17,7 @@
 #include "about.h"
 #include "ui_about.h"
 #include <QXmppGlobal.h>
+#include <QDebug>
 
 static const std::string QXMPP_VERSION_PATCH(std::to_string(QXMPP_VERSION & 0xff));
 static const std::string QXMPP_VERSION_MINOR(std::to_string((QXMPP_VERSION & 0xff00) >> 8));
@@ -37,6 +38,71 @@ About::About(QWidget* parent):
     m_ui->qxmppBuiltAgainstVersion->setText(tr("(built against %1)").arg(QXMPP_VERSION_STRING));
 
     setWindowFlag(Qt::Tool);
+
+    connect(m_ui->licenceLink, &QLabel::linkActivated, this, &About::onLicenseActivated);
 }
 
-About::~About() = default;
+About::~About() {
+    if (license != nullptr) {
+        license->deleteLater();
+    }
+};
+
+void About::onLicenseActivated()
+{
+    if (license == nullptr) {
+        QFile file;
+        bool found = false;
+        QStringList shares = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation);
+        for (const QString& path : shares) {
+            file.setFileName(path + "/LICENSE.md");
+
+            if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
+                found = true;
+                break;
+            }
+        }
+        if (!found) {
+            qDebug() << "couldn't read license file, bailing";
+            return;
+        }
+
+        license = new QWidget();
+        license->setWindowTitle(tr("License"));
+        QVBoxLayout* layout = new QVBoxLayout(license);
+        QLabel* text = new QLabel(license);
+        QScrollArea* area = new QScrollArea(license);
+        text->setTextFormat(Qt::MarkdownText);
+        text->setWordWrap(true);
+        text->setOpenExternalLinks(true);
+        text->setMargin(5);
+        area->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
+
+        layout->addWidget(area);
+        license->setAttribute(Qt::WA_DeleteOnClose);
+        connect(license, &QWidget::destroyed, this, &About::onLicenseClosed);
+
+        QTextStream in(&file);
+        QString line;
+        QString licenseText("");
+        while (!in.atEnd()) {
+            line = in.readLine();
+            licenseText.append(line + "\n");
+        }
+        text->setText(licenseText);
+        file.close();
+
+        area->setWidget(text);
+
+    } else {
+        license->raise();
+        license->activateWindow();
+    }
+
+    license->show();
+}
+
+void About::onLicenseClosed()
+{
+    license = nullptr;
+}
diff --git a/ui/widgets/about.h b/ui/widgets/about.h
index e28b362..1506b7f 100644
--- a/ui/widgets/about.h
+++ b/ui/widgets/about.h
@@ -20,6 +20,9 @@
 #include <QWidget>
 #include <QScopedPointer>
 #include <QApplication>
+#include <QFile>
+#include <QTextStream>
+#include <QStandardPaths>
 
 namespace Ui
 {
@@ -36,6 +39,10 @@ public:
     About(QWidget* parent = nullptr);
     ~About();
 
+protected slots:
+    void onLicenseActivated();
+    void onLicenseClosed();
+
 private:
     QScopedPointer<Ui::About> m_ui;
     QWidget* license;

From 82d54ba4df869981559ba835c836c62112f8f642 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Thu, 7 Apr 2022 18:26:43 +0300
Subject: [PATCH 182/281] Report bugs tab and thanks to tab in about widget

---
 ui/widgets/about.ui | 271 +++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 267 insertions(+), 4 deletions(-)

diff --git a/ui/widgets/about.ui b/ui/widgets/about.ui
index 58c136b..e7b9ce4 100644
--- a/ui/widgets/about.ui
+++ b/ui/widgets/about.ui
@@ -6,8 +6,8 @@
    <rect>
     <x>0</x>
     <y>0</y>
-    <width>354</width>
-    <height>349</height>
+    <width>375</width>
+    <height>290</height>
    </rect>
   </property>
   <property name="windowTitle">
@@ -88,6 +88,9 @@
      <property name="currentIndex">
       <number>0</number>
      </property>
+     <property name="usesScrollButtons">
+      <bool>false</bool>
+     </property>
      <widget class="QWidget" name="aboutTab">
       <attribute name="title">
        <string>About</string>
@@ -176,8 +179,8 @@
         <rect>
          <x>0</x>
          <y>0</y>
-         <width>334</width>
-         <height>240</height>
+         <width>355</width>
+         <height>181</height>
         </rect>
        </property>
        <layout class="QVBoxLayout" name="verticalLayout_1">
@@ -389,6 +392,266 @@
        </layout>
       </widget>
      </widget>
+     <widget class="QWidget" name="reportTab">
+      <attribute name="title">
+       <string>Report Bugs</string>
+      </attribute>
+      <layout class="QVBoxLayout" name="verticalLayout_2">
+       <item>
+        <spacer name="verticalSpacer_6">
+         <property name="orientation">
+          <enum>Qt::Vertical</enum>
+         </property>
+         <property name="sizeHint" stdset="0">
+          <size>
+           <width>20</width>
+           <height>40</height>
+          </size>
+         </property>
+        </spacer>
+       </item>
+       <item>
+        <widget class="QLabel" name="label_8">
+         <property name="text">
+          <string>Please report any bug you find!
+To report bugs you can use:</string>
+         </property>
+        </widget>
+       </item>
+       <item>
+        <widget class="QLabel" name="label_7">
+         <property name="text">
+          <string>&lt;a href=&quot;https://git.macaw.me/blue/squawk/issues&quot;&gt;Project bug tracker&lt;/&gt;</string>
+         </property>
+         <property name="openExternalLinks">
+          <bool>true</bool>
+         </property>
+        </widget>
+       </item>
+       <item>
+        <widget class="QLabel" name="label_10">
+         <property name="text">
+          <string>XMPP (&lt;a href=&quot;xmpp:blue@macaw.me&quot;&gt;blue@macaw.me&lt;/a&gt;)</string>
+         </property>
+         <property name="openExternalLinks">
+          <bool>true</bool>
+         </property>
+        </widget>
+       </item>
+       <item>
+        <widget class="QLabel" name="label_9">
+         <property name="text">
+          <string>E-Mail (&lt;a href=&quot;mailto:blue@macaw.me&quot;&gt;blue@macaw.me&lt;/a&gt;)</string>
+         </property>
+         <property name="openExternalLinks">
+          <bool>true</bool>
+         </property>
+        </widget>
+       </item>
+       <item>
+        <spacer name="verticalSpacer_5">
+         <property name="orientation">
+          <enum>Qt::Vertical</enum>
+         </property>
+         <property name="sizeHint" stdset="0">
+          <size>
+           <width>20</width>
+           <height>40</height>
+          </size>
+         </property>
+        </spacer>
+       </item>
+      </layout>
+     </widget>
+     <widget class="QScrollArea" name="thanksTab">
+      <property name="frameShape">
+       <enum>QFrame::NoFrame</enum>
+      </property>
+      <property name="horizontalScrollBarPolicy">
+       <enum>Qt::ScrollBarAlwaysOff</enum>
+      </property>
+      <property name="widgetResizable">
+       <bool>true</bool>
+      </property>
+      <attribute name="title">
+       <string>Thanks To</string>
+      </attribute>
+      <widget class="QWidget" name="thanksTabContent">
+       <property name="geometry">
+        <rect>
+         <x>0</x>
+         <y>0</y>
+         <width>355</width>
+         <height>181</height>
+        </rect>
+       </property>
+       <layout class="QVBoxLayout" name="verticalLayout_3">
+        <property name="spacing">
+         <number>10</number>
+        </property>
+        <item>
+         <widget class="QWidget" name="widget_3" native="true">
+          <layout class="QGridLayout" name="gridLayout_4">
+           <property name="leftMargin">
+            <number>0</number>
+           </property>
+           <property name="topMargin">
+            <number>0</number>
+           </property>
+           <property name="rightMargin">
+            <number>0</number>
+           </property>
+           <property name="bottomMargin">
+            <number>0</number>
+           </property>
+           <property name="spacing">
+            <number>0</number>
+           </property>
+           <item row="0" column="0">
+            <widget class="QLabel" name="label_11">
+             <property name="font">
+              <font>
+               <weight>75</weight>
+               <bold>true</bold>
+              </font>
+             </property>
+             <property name="text">
+              <string>Vae</string>
+             </property>
+            </widget>
+           </item>
+           <item row="1" column="0">
+            <widget class="QLabel" name="label_12">
+             <property name="font">
+              <font>
+               <italic>true</italic>
+              </font>
+             </property>
+             <property name="text">
+              <string>Major refactoring, bug fixes, constructive criticism</string>
+             </property>
+             <property name="wordWrap">
+              <bool>true</bool>
+             </property>
+            </widget>
+           </item>
+          </layout>
+         </widget>
+        </item>
+        <item>
+         <widget class="QWidget" name="widget_4" native="true">
+          <layout class="QGridLayout" name="gridLayout_5">
+           <property name="leftMargin">
+            <number>0</number>
+           </property>
+           <property name="topMargin">
+            <number>0</number>
+           </property>
+           <property name="rightMargin">
+            <number>0</number>
+           </property>
+           <property name="bottomMargin">
+            <number>0</number>
+           </property>
+           <property name="spacing">
+            <number>0</number>
+           </property>
+           <item row="0" column="0">
+            <widget class="QLabel" name="label_13">
+             <property name="font">
+              <font>
+               <weight>75</weight>
+               <bold>true</bold>
+              </font>
+             </property>
+             <property name="text">
+              <string>Shunf4</string>
+             </property>
+            </widget>
+           </item>
+           <item row="1" column="0">
+            <widget class="QLabel" name="label_14">
+             <property name="font">
+              <font>
+               <italic>true</italic>
+              </font>
+             </property>
+             <property name="text">
+              <string>Major refactoring, bug fixes, build adaptations for Windows and MacOS</string>
+             </property>
+             <property name="wordWrap">
+              <bool>true</bool>
+             </property>
+            </widget>
+           </item>
+          </layout>
+         </widget>
+        </item>
+        <item>
+         <widget class="QWidget" name="widget_5" native="true">
+          <layout class="QGridLayout" name="gridLayout_6">
+           <property name="leftMargin">
+            <number>0</number>
+           </property>
+           <property name="topMargin">
+            <number>0</number>
+           </property>
+           <property name="rightMargin">
+            <number>0</number>
+           </property>
+           <property name="bottomMargin">
+            <number>0</number>
+           </property>
+           <property name="spacing">
+            <number>0</number>
+           </property>
+           <item row="0" column="0">
+            <widget class="QLabel" name="label_15">
+             <property name="font">
+              <font>
+               <weight>75</weight>
+               <bold>true</bold>
+              </font>
+             </property>
+             <property name="text">
+              <string>Bruno F. Fontes</string>
+             </property>
+            </widget>
+           </item>
+           <item row="1" column="0">
+            <widget class="QLabel" name="label_16">
+             <property name="font">
+              <font>
+               <italic>true</italic>
+              </font>
+             </property>
+             <property name="text">
+              <string>Brazilian Portuguese translation</string>
+             </property>
+             <property name="wordWrap">
+              <bool>true</bool>
+             </property>
+            </widget>
+           </item>
+          </layout>
+         </widget>
+        </item>
+        <item>
+         <spacer name="verticalSpacer_7">
+          <property name="orientation">
+           <enum>Qt::Vertical</enum>
+          </property>
+          <property name="sizeHint" stdset="0">
+           <size>
+            <width>20</width>
+            <height>0</height>
+           </size>
+          </property>
+         </spacer>
+        </item>
+       </layout>
+      </widget>
+     </widget>
     </widget>
    </item>
    <item row="1" column="1">

From 69e0c88d8d278925c005418fca0b2caa33ce0405 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Fri, 8 Apr 2022 19:18:15 +0300
Subject: [PATCH 183/281] account refactoring, pep support discovery started

---
 core/CMakeLists.txt                           |   3 +-
 core/account.cpp                              | 521 +++++-------------
 core/account.h                                |  19 +-
 ...apterFuctions.cpp => adapterfunctions.cpp} |   8 +-
 core/adapterfunctions.h                       |  32 ++
 core/handlers/CMakeLists.txt                  |   2 +
 core/handlers/messagehandler.cpp              |   2 +-
 core/handlers/rosterhandler.cpp               |  15 +-
 core/handlers/rosterhandler.h                 |   2 +
 core/handlers/vcardhandler.cpp                | 312 +++++++++++
 core/handlers/vcardhandler.h                  |  65 +++
 core/rosteritem.h                             |   1 +
 12 files changed, 588 insertions(+), 394 deletions(-)
 rename core/{adapterFuctions.cpp => adapterfunctions.cpp} (98%)
 create mode 100644 core/adapterfunctions.h
 create mode 100644 core/handlers/vcardhandler.cpp
 create mode 100644 core/handlers/vcardhandler.h

diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt
index 9369cb7..8b6fa69 100644
--- a/core/CMakeLists.txt
+++ b/core/CMakeLists.txt
@@ -6,7 +6,8 @@ endif(WIN32)
 target_sources(squawk PRIVATE
   account.cpp
   account.h
-  adapterFuctions.cpp
+  adapterfunctions.cpp
+  adapterfunctions.h
   archive.cpp
   archive.h
   conference.cpp
diff --git a/core/account.cpp b/core/account.cpp
index 91d0f2b..3d782cd 100644
--- a/core/account.cpp
+++ b/core/account.cpp
@@ -41,13 +41,12 @@ Account::Account(const QString& p_login, const QString& p_server, const QString&
     rcpm(new QXmppMessageReceiptManager()),
     reconnectScheduled(false),
     reconnectTimer(new QTimer),
-    avatarHash(),
-    avatarType(),
-    ownVCardRequestInProgress(false),
     network(p_net),
     passwordType(Shared::AccountPassword::plain),
+    pepSupport(false),
     mh(new MessageHandler(this)),
-    rh(new RosterHandler(this))
+    rh(new RosterHandler(this)),
+    vh(new VCardHandler(this))
 {
     config.setUser(p_login);
     config.setDomain(p_server);
@@ -73,10 +72,6 @@ Account::Account(const QString& p_login, const QString& p_server, const QString&
     
     client.addExtension(mm);
     client.addExtension(bm);
-    
-    QObject::connect(vm, &QXmppVCardManager::vCardReceived, this, &Account::onVCardReceived);
-    //QObject::connect(&vm, &QXmppVCardManager::clientVCardReceived, this, &Account::onOwnVCardReceived); //for some reason it doesn't work, launching from common handler
-    
     client.addExtension(um);
     QObject::connect(um, &QXmppUploadRequestManager::slotReceived, mh, &MessageHandler::onUploadSlotReceived);
     QObject::connect(um, &QXmppUploadRequestManager::requestFailed, mh, &MessageHandler::onUploadSlotRequestFailed);
@@ -91,52 +86,6 @@ Account::Account(const QString& p_login, const QString& p_server, const QString&
     client.addExtension(rcpm);
     QObject::connect(rcpm, &QXmppMessageReceiptManager::messageDelivered, mh, &MessageHandler::onReceiptReceived);
     
-    
-    QString path(QStandardPaths::writableLocation(QStandardPaths::CacheLocation));
-    path += "/" + name;
-    QDir dir(path);
-    
-    if (!dir.exists()) {
-        bool res = dir.mkpath(path);
-        if (!res) {
-            qDebug() << "Couldn't create a cache directory for account" << name;
-            throw 22;
-        }
-    }
-    
-    QFile* avatar = new QFile(path + "/avatar.png");
-    QString type = "png";
-    if (!avatar->exists()) {
-        delete avatar;
-        avatar = new QFile(path + "/avatar.jpg");
-        type = "jpg";
-        if (!avatar->exists()) {
-            delete avatar;
-            avatar = new QFile(path + "/avatar.jpeg");
-            type = "jpeg";
-            if (!avatar->exists()) {
-                delete avatar;
-                avatar = new QFile(path + "/avatar.gif");
-                type = "gif";
-            }
-        }
-    }
-    
-    if (avatar->exists()) {
-        if (avatar->open(QFile::ReadOnly)) {
-            QCryptographicHash sha1(QCryptographicHash::Sha1);
-            sha1.addData(avatar);
-            avatarHash = sha1.result();
-            avatarType = type;
-        }
-    }
-    if (avatarType.size() != 0) {
-        presence.setVCardUpdateType(QXmppPresence::VCardUpdateValidPhoto);
-        presence.setPhotoHash(avatarHash.toUtf8());
-    } else {
-        presence.setVCardUpdateType(QXmppPresence::VCardUpdateNotReady);
-    }
-    
     reconnectTimer->setSingleShot(true);
     QObject::connect(reconnectTimer, &QTimer::timeout, this, &Account::onReconnectTimer);
     
@@ -160,6 +109,7 @@ Account::~Account()
     QObject::disconnect(network, &NetworkAccess::downloadFileComplete, mh, &MessageHandler::onDownloadFileComplete);
     QObject::disconnect(network, &NetworkAccess::loadFileError, mh, &MessageHandler::onLoadFileError);
     
+    delete vh;
     delete mh;
     delete rh;
     
@@ -264,36 +214,6 @@ void Core::Account::reconnect()
     }
 }
 
-QString Core::Account::getName() const {
-    return name;}
-
-QString Core::Account::getLogin() const {
-    return config.user();}
-
-QString Core::Account::getPassword() const {
-    return config.password();}
-
-QString Core::Account::getServer() const {
-    return config.domain();}
-
-Shared::AccountPassword Core::Account::getPasswordType() const {
-    return passwordType;}
-
-void Core::Account::setPasswordType(Shared::AccountPassword pt) {
-    passwordType = pt; }
-
-void Core::Account::setLogin(const QString& p_login) {
-    config.setUser(p_login);}
-
-void Core::Account::setName(const QString& p_name) {
-    name = p_name;}
-
-void Core::Account::setPassword(const QString& p_password) {
-    config.setPassword(p_password);}
-
-void Core::Account::setServer(const QString& p_server) {
-    config.setDomain(p_server);}
-
 Shared::Availability Core::Account::getAvailability() const
 {
     if (state == Shared::ConnectionState::connected) {
@@ -325,32 +245,11 @@ void Core::Account::onPresenceReceived(const QXmppPresence& p_presence)
     QString jid = comps.front().toLower();
     QString resource = comps.back();
     
-    QString myJid = getLogin() + "@" + getServer();
-    
-    if (jid == myJid) {
+    if (jid == getBareJid()) {
         if (resource == getResource()) {
             emit availabilityChanged(static_cast<Shared::Availability>(p_presence.availableStatusType()));
         } else {
-            if (!ownVCardRequestInProgress) {
-                switch (p_presence.vCardUpdateType()) {
-                    case QXmppPresence::VCardUpdateNone:            //this presence has nothing to do with photo
-                        break;
-                    case QXmppPresence::VCardUpdateNotReady:        //let's say the photo didn't change here
-                        break;
-                    case QXmppPresence::VCardUpdateNoPhoto:         //there is no photo, need to drop if any
-                        if (avatarType.size() > 0) {
-                            vm->requestClientVCard();
-                            ownVCardRequestInProgress = true;
-                        }
-                        break;
-                    case QXmppPresence::VCardUpdateValidPhoto:      //there is a photo, need to load
-                        if (avatarHash != p_presence.photoHash()) {
-                            vm->requestClientVCard();
-                            ownVCardRequestInProgress = true;
-                        }
-                        break;
-                }
-            }
+            vh->handleOtherPresenceOfMyAccountChange(p_presence);
         }
     } else {
         RosterItem* item = rh->getRosterItem(jid);
@@ -392,18 +291,6 @@ void Core::Account::onPresenceReceived(const QXmppPresence& p_presence)
     }
 }
 
-QString Core::Account::getResource() const {
-    return config.resource();}
-
-void Core::Account::setResource(const QString& p_resource) {
-    config.setResource(p_resource);}
-
-QString Core::Account::getFullJid() const {
-    return getLogin() + "@" + getServer() + "/" + getResource();}
-
-void Core::Account::sendMessage(const Shared::Message& data) {
-    mh->sendMessage(data);}
-
 void Core::Account::onMamMessageReceived(const QString& queryId, const QXmppMessage& msg)
 {
     if (msg.id().size() > 0 && (msg.body().size() > 0 || msg.outOfBandUrl().size() > 0)) {
@@ -667,6 +554,151 @@ void Core::Account::setRoomJoined(const QString& jid, bool joined)
     conf->setJoined(joined);
 }
 
+void Core::Account::onDiscoveryItemsReceived(const QXmppDiscoveryIq& items)
+{
+    if (items.from() == getServer()) {
+        std::set<QString> needToRequest;
+        qDebug() << "Server items list received for account " << name << ":";
+        for (QXmppDiscoveryIq::Item item : items.items()) {
+            QString jid = item.jid();
+            if (jid != getServer()) {
+                qDebug() << "     Node" << jid;
+                needToRequest.insert(jid);
+            } else {
+                qDebug() << "    " << item.node().toStdString().c_str();
+            }
+        }
+
+        for (const QString& jid : needToRequest) {
+            dm->requestInfo(jid);
+        }
+    }
+}
+
+void Core::Account::onDiscoveryInfoReceived(const QXmppDiscoveryIq& info)
+{
+    if (info.from() == getServer()) {
+        bool enableCC = false;
+        qDebug() << "Server info received for account" << name;
+        QStringList features = info.features();
+        qDebug() << "List of supported features of the server " << getServer() << ":";
+        for (const QString& feature : features) {
+            qDebug() << "    " << feature.toStdString().c_str();
+            if (feature == "urn:xmpp:carbons:2") {
+                enableCC = true;
+            }
+        }
+
+        if (enableCC) {
+            qDebug() << "Enabling carbon copies for account" << name;
+            cm->setCarbonsEnabled(true);
+        }
+
+        qDebug() << "Requesting account" << name << "capabilities";
+        dm->requestInfo(getBareJid());
+    } else if (info.from() == getBareJid()) {
+        qDebug() << "Received capabilities for account" << name << ":";
+        QList<QXmppDiscoveryIq::Identity> identities = info.identities();
+        bool pepSupported = false;
+        for (const QXmppDiscoveryIq::Identity& identity : identities) {
+            QString type = identity.type();
+            qDebug() << "    " << identity.category() << type;
+            if (type == "pep") {
+                pepSupported = true;
+            }
+        }
+        rh->setPepSupport(pepSupported);
+    } else  {
+        qDebug() << "Received info for account" << name << "about" << info.from();
+        QList<QXmppDiscoveryIq::Identity> identities = info.identities();
+        for (const QXmppDiscoveryIq::Identity& identity : identities) {
+            qDebug() << "    " << identity.name() << identity.category() << identity.type();
+        }
+    }
+}
+
+void Core::Account::handleDisconnection()
+{
+    cm->setCarbonsEnabled(false);
+    rh->handleOffline();
+    vh->handleOffline();
+    archiveQueries.clear();
+}
+
+void Core::Account::onContactHistoryResponse(const std::list<Shared::Message>& list, bool last)
+{
+    RosterItem* contact = static_cast<RosterItem*>(sender());
+    
+    qDebug() << "Collected history for contact " << contact->jid << list.size() << "elements";
+    if (last) {
+        qDebug() << "The response contains the first accounted message";
+    }
+    emit responseArchive(contact->jid, list, last);
+}
+
+QString Core::Account::getResource() const {
+    return config.resource();}
+
+void Core::Account::setResource(const QString& p_resource) {
+    config.setResource(p_resource);}
+
+QString Core::Account::getBareJid() const {
+    return getLogin() + "@" + getServer();}
+
+QString Core::Account::getFullJid() const {
+    return getBareJid() + "/" + getResource();}
+
+QString Core::Account::getName() const {
+    return name;}
+
+QString Core::Account::getLogin() const {
+    return config.user();}
+
+QString Core::Account::getPassword() const {
+    return config.password();}
+
+QString Core::Account::getServer() const {
+    return config.domain();}
+
+Shared::AccountPassword Core::Account::getPasswordType() const {
+    return passwordType;}
+
+void Core::Account::setPasswordType(Shared::AccountPassword pt) {
+    passwordType = pt; }
+
+void Core::Account::setLogin(const QString& p_login) {
+    config.setUser(p_login);}
+
+void Core::Account::setName(const QString& p_name) {
+    name = p_name;}
+
+void Core::Account::setPassword(const QString& p_password) {
+    config.setPassword(p_password);}
+
+void Core::Account::setServer(const QString& p_server) {
+    config.setDomain(p_server);}
+
+void Core::Account::sendMessage(const Shared::Message& data) {
+    mh->sendMessage(data);}
+
+void Core::Account::requestChangeMessage(const QString& jid, const QString& messageId, const QMap<QString, QVariant>& data){
+    mh->requestChangeMessage(jid, messageId, data);}
+
+void Core::Account::resendMessage(const QString& jid, const QString& id) {
+    mh->resendMessage(jid, id);}
+
+void Core::Account::replaceMessage(const QString& originalId, const Shared::Message& data) {
+    mh->sendMessage(data, false, originalId);}
+
+void Core::Account::requestVCard(const QString& jid) {
+    vh->requestVCard(jid);}
+
+void Core::Account::uploadVCard(const Shared::VCard& card) {
+    vh->uploadVCard(card);}
+
+QString Core::Account::getAvatarPath() const {
+    return vh->getAvatarPath();}
+
 void Core::Account::removeRoomRequest(const QString& jid){
     rh->removeRoomRequest(jid);}
 
@@ -688,254 +720,3 @@ void Core::Account::renameContactRequest(const QString& jid, const QString& newN
         rm->renameItem(jid, newName);
     }
 }
-
-void Core::Account::onVCardReceived(const QXmppVCardIq& card)
-{
-    QString id = card.from();
-    QStringList comps = id.split("/");
-    QString jid = comps.front().toLower();
-    QString resource("");
-    if (comps.size() > 1) {
-        resource = comps.back();
-    }
-    pendingVCardRequests.erase(id);
-    RosterItem* item = rh->getRosterItem(jid);
-    
-    if (item == 0) {
-        if (jid == getLogin() + "@" + getServer()) {
-            onOwnVCardReceived(card);
-        } else {
-            qDebug() << "received vCard" << jid << "doesn't belong to any of known contacts or conferences, skipping";
-        }
-        return;
-    }
-    
-    Shared::VCard vCard = item->handleResponseVCard(card, resource);
-    
-    emit receivedVCard(jid, vCard);
-}
-
-void Core::Account::onOwnVCardReceived(const QXmppVCardIq& card)
-{
-    QByteArray ava = card.photo();
-    bool avaChanged = false;
-    QString path = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/" + name + "/";
-    if (ava.size() > 0) {
-        QCryptographicHash sha1(QCryptographicHash::Sha1);
-        sha1.addData(ava);
-        QString newHash(sha1.result());
-        QMimeDatabase db;
-        QMimeType newType = db.mimeTypeForData(ava);
-        if (avatarType.size() > 0) {
-            if (avatarHash != newHash) {
-                QString oldPath = path + "avatar." + avatarType;
-                QFile oldAvatar(oldPath);
-                bool oldToRemove = false;
-                if (oldAvatar.exists()) {
-                    if (oldAvatar.rename(oldPath + ".bak")) {
-                        oldToRemove = true;
-                    } else {
-                        qDebug() << "Received new avatar for account" << name << "but can't get rid of the old one, doing nothing";
-                    }
-                }
-                QFile newAvatar(path + "avatar." + newType.preferredSuffix());
-                if (newAvatar.open(QFile::WriteOnly)) {
-                    newAvatar.write(ava);
-                    newAvatar.close();
-                    avatarHash = newHash;
-                    avatarType = newType.preferredSuffix();
-                    avaChanged = true;
-                } else {
-                    qDebug() << "Received new avatar for account" << name << "but can't save it";
-                    if (oldToRemove) {
-                        qDebug() << "rolling back to the old avatar";
-                        if (!oldAvatar.rename(oldPath)) {
-                            qDebug() << "Couldn't roll back to the old avatar in account" << name;
-                        }
-                    }
-                }
-            }
-        } else {
-            QFile newAvatar(path + "avatar." + newType.preferredSuffix());
-            if (newAvatar.open(QFile::WriteOnly)) {
-                newAvatar.write(ava);
-                newAvatar.close();
-                avatarHash = newHash;
-                avatarType = newType.preferredSuffix();
-                avaChanged = true;
-            } else {
-                qDebug() << "Received new avatar for account" << name << "but can't save it";
-            }
-        }
-    } else {
-        if (avatarType.size() > 0) {
-            QFile oldAvatar(path + "avatar." + avatarType);
-            if (!oldAvatar.remove()) {
-                qDebug() << "Received vCard for account" << name << "without avatar, but can't get rid of the file, doing nothing";
-            } else {
-                avatarType = "";
-                avatarHash = "";
-                avaChanged = true;
-            }
-        }
-    }
-    
-    if (avaChanged) {
-        QMap<QString, QVariant> change;
-        if (avatarType.size() > 0) {
-            presence.setPhotoHash(avatarHash.toUtf8());
-            presence.setVCardUpdateType(QXmppPresence::VCardUpdateValidPhoto);
-            change.insert("avatarPath", path + "avatar." + avatarType);
-        } else {
-            presence.setPhotoHash("");
-            presence.setVCardUpdateType(QXmppPresence::VCardUpdateNoPhoto);
-            change.insert("avatarPath", "");
-        }
-        client.setClientPresence(presence);
-        emit changed(change);
-    }
-    
-    ownVCardRequestInProgress = false;
-    
-    Shared::VCard vCard;
-    initializeVCard(vCard, card);
-    
-    if (avatarType.size() > 0) {
-        vCard.setAvatarType(Shared::Avatar::valid);
-        vCard.setAvatarPath(path + "avatar." + avatarType);
-    } else {
-        vCard.setAvatarType(Shared::Avatar::empty);
-    }
-    
-    emit receivedVCard(getLogin() + "@" + getServer(), vCard);
-}
-
-QString Core::Account::getAvatarPath() const
-{
-    if (avatarType.size() == 0) {
-        return "";
-    } else {
-        return QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/" + name + "/" + "avatar." + avatarType;
-    }
-}
-
-void Core::Account::requestVCard(const QString& jid)
-{
-    if (pendingVCardRequests.find(jid) == pendingVCardRequests.end()) {
-        qDebug() << "requesting vCard" << jid;
-        if (jid == getLogin() + "@" + getServer()) {
-            if (!ownVCardRequestInProgress) {
-                vm->requestClientVCard();
-                ownVCardRequestInProgress = true;
-            }
-        } else {
-            vm->requestVCard(jid);
-            pendingVCardRequests.insert(jid);
-        }
-    }
-}
-
-void Core::Account::uploadVCard(const Shared::VCard& card)
-{
-    QXmppVCardIq iq;
-    initializeQXmppVCard(iq, card);
-    
-    if (card.getAvatarType() != Shared::Avatar::empty) {
-        QString newPath = card.getAvatarPath();
-        QString oldPath = getAvatarPath();
-        QByteArray data;
-        QString type;
-        if (newPath != oldPath) {
-            QFile avatar(newPath);
-            if (!avatar.open(QFile::ReadOnly)) {
-                qDebug() << "An attempt to upload new vCard to account" << name 
-                << "but it wasn't possible to read file" << newPath 
-                << "which was supposed to be new avatar, uploading old avatar";
-                if (avatarType.size() > 0) {
-                    QFile oA(oldPath);
-                    if (!oA.open(QFile::ReadOnly)) {
-                        qDebug() << "Couldn't read old avatar of account" << name << ", uploading empty avatar";
-                    } else {
-                        data = oA.readAll();
-                    }
-                }
-            } else {
-                data = avatar.readAll();
-            }
-        } else {
-            if (avatarType.size() > 0) {
-                QFile oA(oldPath);
-                if (!oA.open(QFile::ReadOnly)) {
-                    qDebug() << "Couldn't read old avatar of account" << name << ", uploading empty avatar";
-                } else {
-                    data = oA.readAll();
-                }
-            }
-        }
-        
-        if (data.size() > 0) {
-            QMimeDatabase db;
-            type = db.mimeTypeForData(data).name();
-            iq.setPhoto(data);
-            iq.setPhotoType(type);
-        }
-    }
-    
-    vm->setClientVCard(iq);
-    onOwnVCardReceived(iq);
-}
-
-void Core::Account::onDiscoveryItemsReceived(const QXmppDiscoveryIq& items)
-{
-    for (QXmppDiscoveryIq::Item item : items.items()) {
-        if (item.jid() != getServer()) {
-            dm->requestInfo(item.jid());
-        }
-    }
-}
-
-void Core::Account::onDiscoveryInfoReceived(const QXmppDiscoveryIq& info)
-{
-    qDebug() << "Discovery info received for account" << name;
-    if (info.from() == getServer()) {
-        if (info.features().contains("urn:xmpp:carbons:2")) {
-            qDebug() << "Enabling carbon copies for account" << name;
-            cm->setCarbonsEnabled(true);
-        }
-    }
-}
-
-void Core::Account::handleDisconnection()
-{
-    cm->setCarbonsEnabled(false);
-    rh->handleOffline();
-    archiveQueries.clear();
-    pendingVCardRequests.clear();
-    Shared::VCard vCard;                    //just to show, that there is now more pending request
-    for (const QString& jid : pendingVCardRequests) {
-        emit receivedVCard(jid, vCard);     //need to show it better in the future, like with an error
-    }
-    pendingVCardRequests.clear();
-    ownVCardRequestInProgress = false;
-}
-
-void Core::Account::onContactHistoryResponse(const std::list<Shared::Message>& list, bool last)
-{
-    RosterItem* contact = static_cast<RosterItem*>(sender());
-    
-    qDebug() << "Collected history for contact " << contact->jid << list.size() << "elements";
-    if (last) {
-        qDebug() << "The response contains the first accounted message";
-    }
-    emit responseArchive(contact->jid, list, last);
-}
-
-void Core::Account::requestChangeMessage(const QString& jid, const QString& messageId, const QMap<QString, QVariant>& data){
-    mh->requestChangeMessage(jid, messageId, data);}
-
-void Core::Account::resendMessage(const QString& jid, const QString& id) {
-    mh->resendMessage(jid, id);}
-
-void Core::Account::replaceMessage(const QString& originalId, const Shared::Message& data) {
-    mh->sendMessage(data, false, originalId);}
-
diff --git a/core/account.h b/core/account.h
index 664b547..c8e6e41 100644
--- a/core/account.h
+++ b/core/account.h
@@ -39,7 +39,6 @@
 #include <QXmppBookmarkManager.h>
 #include <QXmppBookmarkSet.h>
 #include <QXmppUploadRequestManager.h>
-#include <QXmppVCardIq.h>
 #include <QXmppVCardManager.h>
 #include <QXmppMessageReceiptManager.h>
 
@@ -50,6 +49,7 @@
 
 #include "handlers/messagehandler.h"
 #include "handlers/rosterhandler.h"
+#include "handlers/vcardhandler.h"
 
 namespace Core
 {
@@ -59,6 +59,7 @@ class Account : public QObject
     Q_OBJECT
     friend class MessageHandler;
     friend class RosterHandler;
+    friend class VCardHandler;
 public:
     Account(
         const QString& p_login, 
@@ -76,6 +77,8 @@ public:
     QString getPassword() const;
     QString getResource() const;
     QString getAvatarPath() const;
+    QString getBareJid() const;
+    QString getFullJid() const;
     Shared::Availability getAvailability() const;
     Shared::AccountPassword getPasswordType() const;
     
@@ -86,7 +89,6 @@ public:
     void setResource(const QString& p_resource);
     void setAvailability(Shared::Availability avail);
     void setPasswordType(Shared::AccountPassword pt);
-    QString getFullJid() const;
     void sendMessage(const Shared::Message& data);
     void requestArchive(const QString& jid, int count, const QString& before);
     void subscribeToContact(const QString& jid, const QString& reason);
@@ -157,16 +159,13 @@ private:
     bool reconnectScheduled;
     QTimer* reconnectTimer;
     
-    std::set<QString> pendingVCardRequests;
-    
-    QString avatarHash;
-    QString avatarType;
-    bool ownVCardRequestInProgress;
     NetworkAccess* network;
     Shared::AccountPassword passwordType;
+    bool pepSupport;
     
     MessageHandler* mh;
     RosterHandler* rh;
+    VCardHandler* vh;
     
 private slots:
     void onClientStateChange(QXmppClient::State state);
@@ -179,9 +178,6 @@ private slots:
     void onMamResultsReceived(const QString &queryId, const QXmppResultSetReply &resultSetReply, bool complete);
 
     void onMamLog(QXmppLogger::MessageType type, const QString &msg);
-    
-    void onVCardReceived(const QXmppVCardIq& card);
-    void onOwnVCardReceived(const QXmppVCardIq& card);
 
     void onDiscoveryItemsReceived (const QXmppDiscoveryIq& items);
     void onDiscoveryInfoReceived (const QXmppDiscoveryIq& info);
@@ -191,9 +187,6 @@ private:
     void handleDisconnection();
     void onReconnectTimer();
 };
-
-void initializeVCard(Shared::VCard& vCard, const QXmppVCardIq& card);
-void initializeQXmppVCard(QXmppVCardIq& card, const Shared::VCard& vCard);
 }
 
 
diff --git a/core/adapterFuctions.cpp b/core/adapterfunctions.cpp
similarity index 98%
rename from core/adapterFuctions.cpp
rename to core/adapterfunctions.cpp
index 3d84dfb..eec5a9f 100644
--- a/core/adapterFuctions.cpp
+++ b/core/adapterfunctions.cpp
@@ -1,5 +1,5 @@
 /*
- * Squawk messenger. 
+ * Squawk messenger.
  * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
  *
  * This program is free software: you can redistribute it and/or modify
@@ -15,10 +15,8 @@
  * You should have received a copy of the GNU General Public License
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
-#ifndef CORE_ADAPTER_FUNCTIONS_H
-#define CORE_ADAPTER_FUNCTIONS_H
 
-#include "account.h"
+#include "adapterfunctions.h"
 
 void Core::initializeVCard(Shared::VCard& vCard, const QXmppVCardIq& card)
 {
@@ -271,5 +269,3 @@ void Core::initializeQXmppVCard(QXmppVCardIq& iq, const Shared::VCard& card) {
     iq.setEmails(emails);
     iq.setPhones(phs);
 }
-
-#endif // CORE_ADAPTER_FUNCTIONS_H
diff --git a/core/adapterfunctions.h b/core/adapterfunctions.h
new file mode 100644
index 0000000..6e50a75
--- /dev/null
+++ b/core/adapterfunctions.h
@@ -0,0 +1,32 @@
+/*
+ * Squawk messenger.
+ * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+ *
+ * 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/>.
+ */
+#ifndef CORE_ADAPTER_FUNCTIONS_H
+#define CORE_ADAPTER_FUNCTIONS_H
+
+#include <QXmppVCardIq.h>
+#include <shared/vcard.h>
+
+namespace Core {
+
+void initializeVCard(Shared::VCard& vCard, const QXmppVCardIq& card);
+void initializeQXmppVCard(QXmppVCardIq& card, const Shared::VCard& vCard);
+
+}
+
+
+#endif // CORE_ADAPTER_FUNCTIONS_H
diff --git a/core/handlers/CMakeLists.txt b/core/handlers/CMakeLists.txt
index 6da2ef3..fb67953 100644
--- a/core/handlers/CMakeLists.txt
+++ b/core/handlers/CMakeLists.txt
@@ -3,4 +3,6 @@ target_sources(squawk PRIVATE
   messagehandler.h
   rosterhandler.cpp
   rosterhandler.h
+  vcardhandler.cpp
+  vcardhandler.h
   )
diff --git a/core/handlers/messagehandler.cpp b/core/handlers/messagehandler.cpp
index 0555873..b6d32b9 100644
--- a/core/handlers/messagehandler.cpp
+++ b/core/handlers/messagehandler.cpp
@@ -176,7 +176,7 @@ void Core::MessageHandler::initializeMessage(Shared::Message& target, const QXmp
     target.setForwarded(forwarded);
     
     if (guessing) {
-        if (target.getFromJid() == acc->getLogin() + "@" + acc->getServer()) {
+        if (target.getFromJid() == acc->getBareJid()) {
             outgoing = true;
         } else {
             outgoing = false;
diff --git a/core/handlers/rosterhandler.cpp b/core/handlers/rosterhandler.cpp
index ce5f1b7..6a233d6 100644
--- a/core/handlers/rosterhandler.cpp
+++ b/core/handlers/rosterhandler.cpp
@@ -26,7 +26,8 @@ Core::RosterHandler::RosterHandler(Core::Account* account):
     conferences(),
     groups(),
     queuedContacts(),
-    outOfRosterContacts()
+    outOfRosterContacts(),
+    pepSupport(false)
 {
     connect(acc->rm, &QXmppRosterManager::rosterReceived, this, &RosterHandler::onRosterReceived);
     connect(acc->rm, &QXmppRosterManager::itemAdded, this, &RosterHandler::onRosterItemAdded);
@@ -51,8 +52,7 @@ Core::RosterHandler::~RosterHandler()
 
 void Core::RosterHandler::onRosterReceived()
 {
-    acc->vm->requestClientVCard();         //TODO need to make sure server actually supports vCards
-    acc->ownVCardRequestInProgress = true;
+    acc->requestVCard(acc->getBareJid());         //TODO need to make sure server actually supports vCards
     
     QStringList bj = acc->rm->getRosterBareJids();
     for (int i = 0; i < bj.size(); ++i) {
@@ -588,4 +588,13 @@ void Core::RosterHandler::handleOffline()
         pair.second->clearArchiveRequests();
         pair.second->downgradeDatabaseState();
     }
+    setPepSupport(false);
+}
+
+
+void Core::RosterHandler::setPepSupport(bool support)
+{
+    if (pepSupport != support) {
+        pepSupport = support;
+    }
 }
diff --git a/core/handlers/rosterhandler.h b/core/handlers/rosterhandler.h
index b1dfc45..02bbc98 100644
--- a/core/handlers/rosterhandler.h
+++ b/core/handlers/rosterhandler.h
@@ -64,6 +64,7 @@ public:
     
     void storeConferences();
     void clearConferences();
+    void setPepSupport(bool support);
     
 private slots:
     void onRosterReceived();
@@ -107,6 +108,7 @@ private:
     std::map<QString, std::set<QString>> groups;
     std::map<QString, QString> queuedContacts;
     std::set<QString> outOfRosterContacts;
+    bool pepSupport;
 };
 
 }
diff --git a/core/handlers/vcardhandler.cpp b/core/handlers/vcardhandler.cpp
new file mode 100644
index 0000000..2a8d65c
--- /dev/null
+++ b/core/handlers/vcardhandler.cpp
@@ -0,0 +1,312 @@
+// Squawk messenger. 
+// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+// 
+// 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 "vcardhandler.h"
+#include "core/account.h"
+
+Core::VCardHandler::VCardHandler(Account* account):
+    QObject(),
+    acc(account),
+    ownVCardRequestInProgress(false),
+    pendingVCardRequests(),
+    avatarHash(),
+    avatarType()
+{
+    connect(acc->vm, &QXmppVCardManager::vCardReceived, this, &VCardHandler::onVCardReceived);
+    //for some reason it doesn't work, launching from common handler
+    //connect(acc->vm, &QXmppVCardManager::clientVCardReceived, this, &VCardHandler::onOwnVCardReceived);
+
+    QString path(QStandardPaths::writableLocation(QStandardPaths::CacheLocation));
+    path += "/" + acc->name;
+    QDir dir(path);
+
+    if (!dir.exists()) {
+        bool res = dir.mkpath(path);
+        if (!res) {
+            qDebug() << "Couldn't create a cache directory for account" << acc->name;
+            throw 22;
+        }
+    }
+
+    QFile* avatar = new QFile(path + "/avatar.png");
+    QString type = "png";
+    if (!avatar->exists()) {
+        delete avatar;
+        avatar = new QFile(path + "/avatar.jpg");
+        type = "jpg";
+        if (!avatar->exists()) {
+            delete avatar;
+            avatar = new QFile(path + "/avatar.jpeg");
+            type = "jpeg";
+            if (!avatar->exists()) {
+                delete avatar;
+                avatar = new QFile(path + "/avatar.gif");
+                type = "gif";
+            }
+        }
+    }
+
+    if (avatar->exists()) {
+        if (avatar->open(QFile::ReadOnly)) {
+            QCryptographicHash sha1(QCryptographicHash::Sha1);
+            sha1.addData(avatar);
+            avatarHash = sha1.result();
+            avatarType = type;
+        }
+    }
+    if (avatarType.size() != 0) {
+        acc->presence.setVCardUpdateType(QXmppPresence::VCardUpdateValidPhoto);
+        acc->presence.setPhotoHash(avatarHash.toUtf8());
+    } else {
+        acc->presence.setVCardUpdateType(QXmppPresence::VCardUpdateNotReady);
+    }
+}
+
+Core::VCardHandler::~VCardHandler()
+{
+
+}
+
+void Core::VCardHandler::onVCardReceived(const QXmppVCardIq& card)
+{
+    QString id = card.from();
+    QStringList comps = id.split("/");
+    QString jid = comps.front().toLower();
+    QString resource("");
+    if (comps.size() > 1) {
+        resource = comps.back();
+    }
+    pendingVCardRequests.erase(id);
+    RosterItem* item = acc->rh->getRosterItem(jid);
+
+    if (item == 0) {
+        if (jid == acc->getBareJid()) {
+            onOwnVCardReceived(card);
+        } else {
+            qDebug() << "received vCard" << jid << "doesn't belong to any of known contacts or conferences, skipping";
+        }
+        return;
+    }
+
+    Shared::VCard vCard = item->handleResponseVCard(card, resource);
+
+    emit acc->receivedVCard(jid, vCard);
+}
+
+void Core::VCardHandler::onOwnVCardReceived(const QXmppVCardIq& card)
+{
+    QByteArray ava = card.photo();
+    bool avaChanged = false;
+    QString path = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/" + acc->name + "/";
+    if (ava.size() > 0) {
+        QCryptographicHash sha1(QCryptographicHash::Sha1);
+        sha1.addData(ava);
+        QString newHash(sha1.result());
+        QMimeDatabase db;
+        QMimeType newType = db.mimeTypeForData(ava);
+        if (avatarType.size() > 0) {
+            if (avatarHash != newHash) {
+                QString oldPath = path + "avatar." + avatarType;
+                QFile oldAvatar(oldPath);
+                bool oldToRemove = false;
+                if (oldAvatar.exists()) {
+                    if (oldAvatar.rename(oldPath + ".bak")) {
+                        oldToRemove = true;
+                    } else {
+                        qDebug() << "Received new avatar for account" << acc->name << "but can't get rid of the old one, doing nothing";
+                    }
+                }
+                QFile newAvatar(path + "avatar." + newType.preferredSuffix());
+                if (newAvatar.open(QFile::WriteOnly)) {
+                    newAvatar.write(ava);
+                    newAvatar.close();
+                    avatarHash = newHash;
+                    avatarType = newType.preferredSuffix();
+                    avaChanged = true;
+                } else {
+                    qDebug() << "Received new avatar for account" << acc->name << "but can't save it";
+                    if (oldToRemove) {
+                        qDebug() << "rolling back to the old avatar";
+                        if (!oldAvatar.rename(oldPath)) {
+                            qDebug() << "Couldn't roll back to the old avatar in account" << acc->name;
+                        }
+                    }
+                }
+            }
+        } else {
+            QFile newAvatar(path + "avatar." + newType.preferredSuffix());
+            if (newAvatar.open(QFile::WriteOnly)) {
+                newAvatar.write(ava);
+                newAvatar.close();
+                avatarHash = newHash;
+                avatarType = newType.preferredSuffix();
+                avaChanged = true;
+            } else {
+                qDebug() << "Received new avatar for account" << acc->name << "but can't save it";
+            }
+        }
+    } else {
+        if (avatarType.size() > 0) {
+            QFile oldAvatar(path + "avatar." + avatarType);
+            if (!oldAvatar.remove()) {
+                qDebug() << "Received vCard for account" << acc->name << "without avatar, but can't get rid of the file, doing nothing";
+            } else {
+                avatarType = "";
+                avatarHash = "";
+                avaChanged = true;
+            }
+        }
+    }
+
+    if (avaChanged) {
+        QMap<QString, QVariant> change;
+        if (avatarType.size() > 0) {
+            acc->presence.setPhotoHash(avatarHash.toUtf8());
+            acc->presence.setVCardUpdateType(QXmppPresence::VCardUpdateValidPhoto);
+            change.insert("avatarPath", path + "avatar." + avatarType);
+        } else {
+            acc->presence.setPhotoHash("");
+            acc->presence.setVCardUpdateType(QXmppPresence::VCardUpdateNoPhoto);
+            change.insert("avatarPath", "");
+        }
+        acc->client.setClientPresence(acc->presence);
+        emit acc->changed(change);
+    }
+
+    ownVCardRequestInProgress = false;
+
+    Shared::VCard vCard;
+    initializeVCard(vCard, card);
+
+    if (avatarType.size() > 0) {
+        vCard.setAvatarType(Shared::Avatar::valid);
+        vCard.setAvatarPath(path + "avatar." + avatarType);
+    } else {
+        vCard.setAvatarType(Shared::Avatar::empty);
+    }
+
+    emit acc->receivedVCard(acc->getBareJid(), vCard);
+}
+
+void Core::VCardHandler::handleOffline()
+{
+    pendingVCardRequests.clear();
+    Shared::VCard vCard;                    //just to show, that there is now more pending request
+    for (const QString& jid : pendingVCardRequests) {
+        emit acc->receivedVCard(jid, vCard);     //need to show it better in the future, like with an error
+    }
+    pendingVCardRequests.clear();
+    ownVCardRequestInProgress = false;
+}
+
+void Core::VCardHandler::requestVCard(const QString& jid)
+{
+    if (pendingVCardRequests.find(jid) == pendingVCardRequests.end()) {
+        qDebug() << "requesting vCard" << jid;
+        if (jid == acc->getBareJid()) {
+            if (!ownVCardRequestInProgress) {
+                acc->vm->requestClientVCard();
+                ownVCardRequestInProgress = true;
+            }
+        } else {
+            acc->vm->requestVCard(jid);
+            pendingVCardRequests.insert(jid);
+        }
+    }
+}
+
+void Core::VCardHandler::handleOtherPresenceOfMyAccountChange(const QXmppPresence& p_presence)
+{
+    if (!ownVCardRequestInProgress) {
+        switch (p_presence.vCardUpdateType()) {
+            case QXmppPresence::VCardUpdateNone:            //this presence has nothing to do with photo
+                break;
+            case QXmppPresence::VCardUpdateNotReady:        //let's say the photo didn't change here
+                break;
+            case QXmppPresence::VCardUpdateNoPhoto:         //there is no photo, need to drop if any
+                if (avatarType.size() > 0) {
+                    acc->vm->requestClientVCard();
+                    ownVCardRequestInProgress = true;
+                }
+                break;
+            case QXmppPresence::VCardUpdateValidPhoto:      //there is a photo, need to load
+                if (avatarHash != p_presence.photoHash()) {
+                    acc->vm->requestClientVCard();
+                    ownVCardRequestInProgress = true;
+                }
+                break;
+        }
+    }
+}
+
+void Core::VCardHandler::uploadVCard(const Shared::VCard& card)
+{
+    QXmppVCardIq iq;
+    initializeQXmppVCard(iq, card);
+
+    if (card.getAvatarType() != Shared::Avatar::empty) {
+        QString newPath = card.getAvatarPath();
+        QString oldPath = getAvatarPath();
+        QByteArray data;
+        QString type;
+        if (newPath != oldPath) {
+            QFile avatar(newPath);
+            if (!avatar.open(QFile::ReadOnly)) {
+                qDebug() << "An attempt to upload new vCard to account" << acc->name
+                << "but it wasn't possible to read file" << newPath
+                << "which was supposed to be new avatar, uploading old avatar";
+                if (avatarType.size() > 0) {
+                    QFile oA(oldPath);
+                    if (!oA.open(QFile::ReadOnly)) {
+                        qDebug() << "Couldn't read old avatar of account" << acc->name << ", uploading empty avatar";
+                    } else {
+                        data = oA.readAll();
+                    }
+                }
+            } else {
+                data = avatar.readAll();
+            }
+        } else {
+            if (avatarType.size() > 0) {
+                QFile oA(oldPath);
+                if (!oA.open(QFile::ReadOnly)) {
+                    qDebug() << "Couldn't read old avatar of account" << acc->name << ", uploading empty avatar";
+                } else {
+                    data = oA.readAll();
+                }
+            }
+        }
+
+        if (data.size() > 0) {
+            QMimeDatabase db;
+            type = db.mimeTypeForData(data).name();
+            iq.setPhoto(data);
+            iq.setPhotoType(type);
+        }
+    }
+
+    acc->vm->setClientVCard(iq);
+    onOwnVCardReceived(iq);
+}
+
+QString Core::VCardHandler::getAvatarPath() const
+{
+    if (avatarType.size() == 0) {
+        return "";
+    } else {
+        return QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/" + acc->name + "/" + "avatar." + avatarType;
+    }
+}
diff --git a/core/handlers/vcardhandler.h b/core/handlers/vcardhandler.h
new file mode 100644
index 0000000..4febb69
--- /dev/null
+++ b/core/handlers/vcardhandler.h
@@ -0,0 +1,65 @@
+// Squawk messenger. 
+// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+// 
+// 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/>.
+
+#ifndef CORE_VCARDHANDLER_H
+#define CORE_VCARDHANDLER_H
+
+#include <set>
+
+#include <QXmppVCardIq.h>
+#include <QXmppPresence.h>
+
+#include <QObject>
+
+#include <shared/vcard.h>
+#include <core/adapterfunctions.h>
+
+/**
+ * @todo write docs
+ */
+
+namespace Core {
+
+class Account;
+
+class VCardHandler : public QObject
+{
+    Q_OBJECT
+public:
+    VCardHandler(Account* account);
+    ~VCardHandler();
+
+    void handleOffline();
+    void requestVCard(const QString& jid);
+    void handleOtherPresenceOfMyAccountChange(const QXmppPresence& p_presence);
+    void uploadVCard(const Shared::VCard& card);
+    QString getAvatarPath() const;
+
+private slots:
+    void onVCardReceived(const QXmppVCardIq& card);
+    void onOwnVCardReceived(const QXmppVCardIq& card);
+
+private:
+    Account* acc;
+
+    bool ownVCardRequestInProgress;
+    std::set<QString> pendingVCardRequests;
+    QString avatarHash;
+    QString avatarType;
+};
+}
+
+#endif // CORE_VCARDHANDLER_H
diff --git a/core/rosteritem.h b/core/rosteritem.h
index 237a46a..d422e3f 100644
--- a/core/rosteritem.h
+++ b/core/rosteritem.h
@@ -35,6 +35,7 @@
 #include "shared/message.h"
 #include "shared/vcard.h"
 #include "archive.h"
+#include "adapterfunctions.h"
 
 namespace Core {
 

From 2c26c7e264922532b37ed94eb366472c85c75062 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Mon, 11 Apr 2022 18:45:12 +0300
Subject: [PATCH 184/281] ui squawk refactoring

---
 ui/CMakeLists.txt                      |   8 +-
 ui/dialogqueue.cpp                     | 148 +++++++++++++++++++++++++
 ui/dialogqueue.h                       |  92 +++++++++++++++
 ui/squawk.cpp                          |  49 +-------
 ui/squawk.h                            |  10 +-
 ui/widgets/CMakeLists.txt              |   7 +-
 ui/widgets/accounts/CMakeLists.txt     |   8 ++
 ui/widgets/{ => accounts}/account.cpp  |   0
 ui/widgets/{ => accounts}/account.h    |   0
 ui/widgets/{ => accounts}/account.ui   |   0
 ui/widgets/{ => accounts}/accounts.cpp |   0
 ui/widgets/{ => accounts}/accounts.h   |   2 +-
 ui/widgets/{ => accounts}/accounts.ui  |   0
 13 files changed, 263 insertions(+), 61 deletions(-)
 create mode 100644 ui/dialogqueue.cpp
 create mode 100644 ui/dialogqueue.h
 create mode 100644 ui/widgets/accounts/CMakeLists.txt
 rename ui/widgets/{ => accounts}/account.cpp (100%)
 rename ui/widgets/{ => accounts}/account.h (100%)
 rename ui/widgets/{ => accounts}/account.ui (100%)
 rename ui/widgets/{ => accounts}/accounts.cpp (100%)
 rename ui/widgets/{ => accounts}/accounts.h (98%)
 rename ui/widgets/{ => accounts}/accounts.ui (100%)

diff --git a/ui/CMakeLists.txt b/ui/CMakeLists.txt
index 36207b6..fcbb24c 100644
--- a/ui/CMakeLists.txt
+++ b/ui/CMakeLists.txt
@@ -1,4 +1,10 @@
-target_sources(squawk PRIVATE squawk.cpp squawk.h squawk.ui)
+target_sources(squawk PRIVATE
+    squawk.cpp
+    squawk.h
+    squawk.ui
+    dialogqueue.cpp
+    dialogqueue.h
+)
 
 add_subdirectory(models)
 add_subdirectory(utils)
diff --git a/ui/dialogqueue.cpp b/ui/dialogqueue.cpp
new file mode 100644
index 0000000..1887b28
--- /dev/null
+++ b/ui/dialogqueue.cpp
@@ -0,0 +1,148 @@
+// Squawk messenger. 
+// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+// 
+// 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 "dialogqueue.h"
+#include "squawk.h"
+#include <QDebug>
+
+DialogQueue::DialogQueue(Squawk* p_squawk):
+    QObject(),
+    currentSource(),
+    currentAction(none),
+    queue(),
+    collection(queue.get<0>()),
+    sequence(queue.get<1>()),
+    prompt(nullptr),
+    squawk(p_squawk)
+{
+}
+
+DialogQueue::~DialogQueue()
+{
+}
+
+bool DialogQueue::addAction(const QString& source, DialogQueue::Action action)
+{
+    if (action == none) {
+        return false;
+    }
+    if (currentAction == none) {
+        currentAction = action;
+        currentSource = source;
+        performNextAction();
+        return true;
+    } else {
+        if (currentAction != action || currentSource != source) {
+            std::pair<Queue::iterator, bool> result = queue.emplace(source, action);
+            return result.second;
+        } else {
+            return false;
+        }
+    }
+}
+
+bool DialogQueue::cancelAction(const QString& source, DialogQueue::Action action)
+{
+    if (source == currentSource && action == currentAction) {
+        actionDone();
+        return true;
+    } else {
+        Collection::iterator itr = collection.find(ActionId{source, action});
+        if (itr != collection.end()) {
+            collection.erase(itr);
+            return true;
+        } else {
+            return false;
+        }
+    }
+}
+
+void DialogQueue::performNextAction()
+{
+    switch (currentAction) {
+        case none:
+            actionDone();
+            break;
+        case askPassword:
+            prompt = new QInputDialog(squawk);
+            connect(prompt, &QDialog::accepted, this, &DialogQueue::onPropmtAccepted);
+            connect(prompt, &QDialog::rejected, this, &DialogQueue::onPropmtRejected);
+            prompt->setInputMode(QInputDialog::TextInput);
+            prompt->setTextEchoMode(QLineEdit::Password);
+            prompt->setLabelText(tr("Input the password for account %1").arg(currentSource));
+            prompt->setWindowTitle(tr("Password for account %1").arg(currentSource));
+            prompt->setTextValue("");
+            prompt->exec();
+    }
+}
+
+void DialogQueue::onPropmtAccepted()
+{
+    switch (currentAction) {
+        case none:
+            break;
+        case askPassword:
+            emit squawk->responsePassword(currentSource, prompt->textValue());
+            break;
+    }
+    actionDone();
+}
+
+void DialogQueue::onPropmtRejected()
+{
+    switch (currentAction) {
+        case none:
+            break;
+        case askPassword:
+            emit squawk->responsePassword(currentSource, prompt->textValue());
+            break;
+    }
+    actionDone();
+}
+
+void DialogQueue::actionDone()
+{
+    prompt->deleteLater();
+    prompt = nullptr;
+
+    if (queue.empty()) {
+        currentAction = none;
+        currentSource = "";
+    } else {
+        Sequence::iterator itr = sequence.begin();
+        currentAction = itr->action;
+        currentSource = itr->source;
+        sequence.erase(itr);
+        performNextAction();
+    }
+}
+
+DialogQueue::ActionId::ActionId(const QString& p_source, DialogQueue::Action p_action):
+    source(p_source),
+    action(p_action) {}
+
+bool DialogQueue::ActionId::operator < (const DialogQueue::ActionId& other) const
+{
+    if (action == other.action) {
+        return source < other.source;
+    } else {
+        return action < other.action;
+    }
+}
+
+DialogQueue::ActionId::ActionId(const DialogQueue::ActionId& other):
+    source(other.source),
+    action(other.action) {}
diff --git a/ui/dialogqueue.h b/ui/dialogqueue.h
new file mode 100644
index 0000000..c5bf011
--- /dev/null
+++ b/ui/dialogqueue.h
@@ -0,0 +1,92 @@
+// Squawk messenger.
+// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+//
+// 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/>.
+
+#ifndef DIALOGQUEUE_H
+#define DIALOGQUEUE_H
+
+#include <QObject>
+#include <QInputDialog>
+
+#include <boost/multi_index_container.hpp>
+#include <boost/multi_index/ordered_index.hpp>
+#include <boost/multi_index/sequenced_index.hpp>
+
+/**
+ * @todo write docs
+ */
+
+class Squawk;
+
+class DialogQueue : public QObject
+{
+    Q_OBJECT
+public:
+    enum Action {
+        none,
+        askPassword
+    };
+
+    DialogQueue(Squawk* squawk);
+    ~DialogQueue();
+
+    bool addAction(const QString& source, Action action);
+    bool cancelAction(const QString& source, Action action);
+
+private:
+    void performNextAction();
+    void actionDone();
+
+private slots:
+    void onPropmtAccepted();
+    void onPropmtRejected();
+
+private:
+    QString currentSource;
+    Action currentAction;
+
+    struct ActionId {
+    public:
+        ActionId(const QString& p_source, Action p_action);
+        ActionId(const ActionId& other);
+
+        const QString source;
+        const Action action;
+
+        bool operator < (const ActionId& other) const;
+    };
+
+    typedef boost::multi_index_container <
+        ActionId,
+        boost::multi_index::indexed_by <
+            boost::multi_index::ordered_unique <
+                boost::multi_index::identity <ActionId>
+            >,
+            boost::multi_index::sequenced<>
+        >
+    > Queue;
+
+    typedef Queue::nth_index<0>::type Collection;
+    typedef Queue::nth_index<1>::type Sequence;
+
+    Queue queue;
+    Collection& collection;
+    Sequence& sequence;
+
+    QInputDialog* prompt;
+    Squawk* squawk;
+};
+
+#endif // DIALOGQUEUE_H
diff --git a/ui/squawk.cpp b/ui/squawk.cpp
index 4c7320b..335b8d0 100644
--- a/ui/squawk.cpp
+++ b/ui/squawk.cpp
@@ -27,13 +27,12 @@ Squawk::Squawk(QWidget *parent) :
     accounts(nullptr),
     preferences(nullptr),
     about(nullptr),
+    dialogueQueue(this),
     rosterModel(),
     conversations(),
     contextMenu(new QMenu()),
     dbus("org.freedesktop.Notifications", "/org/freedesktop/Notifications", "org.freedesktop.Notifications", QDBusConnection::sessionBus()),
     vCards(),
-    requestedAccountsForPasswords(),
-    prompt(nullptr),
     currentConversation(nullptr),
     restoreSelection(),
     needToRestore(false)
@@ -925,50 +924,8 @@ void Squawk::onItemCollepsed(const QModelIndex& index)
     }
 }
 
-void Squawk::requestPassword(const QString& account)
-{
-    requestedAccountsForPasswords.push_back(account);
-    checkNextAccountForPassword();
-}
-
-void Squawk::checkNextAccountForPassword()
-{
-    if (prompt == nullptr && requestedAccountsForPasswords.size() > 0) {
-        prompt = new QInputDialog(this);
-        QString accName = requestedAccountsForPasswords.front();
-        connect(prompt, &QDialog::accepted, this, &Squawk::onPasswordPromptAccepted);
-        connect(prompt, &QDialog::rejected, this, &Squawk::onPasswordPromptRejected);
-        prompt->setInputMode(QInputDialog::TextInput);
-        prompt->setTextEchoMode(QLineEdit::Password);
-        prompt->setLabelText(tr("Input the password for account %1").arg(accName));
-        prompt->setWindowTitle(tr("Password for account %1").arg(accName));
-        prompt->setTextValue("");
-        prompt->exec();
-    }
-}
-
-void Squawk::onPasswordPromptAccepted()
-{
-    emit responsePassword(requestedAccountsForPasswords.front(), prompt->textValue());
-    onPasswordPromptDone();
-}
-
-void Squawk::onPasswordPromptDone()
-{
-    prompt->deleteLater();
-    prompt = nullptr;
-    requestedAccountsForPasswords.pop_front();
-    checkNextAccountForPassword();
-}
-
-void Squawk::onPasswordPromptRejected()
-{
-    //for now it's the same on reject and on accept, but one day I'm gonna make 
-    //"Asking for the password again on the authentication failure" feature
-    //and here I'll be able to break the circle of password requests
-    emit responsePassword(requestedAccountsForPasswords.front(), prompt->textValue());
-    onPasswordPromptDone();
-}
+void Squawk::requestPassword(const QString& account) {
+    dialogueQueue.addAction(account, DialogQueue::askPassword);}
 
 void Squawk::subscribeConversation(Conversation* conv)
 {
diff --git a/ui/squawk.h b/ui/squawk.h
index 95c5ce3..6ee666c 100644
--- a/ui/squawk.h
+++ b/ui/squawk.h
@@ -31,7 +31,7 @@
 #include <set>
 #include <list>
 
-#include "widgets/accounts.h"
+#include "widgets/accounts/accounts.h"
 #include "widgets/chat.h"
 #include "widgets/room.h"
 #include "widgets/newcontact.h"
@@ -40,6 +40,7 @@
 #include "widgets/vcard/vcard.h"
 #include "widgets/settings/settings.h"
 #include "widgets/about.h"
+#include "dialogqueue.h"
 
 #include "shared/shared.h"
 
@@ -122,13 +123,12 @@ private:
     Accounts* accounts;
     Settings* preferences;
     About* about;
+    DialogQueue dialogueQueue;
     Models::Roster rosterModel;
     Conversations conversations;
     QMenu* contextMenu;
     QDBusInterface dbus;
     std::map<QString, VCard*> vCards;
-    std::deque<QString> requestedAccountsForPasswords;
-    QInputDialog* prompt;
     Conversation* currentConversation;
     QModelIndex restoreSelection;
     bool needToRestore;
@@ -161,8 +161,6 @@ private slots:
     void onRequestArchive(const QString& account, const QString& jid, const QString& before);
     void onRosterContextMenu(const QPoint& point);
     void onItemCollepsed(const QModelIndex& index);
-    void onPasswordPromptAccepted();
-    void onPasswordPromptRejected();
     void onRosterSelectionChanged(const QModelIndex& current, const QModelIndex& previous);
     void onContextAboutToHide();
     void onAboutSquawkCalled();
@@ -171,8 +169,6 @@ private slots:
     void onUnnoticedMessage(const QString& account, const Shared::Message& msg);
     
 private:
-    void checkNextAccountForPassword();
-    void onPasswordPromptDone();
     void subscribeConversation(Conversation* conv);
 };
 
diff --git a/ui/widgets/CMakeLists.txt b/ui/widgets/CMakeLists.txt
index 7ba83d2..21d9504 100644
--- a/ui/widgets/CMakeLists.txt
+++ b/ui/widgets/CMakeLists.txt
@@ -1,10 +1,4 @@
 target_sources(squawk PRIVATE
-  account.cpp
-  account.h
-  account.ui
-  accounts.cpp
-  accounts.h
-  accounts.ui
   chat.cpp
   chat.h
   conversation.cpp
@@ -26,3 +20,4 @@ target_sources(squawk PRIVATE
 add_subdirectory(vcard)
 add_subdirectory(messageline)
 add_subdirectory(settings)
+add_subdirectory(accounts)
diff --git a/ui/widgets/accounts/CMakeLists.txt b/ui/widgets/accounts/CMakeLists.txt
new file mode 100644
index 0000000..ad2f117
--- /dev/null
+++ b/ui/widgets/accounts/CMakeLists.txt
@@ -0,0 +1,8 @@
+target_sources(squawk PRIVATE
+  account.cpp
+  account.h
+  account.ui
+  accounts.cpp
+  accounts.h
+  accounts.ui
+  )
diff --git a/ui/widgets/account.cpp b/ui/widgets/accounts/account.cpp
similarity index 100%
rename from ui/widgets/account.cpp
rename to ui/widgets/accounts/account.cpp
diff --git a/ui/widgets/account.h b/ui/widgets/accounts/account.h
similarity index 100%
rename from ui/widgets/account.h
rename to ui/widgets/accounts/account.h
diff --git a/ui/widgets/account.ui b/ui/widgets/accounts/account.ui
similarity index 100%
rename from ui/widgets/account.ui
rename to ui/widgets/accounts/account.ui
diff --git a/ui/widgets/accounts.cpp b/ui/widgets/accounts/accounts.cpp
similarity index 100%
rename from ui/widgets/accounts.cpp
rename to ui/widgets/accounts/accounts.cpp
diff --git a/ui/widgets/accounts.h b/ui/widgets/accounts/accounts.h
similarity index 98%
rename from ui/widgets/accounts.h
rename to ui/widgets/accounts/accounts.h
index 9fd0b57..6d5eb95 100644
--- a/ui/widgets/accounts.h
+++ b/ui/widgets/accounts/accounts.h
@@ -24,7 +24,7 @@
 #include <QItemSelection>
 
 #include "account.h"
-#include "../models/accounts.h"
+#include "ui/models/accounts.h"
 
 namespace Ui
 {
diff --git a/ui/widgets/accounts.ui b/ui/widgets/accounts/accounts.ui
similarity index 100%
rename from ui/widgets/accounts.ui
rename to ui/widgets/accounts/accounts.ui

From f64e5c2df079aedcfb452be226930339f5fdac72 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Tue, 12 Apr 2022 23:33:10 +0300
Subject: [PATCH 185/281] account connect/disconnect now activate/deactivate,
 it's a bit less contraversial; async account password asking new concept

---
 core/account.cpp                 |  62 +++++++---
 core/account.h                   |   7 ++
 core/squawk.cpp                  | 194 +++++++++++++++----------------
 core/squawk.h                    |   6 +-
 ui/dialogqueue.cpp               |   2 +-
 ui/models/account.cpp            |  26 ++++-
 ui/models/account.h              |   4 +
 ui/models/accounts.cpp           |   4 +
 ui/models/roster.cpp             |  12 ++
 ui/squawk.cpp                    |  74 +++---------
 ui/widgets/accounts/account.cpp  |   1 +
 ui/widgets/accounts/account.ui   |  45 ++++---
 ui/widgets/accounts/accounts.cpp |  11 +-
 13 files changed, 248 insertions(+), 200 deletions(-)

diff --git a/core/account.cpp b/core/account.cpp
index 3d782cd..4d0480f 100644
--- a/core/account.cpp
+++ b/core/account.cpp
@@ -22,7 +22,7 @@
 
 using namespace Core;
 
-Account::Account(const QString& p_login, const QString& p_server, const QString& p_password, const QString& p_name, NetworkAccess* p_net, QObject* parent):
+Account::Account(const QString& p_login, const QString& p_server, const QString& p_password, const QString& p_name, bool p_active, NetworkAccess* p_net, QObject* parent):
     QObject(parent),
     name(p_name),
     archiveQueries(),
@@ -44,6 +44,8 @@ Account::Account(const QString& p_login, const QString& p_server, const QString&
     network(p_net),
     passwordType(Shared::AccountPassword::plain),
     pepSupport(false),
+    active(p_active),
+    notReadyPassword(false),
     mh(new MessageHandler(this)),
     rh(new RosterHandler(this)),
     vh(new VCardHandler(this))
@@ -89,13 +91,15 @@ Account::Account(const QString& p_login, const QString& p_server, const QString&
     reconnectTimer->setSingleShot(true);
     QObject::connect(reconnectTimer, &QTimer::timeout, this, &Account::onReconnectTimer);
     
-//     QXmppLogger* logger = new QXmppLogger(this);
-//     logger->setLoggingType(QXmppLogger::SignalLogging);
-//     client.setLogger(logger);
-//     
-//     QObject::connect(logger, &QXmppLogger::message, this, [](QXmppLogger::MessageType type, const QString& text){
-//         qDebug() << text;
-//     });
+    if (name == "Test") {
+        QXmppLogger* logger = new QXmppLogger(this);
+        logger->setLoggingType(QXmppLogger::SignalLogging);
+        client.setLogger(logger);
+
+        QObject::connect(logger, &QXmppLogger::message, this, [](QXmppLogger::MessageType type, const QString& text){
+            qDebug() << text;
+        });
+    }
 }
 
 Account::~Account()
@@ -135,7 +139,12 @@ void Core::Account::connect()
         reconnectTimer->stop();
     }
     if (state == Shared::ConnectionState::disconnected) {
-        client.connectToServer(config, presence);
+        if (notReadyPassword) {
+            emit needPassword();
+        } else {
+            client.connectToServer(config, presence);
+        }
+
     } else {
         qDebug("An attempt to connect an account which is already connected, skipping");
     }
@@ -205,12 +214,14 @@ void Core::Account::onClientStateChange(QXmppClient::State st)
 
 void Core::Account::reconnect()
 {
-    if (state == Shared::ConnectionState::connected && !reconnectScheduled) {
-        reconnectScheduled = true;
-        reconnectTimer->start(500);
-        client.disconnectFromServer();
-    } else {
-        qDebug() << "An attempt to reconnect account" << getName() << "which was not connected";
+    if (!reconnectScheduled) {      //TODO define behavior if It was connection or disconnecting
+        if (state == Shared::ConnectionState::connected) {
+            reconnectScheduled = true;
+            reconnectTimer->start(500);
+            client.disconnectFromServer();
+        } else {
+            qDebug() << "An attempt to reconnect account" << getName() << "which was not connected";
+        }
     }
 }
 
@@ -636,6 +647,19 @@ void Core::Account::onContactHistoryResponse(const std::list<Shared::Message>& l
     emit responseArchive(contact->jid, list, last);
 }
 
+bool Core::Account::getActive() const {
+    return active;}
+
+void Core::Account::setActive(bool p_active) {
+    if (active != p_active) {
+        active = p_active;
+
+        emit changed({
+            {"active", active}
+        });
+    }
+}
+
 QString Core::Account::getResource() const {
     return config.resource();}
 
@@ -673,7 +697,9 @@ void Core::Account::setName(const QString& p_name) {
     name = p_name;}
 
 void Core::Account::setPassword(const QString& p_password) {
-    config.setPassword(p_password);}
+    config.setPassword(p_password);
+    notReadyPassword = false;
+}
 
 void Core::Account::setServer(const QString& p_server) {
     config.setDomain(p_server);}
@@ -720,3 +746,7 @@ void Core::Account::renameContactRequest(const QString& jid, const QString& newN
         rm->renameItem(jid, newName);
     }
 }
+
+void Core::Account::invalidatePassword() {
+    notReadyPassword = true;}
+
diff --git a/core/account.h b/core/account.h
index c8e6e41..aa65b27 100644
--- a/core/account.h
+++ b/core/account.h
@@ -66,6 +66,7 @@ public:
         const QString& p_server, 
         const QString& p_password, 
         const QString& p_name, 
+        bool p_active,
         NetworkAccess* p_net, 
         QObject* parent = 0);
     ~Account();
@@ -81,6 +82,7 @@ public:
     QString getFullJid() const;
     Shared::Availability getAvailability() const;
     Shared::AccountPassword getPasswordType() const;
+    bool getActive() const;
     
     void setName(const QString& p_name);
     void setLogin(const QString& p_login);
@@ -90,6 +92,7 @@ public:
     void setAvailability(Shared::Availability avail);
     void setPasswordType(Shared::AccountPassword pt);
     void sendMessage(const Shared::Message& data);
+    void setActive(bool p_active);
     void requestArchive(const QString& jid, int count, const QString& before);
     void subscribeToContact(const QString& jid, const QString& reason);
     void unsubscribeFromContact(const QString& jid, const QString& reason);
@@ -107,6 +110,7 @@ public:
     void uploadVCard(const Shared::VCard& card);
     void resendMessage(const QString& jid, const QString& id);
     void replaceMessage(const QString& originalId, const Shared::Message& data);
+    void invalidatePassword();
     
 public slots:
     void connect();
@@ -139,6 +143,7 @@ signals:
     void receivedVCard(const QString& jid, const Shared::VCard& card);
     void uploadFile(const QFileInfo& file, const QUrl& set, const QUrl& get, QMap<QString, QString> headers);
     void uploadFileError(const QString& jid, const QString& messageId, const QString& error);
+    void needPassword();
     
 private:
     QString name;
@@ -162,6 +167,8 @@ private:
     NetworkAccess* network;
     Shared::AccountPassword passwordType;
     bool pepSupport;
+    bool active;
+    bool notReadyPassword;
     
     MessageHandler* mh;
     RosterHandler* rh;
diff --git a/core/squawk.cpp b/core/squawk.cpp
index af131d5..d594553 100644
--- a/core/squawk.cpp
+++ b/core/squawk.cpp
@@ -26,8 +26,8 @@ Core::Squawk::Squawk(QObject* parent):
     QObject(parent),
     accounts(),
     amap(),
+    state(Shared::Availability::offline),
     network(),
-    waitingForAccounts(0),
     isInitialized(false)
 #ifdef WITH_KWALLET
     ,kwallet()
@@ -42,7 +42,7 @@ Core::Squawk::Squawk(QObject* parent):
     if (kwallet.supportState() == PSE::KWallet::success) {
         connect(&kwallet, &PSE::KWallet::opened, this, &Squawk::onWalletOpened);
         connect(&kwallet, &PSE::KWallet::rejectPassword, this, &Squawk::onWalletRejectPassword);
-        connect(&kwallet, &PSE::KWallet::responsePassword, this, &Squawk::onWalletResponsePassword);
+        connect(&kwallet, &PSE::KWallet::responsePassword, this, &Squawk::responsePassword);
         
         Shared::Global::setSupported("KWallet", true);
     }
@@ -97,6 +97,7 @@ void Core::Squawk::stop()
             settings.setValue("password", password);
             settings.setValue("resource", acc->getResource());
             settings.setValue("passwordType", static_cast<int>(ap));
+            settings.setValue("active", acc->getActive());
         }
         settings.endArray();
         settings.endGroup();
@@ -124,8 +125,9 @@ void Core::Squawk::newAccountRequest(const QMap<QString, QVariant>& map)
     QString password = map.value("password").toString();
     QString resource = map.value("resource").toString();
     int passwordType = map.value("passwordType").toInt();
+    bool active = map.value("active").toBool();
     
-    addAccount(login, server, password, name, resource, Shared::Global::fromInt<Shared::AccountPassword>(passwordType));
+    addAccount(login, server, password, name, resource, active, Shared::Global::fromInt<Shared::AccountPassword>(passwordType));
 }
 
 void Core::Squawk::addAccount(
@@ -133,13 +135,13 @@ void Core::Squawk::addAccount(
     const QString& server, 
     const QString& password, 
     const QString& name, 
-    const QString& resource,                          
-    Shared::AccountPassword passwordType
-)
+    const QString& resource,
+    bool active,
+    Shared::AccountPassword passwordType)
 {
     QSettings settings;
     
-    Account* acc = new Account(login, server, password, name, &network);
+    Account* acc = new Account(login, server, password, name, active, &network);
     acc->setResource(resource);
     acc->setPasswordType(passwordType);
     accounts.push_back(acc);
@@ -148,6 +150,8 @@ void Core::Squawk::addAccount(
     connect(acc, &Account::connectionStateChanged, this, &Squawk::onAccountConnectionStateChanged);
     connect(acc, &Account::changed, this, &Squawk::onAccountChanged);
     connect(acc, &Account::error, this, &Squawk::onAccountError);
+    connect(acc, &Account::needPassword, this, &Squawk::onAccountNeedPassword);
+
     connect(acc, &Account::availabilityChanged, this, &Squawk::onAccountAvailabilityChanged);
     connect(acc, &Account::addContact, this, &Squawk::onAccountAddContact);
     connect(acc, &Account::addGroup, this, &Squawk::onAccountAddGroup);
@@ -185,20 +189,42 @@ void Core::Squawk::addAccount(
         {"offline", QVariant::fromValue(Shared::Availability::offline)},
         {"error", ""},
         {"avatarPath", acc->getAvatarPath()},
-        {"passwordType", QVariant::fromValue(passwordType)}
+        {"passwordType", QVariant::fromValue(passwordType)},
+        {"active", active}
     };
     
     emit newAccount(map);
+
+    switch (passwordType) {
+        case Shared::AccountPassword::alwaysAsk:
+        case Shared::AccountPassword::kwallet:
+            acc->invalidatePassword();
+            break;
+        default:
+            break;
+    }
+
+    if (state != Shared::Availability::offline) {
+        acc->setAvailability(state);
+        if (acc->getActive()) {
+            acc->connect();
+        }
+    }
 }
 
 void Core::Squawk::changeState(Shared::Availability p_state)
 {
     if (state != p_state) {
+        for (std::deque<Account*>::iterator itr = accounts.begin(), end = accounts.end(); itr != end; ++itr) {
+            Account* acc = *itr;
+            acc->setAvailability(p_state);
+            if (state == Shared::Availability::offline && acc->getActive()) {
+                acc->connect();
+            }
+        }
         state = p_state;
-    }
-    
-    for (std::deque<Account*>::iterator itr = accounts.begin(), end = accounts.end(); itr != end; ++itr) {
-        (*itr)->setAvailability(state);
+
+        emit stateChanged(p_state);
     }
 }
 
@@ -209,7 +235,10 @@ void Core::Squawk::connectAccount(const QString& account)
         qDebug("An attempt to connect non existing account, skipping");
         return;
     }
-    itr->second->connect();
+    itr->second->setActive(true);
+    if (state != Shared::Availability::offline) {
+        itr->second->connect();
+    }
 }
 
 void Core::Squawk::disconnectAccount(const QString& account)
@@ -220,6 +249,7 @@ void Core::Squawk::disconnectAccount(const QString& account)
         return;
     }
     
+    itr->second->setActive(false);
     itr->second->disconnect();
 }
 
@@ -227,7 +257,7 @@ void Core::Squawk::onAccountConnectionStateChanged(Shared::ConnectionState p_sta
 {
     Account* acc = static_cast<Account*>(sender());
     emit changeAccount(acc->getName(), {{"state", QVariant::fromValue(p_state)}});
-    
+
 #ifdef WITH_KWALLET
     if (p_state == Shared::ConnectionState::connected) {
         if (acc->getPasswordType() == Shared::AccountPassword::kwallet && kwallet.supportState() == PSE::KWallet::success) {
@@ -235,33 +265,6 @@ void Core::Squawk::onAccountConnectionStateChanged(Shared::ConnectionState p_sta
         }
     }
 #endif
-    
-    Accounts::const_iterator itr = accounts.begin();
-    bool es = true;
-    bool ea = true;
-    Shared::ConnectionState cs = (*itr)->getState();
-    Shared::Availability av = (*itr)->getAvailability();
-    itr++;
-    for (Accounts::const_iterator end = accounts.end(); itr != end; itr++) {
-        Account* item = *itr;
-        if (item->getState() != cs) {
-            es = false;
-        }
-        if (item->getAvailability() != av) {
-            ea = false;
-        }
-    }
-    
-    if (es) {
-        if (cs == Shared::ConnectionState::disconnected) {
-            state = Shared::Availability::offline;
-            emit stateChanged(state);
-        } else if (ea) {
-            state = av;
-            emit stateChanged(state);
-        }
-    }
-    
 }
 
 void Core::Squawk::onAccountAddContact(const QString& jid, const QString& group, const QMap<QString, QVariant>& data)
@@ -416,8 +419,15 @@ void Core::Squawk::modifyAccountRequest(const QString& name, const QMap<QString,
         }
     }
     
-    if (needToReconnect && st != Shared::ConnectionState::disconnected) {
-        acc->reconnect();
+    bool activeChanged = false;
+    mItr = map.find("active");
+    if (mItr == map.end() || mItr->toBool() == acc->getActive()) {
+        if (needToReconnect && st != Shared::ConnectionState::disconnected) {
+            acc->reconnect();
+        }
+    } else {
+        acc->setActive(mItr->toBool());
+        activeChanged = true;
     }
     
     mItr = map.find("login");
@@ -454,6 +464,10 @@ void Core::Squawk::modifyAccountRequest(const QString& name, const QMap<QString,
     }
 #endif
     
+    if (activeChanged && acc->getActive() && state != Shared::Availability::offline) {
+        acc->connect();
+    }
+
     emit changeAccount(name, map);
 }
 
@@ -675,85 +689,62 @@ void Core::Squawk::uploadVCard(const QString& account, const Shared::VCard& card
     itr->second->uploadVCard(card);
 }
 
-void Core::Squawk::responsePassword(const QString& account, const QString& password)
-{
-    AccountsMap::const_iterator itr = amap.find(account);
-    if (itr == amap.end()) {
-        qDebug() << "An attempt to set password to non existing account" << account << ", skipping";
-        return;
-    }
-    itr->second->setPassword(password);
-    emit changeAccount(account, {{"password", password}});
-    accountReady();
-}
-
 void Core::Squawk::readSettings()
 {
     QSettings settings;
     settings.beginGroup("core");
     int size = settings.beginReadArray("accounts");
-    waitingForAccounts = size;
     for (int i = 0; i < size; ++i) {
         settings.setArrayIndex(i);
-        parseAccount(
+        Shared::AccountPassword passwordType =
+            Shared::Global::fromInt<Shared::AccountPassword>(
+                settings.value("passwordType", static_cast<int>(Shared::AccountPassword::plain)).toInt()
+            );
+
+        QString password = settings.value("password", "").toString();
+        if (passwordType == Shared::AccountPassword::jammed) {
+            SimpleCrypt crypto(passwordHash);
+            password = crypto.decryptToString(password);
+        }
+
+        addAccount(
             settings.value("login").toString(), 
             settings.value("server").toString(), 
-            settings.value("password", "").toString(), 
+            password,
             settings.value("name").toString(),
             settings.value("resource").toString(),
-            Shared::Global::fromInt<Shared::AccountPassword>(settings.value("passwordType", static_cast<int>(Shared::AccountPassword::plain)).toInt())
+            settings.value("active").toBool(),
+            passwordType
         );
     }
     settings.endArray();
     settings.endGroup();
+
+    qDebug() << "Squawk core is ready";
+    emit ready();
 }
 
-void Core::Squawk::accountReady()
+void Core::Squawk::onAccountNeedPassword()
 {
-    --waitingForAccounts;
-    
-    if (waitingForAccounts == 0) {
-        emit ready();
-    }
-}
-
-void Core::Squawk::parseAccount(
-    const QString& login, 
-    const QString& server, 
-    const QString& password, 
-    const QString& name, 
-    const QString& resource, 
-    Shared::AccountPassword passwordType
-)
-{
-    switch (passwordType) {
-        case Shared::AccountPassword::plain:
-            addAccount(login, server, password, name, resource, passwordType);
-            accountReady();
-            break;
-        case Shared::AccountPassword::jammed: {
-            SimpleCrypt crypto(passwordHash);
-            QString decrypted = crypto.decryptToString(password);
-            addAccount(login, server, decrypted, name, resource, passwordType);
-            accountReady();
-        }
-            break;
-        case Shared::AccountPassword::alwaysAsk: 
-            addAccount(login, server, QString(), name, resource, passwordType);
-            emit requestPassword(name);
+    Account* acc = static_cast<Account*>(sender());
+    switch (acc->getPasswordType()) {
+        case Shared::AccountPassword::alwaysAsk:
+            emit requestPassword(acc->getName());
             break;
         case Shared::AccountPassword::kwallet: {
-            addAccount(login, server, QString(), name, resource, passwordType);
 #ifdef WITH_KWALLET
             if (kwallet.supportState() == PSE::KWallet::success) {
-                kwallet.requestReadPassword(name);
+                kwallet.requestReadPassword(acc->getName());
             } else {
 #endif
-                emit requestPassword(name);
+                emit requestPassword(acc->getName());
 #ifdef WITH_KWALLET
             }
 #endif
+            break;
         }
+        default:
+            break;      //should never happen;
     }
 }
 
@@ -762,16 +753,19 @@ void Core::Squawk::onWalletRejectPassword(const QString& login)
     emit requestPassword(login);
 }
 
-void Core::Squawk::onWalletResponsePassword(const QString& login, const QString& password)
+void Core::Squawk::responsePassword(const QString& account, const QString& password)
 {
-    AccountsMap::const_iterator itr = amap.find(login);
+    AccountsMap::const_iterator itr = amap.find(account);
     if (itr == amap.end()) {
-        qDebug() << "An attempt to set password to non existing account" << login << ", skipping";
+        qDebug() << "An attempt to set password to non existing account" << account << ", skipping";
         return;
     }
-    itr->second->setPassword(password);
-    emit changeAccount(login, {{"password", password}});
-    accountReady();
+    Account* acc = itr->second;
+    acc->setPassword(password);
+    emit changeAccount(account, {{"password", password}});
+    if (state != Shared::Availability::offline && acc->getActive()) {
+        acc->connect();
+    }
 }
 
 void Core::Squawk::onAccountUploadFileError(const QString& jid, const QString id, const QString& errorText)
diff --git a/core/squawk.h b/core/squawk.h
index 6cd251f..6cb3115 100644
--- a/core/squawk.h
+++ b/core/squawk.h
@@ -134,7 +134,6 @@ private:
     AccountsMap amap;
     Shared::Availability state;
     NetworkAccess network;
-    uint8_t waitingForAccounts;
     bool isInitialized;
 
 #ifdef WITH_KWALLET
@@ -148,6 +147,7 @@ private slots:
         const QString& password, 
         const QString& name, 
         const QString& resource, 
+        bool active,
         Shared::AccountPassword passwordType
     );
     
@@ -172,22 +172,22 @@ private slots:
     void onAccountChangeRoomPresence(const QString& jid, const QString& nick, const QMap<QString, QVariant>& data);
     void onAccountRemoveRoomPresence(const QString& jid, const QString& nick);
     void onAccountChangeMessage(const QString& jid, const QString& id, const QMap<QString, QVariant>& data);
+    void onAccountNeedPassword();
     
     void onAccountUploadFileError(const QString& jid, const QString id, const QString& errorText);
     
     void onWalletOpened(bool success);
-    void onWalletResponsePassword(const QString& login, const QString& password);
     void onWalletRejectPassword(const QString& login);
     
 private:
     void readSettings();
-    void accountReady();
     void parseAccount(
         const QString& login, 
         const QString& server, 
         const QString& password, 
         const QString& name, 
         const QString& resource, 
+        bool active,
         Shared::AccountPassword passwordType
     );
     
diff --git a/ui/dialogqueue.cpp b/ui/dialogqueue.cpp
index 1887b28..f5be82b 100644
--- a/ui/dialogqueue.cpp
+++ b/ui/dialogqueue.cpp
@@ -107,7 +107,7 @@ void DialogQueue::onPropmtRejected()
         case none:
             break;
         case askPassword:
-            emit squawk->responsePassword(currentSource, prompt->textValue());
+            emit squawk->disconnectAccount(currentSource);
             break;
     }
     actionDone();
diff --git a/ui/models/account.cpp b/ui/models/account.cpp
index 43cb3ed..cf1efb4 100644
--- a/ui/models/account.cpp
+++ b/ui/models/account.cpp
@@ -32,7 +32,8 @@ Models::Account::Account(const QMap<QString, QVariant>& data, Models::Item* pare
     state(Shared::ConnectionState::disconnected),
     availability(Shared::Availability::offline),
     passwordType(Shared::AccountPassword::plain),
-    wasEverConnected(false)
+    wasEverConnected(false),
+    active(false)
 {
     QMap<QString, QVariant>::const_iterator sItr = data.find("state");
     if (sItr != data.end()) {
@@ -46,6 +47,10 @@ Models::Account::Account(const QMap<QString, QVariant>& data, Models::Item* pare
     if (pItr != data.end()) {
         setPasswordType(pItr.value().toUInt());
     }
+    QMap<QString, QVariant>::const_iterator acItr = data.find("active");
+    if (acItr != data.end()) {
+        setActive(acItr.value().toBool());
+    }
 }
 
 Models::Account::~Account()
@@ -176,6 +181,8 @@ QVariant Models::Account::data(int column) const
             return avatarPath;
         case 9:
             return Shared::Global::getName(passwordType);
+        case 10:
+            return active;
         default:
             return QVariant();
     }
@@ -183,7 +190,7 @@ QVariant Models::Account::data(int column) const
 
 int Models::Account::columnCount() const
 {
-    return 10;
+    return 11;
 }
 
 void Models::Account::update(const QString& field, const QVariant& value)
@@ -208,6 +215,8 @@ void Models::Account::update(const QString& field, const QVariant& value)
         setAvatarPath(value.toString());
     } else if (field == "passwordType") {
         setPasswordType(value.toUInt());
+    } else if (field == "active") {
+        setActive(value.toBool());
     }
 }
 
@@ -281,3 +290,16 @@ void Models::Account::setPasswordType(unsigned int pt)
 {
     setPasswordType(Shared::Global::fromInt<Shared::AccountPassword>(pt));
 }
+
+bool Models::Account::getActive() const
+{
+    return active;
+}
+
+void Models::Account::setActive(bool p_active)
+{
+    if (active != p_active) {
+        active = p_active;
+        changed(10);
+    }
+}
diff --git a/ui/models/account.h b/ui/models/account.h
index 3d2310f..ab2b629 100644
--- a/ui/models/account.h
+++ b/ui/models/account.h
@@ -58,6 +58,9 @@ namespace Models {
         
         void setAvatarPath(const QString& path);
         QString getAvatarPath() const;
+
+        void setActive(bool active);
+        bool getActive() const;
         
         void setAvailability(Shared::Availability p_avail);
         void setAvailability(unsigned int p_avail);
@@ -91,6 +94,7 @@ namespace Models {
         Shared::Availability availability;
         Shared::AccountPassword passwordType;
         bool wasEverConnected;
+        bool active;
         
     protected slots:
         void toOfflineState() override;
diff --git a/ui/models/accounts.cpp b/ui/models/accounts.cpp
index 4343481..463ab40 100644
--- a/ui/models/accounts.cpp
+++ b/ui/models/accounts.cpp
@@ -48,6 +48,10 @@ QVariant Models::Accounts::data (const QModelIndex& index, int role) const
                 answer = Shared::connectionStateIcon(accs[index.row()]->getState());
             }
             break;
+        case Qt::ForegroundRole:
+            if (!accs[index.row()]->getActive()) {
+                answer = qApp->palette().brush(QPalette::Disabled, QPalette::Text);
+            }
         default:
             break;
     }
diff --git a/ui/models/roster.cpp b/ui/models/roster.cpp
index 588fb1d..1355fe3 100644
--- a/ui/models/roster.cpp
+++ b/ui/models/roster.cpp
@@ -276,6 +276,18 @@ QVariant Models::Roster::data (const QModelIndex& index, int role) const
                     break;
             }
             break;
+        case Qt::ForegroundRole:
+            switch (item->type) {
+                case Item::account: {
+                    Account* acc = static_cast<Account*>(item);
+                    if (!acc->getActive()) {
+                        result = qApp->palette().brush(QPalette::Disabled, QPalette::Text);
+                    }
+                }
+                    break;
+                default:
+                    break;
+            }
         default:
             break;
     }
diff --git a/ui/squawk.cpp b/ui/squawk.cpp
index 335b8d0..3a3d1d9 100644
--- a/ui/squawk.cpp
+++ b/ui/squawk.cpp
@@ -234,29 +234,7 @@ void Squawk::newAccount(const QMap<QString, QVariant>& account)
 void Squawk::onComboboxActivated(int index)
 {
     Shared::Availability av = Shared::Global::fromInt<Shared::Availability>(index);
-    if (av != Shared::Availability::offline) {
-        int size = rosterModel.accountsModel->rowCount(QModelIndex());
-        if (size > 0) {
-            emit changeState(av);
-            for (int i = 0; i < size; ++i) {
-                Models::Account* acc = rosterModel.accountsModel->getAccount(i);
-                if (acc->getState() == Shared::ConnectionState::disconnected) {
-                    emit connectAccount(acc->getName());
-                }
-            }
-        } else {
-            m_ui->comboBox->setCurrentIndex(static_cast<int>(Shared::Availability::offline));
-        }
-    } else {
-        emit changeState(av);
-        int size = rosterModel.accountsModel->rowCount(QModelIndex());
-        for (int i = 0; i != size; ++i) {
-            Models::Account* acc = rosterModel.accountsModel->getAccount(i);
-            if (acc->getState() != Shared::ConnectionState::disconnected) {
-                emit disconnectAccount(acc->getName());
-            }
-        }
-    }
+    emit changeState(av);
 }
 
 void Squawk::changeAccount(const QString& account, const QMap<QString, QVariant>& data)
@@ -573,17 +551,12 @@ void Squawk::onRosterContextMenu(const QPoint& point)
                 hasMenu = true;
                 QString name = acc->getName();
                 
-                if (acc->getState() != Shared::ConnectionState::disconnected) {
-                    QAction* con = contextMenu->addAction(Shared::icon("network-disconnect"), tr("Disconnect"));
-                    con->setEnabled(active);
-                    connect(con, &QAction::triggered, [this, name]() {
-                        emit disconnectAccount(name);
-                    });
+                if (acc->getActive()) {
+                    QAction* con = contextMenu->addAction(Shared::icon("network-disconnect"), tr("Deactivate"));
+                    connect(con, &QAction::triggered, std::bind(&Squawk::disconnectAccount, this, name));
                 } else {
-                    QAction* con = contextMenu->addAction(Shared::icon("network-connect"), tr("Connect"));
-                    connect(con, &QAction::triggered, [this, name]() {
-                        emit connectAccount(name);
-                    });
+                    QAction* con = contextMenu->addAction(Shared::icon("network-connect"), tr("Activate"));
+                    connect(con, &QAction::triggered, std::bind(&Squawk::connectAccount, this, name));
                 }
                 
                 QAction* card = contextMenu->addAction(Shared::icon("user-properties"), tr("VCard"));
@@ -591,11 +564,7 @@ void Squawk::onRosterContextMenu(const QPoint& point)
                 connect(card, &QAction::triggered, std::bind(&Squawk::onActivateVCard, this, name, acc->getBareJid(), true));
                 
                 QAction* remove = contextMenu->addAction(Shared::icon("edit-delete"), tr("Remove"));
-                remove->setEnabled(active);
-                connect(remove, &QAction::triggered, [this, name]() {
-                    emit removeAccount(name);
-                });
-                
+                connect(remove, &QAction::triggered, std::bind(&Squawk::removeAccount, this, name));
             }
                 break;
             case Models::Item::contact: {
@@ -839,20 +808,16 @@ void Squawk::readSettings()
 {
     QSettings settings;
     settings.beginGroup("ui");
-    
+    int avail;
     if (settings.contains("availability")) {
-        int avail = settings.value("availability").toInt();
-        m_ui->comboBox->setCurrentIndex(avail);
-        emit stateChanged(Shared::Global::fromInt<Shared::Availability>(avail));
-        
-        int size = settings.beginReadArray("connectedAccounts");
-        for (int i = 0; i < size; ++i) {
-            settings.setArrayIndex(i);
-            emit connectAccount(settings.value("name").toString());     //TODO  this is actually not needed, stateChanged event already connects everything you have
-        }                                                               //      need to fix that
-        settings.endArray();
+        avail = settings.value("availability").toInt();
+    } else {
+        avail = static_cast<int>(Shared::Availability::online);
     }
     settings.endGroup();
+    m_ui->comboBox->setCurrentIndex(avail);
+
+    emit changeState(Shared::Global::fromInt<Shared::Availability>(avail));
 }
 
 void Squawk::writeSettings()
@@ -867,19 +832,10 @@ void Squawk::writeSettings()
     settings.setValue("splitter", m_ui->splitter->saveState());
     
     settings.setValue("availability", m_ui->comboBox->currentIndex());
-    settings.beginWriteArray("connectedAccounts");
-    int size = rosterModel.accountsModel->rowCount(QModelIndex());
-    for (int i = 0; i < size; ++i) {
-        Models::Account* acc = rosterModel.accountsModel->getAccount(i);
-        if (acc->getState() != Shared::ConnectionState::disconnected) {
-            settings.setArrayIndex(i);
-            settings.setValue("name", acc->getName());
-        }
-    }
-    settings.endArray();
     
     settings.remove("roster");
     settings.beginGroup("roster");
+    int size = rosterModel.accountsModel->rowCount(QModelIndex());
     for (int i = 0; i < size; ++i) {
         QModelIndex acc = rosterModel.index(i, 0, QModelIndex());
         Models::Account* account = rosterModel.accountsModel->getAccount(i);
diff --git a/ui/widgets/accounts/account.cpp b/ui/widgets/accounts/account.cpp
index ba3af6b..164af6c 100644
--- a/ui/widgets/accounts/account.cpp
+++ b/ui/widgets/accounts/account.cpp
@@ -53,6 +53,7 @@ QMap<QString, QVariant> Account::value() const
     map["name"] = m_ui->name->text();
     map["resource"] = m_ui->resource->text();
     map["passwordType"] = m_ui->passwordType->currentIndex();
+    map["active"] = m_ui->active->isChecked();
     
     return map;
 }
diff --git a/ui/widgets/accounts/account.ui b/ui/widgets/accounts/account.ui
index a1879bc..b7f9f26 100644
--- a/ui/widgets/accounts/account.ui
+++ b/ui/widgets/accounts/account.ui
@@ -7,7 +7,7 @@
     <x>0</x>
     <y>0</y>
     <width>438</width>
-    <height>342</height>
+    <height>345</height>
    </rect>
   </property>
   <property name="windowTitle">
@@ -34,7 +34,7 @@
      <property name="verticalSpacing">
       <number>6</number>
      </property>
-     <item row="1" column="1">
+     <item row="2" column="1">
       <widget class="QLineEdit" name="login">
        <property name="toolTip">
         <string>Your account login</string>
@@ -44,14 +44,14 @@
        </property>
       </widget>
      </item>
-     <item row="2" column="0">
+     <item row="3" column="0">
       <widget class="QLabel" name="label_2">
        <property name="text">
         <string>Server</string>
        </property>
       </widget>
      </item>
-     <item row="2" column="1">
+     <item row="3" column="1">
       <widget class="QLineEdit" name="server">
        <property name="toolTip">
         <string>A server address of your account. Like 404.city or macaw.me</string>
@@ -61,21 +61,21 @@
        </property>
       </widget>
      </item>
-     <item row="1" column="0">
+     <item row="2" column="0">
       <widget class="QLabel" name="label">
        <property name="text">
         <string>Login</string>
        </property>
       </widget>
      </item>
-     <item row="3" column="0">
+     <item row="4" column="0">
       <widget class="QLabel" name="label_3">
        <property name="text">
         <string>Password</string>
        </property>
       </widget>
      </item>
-     <item row="3" column="1">
+     <item row="4" column="1">
       <widget class="QLineEdit" name="password">
        <property name="toolTip">
         <string>Password of your account</string>
@@ -97,14 +97,14 @@
        </property>
       </widget>
      </item>
-     <item row="0" column="0">
+     <item row="1" column="0">
       <widget class="QLabel" name="label_4">
        <property name="text">
         <string>Name</string>
        </property>
       </widget>
      </item>
-     <item row="0" column="1">
+     <item row="1" column="1">
       <widget class="QLineEdit" name="name">
        <property name="toolTip">
         <string>Just a name how would you call this account, doesn't affect anything</string>
@@ -114,14 +114,14 @@
        </property>
       </widget>
      </item>
-     <item row="6" column="0">
+     <item row="7" column="0">
       <widget class="QLabel" name="label_5">
        <property name="text">
         <string>Resource</string>
        </property>
       </widget>
      </item>
-     <item row="6" column="1">
+     <item row="7" column="1">
       <widget class="QLineEdit" name="resource">
        <property name="toolTip">
         <string>A resource name like &quot;Home&quot; or &quot;Work&quot;</string>
@@ -131,17 +131,17 @@
        </property>
       </widget>
      </item>
-     <item row="4" column="0">
+     <item row="5" column="0">
       <widget class="QLabel" name="label_6">
        <property name="text">
         <string>Password storage</string>
        </property>
       </widget>
      </item>
-     <item row="4" column="1">
+     <item row="5" column="1">
       <widget class="QComboBox" name="passwordType"/>
      </item>
-     <item row="5" column="1">
+     <item row="6" column="1">
       <widget class="QLabel" name="comment">
        <property name="sizePolicy">
         <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
@@ -157,6 +157,23 @@
        </property>
       </widget>
      </item>
+     <item row="0" column="0">
+      <widget class="QLabel" name="label_7">
+       <property name="text">
+        <string>Active</string>
+       </property>
+      </widget>
+     </item>
+     <item row="0" column="1">
+      <widget class="QCheckBox" name="active">
+       <property name="text">
+        <string>enable</string>
+       </property>
+       <property name="checked">
+        <bool>true</bool>
+       </property>
+      </widget>
+     </item>
     </layout>
    </item>
    <item>
diff --git a/ui/widgets/accounts/accounts.cpp b/ui/widgets/accounts/accounts.cpp
index 7f4a135..82a8ca0 100644
--- a/ui/widgets/accounts/accounts.cpp
+++ b/ui/widgets/accounts/accounts.cpp
@@ -83,7 +83,8 @@ void Accounts::onEditButton()
         {"server", mAcc->getServer()},
         {"name", mAcc->getName()},
         {"resource", mAcc->getResource()},
-        {"passwordType", QVariant::fromValue(mAcc->getPasswordType())}
+        {"passwordType", QVariant::fromValue(mAcc->getPasswordType())},
+        {"active", mAcc->getActive()}
     });
     acc->lockId();
     connect(acc, &Account::accepted, this, &Accounts::onAccountAccepted);
@@ -118,17 +119,17 @@ void Accounts::updateConnectButton()
         bool allConnected = true;
         for (int i = 0; i < selectionSize && allConnected; ++i) {
             const Models::Account* mAcc = model->getAccount(sm->selectedRows().at(i).row());
-            allConnected = mAcc->getState() == Shared::ConnectionState::connected;
+            allConnected = allConnected && mAcc->getActive();
         }
         if (allConnected) {
             toDisconnect = true;
-            m_ui->connectButton->setText(tr("Disconnect"));
+            m_ui->connectButton->setText(tr("Deactivate"));
         } else {
             toDisconnect = false;
-            m_ui->connectButton->setText(tr("Connect"));
+            m_ui->connectButton->setText(tr("Activate"));
         }
     } else {
-        m_ui->connectButton->setText(tr("Connect"));
+        m_ui->connectButton->setText(tr("Activate"));
         toDisconnect = false;
         m_ui->connectButton->setEnabled(false);
     }

From ce686e121b7f29b3fe5d9c05c85f205185734089 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Wed, 13 Apr 2022 22:02:48 +0300
Subject: [PATCH 186/281] account removal bugfix, some testing

---
 CHANGELOG.md    |  9 ++++++++-
 core/squawk.cpp | 12 ++++++++----
 ui/squawk.cpp   |  2 +-
 3 files changed, 17 insertions(+), 6 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index f563c85..410ff11 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,11 +2,18 @@
 
 ## Squawk 0.2.2 (UNRELEASED)
 ### Bug fixes
+- now when you remove an account it actually gets removed
+- segfault on unitialized Availability in some rare occesions
 
 ### Improvements
+- there is a way to disable an account and it wouldn't connect when you change availability
+- if you cancel password query an account becomes inactive and doesn't annoy you anymore
+- if you filled password field and chose KWallet as a storage Squawk wouldn't ask you again for the same password
+- if left the password field empty and chose KWallet as a storage Squawk will try to get that passord from KWallet before asking you to input it
+- accounts now connect to the server asyncronously - if one is stopped on password prompt another is connecting
 
 ### New features
-
+- new "About" window with links, license, gratitudes
 
 ## Squawk 0.2.1 (Apr 02, 2022)
 ### Bug fixes
diff --git a/core/squawk.cpp b/core/squawk.cpp
index d594553..49a2b34 100644
--- a/core/squawk.cpp
+++ b/core/squawk.cpp
@@ -139,8 +139,10 @@ void Core::Squawk::addAccount(
     bool active,
     Shared::AccountPassword passwordType)
 {
-    QSettings settings;
-    
+    if (amap.count(name) > 0) {
+        qDebug() << "An attempt to add account" << name << "but an account with such name already exist, ignoring";
+        return;
+    }
     Account* acc = new Account(login, server, password, name, active, &network);
     acc->setResource(resource);
     acc->setPasswordType(passwordType);
@@ -198,8 +200,10 @@ void Core::Squawk::addAccount(
     switch (passwordType) {
         case Shared::AccountPassword::alwaysAsk:
         case Shared::AccountPassword::kwallet:
-            acc->invalidatePassword();
-            break;
+            if (password == "") {
+                acc->invalidatePassword();
+                break;
+            }
         default:
             break;
     }
diff --git a/ui/squawk.cpp b/ui/squawk.cpp
index 3a3d1d9..a447458 100644
--- a/ui/squawk.cpp
+++ b/ui/squawk.cpp
@@ -564,7 +564,7 @@ void Squawk::onRosterContextMenu(const QPoint& point)
                 connect(card, &QAction::triggered, std::bind(&Squawk::onActivateVCard, this, name, acc->getBareJid(), true));
                 
                 QAction* remove = contextMenu->addAction(Shared::icon("edit-delete"), tr("Remove"));
-                connect(remove, &QAction::triggered, std::bind(&Squawk::removeAccount, this, name));
+                connect(remove, &QAction::triggered, std::bind(&Squawk::removeAccountRequest, this, name));
             }
                 break;
             case Models::Item::contact: {

From 8f949277f68be1f4baeb282b3c483948400dd6ca Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Thu, 14 Apr 2022 11:13:27 +0300
Subject: [PATCH 187/281] actual pasword reasking on failed authentication

---
 CHANGELOG.md                              |   1 +
 core/account.cpp                          |   7 ++
 core/account.h                            |   8 ++
 core/squawk.cpp                           |  25 +++-
 core/squawk.h                             |   2 +-
 ui/dialogqueue.cpp                        |  50 ++++++--
 ui/dialogqueue.h                          |   9 +-
 ui/squawk.cpp                             |  10 +-
 ui/squawk.h                               |   4 +-
 ui/widgets/accounts/CMakeLists.txt        |   3 +
 ui/widgets/accounts/credentialsprompt.cpp |  60 +++++++++
 ui/widgets/accounts/credentialsprompt.h   |  52 ++++++++
 ui/widgets/accounts/credentialsprompt.ui  | 144 ++++++++++++++++++++++
 13 files changed, 347 insertions(+), 28 deletions(-)
 create mode 100644 ui/widgets/accounts/credentialsprompt.cpp
 create mode 100644 ui/widgets/accounts/credentialsprompt.h
 create mode 100644 ui/widgets/accounts/credentialsprompt.ui

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 410ff11..55ef1f1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,6 +14,7 @@
 
 ### New features
 - new "About" window with links, license, gratitudes
+- if the authentication failed Squawk will ask againg for your password and login
 
 ## Squawk 0.2.1 (Apr 02, 2022)
 ### Bug fixes
diff --git a/core/account.cpp b/core/account.cpp
index 4d0480f..3b9d7ec 100644
--- a/core/account.cpp
+++ b/core/account.cpp
@@ -43,6 +43,7 @@ Account::Account(const QString& p_login, const QString& p_server, const QString&
     reconnectTimer(new QTimer),
     network(p_net),
     passwordType(Shared::AccountPassword::plain),
+    lastError(Error::none),
     pepSupport(false),
     active(p_active),
     notReadyPassword(false),
@@ -183,6 +184,7 @@ void Core::Account::onClientStateChange(QXmppClient::State st)
                         dm->requestItems(getServer());
                         dm->requestInfo(getServer());
                     }
+                    lastError = Error::none;
                     emit connectionStateChanged(state);
                 }
             } else {
@@ -415,6 +417,7 @@ void Core::Account::onClientError(QXmppClient::Error err)
     qDebug() << "Error";
     QString errorText;
     QString errorType;
+    lastError = Error::other;
     switch (err) {
         case QXmppClient::SocketError:
             errorText = client.socketErrorString();
@@ -456,6 +459,7 @@ void Core::Account::onClientError(QXmppClient::Error err)
                     break;
                 case QXmppStanza::Error::NotAuthorized:
                     errorText = "Authentication error";
+                    lastError = Error::authentication;
                     break;
 #if (QXMPP_VERSION) < QT_VERSION_CHECK(1, 3, 0)
                 case QXmppStanza::Error::PaymentRequired:
@@ -750,3 +754,6 @@ void Core::Account::renameContactRequest(const QString& jid, const QString& newN
 void Core::Account::invalidatePassword() {
     notReadyPassword = true;}
 
+Core::Account::Error Core::Account::getLastError() const {
+    return lastError;}
+
diff --git a/core/account.h b/core/account.h
index aa65b27..2c9ec70 100644
--- a/core/account.h
+++ b/core/account.h
@@ -61,6 +61,12 @@ class Account : public QObject
     friend class RosterHandler;
     friend class VCardHandler;
 public:
+    enum class Error {
+        authentication,
+        other,
+        none
+    };
+
     Account(
         const QString& p_login, 
         const QString& p_server, 
@@ -82,6 +88,7 @@ public:
     QString getFullJid() const;
     Shared::Availability getAvailability() const;
     Shared::AccountPassword getPasswordType() const;
+    Error getLastError() const;
     bool getActive() const;
     
     void setName(const QString& p_name);
@@ -166,6 +173,7 @@ private:
     
     NetworkAccess* network;
     Shared::AccountPassword passwordType;
+    Error lastError;
     bool pepSupport;
     bool active;
     bool notReadyPassword;
diff --git a/core/squawk.cpp b/core/squawk.cpp
index 49a2b34..0f8fe9f 100644
--- a/core/squawk.cpp
+++ b/core/squawk.cpp
@@ -260,7 +260,10 @@ void Core::Squawk::disconnectAccount(const QString& account)
 void Core::Squawk::onAccountConnectionStateChanged(Shared::ConnectionState p_state)
 {
     Account* acc = static_cast<Account*>(sender());
-    emit changeAccount(acc->getName(), {{"state", QVariant::fromValue(p_state)}});
+    emit changeAccount(acc->getName(), {
+        {"state", QVariant::fromValue(p_state)},
+        {"error", ""}
+    });
 
 #ifdef WITH_KWALLET
     if (p_state == Shared::ConnectionState::connected) {
@@ -398,6 +401,7 @@ void Core::Squawk::modifyAccountRequest(const QString& name, const QMap<QString,
     Shared::ConnectionState st = acc->getState();
     QMap<QString, QVariant>::const_iterator mItr;
     bool needToReconnect = false;
+    bool wentReconnecting = false;
     
     mItr = map.find("login");
     if (mItr != map.end()) {
@@ -428,6 +432,7 @@ void Core::Squawk::modifyAccountRequest(const QString& name, const QMap<QString,
     if (mItr == map.end() || mItr->toBool() == acc->getActive()) {
         if (needToReconnect && st != Shared::ConnectionState::disconnected) {
             acc->reconnect();
+            wentReconnecting = true;
         }
     } else {
         acc->setActive(mItr->toBool());
@@ -468,8 +473,12 @@ void Core::Squawk::modifyAccountRequest(const QString& name, const QMap<QString,
     }
 #endif
     
-    if (activeChanged && acc->getActive() && state != Shared::Availability::offline) {
-        acc->connect();
+    if (state != Shared::Availability::offline) {
+        if (activeChanged && acc->getActive()) {
+            acc->connect();
+        } else if (!wentReconnecting && acc->getActive() && acc->getLastError() == Account::Error::authentication) {
+            acc->connect();
+        }
     }
 
     emit changeAccount(name, map);
@@ -479,6 +488,10 @@ void Core::Squawk::onAccountError(const QString& text)
 {
     Account* acc = static_cast<Account*>(sender());
     emit changeAccount(acc->getName(), {{"error", text}});
+
+    if (acc->getLastError() == Account::Error::authentication) {
+        emit requestPassword(acc->getName(), true);
+    }
 }
 
 void Core::Squawk::removeAccountRequest(const QString& name)
@@ -733,7 +746,7 @@ void Core::Squawk::onAccountNeedPassword()
     Account* acc = static_cast<Account*>(sender());
     switch (acc->getPasswordType()) {
         case Shared::AccountPassword::alwaysAsk:
-            emit requestPassword(acc->getName());
+            emit requestPassword(acc->getName(), false);
             break;
         case Shared::AccountPassword::kwallet: {
 #ifdef WITH_KWALLET
@@ -741,7 +754,7 @@ void Core::Squawk::onAccountNeedPassword()
                 kwallet.requestReadPassword(acc->getName());
             } else {
 #endif
-                emit requestPassword(acc->getName());
+                emit requestPassword(acc->getName(), false);
 #ifdef WITH_KWALLET
             }
 #endif
@@ -754,7 +767,7 @@ void Core::Squawk::onAccountNeedPassword()
 
 void Core::Squawk::onWalletRejectPassword(const QString& login)
 {
-    emit requestPassword(login);
+    emit requestPassword(login, false);
 }
 
 void Core::Squawk::responsePassword(const QString& account, const QString& password)
diff --git a/core/squawk.h b/core/squawk.h
index 6cb3115..c82b1c8 100644
--- a/core/squawk.h
+++ b/core/squawk.h
@@ -86,7 +86,7 @@ signals:
     
     void responseVCard(const QString& jid, const Shared::VCard& card);
     void changeMessage(const QString& account, const QString& jid, const QString& id, const QMap<QString, QVariant>& data);
-    void requestPassword(const QString& account);
+    void requestPassword(const QString& account, bool authernticationError);
     
 public slots:
     void start();
diff --git a/ui/dialogqueue.cpp b/ui/dialogqueue.cpp
index f5be82b..02f8688 100644
--- a/ui/dialogqueue.cpp
+++ b/ui/dialogqueue.cpp
@@ -76,16 +76,31 @@ void DialogQueue::performNextAction()
         case none:
             actionDone();
             break;
-        case askPassword:
-            prompt = new QInputDialog(squawk);
-            connect(prompt, &QDialog::accepted, this, &DialogQueue::onPropmtAccepted);
-            connect(prompt, &QDialog::rejected, this, &DialogQueue::onPropmtRejected);
-            prompt->setInputMode(QInputDialog::TextInput);
-            prompt->setTextEchoMode(QLineEdit::Password);
-            prompt->setLabelText(tr("Input the password for account %1").arg(currentSource));
-            prompt->setWindowTitle(tr("Password for account %1").arg(currentSource));
-            prompt->setTextValue("");
-            prompt->exec();
+        case askPassword: {
+            QInputDialog* dialog = new QInputDialog(squawk);
+            prompt = dialog;
+            connect(dialog, &QDialog::accepted, this, &DialogQueue::onPropmtAccepted);
+            connect(dialog, &QDialog::rejected, this, &DialogQueue::onPropmtRejected);
+            dialog->setInputMode(QInputDialog::TextInput);
+            dialog->setTextEchoMode(QLineEdit::Password);
+            dialog->setLabelText(tr("Input the password for account %1").arg(currentSource));
+            dialog->setWindowTitle(tr("Password for account %1").arg(currentSource));
+            dialog->setTextValue("");
+            dialog->exec();
+        }
+            break;
+        case askCredentials: {
+            CredentialsPrompt* dialog = new CredentialsPrompt(squawk);
+            prompt = dialog;
+            connect(dialog, &QDialog::accepted, this, &DialogQueue::onPropmtAccepted);
+            connect(dialog, &QDialog::rejected, this, &DialogQueue::onPropmtRejected);
+            Models::Account* acc = squawk->rosterModel.getAccount(currentSource);
+            dialog->setAccount(currentSource);
+            dialog->setLogin(acc->getLogin());
+            dialog->setPassword(acc->getPassword());
+            dialog->exec();
+        }
+            break;
     }
 }
 
@@ -94,8 +109,18 @@ void DialogQueue::onPropmtAccepted()
     switch (currentAction) {
         case none:
             break;
-        case askPassword:
-            emit squawk->responsePassword(currentSource, prompt->textValue());
+        case askPassword: {
+            QInputDialog* dialog = static_cast<QInputDialog*>(prompt);
+            emit squawk->responsePassword(currentSource, dialog->textValue());
+        }
+            break;
+        case askCredentials: {
+            CredentialsPrompt* dialog = static_cast<CredentialsPrompt*>(prompt);
+            emit squawk->modifyAccountRequest(currentSource, {
+                {"login", dialog->getLogin()},
+                {"password", dialog->getPassword()}
+            });
+        }
             break;
     }
     actionDone();
@@ -107,6 +132,7 @@ void DialogQueue::onPropmtRejected()
         case none:
             break;
         case askPassword:
+        case askCredentials:
             emit squawk->disconnectAccount(currentSource);
             break;
     }
diff --git a/ui/dialogqueue.h b/ui/dialogqueue.h
index c5bf011..bfc1f21 100644
--- a/ui/dialogqueue.h
+++ b/ui/dialogqueue.h
@@ -24,9 +24,7 @@
 #include <boost/multi_index/ordered_index.hpp>
 #include <boost/multi_index/sequenced_index.hpp>
 
-/**
- * @todo write docs
- */
+#include <ui/widgets/accounts/credentialsprompt.h>
 
 class Squawk;
 
@@ -36,7 +34,8 @@ class DialogQueue : public QObject
 public:
     enum Action {
         none,
-        askPassword
+        askPassword,
+        askCredentials
     };
 
     DialogQueue(Squawk* squawk);
@@ -85,7 +84,7 @@ private:
     Collection& collection;
     Sequence& sequence;
 
-    QInputDialog* prompt;
+    QDialog* prompt;
     Squawk* squawk;
 };
 
diff --git a/ui/squawk.cpp b/ui/squawk.cpp
index a447458..a0f16b2 100644
--- a/ui/squawk.cpp
+++ b/ui/squawk.cpp
@@ -880,8 +880,14 @@ void Squawk::onItemCollepsed(const QModelIndex& index)
     }
 }
 
-void Squawk::requestPassword(const QString& account) {
-    dialogueQueue.addAction(account, DialogQueue::askPassword);}
+void Squawk::requestPassword(const QString& account, bool authenticationError) {
+    if (authenticationError) {
+        dialogueQueue.addAction(account, DialogQueue::askCredentials);
+    } else {
+        dialogueQueue.addAction(account, DialogQueue::askPassword);
+    }
+
+}
 
 void Squawk::subscribeConversation(Conversation* conv)
 {
diff --git a/ui/squawk.h b/ui/squawk.h
index 6ee666c..5a77f17 100644
--- a/ui/squawk.h
+++ b/ui/squawk.h
@@ -51,7 +51,7 @@ class Squawk;
 class Squawk : public QMainWindow
 {
     Q_OBJECT
-
+    friend class DialogQueue;
 public:
     explicit Squawk(QWidget *parent = nullptr);
     ~Squawk() override;
@@ -114,7 +114,7 @@ public slots:
     void fileUploadComplete(const std::list<Shared::MessageInfo> msgs, const QString& url, const QString& path);
     void responseVCard(const QString& jid, const Shared::VCard& card);
     void changeMessage(const QString& account, const QString& jid, const QString& id, const QMap<QString, QVariant>& data);
-    void requestPassword(const QString& account);
+    void requestPassword(const QString& account, bool authenticationError);
     
 private:
     typedef std::map<Models::Roster::ElId, Conversation*> Conversations;
diff --git a/ui/widgets/accounts/CMakeLists.txt b/ui/widgets/accounts/CMakeLists.txt
index ad2f117..970985d 100644
--- a/ui/widgets/accounts/CMakeLists.txt
+++ b/ui/widgets/accounts/CMakeLists.txt
@@ -5,4 +5,7 @@ target_sources(squawk PRIVATE
   accounts.cpp
   accounts.h
   accounts.ui
+  credentialsprompt.cpp
+  credentialsprompt.h
+  credentialsprompt.ui
   )
diff --git a/ui/widgets/accounts/credentialsprompt.cpp b/ui/widgets/accounts/credentialsprompt.cpp
new file mode 100644
index 0000000..3d1bafa
--- /dev/null
+++ b/ui/widgets/accounts/credentialsprompt.cpp
@@ -0,0 +1,60 @@
+// Squawk messenger. 
+// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+// 
+// 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 "credentialsprompt.h"
+#include "ui_credentialsprompt.h"
+
+CredentialsPrompt::CredentialsPrompt(QWidget* parent):
+    QDialog(parent),
+    m_ui(new Ui::CredentialsPrompt),
+    title(),
+    message()
+{
+    m_ui->setupUi(this);
+
+    title = windowTitle();
+    message = m_ui->message->text();
+}
+
+CredentialsPrompt::~CredentialsPrompt()
+{
+}
+
+void CredentialsPrompt::setAccount(const QString& account)
+{
+    m_ui->message->setText(message.arg(account));
+    setWindowTitle(title.arg(account));
+}
+
+QString CredentialsPrompt::getLogin() const
+{
+    return m_ui->login->text();
+}
+
+QString CredentialsPrompt::getPassword() const
+{
+    return m_ui->password->text();
+}
+
+void CredentialsPrompt::setLogin(const QString& login)
+{
+    m_ui->login->setText(login);
+}
+
+void CredentialsPrompt::setPassword(const QString& password)
+{
+    m_ui->password->setText(password);
+}
diff --git a/ui/widgets/accounts/credentialsprompt.h b/ui/widgets/accounts/credentialsprompt.h
new file mode 100644
index 0000000..ce9a791
--- /dev/null
+++ b/ui/widgets/accounts/credentialsprompt.h
@@ -0,0 +1,52 @@
+// Squawk messenger. 
+// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+// 
+// 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/>.
+
+#ifndef CREDENTIALSPROMPT_H
+#define CREDENTIALSPROMPT_H
+
+#include <qdialog.h>
+#include <QScopedPointer>
+
+namespace Ui
+{
+class CredentialsPrompt;
+}
+
+/**
+ * @todo write docs
+ */
+class CredentialsPrompt : public QDialog
+{
+    Q_OBJECT
+
+public:
+    CredentialsPrompt(QWidget* parent = nullptr);
+    ~CredentialsPrompt();
+
+    void setAccount(const QString& account);
+    void setLogin(const QString& login);
+    void setPassword(const QString& password);
+
+    QString getLogin() const;
+    QString getPassword() const;
+
+private:
+    QScopedPointer<Ui::CredentialsPrompt> m_ui;
+    QString title;
+    QString message;
+};
+
+#endif // CREDENTIALSPROMPT_H
diff --git a/ui/widgets/accounts/credentialsprompt.ui b/ui/widgets/accounts/credentialsprompt.ui
new file mode 100644
index 0000000..2ad4d8d
--- /dev/null
+++ b/ui/widgets/accounts/credentialsprompt.ui
@@ -0,0 +1,144 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>CredentialsPrompt</class>
+ <widget class="QDialog" name="CredentialsPrompt">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>318</width>
+    <height>229</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Authentication error: %1</string>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout" stretch="0,0,0">
+   <item>
+    <widget class="QLabel" name="message">
+     <property name="enabled">
+      <bool>true</bool>
+     </property>
+     <property name="sizePolicy">
+      <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+       <horstretch>0</horstretch>
+       <verstretch>0</verstretch>
+      </sizepolicy>
+     </property>
+     <property name="text">
+      <string>Couldn't authenticate account %1: login or password is icorrect.
+Would you like to check them and try again?</string>
+     </property>
+     <property name="alignment">
+      <set>Qt::AlignCenter</set>
+     </property>
+     <property name="wordWrap">
+      <bool>true</bool>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <layout class="QFormLayout" name="formLayout">
+     <item row="0" column="0">
+      <widget class="QLabel" name="loginLabel">
+       <property name="text">
+        <string>Login</string>
+       </property>
+      </widget>
+     </item>
+     <item row="0" column="1">
+      <widget class="QLineEdit" name="login">
+       <property name="toolTip">
+        <string>Your account login (without @server.domain)</string>
+       </property>
+      </widget>
+     </item>
+     <item row="1" column="0">
+      <widget class="QLabel" name="passwordLabel">
+       <property name="text">
+        <string>Password</string>
+       </property>
+      </widget>
+     </item>
+     <item row="1" column="1">
+      <widget class="QLineEdit" name="password">
+       <property name="toolTip">
+        <string>Your password</string>
+       </property>
+       <property name="inputMethodHints">
+        <set>Qt::ImhHiddenText|Qt::ImhNoAutoUppercase|Qt::ImhNoPredictiveText|Qt::ImhSensitiveData</set>
+       </property>
+       <property name="text">
+        <string/>
+       </property>
+       <property name="echoMode">
+        <enum>QLineEdit::Password</enum>
+       </property>
+       <property name="clearButtonEnabled">
+        <bool>false</bool>
+       </property>
+      </widget>
+     </item>
+     <item row="2" column="1">
+      <spacer name="verticalSpacer">
+       <property name="orientation">
+        <enum>Qt::Vertical</enum>
+       </property>
+       <property name="sizeHint" stdset="0">
+        <size>
+         <width>20</width>
+         <height>40</height>
+        </size>
+       </property>
+      </spacer>
+     </item>
+    </layout>
+   </item>
+   <item>
+    <widget class="QDialogButtonBox" name="buttonBox">
+     <property name="orientation">
+      <enum>Qt::Horizontal</enum>
+     </property>
+     <property name="standardButtons">
+      <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+     </property>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>accepted()</signal>
+   <receiver>CredentialsPrompt</receiver>
+   <slot>accept()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>20</x>
+     <y>20</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>20</x>
+     <y>20</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>rejected()</signal>
+   <receiver>CredentialsPrompt</receiver>
+   <slot>reject()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>20</x>
+     <y>20</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>20</x>
+     <y>20</y>
+    </hint>
+   </hints>
+  </connection>
+ </connections>
+</ui>

From 51ac1ac709042cb61f122860de5f47c8f82e8e63 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Sun, 17 Apr 2022 14:58:46 +0300
Subject: [PATCH 188/281] first attempt

---
 ui/utils/CMakeLists.txt                    |   2 +
 ui/utils/textmeter.cpp                     | 233 +++++++++++++++++++++
 ui/utils/textmeter.h                       |  68 ++++++
 ui/widgets/messageline/messagedelegate.cpp |  11 +-
 ui/widgets/messageline/messagedelegate.h   |   2 +
 5 files changed, 311 insertions(+), 5 deletions(-)
 create mode 100644 ui/utils/textmeter.cpp
 create mode 100644 ui/utils/textmeter.h

diff --git a/ui/utils/CMakeLists.txt b/ui/utils/CMakeLists.txt
index b46d30d..823287d 100644
--- a/ui/utils/CMakeLists.txt
+++ b/ui/utils/CMakeLists.txt
@@ -15,4 +15,6 @@ target_sources(squawk PRIVATE
   resizer.h
   shadowoverlay.cpp
   shadowoverlay.h
+  textmeter.cpp
+  textmeter.h
   )
diff --git a/ui/utils/textmeter.cpp b/ui/utils/textmeter.cpp
new file mode 100644
index 0000000..51c6d54
--- /dev/null
+++ b/ui/utils/textmeter.cpp
@@ -0,0 +1,233 @@
+// Squawk messenger. 
+// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+// 
+// 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 "textmeter.h"
+#include <QDebug>
+#include <QApplication>
+
+TextMeter::TextMeter():
+    base(),
+    fonts()
+{
+}
+
+TextMeter::~TextMeter()
+{
+}
+
+void TextMeter::initializeFonts(const QFont& font)
+{
+    fonts.clear();
+    QList<QFontDatabase::WritingSystem> supported = base.writingSystems(font.family());
+    std::set<QFontDatabase::WritingSystem> sup;
+    std::set<QString> added({font.family()});
+    for (const QFontDatabase::WritingSystem& system : supported) {
+        sup.insert(system);
+    }
+    fonts.push_back(QFontMetrics(font));
+    QString style = base.styleString(font);
+
+    QList<QFontDatabase::WritingSystem> systems = base.writingSystems();
+    for (const QFontDatabase::WritingSystem& system : systems) {
+        if (sup.count(system) == 0) {
+            QStringList families = base.families(system);
+            if (!families.empty() && added.count(families.first()) == 0) {
+                QString family(families.first());
+                QFont nfont = base.font(family, style, font.pointSize());
+                if (added.count(nfont.family()) == 0) {
+                    added.insert(family);
+                    fonts.push_back(QFontMetrics(nfont));
+                    qDebug() << "Added font" << nfont.family() << "for" << system;
+                }
+            }
+        }
+    }
+}
+
+QSize TextMeter::boundingSize(const QString& text, const QSize& limits) const
+{
+//     QString str("ridiculus mus. Suspendisse potenti. Cras pretium venenatis enim, faucibus accumsan ex");
+//     bool first = true;
+//     int width = 0;
+//     QStringList list = str.split(" ");
+//     QFontMetrics m = fonts.front();
+//     for (const QString& word : list) {
+//         if (first) {
+//             first = false;
+//         } else {
+//             width += m.horizontalAdvance(QChar::Space);
+//         }
+//         width += m.horizontalAdvance(word);
+//         for (const QChar& ch : word) {
+//             width += m.horizontalAdvance(ch);
+//         }
+//     }
+//     qDebug() << "together:" << m.horizontalAdvance(str);
+//     qDebug() << "apart:" << width;
+//     I cant measure or wrap text this way, this simple example shows that even this gives differen result
+//     The Qt implementation under it is thousands and thousands lines of code in QTextEngine
+//     I simply can't get though it
+
+    if (text.size() == 0) {
+        return QSize (0, 0);
+    }
+    Helper current(limits.width());
+    for (const QChar& ch : text) {
+        if (newLine(ch)) {
+            current.computeNewWord();
+            if (current.height == 0) {
+                current.height = fonts.front().lineSpacing();
+            }
+            current.beginNewLine();
+        } else if (visible(ch)) {
+            bool found = false;
+            for (const QFontMetrics& metrics : fonts) {
+                if (metrics.inFont(ch)) {
+                    current.computeChar(ch, metrics);
+                    found = true;
+                    break;
+                }
+            }
+
+            if (!found) {
+                current.computeChar(ch, fonts.front());
+            }
+        }
+    }
+    current.computeNewWord();
+    current.beginNewLine();
+
+    int& height = current.size.rheight();
+    if (height > 0) {
+        height -= fonts.front().leading();
+    }
+
+    return current.size;
+}
+
+void TextMeter::Helper::computeChar(const QChar& ch, const QFontMetrics& metrics)
+{
+    int ha = metrics.horizontalAdvance(ch);
+    if (newWord(ch)) {
+        if (printOnLineBreak(ch)) {
+            if (!lineOverflow(metrics, ha, ch)){
+                computeNewWord();
+            }
+        } else {
+            computeNewWord();
+            delayedSpaceWidth = ha;
+            lastSpace = ch;
+        }
+    } else {
+        lineOverflow(metrics, ha, ch);
+    }
+}
+
+void TextMeter::Helper::computeNewLine(const QFontMetrics& metrics, int horizontalAdvance, const QChar& ch)
+{
+    if (wordBeganWithTheLine) {
+        text = word.chopped(1);
+        width = wordWidth - horizontalAdvance;
+        height = wordHeight;
+    }
+    if (width != metrics.horizontalAdvance(text)) {
+        qDebug() << "Kerning Error" << width - metrics.horizontalAdvance(text);
+    }
+    beginNewLine();
+    if (wordBeganWithTheLine) {
+        word = ch;
+        wordWidth = horizontalAdvance;
+        wordHeight = metrics.lineSpacing();
+    }
+
+    wordBeganWithTheLine = true;
+    delayedSpaceWidth = 0;
+    lastSpace = QChar::Null;
+}
+
+void TextMeter::Helper::beginNewLine()
+{
+    size.rheight() += height;
+    size.rwidth() = qMax(size.width(), width);
+    qDebug() << text;
+    text = "";
+    width = 0;
+    height = 0;
+}
+
+void TextMeter::Helper::computeNewWord()
+{
+    width += wordWidth + delayedSpaceWidth;
+    height = qMax(height, wordHeight);
+    if (lastSpace != QChar::Null) {
+        text += lastSpace;
+    }
+    text += word;
+    word = "";
+    wordWidth = 0;
+    wordHeight = 0;
+    delayedSpaceWidth = 0;
+    lastSpace = QChar::Null;
+    wordBeganWithTheLine = false;
+}
+
+bool TextMeter::Helper::lineOverflow(const QFontMetrics& metrics, int horizontalAdvance, const QChar& ch)
+{
+    wordHeight = qMax(wordHeight, metrics.lineSpacing());
+    wordWidth += horizontalAdvance;
+    word += ch;
+    if (width + delayedSpaceWidth + wordWidth > maxWidth) {
+        computeNewLine(metrics, horizontalAdvance, ch);
+        return true;
+    }
+    return false;
+}
+
+
+bool TextMeter::newLine(const QChar& ch)
+{
+    return ch == QChar::LineFeed;
+}
+
+bool TextMeter::newWord(const QChar& ch)
+{
+    return ch.isSpace() || ch.isPunct();
+}
+
+bool TextMeter::printOnLineBreak(const QChar& ch)
+{
+    return ch != QChar::Space;
+}
+
+bool TextMeter::visible(const QChar& ch)
+{
+    return true;
+}
+
+TextMeter::Helper::Helper(int p_maxWidth):
+    width(0),
+    height(0),
+    wordWidth(0),
+    wordHeight(0),
+    delayedSpaceWidth(0),
+    maxWidth(p_maxWidth),
+    wordBeganWithTheLine(true),
+    text(""),
+    word(""),
+    lastSpace(QChar::Null),
+    size(0, 0)
+{
+}
diff --git a/ui/utils/textmeter.h b/ui/utils/textmeter.h
new file mode 100644
index 0000000..243d989
--- /dev/null
+++ b/ui/utils/textmeter.h
@@ -0,0 +1,68 @@
+// Squawk messenger. 
+// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+// 
+// 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/>.
+
+#ifndef TEXTMETER_H
+#define TEXTMETER_H
+
+#include <list>
+#include <set>
+
+#include <QFontMetrics>
+#include <QFont>
+#include <QSize>
+#include <QFontDatabase>
+
+class TextMeter
+{
+public:
+    TextMeter();
+    ~TextMeter();
+    void initializeFonts(const QFont& font);
+    QSize boundingSize(const QString& text, const QSize& limits) const;
+
+private:
+    QFontDatabase base;
+    std::list<QFontMetrics> fonts;
+
+    struct Helper {
+        Helper(int maxWidth);
+        int width;
+        int height;
+        int wordWidth;
+        int wordHeight;
+        int delayedSpaceWidth;
+        int maxWidth;
+        bool wordBeganWithTheLine;
+        QString text;
+        QString word;
+        QChar lastSpace;
+        QSize size;
+
+        void computeNewLine(const QFontMetrics& metrics, int horizontalAdvance, const QChar& ch);
+        void computeChar(const QChar& ch, const QFontMetrics& metrics);
+        void computeNewWord();
+        void beginNewLine();
+        bool lineOverflow(const QFontMetrics& metrics, int horizontalAdvance, const QChar& ch);
+    };
+
+    static bool newLine(const QChar& ch);
+    static bool newWord(const QChar& ch);
+    static bool visible(const QChar& ch);
+    static bool printOnLineBreak(const QChar& ch);
+
+};
+
+#endif // TEXTMETER_H
diff --git a/ui/widgets/messageline/messagedelegate.cpp b/ui/widgets/messageline/messagedelegate.cpp
index 15a5e46..4ddecee 100644
--- a/ui/widgets/messageline/messagedelegate.cpp
+++ b/ui/widgets/messageline/messagedelegate.cpp
@@ -42,6 +42,7 @@ MessageDelegate::MessageDelegate(QObject* parent):
     nickFont(),
     dateFont(),
     bodyMetrics(bodyFont),
+    bodyMeter(),
     nickMetrics(nickFont),
     dateMetrics(dateFont),
     buttonHeight(0),
@@ -123,10 +124,7 @@ void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio
         opt.displayAlignment = Qt::AlignRight | Qt::AlignTop;
     }
 
-    QSize bodySize(0, 0);
-    if (data.text.size() > 0) {
-        bodySize = bodyMetrics.boundingRect(opt.rect, Qt::TextWordWrap, data.text).size();
-    }
+    QSize bodySize = bodyMeter.boundingSize(data.text, opt.rect.size());
 
     QRect rect;
     if (ntds) {
@@ -306,7 +304,7 @@ QSize MessageDelegate::sizeHint(const QStyleOptionViewItem& option, const QModel
     Models::FeedItem data = qvariant_cast<Models::FeedItem>(vi);
     QSize messageSize(0, 0);
     if (data.text.size() > 0) {
-        messageSize = bodyMetrics.boundingRect(messageRect, Qt::TextWordWrap, data.text).size();
+        messageSize = bodyMeter.boundingSize(data.text, messageRect.size());
         messageSize.rheight() += textMargin;
     }
     
@@ -390,9 +388,12 @@ void MessageDelegate::initializeFonts(const QFont& font)
         dateFont.setPointSize(dateFont.pointSize() * dateFontMultiplier);
     }
     
+    bodyFont.setKerning(false);
     bodyMetrics = QFontMetrics(bodyFont);
     nickMetrics = QFontMetrics(nickFont);
     dateMetrics = QFontMetrics(dateFont);
+
+    bodyMeter.initializeFonts(bodyFont);
     
     Preview::initializeFont(bodyFont);
 }
diff --git a/ui/widgets/messageline/messagedelegate.h b/ui/widgets/messageline/messagedelegate.h
index 9225412..38ec0ee 100644
--- a/ui/widgets/messageline/messagedelegate.h
+++ b/ui/widgets/messageline/messagedelegate.h
@@ -34,6 +34,7 @@
 #include "shared/global.h"
 #include "shared/utils.h"
 #include "shared/pathcheck.h"
+#include "ui/utils/textmeter.h"
 
 #include "preview.h"
 
@@ -94,6 +95,7 @@ private:
     QFont nickFont;
     QFont dateFont;
     QFontMetrics bodyMetrics;
+    TextMeter bodyMeter;
     QFontMetrics nickMetrics;
     QFontMetrics dateMetrics;
     

From 4c20a314f050236fbcb79fa2d43322ec9045eaa1 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Sun, 17 Apr 2022 16:25:15 +0300
Subject: [PATCH 189/281] a crash fix on one of archive corner cases

---
 CHANGELOG.md        |  1 +
 core/rosteritem.cpp | 28 ++++++++++++++++++----------
 2 files changed, 19 insertions(+), 10 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 55ef1f1..4daf652 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,7 @@
 ### Bug fixes
 - now when you remove an account it actually gets removed
 - segfault on unitialized Availability in some rare occesions
+- fixed crash when you open a dialog with someone that has only error messages in archive
 
 ### Improvements
 - there is a way to disable an account and it wouldn't connect when you change availability
diff --git a/core/rosteritem.cpp b/core/rosteritem.cpp
index b1951d6..1b8d1e6 100644
--- a/core/rosteritem.cpp
+++ b/core/rosteritem.cpp
@@ -124,15 +124,19 @@ void Core::RosterItem::nextRequest()
         if (requestedCount != -1) {
             bool last = false;
             if (archiveState == beginning || archiveState == complete) {
-                QString firstId = archive->oldestId();
-                if (responseCache.size() == 0) {
-                    if (requestedBefore == firstId) {
-                        last = true;
-                    }
-                } else {
-                    if (responseCache.front().getId() == firstId) {
-                        last = true;
+                try {
+                    QString firstId = archive->oldestId();
+                    if (responseCache.size() == 0) {
+                        if (requestedBefore == firstId) {
+                            last = true;
+                        }
+                    } else {
+                        if (responseCache.front().getId() == firstId) {
+                            last = true;
+                        }
                     }
+                } catch (const Archive::Empty& e) {
+                    last = true;
                 }
             } else if (archiveState == empty && responseCache.size() == 0) {
                 last = true;
@@ -171,8 +175,12 @@ void Core::RosterItem::performRequest(int count, const QString& before)
                 requestCache.emplace_back(requestedCount, before);
                 requestedCount = -1;
             }
-            Shared::Message msg = archive->newest();
-            emit needHistory("", getId(msg), msg.getTime());
+            try {
+                Shared::Message msg = archive->newest();
+                emit needHistory("", getId(msg), msg.getTime());
+            } catch (const Archive::Empty& e) {                     //this can happen when the only message in archive is not server stored (error, for example)
+                emit needHistory(before, "");
+            }
         }
             break;
         case end: 

From 18859cb960aa6de0dbb393186392b3449b5177ff Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Mon, 18 Apr 2022 19:54:42 +0300
Subject: [PATCH 190/281] first ideas for notifications

---
 shared/global.cpp    | 35 +++++++++++++++++++++++++++++++++++
 shared/global.h      | 14 ++++++++++++--
 ui/models/roster.cpp |  4 ++--
 ui/models/roster.h   |  4 ++--
 ui/squawk.cpp        | 31 ++-----------------------------
 ui/squawk.h          |  5 ++---
 6 files changed, 55 insertions(+), 38 deletions(-)

diff --git a/shared/global.cpp b/shared/global.cpp
index 122bc79..14ae90d 100644
--- a/shared/global.cpp
+++ b/shared/global.cpp
@@ -19,6 +19,7 @@
 #include "global.h"
 
 #include "enums.h"
+#include "ui/models/roster.h"
 
 Shared::Global* Shared::Global::instance = 0;
 const std::set<QString> Shared::Global::supportedImagesExts = {"png", "jpg", "webp", "jpeg", "gif", "svg"};
@@ -94,6 +95,8 @@ Shared::Global::Global():
     }),
     defaultSystemStyle(QApplication::style()->objectName()),
     defaultSystemPalette(QApplication::palette()),
+    rosterModel(new Models::Roster()),
+    dbus("org.freedesktop.Notifications", "/org/freedesktop/Notifications", "org.freedesktop.Notifications", QDBusConnection::sessionBus()),
     pluginSupport({
         {"KWallet", false},
         {"openFileManagerWindowJob", false},
@@ -349,6 +352,38 @@ void Shared::Global::setStyle(const QString& style)
     }
 }
 
+void Shared::Global::notify(const QString& account, const Shared::Message& msg)
+{
+    QString name = QString(instance->rosterModel->getContactName(account, msg.getPenPalJid()));
+    QString path = QString(instance->rosterModel->getContactIconPath(account, msg.getPenPalJid(), msg.getPenPalResource()));
+    QVariantList args;
+    args << QString(QCoreApplication::applicationName());
+    args << QVariant(QVariant::UInt);   //TODO some normal id
+    if (path.size() > 0) {
+        args << path;
+    } else {
+        args << QString("mail-message");    //TODO should here better be unknown user icon?
+    }
+    if (msg.getType() == Shared::Message::groupChat) {
+        args << msg.getFromResource() + " from " + name;
+    } else {
+        args << name;
+    }
+
+    QString body(msg.getBody());
+    QString oob(msg.getOutOfBandUrl());
+    if (body == oob) {
+        body = tr("Attached file");
+    }
+
+    args << body;
+    args << QStringList();
+    args << QVariantMap();
+    args << 3000;
+    instance->dbus.callWithArgumentList(QDBus::AutoDetect, "Notify", args);
+}
+
+
 #define FROM_INT_INPL(Enum)                                                                 \
 template<>                                                                                  \
 Enum Shared::Global::fromInt(int src)                                                       \
diff --git a/shared/global.h b/shared/global.h
index 2056639..fcd8105 100644
--- a/shared/global.h
+++ b/shared/global.h
@@ -42,12 +42,18 @@
 #include <QProcess>
 #include <QDesktopServices>
 #include <QRegularExpression>
+#include <QDBusInterface>
+
+class Squawk;
+namespace Models {
+    class Roster;
+}
 
 namespace Shared {
     
     class Global {
         Q_DECLARE_TR_FUNCTIONS(Global)
-        
+        friend class ::Squawk;
     public:
         struct FileInfo {
             enum class Preview {
@@ -64,7 +70,9 @@ namespace Shared {
         };
         
         Global();
-        
+
+        static void notify(const QString& account, const Shared::Message& msg);
+
         static Global* getInstance();
         static QString getName(Availability av);
         static QString getName(ConnectionState cs);
@@ -122,6 +130,8 @@ namespace Shared {
         
     private:
         static Global* instance;
+        Models::Roster* rosterModel;
+        QDBusInterface dbus;
         
         std::map<QString, bool> pluginSupport;
         std::map<QString, FileInfo> fileCache;
diff --git a/ui/models/roster.cpp b/ui/models/roster.cpp
index 1355fe3..e5ada43 100644
--- a/ui/models/roster.cpp
+++ b/ui/models/roster.cpp
@@ -763,7 +763,7 @@ void Models::Roster::removeAccount(const QString& account)
     acc->deleteLater();
 }
 
-QString Models::Roster::getContactName(const QString& account, const QString& jid)
+QString Models::Roster::getContactName(const QString& account, const QString& jid) const
 {
     ElId id(account, jid);
     std::map<ElId, Contact*>::const_iterator cItr = contacts.find(id);
@@ -907,7 +907,7 @@ bool Models::Roster::groupHasContact(const QString& account, const QString& grou
     }
 }
 
-QString Models::Roster::getContactIconPath(const QString& account, const QString& jid, const QString& resource)
+QString Models::Roster::getContactIconPath(const QString& account, const QString& jid, const QString& resource) const
 {
     ElId id(account, jid);
     std::map<ElId, Contact*>::const_iterator cItr = contacts.find(id);
diff --git a/ui/models/roster.h b/ui/models/roster.h
index 08d5afc..28f4d30 100644
--- a/ui/models/roster.h
+++ b/ui/models/roster.h
@@ -65,7 +65,7 @@ public:
     void addRoomParticipant(const QString& account, const QString& jid, const QString& name, const QMap<QString, QVariant>& data);
     void changeRoomParticipant(const QString& account, const QString& jid, const QString& name, const QMap<QString, QVariant>& data);
     void removeRoomParticipant(const QString& account, const QString& jid, const QString& name);
-    QString getContactName(const QString& account, const QString& jid);
+    QString getContactName(const QString& account, const QString& jid) const;
     
     QVariant data ( const QModelIndex& index, int role ) const override;
     Qt::ItemFlags flags(const QModelIndex &index) const override;
@@ -77,7 +77,7 @@ public:
     
     std::deque<QString> groupList(const QString& account) const;
     bool groupHasContact(const QString& account, const QString& group, const QString& contactJID) const;
-    QString getContactIconPath(const QString& account, const QString& jid, const QString& resource);
+    QString getContactIconPath(const QString& account, const QString& jid, const QString& resource) const;
     Account* getAccount(const QString& name);
     QModelIndex getAccountIndex(const QString& name);
     QModelIndex getGroupIndex(const QString& account, const QString& name);
diff --git a/ui/squawk.cpp b/ui/squawk.cpp
index a0f16b2..a08f38b 100644
--- a/ui/squawk.cpp
+++ b/ui/squawk.cpp
@@ -28,10 +28,9 @@ Squawk::Squawk(QWidget *parent) :
     preferences(nullptr),
     about(nullptr),
     dialogueQueue(this),
-    rosterModel(),
+    rosterModel(*(Shared::Global::getInstance()->rosterModel)),
     conversations(),
     contextMenu(new QMenu()),
-    dbus("org.freedesktop.Notifications", "/org/freedesktop/Notifications", "org.freedesktop.Notifications", QDBusConnection::sessionBus()),
     vCards(),
     currentConversation(nullptr),
     restoreSelection(),
@@ -441,33 +440,7 @@ void Squawk::changeMessage(const QString& account, const QString& jid, const QSt
 
 void Squawk::notify(const QString& account, const Shared::Message& msg)
 {
-    QString name = QString(rosterModel.getContactName(account, msg.getPenPalJid()));
-    QString path = QString(rosterModel.getContactIconPath(account, msg.getPenPalJid(), msg.getPenPalResource()));
-    QVariantList args;
-    args << QString(QCoreApplication::applicationName());
-    args << QVariant(QVariant::UInt);   //TODO some normal id
-    if (path.size() > 0) {
-        args << path;
-    } else {
-        args << QString("mail-message");    //TODO should here better be unknown user icon?
-    }
-    if (msg.getType() == Shared::Message::groupChat) {
-        args << msg.getFromResource() + " from " + name;
-    } else {
-        args << name;
-    }
-    
-    QString body(msg.getBody());
-    QString oob(msg.getOutOfBandUrl());
-    if (body == oob) {
-        body = tr("Attached file");
-    }
-    
-    args << body;
-    args << QStringList();
-    args << QVariantMap();
-    args << 3000;
-    dbus.callWithArgumentList(QDBus::AutoDetect, "Notify", args);
+    Shared::Global::notify(account, msg);
 }
 
 void Squawk::onConversationMessage(const Shared::Message& msg)
diff --git a/ui/squawk.h b/ui/squawk.h
index 5a77f17..aa52153 100644
--- a/ui/squawk.h
+++ b/ui/squawk.h
@@ -22,7 +22,6 @@
 #include <QMainWindow>
 #include <QScopedPointer>
 #include <QCloseEvent>
-#include <QtDBus/QDBusInterface>
 #include <QSettings>
 #include <QInputDialog>
 
@@ -43,6 +42,7 @@
 #include "dialogqueue.h"
 
 #include "shared/shared.h"
+#include "shared/global.h"
 
 namespace Ui {
 class Squawk;
@@ -124,10 +124,9 @@ private:
     Settings* preferences;
     About* about;
     DialogQueue dialogueQueue;
-    Models::Roster rosterModel;
+    Models::Roster& rosterModel;
     Conversations conversations;
     QMenu* contextMenu;
-    QDBusInterface dbus;
     std::map<QString, VCard*> vCards;
     Conversation* currentConversation;
     QModelIndex restoreSelection;

From 83cb2201752e225b0bfb9ce5d85caafa3e8cf97e Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Tue, 19 Apr 2022 20:24:41 +0300
Subject: [PATCH 191/281] better notification sending, edited message now
 modifies notification (or sends), little structure change

---
 core/CMakeLists.txt                    |  7 +------
 core/networkaccess.h                   |  2 +-
 core/rosteritem.h                      |  2 +-
 core/storage/CMakeLists.txt            |  8 ++++++++
 core/{ => storage}/archive.cpp         |  0
 core/{ => storage}/archive.h           |  0
 core/{ => storage}/storage.cpp         |  0
 core/{ => storage}/storage.h           |  0
 core/{ => storage}/urlstorage.cpp      |  0
 core/{ => storage}/urlstorage.h        |  0
 shared/global.cpp                      | 14 ++++++++++----
 ui/models/roster.cpp                   |  8 ++++----
 ui/widgets/messageline/messagefeed.cpp |  6 ++++++
 13 files changed, 31 insertions(+), 16 deletions(-)
 create mode 100644 core/storage/CMakeLists.txt
 rename core/{ => storage}/archive.cpp (100%)
 rename core/{ => storage}/archive.h (100%)
 rename core/{ => storage}/storage.cpp (100%)
 rename core/{ => storage}/storage.h (100%)
 rename core/{ => storage}/urlstorage.cpp (100%)
 rename core/{ => storage}/urlstorage.h (100%)

diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt
index 8b6fa69..8f49b11 100644
--- a/core/CMakeLists.txt
+++ b/core/CMakeLists.txt
@@ -8,8 +8,6 @@ target_sources(squawk PRIVATE
   account.h
   adapterfunctions.cpp
   adapterfunctions.h
-  archive.cpp
-  archive.h
   conference.cpp
   conference.h
   contact.cpp
@@ -23,13 +21,10 @@ target_sources(squawk PRIVATE
   signalcatcher.h
   squawk.cpp
   squawk.h
-  storage.cpp
-  storage.h
-  urlstorage.cpp
-  urlstorage.h
   )
 
 target_include_directories(squawk PRIVATE ${LMDB_INCLUDE_DIRS})
 
 add_subdirectory(handlers)
+add_subdirectory(storage)
 add_subdirectory(passwordStorageEngines)
diff --git a/core/networkaccess.h b/core/networkaccess.h
index 0b7bb7d..6ddfa99 100644
--- a/core/networkaccess.h
+++ b/core/networkaccess.h
@@ -30,7 +30,7 @@
 
 #include <set>
 
-#include "urlstorage.h"
+#include "storage/urlstorage.h"
 #include "shared/pathcheck.h"
 
 namespace Core {
diff --git a/core/rosteritem.h b/core/rosteritem.h
index d422e3f..5f99017 100644
--- a/core/rosteritem.h
+++ b/core/rosteritem.h
@@ -34,7 +34,7 @@
 #include "shared/enums.h"
 #include "shared/message.h"
 #include "shared/vcard.h"
-#include "archive.h"
+#include "storage/archive.h"
 #include "adapterfunctions.h"
 
 namespace Core {
diff --git a/core/storage/CMakeLists.txt b/core/storage/CMakeLists.txt
new file mode 100644
index 0000000..4c263d5
--- /dev/null
+++ b/core/storage/CMakeLists.txt
@@ -0,0 +1,8 @@
+target_sources(squawk PRIVATE
+    archive.cpp
+    archive.h
+    storage.cpp
+    storage.h
+    urlstorage.cpp
+    urlstorage.h
+)
diff --git a/core/archive.cpp b/core/storage/archive.cpp
similarity index 100%
rename from core/archive.cpp
rename to core/storage/archive.cpp
diff --git a/core/archive.h b/core/storage/archive.h
similarity index 100%
rename from core/archive.h
rename to core/storage/archive.h
diff --git a/core/storage.cpp b/core/storage/storage.cpp
similarity index 100%
rename from core/storage.cpp
rename to core/storage/storage.cpp
diff --git a/core/storage.h b/core/storage/storage.h
similarity index 100%
rename from core/storage.h
rename to core/storage/storage.h
diff --git a/core/urlstorage.cpp b/core/storage/urlstorage.cpp
similarity index 100%
rename from core/urlstorage.cpp
rename to core/storage/urlstorage.cpp
diff --git a/core/urlstorage.h b/core/storage/urlstorage.h
similarity index 100%
rename from core/urlstorage.h
rename to core/storage/urlstorage.h
diff --git a/shared/global.cpp b/shared/global.cpp
index 14ae90d..be660bd 100644
--- a/shared/global.cpp
+++ b/shared/global.cpp
@@ -357,8 +357,9 @@ void Shared::Global::notify(const QString& account, const Shared::Message& msg)
     QString name = QString(instance->rosterModel->getContactName(account, msg.getPenPalJid()));
     QString path = QString(instance->rosterModel->getContactIconPath(account, msg.getPenPalJid(), msg.getPenPalResource()));
     QVariantList args;
-    args << QString(QCoreApplication::applicationName());
-    args << QVariant(QVariant::UInt);   //TODO some normal id
+    args << QString();
+
+    args << qHash(msg.getId());
     if (path.size() > 0) {
         args << path;
     } else {
@@ -378,8 +379,13 @@ void Shared::Global::notify(const QString& account, const Shared::Message& msg)
 
     args << body;
     args << QStringList();
-    args << QVariantMap();
-    args << 3000;
+    args << QVariantMap({
+        {"desktop-entry", QString(QCoreApplication::applicationName())},
+        {"category", QString("message")},
+       // {"sound-file", "/path/to/macaw/squawk"},
+        {"sound-name", QString("message-new-instant")}
+    });
+    args << -1;
     instance->dbus.callWithArgumentList(QDBus::AutoDetect, "Notify", args);
 }
 
diff --git a/ui/models/roster.cpp b/ui/models/roster.cpp
index e5ada43..b96ddda 100644
--- a/ui/models/roster.cpp
+++ b/ui/models/roster.cpp
@@ -801,10 +801,10 @@ void Models::Roster::addRoom(const QString& account, const QString jid, const QM
     }
     
     Room* room = new Room(acc, jid, data);
-    connect(room, &Contact::requestArchive, this, &Roster::onElementRequestArchive);
-    connect(room, &Contact::fileDownloadRequest, this, &Roster::fileDownloadRequest);
-    connect(room, &Contact::unnoticedMessage, this, &Roster::unnoticedMessage);
-    connect(room, &Contact::localPathInvalid, this, &Roster::localPathInvalid);
+    connect(room, &Room::requestArchive, this, &Roster::onElementRequestArchive);
+    connect(room, &Room::fileDownloadRequest, this, &Roster::fileDownloadRequest);
+    connect(room, &Room::unnoticedMessage, this, &Roster::unnoticedMessage);
+    connect(room, &Room::localPathInvalid, this, &Roster::localPathInvalid);
     rooms.insert(std::make_pair(id, room));
     acc->appendChild(room);
 }
diff --git a/ui/widgets/messageline/messagefeed.cpp b/ui/widgets/messageline/messagefeed.cpp
index 33fbdd4..521e981 100644
--- a/ui/widgets/messageline/messagefeed.cpp
+++ b/ui/widgets/messageline/messagefeed.cpp
@@ -163,6 +163,12 @@ void Models::MessageFeed::changeMessage(const QString& id, const QMap<QString, Q
         }
         
         emit dataChanged(index, index, cr);
+
+        if (observersAmount == 0 && !msg->getForwarded() && changeRoles.count(MessageRoles::Text) > 0) {
+            unreadMessages->insert(id);
+            emit unreadMessagesCountChanged();
+            emit unnoticedMessage(*msg);
+        }
     }
 }
 

From 721d3a1a89fd4daf4b0ab0643aa48f3155f23954 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Fri, 22 Apr 2022 18:26:18 +0300
Subject: [PATCH 192/281] refactoring: UI squawk now belongs to a new class, it
 enables me doing trayed mode, when main window is destroyed

---
 CMakeLists.txt               |   1 +
 core/CMakeLists.txt          |   1 -
 core/main.cpp                | 201 --------------
 main/CMakeLists.txt          |   7 +
 main/application.cpp         | 476 ++++++++++++++++++++++++++++++++
 main/application.h           | 111 ++++++++
 {ui => main}/dialogqueue.cpp |  31 ++-
 {ui => main}/dialogqueue.h   |  18 +-
 main/main.cpp                | 139 ++++++++++
 shared/global.cpp            |  40 ---
 shared/global.h              |  11 -
 ui/CMakeLists.txt            |   2 -
 ui/models/roster.cpp         |  46 +++-
 ui/models/roster.h           |  10 +-
 ui/squawk.cpp                | 509 ++++++-----------------------------
 ui/squawk.h                  |  74 ++---
 16 files changed, 908 insertions(+), 769 deletions(-)
 delete mode 100644 core/main.cpp
 create mode 100644 main/CMakeLists.txt
 create mode 100644 main/application.cpp
 create mode 100644 main/application.h
 rename {ui => main}/dialogqueue.cpp (87%)
 rename {ui => main}/dialogqueue.h (83%)
 create mode 100644 main/main.cpp

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 85aa98a..75011d8 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -148,6 +148,7 @@ if(CMAKE_COMPILER_IS_GNUCXX)
   target_compile_options(squawk PRIVATE ${COMPILE_OPTIONS})
 endif(CMAKE_COMPILER_IS_GNUCXX)
 
+add_subdirectory(main)
 add_subdirectory(core)
 add_subdirectory(external/simpleCrypt)
 add_subdirectory(packaging)
diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt
index 8f49b11..6c7a3b5 100644
--- a/core/CMakeLists.txt
+++ b/core/CMakeLists.txt
@@ -12,7 +12,6 @@ target_sources(squawk PRIVATE
   conference.h
   contact.cpp
   contact.h
-  main.cpp
   networkaccess.cpp
   networkaccess.h
   rosteritem.cpp
diff --git a/core/main.cpp b/core/main.cpp
deleted file mode 100644
index 4fbb1f7..0000000
--- a/core/main.cpp
+++ /dev/null
@@ -1,201 +0,0 @@
-/*
- * Squawk messenger. 
- * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
- *
- * 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 "../shared/global.h"
-#include "../shared/messageinfo.h"
-#include "../shared/pathcheck.h"
-#include "../ui/squawk.h"
-#include "signalcatcher.h"
-#include "squawk.h"
-
-#include <QLibraryInfo>
-#include <QSettings>
-#include <QStandardPaths>
-#include <QTranslator>
-#include <QtCore/QObject>
-#include <QtCore/QThread>
-#include <QtWidgets/QApplication>
-#include <QDir>
-
-int main(int argc, char *argv[])
-{
-    qRegisterMetaType<Shared::Message>("Shared::Message");
-    qRegisterMetaType<Shared::MessageInfo>("Shared::MessageInfo");
-    qRegisterMetaType<Shared::VCard>("Shared::VCard");
-    qRegisterMetaType<std::list<Shared::Message>>("std::list<Shared::Message>");
-    qRegisterMetaType<std::list<Shared::MessageInfo>>("std::list<Shared::MessageInfo>");
-    qRegisterMetaType<QSet<QString>>("QSet<QString>");
-    qRegisterMetaType<Shared::ConnectionState>("Shared::ConnectionState");
-    qRegisterMetaType<Shared::Availability>("Shared::Availability");
-    
-    QApplication app(argc, argv);
-    SignalCatcher sc(&app);
-
-    QApplication::setApplicationName("squawk");
-    QApplication::setOrganizationName("macaw.me");
-    QApplication::setApplicationDisplayName("Squawk");
-    QApplication::setApplicationVersion("0.2.2");
-
-    QTranslator qtTranslator;
-    qtTranslator.load("qt_" + QLocale::system().name(), QLibraryInfo::location(QLibraryInfo::TranslationsPath));
-    app.installTranslator(&qtTranslator);
-
-    QTranslator myappTranslator;
-    QStringList shares = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation);
-    bool found = false;
-    for (QString share : shares) {
-        found = myappTranslator.load(QLocale(), QLatin1String("squawk"), ".", share + "/l10n");
-        if (found) {
-            break;
-        }
-    }
-    if (!found) {
-        myappTranslator.load(QLocale(), QLatin1String("squawk"), ".", QCoreApplication::applicationDirPath());
-    }
-    
-    app.installTranslator(&myappTranslator);
-    
-    QIcon icon;
-    icon.addFile(":images/logo.svg", QSize(16, 16));
-    icon.addFile(":images/logo.svg", QSize(24, 24));
-    icon.addFile(":images/logo.svg", QSize(32, 32));
-    icon.addFile(":images/logo.svg", QSize(48, 48));
-    icon.addFile(":images/logo.svg", QSize(64, 64));
-    icon.addFile(":images/logo.svg", QSize(96, 96));
-    icon.addFile(":images/logo.svg", QSize(128, 128));
-    icon.addFile(":images/logo.svg", QSize(256, 256));
-    icon.addFile(":images/logo.svg", QSize(512, 512));
-    QApplication::setWindowIcon(icon);
-    
-    new Shared::Global();        //translates enums
-
-    QSettings settings;
-    QVariant vs = settings.value("style");
-    if (vs.isValid()) {
-        QString style = vs.toString().toLower();
-        if (style != "system") {
-            Shared::Global::setStyle(style);
-        }
-    }
-    if (Shared::Global::supported("colorSchemeTools")) {
-        QVariant vt = settings.value("theme");
-        if (vt.isValid()) {
-            QString theme = vt.toString();
-            if (theme.toLower() != "system") {
-                Shared::Global::setTheme(theme);
-            }
-        }
-    }
-    QString path = Shared::downloadsPathCheck();
-    if (path.size() > 0) {
-        settings.setValue("downloadsPath", path);
-    } else {
-        qDebug() << "couldn't initialize directory for downloads, quitting";
-        return -1;
-    }
-
-    
-    Squawk w;
-    w.show();
-    
-    Core::Squawk* squawk = new Core::Squawk();
-    QThread* coreThread = new QThread();
-    squawk->moveToThread(coreThread);
-    
-    QObject::connect(&sc, &SignalCatcher::interrupt, &app, &QApplication::closeAllWindows);
-
-    QObject::connect(coreThread, &QThread::started, squawk, &Core::Squawk::start);
-    QObject::connect(&app, &QApplication::lastWindowClosed, squawk, &Core::Squawk::stop);
-    QObject::connect(&app, &QApplication::lastWindowClosed, &w, &Squawk::writeSettings);
-    //QObject::connect(&app, &QApplication::aboutToQuit, &w, &QMainWindow::close);
-    QObject::connect(squawk, &Core::Squawk::quit, squawk, &Core::Squawk::deleteLater);
-    QObject::connect(squawk, &Core::Squawk::quit, coreThread, &QThread::quit, Qt::QueuedConnection);
-    QObject::connect(coreThread, &QThread::finished, &app, &QApplication::quit, Qt::QueuedConnection);
-    
-    QObject::connect(&w, &Squawk::newAccountRequest, squawk, &Core::Squawk::newAccountRequest);
-    QObject::connect(&w, &Squawk::modifyAccountRequest, squawk, &Core::Squawk::modifyAccountRequest);
-    QObject::connect(&w, &Squawk::removeAccountRequest, squawk, &Core::Squawk::removeAccountRequest);
-    QObject::connect(&w, &Squawk::connectAccount, squawk, &Core::Squawk::connectAccount);
-    QObject::connect(&w, &Squawk::disconnectAccount, squawk, &Core::Squawk::disconnectAccount);
-    QObject::connect(&w, &Squawk::changeState, squawk, &Core::Squawk::changeState);
-    QObject::connect(&w, &Squawk::sendMessage, squawk,&Core::Squawk::sendMessage);
-    QObject::connect(&w, &Squawk::replaceMessage, squawk,&Core::Squawk::replaceMessage);
-    QObject::connect(&w, &Squawk::resendMessage, squawk,&Core::Squawk::resendMessage);
-    QObject::connect(&w, &Squawk::requestArchive, squawk, &Core::Squawk::requestArchive);
-    QObject::connect(&w, &Squawk::subscribeContact, squawk, &Core::Squawk::subscribeContact);
-    QObject::connect(&w, &Squawk::unsubscribeContact, squawk, &Core::Squawk::unsubscribeContact);
-    QObject::connect(&w, &Squawk::addContactRequest, squawk, &Core::Squawk::addContactRequest);
-    QObject::connect(&w, &Squawk::removeContactRequest, squawk, &Core::Squawk::removeContactRequest);
-    QObject::connect(&w, &Squawk::setRoomJoined, squawk, &Core::Squawk::setRoomJoined);
-    QObject::connect(&w, &Squawk::setRoomAutoJoin, squawk, &Core::Squawk::setRoomAutoJoin);
-    QObject::connect(&w, &Squawk::removeRoomRequest, squawk, &Core::Squawk::removeRoomRequest);
-    QObject::connect(&w, &Squawk::addRoomRequest, squawk, &Core::Squawk::addRoomRequest);
-    QObject::connect(&w, &Squawk::fileDownloadRequest, squawk, &Core::Squawk::fileDownloadRequest);
-    QObject::connect(&w, &Squawk::addContactToGroupRequest, squawk, &Core::Squawk::addContactToGroupRequest);
-    QObject::connect(&w, &Squawk::removeContactFromGroupRequest, squawk, &Core::Squawk::removeContactFromGroupRequest);
-    QObject::connect(&w, &Squawk::renameContactRequest, squawk, &Core::Squawk::renameContactRequest);
-    QObject::connect(&w, &Squawk::requestVCard, squawk, &Core::Squawk::requestVCard);
-    QObject::connect(&w, &Squawk::uploadVCard, squawk, &Core::Squawk::uploadVCard);
-    QObject::connect(&w, &Squawk::responsePassword, squawk, &Core::Squawk::responsePassword);
-    QObject::connect(&w, &Squawk::localPathInvalid, squawk, &Core::Squawk::onLocalPathInvalid);
-    QObject::connect(&w, &Squawk::changeDownloadsPath, squawk, &Core::Squawk::changeDownloadsPath);
-    
-    QObject::connect(squawk, &Core::Squawk::newAccount, &w, &Squawk::newAccount);
-    QObject::connect(squawk, &Core::Squawk::addContact, &w, &Squawk::addContact);
-    QObject::connect(squawk, &Core::Squawk::changeAccount, &w, &Squawk::changeAccount);
-    QObject::connect(squawk, &Core::Squawk::removeAccount, &w, &Squawk::removeAccount);
-    QObject::connect(squawk, &Core::Squawk::addGroup, &w, &Squawk::addGroup);
-    QObject::connect(squawk, &Core::Squawk::removeGroup, &w, &Squawk::removeGroup);
-    QObject::connect(squawk, qOverload<const QString&, const QString&>(&Core::Squawk::removeContact), 
-                     &w, qOverload<const QString&, const QString&>(&Squawk::removeContact));
-    QObject::connect(squawk, qOverload<const QString&, const QString&, const QString&>(&Core::Squawk::removeContact), 
-                     &w, qOverload<const QString&, const QString&, const QString&>(&Squawk::removeContact));
-    QObject::connect(squawk, &Core::Squawk::changeContact, &w, &Squawk::changeContact);
-    QObject::connect(squawk, &Core::Squawk::addPresence, &w, &Squawk::addPresence);
-    QObject::connect(squawk, &Core::Squawk::removePresence, &w, &Squawk::removePresence);
-    QObject::connect(squawk, &Core::Squawk::stateChanged, &w, &Squawk::stateChanged);
-    QObject::connect(squawk, &Core::Squawk::accountMessage, &w, &Squawk::accountMessage);
-    QObject::connect(squawk, &Core::Squawk::changeMessage, &w, &Squawk::changeMessage);
-    QObject::connect(squawk, &Core::Squawk::responseArchive, &w, &Squawk::responseArchive);
-    QObject::connect(squawk, &Core::Squawk::addRoom, &w, &Squawk::addRoom);
-    QObject::connect(squawk, &Core::Squawk::changeRoom, &w, &Squawk::changeRoom);
-    QObject::connect(squawk, &Core::Squawk::removeRoom, &w, &Squawk::removeRoom);
-    QObject::connect(squawk, &Core::Squawk::addRoomParticipant, &w, &Squawk::addRoomParticipant);
-    QObject::connect(squawk, &Core::Squawk::changeRoomParticipant, &w, &Squawk::changeRoomParticipant);
-    QObject::connect(squawk, &Core::Squawk::removeRoomParticipant, &w, &Squawk::removeRoomParticipant);
-    QObject::connect(squawk, &Core::Squawk::fileDownloadComplete, &w, &Squawk::fileDownloadComplete);
-    QObject::connect(squawk, &Core::Squawk::fileUploadComplete, &w, &Squawk::fileUploadComplete);
-    QObject::connect(squawk, &Core::Squawk::fileProgress, &w, &Squawk::fileProgress);
-    QObject::connect(squawk, &Core::Squawk::fileError, &w, &Squawk::fileError);
-    QObject::connect(squawk, &Core::Squawk::responseVCard, &w, &Squawk::responseVCard);
-    QObject::connect(squawk, &Core::Squawk::requestPassword, &w, &Squawk::requestPassword);
-    QObject::connect(squawk, &Core::Squawk::ready, &w, &Squawk::readSettings);
-    
-    coreThread->start();
-    int result = app.exec();
-
-    if (coreThread->isRunning()) {
-        //coreThread->wait();
-        //todo if I uncomment that, the app will not quit if it has reconnected at least once
-        //it feels like a symptom of something badly desinged in the core thread
-        //need to investigate;
-    }
-    
-    return result;
-}
-
diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt
new file mode 100644
index 0000000..3c23932
--- /dev/null
+++ b/main/CMakeLists.txt
@@ -0,0 +1,7 @@
+target_sources(squawk PRIVATE
+    main.cpp
+    application.cpp
+    application.h
+    dialogqueue.cpp
+    dialogqueue.h
+)
diff --git a/main/application.cpp b/main/application.cpp
new file mode 100644
index 0000000..f6ffe07
--- /dev/null
+++ b/main/application.cpp
@@ -0,0 +1,476 @@
+// Squawk messenger. 
+// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+// 
+// 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 "application.h"
+
+Application::Application(Core::Squawk* p_core):
+    QObject(),
+    availability(Shared::Availability::offline),
+    core(p_core),
+    squawk(nullptr),
+    notifications("org.freedesktop.Notifications", "/org/freedesktop/Notifications", "org.freedesktop.Notifications", QDBusConnection::sessionBus()),
+    roster(),
+    conversations(),
+    dialogueQueue(roster),
+    nowQuitting(false),
+    destroyingSquawk(false)
+{
+    connect(&roster, &Models::Roster::unnoticedMessage, this, &Application::notify);
+
+
+    //connecting myself to the backed
+    connect(this, &Application::changeState, core, &Core::Squawk::changeState);
+    connect(this, &Application::setRoomJoined, core, &Core::Squawk::setRoomJoined);
+    connect(this, &Application::setRoomAutoJoin, core, &Core::Squawk::setRoomAutoJoin);
+    connect(this, &Application::subscribeContact, core, &Core::Squawk::subscribeContact);
+    connect(this, &Application::unsubscribeContact, core, &Core::Squawk::unsubscribeContact);
+    connect(this, &Application::replaceMessage, core, &Core::Squawk::replaceMessage);
+    connect(this, &Application::sendMessage, core, &Core::Squawk::sendMessage);
+    connect(this, &Application::resendMessage, core, &Core::Squawk::resendMessage);
+    connect(&roster, &Models::Roster::requestArchive,
+            std::bind(&Core::Squawk::requestArchive, core, std::placeholders::_1, std::placeholders::_2, 20, std::placeholders::_3));
+
+    connect(&dialogueQueue, &DialogQueue::modifyAccountRequest, core, &Core::Squawk::modifyAccountRequest);
+    connect(&dialogueQueue, &DialogQueue::responsePassword, core, &Core::Squawk::responsePassword);
+    connect(&dialogueQueue, &DialogQueue::disconnectAccount, core, &Core::Squawk::disconnectAccount);
+
+    connect(&roster, &Models::Roster::fileDownloadRequest, core, &Core::Squawk::fileDownloadRequest);
+    connect(&roster, &Models::Roster::localPathInvalid, core, &Core::Squawk::onLocalPathInvalid);
+
+
+    //coonecting backend to myself
+    connect(core, &Core::Squawk::stateChanged, this, &Application::stateChanged);
+
+    connect(core, &Core::Squawk::accountMessage, &roster, &Models::Roster::addMessage);
+    connect(core, &Core::Squawk::responseArchive, &roster, &Models::Roster::responseArchive);
+    connect(core, &Core::Squawk::changeMessage, &roster, &Models::Roster::changeMessage);
+
+    connect(core, &Core::Squawk::newAccount, &roster, &Models::Roster::addAccount);
+    connect(core, &Core::Squawk::changeAccount, this, &Application::changeAccount);
+    connect(core, &Core::Squawk::removeAccount, this, &Application::removeAccount);
+
+    connect(core, &Core::Squawk::addContact, this, &Application::addContact);
+    connect(core, &Core::Squawk::addGroup, this, &Application::addGroup);
+    connect(core, &Core::Squawk::removeGroup, &roster, &Models::Roster::removeGroup);
+    connect(core, qOverload<const QString&, const QString&>(&Core::Squawk::removeContact),
+            &roster, qOverload<const QString&, const QString&>(&Models::Roster::removeContact));
+    connect(core, qOverload<const QString&, const QString&, const QString&>(&Core::Squawk::removeContact),
+            &roster, qOverload<const QString&, const QString&, const QString&>(&Models::Roster::removeContact));
+    connect(core, &Core::Squawk::changeContact, &roster, &Models::Roster::changeContact);
+    connect(core, &Core::Squawk::addPresence, &roster, &Models::Roster::addPresence);
+    connect(core, &Core::Squawk::removePresence, &roster, &Models::Roster::removePresence);
+
+    connect(core, &Core::Squawk::addRoom, &roster, &Models::Roster::addRoom);
+    connect(core, &Core::Squawk::changeRoom, &roster, &Models::Roster::changeRoom);
+    connect(core, &Core::Squawk::removeRoom, &roster, &Models::Roster::removeRoom);
+    connect(core, &Core::Squawk::addRoomParticipant, &roster, &Models::Roster::addRoomParticipant);
+    connect(core, &Core::Squawk::changeRoomParticipant, &roster, &Models::Roster::changeRoomParticipant);
+    connect(core, &Core::Squawk::removeRoomParticipant, &roster, &Models::Roster::removeRoomParticipant);
+
+
+    connect(core, &Core::Squawk::fileDownloadComplete, std::bind(&Models::Roster::fileComplete, &roster, std::placeholders::_1, false));
+    connect(core, &Core::Squawk::fileUploadComplete, std::bind(&Models::Roster::fileComplete, &roster, std::placeholders::_1, true));
+    connect(core, &Core::Squawk::fileProgress, &roster, &Models::Roster::fileProgress);
+    connect(core, &Core::Squawk::fileError, &roster, &Models::Roster::fileError);
+
+    connect(core, &Core::Squawk::requestPassword, this, &Application::requestPassword);
+    connect(core, &Core::Squawk::ready, this, &Application::readSettings);
+
+}
+
+Application::~Application() {}
+
+void Application::quit()
+{
+    if (!nowQuitting) {
+        nowQuitting = true;
+        emit quitting();
+
+        writeSettings();
+        for (Conversations::const_iterator itr = conversations.begin(), end = conversations.end(); itr != end; ++itr) {
+            disconnect(itr->second, &Conversation::destroyed, this, &Application::onConversationClosed);
+            itr->second->close();
+        }
+        conversations.clear();
+        dialogueQueue.quit();
+
+        if (squawk != nullptr) {
+            squawk->close();
+        }
+        if (!destroyingSquawk) {
+            checkForTheLastWindow();
+        }
+    }
+}
+
+void Application::checkForTheLastWindow()
+{
+    if (QApplication::topLevelWidgets().size() > 0) {
+        emit readyToQuit();
+    } else {
+        connect(qApp, &QApplication::lastWindowClosed, this, &Application::readyToQuit);
+    }
+}
+
+void Application::createMainWindow()
+{
+    if (squawk == nullptr) {
+        squawk = new Squawk(roster);
+
+        connect(squawk, &Squawk::notify, this, &Application::notify);
+        connect(squawk, &Squawk::changeSubscription, this, &Application::changeSubscription);
+        connect(squawk, &Squawk::openedConversation, this, &Application::onSquawkOpenedConversation);
+        connect(squawk, &Squawk::openConversation, this, &Application::openConversation);
+        connect(squawk, &Squawk::changeState, this, &Application::setState);
+        connect(squawk, &Squawk::closing, this, &Application::onSquawkClosing);
+
+        connect(squawk, &Squawk::modifyAccountRequest, core, &Core::Squawk::modifyAccountRequest);
+        connect(squawk, &Squawk::newAccountRequest, core, &Core::Squawk::newAccountRequest);
+        connect(squawk, &Squawk::removeAccountRequest, core, &Core::Squawk::removeAccountRequest);
+        connect(squawk, &Squawk::connectAccount, core, &Core::Squawk::connectAccount);
+        connect(squawk, &Squawk::disconnectAccount, core, &Core::Squawk::disconnectAccount);
+
+        connect(squawk, &Squawk::addContactRequest, core, &Core::Squawk::addContactRequest);
+        connect(squawk, &Squawk::removeContactRequest, core, &Core::Squawk::removeContactRequest);
+        connect(squawk, &Squawk::removeRoomRequest, core, &Core::Squawk::removeRoomRequest);
+        connect(squawk, &Squawk::addRoomRequest, core, &Core::Squawk::addRoomRequest);
+        connect(squawk, &Squawk::addContactToGroupRequest, core, &Core::Squawk::addContactToGroupRequest);
+        connect(squawk, &Squawk::removeContactFromGroupRequest, core, &Core::Squawk::removeContactFromGroupRequest);
+        connect(squawk, &Squawk::renameContactRequest, core, &Core::Squawk::renameContactRequest);
+        connect(squawk, &Squawk::requestVCard, core, &Core::Squawk::requestVCard);
+        connect(squawk, &Squawk::uploadVCard, core, &Core::Squawk::uploadVCard);
+        connect(squawk, &Squawk::changeDownloadsPath, core, &Core::Squawk::changeDownloadsPath);
+
+        connect(core, &Core::Squawk::responseVCard, squawk, &Squawk::responseVCard);
+
+        dialogueQueue.setParentWidnow(squawk);
+        squawk->stateChanged(availability);
+        squawk->show();
+    }
+}
+
+void Application::onSquawkClosing()
+{
+    dialogueQueue.setParentWidnow(nullptr);
+
+    disconnect(core, &Core::Squawk::responseVCard, squawk, &Squawk::responseVCard);
+
+    destroyingSquawk = true;
+    squawk->deleteLater();
+    squawk = nullptr;
+
+    //for now
+    quit();
+}
+
+void Application::onSquawkDestroyed() {
+    destroyingSquawk = false;
+    if (nowQuitting) {
+        checkForTheLastWindow();
+    }
+}
+
+
+void Application::notify(const QString& account, const Shared::Message& msg)
+{
+    QString name = QString(roster.getContactName(account, msg.getPenPalJid()));
+    QString path = QString(roster.getContactIconPath(account, msg.getPenPalJid(), msg.getPenPalResource()));
+    QVariantList args;
+    args << QString();
+
+    args << qHash(msg.getId());
+    if (path.size() > 0) {
+        args << path;
+    } else {
+        args << QString("mail-message");    //TODO should here better be unknown user icon?
+    }
+    if (msg.getType() == Shared::Message::groupChat) {
+        args << msg.getFromResource() + " from " + name;
+    } else {
+        args << name;
+    }
+
+    QString body(msg.getBody());
+    QString oob(msg.getOutOfBandUrl());
+    if (body == oob) {
+        body = tr("Attached file");
+    }
+
+    args << body;
+    args << QStringList();
+    args << QVariantMap({
+        {"desktop-entry", QString(QCoreApplication::applicationName())},
+        {"category", QString("message")},
+       // {"sound-file", "/path/to/macaw/squawk"},
+        {"sound-name", QString("message-new-instant")}
+    });
+    args << -1;
+    notifications.callWithArgumentList(QDBus::AutoDetect, "Notify", args);
+
+    if (squawk != nullptr) {
+        QApplication::alert(squawk);
+    }
+}
+
+void Application::setState(Shared::Availability p_availability)
+{
+    if (availability != p_availability) {
+        availability = p_availability;
+        emit changeState(availability);
+    }
+}
+
+void Application::stateChanged(Shared::Availability state)
+{
+    availability = state;
+    if (squawk != nullptr) {
+        squawk->stateChanged(state);
+    }
+}
+
+void Application::readSettings()
+{
+    QSettings settings;
+    settings.beginGroup("ui");
+    int avail;
+    if (settings.contains("availability")) {
+        avail = settings.value("availability").toInt();
+    } else {
+        avail = static_cast<int>(Shared::Availability::online);
+    }
+    settings.endGroup();
+
+    setState(Shared::Global::fromInt<Shared::Availability>(avail));
+    createMainWindow();
+}
+
+void Application::writeSettings()
+{
+    QSettings settings;
+    settings.setValue("availability", static_cast<int>(availability));
+}
+
+void Application::requestPassword(const QString& account, bool authenticationError) {
+    if (authenticationError) {
+        dialogueQueue.addAction(account, DialogQueue::askCredentials);
+    } else {
+        dialogueQueue.addAction(account, DialogQueue::askPassword);
+    }
+
+}
+void Application::onConversationClosed()
+{
+    Conversation* conv = static_cast<Conversation*>(sender());
+    Models::Roster::ElId id(conv->getAccount(), conv->getJid());
+    Conversations::const_iterator itr = conversations.find(id);
+    if (itr != conversations.end()) {
+        conversations.erase(itr);
+    }
+    if (conv->isMuc) {
+        Room* room = static_cast<Room*>(conv);
+        if (!room->autoJoined()) {
+            emit setRoomJoined(id.account, id.name, false);
+        }
+    }
+}
+
+void Application::changeSubscription(const Models::Roster::ElId& id, bool subscribe)
+{
+    Models::Item::Type type = roster.getContactType(id);
+
+    switch (type) {
+        case Models::Item::contact:
+            if (subscribe) {
+                emit subscribeContact(id.account, id.name, "");
+            } else {
+                emit unsubscribeContact(id.account, id.name, "");
+            }
+            break;
+        case Models::Item::room:
+            setRoomAutoJoin(id.account, id.name, subscribe);
+            if (!isConverstationOpened(id)) {
+                emit setRoomJoined(id.account, id.name, subscribe);
+            }
+            break;
+        default:
+            break;
+    }
+}
+
+void Application::subscribeConversation(Conversation* conv)
+{
+    connect(conv, &Conversation::destroyed, this, &Application::onConversationClosed);
+    connect(conv, &Conversation::sendMessage, this, &Application::onConversationMessage);
+    connect(conv, &Conversation::replaceMessage, this, &Application::onConversationReplaceMessage);
+    connect(conv, &Conversation::resendMessage, this, &Application::onConversationResend);
+    connect(conv, &Conversation::notifyableMessage, this, &Application::notify);
+}
+
+void Application::openConversation(const Models::Roster::ElId& id, const QString& resource)
+{
+    Conversations::const_iterator itr = conversations.find(id);
+    Models::Account* acc = roster.getAccount(id.account);
+    Conversation* conv = nullptr;
+    bool created = false;
+    if (itr != conversations.end()) {
+        conv = itr->second;
+    } else {
+        Models::Element* el = roster.getElement(id);
+        if (el != NULL) {
+            if (el->type == Models::Item::room) {
+                created = true;
+                Models::Room* room = static_cast<Models::Room*>(el);
+                conv = new Room(acc, room);
+                if (!room->getJoined()) {
+                    emit setRoomJoined(id.account, id.name, true);
+                }
+            } else if (el->type == Models::Item::contact) {
+                created = true;
+                conv = new Chat(acc, static_cast<Models::Contact*>(el));
+            }
+        }
+    }
+
+    if (conv != nullptr) {
+        if (created) {
+            conv->setAttribute(Qt::WA_DeleteOnClose);
+            subscribeConversation(conv);
+            conversations.insert(std::make_pair(id, conv));
+        }
+
+        conv->show();
+        conv->raise();
+        conv->activateWindow();
+
+        if (resource.size() > 0) {
+            conv->setPalResource(resource);
+        }
+    }
+}
+
+void Application::onConversationMessage(const Shared::Message& msg)
+{
+    Conversation* conv = static_cast<Conversation*>(sender());
+    QString acc = conv->getAccount();
+
+    roster.addMessage(acc, msg);
+    emit sendMessage(acc, msg);
+}
+
+void Application::onConversationReplaceMessage(const QString& originalId, const Shared::Message& msg)
+{
+    Conversation* conv = static_cast<Conversation*>(sender());
+    QString acc = conv->getAccount();
+
+    roster.changeMessage(acc, msg.getPenPalJid(), originalId, {
+        {"state", static_cast<uint>(Shared::Message::State::pending)}
+    });
+    emit replaceMessage(acc, originalId, msg);
+}
+
+void Application::onConversationResend(const QString& id)
+{
+    Conversation* conv = static_cast<Conversation*>(sender());
+    QString acc = conv->getAccount();
+    QString jid = conv->getJid();
+
+    emit resendMessage(acc, jid, id);
+}
+
+void Application::onSquawkOpenedConversation() {
+    subscribeConversation(squawk->currentConversation);
+    Models::Roster::ElId id = squawk->currentConversationId();
+
+    const Models::Element* el = roster.getElementConst(id);
+    if (el != NULL && el->isRoom() && !static_cast<const Models::Room*>(el)->getJoined()) {
+        emit setRoomJoined(id.account, id.name, true);
+    }
+}
+
+void Application::removeAccount(const QString& account)
+{
+    Conversations::const_iterator itr = conversations.begin();
+    while (itr != conversations.end()) {
+        if (itr->first.account == account) {
+            Conversations::const_iterator lItr = itr;
+            ++itr;
+            Conversation* conv = lItr->second;
+            disconnect(conv, &Conversation::destroyed, this, &Application::onConversationClosed);
+            conv->close();
+            conversations.erase(lItr);
+        } else {
+            ++itr;
+        }
+    }
+
+    if (squawk != nullptr && squawk->currentConversationId().account == account) {
+        squawk->closeCurrentConversation();
+    }
+
+    roster.removeAccount(account);
+}
+
+void Application::changeAccount(const QString& account, const QMap<QString, QVariant>& data)
+{
+    for (QMap<QString, QVariant>::const_iterator itr = data.begin(), end = data.end(); itr != end; ++itr) {
+        QString attr = itr.key();
+        roster.updateAccount(account, attr, *itr);
+    }
+}
+
+void Application::addContact(const QString& account, const QString& jid, const QString& group, const QMap<QString, QVariant>& data)
+{
+    roster.addContact(account, jid, group, data);
+
+    if (squawk != nullptr) {
+        QSettings settings;
+        settings.beginGroup("ui");
+        settings.beginGroup("roster");
+        settings.beginGroup(account);
+        if (settings.value("expanded", false).toBool()) {
+            QModelIndex ind = roster.getAccountIndex(account);
+            squawk->expand(ind);
+        }
+        settings.endGroup();
+        settings.endGroup();
+        settings.endGroup();
+    }
+}
+
+void Application::addGroup(const QString& account, const QString& name)
+{
+    roster.addGroup(account, name);
+
+    if (squawk != nullptr) {
+        QSettings settings;
+        settings.beginGroup("ui");
+        settings.beginGroup("roster");
+        settings.beginGroup(account);
+        if (settings.value("expanded", false).toBool()) {
+            QModelIndex ind = roster.getAccountIndex(account);
+            squawk->expand(ind);
+            if (settings.value(name + "/expanded", false).toBool()) {
+                squawk->expand(roster.getGroupIndex(account, name));
+            }
+        }
+        settings.endGroup();
+        settings.endGroup();
+        settings.endGroup();
+    }
+}
+
+bool Application::isConverstationOpened(const Models::Roster::ElId& id) const {
+    return (conversations.count(id) > 0) || (squawk != nullptr && squawk->currentConversationId() == id);}
diff --git a/main/application.h b/main/application.h
new file mode 100644
index 0000000..15adce7
--- /dev/null
+++ b/main/application.h
@@ -0,0 +1,111 @@
+// Squawk messenger. 
+// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+// 
+// 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/>.
+
+#ifndef APPLICATION_H
+#define APPLICATION_H
+
+#include <map>
+
+#include <QObject>
+#include <QDBusInterface>
+
+#include "dialogqueue.h"
+#include "core/squawk.h"
+
+#include "ui/squawk.h"
+#include "ui/models/roster.h"
+#include "ui/widgets/conversation.h"
+
+#include "shared/message.h"
+#include "shared/enums.h"
+
+/**
+ * @todo write docs
+ */
+class Application : public QObject
+{
+    Q_OBJECT
+public:
+    Application(Core::Squawk* core);
+    ~Application();
+
+    bool isConverstationOpened(const Models::Roster::ElId& id) const;
+
+signals:
+    void sendMessage(const QString& account, const Shared::Message& data);
+    void replaceMessage(const QString& account, const QString& originalId, const Shared::Message& data);
+    void resendMessage(const QString& account, const QString& jid, const QString& id);
+
+    void changeState(Shared::Availability state);
+
+    void setRoomJoined(const QString& account, const QString& jid, bool joined);
+    void setRoomAutoJoin(const QString& account, const QString& jid, bool joined);
+    void subscribeContact(const QString& account, const QString& jid, const QString& reason);
+    void unsubscribeContact(const QString& account, const QString& jid, const QString& reason);
+
+    void quitting();
+    void readyToQuit();
+
+public slots:
+    void readSettings();
+    void quit();
+
+protected slots:
+    void notify(const QString& account, const Shared::Message& msg);
+    void setState(Shared::Availability availability);
+
+    void changeAccount(const QString& account, const QMap<QString, QVariant>& data);
+    void removeAccount(const QString& account);
+    void openConversation(const Models::Roster::ElId& id, const QString& resource = "");
+
+    void addGroup(const QString& account, const QString& name);
+    void addContact(const QString& account, const QString& jid, const QString& group, const QMap<QString, QVariant>& data);
+
+    void requestPassword(const QString& account, bool authenticationError);
+
+    void writeSettings();
+
+private slots:
+    void onConversationClosed();
+    void changeSubscription(const Models::Roster::ElId& id, bool subscribe);
+    void onSquawkOpenedConversation();
+    void onConversationMessage(const Shared::Message& msg);
+    void onConversationReplaceMessage(const QString& originalId, const Shared::Message& msg);
+    void onConversationResend(const QString& id);
+    void stateChanged(Shared::Availability state);
+    void onSquawkClosing();
+    void onSquawkDestroyed();
+
+private:
+    void createMainWindow();
+    void subscribeConversation(Conversation* conv);
+    void checkForTheLastWindow();
+
+private:
+    typedef std::map<Models::Roster::ElId, Conversation*> Conversations;
+
+    Shared::Availability availability;
+    Core::Squawk* core;
+    Squawk* squawk;
+    QDBusInterface notifications;
+    Models::Roster roster;
+    Conversations conversations;
+    DialogQueue dialogueQueue;
+    bool nowQuitting;
+    bool destroyingSquawk;
+};
+
+#endif // APPLICATION_H
diff --git a/ui/dialogqueue.cpp b/main/dialogqueue.cpp
similarity index 87%
rename from ui/dialogqueue.cpp
rename to main/dialogqueue.cpp
index 02f8688..d7b4570 100644
--- a/ui/dialogqueue.cpp
+++ b/main/dialogqueue.cpp
@@ -15,10 +15,9 @@
 // along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 #include "dialogqueue.h"
-#include "squawk.h"
 #include <QDebug>
 
-DialogQueue::DialogQueue(Squawk* p_squawk):
+DialogQueue::DialogQueue(const Models::Roster& p_roster):
     QObject(),
     currentSource(),
     currentAction(none),
@@ -26,7 +25,8 @@ DialogQueue::DialogQueue(Squawk* p_squawk):
     collection(queue.get<0>()),
     sequence(queue.get<1>()),
     prompt(nullptr),
-    squawk(p_squawk)
+    parent(nullptr),
+    roster(p_roster)
 {
 }
 
@@ -34,6 +34,19 @@ DialogQueue::~DialogQueue()
 {
 }
 
+void DialogQueue::quit()
+{
+    queue.clear();
+    if (currentAction != none) {
+        actionDone();
+    }
+}
+
+void DialogQueue::setParentWidnow(QMainWindow* p_parent)
+{
+    parent = p_parent;
+}
+
 bool DialogQueue::addAction(const QString& source, DialogQueue::Action action)
 {
     if (action == none) {
@@ -77,7 +90,7 @@ void DialogQueue::performNextAction()
             actionDone();
             break;
         case askPassword: {
-            QInputDialog* dialog = new QInputDialog(squawk);
+            QInputDialog* dialog = new QInputDialog(parent);
             prompt = dialog;
             connect(dialog, &QDialog::accepted, this, &DialogQueue::onPropmtAccepted);
             connect(dialog, &QDialog::rejected, this, &DialogQueue::onPropmtRejected);
@@ -90,11 +103,11 @@ void DialogQueue::performNextAction()
         }
             break;
         case askCredentials: {
-            CredentialsPrompt* dialog = new CredentialsPrompt(squawk);
+            CredentialsPrompt* dialog = new CredentialsPrompt(parent);
             prompt = dialog;
             connect(dialog, &QDialog::accepted, this, &DialogQueue::onPropmtAccepted);
             connect(dialog, &QDialog::rejected, this, &DialogQueue::onPropmtRejected);
-            Models::Account* acc = squawk->rosterModel.getAccount(currentSource);
+            const Models::Account* acc = roster.getAccountConst(currentSource);
             dialog->setAccount(currentSource);
             dialog->setLogin(acc->getLogin());
             dialog->setPassword(acc->getPassword());
@@ -111,12 +124,12 @@ void DialogQueue::onPropmtAccepted()
             break;
         case askPassword: {
             QInputDialog* dialog = static_cast<QInputDialog*>(prompt);
-            emit squawk->responsePassword(currentSource, dialog->textValue());
+            emit responsePassword(currentSource, dialog->textValue());
         }
             break;
         case askCredentials: {
             CredentialsPrompt* dialog = static_cast<CredentialsPrompt*>(prompt);
-            emit squawk->modifyAccountRequest(currentSource, {
+            emit modifyAccountRequest(currentSource, {
                 {"login", dialog->getLogin()},
                 {"password", dialog->getPassword()}
             });
@@ -133,7 +146,7 @@ void DialogQueue::onPropmtRejected()
             break;
         case askPassword:
         case askCredentials:
-            emit squawk->disconnectAccount(currentSource);
+            emit disconnectAccount(currentSource);
             break;
     }
     actionDone();
diff --git a/ui/dialogqueue.h b/main/dialogqueue.h
similarity index 83%
rename from ui/dialogqueue.h
rename to main/dialogqueue.h
index bfc1f21..b0da9dc 100644
--- a/ui/dialogqueue.h
+++ b/main/dialogqueue.h
@@ -19,14 +19,14 @@
 
 #include <QObject>
 #include <QInputDialog>
+#include <QMainWindow>
 
 #include <boost/multi_index_container.hpp>
 #include <boost/multi_index/ordered_index.hpp>
 #include <boost/multi_index/sequenced_index.hpp>
 
 #include <ui/widgets/accounts/credentialsprompt.h>
-
-class Squawk;
+#include <ui/models/roster.h>
 
 class DialogQueue : public QObject
 {
@@ -38,12 +38,21 @@ public:
         askCredentials
     };
 
-    DialogQueue(Squawk* squawk);
+    DialogQueue(const Models::Roster& roster);
     ~DialogQueue();
 
     bool addAction(const QString& source, Action action);
     bool cancelAction(const QString& source, Action action);
 
+signals:
+    void modifyAccountRequest(const QString&, const QMap<QString, QVariant>&);
+    void responsePassword(const QString& account, const QString& password);
+    void disconnectAccount(const QString&);
+
+public:
+    void setParentWidnow(QMainWindow* parent);
+    void quit();
+
 private:
     void performNextAction();
     void actionDone();
@@ -85,7 +94,8 @@ private:
     Sequence& sequence;
 
     QDialog* prompt;
-    Squawk* squawk;
+    QMainWindow* parent;
+    const Models::Roster& roster;
 };
 
 #endif // DIALOGQUEUE_H
diff --git a/main/main.cpp b/main/main.cpp
new file mode 100644
index 0000000..77719a2
--- /dev/null
+++ b/main/main.cpp
@@ -0,0 +1,139 @@
+/*
+ * Squawk messenger. 
+ * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+ *
+ * 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 "shared/global.h"
+#include "shared/messageinfo.h"
+#include "shared/pathcheck.h"
+#include "main/application.h"
+#include "core/signalcatcher.h"
+#include "core/squawk.h"
+
+#include <QLibraryInfo>
+#include <QSettings>
+#include <QStandardPaths>
+#include <QTranslator>
+#include <QtCore/QObject>
+#include <QtCore/QThread>
+#include <QtWidgets/QApplication>
+#include <QDir>
+
+int main(int argc, char *argv[])
+{
+    qRegisterMetaType<Shared::Message>("Shared::Message");
+    qRegisterMetaType<Shared::MessageInfo>("Shared::MessageInfo");
+    qRegisterMetaType<Shared::VCard>("Shared::VCard");
+    qRegisterMetaType<std::list<Shared::Message>>("std::list<Shared::Message>");
+    qRegisterMetaType<std::list<Shared::MessageInfo>>("std::list<Shared::MessageInfo>");
+    qRegisterMetaType<QSet<QString>>("QSet<QString>");
+    qRegisterMetaType<Shared::ConnectionState>("Shared::ConnectionState");
+    qRegisterMetaType<Shared::Availability>("Shared::Availability");
+    
+    QApplication app(argc, argv);
+    SignalCatcher sc(&app);
+
+    QApplication::setApplicationName("squawk");
+    QApplication::setOrganizationName("macaw.me");
+    QApplication::setApplicationDisplayName("Squawk");
+    QApplication::setApplicationVersion("0.2.2");
+
+    QTranslator qtTranslator;
+    qtTranslator.load("qt_" + QLocale::system().name(), QLibraryInfo::location(QLibraryInfo::TranslationsPath));
+    app.installTranslator(&qtTranslator);
+
+    QTranslator myappTranslator;
+    QStringList shares = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation);
+    bool found = false;
+    for (QString share : shares) {
+        found = myappTranslator.load(QLocale(), QLatin1String("squawk"), ".", share + "/l10n");
+        if (found) {
+            break;
+        }
+    }
+    if (!found) {
+        myappTranslator.load(QLocale(), QLatin1String("squawk"), ".", QCoreApplication::applicationDirPath());
+    }
+    
+    app.installTranslator(&myappTranslator);
+    
+    QIcon icon;
+    icon.addFile(":images/logo.svg", QSize(16, 16));
+    icon.addFile(":images/logo.svg", QSize(24, 24));
+    icon.addFile(":images/logo.svg", QSize(32, 32));
+    icon.addFile(":images/logo.svg", QSize(48, 48));
+    icon.addFile(":images/logo.svg", QSize(64, 64));
+    icon.addFile(":images/logo.svg", QSize(96, 96));
+    icon.addFile(":images/logo.svg", QSize(128, 128));
+    icon.addFile(":images/logo.svg", QSize(256, 256));
+    icon.addFile(":images/logo.svg", QSize(512, 512));
+    QApplication::setWindowIcon(icon);
+    
+    new Shared::Global();        //translates enums
+
+    QSettings settings;
+    QVariant vs = settings.value("style");
+    if (vs.isValid()) {
+        QString style = vs.toString().toLower();
+        if (style != "system") {
+            Shared::Global::setStyle(style);
+        }
+    }
+    if (Shared::Global::supported("colorSchemeTools")) {
+        QVariant vt = settings.value("theme");
+        if (vt.isValid()) {
+            QString theme = vt.toString();
+            if (theme.toLower() != "system") {
+                Shared::Global::setTheme(theme);
+            }
+        }
+    }
+    QString path = Shared::downloadsPathCheck();
+    if (path.size() > 0) {
+        settings.setValue("downloadsPath", path);
+    } else {
+        qDebug() << "couldn't initialize directory for downloads, quitting";
+        return -1;
+    }
+    
+    Core::Squawk* squawk = new Core::Squawk();
+    QThread* coreThread = new QThread();
+    squawk->moveToThread(coreThread);
+    
+    Application application(squawk);
+
+    QObject::connect(&sc, &SignalCatcher::interrupt, &application, &Application::quit);
+
+    QObject::connect(coreThread, &QThread::started, squawk, &Core::Squawk::start);
+    QObject::connect(&application, &Application::quitting, squawk, &Core::Squawk::stop);
+    //QObject::connect(&app, &QApplication::aboutToQuit, &w, &QMainWindow::close);
+    QObject::connect(squawk, &Core::Squawk::quit, squawk, &Core::Squawk::deleteLater);
+    QObject::connect(squawk, &Core::Squawk::destroyed, coreThread, &QThread::quit, Qt::QueuedConnection);
+    QObject::connect(coreThread, &QThread::finished, &app, &QApplication::quit, Qt::QueuedConnection);
+    
+    coreThread->start();
+    int result = app.exec();
+
+    if (coreThread->isRunning()) {
+        //coreThread->wait();
+        //todo if I uncomment that, the app will not quit if it has reconnected at least once
+        //it feels like a symptom of something badly desinged in the core thread
+        //need to investigate;
+    }
+    
+    return result;
+}
+
diff --git a/shared/global.cpp b/shared/global.cpp
index be660bd..6519952 100644
--- a/shared/global.cpp
+++ b/shared/global.cpp
@@ -95,8 +95,6 @@ Shared::Global::Global():
     }),
     defaultSystemStyle(QApplication::style()->objectName()),
     defaultSystemPalette(QApplication::palette()),
-    rosterModel(new Models::Roster()),
-    dbus("org.freedesktop.Notifications", "/org/freedesktop/Notifications", "org.freedesktop.Notifications", QDBusConnection::sessionBus()),
     pluginSupport({
         {"KWallet", false},
         {"openFileManagerWindowJob", false},
@@ -352,44 +350,6 @@ void Shared::Global::setStyle(const QString& style)
     }
 }
 
-void Shared::Global::notify(const QString& account, const Shared::Message& msg)
-{
-    QString name = QString(instance->rosterModel->getContactName(account, msg.getPenPalJid()));
-    QString path = QString(instance->rosterModel->getContactIconPath(account, msg.getPenPalJid(), msg.getPenPalResource()));
-    QVariantList args;
-    args << QString();
-
-    args << qHash(msg.getId());
-    if (path.size() > 0) {
-        args << path;
-    } else {
-        args << QString("mail-message");    //TODO should here better be unknown user icon?
-    }
-    if (msg.getType() == Shared::Message::groupChat) {
-        args << msg.getFromResource() + " from " + name;
-    } else {
-        args << name;
-    }
-
-    QString body(msg.getBody());
-    QString oob(msg.getOutOfBandUrl());
-    if (body == oob) {
-        body = tr("Attached file");
-    }
-
-    args << body;
-    args << QStringList();
-    args << QVariantMap({
-        {"desktop-entry", QString(QCoreApplication::applicationName())},
-        {"category", QString("message")},
-       // {"sound-file", "/path/to/macaw/squawk"},
-        {"sound-name", QString("message-new-instant")}
-    });
-    args << -1;
-    instance->dbus.callWithArgumentList(QDBus::AutoDetect, "Notify", args);
-}
-
-
 #define FROM_INT_INPL(Enum)                                                                 \
 template<>                                                                                  \
 Enum Shared::Global::fromInt(int src)                                                       \
diff --git a/shared/global.h b/shared/global.h
index fcd8105..ebed931 100644
--- a/shared/global.h
+++ b/shared/global.h
@@ -42,18 +42,11 @@
 #include <QProcess>
 #include <QDesktopServices>
 #include <QRegularExpression>
-#include <QDBusInterface>
-
-class Squawk;
-namespace Models {
-    class Roster;
-}
 
 namespace Shared {
     
     class Global {
         Q_DECLARE_TR_FUNCTIONS(Global)
-        friend class ::Squawk;
     public:
         struct FileInfo {
             enum class Preview {
@@ -71,8 +64,6 @@ namespace Shared {
         
         Global();
 
-        static void notify(const QString& account, const Shared::Message& msg);
-
         static Global* getInstance();
         static QString getName(Availability av);
         static QString getName(ConnectionState cs);
@@ -130,8 +121,6 @@ namespace Shared {
         
     private:
         static Global* instance;
-        Models::Roster* rosterModel;
-        QDBusInterface dbus;
         
         std::map<QString, bool> pluginSupport;
         std::map<QString, FileInfo> fileCache;
diff --git a/ui/CMakeLists.txt b/ui/CMakeLists.txt
index fcbb24c..296c289 100644
--- a/ui/CMakeLists.txt
+++ b/ui/CMakeLists.txt
@@ -2,8 +2,6 @@ target_sources(squawk PRIVATE
     squawk.cpp
     squawk.h
     squawk.ui
-    dialogqueue.cpp
-    dialogqueue.h
 )
 
 add_subdirectory(models)
diff --git a/ui/models/roster.cpp b/ui/models/roster.cpp
index b96ddda..fef3e43 100644
--- a/ui/models/roster.cpp
+++ b/ui/models/roster.cpp
@@ -927,11 +927,29 @@ QString Models::Roster::getContactIconPath(const QString& account, const QString
     return path;
 }
 
-Models::Account * Models::Roster::getAccount(const QString& name)
+Models::Account * Models::Roster::getAccount(const QString& name) {
+    return const_cast<Models::Account*>(getAccountConst(name));}
+
+const Models::Account * Models::Roster::getAccountConst(const QString& name) const {
+    return accounts.at(name);}
+
+const Models::Element * Models::Roster::getElementConst(const Models::Roster::ElId& id) const
 {
-    return accounts.find(name)->second;
+    std::map<ElId, Contact*>::const_iterator cItr = contacts.find(id);
+
+    if (cItr != contacts.end()) {
+        return cItr->second;
+    } else {
+        std::map<ElId, Room*>::const_iterator rItr = rooms.find(id);
+        if (rItr != rooms.end()) {
+            return rItr->second;
+        }
+    }
+
+    return NULL;
 }
 
+
 QModelIndex Models::Roster::getAccountIndex(const QString& name)
 {
     std::map<QString, Account*>::const_iterator itr = accounts.find(name);
@@ -1005,20 +1023,20 @@ void Models::Roster::fileError(const std::list<Shared::MessageInfo>& msgs, const
 
 Models::Element * Models::Roster::getElement(const Models::Roster::ElId& id)
 {
-    std::map<ElId, Contact*>::iterator cItr = contacts.find(id);
-    
-    if (cItr != contacts.end()) {
-        return cItr->second;
-    } else {
-        std::map<ElId, Room*>::iterator rItr = rooms.find(id);
-        if (rItr != rooms.end()) {
-            return rItr->second;
-        }
-    }
-    
-    return NULL;
+    return const_cast<Models::Element*>(getElementConst(id));
 }
 
+Models::Item::Type Models::Roster::getContactType(const Models::Roster::ElId& id) const
+{
+    const Models::Element* el = getElementConst(id);
+    if (el == NULL) {
+        return Item::root;
+    }
+
+    return el->type;
+}
+
+
 void Models::Roster::onAccountReconnected()
 {
     Account* acc = static_cast<Account*>(sender());
diff --git a/ui/models/roster.h b/ui/models/roster.h
index 28f4d30..60adf13 100644
--- a/ui/models/roster.h
+++ b/ui/models/roster.h
@@ -46,6 +46,7 @@ public:
     Roster(QObject* parent = 0);
     ~Roster();
 
+public slots:
     void addAccount(const QMap<QString, QVariant> &data);
     void updateAccount(const QString& account, const QString& field, const QVariant& value);
     void removeAccount(const QString& account);
@@ -65,7 +66,12 @@ public:
     void addRoomParticipant(const QString& account, const QString& jid, const QString& name, const QMap<QString, QVariant>& data);
     void changeRoomParticipant(const QString& account, const QString& jid, const QString& name, const QMap<QString, QVariant>& data);
     void removeRoomParticipant(const QString& account, const QString& jid, const QString& name);
+
+public:
     QString getContactName(const QString& account, const QString& jid) const;
+    Item::Type getContactType(const Models::Roster::ElId& id) const;
+    const Element* getElementConst(const ElId& id) const;
+    Element* getElement(const ElId& id);
     
     QVariant data ( const QModelIndex& index, int role ) const override;
     Qt::ItemFlags flags(const QModelIndex &index) const override;
@@ -79,6 +85,7 @@ public:
     bool groupHasContact(const QString& account, const QString& group, const QString& contactJID) const;
     QString getContactIconPath(const QString& account, const QString& jid, const QString& resource) const;
     Account* getAccount(const QString& name);
+    const Account* getAccountConst(const QString& name) const;
     QModelIndex getAccountIndex(const QString& name);
     QModelIndex getGroupIndex(const QString& account, const QString& name);
     void responseArchive(const QString& account, const QString& jid, const std::list<Shared::Message>& list, bool last);
@@ -95,9 +102,6 @@ signals:
     void unnoticedMessage(const QString& account, const Shared::Message& msg);
     void localPathInvalid(const QString& path);
     
-private:
-    Element* getElement(const ElId& id);
-    
 private slots:
     void onAccountDataChanged(const QModelIndex& tl, const QModelIndex& br, const QVector<int>& roles);
     void onAccountReconnected();
diff --git a/ui/squawk.cpp b/ui/squawk.cpp
index a08f38b..434b442 100644
--- a/ui/squawk.cpp
+++ b/ui/squawk.cpp
@@ -21,15 +21,13 @@
 #include <QDebug>
 #include <QIcon>
 
-Squawk::Squawk(QWidget *parent) :
+Squawk::Squawk(Models::Roster& p_rosterModel, QWidget *parent) :
     QMainWindow(parent),
     m_ui(new Ui::Squawk),
     accounts(nullptr),
     preferences(nullptr),
     about(nullptr),
-    dialogueQueue(this),
-    rosterModel(*(Shared::Global::getInstance()->rosterModel)),
-    conversations(),
+    rosterModel(p_rosterModel),
     contextMenu(new QMenu()),
     vCards(),
     currentConversation(nullptr),
@@ -64,12 +62,8 @@ Squawk::Squawk(QWidget *parent) :
     connect(m_ui->roster, &QTreeView::customContextMenuRequested, this, &Squawk::onRosterContextMenu);
     connect(m_ui->roster, &QTreeView::collapsed, this, &Squawk::onItemCollepsed);
     connect(m_ui->roster->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &Squawk::onRosterSelectionChanged);
-    connect(&rosterModel, &Models::Roster::unnoticedMessage, this, &Squawk::onUnnoticedMessage);
     
     connect(rosterModel.accountsModel, &Models::Accounts::sizeChanged, this, &Squawk::onAccountsSizeChanged);
-    connect(&rosterModel, &Models::Roster::requestArchive, this, &Squawk::onRequestArchive);
-    connect(&rosterModel, &Models::Roster::fileDownloadRequest, this, &Squawk::fileDownloadRequest);
-    connect(&rosterModel, &Models::Roster::localPathInvalid, this, &Squawk::localPathInvalid);
     connect(contextMenu, &QMenu::aboutToHide, this, &Squawk::onContextAboutToHide);
     connect(m_ui->actionAboutSquawk, &QAction::triggered, this, &Squawk::onAboutSquawkCalled);
     //m_ui->mainToolBar->addWidget(m_ui->comboBox);
@@ -199,36 +193,25 @@ void Squawk::closeEvent(QCloseEvent* event)
         about->close();
     }
     
-    for (Conversations::const_iterator itr = conversations.begin(), end = conversations.end(); itr != end; ++itr) {
-        disconnect(itr->second, &Conversation::destroyed, this, &Squawk::onConversationClosed);
-        itr->second->close();
-    }
-    conversations.clear();
-    
     for (std::map<QString, VCard*>::const_iterator itr = vCards.begin(), end = vCards.end(); itr != end; ++itr) {
         disconnect(itr->second, &VCard::destroyed, this, &Squawk::onVCardClosed);
         itr->second->close();
     }
     vCards.clear();
-    
+    writeSettings();
+    emit closing();;
+
     QMainWindow::closeEvent(event);
 }
 
+void Squawk::onAccountsClosed() {
+    accounts = nullptr;}
 
-void Squawk::onAccountsClosed()
-{
-    accounts = nullptr;
-}
+void Squawk::onPreferencesClosed() {
+    preferences = nullptr;}
 
-void Squawk::onPreferencesClosed()
-{
-    preferences = nullptr;
-}
-
-void Squawk::newAccount(const QMap<QString, QVariant>& account)
-{
-    rosterModel.addAccount(account);
-}
+void Squawk::onAboutSquawkClosed() {
+    about = nullptr;}
 
 void Squawk::onComboboxActivated(int index)
 {
@@ -236,85 +219,11 @@ void Squawk::onComboboxActivated(int index)
     emit changeState(av);
 }
 
-void Squawk::changeAccount(const QString& account, const QMap<QString, QVariant>& data)
-{
-    for (QMap<QString, QVariant>::const_iterator itr = data.begin(), end = data.end(); itr != end; ++itr) {
-        QString attr = itr.key();
-        rosterModel.updateAccount(account, attr, *itr);
-    }
-}
+void Squawk::expand(const QModelIndex& index) {
+    m_ui->roster->expand(index);}
 
-void Squawk::addContact(const QString& account, const QString& jid, const QString& group, const QMap<QString, QVariant>& data)
-{
-    rosterModel.addContact(account, jid, group, data);
-        
-    QSettings settings;
-    settings.beginGroup("ui");
-    settings.beginGroup("roster");
-    settings.beginGroup(account);
-    if (settings.value("expanded", false).toBool()) {
-        QModelIndex ind = rosterModel.getAccountIndex(account);
-        m_ui->roster->expand(ind);
-    }
-    settings.endGroup();
-    settings.endGroup();
-    settings.endGroup();
-}
-
-void Squawk::addGroup(const QString& account, const QString& name)
-{
-    rosterModel.addGroup(account, name);
-    
-    QSettings settings;
-    settings.beginGroup("ui");
-    settings.beginGroup("roster");
-    settings.beginGroup(account);
-    if (settings.value("expanded", false).toBool()) {
-        QModelIndex ind = rosterModel.getAccountIndex(account);
-        m_ui->roster->expand(ind);
-        if (settings.value(name + "/expanded", false).toBool()) {
-            m_ui->roster->expand(rosterModel.getGroupIndex(account, name));
-        }
-    }
-    settings.endGroup();
-    settings.endGroup();
-    settings.endGroup();
-}
-
-void Squawk::removeGroup(const QString& account, const QString& name)
-{
-    rosterModel.removeGroup(account, name);
-}
-
-void Squawk::changeContact(const QString& account, const QString& jid, const QMap<QString, QVariant>& data)
-{
-    rosterModel.changeContact(account, jid, data);
-}
-
-void Squawk::removeContact(const QString& account, const QString& jid)
-{
-    rosterModel.removeContact(account, jid);
-}
-
-void Squawk::removeContact(const QString& account, const QString& jid, const QString& group)
-{
-    rosterModel.removeContact(account, jid, group);
-}
-
-void Squawk::addPresence(const QString& account, const QString& jid, const QString& name, const QMap<QString, QVariant>& data)
-{
-    rosterModel.addPresence(account, jid, name, data);
-}
-
-void Squawk::removePresence(const QString& account, const QString& jid, const QString& name)
-{
-    rosterModel.removePresence(account, jid, name);
-}
-
-void Squawk::stateChanged(Shared::Availability state)
-{
-    m_ui->comboBox->setCurrentIndex(static_cast<int>(state));
-}
+void Squawk::stateChanged(Shared::Availability state) {
+    m_ui->comboBox->setCurrentIndex(static_cast<int>(state));}
 
 void Squawk::onRosterItemDoubleClicked(const QModelIndex& item)
 {
@@ -325,186 +234,33 @@ void Squawk::onRosterItemDoubleClicked(const QModelIndex& item)
         }
         Models::Contact* contact = nullptr;
         Models::Room* room = nullptr;
-        QString res;
-        Models::Roster::ElId* id = nullptr;
         switch (node->type) {
             case Models::Item::contact:
                 contact = static_cast<Models::Contact*>(node);
-                id = new Models::Roster::ElId(contact->getAccountName(), contact->getJid());
+                emit openConversation(Models::Roster::ElId(contact->getAccountName(), contact->getJid()));
                 break;
             case Models::Item::presence:
                 contact = static_cast<Models::Contact*>(node->parentItem());
-                id = new Models::Roster::ElId(contact->getAccountName(), contact->getJid());
-                res = node->getName();
+                emit openConversation(Models::Roster::ElId(contact->getAccountName(), contact->getJid()), node->getName());
                 break;
             case Models::Item::room:
                 room = static_cast<Models::Room*>(node);
-                id = new Models::Roster::ElId(room->getAccountName(), room->getJid());
+                emit openConversation(Models::Roster::ElId(room->getAccountName(), room->getJid()));
                 break;
             default:
                 m_ui->roster->expand(item);
                 break;
         }
-        
-        if (id != nullptr) {
-            Conversations::const_iterator itr = conversations.find(*id);
-            Models::Account* acc = rosterModel.getAccount(id->account);
-            Conversation* conv = nullptr;
-            bool created = false;
-            if (itr != conversations.end()) {
-                conv = itr->second;
-            } else if (contact != nullptr) {
-                created = true;
-                conv = new Chat(acc, contact);
-            } else if (room != nullptr) {
-                created = true;
-                conv = new Room(acc, room);
-                
-                if (!room->getJoined()) {
-                    emit setRoomJoined(id->account, id->name, true);
-                }
-            }
-            
-            if (conv != nullptr) {
-                if (created) {
-                    conv->setAttribute(Qt::WA_DeleteOnClose);
-                    subscribeConversation(conv);
-                    conversations.insert(std::make_pair(*id, conv));
-                }
-                
-                conv->show();
-                conv->raise();
-                conv->activateWindow();
-                
-                if (res.size() > 0) {
-                    conv->setPalResource(res);
-                }
-            }
-            
-            delete id;
-        }
     }
 }
 
-void Squawk::onConversationClosed(QObject* parent)
+void Squawk::closeCurrentConversation()
 {
-    Conversation* conv = static_cast<Conversation*>(sender());
-    Models::Roster::ElId id(conv->getAccount(), conv->getJid());
-    Conversations::const_iterator itr = conversations.find(id);
-    if (itr != conversations.end()) {
-        conversations.erase(itr);
-    }
-    if (conv->isMuc) {
-        Room* room = static_cast<Room*>(conv);
-        if (!room->autoJoined()) {
-            emit setRoomJoined(id.account, id.name, false);
-        }
-    }
-}
-
-void Squawk::fileProgress(const std::list<Shared::MessageInfo> msgs, qreal value, bool up)
-{
-    rosterModel.fileProgress(msgs, value, up);
-}
-
-void Squawk::fileDownloadComplete(const std::list<Shared::MessageInfo> msgs, const QString& path)
-{
-    rosterModel.fileComplete(msgs, false);
-}
-
-void Squawk::fileError(const std::list<Shared::MessageInfo> msgs, const QString& error, bool up)
-{
-    rosterModel.fileError(msgs, error, up);
-}
-
-void Squawk::fileUploadComplete(const std::list<Shared::MessageInfo> msgs, const QString& url, const QString& path)
-{
-    rosterModel.fileComplete(msgs, true);
-}
-
-void Squawk::accountMessage(const QString& account, const Shared::Message& data)
-{
-    rosterModel.addMessage(account, data);
-}
-
-void Squawk::onUnnoticedMessage(const QString& account, const Shared::Message& msg)
-{
-    notify(account, msg);             //Telegram does this way - notifies even if the app is visible
-    QApplication::alert(this);
-}
-
-void Squawk::changeMessage(const QString& account, const QString& jid, const QString& id, const QMap<QString, QVariant>& data)
-{
-    rosterModel.changeMessage(account, jid, id, data);
-}
-
-void Squawk::notify(const QString& account, const Shared::Message& msg)
-{
-    Shared::Global::notify(account, msg);
-}
-
-void Squawk::onConversationMessage(const Shared::Message& msg)
-{
-    Conversation* conv = static_cast<Conversation*>(sender());
-    QString acc = conv->getAccount();
-    
-    rosterModel.addMessage(acc, msg);
-    emit sendMessage(acc, msg);
-}
-
-void Squawk::onConversationReplaceMessage(const QString& originalId, const Shared::Message& msg)
-{
-    Conversation* conv = static_cast<Conversation*>(sender());
-    QString acc = conv->getAccount();
-
-    rosterModel.changeMessage(acc, msg.getPenPalJid(), originalId, {
-        {"state", static_cast<uint>(Shared::Message::State::pending)}
-    });
-    emit replaceMessage(acc, originalId, msg);
-}
-
-void Squawk::onConversationResend(const QString& id)
-{
-    Conversation* conv = static_cast<Conversation*>(sender());
-    QString acc = conv->getAccount();
-    QString jid = conv->getJid();
-    
-    emit resendMessage(acc, jid, id);
-}
-
-void Squawk::onRequestArchive(const QString& account, const QString& jid, const QString& before)
-{
-    emit requestArchive(account, jid, 20, before); //TODO amount as a settings value
-}
-
-void Squawk::responseArchive(const QString& account, const QString& jid, const std::list<Shared::Message>& list, bool last)
-{
-    rosterModel.responseArchive(account, jid, list, last);
-}
-
-void Squawk::removeAccount(const QString& account)
-{
-    Conversations::const_iterator itr = conversations.begin();
-    while (itr != conversations.end()) {
-        if (itr->first.account == account) {
-            Conversations::const_iterator lItr = itr;
-            ++itr;
-            Conversation* conv = lItr->second;
-            disconnect(conv, &Conversation::destroyed, this, &Squawk::onConversationClosed);
-            conv->close();
-            conversations.erase(lItr);
-        } else {
-            ++itr;
-        }
-    }
-    
-    if (currentConversation != nullptr && currentConversation->getAccount() == account) {
+    if (currentConversation != nullptr) {
         currentConversation->deleteLater();
         currentConversation = nullptr;
         m_ui->filler->show();
     }
-    
-    rosterModel.removeAccount(account);
 }
 
 void Squawk::onRosterContextMenu(const QPoint& point)
@@ -542,13 +298,13 @@ void Squawk::onRosterContextMenu(const QPoint& point)
                 break;
             case Models::Item::contact: {
                 Models::Contact* cnt = static_cast<Models::Contact*>(item);
+                Models::Roster::ElId id(cnt->getAccountName(), cnt->getJid());
+                QString cntName = cnt->getName();
                 hasMenu = true;
                 
                 QAction* dialog = contextMenu->addAction(Shared::icon("mail-message"), tr("Open dialog"));
                 dialog->setEnabled(active);
-                connect(dialog, &QAction::triggered, [this, index]() {
-                    onRosterItemDoubleClicked(index);
-                });
+                connect(dialog, &QAction::triggered, std::bind(&Squawk::onRosterItemDoubleClicked, this, index));
                 
                 Shared::SubscriptionState state = cnt->getState();
                 switch (state) {
@@ -556,9 +312,7 @@ void Squawk::onRosterContextMenu(const QPoint& point)
                     case Shared::SubscriptionState::to: {
                         QAction* unsub = contextMenu->addAction(Shared::icon("news-unsubscribe"), tr("Unsubscribe"));
                         unsub->setEnabled(active);
-                        connect(unsub, &QAction::triggered, [this, cnt]() {
-                            emit unsubscribeContact(cnt->getAccountName(), cnt->getJid(), "");
-                        });
+                        connect(unsub, &QAction::triggered, std::bind(&Squawk::changeSubscription, this, id, false));
                     }
                     break;
                     case Shared::SubscriptionState::from:
@@ -566,75 +320,68 @@ void Squawk::onRosterContextMenu(const QPoint& point)
                     case Shared::SubscriptionState::none: {
                         QAction* sub = contextMenu->addAction(Shared::icon("news-subscribe"), tr("Subscribe"));
                         sub->setEnabled(active);
-                        connect(sub, &QAction::triggered, [this, cnt]() {
-                            emit subscribeContact(cnt->getAccountName(), cnt->getJid(), "");
-                        });
+                        connect(sub, &QAction::triggered, std::bind(&Squawk::changeSubscription, this, id, true));
                     }    
                 }
-                QString accName = cnt->getAccountName();
-                QString cntJID = cnt->getJid();
-                QString cntName = cnt->getName();
                 
                 QAction* rename = contextMenu->addAction(Shared::icon("edit-rename"), tr("Rename"));
                 rename->setEnabled(active);
-                connect(rename, &QAction::triggered, [this, cntName, accName, cntJID]() {
+                connect(rename, &QAction::triggered, [this, cntName, id]() {
                     QInputDialog* dialog = new QInputDialog(this);
-                    connect(dialog, &QDialog::accepted, [this, dialog, cntName, accName, cntJID]() {
+                    connect(dialog, &QDialog::accepted, [this, dialog, cntName, id]() {
                         QString newName = dialog->textValue();
                         if (newName != cntName) {
-                            emit renameContactRequest(accName, cntJID, newName);
+                            emit renameContactRequest(id.account, id.name, newName);
                         }
                         dialog->deleteLater();
                     });
                     connect(dialog, &QDialog::rejected, dialog, &QObject::deleteLater);
                     dialog->setInputMode(QInputDialog::TextInput);
-                    dialog->setLabelText(tr("Input new name for %1\nor leave it empty for the contact \nto be displayed as %1").arg(cntJID));
-                    dialog->setWindowTitle(tr("Renaming %1").arg(cntJID));
+                    dialog->setLabelText(tr("Input new name for %1\nor leave it empty for the contact \nto be displayed as %1").arg(id.name));
+                    dialog->setWindowTitle(tr("Renaming %1").arg(id.name));
                     dialog->setTextValue(cntName);
                     dialog->exec();
                 });
                 
                 
                 QMenu* groupsMenu = contextMenu->addMenu(Shared::icon("group"), tr("Groups"));
-                std::deque<QString> groupList = rosterModel.groupList(accName);
+                std::deque<QString> groupList = rosterModel.groupList(id.account);
                 for (QString groupName : groupList) {
                     QAction* gr = groupsMenu->addAction(groupName);
                     gr->setCheckable(true);
-                    gr->setChecked(rosterModel.groupHasContact(accName, groupName, cntJID));
+                    gr->setChecked(rosterModel.groupHasContact(id.account, groupName, id.name));
                     gr->setEnabled(active);
-                    connect(gr, &QAction::toggled, [this, accName, groupName, cntJID](bool checked) {
+                    connect(gr, &QAction::toggled, [this,  groupName, id](bool checked) {
                         if (checked) {
-                            emit addContactToGroupRequest(accName, cntJID, groupName);
+                            emit addContactToGroupRequest(id.account, id.name, groupName);
                         } else {
-                            emit removeContactFromGroupRequest(accName, cntJID, groupName);
+                            emit removeContactFromGroupRequest(id.account, id.name, groupName);
                         }
                     });
                 }
                 QAction* newGroup = groupsMenu->addAction(Shared::icon("group-new"), tr("New group"));
                 newGroup->setEnabled(active);
-                connect(newGroup, &QAction::triggered, [this, accName, cntJID]() {
+                connect(newGroup, &QAction::triggered, [this, id]() {
                     QInputDialog* dialog = new QInputDialog(this);
-                    connect(dialog, &QDialog::accepted, [this, dialog, accName, cntJID]() {
-                        emit addContactToGroupRequest(accName, cntJID, dialog->textValue());
+                    connect(dialog, &QDialog::accepted, [this, dialog, id]() {
+                        emit addContactToGroupRequest(id.account, id.name, dialog->textValue());
                         dialog->deleteLater();
                     });
                     connect(dialog, &QDialog::rejected, dialog, &QObject::deleteLater);
                     dialog->setInputMode(QInputDialog::TextInput);
                     dialog->setLabelText(tr("New group name"));
-                    dialog->setWindowTitle(tr("Add %1 to a new group").arg(cntJID));
+                    dialog->setWindowTitle(tr("Add %1 to a new group").arg(id.name));
                     dialog->exec();
                 });
                 
                 
                 QAction* card = contextMenu->addAction(Shared::icon("user-properties"), tr("VCard"));
                 card->setEnabled(active);
-                connect(card, &QAction::triggered, std::bind(&Squawk::onActivateVCard, this, accName, cnt->getJid(), false));
+                connect(card, &QAction::triggered, std::bind(&Squawk::onActivateVCard, this, id.account, id.name, false));
                 
                 QAction* remove = contextMenu->addAction(Shared::icon("edit-delete"), tr("Remove"));
                 remove->setEnabled(active);
-                connect(remove, &QAction::triggered, [this, cnt]() {
-                    emit removeContactRequest(cnt->getAccountName(), cnt->getJid());
-                });
+                connect(remove, &QAction::triggered, std::bind(&Squawk::removeContactRequest, this, id.account, id.name));
                 
             }
                 break;
@@ -653,32 +400,16 @@ void Squawk::onRosterContextMenu(const QPoint& point)
                 if (room->getAutoJoin()) {
                     QAction* unsub = contextMenu->addAction(Shared::icon("news-unsubscribe"), tr("Unsubscribe"));
                     unsub->setEnabled(active);
-                    connect(unsub, &QAction::triggered, [this, id]() {
-                        emit setRoomAutoJoin(id.account, id.name, false);
-                        if (conversations.find(id) == conversations.end()
-                            && (currentConversation == nullptr || currentConversation->getId() != id)
-                        ) {    //to leave the room if it's not opened in a conversation window
-                            emit setRoomJoined(id.account, id.name, false);
-                        }
-                    });
+                    connect(unsub, &QAction::triggered, std::bind(&Squawk::changeSubscription, this, id, false));
                 } else {
-                    QAction* unsub = contextMenu->addAction(Shared::icon("news-subscribe"), tr("Subscribe"));
-                    unsub->setEnabled(active);
-                    connect(unsub, &QAction::triggered, [this, id]() {
-                        emit setRoomAutoJoin(id.account, id.name, true);
-                        if (conversations.find(id) == conversations.end()
-                            && (currentConversation == nullptr || currentConversation->getId() != id)
-                        ) {    //to join the room if it's not already joined
-                            emit setRoomJoined(id.account, id.name, true);
-                        }
-                    });
+                    QAction* sub = contextMenu->addAction(Shared::icon("news-subscribe"), tr("Subscribe"));
+                    sub->setEnabled(active);
+                    connect(sub, &QAction::triggered, std::bind(&Squawk::changeSubscription, this, id, true));
                 }
                 
                 QAction* remove = contextMenu->addAction(Shared::icon("edit-delete"), tr("Remove"));
                 remove->setEnabled(active);
-                connect(remove, &QAction::triggered, [this, id]() {
-                    emit removeRoomRequest(id.account, id.name);
-                });
+                connect(remove, &QAction::triggered, std::bind(&Squawk::removeRoomRequest, this, id.account, id.name));
             }
                 break;
             default:
@@ -690,36 +421,6 @@ void Squawk::onRosterContextMenu(const QPoint& point)
     }    
 }
 
-void Squawk::addRoom(const QString& account, const QString jid, const QMap<QString, QVariant>& data)
-{
-    rosterModel.addRoom(account, jid, data);
-}
-
-void Squawk::changeRoom(const QString& account, const QString jid, const QMap<QString, QVariant>& data)
-{
-    rosterModel.changeRoom(account, jid, data);
-}
-
-void Squawk::removeRoom(const QString& account, const QString jid)
-{
-    rosterModel.removeRoom(account, jid);
-}
-
-void Squawk::addRoomParticipant(const QString& account, const QString& jid, const QString& name, const QMap<QString, QVariant>& data)
-{
-    rosterModel.addRoomParticipant(account, jid, name, data);
-}
-
-void Squawk::changeRoomParticipant(const QString& account, const QString& jid, const QString& name, const QMap<QString, QVariant>& data)
-{
-    rosterModel.changeRoomParticipant(account, jid, name, data);
-}
-
-void Squawk::removeRoomParticipant(const QString& account, const QString& jid, const QString& name)
-{
-    rosterModel.removeRoomParticipant(account, jid, name);
-}
-
 void Squawk::responseVCard(const QString& jid, const Shared::VCard& card)
 {
     std::map<QString, VCard*>::const_iterator itr = vCards.find(jid);
@@ -777,61 +478,40 @@ void Squawk::onVCardSave(const Shared::VCard& card, const QString& account)
     widget->deleteLater();
 }
 
-void Squawk::readSettings()
-{
-    QSettings settings;
-    settings.beginGroup("ui");
-    int avail;
-    if (settings.contains("availability")) {
-        avail = settings.value("availability").toInt();
-    } else {
-        avail = static_cast<int>(Shared::Availability::online);
-    }
-    settings.endGroup();
-    m_ui->comboBox->setCurrentIndex(avail);
-
-    emit changeState(Shared::Global::fromInt<Shared::Availability>(avail));
-}
-
 void Squawk::writeSettings()
 {
     QSettings settings;
     settings.beginGroup("ui");
-    settings.beginGroup("window");
-    settings.setValue("geometry", saveGeometry());
-    settings.setValue("state", saveState());
-    settings.endGroup();
-    
-    settings.setValue("splitter", m_ui->splitter->saveState());
-    
-    settings.setValue("availability", m_ui->comboBox->currentIndex());
-    
-    settings.remove("roster");
-    settings.beginGroup("roster");
-    int size = rosterModel.accountsModel->rowCount(QModelIndex());
-    for (int i = 0; i < size; ++i) {
-        QModelIndex acc = rosterModel.index(i, 0, QModelIndex());
-        Models::Account* account = rosterModel.accountsModel->getAccount(i);
-        QString accName = account->getName();
-        settings.beginGroup(accName);
-        
-        settings.setValue("expanded", m_ui->roster->isExpanded(acc));
-        std::deque<QString> groups = rosterModel.groupList(accName);
-        for (const QString& groupName : groups) {
-            settings.beginGroup(groupName);
-            QModelIndex gIndex = rosterModel.getGroupIndex(accName, groupName);
-            settings.setValue("expanded", m_ui->roster->isExpanded(gIndex));
-            settings.endGroup();
-        }
-        
+        settings.beginGroup("window");
+            settings.setValue("geometry", saveGeometry());
+            settings.setValue("state", saveState());
+        settings.endGroup();
+    
+        settings.setValue("splitter", m_ui->splitter->saveState());
+        settings.remove("roster");
+        settings.beginGroup("roster");
+            int size = rosterModel.accountsModel->rowCount(QModelIndex());
+            for (int i = 0; i < size; ++i) {
+                QModelIndex acc = rosterModel.index(i, 0, QModelIndex());
+                Models::Account* account = rosterModel.accountsModel->getAccount(i);
+                QString accName = account->getName();
+                settings.beginGroup(accName);
+
+                    settings.setValue("expanded", m_ui->roster->isExpanded(acc));
+                    std::deque<QString> groups = rosterModel.groupList(accName);
+                    for (const QString& groupName : groups) {
+                        settings.beginGroup(groupName);
+                            QModelIndex gIndex = rosterModel.getGroupIndex(accName, groupName);
+                            settings.setValue("expanded", m_ui->roster->isExpanded(gIndex));
+                        settings.endGroup();
+                    }
+
+                settings.endGroup();
+            }
         settings.endGroup();
-    }
-    settings.endGroup();
     settings.endGroup();
 
     settings.sync();
-
-    qDebug() << "Saved settings";
 }
 
 void Squawk::onItemCollepsed(const QModelIndex& index)
@@ -853,24 +533,6 @@ void Squawk::onItemCollepsed(const QModelIndex& index)
     }
 }
 
-void Squawk::requestPassword(const QString& account, bool authenticationError) {
-    if (authenticationError) {
-        dialogueQueue.addAction(account, DialogQueue::askCredentials);
-    } else {
-        dialogueQueue.addAction(account, DialogQueue::askPassword);
-    }
-
-}
-
-void Squawk::subscribeConversation(Conversation* conv)
-{
-    connect(conv, &Conversation::destroyed, this, &Squawk::onConversationClosed);
-    connect(conv, &Conversation::sendMessage, this, &Squawk::onConversationMessage);
-    connect(conv, &Conversation::replaceMessage, this, &Squawk::onConversationReplaceMessage);
-    connect(conv, &Conversation::resendMessage, this, &Squawk::onConversationResend);
-    connect(conv, &Conversation::notifyableMessage, this, &Squawk::notify);
-}
-
 void Squawk::onRosterSelectionChanged(const QModelIndex& current, const QModelIndex& previous)
 {   
     if (restoreSelection.isValid() && restoreSelection == current) {
@@ -942,16 +604,12 @@ void Squawk::onRosterSelectionChanged(const QModelIndex& current, const QModelIn
                 currentConversation = new Chat(acc, contact);
             } else if (room != nullptr) {
                 currentConversation = new Room(acc, room);
-                
-                if (!room->getJoined()) {
-                    emit setRoomJoined(id->account, id->name, true);
-                }
             }
             if (!testAttribute(Qt::WA_TranslucentBackground)) {
                 currentConversation->setFeedFrames(true, false, true, true);
             }
             
-            subscribeConversation(currentConversation);
+            emit openedConversation();
             
             if (res.size() > 0) {
                 currentConversation->setPalResource(res);
@@ -961,18 +619,10 @@ void Squawk::onRosterSelectionChanged(const QModelIndex& current, const QModelIn
             
             delete id;
         } else {
-            if (currentConversation != nullptr) {
-                currentConversation->deleteLater();
-                currentConversation = nullptr;
-                m_ui->filler->show();
-            }
+            closeCurrentConversation();
         }
     } else {
-        if (currentConversation != nullptr) {
-            currentConversation->deleteLater();
-            currentConversation = nullptr;
-            m_ui->filler->show();
-        }
+        closeCurrentConversation();
     }
 }
 
@@ -997,7 +647,12 @@ void Squawk::onAboutSquawkCalled()
     about->show();
 }
 
-void Squawk::onAboutSquawkClosed()
+Models::Roster::ElId Squawk::currentConversationId() const
 {
-    about = nullptr;
+    if (currentConversation == nullptr) {
+        return Models::Roster::ElId();
+    } else {
+        return Models::Roster::ElId(currentConversation->getAccount(), currentConversation->getJid());
+    }
 }
+
diff --git a/ui/squawk.h b/ui/squawk.h
index aa52153..5ffe090 100644
--- a/ui/squawk.h
+++ b/ui/squawk.h
@@ -39,7 +39,6 @@
 #include "widgets/vcard/vcard.h"
 #include "widgets/settings/settings.h"
 #include "widgets/about.h"
-#include "dialogqueue.h"
 
 #include "shared/shared.h"
 #include "shared/global.h"
@@ -48,84 +47,57 @@ namespace Ui {
 class Squawk;
 }
 
+class Application;
+
 class Squawk : public QMainWindow
 {
     Q_OBJECT
-    friend class DialogQueue;
+    friend class Application;
 public:
-    explicit Squawk(QWidget *parent = nullptr);
+    explicit Squawk(Models::Roster& rosterModel, QWidget *parent = nullptr);
     ~Squawk() override;
     
 signals:
+    void closing();
     void newAccountRequest(const QMap<QString, QVariant>&);
-    void modifyAccountRequest(const QString&, const QMap<QString, QVariant>&);
     void removeAccountRequest(const QString&);
     void connectAccount(const QString&);
     void disconnectAccount(const QString&);
     void changeState(Shared::Availability state);
-    void sendMessage(const QString& account, const Shared::Message& data);
-    void replaceMessage(const QString& account, const QString& originalId, const Shared::Message& data);
-    void resendMessage(const QString& account, const QString& jid, const QString& id);
-    void requestArchive(const QString& account, const QString& jid, int count, const QString& before);
-    void subscribeContact(const QString& account, const QString& jid, const QString& reason);
-    void unsubscribeContact(const QString& account, const QString& jid, const QString& reason);
     void removeContactRequest(const QString& account, const QString& jid);
     void addContactRequest(const QString& account, const QString& jid, const QString& name, const QSet<QString>& groups);
     void addContactToGroupRequest(const QString& account, const QString& jid, const QString& groupName);
     void removeContactFromGroupRequest(const QString& account, const QString& jid, const QString& groupName);
     void renameContactRequest(const QString& account, const QString& jid, const QString& newName);
-    void setRoomJoined(const QString& account, const QString& jid, bool joined);
-    void setRoomAutoJoin(const QString& account, const QString& jid, bool joined);
     void addRoomRequest(const QString& account, const QString& jid, const QString& nick, const QString& password, bool autoJoin);
     void removeRoomRequest(const QString& account, const QString& jid);
-    void fileDownloadRequest(const QString& url);
     void requestVCard(const QString& account, const QString& jid);
     void uploadVCard(const QString& account, const Shared::VCard& card);
-    void responsePassword(const QString& account, const QString& password);
-    void localPathInvalid(const QString& path);
     void changeDownloadsPath(const QString& path);
+
+    void notify(const QString& account, const Shared::Message& msg);
+    void changeSubscription(const Models::Roster::ElId& id, bool subscribe);
+    void openedConversation();
+    void openConversation(const Models::Roster::ElId& id, const QString& resource = "");
+
+    void modifyAccountRequest(const QString&, const QMap<QString, QVariant>&);
     
+public:
+    Models::Roster::ElId currentConversationId() const;
+    void closeCurrentConversation();
+
 public slots:
     void writeSettings();
-    void readSettings();
-    void newAccount(const QMap<QString, QVariant>& account);
-    void changeAccount(const QString& account, const QMap<QString, QVariant>& data);
-    void removeAccount(const QString& account);
-    void addGroup(const QString& account, const QString& name);
-    void removeGroup(const QString& account, const QString& name);
-    void addContact(const QString& account, const QString& jid, const QString& group, const QMap<QString, QVariant>& data);
-    void removeContact(const QString& account, const QString& jid, const QString& group);
-    void removeContact(const QString& account, const QString& jid);
-    void changeContact(const QString& account, const QString& jid, const QMap<QString, QVariant>& data);
-    void addPresence(const QString& account, const QString& jid, const QString& name, const QMap<QString, QVariant>& data);
-    void removePresence(const QString& account, const QString& jid, const QString& name);
     void stateChanged(Shared::Availability state);
-    void accountMessage(const QString& account, const Shared::Message& data);
-    void responseArchive(const QString& account, const QString& jid, const std::list<Shared::Message>& list, bool last);
-    void addRoom(const QString& account, const QString jid, const QMap<QString, QVariant>& data);
-    void changeRoom(const QString& account, const QString jid, const QMap<QString, QVariant>& data);
-    void removeRoom(const QString& account, const QString jid);
-    void addRoomParticipant(const QString& account, const QString& jid, const QString& name, const QMap<QString, QVariant>& data);
-    void changeRoomParticipant(const QString& account, const QString& jid, const QString& name, const QMap<QString, QVariant>& data);
-    void removeRoomParticipant(const QString& account, const QString& jid, const QString& name);
-    void fileError(const std::list<Shared::MessageInfo> msgs, const QString& error, bool up);
-    void fileProgress(const std::list<Shared::MessageInfo> msgs, qreal value, bool up);
-    void fileDownloadComplete(const std::list<Shared::MessageInfo> msgs, const QString& path);
-    void fileUploadComplete(const std::list<Shared::MessageInfo> msgs, const QString& url, const QString& path);
     void responseVCard(const QString& jid, const Shared::VCard& card);
-    void changeMessage(const QString& account, const QString& jid, const QString& id, const QMap<QString, QVariant>& data);
-    void requestPassword(const QString& account, bool authenticationError);
     
 private:
-    typedef std::map<Models::Roster::ElId, Conversation*> Conversations;
     QScopedPointer<Ui::Squawk> m_ui;
     
     Accounts* accounts;
     Settings* preferences;
     About* about;
-    DialogQueue dialogueQueue;
     Models::Roster& rosterModel;
-    Conversations conversations;
     QMenu* contextMenu;
     std::map<QString, VCard*> vCards;
     Conversation* currentConversation;
@@ -134,9 +106,7 @@ private:
     
 protected:
     void closeEvent(QCloseEvent * event) override;
-    
-protected slots:
-    void notify(const QString& account, const Shared::Message& msg);
+    void expand(const QModelIndex& index);
     
 private slots:
     void onAccounts();
@@ -148,27 +118,17 @@ private slots:
     void onAccountsSizeChanged(unsigned int size);
     void onAccountsClosed();
     void onPreferencesClosed();
-    void onConversationClosed(QObject* parent = 0);
     void onVCardClosed();
     void onVCardSave(const Shared::VCard& card, const QString& account);
     void onActivateVCard(const QString& account, const QString& jid, bool edition = false);
     void onComboboxActivated(int index);
     void onRosterItemDoubleClicked(const QModelIndex& item);
-    void onConversationMessage(const Shared::Message& msg);
-    void onConversationReplaceMessage(const QString& originalId, const Shared::Message& msg);
-    void onConversationResend(const QString& id);
-    void onRequestArchive(const QString& account, const QString& jid, const QString& before);
     void onRosterContextMenu(const QPoint& point);
     void onItemCollepsed(const QModelIndex& index);
     void onRosterSelectionChanged(const QModelIndex& current, const QModelIndex& previous);
     void onContextAboutToHide();
     void onAboutSquawkCalled();
     void onAboutSquawkClosed();
-    
-    void onUnnoticedMessage(const QString& account, const Shared::Message& msg);
-    
-private:
-    void subscribeConversation(Conversation* conv);
 };
 
 #endif // SQUAWK_H

From 3916aec358cb9ec81e7d55930bdc3ae55c38bc79 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Sat, 23 Apr 2022 16:58:08 +0300
Subject: [PATCH 193/281] unread messages count now is displayed on the
 launcher icon

---
 main/application.cpp  | 18 ++++++++++++++++--
 main/application.h    |  1 +
 main/main.cpp         |  1 +
 ui/models/element.cpp |  1 +
 ui/models/element.h   |  1 +
 ui/models/roster.cpp  | 13 +++++++++++++
 ui/models/roster.h    |  2 ++
 7 files changed, 35 insertions(+), 2 deletions(-)

diff --git a/main/application.cpp b/main/application.cpp
index f6ffe07..696ec03 100644
--- a/main/application.cpp
+++ b/main/application.cpp
@@ -21,7 +21,7 @@ Application::Application(Core::Squawk* p_core):
     availability(Shared::Availability::offline),
     core(p_core),
     squawk(nullptr),
-    notifications("org.freedesktop.Notifications", "/org/freedesktop/Notifications", "org.freedesktop.Notifications", QDBusConnection::sessionBus()),
+    notifications("org.freedesktop.Notifications", "/org/freedesktop/Notifications", "org.freedesktop.Notifications"),
     roster(),
     conversations(),
     dialogueQueue(roster),
@@ -29,6 +29,7 @@ Application::Application(Core::Squawk* p_core):
     destroyingSquawk(false)
 {
     connect(&roster, &Models::Roster::unnoticedMessage, this, &Application::notify);
+    connect(&roster, &Models::Roster::unreadMessagesCountChanged, this, &Application::unreadMessagesCountChanged);
 
 
     //connecting myself to the backed
@@ -100,6 +101,7 @@ void Application::quit()
         emit quitting();
 
         writeSettings();
+        unreadMessagesCountChanged(0);      //this notification persist in the desktop, for now I'll zero it on quit not to confuse people
         for (Conversations::const_iterator itr = conversations.begin(), end = conversations.end(); itr != end; ++itr) {
             disconnect(itr->second, &Conversation::destroyed, this, &Application::onConversationClosed);
             itr->second->close();
@@ -212,7 +214,7 @@ void Application::notify(const QString& account, const Shared::Message& msg)
     args << body;
     args << QStringList();
     args << QVariantMap({
-        {"desktop-entry", QString(QCoreApplication::applicationName())},
+        {"desktop-entry", qApp->desktopFileName()},
         {"category", QString("message")},
        // {"sound-file", "/path/to/macaw/squawk"},
         {"sound-name", QString("message-new-instant")}
@@ -225,6 +227,18 @@ void Application::notify(const QString& account, const Shared::Message& msg)
     }
 }
 
+void Application::unreadMessagesCountChanged(int count)
+{
+    QDBusMessage signal = QDBusMessage::createSignal("/", "com.canonical.Unity.LauncherEntry", "Update");
+    signal << qApp->desktopFileName() + QLatin1String(".desktop");
+    signal << QVariantMap ({
+        {"count-visible", count != 0},
+        {"count", count}
+    });
+    QDBusConnection::sessionBus().send(signal);
+}
+
+
 void Application::setState(Shared::Availability p_availability)
 {
     if (availability != p_availability) {
diff --git a/main/application.h b/main/application.h
index 15adce7..7d70877 100644
--- a/main/application.h
+++ b/main/application.h
@@ -65,6 +65,7 @@ public slots:
 
 protected slots:
     void notify(const QString& account, const Shared::Message& msg);
+    void unreadMessagesCountChanged(int count);
     void setState(Shared::Availability availability);
 
     void changeAccount(const QString& account, const QMap<QString, QVariant>& data);
diff --git a/main/main.cpp b/main/main.cpp
index 77719a2..60b3c83 100644
--- a/main/main.cpp
+++ b/main/main.cpp
@@ -50,6 +50,7 @@ int main(int argc, char *argv[])
     QApplication::setOrganizationName("macaw.me");
     QApplication::setApplicationDisplayName("Squawk");
     QApplication::setApplicationVersion("0.2.2");
+    app.setDesktopFileName("squawk");
 
     QTranslator qtTranslator;
     qtTranslator.load("qt_" + QLocale::system().name(), QLibraryInfo::location(QLibraryInfo::TranslationsPath));
diff --git a/ui/models/element.cpp b/ui/models/element.cpp
index 4e741a4..0c709ab 100644
--- a/ui/models/element.cpp
+++ b/ui/models/element.cpp
@@ -171,6 +171,7 @@ void Models::Element::fileError(const QString& messageId, const QString& error,
 
 void Models::Element::onFeedUnreadMessagesCountChanged()
 {
+    emit unreadMessagesCountChanged();
     if (type == contact) {
         changed(4);
     } else if (type == room) {
diff --git a/ui/models/element.h b/ui/models/element.h
index 94d67cb..dd90ea1 100644
--- a/ui/models/element.h
+++ b/ui/models/element.h
@@ -52,6 +52,7 @@ signals:
     void requestArchive(const QString& before);
     void fileDownloadRequest(const QString& url);
     void unnoticedMessage(const QString& account, const Shared::Message& msg);
+    void unreadMessagesCountChanged();
     void localPathInvalid(const QString& path);
     
 protected:
diff --git a/ui/models/roster.cpp b/ui/models/roster.cpp
index fef3e43..b2caf6b 100644
--- a/ui/models/roster.cpp
+++ b/ui/models/roster.cpp
@@ -463,6 +463,7 @@ void Models::Roster::addContact(const QString& account, const QString& jid, cons
             connect(contact, &Contact::fileDownloadRequest, this, &Roster::fileDownloadRequest);
             connect(contact, &Contact::unnoticedMessage, this, &Roster::unnoticedMessage);
             connect(contact, &Contact::localPathInvalid, this, &Roster::localPathInvalid);
+            connect(contact, &Contact::unreadMessagesCountChanged, this, &Roster::recalculateUnreadMessages);
             contacts.insert(std::make_pair(id, contact));
         } else {
             contact = itr->second;
@@ -805,6 +806,7 @@ void Models::Roster::addRoom(const QString& account, const QString jid, const QM
     connect(room, &Room::fileDownloadRequest, this, &Roster::fileDownloadRequest);
     connect(room, &Room::unnoticedMessage, this, &Roster::unnoticedMessage);
     connect(room, &Room::localPathInvalid, this, &Roster::localPathInvalid);
+    connect(room, &Room::unreadMessagesCountChanged, this, &Roster::recalculateUnreadMessages);
     rooms.insert(std::make_pair(id, room));
     acc->appendChild(room);
 }
@@ -1049,3 +1051,14 @@ void Models::Roster::onAccountReconnected()
     }
 }
 
+void Models::Roster::recalculateUnreadMessages()
+{
+    int count(0);
+    for (const std::pair<const ElId, Contact*>& pair : contacts) {
+        count += pair.second->getMessagesCount();
+    }
+    for (const std::pair<const ElId, Room*>& pair : rooms) {
+        count += pair.second->getMessagesCount();
+    }
+    emit unreadMessagesCountChanged(count);
+}
diff --git a/ui/models/roster.h b/ui/models/roster.h
index 60adf13..249947b 100644
--- a/ui/models/roster.h
+++ b/ui/models/roster.h
@@ -99,6 +99,7 @@ public:
 signals:
     void requestArchive(const QString& account, const QString& jid, const QString& before);
     void fileDownloadRequest(const QString& url);
+    void unreadMessagesCountChanged(int count);
     void unnoticedMessage(const QString& account, const Shared::Message& msg);
     void localPathInvalid(const QString& path);
     
@@ -113,6 +114,7 @@ private slots:
     void onChildIsAboutToBeMoved(Item* source, int first, int last, Item* destination, int newIndex);
     void onChildMoved();
     void onElementRequestArchive(const QString& before);
+    void recalculateUnreadMessages();
     
 private:
     Item* root;

From e58213b2943871f4013dedc4c8577fd373437270 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Sun, 24 Apr 2022 18:52:29 +0300
Subject: [PATCH 194/281] Now notifications have actions! Some more usefull
 functions to roster model

---
 CHANGELOG.md                           |  2 +
 main/application.cpp                   | 87 +++++++++++++++++++++++---
 main/application.h                     |  6 ++
 ui/models/contact.cpp                  | 10 +++
 ui/models/contact.h                    |  1 +
 ui/models/element.cpp                  |  5 ++
 ui/models/element.h                    |  1 +
 ui/models/room.cpp                     | 10 +++
 ui/models/room.h                       |  1 +
 ui/models/roster.cpp                   | 83 +++++++++++++++++++-----
 ui/models/roster.h                     |  4 +-
 ui/squawk.cpp                          |  5 ++
 ui/squawk.h                            |  1 +
 ui/widgets/messageline/messagefeed.cpp | 18 ++++--
 ui/widgets/messageline/messagefeed.h   |  1 +
 15 files changed, 205 insertions(+), 30 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4daf652..241d61d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -16,6 +16,8 @@
 ### New features
 - new "About" window with links, license, gratitudes
 - if the authentication failed Squawk will ask againg for your password and login
+- now there is an amount of unread messages showing on top of Squawk launcher icon
+- notifications now have buttons to open a conversation or to mark that message as read
 
 ## Squawk 0.2.1 (Apr 02, 2022)
 ### Bug fixes
diff --git a/main/application.cpp b/main/application.cpp
index 696ec03..ddf17b3 100644
--- a/main/application.cpp
+++ b/main/application.cpp
@@ -26,7 +26,8 @@ Application::Application(Core::Squawk* p_core):
     conversations(),
     dialogueQueue(roster),
     nowQuitting(false),
-    destroyingSquawk(false)
+    destroyingSquawk(false),
+    storage()
 {
     connect(&roster, &Models::Roster::unnoticedMessage, this, &Application::notify);
     connect(&roster, &Models::Roster::unreadMessagesCountChanged, this, &Application::unreadMessagesCountChanged);
@@ -90,6 +91,23 @@ Application::Application(Core::Squawk* p_core):
     connect(core, &Core::Squawk::requestPassword, this, &Application::requestPassword);
     connect(core, &Core::Squawk::ready, this, &Application::readSettings);
 
+    QDBusConnection sys = QDBusConnection::sessionBus();
+    sys.connect(
+        "org.freedesktop.Notifications",
+        "/org/freedesktop/Notifications",
+        "org.freedesktop.Notifications",
+        "NotificationClosed",
+        this,
+        SLOT(onNotificationClosed(quint32, quint32))
+    );
+    sys.connect(
+        "org.freedesktop.Notifications",
+        "/org/freedesktop/Notifications",
+        "org.freedesktop.Notifications",
+        "ActionInvoked",
+        this,
+        SLOT(onNotificationInvoked(quint32, const QString&))
+    );
 }
 
 Application::~Application() {}
@@ -188,12 +206,14 @@ void Application::onSquawkDestroyed() {
 
 void Application::notify(const QString& account, const Shared::Message& msg)
 {
-    QString name = QString(roster.getContactName(account, msg.getPenPalJid()));
-    QString path = QString(roster.getContactIconPath(account, msg.getPenPalJid(), msg.getPenPalResource()));
+    QString jid = msg.getPenPalJid();
+    QString name = QString(roster.getContactName(account, jid));
+    QString path = QString(roster.getContactIconPath(account, jid, msg.getPenPalResource()));
     QVariantList args;
     args << QString();
 
-    args << qHash(msg.getId());
+    uint32_t notificationId = qHash(msg.getId());
+    args << notificationId;
     if (path.size() > 0) {
         args << path;
     } else {
@@ -212,7 +232,10 @@ void Application::notify(const QString& account, const Shared::Message& msg)
     }
 
     args << body;
-    args << QStringList();
+    args << QStringList({
+        "markAsRead", tr("Mark as Read"),
+        "openConversation", tr("Open conversation")
+    });
     args << QVariantMap({
         {"desktop-entry", qApp->desktopFileName()},
         {"category", QString("message")},
@@ -222,11 +245,40 @@ void Application::notify(const QString& account, const Shared::Message& msg)
     args << -1;
     notifications.callWithArgumentList(QDBus::AutoDetect, "Notify", args);
 
+    storage.insert(std::make_pair(notificationId, std::make_pair(Models::Roster::ElId(account, name), msg.getId())));
+
     if (squawk != nullptr) {
         QApplication::alert(squawk);
     }
 }
 
+void Application::onNotificationClosed(quint32 id, quint32 reason)
+{
+    Notifications::const_iterator itr = storage.find(id);
+    if (itr != storage.end()) {
+        if (reason == 2) {  //dissmissed by user (https://specifications.freedesktop.org/notification-spec/latest/ar01s09.html)
+            //TODO may ba also mark as read?
+        }
+        if (reason != 1) {  //just expired, can be activated again from history, so no removing for now
+            storage.erase(id);
+            qDebug() << "Notification" << id << "was closed";
+        }
+    }
+}
+
+void Application::onNotificationInvoked(quint32 id, const QString& action)
+{
+    qDebug() << "Notification" << id << action << "request";
+    Notifications::const_iterator itr = storage.find(id);
+    if (itr != storage.end()) {
+        if (action == "markAsRead") {
+            roster.markMessageAsRead(itr->second.first, itr->second.second);
+        } else if (action == "openConversation") {
+            focusConversation(itr->second.first, "", itr->second.second);
+        }
+    }
+}
+
 void Application::unreadMessagesCountChanged(int count)
 {
     QDBusMessage signal = QDBusMessage::createSignal("/", "com.canonical.Unity.LauncherEntry", "Update");
@@ -238,6 +290,27 @@ void Application::unreadMessagesCountChanged(int count)
     QDBusConnection::sessionBus().send(signal);
 }
 
+void Application::focusConversation(const Models::Roster::ElId& id, const QString& resource, const QString& messageId)
+{
+    if (squawk != nullptr) {
+        if (squawk->currentConversationId() != id) {
+            QModelIndex index = roster.getContactIndex(id.account, id.name, resource);
+            squawk->select(index);
+        }
+
+        if (squawk->isMinimized()) {
+            squawk->showNormal();
+        } else {
+            squawk->show();
+        }
+        squawk->raise();
+        squawk->activateWindow();
+    } else {
+        openConversation(id, resource);
+    }
+
+    //TODO focus messageId;
+}
 
 void Application::setState(Shared::Availability p_availability)
 {
@@ -343,7 +416,7 @@ void Application::openConversation(const Models::Roster::ElId& id, const QString
         conv = itr->second;
     } else {
         Models::Element* el = roster.getElement(id);
-        if (el != NULL) {
+        if (el != nullptr) {
             if (el->type == Models::Item::room) {
                 created = true;
                 Models::Room* room = static_cast<Models::Room*>(el);
@@ -409,7 +482,7 @@ void Application::onSquawkOpenedConversation() {
     Models::Roster::ElId id = squawk->currentConversationId();
 
     const Models::Element* el = roster.getElementConst(id);
-    if (el != NULL && el->isRoom() && !static_cast<const Models::Room*>(el)->getJoined()) {
+    if (el != nullptr && el->isRoom() && !static_cast<const Models::Room*>(el)->getJoined()) {
         emit setRoomJoined(id.account, id.name, true);
     }
 }
diff --git a/main/application.h b/main/application.h
index 7d70877..301edc4 100644
--- a/main/application.h
+++ b/main/application.h
@@ -89,14 +89,19 @@ private slots:
     void stateChanged(Shared::Availability state);
     void onSquawkClosing();
     void onSquawkDestroyed();
+    void onNotificationClosed(quint32 id, quint32 reason);
+    void onNotificationInvoked(quint32 id, const QString& action);
+
 
 private:
     void createMainWindow();
     void subscribeConversation(Conversation* conv);
     void checkForTheLastWindow();
+    void focusConversation(const Models::Roster::ElId& id, const QString& resource = "", const QString& messageId = "");
 
 private:
     typedef std::map<Models::Roster::ElId, Conversation*> Conversations;
+    typedef std::map<uint32_t, std::pair<Models::Roster::ElId, QString>> Notifications;
 
     Shared::Availability availability;
     Core::Squawk* core;
@@ -107,6 +112,7 @@ private:
     DialogQueue dialogueQueue;
     bool nowQuitting;
     bool destroyingSquawk;
+    Notifications storage;
 };
 
 #endif // APPLICATION_H
diff --git a/ui/models/contact.cpp b/ui/models/contact.cpp
index a0c70ac..d5c7dc4 100644
--- a/ui/models/contact.cpp
+++ b/ui/models/contact.cpp
@@ -155,6 +155,16 @@ void Models::Contact::removePresence(const QString& name)
     }
 }
 
+Models::Presence * Models::Contact::getPresence(const QString& name)
+{
+    QMap<QString, Presence*>::iterator itr = presences.find(name);
+    if (itr == presences.end()) {
+        return nullptr;
+    } else {
+        return itr.value();
+    }
+}
+
 void Models::Contact::refresh()
 {
     QDateTime lastActivity;
diff --git a/ui/models/contact.h b/ui/models/contact.h
index a8b80a3..c4fc131 100644
--- a/ui/models/contact.h
+++ b/ui/models/contact.h
@@ -51,6 +51,7 @@ public:
     
     void addPresence(const QString& name, const QMap<QString, QVariant>& data);
     void removePresence(const QString& name);
+    Presence* getPresence(const QString& name);
     
     QString getContactName() const;
     QString getStatus() const;
diff --git a/ui/models/element.cpp b/ui/models/element.cpp
index 0c709ab..acea46f 100644
--- a/ui/models/element.cpp
+++ b/ui/models/element.cpp
@@ -134,6 +134,11 @@ unsigned int Models::Element::getMessagesCount() const
     return feed->unreadMessagesCount();
 }
 
+bool Models::Element::markMessageAsRead(const QString& id) const
+{
+    return feed->markMessageAsRead(id);
+}
+
 void Models::Element::addMessage(const Shared::Message& data)
 {
     feed->addMessage(data);
diff --git a/ui/models/element.h b/ui/models/element.h
index dd90ea1..c6d3d6e 100644
--- a/ui/models/element.h
+++ b/ui/models/element.h
@@ -42,6 +42,7 @@ public:
     void addMessage(const Shared::Message& data);
     void changeMessage(const QString& id, const QMap<QString, QVariant>& data);
     unsigned int getMessagesCount() const;
+    bool markMessageAsRead(const QString& id) const;
     void responseArchive(const std::list<Shared::Message> list, bool last);
     bool isRoom() const;
     void fileProgress(const QString& messageId, qreal value, bool up);
diff --git a/ui/models/room.cpp b/ui/models/room.cpp
index a6a36d0..4aaa07e 100644
--- a/ui/models/room.cpp
+++ b/ui/models/room.cpp
@@ -264,6 +264,16 @@ void Models::Room::removeParticipant(const QString& p_name)
     }
 }
 
+Models::Participant * Models::Room::getParticipant(const QString& p_name)
+{
+    std::map<QString, Participant*>::const_iterator itr = participants.find(p_name);
+    if (itr == participants.end()) {
+        return nullptr;
+    } else {
+        return itr->second;
+    }
+}
+
 void Models::Room::handleParticipantUpdate(std::map<QString, Participant*>::const_iterator itr, const QMap<QString, QVariant>& data)
 {
     Participant* part = itr->second;
diff --git a/ui/models/room.h b/ui/models/room.h
index a51a537..707b35b 100644
--- a/ui/models/room.h
+++ b/ui/models/room.h
@@ -58,6 +58,7 @@ public:
     void addParticipant(const QString& name, const QMap<QString, QVariant>& data);
     void changeParticipant(const QString& name, const QMap<QString, QVariant>& data);
     void removeParticipant(const QString& name);
+    Participant* getParticipant(const QString& name);
     
     void toOfflineState() override;
     QString getDisplayedName() const override;
diff --git a/ui/models/roster.cpp b/ui/models/roster.cpp
index b2caf6b..fbb7e52 100644
--- a/ui/models/roster.cpp
+++ b/ui/models/roster.cpp
@@ -549,8 +549,8 @@ void Models::Roster::removeGroup(const QString& account, const QString& name)
 
 void Models::Roster::changeContact(const QString& account, const QString& jid, const QMap<QString, QVariant>& data)
 {
-    Element* el = getElement({account, jid});
-    if (el != NULL) {
+    Element* el = getElement(ElId(account, jid));
+    if (el != nullptr) {
         for (QMap<QString, QVariant>::const_iterator itr = data.begin(), end = data.end(); itr != end; ++itr) {
             el->update(itr.key(), itr.value());
         }
@@ -559,8 +559,8 @@ void Models::Roster::changeContact(const QString& account, const QString& jid, c
 
 void Models::Roster::changeMessage(const QString& account, const QString& jid, const QString& id, const QMap<QString, QVariant>& data)
 {
-    Element* el = getElement({account, jid});
-    if (el != NULL) {
+    Element* el = getElement(ElId(account, jid));
+    if (el != nullptr) {
         el->changeMessage(id, data);
     } else {
         qDebug() << "A request to change a message of the contact " << jid << " in the account " << account << " but it wasn't found";
@@ -707,8 +707,8 @@ void Models::Roster::removePresence(const QString& account, const QString& jid,
 
 void Models::Roster::addMessage(const QString& account, const Shared::Message& data)
 {
-    Element* el = getElement({account, data.getPenPalJid()});
-    if (el != NULL) {
+    Element* el = getElement(ElId(account, data.getPenPalJid()));
+    if (el != nullptr) {
         el->addMessage(data);
     }
 }
@@ -948,9 +948,18 @@ const Models::Element * Models::Roster::getElementConst(const Models::Roster::El
         }
     }
 
-    return NULL;
+    return nullptr;
 }
 
+bool Models::Roster::markMessageAsRead(const Models::Roster::ElId& elementId, const QString& messageId)
+{
+    const Element* el = getElementConst(elementId);
+    if (el != nullptr) {
+        return el->markMessageAsRead(messageId);
+    } else {
+        return false;
+    }
+}
 
 QModelIndex Models::Roster::getAccountIndex(const QString& name)
 {
@@ -968,7 +977,7 @@ QModelIndex Models::Roster::getGroupIndex(const QString& account, const QString&
     if (itr == accounts.end()) {
         return QModelIndex();
     } else {
-        std::map<ElId, Group*>::const_iterator gItr = groups.find({account, name});
+        std::map<ElId, Group*>::const_iterator gItr = groups.find(ElId(account, name));
         if (gItr == groups.end()) {
             return QModelIndex();
         } else {
@@ -978,6 +987,48 @@ QModelIndex Models::Roster::getGroupIndex(const QString& account, const QString&
     }
 }
 
+QModelIndex Models::Roster::getContactIndex(const QString& account, const QString& jid, const QString& resource)
+{
+    std::map<QString, Account*>::const_iterator itr = accounts.find(account);
+    if (itr == accounts.end()) {
+        return QModelIndex();
+    } else {
+        Account* acc = itr->second;
+        QModelIndex accIndex = index(acc->row(), 0, QModelIndex());
+        std::map<ElId, Contact*>::const_iterator cItr = contacts.find(ElId(account, jid));
+        if (cItr != contacts.end()) {
+            QModelIndex contactIndex = index(acc->getContact(jid), 0, accIndex);
+            if (resource.size() == 0) {
+                return contactIndex;
+            } else {
+                Presence* pres = cItr->second->getPresence(resource);
+                if (pres != nullptr) {
+                    return index(pres->row(), 0, contactIndex);
+                } else {
+                    return contactIndex;
+                }
+            }
+        } else {
+            std::map<ElId, Room*>::const_iterator rItr = rooms.find(ElId(account, jid));
+            if (rItr != rooms.end()) {
+                QModelIndex roomIndex = index(rItr->second->row(), 0, accIndex);
+                if (resource.size() == 0) {
+                    return roomIndex;
+                } else {
+                    Participant* part = rItr->second->getParticipant(resource);
+                    if (part != nullptr) {
+                        return index(part->row(), 0, roomIndex);
+                    } else {
+                        return roomIndex;
+                    }
+                }
+            } else {
+                return QModelIndex();
+            }
+        }
+    }
+}
+
 void Models::Roster::onElementRequestArchive(const QString& before)
 {
     Element* el = static_cast<Element*>(sender());
@@ -988,7 +1039,7 @@ void Models::Roster::responseArchive(const QString& account, const QString& jid,
 {
     ElId id(account, jid);
     Element* el = getElement(id);
-    if (el != NULL) {
+    if (el != nullptr) {
         el->responseArchive(list, last);
     }
 }
@@ -996,8 +1047,8 @@ void Models::Roster::responseArchive(const QString& account, const QString& jid,
 void Models::Roster::fileProgress(const std::list<Shared::MessageInfo>& msgs, qreal value, bool up)
 {
     for (const Shared::MessageInfo& info : msgs) {
-        Element* el = getElement({info.account, info.jid});
-        if (el != NULL) {
+        Element* el = getElement(ElId(info.account, info.jid));
+        if (el != nullptr) {
             el->fileProgress(info.messageId, value, up);
         }
     }
@@ -1006,8 +1057,8 @@ void Models::Roster::fileProgress(const std::list<Shared::MessageInfo>& msgs, qr
 void Models::Roster::fileComplete(const std::list<Shared::MessageInfo>& msgs, bool up)
 {
     for (const Shared::MessageInfo& info : msgs) {
-        Element* el = getElement({info.account, info.jid});
-        if (el != NULL) {
+        Element* el = getElement(ElId(info.account, info.jid));
+        if (el != nullptr) {
             el->fileComplete(info.messageId, up);
         }
     }
@@ -1016,8 +1067,8 @@ void Models::Roster::fileComplete(const std::list<Shared::MessageInfo>& msgs, bo
 void Models::Roster::fileError(const std::list<Shared::MessageInfo>& msgs, const QString& err, bool up)
 {
     for (const Shared::MessageInfo& info : msgs) {
-        Element* el = getElement({info.account, info.jid});
-        if (el != NULL) {
+        Element* el = getElement(ElId(info.account, info.jid));
+        if (el != nullptr) {
             el->fileError(info.messageId, err, up);
         }
     }
@@ -1031,7 +1082,7 @@ Models::Element * Models::Roster::getElement(const Models::Roster::ElId& id)
 Models::Item::Type Models::Roster::getContactType(const Models::Roster::ElId& id) const
 {
     const Models::Element* el = getElementConst(id);
-    if (el == NULL) {
+    if (el == nullptr) {
         return Item::root;
     }
 
diff --git a/ui/models/roster.h b/ui/models/roster.h
index 249947b..efc50f2 100644
--- a/ui/models/roster.h
+++ b/ui/models/roster.h
@@ -88,6 +88,8 @@ public:
     const Account* getAccountConst(const QString& name) const;
     QModelIndex getAccountIndex(const QString& name);
     QModelIndex getGroupIndex(const QString& account, const QString& name);
+    QModelIndex getContactIndex(const QString& account, const QString& jid, const QString& resource = "");
+    bool markMessageAsRead(const ElId& elementId, const QString& messageId);
     void responseArchive(const QString& account, const QString& jid, const std::list<Shared::Message>& list, bool last);
     
     void fileProgress(const std::list<Shared::MessageInfo>& msgs, qreal value, bool up);
@@ -115,7 +117,7 @@ private slots:
     void onChildMoved();
     void onElementRequestArchive(const QString& before);
     void recalculateUnreadMessages();
-    
+
 private:
     Item* root;
     std::map<QString, Account*> accounts;
diff --git a/ui/squawk.cpp b/ui/squawk.cpp
index 434b442..9b6158c 100644
--- a/ui/squawk.cpp
+++ b/ui/squawk.cpp
@@ -656,3 +656,8 @@ Models::Roster::ElId Squawk::currentConversationId() const
     }
 }
 
+void Squawk::select(QModelIndex index)
+{
+    m_ui->roster->scrollTo(index, QAbstractItemView::EnsureVisible);
+    m_ui->roster->selectionModel()->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect);
+}
diff --git a/ui/squawk.h b/ui/squawk.h
index 5ffe090..15a73dd 100644
--- a/ui/squawk.h
+++ b/ui/squawk.h
@@ -90,6 +90,7 @@ public slots:
     void writeSettings();
     void stateChanged(Shared::Availability state);
     void responseVCard(const QString& jid, const Shared::VCard& card);
+    void select(QModelIndex index);
     
 private:
     QScopedPointer<Ui::Squawk> m_ui;
diff --git a/ui/widgets/messageline/messagefeed.cpp b/ui/widgets/messageline/messagefeed.cpp
index 521e981..ad67bb3 100644
--- a/ui/widgets/messageline/messagefeed.cpp
+++ b/ui/widgets/messageline/messagefeed.cpp
@@ -318,12 +318,7 @@ QVariant Models::MessageFeed::data(const QModelIndex& index, int role) const
             case Bulk: {
                 FeedItem item;
                 item.id = msg->getId();
-                
-                std::set<QString>::const_iterator umi = unreadMessages->find(item.id);
-                if (umi != unreadMessages->end()) {
-                    unreadMessages->erase(umi);
-                    emit unreadMessagesCountChanged();
-                }
+                markMessageAsRead(item.id);
                 
                 item.sentByMe = sentByMe(*msg);
                 item.date = msg->getTime();
@@ -373,6 +368,17 @@ int Models::MessageFeed::rowCount(const QModelIndex& parent) const
     return storage.size();
 }
 
+bool Models::MessageFeed::markMessageAsRead(const QString& id) const
+{
+    std::set<QString>::const_iterator umi = unreadMessages->find(id);
+    if (umi != unreadMessages->end()) {
+        unreadMessages->erase(umi);
+        emit unreadMessagesCountChanged();
+        return true;
+    }
+    return false;
+}
+
 unsigned int Models::MessageFeed::unreadMessagesCount() const
 {
     return unreadMessages->size();
diff --git a/ui/widgets/messageline/messagefeed.h b/ui/widgets/messageline/messagefeed.h
index c9701ae..f362989 100644
--- a/ui/widgets/messageline/messagefeed.h
+++ b/ui/widgets/messageline/messagefeed.h
@@ -72,6 +72,7 @@ public:
     void reportLocalPathInvalid(const QString& messageId);
     
     unsigned int unreadMessagesCount() const;
+    bool markMessageAsRead(const QString& id) const;
     void fileProgress(const QString& messageId, qreal value, bool up);
     void fileError(const QString& messageId, const QString& error, bool up);
     void fileComplete(const QString& messageId, bool up);

From 2fcc432aef6e1656ffff967aad7bbcbb9c2e2cfd Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Tue, 26 Apr 2022 23:08:25 +0300
Subject: [PATCH 195/281] some polish

---
 main/application.cpp | 3 ++-
 shared/utils.cpp     | 2 +-
 2 files changed, 3 insertions(+), 2 deletions(-)

diff --git a/main/application.cpp b/main/application.cpp
index ddf17b3..410cd81 100644
--- a/main/application.cpp
+++ b/main/application.cpp
@@ -220,7 +220,7 @@ void Application::notify(const QString& account, const Shared::Message& msg)
         args << QString("mail-message");    //TODO should here better be unknown user icon?
     }
     if (msg.getType() == Shared::Message::groupChat) {
-        args << msg.getFromResource() + " from " + name;
+        args << msg.getFromResource() + tr(" from ") + name;
     } else {
         args << name;
     }
@@ -239,6 +239,7 @@ void Application::notify(const QString& account, const Shared::Message& msg)
     args << QVariantMap({
         {"desktop-entry", qApp->desktopFileName()},
         {"category", QString("message")},
+        {"urgency", 1},
        // {"sound-file", "/path/to/macaw/squawk"},
         {"sound-name", QString("message-new-instant")}
     });
diff --git a/shared/utils.cpp b/shared/utils.cpp
index a7a4ecb..fdaf9a4 100644
--- a/shared/utils.cpp
+++ b/shared/utils.cpp
@@ -40,5 +40,5 @@ QString Shared::processMessageBody(const QString& msg)
 {
     QString processed = msg.toHtmlEscaped();
     processed.replace(urlReg, "<a href=\"\\1\">\\1</a>");
-    return "<p style=\"white-space: pre-wrap;\">" + processed + "</p>";
+    return processed;
 }

From d86e2c28a02955135759fe3835676c1a16b4396f Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Wed, 27 Apr 2022 01:17:53 +0300
Subject: [PATCH 196/281] an attempt to display text in a better way with
 QTextDocument + QTextBrowser

---
 shared/utils.cpp                           |   2 +-
 ui/utils/CMakeLists.txt                    |   2 -
 ui/utils/textmeter.cpp                     | 233 ---------------------
 ui/utils/textmeter.h                       |  68 ------
 ui/widgets/messageline/feedview.cpp        |   6 +-
 ui/widgets/messageline/messagedelegate.cpp | 104 ++++++---
 ui/widgets/messageline/messagedelegate.h   |  11 +-
 7 files changed, 88 insertions(+), 338 deletions(-)
 delete mode 100644 ui/utils/textmeter.cpp
 delete mode 100644 ui/utils/textmeter.h

diff --git a/shared/utils.cpp b/shared/utils.cpp
index a7a4ecb..518d288 100644
--- a/shared/utils.cpp
+++ b/shared/utils.cpp
@@ -40,5 +40,5 @@ QString Shared::processMessageBody(const QString& msg)
 {
     QString processed = msg.toHtmlEscaped();
     processed.replace(urlReg, "<a href=\"\\1\">\\1</a>");
-    return "<p style=\"white-space: pre-wrap;\">" + processed + "</p>";
+    return "<p style=\"white-space: pre-wrap; line-height: 1em;\">" + processed + "</p>";
 }
diff --git a/ui/utils/CMakeLists.txt b/ui/utils/CMakeLists.txt
index 823287d..b46d30d 100644
--- a/ui/utils/CMakeLists.txt
+++ b/ui/utils/CMakeLists.txt
@@ -15,6 +15,4 @@ target_sources(squawk PRIVATE
   resizer.h
   shadowoverlay.cpp
   shadowoverlay.h
-  textmeter.cpp
-  textmeter.h
   )
diff --git a/ui/utils/textmeter.cpp b/ui/utils/textmeter.cpp
deleted file mode 100644
index 51c6d54..0000000
--- a/ui/utils/textmeter.cpp
+++ /dev/null
@@ -1,233 +0,0 @@
-// Squawk messenger. 
-// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
-// 
-// 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 "textmeter.h"
-#include <QDebug>
-#include <QApplication>
-
-TextMeter::TextMeter():
-    base(),
-    fonts()
-{
-}
-
-TextMeter::~TextMeter()
-{
-}
-
-void TextMeter::initializeFonts(const QFont& font)
-{
-    fonts.clear();
-    QList<QFontDatabase::WritingSystem> supported = base.writingSystems(font.family());
-    std::set<QFontDatabase::WritingSystem> sup;
-    std::set<QString> added({font.family()});
-    for (const QFontDatabase::WritingSystem& system : supported) {
-        sup.insert(system);
-    }
-    fonts.push_back(QFontMetrics(font));
-    QString style = base.styleString(font);
-
-    QList<QFontDatabase::WritingSystem> systems = base.writingSystems();
-    for (const QFontDatabase::WritingSystem& system : systems) {
-        if (sup.count(system) == 0) {
-            QStringList families = base.families(system);
-            if (!families.empty() && added.count(families.first()) == 0) {
-                QString family(families.first());
-                QFont nfont = base.font(family, style, font.pointSize());
-                if (added.count(nfont.family()) == 0) {
-                    added.insert(family);
-                    fonts.push_back(QFontMetrics(nfont));
-                    qDebug() << "Added font" << nfont.family() << "for" << system;
-                }
-            }
-        }
-    }
-}
-
-QSize TextMeter::boundingSize(const QString& text, const QSize& limits) const
-{
-//     QString str("ridiculus mus. Suspendisse potenti. Cras pretium venenatis enim, faucibus accumsan ex");
-//     bool first = true;
-//     int width = 0;
-//     QStringList list = str.split(" ");
-//     QFontMetrics m = fonts.front();
-//     for (const QString& word : list) {
-//         if (first) {
-//             first = false;
-//         } else {
-//             width += m.horizontalAdvance(QChar::Space);
-//         }
-//         width += m.horizontalAdvance(word);
-//         for (const QChar& ch : word) {
-//             width += m.horizontalAdvance(ch);
-//         }
-//     }
-//     qDebug() << "together:" << m.horizontalAdvance(str);
-//     qDebug() << "apart:" << width;
-//     I cant measure or wrap text this way, this simple example shows that even this gives differen result
-//     The Qt implementation under it is thousands and thousands lines of code in QTextEngine
-//     I simply can't get though it
-
-    if (text.size() == 0) {
-        return QSize (0, 0);
-    }
-    Helper current(limits.width());
-    for (const QChar& ch : text) {
-        if (newLine(ch)) {
-            current.computeNewWord();
-            if (current.height == 0) {
-                current.height = fonts.front().lineSpacing();
-            }
-            current.beginNewLine();
-        } else if (visible(ch)) {
-            bool found = false;
-            for (const QFontMetrics& metrics : fonts) {
-                if (metrics.inFont(ch)) {
-                    current.computeChar(ch, metrics);
-                    found = true;
-                    break;
-                }
-            }
-
-            if (!found) {
-                current.computeChar(ch, fonts.front());
-            }
-        }
-    }
-    current.computeNewWord();
-    current.beginNewLine();
-
-    int& height = current.size.rheight();
-    if (height > 0) {
-        height -= fonts.front().leading();
-    }
-
-    return current.size;
-}
-
-void TextMeter::Helper::computeChar(const QChar& ch, const QFontMetrics& metrics)
-{
-    int ha = metrics.horizontalAdvance(ch);
-    if (newWord(ch)) {
-        if (printOnLineBreak(ch)) {
-            if (!lineOverflow(metrics, ha, ch)){
-                computeNewWord();
-            }
-        } else {
-            computeNewWord();
-            delayedSpaceWidth = ha;
-            lastSpace = ch;
-        }
-    } else {
-        lineOverflow(metrics, ha, ch);
-    }
-}
-
-void TextMeter::Helper::computeNewLine(const QFontMetrics& metrics, int horizontalAdvance, const QChar& ch)
-{
-    if (wordBeganWithTheLine) {
-        text = word.chopped(1);
-        width = wordWidth - horizontalAdvance;
-        height = wordHeight;
-    }
-    if (width != metrics.horizontalAdvance(text)) {
-        qDebug() << "Kerning Error" << width - metrics.horizontalAdvance(text);
-    }
-    beginNewLine();
-    if (wordBeganWithTheLine) {
-        word = ch;
-        wordWidth = horizontalAdvance;
-        wordHeight = metrics.lineSpacing();
-    }
-
-    wordBeganWithTheLine = true;
-    delayedSpaceWidth = 0;
-    lastSpace = QChar::Null;
-}
-
-void TextMeter::Helper::beginNewLine()
-{
-    size.rheight() += height;
-    size.rwidth() = qMax(size.width(), width);
-    qDebug() << text;
-    text = "";
-    width = 0;
-    height = 0;
-}
-
-void TextMeter::Helper::computeNewWord()
-{
-    width += wordWidth + delayedSpaceWidth;
-    height = qMax(height, wordHeight);
-    if (lastSpace != QChar::Null) {
-        text += lastSpace;
-    }
-    text += word;
-    word = "";
-    wordWidth = 0;
-    wordHeight = 0;
-    delayedSpaceWidth = 0;
-    lastSpace = QChar::Null;
-    wordBeganWithTheLine = false;
-}
-
-bool TextMeter::Helper::lineOverflow(const QFontMetrics& metrics, int horizontalAdvance, const QChar& ch)
-{
-    wordHeight = qMax(wordHeight, metrics.lineSpacing());
-    wordWidth += horizontalAdvance;
-    word += ch;
-    if (width + delayedSpaceWidth + wordWidth > maxWidth) {
-        computeNewLine(metrics, horizontalAdvance, ch);
-        return true;
-    }
-    return false;
-}
-
-
-bool TextMeter::newLine(const QChar& ch)
-{
-    return ch == QChar::LineFeed;
-}
-
-bool TextMeter::newWord(const QChar& ch)
-{
-    return ch.isSpace() || ch.isPunct();
-}
-
-bool TextMeter::printOnLineBreak(const QChar& ch)
-{
-    return ch != QChar::Space;
-}
-
-bool TextMeter::visible(const QChar& ch)
-{
-    return true;
-}
-
-TextMeter::Helper::Helper(int p_maxWidth):
-    width(0),
-    height(0),
-    wordWidth(0),
-    wordHeight(0),
-    delayedSpaceWidth(0),
-    maxWidth(p_maxWidth),
-    wordBeganWithTheLine(true),
-    text(""),
-    word(""),
-    lastSpace(QChar::Null),
-    size(0, 0)
-{
-}
diff --git a/ui/utils/textmeter.h b/ui/utils/textmeter.h
deleted file mode 100644
index 243d989..0000000
--- a/ui/utils/textmeter.h
+++ /dev/null
@@ -1,68 +0,0 @@
-// Squawk messenger. 
-// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
-// 
-// 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/>.
-
-#ifndef TEXTMETER_H
-#define TEXTMETER_H
-
-#include <list>
-#include <set>
-
-#include <QFontMetrics>
-#include <QFont>
-#include <QSize>
-#include <QFontDatabase>
-
-class TextMeter
-{
-public:
-    TextMeter();
-    ~TextMeter();
-    void initializeFonts(const QFont& font);
-    QSize boundingSize(const QString& text, const QSize& limits) const;
-
-private:
-    QFontDatabase base;
-    std::list<QFontMetrics> fonts;
-
-    struct Helper {
-        Helper(int maxWidth);
-        int width;
-        int height;
-        int wordWidth;
-        int wordHeight;
-        int delayedSpaceWidth;
-        int maxWidth;
-        bool wordBeganWithTheLine;
-        QString text;
-        QString word;
-        QChar lastSpace;
-        QSize size;
-
-        void computeNewLine(const QFontMetrics& metrics, int horizontalAdvance, const QChar& ch);
-        void computeChar(const QChar& ch, const QFontMetrics& metrics);
-        void computeNewWord();
-        void beginNewLine();
-        bool lineOverflow(const QFontMetrics& metrics, int horizontalAdvance, const QChar& ch);
-    };
-
-    static bool newLine(const QChar& ch);
-    static bool newWord(const QChar& ch);
-    static bool visible(const QChar& ch);
-    static bool printOnLineBreak(const QChar& ch);
-
-};
-
-#endif // TEXTMETER_H
diff --git a/ui/widgets/messageline/feedview.cpp b/ui/widgets/messageline/feedview.cpp
index de7f56f..e0c1477 100644
--- a/ui/widgets/messageline/feedview.cpp
+++ b/ui/widgets/messageline/feedview.cpp
@@ -343,6 +343,7 @@ void FeedView::paintEvent(QPaintEvent* event)
     
     QDateTime lastDate;
     bool first = true;
+    QRect viewportRect = vp->rect();
     for (const QModelIndex& index : toRener) {
         QDateTime currentDate = index.data(Models::MessageFeed::Date).toDateTime();
         option.rect = visualRect(index);
@@ -356,7 +357,10 @@ void FeedView::paintEvent(QPaintEvent* event)
             }
             first = false;
         }
-        bool mouseOver = option.rect.contains(cursor) && vp->rect().contains(cursor);
+        QRect stripe = option.rect;
+        stripe.setLeft(0);
+        stripe.setWidth(viewportRect.width());
+        bool mouseOver = stripe.contains(cursor) && viewportRect.contains(cursor);
         option.state.setFlag(QStyle::State_MouseOver, mouseOver);
         itemDelegate(index)->paint(&painter, option, index);
 
diff --git a/ui/widgets/messageline/messagedelegate.cpp b/ui/widgets/messageline/messagedelegate.cpp
index 4ddecee..1d094fa 100644
--- a/ui/widgets/messageline/messagedelegate.cpp
+++ b/ui/widgets/messageline/messagedelegate.cpp
@@ -22,6 +22,7 @@
 #include <QApplication>
 #include <QMouseEvent>
 #include <QAbstractItemView>
+#include <QtMath>
 
 #include "messagedelegate.h"
 #include "messagefeed.h"
@@ -42,7 +43,7 @@ MessageDelegate::MessageDelegate(QObject* parent):
     nickFont(),
     dateFont(),
     bodyMetrics(bodyFont),
-    bodyMeter(),
+    bodyRenderer(new QTextDocument()),
     nickMetrics(nickFont),
     dateMetrics(dateFont),
     buttonHeight(0),
@@ -52,11 +53,13 @@ MessageDelegate::MessageDelegate(QObject* parent):
     bars(new std::map<QString, QProgressBar*>()),
     statusIcons(new std::map<QString, QLabel*>()),
     pencilIcons(new std::map<QString, QLabel*>()),
-    bodies(new std::map<QString, QLabel*>()),
+    bodies(new std::map<QString, QTextBrowser*>()),
     previews(new std::map<QString, Preview*>()),
     idsToKeep(new std::set<QString>()),
     clearingWidgets(false)
 {
+    bodyRenderer->setDocumentMargin(0);
+
     QPushButton btn(QCoreApplication::translate("MessageLine", "Download"));
     buttonHeight = btn.sizeHint().height();
     buttonWidth = btn.sizeHint().width();
@@ -83,7 +86,7 @@ MessageDelegate::~MessageDelegate()
         delete pair.second;
     }
     
-    for (const std::pair<const QString, QLabel*>& pair: *bodies){
+    for (const std::pair<const QString, QTextBrowser*>& pair: *bodies){
         delete pair.second;
     }
     
@@ -98,6 +101,7 @@ MessageDelegate::~MessageDelegate()
     delete bars;
     delete bodies;
     delete previews;
+    delete bodyRenderer;
 }
 
 void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
@@ -124,8 +128,6 @@ void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio
         opt.displayAlignment = Qt::AlignRight | Qt::AlignTop;
     }
 
-    QSize bodySize = bodyMeter.boundingSize(data.text, opt.rect.size());
-
     QRect rect;
     if (ntds) {
         painter->setFont(nickFont);
@@ -168,15 +170,7 @@ void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio
     painter->restore();
     
     QWidget* vp = static_cast<QWidget*>(painter->device());
-    if (data.text.size() > 0) {
-        QLabel* body = getBody(data);
-        body->setParent(vp);
-        body->setMinimumSize(bodySize);
-        body->setMaximumSize(bodySize);
-        body->move(opt.rect.left(), opt.rect.y());
-        body->show();
-        opt.rect.adjust(0, bodySize.height() + textMargin, 0, 0);
-    }
+    paintBody(data, painter, opt);
     painter->setFont(dateFont);
     QColor q = painter->pen().color();
     QString dateString = data.date.toLocalTime().toString("hh:mm");
@@ -304,7 +298,12 @@ QSize MessageDelegate::sizeHint(const QStyleOptionViewItem& option, const QModel
     Models::FeedItem data = qvariant_cast<Models::FeedItem>(vi);
     QSize messageSize(0, 0);
     if (data.text.size() > 0) {
-        messageSize = bodyMeter.boundingSize(data.text, messageRect.size());
+        bodyRenderer->setPlainText(data.text);
+        bodyRenderer->setTextWidth(messageRect.size().width());
+
+        QSizeF size = bodyRenderer->size();
+        size.setWidth(bodyRenderer->idealWidth());
+        messageSize = QSize(qCeil(size.width()), qCeil(size.height()));
         messageSize.rheight() += textMargin;
     }
     
@@ -393,7 +392,7 @@ void MessageDelegate::initializeFonts(const QFont& font)
     nickMetrics = QFontMetrics(nickFont);
     dateMetrics = QFontMetrics(dateFont);
 
-    bodyMeter.initializeFonts(bodyFont);
+    bodyRenderer->setDefaultFont(bodyFont);
     
     Preview::initializeFont(bodyFont);
 }
@@ -573,34 +572,36 @@ QLabel * MessageDelegate::getPencilIcon(const Models::FeedItem& data) const
     return result;
 }
 
-QLabel * MessageDelegate::getBody(const Models::FeedItem& data) const
+QTextBrowser * MessageDelegate::getBody(const Models::FeedItem& data) const
 {
-    std::map<QString, QLabel*>::const_iterator itr = bodies->find(data.id);
-    QLabel* result = 0;
+    std::map<QString, QTextBrowser*>::const_iterator itr = bodies->find(data.id);
+    QTextBrowser* result = 0;
     
     if (itr != bodies->end()) {
         result = itr->second;
     } else {
-        result = new QLabel();
+        result = new QTextBrowser();
         result->setFont(bodyFont);
         result->setContextMenuPolicy(Qt::NoContextMenu);
-        result->setWordWrap(true);
+        result->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+        result->setContentsMargins(0, 0, 0, 0);
+        //result->viewport()->setAutoFillBackground(false);
+        result->document()->setDocumentMargin(0);
+        result->setFrameStyle(0);
+        result->setLineWidth(0);
+        //result->setAutoFillBackground(false);
+        //->setWordWrap(true);
+        result->setOpenExternalLinks(true);
+        //result->setTextInteractionFlags(result->textInteractionFlags() | Qt::TextSelectableByMouse | Qt::LinksAccessibleByMouse);
         result->setOpenExternalLinks(true);
-        result->setTextInteractionFlags(result->textInteractionFlags() | Qt::TextSelectableByMouse | Qt::LinksAccessibleByMouse);
         bodies->insert(std::make_pair(data.id, result));
     }
     
-    result->setText(Shared::processMessageBody(data.text));
+    result->setHtml(Shared::processMessageBody(data.text));
     
     return result;
 }
 
-void MessageDelegate::beginClearWidgets()
-{
-    idsToKeep->clear();
-    clearingWidgets = true;
-}
-
 template <typename T>
 void removeElements(std::map<QString, T*>* elements, std::set<QString>* idsToKeep) {
     std::set<QString> toRemove;
@@ -615,6 +616,51 @@ void removeElements(std::map<QString, T*>* elements, std::set<QString>* idsToKee
     }
 }
 
+int MessageDelegate::paintBody(const Models::FeedItem& data, QPainter* painter, QStyleOptionViewItem& option) const
+{
+    if (data.text.size() > 0) {
+        bodyRenderer->setHtml(Shared::processMessageBody(data.text));
+        bodyRenderer->setTextWidth(option.rect.size().width());
+        painter->save();
+        painter->translate(option.rect.topLeft());
+        bodyRenderer->drawContents(painter);
+        painter->restore();
+        QSize bodySize(qCeil(bodyRenderer->idealWidth()), qCeil(bodyRenderer->size().height()));
+
+
+        QTextBrowser* editor = nullptr;
+        if (option.state.testFlag(QStyle::State_MouseOver)) {
+            std::set<QString> ids({data.id});
+            removeElements(bodies, &ids);
+            editor = getBody(data);
+            editor->setParent(static_cast<QWidget*>(painter->device()));
+        } else {
+            std::map<QString, QTextBrowser*>::const_iterator itr = bodies->find(data.id);
+            if (itr != bodies->end()) {
+                editor = itr->second;
+            }
+        }
+        if (editor != nullptr) {
+            editor->setMinimumSize(bodySize);
+            editor->setMaximumSize(bodySize);
+            editor->move(option.rect.left(), option.rect.y());
+            editor->show();
+        }
+
+        option.rect.adjust(0, bodySize.height() + textMargin, 0, 0);
+        return bodySize.width();
+    }
+    return 0;
+}
+
+void MessageDelegate::beginClearWidgets()
+{
+    idsToKeep->clear();
+    clearingWidgets = true;
+}
+
+
+
 void MessageDelegate::endClearWidgets()
 {
     if (clearingWidgets) {
diff --git a/ui/widgets/messageline/messagedelegate.h b/ui/widgets/messageline/messagedelegate.h
index 38ec0ee..b49410f 100644
--- a/ui/widgets/messageline/messagedelegate.h
+++ b/ui/widgets/messageline/messagedelegate.h
@@ -29,12 +29,13 @@
 #include <QPushButton>
 #include <QProgressBar>
 #include <QLabel>
+#include <QTextEdit>
+#include <QTextBrowser>
 
 #include "shared/icons.h"
 #include "shared/global.h"
 #include "shared/utils.h"
 #include "shared/pathcheck.h"
-#include "ui/utils/textmeter.h"
 
 #include "preview.h"
 
@@ -70,13 +71,15 @@ protected:
     int paintBar(QProgressBar* bar, QPainter* painter, bool sentByMe, QStyleOptionViewItem& option) const;
     int paintPreview(const Models::FeedItem& data, QPainter* painter, QStyleOptionViewItem& option) const;
     int paintComment(const Models::FeedItem& data, QPainter* painter, QStyleOptionViewItem& option) const;
+    int paintBody(const Models::FeedItem& data, QPainter* painter, QStyleOptionViewItem& option) const;
     void paintAvatar(const Models::FeedItem& data, const QModelIndex& index, const QStyleOptionViewItem& option, QPainter* painter) const;
     void paintBubble(const Models::FeedItem& data, QPainter* painter, const QStyleOptionViewItem& option) const;
+
     QPushButton* getButton(const Models::FeedItem& data) const;
     QProgressBar* getBar(const Models::FeedItem& data) const;
     QLabel* getStatusIcon(const Models::FeedItem& data) const;
     QLabel* getPencilIcon(const Models::FeedItem& data) const;
-    QLabel* getBody(const Models::FeedItem& data) const;
+    QTextBrowser* getBody(const Models::FeedItem& data) const;
     void clearHelperWidget(const Models::FeedItem& data) const;
 
     bool needToDrawAvatar(const QModelIndex& index, const Models::FeedItem& data, const QStyleOptionViewItem& option) const;
@@ -95,7 +98,7 @@ private:
     QFont nickFont;
     QFont dateFont;
     QFontMetrics bodyMetrics;
-    TextMeter bodyMeter;
+    QTextDocument* bodyRenderer;
     QFontMetrics nickMetrics;
     QFontMetrics dateMetrics;
     
@@ -107,7 +110,7 @@ private:
     std::map<QString, QProgressBar*>* bars;
     std::map<QString, QLabel*>* statusIcons;
     std::map<QString, QLabel*>* pencilIcons;
-    std::map<QString, QLabel*>* bodies;
+    std::map<QString, QTextBrowser*>* bodies;
     std::map<QString, Preview*>* previews;
     std::set<QString>* idsToKeep;
     bool clearingWidgets;

From eac87e713f72f51f1f24ac11d4fc52a57c861f8f Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Thu, 28 Apr 2022 00:08:59 +0300
Subject: [PATCH 197/281] seem to have found a text block, to activate with the
 click later

---
 ui/widgets/messageline/feedview.cpp        | 29 ++++++++-
 ui/widgets/messageline/feedview.h          |  3 +
 ui/widgets/messageline/messagedelegate.cpp | 74 ++++++++++++++++++++--
 ui/widgets/messageline/messagedelegate.h   |  1 +
 4 files changed, 101 insertions(+), 6 deletions(-)

diff --git a/ui/widgets/messageline/feedview.cpp b/ui/widgets/messageline/feedview.cpp
index e0c1477..bf1da61 100644
--- a/ui/widgets/messageline/feedview.cpp
+++ b/ui/widgets/messageline/feedview.cpp
@@ -50,7 +50,8 @@ FeedView::FeedView(QWidget* parent):
     modelState(Models::MessageFeed::complete),
     progress(),
     dividerFont(),
-    dividerMetrics(dividerFont)
+    dividerMetrics(dividerFont),
+    mousePressed(false)
 {
     horizontalScrollBar()->setRange(0, 0);
     verticalScrollBar()->setSingleStep(approximateSingleMessageHeight);
@@ -412,10 +413,36 @@ void FeedView::mouseMoveEvent(QMouseEvent* event)
     if (!isVisible()) {
         return;
     }
+
+    mousePressed = false;
+    //qDebug() << event;
     
     QAbstractItemView::mouseMoveEvent(event);
 }
 
+void FeedView::mousePressEvent(QMouseEvent* event)
+{
+    QAbstractItemView::mousePressEvent(event);
+    mousePressed = event->button() == Qt::LeftButton;
+}
+
+void FeedView::mouseReleaseEvent(QMouseEvent* event)
+{
+    QAbstractItemView::mouseReleaseEvent(event);
+
+    if (mousePressed && specialDelegate) {
+        QPoint point = event->localPos().toPoint();
+        QModelIndex index = indexAt(point);
+        if (index.isValid()) {
+            QRect rect = visualRect(index);
+            MessageDelegate* del = static_cast<MessageDelegate*>(itemDelegate());
+            if (rect.contains(point)) {
+                del->leftClick(point, index, rect);
+            }
+        }
+    }
+}
+
 void FeedView::resizeEvent(QResizeEvent* event)
 {
     QAbstractItemView::resizeEvent(event);
diff --git a/ui/widgets/messageline/feedview.h b/ui/widgets/messageline/feedview.h
index 8bcd913..c757986 100644
--- a/ui/widgets/messageline/feedview.h
+++ b/ui/widgets/messageline/feedview.h
@@ -68,6 +68,8 @@ protected:
     void paintEvent(QPaintEvent * event) override;
     void updateGeometries() override;
     void mouseMoveEvent(QMouseEvent * event) override;
+    void mousePressEvent(QMouseEvent * event) override;
+    void mouseReleaseEvent(QMouseEvent * event) override;
     void resizeEvent(QResizeEvent * event) override;
     
 private:
@@ -93,6 +95,7 @@ private:
     Progress progress;
     QFont dividerFont;
     QFontMetrics dividerMetrics;
+    bool mousePressed;
     
     static const std::set<int> geometryChangingRoles;
     
diff --git a/ui/widgets/messageline/messagedelegate.cpp b/ui/widgets/messageline/messagedelegate.cpp
index 1d094fa..c787cfa 100644
--- a/ui/widgets/messageline/messagedelegate.cpp
+++ b/ui/widgets/messageline/messagedelegate.cpp
@@ -22,7 +22,9 @@
 #include <QApplication>
 #include <QMouseEvent>
 #include <QAbstractItemView>
-#include <QtMath>
+#include <QAbstractTextDocumentLayout>
+#include <QTextBlock>
+#include <cmath>
 
 #include "messagedelegate.h"
 #include "messagefeed.h"
@@ -303,7 +305,7 @@ QSize MessageDelegate::sizeHint(const QStyleOptionViewItem& option, const QModel
 
         QSizeF size = bodyRenderer->size();
         size.setWidth(bodyRenderer->idealWidth());
-        messageSize = QSize(qCeil(size.width()), qCeil(size.height()));
+        messageSize = QSize(std::ceil(size.width()), std::ceil(size.height()));
         messageSize.rheight() += textMargin;
     }
     
@@ -364,6 +366,68 @@ QSize MessageDelegate::sizeHint(const QStyleOptionViewItem& option, const QModel
     return messageSize;
 }
 
+void MessageDelegate::leftClick(const QPoint& point, const QModelIndex& index, const QRect& sizeHint) const
+{
+    QVariant vi = index.data(Models::MessageFeed::Bulk);
+    Models::FeedItem data = qvariant_cast<Models::FeedItem>(vi);
+    if (data.text.size() > 0) {
+        QRect localHint = sizeHint.adjusted(bubbleMargin, bubbleMargin + margin, -bubbleMargin, -bubbleMargin / 2);
+        if (needToDrawSender(index, data)) {
+            localHint.adjust(0, nickMetrics.lineSpacing() + textMargin, 0, 0);
+        }
+
+        int attachHeight = 0;
+        switch (data.attach.state) {
+            case Models::none:
+                break;
+            case Models::uploading:
+                attachHeight += Preview::calculateAttachSize(Shared::resolvePath(data.attach.localPath), localHint).height() + textMargin;
+                [[fallthrough]];
+            case Models::downloading:
+                attachHeight += barHeight + textMargin;
+                break;
+            case Models::remote:
+                attachHeight += buttonHeight + textMargin;
+                break;
+            case Models::ready:
+            case Models::local: {
+                QSize aSize = Preview::calculateAttachSize(Shared::resolvePath(data.attach.localPath), localHint);
+                attachHeight += aSize.height() + textMargin;
+            }
+                break;
+            case Models::errorDownload: {
+                QSize commentSize = dateMetrics.boundingRect(localHint, Qt::TextWordWrap, data.attach.error).size();
+                attachHeight += commentSize.height() + buttonHeight + textMargin * 2;
+            }
+                break;
+            case Models::errorUpload: {
+                QSize aSize = Preview::calculateAttachSize(Shared::resolvePath(data.attach.localPath), localHint);
+                QSize commentSize = dateMetrics.boundingRect(localHint, Qt::TextWordWrap, data.attach.error).size();
+                attachHeight += aSize.height() + commentSize.height() + textMargin * 2;
+            }
+                break;
+        }
+
+        int bottomSize = std::max(dateMetrics.lineSpacing(), statusIconSize);
+        localHint.adjust(0, attachHeight, 0, -(bottomSize + textMargin));
+
+        if (localHint.contains(point)) {
+            qDebug() << "MESSAGE CLICKED";
+            QPoint translated = point - localHint.topLeft();
+
+            bodyRenderer->setPlainText(data.text);
+            bodyRenderer->setTextWidth(localHint.size().width());
+
+            int pos = bodyRenderer->documentLayout()->hitTest(translated, Qt::FuzzyHit);
+            QTextBlock block = bodyRenderer->findBlock(pos);
+            QString text = block.text();
+            if (text.size() > 0) {
+                qDebug() << text;
+            }
+        }
+    }
+}
+
 void MessageDelegate::initializeFonts(const QFont& font)
 {
     bodyFont = font;
@@ -625,9 +689,9 @@ int MessageDelegate::paintBody(const Models::FeedItem& data, QPainter* painter,
         painter->translate(option.rect.topLeft());
         bodyRenderer->drawContents(painter);
         painter->restore();
-        QSize bodySize(qCeil(bodyRenderer->idealWidth()), qCeil(bodyRenderer->size().height()));
-
+        QSize bodySize(std::ceil(bodyRenderer->idealWidth()), std::ceil(bodyRenderer->size().height()));
 
+/*
         QTextBrowser* editor = nullptr;
         if (option.state.testFlag(QStyle::State_MouseOver)) {
             std::set<QString> ids({data.id});
@@ -645,7 +709,7 @@ int MessageDelegate::paintBody(const Models::FeedItem& data, QPainter* painter,
             editor->setMaximumSize(bodySize);
             editor->move(option.rect.left(), option.rect.y());
             editor->show();
-        }
+        }*/
 
         option.rect.adjust(0, bodySize.height() + textMargin, 0, 0);
         return bodySize.width();
diff --git a/ui/widgets/messageline/messagedelegate.h b/ui/widgets/messageline/messagedelegate.h
index b49410f..9333d45 100644
--- a/ui/widgets/messageline/messagedelegate.h
+++ b/ui/widgets/messageline/messagedelegate.h
@@ -58,6 +58,7 @@ public:
     bool editorEvent(QEvent * event, QAbstractItemModel * model, const QStyleOptionViewItem & option, const QModelIndex & index) override;
     void endClearWidgets();
     void beginClearWidgets();
+    void leftClick(const QPoint& point, const QModelIndex& index, const QRect& sizeHint) const;
 
     static int avatarHeight;
     static int margin;

From 7ba94e9deb786393e78880ee279554410662a168 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Fri, 29 Apr 2022 00:29:44 +0300
Subject: [PATCH 198/281] link clicking and hovering in message body now works!

---
 ui/widgets/messageline/feedview.cpp        |  32 +++-
 ui/widgets/messageline/feedview.h          |   4 +
 ui/widgets/messageline/messagedelegate.cpp | 179 ++++++++-------------
 ui/widgets/messageline/messagedelegate.h   |  10 +-
 4 files changed, 105 insertions(+), 120 deletions(-)

diff --git a/ui/widgets/messageline/feedview.cpp b/ui/widgets/messageline/feedview.cpp
index bf1da61..0758dd9 100644
--- a/ui/widgets/messageline/feedview.cpp
+++ b/ui/widgets/messageline/feedview.cpp
@@ -51,7 +51,8 @@ FeedView::FeedView(QWidget* parent):
     progress(),
     dividerFont(),
     dividerMetrics(dividerFont),
-    mousePressed(false)
+    mousePressed(false),
+    anchorHovered(false)
 {
     horizontalScrollBar()->setRange(0, 0);
     verticalScrollBar()->setSingleStep(approximateSingleMessageHeight);
@@ -408,6 +409,18 @@ void FeedView::verticalScrollbarValueChanged(int value)
     QAbstractItemView::verticalScrollbarValueChanged(vo);
 }
 
+void FeedView::setAnchorHovered(bool hovered)
+{
+    if (anchorHovered != hovered) {
+        anchorHovered = hovered;
+        if (anchorHovered) {
+            setCursor(Qt::PointingHandCursor);
+        } else {
+            setCursor(Qt::ArrowCursor);
+        }
+    }
+}
+
 void FeedView::mouseMoveEvent(QMouseEvent* event)
 {
     if (!isVisible()) {
@@ -418,6 +431,22 @@ void FeedView::mouseMoveEvent(QMouseEvent* event)
     //qDebug() << event;
     
     QAbstractItemView::mouseMoveEvent(event);
+
+    if (specialDelegate) {
+        QPoint point = event->localPos().toPoint();
+        QModelIndex index = indexAt(point);
+        if (index.isValid()) {
+            QRect rect = visualRect(index);
+            MessageDelegate* del = static_cast<MessageDelegate*>(itemDelegate());
+            if (rect.contains(point)) {
+                setAnchorHovered(del->isAnchorHovered(point, index, rect));
+            } else {
+                setAnchorHovered(false);
+            }
+        } else {
+            setAnchorHovered(false);
+        }
+    }
 }
 
 void FeedView::mousePressEvent(QMouseEvent* event)
@@ -487,6 +516,7 @@ void FeedView::setItemDelegate(QAbstractItemDelegate* delegate)
         elementMargin = MessageDelegate::margin;
         connect(del, &MessageDelegate::buttonPushed, this, &FeedView::onMessageButtonPushed);
         connect(del, &MessageDelegate::invalidPath, this, &FeedView::onMessageInvalidPath);
+        connect(del, &MessageDelegate::openLink, &QDesktopServices::openUrl);
     } else {
         specialDelegate = false;
         elementMargin = 0;
diff --git a/ui/widgets/messageline/feedview.h b/ui/widgets/messageline/feedview.h
index c757986..7a00dd7 100644
--- a/ui/widgets/messageline/feedview.h
+++ b/ui/widgets/messageline/feedview.h
@@ -20,6 +20,8 @@
 #define FEEDVIEW_H
 
 #include <QAbstractItemView>
+#include <QDesktopServices>
+#include <QUrl>
 
 #include <deque>
 #include <set>
@@ -76,6 +78,7 @@ private:
     bool tryToCalculateGeometriesWithNoScrollbars(const QStyleOptionViewItem& option, const QAbstractItemModel* model, uint32_t totalHeight);
     void positionProgress();
     void drawDateDevider(int top, const QDateTime& date, QPainter& painter);
+    void setAnchorHovered(bool hovered);
     
 private:
     struct Hint {
@@ -96,6 +99,7 @@ private:
     QFont dividerFont;
     QFontMetrics dividerMetrics;
     bool mousePressed;
+    bool anchorHovered;
     
     static const std::set<int> geometryChangingRoles;
     
diff --git a/ui/widgets/messageline/messagedelegate.cpp b/ui/widgets/messageline/messagedelegate.cpp
index c787cfa..22a575b 100644
--- a/ui/widgets/messageline/messagedelegate.cpp
+++ b/ui/widgets/messageline/messagedelegate.cpp
@@ -44,7 +44,6 @@ MessageDelegate::MessageDelegate(QObject* parent):
     bodyFont(),
     nickFont(),
     dateFont(),
-    bodyMetrics(bodyFont),
     bodyRenderer(new QTextDocument()),
     nickMetrics(nickFont),
     dateMetrics(dateFont),
@@ -55,7 +54,6 @@ MessageDelegate::MessageDelegate(QObject* parent):
     bars(new std::map<QString, QProgressBar*>()),
     statusIcons(new std::map<QString, QLabel*>()),
     pencilIcons(new std::map<QString, QLabel*>()),
-    bodies(new std::map<QString, QTextBrowser*>()),
     previews(new std::map<QString, Preview*>()),
     idsToKeep(new std::set<QString>()),
     clearingWidgets(false)
@@ -88,10 +86,6 @@ MessageDelegate::~MessageDelegate()
         delete pair.second;
     }
     
-    for (const std::pair<const QString, QTextBrowser*>& pair: *bodies){
-        delete pair.second;
-    }
-    
     for (const std::pair<const QString, Preview*>& pair: *previews){
         delete pair.second;
     }
@@ -101,7 +95,6 @@ MessageDelegate::~MessageDelegate()
     delete idsToKeep;
     delete buttons;
     delete bars;
-    delete bodies;
     delete previews;
     delete bodyRenderer;
 }
@@ -366,66 +359,83 @@ QSize MessageDelegate::sizeHint(const QStyleOptionViewItem& option, const QModel
     return messageSize;
 }
 
-void MessageDelegate::leftClick(const QPoint& point, const QModelIndex& index, const QRect& sizeHint) const
+QRect MessageDelegate::getHoveredMessageBodyRect(const QModelIndex& index, const Models::FeedItem& data, const QRect& sizeHint) const
+{
+    QRect localHint = sizeHint.adjusted(bubbleMargin, bubbleMargin + margin, -bubbleMargin, -bubbleMargin / 2);
+    if (needToDrawSender(index, data)) {
+        localHint.adjust(0, nickMetrics.lineSpacing() + textMargin, 0, 0);
+    }
+
+    int attachHeight = 0;
+    switch (data.attach.state) {
+        case Models::none:
+            break;
+        case Models::uploading:
+            attachHeight += Preview::calculateAttachSize(Shared::resolvePath(data.attach.localPath), localHint).height() + textMargin;
+            [[fallthrough]];
+        case Models::downloading:
+            attachHeight += barHeight + textMargin;
+            break;
+        case Models::remote:
+            attachHeight += buttonHeight + textMargin;
+            break;
+        case Models::ready:
+        case Models::local: {
+            QSize aSize = Preview::calculateAttachSize(Shared::resolvePath(data.attach.localPath), localHint);
+            attachHeight += aSize.height() + textMargin;
+        }
+            break;
+        case Models::errorDownload: {
+            QSize commentSize = dateMetrics.boundingRect(localHint, Qt::TextWordWrap, data.attach.error).size();
+            attachHeight += commentSize.height() + buttonHeight + textMargin * 2;
+        }
+            break;
+        case Models::errorUpload: {
+            QSize aSize = Preview::calculateAttachSize(Shared::resolvePath(data.attach.localPath), localHint);
+            QSize commentSize = dateMetrics.boundingRect(localHint, Qt::TextWordWrap, data.attach.error).size();
+            attachHeight += aSize.height() + commentSize.height() + textMargin * 2;
+        }
+            break;
+    }
+
+    int bottomSize = std::max(dateMetrics.lineSpacing(), statusIconSize);
+    localHint.adjust(0, attachHeight, 0, -(bottomSize + textMargin));
+
+    return localHint;
+}
+
+QString MessageDelegate::getAnchor(const QPoint& point, const QModelIndex& index, const QRect& sizeHint) const
 {
     QVariant vi = index.data(Models::MessageFeed::Bulk);
     Models::FeedItem data = qvariant_cast<Models::FeedItem>(vi);
     if (data.text.size() > 0) {
-        QRect localHint = sizeHint.adjusted(bubbleMargin, bubbleMargin + margin, -bubbleMargin, -bubbleMargin / 2);
-        if (needToDrawSender(index, data)) {
-            localHint.adjust(0, nickMetrics.lineSpacing() + textMargin, 0, 0);
-        }
-
-        int attachHeight = 0;
-        switch (data.attach.state) {
-            case Models::none:
-                break;
-            case Models::uploading:
-                attachHeight += Preview::calculateAttachSize(Shared::resolvePath(data.attach.localPath), localHint).height() + textMargin;
-                [[fallthrough]];
-            case Models::downloading:
-                attachHeight += barHeight + textMargin;
-                break;
-            case Models::remote:
-                attachHeight += buttonHeight + textMargin;
-                break;
-            case Models::ready:
-            case Models::local: {
-                QSize aSize = Preview::calculateAttachSize(Shared::resolvePath(data.attach.localPath), localHint);
-                attachHeight += aSize.height() + textMargin;
-            }
-                break;
-            case Models::errorDownload: {
-                QSize commentSize = dateMetrics.boundingRect(localHint, Qt::TextWordWrap, data.attach.error).size();
-                attachHeight += commentSize.height() + buttonHeight + textMargin * 2;
-            }
-                break;
-            case Models::errorUpload: {
-                QSize aSize = Preview::calculateAttachSize(Shared::resolvePath(data.attach.localPath), localHint);
-                QSize commentSize = dateMetrics.boundingRect(localHint, Qt::TextWordWrap, data.attach.error).size();
-                attachHeight += aSize.height() + commentSize.height() + textMargin * 2;
-            }
-                break;
-        }
-
-        int bottomSize = std::max(dateMetrics.lineSpacing(), statusIconSize);
-        localHint.adjust(0, attachHeight, 0, -(bottomSize + textMargin));
+        QRect localHint = getHoveredMessageBodyRect(index, data, sizeHint);
 
         if (localHint.contains(point)) {
-            qDebug() << "MESSAGE CLICKED";
             QPoint translated = point - localHint.topLeft();
 
-            bodyRenderer->setPlainText(data.text);
+            bodyRenderer->setHtml(Shared::processMessageBody(data.text));
             bodyRenderer->setTextWidth(localHint.size().width());
 
-            int pos = bodyRenderer->documentLayout()->hitTest(translated, Qt::FuzzyHit);
-            QTextBlock block = bodyRenderer->findBlock(pos);
-            QString text = block.text();
-            if (text.size() > 0) {
-                qDebug() << text;
-            }
+            return bodyRenderer->documentLayout()->anchorAt(translated);
         }
     }
+
+    return QString();
+}
+
+void MessageDelegate::leftClick(const QPoint& point, const QModelIndex& index, const QRect& sizeHint) const
+{
+    QString anchor = getAnchor(point, index, sizeHint);
+    if (anchor.size() > 0) {
+        emit openLink(anchor);
+    }
+}
+
+bool MessageDelegate::isAnchorHovered(const QPoint& point, const QModelIndex& index, const QRect& sizeHint) const
+{
+    QString anchor = getAnchor(point, index, sizeHint);
+    return anchor.size() > 0;
 }
 
 void MessageDelegate::initializeFonts(const QFont& font)
@@ -452,7 +462,6 @@ void MessageDelegate::initializeFonts(const QFont& font)
     }
     
     bodyFont.setKerning(false);
-    bodyMetrics = QFontMetrics(bodyFont);
     nickMetrics = QFontMetrics(nickFont);
     dateMetrics = QFontMetrics(dateFont);
 
@@ -636,36 +645,6 @@ QLabel * MessageDelegate::getPencilIcon(const Models::FeedItem& data) const
     return result;
 }
 
-QTextBrowser * MessageDelegate::getBody(const Models::FeedItem& data) const
-{
-    std::map<QString, QTextBrowser*>::const_iterator itr = bodies->find(data.id);
-    QTextBrowser* result = 0;
-    
-    if (itr != bodies->end()) {
-        result = itr->second;
-    } else {
-        result = new QTextBrowser();
-        result->setFont(bodyFont);
-        result->setContextMenuPolicy(Qt::NoContextMenu);
-        result->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
-        result->setContentsMargins(0, 0, 0, 0);
-        //result->viewport()->setAutoFillBackground(false);
-        result->document()->setDocumentMargin(0);
-        result->setFrameStyle(0);
-        result->setLineWidth(0);
-        //result->setAutoFillBackground(false);
-        //->setWordWrap(true);
-        result->setOpenExternalLinks(true);
-        //result->setTextInteractionFlags(result->textInteractionFlags() | Qt::TextSelectableByMouse | Qt::LinksAccessibleByMouse);
-        result->setOpenExternalLinks(true);
-        bodies->insert(std::make_pair(data.id, result));
-    }
-    
-    result->setHtml(Shared::processMessageBody(data.text));
-    
-    return result;
-}
-
 template <typename T>
 void removeElements(std::map<QString, T*>* elements, std::set<QString>* idsToKeep) {
     std::set<QString> toRemove;
@@ -691,26 +670,6 @@ int MessageDelegate::paintBody(const Models::FeedItem& data, QPainter* painter,
         painter->restore();
         QSize bodySize(std::ceil(bodyRenderer->idealWidth()), std::ceil(bodyRenderer->size().height()));
 
-/*
-        QTextBrowser* editor = nullptr;
-        if (option.state.testFlag(QStyle::State_MouseOver)) {
-            std::set<QString> ids({data.id});
-            removeElements(bodies, &ids);
-            editor = getBody(data);
-            editor->setParent(static_cast<QWidget*>(painter->device()));
-        } else {
-            std::map<QString, QTextBrowser*>::const_iterator itr = bodies->find(data.id);
-            if (itr != bodies->end()) {
-                editor = itr->second;
-            }
-        }
-        if (editor != nullptr) {
-            editor->setMinimumSize(bodySize);
-            editor->setMaximumSize(bodySize);
-            editor->move(option.rect.left(), option.rect.y());
-            editor->show();
-        }*/
-
         option.rect.adjust(0, bodySize.height() + textMargin, 0, 0);
         return bodySize.width();
     }
@@ -723,8 +682,6 @@ void MessageDelegate::beginClearWidgets()
     clearingWidgets = true;
 }
 
-
-
 void MessageDelegate::endClearWidgets()
 {
     if (clearingWidgets) {
@@ -732,7 +689,6 @@ void MessageDelegate::endClearWidgets()
         removeElements(bars, idsToKeep);
         removeElements(statusIcons, idsToKeep);
         removeElements(pencilIcons, idsToKeep);
-        removeElements(bodies, idsToKeep);
         removeElements(previews, idsToKeep);
         
         idsToKeep->clear();
@@ -760,8 +716,3 @@ void MessageDelegate::clearHelperWidget(const Models::FeedItem& data) const
         }
     }
 }
-
-// void MessageDelegate::setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const
-// {
-//     
-// }
diff --git a/ui/widgets/messageline/messagedelegate.h b/ui/widgets/messageline/messagedelegate.h
index 9333d45..d871a52 100644
--- a/ui/widgets/messageline/messagedelegate.h
+++ b/ui/widgets/messageline/messagedelegate.h
@@ -29,8 +29,6 @@
 #include <QPushButton>
 #include <QProgressBar>
 #include <QLabel>
-#include <QTextEdit>
-#include <QTextBrowser>
 
 #include "shared/icons.h"
 #include "shared/global.h"
@@ -59,6 +57,7 @@ public:
     void endClearWidgets();
     void beginClearWidgets();
     void leftClick(const QPoint& point, const QModelIndex& index, const QRect& sizeHint) const;
+    bool isAnchorHovered(const QPoint& point, const QModelIndex& index, const QRect& sizeHint) const;
 
     static int avatarHeight;
     static int margin;
@@ -66,6 +65,7 @@ public:
 signals:
     void buttonPushed(const QString& messageId) const;
     void invalidPath(const QString& messageId) const;
+    void openLink(const QString& href) const;
     
 protected:
     int paintButton(QPushButton* btn, QPainter* painter, bool sentByMe, QStyleOptionViewItem& option) const;
@@ -80,11 +80,13 @@ protected:
     QProgressBar* getBar(const Models::FeedItem& data) const;
     QLabel* getStatusIcon(const Models::FeedItem& data) const;
     QLabel* getPencilIcon(const Models::FeedItem& data) const;
-    QTextBrowser* getBody(const Models::FeedItem& data) const;
     void clearHelperWidget(const Models::FeedItem& data) const;
 
     bool needToDrawAvatar(const QModelIndex& index, const Models::FeedItem& data, const QStyleOptionViewItem& option) const;
     bool needToDrawSender(const QModelIndex& index, const Models::FeedItem& data) const;
+
+    QRect getHoveredMessageBodyRect(const QModelIndex& index, const Models::FeedItem& data, const QRect& sizeHint) const;
+    QString getAnchor(const QPoint& point, const QModelIndex& index, const QRect& sizeHint) const;
     
 protected slots:
     void onButtonPushed() const;
@@ -98,7 +100,6 @@ private:
     QFont bodyFont;
     QFont nickFont;
     QFont dateFont;
-    QFontMetrics bodyMetrics;
     QTextDocument* bodyRenderer;
     QFontMetrics nickMetrics;
     QFontMetrics dateMetrics;
@@ -111,7 +112,6 @@ private:
     std::map<QString, QProgressBar*>* bars;
     std::map<QString, QLabel*>* statusIcons;
     std::map<QString, QLabel*>* pencilIcons;
-    std::map<QString, QTextBrowser*>* bodies;
     std::map<QString, Preview*>* previews;
     std::set<QString>* idsToKeep;
     bool clearingWidgets;

From 0340db7f2fcff211f99d6ccc92bd98669a406581 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Sun, 1 May 2022 23:19:52 +0300
Subject: [PATCH 199/281] first successfull attempt to visualize selection on
 message body

---
 ui/widgets/messageline/feedview.cpp        | 56 +++++++++++------
 ui/widgets/messageline/feedview.h          |  3 +
 ui/widgets/messageline/messagedelegate.cpp | 70 ++++++++++++++--------
 ui/widgets/messageline/messagedelegate.h   |  5 +-
 4 files changed, 92 insertions(+), 42 deletions(-)

diff --git a/ui/widgets/messageline/feedview.cpp b/ui/widgets/messageline/feedview.cpp
index 0758dd9..f467f43 100644
--- a/ui/widgets/messageline/feedview.cpp
+++ b/ui/widgets/messageline/feedview.cpp
@@ -52,7 +52,10 @@ FeedView::FeedView(QWidget* parent):
     dividerFont(),
     dividerMetrics(dividerFont),
     mousePressed(false),
-    anchorHovered(false)
+    dragging(false),
+    anchorHovered(false),
+    dragStartPoint(),
+    dragEndPoint()
 {
     horizontalScrollBar()->setRange(0, 0);
     verticalScrollBar()->setSingleStep(approximateSingleMessageHeight);
@@ -304,7 +307,7 @@ bool FeedView::tryToCalculateGeometriesWithNoScrollbars(const QStyleOptionViewIt
 
 void FeedView::paintEvent(QPaintEvent* event)
 {
-    //qDebug() << "paint" << event->rect();
+    qDebug() << "paint" << event->rect();
     const QAbstractItemModel* m = model();
     QWidget* vp = viewport();
     QRect zone = event->rect().translated(0, -vo);
@@ -427,19 +430,31 @@ void FeedView::mouseMoveEvent(QMouseEvent* event)
         return;
     }
 
-    mousePressed = false;
-    //qDebug() << event;
+    dragEndPoint = event->localPos().toPoint();
+    if (mousePressed) {
+        QPoint distance = dragStartPoint - dragEndPoint;
+        if (distance.manhattanLength() > 5) {
+            dragging = true;
+        }
+    }
     
     QAbstractItemView::mouseMoveEvent(event);
 
     if (specialDelegate) {
-        QPoint point = event->localPos().toPoint();
-        QModelIndex index = indexAt(point);
+        QModelIndex index = indexAt(dragEndPoint);
         if (index.isValid()) {
             QRect rect = visualRect(index);
-            MessageDelegate* del = static_cast<MessageDelegate*>(itemDelegate());
-            if (rect.contains(point)) {
-                setAnchorHovered(del->isAnchorHovered(point, index, rect));
+            if (rect.contains(dragEndPoint)) {
+                MessageDelegate* del = static_cast<MessageDelegate*>(itemDelegate());
+                if (dragging) {
+                    setAnchorHovered(false);
+                    if (del->mouseDrag(dragStartPoint, dragEndPoint, index, rect)) {
+                        qDebug() << "asking to repaint" << rect;
+                        setDirtyRegion(rect);
+                    }
+                } else {
+                    setAnchorHovered(del->isAnchorHovered(dragEndPoint, index, rect));
+                }
             } else {
                 setAnchorHovered(false);
             }
@@ -453,22 +468,29 @@ void FeedView::mousePressEvent(QMouseEvent* event)
 {
     QAbstractItemView::mousePressEvent(event);
     mousePressed = event->button() == Qt::LeftButton;
+    if (mousePressed) {
+        dragStartPoint = event->localPos().toPoint();
+    }
 }
 
 void FeedView::mouseReleaseEvent(QMouseEvent* event)
 {
     QAbstractItemView::mouseReleaseEvent(event);
 
-    if (mousePressed && specialDelegate) {
-        QPoint point = event->localPos().toPoint();
-        QModelIndex index = indexAt(point);
-        if (index.isValid()) {
-            QRect rect = visualRect(index);
-            MessageDelegate* del = static_cast<MessageDelegate*>(itemDelegate());
-            if (rect.contains(point)) {
-                del->leftClick(point, index, rect);
+    if (mousePressed) {
+        if (!dragging && specialDelegate) {
+            QPoint point = event->localPos().toPoint();
+            QModelIndex index = indexAt(point);
+            if (index.isValid()) {
+                QRect rect = visualRect(index);
+                MessageDelegate* del = static_cast<MessageDelegate*>(itemDelegate());
+                if (rect.contains(point)) {
+                    del->leftClick(point, index, rect);
+                }
             }
         }
+        dragging = false;
+        mousePressed = false;
     }
 }
 
diff --git a/ui/widgets/messageline/feedview.h b/ui/widgets/messageline/feedview.h
index 7a00dd7..c0d6254 100644
--- a/ui/widgets/messageline/feedview.h
+++ b/ui/widgets/messageline/feedview.h
@@ -99,7 +99,10 @@ private:
     QFont dividerFont;
     QFontMetrics dividerMetrics;
     bool mousePressed;
+    bool dragging;
     bool anchorHovered;
+    QPoint dragStartPoint;
+    QPoint dragEndPoint;
     
     static const std::set<int> geometryChangingRoles;
     
diff --git a/ui/widgets/messageline/messagedelegate.cpp b/ui/widgets/messageline/messagedelegate.cpp
index ca2e0a6..197248a 100644
--- a/ui/widgets/messageline/messagedelegate.cpp
+++ b/ui/widgets/messageline/messagedelegate.cpp
@@ -56,7 +56,9 @@ MessageDelegate::MessageDelegate(QObject* parent):
     pencilIcons(new std::map<QString, QLabel*>()),
     previews(new std::map<QString, Preview*>()),
     idsToKeep(new std::set<QString>()),
-    clearingWidgets(false)
+    clearingWidgets(false),
+    currentId(""),
+    selection(0, 0)
 {
     bodyRenderer->setDocumentMargin(0);
 
@@ -438,6 +440,37 @@ bool MessageDelegate::isAnchorHovered(const QPoint& point, const QModelIndex& in
     return anchor.size() > 0;
 }
 
+bool MessageDelegate::mouseDrag(const QPoint& start, const QPoint& end, const QModelIndex& index, const QRect& sizeHint)
+{
+    QVariant vi = index.data(Models::MessageFeed::Bulk);
+    Models::FeedItem data = qvariant_cast<Models::FeedItem>(vi);
+    if (data.text.size() > 0) {
+        QRect localHint = getHoveredMessageBodyRect(index, data, sizeHint);
+
+        if (localHint.contains(start)) {
+            QPoint translated = start - localHint.topLeft();
+
+            bodyRenderer->setHtml(Shared::processMessageBody(data.text));
+            bodyRenderer->setTextWidth(localHint.size().width());
+            selection.first = bodyRenderer->documentLayout()->hitTest(translated, Qt::HitTestAccuracy::FuzzyHit);
+            selection.second = bodyRenderer->documentLayout()->hitTest(end - localHint.topLeft(), Qt::HitTestAccuracy::FuzzyHit);
+
+            currentId = data.id;
+
+            return true;
+        }
+    }
+    return false;
+}
+
+QString MessageDelegate::clearSelection()
+{
+    QString lastSelectedId = currentId;
+    currentId = "";
+    selection = std::pair(0, 0);
+    return lastSelectedId;
+}
+
 void MessageDelegate::initializeFonts(const QFont& font)
 {
     bodyFont = font;
@@ -664,32 +697,21 @@ int MessageDelegate::paintBody(const Models::FeedItem& data, QPainter* painter,
     if (data.text.size() > 0) {
         bodyRenderer->setHtml(Shared::processMessageBody(data.text));
         bodyRenderer->setTextWidth(option.rect.size().width());
-        painter->setBackgroundMode(Qt::BGMode::OpaqueMode);
         painter->save();
-//         QTextCursor cursor(bodyRenderer);
-//         cursor.setPosition(2, QTextCursor::KeepAnchor);
         painter->translate(option.rect.topLeft());
-//         QTextFrameFormat format = bodyRenderer->rootFrame()->frameFormat();
-//         format.setBackground(option.palette.brush(QPalette::Active, QPalette::Highlight));
-//         bodyRenderer->rootFrame()->setFrameFormat(format);
+
+        if (data.id == currentId) {
+            QTextCursor cursor(bodyRenderer);
+            cursor.setPosition(selection.first, QTextCursor::MoveAnchor);
+            cursor.setPosition(selection.second, QTextCursor::KeepAnchor);
+            QTextCharFormat format = cursor.charFormat();
+            format.setBackground(option.palette.color(QPalette::Active, QPalette::Highlight));
+            format.setForeground(option.palette.color(QPalette::Active, QPalette::HighlightedText));
+            cursor.setCharFormat(format);
+        }
+
         bodyRenderer->drawContents(painter);
-//         QColor c = option.palette.color(QPalette::Active, QPalette::Highlight);
-//         QTextBlock b = bodyRenderer->begin();
-//         QTextBlockFormat format = b.blockFormat();
-//         format.setBackground(option.palette.brush(QPalette::Active, QPalette::Highlight));
-//         format.setProperty(QTextFormat::BackgroundBrush, option.palette.brush(QPalette::Active, QPalette::Highlight));
-//         QTextCursor cursor(bodyRenderer);
-//         cursor.setBlockFormat(format);
-//         b = bodyRenderer->begin();
-//         while (b.isValid() > 0) {
-//             QTextLayout* lay = b.layout();
-//             QTextLayout::FormatRange range;
-//             range.format = b.charFormat();
-//             range.start = 0;
-//             range.length = 2;
-//             lay->draw(painter, option.rect.topLeft(), {range});
-//             b = b.next();
-//         }
+
         painter->restore();
         QSize bodySize(std::ceil(bodyRenderer->idealWidth()), std::ceil(bodyRenderer->size().height()));
 
diff --git a/ui/widgets/messageline/messagedelegate.h b/ui/widgets/messageline/messagedelegate.h
index df883f7..2aea240 100644
--- a/ui/widgets/messageline/messagedelegate.h
+++ b/ui/widgets/messageline/messagedelegate.h
@@ -59,6 +59,8 @@ public:
     void beginClearWidgets();
     void leftClick(const QPoint& point, const QModelIndex& index, const QRect& sizeHint) const;
     bool isAnchorHovered(const QPoint& point, const QModelIndex& index, const QRect& sizeHint) const;
+    bool mouseDrag(const QPoint& start, const QPoint& end, const QModelIndex& index, const QRect& sizeHint);
+    QString clearSelection();
 
     static int avatarHeight;
     static int margin;
@@ -116,7 +118,8 @@ private:
     std::map<QString, Preview*>* previews;
     std::set<QString>* idsToKeep;
     bool clearingWidgets;
-    
+    QString currentId;
+    std::pair<int, int> selection;
 };
 
 #endif // MESSAGEDELEGATE_H

From 3c48577eeed23e5f21af70c0ae90695e91893ac7 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Mon, 2 May 2022 22:25:50 +0300
Subject: [PATCH 200/281] selection message body now actually working

---
 shared/utils.h                             |  6 ++
 ui/widgets/conversation.cpp                | 10 +++
 ui/widgets/messageline/feedview.cpp        | 92 ++++++++++++++++------
 ui/widgets/messageline/feedview.h          |  8 +-
 ui/widgets/messageline/messagedelegate.cpp | 54 ++++++++++---
 ui/widgets/messageline/messagedelegate.h   |  4 +-
 ui/widgets/messageline/messagefeed.h       |  4 +-
 7 files changed, 138 insertions(+), 40 deletions(-)

diff --git a/shared/utils.h b/shared/utils.h
index 564e2e6..0329cee 100644
--- a/shared/utils.h
+++ b/shared/utils.h
@@ -69,6 +69,12 @@ static const std::vector<QColor> colorPalette = {
     QColor(17, 17, 80),
     QColor(54, 54, 94)
 };
+
+enum class Hover {
+    nothing,
+    text,
+    anchor
+};
 }
 
 #endif // SHARED_UTILS_H
diff --git a/ui/widgets/conversation.cpp b/ui/widgets/conversation.cpp
index 70a468c..b2c7a5f 100644
--- a/ui/widgets/conversation.cpp
+++ b/ui/widgets/conversation.cpp
@@ -499,6 +499,16 @@ void Conversation::onFeedContext(const QPoint& pos)
             });
         }
 
+        QString selected = feed->getSelectedText();
+        if (selected.size() > 0) {
+            showMenu = true;
+            QAction* copy = contextMenu->addAction(Shared::icon("edit-copy"), tr("Copy selected"));
+            connect(copy, &QAction::triggered, [selected] () {
+                QClipboard* cb = QApplication::clipboard();
+                cb->setText(selected);
+            });
+        }
+
         QString body = item->getBody();
         if (body.size() > 0) {
             showMenu = true;
diff --git a/ui/widgets/messageline/feedview.cpp b/ui/widgets/messageline/feedview.cpp
index f467f43..353d851 100644
--- a/ui/widgets/messageline/feedview.cpp
+++ b/ui/widgets/messageline/feedview.cpp
@@ -21,6 +21,8 @@
 #include <QPaintEvent>
 #include <QPainter>
 #include <QScrollBar>
+#include <QApplication>
+#include <QClipboard>
 #include <QDebug>
 
 #include "messagedelegate.h"
@@ -53,9 +55,10 @@ FeedView::FeedView(QWidget* parent):
     dividerMetrics(dividerFont),
     mousePressed(false),
     dragging(false),
-    anchorHovered(false),
+    hovered(Shared::Hover::nothing),
     dragStartPoint(),
-    dragEndPoint()
+    dragEndPoint(),
+    selectedText()
 {
     horizontalScrollBar()->setRange(0, 0);
     verticalScrollBar()->setSingleStep(approximateSingleMessageHeight);
@@ -167,7 +170,7 @@ void FeedView::dataChanged(const QModelIndex& topLeft, const QModelIndex& bottom
 
 void FeedView::updateGeometries()
 {
-    qDebug() << "updateGeometries";
+    //qDebug() << "updateGeometries";
     QScrollBar* bar = verticalScrollBar();
     
     const QStyle* st = style();
@@ -307,7 +310,7 @@ bool FeedView::tryToCalculateGeometriesWithNoScrollbars(const QStyleOptionViewIt
 
 void FeedView::paintEvent(QPaintEvent* event)
 {
-    qDebug() << "paint" << event->rect();
+    //qDebug() << "paint" << event->rect();
     const QAbstractItemModel* m = model();
     QWidget* vp = viewport();
     QRect zone = event->rect().translated(0, -vo);
@@ -412,14 +415,20 @@ void FeedView::verticalScrollbarValueChanged(int value)
     QAbstractItemView::verticalScrollbarValueChanged(vo);
 }
 
-void FeedView::setAnchorHovered(bool hovered)
+void FeedView::setAnchorHovered(Shared::Hover type)
 {
-    if (anchorHovered != hovered) {
-        anchorHovered = hovered;
-        if (anchorHovered) {
-            setCursor(Qt::PointingHandCursor);
-        } else {
-            setCursor(Qt::ArrowCursor);
+    if (hovered != type) {
+        hovered = type;
+        switch (hovered) {
+            case Shared::Hover::nothing:
+                setCursor(Qt::ArrowCursor);
+                break;
+            case Shared::Hover::text:
+                setCursor(Qt::IBeamCursor);
+                break;
+            case Shared::Hover::anchor:
+                setCursor(Qt::PointingHandCursor);
+                break;
         }
     }
 }
@@ -441,25 +450,31 @@ void FeedView::mouseMoveEvent(QMouseEvent* event)
     QAbstractItemView::mouseMoveEvent(event);
 
     if (specialDelegate) {
-        QModelIndex index = indexAt(dragEndPoint);
-        if (index.isValid()) {
-            QRect rect = visualRect(index);
-            if (rect.contains(dragEndPoint)) {
-                MessageDelegate* del = static_cast<MessageDelegate*>(itemDelegate());
-                if (dragging) {
-                    setAnchorHovered(false);
-                    if (del->mouseDrag(dragStartPoint, dragEndPoint, index, rect)) {
-                        qDebug() << "asking to repaint" << rect;
+        MessageDelegate* del = static_cast<MessageDelegate*>(itemDelegate());
+        if (dragging) {
+            QModelIndex index = indexAt(dragStartPoint);
+            if (index.isValid()) {
+                QRect rect = visualRect(index);
+                if (rect.contains(dragStartPoint)) {
+                    QString selected = del->mouseDrag(dragStartPoint, dragEndPoint, index, rect);
+                    if (selectedText != selected) {
+                        selectedText = selected;
                         setDirtyRegion(rect);
                     }
-                } else {
-                    setAnchorHovered(del->isAnchorHovered(dragEndPoint, index, rect));
                 }
-            } else {
-                setAnchorHovered(false);
             }
         } else {
-            setAnchorHovered(false);
+            QModelIndex index = indexAt(dragEndPoint);
+            if (index.isValid()) {
+                QRect rect = visualRect(index);
+                if (rect.contains(dragEndPoint)) {
+                    setAnchorHovered(del->hoverType(dragEndPoint, index, rect));
+                } else {
+                    setAnchorHovered(Shared::Hover::nothing);
+                }
+            } else {
+                setAnchorHovered(Shared::Hover::nothing);
+            }
         }
     }
 }
@@ -470,6 +485,17 @@ void FeedView::mousePressEvent(QMouseEvent* event)
     mousePressed = event->button() == Qt::LeftButton;
     if (mousePressed) {
         dragStartPoint = event->localPos().toPoint();
+        if (specialDelegate && specialModel) {
+            MessageDelegate* del = static_cast<MessageDelegate*>(itemDelegate());
+            QString lastSelectedId = del->clearSelection();
+            if (lastSelectedId.size()) {
+                Models::MessageFeed* feed = static_cast<Models::MessageFeed*>(model());
+                QModelIndex index = feed->modelIndexById(lastSelectedId);
+                if (index.isValid()) {
+                    setDirtyRegion(visualRect(index));
+                }
+            }
+        }
     }
 }
 
@@ -494,6 +520,17 @@ void FeedView::mouseReleaseEvent(QMouseEvent* event)
     }
 }
 
+void FeedView::keyPressEvent(QKeyEvent* event)
+{
+    QKeyEvent *key_event = static_cast<QKeyEvent*>(event);
+    if (key_event->matches(QKeySequence::Copy)) {
+        if (selectedText.size() > 0) {
+            QClipboard* cb = QApplication::clipboard();
+            cb->setText(selectedText);
+        }
+    }
+}
+
 void FeedView::resizeEvent(QResizeEvent* event)
 {
     QAbstractItemView::resizeEvent(event);
@@ -603,3 +640,8 @@ void FeedView::onModelSyncStateChange(Models::MessageFeed::SyncState state)
         scheduleDelayedItemsLayout();
     }
 }
+
+QString FeedView::getSelectedText() const
+{
+    return selectedText;
+}
diff --git a/ui/widgets/messageline/feedview.h b/ui/widgets/messageline/feedview.h
index c0d6254..d0763a5 100644
--- a/ui/widgets/messageline/feedview.h
+++ b/ui/widgets/messageline/feedview.h
@@ -28,6 +28,7 @@
 
 #include <ui/widgets/messageline/messagefeed.h>
 #include <ui/utils/progress.h>
+#include <shared/utils.h>
 
 /**
  * @todo write docs
@@ -50,6 +51,7 @@ public:
     void setModel(QAbstractItemModel * model) override;
     
     QFont getFont() const;
+    QString getSelectedText() const;
     
 signals:
     void resized();
@@ -72,13 +74,14 @@ protected:
     void mouseMoveEvent(QMouseEvent * event) override;
     void mousePressEvent(QMouseEvent * event) override;
     void mouseReleaseEvent(QMouseEvent * event) override;
+    void keyPressEvent(QKeyEvent * event) override;
     void resizeEvent(QResizeEvent * event) override;
     
 private:
     bool tryToCalculateGeometriesWithNoScrollbars(const QStyleOptionViewItem& option, const QAbstractItemModel* model, uint32_t totalHeight);
     void positionProgress();
     void drawDateDevider(int top, const QDateTime& date, QPainter& painter);
-    void setAnchorHovered(bool hovered);
+    void setAnchorHovered(Shared::Hover type);
     
 private:
     struct Hint {
@@ -100,9 +103,10 @@ private:
     QFontMetrics dividerMetrics;
     bool mousePressed;
     bool dragging;
-    bool anchorHovered;
+    Shared::Hover hovered;
     QPoint dragStartPoint;
     QPoint dragEndPoint;
+    QString selectedText;
     
     static const std::set<int> geometryChangingRoles;
     
diff --git a/ui/widgets/messageline/messagedelegate.cpp b/ui/widgets/messageline/messagedelegate.cpp
index 197248a..840ef5c 100644
--- a/ui/widgets/messageline/messagedelegate.cpp
+++ b/ui/widgets/messageline/messagedelegate.cpp
@@ -434,13 +434,37 @@ void MessageDelegate::leftClick(const QPoint& point, const QModelIndex& index, c
     }
 }
 
-bool MessageDelegate::isAnchorHovered(const QPoint& point, const QModelIndex& index, const QRect& sizeHint) const
+Shared::Hover MessageDelegate::hoverType(const QPoint& point, const QModelIndex& index, const QRect& sizeHint) const
 {
-    QString anchor = getAnchor(point, index, sizeHint);
-    return anchor.size() > 0;
+    QVariant vi = index.data(Models::MessageFeed::Bulk);
+    Models::FeedItem data = qvariant_cast<Models::FeedItem>(vi);
+    if (data.text.size() > 0) {
+        QRect localHint = getHoveredMessageBodyRect(index, data, sizeHint);
+
+        if (localHint.contains(point)) {
+            QPoint translated = point - localHint.topLeft();
+
+            bodyRenderer->setHtml(Shared::processMessageBody(data.text));
+            bodyRenderer->setTextWidth(localHint.size().width());
+
+            QAbstractTextDocumentLayout* lay = bodyRenderer->documentLayout();
+            QString anchor = lay->anchorAt(translated);
+
+            if (anchor.size() > 0) {
+                return Shared::Hover::anchor;
+            } else {
+                int position = lay->hitTest(translated, Qt::HitTestAccuracy::ExactHit);
+                if (position != -1) {           //this is a bad way, it's false positive on the end of the last
+                    return Shared::Hover::text; //line of a multiline block, so it's not better the checking the rect
+                }
+                //return Shared::Hover::text;
+            }
+        }
+    }
+    return Shared::Hover::nothing;
 }
 
-bool MessageDelegate::mouseDrag(const QPoint& start, const QPoint& end, const QModelIndex& index, const QRect& sizeHint)
+QString MessageDelegate::mouseDrag(const QPoint& start, const QPoint& end, const QModelIndex& index, const QRect& sizeHint)
 {
     QVariant vi = index.data(Models::MessageFeed::Bulk);
     Models::FeedItem data = qvariant_cast<Models::FeedItem>(vi);
@@ -448,19 +472,31 @@ bool MessageDelegate::mouseDrag(const QPoint& start, const QPoint& end, const QM
         QRect localHint = getHoveredMessageBodyRect(index, data, sizeHint);
 
         if (localHint.contains(start)) {
-            QPoint translated = start - localHint.topLeft();
+            QPoint tl = localHint.topLeft();
+            QPoint first = start - tl;
+            QPoint last = end - tl;
+            last.setX(std::max(last.x(), 0));
+            last.setX(std::min(last.x(), localHint.width() - 1));
+            last.setY(std::max(last.y(), 0));
+            last.setY(std::min(last.y(), localHint.height()));
+
 
             bodyRenderer->setHtml(Shared::processMessageBody(data.text));
             bodyRenderer->setTextWidth(localHint.size().width());
-            selection.first = bodyRenderer->documentLayout()->hitTest(translated, Qt::HitTestAccuracy::FuzzyHit);
-            selection.second = bodyRenderer->documentLayout()->hitTest(end - localHint.topLeft(), Qt::HitTestAccuracy::FuzzyHit);
+            selection.first = bodyRenderer->documentLayout()->hitTest(first, Qt::HitTestAccuracy::FuzzyHit);
+            selection.second = bodyRenderer->documentLayout()->hitTest(last, Qt::HitTestAccuracy::FuzzyHit);
 
             currentId = data.id;
 
-            return true;
+            if (selection.first != selection.second) {
+                QTextCursor cursor(bodyRenderer);
+                cursor.setPosition(selection.first, QTextCursor::MoveAnchor);
+                cursor.setPosition(selection.second, QTextCursor::KeepAnchor);
+                return cursor.selectedText();
+            }
         }
     }
-    return false;
+    return "";
 }
 
 QString MessageDelegate::clearSelection()
diff --git a/ui/widgets/messageline/messagedelegate.h b/ui/widgets/messageline/messagedelegate.h
index 2aea240..dc0fb49 100644
--- a/ui/widgets/messageline/messagedelegate.h
+++ b/ui/widgets/messageline/messagedelegate.h
@@ -58,8 +58,8 @@ public:
     void endClearWidgets();
     void beginClearWidgets();
     void leftClick(const QPoint& point, const QModelIndex& index, const QRect& sizeHint) const;
-    bool isAnchorHovered(const QPoint& point, const QModelIndex& index, const QRect& sizeHint) const;
-    bool mouseDrag(const QPoint& start, const QPoint& end, const QModelIndex& index, const QRect& sizeHint);
+    Shared::Hover hoverType(const QPoint& point, const QModelIndex& index, const QRect& sizeHint) const;
+    QString mouseDrag(const QPoint& start, const QPoint& end, const QModelIndex& index, const QRect& sizeHint);
     QString clearSelection();
 
     static int avatarHeight;
diff --git a/ui/widgets/messageline/messagefeed.h b/ui/widgets/messageline/messagefeed.h
index f362989..db174d2 100644
--- a/ui/widgets/messageline/messagefeed.h
+++ b/ui/widgets/messageline/messagefeed.h
@@ -57,6 +57,8 @@ public:
     void changeMessage(const QString& id, const QMap<QString, QVariant>& data);
     void removeMessage(const QString& id);
     Shared::Message getMessage(const QString& id);
+    QModelIndex modelIndexById(const QString& id) const;
+    QModelIndex modelIndexByTime(const QString& id, const QDateTime& time) const;
     
     QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const override;
     int rowCount(const QModelIndex& parent = QModelIndex()) const override;
@@ -126,8 +128,6 @@ protected:
     bool sentByMe(const Shared::Message& msg) const;
     Attachment fillAttach(const Shared::Message& msg) const;
     Edition fillCorrection(const Shared::Message& msg) const;
-    QModelIndex modelIndexById(const QString& id) const;
-    QModelIndex modelIndexByTime(const QString& id, const QDateTime& time) const;
     std::set<MessageRoles> detectChanges(const Shared::Message& msg, const QMap<QString, QVariant>& data) const;
     
 private:

From 1f065f23e65d3d85c994a9d3d342cac14a9bccc1 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Tue, 3 May 2022 12:17:08 +0300
Subject: [PATCH 201/281] double click word selection handle, sigint
 sermentation fault fix

---
 CHANGELOG.md                               |  2 ++
 main/application.cpp                       |  4 ++-
 ui/widgets/messageline/feedview.cpp        | 31 +++++++++++++++++
 ui/widgets/messageline/feedview.h          |  1 +
 ui/widgets/messageline/messagedelegate.cpp | 39 +++++++++++++++++++---
 ui/widgets/messageline/messagedelegate.h   |  1 +
 6 files changed, 73 insertions(+), 5 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 241d61d..01fa4a2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,8 @@
 - now when you remove an account it actually gets removed
 - segfault on unitialized Availability in some rare occesions
 - fixed crash when you open a dialog with someone that has only error messages in archive
+- message height is now calculated correctly on Chinese and Japanese paragraphs
+- the app doesn't crash on SIGINT anymore
 
 ### Improvements
 - there is a way to disable an account and it wouldn't connect when you change availability
diff --git a/main/application.cpp b/main/application.cpp
index 410cd81..216e322 100644
--- a/main/application.cpp
+++ b/main/application.cpp
@@ -186,7 +186,9 @@ void Application::onSquawkClosing()
 {
     dialogueQueue.setParentWidnow(nullptr);
 
-    disconnect(core, &Core::Squawk::responseVCard, squawk, &Squawk::responseVCard);
+    if (!nowQuitting) {
+        disconnect(core, &Core::Squawk::responseVCard, squawk, &Squawk::responseVCard);
+    }
 
     destroyingSquawk = true;
     squawk->deleteLater();
diff --git a/ui/widgets/messageline/feedview.cpp b/ui/widgets/messageline/feedview.cpp
index 353d851..69b5093 100644
--- a/ui/widgets/messageline/feedview.cpp
+++ b/ui/widgets/messageline/feedview.cpp
@@ -482,6 +482,7 @@ void FeedView::mouseMoveEvent(QMouseEvent* event)
 void FeedView::mousePressEvent(QMouseEvent* event)
 {
     QAbstractItemView::mousePressEvent(event);
+
     mousePressed = event->button() == Qt::LeftButton;
     if (mousePressed) {
         dragStartPoint = event->localPos().toPoint();
@@ -499,6 +500,36 @@ void FeedView::mousePressEvent(QMouseEvent* event)
     }
 }
 
+void FeedView::mouseDoubleClickEvent(QMouseEvent* event)
+{
+    QAbstractItemView::mouseDoubleClickEvent(event);
+    mousePressed = event->button() == Qt::LeftButton;
+    if (mousePressed) {
+        dragStartPoint = event->localPos().toPoint();
+        if (specialDelegate && specialModel) {
+            MessageDelegate* del = static_cast<MessageDelegate*>(itemDelegate());
+            QString lastSelectedId = del->clearSelection();
+            selectedText = "";
+            if (lastSelectedId.size()) {
+                Models::MessageFeed* feed = static_cast<Models::MessageFeed*>(model());
+                QModelIndex index = feed->modelIndexById(lastSelectedId);
+                if (index.isValid()) {
+                    setDirtyRegion(visualRect(index));
+                }
+            }
+
+            QModelIndex index = indexAt(dragStartPoint);
+            QRect rect = visualRect(index);
+            if (rect.contains(dragStartPoint)) {
+                selectedText = del->leftDoubleClick(dragStartPoint, index, rect);
+                if (selectedText.size() > 0) {
+                    setDirtyRegion(rect);
+                }
+            }
+        }
+    }
+}
+
 void FeedView::mouseReleaseEvent(QMouseEvent* event)
 {
     QAbstractItemView::mouseReleaseEvent(event);
diff --git a/ui/widgets/messageline/feedview.h b/ui/widgets/messageline/feedview.h
index d0763a5..4194849 100644
--- a/ui/widgets/messageline/feedview.h
+++ b/ui/widgets/messageline/feedview.h
@@ -74,6 +74,7 @@ protected:
     void mouseMoveEvent(QMouseEvent * event) override;
     void mousePressEvent(QMouseEvent * event) override;
     void mouseReleaseEvent(QMouseEvent * event) override;
+    void mouseDoubleClickEvent(QMouseEvent * event) override;
     void keyPressEvent(QKeyEvent * event) override;
     void resizeEvent(QResizeEvent * event) override;
     
diff --git a/ui/widgets/messageline/messagedelegate.cpp b/ui/widgets/messageline/messagedelegate.cpp
index 840ef5c..f935057 100644
--- a/ui/widgets/messageline/messagedelegate.cpp
+++ b/ui/widgets/messageline/messagedelegate.cpp
@@ -434,6 +434,39 @@ void MessageDelegate::leftClick(const QPoint& point, const QModelIndex& index, c
     }
 }
 
+QString MessageDelegate::leftDoubleClick(const QPoint& point, const QModelIndex& index, const QRect& sizeHint)
+{
+    QVariant vi = index.data(Models::MessageFeed::Bulk);
+    Models::FeedItem data = qvariant_cast<Models::FeedItem>(vi);
+    if (data.text.size() > 0) {
+        QRect localHint = getHoveredMessageBodyRect(index, data, sizeHint);
+
+        if (localHint.contains(point)) {
+            QPoint translated = point - localHint.topLeft();
+
+            bodyRenderer->setHtml(Shared::processMessageBody(data.text));
+            bodyRenderer->setTextWidth(localHint.size().width());
+
+            QAbstractTextDocumentLayout* lay = bodyRenderer->documentLayout();
+
+            int position = lay->hitTest(translated, Qt::HitTestAccuracy::FuzzyHit);
+            QTextCursor cursor(bodyRenderer);
+            cursor.setPosition(position, QTextCursor::MoveAnchor);
+            cursor.movePosition(QTextCursor::StartOfWord, QTextCursor::MoveAnchor);
+            cursor.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor);
+
+            selection.first = cursor.anchor();
+            selection.second = cursor.position();
+            currentId = data.id;
+
+            if (selection.first != selection.second) {
+                return cursor.selectedText();
+            }
+        }
+    }
+    return "";
+}
+
 Shared::Hover MessageDelegate::hoverType(const QPoint& point, const QModelIndex& index, const QRect& sizeHint) const
 {
     QVariant vi = index.data(Models::MessageFeed::Bulk);
@@ -454,10 +487,9 @@ Shared::Hover MessageDelegate::hoverType(const QPoint& point, const QModelIndex&
                 return Shared::Hover::anchor;
             } else {
                 int position = lay->hitTest(translated, Qt::HitTestAccuracy::ExactHit);
-                if (position != -1) {           //this is a bad way, it's false positive on the end of the last
-                    return Shared::Hover::text; //line of a multiline block, so it's not better the checking the rect
+                if (position != -1) {
+                    return Shared::Hover::text;
                 }
-                //return Shared::Hover::text;
             }
         }
     }
@@ -480,7 +512,6 @@ QString MessageDelegate::mouseDrag(const QPoint& start, const QPoint& end, const
             last.setY(std::max(last.y(), 0));
             last.setY(std::min(last.y(), localHint.height()));
 
-
             bodyRenderer->setHtml(Shared::processMessageBody(data.text));
             bodyRenderer->setTextWidth(localHint.size().width());
             selection.first = bodyRenderer->documentLayout()->hitTest(first, Qt::HitTestAccuracy::FuzzyHit);
diff --git a/ui/widgets/messageline/messagedelegate.h b/ui/widgets/messageline/messagedelegate.h
index dc0fb49..322fb76 100644
--- a/ui/widgets/messageline/messagedelegate.h
+++ b/ui/widgets/messageline/messagedelegate.h
@@ -58,6 +58,7 @@ public:
     void endClearWidgets();
     void beginClearWidgets();
     void leftClick(const QPoint& point, const QModelIndex& index, const QRect& sizeHint) const;
+    QString leftDoubleClick(const QPoint& point, const QModelIndex& index, const QRect& sizeHint);
     Shared::Hover hoverType(const QPoint& point, const QModelIndex& index, const QRect& sizeHint) const;
     QString mouseDrag(const QPoint& start, const QPoint& end, const QModelIndex& index, const QRect& sizeHint);
     QString clearSelection();

From 80c5e2f2b4214656ea6b62a9c3e981c79e3c122b Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Wed, 4 May 2022 19:20:30 +0300
Subject: [PATCH 202/281] added en lolcalization file, actualized localizations

---
 CHANGELOG.md                 |    1 +
 translations/CMakeLists.txt  |    1 +
 translations/squawk.en.ts    | 1420 ++++++++++++++++++++++++++++++++++
 translations/squawk.pt_BR.ts |  347 ++++++++-
 translations/squawk.ru.ts    |  345 ++++++++-
 5 files changed, 2072 insertions(+), 42 deletions(-)
 create mode 100644 translations/squawk.en.ts

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 01fa4a2..f34bf3c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,6 +14,7 @@
 - if you filled password field and chose KWallet as a storage Squawk wouldn't ask you again for the same password
 - if left the password field empty and chose KWallet as a storage Squawk will try to get that passord from KWallet before asking you to input it
 - accounts now connect to the server asyncronously - if one is stopped on password prompt another is connecting
+- actualized translations, added English localization file
 
 ### New features
 - new "About" window with links, license, gratitudes
diff --git a/translations/CMakeLists.txt b/translations/CMakeLists.txt
index eee4f98..f70fe2b 100644
--- a/translations/CMakeLists.txt
+++ b/translations/CMakeLists.txt
@@ -1,6 +1,7 @@
 find_package(Qt5LinguistTools)
 
 set(TS_FILES
+    squawk.en.ts
     squawk.ru.ts
     squawk.pt_BR.ts
 )
diff --git a/translations/squawk.en.ts b/translations/squawk.en.ts
new file mode 100644
index 0000000..b219af0
--- /dev/null
+++ b/translations/squawk.en.ts
@@ -0,0 +1,1420 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="en_US">
+<context>
+    <name>About</name>
+    <message>
+        <location filename="../ui/widgets/about.ui" line="14"/>
+        <location filename="../build/squawk_autogen/include/ui_about.h" line="420"/>
+        <source>About Squawk</source>
+        <translatorcomment>About window header</translatorcomment>
+        <translation>About Squawk</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/about.ui" line="44"/>
+        <location filename="../build/squawk_autogen/include/ui_about.h" line="421"/>
+        <source>Squawk</source>
+        <translation>Squawk</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/about.ui" line="96"/>
+        <location filename="../build/squawk_autogen/include/ui_about.h" line="427"/>
+        <source>About</source>
+        <translatorcomment>Tab title</translatorcomment>
+        <translation>About</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/about.ui" line="115"/>
+        <location filename="../build/squawk_autogen/include/ui_about.h" line="423"/>
+        <source>XMPP (jabber) messenger</source>
+        <translation>XMPP (jabber) messenger</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/about.ui" line="122"/>
+        <location filename="../build/squawk_autogen/include/ui_about.h" line="424"/>
+        <source>(c) 2019 - 2022, Yury Gubich</source>
+        <translation>(c) 2019 - 2022, Yury Gubich</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/about.ui" line="129"/>
+        <location filename="../build/squawk_autogen/include/ui_about.h" line="425"/>
+        <source>&lt;a href=&quot;https://git.macaw.me/blue/squawk&quot;&gt;Project site&lt;/a&gt;</source>
+        <translation>&lt;a href=&quot;https://git.macaw.me/blue/squawk&quot;&gt;Project site&lt;/a&gt;</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/about.ui" line="142"/>
+        <location filename="../build/squawk_autogen/include/ui_about.h" line="426"/>
+        <source>&lt;a href=&quot;https://git.macaw.me/blue/squawk/src/branch/master/LICENSE.md&quot;&gt;License: GNU General Public License version 3&lt;/a&gt;</source>
+        <translation>&lt;a href=&quot;https://git.macaw.me/blue/squawk/src/branch/master/LICENSE.md&quot;&gt;License: GNU General Public License version 3&lt;/a&gt;</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/about.ui" line="175"/>
+        <location filename="../build/squawk_autogen/include/ui_about.h" line="432"/>
+        <source>Components</source>
+        <translatorcomment>Tab header</translatorcomment>
+        <translation>Components</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/about.ui" line="216"/>
+        <location filename="../ui/widgets/about.ui" line="312"/>
+        <location filename="../ui/widgets/about.ui" line="660"/>
+        <location filename="../build/squawk_autogen/include/ui_about.h" line="428"/>
+        <location filename="../build/squawk_autogen/include/ui_about.h" line="430"/>
+        <location filename="../build/squawk_autogen/include/ui_about.h" line="446"/>
+        <source>Version</source>
+        <translation>Version</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/about.ui" line="228"/>
+        <location filename="../ui/widgets/about.ui" line="324"/>
+        <location filename="../ui/widgets/about.ui" line="670"/>
+        <location filename="../build/squawk_autogen/include/ui_about.h" line="429"/>
+        <location filename="../build/squawk_autogen/include/ui_about.h" line="431"/>
+        <location filename="../build/squawk_autogen/include/ui_about.h" line="447"/>
+        <source>0.0.0</source>
+        <translation>0.0.0</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/about.ui" line="397"/>
+        <location filename="../build/squawk_autogen/include/ui_about.h" line="438"/>
+        <source>Report Bugs</source>
+        <translation>Report Bugs</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/about.ui" line="416"/>
+        <location filename="../build/squawk_autogen/include/ui_about.h" line="433"/>
+        <source>Please report any bug you find!
+To report bugs you can use:</source>
+        <translation>Please report any bug you find!
+To report bugs you can use:</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/about.ui" line="424"/>
+        <location filename="../build/squawk_autogen/include/ui_about.h" line="435"/>
+        <source>&lt;a href=&quot;https://git.macaw.me/blue/squawk/issues&quot;&gt;Project bug tracker&lt;/&gt;</source>
+        <translation>&lt;a href=&quot;https://git.macaw.me/blue/squawk/issues&quot;&gt;Project bug tracker&lt;/&gt;</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/about.ui" line="434"/>
+        <location filename="../build/squawk_autogen/include/ui_about.h" line="436"/>
+        <source>XMPP (&lt;a href=&quot;xmpp:blue@macaw.me&quot;&gt;blue@macaw.me&lt;/a&gt;)</source>
+        <translation>XMPP (&lt;a href=&quot;xmpp:blue@macaw.me&quot;&gt;blue@macaw.me&lt;/a&gt;)</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/about.ui" line="444"/>
+        <location filename="../build/squawk_autogen/include/ui_about.h" line="437"/>
+        <source>E-Mail (&lt;a href=&quot;mailto:blue@macaw.me&quot;&gt;blue@macaw.me&lt;/a&gt;)</source>
+        <translation>E-Mail (&lt;a href=&quot;mailto:blue@macaw.me&quot;&gt;blue@macaw.me&lt;/a&gt;)</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/about.ui" line="477"/>
+        <location filename="../build/squawk_autogen/include/ui_about.h" line="445"/>
+        <source>Thanks To</source>
+        <translation>Thanks to</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/about.ui" line="519"/>
+        <location filename="../build/squawk_autogen/include/ui_about.h" line="439"/>
+        <source>Vae</source>
+        <translation>Vae</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/about.ui" line="531"/>
+        <location filename="../build/squawk_autogen/include/ui_about.h" line="440"/>
+        <source>Major refactoring, bug fixes, constructive criticism</source>
+        <translation>Major refactoring, bug fixes, constructive criticism</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/about.ui" line="568"/>
+        <location filename="../build/squawk_autogen/include/ui_about.h" line="441"/>
+        <source>Shunf4</source>
+        <translation>Shunf4</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/about.ui" line="580"/>
+        <location filename="../build/squawk_autogen/include/ui_about.h" line="442"/>
+        <source>Major refactoring, bug fixes, build adaptations for Windows and MacOS</source>
+        <translation>Major refactoring, bug fixes, build adaptations for Windows and MacOS</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/about.ui" line="617"/>
+        <location filename="../build/squawk_autogen/include/ui_about.h" line="443"/>
+        <source>Bruno F. Fontes</source>
+        <translation>Bruno F. Fontes</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/about.ui" line="629"/>
+        <location filename="../build/squawk_autogen/include/ui_about.h" line="444"/>
+        <source>Brazilian Portuguese translation</source>
+        <translation>Brazilian Portuguese translation</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/about.cpp" line="35"/>
+        <location filename="../ui/widgets/about.cpp" line="38"/>
+        <source>(built against %1)</source>
+        <translation>(built against %1)</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/about.cpp" line="71"/>
+        <source>License</source>
+        <translation>License</translation>
+    </message>
+</context>
+<context>
+    <name>Account</name>
+    <message>
+        <location filename="../ui/widgets/accounts/account.ui" line="14"/>
+        <location filename="../build/squawk_autogen/include/ui_account.h" line="166"/>
+        <source>Account</source>
+        <translatorcomment>Window title</translatorcomment>
+        <translation>Account</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/accounts/account.ui" line="40"/>
+        <location filename="../build/squawk_autogen/include/ui_account.h" line="168"/>
+        <source>Your account login</source>
+        <translatorcomment>Tooltip</translatorcomment>
+        <translation>Your account login</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/accounts/account.ui" line="43"/>
+        <location filename="../build/squawk_autogen/include/ui_account.h" line="170"/>
+        <source>john_smith1987</source>
+        <translatorcomment>Login placeholder</translatorcomment>
+        <translation>john_smith1987</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/accounts/account.ui" line="50"/>
+        <location filename="../build/squawk_autogen/include/ui_account.h" line="171"/>
+        <source>Server</source>
+        <translation>Server</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/accounts/account.ui" line="57"/>
+        <location filename="../build/squawk_autogen/include/ui_account.h" line="173"/>
+        <source>A server address of your account. Like 404.city or macaw.me</source>
+        <translatorcomment>Tooltip</translatorcomment>
+        <translation>A server address of your account. Like 404.city or macaw.me</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/accounts/account.ui" line="60"/>
+        <location filename="../build/squawk_autogen/include/ui_account.h" line="175"/>
+        <source>macaw.me</source>
+        <translatorcomment>Placeholder</translatorcomment>
+        <translation>macaw.me</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/accounts/account.ui" line="67"/>
+        <location filename="../build/squawk_autogen/include/ui_account.h" line="176"/>
+        <source>Login</source>
+        <translation>Login</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/accounts/account.ui" line="74"/>
+        <location filename="../build/squawk_autogen/include/ui_account.h" line="177"/>
+        <source>Password</source>
+        <translation>Password</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/accounts/account.ui" line="81"/>
+        <location filename="../build/squawk_autogen/include/ui_account.h" line="179"/>
+        <source>Password of your account</source>
+        <translatorcomment>Tooltip</translatorcomment>
+        <translation>Password of your account</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/accounts/account.ui" line="103"/>
+        <location filename="../build/squawk_autogen/include/ui_account.h" line="184"/>
+        <source>Name</source>
+        <translation>Name</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/accounts/account.ui" line="110"/>
+        <location filename="../build/squawk_autogen/include/ui_account.h" line="186"/>
+        <source>Just a name how would you call this account, doesn&apos;t affect anything</source>
+        <translation>Just a name how would you call this account, doesn&apos;t affect anything (cant be changed)</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/accounts/account.ui" line="113"/>
+        <location filename="../build/squawk_autogen/include/ui_account.h" line="188"/>
+        <source>John</source>
+        <translatorcomment>Placeholder</translatorcomment>
+        <translation>John</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/accounts/account.ui" line="120"/>
+        <location filename="../build/squawk_autogen/include/ui_account.h" line="189"/>
+        <source>Resource</source>
+        <translation>Resource</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/accounts/account.ui" line="127"/>
+        <location filename="../build/squawk_autogen/include/ui_account.h" line="191"/>
+        <source>A resource name like &quot;Home&quot; or &quot;Work&quot;</source>
+        <translatorcomment>Tooltip</translatorcomment>
+        <translation>A resource name like &quot;Home&quot; or &quot;Work&quot;</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/accounts/account.ui" line="130"/>
+        <location filename="../build/squawk_autogen/include/ui_account.h" line="193"/>
+        <source>QXmpp</source>
+        <translatorcomment>Default resource</translatorcomment>
+        <translation>QXmpp</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/accounts/account.ui" line="137"/>
+        <location filename="../build/squawk_autogen/include/ui_account.h" line="194"/>
+        <source>Password storage</source>
+        <translation>Password storage</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/accounts/account.ui" line="163"/>
+        <location filename="../build/squawk_autogen/include/ui_account.h" line="196"/>
+        <source>Active</source>
+        <translation>Active</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/accounts/account.ui" line="170"/>
+        <location filename="../build/squawk_autogen/include/ui_account.h" line="197"/>
+        <source>enable</source>
+        <translation>enable</translation>
+    </message>
+</context>
+<context>
+    <name>Accounts</name>
+    <message>
+        <location filename="../ui/widgets/accounts/accounts.ui" line="14"/>
+        <location filename="../build/squawk_autogen/include/ui_accounts.h" line="108"/>
+        <source>Accounts</source>
+        <translation>Accounts</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/accounts/accounts.ui" line="45"/>
+        <location filename="../build/squawk_autogen/include/ui_accounts.h" line="109"/>
+        <source>Delete</source>
+        <translation>Delete</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/accounts/accounts.ui" line="86"/>
+        <location filename="../build/squawk_autogen/include/ui_accounts.h" line="110"/>
+        <source>Add</source>
+        <translation>Add</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/accounts/accounts.ui" line="96"/>
+        <location filename="../build/squawk_autogen/include/ui_accounts.h" line="111"/>
+        <source>Edit</source>
+        <translation>Edit</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/accounts/accounts.ui" line="106"/>
+        <location filename="../build/squawk_autogen/include/ui_accounts.h" line="112"/>
+        <source>Change password</source>
+        <translation>Change password</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/accounts/accounts.ui" line="129"/>
+        <location filename="../build/squawk_autogen/include/ui_accounts.h" line="113"/>
+        <source>Connect</source>
+        <translation>Connect</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/accounts/accounts.cpp" line="126"/>
+        <source>Deactivate</source>
+        <translation>Deactivate</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/accounts/accounts.cpp" line="129"/>
+        <location filename="../ui/widgets/accounts/accounts.cpp" line="132"/>
+        <source>Activate</source>
+        <translation>Activate</translation>
+    </message>
+</context>
+<context>
+    <name>Application</name>
+    <message>
+        <location filename="../main/application.cpp" line="225"/>
+        <source> from </source>
+        <translation> from </translation>
+    </message>
+    <message>
+        <location filename="../main/application.cpp" line="233"/>
+        <source>Attached file</source>
+        <translation>Attached file</translation>
+    </message>
+    <message>
+        <location filename="../main/application.cpp" line="238"/>
+        <source>Mark as Read</source>
+        <translation>Mark as Read</translation>
+    </message>
+    <message>
+        <location filename="../main/application.cpp" line="239"/>
+        <source>Open conversation</source>
+        <translation>Open conversation</translation>
+    </message>
+</context>
+<context>
+    <name>Conversation</name>
+    <message>
+        <location filename="../ui/widgets/conversation.ui" line="426"/>
+        <location filename="../build/squawk_autogen/include/ui_conversation.h" line="310"/>
+        <source>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;Noto Sans&apos;; font-size:8pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;Noto Sans&apos;; font-size:8pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/conversation.ui" line="436"/>
+        <location filename="../build/squawk_autogen/include/ui_conversation.h" line="315"/>
+        <source>Type your message here...</source>
+        <translatorcomment>Placeholder</translatorcomment>
+        <translation>Type your message here...</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/conversation.cpp" line="58"/>
+        <source>Paste Image</source>
+        <translation>Paste Image</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/conversation.cpp" line="141"/>
+        <source>Drop files here to attach them to your message</source>
+        <translation>Drop files here to attach them to your message</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/conversation.cpp" line="277"/>
+        <source>Chose a file to send</source>
+        <translation>Chose a file to send</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/conversation.cpp" line="495"/>
+        <source>Try sending again</source>
+        <translation>Try sending again</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/conversation.cpp" line="505"/>
+        <source>Copy selected</source>
+        <translation>Copy selected</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/conversation.cpp" line="515"/>
+        <source>Copy message</source>
+        <translation>Copy message</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/conversation.cpp" line="525"/>
+        <source>Open</source>
+        <translation>Open</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/conversation.cpp" line="530"/>
+        <source>Show in folder</source>
+        <translation>Show in folder</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/conversation.cpp" line="540"/>
+        <source>Edit</source>
+        <translation>Edit</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/conversation.cpp" line="570"/>
+        <source>Editing message...</source>
+        <translation>Editing message...</translation>
+    </message>
+</context>
+<context>
+    <name>CredentialsPrompt</name>
+    <message>
+        <location filename="../ui/widgets/accounts/credentialsprompt.ui" line="14"/>
+        <location filename="../build/squawk_autogen/include/ui_credentialsprompt.h" line="106"/>
+        <source>Authentication error: %1</source>
+        <translatorcomment>Window title</translatorcomment>
+        <translation>Authentication error: %1</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/accounts/credentialsprompt.ui" line="29"/>
+        <location filename="../build/squawk_autogen/include/ui_credentialsprompt.h" line="107"/>
+        <source>Couldn&apos;t authenticate account %1: login or password is icorrect.
+Would you like to check them and try again?</source>
+        <translation>Couldn&apos;t authenticate account %1: login or password is incorrect.
+Would you like to check them and try again?</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/accounts/credentialsprompt.ui" line="45"/>
+        <location filename="../build/squawk_autogen/include/ui_credentialsprompt.h" line="109"/>
+        <source>Login</source>
+        <translation>Login</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/accounts/credentialsprompt.ui" line="52"/>
+        <location filename="../build/squawk_autogen/include/ui_credentialsprompt.h" line="111"/>
+        <source>Your account login (without @server.domain)</source>
+        <translatorcomment>Tooltip</translatorcomment>
+        <translation>Your account login (without @server.domain)</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/accounts/credentialsprompt.ui" line="59"/>
+        <location filename="../build/squawk_autogen/include/ui_credentialsprompt.h" line="113"/>
+        <source>Password</source>
+        <translation>Password</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/accounts/credentialsprompt.ui" line="66"/>
+        <location filename="../build/squawk_autogen/include/ui_credentialsprompt.h" line="115"/>
+        <source>Your password</source>
+        <translation>Password</translation>
+    </message>
+</context>
+<context>
+    <name>DialogQueue</name>
+    <message>
+        <location filename="../main/dialogqueue.cpp" line="99"/>
+        <source>Input the password for account %1</source>
+        <translation>Input the password for account %1</translation>
+    </message>
+    <message>
+        <location filename="../main/dialogqueue.cpp" line="100"/>
+        <source>Password for account %1</source>
+        <translation>Password for account %1</translation>
+    </message>
+</context>
+<context>
+    <name>Global</name>
+    <message>
+        <location filename="../shared/global.cpp" line="42"/>
+        <source>Online</source>
+        <comment>Availability</comment>
+        <translation>Online</translation>
+    </message>
+    <message>
+        <location filename="../shared/global.cpp" line="43"/>
+        <source>Away</source>
+        <comment>Availability</comment>
+        <translation>Away</translation>
+    </message>
+    <message>
+        <location filename="../shared/global.cpp" line="44"/>
+        <source>Absent</source>
+        <comment>Availability</comment>
+        <translation>Absent</translation>
+    </message>
+    <message>
+        <location filename="../shared/global.cpp" line="45"/>
+        <source>Busy</source>
+        <comment>Availability</comment>
+        <translation>Busy</translation>
+    </message>
+    <message>
+        <location filename="../shared/global.cpp" line="46"/>
+        <source>Chatty</source>
+        <comment>Availability</comment>
+        <translation>Chatty</translation>
+    </message>
+    <message>
+        <location filename="../shared/global.cpp" line="47"/>
+        <source>Invisible</source>
+        <comment>Availability</comment>
+        <translation>Invisible</translation>
+    </message>
+    <message>
+        <location filename="../shared/global.cpp" line="48"/>
+        <source>Offline</source>
+        <comment>Availability</comment>
+        <translation>Offline</translation>
+    </message>
+    <message>
+        <location filename="../shared/global.cpp" line="51"/>
+        <source>Disconnected</source>
+        <comment>ConnectionState</comment>
+        <translation>Disconnected</translation>
+    </message>
+    <message>
+        <location filename="../shared/global.cpp" line="52"/>
+        <source>Connecting</source>
+        <comment>ConnectionState</comment>
+        <translation>Connecting</translation>
+    </message>
+    <message>
+        <location filename="../shared/global.cpp" line="53"/>
+        <source>Connected</source>
+        <comment>ConnectionState</comment>
+        <translation>Connected</translation>
+    </message>
+    <message>
+        <location filename="../shared/global.cpp" line="54"/>
+        <source>Error</source>
+        <comment>ConnectionState</comment>
+        <translation>Error</translation>
+    </message>
+    <message>
+        <location filename="../shared/global.cpp" line="57"/>
+        <source>None</source>
+        <comment>SubscriptionState</comment>
+        <translation>None</translation>
+    </message>
+    <message>
+        <location filename="../shared/global.cpp" line="58"/>
+        <source>From</source>
+        <comment>SubscriptionState</comment>
+        <translation>From</translation>
+    </message>
+    <message>
+        <location filename="../shared/global.cpp" line="59"/>
+        <source>To</source>
+        <comment>SubscriptionState</comment>
+        <translation>To</translation>
+    </message>
+    <message>
+        <location filename="../shared/global.cpp" line="60"/>
+        <source>Both</source>
+        <comment>SubscriptionState</comment>
+        <translation>Both</translation>
+    </message>
+    <message>
+        <location filename="../shared/global.cpp" line="61"/>
+        <source>Unknown</source>
+        <comment>SubscriptionState</comment>
+        <translation>Unknown</translation>
+    </message>
+    <message>
+        <location filename="../shared/global.cpp" line="64"/>
+        <source>Unspecified</source>
+        <comment>Affiliation</comment>
+        <translation>Unspecified</translation>
+    </message>
+    <message>
+        <location filename="../shared/global.cpp" line="65"/>
+        <source>Outcast</source>
+        <comment>Affiliation</comment>
+        <translation>Outcast</translation>
+    </message>
+    <message>
+        <location filename="../shared/global.cpp" line="66"/>
+        <source>Nobody</source>
+        <comment>Affiliation</comment>
+        <translation>Nobody</translation>
+    </message>
+    <message>
+        <location filename="../shared/global.cpp" line="67"/>
+        <source>Member</source>
+        <comment>Affiliation</comment>
+        <translation>Member</translation>
+    </message>
+    <message>
+        <location filename="../shared/global.cpp" line="68"/>
+        <source>Admin</source>
+        <comment>Affiliation</comment>
+        <translation>Admin</translation>
+    </message>
+    <message>
+        <location filename="../shared/global.cpp" line="69"/>
+        <source>Owner</source>
+        <comment>Affiliation</comment>
+        <translation>Owner</translation>
+    </message>
+    <message>
+        <location filename="../shared/global.cpp" line="72"/>
+        <source>Unspecified</source>
+        <comment>Role</comment>
+        <translation>Unspecified</translation>
+    </message>
+    <message>
+        <location filename="../shared/global.cpp" line="73"/>
+        <source>Nobody</source>
+        <comment>Role</comment>
+        <translation>Nobody</translation>
+    </message>
+    <message>
+        <location filename="../shared/global.cpp" line="74"/>
+        <source>Visitor</source>
+        <comment>Role</comment>
+        <translation>Visitor</translation>
+    </message>
+    <message>
+        <location filename="../shared/global.cpp" line="75"/>
+        <source>Participant</source>
+        <comment>Role</comment>
+        <translation>Participant</translation>
+    </message>
+    <message>
+        <location filename="../shared/global.cpp" line="76"/>
+        <source>Moderator</source>
+        <comment>Role</comment>
+        <translation>Moderator</translation>
+    </message>
+    <message>
+        <location filename="../shared/global.cpp" line="79"/>
+        <source>Pending</source>
+        <comment>MessageState</comment>
+        <translation>Pending</translation>
+    </message>
+    <message>
+        <location filename="../shared/global.cpp" line="80"/>
+        <source>Sent</source>
+        <comment>MessageState</comment>
+        <translation>Sent</translation>
+    </message>
+    <message>
+        <location filename="../shared/global.cpp" line="81"/>
+        <source>Delivered</source>
+        <comment>MessageState</comment>
+        <translation>Delivered</translation>
+    </message>
+    <message>
+        <location filename="../shared/global.cpp" line="82"/>
+        <source>Error</source>
+        <comment>MessageState</comment>
+        <translation>Error</translation>
+    </message>
+    <message>
+        <location filename="../shared/global.cpp" line="85"/>
+        <source>Plain</source>
+        <comment>AccountPassword</comment>
+        <translation>Plain</translation>
+    </message>
+    <message>
+        <location filename="../shared/global.cpp" line="86"/>
+        <source>Jammed</source>
+        <comment>AccountPassword</comment>
+        <translation>Jammed</translation>
+    </message>
+    <message>
+        <location filename="../shared/global.cpp" line="87"/>
+        <source>Always Ask</source>
+        <comment>AccountPassword</comment>
+        <translation>Always Ask</translation>
+    </message>
+    <message>
+        <location filename="../shared/global.cpp" line="88"/>
+        <source>KWallet</source>
+        <comment>AccountPassword</comment>
+        <translation>KWallet</translation>
+    </message>
+    <message>
+        <location filename="../shared/global.cpp" line="91"/>
+        <source>Your password is going to be stored in config file in plain text</source>
+        <comment>AccountPasswordDescription</comment>
+        <translation>Your password is going to be stored in config file in plain text</translation>
+    </message>
+    <message>
+        <location filename="../shared/global.cpp" line="92"/>
+        <source>Your password is going to be stored in config file but jammed with constant encryption key you can find in program source code. It might look like encryption but it&apos;s not</source>
+        <comment>AccountPasswordDescription</comment>
+        <translation>Your password is going to be stored in config file but jammed with constant encryption key you can find in program source code. It might look like encryption but it&apos;s not</translation>
+    </message>
+    <message>
+        <location filename="../shared/global.cpp" line="93"/>
+        <source>Squawk is going to query you for the password on every start of the program</source>
+        <comment>AccountPasswordDescription</comment>
+        <translation>Squawk is going to query you for the password on every start of the program</translation>
+    </message>
+    <message>
+        <location filename="../shared/global.cpp" line="94"/>
+        <source>Your password is going to be stored in KDE wallet storage (KWallet). You&apos;re going to be queried for permissions</source>
+        <comment>AccountPasswordDescription</comment>
+        <translation>Your password is going to be stored in KDE wallet storage (KWallet). You&apos;re going to be queried for permissions</translation>
+    </message>
+</context>
+<context>
+    <name>JoinConference</name>
+    <message>
+        <location filename="../ui/widgets/joinconference.ui" line="14"/>
+        <location filename="../build/squawk_autogen/include/ui_joinconference.h" line="116"/>
+        <source>Join new conference</source>
+        <translation>Join new conference</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/joinconference.ui" line="22"/>
+        <location filename="../build/squawk_autogen/include/ui_joinconference.h" line="117"/>
+        <source>JID</source>
+        <translation>JID</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/joinconference.ui" line="29"/>
+        <location filename="../build/squawk_autogen/include/ui_joinconference.h" line="119"/>
+        <source>Room JID</source>
+        <translation>Room JID</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/joinconference.ui" line="32"/>
+        <location filename="../build/squawk_autogen/include/ui_joinconference.h" line="121"/>
+        <source>identifier@conference.server.org</source>
+        <translation>identifier@conference.server.org</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/joinconference.ui" line="39"/>
+        <location filename="../build/squawk_autogen/include/ui_joinconference.h" line="122"/>
+        <source>Account</source>
+        <translation>Account</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/joinconference.ui" line="49"/>
+        <location filename="../build/squawk_autogen/include/ui_joinconference.h" line="123"/>
+        <source>Join on login</source>
+        <translation>Join on login</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/joinconference.ui" line="56"/>
+        <location filename="../build/squawk_autogen/include/ui_joinconference.h" line="125"/>
+        <source>If checked Squawk will try to join this conference on login</source>
+        <translation>If checked Squawk will try to join this conference on login</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/joinconference.ui" line="66"/>
+        <location filename="../build/squawk_autogen/include/ui_joinconference.h" line="128"/>
+        <source>Nick name</source>
+        <translation>Nick name</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/joinconference.ui" line="73"/>
+        <location filename="../build/squawk_autogen/include/ui_joinconference.h" line="130"/>
+        <source>Your nick name for that conference. If you leave this field empty your account name will be used as a nick name</source>
+        <translation>Your nick name for that conference. If you leave this field empty your account name will be used as a nick name</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/joinconference.ui" line="76"/>
+        <location filename="../build/squawk_autogen/include/ui_joinconference.h" line="132"/>
+        <source>John</source>
+        <translation>John</translation>
+    </message>
+</context>
+<context>
+    <name>MessageLine</name>
+    <message>
+        <location filename="../ui/widgets/messageline/messagedelegate.cpp" line="65"/>
+        <location filename="../ui/widgets/messageline/messagedelegate.cpp" line="668"/>
+        <source>Download</source>
+        <translation>Download</translation>
+    </message>
+</context>
+<context>
+    <name>Models::Room</name>
+    <message>
+        <location filename="../ui/models/room.cpp" line="191"/>
+        <source>Subscribed</source>
+        <translation>Subscribed</translation>
+    </message>
+    <message>
+        <location filename="../ui/models/room.cpp" line="193"/>
+        <source>Temporarily unsubscribed</source>
+        <translation>Temporarily unsubscribed</translation>
+    </message>
+    <message>
+        <location filename="../ui/models/room.cpp" line="197"/>
+        <source>Temporarily subscribed</source>
+        <translation>Temporarily subscribed</translation>
+    </message>
+    <message>
+        <location filename="../ui/models/room.cpp" line="199"/>
+        <source>Unsubscribed</source>
+        <translation>Unsubscribed</translation>
+    </message>
+</context>
+<context>
+    <name>Models::Roster</name>
+    <message>
+        <location filename="../ui/models/roster.cpp" line="83"/>
+        <source>New messages</source>
+        <translation>New messages</translation>
+    </message>
+    <message>
+        <location filename="../ui/models/roster.cpp" line="196"/>
+        <location filename="../ui/models/roster.cpp" line="251"/>
+        <location filename="../ui/models/roster.cpp" line="263"/>
+        <source>New messages: </source>
+        <translation>New messages: </translation>
+    </message>
+    <message>
+        <location filename="../ui/models/roster.cpp" line="198"/>
+        <location filename="../ui/models/roster.cpp" line="266"/>
+        <source>Jabber ID: </source>
+        <translation>Jabber ID: </translation>
+    </message>
+    <message>
+        <location filename="../ui/models/roster.cpp" line="202"/>
+        <location filename="../ui/models/roster.cpp" line="221"/>
+        <location filename="../ui/models/roster.cpp" line="234"/>
+        <source>Availability: </source>
+        <translation>Availability: </translation>
+    </message>
+    <message>
+        <location filename="../ui/models/roster.cpp" line="206"/>
+        <location filename="../ui/models/roster.cpp" line="224"/>
+        <location filename="../ui/models/roster.cpp" line="237"/>
+        <source>Status: </source>
+        <translation>Status: </translation>
+    </message>
+    <message>
+        <location filename="../ui/models/roster.cpp" line="209"/>
+        <location filename="../ui/models/roster.cpp" line="211"/>
+        <location filename="../ui/models/roster.cpp" line="267"/>
+        <source>Subscription: </source>
+        <translation>Subscription: </translation>
+    </message>
+    <message>
+        <location filename="../ui/models/roster.cpp" line="240"/>
+        <source>Affiliation: </source>
+        <translation>Affiliation: </translation>
+    </message>
+    <message>
+        <location filename="../ui/models/roster.cpp" line="241"/>
+        <source>Role: </source>
+        <translation>Role: </translation>
+    </message>
+    <message>
+        <location filename="../ui/models/roster.cpp" line="253"/>
+        <source>Online contacts: </source>
+        <translation>Online contacts: </translation>
+    </message>
+    <message>
+        <location filename="../ui/models/roster.cpp" line="254"/>
+        <source>Total contacts: </source>
+        <translation>Total contacts: </translation>
+    </message>
+    <message>
+        <location filename="../ui/models/roster.cpp" line="269"/>
+        <source>Members: </source>
+        <translation>Members: </translation>
+    </message>
+</context>
+<context>
+    <name>NewContact</name>
+    <message>
+        <location filename="../ui/widgets/newcontact.ui" line="14"/>
+        <location filename="../build/squawk_autogen/include/ui_newcontact.h" line="103"/>
+        <source>Add new contact</source>
+        <translatorcomment>Window title</translatorcomment>
+        <translation>Add new contact</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/newcontact.ui" line="22"/>
+        <location filename="../build/squawk_autogen/include/ui_newcontact.h" line="104"/>
+        <source>Account</source>
+        <translation>Account</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/newcontact.ui" line="29"/>
+        <location filename="../build/squawk_autogen/include/ui_newcontact.h" line="106"/>
+        <source>An account that is going to have new contact</source>
+        <translation>An account that is going to have new contact</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/newcontact.ui" line="36"/>
+        <location filename="../build/squawk_autogen/include/ui_newcontact.h" line="108"/>
+        <source>JID</source>
+        <translation>JID</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/newcontact.ui" line="43"/>
+        <location filename="../build/squawk_autogen/include/ui_newcontact.h" line="110"/>
+        <source>Jabber id of your new contact</source>
+        <translation>Jabber id of your new contact</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/newcontact.ui" line="46"/>
+        <location filename="../build/squawk_autogen/include/ui_newcontact.h" line="112"/>
+        <source>name@server.dmn</source>
+        <translatorcomment>Placeholder</translatorcomment>
+        <translation>name@server.dmn</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/newcontact.ui" line="53"/>
+        <location filename="../build/squawk_autogen/include/ui_newcontact.h" line="113"/>
+        <source>Name</source>
+        <translation>Name</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/newcontact.ui" line="60"/>
+        <location filename="../build/squawk_autogen/include/ui_newcontact.h" line="115"/>
+        <source>The way this new contact will be labeled in your roster (optional)</source>
+        <translation>The way this new contact will be labeled in your roster (optional)</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/newcontact.ui" line="63"/>
+        <location filename="../build/squawk_autogen/include/ui_newcontact.h" line="117"/>
+        <source>John Smith</source>
+        <translation>John Smith</translation>
+    </message>
+</context>
+<context>
+    <name>PageAppearance</name>
+    <message>
+        <location filename="../ui/widgets/settings/pageappearance.ui" line="17"/>
+        <location filename="../build/squawk_autogen/include/ui_pageappearance.h" line="65"/>
+        <source>Theme</source>
+        <translation>Style</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/settings/pageappearance.ui" line="30"/>
+        <location filename="../build/squawk_autogen/include/ui_pageappearance.h" line="66"/>
+        <source>Color scheme</source>
+        <translation>Color scheme</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/settings/pageappearance.cpp" line="36"/>
+        <location filename="../ui/widgets/settings/pageappearance.cpp" line="49"/>
+        <location filename="../ui/widgets/settings/pageappearance.cpp" line="56"/>
+        <location filename="../ui/widgets/settings/pageappearance.cpp" line="59"/>
+        <source>System</source>
+        <translation>System</translation>
+    </message>
+</context>
+<context>
+    <name>PageGeneral</name>
+    <message>
+        <location filename="../ui/widgets/settings/pagegeneral.ui" line="17"/>
+        <location filename="../build/squawk_autogen/include/ui_pagegeneral.h" line="69"/>
+        <source>Downloads path</source>
+        <translation>Downloads path</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/settings/pagegeneral.ui" line="36"/>
+        <location filename="../build/squawk_autogen/include/ui_pagegeneral.h" line="70"/>
+        <source>Browse</source>
+        <translation>Browse</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/settings/pagegeneral.cpp" line="45"/>
+        <source>Select where downloads folder is going to be</source>
+        <translation>Select where downloads folder is going to be</translation>
+    </message>
+</context>
+<context>
+    <name>Settings</name>
+    <message>
+        <location filename="../ui/widgets/settings/settings.ui" line="14"/>
+        <location filename="../build/squawk_autogen/include/ui_settings.h" line="188"/>
+        <source>Preferences</source>
+        <translatorcomment>Window title</translatorcomment>
+        <translation>Preferences</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/settings/settings.ui" line="105"/>
+        <location filename="../ui/widgets/settings/settings.ui" line="177"/>
+        <location filename="../build/squawk_autogen/include/ui_settings.h" line="193"/>
+        <location filename="../build/squawk_autogen/include/ui_settings.h" line="201"/>
+        <source>General</source>
+        <translation>General</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/settings/settings.ui" line="117"/>
+        <location filename="../build/squawk_autogen/include/ui_settings.h" line="195"/>
+        <source>Appearance</source>
+        <translation>Appearance</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/settings/settings.ui" line="141"/>
+        <location filename="../build/squawk_autogen/include/ui_settings.h" line="198"/>
+        <source>Apply</source>
+        <translation>Apply</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/settings/settings.ui" line="152"/>
+        <location filename="../build/squawk_autogen/include/ui_settings.h" line="199"/>
+        <source>Cancel</source>
+        <translation>Cancel</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/settings/settings.ui" line="163"/>
+        <location filename="../build/squawk_autogen/include/ui_settings.h" line="200"/>
+        <source>Ok</source>
+        <translation>Ok</translation>
+    </message>
+</context>
+<context>
+    <name>Squawk</name>
+    <message>
+        <location filename="../ui/squawk.ui" line="14"/>
+        <location filename="../build/squawk_autogen/include/ui_squawk.h" line="220"/>
+        <source>squawk</source>
+        <translation>Squawk</translation>
+    </message>
+    <message>
+        <location filename="../ui/squawk.ui" line="161"/>
+        <location filename="../build/squawk_autogen/include/ui_squawk.h" line="228"/>
+        <source>Please select a contact to start chatting</source>
+        <translation>Please select a contact to start chatting</translation>
+    </message>
+    <message>
+        <location filename="../ui/squawk.ui" line="191"/>
+        <location filename="../build/squawk_autogen/include/ui_squawk.h" line="229"/>
+        <source>Settings</source>
+        <translation>Settings</translation>
+    </message>
+    <message>
+        <location filename="../ui/squawk.ui" line="198"/>
+        <location filename="../build/squawk_autogen/include/ui_squawk.h" line="230"/>
+        <source>Squawk</source>
+        <translatorcomment>Menu bar entry</translatorcomment>
+        <translation>Squawk</translation>
+    </message>
+    <message>
+        <location filename="../ui/squawk.ui" line="206"/>
+        <location filename="../build/squawk_autogen/include/ui_squawk.h" line="231"/>
+        <source>Help</source>
+        <translation>Help</translation>
+    </message>
+    <message>
+        <location filename="../ui/squawk.ui" line="220"/>
+        <location filename="../build/squawk_autogen/include/ui_squawk.h" line="221"/>
+        <source>Accounts</source>
+        <translation>Accounts</translation>
+    </message>
+    <message>
+        <location filename="../ui/squawk.ui" line="229"/>
+        <location filename="../build/squawk_autogen/include/ui_squawk.h" line="222"/>
+        <source>Quit</source>
+        <translation>Quit</translation>
+    </message>
+    <message>
+        <location filename="../ui/squawk.ui" line="241"/>
+        <location filename="../build/squawk_autogen/include/ui_squawk.h" line="223"/>
+        <source>Add contact</source>
+        <translation>Add contact</translation>
+    </message>
+    <message>
+        <location filename="../ui/squawk.ui" line="253"/>
+        <location filename="../build/squawk_autogen/include/ui_squawk.h" line="224"/>
+        <source>Add conference</source>
+        <translation>Join conference</translation>
+    </message>
+    <message>
+        <location filename="../ui/squawk.ui" line="262"/>
+        <location filename="../build/squawk_autogen/include/ui_squawk.h" line="225"/>
+        <source>Preferences</source>
+        <translation>Preferences</translation>
+    </message>
+    <message>
+        <location filename="../ui/squawk.ui" line="267"/>
+        <location filename="../build/squawk_autogen/include/ui_squawk.h" line="226"/>
+        <source>About Squawk</source>
+        <translation>About Squawk</translation>
+    </message>
+    <message>
+        <location filename="../ui/squawk.cpp" line="284"/>
+        <source>Deactivate</source>
+        <translation>Deactivate</translation>
+    </message>
+    <message>
+        <location filename="../ui/squawk.cpp" line="287"/>
+        <source>Activate</source>
+        <translation>Activate</translation>
+    </message>
+    <message>
+        <location filename="../ui/squawk.cpp" line="291"/>
+        <location filename="../ui/squawk.cpp" line="378"/>
+        <source>VCard</source>
+        <translation>VCard</translation>
+    </message>
+    <message>
+        <location filename="../ui/squawk.cpp" line="295"/>
+        <location filename="../ui/squawk.cpp" line="382"/>
+        <location filename="../ui/squawk.cpp" line="410"/>
+        <source>Remove</source>
+        <translation>Remove</translation>
+    </message>
+    <message>
+        <location filename="../ui/squawk.cpp" line="305"/>
+        <source>Open dialog</source>
+        <translation>Open dialog</translation>
+    </message>
+    <message>
+        <location filename="../ui/squawk.cpp" line="313"/>
+        <location filename="../ui/squawk.cpp" line="401"/>
+        <source>Unsubscribe</source>
+        <translation>Unsubscribe</translation>
+    </message>
+    <message>
+        <location filename="../ui/squawk.cpp" line="321"/>
+        <location filename="../ui/squawk.cpp" line="405"/>
+        <source>Subscribe</source>
+        <translation>Subscribe</translation>
+    </message>
+    <message>
+        <location filename="../ui/squawk.cpp" line="327"/>
+        <source>Rename</source>
+        <translation>Rename</translation>
+    </message>
+    <message>
+        <location filename="../ui/squawk.cpp" line="340"/>
+        <source>Input new name for %1
+or leave it empty for the contact 
+to be displayed as %1</source>
+        <translation>Input new name for %1
+or leave it empty for the contact 
+to be displayed as %1</translation>
+    </message>
+    <message>
+        <location filename="../ui/squawk.cpp" line="341"/>
+        <source>Renaming %1</source>
+        <translation>Renaming %1</translation>
+    </message>
+    <message>
+        <location filename="../ui/squawk.cpp" line="347"/>
+        <source>Groups</source>
+        <translation>Groups</translation>
+    </message>
+    <message>
+        <location filename="../ui/squawk.cpp" line="362"/>
+        <source>New group</source>
+        <translation>New group</translation>
+    </message>
+    <message>
+        <location filename="../ui/squawk.cpp" line="372"/>
+        <source>New group name</source>
+        <translation>New group name</translation>
+    </message>
+    <message>
+        <location filename="../ui/squawk.cpp" line="373"/>
+        <source>Add %1 to a new group</source>
+        <translation>Add %1 to a new group</translation>
+    </message>
+    <message>
+        <location filename="../ui/squawk.cpp" line="392"/>
+        <source>Open conversation</source>
+        <translation>Open conversation</translation>
+    </message>
+    <message>
+        <location filename="../ui/squawk.cpp" line="454"/>
+        <source>%1 account card</source>
+        <translation>%1 account card</translation>
+    </message>
+    <message>
+        <location filename="../ui/squawk.cpp" line="456"/>
+        <source>%1 contact card</source>
+        <translation>%1 contact card</translation>
+    </message>
+    <message>
+        <location filename="../ui/squawk.cpp" line="468"/>
+        <source>Downloading vCard</source>
+        <translation>Downloading vCard</translation>
+    </message>
+</context>
+<context>
+    <name>VCard</name>
+    <message>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="65"/>
+        <location filename="../build/squawk_autogen/include/ui_vcard.h" line="612"/>
+        <source>Received 12.07.2007 at 17.35</source>
+        <translation>Never updated</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="100"/>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="425"/>
+        <location filename="../build/squawk_autogen/include/ui_vcard.h" line="624"/>
+        <location filename="../build/squawk_autogen/include/ui_vcard.h" line="627"/>
+        <source>General</source>
+        <translation>General</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="130"/>
+        <location filename="../build/squawk_autogen/include/ui_vcard.h" line="613"/>
+        <source>Organization</source>
+        <translation>Organization</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="180"/>
+        <location filename="../build/squawk_autogen/include/ui_vcard.h" line="614"/>
+        <source>Middle name</source>
+        <translation>Middle name</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="190"/>
+        <location filename="../build/squawk_autogen/include/ui_vcard.h" line="615"/>
+        <source>First name</source>
+        <translation>First name</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="200"/>
+        <location filename="../build/squawk_autogen/include/ui_vcard.h" line="616"/>
+        <source>Last name</source>
+        <translation>Last name</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="226"/>
+        <location filename="../build/squawk_autogen/include/ui_vcard.h" line="617"/>
+        <source>Nick name</source>
+        <translation>Nick name</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="252"/>
+        <location filename="../build/squawk_autogen/include/ui_vcard.h" line="618"/>
+        <source>Birthday</source>
+        <translation>Birthday</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="272"/>
+        <location filename="../build/squawk_autogen/include/ui_vcard.h" line="619"/>
+        <source>Organization name</source>
+        <translation>Organization name</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="298"/>
+        <location filename="../build/squawk_autogen/include/ui_vcard.h" line="620"/>
+        <source>Unit / Department</source>
+        <translation>Unit / Department</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="324"/>
+        <location filename="../build/squawk_autogen/include/ui_vcard.h" line="621"/>
+        <source>Role / Profession</source>
+        <translation>Role / Profession</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="350"/>
+        <location filename="../build/squawk_autogen/include/ui_vcard.h" line="622"/>
+        <source>Job title</source>
+        <translation>Job title</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="390"/>
+        <location filename="../build/squawk_autogen/include/ui_vcard.h" line="623"/>
+        <source>Full name</source>
+        <translation>Full name</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="460"/>
+        <location filename="../build/squawk_autogen/include/ui_vcard.h" line="625"/>
+        <source>Personal information</source>
+        <translation>Personal information</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="522"/>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="546"/>
+        <location filename="../build/squawk_autogen/include/ui_vcard.h" line="628"/>
+        <location filename="../build/squawk_autogen/include/ui_vcard.h" line="634"/>
+        <source>Contact</source>
+        <translation>Contact</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="653"/>
+        <location filename="../build/squawk_autogen/include/ui_vcard.h" line="629"/>
+        <source>Addresses</source>
+        <translation>Addresses</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="682"/>
+        <location filename="../build/squawk_autogen/include/ui_vcard.h" line="630"/>
+        <source>E-Mail addresses</source>
+        <translation>E-Mail addresses</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="713"/>
+        <location filename="../build/squawk_autogen/include/ui_vcard.h" line="631"/>
+        <source>Jabber ID</source>
+        <translation>Jabber ID</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="739"/>
+        <location filename="../build/squawk_autogen/include/ui_vcard.h" line="632"/>
+        <source>Web site</source>
+        <translation>Web site</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="767"/>
+        <location filename="../build/squawk_autogen/include/ui_vcard.h" line="633"/>
+        <source>Phone numbers</source>
+        <translation>Phone numbers</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="798"/>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="822"/>
+        <location filename="../build/squawk_autogen/include/ui_vcard.h" line="635"/>
+        <location filename="../build/squawk_autogen/include/ui_vcard.h" line="636"/>
+        <source>Description</source>
+        <translation>Description</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="859"/>
+        <location filename="../build/squawk_autogen/include/ui_vcard.h" line="610"/>
+        <source>Set avatar</source>
+        <translation>Set avatar</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="868"/>
+        <location filename="../build/squawk_autogen/include/ui_vcard.h" line="611"/>
+        <source>Clear avatar</source>
+        <translation>Clear avatar</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/vcard/vcard.cpp" line="93"/>
+        <source>Account %1 card</source>
+        <translation>Account %1 card</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/vcard/vcard.cpp" line="108"/>
+        <source>Contact %1 card</source>
+        <translation>Contact %1 card</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/vcard/vcard.cpp" line="174"/>
+        <source>Received %1 at %2</source>
+        <translation>Received %1 at %2</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/vcard/vcard.cpp" line="225"/>
+        <source>Chose your new avatar</source>
+        <translation>Chose your new avatar</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/vcard/vcard.cpp" line="227"/>
+        <source>Images (*.png *.jpg *.jpeg)</source>
+        <translation>Images (*.png *.jpg *.jpeg)</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/vcard/vcard.cpp" line="314"/>
+        <source>Add email address</source>
+        <translation>Add email address</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/vcard/vcard.cpp" line="324"/>
+        <source>Unset this email as preferred</source>
+        <translation>Unset this email as preferred</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/vcard/vcard.cpp" line="327"/>
+        <source>Set this email as preferred</source>
+        <translation>Set this email as preferred</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/vcard/vcard.cpp" line="332"/>
+        <source>Remove selected email addresses</source>
+        <translation>Remove selected email addresses</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/vcard/vcard.cpp" line="337"/>
+        <source>Copy selected emails to clipboard</source>
+        <translation>Copy selected emails to clipboard</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/vcard/vcard.cpp" line="342"/>
+        <source>Add phone number</source>
+        <translation>Add phone number</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/vcard/vcard.cpp" line="352"/>
+        <source>Unset this phone as preferred</source>
+        <translation>Unset this phone as preferred</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/vcard/vcard.cpp" line="355"/>
+        <source>Set this phone as preferred</source>
+        <translation>Set this phone as preferred</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/vcard/vcard.cpp" line="360"/>
+        <source>Remove selected phone numbers</source>
+        <translation>Remove selected phone numbers</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/vcard/vcard.cpp" line="365"/>
+        <source>Copy selected phones to clipboard</source>
+        <translation>Copy selected phones to clipboard</translation>
+    </message>
+</context>
+</TS>
diff --git a/translations/squawk.pt_BR.ts b/translations/squawk.pt_BR.ts
index 4330979..6041678 100644
--- a/translations/squawk.pt_BR.ts
+++ b/translations/squawk.pt_BR.ts
@@ -1,6 +1,108 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!DOCTYPE TS>
 <TS version="2.1" language="pt_BR">
+<context>
+    <name>About</name>
+    <message>
+        <source>About Squawk</source>
+        <translation>Sorbe Squawk</translation>
+    </message>
+    <message>
+        <source>Squawk</source>
+        <translation>Squawk</translation>
+    </message>
+    <message>
+        <source>About</source>
+        <translatorcomment>Tab title</translatorcomment>
+        <translation>Sobre</translation>
+    </message>
+    <message>
+        <source>XMPP (jabber) messenger</source>
+        <translation>XMPP (jabber) mensageiro</translation>
+    </message>
+    <message>
+        <source>(c) 2019 - 2022, Yury Gubich</source>
+        <translation>(c) 2019 - 2022, Yury Gubich</translation>
+    </message>
+    <message>
+        <source>&lt;a href=&quot;https://git.macaw.me/blue/squawk&quot;&gt;Project site&lt;/a&gt;</source>
+        <translation>&lt;a href=&quot;https://git.macaw.me/blue/squawk&quot;&gt;Site do projeto&lt;/a&gt;</translation>
+    </message>
+    <message>
+        <source>&lt;a href=&quot;https://git.macaw.me/blue/squawk/src/branch/master/LICENSE.md&quot;&gt;License: GNU General Public License version 3&lt;/a&gt;</source>
+        <translation>&lt;a href=&quot;https://git.macaw.me/blue/squawk/src/branch/master/LICENSE.md&quot;&gt;Licença: GNU General Public License versão 3&lt;/a&gt;</translation>
+    </message>
+    <message>
+        <source>Components</source>
+        <translation>Componentes</translation>
+    </message>
+    <message>
+        <source>Version</source>
+        <translation>Versão</translation>
+    </message>
+    <message>
+        <source>0.0.0</source>
+        <translation>0.0.0</translation>
+    </message>
+    <message>
+        <source>Report Bugs</source>
+        <translation>Relatório de erros</translation>
+    </message>
+    <message>
+        <source>Please report any bug you find!
+To report bugs you can use:</source>
+        <translation>Por favor reportar qualquer erro que você encontrar!
+Para relatar bugs você pode usar:</translation>
+    </message>
+    <message>
+        <source>&lt;a href=&quot;https://git.macaw.me/blue/squawk/issues&quot;&gt;Project bug tracker&lt;/&gt;</source>
+        <translation>&lt;a href=&quot;https://git.macaw.me/blue/squawk/issues&quot;&gt;Rastreador de bugs do projeto&lt;/&gt;</translation>
+    </message>
+    <message>
+        <source>XMPP (&lt;a href=&quot;xmpp:blue@macaw.me&quot;&gt;blue@macaw.me&lt;/a&gt;)</source>
+        <translation>XMPP (&lt;a href=&quot;xmpp:blue@macaw.me&quot;&gt;blue@macaw.me&lt;/a&gt;)</translation>
+    </message>
+    <message>
+        <source>E-Mail (&lt;a href=&quot;mailto:blue@macaw.me&quot;&gt;blue@macaw.me&lt;/a&gt;)</source>
+        <translation>E-Mail (&lt;a href=&quot;mailto:blue@macaw.me&quot;&gt;blue@macaw.me&lt;/a&gt;)</translation>
+    </message>
+    <message>
+        <source>Thanks To</source>
+        <translation>Graças ao</translation>
+    </message>
+    <message>
+        <source>Vae</source>
+        <translation>Vae</translation>
+    </message>
+    <message>
+        <source>Major refactoring, bug fixes, constructive criticism</source>
+        <translation>Refatoração importante, correção de erros, críticas construtivas</translation>
+    </message>
+    <message>
+        <source>Shunf4</source>
+        <translation>Shunf4</translation>
+    </message>
+    <message>
+        <source>Major refactoring, bug fixes, build adaptations for Windows and MacOS</source>
+        <translation>Refatoração importante, correção de erros, adaptações de construção para Windows e MacOS</translation>
+    </message>
+    <message>
+        <source>Bruno F. Fontes</source>
+        <translation>Bruno F. Fontes</translation>
+    </message>
+    <message>
+        <source>Brazilian Portuguese translation</source>
+        <translation>Tradução para o português do Brasil</translation>
+    </message>
+    <message>
+        <source>(built against %1)</source>
+        <translation>(Versão durante a compilação %1)</translation>
+    </message>
+    <message>
+        <source>License</source>
+        <translation>Licença</translation>
+    </message>
+</context>
 <context>
     <name>Account</name>
     <message>
@@ -10,10 +112,12 @@
     </message>
     <message>
         <source>Your account login</source>
+        <translatorcomment>Tooltip</translatorcomment>
         <translation>Suas informações de login</translation>
     </message>
     <message>
         <source>john_smith1987</source>
+        <translatorcomment>Login placeholder</translatorcomment>
         <translation>josé_silva1987</translation>
     </message>
     <message>
@@ -22,10 +126,12 @@
     </message>
     <message>
         <source>A server address of your account. Like 404.city or macaw.me</source>
+        <translatorcomment>Tooltip</translatorcomment>
         <translation>O endereço do servidor da sua conta, como o 404.city ou o macaw.me</translation>
     </message>
     <message>
         <source>macaw.me</source>
+        <translatorcomment>Placeholder</translatorcomment>
         <translation>macaw.me</translation>
     </message>
     <message>
@@ -38,6 +144,7 @@
     </message>
     <message>
         <source>Password of your account</source>
+        <translatorcomment>Tooltip</translatorcomment>
         <translation>Senha da sua conta</translation>
     </message>
     <message>
@@ -46,10 +153,11 @@
     </message>
     <message>
         <source>Just a name how would you call this account, doesn&apos;t affect anything</source>
-        <translation>Apenas um nome para identificar esta conta. Não influencia em nada</translation>
+        <translation>Apenas um nome para identificar esta conta. Não influencia em nada (não pode ser mudado)</translation>
     </message>
     <message>
         <source>John</source>
+        <translatorcomment>Placeholder</translatorcomment>
         <translation>José</translation>
     </message>
     <message>
@@ -58,6 +166,7 @@
     </message>
     <message>
         <source>A resource name like &quot;Home&quot; or &quot;Work&quot;</source>
+        <translatorcomment>Tooltip</translatorcomment>
         <translation>Um nome de recurso como  &quot;Casa&quot; ou &quot;Trabalho&quot;</translation>
     </message>
     <message>
@@ -69,6 +178,14 @@
         <source>Password storage</source>
         <translation>Armazenamento de senha</translation>
     </message>
+    <message>
+        <source>Active</source>
+        <translation>Ativo</translation>
+    </message>
+    <message>
+        <source>enable</source>
+        <translation>habilitar</translation>
+    </message>
 </context>
 <context>
     <name>Accounts</name>
@@ -98,30 +215,135 @@
     </message>
     <message>
         <source>Disconnect</source>
-        <translation>Desconectar</translation>
+        <translation type="vanished">Desconectar</translation>
+    </message>
+    <message>
+        <source>Deactivate</source>
+        <translation>Desativar</translation>
+    </message>
+    <message>
+        <source>Activate</source>
+        <translation>Ativar</translation>
+    </message>
+</context>
+<context>
+    <name>Application</name>
+    <message>
+        <source> from </source>
+        <translation> de </translation>
+    </message>
+    <message>
+        <source>Attached file</source>
+        <translation>Arquivo anexado</translation>
+    </message>
+    <message>
+        <source>Mark as Read</source>
+        <translation>Marcar como lido</translation>
+    </message>
+    <message>
+        <source>Open conversation</source>
+        <translation>Abrir conversa</translation>
     </message>
 </context>
 <context>
     <name>Conversation</name>
     <message>
         <source>Type your message here...</source>
+        <translatorcomment>Placeholder</translatorcomment>
         <translation>Digite sua mensagem aqui...</translation>
     </message>
     <message>
         <source>Chose a file to send</source>
         <translation>Escolha um arquivo para enviar</translation>
     </message>
+    <message>
+        <source>Drop files here to attach them to your message</source>
+        <translation>Arraste seus arquivos aqui para anexá-los a sua mensagem</translation>
+    </message>
     <message>
         <source>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
 &lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
 p, li { white-space: pre-wrap; }
-&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;Liberation Sans&apos;; font-size:10pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;Noto Sans&apos;; font-size:8pt; font-weight:400; font-style:normal;&quot;&gt;
 &lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
-        <translation></translation>
+        <translation>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;Noto Sans&apos;; font-size:8pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
     </message>
     <message>
-        <source>Drop files here to attach them to your message</source>
-        <translation>Arraste seus arquivos aqui para anexá-los a sua mensagem</translation>
+        <source>Paste Image</source>
+        <translation>Colar imagem</translation>
+    </message>
+    <message>
+        <source>Try sending again</source>
+        <translation>Tente enviar de novo</translation>
+    </message>
+    <message>
+        <source>Copy selected</source>
+        <translation>Copiar selecionado</translation>
+    </message>
+    <message>
+        <source>Copy message</source>
+        <translation>Copiar mensagem</translation>
+    </message>
+    <message>
+        <source>Open</source>
+        <translation>Abrir</translation>
+    </message>
+    <message>
+        <source>Show in folder</source>
+        <translation>Show in explorer</translation>
+    </message>
+    <message>
+        <source>Edit</source>
+        <translation>Editar</translation>
+    </message>
+    <message>
+        <source>Editing message...</source>
+        <translation>Messae está sendo editado...</translation>
+    </message>
+</context>
+<context>
+    <name>CredentialsPrompt</name>
+    <message>
+        <source>Authentication error: %1</source>
+        <translatorcomment>Window title</translatorcomment>
+        <translation>Erro de autenticação: %1</translation>
+    </message>
+    <message>
+        <source>Couldn&apos;t authenticate account %1: login or password is icorrect.
+Would you like to check them and try again?</source>
+        <translation>Não foi possível autenticar a conta %1: login ou senha incorretos.
+Deseja verificá-los e tentar novamente?</translation>
+    </message>
+    <message>
+        <source>Login</source>
+        <translation>Usuário</translation>
+    </message>
+    <message>
+        <source>Your account login (without @server.domain)</source>
+        <translation>Suas informações de login (sem @server.domain)</translation>
+    </message>
+    <message>
+        <source>Password</source>
+        <translation>Senha</translation>
+    </message>
+    <message>
+        <source>Your password</source>
+        <translation>Senha da sua conta</translation>
+    </message>
+</context>
+<context>
+    <name>DialogQueue</name>
+    <message>
+        <source>Input the password for account %1</source>
+        <translation>Digite a senha para a conta %1</translation>
+    </message>
+    <message>
+        <source>Password for account %1</source>
+        <translation>Senha para a conta %1</translation>
     </message>
 </context>
 <context>
@@ -370,14 +592,14 @@ p, li { white-space: pre-wrap; }
     <name>Message</name>
     <message>
         <source>Open</source>
-        <translation>Abrir</translation>
+        <translation type="vanished">Abrir</translation>
     </message>
 </context>
 <context>
     <name>MessageLine</name>
     <message>
         <source>Downloading...</source>
-        <translation>Baixando...</translation>
+        <translation type="vanished">Baixando...</translation>
     </message>
     <message>
         <source>Download</source>
@@ -386,28 +608,28 @@ p, li { white-space: pre-wrap; }
     <message>
         <source>Error uploading file: %1
 You can try again</source>
-        <translation>Error fazendo upload do arquivo:
+        <translation type="vanished">Error fazendo upload do arquivo:
 %1
 Você pode tentar novamente</translation>
     </message>
     <message>
         <source>Upload</source>
-        <translation>Upload</translation>
+        <translation type="vanished">Upload</translation>
     </message>
     <message>
         <source>Error downloading file: %1
 You can try again</source>
-        <translation>Erro baixando arquivo:
+        <translation type="vanished">Erro baixando arquivo:
 %1
 Você pode tentar novamente</translation>
     </message>
     <message>
         <source>Uploading...</source>
-        <translation>Fazendo upload...</translation>
+        <translation type="vanished">Fazendo upload...</translation>
     </message>
     <message>
         <source>Push the button to download the file</source>
-        <translation>Pressione o botão para baixar o arquivo</translation>
+        <translation type="vanished">Pressione o botão para baixar o arquivo</translation>
     </message>
 </context>
 <context>
@@ -481,7 +703,7 @@ Você pode tentar novamente</translation>
     <name>NewContact</name>
     <message>
         <source>Add new contact</source>
-        <translatorcomment>Заголовок окна</translatorcomment>
+        <translatorcomment>Window title</translatorcomment>
         <translation>Adicionar novo contato</translation>
     </message>
     <message>
@@ -502,7 +724,7 @@ Você pode tentar novamente</translation>
     </message>
     <message>
         <source>name@server.dmn</source>
-        <translatorcomment>Placeholder поля ввода JID</translatorcomment>
+        <translatorcomment>Placeholder</translatorcomment>
         <translation>nome@servidor.com.br</translation>
     </message>
     <message>
@@ -518,6 +740,66 @@ Você pode tentar novamente</translation>
         <translation>José Silva</translation>
     </message>
 </context>
+<context>
+    <name>PageAppearance</name>
+    <message>
+        <source>Theme</source>
+        <translation>Estilo</translation>
+    </message>
+    <message>
+        <source>Color scheme</source>
+        <translation>Esquema de cores</translation>
+    </message>
+    <message>
+        <source>System</source>
+        <translation>Do sistema</translation>
+    </message>
+</context>
+<context>
+    <name>PageGeneral</name>
+    <message>
+        <source>Downloads path</source>
+        <translation>Pasta de downloads</translation>
+    </message>
+    <message>
+        <source>Browse</source>
+        <translation>6 / 5,000
+Translation results
+Navegar</translation>
+    </message>
+    <message>
+        <source>Select where downloads folder is going to be</source>
+        <translation>Selecione onde a pasta de downloads ficará</translation>
+    </message>
+</context>
+<context>
+    <name>Settings</name>
+    <message>
+        <source>Preferences</source>
+        <translatorcomment>Window title</translatorcomment>
+        <translation>Preferências</translation>
+    </message>
+    <message>
+        <source>General</source>
+        <translation>Geral</translation>
+    </message>
+    <message>
+        <source>Appearance</source>
+        <translation>Aparência</translation>
+    </message>
+    <message>
+        <source>Apply</source>
+        <translation>Aplicar</translation>
+    </message>
+    <message>
+        <source>Cancel</source>
+        <translation>Cancelar</translation>
+    </message>
+    <message>
+        <source>Ok</source>
+        <translation>Feito</translation>
+    </message>
+</context>
 <context>
     <name>Squawk</name>
     <message>
@@ -530,6 +812,7 @@ Você pode tentar novamente</translation>
     </message>
     <message>
         <source>Squawk</source>
+        <translatorcomment>Menu bar entry</translatorcomment>
         <translation>Squawk</translation>
     </message>
     <message>
@@ -550,11 +833,11 @@ Você pode tentar novamente</translation>
     </message>
     <message>
         <source>Disconnect</source>
-        <translation>Desconectar</translation>
+        <translation type="vanished">Desconectar</translation>
     </message>
     <message>
         <source>Connect</source>
-        <translation>Conectar</translation>
+        <translation type="vanished">Conectar</translation>
     </message>
     <message>
         <source>VCard</source>
@@ -627,26 +910,46 @@ ser exibido com
     </message>
     <message>
         <source>Attached file</source>
-        <translation>Arquivo anexado</translation>
+        <translation type="vanished">Arquivo anexado</translation>
     </message>
     <message>
         <source>Input the password for account %1</source>
-        <translation>Digite a senha para a conta %1</translation>
+        <translation type="vanished">Digite a senha para a conta %1</translation>
     </message>
     <message>
         <source>Password for account %1</source>
-        <translation>Senha para a conta %1</translation>
+        <translation type="vanished">Senha para a conta %1</translation>
     </message>
     <message>
         <source>Please select a contact to start chatting</source>
         <translation>Por favor selecione um contato para começar a conversar</translation>
     </message>
+    <message>
+        <source>Help</source>
+        <translation>Ajuda</translation>
+    </message>
+    <message>
+        <source>Preferences</source>
+        <translation>Preferências</translation>
+    </message>
+    <message>
+        <source>About Squawk</source>
+        <translation>Sorbe Squawk</translation>
+    </message>
+    <message>
+        <source>Deactivate</source>
+        <translation>Desativar</translation>
+    </message>
+    <message>
+        <source>Activate</source>
+        <translation>Ativar</translation>
+    </message>
 </context>
 <context>
     <name>VCard</name>
     <message>
         <source>Received 12.07.2007 at 17.35</source>
-        <translation>Recebido 12/07/2007 às 17:35</translation>
+        <translation>Nunca atualizado</translation>
     </message>
     <message>
         <source>General</source>
@@ -682,7 +985,7 @@ ser exibido com
     </message>
     <message>
         <source>Unit / Department</source>
-        <translation>Unidade/Departamento</translation>
+        <translation>Unidade / Departamento</translation>
     </message>
     <message>
         <source>Role / Profession</source>
diff --git a/translations/squawk.ru.ts b/translations/squawk.ru.ts
index e3b4d52..39dbfac 100644
--- a/translations/squawk.ru.ts
+++ b/translations/squawk.ru.ts
@@ -1,6 +1,108 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!DOCTYPE TS>
 <TS version="2.1" language="ru_RU">
+<context>
+    <name>About</name>
+    <message>
+        <source>About Squawk</source>
+        <translation>О Программе Squawk</translation>
+    </message>
+    <message>
+        <source>Squawk</source>
+        <translation>Squawk</translation>
+    </message>
+    <message>
+        <source>About</source>
+        <translatorcomment>Tab title</translatorcomment>
+        <translation>Общее</translation>
+    </message>
+    <message>
+        <source>XMPP (jabber) messenger</source>
+        <translation>XMPP (jabber) мессенджер</translation>
+    </message>
+    <message>
+        <source>(c) 2019 - 2022, Yury Gubich</source>
+        <translation>(c) 2019 - 2022, Юрий Губич</translation>
+    </message>
+    <message>
+        <source>&lt;a href=&quot;https://git.macaw.me/blue/squawk&quot;&gt;Project site&lt;/a&gt;</source>
+        <translation>&lt;a href=&quot;https://git.macaw.me/blue/squawk&quot;&gt;Сайт проекта&lt;/a&gt;</translation>
+    </message>
+    <message>
+        <source>&lt;a href=&quot;https://git.macaw.me/blue/squawk/src/branch/master/LICENSE.md&quot;&gt;License: GNU General Public License version 3&lt;/a&gt;</source>
+        <translation>&lt;a href=&quot;https://git.macaw.me/blue/squawk/src/branch/master/LICENSE.md&quot;&gt;Лицензия: GNU General Public License версия 3&lt;/a&gt;</translation>
+    </message>
+    <message>
+        <source>Components</source>
+        <translation>Компоненты</translation>
+    </message>
+    <message>
+        <source>Version</source>
+        <translation>Версия</translation>
+    </message>
+    <message>
+        <source>0.0.0</source>
+        <translation>0.0.0</translation>
+    </message>
+    <message>
+        <source>Report Bugs</source>
+        <translation>Сообщать об ошибках</translation>
+    </message>
+    <message>
+        <source>Please report any bug you find!
+To report bugs you can use:</source>
+        <translation>Пожалуйста, сообщайте о любых ошибках!
+Способы сообщить об ошибках:</translation>
+    </message>
+    <message>
+        <source>&lt;a href=&quot;https://git.macaw.me/blue/squawk/issues&quot;&gt;Project bug tracker&lt;/&gt;</source>
+        <translation>&lt;a href=&quot;https://git.macaw.me/blue/squawk/issues&quot;&gt;Баг-трекер проекта&lt;/&gt;</translation>
+    </message>
+    <message>
+        <source>XMPP (&lt;a href=&quot;xmpp:blue@macaw.me&quot;&gt;blue@macaw.me&lt;/a&gt;)</source>
+        <translation>XMPP (&lt;a href=&quot;xmpp:blue@macaw.me&quot;&gt;blue@macaw.me&lt;/a&gt;)</translation>
+    </message>
+    <message>
+        <source>E-Mail (&lt;a href=&quot;mailto:blue@macaw.me&quot;&gt;blue@macaw.me&lt;/a&gt;)</source>
+        <translation>E-Mail (&lt;a href=&quot;mailto:blue@macaw.me&quot;&gt;blue@macaw.me&lt;/a&gt;)</translation>
+    </message>
+    <message>
+        <source>Thanks To</source>
+        <translation>Благодарности</translation>
+    </message>
+    <message>
+        <source>Vae</source>
+        <translation>Vae</translation>
+    </message>
+    <message>
+        <source>Major refactoring, bug fixes, constructive criticism</source>
+        <translation>Крупный рефакторинг, исправление ошибок, конструктивная критика</translation>
+    </message>
+    <message>
+        <source>Shunf4</source>
+        <translation>Shunf4</translation>
+    </message>
+    <message>
+        <source>Major refactoring, bug fixes, build adaptations for Windows and MacOS</source>
+        <translation>Крупный рефакторинг, исправление ошибок, адаптация сборки под Windows and MacOS</translation>
+    </message>
+    <message>
+        <source>Bruno F. Fontes</source>
+        <translation>Bruno F. Fontes</translation>
+    </message>
+    <message>
+        <source>Brazilian Portuguese translation</source>
+        <translation>Перевод на Португальский (Бразилия)</translation>
+    </message>
+    <message>
+        <source>(built against %1)</source>
+        <translation>(версия при сборке %1)</translation>
+    </message>
+    <message>
+        <source>License</source>
+        <translation>Лицензия</translation>
+    </message>
+</context>
 <context>
     <name>Account</name>
     <message>
@@ -10,10 +112,12 @@
     </message>
     <message>
         <source>Your account login</source>
+        <translatorcomment>Tooltip</translatorcomment>
         <translation>Имя пользователя Вашей учетной записи</translation>
     </message>
     <message>
         <source>john_smith1987</source>
+        <translatorcomment>Login placeholder</translatorcomment>
         <translation>ivan_ivanov1987</translation>
     </message>
     <message>
@@ -22,10 +126,12 @@
     </message>
     <message>
         <source>A server address of your account. Like 404.city or macaw.me</source>
+        <translatorcomment>Tooltip</translatorcomment>
         <translation>Адресс сервера вашей учетной записи (выглядит как 404.city или macaw.me)</translation>
     </message>
     <message>
         <source>macaw.me</source>
+        <translatorcomment>Placeholder</translatorcomment>
         <translation>macaw.me</translation>
     </message>
     <message>
@@ -38,6 +144,7 @@
     </message>
     <message>
         <source>Password of your account</source>
+        <translatorcomment>Tooltip</translatorcomment>
         <translation>Пароль вашей учетной записи</translation>
     </message>
     <message>
@@ -46,10 +153,11 @@
     </message>
     <message>
         <source>Just a name how would you call this account, doesn&apos;t affect anything</source>
-        <translation>Просто имя, то как Вы называете свою учетную запись, может быть любым</translation>
+        <translation>Просто имя, то как Вы называете свою учетную запись, может быть любым (нельзя поменять)</translation>
     </message>
     <message>
         <source>John</source>
+        <translatorcomment>Placeholder</translatorcomment>
         <translation>Иван</translation>
     </message>
     <message>
@@ -58,6 +166,7 @@
     </message>
     <message>
         <source>A resource name like &quot;Home&quot; or &quot;Work&quot;</source>
+        <translatorcomment>Tooltip</translatorcomment>
         <translation>Имя этой программы для ваших контактов, может быть &quot;Home&quot; или &quot;Phone&quot;</translation>
     </message>
     <message>
@@ -69,6 +178,14 @@
         <source>Password storage</source>
         <translation>Хранение пароля</translation>
     </message>
+    <message>
+        <source>Active</source>
+        <translation>Активен</translation>
+    </message>
+    <message>
+        <source>enable</source>
+        <translation>включен</translation>
+    </message>
 </context>
 <context>
     <name>Accounts</name>
@@ -98,30 +215,139 @@
     </message>
     <message>
         <source>Disconnect</source>
-        <translation>Отключить</translation>
+        <translation type="vanished">Отключить</translation>
+    </message>
+    <message>
+        <source>Deactivate</source>
+        <translation>Деактивировать</translation>
+    </message>
+    <message>
+        <source>Activate</source>
+        <translation>Активировать</translation>
+    </message>
+</context>
+<context>
+    <name>Application</name>
+    <message>
+        <source> from </source>
+        <translation> от </translation>
+    </message>
+    <message>
+        <source>Attached file</source>
+        <translation>Прикрепленный файл</translation>
+    </message>
+    <message>
+        <source>Mark as Read</source>
+        <translation>Пометить прочитанным</translation>
+    </message>
+    <message>
+        <source>Open conversation</source>
+        <translation>Открыть окно беседы</translation>
     </message>
 </context>
 <context>
     <name>Conversation</name>
     <message>
         <source>Type your message here...</source>
+        <translatorcomment>Placeholder</translatorcomment>
         <translation>Введите сообщение...</translation>
     </message>
     <message>
         <source>Chose a file to send</source>
         <translation>Выберите файл для отправки</translation>
     </message>
+    <message>
+        <source>Drop files here to attach them to your message</source>
+        <translation>Бросьте файлы сюда для того что бы прикрепить их к сообщению</translation>
+    </message>
     <message>
         <source>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
 &lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
 p, li { white-space: pre-wrap; }
-&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;Liberation Sans&apos;; font-size:10pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;Noto Sans&apos;; font-size:8pt; font-weight:400; font-style:normal;&quot;&gt;
 &lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
-        <translation></translation>
+        <translation>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;Noto Sans&apos;; font-size:8pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
     </message>
     <message>
-        <source>Drop files here to attach them to your message</source>
-        <translation>Бросьте файлы сюда для того что бы прикрепить их к сообщению</translation>
+        <source>Paste Image</source>
+        <translation>Вставить изображение</translation>
+    </message>
+    <message>
+        <source>Try sending again</source>
+        <translation>Отправить снова</translation>
+    </message>
+    <message>
+        <source>Copy selected</source>
+        <translation>Скопировать выделенное</translation>
+    </message>
+    <message>
+        <source>Copy message</source>
+        <translation>Скопировать сообщение</translation>
+    </message>
+    <message>
+        <source>Open</source>
+        <translation>Открыть</translation>
+    </message>
+    <message>
+        <source>Show in folder</source>
+        <translation>Показать в проводнике</translation>
+    </message>
+    <message>
+        <source>Edit</source>
+        <translation>Редактировать</translation>
+    </message>
+    <message>
+        <source>Editing message...</source>
+        <translation>Сообщение редактируется...</translation>
+    </message>
+</context>
+<context>
+    <name>CredentialsPrompt</name>
+    <message>
+        <source>Authentication error: %1</source>
+        <translatorcomment>Window title</translatorcomment>
+        <translation>Ошибка аутентификации: %1</translation>
+    </message>
+    <message>
+        <source>Couldn&apos;t authenticate account %1: login or password is icorrect.
+Would you like to check them and try again?</source>
+        <translation>Не получилось аутентифицировать
+учетную запись %1:
+имя пользователя или пароль введены неверно.
+Желаете ли проверить их и
+попробовать аутентифицироваться еще раз?</translation>
+    </message>
+    <message>
+        <source>Login</source>
+        <translation>Имя учетной записи</translation>
+    </message>
+    <message>
+        <source>Your account login (without @server.domain)</source>
+        <translatorcomment>Tooltip</translatorcomment>
+        <translation>Имя вашей учтетной записи (без @server.domain)</translation>
+    </message>
+    <message>
+        <source>Password</source>
+        <translation>Пароль</translation>
+    </message>
+    <message>
+        <source>Your password</source>
+        <translation>Ваш пароль</translation>
+    </message>
+</context>
+<context>
+    <name>DialogQueue</name>
+    <message>
+        <source>Input the password for account %1</source>
+        <translation>Введите пароль для учетной записи %1</translation>
+    </message>
+    <message>
+        <source>Password for account %1</source>
+        <translation>Пароль для учетной записи %1</translation>
     </message>
 </context>
 <context>
@@ -370,14 +596,14 @@ p, li { white-space: pre-wrap; }
     <name>Message</name>
     <message>
         <source>Open</source>
-        <translation>Открыть</translation>
+        <translation type="vanished">Открыть</translation>
     </message>
 </context>
 <context>
     <name>MessageLine</name>
     <message>
         <source>Downloading...</source>
-        <translation>Скачивается...</translation>
+        <translation type="vanished">Скачивается...</translation>
     </message>
     <message>
         <source>Download</source>
@@ -386,28 +612,28 @@ p, li { white-space: pre-wrap; }
     <message>
         <source>Error uploading file: %1
 You can try again</source>
-        <translation>Ошибка загрузки файла на сервер:
+        <translation type="vanished">Ошибка загрузки файла на сервер:
 %1
 Для того, что бы попробовать снова нажмите на кнопку</translation>
     </message>
     <message>
         <source>Upload</source>
-        <translation>Загрузить</translation>
+        <translation type="vanished">Загрузить</translation>
     </message>
     <message>
         <source>Error downloading file: %1
 You can try again</source>
-        <translation>Ошибка скачивания файла: 
+        <translation type="vanished">Ошибка скачивания файла: 
 %1
 Вы можете попробовать снова</translation>
     </message>
     <message>
         <source>Uploading...</source>
-        <translation>Загружается...</translation>
+        <translation type="vanished">Загружается...</translation>
     </message>
     <message>
         <source>Push the button to download the file</source>
-        <translation>Нажмите на кнопку что бы загрузить файл</translation>
+        <translation type="vanished">Нажмите на кнопку что бы загрузить файл</translation>
     </message>
 </context>
 <context>
@@ -481,7 +707,7 @@ You can try again</source>
     <name>NewContact</name>
     <message>
         <source>Add new contact</source>
-        <translatorcomment>Заголовок окна</translatorcomment>
+        <translatorcomment>Window title</translatorcomment>
         <translation>Добавление нового контакта</translation>
     </message>
     <message>
@@ -502,7 +728,7 @@ You can try again</source>
     </message>
     <message>
         <source>name@server.dmn</source>
-        <translatorcomment>Placeholder поля ввода JID</translatorcomment>
+        <translatorcomment>Placeholder</translatorcomment>
         <translation>name@server.dmn</translation>
     </message>
     <message>
@@ -518,6 +744,64 @@ You can try again</source>
         <translation>Иван Иванов</translation>
     </message>
 </context>
+<context>
+    <name>PageAppearance</name>
+    <message>
+        <source>Theme</source>
+        <translation>Оформление</translation>
+    </message>
+    <message>
+        <source>Color scheme</source>
+        <translation>Цветовая схема</translation>
+    </message>
+    <message>
+        <source>System</source>
+        <translation>Системная</translation>
+    </message>
+</context>
+<context>
+    <name>PageGeneral</name>
+    <message>
+        <source>Downloads path</source>
+        <translation>Папка для сохраненных файлов</translation>
+    </message>
+    <message>
+        <source>Browse</source>
+        <translation>Выбрать</translation>
+    </message>
+    <message>
+        <source>Select where downloads folder is going to be</source>
+        <translation>Выберете папку, в которую будут сохраняться файлы</translation>
+    </message>
+</context>
+<context>
+    <name>Settings</name>
+    <message>
+        <source>Preferences</source>
+        <translatorcomment>Window title</translatorcomment>
+        <translation>Настройки</translation>
+    </message>
+    <message>
+        <source>General</source>
+        <translation>Общее</translation>
+    </message>
+    <message>
+        <source>Appearance</source>
+        <translation>Внешний вид</translation>
+    </message>
+    <message>
+        <source>Apply</source>
+        <translation>Применить</translation>
+    </message>
+    <message>
+        <source>Cancel</source>
+        <translation>Отменить</translation>
+    </message>
+    <message>
+        <source>Ok</source>
+        <translation>Готово</translation>
+    </message>
+</context>
 <context>
     <name>Squawk</name>
     <message>
@@ -530,6 +814,7 @@ You can try again</source>
     </message>
     <message>
         <source>Squawk</source>
+        <translatorcomment>Menu bar entry</translatorcomment>
         <translation>Squawk</translation>
     </message>
     <message>
@@ -550,11 +835,11 @@ You can try again</source>
     </message>
     <message>
         <source>Disconnect</source>
-        <translation>Отключить</translation>
+        <translation type="vanished">Отключить</translation>
     </message>
     <message>
         <source>Connect</source>
-        <translation>Подключить</translation>
+        <translation type="vanished">Подключить</translation>
     </message>
     <message>
         <source>VCard</source>
@@ -627,20 +912,40 @@ to be displayed as %1</source>
     </message>
     <message>
         <source>Attached file</source>
-        <translation>Прикрепленный файл</translation>
+        <translation type="vanished">Прикрепленный файл</translation>
     </message>
     <message>
         <source>Input the password for account %1</source>
-        <translation>Введите пароль для учетной записи %1</translation>
+        <translation type="vanished">Введите пароль для учетной записи %1</translation>
     </message>
     <message>
         <source>Password for account %1</source>
-        <translation>Пароль для учетной записи %1</translation>
+        <translation type="vanished">Пароль для учетной записи %1</translation>
     </message>
     <message>
         <source>Please select a contact to start chatting</source>
         <translation>Выберите контакт или группу что бы начать переписку</translation>
     </message>
+    <message>
+        <source>Help</source>
+        <translation>Помощь</translation>
+    </message>
+    <message>
+        <source>Preferences</source>
+        <translation>Настройки</translation>
+    </message>
+    <message>
+        <source>About Squawk</source>
+        <translation>О Программе Squawk</translation>
+    </message>
+    <message>
+        <source>Deactivate</source>
+        <translation>Деактивировать</translation>
+    </message>
+    <message>
+        <source>Activate</source>
+        <translation>Активировать</translation>
+    </message>
 </context>
 <context>
     <name>VCard</name>

From 645b92ba51124006d61cb2f20059b186d2213b93 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Thu, 5 May 2022 20:46:49 +0300
Subject: [PATCH 203/281] release 0.2.2 preparation

---
 CHANGELOG.md                 | 2 +-
 README.md                    | 2 +-
 packaging/Archlinux/PKGBUILD | 4 ++--
 3 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index f34bf3c..f4fb579 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,6 @@
 # Changelog
 
-## Squawk 0.2.2 (UNRELEASED)
+## Squawk 0.2.2 (May 05, 2022)
 ### Bug fixes
 - now when you remove an account it actually gets removed
 - segfault on unitialized Availability in some rare occesions
diff --git a/README.md b/README.md
index 5845c46..3e20568 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@
 [![AUR version](https://img.shields.io/aur/version/squawk?style=flat-square)](https://aur.archlinux.org/packages/squawk/)
 [![Liberapay patrons](https://img.shields.io/liberapay/patrons/macaw.me?logo=liberapay&style=flat-square)](https://liberapay.com/macaw.me)
 
-![Squawk screenshot](https://macaw.me/images/squawk/0.2.1.png)
+![Squawk screenshot](https://macaw.me/images/squawk/0.2.2.png)
 
 ### Prerequisites
 
diff --git a/packaging/Archlinux/PKGBUILD b/packaging/Archlinux/PKGBUILD
index 899f058..7db43ff 100644
--- a/packaging/Archlinux/PKGBUILD
+++ b/packaging/Archlinux/PKGBUILD
@@ -1,6 +1,6 @@
 # Maintainer: Yury Gubich <blue@macaw.me>
 pkgname=squawk
-pkgver=0.2.1
+pkgver=0.2.2
 pkgrel=1
 pkgdesc="An XMPP desktop messenger, written on pure c++ (qt)"
 arch=('i686' 'x86_64')
@@ -14,7 +14,7 @@ optdepends=('kwallet: secure password storage (requires rebuild)'
             'kio: better show in folder action (requires rebuild)')
 
 source=("$pkgname-$pkgver.tar.gz")
-sha256sums=('c00dad1e441601acabb5200dc394f53abfc9876f3902a7dd4ad2fee3232ee84d')
+sha256sums=('e4fa2174a3ba95159cc3b0bac3f00550c9e0ce971c55334e2662696a4543fc7e')
 build() {
         cd "$srcdir/squawk"
         cmake . -D CMAKE_INSTALL_PREFIX=/usr -D CMAKE_BUILD_TYPE=Release

From 7192286aeb69a20c8ecd4f5337f7ea5a59935c71 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Fri, 3 Jun 2022 09:44:48 +0300
Subject: [PATCH 204/281] fix some bugs about disabled menus

---
 CHANGELOG.md                  | 17 +++++++++++++----
 CMakeLists.txt                |  2 +-
 main/main.cpp                 |  2 +-
 ui/models/accounts.cpp        | 26 +++++++++++++++++++++++---
 ui/models/accounts.h          |  4 +++-
 ui/squawk.cpp                 |  8 +++++---
 ui/squawk.h                   |  2 +-
 ui/widgets/joinconference.cpp | 11 +++++------
 ui/widgets/newcontact.cpp     |  8 ++++----
 9 files changed, 56 insertions(+), 24 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index f4fb579..600c795 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,9 +1,18 @@
 # Changelog
 
+## Squawk 0.2.3 (UNRELEASED)
+### Bug fixes
+- "Add contact" and "Join conference" menu are enabled once again (pavavno)!
+
+### Improvements
+- deactivated accounts now don't appear in combobox of "Add contact" and "Join conference" dialogues
+
+### New features
+
 ## Squawk 0.2.2 (May 05, 2022)
 ### Bug fixes
 - now when you remove an account it actually gets removed
-- segfault on unitialized Availability in some rare occesions
+- segfault on uninitialized Availability in some rare occasions
 - fixed crash when you open a dialog with someone that has only error messages in archive
 - message height is now calculated correctly on Chinese and Japanese paragraphs
 - the app doesn't crash on SIGINT anymore
@@ -12,13 +21,13 @@
 - there is a way to disable an account and it wouldn't connect when you change availability
 - if you cancel password query an account becomes inactive and doesn't annoy you anymore
 - if you filled password field and chose KWallet as a storage Squawk wouldn't ask you again for the same password
-- if left the password field empty and chose KWallet as a storage Squawk will try to get that passord from KWallet before asking you to input it
-- accounts now connect to the server asyncronously - if one is stopped on password prompt another is connecting
+- if left the password field empty and chose KWallet as a storage Squawk will try to get that password from KWallet before asking you to input it
+- accounts now connect to the server asynchronously - if one is stopped on password prompt another is connecting
 - actualized translations, added English localization file
 
 ### New features
 - new "About" window with links, license, gratitudes
-- if the authentication failed Squawk will ask againg for your password and login
+- if the authentication failed Squawk will ask again for your password and login
 - now there is an amount of unread messages showing on top of Squawk launcher icon
 - notifications now have buttons to open a conversation or to mark that message as read
 
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 75011d8..bbae079 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,5 +1,5 @@
 cmake_minimum_required(VERSION 3.4)
-project(squawk VERSION 0.2.2 LANGUAGES CXX)
+project(squawk VERSION 0.2.3 LANGUAGES CXX)
 
 cmake_policy(SET CMP0076 NEW)
 cmake_policy(SET CMP0079 NEW)
diff --git a/main/main.cpp b/main/main.cpp
index 60b3c83..3368e0a 100644
--- a/main/main.cpp
+++ b/main/main.cpp
@@ -49,7 +49,7 @@ int main(int argc, char *argv[])
     QApplication::setApplicationName("squawk");
     QApplication::setOrganizationName("macaw.me");
     QApplication::setApplicationDisplayName("Squawk");
-    QApplication::setApplicationVersion("0.2.2");
+    QApplication::setApplicationVersion("0.2.3");
     app.setDesktopFileName("squawk");
 
     QTranslator qtTranslator;
diff --git a/ui/models/accounts.cpp b/ui/models/accounts.cpp
index 463ab40..8a9e5d9 100644
--- a/ui/models/accounts.cpp
+++ b/ui/models/accounts.cpp
@@ -69,6 +69,23 @@ int Models::Accounts::rowCount ( const QModelIndex& parent ) const
     return accs.size();
 }
 
+unsigned int Models::Accounts::size() const
+{
+    return rowCount(QModelIndex());
+}
+
+unsigned int Models::Accounts::activeSize() const
+{
+    unsigned int size = 0;
+    for (const Models::Account* acc : accs) {
+        if (acc->getActive()) {
+            ++size;
+        }
+    }
+
+    return size;
+}
+
 QVariant Models::Accounts::headerData(int section, Qt::Orientation orientation, int role) const
 {
     if (role == Qt::DisplayRole && orientation == Qt::Horizontal) {
@@ -97,6 +114,7 @@ void Models::Accounts::addAccount(Account* account)
     endInsertRows();
     
     emit sizeChanged(accs.size());
+    emit changed();
 }
 
 void Models::Accounts::onAccountChanged(Item* item, int row, int col)
@@ -157,12 +175,14 @@ void Models::Accounts::removeAccount(int index)
     emit sizeChanged(accs.size());
 }
 
-std::deque<QString> Models::Accounts::getNames() const
+std::deque<QString> Models::Accounts::getActiveNames() const
 {
     std::deque<QString> res;
     
-    for (std::deque<Models::Account*>::const_iterator i = accs.begin(), end = accs.end(); i != end; ++i) {
-        res.push_back((*i)->getName());
+    for (const Models::Account* acc : accs) {
+        if (acc->getActive()) {
+            res.push_back(acc->getName());
+        }
     }
     
     return res;
diff --git a/ui/models/accounts.h b/ui/models/accounts.h
index e8be07c..c5d59af 100644
--- a/ui/models/accounts.h
+++ b/ui/models/accounts.h
@@ -39,11 +39,13 @@ public:
     QVariant data ( const QModelIndex& index, int role ) const override;
     int columnCount ( const QModelIndex& parent ) const override;
     int rowCount ( const QModelIndex& parent ) const override;
+    unsigned int size () const;
+    unsigned int activeSize () const;
     QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
 
     Account* getAccount(int index);
     
-    std::deque<QString> getNames() const;
+    std::deque<QString> getActiveNames() const;
     
 signals:
     void changed();
diff --git a/ui/squawk.cpp b/ui/squawk.cpp
index 9b6158c..db92e93 100644
--- a/ui/squawk.cpp
+++ b/ui/squawk.cpp
@@ -63,7 +63,7 @@ Squawk::Squawk(Models::Roster& p_rosterModel, QWidget *parent) :
     connect(m_ui->roster, &QTreeView::collapsed, this, &Squawk::onItemCollepsed);
     connect(m_ui->roster->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &Squawk::onRosterSelectionChanged);
     
-    connect(rosterModel.accountsModel, &Models::Accounts::sizeChanged, this, &Squawk::onAccountsSizeChanged);
+    connect(rosterModel.accountsModel, &Models::Accounts::changed, this, &Squawk::onAccountsChanged);
     connect(contextMenu, &QMenu::aboutToHide, this, &Squawk::onContextAboutToHide);
     connect(m_ui->actionAboutSquawk, &QAction::triggered, this, &Squawk::onAboutSquawkCalled);
     //m_ui->mainToolBar->addWidget(m_ui->comboBox);
@@ -87,6 +87,8 @@ Squawk::Squawk(Models::Roster& p_rosterModel, QWidget *parent) :
         m_ui->splitter->restoreState(settings.value("splitter").toByteArray());
     }
     settings.endGroup();
+
+    onAccountsChanged();
 }
 
 Squawk::~Squawk() {
@@ -129,9 +131,9 @@ void Squawk::onPreferences()
     }
 }
 
-
-void Squawk::onAccountsSizeChanged(unsigned int size)
+void Squawk::onAccountsChanged()
 {
+    unsigned int size = rosterModel.accountsModel->activeSize();
     if (size > 0) {
         m_ui->actionAddContact->setEnabled(true);
         m_ui->actionAddConference->setEnabled(true);
diff --git a/ui/squawk.h b/ui/squawk.h
index 15a73dd..f55a000 100644
--- a/ui/squawk.h
+++ b/ui/squawk.h
@@ -116,7 +116,7 @@ private slots:
     void onNewConference();
     void onNewContactAccepted();
     void onJoinConferenceAccepted();
-    void onAccountsSizeChanged(unsigned int size);
+    void onAccountsChanged();
     void onAccountsClosed();
     void onPreferencesClosed();
     void onVCardClosed();
diff --git a/ui/widgets/joinconference.cpp b/ui/widgets/joinconference.cpp
index 648de25..890e4e2 100644
--- a/ui/widgets/joinconference.cpp
+++ b/ui/widgets/joinconference.cpp
@@ -26,10 +26,10 @@ JoinConference::JoinConference(const Models::Accounts* accounts, QWidget* parent
     m_ui(new Ui::JoinConference())
 {
     m_ui->setupUi ( this );
-    std::deque<QString> names = accounts->getNames();
+    std::deque<QString> names = accounts->getActiveNames();
     
-    for (std::deque<QString>::const_iterator i = names.begin(), end = names.end(); i != end; i++) {
-        m_ui->account->addItem(*i);
+    for (const QString& name : names) {
+        m_ui->account->addItem(name);
     }
     
     m_ui->account->setCurrentIndex(0);
@@ -40,12 +40,11 @@ JoinConference::JoinConference(const QString& acc, const Models::Accounts* accou
     m_ui(new Ui::JoinConference())
 {
     m_ui->setupUi ( this );
-    std::deque<QString> names = accounts->getNames();
+    std::deque<QString> names = accounts->getActiveNames();
     
     int index = 0;
     bool found = false;
-    for (std::deque<QString>::const_iterator i = names.begin(), end = names.end(); i != end; i++) {
-        const QString& name = *i;
+    for (const QString& name : names) {
         m_ui->account->addItem(name);
         if (!found) {
             if (name == acc) {
diff --git a/ui/widgets/newcontact.cpp b/ui/widgets/newcontact.cpp
index 920c757..9303963 100644
--- a/ui/widgets/newcontact.cpp
+++ b/ui/widgets/newcontact.cpp
@@ -25,10 +25,10 @@ NewContact::NewContact(const Models::Accounts* accounts, QWidget* parent):
     m_ui(new Ui::NewContact())
 {
     m_ui->setupUi ( this );
-    std::deque<QString> names = accounts->getNames();
+    std::deque<QString> names = accounts->getActiveNames();
     
-    for (std::deque<QString>::const_iterator i = names.begin(), end = names.end(); i != end; i++) {
-        m_ui->account->addItem(*i);
+    for (const QString& name : names) {
+        m_ui->account->addItem(name);
     }
     
     m_ui->account->setCurrentIndex(0);
@@ -40,7 +40,7 @@ NewContact::NewContact(const QString& acc, const Models::Accounts* accounts, QWi
 {
 
     m_ui->setupUi ( this );
-    std::deque<QString> names = accounts->getNames();
+    std::deque<QString> names = accounts->getActiveNames();
     
     int index = 0;
     bool found = false;

From 7e9eed207557188f375d455d442cedacd032ed8e Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Mon, 15 Aug 2022 19:40:07 +0300
Subject: [PATCH 205/281] First tray attempt, seems to be working

---
 CHANGELOG.md                        |  1 +
 main/application.cpp                | 96 ++++++++++++++++++++++++++++-
 main/application.h                  | 11 +++-
 translations/squawk.en.ts           |  5 ++
 translations/squawk.pt_BR.ts        |  5 ++
 translations/squawk.ru.ts           |  5 ++
 ui/squawk.cpp                       |  3 +-
 ui/squawk.h                         |  2 +
 ui/widgets/settings/pagegeneral.cpp | 33 ++++++++++
 ui/widgets/settings/pagegeneral.h   |  3 +
 ui/widgets/settings/pagegeneral.ui  | 32 +++++++++-
 ui/widgets/settings/settings.cpp    | 11 ++++
 ui/widgets/settings/settings.h      |  1 +
 13 files changed, 201 insertions(+), 7 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 600c795..40a85c8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,7 @@
 - deactivated accounts now don't appear in combobox of "Add contact" and "Join conference" dialogues
 
 ### New features
+- Now you can enable tray icon from settings!
 
 ## Squawk 0.2.2 (May 05, 2022)
 ### Bug fixes
diff --git a/main/application.cpp b/main/application.cpp
index 216e322..a319ee5 100644
--- a/main/application.cpp
+++ b/main/application.cpp
@@ -27,8 +27,14 @@ Application::Application(Core::Squawk* p_core):
     dialogueQueue(roster),
     nowQuitting(false),
     destroyingSquawk(false),
-    storage()
+    storage(),
+    trayIcon(nullptr),
+    actionQuit(Shared::icon("application-exit"), tr("Quit")),
+    actionToggle(tr("Minimize to tray"))
 {
+    connect(&actionQuit, &QAction::triggered, this, &Application::quit);
+    connect(&actionToggle, &QAction::triggered, this, &Application::toggleSquawk);
+
     connect(&roster, &Models::Roster::unnoticedMessage, this, &Application::notify);
     connect(&roster, &Models::Roster::unreadMessagesCountChanged, this, &Application::unreadMessagesCountChanged);
 
@@ -130,6 +136,12 @@ void Application::quit()
         if (squawk != nullptr) {
             squawk->close();
         }
+
+        if (trayIcon != nullptr) {
+            trayIcon->deleteLater();
+            trayIcon = nullptr;
+        }
+
         if (!destroyingSquawk) {
             checkForTheLastWindow();
         }
@@ -155,6 +167,8 @@ void Application::createMainWindow()
         connect(squawk, &Squawk::openedConversation, this, &Application::onSquawkOpenedConversation);
         connect(squawk, &Squawk::openConversation, this, &Application::openConversation);
         connect(squawk, &Squawk::changeState, this, &Application::setState);
+        connect(squawk, &Squawk::changeTray, this, &Application::onChangeTray);
+        connect(squawk, &Squawk::quit, this, &Application::quit);
         connect(squawk, &Squawk::closing, this, &Application::onSquawkClosing);
 
         connect(squawk, &Squawk::modifyAccountRequest, core, &Core::Squawk::modifyAccountRequest);
@@ -194,8 +208,15 @@ void Application::onSquawkClosing()
     squawk->deleteLater();
     squawk = nullptr;
 
-    //for now
-    quit();
+    QSettings settings;
+    if (!nowQuitting && QSystemTrayIcon::isSystemTrayAvailable() && settings.value("tray", false).toBool()) {
+        if (settings.value("hideTray", false).toBool()) {
+            createTrayIcon();
+        }
+        actionToggle.setText(tr("Show Squawk"));
+    } else {
+        quit();
+    }
 }
 
 void Application::onSquawkDestroyed() {
@@ -205,6 +226,71 @@ void Application::onSquawkDestroyed() {
     }
 }
 
+void Application::onChangeTray(bool enabled, bool hide)
+{
+    if (enabled) {
+        if (trayIcon == nullptr) {
+            if (!hide || squawk == nullptr) {
+                createTrayIcon();
+            }
+        } else {
+            if (hide && squawk != nullptr) {
+                trayIcon->deleteLater();
+                trayIcon = nullptr;
+            }
+        }
+    } else if (trayIcon == nullptr) {
+        trayIcon->deleteLater();
+        trayIcon = nullptr;
+    }
+}
+
+void Application::createTrayIcon()
+{
+    trayIcon = new QSystemTrayIcon();
+
+    QMenu* trayIconMenu = new QMenu();
+    trayIconMenu->addAction(&actionToggle);
+    trayIconMenu->addAction(&actionQuit);
+
+    trayIcon->setContextMenu(trayIconMenu);
+    trayIcon->setIcon(QApplication::windowIcon().pixmap(32, 32));
+    trayIcon->setToolTip(QApplication::applicationDisplayName());
+
+    connect(trayIcon, &QSystemTrayIcon::activated, this, &Application::trayClicked);
+    connect(trayIcon, &QSystemTrayIcon::destroyed, trayIconMenu, &QMenu::deleteLater);
+
+    trayIcon->show();
+}
+
+void Application::trayClicked(QSystemTrayIcon::ActivationReason reason)
+{
+    switch (reason) {
+        case QSystemTrayIcon::Trigger:
+        case QSystemTrayIcon::DoubleClick:
+        case QSystemTrayIcon::MiddleClick:
+            toggleSquawk();
+            break;
+        default:
+            break;
+    }
+}
+
+void Application::toggleSquawk()
+{
+    QSettings settings;
+    if (squawk == nullptr) {
+        createMainWindow();
+        if (settings.value("hideTray", false).toBool()) {
+            trayIcon->deleteLater();
+            trayIcon = nullptr;
+        }
+
+        actionToggle.setText(tr("Minimize to tray"));
+    } else {
+        squawk->close();
+    }
+}
 
 void Application::notify(const QString& account, const Shared::Message& msg)
 {
@@ -345,6 +431,10 @@ void Application::readSettings()
 
     setState(Shared::Global::fromInt<Shared::Availability>(avail));
     createMainWindow();
+
+    if (settings.value("tray", false).toBool() && !settings.value("hideTray", false).toBool()) {
+        createTrayIcon();
+    }
 }
 
 void Application::writeSettings()
diff --git a/main/application.h b/main/application.h
index 301edc4..db60009 100644
--- a/main/application.h
+++ b/main/application.h
@@ -21,6 +21,9 @@
 
 #include <QObject>
 #include <QDBusInterface>
+#include <QSystemTrayIcon>
+#include <QMenu>
+#include <QAction>
 
 #include "dialogqueue.h"
 #include "core/squawk.h"
@@ -91,13 +94,16 @@ private slots:
     void onSquawkDestroyed();
     void onNotificationClosed(quint32 id, quint32 reason);
     void onNotificationInvoked(quint32 id, const QString& action);
-
+    void onChangeTray(bool enabled, bool hide);
+    void trayClicked(QSystemTrayIcon::ActivationReason reason);
+    void toggleSquawk();
 
 private:
     void createMainWindow();
     void subscribeConversation(Conversation* conv);
     void checkForTheLastWindow();
     void focusConversation(const Models::Roster::ElId& id, const QString& resource = "", const QString& messageId = "");
+    void createTrayIcon();
 
 private:
     typedef std::map<Models::Roster::ElId, Conversation*> Conversations;
@@ -113,6 +119,9 @@ private:
     bool nowQuitting;
     bool destroyingSquawk;
     Notifications storage;
+    QSystemTrayIcon* trayIcon;
+    QAction actionQuit;
+    QAction actionToggle;
 };
 
 #endif // APPLICATION_H
diff --git a/translations/squawk.en.ts b/translations/squawk.en.ts
index b219af0..0aef979 100644
--- a/translations/squawk.en.ts
+++ b/translations/squawk.en.ts
@@ -972,6 +972,11 @@ Would you like to check them and try again?</translation>
         <source>Downloads path</source>
         <translation>Downloads path</translation>
     </message>
+    <message>
+        <location filename="../ui/widgets/settings/pagegeneral.ui" line="45"/>
+        <source>Close to tray icon</source>
+        <translation>Close to tray icon</translation>
+    </message>
     <message>
         <location filename="../ui/widgets/settings/pagegeneral.ui" line="36"/>
         <location filename="../build/squawk_autogen/include/ui_pagegeneral.h" line="70"/>
diff --git a/translations/squawk.pt_BR.ts b/translations/squawk.pt_BR.ts
index 6041678..f818e63 100644
--- a/translations/squawk.pt_BR.ts
+++ b/translations/squawk.pt_BR.ts
@@ -761,6 +761,11 @@ Você pode tentar novamente</translation>
         <source>Downloads path</source>
         <translation>Pasta de downloads</translation>
     </message>
+    <message>
+        <location filename="../ui/widgets/settings/pagegeneral.ui" line="45"/>
+        <source>Close to tray icon</source>
+        <translation>Fechar ao ícone da bandeja</translation>
+    </message>
     <message>
         <source>Browse</source>
         <translation>6 / 5,000
diff --git a/translations/squawk.ru.ts b/translations/squawk.ru.ts
index 39dbfac..1774bb9 100644
--- a/translations/squawk.ru.ts
+++ b/translations/squawk.ru.ts
@@ -765,6 +765,11 @@ You can try again</source>
         <source>Downloads path</source>
         <translation>Папка для сохраненных файлов</translation>
     </message>
+    <message>
+        <location filename="../ui/widgets/settings/pagegeneral.ui" line="45"/>
+        <source>Close to tray icon</source>
+        <translation>Закрывать в трей</translation>
+    </message>
     <message>
         <source>Browse</source>
         <translation>Выбрать</translation>
diff --git a/ui/squawk.cpp b/ui/squawk.cpp
index db92e93..39a7202 100644
--- a/ui/squawk.cpp
+++ b/ui/squawk.cpp
@@ -56,7 +56,7 @@ Squawk::Squawk(Models::Roster& p_rosterModel, QWidget *parent) :
     connect(m_ui->actionPreferences, &QAction::triggered, this, &Squawk::onPreferences);
     connect(m_ui->actionAddContact, &QAction::triggered, this, &Squawk::onNewContact);
     connect(m_ui->actionAddConference, &QAction::triggered, this, &Squawk::onNewConference);
-    connect(m_ui->actionQuit, &QAction::triggered, this, &Squawk::close);
+    connect(m_ui->actionQuit, &QAction::triggered, this, &Squawk::quit);
     connect(m_ui->comboBox, qOverload<int>(&QComboBox::activated), this, &Squawk::onComboboxActivated);
     //connect(m_ui->roster, &QTreeView::doubleClicked, this, &Squawk::onRosterItemDoubleClicked);
     connect(m_ui->roster, &QTreeView::customContextMenuRequested, this, &Squawk::onRosterContextMenu);
@@ -122,6 +122,7 @@ void Squawk::onPreferences()
         preferences->setAttribute(Qt::WA_DeleteOnClose);
         connect(preferences, &Settings::destroyed, this, &Squawk::onPreferencesClosed);
         connect(preferences, &Settings::changeDownloadsPath, this, &Squawk::changeDownloadsPath);
+        connect(preferences, &Settings::changeTray, this, &Squawk::changeTray);
 
         preferences->show();
     } else {
diff --git a/ui/squawk.h b/ui/squawk.h
index f55a000..19fc058 100644
--- a/ui/squawk.h
+++ b/ui/squawk.h
@@ -59,6 +59,7 @@ public:
     
 signals:
     void closing();
+    void quit();
     void newAccountRequest(const QMap<QString, QVariant>&);
     void removeAccountRequest(const QString&);
     void connectAccount(const QString&);
@@ -74,6 +75,7 @@ signals:
     void requestVCard(const QString& account, const QString& jid);
     void uploadVCard(const QString& account, const Shared::VCard& card);
     void changeDownloadsPath(const QString& path);
+    void changeTray(bool enabled, bool hide);
 
     void notify(const QString& account, const Shared::Message& msg);
     void changeSubscription(const Models::Roster::ElId& id, bool subscribe);
diff --git a/ui/widgets/settings/pagegeneral.cpp b/ui/widgets/settings/pagegeneral.cpp
index 9ed46a2..39e155a 100644
--- a/ui/widgets/settings/pagegeneral.cpp
+++ b/ui/widgets/settings/pagegeneral.cpp
@@ -28,6 +28,23 @@ PageGeneral::PageGeneral(QWidget* parent):
 
     QSettings settings;
     m_ui->downloadsPathInput->setText(settings.value("downloadsPath").toString());
+    if (QSystemTrayIcon::isSystemTrayAvailable()) {
+        bool tray = settings.value("tray", false).toBool();
+        m_ui->trayInput->setChecked(tray);
+        if (tray) {
+            m_ui->hideTrayInput->setChecked(settings.value("hideTray", false).toBool());
+        } else {
+            m_ui->hideTrayInput->setEnabled(false);
+        }
+
+        connect(m_ui->trayInput, &QCheckBox::stateChanged, this, &PageGeneral::onTrayChecked);
+        connect(m_ui->hideTrayInput, &QCheckBox::stateChanged, this, &PageGeneral::onHideTrayChecked);
+    } else {
+        m_ui->trayInput->setEnabled(false);
+        m_ui->hideTrayInput->setEnabled(false);
+        m_ui->trayInput->setToolTip(tr("Tray is not available for your system"));       //TODO translate
+    }
+
     connect(m_ui->downloadsPathButton, &QPushButton::clicked, this, &PageGeneral::onBrowseButtonClicked);
 }
 
@@ -76,3 +93,19 @@ void PageGeneral::onDialogDestroyed()
 {
     dialog = nullptr;
 }
+
+void PageGeneral::onTrayChecked(int state)
+{
+    bool enabled = state == Qt::Checked;
+    emit variableModified("tray", enabled);
+    m_ui->hideTrayInput->setEnabled(enabled);
+    if (!enabled) {
+        m_ui->hideTrayInput->setEnabled(false);
+    }
+}
+
+void PageGeneral::onHideTrayChecked(int state)
+{
+    bool enabled = state == Qt::Checked;
+    emit variableModified("hideTray", enabled);
+}
diff --git a/ui/widgets/settings/pagegeneral.h b/ui/widgets/settings/pagegeneral.h
index 7f58d71..15738be 100644
--- a/ui/widgets/settings/pagegeneral.h
+++ b/ui/widgets/settings/pagegeneral.h
@@ -24,6 +24,7 @@
 #include <QVariant>
 #include <QSettings>
 #include <QFileDialog>
+#include <QSystemTrayIcon>
 
 namespace Ui
 {
@@ -47,6 +48,8 @@ private slots:
     void onBrowseButtonClicked();
     void onDialogAccepted();
     void onDialogDestroyed();
+    void onTrayChecked(int state);
+    void onHideTrayChecked(int state);
 
 private:
     QScopedPointer<Ui::PageGeneral> m_ui;
diff --git a/ui/widgets/settings/pagegeneral.ui b/ui/widgets/settings/pagegeneral.ui
index e412668..e08b606 100644
--- a/ui/widgets/settings/pagegeneral.ui
+++ b/ui/widgets/settings/pagegeneral.ui
@@ -6,8 +6,8 @@
    <rect>
     <x>0</x>
     <y>0</y>
-    <width>400</width>
-    <height>300</height>
+    <width>477</width>
+    <height>310</height>
    </rect>
   </property>
   <layout class="QFormLayout" name="formLayout">
@@ -39,6 +39,34 @@
      </item>
     </layout>
    </item>
+   <item row="2" column="0">
+    <widget class="QLabel" name="trayLabel">
+     <property name="text">
+      <string>Tray icon</string>
+     </property>
+    </widget>
+   </item>
+   <item row="2" column="1">
+    <widget class="QCheckBox" name="trayInput">
+     <property name="text">
+      <string>Mimimize Squawk to tray when closing main window</string>
+     </property>
+    </widget>
+   </item>
+   <item row="3" column="0">
+    <widget class="QLabel" name="hideTrayLabel">
+     <property name="text">
+      <string>Hide tray icon</string>
+     </property>
+    </widget>
+   </item>
+   <item row="3" column="1">
+    <widget class="QCheckBox" name="hideTrayInput">
+     <property name="text">
+      <string>Hide tray icon when Squawk main window is visible</string>
+     </property>
+    </widget>
+   </item>
   </layout>
  </widget>
  <resources/>
diff --git a/ui/widgets/settings/settings.cpp b/ui/widgets/settings/settings.cpp
index cf5e905..62dd80b 100644
--- a/ui/widgets/settings/settings.cpp
+++ b/ui/widgets/settings/settings.cpp
@@ -57,6 +57,7 @@ void Settings::onVariableModified(const QString& key, const QVariant& value)
 void Settings::apply()
 {
     QSettings settings;
+    bool trayChanged = false;
     for (const std::pair<const QString, QVariant>& pair: modifiedSettings) {
         if (pair.first == "style") {
             Shared::Global::setStyle(pair.second.toString());
@@ -73,8 +74,18 @@ void Settings::apply()
                     emit changeDownloadsPath(path);
                 }
             }
+        } else if (pair.first == "tray" || pair.first == "hideTray") {
+            bool oldValue = settings.value(pair.first, false).toBool();
+            bool newValue = pair.second.toBool();
+            if (oldValue != newValue) {
+                trayChanged = true;
+                settings.setValue(pair.first, pair.second);
+            }
         }
+    }
 
+    if (trayChanged) {
+        emit changeTray(settings.value("tray", false).toBool(), settings.value("hideTray", false).toBool());
     }
 
     modifiedSettings.clear();
diff --git a/ui/widgets/settings/settings.h b/ui/widgets/settings/settings.h
index 689e0ce..59a65cb 100644
--- a/ui/widgets/settings/settings.h
+++ b/ui/widgets/settings/settings.h
@@ -45,6 +45,7 @@ public:
 
 signals:
     void changeDownloadsPath(const QString& path);
+    void changeTray(bool enable, bool hide);
 
 public slots:
     void apply();

From d162494ec8d6c26fd48099ca3558acd931705b88 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Wed, 17 Aug 2022 19:25:35 +0300
Subject: [PATCH 206/281] Better way to store expanded elements in roster,
 several clean ups, translations

---
 CHANGELOG.md                        |   3 +
 main/application.cpp                |  96 ++++++---
 main/application.h                  |   7 +-
 translations/squawk.en.ts           | 259 ++++++++----------------
 translations/squawk.pt_BR.ts        | 299 +++++++++++++++++++++++++++-
 translations/squawk.ru.ts           | 299 +++++++++++++++++++++++++++-
 ui/models/element.cpp               |   5 +
 ui/models/element.h                 |   1 +
 ui/models/item.cpp                  |   5 +
 ui/models/item.h                    |   1 +
 ui/models/reference.cpp             |   5 +
 ui/models/reference.h               |   2 +
 ui/models/room.h                    |   1 -
 ui/models/roster.cpp                |  82 +++++++-
 ui/models/roster.h                  |  11 +-
 ui/squawk.cpp                       |  45 +----
 ui/squawk.h                         |   3 +-
 ui/widgets/settings/pagegeneral.cpp |   2 +-
 18 files changed, 874 insertions(+), 252 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 40a85c8..7107e26 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,9 +3,12 @@
 ## Squawk 0.2.3 (UNRELEASED)
 ### Bug fixes
 - "Add contact" and "Join conference" menu are enabled once again (pavavno)!
+- availability is now read from the same section of config file it was stored
 
 ### Improvements
 - deactivated accounts now don't appear in combobox of "Add contact" and "Join conference" dialogues
+- all of the expandable roster items now get saved between launches
+- settings file on the disk is not rewritten every roster element expansion or collapse
 
 ### New features
 - Now you can enable tray icon from settings!
diff --git a/main/application.cpp b/main/application.cpp
index a319ee5..3942e53 100644
--- a/main/application.cpp
+++ b/main/application.cpp
@@ -30,16 +30,18 @@ Application::Application(Core::Squawk* p_core):
     storage(),
     trayIcon(nullptr),
     actionQuit(Shared::icon("application-exit"), tr("Quit")),
-    actionToggle(tr("Minimize to tray"))
+    actionToggle(QApplication::windowIcon(), tr("Minimize to tray")),
+    expandedPaths()
 {
     connect(&actionQuit, &QAction::triggered, this, &Application::quit);
     connect(&actionToggle, &QAction::triggered, this, &Application::toggleSquawk);
 
     connect(&roster, &Models::Roster::unnoticedMessage, this, &Application::notify);
     connect(&roster, &Models::Roster::unreadMessagesCountChanged, this, &Application::unreadMessagesCountChanged);
+    connect(&roster, &Models::Roster::addedElement, this, &Application::onAddedElement);
 
 
-    //connecting myself to the backed
+    //connecting myself to the backend
     connect(this, &Application::changeState, core, &Core::Squawk::changeState);
     connect(this, &Application::setRoomJoined, core, &Core::Squawk::setRoomJoined);
     connect(this, &Application::setRoomAutoJoin, core, &Core::Squawk::setRoomAutoJoin);
@@ -70,7 +72,7 @@ Application::Application(Core::Squawk* p_core):
     connect(core, &Core::Squawk::changeAccount, this, &Application::changeAccount);
     connect(core, &Core::Squawk::removeAccount, this, &Application::removeAccount);
 
-    connect(core, &Core::Squawk::addContact, this, &Application::addContact);
+    connect(core, &Core::Squawk::addContact, &roster, &Models::Roster::addContact);
     connect(core, &Core::Squawk::addGroup, this, &Application::addGroup);
     connect(core, &Core::Squawk::removeGroup, &roster, &Models::Roster::removeGroup);
     connect(core, qOverload<const QString&, const QString&>(&Core::Squawk::removeContact),
@@ -168,6 +170,8 @@ void Application::createMainWindow()
         connect(squawk, &Squawk::openConversation, this, &Application::openConversation);
         connect(squawk, &Squawk::changeState, this, &Application::setState);
         connect(squawk, &Squawk::changeTray, this, &Application::onChangeTray);
+        connect(squawk, &Squawk::itemExpanded, this, &Application::onItemExpanded);
+        connect(squawk, &Squawk::itemCollapsed, this, &Application::onItemCollapsed);
         connect(squawk, &Squawk::quit, this, &Application::quit);
         connect(squawk, &Squawk::closing, this, &Application::onSquawkClosing);
 
@@ -192,7 +196,19 @@ void Application::createMainWindow()
 
         dialogueQueue.setParentWidnow(squawk);
         squawk->stateChanged(availability);
+        squawk->raise();
         squawk->show();
+        squawk->activateWindow();
+
+        for (const std::list<QString>& entry : expandedPaths) {
+            QModelIndex ind = roster.getIndexByPath(entry);
+            if (ind.isValid()) {
+                squawk->expand(ind);
+            }
+        }
+
+        connect(squawk, &Squawk::itemExpanded, this, &Application::onItemExpanded);
+        connect(squawk, &Squawk::itemCollapsed, this, &Application::onItemCollapsed);
     }
 }
 
@@ -292,6 +308,32 @@ void Application::toggleSquawk()
     }
 }
 
+void Application::onItemCollapsed(const QModelIndex& index)
+{
+    std::list<QString> address = roster.getItemPath(index);
+    if (address.size() > 0) {
+        expandedPaths.erase(address);
+    }
+}
+
+void Application::onItemExpanded(const QModelIndex& index)
+{
+    std::list<QString> address = roster.getItemPath(index);
+    if (address.size() > 0) {
+        expandedPaths.insert(address);
+    }
+}
+
+void Application::onAddedElement(const std::list<QString>& path)
+{
+    if (squawk != nullptr && expandedPaths.count(path) > 0) {
+        QModelIndex index = roster.getIndexByPath(path);
+        if (index.isValid()) {
+            squawk->expand(index);
+        }
+    }
+}
+
 void Application::notify(const QString& account, const Shared::Message& msg)
 {
     QString jid = msg.getPenPalJid();
@@ -427,6 +469,18 @@ void Application::readSettings()
     } else {
         avail = static_cast<int>(Shared::Availability::online);
     }
+
+    settings.beginGroup("roster");
+    QStringList entries = settings.allKeys();
+    for (const QString& entry : entries) {
+        QStringList p = entry.split("/");
+        if (p.last() == "expanded" && settings.value(entry, false).toBool()) {
+            p.pop_back();
+            expandedPaths.emplace(p.begin(), p.end());
+        }
+    }
+
+    settings.endGroup();
     settings.endGroup();
 
     setState(Shared::Global::fromInt<Shared::Availability>(avail));
@@ -440,7 +494,22 @@ void Application::readSettings()
 void Application::writeSettings()
 {
     QSettings settings;
-    settings.setValue("availability", static_cast<int>(availability));
+    settings.beginGroup("ui");
+        settings.setValue("availability", static_cast<int>(availability));
+
+        settings.remove("roster");
+        settings.beginGroup("roster");
+            for (const std::list<QString>& address : expandedPaths) {
+                QString path = "";
+                for (const QString& hop : address) {
+                    path += hop + "/";
+                }
+                path += "expanded";
+                settings.setValue(path, true);
+            }
+
+        settings.endGroup();
+    settings.endGroup();
 }
 
 void Application::requestPassword(const QString& account, bool authenticationError) {
@@ -611,25 +680,6 @@ void Application::changeAccount(const QString& account, const QMap<QString, QVar
     }
 }
 
-void Application::addContact(const QString& account, const QString& jid, const QString& group, const QMap<QString, QVariant>& data)
-{
-    roster.addContact(account, jid, group, data);
-
-    if (squawk != nullptr) {
-        QSettings settings;
-        settings.beginGroup("ui");
-        settings.beginGroup("roster");
-        settings.beginGroup(account);
-        if (settings.value("expanded", false).toBool()) {
-            QModelIndex ind = roster.getAccountIndex(account);
-            squawk->expand(ind);
-        }
-        settings.endGroup();
-        settings.endGroup();
-        settings.endGroup();
-    }
-}
-
 void Application::addGroup(const QString& account, const QString& name)
 {
     roster.addGroup(account, name);
diff --git a/main/application.h b/main/application.h
index db60009..54c2dbc 100644
--- a/main/application.h
+++ b/main/application.h
@@ -18,6 +18,8 @@
 #define APPLICATION_H
 
 #include <map>
+#include <list>
+#include <set>
 
 #include <QObject>
 #include <QDBusInterface>
@@ -76,7 +78,6 @@ protected slots:
     void openConversation(const Models::Roster::ElId& id, const QString& resource = "");
 
     void addGroup(const QString& account, const QString& name);
-    void addContact(const QString& account, const QString& jid, const QString& group, const QMap<QString, QVariant>& data);
 
     void requestPassword(const QString& account, bool authenticationError);
 
@@ -97,6 +98,9 @@ private slots:
     void onChangeTray(bool enabled, bool hide);
     void trayClicked(QSystemTrayIcon::ActivationReason reason);
     void toggleSquawk();
+    void onItemExpanded(const QModelIndex& index);
+    void onItemCollapsed(const QModelIndex& index);
+    void onAddedElement(const std::list<QString>& path);
 
 private:
     void createMainWindow();
@@ -122,6 +126,7 @@ private:
     QSystemTrayIcon* trayIcon;
     QAction actionQuit;
     QAction actionToggle;
+    std::set<std::list<QString>> expandedPaths;
 };
 
 #endif // APPLICATION_H
diff --git a/translations/squawk.en.ts b/translations/squawk.en.ts
index 0aef979..db178ac 100644
--- a/translations/squawk.en.ts
+++ b/translations/squawk.en.ts
@@ -5,51 +5,43 @@
     <name>About</name>
     <message>
         <location filename="../ui/widgets/about.ui" line="14"/>
-        <location filename="../build/squawk_autogen/include/ui_about.h" line="420"/>
         <source>About Squawk</source>
         <translatorcomment>About window header</translatorcomment>
         <translation>About Squawk</translation>
     </message>
     <message>
         <location filename="../ui/widgets/about.ui" line="44"/>
-        <location filename="../build/squawk_autogen/include/ui_about.h" line="421"/>
         <source>Squawk</source>
         <translation>Squawk</translation>
     </message>
     <message>
         <location filename="../ui/widgets/about.ui" line="96"/>
-        <location filename="../build/squawk_autogen/include/ui_about.h" line="427"/>
         <source>About</source>
         <translatorcomment>Tab title</translatorcomment>
         <translation>About</translation>
     </message>
     <message>
         <location filename="../ui/widgets/about.ui" line="115"/>
-        <location filename="../build/squawk_autogen/include/ui_about.h" line="423"/>
         <source>XMPP (jabber) messenger</source>
         <translation>XMPP (jabber) messenger</translation>
     </message>
     <message>
         <location filename="../ui/widgets/about.ui" line="122"/>
-        <location filename="../build/squawk_autogen/include/ui_about.h" line="424"/>
         <source>(c) 2019 - 2022, Yury Gubich</source>
         <translation>(c) 2019 - 2022, Yury Gubich</translation>
     </message>
     <message>
         <location filename="../ui/widgets/about.ui" line="129"/>
-        <location filename="../build/squawk_autogen/include/ui_about.h" line="425"/>
         <source>&lt;a href=&quot;https://git.macaw.me/blue/squawk&quot;&gt;Project site&lt;/a&gt;</source>
         <translation>&lt;a href=&quot;https://git.macaw.me/blue/squawk&quot;&gt;Project site&lt;/a&gt;</translation>
     </message>
     <message>
         <location filename="../ui/widgets/about.ui" line="142"/>
-        <location filename="../build/squawk_autogen/include/ui_about.h" line="426"/>
         <source>&lt;a href=&quot;https://git.macaw.me/blue/squawk/src/branch/master/LICENSE.md&quot;&gt;License: GNU General Public License version 3&lt;/a&gt;</source>
         <translation>&lt;a href=&quot;https://git.macaw.me/blue/squawk/src/branch/master/LICENSE.md&quot;&gt;License: GNU General Public License version 3&lt;/a&gt;</translation>
     </message>
     <message>
         <location filename="../ui/widgets/about.ui" line="175"/>
-        <location filename="../build/squawk_autogen/include/ui_about.h" line="432"/>
         <source>Components</source>
         <translatorcomment>Tab header</translatorcomment>
         <translation>Components</translation>
@@ -58,9 +50,6 @@
         <location filename="../ui/widgets/about.ui" line="216"/>
         <location filename="../ui/widgets/about.ui" line="312"/>
         <location filename="../ui/widgets/about.ui" line="660"/>
-        <location filename="../build/squawk_autogen/include/ui_about.h" line="428"/>
-        <location filename="../build/squawk_autogen/include/ui_about.h" line="430"/>
-        <location filename="../build/squawk_autogen/include/ui_about.h" line="446"/>
         <source>Version</source>
         <translation>Version</translation>
     </message>
@@ -68,21 +57,16 @@
         <location filename="../ui/widgets/about.ui" line="228"/>
         <location filename="../ui/widgets/about.ui" line="324"/>
         <location filename="../ui/widgets/about.ui" line="670"/>
-        <location filename="../build/squawk_autogen/include/ui_about.h" line="429"/>
-        <location filename="../build/squawk_autogen/include/ui_about.h" line="431"/>
-        <location filename="../build/squawk_autogen/include/ui_about.h" line="447"/>
         <source>0.0.0</source>
         <translation>0.0.0</translation>
     </message>
     <message>
         <location filename="../ui/widgets/about.ui" line="397"/>
-        <location filename="../build/squawk_autogen/include/ui_about.h" line="438"/>
         <source>Report Bugs</source>
         <translation>Report Bugs</translation>
     </message>
     <message>
         <location filename="../ui/widgets/about.ui" line="416"/>
-        <location filename="../build/squawk_autogen/include/ui_about.h" line="433"/>
         <source>Please report any bug you find!
 To report bugs you can use:</source>
         <translation>Please report any bug you find!
@@ -90,61 +74,51 @@ To report bugs you can use:</translation>
     </message>
     <message>
         <location filename="../ui/widgets/about.ui" line="424"/>
-        <location filename="../build/squawk_autogen/include/ui_about.h" line="435"/>
         <source>&lt;a href=&quot;https://git.macaw.me/blue/squawk/issues&quot;&gt;Project bug tracker&lt;/&gt;</source>
         <translation>&lt;a href=&quot;https://git.macaw.me/blue/squawk/issues&quot;&gt;Project bug tracker&lt;/&gt;</translation>
     </message>
     <message>
         <location filename="../ui/widgets/about.ui" line="434"/>
-        <location filename="../build/squawk_autogen/include/ui_about.h" line="436"/>
         <source>XMPP (&lt;a href=&quot;xmpp:blue@macaw.me&quot;&gt;blue@macaw.me&lt;/a&gt;)</source>
         <translation>XMPP (&lt;a href=&quot;xmpp:blue@macaw.me&quot;&gt;blue@macaw.me&lt;/a&gt;)</translation>
     </message>
     <message>
         <location filename="../ui/widgets/about.ui" line="444"/>
-        <location filename="../build/squawk_autogen/include/ui_about.h" line="437"/>
         <source>E-Mail (&lt;a href=&quot;mailto:blue@macaw.me&quot;&gt;blue@macaw.me&lt;/a&gt;)</source>
         <translation>E-Mail (&lt;a href=&quot;mailto:blue@macaw.me&quot;&gt;blue@macaw.me&lt;/a&gt;)</translation>
     </message>
     <message>
         <location filename="../ui/widgets/about.ui" line="477"/>
-        <location filename="../build/squawk_autogen/include/ui_about.h" line="445"/>
         <source>Thanks To</source>
         <translation>Thanks to</translation>
     </message>
     <message>
         <location filename="../ui/widgets/about.ui" line="519"/>
-        <location filename="../build/squawk_autogen/include/ui_about.h" line="439"/>
         <source>Vae</source>
         <translation>Vae</translation>
     </message>
     <message>
         <location filename="../ui/widgets/about.ui" line="531"/>
-        <location filename="../build/squawk_autogen/include/ui_about.h" line="440"/>
         <source>Major refactoring, bug fixes, constructive criticism</source>
         <translation>Major refactoring, bug fixes, constructive criticism</translation>
     </message>
     <message>
         <location filename="../ui/widgets/about.ui" line="568"/>
-        <location filename="../build/squawk_autogen/include/ui_about.h" line="441"/>
         <source>Shunf4</source>
         <translation>Shunf4</translation>
     </message>
     <message>
         <location filename="../ui/widgets/about.ui" line="580"/>
-        <location filename="../build/squawk_autogen/include/ui_about.h" line="442"/>
         <source>Major refactoring, bug fixes, build adaptations for Windows and MacOS</source>
         <translation>Major refactoring, bug fixes, build adaptations for Windows and MacOS</translation>
     </message>
     <message>
         <location filename="../ui/widgets/about.ui" line="617"/>
-        <location filename="../build/squawk_autogen/include/ui_about.h" line="443"/>
         <source>Bruno F. Fontes</source>
         <translation>Bruno F. Fontes</translation>
     </message>
     <message>
         <location filename="../ui/widgets/about.ui" line="629"/>
-        <location filename="../build/squawk_autogen/include/ui_about.h" line="444"/>
         <source>Brazilian Portuguese translation</source>
         <translation>Brazilian Portuguese translation</translation>
     </message>
@@ -164,118 +138,100 @@ To report bugs you can use:</translation>
     <name>Account</name>
     <message>
         <location filename="../ui/widgets/accounts/account.ui" line="14"/>
-        <location filename="../build/squawk_autogen/include/ui_account.h" line="166"/>
         <source>Account</source>
         <translatorcomment>Window title</translatorcomment>
         <translation>Account</translation>
     </message>
     <message>
         <location filename="../ui/widgets/accounts/account.ui" line="40"/>
-        <location filename="../build/squawk_autogen/include/ui_account.h" line="168"/>
         <source>Your account login</source>
         <translatorcomment>Tooltip</translatorcomment>
         <translation>Your account login</translation>
     </message>
     <message>
         <location filename="../ui/widgets/accounts/account.ui" line="43"/>
-        <location filename="../build/squawk_autogen/include/ui_account.h" line="170"/>
         <source>john_smith1987</source>
         <translatorcomment>Login placeholder</translatorcomment>
         <translation>john_smith1987</translation>
     </message>
     <message>
         <location filename="../ui/widgets/accounts/account.ui" line="50"/>
-        <location filename="../build/squawk_autogen/include/ui_account.h" line="171"/>
         <source>Server</source>
         <translation>Server</translation>
     </message>
     <message>
         <location filename="../ui/widgets/accounts/account.ui" line="57"/>
-        <location filename="../build/squawk_autogen/include/ui_account.h" line="173"/>
         <source>A server address of your account. Like 404.city or macaw.me</source>
         <translatorcomment>Tooltip</translatorcomment>
         <translation>A server address of your account. Like 404.city or macaw.me</translation>
     </message>
     <message>
         <location filename="../ui/widgets/accounts/account.ui" line="60"/>
-        <location filename="../build/squawk_autogen/include/ui_account.h" line="175"/>
         <source>macaw.me</source>
         <translatorcomment>Placeholder</translatorcomment>
         <translation>macaw.me</translation>
     </message>
     <message>
         <location filename="../ui/widgets/accounts/account.ui" line="67"/>
-        <location filename="../build/squawk_autogen/include/ui_account.h" line="176"/>
         <source>Login</source>
         <translation>Login</translation>
     </message>
     <message>
         <location filename="../ui/widgets/accounts/account.ui" line="74"/>
-        <location filename="../build/squawk_autogen/include/ui_account.h" line="177"/>
         <source>Password</source>
         <translation>Password</translation>
     </message>
     <message>
         <location filename="../ui/widgets/accounts/account.ui" line="81"/>
-        <location filename="../build/squawk_autogen/include/ui_account.h" line="179"/>
         <source>Password of your account</source>
         <translatorcomment>Tooltip</translatorcomment>
         <translation>Password of your account</translation>
     </message>
     <message>
         <location filename="../ui/widgets/accounts/account.ui" line="103"/>
-        <location filename="../build/squawk_autogen/include/ui_account.h" line="184"/>
         <source>Name</source>
         <translation>Name</translation>
     </message>
     <message>
         <location filename="../ui/widgets/accounts/account.ui" line="110"/>
-        <location filename="../build/squawk_autogen/include/ui_account.h" line="186"/>
         <source>Just a name how would you call this account, doesn&apos;t affect anything</source>
         <translation>Just a name how would you call this account, doesn&apos;t affect anything (cant be changed)</translation>
     </message>
     <message>
         <location filename="../ui/widgets/accounts/account.ui" line="113"/>
-        <location filename="../build/squawk_autogen/include/ui_account.h" line="188"/>
         <source>John</source>
         <translatorcomment>Placeholder</translatorcomment>
         <translation>John</translation>
     </message>
     <message>
         <location filename="../ui/widgets/accounts/account.ui" line="120"/>
-        <location filename="../build/squawk_autogen/include/ui_account.h" line="189"/>
         <source>Resource</source>
         <translation>Resource</translation>
     </message>
     <message>
         <location filename="../ui/widgets/accounts/account.ui" line="127"/>
-        <location filename="../build/squawk_autogen/include/ui_account.h" line="191"/>
         <source>A resource name like &quot;Home&quot; or &quot;Work&quot;</source>
         <translatorcomment>Tooltip</translatorcomment>
         <translation>A resource name like &quot;Home&quot; or &quot;Work&quot;</translation>
     </message>
     <message>
         <location filename="../ui/widgets/accounts/account.ui" line="130"/>
-        <location filename="../build/squawk_autogen/include/ui_account.h" line="193"/>
         <source>QXmpp</source>
         <translatorcomment>Default resource</translatorcomment>
         <translation>QXmpp</translation>
     </message>
     <message>
         <location filename="../ui/widgets/accounts/account.ui" line="137"/>
-        <location filename="../build/squawk_autogen/include/ui_account.h" line="194"/>
         <source>Password storage</source>
         <translation>Password storage</translation>
     </message>
     <message>
         <location filename="../ui/widgets/accounts/account.ui" line="163"/>
-        <location filename="../build/squawk_autogen/include/ui_account.h" line="196"/>
         <source>Active</source>
         <translation>Active</translation>
     </message>
     <message>
         <location filename="../ui/widgets/accounts/account.ui" line="170"/>
-        <location filename="../build/squawk_autogen/include/ui_account.h" line="197"/>
         <source>enable</source>
         <translation>enable</translation>
     </message>
@@ -284,37 +240,31 @@ To report bugs you can use:</translation>
     <name>Accounts</name>
     <message>
         <location filename="../ui/widgets/accounts/accounts.ui" line="14"/>
-        <location filename="../build/squawk_autogen/include/ui_accounts.h" line="108"/>
         <source>Accounts</source>
         <translation>Accounts</translation>
     </message>
     <message>
         <location filename="../ui/widgets/accounts/accounts.ui" line="45"/>
-        <location filename="../build/squawk_autogen/include/ui_accounts.h" line="109"/>
         <source>Delete</source>
         <translation>Delete</translation>
     </message>
     <message>
         <location filename="../ui/widgets/accounts/accounts.ui" line="86"/>
-        <location filename="../build/squawk_autogen/include/ui_accounts.h" line="110"/>
         <source>Add</source>
         <translation>Add</translation>
     </message>
     <message>
         <location filename="../ui/widgets/accounts/accounts.ui" line="96"/>
-        <location filename="../build/squawk_autogen/include/ui_accounts.h" line="111"/>
         <source>Edit</source>
         <translation>Edit</translation>
     </message>
     <message>
         <location filename="../ui/widgets/accounts/accounts.ui" line="106"/>
-        <location filename="../build/squawk_autogen/include/ui_accounts.h" line="112"/>
         <source>Change password</source>
         <translation>Change password</translation>
     </message>
     <message>
         <location filename="../ui/widgets/accounts/accounts.ui" line="129"/>
-        <location filename="../build/squawk_autogen/include/ui_accounts.h" line="113"/>
         <source>Connect</source>
         <translation>Connect</translation>
     </message>
@@ -333,22 +283,38 @@ To report bugs you can use:</translation>
 <context>
     <name>Application</name>
     <message>
-        <location filename="../main/application.cpp" line="225"/>
+        <location filename="../main/application.cpp" line="32"/>
+        <source>Quit</source>
+        <translation>Quit</translation>
+    </message>
+    <message>
+        <location filename="../main/application.cpp" line="33"/>
+        <location filename="../main/application.cpp" line="305"/>
+        <source>Minimize to tray</source>
+        <translation>Minimize to tray</translation>
+    </message>
+    <message>
+        <location filename="../main/application.cpp" line="232"/>
+        <source>Show Squawk</source>
+        <translation>Show Squawk</translation>
+    </message>
+    <message>
+        <location filename="../main/application.cpp" line="353"/>
         <source> from </source>
         <translation> from </translation>
     </message>
     <message>
-        <location filename="../main/application.cpp" line="233"/>
+        <location filename="../main/application.cpp" line="361"/>
         <source>Attached file</source>
         <translation>Attached file</translation>
     </message>
     <message>
-        <location filename="../main/application.cpp" line="238"/>
+        <location filename="../main/application.cpp" line="366"/>
         <source>Mark as Read</source>
         <translation>Mark as Read</translation>
     </message>
     <message>
-        <location filename="../main/application.cpp" line="239"/>
+        <location filename="../main/application.cpp" line="367"/>
         <source>Open conversation</source>
         <translation>Open conversation</translation>
     </message>
@@ -357,7 +323,6 @@ To report bugs you can use:</translation>
     <name>Conversation</name>
     <message>
         <location filename="../ui/widgets/conversation.ui" line="426"/>
-        <location filename="../build/squawk_autogen/include/ui_conversation.h" line="310"/>
         <source>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
 &lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
 p, li { white-space: pre-wrap; }
@@ -371,7 +336,6 @@ p, li { white-space: pre-wrap; }
     </message>
     <message>
         <location filename="../ui/widgets/conversation.ui" line="436"/>
-        <location filename="../build/squawk_autogen/include/ui_conversation.h" line="315"/>
         <source>Type your message here...</source>
         <translatorcomment>Placeholder</translatorcomment>
         <translation>Type your message here...</translation>
@@ -431,14 +395,12 @@ p, li { white-space: pre-wrap; }
     <name>CredentialsPrompt</name>
     <message>
         <location filename="../ui/widgets/accounts/credentialsprompt.ui" line="14"/>
-        <location filename="../build/squawk_autogen/include/ui_credentialsprompt.h" line="106"/>
         <source>Authentication error: %1</source>
         <translatorcomment>Window title</translatorcomment>
         <translation>Authentication error: %1</translation>
     </message>
     <message>
         <location filename="../ui/widgets/accounts/credentialsprompt.ui" line="29"/>
-        <location filename="../build/squawk_autogen/include/ui_credentialsprompt.h" line="107"/>
         <source>Couldn&apos;t authenticate account %1: login or password is icorrect.
 Would you like to check them and try again?</source>
         <translation>Couldn&apos;t authenticate account %1: login or password is incorrect.
@@ -446,26 +408,22 @@ Would you like to check them and try again?</translation>
     </message>
     <message>
         <location filename="../ui/widgets/accounts/credentialsprompt.ui" line="45"/>
-        <location filename="../build/squawk_autogen/include/ui_credentialsprompt.h" line="109"/>
         <source>Login</source>
         <translation>Login</translation>
     </message>
     <message>
         <location filename="../ui/widgets/accounts/credentialsprompt.ui" line="52"/>
-        <location filename="../build/squawk_autogen/include/ui_credentialsprompt.h" line="111"/>
         <source>Your account login (without @server.domain)</source>
         <translatorcomment>Tooltip</translatorcomment>
         <translation>Your account login (without @server.domain)</translation>
     </message>
     <message>
         <location filename="../ui/widgets/accounts/credentialsprompt.ui" line="59"/>
-        <location filename="../build/squawk_autogen/include/ui_credentialsprompt.h" line="113"/>
         <source>Password</source>
         <translation>Password</translation>
     </message>
     <message>
         <location filename="../ui/widgets/accounts/credentialsprompt.ui" line="66"/>
-        <location filename="../build/squawk_autogen/include/ui_credentialsprompt.h" line="115"/>
         <source>Your password</source>
         <translation>Password</translation>
     </message>
@@ -724,61 +682,51 @@ Would you like to check them and try again?</translation>
     <name>JoinConference</name>
     <message>
         <location filename="../ui/widgets/joinconference.ui" line="14"/>
-        <location filename="../build/squawk_autogen/include/ui_joinconference.h" line="116"/>
         <source>Join new conference</source>
         <translation>Join new conference</translation>
     </message>
     <message>
         <location filename="../ui/widgets/joinconference.ui" line="22"/>
-        <location filename="../build/squawk_autogen/include/ui_joinconference.h" line="117"/>
         <source>JID</source>
         <translation>JID</translation>
     </message>
     <message>
         <location filename="../ui/widgets/joinconference.ui" line="29"/>
-        <location filename="../build/squawk_autogen/include/ui_joinconference.h" line="119"/>
         <source>Room JID</source>
         <translation>Room JID</translation>
     </message>
     <message>
         <location filename="../ui/widgets/joinconference.ui" line="32"/>
-        <location filename="../build/squawk_autogen/include/ui_joinconference.h" line="121"/>
         <source>identifier@conference.server.org</source>
         <translation>identifier@conference.server.org</translation>
     </message>
     <message>
         <location filename="../ui/widgets/joinconference.ui" line="39"/>
-        <location filename="../build/squawk_autogen/include/ui_joinconference.h" line="122"/>
         <source>Account</source>
         <translation>Account</translation>
     </message>
     <message>
         <location filename="../ui/widgets/joinconference.ui" line="49"/>
-        <location filename="../build/squawk_autogen/include/ui_joinconference.h" line="123"/>
         <source>Join on login</source>
         <translation>Join on login</translation>
     </message>
     <message>
         <location filename="../ui/widgets/joinconference.ui" line="56"/>
-        <location filename="../build/squawk_autogen/include/ui_joinconference.h" line="125"/>
         <source>If checked Squawk will try to join this conference on login</source>
         <translation>If checked Squawk will try to join this conference on login</translation>
     </message>
     <message>
         <location filename="../ui/widgets/joinconference.ui" line="66"/>
-        <location filename="../build/squawk_autogen/include/ui_joinconference.h" line="128"/>
         <source>Nick name</source>
         <translation>Nick name</translation>
     </message>
     <message>
         <location filename="../ui/widgets/joinconference.ui" line="73"/>
-        <location filename="../build/squawk_autogen/include/ui_joinconference.h" line="130"/>
         <source>Your nick name for that conference. If you leave this field empty your account name will be used as a nick name</source>
         <translation>Your nick name for that conference. If you leave this field empty your account name will be used as a nick name</translation>
     </message>
     <message>
         <location filename="../ui/widgets/joinconference.ui" line="76"/>
-        <location filename="../build/squawk_autogen/include/ui_joinconference.h" line="132"/>
         <source>John</source>
         <translation>John</translation>
     </message>
@@ -818,66 +766,66 @@ Would you like to check them and try again?</translation>
 <context>
     <name>Models::Roster</name>
     <message>
-        <location filename="../ui/models/roster.cpp" line="83"/>
+        <location filename="../ui/models/roster.cpp" line="85"/>
         <source>New messages</source>
         <translation>New messages</translation>
     </message>
     <message>
-        <location filename="../ui/models/roster.cpp" line="196"/>
-        <location filename="../ui/models/roster.cpp" line="251"/>
-        <location filename="../ui/models/roster.cpp" line="263"/>
+        <location filename="../ui/models/roster.cpp" line="198"/>
+        <location filename="../ui/models/roster.cpp" line="253"/>
+        <location filename="../ui/models/roster.cpp" line="265"/>
         <source>New messages: </source>
         <translation>New messages: </translation>
     </message>
     <message>
-        <location filename="../ui/models/roster.cpp" line="198"/>
-        <location filename="../ui/models/roster.cpp" line="266"/>
+        <location filename="../ui/models/roster.cpp" line="200"/>
+        <location filename="../ui/models/roster.cpp" line="268"/>
         <source>Jabber ID: </source>
         <translation>Jabber ID: </translation>
     </message>
     <message>
-        <location filename="../ui/models/roster.cpp" line="202"/>
-        <location filename="../ui/models/roster.cpp" line="221"/>
-        <location filename="../ui/models/roster.cpp" line="234"/>
+        <location filename="../ui/models/roster.cpp" line="204"/>
+        <location filename="../ui/models/roster.cpp" line="223"/>
+        <location filename="../ui/models/roster.cpp" line="236"/>
         <source>Availability: </source>
         <translation>Availability: </translation>
     </message>
     <message>
-        <location filename="../ui/models/roster.cpp" line="206"/>
-        <location filename="../ui/models/roster.cpp" line="224"/>
-        <location filename="../ui/models/roster.cpp" line="237"/>
+        <location filename="../ui/models/roster.cpp" line="208"/>
+        <location filename="../ui/models/roster.cpp" line="226"/>
+        <location filename="../ui/models/roster.cpp" line="239"/>
         <source>Status: </source>
         <translation>Status: </translation>
     </message>
     <message>
-        <location filename="../ui/models/roster.cpp" line="209"/>
         <location filename="../ui/models/roster.cpp" line="211"/>
-        <location filename="../ui/models/roster.cpp" line="267"/>
+        <location filename="../ui/models/roster.cpp" line="213"/>
+        <location filename="../ui/models/roster.cpp" line="269"/>
         <source>Subscription: </source>
         <translation>Subscription: </translation>
     </message>
     <message>
-        <location filename="../ui/models/roster.cpp" line="240"/>
+        <location filename="../ui/models/roster.cpp" line="242"/>
         <source>Affiliation: </source>
         <translation>Affiliation: </translation>
     </message>
     <message>
-        <location filename="../ui/models/roster.cpp" line="241"/>
+        <location filename="../ui/models/roster.cpp" line="243"/>
         <source>Role: </source>
         <translation>Role: </translation>
     </message>
     <message>
-        <location filename="../ui/models/roster.cpp" line="253"/>
+        <location filename="../ui/models/roster.cpp" line="255"/>
         <source>Online contacts: </source>
         <translation>Online contacts: </translation>
     </message>
     <message>
-        <location filename="../ui/models/roster.cpp" line="254"/>
+        <location filename="../ui/models/roster.cpp" line="256"/>
         <source>Total contacts: </source>
         <translation>Total contacts: </translation>
     </message>
     <message>
-        <location filename="../ui/models/roster.cpp" line="269"/>
+        <location filename="../ui/models/roster.cpp" line="271"/>
         <source>Members: </source>
         <translation>Members: </translation>
     </message>
@@ -886,57 +834,48 @@ Would you like to check them and try again?</translation>
     <name>NewContact</name>
     <message>
         <location filename="../ui/widgets/newcontact.ui" line="14"/>
-        <location filename="../build/squawk_autogen/include/ui_newcontact.h" line="103"/>
         <source>Add new contact</source>
         <translatorcomment>Window title</translatorcomment>
         <translation>Add new contact</translation>
     </message>
     <message>
         <location filename="../ui/widgets/newcontact.ui" line="22"/>
-        <location filename="../build/squawk_autogen/include/ui_newcontact.h" line="104"/>
         <source>Account</source>
         <translation>Account</translation>
     </message>
     <message>
         <location filename="../ui/widgets/newcontact.ui" line="29"/>
-        <location filename="../build/squawk_autogen/include/ui_newcontact.h" line="106"/>
         <source>An account that is going to have new contact</source>
         <translation>An account that is going to have new contact</translation>
     </message>
     <message>
         <location filename="../ui/widgets/newcontact.ui" line="36"/>
-        <location filename="../build/squawk_autogen/include/ui_newcontact.h" line="108"/>
         <source>JID</source>
         <translation>JID</translation>
     </message>
     <message>
         <location filename="../ui/widgets/newcontact.ui" line="43"/>
-        <location filename="../build/squawk_autogen/include/ui_newcontact.h" line="110"/>
         <source>Jabber id of your new contact</source>
         <translation>Jabber id of your new contact</translation>
     </message>
     <message>
         <location filename="../ui/widgets/newcontact.ui" line="46"/>
-        <location filename="../build/squawk_autogen/include/ui_newcontact.h" line="112"/>
         <source>name@server.dmn</source>
         <translatorcomment>Placeholder</translatorcomment>
         <translation>name@server.dmn</translation>
     </message>
     <message>
         <location filename="../ui/widgets/newcontact.ui" line="53"/>
-        <location filename="../build/squawk_autogen/include/ui_newcontact.h" line="113"/>
         <source>Name</source>
         <translation>Name</translation>
     </message>
     <message>
         <location filename="../ui/widgets/newcontact.ui" line="60"/>
-        <location filename="../build/squawk_autogen/include/ui_newcontact.h" line="115"/>
         <source>The way this new contact will be labeled in your roster (optional)</source>
         <translation>The way this new contact will be labeled in your roster (optional)</translation>
     </message>
     <message>
         <location filename="../ui/widgets/newcontact.ui" line="63"/>
-        <location filename="../build/squawk_autogen/include/ui_newcontact.h" line="117"/>
         <source>John Smith</source>
         <translation>John Smith</translation>
     </message>
@@ -945,13 +884,11 @@ Would you like to check them and try again?</translation>
     <name>PageAppearance</name>
     <message>
         <location filename="../ui/widgets/settings/pageappearance.ui" line="17"/>
-        <location filename="../build/squawk_autogen/include/ui_pageappearance.h" line="65"/>
         <source>Theme</source>
         <translation>Style</translation>
     </message>
     <message>
         <location filename="../ui/widgets/settings/pageappearance.ui" line="30"/>
-        <location filename="../build/squawk_autogen/include/ui_pageappearance.h" line="66"/>
         <source>Color scheme</source>
         <translation>Color scheme</translation>
     </message>
@@ -968,23 +905,45 @@ Would you like to check them and try again?</translation>
     <name>PageGeneral</name>
     <message>
         <location filename="../ui/widgets/settings/pagegeneral.ui" line="17"/>
-        <location filename="../build/squawk_autogen/include/ui_pagegeneral.h" line="69"/>
         <source>Downloads path</source>
         <translation>Downloads path</translation>
     </message>
     <message>
         <location filename="../ui/widgets/settings/pagegeneral.ui" line="45"/>
+        <source>Tray icon</source>
+        <translation>Tray icon</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/settings/pagegeneral.ui" line="52"/>
+        <source>Mimimize Squawk to tray when closing main window</source>
+        <translation>Mimimize Squawk to tray when closing main window</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/settings/pagegeneral.ui" line="59"/>
+        <source>Hide tray icon</source>
+        <translation>Hide tray icon</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/settings/pagegeneral.ui" line="66"/>
+        <source>Hide tray icon when Squawk main window is visible</source>
+        <translation>Hide tray icon when Squawk main window is visible</translation>
+    </message>
+    <message>
         <source>Close to tray icon</source>
-        <translation>Close to tray icon</translation>
+        <translation type="vanished">Close to tray icon</translation>
     </message>
     <message>
         <location filename="../ui/widgets/settings/pagegeneral.ui" line="36"/>
-        <location filename="../build/squawk_autogen/include/ui_pagegeneral.h" line="70"/>
         <source>Browse</source>
         <translation>Browse</translation>
     </message>
     <message>
         <location filename="../ui/widgets/settings/pagegeneral.cpp" line="45"/>
+        <source>Tray is not available for your system</source>
+        <translation>Tray is not available for your system</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/settings/pagegeneral.cpp" line="62"/>
         <source>Select where downloads folder is going to be</source>
         <translation>Select where downloads folder is going to be</translation>
     </message>
@@ -993,7 +952,6 @@ Would you like to check them and try again?</translation>
     <name>Settings</name>
     <message>
         <location filename="../ui/widgets/settings/settings.ui" line="14"/>
-        <location filename="../build/squawk_autogen/include/ui_settings.h" line="188"/>
         <source>Preferences</source>
         <translatorcomment>Window title</translatorcomment>
         <translation>Preferences</translation>
@@ -1001,32 +959,26 @@ Would you like to check them and try again?</translation>
     <message>
         <location filename="../ui/widgets/settings/settings.ui" line="105"/>
         <location filename="../ui/widgets/settings/settings.ui" line="177"/>
-        <location filename="../build/squawk_autogen/include/ui_settings.h" line="193"/>
-        <location filename="../build/squawk_autogen/include/ui_settings.h" line="201"/>
         <source>General</source>
         <translation>General</translation>
     </message>
     <message>
         <location filename="../ui/widgets/settings/settings.ui" line="117"/>
-        <location filename="../build/squawk_autogen/include/ui_settings.h" line="195"/>
         <source>Appearance</source>
         <translation>Appearance</translation>
     </message>
     <message>
         <location filename="../ui/widgets/settings/settings.ui" line="141"/>
-        <location filename="../build/squawk_autogen/include/ui_settings.h" line="198"/>
         <source>Apply</source>
         <translation>Apply</translation>
     </message>
     <message>
         <location filename="../ui/widgets/settings/settings.ui" line="152"/>
-        <location filename="../build/squawk_autogen/include/ui_settings.h" line="199"/>
         <source>Cancel</source>
         <translation>Cancel</translation>
     </message>
     <message>
         <location filename="../ui/widgets/settings/settings.ui" line="163"/>
-        <location filename="../build/squawk_autogen/include/ui_settings.h" line="200"/>
         <source>Ok</source>
         <translation>Ok</translation>
     </message>
@@ -1035,118 +987,107 @@ Would you like to check them and try again?</translation>
     <name>Squawk</name>
     <message>
         <location filename="../ui/squawk.ui" line="14"/>
-        <location filename="../build/squawk_autogen/include/ui_squawk.h" line="220"/>
         <source>squawk</source>
         <translation>Squawk</translation>
     </message>
     <message>
         <location filename="../ui/squawk.ui" line="161"/>
-        <location filename="../build/squawk_autogen/include/ui_squawk.h" line="228"/>
         <source>Please select a contact to start chatting</source>
         <translation>Please select a contact to start chatting</translation>
     </message>
     <message>
         <location filename="../ui/squawk.ui" line="191"/>
-        <location filename="../build/squawk_autogen/include/ui_squawk.h" line="229"/>
         <source>Settings</source>
         <translation>Settings</translation>
     </message>
     <message>
         <location filename="../ui/squawk.ui" line="198"/>
-        <location filename="../build/squawk_autogen/include/ui_squawk.h" line="230"/>
         <source>Squawk</source>
         <translatorcomment>Menu bar entry</translatorcomment>
         <translation>Squawk</translation>
     </message>
     <message>
         <location filename="../ui/squawk.ui" line="206"/>
-        <location filename="../build/squawk_autogen/include/ui_squawk.h" line="231"/>
         <source>Help</source>
         <translation>Help</translation>
     </message>
     <message>
         <location filename="../ui/squawk.ui" line="220"/>
-        <location filename="../build/squawk_autogen/include/ui_squawk.h" line="221"/>
         <source>Accounts</source>
         <translation>Accounts</translation>
     </message>
     <message>
         <location filename="../ui/squawk.ui" line="229"/>
-        <location filename="../build/squawk_autogen/include/ui_squawk.h" line="222"/>
         <source>Quit</source>
         <translation>Quit</translation>
     </message>
     <message>
         <location filename="../ui/squawk.ui" line="241"/>
-        <location filename="../build/squawk_autogen/include/ui_squawk.h" line="223"/>
         <source>Add contact</source>
         <translation>Add contact</translation>
     </message>
     <message>
         <location filename="../ui/squawk.ui" line="253"/>
-        <location filename="../build/squawk_autogen/include/ui_squawk.h" line="224"/>
         <source>Add conference</source>
         <translation>Join conference</translation>
     </message>
     <message>
         <location filename="../ui/squawk.ui" line="262"/>
-        <location filename="../build/squawk_autogen/include/ui_squawk.h" line="225"/>
         <source>Preferences</source>
         <translation>Preferences</translation>
     </message>
     <message>
         <location filename="../ui/squawk.ui" line="267"/>
-        <location filename="../build/squawk_autogen/include/ui_squawk.h" line="226"/>
         <source>About Squawk</source>
         <translation>About Squawk</translation>
     </message>
     <message>
-        <location filename="../ui/squawk.cpp" line="284"/>
+        <location filename="../ui/squawk.cpp" line="288"/>
         <source>Deactivate</source>
         <translation>Deactivate</translation>
     </message>
     <message>
-        <location filename="../ui/squawk.cpp" line="287"/>
+        <location filename="../ui/squawk.cpp" line="291"/>
         <source>Activate</source>
         <translation>Activate</translation>
     </message>
     <message>
-        <location filename="../ui/squawk.cpp" line="291"/>
-        <location filename="../ui/squawk.cpp" line="378"/>
+        <location filename="../ui/squawk.cpp" line="295"/>
+        <location filename="../ui/squawk.cpp" line="382"/>
         <source>VCard</source>
         <translation>VCard</translation>
     </message>
     <message>
-        <location filename="../ui/squawk.cpp" line="295"/>
-        <location filename="../ui/squawk.cpp" line="382"/>
-        <location filename="../ui/squawk.cpp" line="410"/>
+        <location filename="../ui/squawk.cpp" line="299"/>
+        <location filename="../ui/squawk.cpp" line="386"/>
+        <location filename="../ui/squawk.cpp" line="414"/>
         <source>Remove</source>
         <translation>Remove</translation>
     </message>
     <message>
-        <location filename="../ui/squawk.cpp" line="305"/>
+        <location filename="../ui/squawk.cpp" line="309"/>
         <source>Open dialog</source>
         <translation>Open dialog</translation>
     </message>
     <message>
-        <location filename="../ui/squawk.cpp" line="313"/>
-        <location filename="../ui/squawk.cpp" line="401"/>
+        <location filename="../ui/squawk.cpp" line="317"/>
+        <location filename="../ui/squawk.cpp" line="405"/>
         <source>Unsubscribe</source>
         <translation>Unsubscribe</translation>
     </message>
     <message>
-        <location filename="../ui/squawk.cpp" line="321"/>
-        <location filename="../ui/squawk.cpp" line="405"/>
+        <location filename="../ui/squawk.cpp" line="325"/>
+        <location filename="../ui/squawk.cpp" line="409"/>
         <source>Subscribe</source>
         <translation>Subscribe</translation>
     </message>
     <message>
-        <location filename="../ui/squawk.cpp" line="327"/>
+        <location filename="../ui/squawk.cpp" line="331"/>
         <source>Rename</source>
         <translation>Rename</translation>
     </message>
     <message>
-        <location filename="../ui/squawk.cpp" line="340"/>
+        <location filename="../ui/squawk.cpp" line="344"/>
         <source>Input new name for %1
 or leave it empty for the contact 
 to be displayed as %1</source>
@@ -1155,47 +1096,47 @@ or leave it empty for the contact
 to be displayed as %1</translation>
     </message>
     <message>
-        <location filename="../ui/squawk.cpp" line="341"/>
+        <location filename="../ui/squawk.cpp" line="345"/>
         <source>Renaming %1</source>
         <translation>Renaming %1</translation>
     </message>
     <message>
-        <location filename="../ui/squawk.cpp" line="347"/>
+        <location filename="../ui/squawk.cpp" line="351"/>
         <source>Groups</source>
         <translation>Groups</translation>
     </message>
     <message>
-        <location filename="../ui/squawk.cpp" line="362"/>
+        <location filename="../ui/squawk.cpp" line="366"/>
         <source>New group</source>
         <translation>New group</translation>
     </message>
     <message>
-        <location filename="../ui/squawk.cpp" line="372"/>
+        <location filename="../ui/squawk.cpp" line="376"/>
         <source>New group name</source>
         <translation>New group name</translation>
     </message>
     <message>
-        <location filename="../ui/squawk.cpp" line="373"/>
+        <location filename="../ui/squawk.cpp" line="377"/>
         <source>Add %1 to a new group</source>
         <translation>Add %1 to a new group</translation>
     </message>
     <message>
-        <location filename="../ui/squawk.cpp" line="392"/>
+        <location filename="../ui/squawk.cpp" line="396"/>
         <source>Open conversation</source>
         <translation>Open conversation</translation>
     </message>
     <message>
-        <location filename="../ui/squawk.cpp" line="454"/>
+        <location filename="../ui/squawk.cpp" line="458"/>
         <source>%1 account card</source>
         <translation>%1 account card</translation>
     </message>
     <message>
-        <location filename="../ui/squawk.cpp" line="456"/>
+        <location filename="../ui/squawk.cpp" line="460"/>
         <source>%1 contact card</source>
         <translation>%1 contact card</translation>
     </message>
     <message>
-        <location filename="../ui/squawk.cpp" line="468"/>
+        <location filename="../ui/squawk.cpp" line="472"/>
         <source>Downloading vCard</source>
         <translation>Downloading vCard</translation>
     </message>
@@ -1204,145 +1145,119 @@ to be displayed as %1</translation>
     <name>VCard</name>
     <message>
         <location filename="../ui/widgets/vcard/vcard.ui" line="65"/>
-        <location filename="../build/squawk_autogen/include/ui_vcard.h" line="612"/>
         <source>Received 12.07.2007 at 17.35</source>
         <translation>Never updated</translation>
     </message>
     <message>
         <location filename="../ui/widgets/vcard/vcard.ui" line="100"/>
         <location filename="../ui/widgets/vcard/vcard.ui" line="425"/>
-        <location filename="../build/squawk_autogen/include/ui_vcard.h" line="624"/>
-        <location filename="../build/squawk_autogen/include/ui_vcard.h" line="627"/>
         <source>General</source>
         <translation>General</translation>
     </message>
     <message>
         <location filename="../ui/widgets/vcard/vcard.ui" line="130"/>
-        <location filename="../build/squawk_autogen/include/ui_vcard.h" line="613"/>
         <source>Organization</source>
         <translation>Organization</translation>
     </message>
     <message>
         <location filename="../ui/widgets/vcard/vcard.ui" line="180"/>
-        <location filename="../build/squawk_autogen/include/ui_vcard.h" line="614"/>
         <source>Middle name</source>
         <translation>Middle name</translation>
     </message>
     <message>
         <location filename="../ui/widgets/vcard/vcard.ui" line="190"/>
-        <location filename="../build/squawk_autogen/include/ui_vcard.h" line="615"/>
         <source>First name</source>
         <translation>First name</translation>
     </message>
     <message>
         <location filename="../ui/widgets/vcard/vcard.ui" line="200"/>
-        <location filename="../build/squawk_autogen/include/ui_vcard.h" line="616"/>
         <source>Last name</source>
         <translation>Last name</translation>
     </message>
     <message>
         <location filename="../ui/widgets/vcard/vcard.ui" line="226"/>
-        <location filename="../build/squawk_autogen/include/ui_vcard.h" line="617"/>
         <source>Nick name</source>
         <translation>Nick name</translation>
     </message>
     <message>
         <location filename="../ui/widgets/vcard/vcard.ui" line="252"/>
-        <location filename="../build/squawk_autogen/include/ui_vcard.h" line="618"/>
         <source>Birthday</source>
         <translation>Birthday</translation>
     </message>
     <message>
         <location filename="../ui/widgets/vcard/vcard.ui" line="272"/>
-        <location filename="../build/squawk_autogen/include/ui_vcard.h" line="619"/>
         <source>Organization name</source>
         <translation>Organization name</translation>
     </message>
     <message>
         <location filename="../ui/widgets/vcard/vcard.ui" line="298"/>
-        <location filename="../build/squawk_autogen/include/ui_vcard.h" line="620"/>
         <source>Unit / Department</source>
         <translation>Unit / Department</translation>
     </message>
     <message>
         <location filename="../ui/widgets/vcard/vcard.ui" line="324"/>
-        <location filename="../build/squawk_autogen/include/ui_vcard.h" line="621"/>
         <source>Role / Profession</source>
         <translation>Role / Profession</translation>
     </message>
     <message>
         <location filename="../ui/widgets/vcard/vcard.ui" line="350"/>
-        <location filename="../build/squawk_autogen/include/ui_vcard.h" line="622"/>
         <source>Job title</source>
         <translation>Job title</translation>
     </message>
     <message>
         <location filename="../ui/widgets/vcard/vcard.ui" line="390"/>
-        <location filename="../build/squawk_autogen/include/ui_vcard.h" line="623"/>
         <source>Full name</source>
         <translation>Full name</translation>
     </message>
     <message>
         <location filename="../ui/widgets/vcard/vcard.ui" line="460"/>
-        <location filename="../build/squawk_autogen/include/ui_vcard.h" line="625"/>
         <source>Personal information</source>
         <translation>Personal information</translation>
     </message>
     <message>
         <location filename="../ui/widgets/vcard/vcard.ui" line="522"/>
         <location filename="../ui/widgets/vcard/vcard.ui" line="546"/>
-        <location filename="../build/squawk_autogen/include/ui_vcard.h" line="628"/>
-        <location filename="../build/squawk_autogen/include/ui_vcard.h" line="634"/>
         <source>Contact</source>
         <translation>Contact</translation>
     </message>
     <message>
         <location filename="../ui/widgets/vcard/vcard.ui" line="653"/>
-        <location filename="../build/squawk_autogen/include/ui_vcard.h" line="629"/>
         <source>Addresses</source>
         <translation>Addresses</translation>
     </message>
     <message>
         <location filename="../ui/widgets/vcard/vcard.ui" line="682"/>
-        <location filename="../build/squawk_autogen/include/ui_vcard.h" line="630"/>
         <source>E-Mail addresses</source>
         <translation>E-Mail addresses</translation>
     </message>
     <message>
         <location filename="../ui/widgets/vcard/vcard.ui" line="713"/>
-        <location filename="../build/squawk_autogen/include/ui_vcard.h" line="631"/>
         <source>Jabber ID</source>
         <translation>Jabber ID</translation>
     </message>
     <message>
         <location filename="../ui/widgets/vcard/vcard.ui" line="739"/>
-        <location filename="../build/squawk_autogen/include/ui_vcard.h" line="632"/>
         <source>Web site</source>
         <translation>Web site</translation>
     </message>
     <message>
         <location filename="../ui/widgets/vcard/vcard.ui" line="767"/>
-        <location filename="../build/squawk_autogen/include/ui_vcard.h" line="633"/>
         <source>Phone numbers</source>
         <translation>Phone numbers</translation>
     </message>
     <message>
         <location filename="../ui/widgets/vcard/vcard.ui" line="798"/>
         <location filename="../ui/widgets/vcard/vcard.ui" line="822"/>
-        <location filename="../build/squawk_autogen/include/ui_vcard.h" line="635"/>
-        <location filename="../build/squawk_autogen/include/ui_vcard.h" line="636"/>
         <source>Description</source>
         <translation>Description</translation>
     </message>
     <message>
         <location filename="../ui/widgets/vcard/vcard.ui" line="859"/>
-        <location filename="../build/squawk_autogen/include/ui_vcard.h" line="610"/>
         <source>Set avatar</source>
         <translation>Set avatar</translation>
     </message>
     <message>
         <location filename="../ui/widgets/vcard/vcard.ui" line="868"/>
-        <location filename="../build/squawk_autogen/include/ui_vcard.h" line="611"/>
         <source>Clear avatar</source>
         <translation>Clear avatar</translation>
     </message>
diff --git a/translations/squawk.pt_BR.ts b/translations/squawk.pt_BR.ts
index f818e63..5d76ade 100644
--- a/translations/squawk.pt_BR.ts
+++ b/translations/squawk.pt_BR.ts
@@ -4,101 +4,130 @@
 <context>
     <name>About</name>
     <message>
+        <location filename="../ui/widgets/about.ui" line="14"/>
         <source>About Squawk</source>
         <translation>Sorbe Squawk</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/about.ui" line="44"/>
         <source>Squawk</source>
         <translation>Squawk</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/about.ui" line="96"/>
         <source>About</source>
         <translatorcomment>Tab title</translatorcomment>
         <translation>Sobre</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/about.ui" line="115"/>
         <source>XMPP (jabber) messenger</source>
         <translation>XMPP (jabber) mensageiro</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/about.ui" line="122"/>
         <source>(c) 2019 - 2022, Yury Gubich</source>
         <translation>(c) 2019 - 2022, Yury Gubich</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/about.ui" line="129"/>
         <source>&lt;a href=&quot;https://git.macaw.me/blue/squawk&quot;&gt;Project site&lt;/a&gt;</source>
         <translation>&lt;a href=&quot;https://git.macaw.me/blue/squawk&quot;&gt;Site do projeto&lt;/a&gt;</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/about.ui" line="142"/>
         <source>&lt;a href=&quot;https://git.macaw.me/blue/squawk/src/branch/master/LICENSE.md&quot;&gt;License: GNU General Public License version 3&lt;/a&gt;</source>
         <translation>&lt;a href=&quot;https://git.macaw.me/blue/squawk/src/branch/master/LICENSE.md&quot;&gt;Licença: GNU General Public License versão 3&lt;/a&gt;</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/about.ui" line="175"/>
         <source>Components</source>
         <translation>Componentes</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/about.ui" line="216"/>
+        <location filename="../ui/widgets/about.ui" line="312"/>
+        <location filename="../ui/widgets/about.ui" line="660"/>
         <source>Version</source>
         <translation>Versão</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/about.ui" line="228"/>
+        <location filename="../ui/widgets/about.ui" line="324"/>
+        <location filename="../ui/widgets/about.ui" line="670"/>
         <source>0.0.0</source>
         <translation>0.0.0</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/about.ui" line="397"/>
         <source>Report Bugs</source>
         <translation>Relatório de erros</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/about.ui" line="416"/>
         <source>Please report any bug you find!
 To report bugs you can use:</source>
         <translation>Por favor reportar qualquer erro que você encontrar!
 Para relatar bugs você pode usar:</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/about.ui" line="424"/>
         <source>&lt;a href=&quot;https://git.macaw.me/blue/squawk/issues&quot;&gt;Project bug tracker&lt;/&gt;</source>
         <translation>&lt;a href=&quot;https://git.macaw.me/blue/squawk/issues&quot;&gt;Rastreador de bugs do projeto&lt;/&gt;</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/about.ui" line="434"/>
         <source>XMPP (&lt;a href=&quot;xmpp:blue@macaw.me&quot;&gt;blue@macaw.me&lt;/a&gt;)</source>
         <translation>XMPP (&lt;a href=&quot;xmpp:blue@macaw.me&quot;&gt;blue@macaw.me&lt;/a&gt;)</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/about.ui" line="444"/>
         <source>E-Mail (&lt;a href=&quot;mailto:blue@macaw.me&quot;&gt;blue@macaw.me&lt;/a&gt;)</source>
         <translation>E-Mail (&lt;a href=&quot;mailto:blue@macaw.me&quot;&gt;blue@macaw.me&lt;/a&gt;)</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/about.ui" line="477"/>
         <source>Thanks To</source>
         <translation>Graças ao</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/about.ui" line="519"/>
         <source>Vae</source>
         <translation>Vae</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/about.ui" line="531"/>
         <source>Major refactoring, bug fixes, constructive criticism</source>
         <translation>Refatoração importante, correção de erros, críticas construtivas</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/about.ui" line="568"/>
         <source>Shunf4</source>
         <translation>Shunf4</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/about.ui" line="580"/>
         <source>Major refactoring, bug fixes, build adaptations for Windows and MacOS</source>
         <translation>Refatoração importante, correção de erros, adaptações de construção para Windows e MacOS</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/about.ui" line="617"/>
         <source>Bruno F. Fontes</source>
         <translation>Bruno F. Fontes</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/about.ui" line="629"/>
         <source>Brazilian Portuguese translation</source>
         <translation>Tradução para o português do Brasil</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/about.cpp" line="35"/>
+        <location filename="../ui/widgets/about.cpp" line="38"/>
         <source>(built against %1)</source>
         <translation>(Versão durante a compilação %1)</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/about.cpp" line="71"/>
         <source>License</source>
         <translation>Licença</translation>
     </message>
@@ -106,83 +135,101 @@ Para relatar bugs você pode usar:</translation>
 <context>
     <name>Account</name>
     <message>
+        <location filename="../ui/widgets/accounts/account.ui" line="14"/>
         <source>Account</source>
         <translatorcomment>Window header</translatorcomment>
         <translation>Conta</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/accounts/account.ui" line="40"/>
         <source>Your account login</source>
         <translatorcomment>Tooltip</translatorcomment>
         <translation>Suas informações de login</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/accounts/account.ui" line="43"/>
         <source>john_smith1987</source>
         <translatorcomment>Login placeholder</translatorcomment>
         <translation>josé_silva1987</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/accounts/account.ui" line="50"/>
         <source>Server</source>
         <translation>Servidor</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/accounts/account.ui" line="57"/>
         <source>A server address of your account. Like 404.city or macaw.me</source>
         <translatorcomment>Tooltip</translatorcomment>
         <translation>O endereço do servidor da sua conta, como o 404.city ou o macaw.me</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/accounts/account.ui" line="60"/>
         <source>macaw.me</source>
         <translatorcomment>Placeholder</translatorcomment>
         <translation>macaw.me</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/accounts/account.ui" line="67"/>
         <source>Login</source>
         <translation>Usuário</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/accounts/account.ui" line="74"/>
         <source>Password</source>
         <translation>Senha</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/accounts/account.ui" line="81"/>
         <source>Password of your account</source>
         <translatorcomment>Tooltip</translatorcomment>
         <translation>Senha da sua conta</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/accounts/account.ui" line="103"/>
         <source>Name</source>
         <translation>Nome</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/accounts/account.ui" line="110"/>
         <source>Just a name how would you call this account, doesn&apos;t affect anything</source>
         <translation>Apenas um nome para identificar esta conta. Não influencia em nada (não pode ser mudado)</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/accounts/account.ui" line="113"/>
         <source>John</source>
         <translatorcomment>Placeholder</translatorcomment>
         <translation>José</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/accounts/account.ui" line="120"/>
         <source>Resource</source>
         <translation>Recurso</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/accounts/account.ui" line="127"/>
         <source>A resource name like &quot;Home&quot; or &quot;Work&quot;</source>
         <translatorcomment>Tooltip</translatorcomment>
         <translation>Um nome de recurso como  &quot;Casa&quot; ou &quot;Trabalho&quot;</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/accounts/account.ui" line="130"/>
         <source>QXmpp</source>
         <translatorcomment>Default resource</translatorcomment>
         <translation>QXmpp</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/accounts/account.ui" line="137"/>
         <source>Password storage</source>
         <translation>Armazenamento de senha</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/accounts/account.ui" line="163"/>
         <source>Active</source>
         <translation>Ativo</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/accounts/account.ui" line="170"/>
         <source>enable</source>
         <translation>habilitar</translation>
     </message>
@@ -190,26 +237,32 @@ Para relatar bugs você pode usar:</translation>
 <context>
     <name>Accounts</name>
     <message>
+        <location filename="../ui/widgets/accounts/accounts.ui" line="14"/>
         <source>Accounts</source>
         <translation>Contas</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/accounts/accounts.ui" line="45"/>
         <source>Delete</source>
         <translation>Apagar</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/accounts/accounts.ui" line="86"/>
         <source>Add</source>
         <translation>Adicionar</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/accounts/accounts.ui" line="96"/>
         <source>Edit</source>
         <translation>Editar</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/accounts/accounts.ui" line="106"/>
         <source>Change password</source>
         <translation>Alterar senha</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/accounts/accounts.ui" line="129"/>
         <source>Connect</source>
         <translation>Conectar</translation>
     </message>
@@ -218,10 +271,13 @@ Para relatar bugs você pode usar:</translation>
         <translation type="vanished">Desconectar</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/accounts/accounts.cpp" line="126"/>
         <source>Deactivate</source>
         <translation>Desativar</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/accounts/accounts.cpp" line="129"/>
+        <location filename="../ui/widgets/accounts/accounts.cpp" line="132"/>
         <source>Activate</source>
         <translation>Ativar</translation>
     </message>
@@ -229,18 +285,38 @@ Para relatar bugs você pode usar:</translation>
 <context>
     <name>Application</name>
     <message>
+        <location filename="../main/application.cpp" line="32"/>
+        <source>Quit</source>
+        <translation>Sair</translation>
+    </message>
+    <message>
+        <location filename="../main/application.cpp" line="33"/>
+        <location filename="../main/application.cpp" line="305"/>
+        <source>Minimize to tray</source>
+        <translation>Minimizar para bandeja</translation>
+    </message>
+    <message>
+        <location filename="../main/application.cpp" line="232"/>
+        <source>Show Squawk</source>
+        <translation>Aparecer Squawk</translation>
+    </message>
+    <message>
+        <location filename="../main/application.cpp" line="353"/>
         <source> from </source>
         <translation> de </translation>
     </message>
     <message>
+        <location filename="../main/application.cpp" line="361"/>
         <source>Attached file</source>
         <translation>Arquivo anexado</translation>
     </message>
     <message>
+        <location filename="../main/application.cpp" line="366"/>
         <source>Mark as Read</source>
         <translation>Marcar como lido</translation>
     </message>
     <message>
+        <location filename="../main/application.cpp" line="367"/>
         <source>Open conversation</source>
         <translation>Abrir conversa</translation>
     </message>
@@ -248,19 +324,23 @@ Para relatar bugs você pode usar:</translation>
 <context>
     <name>Conversation</name>
     <message>
+        <location filename="../ui/widgets/conversation.ui" line="436"/>
         <source>Type your message here...</source>
         <translatorcomment>Placeholder</translatorcomment>
         <translation>Digite sua mensagem aqui...</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/conversation.cpp" line="277"/>
         <source>Chose a file to send</source>
         <translation>Escolha um arquivo para enviar</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/conversation.cpp" line="141"/>
         <source>Drop files here to attach them to your message</source>
         <translation>Arraste seus arquivos aqui para anexá-los a sua mensagem</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/conversation.ui" line="426"/>
         <source>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
 &lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
 p, li { white-space: pre-wrap; }
@@ -273,34 +353,42 @@ p, li { white-space: pre-wrap; }
 &lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/conversation.cpp" line="58"/>
         <source>Paste Image</source>
         <translation>Colar imagem</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/conversation.cpp" line="495"/>
         <source>Try sending again</source>
         <translation>Tente enviar de novo</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/conversation.cpp" line="505"/>
         <source>Copy selected</source>
         <translation>Copiar selecionado</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/conversation.cpp" line="515"/>
         <source>Copy message</source>
         <translation>Copiar mensagem</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/conversation.cpp" line="525"/>
         <source>Open</source>
         <translation>Abrir</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/conversation.cpp" line="530"/>
         <source>Show in folder</source>
         <translation>Show in explorer</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/conversation.cpp" line="540"/>
         <source>Edit</source>
         <translation>Editar</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/conversation.cpp" line="570"/>
         <source>Editing message...</source>
         <translation>Messae está sendo editado...</translation>
     </message>
@@ -308,29 +396,35 @@ p, li { white-space: pre-wrap; }
 <context>
     <name>CredentialsPrompt</name>
     <message>
+        <location filename="../ui/widgets/accounts/credentialsprompt.ui" line="14"/>
         <source>Authentication error: %1</source>
         <translatorcomment>Window title</translatorcomment>
         <translation>Erro de autenticação: %1</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/accounts/credentialsprompt.ui" line="29"/>
         <source>Couldn&apos;t authenticate account %1: login or password is icorrect.
 Would you like to check them and try again?</source>
         <translation>Não foi possível autenticar a conta %1: login ou senha incorretos.
 Deseja verificá-los e tentar novamente?</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/accounts/credentialsprompt.ui" line="45"/>
         <source>Login</source>
         <translation>Usuário</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/accounts/credentialsprompt.ui" line="52"/>
         <source>Your account login (without @server.domain)</source>
         <translation>Suas informações de login (sem @server.domain)</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/accounts/credentialsprompt.ui" line="59"/>
         <source>Password</source>
         <translation>Senha</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/accounts/credentialsprompt.ui" line="66"/>
         <source>Your password</source>
         <translation>Senha da sua conta</translation>
     </message>
@@ -338,10 +432,12 @@ Deseja verificá-los e tentar novamente?</translation>
 <context>
     <name>DialogQueue</name>
     <message>
+        <location filename="../main/dialogqueue.cpp" line="99"/>
         <source>Input the password for account %1</source>
         <translation>Digite a senha para a conta %1</translation>
     </message>
     <message>
+        <location filename="../main/dialogqueue.cpp" line="100"/>
         <source>Password for account %1</source>
         <translation>Senha para a conta %1</translation>
     </message>
@@ -349,196 +445,235 @@ Deseja verificá-los e tentar novamente?</translation>
 <context>
     <name>Global</name>
     <message>
+        <location filename="../shared/global.cpp" line="42"/>
         <source>Online</source>
         <comment>Availability</comment>
         <translation>Conectado</translation>
     </message>
     <message>
+        <location filename="../shared/global.cpp" line="43"/>
         <source>Away</source>
         <comment>Availability</comment>
         <translation>Distante</translation>
     </message>
     <message>
+        <location filename="../shared/global.cpp" line="44"/>
         <source>Absent</source>
         <comment>Availability</comment>
         <translation>Ausente</translation>
     </message>
     <message>
+        <location filename="../shared/global.cpp" line="45"/>
         <source>Busy</source>
         <comment>Availability</comment>
         <translation>Ocupado</translation>
     </message>
     <message>
+        <location filename="../shared/global.cpp" line="46"/>
         <source>Chatty</source>
         <comment>Availability</comment>
         <translation>Tagarela</translation>
     </message>
     <message>
+        <location filename="../shared/global.cpp" line="47"/>
         <source>Invisible</source>
         <comment>Availability</comment>
         <translation>Invisível</translation>
     </message>
     <message>
+        <location filename="../shared/global.cpp" line="48"/>
         <source>Offline</source>
         <comment>Availability</comment>
         <translation>Desconectado</translation>
     </message>
     <message>
+        <location filename="../shared/global.cpp" line="51"/>
         <source>Disconnected</source>
         <comment>ConnectionState</comment>
         <translation>Desconectado</translation>
     </message>
     <message>
+        <location filename="../shared/global.cpp" line="52"/>
         <source>Connecting</source>
         <comment>ConnectionState</comment>
         <translation>Connectando</translation>
     </message>
     <message>
+        <location filename="../shared/global.cpp" line="53"/>
         <source>Connected</source>
         <comment>ConnectionState</comment>
         <translation>Conectado</translation>
     </message>
     <message>
+        <location filename="../shared/global.cpp" line="54"/>
         <source>Error</source>
         <comment>ConnectionState</comment>
         <translation>Erro</translation>
     </message>
     <message>
+        <location filename="../shared/global.cpp" line="57"/>
         <source>None</source>
         <comment>SubscriptionState</comment>
         <translation>Nenhum</translation>
     </message>
     <message>
+        <location filename="../shared/global.cpp" line="58"/>
         <source>From</source>
         <comment>SubscriptionState</comment>
         <translation>De</translation>
     </message>
     <message>
+        <location filename="../shared/global.cpp" line="59"/>
         <source>To</source>
         <comment>SubscriptionState</comment>
         <translation>Para</translation>
     </message>
     <message>
+        <location filename="../shared/global.cpp" line="60"/>
         <source>Both</source>
         <comment>SubscriptionState</comment>
         <translation>Ambos</translation>
     </message>
     <message>
+        <location filename="../shared/global.cpp" line="61"/>
         <source>Unknown</source>
         <comment>SubscriptionState</comment>
         <translation>Desconhecido</translation>
     </message>
     <message>
+        <location filename="../shared/global.cpp" line="64"/>
         <source>Unspecified</source>
         <comment>Affiliation</comment>
         <translation>Não especificado</translation>
     </message>
     <message>
+        <location filename="../shared/global.cpp" line="65"/>
         <source>Outcast</source>
         <comment>Affiliation</comment>
         <translation>Rejeitado</translation>
     </message>
     <message>
+        <location filename="../shared/global.cpp" line="66"/>
         <source>Nobody</source>
         <comment>Affiliation</comment>
         <translation>Ninguém</translation>
     </message>
     <message>
+        <location filename="../shared/global.cpp" line="67"/>
         <source>Member</source>
         <comment>Affiliation</comment>
         <translation>Membro</translation>
     </message>
     <message>
+        <location filename="../shared/global.cpp" line="68"/>
         <source>Admin</source>
         <comment>Affiliation</comment>
         <translation>Administrador</translation>
     </message>
     <message>
+        <location filename="../shared/global.cpp" line="69"/>
         <source>Owner</source>
         <comment>Affiliation</comment>
         <translation>Dono</translation>
     </message>
     <message>
+        <location filename="../shared/global.cpp" line="72"/>
         <source>Unspecified</source>
         <comment>Role</comment>
         <translation>Não especificado</translation>
     </message>
     <message>
+        <location filename="../shared/global.cpp" line="73"/>
         <source>Nobody</source>
         <comment>Role</comment>
         <translation>Ninguém</translation>
     </message>
     <message>
+        <location filename="../shared/global.cpp" line="74"/>
         <source>Visitor</source>
         <comment>Role</comment>
         <translation>Visitante</translation>
     </message>
     <message>
+        <location filename="../shared/global.cpp" line="75"/>
         <source>Participant</source>
         <comment>Role</comment>
         <translation>Participante</translation>
     </message>
     <message>
+        <location filename="../shared/global.cpp" line="76"/>
         <source>Moderator</source>
         <comment>Role</comment>
         <translation>Moderador</translation>
     </message>
     <message>
+        <location filename="../shared/global.cpp" line="79"/>
         <source>Pending</source>
         <comment>MessageState</comment>
         <translation>Aguardando</translation>
     </message>
     <message>
+        <location filename="../shared/global.cpp" line="80"/>
         <source>Sent</source>
         <comment>MessageState</comment>
         <translation>Enviada</translation>
     </message>
     <message>
+        <location filename="../shared/global.cpp" line="81"/>
         <source>Delivered</source>
         <comment>MessageState</comment>
         <translation>Entregue</translation>
     </message>
     <message>
+        <location filename="../shared/global.cpp" line="82"/>
         <source>Error</source>
         <comment>MessageState</comment>
         <translation>Erro</translation>
     </message>
     <message>
+        <location filename="../shared/global.cpp" line="85"/>
         <source>Plain</source>
         <comment>AccountPassword</comment>
         <translation>Texto simples</translation>
     </message>
     <message>
+        <location filename="../shared/global.cpp" line="86"/>
         <source>Jammed</source>
         <comment>AccountPassword</comment>
         <translation>Embaralhado</translation>
     </message>
     <message>
+        <location filename="../shared/global.cpp" line="87"/>
         <source>Always Ask</source>
         <comment>AccountPassword</comment>
         <translation>Sempre perguntar</translation>
     </message>
     <message>
+        <location filename="../shared/global.cpp" line="88"/>
         <source>KWallet</source>
         <comment>AccountPassword</comment>
         <translation>KWallet</translation>
     </message>
     <message>
+        <location filename="../shared/global.cpp" line="92"/>
         <source>Your password is going to be stored in config file but jammed with constant encryption key you can find in program source code. It might look like encryption but it&apos;s not</source>
         <comment>AccountPasswordDescription</comment>
         <translation>Sua senha será armazenada em um arquivo de configurações, porém embaralhada com uma chave criptográfica constante que você pode encontrar no código fonte do programa. Parece criptografado, mas não é</translation>
     </message>
     <message>
+        <location filename="../shared/global.cpp" line="93"/>
         <source>Squawk is going to query you for the password on every start of the program</source>
         <comment>AccountPasswordDescription</comment>
         <translation>O Squark vai requisitar sua senha a cada vez que você abrir o programa</translation>
     </message>
     <message>
+        <location filename="../shared/global.cpp" line="91"/>
         <source>Your password is going to be stored in config file in plain text</source>
         <comment>AccountPasswordDescription</comment>
         <translation>Sua senha será armazenada em um arquivo de configurações em texto simples</translation>
     </message>
     <message>
+        <location filename="../shared/global.cpp" line="94"/>
         <source>Your password is going to be stored in KDE wallet storage (KWallet). You&apos;re going to be queried for permissions</source>
         <comment>AccountPasswordDescription</comment>
         <translation>Sua senha será armazenada no KDE wallet (KWallet). Sua autorização será requerida</translation>
@@ -547,43 +682,53 @@ Deseja verificá-los e tentar novamente?</translation>
 <context>
     <name>JoinConference</name>
     <message>
+        <location filename="../ui/widgets/joinconference.ui" line="14"/>
         <source>Join new conference</source>
         <translatorcomment>Заголовок окна</translatorcomment>
         <translation>Entrar em uma nova conferência</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/joinconference.ui" line="22"/>
         <source>JID</source>
         <translation>JID</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/joinconference.ui" line="29"/>
         <source>Room JID</source>
         <translation>Sala JID</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/joinconference.ui" line="32"/>
         <source>identifier@conference.server.org</source>
         <translation>identifier@conference.server.org</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/joinconference.ui" line="39"/>
         <source>Account</source>
         <translation>Conta</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/joinconference.ui" line="49"/>
         <source>Join on login</source>
         <translation>Entrar ao se conectar</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/joinconference.ui" line="56"/>
         <source>If checked Squawk will try to join this conference on login</source>
         <translation>Se marcado, o Squawk tentará entrar nesta conferência automaticamente durante o login</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/joinconference.ui" line="66"/>
         <source>Nick name</source>
         <translation>Apelido</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/joinconference.ui" line="73"/>
         <source>Your nick name for that conference. If you leave this field empty your account name will be used as a nick name</source>
         <translation>Seu apelido para essa conferência. Se você deixar este campo em branco, seu nome de usuário será usado como apelido</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/joinconference.ui" line="76"/>
         <source>John</source>
         <translation>José</translation>
     </message>
@@ -602,6 +747,8 @@ Deseja verificá-los e tentar novamente?</translation>
         <translation type="vanished">Baixando...</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/messageline/messagedelegate.cpp" line="65"/>
+        <location filename="../ui/widgets/messageline/messagedelegate.cpp" line="668"/>
         <source>Download</source>
         <translation>Baixar</translation>
     </message>
@@ -635,18 +782,22 @@ Você pode tentar novamente</translation>
 <context>
     <name>Models::Room</name>
     <message>
+        <location filename="../ui/models/room.cpp" line="191"/>
         <source>Subscribed</source>
         <translation>Inscrito</translation>
     </message>
     <message>
+        <location filename="../ui/models/room.cpp" line="193"/>
         <source>Temporarily unsubscribed</source>
         <translation>Inscrição temporariamente cancelada</translation>
     </message>
     <message>
+        <location filename="../ui/models/room.cpp" line="197"/>
         <source>Temporarily subscribed</source>
         <translation>Temporariamente inscrito</translation>
     </message>
     <message>
+        <location filename="../ui/models/room.cpp" line="199"/>
         <source>Unsubscribed</source>
         <translation>Não inscrito</translation>
     </message>
@@ -654,47 +805,67 @@ Você pode tentar novamente</translation>
 <context>
     <name>Models::Roster</name>
     <message>
+        <location filename="../ui/models/roster.cpp" line="85"/>
         <source>New messages</source>
         <translation>Novas mensagens</translation>
     </message>
     <message>
+        <location filename="../ui/models/roster.cpp" line="198"/>
+        <location filename="../ui/models/roster.cpp" line="253"/>
+        <location filename="../ui/models/roster.cpp" line="265"/>
         <source>New messages: </source>
         <translation>Novas mensagens: </translation>
     </message>
     <message>
+        <location filename="../ui/models/roster.cpp" line="200"/>
+        <location filename="../ui/models/roster.cpp" line="268"/>
         <source>Jabber ID: </source>
         <translation>ID Jabber: </translation>
     </message>
     <message>
+        <location filename="../ui/models/roster.cpp" line="204"/>
+        <location filename="../ui/models/roster.cpp" line="223"/>
+        <location filename="../ui/models/roster.cpp" line="236"/>
         <source>Availability: </source>
         <translation>Disponibilidade: </translation>
     </message>
     <message>
+        <location filename="../ui/models/roster.cpp" line="208"/>
+        <location filename="../ui/models/roster.cpp" line="226"/>
+        <location filename="../ui/models/roster.cpp" line="239"/>
         <source>Status: </source>
         <translation>Status: </translation>
     </message>
     <message>
+        <location filename="../ui/models/roster.cpp" line="211"/>
+        <location filename="../ui/models/roster.cpp" line="213"/>
+        <location filename="../ui/models/roster.cpp" line="269"/>
         <source>Subscription: </source>
         <translation>Inscrição: </translation>
     </message>
     <message>
+        <location filename="../ui/models/roster.cpp" line="242"/>
         <source>Affiliation: </source>
         <translatorcomment>Я правда не знаю, как это объяснить, не то что перевести</translatorcomment>
         <translation>Afiliação: </translation>
     </message>
     <message>
+        <location filename="../ui/models/roster.cpp" line="243"/>
         <source>Role: </source>
         <translation>Papel: </translation>
     </message>
     <message>
+        <location filename="../ui/models/roster.cpp" line="255"/>
         <source>Online contacts: </source>
         <translation>Contatos online: </translation>
     </message>
     <message>
+        <location filename="../ui/models/roster.cpp" line="256"/>
         <source>Total contacts: </source>
         <translation>Contatos totais: </translation>
     </message>
     <message>
+        <location filename="../ui/models/roster.cpp" line="271"/>
         <source>Members: </source>
         <translation>Membros: </translation>
     </message>
@@ -702,40 +873,49 @@ Você pode tentar novamente</translation>
 <context>
     <name>NewContact</name>
     <message>
+        <location filename="../ui/widgets/newcontact.ui" line="14"/>
         <source>Add new contact</source>
         <translatorcomment>Window title</translatorcomment>
         <translation>Adicionar novo contato</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/newcontact.ui" line="22"/>
         <source>Account</source>
         <translation>Conta</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/newcontact.ui" line="29"/>
         <source>An account that is going to have new contact</source>
         <translation>A conta que terá um novo contato</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/newcontact.ui" line="36"/>
         <source>JID</source>
         <translation>JID</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/newcontact.ui" line="43"/>
         <source>Jabber id of your new contact</source>
         <translation>ID Jabber do seu novo contato</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/newcontact.ui" line="46"/>
         <source>name@server.dmn</source>
         <translatorcomment>Placeholder</translatorcomment>
         <translation>nome@servidor.com.br</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/newcontact.ui" line="53"/>
         <source>Name</source>
         <translation>Nome</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/newcontact.ui" line="60"/>
         <source>The way this new contact will be labeled in your roster (optional)</source>
         <translation>A forma com que o novo contato será classificado em sua lista (opcional)</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/newcontact.ui" line="63"/>
         <source>John Smith</source>
         <translation>José Silva</translation>
     </message>
@@ -743,14 +923,20 @@ Você pode tentar novamente</translation>
 <context>
     <name>PageAppearance</name>
     <message>
+        <location filename="../ui/widgets/settings/pageappearance.ui" line="17"/>
         <source>Theme</source>
         <translation>Estilo</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/settings/pageappearance.ui" line="30"/>
         <source>Color scheme</source>
         <translation>Esquema de cores</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/settings/pageappearance.cpp" line="36"/>
+        <location filename="../ui/widgets/settings/pageappearance.cpp" line="49"/>
+        <location filename="../ui/widgets/settings/pageappearance.cpp" line="56"/>
+        <location filename="../ui/widgets/settings/pageappearance.cpp" line="59"/>
         <source>System</source>
         <translation>Do sistema</translation>
     </message>
@@ -758,21 +944,48 @@ Você pode tentar novamente</translation>
 <context>
     <name>PageGeneral</name>
     <message>
+        <location filename="../ui/widgets/settings/pagegeneral.ui" line="17"/>
         <source>Downloads path</source>
         <translation>Pasta de downloads</translation>
     </message>
     <message>
         <location filename="../ui/widgets/settings/pagegeneral.ui" line="45"/>
-        <source>Close to tray icon</source>
-        <translation>Fechar ao ícone da bandeja</translation>
+        <source>Tray icon</source>
+        <translation>Ícone da bandeja</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/settings/pagegeneral.ui" line="52"/>
+        <source>Mimimize Squawk to tray when closing main window</source>
+        <translation>Minimizar Squawk para bandeja ao fechar a janela principal</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/settings/pagegeneral.ui" line="59"/>
+        <source>Hide tray icon</source>
+        <translation>Esconder o ícone da bandeja</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/settings/pagegeneral.ui" line="66"/>
+        <source>Hide tray icon when Squawk main window is visible</source>
+        <translation>Esconder o ícone da bandeja quando a janela principal não está fechada</translation>
+    </message>
+    <message>
+        <source>Close to tray icon</source>
+        <translation type="vanished">Fechar ao ícone da bandeja</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/settings/pagegeneral.ui" line="36"/>
         <source>Browse</source>
         <translation>6 / 5,000
 Translation results
 Navegar</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/settings/pagegeneral.cpp" line="45"/>
+        <source>Tray is not available for your system</source>
+        <translation>A bandeja não está disponível para seu sistema</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/settings/pagegeneral.cpp" line="62"/>
         <source>Select where downloads folder is going to be</source>
         <translation>Selecione onde a pasta de downloads ficará</translation>
     </message>
@@ -780,27 +993,34 @@ Navegar</translation>
 <context>
     <name>Settings</name>
     <message>
+        <location filename="../ui/widgets/settings/settings.ui" line="14"/>
         <source>Preferences</source>
         <translatorcomment>Window title</translatorcomment>
         <translation>Preferências</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/settings/settings.ui" line="105"/>
+        <location filename="../ui/widgets/settings/settings.ui" line="177"/>
         <source>General</source>
         <translation>Geral</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/settings/settings.ui" line="117"/>
         <source>Appearance</source>
         <translation>Aparência</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/settings/settings.ui" line="141"/>
         <source>Apply</source>
         <translation>Aplicar</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/settings/settings.ui" line="152"/>
         <source>Cancel</source>
         <translation>Cancelar</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/settings/settings.ui" line="163"/>
         <source>Ok</source>
         <translation>Feito</translation>
     </message>
@@ -808,31 +1028,38 @@ Navegar</translation>
 <context>
     <name>Squawk</name>
     <message>
+        <location filename="../ui/squawk.ui" line="14"/>
         <source>squawk</source>
         <translation>Squawk</translation>
     </message>
     <message>
+        <location filename="../ui/squawk.ui" line="191"/>
         <source>Settings</source>
         <translation>Configurações</translation>
     </message>
     <message>
+        <location filename="../ui/squawk.ui" line="198"/>
         <source>Squawk</source>
         <translatorcomment>Menu bar entry</translatorcomment>
         <translation>Squawk</translation>
     </message>
     <message>
+        <location filename="../ui/squawk.ui" line="220"/>
         <source>Accounts</source>
         <translation>Contas</translation>
     </message>
     <message>
+        <location filename="../ui/squawk.ui" line="229"/>
         <source>Quit</source>
         <translation>Sair</translation>
     </message>
     <message>
+        <location filename="../ui/squawk.ui" line="241"/>
         <source>Add contact</source>
         <translation>Adicionar contato</translation>
     </message>
     <message>
+        <location filename="../ui/squawk.ui" line="253"/>
         <source>Add conference</source>
         <translation>Adicionar conferência</translation>
     </message>
@@ -845,30 +1072,42 @@ Navegar</translation>
         <translation type="vanished">Conectar</translation>
     </message>
     <message>
+        <location filename="../ui/squawk.cpp" line="295"/>
+        <location filename="../ui/squawk.cpp" line="382"/>
         <source>VCard</source>
         <translation>VCard</translation>
     </message>
     <message>
+        <location filename="../ui/squawk.cpp" line="299"/>
+        <location filename="../ui/squawk.cpp" line="386"/>
+        <location filename="../ui/squawk.cpp" line="414"/>
         <source>Remove</source>
         <translation>Remover</translation>
     </message>
     <message>
+        <location filename="../ui/squawk.cpp" line="309"/>
         <source>Open dialog</source>
         <translation>Abrir caixa de diálogo</translation>
     </message>
     <message>
+        <location filename="../ui/squawk.cpp" line="317"/>
+        <location filename="../ui/squawk.cpp" line="405"/>
         <source>Unsubscribe</source>
         <translation>Cancelar inscrição</translation>
     </message>
     <message>
+        <location filename="../ui/squawk.cpp" line="325"/>
+        <location filename="../ui/squawk.cpp" line="409"/>
         <source>Subscribe</source>
         <translation>Inscrever-se</translation>
     </message>
     <message>
+        <location filename="../ui/squawk.cpp" line="331"/>
         <source>Rename</source>
         <translation>Renomear</translation>
     </message>
     <message>
+        <location filename="../ui/squawk.cpp" line="344"/>
         <source>Input new name for %1
 or leave it empty for the contact 
 to be displayed as %1</source>
@@ -878,38 +1117,47 @@ ser exibido com
 %1</translation>
     </message>
     <message>
+        <location filename="../ui/squawk.cpp" line="345"/>
         <source>Renaming %1</source>
         <translation>Renomeando %1</translation>
     </message>
     <message>
+        <location filename="../ui/squawk.cpp" line="351"/>
         <source>Groups</source>
         <translation>Grupos</translation>
     </message>
     <message>
+        <location filename="../ui/squawk.cpp" line="366"/>
         <source>New group</source>
         <translation>Novo grupo</translation>
     </message>
     <message>
+        <location filename="../ui/squawk.cpp" line="376"/>
         <source>New group name</source>
         <translation>Novo nome do grupo</translation>
     </message>
     <message>
+        <location filename="../ui/squawk.cpp" line="377"/>
         <source>Add %1 to a new group</source>
         <translation>Adicionar %1 a um novo grupo</translation>
     </message>
     <message>
+        <location filename="../ui/squawk.cpp" line="396"/>
         <source>Open conversation</source>
         <translation>Abrir conversa</translation>
     </message>
     <message>
+        <location filename="../ui/squawk.cpp" line="458"/>
         <source>%1 account card</source>
         <translation>cartão da conta %1</translation>
     </message>
     <message>
+        <location filename="../ui/squawk.cpp" line="460"/>
         <source>%1 contact card</source>
         <translation>cartão de contato %1</translation>
     </message>
     <message>
+        <location filename="../ui/squawk.cpp" line="472"/>
         <source>Downloading vCard</source>
         <translation>Baixando vCard</translation>
     </message>
@@ -926,26 +1174,32 @@ ser exibido com
         <translation type="vanished">Senha para a conta %1</translation>
     </message>
     <message>
+        <location filename="../ui/squawk.ui" line="161"/>
         <source>Please select a contact to start chatting</source>
         <translation>Por favor selecione um contato para começar a conversar</translation>
     </message>
     <message>
+        <location filename="../ui/squawk.ui" line="206"/>
         <source>Help</source>
         <translation>Ajuda</translation>
     </message>
     <message>
+        <location filename="../ui/squawk.ui" line="262"/>
         <source>Preferences</source>
         <translation>Preferências</translation>
     </message>
     <message>
+        <location filename="../ui/squawk.ui" line="267"/>
         <source>About Squawk</source>
         <translation>Sorbe Squawk</translation>
     </message>
     <message>
+        <location filename="../ui/squawk.cpp" line="288"/>
         <source>Deactivate</source>
         <translation>Desativar</translation>
     </message>
     <message>
+        <location filename="../ui/squawk.cpp" line="291"/>
         <source>Activate</source>
         <translation>Ativar</translation>
     </message>
@@ -953,154 +1207,195 @@ ser exibido com
 <context>
     <name>VCard</name>
     <message>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="65"/>
         <source>Received 12.07.2007 at 17.35</source>
         <translation>Nunca atualizado</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="100"/>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="425"/>
         <source>General</source>
         <translation>Geral</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="130"/>
         <source>Organization</source>
         <translation>Empresa</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="180"/>
         <source>Middle name</source>
         <translation>Nome do meio</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="190"/>
         <source>First name</source>
         <translation>Primeiro nome</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="200"/>
         <source>Last name</source>
         <translation>Sobrenome</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="226"/>
         <source>Nick name</source>
         <translation>Apelido</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="252"/>
         <source>Birthday</source>
         <translation>Data de aniversário</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="272"/>
         <source>Organization name</source>
         <translation>Nome da empresa</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="298"/>
         <source>Unit / Department</source>
         <translation>Unidade / Departamento</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="324"/>
         <source>Role / Profession</source>
         <translation>Profissão</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="350"/>
         <source>Job title</source>
         <translation>Cargo</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="390"/>
         <source>Full name</source>
         <translation>Nome completo</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="460"/>
         <source>Personal information</source>
         <translation>Informações pessoais</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="653"/>
         <source>Addresses</source>
         <translation>Endereços</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="682"/>
         <source>E-Mail addresses</source>
         <translation>Endereços de e-mail</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="767"/>
         <source>Phone numbers</source>
         <translation>Números de telefone</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="522"/>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="546"/>
         <source>Contact</source>
         <translation>Contato</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="713"/>
         <source>Jabber ID</source>
         <translation>ID Jabber</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="739"/>
         <source>Web site</source>
         <translation>Site web</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="798"/>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="822"/>
         <source>Description</source>
         <translation>Descrição</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="859"/>
         <source>Set avatar</source>
         <translation>Definir avatar</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="868"/>
         <source>Clear avatar</source>
         <translation>Apagar avatar</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/vcard/vcard.cpp" line="93"/>
         <source>Account %1 card</source>
         <translation>Cartão da conta %1</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/vcard/vcard.cpp" line="108"/>
         <source>Contact %1 card</source>
         <translation>Cartão do contato %1</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/vcard/vcard.cpp" line="174"/>
         <source>Received %1 at %2</source>
         <translation>Recebido %1 em %2</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/vcard/vcard.cpp" line="225"/>
         <source>Chose your new avatar</source>
         <translation>Escolha um novo avatar</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/vcard/vcard.cpp" line="227"/>
         <source>Images (*.png *.jpg *.jpeg)</source>
         <translation>Imagens (*.png *.jpg *.jpeg)</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/vcard/vcard.cpp" line="314"/>
         <source>Add email address</source>
         <translation>Adicionar endereço de e-mail</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/vcard/vcard.cpp" line="324"/>
         <source>Unset this email as preferred</source>
         <translation>Desmarcar este e-mail como preferido</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/vcard/vcard.cpp" line="327"/>
         <source>Set this email as preferred</source>
         <translation>Marcar este e-mail como preferido</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/vcard/vcard.cpp" line="332"/>
         <source>Remove selected email addresses</source>
         <translation>Remover endereço de e-mail selecionado</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/vcard/vcard.cpp" line="337"/>
         <source>Copy selected emails to clipboard</source>
         <translation>Copiar endereços de e-mails selecionados para a área de transferência</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/vcard/vcard.cpp" line="342"/>
         <source>Add phone number</source>
         <translation>Adicionar número de telefone</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/vcard/vcard.cpp" line="352"/>
         <source>Unset this phone as preferred</source>
         <translation>Desmarcar este número de telefone como preferido</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/vcard/vcard.cpp" line="355"/>
         <source>Set this phone as preferred</source>
         <translation>Marcar este número de telefone como preferido</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/vcard/vcard.cpp" line="360"/>
         <source>Remove selected phone numbers</source>
         <translation>Remover os números de telefones selecionados</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/vcard/vcard.cpp" line="365"/>
         <source>Copy selected phones to clipboard</source>
         <translation>Copiar os números de telefone selecionados para a área de transferência</translation>
     </message>
diff --git a/translations/squawk.ru.ts b/translations/squawk.ru.ts
index 1774bb9..421a2d2 100644
--- a/translations/squawk.ru.ts
+++ b/translations/squawk.ru.ts
@@ -4,101 +4,130 @@
 <context>
     <name>About</name>
     <message>
+        <location filename="../ui/widgets/about.ui" line="14"/>
         <source>About Squawk</source>
         <translation>О Программе Squawk</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/about.ui" line="44"/>
         <source>Squawk</source>
         <translation>Squawk</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/about.ui" line="96"/>
         <source>About</source>
         <translatorcomment>Tab title</translatorcomment>
         <translation>Общее</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/about.ui" line="115"/>
         <source>XMPP (jabber) messenger</source>
         <translation>XMPP (jabber) мессенджер</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/about.ui" line="122"/>
         <source>(c) 2019 - 2022, Yury Gubich</source>
         <translation>(c) 2019 - 2022, Юрий Губич</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/about.ui" line="129"/>
         <source>&lt;a href=&quot;https://git.macaw.me/blue/squawk&quot;&gt;Project site&lt;/a&gt;</source>
         <translation>&lt;a href=&quot;https://git.macaw.me/blue/squawk&quot;&gt;Сайт проекта&lt;/a&gt;</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/about.ui" line="142"/>
         <source>&lt;a href=&quot;https://git.macaw.me/blue/squawk/src/branch/master/LICENSE.md&quot;&gt;License: GNU General Public License version 3&lt;/a&gt;</source>
         <translation>&lt;a href=&quot;https://git.macaw.me/blue/squawk/src/branch/master/LICENSE.md&quot;&gt;Лицензия: GNU General Public License версия 3&lt;/a&gt;</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/about.ui" line="175"/>
         <source>Components</source>
         <translation>Компоненты</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/about.ui" line="216"/>
+        <location filename="../ui/widgets/about.ui" line="312"/>
+        <location filename="../ui/widgets/about.ui" line="660"/>
         <source>Version</source>
         <translation>Версия</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/about.ui" line="228"/>
+        <location filename="../ui/widgets/about.ui" line="324"/>
+        <location filename="../ui/widgets/about.ui" line="670"/>
         <source>0.0.0</source>
         <translation>0.0.0</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/about.ui" line="397"/>
         <source>Report Bugs</source>
         <translation>Сообщать об ошибках</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/about.ui" line="416"/>
         <source>Please report any bug you find!
 To report bugs you can use:</source>
         <translation>Пожалуйста, сообщайте о любых ошибках!
 Способы сообщить об ошибках:</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/about.ui" line="424"/>
         <source>&lt;a href=&quot;https://git.macaw.me/blue/squawk/issues&quot;&gt;Project bug tracker&lt;/&gt;</source>
         <translation>&lt;a href=&quot;https://git.macaw.me/blue/squawk/issues&quot;&gt;Баг-трекер проекта&lt;/&gt;</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/about.ui" line="434"/>
         <source>XMPP (&lt;a href=&quot;xmpp:blue@macaw.me&quot;&gt;blue@macaw.me&lt;/a&gt;)</source>
         <translation>XMPP (&lt;a href=&quot;xmpp:blue@macaw.me&quot;&gt;blue@macaw.me&lt;/a&gt;)</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/about.ui" line="444"/>
         <source>E-Mail (&lt;a href=&quot;mailto:blue@macaw.me&quot;&gt;blue@macaw.me&lt;/a&gt;)</source>
         <translation>E-Mail (&lt;a href=&quot;mailto:blue@macaw.me&quot;&gt;blue@macaw.me&lt;/a&gt;)</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/about.ui" line="477"/>
         <source>Thanks To</source>
         <translation>Благодарности</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/about.ui" line="519"/>
         <source>Vae</source>
         <translation>Vae</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/about.ui" line="531"/>
         <source>Major refactoring, bug fixes, constructive criticism</source>
         <translation>Крупный рефакторинг, исправление ошибок, конструктивная критика</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/about.ui" line="568"/>
         <source>Shunf4</source>
         <translation>Shunf4</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/about.ui" line="580"/>
         <source>Major refactoring, bug fixes, build adaptations for Windows and MacOS</source>
         <translation>Крупный рефакторинг, исправление ошибок, адаптация сборки под Windows and MacOS</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/about.ui" line="617"/>
         <source>Bruno F. Fontes</source>
         <translation>Bruno F. Fontes</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/about.ui" line="629"/>
         <source>Brazilian Portuguese translation</source>
         <translation>Перевод на Португальский (Бразилия)</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/about.cpp" line="35"/>
+        <location filename="../ui/widgets/about.cpp" line="38"/>
         <source>(built against %1)</source>
         <translation>(версия при сборке %1)</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/about.cpp" line="71"/>
         <source>License</source>
         <translation>Лицензия</translation>
     </message>
@@ -106,83 +135,101 @@ To report bugs you can use:</source>
 <context>
     <name>Account</name>
     <message>
+        <location filename="../ui/widgets/accounts/account.ui" line="14"/>
         <source>Account</source>
         <translatorcomment>Заголовок окна</translatorcomment>
         <translation>Учетная запись</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/accounts/account.ui" line="40"/>
         <source>Your account login</source>
         <translatorcomment>Tooltip</translatorcomment>
         <translation>Имя пользователя Вашей учетной записи</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/accounts/account.ui" line="43"/>
         <source>john_smith1987</source>
         <translatorcomment>Login placeholder</translatorcomment>
         <translation>ivan_ivanov1987</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/accounts/account.ui" line="50"/>
         <source>Server</source>
         <translation>Сервер</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/accounts/account.ui" line="57"/>
         <source>A server address of your account. Like 404.city or macaw.me</source>
         <translatorcomment>Tooltip</translatorcomment>
         <translation>Адресс сервера вашей учетной записи (выглядит как 404.city или macaw.me)</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/accounts/account.ui" line="60"/>
         <source>macaw.me</source>
         <translatorcomment>Placeholder</translatorcomment>
         <translation>macaw.me</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/accounts/account.ui" line="67"/>
         <source>Login</source>
         <translation>Имя учетной записи</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/accounts/account.ui" line="74"/>
         <source>Password</source>
         <translation>Пароль</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/accounts/account.ui" line="81"/>
         <source>Password of your account</source>
         <translatorcomment>Tooltip</translatorcomment>
         <translation>Пароль вашей учетной записи</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/accounts/account.ui" line="103"/>
         <source>Name</source>
         <translation>Имя</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/accounts/account.ui" line="110"/>
         <source>Just a name how would you call this account, doesn&apos;t affect anything</source>
         <translation>Просто имя, то как Вы называете свою учетную запись, может быть любым (нельзя поменять)</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/accounts/account.ui" line="113"/>
         <source>John</source>
         <translatorcomment>Placeholder</translatorcomment>
         <translation>Иван</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/accounts/account.ui" line="120"/>
         <source>Resource</source>
         <translation>Ресурс</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/accounts/account.ui" line="127"/>
         <source>A resource name like &quot;Home&quot; or &quot;Work&quot;</source>
         <translatorcomment>Tooltip</translatorcomment>
         <translation>Имя этой программы для ваших контактов, может быть &quot;Home&quot; или &quot;Phone&quot;</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/accounts/account.ui" line="130"/>
         <source>QXmpp</source>
         <translatorcomment>Ресурс по умолчанию</translatorcomment>
         <translation>QXmpp</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/accounts/account.ui" line="137"/>
         <source>Password storage</source>
         <translation>Хранение пароля</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/accounts/account.ui" line="163"/>
         <source>Active</source>
         <translation>Активен</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/accounts/account.ui" line="170"/>
         <source>enable</source>
         <translation>включен</translation>
     </message>
@@ -190,26 +237,32 @@ To report bugs you can use:</source>
 <context>
     <name>Accounts</name>
     <message>
+        <location filename="../ui/widgets/accounts/accounts.ui" line="14"/>
         <source>Accounts</source>
         <translation>Учетные записи</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/accounts/accounts.ui" line="45"/>
         <source>Delete</source>
         <translation>Удалить</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/accounts/accounts.ui" line="86"/>
         <source>Add</source>
         <translation>Добавить</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/accounts/accounts.ui" line="96"/>
         <source>Edit</source>
         <translation>Редактировать</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/accounts/accounts.ui" line="106"/>
         <source>Change password</source>
         <translation>Изменить пароль</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/accounts/accounts.ui" line="129"/>
         <source>Connect</source>
         <translation>Подключить</translation>
     </message>
@@ -218,10 +271,13 @@ To report bugs you can use:</source>
         <translation type="vanished">Отключить</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/accounts/accounts.cpp" line="126"/>
         <source>Deactivate</source>
         <translation>Деактивировать</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/accounts/accounts.cpp" line="129"/>
+        <location filename="../ui/widgets/accounts/accounts.cpp" line="132"/>
         <source>Activate</source>
         <translation>Активировать</translation>
     </message>
@@ -229,18 +285,38 @@ To report bugs you can use:</source>
 <context>
     <name>Application</name>
     <message>
+        <location filename="../main/application.cpp" line="32"/>
+        <source>Quit</source>
+        <translation>Выйти</translation>
+    </message>
+    <message>
+        <location filename="../main/application.cpp" line="33"/>
+        <location filename="../main/application.cpp" line="305"/>
+        <source>Minimize to tray</source>
+        <translation>Свернуть в трей</translation>
+    </message>
+    <message>
+        <location filename="../main/application.cpp" line="232"/>
+        <source>Show Squawk</source>
+        <translation>Показать Squawk</translation>
+    </message>
+    <message>
+        <location filename="../main/application.cpp" line="353"/>
         <source> from </source>
         <translation> от </translation>
     </message>
     <message>
+        <location filename="../main/application.cpp" line="361"/>
         <source>Attached file</source>
         <translation>Прикрепленный файл</translation>
     </message>
     <message>
+        <location filename="../main/application.cpp" line="366"/>
         <source>Mark as Read</source>
         <translation>Пометить прочитанным</translation>
     </message>
     <message>
+        <location filename="../main/application.cpp" line="367"/>
         <source>Open conversation</source>
         <translation>Открыть окно беседы</translation>
     </message>
@@ -248,19 +324,23 @@ To report bugs you can use:</source>
 <context>
     <name>Conversation</name>
     <message>
+        <location filename="../ui/widgets/conversation.ui" line="436"/>
         <source>Type your message here...</source>
         <translatorcomment>Placeholder</translatorcomment>
         <translation>Введите сообщение...</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/conversation.cpp" line="277"/>
         <source>Chose a file to send</source>
         <translation>Выберите файл для отправки</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/conversation.cpp" line="141"/>
         <source>Drop files here to attach them to your message</source>
         <translation>Бросьте файлы сюда для того что бы прикрепить их к сообщению</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/conversation.ui" line="426"/>
         <source>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
 &lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
 p, li { white-space: pre-wrap; }
@@ -273,34 +353,42 @@ p, li { white-space: pre-wrap; }
 &lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/conversation.cpp" line="58"/>
         <source>Paste Image</source>
         <translation>Вставить изображение</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/conversation.cpp" line="495"/>
         <source>Try sending again</source>
         <translation>Отправить снова</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/conversation.cpp" line="505"/>
         <source>Copy selected</source>
         <translation>Скопировать выделенное</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/conversation.cpp" line="515"/>
         <source>Copy message</source>
         <translation>Скопировать сообщение</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/conversation.cpp" line="525"/>
         <source>Open</source>
         <translation>Открыть</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/conversation.cpp" line="530"/>
         <source>Show in folder</source>
         <translation>Показать в проводнике</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/conversation.cpp" line="540"/>
         <source>Edit</source>
         <translation>Редактировать</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/conversation.cpp" line="570"/>
         <source>Editing message...</source>
         <translation>Сообщение редактируется...</translation>
     </message>
@@ -308,11 +396,13 @@ p, li { white-space: pre-wrap; }
 <context>
     <name>CredentialsPrompt</name>
     <message>
+        <location filename="../ui/widgets/accounts/credentialsprompt.ui" line="14"/>
         <source>Authentication error: %1</source>
         <translatorcomment>Window title</translatorcomment>
         <translation>Ошибка аутентификации: %1</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/accounts/credentialsprompt.ui" line="29"/>
         <source>Couldn&apos;t authenticate account %1: login or password is icorrect.
 Would you like to check them and try again?</source>
         <translation>Не получилось аутентифицировать
@@ -322,19 +412,23 @@ Would you like to check them and try again?</source>
 попробовать аутентифицироваться еще раз?</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/accounts/credentialsprompt.ui" line="45"/>
         <source>Login</source>
         <translation>Имя учетной записи</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/accounts/credentialsprompt.ui" line="52"/>
         <source>Your account login (without @server.domain)</source>
         <translatorcomment>Tooltip</translatorcomment>
         <translation>Имя вашей учтетной записи (без @server.domain)</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/accounts/credentialsprompt.ui" line="59"/>
         <source>Password</source>
         <translation>Пароль</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/accounts/credentialsprompt.ui" line="66"/>
         <source>Your password</source>
         <translation>Ваш пароль</translation>
     </message>
@@ -342,10 +436,12 @@ Would you like to check them and try again?</source>
 <context>
     <name>DialogQueue</name>
     <message>
+        <location filename="../main/dialogqueue.cpp" line="99"/>
         <source>Input the password for account %1</source>
         <translation>Введите пароль для учетной записи %1</translation>
     </message>
     <message>
+        <location filename="../main/dialogqueue.cpp" line="100"/>
         <source>Password for account %1</source>
         <translation>Пароль для учетной записи %1</translation>
     </message>
@@ -353,196 +449,235 @@ Would you like to check them and try again?</source>
 <context>
     <name>Global</name>
     <message>
+        <location filename="../shared/global.cpp" line="42"/>
         <source>Online</source>
         <comment>Availability</comment>
         <translation>В сети</translation>
     </message>
     <message>
+        <location filename="../shared/global.cpp" line="43"/>
         <source>Away</source>
         <comment>Availability</comment>
         <translation>Отошел</translation>
     </message>
     <message>
+        <location filename="../shared/global.cpp" line="44"/>
         <source>Absent</source>
         <comment>Availability</comment>
         <translation>Недоступен</translation>
     </message>
     <message>
+        <location filename="../shared/global.cpp" line="45"/>
         <source>Busy</source>
         <comment>Availability</comment>
         <translation>Занят</translation>
     </message>
     <message>
+        <location filename="../shared/global.cpp" line="46"/>
         <source>Chatty</source>
         <comment>Availability</comment>
         <translation>Готов поболтать</translation>
     </message>
     <message>
+        <location filename="../shared/global.cpp" line="47"/>
         <source>Invisible</source>
         <comment>Availability</comment>
         <translation>Невидимый</translation>
     </message>
     <message>
+        <location filename="../shared/global.cpp" line="48"/>
         <source>Offline</source>
         <comment>Availability</comment>
         <translation>Отключен</translation>
     </message>
     <message>
+        <location filename="../shared/global.cpp" line="51"/>
         <source>Disconnected</source>
         <comment>ConnectionState</comment>
         <translation>Отключен</translation>
     </message>
     <message>
+        <location filename="../shared/global.cpp" line="52"/>
         <source>Connecting</source>
         <comment>ConnectionState</comment>
         <translation>Подключается</translation>
     </message>
     <message>
+        <location filename="../shared/global.cpp" line="53"/>
         <source>Connected</source>
         <comment>ConnectionState</comment>
         <translation>Подключен</translation>
     </message>
     <message>
+        <location filename="../shared/global.cpp" line="54"/>
         <source>Error</source>
         <comment>ConnectionState</comment>
         <translation>Ошибка</translation>
     </message>
     <message>
+        <location filename="../shared/global.cpp" line="57"/>
         <source>None</source>
         <comment>SubscriptionState</comment>
         <translation>Нет</translation>
     </message>
     <message>
+        <location filename="../shared/global.cpp" line="58"/>
         <source>From</source>
         <comment>SubscriptionState</comment>
         <translation>Входящая</translation>
     </message>
     <message>
+        <location filename="../shared/global.cpp" line="59"/>
         <source>To</source>
         <comment>SubscriptionState</comment>
         <translation>Исходящая</translation>
     </message>
     <message>
+        <location filename="../shared/global.cpp" line="60"/>
         <source>Both</source>
         <comment>SubscriptionState</comment>
         <translation>Взаимная</translation>
     </message>
     <message>
+        <location filename="../shared/global.cpp" line="61"/>
         <source>Unknown</source>
         <comment>SubscriptionState</comment>
         <translation>Неизвестно</translation>
     </message>
     <message>
+        <location filename="../shared/global.cpp" line="64"/>
         <source>Unspecified</source>
         <comment>Affiliation</comment>
         <translation>Не назначено</translation>
     </message>
     <message>
+        <location filename="../shared/global.cpp" line="65"/>
         <source>Outcast</source>
         <comment>Affiliation</comment>
         <translation>Изгой</translation>
     </message>
     <message>
+        <location filename="../shared/global.cpp" line="66"/>
         <source>Nobody</source>
         <comment>Affiliation</comment>
         <translation>Никто</translation>
     </message>
     <message>
+        <location filename="../shared/global.cpp" line="67"/>
         <source>Member</source>
         <comment>Affiliation</comment>
         <translation>Участник</translation>
     </message>
     <message>
+        <location filename="../shared/global.cpp" line="68"/>
         <source>Admin</source>
         <comment>Affiliation</comment>
         <translation>Администратор</translation>
     </message>
     <message>
+        <location filename="../shared/global.cpp" line="69"/>
         <source>Owner</source>
         <comment>Affiliation</comment>
         <translation>Владелец</translation>
     </message>
     <message>
+        <location filename="../shared/global.cpp" line="72"/>
         <source>Unspecified</source>
         <comment>Role</comment>
         <translation>Не назначено</translation>
     </message>
     <message>
+        <location filename="../shared/global.cpp" line="73"/>
         <source>Nobody</source>
         <comment>Role</comment>
         <translation>Никто</translation>
     </message>
     <message>
+        <location filename="../shared/global.cpp" line="74"/>
         <source>Visitor</source>
         <comment>Role</comment>
         <translation>Гость</translation>
     </message>
     <message>
+        <location filename="../shared/global.cpp" line="75"/>
         <source>Participant</source>
         <comment>Role</comment>
         <translation>Участник</translation>
     </message>
     <message>
+        <location filename="../shared/global.cpp" line="76"/>
         <source>Moderator</source>
         <comment>Role</comment>
         <translation>Модератор</translation>
     </message>
     <message>
+        <location filename="../shared/global.cpp" line="79"/>
         <source>Pending</source>
         <comment>MessageState</comment>
         <translation>В процессе отправки</translation>
     </message>
     <message>
+        <location filename="../shared/global.cpp" line="80"/>
         <source>Sent</source>
         <comment>MessageState</comment>
         <translation>Отправлено</translation>
     </message>
     <message>
+        <location filename="../shared/global.cpp" line="81"/>
         <source>Delivered</source>
         <comment>MessageState</comment>
         <translation>Доставлено</translation>
     </message>
     <message>
+        <location filename="../shared/global.cpp" line="82"/>
         <source>Error</source>
         <comment>MessageState</comment>
         <translation>Ошибка</translation>
     </message>
     <message>
+        <location filename="../shared/global.cpp" line="85"/>
         <source>Plain</source>
         <comment>AccountPassword</comment>
         <translation>Открытый текст</translation>
     </message>
     <message>
+        <location filename="../shared/global.cpp" line="86"/>
         <source>Jammed</source>
         <comment>AccountPassword</comment>
         <translation>Обфусцированный</translation>
     </message>
     <message>
+        <location filename="../shared/global.cpp" line="87"/>
         <source>Always Ask</source>
         <comment>AccountPassword</comment>
         <translation>Всегда спрашивать</translation>
     </message>
     <message>
+        <location filename="../shared/global.cpp" line="88"/>
         <source>KWallet</source>
         <comment>AccountPassword</comment>
         <translation>KWallet</translation>
     </message>
     <message>
+        <location filename="../shared/global.cpp" line="92"/>
         <source>Your password is going to be stored in config file but jammed with constant encryption key you can find in program source code. It might look like encryption but it&apos;s not</source>
         <comment>AccountPasswordDescription</comment>
         <translation>Ваш пароль будет храниться в обфусцированном виде в конфигурационном файле. Обфускация производится с помощью постоянного числа, которое можно найти в исходном коде программы. Это может и выглядит как шифрование но им не является</translation>
     </message>
     <message>
+        <location filename="../shared/global.cpp" line="93"/>
         <source>Squawk is going to query you for the password on every start of the program</source>
         <comment>AccountPasswordDescription</comment>
         <translation>Squawk будет спрашивать пароль от этой учетной записи каждый раз при запуске</translation>
     </message>
     <message>
+        <location filename="../shared/global.cpp" line="91"/>
         <source>Your password is going to be stored in config file in plain text</source>
         <comment>AccountPasswordDescription</comment>
         <translation>Ваш пароль будет храниться в конфигурационном файле открытым текстром</translation>
     </message>
     <message>
+        <location filename="../shared/global.cpp" line="94"/>
         <source>Your password is going to be stored in KDE wallet storage (KWallet). You&apos;re going to be queried for permissions</source>
         <comment>AccountPasswordDescription</comment>
         <translation>Ваш пароль будет храниться в бумажнике KDE (KWallet). В первый раз программа попросит разрешения для доступа к бумажнику</translation>
@@ -551,43 +686,53 @@ Would you like to check them and try again?</source>
 <context>
     <name>JoinConference</name>
     <message>
+        <location filename="../ui/widgets/joinconference.ui" line="14"/>
         <source>Join new conference</source>
         <translatorcomment>Заголовок окна</translatorcomment>
         <translation>Присоединиться к новой беседе</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/joinconference.ui" line="22"/>
         <source>JID</source>
         <translation>JID</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/joinconference.ui" line="29"/>
         <source>Room JID</source>
         <translation>Jabber-идентификатор беседы</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/joinconference.ui" line="32"/>
         <source>identifier@conference.server.org</source>
         <translation>identifier@conference.server.org</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/joinconference.ui" line="39"/>
         <source>Account</source>
         <translation>Учетная запись</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/joinconference.ui" line="49"/>
         <source>Join on login</source>
         <translation>Автовход</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/joinconference.ui" line="56"/>
         <source>If checked Squawk will try to join this conference on login</source>
         <translation>Если стоит галочка Squawk автоматически присоединится к этой беседе при подключении</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/joinconference.ui" line="66"/>
         <source>Nick name</source>
         <translation>Псевдоним</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/joinconference.ui" line="73"/>
         <source>Your nick name for that conference. If you leave this field empty your account name will be used as a nick name</source>
         <translation>Ваш псевдоним в этой беседе, если оставите это поле пустым - будет использовано имя Вашей учетной записи</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/joinconference.ui" line="76"/>
         <source>John</source>
         <translation>Ivan</translation>
     </message>
@@ -606,6 +751,8 @@ Would you like to check them and try again?</source>
         <translation type="vanished">Скачивается...</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/messageline/messagedelegate.cpp" line="65"/>
+        <location filename="../ui/widgets/messageline/messagedelegate.cpp" line="668"/>
         <source>Download</source>
         <translation>Скачать</translation>
     </message>
@@ -639,18 +786,22 @@ You can try again</source>
 <context>
     <name>Models::Room</name>
     <message>
+        <location filename="../ui/models/room.cpp" line="191"/>
         <source>Subscribed</source>
         <translation>Вы состоите в беседе</translation>
     </message>
     <message>
+        <location filename="../ui/models/room.cpp" line="193"/>
         <source>Temporarily unsubscribed</source>
         <translation>Вы временно не состоите в беседе</translation>
     </message>
     <message>
+        <location filename="../ui/models/room.cpp" line="197"/>
         <source>Temporarily subscribed</source>
         <translation>Вы временно состоите в беседе</translation>
     </message>
     <message>
+        <location filename="../ui/models/room.cpp" line="199"/>
         <source>Unsubscribed</source>
         <translation>Вы не состоите в беседе</translation>
     </message>
@@ -658,47 +809,67 @@ You can try again</source>
 <context>
     <name>Models::Roster</name>
     <message>
+        <location filename="../ui/models/roster.cpp" line="85"/>
         <source>New messages</source>
         <translation>Есть непрочитанные сообщения</translation>
     </message>
     <message>
+        <location filename="../ui/models/roster.cpp" line="198"/>
+        <location filename="../ui/models/roster.cpp" line="253"/>
+        <location filename="../ui/models/roster.cpp" line="265"/>
         <source>New messages: </source>
         <translation>Новых сообщений: </translation>
     </message>
     <message>
+        <location filename="../ui/models/roster.cpp" line="200"/>
+        <location filename="../ui/models/roster.cpp" line="268"/>
         <source>Jabber ID: </source>
         <translation>Идентификатор: </translation>
     </message>
     <message>
+        <location filename="../ui/models/roster.cpp" line="204"/>
+        <location filename="../ui/models/roster.cpp" line="223"/>
+        <location filename="../ui/models/roster.cpp" line="236"/>
         <source>Availability: </source>
         <translation>Доступность: </translation>
     </message>
     <message>
+        <location filename="../ui/models/roster.cpp" line="208"/>
+        <location filename="../ui/models/roster.cpp" line="226"/>
+        <location filename="../ui/models/roster.cpp" line="239"/>
         <source>Status: </source>
         <translation>Статус: </translation>
     </message>
     <message>
+        <location filename="../ui/models/roster.cpp" line="211"/>
+        <location filename="../ui/models/roster.cpp" line="213"/>
+        <location filename="../ui/models/roster.cpp" line="269"/>
         <source>Subscription: </source>
         <translation>Подписка: </translation>
     </message>
     <message>
+        <location filename="../ui/models/roster.cpp" line="242"/>
         <source>Affiliation: </source>
         <translatorcomment>Я правда не знаю, как это объяснить, не то что перевести</translatorcomment>
         <translation>Причастность: </translation>
     </message>
     <message>
+        <location filename="../ui/models/roster.cpp" line="243"/>
         <source>Role: </source>
         <translation>Роль: </translation>
     </message>
     <message>
+        <location filename="../ui/models/roster.cpp" line="255"/>
         <source>Online contacts: </source>
         <translation>Контакстов в сети: </translation>
     </message>
     <message>
+        <location filename="../ui/models/roster.cpp" line="256"/>
         <source>Total contacts: </source>
         <translation>Всего контактов: </translation>
     </message>
     <message>
+        <location filename="../ui/models/roster.cpp" line="271"/>
         <source>Members: </source>
         <translation>Участников: </translation>
     </message>
@@ -706,40 +877,49 @@ You can try again</source>
 <context>
     <name>NewContact</name>
     <message>
+        <location filename="../ui/widgets/newcontact.ui" line="14"/>
         <source>Add new contact</source>
         <translatorcomment>Window title</translatorcomment>
         <translation>Добавление нового контакта</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/newcontact.ui" line="22"/>
         <source>Account</source>
         <translation>Учетная запись</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/newcontact.ui" line="29"/>
         <source>An account that is going to have new contact</source>
         <translation>Учетная запись для которой будет добавлен контакт</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/newcontact.ui" line="36"/>
         <source>JID</source>
         <translation>JID</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/newcontact.ui" line="43"/>
         <source>Jabber id of your new contact</source>
         <translation>Jabber-идентификатор нового контакта</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/newcontact.ui" line="46"/>
         <source>name@server.dmn</source>
         <translatorcomment>Placeholder</translatorcomment>
         <translation>name@server.dmn</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/newcontact.ui" line="53"/>
         <source>Name</source>
         <translation>Имя</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/newcontact.ui" line="60"/>
         <source>The way this new contact will be labeled in your roster (optional)</source>
         <translation>То, как будет подписан контакт в вашем списке контактов (не обязательно)</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/newcontact.ui" line="63"/>
         <source>John Smith</source>
         <translation>Иван Иванов</translation>
     </message>
@@ -747,14 +927,20 @@ You can try again</source>
 <context>
     <name>PageAppearance</name>
     <message>
+        <location filename="../ui/widgets/settings/pageappearance.ui" line="17"/>
         <source>Theme</source>
         <translation>Оформление</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/settings/pageappearance.ui" line="30"/>
         <source>Color scheme</source>
         <translation>Цветовая схема</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/settings/pageappearance.cpp" line="36"/>
+        <location filename="../ui/widgets/settings/pageappearance.cpp" line="49"/>
+        <location filename="../ui/widgets/settings/pageappearance.cpp" line="56"/>
+        <location filename="../ui/widgets/settings/pageappearance.cpp" line="59"/>
         <source>System</source>
         <translation>Системная</translation>
     </message>
@@ -762,19 +948,46 @@ You can try again</source>
 <context>
     <name>PageGeneral</name>
     <message>
+        <location filename="../ui/widgets/settings/pagegeneral.ui" line="17"/>
         <source>Downloads path</source>
         <translation>Папка для сохраненных файлов</translation>
     </message>
     <message>
         <location filename="../ui/widgets/settings/pagegeneral.ui" line="45"/>
-        <source>Close to tray icon</source>
-        <translation>Закрывать в трей</translation>
+        <source>Tray icon</source>
+        <translation>Иконка трея</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/settings/pagegeneral.ui" line="52"/>
+        <source>Mimimize Squawk to tray when closing main window</source>
+        <translation>Сворачивать Squawk в трей когда закрывается основное окно</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/settings/pagegeneral.ui" line="59"/>
+        <source>Hide tray icon</source>
+        <translation>Скрывать иконку трея</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/settings/pagegeneral.ui" line="66"/>
+        <source>Hide tray icon when Squawk main window is visible</source>
+        <translation>Прятать иконку трея если основное окно не закрыто</translation>
+    </message>
+    <message>
+        <source>Close to tray icon</source>
+        <translation type="vanished">Закрывать в трей</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/settings/pagegeneral.ui" line="36"/>
         <source>Browse</source>
         <translation>Выбрать</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/settings/pagegeneral.cpp" line="45"/>
+        <source>Tray is not available for your system</source>
+        <translation>На вашей системе недоступен трей</translation>
+    </message>
+    <message>
+        <location filename="../ui/widgets/settings/pagegeneral.cpp" line="62"/>
         <source>Select where downloads folder is going to be</source>
         <translation>Выберете папку, в которую будут сохраняться файлы</translation>
     </message>
@@ -782,27 +995,34 @@ You can try again</source>
 <context>
     <name>Settings</name>
     <message>
+        <location filename="../ui/widgets/settings/settings.ui" line="14"/>
         <source>Preferences</source>
         <translatorcomment>Window title</translatorcomment>
         <translation>Настройки</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/settings/settings.ui" line="105"/>
+        <location filename="../ui/widgets/settings/settings.ui" line="177"/>
         <source>General</source>
         <translation>Общее</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/settings/settings.ui" line="117"/>
         <source>Appearance</source>
         <translation>Внешний вид</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/settings/settings.ui" line="141"/>
         <source>Apply</source>
         <translation>Применить</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/settings/settings.ui" line="152"/>
         <source>Cancel</source>
         <translation>Отменить</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/settings/settings.ui" line="163"/>
         <source>Ok</source>
         <translation>Готово</translation>
     </message>
@@ -810,31 +1030,38 @@ You can try again</source>
 <context>
     <name>Squawk</name>
     <message>
+        <location filename="../ui/squawk.ui" line="14"/>
         <source>squawk</source>
         <translation>Squawk</translation>
     </message>
     <message>
+        <location filename="../ui/squawk.ui" line="191"/>
         <source>Settings</source>
         <translation>Настройки</translation>
     </message>
     <message>
+        <location filename="../ui/squawk.ui" line="198"/>
         <source>Squawk</source>
         <translatorcomment>Menu bar entry</translatorcomment>
         <translation>Squawk</translation>
     </message>
     <message>
+        <location filename="../ui/squawk.ui" line="220"/>
         <source>Accounts</source>
         <translation>Учетные записи</translation>
     </message>
     <message>
+        <location filename="../ui/squawk.ui" line="229"/>
         <source>Quit</source>
         <translation>Выйти</translation>
     </message>
     <message>
+        <location filename="../ui/squawk.ui" line="241"/>
         <source>Add contact</source>
         <translation>Добавить контакт</translation>
     </message>
     <message>
+        <location filename="../ui/squawk.ui" line="253"/>
         <source>Add conference</source>
         <translation>Присоединиться к беседе</translation>
     </message>
@@ -847,30 +1074,42 @@ You can try again</source>
         <translation type="vanished">Подключить</translation>
     </message>
     <message>
+        <location filename="../ui/squawk.cpp" line="295"/>
+        <location filename="../ui/squawk.cpp" line="382"/>
         <source>VCard</source>
         <translation>Карточка</translation>
     </message>
     <message>
+        <location filename="../ui/squawk.cpp" line="299"/>
+        <location filename="../ui/squawk.cpp" line="386"/>
+        <location filename="../ui/squawk.cpp" line="414"/>
         <source>Remove</source>
         <translation>Удалить</translation>
     </message>
     <message>
+        <location filename="../ui/squawk.cpp" line="309"/>
         <source>Open dialog</source>
         <translation>Открыть диалог</translation>
     </message>
     <message>
+        <location filename="../ui/squawk.cpp" line="317"/>
+        <location filename="../ui/squawk.cpp" line="405"/>
         <source>Unsubscribe</source>
         <translation>Отписаться</translation>
     </message>
     <message>
+        <location filename="../ui/squawk.cpp" line="325"/>
+        <location filename="../ui/squawk.cpp" line="409"/>
         <source>Subscribe</source>
         <translation>Подписаться</translation>
     </message>
     <message>
+        <location filename="../ui/squawk.cpp" line="331"/>
         <source>Rename</source>
         <translation>Переименовать</translation>
     </message>
     <message>
+        <location filename="../ui/squawk.cpp" line="344"/>
         <source>Input new name for %1
 or leave it empty for the contact 
 to be displayed as %1</source>
@@ -880,38 +1119,47 @@ to be displayed as %1</source>
 %1</translation>
     </message>
     <message>
+        <location filename="../ui/squawk.cpp" line="345"/>
         <source>Renaming %1</source>
         <translation>Назначение имени контакту %1</translation>
     </message>
     <message>
+        <location filename="../ui/squawk.cpp" line="351"/>
         <source>Groups</source>
         <translation>Группы</translation>
     </message>
     <message>
+        <location filename="../ui/squawk.cpp" line="366"/>
         <source>New group</source>
         <translation>Создать новую группу</translation>
     </message>
     <message>
+        <location filename="../ui/squawk.cpp" line="376"/>
         <source>New group name</source>
         <translation>Имя группы</translation>
     </message>
     <message>
+        <location filename="../ui/squawk.cpp" line="377"/>
         <source>Add %1 to a new group</source>
         <translation>Добавление %1 в новую группу</translation>
     </message>
     <message>
+        <location filename="../ui/squawk.cpp" line="396"/>
         <source>Open conversation</source>
         <translation>Открыть окно беседы</translation>
     </message>
     <message>
+        <location filename="../ui/squawk.cpp" line="458"/>
         <source>%1 account card</source>
         <translation>Карточка учетной записи %1</translation>
     </message>
     <message>
+        <location filename="../ui/squawk.cpp" line="460"/>
         <source>%1 contact card</source>
         <translation>Карточка контакта %1</translation>
     </message>
     <message>
+        <location filename="../ui/squawk.cpp" line="472"/>
         <source>Downloading vCard</source>
         <translation>Получение карточки</translation>
     </message>
@@ -928,26 +1176,32 @@ to be displayed as %1</source>
         <translation type="vanished">Пароль для учетной записи %1</translation>
     </message>
     <message>
+        <location filename="../ui/squawk.ui" line="161"/>
         <source>Please select a contact to start chatting</source>
         <translation>Выберите контакт или группу что бы начать переписку</translation>
     </message>
     <message>
+        <location filename="../ui/squawk.ui" line="206"/>
         <source>Help</source>
         <translation>Помощь</translation>
     </message>
     <message>
+        <location filename="../ui/squawk.ui" line="262"/>
         <source>Preferences</source>
         <translation>Настройки</translation>
     </message>
     <message>
+        <location filename="../ui/squawk.ui" line="267"/>
         <source>About Squawk</source>
         <translation>О Программе Squawk</translation>
     </message>
     <message>
+        <location filename="../ui/squawk.cpp" line="288"/>
         <source>Deactivate</source>
         <translation>Деактивировать</translation>
     </message>
     <message>
+        <location filename="../ui/squawk.cpp" line="291"/>
         <source>Activate</source>
         <translation>Активировать</translation>
     </message>
@@ -955,154 +1209,195 @@ to be displayed as %1</source>
 <context>
     <name>VCard</name>
     <message>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="65"/>
         <source>Received 12.07.2007 at 17.35</source>
         <translation>Не обновлялось</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="100"/>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="425"/>
         <source>General</source>
         <translation>Общее</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="130"/>
         <source>Organization</source>
         <translation>Место работы</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="180"/>
         <source>Middle name</source>
         <translation>Среднее имя</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="190"/>
         <source>First name</source>
         <translation>Имя</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="200"/>
         <source>Last name</source>
         <translation>Фамилия</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="226"/>
         <source>Nick name</source>
         <translation>Псевдоним</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="252"/>
         <source>Birthday</source>
         <translation>Дата рождения</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="272"/>
         <source>Organization name</source>
         <translation>Название организации</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="298"/>
         <source>Unit / Department</source>
         <translation>Отдел</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="324"/>
         <source>Role / Profession</source>
         <translation>Профессия</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="350"/>
         <source>Job title</source>
         <translation>Наименование должности</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="390"/>
         <source>Full name</source>
         <translation>Полное имя</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="460"/>
         <source>Personal information</source>
         <translation>Личная информация</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="653"/>
         <source>Addresses</source>
         <translation>Адреса</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="682"/>
         <source>E-Mail addresses</source>
         <translation>Адреса электронной почты</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="767"/>
         <source>Phone numbers</source>
         <translation>Номера телефонов</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="522"/>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="546"/>
         <source>Contact</source>
         <translation>Контактная информация</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="713"/>
         <source>Jabber ID</source>
         <translation>Jabber ID</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="739"/>
         <source>Web site</source>
         <translation>Веб сайт</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="798"/>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="822"/>
         <source>Description</source>
         <translation>Описание</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="859"/>
         <source>Set avatar</source>
         <translation>Установить иконку</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/vcard/vcard.ui" line="868"/>
         <source>Clear avatar</source>
         <translation>Убрать иконку</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/vcard/vcard.cpp" line="93"/>
         <source>Account %1 card</source>
         <translation>Карточка учетной записи %1</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/vcard/vcard.cpp" line="108"/>
         <source>Contact %1 card</source>
         <translation>Карточка контакта %1</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/vcard/vcard.cpp" line="174"/>
         <source>Received %1 at %2</source>
         <translation>Получено %1 в %2</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/vcard/vcard.cpp" line="225"/>
         <source>Chose your new avatar</source>
         <translation>Выберите новую иконку</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/vcard/vcard.cpp" line="227"/>
         <source>Images (*.png *.jpg *.jpeg)</source>
         <translation>Изображения (*.png *.jpg *.jpeg)</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/vcard/vcard.cpp" line="314"/>
         <source>Add email address</source>
         <translation>Добавить адрес электронной почты</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/vcard/vcard.cpp" line="324"/>
         <source>Unset this email as preferred</source>
         <translation>Убрать отметку &quot;предпочтительный&quot; с этого адреса</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/vcard/vcard.cpp" line="327"/>
         <source>Set this email as preferred</source>
         <translation>Отметить этот адрес как &quot;предпочтительный&quot;</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/vcard/vcard.cpp" line="332"/>
         <source>Remove selected email addresses</source>
         <translation>Удалить выбранные адреса</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/vcard/vcard.cpp" line="337"/>
         <source>Copy selected emails to clipboard</source>
         <translation>Скопировать выбранные адреса в буфер обмена</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/vcard/vcard.cpp" line="342"/>
         <source>Add phone number</source>
         <translation>Добавить номер телефона</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/vcard/vcard.cpp" line="352"/>
         <source>Unset this phone as preferred</source>
         <translation>Убрать отметку &quot;предпочтительный&quot; с этого номера</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/vcard/vcard.cpp" line="355"/>
         <source>Set this phone as preferred</source>
         <translation>Отметить этот номер как &quot;предпочтительный&quot;</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/vcard/vcard.cpp" line="360"/>
         <source>Remove selected phone numbers</source>
         <translation>Удалить выбранные телефонные номера</translation>
     </message>
     <message>
+        <location filename="../ui/widgets/vcard/vcard.cpp" line="365"/>
         <source>Copy selected phones to clipboard</source>
         <translation>Скопировать выбранные телефонные номера в буфер обмена</translation>
     </message>
diff --git a/ui/models/element.cpp b/ui/models/element.cpp
index acea46f..e89c5aa 100644
--- a/ui/models/element.cpp
+++ b/ui/models/element.cpp
@@ -75,6 +75,11 @@ void Models::Element::update(const QString& field, const QVariant& value)
     }
 }
 
+QString Models::Element::getId() const
+{
+    return jid;
+}
+
 QString Models::Element::getAvatarPath() const
 {
     return avatarPath;
diff --git a/ui/models/element.h b/ui/models/element.h
index c6d3d6e..2ce5a21 100644
--- a/ui/models/element.h
+++ b/ui/models/element.h
@@ -38,6 +38,7 @@ public:
     QString getAvatarPath() const;
     
     virtual void update(const QString& field, const QVariant& value);
+    virtual QString getId() const override;
     
     void addMessage(const Shared::Message& data);
     void changeMessage(const QString& id, const QMap<QString, QVariant>& data);
diff --git a/ui/models/item.cpp b/ui/models/item.cpp
index 4a88dd2..dda28d6 100644
--- a/ui/models/item.cpp
+++ b/ui/models/item.cpp
@@ -181,6 +181,11 @@ QString Models::Item::getName() const
     return name;
 }
 
+QString Models::Item::getId() const
+{
+    return name;
+}
+
 QVariant Models::Item::data(int column) const
 {
     if (column != 0) {
diff --git a/ui/models/item.h b/ui/models/item.h
index 4661479..d3fbb02 100644
--- a/ui/models/item.h
+++ b/ui/models/item.h
@@ -65,6 +65,7 @@ class Item : public QObject{
         virtual void appendChild(Item *child);
         virtual void removeChild(int index);
         virtual QString getDisplayedName() const;
+        virtual QString getId() const;
         QString getName() const;
         void setName(const QString& name);
         
diff --git a/ui/models/reference.cpp b/ui/models/reference.cpp
index 1aaea15..fe3bf41 100644
--- a/ui/models/reference.cpp
+++ b/ui/models/reference.cpp
@@ -75,6 +75,11 @@ QString Models::Reference::getDisplayedName() const
     return original->getDisplayedName();
 }
 
+QString Models::Reference::getId() const
+{
+    return original->getId();
+}
+
 Models::Item * Models::Reference::dereference()
 {
     return original;
diff --git a/ui/models/reference.h b/ui/models/reference.h
index 8ec5352..bc717bb 100644
--- a/ui/models/reference.h
+++ b/ui/models/reference.h
@@ -38,6 +38,8 @@ public:
     QString getDisplayedName() const override;
     void appendChild(Models::Item * child) override;
     void removeChild(int index) override;
+    QString getId() const override;
+
     Item* dereference();
     const Item* dereferenceConst() const;
     
diff --git a/ui/models/room.h b/ui/models/room.h
index 707b35b..b3e889c 100644
--- a/ui/models/room.h
+++ b/ui/models/room.h
@@ -76,7 +76,6 @@ private:
 private:
     bool autoJoin;
     bool joined;
-    QString jid;
     QString nick;
     QString subject;
     std::map<QString, Participant*> participants;
diff --git a/ui/models/roster.cpp b/ui/models/roster.cpp
index fbb7e52..8ce3464 100644
--- a/ui/models/roster.cpp
+++ b/ui/models/roster.cpp
@@ -52,6 +52,8 @@ void Models::Roster::addAccount(const QMap<QString, QVariant>& data)
     root->appendChild(acc);
     accounts.insert(std::make_pair(acc->getName(), acc));
     accountsModel->addAccount(acc);
+
+    emit addedElement({acc->getId()});
 }
 
 QVariant Models::Roster::data (const QModelIndex& index, int role) const
@@ -433,6 +435,9 @@ void Models::Roster::addGroup(const QString& account, const QString& name)
         Group* group = new Group({{"name", name}});
         groups.insert(std::make_pair(id, group));
         acc->appendChild(group);
+
+
+        emit addedElement({acc->getId(), group->getId()});
     } else {
         qDebug() << "An attempt to add group " << name << " to non existing account " << account << ", skipping";
     }
@@ -470,6 +475,7 @@ void Models::Roster::addContact(const QString& account, const QString& jid, cons
         }
     }
     
+    std::list<QString> path = {acc->getId()};
     if (group == "") {
         if (acc->getContact(jid) != -1) {
             qDebug() << "An attempt to add a contact" << jid << "to the ungrouped contact set of account" << account << "for the second time, skipping";
@@ -486,6 +492,7 @@ void Models::Roster::addContact(const QString& account, const QString& jid, cons
         }
         
         parent = itr->second;
+        path.push_back(parent->getId());
         
         if (parent->getContact(jid) != -1) {
             qDebug() << "An attempt to add a contact" << jid << "to the group" << group << "for the second time, skipping";
@@ -502,11 +509,14 @@ void Models::Roster::addContact(const QString& account, const QString& jid, cons
         }
         
     }
+    path.push_back(contact->getId());
     
     if (ref == 0) {
         ref = new Reference(contact);
     }
     parent->appendChild(ref);
+
+    emit addedElement(path);
 }
 
 void Models::Roster::removeGroup(const QString& account, const QString& name)
@@ -694,6 +704,7 @@ void Models::Roster::addPresence(const QString& account, const QString& jid, con
     if (itr != contacts.end()) {
         itr->second->addPresence(name, data);
     }
+
 }
 
 void Models::Roster::removePresence(const QString& account, const QString& jid, const QString& name)
@@ -809,6 +820,8 @@ void Models::Roster::addRoom(const QString& account, const QString jid, const QM
     connect(room, &Room::unreadMessagesCountChanged, this, &Roster::recalculateUnreadMessages);
     rooms.insert(std::make_pair(id, room));
     acc->appendChild(room);
+
+    emit addedElement({acc->getId(), room->getId()});
 }
 
 void Models::Roster::changeRoom(const QString& account, const QString jid, const QMap<QString, QVariant>& data)
@@ -961,7 +974,7 @@ bool Models::Roster::markMessageAsRead(const Models::Roster::ElId& elementId, co
     }
 }
 
-QModelIndex Models::Roster::getAccountIndex(const QString& name)
+QModelIndex Models::Roster::getAccountIndex(const QString& name) const
 {
     std::map<QString, Account*>::const_iterator itr = accounts.find(name);
     if (itr == accounts.end()) {
@@ -971,7 +984,7 @@ QModelIndex Models::Roster::getAccountIndex(const QString& name)
     }
 }
 
-QModelIndex Models::Roster::getGroupIndex(const QString& account, const QString& name)
+QModelIndex Models::Roster::getGroupIndex(const QString& account, const QString& name) const
 {
     std::map<QString, Account*>::const_iterator itr = accounts.find(account);
     if (itr == accounts.end()) {
@@ -987,7 +1000,7 @@ QModelIndex Models::Roster::getGroupIndex(const QString& account, const QString&
     }
 }
 
-QModelIndex Models::Roster::getContactIndex(const QString& account, const QString& jid, const QString& resource)
+QModelIndex Models::Roster::getContactIndex(const QString& account, const QString& jid, const QString& resource) const
 {
     std::map<QString, Account*>::const_iterator itr = accounts.find(account);
     if (itr == accounts.end()) {
@@ -1113,3 +1126,66 @@ void Models::Roster::recalculateUnreadMessages()
     }
     emit unreadMessagesCountChanged(count);
 }
+
+std::list<QString> Models::Roster::getItemPath(const QModelIndex& index) const
+{
+    std::list<QString> result;
+    if (index.isValid() && index.model() == this) {
+        Item* item = static_cast<Item*>(index.internalPointer());
+        while (item->type != Item::root) {
+            result.push_front(item->getId());
+            item = item->parentItem();
+        }
+    }
+
+    return result;
+}
+
+QModelIndex Models::Roster::getIndexByPath(const std::list<QString>& path) const
+{
+    if (path.empty())
+        return QModelIndex();
+
+    QModelIndex current;
+    for (const QString& hop : path) {
+        int rows = rowCount(current);
+        bool found = false;
+        for (int i = 0; i < rows; ++i) {
+            QModelIndex el = index(i, 0, current);
+            Item* item = static_cast<Item*>(el.internalPointer());
+            if (item->getId() == hop) {
+                found = true;
+                current = el;
+                break;
+            }
+        }
+        if (!found)
+            break;
+
+    }
+    return current;     //this way I will return the last matching model index, may be it's logically incorrect
+
+
+//     std::list<QString>::const_iterator pathItr = path.begin();
+//     QString accName = *pathItr;
+//     QModelIndex accIndex = getAccountIndex(accName);
+//     if (path.size() == 1)
+//         return accIndex;
+//
+//     if (!accIndex.isValid())
+//         return QModelIndex();
+//
+//     ++pathItr;
+//     ElId id{accName, *pathItr};
+//     QModelIndex contactIndex = getContactIndex(id.account, id.name);
+//     if (!contactIndex.isValid())
+//         contactIndex = getGroupIndex(id.account, id.name);
+//
+//     if (path.size() == 2)
+//         return contactIndex;
+//
+//     if (!contactIndex.isValid())
+//         return QModelIndex();
+//
+//     ++pathItr;
+}
diff --git a/ui/models/roster.h b/ui/models/roster.h
index efc50f2..59fef44 100644
--- a/ui/models/roster.h
+++ b/ui/models/roster.h
@@ -22,6 +22,7 @@
 #include <qabstractitemmodel.h>
 #include <deque>
 #include <map>
+#include <list>
 #include <QVector>
 
 #include "shared/message.h"
@@ -86,9 +87,12 @@ public:
     QString getContactIconPath(const QString& account, const QString& jid, const QString& resource) const;
     Account* getAccount(const QString& name);
     const Account* getAccountConst(const QString& name) const;
-    QModelIndex getAccountIndex(const QString& name);
-    QModelIndex getGroupIndex(const QString& account, const QString& name);
-    QModelIndex getContactIndex(const QString& account, const QString& jid, const QString& resource = "");
+    QModelIndex getAccountIndex(const QString& name) const;
+    QModelIndex getGroupIndex(const QString& account, const QString& name) const;
+    QModelIndex getContactIndex(const QString& account, const QString& jid, const QString& resource = "") const;
+    QModelIndex getIndexByPath(const std::list<QString>& path) const;
+    std::list<QString> getItemPath(const QModelIndex& index) const;
+
     bool markMessageAsRead(const ElId& elementId, const QString& messageId);
     void responseArchive(const QString& account, const QString& jid, const std::list<Shared::Message>& list, bool last);
     
@@ -104,6 +108,7 @@ signals:
     void unreadMessagesCountChanged(int count);
     void unnoticedMessage(const QString& account, const Shared::Message& msg);
     void localPathInvalid(const QString& path);
+    void addedElement(const std::list<QString>& path);      //emits only on addition of Account, Contact, Room or Group. Presence and Participant are ignored
     
 private slots:
     void onAccountDataChanged(const QModelIndex& tl, const QModelIndex& br, const QVector<int>& roles);
diff --git a/ui/squawk.cpp b/ui/squawk.cpp
index 39a7202..c086e10 100644
--- a/ui/squawk.cpp
+++ b/ui/squawk.cpp
@@ -60,7 +60,8 @@ Squawk::Squawk(Models::Roster& p_rosterModel, QWidget *parent) :
     connect(m_ui->comboBox, qOverload<int>(&QComboBox::activated), this, &Squawk::onComboboxActivated);
     //connect(m_ui->roster, &QTreeView::doubleClicked, this, &Squawk::onRosterItemDoubleClicked);
     connect(m_ui->roster, &QTreeView::customContextMenuRequested, this, &Squawk::onRosterContextMenu);
-    connect(m_ui->roster, &QTreeView::collapsed, this, &Squawk::onItemCollepsed);
+    connect(m_ui->roster, &QTreeView::collapsed, this, &Squawk::itemCollapsed);
+    connect(m_ui->roster, &QTreeView::expanded, this, &Squawk::itemExpanded);
     connect(m_ui->roster->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &Squawk::onRosterSelectionChanged);
     
     connect(rosterModel.accountsModel, &Models::Accounts::changed, this, &Squawk::onAccountsChanged);
@@ -491,49 +492,7 @@ void Squawk::writeSettings()
         settings.endGroup();
     
         settings.setValue("splitter", m_ui->splitter->saveState());
-        settings.remove("roster");
-        settings.beginGroup("roster");
-            int size = rosterModel.accountsModel->rowCount(QModelIndex());
-            for (int i = 0; i < size; ++i) {
-                QModelIndex acc = rosterModel.index(i, 0, QModelIndex());
-                Models::Account* account = rosterModel.accountsModel->getAccount(i);
-                QString accName = account->getName();
-                settings.beginGroup(accName);
-
-                    settings.setValue("expanded", m_ui->roster->isExpanded(acc));
-                    std::deque<QString> groups = rosterModel.groupList(accName);
-                    for (const QString& groupName : groups) {
-                        settings.beginGroup(groupName);
-                            QModelIndex gIndex = rosterModel.getGroupIndex(accName, groupName);
-                            settings.setValue("expanded", m_ui->roster->isExpanded(gIndex));
-                        settings.endGroup();
-                    }
-
-                settings.endGroup();
-            }
-        settings.endGroup();
     settings.endGroup();
-
-    settings.sync();
-}
-
-void Squawk::onItemCollepsed(const QModelIndex& index)
-{
-    QSettings settings;
-    Models::Item* item = static_cast<Models::Item*>(index.internalPointer());
-    switch (item->type) {
-        case Models::Item::account:
-            settings.setValue("ui/roster/" + item->getName() + "/expanded", false);
-            break;
-        case Models::Item::group: {
-            QModelIndex accInd = rosterModel.parent(index);
-            Models::Account* account = rosterModel.accountsModel->getAccount(accInd.row());
-            settings.setValue("ui/roster/" + account->getName() + "/" + item->getName() + "/expanded", false);
-        }
-            break;
-        default:
-            break;
-    }
 }
 
 void Squawk::onRosterSelectionChanged(const QModelIndex& current, const QModelIndex& previous)
diff --git a/ui/squawk.h b/ui/squawk.h
index 19fc058..84e94a8 100644
--- a/ui/squawk.h
+++ b/ui/squawk.h
@@ -83,6 +83,8 @@ signals:
     void openConversation(const Models::Roster::ElId& id, const QString& resource = "");
 
     void modifyAccountRequest(const QString&, const QMap<QString, QVariant>&);
+    void itemExpanded (const QModelIndex& index);
+    void itemCollapsed (const QModelIndex& index);
     
 public:
     Models::Roster::ElId currentConversationId() const;
@@ -127,7 +129,6 @@ private slots:
     void onComboboxActivated(int index);
     void onRosterItemDoubleClicked(const QModelIndex& item);
     void onRosterContextMenu(const QPoint& point);
-    void onItemCollepsed(const QModelIndex& index);
     void onRosterSelectionChanged(const QModelIndex& current, const QModelIndex& previous);
     void onContextAboutToHide();
     void onAboutSquawkCalled();
diff --git a/ui/widgets/settings/pagegeneral.cpp b/ui/widgets/settings/pagegeneral.cpp
index 39e155a..9e73574 100644
--- a/ui/widgets/settings/pagegeneral.cpp
+++ b/ui/widgets/settings/pagegeneral.cpp
@@ -100,7 +100,7 @@ void PageGeneral::onTrayChecked(int state)
     emit variableModified("tray", enabled);
     m_ui->hideTrayInput->setEnabled(enabled);
     if (!enabled) {
-        m_ui->hideTrayInput->setEnabled(false);
+        m_ui->hideTrayInput->setChecked(false);
     }
 }
 

From 2ae75a4b916b01b91a8e55bb005a6bbf7b23246f Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Sat, 20 Aug 2022 00:28:59 +0300
Subject: [PATCH 207/281] New object for cached database, also ClientInfo class

---
 core/account.cpp            |  2 ++
 core/squawk.h               |  3 ++
 core/storage/CMakeLists.txt |  2 ++
 core/storage/cache.cpp      | 69 +++++++++++++++++++++++++++++++++++++
 core/storage/cache.h        | 50 +++++++++++++++++++++++++++
 core/storage/storage.cpp    | 52 +++++++++++++++++++---------
 core/storage/storage.h      |  7 ++--
 shared/CMakeLists.txt       |  2 ++
 shared/clientinfo.cpp       | 69 +++++++++++++++++++++++++++++++++++++
 shared/clientinfo.h         | 50 +++++++++++++++++++++++++++
 10 files changed, 287 insertions(+), 19 deletions(-)
 create mode 100644 core/storage/cache.cpp
 create mode 100644 core/storage/cache.h
 create mode 100644 shared/clientinfo.cpp
 create mode 100644 shared/clientinfo.h

diff --git a/core/account.cpp b/core/account.cpp
index 3b9d7ec..0cb159d 100644
--- a/core/account.cpp
+++ b/core/account.cpp
@@ -93,6 +93,8 @@ Account::Account(const QString& p_login, const QString& p_server, const QString&
     QObject::connect(reconnectTimer, &QTimer::timeout, this, &Account::onReconnectTimer);
     
     if (name == "Test") {
+        qDebug() << "Presence capabilities: " << presence.capabilityNode();
+
         QXmppLogger* logger = new QXmppLogger(this);
         logger->setLoggingType(QXmppLogger::SignalLogging);
         client.setLogger(logger);
diff --git a/core/squawk.h b/core/squawk.h
index c82b1c8..3b8073b 100644
--- a/core/squawk.h
+++ b/core/squawk.h
@@ -31,8 +31,10 @@
 #include "shared/enums.h"
 #include "shared/message.h"
 #include "shared/global.h"
+#include "shared/clientinfo.h"
 #include "networkaccess.h"
 #include "external/simpleCrypt/simplecrypt.h"
+#include <core/storage/cache.h>
 
 #ifdef WITH_KWALLET
 #include "passwordStorageEngines/kwallet.h"
@@ -135,6 +137,7 @@ private:
     Shared::Availability state;
     NetworkAccess network;
     bool isInitialized;
+    //Cache<Shared::ClientInfo> clientCache;
 
 #ifdef WITH_KWALLET
     PSE::KWallet kwallet;
diff --git a/core/storage/CMakeLists.txt b/core/storage/CMakeLists.txt
index 4c263d5..5d34b7c 100644
--- a/core/storage/CMakeLists.txt
+++ b/core/storage/CMakeLists.txt
@@ -5,4 +5,6 @@ target_sources(squawk PRIVATE
     storage.h
     urlstorage.cpp
     urlstorage.h
+    cache.cpp
+    cache.h
 )
diff --git a/core/storage/cache.cpp b/core/storage/cache.cpp
new file mode 100644
index 0000000..71fe369
--- /dev/null
+++ b/core/storage/cache.cpp
@@ -0,0 +1,69 @@
+// Squawk messenger. 
+// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+// 
+// 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 "cache.h"
+
+#include <shared/clientinfo.h>
+
+template class Core::Cache<Shared::ClientInfo>;
+
+template <class T>
+Core::Cache<T>::Cache(const QString& name):
+    storage(name),
+    cache(new std::map<QString, T> ()) {}
+
+template <class T>
+Core::Cache<T>::~Cache() {
+    close();
+    delete cache;
+}
+
+template <class T>
+void Core::Cache<T>::open() {
+    storage.open();}
+
+template <class T>
+void Core::Cache<T>::close() {
+    storage.close();}
+
+template <class T>
+void Core::Cache<T>::addRecord(const QString& key, const T& value) {
+    storage.addRecord(key, value);
+    cache->insert(std::make_pair(key, value));
+}
+
+template <class T>
+T Core::Cache<T>::getRecord(const QString& key) const {
+    typename std::map<QString, T>::const_iterator itr = cache->find(key);
+    if (itr == cache->end()) {
+        T value = storage.getRecord(key);
+        itr = cache->insert(std::make_pair(key, value)).first;
+    }
+
+    return itr->second;
+}
+
+template<typename T>
+void Core::Cache<T>::changeRecord(const QString& key, const T& value) {
+    storage.changeRecord(key, value);
+    cache->at(key) = value;
+}
+
+template<typename T>
+void Core::Cache<T>::removeRecord(const QString& key) {
+    storage.removeRecord(key);
+    cache->erase(key);
+}
diff --git a/core/storage/cache.h b/core/storage/cache.h
new file mode 100644
index 0000000..e5b1b88
--- /dev/null
+++ b/core/storage/cache.h
@@ -0,0 +1,50 @@
+// Squawk messenger. 
+// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+// 
+// 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/>.
+
+#ifndef CORE_CACHE_H
+#define CORE_CACHE_H
+
+#include <map>
+
+#include <QString>
+
+#include <core/storage/storage.h>
+
+namespace Core {
+
+template <class T>
+class Cache
+{
+public:
+    Cache(const QString& name);
+    ~Cache();
+
+    void open();
+    void close();
+
+    void addRecord(const QString& key, const T& value);
+    void changeRecord(const QString& key, const T& value);
+    void removeRecord(const QString& key);
+    T getRecord(const QString& key) const;
+
+private:
+    Core::Storage<T> storage;
+    std::map<QString, T>* cache;
+};
+
+}
+
+#endif // CORE_CACHE_H
diff --git a/core/storage/storage.cpp b/core/storage/storage.cpp
index 7ff0ef7..99db8af 100644
--- a/core/storage/storage.cpp
+++ b/core/storage/storage.cpp
@@ -21,7 +21,12 @@
 
 #include "storage.h"
 
-Core::Storage::Storage(const QString& p_name):
+#include <shared/clientinfo.h>
+
+template class Core::Storage<Shared::ClientInfo>;
+
+template <class T>
+Core::Storage<T>::Storage(const QString& p_name):
     name(p_name),
     opened(false),
     environment(),
@@ -29,12 +34,14 @@ Core::Storage::Storage(const QString& p_name):
 {
 }
 
-Core::Storage::~Storage()
+template <class T>
+Core::Storage<T>::~Storage()
 {
     close();
 }
 
-void Core::Storage::open()
+template <class T>
+void Core::Storage<T>::open()
 {
     if (!opened) {
         mdb_env_create(&environment);
@@ -61,7 +68,8 @@ void Core::Storage::open()
     }
 }
 
-void Core::Storage::close()
+template <class T>
+void Core::Storage<T>::close()
 {
     if (opened) {
         mdb_dbi_close(environment, base);
@@ -70,19 +78,22 @@ void Core::Storage::close()
     }
 }
 
-void Core::Storage::addRecord(const QString& key, const QString& value)
+template <class T>
+void Core::Storage<T>::addRecord(const QString& key, const T& value)
 {
     if (!opened) {
         throw Archive::Closed("addRecord", name.toStdString());
     }
+    QByteArray ba;
+    QDataStream ds(&ba, QIODevice::WriteOnly);
     const std::string& id = key.toStdString();
-    const std::string& val = value.toStdString();
+    ds << value;
     
     MDB_val lmdbKey, lmdbData;
     lmdbKey.mv_size = id.size();
     lmdbKey.mv_data = (char*)id.c_str();
-    lmdbData.mv_size = val.size();
-    lmdbData.mv_data = (char*)val.c_str();
+    lmdbData.mv_size = ba.size();
+    lmdbData.mv_data = (uint8_t*)ba.data();
     MDB_txn *txn;
     mdb_txn_begin(environment, NULL, 0, &txn);
     int rc;
@@ -99,19 +110,23 @@ void Core::Storage::addRecord(const QString& key, const QString& value)
     }
 }
 
-void Core::Storage::changeRecord(const QString& key, const QString& value)
+template <class T>
+void Core::Storage<T>::changeRecord(const QString& key, const T& value)
 {
     if (!opened) {
         throw Archive::Closed("changeRecord", name.toStdString());
     }
+
+    QByteArray ba;
+    QDataStream ds(&ba, QIODevice::WriteOnly);
     const std::string& id = key.toStdString();
-    const std::string& val = value.toStdString();
+    ds << value;
     
     MDB_val lmdbKey, lmdbData;
     lmdbKey.mv_size = id.size();
     lmdbKey.mv_data = (char*)id.c_str();
-    lmdbData.mv_size = val.size();
-    lmdbData.mv_data = (char*)val.c_str();
+    lmdbData.mv_size = ba.size();
+    lmdbData.mv_data = (uint8_t*)ba.data();
     MDB_txn *txn;
     mdb_txn_begin(environment, NULL, 0, &txn);
     int rc;
@@ -126,7 +141,8 @@ void Core::Storage::changeRecord(const QString& key, const QString& value)
     }
 }
 
-QString Core::Storage::getRecord(const QString& key) const
+template <class T>
+T Core::Storage<T>::getRecord(const QString& key) const
 {
     if (!opened) {
         throw Archive::Closed("addElement", name.toStdString());
@@ -149,14 +165,18 @@ QString Core::Storage::getRecord(const QString& key) const
             throw Archive::Unknown(name.toStdString(), mdb_strerror(rc));
         }
     } else {
-        std::string sId((char*)lmdbData.mv_data, lmdbData.mv_size);
-        QString value(sId.c_str());
+        QByteArray ba((char*)lmdbData.mv_data, lmdbData.mv_size);
+        QDataStream ds(&ba, QIODevice::ReadOnly);
+        T value;
+        ds >> value;
         mdb_txn_abort(txn);
+
         return value;
     }
 }
 
-void Core::Storage::removeRecord(const QString& key)
+template <class T>
+void Core::Storage<T>::removeRecord(const QString& key)
 {
     if (!opened) {
         throw Archive::Closed("addElement", name.toStdString());
diff --git a/core/storage/storage.h b/core/storage/storage.h
index d2abfde..5942c0f 100644
--- a/core/storage/storage.h
+++ b/core/storage/storage.h
@@ -29,6 +29,7 @@ namespace Core {
 /**
  * @todo write docs
  */
+template <class T>
 class Storage
 {
 public:
@@ -38,10 +39,10 @@ public:
     void open();
     void close();
     
-    void addRecord(const QString& key, const QString& value);
-    void changeRecord(const QString& key, const QString& value);
+    void addRecord(const QString& key, const T& value);
+    void changeRecord(const QString& key, const T& value);
     void removeRecord(const QString& key);
-    QString getRecord(const QString& key) const;
+    T getRecord(const QString& key) const;
     
     
 private:
diff --git a/shared/CMakeLists.txt b/shared/CMakeLists.txt
index 0ab7dbd..1e47618 100644
--- a/shared/CMakeLists.txt
+++ b/shared/CMakeLists.txt
@@ -18,4 +18,6 @@ target_sources(squawk PRIVATE
   vcard.h
   pathcheck.cpp
   pathcheck.h
+  clientinfo.h
+  clientinfo.cpp
   )
diff --git a/shared/clientinfo.cpp b/shared/clientinfo.cpp
new file mode 100644
index 0000000..745bd54
--- /dev/null
+++ b/shared/clientinfo.cpp
@@ -0,0 +1,69 @@
+// Squawk messenger. 
+// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+// 
+// 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 "clientinfo.h"
+
+Shared::ClientInfo::ClientInfo():
+    name(),
+    category(),
+    type(),
+    capabilitiesNode(),
+    capabilitiesVerification(),
+    capabilitiesHash(),
+    capabilitiesExtensions() {}
+
+QDataStream & Shared::ClientInfo::operator >> (QDataStream& stream) const {
+    stream << name;
+    stream << category;
+    stream << type;
+    stream << capabilitiesNode;
+    stream << capabilitiesVerification;
+    stream << capabilitiesHash;
+    stream << (quint8)capabilitiesExtensions.size();
+    for (const QString& ext : capabilitiesExtensions) {
+        stream << ext;
+    }
+
+    return stream;
+}
+
+QDataStream & Shared::ClientInfo::operator << (QDataStream& stream) {
+    stream >> name;
+    stream >> category;
+    stream >> type;
+    stream >> capabilitiesNode;
+    stream >> capabilitiesVerification;
+    stream >> capabilitiesHash;
+
+    quint8 size;
+    stream >> size;
+    for (quint8 i = 0; i < size; ++i) {
+        QString ext;
+        stream >> ext;
+        capabilitiesExtensions.insert(ext);
+    }
+
+    return stream;
+}
+
+QDataStream& operator<< (QDataStream& stream, const Shared::ClientInfo& image) {
+    image >> stream;
+    return stream;
+}
+QDataStream& operator>> (QDataStream& stream, Shared::ClientInfo& image) {
+    image << stream;
+    return stream;
+}
diff --git a/shared/clientinfo.h b/shared/clientinfo.h
new file mode 100644
index 0000000..742e49a
--- /dev/null
+++ b/shared/clientinfo.h
@@ -0,0 +1,50 @@
+// Squawk messenger. 
+// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+// 
+// 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/>.
+
+#ifndef SHARED_CLIENTINFO_H
+#define SHARED_CLIENTINFO_H
+
+#include <set>
+
+#include <QDataStream>
+#include <QString>
+
+namespace Shared {
+
+class ClientInfo
+{
+public:
+    ClientInfo();
+
+    QDataStream& operator << (QDataStream& stream);
+    QDataStream& operator >> (QDataStream& stream) const;
+
+public:
+    QString name;
+    QString category;
+    QString type;
+    QString capabilitiesNode;
+    QString capabilitiesVerification;
+    QString capabilitiesHash;
+    std::set<QString> capabilitiesExtensions;
+};
+
+}
+
+QDataStream& operator<< (QDataStream& stream, const Shared::ClientInfo& image);
+QDataStream& operator>> (QDataStream& stream, Shared::ClientInfo& image);
+
+#endif // SHARED_CLIENTINFO_H

From 037dabbe06bf8ec6e219c15becac83eb8ad3c2c6 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Mon, 22 Aug 2022 23:29:43 +0300
Subject: [PATCH 208/281] some new shared classes, little reorganization,
 preparation to cache client info

---
 core/CMakeLists.txt                       |  3 +-
 core/account.cpp                          | 35 +++++++++++---
 core/account.h                            |  9 +++-
 core/components/CMakeLists.txt            |  6 +++
 core/components/clientcache.cpp           | 55 ++++++++++++++++++++++
 core/components/clientcache.h             | 56 +++++++++++++++++++++++
 core/{ => components}/networkaccess.cpp   |  0
 core/{ => components}/networkaccess.h     |  4 +-
 core/squawk.cpp                           | 30 +++++++++++-
 core/squawk.h                             |  9 ++--
 core/storage/CMakeLists.txt               |  4 +-
 core/storage/cache.h                      |  5 ++
 core/storage/{cache.cpp => cache.hpp}     | 49 ++++++++++++++++----
 core/storage/storage.h                    |  3 ++
 core/storage/{storage.cpp => storage.hpp} | 12 +++--
 main/main.cpp                             |  4 ++
 shared/CMakeLists.txt                     |  1 +
 shared/clientinfo.cpp                     |  6 +++
 shared/clientinfo.h                       |  3 ++
 shared/identity.h                         | 35 ++++++++++++++
 20 files changed, 297 insertions(+), 32 deletions(-)
 create mode 100644 core/components/CMakeLists.txt
 create mode 100644 core/components/clientcache.cpp
 create mode 100644 core/components/clientcache.h
 rename core/{ => components}/networkaccess.cpp (100%)
 rename core/{ => components}/networkaccess.h (98%)
 rename core/storage/{cache.cpp => cache.hpp} (60%)
 rename core/storage/{storage.cpp => storage.hpp} (97%)
 create mode 100644 shared/identity.h

diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt
index 6c7a3b5..d3327c9 100644
--- a/core/CMakeLists.txt
+++ b/core/CMakeLists.txt
@@ -12,8 +12,6 @@ target_sources(squawk PRIVATE
   conference.h
   contact.cpp
   contact.h
-  networkaccess.cpp
-  networkaccess.h
   rosteritem.cpp
   rosteritem.h
   ${SIGNALCATCHER_SOURCE}
@@ -27,3 +25,4 @@ target_include_directories(squawk PRIVATE ${LMDB_INCLUDE_DIRS})
 add_subdirectory(handlers)
 add_subdirectory(storage)
 add_subdirectory(passwordStorageEngines)
+add_subdirectory(components)
diff --git a/core/account.cpp b/core/account.cpp
index 0cb159d..08321d3 100644
--- a/core/account.cpp
+++ b/core/account.cpp
@@ -285,7 +285,10 @@ void Core::Account::onPresenceReceived(const QXmppPresence& p_presence)
             emit addPresence(jid, resource, {
                 {"lastActivity", lastInteraction},
                 {"availability", p_presence.availableStatusType()},           //TODO check and handle invisible
-                {"status", p_presence.statusText()}
+                {"status", p_presence.statusText()},
+                {"capabilityNode", p_presence.capabilityNode()},
+                {"capabilityVer", p_presence.capabilityVer().toBase64()},
+                {"capabilityHash", p_presence.capabilityHash()}
             });
         }
         break;
@@ -594,7 +597,8 @@ void Core::Account::onDiscoveryItemsReceived(const QXmppDiscoveryIq& items)
 
 void Core::Account::onDiscoveryInfoReceived(const QXmppDiscoveryIq& info)
 {
-    if (info.from() == getServer()) {
+    QString from = info.from();
+    if (from == getServer()) {
         bool enableCC = false;
         qDebug() << "Server info received for account" << name;
         QStringList features = info.features();
@@ -613,7 +617,7 @@ void Core::Account::onDiscoveryInfoReceived(const QXmppDiscoveryIq& info)
 
         qDebug() << "Requesting account" << name << "capabilities";
         dm->requestInfo(getBareJid());
-    } else if (info.from() == getBareJid()) {
+    } else if (from == getBareJid()) {
         qDebug() << "Received capabilities for account" << name << ":";
         QList<QXmppDiscoveryIq::Identity> identities = info.identities();
         bool pepSupported = false;
@@ -626,10 +630,27 @@ void Core::Account::onDiscoveryInfoReceived(const QXmppDiscoveryIq& info)
         }
         rh->setPepSupport(pepSupported);
     } else  {
-        qDebug() << "Received info for account" << name << "about" << info.from();
-        QList<QXmppDiscoveryIq::Identity> identities = info.identities();
-        for (const QXmppDiscoveryIq::Identity& identity : identities) {
-            qDebug() << "    " << identity.name() << identity.category() << identity.type();
+        qDebug() << "Received info for account" << name << "about" << from;
+        QString node = info.queryNode();
+        if (!node.isEmpty()) {
+            QStringList feats = info.features();
+            std::list<Shared::Identity> identities;
+            std::set<QString> features(feats.begin(), feats.end());
+            QList<QXmppDiscoveryIq::Identity> idents = info.identities();
+            for (const QXmppDiscoveryIq::Identity& ident : idents) {
+                identities.emplace_back();
+                Shared::Identity& identity = identities.back();
+                identity.category = ident.category();
+                identity.language = ident.language();
+                identity.name = ident.name();
+                identity.type = ident.type();
+
+                qDebug() << "    " << identity.name << identity.category << identity.type;
+            }
+            for (const QString& feat : features) {
+                qDebug() << "    " << feat;
+            }
+            emit infoDiscovered(from, node, identities, features);
         }
     }
 }
diff --git a/core/account.h b/core/account.h
index 2c9ec70..5651ad9 100644
--- a/core/account.h
+++ b/core/account.h
@@ -29,6 +29,7 @@
 
 #include <map>
 #include <set>
+#include <list>
 
 #include <QXmppRosterManager.h>
 #include <QXmppCarbonManager.h>
@@ -42,10 +43,11 @@
 #include <QXmppVCardManager.h>
 #include <QXmppMessageReceiptManager.h>
 
-#include "shared/shared.h"
+#include <shared/shared.h>
+#include <shared/identity.h>
 #include "contact.h"
 #include "conference.h"
-#include "networkaccess.h"
+#include <core/components/networkaccess.h>
 
 #include "handlers/messagehandler.h"
 #include "handlers/rosterhandler.h"
@@ -118,6 +120,8 @@ public:
     void resendMessage(const QString& jid, const QString& id);
     void replaceMessage(const QString& originalId, const Shared::Message& data);
     void invalidatePassword();
+
+    void discoverInfo(const QString& address, const QString& node);
     
 public slots:
     void connect();
@@ -151,6 +155,7 @@ signals:
     void uploadFile(const QFileInfo& file, const QUrl& set, const QUrl& get, QMap<QString, QString> headers);
     void uploadFileError(const QString& jid, const QString& messageId, const QString& error);
     void needPassword();
+    void infoDiscovered(const QString& address, const QString& node, const std::list<Shared::Identity>& identities, const std::set<QString>& features);
     
 private:
     QString name;
diff --git a/core/components/CMakeLists.txt b/core/components/CMakeLists.txt
new file mode 100644
index 0000000..5faf837
--- /dev/null
+++ b/core/components/CMakeLists.txt
@@ -0,0 +1,6 @@
+target_sources(squawk PRIVATE
+  networkaccess.cpp
+  networkaccess.h
+  clientcache.cpp
+  clientcache.h
+)
diff --git a/core/components/clientcache.cpp b/core/components/clientcache.cpp
new file mode 100644
index 0000000..0259f9c
--- /dev/null
+++ b/core/components/clientcache.cpp
@@ -0,0 +1,55 @@
+// Squawk messenger. 
+// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+// 
+// 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 "clientcache.h"
+
+Core::ClientCache::ClientCache():
+    requested(),
+    cache("clients")
+{
+    cache.open();
+}
+
+Core::ClientCache::~ClientCache() {
+    cache.close();
+}
+
+void Core::ClientCache::open() {
+    cache.open();}
+
+void Core::ClientCache::close() {
+    cache.close();}
+
+
+bool Core::ClientCache::checkClient(const QString& node, const QString& ver, const QString& hash) {
+    QString id = node + "/" + ver;
+    if (requested.count(id) == 0 && !cache.checkRecord(id)) {
+        Shared::ClientInfo& info = requested.insert(std::make_pair(id, Shared::ClientInfo())).first->second;
+        info.capabilitiesNode = node;
+        info.capabilitiesVerification = ver;
+        info.capabilitiesHash = hash;
+        emit requestClientInfo(id);
+        return false;
+    }
+
+    return true;
+}
+
+
+bool Core::ClientCache::registerClientInfo(const QString& sourceFullJid, const QString& id, const Shared::Identity& identity, const std::set<QString>& features) {
+
+}
diff --git a/core/components/clientcache.h b/core/components/clientcache.h
new file mode 100644
index 0000000..78c7e84
--- /dev/null
+++ b/core/components/clientcache.h
@@ -0,0 +1,56 @@
+// Squawk messenger. 
+// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+// 
+// 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/>.
+
+#ifndef CORE_CLIENTCACHE_H
+#define CORE_CLIENTCACHE_H
+
+#include <map>
+#include <set>
+
+#include <QObject>
+#include <QString>
+
+#include <core/storage/cache.h>
+#include <shared/clientinfo.h>
+#include <shared/identity.h>
+
+namespace Core {
+
+class ClientCache : public QObject {
+    Q_OBJECT
+public:
+    ClientCache();
+    ~ClientCache();
+
+    void open();
+    void close();
+
+signals:
+    void requestClientInfo(const QString& id);
+
+public slots:
+    bool checkClient(const QString& node, const QString& ver, const QString& hash);
+    bool registerClientInfo(const QString& sourceFullJid, const QString& id, const Shared::Identity& identity, const std::set<QString>& features);
+
+private:
+    std::map<QString, Shared::ClientInfo> requested;
+    Cache<Shared::ClientInfo> cache;
+};
+
+}
+
+
+#endif // CORE_CLIENTCACHE_H
diff --git a/core/networkaccess.cpp b/core/components/networkaccess.cpp
similarity index 100%
rename from core/networkaccess.cpp
rename to core/components/networkaccess.cpp
diff --git a/core/networkaccess.h b/core/components/networkaccess.h
similarity index 98%
rename from core/networkaccess.h
rename to core/components/networkaccess.h
index 6ddfa99..c94c22a 100644
--- a/core/networkaccess.h
+++ b/core/components/networkaccess.h
@@ -30,8 +30,8 @@
 
 #include <set>
 
-#include "storage/urlstorage.h"
-#include "shared/pathcheck.h"
+#include <core/storage/urlstorage.h>
+#include <shared/pathcheck.h>
 
 namespace Core {
 
diff --git a/core/squawk.cpp b/core/squawk.cpp
index 0f8fe9f..5edcf58 100644
--- a/core/squawk.cpp
+++ b/core/squawk.cpp
@@ -28,9 +28,10 @@ Core::Squawk::Squawk(QObject* parent):
     amap(),
     state(Shared::Availability::offline),
     network(),
-    isInitialized(false)
+    isInitialized(false),
+    clientCache(),
 #ifdef WITH_KWALLET
-    ,kwallet()
+    kwallet()
 #endif
 {
     connect(&network, &NetworkAccess::loadFileProgress, this, &Squawk::fileProgress);
@@ -67,6 +68,7 @@ void Core::Squawk::stop()
 {
     qDebug("Stopping squawk core..");
     network.stop();
+    clientCache.close();
 
     if (isInitialized) {
         QSettings settings;
@@ -115,6 +117,7 @@ void Core::Squawk::start()
     readSettings();
     isInitialized = true;
     network.start();
+    clientCache.open();
 }
 
 void Core::Squawk::newAccountRequest(const QMap<QString, QVariant>& map)
@@ -180,6 +183,8 @@ void Core::Squawk::addAccount(
     connect(acc, &Account::receivedVCard, this, &Squawk::responseVCard);
     
     connect(acc, &Account::uploadFileError, this, &Squawk::onAccountUploadFileError);
+
+    connect(acc, &Account::infoDiscovered, this, &Squawk::onAccountInfoDiscovered);
     
     QMap<QString, QVariant> map = {
         {"login", login},
@@ -314,6 +319,27 @@ void Core::Squawk::onAccountAddPresence(const QString& jid, const QString& name,
 {
     Account* acc = static_cast<Account*>(sender());
     emit addPresence(acc->getName(), jid, name, data);
+
+    QString node = data["capabilityNode"].toString();
+    QString ver = data["capabilityVer"].toString();
+    QString hash = data["capabilityHash"].toString();
+    if (!clientCache.checkClient(node, ver, hash)) {
+        acc->discoverInfo(jid + "/" + name, node + "/" + ver);
+    }
+}
+
+void Core::Squawk::onAccountInfoDiscovered(
+    const QString& address,
+    const QString& node,
+    const std::list<Shared::Identity>& identities,
+    const std::set<QString>& features)
+{
+    Account* acc = static_cast<Account*>(sender());
+
+    if (identities.size() != 1 || clientCache.registerClientInfo(address, node, identities.back(), features)) {
+        qDebug() << "Account" << acc->getName() << "received an ill-formed client discovery response from" << address << "about" << node;
+        return;
+    }
 }
 
 void Core::Squawk::onAccountRemovePresence(const QString& jid, const QString& name)
diff --git a/core/squawk.h b/core/squawk.h
index 3b8073b..ea17cdf 100644
--- a/core/squawk.h
+++ b/core/squawk.h
@@ -32,9 +32,10 @@
 #include "shared/message.h"
 #include "shared/global.h"
 #include "shared/clientinfo.h"
-#include "networkaccess.h"
 #include "external/simpleCrypt/simplecrypt.h"
-#include <core/storage/cache.h>
+
+#include <core/components/clientcache.h>
+#include <core/components/networkaccess.h>
 
 #ifdef WITH_KWALLET
 #include "passwordStorageEngines/kwallet.h"
@@ -137,7 +138,7 @@ private:
     Shared::Availability state;
     NetworkAccess network;
     bool isInitialized;
-    //Cache<Shared::ClientInfo> clientCache;
+    ClientCache clientCache;
 
 #ifdef WITH_KWALLET
     PSE::KWallet kwallet;
@@ -181,6 +182,8 @@ private slots:
     
     void onWalletOpened(bool success);
     void onWalletRejectPassword(const QString& login);
+
+    void onAccountInfoDiscovered(const QString& address, const QString& node, const std::list<Shared::Identity>& identities, const std::set<QString>& features);
     
 private:
     void readSettings();
diff --git a/core/storage/CMakeLists.txt b/core/storage/CMakeLists.txt
index 5d34b7c..2da3c67 100644
--- a/core/storage/CMakeLists.txt
+++ b/core/storage/CMakeLists.txt
@@ -1,10 +1,10 @@
 target_sources(squawk PRIVATE
     archive.cpp
     archive.h
-    storage.cpp
+    storage.hpp
     storage.h
     urlstorage.cpp
     urlstorage.h
-    cache.cpp
+    cache.hpp
     cache.h
 )
diff --git a/core/storage/cache.h b/core/storage/cache.h
index e5b1b88..5091561 100644
--- a/core/storage/cache.h
+++ b/core/storage/cache.h
@@ -18,6 +18,7 @@
 #define CORE_CACHE_H
 
 #include <map>
+#include <set>
 
 #include <QString>
 
@@ -39,12 +40,16 @@ public:
     void changeRecord(const QString& key, const T& value);
     void removeRecord(const QString& key);
     T getRecord(const QString& key) const;
+    bool checkRecord(const QString& key) const;
 
 private:
     Core::Storage<T> storage;
     std::map<QString, T>* cache;
+    std::set<QString>* abscent;
 };
 
 }
 
+#include "cache.hpp"
+
 #endif // CORE_CACHE_H
diff --git a/core/storage/cache.cpp b/core/storage/cache.hpp
similarity index 60%
rename from core/storage/cache.cpp
rename to core/storage/cache.hpp
index 71fe369..1604d66 100644
--- a/core/storage/cache.cpp
+++ b/core/storage/cache.hpp
@@ -14,21 +14,21 @@
 // You should have received a copy of the GNU General Public License
 // along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
+#ifndef CORE_CACHE_HPP
+#define CORE_CACHE_HPP
 #include "cache.h"
 
-#include <shared/clientinfo.h>
-
-template class Core::Cache<Shared::ClientInfo>;
-
 template <class T>
 Core::Cache<T>::Cache(const QString& name):
     storage(name),
-    cache(new std::map<QString, T> ()) {}
+    cache(new std::map<QString, T> ()),
+    abscent(new std::set<QString> ()) {}
 
 template <class T>
 Core::Cache<T>::~Cache() {
     close();
     delete cache;
+    delete abscent;
 }
 
 template <class T>
@@ -43,27 +43,60 @@ template <class T>
 void Core::Cache<T>::addRecord(const QString& key, const T& value) {
     storage.addRecord(key, value);
     cache->insert(std::make_pair(key, value));
+    abscent->erase(key);
 }
 
 template <class T>
 T Core::Cache<T>::getRecord(const QString& key) const {
     typename std::map<QString, T>::const_iterator itr = cache->find(key);
     if (itr == cache->end()) {
-        T value = storage.getRecord(key);
-        itr = cache->insert(std::make_pair(key, value)).first;
+        if (abscent->count(key) > 0) {
+            throw Archive::NotFound(key, storage.getName().toStdString());
+        }
+
+        try {
+            T value = storage.getRecord(key);
+            itr = cache->insert(std::make_pair(key, value)).first;
+        } catch (const Archive::NotFound& error) {
+            abscent->insert(key);
+            throw error;
+        }
     }
 
     return itr->second;
 }
 
+template<class T>
+bool Core::Cache<T>::checkRecord(const QString& key) const {
+    typename std::map<QString, T>::const_iterator itr = cache->find(key);
+    if (itr != cache->end())
+        return true;
+
+    if (abscent->count(key) > 0)
+        return false;
+
+    try {
+        T value = storage.getRecord(key);
+        itr = cache->insert(std::make_pair(key, value)).first;
+    } catch (const Archive::NotFound& error) {
+        return false;
+    }
+
+    return true;
+}
+
 template<typename T>
 void Core::Cache<T>::changeRecord(const QString& key, const T& value) {
-    storage.changeRecord(key, value);
+    storage.changeRecord(key, value);   //there is a non straightforward behaviour: if there was no element at the sorage it will be added
     cache->at(key) = value;
+    abscent->erase(key);                //so... this line here is to make it coherent with the storage
 }
 
 template<typename T>
 void Core::Cache<T>::removeRecord(const QString& key) {
     storage.removeRecord(key);
     cache->erase(key);
+    abscent->insert(key);
 }
+
+#endif //CORE_CACHE_HPP
diff --git a/core/storage/storage.h b/core/storage/storage.h
index 5942c0f..5d6dabc 100644
--- a/core/storage/storage.h
+++ b/core/storage/storage.h
@@ -43,6 +43,7 @@ public:
     void changeRecord(const QString& key, const T& value);
     void removeRecord(const QString& key);
     T getRecord(const QString& key) const;
+    QString getName() const;
     
     
 private:
@@ -54,4 +55,6 @@ private:
 
 }
 
+#include "storage.hpp"
+
 #endif // CORE_STORAGE_H
diff --git a/core/storage/storage.cpp b/core/storage/storage.hpp
similarity index 97%
rename from core/storage/storage.cpp
rename to core/storage/storage.hpp
index 99db8af..48e334d 100644
--- a/core/storage/storage.cpp
+++ b/core/storage/storage.hpp
@@ -15,16 +15,14 @@
  * You should have received a copy of the GNU General Public License
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
+#ifndef CORE_STORAGE_HPP
+#define CORE_STORAGE_HPP
 
 #include <QStandardPaths>
 #include <QDir>
 
 #include "storage.h"
 
-#include <shared/clientinfo.h>
-
-template class Core::Storage<Shared::ClientInfo>;
-
 template <class T>
 Core::Storage<T>::Storage(const QString& p_name):
     name(p_name),
@@ -202,3 +200,9 @@ void Core::Storage<T>::removeRecord(const QString& key)
         mdb_txn_commit(txn);
     }
 }
+
+template <class T>
+QString Core::Storage<T>::getName() const {
+    return name;}
+
+#endif //CORE_STORAGE_HPP
diff --git a/main/main.cpp b/main/main.cpp
index 3368e0a..086dbc0 100644
--- a/main/main.cpp
+++ b/main/main.cpp
@@ -19,6 +19,7 @@
 #include "shared/global.h"
 #include "shared/messageinfo.h"
 #include "shared/pathcheck.h"
+#include "shared/identity.h"
 #include "main/application.h"
 #include "core/signalcatcher.h"
 #include "core/squawk.h"
@@ -39,6 +40,9 @@ int main(int argc, char *argv[])
     qRegisterMetaType<Shared::VCard>("Shared::VCard");
     qRegisterMetaType<std::list<Shared::Message>>("std::list<Shared::Message>");
     qRegisterMetaType<std::list<Shared::MessageInfo>>("std::list<Shared::MessageInfo>");
+    qRegisterMetaType<std::list<Shared::MessageInfo>>("std::list<QString>");
+    qRegisterMetaType<std::list<Shared::MessageInfo>>("std::set<QString>");
+    qRegisterMetaType<std::list<Shared::MessageInfo>>("std::list<Shared::Identity>");
     qRegisterMetaType<QSet<QString>>("QSet<QString>");
     qRegisterMetaType<Shared::ConnectionState>("Shared::ConnectionState");
     qRegisterMetaType<Shared::Availability>("Shared::Availability");
diff --git a/shared/CMakeLists.txt b/shared/CMakeLists.txt
index 1e47618..5828bd4 100644
--- a/shared/CMakeLists.txt
+++ b/shared/CMakeLists.txt
@@ -20,4 +20,5 @@ target_sources(squawk PRIVATE
   pathcheck.h
   clientinfo.h
   clientinfo.cpp
+  identity.h
   )
diff --git a/shared/clientinfo.cpp b/shared/clientinfo.cpp
index 745bd54..3a2f441 100644
--- a/shared/clientinfo.cpp
+++ b/shared/clientinfo.cpp
@@ -23,8 +23,14 @@ Shared::ClientInfo::ClientInfo():
     capabilitiesNode(),
     capabilitiesVerification(),
     capabilitiesHash(),
+    specificPresence(),
     capabilitiesExtensions() {}
 
+QString Shared::ClientInfo::getId() const {
+    return capabilitiesNode + "/" + capabilitiesVerification;
+}
+
+
 QDataStream & Shared::ClientInfo::operator >> (QDataStream& stream) const {
     stream << name;
     stream << category;
diff --git a/shared/clientinfo.h b/shared/clientinfo.h
index 742e49a..55dd29f 100644
--- a/shared/clientinfo.h
+++ b/shared/clientinfo.h
@@ -29,6 +29,8 @@ class ClientInfo
 public:
     ClientInfo();
 
+    QString getId() const;
+
     QDataStream& operator << (QDataStream& stream);
     QDataStream& operator >> (QDataStream& stream) const;
 
@@ -39,6 +41,7 @@ public:
     QString capabilitiesNode;
     QString capabilitiesVerification;
     QString capabilitiesHash;
+    QString specificPresence;
     std::set<QString> capabilitiesExtensions;
 };
 
diff --git a/shared/identity.h b/shared/identity.h
new file mode 100644
index 0000000..6041e1a
--- /dev/null
+++ b/shared/identity.h
@@ -0,0 +1,35 @@
+/*
+ * Squawk messenger.
+ * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+ *
+ * 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/>.
+ */
+
+#ifndef SHARED_IDENTITY_H
+#define SHARED_IDENTITY_H
+
+#include <QString>
+
+namespace Shared {
+
+struct Identity {
+    QString category;
+    QString language;
+    QString name;
+    QString type;
+};
+
+}
+
+#endif //SHARED_IDENTITY_H

From c50cd1140e211d9864b1295b4115b43bf93d2e2a Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Thu, 25 Aug 2022 01:41:06 +0300
Subject: [PATCH 209/281] first ever received and cached client data!

---
 core/account.cpp                |  16 +++-
 core/account.h                  |   2 +-
 core/components/clientcache.cpp |  37 +++++++--
 core/components/clientcache.h   |   3 +-
 core/squawk.cpp                 |  18 ++--
 core/squawk.h                   |   2 +-
 shared/CMakeLists.txt           |   1 +
 shared/clientinfo.cpp           |  92 ++++++++++++++-------
 shared/clientinfo.h             |  23 ++++--
 shared/identity.cpp             | 142 ++++++++++++++++++++++++++++++++
 shared/identity.h               |  21 ++++-
 11 files changed, 297 insertions(+), 60 deletions(-)
 create mode 100644 shared/identity.cpp

diff --git a/core/account.cpp b/core/account.cpp
index 08321d3..48c48f9 100644
--- a/core/account.cpp
+++ b/core/account.cpp
@@ -634,16 +634,16 @@ void Core::Account::onDiscoveryInfoReceived(const QXmppDiscoveryIq& info)
         QString node = info.queryNode();
         if (!node.isEmpty()) {
             QStringList feats = info.features();
-            std::list<Shared::Identity> identities;
+            std::set<Shared::Identity> identities;
             std::set<QString> features(feats.begin(), feats.end());
             QList<QXmppDiscoveryIq::Identity> idents = info.identities();
             for (const QXmppDiscoveryIq::Identity& ident : idents) {
-                identities.emplace_back();
-                Shared::Identity& identity = identities.back();
+                Shared::Identity identity;
                 identity.category = ident.category();
                 identity.language = ident.language();
                 identity.name = ident.name();
                 identity.type = ident.type();
+                identities.insert(identity);
 
                 qDebug() << "    " << identity.name << identity.category << identity.type;
             }
@@ -655,6 +655,16 @@ void Core::Account::onDiscoveryInfoReceived(const QXmppDiscoveryIq& info)
     }
 }
 
+void Core::Account::discoverInfo(const QString& address, const QString& node) {
+    if (state == Shared::ConnectionState::connected) {
+        dm->requestInfo(address, node);
+    } else {
+        qDebug() << "An attempt to send a discover info by account" << name <<
+                    "sending request to" << address << "about node" << node <<
+                    "but the account is not in the connected state, skipping";
+    }
+}
+
 void Core::Account::handleDisconnection()
 {
     cm->setCarbonsEnabled(false);
diff --git a/core/account.h b/core/account.h
index 5651ad9..a409490 100644
--- a/core/account.h
+++ b/core/account.h
@@ -155,7 +155,7 @@ signals:
     void uploadFile(const QFileInfo& file, const QUrl& set, const QUrl& get, QMap<QString, QString> headers);
     void uploadFileError(const QString& jid, const QString& messageId, const QString& error);
     void needPassword();
-    void infoDiscovered(const QString& address, const QString& node, const std::list<Shared::Identity>& identities, const std::set<QString>& features);
+    void infoDiscovered(const QString& address, const QString& node, const std::set<Shared::Identity>& identities, const std::set<QString>& features);
     
 private:
     QString name;
diff --git a/core/components/clientcache.cpp b/core/components/clientcache.cpp
index 0259f9c..a054428 100644
--- a/core/components/clientcache.cpp
+++ b/core/components/clientcache.cpp
@@ -14,12 +14,14 @@
 // 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 "clientcache.h"
 
+#include <QDebug>
+
 Core::ClientCache::ClientCache():
     requested(),
-    cache("clients")
+    cache("clients"),
+    specific()
 {
     cache.open();
 }
@@ -39,9 +41,9 @@ bool Core::ClientCache::checkClient(const QString& node, const QString& ver, con
     QString id = node + "/" + ver;
     if (requested.count(id) == 0 && !cache.checkRecord(id)) {
         Shared::ClientInfo& info = requested.insert(std::make_pair(id, Shared::ClientInfo())).first->second;
-        info.capabilitiesNode = node;
-        info.capabilitiesVerification = ver;
-        info.capabilitiesHash = hash;
+        info.node = node;
+        info.verification = ver;
+        info.hash = hash;
         emit requestClientInfo(id);
         return false;
     }
@@ -49,7 +51,28 @@ bool Core::ClientCache::checkClient(const QString& node, const QString& ver, con
     return true;
 }
 
+bool Core::ClientCache::registerClientInfo (
+    const QString& sourceFullJid,
+    const QString& id,
+    const std::set<Shared::Identity>& identities,
+    const std::set<QString>& features)
+{
+    std::map<QString, Shared::ClientInfo>::iterator itr = requested.find(id);
+    if (itr != requested.end()) {
+        Shared::ClientInfo& info = itr->second;
+        info.identities = identities;
+        info.extensions = features;
 
-bool Core::ClientCache::registerClientInfo(const QString& sourceFullJid, const QString& id, const Shared::Identity& identity, const std::set<QString>& features) {
-
+        bool valid = info.valid();
+        if (valid) {
+            cache.addRecord(id, info);
+        } else {
+            info.specificPresence = sourceFullJid;
+            specific.insert(std::make_pair(sourceFullJid, info));
+        }
+        requested.erase(id);
+        return valid;
+    } else {
+        return false;
+    }
 }
diff --git a/core/components/clientcache.h b/core/components/clientcache.h
index 78c7e84..b2ff70d 100644
--- a/core/components/clientcache.h
+++ b/core/components/clientcache.h
@@ -43,11 +43,12 @@ signals:
 
 public slots:
     bool checkClient(const QString& node, const QString& ver, const QString& hash);
-    bool registerClientInfo(const QString& sourceFullJid, const QString& id, const Shared::Identity& identity, const std::set<QString>& features);
+    bool registerClientInfo(const QString& sourceFullJid, const QString& id, const std::set<Shared::Identity>& identities, const std::set<QString>& features);
 
 private:
     std::map<QString, Shared::ClientInfo> requested;
     Cache<Shared::ClientInfo> cache;
+    std::map<QString, Shared::ClientInfo> specific;
 };
 
 }
diff --git a/core/squawk.cpp b/core/squawk.cpp
index 5edcf58..3b976b0 100644
--- a/core/squawk.cpp
+++ b/core/squawk.cpp
@@ -320,25 +320,27 @@ void Core::Squawk::onAccountAddPresence(const QString& jid, const QString& name,
     Account* acc = static_cast<Account*>(sender());
     emit addPresence(acc->getName(), jid, name, data);
 
-    QString node = data["capabilityNode"].toString();
-    QString ver = data["capabilityVer"].toString();
-    QString hash = data["capabilityHash"].toString();
-    if (!clientCache.checkClient(node, ver, hash)) {
-        acc->discoverInfo(jid + "/" + name, node + "/" + ver);
+    //it's equal if a MUC sends its status with presence of the same jid (ex: muc@srv.im/muc@srv.im), it's not a client, so, no need to request
+    if (jid != name) {
+        QString node = data["capabilityNode"].toString();
+        QString ver = data["capabilityVer"].toString();
+        QString hash = data["capabilityHash"].toString();
+        if (!clientCache.checkClient(node, ver, hash)) {
+            acc->discoverInfo(jid + "/" + name, node + "/" + ver);
+        }
     }
 }
 
 void Core::Squawk::onAccountInfoDiscovered(
     const QString& address,
     const QString& node,
-    const std::list<Shared::Identity>& identities,
+    const std::set<Shared::Identity>& identities,
     const std::set<QString>& features)
 {
     Account* acc = static_cast<Account*>(sender());
 
-    if (identities.size() != 1 || clientCache.registerClientInfo(address, node, identities.back(), features)) {
+    if (!clientCache.registerClientInfo(address, node, identities, features)) {
         qDebug() << "Account" << acc->getName() << "received an ill-formed client discovery response from" << address << "about" << node;
-        return;
     }
 }
 
diff --git a/core/squawk.h b/core/squawk.h
index ea17cdf..cdaaf6e 100644
--- a/core/squawk.h
+++ b/core/squawk.h
@@ -183,7 +183,7 @@ private slots:
     void onWalletOpened(bool success);
     void onWalletRejectPassword(const QString& login);
 
-    void onAccountInfoDiscovered(const QString& address, const QString& node, const std::list<Shared::Identity>& identities, const std::set<QString>& features);
+    void onAccountInfoDiscovered(const QString& address, const QString& node, const std::set<Shared::Identity>& identities, const std::set<QString>& features);
     
 private:
     void readSettings();
diff --git a/shared/CMakeLists.txt b/shared/CMakeLists.txt
index 5828bd4..7495d4e 100644
--- a/shared/CMakeLists.txt
+++ b/shared/CMakeLists.txt
@@ -21,4 +21,5 @@ target_sources(squawk PRIVATE
   clientinfo.h
   clientinfo.cpp
   identity.h
+  identity.cpp
   )
diff --git a/shared/clientinfo.cpp b/shared/clientinfo.cpp
index 3a2f441..86af1c8 100644
--- a/shared/clientinfo.cpp
+++ b/shared/clientinfo.cpp
@@ -16,30 +16,42 @@
 
 #include "clientinfo.h"
 
+const std::map<QString, QCryptographicHash::Algorithm> Shared::ClientInfo::hashes = {
+    //md2 is missing
+    {"md5", QCryptographicHash::Md5},
+    {"sha-1", QCryptographicHash::Sha1},
+    {"sha-224", QCryptographicHash::Sha224},
+    {"sha-256", QCryptographicHash::Sha256},
+    {"sha-384", QCryptographicHash::Sha384},
+    {"sha-512", QCryptographicHash::Sha512},
+    //shake128 is missing
+    //shake256 is missing
+
+};
+
 Shared::ClientInfo::ClientInfo():
-    name(),
-    category(),
-    type(),
-    capabilitiesNode(),
-    capabilitiesVerification(),
-    capabilitiesHash(),
-    specificPresence(),
-    capabilitiesExtensions() {}
+    identities(),
+    extensions(),
+    node(),
+    verification(),
+    hash(),
+    specificPresence() {}
 
 QString Shared::ClientInfo::getId() const {
-    return capabilitiesNode + "/" + capabilitiesVerification;
+    return node + "/" + verification;
 }
 
 
 QDataStream & Shared::ClientInfo::operator >> (QDataStream& stream) const {
-    stream << name;
-    stream << category;
-    stream << type;
-    stream << capabilitiesNode;
-    stream << capabilitiesVerification;
-    stream << capabilitiesHash;
-    stream << (quint8)capabilitiesExtensions.size();
-    for (const QString& ext : capabilitiesExtensions) {
+    stream << node;
+    stream << verification;
+    stream << hash;
+    stream << (quint8)identities.size();
+    for (const Shared::Identity& identity : identities) {
+        stream << identity;
+    }
+    stream << (quint8)extensions.size();
+    for (const QString& ext : extensions) {
         stream << ext;
     }
 
@@ -47,29 +59,53 @@ QDataStream & Shared::ClientInfo::operator >> (QDataStream& stream) const {
 }
 
 QDataStream & Shared::ClientInfo::operator << (QDataStream& stream) {
-    stream >> name;
-    stream >> category;
-    stream >> type;
-    stream >> capabilitiesNode;
-    stream >> capabilitiesVerification;
-    stream >> capabilitiesHash;
+    stream >> node;
+    stream >> verification;
+    stream >> hash;
 
     quint8 size;
+    stream >> size;
+    for (quint8 i = 0; i < size; ++i) {
+        Shared::Identity identity;
+        stream >> identity;
+        identities.insert(identity);
+    }
+
     stream >> size;
     for (quint8 i = 0; i < size; ++i) {
         QString ext;
         stream >> ext;
-        capabilitiesExtensions.insert(ext);
+        extensions.insert(ext);
     }
 
     return stream;
 }
 
-QDataStream& operator<< (QDataStream& stream, const Shared::ClientInfo& image) {
-    image >> stream;
+bool Shared::ClientInfo::valid() const {
+    std::map<QString, QCryptographicHash::Algorithm>::const_iterator itr = hashes.find(hash);
+    if (itr == hashes.end()) {
+        return false;
+    }
+
+    QCryptographicHash calc(itr->second);
+    QString validationString = "";
+    for (const Identity& identity : identities) {
+        calc.addData((identity.category + "/" + identity.type + "/" + identity.language + "/" + identity.name + "<").toUtf8());
+    }
+    for (const QString& ext : extensions) {
+        calc.addData((ext + "<").toUtf8());
+    }
+
+    QString result = calc.result().toBase64();
+
+    return result == verification;
+}
+
+QDataStream& operator << (QDataStream& stream, const Shared::ClientInfo& info) {
+    info >> stream;
     return stream;
 }
-QDataStream& operator>> (QDataStream& stream, Shared::ClientInfo& image) {
-    image << stream;
+QDataStream& operator >> (QDataStream& stream, Shared::ClientInfo& info) {
+    info << stream;
     return stream;
 }
diff --git a/shared/clientinfo.h b/shared/clientinfo.h
index 55dd29f..53c7dd0 100644
--- a/shared/clientinfo.h
+++ b/shared/clientinfo.h
@@ -21,6 +21,9 @@
 
 #include <QDataStream>
 #include <QString>
+#include <QCryptographicHash>
+
+#include <shared/identity.h>
 
 namespace Shared {
 
@@ -30,24 +33,26 @@ public:
     ClientInfo();
 
     QString getId() const;
+    bool valid() const;
 
     QDataStream& operator << (QDataStream& stream);
     QDataStream& operator >> (QDataStream& stream) const;
 
 public:
-    QString name;
-    QString category;
-    QString type;
-    QString capabilitiesNode;
-    QString capabilitiesVerification;
-    QString capabilitiesHash;
+    std::set<Identity> identities;
+    std::set<QString> extensions;
+    QString node;
+    QString verification;
+    QString hash;
     QString specificPresence;
-    std::set<QString> capabilitiesExtensions;
+
+private:
+    static const std::map<QString, QCryptographicHash::Algorithm> hashes;
 };
 
 }
 
-QDataStream& operator<< (QDataStream& stream, const Shared::ClientInfo& image);
-QDataStream& operator>> (QDataStream& stream, Shared::ClientInfo& image);
+QDataStream& operator << (QDataStream& stream, const Shared::ClientInfo& info);
+QDataStream& operator >> (QDataStream& stream, Shared::ClientInfo& info);
 
 #endif // SHARED_CLIENTINFO_H
diff --git a/shared/identity.cpp b/shared/identity.cpp
new file mode 100644
index 0000000..a0a4f0a
--- /dev/null
+++ b/shared/identity.cpp
@@ -0,0 +1,142 @@
+/*
+ * Squawk messenger.
+ * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+ *
+ * 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 "identity.h"
+
+Shared::Identity::Identity():
+    category(),
+    type(),
+    language(),
+    name() {}
+
+bool Shared::Identity::operator==(const Shared::Identity& other) const {
+    return category == other.category && type == other.type && language == other.language && name == other.name;
+}
+
+bool Shared::Identity::operator!=(const Shared::Identity& other) const {
+    return category != other.category || type != other.type || language != other.language || name != other.name;
+}
+
+bool Shared::Identity::operator > (const Shared::Identity& other) const {
+    if (category > other.category) {
+        return true;
+    } else if (category < other.category) {
+        return false;
+    } else if (type > other.type) {
+        return true;
+    } else if (type < other.type) {
+        return false;
+    } else if (language > other.language) {
+        return true;
+    } else if (language < other.language) {
+        return false;
+    } else if (name > other.name) {
+        return true;
+    } else {
+        return false;
+    }
+}
+
+bool Shared::Identity::operator < (const Shared::Identity& other) const {
+    if (category < other.category) {
+        return true;
+    } else if (category > other.category) {
+        return false;
+    } else if (type < other.type) {
+        return true;
+    } else if (type > other.type) {
+        return false;
+    } else if (language < other.language) {
+        return true;
+    } else if (language > other.language) {
+        return false;
+    } else if (name < other.name) {
+        return true;
+    } else {
+        return false;
+    }
+}
+
+bool Shared::Identity::operator >= (const Shared::Identity& other) const {
+    if (category > other.category) {
+        return true;
+    } else if (category < other.category) {
+        return false;
+    } else if (type > other.type) {
+        return true;
+    } else if (type < other.type) {
+        return false;
+    } else if (language > other.language) {
+        return true;
+    } else if (language < other.language) {
+        return false;
+    } else if (name > other.name) {
+        return true;
+    } else if (name < other.name) {
+        return false;
+    } else {
+        return true;
+    }
+}
+
+bool Shared::Identity::operator <= (const Shared::Identity& other) const {
+    if (category < other.category) {
+        return true;
+    } else if (category > other.category) {
+        return false;
+    } else if (type < other.type) {
+        return true;
+    } else if (type > other.type) {
+        return false;
+    } else if (language < other.language) {
+        return true;
+    } else if (language > other.language) {
+        return false;
+    } else if (name < other.name) {
+        return true;
+    } else if (name > other.name) {
+        return false;
+    } else {
+        return true;
+    }
+}
+
+QDataStream & Shared::Identity::operator >> (QDataStream& stream) const {
+    stream << category;
+    stream << type;
+    stream << language;
+    stream << name;
+}
+
+QDataStream & Shared::Identity::operator << (QDataStream& stream) {
+    stream >> category;
+    stream >> type;
+    stream >> language;
+    stream >> name;
+}
+
+QDataStream & operator >> (QDataStream& stream, Shared::Identity& identity) {
+    identity << stream;
+    return stream;
+}
+
+QDataStream & operator << (QDataStream& stream, const Shared::Identity& identity) {
+    identity >> stream;
+    return stream;
+}
+
diff --git a/shared/identity.h b/shared/identity.h
index 6041e1a..c19102b 100644
--- a/shared/identity.h
+++ b/shared/identity.h
@@ -19,17 +19,34 @@
 #ifndef SHARED_IDENTITY_H
 #define SHARED_IDENTITY_H
 
+#include <QDataStream>
 #include <QString>
 
 namespace Shared {
 
-struct Identity {
+class Identity {
+public:
+    Identity();
+
+    QDataStream& operator << (QDataStream& stream);
+    QDataStream& operator >> (QDataStream& stream) const;
+
+    bool operator < (const Identity& other) const;
+    bool operator > (const Identity& other) const;
+    bool operator >= (const Identity& other) const;
+    bool operator <= (const Identity& other) const;
+    bool operator == (const Identity& other) const;
+    bool operator != (const Identity& other) const;
+public:
     QString category;
+    QString type;
     QString language;
     QString name;
-    QString type;
 };
 
 }
 
+QDataStream& operator << (QDataStream& stream, const Shared::Identity& identity);
+QDataStream& operator >> (QDataStream& stream, Shared::Identity& identity);
+
 #endif //SHARED_IDENTITY_H

From 7b2b7ee5d5ff55d8a7dd5fb05995e61337a237e7 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Fri, 26 Aug 2022 01:49:49 +0300
Subject: [PATCH 210/281] first thought about forms, discovering contact pep
 support

---
 core/account.cpp                | 23 +++++++++++--
 core/contact.cpp                | 15 +++++++-
 core/contact.h                  |  7 ++++
 core/handlers/rosterhandler.cpp | 18 ++++++++--
 core/handlers/rosterhandler.h   |  5 +--
 shared/CMakeLists.txt           |  4 +++
 shared/enums.h                  |  7 ++++
 shared/field.cpp                | 28 +++++++++++++++
 shared/field.h                  | 61 +++++++++++++++++++++++++++++++++
 shared/form.cpp                 | 23 +++++++++++++
 shared/form.h                   | 48 ++++++++++++++++++++++++++
 11 files changed, 230 insertions(+), 9 deletions(-)
 create mode 100644 shared/field.cpp
 create mode 100644 shared/field.h
 create mode 100644 shared/form.cpp
 create mode 100644 shared/form.h

diff --git a/core/account.cpp b/core/account.cpp
index 48c48f9..c3203b4 100644
--- a/core/account.cpp
+++ b/core/account.cpp
@@ -623,12 +623,13 @@ void Core::Account::onDiscoveryInfoReceived(const QXmppDiscoveryIq& info)
         bool pepSupported = false;
         for (const QXmppDiscoveryIq::Identity& identity : identities) {
             QString type = identity.type();
-            qDebug() << "    " << identity.category() << type;
-            if (type == "pep") {
+            QString category = identity.category();
+            qDebug() << "    " << category << type;
+            if (type == "pep" && category == "pubsub") {
                 pepSupported = true;
             }
         }
-        rh->setPepSupport(pepSupported);
+        rh->setPepSupport(pepSupported ? Shared::Support::supported : Shared::Support::unsupported);
     } else  {
         qDebug() << "Received info for account" << name << "about" << from;
         QString node = info.queryNode();
@@ -651,6 +652,22 @@ void Core::Account::onDiscoveryInfoReceived(const QXmppDiscoveryIq& info)
                 qDebug() << "    " << feat;
             }
             emit infoDiscovered(from, node, identities, features);
+        } else {
+            Contact* cont = rh->getContact(from);
+            if (cont != nullptr) {
+                qDebug() << "Received info for account" << name << "about" << from;
+                QList<QXmppDiscoveryIq::Identity> identities = info.identities();
+                bool pepSupported = false;
+                for (const QXmppDiscoveryIq::Identity& identity : identities) {
+                    QString type = identity.type();
+                    QString category = identity.category();
+                    qDebug() << "    " << category << type;
+                    if (type == "pep" && category == "pubsub") {
+                        pepSupported = true;
+                    }
+                }
+                cont->setPepSupport(pepSupported ? Shared::Support::supported : Shared::Support::unsupported);
+            }
         }
     }
 }
diff --git a/core/contact.cpp b/core/contact.cpp
index 0471a5c..3030f4d 100644
--- a/core/contact.cpp
+++ b/core/contact.cpp
@@ -22,7 +22,8 @@
 Core::Contact::Contact(const QString& pJid, const QString& account, QObject* parent):
     RosterItem(pJid, account, parent),
     groups(),
-    subscriptionState(Shared::SubscriptionState::unknown)
+    subscriptionState(Shared::SubscriptionState::unknown),
+    pep(Shared::Support::unknown)
 {
 }
 
@@ -98,3 +99,15 @@ void Core::Contact::handlePresence(const QXmppPresence& pres)
         }      
     }
 }
+
+void Core::Contact::setPepSupport(Shared::Support support) {
+    if (pep != support) {
+        pep = support;
+    }
+}
+
+Shared::Support Core::Contact::getPepSupport() const {
+    return pep;}
+
+
+
diff --git a/core/contact.h b/core/contact.h
index aa81010..01c082f 100644
--- a/core/contact.h
+++ b/core/contact.h
@@ -21,8 +21,11 @@
 
 #include <QObject>
 #include <QSet>
+
 #include "rosteritem.h"
 
+#include <shared/enums.h>
+
 namespace Core {
 
 class Contact : public RosterItem
@@ -38,6 +41,9 @@ public:
     
     void setSubscriptionState(Shared::SubscriptionState state);
     Shared::SubscriptionState getSubscriptionState() const;
+    void setPepSupport(Shared::Support support);
+    Shared::Support getPepSupport() const;
+
     void handlePresence(const QXmppPresence & pres) override;
 
 signals:
@@ -48,6 +54,7 @@ signals:
 private:
     QSet<QString> groups;
     Shared::SubscriptionState subscriptionState;
+    Shared::Support pep;
 };
 }
 
diff --git a/core/handlers/rosterhandler.cpp b/core/handlers/rosterhandler.cpp
index 6a233d6..748f0f7 100644
--- a/core/handlers/rosterhandler.cpp
+++ b/core/handlers/rosterhandler.cpp
@@ -27,7 +27,7 @@ Core::RosterHandler::RosterHandler(Core::Account* account):
     groups(),
     queuedContacts(),
     outOfRosterContacts(),
-    pepSupport(false)
+    pepSupport(Shared::Support::unknown)
 {
     connect(acc->rm, &QXmppRosterManager::rosterReceived, this, &RosterHandler::onRosterReceived);
     connect(acc->rm, &QXmppRosterManager::itemAdded, this, &RosterHandler::onRosterItemAdded);
@@ -111,6 +111,10 @@ void Core::RosterHandler::addedAccount(const QString& jid)
         if (grCount == 0) {
             emit acc->addContact(jid, "", cData);
         }
+        if (pepSupport == Shared::Support::supported) {
+            acc->dm->requestInfo(jid);
+            //acc->dm->requestItems(jid);
+        }
         handleNewContact(contact);
     }
 }
@@ -588,13 +592,21 @@ void Core::RosterHandler::handleOffline()
         pair.second->clearArchiveRequests();
         pair.second->downgradeDatabaseState();
     }
-    setPepSupport(false);
+    setPepSupport(Shared::Support::unknown);
 }
 
 
-void Core::RosterHandler::setPepSupport(bool support)
+void Core::RosterHandler::setPepSupport(Shared::Support support)
 {
     if (pepSupport != support) {
         pepSupport = support;
+
+        if (pepSupport == Shared::Support::supported) {
+            for (const std::pair<const QString, Contact*>& pair : contacts) {
+                if (pair.second->getPepSupport() == Shared::Support::unknown) {
+                    acc->dm->requestInfo(pair.first);
+                }
+            }
+        }
     }
 }
diff --git a/core/handlers/rosterhandler.h b/core/handlers/rosterhandler.h
index 02bbc98..7be38e1 100644
--- a/core/handlers/rosterhandler.h
+++ b/core/handlers/rosterhandler.h
@@ -32,6 +32,7 @@
 #include <QXmppMucManager.h>
 #include <QXmppRosterIq.h>
 
+#include <shared/enums.h>
 #include <shared/message.h>
 #include <core/contact.h>
 #include <core/conference.h>
@@ -64,7 +65,7 @@ public:
     
     void storeConferences();
     void clearConferences();
-    void setPepSupport(bool support);
+    void setPepSupport(Shared::Support support);
     
 private slots:
     void onRosterReceived();
@@ -108,7 +109,7 @@ private:
     std::map<QString, std::set<QString>> groups;
     std::map<QString, QString> queuedContacts;
     std::set<QString> outOfRosterContacts;
-    bool pepSupport;
+    Shared::Support pepSupport;
 };
 
 }
diff --git a/shared/CMakeLists.txt b/shared/CMakeLists.txt
index 7495d4e..51c599f 100644
--- a/shared/CMakeLists.txt
+++ b/shared/CMakeLists.txt
@@ -22,4 +22,8 @@ target_sources(squawk PRIVATE
   clientinfo.cpp
   identity.h
   identity.cpp
+  form.h
+  form.cpp
+  field.h
+  field.cpp
   )
diff --git a/shared/enums.h b/shared/enums.h
index cb41443..273a22d 100644
--- a/shared/enums.h
+++ b/shared/enums.h
@@ -117,5 +117,12 @@ Q_ENUM_NS(AccountPassword)
 static const AccountPassword AccountPasswordHighest = AccountPassword::kwallet;
 static const AccountPassword AccountPasswordLowest = AccountPassword::plain;
 
+enum class Support {
+    unknown,
+    supported,
+    unsupported
+};
+Q_ENUM_NS(Support)
+
 }
 #endif // SHARED_ENUMS_H
diff --git a/shared/field.cpp b/shared/field.cpp
new file mode 100644
index 0000000..f469646
--- /dev/null
+++ b/shared/field.cpp
@@ -0,0 +1,28 @@
+// Squawk messenger. 
+// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+// 
+// 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 "field.h"
+
+Shared::Field::Field(Shared::Field::Type fieldTtype):
+    type(fieldTtype),
+    key(),
+    label(),
+    description(),
+    required(false),
+    options(),
+    value()
+{
+}
diff --git a/shared/field.h b/shared/field.h
new file mode 100644
index 0000000..e7c9a02
--- /dev/null
+++ b/shared/field.h
@@ -0,0 +1,61 @@
+// Squawk messenger. 
+// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+// 
+// 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/>.
+
+#ifndef SHARED_FIELD_H
+#define SHARED_FIELD_H
+
+#include <list>
+
+#include <QString>
+#include <QVariant>
+
+namespace Shared {
+
+/**
+ * @todo write docs
+ */
+class Field
+{
+public:
+    enum class Type {
+        boolean,
+        fixed,
+        hidden,
+        jidMultiple,
+        jidSingle,
+        listMultiple,
+        listSingle,
+        textMultiple,
+        textPrivate,
+        textSingle
+    };
+
+    Field(Type fieldType);
+
+public:
+    const Type type;
+    QString key;
+    QString label;
+    QString description;
+    bool required;
+    std::list<std::pair<QString, QString>> options;
+    QVariant value;
+
+};
+
+}
+
+#endif // SHARED_FIELD_H
diff --git a/shared/form.cpp b/shared/form.cpp
new file mode 100644
index 0000000..bf309ca
--- /dev/null
+++ b/shared/form.cpp
@@ -0,0 +1,23 @@
+// Squawk messenger. 
+// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+// 
+// 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 "form.h"
+
+Shared::Form::Form(Shared::Form::Type formType):
+    type(formType),
+    title(),
+    instructions(),
+    fields() {}
diff --git a/shared/form.h b/shared/form.h
new file mode 100644
index 0000000..08c8c95
--- /dev/null
+++ b/shared/form.h
@@ -0,0 +1,48 @@
+// Squawk messenger. 
+// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+// 
+// 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/>.
+
+#ifndef SHARED_FORM_H
+#define SHARED_FORM_H
+
+#include <list>
+
+#include <shared/field.h>
+
+namespace Shared {
+
+class Form
+{
+public:
+    enum class Type {
+        none,
+        form,
+        submit,
+        cancel,
+        result
+    };
+
+    Form(Type formType);
+
+public:
+    const Type type;
+    QString title;
+    QString instructions;
+    std::list<Field> fields;
+};
+
+}
+
+#endif // SHARED_FORM_H

From b6ba022bffd435746087814a917afe63b875e7a6 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Sat, 27 Aug 2022 14:39:24 +0300
Subject: [PATCH 211/281] removed own VCard request at the start if the
 presence doesn't show that the avatar changed, little refactoring

---
 CHANGELOG.md                       |   1 +
 core/account.cpp                   | 121 +++-----------------------
 core/account.h                     |   9 +-
 core/handlers/CMakeLists.txt       |   2 +
 core/handlers/discoveryhandler.cpp | 135 +++++++++++++++++++++++++++++
 core/handlers/discoveryhandler.h   |  45 ++++++++++
 core/handlers/rosterhandler.cpp    |  24 ++---
 core/handlers/rosterhandler.h      |   3 +-
 core/handlers/vcardhandler.cpp     |   2 +-
 core/handlers/vcardhandler.h       |   2 +-
 10 files changed, 215 insertions(+), 129 deletions(-)
 create mode 100644 core/handlers/discoveryhandler.cpp
 create mode 100644 core/handlers/discoveryhandler.h

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7107e26..a36da73 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,7 @@
 - deactivated accounts now don't appear in combobox of "Add contact" and "Join conference" dialogues
 - all of the expandable roster items now get saved between launches
 - settings file on the disk is not rewritten every roster element expansion or collapse
+- removed unnecessary own vcard request at sturtup (used to do it to discover my own avatar)
 
 ### New features
 - Now you can enable tray icon from settings!
diff --git a/core/account.cpp b/core/account.cpp
index c3203b4..1c3314f 100644
--- a/core/account.cpp
+++ b/core/account.cpp
@@ -18,6 +18,7 @@
 
 #include "account.h"
 #include <QXmppMessage.h>
+
 #include <QDateTime>
 
 using namespace Core;
@@ -44,12 +45,13 @@ Account::Account(const QString& p_login, const QString& p_server, const QString&
     network(p_net),
     passwordType(Shared::AccountPassword::plain),
     lastError(Error::none),
-    pepSupport(false),
+    pepSupport(Shared::Support::unknown),
     active(p_active),
     notReadyPassword(false),
     mh(new MessageHandler(this)),
     rh(new RosterHandler(this)),
-    vh(new VCardHandler(this))
+    vh(new VCardHandler(this)),
+    dh(new DiscoveryHandler(this))
 {
     config.setUser(p_login);
     config.setDomain(p_server);
@@ -79,9 +81,6 @@ Account::Account(const QString& p_login, const QString& p_server, const QString&
     QObject::connect(um, &QXmppUploadRequestManager::slotReceived, mh, &MessageHandler::onUploadSlotReceived);
     QObject::connect(um, &QXmppUploadRequestManager::requestFailed, mh, &MessageHandler::onUploadSlotRequestFailed);
     
-    QObject::connect(dm, &QXmppDiscoveryManager::itemsReceived, this, &Account::onDiscoveryItemsReceived);
-    QObject::connect(dm, &QXmppDiscoveryManager::infoReceived, this, &Account::onDiscoveryInfoReceived);
-    
     QObject::connect(network, &NetworkAccess::uploadFileComplete, mh, &MessageHandler::onUploadFileComplete);
     QObject::connect(network, &NetworkAccess::downloadFileComplete, mh, &MessageHandler::onDownloadFileComplete);
     QObject::connect(network, &NetworkAccess::loadFileError, mh, &MessageHandler::onLoadFileError);
@@ -93,8 +92,6 @@ Account::Account(const QString& p_login, const QString& p_server, const QString&
     QObject::connect(reconnectTimer, &QTimer::timeout, this, &Account::onReconnectTimer);
     
     if (name == "Test") {
-        qDebug() << "Presence capabilities: " << presence.capabilityNode();
-
         QXmppLogger* logger = new QXmppLogger(this);
         logger->setLoggingType(QXmppLogger::SignalLogging);
         client.setLogger(logger);
@@ -263,9 +260,8 @@ void Core::Account::onPresenceReceived(const QXmppPresence& p_presence)
     if (jid == getBareJid()) {
         if (resource == getResource()) {
             emit availabilityChanged(static_cast<Shared::Availability>(p_presence.availableStatusType()));
-        } else {
-            vh->handleOtherPresenceOfMyAccountChange(p_presence);
         }
+        vh->handlePresenceOfMyAccountChange(p_presence);
     } else {
         RosterItem* item = rh->getRosterItem(jid);
         if (item != 0) {
@@ -574,104 +570,6 @@ void Core::Account::setRoomJoined(const QString& jid, bool joined)
     conf->setJoined(joined);
 }
 
-void Core::Account::onDiscoveryItemsReceived(const QXmppDiscoveryIq& items)
-{
-    if (items.from() == getServer()) {
-        std::set<QString> needToRequest;
-        qDebug() << "Server items list received for account " << name << ":";
-        for (QXmppDiscoveryIq::Item item : items.items()) {
-            QString jid = item.jid();
-            if (jid != getServer()) {
-                qDebug() << "     Node" << jid;
-                needToRequest.insert(jid);
-            } else {
-                qDebug() << "    " << item.node().toStdString().c_str();
-            }
-        }
-
-        for (const QString& jid : needToRequest) {
-            dm->requestInfo(jid);
-        }
-    }
-}
-
-void Core::Account::onDiscoveryInfoReceived(const QXmppDiscoveryIq& info)
-{
-    QString from = info.from();
-    if (from == getServer()) {
-        bool enableCC = false;
-        qDebug() << "Server info received for account" << name;
-        QStringList features = info.features();
-        qDebug() << "List of supported features of the server " << getServer() << ":";
-        for (const QString& feature : features) {
-            qDebug() << "    " << feature.toStdString().c_str();
-            if (feature == "urn:xmpp:carbons:2") {
-                enableCC = true;
-            }
-        }
-
-        if (enableCC) {
-            qDebug() << "Enabling carbon copies for account" << name;
-            cm->setCarbonsEnabled(true);
-        }
-
-        qDebug() << "Requesting account" << name << "capabilities";
-        dm->requestInfo(getBareJid());
-    } else if (from == getBareJid()) {
-        qDebug() << "Received capabilities for account" << name << ":";
-        QList<QXmppDiscoveryIq::Identity> identities = info.identities();
-        bool pepSupported = false;
-        for (const QXmppDiscoveryIq::Identity& identity : identities) {
-            QString type = identity.type();
-            QString category = identity.category();
-            qDebug() << "    " << category << type;
-            if (type == "pep" && category == "pubsub") {
-                pepSupported = true;
-            }
-        }
-        rh->setPepSupport(pepSupported ? Shared::Support::supported : Shared::Support::unsupported);
-    } else  {
-        qDebug() << "Received info for account" << name << "about" << from;
-        QString node = info.queryNode();
-        if (!node.isEmpty()) {
-            QStringList feats = info.features();
-            std::set<Shared::Identity> identities;
-            std::set<QString> features(feats.begin(), feats.end());
-            QList<QXmppDiscoveryIq::Identity> idents = info.identities();
-            for (const QXmppDiscoveryIq::Identity& ident : idents) {
-                Shared::Identity identity;
-                identity.category = ident.category();
-                identity.language = ident.language();
-                identity.name = ident.name();
-                identity.type = ident.type();
-                identities.insert(identity);
-
-                qDebug() << "    " << identity.name << identity.category << identity.type;
-            }
-            for (const QString& feat : features) {
-                qDebug() << "    " << feat;
-            }
-            emit infoDiscovered(from, node, identities, features);
-        } else {
-            Contact* cont = rh->getContact(from);
-            if (cont != nullptr) {
-                qDebug() << "Received info for account" << name << "about" << from;
-                QList<QXmppDiscoveryIq::Identity> identities = info.identities();
-                bool pepSupported = false;
-                for (const QXmppDiscoveryIq::Identity& identity : identities) {
-                    QString type = identity.type();
-                    QString category = identity.category();
-                    qDebug() << "    " << category << type;
-                    if (type == "pep" && category == "pubsub") {
-                        pepSupported = true;
-                    }
-                }
-                cont->setPepSupport(pepSupported ? Shared::Support::supported : Shared::Support::unsupported);
-            }
-        }
-    }
-}
-
 void Core::Account::discoverInfo(const QString& address, const QString& node) {
     if (state == Shared::ConnectionState::connected) {
         dm->requestInfo(address, node);
@@ -682,8 +580,17 @@ void Core::Account::discoverInfo(const QString& address, const QString& node) {
     }
 }
 
+void Core::Account::setPepSupport(Shared::Support support)
+{
+    if (support != pepSupport) {
+        pepSupport = support;
+        emit pepSupportChanged(pepSupport);
+    }
+}
+
 void Core::Account::handleDisconnection()
 {
+    setPepSupport(Shared::Support::unknown);
     cm->setCarbonsEnabled(false);
     rh->handleOffline();
     vh->handleOffline();
diff --git a/core/account.h b/core/account.h
index a409490..0ce39e5 100644
--- a/core/account.h
+++ b/core/account.h
@@ -52,6 +52,7 @@
 #include "handlers/messagehandler.h"
 #include "handlers/rosterhandler.h"
 #include "handlers/vcardhandler.h"
+#include "handlers/discoveryhandler.h"
 
 namespace Core
 {
@@ -62,6 +63,7 @@ class Account : public QObject
     friend class MessageHandler;
     friend class RosterHandler;
     friend class VCardHandler;
+    friend class DiscoveryHandler;
 public:
     enum class Error {
         authentication,
@@ -156,6 +158,7 @@ signals:
     void uploadFileError(const QString& jid, const QString& messageId, const QString& error);
     void needPassword();
     void infoDiscovered(const QString& address, const QString& node, const std::set<Shared::Identity>& identities, const std::set<QString>& features);
+    void pepSupportChanged(Shared::Support support);
     
 private:
     QString name;
@@ -179,13 +182,14 @@ private:
     NetworkAccess* network;
     Shared::AccountPassword passwordType;
     Error lastError;
-    bool pepSupport;
+    Shared::Support pepSupport;
     bool active;
     bool notReadyPassword;
     
     MessageHandler* mh;
     RosterHandler* rh;
     VCardHandler* vh;
+    DiscoveryHandler* dh;
     
 private slots:
     void onClientStateChange(QXmppClient::State state);
@@ -199,13 +203,12 @@ private slots:
 
     void onMamLog(QXmppLogger::MessageType type, const QString &msg);
 
-    void onDiscoveryItemsReceived (const QXmppDiscoveryIq& items);
-    void onDiscoveryInfoReceived (const QXmppDiscoveryIq& info);
     void onContactHistoryResponse(const std::list<Shared::Message>& list, bool last);
   
 private:
     void handleDisconnection();
     void onReconnectTimer();
+    void setPepSupport(Shared::Support support);
 };
 }
 
diff --git a/core/handlers/CMakeLists.txt b/core/handlers/CMakeLists.txt
index fb67953..255d7fa 100644
--- a/core/handlers/CMakeLists.txt
+++ b/core/handlers/CMakeLists.txt
@@ -5,4 +5,6 @@ target_sources(squawk PRIVATE
   rosterhandler.h
   vcardhandler.cpp
   vcardhandler.h
+  discoveryhandler.cpp
+  discoveryhandler.h
   )
diff --git a/core/handlers/discoveryhandler.cpp b/core/handlers/discoveryhandler.cpp
new file mode 100644
index 0000000..e562e68
--- /dev/null
+++ b/core/handlers/discoveryhandler.cpp
@@ -0,0 +1,135 @@
+// Squawk messenger. 
+// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+// 
+// 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 "discoveryhandler.h"
+#include "core/account.h"
+
+#include <QDebug>
+
+Core::DiscoveryHandler::DiscoveryHandler(Core::Account* account):
+    QObject(),
+    acc(account)
+{
+    QObject::connect(acc->dm, &QXmppDiscoveryManager::itemsReceived, this, &DiscoveryHandler::onItemsReceived);
+    QObject::connect(acc->dm, &QXmppDiscoveryManager::infoReceived, this, &DiscoveryHandler::onInfoReceived);
+
+    acc->dm->setClientType("pc");
+    acc->dm->setClientCategory("client");
+    acc->dm->setClientName(qApp->applicationDisplayName() + " " + qApp->applicationVersion());
+    acc->dm->setClientCapabilitiesNode("https://git.macaw.me/blue/squawk");
+}
+
+void Core::DiscoveryHandler::onItemsReceived(const QXmppDiscoveryIq& items)
+{
+    QString server = acc->getServer();
+    if (items.from() == server) {
+        std::set<QString> needToRequest;
+        qDebug() << "Server items list received for account " << acc->getName() << ":";
+        for (QXmppDiscoveryIq::Item item : items.items()) {
+            QString jid = item.jid();
+            if (jid != server) {
+                qDebug() << "     Node" << jid;
+                needToRequest.insert(jid);
+            } else {
+                qDebug() << "    " << item.node().toStdString().c_str();
+            }
+        }
+
+        for (const QString& jid : needToRequest) {
+            acc->dm->requestInfo(jid);
+        }
+    }
+}
+
+void Core::DiscoveryHandler::onInfoReceived(const QXmppDiscoveryIq& info)
+{
+    QString from = info.from();
+    QString server = acc->getServer();
+    QString accName = acc->getName();
+    QString bareJid = acc->getBareJid();
+    if (from == server) {
+        bool enableCC = false;
+        qDebug() << "Server info received for account" << accName;
+        QStringList features = info.features();
+        qDebug() << "List of supported features of the server " << server << ":";
+        for (const QString& feature : features) {
+            qDebug() << "    " << feature.toStdString().c_str();
+            if (feature == "urn:xmpp:carbons:2") {
+                enableCC = true;
+            }
+        }
+
+        if (enableCC) {
+            qDebug() << "Enabling carbon copies for account" << accName;
+            acc->cm->setCarbonsEnabled(true);
+        }
+
+        qDebug() << "Requesting account" << accName << "capabilities";
+        acc->dm->requestInfo(bareJid);
+    } else if (from == bareJid) {
+        qDebug() << "Received capabilities for account" << accName << ":";
+        QList<QXmppDiscoveryIq::Identity> identities = info.identities();
+        bool pepSupported = false;
+        for (const QXmppDiscoveryIq::Identity& identity : identities) {
+            QString type = identity.type();
+            QString category = identity.category();
+            qDebug() << "    " << category << type;
+            if (type == "pep" && category == "pubsub") {
+                pepSupported = true;
+            }
+        }
+        acc->setPepSupport(pepSupported ? Shared::Support::supported : Shared::Support::unsupported);
+    } else  {
+        qDebug() << "Received info for account" << accName << "about" << from;
+        QString node = info.queryNode();
+        if (!node.isEmpty()) {
+            QStringList feats = info.features();
+            std::set<Shared::Identity> identities;
+            std::set<QString> features(feats.begin(), feats.end());
+            QList<QXmppDiscoveryIq::Identity> idents = info.identities();
+            for (const QXmppDiscoveryIq::Identity& ident : idents) {
+                Shared::Identity identity;
+                identity.category = ident.category();
+                identity.language = ident.language();
+                identity.name = ident.name();
+                identity.type = ident.type();
+                identities.insert(identity);
+
+                qDebug() << "    " << identity.name << identity.category << identity.type;
+            }
+            for (const QString& feat : features) {
+                qDebug() << "    " << feat;
+            }
+            emit acc->infoDiscovered(from, node, identities, features);
+        } else {
+            Contact* cont = acc->rh->getContact(from);
+            if (cont != nullptr) {
+                qDebug() << "Received info for account" << accName << "about" << from;
+                QList<QXmppDiscoveryIq::Identity> identities = info.identities();
+                bool pepSupported = false;
+                for (const QXmppDiscoveryIq::Identity& identity : identities) {
+                    QString type = identity.type();
+                    QString category = identity.category();
+                    qDebug() << "    " << category << type;
+                    if (type == "pep" && category == "pubsub") {
+                        pepSupported = true;
+                    }
+                }
+                cont->setPepSupport(pepSupported ? Shared::Support::supported : Shared::Support::unsupported);
+            }
+        }
+    }
+}
diff --git a/core/handlers/discoveryhandler.h b/core/handlers/discoveryhandler.h
new file mode 100644
index 0000000..3129219
--- /dev/null
+++ b/core/handlers/discoveryhandler.h
@@ -0,0 +1,45 @@
+// Squawk messenger. 
+// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+// 
+// 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/>.
+
+#ifndef CORE_DISCOVERYHANDLER_H
+#define CORE_DISCOVERYHANDLER_H
+
+#include <QObject>
+
+#include <QXmppDiscoveryManager.h>
+#include <QXmppDiscoveryIq.h>
+
+namespace Core {
+class Account;
+
+class DiscoveryHandler : public QObject
+{
+    Q_OBJECT
+public:
+    DiscoveryHandler(Account* account);
+    ~DiscoveryHandler();
+
+private slots:
+    void onItemsReceived (const QXmppDiscoveryIq& items);
+    void onInfoReceived (const QXmppDiscoveryIq& info);
+
+private:
+    Account* acc;
+};
+
+}
+
+#endif // CORE_DISCOVERYHANDLER_H
diff --git a/core/handlers/rosterhandler.cpp b/core/handlers/rosterhandler.cpp
index 748f0f7..1a61440 100644
--- a/core/handlers/rosterhandler.cpp
+++ b/core/handlers/rosterhandler.cpp
@@ -26,8 +26,7 @@ Core::RosterHandler::RosterHandler(Core::Account* account):
     conferences(),
     groups(),
     queuedContacts(),
-    outOfRosterContacts(),
-    pepSupport(Shared::Support::unknown)
+    outOfRosterContacts()
 {
     connect(acc->rm, &QXmppRosterManager::rosterReceived, this, &RosterHandler::onRosterReceived);
     connect(acc->rm, &QXmppRosterManager::itemAdded, this, &RosterHandler::onRosterItemAdded);
@@ -37,6 +36,8 @@ Core::RosterHandler::RosterHandler(Core::Account* account):
     
     connect(acc->mm, &QXmppMucManager::roomAdded, this, &RosterHandler::onMucRoomAdded);
     connect(acc->bm, &QXmppBookmarkManager::bookmarksReceived, this, &RosterHandler::bookmarksReceived);
+
+    connect(acc, &Account::pepSupportChanged, this, &RosterHandler::onPepSupportedChanged);
 }
 
 Core::RosterHandler::~RosterHandler()
@@ -52,8 +53,6 @@ Core::RosterHandler::~RosterHandler()
 
 void Core::RosterHandler::onRosterReceived()
 {
-    acc->requestVCard(acc->getBareJid());         //TODO need to make sure server actually supports vCards
-    
     QStringList bj = acc->rm->getRosterBareJids();
     for (int i = 0; i < bj.size(); ++i) {
         const QString& jid = bj[i];
@@ -111,7 +110,7 @@ void Core::RosterHandler::addedAccount(const QString& jid)
         if (grCount == 0) {
             emit acc->addContact(jid, "", cData);
         }
-        if (pepSupport == Shared::Support::supported) {
+        if (acc->pepSupport == Shared::Support::supported) {
             acc->dm->requestInfo(jid);
             //acc->dm->requestItems(jid);
         }
@@ -592,20 +591,15 @@ void Core::RosterHandler::handleOffline()
         pair.second->clearArchiveRequests();
         pair.second->downgradeDatabaseState();
     }
-    setPepSupport(Shared::Support::unknown);
 }
 
 
-void Core::RosterHandler::setPepSupport(Shared::Support support)
+void Core::RosterHandler::onPepSupportedChanged(Shared::Support support)
 {
-    if (pepSupport != support) {
-        pepSupport = support;
-
-        if (pepSupport == Shared::Support::supported) {
-            for (const std::pair<const QString, Contact*>& pair : contacts) {
-                if (pair.second->getPepSupport() == Shared::Support::unknown) {
-                    acc->dm->requestInfo(pair.first);
-                }
+    if (support == Shared::Support::supported) {
+        for (const std::pair<const QString, Contact*>& pair : contacts) {
+            if (pair.second->getPepSupport() == Shared::Support::unknown) {
+                acc->dm->requestInfo(pair.first);
             }
         }
     }
diff --git a/core/handlers/rosterhandler.h b/core/handlers/rosterhandler.h
index 7be38e1..6a56b15 100644
--- a/core/handlers/rosterhandler.h
+++ b/core/handlers/rosterhandler.h
@@ -65,7 +65,6 @@ public:
     
     void storeConferences();
     void clearConferences();
-    void setPepSupport(Shared::Support support);
     
 private slots:
     void onRosterReceived();
@@ -89,6 +88,7 @@ private slots:
     void onContactNameChanged(const QString& name);
     void onContactSubscriptionStateChanged(Shared::SubscriptionState state);
     void onContactAvatarChanged(Shared::Avatar, const QString& path);
+    void onPepSupportedChanged(Shared::Support support);
     
 private:
     void addNewRoom(const QString& jid, const QString& nick, const QString& roomName, bool autoJoin);
@@ -109,7 +109,6 @@ private:
     std::map<QString, std::set<QString>> groups;
     std::map<QString, QString> queuedContacts;
     std::set<QString> outOfRosterContacts;
-    Shared::Support pepSupport;
 };
 
 }
diff --git a/core/handlers/vcardhandler.cpp b/core/handlers/vcardhandler.cpp
index 2a8d65c..24cb6ee 100644
--- a/core/handlers/vcardhandler.cpp
+++ b/core/handlers/vcardhandler.cpp
@@ -228,7 +228,7 @@ void Core::VCardHandler::requestVCard(const QString& jid)
     }
 }
 
-void Core::VCardHandler::handleOtherPresenceOfMyAccountChange(const QXmppPresence& p_presence)
+void Core::VCardHandler::handlePresenceOfMyAccountChange(const QXmppPresence& p_presence)
 {
     if (!ownVCardRequestInProgress) {
         switch (p_presence.vCardUpdateType()) {
diff --git a/core/handlers/vcardhandler.h b/core/handlers/vcardhandler.h
index 4febb69..ee61d0a 100644
--- a/core/handlers/vcardhandler.h
+++ b/core/handlers/vcardhandler.h
@@ -44,7 +44,7 @@ public:
 
     void handleOffline();
     void requestVCard(const QString& jid);
-    void handleOtherPresenceOfMyAccountChange(const QXmppPresence& p_presence);
+    void handlePresenceOfMyAccountChange(const QXmppPresence& p_presence);
     void uploadVCard(const Shared::VCard& card);
     QString getAvatarPath() const;
 

From 87973b3b67fc87174333f906a119af49cad91e93 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Mon, 29 Aug 2022 21:34:25 +0300
Subject: [PATCH 212/281] first attempts to build against upstream qxmpp

---
 CMakeLists.txt                     | 83 ++++++++++++++++++++++--------
 core/account.cpp                   | 11 ++++
 core/account.h                     |  6 +++
 core/handlers/discoveryhandler.cpp |  5 ++
 4 files changed, 84 insertions(+), 21 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index bbae079..d9d0dab 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,10 +1,11 @@
-cmake_minimum_required(VERSION 3.4)
+cmake_minimum_required(VERSION 3.5)
 project(squawk VERSION 0.2.3 LANGUAGES CXX)
 
 cmake_policy(SET CMP0076 NEW)
 cmake_policy(SET CMP0079 NEW)
 set(CMAKE_CXX_STANDARD 17)
 
+set(QT_VERSION_MAJOR 5)
 set(CMAKE_AUTOMOC ON)
 set(CMAKE_AUTOUIC ON)
 set(CMAKE_AUTORCC ON)
@@ -30,21 +31,44 @@ option(SYSTEM_QXMPP "Use system qxmpp lib" ON)
 option(WITH_KWALLET "Build KWallet support module" ON)
 option(WITH_KIO "Build KIO support module" ON)
 option(WITH_KCONFIG "Build KConfig support module" ON)
+option(WITH_OMEMO "Build OMEMO support module" ON)
 
 # Dependencies
 ## Qt
-set(QT_VERSION_MAJOR 5)
-find_package(Qt5 COMPONENTS Widgets DBus Gui Xml Network Core REQUIRED)
+find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets DBus Gui Xml Network Core)
 find_package(Boost COMPONENTS)
 
 target_include_directories(squawk PRIVATE ${Boost_INCLUDE_DIRS})
-target_include_directories(squawk PRIVATE ${Qt5_INCLUDE_DIRS})
-target_include_directories(squawk PRIVATE ${Qt5Widgets_INCLUDE_DIRS})
-target_include_directories(squawk PRIVATE ${Qt5DBus_INCLUDE_DIRS})
-target_include_directories(squawk PRIVATE ${Qt5Gui_INCLUDE_DIRS})
-target_include_directories(squawk PRIVATE ${Qt5Xml_INCLUDE_DIRS})
-target_include_directories(squawk PRIVATE ${Qt5Network_INCLUDE_DIRS})
-target_include_directories(squawk PRIVATE ${Qt5Core_INCLUDE_DIRS})
+target_include_directories(squawk PRIVATE ${Qt${QT_VERSION_MAJOR}_INCLUDE_DIRS})
+target_include_directories(squawk PRIVATE ${Qt${QT_VERSION_MAJOR}Widgets_INCLUDE_DIRS})
+target_include_directories(squawk PRIVATE ${Qt${QT_VERSION_MAJOR}DBus_INCLUDE_DIRS})
+target_include_directories(squawk PRIVATE ${Qt${QT_VERSION_MAJOR}Gui_INCLUDE_DIRS})
+target_include_directories(squawk PRIVATE ${Qt${QT_VERSION_MAJOR}Xml_INCLUDE_DIRS})
+target_include_directories(squawk PRIVATE ${Qt${QT_VERSION_MAJOR}Network_INCLUDE_DIRS})
+target_include_directories(squawk PRIVATE ${Qt${QT_VERSION_MAJOR}Core_INCLUDE_DIRS})
+
+#OMEMO
+if (WITH_OMEMO)
+  find_package(PkgConfig)
+  if (PKG_CONFIG_FOUND)
+    pkg_check_modules(OMEMO libomemo)
+    if (OMEMO_FOUND)
+      pkg_check_modules(SIGNAL libsignal-protocol-c)
+      if (SIGNAL_FOUND)
+        message("Building with support of OMEMO")
+      else ()
+        message("signal-protocol package wasn't found, trying to build without OMEMO support")
+        set(WITH_OMEMO OFF)
+      endif()
+    else ()
+      message("libomemo package wasn't found, trying to build without OMEMO support")
+      set(WITH_OMEMO OFF)
+    endif ()
+  else ()
+    message("PKG_CONFIG module wasn't found, can not check libomemo and libsignal-protocol support, trying to build without OMEMO support")
+    set(WITH_OMEMO OFF)
+  endif ()
+endif ()
 
 ## QXmpp
 if (SYSTEM_QXMPP)
@@ -58,13 +82,6 @@ if (SYSTEM_QXMPP)
   endif ()
 endif ()
 
-if (NOT SYSTEM_QXMPP)
-  target_link_libraries(squawk PRIVATE qxmpp)
-  add_subdirectory(external/qxmpp)
-else ()
-  target_link_libraries(squawk PRIVATE QXmpp::QXmpp)
-endif ()
-
 ## KIO
 if (WITH_KIO)
   find_package(KF5KIO CONFIG)
@@ -109,14 +126,38 @@ if (WITH_KCONFIG)
   endif()
 endif()
 
-## Signal (TODO)
-# find_package(Signal REQUIRED)
+if (NOT SYSTEM_QXMPP)
+  message("Building with bundled QXmpp")
+
+  if (WITH_OMEMO)
+    set(BUILD_OMEMO ON)
+  else ()
+    set(BUILD_OMEMO OFF)
+  endif ()
+  add_subdirectory(external/qxmpp)
+  if (WITH_OMEMO)
+    target_include_directories(QXmppOmemo PRIVATE ${SIGNAL_INCLUDE_DIRS})
+    target_include_directories(QXmppOmemo PRIVATE ${OMEMO_INCLUDE_DIRS})
+  endif ()
+
+  target_link_libraries(squawk PRIVATE qxmpp)
+else ()
+  target_link_libraries(squawk PRIVATE QXmpp::QXmpp)
+endif ()
 
 ## LMDB
 find_package(LMDB REQUIRED)
 
 # Linking
-target_link_libraries(squawk PRIVATE Qt5::Core Qt5::Widgets Qt5::DBus Qt5::Network Qt5::Gui Qt5::Xml)
+target_link_libraries(squawk
+  PRIVATE
+  Qt${QT_VERSION_MAJOR}::Core
+  Qt${QT_VERSION_MAJOR}::Widgets
+  Qt${QT_VERSION_MAJOR}::DBus
+  Qt${QT_VERSION_MAJOR}::Network
+  Qt${QT_VERSION_MAJOR}::Gui
+  Qt${QT_VERSION_MAJOR}::Xml
+)
 target_link_libraries(squawk PRIVATE lmdb)
 target_link_libraries(squawk PRIVATE simpleCrypt)
 # Link thread libraries on Linux
@@ -166,7 +207,7 @@ install(FILES LICENSE.md DESTINATION ${CMAKE_INSTALL_DATADIR}/macaw.me/squawk)
 if (CMAKE_BUILD_TYPE STREQUAL "Release")
   if (APPLE)
     add_custom_command(TARGET squawk POST_BUILD COMMENT "Running macdeployqt..."
-      COMMAND "${Qt5Widgets_DIR}/../../../bin/macdeployqt" "${CMAKE_CURRENT_BINARY_DIR}/squawk.app"
+      COMMAND "${Qt${QT_VERSION_MAJOR}Widgets_DIR}/../../../bin/macdeployqt" "${CMAKE_CURRENT_BINARY_DIR}/squawk.app"
     )
   endif(APPLE)
 endif()
diff --git a/core/account.cpp b/core/account.cpp
index 1c3314f..a523554 100644
--- a/core/account.cpp
+++ b/core/account.cpp
@@ -40,6 +40,9 @@ Account::Account(const QString& p_login, const QString& p_server, const QString&
     um(new QXmppUploadRequestManager()),
     dm(client.findExtension<QXmppDiscoveryManager>()),
     rcpm(new QXmppMessageReceiptManager()),
+#ifdef WITH_OMEMO
+    om(new QXmppOmemoManager()),
+#endif
     reconnectScheduled(false),
     reconnectTimer(new QTimer),
     network(p_net),
@@ -87,6 +90,11 @@ Account::Account(const QString& p_login, const QString& p_server, const QString&
     
     client.addExtension(rcpm);
     QObject::connect(rcpm, &QXmppMessageReceiptManager::messageDelivered, mh, &MessageHandler::onReceiptReceived);
+
+#ifdef WITH_OMEMO
+    client.addExtension(om);
+    qDebug("Added OMEMO manager");
+#endif
     
     reconnectTimer->setSingleShot(true);
     QObject::connect(reconnectTimer, &QTimer::timeout, this, &Account::onReconnectTimer);
@@ -118,6 +126,9 @@ Account::~Account()
     delete rh;
     
     delete reconnectTimer;
+#ifdef WITH_OMEMO
+    delete om;
+#endif
     delete rcpm;
     delete dm;
     delete um;
diff --git a/core/account.h b/core/account.h
index 0ce39e5..1c5d14a 100644
--- a/core/account.h
+++ b/core/account.h
@@ -42,6 +42,9 @@
 #include <QXmppUploadRequestManager.h>
 #include <QXmppVCardManager.h>
 #include <QXmppMessageReceiptManager.h>
+#ifdef WITH_OMEMO
+#include <QXmppOmemoManager.h>
+#endif
 
 #include <shared/shared.h>
 #include <shared/identity.h>
@@ -176,6 +179,9 @@ private:
     QXmppUploadRequestManager* um;
     QXmppDiscoveryManager* dm;
     QXmppMessageReceiptManager* rcpm;
+#ifdef WITH_OMEMO
+    QXmppOmemoManager* om;
+#endif
     bool reconnectScheduled;
     QTimer* reconnectTimer;
     
diff --git a/core/handlers/discoveryhandler.cpp b/core/handlers/discoveryhandler.cpp
index e562e68..e17c072 100644
--- a/core/handlers/discoveryhandler.cpp
+++ b/core/handlers/discoveryhandler.cpp
@@ -32,6 +32,11 @@ Core::DiscoveryHandler::DiscoveryHandler(Core::Account* account):
     acc->dm->setClientCapabilitiesNode("https://git.macaw.me/blue/squawk");
 }
 
+Core::DiscoveryHandler::~DiscoveryHandler()
+{
+}
+
+
 void Core::DiscoveryHandler::onItemsReceived(const QXmppDiscoveryIq& items)
 {
     QString server = acc->getServer();

From 820dc845eaa77943b5d5d879fe52e4710ad25288 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Sat, 3 Sep 2022 14:39:42 +0300
Subject: [PATCH 213/281] BUILD FAILS! some ideas of storage and cache

---
 CMakeLists.txt                 | 31 ++++++------
 core/account.cpp               |  4 ++
 core/components/clientcache.h  |  2 +-
 core/handlers/CMakeLists.txt   |  2 +
 core/handlers/omemohandler.cpp | 17 +++++++
 core/handlers/omemohandler.h   | 57 ++++++++++++++++++++++
 core/storage/cache.h           | 18 +++----
 core/storage/cache.hpp         | 50 ++++++++++----------
 core/storage/storage.h         | 20 ++++++--
 core/storage/storage.hpp       | 86 ++++++++++++++++++++--------------
 10 files changed, 198 insertions(+), 89 deletions(-)
 create mode 100644 core/handlers/omemohandler.cpp
 create mode 100644 core/handlers/omemohandler.h

diff --git a/CMakeLists.txt b/CMakeLists.txt
index d9d0dab..af1e7f3 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -35,7 +35,12 @@ option(WITH_OMEMO "Build OMEMO support module" ON)
 
 # Dependencies
 ## Qt
-find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets DBus Gui Xml Network Core)
+if (NOT DEFINED QT_VERSION_MAJOR)
+  find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets DBus Gui Xml Network Core)
+else ()
+  find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets DBus Gui Xml Network Core)
+endif()
+
 find_package(Boost COMPONENTS)
 
 target_include_directories(squawk PRIVATE ${Boost_INCLUDE_DIRS})
@@ -51,21 +56,15 @@ target_include_directories(squawk PRIVATE ${Qt${QT_VERSION_MAJOR}Core_INCLUDE_DI
 if (WITH_OMEMO)
   find_package(PkgConfig)
   if (PKG_CONFIG_FOUND)
-    pkg_check_modules(OMEMO libomemo)
+    pkg_check_modules(OMEMO libomemo-c)
     if (OMEMO_FOUND)
-      pkg_check_modules(SIGNAL libsignal-protocol-c)
-      if (SIGNAL_FOUND)
-        message("Building with support of OMEMO")
-      else ()
-        message("signal-protocol package wasn't found, trying to build without OMEMO support")
-        set(WITH_OMEMO OFF)
-      endif()
+      message("Building with support of OMEMO")
     else ()
-      message("libomemo package wasn't found, trying to build without OMEMO support")
+      message("libomemo-c package wasn't found, trying to build without OMEMO support")
       set(WITH_OMEMO OFF)
     endif ()
   else ()
-    message("PKG_CONFIG module wasn't found, can not check libomemo and libsignal-protocol support, trying to build without OMEMO support")
+    message("PKG_CONFIG module wasn't found, can not check libomemo-c support, trying to build without OMEMO support")
     set(WITH_OMEMO OFF)
   endif ()
 endif ()
@@ -129,16 +128,18 @@ endif()
 if (NOT SYSTEM_QXMPP)
   message("Building with bundled QXmpp")
 
+  target_include_directories(squawk PRIVATE ${CMAKE_SOURCE_DIR}/external/qxmpp/src/base)
+  target_include_directories(squawk PRIVATE ${CMAKE_SOURCE_DIR}/external/qxmpp/src/client)
+
   if (WITH_OMEMO)
+    target_include_directories(squawk PRIVATE ${CMAKE_SOURCE_DIR}/external/qxmpp/src/omemo)
+    target_include_directories(squawk PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/external/qxmpp/src/omemo)
     set(BUILD_OMEMO ON)
+    target_compile_definitions(squawk PRIVATE WITH_OMEMO)
   else ()
     set(BUILD_OMEMO OFF)
   endif ()
   add_subdirectory(external/qxmpp)
-  if (WITH_OMEMO)
-    target_include_directories(QXmppOmemo PRIVATE ${SIGNAL_INCLUDE_DIRS})
-    target_include_directories(QXmppOmemo PRIVATE ${OMEMO_INCLUDE_DIRS})
-  endif ()
 
   target_link_libraries(squawk PRIVATE qxmpp)
 else ()
diff --git a/core/account.cpp b/core/account.cpp
index a523554..87c341b 100644
--- a/core/account.cpp
+++ b/core/account.cpp
@@ -304,12 +304,16 @@ void Core::Account::onPresenceReceived(const QXmppPresence& p_presence)
             break;
         case QXmppPresence::Subscribe:
             qDebug("xmpp presence \"subscribe\" received, do not yet know what to do, skipping");
+            break;
         case QXmppPresence::Subscribed:
             qDebug("xmpp presence \"subscribed\" received, do not yet know what to do, skipping");
+            break;
         case QXmppPresence::Unsubscribe:
             qDebug("xmpp presence \"unsubscribe\" received, do not yet know what to do, skipping");
+            break;
         case QXmppPresence::Unsubscribed:
             qDebug("xmpp presence \"unsubscribed\" received, do not yet know what to do, skipping");
+            break;
         case QXmppPresence::Probe:
             qDebug("xmpp presence \"probe\" received, do not yet know what to do, skipping");
             break;
diff --git a/core/components/clientcache.h b/core/components/clientcache.h
index b2ff70d..15bcb7d 100644
--- a/core/components/clientcache.h
+++ b/core/components/clientcache.h
@@ -47,7 +47,7 @@ public slots:
 
 private:
     std::map<QString, Shared::ClientInfo> requested;
-    Cache<Shared::ClientInfo> cache;
+    Cache<QString, Shared::ClientInfo> cache;
     std::map<QString, Shared::ClientInfo> specific;
 };
 
diff --git a/core/handlers/CMakeLists.txt b/core/handlers/CMakeLists.txt
index 255d7fa..1516aa7 100644
--- a/core/handlers/CMakeLists.txt
+++ b/core/handlers/CMakeLists.txt
@@ -7,4 +7,6 @@ target_sources(squawk PRIVATE
   vcardhandler.h
   discoveryhandler.cpp
   discoveryhandler.h
+  omemohandler.cpp
+  omemohandler.h
   )
diff --git a/core/handlers/omemohandler.cpp b/core/handlers/omemohandler.cpp
new file mode 100644
index 0000000..ef2f5ea
--- /dev/null
+++ b/core/handlers/omemohandler.cpp
@@ -0,0 +1,17 @@
+// Squawk messenger. 
+// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+// 
+// 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 "omemohandler.h"
diff --git a/core/handlers/omemohandler.h b/core/handlers/omemohandler.h
new file mode 100644
index 0000000..2759a21
--- /dev/null
+++ b/core/handlers/omemohandler.h
@@ -0,0 +1,57 @@
+// Squawk messenger. 
+// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+// 
+// 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/>.
+
+#ifndef CORE_OMEMOHANDLER_H
+#define CORE_OMEMOHANDLER_H
+
+#include <QXmppOmemoStorage.h>
+#include <core/storage/cache.h>
+
+namespace Core {
+
+class OmemoHandler : public QXmppOmemoStorage
+{
+public:
+    OmemoHandler();
+    ~OmemoHandler() override;
+
+    QFuture<OmemoData> allData() override;
+
+    QFuture<void> setOwnDevice(const std::optional<OwnDevice> &device) override;
+
+    QFuture<void> addSignedPreKeyPair(uint32_t keyId, const SignedPreKeyPair &keyPair) override;
+    QFuture<void> removeSignedPreKeyPair(uint32_t keyId) override;
+
+    QFuture<void> addPreKeyPairs(const QHash<uint32_t, QByteArray> &keyPairs) override;
+    QFuture<void> removePreKeyPair(uint32_t keyId) override;
+
+    QFuture<void> addDevice(const QString &jid, uint32_t deviceId, const Device &device) override;
+    QFuture<void> removeDevice(const QString &jid, uint32_t deviceId) override;
+    QFuture<void> removeDevices(const QString &jid) override;
+
+    QFuture<void> resetAll() override;
+
+private:
+    std::optional<OwnDevice> ownDevice;
+    Cache<QString, QHash<uint32_t, Device>> devices;
+    Cache<uint32_t, QByteArray> preKeyPairs;
+    Cache<uint32_t, QXmppOmemoStorage::SignedPreKeyPair> signedPreKeyPairs;
+
+
+};
+
+}
+#endif // CORE_OMEMOHANDLER_H
diff --git a/core/storage/cache.h b/core/storage/cache.h
index 5091561..23f72f0 100644
--- a/core/storage/cache.h
+++ b/core/storage/cache.h
@@ -26,7 +26,7 @@
 
 namespace Core {
 
-template <class T>
+template <class K, class V>
 class Cache
 {
 public:
@@ -36,16 +36,16 @@ public:
     void open();
     void close();
 
-    void addRecord(const QString& key, const T& value);
-    void changeRecord(const QString& key, const T& value);
-    void removeRecord(const QString& key);
-    T getRecord(const QString& key) const;
-    bool checkRecord(const QString& key) const;
+    void addRecord(const K& key, const V& value);
+    void changeRecord(const K& key, const V& value);
+    void removeRecord(const K& key);
+    V getRecord(const K& key) const;
+    bool checkRecord(const K& key) const;
 
 private:
-    Core::Storage<T> storage;
-    std::map<QString, T>* cache;
-    std::set<QString>* abscent;
+    Core::Storage<K, V> storage;
+    std::map<K, V>* cache;
+    std::set<K>* abscent;
 };
 
 }
diff --git a/core/storage/cache.hpp b/core/storage/cache.hpp
index 1604d66..9491de2 100644
--- a/core/storage/cache.hpp
+++ b/core/storage/cache.hpp
@@ -18,44 +18,44 @@
 #define CORE_CACHE_HPP
 #include "cache.h"
 
-template <class T>
-Core::Cache<T>::Cache(const QString& name):
+template <class K, class V>
+Core::Cache<K, V>::Cache(const QString& name):
     storage(name),
-    cache(new std::map<QString, T> ()),
-    abscent(new std::set<QString> ()) {}
+    cache(new std::map<K, V> ()),
+    abscent(new std::set<K> ()) {}
 
-template <class T>
-Core::Cache<T>::~Cache() {
+template <class K, class V>
+Core::Cache<K, V>::~Cache() {
     close();
     delete cache;
     delete abscent;
 }
 
-template <class T>
-void Core::Cache<T>::open() {
+template <class K, class V>
+void Core::Cache<K, V>::open() {
     storage.open();}
 
-template <class T>
-void Core::Cache<T>::close() {
+template <class K, class V>
+void Core::Cache<K, V>::close() {
     storage.close();}
 
-template <class T>
-void Core::Cache<T>::addRecord(const QString& key, const T& value) {
+template <class K, class V>
+void Core::Cache<K, V>::addRecord(const K& key, const V& value) {
     storage.addRecord(key, value);
     cache->insert(std::make_pair(key, value));
     abscent->erase(key);
 }
 
-template <class T>
-T Core::Cache<T>::getRecord(const QString& key) const {
-    typename std::map<QString, T>::const_iterator itr = cache->find(key);
+template <class K, class V>
+V Core::Cache<K, V>::getRecord(const K& key) const {
+    typename std::map<K, V>::const_iterator itr = cache->find(key);
     if (itr == cache->end()) {
         if (abscent->count(key) > 0) {
-            throw Archive::NotFound(key, storage.getName().toStdString());
+            throw Archive::NotFound(std::to_string(key), storage.getName().toStdString());
         }
 
         try {
-            T value = storage.getRecord(key);
+            V value = storage.getRecord(key);
             itr = cache->insert(std::make_pair(key, value)).first;
         } catch (const Archive::NotFound& error) {
             abscent->insert(key);
@@ -66,9 +66,9 @@ T Core::Cache<T>::getRecord(const QString& key) const {
     return itr->second;
 }
 
-template<class T>
-bool Core::Cache<T>::checkRecord(const QString& key) const {
-    typename std::map<QString, T>::const_iterator itr = cache->find(key);
+template<class K, class V>
+bool Core::Cache<K, V>::checkRecord(const K& key) const {
+    typename std::map<K, V>::const_iterator itr = cache->find(key);
     if (itr != cache->end())
         return true;
 
@@ -76,7 +76,7 @@ bool Core::Cache<T>::checkRecord(const QString& key) const {
         return false;
 
     try {
-        T value = storage.getRecord(key);
+        V value = storage.getRecord(key);
         itr = cache->insert(std::make_pair(key, value)).first;
     } catch (const Archive::NotFound& error) {
         return false;
@@ -85,15 +85,15 @@ bool Core::Cache<T>::checkRecord(const QString& key) const {
     return true;
 }
 
-template<typename T>
-void Core::Cache<T>::changeRecord(const QString& key, const T& value) {
+template<class K, class V>
+void Core::Cache<K, V>::changeRecord(const K& key, const V& value) {
     storage.changeRecord(key, value);   //there is a non straightforward behaviour: if there was no element at the sorage it will be added
     cache->at(key) = value;
     abscent->erase(key);                //so... this line here is to make it coherent with the storage
 }
 
-template<typename T>
-void Core::Cache<T>::removeRecord(const QString& key) {
+template<class K, class V>
+void Core::Cache<K, V>::removeRecord(const K& key) {
     storage.removeRecord(key);
     cache->erase(key);
     abscent->insert(key);
diff --git a/core/storage/storage.h b/core/storage/storage.h
index 5d6dabc..e43ae1a 100644
--- a/core/storage/storage.h
+++ b/core/storage/storage.h
@@ -29,7 +29,7 @@ namespace Core {
 /**
  * @todo write docs
  */
-template <class T>
+template <class K, class V>
 class Storage
 {
 public:
@@ -39,10 +39,10 @@ public:
     void open();
     void close();
     
-    void addRecord(const QString& key, const T& value);
-    void changeRecord(const QString& key, const T& value);
-    void removeRecord(const QString& key);
-    T getRecord(const QString& key) const;
+    void addRecord(const K& key, const V& value);
+    void changeRecord(const K& key, const V& value);
+    void removeRecord(const K& key);
+    V getRecord(const K& key) const;
     QString getName() const;
     
     
@@ -55,6 +55,16 @@ private:
 
 }
 
+MDB_val& operator << (MDB_val& data, QString& value);
+MDB_val& operator >> (MDB_val& data, QString& value);
+
+MDB_val& operator << (MDB_val& data, uint32_t& value);
+MDB_val& operator >> (MDB_val& data, uint32_t& value);
+
+namespace std {
+    std::string to_string(const QString& str);
+}
+
 #include "storage.hpp"
 
 #endif // CORE_STORAGE_H
diff --git a/core/storage/storage.hpp b/core/storage/storage.hpp
index 48e334d..33b8b56 100644
--- a/core/storage/storage.hpp
+++ b/core/storage/storage.hpp
@@ -22,9 +22,10 @@
 #include <QDir>
 
 #include "storage.h"
+#include <cstring>
 
-template <class T>
-Core::Storage<T>::Storage(const QString& p_name):
+template <class K, class V>
+Core::Storage<K, V>::Storage(const QString& p_name):
     name(p_name),
     opened(false),
     environment(),
@@ -32,14 +33,14 @@ Core::Storage<T>::Storage(const QString& p_name):
 {
 }
 
-template <class T>
-Core::Storage<T>::~Storage()
+template <class K, class V>
+Core::Storage<K, V>::~Storage()
 {
     close();
 }
 
-template <class T>
-void Core::Storage<T>::open()
+template <class K, class V>
+void Core::Storage<K, V>::open()
 {
     if (!opened) {
         mdb_env_create(&environment);
@@ -66,8 +67,8 @@ void Core::Storage<T>::open()
     }
 }
 
-template <class T>
-void Core::Storage<T>::close()
+template <class K, class V>
+void Core::Storage<K, V>::close()
 {
     if (opened) {
         mdb_dbi_close(environment, base);
@@ -76,20 +77,19 @@ void Core::Storage<T>::close()
     }
 }
 
-template <class T>
-void Core::Storage<T>::addRecord(const QString& key, const T& value)
+template <class K, class V>
+void Core::Storage<K, V>::addRecord(const K& key, const V& value)
 {
     if (!opened) {
         throw Archive::Closed("addRecord", name.toStdString());
     }
     QByteArray ba;
     QDataStream ds(&ba, QIODevice::WriteOnly);
-    const std::string& id = key.toStdString();
     ds << value;
     
     MDB_val lmdbKey, lmdbData;
-    lmdbKey.mv_size = id.size();
-    lmdbKey.mv_data = (char*)id.c_str();
+    lmdbKey << key;
+
     lmdbData.mv_size = ba.size();
     lmdbData.mv_data = (uint8_t*)ba.data();
     MDB_txn *txn;
@@ -99,7 +99,7 @@ void Core::Storage<T>::addRecord(const QString& key, const T& value)
     if (rc != 0) {
         mdb_txn_abort(txn);
         if (rc == MDB_KEYEXIST) {
-            throw Archive::Exist(name.toStdString(), id);
+            throw Archive::Exist(name.toStdString(), std::to_string(key));
         } else {
             throw Archive::Unknown(name.toStdString(), mdb_strerror(rc));
         }
@@ -108,8 +108,8 @@ void Core::Storage<T>::addRecord(const QString& key, const T& value)
     }
 }
 
-template <class T>
-void Core::Storage<T>::changeRecord(const QString& key, const T& value)
+template <class K, class V>
+void Core::Storage<K, V>::changeRecord(const K& key, const V& value)
 {
     if (!opened) {
         throw Archive::Closed("changeRecord", name.toStdString());
@@ -117,12 +117,10 @@ void Core::Storage<T>::changeRecord(const QString& key, const T& value)
 
     QByteArray ba;
     QDataStream ds(&ba, QIODevice::WriteOnly);
-    const std::string& id = key.toStdString();
     ds << value;
     
     MDB_val lmdbKey, lmdbData;
-    lmdbKey.mv_size = id.size();
-    lmdbKey.mv_data = (char*)id.c_str();
+    lmdbKey << key;
     lmdbData.mv_size = ba.size();
     lmdbData.mv_data = (uint8_t*)ba.data();
     MDB_txn *txn;
@@ -139,17 +137,15 @@ void Core::Storage<T>::changeRecord(const QString& key, const T& value)
     }
 }
 
-template <class T>
-T Core::Storage<T>::getRecord(const QString& key) const
+template <class K, class V>
+V Core::Storage<K, V>::getRecord(const K& key) const
 {
     if (!opened) {
         throw Archive::Closed("addElement", name.toStdString());
     }
-    const std::string& id = key.toStdString();
     
     MDB_val lmdbKey, lmdbData;
-    lmdbKey.mv_size = id.size();
-    lmdbKey.mv_data = (char*)id.c_str();
+    lmdbKey << key;
     
     MDB_txn *txn;
     int rc;
@@ -158,14 +154,14 @@ T Core::Storage<T>::getRecord(const QString& key) const
     if (rc) {
         mdb_txn_abort(txn);
         if (rc == MDB_NOTFOUND) {
-            throw Archive::NotFound(id, name.toStdString());
+            throw Archive::NotFound(std::to_string(key), name.toStdString());
         } else {
             throw Archive::Unknown(name.toStdString(), mdb_strerror(rc));
         }
     } else {
         QByteArray ba((char*)lmdbData.mv_data, lmdbData.mv_size);
         QDataStream ds(&ba, QIODevice::ReadOnly);
-        T value;
+        V value;
         ds >> value;
         mdb_txn_abort(txn);
 
@@ -173,17 +169,15 @@ T Core::Storage<T>::getRecord(const QString& key) const
     }
 }
 
-template <class T>
-void Core::Storage<T>::removeRecord(const QString& key)
+template <class K, class V>
+void Core::Storage<K, V>::removeRecord(const K& key)
 {
     if (!opened) {
         throw Archive::Closed("addElement", name.toStdString());
     }
-    const std::string& id = key.toStdString();
     
     MDB_val lmdbKey;
-    lmdbKey.mv_size = id.size();
-    lmdbKey.mv_data = (char*)id.c_str();
+    lmdbKey << key;
     
     MDB_txn *txn;
     int rc;
@@ -192,7 +186,7 @@ void Core::Storage<T>::removeRecord(const QString& key)
     if (rc) {
         mdb_txn_abort(txn);
         if (rc == MDB_NOTFOUND) {
-            throw Archive::NotFound(id, name.toStdString());
+            throw Archive::NotFound(std::to_string(key), name.toStdString());
         } else {
             throw Archive::Unknown(name.toStdString(), mdb_strerror(rc));
         }
@@ -201,8 +195,32 @@ void Core::Storage<T>::removeRecord(const QString& key)
     }
 }
 
-template <class T>
-QString Core::Storage<T>::getName() const {
+template <class K, class V>
+QString Core::Storage<K, V>::getName() const {
     return name;}
 
+MDB_val& operator << (MDB_val& data, const QString& value) {
+    QByteArray ba = value.toUtf8();
+    data.mv_size = ba.size();
+    data.mv_data = ba.data();
+    return data;
+}
+MDB_val& operator >> (MDB_val& data, QString& value) {
+    value = QString::fromUtf8((const char*)data.mv_data, data.mv_size);
+    return data;
+}
+
+MDB_val& operator << (MDB_val& data, uint32_t& value) {
+    data.mv_size = 4;
+    data.mv_data = &value;
+    return data;
+}
+MDB_val& operator >> (MDB_val& data, uint32_t& value) {
+    std::memcpy(&value, data.mv_data, data.mv_size);
+    return data;
+}
+
+std::string std::to_string(const QString& str) {
+    return str.toStdString();
+}
 #endif //CORE_STORAGE_HPP

From 0b61b6e928d60c21396ec9279510df945549adf6 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Thu, 15 Dec 2022 02:08:08 +0300
Subject: [PATCH 214/281] Some work on omemo handler, NOT DONE, BUILD FAILS!

---
 .gitmodules                    |   3 +
 CMakeLists.txt                 |   8 +-
 core/handlers/omemohandler.cpp | 144 +++++++++++++++++++++++++++++++++
 core/handlers/omemohandler.h   |  28 +++++--
 external/qxmpp                 |   2 +-
 external/storage               |   1 +
 main/main.cpp                  |  13 ++-
 7 files changed, 187 insertions(+), 12 deletions(-)
 create mode 160000 external/storage

diff --git a/.gitmodules b/.gitmodules
index bbe5364..098973a 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,3 +1,6 @@
 [submodule "external/qxmpp"]
 	path = external/qxmpp
 	url = https://github.com/qxmpp-project/qxmpp.git
+[submodule "external/storage"]
+	path = external/storage
+	url = https://git.macaw.me/blue/storage
diff --git a/CMakeLists.txt b/CMakeLists.txt
index af1e7f3..3f5bd96 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -147,7 +147,13 @@ else ()
 endif ()
 
 ## LMDB
-find_package(LMDB REQUIRED)
+#find_package(LMDB REQUIRED)
+
+
+#TODO conditioning!
+add_subdirectory(external/storage)
+target_include_directories(squawk PRIVATE external/storage)
+target_link_libraries(squawk PRIVATE storage)
 
 # Linking
 target_link_libraries(squawk
diff --git a/core/handlers/omemohandler.cpp b/core/handlers/omemohandler.cpp
index ef2f5ea..c1dc7ef 100644
--- a/core/handlers/omemohandler.cpp
+++ b/core/handlers/omemohandler.cpp
@@ -14,4 +14,148 @@
 // 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 <QDebug>
 #include "omemohandler.h"
+#include "core/account.h"
+
+Core::OmemoHandler::OmemoHandler(Account* account) :
+    QXmppOmemoStorage(),
+    acc(account),
+    ownDevice(std::nullopt),
+    db("omemo"),
+    meta(db.addCache<QString, QVariant>("meta")),
+    devices(db.addCache<QString, QHash<uint32_t, Device>>("devices")),
+    preKeyPairs(db.addCache<uint32_t, QByteArray>("preKeyPairs")),
+    signedPreKeyPairs(db.addCache<uint32_t, QXmppOmemoStorage::SignedPreKeyPair>("signedPreKeyPairs"))
+{
+    db.open();
+    try {
+        QVariant own = meta->getRecord("ownDevice");
+        ownDevice.value() = own.value<OwnDevice>();
+        qDebug() << "Successfully found own device omemo data for account" << acc->getName();
+    } catch (const DataBase::NotFound& e) {
+        qDebug() << "No device omemo data was found for account" << acc->getName();
+    }
+}
+
+Core::OmemoHandler::~OmemoHandler() {
+    db.close();
+}
+
+QFuture<void> Core::OmemoHandler::emptyVoidFuture() {
+    QFutureInterface<QXmppOmemoStorage::OmemoData> result(QFutureInterfaceBase::Started);
+    result.reportFinished();
+    return result.future();
+}
+
+QFuture<QXmppOmemoStorage::OmemoData> Core::OmemoHandler::allData() {
+    OmemoData data;
+    data.ownDevice = ownDevice;
+
+    std::map<uint32_t, QByteArray> pkeys = preKeyPairs->readAll();
+    for (const std::pair<const uint32_t, QByteArray>& pair : pkeys) {
+        data.preKeyPairs.insert(pair.first, pair.second);
+    }
+
+    std::map<uint32_t, QXmppOmemoStorage::SignedPreKeyPair> spre = signedPreKeyPairs->readAll();
+    for (const std::pair<const uint32_t, QXmppOmemoStorage::SignedPreKeyPair>& pair : spre) {
+        data.signedPreKeyPairs.insert(pair.first, pair.second);
+    }
+
+    std::map<QString, QHash<uint32_t, Device>> devs = devices->readAll();
+    for (const std::pair<const QString, QHash<uint32_t, Device>>& pair : devs) {
+        data.devices.insert(pair.first, pair.second);
+    }
+
+    QFutureInterface<QXmppOmemoStorage::OmemoData> result(QFutureInterfaceBase::Started);
+    result.reportResult(std::move(data));
+    result.reportFinished();
+    return result.future();
+}
+
+QFuture<void> Core::OmemoHandler::addDevice(const QString& jid, uint32_t deviceId, const QXmppOmemoStorage::Device& device) {
+    QHash<uint32_t, Device> devs;
+    bool had = true;
+    try {
+        devs = devices->getRecord(jid);
+    } catch (const DataBase::NotFound& error) {
+        had = false;
+    }
+
+    devs.insert(deviceId, device);
+    if (had) {
+        devices->changeRecord(jid, devs);
+    } else {
+        devices->addRecord(jid, devs);
+    }
+
+    return emptyVoidFuture();
+}
+
+QFuture<void> Core::OmemoHandler::addPreKeyPairs(const QHash<uint32_t, QByteArray>& keyPairs) {
+    for (QHash<uint32_t, QByteArray>::const_iterator itr = keyPairs.begin(), end = keyPairs.end(); itr != end; ++itr) {
+        preKeyPairs->forceRecord(itr.key(), itr.value());
+    }
+
+    return emptyVoidFuture();
+}
+
+QFuture<void> Core::OmemoHandler::addSignedPreKeyPair(uint32_t keyId, const QXmppOmemoStorage::SignedPreKeyPair& keyPair) {
+    signedPreKeyPairs->addRecord(keyId, keyPair);
+    return emptyVoidFuture();
+}
+
+QFuture<void> Core::OmemoHandler::removeDevice(const QString& jid, uint32_t deviceId) {
+    QHash<uint32_t, Device> devs = devices->getRecord(jid);
+    devs.remove(deviceId);
+    if (devs.isEmpty()) {
+        devices->removeRecord(jid);
+    } else {
+        devices->changeRecord(jid, devs);
+    }
+    return emptyVoidFuture();
+}
+
+QFuture<void> Core::OmemoHandler::removeDevices(const QString& jid) {
+    devices->removeRecord(jid);
+    return emptyVoidFuture();
+}
+
+QFuture<void> Core::OmemoHandler::removePreKeyPair(uint32_t keyId) {
+    preKeyPairs->removeRecord(keyId);
+    return emptyVoidFuture();
+}
+
+QFuture<void> Core::OmemoHandler::removeSignedPreKeyPair(uint32_t keyId) {
+    signedPreKeyPairs->removeRecord(keyId);
+    return emptyVoidFuture();
+}
+
+QFuture<void> Core::OmemoHandler::setOwnDevice(const std::optional<OwnDevice>& device) {
+    bool had = ownDevice.has_value();
+    ownDevice = device;
+    if (ownDevice.has_value()) {
+        if (had) {
+            meta->changeRecord("ownDevice", QVariant::fromValue(ownDevice.value()));
+        } else {
+            meta->addRecord("ownDevice", QVariant::fromValue(ownDevice.value()));
+        }
+    } else {
+        if (had) {
+            meta->removeRecord("ownDevice");
+        }
+    }
+    return emptyVoidFuture();
+}
+
+QFuture<void> Core::OmemoHandler::resetAll() {
+    ownDevice = std::nullopt;
+    db.drop();
+
+    return emptyVoidFuture();
+}
+
+
+
+
+
diff --git a/core/handlers/omemohandler.h b/core/handlers/omemohandler.h
index 2759a21..8f3ac9c 100644
--- a/core/handlers/omemohandler.h
+++ b/core/handlers/omemohandler.h
@@ -18,14 +18,17 @@
 #define CORE_OMEMOHANDLER_H
 
 #include <QXmppOmemoStorage.h>
-#include <core/storage/cache.h>
+#include <cache.h>
+
+Q_DECLARE_METATYPE(QXmppOmemoStorage::OwnDevice);
 
 namespace Core {
+class Account;
 
 class OmemoHandler : public QXmppOmemoStorage
 {
 public:
-    OmemoHandler();
+    OmemoHandler(Account* account);
     ~OmemoHandler() override;
 
     QFuture<OmemoData> allData() override;
@@ -45,13 +48,24 @@ public:
     QFuture<void> resetAll() override;
 
 private:
+    static QFuture<void> emptyVoidFuture();
+
+private:
+    Account* acc;
     std::optional<OwnDevice> ownDevice;
-    Cache<QString, QHash<uint32_t, Device>> devices;
-    Cache<uint32_t, QByteArray> preKeyPairs;
-    Cache<uint32_t, QXmppOmemoStorage::SignedPreKeyPair> signedPreKeyPairs;
-
-
+    DataBase db;
+    DataBase::Cache<QString, QVariant>* meta;
+    DataBase::Cache<QString, QHash<uint32_t, Device>>* devices;
+    DataBase::Cache<uint32_t, QByteArray>* preKeyPairs;
+    DataBase::Cache<uint32_t, QXmppOmemoStorage::SignedPreKeyPair>* signedPreKeyPairs;
 };
 
 }
+
+QDataStream& operator << (QDataStream &out, const QXmppOmemoStorage::Device& device);
+QDataStream& operator >> (QDataStream &out, QXmppOmemoStorage::Device device);
+
+QDataStream& operator << (QDataStream &out, const QXmppOmemoStorage::SignedPreKeyPair& device);
+QDataStream& operator >> (QDataStream &out, QXmppOmemoStorage::SignedPreKeyPair device);
+
 #endif // CORE_OMEMOHANDLER_H
diff --git a/external/qxmpp b/external/qxmpp
index fe83e9c..f6e7591 160000
--- a/external/qxmpp
+++ b/external/qxmpp
@@ -1 +1 @@
-Subproject commit fe83e9c3d42c3becf682e2b5ecfc9d77b24c614f
+Subproject commit f6e7591e21b4c55319918ac296b2341b2e9f1988
diff --git a/external/storage b/external/storage
new file mode 160000
index 0000000..08daad4
--- /dev/null
+++ b/external/storage
@@ -0,0 +1 @@
+Subproject commit 08daad48ad36d9f12dc6485a085190e31e4cf49e
diff --git a/main/main.cpp b/main/main.cpp
index 086dbc0..58063e0 100644
--- a/main/main.cpp
+++ b/main/main.cpp
@@ -33,6 +33,10 @@
 #include <QtWidgets/QApplication>
 #include <QDir>
 
+#ifdef WITH_OMEMO
+#include <QXmppOmemoStorage.h>
+#endif
+
 int main(int argc, char *argv[])
 {
     qRegisterMetaType<Shared::Message>("Shared::Message");
@@ -40,12 +44,15 @@ int main(int argc, char *argv[])
     qRegisterMetaType<Shared::VCard>("Shared::VCard");
     qRegisterMetaType<std::list<Shared::Message>>("std::list<Shared::Message>");
     qRegisterMetaType<std::list<Shared::MessageInfo>>("std::list<Shared::MessageInfo>");
-    qRegisterMetaType<std::list<Shared::MessageInfo>>("std::list<QString>");
-    qRegisterMetaType<std::list<Shared::MessageInfo>>("std::set<QString>");
-    qRegisterMetaType<std::list<Shared::MessageInfo>>("std::list<Shared::Identity>");
+    qRegisterMetaType<std::list<QString>>("std::list<QString>");
+    qRegisterMetaType<std::set<QString>>("std::set<QString>");
+    qRegisterMetaType<std::list<Shared::Identity>>("std::list<Shared::Identity>");
     qRegisterMetaType<QSet<QString>>("QSet<QString>");
     qRegisterMetaType<Shared::ConnectionState>("Shared::ConnectionState");
     qRegisterMetaType<Shared::Availability>("Shared::Availability");
+#ifdef WITH_OMEMO
+    qRegisterMetaType<QXmppOmemoStorage::OwnDevice>("QXmppOmemoStorage::OwnDevice");
+#endif
     
     QApplication app(argc, argv);
     SignalCatcher sc(&app);

From db3bc358a7f6b8079f0651b9f6f9f3e0ca5a537d Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Mon, 19 Dec 2022 18:43:24 +0300
Subject: [PATCH 215/281] work progress: trust manager. DOESN'T START!

---
 CMakeLists.txt                     |   1 +
 core/account.cpp                   |  25 +-
 core/account.h                     |  30 ++-
 core/components/clientcache.cpp    |  15 +-
 core/components/clientcache.h      |   6 +-
 core/handlers/CMakeLists.txt       |   2 +
 core/handlers/discoveryhandler.cpp |  11 +-
 core/handlers/discoveryhandler.h   |   2 +
 core/handlers/omemohandler.cpp     |  55 +++-
 core/handlers/omemohandler.h       |   9 +-
 core/handlers/rosterhandler.cpp    |   9 +-
 core/handlers/rosterhandler.h      |   2 +
 core/handlers/trusthandler.cpp     | 390 +++++++++++++++++++++++++++++
 core/handlers/trusthandler.h       |  81 ++++++
 core/handlers/vcardhandler.cpp     |  17 +-
 core/handlers/vcardhandler.h       |   2 +
 core/storage/CMakeLists.txt        |   8 +-
 external/qxmpp                     |   2 +-
 external/storage                   |   2 +-
 shared/enums.h                     |  20 ++
 20 files changed, 634 insertions(+), 55 deletions(-)
 create mode 100644 core/handlers/trusthandler.cpp
 create mode 100644 core/handlers/trusthandler.h

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 3f5bd96..b40e876 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -142,6 +142,7 @@ if (NOT SYSTEM_QXMPP)
   add_subdirectory(external/qxmpp)
 
   target_link_libraries(squawk PRIVATE qxmpp)
+  target_link_libraries(squawk PRIVATE QXmppOmemo)
 else ()
   target_link_libraries(squawk PRIVATE QXmpp::QXmpp)
 endif ()
diff --git a/core/account.cpp b/core/account.cpp
index 87c341b..4b25737 100644
--- a/core/account.cpp
+++ b/core/account.cpp
@@ -31,6 +31,17 @@ Account::Account(const QString& p_login, const QString& p_server, const QString&
     config(),
     presence(),
     state(Shared::ConnectionState::disconnected),
+
+    mh(new MessageHandler(this)),
+    rh(new RosterHandler(this)),
+    vh(new VCardHandler(this)),
+    dh(new DiscoveryHandler(this)),
+#ifdef WITH_OMEMO
+    th(new TrustHandler(this)),
+    oh(new OmemoHandler(this)),
+    tm(new QXmppTrustManager(th)),
+    om(new QXmppOmemoManager(oh)),
+#endif
     cm(new QXmppCarbonManager()),
     am(new QXmppMamManager()),
     mm(new QXmppMucManager()),
@@ -40,9 +51,6 @@ Account::Account(const QString& p_login, const QString& p_server, const QString&
     um(new QXmppUploadRequestManager()),
     dm(client.findExtension<QXmppDiscoveryManager>()),
     rcpm(new QXmppMessageReceiptManager()),
-#ifdef WITH_OMEMO
-    om(new QXmppOmemoManager()),
-#endif
     reconnectScheduled(false),
     reconnectTimer(new QTimer),
     network(p_net),
@@ -50,17 +58,17 @@ Account::Account(const QString& p_login, const QString& p_server, const QString&
     lastError(Error::none),
     pepSupport(Shared::Support::unknown),
     active(p_active),
-    notReadyPassword(false),
-    mh(new MessageHandler(this)),
-    rh(new RosterHandler(this)),
-    vh(new VCardHandler(this)),
-    dh(new DiscoveryHandler(this))
+    notReadyPassword(false)
 {
     config.setUser(p_login);
     config.setDomain(p_server);
     config.setPassword(p_password);
     config.setAutoAcceptSubscriptions(true);
     //config.setAutoReconnectionEnabled(false);
+
+    rh->initialize();
+    vh->initialize();
+    dh->initialize();
     
     QObject::connect(&client, &QXmppClient::stateChanged, this, &Account::onClientStateChange);
     QObject::connect(&client, &QXmppClient::presenceReceived, this, &Account::onPresenceReceived);
@@ -92,6 +100,7 @@ Account::Account(const QString& p_login, const QString& p_server, const QString&
     QObject::connect(rcpm, &QXmppMessageReceiptManager::messageDelivered, mh, &MessageHandler::onReceiptReceived);
 
 #ifdef WITH_OMEMO
+    client.addExtension(tm);
     client.addExtension(om);
     qDebug("Added OMEMO manager");
 #endif
diff --git a/core/account.h b/core/account.h
index 1c5d14a..7e5e3b0 100644
--- a/core/account.h
+++ b/core/account.h
@@ -42,9 +42,6 @@
 #include <QXmppUploadRequestManager.h>
 #include <QXmppVCardManager.h>
 #include <QXmppMessageReceiptManager.h>
-#ifdef WITH_OMEMO
-#include <QXmppOmemoManager.h>
-#endif
 
 #include <shared/shared.h>
 #include <shared/identity.h>
@@ -57,6 +54,13 @@
 #include "handlers/vcardhandler.h"
 #include "handlers/discoveryhandler.h"
 
+#ifdef WITH_OMEMO
+#include <QXmppOmemoManager.h>
+#include <QXmppTrustManager.h>
+#include "handlers/trusthandler.h"
+#include "handlers/omemohandler.h"
+#endif
+
 namespace Core
 {
 
@@ -170,6 +174,18 @@ private:
     QXmppConfiguration config;
     QXmppPresence presence;
     Shared::ConnectionState state;
+
+    MessageHandler* mh;
+    RosterHandler* rh;
+    VCardHandler* vh;
+    DiscoveryHandler* dh;
+#ifdef WITH_OMEMO
+    TrustHandler* th;
+    OmemoHandler* oh;
+
+    QXmppTrustManager* tm;
+    QXmppOmemoManager* om;
+#endif
     QXmppCarbonManager* cm;
     QXmppMamManager* am;
     QXmppMucManager* mm;
@@ -179,9 +195,6 @@ private:
     QXmppUploadRequestManager* um;
     QXmppDiscoveryManager* dm;
     QXmppMessageReceiptManager* rcpm;
-#ifdef WITH_OMEMO
-    QXmppOmemoManager* om;
-#endif
     bool reconnectScheduled;
     QTimer* reconnectTimer;
     
@@ -192,11 +205,6 @@ private:
     bool active;
     bool notReadyPassword;
     
-    MessageHandler* mh;
-    RosterHandler* rh;
-    VCardHandler* vh;
-    DiscoveryHandler* dh;
-    
 private slots:
     void onClientStateChange(QXmppClient::State state);
     void onClientError(QXmppClient::Error err);
diff --git a/core/components/clientcache.cpp b/core/components/clientcache.cpp
index a054428..a312b24 100644
--- a/core/components/clientcache.cpp
+++ b/core/components/clientcache.cpp
@@ -19,27 +19,28 @@
 #include <QDebug>
 
 Core::ClientCache::ClientCache():
+    db("clients"),
+    cache(db.addCache<QString, Shared::ClientInfo>("info")),
     requested(),
-    cache("clients"),
     specific()
 {
-    cache.open();
+    db.open();
 }
 
 Core::ClientCache::~ClientCache() {
-    cache.close();
+    db.close();
 }
 
 void Core::ClientCache::open() {
-    cache.open();}
+    db.open();}
 
 void Core::ClientCache::close() {
-    cache.close();}
+    db.close();}
 
 
 bool Core::ClientCache::checkClient(const QString& node, const QString& ver, const QString& hash) {
     QString id = node + "/" + ver;
-    if (requested.count(id) == 0 && !cache.checkRecord(id)) {
+    if (requested.count(id) == 0 && !cache->checkRecord(id)) {
         Shared::ClientInfo& info = requested.insert(std::make_pair(id, Shared::ClientInfo())).first->second;
         info.node = node;
         info.verification = ver;
@@ -65,7 +66,7 @@ bool Core::ClientCache::registerClientInfo (
 
         bool valid = info.valid();
         if (valid) {
-            cache.addRecord(id, info);
+            cache->addRecord(id, info);
         } else {
             info.specificPresence = sourceFullJid;
             specific.insert(std::make_pair(sourceFullJid, info));
diff --git a/core/components/clientcache.h b/core/components/clientcache.h
index 15bcb7d..640def3 100644
--- a/core/components/clientcache.h
+++ b/core/components/clientcache.h
@@ -23,7 +23,8 @@
 #include <QObject>
 #include <QString>
 
-#include <core/storage/cache.h>
+#include <cache.h>
+
 #include <shared/clientinfo.h>
 #include <shared/identity.h>
 
@@ -46,8 +47,9 @@ public slots:
     bool registerClientInfo(const QString& sourceFullJid, const QString& id, const std::set<Shared::Identity>& identities, const std::set<QString>& features);
 
 private:
+    DataBase db;
+    DataBase::Cache<QString, Shared::ClientInfo>* cache;
     std::map<QString, Shared::ClientInfo> requested;
-    Cache<QString, Shared::ClientInfo> cache;
     std::map<QString, Shared::ClientInfo> specific;
 };
 
diff --git a/core/handlers/CMakeLists.txt b/core/handlers/CMakeLists.txt
index 1516aa7..746a36f 100644
--- a/core/handlers/CMakeLists.txt
+++ b/core/handlers/CMakeLists.txt
@@ -9,4 +9,6 @@ target_sources(squawk PRIVATE
   discoveryhandler.h
   omemohandler.cpp
   omemohandler.h
+  trusthandler.cpp
+  trusthandler.h
   )
diff --git a/core/handlers/discoveryhandler.cpp b/core/handlers/discoveryhandler.cpp
index e17c072..2464f07 100644
--- a/core/handlers/discoveryhandler.cpp
+++ b/core/handlers/discoveryhandler.cpp
@@ -21,7 +21,11 @@
 
 Core::DiscoveryHandler::DiscoveryHandler(Core::Account* account):
     QObject(),
-    acc(account)
+    acc(account) {}
+
+Core::DiscoveryHandler::~DiscoveryHandler() {}
+
+void Core::DiscoveryHandler::initialize()
 {
     QObject::connect(acc->dm, &QXmppDiscoveryManager::itemsReceived, this, &DiscoveryHandler::onItemsReceived);
     QObject::connect(acc->dm, &QXmppDiscoveryManager::infoReceived, this, &DiscoveryHandler::onInfoReceived);
@@ -32,11 +36,6 @@ Core::DiscoveryHandler::DiscoveryHandler(Core::Account* account):
     acc->dm->setClientCapabilitiesNode("https://git.macaw.me/blue/squawk");
 }
 
-Core::DiscoveryHandler::~DiscoveryHandler()
-{
-}
-
-
 void Core::DiscoveryHandler::onItemsReceived(const QXmppDiscoveryIq& items)
 {
     QString server = acc->getServer();
diff --git a/core/handlers/discoveryhandler.h b/core/handlers/discoveryhandler.h
index 3129219..e4ef265 100644
--- a/core/handlers/discoveryhandler.h
+++ b/core/handlers/discoveryhandler.h
@@ -32,6 +32,8 @@ public:
     DiscoveryHandler(Account* account);
     ~DiscoveryHandler();
 
+    void initialize();
+
 private slots:
     void onItemsReceived (const QXmppDiscoveryIq& items);
     void onInfoReceived (const QXmppDiscoveryIq& info);
diff --git a/core/handlers/omemohandler.cpp b/core/handlers/omemohandler.cpp
index c1dc7ef..25ac752 100644
--- a/core/handlers/omemohandler.cpp
+++ b/core/handlers/omemohandler.cpp
@@ -22,7 +22,7 @@ Core::OmemoHandler::OmemoHandler(Account* account) :
     QXmppOmemoStorage(),
     acc(account),
     ownDevice(std::nullopt),
-    db("omemo"),
+    db(acc->getName() + "/omemo"),
     meta(db.addCache<QString, QVariant>("meta")),
     devices(db.addCache<QString, QHash<uint32_t, Device>>("devices")),
     preKeyPairs(db.addCache<uint32_t, QByteArray>("preKeyPairs")),
@@ -155,7 +155,60 @@ QFuture<void> Core::OmemoHandler::resetAll() {
     return emptyVoidFuture();
 }
 
+QDataStream & operator >> (QDataStream& in, QXmppOmemoStorage::Device& device) {
+    in >> device.label;
+    in >> device.keyId;
+    in >> device.session;
+    in >> device.unrespondedSentStanzasCount;
+    in >> device.unrespondedReceivedStanzasCount;
+    in >> device.removalFromDeviceListDate;
 
+    return in;
+}
 
+QDataStream & operator << (QDataStream& out, const QXmppOmemoStorage::Device& device) {
+    out << device.label;
+    out << device.keyId;
+    out << device.session;
+    out << device.unrespondedSentStanzasCount;
+    out << device.unrespondedReceivedStanzasCount;
+    out << device.removalFromDeviceListDate;
 
+    return out;
+}
 
+QDataStream & operator >> (QDataStream& in, QXmppOmemoStorage::OwnDevice& device) {
+    in >> device.id;
+    in >> device.label;
+    in >> device.privateIdentityKey;
+    in >> device.publicIdentityKey;
+    in >> device.latestSignedPreKeyId;
+    in >> device.latestPreKeyId;
+
+    return in;
+}
+
+QDataStream & operator << (QDataStream& out, const QXmppOmemoStorage::OwnDevice& device) {
+    out << device.id;
+    out << device.label;
+    out << device.privateIdentityKey;
+    out << device.publicIdentityKey;
+    out << device.latestSignedPreKeyId;
+    out << device.latestPreKeyId;
+
+    return out;
+}
+
+QDataStream & operator >> (QDataStream& in, QXmppOmemoStorage::SignedPreKeyPair& pair) {
+    in >> pair.creationDate;
+    in >> pair.data;
+
+    return in;
+}
+
+QDataStream & operator << (QDataStream& out, const QXmppOmemoStorage::SignedPreKeyPair& pair) {
+    out << pair.creationDate;
+    out << pair.data;
+
+    return out;
+}
diff --git a/core/handlers/omemohandler.h b/core/handlers/omemohandler.h
index 8f3ac9c..537dfec 100644
--- a/core/handlers/omemohandler.h
+++ b/core/handlers/omemohandler.h
@@ -21,6 +21,8 @@
 #include <cache.h>
 
 Q_DECLARE_METATYPE(QXmppOmemoStorage::OwnDevice);
+Q_DECLARE_METATYPE(QXmppOmemoStorage::Device);
+Q_DECLARE_METATYPE(QXmppOmemoStorage::SignedPreKeyPair);
 
 namespace Core {
 class Account;
@@ -63,9 +65,12 @@ private:
 }
 
 QDataStream& operator << (QDataStream &out, const QXmppOmemoStorage::Device& device);
-QDataStream& operator >> (QDataStream &out, QXmppOmemoStorage::Device device);
+QDataStream& operator >> (QDataStream &in, QXmppOmemoStorage::Device& device);
+
+QDataStream& operator << (QDataStream &out, const QXmppOmemoStorage::OwnDevice& device);
+QDataStream& operator >> (QDataStream &in, QXmppOmemoStorage::OwnDevice& device);
 
 QDataStream& operator << (QDataStream &out, const QXmppOmemoStorage::SignedPreKeyPair& device);
-QDataStream& operator >> (QDataStream &out, QXmppOmemoStorage::SignedPreKeyPair device);
+QDataStream& operator >> (QDataStream &in, QXmppOmemoStorage::SignedPreKeyPair& device);
 
 #endif // CORE_OMEMOHANDLER_H
diff --git a/core/handlers/rosterhandler.cpp b/core/handlers/rosterhandler.cpp
index 1a61440..8c65c63 100644
--- a/core/handlers/rosterhandler.cpp
+++ b/core/handlers/rosterhandler.cpp
@@ -26,14 +26,15 @@ Core::RosterHandler::RosterHandler(Core::Account* account):
     conferences(),
     groups(),
     queuedContacts(),
-    outOfRosterContacts()
-{
+    outOfRosterContacts() {}
+
+void Core::RosterHandler::initialize() {
     connect(acc->rm, &QXmppRosterManager::rosterReceived, this, &RosterHandler::onRosterReceived);
     connect(acc->rm, &QXmppRosterManager::itemAdded, this, &RosterHandler::onRosterItemAdded);
     connect(acc->rm, &QXmppRosterManager::itemRemoved, this, &RosterHandler::onRosterItemRemoved);
     connect(acc->rm, &QXmppRosterManager::itemChanged, this, &RosterHandler::onRosterItemChanged);
-    
-    
+
+
     connect(acc->mm, &QXmppMucManager::roomAdded, this, &RosterHandler::onMucRoomAdded);
     connect(acc->bm, &QXmppBookmarkManager::bookmarksReceived, this, &RosterHandler::bookmarksReceived);
 
diff --git a/core/handlers/rosterhandler.h b/core/handlers/rosterhandler.h
index 6a56b15..11525be 100644
--- a/core/handlers/rosterhandler.h
+++ b/core/handlers/rosterhandler.h
@@ -65,6 +65,8 @@ public:
     
     void storeConferences();
     void clearConferences();
+
+    void initialize();
     
 private slots:
     void onRosterReceived();
diff --git a/core/handlers/trusthandler.cpp b/core/handlers/trusthandler.cpp
new file mode 100644
index 0000000..2c0be29
--- /dev/null
+++ b/core/handlers/trusthandler.cpp
@@ -0,0 +1,390 @@
+// Squawk messenger. 
+// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+// 
+// 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 "trusthandler.h"
+#include "core/account.h"
+
+using namespace Core;
+
+Core::TrustHandler::TrustHandler(Account* account):
+    acc(account),
+    db(acc->getName() + "/trust"),
+    protocols(db.createDirectory() + "/protocols"),
+    securityPolicies(db.addCache<QString, uint8_t>("securityPolicies")),
+    ownKeys(db.addCache<QString, QByteArray>("ownKeys")),
+    keysByProtocol()
+{
+    if (!protocols.open(QIODevice::ReadWrite | QIODevice::Text)) {               //never supposed to happen since I have just created a directory;
+        throw DataBase::Directory(protocols.fileName().toStdString());
+    }
+
+    QTextStream in(&protocols);
+    while(!in.atEnd()) {
+        QString protocol = in.readLine();
+
+        if (protocol.size() > 1) {  //I'm afraid of reading some nonsence like separately standing \n or EF or BOM, so... let it be at least 2 chars long
+            KeyCache* cache = db.addCache<QString, Keys>(protocol.toStdString());
+            keysByProtocol.insert(std::make_pair(protocol, cache));
+        }
+    }
+
+    protocols.close();
+    db.open();
+}
+
+Core::TrustHandler::~TrustHandler() {
+    protocols.close();
+    db.close();
+}
+
+Core::TrustHandler::KeyCache * Core::TrustHandler::getCache(const QString& encryption) {
+    std::map<QString, KeyCache*>::iterator itr = keysByProtocol.find(encryption);
+    if (itr == keysByProtocol.end()) {
+        return createNewCache(encryption);
+    } else {
+        return itr->second;
+    }
+}
+
+Core::TrustHandler::KeyCache * Core::TrustHandler::createNewCache(const QString& encryption) {
+    db.close();
+    KeyCache* cache = db.addCache<QString, Keys>(encryption.toStdString());
+    keysByProtocol.insert(std::make_pair(encryption, cache));
+
+    if(!protocols.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text)) {
+        throw DataBase::Directory(protocols.fileName().toStdString());
+    }
+    QTextStream out(&protocols);
+    out << encryption + "\n";
+    protocols.close();
+
+    db.open();
+    return cache;
+}
+
+
+QFuture<void> Core::TrustHandler::emptyVoidFuture() {
+    QFutureInterface<QXmppOmemoStorage::OmemoData> result(QFutureInterfaceBase::Started);
+    result.reportFinished();
+    return result.future();
+}
+
+
+QFuture<void> Core::TrustHandler::resetAll(const QString& encryption) {
+    securityPolicies->removeRecord(encryption);
+    ownKeys->removeRecord(encryption);
+    getCache(encryption)->drop();
+
+    return emptyVoidFuture();
+}
+
+QFuture<QXmpp::TrustLevel> Core::TrustHandler::trustLevel(
+    const QString& encryption,
+    const QString& keyOwnerJid,
+    const QByteArray& keyId)
+{
+    Keys map = getCache(encryption)->getRecord(keyOwnerJid);
+    Shared::TrustLevel level = map.at(keyId);
+
+    QFutureInterface<QXmpp::TrustLevel> result(QFutureInterfaceBase::Started);
+    result.reportResult(convert(level));
+    result.reportFinished();
+    return result.future();
+}
+
+QFuture<QHash<QString, QMultiHash<QString, QByteArray>>> Core::TrustHandler::setTrustLevel(
+    const QString& encryption,
+    const QList<QString>& keyOwnerJids,
+    QXmpp::TrustLevel oldTrustLevel,
+    QXmpp::TrustLevel newTrustLevel)
+{
+    QHash<QString, QMultiHash<QString, QByteArray>> modifiedKeys;
+    Shared::TrustLevel oldLevel = convert(oldTrustLevel);
+    Shared::TrustLevel newLevel = convert(newTrustLevel);
+    KeyCache* cache = getCache(encryption);
+    for (const QString& keyOwnerJid : keyOwnerJids) {
+        Keys map = cache->getRecord(keyOwnerJid);
+        uint count = 0;
+        for (std::pair<const QByteArray, Shared::TrustLevel>& pair : map) {
+            Shared::TrustLevel& current = pair.second;
+            if (current == oldLevel) {
+                current = newLevel;
+                modifiedKeys[encryption].insert(keyOwnerJid, pair.first);
+                ++count;
+            }
+        }
+        if (count > 0) {
+            cache->changeRecord(keyOwnerJid, map);
+        }
+    }
+
+    QFutureInterface<QHash<QString, QMultiHash<QString, QByteArray>>> result(QFutureInterfaceBase::Started);
+    result.reportResult(modifiedKeys);
+    result.reportFinished();
+    return result.future();
+}
+
+QFuture<QHash<QString, QMultiHash<QString, QByteArray>>> Core::TrustHandler::setTrustLevel(
+    const QString& encryption,
+    const QMultiHash<QString, QByteArray>& keyIds,
+    QXmpp::TrustLevel trustLevel)
+{
+    QHash<QString, QMultiHash<QString, QByteArray>> modifiedKeys;
+    Shared::TrustLevel level = convert(trustLevel);
+    KeyCache* cache = getCache(encryption);
+
+    for (MultySB::const_iterator itr = keyIds.begin(), end = keyIds.end(); itr != end; ++itr) {
+        const QString& keyOwnerJid = itr.key();
+        const QByteArray& keyId = itr.value();
+        Keys map = cache->getRecord(keyOwnerJid);
+        std::pair<Keys::iterator, bool> result = map.insert(std::make_pair(keyId, level));
+        if (result.second) {
+            modifiedKeys[encryption].insert(keyOwnerJid, keyId);
+            cache->changeRecord(keyOwnerJid, map);
+        } else if (result.first->second != level) {
+            result.first->second = level;
+            modifiedKeys[encryption].insert(keyOwnerJid, keyId);
+            cache->changeRecord(keyOwnerJid, map);
+        }
+    }
+
+    QFutureInterface<QHash<QString, QMultiHash<QString, QByteArray>>> result(QFutureInterfaceBase::Started);
+    result.reportResult(modifiedKeys);
+    result.reportFinished();
+    return result.future();
+}
+
+QFuture<bool> TrustHandler::hasKey(const QString& encryption,
+                                   const QString& keyOwnerJid,
+                                   QXmpp::TrustLevels trustLevels)
+{
+    KeyCache* cache = getCache(encryption);
+    bool found = false;
+    try {
+        Keys map = cache->getRecord(keyOwnerJid);
+        for (const std::pair<const QByteArray, Shared::TrustLevel>& pair : map) {
+            if (trustLevels.testFlag(convert(pair.second))) {
+                found = true;
+                break;
+            }
+        }
+    } catch (const DataBase::NotFound& e) {}
+
+    QFutureInterface<bool> result(QFutureInterfaceBase::Started);
+    result.reportResult(found);
+    result.reportFinished();
+    return result.future();
+}
+
+QFuture<QHash<QString, QHash<QByteArray, QXmpp::TrustLevel>>> TrustHandler::keys(
+    const QString& encryption,
+    const QList<QString>& keyOwnerJids,
+    QXmpp::TrustLevels trustLevels)
+{
+    HSHBTL res;
+
+    KeyCache* cache = getCache(encryption);
+    for (const QString& keyOwnerJid : keyOwnerJids) {
+        try {
+            Keys map = cache->getRecord(keyOwnerJid);
+            QHash<QByteArray, QXmpp::TrustLevel>& pRes = res[keyOwnerJid];
+            for (const std::pair<const QByteArray, Shared::TrustLevel>& pair : map) {
+                QXmpp::TrustLevel level = convert(pair.second);
+                if (!trustLevels || trustLevels.testFlag(level)) {
+                    pRes.insert(pair.first, level);
+                }
+            }
+        } catch (const DataBase::NotFound& e) {}
+    }
+
+    QFutureInterface<HSHBTL> result(QFutureInterfaceBase::Started);
+    result.reportResult(res);
+    result.reportFinished();
+    return result.future();
+}
+
+QFuture<QHash<QXmpp::TrustLevel, QMultiHash<QString, QByteArray>>> TrustHandler::keys(
+    const QString& encryption,
+    QXmpp::TrustLevels trustLevels)
+{
+    QHash<TL, MultySB> res;
+    KeyCache* cache = getCache(encryption);
+    std::map<QString, Keys> storage = cache->readAll();
+    for (const std::pair<const QString, Keys>& value : storage) {
+        for (const std::pair<const QByteArray, Shared::TrustLevel>& pair : value.second) {
+            QXmpp::TrustLevel level = convert(pair.second);
+            if (!trustLevels || trustLevels.testFlag(level)) {
+                res[level].insert(value.first, pair.first);
+            }
+        }
+    }
+
+    QFutureInterface<QHash<TL, MultySB>> result(QFutureInterfaceBase::Started);
+    result.reportResult(res);
+    result.reportFinished();
+    return result.future();
+}
+
+QFuture<void> TrustHandler::removeKeys(const QString& encryption) {
+    getCache(encryption)->drop();
+    return emptyVoidFuture();
+}
+
+QFuture<void> TrustHandler::removeKeys(const QString& encryption, const QString& keyOwnerJid) {
+    getCache(encryption)->removeRecord(keyOwnerJid);
+    return emptyVoidFuture();
+}
+
+QFuture<void> TrustHandler::removeKeys(const QString& encryption, const QList<QByteArray>& keyIds) {
+    std::set<QByteArray> set;
+    for (const QByteArray& keyId : keyIds) {
+        set.insert(keyId);
+    }
+
+    KeyCache* cache = getCache(encryption);
+    std::map<QString, Keys> data = cache->readAll();
+    bool changed = false;
+    for (std::map<QString, Keys>::iterator cItr = data.begin(), cEnd = data.end(); cItr != cEnd; /*no increment*/) {
+        Keys& byOwner = cItr->second;
+        for (Keys::const_iterator itr = byOwner.begin(), end = byOwner.end(); itr != end; /*no increment*/) {
+            const QByteArray& keyId = itr->first;
+            if (set.erase(keyId)) {
+                byOwner.erase(itr++);
+                changed = true;
+            } else {
+                ++itr;
+            }
+        }
+        if (byOwner.size() > 0) {
+            data.erase(cItr++);
+        } else {
+            ++cItr;
+        }
+    }
+    if (changed) {
+        cache->replaceAll(data);
+    }
+
+    return emptyVoidFuture();
+}
+
+QFuture<void> TrustHandler::addKeys(
+    const QString& encryption,
+    const QString& keyOwnerJid,
+    const QList<QByteArray>& keyIds,
+    QXmpp::TrustLevel trustLevel)
+{
+    KeyCache* cache = getCache(encryption);
+    Shared::TrustLevel level = convert(trustLevel);
+    Keys data;
+    bool had = false;
+    try {
+        data = cache->getRecord(keyOwnerJid);
+        had = true;
+    } catch (const DataBase::NotFound& e) {}
+    for (const QByteArray& keyId : keyIds) {
+        std::pair<Keys::iterator, bool> result = data.insert(std::make_pair(keyId, level));
+        if (!result.second) {
+            result.first->second = level;
+        }
+    }
+
+    if (had) {
+        cache->changeRecord(keyOwnerJid, data);
+    } else {
+        cache->addRecord(keyOwnerJid, data);
+    }
+
+    return emptyVoidFuture();
+}
+
+QFuture<QByteArray> TrustHandler::ownKey(const QString& encryption) {
+    QByteArray res;
+    try {
+        res = ownKeys->getRecord(encryption);
+    } catch (const DataBase::NotFound& e) {}
+
+    QFutureInterface<QByteArray> result(QFutureInterfaceBase::Started);
+    result.reportResult(res);
+    result.reportFinished();
+    return result.future();
+}
+
+QFuture<void> TrustHandler::resetOwnKey(const QString& encryption) {
+    try {
+        ownKeys->removeRecord(encryption);
+    } catch (const DataBase::NotFound& e) {}
+
+    return emptyVoidFuture();
+}
+
+QFuture<void> TrustHandler::setOwnKey(const QString& encryption, const QByteArray& keyId) {
+    ownKeys->forceRecord(encryption, keyId);
+    return emptyVoidFuture();
+}
+
+QFuture<QXmpp::TrustSecurityPolicy> TrustHandler::securityPolicy(const QString& encryption) {
+    QXmpp::TrustSecurityPolicy res;
+    try {
+        res = static_cast<QXmpp::TrustSecurityPolicy>(securityPolicies->getRecord(encryption));
+    } catch (const DataBase::NotFound& e) {}
+
+    QFutureInterface<QXmpp::TrustSecurityPolicy> result(QFutureInterfaceBase::Started);
+    result.reportResult(res);
+    result.reportFinished();
+    return result.future();
+}
+
+QFuture<void> TrustHandler::resetSecurityPolicy(const QString& encryption) {
+    try {
+        securityPolicies->removeRecord(encryption);
+    } catch (const DataBase::NotFound& e) {}
+    return emptyVoidFuture();
+}
+
+QFuture<void> TrustHandler::setSecurityPolicy(
+    const QString& encryption,
+    QXmpp::TrustSecurityPolicy securityPolicy)
+{
+    uint8_t pol = securityPolicy;
+    securityPolicies->forceRecord(encryption, pol);
+
+    return emptyVoidFuture();
+}
+
+Shared::TrustLevel Core::TrustHandler::convert(Core::TrustHandler::TL level)
+{
+    switch (level) {
+        case QXmpp::TrustLevel::Undecided: return Shared::TrustLevel::Undecided;
+        case QXmpp::TrustLevel::AutomaticallyDistrusted: return Shared::TrustLevel::AutomaticallyDistrusted;
+        case QXmpp::TrustLevel::ManuallyDistrusted: return Shared::TrustLevel::ManuallyDistrusted;
+        case QXmpp::TrustLevel::AutomaticallyTrusted: return Shared::TrustLevel::AutomaticallyTrusted;
+        case QXmpp::TrustLevel::ManuallyTrusted: return Shared::TrustLevel::ManuallyTrusted;
+        case QXmpp::TrustLevel::Authenticated: return Shared::TrustLevel::Authenticated;
+    }
+}
+
+Core::TrustHandler::TL Core::TrustHandler::convert(Shared::TrustLevel level)
+{
+    switch (level) {
+        case Shared::TrustLevel::Undecided: return QXmpp::TrustLevel::Undecided;
+        case Shared::TrustLevel::AutomaticallyDistrusted: return QXmpp::TrustLevel::AutomaticallyDistrusted;
+        case Shared::TrustLevel::ManuallyDistrusted: return QXmpp::TrustLevel::ManuallyDistrusted;
+        case Shared::TrustLevel::AutomaticallyTrusted: return QXmpp::TrustLevel::AutomaticallyTrusted;
+        case Shared::TrustLevel::ManuallyTrusted: return QXmpp::TrustLevel::ManuallyTrusted;
+        case Shared::TrustLevel::Authenticated: return QXmpp::TrustLevel::Authenticated;
+    }
+}
diff --git a/core/handlers/trusthandler.h b/core/handlers/trusthandler.h
new file mode 100644
index 0000000..b1fe0b4
--- /dev/null
+++ b/core/handlers/trusthandler.h
@@ -0,0 +1,81 @@
+// Squawk messenger. 
+// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+// 
+// 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/>.
+
+#ifndef CORE_TRUSTHANDLER_H
+#define CORE_TRUSTHANDLER_H
+
+#include <shared/enums.h>
+
+#include <QXmppTrustStorage.h>
+#include <cache.h>
+
+namespace Core {
+class Account;
+
+class TrustHandler : public QXmppTrustStorage {
+public:
+    TrustHandler(Account* account);
+    ~TrustHandler();
+
+    typedef QMultiHash<QString, QByteArray> MultySB;
+    typedef QHash<QString, MultySB> HashSM;
+    typedef const QList<QString>& CLSR;
+    typedef const QList<QByteArray>& CLBAR;
+    typedef const QString& CSR;
+    typedef QXmpp::TrustLevel TL;
+    typedef QHash<QString, QHash<QByteArray, TL>> HSHBTL;
+
+    typedef std::map<QByteArray, Shared::TrustLevel> Keys;
+    typedef DataBase::Cache<QString, Keys> KeyCache;
+
+    virtual QFuture<void> resetAll(CSR encryption);
+    virtual QFuture<TL> trustLevel(CSR encryption, CSR keyOwnerJid, const QByteArray& keyId);
+    virtual QFuture<HashSM> setTrustLevel(CSR encryption, CLSR keyOwnerJids, TL oldTrustLevel, TL newTrustLevel);
+    virtual QFuture<HashSM> setTrustLevel(CSR encryption, const MultySB& keyIds, TL trustLevel);
+    virtual QFuture<bool> hasKey(CSR encryption, CSR keyOwnerJid, QXmpp::TrustLevels trustLevels);
+    virtual QFuture<HSHBTL> keys(CSR encryption, CLSR keyOwnerJids, QXmpp::TrustLevels trustLevels);
+    virtual QFuture<QHash<TL, MultySB>> keys(CSR encryption, QXmpp::TrustLevels trustLevels);
+    virtual QFuture<void> removeKeys(CSR encryption);
+    virtual QFuture<void> removeKeys(CSR encryption, CSR keyOwnerJid);
+    virtual QFuture<void> removeKeys(CSR encryption, CLBAR keyIds);
+    virtual QFuture<void> addKeys(CSR encryption, CSR keyOwnerJid, CLBAR keyIds, TL trustLevel);
+    virtual QFuture<QByteArray> ownKey(CSR encryption);
+    virtual QFuture<void> resetOwnKey(CSR encryption);
+    virtual QFuture<void> setOwnKey(CSR encryption, const QByteArray& keyId);
+    virtual QFuture<QXmpp::TrustSecurityPolicy> securityPolicy(CSR encryption);
+    virtual QFuture<void> resetSecurityPolicy(CSR encryption);
+    virtual QFuture<void> setSecurityPolicy(CSR encryption, QXmpp::TrustSecurityPolicy securityPolicy);
+
+    static TL convert(Shared::TrustLevel level);
+    static Shared::TrustLevel convert(TL level);
+
+private:
+    static QFuture<void> emptyVoidFuture();
+    KeyCache* createNewCache(const QString& encryption);
+    KeyCache* getCache(const QString& encryption);
+
+private:
+    Account* acc;
+    DataBase db;
+    QFile protocols;
+    DataBase::Cache<QString, uint8_t>* securityPolicies;
+    DataBase::Cache<QString, QByteArray>* ownKeys;
+    std::map<QString, KeyCache*> keysByProtocol;
+};
+
+}
+
+#endif // CORE_TRUSTHANDLER_H
diff --git a/core/handlers/vcardhandler.cpp b/core/handlers/vcardhandler.cpp
index 24cb6ee..bde0e64 100644
--- a/core/handlers/vcardhandler.cpp
+++ b/core/handlers/vcardhandler.cpp
@@ -25,10 +25,6 @@ Core::VCardHandler::VCardHandler(Account* account):
     avatarHash(),
     avatarType()
 {
-    connect(acc->vm, &QXmppVCardManager::vCardReceived, this, &VCardHandler::onVCardReceived);
-    //for some reason it doesn't work, launching from common handler
-    //connect(acc->vm, &QXmppVCardManager::clientVCardReceived, this, &VCardHandler::onOwnVCardReceived);
-
     QString path(QStandardPaths::writableLocation(QStandardPaths::CacheLocation));
     path += "/" + acc->name;
     QDir dir(path);
@@ -67,6 +63,15 @@ Core::VCardHandler::VCardHandler(Account* account):
             avatarType = type;
         }
     }
+}
+
+Core::VCardHandler::~VCardHandler() {}
+
+void Core::VCardHandler::initialize() {
+    connect(acc->vm, &QXmppVCardManager::vCardReceived, this, &VCardHandler::onVCardReceived);
+    //for some reason it doesn't work, launching from common handler
+    //connect(acc->vm, &QXmppVCardManager::clientVCardReceived, this, &VCardHandler::onOwnVCardReceived);
+
     if (avatarType.size() != 0) {
         acc->presence.setVCardUpdateType(QXmppPresence::VCardUpdateValidPhoto);
         acc->presence.setPhotoHash(avatarHash.toUtf8());
@@ -75,10 +80,6 @@ Core::VCardHandler::VCardHandler(Account* account):
     }
 }
 
-Core::VCardHandler::~VCardHandler()
-{
-
-}
 
 void Core::VCardHandler::onVCardReceived(const QXmppVCardIq& card)
 {
diff --git a/core/handlers/vcardhandler.h b/core/handlers/vcardhandler.h
index ee61d0a..f774cd5 100644
--- a/core/handlers/vcardhandler.h
+++ b/core/handlers/vcardhandler.h
@@ -48,6 +48,8 @@ public:
     void uploadVCard(const Shared::VCard& card);
     QString getAvatarPath() const;
 
+    void initialize();
+
 private slots:
     void onVCardReceived(const QXmppVCardIq& card);
     void onOwnVCardReceived(const QXmppVCardIq& card);
diff --git a/core/storage/CMakeLists.txt b/core/storage/CMakeLists.txt
index 2da3c67..238b59a 100644
--- a/core/storage/CMakeLists.txt
+++ b/core/storage/CMakeLists.txt
@@ -1,10 +1,10 @@
 target_sources(squawk PRIVATE
     archive.cpp
     archive.h
-    storage.hpp
-    storage.h
+#    storage.hpp
+#    storage.h
     urlstorage.cpp
     urlstorage.h
-    cache.hpp
-    cache.h
+#    cache.hpp
+#    cache.h
 )
diff --git a/external/qxmpp b/external/qxmpp
index f6e7591..befab2f 160000
--- a/external/qxmpp
+++ b/external/qxmpp
@@ -1 +1 @@
-Subproject commit f6e7591e21b4c55319918ac296b2341b2e9f1988
+Subproject commit befab2fe2e71330170bba48f173258be724c65b9
diff --git a/external/storage b/external/storage
index 08daad4..a79dae8 160000
--- a/external/storage
+++ b/external/storage
@@ -1 +1 @@
-Subproject commit 08daad48ad36d9f12dc6485a085190e31e4cf49e
+Subproject commit a79dae8fd07b36446041f6f85e2ad42cf5ae5264
diff --git a/shared/enums.h b/shared/enums.h
index 273a22d..a32b57e 100644
--- a/shared/enums.h
+++ b/shared/enums.h
@@ -124,5 +124,25 @@ enum class Support {
 };
 Q_ENUM_NS(Support)
 
+enum class TrustLevel {
+    /// The key's trust is not decided.
+    Undecided,
+    /// The key is automatically distrusted (e.g., by the security policy TOAKAFA).
+    /// \see SecurityPolicy
+    AutomaticallyDistrusted,
+    /// The key is manually distrusted (e.g., by clicking a button or \xep{0450, Automatic Trust
+    /// Management (ATM)}).
+    ManuallyDistrusted,
+    /// The key is automatically trusted (e.g., by the client for all keys of a bare JID until one
+    /// of it is authenticated).
+    AutomaticallyTrusted,
+    /// The key is manually trusted (e.g., by clicking a button).
+    ManuallyTrusted,
+    /// The key is authenticated (e.g., by QR code scanning or \xep{0450, Automatic Trust
+    /// Management (ATM)}).
+    Authenticated
+};
+Q_ENUM_NS(TrustLevel)
+
 }
 #endif // SHARED_ENUMS_H

From dfe72ca36c998e39b8bd4a1e9f44adcfe1b28089 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Tue, 27 Dec 2022 01:01:01 +0300
Subject: [PATCH 216/281] support of the new managers in account code, new
 states, new lambdas, even launches now, even receives some bundles

---
 core/account.cpp                   | 74 +++++++++++++++++++++++++++---
 core/account.h                     |  4 ++
 core/handlers/discoveryhandler.cpp | 16 ++++++-
 core/handlers/discoveryhandler.h   |  1 +
 core/handlers/omemohandler.cpp     |  8 +++-
 core/handlers/omemohandler.h       |  2 +
 core/squawk.cpp                    | 11 +++--
 core/squawk.h                      |  9 ----
 main/main.cpp                      |  3 ++
 shared/enums.h                     |  3 +-
 shared/global.cpp                  |  3 +-
 shared/icons.h                     |  4 ++
 12 files changed, 113 insertions(+), 25 deletions(-)

diff --git a/core/account.cpp b/core/account.cpp
index 4b25737..e1bbfb9 100644
--- a/core/account.cpp
+++ b/core/account.cpp
@@ -51,6 +51,7 @@ Account::Account(const QString& p_login, const QString& p_server, const QString&
     um(new QXmppUploadRequestManager()),
     dm(client.findExtension<QXmppDiscoveryManager>()),
     rcpm(new QXmppMessageReceiptManager()),
+    psm(new QXmppPubSubManager()),
     reconnectScheduled(false),
     reconnectTimer(new QTimer),
     network(p_net),
@@ -58,7 +59,8 @@ Account::Account(const QString& p_login, const QString& p_server, const QString&
     lastError(Error::none),
     pepSupport(Shared::Support::unknown),
     active(p_active),
-    notReadyPassword(false)
+    notReadyPassword(false),
+    loadingOmemo(false)
 {
     config.setUser(p_login);
     config.setDomain(p_server);
@@ -99,10 +101,31 @@ Account::Account(const QString& p_login, const QString& p_server, const QString&
     client.addExtension(rcpm);
     QObject::connect(rcpm, &QXmppMessageReceiptManager::messageDelivered, mh, &MessageHandler::onReceiptReceived);
 
+    client.addExtension(psm);
+
 #ifdef WITH_OMEMO
     client.addExtension(tm);
     client.addExtension(om);
-    qDebug("Added OMEMO manager");
+
+    if (oh->hasOwnDevice()) {
+        QFuture<bool> future = om->load();
+        loadingOmemo = true;
+
+        QFutureWatcher<bool> *watcher = new QFutureWatcher<bool>(this);
+        QObject::connect(watcher, &QFutureWatcherBase::finished, [watcher, this] () {
+            loadingOmemo = false;
+            if (state == Shared::ConnectionState::scheduled) {
+                client.connectToServer(config, presence);
+            }
+            if (watcher->result()) {
+                qDebug() << "successfully loaded OMEMO data for account" << getName();
+            } else {
+                qDebug() << "couldn't load OMEMO data for account" << getName();
+            }
+            watcher->deleteLater();
+        });
+        watcher->setFuture(future);
+    }
 #endif
     
     reconnectTimer->setSingleShot(true);
@@ -162,7 +185,12 @@ void Core::Account::connect()
         if (notReadyPassword) {
             emit needPassword();
         } else {
-            client.connectToServer(config, presence);
+            if (loadingOmemo) {
+                state = Shared::ConnectionState::scheduled;
+                emit connectionStateChanged(state);
+            } else {
+                client.connectToServer(config, presence);
+            }
         }
 
     } else {
@@ -186,7 +214,12 @@ void Core::Account::disconnect()
     }
     if (state != Shared::ConnectionState::disconnected) {
         //rh->clearConferences();
-        client.disconnectFromServer();
+        if (state != Shared::ConnectionState::scheduled) {
+            client.disconnectFromServer();
+        } else {
+            state = Shared::ConnectionState::disconnected;
+            emit connectionStateChanged(state);
+        }
     }
 }
 
@@ -199,9 +232,29 @@ void Core::Account::onClientStateChange(QXmppClient::State st)
                     Shared::ConnectionState os = state;
                     state = Shared::ConnectionState::connected;
                     if (os == Shared::ConnectionState::connecting) {
-                        qDebug() << "running service discovery for account" << name;
-                        dm->requestItems(getServer());
-                        dm->requestInfo(getServer());
+#ifdef WITH_OMEMO
+                        if (!oh->hasOwnDevice()) {
+                            qDebug() << "setting up OMEMO data for account" << getName();
+                            QFuture<bool> future = om->setUp();
+                            QFutureWatcher<bool> *watcher = new QFutureWatcher<bool>(this);
+                            QObject::connect(watcher, &QFutureWatcherBase::finished, [watcher, this] () {
+                                if (watcher->result()) {
+                                    qDebug() << "successfully set up OMEMO data for account" << getName();
+                                } else {
+                                    qDebug() << "couldn't set up OMEMO data for account" << getName();
+                                }
+                                watcher->deleteLater();
+                                if (state == Shared::ConnectionState::connected) {
+                                    runDiscoveryService();
+                                }
+                            });
+                            watcher->setFuture(future);
+                        } else {
+                            runDiscoveryService();
+                        }
+#else
+                        runDiscoveryService();
+#endif
                     }
                     lastError = Error::none;
                     emit connectionStateChanged(state);
@@ -270,6 +323,13 @@ void Core::Account::setAvailability(Shared::Availability avail)
     }
 }
 
+void Core::Account::runDiscoveryService() {
+    qDebug() << "running service discovery for account" << name;
+    dm->requestItems(getServer());
+    dm->requestInfo(getServer());
+}
+
+
 void Core::Account::onPresenceReceived(const QXmppPresence& p_presence)
 {
     QString id = p_presence.from();
diff --git a/core/account.h b/core/account.h
index 7e5e3b0..393b6e6 100644
--- a/core/account.h
+++ b/core/account.h
@@ -42,6 +42,7 @@
 #include <QXmppUploadRequestManager.h>
 #include <QXmppVCardManager.h>
 #include <QXmppMessageReceiptManager.h>
+#include <QXmppPubSubManager.h>
 
 #include <shared/shared.h>
 #include <shared/identity.h>
@@ -195,6 +196,7 @@ private:
     QXmppUploadRequestManager* um;
     QXmppDiscoveryManager* dm;
     QXmppMessageReceiptManager* rcpm;
+    QXmppPubSubManager* psm;
     bool reconnectScheduled;
     QTimer* reconnectTimer;
     
@@ -204,6 +206,7 @@ private:
     Shared::Support pepSupport;
     bool active;
     bool notReadyPassword;
+    bool loadingOmemo;
     
 private slots:
     void onClientStateChange(QXmppClient::State state);
@@ -223,6 +226,7 @@ private:
     void handleDisconnection();
     void onReconnectTimer();
     void setPepSupport(Shared::Support support);
+    void runDiscoveryService();
 };
 }
 
diff --git a/core/handlers/discoveryhandler.cpp b/core/handlers/discoveryhandler.cpp
index 2464f07..20e982c 100644
--- a/core/handlers/discoveryhandler.cpp
+++ b/core/handlers/discoveryhandler.cpp
@@ -21,7 +21,8 @@
 
 Core::DiscoveryHandler::DiscoveryHandler(Core::Account* account):
     QObject(),
-    acc(account) {}
+    acc(account),
+    omemoToCarbonsConnected (false) {}
 
 Core::DiscoveryHandler::~DiscoveryHandler() {}
 
@@ -79,6 +80,19 @@ void Core::DiscoveryHandler::onInfoReceived(const QXmppDiscoveryIq& info)
         if (enableCC) {
             qDebug() << "Enabling carbon copies for account" << accName;
             acc->cm->setCarbonsEnabled(true);
+#ifdef WITH_OMEMO
+            if (!omemoToCarbonsConnected && acc->oh->hasOwnDevice()) {
+                // connect(this, &QXmppCarbonManager::messageSent, acc->om, &QXmppOmemoManager::handleMessage);
+                // connect(this, &QXmppCarbonManager::messageReceived, acc->om, &QXmppOmemoManager::handleMessage);
+                omemoToCarbonsConnected = true;
+            }
+        } else {
+            if (omemoToCarbonsConnected) {
+                // disconnect(this, &QXmppCarbonManager::messageSent, acc->om, &QXmppOmemoManager::handleMessage);
+                // disconnect(this, &QXmppCarbonManager::messageReceived, acc->om, &QXmppOmemoManager::handleMessage);
+                omemoToCarbonsConnected = false;
+            }
+#endif
         }
 
         qDebug() << "Requesting account" << accName << "capabilities";
diff --git a/core/handlers/discoveryhandler.h b/core/handlers/discoveryhandler.h
index e4ef265..4bf5baf 100644
--- a/core/handlers/discoveryhandler.h
+++ b/core/handlers/discoveryhandler.h
@@ -40,6 +40,7 @@ private slots:
 
 private:
     Account* acc;
+    bool omemoToCarbonsConnected;
 };
 
 }
diff --git a/core/handlers/omemohandler.cpp b/core/handlers/omemohandler.cpp
index 25ac752..8ce5932 100644
--- a/core/handlers/omemohandler.cpp
+++ b/core/handlers/omemohandler.cpp
@@ -31,7 +31,7 @@ Core::OmemoHandler::OmemoHandler(Account* account) :
     db.open();
     try {
         QVariant own = meta->getRecord("ownDevice");
-        ownDevice.value() = own.value<OwnDevice>();
+        ownDevice = own.value<OwnDevice>();
         qDebug() << "Successfully found own device omemo data for account" << acc->getName();
     } catch (const DataBase::NotFound& e) {
         qDebug() << "No device omemo data was found for account" << acc->getName();
@@ -42,6 +42,10 @@ Core::OmemoHandler::~OmemoHandler() {
     db.close();
 }
 
+bool Core::OmemoHandler::hasOwnDevice() {
+    return ownDevice.has_value();
+}
+
 QFuture<void> Core::OmemoHandler::emptyVoidFuture() {
     QFutureInterface<QXmppOmemoStorage::OmemoData> result(QFutureInterfaceBase::Started);
     result.reportFinished();
@@ -101,7 +105,7 @@ QFuture<void> Core::OmemoHandler::addPreKeyPairs(const QHash<uint32_t, QByteArra
 }
 
 QFuture<void> Core::OmemoHandler::addSignedPreKeyPair(uint32_t keyId, const QXmppOmemoStorage::SignedPreKeyPair& keyPair) {
-    signedPreKeyPairs->addRecord(keyId, keyPair);
+    signedPreKeyPairs->forceRecord(keyId, keyPair);
     return emptyVoidFuture();
 }
 
diff --git a/core/handlers/omemohandler.h b/core/handlers/omemohandler.h
index 537dfec..bae14cb 100644
--- a/core/handlers/omemohandler.h
+++ b/core/handlers/omemohandler.h
@@ -49,6 +49,8 @@ public:
 
     QFuture<void> resetAll() override;
 
+    bool hasOwnDevice();
+
 private:
     static QFuture<void> emptyVoidFuture();
 
diff --git a/core/squawk.cpp b/core/squawk.cpp
index 3b976b0..6600fcb 100644
--- a/core/squawk.cpp
+++ b/core/squawk.cpp
@@ -265,10 +265,13 @@ void Core::Squawk::disconnectAccount(const QString& account)
 void Core::Squawk::onAccountConnectionStateChanged(Shared::ConnectionState p_state)
 {
     Account* acc = static_cast<Account*>(sender());
-    emit changeAccount(acc->getName(), {
-        {"state", QVariant::fromValue(p_state)},
-        {"error", ""}
-    });
+    QMap<QString, QVariant> changes = {
+        {"state", QVariant::fromValue(p_state)}
+    };
+    if (acc->getLastError() == Account::Error::none) {
+        changes.insert("error", "");
+    }
+    emit changeAccount(acc->getName(), changes);
 
 #ifdef WITH_KWALLET
     if (p_state == Shared::ConnectionState::connected) {
diff --git a/core/squawk.h b/core/squawk.h
index cdaaf6e..da4aa7e 100644
--- a/core/squawk.h
+++ b/core/squawk.h
@@ -187,15 +187,6 @@ private slots:
     
 private:
     void readSettings();
-    void parseAccount(
-        const QString& login, 
-        const QString& server, 
-        const QString& password, 
-        const QString& name, 
-        const QString& resource, 
-        bool active,
-        Shared::AccountPassword passwordType
-    );
     
     static const quint64 passwordHash = 0x08d054225ac4871d;
 };
diff --git a/main/main.cpp b/main/main.cpp
index 58063e0..020d755 100644
--- a/main/main.cpp
+++ b/main/main.cpp
@@ -52,6 +52,9 @@ int main(int argc, char *argv[])
     qRegisterMetaType<Shared::Availability>("Shared::Availability");
 #ifdef WITH_OMEMO
     qRegisterMetaType<QXmppOmemoStorage::OwnDevice>("QXmppOmemoStorage::OwnDevice");
+    qRegisterMetaTypeStreamOperators<QXmppOmemoStorage::OwnDevice>("QXmppOmemoStorage::OwnDevice");
+    qRegisterMetaType<QXmppOmemoStorage::Device>("QXmppOmemoStorage::Device");
+    qRegisterMetaType<QXmppOmemoStorage::SignedPreKeyPair>("QXmppOmemoStorage::SignedPreKeyPair");
 #endif
     
     QApplication app(argc, argv);
diff --git a/shared/enums.h b/shared/enums.h
index a32b57e..7a1f092 100644
--- a/shared/enums.h
+++ b/shared/enums.h
@@ -29,12 +29,13 @@ Q_NAMESPACE
     
 enum class ConnectionState {
     disconnected,
+    scheduled,
     connecting,
     connected,
     error
 };
 Q_ENUM_NS(ConnectionState)
-static const std::deque<QString> connectionStateThemeIcons = {"state-offline", "state-sync", "state-ok", "state-error"};
+static const std::deque<QString> connectionStateThemeIcons = {"state-offline", "state-sync", "state-sync", "state-ok", "state-error"};
 static const ConnectionState ConnectionStateHighest = ConnectionState::error;
 static const ConnectionState ConnectionStateLowest = ConnectionState::disconnected;
 
diff --git a/shared/global.cpp b/shared/global.cpp
index 6519952..a6f17dc 100644
--- a/shared/global.cpp
+++ b/shared/global.cpp
@@ -49,7 +49,8 @@ Shared::Global::Global():
     }),
     connectionState({
         tr("Disconnected", "ConnectionState"), 
-        tr("Connecting", "ConnectionState"), 
+        tr("Scheduled", "ConnectionState"),
+        tr("Connecting", "ConnectionState"),
         tr("Connected", "ConnectionState"), 
         tr("Error", "ConnectionState")
     }),
diff --git a/shared/icons.h b/shared/icons.h
index 540d3e9..cf31b3b 100644
--- a/shared/icons.h
+++ b/shared/icons.h
@@ -48,6 +48,7 @@ static const std::deque<QString> fallbackSubscriptionStateThemeIconsLightBig = {
 static const std::deque<QString> fallbackConnectionStateThemeIconsLightBig = {
     ":images/fallback/light/big/state-offline.svg",
     ":images/fallback/light/big/state-sync.svg",
+    ":images/fallback/light/big/state-sync.svg",
     ":images/fallback/light/big/state-ok.svg",
     ":images/fallback/light/big/state-error.svg"
 };
@@ -73,6 +74,7 @@ static const std::deque<QString> fallbackSubscriptionStateThemeIconsLightSmall =
 static const std::deque<QString> fallbackConnectionStateThemeIconsLightSmall = {
     ":images/fallback/light/small/state-offline.svg",
     ":images/fallback/light/small/state-sync.svg",
+    ":images/fallback/light/small/state-sync.svg",
     ":images/fallback/light/small/state-ok.svg",
     ":images/fallback/light/small/state-error.svg"
 };
@@ -98,6 +100,7 @@ static const std::deque<QString> fallbackSubscriptionStateThemeIconsDarkBig = {
 static const std::deque<QString> fallbackConnectionStateThemeIconsDarkBig = {
     ":images/fallback/dark/big/state-offline.svg",
     ":images/fallback/dark/big/state-sync.svg",
+    ":images/fallback/dark/big/state-sync.svg",
     ":images/fallback/dark/big/state-ok.svg",
     ":images/fallback/dark/big/state-error.svg"
 };
@@ -123,6 +126,7 @@ static const std::deque<QString> fallbackSubscriptionStateThemeIconsDarkSmall =
 static const std::deque<QString> fallbackConnectionStateThemeIconsDarkSmall = {
     ":images/fallback/dark/small/state-offline.svg",
     ":images/fallback/dark/small/state-sync.svg",
+    ":images/fallback/dark/small/state-sync.svg",
     ":images/fallback/dark/small/state-ok.svg",
     ":images/fallback/dark/small/state-error.svg"
 };

From 758a9d95f3e738f9c352b37e5753ff510e185a68 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Thu, 29 Dec 2022 01:41:59 +0300
Subject: [PATCH 217/281] replaced one structure, stored omemo support in
 Global object

---
 core/handlers/omemohandler.cpp | 25 ++++++-------------------
 core/handlers/omemohandler.h   | 10 ++++------
 main/main.cpp                  |  1 -
 shared/global.cpp              |  6 ++++++
 shared/global.h                |  1 +
 5 files changed, 17 insertions(+), 26 deletions(-)

diff --git a/core/handlers/omemohandler.cpp b/core/handlers/omemohandler.cpp
index 8ce5932..57d9749 100644
--- a/core/handlers/omemohandler.cpp
+++ b/core/handlers/omemohandler.cpp
@@ -26,7 +26,7 @@ Core::OmemoHandler::OmemoHandler(Account* account) :
     meta(db.addCache<QString, QVariant>("meta")),
     devices(db.addCache<QString, QHash<uint32_t, Device>>("devices")),
     preKeyPairs(db.addCache<uint32_t, QByteArray>("preKeyPairs")),
-    signedPreKeyPairs(db.addCache<uint32_t, QXmppOmemoStorage::SignedPreKeyPair>("signedPreKeyPairs"))
+    signedPreKeyPairs(db.addCache<uint32_t, SignedPreKeyPair>("signedPreKeyPairs"))
 {
     db.open();
     try {
@@ -61,9 +61,10 @@ QFuture<QXmppOmemoStorage::OmemoData> Core::OmemoHandler::allData() {
         data.preKeyPairs.insert(pair.first, pair.second);
     }
 
-    std::map<uint32_t, QXmppOmemoStorage::SignedPreKeyPair> spre = signedPreKeyPairs->readAll();
-    for (const std::pair<const uint32_t, QXmppOmemoStorage::SignedPreKeyPair>& pair : spre) {
-        data.signedPreKeyPairs.insert(pair.first, pair.second);
+    std::map<uint32_t, SignedPreKeyPair> spre = signedPreKeyPairs->readAll();
+    for (const std::pair<const uint32_t, SignedPreKeyPair>& pair : spre) {
+        QXmppOmemoStorage::SignedPreKeyPair qxpair = {pair.second.first, pair.second.second};
+        data.signedPreKeyPairs.insert(pair.first, qxpair);
     }
 
     std::map<QString, QHash<uint32_t, Device>> devs = devices->readAll();
@@ -105,7 +106,7 @@ QFuture<void> Core::OmemoHandler::addPreKeyPairs(const QHash<uint32_t, QByteArra
 }
 
 QFuture<void> Core::OmemoHandler::addSignedPreKeyPair(uint32_t keyId, const QXmppOmemoStorage::SignedPreKeyPair& keyPair) {
-    signedPreKeyPairs->forceRecord(keyId, keyPair);
+    signedPreKeyPairs->forceRecord(keyId, std::make_pair(keyPair.creationDate, keyPair.data));
     return emptyVoidFuture();
 }
 
@@ -202,17 +203,3 @@ QDataStream & operator << (QDataStream& out, const QXmppOmemoStorage::OwnDevice&
 
     return out;
 }
-
-QDataStream & operator >> (QDataStream& in, QXmppOmemoStorage::SignedPreKeyPair& pair) {
-    in >> pair.creationDate;
-    in >> pair.data;
-
-    return in;
-}
-
-QDataStream & operator << (QDataStream& out, const QXmppOmemoStorage::SignedPreKeyPair& pair) {
-    out << pair.creationDate;
-    out << pair.data;
-
-    return out;
-}
diff --git a/core/handlers/omemohandler.h b/core/handlers/omemohandler.h
index bae14cb..cea9603 100644
--- a/core/handlers/omemohandler.h
+++ b/core/handlers/omemohandler.h
@@ -22,7 +22,6 @@
 
 Q_DECLARE_METATYPE(QXmppOmemoStorage::OwnDevice);
 Q_DECLARE_METATYPE(QXmppOmemoStorage::Device);
-Q_DECLARE_METATYPE(QXmppOmemoStorage::SignedPreKeyPair);
 
 namespace Core {
 class Account;
@@ -30,6 +29,8 @@ class Account;
 class OmemoHandler : public QXmppOmemoStorage
 {
 public:
+    typedef std::pair<QDateTime, QByteArray> SignedPreKeyPair;
+
     OmemoHandler(Account* account);
     ~OmemoHandler() override;
 
@@ -37,7 +38,7 @@ public:
 
     QFuture<void> setOwnDevice(const std::optional<OwnDevice> &device) override;
 
-    QFuture<void> addSignedPreKeyPair(uint32_t keyId, const SignedPreKeyPair &keyPair) override;
+    QFuture<void> addSignedPreKeyPair(uint32_t keyId, const QXmppOmemoStorage::SignedPreKeyPair &keyPair) override;
     QFuture<void> removeSignedPreKeyPair(uint32_t keyId) override;
 
     QFuture<void> addPreKeyPairs(const QHash<uint32_t, QByteArray> &keyPairs) override;
@@ -61,7 +62,7 @@ private:
     DataBase::Cache<QString, QVariant>* meta;
     DataBase::Cache<QString, QHash<uint32_t, Device>>* devices;
     DataBase::Cache<uint32_t, QByteArray>* preKeyPairs;
-    DataBase::Cache<uint32_t, QXmppOmemoStorage::SignedPreKeyPair>* signedPreKeyPairs;
+    DataBase::Cache<uint32_t, SignedPreKeyPair>* signedPreKeyPairs;
 };
 
 }
@@ -72,7 +73,4 @@ QDataStream& operator >> (QDataStream &in, QXmppOmemoStorage::Device& device);
 QDataStream& operator << (QDataStream &out, const QXmppOmemoStorage::OwnDevice& device);
 QDataStream& operator >> (QDataStream &in, QXmppOmemoStorage::OwnDevice& device);
 
-QDataStream& operator << (QDataStream &out, const QXmppOmemoStorage::SignedPreKeyPair& device);
-QDataStream& operator >> (QDataStream &in, QXmppOmemoStorage::SignedPreKeyPair& device);
-
 #endif // CORE_OMEMOHANDLER_H
diff --git a/main/main.cpp b/main/main.cpp
index 020d755..2958b66 100644
--- a/main/main.cpp
+++ b/main/main.cpp
@@ -54,7 +54,6 @@ int main(int argc, char *argv[])
     qRegisterMetaType<QXmppOmemoStorage::OwnDevice>("QXmppOmemoStorage::OwnDevice");
     qRegisterMetaTypeStreamOperators<QXmppOmemoStorage::OwnDevice>("QXmppOmemoStorage::OwnDevice");
     qRegisterMetaType<QXmppOmemoStorage::Device>("QXmppOmemoStorage::Device");
-    qRegisterMetaType<QXmppOmemoStorage::SignedPreKeyPair>("QXmppOmemoStorage::SignedPreKeyPair");
 #endif
     
     QApplication app(argc, argv);
diff --git a/shared/global.cpp b/shared/global.cpp
index a6f17dc..2e2978a 100644
--- a/shared/global.cpp
+++ b/shared/global.cpp
@@ -20,6 +20,11 @@
 
 #include "enums.h"
 #include "ui/models/roster.h"
+#ifdef WITH_OMEMO
+constexpr bool OMEMO_SUPPORT = true;
+#else
+constexpr bool OMEMO_SUPPORT = false
+#endif
 
 Shared::Global* Shared::Global::instance = 0;
 const std::set<QString> Shared::Global::supportedImagesExts = {"png", "jpg", "webp", "jpeg", "gif", "svg"};
@@ -96,6 +101,7 @@ Shared::Global::Global():
     }),
     defaultSystemStyle(QApplication::style()->objectName()),
     defaultSystemPalette(QApplication::palette()),
+    omemoSupport(OMEMO_SUPPORT),
     pluginSupport({
         {"KWallet", false},
         {"openFileManagerWindowJob", false},
diff --git a/shared/global.h b/shared/global.h
index ebed931..d212cd1 100644
--- a/shared/global.h
+++ b/shared/global.h
@@ -99,6 +99,7 @@ namespace Shared {
         static QString getColorSchemeName(const QString& path);
         static void setTheme(const QString& path);
         static void setStyle(const QString& style);
+        const bool omemoSupport;
         
         template<typename T>
         static T fromInt(int src);

From b45a73b723039caf61c74143dd16f9ab484ebd14 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Sun, 1 Jan 2023 20:25:51 +0300
Subject: [PATCH 218/281] some initial work and thoughts about encryption

---
 core/handlers/trusthandler.cpp        |  24 ++---
 main/main.cpp                         |   1 +
 shared/CMakeLists.txt                 |   2 +
 shared/enums.h                        |  17 +--
 shared/keyinfo.cpp                    |  46 +++++++++
 shared/keyinfo.h                      |  55 ++++++++++
 shared/shared.h                       |   1 +
 ui/widgets/vcard/CMakeLists.txt       |   4 +
 ui/widgets/vcard/omemo/CMakeLists.txt |   5 +
 ui/widgets/vcard/omemo/omemo.cpp      |  20 ++++
 ui/widgets/vcard/omemo/omemo.h        |  43 ++++++++
 ui/widgets/vcard/omemo/omemo.ui       | 142 ++++++++++++++++++++++++++
 12 files changed, 342 insertions(+), 18 deletions(-)
 create mode 100644 shared/keyinfo.cpp
 create mode 100644 shared/keyinfo.h
 create mode 100644 ui/widgets/vcard/omemo/CMakeLists.txt
 create mode 100644 ui/widgets/vcard/omemo/omemo.cpp
 create mode 100644 ui/widgets/vcard/omemo/omemo.h
 create mode 100644 ui/widgets/vcard/omemo/omemo.ui

diff --git a/core/handlers/trusthandler.cpp b/core/handlers/trusthandler.cpp
index 2c0be29..caaaf0e 100644
--- a/core/handlers/trusthandler.cpp
+++ b/core/handlers/trusthandler.cpp
@@ -368,23 +368,23 @@ QFuture<void> TrustHandler::setSecurityPolicy(
 Shared::TrustLevel Core::TrustHandler::convert(Core::TrustHandler::TL level)
 {
     switch (level) {
-        case QXmpp::TrustLevel::Undecided: return Shared::TrustLevel::Undecided;
-        case QXmpp::TrustLevel::AutomaticallyDistrusted: return Shared::TrustLevel::AutomaticallyDistrusted;
-        case QXmpp::TrustLevel::ManuallyDistrusted: return Shared::TrustLevel::ManuallyDistrusted;
-        case QXmpp::TrustLevel::AutomaticallyTrusted: return Shared::TrustLevel::AutomaticallyTrusted;
-        case QXmpp::TrustLevel::ManuallyTrusted: return Shared::TrustLevel::ManuallyTrusted;
-        case QXmpp::TrustLevel::Authenticated: return Shared::TrustLevel::Authenticated;
+        case QXmpp::TrustLevel::Undecided: return Shared::TrustLevel::undecided;
+        case QXmpp::TrustLevel::AutomaticallyDistrusted: return Shared::TrustLevel::automaticallyDistrusted;
+        case QXmpp::TrustLevel::ManuallyDistrusted: return Shared::TrustLevel::manuallyDistrusted;
+        case QXmpp::TrustLevel::AutomaticallyTrusted: return Shared::TrustLevel::automaticallyTrusted;
+        case QXmpp::TrustLevel::ManuallyTrusted: return Shared::TrustLevel::manuallyTrusted;
+        case QXmpp::TrustLevel::Authenticated: return Shared::TrustLevel::authenticated;
     }
 }
 
 Core::TrustHandler::TL Core::TrustHandler::convert(Shared::TrustLevel level)
 {
     switch (level) {
-        case Shared::TrustLevel::Undecided: return QXmpp::TrustLevel::Undecided;
-        case Shared::TrustLevel::AutomaticallyDistrusted: return QXmpp::TrustLevel::AutomaticallyDistrusted;
-        case Shared::TrustLevel::ManuallyDistrusted: return QXmpp::TrustLevel::ManuallyDistrusted;
-        case Shared::TrustLevel::AutomaticallyTrusted: return QXmpp::TrustLevel::AutomaticallyTrusted;
-        case Shared::TrustLevel::ManuallyTrusted: return QXmpp::TrustLevel::ManuallyTrusted;
-        case Shared::TrustLevel::Authenticated: return QXmpp::TrustLevel::Authenticated;
+        case Shared::TrustLevel::undecided: return QXmpp::TrustLevel::Undecided;
+        case Shared::TrustLevel::automaticallyDistrusted: return QXmpp::TrustLevel::AutomaticallyDistrusted;
+        case Shared::TrustLevel::manuallyDistrusted: return QXmpp::TrustLevel::ManuallyDistrusted;
+        case Shared::TrustLevel::automaticallyTrusted: return QXmpp::TrustLevel::AutomaticallyTrusted;
+        case Shared::TrustLevel::manuallyTrusted: return QXmpp::TrustLevel::ManuallyTrusted;
+        case Shared::TrustLevel::authenticated: return QXmpp::TrustLevel::Authenticated;
     }
 }
diff --git a/main/main.cpp b/main/main.cpp
index 2958b66..3e9add3 100644
--- a/main/main.cpp
+++ b/main/main.cpp
@@ -50,6 +50,7 @@ int main(int argc, char *argv[])
     qRegisterMetaType<QSet<QString>>("QSet<QString>");
     qRegisterMetaType<Shared::ConnectionState>("Shared::ConnectionState");
     qRegisterMetaType<Shared::Availability>("Shared::Availability");
+    qRegisterMetaType<Shared::KeyInfo>("Shared::KeyInfo");
 #ifdef WITH_OMEMO
     qRegisterMetaType<QXmppOmemoStorage::OwnDevice>("QXmppOmemoStorage::OwnDevice");
     qRegisterMetaTypeStreamOperators<QXmppOmemoStorage::OwnDevice>("QXmppOmemoStorage::OwnDevice");
diff --git a/shared/CMakeLists.txt b/shared/CMakeLists.txt
index 51c599f..a227163 100644
--- a/shared/CMakeLists.txt
+++ b/shared/CMakeLists.txt
@@ -26,4 +26,6 @@ target_sources(squawk PRIVATE
   form.cpp
   field.h
   field.cpp
+  keyinfo.cpp
+  keyinfo.h
   )
diff --git a/shared/enums.h b/shared/enums.h
index 7a1f092..7273b2e 100644
--- a/shared/enums.h
+++ b/shared/enums.h
@@ -127,23 +127,28 @@ Q_ENUM_NS(Support)
 
 enum class TrustLevel {
     /// The key's trust is not decided.
-    Undecided,
+    undecided,
     /// The key is automatically distrusted (e.g., by the security policy TOAKAFA).
     /// \see SecurityPolicy
-    AutomaticallyDistrusted,
+    automaticallyDistrusted,
     /// The key is manually distrusted (e.g., by clicking a button or \xep{0450, Automatic Trust
     /// Management (ATM)}).
-    ManuallyDistrusted,
+    manuallyDistrusted,
     /// The key is automatically trusted (e.g., by the client for all keys of a bare JID until one
     /// of it is authenticated).
-    AutomaticallyTrusted,
+    automaticallyTrusted,
     /// The key is manually trusted (e.g., by clicking a button).
-    ManuallyTrusted,
+    manuallyTrusted,
     /// The key is authenticated (e.g., by QR code scanning or \xep{0450, Automatic Trust
     /// Management (ATM)}).
-    Authenticated
+    authenticated
 };
 Q_ENUM_NS(TrustLevel)
 
+enum class EncryptionProtocol {
+    omemo
+};
+Q_ENUM_NS(EncryptionProtocol)
+
 }
 #endif // SHARED_ENUMS_H
diff --git a/shared/keyinfo.cpp b/shared/keyinfo.cpp
new file mode 100644
index 0000000..0c09d1f
--- /dev/null
+++ b/shared/keyinfo.cpp
@@ -0,0 +1,46 @@
+// Squawk messenger. 
+// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+// 
+// 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 "keyinfo.h"
+
+using namespace Shared;
+
+Shared::KeyInfo::KeyInfo(
+    uint32_t p_id,
+    const QByteArray& p_fingerPrint,
+    const QString& p_label,
+    Shared::TrustLevel p_trustLevel,
+    Shared::EncryptionProtocol p_protocol,
+    bool p_currentDevice
+):
+    id(p_id),
+    fingerPrint(p_fingerPrint),
+    label(p_label),
+    trustLevel(p_trustLevel),
+    protocol(p_protocol),
+    currentDevice(p_currentDevice)
+{
+}
+
+Shared::KeyInfo::KeyInfo():
+    id(0),
+    fingerPrint(),
+    label(),
+    trustLevel(TrustLevel::Undecided),
+    protocol(EncryptionProtocol::omemo),
+    currentDevice(false)
+{
+}
diff --git a/shared/keyinfo.h b/shared/keyinfo.h
new file mode 100644
index 0000000..b5dc793
--- /dev/null
+++ b/shared/keyinfo.h
@@ -0,0 +1,55 @@
+// Squawk messenger. 
+// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+// 
+// 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/>.
+
+#ifndef SHARED_KEYINFO_H
+#define SHARED_KEYINFO_H
+
+#include <QString>
+#include <QByteArray>
+
+#include <stdint.h>
+
+#include "enums.h"
+
+namespace Shared {
+
+class KeyInfo
+{
+public:
+    KeyInfo(
+        uint32_t id,
+        const QByteArray&
+        fingerPrint,
+        const QString& label,
+        TrustLevel trustLevel,
+        EncryptionProtocol protocol = EncryptionProtocol::omemo,
+        bool currentDevice = false
+    );
+    KeyInfo();
+
+private:
+    uint32_t id;
+    QByteArray fingerPrint;
+    QString label;
+    TrustLevel trustLevel;
+    EncryptionProtocol protocol;
+    bool currentDevice;
+
+};
+
+}
+
+#endif // SHARED_KEYINFO_H
diff --git a/shared/shared.h b/shared/shared.h
index 1e86c5a..68e5c8d 100644
--- a/shared/shared.h
+++ b/shared/shared.h
@@ -26,5 +26,6 @@
 #include "messageinfo.h"
 #include "utils.h"
 #include "vcard.h"
+#include "keyinfo.h"
 
 #endif // SHARED_H
diff --git a/ui/widgets/vcard/CMakeLists.txt b/ui/widgets/vcard/CMakeLists.txt
index 51cbaab..c37f4c6 100644
--- a/ui/widgets/vcard/CMakeLists.txt
+++ b/ui/widgets/vcard/CMakeLists.txt
@@ -7,3 +7,7 @@ target_sources(squawk PRIVATE
   vcard.h
   vcard.ui
   )
+
+if (WITH_OMEMO)
+ add_subdirectory(omemo)
+endif()
diff --git a/ui/widgets/vcard/omemo/CMakeLists.txt b/ui/widgets/vcard/omemo/CMakeLists.txt
new file mode 100644
index 0000000..1468841
--- /dev/null
+++ b/ui/widgets/vcard/omemo/CMakeLists.txt
@@ -0,0 +1,5 @@
+target_sources(squawk PRIVATE
+    omemo.cpp
+    omemo.h
+    omemo.ui
+)
diff --git a/ui/widgets/vcard/omemo/omemo.cpp b/ui/widgets/vcard/omemo/omemo.cpp
new file mode 100644
index 0000000..7e39ec0
--- /dev/null
+++ b/ui/widgets/vcard/omemo/omemo.cpp
@@ -0,0 +1,20 @@
+// Squawk messenger. 
+// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+// 
+// 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 "omemo.h"
+#include "ui_omemo.h"
+
+using namespace Ui;
diff --git a/ui/widgets/vcard/omemo/omemo.h b/ui/widgets/vcard/omemo/omemo.h
new file mode 100644
index 0000000..3dccbd3
--- /dev/null
+++ b/ui/widgets/vcard/omemo/omemo.h
@@ -0,0 +1,43 @@
+// Squawk messenger. 
+// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+// 
+// 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/>.
+
+#ifndef UI_OMEMO_H
+#define UI_OMEMO_H
+
+#include <qwidget.h>
+#include <QScopedPointer>
+
+namespace Ui {
+
+namespace Ui
+{
+class Omemo;
+}
+
+/**
+ * @todo write docs
+ */
+class Omemo : public QWidget
+{
+    Q_OBJECT
+
+private:
+    QScopedPointer<Ui::Omemo> m_ui;
+};
+
+}
+
+#endif // UI_OMEMO_H
diff --git a/ui/widgets/vcard/omemo/omemo.ui b/ui/widgets/vcard/omemo/omemo.ui
new file mode 100644
index 0000000..095a3e0
--- /dev/null
+++ b/ui/widgets/vcard/omemo/omemo.ui
@@ -0,0 +1,142 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>Ui::Omemo</class>
+ <widget class="QWidget" name="Ui::Omemo">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>473</width>
+    <height>657</height>
+   </rect>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout">
+   <property name="spacing">
+    <number>0</number>
+   </property>
+   <property name="leftMargin">
+    <number>0</number>
+   </property>
+   <property name="topMargin">
+    <number>6</number>
+   </property>
+   <property name="rightMargin">
+    <number>0</number>
+   </property>
+   <property name="bottomMargin">
+    <number>0</number>
+   </property>
+   <item>
+    <widget class="QLabel" name="OMEMOHeading">
+     <property name="font">
+      <font>
+       <pointsize>24</pointsize>
+       <weight>75</weight>
+       <bold>true</bold>
+      </font>
+     </property>
+     <property name="text">
+      <string>OMEMO</string>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <widget class="QScrollArea" name="scrollArea">
+     <property name="frameShape">
+      <enum>QFrame::NoFrame</enum>
+     </property>
+     <property name="frameShadow">
+      <enum>QFrame::Plain</enum>
+     </property>
+     <property name="lineWidth">
+      <number>0</number>
+     </property>
+     <property name="widgetResizable">
+      <bool>true</bool>
+     </property>
+     <widget class="QWidget" name="scrollAreaWidgetContents">
+      <property name="geometry">
+       <rect>
+        <x>0</x>
+        <y>0</y>
+        <width>473</width>
+        <height>592</height>
+       </rect>
+      </property>
+      <layout class="QGridLayout" name="gridLayout">
+       <item row="1" column="1">
+        <widget class="QListView" name="keysView"/>
+       </item>
+       <item row="3" column="1">
+        <widget class="Line" name="line">
+         <property name="orientation">
+          <enum>Qt::Horizontal</enum>
+         </property>
+        </widget>
+       </item>
+       <item row="0" column="1">
+        <widget class="QLabel" name="keysHeading">
+         <property name="font">
+          <font>
+           <pointsize>16</pointsize>
+           <weight>75</weight>
+           <bold>true</bold>
+          </font>
+         </property>
+         <property name="text">
+          <string>Active keys</string>
+         </property>
+        </widget>
+       </item>
+       <item row="4" column="1">
+        <widget class="QLabel" name="unusedKeysHeading">
+         <property name="font">
+          <font>
+           <pointsize>16</pointsize>
+           <weight>75</weight>
+           <bold>true</bold>
+          </font>
+         </property>
+         <property name="text">
+          <string>Unused keys</string>
+         </property>
+        </widget>
+       </item>
+       <item row="5" column="1">
+        <widget class="QListView" name="unusedKeysView"/>
+       </item>
+       <item row="0" column="0" rowspan="6">
+        <spacer name="spacerLeft">
+         <property name="orientation">
+          <enum>Qt::Horizontal</enum>
+         </property>
+         <property name="sizeHint" stdset="0">
+          <size>
+           <width>40</width>
+           <height>20</height>
+          </size>
+         </property>
+        </spacer>
+       </item>
+       <item row="0" column="2" rowspan="6">
+        <spacer name="spacerRight">
+         <property name="orientation">
+          <enum>Qt::Horizontal</enum>
+         </property>
+         <property name="sizeHint" stdset="0">
+          <size>
+           <width>40</width>
+           <height>20</height>
+          </size>
+         </property>
+        </spacer>
+       </item>
+      </layout>
+     </widget>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>

From 5aa0f4bca90a701238260eb28e082b124fcac0cc Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Tue, 3 Jan 2023 18:27:03 +0300
Subject: [PATCH 219/281] some initial classes for keys form

---
 shared/keyinfo.cpp                     |  23 ++-
 shared/keyinfo.h                       |   5 +-
 ui/widgets/messageline/messagefeed.cpp |   5 +-
 ui/widgets/vcard/omemo/CMakeLists.txt  |   2 +
 ui/widgets/vcard/omemo/keysmodel.cpp   |  71 +++++++
 ui/widgets/vcard/omemo/keysmodel.h     |  58 ++++++
 ui/widgets/vcard/omemo/omemo.cpp       |  29 ++-
 ui/widgets/vcard/omemo/omemo.h         |  24 ++-
 ui/widgets/vcard/omemo/omemo.ui        |   4 +-
 ui/widgets/vcard/vcard.cpp             | 276 +++++++++++++------------
 ui/widgets/vcard/vcard.h               |  23 ++-
 11 files changed, 361 insertions(+), 159 deletions(-)
 create mode 100644 ui/widgets/vcard/omemo/keysmodel.cpp
 create mode 100644 ui/widgets/vcard/omemo/keysmodel.h

diff --git a/shared/keyinfo.cpp b/shared/keyinfo.cpp
index 0c09d1f..bff716e 100644
--- a/shared/keyinfo.cpp
+++ b/shared/keyinfo.cpp
@@ -39,8 +39,29 @@ Shared::KeyInfo::KeyInfo():
     id(0),
     fingerPrint(),
     label(),
-    trustLevel(TrustLevel::Undecided),
+    trustLevel(TrustLevel::undecided),
     protocol(EncryptionProtocol::omemo),
     currentDevice(false)
 {
 }
+
+Shared::KeyInfo::KeyInfo(const Shared::KeyInfo& other):
+    id(other.id),
+    fingerPrint(other.fingerPrint),
+    label(other.label),
+    trustLevel(other.trustLevel),
+    protocol(other.protocol),
+    currentDevice(other.currentDevice)
+{
+}
+
+Shared::KeyInfo & Shared::KeyInfo::operator=(const Shared::KeyInfo& other) {
+    id = other.id;
+    fingerPrint = other.fingerPrint;
+    label = other.label;
+    trustLevel = other.trustLevel;
+    protocol = other.protocol;
+    currentDevice = other.currentDevice;
+
+    return *this;
+}
diff --git a/shared/keyinfo.h b/shared/keyinfo.h
index b5dc793..360a8b9 100644
--- a/shared/keyinfo.h
+++ b/shared/keyinfo.h
@@ -39,8 +39,11 @@ public:
         bool currentDevice = false
     );
     KeyInfo();
+    KeyInfo(const KeyInfo& other);
 
-private:
+    KeyInfo& operator=(const KeyInfo& other);
+
+public:
     uint32_t id;
     QByteArray fingerPrint;
     QString label;
diff --git a/ui/widgets/messageline/messagefeed.cpp b/ui/widgets/messageline/messagefeed.cpp
index ad67bb3..af772fd 100644
--- a/ui/widgets/messageline/messagefeed.cpp
+++ b/ui/widgets/messageline/messagefeed.cpp
@@ -440,10 +440,7 @@ QModelIndex Models::MessageFeed::index(int row, int column, const QModelIndex& p
     }
 }
 
-QHash<int, QByteArray> Models::MessageFeed::roleNames() const
-{
-    return roles;
-}
+QHash<int, QByteArray> Models::MessageFeed::roleNames() const {return roles;}
 
 bool Models::MessageFeed::sentByMe(const Shared::Message& msg) const
 {
diff --git a/ui/widgets/vcard/omemo/CMakeLists.txt b/ui/widgets/vcard/omemo/CMakeLists.txt
index 1468841..19a9fe9 100644
--- a/ui/widgets/vcard/omemo/CMakeLists.txt
+++ b/ui/widgets/vcard/omemo/CMakeLists.txt
@@ -2,4 +2,6 @@ target_sources(squawk PRIVATE
     omemo.cpp
     omemo.h
     omemo.ui
+    keysmodel.cpp
+    keysmodel.h
 )
diff --git a/ui/widgets/vcard/omemo/keysmodel.cpp b/ui/widgets/vcard/omemo/keysmodel.cpp
new file mode 100644
index 0000000..b70fefd
--- /dev/null
+++ b/ui/widgets/vcard/omemo/keysmodel.cpp
@@ -0,0 +1,71 @@
+// Squawk messenger. 
+// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+// 
+// 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 "keysmodel.h"
+
+const QHash<int, QByteArray> UI::KeysModel::roles = {
+    {Label, "label"},
+    {FingerPrint, "fingerPrint"},
+    {TrustLevel, "trustLevel"}
+};
+
+UI::KeysModel::KeysModel(QObject* parent):
+    QAbstractListModel(parent),
+    keys()
+{
+}
+
+UI::KeysModel::~KeysModel() {
+
+}
+
+void UI::KeysModel::addKey(const Shared::KeyInfo& info) {
+    beginInsertRows(QModelIndex(), keys.size(), keys.size());
+    keys.push_back(new Shared::KeyInfo(info));
+    endInsertRows();
+}
+
+QVariant UI::KeysModel::data(const QModelIndex& index, int role) const {
+    int i = index.row();
+    QVariant answer;
+
+    switch (role) {
+        case Qt::DisplayRole:
+            answer = keys[i]->label;
+            break;
+    }
+
+    return answer;
+}
+
+int UI::KeysModel::rowCount(const QModelIndex& parent) const {
+    return keys.size();
+}
+
+QHash<int, QByteArray> UI::KeysModel::roleNames() const {return roles;}
+
+QModelIndex UI::KeysModel::index(int row, int column, const QModelIndex& parent) const {
+    if (!hasIndex(row, column, parent)) {
+        return QModelIndex();
+    }
+
+    return createIndex(row, column, keys[row]);
+}
+
+
+
+
+
diff --git a/ui/widgets/vcard/omemo/keysmodel.h b/ui/widgets/vcard/omemo/keysmodel.h
new file mode 100644
index 0000000..853c5c6
--- /dev/null
+++ b/ui/widgets/vcard/omemo/keysmodel.h
@@ -0,0 +1,58 @@
+// Squawk messenger. 
+// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+// 
+// 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/>.
+
+#ifndef UI_KEYSMODEL_H
+#define UI_KEYSMODEL_H
+
+#include <QAbstractListModel>
+
+#include <shared/keyinfo.h>
+
+namespace UI {
+
+/**
+ * @todo write docs
+ */
+class KeysModel : public QAbstractListModel
+{
+public:
+    KeysModel(QObject *parent = nullptr);
+    ~KeysModel();
+
+    void addKey(const Shared::KeyInfo& info);
+
+    QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const override;
+    int rowCount(const QModelIndex& parent = QModelIndex()) const override;
+
+    QHash<int, QByteArray> roleNames() const override;
+    QModelIndex index(int row, int column, const QModelIndex & parent) const override;
+
+    enum Roles {
+        Label = Qt::UserRole + 1,
+        FingerPrint,
+        TrustLevel
+    };
+
+private:
+    std::deque<Shared::KeyInfo*> keys;
+
+private:
+    static const QHash<int, QByteArray> roles;
+};
+
+}
+
+#endif // UI_KEYSMODEL_H
diff --git a/ui/widgets/vcard/omemo/omemo.cpp b/ui/widgets/vcard/omemo/omemo.cpp
index 7e39ec0..8a1e1cc 100644
--- a/ui/widgets/vcard/omemo/omemo.cpp
+++ b/ui/widgets/vcard/omemo/omemo.cpp
@@ -17,4 +17,31 @@
 #include "omemo.h"
 #include "ui_omemo.h"
 
-using namespace Ui;
+Omemo::Omemo(QWidget* parent):
+    QWidget(parent),
+    m_ui(new Ui::Omemo()),
+    keysModel(),
+    unusedKeysModel()
+{
+    m_ui->setupUi(this);
+
+    generateMockData();
+
+    m_ui->keysView->setModel(&keysModel);
+    m_ui->unusedKeysView->setModel(&unusedKeysModel);
+}
+
+Omemo::~Omemo()
+{
+
+}
+
+void Omemo::generateMockData()
+{
+    for (int i = 0; i < 5; ++i) {
+        Shared::KeyInfo info;
+        info.id = i;
+        info.label = QString("test_") + std::to_string(i).c_str();
+        keysModel.addKey(info);
+    }
+}
diff --git a/ui/widgets/vcard/omemo/omemo.h b/ui/widgets/vcard/omemo/omemo.h
index 3dccbd3..d01f4a8 100644
--- a/ui/widgets/vcard/omemo/omemo.h
+++ b/ui/widgets/vcard/omemo/omemo.h
@@ -14,30 +14,32 @@
 // You should have received a copy of the GNU General Public License
 // along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
-#ifndef UI_OMEMO_H
-#define UI_OMEMO_H
+#ifndef VCARD_OMEMO_H
+#define VCARD_OMEMO_H
 
 #include <qwidget.h>
 #include <QScopedPointer>
 
-namespace Ui {
+#include "keysmodel.h"
 
 namespace Ui
 {
 class Omemo;
 }
 
-/**
- * @todo write docs
- */
-class Omemo : public QWidget
-{
+class Omemo : public QWidget {
     Q_OBJECT
+public:
+    Omemo(QWidget* parent = nullptr);
+    ~Omemo();
+
+private:
+    void generateMockData();
 
 private:
     QScopedPointer<Ui::Omemo> m_ui;
+    UI::KeysModel keysModel;
+    UI::KeysModel unusedKeysModel;
 };
 
-}
-
-#endif // UI_OMEMO_H
+#endif // VCARD_OMEMO_H
diff --git a/ui/widgets/vcard/omemo/omemo.ui b/ui/widgets/vcard/omemo/omemo.ui
index 095a3e0..7052486 100644
--- a/ui/widgets/vcard/omemo/omemo.ui
+++ b/ui/widgets/vcard/omemo/omemo.ui
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <ui version="4.0">
- <class>Ui::Omemo</class>
- <widget class="QWidget" name="Ui::Omemo">
+ <class>Omemo</class>
+ <widget class="QWidget" name="Omemo">
   <property name="geometry">
    <rect>
     <x>0</x>
diff --git a/ui/widgets/vcard/vcard.cpp b/ui/widgets/vcard/vcard.cpp
index f1cfe3b..a3e877b 100644
--- a/ui/widgets/vcard/vcard.cpp
+++ b/ui/widgets/vcard/vcard.cpp
@@ -41,105 +41,29 @@ VCard::VCard(const QString& jid, bool edit, QWidget* parent):
     phones(edit),
     roleDelegate(new ComboboxDelegate()),
     phoneTypeDelegate(new ComboboxDelegate())
+
+#ifdef WITH_OMEMO
+    ,omemo(new Omemo())
+#endif
 {
     m_ui->setupUi(this);
     m_ui->jabberID->setText(jid);
     m_ui->jabberID->setReadOnly(true);
     
-    QAction* setAvatar = m_ui->actionSetAvatar;
-    QAction* clearAvatar = m_ui->actionClearAvatar;
-    
-    connect(setAvatar, &QAction::triggered, this, &VCard::onSetAvatar);
-    connect(clearAvatar, &QAction::triggered, this, &VCard::onClearAvatar);
-    
-    setAvatar->setEnabled(true);
-    clearAvatar->setEnabled(false);
-    
-    roleDelegate->addEntry(QCoreApplication::translate("Global", Shared::VCard::Email::roleNames[0].toStdString().c_str()));
-    roleDelegate->addEntry(QCoreApplication::translate("Global", Shared::VCard::Email::roleNames[1].toStdString().c_str()));
-    roleDelegate->addEntry(QCoreApplication::translate("Global", Shared::VCard::Email::roleNames[2].toStdString().c_str()));
-    
-    phoneTypeDelegate->addEntry(QCoreApplication::translate("Global", Shared::VCard::Phone::typeNames[0].toStdString().c_str()));
-    phoneTypeDelegate->addEntry(QCoreApplication::translate("Global", Shared::VCard::Phone::typeNames[1].toStdString().c_str()));
-    phoneTypeDelegate->addEntry(QCoreApplication::translate("Global", Shared::VCard::Phone::typeNames[2].toStdString().c_str()));
-    phoneTypeDelegate->addEntry(QCoreApplication::translate("Global", Shared::VCard::Phone::typeNames[3].toStdString().c_str()));
-    phoneTypeDelegate->addEntry(QCoreApplication::translate("Global", Shared::VCard::Phone::typeNames[4].toStdString().c_str()));
-    phoneTypeDelegate->addEntry(QCoreApplication::translate("Global", Shared::VCard::Phone::typeNames[5].toStdString().c_str()));
-    phoneTypeDelegate->addEntry(QCoreApplication::translate("Global", Shared::VCard::Phone::typeNames[6].toStdString().c_str()));
-    
-    m_ui->emailsView->setContextMenuPolicy(Qt::CustomContextMenu);
-    m_ui->emailsView->setModel(&emails);
-    m_ui->emailsView->setItemDelegateForColumn(1, roleDelegate);
-    m_ui->emailsView->setColumnWidth(2, 25);
-    m_ui->emailsView->horizontalHeader()->setStretchLastSection(false);
-    m_ui->emailsView->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch);
-    
-    m_ui->phonesView->setContextMenuPolicy(Qt::CustomContextMenu);
-    m_ui->phonesView->setModel(&phones);
-    m_ui->phonesView->setItemDelegateForColumn(1, roleDelegate);
-    m_ui->phonesView->setItemDelegateForColumn(2, phoneTypeDelegate);
-    m_ui->phonesView->setColumnWidth(3, 25);
-    m_ui->phonesView->horizontalHeader()->setStretchLastSection(false);
-    m_ui->phonesView->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch);
-    
-    connect(m_ui->phonesView, &QWidget::customContextMenuRequested, this, &VCard::onContextMenu);
-    connect(m_ui->emailsView, &QWidget::customContextMenuRequested, this, &VCard::onContextMenu);
-    
-    if (edit) {
-        avatarMenu = new QMenu();
-        m_ui->avatarButton->setMenu(avatarMenu);
-        avatarMenu->addAction(setAvatar);
-        avatarMenu->addAction(clearAvatar);
-        m_ui->title->setText(tr("Account %1 card").arg(jid));
-    } else {
-        m_ui->buttonBox->hide();
-        m_ui->fullName->setReadOnly(true);
-        m_ui->firstName->setReadOnly(true);
-        m_ui->middleName->setReadOnly(true);
-        m_ui->lastName->setReadOnly(true);
-        m_ui->nickName->setReadOnly(true);
-        m_ui->birthday->setReadOnly(true);
-        m_ui->organizationName->setReadOnly(true);
-        m_ui->organizationDepartment->setReadOnly(true);
-        m_ui->organizationTitle->setReadOnly(true);
-        m_ui->organizationRole->setReadOnly(true);
-        m_ui->description->setReadOnly(true);
-        m_ui->url->setReadOnly(true);
-        m_ui->title->setText(tr("Contact %1 card").arg(jid));
-    }
-    
-    connect(m_ui->buttonBox, &QDialogButtonBox::accepted, this, &VCard::onButtonBoxAccepted);
-    connect(m_ui->buttonBox, &QDialogButtonBox::rejected, this, &VCard::close);
-    
-    avatarButtonMargins = m_ui->avatarButton->size();
-    
-    int height = m_ui->personalForm->minimumSize().height() - avatarButtonMargins.height();
-    m_ui->avatarButton->setIconSize(QSize(height, height));
-    
-    QGridLayout* gr = static_cast<QGridLayout*>(layout());
-    gr->addWidget(overlay, 0, 0, 4, 1);
-    QVBoxLayout* nl = new QVBoxLayout();
-    QGraphicsOpacityEffect* opacity = new QGraphicsOpacityEffect();
-    opacity->setOpacity(0.8);
-    overlay->setLayout(nl);
-    overlay->setBackgroundRole(QPalette::Base);
-    overlay->setAutoFillBackground(true);
-    overlay->setGraphicsEffect(opacity);
-    progressLabel->setAlignment(Qt::AlignCenter);
-    QFont pf = progressLabel->font();
-    pf.setBold(true);
-    pf.setPointSize(26);
-    progressLabel->setFont(pf);
-    progressLabel->setWordWrap(true);
-    nl->addStretch();
-    nl->addWidget(progress);
-    nl->addWidget(progressLabel);
-    nl->addStretch();
-    overlay->hide();
+    initializeActions();
+    initializeDelegates();
+    initializeViews();
+    initializeInteractiveElements(jid);
+    initializeOverlay();
+#ifdef WITH_OMEMO
+    initializeOmemo();
+#endif
 }
 
-VCard::~VCard()
-{
+VCard::~VCard() {
+#ifdef WITH_OMEMO
+    delete omemo;
+#endif
     if (editable) {
         avatarMenu->deleteLater();
     }
@@ -149,14 +73,12 @@ VCard::~VCard()
     contextMenu->deleteLater();
 }
 
-void VCard::setVCard(const QString& jid, const Shared::VCard& card)
-{
+void VCard::setVCard(const QString& jid, const Shared::VCard& card) {
     m_ui->jabberID->setText(jid);
     setVCard(card);
 }
 
-void VCard::setVCard(const Shared::VCard& card)
-{
+void VCard::setVCard(const Shared::VCard& card) {
     m_ui->fullName->setText(card.getFullName());
     m_ui->firstName->setText(card.getFirstName());
     m_ui->middleName->setText(card.getMiddleName());
@@ -183,13 +105,11 @@ void VCard::setVCard(const Shared::VCard& card)
     phones.setPhones(phs);
 }
 
-QString VCard::getJid() const
-{
+QString VCard::getJid() const {
     return m_ui->jabberID->text();
 }
 
-void VCard::onButtonBoxAccepted()
-{
+void VCard::onButtonBoxAccepted() {
     Shared::VCard card;
     card.setFullName(m_ui->fullName->text());
     card.setFirstName(m_ui->firstName->text());
@@ -212,16 +132,14 @@ void VCard::onButtonBoxAccepted()
     emit saveVCard(card);
 }
 
-void VCard::onClearAvatar()
-{
+void VCard::onClearAvatar() {
     currentAvatarType = Shared::Avatar::empty;
     currentAvatarPath = "";
     
     updateAvatar();
 }
 
-void VCard::onSetAvatar()
-{
+void VCard::onSetAvatar() {
     QFileDialog* d = new QFileDialog(this, tr("Chose your new avatar"));
     d->setFileMode(QFileDialog::ExistingFile);
     d->setNameFilter(tr("Images (*.png *.jpg *.jpeg)"));
@@ -232,8 +150,7 @@ void VCard::onSetAvatar()
     d->show();
 }
 
-void VCard::updateAvatar()
-{
+void VCard::updateAvatar() {
     int height = m_ui->personalForm->minimumSize().height() - avatarButtonMargins.height();
     switch (currentAvatarType) {
         case Shared::Avatar::empty:
@@ -254,8 +171,7 @@ void VCard::updateAvatar()
     }
 }
 
-void VCard::onAvatarSelected()
-{
+void VCard::onAvatarSelected() {
     QFileDialog* d = static_cast<QFileDialog*>(sender());
     QMimeDatabase db;
     QString path = d->selectedFiles().front();
@@ -290,21 +206,18 @@ void VCard::onAvatarSelected()
     }
 }
 
-void VCard::showProgress(const QString& line)
-{
+void VCard::showProgress(const QString& line) {
     progressLabel->setText(line);
     overlay->show();
     progress->start();
 }
 
-void VCard::hideProgress()
-{
+void VCard::hideProgress() {
     overlay->hide();
     progress->stop();
 }
 
-void VCard::onContextMenu(const QPoint& point)
-{
+void VCard::onContextMenu(const QPoint& point) {
     contextMenu->clear();
     bool hasMenu = false;
     QAbstractItemView* snd = static_cast<QAbstractItemView*>(sender());
@@ -371,28 +284,20 @@ void VCard::onContextMenu(const QPoint& point)
     }
 }
 
-void VCard::onAddEmail()
-{
+void VCard::onAddEmail() {
     QModelIndex index = emails.addNewEmptyLine();
     m_ui->emailsView->setCurrentIndex(index);
     m_ui->emailsView->edit(index);
 }
 
-void VCard::onAddAddress()
-{
-    
-}
-void VCard::onAddPhone()
-{
+void VCard::onAddAddress() {}
+void VCard::onAddPhone() {
     QModelIndex index = phones.addNewEmptyLine();
     m_ui->phonesView->setCurrentIndex(index);
     m_ui->phonesView->edit(index);
 }
-void VCard::onRemoveAddress()
-{
-}
-void VCard::onRemoveEmail()
-{
+void VCard::onRemoveAddress() {}
+void VCard::onRemoveEmail() {
     QItemSelection selection(m_ui->emailsView->selectionModel()->selection());
     
     QList<int> rows;
@@ -412,8 +317,7 @@ void VCard::onRemoveEmail()
     }
 }
 
-void VCard::onRemovePhone()
-{
+void VCard::onRemovePhone() {
     QItemSelection selection(m_ui->phonesView->selectionModel()->selection());
     
     QList<int> rows;
@@ -433,8 +337,7 @@ void VCard::onRemovePhone()
     }
 }
 
-void VCard::onCopyEmail()
-{
+void VCard::onCopyEmail() {
     QList<QModelIndex> selection(m_ui->emailsView->selectionModel()->selectedRows());
     
     QList<QString> addrs;
@@ -448,8 +351,7 @@ void VCard::onCopyEmail()
     cb->setText(list);
 }
 
-void VCard::onCopyPhone()
-{
+void VCard::onCopyPhone() {
     QList<QModelIndex> selection(m_ui->phonesView->selectionModel()->selectedRows());
     
     QList<QString> phs;
@@ -462,3 +364,111 @@ void VCard::onCopyPhone()
     QClipboard* cb = QApplication::clipboard();
     cb->setText(list);
 }
+
+void VCard::initializeDelegates() {
+    roleDelegate->addEntry(QCoreApplication::translate("Global", Shared::VCard::Email::roleNames[0].toStdString().c_str()));
+    roleDelegate->addEntry(QCoreApplication::translate("Global", Shared::VCard::Email::roleNames[1].toStdString().c_str()));
+    roleDelegate->addEntry(QCoreApplication::translate("Global", Shared::VCard::Email::roleNames[2].toStdString().c_str()));
+
+    phoneTypeDelegate->addEntry(QCoreApplication::translate("Global", Shared::VCard::Phone::typeNames[0].toStdString().c_str()));
+    phoneTypeDelegate->addEntry(QCoreApplication::translate("Global", Shared::VCard::Phone::typeNames[1].toStdString().c_str()));
+    phoneTypeDelegate->addEntry(QCoreApplication::translate("Global", Shared::VCard::Phone::typeNames[2].toStdString().c_str()));
+    phoneTypeDelegate->addEntry(QCoreApplication::translate("Global", Shared::VCard::Phone::typeNames[3].toStdString().c_str()));
+    phoneTypeDelegate->addEntry(QCoreApplication::translate("Global", Shared::VCard::Phone::typeNames[4].toStdString().c_str()));
+    phoneTypeDelegate->addEntry(QCoreApplication::translate("Global", Shared::VCard::Phone::typeNames[5].toStdString().c_str()));
+    phoneTypeDelegate->addEntry(QCoreApplication::translate("Global", Shared::VCard::Phone::typeNames[6].toStdString().c_str()));
+}
+
+void VCard::initializeViews() {
+    m_ui->emailsView->setContextMenuPolicy(Qt::CustomContextMenu);
+    m_ui->emailsView->setModel(&emails);
+    m_ui->emailsView->setItemDelegateForColumn(1, roleDelegate);
+    m_ui->emailsView->setColumnWidth(2, 25);
+    m_ui->emailsView->horizontalHeader()->setStretchLastSection(false);
+    m_ui->emailsView->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch);
+
+    m_ui->phonesView->setContextMenuPolicy(Qt::CustomContextMenu);
+    m_ui->phonesView->setModel(&phones);
+    m_ui->phonesView->setItemDelegateForColumn(1, roleDelegate);
+    m_ui->phonesView->setItemDelegateForColumn(2, phoneTypeDelegate);
+    m_ui->phonesView->setColumnWidth(3, 25);
+    m_ui->phonesView->horizontalHeader()->setStretchLastSection(false);
+    m_ui->phonesView->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch);
+
+    connect(m_ui->phonesView, &QWidget::customContextMenuRequested, this, &VCard::onContextMenu);
+    connect(m_ui->emailsView, &QWidget::customContextMenuRequested, this, &VCard::onContextMenu);
+}
+
+void VCard::initializeActions() {
+    QAction* setAvatar = m_ui->actionSetAvatar;
+    QAction* clearAvatar = m_ui->actionClearAvatar;
+
+    connect(setAvatar, &QAction::triggered, this, &VCard::onSetAvatar);
+    connect(clearAvatar, &QAction::triggered, this, &VCard::onClearAvatar);
+
+    setAvatar->setEnabled(true);
+    clearAvatar->setEnabled(false);
+}
+
+void VCard::initializeInteractiveElements(const QString& jid) {
+    if (editable) {
+        avatarMenu = new QMenu();
+        m_ui->avatarButton->setMenu(avatarMenu);
+        avatarMenu->addAction(m_ui->actionSetAvatar);
+        avatarMenu->addAction(m_ui->actionClearAvatar);
+        m_ui->title->setText(tr("Account %1 card").arg(jid));
+    } else {
+        m_ui->buttonBox->hide();
+        m_ui->fullName->setReadOnly(true);
+        m_ui->firstName->setReadOnly(true);
+        m_ui->middleName->setReadOnly(true);
+        m_ui->lastName->setReadOnly(true);
+        m_ui->nickName->setReadOnly(true);
+        m_ui->birthday->setReadOnly(true);
+        m_ui->organizationName->setReadOnly(true);
+        m_ui->organizationDepartment->setReadOnly(true);
+        m_ui->organizationTitle->setReadOnly(true);
+        m_ui->organizationRole->setReadOnly(true);
+        m_ui->description->setReadOnly(true);
+        m_ui->url->setReadOnly(true);
+        m_ui->title->setText(tr("Contact %1 card").arg(jid));
+    }
+
+    connect(m_ui->buttonBox, &QDialogButtonBox::accepted, this, &VCard::onButtonBoxAccepted);
+    connect(m_ui->buttonBox, &QDialogButtonBox::rejected, this, &VCard::close);
+
+    avatarButtonMargins = m_ui->avatarButton->size();
+
+    int height = m_ui->personalForm->minimumSize().height() - avatarButtonMargins.height();
+    m_ui->avatarButton->setIconSize(QSize(height, height));
+}
+
+void VCard::initializeOverlay() {
+    QGridLayout* gr = static_cast<QGridLayout*>(layout());
+    gr->addWidget(overlay, 0, 0, 4, 1);
+    QVBoxLayout* nl = new QVBoxLayout();
+    QGraphicsOpacityEffect* opacity = new QGraphicsOpacityEffect();
+    opacity->setOpacity(0.8);
+    overlay->setLayout(nl);
+    overlay->setBackgroundRole(QPalette::Base);
+    overlay->setAutoFillBackground(true);
+    overlay->setGraphicsEffect(opacity);
+    progressLabel->setAlignment(Qt::AlignCenter);
+    QFont pf = progressLabel->font();
+    pf.setBold(true);
+    pf.setPointSize(26);
+    progressLabel->setFont(pf);
+    progressLabel->setWordWrap(true);
+    nl->addStretch();
+    nl->addWidget(progress);
+    nl->addWidget(progressLabel);
+    nl->addStretch();
+    overlay->hide();
+}
+
+#ifdef WITH_OMEMO
+void VCard::initializeOmemo() {
+    m_ui->tabWidget->addTab(omemo, "OMEMO");
+}
+
+#endif
diff --git a/ui/widgets/vcard/vcard.h b/ui/widgets/vcard/vcard.h
index 4d579e1..82b7d53 100644
--- a/ui/widgets/vcard/vcard.h
+++ b/ui/widgets/vcard/vcard.h
@@ -42,16 +42,16 @@
 #include "ui/utils/progress.h"
 #include "ui/utils/comboboxdelegate.h"
 
+#ifdef WITH_OMEMO
+#include "omemo/omemo.h"
+#endif
+
 namespace Ui
 {
 class VCard;
 }
 
-/**
- * @todo write docs
- */
-class VCard : public QWidget
-{
+class VCard : public QWidget {
     Q_OBJECT
 public:
     VCard(const QString& jid, bool edit = false, QWidget* parent = nullptr);
@@ -96,11 +96,22 @@ private:
     UI::VCard::PhonesModel phones;
     ComboboxDelegate* roleDelegate;
     ComboboxDelegate* phoneTypeDelegate;
+#ifdef WITH_OMEMO
+    Omemo* omemo;
+#endif
     
     static const std::set<QString> supportedTypes;
-    
+
 private:
     void updateAvatar();
+    void initializeDelegates();
+    void initializeViews();
+    void initializeActions();
+    void initializeInteractiveElements(const QString& jid);
+    void initializeOverlay();
+#ifdef WITH_OMEMO
+    void initializeOmemo();
+#endif
 };
 
 #endif // VCARD_H

From 78ef3664f76a98ba871d322468877b72499cbe52 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Sat, 7 Jan 2023 17:30:22 +0300
Subject: [PATCH 220/281] some initial delegate stuff

---
 ui/widgets/vcard/omemo/CMakeLists.txt  |  2 +
 ui/widgets/vcard/omemo/keydelegate.cpp | 71 ++++++++++++++++++++++++++
 ui/widgets/vcard/omemo/keydelegate.h   | 39 ++++++++++++++
 ui/widgets/vcard/omemo/keysmodel.cpp   |  3 ++
 ui/widgets/vcard/omemo/omemo.cpp       | 14 +++++
 ui/widgets/vcard/omemo/omemo.h         |  3 ++
 ui/widgets/vcard/omemo/omemo.ui        | 23 ++++++++-
 ui/widgets/vcard/vcard.ui              |  8 +--
 8 files changed, 157 insertions(+), 6 deletions(-)
 create mode 100644 ui/widgets/vcard/omemo/keydelegate.cpp
 create mode 100644 ui/widgets/vcard/omemo/keydelegate.h

diff --git a/ui/widgets/vcard/omemo/CMakeLists.txt b/ui/widgets/vcard/omemo/CMakeLists.txt
index 19a9fe9..e2ade51 100644
--- a/ui/widgets/vcard/omemo/CMakeLists.txt
+++ b/ui/widgets/vcard/omemo/CMakeLists.txt
@@ -4,4 +4,6 @@ target_sources(squawk PRIVATE
     omemo.ui
     keysmodel.cpp
     keysmodel.h
+    keydelegate.cpp
+    keydelegate.h
 )
diff --git a/ui/widgets/vcard/omemo/keydelegate.cpp b/ui/widgets/vcard/omemo/keydelegate.cpp
new file mode 100644
index 0000000..1816ecd
--- /dev/null
+++ b/ui/widgets/vcard/omemo/keydelegate.cpp
@@ -0,0 +1,71 @@
+// Squawk messenger. 
+// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+// 
+// 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 "keydelegate.h"
+#include <QPainter>
+#include <QFontDatabase>
+
+#include "keysmodel.h"
+
+constexpr int minHeight = 50;
+constexpr int minWidth = 400;
+
+UI::KeyDelegate::KeyDelegate(QObject* parent):
+    QStyledItemDelegate(parent),
+    fingerPrintFont(QFontDatabase::systemFont(QFontDatabase::FixedFont))
+{
+}
+
+UI::KeyDelegate::~KeyDelegate() {}
+
+
+void UI::KeyDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const {
+    painter->save();
+    painter->setRenderHint(QPainter::Antialiasing, true);
+
+    bool hover = option.state & QStyle::State_MouseOver;
+    if (hover) {
+        painter->save();
+        painter->fillRect(option.rect, option.palette.brush(QPalette::Inactive, QPalette::Highlight));
+        painter->restore();
+    }
+
+    QVariant fingerPrintV = index.data(UI::KeysModel::FingerPrint);
+    if (fingerPrintV.isValid()) {
+        painter->save();
+        QByteArray fingerPrint = fingerPrintV.toByteArray();
+
+        painter->setFont(fingerPrintFont);
+        painter->drawText(option.rect, option.displayAlignment | Qt::AlignTop, fingerPrint.toHex());
+
+        painter->restore();
+    }
+    painter->drawText(option.rect, option.displayAlignment, index.data().toString());
+
+    painter->restore();
+}
+
+QSize UI::KeyDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const {
+    QSize size = QStyledItemDelegate::sizeHint(option, index);
+
+    if (size.width() < minWidth)
+        size.setWidth(minWidth);
+
+    if (size.height() < minHeight)
+        size.setHeight(minHeight);
+
+    return size;
+}
diff --git a/ui/widgets/vcard/omemo/keydelegate.h b/ui/widgets/vcard/omemo/keydelegate.h
new file mode 100644
index 0000000..d1f768b
--- /dev/null
+++ b/ui/widgets/vcard/omemo/keydelegate.h
@@ -0,0 +1,39 @@
+// Squawk messenger. 
+// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+// 
+// 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/>.
+
+#ifndef UI_KEYDELEGATE_H
+#define UI_KEYDELEGATE_H
+
+#include <QStyledItemDelegate>
+
+namespace UI {
+
+class KeyDelegate : public QStyledItemDelegate
+{
+public:
+    KeyDelegate(QObject *parent = nullptr);
+    ~KeyDelegate();
+
+    QSize sizeHint(const QStyleOptionViewItem & option, const QModelIndex & index) const override;
+    void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override;
+
+private:
+    QFont fingerPrintFont;
+};
+
+}
+
+#endif // UI_KEYDELEGATE_H
diff --git a/ui/widgets/vcard/omemo/keysmodel.cpp b/ui/widgets/vcard/omemo/keysmodel.cpp
index b70fefd..4184133 100644
--- a/ui/widgets/vcard/omemo/keysmodel.cpp
+++ b/ui/widgets/vcard/omemo/keysmodel.cpp
@@ -46,6 +46,9 @@ QVariant UI::KeysModel::data(const QModelIndex& index, int role) const {
         case Qt::DisplayRole:
             answer = keys[i]->label;
             break;
+        case FingerPrint:
+            answer = keys[i]->fingerPrint;
+            break;
     }
 
     return answer;
diff --git a/ui/widgets/vcard/omemo/omemo.cpp b/ui/widgets/vcard/omemo/omemo.cpp
index 8a1e1cc..b2db902 100644
--- a/ui/widgets/vcard/omemo/omemo.cpp
+++ b/ui/widgets/vcard/omemo/omemo.cpp
@@ -17,9 +17,14 @@
 #include "omemo.h"
 #include "ui_omemo.h"
 
+#include <random>
+constexpr uint8_t fingerprintLength = 24;
+
 Omemo::Omemo(QWidget* parent):
     QWidget(parent),
     m_ui(new Ui::Omemo()),
+    keysDelegate(),
+    unusedKeysDelegate(),
     keysModel(),
     unusedKeysModel()
 {
@@ -27,7 +32,9 @@ Omemo::Omemo(QWidget* parent):
 
     generateMockData();
 
+    m_ui->keysView->setItemDelegate(&keysDelegate);
     m_ui->keysView->setModel(&keysModel);
+    m_ui->unusedKeysView->setItemDelegate(&unusedKeysDelegate);
     m_ui->unusedKeysView->setModel(&unusedKeysModel);
 }
 
@@ -38,10 +45,17 @@ Omemo::~Omemo()
 
 void Omemo::generateMockData()
 {
+    std::random_device rd;
+    std::uniform_int_distribution<char> dist(CHAR_MIN, CHAR_MAX);
     for (int i = 0; i < 5; ++i) {
+        QByteArray fp(fingerprintLength, 0);
+        for (int i = 0; i < fingerprintLength; ++i) {
+            fp[i] = dist(rd);
+        }
         Shared::KeyInfo info;
         info.id = i;
         info.label = QString("test_") + std::to_string(i).c_str();
+        info.fingerPrint = fp;
         keysModel.addKey(info);
     }
 }
diff --git a/ui/widgets/vcard/omemo/omemo.h b/ui/widgets/vcard/omemo/omemo.h
index d01f4a8..2bc0433 100644
--- a/ui/widgets/vcard/omemo/omemo.h
+++ b/ui/widgets/vcard/omemo/omemo.h
@@ -21,6 +21,7 @@
 #include <QScopedPointer>
 
 #include "keysmodel.h"
+#include "keydelegate.h"
 
 namespace Ui
 {
@@ -38,6 +39,8 @@ private:
 
 private:
     QScopedPointer<Ui::Omemo> m_ui;
+    UI::KeyDelegate keysDelegate;
+    UI::KeyDelegate unusedKeysDelegate;
     UI::KeysModel keysModel;
     UI::KeysModel unusedKeysModel;
 };
diff --git a/ui/widgets/vcard/omemo/omemo.ui b/ui/widgets/vcard/omemo/omemo.ui
index 7052486..cf72e0f 100644
--- a/ui/widgets/vcard/omemo/omemo.ui
+++ b/ui/widgets/vcard/omemo/omemo.ui
@@ -63,9 +63,28 @@
         <height>592</height>
        </rect>
       </property>
-      <layout class="QGridLayout" name="gridLayout">
+      <layout class="QGridLayout" name="gridLayout" columnstretch="1,3,1">
        <item row="1" column="1">
-        <widget class="QListView" name="keysView"/>
+        <widget class="QListView" name="keysView">
+         <property name="sizeAdjustPolicy">
+          <enum>QAbstractScrollArea::AdjustToContents</enum>
+         </property>
+         <property name="showDropIndicator" stdset="0">
+          <bool>false</bool>
+         </property>
+         <property name="selectionBehavior">
+          <enum>QAbstractItemView::SelectColumns</enum>
+         </property>
+         <property name="verticalScrollMode">
+          <enum>QAbstractItemView::ScrollPerPixel</enum>
+         </property>
+         <property name="horizontalScrollMode">
+          <enum>QAbstractItemView::ScrollPerItem</enum>
+         </property>
+         <property name="uniformItemSizes">
+          <bool>true</bool>
+         </property>
+        </widget>
        </item>
        <item row="3" column="1">
         <widget class="Line" name="line">
diff --git a/ui/widgets/vcard/vcard.ui b/ui/widgets/vcard/vcard.ui
index b71d262..7e09257 100644
--- a/ui/widgets/vcard/vcard.ui
+++ b/ui/widgets/vcard/vcard.ui
@@ -10,7 +10,7 @@
     <x>0</x>
     <y>0</y>
     <width>578</width>
-    <height>671</height>
+    <height>748</height>
    </rect>
   </property>
   <layout class="QGridLayout" name="gridLayout_4">
@@ -84,7 +84,7 @@
         <enum>QTabWidget::Rounded</enum>
        </property>
        <property name="currentIndex">
-        <number>0</number>
+        <number>1</number>
        </property>
        <property name="elideMode">
         <enum>Qt::ElideNone</enum>
@@ -566,8 +566,8 @@
              <rect>
               <x>0</x>
               <y>0</y>
-              <width>566</width>
-              <height>498</height>
+              <width>545</width>
+              <height>544</height>
              </rect>
             </property>
             <layout class="QGridLayout" name="gridLayout_3" columnstretch="1,3,1">

From 2aed8a120982c7e04b7e6d94c39b4a1e7a10343e Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Sun, 8 Jan 2023 18:16:41 +0300
Subject: [PATCH 221/281] a bit better drawing of a key fingerprint

---
 ui/widgets/vcard/omemo/keydelegate.cpp | 79 +++++++++++++++++++++++---
 ui/widgets/vcard/omemo/keydelegate.h   |  7 +++
 2 files changed, 77 insertions(+), 9 deletions(-)

diff --git a/ui/widgets/vcard/omemo/keydelegate.cpp b/ui/widgets/vcard/omemo/keydelegate.cpp
index 1816ecd..2ab969d 100644
--- a/ui/widgets/vcard/omemo/keydelegate.cpp
+++ b/ui/widgets/vcard/omemo/keydelegate.cpp
@@ -22,10 +22,15 @@
 
 constexpr int minHeight = 50;
 constexpr int minWidth = 400;
+constexpr uint8_t margin = 10;
+constexpr uint8_t partSize = 8;
+constexpr uint8_t maxSingleLineParts = 3;
 
 UI::KeyDelegate::KeyDelegate(QObject* parent):
     QStyledItemDelegate(parent),
-    fingerPrintFont(QFontDatabase::systemFont(QFontDatabase::FixedFont))
+    fingerPrintFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)),
+    fingerPrintMetrics(fingerPrintFont),
+    spaceWidth(fingerPrintMetrics.horizontalAdvance(" "))
 {
 }
 
@@ -46,14 +51,31 @@ void UI::KeyDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio
     QVariant fingerPrintV = index.data(UI::KeysModel::FingerPrint);
     if (fingerPrintV.isValid()) {
         painter->save();
-        QByteArray fingerPrint = fingerPrintV.toByteArray();
-
         painter->setFont(fingerPrintFont);
-        painter->drawText(option.rect, option.displayAlignment | Qt::AlignTop, fingerPrint.toHex());
+        QByteArray fingerPrint = fingerPrintV.toByteArray();
+        std::vector<QString> parts = getParts(fingerPrint);
+        uint8_t partsLength = parts.size();
+        QRect rect = option.rect;
+        rect.adjust(margin, margin, 0, 0);
+        QRect r;
+        uint8_t firstLine;
+        if (partsLength > maxSingleLineParts)
+            firstLine = partsLength / 2 + partsLength % 2;
+        else
+            firstLine = partsLength;
+
+        for (uint8_t i = 0; i < partsLength; ++i) {
+            if (i == firstLine) {
+                rect.setLeft(option.rect.left() + margin);
+                rect.adjust(0, r.height() + fingerPrintMetrics.leading(), 0, 0);
+            }
+            painter->drawText(rect, Qt::AlignLeft | Qt::AlignTop, parts[i], &r);
+            rect.adjust(r.width() + spaceWidth ,0, 0, 0);
+        }
 
         painter->restore();
     }
-    painter->drawText(option.rect, option.displayAlignment, index.data().toString());
+    //painter->drawText(option.rect, option.displayAlignment, index.data().toString());
 
     painter->restore();
 }
@@ -61,11 +83,50 @@ void UI::KeyDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio
 QSize UI::KeyDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const {
     QSize size = QStyledItemDelegate::sizeHint(option, index);
 
-    if (size.width() < minWidth)
-        size.setWidth(minWidth);
+    int mh = margin * 2;
+    int mw = margin * 2;
+    QVariant fingerPrintV = index.data(UI::KeysModel::FingerPrint);
+    if (fingerPrintV.isValid()) {
+        QString hex = fingerPrintV.toByteArray().toHex();
+        uint8_t parts = hex.size() / partSize;
 
-    if (size.height() < minHeight)
-        size.setHeight(minHeight);
+        mh += fingerPrintMetrics.height();
+        if (parts > maxSingleLineParts) {
+            mh += fingerPrintMetrics.height() + fingerPrintMetrics.leading();
+        }
+
+        uint8_t firstLine;
+        if (parts > maxSingleLineParts)
+            firstLine = parts / 2 + parts % 2;
+        else
+            firstLine = parts;
+
+        mw += firstLine * partSize * spaceWidth + (firstLine - 1) * spaceWidth;
+    }
+
+
+    if (size.width() < mw)
+        size.setWidth(mw);
+
+    if (size.height() < mh)
+        size.setHeight(mh);
 
     return size;
 }
+
+std::vector<QString> UI::KeyDelegate::getParts(const QByteArray& data) {
+    QString hex = data.toHex();
+    uint8_t parts = hex.size() / partSize;
+    std::vector<QString> result(parts);
+    for (uint8_t i = 0; i < parts; ++i) {
+        uint16_t index = i * partSize;
+        uint8_t length = partSize;
+        if (index + length > hex.size())
+            length = hex.size() - index;
+        QStringRef part(&hex, index, length);
+        result[i] = part.toString();
+    }
+
+    return result;
+}
+
diff --git a/ui/widgets/vcard/omemo/keydelegate.h b/ui/widgets/vcard/omemo/keydelegate.h
index d1f768b..0ce3e7f 100644
--- a/ui/widgets/vcard/omemo/keydelegate.h
+++ b/ui/widgets/vcard/omemo/keydelegate.h
@@ -18,6 +18,8 @@
 #define UI_KEYDELEGATE_H
 
 #include <QStyledItemDelegate>
+#include <QString>
+#include <vector>
 
 namespace UI {
 
@@ -32,6 +34,11 @@ public:
 
 private:
     QFont fingerPrintFont;
+    QFontMetrics fingerPrintMetrics;
+    int spaceWidth;
+
+private:
+    static std::vector<QString> getParts(const QByteArray& data);
 };
 
 }

From 15fb4bbd62ae46c435dcd85b3a018665fa24a748 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Wed, 11 Jan 2023 23:45:38 +0300
Subject: [PATCH 222/281] some thoughts about fonts, lastInteraction and label
 into keyDelegate

---
 shared/keyinfo.cpp                         |   5 +
 shared/keyinfo.h                           |   6 +-
 ui/widgets/conversation.cpp                |   1 -
 ui/widgets/messageline/feedview.cpp        | 134 ++++++---------------
 ui/widgets/messageline/feedview.h          |   5 +-
 ui/widgets/messageline/messagedelegate.cpp |  76 ++++++------
 ui/widgets/messageline/messagedelegate.h   |  15 ++-
 ui/widgets/messageline/preview.cpp         |  17 ++-
 ui/widgets/messageline/preview.h           |   1 -
 ui/widgets/vcard/omemo/keydelegate.cpp     |  53 +++++++-
 ui/widgets/vcard/omemo/keydelegate.h       |   2 +
 ui/widgets/vcard/omemo/keysmodel.cpp       |  10 ++
 ui/widgets/vcard/omemo/keysmodel.h         |   4 +-
 ui/widgets/vcard/omemo/omemo.cpp           |   6 +-
 ui/widgets/vcard/omemo/omemo.ui            |   2 +-
 15 files changed, 166 insertions(+), 171 deletions(-)

diff --git a/shared/keyinfo.cpp b/shared/keyinfo.cpp
index bff716e..db3ce2b 100644
--- a/shared/keyinfo.cpp
+++ b/shared/keyinfo.cpp
@@ -22,6 +22,7 @@ Shared::KeyInfo::KeyInfo(
     uint32_t p_id,
     const QByteArray& p_fingerPrint,
     const QString& p_label,
+    const QDateTime& p_lastInteraction,
     Shared::TrustLevel p_trustLevel,
     Shared::EncryptionProtocol p_protocol,
     bool p_currentDevice
@@ -29,6 +30,7 @@ Shared::KeyInfo::KeyInfo(
     id(p_id),
     fingerPrint(p_fingerPrint),
     label(p_label),
+    lastInteraction(p_lastInteraction),
     trustLevel(p_trustLevel),
     protocol(p_protocol),
     currentDevice(p_currentDevice)
@@ -39,6 +41,7 @@ Shared::KeyInfo::KeyInfo():
     id(0),
     fingerPrint(),
     label(),
+    lastInteraction(),
     trustLevel(TrustLevel::undecided),
     protocol(EncryptionProtocol::omemo),
     currentDevice(false)
@@ -49,6 +52,7 @@ Shared::KeyInfo::KeyInfo(const Shared::KeyInfo& other):
     id(other.id),
     fingerPrint(other.fingerPrint),
     label(other.label),
+    lastInteraction(other.lastInteraction),
     trustLevel(other.trustLevel),
     protocol(other.protocol),
     currentDevice(other.currentDevice)
@@ -59,6 +63,7 @@ Shared::KeyInfo & Shared::KeyInfo::operator=(const Shared::KeyInfo& other) {
     id = other.id;
     fingerPrint = other.fingerPrint;
     label = other.label;
+    lastInteraction = other.lastInteraction;
     trustLevel = other.trustLevel;
     protocol = other.protocol;
     currentDevice = other.currentDevice;
diff --git a/shared/keyinfo.h b/shared/keyinfo.h
index 360a8b9..1befbc9 100644
--- a/shared/keyinfo.h
+++ b/shared/keyinfo.h
@@ -19,6 +19,7 @@
 
 #include <QString>
 #include <QByteArray>
+#include <QDateTime>
 
 #include <stdint.h>
 
@@ -31,9 +32,9 @@ class KeyInfo
 public:
     KeyInfo(
         uint32_t id,
-        const QByteArray&
-        fingerPrint,
+        const QByteArray& fingerPrint,
         const QString& label,
+        const QDateTime& lastInteraction,
         TrustLevel trustLevel,
         EncryptionProtocol protocol = EncryptionProtocol::omemo,
         bool currentDevice = false
@@ -47,6 +48,7 @@ public:
     uint32_t id;
     QByteArray fingerPrint;
     QString label;
+    QDateTime lastInteraction;
     TrustLevel trustLevel;
     EncryptionProtocol protocol;
     bool currentDevice;
diff --git a/ui/widgets/conversation.cpp b/ui/widgets/conversation.cpp
index b2c7a5f..3e6a6e0 100644
--- a/ui/widgets/conversation.cpp
+++ b/ui/widgets/conversation.cpp
@@ -68,7 +68,6 @@ Conversation::Conversation(bool muc, Models::Account* acc, Models::Element* el,
     feed->setItemDelegate(delegate);
     feed->setFrameShape(QFrame::NoFrame);
     feed->setContextMenuPolicy(Qt::CustomContextMenu);
-    delegate->initializeFonts(feed->getFont());
     feed->setModel(el->feed);
     el->feed->incrementObservers();
     m_ui->widget->layout()->addWidget(feed);
diff --git a/ui/widgets/messageline/feedview.cpp b/ui/widgets/messageline/feedview.cpp
index 69b5093..57e8143 100644
--- a/ui/widgets/messageline/feedview.cpp
+++ b/ui/widgets/messageline/feedview.cpp
@@ -24,6 +24,7 @@
 #include <QApplication>
 #include <QClipboard>
 #include <QDebug>
+#include <QFontDatabase>
 
 #include "messagedelegate.h"
 #include "messagefeed.h"
@@ -40,6 +41,8 @@ const std::set<int> FeedView::geometryChangingRoles = {
     Models::MessageFeed::Error,
     Models::MessageFeed::Date
 };
+QFont FeedView::dividerFont = QFontDatabase::systemFont(QFontDatabase::TitleFont);
+QFontMetrics FeedView::dividerMetrics = QFontMetrics(dividerFont);
 
 FeedView::FeedView(QWidget* parent):
     QAbstractItemView(parent),
@@ -51,8 +54,6 @@ FeedView::FeedView(QWidget* parent):
     clearWidgetsMode(false),
     modelState(Models::MessageFeed::complete),
     progress(),
-    dividerFont(),
-    dividerMetrics(dividerFont),
     mousePressed(false),
     dragging(false),
     hovered(Shared::Hover::nothing),
@@ -68,23 +69,11 @@ FeedView::FeedView(QWidget* parent):
     
     progress.setParent(viewport());
     progress.resize(progressSize, progressSize);
-
-    dividerFont = getFont();
-    dividerFont.setBold(true);
-    float ndps = dividerFont.pointSizeF();
-    if (ndps != -1) {
-        dividerFont.setPointSizeF(ndps * 1.2);
-    } else {
-        dividerFont.setPointSize(dividerFont.pointSize() + 2);
-    }
 }
 
-FeedView::~FeedView()
-{
-}
+FeedView::~FeedView() {}
 
-QModelIndex FeedView::indexAt(const QPoint& point) const
-{
+QModelIndex FeedView::indexAt(const QPoint& point) const {
     int32_t vh = viewport()->height();
     uint32_t y = vh - point.y() + vo;
     
@@ -102,12 +91,8 @@ QModelIndex FeedView::indexAt(const QPoint& point) const
     return QModelIndex();
 }
 
-void FeedView::scrollTo(const QModelIndex& index, QAbstractItemView::ScrollHint hint)
-{
-}
 
-QRect FeedView::visualRect(const QModelIndex& index) const
-{
+QRect FeedView::visualRect(const QModelIndex& index) const {
     unsigned int row = index.row();
     if (!index.isValid() || row >= hints.size()) {
         qDebug() << "visualRect for" << row;
@@ -119,44 +104,24 @@ QRect FeedView::visualRect(const QModelIndex& index) const
     }
 }
 
-int FeedView::horizontalOffset() const
-{
-    return 0;
-}
+QString FeedView::getSelectedText() const{return selectedText;}
 
-bool FeedView::isIndexHidden(const QModelIndex& index) const
-{
-    return false;
-}
+//TODO!!!
+void FeedView::scrollTo(const QModelIndex& index, QAbstractItemView::ScrollHint hint) {}
+int FeedView::horizontalOffset() const {return 0;}
+bool FeedView::isIndexHidden(const QModelIndex& index) const{return false;}
+QModelIndex FeedView::moveCursor(QAbstractItemView::CursorAction cursorAction, Qt::KeyboardModifiers modifiers) {return QModelIndex();}
+void FeedView::setSelection(const QRect& rect, QItemSelectionModel::SelectionFlags command) {}
+int FeedView::verticalOffset() const {return vo;}
+QRegion FeedView::visualRegionForSelection(const QItemSelection& selection) const {return QRegion();}
 
-QModelIndex FeedView::moveCursor(QAbstractItemView::CursorAction cursorAction, Qt::KeyboardModifiers modifiers)
-{
-    return QModelIndex();
-}
-
-void FeedView::setSelection(const QRect& rect, QItemSelectionModel::SelectionFlags command)
-{
-}
-
-int FeedView::verticalOffset() const
-{
-    return vo;
-}
-
-QRegion FeedView::visualRegionForSelection(const QItemSelection& selection) const
-{
-    return QRegion();
-}
-
-void FeedView::rowsInserted(const QModelIndex& parent, int start, int end)
-{
+void FeedView::rowsInserted(const QModelIndex& parent, int start, int end){
     QAbstractItemView::rowsInserted(parent, start, end);
     
     scheduleDelayedItemsLayout();
 }
 
-void FeedView::dataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight, const QVector<int>& roles)
-{
+void FeedView::dataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight, const QVector<int>& roles) {
     if (specialDelegate) {
         for (int role : roles) {
             if (geometryChangingRoles.count(role) != 0) {
@@ -168,8 +133,7 @@ void FeedView::dataChanged(const QModelIndex& topLeft, const QModelIndex& bottom
     QAbstractItemView::dataChanged(topLeft, bottomRight, roles);
 }
 
-void FeedView::updateGeometries()
-{
+void FeedView::updateGeometries() {
     //qDebug() << "updateGeometries";
     QScrollBar* bar = verticalScrollBar();
     
@@ -260,8 +224,7 @@ void FeedView::updateGeometries()
     QAbstractItemView::updateGeometries();
 }
 
-bool FeedView::tryToCalculateGeometriesWithNoScrollbars(const QStyleOptionViewItem& option, const QAbstractItemModel* m, uint32_t totalHeight)
-{
+bool FeedView::tryToCalculateGeometriesWithNoScrollbars(const QStyleOptionViewItem& option, const QAbstractItemModel* m, uint32_t totalHeight) {
     uint32_t previousOffset = elementMargin;
     QDateTime lastDate;
     for (int i = 0, size = m->rowCount(); i < size; ++i) {
@@ -307,9 +270,7 @@ bool FeedView::tryToCalculateGeometriesWithNoScrollbars(const QStyleOptionViewIt
     return true;
 }
 
-
-void FeedView::paintEvent(QPaintEvent* event)
-{
+void FeedView::paintEvent(QPaintEvent* event) {
     //qDebug() << "paint" << event->rect();
     const QAbstractItemModel* m = model();
     QWidget* vp = viewport();
@@ -388,8 +349,7 @@ void FeedView::paintEvent(QPaintEvent* event)
     }
 }
 
-void FeedView::drawDateDevider(int top, const QDateTime& date, QPainter& painter)
-{
+void FeedView::drawDateDevider(int top, const QDateTime& date, QPainter& painter) {
     int divisionHeight = dateDeviderMargin * 2 + dividerMetrics.height();
     QRect r(QPoint(0, top), QSize(viewport()->width(), divisionHeight));
     painter.save();
@@ -398,8 +358,7 @@ void FeedView::drawDateDevider(int top, const QDateTime& date, QPainter& painter
     painter.restore();
 }
 
-void FeedView::verticalScrollbarValueChanged(int value)
-{
+void FeedView::verticalScrollbarValueChanged(int value) {
     vo = verticalScrollBar()->maximum() - value;
     
     positionProgress();
@@ -415,8 +374,7 @@ void FeedView::verticalScrollbarValueChanged(int value)
     QAbstractItemView::verticalScrollbarValueChanged(vo);
 }
 
-void FeedView::setAnchorHovered(Shared::Hover type)
-{
+void FeedView::setAnchorHovered(Shared::Hover type) {
     if (hovered != type) {
         hovered = type;
         switch (hovered) {
@@ -433,8 +391,7 @@ void FeedView::setAnchorHovered(Shared::Hover type)
     }
 }
 
-void FeedView::mouseMoveEvent(QMouseEvent* event)
-{
+void FeedView::mouseMoveEvent(QMouseEvent* event) {
     if (!isVisible()) {
         return;
     }
@@ -479,8 +436,7 @@ void FeedView::mouseMoveEvent(QMouseEvent* event)
     }
 }
 
-void FeedView::mousePressEvent(QMouseEvent* event)
-{
+void FeedView::mousePressEvent(QMouseEvent* event) {
     QAbstractItemView::mousePressEvent(event);
 
     mousePressed = event->button() == Qt::LeftButton;
@@ -500,8 +456,7 @@ void FeedView::mousePressEvent(QMouseEvent* event)
     }
 }
 
-void FeedView::mouseDoubleClickEvent(QMouseEvent* event)
-{
+void FeedView::mouseDoubleClickEvent(QMouseEvent* event) {
     QAbstractItemView::mouseDoubleClickEvent(event);
     mousePressed = event->button() == Qt::LeftButton;
     if (mousePressed) {
@@ -530,8 +485,7 @@ void FeedView::mouseDoubleClickEvent(QMouseEvent* event)
     }
 }
 
-void FeedView::mouseReleaseEvent(QMouseEvent* event)
-{
+void FeedView::mouseReleaseEvent(QMouseEvent* event) {
     QAbstractItemView::mouseReleaseEvent(event);
 
     if (mousePressed) {
@@ -551,8 +505,7 @@ void FeedView::mouseReleaseEvent(QMouseEvent* event)
     }
 }
 
-void FeedView::keyPressEvent(QKeyEvent* event)
-{
+void FeedView::keyPressEvent(QKeyEvent* event) {
     QKeyEvent *key_event = static_cast<QKeyEvent*>(event);
     if (key_event->matches(QKeySequence::Copy)) {
         if (selectedText.size() > 0) {
@@ -562,16 +515,14 @@ void FeedView::keyPressEvent(QKeyEvent* event)
     }
 }
 
-void FeedView::resizeEvent(QResizeEvent* event)
-{
+void FeedView::resizeEvent(QResizeEvent* event) {
     QAbstractItemView::resizeEvent(event);
     
     positionProgress();
     emit resized();
 }
 
-void FeedView::positionProgress()
-{
+void FeedView::positionProgress() {
     QSize layoutBounds = maximumViewportSize();
     int progressPosition = layoutBounds.height() - progressSize;
     std::deque<Hint>::size_type size = hints.size();
@@ -585,13 +536,7 @@ void FeedView::positionProgress()
     progress.move((width() - progressSize) / 2, progressPosition);
 }
 
-QFont FeedView::getFont() const
-{
-    return viewOptions().font;
-}
-
-void FeedView::setItemDelegate(QAbstractItemDelegate* delegate)
-{
+void FeedView::setItemDelegate(QAbstractItemDelegate* delegate) {
     if (specialDelegate) {
         MessageDelegate* del = static_cast<MessageDelegate*>(itemDelegate());
         disconnect(del, &MessageDelegate::buttonPushed, this, &FeedView::onMessageButtonPushed);
@@ -613,8 +558,7 @@ void FeedView::setItemDelegate(QAbstractItemDelegate* delegate)
     }
 }
 
-void FeedView::setModel(QAbstractItemModel* p_model)
-{
+void FeedView::setModel(QAbstractItemModel* p_model) {
     if (specialModel) {
         Models::MessageFeed* feed = static_cast<Models::MessageFeed*>(model());
         disconnect(feed, &Models::MessageFeed::syncStateChange, this, &FeedView::onModelSyncStateChange);
@@ -633,24 +577,21 @@ void FeedView::setModel(QAbstractItemModel* p_model)
     }
 }
 
-void FeedView::onMessageButtonPushed(const QString& messageId)
-{
+void FeedView::onMessageButtonPushed(const QString& messageId) {
     if (specialModel) {
         Models::MessageFeed* feed = static_cast<Models::MessageFeed*>(model());
         feed->downloadAttachment(messageId);
     }
 }
 
-void FeedView::onMessageInvalidPath(const QString& messageId)
-{
+void FeedView::onMessageInvalidPath(const QString& messageId) {
     if (specialModel) {
         Models::MessageFeed* feed = static_cast<Models::MessageFeed*>(model());
         feed->reportLocalPathInvalid(messageId);
     }
 }
 
-void FeedView::onModelSyncStateChange(Models::MessageFeed::SyncState state)
-{
+void FeedView::onModelSyncStateChange(Models::MessageFeed::SyncState state) {
     bool needToUpdateGeometry = false;
     if (modelState != state) {
         if (state == Models::MessageFeed::complete || modelState == Models::MessageFeed::complete) {
@@ -671,8 +612,3 @@ void FeedView::onModelSyncStateChange(Models::MessageFeed::SyncState state)
         scheduleDelayedItemsLayout();
     }
 }
-
-QString FeedView::getSelectedText() const
-{
-    return selectedText;
-}
diff --git a/ui/widgets/messageline/feedview.h b/ui/widgets/messageline/feedview.h
index 4194849..1d0c92a 100644
--- a/ui/widgets/messageline/feedview.h
+++ b/ui/widgets/messageline/feedview.h
@@ -50,7 +50,6 @@ public:
     void setItemDelegate(QAbstractItemDelegate* delegate);
     void setModel(QAbstractItemModel * model) override;
     
-    QFont getFont() const;
     QString getSelectedText() const;
     
 signals:
@@ -100,8 +99,8 @@ private:
     bool clearWidgetsMode;
     Models::MessageFeed::SyncState modelState;
     Progress progress;
-    QFont dividerFont;
-    QFontMetrics dividerMetrics;
+    static QFont dividerFont;
+    static QFontMetrics dividerMetrics;
     bool mousePressed;
     bool dragging;
     Shared::Hover hovered;
diff --git a/ui/widgets/messageline/messagedelegate.cpp b/ui/widgets/messageline/messagedelegate.cpp
index f935057..6247f4f 100644
--- a/ui/widgets/messageline/messagedelegate.cpp
+++ b/ui/widgets/messageline/messagedelegate.cpp
@@ -24,29 +24,40 @@
 #include <QAbstractItemView>
 #include <QAbstractTextDocumentLayout>
 #include <QTextBlock>
+#include <QFontDatabase>
 #include <cmath>
 
 #include "messagedelegate.h"
 #include "messagefeed.h"
 
-int MessageDelegate::avatarHeight(50);
-int MessageDelegate::margin(6);
+QFont getFont (QFontDatabase::SystemFont type, bool bold, bool italic) {
+    QFont font = QFontDatabase::systemFont(type);
+    if (bold)
+        font.setBold(true);
+    if (italic)
+        font.setItalic(true);
+
+    return font;
+}
 constexpr int textMargin = 2;
 constexpr int statusIconSize = 16;
-constexpr float nickFontMultiplier = 1.1;
-constexpr float dateFontMultiplier = 0.8;
 
 constexpr int bubbleMargin = 6;
 constexpr int bubbleBorderRadius = 3;
 
+int MessageDelegate::avatarHeight(50);
+int MessageDelegate::margin(6);
+
+bool MessageDelegate::fontsInitialized(false);
+QFont MessageDelegate::bodyFont;
+QFont MessageDelegate::nickFont;
+QFont MessageDelegate::dateFont;
+QFontMetrics MessageDelegate::nickMetrics(nickFont);
+QFontMetrics MessageDelegate::dateMetrics(dateFont);
+
 MessageDelegate::MessageDelegate(QObject* parent):
     QStyledItemDelegate(parent),
-    bodyFont(),
-    nickFont(),
-    dateFont(),
     bodyRenderer(new QTextDocument()),
-    nickMetrics(nickFont),
-    dateMetrics(dateFont),
     buttonHeight(0),
     buttonWidth(0),
     barHeight(0),
@@ -60,7 +71,13 @@ MessageDelegate::MessageDelegate(QObject* parent):
     currentId(""),
     selection(0, 0)
 {
+    if (!fontsInitialized) {
+        fontsInitialized = true;
+        initializeFonts();
+    }
+
     bodyRenderer->setDocumentMargin(0);
+    bodyRenderer->setDefaultFont(bodyFont);
 
     QPushButton btn(QCoreApplication::translate("MessageLine", "Download"));
     buttonHeight = btn.sizeHint().height();
@@ -285,7 +302,6 @@ bool MessageDelegate::needToDrawSender(const QModelIndex& index, const Models::F
     }
 }
 
-
 QSize MessageDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const
 {
     QRect messageRect = option.rect.adjusted(bubbleMargin, margin / 2 + bubbleMargin, -(avatarHeight + 3 * margin + bubbleMargin), -(margin + bubbleMargin) / 2);
@@ -538,38 +554,6 @@ QString MessageDelegate::clearSelection()
     return lastSelectedId;
 }
 
-void MessageDelegate::initializeFonts(const QFont& font)
-{
-    bodyFont = font;
-    nickFont = font;
-    dateFont = font;
-    
-    nickFont.setBold(true);
-    
-    float ndps = nickFont.pointSizeF();
-    if (ndps != -1) {
-        nickFont.setPointSizeF(ndps * nickFontMultiplier);
-    } else {
-        nickFont.setPointSize(nickFont.pointSize() * nickFontMultiplier);
-    }
-    
-    dateFont.setItalic(true);
-    float dps = dateFont.pointSizeF();
-    if (dps != -1) {
-        dateFont.setPointSizeF(dps * dateFontMultiplier);
-    } else {
-        dateFont.setPointSize(dateFont.pointSize() * dateFontMultiplier);
-    }
-    
-    bodyFont.setKerning(false);
-    nickMetrics = QFontMetrics(nickFont);
-    dateMetrics = QFontMetrics(dateFont);
-
-    bodyRenderer->setDefaultFont(bodyFont);
-    
-    Preview::initializeFont(bodyFont);
-}
-
 bool MessageDelegate::editorEvent(QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& index)
 {
     //qDebug() << event->type();
@@ -828,3 +812,11 @@ void MessageDelegate::clearHelperWidget(const Models::FeedItem& data) const
         }
     }
 }
+
+void MessageDelegate::initializeFonts () {
+    bodyFont = getFont(QFontDatabase::GeneralFont, false, false);
+    nickFont = getFont(QFontDatabase::TitleFont, true, false);
+    dateFont = getFont(QFontDatabase::SmallestReadableFont, false, true);
+    nickMetrics = QFontMetrics(nickFont);
+    dateMetrics = QFontMetrics(dateFont);
+}
diff --git a/ui/widgets/messageline/messagedelegate.h b/ui/widgets/messageline/messagedelegate.h
index 322fb76..7a54e66 100644
--- a/ui/widgets/messageline/messagedelegate.h
+++ b/ui/widgets/messageline/messagedelegate.h
@@ -53,7 +53,6 @@ public:
     QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const override;
     //void setModelData(QWidget * editor, QAbstractItemModel * model, const QModelIndex & index) const override;
     
-    void initializeFonts(const QFont& font);
     bool editorEvent(QEvent * event, QAbstractItemModel * model, const QStyleOptionViewItem & option, const QModelIndex & index) override;
     void endClearWidgets();
     void beginClearWidgets();
@@ -94,6 +93,9 @@ protected:
     
 protected slots:
     void onButtonPushed() const;
+
+private:
+    static void initializeFonts();
     
 private:
     class FeedButton : public QPushButton {
@@ -101,12 +103,13 @@ private:
         QString messageId;
     };
     
-    QFont bodyFont;
-    QFont nickFont;
-    QFont dateFont;
+    static bool fontsInitialized;
+    static QFont bodyFont;
+    static QFont nickFont;
+    static QFont dateFont;
     QTextDocument* bodyRenderer;
-    QFontMetrics nickMetrics;
-    QFontMetrics dateMetrics;
+    static QFontMetrics nickMetrics;
+    static QFontMetrics dateMetrics;
     
     int buttonHeight;
     int buttonWidth;
diff --git a/ui/widgets/messageline/preview.cpp b/ui/widgets/messageline/preview.cpp
index 137293f..8c844ae 100644
--- a/ui/widgets/messageline/preview.cpp
+++ b/ui/widgets/messageline/preview.cpp
@@ -18,11 +18,18 @@
 
 #include "preview.h"
 
+#include <QFontDatabase>
 
 constexpr int margin = 6;
 constexpr int maxAttachmentHeight = 500;
 
-QFont Preview::font;
+QFont getFont () {
+    QFont font = QFontDatabase::systemFont(QFontDatabase::GeneralFont);
+    font.setBold(true);
+    return font;
+}
+
+QFont Preview::font = getFont();
 QFontMetrics Preview::metrics(Preview::font);
 
 Preview::Preview(const QString& pPath, const QSize& pMaxSize, const QPoint& pos, QWidget* pParent):
@@ -38,20 +45,12 @@ Preview::Preview(const QString& pPath, const QSize& pMaxSize, const QPoint& pos,
     fileReachable(true),
     actualPreview(false)
 {
-    
     initializeElements();
     if (fileReachable) {
         positionElements();
     }
 }
 
-void Preview::initializeFont(const QFont& newFont)
-{
-    font = newFont;
-    font.setBold(true);
-    metrics = QFontMetrics(font);
-}
-
 Preview::~Preview()
 {
     clean();
diff --git a/ui/widgets/messageline/preview.h b/ui/widgets/messageline/preview.h
index 5165137..11a5d7d 100644
--- a/ui/widgets/messageline/preview.h
+++ b/ui/widgets/messageline/preview.h
@@ -48,7 +48,6 @@ public:
     bool isFileReachable() const;
     QSize size() const;
     
-    static void initializeFont(const QFont& newFont);
     static QSize constrainAttachSize(QSize src, QSize bounds);
     static QSize calculateAttachSize(const QString& path, const QRect& bounds);
     static bool canVisualize(const Shared::Global::FileInfo& info);
diff --git a/ui/widgets/vcard/omemo/keydelegate.cpp b/ui/widgets/vcard/omemo/keydelegate.cpp
index 2ab969d..646dfe9 100644
--- a/ui/widgets/vcard/omemo/keydelegate.cpp
+++ b/ui/widgets/vcard/omemo/keydelegate.cpp
@@ -29,10 +29,11 @@ constexpr uint8_t maxSingleLineParts = 3;
 UI::KeyDelegate::KeyDelegate(QObject* parent):
     QStyledItemDelegate(parent),
     fingerPrintFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)),
+    labelFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont)),
     fingerPrintMetrics(fingerPrintFont),
+    labelFontMetrics(labelFont),
     spaceWidth(fingerPrintMetrics.horizontalAdvance(" "))
-{
-}
+{}
 
 UI::KeyDelegate::~KeyDelegate() {}
 
@@ -48,6 +49,24 @@ void UI::KeyDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio
         painter->restore();
     }
 
+    QColor q = painter->pen().color();
+    q.setAlpha(180);
+
+    QRect rect = option.rect;
+    rect.adjust(margin, margin, 0, 0);
+    QVariant labelV = index.data(UI::KeysModel::Label);
+    if (labelV.isValid()) {
+        QString label = labelV.toString();
+        if (label.size() > 0) {
+            painter->save();
+            painter->setFont(labelFont);
+            painter->setPen(q);
+            painter->drawText(rect, Qt::AlignLeft | Qt::AlignTop, label);
+            rect.adjust(0, labelFontMetrics.lineSpacing(), 0, 0);
+            painter->restore();
+        }
+    }
+
     QVariant fingerPrintV = index.data(UI::KeysModel::FingerPrint);
     if (fingerPrintV.isValid()) {
         painter->save();
@@ -55,8 +74,6 @@ void UI::KeyDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio
         QByteArray fingerPrint = fingerPrintV.toByteArray();
         std::vector<QString> parts = getParts(fingerPrint);
         uint8_t partsLength = parts.size();
-        QRect rect = option.rect;
-        rect.adjust(margin, margin, 0, 0);
         QRect r;
         uint8_t firstLine;
         if (partsLength > maxSingleLineParts)
@@ -73,8 +90,25 @@ void UI::KeyDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio
             rect.adjust(r.width() + spaceWidth ,0, 0, 0);
         }
 
+        rect.adjust(0, r.height() + fingerPrintMetrics.leading(), 0, 0);
+        rect.setLeft(option.rect.left() + margin);
+
         painter->restore();
     }
+
+
+    QVariant lastV = index.data(UI::KeysModel::LastInteraction);
+    if (lastV.isValid()) {
+        QDateTime last = lastV.toDateTime();
+        if (last.isValid()) {
+            painter->save();
+            painter->setFont(labelFont);
+            painter->setPen(q);
+            painter->drawText(rect, Qt::AlignLeft | Qt::AlignTop, last.toString());
+            rect.adjust(0, labelFontMetrics.lineSpacing(), 0, 0);
+            painter->restore();
+        }
+    }
     //painter->drawText(option.rect, option.displayAlignment, index.data().toString());
 
     painter->restore();
@@ -85,6 +119,12 @@ QSize UI::KeyDelegate::sizeHint(const QStyleOptionViewItem& option, const QModel
 
     int mh = margin * 2;
     int mw = margin * 2;
+
+    QVariant labelV = index.data(UI::KeysModel::Label);
+    if (labelV.isValid() && labelV.toString().size() > 0) {
+        mh += labelFontMetrics.lineSpacing();
+    }
+
     QVariant fingerPrintV = index.data(UI::KeysModel::FingerPrint);
     if (fingerPrintV.isValid()) {
         QString hex = fingerPrintV.toByteArray().toHex();
@@ -103,7 +143,10 @@ QSize UI::KeyDelegate::sizeHint(const QStyleOptionViewItem& option, const QModel
 
         mw += firstLine * partSize * spaceWidth + (firstLine - 1) * spaceWidth;
     }
-
+    QVariant lastV = index.data(UI::KeysModel::LastInteraction);
+    if (lastV.isValid() && lastV.toDateTime().isValid()) {
+        mh += labelFontMetrics.lineSpacing();
+    }
 
     if (size.width() < mw)
         size.setWidth(mw);
diff --git a/ui/widgets/vcard/omemo/keydelegate.h b/ui/widgets/vcard/omemo/keydelegate.h
index 0ce3e7f..2b0eb5b 100644
--- a/ui/widgets/vcard/omemo/keydelegate.h
+++ b/ui/widgets/vcard/omemo/keydelegate.h
@@ -34,7 +34,9 @@ public:
 
 private:
     QFont fingerPrintFont;
+    QFont labelFont;
     QFontMetrics fingerPrintMetrics;
+    QFontMetrics labelFontMetrics;
     int spaceWidth;
 
 private:
diff --git a/ui/widgets/vcard/omemo/keysmodel.cpp b/ui/widgets/vcard/omemo/keysmodel.cpp
index 4184133..750e42a 100644
--- a/ui/widgets/vcard/omemo/keysmodel.cpp
+++ b/ui/widgets/vcard/omemo/keysmodel.cpp
@@ -44,11 +44,21 @@ QVariant UI::KeysModel::data(const QModelIndex& index, int role) const {
 
     switch (role) {
         case Qt::DisplayRole:
+        case Label:
             answer = keys[i]->label;
             break;
         case FingerPrint:
             answer = keys[i]->fingerPrint;
             break;
+        case TrustLevel:
+            answer = static_cast<uint8_t>(keys[i]->trustLevel);
+            break;
+        case LastInteraction:
+            answer = keys[i]->lastInteraction;
+            break;
+        case Dirty:
+            answer = false;
+            break;
     }
 
     return answer;
diff --git a/ui/widgets/vcard/omemo/keysmodel.h b/ui/widgets/vcard/omemo/keysmodel.h
index 853c5c6..68f222c 100644
--- a/ui/widgets/vcard/omemo/keysmodel.h
+++ b/ui/widgets/vcard/omemo/keysmodel.h
@@ -43,7 +43,9 @@ public:
     enum Roles {
         Label = Qt::UserRole + 1,
         FingerPrint,
-        TrustLevel
+        TrustLevel,
+        LastInteraction,
+        Dirty
     };
 
 private:
diff --git a/ui/widgets/vcard/omemo/omemo.cpp b/ui/widgets/vcard/omemo/omemo.cpp
index b2db902..0aea97c 100644
--- a/ui/widgets/vcard/omemo/omemo.cpp
+++ b/ui/widgets/vcard/omemo/omemo.cpp
@@ -54,8 +54,12 @@ void Omemo::generateMockData()
         }
         Shared::KeyInfo info;
         info.id = i;
-        info.label = QString("test_") + std::to_string(i).c_str();
+        if (i % 3 == 0)
+            info.label = QString("test_") + std::to_string(i).c_str();
         info.fingerPrint = fp;
+        if (i % 2 == 0)
+            info.lastInteraction = QDateTime::currentDateTime();
+
         keysModel.addKey(info);
     }
 }
diff --git a/ui/widgets/vcard/omemo/omemo.ui b/ui/widgets/vcard/omemo/omemo.ui
index cf72e0f..1dc6dc9 100644
--- a/ui/widgets/vcard/omemo/omemo.ui
+++ b/ui/widgets/vcard/omemo/omemo.ui
@@ -82,7 +82,7 @@
           <enum>QAbstractItemView::ScrollPerItem</enum>
          </property>
          <property name="uniformItemSizes">
-          <bool>true</bool>
+          <bool>false</bool>
          </property>
         </widget>
        </item>

From d4bf7e599ae25ca6d9d40a94c326ffe91eab4ce2 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Thu, 12 Jan 2023 20:56:01 +0300
Subject: [PATCH 223/281] better way to solve yesterday font problem, small
 visual avatar rendering fix

---
 shared/global.cpp                          | 28 +++++++++++++++++
 shared/global.h                            | 10 +++++++
 ui/widgets/conversation.cpp                |  2 +-
 ui/widgets/messageline/feedview.cpp        |  5 ++--
 ui/widgets/messageline/feedview.h          |  4 +--
 ui/widgets/messageline/messagedelegate.cpp | 35 ++++------------------
 ui/widgets/messageline/messagedelegate.h   | 14 ++++-----
 ui/widgets/vcard/omemo/keydelegate.cpp     | 12 ++++----
 ui/widgets/vcard/omemo/keydelegate.h       |  8 ++---
 9 files changed, 62 insertions(+), 56 deletions(-)

diff --git a/shared/global.cpp b/shared/global.cpp
index 2e2978a..dc1cb80 100644
--- a/shared/global.cpp
+++ b/shared/global.cpp
@@ -17,6 +17,7 @@
  */
 
 #include "global.h"
+#include <QFontDatabase>
 
 #include "enums.h"
 #include "ui/models/roster.h"
@@ -26,6 +27,25 @@ constexpr bool OMEMO_SUPPORT = true;
 constexpr bool OMEMO_SUPPORT = false
 #endif
 
+QFont getFont (QFontDatabase::SystemFont type, bool bold = false, bool italic = false, qreal factor = 1.0) {
+    QFont font = QFontDatabase::systemFont(type);
+    if (bold)
+        font.setBold(true);
+    if (italic)
+        font.setItalic(true);
+
+    if (factor != 1.0) {
+        float ps = font.pointSizeF();
+        if (ps != -1) {
+            font.setPointSizeF(ps * factor);
+        } else {
+            font.setPointSize(font.pointSize() * factor);
+        }
+    }
+
+    return font;
+}
+
 Shared::Global* Shared::Global::instance = 0;
 const std::set<QString> Shared::Global::supportedImagesExts = {"png", "jpg", "webp", "jpeg", "gif", "svg"};
 
@@ -102,6 +122,14 @@ Shared::Global::Global():
     defaultSystemStyle(QApplication::style()->objectName()),
     defaultSystemPalette(QApplication::palette()),
     omemoSupport(OMEMO_SUPPORT),
+    defaultFont(QFontDatabase::systemFont(QFontDatabase::GeneralFont)),
+    smallFont(getFont(QFontDatabase::SmallestReadableFont, false, true)),
+    headerFont(getFont(QFontDatabase::TitleFont, true, false, 1.1)),
+    titleFont(getFont(QFontDatabase::TitleFont, true, false, 1.3)),
+    defaultFontMetrics(defaultFont),
+    smallFontMetrics(smallFont),
+    headerFontMetrics(headerFont),
+    titleFontMetrics(titleFont),
     pluginSupport({
         {"KWallet", false},
         {"openFileManagerWindowJob", false},
diff --git a/shared/global.h b/shared/global.h
index d212cd1..1fc4aca 100644
--- a/shared/global.h
+++ b/shared/global.h
@@ -42,6 +42,8 @@
 #include <QProcess>
 #include <QDesktopServices>
 #include <QRegularExpression>
+#include <QFont>
+#include <QFontMetrics>
 
 namespace Shared {
     
@@ -100,6 +102,14 @@ namespace Shared {
         static void setTheme(const QString& path);
         static void setStyle(const QString& style);
         const bool omemoSupport;
+        QFont defaultFont;
+        QFont smallFont;
+        QFont headerFont;
+        QFont titleFont;
+        QFontMetrics defaultFontMetrics;
+        QFontMetrics smallFontMetrics;
+        QFontMetrics headerFontMetrics;
+        QFontMetrics titleFontMetrics;
         
         template<typename T>
         static T fromInt(int src);
diff --git a/ui/widgets/conversation.cpp b/ui/widgets/conversation.cpp
index 3e6a6e0..61d3163 100644
--- a/ui/widgets/conversation.cpp
+++ b/ui/widgets/conversation.cpp
@@ -371,7 +371,7 @@ void Conversation::setAvatar(const QString& path)
     if (path.size() == 0) {
         pixmap = Shared::icon("user", true).pixmap(avatarSize);
     } else {
-        pixmap = QPixmap(path).scaled(avatarSize);
+        pixmap = QPixmap(path).scaled(avatarSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
     }
 
 
diff --git a/ui/widgets/messageline/feedview.cpp b/ui/widgets/messageline/feedview.cpp
index 57e8143..9d222ae 100644
--- a/ui/widgets/messageline/feedview.cpp
+++ b/ui/widgets/messageline/feedview.cpp
@@ -24,7 +24,6 @@
 #include <QApplication>
 #include <QClipboard>
 #include <QDebug>
-#include <QFontDatabase>
 
 #include "messagedelegate.h"
 #include "messagefeed.h"
@@ -41,8 +40,6 @@ const std::set<int> FeedView::geometryChangingRoles = {
     Models::MessageFeed::Error,
     Models::MessageFeed::Date
 };
-QFont FeedView::dividerFont = QFontDatabase::systemFont(QFontDatabase::TitleFont);
-QFontMetrics FeedView::dividerMetrics = QFontMetrics(dividerFont);
 
 FeedView::FeedView(QWidget* parent):
     QAbstractItemView(parent),
@@ -54,6 +51,8 @@ FeedView::FeedView(QWidget* parent):
     clearWidgetsMode(false),
     modelState(Models::MessageFeed::complete),
     progress(),
+    dividerFont(Shared::Global::getInstance()->titleFont),
+    dividerMetrics(Shared::Global::getInstance()->titleFontMetrics),
     mousePressed(false),
     dragging(false),
     hovered(Shared::Hover::nothing),
diff --git a/ui/widgets/messageline/feedview.h b/ui/widgets/messageline/feedview.h
index 1d0c92a..a39afc8 100644
--- a/ui/widgets/messageline/feedview.h
+++ b/ui/widgets/messageline/feedview.h
@@ -99,8 +99,8 @@ private:
     bool clearWidgetsMode;
     Models::MessageFeed::SyncState modelState;
     Progress progress;
-    static QFont dividerFont;
-    static QFontMetrics dividerMetrics;
+    const QFont& dividerFont;
+    const QFontMetrics& dividerMetrics;
     bool mousePressed;
     bool dragging;
     Shared::Hover hovered;
diff --git a/ui/widgets/messageline/messagedelegate.cpp b/ui/widgets/messageline/messagedelegate.cpp
index 6247f4f..1830d71 100644
--- a/ui/widgets/messageline/messagedelegate.cpp
+++ b/ui/widgets/messageline/messagedelegate.cpp
@@ -24,21 +24,11 @@
 #include <QAbstractItemView>
 #include <QAbstractTextDocumentLayout>
 #include <QTextBlock>
-#include <QFontDatabase>
 #include <cmath>
 
 #include "messagedelegate.h"
 #include "messagefeed.h"
 
-QFont getFont (QFontDatabase::SystemFont type, bool bold, bool italic) {
-    QFont font = QFontDatabase::systemFont(type);
-    if (bold)
-        font.setBold(true);
-    if (italic)
-        font.setItalic(true);
-
-    return font;
-}
 constexpr int textMargin = 2;
 constexpr int statusIconSize = 16;
 
@@ -48,15 +38,13 @@ constexpr int bubbleBorderRadius = 3;
 int MessageDelegate::avatarHeight(50);
 int MessageDelegate::margin(6);
 
-bool MessageDelegate::fontsInitialized(false);
-QFont MessageDelegate::bodyFont;
-QFont MessageDelegate::nickFont;
-QFont MessageDelegate::dateFont;
-QFontMetrics MessageDelegate::nickMetrics(nickFont);
-QFontMetrics MessageDelegate::dateMetrics(dateFont);
-
 MessageDelegate::MessageDelegate(QObject* parent):
     QStyledItemDelegate(parent),
+    bodyFont(Shared::Global::getInstance()->defaultFont),
+    nickFont(Shared::Global::getInstance()->headerFont),
+    dateFont(Shared::Global::getInstance()->smallFont),
+    nickMetrics(Shared::Global::getInstance()->headerFontMetrics),
+    dateMetrics(Shared::Global::getInstance()->smallFontMetrics),
     bodyRenderer(new QTextDocument()),
     buttonHeight(0),
     buttonWidth(0),
@@ -71,11 +59,6 @@ MessageDelegate::MessageDelegate(QObject* parent):
     currentId(""),
     selection(0, 0)
 {
-    if (!fontsInitialized) {
-        fontsInitialized = true;
-        initializeFonts();
-    }
-
     bodyRenderer->setDocumentMargin(0);
     bodyRenderer->setDefaultFont(bodyFont);
 
@@ -812,11 +795,3 @@ void MessageDelegate::clearHelperWidget(const Models::FeedItem& data) const
         }
     }
 }
-
-void MessageDelegate::initializeFonts () {
-    bodyFont = getFont(QFontDatabase::GeneralFont, false, false);
-    nickFont = getFont(QFontDatabase::TitleFont, true, false);
-    dateFont = getFont(QFontDatabase::SmallestReadableFont, false, true);
-    nickMetrics = QFontMetrics(nickFont);
-    dateMetrics = QFontMetrics(dateFont);
-}
diff --git a/ui/widgets/messageline/messagedelegate.h b/ui/widgets/messageline/messagedelegate.h
index 7a54e66..a05b4cd 100644
--- a/ui/widgets/messageline/messagedelegate.h
+++ b/ui/widgets/messageline/messagedelegate.h
@@ -93,9 +93,6 @@ protected:
     
 protected slots:
     void onButtonPushed() const;
-
-private:
-    static void initializeFonts();
     
 private:
     class FeedButton : public QPushButton {
@@ -103,13 +100,12 @@ private:
         QString messageId;
     };
     
-    static bool fontsInitialized;
-    static QFont bodyFont;
-    static QFont nickFont;
-    static QFont dateFont;
+    const QFont& bodyFont;
+    const QFont& nickFont;
+    const QFont& dateFont;
+    const QFontMetrics& nickMetrics;
+    const QFontMetrics& dateMetrics;
     QTextDocument* bodyRenderer;
-    static QFontMetrics nickMetrics;
-    static QFontMetrics dateMetrics;
     
     int buttonHeight;
     int buttonWidth;
diff --git a/ui/widgets/vcard/omemo/keydelegate.cpp b/ui/widgets/vcard/omemo/keydelegate.cpp
index 646dfe9..e64cb19 100644
--- a/ui/widgets/vcard/omemo/keydelegate.cpp
+++ b/ui/widgets/vcard/omemo/keydelegate.cpp
@@ -16,22 +16,20 @@
 
 #include "keydelegate.h"
 #include <QPainter>
-#include <QFontDatabase>
 
 #include "keysmodel.h"
+#include <shared/global.h>
 
-constexpr int minHeight = 50;
-constexpr int minWidth = 400;
 constexpr uint8_t margin = 10;
 constexpr uint8_t partSize = 8;
 constexpr uint8_t maxSingleLineParts = 3;
 
 UI::KeyDelegate::KeyDelegate(QObject* parent):
     QStyledItemDelegate(parent),
-    fingerPrintFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)),
-    labelFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont)),
-    fingerPrintMetrics(fingerPrintFont),
-    labelFontMetrics(labelFont),
+    fingerPrintFont(Shared::Global::getInstance()->defaultFont),
+    labelFont(Shared::Global::getInstance()->smallFont),
+    fingerPrintMetrics(Shared::Global::getInstance()->defaultFontMetrics),
+    labelFontMetrics(Shared::Global::getInstance()->smallFontMetrics),
     spaceWidth(fingerPrintMetrics.horizontalAdvance(" "))
 {}
 
diff --git a/ui/widgets/vcard/omemo/keydelegate.h b/ui/widgets/vcard/omemo/keydelegate.h
index 2b0eb5b..4c914f3 100644
--- a/ui/widgets/vcard/omemo/keydelegate.h
+++ b/ui/widgets/vcard/omemo/keydelegate.h
@@ -33,10 +33,10 @@ public:
     void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override;
 
 private:
-    QFont fingerPrintFont;
-    QFont labelFont;
-    QFontMetrics fingerPrintMetrics;
-    QFontMetrics labelFontMetrics;
+    const QFont& fingerPrintFont;
+    const QFont& labelFont;
+    const QFontMetrics& fingerPrintMetrics;
+    const QFontMetrics& labelFontMetrics;
     int spaceWidth;
 
 private:

From b72a837754d7111b80bbb00fe717c27204425a1b Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Sat, 14 Jan 2023 18:34:14 +0300
Subject: [PATCH 224/281] trust level display in delegate, list size tweaking

---
 shared/enums.h                         |  2 +
 shared/global.cpp                      | 16 +++++
 shared/global.h                        |  4 ++
 ui/utils/CMakeLists.txt                |  2 +
 ui/utils/expandinglist.cpp             | 43 ++++++++++++++
 ui/utils/expandinglist.h               | 31 ++++++++++
 ui/widgets/vcard/omemo/keydelegate.cpp | 81 ++++++++++++++++++++------
 ui/widgets/vcard/omemo/keydelegate.h   |  4 +-
 ui/widgets/vcard/omemo/omemo.cpp       |  2 +-
 ui/widgets/vcard/omemo/omemo.ui        | 45 ++++++++++++--
 10 files changed, 203 insertions(+), 27 deletions(-)
 create mode 100644 ui/utils/expandinglist.cpp
 create mode 100644 ui/utils/expandinglist.h

diff --git a/shared/enums.h b/shared/enums.h
index 7273b2e..9fb4aad 100644
--- a/shared/enums.h
+++ b/shared/enums.h
@@ -144,6 +144,8 @@ enum class TrustLevel {
     authenticated
 };
 Q_ENUM_NS(TrustLevel)
+static const TrustLevel TrustLevelHighest = TrustLevel::undecided;
+static const TrustLevel TrustLevelLowest = TrustLevel::authenticated;
 
 enum class EncryptionProtocol {
     omemo
diff --git a/shared/global.cpp b/shared/global.cpp
index dc1cb80..7a1b494 100644
--- a/shared/global.cpp
+++ b/shared/global.cpp
@@ -113,6 +113,14 @@ Shared::Global::Global():
         tr("Always Ask", "AccountPassword"),
         tr("KWallet", "AccountPassword")
     }),
+    trustLevel({
+        tr("Undecided", "TrustLevel"),
+        tr("Automatically distrusted", "TrustLevel"),
+        tr("Manually distrusted", "TrustLevel"),
+        tr("Automatically trusted", "TrustLevel"),
+        tr("Manually trusted", "TrustLevel"),
+        tr("Authenticated", "TrustLevel")
+    }),
     accountPasswordDescription({
         tr("Your password is going to be stored in config file in plain text", "AccountPasswordDescription"),
         tr("Your password is going to be stored in config file but jammed with constant encryption key you can find in program source code. It might look like encryption but it's not", "AccountPasswordDescription"),
@@ -123,10 +131,12 @@ Shared::Global::Global():
     defaultSystemPalette(QApplication::palette()),
     omemoSupport(OMEMO_SUPPORT),
     defaultFont(QFontDatabase::systemFont(QFontDatabase::GeneralFont)),
+    monospaceFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)),
     smallFont(getFont(QFontDatabase::SmallestReadableFont, false, true)),
     headerFont(getFont(QFontDatabase::TitleFont, true, false, 1.1)),
     titleFont(getFont(QFontDatabase::TitleFont, true, false, 1.3)),
     defaultFontMetrics(defaultFont),
+    monospaceMetrics(monospaceFont),
     smallFontMetrics(smallFont),
     headerFontMetrics(headerFont),
     titleFontMetrics(titleFont),
@@ -256,6 +266,11 @@ QString Shared::Global::getName(Shared::AccountPassword ap)
     return instance->accountPassword[static_cast<int>(ap)];
 }
 
+QString Shared::Global::getName(Shared::TrustLevel tl)
+{
+    return instance->trustLevel[static_cast<int>(tl)];
+}
+
 void Shared::Global::setSupported(const QString& pluginName, bool support)
 {
     std::map<QString, bool>::iterator itr = instance->pluginSupport.find(pluginName);
@@ -405,3 +420,4 @@ FROM_INT_INPL(Shared::SubscriptionState)
 FROM_INT_INPL(Shared::AccountPassword)
 FROM_INT_INPL(Shared::Avatar)
 FROM_INT_INPL(Shared::Availability)
+FROM_INT_INPL(Shared::TrustLevel)
diff --git a/shared/global.h b/shared/global.h
index 1fc4aca..b311f9f 100644
--- a/shared/global.h
+++ b/shared/global.h
@@ -74,6 +74,7 @@ namespace Shared {
         static QString getName(Role rl);
         static QString getName(Message::State rl);
         static QString getName(AccountPassword ap);
+        static QString getName(TrustLevel tl);
         
         static QString getDescription(AccountPassword ap);
         
@@ -84,6 +85,7 @@ namespace Shared {
         const std::deque<QString> role;
         const std::deque<QString> messageState;
         const std::deque<QString> accountPassword;
+        const std::deque<QString> trustLevel;
         
         const std::deque<QString> accountPasswordDescription;
 
@@ -103,10 +105,12 @@ namespace Shared {
         static void setStyle(const QString& style);
         const bool omemoSupport;
         QFont defaultFont;
+        QFont monospaceFont;
         QFont smallFont;
         QFont headerFont;
         QFont titleFont;
         QFontMetrics defaultFontMetrics;
+        QFontMetrics monospaceMetrics;
         QFontMetrics smallFontMetrics;
         QFontMetrics headerFontMetrics;
         QFontMetrics titleFontMetrics;
diff --git a/ui/utils/CMakeLists.txt b/ui/utils/CMakeLists.txt
index b46d30d..7e68f25 100644
--- a/ui/utils/CMakeLists.txt
+++ b/ui/utils/CMakeLists.txt
@@ -15,4 +15,6 @@ target_sources(squawk PRIVATE
   resizer.h
   shadowoverlay.cpp
   shadowoverlay.h
+  expandinglist.cpp
+  expandinglist.h
   )
diff --git a/ui/utils/expandinglist.cpp b/ui/utils/expandinglist.cpp
new file mode 100644
index 0000000..6d1546d
--- /dev/null
+++ b/ui/utils/expandinglist.cpp
@@ -0,0 +1,43 @@
+// Squawk messenger. 
+// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+// 
+// 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 "expandinglist.h"
+
+QSize ExpandingList::viewportSizeHint() const {
+    if (QAbstractItemView::sizeAdjustPolicy() != QAbstractScrollArea::AdjustToContents)
+        return QListView::viewportSizeHint();
+
+    if (model() == nullptr)
+        return QSize(0, 0);
+    if (model()->rowCount() == 0)
+        return QSize(0, 0);
+
+#if (QT_VERSION < QT_VERSION_CHECK(6, 2, 0))
+    const int rowCount = model()->rowCount();
+    int height = 0;
+    for (int i = 0; i < rowCount; i++) {
+        height += QListView::sizeHintForRow(i);
+    }
+    return QSize(QListView::viewportSizeHint().width(), height);
+#else
+    return QListView::viewportSizeHint();
+#endif
+}
+
+QSize ExpandingList::minimumSizeHint() const {
+    return viewportSizeHint();
+}
+
diff --git a/ui/utils/expandinglist.h b/ui/utils/expandinglist.h
new file mode 100644
index 0000000..0b29e89
--- /dev/null
+++ b/ui/utils/expandinglist.h
@@ -0,0 +1,31 @@
+// Squawk messenger. 
+// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+// 
+// 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/>.
+
+#ifndef EXPANDINGLIST_H
+#define EXPANDINGLIST_H
+
+#include <QListView>
+
+class ExpandingList : public QListView {
+    Q_OBJECT
+public:
+    using QListView::QListView; //explicit constructor inheriatnce
+
+    QSize viewportSizeHint() const override;
+    QSize minimumSizeHint() const override;
+};
+
+#endif // EXPANDINGLIST_H
diff --git a/ui/widgets/vcard/omemo/keydelegate.cpp b/ui/widgets/vcard/omemo/keydelegate.cpp
index e64cb19..189d8df 100644
--- a/ui/widgets/vcard/omemo/keydelegate.cpp
+++ b/ui/widgets/vcard/omemo/keydelegate.cpp
@@ -16,6 +16,7 @@
 
 #include "keydelegate.h"
 #include <QPainter>
+#include <QDebug>
 
 #include "keysmodel.h"
 #include <shared/global.h>
@@ -26,10 +27,12 @@ constexpr uint8_t maxSingleLineParts = 3;
 
 UI::KeyDelegate::KeyDelegate(QObject* parent):
     QStyledItemDelegate(parent),
-    fingerPrintFont(Shared::Global::getInstance()->defaultFont),
+    defaultFont(Shared::Global::getInstance()->defaultFont),
+    fingerPrintFont(Shared::Global::getInstance()->monospaceFont),
     labelFont(Shared::Global::getInstance()->smallFont),
-    fingerPrintMetrics(Shared::Global::getInstance()->defaultFontMetrics),
-    labelFontMetrics(Shared::Global::getInstance()->smallFontMetrics),
+    defaultMetrics(Shared::Global::getInstance()->defaultFontMetrics),
+    fingerPrintMetrics(Shared::Global::getInstance()->monospaceMetrics),
+    labelMetrics(Shared::Global::getInstance()->smallFontMetrics),
     spaceWidth(fingerPrintMetrics.horizontalAdvance(" "))
 {}
 
@@ -47,11 +50,14 @@ void UI::KeyDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio
         painter->restore();
     }
 
+    QRect r;
+    int maxRight = 0;
+    int leftOrigin = option.rect.left() + margin;
     QColor q = painter->pen().color();
     q.setAlpha(180);
 
     QRect rect = option.rect;
-    rect.adjust(margin, margin, 0, 0);
+    rect.adjust(margin, margin, -margin, -margin);
     QVariant labelV = index.data(UI::KeysModel::Label);
     if (labelV.isValid()) {
         QString label = labelV.toString();
@@ -59,8 +65,9 @@ void UI::KeyDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio
             painter->save();
             painter->setFont(labelFont);
             painter->setPen(q);
-            painter->drawText(rect, Qt::AlignLeft | Qt::AlignTop, label);
-            rect.adjust(0, labelFontMetrics.lineSpacing(), 0, 0);
+            painter->drawText(rect, Qt::AlignLeft | Qt::AlignTop, label, &r);
+            rect.adjust(0, labelMetrics.lineSpacing(), 0, 0);
+            maxRight = std::max(r.width(), maxRight);
             painter->restore();
         }
     }
@@ -72,7 +79,6 @@ void UI::KeyDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio
         QByteArray fingerPrint = fingerPrintV.toByteArray();
         std::vector<QString> parts = getParts(fingerPrint);
         uint8_t partsLength = parts.size();
-        QRect r;
         uint8_t firstLine;
         if (partsLength > maxSingleLineParts)
             firstLine = partsLength / 2 + partsLength % 2;
@@ -81,15 +87,17 @@ void UI::KeyDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio
 
         for (uint8_t i = 0; i < partsLength; ++i) {
             if (i == firstLine) {
-                rect.setLeft(option.rect.left() + margin);
+                maxRight = std::max(rect.left() - leftOrigin - margin, maxRight);
+                rect.setLeft(leftOrigin);
                 rect.adjust(0, r.height() + fingerPrintMetrics.leading(), 0, 0);
             }
             painter->drawText(rect, Qt::AlignLeft | Qt::AlignTop, parts[i], &r);
             rect.adjust(r.width() + spaceWidth ,0, 0, 0);
         }
 
+        maxRight = std::max(rect.left() - leftOrigin - margin, maxRight);
         rect.adjust(0, r.height() + fingerPrintMetrics.leading(), 0, 0);
-        rect.setLeft(option.rect.left() + margin);
+        rect.setLeft(leftOrigin);
 
         painter->restore();
     }
@@ -102,12 +110,27 @@ void UI::KeyDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio
             painter->save();
             painter->setFont(labelFont);
             painter->setPen(q);
-            painter->drawText(rect, Qt::AlignLeft | Qt::AlignTop, last.toString());
-            rect.adjust(0, labelFontMetrics.lineSpacing(), 0, 0);
+            painter->drawText(rect, Qt::AlignLeft | Qt::AlignTop, last.toString(), &r);
+            rect.adjust(0, labelMetrics.lineSpacing(), 0, 0);
+            maxRight = std::max(r.width(), maxRight);
             painter->restore();
         }
     }
-    //painter->drawText(option.rect, option.displayAlignment, index.data().toString());
+
+    QVariant levelV = index.data(UI::KeysModel::TrustLevel);
+    if (levelV.isValid()) {
+        Shared::TrustLevel level = static_cast<Shared::TrustLevel>(levelV.toUInt());
+        QString levelName = Shared::Global::getName(level);
+
+        if (maxRight > 0)
+            maxRight += margin;
+        rect.setLeft(leftOrigin + maxRight);
+        rect.setTop(option.rect.top() + maxRight);
+        rect.setBottom(option.rect.bottom() - maxRight);
+        painter->setFont(defaultFont);
+        painter->drawText(rect, Qt::AlignLeft | Qt::AlignVCenter, levelName, &r);
+    }
+
 
     painter->restore();
 }
@@ -117,10 +140,15 @@ QSize UI::KeyDelegate::sizeHint(const QStyleOptionViewItem& option, const QModel
 
     int mh = margin * 2;
     int mw = margin * 2;
+    int width = 0;
 
     QVariant labelV = index.data(UI::KeysModel::Label);
-    if (labelV.isValid() && labelV.toString().size() > 0) {
-        mh += labelFontMetrics.lineSpacing();
+    if (labelV.isValid()) {
+        QString label = labelV.toString();
+        if (label.size() > 0) {
+            mh += labelMetrics.lineSpacing();
+            width = labelMetrics.horizontalAdvance(label);
+        }
     }
 
     QVariant fingerPrintV = index.data(UI::KeysModel::FingerPrint);
@@ -129,9 +157,8 @@ QSize UI::KeyDelegate::sizeHint(const QStyleOptionViewItem& option, const QModel
         uint8_t parts = hex.size() / partSize;
 
         mh += fingerPrintMetrics.height();
-        if (parts > maxSingleLineParts) {
+        if (parts > maxSingleLineParts)
             mh += fingerPrintMetrics.height() + fingerPrintMetrics.leading();
-        }
 
         uint8_t firstLine;
         if (parts > maxSingleLineParts)
@@ -139,13 +166,29 @@ QSize UI::KeyDelegate::sizeHint(const QStyleOptionViewItem& option, const QModel
         else
             firstLine = parts;
 
-        mw += firstLine * partSize * spaceWidth + (firstLine - 1) * spaceWidth;
+        width = std::max(width, firstLine * fingerPrintMetrics.horizontalAdvance(hex, partSize) + (firstLine - 1) * spaceWidth);
     }
     QVariant lastV = index.data(UI::KeysModel::LastInteraction);
-    if (lastV.isValid() && lastV.toDateTime().isValid()) {
-        mh += labelFontMetrics.lineSpacing();
+    if (lastV.isValid()) {
+        QDateTime last = lastV.toDateTime();
+        if (last.isValid()) {
+            mh += labelMetrics.lineSpacing();
+            QString dt = last.toString();
+            width = std::max(labelMetrics.horizontalAdvance(dt), width);
+        }
     }
 
+    QVariant levelV = index.data(UI::KeysModel::TrustLevel);
+    if (levelV.isValid()) {
+        Shared::TrustLevel level = static_cast<Shared::TrustLevel>(levelV.toUInt());
+        QString levelName = Shared::Global::getName(level);
+        if (width > 0)
+            width += margin;
+
+        width += defaultMetrics.horizontalAdvance(levelName);
+    }
+
+    mw += width;
     if (size.width() < mw)
         size.setWidth(mw);
 
diff --git a/ui/widgets/vcard/omemo/keydelegate.h b/ui/widgets/vcard/omemo/keydelegate.h
index 4c914f3..01b45c1 100644
--- a/ui/widgets/vcard/omemo/keydelegate.h
+++ b/ui/widgets/vcard/omemo/keydelegate.h
@@ -33,10 +33,12 @@ public:
     void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override;
 
 private:
+    const QFont& defaultFont;
     const QFont& fingerPrintFont;
     const QFont& labelFont;
+    const QFontMetrics& defaultMetrics;
     const QFontMetrics& fingerPrintMetrics;
-    const QFontMetrics& labelFontMetrics;
+    const QFontMetrics& labelMetrics;
     int spaceWidth;
 
 private:
diff --git a/ui/widgets/vcard/omemo/omemo.cpp b/ui/widgets/vcard/omemo/omemo.cpp
index 0aea97c..c260ab8 100644
--- a/ui/widgets/vcard/omemo/omemo.cpp
+++ b/ui/widgets/vcard/omemo/omemo.cpp
@@ -18,7 +18,7 @@
 #include "ui_omemo.h"
 
 #include <random>
-constexpr uint8_t fingerprintLength = 24;
+constexpr uint8_t fingerprintLength = 32;
 
 Omemo::Omemo(QWidget* parent):
     QWidget(parent),
diff --git a/ui/widgets/vcard/omemo/omemo.ui b/ui/widgets/vcard/omemo/omemo.ui
index 1dc6dc9..d9f55b1 100644
--- a/ui/widgets/vcard/omemo/omemo.ui
+++ b/ui/widgets/vcard/omemo/omemo.ui
@@ -65,21 +65,24 @@
       </property>
       <layout class="QGridLayout" name="gridLayout" columnstretch="1,3,1">
        <item row="1" column="1">
-        <widget class="QListView" name="keysView">
+        <widget class="ExpandingList" name="keysView">
+         <property name="sizePolicy">
+          <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+           <horstretch>0</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
          <property name="sizeAdjustPolicy">
           <enum>QAbstractScrollArea::AdjustToContents</enum>
          </property>
          <property name="showDropIndicator" stdset="0">
           <bool>false</bool>
          </property>
-         <property name="selectionBehavior">
-          <enum>QAbstractItemView::SelectColumns</enum>
-         </property>
          <property name="verticalScrollMode">
           <enum>QAbstractItemView::ScrollPerPixel</enum>
          </property>
          <property name="horizontalScrollMode">
-          <enum>QAbstractItemView::ScrollPerItem</enum>
+          <enum>QAbstractItemView::ScrollPerPixel</enum>
          </property>
          <property name="uniformItemSizes">
           <bool>false</bool>
@@ -122,7 +125,29 @@
         </widget>
        </item>
        <item row="5" column="1">
-        <widget class="QListView" name="unusedKeysView"/>
+        <widget class="ExpandingList" name="unusedKeysView">
+         <property name="sizePolicy">
+          <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+           <horstretch>0</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
+         <property name="sizeAdjustPolicy">
+          <enum>QAbstractScrollArea::AdjustToContents</enum>
+         </property>
+         <property name="showDropIndicator" stdset="0">
+          <bool>false</bool>
+         </property>
+         <property name="verticalScrollMode">
+          <enum>QAbstractItemView::ScrollPerPixel</enum>
+         </property>
+         <property name="horizontalScrollMode">
+          <enum>QAbstractItemView::ScrollPerPixel</enum>
+         </property>
+         <property name="uniformItemSizes">
+          <bool>false</bool>
+         </property>
+        </widget>
        </item>
        <item row="0" column="0" rowspan="6">
         <spacer name="spacerLeft">
@@ -156,6 +181,14 @@
    </item>
   </layout>
  </widget>
+ <customwidgets>
+  <customwidget>
+   <class>ExpandingList</class>
+   <extends>QListView</extends>
+   <header location="global">ui/utils/expandinglist.h</header>
+   <container>1</container>
+  </customwidget>
+ </customwidgets>
  <resources/>
  <connections/>
 </ui>

From 73d83f55af829135db3f2879cf849e5c6b9ed144 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Sun, 15 Jan 2023 21:17:38 +0300
Subject: [PATCH 225/281] context menu to trust or distrust keys

---
 ui/widgets/vcard/omemo/keydelegate.cpp | 22 +++++---
 ui/widgets/vcard/omemo/keysmodel.cpp   | 71 +++++++++++++++++++++++---
 ui/widgets/vcard/omemo/keysmodel.h     |  7 +++
 ui/widgets/vcard/omemo/omemo.cpp       | 54 ++++++++++++++++++--
 ui/widgets/vcard/omemo/omemo.h         |  8 ++-
 5 files changed, 144 insertions(+), 18 deletions(-)

diff --git a/ui/widgets/vcard/omemo/keydelegate.cpp b/ui/widgets/vcard/omemo/keydelegate.cpp
index 189d8df..d0023e5 100644
--- a/ui/widgets/vcard/omemo/keydelegate.cpp
+++ b/ui/widgets/vcard/omemo/keydelegate.cpp
@@ -43,6 +43,12 @@ void UI::KeyDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio
     painter->save();
     painter->setRenderHint(QPainter::Antialiasing, true);
 
+    QRect r;
+    int maxRight = 0;
+    int leftOrigin = option.rect.left() + margin;
+    QColor q = painter->pen().color();
+    q.setAlpha(180);
+    QRect rect = option.rect;
     bool hover = option.state & QStyle::State_MouseOver;
     if (hover) {
         painter->save();
@@ -50,13 +56,15 @@ void UI::KeyDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio
         painter->restore();
     }
 
-    QRect r;
-    int maxRight = 0;
-    int leftOrigin = option.rect.left() + margin;
-    QColor q = painter->pen().color();
-    q.setAlpha(180);
+    QVariant dirtyV = index.data(UI::KeysModel::Dirty);
+    if (dirtyV.isValid() && dirtyV.toBool()) {
+        painter->save();
+        rect.setWidth(margin / 2);
+        painter->fillRect(rect, option.palette.brush(QPalette::Active, QPalette::Highlight));
+        rect.setWidth(option.rect.width());
+        painter->restore();
+    }
 
-    QRect rect = option.rect;
     rect.adjust(margin, margin, -margin, -margin);
     QVariant labelV = index.data(UI::KeysModel::Label);
     if (labelV.isValid()) {
@@ -167,6 +175,7 @@ QSize UI::KeyDelegate::sizeHint(const QStyleOptionViewItem& option, const QModel
             firstLine = parts;
 
         width = std::max(width, firstLine * fingerPrintMetrics.horizontalAdvance(hex, partSize) + (firstLine - 1) * spaceWidth);
+        width += 1;     //there is a mistake somewhere, this the cheapest way to compensate it
     }
     QVariant lastV = index.data(UI::KeysModel::LastInteraction);
     if (lastV.isValid()) {
@@ -186,6 +195,7 @@ QSize UI::KeyDelegate::sizeHint(const QStyleOptionViewItem& option, const QModel
             width += margin;
 
         width += defaultMetrics.horizontalAdvance(levelName);
+        width += 1;     //there is a mistake somewhere, this the cheapest way to compensate it
     }
 
     mw += width;
diff --git a/ui/widgets/vcard/omemo/keysmodel.cpp b/ui/widgets/vcard/omemo/keysmodel.cpp
index 750e42a..1488370 100644
--- a/ui/widgets/vcard/omemo/keysmodel.cpp
+++ b/ui/widgets/vcard/omemo/keysmodel.cpp
@@ -24,12 +24,32 @@ const QHash<int, QByteArray> UI::KeysModel::roles = {
 
 UI::KeysModel::KeysModel(QObject* parent):
     QAbstractListModel(parent),
-    keys()
+    keys(),
+    modified()
 {
+
 }
 
 UI::KeysModel::~KeysModel() {
+    for (Shared::KeyInfo* key : keys) {
+        delete key;
+    }
 
+    for (std::pair<const int, Shared::KeyInfo*>& pair: modified) {
+        delete pair.second;
+    }
+}
+
+std::deque<Shared::KeyInfo> UI::KeysModel::modifiedKeys() const {
+    std::deque<Shared::KeyInfo> response(modified.size());
+
+    int i = 0;
+    for (const std::pair<const int, Shared::KeyInfo*>& pair: modified) {
+        response[i] = *(pair.second);
+        ++i;
+    }
+
+    return response;
 }
 
 void UI::KeysModel::addKey(const Shared::KeyInfo& info) {
@@ -40,24 +60,34 @@ void UI::KeysModel::addKey(const Shared::KeyInfo& info) {
 
 QVariant UI::KeysModel::data(const QModelIndex& index, int role) const {
     int i = index.row();
+    const Shared::KeyInfo* info;
+    bool dirty;
+    std::map<int, Shared::KeyInfo*>::const_iterator itr = modified.find(i);
+    if (itr != modified.end()) {
+        info = itr->second;
+        dirty = true;
+    } else {
+        dirty = false;
+        info = keys[i];
+    }
     QVariant answer;
 
     switch (role) {
         case Qt::DisplayRole:
         case Label:
-            answer = keys[i]->label;
+            answer = info->label;
             break;
         case FingerPrint:
-            answer = keys[i]->fingerPrint;
+            answer = info->fingerPrint;
             break;
         case TrustLevel:
-            answer = static_cast<uint8_t>(keys[i]->trustLevel);
+            answer = static_cast<uint8_t>(info->trustLevel);
             break;
         case LastInteraction:
-            answer = keys[i]->lastInteraction;
+            answer = info->lastInteraction;
             break;
         case Dirty:
-            answer = false;
+            answer = dirty;
             break;
     }
 
@@ -78,7 +108,34 @@ QModelIndex UI::KeysModel::index(int row, int column, const QModelIndex& parent)
     return createIndex(row, column, keys[row]);
 }
 
-
+void UI::KeysModel::revertKey(int row) {
+    std::map<int, Shared::KeyInfo*>::const_iterator itr = modified.find(row);
+    if (itr != modified.end()) {
+        modified.erase(itr);
+        QModelIndex index = createIndex(row, 0, keys[row]);
+        dataChanged(index, index);
+    }
+}
+
+void UI::KeysModel::setTrustLevel(int row, Shared::TrustLevel level) {
+    std::map<int, Shared::KeyInfo*>::const_iterator itr = modified.find(row);
+    Shared::KeyInfo* info;
+    if (itr == modified.end()) {
+        if (row < rowCount()) {
+            info = new Shared::KeyInfo(*(keys[row]));
+            modified.insert(std::make_pair(row, info));
+        } else {
+            return;
+        }
+    } else {
+        info = itr->second;
+    }
+
+    info->trustLevel = level;
+
+    QModelIndex index = createIndex(row, 0, info);
+    dataChanged(index, index, {KeysModel::Dirty});
+}
 
 
 
diff --git a/ui/widgets/vcard/omemo/keysmodel.h b/ui/widgets/vcard/omemo/keysmodel.h
index 68f222c..e1a7606 100644
--- a/ui/widgets/vcard/omemo/keysmodel.h
+++ b/ui/widgets/vcard/omemo/keysmodel.h
@@ -40,6 +40,8 @@ public:
     QHash<int, QByteArray> roleNames() const override;
     QModelIndex index(int row, int column, const QModelIndex & parent) const override;
 
+    std::deque<Shared::KeyInfo> modifiedKeys() const;
+
     enum Roles {
         Label = Qt::UserRole + 1,
         FingerPrint,
@@ -48,8 +50,13 @@ public:
         Dirty
     };
 
+public slots:
+    void revertKey(int index);
+    void setTrustLevel(int index, Shared::TrustLevel level);
+
 private:
     std::deque<Shared::KeyInfo*> keys;
+    std::map<int, Shared::KeyInfo*> modified;
 
 private:
     static const QHash<int, QByteArray> roles;
diff --git a/ui/widgets/vcard/omemo/omemo.cpp b/ui/widgets/vcard/omemo/omemo.cpp
index c260ab8..3a5ab73 100644
--- a/ui/widgets/vcard/omemo/omemo.cpp
+++ b/ui/widgets/vcard/omemo/omemo.cpp
@@ -26,7 +26,8 @@ Omemo::Omemo(QWidget* parent):
     keysDelegate(),
     unusedKeysDelegate(),
     keysModel(),
-    unusedKeysModel()
+    unusedKeysModel(),
+    contextMenu(new QMenu())
 {
     m_ui->setupUi(this);
 
@@ -36,15 +37,17 @@ Omemo::Omemo(QWidget* parent):
     m_ui->keysView->setModel(&keysModel);
     m_ui->unusedKeysView->setItemDelegate(&unusedKeysDelegate);
     m_ui->unusedKeysView->setModel(&unusedKeysModel);
+
+    m_ui->keysView->setContextMenuPolicy(Qt::CustomContextMenu);
+    connect(m_ui->keysView, &QWidget::customContextMenuRequested, this, &Omemo::onActiveKeysContextMenu);
 }
 
 Omemo::~Omemo()
 {
-
+    contextMenu->deleteLater();
 }
 
-void Omemo::generateMockData()
-{
+void Omemo::generateMockData() {
     std::random_device rd;
     std::uniform_int_distribution<char> dist(CHAR_MIN, CHAR_MAX);
     for (int i = 0; i < 5; ++i) {
@@ -63,3 +66,46 @@ void Omemo::generateMockData()
         keysModel.addKey(info);
     }
 }
+
+void Omemo::onActiveKeysContextMenu(const QPoint& pos) {
+    contextMenu->clear();
+    QModelIndex index = m_ui->keysView->indexAt(pos);
+    if (index.isValid()) {
+        QVariant dirtyV = index.data(UI::KeysModel::Dirty);
+        if (dirtyV.isValid() && dirtyV.toBool()) {
+            QAction* rev = contextMenu->addAction(Shared::icon("clean"), tr("Revert changes"));
+            connect(rev, &QAction::triggered, std::bind(&UI::KeysModel::revertKey, &keysModel, index.row()));
+        }
+
+        QVariant levelV = index.data(UI::KeysModel::TrustLevel);
+        if (levelV.isValid()) {
+            Shared::TrustLevel level = static_cast<Shared::TrustLevel>(levelV.toUInt());
+            if (level == Shared::TrustLevel::undecided ||
+                level == Shared::TrustLevel::automaticallyDistrusted ||
+                level == Shared::TrustLevel::manuallyDistrusted
+            ) {
+                QAction* rev = contextMenu->addAction(Shared::icon("favorite"), tr("Trust"));
+                connect(rev, &QAction::triggered,
+                        std::bind(&UI::KeysModel::setTrustLevel, &keysModel,
+                                  index.row(), Shared::TrustLevel::manuallyTrusted
+                        )
+                );
+            }
+
+            if (level == Shared::TrustLevel::undecided ||
+                level == Shared::TrustLevel::automaticallyTrusted ||
+                level == Shared::TrustLevel::manuallyTrusted ||
+                level == Shared::TrustLevel::authenticated
+            ) {
+                QAction* rev = contextMenu->addAction(Shared::icon("unfavorite"), tr("Distrust"));
+                connect(rev, &QAction::triggered,
+                        std::bind(&UI::KeysModel::setTrustLevel, &keysModel,
+                                  index.row(), Shared::TrustLevel::manuallyDistrusted
+                        )
+                );
+            }
+        }
+    }
+
+    contextMenu->popup(m_ui->keysView->viewport()->mapToGlobal(pos));
+}
diff --git a/ui/widgets/vcard/omemo/omemo.h b/ui/widgets/vcard/omemo/omemo.h
index 2bc0433..59e3dac 100644
--- a/ui/widgets/vcard/omemo/omemo.h
+++ b/ui/widgets/vcard/omemo/omemo.h
@@ -17,11 +17,13 @@
 #ifndef VCARD_OMEMO_H
 #define VCARD_OMEMO_H
 
-#include <qwidget.h>
+#include <QWidget>
 #include <QScopedPointer>
+#include <QMenu>
 
 #include "keysmodel.h"
 #include "keydelegate.h"
+#include "shared/icons.h"
 
 namespace Ui
 {
@@ -34,6 +36,9 @@ public:
     Omemo(QWidget* parent = nullptr);
     ~Omemo();
 
+private slots:
+    void onActiveKeysContextMenu(const QPoint& pos);
+
 private:
     void generateMockData();
 
@@ -43,6 +48,7 @@ private:
     UI::KeyDelegate unusedKeysDelegate;
     UI::KeysModel keysModel;
     UI::KeysModel unusedKeysModel;
+    QMenu* contextMenu;
 };
 
 #endif // VCARD_OMEMO_H

From 3c6b611a411e978d4b9e7e36709b342d6e344ba1 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Sun, 29 Jan 2023 20:26:54 +0300
Subject: [PATCH 226/281] keeping up with qxmpp

---
 CMakeLists.txt                   |   1 +
 core/account.cpp                 |  27 +++-----
 core/adapterfunctions.h          |  15 +++++
 core/handlers/messagehandler.cpp |  29 ++++++++
 core/handlers/omemohandler.cpp   |  54 +++++++--------
 core/handlers/omemohandler.h     |  23 +++----
 core/handlers/trusthandler.cpp   | 109 ++++++++++---------------------
 core/handlers/trusthandler.h     |  35 +++++-----
 external/qxmpp                   |   2 +-
 ui/widgets/about.cpp             |   4 ++
 10 files changed, 146 insertions(+), 153 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index b40e876..01a9bd1 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -130,6 +130,7 @@ if (NOT SYSTEM_QXMPP)
 
   target_include_directories(squawk PRIVATE ${CMAKE_SOURCE_DIR}/external/qxmpp/src/base)
   target_include_directories(squawk PRIVATE ${CMAKE_SOURCE_DIR}/external/qxmpp/src/client)
+  target_include_directories(squawk PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/external/qxmpp/src)
 
   if (WITH_OMEMO)
     target_include_directories(squawk PRIVATE ${CMAKE_SOURCE_DIR}/external/qxmpp/src/omemo)
diff --git a/core/account.cpp b/core/account.cpp
index e1bbfb9..bb3ebea 100644
--- a/core/account.cpp
+++ b/core/account.cpp
@@ -108,23 +108,19 @@ Account::Account(const QString& p_login, const QString& p_server, const QString&
     client.addExtension(om);
 
     if (oh->hasOwnDevice()) {
-        QFuture<bool> future = om->load();
+        QXmppTask<bool> future = om->load();
         loadingOmemo = true;
-
-        QFutureWatcher<bool> *watcher = new QFutureWatcher<bool>(this);
-        QObject::connect(watcher, &QFutureWatcherBase::finished, [watcher, this] () {
+        future.then(this, [this] (bool result) {
             loadingOmemo = false;
             if (state == Shared::ConnectionState::scheduled) {
                 client.connectToServer(config, presence);
             }
-            if (watcher->result()) {
+            if (result) {
                 qDebug() << "successfully loaded OMEMO data for account" << getName();
             } else {
                 qDebug() << "couldn't load OMEMO data for account" << getName();
             }
-            watcher->deleteLater();
         });
-        watcher->setFuture(future);
     }
 #endif
     
@@ -235,20 +231,17 @@ void Core::Account::onClientStateChange(QXmppClient::State st)
 #ifdef WITH_OMEMO
                         if (!oh->hasOwnDevice()) {
                             qDebug() << "setting up OMEMO data for account" << getName();
-                            QFuture<bool> future = om->setUp();
-                            QFutureWatcher<bool> *watcher = new QFutureWatcher<bool>(this);
-                            QObject::connect(watcher, &QFutureWatcherBase::finished, [watcher, this] () {
-                                if (watcher->result()) {
+                            QXmppTask<bool> future = om->setUp();
+                            future.then(this, [this] (bool result) {
+                                if (result)
                                     qDebug() << "successfully set up OMEMO data for account" << getName();
-                                } else {
+                                 else
                                     qDebug() << "couldn't set up OMEMO data for account" << getName();
-                                }
-                                watcher->deleteLater();
-                                if (state == Shared::ConnectionState::connected) {
+
+                                if (state == Shared::ConnectionState::connected)
                                     runDiscoveryService();
-                                }
+
                             });
-                            watcher->setFuture(future);
                         } else {
                             runDiscoveryService();
                         }
diff --git a/core/adapterfunctions.h b/core/adapterfunctions.h
index 6e50a75..287816b 100644
--- a/core/adapterfunctions.h
+++ b/core/adapterfunctions.h
@@ -19,6 +19,8 @@
 #define CORE_ADAPTER_FUNCTIONS_H
 
 #include <QXmppVCardIq.h>
+#include <QXmppTask.h>
+#include <QXmppPromise.h>
 #include <shared/vcard.h>
 
 namespace Core {
@@ -26,6 +28,19 @@ namespace Core {
 void initializeVCard(Shared::VCard& vCard, const QXmppVCardIq& card);
 void initializeQXmppVCard(QXmppVCardIq& card, const Shared::VCard& vCard);
 
+template<typename T>
+QXmppTask<T> makeReadyTask(T &&value) {
+    QXmppPromise<T> promise;
+    promise.finish(std::move(value));
+    return promise.task();
+}
+
+inline QXmppTask<void> makeReadyTask() {
+    QXmppPromise<void> promise;
+    promise.finish();
+    return promise.task();
+}
+
 }
 
 
diff --git a/core/handlers/messagehandler.cpp b/core/handlers/messagehandler.cpp
index b6d32b9..b11ea2a 100644
--- a/core/handlers/messagehandler.cpp
+++ b/core/handlers/messagehandler.cpp
@@ -29,6 +29,35 @@ Core::MessageHandler::MessageHandler(Core::Account* account):
 
 void Core::MessageHandler::onMessageReceived(const QXmppMessage& msg)
 {
+#if (QXMPP_VERSION) >= QT_VERSION_CHECK(1, 5, 0)
+#ifdef WITH_OMEMO
+    switch (msg.encryptionMethod()) {
+        case QXmpp::NoEncryption:
+            break;                          //just do nothing
+        case QXmpp::UnknownEncryption:
+            qDebug() << "Account" << acc->getName() << "received a message with unknown encryption type";
+            break;                          //let it go the way it is, there is nothing I can do here
+        case QXmpp::Otr:
+            qDebug() << "Account" << acc->getName() << "received an OTR encrypted message, not supported yet";
+            break;                          //let it go the way it is, there is nothing I can do yet
+        case QXmpp::LegacyOpenPgp:
+            qDebug() << "Account" << acc->getName() << "received an LegacyOpenPgp encrypted message, not supported yet";
+            break;                          //let it go the way it is, there is nothing I can do yet
+        case QXmpp::Ox:
+            qDebug() << "Account" << acc->getName() << "received an Ox encrypted message, not supported yet";
+            break;                          //let it go the way it is, there is nothing I can do yet
+        case QXmpp::Omemo0:
+            qDebug() << "Account" << acc->getName() << "received an Omemo0 encrypted message, not supported yet";
+            break;                          //let it go the way it is, there is nothing I can do yet
+        case QXmpp::Omemo1:
+            qDebug() << "Account" << acc->getName() << "received an Omemo1 encrypted message, not supported yet";
+            break;                          //let it go the way it is, there is nothing I can do yet
+        case QXmpp::Omemo2:
+            qDebug() << "Account" << acc->getName() << "received an Omemo2 encrypted message, not supported yet";
+            break;                          //let it go the way it is, there is nothing I can do yet
+    }
+#endif
+#endif
     bool handled = false;
     switch (msg.type()) {
         case QXmppMessage::Normal:
diff --git a/core/handlers/omemohandler.cpp b/core/handlers/omemohandler.cpp
index 57d9749..2b864e1 100644
--- a/core/handlers/omemohandler.cpp
+++ b/core/handlers/omemohandler.cpp
@@ -17,6 +17,7 @@
 #include <QDebug>
 #include "omemohandler.h"
 #include "core/account.h"
+#include "core/adapterfunctions.h"
 
 Core::OmemoHandler::OmemoHandler(Account* account) :
     QXmppOmemoStorage(),
@@ -46,13 +47,7 @@ bool Core::OmemoHandler::hasOwnDevice() {
     return ownDevice.has_value();
 }
 
-QFuture<void> Core::OmemoHandler::emptyVoidFuture() {
-    QFutureInterface<QXmppOmemoStorage::OmemoData> result(QFutureInterfaceBase::Started);
-    result.reportFinished();
-    return result.future();
-}
-
-QFuture<QXmppOmemoStorage::OmemoData> Core::OmemoHandler::allData() {
+QXmppTask<QXmppOmemoStorage::OmemoData> Core::OmemoHandler::allData() {
     OmemoData data;
     data.ownDevice = ownDevice;
 
@@ -72,13 +67,10 @@ QFuture<QXmppOmemoStorage::OmemoData> Core::OmemoHandler::allData() {
         data.devices.insert(pair.first, pair.second);
     }
 
-    QFutureInterface<QXmppOmemoStorage::OmemoData> result(QFutureInterfaceBase::Started);
-    result.reportResult(std::move(data));
-    result.reportFinished();
-    return result.future();
+    return Core::makeReadyTask(std::move(data));
 }
 
-QFuture<void> Core::OmemoHandler::addDevice(const QString& jid, uint32_t deviceId, const QXmppOmemoStorage::Device& device) {
+QXmppTask<void> Core::OmemoHandler::addDevice(const QString& jid, uint32_t deviceId, const QXmppOmemoStorage::Device& device) {
     QHash<uint32_t, Device> devs;
     bool had = true;
     try {
@@ -94,23 +86,23 @@ QFuture<void> Core::OmemoHandler::addDevice(const QString& jid, uint32_t deviceI
         devices->addRecord(jid, devs);
     }
 
-    return emptyVoidFuture();
+    return Core::makeReadyTask();
 }
 
-QFuture<void> Core::OmemoHandler::addPreKeyPairs(const QHash<uint32_t, QByteArray>& keyPairs) {
+QXmppTask<void> Core::OmemoHandler::addPreKeyPairs(const QHash<uint32_t, QByteArray>& keyPairs) {
     for (QHash<uint32_t, QByteArray>::const_iterator itr = keyPairs.begin(), end = keyPairs.end(); itr != end; ++itr) {
         preKeyPairs->forceRecord(itr.key(), itr.value());
     }
 
-    return emptyVoidFuture();
+    return Core::makeReadyTask();
 }
 
-QFuture<void> Core::OmemoHandler::addSignedPreKeyPair(uint32_t keyId, const QXmppOmemoStorage::SignedPreKeyPair& keyPair) {
+QXmppTask<void> Core::OmemoHandler::addSignedPreKeyPair(uint32_t keyId, const QXmppOmemoStorage::SignedPreKeyPair& keyPair) {
     signedPreKeyPairs->forceRecord(keyId, std::make_pair(keyPair.creationDate, keyPair.data));
-    return emptyVoidFuture();
+    return Core::makeReadyTask();
 }
 
-QFuture<void> Core::OmemoHandler::removeDevice(const QString& jid, uint32_t deviceId) {
+QXmppTask<void> Core::OmemoHandler::removeDevice(const QString& jid, uint32_t deviceId) {
     QHash<uint32_t, Device> devs = devices->getRecord(jid);
     devs.remove(deviceId);
     if (devs.isEmpty()) {
@@ -118,25 +110,27 @@ QFuture<void> Core::OmemoHandler::removeDevice(const QString& jid, uint32_t devi
     } else {
         devices->changeRecord(jid, devs);
     }
-    return emptyVoidFuture();
+    return Core::makeReadyTask();
 }
 
-QFuture<void> Core::OmemoHandler::removeDevices(const QString& jid) {
+QXmppTask<void> Core::OmemoHandler::removeDevices(const QString& jid) {
     devices->removeRecord(jid);
-    return emptyVoidFuture();
+    return Core::makeReadyTask();
 }
 
-QFuture<void> Core::OmemoHandler::removePreKeyPair(uint32_t keyId) {
+QXmppTask<void> Core::OmemoHandler::removePreKeyPair(uint32_t keyId) {
     preKeyPairs->removeRecord(keyId);
-    return emptyVoidFuture();
+    return Core::makeReadyTask();
 }
 
-QFuture<void> Core::OmemoHandler::removeSignedPreKeyPair(uint32_t keyId) {
-    signedPreKeyPairs->removeRecord(keyId);
-    return emptyVoidFuture();
+QXmppTask<void> Core::OmemoHandler::removeSignedPreKeyPair(uint32_t keyId) {
+    try {
+        signedPreKeyPairs->removeRecord(keyId);
+    } catch (const DataBase::NotFound& e) {}
+    return Core::makeReadyTask();
 }
 
-QFuture<void> Core::OmemoHandler::setOwnDevice(const std::optional<OwnDevice>& device) {
+QXmppTask<void> Core::OmemoHandler::setOwnDevice(const std::optional<OwnDevice>& device) {
     bool had = ownDevice.has_value();
     ownDevice = device;
     if (ownDevice.has_value()) {
@@ -150,14 +144,14 @@ QFuture<void> Core::OmemoHandler::setOwnDevice(const std::optional<OwnDevice>& d
             meta->removeRecord("ownDevice");
         }
     }
-    return emptyVoidFuture();
+    return Core::makeReadyTask();
 }
 
-QFuture<void> Core::OmemoHandler::resetAll() {
+QXmppTask<void> Core::OmemoHandler::resetAll() {
     ownDevice = std::nullopt;
     db.drop();
 
-    return emptyVoidFuture();
+    return Core::makeReadyTask();
 }
 
 QDataStream & operator >> (QDataStream& in, QXmppOmemoStorage::Device& device) {
diff --git a/core/handlers/omemohandler.h b/core/handlers/omemohandler.h
index cea9603..7783c04 100644
--- a/core/handlers/omemohandler.h
+++ b/core/handlers/omemohandler.h
@@ -34,27 +34,24 @@ public:
     OmemoHandler(Account* account);
     ~OmemoHandler() override;
 
-    QFuture<OmemoData> allData() override;
+    QXmppTask<OmemoData> allData() override;
 
-    QFuture<void> setOwnDevice(const std::optional<OwnDevice> &device) override;
+    QXmppTask<void> setOwnDevice(const std::optional<OwnDevice> &device) override;
 
-    QFuture<void> addSignedPreKeyPair(uint32_t keyId, const QXmppOmemoStorage::SignedPreKeyPair &keyPair) override;
-    QFuture<void> removeSignedPreKeyPair(uint32_t keyId) override;
+    QXmppTask<void> addSignedPreKeyPair(uint32_t keyId, const QXmppOmemoStorage::SignedPreKeyPair &keyPair) override;
+    QXmppTask<void> removeSignedPreKeyPair(uint32_t keyId) override;
 
-    QFuture<void> addPreKeyPairs(const QHash<uint32_t, QByteArray> &keyPairs) override;
-    QFuture<void> removePreKeyPair(uint32_t keyId) override;
+    QXmppTask<void> addPreKeyPairs(const QHash<uint32_t, QByteArray> &keyPairs) override;
+    QXmppTask<void> removePreKeyPair(uint32_t keyId) override;
 
-    QFuture<void> addDevice(const QString &jid, uint32_t deviceId, const Device &device) override;
-    QFuture<void> removeDevice(const QString &jid, uint32_t deviceId) override;
-    QFuture<void> removeDevices(const QString &jid) override;
+    QXmppTask<void> addDevice(const QString &jid, uint32_t deviceId, const Device &device) override;
+    QXmppTask<void> removeDevice(const QString &jid, uint32_t deviceId) override;
+    QXmppTask<void> removeDevices(const QString &jid) override;
 
-    QFuture<void> resetAll() override;
+    QXmppTask<void> resetAll() override;
 
     bool hasOwnDevice();
 
-private:
-    static QFuture<void> emptyVoidFuture();
-
 private:
     Account* acc;
     std::optional<OwnDevice> ownDevice;
diff --git a/core/handlers/trusthandler.cpp b/core/handlers/trusthandler.cpp
index caaaf0e..3ea6e47 100644
--- a/core/handlers/trusthandler.cpp
+++ b/core/handlers/trusthandler.cpp
@@ -16,6 +16,7 @@
 
 #include "trusthandler.h"
 #include "core/account.h"
+#include "core/adapterfunctions.h"
 
 using namespace Core;
 
@@ -75,37 +76,25 @@ Core::TrustHandler::KeyCache * Core::TrustHandler::createNewCache(const QString&
     return cache;
 }
 
-
-QFuture<void> Core::TrustHandler::emptyVoidFuture() {
-    QFutureInterface<QXmppOmemoStorage::OmemoData> result(QFutureInterfaceBase::Started);
-    result.reportFinished();
-    return result.future();
-}
-
-
-QFuture<void> Core::TrustHandler::resetAll(const QString& encryption) {
+QXmppTask<void> Core::TrustHandler::resetAll(const QString& encryption) {
     securityPolicies->removeRecord(encryption);
     ownKeys->removeRecord(encryption);
     getCache(encryption)->drop();
 
-    return emptyVoidFuture();
+    return Core::makeReadyTask();
 }
 
-QFuture<QXmpp::TrustLevel> Core::TrustHandler::trustLevel(
+QXmppTask<QXmpp::TrustLevel> Core::TrustHandler::trustLevel(
     const QString& encryption,
     const QString& keyOwnerJid,
     const QByteArray& keyId)
 {
     Keys map = getCache(encryption)->getRecord(keyOwnerJid);
     Shared::TrustLevel level = map.at(keyId);
-
-    QFutureInterface<QXmpp::TrustLevel> result(QFutureInterfaceBase::Started);
-    result.reportResult(convert(level));
-    result.reportFinished();
-    return result.future();
+    return Core::makeReadyTask(std::move(convert(level)));
 }
 
-QFuture<QHash<QString, QMultiHash<QString, QByteArray>>> Core::TrustHandler::setTrustLevel(
+QXmppTask<QHash<QString, QMultiHash<QString, QByteArray>>> Core::TrustHandler::setTrustLevel(
     const QString& encryption,
     const QList<QString>& keyOwnerJids,
     QXmpp::TrustLevel oldTrustLevel,
@@ -130,14 +119,10 @@ QFuture<QHash<QString, QMultiHash<QString, QByteArray>>> Core::TrustHandler::set
             cache->changeRecord(keyOwnerJid, map);
         }
     }
-
-    QFutureInterface<QHash<QString, QMultiHash<QString, QByteArray>>> result(QFutureInterfaceBase::Started);
-    result.reportResult(modifiedKeys);
-    result.reportFinished();
-    return result.future();
+    return Core::makeReadyTask(std::move(modifiedKeys));
 }
 
-QFuture<QHash<QString, QMultiHash<QString, QByteArray>>> Core::TrustHandler::setTrustLevel(
+QXmppTask<QHash<QString, QMultiHash<QString, QByteArray>>> Core::TrustHandler::setTrustLevel(
     const QString& encryption,
     const QMultiHash<QString, QByteArray>& keyIds,
     QXmpp::TrustLevel trustLevel)
@@ -160,14 +145,10 @@ QFuture<QHash<QString, QMultiHash<QString, QByteArray>>> Core::TrustHandler::set
             cache->changeRecord(keyOwnerJid, map);
         }
     }
-
-    QFutureInterface<QHash<QString, QMultiHash<QString, QByteArray>>> result(QFutureInterfaceBase::Started);
-    result.reportResult(modifiedKeys);
-    result.reportFinished();
-    return result.future();
+    return Core::makeReadyTask(std::move(modifiedKeys));
 }
 
-QFuture<bool> TrustHandler::hasKey(const QString& encryption,
+QXmppTask<bool> TrustHandler::hasKey(const QString& encryption,
                                    const QString& keyOwnerJid,
                                    QXmpp::TrustLevels trustLevels)
 {
@@ -182,14 +163,10 @@ QFuture<bool> TrustHandler::hasKey(const QString& encryption,
             }
         }
     } catch (const DataBase::NotFound& e) {}
-
-    QFutureInterface<bool> result(QFutureInterfaceBase::Started);
-    result.reportResult(found);
-    result.reportFinished();
-    return result.future();
+    return Core::makeReadyTask(std::move(found));
 }
 
-QFuture<QHash<QString, QHash<QByteArray, QXmpp::TrustLevel>>> TrustHandler::keys(
+QXmppTask<QHash<QString, QHash<QByteArray, QXmpp::TrustLevel>>> TrustHandler::keys(
     const QString& encryption,
     const QList<QString>& keyOwnerJids,
     QXmpp::TrustLevels trustLevels)
@@ -209,14 +186,10 @@ QFuture<QHash<QString, QHash<QByteArray, QXmpp::TrustLevel>>> TrustHandler::keys
             }
         } catch (const DataBase::NotFound& e) {}
     }
-
-    QFutureInterface<HSHBTL> result(QFutureInterfaceBase::Started);
-    result.reportResult(res);
-    result.reportFinished();
-    return result.future();
+    return Core::makeReadyTask(std::move(res));
 }
 
-QFuture<QHash<QXmpp::TrustLevel, QMultiHash<QString, QByteArray>>> TrustHandler::keys(
+QXmppTask<QHash<QXmpp::TrustLevel, QMultiHash<QString, QByteArray>>> TrustHandler::keys(
     const QString& encryption,
     QXmpp::TrustLevels trustLevels)
 {
@@ -231,24 +204,20 @@ QFuture<QHash<QXmpp::TrustLevel, QMultiHash<QString, QByteArray>>> TrustHandler:
             }
         }
     }
-
-    QFutureInterface<QHash<TL, MultySB>> result(QFutureInterfaceBase::Started);
-    result.reportResult(res);
-    result.reportFinished();
-    return result.future();
+    return Core::makeReadyTask(std::move(res));
 }
 
-QFuture<void> TrustHandler::removeKeys(const QString& encryption) {
+QXmppTask<void> TrustHandler::removeKeys(const QString& encryption) {
     getCache(encryption)->drop();
-    return emptyVoidFuture();
+    return Core::makeReadyTask();
 }
 
-QFuture<void> TrustHandler::removeKeys(const QString& encryption, const QString& keyOwnerJid) {
+QXmppTask<void> TrustHandler::removeKeys(const QString& encryption, const QString& keyOwnerJid) {
     getCache(encryption)->removeRecord(keyOwnerJid);
-    return emptyVoidFuture();
+    return Core::makeReadyTask();
 }
 
-QFuture<void> TrustHandler::removeKeys(const QString& encryption, const QList<QByteArray>& keyIds) {
+QXmppTask<void> TrustHandler::removeKeys(const QString& encryption, const QList<QByteArray>& keyIds) {
     std::set<QByteArray> set;
     for (const QByteArray& keyId : keyIds) {
         set.insert(keyId);
@@ -278,10 +247,10 @@ QFuture<void> TrustHandler::removeKeys(const QString& encryption, const QList<QB
         cache->replaceAll(data);
     }
 
-    return emptyVoidFuture();
+    return Core::makeReadyTask();
 }
 
-QFuture<void> TrustHandler::addKeys(
+QXmppTask<void> TrustHandler::addKeys(
     const QString& encryption,
     const QString& keyOwnerJid,
     const QList<QByteArray>& keyIds,
@@ -308,61 +277,53 @@ QFuture<void> TrustHandler::addKeys(
         cache->addRecord(keyOwnerJid, data);
     }
 
-    return emptyVoidFuture();
+    return Core::makeReadyTask();
 }
 
-QFuture<QByteArray> TrustHandler::ownKey(const QString& encryption) {
+QXmppTask<QByteArray> TrustHandler::ownKey(const QString& encryption) {
     QByteArray res;
     try {
         res = ownKeys->getRecord(encryption);
     } catch (const DataBase::NotFound& e) {}
-
-    QFutureInterface<QByteArray> result(QFutureInterfaceBase::Started);
-    result.reportResult(res);
-    result.reportFinished();
-    return result.future();
+    return Core::makeReadyTask(std::move(res));
 }
 
-QFuture<void> TrustHandler::resetOwnKey(const QString& encryption) {
+QXmppTask<void> TrustHandler::resetOwnKey(const QString& encryption) {
     try {
         ownKeys->removeRecord(encryption);
     } catch (const DataBase::NotFound& e) {}
 
-    return emptyVoidFuture();
+    return Core::makeReadyTask();
 }
 
-QFuture<void> TrustHandler::setOwnKey(const QString& encryption, const QByteArray& keyId) {
+QXmppTask<void> TrustHandler::setOwnKey(const QString& encryption, const QByteArray& keyId) {
     ownKeys->forceRecord(encryption, keyId);
-    return emptyVoidFuture();
+    return Core::makeReadyTask();
 }
 
-QFuture<QXmpp::TrustSecurityPolicy> TrustHandler::securityPolicy(const QString& encryption) {
+QXmppTask<QXmpp::TrustSecurityPolicy> TrustHandler::securityPolicy(const QString& encryption) {
     QXmpp::TrustSecurityPolicy res;
     try {
         res = static_cast<QXmpp::TrustSecurityPolicy>(securityPolicies->getRecord(encryption));
     } catch (const DataBase::NotFound& e) {}
-
-    QFutureInterface<QXmpp::TrustSecurityPolicy> result(QFutureInterfaceBase::Started);
-    result.reportResult(res);
-    result.reportFinished();
-    return result.future();
+    return Core::makeReadyTask(std::move(res));
 }
 
-QFuture<void> TrustHandler::resetSecurityPolicy(const QString& encryption) {
+QXmppTask<void> TrustHandler::resetSecurityPolicy(const QString& encryption) {
     try {
         securityPolicies->removeRecord(encryption);
     } catch (const DataBase::NotFound& e) {}
-    return emptyVoidFuture();
+    return Core::makeReadyTask();
 }
 
-QFuture<void> TrustHandler::setSecurityPolicy(
+QXmppTask<void> TrustHandler::setSecurityPolicy(
     const QString& encryption,
     QXmpp::TrustSecurityPolicy securityPolicy)
 {
     uint8_t pol = securityPolicy;
     securityPolicies->forceRecord(encryption, pol);
 
-    return emptyVoidFuture();
+    return Core::makeReadyTask();
 }
 
 Shared::TrustLevel Core::TrustHandler::convert(Core::TrustHandler::TL level)
diff --git a/core/handlers/trusthandler.h b/core/handlers/trusthandler.h
index b1fe0b4..e46c7b3 100644
--- a/core/handlers/trusthandler.h
+++ b/core/handlers/trusthandler.h
@@ -41,29 +41,28 @@ public:
     typedef std::map<QByteArray, Shared::TrustLevel> Keys;
     typedef DataBase::Cache<QString, Keys> KeyCache;
 
-    virtual QFuture<void> resetAll(CSR encryption);
-    virtual QFuture<TL> trustLevel(CSR encryption, CSR keyOwnerJid, const QByteArray& keyId);
-    virtual QFuture<HashSM> setTrustLevel(CSR encryption, CLSR keyOwnerJids, TL oldTrustLevel, TL newTrustLevel);
-    virtual QFuture<HashSM> setTrustLevel(CSR encryption, const MultySB& keyIds, TL trustLevel);
-    virtual QFuture<bool> hasKey(CSR encryption, CSR keyOwnerJid, QXmpp::TrustLevels trustLevels);
-    virtual QFuture<HSHBTL> keys(CSR encryption, CLSR keyOwnerJids, QXmpp::TrustLevels trustLevels);
-    virtual QFuture<QHash<TL, MultySB>> keys(CSR encryption, QXmpp::TrustLevels trustLevels);
-    virtual QFuture<void> removeKeys(CSR encryption);
-    virtual QFuture<void> removeKeys(CSR encryption, CSR keyOwnerJid);
-    virtual QFuture<void> removeKeys(CSR encryption, CLBAR keyIds);
-    virtual QFuture<void> addKeys(CSR encryption, CSR keyOwnerJid, CLBAR keyIds, TL trustLevel);
-    virtual QFuture<QByteArray> ownKey(CSR encryption);
-    virtual QFuture<void> resetOwnKey(CSR encryption);
-    virtual QFuture<void> setOwnKey(CSR encryption, const QByteArray& keyId);
-    virtual QFuture<QXmpp::TrustSecurityPolicy> securityPolicy(CSR encryption);
-    virtual QFuture<void> resetSecurityPolicy(CSR encryption);
-    virtual QFuture<void> setSecurityPolicy(CSR encryption, QXmpp::TrustSecurityPolicy securityPolicy);
+    virtual QXmppTask<void> resetAll(CSR encryption);
+    virtual QXmppTask<TL> trustLevel(CSR encryption, CSR keyOwnerJid, const QByteArray& keyId);
+    virtual QXmppTask<HashSM> setTrustLevel(CSR encryption, CLSR keyOwnerJids, TL oldTrustLevel, TL newTrustLevel);
+    virtual QXmppTask<HashSM> setTrustLevel(CSR encryption, const MultySB& keyIds, TL trustLevel);
+    virtual QXmppTask<bool> hasKey(CSR encryption, CSR keyOwnerJid, QXmpp::TrustLevels trustLevels);
+    virtual QXmppTask<HSHBTL> keys(CSR encryption, CLSR keyOwnerJids, QXmpp::TrustLevels trustLevels);
+    virtual QXmppTask<QHash<TL, MultySB>> keys(CSR encryption, QXmpp::TrustLevels trustLevels);
+    virtual QXmppTask<void> removeKeys(CSR encryption);
+    virtual QXmppTask<void> removeKeys(CSR encryption, CSR keyOwnerJid);
+    virtual QXmppTask<void> removeKeys(CSR encryption, CLBAR keyIds);
+    virtual QXmppTask<void> addKeys(CSR encryption, CSR keyOwnerJid, CLBAR keyIds, TL trustLevel);
+    virtual QXmppTask<QByteArray> ownKey(CSR encryption);
+    virtual QXmppTask<void> resetOwnKey(CSR encryption);
+    virtual QXmppTask<void> setOwnKey(CSR encryption, const QByteArray& keyId);
+    virtual QXmppTask<QXmpp::TrustSecurityPolicy> securityPolicy(CSR encryption);
+    virtual QXmppTask<void> resetSecurityPolicy(CSR encryption);
+    virtual QXmppTask<void> setSecurityPolicy(CSR encryption, QXmpp::TrustSecurityPolicy securityPolicy);
 
     static TL convert(Shared::TrustLevel level);
     static Shared::TrustLevel convert(TL level);
 
 private:
-    static QFuture<void> emptyVoidFuture();
     KeyCache* createNewCache(const QString& encryption);
     KeyCache* getCache(const QString& encryption);
 
diff --git a/external/qxmpp b/external/qxmpp
index befab2f..d2c2acd 160000
--- a/external/qxmpp
+++ b/external/qxmpp
@@ -1 +1 @@
-Subproject commit befab2fe2e71330170bba48f173258be724c65b9
+Subproject commit d2c2acd4848f815d0dc3d108f8bc306f9015fc89
diff --git a/ui/widgets/about.cpp b/ui/widgets/about.cpp
index 3782a94..f46c661 100644
--- a/ui/widgets/about.cpp
+++ b/ui/widgets/about.cpp
@@ -19,10 +19,14 @@
 #include <QXmppGlobal.h>
 #include <QDebug>
 
+#if (QXMPP_VERSION) < QT_VERSION_CHECK(1, 3, 0)
 static const std::string QXMPP_VERSION_PATCH(std::to_string(QXMPP_VERSION & 0xff));
 static const std::string QXMPP_VERSION_MINOR(std::to_string((QXMPP_VERSION & 0xff00) >> 8));
 static const std::string QXMPP_VERSION_MAJOR(std::to_string(QXMPP_VERSION >> 16));
 static const QString QXMPP_VERSION_STRING = QString::fromStdString(QXMPP_VERSION_MAJOR + "." + QXMPP_VERSION_MINOR + "." + QXMPP_VERSION_PATCH);
+#else
+static const QString QXMPP_VERSION_STRING = QXmppVersion();
+#endif
 
 About::About(QWidget* parent):
     QWidget(parent),

From bb304ce77453d7a789ead1671642d7192277f9ab Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Mon, 30 Jan 2023 20:52:26 +0300
Subject: [PATCH 227/281] just some unfinished thoughts

---
 main/main.cpp         |  2 ++
 shared/CMakeLists.txt |  2 ++
 shared/info.cpp       | 32 ++++++++++++++++++++++++++++++
 shared/info.h         | 46 +++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 82 insertions(+)
 create mode 100644 shared/info.cpp
 create mode 100644 shared/info.h

diff --git a/main/main.cpp b/main/main.cpp
index 3e9add3..9147ef0 100644
--- a/main/main.cpp
+++ b/main/main.cpp
@@ -20,6 +20,7 @@
 #include "shared/messageinfo.h"
 #include "shared/pathcheck.h"
 #include "shared/identity.h"
+#include "shared/info.h"
 #include "main/application.h"
 #include "core/signalcatcher.h"
 #include "core/squawk.h"
@@ -51,6 +52,7 @@ int main(int argc, char *argv[])
     qRegisterMetaType<Shared::ConnectionState>("Shared::ConnectionState");
     qRegisterMetaType<Shared::Availability>("Shared::Availability");
     qRegisterMetaType<Shared::KeyInfo>("Shared::KeyInfo");
+    qRegisterMetaType<Shared::Info>("Shared::Info");
 #ifdef WITH_OMEMO
     qRegisterMetaType<QXmppOmemoStorage::OwnDevice>("QXmppOmemoStorage::OwnDevice");
     qRegisterMetaTypeStreamOperators<QXmppOmemoStorage::OwnDevice>("QXmppOmemoStorage::OwnDevice");
diff --git a/shared/CMakeLists.txt b/shared/CMakeLists.txt
index a227163..9080fb6 100644
--- a/shared/CMakeLists.txt
+++ b/shared/CMakeLists.txt
@@ -28,4 +28,6 @@ target_sources(squawk PRIVATE
   field.cpp
   keyinfo.cpp
   keyinfo.h
+  info.cpp
+  info.h
   )
diff --git a/shared/info.cpp b/shared/info.cpp
new file mode 100644
index 0000000..bda2b9f
--- /dev/null
+++ b/shared/info.cpp
@@ -0,0 +1,32 @@
+// Squawk messenger. 
+// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+// 
+// 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 "info.h"
+
+Shared::Info::Info():
+    vcard(),
+    activeKeys(),
+    inactiveKeys()
+{}
+
+Shared::Info::Info(const Shared::Info& other):
+    vcard(other.vcard),
+    activeKeys(other.activeKeys),
+    inactiveKeys(other.inactiveKeys)
+{}
+
+Shared::Info::~Info()
+{}
diff --git a/shared/info.h b/shared/info.h
new file mode 100644
index 0000000..1692991
--- /dev/null
+++ b/shared/info.h
@@ -0,0 +1,46 @@
+// Squawk messenger. 
+// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+// 
+// 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/>.
+
+#ifndef SHARED_INFO_H
+#define SHARED_INFO_H
+
+#include "vcard.h"
+#include "keyinfo.h"
+
+#include <list>
+
+namespace Shared {
+
+/**
+ * This class should contain all nessesary data to display
+ * roster element info (contact, or out of roster contact, or MUC, or MIX in the future)
+ *
+ * under development yet
+ */
+class Info {
+public:
+    Info();
+    Info(const Info& other);
+    ~Info();
+
+    VCard vcard;
+    std::list<KeyInfo> activeKeys;
+    std::list<KeyInfo> inactiveKeys;
+};
+
+}
+
+#endif // SHARED_INFO_H

From 4af16b75bf864aaac391276f70dc45898be31a01 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Wed, 1 Feb 2023 18:56:00 +0300
Subject: [PATCH 228/281] started refactoring of the VCard UI

---
 shared/info.cpp                               |  12 +
 shared/info.h                                 |   3 +
 ui/models/CMakeLists.txt                      |   2 +
 ui/models/info/CMakeLists.txt                 |  10 +
 .../info/emails.cpp}                          |  63 +--
 .../emailsmodel.h => models/info/emails.h}    |  16 +-
 ui/models/info/omemo/CMakeLists.txt           |   4 +
 .../info/omemo/keys.cpp}                      |  31 +-
 .../keysmodel.h => models/info/omemo/keys.h}  |  14 +-
 .../info/phones.cpp}                          |  64 +--
 .../phonesmodel.h => models/info/phones.h}    |  20 +-
 ui/widgets/CMakeLists.txt                     |   1 +
 ui/widgets/info/CMakeLists.txt                |  27 ++
 ui/widgets/info/contactcontacts.cpp           |  31 ++
 ui/widgets/info/contactcontacts.h             |  41 ++
 ui/widgets/info/contactcontacts.ui            | 282 +++++++++++
 ui/widgets/info/contactgeneral.cpp            |  28 ++
 ui/widgets/info/contactgeneral.h              |  41 ++
 ui/widgets/info/contactgeneral.ui             | 448 ++++++++++++++++++
 ui/widgets/info/info.cpp                      |  30 ++
 ui/widgets/info/info.h                        |  45 ++
 ui/widgets/info/info.ui                       | 137 ++++++
 .../{vcard => info}/omemo/CMakeLists.txt      |   2 -
 .../{vcard => info}/omemo/keydelegate.cpp     |  20 +-
 .../{vcard => info}/omemo/keydelegate.h       |   0
 ui/widgets/{vcard => info}/omemo/omemo.cpp    |  10 +-
 ui/widgets/{vcard => info}/omemo/omemo.h      |   6 +-
 ui/widgets/{vcard => info}/omemo/omemo.ui     |   0
 ui/widgets/room.h                             |   2 +-
 ui/widgets/vcard/CMakeLists.txt               |   8 -
 ui/widgets/vcard/vcard.cpp                    |   8 +-
 ui/widgets/vcard/vcard.h                      |  10 +-
 32 files changed, 1250 insertions(+), 166 deletions(-)
 create mode 100644 ui/models/info/CMakeLists.txt
 rename ui/{widgets/vcard/emailsmodel.cpp => models/info/emails.cpp} (80%)
 rename ui/{widgets/vcard/emailsmodel.h => models/info/emails.h} (87%)
 create mode 100644 ui/models/info/omemo/CMakeLists.txt
 rename ui/{widgets/vcard/omemo/keysmodel.cpp => models/info/omemo/keys.cpp} (80%)
 rename ui/{widgets/vcard/omemo/keysmodel.h => models/info/omemo/keys.h} (89%)
 rename ui/{widgets/vcard/phonesmodel.cpp => models/info/phones.cpp} (83%)
 rename ui/{widgets/vcard/phonesmodel.h => models/info/phones.h} (86%)
 create mode 100644 ui/widgets/info/CMakeLists.txt
 create mode 100644 ui/widgets/info/contactcontacts.cpp
 create mode 100644 ui/widgets/info/contactcontacts.h
 create mode 100644 ui/widgets/info/contactcontacts.ui
 create mode 100644 ui/widgets/info/contactgeneral.cpp
 create mode 100644 ui/widgets/info/contactgeneral.h
 create mode 100644 ui/widgets/info/contactgeneral.ui
 create mode 100644 ui/widgets/info/info.cpp
 create mode 100644 ui/widgets/info/info.h
 create mode 100644 ui/widgets/info/info.ui
 rename ui/widgets/{vcard => info}/omemo/CMakeLists.txt (76%)
 rename ui/widgets/{vcard => info}/omemo/keydelegate.cpp (92%)
 rename ui/widgets/{vcard => info}/omemo/keydelegate.h (100%)
 rename ui/widgets/{vcard => info}/omemo/omemo.cpp (90%)
 rename ui/widgets/{vcard => info}/omemo/omemo.h (92%)
 rename ui/widgets/{vcard => info}/omemo/omemo.ui (100%)

diff --git a/shared/info.cpp b/shared/info.cpp
index bda2b9f..a5cf046 100644
--- a/shared/info.cpp
+++ b/shared/info.cpp
@@ -16,13 +16,25 @@
 
 #include "info.h"
 
+Shared::Info::Info(const QString& p_jid, bool p_editable):
+    jid(p_jid),
+    editable(p_editable),
+    vcard(),
+    activeKeys(),
+    inactiveKeys()
+{}
+
 Shared::Info::Info():
+    jid(),
+    editable(false),
     vcard(),
     activeKeys(),
     inactiveKeys()
 {}
 
 Shared::Info::Info(const Shared::Info& other):
+    jid(other.jid),
+    editable(other.editable),
     vcard(other.vcard),
     activeKeys(other.activeKeys),
     inactiveKeys(other.inactiveKeys)
diff --git a/shared/info.h b/shared/info.h
index 1692991..c3f16f8 100644
--- a/shared/info.h
+++ b/shared/info.h
@@ -33,9 +33,12 @@ namespace Shared {
 class Info {
 public:
     Info();
+    Info(const QString& jid, bool editable = false);
     Info(const Info& other);
     ~Info();
 
+    QString jid;
+    bool editable;
     VCard vcard;
     std::list<KeyInfo> activeKeys;
     std::list<KeyInfo> inactiveKeys;
diff --git a/ui/models/CMakeLists.txt b/ui/models/CMakeLists.txt
index 629db32..84aba66 100644
--- a/ui/models/CMakeLists.txt
+++ b/ui/models/CMakeLists.txt
@@ -24,3 +24,5 @@ target_sources(squawk PRIVATE
   roster.cpp
   roster.h
   )
+
+add_subdirectory(info)
diff --git a/ui/models/info/CMakeLists.txt b/ui/models/info/CMakeLists.txt
new file mode 100644
index 0000000..57a078c
--- /dev/null
+++ b/ui/models/info/CMakeLists.txt
@@ -0,0 +1,10 @@
+target_sources(squawk PRIVATE
+  emails.cpp
+  emails.h
+  phones.cpp
+  phones.h
+  )
+
+if (WITH_OMEMO)
+ add_subdirectory(omemo)
+endif()
diff --git a/ui/widgets/vcard/emailsmodel.cpp b/ui/models/info/emails.cpp
similarity index 80%
rename from ui/widgets/vcard/emailsmodel.cpp
rename to ui/models/info/emails.cpp
index 994fcc3..6c902bf 100644
--- a/ui/widgets/vcard/emailsmodel.cpp
+++ b/ui/models/info/emails.cpp
@@ -16,30 +16,29 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#include "emailsmodel.h"
+#include "emails.h"
 
 #include "shared/icons.h"
 #include <QCoreApplication>
 
-UI::VCard::EMailsModel::EMailsModel(bool p_edit, QObject* parent):
+Models::EMails::EMails(bool p_edit, QObject* parent):
     QAbstractTableModel(parent),
     edit(p_edit),
-    deque()
-{
-}
+    deque() {}
 
-int UI::VCard::EMailsModel::columnCount(const QModelIndex& parent) const
-{
-    return 3;
-}
+int Models::EMails::columnCount(const QModelIndex& parent) const {
+    return 3;}
 
-int UI::VCard::EMailsModel::rowCount(const QModelIndex& parent) const
-{
-    return deque.size();
-}
+int Models::EMails::rowCount(const QModelIndex& parent) const {
+    return deque.size();}
 
-QVariant UI::VCard::EMailsModel::data(const QModelIndex& index, int role) const
-{
+void Models::EMails::revertPreferred(quint32 row) {
+    setData(createIndex(row, 2), !isPreferred(row));}
+
+QString Models::EMails::getEmail(quint32 row) const {
+    return deque[row].address;}
+
+QVariant Models::EMails::data(const QModelIndex& index, int role) const {
     if (index.isValid()) {
         int col = index.column();
         switch (col) {
@@ -82,8 +81,7 @@ QVariant UI::VCard::EMailsModel::data(const QModelIndex& index, int role) const
     return QVariant();
 }
 
-Qt::ItemFlags UI::VCard::EMailsModel::flags(const QModelIndex& index) const
-{
+Qt::ItemFlags Models::EMails::flags(const QModelIndex& index) const {
     Qt::ItemFlags f = QAbstractTableModel::flags(index);
     if (edit && index.column() != 2) {
         f = Qt::ItemIsEditable | f;
@@ -91,8 +89,7 @@ Qt::ItemFlags UI::VCard::EMailsModel::flags(const QModelIndex& index) const
     return  f;
 }
 
-bool UI::VCard::EMailsModel::setData(const QModelIndex& index, const QVariant& value, int role)
-{
+bool Models::EMails::setData(const QModelIndex& index, const QVariant& value, int role) {
     if (role == Qt::EditRole && checkIndex(index)) {
         Shared::VCard::Email& item = deque[index.row()];
         switch (index.column()) {
@@ -124,8 +121,7 @@ bool UI::VCard::EMailsModel::setData(const QModelIndex& index, const QVariant& v
 }
 
 
-bool UI::VCard::EMailsModel::dropPrefered()
-{
+bool Models::EMails::dropPrefered() {
     bool dropped = false;
     int i = 0;
     for (Shared::VCard::Email& email : deque) {
@@ -140,16 +136,14 @@ bool UI::VCard::EMailsModel::dropPrefered()
     return dropped;
 }
 
-QModelIndex UI::VCard::EMailsModel::addNewEmptyLine()
-{
+QModelIndex Models::EMails::addNewEmptyLine() {
     beginInsertRows(QModelIndex(), deque.size(), deque.size());
     deque.emplace_back("");
     endInsertRows();
     return createIndex(deque.size() - 1, 0, &(deque.back()));
 }
 
-bool UI::VCard::EMailsModel::isPreferred(quint32 row) const
-{
+bool Models::EMails::isPreferred(quint32 row) const {
     if (row < deque.size()) {
         return deque[row].prefered;
     } else {
@@ -157,8 +151,7 @@ bool UI::VCard::EMailsModel::isPreferred(quint32 row) const
     }
 }
 
-void UI::VCard::EMailsModel::removeLines(quint32 index, quint32 count)
-{
+void Models::EMails::removeLines(quint32 index, quint32 count) {
     if (index < deque.size()) {
         quint32 maxCount = deque.size() - index;
         if (count > maxCount) {
@@ -175,15 +168,13 @@ void UI::VCard::EMailsModel::removeLines(quint32 index, quint32 count)
     }
 }
 
-void UI::VCard::EMailsModel::getEmails(std::deque<Shared::VCard::Email>& emails) const
-{
+void Models::EMails::getEmails(std::deque<Shared::VCard::Email>& emails) const {
     for (const Shared::VCard::Email& my : deque) {
         emails.emplace_back(my);
     }
 }
 
-void UI::VCard::EMailsModel::setEmails(const std::deque<Shared::VCard::Email>& emails)
-{
+void Models::EMails::setEmails(const std::deque<Shared::VCard::Email>& emails) {
     if (deque.size() > 0) {
         removeLines(0, deque.size());
     }
@@ -196,13 +187,3 @@ void UI::VCard::EMailsModel::setEmails(const std::deque<Shared::VCard::Email>& e
         endInsertRows();
     }
 }
-
-void UI::VCard::EMailsModel::revertPreferred(quint32 row)
-{
-    setData(createIndex(row, 2), !isPreferred(row));
-}
-
-QString UI::VCard::EMailsModel::getEmail(quint32 row) const
-{
-    return deque[row].address;
-}
diff --git a/ui/widgets/vcard/emailsmodel.h b/ui/models/info/emails.h
similarity index 87%
rename from ui/widgets/vcard/emailsmodel.h
rename to ui/models/info/emails.h
index bafbe9f..bb05a5c 100644
--- a/ui/widgets/vcard/emailsmodel.h
+++ b/ui/models/info/emails.h
@@ -16,8 +16,8 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#ifndef UI_VCARD_EMAILSMODEL_H
-#define UI_VCARD_EMAILSMODEL_H
+#ifndef MODELS_EMAILS_H
+#define MODELS_EMAILS_H
 
 #include <QAbstractTableModel>
 #include <QIcon>
@@ -26,14 +26,12 @@
 
 #include "shared/vcard.h"
 
-namespace UI {
-namespace VCard {
+namespace Models {
 
-class EMailsModel : public QAbstractTableModel
-{
+class EMails : public QAbstractTableModel {
     Q_OBJECT
 public:
-    EMailsModel(bool edit = false, QObject *parent = nullptr);
+    EMails(bool edit = false, QObject *parent = nullptr);
     
     int rowCount(const QModelIndex& parent = QModelIndex()) const override;
     int columnCount(const QModelIndex& parent = QModelIndex()) const override;
@@ -59,6 +57,6 @@ private:
     bool dropPrefered();
 };
 
-}}
+}
 
-#endif // UI_VCARD_EMAILSMODEL_H
+#endif // MODELS_EMAILS_H
diff --git a/ui/models/info/omemo/CMakeLists.txt b/ui/models/info/omemo/CMakeLists.txt
new file mode 100644
index 0000000..166ca76
--- /dev/null
+++ b/ui/models/info/omemo/CMakeLists.txt
@@ -0,0 +1,4 @@
+target_sources(squawk PRIVATE
+    keys.cpp
+    keys.h
+)
diff --git a/ui/widgets/vcard/omemo/keysmodel.cpp b/ui/models/info/omemo/keys.cpp
similarity index 80%
rename from ui/widgets/vcard/omemo/keysmodel.cpp
rename to ui/models/info/omemo/keys.cpp
index 1488370..fa753c0 100644
--- a/ui/widgets/vcard/omemo/keysmodel.cpp
+++ b/ui/models/info/omemo/keys.cpp
@@ -14,23 +14,20 @@
 // 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 "keysmodel.h"
+#include "keys.h"
 
-const QHash<int, QByteArray> UI::KeysModel::roles = {
+const QHash<int, QByteArray> Models::Keys::roles = {
     {Label, "label"},
     {FingerPrint, "fingerPrint"},
     {TrustLevel, "trustLevel"}
 };
 
-UI::KeysModel::KeysModel(QObject* parent):
+Models::Keys::Keys(QObject* parent):
     QAbstractListModel(parent),
     keys(),
-    modified()
-{
+    modified() {}
 
-}
-
-UI::KeysModel::~KeysModel() {
+Models::Keys::~Keys() {
     for (Shared::KeyInfo* key : keys) {
         delete key;
     }
@@ -40,7 +37,7 @@ UI::KeysModel::~KeysModel() {
     }
 }
 
-std::deque<Shared::KeyInfo> UI::KeysModel::modifiedKeys() const {
+std::deque<Shared::KeyInfo> Models::Keys::modifiedKeys() const {
     std::deque<Shared::KeyInfo> response(modified.size());
 
     int i = 0;
@@ -52,13 +49,13 @@ std::deque<Shared::KeyInfo> UI::KeysModel::modifiedKeys() const {
     return response;
 }
 
-void UI::KeysModel::addKey(const Shared::KeyInfo& info) {
+void Models::Keys::addKey(const Shared::KeyInfo& info) {
     beginInsertRows(QModelIndex(), keys.size(), keys.size());
     keys.push_back(new Shared::KeyInfo(info));
     endInsertRows();
 }
 
-QVariant UI::KeysModel::data(const QModelIndex& index, int role) const {
+QVariant Models::Keys::data(const QModelIndex& index, int role) const {
     int i = index.row();
     const Shared::KeyInfo* info;
     bool dirty;
@@ -94,13 +91,13 @@ QVariant UI::KeysModel::data(const QModelIndex& index, int role) const {
     return answer;
 }
 
-int UI::KeysModel::rowCount(const QModelIndex& parent) const {
+int Models::Keys::rowCount(const QModelIndex& parent) const {
     return keys.size();
 }
 
-QHash<int, QByteArray> UI::KeysModel::roleNames() const {return roles;}
+QHash<int, QByteArray> Models::Keys::roleNames() const {return roles;}
 
-QModelIndex UI::KeysModel::index(int row, int column, const QModelIndex& parent) const {
+QModelIndex Models::Keys::index(int row, int column, const QModelIndex& parent) const {
     if (!hasIndex(row, column, parent)) {
         return QModelIndex();
     }
@@ -108,7 +105,7 @@ QModelIndex UI::KeysModel::index(int row, int column, const QModelIndex& parent)
     return createIndex(row, column, keys[row]);
 }
 
-void UI::KeysModel::revertKey(int row) {
+void Models::Keys::revertKey(int row) {
     std::map<int, Shared::KeyInfo*>::const_iterator itr = modified.find(row);
     if (itr != modified.end()) {
         modified.erase(itr);
@@ -117,7 +114,7 @@ void UI::KeysModel::revertKey(int row) {
     }
 }
 
-void UI::KeysModel::setTrustLevel(int row, Shared::TrustLevel level) {
+void Models::Keys::setTrustLevel(int row, Shared::TrustLevel level) {
     std::map<int, Shared::KeyInfo*>::const_iterator itr = modified.find(row);
     Shared::KeyInfo* info;
     if (itr == modified.end()) {
@@ -134,7 +131,7 @@ void UI::KeysModel::setTrustLevel(int row, Shared::TrustLevel level) {
     info->trustLevel = level;
 
     QModelIndex index = createIndex(row, 0, info);
-    dataChanged(index, index, {KeysModel::Dirty});
+    dataChanged(index, index, {Keys::Dirty});
 }
 
 
diff --git a/ui/widgets/vcard/omemo/keysmodel.h b/ui/models/info/omemo/keys.h
similarity index 89%
rename from ui/widgets/vcard/omemo/keysmodel.h
rename to ui/models/info/omemo/keys.h
index e1a7606..2710bab 100644
--- a/ui/widgets/vcard/omemo/keysmodel.h
+++ b/ui/models/info/omemo/keys.h
@@ -14,23 +14,23 @@
 // You should have received a copy of the GNU General Public License
 // along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
-#ifndef UI_KEYSMODEL_H
-#define UI_KEYSMODEL_H
+#ifndef MODELS_KEYS_H
+#define MODELS_KEYS_H
 
 #include <QAbstractListModel>
 
 #include <shared/keyinfo.h>
 
-namespace UI {
+namespace Models {
 
 /**
  * @todo write docs
  */
-class KeysModel : public QAbstractListModel
+class Keys : public QAbstractListModel
 {
 public:
-    KeysModel(QObject *parent = nullptr);
-    ~KeysModel();
+    Keys(QObject *parent = nullptr);
+    ~Keys();
 
     void addKey(const Shared::KeyInfo& info);
 
@@ -64,4 +64,4 @@ private:
 
 }
 
-#endif // UI_KEYSMODEL_H
+#endif // MODELS_KEYS_H
diff --git a/ui/widgets/vcard/phonesmodel.cpp b/ui/models/info/phones.cpp
similarity index 83%
rename from ui/widgets/vcard/phonesmodel.cpp
rename to ui/models/info/phones.cpp
index d3cace8..7c3b04f 100644
--- a/ui/widgets/vcard/phonesmodel.cpp
+++ b/ui/models/info/phones.cpp
@@ -16,30 +16,30 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#include "phonesmodel.h"
+#include "phones.h"
 
 #include "shared/icons.h"
 #include <QCoreApplication>
 
-UI::VCard::PhonesModel::PhonesModel(bool p_edit, QObject* parent):
+Models::Phones::Phones(bool p_edit, QObject* parent):
     QAbstractTableModel(parent),
     edit(p_edit),
-    deque()
-{
+    deque() {}
+
+int Models::Phones::columnCount(const QModelIndex& parent) const {
+    return 4;}
+
+int Models::Phones::rowCount(const QModelIndex& parent) const {
+    return deque.size();}
+
+void Models::Phones::revertPreferred(quint32 row) {
+    setData(createIndex(row, 3), !isPreferred(row));
 }
 
-int UI::VCard::PhonesModel::columnCount(const QModelIndex& parent) const
-{
-    return 4;
-}
+QString Models::Phones::getPhone(quint32 row) const {
+    return deque[row].number;}
 
-int UI::VCard::PhonesModel::rowCount(const QModelIndex& parent) const
-{
-    return deque.size();
-}
-
-QVariant UI::VCard::PhonesModel::data(const QModelIndex& index, int role) const
-{
+QVariant Models::Phones::data(const QModelIndex& index, int role) const {
     if (index.isValid()) {
         int col = index.column();
         switch (col) {
@@ -92,16 +92,14 @@ QVariant UI::VCard::PhonesModel::data(const QModelIndex& index, int role) const
     return QVariant();
 }
 
-QModelIndex UI::VCard::PhonesModel::addNewEmptyLine()
-{
+QModelIndex Models::Phones::addNewEmptyLine() {
     beginInsertRows(QModelIndex(), deque.size(), deque.size());
     deque.emplace_back("", Shared::VCard::Phone::other);
     endInsertRows();
     return createIndex(deque.size() - 1, 0, &(deque.back()));
 }
 
-Qt::ItemFlags UI::VCard::PhonesModel::flags(const QModelIndex& index) const
-{
+Qt::ItemFlags Models::Phones::flags(const QModelIndex& index) const {
     Qt::ItemFlags f = QAbstractTableModel::flags(index);
     if (edit && index.column() != 3) {
         f = Qt::ItemIsEditable | f;
@@ -109,8 +107,7 @@ Qt::ItemFlags UI::VCard::PhonesModel::flags(const QModelIndex& index) const
     return  f;
 }
 
-bool UI::VCard::PhonesModel::dropPrefered()
-{
+bool Models::Phones::dropPrefered() {
     bool dropped = false;
     int i = 0;
     for (Shared::VCard::Phone& phone : deque) {
@@ -125,15 +122,13 @@ bool UI::VCard::PhonesModel::dropPrefered()
     return dropped;
 }
 
-void UI::VCard::PhonesModel::getPhones(std::deque<Shared::VCard::Phone>& phones) const
-{
+void Models::Phones::getPhones(std::deque<Shared::VCard::Phone>& phones) const {
     for (const Shared::VCard::Phone& my : deque) {
         phones.emplace_back(my);
     }
 }
 
-bool UI::VCard::PhonesModel::isPreferred(quint32 row) const
-{
+bool Models::Phones::isPreferred(quint32 row) const {
     if (row < deque.size()) {
         return deque[row].prefered;
     } else {
@@ -141,8 +136,7 @@ bool UI::VCard::PhonesModel::isPreferred(quint32 row) const
     }
 }
 
-void UI::VCard::PhonesModel::removeLines(quint32 index, quint32 count)
-{
+void Models::Phones::removeLines(quint32 index, quint32 count) {
     if (index < deque.size()) {
         quint32 maxCount = deque.size() - index;
         if (count > maxCount) {
@@ -159,13 +153,7 @@ void UI::VCard::PhonesModel::removeLines(quint32 index, quint32 count)
     }
 }
 
-void UI::VCard::PhonesModel::revertPreferred(quint32 row)
-{
-    setData(createIndex(row, 3), !isPreferred(row));
-}
-
-bool UI::VCard::PhonesModel::setData(const QModelIndex& index, const QVariant& value, int role)
-{
+bool Models::Phones::setData(const QModelIndex& index, const QVariant& value, int role) {
     if (role == Qt::EditRole && checkIndex(index)) {
         Shared::VCard::Phone& item = deque[index.row()];
         switch (index.column()) {
@@ -204,8 +192,7 @@ bool UI::VCard::PhonesModel::setData(const QModelIndex& index, const QVariant& v
     return false;
 }
 
-void UI::VCard::PhonesModel::setPhones(const std::deque<Shared::VCard::Phone>& phones)
-{
+void Models::Phones::setPhones(const std::deque<Shared::VCard::Phone>& phones) {
     if (deque.size() > 0) {
         removeLines(0, deque.size());
     }
@@ -218,8 +205,3 @@ void UI::VCard::PhonesModel::setPhones(const std::deque<Shared::VCard::Phone>& p
         endInsertRows();
     }
 }
-
-QString UI::VCard::PhonesModel::getPhone(quint32 row) const
-{
-    return deque[row].number;
-}
diff --git a/ui/widgets/vcard/phonesmodel.h b/ui/models/info/phones.h
similarity index 86%
rename from ui/widgets/vcard/phonesmodel.h
rename to ui/models/info/phones.h
index 32d08b6..dec27d9 100644
--- a/ui/widgets/vcard/phonesmodel.h
+++ b/ui/models/info/phones.h
@@ -16,25 +16,19 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#ifndef UI_VCARD_PHONESMODEL_H
-#define UI_VCARD_PHONESMODEL_H
+#ifndef MODELS_PHONES_H
+#define MODELS_PHONES_H
 
 #include <QAbstractTableModel>
 #include <QIcon>
 
 #include "shared/vcard.h"
 
-namespace UI {
-namespace VCard {
-
-/**
- * @todo write docs
- */
-class PhonesModel : public QAbstractTableModel
-{
+namespace Models {
+class Phones : public QAbstractTableModel {
     Q_OBJECT
 public:
-    PhonesModel(bool edit = false, QObject *parent = nullptr);
+    Phones(bool edit = false, QObject *parent = nullptr);
     
     QVariant data(const QModelIndex& index, int role) const override;
     int columnCount(const QModelIndex& parent) const override;
@@ -60,6 +54,6 @@ private:
     bool dropPrefered();
 };
 
-}}
+}
 
-#endif // UI_VCARD_PHONESMODEL_H
+#endif // MODELS_PHONES_H
diff --git a/ui/widgets/CMakeLists.txt b/ui/widgets/CMakeLists.txt
index 21d9504..be77db2 100644
--- a/ui/widgets/CMakeLists.txt
+++ b/ui/widgets/CMakeLists.txt
@@ -18,6 +18,7 @@ target_sources(squawk PRIVATE
   )
 
 add_subdirectory(vcard)
+add_subdirectory(info)
 add_subdirectory(messageline)
 add_subdirectory(settings)
 add_subdirectory(accounts)
diff --git a/ui/widgets/info/CMakeLists.txt b/ui/widgets/info/CMakeLists.txt
new file mode 100644
index 0000000..0029e84
--- /dev/null
+++ b/ui/widgets/info/CMakeLists.txt
@@ -0,0 +1,27 @@
+set(SOURCE_FILES
+  info.cpp
+  contactgeneral.cpp
+  contactcontacts.cpp
+)
+
+set(UI_FILES
+  info.ui
+  contactgeneral.ui
+  contactcontacts.ui
+)
+
+set(HEADER_FILES
+  info.h
+  contactgeneral.h
+  contactcontacts.h
+)
+
+target_sources(squawk PRIVATE
+  ${SOURCE_FILES}
+  ${UI_FILES}
+  ${HEADER_FILES}
+)
+
+if (WITH_OMEMO)
+ add_subdirectory(omemo)
+endif()
diff --git a/ui/widgets/info/contactcontacts.cpp b/ui/widgets/info/contactcontacts.cpp
new file mode 100644
index 0000000..620d235
--- /dev/null
+++ b/ui/widgets/info/contactcontacts.cpp
@@ -0,0 +1,31 @@
+// Squawk messenger. 
+// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+// 
+// 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 "contactcontacts.h"
+#include "ui_contactcontacts.h"
+
+UI::ContactContacts::ContactContacts(const QString& jid, QWidget* parent):
+    QWidget(parent),
+    m_ui(new Ui::ContactContacts)
+{
+    m_ui->setupUi(this);
+
+    m_ui->jabberID->setText(jid);
+    m_ui->jabberID->setReadOnly(true);
+}
+
+UI::ContactContacts::~ContactContacts() {
+}
diff --git a/ui/widgets/info/contactcontacts.h b/ui/widgets/info/contactcontacts.h
new file mode 100644
index 0000000..7b26eed
--- /dev/null
+++ b/ui/widgets/info/contactcontacts.h
@@ -0,0 +1,41 @@
+// Squawk messenger. 
+// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+// 
+// 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/>.
+
+#ifndef UI_WIDGETS_CONTACTCONTACTS_H
+#define UI_WIDGETS_CONTACTCONTACTS_H
+
+#include <QWidget>
+#include <QScopedPointer>
+
+namespace UI {
+namespace Ui
+{
+class ContactContacts;
+}
+
+class ContactContacts : public QWidget {
+    Q_OBJECT
+public:
+    ContactContacts(const QString& jid, QWidget* parent = nullptr);
+    ~ContactContacts();
+
+private:
+    QScopedPointer<Ui::ContactContacts> m_ui;
+};
+
+}
+
+#endif // UI_WIDGETS_CONTACTCONTACTS_H
diff --git a/ui/widgets/info/contactcontacts.ui b/ui/widgets/info/contactcontacts.ui
new file mode 100644
index 0000000..8104d50
--- /dev/null
+++ b/ui/widgets/info/contactcontacts.ui
@@ -0,0 +1,282 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>UI::ContactContacts</class>
+       <widget class="QWidget" name="Contacts">
+        <attribute name="title">
+         <string>Contacts</string>
+        </attribute>
+        <layout class="QVBoxLayout" name="verticalLayout_7">
+         <property name="spacing">
+          <number>0</number>
+         </property>
+         <property name="leftMargin">
+          <number>0</number>
+         </property>
+         <property name="topMargin">
+          <number>6</number>
+         </property>
+         <property name="rightMargin">
+          <number>0</number>
+         </property>
+         <property name="bottomMargin">
+          <number>0</number>
+         </property>
+         <item>
+          <widget class="QLabel" name="contactHeading">
+           <property name="styleSheet">
+            <string notr="true">font: 600 24pt ;</string>
+           </property>
+           <property name="text">
+            <string>Contact</string>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <widget class="QScrollArea" name="scrollArea">
+           <property name="frameShape">
+            <enum>QFrame::NoFrame</enum>
+           </property>
+           <property name="frameShadow">
+            <enum>QFrame::Plain</enum>
+           </property>
+           <property name="lineWidth">
+            <number>0</number>
+           </property>
+           <property name="widgetResizable">
+            <bool>true</bool>
+           </property>
+           <widget class="QWidget" name="contactScrollArea">
+            <property name="geometry">
+             <rect>
+              <x>0</x>
+              <y>0</y>
+              <width>545</width>
+              <height>544</height>
+             </rect>
+            </property>
+            <layout class="QGridLayout" name="gridLayout_3" columnstretch="1,3,1">
+             <item row="7" column="1">
+              <widget class="Line" name="phonesLine">
+               <property name="orientation">
+                <enum>Qt::Horizontal</enum>
+               </property>
+              </widget>
+             </item>
+             <item row="3" column="1">
+              <widget class="QTableView" name="emailsView">
+               <property name="selectionMode">
+                <enum>QAbstractItemView::ExtendedSelection</enum>
+               </property>
+               <property name="selectionBehavior">
+                <enum>QAbstractItemView::SelectRows</enum>
+               </property>
+               <property name="showGrid">
+                <bool>false</bool>
+               </property>
+               <attribute name="horizontalHeaderVisible">
+                <bool>false</bool>
+               </attribute>
+               <attribute name="verticalHeaderVisible">
+                <bool>false</bool>
+               </attribute>
+              </widget>
+             </item>
+             <item row="10" column="1">
+              <widget class="Line" name="addressesLine">
+               <property name="orientation">
+                <enum>Qt::Horizontal</enum>
+               </property>
+              </widget>
+             </item>
+             <item row="0" column="0" rowspan="12">
+              <spacer name="contactLeftSpacer">
+               <property name="orientation">
+                <enum>Qt::Horizontal</enum>
+               </property>
+               <property name="sizeHint" stdset="0">
+                <size>
+                 <width>40</width>
+                 <height>20</height>
+                </size>
+               </property>
+              </spacer>
+             </item>
+             <item row="1" column="1">
+              <widget class="Line" name="contactFormLine">
+               <property name="orientation">
+                <enum>Qt::Horizontal</enum>
+               </property>
+              </widget>
+             </item>
+             <item row="4" column="1">
+              <widget class="Line" name="emailsLine">
+               <property name="orientation">
+                <enum>Qt::Horizontal</enum>
+               </property>
+              </widget>
+             </item>
+             <item row="11" column="1">
+              <spacer name="contactBottomSpacer">
+               <property name="orientation">
+                <enum>Qt::Vertical</enum>
+               </property>
+               <property name="sizeHint" stdset="0">
+                <size>
+                 <width>20</width>
+                 <height>40</height>
+                </size>
+               </property>
+              </spacer>
+             </item>
+             <item row="8" column="1">
+              <widget class="QLabel" name="addressesHeading">
+               <property name="styleSheet">
+                <string notr="true">font: 600 16pt;</string>
+               </property>
+               <property name="text">
+                <string>Addresses</string>
+               </property>
+               <property name="alignment">
+                <set>Qt::AlignCenter</set>
+               </property>
+              </widget>
+             </item>
+             <item row="6" column="1">
+              <widget class="QTableView" name="phonesView">
+               <property name="selectionBehavior">
+                <enum>QAbstractItemView::SelectRows</enum>
+               </property>
+               <property name="showGrid">
+                <bool>false</bool>
+               </property>
+               <attribute name="horizontalHeaderVisible">
+                <bool>false</bool>
+               </attribute>
+               <attribute name="verticalHeaderVisible">
+                <bool>false</bool>
+               </attribute>
+              </widget>
+             </item>
+             <item row="2" column="1">
+              <widget class="QLabel" name="emailsHeading">
+               <property name="styleSheet">
+                <string notr="true">font: 600 16pt;</string>
+               </property>
+               <property name="text">
+                <string>E-Mail addresses</string>
+               </property>
+               <property name="alignment">
+                <set>Qt::AlignCenter</set>
+               </property>
+              </widget>
+             </item>
+             <item row="0" column="1">
+              <layout class="QFormLayout" name="contactForm">
+               <property name="formAlignment">
+                <set>Qt::AlignHCenter|Qt::AlignTop</set>
+               </property>
+               <item row="0" column="1">
+                <widget class="QLineEdit" name="jabberID">
+                 <property name="minimumSize">
+                  <size>
+                   <width>150</width>
+                   <height>0</height>
+                  </size>
+                 </property>
+                 <property name="maximumSize">
+                  <size>
+                   <width>300</width>
+                   <height>16777215</height>
+                  </size>
+                 </property>
+                </widget>
+               </item>
+               <item row="0" column="0">
+                <widget class="QLabel" name="jabberIDLabel">
+                 <property name="text">
+                  <string>Jabber ID</string>
+                 </property>
+                 <property name="buddy">
+                  <cstring>jabberID</cstring>
+                 </property>
+                </widget>
+               </item>
+               <item row="1" column="1">
+                <widget class="QLineEdit" name="url">
+                 <property name="minimumSize">
+                  <size>
+                   <width>150</width>
+                   <height>0</height>
+                  </size>
+                 </property>
+                 <property name="maximumSize">
+                  <size>
+                   <width>300</width>
+                   <height>16777215</height>
+                  </size>
+                 </property>
+                </widget>
+               </item>
+               <item row="1" column="0">
+                <widget class="QLabel" name="urlLabel">
+                 <property name="text">
+                  <string>Web site</string>
+                 </property>
+                 <property name="buddy">
+                  <cstring>url</cstring>
+                 </property>
+                </widget>
+               </item>
+              </layout>
+             </item>
+             <item row="0" column="2" rowspan="12">
+              <spacer name="contactRightSpacer">
+               <property name="orientation">
+                <enum>Qt::Horizontal</enum>
+               </property>
+               <property name="sizeHint" stdset="0">
+                <size>
+                 <width>40</width>
+                 <height>20</height>
+                </size>
+               </property>
+              </spacer>
+             </item>
+             <item row="5" column="1">
+              <widget class="QLabel" name="phenesHeading">
+               <property name="styleSheet">
+                <string notr="true">font: 600 16pt;</string>
+               </property>
+               <property name="text">
+                <string>Phone numbers</string>
+               </property>
+               <property name="alignment">
+                <set>Qt::AlignCenter</set>
+               </property>
+              </widget>
+             </item>
+             <item row="9" column="1">
+              <widget class="QTableView" name="addressesView">
+               <property name="selectionBehavior">
+                <enum>QAbstractItemView::SelectRows</enum>
+               </property>
+               <property name="showGrid">
+                <bool>false</bool>
+               </property>
+               <attribute name="horizontalHeaderVisible">
+                <bool>false</bool>
+               </attribute>
+               <attribute name="verticalHeaderVisible">
+                <bool>false</bool>
+               </attribute>
+              </widget>
+             </item>
+            </layout>
+           </widget>
+          </widget>
+         </item>
+        </layout>
+       </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/ui/widgets/info/contactgeneral.cpp b/ui/widgets/info/contactgeneral.cpp
new file mode 100644
index 0000000..62efe81
--- /dev/null
+++ b/ui/widgets/info/contactgeneral.cpp
@@ -0,0 +1,28 @@
+// Squawk messenger. 
+// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+// 
+// 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 "contactgeneral.h"
+#include "ui_contactgeneral.h"
+
+UI::ContactGeneral::ContactGeneral(QWidget* parent):
+    QWidget(parent),
+    m_ui(new Ui::ContactGeneral)
+{
+    m_ui->setupUi(this);
+}
+
+UI::ContactGeneral::~ContactGeneral()
+{}
diff --git a/ui/widgets/info/contactgeneral.h b/ui/widgets/info/contactgeneral.h
new file mode 100644
index 0000000..5e116c6
--- /dev/null
+++ b/ui/widgets/info/contactgeneral.h
@@ -0,0 +1,41 @@
+// Squawk messenger. 
+// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+// 
+// 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/>.
+
+#ifndef UI_WIDGETS_CONTACTGENERAL_H
+#define UI_WIDGETS_CONTACTGENERAL_H
+
+#include <QWidget>
+#include <QScopedPointer>
+
+namespace UI {
+namespace Ui
+{
+class ContactGeneral;
+}
+
+class ContactGeneral : public QWidget{
+    Q_OBJECT
+public:
+    ContactGeneral(QWidget* parent = nullptr);
+    ~ContactGeneral();
+
+private:
+    QScopedPointer<Ui::ContactGeneral> m_ui;
+};
+
+}
+
+#endif // UI_WIDGETS_CONTACTGENERAL_H
diff --git a/ui/widgets/info/contactgeneral.ui b/ui/widgets/info/contactgeneral.ui
new file mode 100644
index 0000000..bb2b9e7
--- /dev/null
+++ b/ui/widgets/info/contactgeneral.ui
@@ -0,0 +1,448 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>UI::ContactGeneral</class>
+ <widget class="QWidget" name="General">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>340</width>
+    <height>625</height>
+   </rect>
+  </property>
+  <layout class="QGridLayout" name="gridLayout_2" columnstretch="1,2,2,1">
+   <property name="leftMargin">
+    <number>0</number>
+   </property>
+   <property name="topMargin">
+    <number>6</number>
+   </property>
+   <property name="rightMargin">
+    <number>0</number>
+   </property>
+   <property name="bottomMargin">
+    <number>0</number>
+   </property>
+   <property name="horizontalSpacing">
+    <number>6</number>
+   </property>
+   <item row="6" column="1" colspan="2">
+    <widget class="QLabel" name="organizationHeading">
+     <property name="sizePolicy">
+      <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+       <horstretch>0</horstretch>
+       <verstretch>0</verstretch>
+      </sizepolicy>
+     </property>
+     <property name="styleSheet">
+      <string notr="true">font: 600 16pt;</string>
+     </property>
+     <property name="text">
+      <string>Organization</string>
+     </property>
+     <property name="alignment">
+      <set>Qt::AlignCenter</set>
+     </property>
+    </widget>
+   </item>
+   <item row="3" column="1">
+    <layout class="QFormLayout" name="personalForm">
+     <property name="sizeConstraint">
+      <enum>QLayout::SetDefaultConstraint</enum>
+     </property>
+     <property name="formAlignment">
+      <set>Qt::AlignHCenter|Qt::AlignTop</set>
+     </property>
+     <item row="1" column="1">
+      <widget class="QLineEdit" name="middleName">
+       <property name="minimumSize">
+        <size>
+         <width>200</width>
+         <height>0</height>
+        </size>
+       </property>
+       <property name="maximumSize">
+        <size>
+         <width>350</width>
+         <height>16777215</height>
+        </size>
+       </property>
+      </widget>
+     </item>
+     <item row="0" column="1">
+      <widget class="QLineEdit" name="firstName">
+       <property name="minimumSize">
+        <size>
+         <width>200</width>
+         <height>0</height>
+        </size>
+       </property>
+       <property name="maximumSize">
+        <size>
+         <width>350</width>
+         <height>16777215</height>
+        </size>
+       </property>
+      </widget>
+     </item>
+     <item row="1" column="0">
+      <widget class="QLabel" name="middleNameLabel">
+       <property name="text">
+        <string>Middle name</string>
+       </property>
+       <property name="buddy">
+        <cstring>middleName</cstring>
+       </property>
+      </widget>
+     </item>
+     <item row="0" column="0">
+      <widget class="QLabel" name="firstNameLabel">
+       <property name="text">
+        <string>First name</string>
+       </property>
+       <property name="buddy">
+        <cstring>firstName</cstring>
+       </property>
+      </widget>
+     </item>
+     <item row="2" column="0">
+      <widget class="QLabel" name="lastNameLabel">
+       <property name="text">
+        <string>Last name</string>
+       </property>
+       <property name="buddy">
+        <cstring>lastName</cstring>
+       </property>
+      </widget>
+     </item>
+     <item row="2" column="1">
+      <widget class="QLineEdit" name="lastName">
+       <property name="minimumSize">
+        <size>
+         <width>200</width>
+         <height>0</height>
+        </size>
+       </property>
+       <property name="maximumSize">
+        <size>
+         <width>350</width>
+         <height>16777215</height>
+        </size>
+       </property>
+      </widget>
+     </item>
+     <item row="3" column="0">
+      <widget class="QLabel" name="nickNameLabel">
+       <property name="text">
+        <string>Nick name</string>
+       </property>
+       <property name="buddy">
+        <cstring>nickName</cstring>
+       </property>
+      </widget>
+     </item>
+     <item row="3" column="1">
+      <widget class="QLineEdit" name="nickName">
+       <property name="minimumSize">
+        <size>
+         <width>200</width>
+         <height>0</height>
+        </size>
+       </property>
+       <property name="maximumSize">
+        <size>
+         <width>350</width>
+         <height>16777215</height>
+        </size>
+       </property>
+      </widget>
+     </item>
+     <item row="4" column="0">
+      <widget class="QLabel" name="birthdayLabel">
+       <property name="text">
+        <string>Birthday</string>
+       </property>
+       <property name="buddy">
+        <cstring>birthday</cstring>
+       </property>
+      </widget>
+     </item>
+     <item row="4" column="1">
+      <widget class="QDateEdit" name="birthday"/>
+     </item>
+    </layout>
+   </item>
+   <item row="7" column="1" colspan="2">
+    <layout class="QFormLayout" name="organizationForm">
+     <property name="formAlignment">
+      <set>Qt::AlignHCenter|Qt::AlignTop</set>
+     </property>
+     <item row="0" column="0">
+      <widget class="QLabel" name="organizationNameLabel">
+       <property name="text">
+        <string>Organization name</string>
+       </property>
+       <property name="buddy">
+        <cstring>organizationName</cstring>
+       </property>
+      </widget>
+     </item>
+     <item row="0" column="1">
+      <widget class="QLineEdit" name="organizationName">
+       <property name="minimumSize">
+        <size>
+         <width>200</width>
+         <height>0</height>
+        </size>
+       </property>
+       <property name="maximumSize">
+        <size>
+         <width>350</width>
+         <height>16777215</height>
+        </size>
+       </property>
+      </widget>
+     </item>
+     <item row="1" column="0">
+      <widget class="QLabel" name="organizationDepartmentLabel">
+       <property name="text">
+        <string>Unit / Department</string>
+       </property>
+       <property name="buddy">
+        <cstring>organizationDepartment</cstring>
+       </property>
+      </widget>
+     </item>
+     <item row="1" column="1">
+      <widget class="QLineEdit" name="organizationDepartment">
+       <property name="minimumSize">
+        <size>
+         <width>200</width>
+         <height>0</height>
+        </size>
+       </property>
+       <property name="maximumSize">
+        <size>
+         <width>350</width>
+         <height>16777215</height>
+        </size>
+       </property>
+      </widget>
+     </item>
+     <item row="2" column="0">
+      <widget class="QLabel" name="roleLabel">
+       <property name="text">
+        <string>Role / Profession</string>
+       </property>
+       <property name="buddy">
+        <cstring>organizationRole</cstring>
+       </property>
+      </widget>
+     </item>
+     <item row="2" column="1">
+      <widget class="QLineEdit" name="organizationRole">
+       <property name="minimumSize">
+        <size>
+         <width>200</width>
+         <height>0</height>
+        </size>
+       </property>
+       <property name="maximumSize">
+        <size>
+         <width>350</width>
+         <height>16777215</height>
+        </size>
+       </property>
+      </widget>
+     </item>
+     <item row="3" column="0">
+      <widget class="QLabel" name="organizationTitleLabel">
+       <property name="text">
+        <string>Job title</string>
+       </property>
+       <property name="buddy">
+        <cstring>organizationTitle</cstring>
+       </property>
+      </widget>
+     </item>
+     <item row="3" column="1">
+      <widget class="QLineEdit" name="organizationTitle">
+       <property name="minimumSize">
+        <size>
+         <width>200</width>
+         <height>0</height>
+        </size>
+       </property>
+       <property name="maximumSize">
+        <size>
+         <width>350</width>
+         <height>16777215</height>
+        </size>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </item>
+   <item row="8" column="1" colspan="2">
+    <widget class="Line" name="organizationLine">
+     <property name="orientation">
+      <enum>Qt::Horizontal</enum>
+     </property>
+    </widget>
+   </item>
+   <item row="2" column="1" colspan="2">
+    <layout class="QFormLayout" name="commonForm">
+     <item row="0" column="1">
+      <widget class="QLineEdit" name="fullName"/>
+     </item>
+     <item row="0" column="0">
+      <widget class="QLabel" name="fullNameLabel">
+       <property name="text">
+        <string>Full name</string>
+       </property>
+       <property name="buddy">
+        <cstring>fullName</cstring>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </item>
+   <item row="3" column="0" rowspan="7">
+    <spacer name="generalLeftHSpacer">
+     <property name="orientation">
+      <enum>Qt::Horizontal</enum>
+     </property>
+     <property name="sizeHint" stdset="0">
+      <size>
+       <width>40</width>
+       <height>20</height>
+      </size>
+     </property>
+    </spacer>
+   </item>
+   <item row="4" column="1" colspan="2">
+    <widget class="Line" name="personalLine">
+     <property name="orientation">
+      <enum>Qt::Horizontal</enum>
+     </property>
+    </widget>
+   </item>
+   <item row="0" column="0" colspan="4">
+    <widget class="QLabel" name="generalHeading">
+     <property name="styleSheet">
+      <string notr="true">font: 600 24pt ;</string>
+     </property>
+     <property name="text">
+      <string>General</string>
+     </property>
+    </widget>
+   </item>
+   <item row="3" column="3" rowspan="7">
+    <spacer name="generalRightHSpacer">
+     <property name="orientation">
+      <enum>Qt::Horizontal</enum>
+     </property>
+     <property name="sizeHint" stdset="0">
+      <size>
+       <width>40</width>
+       <height>20</height>
+      </size>
+     </property>
+    </spacer>
+   </item>
+   <item row="1" column="1" colspan="2">
+    <widget class="QLabel" name="personalHeading">
+     <property name="sizePolicy">
+      <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+       <horstretch>0</horstretch>
+       <verstretch>0</verstretch>
+      </sizepolicy>
+     </property>
+     <property name="styleSheet">
+      <string notr="true">font: 600 16pt;</string>
+     </property>
+     <property name="frameShape">
+      <enum>QFrame::NoFrame</enum>
+     </property>
+     <property name="frameShadow">
+      <enum>QFrame::Plain</enum>
+     </property>
+     <property name="text">
+      <string>Personal information</string>
+     </property>
+     <property name="alignment">
+      <set>Qt::AlignCenter</set>
+     </property>
+    </widget>
+   </item>
+   <item row="3" column="2">
+    <widget class="QToolButton" name="avatarButton">
+     <property name="sizePolicy">
+      <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+       <horstretch>0</horstretch>
+       <verstretch>0</verstretch>
+      </sizepolicy>
+     </property>
+     <property name="minimumSize">
+      <size>
+       <width>0</width>
+       <height>0</height>
+      </size>
+     </property>
+     <property name="text">
+      <string/>
+     </property>
+     <property name="icon">
+      <iconset theme="user">
+       <normaloff>:/images/fallback/dark/big/user.svg</normaloff>:/images/fallback/dark/big/user.svg</iconset>
+     </property>
+     <property name="iconSize">
+      <size>
+       <width>0</width>
+       <height>0</height>
+      </size>
+     </property>
+     <property name="popupMode">
+      <enum>QToolButton::InstantPopup</enum>
+     </property>
+     <property name="toolButtonStyle">
+      <enum>Qt::ToolButtonIconOnly</enum>
+     </property>
+     <property name="arrowType">
+      <enum>Qt::NoArrow</enum>
+     </property>
+    </widget>
+   </item>
+   <item row="9" column="1" colspan="2">
+    <spacer name="verticalSpacer">
+     <property name="orientation">
+      <enum>Qt::Vertical</enum>
+     </property>
+     <property name="sizeHint" stdset="0">
+      <size>
+       <width>20</width>
+       <height>40</height>
+      </size>
+     </property>
+    </spacer>
+   </item>
+  </layout>
+ </widget>
+  <tabstops>
+  <tabstop>fullName</tabstop>
+  <tabstop>firstName</tabstop>
+  <tabstop>middleName</tabstop>
+  <tabstop>lastName</tabstop>
+  <tabstop>nickName</tabstop>
+  <tabstop>birthday</tabstop>
+  <tabstop>avatarButton</tabstop>
+  <tabstop>organizationName</tabstop>
+  <tabstop>organizationDepartment</tabstop>
+  <tabstop>organizationRole</tabstop>
+  <tabstop>organizationTitle</tabstop>
+ </tabstops>
+ <resources>
+  <include location="../../../resources/resources.qrc"/>
+ </resources>
+ <connections/>
+</ui>
diff --git a/ui/widgets/info/info.cpp b/ui/widgets/info/info.cpp
new file mode 100644
index 0000000..eacab0f
--- /dev/null
+++ b/ui/widgets/info/info.cpp
@@ -0,0 +1,30 @@
+// Squawk messenger. 
+// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+// 
+// 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 "info.h"
+#include "ui_info.h"
+
+UI::Info::Info(const Shared::Info& info, QWidget* parent):
+    QWidget(parent),
+    m_ui(new Ui::Info())
+{
+    m_ui->setupUi(this);
+
+
+}
+
+UI::Info::~Info()
+{}
diff --git a/ui/widgets/info/info.h b/ui/widgets/info/info.h
new file mode 100644
index 0000000..6858074
--- /dev/null
+++ b/ui/widgets/info/info.h
@@ -0,0 +1,45 @@
+// Squawk messenger. 
+// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+// 
+// 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/>.
+
+#ifndef UI_WIDGETS_INFO_H
+#define UI_WIDGETS_INFO_H
+
+#include <QWidget>
+#include <QScopedPointer>
+
+#include <shared/info.h>
+
+#include "contactgeneral.h"
+
+namespace UI {
+namespace Ui
+{
+class Info;
+}
+
+class Info : public QWidget {
+    Q_OBJECT
+public:
+    Info(const Shared::Info& info, QWidget* parent = nullptr);
+    ~Info();
+
+private:
+    QScopedPointer<Ui::Info> m_ui;
+};
+
+}
+
+#endif // UI_WIDGETS_INFO_H
diff --git a/ui/widgets/info/info.ui b/ui/widgets/info/info.ui
new file mode 100644
index 0000000..f600d8b
--- /dev/null
+++ b/ui/widgets/info/info.ui
@@ -0,0 +1,137 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>UI::Info</class>
+ <widget class="QWidget" name="UI::Info">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>400</width>
+    <height>300</height>
+   </rect>
+  </property>
+
+    <layout class="QVBoxLayout" name="verticalLayout">
+     <property name="leftMargin">
+      <number>6</number>
+     </property>
+     <property name="topMargin">
+      <number>6</number>
+     </property>
+     <property name="rightMargin">
+      <number>6</number>
+     </property>
+     <property name="bottomMargin">
+      <number>6</number>
+     </property>
+     <item>
+      <widget class="QLabel" name="title">
+       <property name="styleSheet">
+        <string notr="true">font: 16pt </string>
+       </property>
+       <property name="text">
+        <string notr="true">Contact john@dow.org card</string>
+       </property>
+       <property name="alignment">
+        <set>Qt::AlignCenter</set>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QLabel" name="receivingTimeLabel">
+       <property name="styleSheet">
+        <string notr="true">font: italic 8pt;</string>
+       </property>
+       <property name="text">
+        <string>Received 12.07.2007 at 17.35</string>
+       </property>
+       <property name="alignment">
+        <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QTabWidget" name="tabWidget">
+       <property name="enabled">
+        <bool>true</bool>
+       </property>
+       <property name="focusPolicy">
+        <enum>Qt::TabFocus</enum>
+       </property>
+       <property name="tabPosition">
+        <enum>QTabWidget::North</enum>
+       </property>
+       <property name="tabShape">
+        <enum>QTabWidget::Rounded</enum>
+       </property>
+       <property name="currentIndex">
+        <number>1</number>
+       </property>
+       <property name="elideMode">
+        <enum>Qt::ElideNone</enum>
+       </property>
+       <property name="documentMode">
+        <bool>true</bool>
+       </property>
+       <property name="tabBarAutoHide">
+        <bool>false</bool>
+       </property>
+       <widget class="QWidget" name="Description">
+        <attribute name="title">
+         <string>Description</string>
+        </attribute>
+        <layout class="QGridLayout" name="gridLayout">
+         <property name="leftMargin">
+          <number>0</number>
+         </property>
+         <property name="topMargin">
+          <number>6</number>
+         </property>
+         <property name="rightMargin">
+          <number>0</number>
+         </property>
+         <property name="bottomMargin">
+          <number>0</number>
+         </property>
+         <property name="horizontalSpacing">
+          <number>6</number>
+         </property>
+         <item row="0" column="0">
+          <widget class="QLabel" name="descriptionHeading">
+           <property name="styleSheet">
+            <string notr="true">font: 600 24pt ;</string>
+           </property>
+           <property name="text">
+            <string>Description</string>
+           </property>
+          </widget>
+         </item>
+         <item row="1" column="0">
+          <widget class="QTextEdit" name="description">
+           <property name="frameShape">
+            <enum>QFrame::StyledPanel</enum>
+           </property>
+           <property name="textInteractionFlags">
+            <set>Qt::LinksAccessibleByMouse|Qt::TextEditable|Qt::TextEditorInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
+           </property>
+          </widget>
+         </item>
+        </layout>
+       </widget>
+      </widget>
+     </item>
+     <item>
+      <widget class="QDialogButtonBox" name="buttonBox">
+       <property name="standardButtons">
+        <set>QDialogButtonBox::Close|QDialogButtonBox::Save</set>
+       </property>
+       <property name="centerButtons">
+        <bool>false</bool>
+       </property>
+      </widget>
+     </item>
+    </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/ui/widgets/vcard/omemo/CMakeLists.txt b/ui/widgets/info/omemo/CMakeLists.txt
similarity index 76%
rename from ui/widgets/vcard/omemo/CMakeLists.txt
rename to ui/widgets/info/omemo/CMakeLists.txt
index e2ade51..f1dc4ed 100644
--- a/ui/widgets/vcard/omemo/CMakeLists.txt
+++ b/ui/widgets/info/omemo/CMakeLists.txt
@@ -2,8 +2,6 @@ target_sources(squawk PRIVATE
     omemo.cpp
     omemo.h
     omemo.ui
-    keysmodel.cpp
-    keysmodel.h
     keydelegate.cpp
     keydelegate.h
 )
diff --git a/ui/widgets/vcard/omemo/keydelegate.cpp b/ui/widgets/info/omemo/keydelegate.cpp
similarity index 92%
rename from ui/widgets/vcard/omemo/keydelegate.cpp
rename to ui/widgets/info/omemo/keydelegate.cpp
index d0023e5..fd687ad 100644
--- a/ui/widgets/vcard/omemo/keydelegate.cpp
+++ b/ui/widgets/info/omemo/keydelegate.cpp
@@ -18,7 +18,7 @@
 #include <QPainter>
 #include <QDebug>
 
-#include "keysmodel.h"
+#include "ui/models/info/omemo/keys.h"
 #include <shared/global.h>
 
 constexpr uint8_t margin = 10;
@@ -56,7 +56,7 @@ void UI::KeyDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio
         painter->restore();
     }
 
-    QVariant dirtyV = index.data(UI::KeysModel::Dirty);
+    QVariant dirtyV = index.data(Models::Keys::Dirty);
     if (dirtyV.isValid() && dirtyV.toBool()) {
         painter->save();
         rect.setWidth(margin / 2);
@@ -66,7 +66,7 @@ void UI::KeyDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio
     }
 
     rect.adjust(margin, margin, -margin, -margin);
-    QVariant labelV = index.data(UI::KeysModel::Label);
+    QVariant labelV = index.data(Models::Keys::Label);
     if (labelV.isValid()) {
         QString label = labelV.toString();
         if (label.size() > 0) {
@@ -80,7 +80,7 @@ void UI::KeyDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio
         }
     }
 
-    QVariant fingerPrintV = index.data(UI::KeysModel::FingerPrint);
+    QVariant fingerPrintV = index.data(Models::Keys::FingerPrint);
     if (fingerPrintV.isValid()) {
         painter->save();
         painter->setFont(fingerPrintFont);
@@ -111,7 +111,7 @@ void UI::KeyDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio
     }
 
 
-    QVariant lastV = index.data(UI::KeysModel::LastInteraction);
+    QVariant lastV = index.data(Models::Keys::LastInteraction);
     if (lastV.isValid()) {
         QDateTime last = lastV.toDateTime();
         if (last.isValid()) {
@@ -125,7 +125,7 @@ void UI::KeyDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio
         }
     }
 
-    QVariant levelV = index.data(UI::KeysModel::TrustLevel);
+    QVariant levelV = index.data(Models::Keys::TrustLevel);
     if (levelV.isValid()) {
         Shared::TrustLevel level = static_cast<Shared::TrustLevel>(levelV.toUInt());
         QString levelName = Shared::Global::getName(level);
@@ -150,7 +150,7 @@ QSize UI::KeyDelegate::sizeHint(const QStyleOptionViewItem& option, const QModel
     int mw = margin * 2;
     int width = 0;
 
-    QVariant labelV = index.data(UI::KeysModel::Label);
+    QVariant labelV = index.data(Models::Keys::Label);
     if (labelV.isValid()) {
         QString label = labelV.toString();
         if (label.size() > 0) {
@@ -159,7 +159,7 @@ QSize UI::KeyDelegate::sizeHint(const QStyleOptionViewItem& option, const QModel
         }
     }
 
-    QVariant fingerPrintV = index.data(UI::KeysModel::FingerPrint);
+    QVariant fingerPrintV = index.data(Models::Keys::FingerPrint);
     if (fingerPrintV.isValid()) {
         QString hex = fingerPrintV.toByteArray().toHex();
         uint8_t parts = hex.size() / partSize;
@@ -177,7 +177,7 @@ QSize UI::KeyDelegate::sizeHint(const QStyleOptionViewItem& option, const QModel
         width = std::max(width, firstLine * fingerPrintMetrics.horizontalAdvance(hex, partSize) + (firstLine - 1) * spaceWidth);
         width += 1;     //there is a mistake somewhere, this the cheapest way to compensate it
     }
-    QVariant lastV = index.data(UI::KeysModel::LastInteraction);
+    QVariant lastV = index.data(Models::Keys::LastInteraction);
     if (lastV.isValid()) {
         QDateTime last = lastV.toDateTime();
         if (last.isValid()) {
@@ -187,7 +187,7 @@ QSize UI::KeyDelegate::sizeHint(const QStyleOptionViewItem& option, const QModel
         }
     }
 
-    QVariant levelV = index.data(UI::KeysModel::TrustLevel);
+    QVariant levelV = index.data(Models::Keys::TrustLevel);
     if (levelV.isValid()) {
         Shared::TrustLevel level = static_cast<Shared::TrustLevel>(levelV.toUInt());
         QString levelName = Shared::Global::getName(level);
diff --git a/ui/widgets/vcard/omemo/keydelegate.h b/ui/widgets/info/omemo/keydelegate.h
similarity index 100%
rename from ui/widgets/vcard/omemo/keydelegate.h
rename to ui/widgets/info/omemo/keydelegate.h
diff --git a/ui/widgets/vcard/omemo/omemo.cpp b/ui/widgets/info/omemo/omemo.cpp
similarity index 90%
rename from ui/widgets/vcard/omemo/omemo.cpp
rename to ui/widgets/info/omemo/omemo.cpp
index 3a5ab73..16414ee 100644
--- a/ui/widgets/vcard/omemo/omemo.cpp
+++ b/ui/widgets/info/omemo/omemo.cpp
@@ -71,13 +71,13 @@ void Omemo::onActiveKeysContextMenu(const QPoint& pos) {
     contextMenu->clear();
     QModelIndex index = m_ui->keysView->indexAt(pos);
     if (index.isValid()) {
-        QVariant dirtyV = index.data(UI::KeysModel::Dirty);
+        QVariant dirtyV = index.data(Models::Keys::Dirty);
         if (dirtyV.isValid() && dirtyV.toBool()) {
             QAction* rev = contextMenu->addAction(Shared::icon("clean"), tr("Revert changes"));
-            connect(rev, &QAction::triggered, std::bind(&UI::KeysModel::revertKey, &keysModel, index.row()));
+            connect(rev, &QAction::triggered, std::bind(&Models::Keys::revertKey, &keysModel, index.row()));
         }
 
-        QVariant levelV = index.data(UI::KeysModel::TrustLevel);
+        QVariant levelV = index.data(Models::Keys::TrustLevel);
         if (levelV.isValid()) {
             Shared::TrustLevel level = static_cast<Shared::TrustLevel>(levelV.toUInt());
             if (level == Shared::TrustLevel::undecided ||
@@ -86,7 +86,7 @@ void Omemo::onActiveKeysContextMenu(const QPoint& pos) {
             ) {
                 QAction* rev = contextMenu->addAction(Shared::icon("favorite"), tr("Trust"));
                 connect(rev, &QAction::triggered,
-                        std::bind(&UI::KeysModel::setTrustLevel, &keysModel,
+                        std::bind(&Models::Keys::setTrustLevel, &keysModel,
                                   index.row(), Shared::TrustLevel::manuallyTrusted
                         )
                 );
@@ -99,7 +99,7 @@ void Omemo::onActiveKeysContextMenu(const QPoint& pos) {
             ) {
                 QAction* rev = contextMenu->addAction(Shared::icon("unfavorite"), tr("Distrust"));
                 connect(rev, &QAction::triggered,
-                        std::bind(&UI::KeysModel::setTrustLevel, &keysModel,
+                        std::bind(&Models::Keys::setTrustLevel, &keysModel,
                                   index.row(), Shared::TrustLevel::manuallyDistrusted
                         )
                 );
diff --git a/ui/widgets/vcard/omemo/omemo.h b/ui/widgets/info/omemo/omemo.h
similarity index 92%
rename from ui/widgets/vcard/omemo/omemo.h
rename to ui/widgets/info/omemo/omemo.h
index 59e3dac..7c6b5a1 100644
--- a/ui/widgets/vcard/omemo/omemo.h
+++ b/ui/widgets/info/omemo/omemo.h
@@ -21,7 +21,7 @@
 #include <QScopedPointer>
 #include <QMenu>
 
-#include "keysmodel.h"
+#include "ui/models/info/omemo/keys.h"
 #include "keydelegate.h"
 #include "shared/icons.h"
 
@@ -46,8 +46,8 @@ private:
     QScopedPointer<Ui::Omemo> m_ui;
     UI::KeyDelegate keysDelegate;
     UI::KeyDelegate unusedKeysDelegate;
-    UI::KeysModel keysModel;
-    UI::KeysModel unusedKeysModel;
+    Models::Keys keysModel;
+    Models::Keys unusedKeysModel;
     QMenu* contextMenu;
 };
 
diff --git a/ui/widgets/vcard/omemo/omemo.ui b/ui/widgets/info/omemo/omemo.ui
similarity index 100%
rename from ui/widgets/vcard/omemo/omemo.ui
rename to ui/widgets/info/omemo/omemo.ui
diff --git a/ui/widgets/room.h b/ui/widgets/room.h
index 3f74e4e..3d231b8 100644
--- a/ui/widgets/room.h
+++ b/ui/widgets/room.h
@@ -20,7 +20,7 @@
 #define ROOM_H
 
 #include "conversation.h"
-#include "../models/room.h"
+#include "ui/models/room.h"
 
 /**
  * @todo write docs
diff --git a/ui/widgets/vcard/CMakeLists.txt b/ui/widgets/vcard/CMakeLists.txt
index c37f4c6..5dca28d 100644
--- a/ui/widgets/vcard/CMakeLists.txt
+++ b/ui/widgets/vcard/CMakeLists.txt
@@ -1,13 +1,5 @@
 target_sources(squawk PRIVATE
-  emailsmodel.cpp
-  emailsmodel.h
-  phonesmodel.cpp
-  phonesmodel.h
   vcard.cpp
   vcard.h
   vcard.ui
   )
-
-if (WITH_OMEMO)
- add_subdirectory(omemo)
-endif()
diff --git a/ui/widgets/vcard/vcard.cpp b/ui/widgets/vcard/vcard.cpp
index a3e877b..d16aa83 100644
--- a/ui/widgets/vcard/vcard.cpp
+++ b/ui/widgets/vcard/vcard.cpp
@@ -235,10 +235,10 @@ void VCard::onContextMenu(const QPoint& point) {
                     int row = sm->selectedRows().at(0).row();
                     if (emails.isPreferred(row)) {
                         QAction* rev = contextMenu->addAction(Shared::icon("unfavorite"), tr("Unset this email as preferred"));
-                        connect(rev, &QAction::triggered, std::bind(&UI::VCard::EMailsModel::revertPreferred, &emails, row));
+                        connect(rev, &QAction::triggered, std::bind(&Models::EMails::revertPreferred, &emails, row));
                     } else {
                         QAction* rev = contextMenu->addAction(Shared::icon("favorite"), tr("Set this email as preferred"));
-                        connect(rev, &QAction::triggered, std::bind(&UI::VCard::EMailsModel::revertPreferred, &emails, row));
+                        connect(rev, &QAction::triggered, std::bind(&Models::EMails::revertPreferred, &emails, row));
                     }
                 }
                 
@@ -263,10 +263,10 @@ void VCard::onContextMenu(const QPoint& point) {
                     int row = sm->selectedRows().at(0).row();
                     if (phones.isPreferred(row)) {
                         QAction* rev = contextMenu->addAction(Shared::icon("view-media-favorite"), tr("Unset this phone as preferred"));
-                        connect(rev, &QAction::triggered, std::bind(&UI::VCard::PhonesModel::revertPreferred, &phones, row));
+                        connect(rev, &QAction::triggered, std::bind(&Models::Phones::revertPreferred, &phones, row));
                     } else {
                         QAction* rev = contextMenu->addAction(Shared::icon("favorite"), tr("Set this phone as preferred"));
-                        connect(rev, &QAction::triggered, std::bind(&UI::VCard::PhonesModel::revertPreferred, &phones, row));
+                        connect(rev, &QAction::triggered, std::bind(&Models::Phones::revertPreferred, &phones, row));
                     }
                 }
                 
diff --git a/ui/widgets/vcard/vcard.h b/ui/widgets/vcard/vcard.h
index 82b7d53..7019797 100644
--- a/ui/widgets/vcard/vcard.h
+++ b/ui/widgets/vcard/vcard.h
@@ -37,13 +37,13 @@
 #include <set>
 
 #include "shared/vcard.h"
-#include "emailsmodel.h"
-#include "phonesmodel.h"
+#include "ui/models/info/emails.h"
+#include "ui/models/info/phones.h"
 #include "ui/utils/progress.h"
 #include "ui/utils/comboboxdelegate.h"
 
 #ifdef WITH_OMEMO
-#include "omemo/omemo.h"
+#include "ui/widgets/info/omemo/omemo.h"
 #endif
 
 namespace Ui
@@ -92,8 +92,8 @@ private:
     QLabel* progressLabel;
     QWidget* overlay;
     QMenu* contextMenu;
-    UI::VCard::EMailsModel emails;
-    UI::VCard::PhonesModel phones;
+    Models::EMails emails;
+    Models::Phones phones;
     ComboboxDelegate* roleDelegate;
     ComboboxDelegate* phoneTypeDelegate;
 #ifdef WITH_OMEMO

From edf1ee60cdb1ee7fcd3772118852b806106371b3 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Thu, 2 Feb 2023 21:39:38 +0300
Subject: [PATCH 229/281] keep going on refactoring vcard

---
 shared/enums.h                     |  10 ++
 shared/info.cpp                    |   5 +-
 shared/info.h                      |   4 +-
 ui/widgets/info/CMakeLists.txt     |   3 +
 ui/widgets/info/contactgeneral.cpp | 176 ++++++++++++++++++++++++++++-
 ui/widgets/info/contactgeneral.h   |  36 ++++++
 ui/widgets/info/contactgeneral.ui  |  18 +++
 ui/widgets/info/description.cpp    |  27 +++++
 ui/widgets/info/description.h      |  41 +++++++
 ui/widgets/info/description.ui     |  48 ++++++++
 ui/widgets/info/info.cpp           |  81 ++++++++++++-
 ui/widgets/info/info.h             |  25 +++-
 ui/widgets/info/info.ui            |  64 ++++-------
 13 files changed, 483 insertions(+), 55 deletions(-)
 create mode 100644 ui/widgets/info/description.cpp
 create mode 100644 ui/widgets/info/description.h
 create mode 100644 ui/widgets/info/description.ui

diff --git a/shared/enums.h b/shared/enums.h
index 9fb4aad..9eb9e5e 100644
--- a/shared/enums.h
+++ b/shared/enums.h
@@ -118,6 +118,16 @@ Q_ENUM_NS(AccountPassword)
 static const AccountPassword AccountPasswordHighest = AccountPassword::kwallet;
 static const AccountPassword AccountPasswordLowest = AccountPassword::plain;
 
+enum class EntryType {
+    contact,
+    conference,
+    presence,
+    participant
+};
+Q_ENUM_NS(EntryType)
+static const EntryType EntryTypeHighest = EntryType::participant;
+static const EntryType EntryTypeLowest = EntryType::contact;
+
 enum class Support {
     unknown,
     supported,
diff --git a/shared/info.cpp b/shared/info.cpp
index a5cf046..a0ac299 100644
--- a/shared/info.cpp
+++ b/shared/info.cpp
@@ -16,7 +16,8 @@
 
 #include "info.h"
 
-Shared::Info::Info(const QString& p_jid, bool p_editable):
+Shared::Info::Info(const QString& p_jid, EntryType p_type, bool p_editable):
+    type(p_type),
     jid(p_jid),
     editable(p_editable),
     vcard(),
@@ -25,6 +26,7 @@ Shared::Info::Info(const QString& p_jid, bool p_editable):
 {}
 
 Shared::Info::Info():
+    type(EntryType::contact),
     jid(),
     editable(false),
     vcard(),
@@ -33,6 +35,7 @@ Shared::Info::Info():
 {}
 
 Shared::Info::Info(const Shared::Info& other):
+    type(other.type),
     jid(other.jid),
     editable(other.editable),
     vcard(other.vcard),
diff --git a/shared/info.h b/shared/info.h
index c3f16f8..90248c3 100644
--- a/shared/info.h
+++ b/shared/info.h
@@ -19,6 +19,7 @@
 
 #include "vcard.h"
 #include "keyinfo.h"
+#include "enums.h"
 
 #include <list>
 
@@ -33,10 +34,11 @@ namespace Shared {
 class Info {
 public:
     Info();
-    Info(const QString& jid, bool editable = false);
+    Info(const QString& jid, EntryType = EntryType::contact, bool editable = false);
     Info(const Info& other);
     ~Info();
 
+    EntryType type;
     QString jid;
     bool editable;
     VCard vcard;
diff --git a/ui/widgets/info/CMakeLists.txt b/ui/widgets/info/CMakeLists.txt
index 0029e84..44edb66 100644
--- a/ui/widgets/info/CMakeLists.txt
+++ b/ui/widgets/info/CMakeLists.txt
@@ -2,18 +2,21 @@ set(SOURCE_FILES
   info.cpp
   contactgeneral.cpp
   contactcontacts.cpp
+  description.cpp
 )
 
 set(UI_FILES
   info.ui
   contactgeneral.ui
   contactcontacts.ui
+  description.ui
 )
 
 set(HEADER_FILES
   info.h
   contactgeneral.h
   contactcontacts.h
+  description.h
 )
 
 target_sources(squawk PRIVATE
diff --git a/ui/widgets/info/contactgeneral.cpp b/ui/widgets/info/contactgeneral.cpp
index 62efe81..57ce700 100644
--- a/ui/widgets/info/contactgeneral.cpp
+++ b/ui/widgets/info/contactgeneral.cpp
@@ -17,12 +17,182 @@
 #include "contactgeneral.h"
 #include "ui_contactgeneral.h"
 
+#include <QDebug>
+
+const std::set<QString> UI::ContactGeneral::supportedTypes = {"image/jpeg", "image/png"};
+constexpr int maxAvatarSize = 160;
+
 UI::ContactGeneral::ContactGeneral(QWidget* parent):
     QWidget(parent),
-    m_ui(new Ui::ContactGeneral)
+    m_ui(new Ui::ContactGeneral),
+    avatarMenu(nullptr),
+    avatarButtonMargins(),
+    currentAvatarType(Shared::Avatar::empty),
+    currentAvatarPath(""),
+    currentJid(""),
+    editable(false),
+    avatarDiablog(nullptr)
 {
     m_ui->setupUi(this);
+
+    initializeActions();
+    initializeAvatar();
 }
 
-UI::ContactGeneral::~ContactGeneral()
-{}
+UI::ContactGeneral::~ContactGeneral() {
+    if (avatarMenu != nullptr)
+        avatarMenu->deleteLater();
+
+    if (avatarDiablog != nullptr)
+        deleteAvatarDialog();
+}
+
+QString UI::ContactGeneral::title() const {
+    return m_ui->generalHeading->text();}
+
+void UI::ContactGeneral::setEditable(bool edit) {
+    m_ui->fullName->setReadOnly(!edit);
+    m_ui->firstName->setReadOnly(!edit);
+    m_ui->middleName->setReadOnly(!edit);
+    m_ui->lastName->setReadOnly(!edit);
+    m_ui->nickName->setReadOnly(!edit);
+    m_ui->birthday->setReadOnly(!edit);
+    m_ui->organizationName->setReadOnly(!edit);
+    m_ui->organizationDepartment->setReadOnly(!edit);
+    m_ui->organizationTitle->setReadOnly(!edit);
+    m_ui->organizationRole->setReadOnly(!edit);
+
+    if (edit) {
+        avatarMenu = new QMenu();
+        m_ui->avatarButton->setMenu(avatarMenu);
+        avatarMenu->addAction(m_ui->actionSetAvatar);
+        avatarMenu->addAction(m_ui->actionClearAvatar);
+    } else {
+        if (avatarMenu != nullptr) {
+            avatarMenu->deleteLater();
+            avatarMenu = nullptr;
+            m_ui->avatarButton->setMenu(nullptr);
+        }
+    }
+}
+
+void UI::ContactGeneral::deleteAvatarDialog() {
+    avatarDiablog->deleteLater();
+
+    disconnect(avatarDiablog, &QFileDialog::accepted, this, &UI::ContactGeneral::avatarSelected);
+    disconnect(avatarDiablog, &QFileDialog::rejected, this, &UI::ContactGeneral::deleteAvatarDialog);
+
+    avatarDiablog = nullptr;
+}
+
+void UI::ContactGeneral::initializeActions() {
+    QAction* setAvatar = m_ui->actionSetAvatar;
+    QAction* clearAvatar = m_ui->actionClearAvatar;
+
+    connect(setAvatar, &QAction::triggered, this, &UI::ContactGeneral::onSetAvatar);
+    connect(clearAvatar, &QAction::triggered, this, &UI::ContactGeneral::onClearAvatar);
+
+    setAvatar->setEnabled(editable);
+    clearAvatar->setEnabled(false);
+}
+
+void UI::ContactGeneral::initializeAvatar() {
+    QToolButton* avatarButton = m_ui->avatarButton;
+    avatarButtonMargins = avatarButton->size();
+
+    int height = m_ui->personalForm->minimumSize().height() - avatarButtonMargins.height();
+    avatarButton->setIconSize(QSize(height, height));
+}
+
+void UI::ContactGeneral::onSetAvatar() {
+    avatarDiablog = new QFileDialog(this, tr("Chose your new avatar"));
+    avatarDiablog->setFileMode(QFileDialog::ExistingFile);
+    avatarDiablog->setNameFilter(tr("Images (*.png *.jpg *.jpeg)"));
+
+    connect(avatarDiablog, &QFileDialog::accepted, this, &UI::ContactGeneral::avatarSelected);
+    connect(avatarDiablog, &QFileDialog::rejected, this, &UI::ContactGeneral::deleteAvatarDialog);
+
+    avatarDiablog->show();
+}
+
+void UI::ContactGeneral::onClearAvatar() {
+    currentAvatarType = Shared::Avatar::empty;
+    currentAvatarPath = "";
+
+    updateAvatar();
+}
+
+void UI::ContactGeneral::updateAvatar() {
+    int height = m_ui->personalForm->minimumSize().height() - avatarButtonMargins.height();
+    switch (currentAvatarType) {
+        case Shared::Avatar::empty:
+            m_ui->avatarButton->setIcon(Shared::icon("user", true));
+            m_ui->avatarButton->setIconSize(QSize(height, height));
+            m_ui->actionClearAvatar->setEnabled(false);
+            break;
+        case Shared::Avatar::autocreated:
+        case Shared::Avatar::valid:
+            QPixmap pixmap(currentAvatarPath);
+            qreal h = pixmap.height();
+            qreal w = pixmap.width();
+            qreal aspectRatio = w / h;
+            m_ui->avatarButton->setIconSize(QSize(height * aspectRatio, height));
+            m_ui->avatarButton->setIcon(QIcon(currentAvatarPath));
+            m_ui->actionClearAvatar->setEnabled(editable);
+            break;
+    }
+}
+
+void UI::ContactGeneral::avatarSelected() {
+    QMimeDatabase db;
+    QString path = avatarDiablog->selectedFiles().front();
+    QMimeType type = db.mimeTypeForFile(path);
+    deleteAvatarDialog();
+
+    if (supportedTypes.find(type.name()) == supportedTypes.end()) {
+        qDebug() << "Selected for avatar file is not supported, skipping";
+    } else {
+        QImage src(path);
+        QImage dst;
+        if (src.width() > maxAvatarSize || src.height() > maxAvatarSize) {
+            dst = src.scaled(maxAvatarSize, maxAvatarSize, Qt::KeepAspectRatio);
+        }
+        QString path = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/" + currentJid + ".temp." + type.preferredSuffix();
+        QFile oldTemp(path);
+        if (oldTemp.exists()) {
+            if (!oldTemp.remove()) {
+                qDebug() << "Error removing old temp avatar" << path;
+                return;
+            }
+        }
+        bool success = dst.save(path);
+        if (success) {
+            currentAvatarPath = path;
+            currentAvatarType = Shared::Avatar::valid;
+
+            updateAvatar();
+        } else {
+            qDebug() << "couldn't save temp avatar" << path << ", skipping";
+        }
+    }
+}
+
+void UI::ContactGeneral::setVCard(const QString& jid, const Shared::VCard& card, bool editable) {
+    currentJid = jid;
+    setEditable(editable);
+    m_ui->fullName->setText(card.getFullName());
+    m_ui->firstName->setText(card.getFirstName());
+    m_ui->middleName->setText(card.getMiddleName());
+    m_ui->lastName->setText(card.getLastName());
+    m_ui->nickName->setText(card.getNickName());
+    m_ui->birthday->setDate(card.getBirthday());
+    m_ui->organizationName->setText(card.getOrgName());
+    m_ui->organizationDepartment->setText(card.getOrgUnit());
+    m_ui->organizationTitle->setText(card.getOrgTitle());
+    m_ui->organizationRole->setText(card.getOrgRole());
+
+    currentAvatarType = card.getAvatarType();
+    currentAvatarPath = card.getAvatarPath();
+
+    updateAvatar();
+}
diff --git a/ui/widgets/info/contactgeneral.h b/ui/widgets/info/contactgeneral.h
index 5e116c6..6233c83 100644
--- a/ui/widgets/info/contactgeneral.h
+++ b/ui/widgets/info/contactgeneral.h
@@ -19,6 +19,18 @@
 
 #include <QWidget>
 #include <QScopedPointer>
+#include <QMenu>
+#include <QString>
+#include <QFileDialog>
+#include <QMimeDatabase>
+#include <QStandardPaths>
+#include <QImage>
+
+#include <set>
+
+#include "shared/enums.h"
+#include "shared/vcard.h"
+#include "shared/icons.h"
 
 namespace UI {
 namespace Ui
@@ -32,8 +44,32 @@ public:
     ContactGeneral(QWidget* parent = nullptr);
     ~ContactGeneral();
 
+    void setVCard(const QString& jid, const Shared::VCard& card, bool editable = false);
+    QString title() const;
+
+private:
+    void setEditable(bool edit);
+    void initializeActions();
+    void initializeAvatar();
+    void updateAvatar();
+
+private slots:
+    void deleteAvatarDialog();
+    void avatarSelected();
+    void onSetAvatar();
+    void onClearAvatar();
+
 private:
     QScopedPointer<Ui::ContactGeneral> m_ui;
+    QMenu* avatarMenu;
+    QSize avatarButtonMargins;
+    Shared::Avatar currentAvatarType;
+    QString currentAvatarPath;
+    QString currentJid;
+    bool editable;
+    QFileDialog* avatarDiablog;
+
+    static const std::set<QString> supportedTypes;
 };
 
 }
diff --git a/ui/widgets/info/contactgeneral.ui b/ui/widgets/info/contactgeneral.ui
index bb2b9e7..5ec123e 100644
--- a/ui/widgets/info/contactgeneral.ui
+++ b/ui/widgets/info/contactgeneral.ui
@@ -427,6 +427,24 @@
     </spacer>
    </item>
   </layout>
+    <action name="actionSetAvatar">
+   <property name="icon">
+    <iconset theme="photo" resource="../../../resources/resources.qrc">
+     <normaloff>:/images/fallback/dark/big/edit-rename.svg</normaloff>:/images/fallback/dark/big/edit-rename.svg</iconset>
+   </property>
+   <property name="text">
+    <string>Set avatar</string>
+   </property>
+  </action>
+  <action name="actionClearAvatar">
+   <property name="icon">
+    <iconset theme="edit-clear-all" resource="../../../resources/resources.qrc">
+     <normaloff>:/images/fallback/dark/big/clean.svg</normaloff>:/images/fallback/dark/big/clean.svg</iconset>
+   </property>
+   <property name="text">
+    <string>Clear avatar</string>
+   </property>
+  </action>
  </widget>
   <tabstops>
   <tabstop>fullName</tabstop>
diff --git a/ui/widgets/info/description.cpp b/ui/widgets/info/description.cpp
new file mode 100644
index 0000000..5132321
--- /dev/null
+++ b/ui/widgets/info/description.cpp
@@ -0,0 +1,27 @@
+// Squawk messenger. 
+// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+// 
+// 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 "description.h"
+#include "ui_description.h"
+
+UI::Description::Description(QWidget* parent):
+    QWidget(parent),
+    m_ui(new Ui::Description())
+{
+    m_ui->setupUi(this);
+}
+
+UI::Description::~Description() {}
diff --git a/ui/widgets/info/description.h b/ui/widgets/info/description.h
new file mode 100644
index 0000000..42b0900
--- /dev/null
+++ b/ui/widgets/info/description.h
@@ -0,0 +1,41 @@
+// Squawk messenger. 
+// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+// 
+// 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/>.
+
+#ifndef UI_WIDGETS_DESCRIPTION_H
+#define UI_WIDGETS_DESCRIPTION_H
+
+#include <QWidget>
+#include <QScopedPointer>
+
+namespace UI {
+namespace Ui
+{
+class Description;
+}
+
+class Description : public QWidget {
+    Q_OBJECT
+public:
+    Description(QWidget* parent = nullptr);
+    ~Description();
+
+private:
+    QScopedPointer<Ui::Description> m_ui;
+};
+
+}
+
+#endif // UI_WIDGETS_DESCRIPTION_H
diff --git a/ui/widgets/info/description.ui b/ui/widgets/info/description.ui
new file mode 100644
index 0000000..e69a8f6
--- /dev/null
+++ b/ui/widgets/info/description.ui
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>UI::Description</class>
+<widget class="QWidget" name="Description">
+        <attribute name="title">
+         <string>Description</string>
+        </attribute>
+        <layout class="QGridLayout" name="gridLayout">
+         <property name="leftMargin">
+          <number>0</number>
+         </property>
+         <property name="topMargin">
+          <number>6</number>
+         </property>
+         <property name="rightMargin">
+          <number>0</number>
+         </property>
+         <property name="bottomMargin">
+          <number>0</number>
+         </property>
+         <property name="horizontalSpacing">
+          <number>6</number>
+         </property>
+         <item row="0" column="0">
+          <widget class="QLabel" name="descriptionHeading">
+           <property name="styleSheet">
+            <string notr="true">font: 600 24pt ;</string>
+           </property>
+           <property name="text">
+            <string>Description</string>
+           </property>
+          </widget>
+         </item>
+         <item row="1" column="0">
+          <widget class="QTextEdit" name="description">
+           <property name="frameShape">
+            <enum>QFrame::StyledPanel</enum>
+           </property>
+           <property name="textInteractionFlags">
+            <set>Qt::LinksAccessibleByMouse|Qt::TextEditable|Qt::TextEditorInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
+           </property>
+          </widget>
+         </item>
+        </layout>
+       </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/ui/widgets/info/info.cpp b/ui/widgets/info/info.cpp
index eacab0f..cbaffa8 100644
--- a/ui/widgets/info/info.cpp
+++ b/ui/widgets/info/info.cpp
@@ -17,14 +17,85 @@
 #include "info.h"
 #include "ui_info.h"
 
-UI::Info::Info(const Shared::Info& info, QWidget* parent):
+UI::Info::Info(QWidget* parent):
     QWidget(parent),
-    m_ui(new Ui::Info())
+    m_ui(new Ui::Info()),
+    contactGeneral(nullptr),
+    contactContacts(nullptr),
+    description(nullptr),
+    overlay(new QWidget()),
+    progress(new Progress(100)),
+    progressLabel(new QLabel())
 {
     m_ui->setupUi(this);
 
-
+    initializeOverlay();
 }
 
-UI::Info::~Info()
-{}
+UI::Info::~Info() {
+    if (contactGeneral != nullptr)
+        contactGeneral->deleteLater();
+
+    if (contactContacts != nullptr)
+        contactContacts->deleteLater();
+
+    if (description != nullptr)
+        description->deleteLater();
+
+    overlay->deleteLater();
+}
+
+void UI::Info::setData(const Shared::Info& info) {
+    switch (info.type) {
+        case Shared::EntryType::contact:
+            initializeContactGeneral(info);
+            initializeContactContacts(info);
+            initializeDescription(info.editable);
+            break;
+        default:
+            break;
+    }
+}
+
+void UI::Info::initializeOverlay() {
+    QGridLayout* gr = static_cast<QGridLayout*>(layout());
+    gr->addWidget(overlay, 0, 0, 4, 1);
+    QVBoxLayout* nl = new QVBoxLayout();
+    QGraphicsOpacityEffect* opacity = new QGraphicsOpacityEffect();
+    opacity->setOpacity(0.8);
+    overlay->setLayout(nl);
+    overlay->setBackgroundRole(QPalette::Base);
+    overlay->setAutoFillBackground(true);
+    overlay->setGraphicsEffect(opacity);
+    progressLabel->setAlignment(Qt::AlignCenter);
+    QFont pf = progressLabel->font();
+    pf.setBold(true);
+    pf.setPointSize(26);
+    progressLabel->setFont(pf);
+    progressLabel->setWordWrap(true);
+    nl->addStretch();
+    nl->addWidget(progress);
+    nl->addWidget(progressLabel);
+    nl->addStretch();
+    overlay->hide();
+}
+
+
+void UI::Info::showProgress(const QString& line) {
+    progressLabel->setText(line);
+    overlay->show();
+    progress->start();
+}
+
+void UI::Info::hideProgress() {
+    overlay->hide();
+    progress->stop();
+}
+
+void UI::Info::initializeContactGeneral(const Shared::Info& info) {
+    if (contactGeneral == nullptr) {
+        contactGeneral = new ContactGeneral;
+        m_ui->tabWidget->addTab(contactGeneral, contactGeneral->title());
+    }
+    contactGeneral->setVCard(info.jid, info.vcard, info.editable);
+}
diff --git a/ui/widgets/info/info.h b/ui/widgets/info/info.h
index 6858074..126a092 100644
--- a/ui/widgets/info/info.h
+++ b/ui/widgets/info/info.h
@@ -19,10 +19,16 @@
 
 #include <QWidget>
 #include <QScopedPointer>
+#include <QGraphicsOpacityEffect>
+#include <QLabel>
 
 #include <shared/info.h>
 
+#include "ui/utils/progress.h"
+
 #include "contactgeneral.h"
+#include "contactcontacts.h"
+#include "description.h"
 
 namespace UI {
 namespace Ui
@@ -33,11 +39,28 @@ class Info;
 class Info : public QWidget {
     Q_OBJECT
 public:
-    Info(const Shared::Info& info, QWidget* parent = nullptr);
+    Info(QWidget* parent = nullptr);
     ~Info();
 
+    void setData(const Shared::Info& info);
+    void showProgress(const QString& = "");
+    void hideProgress();
+
+private:
+    void initializeContactGeneral(const Shared::Info& info);
+    void initializeContactContacts(const Shared::Info& info);
+    void initializeDescription(bool editable);
+    void initializeOverlay();
+
 private:
     QScopedPointer<Ui::Info> m_ui;
+    ContactGeneral* contactGeneral;
+    ContactContacts* contactContacts;
+    Description* description;
+    QWidget* overlay;
+    Progress* progress;
+    QLabel* progressLabel;
+
 };
 
 }
diff --git a/ui/widgets/info/info.ui b/ui/widgets/info/info.ui
index f600d8b..ed2d42e 100644
--- a/ui/widgets/info/info.ui
+++ b/ui/widgets/info/info.ui
@@ -10,7 +10,23 @@
     <height>300</height>
    </rect>
   </property>
-
+<layout class="QGridLayout" name="gridLayout_4">
+   <property name="leftMargin">
+    <number>0</number>
+   </property>
+   <property name="topMargin">
+    <number>0</number>
+   </property>
+   <property name="rightMargin">
+    <number>0</number>
+   </property>
+   <property name="bottomMargin">
+    <number>0</number>
+   </property>
+   <property name="spacing">
+    <number>0</number>
+   </property>
+   <item row="0" column="0">
     <layout class="QVBoxLayout" name="verticalLayout">
      <property name="leftMargin">
       <number>6</number>
@@ -76,49 +92,7 @@
        <property name="tabBarAutoHide">
         <bool>false</bool>
        </property>
-       <widget class="QWidget" name="Description">
-        <attribute name="title">
-         <string>Description</string>
-        </attribute>
-        <layout class="QGridLayout" name="gridLayout">
-         <property name="leftMargin">
-          <number>0</number>
-         </property>
-         <property name="topMargin">
-          <number>6</number>
-         </property>
-         <property name="rightMargin">
-          <number>0</number>
-         </property>
-         <property name="bottomMargin">
-          <number>0</number>
-         </property>
-         <property name="horizontalSpacing">
-          <number>6</number>
-         </property>
-         <item row="0" column="0">
-          <widget class="QLabel" name="descriptionHeading">
-           <property name="styleSheet">
-            <string notr="true">font: 600 24pt ;</string>
-           </property>
-           <property name="text">
-            <string>Description</string>
-           </property>
-          </widget>
-         </item>
-         <item row="1" column="0">
-          <widget class="QTextEdit" name="description">
-           <property name="frameShape">
-            <enum>QFrame::StyledPanel</enum>
-           </property>
-           <property name="textInteractionFlags">
-            <set>Qt::LinksAccessibleByMouse|Qt::TextEditable|Qt::TextEditorInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
-           </property>
-          </widget>
-         </item>
-        </layout>
-       </widget>
-      </widget>
+ </widget>
      </item>
      <item>
       <widget class="QDialogButtonBox" name="buttonBox">
@@ -131,6 +105,8 @@
       </widget>
      </item>
     </layout>
+  </item>
+</layout>
  </widget>
  <resources/>
  <connections/>

From bf11d8a74e1d746aa8b908fda7f2ffc4b61e3ef0 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Fri, 3 Feb 2023 21:43:13 +0300
Subject: [PATCH 230/281] keeping with the refactoring

---
 ui/models/info/emails.cpp           |  16 +++
 ui/models/info/emails.h             |   1 +
 ui/models/info/phones.cpp           |  16 +++
 ui/models/info/phones.h             |   5 +-
 ui/widgets/info/contactcontacts.cpp | 213 +++++++++++++++++++++++++++-
 ui/widgets/info/contactcontacts.h   |  37 ++++-
 ui/widgets/info/contactgeneral.cpp  |  15 ++
 ui/widgets/info/contactgeneral.h    |   1 +
 8 files changed, 298 insertions(+), 6 deletions(-)

diff --git a/ui/models/info/emails.cpp b/ui/models/info/emails.cpp
index 6c902bf..4bb8a17 100644
--- a/ui/models/info/emails.cpp
+++ b/ui/models/info/emails.cpp
@@ -89,6 +89,22 @@ Qt::ItemFlags Models::EMails::flags(const QModelIndex& index) const {
     return  f;
 }
 
+bool Models::EMails::setEditable(bool editable) {
+    if (edit != editable) {
+        edit = editable;
+
+        if (deque.size() > 0) {
+            int lastRow = deque.size() - 1;
+            QModelIndex begin = createIndex(0, 0, &(deque[0]));
+            QModelIndex end = createIndex(lastRow, columnCount() - 1, &(deque[lastRow]));
+            emit dataChanged(begin, end);
+        }
+        return true;
+    }
+    return false;
+}
+
+
 bool Models::EMails::setData(const QModelIndex& index, const QVariant& value, int role) {
     if (role == Qt::EditRole && checkIndex(index)) {
         Shared::VCard::Email& item = deque[index.row()];
diff --git a/ui/models/info/emails.h b/ui/models/info/emails.h
index bb05a5c..f07d238 100644
--- a/ui/models/info/emails.h
+++ b/ui/models/info/emails.h
@@ -33,6 +33,7 @@ class EMails : public QAbstractTableModel {
 public:
     EMails(bool edit = false, QObject *parent = nullptr);
     
+    bool setEditable(bool editable);
     int rowCount(const QModelIndex& parent = QModelIndex()) const override;
     int columnCount(const QModelIndex& parent = QModelIndex()) const override;
     QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
diff --git a/ui/models/info/phones.cpp b/ui/models/info/phones.cpp
index 7c3b04f..99c52c2 100644
--- a/ui/models/info/phones.cpp
+++ b/ui/models/info/phones.cpp
@@ -107,6 +107,22 @@ Qt::ItemFlags Models::Phones::flags(const QModelIndex& index) const {
     return  f;
 }
 
+bool Models::Phones::setEditable(bool editable) {
+    if (edit != editable) {
+        edit = editable;
+
+        if (deque.size() > 0) {
+            int lastRow = deque.size() - 1;
+            QModelIndex begin = createIndex(0, 0, &(deque[0]));
+            QModelIndex end = createIndex(lastRow, columnCount() - 1, &(deque[lastRow]));
+            emit dataChanged(begin, end);
+        }
+        return true;
+    }
+    return false;
+}
+
+
 bool Models::Phones::dropPrefered() {
     bool dropped = false;
     int i = 0;
diff --git a/ui/models/info/phones.h b/ui/models/info/phones.h
index dec27d9..aea03ed 100644
--- a/ui/models/info/phones.h
+++ b/ui/models/info/phones.h
@@ -30,9 +30,10 @@ class Phones : public QAbstractTableModel {
 public:
     Phones(bool edit = false, QObject *parent = nullptr);
     
+    bool setEditable(bool editable);
     QVariant data(const QModelIndex& index, int role) const override;
-    int columnCount(const QModelIndex& parent) const override;
-    int rowCount(const QModelIndex& parent) const override;
+    int columnCount(const QModelIndex& parent = QModelIndex()) const override;
+    int rowCount(const QModelIndex& parent = QModelIndex()) const override;
     bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
     Qt::ItemFlags flags(const QModelIndex &index) const override;
     bool isPreferred(quint32 row) const;
diff --git a/ui/widgets/info/contactcontacts.cpp b/ui/widgets/info/contactcontacts.cpp
index 620d235..1196aa9 100644
--- a/ui/widgets/info/contactcontacts.cpp
+++ b/ui/widgets/info/contactcontacts.cpp
@@ -17,15 +17,222 @@
 #include "contactcontacts.h"
 #include "ui_contactcontacts.h"
 
-UI::ContactContacts::ContactContacts(const QString& jid, QWidget* parent):
+UI::ContactContacts::ContactContacts(QWidget* parent):
     QWidget(parent),
-    m_ui(new Ui::ContactContacts)
+    m_ui(new Ui::ContactContacts),
+    contextMenu(new QMenu()),
+    emails(),
+    phones(),
+    roleDelegate(new ComboboxDelegate()),
+    phoneTypeDelegate(new ComboboxDelegate()),
+    editable(false)
 {
     m_ui->setupUi(this);
 
-    m_ui->jabberID->setText(jid);
     m_ui->jabberID->setReadOnly(true);
+
+    initializeDelegates();
+    initializeViews();
 }
 
 UI::ContactContacts::~ContactContacts() {
+    contextMenu->deleteLater();
+}
+
+void UI::ContactContacts::setVCard(const QString& jid, const Shared::VCard& card, bool p_editable) {
+    editable = p_editable;
+    m_ui->jabberID->setText(jid);
+    m_ui->url->setText(card.getUrl());
+    m_ui->url->setReadOnly(!p_editable);
+
+    emails.setEditable(editable);
+    phones.setEditable(editable);
+    emails.setEmails(card.getEmails());
+    phones.setPhones(card.getPhones());
+}
+
+void UI::ContactContacts::initializeDelegates() {
+    roleDelegate->addEntry(QCoreApplication::translate("Global", Shared::VCard::Email::roleNames[0].toStdString().c_str()));
+    roleDelegate->addEntry(QCoreApplication::translate("Global", Shared::VCard::Email::roleNames[1].toStdString().c_str()));
+    roleDelegate->addEntry(QCoreApplication::translate("Global", Shared::VCard::Email::roleNames[2].toStdString().c_str()));
+
+    phoneTypeDelegate->addEntry(QCoreApplication::translate("Global", Shared::VCard::Phone::typeNames[0].toStdString().c_str()));
+    phoneTypeDelegate->addEntry(QCoreApplication::translate("Global", Shared::VCard::Phone::typeNames[1].toStdString().c_str()));
+    phoneTypeDelegate->addEntry(QCoreApplication::translate("Global", Shared::VCard::Phone::typeNames[2].toStdString().c_str()));
+    phoneTypeDelegate->addEntry(QCoreApplication::translate("Global", Shared::VCard::Phone::typeNames[3].toStdString().c_str()));
+    phoneTypeDelegate->addEntry(QCoreApplication::translate("Global", Shared::VCard::Phone::typeNames[4].toStdString().c_str()));
+    phoneTypeDelegate->addEntry(QCoreApplication::translate("Global", Shared::VCard::Phone::typeNames[5].toStdString().c_str()));
+    phoneTypeDelegate->addEntry(QCoreApplication::translate("Global", Shared::VCard::Phone::typeNames[6].toStdString().c_str()));
+}
+
+void UI::ContactContacts::initializeViews() {
+    m_ui->emailsView->setContextMenuPolicy(Qt::CustomContextMenu);
+    m_ui->emailsView->setModel(&emails);
+    m_ui->emailsView->setItemDelegateForColumn(1, roleDelegate);
+    m_ui->emailsView->setColumnWidth(2, 25);
+    m_ui->emailsView->horizontalHeader()->setStretchLastSection(false);
+    m_ui->emailsView->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch);
+
+    m_ui->phonesView->setContextMenuPolicy(Qt::CustomContextMenu);
+    m_ui->phonesView->setModel(&phones);
+    m_ui->phonesView->setItemDelegateForColumn(1, roleDelegate);
+    m_ui->phonesView->setItemDelegateForColumn(2, phoneTypeDelegate);
+    m_ui->phonesView->setColumnWidth(3, 25);
+    m_ui->phonesView->horizontalHeader()->setStretchLastSection(false);
+    m_ui->phonesView->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch);
+
+    connect(m_ui->phonesView, &QWidget::customContextMenuRequested, this, &UI::ContactContacts::onContextMenu);
+    connect(m_ui->emailsView, &QWidget::customContextMenuRequested, this, &UI::ContactContacts::onContextMenu);
+}
+
+void UI::ContactContacts::onContextMenu(const QPoint& point) {
+    contextMenu->clear();
+    bool hasMenu = false;
+    QAbstractItemView* snd = static_cast<QAbstractItemView*>(sender());
+    if (snd == m_ui->emailsView) {
+        hasMenu = true;
+        if (editable) {
+            QAction* add = contextMenu->addAction(Shared::icon("list-add"), tr("Add email address"));
+            connect(add, &QAction::triggered, this, &UI::ContactContacts::onAddEmail);
+
+            QItemSelectionModel* sm = m_ui->emailsView->selectionModel();
+            int selectionSize = sm->selectedRows().size();
+
+            if (selectionSize > 0) {
+                if (selectionSize == 1) {
+                    int row = sm->selectedRows().at(0).row();
+                    if (emails.isPreferred(row)) {
+                        QAction* rev = contextMenu->addAction(Shared::icon("unfavorite"), tr("Unset this email as preferred"));
+                        connect(rev, &QAction::triggered, std::bind(&Models::EMails::revertPreferred, &emails, row));
+                    } else {
+                        QAction* rev = contextMenu->addAction(Shared::icon("favorite"), tr("Set this email as preferred"));
+                        connect(rev, &QAction::triggered, std::bind(&Models::EMails::revertPreferred, &emails, row));
+                    }
+                }
+
+                QAction* del = contextMenu->addAction(Shared::icon("edit-delete"), tr("Remove selected email addresses"));
+                connect(del, &QAction::triggered, this, &UI::ContactContacts::onRemoveEmail);
+            }
+        }
+
+        QAction* cp = contextMenu->addAction(Shared::icon("edit-copy"), tr("Copy selected emails to clipboard"));
+        connect(cp, &QAction::triggered, this, &UI::ContactContacts::onCopyEmail);
+    } else if (snd == m_ui->phonesView) {
+        hasMenu = true;
+        if (editable) {
+            QAction* add = contextMenu->addAction(Shared::icon("list-add"), tr("Add phone number"));
+            connect(add, &QAction::triggered, this, &UI::ContactContacts::onAddPhone);
+
+            QItemSelectionModel* sm = m_ui->phonesView->selectionModel();
+            int selectionSize = sm->selectedRows().size();
+
+            if (selectionSize > 0) {
+                if (selectionSize == 1) {
+                    int row = sm->selectedRows().at(0).row();
+                    if (phones.isPreferred(row)) {
+                        QAction* rev = contextMenu->addAction(Shared::icon("view-media-favorite"), tr("Unset this phone as preferred"));
+                        connect(rev, &QAction::triggered, std::bind(&Models::Phones::revertPreferred, &phones, row));
+                    } else {
+                        QAction* rev = contextMenu->addAction(Shared::icon("favorite"), tr("Set this phone as preferred"));
+                        connect(rev, &QAction::triggered, std::bind(&Models::Phones::revertPreferred, &phones, row));
+                    }
+                }
+
+                QAction* del = contextMenu->addAction(Shared::icon("edit-delete"), tr("Remove selected phone numbers"));
+                connect(del, &QAction::triggered, this, &UI::ContactContacts::onRemovePhone);
+            }
+        }
+
+        QAction* cp = contextMenu->addAction(Shared::icon("edit-copy"), tr("Copy selected phones to clipboard"));
+        connect(cp, &QAction::triggered, this, &UI::ContactContacts::onCopyPhone);
+    }
+
+    if (hasMenu) {
+        contextMenu->popup(snd->viewport()->mapToGlobal(point));
+    }
+}
+
+void UI::ContactContacts::onAddEmail() {
+    QModelIndex index = emails.addNewEmptyLine();
+    m_ui->emailsView->setCurrentIndex(index);
+    m_ui->emailsView->edit(index);
+}
+
+void UI::ContactContacts::onAddAddress() {}     //TODO
+void UI::ContactContacts::onAddPhone() {
+    QModelIndex index = phones.addNewEmptyLine();
+    m_ui->phonesView->setCurrentIndex(index);
+    m_ui->phonesView->edit(index);
+}
+void UI::ContactContacts::onRemoveAddress() {}  //TODO
+void UI::ContactContacts::onRemoveEmail() {
+    QItemSelection selection(m_ui->emailsView->selectionModel()->selection());
+
+    QList<int> rows;
+    for (const QModelIndex& index : selection.indexes()) {
+        rows.append(index.row());
+    }
+
+    std::sort(rows.begin(), rows.end());
+
+    int prev = -1;
+    for (int i = rows.count() - 1; i >= 0; i -= 1) {
+        int current = rows[i];
+        if (current != prev) {
+            emails.removeLines(current, 1);
+            prev = current;
+        }
+    }
+}
+
+void UI::ContactContacts::onRemovePhone() {
+    QItemSelection selection(m_ui->phonesView->selectionModel()->selection());
+
+    QList<int> rows;
+    for (const QModelIndex& index : selection.indexes()) {
+        rows.append(index.row());
+    }
+
+    std::sort(rows.begin(), rows.end());
+
+    int prev = -1;
+    for (int i = rows.count() - 1; i >= 0; i -= 1) {
+        int current = rows[i];
+        if (current != prev) {
+            phones.removeLines(current, 1);
+            prev = current;
+        }
+    }
+}
+
+void UI::ContactContacts::onCopyEmail() {
+    QList<QModelIndex> selection(m_ui->emailsView->selectionModel()->selectedRows());
+
+    QList<QString> addrs;
+    for (const QModelIndex& index : selection) {
+        addrs.push_back(emails.getEmail(index.row()));
+    }
+
+    QString list = addrs.join("\n");
+
+    QClipboard* cb = QApplication::clipboard();
+    cb->setText(list);
+}
+
+void UI::ContactContacts::onCopyPhone() {
+    QList<QModelIndex> selection(m_ui->phonesView->selectionModel()->selectedRows());
+
+    QList<QString> phs;
+    for (const QModelIndex& index : selection) {
+        phs.push_back(phones.getPhone(index.row()));
+    }
+
+    QString list = phs.join("\n");
+
+    QClipboard* cb = QApplication::clipboard();
+    cb->setText(list);
+}
+
+QString UI::ContactContacts::title() const {
+    return m_ui->contactHeading->text();
 }
diff --git a/ui/widgets/info/contactcontacts.h b/ui/widgets/info/contactcontacts.h
index 7b26eed..d2c7e9f 100644
--- a/ui/widgets/info/contactcontacts.h
+++ b/ui/widgets/info/contactcontacts.h
@@ -19,6 +19,16 @@
 
 #include <QWidget>
 #include <QScopedPointer>
+#include <QMenu>
+#include <QPoint>
+#include <QApplication>
+#include <QClipboard>
+
+#include "shared/vcard.h"
+#include "shared/icons.h"
+#include "ui/models/info/emails.h"
+#include "ui/models/info/phones.h"
+#include "ui/utils/comboboxdelegate.h"
 
 namespace UI {
 namespace Ui
@@ -29,11 +39,36 @@ class ContactContacts;
 class ContactContacts : public QWidget {
     Q_OBJECT
 public:
-    ContactContacts(const QString& jid, QWidget* parent = nullptr);
+    ContactContacts(QWidget* parent = nullptr);
     ~ContactContacts();
 
+    void setVCard(const QString& jid, const Shared::VCard& card, bool editable = false);
+    void fillVCard(Shared::VCard& card) const;
+    QString title() const;
+
+private slots:
+    void onContextMenu(const QPoint& point);
+    void onAddAddress();
+    void onRemoveAddress();
+    void onAddEmail();
+    void onCopyEmail();
+    void onRemoveEmail();
+    void onAddPhone();
+    void onCopyPhone();
+    void onRemovePhone();
+
+private:
+    void initializeDelegates();
+    void initializeViews();
+
 private:
     QScopedPointer<Ui::ContactContacts> m_ui;
+    QMenu* contextMenu;
+    Models::EMails emails;
+    Models::Phones phones;
+    ComboboxDelegate* roleDelegate;
+    ComboboxDelegate* phoneTypeDelegate;
+    bool editable;
 };
 
 }
diff --git a/ui/widgets/info/contactgeneral.cpp b/ui/widgets/info/contactgeneral.cpp
index 57ce700..ccc6996 100644
--- a/ui/widgets/info/contactgeneral.cpp
+++ b/ui/widgets/info/contactgeneral.cpp
@@ -196,3 +196,18 @@ void UI::ContactGeneral::setVCard(const QString& jid, const Shared::VCard& card,
 
     updateAvatar();
 }
+
+void UI::ContactGeneral::fillVCard(Shared::VCard& card) const {
+    card.setFullName(m_ui->fullName->text());
+    card.setFirstName(m_ui->firstName->text());
+    card.setMiddleName(m_ui->middleName->text());
+    card.setLastName(m_ui->lastName->text());
+    card.setNickName(m_ui->nickName->text());
+    card.setBirthday(m_ui->birthday->date());
+    card.setOrgName(m_ui->organizationName->text());
+    card.setOrgUnit(m_ui->organizationDepartment->text());
+    card.setOrgRole(m_ui->organizationRole->text());
+    card.setOrgTitle(m_ui->organizationTitle->text());
+    card.setAvatarPath(currentAvatarPath);
+    card.setAvatarType(currentAvatarType);
+}
diff --git a/ui/widgets/info/contactgeneral.h b/ui/widgets/info/contactgeneral.h
index 6233c83..2817e91 100644
--- a/ui/widgets/info/contactgeneral.h
+++ b/ui/widgets/info/contactgeneral.h
@@ -45,6 +45,7 @@ public:
     ~ContactGeneral();
 
     void setVCard(const QString& jid, const Shared::VCard& card, bool editable = false);
+    void fillVCard(Shared::VCard& card) const;
     QString title() const;
 
 private:

From e4a2728ef8d5671bc2b7091010804e19e29ebbc6 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Mon, 20 Feb 2023 21:12:32 +0300
Subject: [PATCH 231/281] hopefully end of refactoring of vcard to Info widget

---
 core/account.cpp                    | 112 ++--
 core/account.h                      |   7 +-
 core/conference.cpp                 |   2 +-
 core/handlers/rosterhandler.cpp     |   4 +-
 core/handlers/vcardhandler.cpp      |  53 +-
 core/squawk.cpp                     |  14 +-
 core/squawk.h                       |   7 +-
 main/application.cpp                |   8 +-
 ui/squawk.cpp                       | 170 +++---
 ui/squawk.h                         |  17 +-
 ui/widgets/CMakeLists.txt           |   1 -
 ui/widgets/info/contactcontacts.cpp |   8 +
 ui/widgets/info/description.cpp     |  17 +
 ui/widgets/info/description.h       |   5 +
 ui/widgets/info/info.cpp            |  58 +-
 ui/widgets/info/info.h              |  14 +-
 ui/widgets/vcard/CMakeLists.txt     |   5 -
 ui/widgets/vcard/vcard.cpp          | 474 ---------------
 ui/widgets/vcard/vcard.h            | 117 ----
 ui/widgets/vcard/vcard.ui           | 892 ----------------------------
 20 files changed, 268 insertions(+), 1717 deletions(-)
 delete mode 100644 ui/widgets/vcard/CMakeLists.txt
 delete mode 100644 ui/widgets/vcard/vcard.cpp
 delete mode 100644 ui/widgets/vcard/vcard.h
 delete mode 100644 ui/widgets/vcard/vcard.ui

diff --git a/core/account.cpp b/core/account.cpp
index bb3ebea..98862a4 100644
--- a/core/account.cpp
+++ b/core/account.cpp
@@ -138,8 +138,7 @@ Account::Account(const QString& p_login, const QString& p_server, const QString&
     }
 }
 
-Account::~Account()
-{
+Account::~Account() {
     if (reconnectScheduled) {
         reconnectScheduled = false;
         reconnectTimer->stop();
@@ -166,13 +165,10 @@ Account::~Account()
     delete cm;
 }
 
-Shared::ConnectionState Core::Account::getState() const
-{
-    return state;
-}
+Shared::ConnectionState Core::Account::getState() const {
+    return state;}
 
-void Core::Account::connect()
-{
+void Core::Account::connect() {
     if (reconnectScheduled) {
         reconnectScheduled = false;
         reconnectTimer->stop();
@@ -194,16 +190,14 @@ void Core::Account::connect()
     }
 }
 
-void Core::Account::onReconnectTimer()
-{
+void Core::Account::onReconnectTimer() {
     if (reconnectScheduled) {
         reconnectScheduled = false;
         connect();
     }
 }
 
-void Core::Account::disconnect()
-{
+void Core::Account::disconnect() {
     if (reconnectScheduled) {
         reconnectScheduled = false;
         reconnectTimer->stop();
@@ -219,8 +213,7 @@ void Core::Account::disconnect()
     }
 }
 
-void Core::Account::onClientStateChange(QXmppClient::State st)
-{
+void Core::Account::onClientStateChange(QXmppClient::State st) {
     switch (st) {
         case QXmppClient::ConnectedState: {
             if (state != Shared::ConnectionState::connected) {
@@ -279,8 +272,7 @@ void Core::Account::onClientStateChange(QXmppClient::State st)
     }
 }
 
-void Core::Account::reconnect()
-{
+void Core::Account::reconnect() {
     if (!reconnectScheduled) {      //TODO define behavior if It was connection or disconnecting
         if (state == Shared::ConnectionState::connected) {
             reconnectScheduled = true;
@@ -292,8 +284,7 @@ void Core::Account::reconnect()
     }
 }
 
-Shared::Availability Core::Account::getAvailability() const
-{
+Shared::Availability Core::Account::getAvailability() const {
     if (state == Shared::ConnectionState::connected) {
         QXmppPresence::AvailableStatusType pres = presence.availableStatusType();
         return static_cast<Shared::Availability>(pres);         //they are compatible;
@@ -302,8 +293,7 @@ Shared::Availability Core::Account::getAvailability() const
     }
 }
 
-void Core::Account::setAvailability(Shared::Availability avail)
-{
+void Core::Account::setAvailability(Shared::Availability avail) {
     if (avail == Shared::Availability::offline) {
         disconnect();               //TODO not sure how to do here - changing state may cause connection or disconnection
     } else {
@@ -323,23 +313,21 @@ void Core::Account::runDiscoveryService() {
 }
 
 
-void Core::Account::onPresenceReceived(const QXmppPresence& p_presence)
-{
+void Core::Account::onPresenceReceived(const QXmppPresence& p_presence) {
     QString id = p_presence.from();
     QStringList comps = id.split("/");
     QString jid = comps.front().toLower();
     QString resource = comps.back();
     
     if (jid == getBareJid()) {
-        if (resource == getResource()) {
+        if (resource == getResource())
             emit availabilityChanged(static_cast<Shared::Availability>(p_presence.availableStatusType()));
-        }
+
         vh->handlePresenceOfMyAccountChange(p_presence);
     } else {
         RosterItem* item = rh->getRosterItem(jid);
-        if (item != 0) {
+        if (item != 0)
             item->handlePresence(p_presence);
-        }
     }
     
     switch (p_presence.type()) {
@@ -382,8 +370,7 @@ void Core::Account::onPresenceReceived(const QXmppPresence& p_presence)
     }
 }
 
-void Core::Account::onMamMessageReceived(const QString& queryId, const QXmppMessage& msg)
-{
+void Core::Account::onMamMessageReceived(const QString& queryId, const QXmppMessage& msg) {
     if (msg.id().size() > 0 && (msg.body().size() > 0 || msg.outOfBandUrl().size() > 0)) {
         std::map<QString, QString>::const_iterator itr = archiveQueries.find(queryId);
         if (itr != archiveQueries.end()) {
@@ -395,17 +382,15 @@ void Core::Account::onMamMessageReceived(const QString& queryId, const QXmppMess
             sMsg.setState(Shared::Message::State::sent);
             
             QString oId = msg.replaceId();
-            if (oId.size() > 0) {
+            if (oId.size() > 0)
                 item->correctMessageInArchive(oId, sMsg);
-            } else {
+            else
                 item->addMessageToArchive(sMsg);
-            }
         }
     }
 }
 
-void Core::Account::requestArchive(const QString& jid, int count, const QString& before)
-{
+void Core::Account::requestArchive(const QString& jid, int count, const QString& before) {
     qDebug() << "An archive request for " << jid << ", before " << before;
     RosterItem* contact = rh->getRosterItem(jid);
     
@@ -423,8 +408,7 @@ void Core::Account::requestArchive(const QString& jid, int count, const QString&
     contact->requestHistory(count, before);
 }
 
-void Core::Account::onContactNeedHistory(const QString& before, const QString& after, const QDateTime& at)
-{
+void Core::Account::onContactNeedHistory(const QString& before, const QString& after, const QDateTime& at) {
     RosterItem* contact = static_cast<RosterItem*>(sender());
 
     QString to;
@@ -468,8 +452,7 @@ void Core::Account::onContactNeedHistory(const QString& before, const QString& a
     archiveQueries.insert(std::make_pair(q, contact->jid));
 }
 
-void Core::Account::onMamResultsReceived(const QString& queryId, const QXmppResultSetReply& resultSetReply, bool complete)
-{
+void Core::Account::onMamResultsReceived(const QString& queryId, const QXmppResultSetReply& resultSetReply, bool complete) {
     std::map<QString, QString>::const_iterator itr = archiveQueries.find(queryId);
     if (itr != archiveQueries.end()) {
         QString jid = itr->second;
@@ -484,14 +467,12 @@ void Core::Account::onMamResultsReceived(const QString& queryId, const QXmppResu
     }
 }
 
-void Core::Account::onMamLog(QXmppLogger::MessageType type, const QString& msg)
-{
+void Core::Account::onMamLog(QXmppLogger::MessageType type, const QString& msg) {
     qDebug() << "MAM MESSAGE LOG::";
     qDebug() << msg;
 }
 
-void Core::Account::onClientError(QXmppClient::Error err)
-{
+void Core::Account::onClientError(QXmppClient::Error err) {
     qDebug() << "Error";
     QString errorText;
     QString errorType;
@@ -601,22 +582,18 @@ void Core::Account::onClientError(QXmppClient::Error err)
     emit error(errorText);
 }
 
-void Core::Account::subscribeToContact(const QString& jid, const QString& reason)
-{
-    if (state == Shared::ConnectionState::connected) {
+void Core::Account::subscribeToContact(const QString& jid, const QString& reason) {
+    if (state == Shared::ConnectionState::connected)
         rm->subscribe(jid, reason);
-    } else {
+    else
         qDebug() << "An attempt to subscribe account " << name << " to contact " << jid << " but the account is not in the connected state, skipping";
-    }
 }
 
-void Core::Account::unsubscribeFromContact(const QString& jid, const QString& reason)
-{
-    if (state == Shared::ConnectionState::connected) {
+void Core::Account::unsubscribeFromContact(const QString& jid, const QString& reason) {
+    if (state == Shared::ConnectionState::connected)
         rm->unsubscribe(jid, reason);
-    } else {
+    else
         qDebug() << "An attempt to unsubscribe account " << name << " from contact " << jid << " but the account is not in the connected state, skipping";
-    }
 }
 
 void Core::Account::removeContactRequest(const QString& jid) {
@@ -625,8 +602,7 @@ void Core::Account::removeContactRequest(const QString& jid) {
 void Core::Account::addContactRequest(const QString& jid, const QString& name, const QSet<QString>& groups) {
     rh->addContactRequest(jid, name, groups);}
 
-void Core::Account::setRoomAutoJoin(const QString& jid, bool joined)
-{
+void Core::Account::setRoomAutoJoin(const QString& jid, bool joined) {
     Conference* conf = rh->getConference(jid);
     if (conf == 0) {
         qDebug() << "An attempt to set auto join to the non existing room" << jid << "of the account" << getName() << ", skipping";
@@ -636,8 +612,7 @@ void Core::Account::setRoomAutoJoin(const QString& jid, bool joined)
     conf->setAutoJoin(joined);
 }
 
-void Core::Account::setRoomJoined(const QString& jid, bool joined)
-{
+void Core::Account::setRoomJoined(const QString& jid, bool joined) {
     Conference* conf = rh->getConference(jid);
     if (conf == 0) {
         qDebug() << "An attempt to set joined to the non existing room" << jid << "of the account" << getName() << ", skipping";
@@ -657,16 +632,14 @@ void Core::Account::discoverInfo(const QString& address, const QString& node) {
     }
 }
 
-void Core::Account::setPepSupport(Shared::Support support)
-{
+void Core::Account::setPepSupport(Shared::Support support) {
     if (support != pepSupport) {
         pepSupport = support;
         emit pepSupportChanged(pepSupport);
     }
 }
 
-void Core::Account::handleDisconnection()
-{
+void Core::Account::handleDisconnection() {
     setPepSupport(Shared::Support::unknown);
     cm->setCarbonsEnabled(false);
     rh->handleOffline();
@@ -674,14 +647,13 @@ void Core::Account::handleDisconnection()
     archiveQueries.clear();
 }
 
-void Core::Account::onContactHistoryResponse(const std::list<Shared::Message>& list, bool last)
-{
+void Core::Account::onContactHistoryResponse(const std::list<Shared::Message>& list, bool last) {
     RosterItem* contact = static_cast<RosterItem*>(sender());
     
     qDebug() << "Collected history for contact " << contact->jid << list.size() << "elements";
-    if (last) {
+    if (last)
         qDebug() << "The response contains the first accounted message";
-    }
+
     emit responseArchive(contact->jid, list, last);
 }
 
@@ -754,11 +726,17 @@ void Core::Account::resendMessage(const QString& jid, const QString& id) {
 void Core::Account::replaceMessage(const QString& originalId, const Shared::Message& data) {
     mh->sendMessage(data, false, originalId);}
 
-void Core::Account::requestVCard(const QString& jid) {
-    vh->requestVCard(jid);}
+void Core::Account::requestInfo(const QString& jid) {
+    //TODO switch case of what kind of entity this info request is about
+    //right now it could be only about myself or some contact
+    vh->requestVCard(jid);
+}
 
-void Core::Account::uploadVCard(const Shared::VCard& card) {
-    vh->uploadVCard(card);}
+void Core::Account::updateInfo(const Shared::Info& info) {
+    //TODO switch case of what kind of entity this info update is about
+    //right now it could be only about myself
+    vh->uploadVCard(info.vcard);
+}
 
 QString Core::Account::getAvatarPath() const {
     return vh->getAvatarPath();}
diff --git a/core/account.h b/core/account.h
index 393b6e6..598a06e 100644
--- a/core/account.h
+++ b/core/account.h
@@ -46,6 +46,7 @@
 
 #include <shared/shared.h>
 #include <shared/identity.h>
+#include <shared/info.h>
 #include "contact.h"
 #include "conference.h"
 #include <core/components/networkaccess.h>
@@ -126,7 +127,7 @@ public:
     void setRoomAutoJoin(const QString& jid, bool joined);
     void removeRoomRequest(const QString& jid);
     void addRoomRequest(const QString& jid, const QString& nick, const QString& password, bool autoJoin);
-    void uploadVCard(const Shared::VCard& card);
+    void updateInfo(const Shared::Info& info);
     void resendMessage(const QString& jid, const QString& id);
     void replaceMessage(const QString& originalId, const Shared::Message& data);
     void invalidatePassword();
@@ -137,7 +138,7 @@ public slots:
     void connect();
     void disconnect();
     void reconnect();
-    void requestVCard(const QString& jid);
+    void requestInfo(const QString& jid);
     
 signals:
     void changed(const QMap<QString, QVariant>& data);
@@ -161,7 +162,7 @@ signals:
     void addRoomParticipant(const QString& jid, const QString& nickName, const QMap<QString, QVariant>& data);
     void changeRoomParticipant(const QString& jid, const QString& nickName, const QMap<QString, QVariant>& data);
     void removeRoomParticipant(const QString& jid, const QString& nickName);
-    void receivedVCard(const QString& jid, const Shared::VCard& card);
+    void infoReady(const Shared::Info& info);
     void uploadFile(const QFileInfo& file, const QUrl& set, const QUrl& get, QMap<QString, QString> headers);
     void uploadFileError(const QString& jid, const QString& messageId, const QString& error);
     void needPassword();
diff --git a/core/conference.cpp b/core/conference.cpp
index 55280e2..a984f41 100644
--- a/core/conference.cpp
+++ b/core/conference.cpp
@@ -170,7 +170,7 @@ void Core::Conference::onRoomParticipantAdded(const QString& p_name)
         } else {
             cData.insert("avatarState", static_cast<uint>(Shared::Avatar::empty));
             cData.insert("avatarPath", "");
-            requestVCard(p_name);
+            emit requestVCard(p_name);
         }
         
         emit addParticipant(resource, cData);
diff --git a/core/handlers/rosterhandler.cpp b/core/handlers/rosterhandler.cpp
index 8c65c63..0027514 100644
--- a/core/handlers/rosterhandler.cpp
+++ b/core/handlers/rosterhandler.cpp
@@ -156,7 +156,7 @@ void Core::RosterHandler::careAboutAvatar(Core::RosterItem* item, QMap<QString,
     } else {
         data.insert("avatarState", QVariant::fromValue(Shared::Avatar::empty));
         data.insert("avatarPath", "");
-        acc->requestVCard(item->jid);
+        acc->vh->requestVCard(item->jid);
     }
 }
 
@@ -197,7 +197,7 @@ void Core::RosterHandler::handleNewRosterItem(Core::RosterItem* contact)
     connect(contact, &RosterItem::historyResponse, this->acc, &Account::onContactHistoryResponse);
     connect(contact, &RosterItem::nameChanged, this, &RosterHandler::onContactNameChanged);
     connect(contact, &RosterItem::avatarChanged, this, &RosterHandler::onContactAvatarChanged);
-    connect(contact, &RosterItem::requestVCard, this->acc, &Account::requestVCard);
+    connect(contact, &RosterItem::requestVCard, this->acc->vh, &VCardHandler::requestVCard);
 }
 
 void Core::RosterHandler::handleNewContact(Core::Contact* contact)
diff --git a/core/handlers/vcardhandler.cpp b/core/handlers/vcardhandler.cpp
index bde0e64..c9e7170 100644
--- a/core/handlers/vcardhandler.cpp
+++ b/core/handlers/vcardhandler.cpp
@@ -80,9 +80,7 @@ void Core::VCardHandler::initialize() {
     }
 }
 
-
-void Core::VCardHandler::onVCardReceived(const QXmppVCardIq& card)
-{
+void Core::VCardHandler::onVCardReceived(const QXmppVCardIq& card) {
     QString id = card.from();
     QStringList comps = id.split("/");
     QString jid = comps.front().toLower();
@@ -102,13 +100,13 @@ void Core::VCardHandler::onVCardReceived(const QXmppVCardIq& card)
         return;
     }
 
-    Shared::VCard vCard = item->handleResponseVCard(card, resource);
+    Shared::Info info(jid, Shared::EntryType::contact);
+    info.vcard = item->handleResponseVCard(card, resource);
 
-    emit acc->receivedVCard(jid, vCard);
+    emit acc->infoReady(info);
 }
 
-void Core::VCardHandler::onOwnVCardReceived(const QXmppVCardIq& card)
-{
+void Core::VCardHandler::onOwnVCardReceived(const QXmppVCardIq& card) {
     QByteArray ava = card.photo();
     bool avaChanged = false;
     QString path = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/" + acc->name + "/";
@@ -189,32 +187,30 @@ void Core::VCardHandler::onOwnVCardReceived(const QXmppVCardIq& card)
 
     ownVCardRequestInProgress = false;
 
-    Shared::VCard vCard;
-    initializeVCard(vCard, card);
+    Shared::Info info(acc->getBareJid(), Shared::EntryType::contact, true);
+    initializeVCard(info.vcard, card);
 
     if (avatarType.size() > 0) {
-        vCard.setAvatarType(Shared::Avatar::valid);
-        vCard.setAvatarPath(path + "avatar." + avatarType);
+        info.vcard.setAvatarType(Shared::Avatar::valid);
+        info.vcard.setAvatarPath(path + "avatar." + avatarType);
     } else {
-        vCard.setAvatarType(Shared::Avatar::empty);
+        info.vcard.setAvatarType(Shared::Avatar::empty);
     }
 
-    emit acc->receivedVCard(acc->getBareJid(), vCard);
+    emit acc->infoReady(info);
 }
 
-void Core::VCardHandler::handleOffline()
-{
+void Core::VCardHandler::handleOffline() {
     pendingVCardRequests.clear();
-    Shared::VCard vCard;                    //just to show, that there is now more pending request
     for (const QString& jid : pendingVCardRequests) {
-        emit acc->receivedVCard(jid, vCard);     //need to show it better in the future, like with an error
+        Shared::Info info(jid, Shared::EntryType::contact);
+        emit acc->infoReady(info);     //need to show it better in the future, like with an error
     }
     pendingVCardRequests.clear();
     ownVCardRequestInProgress = false;
 }
 
-void Core::VCardHandler::requestVCard(const QString& jid)
-{
+void Core::VCardHandler::requestVCard(const QString& jid) {
     if (pendingVCardRequests.find(jid) == pendingVCardRequests.end()) {
         qDebug() << "requesting vCard" << jid;
         if (jid == acc->getBareJid()) {
@@ -229,8 +225,7 @@ void Core::VCardHandler::requestVCard(const QString& jid)
     }
 }
 
-void Core::VCardHandler::handlePresenceOfMyAccountChange(const QXmppPresence& p_presence)
-{
+void Core::VCardHandler::handlePresenceOfMyAccountChange(const QXmppPresence& p_presence) {
     if (!ownVCardRequestInProgress) {
         switch (p_presence.vCardUpdateType()) {
             case QXmppPresence::VCardUpdateNone:            //this presence has nothing to do with photo
@@ -253,8 +248,7 @@ void Core::VCardHandler::handlePresenceOfMyAccountChange(const QXmppPresence& p_
     }
 }
 
-void Core::VCardHandler::uploadVCard(const Shared::VCard& card)
-{
+void Core::VCardHandler::uploadVCard(const Shared::VCard& card) {
     QXmppVCardIq iq;
     initializeQXmppVCard(iq, card);
 
@@ -283,11 +277,10 @@ void Core::VCardHandler::uploadVCard(const Shared::VCard& card)
         } else {
             if (avatarType.size() > 0) {
                 QFile oA(oldPath);
-                if (!oA.open(QFile::ReadOnly)) {
+                if (!oA.open(QFile::ReadOnly))
                     qDebug() << "Couldn't read old avatar of account" << acc->name << ", uploading empty avatar";
-                } else {
+                else
                     data = oA.readAll();
-                }
             }
         }
 
@@ -303,11 +296,9 @@ void Core::VCardHandler::uploadVCard(const Shared::VCard& card)
     onOwnVCardReceived(iq);
 }
 
-QString Core::VCardHandler::getAvatarPath() const
-{
-    if (avatarType.size() == 0) {
+QString Core::VCardHandler::getAvatarPath() const {
+    if (avatarType.size() == 0)
         return "";
-    } else {
+    else
         return QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/" + acc->name + "/" + "avatar." + avatarType;
-    }
 }
diff --git a/core/squawk.cpp b/core/squawk.cpp
index 6600fcb..33f2bf5 100644
--- a/core/squawk.cpp
+++ b/core/squawk.cpp
@@ -180,7 +180,7 @@ void Core::Squawk::addAccount(
     connect(acc, &Account::changeRoomParticipant, this, &Squawk::onAccountChangeRoomPresence);
     connect(acc, &Account::removeRoomParticipant, this, &Squawk::onAccountRemoveRoomPresence);
     
-    connect(acc, &Account::receivedVCard, this, &Squawk::responseVCard);
+    connect(acc, &Account::infoReady, this, &Squawk::responseInfo);
     
     connect(acc, &Account::uploadFileError, this, &Squawk::onAccountUploadFileError);
 
@@ -717,24 +717,24 @@ void Core::Squawk::renameContactRequest(const QString& account, const QString& j
     itr->second->renameContactRequest(jid, newName);
 }
 
-void Core::Squawk::requestVCard(const QString& account, const QString& jid)
+void Core::Squawk::requestInfo(const QString& account, const QString& jid)
 {
     AccountsMap::const_iterator itr = amap.find(account);
     if (itr == amap.end()) {
-        qDebug() << "An attempt to request" << jid << "vcard of non existing account" << account << ", skipping";
+        qDebug() << "An attempt to request info about" << jid << "of non existing account" << account << ", skipping";
         return;
     }
-    itr->second->requestVCard(jid);
+    itr->second->requestInfo(jid);
 }
 
-void Core::Squawk::uploadVCard(const QString& account, const Shared::VCard& card)
+void Core::Squawk::updateInfo(const QString& account, const Shared::Info& info)
 {
     AccountsMap::const_iterator itr = amap.find(account);
     if (itr == amap.end()) {
-        qDebug() << "An attempt to upload vcard to non existing account" << account << ", skipping";
+        qDebug() << "An attempt to update info to non existing account" << account << ", skipping";
         return;
     }
-    itr->second->uploadVCard(card);
+    itr->second->updateInfo(info);
 }
 
 void Core::Squawk::readSettings()
diff --git a/core/squawk.h b/core/squawk.h
index da4aa7e..eebe917 100644
--- a/core/squawk.h
+++ b/core/squawk.h
@@ -31,6 +31,7 @@
 #include "shared/enums.h"
 #include "shared/message.h"
 #include "shared/global.h"
+#include "shared/info.h"
 #include "shared/clientinfo.h"
 #include "external/simpleCrypt/simplecrypt.h"
 
@@ -87,7 +88,7 @@ signals:
     void fileDownloadComplete(const std::list<Shared::MessageInfo> msgs, const QString& path);
     void fileUploadComplete(const std::list<Shared::MessageInfo> msgs, const QString& url, const QString& path);
     
-    void responseVCard(const QString& jid, const Shared::VCard& card);
+    void responseInfo(const Shared::Info& info);
     void changeMessage(const QString& account, const QString& jid, const QString& id, const QMap<QString, QVariant>& data);
     void requestPassword(const QString& account, bool authernticationError);
     
@@ -123,8 +124,8 @@ public slots:
     
     void fileDownloadRequest(const QString& url);
     
-    void requestVCard(const QString& account, const QString& jid);
-    void uploadVCard(const QString& account, const Shared::VCard& card);
+    void requestInfo(const QString& account, const QString& jid);
+    void updateInfo(const QString& account, const Shared::Info& info);
     void responsePassword(const QString& account, const QString& password);
     void onLocalPathInvalid(const QString& path);
     void changeDownloadsPath(const QString& path);
diff --git a/main/application.cpp b/main/application.cpp
index 3942e53..edf9fd7 100644
--- a/main/application.cpp
+++ b/main/application.cpp
@@ -188,11 +188,11 @@ void Application::createMainWindow()
         connect(squawk, &Squawk::addContactToGroupRequest, core, &Core::Squawk::addContactToGroupRequest);
         connect(squawk, &Squawk::removeContactFromGroupRequest, core, &Core::Squawk::removeContactFromGroupRequest);
         connect(squawk, &Squawk::renameContactRequest, core, &Core::Squawk::renameContactRequest);
-        connect(squawk, &Squawk::requestVCard, core, &Core::Squawk::requestVCard);
-        connect(squawk, &Squawk::uploadVCard, core, &Core::Squawk::uploadVCard);
+        connect(squawk, &Squawk::requestInfo, core, &Core::Squawk::requestInfo);
+        connect(squawk, &Squawk::updateInfo, core, &Core::Squawk::updateInfo);
         connect(squawk, &Squawk::changeDownloadsPath, core, &Core::Squawk::changeDownloadsPath);
 
-        connect(core, &Core::Squawk::responseVCard, squawk, &Squawk::responseVCard);
+        connect(core, &Core::Squawk::responseInfo, squawk, &Squawk::responseInfo);
 
         dialogueQueue.setParentWidnow(squawk);
         squawk->stateChanged(availability);
@@ -217,7 +217,7 @@ void Application::onSquawkClosing()
     dialogueQueue.setParentWidnow(nullptr);
 
     if (!nowQuitting) {
-        disconnect(core, &Core::Squawk::responseVCard, squawk, &Squawk::responseVCard);
+        disconnect(core, &Core::Squawk::responseInfo, squawk, &Squawk::responseInfo);
     }
 
     destroyingSquawk = true;
diff --git a/ui/squawk.cpp b/ui/squawk.cpp
index c086e10..9702357 100644
--- a/ui/squawk.cpp
+++ b/ui/squawk.cpp
@@ -29,7 +29,7 @@ Squawk::Squawk(Models::Roster& p_rosterModel, QWidget *parent) :
     about(nullptr),
     rosterModel(p_rosterModel),
     contextMenu(new QMenu()),
-    vCards(),
+    infoWidgets(),
     currentConversation(nullptr),
     restoreSelection(),
     needToRestore(false)
@@ -96,8 +96,7 @@ Squawk::~Squawk() {
     delete contextMenu;
 }
 
-void Squawk::onAccounts()
-{
+void Squawk::onAccounts() {
     if (accounts == nullptr) {
         accounts = new Accounts(rosterModel.accountsModel);
         accounts->setAttribute(Qt::WA_DeleteOnClose);
@@ -116,8 +115,7 @@ void Squawk::onAccounts()
     }
 }
 
-void Squawk::onPreferences()
-{
+void Squawk::onPreferences() {
     if (preferences == nullptr) {
         preferences = new Settings();
         preferences->setAttribute(Qt::WA_DeleteOnClose);
@@ -133,8 +131,7 @@ void Squawk::onPreferences()
     }
 }
 
-void Squawk::onAccountsChanged()
-{
+void Squawk::onAccountsChanged() {
     unsigned int size = rosterModel.accountsModel->activeSize();
     if (size > 0) {
         m_ui->actionAddContact->setEnabled(true);
@@ -145,8 +142,7 @@ void Squawk::onAccountsChanged()
     }
 }
 
-void Squawk::onNewContact()
-{
+void Squawk::onNewContact() {
     NewContact* nc = new NewContact(rosterModel.accountsModel, this);
     
     connect(nc, &NewContact::accepted, this, &Squawk::onNewContactAccepted);
@@ -155,8 +151,7 @@ void Squawk::onNewContact()
     nc->exec();
 }
 
-void Squawk::onNewConference()
-{
+void Squawk::onNewConference() {
     JoinConference* jc = new JoinConference(rosterModel.accountsModel, this);
     
     connect(jc, &JoinConference::accepted, this, &Squawk::onJoinConferenceAccepted);
@@ -165,8 +160,7 @@ void Squawk::onNewConference()
     jc->exec();
 }
 
-void Squawk::onNewContactAccepted()
-{
+void Squawk::onNewContactAccepted() {
     NewContact* nc = static_cast<NewContact*>(sender());
     NewContact::Data value = nc->value();
     
@@ -175,8 +169,7 @@ void Squawk::onNewContactAccepted()
     nc->deleteLater();
 }
 
-void Squawk::onJoinConferenceAccepted()
-{
+void Squawk::onJoinConferenceAccepted() {
     JoinConference* jc = static_cast<JoinConference*>(sender());
     JoinConference::Data value = jc->value();
     
@@ -185,23 +178,20 @@ void Squawk::onJoinConferenceAccepted()
     jc->deleteLater();
 }
 
-void Squawk::closeEvent(QCloseEvent* event)
-{
-    if (accounts != nullptr) {
+void Squawk::closeEvent(QCloseEvent* event) {
+    if (accounts != nullptr)
         accounts->close();
-    }
-    if (preferences != nullptr) {
+    if (preferences != nullptr)
         preferences->close();
-    }
-    if (about != nullptr) {
+    if (about != nullptr)
         about->close();
-    }
+
     
-    for (std::map<QString, VCard*>::const_iterator itr = vCards.begin(), end = vCards.end(); itr != end; ++itr) {
-        disconnect(itr->second, &VCard::destroyed, this, &Squawk::onVCardClosed);
-        itr->second->close();
+    for (const std::pair<const QString, UI::Info*>& pair : infoWidgets) {
+        disconnect(pair.second, &UI::Info::destroyed, this, &Squawk::onInfoClosed);
+        pair.second->close();
     }
-    vCards.clear();
+    infoWidgets.clear();
     writeSettings();
     emit closing();;
 
@@ -217,8 +207,7 @@ void Squawk::onPreferencesClosed() {
 void Squawk::onAboutSquawkClosed() {
     about = nullptr;}
 
-void Squawk::onComboboxActivated(int index)
-{
+void Squawk::onComboboxActivated(int index) {
     Shared::Availability av = Shared::Global::fromInt<Shared::Availability>(index);
     emit changeState(av);
 }
@@ -229,8 +218,7 @@ void Squawk::expand(const QModelIndex& index) {
 void Squawk::stateChanged(Shared::Availability state) {
     m_ui->comboBox->setCurrentIndex(static_cast<int>(state));}
 
-void Squawk::onRosterItemDoubleClicked(const QModelIndex& item)
-{
+void Squawk::onRosterItemDoubleClicked(const QModelIndex& item) {
     if (item.isValid()) {
         Models::Item* node = static_cast<Models::Item*>(item.internalPointer());
         if (node->type == Models::Item::reference) {
@@ -258,8 +246,7 @@ void Squawk::onRosterItemDoubleClicked(const QModelIndex& item)
     }
 }
 
-void Squawk::closeCurrentConversation()
-{
+void Squawk::closeCurrentConversation() {
     if (currentConversation != nullptr) {
         currentConversation->deleteLater();
         currentConversation = nullptr;
@@ -267,8 +254,7 @@ void Squawk::closeCurrentConversation()
     }
 }
 
-void Squawk::onRosterContextMenu(const QPoint& point)
-{
+void Squawk::onRosterContextMenu(const QPoint& point) {
     QModelIndex index = m_ui->roster->indexAt(point);
     if (index.isValid()) {
         Models::Item* item = static_cast<Models::Item*>(index.internalPointer());
@@ -292,9 +278,9 @@ void Squawk::onRosterContextMenu(const QPoint& point)
                     connect(con, &QAction::triggered, std::bind(&Squawk::connectAccount, this, name));
                 }
                 
-                QAction* card = contextMenu->addAction(Shared::icon("user-properties"), tr("VCard"));
-                card->setEnabled(active);
-                connect(card, &QAction::triggered, std::bind(&Squawk::onActivateVCard, this, name, acc->getBareJid(), true));
+                QAction* info = contextMenu->addAction(Shared::icon("user-properties"), tr("Info"));
+                info->setEnabled(active);
+                connect(info, &QAction::triggered, std::bind(&Squawk::onActivateInfo, this, name, acc->getBareJid()));
                 
                 QAction* remove = contextMenu->addAction(Shared::icon("edit-delete"), tr("Remove"));
                 connect(remove, &QAction::triggered, std::bind(&Squawk::removeAccountRequest, this, name));
@@ -379,9 +365,9 @@ void Squawk::onRosterContextMenu(const QPoint& point)
                 });
                 
                 
-                QAction* card = contextMenu->addAction(Shared::icon("user-properties"), tr("VCard"));
-                card->setEnabled(active);
-                connect(card, &QAction::triggered, std::bind(&Squawk::onActivateVCard, this, id.account, id.name, false));
+                QAction* info = contextMenu->addAction(Shared::icon("user-properties"), tr("Info"));
+                info->setEnabled(active);
+                connect(info, &QAction::triggered, std::bind(&Squawk::onActivateInfo, this, id.account, id.name));
                 
                 QAction* remove = contextMenu->addAction(Shared::icon("edit-delete"), tr("Remove"));
                 remove->setEnabled(active);
@@ -425,65 +411,61 @@ void Squawk::onRosterContextMenu(const QPoint& point)
     }    
 }
 
-void Squawk::responseVCard(const QString& jid, const Shared::VCard& card)
-{
-    std::map<QString, VCard*>::const_iterator itr = vCards.find(jid);
-    if (itr != vCards.end()) {
-        itr->second->setVCard(card);
+void Squawk::responseInfo(const Shared::Info& info) {
+    std::map<QString, UI::Info*>::const_iterator itr = infoWidgets.find(info.jid);
+    if (itr != infoWidgets.end()) {
+        itr->second->setData(info);
         itr->second->hideProgress();
     }
 }
 
-void Squawk::onVCardClosed()
-{
-    VCard* vCard = static_cast<VCard*>(sender());
+void Squawk::onInfoClosed() {
+    UI::Info* info = static_cast<UI::Info*>(sender());
     
-    std::map<QString, VCard*>::const_iterator itr = vCards.find(vCard->getJid());
-    if (itr == vCards.end()) {
-        qDebug() << "VCard has been closed but can not be found among other opened vCards, application is most probably going to crash";
+    std::map<QString, UI::Info*>::const_iterator itr = infoWidgets.find(info->jid);
+    if (itr == infoWidgets.end()) {
+        qDebug() << "Info widget has been closed but can not be found among other opened vCards, application is most probably going to crash";
         return;
     }
-    vCards.erase(itr);
+    infoWidgets.erase(itr);
 }
 
-void Squawk::onActivateVCard(const QString& account, const QString& jid, bool edition)
-{
-    std::map<QString, VCard*>::const_iterator itr = vCards.find(jid);
-    VCard* card;
-    if (itr != vCards.end()) {
-        card = itr->second;
+void Squawk::onActivateInfo(const QString& account, const QString& jid) {
+    std::map<QString, UI::Info*>::const_iterator itr = infoWidgets.find(jid);
+    UI::Info* info;
+    if (itr != infoWidgets.end()) {
+        info = itr->second;
     } else {
-        card = new VCard(jid, edition);
-        if (edition) {
-            card->setWindowTitle(tr("%1 account card").arg(account));
-        } else {
-            card->setWindowTitle(tr("%1 contact card").arg(jid));
-        }
-        card->setAttribute(Qt::WA_DeleteOnClose);
-        vCards.insert(std::make_pair(jid, card));
+        info = new UI::Info(jid);
+        // TODO need to handle it somewhere else
+        // if (edition) {
+        //     card->setWindowTitle(tr("%1 account card").arg(account));
+        // } else {
+        //     card->setWindowTitle(tr("%1 contact card").arg(jid));
+        // }
+        info->setAttribute(Qt::WA_DeleteOnClose);
+        infoWidgets.insert(std::make_pair(jid, info));
         
-        connect(card, &VCard::destroyed, this, &Squawk::onVCardClosed);
-        connect(card, &VCard::saveVCard, std::bind( &Squawk::onVCardSave, this, std::placeholders::_1, account));
+        connect(info, &UI::Info::destroyed, this, &Squawk::onInfoClosed);
+        connect(info, &UI::Info::saveInfo, std::bind(&Squawk::onInfoSave, this, std::placeholders::_1, account));
     }
     
-    card->show();
-    card->raise();
-    card->activateWindow();
-    card->showProgress(tr("Downloading vCard"));
+    info->show();
+    info->raise();
+    info->activateWindow();
+    info->showProgress();
     
-    emit requestVCard(account, jid);
+    emit requestInfo(account, jid);
 }
 
-void Squawk::onVCardSave(const Shared::VCard& card, const QString& account)
-{
-    VCard* widget = static_cast<VCard*>(sender());
-    emit uploadVCard(account, card);
+void Squawk::onInfoSave(const Shared::Info& info, const QString& account) {
+    UI::Info* widget = static_cast<UI::Info*>(sender());
+    emit updateInfo(account, info);
     
     widget->deleteLater();
 }
 
-void Squawk::writeSettings()
-{
+void Squawk::writeSettings() {
     QSettings settings;
     settings.beginGroup("ui");
         settings.beginGroup("window");
@@ -495,8 +477,7 @@ void Squawk::writeSettings()
     settings.endGroup();
 }
 
-void Squawk::onRosterSelectionChanged(const QModelIndex& current, const QModelIndex& previous)
-{   
+void Squawk::onRosterSelectionChanged(const QModelIndex& current, const QModelIndex& previous) {
     if (restoreSelection.isValid() && restoreSelection == current) {
         restoreSelection = QModelIndex();
         return;
@@ -504,9 +485,9 @@ void Squawk::onRosterSelectionChanged(const QModelIndex& current, const QModelIn
     
     if (current.isValid()) {
         Models::Item* node = static_cast<Models::Item*>(current.internalPointer());
-        if (node->type == Models::Item::reference) {
+        if (node->type == Models::Item::reference)
             node = static_cast<Models::Reference*>(node)->dereference();
-        }
+
         Models::Contact* contact = nullptr;
         Models::Room* room = nullptr;
         QString res;
@@ -550,9 +531,9 @@ void Squawk::onRosterSelectionChanged(const QModelIndex& current, const QModelIn
         if (id != nullptr) {
             if (currentConversation != nullptr) {
                 if (currentConversation->getId() == *id) {
-                    if (contact != nullptr) {
+                    if (contact != nullptr)
                         currentConversation->setPalResource(res);
-                    }
+
                     return;
                 } else {
                     currentConversation->deleteLater();
@@ -588,16 +569,14 @@ void Squawk::onRosterSelectionChanged(const QModelIndex& current, const QModelIn
     }
 }
 
-void Squawk::onContextAboutToHide()
-{
+void Squawk::onContextAboutToHide() {
     if (needToRestore) {
         needToRestore = false;
         m_ui->roster->selectionModel()->setCurrentIndex(restoreSelection, QItemSelectionModel::ClearAndSelect);
     }
 }
 
-void Squawk::onAboutSquawkCalled()
-{
+void Squawk::onAboutSquawkCalled() {
     if (about == nullptr) {
         about = new About();
         about->setAttribute(Qt::WA_DeleteOnClose);
@@ -609,17 +588,14 @@ void Squawk::onAboutSquawkCalled()
     about->show();
 }
 
-Models::Roster::ElId Squawk::currentConversationId() const
-{
-    if (currentConversation == nullptr) {
+Models::Roster::ElId Squawk::currentConversationId() const {
+    if (currentConversation == nullptr)
         return Models::Roster::ElId();
-    } else {
+    else
         return Models::Roster::ElId(currentConversation->getAccount(), currentConversation->getJid());
-    }
 }
 
-void Squawk::select(QModelIndex index)
-{
+void Squawk::select(QModelIndex index) {
     m_ui->roster->scrollTo(index, QAbstractItemView::EnsureVisible);
     m_ui->roster->selectionModel()->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect);
 }
diff --git a/ui/squawk.h b/ui/squawk.h
index 84e94a8..4e1ca4b 100644
--- a/ui/squawk.h
+++ b/ui/squawk.h
@@ -36,12 +36,13 @@
 #include "widgets/newcontact.h"
 #include "widgets/joinconference.h"
 #include "models/roster.h"
-#include "widgets/vcard/vcard.h"
+#include "widgets/info/info.h"
 #include "widgets/settings/settings.h"
 #include "widgets/about.h"
 
 #include "shared/shared.h"
 #include "shared/global.h"
+#include "shared/info.h"
 
 namespace Ui {
 class Squawk;
@@ -72,8 +73,8 @@ signals:
     void renameContactRequest(const QString& account, const QString& jid, const QString& newName);
     void addRoomRequest(const QString& account, const QString& jid, const QString& nick, const QString& password, bool autoJoin);
     void removeRoomRequest(const QString& account, const QString& jid);
-    void requestVCard(const QString& account, const QString& jid);
-    void uploadVCard(const QString& account, const Shared::VCard& card);
+    void requestInfo(const QString& account, const QString& jid);
+    void updateInfo(const QString& account, const Shared::Info& info);
     void changeDownloadsPath(const QString& path);
     void changeTray(bool enabled, bool hide);
 
@@ -93,7 +94,7 @@ public:
 public slots:
     void writeSettings();
     void stateChanged(Shared::Availability state);
-    void responseVCard(const QString& jid, const Shared::VCard& card);
+    void responseInfo(const Shared::Info& info);
     void select(QModelIndex index);
     
 private:
@@ -104,7 +105,7 @@ private:
     About* about;
     Models::Roster& rosterModel;
     QMenu* contextMenu;
-    std::map<QString, VCard*> vCards;
+    std::map<QString, UI::Info*> infoWidgets;
     Conversation* currentConversation;
     QModelIndex restoreSelection;
     bool needToRestore;
@@ -123,9 +124,9 @@ private slots:
     void onAccountsChanged();
     void onAccountsClosed();
     void onPreferencesClosed();
-    void onVCardClosed();
-    void onVCardSave(const Shared::VCard& card, const QString& account);
-    void onActivateVCard(const QString& account, const QString& jid, bool edition = false);
+    void onInfoClosed();
+    void onInfoSave(const Shared::Info& info, const QString& account);
+    void onActivateInfo(const QString& account, const QString& jid);
     void onComboboxActivated(int index);
     void onRosterItemDoubleClicked(const QModelIndex& item);
     void onRosterContextMenu(const QPoint& point);
diff --git a/ui/widgets/CMakeLists.txt b/ui/widgets/CMakeLists.txt
index be77db2..2099db1 100644
--- a/ui/widgets/CMakeLists.txt
+++ b/ui/widgets/CMakeLists.txt
@@ -17,7 +17,6 @@ target_sources(squawk PRIVATE
   about.ui
   )
 
-add_subdirectory(vcard)
 add_subdirectory(info)
 add_subdirectory(messageline)
 add_subdirectory(settings)
diff --git a/ui/widgets/info/contactcontacts.cpp b/ui/widgets/info/contactcontacts.cpp
index 1196aa9..10615ed 100644
--- a/ui/widgets/info/contactcontacts.cpp
+++ b/ui/widgets/info/contactcontacts.cpp
@@ -236,3 +236,11 @@ void UI::ContactContacts::onCopyPhone() {
 QString UI::ContactContacts::title() const {
     return m_ui->contactHeading->text();
 }
+
+void UI::ContactContacts::fillVCard(Shared::VCard& card) const {
+    card.setUrl(m_ui->url->text());
+
+    emails.getEmails(card.getEmails());
+    phones.getPhones(card.getPhones());
+}
+
diff --git a/ui/widgets/info/description.cpp b/ui/widgets/info/description.cpp
index 5132321..b2981ca 100644
--- a/ui/widgets/info/description.cpp
+++ b/ui/widgets/info/description.cpp
@@ -25,3 +25,20 @@ UI::Description::Description(QWidget* parent):
 }
 
 UI::Description::~Description() {}
+
+QString UI::Description::title() const {
+    return m_ui->descriptionHeading->text();
+}
+
+QString UI::Description::description() const {
+    return m_ui->description->toPlainText();
+}
+
+void UI::Description::setDescription(const QString& description) {
+    m_ui->description->setText(description);
+}
+
+void UI::Description::setEditable(bool editable) {
+    m_ui->description->setReadOnly(!editable);
+}
+
diff --git a/ui/widgets/info/description.h b/ui/widgets/info/description.h
index 42b0900..dc823d0 100644
--- a/ui/widgets/info/description.h
+++ b/ui/widgets/info/description.h
@@ -32,6 +32,11 @@ public:
     Description(QWidget* parent = nullptr);
     ~Description();
 
+    QString title() const;
+    QString description() const;
+    void setEditable(bool editable);
+    void setDescription(const QString& description);
+
 private:
     QScopedPointer<Ui::Description> m_ui;
 };
diff --git a/ui/widgets/info/info.cpp b/ui/widgets/info/info.cpp
index cbaffa8..f7d7a97 100644
--- a/ui/widgets/info/info.cpp
+++ b/ui/widgets/info/info.cpp
@@ -17,8 +17,9 @@
 #include "info.h"
 #include "ui_info.h"
 
-UI::Info::Info(QWidget* parent):
+UI::Info::Info(const QString& p_jid, QWidget* parent):
     QWidget(parent),
+    jid(p_jid),
     m_ui(new Ui::Info()),
     contactGeneral(nullptr),
     contactContacts(nullptr),
@@ -28,8 +29,10 @@ UI::Info::Info(QWidget* parent):
     progressLabel(new QLabel())
 {
     m_ui->setupUi(this);
+    m_ui->buttonBox->hide();
 
     initializeOverlay();
+    initializeButtonBox();
 }
 
 UI::Info::~Info() {
@@ -50,11 +53,17 @@ void UI::Info::setData(const Shared::Info& info) {
         case Shared::EntryType::contact:
             initializeContactGeneral(info);
             initializeContactContacts(info);
-            initializeDescription(info.editable);
+            initializeDescription(info);
             break;
         default:
             break;
     }
+
+    if (!info.editable)
+        m_ui->buttonBox->hide();
+    else
+        m_ui->buttonBox->show();
+
 }
 
 void UI::Info::initializeOverlay() {
@@ -80,9 +89,32 @@ void UI::Info::initializeOverlay() {
     overlay->hide();
 }
 
+void UI::Info::initializeButtonBox() {
+    connect(m_ui->buttonBox, &QDialogButtonBox::accepted, this, &UI::Info::onButtonBoxAccepted);
+    connect(m_ui->buttonBox, &QDialogButtonBox::rejected, this, &UI::Info::close);
+
+    m_ui->buttonBox->hide();
+}
+
+void UI::Info::onButtonBoxAccepted() {
+    //TODO this is not good, since I don't exactly know what am I editing it's bad to assume there even going to be a vcard
+    Shared::Info info;
+    if (contactGeneral != nullptr)
+        contactGeneral->fillVCard(info.vcard);
+    if (contactContacts != nullptr)
+        contactContacts->fillVCard(info.vcard);
+    if (description != nullptr)
+        info.vcard.setDescription(description->description());
+
+    emit saveInfo(info);
+}
 
 void UI::Info::showProgress(const QString& line) {
-    progressLabel->setText(line);
+    if (line.size() > 0)
+        progressLabel->setText(line);
+    else
+        progressLabel->setText(tr("Requesting information about %1").arg(jid));
+
     overlay->show();
     progress->start();
 }
@@ -99,3 +131,23 @@ void UI::Info::initializeContactGeneral(const Shared::Info& info) {
     }
     contactGeneral->setVCard(info.jid, info.vcard, info.editable);
 }
+
+void UI::Info::initializeContactContacts(const Shared::Info& info) {
+    if (contactContacts == nullptr) {
+        contactContacts = new ContactContacts;
+        m_ui->tabWidget->addTab(contactContacts, contactContacts->title());
+    }
+    contactContacts->setVCard(info.jid, info.vcard, info.editable);
+}
+
+void UI::Info::initializeDescription(const Shared::Info& info) {
+    if (description == nullptr) {
+        description = new Description;
+        m_ui->tabWidget->addTab(description, description->title());
+    }
+
+    description->setDescription(info.vcard.getDescription());
+    description->setEditable(info.editable);
+}
+
+
diff --git a/ui/widgets/info/info.h b/ui/widgets/info/info.h
index 126a092..461242f 100644
--- a/ui/widgets/info/info.h
+++ b/ui/widgets/info/info.h
@@ -39,18 +39,28 @@ class Info;
 class Info : public QWidget {
     Q_OBJECT
 public:
-    Info(QWidget* parent = nullptr);
+    Info(const QString& jid, QWidget* parent = nullptr);
     ~Info();
 
     void setData(const Shared::Info& info);
     void showProgress(const QString& = "");
     void hideProgress();
 
+public:
+    QString jid;
+
+signals:
+    void saveInfo(const Shared::Info& info);
+
+private slots:
+    void onButtonBoxAccepted();
+
 private:
     void initializeContactGeneral(const Shared::Info& info);
     void initializeContactContacts(const Shared::Info& info);
-    void initializeDescription(bool editable);
+    void initializeDescription(const Shared::Info& info);
     void initializeOverlay();
+    void initializeButtonBox();
 
 private:
     QScopedPointer<Ui::Info> m_ui;
diff --git a/ui/widgets/vcard/CMakeLists.txt b/ui/widgets/vcard/CMakeLists.txt
deleted file mode 100644
index 5dca28d..0000000
--- a/ui/widgets/vcard/CMakeLists.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-target_sources(squawk PRIVATE
-  vcard.cpp
-  vcard.h
-  vcard.ui
-  )
diff --git a/ui/widgets/vcard/vcard.cpp b/ui/widgets/vcard/vcard.cpp
deleted file mode 100644
index d16aa83..0000000
--- a/ui/widgets/vcard/vcard.cpp
+++ /dev/null
@@ -1,474 +0,0 @@
-/*
- * Squawk messenger. 
- * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
- * 
- * 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 "vcard.h"
-#include "ui_vcard.h"
-#include "shared/icons.h"
-#include <QDebug>
-
-#include <algorithm>
-
-const std::set<QString> VCard::supportedTypes = {"image/jpeg", "image/png"};
-
-VCard::VCard(const QString& jid, bool edit, QWidget* parent):
-    QWidget(parent),
-    m_ui(new Ui::VCard()),
-    avatarButtonMargins(),
-    avatarMenu(nullptr),
-    editable(edit),
-    currentAvatarType(Shared::Avatar::empty),
-    currentAvatarPath(""),
-    progress(new Progress(100)),
-    progressLabel(new QLabel()),
-    overlay(new QWidget()),
-    contextMenu(new QMenu()),
-    emails(edit),
-    phones(edit),
-    roleDelegate(new ComboboxDelegate()),
-    phoneTypeDelegate(new ComboboxDelegate())
-
-#ifdef WITH_OMEMO
-    ,omemo(new Omemo())
-#endif
-{
-    m_ui->setupUi(this);
-    m_ui->jabberID->setText(jid);
-    m_ui->jabberID->setReadOnly(true);
-    
-    initializeActions();
-    initializeDelegates();
-    initializeViews();
-    initializeInteractiveElements(jid);
-    initializeOverlay();
-#ifdef WITH_OMEMO
-    initializeOmemo();
-#endif
-}
-
-VCard::~VCard() {
-#ifdef WITH_OMEMO
-    delete omemo;
-#endif
-    if (editable) {
-        avatarMenu->deleteLater();
-    }
-    
-    phoneTypeDelegate->deleteLater();
-    roleDelegate->deleteLater();
-    contextMenu->deleteLater();
-}
-
-void VCard::setVCard(const QString& jid, const Shared::VCard& card) {
-    m_ui->jabberID->setText(jid);
-    setVCard(card);
-}
-
-void VCard::setVCard(const Shared::VCard& card) {
-    m_ui->fullName->setText(card.getFullName());
-    m_ui->firstName->setText(card.getFirstName());
-    m_ui->middleName->setText(card.getMiddleName());
-    m_ui->lastName->setText(card.getLastName());
-    m_ui->nickName->setText(card.getNickName());
-    m_ui->birthday->setDate(card.getBirthday());
-    m_ui->organizationName->setText(card.getOrgName());
-    m_ui->organizationDepartment->setText(card.getOrgUnit());
-    m_ui->organizationTitle->setText(card.getOrgTitle());
-    m_ui->organizationRole->setText(card.getOrgRole());
-    m_ui->description->setText(card.getDescription());
-    m_ui->url->setText(card.getUrl());
-    
-    QDateTime receivingTime = card.getReceivingTime();
-    m_ui->receivingTimeLabel->setText(tr("Received %1 at %2").arg(receivingTime.date().toString()).arg(receivingTime.time().toString()));
-    currentAvatarType = card.getAvatarType();
-    currentAvatarPath = card.getAvatarPath();
-    
-    updateAvatar();
-    
-    const std::deque<Shared::VCard::Email>& ems = card.getEmails();
-    const std::deque<Shared::VCard::Phone>& phs = card.getPhones();
-    emails.setEmails(ems);
-    phones.setPhones(phs);
-}
-
-QString VCard::getJid() const {
-    return m_ui->jabberID->text();
-}
-
-void VCard::onButtonBoxAccepted() {
-    Shared::VCard card;
-    card.setFullName(m_ui->fullName->text());
-    card.setFirstName(m_ui->firstName->text());
-    card.setMiddleName(m_ui->middleName->text());
-    card.setLastName(m_ui->lastName->text());
-    card.setNickName(m_ui->nickName->text());
-    card.setBirthday(m_ui->birthday->date());
-    card.setDescription(m_ui->description->toPlainText());
-    card.setUrl(m_ui->url->text());
-    card.setOrgName(m_ui->organizationName->text());
-    card.setOrgUnit(m_ui->organizationDepartment->text());
-    card.setOrgRole(m_ui->organizationRole->text());
-    card.setOrgTitle(m_ui->organizationTitle->text());
-    card.setAvatarPath(currentAvatarPath);
-    card.setAvatarType(currentAvatarType);
-    
-    emails.getEmails(card.getEmails());
-    phones.getPhones(card.getPhones());
-    
-    emit saveVCard(card);
-}
-
-void VCard::onClearAvatar() {
-    currentAvatarType = Shared::Avatar::empty;
-    currentAvatarPath = "";
-    
-    updateAvatar();
-}
-
-void VCard::onSetAvatar() {
-    QFileDialog* d = new QFileDialog(this, tr("Chose your new avatar"));
-    d->setFileMode(QFileDialog::ExistingFile);
-    d->setNameFilter(tr("Images (*.png *.jpg *.jpeg)"));
-    
-    connect(d, &QFileDialog::accepted, this, &VCard::onAvatarSelected);
-    connect(d, &QFileDialog::rejected, d, &QFileDialog::deleteLater);
-    
-    d->show();
-}
-
-void VCard::updateAvatar() {
-    int height = m_ui->personalForm->minimumSize().height() - avatarButtonMargins.height();
-    switch (currentAvatarType) {
-        case Shared::Avatar::empty:
-            m_ui->avatarButton->setIcon(Shared::icon("user", true));
-            m_ui->avatarButton->setIconSize(QSize(height, height));
-            m_ui->actionClearAvatar->setEnabled(false);
-            break;
-        case Shared::Avatar::autocreated:
-        case Shared::Avatar::valid:
-            QPixmap pixmap(currentAvatarPath);
-            qreal h = pixmap.height();
-            qreal w = pixmap.width();
-            qreal aspectRatio = w / h;
-            m_ui->avatarButton->setIconSize(QSize(height * aspectRatio, height));
-            m_ui->avatarButton->setIcon(QIcon(currentAvatarPath));
-            m_ui->actionClearAvatar->setEnabled(true);
-            break;
-    }
-}
-
-void VCard::onAvatarSelected() {
-    QFileDialog* d = static_cast<QFileDialog*>(sender());
-    QMimeDatabase db;
-    QString path = d->selectedFiles().front();
-    QMimeType type = db.mimeTypeForFile(path);
-    d->deleteLater();
-    
-    if (supportedTypes.find(type.name()) == supportedTypes.end()) {
-        qDebug() << "Selected for avatar file is not supported, skipping";
-    } else {
-        QImage src(path);
-        QImage dst;
-        if (src.width() > 160 || src.height() > 160) {
-            dst = src.scaled(160, 160, Qt::KeepAspectRatio);
-        }
-        QString path = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/" + m_ui->jabberID->text() + ".temp." + type.preferredSuffix();
-        QFile oldTemp(path);
-        if (oldTemp.exists()) {
-            if (!oldTemp.remove()) {
-                qDebug() << "Error removing old temp avatar" << path;
-                return;
-            }
-        }
-        bool success = dst.save(path);
-        if (success) {
-            currentAvatarPath = path;
-            currentAvatarType = Shared::Avatar::valid;
-            
-            updateAvatar();
-        } else {
-            qDebug() << "couldn't save avatar" << path << ", skipping";
-        }
-    }
-}
-
-void VCard::showProgress(const QString& line) {
-    progressLabel->setText(line);
-    overlay->show();
-    progress->start();
-}
-
-void VCard::hideProgress() {
-    overlay->hide();
-    progress->stop();
-}
-
-void VCard::onContextMenu(const QPoint& point) {
-    contextMenu->clear();
-    bool hasMenu = false;
-    QAbstractItemView* snd = static_cast<QAbstractItemView*>(sender());
-    if (snd == m_ui->emailsView) {
-        hasMenu = true;
-        if (editable) {
-            QAction* add = contextMenu->addAction(Shared::icon("list-add"), tr("Add email address"));
-            connect(add, &QAction::triggered, this, &VCard::onAddEmail);
-            
-            QItemSelectionModel* sm = m_ui->emailsView->selectionModel();
-            int selectionSize = sm->selectedRows().size();
-            
-            if (selectionSize > 0) {
-                if (selectionSize == 1) {
-                    int row = sm->selectedRows().at(0).row();
-                    if (emails.isPreferred(row)) {
-                        QAction* rev = contextMenu->addAction(Shared::icon("unfavorite"), tr("Unset this email as preferred"));
-                        connect(rev, &QAction::triggered, std::bind(&Models::EMails::revertPreferred, &emails, row));
-                    } else {
-                        QAction* rev = contextMenu->addAction(Shared::icon("favorite"), tr("Set this email as preferred"));
-                        connect(rev, &QAction::triggered, std::bind(&Models::EMails::revertPreferred, &emails, row));
-                    }
-                }
-                
-                QAction* del = contextMenu->addAction(Shared::icon("edit-delete"), tr("Remove selected email addresses"));
-                connect(del, &QAction::triggered, this, &VCard::onRemoveEmail);
-            }
-        }
-        
-        QAction* cp = contextMenu->addAction(Shared::icon("edit-copy"), tr("Copy selected emails to clipboard"));
-        connect(cp, &QAction::triggered, this, &VCard::onCopyEmail);
-    } else if (snd == m_ui->phonesView) {
-        hasMenu = true;
-        if (editable) {
-            QAction* add = contextMenu->addAction(Shared::icon("list-add"), tr("Add phone number"));
-            connect(add, &QAction::triggered, this, &VCard::onAddPhone);
-            
-            QItemSelectionModel* sm = m_ui->phonesView->selectionModel();
-            int selectionSize = sm->selectedRows().size();
-            
-            if (selectionSize > 0) {
-                if (selectionSize == 1) {
-                    int row = sm->selectedRows().at(0).row();
-                    if (phones.isPreferred(row)) {
-                        QAction* rev = contextMenu->addAction(Shared::icon("view-media-favorite"), tr("Unset this phone as preferred"));
-                        connect(rev, &QAction::triggered, std::bind(&Models::Phones::revertPreferred, &phones, row));
-                    } else {
-                        QAction* rev = contextMenu->addAction(Shared::icon("favorite"), tr("Set this phone as preferred"));
-                        connect(rev, &QAction::triggered, std::bind(&Models::Phones::revertPreferred, &phones, row));
-                    }
-                }
-                
-                QAction* del = contextMenu->addAction(Shared::icon("edit-delete"), tr("Remove selected phone numbers"));
-                connect(del, &QAction::triggered, this, &VCard::onRemovePhone);
-            }
-        }
-        
-        QAction* cp = contextMenu->addAction(Shared::icon("edit-copy"), tr("Copy selected phones to clipboard"));
-        connect(cp, &QAction::triggered, this, &VCard::onCopyPhone);
-    }
-    
-    if (hasMenu) {
-        contextMenu->popup(snd->viewport()->mapToGlobal(point));
-    }
-}
-
-void VCard::onAddEmail() {
-    QModelIndex index = emails.addNewEmptyLine();
-    m_ui->emailsView->setCurrentIndex(index);
-    m_ui->emailsView->edit(index);
-}
-
-void VCard::onAddAddress() {}
-void VCard::onAddPhone() {
-    QModelIndex index = phones.addNewEmptyLine();
-    m_ui->phonesView->setCurrentIndex(index);
-    m_ui->phonesView->edit(index);
-}
-void VCard::onRemoveAddress() {}
-void VCard::onRemoveEmail() {
-    QItemSelection selection(m_ui->emailsView->selectionModel()->selection());
-    
-    QList<int> rows;
-    for (const QModelIndex& index : selection.indexes()) {
-        rows.append(index.row());
-    }
-    
-    std::sort(rows.begin(), rows.end());
-    
-    int prev = -1;
-    for (int i = rows.count() - 1; i >= 0; i -= 1) {
-        int current = rows[i];
-        if (current != prev) {
-            emails.removeLines(current, 1);
-            prev = current;
-        }
-    }
-}
-
-void VCard::onRemovePhone() {
-    QItemSelection selection(m_ui->phonesView->selectionModel()->selection());
-    
-    QList<int> rows;
-    for (const QModelIndex& index : selection.indexes()) {
-        rows.append(index.row());
-    }
-    
-    std::sort(rows.begin(), rows.end());
-    
-    int prev = -1;
-    for (int i = rows.count() - 1; i >= 0; i -= 1) {
-        int current = rows[i];
-        if (current != prev) {
-            phones.removeLines(current, 1);
-            prev = current;
-        }
-    }
-}
-
-void VCard::onCopyEmail() {
-    QList<QModelIndex> selection(m_ui->emailsView->selectionModel()->selectedRows());
-    
-    QList<QString> addrs;
-    for (const QModelIndex& index : selection) {
-        addrs.push_back(emails.getEmail(index.row()));
-    }
-    
-    QString list = addrs.join("\n");
-    
-    QClipboard* cb = QApplication::clipboard();
-    cb->setText(list);
-}
-
-void VCard::onCopyPhone() {
-    QList<QModelIndex> selection(m_ui->phonesView->selectionModel()->selectedRows());
-    
-    QList<QString> phs;
-    for (const QModelIndex& index : selection) {
-        phs.push_back(phones.getPhone(index.row()));
-    }
-    
-    QString list = phs.join("\n");
-    
-    QClipboard* cb = QApplication::clipboard();
-    cb->setText(list);
-}
-
-void VCard::initializeDelegates() {
-    roleDelegate->addEntry(QCoreApplication::translate("Global", Shared::VCard::Email::roleNames[0].toStdString().c_str()));
-    roleDelegate->addEntry(QCoreApplication::translate("Global", Shared::VCard::Email::roleNames[1].toStdString().c_str()));
-    roleDelegate->addEntry(QCoreApplication::translate("Global", Shared::VCard::Email::roleNames[2].toStdString().c_str()));
-
-    phoneTypeDelegate->addEntry(QCoreApplication::translate("Global", Shared::VCard::Phone::typeNames[0].toStdString().c_str()));
-    phoneTypeDelegate->addEntry(QCoreApplication::translate("Global", Shared::VCard::Phone::typeNames[1].toStdString().c_str()));
-    phoneTypeDelegate->addEntry(QCoreApplication::translate("Global", Shared::VCard::Phone::typeNames[2].toStdString().c_str()));
-    phoneTypeDelegate->addEntry(QCoreApplication::translate("Global", Shared::VCard::Phone::typeNames[3].toStdString().c_str()));
-    phoneTypeDelegate->addEntry(QCoreApplication::translate("Global", Shared::VCard::Phone::typeNames[4].toStdString().c_str()));
-    phoneTypeDelegate->addEntry(QCoreApplication::translate("Global", Shared::VCard::Phone::typeNames[5].toStdString().c_str()));
-    phoneTypeDelegate->addEntry(QCoreApplication::translate("Global", Shared::VCard::Phone::typeNames[6].toStdString().c_str()));
-}
-
-void VCard::initializeViews() {
-    m_ui->emailsView->setContextMenuPolicy(Qt::CustomContextMenu);
-    m_ui->emailsView->setModel(&emails);
-    m_ui->emailsView->setItemDelegateForColumn(1, roleDelegate);
-    m_ui->emailsView->setColumnWidth(2, 25);
-    m_ui->emailsView->horizontalHeader()->setStretchLastSection(false);
-    m_ui->emailsView->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch);
-
-    m_ui->phonesView->setContextMenuPolicy(Qt::CustomContextMenu);
-    m_ui->phonesView->setModel(&phones);
-    m_ui->phonesView->setItemDelegateForColumn(1, roleDelegate);
-    m_ui->phonesView->setItemDelegateForColumn(2, phoneTypeDelegate);
-    m_ui->phonesView->setColumnWidth(3, 25);
-    m_ui->phonesView->horizontalHeader()->setStretchLastSection(false);
-    m_ui->phonesView->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch);
-
-    connect(m_ui->phonesView, &QWidget::customContextMenuRequested, this, &VCard::onContextMenu);
-    connect(m_ui->emailsView, &QWidget::customContextMenuRequested, this, &VCard::onContextMenu);
-}
-
-void VCard::initializeActions() {
-    QAction* setAvatar = m_ui->actionSetAvatar;
-    QAction* clearAvatar = m_ui->actionClearAvatar;
-
-    connect(setAvatar, &QAction::triggered, this, &VCard::onSetAvatar);
-    connect(clearAvatar, &QAction::triggered, this, &VCard::onClearAvatar);
-
-    setAvatar->setEnabled(true);
-    clearAvatar->setEnabled(false);
-}
-
-void VCard::initializeInteractiveElements(const QString& jid) {
-    if (editable) {
-        avatarMenu = new QMenu();
-        m_ui->avatarButton->setMenu(avatarMenu);
-        avatarMenu->addAction(m_ui->actionSetAvatar);
-        avatarMenu->addAction(m_ui->actionClearAvatar);
-        m_ui->title->setText(tr("Account %1 card").arg(jid));
-    } else {
-        m_ui->buttonBox->hide();
-        m_ui->fullName->setReadOnly(true);
-        m_ui->firstName->setReadOnly(true);
-        m_ui->middleName->setReadOnly(true);
-        m_ui->lastName->setReadOnly(true);
-        m_ui->nickName->setReadOnly(true);
-        m_ui->birthday->setReadOnly(true);
-        m_ui->organizationName->setReadOnly(true);
-        m_ui->organizationDepartment->setReadOnly(true);
-        m_ui->organizationTitle->setReadOnly(true);
-        m_ui->organizationRole->setReadOnly(true);
-        m_ui->description->setReadOnly(true);
-        m_ui->url->setReadOnly(true);
-        m_ui->title->setText(tr("Contact %1 card").arg(jid));
-    }
-
-    connect(m_ui->buttonBox, &QDialogButtonBox::accepted, this, &VCard::onButtonBoxAccepted);
-    connect(m_ui->buttonBox, &QDialogButtonBox::rejected, this, &VCard::close);
-
-    avatarButtonMargins = m_ui->avatarButton->size();
-
-    int height = m_ui->personalForm->minimumSize().height() - avatarButtonMargins.height();
-    m_ui->avatarButton->setIconSize(QSize(height, height));
-}
-
-void VCard::initializeOverlay() {
-    QGridLayout* gr = static_cast<QGridLayout*>(layout());
-    gr->addWidget(overlay, 0, 0, 4, 1);
-    QVBoxLayout* nl = new QVBoxLayout();
-    QGraphicsOpacityEffect* opacity = new QGraphicsOpacityEffect();
-    opacity->setOpacity(0.8);
-    overlay->setLayout(nl);
-    overlay->setBackgroundRole(QPalette::Base);
-    overlay->setAutoFillBackground(true);
-    overlay->setGraphicsEffect(opacity);
-    progressLabel->setAlignment(Qt::AlignCenter);
-    QFont pf = progressLabel->font();
-    pf.setBold(true);
-    pf.setPointSize(26);
-    progressLabel->setFont(pf);
-    progressLabel->setWordWrap(true);
-    nl->addStretch();
-    nl->addWidget(progress);
-    nl->addWidget(progressLabel);
-    nl->addStretch();
-    overlay->hide();
-}
-
-#ifdef WITH_OMEMO
-void VCard::initializeOmemo() {
-    m_ui->tabWidget->addTab(omemo, "OMEMO");
-}
-
-#endif
diff --git a/ui/widgets/vcard/vcard.h b/ui/widgets/vcard/vcard.h
deleted file mode 100644
index 7019797..0000000
--- a/ui/widgets/vcard/vcard.h
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Squawk messenger. 
- * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
- * 
- * 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/>.
- */
-
-#ifndef VCARD_H
-#define VCARD_H
-
-#include <QWidget>
-#include <QScopedPointer>
-#include <QPixmap>
-#include <QMenu>
-#include <QFileDialog>
-#include <QMimeDatabase>
-#include <QImage>
-#include <QStandardPaths>
-#include <QLabel>
-#include <QGraphicsOpacityEffect>
-#include <QVBoxLayout>
-#include <QMenu>
-#include <QApplication>
-#include <QClipboard>
-
-#include <set>
-
-#include "shared/vcard.h"
-#include "ui/models/info/emails.h"
-#include "ui/models/info/phones.h"
-#include "ui/utils/progress.h"
-#include "ui/utils/comboboxdelegate.h"
-
-#ifdef WITH_OMEMO
-#include "ui/widgets/info/omemo/omemo.h"
-#endif
-
-namespace Ui
-{
-class VCard;
-}
-
-class VCard : public QWidget {
-    Q_OBJECT
-public:
-    VCard(const QString& jid, bool edit = false, QWidget* parent = nullptr);
-    ~VCard();
-    
-    void setVCard(const Shared::VCard& card);
-    void setVCard(const QString& jid, const Shared::VCard& card);
-    QString getJid() const;
-    void showProgress(const QString& = "");
-    void hideProgress();
-    
-signals:
-    void saveVCard(const Shared::VCard& card);
-    
-private slots:
-    void onButtonBoxAccepted();
-    void onClearAvatar();
-    void onSetAvatar();
-    void onAvatarSelected();
-    void onAddAddress();
-    void onRemoveAddress();
-    void onAddEmail();
-    void onCopyEmail();
-    void onRemoveEmail();
-    void onAddPhone();
-    void onCopyPhone();
-    void onRemovePhone();
-    void onContextMenu(const QPoint& point);
-    
-private:
-    QScopedPointer<Ui::VCard> m_ui;
-    QSize avatarButtonMargins;
-    QMenu* avatarMenu;
-    bool editable;
-    Shared::Avatar currentAvatarType;
-    QString currentAvatarPath;
-    Progress* progress;
-    QLabel* progressLabel;
-    QWidget* overlay;
-    QMenu* contextMenu;
-    Models::EMails emails;
-    Models::Phones phones;
-    ComboboxDelegate* roleDelegate;
-    ComboboxDelegate* phoneTypeDelegate;
-#ifdef WITH_OMEMO
-    Omemo* omemo;
-#endif
-    
-    static const std::set<QString> supportedTypes;
-
-private:
-    void updateAvatar();
-    void initializeDelegates();
-    void initializeViews();
-    void initializeActions();
-    void initializeInteractiveElements(const QString& jid);
-    void initializeOverlay();
-#ifdef WITH_OMEMO
-    void initializeOmemo();
-#endif
-};
-
-#endif // VCARD_H
diff --git a/ui/widgets/vcard/vcard.ui b/ui/widgets/vcard/vcard.ui
deleted file mode 100644
index 7e09257..0000000
--- a/ui/widgets/vcard/vcard.ui
+++ /dev/null
@@ -1,892 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<ui version="4.0">
- <class>VCard</class>
- <widget class="QWidget" name="VCard">
-  <property name="enabled">
-   <bool>true</bool>
-  </property>
-  <property name="geometry">
-   <rect>
-    <x>0</x>
-    <y>0</y>
-    <width>578</width>
-    <height>748</height>
-   </rect>
-  </property>
-  <layout class="QGridLayout" name="gridLayout_4">
-   <property name="leftMargin">
-    <number>0</number>
-   </property>
-   <property name="topMargin">
-    <number>0</number>
-   </property>
-   <property name="rightMargin">
-    <number>0</number>
-   </property>
-   <property name="bottomMargin">
-    <number>0</number>
-   </property>
-   <property name="spacing">
-    <number>0</number>
-   </property>
-   <item row="0" column="0">
-    <layout class="QVBoxLayout" name="verticalLayout">
-     <property name="leftMargin">
-      <number>6</number>
-     </property>
-     <property name="topMargin">
-      <number>6</number>
-     </property>
-     <property name="rightMargin">
-      <number>6</number>
-     </property>
-     <property name="bottomMargin">
-      <number>6</number>
-     </property>
-     <item>
-      <widget class="QLabel" name="title">
-       <property name="styleSheet">
-        <string notr="true">font: 16pt </string>
-       </property>
-       <property name="text">
-        <string notr="true">Contact john@dow.org card</string>
-       </property>
-       <property name="alignment">
-        <set>Qt::AlignCenter</set>
-       </property>
-      </widget>
-     </item>
-     <item>
-      <widget class="QLabel" name="receivingTimeLabel">
-       <property name="styleSheet">
-        <string notr="true">font: italic 8pt;</string>
-       </property>
-       <property name="text">
-        <string>Received 12.07.2007 at 17.35</string>
-       </property>
-       <property name="alignment">
-        <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
-       </property>
-      </widget>
-     </item>
-     <item>
-      <widget class="QTabWidget" name="tabWidget">
-       <property name="enabled">
-        <bool>true</bool>
-       </property>
-       <property name="focusPolicy">
-        <enum>Qt::TabFocus</enum>
-       </property>
-       <property name="tabPosition">
-        <enum>QTabWidget::North</enum>
-       </property>
-       <property name="tabShape">
-        <enum>QTabWidget::Rounded</enum>
-       </property>
-       <property name="currentIndex">
-        <number>1</number>
-       </property>
-       <property name="elideMode">
-        <enum>Qt::ElideNone</enum>
-       </property>
-       <property name="documentMode">
-        <bool>true</bool>
-       </property>
-       <property name="tabBarAutoHide">
-        <bool>false</bool>
-       </property>
-       <widget class="QWidget" name="General">
-        <attribute name="title">
-         <string>General</string>
-        </attribute>
-        <layout class="QGridLayout" name="gridLayout_2" columnstretch="1,2,2,1">
-         <property name="leftMargin">
-          <number>0</number>
-         </property>
-         <property name="topMargin">
-          <number>6</number>
-         </property>
-         <property name="rightMargin">
-          <number>0</number>
-         </property>
-         <property name="bottomMargin">
-          <number>0</number>
-         </property>
-         <property name="horizontalSpacing">
-          <number>6</number>
-         </property>
-         <item row="6" column="1" colspan="2">
-          <widget class="QLabel" name="organizationHeading">
-           <property name="sizePolicy">
-            <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
-             <horstretch>0</horstretch>
-             <verstretch>0</verstretch>
-            </sizepolicy>
-           </property>
-           <property name="styleSheet">
-            <string notr="true">font: 600 16pt;</string>
-           </property>
-           <property name="text">
-            <string>Organization</string>
-           </property>
-           <property name="alignment">
-            <set>Qt::AlignCenter</set>
-           </property>
-          </widget>
-         </item>
-         <item row="3" column="1">
-          <layout class="QFormLayout" name="personalForm">
-           <property name="sizeConstraint">
-            <enum>QLayout::SetDefaultConstraint</enum>
-           </property>
-           <property name="formAlignment">
-            <set>Qt::AlignHCenter|Qt::AlignTop</set>
-           </property>
-           <item row="1" column="1">
-            <widget class="QLineEdit" name="middleName">
-             <property name="minimumSize">
-              <size>
-               <width>200</width>
-               <height>0</height>
-              </size>
-             </property>
-             <property name="maximumSize">
-              <size>
-               <width>350</width>
-               <height>16777215</height>
-              </size>
-             </property>
-            </widget>
-           </item>
-           <item row="0" column="1">
-            <widget class="QLineEdit" name="firstName">
-             <property name="minimumSize">
-              <size>
-               <width>200</width>
-               <height>0</height>
-              </size>
-             </property>
-             <property name="maximumSize">
-              <size>
-               <width>350</width>
-               <height>16777215</height>
-              </size>
-             </property>
-            </widget>
-           </item>
-           <item row="1" column="0">
-            <widget class="QLabel" name="middleNameLabel">
-             <property name="text">
-              <string>Middle name</string>
-             </property>
-             <property name="buddy">
-              <cstring>middleName</cstring>
-             </property>
-            </widget>
-           </item>
-           <item row="0" column="0">
-            <widget class="QLabel" name="firstNameLabel">
-             <property name="text">
-              <string>First name</string>
-             </property>
-             <property name="buddy">
-              <cstring>firstName</cstring>
-             </property>
-            </widget>
-           </item>
-           <item row="2" column="0">
-            <widget class="QLabel" name="lastNameLabel">
-             <property name="text">
-              <string>Last name</string>
-             </property>
-             <property name="buddy">
-              <cstring>lastName</cstring>
-             </property>
-            </widget>
-           </item>
-           <item row="2" column="1">
-            <widget class="QLineEdit" name="lastName">
-             <property name="minimumSize">
-              <size>
-               <width>200</width>
-               <height>0</height>
-              </size>
-             </property>
-             <property name="maximumSize">
-              <size>
-               <width>350</width>
-               <height>16777215</height>
-              </size>
-             </property>
-            </widget>
-           </item>
-           <item row="3" column="0">
-            <widget class="QLabel" name="nickNameLabel">
-             <property name="text">
-              <string>Nick name</string>
-             </property>
-             <property name="buddy">
-              <cstring>nickName</cstring>
-             </property>
-            </widget>
-           </item>
-           <item row="3" column="1">
-            <widget class="QLineEdit" name="nickName">
-             <property name="minimumSize">
-              <size>
-               <width>200</width>
-               <height>0</height>
-              </size>
-             </property>
-             <property name="maximumSize">
-              <size>
-               <width>350</width>
-               <height>16777215</height>
-              </size>
-             </property>
-            </widget>
-           </item>
-           <item row="4" column="0">
-            <widget class="QLabel" name="birthdayLabel">
-             <property name="text">
-              <string>Birthday</string>
-             </property>
-             <property name="buddy">
-              <cstring>birthday</cstring>
-             </property>
-            </widget>
-           </item>
-           <item row="4" column="1">
-            <widget class="QDateEdit" name="birthday"/>
-           </item>
-          </layout>
-         </item>
-         <item row="7" column="1" colspan="2">
-          <layout class="QFormLayout" name="organizationForm">
-           <property name="formAlignment">
-            <set>Qt::AlignHCenter|Qt::AlignTop</set>
-           </property>
-           <item row="0" column="0">
-            <widget class="QLabel" name="organizationNameLabel">
-             <property name="text">
-              <string>Organization name</string>
-             </property>
-             <property name="buddy">
-              <cstring>organizationName</cstring>
-             </property>
-            </widget>
-           </item>
-           <item row="0" column="1">
-            <widget class="QLineEdit" name="organizationName">
-             <property name="minimumSize">
-              <size>
-               <width>200</width>
-               <height>0</height>
-              </size>
-             </property>
-             <property name="maximumSize">
-              <size>
-               <width>350</width>
-               <height>16777215</height>
-              </size>
-             </property>
-            </widget>
-           </item>
-           <item row="1" column="0">
-            <widget class="QLabel" name="organizationDepartmentLabel">
-             <property name="text">
-              <string>Unit / Department</string>
-             </property>
-             <property name="buddy">
-              <cstring>organizationDepartment</cstring>
-             </property>
-            </widget>
-           </item>
-           <item row="1" column="1">
-            <widget class="QLineEdit" name="organizationDepartment">
-             <property name="minimumSize">
-              <size>
-               <width>200</width>
-               <height>0</height>
-              </size>
-             </property>
-             <property name="maximumSize">
-              <size>
-               <width>350</width>
-               <height>16777215</height>
-              </size>
-             </property>
-            </widget>
-           </item>
-           <item row="2" column="0">
-            <widget class="QLabel" name="roleLabel">
-             <property name="text">
-              <string>Role / Profession</string>
-             </property>
-             <property name="buddy">
-              <cstring>organizationRole</cstring>
-             </property>
-            </widget>
-           </item>
-           <item row="2" column="1">
-            <widget class="QLineEdit" name="organizationRole">
-             <property name="minimumSize">
-              <size>
-               <width>200</width>
-               <height>0</height>
-              </size>
-             </property>
-             <property name="maximumSize">
-              <size>
-               <width>350</width>
-               <height>16777215</height>
-              </size>
-             </property>
-            </widget>
-           </item>
-           <item row="3" column="0">
-            <widget class="QLabel" name="organizationTitleLabel">
-             <property name="text">
-              <string>Job title</string>
-             </property>
-             <property name="buddy">
-              <cstring>organizationTitle</cstring>
-             </property>
-            </widget>
-           </item>
-           <item row="3" column="1">
-            <widget class="QLineEdit" name="organizationTitle">
-             <property name="minimumSize">
-              <size>
-               <width>200</width>
-               <height>0</height>
-              </size>
-             </property>
-             <property name="maximumSize">
-              <size>
-               <width>350</width>
-               <height>16777215</height>
-              </size>
-             </property>
-            </widget>
-           </item>
-          </layout>
-         </item>
-         <item row="8" column="1" colspan="2">
-          <widget class="Line" name="organizationLine">
-           <property name="orientation">
-            <enum>Qt::Horizontal</enum>
-           </property>
-          </widget>
-         </item>
-         <item row="2" column="1" colspan="2">
-          <layout class="QFormLayout" name="commonForm">
-           <item row="0" column="1">
-            <widget class="QLineEdit" name="fullName"/>
-           </item>
-           <item row="0" column="0">
-            <widget class="QLabel" name="fullNameLabel">
-             <property name="text">
-              <string>Full name</string>
-             </property>
-             <property name="buddy">
-              <cstring>fullName</cstring>
-             </property>
-            </widget>
-           </item>
-          </layout>
-         </item>
-         <item row="3" column="0" rowspan="7">
-          <spacer name="generalLeftHSpacer">
-           <property name="orientation">
-            <enum>Qt::Horizontal</enum>
-           </property>
-           <property name="sizeHint" stdset="0">
-            <size>
-             <width>40</width>
-             <height>20</height>
-            </size>
-           </property>
-          </spacer>
-         </item>
-         <item row="4" column="1" colspan="2">
-          <widget class="Line" name="personalLine">
-           <property name="orientation">
-            <enum>Qt::Horizontal</enum>
-           </property>
-          </widget>
-         </item>
-         <item row="0" column="0" colspan="4">
-          <widget class="QLabel" name="generalHeading">
-           <property name="styleSheet">
-            <string notr="true">font: 600 24pt ;</string>
-           </property>
-           <property name="text">
-            <string>General</string>
-           </property>
-          </widget>
-         </item>
-         <item row="3" column="3" rowspan="7">
-          <spacer name="generalRightHSpacer">
-           <property name="orientation">
-            <enum>Qt::Horizontal</enum>
-           </property>
-           <property name="sizeHint" stdset="0">
-            <size>
-             <width>40</width>
-             <height>20</height>
-            </size>
-           </property>
-          </spacer>
-         </item>
-         <item row="1" column="1" colspan="2">
-          <widget class="QLabel" name="personalHeading">
-           <property name="sizePolicy">
-            <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
-             <horstretch>0</horstretch>
-             <verstretch>0</verstretch>
-            </sizepolicy>
-           </property>
-           <property name="styleSheet">
-            <string notr="true">font: 600 16pt;</string>
-           </property>
-           <property name="frameShape">
-            <enum>QFrame::NoFrame</enum>
-           </property>
-           <property name="frameShadow">
-            <enum>QFrame::Plain</enum>
-           </property>
-           <property name="text">
-            <string>Personal information</string>
-           </property>
-           <property name="alignment">
-            <set>Qt::AlignCenter</set>
-           </property>
-          </widget>
-         </item>
-         <item row="3" column="2">
-          <widget class="QToolButton" name="avatarButton">
-           <property name="sizePolicy">
-            <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
-             <horstretch>0</horstretch>
-             <verstretch>0</verstretch>
-            </sizepolicy>
-           </property>
-           <property name="minimumSize">
-            <size>
-             <width>0</width>
-             <height>0</height>
-            </size>
-           </property>
-           <property name="text">
-            <string/>
-           </property>
-           <property name="icon">
-            <iconset theme="user" resource="../../../resources/resources.qrc">
-             <normaloff>:/images/fallback/dark/big/user.svg</normaloff>:/images/fallback/dark/big/user.svg</iconset>
-           </property>
-           <property name="iconSize">
-            <size>
-             <width>0</width>
-             <height>0</height>
-            </size>
-           </property>
-           <property name="popupMode">
-            <enum>QToolButton::InstantPopup</enum>
-           </property>
-           <property name="toolButtonStyle">
-            <enum>Qt::ToolButtonIconOnly</enum>
-           </property>
-           <property name="arrowType">
-            <enum>Qt::NoArrow</enum>
-           </property>
-          </widget>
-         </item>
-         <item row="9" column="1" colspan="2">
-          <spacer name="verticalSpacer">
-           <property name="orientation">
-            <enum>Qt::Vertical</enum>
-           </property>
-           <property name="sizeHint" stdset="0">
-            <size>
-             <width>20</width>
-             <height>40</height>
-            </size>
-           </property>
-          </spacer>
-         </item>
-        </layout>
-       </widget>
-       <widget class="QWidget" name="Contact">
-        <attribute name="title">
-         <string>Contact</string>
-        </attribute>
-        <layout class="QVBoxLayout" name="verticalLayout_7">
-         <property name="spacing">
-          <number>0</number>
-         </property>
-         <property name="leftMargin">
-          <number>0</number>
-         </property>
-         <property name="topMargin">
-          <number>6</number>
-         </property>
-         <property name="rightMargin">
-          <number>0</number>
-         </property>
-         <property name="bottomMargin">
-          <number>0</number>
-         </property>
-         <item>
-          <widget class="QLabel" name="contactHeading">
-           <property name="styleSheet">
-            <string notr="true">font: 600 24pt ;</string>
-           </property>
-           <property name="text">
-            <string>Contact</string>
-           </property>
-          </widget>
-         </item>
-         <item>
-          <widget class="QScrollArea" name="scrollArea">
-           <property name="frameShape">
-            <enum>QFrame::NoFrame</enum>
-           </property>
-           <property name="frameShadow">
-            <enum>QFrame::Plain</enum>
-           </property>
-           <property name="lineWidth">
-            <number>0</number>
-           </property>
-           <property name="widgetResizable">
-            <bool>true</bool>
-           </property>
-           <widget class="QWidget" name="contactScrollArea">
-            <property name="geometry">
-             <rect>
-              <x>0</x>
-              <y>0</y>
-              <width>545</width>
-              <height>544</height>
-             </rect>
-            </property>
-            <layout class="QGridLayout" name="gridLayout_3" columnstretch="1,3,1">
-             <item row="7" column="1">
-              <widget class="Line" name="phonesLine">
-               <property name="orientation">
-                <enum>Qt::Horizontal</enum>
-               </property>
-              </widget>
-             </item>
-             <item row="3" column="1">
-              <widget class="QTableView" name="emailsView">
-               <property name="selectionMode">
-                <enum>QAbstractItemView::ExtendedSelection</enum>
-               </property>
-               <property name="selectionBehavior">
-                <enum>QAbstractItemView::SelectRows</enum>
-               </property>
-               <property name="showGrid">
-                <bool>false</bool>
-               </property>
-               <attribute name="horizontalHeaderVisible">
-                <bool>false</bool>
-               </attribute>
-               <attribute name="verticalHeaderVisible">
-                <bool>false</bool>
-               </attribute>
-              </widget>
-             </item>
-             <item row="10" column="1">
-              <widget class="Line" name="addressesLine">
-               <property name="orientation">
-                <enum>Qt::Horizontal</enum>
-               </property>
-              </widget>
-             </item>
-             <item row="0" column="0" rowspan="12">
-              <spacer name="contactLeftSpacer">
-               <property name="orientation">
-                <enum>Qt::Horizontal</enum>
-               </property>
-               <property name="sizeHint" stdset="0">
-                <size>
-                 <width>40</width>
-                 <height>20</height>
-                </size>
-               </property>
-              </spacer>
-             </item>
-             <item row="1" column="1">
-              <widget class="Line" name="contactFormLine">
-               <property name="orientation">
-                <enum>Qt::Horizontal</enum>
-               </property>
-              </widget>
-             </item>
-             <item row="4" column="1">
-              <widget class="Line" name="emailsLine">
-               <property name="orientation">
-                <enum>Qt::Horizontal</enum>
-               </property>
-              </widget>
-             </item>
-             <item row="11" column="1">
-              <spacer name="contactBottomSpacer">
-               <property name="orientation">
-                <enum>Qt::Vertical</enum>
-               </property>
-               <property name="sizeHint" stdset="0">
-                <size>
-                 <width>20</width>
-                 <height>40</height>
-                </size>
-               </property>
-              </spacer>
-             </item>
-             <item row="8" column="1">
-              <widget class="QLabel" name="addressesHeading">
-               <property name="styleSheet">
-                <string notr="true">font: 600 16pt;</string>
-               </property>
-               <property name="text">
-                <string>Addresses</string>
-               </property>
-               <property name="alignment">
-                <set>Qt::AlignCenter</set>
-               </property>
-              </widget>
-             </item>
-             <item row="6" column="1">
-              <widget class="QTableView" name="phonesView">
-               <property name="selectionBehavior">
-                <enum>QAbstractItemView::SelectRows</enum>
-               </property>
-               <property name="showGrid">
-                <bool>false</bool>
-               </property>
-               <attribute name="horizontalHeaderVisible">
-                <bool>false</bool>
-               </attribute>
-               <attribute name="verticalHeaderVisible">
-                <bool>false</bool>
-               </attribute>
-              </widget>
-             </item>
-             <item row="2" column="1">
-              <widget class="QLabel" name="emailsHeading">
-               <property name="styleSheet">
-                <string notr="true">font: 600 16pt;</string>
-               </property>
-               <property name="text">
-                <string>E-Mail addresses</string>
-               </property>
-               <property name="alignment">
-                <set>Qt::AlignCenter</set>
-               </property>
-              </widget>
-             </item>
-             <item row="0" column="1">
-              <layout class="QFormLayout" name="contactForm">
-               <property name="formAlignment">
-                <set>Qt::AlignHCenter|Qt::AlignTop</set>
-               </property>
-               <item row="0" column="1">
-                <widget class="QLineEdit" name="jabberID">
-                 <property name="minimumSize">
-                  <size>
-                   <width>150</width>
-                   <height>0</height>
-                  </size>
-                 </property>
-                 <property name="maximumSize">
-                  <size>
-                   <width>300</width>
-                   <height>16777215</height>
-                  </size>
-                 </property>
-                </widget>
-               </item>
-               <item row="0" column="0">
-                <widget class="QLabel" name="jabberIDLabel">
-                 <property name="text">
-                  <string>Jabber ID</string>
-                 </property>
-                 <property name="buddy">
-                  <cstring>jabberID</cstring>
-                 </property>
-                </widget>
-               </item>
-               <item row="1" column="1">
-                <widget class="QLineEdit" name="url">
-                 <property name="minimumSize">
-                  <size>
-                   <width>150</width>
-                   <height>0</height>
-                  </size>
-                 </property>
-                 <property name="maximumSize">
-                  <size>
-                   <width>300</width>
-                   <height>16777215</height>
-                  </size>
-                 </property>
-                </widget>
-               </item>
-               <item row="1" column="0">
-                <widget class="QLabel" name="urlLabel">
-                 <property name="text">
-                  <string>Web site</string>
-                 </property>
-                 <property name="buddy">
-                  <cstring>url</cstring>
-                 </property>
-                </widget>
-               </item>
-              </layout>
-             </item>
-             <item row="0" column="2" rowspan="12">
-              <spacer name="contactRightSpacer">
-               <property name="orientation">
-                <enum>Qt::Horizontal</enum>
-               </property>
-               <property name="sizeHint" stdset="0">
-                <size>
-                 <width>40</width>
-                 <height>20</height>
-                </size>
-               </property>
-              </spacer>
-             </item>
-             <item row="5" column="1">
-              <widget class="QLabel" name="phenesHeading">
-               <property name="styleSheet">
-                <string notr="true">font: 600 16pt;</string>
-               </property>
-               <property name="text">
-                <string>Phone numbers</string>
-               </property>
-               <property name="alignment">
-                <set>Qt::AlignCenter</set>
-               </property>
-              </widget>
-             </item>
-             <item row="9" column="1">
-              <widget class="QTableView" name="addressesView">
-               <property name="selectionBehavior">
-                <enum>QAbstractItemView::SelectRows</enum>
-               </property>
-               <property name="showGrid">
-                <bool>false</bool>
-               </property>
-               <attribute name="horizontalHeaderVisible">
-                <bool>false</bool>
-               </attribute>
-               <attribute name="verticalHeaderVisible">
-                <bool>false</bool>
-               </attribute>
-              </widget>
-             </item>
-            </layout>
-           </widget>
-          </widget>
-         </item>
-        </layout>
-       </widget>
-       <widget class="QWidget" name="Description">
-        <attribute name="title">
-         <string>Description</string>
-        </attribute>
-        <layout class="QGridLayout" name="gridLayout">
-         <property name="leftMargin">
-          <number>0</number>
-         </property>
-         <property name="topMargin">
-          <number>6</number>
-         </property>
-         <property name="rightMargin">
-          <number>0</number>
-         </property>
-         <property name="bottomMargin">
-          <number>0</number>
-         </property>
-         <property name="horizontalSpacing">
-          <number>6</number>
-         </property>
-         <item row="0" column="0">
-          <widget class="QLabel" name="descriptionHeading">
-           <property name="styleSheet">
-            <string notr="true">font: 600 24pt ;</string>
-           </property>
-           <property name="text">
-            <string>Description</string>
-           </property>
-          </widget>
-         </item>
-         <item row="1" column="0">
-          <widget class="QTextEdit" name="description">
-           <property name="frameShape">
-            <enum>QFrame::StyledPanel</enum>
-           </property>
-           <property name="textInteractionFlags">
-            <set>Qt::LinksAccessibleByMouse|Qt::TextEditable|Qt::TextEditorInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
-           </property>
-          </widget>
-         </item>
-        </layout>
-       </widget>
-      </widget>
-     </item>
-     <item>
-      <widget class="QDialogButtonBox" name="buttonBox">
-       <property name="standardButtons">
-        <set>QDialogButtonBox::Close|QDialogButtonBox::Save</set>
-       </property>
-       <property name="centerButtons">
-        <bool>false</bool>
-       </property>
-      </widget>
-     </item>
-    </layout>
-   </item>
-  </layout>
-  <action name="actionSetAvatar">
-   <property name="icon">
-    <iconset theme="photo" resource="../../../resources/resources.qrc">
-     <normaloff>:/images/fallback/dark/big/edit-rename.svg</normaloff>:/images/fallback/dark/big/edit-rename.svg</iconset>
-   </property>
-   <property name="text">
-    <string>Set avatar</string>
-   </property>
-  </action>
-  <action name="actionClearAvatar">
-   <property name="icon">
-    <iconset theme="edit-clear-all" resource="../../../resources/resources.qrc">
-     <normaloff>:/images/fallback/dark/big/clean.svg</normaloff>:/images/fallback/dark/big/clean.svg</iconset>
-   </property>
-   <property name="text">
-    <string>Clear avatar</string>
-   </property>
-  </action>
- </widget>
- <tabstops>
-  <tabstop>fullName</tabstop>
-  <tabstop>firstName</tabstop>
-  <tabstop>middleName</tabstop>
-  <tabstop>lastName</tabstop>
-  <tabstop>nickName</tabstop>
-  <tabstop>birthday</tabstop>
-  <tabstop>avatarButton</tabstop>
-  <tabstop>organizationName</tabstop>
-  <tabstop>organizationDepartment</tabstop>
-  <tabstop>organizationRole</tabstop>
-  <tabstop>organizationTitle</tabstop>
-  <tabstop>jabberID</tabstop>
-  <tabstop>url</tabstop>
-  <tabstop>description</tabstop>
- </tabstops>
- <resources>
-  <include location="../../../resources/resources.qrc"/>
- </resources>
- <connections/>
-</ui>

From ec362cef553f0397065e6004495f0f4cd6a3f22f Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Tue, 21 Feb 2023 23:27:28 +0300
Subject: [PATCH 232/281] some further thinking of info widget

---
 core/handlers/vcardhandler.cpp |  4 +-
 shared/enums.h                 |  4 +-
 ui/squawk.cpp                  |  9 +---
 ui/widgets/info/info.cpp       | 81 ++++++++++++++++++++--------------
 ui/widgets/info/info.h         | 14 +++---
 5 files changed, 64 insertions(+), 48 deletions(-)

diff --git a/core/handlers/vcardhandler.cpp b/core/handlers/vcardhandler.cpp
index c9e7170..ca8b29b 100644
--- a/core/handlers/vcardhandler.cpp
+++ b/core/handlers/vcardhandler.cpp
@@ -187,7 +187,7 @@ void Core::VCardHandler::onOwnVCardReceived(const QXmppVCardIq& card) {
 
     ownVCardRequestInProgress = false;
 
-    Shared::Info info(acc->getBareJid(), Shared::EntryType::contact, true);
+    Shared::Info info(acc->getBareJid(), Shared::EntryType::ownAccount, true);
     initializeVCard(info.vcard, card);
 
     if (avatarType.size() > 0) {
@@ -203,7 +203,7 @@ void Core::VCardHandler::onOwnVCardReceived(const QXmppVCardIq& card) {
 void Core::VCardHandler::handleOffline() {
     pendingVCardRequests.clear();
     for (const QString& jid : pendingVCardRequests) {
-        Shared::Info info(jid, Shared::EntryType::contact);
+        Shared::Info info(jid, Shared::EntryType::none);
         emit acc->infoReady(info);     //need to show it better in the future, like with an error
     }
     pendingVCardRequests.clear();
diff --git a/shared/enums.h b/shared/enums.h
index 9eb9e5e..46d954a 100644
--- a/shared/enums.h
+++ b/shared/enums.h
@@ -119,6 +119,8 @@ static const AccountPassword AccountPasswordHighest = AccountPassword::kwallet;
 static const AccountPassword AccountPasswordLowest = AccountPassword::plain;
 
 enum class EntryType {
+    none,
+    ownAccount,
     contact,
     conference,
     presence,
@@ -126,7 +128,7 @@ enum class EntryType {
 };
 Q_ENUM_NS(EntryType)
 static const EntryType EntryTypeHighest = EntryType::participant;
-static const EntryType EntryTypeLowest = EntryType::contact;
+static const EntryType EntryTypeLowest = EntryType::none;
 
 enum class Support {
     unknown,
diff --git a/ui/squawk.cpp b/ui/squawk.cpp
index 9702357..2c969da 100644
--- a/ui/squawk.cpp
+++ b/ui/squawk.cpp
@@ -422,7 +422,7 @@ void Squawk::responseInfo(const Shared::Info& info) {
 void Squawk::onInfoClosed() {
     UI::Info* info = static_cast<UI::Info*>(sender());
     
-    std::map<QString, UI::Info*>::const_iterator itr = infoWidgets.find(info->jid);
+    std::map<QString, UI::Info*>::const_iterator itr = infoWidgets.find(info->getJid());
     if (itr == infoWidgets.end()) {
         qDebug() << "Info widget has been closed but can not be found among other opened vCards, application is most probably going to crash";
         return;
@@ -437,12 +437,7 @@ void Squawk::onActivateInfo(const QString& account, const QString& jid) {
         info = itr->second;
     } else {
         info = new UI::Info(jid);
-        // TODO need to handle it somewhere else
-        // if (edition) {
-        //     card->setWindowTitle(tr("%1 account card").arg(account));
-        // } else {
-        //     card->setWindowTitle(tr("%1 contact card").arg(jid));
-        // }
+        info->setWindowTitle(tr("Information about %1").arg(jid));
         info->setAttribute(Qt::WA_DeleteOnClose);
         infoWidgets.insert(std::make_pair(jid, info));
         
diff --git a/ui/widgets/info/info.cpp b/ui/widgets/info/info.cpp
index f7d7a97..e951e97 100644
--- a/ui/widgets/info/info.cpp
+++ b/ui/widgets/info/info.cpp
@@ -20,6 +20,7 @@
 UI::Info::Info(const QString& p_jid, QWidget* parent):
     QWidget(parent),
     jid(p_jid),
+    type(Shared::EntryType::none),
     m_ui(new Ui::Info()),
     contactGeneral(nullptr),
     contactContacts(nullptr),
@@ -30,36 +31,39 @@ UI::Info::Info(const QString& p_jid, QWidget* parent):
 {
     m_ui->setupUi(this);
     m_ui->buttonBox->hide();
+    m_ui->title->setText(tr("%1 info").arg(p_jid));
+    m_ui->receivingTimeLabel->hide();
 
     initializeOverlay();
     initializeButtonBox();
 }
 
 UI::Info::~Info() {
-    if (contactGeneral != nullptr)
-        contactGeneral->deleteLater();
-
-    if (contactContacts != nullptr)
-        contactContacts->deleteLater();
-
-    if (description != nullptr)
-        description->deleteLater();
-
+    clear();
     overlay->deleteLater();
 }
 
 void UI::Info::setData(const Shared::Info& info) {
+    bool editable = false;
     switch (info.type) {
-        case Shared::EntryType::contact:
-            initializeContactGeneral(info);
-            initializeContactContacts(info);
-            initializeDescription(info);
+        case Shared::EntryType::ownAccount:
+            editable = true;
+        case Shared::EntryType::contact: {
+            QDateTime receivingTime = info.vcard.getReceivingTime();
+            m_ui->receivingTimeLabel->show();
+            m_ui->receivingTimeLabel->setText(tr("Received %1 at %2").arg(receivingTime.date().toString()).arg(receivingTime.time().toString()));
+            initializeContactGeneral(jid, info.vcard, editable);
+            initializeContactContacts(jid, info.vcard, editable);
+            initializeDescription(info.vcard.getDescription(), editable);
+            type = info.type;
+        }
             break;
         default:
+            clear();
             break;
     }
 
-    if (!info.editable)
+    if (!editable)
         m_ui->buttonBox->hide();
     else
         m_ui->buttonBox->show();
@@ -77,10 +81,7 @@ void UI::Info::initializeOverlay() {
     overlay->setAutoFillBackground(true);
     overlay->setGraphicsEffect(opacity);
     progressLabel->setAlignment(Qt::AlignCenter);
-    QFont pf = progressLabel->font();
-    pf.setBold(true);
-    pf.setPointSize(26);
-    progressLabel->setFont(pf);
+    progressLabel->setFont(Shared::Global::getInstance()->titleFont);
     progressLabel->setWordWrap(true);
     nl->addStretch();
     nl->addWidget(progress);
@@ -97,16 +98,13 @@ void UI::Info::initializeButtonBox() {
 }
 
 void UI::Info::onButtonBoxAccepted() {
-    //TODO this is not good, since I don't exactly know what am I editing it's bad to assume there even going to be a vcard
-    Shared::Info info;
-    if (contactGeneral != nullptr)
+    if (type == Shared::EntryType::ownAccount) {
+        Shared::Info info;
         contactGeneral->fillVCard(info.vcard);
-    if (contactContacts != nullptr)
         contactContacts->fillVCard(info.vcard);
-    if (description != nullptr)
         info.vcard.setDescription(description->description());
-
-    emit saveInfo(info);
+        emit saveInfo(info);
+    }
 }
 
 void UI::Info::showProgress(const QString& line) {
@@ -124,30 +122,49 @@ void UI::Info::hideProgress() {
     progress->stop();
 }
 
-void UI::Info::initializeContactGeneral(const Shared::Info& info) {
+void UI::Info::initializeContactGeneral(const QString& jid, const Shared::VCard& card, bool editable) {
     if (contactGeneral == nullptr) {
         contactGeneral = new ContactGeneral;
         m_ui->tabWidget->addTab(contactGeneral, contactGeneral->title());
     }
-    contactGeneral->setVCard(info.jid, info.vcard, info.editable);
+    contactGeneral->setVCard(jid, card, editable);
 }
 
-void UI::Info::initializeContactContacts(const Shared::Info& info) {
+void UI::Info::initializeContactContacts(const QString& jid, const Shared::VCard& card, bool editable) {
     if (contactContacts == nullptr) {
         contactContacts = new ContactContacts;
         m_ui->tabWidget->addTab(contactContacts, contactContacts->title());
     }
-    contactContacts->setVCard(info.jid, info.vcard, info.editable);
+    contactContacts->setVCard(jid, card, editable);
 }
 
-void UI::Info::initializeDescription(const Shared::Info& info) {
+void UI::Info::initializeDescription(const QString& descr, bool editable) {
     if (description == nullptr) {
         description = new Description;
         m_ui->tabWidget->addTab(description, description->title());
     }
 
-    description->setDescription(info.vcard.getDescription());
-    description->setEditable(info.editable);
+    description->setDescription(descr);
+    description->setEditable(editable);
 }
 
+QString UI::Info::getJid() const {
+    return jid;}
 
+void UI::Info::clear() {
+    if (contactGeneral != nullptr) {
+        contactGeneral->deleteLater();
+        contactGeneral = nullptr;
+    }
+
+    if (contactContacts != nullptr) {
+        contactContacts->deleteLater();
+        contactContacts = nullptr;
+    }
+
+    if (description != nullptr) {
+        description->deleteLater();
+        description = nullptr;
+    }
+    type = Shared::EntryType::none;
+}
diff --git a/ui/widgets/info/info.h b/ui/widgets/info/info.h
index 461242f..14829e7 100644
--- a/ui/widgets/info/info.h
+++ b/ui/widgets/info/info.h
@@ -23,6 +23,7 @@
 #include <QLabel>
 
 #include <shared/info.h>
+#include <shared/global.h>
 
 #include "ui/utils/progress.h"
 
@@ -42,13 +43,11 @@ public:
     Info(const QString& jid, QWidget* parent = nullptr);
     ~Info();
 
+    QString getJid() const;
     void setData(const Shared::Info& info);
     void showProgress(const QString& = "");
     void hideProgress();
 
-public:
-    QString jid;
-
 signals:
     void saveInfo(const Shared::Info& info);
 
@@ -56,13 +55,16 @@ private slots:
     void onButtonBoxAccepted();
 
 private:
-    void initializeContactGeneral(const Shared::Info& info);
-    void initializeContactContacts(const Shared::Info& info);
-    void initializeDescription(const Shared::Info& info);
+    void initializeContactGeneral(const QString& jid, const Shared::VCard& card, bool editable);
+    void initializeContactContacts(const QString& jid, const Shared::VCard& card, bool editable);
+    void initializeDescription(const QString& description, bool editable);
     void initializeOverlay();
     void initializeButtonBox();
+    void clear();
 
 private:
+    QString jid;
+    Shared::EntryType type;
     QScopedPointer<Ui::Info> m_ui;
     ContactGeneral* contactGeneral;
     ContactContacts* contactContacts;

From 6f32e995938f58d0aa10823f04d3729586a0b3cf Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Wed, 1 Mar 2023 22:32:41 +0300
Subject: [PATCH 233/281] an idea how to manage info object better

---
 core/account.cpp                   |   2 +-
 core/conference.cpp                |  10 +-
 core/conference.h                  |   2 +-
 core/handlers/vcardhandler.cpp     |  13 +-
 core/rosteritem.cpp                |   5 +-
 core/rosteritem.h                  |   2 +-
 shared/info.cpp                    | 327 +++++++++++++++++++++++++++--
 shared/info.h                      |  57 ++++-
 ui/squawk.cpp                      |   2 +-
 ui/widgets/info/contactgeneral.cpp |   8 +-
 ui/widgets/info/contactgeneral.h   |   1 -
 ui/widgets/info/info.cpp           |  24 ++-
 12 files changed, 390 insertions(+), 63 deletions(-)

diff --git a/core/account.cpp b/core/account.cpp
index 98862a4..0970a6e 100644
--- a/core/account.cpp
+++ b/core/account.cpp
@@ -735,7 +735,7 @@ void Core::Account::requestInfo(const QString& jid) {
 void Core::Account::updateInfo(const Shared::Info& info) {
     //TODO switch case of what kind of entity this info update is about
     //right now it could be only about myself
-    vh->uploadVCard(info.vcard);
+    vh->uploadVCard(info.getVCardRef());
 }
 
 QString Core::Account::getAvatarPath() const {
diff --git a/core/conference.cpp b/core/conference.cpp
index a984f41..065490c 100644
--- a/core/conference.cpp
+++ b/core/conference.cpp
@@ -339,18 +339,16 @@ bool Core::Conference::setAvatar(const QByteArray& data, Archive::AvatarInfo& in
     return result;
 }
 
-Shared::VCard Core::Conference::handleResponseVCard(const QXmppVCardIq& card, const QString &resource)
+void Core::Conference::handleResponseVCard(const QXmppVCardIq& card, const QString &resource, Shared::VCard& out)
 {
-    Shared::VCard result = RosterItem::handleResponseVCard(card, resource);
+    RosterItem::handleResponseVCard(card, resource, out);
     
     if (resource.size() > 0) {
         emit changeParticipant(resource, {
-            {"avatarState", static_cast<uint>(result.getAvatarType())},
-            {"avatarPath", result.getAvatarPath()}
+            {"avatarState", static_cast<uint>(out.getAvatarType())},
+            {"avatarPath", out.getAvatarPath()}
         });
     }
-    
-    return result;
 }
 
 QMap<QString, QVariant> Core::Conference::getAllAvatars() const
diff --git a/core/conference.h b/core/conference.h
index 4e0e463..41141ad 100644
--- a/core/conference.h
+++ b/core/conference.h
@@ -52,7 +52,7 @@ public:
     void setAutoJoin(bool p_autoJoin);
     void handlePresence(const QXmppPresence & pres) override;
     bool setAutoGeneratedAvatar(const QString& resource = "") override;
-    Shared::VCard handleResponseVCard(const QXmppVCardIq & card, const QString &resource) override;
+    void handleResponseVCard(const QXmppVCardIq & card, const QString &resource, Shared::VCard& out) override;
     QMap<QString, QVariant> getAllAvatars() const;
     
 signals:
diff --git a/core/handlers/vcardhandler.cpp b/core/handlers/vcardhandler.cpp
index ca8b29b..33c9cdb 100644
--- a/core/handlers/vcardhandler.cpp
+++ b/core/handlers/vcardhandler.cpp
@@ -101,7 +101,7 @@ void Core::VCardHandler::onVCardReceived(const QXmppVCardIq& card) {
     }
 
     Shared::Info info(jid, Shared::EntryType::contact);
-    info.vcard = item->handleResponseVCard(card, resource);
+    item->handleResponseVCard(card, resource, info.getVCardRef());
 
     emit acc->infoReady(info);
 }
@@ -187,14 +187,15 @@ void Core::VCardHandler::onOwnVCardReceived(const QXmppVCardIq& card) {
 
     ownVCardRequestInProgress = false;
 
-    Shared::Info info(acc->getBareJid(), Shared::EntryType::ownAccount, true);
-    initializeVCard(info.vcard, card);
+    Shared::Info info(acc->getBareJid(), Shared::EntryType::ownAccount);
+    Shared::VCard& vCard = info.getVCardRef();
+    initializeVCard(vCard, card);
 
     if (avatarType.size() > 0) {
-        info.vcard.setAvatarType(Shared::Avatar::valid);
-        info.vcard.setAvatarPath(path + "avatar." + avatarType);
+        vCard.setAvatarType(Shared::Avatar::valid);
+        vCard.setAvatarPath(path + "avatar." + avatarType);
     } else {
-        info.vcard.setAvatarType(Shared::Avatar::empty);
+        vCard.setAvatarType(Shared::Avatar::empty);
     }
 
     emit acc->infoReady(info);
diff --git a/core/rosteritem.cpp b/core/rosteritem.cpp
index 1b8d1e6..0bac4a4 100644
--- a/core/rosteritem.cpp
+++ b/core/rosteritem.cpp
@@ -506,14 +506,13 @@ bool Core::RosterItem::readAvatarInfo(Archive::AvatarInfo& target, const QString
     return archive->readAvatarInfo(target, resource);
 }
 
-Shared::VCard Core::RosterItem::handleResponseVCard(const QXmppVCardIq& card, const QString& resource)
+void Core::RosterItem::handleResponseVCard(const QXmppVCardIq& card, const QString& resource, Shared::VCard& vCard)
 {
     Archive::AvatarInfo info;
     Archive::AvatarInfo newInfo;
     bool hasAvatar = readAvatarInfo(info, resource);
     
     QByteArray ava = card.photo();
-    Shared::VCard vCard;
     initializeVCard(vCard, card);
     Shared::Avatar type = Shared::Avatar::empty;
     QString path = "";
@@ -546,8 +545,6 @@ Shared::VCard Core::RosterItem::handleResponseVCard(const QXmppVCardIq& card, co
     if (resource.size() == 0) {
         emit avatarChanged(vCard.getAvatarType(), vCard.getAvatarPath());
     }
-    
-    return vCard;
 }
 
 void Core::RosterItem::clearArchiveRequests()
diff --git a/core/rosteritem.h b/core/rosteritem.h
index 5f99017..7c82945 100644
--- a/core/rosteritem.h
+++ b/core/rosteritem.h
@@ -72,7 +72,7 @@ public:
     QString folderPath() const;
     bool readAvatarInfo(Archive::AvatarInfo& target, const QString& resource = "") const;
     virtual bool setAutoGeneratedAvatar(const QString& resource = "");
-    virtual Shared::VCard handleResponseVCard(const QXmppVCardIq& card, const QString& resource);
+    virtual void handleResponseVCard(const QXmppVCardIq& card, const QString& resource, Shared::VCard& out);
     virtual void handlePresence(const QXmppPresence& pres) = 0;
     
     bool changeMessage(const QString& id, const QMap<QString, QVariant>& data);
diff --git a/shared/info.cpp b/shared/info.cpp
index a0ac299..c0d6f26 100644
--- a/shared/info.cpp
+++ b/shared/info.cpp
@@ -16,32 +16,315 @@
 
 #include "info.h"
 
-Shared::Info::Info(const QString& p_jid, EntryType p_type, bool p_editable):
-    type(p_type),
-    jid(p_jid),
-    editable(p_editable),
-    vcard(),
-    activeKeys(),
-    inactiveKeys()
-{}
+Shared::Info::Info(const QString& addr, EntryType tp):
+    type(tp),
+    address(addr),
+    vcard(nullptr),
+    activeKeys(nullptr),
+    inactiveKeys(nullptr)
+{
+    switch (type) {
+        case EntryType::none:
+            break;
+        case EntryType::contact:
+        case EntryType::ownAccount:
+            vcard = new VCard();
+            activeKeys = new std::list<KeyInfo>();
+            inactiveKeys = new std::list<KeyInfo>();
+            break;
+        default:
+            throw 352;
+    }
+}
 
 Shared::Info::Info():
-    type(EntryType::contact),
-    jid(),
-    editable(false),
-    vcard(),
-    activeKeys(),
-    inactiveKeys()
+    type(EntryType::none),
+    address(""),
+    vcard(nullptr),
+    activeKeys(nullptr),
+    inactiveKeys(nullptr)
 {}
 
 Shared::Info::Info(const Shared::Info& other):
     type(other.type),
-    jid(other.jid),
-    editable(other.editable),
-    vcard(other.vcard),
-    activeKeys(other.activeKeys),
-    inactiveKeys(other.inactiveKeys)
-{}
+    address(other.address),
+    vcard(nullptr),
+    activeKeys(nullptr),
+    inactiveKeys(nullptr)
+{
+    switch (type) {
+        case EntryType::none:
+            break;
+        case EntryType::contact:
+        case EntryType::ownAccount:
+            vcard = new VCard(other.getVCardRef());
+            activeKeys = new std::list<KeyInfo>(other.getActiveKeysRef());
+            inactiveKeys = new std::list<KeyInfo>(other.getInactiveKeysRef());
+            break;
+        default:
+            throw 353;
+    }
+}
 
-Shared::Info::~Info()
-{}
+Shared::Info::~Info() {
+    turnIntoNone();
+}
+
+void Shared::Info::turnIntoNone() {
+    switch (type) {
+        case EntryType::none:
+            break;
+        case EntryType::contact:
+        case EntryType::ownAccount:
+            delete vcard;
+            vcard = nullptr;
+            delete activeKeys;
+            activeKeys = nullptr;
+            delete inactiveKeys;
+            inactiveKeys = nullptr;
+            break;
+        default:
+            break;
+    }
+    type = EntryType::none;
+}
+
+void Shared::Info::turnIntoContact(
+    const Shared::VCard& crd,
+    const std::list<KeyInfo>& aks,
+    const std::list<KeyInfo>& iaks
+) {
+    switch (type) {
+        case EntryType::none:
+            vcard = new VCard(crd);
+            activeKeys = new std::list<KeyInfo>(aks);
+            inactiveKeys = new std::list<KeyInfo>(iaks);
+            break;
+        case EntryType::contact:
+        case EntryType::ownAccount:
+            *vcard = crd;
+            *activeKeys = aks;
+            *inactiveKeys = iaks;
+            break;
+        default:
+            break;
+    }
+
+    type = EntryType::contact;
+}
+
+void Shared::Info::turnIntoContact(
+    Shared::VCard* crd,
+    std::list<KeyInfo>* aks,
+    std::list<KeyInfo>* iaks
+) {
+    switch (type) {
+        case EntryType::contact:
+        case EntryType::ownAccount:
+            delete vcard;
+            delete activeKeys;
+            delete inactiveKeys;
+            [[fallthrough]];
+        case EntryType::none:
+            vcard = crd;
+            activeKeys = aks;
+            inactiveKeys = iaks;
+            break;
+        default:
+            break;
+    }
+
+    type = EntryType::contact;
+}
+
+void Shared::Info::turnIntoOwnAccount(
+    const Shared::VCard& crd,
+    const std::list<KeyInfo>& aks,
+    const std::list<KeyInfo>& iaks
+) {
+        switch (type) {
+        case EntryType::none:
+            vcard = new VCard(crd);
+            activeKeys = new std::list<KeyInfo>(aks);
+            inactiveKeys = new std::list<KeyInfo>(iaks);
+            break;
+        case EntryType::contact:
+        case EntryType::ownAccount:
+            *vcard = crd;
+            *activeKeys = aks;
+            *inactiveKeys = iaks;
+            break;
+        default:
+            break;
+    }
+
+    type = EntryType::ownAccount;
+}
+
+void Shared::Info::turnIntoOwnAccount(
+    Shared::VCard* crd,
+    std::list<KeyInfo>* aks,
+    std::list<KeyInfo>* iaks
+) {
+    switch (type) {
+        case EntryType::contact:
+        case EntryType::ownAccount:
+            delete vcard;
+            delete activeKeys;
+            delete inactiveKeys;
+            [[fallthrough]];
+        case EntryType::none:
+            vcard = crd;
+            activeKeys = aks;
+            inactiveKeys = iaks;
+            break;
+        default:
+            break;
+    }
+
+    type = EntryType::ownAccount;
+}
+
+void Shared::Info::setAddress(const QString& addr) {
+    address = addr;}
+
+QString Shared::Info::getAddress() const {
+    return address;}
+
+const QString& Shared::Info::getAddressRef() const {
+    return address;}
+
+Shared::EntryType Shared::Info::getType() const {
+    return type;}
+
+std::list<Shared::KeyInfo> & Shared::Info::getActiveKeysRef() {
+    switch (type) {
+        case EntryType::contact:
+        case EntryType::ownAccount:
+            return *activeKeys;
+            break;
+        default:
+            throw 354;
+    }
+}
+
+const std::list<Shared::KeyInfo> & Shared::Info::getActiveKeysRef() const {
+    switch (type) {
+        case EntryType::contact:
+        case EntryType::ownAccount:
+            return *activeKeys;
+            break;
+        default:
+            throw 355;
+    }
+}
+
+std::list<Shared::KeyInfo>* Shared::Info::getActiveKeys() {
+    switch (type) {
+        case EntryType::contact:
+        case EntryType::ownAccount:
+            return activeKeys;
+            break;
+        default:
+            throw 356;
+    }
+}
+
+const std::list<Shared::KeyInfo>* Shared::Info::getActiveKeys() const {
+    switch (type) {
+        case EntryType::contact:
+        case EntryType::ownAccount:
+            return activeKeys;
+            break;
+        default:
+            throw 357;
+    }
+}
+
+std::list<Shared::KeyInfo> & Shared::Info::getInactiveKeysRef() {
+    switch (type) {
+        case EntryType::contact:
+        case EntryType::ownAccount:
+            return *inactiveKeys;
+            break;
+        default:
+            throw 358;
+    }
+}
+
+const std::list<Shared::KeyInfo> & Shared::Info::getInactiveKeysRef() const {
+    switch (type) {
+        case EntryType::contact:
+        case EntryType::ownAccount:
+            return *inactiveKeys;
+            break;
+        default:
+            throw 359;
+    }
+}
+
+std::list<Shared::KeyInfo>* Shared::Info::getInactiveKeys() {
+    switch (type) {
+        case EntryType::contact:
+        case EntryType::ownAccount:
+            return inactiveKeys;
+            break;
+        default:
+            throw 360;
+    }
+}
+
+const std::list<Shared::KeyInfo>* Shared::Info::getInactiveKeys() const {
+    switch (type) {
+        case EntryType::contact:
+        case EntryType::ownAccount:
+            return inactiveKeys;
+            break;
+        default:
+            throw 361;
+    }
+}
+
+const Shared::VCard & Shared::Info::getVCardRef() const {
+    switch (type) {
+        case EntryType::contact:
+        case EntryType::ownAccount:
+            return *vcard;
+            break;
+        default:
+            throw 362;
+    }
+}
+
+Shared::VCard & Shared::Info::getVCardRef()  {
+    switch (type) {
+        case EntryType::contact:
+        case EntryType::ownAccount:
+            return *vcard;
+            break;
+        default:
+            throw 363;
+    }
+}
+
+const Shared::VCard * Shared::Info::getVCard() const {
+    switch (type) {
+        case EntryType::contact:
+        case EntryType::ownAccount:
+            return vcard;
+            break;
+        default:
+            throw 364;
+    }
+}
+
+Shared::VCard * Shared::Info::getVCard() {
+    switch (type) {
+        case EntryType::contact:
+        case EntryType::ownAccount:
+            return vcard;
+            break;
+        default:
+            throw 365;
+    }
+}
diff --git a/shared/info.h b/shared/info.h
index 90248c3..b0f495e 100644
--- a/shared/info.h
+++ b/shared/info.h
@@ -34,16 +34,59 @@ namespace Shared {
 class Info {
 public:
     Info();
-    Info(const QString& jid, EntryType = EntryType::contact, bool editable = false);
+    Info(const QString& address, EntryType = EntryType::none);
     Info(const Info& other);
-    ~Info();
+    virtual ~Info();
 
+    QString getAddress() const;
+    const QString& getAddressRef() const;
+    void setAddress(const QString& address);
+
+    EntryType getType() const;
+    void turnIntoNone();
+    void turnIntoContact(
+        const VCard& card = VCard(),
+        const std::list<KeyInfo>& activeKeys = {},
+        const std::list<KeyInfo>& inactiveKeys = {}
+    );
+    void turnIntoContact(
+        VCard* card = new VCard,
+        std::list<KeyInfo>* activeKeys = new std::list<KeyInfo>,
+        std::list<KeyInfo>* inactiveKeys = new std::list<KeyInfo>
+    );
+    void turnIntoOwnAccount(
+        const VCard& card = VCard(),
+        const std::list<KeyInfo>& activeKeys = {},
+        const std::list<KeyInfo>& inactiveKeys = {}
+    );
+    void turnIntoOwnAccount(
+        VCard* card = new VCard,
+        std::list<KeyInfo>* activeKeys = new std::list<KeyInfo>,
+        std::list<KeyInfo>* inactiveKeys = new std::list<KeyInfo>
+    );
+
+    const VCard& getVCardRef() const;
+    VCard& getVCardRef();
+    const VCard* getVCard() const;
+    VCard* getVCard();
+
+    const std::list<KeyInfo>& getActiveKeysRef() const;
+    std::list<KeyInfo>& getActiveKeysRef();
+    const std::list<KeyInfo>* getActiveKeys() const;
+    std::list<KeyInfo>* getActiveKeys();
+
+    const std::list<KeyInfo>& getInactiveKeysRef() const;
+    std::list<KeyInfo>& getInactiveKeysRef();
+    const std::list<KeyInfo>* getInactiveKeys() const;
+    std::list<KeyInfo>* getInactiveKeys();
+
+private:
     EntryType type;
-    QString jid;
-    bool editable;
-    VCard vcard;
-    std::list<KeyInfo> activeKeys;
-    std::list<KeyInfo> inactiveKeys;
+    QString address;
+
+    VCard* vcard;
+    std::list<KeyInfo>* activeKeys;
+    std::list<KeyInfo>* inactiveKeys;
 };
 
 }
diff --git a/ui/squawk.cpp b/ui/squawk.cpp
index 2c969da..ce759ac 100644
--- a/ui/squawk.cpp
+++ b/ui/squawk.cpp
@@ -412,7 +412,7 @@ void Squawk::onRosterContextMenu(const QPoint& point) {
 }
 
 void Squawk::responseInfo(const Shared::Info& info) {
-    std::map<QString, UI::Info*>::const_iterator itr = infoWidgets.find(info.jid);
+    std::map<QString, UI::Info*>::const_iterator itr = infoWidgets.find(info.getAddressRef());
     if (itr != infoWidgets.end()) {
         itr->second->setData(info);
         itr->second->hideProgress();
diff --git a/ui/widgets/info/contactgeneral.cpp b/ui/widgets/info/contactgeneral.cpp
index ccc6996..e469fcb 100644
--- a/ui/widgets/info/contactgeneral.cpp
+++ b/ui/widgets/info/contactgeneral.cpp
@@ -30,7 +30,6 @@ UI::ContactGeneral::ContactGeneral(QWidget* parent):
     currentAvatarType(Shared::Avatar::empty),
     currentAvatarPath(""),
     currentJid(""),
-    editable(false),
     avatarDiablog(nullptr)
 {
     m_ui->setupUi(this);
@@ -74,6 +73,9 @@ void UI::ContactGeneral::setEditable(bool edit) {
             m_ui->avatarButton->setMenu(nullptr);
         }
     }
+
+    m_ui->actionSetAvatar->setEnabled(edit);
+    m_ui->actionClearAvatar->setEnabled(false); //need to unlock it explicitly after the type of avatar is clear!
 }
 
 void UI::ContactGeneral::deleteAvatarDialog() {
@@ -92,7 +94,7 @@ void UI::ContactGeneral::initializeActions() {
     connect(setAvatar, &QAction::triggered, this, &UI::ContactGeneral::onSetAvatar);
     connect(clearAvatar, &QAction::triggered, this, &UI::ContactGeneral::onClearAvatar);
 
-    setAvatar->setEnabled(editable);
+    setAvatar->setEnabled(false);
     clearAvatar->setEnabled(false);
 }
 
@@ -138,7 +140,7 @@ void UI::ContactGeneral::updateAvatar() {
             qreal aspectRatio = w / h;
             m_ui->avatarButton->setIconSize(QSize(height * aspectRatio, height));
             m_ui->avatarButton->setIcon(QIcon(currentAvatarPath));
-            m_ui->actionClearAvatar->setEnabled(editable);
+            m_ui->actionClearAvatar->setEnabled(m_ui->actionSetAvatar->isEnabled());    //I assume that if set avatar is enabled then we can also clear
             break;
     }
 }
diff --git a/ui/widgets/info/contactgeneral.h b/ui/widgets/info/contactgeneral.h
index 2817e91..33b4c28 100644
--- a/ui/widgets/info/contactgeneral.h
+++ b/ui/widgets/info/contactgeneral.h
@@ -67,7 +67,6 @@ private:
     Shared::Avatar currentAvatarType;
     QString currentAvatarPath;
     QString currentJid;
-    bool editable;
     QFileDialog* avatarDiablog;
 
     static const std::set<QString> supportedTypes;
diff --git a/ui/widgets/info/info.cpp b/ui/widgets/info/info.cpp
index e951e97..a400f22 100644
--- a/ui/widgets/info/info.cpp
+++ b/ui/widgets/info/info.cpp
@@ -45,17 +45,19 @@ UI::Info::~Info() {
 
 void UI::Info::setData(const Shared::Info& info) {
     bool editable = false;
-    switch (info.type) {
+    switch (info.getType()) {
         case Shared::EntryType::ownAccount:
             editable = true;
+            [[fallthrough]];
         case Shared::EntryType::contact: {
-            QDateTime receivingTime = info.vcard.getReceivingTime();
+            const Shared::VCard card = info.getVCardRef();
+            QDateTime receivingTime = card.getReceivingTime();
             m_ui->receivingTimeLabel->show();
             m_ui->receivingTimeLabel->setText(tr("Received %1 at %2").arg(receivingTime.date().toString()).arg(receivingTime.time().toString()));
-            initializeContactGeneral(jid, info.vcard, editable);
-            initializeContactContacts(jid, info.vcard, editable);
-            initializeDescription(info.vcard.getDescription(), editable);
-            type = info.type;
+            initializeContactGeneral(jid, card, editable);
+            initializeContactContacts(jid, card, editable);
+            initializeDescription(card.getDescription(), editable);
+            type = info.getType();
         }
             break;
         default:
@@ -99,11 +101,13 @@ void UI::Info::initializeButtonBox() {
 
 void UI::Info::onButtonBoxAccepted() {
     if (type == Shared::EntryType::ownAccount) {
-        Shared::Info info;
-        contactGeneral->fillVCard(info.vcard);
-        contactContacts->fillVCard(info.vcard);
-        info.vcard.setDescription(description->description());
+        Shared::Info info(jid, Shared::EntryType::ownAccount);
+        Shared::VCard& card = info.getVCardRef();
+        contactGeneral->fillVCard(card);
+        contactContacts->fillVCard(card);
+        card.setDescription(description->description());
         emit saveInfo(info);
+        emit close();
     }
 }
 

From 77dd28b6006c301a679a208926835e516c3fa4fb Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Thu, 2 Mar 2023 21:17:06 +0300
Subject: [PATCH 234/281] some further work on omemo, far from done yet

---
 core/account.cpp                   |  2 ++
 core/handlers/discoveryhandler.cpp |  6 ++---
 core/handlers/omemohandler.cpp     | 13 +++++++++++
 core/handlers/omemohandler.h       | 28 ++++++++++++++---------
 core/handlers/trusthandler.cpp     | 14 ++++++++++++
 core/handlers/trusthandler.h       | 36 ++++++++++++++++--------------
 core/handlers/vcardhandler.cpp     | 16 +++++++++++++
 external/qxmpp                     |  2 +-
 shared/enums.h                     |  4 +++-
 shared/keyinfo.cpp                 |  2 +-
 shared/keyinfo.h                   |  2 +-
 ui/models/info/omemo/keys.cpp      |  6 +++++
 ui/models/info/omemo/keys.h        |  1 +
 ui/widgets/info/info.cpp           | 25 +++++++++++++++++++++
 ui/widgets/info/info.h             | 12 ++++++++++
 ui/widgets/info/omemo/omemo.cpp    | 22 +++++++++++++-----
 ui/widgets/info/omemo/omemo.h      |  9 +++++++-
 ui/widgets/info/omemo/omemo.ui     |  4 ++--
 18 files changed, 161 insertions(+), 43 deletions(-)

diff --git a/core/account.cpp b/core/account.cpp
index 0970a6e..0a6c59c 100644
--- a/core/account.cpp
+++ b/core/account.cpp
@@ -106,6 +106,8 @@ Account::Account(const QString& p_login, const QString& p_server, const QString&
 #ifdef WITH_OMEMO
     client.addExtension(tm);
     client.addExtension(om);
+    om->setSecurityPolicy(QXmpp::Toakafa);
+    om->setNewDeviceAutoSessionBuildingEnabled(true);
 
     if (oh->hasOwnDevice()) {
         QXmppTask<bool> future = om->load();
diff --git a/core/handlers/discoveryhandler.cpp b/core/handlers/discoveryhandler.cpp
index 20e982c..977dda2 100644
--- a/core/handlers/discoveryhandler.cpp
+++ b/core/handlers/discoveryhandler.cpp
@@ -110,10 +110,10 @@ void Core::DiscoveryHandler::onInfoReceived(const QXmppDiscoveryIq& info)
             }
         }
         acc->setPepSupport(pepSupported ? Shared::Support::supported : Shared::Support::unsupported);
-    } else  {
-        qDebug() << "Received info for account" << accName << "about" << from;
+    } else {
         QString node = info.queryNode();
         if (!node.isEmpty()) {
+            qDebug() << "Received features and identities for account" << accName << "about" << from;
             QStringList feats = info.features();
             std::set<Shared::Identity> identities;
             std::set<QString> features(feats.begin(), feats.end());
@@ -135,7 +135,7 @@ void Core::DiscoveryHandler::onInfoReceived(const QXmppDiscoveryIq& info)
         } else {
             Contact* cont = acc->rh->getContact(from);
             if (cont != nullptr) {
-                qDebug() << "Received info for account" << accName << "about" << from;
+                qDebug() << "Received info for account" << accName << "about contact" << from;
                 QList<QXmppDiscoveryIq::Identity> identities = info.identities();
                 bool pepSupported = false;
                 for (const QXmppDiscoveryIq::Identity& identity : identities) {
diff --git a/core/handlers/omemohandler.cpp b/core/handlers/omemohandler.cpp
index 2b864e1..ce82a96 100644
--- a/core/handlers/omemohandler.cpp
+++ b/core/handlers/omemohandler.cpp
@@ -154,6 +154,19 @@ QXmppTask<void> Core::OmemoHandler::resetAll() {
     return Core::makeReadyTask();
 }
 
+void Core::OmemoHandler::getDevices(const QString& jid, std::list<Shared::KeyInfo>& out) const {
+    QHash<uint32_t, Device> devs;
+    try {
+        devs = devices->getRecord(jid);
+    } catch (const DataBase::NotFound& error) {}
+
+    for (QHash<uint32_t, Device>::const_iterator itr = devs.begin(), end = devs.end(); itr != end; ++itr) {
+        const Device& dev = itr.value();
+        out.emplace_back(itr.key(), dev.keyId, dev.label, QDateTime(), Shared::TrustLevel::undecided, Shared::EncryptionProtocol::omemo2, false);
+    }
+}
+
+
 QDataStream & operator >> (QDataStream& in, QXmppOmemoStorage::Device& device) {
     in >> device.label;
     in >> device.keyId;
diff --git a/core/handlers/omemohandler.h b/core/handlers/omemohandler.h
index 7783c04..050677c 100644
--- a/core/handlers/omemohandler.h
+++ b/core/handlers/omemohandler.h
@@ -17,9 +17,15 @@
 #ifndef CORE_OMEMOHANDLER_H
 #define CORE_OMEMOHANDLER_H
 
+#include <map>
+#include <list>
+
 #include <QXmppOmemoStorage.h>
 #include <cache.h>
 
+#include <shared/keyinfo.h>
+#include <shared/enums.h>
+
 Q_DECLARE_METATYPE(QXmppOmemoStorage::OwnDevice);
 Q_DECLARE_METATYPE(QXmppOmemoStorage::Device);
 
@@ -34,24 +40,26 @@ public:
     OmemoHandler(Account* account);
     ~OmemoHandler() override;
 
-    QXmppTask<OmemoData> allData() override;
+    virtual QXmppTask<OmemoData> allData() override;
 
-    QXmppTask<void> setOwnDevice(const std::optional<OwnDevice> &device) override;
+    virtual QXmppTask<void> setOwnDevice(const std::optional<OwnDevice> &device) override;
 
-    QXmppTask<void> addSignedPreKeyPair(uint32_t keyId, const QXmppOmemoStorage::SignedPreKeyPair &keyPair) override;
-    QXmppTask<void> removeSignedPreKeyPair(uint32_t keyId) override;
+    virtual QXmppTask<void> addSignedPreKeyPair(uint32_t keyId, const QXmppOmemoStorage::SignedPreKeyPair &keyPair) override;
+    virtual QXmppTask<void> removeSignedPreKeyPair(uint32_t keyId) override;
 
-    QXmppTask<void> addPreKeyPairs(const QHash<uint32_t, QByteArray> &keyPairs) override;
-    QXmppTask<void> removePreKeyPair(uint32_t keyId) override;
+    virtual QXmppTask<void> addPreKeyPairs(const QHash<uint32_t, QByteArray> &keyPairs) override;
+    virtual QXmppTask<void> removePreKeyPair(uint32_t keyId) override;
 
-    QXmppTask<void> addDevice(const QString &jid, uint32_t deviceId, const Device &device) override;
-    QXmppTask<void> removeDevice(const QString &jid, uint32_t deviceId) override;
-    QXmppTask<void> removeDevices(const QString &jid) override;
+    virtual QXmppTask<void> addDevice(const QString &jid, uint32_t deviceId, const Device &device) override;
+    virtual QXmppTask<void> removeDevice(const QString &jid, uint32_t deviceId) override;
+    virtual QXmppTask<void> removeDevices(const QString &jid) override;
 
-    QXmppTask<void> resetAll() override;
+    virtual QXmppTask<void> resetAll() override;
 
     bool hasOwnDevice();
 
+    void getDevices(const QString& jid, std::list<Shared::KeyInfo>& out) const;
+
 private:
     Account* acc;
     std::optional<OwnDevice> ownDevice;
diff --git a/core/handlers/trusthandler.cpp b/core/handlers/trusthandler.cpp
index 3ea6e47..50fbcbf 100644
--- a/core/handlers/trusthandler.cpp
+++ b/core/handlers/trusthandler.cpp
@@ -326,6 +326,20 @@ QXmppTask<void> TrustHandler::setSecurityPolicy(
     return Core::makeReadyTask();
 }
 
+Core::TrustHandler::Keys Core::TrustHandler::getKeys(const QString& protocol, const QString& jid) const {
+    std::map<QString, KeyCache*>::const_iterator itr = keysByProtocol.find(protocol);
+    if (itr != keysByProtocol.end()) {
+        try {
+            Keys map = itr->second->getRecord(jid);
+            return map;
+        } catch (const DataBase::NotFound& e) {
+            return Keys();
+        }
+    } else {
+        return Keys();
+    }
+}
+
 Shared::TrustLevel Core::TrustHandler::convert(Core::TrustHandler::TL level)
 {
     switch (level) {
diff --git a/core/handlers/trusthandler.h b/core/handlers/trusthandler.h
index e46c7b3..677a4f7 100644
--- a/core/handlers/trusthandler.h
+++ b/core/handlers/trusthandler.h
@@ -41,27 +41,29 @@ public:
     typedef std::map<QByteArray, Shared::TrustLevel> Keys;
     typedef DataBase::Cache<QString, Keys> KeyCache;
 
-    virtual QXmppTask<void> resetAll(CSR encryption);
-    virtual QXmppTask<TL> trustLevel(CSR encryption, CSR keyOwnerJid, const QByteArray& keyId);
-    virtual QXmppTask<HashSM> setTrustLevel(CSR encryption, CLSR keyOwnerJids, TL oldTrustLevel, TL newTrustLevel);
-    virtual QXmppTask<HashSM> setTrustLevel(CSR encryption, const MultySB& keyIds, TL trustLevel);
-    virtual QXmppTask<bool> hasKey(CSR encryption, CSR keyOwnerJid, QXmpp::TrustLevels trustLevels);
-    virtual QXmppTask<HSHBTL> keys(CSR encryption, CLSR keyOwnerJids, QXmpp::TrustLevels trustLevels);
-    virtual QXmppTask<QHash<TL, MultySB>> keys(CSR encryption, QXmpp::TrustLevels trustLevels);
-    virtual QXmppTask<void> removeKeys(CSR encryption);
-    virtual QXmppTask<void> removeKeys(CSR encryption, CSR keyOwnerJid);
-    virtual QXmppTask<void> removeKeys(CSR encryption, CLBAR keyIds);
-    virtual QXmppTask<void> addKeys(CSR encryption, CSR keyOwnerJid, CLBAR keyIds, TL trustLevel);
-    virtual QXmppTask<QByteArray> ownKey(CSR encryption);
-    virtual QXmppTask<void> resetOwnKey(CSR encryption);
-    virtual QXmppTask<void> setOwnKey(CSR encryption, const QByteArray& keyId);
-    virtual QXmppTask<QXmpp::TrustSecurityPolicy> securityPolicy(CSR encryption);
-    virtual QXmppTask<void> resetSecurityPolicy(CSR encryption);
-    virtual QXmppTask<void> setSecurityPolicy(CSR encryption, QXmpp::TrustSecurityPolicy securityPolicy);
+    virtual QXmppTask<void> resetAll(CSR encryption) override;
+    virtual QXmppTask<TL> trustLevel(CSR encryption, CSR keyOwnerJid, const QByteArray& keyId) override;
+    virtual QXmppTask<HashSM> setTrustLevel(CSR encryption, CLSR keyOwnerJids, TL oldTrustLevel, TL newTrustLevel) override;
+    virtual QXmppTask<HashSM> setTrustLevel(CSR encryption, const MultySB& keyIds, TL trustLevel) override;
+    virtual QXmppTask<bool> hasKey(CSR encryption, CSR keyOwnerJid, QXmpp::TrustLevels trustLevels) override;
+    virtual QXmppTask<HSHBTL> keys(CSR encryption, CLSR keyOwnerJids, QXmpp::TrustLevels trustLevels) override;
+    virtual QXmppTask<QHash<TL, MultySB>> keys(CSR encryption, QXmpp::TrustLevels trustLevels) override;
+    virtual QXmppTask<void> removeKeys(CSR encryption) override;
+    virtual QXmppTask<void> removeKeys(CSR encryption, CSR keyOwnerJid) override;
+    virtual QXmppTask<void> removeKeys(CSR encryption, CLBAR keyIds) override;
+    virtual QXmppTask<void> addKeys(CSR encryption, CSR keyOwnerJid, CLBAR keyIds, TL trustLevel) override;
+    virtual QXmppTask<QByteArray> ownKey(CSR encryption) override;
+    virtual QXmppTask<void> resetOwnKey(CSR encryption) override;
+    virtual QXmppTask<void> setOwnKey(CSR encryption, const QByteArray& keyId) override;
+    virtual QXmppTask<QXmpp::TrustSecurityPolicy> securityPolicy(CSR encryption) override;
+    virtual QXmppTask<void> resetSecurityPolicy(CSR encryption) override;
+    virtual QXmppTask<void> setSecurityPolicy(CSR encryption, QXmpp::TrustSecurityPolicy securityPolicy) override;
 
     static TL convert(Shared::TrustLevel level);
     static Shared::TrustLevel convert(TL level);
 
+    Keys getKeys(const QString& protocol, const QString& jid) const;
+
 private:
     KeyCache* createNewCache(const QString& encryption);
     KeyCache* getCache(const QString& encryption);
diff --git a/core/handlers/vcardhandler.cpp b/core/handlers/vcardhandler.cpp
index 33c9cdb..6c5cb5b 100644
--- a/core/handlers/vcardhandler.cpp
+++ b/core/handlers/vcardhandler.cpp
@@ -17,6 +17,8 @@
 #include "vcardhandler.h"
 #include "core/account.h"
 
+constexpr const char* ns_omemo_2 = "urn:xmpp:omemo:2";
+
 Core::VCardHandler::VCardHandler(Account* account):
     QObject(),
     acc(account),
@@ -102,6 +104,20 @@ void Core::VCardHandler::onVCardReceived(const QXmppVCardIq& card) {
 
     Shared::Info info(jid, Shared::EntryType::contact);
     item->handleResponseVCard(card, resource, info.getVCardRef());
+#ifdef WITH_OMEMO
+    std::list<Shared::KeyInfo>& aks = info.getActiveKeysRef();
+    acc->oh->getDevices(jid, aks);
+    std::map<QByteArray, Shared::TrustLevel> trustLevels = acc->th->getKeys(ns_omemo_2, jid);
+
+    qDebug() << "OMEMO info for " << jid << " devices:" << aks.size() << ", trustLevels:" << trustLevels.size();
+    for (Shared::KeyInfo& key : aks) {
+        std::map<QByteArray, Shared::TrustLevel>::const_iterator itr = trustLevels.find(key.fingerPrint);
+        if (itr != trustLevels.end()) {
+            key.trustLevel = itr->second;
+            qDebug() << "Found a trust level for a device!";
+        }
+    }
+#endif
 
     emit acc->infoReady(info);
 }
diff --git a/external/qxmpp b/external/qxmpp
index d2c2acd..9d57624 160000
--- a/external/qxmpp
+++ b/external/qxmpp
@@ -1 +1 @@
-Subproject commit d2c2acd4848f815d0dc3d108f8bc306f9015fc89
+Subproject commit 9d5762499fbddb3dd1ed8eeca16f9db70adc27d0
diff --git a/shared/enums.h b/shared/enums.h
index 46d954a..aff22b9 100644
--- a/shared/enums.h
+++ b/shared/enums.h
@@ -160,7 +160,9 @@ static const TrustLevel TrustLevelHighest = TrustLevel::undecided;
 static const TrustLevel TrustLevelLowest = TrustLevel::authenticated;
 
 enum class EncryptionProtocol {
-    omemo
+    omemo,
+    omemo1,
+    omemo2
 };
 Q_ENUM_NS(EncryptionProtocol)
 
diff --git a/shared/keyinfo.cpp b/shared/keyinfo.cpp
index db3ce2b..f7d9e90 100644
--- a/shared/keyinfo.cpp
+++ b/shared/keyinfo.cpp
@@ -43,7 +43,7 @@ Shared::KeyInfo::KeyInfo():
     label(),
     lastInteraction(),
     trustLevel(TrustLevel::undecided),
-    protocol(EncryptionProtocol::omemo),
+    protocol(EncryptionProtocol::omemo2),
     currentDevice(false)
 {
 }
diff --git a/shared/keyinfo.h b/shared/keyinfo.h
index 1befbc9..8d7db0a 100644
--- a/shared/keyinfo.h
+++ b/shared/keyinfo.h
@@ -36,7 +36,7 @@ public:
         const QString& label,
         const QDateTime& lastInteraction,
         TrustLevel trustLevel,
-        EncryptionProtocol protocol = EncryptionProtocol::omemo,
+        EncryptionProtocol protocol = EncryptionProtocol::omemo2,
         bool currentDevice = false
     );
     KeyInfo();
diff --git a/ui/models/info/omemo/keys.cpp b/ui/models/info/omemo/keys.cpp
index fa753c0..e4dc8d2 100644
--- a/ui/models/info/omemo/keys.cpp
+++ b/ui/models/info/omemo/keys.cpp
@@ -134,5 +134,11 @@ void Models::Keys::setTrustLevel(int row, Shared::TrustLevel level) {
     dataChanged(index, index, {Keys::Dirty});
 }
 
+void Models::Keys::clear() {
+    beginResetModel();
+    keys.clear();
+    modified.clear();
+    endResetModel();
+}
 
 
diff --git a/ui/models/info/omemo/keys.h b/ui/models/info/omemo/keys.h
index 2710bab..49948a2 100644
--- a/ui/models/info/omemo/keys.h
+++ b/ui/models/info/omemo/keys.h
@@ -33,6 +33,7 @@ public:
     ~Keys();
 
     void addKey(const Shared::KeyInfo& info);
+    void clear();
 
     QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const override;
     int rowCount(const QModelIndex& parent = QModelIndex()) const override;
diff --git a/ui/widgets/info/info.cpp b/ui/widgets/info/info.cpp
index a400f22..6a1105d 100644
--- a/ui/widgets/info/info.cpp
+++ b/ui/widgets/info/info.cpp
@@ -24,6 +24,9 @@ UI::Info::Info(const QString& p_jid, QWidget* parent):
     m_ui(new Ui::Info()),
     contactGeneral(nullptr),
     contactContacts(nullptr),
+#ifdef WITH_OMEMO
+    omemo(nullptr),
+#endif
     description(nullptr),
     overlay(new QWidget()),
     progress(new Progress(100)),
@@ -57,6 +60,9 @@ void UI::Info::setData(const Shared::Info& info) {
             initializeContactGeneral(jid, card, editable);
             initializeContactContacts(jid, card, editable);
             initializeDescription(card.getDescription(), editable);
+#ifdef WITH_OMEMO
+            initializeOmemo(info.getActiveKeysRef());
+#endif
             type = info.getType();
         }
             break;
@@ -170,5 +176,24 @@ void UI::Info::clear() {
         description->deleteLater();
         description = nullptr;
     }
+
+#ifdef WITH_OMEMO
+    if (omemo != nullptr) {
+        omemo->deleteLater();
+        omemo = nullptr;
+    }
+#endif
+
     type = Shared::EntryType::none;
 }
+
+#ifdef WITH_OMEMO
+void UI::Info::initializeOmemo(const std::list<Shared::KeyInfo>& keys) {
+    if (omemo == nullptr) {
+        omemo = new Omemo();
+        m_ui->tabWidget->addTab(omemo, omemo->title());
+    }
+    omemo->setData(keys);
+}
+
+#endif
diff --git a/ui/widgets/info/info.h b/ui/widgets/info/info.h
index 14829e7..10bc063 100644
--- a/ui/widgets/info/info.h
+++ b/ui/widgets/info/info.h
@@ -17,6 +17,8 @@
 #ifndef UI_WIDGETS_INFO_H
 #define UI_WIDGETS_INFO_H
 
+#include <list>
+
 #include <QWidget>
 #include <QScopedPointer>
 #include <QGraphicsOpacityEffect>
@@ -30,6 +32,10 @@
 #include "contactgeneral.h"
 #include "contactcontacts.h"
 #include "description.h"
+#ifdef WITH_OMEMO
+#include "omemo/omemo.h"
+#endif
+
 
 namespace UI {
 namespace Ui
@@ -58,6 +64,9 @@ private:
     void initializeContactGeneral(const QString& jid, const Shared::VCard& card, bool editable);
     void initializeContactContacts(const QString& jid, const Shared::VCard& card, bool editable);
     void initializeDescription(const QString& description, bool editable);
+#ifdef WITH_OMEMO
+    void initializeOmemo(const std::list<Shared::KeyInfo>& keys);
+#endif
     void initializeOverlay();
     void initializeButtonBox();
     void clear();
@@ -68,6 +77,9 @@ private:
     QScopedPointer<Ui::Info> m_ui;
     ContactGeneral* contactGeneral;
     ContactContacts* contactContacts;
+#ifdef WITH_OMEMO
+    Omemo* omemo;
+#endif
     Description* description;
     QWidget* overlay;
     Progress* progress;
diff --git a/ui/widgets/info/omemo/omemo.cpp b/ui/widgets/info/omemo/omemo.cpp
index 16414ee..d3722fe 100644
--- a/ui/widgets/info/omemo/omemo.cpp
+++ b/ui/widgets/info/omemo/omemo.cpp
@@ -20,7 +20,7 @@
 #include <random>
 constexpr uint8_t fingerprintLength = 32;
 
-Omemo::Omemo(QWidget* parent):
+UI::Omemo::Omemo(QWidget* parent):
     QWidget(parent),
     m_ui(new Ui::Omemo()),
     keysDelegate(),
@@ -31,8 +31,6 @@ Omemo::Omemo(QWidget* parent):
 {
     m_ui->setupUi(this);
 
-    generateMockData();
-
     m_ui->keysView->setItemDelegate(&keysDelegate);
     m_ui->keysView->setModel(&keysModel);
     m_ui->unusedKeysView->setItemDelegate(&unusedKeysDelegate);
@@ -42,12 +40,12 @@ Omemo::Omemo(QWidget* parent):
     connect(m_ui->keysView, &QWidget::customContextMenuRequested, this, &Omemo::onActiveKeysContextMenu);
 }
 
-Omemo::~Omemo()
+UI::Omemo::~Omemo()
 {
     contextMenu->deleteLater();
 }
 
-void Omemo::generateMockData() {
+void UI::Omemo::generateMockData() {
     std::random_device rd;
     std::uniform_int_distribution<char> dist(CHAR_MIN, CHAR_MAX);
     for (int i = 0; i < 5; ++i) {
@@ -67,7 +65,19 @@ void Omemo::generateMockData() {
     }
 }
 
-void Omemo::onActiveKeysContextMenu(const QPoint& pos) {
+void UI::Omemo::setData(const std::list<Shared::KeyInfo>& keys) {
+    keysModel.clear();
+    unusedKeysModel.clear();
+    for (const Shared::KeyInfo& key : keys) {
+        keysModel.addKey(key);
+    }
+}
+
+const QString UI::Omemo::title() const {
+    return m_ui->OMEMOHeading->text();}
+
+
+void UI::Omemo::onActiveKeysContextMenu(const QPoint& pos) {
     contextMenu->clear();
     QModelIndex index = m_ui->keysView->indexAt(pos);
     if (index.isValid()) {
diff --git a/ui/widgets/info/omemo/omemo.h b/ui/widgets/info/omemo/omemo.h
index 7c6b5a1..a279492 100644
--- a/ui/widgets/info/omemo/omemo.h
+++ b/ui/widgets/info/omemo/omemo.h
@@ -17,6 +17,8 @@
 #ifndef VCARD_OMEMO_H
 #define VCARD_OMEMO_H
 
+#include <list>
+
 #include <QWidget>
 #include <QScopedPointer>
 #include <QMenu>
@@ -24,7 +26,9 @@
 #include "ui/models/info/omemo/keys.h"
 #include "keydelegate.h"
 #include "shared/icons.h"
+#include "shared/keyinfo.h"
 
+namespace UI {
 namespace Ui
 {
 class Omemo;
@@ -36,6 +40,9 @@ public:
     Omemo(QWidget* parent = nullptr);
     ~Omemo();
 
+    void setData(const std::list<Shared::KeyInfo>& keys);
+    const QString title() const;
+
 private slots:
     void onActiveKeysContextMenu(const QPoint& pos);
 
@@ -50,5 +57,5 @@ private:
     Models::Keys unusedKeysModel;
     QMenu* contextMenu;
 };
-
+}
 #endif // VCARD_OMEMO_H
diff --git a/ui/widgets/info/omemo/omemo.ui b/ui/widgets/info/omemo/omemo.ui
index d9f55b1..745b981 100644
--- a/ui/widgets/info/omemo/omemo.ui
+++ b/ui/widgets/info/omemo/omemo.ui
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <ui version="4.0">
- <class>Omemo</class>
- <widget class="QWidget" name="Omemo">
+ <class>UI::Omemo</class>
+ <widget class="QWidget" name="UI::Omemo">
   <property name="geometry">
    <rect>
     <x>0</x>

From 2d8f32c257a001d74fcac87c11614858ec664cc4 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Sat, 4 Mar 2023 00:27:12 +0300
Subject: [PATCH 235/281] some ideas over delay manager

---
 core/CMakeLists.txt                        |  38 +++---
 core/components/CMakeLists.txt             |  20 ++-
 core/components/delaymanager.cpp           | 136 +++++++++++++++++++++
 core/components/delaymanager.h             |  71 +++++++++++
 core/handlers/CMakeLists.txt               |  22 ++--
 core/passwordStorageEngines/CMakeLists.txt |   2 +-
 ui/widgets/info/omemo/CMakeLists.txt       |  18 ++-
 7 files changed, 276 insertions(+), 31 deletions(-)
 create mode 100644 core/components/delaymanager.cpp
 create mode 100644 core/components/delaymanager.h

diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt
index d3327c9..b971a88 100644
--- a/core/CMakeLists.txt
+++ b/core/CMakeLists.txt
@@ -3,22 +3,30 @@ if(WIN32)
     set(SIGNALCATCHER_SOURCE signalcatcher_win32.cpp)
 endif(WIN32)
 
+set(SOURCE_FILES
+    account.cpp
+    adapterfunctions.cpp
+    conference.cpp
+    contact.cpp
+    rosteritem.cpp
+    ${SIGNALCATCHER_SOURCE}
+    squawk.cpp
+)
+
+set(HEADER_FILES
+    account.h
+    adapterfunctions.h
+    conference.h
+    contact.h
+    rosteritem.h
+    signalcatcher.h
+    squawk.h
+)
+
 target_sources(squawk PRIVATE
-  account.cpp
-  account.h
-  adapterfunctions.cpp
-  adapterfunctions.h
-  conference.cpp
-  conference.h
-  contact.cpp
-  contact.h
-  rosteritem.cpp
-  rosteritem.h
-  ${SIGNALCATCHER_SOURCE}
-  signalcatcher.h
-  squawk.cpp
-  squawk.h
-  )
+    ${SOURCE_FILES}
+    ${HEADER_FILES}
+)
 
 target_include_directories(squawk PRIVATE ${LMDB_INCLUDE_DIRS})
 
diff --git a/core/components/CMakeLists.txt b/core/components/CMakeLists.txt
index 5faf837..0e26037 100644
--- a/core/components/CMakeLists.txt
+++ b/core/components/CMakeLists.txt
@@ -1,6 +1,16 @@
-target_sources(squawk PRIVATE
-  networkaccess.cpp
-  networkaccess.h
-  clientcache.cpp
-  clientcache.h
+set(SOURCE_FILES
+    networkaccess.cpp
+    clientcache.cpp
+    delaymanager.cpp
+)
+
+set(HEADER_FILES
+    networkaccess.h
+    clientcache.h
+    delaymanager.h
+)
+
+target_sources(squawk PRIVATE
+    ${SOURCE_FILES}
+    ${HEADER_FILES}
 )
diff --git a/core/components/delaymanager.cpp b/core/components/delaymanager.cpp
new file mode 100644
index 0000000..627eab8
--- /dev/null
+++ b/core/components/delaymanager.cpp
@@ -0,0 +1,136 @@
+// Squawk messenger. 
+// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+// 
+// 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 "delaymanager.h"
+
+Core::DelayManager::DelayManager(uint16_t mpj, QObject* parent) :
+    QObject(parent),
+    maxParallelJobs(mpj),
+    nextJobId(0),
+    scheduledJobs(),
+    runningJobs(),
+    pendingVCards(),
+    requestedVCards()
+{
+}
+
+Core::DelayManager::~DelayManager() {}
+
+void Core::DelayManager::requestInfo(const QString& jid) {
+    bool needToRequest = false;
+    std::pair<std::set<QString>::const_iterator, bool> result = pendingVCards.insert(jid);
+    if (result.second) {        //if there is a clear evidence that we have not alredy been asked to request a VCard - just request it
+        needToRequest = true;
+    } else {
+        std::map<QString, uint16_t>::const_iterator itr = requestedVCards.find(jid);
+        if (itr != requestedVCards.end()) {     //first check if the card is already requested, and if it is make sure we reply to user after we receive it
+            runningJobs[itr->second].first = TaskType::infoForUser;
+        } else {
+            needToRequest = true;
+            for (Job& job : scheduledJobs) {    //looks like we need to manually check all the scheduled job and find the one with that jid there
+                if (job.first == TaskType::cardInternal || job.first == TaskType::infoForUser) {    //to make sure we reply to user after we receive it
+                    QString* jobJid = static_cast<QString*>(job.second);
+                    if (*jobJid == jid) {
+                        needToRequest = false;
+                        job.first = TaskType::infoForUser;
+                        break;
+                    }
+                }
+            }
+            if (needToRequest) {
+                throw 8573; //something went terribly wrong here, the state is not correct;
+            }
+        }
+    }
+
+    if (needToRequest)
+        scheduleJob(TaskType::infoForUser, new QString(jid));
+}
+
+void Core::DelayManager::requestVCard(const QString& jid) {
+    std::pair<std::set<QString>::const_iterator, bool> result = pendingVCards.insert(jid);
+    if (result.second)
+        scheduleJob(TaskType::cardInternal, new QString(jid));
+}
+
+void Core::DelayManager::scheduleJob(Core::DelayManager::TaskType type, void* data) {
+    if (runningJobs.size() < maxParallelJobs) {
+        uint16_t currentJobId = nextJobId++;
+        runningJobs.emplace(currentJobId, std::make_pair(type, data));
+        switch (type) {
+            case TaskType::cardInternal:
+            case TaskType::infoForUser: {
+                QString* jid = static_cast<QString*>(data);
+                requestedVCards.emplace(*jid, currentJobId);
+                emit requestVCard(*jid);
+            }
+                break;
+        }
+    } else {
+        scheduledJobs.emplace_back(type, data);
+    }
+}
+
+void Core::DelayManager::executeJob(Core::DelayManager::Job job) {
+    uint16_t currentJobId = nextJobId++;
+    runningJobs.emplace(currentJobId, job);
+    switch (job.first) {
+        case TaskType::cardInternal:
+        case TaskType::infoForUser: {
+            QString* jid = static_cast<QString*>(job.second);
+            requestedVCards.emplace(*jid, currentJobId);
+            emit requestVCard(*jid);
+        }
+            break;
+    }
+}
+
+
+void Core::DelayManager::jobIsDone(uint16_t jobId) {
+    std::map<uint16_t, Job>::const_iterator itr = runningJobs.find(jobId);
+    if (itr == runningJobs.end()) {
+        throw 8574; //not supposed to happen, never
+    }
+    runningJobs.erase(itr);
+    if (scheduledJobs.size() > 0) {
+        Job job = scheduledJobs.front();
+        scheduledJobs.pop_front();
+        executeJob(job);
+    }
+}
+
+
+void Core::DelayManager::receivedVCard(const QString& jid, const Shared::VCard& card) {
+    std::map<QString, uint16_t>::const_iterator cardItr = requestedVCards.find(jid);
+    if (cardItr == requestedVCards.end()) {
+        throw 8575; //never supposed to happen, the state is not correct;
+    } else {
+        uint16_t jobId = cardItr->second;
+        requestedVCards.erase(cardItr);
+        pendingVCards.erase(jid);
+        const Job& job = runningJobs[jobId];
+        switch (job.first) {
+            case TaskType::cardInternal:
+                jobIsDone(jobId);
+                break;
+            case TaskType::infoForUser:
+                //all that stuff with further requesting
+                break;
+            default:
+                throw 8576;
+        }
+    }
+}
diff --git a/core/components/delaymanager.h b/core/components/delaymanager.h
new file mode 100644
index 0000000..f34b00e
--- /dev/null
+++ b/core/components/delaymanager.h
@@ -0,0 +1,71 @@
+// Squawk messenger. 
+// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+// 
+// 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/>.
+
+#ifndef CORE_DELAYMANAGER_H
+#define CORE_DELAYMANAGER_H
+
+#include <list>
+#include <set>
+
+#include <QObject>
+#include <QString>
+
+#include <shared/vcard.h>
+#include <shared/info.h>
+
+namespace Core {
+
+class DelayManager : public QObject
+{
+    Q_OBJECT
+
+    enum class TaskType {
+        cardInternal,
+        infoForUser
+    }
+public:
+    DelayManager(uint16_t maxParallelJobs = 5, QObject* parent = nullptr);
+    ~DelayManager();
+
+    void requestVCard(const QString& jid);
+    void requestInfo(const QString& jid);
+
+signals:
+    void requestVCard(const QString& jid);
+
+public slots:
+    void receivedVCard(const QString& jid, const Shared::VCard& card);
+
+private:
+    typedef std::pair<TaskType, void*> Job;
+
+    void scheduleJob(TaskType type, void* data);
+    void executeJob(Job job);
+    void jobIsDone(uint16_t jobId);
+
+private:
+    uint16_t maxParallelJobs;
+    uint16_t nextJobId;
+    std::list<Job> scheduledJobs;
+    std::map<uint16_t, Job> runningJobs;
+
+    std::set<QString> pendingVCards;
+    std::map<QString, uint16_t> requestedVCards;
+};
+
+}
+
+#endif // CORE_DELAYMANAGER_H
diff --git a/core/handlers/CMakeLists.txt b/core/handlers/CMakeLists.txt
index 746a36f..11a902d 100644
--- a/core/handlers/CMakeLists.txt
+++ b/core/handlers/CMakeLists.txt
@@ -1,14 +1,22 @@
-target_sources(squawk PRIVATE
+set(SOURCE_FILES
   messagehandler.cpp
-  messagehandler.h
   rosterhandler.cpp
-  rosterhandler.h
   vcardhandler.cpp
-  vcardhandler.h
   discoveryhandler.cpp
-  discoveryhandler.h
   omemohandler.cpp
-  omemohandler.h
   trusthandler.cpp
+)
+
+set(HEADER_FILES
+  messagehandler.h
+  rosterhandler.h
+  vcardhandler.h
+  discoveryhandler.h
+  omemohandler.h
   trusthandler.h
-  )
+)
+
+target_sources(squawk PRIVATE
+    ${SOURCE_FILES}
+    ${HEADER_FILES}
+)
diff --git a/core/passwordStorageEngines/CMakeLists.txt b/core/passwordStorageEngines/CMakeLists.txt
index 4da3873..2afda3f 100644
--- a/core/passwordStorageEngines/CMakeLists.txt
+++ b/core/passwordStorageEngines/CMakeLists.txt
@@ -2,7 +2,7 @@ if (WITH_KWALLET)
   target_sources(squawk PRIVATE
     kwallet.cpp
     kwallet.h
-    )
+  )
 
   add_subdirectory(wrappers)
   target_include_directories(squawk PRIVATE $<TARGET_PROPERTY:KF5::Wallet,INTERFACE_INCLUDE_DIRECTORIES>)
diff --git a/ui/widgets/info/omemo/CMakeLists.txt b/ui/widgets/info/omemo/CMakeLists.txt
index f1dc4ed..4c76900 100644
--- a/ui/widgets/info/omemo/CMakeLists.txt
+++ b/ui/widgets/info/omemo/CMakeLists.txt
@@ -1,7 +1,19 @@
-target_sources(squawk PRIVATE
+set(SOURCE_FILES
     omemo.cpp
-    omemo.h
-    omemo.ui
     keydelegate.cpp
+)
+
+set(UI_FILES
+    omemo.ui
+)
+
+set(HEADER_FILES
+    omemo.h
     keydelegate.h
 )
+
+target_sources(squawk PRIVATE
+    ${SOURCE_FILES}
+    ${UI_FILES}
+    ${HEADER_FILES}
+)

From 99fd001292b4841564504d487137875e60495b8b Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Sun, 5 Mar 2023 01:36:53 +0300
Subject: [PATCH 236/281] some more thinking about delay manager

---
 core/CMakeLists.txt                   |   1 +
 core/components/CMakeLists.txt        |   2 -
 core/components/delaymanager.cpp      | 136 --------------
 core/components/delaymanager.h        |  71 -------
 core/delayManager/CMakeLists.txt      |  22 +++
 core/delayManager/cardinternal.cpp    |  32 ++++
 core/delayManager/cardinternal.h      |  41 +++++
 core/delayManager/delaymanager.cpp    | 255 ++++++++++++++++++++++++++
 core/delayManager/delaymanager.h      | 116 ++++++++++++
 core/delayManager/infoforuser.cpp     |  26 +++
 core/delayManager/infoforuser.h       |  32 ++++
 core/delayManager/job.cpp             |  32 ++++
 core/delayManager/job.h               |  50 +++++
 core/delayManager/owncardinternal.cpp |  30 +++
 core/delayManager/owncardinternal.h   |  35 ++++
 core/delayManager/owninfoforuser.cpp  |  25 +++
 core/delayManager/owninfoforuser.h    |  32 ++++
 17 files changed, 729 insertions(+), 209 deletions(-)
 delete mode 100644 core/components/delaymanager.cpp
 delete mode 100644 core/components/delaymanager.h
 create mode 100644 core/delayManager/CMakeLists.txt
 create mode 100644 core/delayManager/cardinternal.cpp
 create mode 100644 core/delayManager/cardinternal.h
 create mode 100644 core/delayManager/delaymanager.cpp
 create mode 100644 core/delayManager/delaymanager.h
 create mode 100644 core/delayManager/infoforuser.cpp
 create mode 100644 core/delayManager/infoforuser.h
 create mode 100644 core/delayManager/job.cpp
 create mode 100644 core/delayManager/job.h
 create mode 100644 core/delayManager/owncardinternal.cpp
 create mode 100644 core/delayManager/owncardinternal.h
 create mode 100644 core/delayManager/owninfoforuser.cpp
 create mode 100644 core/delayManager/owninfoforuser.h

diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt
index b971a88..01c6d8f 100644
--- a/core/CMakeLists.txt
+++ b/core/CMakeLists.txt
@@ -34,3 +34,4 @@ add_subdirectory(handlers)
 add_subdirectory(storage)
 add_subdirectory(passwordStorageEngines)
 add_subdirectory(components)
+add_subdirectory(delayManager)
diff --git a/core/components/CMakeLists.txt b/core/components/CMakeLists.txt
index 0e26037..b78794a 100644
--- a/core/components/CMakeLists.txt
+++ b/core/components/CMakeLists.txt
@@ -1,13 +1,11 @@
 set(SOURCE_FILES
     networkaccess.cpp
     clientcache.cpp
-    delaymanager.cpp
 )
 
 set(HEADER_FILES
     networkaccess.h
     clientcache.h
-    delaymanager.h
 )
 
 target_sources(squawk PRIVATE
diff --git a/core/components/delaymanager.cpp b/core/components/delaymanager.cpp
deleted file mode 100644
index 627eab8..0000000
--- a/core/components/delaymanager.cpp
+++ /dev/null
@@ -1,136 +0,0 @@
-// Squawk messenger. 
-// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
-// 
-// 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 "delaymanager.h"
-
-Core::DelayManager::DelayManager(uint16_t mpj, QObject* parent) :
-    QObject(parent),
-    maxParallelJobs(mpj),
-    nextJobId(0),
-    scheduledJobs(),
-    runningJobs(),
-    pendingVCards(),
-    requestedVCards()
-{
-}
-
-Core::DelayManager::~DelayManager() {}
-
-void Core::DelayManager::requestInfo(const QString& jid) {
-    bool needToRequest = false;
-    std::pair<std::set<QString>::const_iterator, bool> result = pendingVCards.insert(jid);
-    if (result.second) {        //if there is a clear evidence that we have not alredy been asked to request a VCard - just request it
-        needToRequest = true;
-    } else {
-        std::map<QString, uint16_t>::const_iterator itr = requestedVCards.find(jid);
-        if (itr != requestedVCards.end()) {     //first check if the card is already requested, and if it is make sure we reply to user after we receive it
-            runningJobs[itr->second].first = TaskType::infoForUser;
-        } else {
-            needToRequest = true;
-            for (Job& job : scheduledJobs) {    //looks like we need to manually check all the scheduled job and find the one with that jid there
-                if (job.first == TaskType::cardInternal || job.first == TaskType::infoForUser) {    //to make sure we reply to user after we receive it
-                    QString* jobJid = static_cast<QString*>(job.second);
-                    if (*jobJid == jid) {
-                        needToRequest = false;
-                        job.first = TaskType::infoForUser;
-                        break;
-                    }
-                }
-            }
-            if (needToRequest) {
-                throw 8573; //something went terribly wrong here, the state is not correct;
-            }
-        }
-    }
-
-    if (needToRequest)
-        scheduleJob(TaskType::infoForUser, new QString(jid));
-}
-
-void Core::DelayManager::requestVCard(const QString& jid) {
-    std::pair<std::set<QString>::const_iterator, bool> result = pendingVCards.insert(jid);
-    if (result.second)
-        scheduleJob(TaskType::cardInternal, new QString(jid));
-}
-
-void Core::DelayManager::scheduleJob(Core::DelayManager::TaskType type, void* data) {
-    if (runningJobs.size() < maxParallelJobs) {
-        uint16_t currentJobId = nextJobId++;
-        runningJobs.emplace(currentJobId, std::make_pair(type, data));
-        switch (type) {
-            case TaskType::cardInternal:
-            case TaskType::infoForUser: {
-                QString* jid = static_cast<QString*>(data);
-                requestedVCards.emplace(*jid, currentJobId);
-                emit requestVCard(*jid);
-            }
-                break;
-        }
-    } else {
-        scheduledJobs.emplace_back(type, data);
-    }
-}
-
-void Core::DelayManager::executeJob(Core::DelayManager::Job job) {
-    uint16_t currentJobId = nextJobId++;
-    runningJobs.emplace(currentJobId, job);
-    switch (job.first) {
-        case TaskType::cardInternal:
-        case TaskType::infoForUser: {
-            QString* jid = static_cast<QString*>(job.second);
-            requestedVCards.emplace(*jid, currentJobId);
-            emit requestVCard(*jid);
-        }
-            break;
-    }
-}
-
-
-void Core::DelayManager::jobIsDone(uint16_t jobId) {
-    std::map<uint16_t, Job>::const_iterator itr = runningJobs.find(jobId);
-    if (itr == runningJobs.end()) {
-        throw 8574; //not supposed to happen, never
-    }
-    runningJobs.erase(itr);
-    if (scheduledJobs.size() > 0) {
-        Job job = scheduledJobs.front();
-        scheduledJobs.pop_front();
-        executeJob(job);
-    }
-}
-
-
-void Core::DelayManager::receivedVCard(const QString& jid, const Shared::VCard& card) {
-    std::map<QString, uint16_t>::const_iterator cardItr = requestedVCards.find(jid);
-    if (cardItr == requestedVCards.end()) {
-        throw 8575; //never supposed to happen, the state is not correct;
-    } else {
-        uint16_t jobId = cardItr->second;
-        requestedVCards.erase(cardItr);
-        pendingVCards.erase(jid);
-        const Job& job = runningJobs[jobId];
-        switch (job.first) {
-            case TaskType::cardInternal:
-                jobIsDone(jobId);
-                break;
-            case TaskType::infoForUser:
-                //all that stuff with further requesting
-                break;
-            default:
-                throw 8576;
-        }
-    }
-}
diff --git a/core/components/delaymanager.h b/core/components/delaymanager.h
deleted file mode 100644
index f34b00e..0000000
--- a/core/components/delaymanager.h
+++ /dev/null
@@ -1,71 +0,0 @@
-// Squawk messenger. 
-// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
-// 
-// 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/>.
-
-#ifndef CORE_DELAYMANAGER_H
-#define CORE_DELAYMANAGER_H
-
-#include <list>
-#include <set>
-
-#include <QObject>
-#include <QString>
-
-#include <shared/vcard.h>
-#include <shared/info.h>
-
-namespace Core {
-
-class DelayManager : public QObject
-{
-    Q_OBJECT
-
-    enum class TaskType {
-        cardInternal,
-        infoForUser
-    }
-public:
-    DelayManager(uint16_t maxParallelJobs = 5, QObject* parent = nullptr);
-    ~DelayManager();
-
-    void requestVCard(const QString& jid);
-    void requestInfo(const QString& jid);
-
-signals:
-    void requestVCard(const QString& jid);
-
-public slots:
-    void receivedVCard(const QString& jid, const Shared::VCard& card);
-
-private:
-    typedef std::pair<TaskType, void*> Job;
-
-    void scheduleJob(TaskType type, void* data);
-    void executeJob(Job job);
-    void jobIsDone(uint16_t jobId);
-
-private:
-    uint16_t maxParallelJobs;
-    uint16_t nextJobId;
-    std::list<Job> scheduledJobs;
-    std::map<uint16_t, Job> runningJobs;
-
-    std::set<QString> pendingVCards;
-    std::map<QString, uint16_t> requestedVCards;
-};
-
-}
-
-#endif // CORE_DELAYMANAGER_H
diff --git a/core/delayManager/CMakeLists.txt b/core/delayManager/CMakeLists.txt
new file mode 100644
index 0000000..f9ccb57
--- /dev/null
+++ b/core/delayManager/CMakeLists.txt
@@ -0,0 +1,22 @@
+set(SOURCE_FILES
+    delaymanager.cpp
+    job.cpp
+    cardinternal.cpp
+    infoforuser.cpp
+    owncardinternal.cpp
+    owninfoforuser.cpp
+)
+
+set(HEADER_FILES
+    delaymanager.h
+    job.h
+    cardinternal.h
+    infoforuser.h
+    owncardinternal.h
+    owninfoforuser.h
+)
+
+target_sources(squawk PRIVATE
+    ${SOURCE_FILES}
+    ${HEADER_FILES}
+)
diff --git a/core/delayManager/cardinternal.cpp b/core/delayManager/cardinternal.cpp
new file mode 100644
index 0000000..c7a599a
--- /dev/null
+++ b/core/delayManager/cardinternal.cpp
@@ -0,0 +1,32 @@
+// Squawk messenger. 
+// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+// 
+// 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 "cardinternal.h"
+
+Core::CardInternal::CardInternal(Job::Id p_id, const QString& p_jid) :
+    Job(p_id, Type::cardInternal),
+    jid(p_id)
+{}
+
+Core::CardInternal::CardInternal(Job::Id p_id, const QString& p_jid, Job::Type p_type) :
+    Job(p_id, p_type),
+    jid(p_id)
+{}
+
+Core::CardInternal::CardInternal(const Core::CardInternal& other) :
+    Job(other),
+    jid(other.jid)
+{}
diff --git a/core/delayManager/cardinternal.h b/core/delayManager/cardinternal.h
new file mode 100644
index 0000000..a95f0ba
--- /dev/null
+++ b/core/delayManager/cardinternal.h
@@ -0,0 +1,41 @@
+// Squawk messenger. 
+// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+// 
+// 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/>.
+
+#ifndef CORE_CARDINTERNAL_H
+#define CORE_CARDINTERNAL_H
+
+
+#include <QString>
+
+#include "job.h"
+
+namespace Core {
+
+class CardInternal : public Job {
+protected:
+    CardInternal(Job::Id id, const QString& jid, Job::Type type);
+
+public:
+    CardInternal(Job::Id id, const QString& jid);
+    CardInternal(const CardInternal& other);
+
+    const QString jid;
+
+};
+
+}
+
+#endif // CORE_CARDINTERNAL_H
diff --git a/core/delayManager/delaymanager.cpp b/core/delayManager/delaymanager.cpp
new file mode 100644
index 0000000..6ebc85d
--- /dev/null
+++ b/core/delayManager/delaymanager.cpp
@@ -0,0 +1,255 @@
+// Squawk messenger. 
+// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+// 
+// 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 "delaymanager.h"
+
+#include "cardinternal.h"
+#include "infoforuser.h"
+#include "owncardinternal.h"
+#include "owninfoforuser.h"
+
+Core::DelayManager::DelayManager(Job::Id mpj, QObject* parent) :
+    QObject(parent),
+    maxParallelJobs(mpj),
+    nextJobId(1),
+    scheduledJobs(),
+    scheduledJobsById(scheduledJobs.get<id>()),
+    jobSequence(scheduledJobs.get<sequence>()),
+    runningJobs(),
+    ownVCardJobId(0),
+    ownInfoJobId(0),
+    scheduledVCards(),
+    requestedVCards(),
+    requestedBundles()
+{
+}
+
+Core::DelayManager::~DelayManager() {}
+
+Core::Job::Id Core::DelayManager::getNextJobId() {
+    Job::Id id = nextJobId++;
+    if (id == 0)
+        id = nextJobId++;
+
+    return id;
+}
+
+void Core::DelayManager::getInfo(const QString& jid) {
+    Job* job = nullptr;
+#ifdef WITH_OMEMO
+    std::map<QString, Job::Id>::const_iterator bitr = requestedBundles.find(jid);
+    if (bitr != requestedBundles.end())
+        job = runningJobs.at(bitr->second);
+    else
+#endif
+        job = getVCardJob(jid);
+
+    if (job != nullptr) {
+        if (job->getType() == Job::Type::cardInternal)
+            replaceJob(new InfoForUser(job->id, jid));
+    } else
+        scheduleJob(new InfoForUser(getNextJobId(), jid));
+}
+
+void Core::DelayManager::getOwnInfo() {
+    if (ownInfoJobId == 0) {
+        if (ownVCardJobId != 0)
+            replaceJob(new OwnInfoForUser(ownVCardJobId));
+        else
+            scheduleJob(new OwnInfoForUser(getNextJobId()));
+    }
+}
+
+void Core::DelayManager::getVCard(const QString& jid) {
+    Job* job = getVCardJob(jid);
+    if (job == nullptr)
+        scheduleJob(new CardInternal(getNextJobId(), jid));
+}
+
+void Core::DelayManager::getOwnVCard() {
+    if (ownInfoJobId == 0)
+        scheduleJob(new OwnCardInternal(getNextJobId()));
+}
+
+Core::Job* Core::DelayManager::getVCardJob(const QString& jid) {
+    Job* job = nullptr;
+    std::map<QString, Job::Id>::const_iterator sitr = scheduledVCards.find(jid);
+    if (sitr == scheduledVCards.end()) {
+        std::map<QString, Job::Id>::const_iterator ritr = requestedVCards.find(jid);
+        if (ritr != requestedVCards.end())
+            job = runningJobs.at(ritr->second);
+    } else {
+        job = *(scheduledJobsById.find(sitr->second));
+    }
+
+    return job;
+}
+void Core::DelayManager::preScheduleJob(Core::Job* job) {
+    switch (job->getType()) {
+        case Job::Type::cardInternal:
+            scheduledVCards.emplace(static_cast<CardInternal*>(job)->jid, job->id);
+            break;
+        case Job::Type::ownCardInternal:
+            ownVCardJobId = job->id;
+            break;
+        case Job::Type::infoForUser:
+            scheduledVCards.emplace(static_cast<InfoForUser*>(job)->jid, job->id);
+            break;
+        case Job::Type::ownInfoForUser:
+            ownVCardJobId = job->id;
+            ownInfoJobId = job->id;
+            break;
+    }
+}
+
+void Core::DelayManager::scheduleJob(Core::Job* job) {
+    preScheduleJob(job);
+    if (runningJobs.size() < maxParallelJobs) {
+        executeJob(job);
+    } else {
+        scheduledJobs.push_back(job);
+    }
+}
+
+void Core::DelayManager::preExecuteJob(Core::Job* job) {
+    switch (job->getType()) {
+        case Job::Type::cardInternal:
+        case Job::Type::infoForUser: {
+            CardInternal* cij = static_cast<CardInternal*>(job);
+            requestedVCards.emplace(cij->jid, job->id);
+            scheduledVCards.erase(cij->jid);
+        }
+            break;
+        case Job::Type::ownInfoForUser:
+        case Job::Type::ownCardInternal:
+            break;
+    }
+}
+
+void Core::DelayManager::executeJob(Core::Job* job) {
+    preExecuteJob(job);
+    runningJobs.emplace(job->id, job);
+    switch (job->getType()) {
+        case Job::Type::cardInternal:
+        case Job::Type::infoForUser:
+            emit requestVCard(static_cast<CardInternal*>(job)->jid);
+            break;
+        case Job::Type::ownInfoForUser:
+        case Job::Type::ownCardInternal:
+            emit requestOwnVCard();
+            break;
+    }
+}
+
+void Core::DelayManager::jobIsDone(Job::Id jobId) {
+    std::map<Job::Id, Job*>::const_iterator itr = runningJobs.find(jobId);
+    if (itr == runningJobs.end()) {
+        throw 8573; //not supposed to happen, ever
+    }
+    Job* job = itr->second;
+    delete job;
+    runningJobs.erase(itr);
+    if (scheduledJobs.size() > 0) {
+        Job* job = scheduledJobs.front();
+        scheduledJobs.pop_front();
+        executeJob(job);
+    }
+}
+
+void Core::DelayManager::replaceJob(Core::Job* job) {
+    preScheduleJob(job);
+    std::map<Job::Id, Job*>::iterator itr = runningJobs.find(job->id);
+    if (itr != runningJobs.end()) {
+        preExecuteJob(job);
+        delete itr->second;
+        itr->second = job;
+    } else {
+        StorageById::iterator sitr = scheduledJobsById.find(job->id);
+        if (sitr != scheduledJobsById.end()) {
+            delete *(sitr);
+            scheduledJobsById.replace(sitr, job);
+        } else {
+            throw 8574; //not supposed to happen, ever
+        }
+    }
+}
+
+void Core::DelayManager::receivedVCard(const QString& jid, const Shared::VCard& card) {
+    std::map<QString, Job::Id>::const_iterator cardItr = requestedVCards.find(jid);
+    if (cardItr == requestedVCards.end()) {
+        throw 8575; //never supposed to happen, the state is not correct;
+    } else {
+        Job::Id jobId = cardItr->second;
+        requestedVCards.erase(cardItr);
+        Job* job = runningJobs.at(jobId);
+        switch (job->getType()) {
+            case Job::Type::cardInternal:
+                jobIsDone(jobId);
+                emit receivedCard(jid, card);
+                break;
+            case Job::Type::infoForUser:
+#ifdef WITH_OMEMO
+                requestedBundles.emplace(jid, jobId);
+                //TODO save card!
+                emit requestBundles(jid);
+#else
+                {
+                    Shared::Info info(jid);
+                    info.turnIntoContact(card);
+                    emit receivedInfo(info);
+                }
+                jobIsDone(jobId);
+#endif
+                emit receivedCard(jid, card);
+                break;
+            default:
+                throw 8576;
+        }
+    }
+}
+
+void Core::DelayManager::ownVCardReceived(const Shared::VCard& card) {
+    Job::Id jobId = ownVCardJobId;
+    ownVCardJobId = 0;
+    Job* job = runningJobs.at(jobId);
+    switch (job->getType()) {
+        case Job::Type::ownCardInternal:
+            jobIsDone(jobId);
+            emit receivedOwnCard(card);
+            break;
+        case Job::Type::ownInfoForUser:
+#ifdef WITH_OMEMO
+            //requestedBundles.emplace(jid, jobId);
+            //TODO save card!
+            emit requestOwnBundle();
+#else
+            {
+                Shared::Info info("");
+                info.turnIntoOwnAccount(card);
+                emit receivedOwnInfo(info);
+            }
+            jobIsDone(jobId);
+#endif
+            emit receivedOwnCard(card);
+            break;
+        default:
+            throw 8576;
+    }
+}
+
+void Core::DelayManager::receivedBundles(const QString& jid) {
+
+}
diff --git a/core/delayManager/delaymanager.h b/core/delayManager/delaymanager.h
new file mode 100644
index 0000000..e4f3e2a
--- /dev/null
+++ b/core/delayManager/delaymanager.h
@@ -0,0 +1,116 @@
+// Squawk messenger. 
+// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+// 
+// 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/>.
+
+#ifndef CORE_DELAYMANAGER_H
+#define CORE_DELAYMANAGER_H
+
+#include <list>
+#include <set>
+
+#include <boost/multi_index_container.hpp>
+#include <boost/multi_index/ordered_index.hpp>
+#include <boost/multi_index/sequenced_index.hpp>
+#include <boost/multi_index/member.hpp>
+
+#include <QObject>
+#include <QString>
+
+#include <shared/vcard.h>
+#include <shared/info.h>
+
+#include "job.h"
+
+namespace Core {
+
+class DelayManager : public QObject
+{
+    Q_OBJECT
+public:
+    DelayManager(Job::Id maxParallelJobs = 5, QObject* parent = nullptr);
+    ~DelayManager();
+
+    void getOwnVCard();
+    void getOwnInfo();
+    void getVCard(const QString& jid);
+    void getInfo(const QString& jid);
+
+signals:
+    void requestVCard(const QString& jid);
+    void requestOwnVCard();
+    void requestBundles(const QString& jid);
+    void requestOwnBundle();
+    void receivedCard(const QString& jid, const Shared::VCard& info);
+    void receivedOwnCard(const Shared::VCard& info);
+    void receivedInfo(const Shared::Info& info);
+    void receivedOwnInfo(const Shared::Info& info);
+
+public slots:
+    void ownVCardReceived(const Shared::VCard& card);
+    void receivedVCard(const QString& jid, const Shared::VCard& card);
+    void receivedBundles(const QString& jid);
+
+private:
+    void preScheduleJob(Job* job);
+    void scheduleJob(Job* job);
+    void preExecuteJob(Job* job);
+    void executeJob(Job* job);
+    void jobIsDone(Job::Id jobId);
+    Job::Id getNextJobId();
+    void replaceJob(Job* job);
+    Job* getVCardJob(const QString& jid);
+
+private:
+    struct id {};
+    struct sequence {};
+
+    typedef boost::multi_index_container<
+        Job*,
+        boost::multi_index::indexed_by<
+            boost::multi_index::sequenced<
+                boost::multi_index::tag<sequence>
+            >,
+            boost::multi_index::ordered_unique<
+                boost::multi_index::tag<id>,
+                boost::multi_index::member<
+                    Job,
+                    const Job::Id,
+                    &Job::id
+                >
+            >
+        >
+    > Storage;
+
+
+    typedef Storage::index<id>::type StorageById;
+    typedef Storage::index<sequence>::type StorageSequence;
+    Job::Id maxParallelJobs;
+    Job::Id nextJobId;
+
+    Storage scheduledJobs;
+    StorageById& scheduledJobsById;
+    StorageSequence& jobSequence;
+    std::map<Job::Id, Job*> runningJobs;
+
+    Job::Id ownVCardJobId;
+    Job::Id ownInfoJobId;
+    std::map<QString, Job::Id> scheduledVCards;
+    std::map<QString, Job::Id> requestedVCards;
+    std::map<QString, Job::Id> requestedBundles;
+};
+
+}
+
+#endif // CORE_DELAYMANAGER_H
diff --git a/core/delayManager/infoforuser.cpp b/core/delayManager/infoforuser.cpp
new file mode 100644
index 0000000..31d0571
--- /dev/null
+++ b/core/delayManager/infoforuser.cpp
@@ -0,0 +1,26 @@
+// Squawk messenger. 
+// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+// 
+// 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 "infoforuser.h"
+
+Core::InfoForUser::InfoForUser(Job::Id p_id, const QString& p_jid) :
+    CardInternal(p_id, p_jid, Type::infoForUser)
+{}
+
+Core::InfoForUser::InfoForUser(const Core::InfoForUser& other) :
+    CardInternal(other)
+{}
+
diff --git a/core/delayManager/infoforuser.h b/core/delayManager/infoforuser.h
new file mode 100644
index 0000000..2bc84e4
--- /dev/null
+++ b/core/delayManager/infoforuser.h
@@ -0,0 +1,32 @@
+// Squawk messenger. 
+// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+// 
+// 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/>.
+
+#ifndef CORE_INFOFORUSER_H
+#define CORE_INFOFORUSER_H
+
+#include "cardinternal.h"
+
+namespace Core {
+
+class InfoForUser : public CardInternal {
+public:
+    InfoForUser(Job::Id id, const QString& jid);
+    InfoForUser(const InfoForUser& other);
+};
+
+}
+
+#endif // CORE_INFOFORUSER_H
diff --git a/core/delayManager/job.cpp b/core/delayManager/job.cpp
new file mode 100644
index 0000000..0d4c868
--- /dev/null
+++ b/core/delayManager/job.cpp
@@ -0,0 +1,32 @@
+// Squawk messenger. 
+// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+// 
+// 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 "job.h"
+
+Core::Job::Job(Core::Job::Id p_id, Core::Job::Type p_type) :
+    id (p_id),
+    type (p_type) {}
+
+
+Core::Job::Job(const Core::Job& other) :
+    id(other.id),
+    type(other.type) {}
+
+Core::Job::~Job() {}
+
+Core::Job::Type Core::Job::getType() const {
+    return type;
+}
diff --git a/core/delayManager/job.h b/core/delayManager/job.h
new file mode 100644
index 0000000..6314f48
--- /dev/null
+++ b/core/delayManager/job.h
@@ -0,0 +1,50 @@
+// Squawk messenger. 
+// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+// 
+// 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/>.
+
+#ifndef CORE_DELAYMANAGER_JOB_H
+#define CORE_DELAYMANAGER_JOB_H
+
+#include <stdint.h>
+
+namespace Core {
+
+class Job {
+public:
+    typedef uint16_t Id;
+
+    enum class Type {
+        cardInternal,
+        ownCardInternal,
+        infoForUser,
+        ownInfoForUser
+    };
+
+    Job(Id id, Type type);
+    Job(const Job& other);
+    virtual ~Job();
+
+    const Id id;
+
+public:
+    Type getType() const;
+
+protected:
+    Type type;
+};
+
+}
+
+#endif // CORE_DELAYMANAGER_JOB_H
diff --git a/core/delayManager/owncardinternal.cpp b/core/delayManager/owncardinternal.cpp
new file mode 100644
index 0000000..0167516
--- /dev/null
+++ b/core/delayManager/owncardinternal.cpp
@@ -0,0 +1,30 @@
+// Squawk messenger. 
+// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+// 
+// 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 "owncardinternal.h"
+
+Core::OwnCardInternal::OwnCardInternal(Job::Id p_id) :
+    Job(p_id, Type::ownCardInternal)
+{}
+
+Core::OwnCardInternal::OwnCardInternal(Job::Id p_id, Job::Type p_type) :
+    Job(p_id, p_type)
+{}
+
+Core::OwnCardInternal::OwnCardInternal(const Core::OwnCardInternal& other) :
+    Job(other)
+{}
+
diff --git a/core/delayManager/owncardinternal.h b/core/delayManager/owncardinternal.h
new file mode 100644
index 0000000..296666f
--- /dev/null
+++ b/core/delayManager/owncardinternal.h
@@ -0,0 +1,35 @@
+// Squawk messenger. 
+// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+// 
+// 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/>.
+
+#ifndef CORE_OWNCARDINTERNAL_H
+#define CORE_OWNCARDINTERNAL_H
+
+#include "job.h"
+
+namespace Core {
+
+class OwnCardInternal : public Job {
+protected:
+    OwnCardInternal(Job::Id id, Job::Type type);
+
+public:
+    OwnCardInternal(Job::Id id);
+    OwnCardInternal(const OwnCardInternal& other);
+};
+
+}
+
+#endif // CORE_OWNCARDINTERNAL_H
diff --git a/core/delayManager/owninfoforuser.cpp b/core/delayManager/owninfoforuser.cpp
new file mode 100644
index 0000000..12d1f72
--- /dev/null
+++ b/core/delayManager/owninfoforuser.cpp
@@ -0,0 +1,25 @@
+// Squawk messenger. 
+// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+// 
+// 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 "owninfoforuser.h"
+
+Core::OwnInfoForUser::OwnInfoForUser(Job::Id p_id) :
+    OwnCardInternal(p_id, Type::ownInfoForUser)
+{}
+
+Core::OwnInfoForUser::OwnInfoForUser(const Core::OwnInfoForUser& other) :
+    OwnCardInternal(other)
+{}
diff --git a/core/delayManager/owninfoforuser.h b/core/delayManager/owninfoforuser.h
new file mode 100644
index 0000000..a2534e5
--- /dev/null
+++ b/core/delayManager/owninfoforuser.h
@@ -0,0 +1,32 @@
+// Squawk messenger. 
+// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+// 
+// 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/>.
+
+#ifndef CORE_OWNINFOFORUSER_H
+#define CORE_OWNINFOFORUSER_H
+
+#include "owncardinternal.h"
+
+namespace Core {
+
+class OwnInfoForUser : public OwnCardInternal {
+public:
+    OwnInfoForUser(Job::Id id);
+    OwnInfoForUser(const OwnInfoForUser& other);
+};
+
+}
+
+#endif // CORE_OWNINFOFORUSER_H

From 9fff40963021b2a0b9d80b9f8cb53f9817ae1678 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Tue, 7 Mar 2023 21:45:01 +0300
Subject: [PATCH 237/281] some more thinking about delay manager

---
 core/delayManager/CMakeLists.txt      |   4 +
 core/delayManager/cardinternal.cpp    |  14 ++-
 core/delayManager/cardinternal.h      |  20 ++---
 core/delayManager/contact.cpp         |  26 ++++++
 core/delayManager/contact.h           |  39 +++++++++
 core/delayManager/delaymanager.cpp    | 120 ++++++++++++++++++++------
 core/delayManager/delaymanager.h      |  18 ++--
 core/delayManager/info.cpp            |  34 ++++++++
 core/delayManager/info.h              |  48 +++++++++++
 core/delayManager/infoforuser.cpp     |  12 ++-
 core/delayManager/infoforuser.h       |  15 ++--
 core/delayManager/job.cpp             |  10 +--
 core/delayManager/job.h               |  13 ++-
 core/delayManager/owncardinternal.cpp |   6 +-
 core/delayManager/owncardinternal.h   |  12 +--
 core/delayManager/owninfoforuser.cpp  |  10 ++-
 core/delayManager/owninfoforuser.h    |  15 ++--
 17 files changed, 320 insertions(+), 96 deletions(-)
 create mode 100644 core/delayManager/contact.cpp
 create mode 100644 core/delayManager/contact.h
 create mode 100644 core/delayManager/info.cpp
 create mode 100644 core/delayManager/info.h

diff --git a/core/delayManager/CMakeLists.txt b/core/delayManager/CMakeLists.txt
index f9ccb57..96f4cef 100644
--- a/core/delayManager/CMakeLists.txt
+++ b/core/delayManager/CMakeLists.txt
@@ -5,6 +5,8 @@ set(SOURCE_FILES
     infoforuser.cpp
     owncardinternal.cpp
     owninfoforuser.cpp
+    contact.cpp
+    info.cpp
 )
 
 set(HEADER_FILES
@@ -14,6 +16,8 @@ set(HEADER_FILES
     infoforuser.h
     owncardinternal.h
     owninfoforuser.h
+    contact.h
+    info.h
 )
 
 target_sources(squawk PRIVATE
diff --git a/core/delayManager/cardinternal.cpp b/core/delayManager/cardinternal.cpp
index c7a599a..c9ed203 100644
--- a/core/delayManager/cardinternal.cpp
+++ b/core/delayManager/cardinternal.cpp
@@ -16,17 +16,13 @@
 
 #include "cardinternal.h"
 
-Core::CardInternal::CardInternal(Job::Id p_id, const QString& p_jid) :
-    Job(p_id, Type::cardInternal),
-    jid(p_id)
+Core::DelayManager::CardInternal::CardInternal(Id p_id, const QString& p_jid) :
+    Job(p_id,  Type::cardInternal),
+    Contact(p_id, p_jid, Type::cardInternal)
 {}
 
-Core::CardInternal::CardInternal(Job::Id p_id, const QString& p_jid, Job::Type p_type) :
-    Job(p_id, p_type),
-    jid(p_id)
-{}
 
-Core::CardInternal::CardInternal(const Core::CardInternal& other) :
+Core::DelayManager::CardInternal::CardInternal(const CardInternal& other) :
     Job(other),
-    jid(other.jid)
+    Contact(other)
 {}
diff --git a/core/delayManager/cardinternal.h b/core/delayManager/cardinternal.h
index a95f0ba..17dd1ba 100644
--- a/core/delayManager/cardinternal.h
+++ b/core/delayManager/cardinternal.h
@@ -14,28 +14,24 @@
 // You should have received a copy of the GNU General Public License
 // along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
-#ifndef CORE_CARDINTERNAL_H
-#define CORE_CARDINTERNAL_H
+#ifndef CORE_DELAYMANAGER_CARDINTERNAL_H
+#define CORE_DELAYMANAGER_CARDINTERNAL_H
 
 
 #include <QString>
 
-#include "job.h"
+#include "contact.h"
 
 namespace Core {
+namespace DelayManager {
 
-class CardInternal : public Job {
-protected:
-    CardInternal(Job::Id id, const QString& jid, Job::Type type);
-
+class CardInternal : public Contact {
 public:
-    CardInternal(Job::Id id, const QString& jid);
+    CardInternal(Id id, const QString& jid);
     CardInternal(const CardInternal& other);
-
-    const QString jid;
-
 };
 
+}
 }
 
-#endif // CORE_CARDINTERNAL_H
+#endif // CORE_DELAYMANAGER_CARDINTERNAL_H
diff --git a/core/delayManager/contact.cpp b/core/delayManager/contact.cpp
new file mode 100644
index 0000000..286c8bd
--- /dev/null
+++ b/core/delayManager/contact.cpp
@@ -0,0 +1,26 @@
+// Squawk messenger. 
+// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+// 
+// 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 "contact.h"
+
+Core::DelayManager::Contact::Contact(const Contact& other):
+    Job(other),
+    jid(other.jid) {}
+
+
+Core::DelayManager::Contact::Contact(Id p_id, const QString& p_jid, Type p_type):
+    Job(p_id, p_type),
+    jid(p_jid) {}
diff --git a/core/delayManager/contact.h b/core/delayManager/contact.h
new file mode 100644
index 0000000..c136525
--- /dev/null
+++ b/core/delayManager/contact.h
@@ -0,0 +1,39 @@
+// Squawk messenger. 
+// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+// 
+// 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/>.
+
+#ifndef CORE_DELAYMANAGER_CONTACT_H
+#define CORE_DELAYMANAGER_CONTACT_H
+
+#include <QString>
+
+#include "job.h"
+
+namespace Core {
+namespace DelayManager {
+
+class Contact : public virtual Job {
+protected:
+    Contact(Id id, const QString& jid, Type type);
+    Contact(const Contact& other);
+
+public:
+    const QString jid;
+};
+
+}
+}
+
+#endif // CORE_DELAYMANAGER_CONTACT_H
diff --git a/core/delayManager/delaymanager.cpp b/core/delayManager/delaymanager.cpp
index 6ebc85d..906724f 100644
--- a/core/delayManager/delaymanager.cpp
+++ b/core/delayManager/delaymanager.cpp
@@ -21,7 +21,7 @@
 #include "owncardinternal.h"
 #include "owninfoforuser.h"
 
-Core::DelayManager::DelayManager(Job::Id mpj, QObject* parent) :
+Core::DelayManager::Manager::Manager(Job::Id mpj, QObject* parent) :
     QObject(parent),
     maxParallelJobs(mpj),
     nextJobId(1),
@@ -33,13 +33,23 @@ Core::DelayManager::DelayManager(Job::Id mpj, QObject* parent) :
     ownInfoJobId(0),
     scheduledVCards(),
     requestedVCards(),
+#ifdef WITH_OMEMO
     requestedBundles()
+#endif
 {
 }
 
-Core::DelayManager::~DelayManager() {}
+Core::DelayManager::Manager::~Manager() {
+    for (const std::pair<const Job::Id, Job*>& pair : runningJobs) {
+        delete pair.second;
+    }
 
-Core::Job::Id Core::DelayManager::getNextJobId() {
+    for (Job* job : jobSequence) {
+        delete job;
+    }
+}
+
+Core::Job::Id Core::DelayManager::Manager::getNextJobId() {
     Job::Id id = nextJobId++;
     if (id == 0)
         id = nextJobId++;
@@ -47,7 +57,7 @@ Core::Job::Id Core::DelayManager::getNextJobId() {
     return id;
 }
 
-void Core::DelayManager::getInfo(const QString& jid) {
+void Core::DelayManager::Manager::getInfo(const QString& jid) {
     Job* job = nullptr;
 #ifdef WITH_OMEMO
     std::map<QString, Job::Id>::const_iterator bitr = requestedBundles.find(jid);
@@ -58,13 +68,13 @@ void Core::DelayManager::getInfo(const QString& jid) {
         job = getVCardJob(jid);
 
     if (job != nullptr) {
-        if (job->getType() == Job::Type::cardInternal)
+        if (job->type == Job::Type::cardInternal)
             replaceJob(new InfoForUser(job->id, jid));
     } else
         scheduleJob(new InfoForUser(getNextJobId(), jid));
 }
 
-void Core::DelayManager::getOwnInfo() {
+void Core::DelayManager::Manager::getOwnInfo() {
     if (ownInfoJobId == 0) {
         if (ownVCardJobId != 0)
             replaceJob(new OwnInfoForUser(ownVCardJobId));
@@ -73,18 +83,18 @@ void Core::DelayManager::getOwnInfo() {
     }
 }
 
-void Core::DelayManager::getVCard(const QString& jid) {
+void Core::DelayManager::Manager::getVCard(const QString& jid) {
     Job* job = getVCardJob(jid);
     if (job == nullptr)
         scheduleJob(new CardInternal(getNextJobId(), jid));
 }
 
-void Core::DelayManager::getOwnVCard() {
+void Core::DelayManager::Manager::getOwnVCard() {
     if (ownInfoJobId == 0)
         scheduleJob(new OwnCardInternal(getNextJobId()));
 }
 
-Core::Job* Core::DelayManager::getVCardJob(const QString& jid) {
+Core::Job* Core::DelayManager::Manager::getVCardJob(const QString& jid) {
     Job* job = nullptr;
     std::map<QString, Job::Id>::const_iterator sitr = scheduledVCards.find(jid);
     if (sitr == scheduledVCards.end()) {
@@ -97,16 +107,16 @@ Core::Job* Core::DelayManager::getVCardJob(const QString& jid) {
 
     return job;
 }
-void Core::DelayManager::preScheduleJob(Core::Job* job) {
-    switch (job->getType()) {
+void Core::DelayManager::Manager::preScheduleJob(Core::Job* job) {
+    switch (job->type) {
         case Job::Type::cardInternal:
-            scheduledVCards.emplace(static_cast<CardInternal*>(job)->jid, job->id);
+            scheduledVCards.emplace(dynamic_cast<CardInternal*>(job)->jid, job->id);
             break;
         case Job::Type::ownCardInternal:
             ownVCardJobId = job->id;
             break;
         case Job::Type::infoForUser:
-            scheduledVCards.emplace(static_cast<InfoForUser*>(job)->jid, job->id);
+            scheduledVCards.emplace(dynamic_cast<InfoForUser*>(job)->jid, job->id);
             break;
         case Job::Type::ownInfoForUser:
             ownVCardJobId = job->id;
@@ -115,7 +125,7 @@ void Core::DelayManager::preScheduleJob(Core::Job* job) {
     }
 }
 
-void Core::DelayManager::scheduleJob(Core::Job* job) {
+void Core::DelayManager::Manager::scheduleJob(Core::Job* job) {
     preScheduleJob(job);
     if (runningJobs.size() < maxParallelJobs) {
         executeJob(job);
@@ -124,11 +134,11 @@ void Core::DelayManager::scheduleJob(Core::Job* job) {
     }
 }
 
-void Core::DelayManager::preExecuteJob(Core::Job* job) {
-    switch (job->getType()) {
+void Core::DelayManager::Manager::preExecuteJob(Core::Job* job) {
+    switch (job->type) {
         case Job::Type::cardInternal:
         case Job::Type::infoForUser: {
-            CardInternal* cij = static_cast<CardInternal*>(job);
+            Contact* cij = dynamic_cast<Contact*>(job);
             requestedVCards.emplace(cij->jid, job->id);
             scheduledVCards.erase(cij->jid);
         }
@@ -139,13 +149,13 @@ void Core::DelayManager::preExecuteJob(Core::Job* job) {
     }
 }
 
-void Core::DelayManager::executeJob(Core::Job* job) {
+void Core::DelayManager::Manager::executeJob(Core::Job* job) {
     preExecuteJob(job);
     runningJobs.emplace(job->id, job);
-    switch (job->getType()) {
+    switch (job->type) {
         case Job::Type::cardInternal:
         case Job::Type::infoForUser:
-            emit requestVCard(static_cast<CardInternal*>(job)->jid);
+            emit requestVCard(dynamic_cast<Contact*>(job)->jid);
             break;
         case Job::Type::ownInfoForUser:
         case Job::Type::ownCardInternal:
@@ -154,7 +164,7 @@ void Core::DelayManager::executeJob(Core::Job* job) {
     }
 }
 
-void Core::DelayManager::jobIsDone(Job::Id jobId) {
+void Core::DelayManager::Manager::jobIsDone(Job::Id jobId) {
     std::map<Job::Id, Job*>::const_iterator itr = runningJobs.find(jobId);
     if (itr == runningJobs.end()) {
         throw 8573; //not supposed to happen, ever
@@ -169,7 +179,7 @@ void Core::DelayManager::jobIsDone(Job::Id jobId) {
     }
 }
 
-void Core::DelayManager::replaceJob(Core::Job* job) {
+void Core::DelayManager::Manager::replaceJob(Core::Job* job) {
     preScheduleJob(job);
     std::map<Job::Id, Job*>::iterator itr = runningJobs.find(job->id);
     if (itr != runningJobs.end()) {
@@ -187,7 +197,63 @@ void Core::DelayManager::replaceJob(Core::Job* job) {
     }
 }
 
-void Core::DelayManager::receivedVCard(const QString& jid, const Shared::VCard& card) {
+void Core::DelayManager::Manager::jobIsCanceled(Core::Job* job, bool wasRunning) {
+    switch (job->type) {
+        case Job::Type::cardInternal: {
+            CardInternal* jb = dynamic_cast<CardInternal*>(job);
+            if (wasRunning)
+                requestedVCards.erase(jb->jid);
+            else
+                scheduledVCards.erase(jb->jid);
+
+            emit receivedVCard(jb->jid, Shared::VCard());
+        }
+            break;
+        case Job::Type::infoForUser: {
+            InfoForUser* jb = dynamic_cast<InfoForUser*>(job);
+            if (jb->getStage() == InfoForUser::Stage::waitingForVCard) {
+                if (wasRunning)
+                    requestedVCards.erase(jb->jid);
+                else
+                    scheduledVCards.erase(jb->jid);
+
+                emit receivedVCard(jb->jid, Shared::VCard());
+            }
+            emit receivedInfo(Shared::Info(jb->jid));
+        }
+            break;
+        case Job::Type::ownInfoForUser: {
+            OwnInfoForUser* jb = dynamic_cast<OwnInfoForUser*>(job);
+            if (jb->getStage() == OwnInfoForUser::Stage::waitingForVCard) {
+                ownVCardJobId = 0;
+                emit receivedOwnCard(Shared::VCard());
+            }
+            ownInfoJobId = 0;
+            emit receivedOwnInfo(Shared::Info (""));
+        }
+
+            break;
+        case Job::Type::ownCardInternal:
+            ownVCardJobId = 0;
+            emit receivedOwnCard(Shared::VCard());
+            break;
+    }
+
+    delete job;
+}
+
+void Core::DelayManager::Manager::disconnected() {
+    for (const std::pair<const Job::Id, Job*> pair : runningJobs)
+        jobIsCanceled(pair.second, true);
+
+    for (Job* job : scheduledJobs)
+        jobIsCanceled(job, false);
+
+    runningJobs.clear();
+    scheduledJobs.clear();
+}
+
+void Core::DelayManager::Manager::receivedVCard(const QString& jid, const Shared::VCard& card) {
     std::map<QString, Job::Id>::const_iterator cardItr = requestedVCards.find(jid);
     if (cardItr == requestedVCards.end()) {
         throw 8575; //never supposed to happen, the state is not correct;
@@ -195,7 +261,7 @@ void Core::DelayManager::receivedVCard(const QString& jid, const Shared::VCard&
         Job::Id jobId = cardItr->second;
         requestedVCards.erase(cardItr);
         Job* job = runningJobs.at(jobId);
-        switch (job->getType()) {
+        switch (job->type) {
             case Job::Type::cardInternal:
                 jobIsDone(jobId);
                 emit receivedCard(jid, card);
@@ -221,11 +287,11 @@ void Core::DelayManager::receivedVCard(const QString& jid, const Shared::VCard&
     }
 }
 
-void Core::DelayManager::ownVCardReceived(const Shared::VCard& card) {
+void Core::DelayManager::Manager::ownVCardReceived(const Shared::VCard& card) {
     Job::Id jobId = ownVCardJobId;
     ownVCardJobId = 0;
     Job* job = runningJobs.at(jobId);
-    switch (job->getType()) {
+    switch (job->type) {
         case Job::Type::ownCardInternal:
             jobIsDone(jobId);
             emit receivedOwnCard(card);
@@ -250,6 +316,6 @@ void Core::DelayManager::ownVCardReceived(const Shared::VCard& card) {
     }
 }
 
-void Core::DelayManager::receivedBundles(const QString& jid) {
+void Core::DelayManager::Manager::receivedBundles(const QString& jid) {
 
 }
diff --git a/core/delayManager/delaymanager.h b/core/delayManager/delaymanager.h
index e4f3e2a..c3d1da8 100644
--- a/core/delayManager/delaymanager.h
+++ b/core/delayManager/delaymanager.h
@@ -14,8 +14,8 @@
 // You should have received a copy of the GNU General Public License
 // along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
-#ifndef CORE_DELAYMANAGER_H
-#define CORE_DELAYMANAGER_H
+#ifndef CORE_DELAYMANAGER_MANAGER_H
+#define CORE_DELAYMANAGER_MANAGER_H
 
 #include <list>
 #include <set>
@@ -34,13 +34,14 @@
 #include "job.h"
 
 namespace Core {
+namespace DelayManager {
 
-class DelayManager : public QObject
+class Manager : public QObject
 {
     Q_OBJECT
 public:
-    DelayManager(Job::Id maxParallelJobs = 5, QObject* parent = nullptr);
-    ~DelayManager();
+    Manager(Job::Id maxParallelJobs = 5, QObject* parent = nullptr);
+    ~Manager();
 
     void getOwnVCard();
     void getOwnInfo();
@@ -58,6 +59,7 @@ signals:
     void receivedOwnInfo(const Shared::Info& info);
 
 public slots:
+    void disconnected();
     void ownVCardReceived(const Shared::VCard& card);
     void receivedVCard(const QString& jid, const Shared::VCard& card);
     void receivedBundles(const QString& jid);
@@ -67,6 +69,7 @@ private:
     void scheduleJob(Job* job);
     void preExecuteJob(Job* job);
     void executeJob(Job* job);
+    void jobIsCanceled(Job* job, bool wasRunning);
     void jobIsDone(Job::Id jobId);
     Job::Id getNextJobId();
     void replaceJob(Job* job);
@@ -108,9 +111,12 @@ private:
     Job::Id ownInfoJobId;
     std::map<QString, Job::Id> scheduledVCards;
     std::map<QString, Job::Id> requestedVCards;
+#ifdef WITH_OMEMO
     std::map<QString, Job::Id> requestedBundles;
+#endif
 };
 
+}
 }
 
-#endif // CORE_DELAYMANAGER_H
+#endif // CORE_DELAYMANAGER_MANAGER_H
diff --git a/core/delayManager/info.cpp b/core/delayManager/info.cpp
new file mode 100644
index 0000000..3b61f63
--- /dev/null
+++ b/core/delayManager/info.cpp
@@ -0,0 +1,34 @@
+// Squawk messenger. 
+// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+// 
+// 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 "info.h"
+
+Core::DelayManager::Info::Info(Id p_id, Type p_type) :
+    Job(p_id, p_type),
+    stage(Stage::waitingForVCard)
+{}
+
+Core::DelayManager::Info::Info(const Info& other) :
+    Job(other),
+    stage(other.stage)
+{}
+
+Core::DelayManager::Info::~Info() {
+}
+
+Core::DelayManager::Info::Stage Core::DelayManager::Info::getStage() const {
+    return stage;
+}
diff --git a/core/delayManager/info.h b/core/delayManager/info.h
new file mode 100644
index 0000000..0c4249a
--- /dev/null
+++ b/core/delayManager/info.h
@@ -0,0 +1,48 @@
+// Squawk messenger. 
+// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+// 
+// 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/>.
+
+#ifndef CORE_DELAYMANAGER_INFO_H
+#define CORE_DELAYMANAGER_INFO_H
+
+#include "job.h"
+
+namespace Core {
+namespace DelayManager {
+
+class Info : public virtual Job {
+public:
+    enum class Stage {
+        waitingForVCard,
+        waitingForBundles
+    };
+
+protected:
+    Info(Id id, Type type);
+    Info(const Info& other);
+
+public:
+    ~Info();
+
+    Stage getStage() const;
+
+private:
+    Stage stage;
+};
+
+}
+}
+
+#endif // CORE_DELAYMANAGER_INFO_H
diff --git a/core/delayManager/infoforuser.cpp b/core/delayManager/infoforuser.cpp
index 31d0571..fc494fb 100644
--- a/core/delayManager/infoforuser.cpp
+++ b/core/delayManager/infoforuser.cpp
@@ -16,11 +16,15 @@
 
 #include "infoforuser.h"
 
-Core::InfoForUser::InfoForUser(Job::Id p_id, const QString& p_jid) :
-    CardInternal(p_id, p_jid, Type::infoForUser)
+Core::DelayManager::InfoForUser::InfoForUser(Id p_id, const QString& p_jid) :
+    Job(p_id, Type::infoForUser),
+    Contact(p_id, p_jid, Type::infoForUser),
+    Info(p_id, Type::infoForUser)
 {}
 
-Core::InfoForUser::InfoForUser(const Core::InfoForUser& other) :
-    CardInternal(other)
+Core::DelayManager::InfoForUser::InfoForUser(const InfoForUser& other) :
+    Job(other),
+    Contact(other),
+    Info(other)
 {}
 
diff --git a/core/delayManager/infoforuser.h b/core/delayManager/infoforuser.h
index 2bc84e4..651d741 100644
--- a/core/delayManager/infoforuser.h
+++ b/core/delayManager/infoforuser.h
@@ -14,19 +14,22 @@
 // You should have received a copy of the GNU General Public License
 // along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
-#ifndef CORE_INFOFORUSER_H
-#define CORE_INFOFORUSER_H
+#ifndef CORE_DELAYMANAGER_INFOFORUSER_H
+#define CORE_DELAYMANAGER_INFOFORUSER_H
 
-#include "cardinternal.h"
+#include "contact.h"
+#include "info.h"
 
 namespace Core {
+namespace DelayManager {
 
-class InfoForUser : public CardInternal {
+class InfoForUser : public Contact, public Info {
 public:
-    InfoForUser(Job::Id id, const QString& jid);
+    InfoForUser(Id id, const QString& jid);
     InfoForUser(const InfoForUser& other);
 };
 
+}
 }
 
-#endif // CORE_INFOFORUSER_H
+#endif // CORE_DELAYMANAGER_INFOFORUSER_H
diff --git a/core/delayManager/job.cpp b/core/delayManager/job.cpp
index 0d4c868..b2d74b2 100644
--- a/core/delayManager/job.cpp
+++ b/core/delayManager/job.cpp
@@ -16,17 +16,13 @@
 
 #include "job.h"
 
-Core::Job::Job(Core::Job::Id p_id, Core::Job::Type p_type) :
+Core::DelayManager::Job::Job(Id p_id, Type p_type) :
     id (p_id),
     type (p_type) {}
 
 
-Core::Job::Job(const Core::Job& other) :
+Core::DelayManager::Job::Job(const Job& other) :
     id(other.id),
     type(other.type) {}
 
-Core::Job::~Job() {}
-
-Core::Job::Type Core::Job::getType() const {
-    return type;
-}
+Core::DelayManager::Job::~Job() {}
diff --git a/core/delayManager/job.h b/core/delayManager/job.h
index 6314f48..633f2b2 100644
--- a/core/delayManager/job.h
+++ b/core/delayManager/job.h
@@ -20,6 +20,7 @@
 #include <stdint.h>
 
 namespace Core {
+namespace DelayManager {
 
 class Job {
 public:
@@ -31,20 +32,18 @@ public:
         infoForUser,
         ownInfoForUser
     };
-
+protected:
     Job(Id id, Type type);
     Job(const Job& other);
+
+public:
     virtual ~Job();
 
     const Id id;
-
-public:
-    Type getType() const;
-
-protected:
-    Type type;
+    const Type type;
 };
 
+}
 }
 
 #endif // CORE_DELAYMANAGER_JOB_H
diff --git a/core/delayManager/owncardinternal.cpp b/core/delayManager/owncardinternal.cpp
index 0167516..43ed540 100644
--- a/core/delayManager/owncardinternal.cpp
+++ b/core/delayManager/owncardinternal.cpp
@@ -16,15 +16,15 @@
 
 #include "owncardinternal.h"
 
-Core::OwnCardInternal::OwnCardInternal(Job::Id p_id) :
+Core::DelayManager::OwnCardInternal::OwnCardInternal(Id p_id) :
     Job(p_id, Type::ownCardInternal)
 {}
 
-Core::OwnCardInternal::OwnCardInternal(Job::Id p_id, Job::Type p_type) :
+Core::DelayManager::OwnCardInternal::OwnCardInternal(Id p_id, Type p_type) :
     Job(p_id, p_type)
 {}
 
-Core::OwnCardInternal::OwnCardInternal(const Core::OwnCardInternal& other) :
+Core::DelayManager::OwnCardInternal::OwnCardInternal(const OwnCardInternal& other) :
     Job(other)
 {}
 
diff --git a/core/delayManager/owncardinternal.h b/core/delayManager/owncardinternal.h
index 296666f..7cca0a0 100644
--- a/core/delayManager/owncardinternal.h
+++ b/core/delayManager/owncardinternal.h
@@ -14,22 +14,24 @@
 // You should have received a copy of the GNU General Public License
 // along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
-#ifndef CORE_OWNCARDINTERNAL_H
-#define CORE_OWNCARDINTERNAL_H
+#ifndef CORE_DELAYMANAGER_OWNCARDINTERNAL_H
+#define CORE_DELAYMANAGER_OWNCARDINTERNAL_H
 
 #include "job.h"
 
 namespace Core {
+namespace DelayManager {
 
 class OwnCardInternal : public Job {
 protected:
-    OwnCardInternal(Job::Id id, Job::Type type);
+    OwnCardInternal(Id id, Type type);
 
 public:
-    OwnCardInternal(Job::Id id);
+    OwnCardInternal(Id id);
     OwnCardInternal(const OwnCardInternal& other);
 };
 
+}
 }
 
-#endif // CORE_OWNCARDINTERNAL_H
+#endif // CORE_DELAYMANAGER_OWNCARDINTERNAL_H
diff --git a/core/delayManager/owninfoforuser.cpp b/core/delayManager/owninfoforuser.cpp
index 12d1f72..396dc49 100644
--- a/core/delayManager/owninfoforuser.cpp
+++ b/core/delayManager/owninfoforuser.cpp
@@ -16,10 +16,12 @@
 
 #include "owninfoforuser.h"
 
-Core::OwnInfoForUser::OwnInfoForUser(Job::Id p_id) :
-    OwnCardInternal(p_id, Type::ownInfoForUser)
+Core::DelayManager::OwnInfoForUser::OwnInfoForUser(Id p_id) :
+    Job(p_id, Type::ownInfoForUser),
+    Info(p_id, Type::ownInfoForUser)
 {}
 
-Core::OwnInfoForUser::OwnInfoForUser(const Core::OwnInfoForUser& other) :
-    OwnCardInternal(other)
+Core::DelayManager::OwnInfoForUser::OwnInfoForUser(const OwnInfoForUser& other) :
+    Job(other),
+    Info(other)
 {}
diff --git a/core/delayManager/owninfoforuser.h b/core/delayManager/owninfoforuser.h
index a2534e5..80a13b6 100644
--- a/core/delayManager/owninfoforuser.h
+++ b/core/delayManager/owninfoforuser.h
@@ -14,19 +14,22 @@
 // You should have received a copy of the GNU General Public License
 // along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
-#ifndef CORE_OWNINFOFORUSER_H
-#define CORE_OWNINFOFORUSER_H
+#ifndef CORE_DELAYMANAGER_OWNINFOFORUSER_H
+#define CORE_DELAYMANAGER_OWNINFOFORUSER_H
 
-#include "owncardinternal.h"
+#include "contact.h"
+#include "info.h"
 
 namespace Core {
+namespace DelayManager {
 
-class OwnInfoForUser : public OwnCardInternal {
+class OwnInfoForUser : public Info {
 public:
-    OwnInfoForUser(Job::Id id);
+    OwnInfoForUser(Id id);
     OwnInfoForUser(const OwnInfoForUser& other);
 };
 
+}
 }
 
-#endif // CORE_OWNINFOFORUSER_H
+#endif // CORE_DELAYMANAGER_OWNINFOFORUSER_H

From 5ba97ecc2538b4f823ec68d75dc751e3f8038933 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Wed, 8 Mar 2023 23:28:48 +0300
Subject: [PATCH 238/281] some hopefully final preparations for delay manager

---
 core/account.cpp                   |   1 -
 core/delayManager/delaymanager.cpp | 133 ++++++++++++++++++-----------
 core/delayManager/delaymanager.h   |   8 +-
 core/delayManager/info.cpp         |  26 +++++-
 core/delayManager/info.h           |  10 ++-
 core/delayManager/infoforuser.cpp  |   1 -
 shared/info.cpp                    |  23 +++++
 shared/info.h                      |   2 +
 8 files changed, 148 insertions(+), 56 deletions(-)

diff --git a/core/account.cpp b/core/account.cpp
index 0a6c59c..d158970 100644
--- a/core/account.cpp
+++ b/core/account.cpp
@@ -107,7 +107,6 @@ Account::Account(const QString& p_login, const QString& p_server, const QString&
     client.addExtension(tm);
     client.addExtension(om);
     om->setSecurityPolicy(QXmpp::Toakafa);
-    om->setNewDeviceAutoSessionBuildingEnabled(true);
 
     if (oh->hasOwnDevice()) {
         QXmppTask<bool> future = om->load();
diff --git a/core/delayManager/delaymanager.cpp b/core/delayManager/delaymanager.cpp
index 906724f..0ecc9a4 100644
--- a/core/delayManager/delaymanager.cpp
+++ b/core/delayManager/delaymanager.cpp
@@ -21,7 +21,7 @@
 #include "owncardinternal.h"
 #include "owninfoforuser.h"
 
-Core::DelayManager::Manager::Manager(Job::Id mpj, QObject* parent) :
+Core::DelayManager::Manager::Manager(const QString& poj, Job::Id mpj, QObject* parent) :
     QObject(parent),
     maxParallelJobs(mpj),
     nextJobId(1),
@@ -34,8 +34,9 @@ Core::DelayManager::Manager::Manager(Job::Id mpj, QObject* parent) :
     scheduledVCards(),
     requestedVCards(),
 #ifdef WITH_OMEMO
-    requestedBundles()
+    requestedBundles(),
 #endif
+    ownJid(poj)
 {
 }
 
@@ -49,7 +50,7 @@ Core::DelayManager::Manager::~Manager() {
     }
 }
 
-Core::Job::Id Core::DelayManager::Manager::getNextJobId() {
+Core::DelayManager::Job::Id Core::DelayManager::Manager::getNextJobId() {
     Job::Id id = nextJobId++;
     if (id == 0)
         id = nextJobId++;
@@ -94,7 +95,7 @@ void Core::DelayManager::Manager::getOwnVCard() {
         scheduleJob(new OwnCardInternal(getNextJobId()));
 }
 
-Core::Job* Core::DelayManager::Manager::getVCardJob(const QString& jid) {
+Core::DelayManager::Job* Core::DelayManager::Manager::getVCardJob(const QString& jid) {
     Job* job = nullptr;
     std::map<QString, Job::Id>::const_iterator sitr = scheduledVCards.find(jid);
     if (sitr == scheduledVCards.end()) {
@@ -107,7 +108,7 @@ Core::Job* Core::DelayManager::Manager::getVCardJob(const QString& jid) {
 
     return job;
 }
-void Core::DelayManager::Manager::preScheduleJob(Core::Job* job) {
+void Core::DelayManager::Manager::preScheduleJob(Job* job) {
     switch (job->type) {
         case Job::Type::cardInternal:
             scheduledVCards.emplace(dynamic_cast<CardInternal*>(job)->jid, job->id);
@@ -125,7 +126,7 @@ void Core::DelayManager::Manager::preScheduleJob(Core::Job* job) {
     }
 }
 
-void Core::DelayManager::Manager::scheduleJob(Core::Job* job) {
+void Core::DelayManager::Manager::scheduleJob(Job* job) {
     preScheduleJob(job);
     if (runningJobs.size() < maxParallelJobs) {
         executeJob(job);
@@ -134,7 +135,7 @@ void Core::DelayManager::Manager::scheduleJob(Core::Job* job) {
     }
 }
 
-void Core::DelayManager::Manager::preExecuteJob(Core::Job* job) {
+void Core::DelayManager::Manager::preExecuteJob(Job* job) {
     switch (job->type) {
         case Job::Type::cardInternal:
         case Job::Type::infoForUser: {
@@ -149,7 +150,7 @@ void Core::DelayManager::Manager::preExecuteJob(Core::Job* job) {
     }
 }
 
-void Core::DelayManager::Manager::executeJob(Core::Job* job) {
+void Core::DelayManager::Manager::executeJob(Job* job) {
     preExecuteJob(job);
     runningJobs.emplace(job->id, job);
     switch (job->type) {
@@ -179,7 +180,7 @@ void Core::DelayManager::Manager::jobIsDone(Job::Id jobId) {
     }
 }
 
-void Core::DelayManager::Manager::replaceJob(Core::Job* job) {
+void Core::DelayManager::Manager::replaceJob(Job* job) {
     preScheduleJob(job);
     std::map<Job::Id, Job*>::iterator itr = runningJobs.find(job->id);
     if (itr != runningJobs.end()) {
@@ -197,7 +198,7 @@ void Core::DelayManager::Manager::replaceJob(Core::Job* job) {
     }
 }
 
-void Core::DelayManager::Manager::jobIsCanceled(Core::Job* job, bool wasRunning) {
+void Core::DelayManager::Manager::jobIsCanceled(Job* job, bool wasRunning) {
     switch (job->type) {
         case Job::Type::cardInternal: {
             CardInternal* jb = dynamic_cast<CardInternal*>(job);
@@ -211,13 +212,20 @@ void Core::DelayManager::Manager::jobIsCanceled(Core::Job* job, bool wasRunning)
             break;
         case Job::Type::infoForUser: {
             InfoForUser* jb = dynamic_cast<InfoForUser*>(job);
-            if (jb->getStage() == InfoForUser::Stage::waitingForVCard) {
-                if (wasRunning)
-                    requestedVCards.erase(jb->jid);
-                else
-                    scheduledVCards.erase(jb->jid);
+            switch (jb->getStage()) {
+                case InfoForUser::Stage::waitingForVCard:
+                    if (wasRunning)
+                        requestedVCards.erase(jb->jid);
+                    else
+                        scheduledVCards.erase(jb->jid);
 
-                emit receivedVCard(jb->jid, Shared::VCard());
+                    emit receivedVCard(jb->jid, Shared::VCard());
+                    break;
+                case InfoForUser::Stage::waitingForBundles:
+                    requestedBundles.erase(jb->jid);
+                    break;
+                default:
+                    break;
             }
             emit receivedInfo(Shared::Info(jb->jid));
         }
@@ -229,7 +237,7 @@ void Core::DelayManager::Manager::jobIsCanceled(Core::Job* job, bool wasRunning)
                 emit receivedOwnCard(Shared::VCard());
             }
             ownInfoJobId = 0;
-            emit receivedOwnInfo(Shared::Info (""));
+            emit receivedOwnInfo(Shared::Info (ownJid));
         }
 
             break;
@@ -257,33 +265,32 @@ void Core::DelayManager::Manager::receivedVCard(const QString& jid, const Shared
     std::map<QString, Job::Id>::const_iterator cardItr = requestedVCards.find(jid);
     if (cardItr == requestedVCards.end()) {
         throw 8575; //never supposed to happen, the state is not correct;
-    } else {
-        Job::Id jobId = cardItr->second;
-        requestedVCards.erase(cardItr);
-        Job* job = runningJobs.at(jobId);
-        switch (job->type) {
-            case Job::Type::cardInternal:
-                jobIsDone(jobId);
-                emit receivedCard(jid, card);
-                break;
-            case Job::Type::infoForUser:
+    }
+    Job::Id jobId = cardItr->second;
+    requestedVCards.erase(cardItr);
+    Job* job = runningJobs.at(jobId);
+    switch (job->type) {
+        case Job::Type::cardInternal:
+            jobIsDone(jobId);
+            emit receivedCard(jid, card);
+            break;
+        case Job::Type::infoForUser: {
 #ifdef WITH_OMEMO
-                requestedBundles.emplace(jid, jobId);
-                //TODO save card!
-                emit requestBundles(jid);
+            requestedBundles.emplace(jid, jobId);
+            InfoForUser* jb = dynamic_cast<InfoForUser*>(job);
+            jb->receivedVCard(card);
+            emit requestBundles(jid);
 #else
-                {
-                    Shared::Info info(jid);
-                    info.turnIntoContact(card);
-                    emit receivedInfo(info);
-                }
-                jobIsDone(jobId);
+            Shared::Info info(jid);
+            info.turnIntoContact(card);
+            emit receivedInfo(info);
+            jobIsDone(jobId);
 #endif
-                emit receivedCard(jid, card);
-                break;
-            default:
-                throw 8576;
+            emit receivedCard(jid, card);
         }
+            break;
+        default:
+            throw 8576;
     }
 }
 
@@ -296,26 +303,54 @@ void Core::DelayManager::Manager::ownVCardReceived(const Shared::VCard& card) {
             jobIsDone(jobId);
             emit receivedOwnCard(card);
             break;
-        case Job::Type::ownInfoForUser:
+        case Job::Type::ownInfoForUser: {
 #ifdef WITH_OMEMO
-            //requestedBundles.emplace(jid, jobId);
-            //TODO save card!
+            OwnInfoForUser* jb = dynamic_cast<OwnInfoForUser*>(job);
+            jb->receivedVCard(card);
             emit requestOwnBundle();
 #else
-            {
-                Shared::Info info("");
-                info.turnIntoOwnAccount(card);
-                emit receivedOwnInfo(info);
-            }
+            Shared::Info info(ownJid);
+            info.turnIntoOwnAccount(card);
+            emit receivedOwnInfo(info);
             jobIsDone(jobId);
 #endif
             emit receivedOwnCard(card);
+        }
             break;
         default:
             throw 8576;
     }
 }
 
-void Core::DelayManager::Manager::receivedBundles(const QString& jid) {
+void Core::DelayManager::Manager::receivedBundles(const QString& jid, const std::list<Shared::KeyInfo>& keys) {
+    std::map<QString, Job::Id>::const_iterator itr = requestedBundles.find(jid);
+    if (itr == requestedBundles.end()) {
+        throw 8577; //never supposed to happen, the state is not correct;
+    }
 
+    Job::Id jobId = itr->second;
+    requestedBundles.erase(itr);
+    Job* jb = runningJobs.at(jobId);
+    InfoForUser* job = dynamic_cast<InfoForUser*>(jb);
+
+    Shared::Info info(jid);
+    info.turnIntoContact(job->claim(), new std::list<Shared::KeyInfo>(keys));
+    emit receivedInfo(info);
+    jobIsDone(jobId);
+}
+
+void Core::DelayManager::Manager::receivedOwnBundles(const std::list<Shared::KeyInfo>& keys) {
+    Job::Id jobId = ownInfoJobId;
+    ownInfoJobId = 0;
+    Job* jb = runningJobs.at(jobId);
+    OwnInfoForUser* job = dynamic_cast<OwnInfoForUser*>(jb);
+
+    Shared::Info info(ownJid);
+    info.turnIntoOwnAccount(job->claim(), new std::list<Shared::KeyInfo>(keys));
+    emit receivedOwnInfo(info);
+    jobIsDone(jobId);
+}
+
+void Core::DelayManager::Manager::setOwnJid(const QString& jid) {
+    ownJid = jid;
 }
diff --git a/core/delayManager/delaymanager.h b/core/delayManager/delaymanager.h
index c3d1da8..d859a18 100644
--- a/core/delayManager/delaymanager.h
+++ b/core/delayManager/delaymanager.h
@@ -40,19 +40,21 @@ class Manager : public QObject
 {
     Q_OBJECT
 public:
-    Manager(Job::Id maxParallelJobs = 5, QObject* parent = nullptr);
+    Manager(const QString& ownJid, Job::Id maxParallelJobs = 5, QObject* parent = nullptr);
     ~Manager();
 
     void getOwnVCard();
     void getOwnInfo();
     void getVCard(const QString& jid);
     void getInfo(const QString& jid);
+    void setOwnJid(const QString& jid);
 
 signals:
     void requestVCard(const QString& jid);
     void requestOwnVCard();
     void requestBundles(const QString& jid);
     void requestOwnBundle();
+
     void receivedCard(const QString& jid, const Shared::VCard& info);
     void receivedOwnCard(const Shared::VCard& info);
     void receivedInfo(const Shared::Info& info);
@@ -62,7 +64,8 @@ public slots:
     void disconnected();
     void ownVCardReceived(const Shared::VCard& card);
     void receivedVCard(const QString& jid, const Shared::VCard& card);
-    void receivedBundles(const QString& jid);
+    void receivedBundles(const QString& jid, const std::list<Shared::KeyInfo>& keys);
+    void receivedOwnBundles(const std::list<Shared::KeyInfo>& keys);
 
 private:
     void preScheduleJob(Job* job);
@@ -114,6 +117,7 @@ private:
 #ifdef WITH_OMEMO
     std::map<QString, Job::Id> requestedBundles;
 #endif
+    QString ownJid;
 };
 
 }
diff --git a/core/delayManager/info.cpp b/core/delayManager/info.cpp
index 3b61f63..b5b619d 100644
--- a/core/delayManager/info.cpp
+++ b/core/delayManager/info.cpp
@@ -18,17 +18,39 @@
 
 Core::DelayManager::Info::Info(Id p_id, Type p_type) :
     Job(p_id, p_type),
-    stage(Stage::waitingForVCard)
+    stage(Stage::waitingForVCard),
+    info(nullptr)
 {}
 
 Core::DelayManager::Info::Info(const Info& other) :
     Job(other),
-    stage(other.stage)
+    stage(other.stage),
+    info(nullptr)
 {}
 
 Core::DelayManager::Info::~Info() {
+    if (stage == Stage::waitingForBundles) {
+        delete info;
+    }
 }
 
 Core::DelayManager::Info::Stage Core::DelayManager::Info::getStage() const {
     return stage;
 }
+
+void Core::DelayManager::Info::receivedVCard(const Shared::VCard& card) {
+    if (stage != Stage::waitingForVCard)
+        throw 245;
+
+    info = new Shared::VCard(card);
+    stage = Stage::waitingForBundles;
+}
+
+Shared::VCard * Core::DelayManager::Info::claim() {
+    if (stage != Stage::waitingForBundles)
+        throw 246;
+
+    Shared::VCard* res = info;
+    info = nullptr;
+    return res;
+}
diff --git a/core/delayManager/info.h b/core/delayManager/info.h
index 0c4249a..a6601a1 100644
--- a/core/delayManager/info.h
+++ b/core/delayManager/info.h
@@ -19,6 +19,9 @@
 
 #include "job.h"
 
+#include <shared/vcard.h>
+#include <shared/info.h>
+
 namespace Core {
 namespace DelayManager {
 
@@ -26,7 +29,8 @@ class Info : public virtual Job {
 public:
     enum class Stage {
         waitingForVCard,
-        waitingForBundles
+        waitingForBundles,
+        finished
     };
 
 protected:
@@ -36,10 +40,14 @@ protected:
 public:
     ~Info();
 
+    void receivedVCard(const Shared::VCard& card);
+    Shared::VCard* claim();
     Stage getStage() const;
 
+
 private:
     Stage stage;
+    Shared::VCard* info;
 };
 
 }
diff --git a/core/delayManager/infoforuser.cpp b/core/delayManager/infoforuser.cpp
index fc494fb..067be80 100644
--- a/core/delayManager/infoforuser.cpp
+++ b/core/delayManager/infoforuser.cpp
@@ -27,4 +27,3 @@ Core::DelayManager::InfoForUser::InfoForUser(const InfoForUser& other) :
     Contact(other),
     Info(other)
 {}
-
diff --git a/shared/info.cpp b/shared/info.cpp
index c0d6f26..36cd4b5 100644
--- a/shared/info.cpp
+++ b/shared/info.cpp
@@ -328,3 +328,26 @@ Shared::VCard * Shared::Info::getVCard() {
             throw 365;
     }
 }
+
+void Shared::Info::setActiveKeys(std::list<KeyInfo>* keys) {
+    switch (type) {
+        case EntryType::contact:
+        case EntryType::ownAccount:
+            activeKeys = keys;
+            break;
+        default:
+            throw 366;
+    }
+}
+
+void Shared::Info::setVCard(Shared::VCard* card) {
+    switch (type) {
+        case EntryType::contact:
+        case EntryType::ownAccount:
+            vcard = card;
+            break;
+        default:
+            throw 367;
+    }
+}
+
diff --git a/shared/info.h b/shared/info.h
index b0f495e..e42eddb 100644
--- a/shared/info.h
+++ b/shared/info.h
@@ -69,11 +69,13 @@ public:
     VCard& getVCardRef();
     const VCard* getVCard() const;
     VCard* getVCard();
+    void setVCard(Shared::VCard* card);
 
     const std::list<KeyInfo>& getActiveKeysRef() const;
     std::list<KeyInfo>& getActiveKeysRef();
     const std::list<KeyInfo>* getActiveKeys() const;
     std::list<KeyInfo>* getActiveKeys();
+    void setActiveKeys(std::list<KeyInfo>* keys);
 
     const std::list<KeyInfo>& getInactiveKeysRef() const;
     std::list<KeyInfo>& getInactiveKeysRef();

From 927bdf0dab1e38b8e5a79ee499d20ede4c570d0e Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Fri, 10 Mar 2023 21:43:31 +0300
Subject: [PATCH 239/281] DONT TAKE, BROKEN! first application of delay manager
 in code, reception of bundles

---
 core/account.cpp                              | 25 ++++--
 core/account.h                                |  6 ++
 core/delayManager/CMakeLists.txt              |  4 +-
 .../{delaymanager.cpp => manager.cpp}         | 48 ++++++-----
 .../{delaymanager.h => manager.h}             | 17 ++--
 core/handlers/omemohandler.cpp                | 48 +++++++++++
 core/handlers/omemohandler.h                  | 10 ++-
 core/handlers/rosterhandler.cpp               |  4 +-
 core/handlers/rosterhandler.h                 |  1 +
 core/handlers/trusthandler.cpp                | 51 ++++++------
 core/handlers/vcardhandler.cpp                | 79 ++++---------------
 core/handlers/vcardhandler.h                  |  4 -
 main/CMakeLists.txt                           | 16 +++-
 main/main.cpp                                 | 16 ++--
 main/root.cpp                                 | 43 ++++++++++
 main/root.h                                   | 28 +++++++
 16 files changed, 261 insertions(+), 139 deletions(-)
 rename core/delayManager/{delaymanager.cpp => manager.cpp} (90%)
 rename core/delayManager/{delaymanager.h => manager.h} (90%)
 create mode 100644 main/root.cpp
 create mode 100644 main/root.h

diff --git a/core/account.cpp b/core/account.cpp
index d158970..c27f2ea 100644
--- a/core/account.cpp
+++ b/core/account.cpp
@@ -55,6 +55,7 @@ Account::Account(const QString& p_login, const QString& p_server, const QString&
     reconnectScheduled(false),
     reconnectTimer(new QTimer),
     network(p_net),
+    delay(nullptr),
     passwordType(Shared::AccountPassword::plain),
     lastError(Error::none),
     pepSupport(Shared::Support::unknown),
@@ -67,6 +68,12 @@ Account::Account(const QString& p_login, const QString& p_server, const QString&
     config.setPassword(p_password);
     config.setAutoAcceptSubscriptions(true);
     //config.setAutoReconnectionEnabled(false);
+    delay = new DelayManager::Manager(getBareJid());
+    QObject::connect(delay, &DelayManager::Manager::gotInfo, this, &Account::infoReady);
+    QObject::connect(delay, &DelayManager::Manager::gotOwnInfo, this, &Account::infoReady);
+
+    QObject::connect(delay, &DelayManager::Manager::requestOwnVCard, vm, &QXmppVCardManager::requestClientVCard);
+    QObject::connect(delay, &DelayManager::Manager::requestVCard, vm, &QXmppVCardManager::requestVCard);
 
     rh->initialize();
     vh->initialize();
@@ -104,6 +111,9 @@ Account::Account(const QString& p_login, const QString& p_server, const QString&
     client.addExtension(psm);
 
 #ifdef WITH_OMEMO
+    QObject::connect(delay, &DelayManager::Manager::requestBundles, oh, &OmemoHandler::requestBundles);
+    QObject::connect(delay, &DelayManager::Manager::requestOwnBundles, oh, &OmemoHandler::requestOwnBundles);
+
     client.addExtension(tm);
     client.addExtension(om);
     om->setSecurityPolicy(QXmpp::Toakafa);
@@ -164,6 +174,7 @@ Account::~Account() {
     delete mm;
     delete am;
     delete cm;
+    delete delay;
 }
 
 Shared::ConnectionState Core::Account::getState() const {
@@ -313,7 +324,6 @@ void Core::Account::runDiscoveryService() {
     dm->requestInfo(getServer());
 }
 
-
 void Core::Account::onPresenceReceived(const QXmppPresence& p_presence) {
     QString id = p_presence.from();
     QStringList comps = id.split("/");
@@ -642,9 +652,9 @@ void Core::Account::setPepSupport(Shared::Support support) {
 
 void Core::Account::handleDisconnection() {
     setPepSupport(Shared::Support::unknown);
+    delay->disconnected();
     cm->setCarbonsEnabled(false);
     rh->handleOffline();
-    vh->handleOffline();
     archiveQueries.clear();
 }
 
@@ -702,7 +712,9 @@ void Core::Account::setPasswordType(Shared::AccountPassword pt) {
     passwordType = pt; }
 
 void Core::Account::setLogin(const QString& p_login) {
-    config.setUser(p_login);}
+    config.setUser(p_login);
+    delay->setOwnJid(getBareJid());
+}
 
 void Core::Account::setName(const QString& p_name) {
     name = p_name;}
@@ -713,7 +725,9 @@ void Core::Account::setPassword(const QString& p_password) {
 }
 
 void Core::Account::setServer(const QString& p_server) {
-    config.setDomain(p_server);}
+    config.setDomain(p_server);
+    delay->setOwnJid(getBareJid());
+}
 
 void Core::Account::sendMessage(const Shared::Message& data) {
     mh->sendMessage(data);}
@@ -730,7 +744,8 @@ void Core::Account::replaceMessage(const QString& originalId, const Shared::Mess
 void Core::Account::requestInfo(const QString& jid) {
     //TODO switch case of what kind of entity this info request is about
     //right now it could be only about myself or some contact
-    vh->requestVCard(jid);
+    delay->getInfo(jid);
+    //vh->requestVCard(jid);
 }
 
 void Core::Account::updateInfo(const Shared::Info& info) {
diff --git a/core/account.h b/core/account.h
index 598a06e..e18966d 100644
--- a/core/account.h
+++ b/core/account.h
@@ -50,6 +50,7 @@
 #include "contact.h"
 #include "conference.h"
 #include <core/components/networkaccess.h>
+#include <core/delayManager/manager.h>
 
 #include "handlers/messagehandler.h"
 #include "handlers/rosterhandler.h"
@@ -73,6 +74,10 @@ class Account : public QObject
     friend class RosterHandler;
     friend class VCardHandler;
     friend class DiscoveryHandler;
+#ifdef WITH_OMEMO
+    friend class OmemoHandler;
+    friend class TrustHandler;
+#endif
 public:
     enum class Error {
         authentication,
@@ -202,6 +207,7 @@ private:
     QTimer* reconnectTimer;
     
     NetworkAccess* network;
+    DelayManager::Manager* delay;
     Shared::AccountPassword passwordType;
     Error lastError;
     Shared::Support pepSupport;
diff --git a/core/delayManager/CMakeLists.txt b/core/delayManager/CMakeLists.txt
index 96f4cef..6d046c5 100644
--- a/core/delayManager/CMakeLists.txt
+++ b/core/delayManager/CMakeLists.txt
@@ -1,5 +1,5 @@
 set(SOURCE_FILES
-    delaymanager.cpp
+    manager.cpp
     job.cpp
     cardinternal.cpp
     infoforuser.cpp
@@ -10,7 +10,7 @@ set(SOURCE_FILES
 )
 
 set(HEADER_FILES
-    delaymanager.h
+    manager.h
     job.h
     cardinternal.h
     infoforuser.h
diff --git a/core/delayManager/delaymanager.cpp b/core/delayManager/manager.cpp
similarity index 90%
rename from core/delayManager/delaymanager.cpp
rename to core/delayManager/manager.cpp
index 0ecc9a4..6a15544 100644
--- a/core/delayManager/delaymanager.cpp
+++ b/core/delayManager/manager.cpp
@@ -14,7 +14,9 @@
 // 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 "delaymanager.h"
+#include "manager.h"
+
+#include <QDebug>
 
 #include "cardinternal.h"
 #include "infoforuser.h"
@@ -59,6 +61,9 @@ Core::DelayManager::Job::Id Core::DelayManager::Manager::getNextJobId() {
 }
 
 void Core::DelayManager::Manager::getInfo(const QString& jid) {
+    if (jid == ownJid)
+        return getOwnInfo();
+
     Job* job = nullptr;
 #ifdef WITH_OMEMO
     std::map<QString, Job::Id>::const_iterator bitr = requestedBundles.find(jid);
@@ -91,10 +96,14 @@ void Core::DelayManager::Manager::getVCard(const QString& jid) {
 }
 
 void Core::DelayManager::Manager::getOwnVCard() {
-    if (ownInfoJobId == 0)
+    if (ownVCardJobId == 0)
         scheduleJob(new OwnCardInternal(getNextJobId()));
 }
 
+bool Core::DelayManager::Manager::isOwnVCardPending() const {
+    return ownVCardJobId != 0;
+}
+
 Core::DelayManager::Job* Core::DelayManager::Manager::getVCardJob(const QString& jid) {
     Job* job = nullptr;
     std::map<QString, Job::Id>::const_iterator sitr = scheduledVCards.find(jid);
@@ -207,7 +216,7 @@ void Core::DelayManager::Manager::jobIsCanceled(Job* job, bool wasRunning) {
             else
                 scheduledVCards.erase(jb->jid);
 
-            emit receivedVCard(jb->jid, Shared::VCard());
+            emit gotVCard(jb->jid, Shared::VCard());
         }
             break;
         case Job::Type::infoForUser: {
@@ -219,7 +228,7 @@ void Core::DelayManager::Manager::jobIsCanceled(Job* job, bool wasRunning) {
                     else
                         scheduledVCards.erase(jb->jid);
 
-                    emit receivedVCard(jb->jid, Shared::VCard());
+                    emit gotVCard(jb->jid, Shared::VCard());
                     break;
                 case InfoForUser::Stage::waitingForBundles:
                     requestedBundles.erase(jb->jid);
@@ -227,23 +236,23 @@ void Core::DelayManager::Manager::jobIsCanceled(Job* job, bool wasRunning) {
                 default:
                     break;
             }
-            emit receivedInfo(Shared::Info(jb->jid));
+            emit gotInfo(Shared::Info(jb->jid));
         }
             break;
         case Job::Type::ownInfoForUser: {
             OwnInfoForUser* jb = dynamic_cast<OwnInfoForUser*>(job);
             if (jb->getStage() == OwnInfoForUser::Stage::waitingForVCard) {
                 ownVCardJobId = 0;
-                emit receivedOwnCard(Shared::VCard());
+                emit gotOwnVCard(Shared::VCard());
             }
             ownInfoJobId = 0;
-            emit receivedOwnInfo(Shared::Info (ownJid));
+            emit gotOwnInfo(Shared::Info (ownJid));
         }
 
             break;
         case Job::Type::ownCardInternal:
             ownVCardJobId = 0;
-            emit receivedOwnCard(Shared::VCard());
+            emit gotOwnVCard(Shared::VCard());
             break;
     }
 
@@ -264,7 +273,8 @@ void Core::DelayManager::Manager::disconnected() {
 void Core::DelayManager::Manager::receivedVCard(const QString& jid, const Shared::VCard& card) {
     std::map<QString, Job::Id>::const_iterator cardItr = requestedVCards.find(jid);
     if (cardItr == requestedVCards.end()) {
-        throw 8575; //never supposed to happen, the state is not correct;
+        qDebug() << "received VCard for" << jid << "but it was never requested through manager, ignoring";
+        return;
     }
     Job::Id jobId = cardItr->second;
     requestedVCards.erase(cardItr);
@@ -272,7 +282,7 @@ void Core::DelayManager::Manager::receivedVCard(const QString& jid, const Shared
     switch (job->type) {
         case Job::Type::cardInternal:
             jobIsDone(jobId);
-            emit receivedCard(jid, card);
+            emit gotVCard(jid, card);
             break;
         case Job::Type::infoForUser: {
 #ifdef WITH_OMEMO
@@ -283,10 +293,10 @@ void Core::DelayManager::Manager::receivedVCard(const QString& jid, const Shared
 #else
             Shared::Info info(jid);
             info.turnIntoContact(card);
-            emit receivedInfo(info);
+            emit gotInfo(info);
             jobIsDone(jobId);
 #endif
-            emit receivedCard(jid, card);
+            emit gotVCard(jid, card);
         }
             break;
         default:
@@ -294,27 +304,27 @@ void Core::DelayManager::Manager::receivedVCard(const QString& jid, const Shared
     }
 }
 
-void Core::DelayManager::Manager::ownVCardReceived(const Shared::VCard& card) {
+void Core::DelayManager::Manager::receivedOwnVCard(const Shared::VCard& card) {
     Job::Id jobId = ownVCardJobId;
     ownVCardJobId = 0;
     Job* job = runningJobs.at(jobId);
     switch (job->type) {
         case Job::Type::ownCardInternal:
             jobIsDone(jobId);
-            emit receivedOwnCard(card);
+            emit gotOwnVCard(card);
             break;
         case Job::Type::ownInfoForUser: {
 #ifdef WITH_OMEMO
             OwnInfoForUser* jb = dynamic_cast<OwnInfoForUser*>(job);
             jb->receivedVCard(card);
-            emit requestOwnBundle();
+            emit requestOwnBundles();
 #else
             Shared::Info info(ownJid);
             info.turnIntoOwnAccount(card);
-            emit receivedOwnInfo(info);
+            emit gotOwnInfo(info);
             jobIsDone(jobId);
 #endif
-            emit receivedOwnCard(card);
+            emit gotOwnVCard(card);
         }
             break;
         default:
@@ -335,7 +345,7 @@ void Core::DelayManager::Manager::receivedBundles(const QString& jid, const std:
 
     Shared::Info info(jid);
     info.turnIntoContact(job->claim(), new std::list<Shared::KeyInfo>(keys));
-    emit receivedInfo(info);
+    emit gotInfo(info);
     jobIsDone(jobId);
 }
 
@@ -347,7 +357,7 @@ void Core::DelayManager::Manager::receivedOwnBundles(const std::list<Shared::Key
 
     Shared::Info info(ownJid);
     info.turnIntoOwnAccount(job->claim(), new std::list<Shared::KeyInfo>(keys));
-    emit receivedOwnInfo(info);
+    emit gotOwnInfo(info);
     jobIsDone(jobId);
 }
 
diff --git a/core/delayManager/delaymanager.h b/core/delayManager/manager.h
similarity index 90%
rename from core/delayManager/delaymanager.h
rename to core/delayManager/manager.h
index d859a18..61920ec 100644
--- a/core/delayManager/delaymanager.h
+++ b/core/delayManager/manager.h
@@ -43,26 +43,29 @@ public:
     Manager(const QString& ownJid, Job::Id maxParallelJobs = 5, QObject* parent = nullptr);
     ~Manager();
 
+    void setOwnJid(const QString& jid);
+    bool isOwnVCardPending() const;
+
+public slots:
     void getOwnVCard();
     void getOwnInfo();
     void getVCard(const QString& jid);
     void getInfo(const QString& jid);
-    void setOwnJid(const QString& jid);
 
 signals:
     void requestVCard(const QString& jid);
     void requestOwnVCard();
     void requestBundles(const QString& jid);
-    void requestOwnBundle();
+    void requestOwnBundles();
 
-    void receivedCard(const QString& jid, const Shared::VCard& info);
-    void receivedOwnCard(const Shared::VCard& info);
-    void receivedInfo(const Shared::Info& info);
-    void receivedOwnInfo(const Shared::Info& info);
+    void gotVCard(const QString& jid, const Shared::VCard& info);
+    void gotOwnVCard(const Shared::VCard& info);
+    void gotInfo(const Shared::Info& info);
+    void gotOwnInfo(const Shared::Info& info);
 
 public slots:
     void disconnected();
-    void ownVCardReceived(const Shared::VCard& card);
+    void receivedOwnVCard(const Shared::VCard& card);
     void receivedVCard(const QString& jid, const Shared::VCard& card);
     void receivedBundles(const QString& jid, const std::list<Shared::KeyInfo>& keys);
     void receivedOwnBundles(const std::list<Shared::KeyInfo>& keys);
diff --git a/core/handlers/omemohandler.cpp b/core/handlers/omemohandler.cpp
index ce82a96..79eded9 100644
--- a/core/handlers/omemohandler.cpp
+++ b/core/handlers/omemohandler.cpp
@@ -19,7 +19,10 @@
 #include "core/account.h"
 #include "core/adapterfunctions.h"
 
+constexpr const char* ns_omemo_2 = "urn:xmpp:omemo:2";
+
 Core::OmemoHandler::OmemoHandler(Account* account) :
+    QObject(),
     QXmppOmemoStorage(),
     acc(account),
     ownDevice(std::nullopt),
@@ -166,6 +169,51 @@ void Core::OmemoHandler::getDevices(const QString& jid, std::list<Shared::KeyInf
     }
 }
 
+void Core::OmemoHandler::requestBundles(const QString& jid) {
+    QXmppTask<void> task = acc->om->buildMissingSessions({jid});
+    task.then(this, std::bind(&OmemoHandler::onBundlesReceived, this, jid));
+}
+
+void Core::OmemoHandler::requestOwnBundles() {
+    QXmppTask<void> task = acc->om->buildMissingSessions({acc->getBareJid()});
+    task.then(this, std::bind(&OmemoHandler::onOwnBundlesReceived, this));
+}
+
+void Core::OmemoHandler::onBundlesReceived(const QString& jid) {
+    std::list<Shared::KeyInfo> keys;
+    acc->oh->getDevices(jid, keys);
+    std::map<QByteArray, Shared::TrustLevel> trustLevels = acc->th->getKeys(ns_omemo_2, jid);
+
+    qDebug() << "OMEMO info for " << jid << " devices:" << keys.size() << ", trustLevels:" << trustLevels.size();
+    for (Shared::KeyInfo& key : keys) {
+        std::map<QByteArray, Shared::TrustLevel>::const_iterator itr = trustLevels.find(key.fingerPrint);
+        if (itr != trustLevels.end()) {
+            key.trustLevel = itr->second;
+            qDebug() << "Found a trust level for a device!";
+        }
+    }
+
+    acc->delay->receivedBundles(jid, keys);
+}
+
+void Core::OmemoHandler::onOwnBundlesReceived() {
+    QString jid = acc->getBareJid();
+    std::list<Shared::KeyInfo> keys;
+    acc->oh->getDevices(jid, keys);
+    std::map<QByteArray, Shared::TrustLevel> trustLevels = acc->th->getKeys(ns_omemo_2, jid);
+
+    qDebug() << "OMEMO info for " << jid << " devices:" << keys.size() << ", trustLevels:" << trustLevels.size();
+    for (Shared::KeyInfo& key : keys) {
+        std::map<QByteArray, Shared::TrustLevel>::const_iterator itr = trustLevels.find(key.fingerPrint);
+        if (itr != trustLevels.end()) {
+            key.trustLevel = itr->second;
+            qDebug() << "Found a trust level for a device!";
+        }
+    }
+
+    acc->delay->receivedOwnBundles(keys);
+}
+
 
 QDataStream & operator >> (QDataStream& in, QXmppOmemoStorage::Device& device) {
     in >> device.label;
diff --git a/core/handlers/omemohandler.h b/core/handlers/omemohandler.h
index 050677c..0d1021b 100644
--- a/core/handlers/omemohandler.h
+++ b/core/handlers/omemohandler.h
@@ -19,6 +19,7 @@
 
 #include <map>
 #include <list>
+#include <functional>
 
 #include <QXmppOmemoStorage.h>
 #include <cache.h>
@@ -32,8 +33,9 @@ Q_DECLARE_METATYPE(QXmppOmemoStorage::Device);
 namespace Core {
 class Account;
 
-class OmemoHandler : public QXmppOmemoStorage
+class OmemoHandler :public QObject, public QXmppOmemoStorage
 {
+    Q_OBJECT
 public:
     typedef std::pair<QDateTime, QByteArray> SignedPreKeyPair;
 
@@ -58,8 +60,14 @@ public:
 
     bool hasOwnDevice();
 
+    void requestBundles(const QString& jid);
+    void requestOwnBundles();
     void getDevices(const QString& jid, std::list<Shared::KeyInfo>& out) const;
 
+private slots:
+    void onBundlesReceived(const QString& jid);
+    void onOwnBundlesReceived();
+
 private:
     Account* acc;
     std::optional<OwnDevice> ownDevice;
diff --git a/core/handlers/rosterhandler.cpp b/core/handlers/rosterhandler.cpp
index 0027514..cccd47d 100644
--- a/core/handlers/rosterhandler.cpp
+++ b/core/handlers/rosterhandler.cpp
@@ -156,7 +156,7 @@ void Core::RosterHandler::careAboutAvatar(Core::RosterItem* item, QMap<QString,
     } else {
         data.insert("avatarState", QVariant::fromValue(Shared::Avatar::empty));
         data.insert("avatarPath", "");
-        acc->vh->requestVCard(item->jid);
+        acc->delay->requestVCard(item->jid);
     }
 }
 
@@ -197,7 +197,7 @@ void Core::RosterHandler::handleNewRosterItem(Core::RosterItem* contact)
     connect(contact, &RosterItem::historyResponse, this->acc, &Account::onContactHistoryResponse);
     connect(contact, &RosterItem::nameChanged, this, &RosterHandler::onContactNameChanged);
     connect(contact, &RosterItem::avatarChanged, this, &RosterHandler::onContactAvatarChanged);
-    connect(contact, &RosterItem::requestVCard, this->acc->vh, &VCardHandler::requestVCard);
+    connect(contact, &RosterItem::requestVCard, acc->delay, &DelayManager::Manager::getVCard);
 }
 
 void Core::RosterHandler::handleNewContact(Core::Contact* contact)
diff --git a/core/handlers/rosterhandler.h b/core/handlers/rosterhandler.h
index 11525be..62a7b8b 100644
--- a/core/handlers/rosterhandler.h
+++ b/core/handlers/rosterhandler.h
@@ -36,6 +36,7 @@
 #include <shared/message.h>
 #include <core/contact.h>
 #include <core/conference.h>
+#include <core/delayManager/manager.h>
 
 namespace Core {
 
diff --git a/core/handlers/trusthandler.cpp b/core/handlers/trusthandler.cpp
index 50fbcbf..eb75219 100644
--- a/core/handlers/trusthandler.cpp
+++ b/core/handlers/trusthandler.cpp
@@ -89,8 +89,12 @@ QXmppTask<QXmpp::TrustLevel> Core::TrustHandler::trustLevel(
     const QString& keyOwnerJid,
     const QByteArray& keyId)
 {
-    Keys map = getCache(encryption)->getRecord(keyOwnerJid);
-    Shared::TrustLevel level = map.at(keyId);
+    KeyCache* cache = getCache(encryption);
+    Shared::TrustLevel level = Shared::TrustLevel::undecided;
+    try {
+        Keys map = cache->getRecord(keyOwnerJid);
+        level = map.at(keyId);
+    } catch (const DataBase::NotFound& e) {}
     return Core::makeReadyTask(std::move(convert(level)));
 }
 
@@ -134,15 +138,22 @@ QXmppTask<QHash<QString, QMultiHash<QString, QByteArray>>> Core::TrustHandler::s
     for (MultySB::const_iterator itr = keyIds.begin(), end = keyIds.end(); itr != end; ++itr) {
         const QString& keyOwnerJid = itr.key();
         const QByteArray& keyId = itr.value();
-        Keys map = cache->getRecord(keyOwnerJid);
-        std::pair<Keys::iterator, bool> result = map.insert(std::make_pair(keyId, level));
-        if (result.second) {
+        try {
+            Keys map = cache->getRecord(keyOwnerJid);
+            std::pair<Keys::iterator, bool> result = map.insert(std::make_pair(keyId, level));
+            bool changed = result.second;
+            if (!changed && result.first->second != level) {
+                result.first->second = level;
+                changed = true;
+            }
+            if (changed) {
+                modifiedKeys[encryption].insert(keyOwnerJid, keyId);
+                cache->changeRecord(keyOwnerJid, map);
+            }
+        } catch (const DataBase::NotFound& e) {
+            Keys map({{keyId, level}});
             modifiedKeys[encryption].insert(keyOwnerJid, keyId);
-            cache->changeRecord(keyOwnerJid, map);
-        } else if (result.first->second != level) {
-            result.first->second = level;
-            modifiedKeys[encryption].insert(keyOwnerJid, keyId);
-            cache->changeRecord(keyOwnerJid, map);
+            cache->addRecord(keyOwnerJid, map);
         }
     }
     return Core::makeReadyTask(std::move(modifiedKeys));
@@ -199,9 +210,8 @@ QXmppTask<QHash<QXmpp::TrustLevel, QMultiHash<QString, QByteArray>>> TrustHandle
     for (const std::pair<const QString, Keys>& value : storage) {
         for (const std::pair<const QByteArray, Shared::TrustLevel>& pair : value.second) {
             QXmpp::TrustLevel level = convert(pair.second);
-            if (!trustLevels || trustLevels.testFlag(level)) {
+            if (!trustLevels || trustLevels.testFlag(level))
                 res[level].insert(value.first, pair.first);
-            }
         }
     }
     return Core::makeReadyTask(std::move(res));
@@ -219,9 +229,8 @@ QXmppTask<void> TrustHandler::removeKeys(const QString& encryption, const QStrin
 
 QXmppTask<void> TrustHandler::removeKeys(const QString& encryption, const QList<QByteArray>& keyIds) {
     std::set<QByteArray> set;
-    for (const QByteArray& keyId : keyIds) {
+    for (const QByteArray& keyId : keyIds)
         set.insert(keyId);
-    }
 
     KeyCache* cache = getCache(encryption);
     std::map<QString, Keys> data = cache->readAll();
@@ -233,19 +242,16 @@ QXmppTask<void> TrustHandler::removeKeys(const QString& encryption, const QList<
             if (set.erase(keyId)) {
                 byOwner.erase(itr++);
                 changed = true;
-            } else {
+            } else
                 ++itr;
-            }
         }
-        if (byOwner.size() > 0) {
+        if (byOwner.size() > 0)
             data.erase(cItr++);
-        } else {
+        else
             ++cItr;
-        }
     }
-    if (changed) {
+    if (changed)
         cache->replaceAll(data);
-    }
 
     return Core::makeReadyTask();
 }
@@ -266,9 +272,8 @@ QXmppTask<void> TrustHandler::addKeys(
     } catch (const DataBase::NotFound& e) {}
     for (const QByteArray& keyId : keyIds) {
         std::pair<Keys::iterator, bool> result = data.insert(std::make_pair(keyId, level));
-        if (!result.second) {
+        if (!result.second)
             result.first->second = level;
-        }
     }
 
     if (had) {
diff --git a/core/handlers/vcardhandler.cpp b/core/handlers/vcardhandler.cpp
index 6c5cb5b..d4125e8 100644
--- a/core/handlers/vcardhandler.cpp
+++ b/core/handlers/vcardhandler.cpp
@@ -17,13 +17,9 @@
 #include "vcardhandler.h"
 #include "core/account.h"
 
-constexpr const char* ns_omemo_2 = "urn:xmpp:omemo:2";
-
 Core::VCardHandler::VCardHandler(Account* account):
     QObject(),
     acc(account),
-    ownVCardRequestInProgress(false),
-    pendingVCardRequests(),
     avatarHash(),
     avatarType()
 {
@@ -90,36 +86,20 @@ void Core::VCardHandler::onVCardReceived(const QXmppVCardIq& card) {
     if (comps.size() > 1) {
         resource = comps.back();
     }
-    pendingVCardRequests.erase(id);
     RosterItem* item = acc->rh->getRosterItem(jid);
 
     if (item == 0) {
-        if (jid == acc->getBareJid()) {
+        if (jid == acc->getBareJid())
             onOwnVCardReceived(card);
-        } else {
+        else
             qDebug() << "received vCard" << jid << "doesn't belong to any of known contacts or conferences, skipping";
-        }
+
         return;
     }
 
-    Shared::Info info(jid, Shared::EntryType::contact);
-    item->handleResponseVCard(card, resource, info.getVCardRef());
-#ifdef WITH_OMEMO
-    std::list<Shared::KeyInfo>& aks = info.getActiveKeysRef();
-    acc->oh->getDevices(jid, aks);
-    std::map<QByteArray, Shared::TrustLevel> trustLevels = acc->th->getKeys(ns_omemo_2, jid);
-
-    qDebug() << "OMEMO info for " << jid << " devices:" << aks.size() << ", trustLevels:" << trustLevels.size();
-    for (Shared::KeyInfo& key : aks) {
-        std::map<QByteArray, Shared::TrustLevel>::const_iterator itr = trustLevels.find(key.fingerPrint);
-        if (itr != trustLevels.end()) {
-            key.trustLevel = itr->second;
-            qDebug() << "Found a trust level for a device!";
-        }
-    }
-#endif
-
-    emit acc->infoReady(info);
+    Shared::VCard vCard;
+    item->handleResponseVCard(card, resource, vCard);
+    acc->delay->receivedVCard(id, vCard);
 }
 
 void Core::VCardHandler::onOwnVCardReceived(const QXmppVCardIq& card) {
@@ -201,10 +181,7 @@ void Core::VCardHandler::onOwnVCardReceived(const QXmppVCardIq& card) {
         emit acc->changed(change);
     }
 
-    ownVCardRequestInProgress = false;
-
-    Shared::Info info(acc->getBareJid(), Shared::EntryType::ownAccount);
-    Shared::VCard& vCard = info.getVCardRef();
+    Shared::VCard vCard;
     initializeVCard(vCard, card);
 
     if (avatarType.size() > 0) {
@@ -214,52 +191,24 @@ void Core::VCardHandler::onOwnVCardReceived(const QXmppVCardIq& card) {
         vCard.setAvatarType(Shared::Avatar::empty);
     }
 
-    emit acc->infoReady(info);
-}
-
-void Core::VCardHandler::handleOffline() {
-    pendingVCardRequests.clear();
-    for (const QString& jid : pendingVCardRequests) {
-        Shared::Info info(jid, Shared::EntryType::none);
-        emit acc->infoReady(info);     //need to show it better in the future, like with an error
-    }
-    pendingVCardRequests.clear();
-    ownVCardRequestInProgress = false;
-}
-
-void Core::VCardHandler::requestVCard(const QString& jid) {
-    if (pendingVCardRequests.find(jid) == pendingVCardRequests.end()) {
-        qDebug() << "requesting vCard" << jid;
-        if (jid == acc->getBareJid()) {
-            if (!ownVCardRequestInProgress) {
-                acc->vm->requestClientVCard();
-                ownVCardRequestInProgress = true;
-            }
-        } else {
-            acc->vm->requestVCard(jid);
-            pendingVCardRequests.insert(jid);
-        }
-    }
+    if (acc->delay->isOwnVCardPending())
+        acc->delay->receivedOwnVCard(vCard);
 }
 
 void Core::VCardHandler::handlePresenceOfMyAccountChange(const QXmppPresence& p_presence) {
-    if (!ownVCardRequestInProgress) {
+    if (!acc->delay->isOwnVCardPending()) {
         switch (p_presence.vCardUpdateType()) {
             case QXmppPresence::VCardUpdateNone:            //this presence has nothing to do with photo
                 break;
             case QXmppPresence::VCardUpdateNotReady:        //let's say the photo didn't change here
                 break;
             case QXmppPresence::VCardUpdateNoPhoto:         //there is no photo, need to drop if any
-                if (avatarType.size() > 0) {
-                    acc->vm->requestClientVCard();
-                    ownVCardRequestInProgress = true;
-                }
+                if (avatarType.size() > 0)
+                    acc->delay->getOwnVCard();
                 break;
             case QXmppPresence::VCardUpdateValidPhoto:      //there is a photo, need to load
-                if (avatarHash != p_presence.photoHash()) {
-                    acc->vm->requestClientVCard();
-                    ownVCardRequestInProgress = true;
-                }
+                if (avatarHash != p_presence.photoHash())
+                    acc->delay->getOwnVCard();
                 break;
         }
     }
diff --git a/core/handlers/vcardhandler.h b/core/handlers/vcardhandler.h
index f774cd5..469398b 100644
--- a/core/handlers/vcardhandler.h
+++ b/core/handlers/vcardhandler.h
@@ -42,8 +42,6 @@ public:
     VCardHandler(Account* account);
     ~VCardHandler();
 
-    void handleOffline();
-    void requestVCard(const QString& jid);
     void handlePresenceOfMyAccountChange(const QXmppPresence& p_presence);
     void uploadVCard(const Shared::VCard& card);
     QString getAvatarPath() const;
@@ -57,8 +55,6 @@ private slots:
 private:
     Account* acc;
 
-    bool ownVCardRequestInProgress;
-    std::set<QString> pendingVCardRequests;
     QString avatarHash;
     QString avatarType;
 };
diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt
index 3c23932..b5bc725 100644
--- a/main/CMakeLists.txt
+++ b/main/CMakeLists.txt
@@ -1,7 +1,17 @@
-target_sources(squawk PRIVATE
+set(SOURCE_FILES
     main.cpp
     application.cpp
-    application.h
     dialogqueue.cpp
-    dialogqueue.h
+    root.cpp
+)
+
+set(HEADER_FILES
+    application.h
+    dialogqueue.h
+    root.h
+)
+
+target_sources(squawk PRIVATE
+    ${SOURCE_FILES}
+    ${HEADER_FILES}
 )
diff --git a/main/main.cpp b/main/main.cpp
index 9147ef0..5a7e034 100644
--- a/main/main.cpp
+++ b/main/main.cpp
@@ -16,6 +16,7 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
+#include "root.h"
 #include "shared/global.h"
 #include "shared/messageinfo.h"
 #include "shared/pathcheck.h"
@@ -31,7 +32,6 @@
 #include <QTranslator>
 #include <QtCore/QObject>
 #include <QtCore/QThread>
-#include <QtWidgets/QApplication>
 #include <QDir>
 
 #ifdef WITH_OMEMO
@@ -59,13 +59,13 @@ int main(int argc, char *argv[])
     qRegisterMetaType<QXmppOmemoStorage::Device>("QXmppOmemoStorage::Device");
 #endif
     
-    QApplication app(argc, argv);
+    Root app(argc, argv);
     SignalCatcher sc(&app);
 
-    QApplication::setApplicationName("squawk");
-    QApplication::setOrganizationName("macaw.me");
-    QApplication::setApplicationDisplayName("Squawk");
-    QApplication::setApplicationVersion("0.2.3");
+    Root::setApplicationName("squawk");
+    Root::setOrganizationName("macaw.me");
+    Root::setApplicationDisplayName("Squawk");
+    Root::setApplicationVersion("0.2.3");
     app.setDesktopFileName("squawk");
 
     QTranslator qtTranslator;
@@ -97,7 +97,7 @@ int main(int argc, char *argv[])
     icon.addFile(":images/logo.svg", QSize(128, 128));
     icon.addFile(":images/logo.svg", QSize(256, 256));
     icon.addFile(":images/logo.svg", QSize(512, 512));
-    QApplication::setWindowIcon(icon);
+    Root::setWindowIcon(icon);
     
     new Shared::Global();        //translates enums
 
@@ -139,7 +139,7 @@ int main(int argc, char *argv[])
     //QObject::connect(&app, &QApplication::aboutToQuit, &w, &QMainWindow::close);
     QObject::connect(squawk, &Core::Squawk::quit, squawk, &Core::Squawk::deleteLater);
     QObject::connect(squawk, &Core::Squawk::destroyed, coreThread, &QThread::quit, Qt::QueuedConnection);
-    QObject::connect(coreThread, &QThread::finished, &app, &QApplication::quit, Qt::QueuedConnection);
+    QObject::connect(coreThread, &QThread::finished, &app, &Root::quit, Qt::QueuedConnection);
     
     coreThread->start();
     int result = app.exec();
diff --git a/main/root.cpp b/main/root.cpp
new file mode 100644
index 0000000..96b37b9
--- /dev/null
+++ b/main/root.cpp
@@ -0,0 +1,43 @@
+// Squawk messenger. 
+// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+// 
+// 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 "root.h"
+#include <QDebug>
+#include <QThread>
+
+Root::Root(int& argc, char *argv[]) :
+    QApplication(argc, argv)
+    {}
+
+bool Root::notify(QObject* receiver, QEvent* e) {
+    try {
+        return QApplication::notify(receiver, e);
+    } catch(const std::runtime_error& e) {
+        qDebug() << "std::runtime_error in thread : " << QThread::currentThreadId();
+        qDebug() << e.what();
+    } catch(const std::exception& e) {
+        qDebug() << "std::exception in thread : " << QThread::currentThreadId();
+        qDebug() << e.what();
+    } catch(const int& e) {
+        qDebug() << "int exception in thread : " << QThread::currentThreadId();
+        qDebug() << e;
+    } catch(...) {
+        qDebug() << "exception thread : " << QThread::currentThreadId();
+    }
+
+    qDebug() << "catch in notify ";
+    return false;
+}
diff --git a/main/root.h b/main/root.h
new file mode 100644
index 0000000..02a82bc
--- /dev/null
+++ b/main/root.h
@@ -0,0 +1,28 @@
+// Squawk messenger. 
+// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+// 
+// 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/>.
+
+#ifndef ROOT_H
+#define ROOT_H
+
+#include <QApplication>
+
+class Root : public QApplication {
+public:
+    Root(int& argc, char* argv[]);
+    bool notify(QObject* receiver, QEvent* e) override;
+};
+
+#endif // ROOT_H

From 4b68da458f6058d979b60d0a87438da123cf25b1 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Sat, 11 Mar 2023 19:46:23 +0300
Subject: [PATCH 240/281] debugged a crash, keys are now fetching, refactored
 main, added some exceptions instead of ints, debugged termination process

---
 core/delayManager/job.h        |   6 ++
 core/delayManager/manager.cpp  | 103 ++++++++++++++++----
 core/delayManager/manager.h    |  21 +++++
 core/handlers/trusthandler.cpp |   4 +-
 external/qxmpp                 |   2 +-
 external/storage               |   2 +-
 main/main.cpp                  | 104 +-------------------
 main/root.cpp                  | 167 +++++++++++++++++++++++++++++++--
 main/root.h                    |  42 +++++++++
 9 files changed, 321 insertions(+), 130 deletions(-)

diff --git a/core/delayManager/job.h b/core/delayManager/job.h
index 633f2b2..6bfdc1d 100644
--- a/core/delayManager/job.h
+++ b/core/delayManager/job.h
@@ -32,6 +32,12 @@ public:
         infoForUser,
         ownInfoForUser
     };
+    inline static constexpr const char * const TypeString[] = {
+        "cardInternal",
+        "ownCardInternal",
+        "infoForUser",
+        "ownInfoForUser"
+    };
 protected:
     Job(Id id, Type type);
     Job(const Job& other);
diff --git a/core/delayManager/manager.cpp b/core/delayManager/manager.cpp
index 6a15544..3281bc6 100644
--- a/core/delayManager/manager.cpp
+++ b/core/delayManager/manager.cpp
@@ -67,8 +67,13 @@ void Core::DelayManager::Manager::getInfo(const QString& jid) {
     Job* job = nullptr;
 #ifdef WITH_OMEMO
     std::map<QString, Job::Id>::const_iterator bitr = requestedBundles.find(jid);
-    if (bitr != requestedBundles.end())
-        job = runningJobs.at(bitr->second);
+    if (bitr != requestedBundles.end()) {
+        std::map<Job::Id, Job*>::const_iterator itr = runningJobs.find(bitr->second);
+        if (itr == runningJobs.end())
+            throw JobNotFound(bitr->second);
+
+        job = itr->second;
+    }
     else
 #endif
         job = getVCardJob(jid);
@@ -109,10 +114,19 @@ Core::DelayManager::Job* Core::DelayManager::Manager::getVCardJob(const QString&
     std::map<QString, Job::Id>::const_iterator sitr = scheduledVCards.find(jid);
     if (sitr == scheduledVCards.end()) {
         std::map<QString, Job::Id>::const_iterator ritr = requestedVCards.find(jid);
-        if (ritr != requestedVCards.end())
-            job = runningJobs.at(ritr->second);
+        if (ritr != requestedVCards.end()) {
+            std::map<Job::Id, Job*>::const_iterator itr = runningJobs.find(ritr->second);
+            if (itr == runningJobs.end())
+                throw JobNotFound(ritr->second, "getVCardJob:1");
+
+            job = itr->second;
+        }
     } else {
-        job = *(scheduledJobsById.find(sitr->second));
+        StorageById::const_iterator itr = scheduledJobsById.find(sitr->second);
+        if (itr == scheduledJobsById.end())
+            throw JobNotFound(sitr->second, "getVCardJob:2");
+
+        job = *itr;
     }
 
     return job;
@@ -177,7 +191,7 @@ void Core::DelayManager::Manager::executeJob(Job* job) {
 void Core::DelayManager::Manager::jobIsDone(Job::Id jobId) {
     std::map<Job::Id, Job*>::const_iterator itr = runningJobs.find(jobId);
     if (itr == runningJobs.end()) {
-        throw 8573; //not supposed to happen, ever
+        throw JobNotFound(jobId, "jobIsDone");
     }
     Job* job = itr->second;
     delete job;
@@ -201,9 +215,8 @@ void Core::DelayManager::Manager::replaceJob(Job* job) {
         if (sitr != scheduledJobsById.end()) {
             delete *(sitr);
             scheduledJobsById.replace(sitr, job);
-        } else {
-            throw 8574; //not supposed to happen, ever
-        }
+        } else
+            throw JobNotFound(job->id, "replaceJob");
     }
 }
 
@@ -278,7 +291,12 @@ void Core::DelayManager::Manager::receivedVCard(const QString& jid, const Shared
     }
     Job::Id jobId = cardItr->second;
     requestedVCards.erase(cardItr);
-    Job* job = runningJobs.at(jobId);
+    std::map<Job::Id, Job*>::const_iterator itr = runningJobs.find(jobId);
+    if (itr == runningJobs.end()) {
+        throw JobNotFound(jobId, "receivedVCard");
+    }
+    Job* job = itr->second;
+
     switch (job->type) {
         case Job::Type::cardInternal:
             jobIsDone(jobId);
@@ -300,14 +318,22 @@ void Core::DelayManager::Manager::receivedVCard(const QString& jid, const Shared
         }
             break;
         default:
-            throw 8576;
+            throw UnexpectedJobType(job->type, "receivedVCard");
     }
 }
 
 void Core::DelayManager::Manager::receivedOwnVCard(const Shared::VCard& card) {
+    if (ownVCardJobId == 0) {
+        qDebug() << "received own VCard for" << ownJid << "but it was never requested through manager, ignoring";
+        return;
+    }
     Job::Id jobId = ownVCardJobId;
     ownVCardJobId = 0;
-    Job* job = runningJobs.at(jobId);
+    std::map<Job::Id, Job*>::const_iterator itr = runningJobs.find(jobId);
+    if (itr == runningJobs.end()) {
+        throw JobNotFound(jobId, "receivedOwnVCard");
+    }
+    Job* job = itr->second;
     switch (job->type) {
         case Job::Type::ownCardInternal:
             jobIsDone(jobId);
@@ -328,19 +354,24 @@ void Core::DelayManager::Manager::receivedOwnVCard(const Shared::VCard& card) {
         }
             break;
         default:
-            throw 8576;
+            throw UnexpectedJobType(job->type, "receivedVCard");
     }
 }
 
 void Core::DelayManager::Manager::receivedBundles(const QString& jid, const std::list<Shared::KeyInfo>& keys) {
     std::map<QString, Job::Id>::const_iterator itr = requestedBundles.find(jid);
     if (itr == requestedBundles.end()) {
-        throw 8577; //never supposed to happen, the state is not correct;
+        qDebug() << "received bundles for" << jid << "but they were never requested through manager, ignoring";
+        return;
     }
 
     Job::Id jobId = itr->second;
     requestedBundles.erase(itr);
-    Job* jb = runningJobs.at(jobId);
+    std::map<Job::Id, Job*>::const_iterator jitr = runningJobs.find(jobId);
+    if (jitr == runningJobs.end()) {
+        throw JobNotFound(jobId, "receivedBundles");
+    }
+    Job* jb = jitr->second;
     InfoForUser* job = dynamic_cast<InfoForUser*>(jb);
 
     Shared::Info info(jid);
@@ -350,9 +381,17 @@ void Core::DelayManager::Manager::receivedBundles(const QString& jid, const std:
 }
 
 void Core::DelayManager::Manager::receivedOwnBundles(const std::list<Shared::KeyInfo>& keys) {
+    if (ownInfoJobId == 0) {
+        qDebug() << "received own bundles for" << ownJid << "but they were never requested through manager, ignoring";
+        return;
+    }
     Job::Id jobId = ownInfoJobId;
     ownInfoJobId = 0;
-    Job* jb = runningJobs.at(jobId);
+    std::map<Job::Id, Job*>::const_iterator jitr = runningJobs.find(jobId);
+    if (jitr == runningJobs.end()) {
+        throw JobNotFound(jobId, "receivedOwnBundles");
+    }
+    Job* jb = jitr->second;
     OwnInfoForUser* job = dynamic_cast<OwnInfoForUser*>(jb);
 
     Shared::Info info(ownJid);
@@ -364,3 +403,35 @@ void Core::DelayManager::Manager::receivedOwnBundles(const std::list<Shared::Key
 void Core::DelayManager::Manager::setOwnJid(const QString& jid) {
     ownJid = jid;
 }
+
+
+Core::DelayManager::Manager::UnexpectedJobType::UnexpectedJobType(Job::Type p_type, const std::string& p_method):
+    Exception(),
+    type(p_type),
+    method(p_method)
+{}
+
+std::string Core::DelayManager::Manager::UnexpectedJobType::getMessage() const{
+    std::string msg("Unexpected job type: ");
+    msg += Job::TypeString[static_cast<int>(type)];
+    if (method.size() > 0) {
+        msg += " in method " + method;
+    }
+    return msg;
+}
+
+Core::DelayManager::Manager::JobNotFound::JobNotFound(Job::Id p_id, const std::string& p_method) :
+    Exception(),
+    id(p_id),
+    method(p_method)
+{}
+
+std::string Core::DelayManager::Manager::JobNotFound::getMessage() const {
+    std::string msg("Job with id ");
+    msg += std::to_string(id);
+    msg += " was not found";
+    if (method.size() > 0) {
+        msg += " in method " + method;
+    }
+    return msg;
+}
diff --git a/core/delayManager/manager.h b/core/delayManager/manager.h
index 61920ec..127d25a 100644
--- a/core/delayManager/manager.h
+++ b/core/delayManager/manager.h
@@ -19,6 +19,7 @@
 
 #include <list>
 #include <set>
+#include <string>
 
 #include <boost/multi_index_container.hpp>
 #include <boost/multi_index/ordered_index.hpp>
@@ -30,6 +31,7 @@
 
 #include <shared/vcard.h>
 #include <shared/info.h>
+#include <shared/exception.h>
 
 #include "job.h"
 
@@ -121,6 +123,25 @@ private:
     std::map<QString, Job::Id> requestedBundles;
 #endif
     QString ownJid;
+
+public:
+    class UnexpectedJobType: public Utils::Exception {
+    public:
+        UnexpectedJobType(Job::Type p_type, const std::string& p_method = "");
+        std::string getMessage() const override;
+    private:
+        Job::Type type;
+        std::string method;
+    };
+
+    class JobNotFound: public Utils::Exception {
+    public:
+        JobNotFound(Job::Id p_id, const std::string& p_method = "");
+        std::string getMessage() const override;
+    private:
+        Job::Id id;
+        std::string method;
+    };
 };
 
 }
diff --git a/core/handlers/trusthandler.cpp b/core/handlers/trusthandler.cpp
index eb75219..647d568 100644
--- a/core/handlers/trusthandler.cpp
+++ b/core/handlers/trusthandler.cpp
@@ -93,7 +93,9 @@ QXmppTask<QXmpp::TrustLevel> Core::TrustHandler::trustLevel(
     Shared::TrustLevel level = Shared::TrustLevel::undecided;
     try {
         Keys map = cache->getRecord(keyOwnerJid);
-        level = map.at(keyId);
+        Keys::const_iterator itr = map.find(keyId);
+        if (itr != map.end())
+            level = itr->second;
     } catch (const DataBase::NotFound& e) {}
     return Core::makeReadyTask(std::move(convert(level)));
 }
diff --git a/external/qxmpp b/external/qxmpp
index 9d57624..d679ad1 160000
--- a/external/qxmpp
+++ b/external/qxmpp
@@ -1 +1 @@
-Subproject commit 9d5762499fbddb3dd1ed8eeca16f9db70adc27d0
+Subproject commit d679ad1c49eeb28be2ac3a75bd7fd1a9be24d483
diff --git a/external/storage b/external/storage
index a79dae8..6a8f67a 160000
--- a/external/storage
+++ b/external/storage
@@ -1 +1 @@
-Subproject commit a79dae8fd07b36446041f6f85e2ad42cf5ae5264
+Subproject commit 6a8f67ac34de286588cd89e3218f55b54da47f42
diff --git a/main/main.cpp b/main/main.cpp
index 5a7e034..afe1cf2 100644
--- a/main/main.cpp
+++ b/main/main.cpp
@@ -17,22 +17,11 @@
  */
 
 #include "root.h"
-#include "shared/global.h"
 #include "shared/messageinfo.h"
-#include "shared/pathcheck.h"
 #include "shared/identity.h"
 #include "shared/info.h"
-#include "main/application.h"
-#include "core/signalcatcher.h"
-#include "core/squawk.h"
 
-#include <QLibraryInfo>
-#include <QSettings>
-#include <QStandardPaths>
-#include <QTranslator>
-#include <QtCore/QObject>
-#include <QtCore/QThread>
-#include <QDir>
+#include <QObject>
 
 #ifdef WITH_OMEMO
 #include <QXmppOmemoStorage.h>
@@ -60,97 +49,10 @@ int main(int argc, char *argv[])
 #endif
     
     Root app(argc, argv);
-    SignalCatcher sc(&app);
-
-    Root::setApplicationName("squawk");
-    Root::setOrganizationName("macaw.me");
-    Root::setApplicationDisplayName("Squawk");
-    Root::setApplicationVersion("0.2.3");
-    app.setDesktopFileName("squawk");
-
-    QTranslator qtTranslator;
-    qtTranslator.load("qt_" + QLocale::system().name(), QLibraryInfo::location(QLibraryInfo::TranslationsPath));
-    app.installTranslator(&qtTranslator);
-
-    QTranslator myappTranslator;
-    QStringList shares = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation);
-    bool found = false;
-    for (QString share : shares) {
-        found = myappTranslator.load(QLocale(), QLatin1String("squawk"), ".", share + "/l10n");
-        if (found) {
-            break;
-        }
-    }
-    if (!found) {
-        myappTranslator.load(QLocale(), QLatin1String("squawk"), ".", QCoreApplication::applicationDirPath());
-    }
-    
-    app.installTranslator(&myappTranslator);
-    
-    QIcon icon;
-    icon.addFile(":images/logo.svg", QSize(16, 16));
-    icon.addFile(":images/logo.svg", QSize(24, 24));
-    icon.addFile(":images/logo.svg", QSize(32, 32));
-    icon.addFile(":images/logo.svg", QSize(48, 48));
-    icon.addFile(":images/logo.svg", QSize(64, 64));
-    icon.addFile(":images/logo.svg", QSize(96, 96));
-    icon.addFile(":images/logo.svg", QSize(128, 128));
-    icon.addFile(":images/logo.svg", QSize(256, 256));
-    icon.addFile(":images/logo.svg", QSize(512, 512));
-    Root::setWindowIcon(icon);
-    
-    new Shared::Global();        //translates enums
-
-    QSettings settings;
-    QVariant vs = settings.value("style");
-    if (vs.isValid()) {
-        QString style = vs.toString().toLower();
-        if (style != "system") {
-            Shared::Global::setStyle(style);
-        }
-    }
-    if (Shared::Global::supported("colorSchemeTools")) {
-        QVariant vt = settings.value("theme");
-        if (vt.isValid()) {
-            QString theme = vt.toString();
-            if (theme.toLower() != "system") {
-                Shared::Global::setTheme(theme);
-            }
-        }
-    }
-    QString path = Shared::downloadsPathCheck();
-    if (path.size() > 0) {
-        settings.setValue("downloadsPath", path);
-    } else {
-        qDebug() << "couldn't initialize directory for downloads, quitting";
+    if (!app.initializeSettings())
         return -1;
-    }
-    
-    Core::Squawk* squawk = new Core::Squawk();
-    QThread* coreThread = new QThread();
-    squawk->moveToThread(coreThread);
-    
-    Application application(squawk);
 
-    QObject::connect(&sc, &SignalCatcher::interrupt, &application, &Application::quit);
-
-    QObject::connect(coreThread, &QThread::started, squawk, &Core::Squawk::start);
-    QObject::connect(&application, &Application::quitting, squawk, &Core::Squawk::stop);
-    //QObject::connect(&app, &QApplication::aboutToQuit, &w, &QMainWindow::close);
-    QObject::connect(squawk, &Core::Squawk::quit, squawk, &Core::Squawk::deleteLater);
-    QObject::connect(squawk, &Core::Squawk::destroyed, coreThread, &QThread::quit, Qt::QueuedConnection);
-    QObject::connect(coreThread, &QThread::finished, &app, &Root::quit, Qt::QueuedConnection);
     
-    coreThread->start();
-    int result = app.exec();
-
-    if (coreThread->isRunning()) {
-        //coreThread->wait();
-        //todo if I uncomment that, the app will not quit if it has reconnected at least once
-        //it feels like a symptom of something badly desinged in the core thread
-        //need to investigate;
-    }
-    
-    return result;
+    return app.run();
 }
 
diff --git a/main/root.cpp b/main/root.cpp
index 96b37b9..d43ae2f 100644
--- a/main/root.cpp
+++ b/main/root.cpp
@@ -17,27 +17,174 @@
 #include "root.h"
 #include <QDebug>
 #include <QThread>
+#include <QLibraryInfo>
+#include <QStandardPaths>
+
+#include <string>
+
+#include <shared/pathcheck.h>
+
+const std::vector<unsigned int> Root::appIconSizes({
+    16, 24, 32, 48, 64, 96, 128, 256, 512
+});
 
 Root::Root(int& argc, char *argv[]) :
-    QApplication(argc, argv)
-    {}
+    QApplication(argc, argv),
+    signalCatcher(this),
+    defaultTranslator(),
+    currentTranslator(),
+    appIcon(),
+    settings(),
+    componentsInitialized(false),
+    global(nullptr),
+    coreThread(nullptr),
+    core(nullptr),
+    gui(nullptr)
+{
+    setApplicationName("squawk");
+    setOrganizationName("macaw.me");
+    setApplicationDisplayName("Squawk");
+    setApplicationVersion("0.2.3");
+    setDesktopFileName("squawk");
+
+    initializeTranslation();
+    initializeAppIcon();
+
+    global = new Shared::Global();  //important to instantiate after initialization of translations;
+}
+
+Root::~Root() {
+    if (componentsInitialized) {
+        delete gui;
+        if (core != nullptr)
+            delete core;
+        delete coreThread;
+    }
+    delete global;
+}
+
+
+void Root::initializeTranslation() {
+    defaultTranslator.load("qt_" + QLocale::system().name(), QLibraryInfo::location(QLibraryInfo::TranslationsPath));
+    installTranslator(&defaultTranslator);
+
+    QStringList shares = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation);
+    bool found = false;
+    for (QString share : shares) {
+        found = currentTranslator.load(QLocale(), QLatin1String("squawk"), ".", share + "/l10n");
+        if (found) {
+            break;
+        }
+    }
+    if (!found) {
+        currentTranslator.load(QLocale(), QLatin1String("squawk"), ".", QCoreApplication::applicationDirPath());
+    }
+
+    installTranslator(&currentTranslator);
+}
+
+void Root::initializeAppIcon() {
+    for (std::vector<unsigned int>::size_type i = 0; i < appIconSizes.size(); ++i)
+        appIcon.addFile(":images/logo.svg", QSize(appIconSizes[i], appIconSizes[i]));
+
+    Root::setWindowIcon(appIcon);
+}
+
+bool Root::initializeSettings() {
+    QVariant vs = settings.value("style");
+    if (vs.isValid()) {
+        QString style = vs.toString().toLower();
+        if (style != "system") {
+            Shared::Global::setStyle(style);
+        }
+    }
+
+    if (Shared::Global::supported("colorSchemeTools")) {
+        QVariant vt = settings.value("theme");
+        if (vt.isValid()) {
+            QString theme = vt.toString();
+            if (theme.toLower() != "system") {
+                Shared::Global::setTheme(theme);
+            }
+        }
+    }
+
+    QString path = Shared::downloadsPathCheck();
+    if (path.size() > 0) {
+        settings.setValue("downloadsPath", path);
+    } else {
+        qDebug() << "couldn't initialize directory for downloads, quitting";
+        return false;
+    }
+
+    return true;
+}
+
+int Root::run() {
+    if (!componentsInitialized)
+        initializeComponents();
+
+    coreThread->start();
+    int result = exec();
+    qDebug("Event loop stopped");
+
+    if (result == 0) {
+        processEvents();                    //I dont like all of this mess
+        if (coreThread->isRunning()) {      //but it's the best solution for now
+            if (core != nullptr) {          //Ideally, following line should never appear in the log
+                qDebug() << "Core is still seems to be running, killing manually";
+                core->deleteLater();
+                coreThread->quit();
+                processEvents();
+                core = nullptr;
+            }
+            coreThread->wait();
+        }
+    }
+
+    return result;
+}
+
+void Root::initializeComponents() {
+    core = new Core::Squawk();
+    coreThread = new QThread();
+    core->moveToThread(coreThread);
+    gui = new Application(core);
+
+    QObject::connect(&signalCatcher, &SignalCatcher::interrupt, gui, &Application::quit);
+
+    QObject::connect(coreThread, &QThread::started, core, &Core::Squawk::start);
+    QObject::connect(gui, &Application::quitting, core, &Core::Squawk::stop);
+
+    QObject::connect(core, &Core::Squawk::quit, core, &Core::Squawk::deleteLater);
+    QObject::connect(core, &Core::Squawk::destroyed, this, &Root::onCoreDestroyed);
+    QObject::connect(core, &Core::Squawk::destroyed, coreThread, &QThread::quit, Qt::QueuedConnection);
+    QObject::connect(coreThread, &QThread::finished, this, &Root::quit, Qt::QueuedConnection);
+
+    componentsInitialized = true;
+}
 
 bool Root::notify(QObject* receiver, QEvent* e) {
     try {
         return QApplication::notify(receiver, e);
     } catch(const std::runtime_error& e) {
-        qDebug() << "std::runtime_error in thread : " << QThread::currentThreadId();
-        qDebug() << e.what();
+        qDebug() << "std::runtime_error in thread:" << QThread::currentThreadId();
+        qDebug() << "error message:" << e.what();
     } catch(const std::exception& e) {
-        qDebug() << "std::exception in thread : " << QThread::currentThreadId();
-        qDebug() << e.what();
+        qDebug() << "std::exception in thread:" << QThread::currentThreadId();
+        qDebug() << "error message:" << e.what();
     } catch(const int& e) {
-        qDebug() << "int exception in thread : " << QThread::currentThreadId();
-        qDebug() << e;
+        qDebug() << "integer exception in thread:" << QThread::currentThreadId();
+        qDebug() << "thrown integer:" << std::to_string(e).c_str();
     } catch(...) {
-        qDebug() << "exception thread : " << QThread::currentThreadId();
+        qDebug() << "unhandled exception thread:" << QThread::currentThreadId();
     }
 
-    qDebug() << "catch in notify ";
+    qDebug() << "Squawk is crashing...";
+    exit(1);
     return false;
 }
+
+void Root::onCoreDestroyed() {
+    core = nullptr;
+}
diff --git a/main/root.h b/main/root.h
index 02a82bc..003d9cb 100644
--- a/main/root.h
+++ b/main/root.h
@@ -18,11 +18,53 @@
 #define ROOT_H
 
 #include <QApplication>
+#include <QTranslator>
+#include <QIcon>
+#include <QSettings>
+#include <QThread>
+
+#include <vector>
+
+#include <core/squawk.h>
+#include <core/signalcatcher.h>
+
+#include <shared/global.h>
+
+#include "application.h"
 
 class Root : public QApplication {
+    Q_OBJECT
 public:
     Root(int& argc, char* argv[]);
+    ~Root();
     bool notify(QObject* receiver, QEvent* e) override;
+    int run();
+
+    bool initializeSettings();
+
+private slots:
+    void onCoreDestroyed();
+
+private:
+    void initializeTranslation();
+    void initializeAppIcon();
+    void initializeComponents();
+
+private:
+    static const std::vector<unsigned int> appIconSizes;
+
+    SignalCatcher signalCatcher;
+    QTranslator defaultTranslator;
+    QTranslator currentTranslator;
+    QIcon appIcon;
+    QSettings settings;
+    bool componentsInitialized;
+
+    Shared::Global* global;
+    QThread* coreThread;
+    Core::Squawk* core;
+    Application* gui;
+
 };
 
 #endif // ROOT_H

From 8ec0af3205d8297b8d92ef7140bb67d628a5f646 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Sun, 12 Mar 2023 01:38:54 +0300
Subject: [PATCH 241/281] transition to QXMppCarbonManagerV2 if QXmpp version
 is heigher than 1.5.0

---
 core/account.cpp                   |  8 ++++++++
 core/account.h                     |  8 ++++++++
 core/handlers/discoveryhandler.cpp |  2 ++
 core/handlers/messagehandler.cpp   | 10 ++++++++--
 core/handlers/messagehandler.h     |  2 ++
 5 files changed, 28 insertions(+), 2 deletions(-)

diff --git a/core/account.cpp b/core/account.cpp
index c27f2ea..a0b7311 100644
--- a/core/account.cpp
+++ b/core/account.cpp
@@ -42,7 +42,11 @@ Account::Account(const QString& p_login, const QString& p_server, const QString&
     tm(new QXmppTrustManager(th)),
     om(new QXmppOmemoManager(oh)),
 #endif
+#if (QXMPP_VERSION) >= QT_VERSION_CHECK(1, 5, 0)
+    cm(new QXmppCarbonManagerV2()),
+#else
     cm(new QXmppCarbonManager()),
+#endif
     am(new QXmppMamManager()),
     mm(new QXmppMucManager()),
     bm(new QXmppBookmarkManager()),
@@ -86,8 +90,10 @@ Account::Account(const QString& p_login, const QString& p_server, const QString&
     
     client.addExtension(cm);
     
+#if (QXMPP_VERSION) < QT_VERSION_CHECK(1, 5, 0)
     QObject::connect(cm, &QXmppCarbonManager::messageReceived, mh, &MessageHandler::onCarbonMessageReceived);
     QObject::connect(cm, &QXmppCarbonManager::messageSent, mh, &MessageHandler::onCarbonMessageSent);
+#endif
     
     client.addExtension(am);
     
@@ -653,7 +659,9 @@ void Core::Account::setPepSupport(Shared::Support support) {
 void Core::Account::handleDisconnection() {
     setPepSupport(Shared::Support::unknown);
     delay->disconnected();
+#if (QXMPP_VERSION) < QT_VERSION_CHECK(1, 5, 0)
     cm->setCarbonsEnabled(false);
+#endif
     rh->handleOffline();
     archiveQueries.clear();
 }
diff --git a/core/account.h b/core/account.h
index e18966d..58ff9b5 100644
--- a/core/account.h
+++ b/core/account.h
@@ -32,7 +32,11 @@
 #include <list>
 
 #include <QXmppRosterManager.h>
+#if (QXMPP_VERSION) >= QT_VERSION_CHECK(1, 5, 0)
+#include <QXmppCarbonManagerV2.h>
+#else
 #include <QXmppCarbonManager.h>
+#endif
 #include <QXmppDiscoveryManager.h>
 #include <QXmppMamManager.h>
 #include <QXmppMucManager.h>
@@ -193,7 +197,11 @@ private:
     QXmppTrustManager* tm;
     QXmppOmemoManager* om;
 #endif
+#if (QXMPP_VERSION) >= QT_VERSION_CHECK(1, 5, 0)
+    QXmppCarbonManagerV2* cm;
+#else
     QXmppCarbonManager* cm;
+#endif
     QXmppMamManager* am;
     QXmppMucManager* mm;
     QXmppBookmarkManager* bm;
diff --git a/core/handlers/discoveryhandler.cpp b/core/handlers/discoveryhandler.cpp
index 977dda2..6ed06c6 100644
--- a/core/handlers/discoveryhandler.cpp
+++ b/core/handlers/discoveryhandler.cpp
@@ -79,7 +79,9 @@ void Core::DiscoveryHandler::onInfoReceived(const QXmppDiscoveryIq& info)
 
         if (enableCC) {
             qDebug() << "Enabling carbon copies for account" << accName;
+#if (QXMPP_VERSION) < QT_VERSION_CHECK(1, 5, 0)
             acc->cm->setCarbonsEnabled(true);
+#endif
 #ifdef WITH_OMEMO
             if (!omemoToCarbonsConnected && acc->oh->hasOwnDevice()) {
                 // connect(this, &QXmppCarbonManager::messageSent, acc->om, &QXmppOmemoManager::handleMessage);
diff --git a/core/handlers/messagehandler.cpp b/core/handlers/messagehandler.cpp
index b11ea2a..2c5b16d 100644
--- a/core/handlers/messagehandler.cpp
+++ b/core/handlers/messagehandler.cpp
@@ -64,7 +64,11 @@ void Core::MessageHandler::onMessageReceived(const QXmppMessage& msg)
             qDebug() << "received a message with type \"Normal\", not sure what to do with it now, skipping";
             break;
         case QXmppMessage::Chat:
+#if (QXMPP_VERSION) >= QT_VERSION_CHECK(1, 5, 0)
+            handled = handleChatMessage(msg, false, msg.isCarbonForwarded(), true);
+#else
             handled = handleChatMessage(msg);
+#endif
             break;
         case QXmppMessage::GroupChat:
             handled = handleGroupMessage(msg);
@@ -109,8 +113,8 @@ bool Core::MessageHandler::handleChatMessage(const QXmppMessage& msg, bool outgo
             cnt = acc->rh->addOutOfRosterContact(jid);
             qDebug() << "appending message" << sMsg.getId() << "to an out of roster contact";
         }
-        if (outgoing) {
-            if (forwarded) {
+        if (sMsg.getOutgoing()) {
+            if (sMsg.getForwarded()) {
                 sMsg.setState(Shared::Message::State::sent);
             }
         } else {
@@ -242,6 +246,7 @@ void Core::MessageHandler::logMessage(const QXmppMessage& msg, const QString& re
     qDebug() << "==============================";
 }
 
+#if (QXMPP_VERSION) < QT_VERSION_CHECK(1, 5, 0)
 void Core::MessageHandler::onCarbonMessageReceived(const QXmppMessage& msg)
 {
     handleChatMessage(msg, false, true);
@@ -251,6 +256,7 @@ void Core::MessageHandler::onCarbonMessageSent(const QXmppMessage& msg)
 {
     handleChatMessage(msg, true, true);
 }
+#endif
 
 std::tuple<bool, QString, QString> Core::MessageHandler::getOriginalPendingMessageId(const QString& id)
 {
diff --git a/core/handlers/messagehandler.h b/core/handlers/messagehandler.h
index 1ab2d0d..a08fee8 100644
--- a/core/handlers/messagehandler.h
+++ b/core/handlers/messagehandler.h
@@ -52,8 +52,10 @@ public:
     
 public slots:
     void onMessageReceived(const QXmppMessage& message);
+#if (QXMPP_VERSION) < QT_VERSION_CHECK(1, 5, 0)
     void onCarbonMessageReceived(const QXmppMessage& message);
     void onCarbonMessageSent(const QXmppMessage& message);
+#endif
     void onReceiptReceived(const QString& jid, const QString& id);    
     void onUploadSlotReceived(const QXmppHttpUploadSlotIq& slot);
     void onUploadSlotRequestFailed(const QXmppHttpUploadRequestIq& request);

From 76a9c5da0cf734e854e2dc49af47ad2e8a4394d4 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Mon, 13 Mar 2023 22:07:10 +0300
Subject: [PATCH 242/281] extracted clientId from clientInfo to use it in the
 presence information later

---
 core/components/clientcache.cpp |  6 ++--
 shared/CMakeLists.txt           | 46 +++++++++++++++++------------
 shared/clientid.cpp             | 52 +++++++++++++++++++++++++++++++++
 shared/clientid.h               | 45 ++++++++++++++++++++++++++++
 shared/clientinfo.cpp           | 18 ++++--------
 shared/clientinfo.h             |  8 ++---
 6 files changed, 137 insertions(+), 38 deletions(-)
 create mode 100644 shared/clientid.cpp
 create mode 100644 shared/clientid.h

diff --git a/core/components/clientcache.cpp b/core/components/clientcache.cpp
index a312b24..dfd9680 100644
--- a/core/components/clientcache.cpp
+++ b/core/components/clientcache.cpp
@@ -42,9 +42,9 @@ bool Core::ClientCache::checkClient(const QString& node, const QString& ver, con
     QString id = node + "/" + ver;
     if (requested.count(id) == 0 && !cache->checkRecord(id)) {
         Shared::ClientInfo& info = requested.insert(std::make_pair(id, Shared::ClientInfo())).first->second;
-        info.node = node;
-        info.verification = ver;
-        info.hash = hash;
+        info.id.node = node;
+        info.id.verification = ver;
+        info.id.hash = hash;
         emit requestClientInfo(id);
         return false;
     }
diff --git a/shared/CMakeLists.txt b/shared/CMakeLists.txt
index 9080fb6..5db96f0 100644
--- a/shared/CMakeLists.txt
+++ b/shared/CMakeLists.txt
@@ -1,33 +1,43 @@
-target_sources(squawk PRIVATE
-  enums.h
+set(SOURCE_FILES
   global.cpp
-  global.h
   exception.cpp
-  exception.h
   icons.cpp
-  icons.h
   message.cpp
-  message.h
   messageinfo.cpp
-  messageinfo.h
-  order.h
-  shared.h
   utils.cpp
-  utils.h
   vcard.cpp
-  vcard.h
   pathcheck.cpp
-  pathcheck.h
-  clientinfo.h
   clientinfo.cpp
-  identity.h
   identity.cpp
-  form.h
   form.cpp
-  field.h
   field.cpp
   keyinfo.cpp
-  keyinfo.h
   info.cpp
+  clientid.cpp
+)
+
+set(HEADER_FILES
+  order.h
+  shared.h
+  enums.h
+  global.h
+  exception.h
+  icons.h
+  message.h
+  messageinfo.h
+  utils.h
+  vcard.h
+  pathcheck.h
+  clientinfo.h
+  identity.h
+  form.h
+  field.h
+  keyinfo.h
   info.h
-  )
+  clientid.h
+)
+
+target_sources(squawk PRIVATE
+    ${SOURCE_FILES}
+    ${HEADER_FILES}
+)
diff --git a/shared/clientid.cpp b/shared/clientid.cpp
new file mode 100644
index 0000000..f16736c
--- /dev/null
+++ b/shared/clientid.cpp
@@ -0,0 +1,52 @@
+// Squawk messenger. 
+// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+// 
+// 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 "clientid.h"
+
+Shared::ClientId::ClientId():
+    node(),
+    verification(),
+    hash()
+{}
+
+QString Shared::ClientId::getId() const {
+    return node + "/" + verification;
+}
+
+QDataStream & Shared::ClientId::operator<<(QDataStream& stream) {
+    stream >> node;
+    stream >> verification;
+    stream >> hash;
+    return stream;
+}
+
+QDataStream & Shared::ClientId::operator>>(QDataStream& stream) const {
+    stream << node;
+    stream << verification;
+    stream << hash;
+    return stream;
+}
+
+QDataStream & operator<<(QDataStream& stream, const Shared::ClientId& info) {
+    info >> stream;
+    return stream;
+}
+
+QDataStream & operator>>(QDataStream& stream, Shared::ClientId& info) {
+    info << stream;
+    return stream;
+}
+
diff --git a/shared/clientid.h b/shared/clientid.h
new file mode 100644
index 0000000..defe909
--- /dev/null
+++ b/shared/clientid.h
@@ -0,0 +1,45 @@
+// Squawk messenger. 
+// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+// 
+// 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/>.
+
+#ifndef SHARED_CLIENTID_H
+#define SHARED_CLIENTID_H
+
+#include <QString>
+#include <QDataStream>
+
+namespace Shared {
+
+class ClientId {
+public:
+    ClientId();
+
+    QString getId() const;
+
+    QDataStream& operator << (QDataStream& stream);
+    QDataStream& operator >> (QDataStream& stream) const;
+
+public:
+    QString node;
+    QString verification;
+    QString hash;
+};
+
+}
+
+QDataStream& operator << (QDataStream& stream, const Shared::ClientId& info);
+QDataStream& operator >> (QDataStream& stream, Shared::ClientId& info);
+
+#endif // SHARED_CLIENTID_H
diff --git a/shared/clientinfo.cpp b/shared/clientinfo.cpp
index 86af1c8..9a3fdac 100644
--- a/shared/clientinfo.cpp
+++ b/shared/clientinfo.cpp
@@ -32,20 +32,16 @@ const std::map<QString, QCryptographicHash::Algorithm> Shared::ClientInfo::hashe
 Shared::ClientInfo::ClientInfo():
     identities(),
     extensions(),
-    node(),
-    verification(),
-    hash(),
+    id(),
     specificPresence() {}
 
 QString Shared::ClientInfo::getId() const {
-    return node + "/" + verification;
+    return id.getId();
 }
 
 
 QDataStream & Shared::ClientInfo::operator >> (QDataStream& stream) const {
-    stream << node;
-    stream << verification;
-    stream << hash;
+    stream << id;
     stream << (quint8)identities.size();
     for (const Shared::Identity& identity : identities) {
         stream << identity;
@@ -59,9 +55,7 @@ QDataStream & Shared::ClientInfo::operator >> (QDataStream& stream) const {
 }
 
 QDataStream & Shared::ClientInfo::operator << (QDataStream& stream) {
-    stream >> node;
-    stream >> verification;
-    stream >> hash;
+    stream >> id;
 
     quint8 size;
     stream >> size;
@@ -82,7 +76,7 @@ QDataStream & Shared::ClientInfo::operator << (QDataStream& stream) {
 }
 
 bool Shared::ClientInfo::valid() const {
-    std::map<QString, QCryptographicHash::Algorithm>::const_iterator itr = hashes.find(hash);
+    std::map<QString, QCryptographicHash::Algorithm>::const_iterator itr = hashes.find(id.hash);
     if (itr == hashes.end()) {
         return false;
     }
@@ -98,7 +92,7 @@ bool Shared::ClientInfo::valid() const {
 
     QString result = calc.result().toBase64();
 
-    return result == verification;
+    return result == id.verification;
 }
 
 QDataStream& operator << (QDataStream& stream, const Shared::ClientInfo& info) {
diff --git a/shared/clientinfo.h b/shared/clientinfo.h
index 53c7dd0..8e95180 100644
--- a/shared/clientinfo.h
+++ b/shared/clientinfo.h
@@ -24,11 +24,11 @@
 #include <QCryptographicHash>
 
 #include <shared/identity.h>
+#include <shared/clientid.h>
 
 namespace Shared {
 
-class ClientInfo
-{
+class ClientInfo {
 public:
     ClientInfo();
 
@@ -41,9 +41,7 @@ public:
 public:
     std::set<Identity> identities;
     std::set<QString> extensions;
-    QString node;
-    QString verification;
-    QString hash;
+    ClientId id;
     QString specificPresence;
 
 private:

From 21b40a9ccbf6a89bc41ed143780359103b996a85 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Tue, 14 Mar 2023 22:49:58 +0300
Subject: [PATCH 243/281] Client node now displays in all participants and
 presences, some additional checkups before querying empty clients,
 refactoring

---
 core/account.cpp                  |  10 +-
 core/account.h                    |   1 +
 core/components/clientcache.cpp   |  10 +-
 core/components/clientcache.h     |   3 +-
 core/conference.cpp               |  18 ++-
 core/conference.h                 |   3 +-
 core/handlers/trusthandler.cpp    |   8 +-
 core/squawk.cpp                   | 232 +++++++++++-------------------
 shared/clientid.cpp               | 115 +++++++++++++++
 shared/clientid.h                 |  13 ++
 shared/clientinfo.cpp             |  13 +-
 shared/clientinfo.h               |   2 +
 ui/models/CMakeLists.txt          |  58 ++++----
 ui/models/abstractparticipant.cpp |  83 ++++++-----
 ui/models/abstractparticipant.h   |  12 +-
 ui/models/participant.cpp         | 127 ++++++++--------
 ui/models/presence.cpp            |  10 +-
 ui/models/presence.h              |   2 -
 ui/models/roster.cpp              |   4 +-
 19 files changed, 407 insertions(+), 317 deletions(-)

diff --git a/core/account.cpp b/core/account.cpp
index a0b7311..9740029 100644
--- a/core/account.cpp
+++ b/core/account.cpp
@@ -360,9 +360,13 @@ void Core::Account::onPresenceReceived(const QXmppPresence& p_presence) {
                 {"lastActivity", lastInteraction},
                 {"availability", p_presence.availableStatusType()},           //TODO check and handle invisible
                 {"status", p_presence.statusText()},
-                {"capabilityNode", p_presence.capabilityNode()},
-                {"capabilityVer", p_presence.capabilityVer().toBase64()},
-                {"capabilityHash", p_presence.capabilityHash()}
+                {"client", QVariant::fromValue(
+                    Shared::ClientId(
+                        p_presence.capabilityNode(),
+                        p_presence.capabilityVer().toBase64(),
+                        p_presence.capabilityHash())
+                    )
+                }
             });
         }
         break;
diff --git a/core/account.h b/core/account.h
index 58ff9b5..b257d2b 100644
--- a/core/account.h
+++ b/core/account.h
@@ -51,6 +51,7 @@
 #include <shared/shared.h>
 #include <shared/identity.h>
 #include <shared/info.h>
+#include <shared/clientid.h>
 #include "contact.h"
 #include "conference.h"
 #include <core/components/networkaccess.h>
diff --git a/core/components/clientcache.cpp b/core/components/clientcache.cpp
index dfd9680..fe1fa64 100644
--- a/core/components/clientcache.cpp
+++ b/core/components/clientcache.cpp
@@ -37,14 +37,10 @@ void Core::ClientCache::open() {
 void Core::ClientCache::close() {
     db.close();}
 
-
-bool Core::ClientCache::checkClient(const QString& node, const QString& ver, const QString& hash) {
-    QString id = node + "/" + ver;
+bool Core::ClientCache::checkClient(const Shared::ClientId& p_id) {
+    QString id = p_id.getId();
     if (requested.count(id) == 0 && !cache->checkRecord(id)) {
-        Shared::ClientInfo& info = requested.insert(std::make_pair(id, Shared::ClientInfo())).first->second;
-        info.id.node = node;
-        info.id.verification = ver;
-        info.id.hash = hash;
+        requested.emplace(id, p_id);
         emit requestClientInfo(id);
         return false;
     }
diff --git a/core/components/clientcache.h b/core/components/clientcache.h
index 640def3..6202af5 100644
--- a/core/components/clientcache.h
+++ b/core/components/clientcache.h
@@ -25,6 +25,7 @@
 
 #include <cache.h>
 
+#include <shared/clientid.h>
 #include <shared/clientinfo.h>
 #include <shared/identity.h>
 
@@ -43,7 +44,7 @@ signals:
     void requestClientInfo(const QString& id);
 
 public slots:
-    bool checkClient(const QString& node, const QString& ver, const QString& hash);
+    bool checkClient(const Shared::ClientId& id);
     bool registerClientInfo(const QString& sourceFullJid, const QString& id, const std::set<Shared::Identity>& identities, const std::set<QString>& features);
 
 private:
diff --git a/core/conference.cpp b/core/conference.cpp
index 065490c..2e49625 100644
--- a/core/conference.cpp
+++ b/core/conference.cpp
@@ -157,7 +157,14 @@ void Core::Conference::onRoomParticipantAdded(const QString& p_name)
             {"availability", pres.availableStatusType()},
             {"status", pres.statusText()},
             {"affiliation", mi.affiliation()},
-            {"role", mi.role()}
+            {"role", mi.role()},
+            {"client", QVariant::fromValue(
+                Shared::ClientId(
+                    pres.capabilityNode(),
+                    pres.capabilityVer().toBase64(),
+                    pres.capabilityHash())
+                )
+            }
         };
         
         if (hasAvatar) {
@@ -218,7 +225,14 @@ void Core::Conference::onRoomParticipantChanged(const QString& p_name)
             {"availability", pres.availableStatusType()},
             {"status", pres.statusText()},
             {"affiliation", mi.affiliation()},
-            {"role", mi.role()}
+            {"role", mi.role()},
+            {"client", QVariant::fromValue(
+                Shared::ClientId(
+                    pres.capabilityNode(),
+                    pres.capabilityVer().toBase64(),
+                    pres.capabilityHash())
+                )
+            }
         });
     }
 }
diff --git a/core/conference.h b/core/conference.h
index 41141ad..00ade47 100644
--- a/core/conference.h
+++ b/core/conference.h
@@ -26,7 +26,8 @@
 #include <set>
 
 #include "rosteritem.h"
-#include "shared/global.h"
+#include <shared/global.h>
+#include <shared/clientid.h>
 
 namespace Core
 {
diff --git a/core/handlers/trusthandler.cpp b/core/handlers/trusthandler.cpp
index 647d568..cc97e57 100644
--- a/core/handlers/trusthandler.cpp
+++ b/core/handlers/trusthandler.cpp
@@ -347,8 +347,7 @@ Core::TrustHandler::Keys Core::TrustHandler::getKeys(const QString& protocol, co
     }
 }
 
-Shared::TrustLevel Core::TrustHandler::convert(Core::TrustHandler::TL level)
-{
+Shared::TrustLevel Core::TrustHandler::convert(Core::TrustHandler::TL level) {
     switch (level) {
         case QXmpp::TrustLevel::Undecided: return Shared::TrustLevel::undecided;
         case QXmpp::TrustLevel::AutomaticallyDistrusted: return Shared::TrustLevel::automaticallyDistrusted;
@@ -356,11 +355,11 @@ Shared::TrustLevel Core::TrustHandler::convert(Core::TrustHandler::TL level)
         case QXmpp::TrustLevel::AutomaticallyTrusted: return Shared::TrustLevel::automaticallyTrusted;
         case QXmpp::TrustLevel::ManuallyTrusted: return Shared::TrustLevel::manuallyTrusted;
         case QXmpp::TrustLevel::Authenticated: return Shared::TrustLevel::authenticated;
+        default: throw 2413;                 //never supposed to get here, switch case if complete, this line is just to suppress a warning
     }
 }
 
-Core::TrustHandler::TL Core::TrustHandler::convert(Shared::TrustLevel level)
-{
+Core::TrustHandler::TL Core::TrustHandler::convert(Shared::TrustLevel level) {
     switch (level) {
         case Shared::TrustLevel::undecided: return QXmpp::TrustLevel::Undecided;
         case Shared::TrustLevel::automaticallyDistrusted: return QXmpp::TrustLevel::AutomaticallyDistrusted;
@@ -368,5 +367,6 @@ Core::TrustHandler::TL Core::TrustHandler::convert(Shared::TrustLevel level)
         case Shared::TrustLevel::automaticallyTrusted: return QXmpp::TrustLevel::AutomaticallyTrusted;
         case Shared::TrustLevel::manuallyTrusted: return QXmpp::TrustLevel::ManuallyTrusted;
         case Shared::TrustLevel::authenticated: return QXmpp::TrustLevel::Authenticated;
+        default: throw 2413;                 //never supposed to get here, switch case if complete, this line is just to suppress a warning
     }
 }
diff --git a/core/squawk.cpp b/core/squawk.cpp
index 33f2bf5..b7a0aad 100644
--- a/core/squawk.cpp
+++ b/core/squawk.cpp
@@ -50,8 +50,7 @@ Core::Squawk::Squawk(QObject* parent):
 #endif
 }
 
-Core::Squawk::~Squawk()
-{
+Core::Squawk::~Squawk() {
     Accounts::const_iterator itr = accounts.begin(); 
     Accounts::const_iterator end = accounts.end();
     for (; itr != end; ++itr) {
@@ -59,13 +58,11 @@ Core::Squawk::~Squawk()
     }
 }
 
-void Core::Squawk::onWalletOpened(bool success)
-{
+void Core::Squawk::onWalletOpened(bool success) {
     qDebug() << "KWallet opened: " << success;
 }
 
-void Core::Squawk::stop()
-{
+void Core::Squawk::stop() {
     qDebug("Stopping squawk core..");
     network.stop();
     clientCache.close();
@@ -110,8 +107,7 @@ void Core::Squawk::stop()
     emit quit();
 }
 
-void Core::Squawk::start()
-{
+void Core::Squawk::start() {
     qDebug("Starting squawk core..");
     
     readSettings();
@@ -120,8 +116,7 @@ void Core::Squawk::start()
     clientCache.open();
 }
 
-void Core::Squawk::newAccountRequest(const QMap<QString, QVariant>& map)
-{
+void Core::Squawk::newAccountRequest(const QMap<QString, QVariant>& map) {
     QString name = map.value("name").toString();
     QString login = map.value("login").toString();
     QString server = map.value("server").toString();
@@ -205,31 +200,28 @@ void Core::Squawk::addAccount(
     switch (passwordType) {
         case Shared::AccountPassword::alwaysAsk:
         case Shared::AccountPassword::kwallet:
-            if (password == "") {
+            if (password == "")
                 acc->invalidatePassword();
-                break;
-            }
+
+            break;
         default:
             break;
     }
 
     if (state != Shared::Availability::offline) {
         acc->setAvailability(state);
-        if (acc->getActive()) {
+        if (acc->getActive())
             acc->connect();
-        }
     }
 }
 
-void Core::Squawk::changeState(Shared::Availability p_state)
-{
+void Core::Squawk::changeState(Shared::Availability p_state) {
     if (state != p_state) {
         for (std::deque<Account*>::iterator itr = accounts.begin(), end = accounts.end(); itr != end; ++itr) {
             Account* acc = *itr;
             acc->setAvailability(p_state);
-            if (state == Shared::Availability::offline && acc->getActive()) {
+            if (state == Shared::Availability::offline && acc->getActive())
                 acc->connect();
-            }
         }
         state = p_state;
 
@@ -237,21 +229,18 @@ void Core::Squawk::changeState(Shared::Availability p_state)
     }
 }
 
-void Core::Squawk::connectAccount(const QString& account)
-{
+void Core::Squawk::connectAccount(const QString& account) {
     AccountsMap::const_iterator itr = amap.find(account);
     if (itr == amap.end()) {
         qDebug("An attempt to connect non existing account, skipping");
         return;
     }
     itr->second->setActive(true);
-    if (state != Shared::Availability::offline) {
+    if (state != Shared::Availability::offline)
         itr->second->connect();
-    }
 }
 
-void Core::Squawk::disconnectAccount(const QString& account)
-{
+void Core::Squawk::disconnectAccount(const QString& account) {
     AccountsMap::const_iterator itr = amap.find(account);
     if (itr == amap.end()) {
         qDebug("An attempt to connect non existing account, skipping");
@@ -262,15 +251,14 @@ void Core::Squawk::disconnectAccount(const QString& account)
     itr->second->disconnect();
 }
 
-void Core::Squawk::onAccountConnectionStateChanged(Shared::ConnectionState p_state)
-{
+void Core::Squawk::onAccountConnectionStateChanged(Shared::ConnectionState p_state) {
     Account* acc = static_cast<Account*>(sender());
     QMap<QString, QVariant> changes = {
         {"state", QVariant::fromValue(p_state)}
     };
-    if (acc->getLastError() == Account::Error::none) {
+    if (acc->getLastError() == Account::Error::none)
         changes.insert("error", "");
-    }
+
     emit changeAccount(acc->getName(), changes);
 
 #ifdef WITH_KWALLET
@@ -282,55 +270,48 @@ void Core::Squawk::onAccountConnectionStateChanged(Shared::ConnectionState p_sta
 #endif
 }
 
-void Core::Squawk::onAccountAddContact(const QString& jid, const QString& group, const QMap<QString, QVariant>& data)
-{
+void Core::Squawk::onAccountAddContact(const QString& jid, const QString& group, const QMap<QString, QVariant>& data) {
     Account* acc = static_cast<Account*>(sender());
     emit addContact(acc->getName(), jid, group, data);
 }
 
-void Core::Squawk::onAccountAddGroup(const QString& name)
-{
+void Core::Squawk::onAccountAddGroup(const QString& name) {
     Account* acc = static_cast<Account*>(sender());
     emit addGroup(acc->getName(), name);
 }
 
-void Core::Squawk::onAccountRemoveGroup(const QString& name)
-{
+void Core::Squawk::onAccountRemoveGroup(const QString& name) {
     Account* acc = static_cast<Account*>(sender());
     emit removeGroup(acc->getName(), name);
 }
 
-void Core::Squawk::onAccountChangeContact(const QString& jid, const QMap<QString, QVariant>& data)
-{
+void Core::Squawk::onAccountChangeContact(const QString& jid, const QMap<QString, QVariant>& data) {
     Account* acc = static_cast<Account*>(sender());
     emit changeContact(acc->getName(), jid, data);
 }
 
-void Core::Squawk::onAccountRemoveContact(const QString& jid)
-{
+void Core::Squawk::onAccountRemoveContact(const QString& jid) {
     Account* acc = static_cast<Account*>(sender());
     emit removeContact(acc->getName(), jid);
 }
 
-void Core::Squawk::onAccountRemoveContact(const QString& jid, const QString& group)
-{
+void Core::Squawk::onAccountRemoveContact(const QString& jid, const QString& group) {
     Account* acc = static_cast<Account*>(sender());
     emit removeContact(acc->getName(), jid, group);
 }
 
-void Core::Squawk::onAccountAddPresence(const QString& jid, const QString& name, const QMap<QString, QVariant>& data)
-{
+void Core::Squawk::onAccountAddPresence(const QString& jid, const QString& name, const QMap<QString, QVariant>& data) {
     Account* acc = static_cast<Account*>(sender());
     emit addPresence(acc->getName(), jid, name, data);
 
     //it's equal if a MUC sends its status with presence of the same jid (ex: muc@srv.im/muc@srv.im), it's not a client, so, no need to request
     if (jid != name) {
-        QString node = data["capabilityNode"].toString();
-        QString ver = data["capabilityVer"].toString();
-        QString hash = data["capabilityHash"].toString();
-        if (!clientCache.checkClient(node, ver, hash)) {
-            acc->discoverInfo(jid + "/" + name, node + "/" + ver);
-        }
+        const Shared::ClientId& id = data["client"].value<Shared::ClientId>();
+        if (!id.valid())
+            return;
+
+        if (!clientCache.checkClient(id))
+            acc->discoverInfo(jid + "/" + name, id.getId());
     }
 }
 
@@ -347,32 +328,27 @@ void Core::Squawk::onAccountInfoDiscovered(
     }
 }
 
-void Core::Squawk::onAccountRemovePresence(const QString& jid, const QString& name)
-{
+void Core::Squawk::onAccountRemovePresence(const QString& jid, const QString& name) {
     Account* acc = static_cast<Account*>(sender());
     emit removePresence(acc->getName(), jid, name);
 }
 
-void Core::Squawk::onAccountAvailabilityChanged(Shared::Availability state)
-{
+void Core::Squawk::onAccountAvailabilityChanged(Shared::Availability state) {
     Account* acc = static_cast<Account*>(sender());
     emit changeAccount(acc->getName(), {{"availability", QVariant::fromValue(state)}});
 }
 
-void Core::Squawk::onAccountChanged(const QMap<QString, QVariant>& data)
-{    
+void Core::Squawk::onAccountChanged(const QMap<QString, QVariant>& data) {
     Account* acc = static_cast<Account*>(sender());
     emit changeAccount(acc->getName(), data);
 }
 
-void Core::Squawk::onAccountMessage(const Shared::Message& data)
-{
+void Core::Squawk::onAccountMessage(const Shared::Message& data) {
     Account* acc = static_cast<Account*>(sender());
     emit accountMessage(acc->getName(), data);
 }
 
-void Core::Squawk::sendMessage(const QString& account, const Shared::Message& data)
-{
+void Core::Squawk::sendMessage(const QString& account, const Shared::Message& data) {
     AccountsMap::const_iterator itr = amap.find(account);
     if (itr == amap.end()) {
         qDebug() << "An attempt to send a message with non existing account" << account << ", skipping";
@@ -382,8 +358,7 @@ void Core::Squawk::sendMessage(const QString& account, const Shared::Message& da
     itr->second->sendMessage(data);
 }
 
-void Core::Squawk::replaceMessage(const QString& account, const QString& originalId, const Shared::Message& data)
-{
+void Core::Squawk::replaceMessage(const QString& account, const QString& originalId, const Shared::Message& data) {
     AccountsMap::const_iterator itr = amap.find(account);
     if (itr == amap.end()) {
         qDebug() << "An attempt to replace a message with non existing account" << account << ", skipping";
@@ -393,8 +368,7 @@ void Core::Squawk::replaceMessage(const QString& account, const QString& origina
     itr->second->replaceMessage(originalId, data);
 }
 
-void Core::Squawk::resendMessage(const QString& account, const QString& jid, const QString& id)
-{
+void Core::Squawk::resendMessage(const QString& account, const QString& jid, const QString& id) {
     AccountsMap::const_iterator itr = amap.find(account);
     if (itr == amap.end()) {
         qDebug() << "An attempt to resend a message with non existing account" << account << ", skipping";
@@ -404,8 +378,7 @@ void Core::Squawk::resendMessage(const QString& account, const QString& jid, con
     itr->second->resendMessage(jid, id);
 }
 
-void Core::Squawk::requestArchive(const QString& account, const QString& jid, int count, const QString& before)
-{
+void Core::Squawk::requestArchive(const QString& account, const QString& jid, int count, const QString& before) {
     AccountsMap::const_iterator itr = amap.find(account);
     if (itr == amap.end()) {
         qDebug("An attempt to request an archive of non existing account, skipping");
@@ -414,14 +387,12 @@ void Core::Squawk::requestArchive(const QString& account, const QString& jid, in
     itr->second->requestArchive(jid, count, before);
 }
 
-void Core::Squawk::onAccountResponseArchive(const QString& jid, const std::list<Shared::Message>& list, bool last)
-{
+void Core::Squawk::onAccountResponseArchive(const QString& jid, const std::list<Shared::Message>& list, bool last) {
     Account* acc = static_cast<Account*>(sender());
     emit responseArchive(acc->getName(), jid, list, last);
 }
 
-void Core::Squawk::modifyAccountRequest(const QString& name, const QMap<QString, QVariant>& map)
-{
+void Core::Squawk::modifyAccountRequest(const QString& name, const QMap<QString, QVariant>& map) {
     AccountsMap::const_iterator itr = amap.find(name);
     if (itr == amap.end()) {
         qDebug("An attempt to modify non existing account, skipping");
@@ -471,29 +442,24 @@ void Core::Squawk::modifyAccountRequest(const QString& name, const QMap<QString,
     }
     
     mItr = map.find("login");
-    if (mItr != map.end()) {
+    if (mItr != map.end())
         acc->setLogin(mItr->toString());
-    }
     
     mItr = map.find("password");
-    if (mItr != map.end()) {
+    if (mItr != map.end())
         acc->setPassword(mItr->toString());
-    }
     
     mItr = map.find("resource");
-    if (mItr != map.end()) {
+    if (mItr != map.end())
         acc->setResource(mItr->toString());
-    }
     
     mItr = map.find("server");
-    if (mItr != map.end()) {
+    if (mItr != map.end())
         acc->setServer(mItr->toString());
-    }
     
     mItr = map.find("passwordType");
-    if (mItr != map.end()) {
+    if (mItr != map.end())
         acc->setPasswordType(Shared::Global::fromInt<Shared::AccountPassword>(mItr->toInt()));
-    }
     
 #ifdef WITH_KWALLET
     if (acc->getPasswordType() == Shared::AccountPassword::kwallet 
@@ -505,28 +471,24 @@ void Core::Squawk::modifyAccountRequest(const QString& name, const QMap<QString,
 #endif
     
     if (state != Shared::Availability::offline) {
-        if (activeChanged && acc->getActive()) {
+        if (activeChanged && acc->getActive())
             acc->connect();
-        } else if (!wentReconnecting && acc->getActive() && acc->getLastError() == Account::Error::authentication) {
+        else if (!wentReconnecting && acc->getActive() && acc->getLastError() == Account::Error::authentication)
             acc->connect();
-        }
     }
 
     emit changeAccount(name, map);
 }
 
-void Core::Squawk::onAccountError(const QString& text)
-{
+void Core::Squawk::onAccountError(const QString& text) {
     Account* acc = static_cast<Account*>(sender());
     emit changeAccount(acc->getName(), {{"error", text}});
 
-    if (acc->getLastError() == Account::Error::authentication) {
+    if (acc->getLastError() == Account::Error::authentication)
         emit requestPassword(acc->getName(), true);
-    }
 }
 
-void Core::Squawk::removeAccountRequest(const QString& name)
-{
+void Core::Squawk::removeAccountRequest(const QString& name) {
     AccountsMap::const_iterator itr = amap.find(name);
     if (itr == amap.end()) {
         qDebug() << "An attempt to remove non existing account " << name << " from core, skipping";
@@ -534,9 +496,8 @@ void Core::Squawk::removeAccountRequest(const QString& name)
     }
     
     Account* acc = itr->second;
-    if (acc->getState() != Shared::ConnectionState::disconnected) {
+    if (acc->getState() != Shared::ConnectionState::disconnected)
         acc->disconnect();
-    }
     
     for (Accounts::const_iterator aItr = accounts.begin(); aItr != accounts.end(); ++aItr) {
         if (*aItr == acc) {
@@ -556,8 +517,7 @@ void Core::Squawk::removeAccountRequest(const QString& name)
     acc->deleteLater();
 }
 
-void Core::Squawk::subscribeContact(const QString& account, const QString& jid, const QString& reason)
-{
+void Core::Squawk::subscribeContact(const QString& account, const QString& jid, const QString& reason) {
     AccountsMap::const_iterator itr = amap.find(account);
     if (itr == amap.end()) {
         qDebug("An attempt to subscribe to the contact with non existing account, skipping");
@@ -567,8 +527,7 @@ void Core::Squawk::subscribeContact(const QString& account, const QString& jid,
     itr->second->subscribeToContact(jid, reason);
 }
 
-void Core::Squawk::unsubscribeContact(const QString& account, const QString& jid, const QString& reason)
-{
+void Core::Squawk::unsubscribeContact(const QString& account, const QString& jid, const QString& reason) {
     AccountsMap::const_iterator itr = amap.find(account);
     if (itr == amap.end()) {
         qDebug("An attempt to subscribe to the contact with non existing account, skipping");
@@ -578,8 +537,7 @@ void Core::Squawk::unsubscribeContact(const QString& account, const QString& jid
     itr->second->unsubscribeFromContact(jid, reason);
 }
 
-void Core::Squawk::removeContactRequest(const QString& account, const QString& jid)
-{
+void Core::Squawk::removeContactRequest(const QString& account, const QString& jid) {
     AccountsMap::const_iterator itr = amap.find(account);
     if (itr == amap.end()) {
         qDebug("An attempt to remove contact from non existing account, skipping");
@@ -589,8 +547,7 @@ void Core::Squawk::removeContactRequest(const QString& account, const QString& j
     itr->second->removeContactRequest(jid);
 }
 
-void Core::Squawk::addContactRequest(const QString& account, const QString& jid, const QString& name, const QSet<QString>& groups)
-{
+void Core::Squawk::addContactRequest(const QString& account, const QString& jid, const QString& name, const QSet<QString>& groups) {
     AccountsMap::const_iterator itr = amap.find(account);
     if (itr == amap.end()) {
         qDebug("An attempt to add contact to a non existing account, skipping");
@@ -600,26 +557,22 @@ void Core::Squawk::addContactRequest(const QString& account, const QString& jid,
     itr->second->addContactRequest(jid, name, groups);
 }
 
-void Core::Squawk::onAccountAddRoom(const QString jid, const QMap<QString, QVariant>& data)
-{
+void Core::Squawk::onAccountAddRoom(const QString jid, const QMap<QString, QVariant>& data) {
     Account* acc = static_cast<Account*>(sender());
     emit addRoom(acc->getName(), jid, data);
 }
 
-void Core::Squawk::onAccountChangeRoom(const QString jid, const QMap<QString, QVariant>& data)
-{
+void Core::Squawk::onAccountChangeRoom(const QString jid, const QMap<QString, QVariant>& data) {
     Account* acc = static_cast<Account*>(sender());
     emit changeRoom(acc->getName(), jid, data);
 }
 
-void Core::Squawk::onAccountRemoveRoom(const QString jid)
-{
+void Core::Squawk::onAccountRemoveRoom(const QString jid) {
     Account* acc = static_cast<Account*>(sender());
     emit removeRoom(acc->getName(), jid);
 }
 
-void Core::Squawk::setRoomJoined(const QString& account, const QString& jid, bool joined)
-{
+void Core::Squawk::setRoomJoined(const QString& account, const QString& jid, bool joined) {
     AccountsMap::const_iterator itr = amap.find(account);
     if (itr == amap.end()) {
         qDebug() << "An attempt to set jouned to the room" << jid << "of non existing account" << account << ", skipping";
@@ -628,8 +581,7 @@ void Core::Squawk::setRoomJoined(const QString& account, const QString& jid, boo
     itr->second->setRoomJoined(jid, joined);
 }
 
-void Core::Squawk::setRoomAutoJoin(const QString& account, const QString& jid, bool joined)
-{
+void Core::Squawk::setRoomAutoJoin(const QString& account, const QString& jid, bool joined) {
     AccountsMap::const_iterator itr = amap.find(account);
     if (itr == amap.end()) {
         qDebug() << "An attempt to set autoJoin to the room" << jid << "of non existing account" << account << ", skipping";
@@ -638,32 +590,27 @@ void Core::Squawk::setRoomAutoJoin(const QString& account, const QString& jid, b
     itr->second->setRoomAutoJoin(jid, joined);
 }
 
-void Core::Squawk::onAccountAddRoomPresence(const QString& jid, const QString& nick, const QMap<QString, QVariant>& data)
-{
+void Core::Squawk::onAccountAddRoomPresence(const QString& jid, const QString& nick, const QMap<QString, QVariant>& data) {
     Account* acc = static_cast<Account*>(sender());
     emit addRoomParticipant(acc->getName(), jid, nick, data);
 }
 
-void Core::Squawk::onAccountChangeRoomPresence(const QString& jid, const QString& nick, const QMap<QString, QVariant>& data)
-{
+void Core::Squawk::onAccountChangeRoomPresence(const QString& jid, const QString& nick, const QMap<QString, QVariant>& data) {
     Account* acc = static_cast<Account*>(sender());
     emit changeRoomParticipant(acc->getName(), jid, nick, data);
 }
 
-void Core::Squawk::onAccountRemoveRoomPresence(const QString& jid, const QString& nick)
-{
+void Core::Squawk::onAccountRemoveRoomPresence(const QString& jid, const QString& nick) {
     Account* acc = static_cast<Account*>(sender());
     emit removeRoomParticipant(acc->getName(), jid, nick);
 }
 
-void Core::Squawk::onAccountChangeMessage(const QString& jid, const QString& id, const QMap<QString, QVariant>& data)
-{
+void Core::Squawk::onAccountChangeMessage(const QString& jid, const QString& id, const QMap<QString, QVariant>& data) {
     Account* acc = static_cast<Account*>(sender());
     emit changeMessage(acc->getName(), jid, id, data);
 }
 
-void Core::Squawk::removeRoomRequest(const QString& account, const QString& jid)
-{
+void Core::Squawk::removeRoomRequest(const QString& account, const QString& jid) {
     AccountsMap::const_iterator itr = amap.find(account);
     if (itr == amap.end()) {
         qDebug() << "An attempt to remove the room" << jid << "of non existing account" << account << ", skipping";
@@ -672,8 +619,7 @@ void Core::Squawk::removeRoomRequest(const QString& account, const QString& jid)
     itr->second->removeRoomRequest(jid);
 }
 
-void Core::Squawk::addRoomRequest(const QString& account, const QString& jid, const QString& nick, const QString& password, bool autoJoin)
-{
+void Core::Squawk::addRoomRequest(const QString& account, const QString& jid, const QString& nick, const QString& password, bool autoJoin) {
     AccountsMap::const_iterator itr = amap.find(account);
     if (itr == amap.end()) {
         qDebug() << "An attempt to add the room" << jid << "to non existing account" << account << ", skipping";
@@ -682,13 +628,11 @@ void Core::Squawk::addRoomRequest(const QString& account, const QString& jid, co
     itr->second->addRoomRequest(jid, nick, password, autoJoin);
 }
 
-void Core::Squawk::fileDownloadRequest(const QString& url)
-{
+void Core::Squawk::fileDownloadRequest(const QString& url) {
     network.downladFile(url);
 }
 
-void Core::Squawk::addContactToGroupRequest(const QString& account, const QString& jid, const QString& groupName)
-{
+void Core::Squawk::addContactToGroupRequest(const QString& account, const QString& jid, const QString& groupName) {
     AccountsMap::const_iterator itr = amap.find(account);
     if (itr == amap.end()) {
         qDebug() << "An attempt to add contact" << jid << "of non existing account" << account << "to the group" << groupName << ", skipping";
@@ -697,8 +641,7 @@ void Core::Squawk::addContactToGroupRequest(const QString& account, const QStrin
     itr->second->addContactToGroupRequest(jid, groupName);
 }
 
-void Core::Squawk::removeContactFromGroupRequest(const QString& account, const QString& jid, const QString& groupName)
-{
+void Core::Squawk::removeContactFromGroupRequest(const QString& account, const QString& jid, const QString& groupName) {
     AccountsMap::const_iterator itr = amap.find(account);
     if (itr == amap.end()) {
         qDebug() << "An attempt to add contact" << jid << "of non existing account" << account << "to the group" << groupName << ", skipping";
@@ -707,8 +650,7 @@ void Core::Squawk::removeContactFromGroupRequest(const QString& account, const Q
     itr->second->removeContactFromGroupRequest(jid, groupName);
 }
 
-void Core::Squawk::renameContactRequest(const QString& account, const QString& jid, const QString& newName)
-{
+void Core::Squawk::renameContactRequest(const QString& account, const QString& jid, const QString& newName) {
     AccountsMap::const_iterator itr = amap.find(account);
     if (itr == amap.end()) {
         qDebug() << "An attempt to rename contact" << jid << "of non existing account" << account << ", skipping";
@@ -717,8 +659,7 @@ void Core::Squawk::renameContactRequest(const QString& account, const QString& j
     itr->second->renameContactRequest(jid, newName);
 }
 
-void Core::Squawk::requestInfo(const QString& account, const QString& jid)
-{
+void Core::Squawk::requestInfo(const QString& account, const QString& jid) {
     AccountsMap::const_iterator itr = amap.find(account);
     if (itr == amap.end()) {
         qDebug() << "An attempt to request info about" << jid << "of non existing account" << account << ", skipping";
@@ -727,8 +668,7 @@ void Core::Squawk::requestInfo(const QString& account, const QString& jid)
     itr->second->requestInfo(jid);
 }
 
-void Core::Squawk::updateInfo(const QString& account, const Shared::Info& info)
-{
+void Core::Squawk::updateInfo(const QString& account, const Shared::Info& info) {
     AccountsMap::const_iterator itr = amap.find(account);
     if (itr == amap.end()) {
         qDebug() << "An attempt to update info to non existing account" << account << ", skipping";
@@ -737,8 +677,7 @@ void Core::Squawk::updateInfo(const QString& account, const Shared::Info& info)
     itr->second->updateInfo(info);
 }
 
-void Core::Squawk::readSettings()
-{
+void Core::Squawk::readSettings() {
     QSettings settings;
     settings.beginGroup("core");
     int size = settings.beginReadArray("accounts");
@@ -772,8 +711,7 @@ void Core::Squawk::readSettings()
     emit ready();
 }
 
-void Core::Squawk::onAccountNeedPassword()
-{
+void Core::Squawk::onAccountNeedPassword() {
     Account* acc = static_cast<Account*>(sender());
     switch (acc->getPasswordType()) {
         case Shared::AccountPassword::alwaysAsk:
@@ -796,13 +734,11 @@ void Core::Squawk::onAccountNeedPassword()
     }
 }
 
-void Core::Squawk::onWalletRejectPassword(const QString& login)
-{
+void Core::Squawk::onWalletRejectPassword(const QString& login) {
     emit requestPassword(login, false);
 }
 
-void Core::Squawk::responsePassword(const QString& account, const QString& password)
-{
+void Core::Squawk::responsePassword(const QString& account, const QString& password) {
     AccountsMap::const_iterator itr = amap.find(account);
     if (itr == amap.end()) {
         qDebug() << "An attempt to set password to non existing account" << account << ", skipping";
@@ -811,24 +747,19 @@ void Core::Squawk::responsePassword(const QString& account, const QString& passw
     Account* acc = itr->second;
     acc->setPassword(password);
     emit changeAccount(account, {{"password", password}});
-    if (state != Shared::Availability::offline && acc->getActive()) {
+    if (state != Shared::Availability::offline && acc->getActive())
         acc->connect();
-    }
 }
 
-void Core::Squawk::onAccountUploadFileError(const QString& jid, const QString id, const QString& errorText)
-{
+void Core::Squawk::onAccountUploadFileError(const QString& jid, const QString id, const QString& errorText) {
     Account* acc = static_cast<Account*>(sender());
     emit fileError({{acc->getName(), jid, id}}, errorText, true);
 }
 
-void Core::Squawk::onLocalPathInvalid(const QString& path)
-{
+void Core::Squawk::onLocalPathInvalid(const QString& path) {
     std::list<Shared::MessageInfo> list = network.reportPathInvalid(path);
     
-    QMap<QString, QVariant> data({
-        {"attachPath", ""}
-    });
+    QMap<QString, QVariant> data({{"attachPath", ""}});
     for (const Shared::MessageInfo& info : list) {
         AccountsMap::const_iterator itr = amap.find(info.account);
         if (itr != amap.end()) {
@@ -839,8 +770,7 @@ void Core::Squawk::onLocalPathInvalid(const QString& path)
     }
 }
 
-void Core::Squawk::changeDownloadsPath(const QString& path)
-{
+void Core::Squawk::changeDownloadsPath(const QString& path) {
     network.moveFilesDirectory(path);
 }
 
diff --git a/shared/clientid.cpp b/shared/clientid.cpp
index f16736c..b050df6 100644
--- a/shared/clientid.cpp
+++ b/shared/clientid.cpp
@@ -22,10 +22,125 @@ Shared::ClientId::ClientId():
     hash()
 {}
 
+Shared::ClientId::ClientId(const QString& p_node, const QString& p_ver, const QString& p_hash):
+    node(p_node),
+    verification(p_ver),
+    hash(p_hash)
+{}
+
+
+Shared::ClientId::ClientId(const Shared::ClientId& other):
+    node(other.node),
+    verification(other.verification),
+    hash(other.hash)
+{}
+
+Shared::ClientId & Shared::ClientId::operator=(const Shared::ClientId& other) {
+    node = other.node;
+    verification = other.verification;
+    hash = other.hash;
+
+    return *this;
+}
+
+bool Shared::ClientId::operator==(const Shared::ClientId& other) const {
+    return hash == other.hash && verification == other.verification && node == other.node;
+}
+
+bool Shared::ClientId::operator!=(const Shared::ClientId& other) const {
+    return hash != other.hash && verification != other.verification && node != other.node;
+}
+
+bool Shared::ClientId::operator<(const Shared::ClientId& other) const {
+    if (hash < other.hash)
+        return true;
+
+    if (hash > other.hash)
+        return false;
+
+    if (verification < other.verification)
+        return true;
+
+    if (verification > other.verification)
+        return false;
+
+    if (node < other.node)
+        return true;
+
+    return false;
+}
+
+bool Shared::ClientId::operator>(const Shared::ClientId& other) const {
+    if (hash > other.hash)
+        return true;
+
+    if (hash < other.hash)
+        return false;
+
+    if (verification > other.verification)
+        return true;
+
+    if (verification < other.verification)
+        return false;
+
+    if (node > other.node)
+        return true;
+
+    return false;
+}
+
+bool Shared::ClientId::operator<=(const Shared::ClientId& other) const {
+    if (hash < other.hash)
+        return true;
+
+    if (hash > other.hash)
+        return false;
+
+    if (verification < other.verification)
+        return true;
+
+    if (verification > other.verification)
+        return false;
+
+    if (node < other.node)
+        return true;
+
+    if (node > other.node)
+        return false;
+
+    return true;
+}
+
+bool Shared::ClientId::operator>=(const Shared::ClientId& other) const {
+    if (hash > other.hash)
+        return true;
+
+    if (hash < other.hash)
+        return false;
+
+    if (verification > other.verification)
+        return true;
+
+    if (verification < other.verification)
+        return false;
+
+    if (node > other.node)
+        return true;
+
+    if (node < other.node)
+        return false;
+
+    return true;
+}
+
 QString Shared::ClientId::getId() const {
     return node + "/" + verification;
 }
 
+bool Shared::ClientId::valid() const {
+    return node.size() > 0 && verification.size() > 0 && hash.size() > 0;
+}
+
 QDataStream & Shared::ClientId::operator<<(QDataStream& stream) {
     stream >> node;
     stream >> verification;
diff --git a/shared/clientid.h b/shared/clientid.h
index defe909..5188b1c 100644
--- a/shared/clientid.h
+++ b/shared/clientid.h
@@ -25,7 +25,18 @@ namespace Shared {
 class ClientId {
 public:
     ClientId();
+    ClientId(const QString& node, const QString& verification, const QString& hash);
+    ClientId(const ClientId& other);
+    ClientId& operator = (const ClientId& other);
 
+    bool operator == (const ClientId& other) const;
+    bool operator != (const ClientId& other) const;
+    bool operator < (const ClientId& other) const;
+    bool operator > (const ClientId& other) const;
+    bool operator <= (const ClientId& other) const;
+    bool operator >= (const ClientId& other) const;
+
+    bool valid() const;
     QString getId() const;
 
     QDataStream& operator << (QDataStream& stream);
@@ -39,6 +50,8 @@ public:
 
 }
 
+Q_DECLARE_METATYPE(Shared::ClientId)
+
 QDataStream& operator << (QDataStream& stream, const Shared::ClientId& info);
 QDataStream& operator >> (QDataStream& stream, Shared::ClientId& info);
 
diff --git a/shared/clientinfo.cpp b/shared/clientinfo.cpp
index 9a3fdac..9b84755 100644
--- a/shared/clientinfo.cpp
+++ b/shared/clientinfo.cpp
@@ -35,11 +35,22 @@ Shared::ClientInfo::ClientInfo():
     id(),
     specificPresence() {}
 
+Shared::ClientInfo::ClientInfo(const QString& p_node, const QString& p_ver, const QString& p_hash) :
+    identities(),
+    extensions(),
+    id(p_node, p_ver, p_hash),
+    specificPresence() {}
+
+Shared::ClientInfo::ClientInfo(const Shared::ClientId& p_id) :
+    identities(),
+    extensions(),
+    id(p_id),
+    specificPresence() {}
+
 QString Shared::ClientInfo::getId() const {
     return id.getId();
 }
 
-
 QDataStream & Shared::ClientInfo::operator >> (QDataStream& stream) const {
     stream << id;
     stream << (quint8)identities.size();
diff --git a/shared/clientinfo.h b/shared/clientinfo.h
index 8e95180..288e9fa 100644
--- a/shared/clientinfo.h
+++ b/shared/clientinfo.h
@@ -31,6 +31,8 @@ namespace Shared {
 class ClientInfo {
 public:
     ClientInfo();
+    ClientInfo(const ClientId& id);
+    ClientInfo(const QString& node, const QString& verification, const QString& hash);
 
     QString getId() const;
     bool valid() const;
diff --git a/ui/models/CMakeLists.txt b/ui/models/CMakeLists.txt
index 84aba66..0e6d8ab 100644
--- a/ui/models/CMakeLists.txt
+++ b/ui/models/CMakeLists.txt
@@ -1,28 +1,36 @@
+set(SOURCE_FILES
+    abstractparticipant.cpp
+    account.cpp
+    accounts.cpp
+    contact.cpp
+    element.cpp
+    group.cpp
+    item.cpp
+    participant.cpp
+    presence.cpp
+    reference.cpp
+    room.cpp
+    roster.cpp
+)
+
+set(HEADER_FILES
+    abstractparticipant.h
+    account.h
+    accounts.h
+    contact.h
+    element.h
+    group.h
+    item.h
+    participant.h
+    presence.h
+    reference.h
+    room.h
+    roster.h
+)
+
 target_sources(squawk PRIVATE
-  abstractparticipant.cpp
-  abstractparticipant.h
-  account.cpp
-  account.h
-  accounts.cpp
-  accounts.h
-  contact.cpp
-  contact.h
-  element.cpp
-  element.h
-  group.cpp
-  group.h
-  item.cpp
-  item.h
-  participant.cpp
-  participant.h
-  presence.cpp
-  presence.h
-  reference.cpp
-  reference.h
-  room.cpp
-  room.h
-  roster.cpp
-  roster.h
-  )
+    ${SOURCE_FILES}
+    ${HEADER_FILES}
+)
 
 add_subdirectory(info)
diff --git a/ui/models/abstractparticipant.cpp b/ui/models/abstractparticipant.cpp
index 029527d..240e5ba 100644
--- a/ui/models/abstractparticipant.cpp
+++ b/ui/models/abstractparticipant.cpp
@@ -18,40 +18,38 @@
 
 #include "abstractparticipant.h"
 
-using namespace Models;
-
 Models::AbstractParticipant::AbstractParticipant(Models::Item::Type p_type, const QMap<QString, QVariant>& data, Models::Item* parentItem):
     Item(p_type, data, parentItem),
     availability(Shared::Availability::offline),
     lastActivity(data.value("lastActivity").toDateTime()),
-    status(data.value("status").toString())
+    status(data.value("status").toString()),
+    client()
 {
     QMap<QString, QVariant>::const_iterator itr = data.find("availability");
-    if (itr != data.end()) {
+    if (itr != data.end())
         setAvailability(itr.value().toUInt());
-    }
+
+    itr = data.find("client");
+    if (itr != data.end())
+        setClient(itr.value().value<Shared::ClientId>());
 }
 
 Models::AbstractParticipant::AbstractParticipant(const Models::AbstractParticipant& other):
     Item(other),
     availability(other.availability),
     lastActivity(other.lastActivity),
-    status(other.status)
-{
+    status(other.status),
+    client(other.client)
+{}
+
+
+Models::AbstractParticipant::~AbstractParticipant() {}
+
+int Models::AbstractParticipant::columnCount() const {
+    return 5;
 }
 
-
-Models::AbstractParticipant::~AbstractParticipant()
-{
-}
-
-int Models::AbstractParticipant::columnCount() const
-{
-    return 4;
-}
-
-QVariant Models::AbstractParticipant::data(int column) const
-{
+QVariant Models::AbstractParticipant::data(int column) const {
     switch (column) {
         case 0:
             return Item::data(column);
@@ -61,62 +59,71 @@ QVariant Models::AbstractParticipant::data(int column) const
             return QVariant::fromValue(availability);
         case 3:
             return status;
+        case 4:
+            return QVariant::fromValue(client);
         default:
             return QVariant();
     }
 }
 
-Shared::Availability Models::AbstractParticipant::getAvailability() const
-{
+Shared::Availability Models::AbstractParticipant::getAvailability() const {
     return availability;
 }
 
-QDateTime Models::AbstractParticipant::getLastActivity() const
-{
+QDateTime Models::AbstractParticipant::getLastActivity() const {
     return lastActivity;
 }
 
-QString Models::AbstractParticipant::getStatus() const
-{
+QString Models::AbstractParticipant::getStatus() const {
     return status;
 }
 
-void Models::AbstractParticipant::setAvailability(Shared::Availability p_avail)
-{
+void Models::AbstractParticipant::setAvailability(Shared::Availability p_avail) {
     if (availability != p_avail) {
         availability = p_avail;
         changed(2);
     }
 }
 
-void Models::AbstractParticipant::setAvailability(unsigned int avail)
-{
+void Models::AbstractParticipant::setAvailability(unsigned int avail) {
     setAvailability(Shared::Global::fromInt<Shared::Availability>(avail));
 }
 
-void Models::AbstractParticipant::setLastActivity(const QDateTime& p_time)
-{
+void Models::AbstractParticipant::setLastActivity(const QDateTime& p_time) {
     if (lastActivity != p_time) {
         lastActivity = p_time;
         changed(1);
     }
 }
 
-void Models::AbstractParticipant::setStatus(const QString& p_state)
-{
+void Models::AbstractParticipant::setStatus(const QString& p_state) {
     if (status != p_state) {
         status = p_state;
         changed(3);
     }
 }
 
-QIcon Models::AbstractParticipant::getStatusIcon(bool big) const
-{
+Shared::ClientId Models::AbstractParticipant::getClient() const {
+    return client;
+}
+
+QString Models::AbstractParticipant::getClientNode() const {
+    return client.node;
+}
+
+void Models::AbstractParticipant::setClient(const Shared::ClientId& id) {
+    if (client != id) {
+        client = id;
+        changed(4);
+    }
+}
+
+
+QIcon Models::AbstractParticipant::getStatusIcon(bool big) const {
     return Shared::availabilityIcon(availability, big);
 }
 
-void Models::AbstractParticipant::update(const QString& key, const QVariant& value)
-{
+void Models::AbstractParticipant::update(const QString& key, const QVariant& value) {
     if (key == "name") {
         setName(value.toString());
     } else if (key == "status") {
@@ -125,5 +132,7 @@ void Models::AbstractParticipant::update(const QString& key, const QVariant& val
         setAvailability(value.toUInt());
     } else if (key == "lastActivity") {
         setLastActivity(value.toDateTime());
+    } else if (key == "client") {
+        setClient(value.value<Shared::ClientId>());
     }
 }
diff --git a/ui/models/abstractparticipant.h b/ui/models/abstractparticipant.h
index cb20788..fe2f0b5 100644
--- a/ui/models/abstractparticipant.h
+++ b/ui/models/abstractparticipant.h
@@ -21,9 +21,10 @@
 
 
 #include "item.h"
-#include "shared/enums.h"
-#include "shared/icons.h"
-#include "shared/global.h"
+#include <shared/enums.h>
+#include <shared/icons.h>
+#include <shared/global.h>
+#include <shared/clientid.h>
 
 #include <QIcon>
 #include <QDateTime>
@@ -51,6 +52,10 @@ public:
     QString getStatus() const;
     void setStatus(const QString& p_state);
     virtual QIcon getStatusIcon(bool big = false) const;
+
+    Shared::ClientId getClient() const;
+    void setClient(const Shared::ClientId& id);
+    QString getClientNode() const;
     
     virtual void update(const QString& key, const QVariant& value);
     
@@ -58,6 +63,7 @@ protected:
     Shared::Availability availability;
     QDateTime lastActivity;
     QString status;
+    Shared::ClientId client;
 };
 
 }
diff --git a/ui/models/participant.cpp b/ui/models/participant.cpp
index dc42c07..7201acb 100644
--- a/ui/models/participant.cpp
+++ b/ui/models/participant.cpp
@@ -26,52 +26,46 @@ Models::Participant::Participant(const QMap<QString, QVariant>& data, Models::It
     role(Shared::Role::unspecified)
 {
     QMap<QString, QVariant>::const_iterator itr = data.find("affiliation");
-    if (itr != data.end()) {
+    if (itr != data.end())
         setAffiliation(itr.value().toUInt());
-    }
-    
+
     itr = data.find("role");
-    if (itr != data.end()) {
+    if (itr != data.end())
         setRole(itr.value().toUInt());
-    }
-    
+
     itr = data.find("avatarState");
-    if (itr != data.end()) {
+    if (itr != data.end())
         setAvatarState(itr.value().toUInt());
-    }
+
     itr = data.find("avatarPath");
-    if (itr != data.end()) {
+    if (itr != data.end())
         setAvatarPath(itr.value().toString());
-    }
+
 }
 
 Models::Participant::~Participant()
-{
+{}
+
+int Models::Participant::columnCount() const {
+    return 9;
 }
 
-int Models::Participant::columnCount() const
-{
-    return 8;
-}
-
-QVariant Models::Participant::data(int column) const
-{
+QVariant Models::Participant::data(int column) const {
     switch (column) {
-        case 4:
-            return QVariant::fromValue(affiliation);
         case 5:
-            return QVariant::fromValue(role);
+            return QVariant::fromValue(affiliation);
         case 6:
-            return QVariant::fromValue(getAvatarState());
+            return QVariant::fromValue(role);
         case 7:
+            return QVariant::fromValue(getAvatarState());
+        case 8:
             return getAvatarPath();
         default:
             return AbstractParticipant::data(column);
     }
 }
 
-void Models::Participant::update(const QString& key, const QVariant& value)
-{
+void Models::Participant::update(const QString& key, const QVariant& value) {
     if (key == "affiliation") {
         setAffiliation(value.toUInt());
     } else if (key == "role") {
@@ -85,67 +79,58 @@ void Models::Participant::update(const QString& key, const QVariant& value)
     }
 }
 
-Shared::Affiliation Models::Participant::getAffiliation() const
-{
+Shared::Affiliation Models::Participant::getAffiliation() const {
     return affiliation;
 }
 
-void Models::Participant::setAffiliation(Shared::Affiliation p_aff)
-{
+void Models::Participant::setAffiliation(Shared::Affiliation p_aff) {
     if (p_aff != affiliation) {
         affiliation = p_aff;
-        changed(4);
-    }
-}
-
-void Models::Participant::setAffiliation(unsigned int aff)
-{
-    setAffiliation(Shared::Global::fromInt<Shared::Affiliation>(aff));
-}
-
-Shared::Role Models::Participant::getRole() const
-{
-    return role;
-}
-
-void Models::Participant::setRole(Shared::Role p_role)
-{
-    if (p_role != role) {
-        role = p_role;
         changed(5);
     }
 }
 
-void Models::Participant::setRole(unsigned int p_role)
-{
-    setRole(Shared::Global::fromInt<Shared::Role>(p_role));
+void Models::Participant::setAffiliation(unsigned int aff) {
+    setAffiliation(Shared::Global::fromInt<Shared::Affiliation>(aff));
 }
 
-QString Models::Participant::getAvatarPath() const
-{
-    return avatarPath;
+Shared::Role Models::Participant::getRole() const {
+    return role;
 }
 
-Shared::Avatar Models::Participant::getAvatarState() const
-{
-    return avatarState;
-}
-
-void Models::Participant::setAvatarPath(const QString& path)
-{
-    if (avatarPath != path) {
-        avatarPath = path;
-        changed(7);
-    }
-}
-
-void Models::Participant::setAvatarState(Shared::Avatar p_state)
-{
-    if (avatarState != p_state) {
-        avatarState = p_state;
+void Models::Participant::setRole(Shared::Role p_role) {
+    if (p_role != role) {
+        role = p_role;
         changed(6);
     }
 }
 
-void Models::Participant::setAvatarState(unsigned int p_state)
-{setAvatarState(Shared::Global::fromInt<Shared::Avatar>(p_state));}
+void Models::Participant::setRole(unsigned int p_role) {
+    setRole(Shared::Global::fromInt<Shared::Role>(p_role));
+}
+
+QString Models::Participant::getAvatarPath() const {
+    return avatarPath;
+}
+
+Shared::Avatar Models::Participant::getAvatarState() const {
+    return avatarState;
+}
+
+void Models::Participant::setAvatarPath(const QString& path) {
+    if (avatarPath != path) {
+        avatarPath = path;
+        changed(8);
+    }
+}
+
+void Models::Participant::setAvatarState(Shared::Avatar p_state) {
+    if (avatarState != p_state) {
+        avatarState = p_state;
+        changed(7);
+    }
+}
+
+void Models::Participant::setAvatarState(unsigned int p_state) {
+    setAvatarState(Shared::Global::fromInt<Shared::Avatar>(p_state));
+}
diff --git a/ui/models/presence.cpp b/ui/models/presence.cpp
index 8ba7c47..a867243 100644
--- a/ui/models/presence.cpp
+++ b/ui/models/presence.cpp
@@ -21,14 +21,8 @@
 
 Models::Presence::Presence(const QMap<QString, QVariant>& data, Item* parentItem):
     AbstractParticipant(Item::presence, data, parentItem)
-{
-}
+{}
 
 Models::Presence::~Presence()
-{
-}
+{}
 
-int Models::Presence::columnCount() const
-{
-    return 4;
-}
diff --git a/ui/models/presence.h b/ui/models/presence.h
index fb1a31c..4df662a 100644
--- a/ui/models/presence.h
+++ b/ui/models/presence.h
@@ -34,8 +34,6 @@ class Presence : public Models::AbstractParticipant
 public:
     explicit Presence(const QMap<QString, QVariant> &data, Item *parentItem = 0);
     ~Presence();
-    
-    int columnCount() const override;
 };
 
 }
diff --git a/ui/models/roster.cpp b/ui/models/roster.cpp
index 8ce3464..43717f8 100644
--- a/ui/models/roster.cpp
+++ b/ui/models/roster.cpp
@@ -225,6 +225,7 @@ QVariant Models::Roster::data (const QModelIndex& index, int role) const
                     if (s.size() > 0) {
                         str += "\n" + tr("Status: ") + s;
                     }
+                    str += "\n" + tr("Client: ") + contact->getClientNode();
                     
                     result = str;
                 }
@@ -240,7 +241,8 @@ QVariant Models::Roster::data (const QModelIndex& index, int role) const
                     }
                     
                     str += tr("Affiliation: ") + Shared::Global::getName(p->getAffiliation()) + "\n";
-                    str += tr("Role: ") + Shared::Global::getName(p->getRole());
+                    str += tr("Role: ") + Shared::Global::getName(p->getRole()) + "\n";
+                    str += tr("Client: ") + p->getClientNode();
                     
                     result = str;
                 }

From 283e9ebc4d72e02242f7dc0af46a648771dc63e9 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Wed, 15 Mar 2023 21:17:44 +0300
Subject: [PATCH 244/281] some thoughts on detecting condition for enablining
 or showing the button for encryption in chat window

---
 CHANGELOG.md                          |   5 +-
 core/account.cpp                      |   2 +
 core/handlers/omemohandler.cpp        |   4 +
 core/handlers/omemohandler.h          |   3 +
 shared/CMakeLists.txt                 |   2 +
 shared/trustsummary.cpp               | 140 ++++++++++++++++++++++++++
 shared/trustsummary.h                 |  51 ++++++++++
 ui/widgets/CMakeLists.txt             |  34 +++++--
 ui/widgets/conversation.cpp           |   2 +
 ui/widgets/conversation.ui            |  25 +++++
 ui/widgets/messageline/CMakeLists.txt |  18 +++-
 11 files changed, 269 insertions(+), 17 deletions(-)
 create mode 100644 shared/trustsummary.cpp
 create mode 100644 shared/trustsummary.h

diff --git a/CHANGELOG.md b/CHANGELOG.md
index a36da73..f554982 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,9 +10,12 @@
 - all of the expandable roster items now get saved between launches
 - settings file on the disk is not rewritten every roster element expansion or collapse
 - removed unnecessary own vcard request at sturtup (used to do it to discover my own avatar)
+- vcard window now is Info system and it can display more information
 
 ### New features
-- Now you can enable tray icon from settings!
+- now you can enable tray icon from settings!
+- there is a job queue now, this allowes to spread a bit the spam on the server at connection time
+- squawk now querries clients of it's peers, you can see what programs other people use
 
 ## Squawk 0.2.2 (May 05, 2022)
 ### Bug fixes
diff --git a/core/account.cpp b/core/account.cpp
index 9740029..165dfd8 100644
--- a/core/account.cpp
+++ b/core/account.cpp
@@ -120,6 +120,8 @@ Account::Account(const QString& p_login, const QString& p_server, const QString&
     QObject::connect(delay, &DelayManager::Manager::requestBundles, oh, &OmemoHandler::requestBundles);
     QObject::connect(delay, &DelayManager::Manager::requestOwnBundles, oh, &OmemoHandler::requestOwnBundles);
 
+    QObject::connect(om, &QXmppOmemoManager::deviceAdded, oh, &OmemoHandler::onOmemoDeviceAdded);
+
     client.addExtension(tm);
     client.addExtension(om);
     om->setSecurityPolicy(QXmpp::Toakafa);
diff --git a/core/handlers/omemohandler.cpp b/core/handlers/omemohandler.cpp
index 79eded9..4bccc4b 100644
--- a/core/handlers/omemohandler.cpp
+++ b/core/handlers/omemohandler.cpp
@@ -214,6 +214,10 @@ void Core::OmemoHandler::onOwnBundlesReceived() {
     acc->delay->receivedOwnBundles(keys);
 }
 
+void Core::OmemoHandler::onOmemoDeviceAdded(const QString& jid, uint32_t id) {
+    qDebug() << "OMEMO device added for" << jid;
+}
+
 
 QDataStream & operator >> (QDataStream& in, QXmppOmemoStorage::Device& device) {
     in >> device.label;
diff --git a/core/handlers/omemohandler.h b/core/handlers/omemohandler.h
index 0d1021b..b0db613 100644
--- a/core/handlers/omemohandler.h
+++ b/core/handlers/omemohandler.h
@@ -64,6 +64,9 @@ public:
     void requestOwnBundles();
     void getDevices(const QString& jid, std::list<Shared::KeyInfo>& out) const;
 
+public slots:
+    void onOmemoDeviceAdded(const QString& jid, uint32_t id);
+
 private slots:
     void onBundlesReceived(const QString& jid);
     void onOwnBundlesReceived();
diff --git a/shared/CMakeLists.txt b/shared/CMakeLists.txt
index 5db96f0..4bcc12f 100644
--- a/shared/CMakeLists.txt
+++ b/shared/CMakeLists.txt
@@ -14,6 +14,7 @@ set(SOURCE_FILES
   keyinfo.cpp
   info.cpp
   clientid.cpp
+  trustsummary.cpp
 )
 
 set(HEADER_FILES
@@ -35,6 +36,7 @@ set(HEADER_FILES
   keyinfo.h
   info.h
   clientid.h
+  trustsummary.h
 )
 
 target_sources(squawk PRIVATE
diff --git a/shared/trustsummary.cpp b/shared/trustsummary.cpp
new file mode 100644
index 0000000..658538c
--- /dev/null
+++ b/shared/trustsummary.cpp
@@ -0,0 +1,140 @@
+// Squawk messenger. 
+// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+// 
+// 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 "trustsummary.h"
+
+const std::set<Shared::TrustLevel> Shared::TrustSummary::trustedLevels({
+    Shared::TrustLevel::authenticated,
+    Shared::TrustLevel::automaticallyTrusted,
+    Shared::TrustLevel::manuallyTrusted
+});
+const std::set<Shared::TrustLevel> Shared::TrustSummary::untrustedLevels({
+    Shared::TrustLevel::undecided,
+    Shared::TrustLevel::automaticallyDistrusted,
+    Shared::TrustLevel::manuallyDistrusted
+});
+
+Shared::TrustSummary::TrustSummary():
+    data()
+{}
+
+void Shared::TrustSummary::set(Shared::EncryptionProtocol protocol, Shared::TrustLevel level, uint8_t amount) {
+    Data::iterator itr = data.find(protocol);
+    if (itr == data.end()) {
+        if (amount == 0)
+            return;
+
+        itr = data.insert(std::make_pair(protocol, Amounts())).first;
+    }
+
+    Amounts& am = itr->second;
+    Amounts::iterator aitr = am.find(level);
+    if (aitr == am.end()) {
+        if (amount == 0)
+            return;
+
+        am.emplace(level, amount);
+        return;
+    }
+    if (amount == 0) {
+        if (am.size() == 1)
+            data.erase(itr);
+        else
+            am.erase(aitr);
+
+        return;
+    }
+    aitr->second = amount;
+}
+
+uint8_t Shared::TrustSummary::amount(Shared::EncryptionProtocol protocol, Shared::TrustLevel level) const {
+    Data::const_iterator itr = data.find(protocol);
+    if (itr == data.end())
+        return 0;
+
+    const Amounts& am = itr->second;
+    Amounts::const_iterator aitr = am.find(level);
+    if (aitr == am.end())
+        return 0;
+
+    return aitr->second;
+}
+
+uint8_t Shared::TrustSummary::increment(Shared::EncryptionProtocol protocol, Shared::TrustLevel level) {
+    Data::iterator itr = data.find(protocol);
+    if (itr == data.end())
+        itr = data.insert(std::make_pair(protocol, Amounts())).first;
+
+    Amounts& am = itr->second;
+    Amounts::iterator aitr = am.find(level);
+    if (aitr == am.end()) {
+        am.emplace(level, 1);
+        return 1;
+    }
+    uint8_t& value = aitr->second;
+    return ++value;
+}
+
+uint8_t Shared::TrustSummary::decrement(Shared::EncryptionProtocol protocol, Shared::TrustLevel level) {
+    Data::iterator itr = data.find(protocol);
+    if (itr == data.end())
+        return 0;                   //should never happen, shall I better throw an exception?
+
+    Amounts& am = itr->second;
+    Amounts::iterator aitr = am.find(level);
+    if (aitr == am.end())
+        return 0;                   //should never happen, shall I better throw an exception?
+
+    uint8_t& value = aitr->second;
+    uint8_t result = --value;
+    if (value == 0) {
+        if (am.size() == 1)
+            data.erase(itr);
+        else
+            am.erase(aitr);
+    }
+    return result;
+}
+
+bool Shared::TrustSummary::hasKeys(Shared::EncryptionProtocol protocol) const {
+    return data.count(protocol) > 0;
+}
+
+bool Shared::TrustSummary::hasTrustedKeys(Shared::EncryptionProtocol protocol) const {
+    Data::const_iterator itr = data.find(protocol);
+    if (itr == data.end())
+        return false;
+
+    for (const std::pair<const TrustLevel, uint8_t>& pair : itr->second) {
+        if (trustedLevels.count(pair.first) > 0)
+            return true;
+    }
+
+    return false;
+}
+
+bool Shared::TrustSummary::hasUntrustedKeys(Shared::EncryptionProtocol protocol) const {
+        Data::const_iterator itr = data.find(protocol);
+    if (itr == data.end())
+        return false;
+
+    for (const std::pair<const TrustLevel, uint8_t>& pair : itr->second) {
+        if (untrustedLevels.count(pair.first) > 0)
+            return true;
+    }
+
+    return false;
+}
diff --git a/shared/trustsummary.h b/shared/trustsummary.h
new file mode 100644
index 0000000..3283c10
--- /dev/null
+++ b/shared/trustsummary.h
@@ -0,0 +1,51 @@
+// Squawk messenger. 
+// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+// 
+// 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/>.
+
+#ifndef SHARED_TRUSTSUMMARY_H
+#define SHARED_TRUSTSUMMARY_H
+
+#include <map>
+#include <set>
+
+#include "enums.h"
+
+namespace Shared {
+
+class TrustSummary {
+public:
+    TrustSummary();
+
+    void set(EncryptionProtocol protocol, TrustLevel level, uint8_t amount);
+    uint8_t increment(EncryptionProtocol protocol, TrustLevel level);
+    uint8_t decrement(EncryptionProtocol protocol, TrustLevel level);
+
+    uint8_t amount(EncryptionProtocol protocol, TrustLevel level) const;
+    bool hasKeys(EncryptionProtocol protocol) const;
+    bool hasTrustedKeys(EncryptionProtocol protocol) const;
+    bool hasUntrustedKeys(EncryptionProtocol protocol) const;
+
+private:
+    typedef std::map<TrustLevel, uint8_t> Amounts;
+    typedef std::map<EncryptionProtocol, Amounts> Data;
+
+    Data data;
+    static const std::set<TrustLevel> trustedLevels;
+    static const std::set<TrustLevel> untrustedLevels;
+};
+
+}
+
+#endif // SHARED_TRUSTSUMMARY_H
diff --git a/ui/widgets/CMakeLists.txt b/ui/widgets/CMakeLists.txt
index 2099db1..e8d846b 100644
--- a/ui/widgets/CMakeLists.txt
+++ b/ui/widgets/CMakeLists.txt
@@ -1,21 +1,33 @@
-target_sources(squawk PRIVATE
+set(SOURCE_FILES
   chat.cpp
-  chat.h
   conversation.cpp
-  conversation.h
-  conversation.ui
   joinconference.cpp
-  joinconference.h
-  joinconference.ui
   newcontact.cpp
-  newcontact.h
-  newcontact.ui
   room.cpp
-  room.h
   about.cpp
-  about.h
+)
+
+set(UI_FILES
+  conversation.ui
+  joinconference.ui
+  newcontact.ui
   about.ui
-  )
+)
+
+set(HEADER_FILES
+  chat.h
+  conversation.h
+  joinconference.h
+  newcontact.h
+  room.h
+  about.h
+)
+
+target_sources(squawk PRIVATE
+  ${SOURCE_FILES}
+  ${UI_FILES}
+  ${HEADER_FILES}
+)
 
 add_subdirectory(info)
 add_subdirectory(messageline)
diff --git a/ui/widgets/conversation.cpp b/ui/widgets/conversation.cpp
index 61d3163..c11449f 100644
--- a/ui/widgets/conversation.cpp
+++ b/ui/widgets/conversation.cpp
@@ -100,6 +100,8 @@ Conversation::Conversation(bool muc, Models::Account* acc, Models::Element* el,
     connect(m_ui->currentActionBadge, &Badge::close, this, &Conversation::clear);
     m_ui->currentActionBadge->setVisible(false);
 
+    m_ui->encryptionButton->setVisible(false);
+
     //line->setAutoFillBackground(false);
     //if (testAttribute(Qt::WA_TranslucentBackground)) {
         //m_ui->scrollArea->setAutoFillBackground(false);
diff --git a/ui/widgets/conversation.ui b/ui/widgets/conversation.ui
index 1f8b483..c73de45 100644
--- a/ui/widgets/conversation.ui
+++ b/ui/widgets/conversation.ui
@@ -315,6 +315,31 @@
            </property>
           </spacer>
          </item>
+         <item>
+          <widget class="QPushButton" name="encryptionButton">
+           <property name="enabled">
+            <bool>false</bool>
+           </property>
+           <property name="text">
+            <string/>
+           </property>
+           <property name="icon">
+            <iconset theme="unlock"/>
+           </property>
+           <property name="checkable">
+            <bool>true</bool>
+           </property>
+           <property name="checked">
+            <bool>false</bool>
+           </property>
+           <property name="default">
+            <bool>false</bool>
+           </property>
+           <property name="flat">
+            <bool>true</bool>
+           </property>
+          </widget>
+         </item>
          <item>
           <widget class="QPushButton" name="attachButton">
            <property name="text">
diff --git a/ui/widgets/messageline/CMakeLists.txt b/ui/widgets/messageline/CMakeLists.txt
index 7a850da..7ded76b 100644
--- a/ui/widgets/messageline/CMakeLists.txt
+++ b/ui/widgets/messageline/CMakeLists.txt
@@ -1,10 +1,18 @@
-target_sources(squawk PRIVATE
+set(SOURCE_FILES
   messagedelegate.cpp
-  messagedelegate.h
   preview.cpp
-  preview.h
   messagefeed.cpp
-  messagefeed.h
   feedview.cpp
+)
+
+set(HEADER_FILES
+  messagedelegate.h
+  preview.h
+  messagefeed.h
   feedview.h
-  )
+)
+
+target_sources(squawk PRIVATE
+  ${SOURCE_FILES}
+  ${HEADER_FILES}
+)

From fffef9876a87a949142688b0bfc6f1bee7783dd2 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Thu, 16 Mar 2023 22:38:05 +0300
Subject: [PATCH 245/281] Refactoring, account destruction fix, some thoughts
 about where to store contact settings (omemo enable status for instance)

---
 core/account.cpp                 |  38 +++--
 core/account.h                   |   2 +-
 core/conference.cpp              | 144 +++++++-----------
 core/conference.h                |   3 +-
 core/contact.cpp                 |  49 +++----
 core/contact.h                   |   1 +
 core/handlers/messagehandler.cpp |  16 +-
 core/handlers/rosterhandler.cpp  | 242 +++++++++++--------------------
 core/handlers/rosterhandler.h    |   3 +-
 core/handlers/vcardhandler.cpp   |   2 +-
 core/rosteritem.cpp              | 162 +++++++++++----------
 core/rosteritem.h                |  23 ++-
 core/storage/archive.cpp         |  38 ++++-
 core/storage/archive.h           |   5 +-
 translations/CMakeLists.txt      |   4 +-
 15 files changed, 352 insertions(+), 380 deletions(-)

diff --git a/core/account.cpp b/core/account.cpp
index 165dfd8..3e713c4 100644
--- a/core/account.cpp
+++ b/core/account.cpp
@@ -44,6 +44,7 @@ Account::Account(const QString& p_login, const QString& p_server, const QString&
 #endif
 #if (QXMPP_VERSION) >= QT_VERSION_CHECK(1, 5, 0)
     cm(new QXmppCarbonManagerV2()),
+    psm(new QXmppPubSubManager()),
 #else
     cm(new QXmppCarbonManager()),
 #endif
@@ -55,7 +56,6 @@ Account::Account(const QString& p_login, const QString& p_server, const QString&
     um(new QXmppUploadRequestManager()),
     dm(client.findExtension<QXmppDiscoveryManager>()),
     rcpm(new QXmppMessageReceiptManager()),
-    psm(new QXmppPubSubManager()),
     reconnectScheduled(false),
     reconnectTimer(new QTimer),
     network(p_net),
@@ -167,22 +167,34 @@ Account::~Account() {
     QObject::disconnect(network, &NetworkAccess::downloadFileComplete, mh, &MessageHandler::onDownloadFileComplete);
     QObject::disconnect(network, &NetworkAccess::loadFileError, mh, &MessageHandler::onLoadFileError);
     
-    delete vh;
-    delete mh;
-    delete rh;
-    
+    rh->clear();    //conferenses inside of roster handler hold QXmppMuc objects.
+                    //If we destroy QXmppMucManager, then when we will be destroying RosterHandler
+                    //it will try to destory Core::Conference objects
+                    //and inside of those QXmppMuc objects will already be destroyed.
+                    //So, clear will start the destruction from Core::Conference and this way it's not gonna crash
+
+    delete delay;
     delete reconnectTimer;
-#ifdef WITH_OMEMO
-    delete om;
-#endif
     delete rcpm;
-    delete dm;
     delete um;
     delete bm;
     delete mm;
     delete am;
     delete cm;
-    delete delay;
+#if (QXMPP_VERSION) >= QT_VERSION_CHECK(1, 5, 0)
+    delete psm;
+#endif
+#ifdef WITH_OMEMO
+    delete om;
+    delete tm;
+    delete oh;
+    delete th;
+#endif
+
+    delete dh;
+    delete vh;
+    delete rh;
+    delete mh;
 }
 
 Shared::ConnectionState Core::Account::getState() const {
@@ -345,7 +357,7 @@ void Core::Account::onPresenceReceived(const QXmppPresence& p_presence) {
         vh->handlePresenceOfMyAccountChange(p_presence);
     } else {
         RosterItem* item = rh->getRosterItem(jid);
-        if (item != 0)
+        if (item != nullptr)
             item->handlePresence(p_presence);
     }
     
@@ -417,7 +429,7 @@ void Core::Account::requestArchive(const QString& jid, int count, const QString&
     qDebug() << "An archive request for " << jid << ", before " << before;
     RosterItem* contact = rh->getRosterItem(jid);
     
-    if (contact == 0) {
+    if (contact == nullptr) {
         qDebug() << "An attempt to request archive for" << jid << "in account" << name << ", but the contact with such id wasn't found, skipping";
         emit responseArchive(jid, std::list<Shared::Message>(), true);
         return;
@@ -483,7 +495,7 @@ void Core::Account::onMamResultsReceived(const QString& queryId, const QXmppResu
         
         RosterItem* ri = rh->getRosterItem(jid);
         
-        if (ri != 0) {
+        if (ri != nullptr) {
             qDebug() << "Flushing messages for" << jid << ", complete:" << complete;
             ri->flushMessagesToArchive(complete, resultSetReply.first(), resultSetReply.last());
         }
diff --git a/core/account.h b/core/account.h
index b257d2b..26365c1 100644
--- a/core/account.h
+++ b/core/account.h
@@ -200,6 +200,7 @@ private:
 #endif
 #if (QXMPP_VERSION) >= QT_VERSION_CHECK(1, 5, 0)
     QXmppCarbonManagerV2* cm;
+    QXmppPubSubManager* psm;
 #else
     QXmppCarbonManager* cm;
 #endif
@@ -211,7 +212,6 @@ private:
     QXmppUploadRequestManager* um;
     QXmppDiscoveryManager* dm;
     QXmppMessageReceiptManager* rcpm;
-    QXmppPubSubManager* psm;
     bool reconnectScheduled;
     QTimer* reconnectTimer;
     
diff --git a/core/conference.cpp b/core/conference.cpp
index 2e49625..3904675 100644
--- a/core/conference.cpp
+++ b/core/conference.cpp
@@ -42,57 +42,48 @@ Core::Conference::Conference(const QString& p_jid, const QString& p_account, boo
     connect(room, &QXmppMucRoom::error, this, &Conference::onRoomError);
     
     room->setNickName(nick);
-    if (autoJoin) {
+    if (autoJoin)
         room->join();
-    }
     
     archive->readAllResourcesAvatars(exParticipants);
 }
 
-Core::Conference::~Conference()
-{
-    if (joined) {
+Core::Conference::~Conference(){
+    if (joined)
         room->leave();
-    }
+
     room->deleteLater();
 }
 
-QString Core::Conference::getNick() const
-{
+QString Core::Conference::getNick() const {
     return nick;
 }
 
-bool Core::Conference::getAutoJoin()
-{
+bool Core::Conference::getAutoJoin() const {
     return autoJoin;
 }
 
-bool Core::Conference::getJoined() const
-{
+bool Core::Conference::getJoined() const {
     return joined;
 }
 
-void Core::Conference::setJoined(bool p_joined)
-{
+void Core::Conference::setJoined(bool p_joined) {
     if (joined != p_joined) {
-        if (p_joined) {
+        if (p_joined)
             room->join();
-        } else {
+        else
             room->leave();
-        }
     }
 }
 
-void Core::Conference::setAutoJoin(bool p_autoJoin)
-{
+void Core::Conference::setAutoJoin(bool p_autoJoin) {
     if (autoJoin != p_autoJoin) {
         autoJoin = p_autoJoin;
         emit autoJoinChanged(autoJoin);
     }
 }
 
-void Core::Conference::setNick(const QString& p_nick)
-{
+void Core::Conference::setNick(const QString& p_nick) {
     if (nick != p_nick) {
         if (joined) {
             room->setNickName(p_nick);
@@ -103,45 +94,38 @@ void Core::Conference::setNick(const QString& p_nick)
     }
 }
 
-void Core::Conference::onRoomJoined()
-{
+void Core::Conference::onRoomJoined() {
     joined = true;
     emit joinedChanged(joined);
 }
 
-void Core::Conference::onRoomLeft()
-{
+void Core::Conference::onRoomLeft() {
     joined = false;
     emit joinedChanged(joined);
 }
 
-void Core::Conference::onRoomNameChanged(const QString& p_name)
-{
+void Core::Conference::onRoomNameChanged(const QString& p_name) {
     setName(p_name);
 }
 
-void Core::Conference::onRoomNickNameChanged(const QString& p_nick)
-{
+void Core::Conference::onRoomNickNameChanged(const QString& p_nick) {
     if (p_nick != nick) {
         nick = p_nick;
         emit nickChanged(nick);
     }
 }
 
-void Core::Conference::onRoomError(const QXmppStanza::Error& err)
-{
+void Core::Conference::onRoomError(const QXmppStanza::Error& err) {
     qDebug() << "MUC" << jid << "error:" << err.text();
 }
 
-void Core::Conference::onRoomParticipantAdded(const QString& p_name)
-{
+void Core::Conference::onRoomParticipantAdded(const QString& p_name) {
     QStringList comps = p_name.split("/");
     QString resource = comps.back();
     QXmppPresence pres = room->participantPresence(p_name);
     QXmppMucItem mi = pres.mucItem();
-    if (resource == jid) {
+    if (resource == jid)
         resource = "";
-    } 
     
     std::map<QString, Archive::AvatarInfo>::const_iterator itr = exParticipants.find(resource);
     bool hasAvatar = itr != exParticipants.end();
@@ -166,19 +150,7 @@ void Core::Conference::onRoomParticipantAdded(const QString& p_name)
                 )
             }
         };
-        
-        if (hasAvatar) {
-            if (itr->second.autogenerated) {
-                cData.insert("avatarState", static_cast<uint>(Shared::Avatar::valid));
-            } else {
-                cData.insert("avatarState", static_cast<uint>(Shared::Avatar::autocreated));
-            }
-            cData.insert("avatarPath", avatarPath(resource) + "." + itr->second.type);
-        } else {
-            cData.insert("avatarState", static_cast<uint>(Shared::Avatar::empty));
-            cData.insert("avatarPath", "");
-            emit requestVCard(p_name);
-        }
+        careAboutAvatar(hasAvatar, itr->second, cData, resource, p_name);
         
         emit addParticipant(resource, cData);
     }
@@ -196,9 +168,9 @@ void Core::Conference::onRoomParticipantAdded(const QString& p_name)
         break;
         case QXmppPresence::VCardUpdateValidPhoto:{     //there is a photo, need to load
             if (hasAvatar) {
-                if (itr->second.autogenerated || itr->second.hash != pres.photoHash()) {
+                if (itr->second.autogenerated || itr->second.hash != pres.photoHash())
                     emit requestVCard(p_name);
-                }
+
             } else {
                 emit requestVCard(p_name);
             }
@@ -207,8 +179,7 @@ void Core::Conference::onRoomParticipantAdded(const QString& p_name)
     }
 }
 
-void Core::Conference::onRoomParticipantChanged(const QString& p_name)
-{
+void Core::Conference::onRoomParticipantChanged(const QString& p_name) {
     QStringList comps = p_name.split("/");
     QString resource = comps.back();
     QXmppPresence pres = room->participantPresence(p_name);
@@ -216,9 +187,8 @@ void Core::Conference::onRoomParticipantChanged(const QString& p_name)
     handlePresence(pres);
     if (resource != jid) {
         QDateTime lastInteraction = pres.lastUserInteraction();
-        if (!lastInteraction.isValid()) {
+        if (!lastInteraction.isValid())
             lastInteraction = QDateTime::currentDateTimeUtc();
-        }
         
         emit changeParticipant(resource, {
             {"lastActivity", lastInteraction},
@@ -237,8 +207,7 @@ void Core::Conference::onRoomParticipantChanged(const QString& p_name)
     }
 }
 
-void Core::Conference::onRoomParticipantRemoved(const QString& p_name)
-{
+void Core::Conference::onRoomParticipantRemoved(const QString& p_name) {
     QStringList comps = p_name.split("/");
     QString resource = comps.back();
     if (resource == jid) {
@@ -248,29 +217,24 @@ void Core::Conference::onRoomParticipantRemoved(const QString& p_name)
     }
 }
 
-QString Core::Conference::getSubject() const
-{
-    if (joined) {
+QString Core::Conference::getSubject() const {
+    if (joined)
         return room->subject();
-    } else {
+    else
         return "";
-    }
 }
 
-void Core::Conference::onRoomSubjectChanged(const QString& p_name)
-{
+void Core::Conference::onRoomSubjectChanged(const QString& p_name) {
     emit subjectChanged(p_name);
 }
 
-void Core::Conference::handlePresence(const QXmppPresence& pres)
-{
+void Core::Conference::handlePresence(const QXmppPresence& pres) {
     QString id = pres.from();
     QStringList comps = id.split("/");
     QString jid = comps.front();
     QString resource("");
-    if (comps.size() > 1) {
+    if (comps.size() > 1)
         resource = comps.back();
-    }
     
     switch (pres.vCardUpdateType()) {
         case QXmppPresence::VCardUpdateNone:            //this presence has nothing to do with photo
@@ -284,14 +248,13 @@ void Core::Conference::handlePresence(const QXmppPresence& pres)
                 setAutoGeneratedAvatar(resource);
             }
         }         
-        break;
+            break;
         case QXmppPresence::VCardUpdateValidPhoto:{     //there is a photo, need to load
             Archive::AvatarInfo info;
             bool hasAvatar = readAvatarInfo(info, resource);
             if (hasAvatar) {
-                if (info.autogenerated || info.hash != pres.photoHash()) {
+                if (info.autogenerated || info.hash != pres.photoHash())
                     emit requestVCard(id);
-                }
             } else {
                 emit requestVCard(id);
             }
@@ -300,17 +263,15 @@ void Core::Conference::handlePresence(const QXmppPresence& pres)
     }
 }
 
-bool Core::Conference::setAutoGeneratedAvatar(const QString& resource)
-{
+bool Core::Conference::setAutoGeneratedAvatar(const QString& resource) {
     Archive::AvatarInfo newInfo;
     bool result = RosterItem::setAutoGeneratedAvatar(newInfo, resource);
     if (result && resource.size() != 0) {
         std::map<QString, Archive::AvatarInfo>::iterator itr = exParticipants.find(resource);
-        if (itr == exParticipants.end()) {
+        if (itr == exParticipants.end())
             exParticipants.insert(std::make_pair(resource, newInfo));
-        } else {
+        else
             itr->second = newInfo;
-        }
         emit changeParticipant(resource, {
             {"avatarState", static_cast<uint>(Shared::Avatar::autocreated)},
             {"avatarPath", avatarPath(resource) + "." + newInfo.type}
@@ -320,17 +281,15 @@ bool Core::Conference::setAutoGeneratedAvatar(const QString& resource)
     return result;
 }
 
-bool Core::Conference::setAvatar(const QByteArray& data, Archive::AvatarInfo& info, const QString& resource)
-{
+bool Core::Conference::setAvatar(const QByteArray& data, Archive::AvatarInfo& info, const QString& resource) {
     bool result = RosterItem::setAvatar(data, info, resource);
     if (result && resource.size() != 0) {
         if (data.size() > 0) {
             std::map<QString, Archive::AvatarInfo>::iterator itr = exParticipants.find(resource);
-            if (itr == exParticipants.end()) {
+            if (itr == exParticipants.end())
                 exParticipants.insert(std::make_pair(resource, info));
-            } else {
+            else
                 itr->second = info;
-            }
             
             emit changeParticipant(resource, {
                 {"avatarState", static_cast<uint>(Shared::Avatar::autocreated)},
@@ -338,9 +297,8 @@ bool Core::Conference::setAvatar(const QByteArray& data, Archive::AvatarInfo& in
             });
         } else {
             std::map<QString, Archive::AvatarInfo>::iterator itr = exParticipants.find(resource);
-            if (itr != exParticipants.end()) {
+            if (itr != exParticipants.end())
                 exParticipants.erase(itr);
-            }
             
             emit changeParticipant(resource, {
                 {"avatarState", static_cast<uint>(Shared::Avatar::empty)},
@@ -353,10 +311,8 @@ bool Core::Conference::setAvatar(const QByteArray& data, Archive::AvatarInfo& in
     return result;
 }
 
-void Core::Conference::handleResponseVCard(const QXmppVCardIq& card, const QString &resource, Shared::VCard& out)
-{
+void Core::Conference::handleResponseVCard(const QXmppVCardIq& card, const QString &resource, Shared::VCard& out) {
     RosterItem::handleResponseVCard(card, resource, out);
-    
     if (resource.size() > 0) {
         emit changeParticipant(resource, {
             {"avatarState", static_cast<uint>(out.getAvatarType())},
@@ -365,11 +321,21 @@ void Core::Conference::handleResponseVCard(const QXmppVCardIq& card, const QStri
     }
 }
 
-QMap<QString, QVariant> Core::Conference::getAllAvatars() const
-{
+QMap<QString, QVariant> Core::Conference::getAllAvatars() const {
     QMap<QString, QVariant> result;
-    for (const std::pair<const QString, Archive::AvatarInfo>& pair : exParticipants) {
+    for (const std::pair<const QString, Archive::AvatarInfo>& pair : exParticipants)
         result.insert(pair.first, avatarPath(pair.first) + "." + pair.second.type);
-    }
     return result;
 }
+
+QMap<QString, QVariant> Core::Conference::getInfo() const {
+    QMap<QString, QVariant> data = RosterItem::getInfo();
+
+    data.insert("autoJoin", getAutoJoin());
+    data.insert("joined", getJoined());
+    data.insert("nick", getNick());
+    data.insert("avatars", getAllAvatars());
+
+    return data;
+}
+
diff --git a/core/conference.h b/core/conference.h
index 00ade47..3c077e3 100644
--- a/core/conference.h
+++ b/core/conference.h
@@ -49,12 +49,13 @@ public:
     bool getJoined() const;
     void setJoined(bool p_joined);
     
-    bool getAutoJoin();
+    bool getAutoJoin() const;
     void setAutoJoin(bool p_autoJoin);
     void handlePresence(const QXmppPresence & pres) override;
     bool setAutoGeneratedAvatar(const QString& resource = "") override;
     void handleResponseVCard(const QXmppVCardIq & card, const QString &resource, Shared::VCard& out) override;
     QMap<QString, QVariant> getAllAvatars() const;
+    QMap<QString, QVariant> getInfo() const override;
     
 signals:
     void nickChanged(const QString& nick);
diff --git a/core/contact.cpp b/core/contact.cpp
index 3030f4d..93139ef 100644
--- a/core/contact.cpp
+++ b/core/contact.cpp
@@ -24,54 +24,43 @@ Core::Contact::Contact(const QString& pJid, const QString& account, QObject* par
     groups(),
     subscriptionState(Shared::SubscriptionState::unknown),
     pep(Shared::Support::unknown)
-{
-}
+{}
 
-Core::Contact::~Contact()
-{
-}
+Core::Contact::~Contact() {}
 
-QSet<QString> Core::Contact::getGroups() const
-{
+QSet<QString> Core::Contact::getGroups() const {
     return groups;
 }
 
-unsigned int Core::Contact::groupsCount() const
-{
+unsigned int Core::Contact::groupsCount() const {
     return groups.size();
 }
 
-void Core::Contact::setGroups(const QSet<QString>& set)
-{
+void Core::Contact::setGroups(const QSet<QString>& set) {
     QSet<QString> toRemove = groups - set;
     QSet<QString> toAdd = set - groups;
     
     groups = set;
     
-    for (QSet<QString>::iterator itr = toRemove.begin(), end = toRemove.end(); itr != end; ++itr) {
-        emit groupRemoved(*itr);
-    }
+    for (const QString& group : toRemove)
+        emit groupRemoved(group);
     
-    for (QSet<QString>::iterator itr = toAdd.begin(), end = toAdd.end(); itr != end; ++itr) {
-        emit groupAdded(*itr);
-    }
+    for (const QString& group : toAdd)
+        emit groupAdded(group);
 }
 
-Shared::SubscriptionState Core::Contact::getSubscriptionState() const
-{
+Shared::SubscriptionState Core::Contact::getSubscriptionState() const {
     return subscriptionState;
 }
 
-void Core::Contact::setSubscriptionState(Shared::SubscriptionState state)
-{
+void Core::Contact::setSubscriptionState(Shared::SubscriptionState state) {
     if (subscriptionState != state) {
         subscriptionState = state;
         emit subscriptionStateChanged(subscriptionState);
     }
 }
 
-void Core::Contact::handlePresence(const QXmppPresence& pres)
-{
+void Core::Contact::handlePresence(const QXmppPresence& pres) {
     switch (pres.vCardUpdateType()) {
         case QXmppPresence::VCardUpdateNone:            //this presence has nothing to do with photo
             break;
@@ -101,13 +90,21 @@ void Core::Contact::handlePresence(const QXmppPresence& pres)
 }
 
 void Core::Contact::setPepSupport(Shared::Support support) {
-    if (pep != support) {
+    if (pep != support)
         pep = support;
-    }
 }
 
 Shared::Support Core::Contact::getPepSupport() const {
-    return pep;}
+    return pep;
+}
+
+QMap<QString, QVariant> Core::Contact::getInfo() const {
+    QMap<QString, QVariant> data = RosterItem::getInfo();
+
+    data.insert("state", QVariant::fromValue(subscriptionState));
+
+    return data;
+}
 
 
 
diff --git a/core/contact.h b/core/contact.h
index 01c082f..bde95f2 100644
--- a/core/contact.h
+++ b/core/contact.h
@@ -45,6 +45,7 @@ public:
     Shared::Support getPepSupport() const;
 
     void handlePresence(const QXmppPresence & pres) override;
+    QMap<QString, QVariant> getInfo() const override;
 
 signals:
     void groupAdded(const QString& name);
diff --git a/core/handlers/messagehandler.cpp b/core/handlers/messagehandler.cpp
index 2c5b16d..020ab6f 100644
--- a/core/handlers/messagehandler.cpp
+++ b/core/handlers/messagehandler.cpp
@@ -83,7 +83,7 @@ void Core::MessageHandler::onMessageReceived(const QXmppMessage& msg)
                     {"state", static_cast<uint>(Shared::Message::State::error)},
                     {"errorText", msg.error().text()}
                 };
-                if (cnt != 0) {
+                if (cnt != nullptr) {
                     cnt->changeMessage(id, cData);
                 }
                 emit acc->changeMessage(jid, id, cData);
@@ -291,7 +291,7 @@ void Core::MessageHandler::onReceiptReceived(const QString& jid, const QString&
         QMap<QString, QVariant> cData = {{"state", static_cast<uint>(Shared::Message::State::delivered)}};
         RosterItem* ri = acc->rh->getRosterItem(std::get<2>(ids));
 
-        if (ri != 0) {
+        if (ri != nullptr) {
             ri->changeMessage(std::get<1>(ids), cData);
         }
         emit acc->changeMessage(std::get<2>(ids), std::get<1>(ids), cData);
@@ -346,7 +346,7 @@ void Core::MessageHandler::performSending(Shared::Message data, const QString& o
     } else {
         realId = id;
     }
-    if (ri != 0) {
+    if (ri != nullptr) {
         if (newMessage) {
             ri->appendMessageToArchive(data);
         } else {
@@ -429,7 +429,7 @@ void Core::MessageHandler::prepareUpload(const Shared::Message& data, bool newMe
         QString jid = data.getPenPalJid();
         QString id = data.getId();
         RosterItem* ri = acc->rh->getRosterItem(jid);
-        if (!ri) {
+        if (ri == nullptr) {
             qDebug() << "An attempt to initialize upload in" << acc->name << "for pal" << jid << "but the object for this pal wasn't found, something went terrebly wrong, skipping send";
             return;
         }
@@ -517,7 +517,7 @@ void Core::MessageHandler::onDownloadFileComplete(const std::list<Shared::Messag
     for (const Shared::MessageInfo& info : msgs) {
         if (info.account == acc->getName()) {
             RosterItem* cnt = acc->rh->getRosterItem(info.jid);
-            if (cnt != 0) {
+            if (cnt != nullptr) {
                 if (cnt->changeMessage(info.messageId, cData)) {
                     emit acc->changeMessage(info.jid, info.messageId, cData);
                 }
@@ -553,7 +553,7 @@ void Core::MessageHandler::onUploadFileComplete(const std::list<Shared::MessageI
     for (const Shared::MessageInfo& info : msgs) {
         if (info.account == acc->getName()) {
             RosterItem* ri = acc->rh->getRosterItem(info.jid);
-            if (ri != 0) {
+            if (ri != nullptr) {
                 Shared::Message msg = ri->getMessage(info.messageId);
                 msg.setAttachPath(path);
                 sendMessageWithLocalUploadedFile(msg, url, false);
@@ -584,7 +584,7 @@ static const std::set<QString> allowedToChangeKeys({
 void Core::MessageHandler::requestChangeMessage(const QString& jid, const QString& messageId, const QMap<QString, QVariant>& data)
 {
     RosterItem* cnt = acc->rh->getRosterItem(jid);
-    if (cnt != 0) {
+    if (cnt != nullptr) {
         bool allSupported = true;
         QString unsupportedString;
         for (QMap<QString, QVariant>::const_iterator itr = data.begin(); itr != data.end(); ++itr) {    //I need all this madness
@@ -607,7 +607,7 @@ void Core::MessageHandler::requestChangeMessage(const QString& jid, const QStrin
 void Core::MessageHandler::resendMessage(const QString& jid, const QString& id)
 {
     RosterItem* cnt = acc->rh->getRosterItem(jid);
-    if (cnt != 0) {
+    if (cnt != nullptr) {
         try {
             Shared::Message msg = cnt->getMessage(id);
             if (msg.getState() == Shared::Message::State::error) {
diff --git a/core/handlers/rosterhandler.cpp b/core/handlers/rosterhandler.cpp
index cccd47d..3738d2c 100644
--- a/core/handlers/rosterhandler.cpp
+++ b/core/handlers/rosterhandler.cpp
@@ -41,19 +41,23 @@ void Core::RosterHandler::initialize() {
     connect(acc, &Account::pepSupportChanged, this, &RosterHandler::onPepSupportedChanged);
 }
 
-Core::RosterHandler::~RosterHandler()
-{
-    for (std::map<QString, Contact*>::const_iterator itr = contacts.begin(), end = contacts.end(); itr != end; ++itr) {
-        delete itr->second;
-    }
-    
-    for (std::map<QString, Conference*>::const_iterator itr = conferences.begin(), end = conferences.end(); itr != end; ++itr) {
-        delete itr->second;
-    }
+Core::RosterHandler::~RosterHandler() {
+    clear();
 }
 
-void Core::RosterHandler::onRosterReceived()
-{
+void Core::RosterHandler::clear() {
+    for (const std::pair<const QString, Contact*>& pair : contacts)
+        delete pair.second;
+
+    for (const std::pair<const QString, Conference*>& pair : conferences)
+        delete pair.second;
+
+    contacts.clear();
+    conferences.clear();
+}
+
+
+void Core::RosterHandler::onRosterReceived() {
     QStringList bj = acc->rm->getRosterBareJids();
     for (int i = 0; i < bj.size(); ++i) {
         const QString& jid = bj[i];
@@ -61,8 +65,7 @@ void Core::RosterHandler::onRosterReceived()
     }
 }
 
-void Core::RosterHandler::onRosterItemAdded(const QString& bareJid)
-{
+void Core::RosterHandler::onRosterItemAdded(const QString& bareJid) {
     QString lcJid = bareJid.toLower();
     addedAccount(lcJid);
     std::map<QString, QString>::const_iterator itr = queuedContacts.find(lcJid);
@@ -72,8 +75,7 @@ void Core::RosterHandler::onRosterItemAdded(const QString& bareJid)
     }
 }
 
-void Core::RosterHandler::addedAccount(const QString& jid)
-{
+void Core::RosterHandler::addedAccount(const QString& jid) {
     std::map<QString, Contact*>::const_iterator itr = contacts.find(jid);
     QXmppRosterIq::Item re = acc->rm->getRosterEntry(jid);
     Contact* contact;
@@ -82,7 +84,6 @@ void Core::RosterHandler::addedAccount(const QString& jid)
         newContact = true;
         contact = new Contact(jid, acc->name);
         contacts.insert(std::make_pair(jid, contact));
-        
     } else {
         contact = itr->second;
     }
@@ -94,12 +95,7 @@ void Core::RosterHandler::addedAccount(const QString& jid)
     contact->setName(re.name());
     
     if (newContact) {
-        QMap<QString, QVariant> cData({
-            {"name", re.name()},
-            {"state", QVariant::fromValue(state)}
-        });
-        
-        careAboutAvatar(contact, cData);
+        QMap<QString, QVariant> cData = contact->getInfo();
         int grCount = 0;
         for (QSet<QString>::const_iterator itr = gr.begin(), end = gr.end(); itr != end; ++itr) {
             const QString& groupName = *itr;
@@ -108,9 +104,9 @@ void Core::RosterHandler::addedAccount(const QString& jid)
             grCount++;
         }
         
-        if (grCount == 0) {
+        if (grCount == 0)
             emit acc->addContact(jid, "", cData);
-        }
+
         if (acc->pepSupport == Shared::Support::supported) {
             acc->dm->requestInfo(jid);
             //acc->dm->requestItems(jid);
@@ -119,49 +115,22 @@ void Core::RosterHandler::addedAccount(const QString& jid)
     }
 }
 
-void Core::RosterHandler::addNewRoom(const QString& jid, const QString& nick, const QString& roomName, bool autoJoin)
-{
+void Core::RosterHandler::addNewRoom(const QString& jid, const QString& nick, const QString& roomName, bool autoJoin) {
     QXmppMucRoom* room = acc->mm->addRoom(jid);
     QString lNick = nick;
-    if (lNick.size() == 0) {
+    if (lNick.size() == 0)
         lNick = acc->getName();
-    }
+
     Conference* conf = new Conference(jid, acc->getName(), autoJoin, roomName, lNick, room);
     conferences.insert(std::make_pair(jid, conf));
     
     handleNewConference(conf);
     
-    QMap<QString, QVariant> cData = {
-        {"autoJoin", conf->getAutoJoin()},
-        {"joined", conf->getJoined()},
-        {"nick", conf->getNick()},
-        {"name", conf->getName()},
-        {"avatars", conf->getAllAvatars()}
-    };
-    careAboutAvatar(conf, cData);
+    QMap<QString, QVariant> cData = conf->getInfo();
     emit acc->addRoom(jid, cData);
 }
 
-void Core::RosterHandler::careAboutAvatar(Core::RosterItem* item, QMap<QString, QVariant>& data)
-{
-    Archive::AvatarInfo info;
-    bool hasAvatar = item->readAvatarInfo(info);
-    if (hasAvatar) {
-        if (info.autogenerated) {
-            data.insert("avatarState", QVariant::fromValue(Shared::Avatar::autocreated));
-        } else {
-            data.insert("avatarState", QVariant::fromValue(Shared::Avatar::valid));
-        }
-        data.insert("avatarPath", item->avatarPath() + "." + info.type);
-    } else {
-        data.insert("avatarState", QVariant::fromValue(Shared::Avatar::empty));
-        data.insert("avatarPath", "");
-        acc->delay->requestVCard(item->jid);
-    }
-}
-
-void Core::RosterHandler::addContactRequest(const QString& jid, const QString& name, const QSet<QString>& groups)
-{
+void Core::RosterHandler::addContactRequest(const QString& jid, const QString& name, const QSet<QString>& groups) {
     if (acc->state == Shared::ConnectionState::connected) {
         std::map<QString, QString>::const_iterator itr = queuedContacts.find(jid);
         if (itr != queuedContacts.end()) {
@@ -175,8 +144,7 @@ void Core::RosterHandler::addContactRequest(const QString& jid, const QString& n
     }
 }
 
-void Core::RosterHandler::removeContactRequest(const QString& jid)
-{
+void Core::RosterHandler::removeContactRequest(const QString& jid) {
     QString lcJid = jid.toLower();
     if (acc->state == Shared::ConnectionState::connected) {
         std::set<QString>::const_iterator itr = outOfRosterContacts.find(lcJid);
@@ -191,25 +159,23 @@ void Core::RosterHandler::removeContactRequest(const QString& jid)
     }
 }
 
-void Core::RosterHandler::handleNewRosterItem(Core::RosterItem* contact)
-{
+void Core::RosterHandler::handleNewRosterItem(Core::RosterItem* contact) {
     connect(contact, &RosterItem::needHistory, this->acc, &Account::onContactNeedHistory);
     connect(contact, &RosterItem::historyResponse, this->acc, &Account::onContactHistoryResponse);
     connect(contact, &RosterItem::nameChanged, this, &RosterHandler::onContactNameChanged);
     connect(contact, &RosterItem::avatarChanged, this, &RosterHandler::onContactAvatarChanged);
+    connect(contact, &RosterItem::encryptionChanged, this, &RosterHandler::onContactEncryptionChanged);
     connect(contact, &RosterItem::requestVCard, acc->delay, &DelayManager::Manager::getVCard);
 }
 
-void Core::RosterHandler::handleNewContact(Core::Contact* contact)
-{
+void Core::RosterHandler::handleNewContact(Core::Contact* contact) {
     handleNewRosterItem(contact);
     connect(contact, &Contact::groupAdded, this, &RosterHandler::onContactGroupAdded);
     connect(contact, &Contact::groupRemoved, this, &RosterHandler::onContactGroupRemoved);
     connect(contact, &Contact::subscriptionStateChanged, this, &RosterHandler::onContactSubscriptionStateChanged);
 }
 
-void Core::RosterHandler::handleNewConference(Core::Conference* contact)
-{
+void Core::RosterHandler::handleNewConference(Core::Conference* contact) {
     handleNewRosterItem(contact);
     connect(contact, &Conference::nickChanged, this, &RosterHandler::onMucNickNameChanged);
     connect(contact, &Conference::subjectChanged, this, &RosterHandler::onMucSubjectChanged);
@@ -220,34 +186,27 @@ void Core::RosterHandler::handleNewConference(Core::Conference* contact)
     connect(contact, &Conference::removeParticipant, this, &RosterHandler::onMucRemoveParticipant);
 }
 
-void Core::RosterHandler::onMucAddParticipant(const QString& nickName, const QMap<QString, QVariant>& data)
-{
+void Core::RosterHandler::onMucAddParticipant(const QString& nickName, const QMap<QString, QVariant>& data) {
     Conference* room = static_cast<Conference*>(sender());
     emit acc->addRoomParticipant(room->jid, nickName, data);
 }
 
-void Core::RosterHandler::onMucChangeParticipant(const QString& nickName, const QMap<QString, QVariant>& data)
-{
+void Core::RosterHandler::onMucChangeParticipant(const QString& nickName, const QMap<QString, QVariant>& data) {
     Conference* room = static_cast<Conference*>(sender());
     emit acc->changeRoomParticipant(room->jid, nickName, data);
 }
 
-void Core::RosterHandler::onMucRemoveParticipant(const QString& nickName)
-{
+void Core::RosterHandler::onMucRemoveParticipant(const QString& nickName) {
     Conference* room = static_cast<Conference*>(sender());
     emit acc->removeRoomParticipant(room->jid, nickName);
 }
 
-void Core::RosterHandler::onMucSubjectChanged(const QString& subject)
-{
+void Core::RosterHandler::onMucSubjectChanged(const QString& subject) {
     Conference* room = static_cast<Conference*>(sender());
-    emit acc->changeRoom(room->jid, {
-        {"subject", subject}
-    });
+    emit acc->changeRoom(room->jid, {{"subject", subject}});
 }
 
-void Core::RosterHandler::onContactGroupAdded(const QString& group)
-{
+void Core::RosterHandler::onContactGroupAdded(const QString& group) {
     Contact* contact = static_cast<Contact*>(sender());
     if (contact->groupsCount() == 1) {
         // not sure i need to handle it here, the situation with grouped and ungrouped contacts handled on the client anyway
@@ -255,14 +214,14 @@ void Core::RosterHandler::onContactGroupAdded(const QString& group)
     
     QMap<QString, QVariant> cData({
         {"name", contact->getName()},
-        {"state", QVariant::fromValue(contact->getSubscriptionState())}
+        {"state", QVariant::fromValue(contact->getSubscriptionState())},
+        {"encryption", contact->isEncryptionEnabled()}
     });
     addToGroup(contact->jid, group);
     emit acc->addContact(contact->jid, group, cData);
 }
 
-void Core::RosterHandler::onContactGroupRemoved(const QString& group)
-{
+void Core::RosterHandler::onContactGroupRemoved(const QString& group) {
     Contact* contact = static_cast<Contact*>(sender());
     if (contact->groupsCount() == 0) {
         // not sure i need to handle it here, the situation with grouped and ungrouped contacts handled on the client anyway
@@ -272,17 +231,17 @@ void Core::RosterHandler::onContactGroupRemoved(const QString& group)
     removeFromGroup(contact->jid, group);
 }
 
-void Core::RosterHandler::onContactNameChanged(const QString& cname)
-{
-    Contact* contact = static_cast<Contact*>(sender());
-    QMap<QString, QVariant> cData({
-        {"name", cname},
-    });
-    emit acc->changeContact(contact->jid, cData);
+void Core::RosterHandler::onContactNameChanged(const QString& cname) {
+    RosterItem* contact = static_cast<RosterItem*>(sender());
+    emit acc->changeContact(contact->jid, {{"name", cname}});
 }
 
-void Core::RosterHandler::onContactSubscriptionStateChanged(Shared::SubscriptionState cstate)
-{
+void Core::RosterHandler::onContactEncryptionChanged(bool value) {
+    RosterItem* contact = static_cast<RosterItem*>(sender());
+    emit acc->changeContact(contact->jid, {{"encryption", value}});
+}
+
+void Core::RosterHandler::onContactSubscriptionStateChanged(Shared::SubscriptionState cstate) {
     Contact* contact = static_cast<Contact*>(sender());
     QMap<QString, QVariant> cData({
         {"state", QVariant::fromValue(cstate)},
@@ -290,8 +249,7 @@ void Core::RosterHandler::onContactSubscriptionStateChanged(Shared::Subscription
     emit acc->changeContact(contact->jid, cData);
 }
 
-void Core::RosterHandler::addToGroup(const QString& jid, const QString& group)
-{
+void Core::RosterHandler::addToGroup(const QString& jid, const QString& group) {
     std::map<QString, std::set<QString>>::iterator gItr = groups.find(group);
     if (gItr == groups.end()) {
         gItr = groups.insert(std::make_pair(group, std::set<QString>())).first;
@@ -300,8 +258,7 @@ void Core::RosterHandler::addToGroup(const QString& jid, const QString& group)
     gItr->second.insert(jid.toLower());
 }
 
-void Core::RosterHandler::removeFromGroup(const QString& jid, const QString& group)
-{
+void Core::RosterHandler::removeFromGroup(const QString& jid, const QString& group) {
     QSet<QString> toRemove;
     std::map<QString, std::set<QString>>::iterator itr = groups.find(group);
     if (itr == groups.end()) {
@@ -319,24 +276,21 @@ void Core::RosterHandler::removeFromGroup(const QString& jid, const QString& gro
     }
 }
 
-Core::RosterItem * Core::RosterHandler::getRosterItem(const QString& jid)
-{
-    RosterItem* item = 0;
+Core::RosterItem * Core::RosterHandler::getRosterItem(const QString& jid) {
+    RosterItem* item = nullptr;
     QString lcJid = jid.toLower();
     std::map<QString, Contact*>::const_iterator citr = contacts.find(lcJid);
     if (citr != contacts.end()) {
         item = citr->second;
     } else {
         std::map<QString, Conference*>::const_iterator coitr = conferences.find(lcJid);
-        if (coitr != conferences.end()) {
+        if (coitr != conferences.end())
             item = coitr->second;
-        }
     }
     return item;
 }
 
-Core::Conference * Core::RosterHandler::getConference(const QString& jid)
-{
+Core::Conference * Core::RosterHandler::getConference(const QString& jid) {
     Conference* item = 0;
     std::map<QString, Conference*>::const_iterator coitr = conferences.find(jid.toLower());
     if (coitr != conferences.end()) {
@@ -345,8 +299,7 @@ Core::Conference * Core::RosterHandler::getConference(const QString& jid)
     return item;
 }
 
-Core::Contact * Core::RosterHandler::getContact(const QString& jid)
-{
+Core::Contact * Core::RosterHandler::getContact(const QString& jid) {
     Contact* item = 0;
     std::map<QString, Contact*>::const_iterator citr = contacts.find(jid.toLower());
     if (citr != contacts.end()) {
@@ -355,22 +308,21 @@ Core::Contact * Core::RosterHandler::getContact(const QString& jid)
     return item;
 }
 
-Core::Contact * Core::RosterHandler::addOutOfRosterContact(const QString& jid)
-{
+Core::Contact * Core::RosterHandler::addOutOfRosterContact(const QString& jid) {
     QString lcJid = jid.toLower();
     Contact* cnt = new Contact(lcJid, acc->name);
     contacts.insert(std::make_pair(lcJid, cnt));
     outOfRosterContacts.insert(lcJid);
     cnt->setSubscriptionState(Shared::SubscriptionState::unknown);
     emit acc->addContact(lcJid, "", QMap<QString, QVariant>({
-        {"state", QVariant::fromValue(Shared::SubscriptionState::unknown)}
+        {"state", QVariant::fromValue(Shared::SubscriptionState::unknown)},
+        {"encryption", false}
     }));
     handleNewContact(cnt);
     return cnt;
 }
 
-void Core::RosterHandler::onRosterItemChanged(const QString& bareJid)
-{
+void Core::RosterHandler::onRosterItemChanged(const QString& bareJid) {
     QString lcJid = bareJid.toLower();
     std::map<QString, Contact*>::const_iterator itr = contacts.find(lcJid);
     if (itr == contacts.end()) {
@@ -387,8 +339,7 @@ void Core::RosterHandler::onRosterItemChanged(const QString& bareJid)
     contact->setName(re.name());
 }
 
-void Core::RosterHandler::onRosterItemRemoved(const QString& bareJid)
-{
+void Core::RosterHandler::onRosterItemRemoved(const QString& bareJid) {
     QString lcJid = bareJid.toLower();
     std::map<QString, Contact*>::const_iterator itr = contacts.find(lcJid);
     if (itr == contacts.end()) {
@@ -406,14 +357,12 @@ void Core::RosterHandler::onRosterItemRemoved(const QString& bareJid)
     contact->deleteLater();
 }
 
-void Core::RosterHandler::onMucRoomAdded(QXmppMucRoom* room)
-{
+void Core::RosterHandler::onMucRoomAdded(QXmppMucRoom* room) {
     qDebug()    << "room" << room->jid() << "added with name" << room->name() 
                 << ", account" << acc->getName() << "joined:" << room->isJoined(); 
 }
 
-void Core::RosterHandler::bookmarksReceived(const QXmppBookmarkSet& bookmarks)
-{
+void Core::RosterHandler::bookmarksReceived(const QXmppBookmarkSet& bookmarks) {
     QList<QXmppBookmarkConference> confs = bookmarks.conferences();
     for (QList<QXmppBookmarkConference>::const_iterator itr = confs.begin(), end = confs.end(); itr != end; ++itr) {
         const QXmppBookmarkConference& c = *itr;
@@ -423,54 +372,42 @@ void Core::RosterHandler::bookmarksReceived(const QXmppBookmarkSet& bookmarks)
         if (cItr == conferences.end()) {
             addNewRoom(jid, c.nickName(), c.name(), c.autoJoin());
         } else {
-            if (c.autoJoin()) {
+            if (c.autoJoin())
                 cItr->second->setJoined(true);
-            } else {
+            else
                 cItr->second->setAutoJoin(false);
-            }
         }
     }
 }
 
-void Core::RosterHandler::onMucJoinedChanged(bool joined)
-{
+void Core::RosterHandler::onMucJoinedChanged(bool joined){
     Conference* room = static_cast<Conference*>(sender());
-    emit acc->changeRoom(room->jid, {
-        {"joined", joined}
-    });
+    emit acc->changeRoom(room->jid, {{"joined", joined}});
 }
 
-void Core::RosterHandler::onMucAutoJoinChanged(bool autoJoin)
-{
+void Core::RosterHandler::onMucAutoJoinChanged(bool autoJoin) {
     storeConferences();
     Conference* room = static_cast<Conference*>(sender());
-    emit acc->changeRoom(room->jid, {
-        {"autoJoin", autoJoin}
-    });
+    emit acc->changeRoom(room->jid, {{"autoJoin", autoJoin}});
 }
 
-void Core::RosterHandler::onMucNickNameChanged(const QString& nickName)
-{
+void Core::RosterHandler::onMucNickNameChanged(const QString& nickName){
     storeConferences();
     Conference* room = static_cast<Conference*>(sender());
-    emit acc->changeRoom(room->jid, {
-        {"nick", nickName}
-    });
+    emit acc->changeRoom(room->jid, {{"nick", nickName}});
 }
 
-Shared::SubscriptionState Core::RosterHandler::castSubscriptionState(QXmppRosterIq::Item::SubscriptionType qs)
-{
+Shared::SubscriptionState Core::RosterHandler::castSubscriptionState(QXmppRosterIq::Item::SubscriptionType qs){
     Shared::SubscriptionState state;
-    if (qs == QXmppRosterIq::Item::NotSet) {
+    if (qs == QXmppRosterIq::Item::NotSet)
         state = Shared::SubscriptionState::unknown;
-    } else {
+    else
         state = static_cast<Shared::SubscriptionState>(qs);
-    }
+
     return state;
 }
 
-void Core::RosterHandler::storeConferences()
-{
+void Core::RosterHandler::storeConferences() {
     QXmppBookmarkSet bms = acc->bm->bookmarks();
     QList<QXmppBookmarkConference> confs;
     for (std::map<QString, Conference*>::const_iterator itr = conferences.begin(), end = conferences.end(); itr != end; ++itr) {
@@ -486,8 +423,7 @@ void Core::RosterHandler::storeConferences()
     acc->bm->setBookmarks(bms);
 }
 
-void Core::RosterHandler::clearConferences()
-{
+void Core::RosterHandler::clearConferences() {
     for (std::map<QString, Conference*>::const_iterator itr = conferences.begin(), end = conferences.end(); itr != end; itr++) {
         itr->second->deleteLater();
         emit acc->removeRoom(itr->first);
@@ -495,8 +431,7 @@ void Core::RosterHandler::clearConferences()
     conferences.clear();
 }
 
-void Core::RosterHandler::removeRoomRequest(const QString& jid)
-{
+void Core::RosterHandler::removeRoomRequest(const QString& jid) {
     QString lcJid = jid.toLower();
     std::map<QString, Conference*>::const_iterator itr = conferences.find(lcJid);
     if (itr == conferences.end()) {
@@ -508,8 +443,7 @@ void Core::RosterHandler::removeRoomRequest(const QString& jid)
     storeConferences();
 }
 
-void Core::RosterHandler::addRoomRequest(const QString& jid, const QString& nick, const QString& password, bool autoJoin)
-{
+void Core::RosterHandler::addRoomRequest(const QString& jid, const QString& nick, const QString& password, bool autoJoin) {
     QString lcJid = jid.toLower();
     std::map<QString, Conference*>::const_iterator cItr = conferences.find(lcJid);
     if (cItr == conferences.end()) {
@@ -520,8 +454,7 @@ void Core::RosterHandler::addRoomRequest(const QString& jid, const QString& nick
     }
 }
 
-void Core::RosterHandler::addContactToGroupRequest(const QString& jid, const QString& groupName)
-{
+void Core::RosterHandler::addContactToGroupRequest(const QString& jid, const QString& groupName) {
     QString lcJid = jid.toLower();
     std::map<QString, Contact*>::const_iterator itr = contacts.find(lcJid);
     if (itr == contacts.end()) {
@@ -545,8 +478,7 @@ void Core::RosterHandler::addContactToGroupRequest(const QString& jid, const QSt
     }
 }
 
-void Core::RosterHandler::removeContactFromGroupRequest(const QString& jid, const QString& groupName)
-{
+void Core::RosterHandler::removeContactFromGroupRequest(const QString& jid, const QString& groupName) {
     QString lcJid = jid.toLower();
     std::map<QString, Contact*>::const_iterator itr = contacts.find(lcJid);
     if (itr == contacts.end()) {
@@ -571,19 +503,17 @@ void Core::RosterHandler::removeContactFromGroupRequest(const QString& jid, cons
     }
 }
 
-void Core::RosterHandler::onContactAvatarChanged(Shared::Avatar type, const QString& path)
-{
+void Core::RosterHandler::onContactAvatarChanged(Shared::Avatar type, const QString& path) {
     RosterItem* item = static_cast<RosterItem*>(sender());
     QMap<QString, QVariant> cData({
-        {"avatarState", static_cast<uint>(type)},
+        {"avatarState", QVariant::fromValue(type)},
         {"avatarPath", path}
     });
     
     emit acc->changeContact(item->jid, cData);
 }
 
-void Core::RosterHandler::handleOffline()
-{
+void Core::RosterHandler::handleOffline() {
     for (const std::pair<const QString, Conference*>& pair : conferences) {
         pair.second->clearArchiveRequests();
         pair.second->downgradeDatabaseState();
@@ -595,13 +525,11 @@ void Core::RosterHandler::handleOffline()
 }
 
 
-void Core::RosterHandler::onPepSupportedChanged(Shared::Support support)
-{
+void Core::RosterHandler::onPepSupportedChanged(Shared::Support support) {
     if (support == Shared::Support::supported) {
         for (const std::pair<const QString, Contact*>& pair : contacts) {
-            if (pair.second->getPepSupport() == Shared::Support::unknown) {
+            if (pair.second->getPepSupport() == Shared::Support::unknown)
                 acc->dm->requestInfo(pair.first);
-            }
         }
     }
 }
diff --git a/core/handlers/rosterhandler.h b/core/handlers/rosterhandler.h
index 62a7b8b..63f291c 100644
--- a/core/handlers/rosterhandler.h
+++ b/core/handlers/rosterhandler.h
@@ -68,6 +68,7 @@ public:
     void clearConferences();
 
     void initialize();
+    void clear();
     
 private slots:
     void onRosterReceived();
@@ -91,6 +92,7 @@ private slots:
     void onContactNameChanged(const QString& name);
     void onContactSubscriptionStateChanged(Shared::SubscriptionState state);
     void onContactAvatarChanged(Shared::Avatar, const QString& path);
+    void onContactEncryptionChanged(bool value);
     void onPepSupportedChanged(Shared::Support support);
     
 private:
@@ -101,7 +103,6 @@ private:
     void handleNewRosterItem(Core::RosterItem* contact);
     void handleNewContact(Core::Contact* contact);
     void handleNewConference(Core::Conference* contact);
-    void careAboutAvatar(Core::RosterItem* item, QMap<QString, QVariant>& data);
     
     static Shared::SubscriptionState castSubscriptionState(QXmppRosterIq::Item::SubscriptionType qs);
     
diff --git a/core/handlers/vcardhandler.cpp b/core/handlers/vcardhandler.cpp
index d4125e8..33b9c31 100644
--- a/core/handlers/vcardhandler.cpp
+++ b/core/handlers/vcardhandler.cpp
@@ -88,7 +88,7 @@ void Core::VCardHandler::onVCardReceived(const QXmppVCardIq& card) {
     }
     RosterItem* item = acc->rh->getRosterItem(jid);
 
-    if (item == 0) {
+    if (item == nullptr) {
         if (jid == acc->getBareJid())
             onOwnVCardReceived(card);
         else
diff --git a/core/rosteritem.cpp b/core/rosteritem.cpp
index 0bac4a4..545e47f 100644
--- a/core/rosteritem.cpp
+++ b/core/rosteritem.cpp
@@ -41,39 +41,33 @@ Core::RosterItem::RosterItem(const QString& pJid, const QString& pAccount, QObje
     archive->open(account);
     
     if (archive->size() != 0) {
-        if (archive->isFromTheBeginning()) {
+        if (archive->isFromTheBeginning())
             archiveState = beginning;
-        } else {
+        else
             archiveState = chunk;
-        }
     }
 }
 
-Core::RosterItem::~RosterItem()
-{
+Core::RosterItem::~RosterItem() {
     delete archive;
 }
 
-Core::RosterItem::ArchiveState Core::RosterItem::getArchiveState() const
-{
+Core::RosterItem::ArchiveState Core::RosterItem::getArchiveState() const {
     return archiveState;
 }
 
-QString Core::RosterItem::getName() const
-{
+QString Core::RosterItem::getName() const {
     return name;
 }
 
-void Core::RosterItem::setName(const QString& n)
-{
+void Core::RosterItem::setName(const QString& n) {
     if (name != n) {
         name = n;
         emit nameChanged(name);
     }
 }
 
-void Core::RosterItem::addMessageToArchive(const Shared::Message& msg)
-{
+void Core::RosterItem::addMessageToArchive(const Shared::Message& msg) {
     if (msg.storable()) {
         hisoryCache.push_back(msg);
         std::map<QString, Shared::Message>::iterator itr = toCorrect.find(msg.getId());
@@ -87,8 +81,7 @@ void Core::RosterItem::addMessageToArchive(const Shared::Message& msg)
     }
 }
 
-void Core::RosterItem::correctMessageInArchive(const QString& originalId, const Shared::Message& msg)
-{
+void Core::RosterItem::correctMessageInArchive(const QString& originalId, const Shared::Message& msg) {
     if (msg.storable()) {
         QDateTime thisTime = msg.getTime();
         std::map<QString, Shared::Message>::iterator itr = toCorrect.find(originalId);
@@ -109,8 +102,7 @@ void Core::RosterItem::correctMessageInArchive(const QString& originalId, const
     }
 }
 
-void Core::RosterItem::requestHistory(int count, const QString& before)
-{
+void Core::RosterItem::requestHistory(int count, const QString& before) {
     if (syncronizing) {
         requestCache.emplace_back(count, before);
     } else {
@@ -118,8 +110,7 @@ void Core::RosterItem::requestHistory(int count, const QString& before)
     }
 }
 
-void Core::RosterItem::nextRequest()
-{
+void Core::RosterItem::nextRequest() {
     if (syncronizing) {
         if (requestedCount != -1) {
             bool last = false;
@@ -157,8 +148,7 @@ void Core::RosterItem::nextRequest()
     }
 }
 
-void Core::RosterItem::performRequest(int count, const QString& before)
-{
+void Core::RosterItem::performRequest(int count, const QString& before) {
     syncronizing = true;
     requestedCount = count;
     requestedBefore = before;
@@ -246,8 +236,7 @@ void Core::RosterItem::performRequest(int count, const QString& before)
     }
 }
 
-QString Core::RosterItem::getId(const Shared::Message& msg)
-{
+QString Core::RosterItem::getId(const Shared::Message& msg) {
     QString id;
     if (muc) {
         id = msg.getStanzaId();
@@ -257,8 +246,7 @@ QString Core::RosterItem::getId(const Shared::Message& msg)
     return id;
 }
 
-void Core::RosterItem::appendMessageToArchive(const Shared::Message& msg)
-{
+void Core::RosterItem::appendMessageToArchive(const Shared::Message& msg) {
     if (msg.getId().size() > 0) {
         if (msg.storable()) {
             switch (archiveState) {
@@ -299,8 +287,7 @@ void Core::RosterItem::appendMessageToArchive(const Shared::Message& msg)
     }
 }
 
-bool Core::RosterItem::changeMessage(const QString& id, const QMap<QString, QVariant>& data)
-{
+bool Core::RosterItem::changeMessage(const QString& id, const QMap<QString, QVariant>& data) {
     bool found = false;
     for (Shared::Message& msg : appendCache) {
         if (msg.getId() == id) {
@@ -341,8 +328,7 @@ bool Core::RosterItem::changeMessage(const QString& id, const QMap<QString, QVar
     return found;
 }
 
-void Core::RosterItem::flushMessagesToArchive(bool finished, const QString& firstId, const QString& lastId)
-{
+void Core::RosterItem::flushMessagesToArchive(bool finished, const QString& firstId, const QString& lastId) {
     unsigned int added(0);
     if (hisoryCache.size() > 0) {
         added = archive->addElements(hisoryCache);
@@ -429,51 +415,43 @@ void Core::RosterItem::flushMessagesToArchive(bool finished, const QString& firs
     }
 }
 
-QString Core::RosterItem::getServer() const
-{
+QString Core::RosterItem::getServer() const {
     QStringList lst = jid.split("@");
     return lst.back();
 }
 
-bool Core::RosterItem::isMuc() const
-{
+bool Core::RosterItem::isMuc() const {
     return muc;
 }
 
-QString Core::RosterItem::avatarPath(const QString& resource) const
-{
+QString Core::RosterItem::avatarPath(const QString& resource) const {
     QString path = folderPath() + "/" + (resource.size() == 0 ? jid : resource);
     return path;
 }
 
-QString Core::RosterItem::folderPath() const
-{
+QString Core::RosterItem::folderPath() const {
     QString path(QStandardPaths::writableLocation(QStandardPaths::CacheLocation));
     path += "/" + account + "/" + jid;
     return path;
 }
 
-bool Core::RosterItem::setAvatar(const QByteArray& data, Archive::AvatarInfo& info, const QString& resource)
-{
+bool Core::RosterItem::setAvatar(const QByteArray& data, Archive::AvatarInfo& info, const QString& resource) {
     bool result = archive->setAvatar(data, info, false, resource);
     if (resource.size() == 0 && result) {
-        if (data.size() == 0) {
+        if (data.size() == 0)
             emit avatarChanged(Shared::Avatar::empty, "");
-        } else {
+        else
             emit avatarChanged(Shared::Avatar::valid, avatarPath(resource) + "." + info.type);
-        }
     }
     return result;
 }
 
-bool Core::RosterItem::setAutoGeneratedAvatar(const QString& resource)
-{
+bool Core::RosterItem::setAutoGeneratedAvatar(const QString& resource) {
     Archive::AvatarInfo info;
     return setAutoGeneratedAvatar(info, resource);
 }
 
-bool Core::RosterItem::setAutoGeneratedAvatar(Archive::AvatarInfo& info, const QString& resource)
-{
+bool Core::RosterItem::setAutoGeneratedAvatar(Archive::AvatarInfo& info, const QString& resource) {
     QImage image(96, 96, QImage::Format_ARGB32_Premultiplied);
     QPainter painter(&image);
     quint8 colorIndex = rand() % Shared::colorPalette.size();
@@ -483,11 +461,11 @@ bool Core::RosterItem::setAutoGeneratedAvatar(Archive::AvatarInfo& info, const Q
     f.setBold(true);
     f.setPixelSize(72);
     painter.setFont(f);
-    if (bg.lightnessF() > 0.5) {
+    if (bg.lightnessF() > 0.5)
         painter.setPen(Qt::black);
-    } else {
+    else
         painter.setPen(Qt::white);
-    }
+
     painter.drawText(image.rect(), Qt::AlignCenter | Qt::AlignVCenter, resource.size() == 0 ? jid.at(0).toUpper() : resource.at(0).toUpper());
     QByteArray arr;
     QBuffer stream(&arr);
@@ -495,19 +473,17 @@ bool Core::RosterItem::setAutoGeneratedAvatar(Archive::AvatarInfo& info, const Q
     image.save(&stream, "PNG");
     stream.close();
     bool result = archive->setAvatar(arr, info, true, resource);
-    if (resource.size() == 0 && result) {
+    if (resource.size() == 0 && result)
         emit avatarChanged(Shared::Avatar::autocreated, avatarPath(resource) + ".png");
-    }
+
     return result;
 }
 
-bool Core::RosterItem::readAvatarInfo(Archive::AvatarInfo& target, const QString& resource) const
-{
+bool Core::RosterItem::readAvatarInfo(Archive::AvatarInfo& target, const QString& resource) const {
     return archive->readAvatarInfo(target, resource);
 }
 
-void Core::RosterItem::handleResponseVCard(const QXmppVCardIq& card, const QString& resource, Shared::VCard& vCard)
-{
+void Core::RosterItem::handleResponseVCard(const QXmppVCardIq& card, const QString& resource, Shared::VCard& vCard) {
     Archive::AvatarInfo info;
     Archive::AvatarInfo newInfo;
     bool hasAvatar = readAvatarInfo(info, resource);
@@ -532,9 +508,9 @@ void Core::RosterItem::handleResponseVCard(const QXmppVCardIq& card, const QStri
             }
         }
     } else {
-        if (!hasAvatar || !info.autogenerated) {
+        if (!hasAvatar || !info.autogenerated)
             setAutoGeneratedAvatar(resource);
-        }
+
         type = Shared::Avatar::autocreated;
         path = avatarPath(resource) + ".png";
     }
@@ -542,13 +518,11 @@ void Core::RosterItem::handleResponseVCard(const QXmppVCardIq& card, const QStri
     vCard.setAvatarType(type);
     vCard.setAvatarPath(path);
     
-    if (resource.size() == 0) {
+    if (resource.size() == 0)
         emit avatarChanged(vCard.getAvatarType(), vCard.getAvatarPath());
-    }
 }
 
-void Core::RosterItem::clearArchiveRequests()
-{
+void Core::RosterItem::clearArchiveRequests() {
     syncronizing = false;
     requestedCount = 0;
     requestedBefore = "";
@@ -564,30 +538,72 @@ void Core::RosterItem::clearArchiveRequests()
     requestCache.clear();
 }
 
-void Core::RosterItem::downgradeDatabaseState()
-{
-    if (archiveState == ArchiveState::complete) {
+void Core::RosterItem::downgradeDatabaseState() {
+    if (archiveState == ArchiveState::complete)
         archiveState = ArchiveState::beginning;
-    }
+
     
-    if (archiveState == ArchiveState::end) {
+    if (archiveState == ArchiveState::end)
         archiveState = ArchiveState::chunk;
-    }
 }
 
-Shared::Message Core::RosterItem::getMessage(const QString& id)
-{
+Shared::Message Core::RosterItem::getMessage(const QString& id) {
     for (const Shared::Message& msg : appendCache) {
-        if (msg.getId() == id) {
+        if (msg.getId() == id)
             return msg;
-        }
     }
     
     for (Shared::Message& msg : hisoryCache) {
-        if (msg.getId() == id) {
+        if (msg.getId() == id)
             return msg;
-        }
     }
     
     return archive->getElement(id);
 }
+
+bool Core::RosterItem::isEncryptionEnabled() const {
+    return archive->isEncryptionEnabled();
+}
+
+void Core::RosterItem::enableEncryption(bool value) {
+    bool changed = archive->setEncryptionEnabled(value);
+    if (changed)
+        emit encryptionChanged(value);
+}
+
+QMap<QString, QVariant> Core::RosterItem::getInfo() const {
+    QMap<QString, QVariant> result({
+        {"name", name},
+        {"encryption", isEncryptionEnabled()},
+    });
+    Archive::AvatarInfo info;
+    bool hasAvatar = readAvatarInfo(info);
+    careAboutAvatar(hasAvatar, info, result);
+
+    return result;
+}
+
+
+void Core::RosterItem::careAboutAvatar (
+    bool hasAvatar,
+    const Archive::AvatarInfo& info,
+    QMap<QString, QVariant>& output,
+    const QString& resource,
+    const QString& subject
+) const {
+    if (hasAvatar) {
+        if (info.autogenerated)
+            output.insert("avatarState", QVariant::fromValue(Shared::Avatar::autocreated));
+        else
+            output.insert("avatarState", QVariant::fromValue(Shared::Avatar::valid));
+
+        output.insert("avatarPath", avatarPath(resource) + "." + info.type);
+    } else {
+        output.insert("avatarState", QVariant::fromValue(Shared::Avatar::empty));
+        output.insert("avatarPath", "");
+        if (subject.size() == 0)
+            emit requestVCard(jid);
+        else
+            emit requestVCard(subject);
+    }
+}
diff --git a/core/rosteritem.h b/core/rosteritem.h
index 7c82945..fa154c0 100644
--- a/core/rosteritem.h
+++ b/core/rosteritem.h
@@ -62,6 +62,8 @@ public:
     void setName(const QString& n);
     QString getServer() const;
     bool isMuc() const;
+    bool isEncryptionEnabled() const;
+    void enableEncryption(bool value = true);
     
     void addMessageToArchive(const Shared::Message& msg);
     void correctMessageInArchive(const QString& originalId, const Shared::Message& msg);
@@ -78,16 +80,18 @@ public:
     bool changeMessage(const QString& id, const QMap<QString, QVariant>& data);
     void clearArchiveRequests();
     void downgradeDatabaseState();
+    virtual QMap<QString, QVariant> getInfo() const;
     
     Shared::Message getMessage(const QString& id);
     
 signals:
-    void nameChanged(const QString& name);
-    void subscriptionStateChanged(Shared::SubscriptionState state);
-    void historyResponse(const std::list<Shared::Message>& messages, bool last);
-    void needHistory(const QString& before, const QString& after, const QDateTime& afterTime = QDateTime());
-    void avatarChanged(Shared::Avatar, const QString& path);
-    void requestVCard(const QString& jid);
+    void nameChanged(const QString& name) const;
+    void subscriptionStateChanged(Shared::SubscriptionState state) const;
+    void historyResponse(const std::list<Shared::Message>& messages, bool last) const;
+    void needHistory(const QString& before, const QString& after, const QDateTime& afterTime = QDateTime()) const;
+    void avatarChanged(Shared::Avatar, const QString& path) const;
+    void requestVCard(const QString& jid) const;
+    void encryptionChanged(bool value) const;
     
 public:
     const QString jid;
@@ -96,6 +100,13 @@ public:
 protected:
     virtual bool setAvatar(const QByteArray& data, Archive::AvatarInfo& info, const QString& resource = "");
     virtual bool setAutoGeneratedAvatar(Archive::AvatarInfo& info, const QString& resource = "");
+    void careAboutAvatar(
+        bool hasAvatar,
+        const Archive::AvatarInfo& info,
+        QMap<QString, QVariant>& output,
+        const QString& resource = "",
+        const QString& subject = ""
+    ) const;
     
 protected:
     QString name;
diff --git a/core/storage/archive.cpp b/core/storage/archive.cpp
index cb65a53..8330cff 100644
--- a/core/storage/archive.cpp
+++ b/core/storage/archive.cpp
@@ -29,6 +29,7 @@ Core::Archive::Archive(const QString& p_jid, QObject* parent):
     jid(p_jid),
     opened(false),
     fromTheBeginning(false),
+    encryptionEnabled(false),
     environment(),
     main(),
     order(),
@@ -84,6 +85,12 @@ void Core::Archive::open(const QString& account)
         } catch (const NotFound& e) {
             fromTheBeginning = false;
         }
+
+        try {
+            encryptionEnabled = getStatBoolValue("encryptionEnabled", txn);
+        } catch (const NotFound& e) {
+            encryptionEnabled = false;
+        }
         
         std::string sJid = jid.toStdString();
         AvatarInfo info;
@@ -603,7 +610,7 @@ std::list<Shared::Message> Core::Archive::getBefore(int count, const QString& id
     return res;
 }
 
-bool Core::Archive::isFromTheBeginning()
+bool Core::Archive::isFromTheBeginning() const
 {
     if (!opened) {
         throw Closed("isFromTheBeginning", jid.toStdString());
@@ -630,6 +637,35 @@ void Core::Archive::setFromTheBeginning(bool is)
     }
 }
 
+bool Core::Archive::isEncryptionEnabled() const
+{
+    if (!opened) {
+        throw Closed("isEncryptionEnabled", jid.toStdString());
+    }
+    return encryptionEnabled;
+}
+
+bool Core::Archive::setEncryptionEnabled(bool is)
+{
+    if (!opened) {
+        throw Closed("setEncryptionEnabled", jid.toStdString());
+    }
+    if (encryptionEnabled != is) {
+        encryptionEnabled = is;
+
+        MDB_txn *txn;
+        mdb_txn_begin(environment, NULL, 0, &txn);
+        bool success = setStatValue("encryptionEnabled", is, txn);
+        if (success) {
+            mdb_txn_commit(txn);
+            return true;
+        } else {
+            mdb_txn_abort(txn);
+        }
+    }
+    return false;
+}
+
 QString Core::Archive::idByStanzaId(const QString& stanzaId) const
 {
     if (!opened) {
diff --git a/core/storage/archive.h b/core/storage/archive.h
index 47c62dc..ef10555 100644
--- a/core/storage/archive.h
+++ b/core/storage/archive.h
@@ -55,8 +55,10 @@ public:
     void clear();
     long unsigned int size() const;
     std::list<Shared::Message> getBefore(int count, const QString& id);
-    bool isFromTheBeginning();
+    bool isFromTheBeginning() const;
     void setFromTheBeginning(bool is);
+    bool isEncryptionEnabled() const;
+    bool setEncryptionEnabled(bool is);     //returns true if changed, false otherwise
     bool setAvatar(const QByteArray& data, AvatarInfo& info, bool generated = false, const QString& resource = "");
     AvatarInfo getAvatarInfo(const QString& resource = "") const;
     bool readAvatarInfo(AvatarInfo& target, const QString& resource = "") const;
@@ -171,6 +173,7 @@ public:
 private:
     bool opened;
     bool fromTheBeginning;
+    bool encryptionEnabled;
     MDB_env* environment;
     MDB_dbi main;           //id to message
     MDB_dbi order;          //time to id
diff --git a/translations/CMakeLists.txt b/translations/CMakeLists.txt
index f70fe2b..6bdb6c9 100644
--- a/translations/CMakeLists.txt
+++ b/translations/CMakeLists.txt
@@ -1,11 +1,11 @@
-find_package(Qt5LinguistTools)
+find_package(Qt${QT_VERSION_MAJOR}LinguistTools)
 
 set(TS_FILES
     squawk.en.ts
     squawk.ru.ts
     squawk.pt_BR.ts
 )
-qt5_add_translation(QM_FILES ${TS_FILES})
+qt_add_translation(QM_FILES ${TS_FILES})
 add_custom_target(translations ALL DEPENDS ${QM_FILES})
 install(FILES ${QM_FILES} DESTINATION ${CMAKE_INSTALL_DATADIR}/macaw.me/squawk/l10n)
 

From 4f295fee3ca6695178b8f8a1095a48c2bbfaae16 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Fri, 17 Mar 2023 23:59:51 +0300
Subject: [PATCH 246/281] trust summary gui delivery

---
 core/account.cpp                |  32 ++++++----
 core/account.h                  |   6 +-
 core/handlers/omemohandler.cpp  |   6 +-
 core/handlers/omemohandler.h    |   3 +-
 core/handlers/rosterhandler.cpp |   3 +
 core/handlers/trusthandler.cpp  |  50 ++++++++++-----
 core/handlers/trusthandler.h    |   9 ++-
 shared/global.cpp               |  50 +++++----------
 shared/global.h                 |   4 +-
 shared/trustsummary.cpp         |  11 ++++
 shared/trustsummary.h           |  10 +++
 ui/models/contact.cpp           | 109 +++++++++++++++-----------------
 ui/models/contact.h             |  19 ++++--
 13 files changed, 169 insertions(+), 143 deletions(-)

diff --git a/core/account.cpp b/core/account.cpp
index 3e713c4..736d5ae 100644
--- a/core/account.cpp
+++ b/core/account.cpp
@@ -21,9 +21,15 @@
 
 #include <QDateTime>
 
-using namespace Core;
-
-Account::Account(const QString& p_login, const QString& p_server, const QString& p_password, const QString& p_name, bool p_active, NetworkAccess* p_net, QObject* parent):
+Core::Account::Account(
+    const QString& p_login,
+    const QString& p_server,
+    const QString& p_password,
+    const QString& p_name,
+    bool p_active,
+    NetworkAccess* p_net,
+    QObject* parent
+):
     QObject(parent),
     name(p_name),
     archiveQueries(),
@@ -36,13 +42,15 @@ Account::Account(const QString& p_login, const QString& p_server, const QString&
     rh(new RosterHandler(this)),
     vh(new VCardHandler(this)),
     dh(new DiscoveryHandler(this)),
-#ifdef WITH_OMEMO
+#if (QXMPP_VERSION) >= QT_VERSION_CHECK(1, 5, 0)
     th(new TrustHandler(this)),
+#endif
+#ifdef WITH_OMEMO
     oh(new OmemoHandler(this)),
-    tm(new QXmppTrustManager(th)),
     om(new QXmppOmemoManager(oh)),
 #endif
 #if (QXMPP_VERSION) >= QT_VERSION_CHECK(1, 5, 0)
+    tm(new QXmppTrustManager(th)),
     cm(new QXmppCarbonManagerV2()),
     psm(new QXmppPubSubManager()),
 #else
@@ -157,7 +165,7 @@ Account::Account(const QString& p_login, const QString& p_server, const QString&
     }
 }
 
-Account::~Account() {
+Core::Account::~Account() {
     if (reconnectScheduled) {
         reconnectScheduled = false;
         reconnectTimer->stop();
@@ -701,9 +709,7 @@ void Core::Account::setActive(bool p_active) {
     if (active != p_active) {
         active = p_active;
 
-        emit changed({
-            {"active", active}
-        });
+        emit changed({{"active", active}});
     }
 }
 
@@ -795,14 +801,12 @@ void Core::Account::addContactToGroupRequest(const QString& jid, const QString&
 void Core::Account::removeContactFromGroupRequest(const QString& jid, const QString& groupName) {
     rh->removeContactFromGroupRequest(jid, groupName);}
 
-void Core::Account::renameContactRequest(const QString& jid, const QString& newName)
-{
+void Core::Account::renameContactRequest(const QString& jid, const QString& newName) {
     Contact* cnt = rh->getContact(jid);
-    if (cnt == 0) {
+    if (cnt == 0)
         qDebug() << "An attempt to rename non existing contact" << jid << "of account" << name << ", skipping";
-    } else {
+    else
         rm->renameItem(jid, newName);
-    }
 }
 
 void Core::Account::invalidatePassword() {
diff --git a/core/account.h b/core/account.h
index 26365c1..fe3988c 100644
--- a/core/account.h
+++ b/core/account.h
@@ -191,14 +191,16 @@ private:
     RosterHandler* rh;
     VCardHandler* vh;
     DiscoveryHandler* dh;
-#ifdef WITH_OMEMO
+#if (QXMPP_VERSION) >= QT_VERSION_CHECK(1, 5, 0)
     TrustHandler* th;
+#endif
+#ifdef WITH_OMEMO
     OmemoHandler* oh;
 
-    QXmppTrustManager* tm;
     QXmppOmemoManager* om;
 #endif
 #if (QXMPP_VERSION) >= QT_VERSION_CHECK(1, 5, 0)
+    QXmppTrustManager* tm;
     QXmppCarbonManagerV2* cm;
     QXmppPubSubManager* psm;
 #else
diff --git a/core/handlers/omemohandler.cpp b/core/handlers/omemohandler.cpp
index 4bccc4b..c8bcb17 100644
--- a/core/handlers/omemohandler.cpp
+++ b/core/handlers/omemohandler.cpp
@@ -19,8 +19,6 @@
 #include "core/account.h"
 #include "core/adapterfunctions.h"
 
-constexpr const char* ns_omemo_2 = "urn:xmpp:omemo:2";
-
 Core::OmemoHandler::OmemoHandler(Account* account) :
     QObject(),
     QXmppOmemoStorage(),
@@ -182,7 +180,7 @@ void Core::OmemoHandler::requestOwnBundles() {
 void Core::OmemoHandler::onBundlesReceived(const QString& jid) {
     std::list<Shared::KeyInfo> keys;
     acc->oh->getDevices(jid, keys);
-    std::map<QByteArray, Shared::TrustLevel> trustLevels = acc->th->getKeys(ns_omemo_2, jid);
+    std::map<QByteArray, Shared::TrustLevel> trustLevels = acc->th->getKeys(Shared::EncryptionProtocol::omemo2, jid);
 
     qDebug() << "OMEMO info for " << jid << " devices:" << keys.size() << ", trustLevels:" << trustLevels.size();
     for (Shared::KeyInfo& key : keys) {
@@ -200,7 +198,7 @@ void Core::OmemoHandler::onOwnBundlesReceived() {
     QString jid = acc->getBareJid();
     std::list<Shared::KeyInfo> keys;
     acc->oh->getDevices(jid, keys);
-    std::map<QByteArray, Shared::TrustLevel> trustLevels = acc->th->getKeys(ns_omemo_2, jid);
+    std::map<QByteArray, Shared::TrustLevel> trustLevels = acc->th->getKeys(Shared::EncryptionProtocol::omemo2, jid);
 
     qDebug() << "OMEMO info for " << jid << " devices:" << keys.size() << ", trustLevels:" << trustLevels.size();
     for (Shared::KeyInfo& key : keys) {
diff --git a/core/handlers/omemohandler.h b/core/handlers/omemohandler.h
index b0db613..7053450 100644
--- a/core/handlers/omemohandler.h
+++ b/core/handlers/omemohandler.h
@@ -33,8 +33,7 @@ Q_DECLARE_METATYPE(QXmppOmemoStorage::Device);
 namespace Core {
 class Account;
 
-class OmemoHandler :public QObject, public QXmppOmemoStorage
-{
+class OmemoHandler : public QObject, public QXmppOmemoStorage {
     Q_OBJECT
 public:
     typedef std::pair<QDateTime, QByteArray> SignedPreKeyPair;
diff --git a/core/handlers/rosterhandler.cpp b/core/handlers/rosterhandler.cpp
index 3738d2c..ceaab2d 100644
--- a/core/handlers/rosterhandler.cpp
+++ b/core/handlers/rosterhandler.cpp
@@ -96,6 +96,9 @@ void Core::RosterHandler::addedAccount(const QString& jid) {
     
     if (newContact) {
         QMap<QString, QVariant> cData = contact->getInfo();
+#if (QXMPP_VERSION) >= QT_VERSION_CHECK(1, 5, 0)
+        cData.insert("trust", QVariant::fromValue(acc->th->getSummary(jid)));
+#endif
         int grCount = 0;
         for (QSet<QString>::const_iterator itr = gr.begin(), end = gr.end(); itr != end; ++itr) {
             const QString& groupName = *itr;
diff --git a/core/handlers/trusthandler.cpp b/core/handlers/trusthandler.cpp
index cc97e57..35e1bc6 100644
--- a/core/handlers/trusthandler.cpp
+++ b/core/handlers/trusthandler.cpp
@@ -18,9 +18,9 @@
 #include "core/account.h"
 #include "core/adapterfunctions.h"
 
-using namespace Core;
-
 Core::TrustHandler::TrustHandler(Account* account):
+    QObject(),
+    QXmppTrustStorage(),
     acc(account),
     db(acc->getName() + "/trust"),
     protocols(db.createDirectory() + "/protocols"),
@@ -161,7 +161,7 @@ QXmppTask<QHash<QString, QMultiHash<QString, QByteArray>>> Core::TrustHandler::s
     return Core::makeReadyTask(std::move(modifiedKeys));
 }
 
-QXmppTask<bool> TrustHandler::hasKey(const QString& encryption,
+QXmppTask<bool> Core::TrustHandler::hasKey(const QString& encryption,
                                    const QString& keyOwnerJid,
                                    QXmpp::TrustLevels trustLevels)
 {
@@ -179,7 +179,7 @@ QXmppTask<bool> TrustHandler::hasKey(const QString& encryption,
     return Core::makeReadyTask(std::move(found));
 }
 
-QXmppTask<QHash<QString, QHash<QByteArray, QXmpp::TrustLevel>>> TrustHandler::keys(
+QXmppTask<QHash<QString, QHash<QByteArray, QXmpp::TrustLevel>>> Core::TrustHandler::keys(
     const QString& encryption,
     const QList<QString>& keyOwnerJids,
     QXmpp::TrustLevels trustLevels)
@@ -202,7 +202,7 @@ QXmppTask<QHash<QString, QHash<QByteArray, QXmpp::TrustLevel>>> TrustHandler::ke
     return Core::makeReadyTask(std::move(res));
 }
 
-QXmppTask<QHash<QXmpp::TrustLevel, QMultiHash<QString, QByteArray>>> TrustHandler::keys(
+QXmppTask<QHash<QXmpp::TrustLevel, QMultiHash<QString, QByteArray>>> Core::TrustHandler::keys(
     const QString& encryption,
     QXmpp::TrustLevels trustLevels)
 {
@@ -219,17 +219,17 @@ QXmppTask<QHash<QXmpp::TrustLevel, QMultiHash<QString, QByteArray>>> TrustHandle
     return Core::makeReadyTask(std::move(res));
 }
 
-QXmppTask<void> TrustHandler::removeKeys(const QString& encryption) {
+QXmppTask<void> Core::TrustHandler::removeKeys(const QString& encryption) {
     getCache(encryption)->drop();
     return Core::makeReadyTask();
 }
 
-QXmppTask<void> TrustHandler::removeKeys(const QString& encryption, const QString& keyOwnerJid) {
+QXmppTask<void> Core::TrustHandler::removeKeys(const QString& encryption, const QString& keyOwnerJid) {
     getCache(encryption)->removeRecord(keyOwnerJid);
     return Core::makeReadyTask();
 }
 
-QXmppTask<void> TrustHandler::removeKeys(const QString& encryption, const QList<QByteArray>& keyIds) {
+QXmppTask<void> Core::TrustHandler::removeKeys(const QString& encryption, const QList<QByteArray>& keyIds) {
     std::set<QByteArray> set;
     for (const QByteArray& keyId : keyIds)
         set.insert(keyId);
@@ -258,7 +258,7 @@ QXmppTask<void> TrustHandler::removeKeys(const QString& encryption, const QList<
     return Core::makeReadyTask();
 }
 
-QXmppTask<void> TrustHandler::addKeys(
+QXmppTask<void> Core::TrustHandler::addKeys(
     const QString& encryption,
     const QString& keyOwnerJid,
     const QList<QByteArray>& keyIds,
@@ -287,7 +287,7 @@ QXmppTask<void> TrustHandler::addKeys(
     return Core::makeReadyTask();
 }
 
-QXmppTask<QByteArray> TrustHandler::ownKey(const QString& encryption) {
+QXmppTask<QByteArray> Core::TrustHandler::ownKey(const QString& encryption) {
     QByteArray res;
     try {
         res = ownKeys->getRecord(encryption);
@@ -295,7 +295,7 @@ QXmppTask<QByteArray> TrustHandler::ownKey(const QString& encryption) {
     return Core::makeReadyTask(std::move(res));
 }
 
-QXmppTask<void> TrustHandler::resetOwnKey(const QString& encryption) {
+QXmppTask<void> Core::TrustHandler::resetOwnKey(const QString& encryption) {
     try {
         ownKeys->removeRecord(encryption);
     } catch (const DataBase::NotFound& e) {}
@@ -303,12 +303,12 @@ QXmppTask<void> TrustHandler::resetOwnKey(const QString& encryption) {
     return Core::makeReadyTask();
 }
 
-QXmppTask<void> TrustHandler::setOwnKey(const QString& encryption, const QByteArray& keyId) {
+QXmppTask<void> Core::TrustHandler::setOwnKey(const QString& encryption, const QByteArray& keyId) {
     ownKeys->forceRecord(encryption, keyId);
     return Core::makeReadyTask();
 }
 
-QXmppTask<QXmpp::TrustSecurityPolicy> TrustHandler::securityPolicy(const QString& encryption) {
+QXmppTask<QXmpp::TrustSecurityPolicy> Core::TrustHandler::securityPolicy(const QString& encryption) {
     QXmpp::TrustSecurityPolicy res;
     try {
         res = static_cast<QXmpp::TrustSecurityPolicy>(securityPolicies->getRecord(encryption));
@@ -316,14 +316,14 @@ QXmppTask<QXmpp::TrustSecurityPolicy> TrustHandler::securityPolicy(const QString
     return Core::makeReadyTask(std::move(res));
 }
 
-QXmppTask<void> TrustHandler::resetSecurityPolicy(const QString& encryption) {
+QXmppTask<void> Core::TrustHandler::resetSecurityPolicy(const QString& encryption) {
     try {
         securityPolicies->removeRecord(encryption);
     } catch (const DataBase::NotFound& e) {}
     return Core::makeReadyTask();
 }
 
-QXmppTask<void> TrustHandler::setSecurityPolicy(
+QXmppTask<void> Core::TrustHandler::setSecurityPolicy(
     const QString& encryption,
     QXmpp::TrustSecurityPolicy securityPolicy)
 {
@@ -333,8 +333,9 @@ QXmppTask<void> TrustHandler::setSecurityPolicy(
     return Core::makeReadyTask();
 }
 
-Core::TrustHandler::Keys Core::TrustHandler::getKeys(const QString& protocol, const QString& jid) const {
-    std::map<QString, KeyCache*>::const_iterator itr = keysByProtocol.find(protocol);
+Core::TrustHandler::Keys Core::TrustHandler::getKeys(Shared::EncryptionProtocol protocol, const QString& jid) const {
+    const QString& prt = Shared::TrustSummary::protocolKeys.at(protocol);
+    std::map<QString, KeyCache*>::const_iterator itr = keysByProtocol.find(prt);
     if (itr != keysByProtocol.end()) {
         try {
             Keys map = itr->second->getRecord(jid);
@@ -347,6 +348,21 @@ Core::TrustHandler::Keys Core::TrustHandler::getKeys(const QString& protocol, co
     }
 }
 
+Shared::TrustSummary Core::TrustHandler::getSummary(const QString& jid) const {
+    Shared::TrustSummary result;
+    for (const std::pair<const QString, KeyCache*>& pair : keysByProtocol) {
+        try {
+            Keys keys = pair.second->getRecord(jid);
+            Shared::EncryptionProtocol protocol = Shared::TrustSummary::protocolValues.at(pair.first);
+            for (const std::pair<const QByteArray, Shared::TrustLevel>& trust : keys) {
+                result.increment(protocol, trust.second);
+            }
+        } catch (const DataBase::NotFound& e) {}
+    }
+
+    return result;
+}
+
 Shared::TrustLevel Core::TrustHandler::convert(Core::TrustHandler::TL level) {
     switch (level) {
         case QXmpp::TrustLevel::Undecided: return Shared::TrustLevel::undecided;
diff --git a/core/handlers/trusthandler.h b/core/handlers/trusthandler.h
index 677a4f7..d21e0c3 100644
--- a/core/handlers/trusthandler.h
+++ b/core/handlers/trusthandler.h
@@ -18,6 +18,7 @@
 #define CORE_TRUSTHANDLER_H
 
 #include <shared/enums.h>
+#include <shared/trustsummary.h>
 
 #include <QXmppTrustStorage.h>
 #include <cache.h>
@@ -25,7 +26,8 @@
 namespace Core {
 class Account;
 
-class TrustHandler : public QXmppTrustStorage {
+class TrustHandler : public QObject, public QXmppTrustStorage {
+    Q_OBJECT
 public:
     TrustHandler(Account* account);
     ~TrustHandler();
@@ -57,12 +59,13 @@ public:
     virtual QXmppTask<void> setOwnKey(CSR encryption, const QByteArray& keyId) override;
     virtual QXmppTask<QXmpp::TrustSecurityPolicy> securityPolicy(CSR encryption) override;
     virtual QXmppTask<void> resetSecurityPolicy(CSR encryption) override;
-    virtual QXmppTask<void> setSecurityPolicy(CSR encryption, QXmpp::TrustSecurityPolicy securityPolicy) override;
+    virtual QXmppTask<void> setSecurityPolicy(const QString& encryption, QXmpp::TrustSecurityPolicy securityPolicy) override;
 
     static TL convert(Shared::TrustLevel level);
     static Shared::TrustLevel convert(TL level);
 
-    Keys getKeys(const QString& protocol, const QString& jid) const;
+    Keys getKeys(Shared::EncryptionProtocol protocol, const QString& jid) const;
+    Shared::TrustSummary getSummary(const QString& jid) const;
 
 private:
     KeyCache* createNewCache(const QString& encryption);
diff --git a/shared/global.cpp b/shared/global.cpp
index 7a1b494..1b5763b 100644
--- a/shared/global.cpp
+++ b/shared/global.cpp
@@ -226,61 +226,50 @@ Shared::Global::FileInfo Shared::Global::getFileInfo(const QString& path)
 }
 
 
-Shared::Global * Shared::Global::getInstance()
-{
+Shared::Global * Shared::Global::getInstance() {
     return instance;
 }
 
-QString Shared::Global::getName(Message::State rl)
-{
+QString Shared::Global::getName(Message::State rl) {
     return instance->messageState[static_cast<int>(rl)];
 }
 
-QString Shared::Global::getName(Shared::Affiliation af)
-{
+QString Shared::Global::getName(Shared::Affiliation af) {
     return instance->affiliation[static_cast<int>(af)];
 }
 
-QString Shared::Global::getName(Shared::Availability av)
-{
+QString Shared::Global::getName(Shared::Availability av) {
     return instance->availability[static_cast<int>(av)];
 }
 
-QString Shared::Global::getName(Shared::ConnectionState cs)
-{
+QString Shared::Global::getName(Shared::ConnectionState cs) {
     return instance->connectionState[static_cast<int>(cs)];
 }
 
-QString Shared::Global::getName(Shared::Role rl)
-{
+QString Shared::Global::getName(Shared::Role rl) {
     return instance->role[static_cast<int>(rl)];
 }
 
-QString Shared::Global::getName(Shared::SubscriptionState ss)
-{
+QString Shared::Global::getName(Shared::SubscriptionState ss) {
     return instance->subscriptionState[static_cast<int>(ss)];
 }
 
-QString Shared::Global::getName(Shared::AccountPassword ap)
-{
+QString Shared::Global::getName(Shared::AccountPassword ap) {
     return instance->accountPassword[static_cast<int>(ap)];
 }
 
-QString Shared::Global::getName(Shared::TrustLevel tl)
-{
+QString Shared::Global::getName(Shared::TrustLevel tl) {
     return instance->trustLevel[static_cast<int>(tl)];
 }
 
-void Shared::Global::setSupported(const QString& pluginName, bool support)
-{
+void Shared::Global::setSupported(const QString& pluginName, bool support) {
     std::map<QString, bool>::iterator itr = instance->pluginSupport.find(pluginName);
     if (itr != instance->pluginSupport.end()) {
         itr->second = support;
     }
 }
 
-bool Shared::Global::supported(const QString& pluginName)
-{
+bool Shared::Global::supported(const QString& pluginName) {
     std::map<QString, bool>::iterator itr = instance->pluginSupport.find(pluginName);
     if (itr != instance->pluginSupport.end()) {
         return itr->second;
@@ -288,8 +277,7 @@ bool Shared::Global::supported(const QString& pluginName)
     return false;
 }
 
-QString Shared::Global::getDescription(Shared::AccountPassword ap)
-{
+QString Shared::Global::getDescription(Shared::AccountPassword ap) {
     return instance->accountPasswordDescription[static_cast<int>(ap)];
 }
 
@@ -355,8 +343,7 @@ void Shared::Global::highlightInFileManager(const QString& path)
     }
 }
 
-QIcon Shared::Global::createThemePreview(const QString& path)
-{
+QIcon Shared::Global::createThemePreview(const QString& path) {
     if (supported("colorSchemeTools")) {
         QIcon* icon = createPreview(path);
         QIcon localIcon = *icon;
@@ -367,8 +354,7 @@ QIcon Shared::Global::createThemePreview(const QString& path)
     }
 }
 
-QString Shared::Global::getColorSchemeName(const QString& path)
-{
+QString Shared::Global::getColorSchemeName(const QString& path) {
     if (supported("colorSchemeTools")) {
         QString res;
         colorSchemeName(path, res);
@@ -378,8 +364,7 @@ QString Shared::Global::getColorSchemeName(const QString& path)
     }
 }
 
-void Shared::Global::setTheme(const QString& path)
-{
+void Shared::Global::setTheme(const QString& path) {
     if (supported("colorSchemeTools")) {
         if (path.toLower() == "system") {
             QApplication::setPalette(getInstance()->defaultSystemPalette);
@@ -391,8 +376,7 @@ void Shared::Global::setTheme(const QString& path)
     }
 }
 
-void Shared::Global::setStyle(const QString& style)
-{
+void Shared::Global::setStyle(const QString& style) {
     if (style.toLower() == "system") {
         QApplication::setStyle(getInstance()->defaultSystemStyle);
     } else {
@@ -404,7 +388,7 @@ void Shared::Global::setStyle(const QString& style)
 template<>                                                                                  \
 Enum Shared::Global::fromInt(int src)                                                       \
 {                                                                                           \
-    if (src < static_cast<int>(Enum##Lowest) && src > static_cast<int>(Enum##Highest)) {    \
+    if (src < static_cast<int>(Enum##Lowest) || src > static_cast<int>(Enum##Highest)) {    \
         throw EnumOutOfRange(#Enum);                                                        \
     }                                                                                       \
     return static_cast<Enum>(src);                                                          \
diff --git a/shared/global.h b/shared/global.h
index b311f9f..578fc42 100644
--- a/shared/global.h
+++ b/shared/global.h
@@ -121,9 +121,7 @@ namespace Shared {
         template<typename T>
         static T fromInt(unsigned int src);
         
-        class EnumOutOfRange: 
-        public Utils::Exception
-        {
+        class EnumOutOfRange: public Utils::Exception {
         public:
             EnumOutOfRange(const std::string& p_name):Exception(), name(p_name) {}
             
diff --git a/shared/trustsummary.cpp b/shared/trustsummary.cpp
index 658538c..33a3873 100644
--- a/shared/trustsummary.cpp
+++ b/shared/trustsummary.cpp
@@ -27,6 +27,17 @@ const std::set<Shared::TrustLevel> Shared::TrustSummary::untrustedLevels({
     Shared::TrustLevel::manuallyDistrusted
 });
 
+const std::map<Shared::EncryptionProtocol, QString> Shared::TrustSummary::protocolKeys({
+    {Shared::EncryptionProtocol::omemo, "eu.siacs.conversations.axolotl"},
+    {Shared::EncryptionProtocol::omemo1, "urn:xmpp:omemo:1"},
+    {Shared::EncryptionProtocol::omemo2, "urn:xmpp:omemo:2"}
+});
+const std::map<QString, Shared::EncryptionProtocol> Shared::TrustSummary::protocolValues({
+    {"eu.siacs.conversations.axolotl", Shared::EncryptionProtocol::omemo},
+    {"urn:xmpp:omemo:1", Shared::EncryptionProtocol::omemo1},
+    {"urn:xmpp:omemo:2", Shared::EncryptionProtocol::omemo2}
+});
+
 Shared::TrustSummary::TrustSummary():
     data()
 {}
diff --git a/shared/trustsummary.h b/shared/trustsummary.h
index 3283c10..7c79978 100644
--- a/shared/trustsummary.h
+++ b/shared/trustsummary.h
@@ -17,6 +17,8 @@
 #ifndef SHARED_TRUSTSUMMARY_H
 #define SHARED_TRUSTSUMMARY_H
 
+#include <QObject>
+
 #include <map>
 #include <set>
 
@@ -42,10 +44,18 @@ private:
     typedef std::map<EncryptionProtocol, Amounts> Data;
 
     Data data;
+
+public:
+    static const std::map<EncryptionProtocol, QString> protocolKeys;
+    static const std::map<QString, EncryptionProtocol> protocolValues;
+
+private:
     static const std::set<TrustLevel> trustedLevels;
     static const std::set<TrustLevel> untrustedLevels;
 };
 
 }
 
+Q_DECLARE_METATYPE(Shared::TrustSummary)
+
 #endif // SHARED_TRUSTSUMMARY_H
diff --git a/ui/models/contact.cpp b/ui/models/contact.cpp
index d5c7dc4..b963421 100644
--- a/ui/models/contact.cpp
+++ b/ui/models/contact.cpp
@@ -24,62 +24,56 @@ Models::Contact::Contact(const Account* acc, const QString& p_jid ,const QMap<QS
     Element(Item::contact, acc, p_jid, data, parentItem),
     availability(Shared::Availability::offline),
     state(Shared::SubscriptionState::none),
+    trust(),
     presences(),
     status()
 {
     QMap<QString, QVariant>::const_iterator itr = data.find("state");
-    if (itr != data.end()) {
+    if (itr != data.end())
         setState(itr.value().toUInt());
-    }
+
+    itr = data.find("trust");
+    if (itr != data.end())
+        setTrust(itr.value().value<Shared::TrustSummary>());
 }
 
-Models::Contact::~Contact()
-{
-}
+Models::Contact::~Contact() {}
 
-void Models::Contact::setAvailability(unsigned int p_state)
-{
+void Models::Contact::setAvailability(unsigned int p_state) {
     setAvailability(Shared::Global::fromInt<Shared::Availability>(p_state));
 }
 
-void Models::Contact::setState(unsigned int p_state)
-{
+void Models::Contact::setState(unsigned int p_state) {
     setState(Shared::Global::fromInt<Shared::SubscriptionState>(p_state));
 }
 
-Shared::Availability Models::Contact::getAvailability() const
-{
+Shared::Availability Models::Contact::getAvailability() const {
     return availability;
 }
 
-void Models::Contact::setAvailability(Shared::Availability p_state)
-{
+void Models::Contact::setAvailability(Shared::Availability p_state) {
     if (availability != p_state) {
         availability = p_state;
         changed(3);
     }
 }
 
-QString Models::Contact::getStatus() const
-{
+QString Models::Contact::getStatus() const {
     return status;
 }
 
-void Models::Contact::setStatus(const QString& p_state)
-{
+void Models::Contact::setStatus(const QString& p_state) {
     if (status != p_state) {
         status = p_state;
         changed(5);
     }
 }
 
-int Models::Contact::columnCount() const
-{
-    return 8;
+int Models::Contact::columnCount() const {
+    return 9;
 }
 
-QVariant Models::Contact::data(int column) const
-{
+QVariant Models::Contact::data(int column) const {
     switch (column) {
         case 0:
             return getContactName();
@@ -97,35 +91,35 @@ QVariant Models::Contact::data(int column) const
             return QVariant::fromValue(getAvatarState());
         case 7:
             return getAvatarPath();
+        case 8:
+            return QVariant::fromValue(getTrust());
         default:
             return QVariant();
     }
 }
 
-QString Models::Contact::getContactName() const
-{
-    if (name == "") {
+QString Models::Contact::getContactName() const {
+    if (name == "")
         return jid;
-    } else {
+    else
         return name;
-    }
 }
 
-void Models::Contact::update(const QString& field, const QVariant& value)
-{
+void Models::Contact::update(const QString& field, const QVariant& value) {
     if (field == "name") {
         setName(value.toString());
     } else if (field == "availability") {
         setAvailability(value.toUInt());
     } else if (field == "state") {
         setState(value.toUInt());
+    } else if (field == "trust") {
+        setTrust(value.value<Shared::TrustSummary>());
     } else {
         Element::update(field, value);
     }
 }
 
-void Models::Contact::addPresence(const QString& p_name, const QMap<QString, QVariant>& data)
-{
+void Models::Contact::addPresence(const QString& p_name, const QMap<QString, QVariant>& data) {
     QMap<QString, Presence*>::iterator itr = presences.find(p_name);
     
     if (itr == presences.end()) {
@@ -135,14 +129,12 @@ void Models::Contact::addPresence(const QString& p_name, const QMap<QString, QVa
         appendChild(pr);
     } else {
         Presence* pr = itr.value();
-        for (QMap<QString, QVariant>::const_iterator itr = data.begin(), end = data.end(); itr != end; ++itr) {
+        for (QMap<QString, QVariant>::const_iterator itr = data.begin(), end = data.end(); itr != end; ++itr)
             pr->update(itr.key(), itr.value());
-        }
     }
 }
 
-void Models::Contact::removePresence(const QString& name)
-{
+void Models::Contact::removePresence(const QString& name) {
     QMap<QString, Presence*>::iterator itr = presences.find(name);
     
     if (itr == presences.end()) {
@@ -155,18 +147,15 @@ void Models::Contact::removePresence(const QString& name)
     }
 }
 
-Models::Presence * Models::Contact::getPresence(const QString& name)
-{
+Models::Presence * Models::Contact::getPresence(const QString& name) {
     QMap<QString, Presence*>::iterator itr = presences.find(name);
-    if (itr == presences.end()) {
+    if (itr == presences.end())
         return nullptr;
-    } else {
+    else
         return itr.value();
-    }
 }
 
-void Models::Contact::refresh()
-{
+void Models::Contact::refresh() {
     QDateTime lastActivity;
     Presence* presence = 0;
     for (QMap<QString, Presence*>::iterator itr = presences.begin(), end = presences.end(); itr != end; ++itr) {
@@ -188,36 +177,43 @@ void Models::Contact::refresh()
     }
 }
 
-void Models::Contact::_removeChild(int index)
-{
+void Models::Contact::_removeChild(int index) {
     Item* child = childItems[index];
     disconnect(child, &Item::childChanged, this, &Contact::refresh);
     Item::_removeChild(index);
     refresh();
 }
 
-void Models::Contact::_appendChild(Models::Item* child)
-{
+void Models::Contact::_appendChild(Models::Item* child) {
     Item::_appendChild(child);
     connect(child, &Item::childChanged, this, &Contact::refresh);
     refresh();
 }
 
-Shared::SubscriptionState Models::Contact::getState() const
-{
+Shared::SubscriptionState Models::Contact::getState() const {
     return state;
 }
 
-void Models::Contact::setState(Shared::SubscriptionState p_state)
-{
+void Models::Contact::setState(Shared::SubscriptionState p_state) {
     if (state != p_state) {
         state = p_state;
         changed(2);
     }
 }
 
-QIcon Models::Contact::getStatusIcon(bool big) const
-{
+Shared::TrustSummary Models::Contact::getTrust() const {
+    return trust;
+}
+
+void Models::Contact::setTrust(const Shared::TrustSummary& p_trust) {
+    //if (trust != p_trust) {
+        trust = p_trust;
+        changed(8);
+    //}
+}
+
+
+QIcon Models::Contact::getStatusIcon(bool big) const {
     if (getMessagesCount() > 0) {
         return Shared::icon("mail-message", big);
     } else if (state == Shared::SubscriptionState::both || state == Shared::SubscriptionState::to) {
@@ -227,8 +223,7 @@ QIcon Models::Contact::getStatusIcon(bool big) const
     }
 }
 
-void Models::Contact::toOfflineState()
-{
+void Models::Contact::toOfflineState() {
     std::deque<Item*>::size_type size = childItems.size();
     if (size > 0) {
         emit childIsAboutToBeRemoved(this, 0, size - 1);
@@ -245,13 +240,11 @@ void Models::Contact::toOfflineState()
     }
 }
 
-QString Models::Contact::getDisplayedName() const
-{
+QString Models::Contact::getDisplayedName() const {
     return getContactName();
 }
 
-void Models::Contact::handleRecconnect()
-{
+void Models::Contact::handleRecconnect() {
     if (getMessagesCount() > 0) {
         feed->requestLatestMessages();
     }
diff --git a/ui/models/contact.h b/ui/models/contact.h
index c4fc131..36802cb 100644
--- a/ui/models/contact.h
+++ b/ui/models/contact.h
@@ -19,17 +19,19 @@
 #ifndef MODELS_CONTACT_H
 #define MODELS_CONTACT_H
 
-#include "element.h"
-#include "presence.h"
-#include "shared/enums.h"
-#include "shared/message.h"
-#include "shared/icons.h"
-#include "shared/global.h"
-
 #include <QMap>
 #include <QIcon>
+
 #include <deque>
 
+#include "element.h"
+#include "presence.h"
+#include <shared/enums.h>
+#include <shared/message.h>
+#include <shared/icons.h>
+#include <shared/global.h>
+#include <shared/trustsummary.h>
+
 namespace Models {
     
 class Contact : public Element
@@ -56,6 +58,7 @@ public:
     QString getContactName() const;
     QString getStatus() const;
     QString getDisplayedName() const override;
+    Shared::TrustSummary getTrust() const;
     
     void handleRecconnect();        //this is a special method Models::Roster calls when reconnect happens
     
@@ -73,10 +76,12 @@ protected:
     void setState(Shared::SubscriptionState p_state);
     void setState(unsigned int p_state);
     void setStatus(const QString& p_state);
+    void setTrust(const Shared::TrustSummary& p_trust);
     
 private:
     Shared::Availability availability;
     Shared::SubscriptionState state;
+    Shared::TrustSummary trust;
     QMap<QString, Presence*> presences;
     QString status;
 };

From 69d797fe51d57a373ee56f67124fe0a239790716 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Sat, 18 Mar 2023 02:50:04 +0300
Subject: [PATCH 247/281] showing the button for encryption if there is at
 least one omemo key, trust summary update calculations

---
 core/handlers/rosterhandler.cpp | 30 +++++++-----
 core/handlers/rosterhandler.h   |  3 ++
 core/handlers/trusthandler.cpp  | 86 ++++++++++++++++++++++++++++-----
 core/handlers/trusthandler.h    |  4 ++
 shared/trustsummary.cpp         | 10 +++-
 shared/trustsummary.h           |  3 ++
 ui/models/contact.cpp           | 12 +++--
 ui/models/contact.h             |  1 +
 ui/widgets/chat.cpp             | 24 ++++-----
 9 files changed, 134 insertions(+), 39 deletions(-)

diff --git a/core/handlers/rosterhandler.cpp b/core/handlers/rosterhandler.cpp
index ceaab2d..2e59971 100644
--- a/core/handlers/rosterhandler.cpp
+++ b/core/handlers/rosterhandler.cpp
@@ -39,6 +39,10 @@ void Core::RosterHandler::initialize() {
     connect(acc->bm, &QXmppBookmarkManager::bookmarksReceived, this, &RosterHandler::bookmarksReceived);
 
     connect(acc, &Account::pepSupportChanged, this, &RosterHandler::onPepSupportedChanged);
+
+#if (QXMPP_VERSION) >= QT_VERSION_CHECK(1, 5, 0)
+    connect(acc->th, &TrustHandler::trustLevelsChanged, this, &RosterHandler::onTrustChanged);
+#endif
 }
 
 Core::RosterHandler::~RosterHandler() {
@@ -214,10 +218,13 @@ void Core::RosterHandler::onContactGroupAdded(const QString& group) {
     if (contact->groupsCount() == 1) {
         // not sure i need to handle it here, the situation with grouped and ungrouped contacts handled on the client anyway
     }
-    
+
     QMap<QString, QVariant> cData({
         {"name", contact->getName()},
         {"state", QVariant::fromValue(contact->getSubscriptionState())},
+#if (QXMPP_VERSION) >= QT_VERSION_CHECK(1, 5, 0)
+        {"trust", QVariant::fromValue(acc->th->getSummary(contact->jid))},
+#endif
         {"encryption", contact->isEncryptionEnabled()}
     });
     addToGroup(contact->jid, group);
@@ -229,7 +236,7 @@ void Core::RosterHandler::onContactGroupRemoved(const QString& group) {
     if (contact->groupsCount() == 0) {
         // not sure i need to handle it here, the situation with grouped and ungrouped contacts handled on the client anyway
     }
-    
+
     emit acc->removeContact(contact->jid, group);
     removeFromGroup(contact->jid, group);
 }
@@ -246,12 +253,14 @@ void Core::RosterHandler::onContactEncryptionChanged(bool value) {
 
 void Core::RosterHandler::onContactSubscriptionStateChanged(Shared::SubscriptionState cstate) {
     Contact* contact = static_cast<Contact*>(sender());
-    QMap<QString, QVariant> cData({
-        {"state", QVariant::fromValue(cstate)},
-    });
-    emit acc->changeContact(contact->jid, cData);
+    emit acc->changeContact(contact->jid, {{"state", QVariant::fromValue(cstate)}});
 }
 
+void Core::RosterHandler::onTrustChanged(const QString& jid, const Shared::TrustSummary& trust) {
+    emit acc->changeContact(jid, {{"trust", QVariant::fromValue(trust)}});
+}
+
+
 void Core::RosterHandler::addToGroup(const QString& jid, const QString& group) {
     std::map<QString, std::set<QString>>::iterator gItr = groups.find(group);
     if (gItr == groups.end()) {
@@ -296,18 +305,18 @@ Core::RosterItem * Core::RosterHandler::getRosterItem(const QString& jid) {
 Core::Conference * Core::RosterHandler::getConference(const QString& jid) {
     Conference* item = 0;
     std::map<QString, Conference*>::const_iterator coitr = conferences.find(jid.toLower());
-    if (coitr != conferences.end()) {
+    if (coitr != conferences.end())
         item = coitr->second;
-    }
+
     return item;
 }
 
 Core::Contact * Core::RosterHandler::getContact(const QString& jid) {
     Contact* item = 0;
     std::map<QString, Contact*>::const_iterator citr = contacts.find(jid.toLower());
-    if (citr != contacts.end()) {
+    if (citr != contacts.end())
         item = citr->second;
-    }
+
     return item;
 }
 
@@ -527,7 +536,6 @@ void Core::RosterHandler::handleOffline() {
     }
 }
 
-
 void Core::RosterHandler::onPepSupportedChanged(Shared::Support support) {
     if (support == Shared::Support::supported) {
         for (const std::pair<const QString, Contact*>& pair : contacts) {
diff --git a/core/handlers/rosterhandler.h b/core/handlers/rosterhandler.h
index 63f291c..61f3d7a 100644
--- a/core/handlers/rosterhandler.h
+++ b/core/handlers/rosterhandler.h
@@ -34,6 +34,8 @@
 
 #include <shared/enums.h>
 #include <shared/message.h>
+#include <shared/trustsummary.h>
+
 #include <core/contact.h>
 #include <core/conference.h>
 #include <core/delayManager/manager.h>
@@ -75,6 +77,7 @@ private slots:
     void onRosterItemAdded(const QString& bareJid);
     void onRosterItemChanged(const QString& bareJid);
     void onRosterItemRemoved(const QString& bareJid);
+    void onTrustChanged(const QString& jid, const Shared::TrustSummary& trust);
     
     void onMucRoomAdded(QXmppMucRoom* room);
     void onMucJoinedChanged(bool joined);
diff --git a/core/handlers/trusthandler.cpp b/core/handlers/trusthandler.cpp
index 35e1bc6..c866d6f 100644
--- a/core/handlers/trusthandler.cpp
+++ b/core/handlers/trusthandler.cpp
@@ -79,7 +79,25 @@ Core::TrustHandler::KeyCache * Core::TrustHandler::createNewCache(const QString&
 QXmppTask<void> Core::TrustHandler::resetAll(const QString& encryption) {
     securityPolicies->removeRecord(encryption);
     ownKeys->removeRecord(encryption);
-    getCache(encryption)->drop();
+    std::map<QString, KeyCache*>::const_iterator itr = keysByProtocol.find(encryption);
+    if (itr == keysByProtocol.end())
+        return Core::makeReadyTask();
+
+    KeyCache* cache = itr->second;
+    std::map<QString, Keys> keys = cache->readAll();
+    cache->drop();
+
+    for (const std::pair<const QString, Keys>& pair : keys) {
+        bool empty = true;
+        for (const std::pair<const QByteArray, Shared::TrustLevel>& trust : pair.second) {
+            if (trust.second != Shared::TrustLevel::undecided) {
+                empty = false;
+                break;
+            }
+        }
+        if (!empty)
+            emit trustLevelsChanged(pair.first, getSummary(pair.first));
+    }
 
     return Core::makeReadyTask();
 }
@@ -109,6 +127,7 @@ QXmppTask<QHash<QString, QMultiHash<QString, QByteArray>>> Core::TrustHandler::s
     QHash<QString, QMultiHash<QString, QByteArray>> modifiedKeys;
     Shared::TrustLevel oldLevel = convert(oldTrustLevel);
     Shared::TrustLevel newLevel = convert(newTrustLevel);
+    std::set<QString> modifiedJids;
     KeyCache* cache = getCache(encryption);
     for (const QString& keyOwnerJid : keyOwnerJids) {
         Keys map = cache->getRecord(keyOwnerJid);
@@ -118,13 +137,16 @@ QXmppTask<QHash<QString, QMultiHash<QString, QByteArray>>> Core::TrustHandler::s
             if (current == oldLevel) {
                 current = newLevel;
                 modifiedKeys[encryption].insert(keyOwnerJid, pair.first);
+                modifiedJids.insert(keyOwnerJid);
                 ++count;
             }
         }
-        if (count > 0) {
+        if (count > 0)
             cache->changeRecord(keyOwnerJid, map);
-        }
     }
+    for (const QString& jid : modifiedJids)
+        emit trustLevelsChanged(jid, getSummary(jid));
+
     return Core::makeReadyTask(std::move(modifiedKeys));
 }
 
@@ -135,6 +157,7 @@ QXmppTask<QHash<QString, QMultiHash<QString, QByteArray>>> Core::TrustHandler::s
 {
     QHash<QString, QMultiHash<QString, QByteArray>> modifiedKeys;
     Shared::TrustLevel level = convert(trustLevel);
+    std::set<QString> modifiedJids;
     KeyCache* cache = getCache(encryption);
 
     for (MultySB::const_iterator itr = keyIds.begin(), end = keyIds.end(); itr != end; ++itr) {
@@ -150,14 +173,20 @@ QXmppTask<QHash<QString, QMultiHash<QString, QByteArray>>> Core::TrustHandler::s
             }
             if (changed) {
                 modifiedKeys[encryption].insert(keyOwnerJid, keyId);
+                modifiedJids.insert(keyOwnerJid);
                 cache->changeRecord(keyOwnerJid, map);
             }
         } catch (const DataBase::NotFound& e) {
             Keys map({{keyId, level}});
             modifiedKeys[encryption].insert(keyOwnerJid, keyId);
+            modifiedJids.insert(keyOwnerJid);
             cache->addRecord(keyOwnerJid, map);
         }
     }
+
+    for (const QString& jid : modifiedJids)
+        emit trustLevelsChanged(jid, getSummary(jid));
+
     return Core::makeReadyTask(std::move(modifiedKeys));
 }
 
@@ -220,12 +249,41 @@ QXmppTask<QHash<QXmpp::TrustLevel, QMultiHash<QString, QByteArray>>> Core::Trust
 }
 
 QXmppTask<void> Core::TrustHandler::removeKeys(const QString& encryption) {
-    getCache(encryption)->drop();
+    std::map<QString, KeyCache*>::const_iterator itr = keysByProtocol.find(encryption);
+    if (itr == keysByProtocol.end())
+        return Core::makeReadyTask();
+
+    KeyCache* cache = itr->second;
+    std::map<QString, Keys> keys = cache->readAll();
+    cache->drop();
+
+    for (const std::pair<const QString, Keys>& pair : keys) {
+        bool empty = true;
+        for (const std::pair<const QByteArray, Shared::TrustLevel>& trust : pair.second) {
+            if (trust.second != Shared::TrustLevel::undecided) {
+                empty = false;
+                break;
+            }
+        }
+        if (!empty)
+            emit trustLevelsChanged(pair.first, getSummary(pair.first));
+    }
+
     return Core::makeReadyTask();
 }
 
 QXmppTask<void> Core::TrustHandler::removeKeys(const QString& encryption, const QString& keyOwnerJid) {
-    getCache(encryption)->removeRecord(keyOwnerJid);
+    std::map<QString, KeyCache*>::const_iterator itr = keysByProtocol.find(encryption);
+    if (itr == keysByProtocol.end())
+        return Core::makeReadyTask();
+
+    KeyCache* cache = itr->second;
+    try {
+        cache->removeRecord(keyOwnerJid);
+        emit trustLevelsChanged(keyOwnerJid, getSummary(keyOwnerJid));      //TODO there is a probability of notification without the actial change
+    } catch (const DataBase::NotFound& e) {}                                //if the movin entry was empty or if it consisted of Undecided keys
+
+
     return Core::makeReadyTask();
 }
 
@@ -236,14 +294,15 @@ QXmppTask<void> Core::TrustHandler::removeKeys(const QString& encryption, const
 
     KeyCache* cache = getCache(encryption);
     std::map<QString, Keys> data = cache->readAll();
-    bool changed = false;
+    std::set<QString> modifiedJids;
+
     for (std::map<QString, Keys>::iterator cItr = data.begin(), cEnd = data.end(); cItr != cEnd; /*no increment*/) {
         Keys& byOwner = cItr->second;
         for (Keys::const_iterator itr = byOwner.begin(), end = byOwner.end(); itr != end; /*no increment*/) {
             const QByteArray& keyId = itr->first;
             if (set.erase(keyId)) {
                 byOwner.erase(itr++);
-                changed = true;
+                modifiedJids.insert(cItr->first);
             } else
                 ++itr;
         }
@@ -252,8 +311,12 @@ QXmppTask<void> Core::TrustHandler::removeKeys(const QString& encryption, const
         else
             ++cItr;
     }
-    if (changed)
+    if (modifiedJids.size() > 0) {
         cache->replaceAll(data);
+    }
+
+    for (const QString& jid : modifiedJids)
+        emit trustLevelsChanged(jid, getSummary(jid));
 
     return Core::makeReadyTask();
 }
@@ -278,11 +341,12 @@ QXmppTask<void> Core::TrustHandler::addKeys(
             result.first->second = level;
     }
 
-    if (had) {
+    if (had)
         cache->changeRecord(keyOwnerJid, data);
-    } else {
+    else
         cache->addRecord(keyOwnerJid, data);
-    }
+
+    emit trustLevelsChanged(keyOwnerJid, getSummary(keyOwnerJid));
 
     return Core::makeReadyTask();
 }
diff --git a/core/handlers/trusthandler.h b/core/handlers/trusthandler.h
index d21e0c3..98d7f1e 100644
--- a/core/handlers/trusthandler.h
+++ b/core/handlers/trusthandler.h
@@ -32,6 +32,10 @@ public:
     TrustHandler(Account* account);
     ~TrustHandler();
 
+signals:
+    void trustLevelsChanged(const QString& jid, const Shared::TrustSummary& summary) const;
+
+public:
     typedef QMultiHash<QString, QByteArray> MultySB;
     typedef QHash<QString, MultySB> HashSM;
     typedef const QList<QString>& CLSR;
diff --git a/shared/trustsummary.cpp b/shared/trustsummary.cpp
index 33a3873..eae98bf 100644
--- a/shared/trustsummary.cpp
+++ b/shared/trustsummary.cpp
@@ -138,7 +138,7 @@ bool Shared::TrustSummary::hasTrustedKeys(Shared::EncryptionProtocol protocol) c
 }
 
 bool Shared::TrustSummary::hasUntrustedKeys(Shared::EncryptionProtocol protocol) const {
-        Data::const_iterator itr = data.find(protocol);
+    Data::const_iterator itr = data.find(protocol);
     if (itr == data.end())
         return false;
 
@@ -149,3 +149,11 @@ bool Shared::TrustSummary::hasUntrustedKeys(Shared::EncryptionProtocol protocol)
 
     return false;
 }
+
+bool Shared::TrustSummary::operator==(const Shared::TrustSummary& other) {
+    return data == other.data;
+}
+
+bool Shared::TrustSummary::operator!=(const Shared::TrustSummary& other) {
+    return data != other.data;
+}
diff --git a/shared/trustsummary.h b/shared/trustsummary.h
index 7c79978..e663a9d 100644
--- a/shared/trustsummary.h
+++ b/shared/trustsummary.h
@@ -30,6 +30,9 @@ class TrustSummary {
 public:
     TrustSummary();
 
+    bool operator == (const TrustSummary& other);
+    bool operator != (const TrustSummary& other);
+
     void set(EncryptionProtocol protocol, TrustLevel level, uint8_t amount);
     uint8_t increment(EncryptionProtocol protocol, TrustLevel level);
     uint8_t decrement(EncryptionProtocol protocol, TrustLevel level);
diff --git a/ui/models/contact.cpp b/ui/models/contact.cpp
index b963421..8d6def9 100644
--- a/ui/models/contact.cpp
+++ b/ui/models/contact.cpp
@@ -206,10 +206,10 @@ Shared::TrustSummary Models::Contact::getTrust() const {
 }
 
 void Models::Contact::setTrust(const Shared::TrustSummary& p_trust) {
-    //if (trust != p_trust) {
+    if (trust != p_trust) {
         trust = p_trust;
         changed(8);
-    //}
+    }
 }
 
 
@@ -245,7 +245,11 @@ QString Models::Contact::getDisplayedName() const {
 }
 
 void Models::Contact::handleRecconnect() {
-    if (getMessagesCount() > 0) {
+    if (getMessagesCount() > 0)
         feed->requestLatestMessages();
-    }
 }
+
+bool Models::Contact::hasKeys(Shared::EncryptionProtocol protocol) const {
+    return trust.hasKeys(protocol);
+}
+
diff --git a/ui/models/contact.h b/ui/models/contact.h
index 36802cb..e7d767d 100644
--- a/ui/models/contact.h
+++ b/ui/models/contact.h
@@ -59,6 +59,7 @@ public:
     QString getStatus() const;
     QString getDisplayedName() const override;
     Shared::TrustSummary getTrust() const;
+    bool hasKeys(Shared::EncryptionProtocol protocol) const;
     
     void handleRecconnect();        //this is a special method Models::Roster calls when reconnect happens
     
diff --git a/ui/widgets/chat.cpp b/ui/widgets/chat.cpp
index 052d83d..607fab4 100644
--- a/ui/widgets/chat.cpp
+++ b/ui/widgets/chat.cpp
@@ -17,6 +17,7 @@
  */
 
 #include "chat.h"
+#include "ui_conversation.h"
 
 Chat::Chat(Models::Account* acc, Models::Contact* p_contact, QWidget* parent):
     Conversation(false, acc, p_contact, p_contact->getJid(), "", parent),
@@ -28,14 +29,14 @@ Chat::Chat(Models::Account* acc, Models::Contact* p_contact, QWidget* parent):
     setAvatar(p_contact->getAvatarPath());
     
     connect(contact, &Models::Contact::childChanged, this, &Chat::onContactChanged);
+    if (p_contact->hasKeys(Shared::EncryptionProtocol::omemo2))
+        m_ui->encryptionButton->setVisible(true);
 }
 
 Chat::~Chat()
-{
-}
+{}
 
-void Chat::onContactChanged(Models::Item* item, int row, int col)
-{
+void Chat::onContactChanged(Models::Item* item, int row, int col) {
     if (item == contact) {
         switch (col) {
             case 0:
@@ -50,19 +51,20 @@ void Chat::onContactChanged(Models::Item* item, int row, int col)
             case 7:
                 setAvatar(contact->getAvatarPath());
                 break;
+            case 8:
+                m_ui->encryptionButton->setVisible(contact->hasKeys(Shared::EncryptionProtocol::omemo2));
+                break;
         }
     }
 }
 
-void Chat::updateState()
-{
+void Chat::updateState() {
     Shared::Availability av = contact->getAvailability();
     statusIcon->setPixmap(Shared::availabilityIcon(av, true).pixmap(40));
     statusIcon->setToolTip(Shared::Global::getName(av));
 }
 
-Shared::Message Chat::createMessage() const
-{
+Shared::Message Chat::createMessage() const {
     Shared::Message msg = Conversation::createMessage();
     msg.setType(Shared::Message::chat);
     msg.setFrom(account->getFullJid());
@@ -71,14 +73,12 @@ Shared::Message Chat::createMessage() const
     return msg;
 }
 
-void Chat::onMessage(const Shared::Message& data)
-{
+void Chat::onMessage(const Shared::Message& data){
     Conversation::onMessage(data);
     
     if (!data.getOutgoing()) {
         const QString& res = data.getPenPalResource();
-        if (res.size() > 0) {
+        if (res.size() > 0)
             setPalResource(res);
-        }
     }
 }

From 81cf0f8d342776e9bb53ea67a475add4f15684ca Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Mon, 27 Mar 2023 21:45:29 +0300
Subject: [PATCH 248/281] transition to LMDBAL

---
 .gitmodules                    |  3 +++
 CMakeLists.txt                 | 24 ++++++++++++++++++------
 README.md                      |  4 +++-
 core/components/clientcache.h  | 11 ++++++++---
 core/handlers/omemohandler.cpp |  8 ++++----
 core/handlers/omemohandler.h   | 10 +++++-----
 core/handlers/trusthandler.cpp | 28 ++++++++++++++--------------
 core/handlers/trusthandler.h   |  8 ++++----
 external/lmdbal                |  1 +
 external/storage               |  1 -
 main/main.cpp                  |  1 +
 packaging/Archlinux/PKGBUILD   |  6 +++---
 shared/enums.h                 |  3 +++
 shared/global.cpp              |  1 +
 shared/trustsummary.cpp        |  2 ++
 ui/models/contact.cpp          |  1 +
 ui/models/contact.h            |  2 ++
 ui/widgets/chat.cpp            |  4 +++-
 18 files changed, 76 insertions(+), 42 deletions(-)
 create mode 160000 external/lmdbal
 delete mode 160000 external/storage

diff --git a/.gitmodules b/.gitmodules
index 098973a..448dae5 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -4,3 +4,6 @@
 [submodule "external/storage"]
 	path = external/storage
 	url = https://git.macaw.me/blue/storage
+[submodule "external/lmdbal"]
+	path = external/lmdbal
+	url = gitea@git.macaw.me:blue/lmdbal.git
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 01a9bd1..6cdb712 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -28,6 +28,7 @@ add_executable(squawk ${WIN32_FLAG} ${MACOSX_BUNDLE_FLAG})
 target_include_directories(squawk PRIVATE ${CMAKE_SOURCE_DIR})
 
 option(SYSTEM_QXMPP "Use system qxmpp lib" ON)
+option(SYSTEM_LMDBAL "Use system lmdbal lib" ON)
 option(WITH_KWALLET "Build KWallet support module" ON)
 option(WITH_KIO "Build KIO support module" ON)
 option(WITH_KCONFIG "Build KConfig support module" ON)
@@ -148,14 +149,25 @@ else ()
   target_link_libraries(squawk PRIVATE QXmpp::QXmpp)
 endif ()
 
-## LMDB
-#find_package(LMDB REQUIRED)
+## LMDBAL
+if (SYSTEM_LMDBAL)
+  find_package(lmdbal CONFIG)
+  if (NOT LMDBAL_FOUND)
+    set(SYSTEM_LMDBAL OFF)
+    message("LMDBAL package wasn't found, trying to build with bundled LMDBAL")
+  else ()
+    message("Building with system LMDBAL")
+  endif ()
+endif()
 
+if (NOT SYSTEM_LMDBAL)
+  message("Building with bundled LMDBAL")
+  set(BUILD_STATIC ON)
+  add_subdirectory(external/lmdbal)
+  add_library(LMDBAL::LMDBAL ALIAS LMDBAL)
+endif()
 
-#TODO conditioning!
-add_subdirectory(external/storage)
-target_include_directories(squawk PRIVATE external/storage)
-target_link_libraries(squawk PRIVATE storage)
+target_link_libraries(squawk PRIVATE LMDBAL::LMDBAL)
 
 # Linking
 target_link_libraries(squawk
diff --git a/README.md b/README.md
index 3e20568..121b640 100644
--- a/README.md
+++ b/README.md
@@ -9,9 +9,9 @@
 ### Prerequisites
 
 - QT 5.12 *(lower versions might work but it wasn't tested)*
-- lmdb
 - CMake 3.4 or higher
 - qxmpp 1.1.0 or higher
+- LMDBAL (my own [library](https://git.macaw.me/blue/lmdbal) around lmdb)
 - KDE Frameworks: kwallet (optional)
 - KDE Frameworks: KIO (optional)
 - KDE Frameworks: KConfig (optional)
@@ -92,9 +92,11 @@ You can always refer to `appveyor.yml` to see how AppVeyor build squawk.
 Here is the list of keys you can pass to configuration phase of `cmake ..`. 
 - `CMAKE_BUILD_TYPE` - `Debug` just builds showing all warnings, `Release` builds with no warnings and applies optimizations (default is `Debug`)
 - `SYSTEM_QXMPP` - `True` tries to link against `qxmpp` installed in the system, `False` builds bundled `qxmpp` library (default is `True`)
+- `SYSTEM_LMDBAL` - `True` tries to link against `LMDABL` installed in the system, `False` builds bundled `LMDBAL` library (default is `True`)
 - `WITH_KWALLET` - `True` builds the `KWallet` capability module if `KWallet` is installed and if not goes to `False`. `False` disables `KWallet` support (default is `True`)
 - `WITH_KIO` - `True` builds the `KIO` capability module if `KIO` is installed and if not goes to `False`. `False` disables `KIO` support (default is `True`)
 - `WITH_KCONFIG` - `True` builds the `KConfig` and `KConfigWidgets` capability module if such packages are installed and if not goes to `False`. `False` disables `KConfig` and `KConfigWidgets` support (default is `True`)
+- `WITH_OMEMO` - `True` builds the OMEMO encryption, requires `qxmpp` of version >= 1.5.0 built with OMEMO support. `False` disables OMEMO support (default is `True`)
 
 ## License
 
diff --git a/core/components/clientcache.h b/core/components/clientcache.h
index 6202af5..b9dba73 100644
--- a/core/components/clientcache.h
+++ b/core/components/clientcache.h
@@ -45,11 +45,16 @@ signals:
 
 public slots:
     bool checkClient(const Shared::ClientId& id);
-    bool registerClientInfo(const QString& sourceFullJid, const QString& id, const std::set<Shared::Identity>& identities, const std::set<QString>& features);
+    bool registerClientInfo(
+        const QString& sourceFullJid,
+        const QString& id,
+        const std::set<Shared::Identity>& identities,
+        const std::set<QString>& features
+    );
 
 private:
-    DataBase db;
-    DataBase::Cache<QString, Shared::ClientInfo>* cache;
+    LMDBAL::Base db;
+    LMDBAL::Cache<QString, Shared::ClientInfo>* cache;
     std::map<QString, Shared::ClientInfo> requested;
     std::map<QString, Shared::ClientInfo> specific;
 };
diff --git a/core/handlers/omemohandler.cpp b/core/handlers/omemohandler.cpp
index c8bcb17..35bca9e 100644
--- a/core/handlers/omemohandler.cpp
+++ b/core/handlers/omemohandler.cpp
@@ -35,7 +35,7 @@ Core::OmemoHandler::OmemoHandler(Account* account) :
         QVariant own = meta->getRecord("ownDevice");
         ownDevice = own.value<OwnDevice>();
         qDebug() << "Successfully found own device omemo data for account" << acc->getName();
-    } catch (const DataBase::NotFound& e) {
+    } catch (const LMDBAL::NotFound& e) {
         qDebug() << "No device omemo data was found for account" << acc->getName();
     }
 }
@@ -76,7 +76,7 @@ QXmppTask<void> Core::OmemoHandler::addDevice(const QString& jid, uint32_t devic
     bool had = true;
     try {
         devs = devices->getRecord(jid);
-    } catch (const DataBase::NotFound& error) {
+    } catch (const LMDBAL::NotFound& error) {
         had = false;
     }
 
@@ -127,7 +127,7 @@ QXmppTask<void> Core::OmemoHandler::removePreKeyPair(uint32_t keyId) {
 QXmppTask<void> Core::OmemoHandler::removeSignedPreKeyPair(uint32_t keyId) {
     try {
         signedPreKeyPairs->removeRecord(keyId);
-    } catch (const DataBase::NotFound& e) {}
+    } catch (const LMDBAL::NotFound& e) {}
     return Core::makeReadyTask();
 }
 
@@ -159,7 +159,7 @@ void Core::OmemoHandler::getDevices(const QString& jid, std::list<Shared::KeyInf
     QHash<uint32_t, Device> devs;
     try {
         devs = devices->getRecord(jid);
-    } catch (const DataBase::NotFound& error) {}
+    } catch (const LMDBAL::NotFound& error) {}
 
     for (QHash<uint32_t, Device>::const_iterator itr = devs.begin(), end = devs.end(); itr != end; ++itr) {
         const Device& dev = itr.value();
diff --git a/core/handlers/omemohandler.h b/core/handlers/omemohandler.h
index 7053450..1ea1f26 100644
--- a/core/handlers/omemohandler.h
+++ b/core/handlers/omemohandler.h
@@ -73,11 +73,11 @@ private slots:
 private:
     Account* acc;
     std::optional<OwnDevice> ownDevice;
-    DataBase db;
-    DataBase::Cache<QString, QVariant>* meta;
-    DataBase::Cache<QString, QHash<uint32_t, Device>>* devices;
-    DataBase::Cache<uint32_t, QByteArray>* preKeyPairs;
-    DataBase::Cache<uint32_t, SignedPreKeyPair>* signedPreKeyPairs;
+    LMDBAL::Base db;
+    LMDBAL::Cache<QString, QVariant>* meta;
+    LMDBAL::Cache<QString, QHash<uint32_t, Device>>* devices;
+    LMDBAL::Cache<uint32_t, QByteArray>* preKeyPairs;
+    LMDBAL::Cache<uint32_t, SignedPreKeyPair>* signedPreKeyPairs;
 };
 
 }
diff --git a/core/handlers/trusthandler.cpp b/core/handlers/trusthandler.cpp
index c866d6f..21b3491 100644
--- a/core/handlers/trusthandler.cpp
+++ b/core/handlers/trusthandler.cpp
@@ -29,7 +29,7 @@ Core::TrustHandler::TrustHandler(Account* account):
     keysByProtocol()
 {
     if (!protocols.open(QIODevice::ReadWrite | QIODevice::Text)) {               //never supposed to happen since I have just created a directory;
-        throw DataBase::Directory(protocols.fileName().toStdString());
+        throw LMDBAL::Directory(protocols.fileName().toStdString());
     }
 
     QTextStream in(&protocols);
@@ -66,7 +66,7 @@ Core::TrustHandler::KeyCache * Core::TrustHandler::createNewCache(const QString&
     keysByProtocol.insert(std::make_pair(encryption, cache));
 
     if(!protocols.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text)) {
-        throw DataBase::Directory(protocols.fileName().toStdString());
+        throw LMDBAL::Directory(protocols.fileName().toStdString());
     }
     QTextStream out(&protocols);
     out << encryption + "\n";
@@ -114,7 +114,7 @@ QXmppTask<QXmpp::TrustLevel> Core::TrustHandler::trustLevel(
         Keys::const_iterator itr = map.find(keyId);
         if (itr != map.end())
             level = itr->second;
-    } catch (const DataBase::NotFound& e) {}
+    } catch (const LMDBAL::NotFound& e) {}
     return Core::makeReadyTask(std::move(convert(level)));
 }
 
@@ -176,7 +176,7 @@ QXmppTask<QHash<QString, QMultiHash<QString, QByteArray>>> Core::TrustHandler::s
                 modifiedJids.insert(keyOwnerJid);
                 cache->changeRecord(keyOwnerJid, map);
             }
-        } catch (const DataBase::NotFound& e) {
+        } catch (const LMDBAL::NotFound& e) {
             Keys map({{keyId, level}});
             modifiedKeys[encryption].insert(keyOwnerJid, keyId);
             modifiedJids.insert(keyOwnerJid);
@@ -204,7 +204,7 @@ QXmppTask<bool> Core::TrustHandler::hasKey(const QString& encryption,
                 break;
             }
         }
-    } catch (const DataBase::NotFound& e) {}
+    } catch (const LMDBAL::NotFound& e) {}
     return Core::makeReadyTask(std::move(found));
 }
 
@@ -226,7 +226,7 @@ QXmppTask<QHash<QString, QHash<QByteArray, QXmpp::TrustLevel>>> Core::TrustHandl
                     pRes.insert(pair.first, level);
                 }
             }
-        } catch (const DataBase::NotFound& e) {}
+        } catch (const LMDBAL::NotFound& e) {}
     }
     return Core::makeReadyTask(std::move(res));
 }
@@ -281,7 +281,7 @@ QXmppTask<void> Core::TrustHandler::removeKeys(const QString& encryption, const
     try {
         cache->removeRecord(keyOwnerJid);
         emit trustLevelsChanged(keyOwnerJid, getSummary(keyOwnerJid));      //TODO there is a probability of notification without the actial change
-    } catch (const DataBase::NotFound& e) {}                                //if the movin entry was empty or if it consisted of Undecided keys
+    } catch (const LMDBAL::NotFound& e) {}                                  //if the movin entry was empty or if it consisted of Undecided keys
 
 
     return Core::makeReadyTask();
@@ -334,7 +334,7 @@ QXmppTask<void> Core::TrustHandler::addKeys(
     try {
         data = cache->getRecord(keyOwnerJid);
         had = true;
-    } catch (const DataBase::NotFound& e) {}
+    } catch (const LMDBAL::NotFound& e) {}
     for (const QByteArray& keyId : keyIds) {
         std::pair<Keys::iterator, bool> result = data.insert(std::make_pair(keyId, level));
         if (!result.second)
@@ -355,14 +355,14 @@ QXmppTask<QByteArray> Core::TrustHandler::ownKey(const QString& encryption) {
     QByteArray res;
     try {
         res = ownKeys->getRecord(encryption);
-    } catch (const DataBase::NotFound& e) {}
+    } catch (const LMDBAL::NotFound& e) {}
     return Core::makeReadyTask(std::move(res));
 }
 
 QXmppTask<void> Core::TrustHandler::resetOwnKey(const QString& encryption) {
     try {
         ownKeys->removeRecord(encryption);
-    } catch (const DataBase::NotFound& e) {}
+    } catch (const LMDBAL::NotFound& e) {}
 
     return Core::makeReadyTask();
 }
@@ -376,14 +376,14 @@ QXmppTask<QXmpp::TrustSecurityPolicy> Core::TrustHandler::securityPolicy(const Q
     QXmpp::TrustSecurityPolicy res;
     try {
         res = static_cast<QXmpp::TrustSecurityPolicy>(securityPolicies->getRecord(encryption));
-    } catch (const DataBase::NotFound& e) {}
+    } catch (const LMDBAL::NotFound& e) {}
     return Core::makeReadyTask(std::move(res));
 }
 
 QXmppTask<void> Core::TrustHandler::resetSecurityPolicy(const QString& encryption) {
     try {
         securityPolicies->removeRecord(encryption);
-    } catch (const DataBase::NotFound& e) {}
+    } catch (const LMDBAL::NotFound& e) {}
     return Core::makeReadyTask();
 }
 
@@ -404,7 +404,7 @@ Core::TrustHandler::Keys Core::TrustHandler::getKeys(Shared::EncryptionProtocol
         try {
             Keys map = itr->second->getRecord(jid);
             return map;
-        } catch (const DataBase::NotFound& e) {
+        } catch (const LMDBAL::NotFound& e) {
             return Keys();
         }
     } else {
@@ -421,7 +421,7 @@ Shared::TrustSummary Core::TrustHandler::getSummary(const QString& jid) const {
             for (const std::pair<const QByteArray, Shared::TrustLevel>& trust : keys) {
                 result.increment(protocol, trust.second);
             }
-        } catch (const DataBase::NotFound& e) {}
+        } catch (const LMDBAL::NotFound& e) {}
     }
 
     return result;
diff --git a/core/handlers/trusthandler.h b/core/handlers/trusthandler.h
index 98d7f1e..68a4aa1 100644
--- a/core/handlers/trusthandler.h
+++ b/core/handlers/trusthandler.h
@@ -45,7 +45,7 @@ public:
     typedef QHash<QString, QHash<QByteArray, TL>> HSHBTL;
 
     typedef std::map<QByteArray, Shared::TrustLevel> Keys;
-    typedef DataBase::Cache<QString, Keys> KeyCache;
+    typedef LMDBAL::Cache<QString, Keys> KeyCache;
 
     virtual QXmppTask<void> resetAll(CSR encryption) override;
     virtual QXmppTask<TL> trustLevel(CSR encryption, CSR keyOwnerJid, const QByteArray& keyId) override;
@@ -77,10 +77,10 @@ private:
 
 private:
     Account* acc;
-    DataBase db;
+    LMDBAL::Base db;
     QFile protocols;
-    DataBase::Cache<QString, uint8_t>* securityPolicies;
-    DataBase::Cache<QString, QByteArray>* ownKeys;
+    LMDBAL::Cache<QString, uint8_t>* securityPolicies;
+    LMDBAL::Cache<QString, QByteArray>* ownKeys;
     std::map<QString, KeyCache*> keysByProtocol;
 };
 
diff --git a/external/lmdbal b/external/lmdbal
new file mode 160000
index 0000000..c83369f
--- /dev/null
+++ b/external/lmdbal
@@ -0,0 +1 @@
+Subproject commit c83369f34761e7a053d62312bd07fe5b3db3a519
diff --git a/external/storage b/external/storage
deleted file mode 160000
index 6a8f67a..0000000
--- a/external/storage
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 6a8f67ac34de286588cd89e3218f55b54da47f42
diff --git a/main/main.cpp b/main/main.cpp
index afe1cf2..ce614b3 100644
--- a/main/main.cpp
+++ b/main/main.cpp
@@ -40,6 +40,7 @@ int main(int argc, char *argv[])
     qRegisterMetaType<QSet<QString>>("QSet<QString>");
     qRegisterMetaType<Shared::ConnectionState>("Shared::ConnectionState");
     qRegisterMetaType<Shared::Availability>("Shared::Availability");
+    qRegisterMetaType<Shared::EncryptionProtocol>("Shared::EncryptionProtocol");
     qRegisterMetaType<Shared::KeyInfo>("Shared::KeyInfo");
     qRegisterMetaType<Shared::Info>("Shared::Info");
 #ifdef WITH_OMEMO
diff --git a/packaging/Archlinux/PKGBUILD b/packaging/Archlinux/PKGBUILD
index 7db43ff..6e63901 100644
--- a/packaging/Archlinux/PKGBUILD
+++ b/packaging/Archlinux/PKGBUILD
@@ -6,7 +6,7 @@ pkgdesc="An XMPP desktop messenger, written on pure c++ (qt)"
 arch=('i686' 'x86_64')
 url="https://git.macaw.me/blue/squawk"
 license=('GPL3')
-depends=('hicolor-icon-theme' 'desktop-file-utils' 'lmdb' 'qxmpp>=1.1.0')
+depends=('hicolor-icon-theme' 'desktop-file-utils' 'lmdbal' 'qxmpp>=1.1.0')
 makedepends=('cmake>=3.3' 'imagemagick' 'qt5-tools' 'boost')
 optdepends=('kwallet: secure password storage (requires rebuild)'
             'kconfig: system themes support (requires rebuild)'
@@ -18,9 +18,9 @@ sha256sums=('e4fa2174a3ba95159cc3b0bac3f00550c9e0ce971c55334e2662696a4543fc7e')
 build() {
         cd "$srcdir/squawk"
         cmake . -D CMAKE_INSTALL_PREFIX=/usr -D CMAKE_BUILD_TYPE=Release
-        cmake --build . -j $nproc
+        cmake --build .
 }
 package() {
         cd "$srcdir/squawk"
-        DESTDIR="$pkgdir/" cmake --build . --target install
+        DESTDIR="$pkgdir/" cmake --install .
 }
diff --git a/shared/enums.h b/shared/enums.h
index aff22b9..c959883 100644
--- a/shared/enums.h
+++ b/shared/enums.h
@@ -160,11 +160,14 @@ static const TrustLevel TrustLevelHighest = TrustLevel::undecided;
 static const TrustLevel TrustLevelLowest = TrustLevel::authenticated;
 
 enum class EncryptionProtocol {
+    none,
     omemo,
     omemo1,
     omemo2
 };
 Q_ENUM_NS(EncryptionProtocol)
+static const EncryptionProtocol EncryptionProtocolHighest = EncryptionProtocol::none;
+static const EncryptionProtocol EncryptionProtocolLowest = EncryptionProtocol::omemo2;
 
 }
 #endif // SHARED_ENUMS_H
diff --git a/shared/global.cpp b/shared/global.cpp
index 1b5763b..95168b2 100644
--- a/shared/global.cpp
+++ b/shared/global.cpp
@@ -405,3 +405,4 @@ FROM_INT_INPL(Shared::AccountPassword)
 FROM_INT_INPL(Shared::Avatar)
 FROM_INT_INPL(Shared::Availability)
 FROM_INT_INPL(Shared::TrustLevel)
+FROM_INT_INPL(Shared::EncryptionProtocol)
diff --git a/shared/trustsummary.cpp b/shared/trustsummary.cpp
index eae98bf..47abb16 100644
--- a/shared/trustsummary.cpp
+++ b/shared/trustsummary.cpp
@@ -28,11 +28,13 @@ const std::set<Shared::TrustLevel> Shared::TrustSummary::untrustedLevels({
 });
 
 const std::map<Shared::EncryptionProtocol, QString> Shared::TrustSummary::protocolKeys({
+    {Shared::EncryptionProtocol::none, "none"},
     {Shared::EncryptionProtocol::omemo, "eu.siacs.conversations.axolotl"},
     {Shared::EncryptionProtocol::omemo1, "urn:xmpp:omemo:1"},
     {Shared::EncryptionProtocol::omemo2, "urn:xmpp:omemo:2"}
 });
 const std::map<QString, Shared::EncryptionProtocol> Shared::TrustSummary::protocolValues({
+    {"none", Shared::EncryptionProtocol::none},
     {"eu.siacs.conversations.axolotl", Shared::EncryptionProtocol::omemo},
     {"urn:xmpp:omemo:1", Shared::EncryptionProtocol::omemo1},
     {"urn:xmpp:omemo:2", Shared::EncryptionProtocol::omemo2}
diff --git a/ui/models/contact.cpp b/ui/models/contact.cpp
index 8d6def9..c27965b 100644
--- a/ui/models/contact.cpp
+++ b/ui/models/contact.cpp
@@ -24,6 +24,7 @@ Models::Contact::Contact(const Account* acc, const QString& p_jid ,const QMap<QS
     Element(Item::contact, acc, p_jid, data, parentItem),
     availability(Shared::Availability::offline),
     state(Shared::SubscriptionState::none),
+    encryption(Shared::EncryptionProtocol::none),
     trust(),
     presences(),
     status()
diff --git a/ui/models/contact.h b/ui/models/contact.h
index e7d767d..dbf33d4 100644
--- a/ui/models/contact.h
+++ b/ui/models/contact.h
@@ -82,9 +82,11 @@ protected:
 private:
     Shared::Availability availability;
     Shared::SubscriptionState state;
+    Shared::EncryptionProtocol encryption;
     Shared::TrustSummary trust;
     QMap<QString, Presence*> presences;
     QString status;
+
 };
 
 }
diff --git a/ui/widgets/chat.cpp b/ui/widgets/chat.cpp
index 607fab4..301553b 100644
--- a/ui/widgets/chat.cpp
+++ b/ui/widgets/chat.cpp
@@ -29,8 +29,10 @@ Chat::Chat(Models::Account* acc, Models::Contact* p_contact, QWidget* parent):
     setAvatar(p_contact->getAvatarPath());
     
     connect(contact, &Models::Contact::childChanged, this, &Chat::onContactChanged);
-    if (p_contact->hasKeys(Shared::EncryptionProtocol::omemo2))
+    if (p_contact->hasKeys(Shared::EncryptionProtocol::omemo2)) {
         m_ui->encryptionButton->setVisible(true);
+        //if ()
+    }
 }
 
 Chat::~Chat()

From 5fbb03fc4604279c6836c7baa8ca837b8a306db0 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Sat, 15 Apr 2023 15:07:27 -0300
Subject: [PATCH 249/281] transitioned urlstorage to LMDBAL, made it possible
 to build against latest qxmpp

---
 CMakeLists.txt                            |  10 +-
 core/components/CMakeLists.txt            |   2 +
 core/components/networkaccess.cpp         | 126 +++---
 core/components/networkaccess.h           |   6 +-
 core/components/urlstorage.cpp            | 289 +++++++++++++
 core/{storage => components}/urlstorage.h |  28 +-
 core/storage/CMakeLists.txt               |   6 -
 core/storage/urlstorage.cpp               | 491 ----------------------
 external/qxmpp                            |   2 +-
 main/main.cpp                             |   2 +-
 10 files changed, 363 insertions(+), 599 deletions(-)
 create mode 100644 core/components/urlstorage.cpp
 rename core/{storage => components}/urlstorage.h (90%)
 delete mode 100644 core/storage/urlstorage.cpp

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 6cdb712..06c4344 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -143,16 +143,18 @@ if (NOT SYSTEM_QXMPP)
   endif ()
   add_subdirectory(external/qxmpp)
 
-  target_link_libraries(squawk PRIVATE qxmpp)
-  target_link_libraries(squawk PRIVATE QXmppOmemo)
+  target_link_libraries(squawk PRIVATE QXmppQt${QT_VERSION_MAJOR})
+  if (WITH_OMEMO)
+    target_link_libraries(squawk PRIVATE QXmppOmemoQt${QT_VERSION_MAJOR})
+  endif ()
 else ()
   target_link_libraries(squawk PRIVATE QXmpp::QXmpp)
 endif ()
 
 ## LMDBAL
 if (SYSTEM_LMDBAL)
-  find_package(lmdbal CONFIG)
-  if (NOT LMDBAL_FOUND)
+  find_package(lmdbal)
+  if (NOT lmdbal_FOUND)
     set(SYSTEM_LMDBAL OFF)
     message("LMDBAL package wasn't found, trying to build with bundled LMDBAL")
   else ()
diff --git a/core/components/CMakeLists.txt b/core/components/CMakeLists.txt
index b78794a..86d9fb8 100644
--- a/core/components/CMakeLists.txt
+++ b/core/components/CMakeLists.txt
@@ -1,11 +1,13 @@
 set(SOURCE_FILES
     networkaccess.cpp
     clientcache.cpp
+    urlstorage.cpp
 )
 
 set(HEADER_FILES
     networkaccess.h
     clientcache.h
+    urlstorage.h
 )
 
 target_sources(squawk PRIVATE
diff --git a/core/components/networkaccess.cpp b/core/components/networkaccess.cpp
index 22bb7a2..862c664 100644
--- a/core/components/networkaccess.cpp
+++ b/core/components/networkaccess.cpp
@@ -35,13 +35,11 @@ Core::NetworkAccess::NetworkAccess(QObject* parent):
     currentPath = settings.value("downloadsPath").toString();
 }
 
-Core::NetworkAccess::~NetworkAccess()
-{
+Core::NetworkAccess::~NetworkAccess() {
     stop();
 }
 
-void Core::NetworkAccess::downladFile(const QString& url)
-{
+void Core::NetworkAccess::downladFile(const QString& url) {
     std::map<QString, Transfer*>::iterator itr = downloads.find(url);
     if (itr != downloads.end()) {
         qDebug() << "NetworkAccess received a request to download a file" << url << ", but the file is currently downloading, skipping";
@@ -50,27 +48,25 @@ void Core::NetworkAccess::downladFile(const QString& url)
             std::pair<QString, std::list<Shared::MessageInfo>> p = storage.getPath(url);
             if (p.first.size() > 0) {
                 QFileInfo info(p.first);
-                if (info.exists() && info.isFile()) {
+                if (info.exists() && info.isFile())
                     emit downloadFileComplete(p.second, p.first);
-                } else {
+                else
                     startDownload(p.second, url);
-                }
             } else {
                 startDownload(p.second, url);
             }
-        } catch (const Archive::NotFound& e) {
+        } catch (const LMDBAL::NotFound& e) {
             qDebug() << "NetworkAccess received a request to download a file" << url << ", but there is now record of which message uses that file, downloading anyway";
             storage.addFile(url);
             startDownload(std::list<Shared::MessageInfo>(), url);
-        } catch (const Archive::Unknown& e) {
+        } catch (const LMDBAL::Unknown& e) {
             qDebug() << "Error requesting file path:" << e.what();
             emit loadFileError(std::list<Shared::MessageInfo>(), QString("Database error: ") + e.what(), false);
         }
     }
 }
 
-void Core::NetworkAccess::start()
-{
+void Core::NetworkAccess::start() {
     if (!running) {
         manager = new QNetworkAccessManager();
 #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
@@ -81,8 +77,7 @@ void Core::NetworkAccess::start()
     }
 }
 
-void Core::NetworkAccess::stop()
-{
+void Core::NetworkAccess::stop() {
     if (running) {
         storage.close();
         manager->deleteLater();
@@ -96,8 +91,7 @@ void Core::NetworkAccess::stop()
     }
 }
 
-void Core::NetworkAccess::onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal)
-{
+void Core::NetworkAccess::onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal) {
     QNetworkReply* rpl = static_cast<QNetworkReply*>(sender());
     QString url = rpl->url().toString();
     std::map<QString, Transfer*>::const_iterator itr = downloads.find(url);
@@ -115,8 +109,7 @@ void Core::NetworkAccess::onDownloadProgress(qint64 bytesReceived, qint64 bytesT
     }
 }
 
-void Core::NetworkAccess::onDownloadError(QNetworkReply::NetworkError code)
-{
+void Core::NetworkAccess::onDownloadError(QNetworkReply::NetworkError code) {
     qDebug() << "DEBUG: DOWNLOAD ERROR";
     QNetworkReply* rpl = static_cast<QNetworkReply*>(sender());
     qDebug() << rpl->errorString();
@@ -134,8 +127,7 @@ void Core::NetworkAccess::onDownloadError(QNetworkReply::NetworkError code)
     }
 }
 
-void Core::NetworkAccess::onDownloadSSLError(const QList<QSslError>& errors)
-{
+void Core::NetworkAccess::onDownloadSSLError(const QList<QSslError>& errors) {
     qDebug() << "DEBUG: DOWNLOAD SSL ERRORS";
     for (const QSslError& err : errors) {
         qDebug() << err.errorString();
@@ -154,9 +146,7 @@ void Core::NetworkAccess::onDownloadSSLError(const QList<QSslError>& errors)
     }
 }
 
-
-QString Core::NetworkAccess::getErrorText(QNetworkReply::NetworkError code)
-{
+QString Core::NetworkAccess::getErrorText(QNetworkReply::NetworkError code) {
     QString errorText("");
     switch (code) {
         case QNetworkReply::NoError:
@@ -280,8 +270,7 @@ QString Core::NetworkAccess::getErrorText(QNetworkReply::NetworkError code)
 }
 
 
-void Core::NetworkAccess::onDownloadFinished()
-{
+void Core::NetworkAccess::onDownloadFinished() {
     qDebug() << "DEBUG: DOWNLOAD FINISHED";
     QNetworkReply* rpl = static_cast<QNetworkReply*>(sender());
     QString url = rpl->url().toString();
@@ -296,11 +285,11 @@ void Core::NetworkAccess::onDownloadFinished()
             QStringList hops = url.split("/");
             QString fileName = hops.back();
             QString jid;
-            if (dwn->messages.size() > 0) {
+            if (dwn->messages.size() > 0)
                 jid = dwn->messages.front().jid;
-            } else {
+            else
                 qDebug() << "An attempt to save the file but it doesn't seem to belong to any message, download is definately going to be broken";
-            }
+
             QString path = prepareDirectory(jid);
             if (path.size() > 0) {
                 path = checkFileName(fileName, path);
@@ -319,11 +308,10 @@ void Core::NetworkAccess::onDownloadFinished()
                 err = "Couldn't prepare a directory for file";
             }
             
-            if (path.size() > 0) {
+            if (path.size() > 0)
                 emit downloadFileComplete(dwn->messages, path);
-            } else {
+            else
                 emit loadFileError(dwn->messages, "Error saving file " + url + "; " + err, false);
-            }
         }
         
         dwn->reply->deleteLater();
@@ -332,8 +320,7 @@ void Core::NetworkAccess::onDownloadFinished()
     }
 }
 
-void Core::NetworkAccess::startDownload(const std::list<Shared::MessageInfo>& msgs, const QString& url)
-{
+void Core::NetworkAccess::startDownload(const std::list<Shared::MessageInfo>& msgs, const QString& url) {
     Transfer* dwn = new Transfer({msgs, 0, 0, true, "", url, 0});
     QNetworkRequest req(url);
     dwn->reply = manager->get(req);
@@ -349,8 +336,7 @@ void Core::NetworkAccess::startDownload(const std::list<Shared::MessageInfo>& ms
     emit loadFileProgress(dwn->messages, 0, false);
 }
 
-void Core::NetworkAccess::onUploadError(QNetworkReply::NetworkError code)
-{
+void Core::NetworkAccess::onUploadError(QNetworkReply::NetworkError code) {
     QNetworkReply* rpl = static_cast<QNetworkReply*>(sender());
     QString url = rpl->url().toString();
     std::map<QString, Transfer*>::const_iterator itr = uploads.find(url);
@@ -368,8 +354,7 @@ void Core::NetworkAccess::onUploadError(QNetworkReply::NetworkError code)
     }
 }
 
-void Core::NetworkAccess::onUploadFinished()
-{
+void Core::NetworkAccess::onUploadFinished() {
     QNetworkReply* rpl = static_cast<QNetworkReply*>(sender());
     QString url = rpl->url().toString();
     std::map<QString, Transfer*>::const_iterator itr = uploads.find(url);
@@ -389,20 +374,16 @@ void Core::NetworkAccess::onUploadFinished()
 
                     // Copy {TEMPDIR}/squawk_img_attach_XXXXXX.png to Download folder
                     bool copyResult = QFile::copy(upl->path, Shared::resolvePath(newPath));
-
-                    if (copyResult) {
-                        // Change storage
-                        upl->path = newPath;
-                    } else {
+                    if (copyResult)
+                        upl->path = newPath;  // Change storage
+                    else
                         err = "copying to " + newPath + " failed";
-                    }
                 } else {
                     err = "Couldn't prepare a directory for file";
                 }
 
-                if (err.size() != 0) {
+                if (err.size() != 0)
                     qDebug() << "failed to copy temporary upload file " << upl->path << " to download folder:" << err;
-                }
             }
 
             storage.addFile(upl->messages, upl->url, upl->path);
@@ -417,8 +398,7 @@ void Core::NetworkAccess::onUploadFinished()
     }
 }
 
-void Core::NetworkAccess::onUploadProgress(qint64 bytesReceived, qint64 bytesTotal)
-{
+void Core::NetworkAccess::onUploadProgress(qint64 bytesReceived, qint64 bytesTotal) {
     QNetworkReply* rpl = static_cast<QNetworkReply*>(sender());
     QString url = rpl->url().toString();
     std::map<QString, Transfer*>::const_iterator itr = uploads.find(url);
@@ -436,13 +416,12 @@ void Core::NetworkAccess::onUploadProgress(qint64 bytesReceived, qint64 bytesTot
     }
 }
 
-QString Core::NetworkAccess::getFileRemoteUrl(const QString& path)
-{
+QString Core::NetworkAccess::getFileRemoteUrl(const QString& path) {
     QString p = Shared::squawkifyPath(path);
     
     try {
         p = storage.getUrl(p);
-    } catch (const Archive::NotFound& err) {
+    } catch (const LMDBAL::NotFound& err) {
         p = "";
     } catch (...) {
         throw;
@@ -451,8 +430,13 @@ QString Core::NetworkAccess::getFileRemoteUrl(const QString& path)
     return p;
 }
 
-void Core::NetworkAccess::uploadFile(const Shared::MessageInfo& info, const QString& path, const QUrl& put, const QUrl& get, const QMap<QString, QString> headers)
-{
+void Core::NetworkAccess::uploadFile(
+    const Shared::MessageInfo& info,
+    const QString& path,
+    const QUrl& put,
+    const QUrl& get,
+    const QMap<QString, QString> headers
+) {
     QFile* file = new QFile(path);
     Transfer* upl = new Transfer({{info}, 0, 0, true, path, get.toString(), file});
     QNetworkRequest req(put);
@@ -479,22 +463,18 @@ void Core::NetworkAccess::uploadFile(const Shared::MessageInfo& info, const QStr
     }
 }
 
-void Core::NetworkAccess::registerFile(const QString& url, const QString& account, const QString& jid, const QString& id)
-{
+void Core::NetworkAccess::registerFile(const QString& url, const QString& account, const QString& jid, const QString& id) {
     storage.addFile(url, account, jid, id);
     std::map<QString, Transfer*>::iterator itr = downloads.find(url);
-    if (itr != downloads.end()) {
+    if (itr != downloads.end())
         itr->second->messages.emplace_back(account, jid, id);   //TODO notification is going to happen the next tick, is that okay?
-    }
 }
 
-void Core::NetworkAccess::registerFile(const QString& url, const QString& path, const QString& account, const QString& jid, const QString& id)
-{
+void Core::NetworkAccess::registerFile(const QString& url, const QString& path, const QString& account, const QString& jid, const QString& id) {
     storage.addFile(url, path, account, jid, id);
 }
 
-bool Core::NetworkAccess::checkAndAddToUploading(const QString& acc, const QString& jid, const QString id, const QString path)
-{
+bool Core::NetworkAccess::checkAndAddToUploading(const QString& acc, const QString& jid, const QString id, const QString path) {
     for (const std::pair<const QString, Transfer*>& pair : uploads) {
         Transfer* info = pair.second;
         if (pair.second->path == path) {
@@ -516,8 +496,7 @@ bool Core::NetworkAccess::checkAndAddToUploading(const QString& acc, const QStri
     return false;
 }
 
-QString Core::NetworkAccess::prepareDirectory(const QString& jid)
-{
+QString Core::NetworkAccess::prepareDirectory(const QString& jid) {
     QString path = currentPath;
     QString addition;
     if (jid.size() > 0) {
@@ -529,25 +508,23 @@ QString Core::NetworkAccess::prepareDirectory(const QString& jid)
     
     if (!location.exists()) {
         bool res = location.mkpath(path);
-        if (!res) {
+        if (!res)
             return "";
-        } else {
+        else
             return "squawk://" + addition;
-        }
     }
     return "squawk://" + addition;
 }
 
-QString Core::NetworkAccess::checkFileName(const QString& name, const QString& path)
-{
+QString Core::NetworkAccess::checkFileName(const QString& name, const QString& path) {
     QStringList parts = name.split(".");
     QString suffix("");
     QStringList::const_iterator sItr = parts.begin();
     QString realName = *sItr;
     ++sItr;
-    for (QStringList::const_iterator sEnd = parts.end(); sItr != sEnd; ++sItr) {
+    for (QStringList::const_iterator sEnd = parts.end(); sItr != sEnd; ++sItr)
         suffix += "." + (*sItr);
-    }
+
     QString postfix("");
     QString resolvedPath = Shared::resolvePath(path);
     QString count("");
@@ -562,18 +539,15 @@ QString Core::NetworkAccess::checkFileName(const QString& name, const QString& p
     return path + QDir::separator() + realName + count + suffix;
 }
 
-QString Core::NetworkAccess::addMessageAndCheckForPath(const QString& url, const QString& account, const QString& jid, const QString& id)
-{
+QString Core::NetworkAccess::addMessageAndCheckForPath(const QString& url, const QString& account, const QString& jid, const QString& id) {
     return storage.addMessageAndCheckForPath(url, account, jid, id);
 }
 
-std::list<Shared::MessageInfo> Core::NetworkAccess::reportPathInvalid(const QString& path)
-{
+std::list<Shared::MessageInfo> Core::NetworkAccess::reportPathInvalid(const QString& path) {
     return storage.deletedFile(path);
 }
 
-void Core::NetworkAccess::moveFilesDirectory(const QString& newPath)
-{
+void Core::NetworkAccess::moveFilesDirectory(const QString& newPath) {
     QDir dir(currentPath);
     bool success = true;
     qDebug() << "moving" << currentPath << "to" << newPath;
@@ -582,8 +556,8 @@ void Core::NetworkAccess::moveFilesDirectory(const QString& newPath)
         success = dir.rename(fileName, newPath + QDir::separator() + fileName) && success;
     }
 
-    if (!success) {
+    if (!success)
         qDebug() << "couldn't move downloads directory, most probably downloads will be broken";
-    }
+
     currentPath = newPath;
 }
diff --git a/core/components/networkaccess.h b/core/components/networkaccess.h
index c94c22a..99a15ed 100644
--- a/core/components/networkaccess.h
+++ b/core/components/networkaccess.h
@@ -30,15 +30,11 @@
 
 #include <set>
 
-#include <core/storage/urlstorage.h>
 #include <shared/pathcheck.h>
+#include "urlstorage.h"
 
 namespace Core {
 
-/**
- * @todo write docs
- */
-
 //TODO Need to describe how to get rid of records when file is no longer reachable;
 class NetworkAccess : public QObject
 {
diff --git a/core/components/urlstorage.cpp b/core/components/urlstorage.cpp
new file mode 100644
index 0000000..c745236
--- /dev/null
+++ b/core/components/urlstorage.cpp
@@ -0,0 +1,289 @@
+/*
+ * Squawk messenger. 
+ * Copyright (C) 2019 Yury Gubich <blue@macaw.me>
+ * 
+ * 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 <QStandardPaths>
+#include <QDir>
+#include <QDebug>
+
+#include "urlstorage.h"
+
+Core::UrlStorage::UrlStorage(const QString& p_name):
+    base(p_name),
+    urlToInfo(base.addStorage<QString, UrlInfo>("urlToInfo")),
+    pathToUrl(base.addStorage<QString, QString>("pathToUrl"))
+{}
+
+Core::UrlStorage::~UrlStorage() {
+    close();
+}
+
+void Core::UrlStorage::open() {
+    base.open();
+}
+
+void Core::UrlStorage::close() {
+    base.close();
+}
+
+void Core::UrlStorage::writeInfo(const QString& key, const Core::UrlStorage::UrlInfo& info, bool overwrite) {
+    LMDBAL::TransactionID txn = base.beginTransaction();
+    
+    try {
+        writeInfo(key, info, txn, overwrite);
+        base.commitTransaction(txn);
+    } catch (...) {
+        base.abortTransaction(txn);
+        throw;
+    }
+}
+
+void Core::UrlStorage::writeInfo(const QString& key, const Core::UrlStorage::UrlInfo& info, MDB_txn* txn, bool overwrite) {
+    if (overwrite)
+        urlToInfo->forceRecord(key, info, txn);
+    else
+        urlToInfo->addRecord(key, info, txn);
+    
+    if (info.hasPath())
+        pathToUrl->forceRecord(info.getPath(), key, txn);
+}
+
+void Core::UrlStorage::addFile(const QString& url) {
+    addToInfo(url, "", "", "");
+}
+
+void Core::UrlStorage::addFile(const QString& url, const QString& path) {
+    addToInfo(url, "", "", "", path);
+}
+
+void Core::UrlStorage::addFile(const QString& url, const QString& account, const QString& jid, const QString& id) {
+    addToInfo(url, account, jid, id);
+}
+
+void Core::UrlStorage::addFile(const QString& url, const QString& path, const QString& account, const QString& jid, const QString& id) {
+    addToInfo(url, account, jid, id, path);
+}
+
+void Core::UrlStorage::addFile(const std::list<Shared::MessageInfo>& msgs, const QString& url, const QString& path) {
+    UrlInfo info (path, msgs);
+    writeInfo(url, info, true);
+}
+
+QString Core::UrlStorage::addMessageAndCheckForPath(const QString& url, const QString& account, const QString& jid, const QString& id){
+    return addToInfo(url, account, jid, id).getPath();
+}
+
+Core::UrlStorage::UrlInfo Core::UrlStorage::addToInfo(
+    const QString& url,
+    const QString& account,
+    const QString& jid,
+    const QString& id,
+    const QString& path
+) {
+    UrlInfo info;
+    LMDBAL::TransactionID txn = base.beginTransaction();
+    
+    try {
+        urlToInfo->getRecord(url, info, txn);
+    } catch (const LMDBAL::NotFound& e) {
+    } catch (...) {
+        base.abortTransaction(txn);
+        throw;
+    }
+    
+    bool pathChange = false;
+    bool listChange = false;
+    if (path != "-s") {
+        if (info.getPath() != path) {
+            info.setPath(path);
+            pathChange = true;
+        }
+    }
+    
+    if (account.size() > 0 && jid.size() > 0 && id.size() > 0)
+        listChange = info.addMessage(account, jid, id);
+
+    if (pathChange || listChange) {
+        try {
+            writeInfo(url, info, txn, true);
+            base.commitTransaction(txn);
+        } catch (...) {
+            base.abortTransaction(txn);
+            throw;
+        }
+    } else {
+        base.abortTransaction(txn);
+    }
+    
+    return info;
+}
+
+std::list<Shared::MessageInfo> Core::UrlStorage::setPath(const QString& url, const QString& path) {
+    std::list<Shared::MessageInfo> list;
+    LMDBAL::TransactionID txn = base.beginTransaction();
+    UrlInfo info;
+    
+    try {
+        urlToInfo->getRecord(url, info, txn);
+        info.getMessages(list);
+    } catch (const LMDBAL::NotFound& e) {
+    } catch (...) {
+        base.abortTransaction(txn);
+        throw;
+    }
+    
+    info.setPath(path);
+    try {
+        writeInfo(url, info, txn, true);
+        base.commitTransaction(txn);
+    } catch (...) {
+        base.abortTransaction(txn);
+        throw;
+    }
+    
+    return list;
+}
+
+std::list<Shared::MessageInfo> Core::UrlStorage::removeFile(const QString& url) {
+    std::list<Shared::MessageInfo> list;
+    LMDBAL::TransactionID txn = base.beginTransaction();
+    UrlInfo info;
+    
+    try {
+        urlToInfo->getRecord(url, info, txn);
+        urlToInfo->removeRecord(url);
+        info.getMessages(list);
+        
+        if (info.hasPath())
+            pathToUrl->removeRecord(info.getPath());
+
+        base.commitTransaction(txn);
+    } catch (...) {
+        base.abortTransaction(txn);
+        throw;
+    }
+    
+    return list;
+}
+
+std::list<Shared::MessageInfo> Core::UrlStorage::deletedFile(const QString& path) {
+    std::list<Shared::MessageInfo> list;
+    LMDBAL::TransactionID txn = base.beginTransaction();
+    
+    try {
+        QString url = pathToUrl->getRecord(path, txn);
+        pathToUrl->removeRecord(path);
+        
+        UrlInfo info = urlToInfo->getRecord(url, txn);
+        info.getMessages(list);
+        info.setPath(QString());
+        urlToInfo->changeRecord(url, info, txn);
+        
+        base.commitTransaction(txn);
+    } catch (...) {
+        base.abortTransaction(txn);
+        throw;
+    }
+    
+    return list;
+}
+
+QString Core::UrlStorage::getUrl(const QString& path) {
+    return pathToUrl->getRecord(path);
+}
+
+std::pair<QString, std::list<Shared::MessageInfo>> Core::UrlStorage::getPath(const QString& url) {
+    UrlInfo info = urlToInfo->getRecord(url);
+    std::list<Shared::MessageInfo> container;
+    info.getMessages(container);
+    return std::make_pair(info.getPath(), container);
+}
+
+Core::UrlStorage::UrlInfo::UrlInfo():
+    localPath(),
+    messages() {}
+
+Core::UrlStorage::UrlInfo::UrlInfo(const QString& path):
+    localPath(path),
+    messages() {}
+ 
+Core::UrlStorage::UrlInfo::UrlInfo(const QString& path, const std::list<Shared::MessageInfo>& msgs):
+    localPath(path),
+    messages(msgs) {}
+ 
+Core::UrlStorage::UrlInfo::~UrlInfo() {}
+ 
+bool Core::UrlStorage::UrlInfo::addMessage(const QString& acc, const QString& jid, const QString& id) {
+    for (const Shared::MessageInfo& info : messages) {
+        if (info.account == acc && info.jid == jid && info.messageId == id) {
+            return false;
+        }
+    }
+    messages.emplace_back(acc, jid, id);
+    return true;
+}
+
+void Core::UrlStorage::UrlInfo::serialize(QDataStream& data) const {
+    data << localPath;
+    std::list<Shared::MessageInfo>::size_type size = messages.size();
+    data << quint32(size);
+    for (const Shared::MessageInfo& info : messages) {
+        data << info.account;
+        data << info.jid;
+        data << info.messageId;
+    }
+}
+
+QDataStream & operator << (QDataStream& in, const Core::UrlStorage::UrlInfo& info) {
+    info.serialize(in);
+    return in;
+}
+
+QDataStream & operator >> (QDataStream& out, Core::UrlStorage::UrlInfo& info) {
+    info.deserialize(out);
+    return out;
+}
+
+void Core::UrlStorage::UrlInfo::deserialize(QDataStream& data) {
+    data >> localPath;
+    quint32 size;
+    data >> size;
+    for (quint32 i = 0; i < size; ++i) {
+        messages.emplace_back();
+        Shared::MessageInfo& info = messages.back();
+        data >> info.account;
+        data >> info.jid;
+        data >> info.messageId;
+    }
+}
+
+void Core::UrlStorage::UrlInfo::getMessages(std::list<Shared::MessageInfo>& container) const {
+    for (const Shared::MessageInfo& info : messages)
+        container.emplace_back(info);
+}
+
+QString Core::UrlStorage::UrlInfo::getPath() const {
+    return localPath;
+}
+
+bool Core::UrlStorage::UrlInfo::hasPath() const {
+    return localPath.size() > 0;
+}
+
+void Core::UrlStorage::UrlInfo::setPath(const QString& path) {
+    localPath = path;
+}
diff --git a/core/storage/urlstorage.h b/core/components/urlstorage.h
similarity index 90%
rename from core/storage/urlstorage.h
rename to core/components/urlstorage.h
index 3dc5c21..ee8e30d 100644
--- a/core/storage/urlstorage.h
+++ b/core/components/urlstorage.h
@@ -21,20 +21,19 @@
 
 #include <QString>
 #include <QDataStream>
-#include <lmdb.h>
+
 #include <list>
 
-#include "archive.h"
+#include <storage.h>
+
 #include <shared/messageinfo.h>
 
 namespace Core {
 
-/**
- * @todo write docs
- */
-class UrlStorage
-{
+class UrlStorage {
+public:
     class UrlInfo;
+
 public:
     UrlStorage(const QString& name);
     ~UrlStorage();
@@ -55,20 +54,16 @@ public:
     std::pair<QString, std::list<Shared::MessageInfo>> getPath(const QString& url);
     
 private:
-    QString name;
-    bool opened;
-    MDB_env* environment;
-    MDB_dbi base;
-    MDB_dbi map;
+    LMDBAL::Base base;
+    LMDBAL::Storage<QString, UrlInfo>* urlToInfo;
+    LMDBAL::Storage<QString, QString>* pathToUrl;
     
 private:
     void writeInfo(const QString& key, const UrlInfo& info, bool overwrite = false);
     void writeInfo(const QString& key, const UrlInfo& info, MDB_txn* txn, bool overwrite = false);
-    void readInfo(const QString& key, UrlInfo& info);
-    void readInfo(const QString& key, UrlInfo& info, MDB_txn* txn);
     UrlInfo addToInfo(const QString& url, const QString& account, const QString& jid, const QString& id, const QString& path = "-s");
     
-private:
+public:
     class UrlInfo {
     public:
         UrlInfo(const QString& path);
@@ -96,4 +91,7 @@ private:
 
 }
 
+QDataStream& operator >> (QDataStream &in, Core::UrlStorage::UrlInfo& info);
+QDataStream& operator << (QDataStream &out, const Core::UrlStorage::UrlInfo& info);
+
 #endif // CORE_URLSTORAGE_H
diff --git a/core/storage/CMakeLists.txt b/core/storage/CMakeLists.txt
index 238b59a..e2de42c 100644
--- a/core/storage/CMakeLists.txt
+++ b/core/storage/CMakeLists.txt
@@ -1,10 +1,4 @@
 target_sources(squawk PRIVATE
     archive.cpp
     archive.h
-#    storage.hpp
-#    storage.h
-    urlstorage.cpp
-    urlstorage.h
-#    cache.hpp
-#    cache.h
 )
diff --git a/core/storage/urlstorage.cpp b/core/storage/urlstorage.cpp
deleted file mode 100644
index f59ff62..0000000
--- a/core/storage/urlstorage.cpp
+++ /dev/null
@@ -1,491 +0,0 @@
-/*
- * Squawk messenger. 
- * Copyright (C) 2019 Yury Gubich <blue@macaw.me>
- * 
- * 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 <QStandardPaths>
-#include <QDir>
-#include <QDebug>
-
-#include "urlstorage.h"
-
-Core::UrlStorage::UrlStorage(const QString& p_name):
-    name(p_name),
-    opened(false),
-    environment(),
-    base(),
-    map()
-{
-}
-
-Core::UrlStorage::~UrlStorage()
-{
-    close();
-}
-
-void Core::UrlStorage::open()
-{
-    if (!opened) {
-        mdb_env_create(&environment);
-        QString path(QStandardPaths::writableLocation(QStandardPaths::CacheLocation));
-        path += "/" + name;
-        QDir cache(path);
-        
-        if (!cache.exists()) {
-            bool res = cache.mkpath(path);
-            if (!res) {
-                throw Archive::Directory(path.toStdString());
-            }
-        }
-        
-        mdb_env_set_maxdbs(environment, 2);
-        mdb_env_set_mapsize(environment, 10UL * 1024UL * 1024UL);
-        mdb_env_open(environment, path.toStdString().c_str(), 0, 0664);
-        
-        MDB_txn *txn;
-        mdb_txn_begin(environment, NULL, 0, &txn);
-        mdb_dbi_open(txn, "base", MDB_CREATE, &base);
-        mdb_dbi_open(txn, "map", MDB_CREATE, &map);
-        mdb_txn_commit(txn);
-        opened = true;
-    }
-}
-
-void Core::UrlStorage::close()
-{
-    if (opened) {
-        mdb_dbi_close(environment, map);
-        mdb_dbi_close(environment, base);
-        mdb_env_close(environment);
-        opened = false;
-    }
-}
-
-void Core::UrlStorage::writeInfo(const QString& key, const Core::UrlStorage::UrlInfo& info, bool overwrite)
-{
-    MDB_txn *txn;
-    mdb_txn_begin(environment, NULL, 0, &txn);
-    
-    try {
-        writeInfo(key, info, txn, overwrite);
-        mdb_txn_commit(txn);
-    } catch (...) {
-        mdb_txn_abort(txn);
-        throw;
-    }
-}
-
-void Core::UrlStorage::writeInfo(const QString& key, const Core::UrlStorage::UrlInfo& info, MDB_txn* txn, bool overwrite)
-{
-    QByteArray ba;
-    QDataStream ds(&ba, QIODevice::WriteOnly);
-    info.serialize(ds);
-    
-    const std::string& id = key.toStdString();
-    MDB_val lmdbKey, lmdbData;
-    lmdbKey.mv_size = id.size();
-    lmdbKey.mv_data = (char*)id.c_str();
-    lmdbData.mv_size = ba.size();
-    lmdbData.mv_data = (uint8_t*)ba.data();
-    
-    int rc;
-    rc = mdb_put(txn, base, &lmdbKey, &lmdbData, overwrite ? 0 : MDB_NOOVERWRITE);
-    
-    if (rc != 0) {
-        if (rc == MDB_KEYEXIST) {
-            if (!overwrite) {
-                throw Archive::Exist(name.toStdString(), id);
-            }
-        } else {
-            throw Archive::Unknown(name.toStdString(), mdb_strerror(rc));
-        }
-    }
-    
-    if (info.hasPath()) {
-        std::string sp = info.getPath().toStdString();
-        lmdbData.mv_size = sp.size();
-        lmdbData.mv_data = (char*)sp.c_str();
-        rc = mdb_put(txn, map, &lmdbData, &lmdbKey, 0);
-        if (rc != 0) {
-            throw Archive::Unknown(name.toStdString(), mdb_strerror(rc));
-        }
-    }
-}
-
-void Core::UrlStorage::readInfo(const QString& key, Core::UrlStorage::UrlInfo& info, MDB_txn* txn)
-{
-    const std::string& id = key.toStdString();
-    MDB_val lmdbKey, lmdbData;
-    lmdbKey.mv_size = id.size();
-    lmdbKey.mv_data = (char*)id.c_str();
-    int rc = mdb_get(txn, base, &lmdbKey, &lmdbData);
-    
-    if (rc == 0) {
-        QByteArray ba((char*)lmdbData.mv_data, lmdbData.mv_size);
-        QDataStream ds(&ba, QIODevice::ReadOnly);
-        
-        info.deserialize(ds);
-    } else if (rc == MDB_NOTFOUND) {
-        throw Archive::NotFound(id, name.toStdString());
-    } else {
-        throw Archive::Unknown(name.toStdString(), mdb_strerror(rc));
-    }
-}
-
-void Core::UrlStorage::readInfo(const QString& key, Core::UrlStorage::UrlInfo& info)
-{
-    MDB_txn *txn;
-    mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn);
-    
-    try {
-        readInfo(key, info, txn);
-        mdb_txn_commit(txn);
-    } catch (...) {
-        mdb_txn_abort(txn);
-        throw;
-    }
-}
-
-void Core::UrlStorage::addFile(const QString& url)
-{
-    if (!opened) {
-        throw Archive::Closed("addFile(no message, no path)", name.toStdString());
-    }
-    
-    addToInfo(url, "", "", "");
-}
-
-void Core::UrlStorage::addFile(const QString& url, const QString& path)
-{
-    if (!opened) {
-        throw Archive::Closed("addFile(no message, with path)", name.toStdString());
-    }
-    
-    addToInfo(url, "", "", "", path);
-}
-
-void Core::UrlStorage::addFile(const QString& url, const QString& account, const QString& jid, const QString& id)
-{
-    if (!opened) {
-        throw Archive::Closed("addFile(with message, no path)", name.toStdString());
-    }
-    
-    addToInfo(url, account, jid, id);
-}
-
-void Core::UrlStorage::addFile(const QString& url, const QString& path, const QString& account, const QString& jid, const QString& id)
-{
-    if (!opened) {
-        throw Archive::Closed("addFile(with message, with path)", name.toStdString());
-    }
-    
-    addToInfo(url, account, jid, id, path);
-}
-
-void Core::UrlStorage::addFile(const std::list<Shared::MessageInfo>& msgs, const QString& url, const QString& path)
-{
-    if (!opened) {
-        throw Archive::Closed("addFile(with list)", name.toStdString());
-    }
-    
-    UrlInfo info (path, msgs);
-    writeInfo(url, info, true);;
-}
-
-QString Core::UrlStorage::addMessageAndCheckForPath(const QString& url, const QString& account, const QString& jid, const QString& id)
-{
-    if (!opened) {
-        throw Archive::Closed("addMessageAndCheckForPath", name.toStdString());
-    }
-    
-    return addToInfo(url, account, jid, id).getPath();
-}
-
-Core::UrlStorage::UrlInfo Core::UrlStorage::addToInfo(const QString& url, const QString& account, const QString& jid, const QString& id, const QString& path)
-{
-    UrlInfo info;
-    MDB_txn *txn;
-    mdb_txn_begin(environment, NULL, 0, &txn);
-    
-    try {
-        readInfo(url, info, txn);
-    } catch (const Archive::NotFound& e) {
-        
-    } catch (...) {
-        mdb_txn_abort(txn);
-        throw;
-    }
-    
-    bool pathChange = false;
-    bool listChange = false;
-    if (path != "-s") {
-        if (info.getPath() != path) {
-            info.setPath(path);
-            pathChange = true;
-        }
-    }
-    
-    if (account.size() > 0 && jid.size() > 0 && id.size() > 0) {
-        listChange = info.addMessage(account, jid, id);
-    }
-    
-    if (pathChange || listChange) {
-        try {
-            writeInfo(url, info, txn, true);
-            mdb_txn_commit(txn);
-        } catch (...) {
-            mdb_txn_abort(txn);
-            throw;
-        }
-    } else {
-        mdb_txn_abort(txn);
-    }
-    
-    return info;
-}
-
-std::list<Shared::MessageInfo> Core::UrlStorage::setPath(const QString& url, const QString& path)
-{
-    std::list<Shared::MessageInfo> list;
-    
-    MDB_txn *txn;
-    mdb_txn_begin(environment, NULL, 0, &txn);
-    UrlInfo info;
-    
-    try {
-        readInfo(url, info, txn);
-        info.getMessages(list);
-    } catch (const Archive::NotFound& e) {
-    } catch (...) {
-        mdb_txn_abort(txn);
-        throw;
-    }
-    
-    info.setPath(path);
-    try {
-        writeInfo(url, info, txn, true);
-        mdb_txn_commit(txn);
-    } catch (...) {
-        mdb_txn_abort(txn);
-        throw;
-    }
-    
-    return list;
-}
-
-std::list<Shared::MessageInfo> Core::UrlStorage::removeFile(const QString& url)
-{
-    std::list<Shared::MessageInfo> list;
-    
-    MDB_txn *txn;
-    mdb_txn_begin(environment, NULL, 0, &txn);
-    UrlInfo info;
-    
-    try {
-        std::string id = url.toStdString();
-        readInfo(url, info, txn);
-        info.getMessages(list);
-        
-        MDB_val lmdbKey;
-        lmdbKey.mv_size = id.size();
-        lmdbKey.mv_data = (char*)id.c_str();
-        int rc = mdb_del(txn, base, &lmdbKey, NULL);
-        if (rc != 0) {
-            throw Archive::Unknown(name.toStdString(), mdb_strerror(rc));
-        }
-        
-        if (info.hasPath()) {
-            std::string path = info.getPath().toStdString();
-            lmdbKey.mv_size = path.size();
-            lmdbKey.mv_data = (char*)path.c_str();
-            
-            int rc = mdb_del(txn, map, &lmdbKey, NULL);
-            if (rc != 0) {
-                throw Archive::Unknown(name.toStdString(), mdb_strerror(rc));
-            }
-        }
-        mdb_txn_commit(txn);
-    } catch (...) {
-        mdb_txn_abort(txn);
-        throw;
-    }
-    
-    return list;
-}
-
-std::list<Shared::MessageInfo> Core::UrlStorage::deletedFile(const QString& path)
-{
-    std::list<Shared::MessageInfo> list;
-    
-    MDB_txn *txn;
-    mdb_txn_begin(environment, NULL, 0, &txn);
-    
-    try {
-        std::string spath = path.toStdString();
-        
-        MDB_val lmdbKey, lmdbData;
-        lmdbKey.mv_size = spath.size();
-        lmdbKey.mv_data = (char*)spath.c_str();
-        
-        QString url;
-        int rc = mdb_get(txn, map, &lmdbKey, &lmdbData);
-        
-        if (rc == 0) {
-            std::string surl((char*)lmdbData.mv_data, lmdbData.mv_size);
-            url = QString(surl.c_str());
-        } else if (rc == MDB_NOTFOUND) {
-            qDebug() << "Have been asked to remove file" << path << ", which isn't in the database, skipping";
-            mdb_txn_abort(txn);
-            return list;
-        } else {
-            throw Archive::Unknown(name.toStdString(), mdb_strerror(rc));
-        }
-        
-        UrlInfo info;
-        std::string id = url.toStdString();
-        readInfo(url, info, txn);
-        info.getMessages(list);
-        info.setPath(QString());
-        writeInfo(url, info, txn, true);
-        
-        rc = mdb_del(txn, map, &lmdbKey, NULL);
-        if (rc != 0) {
-            throw Archive::Unknown(name.toStdString(), mdb_strerror(rc));
-        }
-        
-        mdb_txn_commit(txn);
-    } catch (...) {
-        mdb_txn_abort(txn);
-        throw;
-    }
-    
-    return list;
-}
-
-
-QString Core::UrlStorage::getUrl(const QString& path)
-{
-    std::list<Shared::MessageInfo> list;
-    
-    MDB_txn *txn;
-    mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn);
-    
-    std::string spath = path.toStdString();
-    
-    MDB_val lmdbKey, lmdbData;
-    lmdbKey.mv_size = spath.size();
-    lmdbKey.mv_data = (char*)spath.c_str();
-    
-    QString url;
-    int rc = mdb_get(txn, map, &lmdbKey, &lmdbData);
-    
-    if (rc == 0) {
-        std::string surl((char*)lmdbData.mv_data, lmdbData.mv_size);
-        url = QString(surl.c_str());
-        
-        mdb_txn_abort(txn);
-        return url;
-    } else if (rc == MDB_NOTFOUND) {
-        mdb_txn_abort(txn);
-        throw Archive::NotFound(spath, name.toStdString());
-    } else {
-        mdb_txn_abort(txn);
-        throw Archive::Unknown(name.toStdString(), mdb_strerror(rc));
-    }
-}
-
-std::pair<QString, std::list<Shared::MessageInfo>> Core::UrlStorage::getPath(const QString& url)
-{
-    UrlInfo info;
-    readInfo(url, info);
-    std::list<Shared::MessageInfo> container;
-    info.getMessages(container);
-    return std::make_pair(info.getPath(), container);
-}
-
-Core::UrlStorage::UrlInfo::UrlInfo():
-    localPath(),
-    messages() {}
-
-Core::UrlStorage::UrlInfo::UrlInfo(const QString& path):
-    localPath(path),
-    messages() {}
- 
-Core::UrlStorage::UrlInfo::UrlInfo(const QString& path, const std::list<Shared::MessageInfo>& msgs):
-    localPath(path),
-    messages(msgs) {}
- 
-Core::UrlStorage::UrlInfo::~UrlInfo() {}
- 
-bool Core::UrlStorage::UrlInfo::addMessage(const QString& acc, const QString& jid, const QString& id)
-{
-    for (const Shared::MessageInfo& info : messages) {
-        if (info.account == acc && info.jid == jid && info.messageId == id) {
-            return false;
-        }
-    }
-    messages.emplace_back(acc, jid, id);
-    return true;
-}
-
-void Core::UrlStorage::UrlInfo::serialize(QDataStream& data) const
-{
-    data << localPath;
-    std::list<Shared::MessageInfo>::size_type size = messages.size();
-    data << quint32(size);
-    for (const Shared::MessageInfo& info : messages) {
-        data << info.account;
-        data << info.jid;
-        data << info.messageId;
-    }
-}
-
-void Core::UrlStorage::UrlInfo::deserialize(QDataStream& data)
-{
-    data >> localPath;
-    quint32 size;
-    data >> size;
-    for (quint32 i = 0; i < size; ++i) {
-        messages.emplace_back();
-        Shared::MessageInfo& info = messages.back();
-        data >> info.account;
-        data >> info.jid;
-        data >> info.messageId;
-    }
-}
-
-void Core::UrlStorage::UrlInfo::getMessages(std::list<Shared::MessageInfo>& container) const
-{
-    for (const Shared::MessageInfo& info : messages) {
-        container.emplace_back(info);
-    }
-}
-
-QString Core::UrlStorage::UrlInfo::getPath() const
-{
-    return localPath;
-}
-
-bool Core::UrlStorage::UrlInfo::hasPath() const
-{
-    return localPath.size() > 0;
-}
-
-
-void Core::UrlStorage::UrlInfo::setPath(const QString& path)
-{
-    localPath = path;
-}
diff --git a/external/qxmpp b/external/qxmpp
index d679ad1..ab4bdf2 160000
--- a/external/qxmpp
+++ b/external/qxmpp
@@ -1 +1 @@
-Subproject commit d679ad1c49eeb28be2ac3a75bd7fd1a9be24d483
+Subproject commit ab4bdf2da41a26f462fe3a333a34e32c999e2a6d
diff --git a/main/main.cpp b/main/main.cpp
index ce614b3..d9ad4cc 100644
--- a/main/main.cpp
+++ b/main/main.cpp
@@ -43,6 +43,7 @@ int main(int argc, char *argv[])
     qRegisterMetaType<Shared::EncryptionProtocol>("Shared::EncryptionProtocol");
     qRegisterMetaType<Shared::KeyInfo>("Shared::KeyInfo");
     qRegisterMetaType<Shared::Info>("Shared::Info");
+    qRegisterMetaType<Shared::TrustLevel>("Shared::TrustLevel");
 #ifdef WITH_OMEMO
     qRegisterMetaType<QXmppOmemoStorage::OwnDevice>("QXmppOmemoStorage::OwnDevice");
     qRegisterMetaTypeStreamOperators<QXmppOmemoStorage::OwnDevice>("QXmppOmemoStorage::OwnDevice");
@@ -53,7 +54,6 @@ int main(int argc, char *argv[])
     if (!app.initializeSettings())
         return -1;
 
-    
     return app.run();
 }
 

From 23ec80ccba5604aebcd73e9975bf5b743b685a74 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Tue, 15 Aug 2023 12:28:25 -0300
Subject: [PATCH 250/281] cleanup some warnings suppression

---
 CMakeLists.txt                             |   1 +
 core/account.cpp                           |   6 +-
 core/handlers/messagehandler.cpp           | 152 ++++----
 core/handlers/omemohandler.cpp             |   1 +
 core/handlers/rosterhandler.cpp            |   1 +
 core/signalcatcher.cpp                     |  18 +-
 main/application.cpp                       | 196 ++++-------
 shared/CMakeLists.txt                      |   1 +
 shared/defines.h                           |  24 ++
 shared/identity.cpp                        |   4 +
 shared/shared.h                            |   1 +
 ui/models/accounts.cpp                     |  85 ++---
 ui/models/info/emails.cpp                  |  36 +-
 ui/models/info/omemo/keys.cpp              |  12 +-
 ui/models/info/phones.cpp                  |  44 +--
 ui/models/roster.cpp                       | 390 ++++++++-------------
 ui/utils/comboboxdelegate.cpp              |  30 +-
 ui/utils/resizer.cpp                       |  13 +-
 ui/widgets/chat.cpp                        |   3 +
 ui/widgets/conversation.cpp                | 135 +++----
 ui/widgets/conversation.h                  |   3 +-
 ui/widgets/messageline/feedview.cpp        |  91 ++---
 ui/widgets/messageline/messagedelegate.cpp | 152 +++-----
 ui/widgets/messageline/messagedelegate.h   |   2 +
 ui/widgets/messageline/messagefeed.cpp     | 131 +++----
 ui/widgets/room.cpp                        |  22 +-
 26 files changed, 630 insertions(+), 924 deletions(-)
 create mode 100644 shared/defines.h

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 06c4344..d29bb4f 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -2,6 +2,7 @@ cmake_minimum_required(VERSION 3.5)
 project(squawk VERSION 0.2.3 LANGUAGES CXX)
 
 cmake_policy(SET CMP0076 NEW)
+cmake_policy(SET CMP0077 NEW)
 cmake_policy(SET CMP0079 NEW)
 set(CMAKE_CXX_STANDARD 17)
 
diff --git a/core/account.cpp b/core/account.cpp
index 736d5ae..b06f51b 100644
--- a/core/account.cpp
+++ b/core/account.cpp
@@ -21,6 +21,8 @@
 
 #include <QDateTime>
 
+#include "shared/defines.h"
+
 Core::Account::Account(
     const QString& p_login,
     const QString& p_server,
@@ -159,7 +161,8 @@ Core::Account::Account(
         logger->setLoggingType(QXmppLogger::SignalLogging);
         client.setLogger(logger);
 
-        QObject::connect(logger, &QXmppLogger::message, this, [](QXmppLogger::MessageType type, const QString& text){
+        QObject::connect(logger, &QXmppLogger::message, this, [](QXmppLogger::MessageType type, const QString& text) {
+            SHARED_UNUSED(type);
             qDebug() << text;
         });
     }
@@ -511,6 +514,7 @@ void Core::Account::onMamResultsReceived(const QString& queryId, const QXmppResu
 }
 
 void Core::Account::onMamLog(QXmppLogger::MessageType type, const QString& msg) {
+    SHARED_UNUSED(type);
     qDebug() << "MAM MESSAGE LOG::";
     qDebug() << msg;
 }
diff --git a/core/handlers/messagehandler.cpp b/core/handlers/messagehandler.cpp
index 020ab6f..3ed8dec 100644
--- a/core/handlers/messagehandler.cpp
+++ b/core/handlers/messagehandler.cpp
@@ -24,11 +24,9 @@ Core::MessageHandler::MessageHandler(Core::Account* account):
     acc(account),
     pendingStateMessages(),
     uploadingSlotsQueue()
-{
-}
+{}
 
-void Core::MessageHandler::onMessageReceived(const QXmppMessage& msg)
-{
+void Core::MessageHandler::onMessageReceived(const QXmppMessage& msg) {
 #if (QXMPP_VERSION) >= QT_VERSION_CHECK(1, 5, 0)
 #ifdef WITH_OMEMO
     switch (msg.encryptionMethod()) {
@@ -83,9 +81,9 @@ void Core::MessageHandler::onMessageReceived(const QXmppMessage& msg)
                     {"state", static_cast<uint>(Shared::Message::State::error)},
                     {"errorText", msg.error().text()}
                 };
-                if (cnt != nullptr) {
+                if (cnt != nullptr)
                     cnt->changeMessage(id, cData);
-                }
+
                 emit acc->changeMessage(jid, id, cData);
                 handled = true;
             } else {
@@ -97,13 +95,11 @@ void Core::MessageHandler::onMessageReceived(const QXmppMessage& msg)
             qDebug() << "received a message with type \"Headline\", not sure what to do with it now, skipping";
             break;
     }
-    if (!handled) {
+    if (!handled)
         logMessage(msg);
-    }
 }
 
-bool Core::MessageHandler::handleChatMessage(const QXmppMessage& msg, bool outgoing, bool forwarded, bool guessing)
-{
+bool Core::MessageHandler::handleChatMessage(const QXmppMessage& msg, bool outgoing, bool forwarded, bool guessing) {
     if (msg.body().size() != 0 || msg.outOfBandUrl().size() > 0) {
         Shared::Message sMsg(Shared::Message::chat);
         initializeMessage(sMsg, msg, outgoing, forwarded, guessing);
@@ -138,8 +134,7 @@ bool Core::MessageHandler::handleChatMessage(const QXmppMessage& msg, bool outgo
     return false;
 }
 
-bool Core::MessageHandler::handleGroupMessage(const QXmppMessage& msg, bool outgoing, bool forwarded, bool guessing)
-{
+bool Core::MessageHandler::handleGroupMessage(const QXmppMessage& msg, bool outgoing, bool forwarded, bool guessing) {
     const QString& body(msg.body());
     if (body.size() != 0) {
         
@@ -182,8 +177,7 @@ bool Core::MessageHandler::handleGroupMessage(const QXmppMessage& msg, bool outg
 }
 
 
-void Core::MessageHandler::initializeMessage(Shared::Message& target, const QXmppMessage& source, bool outgoing, bool forwarded, bool guessing) const
-{
+void Core::MessageHandler::initializeMessage(Shared::Message& target, const QXmppMessage& source, bool outgoing, bool forwarded, bool guessing) const {
     const QDateTime& time(source.stamp());
     QString id;
 #if (QXMPP_VERSION) >= QT_VERSION_CHECK(1, 3, 0)
@@ -229,8 +223,7 @@ void Core::MessageHandler::initializeMessage(Shared::Message& target, const QXmp
     target.setOutOfBandUrl(oob);
 }
 
-void Core::MessageHandler::logMessage(const QXmppMessage& msg, const QString& reason)
-{
+void Core::MessageHandler::logMessage(const QXmppMessage& msg, const QString& reason) {
     qDebug() << reason;
     qDebug() << "- from: " << msg.from();
     qDebug() << "- to: " << msg.to();
@@ -247,19 +240,16 @@ void Core::MessageHandler::logMessage(const QXmppMessage& msg, const QString& re
 }
 
 #if (QXMPP_VERSION) < QT_VERSION_CHECK(1, 5, 0)
-void Core::MessageHandler::onCarbonMessageReceived(const QXmppMessage& msg)
-{
+void Core::MessageHandler::onCarbonMessageReceived(const QXmppMessage& msg) {
     handleChatMessage(msg, false, true);
 }
 
-void Core::MessageHandler::onCarbonMessageSent(const QXmppMessage& msg)
-{
+void Core::MessageHandler::onCarbonMessageSent(const QXmppMessage& msg) {
     handleChatMessage(msg, true, true);
 }
 #endif
 
-std::tuple<bool, QString, QString> Core::MessageHandler::getOriginalPendingMessageId(const QString& id)
-{
+std::tuple<bool, QString, QString> Core::MessageHandler::getOriginalPendingMessageId(const QString& id) {
     std::tuple<bool, QString, QString> result({false, "", ""});
     std::map<QString, QString>::const_iterator itr = pendingStateMessages.find(id);
     if (itr != pendingStateMessages.end()) {
@@ -268,11 +258,11 @@ std::tuple<bool, QString, QString> Core::MessageHandler::getOriginalPendingMessa
 
         std::map<QString, QString>::const_iterator itrC = pendingCorrectionMessages.find(id);
         if (itrC != pendingCorrectionMessages.end()) {
-            if (itrC->second.size() > 0) {
+            if (itrC->second.size() > 0)
                 std::get<1>(result) = itrC->second;
-            } else {
+            else
                 std::get<1>(result) = itr->first;
-            }
+
             pendingCorrectionMessages.erase(itrC);
         } else {
             std::get<1>(result) = itr->first;
@@ -284,8 +274,8 @@ std::tuple<bool, QString, QString> Core::MessageHandler::getOriginalPendingMessa
     return result;
 }
 
-void Core::MessageHandler::onReceiptReceived(const QString& jid, const QString& id)
-{
+void Core::MessageHandler::onReceiptReceived(const QString& jid, const QString& id) {
+    SHARED_UNUSED(jid);
     std::tuple<bool, QString, QString> ids = getOriginalPendingMessageId(id);
     if (std::get<0>(ids)) {
         QMap<QString, QVariant> cData = {{"state", static_cast<uint>(Shared::Message::State::delivered)}};
@@ -298,8 +288,7 @@ void Core::MessageHandler::onReceiptReceived(const QString& jid, const QString&
     }
 }
 
-void Core::MessageHandler::sendMessage(const Shared::Message& data, bool newMessage, QString originalId)
-{
+void Core::MessageHandler::sendMessage(const Shared::Message& data, bool newMessage, QString originalId) {
     if (data.getOutOfBandUrl().size() == 0 && data.getAttachPath().size() > 0) {
         pendingCorrectionMessages.insert(std::make_pair(data.getId(), originalId));
         prepareUpload(data, newMessage);
@@ -308,19 +297,18 @@ void Core::MessageHandler::sendMessage(const Shared::Message& data, bool newMess
     }
 }
 
-void Core::MessageHandler::performSending(Shared::Message data, const QString& originalId, bool newMessage)
-{
+void Core::MessageHandler::performSending(Shared::Message data, const QString& originalId, bool newMessage) {
     QString jid = data.getPenPalJid();
     QString id = data.getId();
     qDebug() << "Sending message with id:" << id;
-    if (originalId.size() > 0) {
+    if (originalId.size() > 0)
         qDebug() << "To replace one with id:" << originalId;
-    }
+
     RosterItem* ri = acc->rh->getRosterItem(jid);
     bool sent = false;
-    if (newMessage && originalId.size() > 0) {
+    if (newMessage && originalId.size() > 0)
         newMessage = false;
-    }
+
     QDateTime sendTime = QDateTime::currentDateTimeUtc();
     if (acc->state == Shared::ConnectionState::connected) {
         QXmppMessage msg(createPacket(data, sendTime, originalId));
@@ -341,22 +329,21 @@ void Core::MessageHandler::performSending(Shared::Message data, const QString& o
     QMap<QString, QVariant> changes(getChanges(data, sendTime, newMessage, originalId));
     
     QString realId;
-    if (originalId.size() > 0) {
+    if (originalId.size() > 0)
         realId = originalId;
-    } else {
+    else
         realId = id;
-    }
+
     if (ri != nullptr) {
-        if (newMessage) {
+        if (newMessage)
             ri->appendMessageToArchive(data);
-        } else {
+         else
             ri->changeMessage(realId, changes);
-        }
+
         if (sent) {
             pendingStateMessages.insert(std::make_pair(id, jid));
-            if (originalId.size() > 0) {
+            if (originalId.size() > 0)
                 pendingCorrectionMessages.insert(std::make_pair(id, originalId));
-            }
         } else {
             pendingStateMessages.erase(id);
             pendingCorrectionMessages.erase(id);
@@ -366,25 +353,24 @@ void Core::MessageHandler::performSending(Shared::Message data, const QString& o
     emit acc->changeMessage(jid, realId, changes);
 }
 
-QMap<QString, QVariant> Core::MessageHandler::getChanges(Shared::Message& data, const QDateTime& time, bool newMessage, const QString& originalId) const
-{
+QMap<QString, QVariant> Core::MessageHandler::getChanges(Shared::Message& data, const QDateTime& time, bool newMessage, const QString& originalId) const {
     QMap<QString, QVariant> changes;
 
     QString oob = data.getOutOfBandUrl();
     Shared::Message::State mstate = data.getState();
     changes.insert("state", static_cast<uint>(mstate));
-    if (mstate == Shared::Message::State::error) {
+    if (mstate == Shared::Message::State::error)
         changes.insert("errorText", data.getErrorText());
-    }
-    if (oob.size() > 0) {
+
+    if (oob.size() > 0)
         changes.insert("outOfBandUrl", oob);
-    }
-    if (newMessage) {
+
+    if (newMessage)
         data.setTime(time);
-    }
-    if (originalId.size() > 0) {
+
+    if (originalId.size() > 0)
         changes.insert("body", data.getBody());
-    }
+
     changes.insert("stamp", time);
 
     //sometimes (when the image is pasted with ctrl+v)
@@ -394,22 +380,20 @@ QMap<QString, QVariant> Core::MessageHandler::getChanges(Shared::Message& data,
     if (attachPath.size() > 0) {
         QString squawkified = Shared::squawkifyPath(attachPath);
         changes.insert("attachPath", squawkified);
-        if (attachPath != squawkified) {
+        if (attachPath != squawkified)
             data.setAttachPath(squawkified);
-        }
     }
 
     return changes;
 }
 
-QXmppMessage Core::MessageHandler::createPacket(const Shared::Message& data, const QDateTime& time, const QString& originalId) const
-{
+QXmppMessage Core::MessageHandler::createPacket(const Shared::Message& data, const QDateTime& time, const QString& originalId) const {
     QXmppMessage msg(acc->getFullJid(), data.getTo(), data.getBody(), data.getThread());
     QString id(data.getId());
 
-    if (originalId.size() > 0) {
+    if (originalId.size() > 0)
         msg.setReplaceId(originalId);
-    }
+
 
 #if (QXMPP_VERSION) >= QT_VERSION_CHECK(1, 3, 0)
     msg.setOriginId(id);
@@ -423,8 +407,7 @@ QXmppMessage Core::MessageHandler::createPacket(const Shared::Message& data, con
     return msg;
 }
 
-void Core::MessageHandler::prepareUpload(const Shared::Message& data, bool newMessage)
-{
+void Core::MessageHandler::prepareUpload(const Shared::Message& data, bool newMessage) {
     if (acc->state == Shared::ConnectionState::connected) {
         QString jid = data.getPenPalJid();
         QString id = data.getId();
@@ -455,9 +438,8 @@ void Core::MessageHandler::prepareUpload(const Shared::Message& data, bool newMe
                     if (file.exists() && file.isReadable()) {
                         pendingStateMessages.insert(std::make_pair(id, jid));
                         uploadingSlotsQueue.emplace_back(path, id);
-                        if (uploadingSlotsQueue.size() == 1) {
+                        if (uploadingSlotsQueue.size() == 1)
                             acc->um->requestUploadSlot(file);
-                        }
                     } else {
                         handleUploadError(jid, id, "Uploading file no longer exists or your system user has no permission to read it");
                         qDebug() << "Requested upload slot in account" << acc->name << "for file" << path << "but the file doesn't exist or is not readable";
@@ -474,8 +456,7 @@ void Core::MessageHandler::prepareUpload(const Shared::Message& data, bool newMe
     }
 }
 
-void Core::MessageHandler::onUploadSlotReceived(const QXmppHttpUploadSlotIq& slot)
-{
+void Core::MessageHandler::onUploadSlotReceived(const QXmppHttpUploadSlotIq& slot) {
     if (uploadingSlotsQueue.size() == 0) {
         qDebug() << "HTTP Upload manager of account" << acc->name << "reports about success requesting upload slot, but none was requested";
     } else {
@@ -485,14 +466,12 @@ void Core::MessageHandler::onUploadSlotReceived(const QXmppHttpUploadSlotIq& slo
         acc->network->uploadFile({acc->name, palJid, mId}, pair.first, slot.putUrl(), slot.getUrl(), slot.putHeaders());
         
         uploadingSlotsQueue.pop_front();
-        if (uploadingSlotsQueue.size() > 0) {
+        if (uploadingSlotsQueue.size() > 0)
             acc->um->requestUploadSlot(uploadingSlotsQueue.front().first);
-        }
     }
 }
 
-void Core::MessageHandler::onUploadSlotRequestFailed(const QXmppHttpUploadRequestIq& request)
-{
+void Core::MessageHandler::onUploadSlotRequestFailed(const QXmppHttpUploadRequestIq& request) {
     QString err(request.error().text());
     if (uploadingSlotsQueue.size() == 0) {
         qDebug() << "HTTP Upload manager of account" << acc->name << "reports about an error requesting upload slot, but none was requested";
@@ -503,14 +482,12 @@ void Core::MessageHandler::onUploadSlotRequestFailed(const QXmppHttpUploadReques
         handleUploadError(pendingStateMessages.at(pair.second), pair.second, err);
         
         uploadingSlotsQueue.pop_front();
-        if (uploadingSlotsQueue.size() > 0) {
+        if (uploadingSlotsQueue.size() > 0)
             acc->um->requestUploadSlot(uploadingSlotsQueue.front().first);
-        }
     }
 }
 
-void Core::MessageHandler::onDownloadFileComplete(const std::list<Shared::MessageInfo>& msgs, const QString& path)
-{
+void Core::MessageHandler::onDownloadFileComplete(const std::list<Shared::MessageInfo>& msgs, const QString& path) {
     QMap<QString, QVariant> cData = {
         {"attachPath", path}
     };
@@ -518,27 +495,24 @@ void Core::MessageHandler::onDownloadFileComplete(const std::list<Shared::Messag
         if (info.account == acc->getName()) {
             RosterItem* cnt = acc->rh->getRosterItem(info.jid);
             if (cnt != nullptr) {
-                if (cnt->changeMessage(info.messageId, cData)) {
+                bool changed = cnt->changeMessage(info.messageId, cData);
+                if (changed)
                     emit acc->changeMessage(info.jid, info.messageId, cData);
-                }
             }
         }
     }
 }
 
-void Core::MessageHandler::onLoadFileError(const std::list<Shared::MessageInfo>& msgs, const QString& text, bool up)
-{
+void Core::MessageHandler::onLoadFileError(const std::list<Shared::MessageInfo>& msgs, const QString& text, bool up) {
     if (up) {
         for (const Shared::MessageInfo& info : msgs) {
-            if (info.account == acc->getName()) {
+            if (info.account == acc->getName())
                 handleUploadError(info.jid, info.messageId, text);
-            }
         }
     }
 }
 
-void Core::MessageHandler::handleUploadError(const QString& jid, const QString& messageId, const QString& errorText)
-{
+void Core::MessageHandler::handleUploadError(const QString& jid, const QString& messageId, const QString& errorText) {
     emit acc->uploadFileError(jid, messageId, "Error requesting slot to upload file: " + errorText);
     pendingStateMessages.erase(messageId);
     pendingCorrectionMessages.erase(messageId);
@@ -548,8 +522,7 @@ void Core::MessageHandler::handleUploadError(const QString& jid, const QString&
     });
 }
 
-void Core::MessageHandler::onUploadFileComplete(const std::list<Shared::MessageInfo>& msgs, const QString& url, const QString& path)
-{
+void Core::MessageHandler::onUploadFileComplete(const std::list<Shared::MessageInfo>& msgs, const QString& url, const QString& path) {
     for (const Shared::MessageInfo& info : msgs) {
         if (info.account == acc->getName()) {
             RosterItem* ri = acc->rh->getRosterItem(info.jid);
@@ -564,12 +537,11 @@ void Core::MessageHandler::onUploadFileComplete(const std::list<Shared::MessageI
     }
 }
 
-void Core::MessageHandler::sendMessageWithLocalUploadedFile(Shared::Message msg, const QString& url, bool newMessage)
-{
+void Core::MessageHandler::sendMessageWithLocalUploadedFile(Shared::Message msg, const QString& url, bool newMessage) {
     msg.setOutOfBandUrl(url);
-    if (msg.getBody().size() == 0) {    //not sure why, but most messages do that
+    if (msg.getBody().size() == 0)      //not sure why, but most messages do that
         msg.setBody(url);               //they duplicate oob in body, some of them wouldn't even show an attachment if you don't do that
-    }                                   
+
     performSending(msg, pendingCorrectionMessages.at(msg.getId()), newMessage);
     //TODO removal/progress update
 }
@@ -581,8 +553,7 @@ static const std::set<QString> allowedToChangeKeys({
     "errorText"
 });
 
-void Core::MessageHandler::requestChangeMessage(const QString& jid, const QString& messageId, const QMap<QString, QVariant>& data)
-{
+void Core::MessageHandler::requestChangeMessage(const QString& jid, const QString& messageId, const QMap<QString, QVariant>& data) {
     RosterItem* cnt = acc->rh->getRosterItem(jid);
     if (cnt != nullptr) {
         bool allSupported = true;
@@ -604,8 +575,7 @@ void Core::MessageHandler::requestChangeMessage(const QString& jid, const QStrin
     }
 }
 
-void Core::MessageHandler::resendMessage(const QString& jid, const QString& id)
-{
+void Core::MessageHandler::resendMessage(const QString& jid, const QString& id) {
     RosterItem* cnt = acc->rh->getRosterItem(jid);
     if (cnt != nullptr) {
         try {
diff --git a/core/handlers/omemohandler.cpp b/core/handlers/omemohandler.cpp
index 35bca9e..2f9f70e 100644
--- a/core/handlers/omemohandler.cpp
+++ b/core/handlers/omemohandler.cpp
@@ -213,6 +213,7 @@ void Core::OmemoHandler::onOwnBundlesReceived() {
 }
 
 void Core::OmemoHandler::onOmemoDeviceAdded(const QString& jid, uint32_t id) {
+    SHARED_UNUSED(id);
     qDebug() << "OMEMO device added for" << jid;
 }
 
diff --git a/core/handlers/rosterhandler.cpp b/core/handlers/rosterhandler.cpp
index 2e59971..4ce8939 100644
--- a/core/handlers/rosterhandler.cpp
+++ b/core/handlers/rosterhandler.cpp
@@ -456,6 +456,7 @@ void Core::RosterHandler::removeRoomRequest(const QString& jid) {
 }
 
 void Core::RosterHandler::addRoomRequest(const QString& jid, const QString& nick, const QString& password, bool autoJoin) {
+    SHARED_UNUSED(password);
     QString lcJid = jid.toLower();
     std::map<QString, Conference*>::const_iterator cItr = conferences.find(lcJid);
     if (cItr == conferences.end()) {
diff --git a/core/signalcatcher.cpp b/core/signalcatcher.cpp
index 046c67e..ef3e14c 100644
--- a/core/signalcatcher.cpp
+++ b/core/signalcatcher.cpp
@@ -21,6 +21,8 @@
 #include <sys/socket.h>
 #include <unistd.h>
 
+#include "shared/defines.h"
+
 int SignalCatcher::sigintFd[2] = {0,0};
 
 SignalCatcher::SignalCatcher(QCoreApplication *p_app, QObject *parent):
@@ -28,14 +30,10 @@ SignalCatcher::SignalCatcher(QCoreApplication *p_app, QObject *parent):
     app(p_app)
 {
     if (::socketpair(AF_UNIX, SOCK_STREAM, 0, sigintFd))
-    {
         qFatal("Couldn't create INT socketpair");
-    }
     
     if (setup_unix_signal_handlers() != 0) 
-    {
         qFatal("Couldn't install unix handlers");
-    }
     
     snInt = new QSocketNotifier(sigintFd[1], QSocketNotifier::Read, this);
     connect(snInt, &QSocketNotifier::activated, this, &SignalCatcher::handleSigInt);
@@ -44,25 +42,25 @@ SignalCatcher::SignalCatcher(QCoreApplication *p_app, QObject *parent):
 SignalCatcher::~SignalCatcher()
 {}
 
-void SignalCatcher::handleSigInt()
-{
+void SignalCatcher::handleSigInt() {
     snInt->setEnabled(false);
     char tmp;
     ssize_t s = ::read(sigintFd[1], &tmp, sizeof(tmp));
+    SHARED_UNUSED(s);
 
     emit interrupt();
 
     snInt->setEnabled(true);
 }
 
-void SignalCatcher::intSignalHandler(int unused)
-{
+void SignalCatcher::intSignalHandler(int unused) {
     char a = 1;
     ssize_t s = ::write(sigintFd[0], &a, sizeof(a));
+    SHARED_UNUSED(s);
+    SHARED_UNUSED(unused);
 }
 
-int SignalCatcher::setup_unix_signal_handlers()
-{
+int SignalCatcher::setup_unix_signal_handlers() {
     struct sigaction s_int;
 
     s_int.sa_handler = SignalCatcher::intSignalHandler;
diff --git a/main/application.cpp b/main/application.cpp
index edf9fd7..0701a0c 100644
--- a/main/application.cpp
+++ b/main/application.cpp
@@ -120,8 +120,7 @@ Application::Application(Core::Squawk* p_core):
 
 Application::~Application() {}
 
-void Application::quit()
-{
+void Application::quit() {
     if (!nowQuitting) {
         nowQuitting = true;
         emit quitting();
@@ -135,32 +134,27 @@ void Application::quit()
         conversations.clear();
         dialogueQueue.quit();
 
-        if (squawk != nullptr) {
+        if (squawk != nullptr)
             squawk->close();
-        }
 
         if (trayIcon != nullptr) {
             trayIcon->deleteLater();
             trayIcon = nullptr;
         }
 
-        if (!destroyingSquawk) {
+        if (!destroyingSquawk)
             checkForTheLastWindow();
-        }
     }
 }
 
-void Application::checkForTheLastWindow()
-{
-    if (QApplication::topLevelWidgets().size() > 0) {
+void Application::checkForTheLastWindow() {
+    if (QApplication::topLevelWidgets().size() > 0)
         emit readyToQuit();
-    } else {
+    else
         connect(qApp, &QApplication::lastWindowClosed, this, &Application::readyToQuit);
-    }
 }
 
-void Application::createMainWindow()
-{
+void Application::createMainWindow() {
     if (squawk == nullptr) {
         squawk = new Squawk(roster);
 
@@ -202,9 +196,8 @@ void Application::createMainWindow()
 
         for (const std::list<QString>& entry : expandedPaths) {
             QModelIndex ind = roster.getIndexByPath(entry);
-            if (ind.isValid()) {
+            if (ind.isValid())
                 squawk->expand(ind);
-            }
         }
 
         connect(squawk, &Squawk::itemExpanded, this, &Application::onItemExpanded);
@@ -212,8 +205,7 @@ void Application::createMainWindow()
     }
 }
 
-void Application::onSquawkClosing()
-{
+void Application::onSquawkClosing() {
     dialogueQueue.setParentWidnow(nullptr);
 
     if (!nowQuitting) {
@@ -237,18 +229,15 @@ void Application::onSquawkClosing()
 
 void Application::onSquawkDestroyed() {
     destroyingSquawk = false;
-    if (nowQuitting) {
+    if (nowQuitting)
         checkForTheLastWindow();
-    }
 }
 
-void Application::onChangeTray(bool enabled, bool hide)
-{
+void Application::onChangeTray(bool enabled, bool hide) {
     if (enabled) {
         if (trayIcon == nullptr) {
-            if (!hide || squawk == nullptr) {
+            if (!hide || squawk == nullptr)
                 createTrayIcon();
-            }
         } else {
             if (hide && squawk != nullptr) {
                 trayIcon->deleteLater();
@@ -261,8 +250,7 @@ void Application::onChangeTray(bool enabled, bool hide)
     }
 }
 
-void Application::createTrayIcon()
-{
+void Application::createTrayIcon() {
     trayIcon = new QSystemTrayIcon();
 
     QMenu* trayIconMenu = new QMenu();
@@ -279,8 +267,7 @@ void Application::createTrayIcon()
     trayIcon->show();
 }
 
-void Application::trayClicked(QSystemTrayIcon::ActivationReason reason)
-{
+void Application::trayClicked(QSystemTrayIcon::ActivationReason reason) {
     switch (reason) {
         case QSystemTrayIcon::Trigger:
         case QSystemTrayIcon::DoubleClick:
@@ -292,8 +279,7 @@ void Application::trayClicked(QSystemTrayIcon::ActivationReason reason)
     }
 }
 
-void Application::toggleSquawk()
-{
+void Application::toggleSquawk() {
     QSettings settings;
     if (squawk == nullptr) {
         createMainWindow();
@@ -308,34 +294,27 @@ void Application::toggleSquawk()
     }
 }
 
-void Application::onItemCollapsed(const QModelIndex& index)
-{
+void Application::onItemCollapsed(const QModelIndex& index) {
     std::list<QString> address = roster.getItemPath(index);
-    if (address.size() > 0) {
+    if (address.size() > 0)
         expandedPaths.erase(address);
-    }
 }
 
-void Application::onItemExpanded(const QModelIndex& index)
-{
+void Application::onItemExpanded(const QModelIndex& index) {
     std::list<QString> address = roster.getItemPath(index);
-    if (address.size() > 0) {
+    if (address.size() > 0)
         expandedPaths.insert(address);
-    }
 }
 
-void Application::onAddedElement(const std::list<QString>& path)
-{
+void Application::onAddedElement(const std::list<QString>& path) {
     if (squawk != nullptr && expandedPaths.count(path) > 0) {
         QModelIndex index = roster.getIndexByPath(path);
-        if (index.isValid()) {
+        if (index.isValid())
             squawk->expand(index);
-        }
     }
 }
 
-void Application::notify(const QString& account, const Shared::Message& msg)
-{
+void Application::notify(const QString& account, const Shared::Message& msg) {
     QString jid = msg.getPenPalJid();
     QString name = QString(roster.getContactName(account, jid));
     QString path = QString(roster.getContactIconPath(account, jid, msg.getPenPalResource()));
@@ -344,16 +323,15 @@ void Application::notify(const QString& account, const Shared::Message& msg)
 
     uint32_t notificationId = qHash(msg.getId());
     args << notificationId;
-    if (path.size() > 0) {
+    if (path.size() > 0)
         args << path;
-    } else {
+    else
         args << QString("mail-message");    //TODO should here better be unknown user icon?
-    }
-    if (msg.getType() == Shared::Message::groupChat) {
+
+    if (msg.getType() == Shared::Message::groupChat)
         args << msg.getFromResource() + tr(" from ") + name;
-    } else {
+    else
         args << name;
-    }
 
     QString body(msg.getBody());
     QString oob(msg.getOutOfBandUrl());
@@ -378,13 +356,11 @@ void Application::notify(const QString& account, const Shared::Message& msg)
 
     storage.insert(std::make_pair(notificationId, std::make_pair(Models::Roster::ElId(account, name), msg.getId())));
 
-    if (squawk != nullptr) {
+    if (squawk != nullptr)
         QApplication::alert(squawk);
-    }
 }
 
-void Application::onNotificationClosed(quint32 id, quint32 reason)
-{
+void Application::onNotificationClosed(quint32 id, quint32 reason) {
     Notifications::const_iterator itr = storage.find(id);
     if (itr != storage.end()) {
         if (reason == 2) {  //dissmissed by user (https://specifications.freedesktop.org/notification-spec/latest/ar01s09.html)
@@ -397,21 +373,18 @@ void Application::onNotificationClosed(quint32 id, quint32 reason)
     }
 }
 
-void Application::onNotificationInvoked(quint32 id, const QString& action)
-{
+void Application::onNotificationInvoked(quint32 id, const QString& action) {
     qDebug() << "Notification" << id << action << "request";
     Notifications::const_iterator itr = storage.find(id);
     if (itr != storage.end()) {
-        if (action == "markAsRead") {
+        if (action == "markAsRead")
             roster.markMessageAsRead(itr->second.first, itr->second.second);
-        } else if (action == "openConversation") {
+        else if (action == "openConversation")
             focusConversation(itr->second.first, "", itr->second.second);
-        }
     }
 }
 
-void Application::unreadMessagesCountChanged(int count)
-{
+void Application::unreadMessagesCountChanged(int count) {
     QDBusMessage signal = QDBusMessage::createSignal("/", "com.canonical.Unity.LauncherEntry", "Update");
     signal << qApp->desktopFileName() + QLatin1String(".desktop");
     signal << QVariantMap ({
@@ -421,54 +394,49 @@ void Application::unreadMessagesCountChanged(int count)
     QDBusConnection::sessionBus().send(signal);
 }
 
-void Application::focusConversation(const Models::Roster::ElId& id, const QString& resource, const QString& messageId)
-{
+void Application::focusConversation(const Models::Roster::ElId& id, const QString& resource, const QString& messageId) {
     if (squawk != nullptr) {
         if (squawk->currentConversationId() != id) {
             QModelIndex index = roster.getContactIndex(id.account, id.name, resource);
             squawk->select(index);
         }
 
-        if (squawk->isMinimized()) {
+        if (squawk->isMinimized())
             squawk->showNormal();
-        } else {
+        else
             squawk->show();
-        }
+
         squawk->raise();
         squawk->activateWindow();
     } else {
         openConversation(id, resource);
     }
 
+    SHARED_UNUSED(messageId);
     //TODO focus messageId;
 }
 
-void Application::setState(Shared::Availability p_availability)
-{
+void Application::setState(Shared::Availability p_availability) {
     if (availability != p_availability) {
         availability = p_availability;
         emit changeState(availability);
     }
 }
 
-void Application::stateChanged(Shared::Availability state)
-{
+void Application::stateChanged(Shared::Availability state) {
     availability = state;
-    if (squawk != nullptr) {
+    if (squawk != nullptr)
         squawk->stateChanged(state);
-    }
 }
 
-void Application::readSettings()
-{
+void Application::readSettings() {
     QSettings settings;
     settings.beginGroup("ui");
     int avail;
-    if (settings.contains("availability")) {
+    if (settings.contains("availability"))
         avail = settings.value("availability").toInt();
-    } else {
+    else
         avail = static_cast<int>(Shared::Availability::online);
-    }
 
     settings.beginGroup("roster");
     QStringList entries = settings.allKeys();
@@ -486,13 +454,11 @@ void Application::readSettings()
     setState(Shared::Global::fromInt<Shared::Availability>(avail));
     createMainWindow();
 
-    if (settings.value("tray", false).toBool() && !settings.value("hideTray", false).toBool()) {
+    if (settings.value("tray", false).toBool() && !settings.value("hideTray", false).toBool())
         createTrayIcon();
-    }
 }
 
-void Application::writeSettings()
-{
+void Application::writeSettings() {
     QSettings settings;
     settings.beginGroup("ui");
         settings.setValue("availability", static_cast<int>(availability));
@@ -501,9 +467,9 @@ void Application::writeSettings()
         settings.beginGroup("roster");
             for (const std::list<QString>& address : expandedPaths) {
                 QString path = "";
-                for (const QString& hop : address) {
+                for (const QString& hop : address)
                     path += hop + "/";
-                }
+
                 path += "expanded";
                 settings.setValue(path, true);
             }
@@ -513,54 +479,49 @@ void Application::writeSettings()
 }
 
 void Application::requestPassword(const QString& account, bool authenticationError) {
-    if (authenticationError) {
+    if (authenticationError)
         dialogueQueue.addAction(account, DialogQueue::askCredentials);
-    } else {
+    else
         dialogueQueue.addAction(account, DialogQueue::askPassword);
-    }
-
 }
-void Application::onConversationClosed()
-{
+
+void Application::onConversationClosed() {
     Conversation* conv = static_cast<Conversation*>(sender());
     Models::Roster::ElId id(conv->getAccount(), conv->getJid());
     Conversations::const_iterator itr = conversations.find(id);
-    if (itr != conversations.end()) {
+    if (itr != conversations.end())
         conversations.erase(itr);
-    }
+
     if (conv->isMuc) {
         Room* room = static_cast<Room*>(conv);
-        if (!room->autoJoined()) {
+        if (!room->autoJoined())
             emit setRoomJoined(id.account, id.name, false);
-        }
     }
 }
 
-void Application::changeSubscription(const Models::Roster::ElId& id, bool subscribe)
-{
+void Application::changeSubscription(const Models::Roster::ElId& id, bool subscribe) {
     Models::Item::Type type = roster.getContactType(id);
 
     switch (type) {
         case Models::Item::contact:
-            if (subscribe) {
+            if (subscribe)
                 emit subscribeContact(id.account, id.name, "");
-            } else {
+            else
                 emit unsubscribeContact(id.account, id.name, "");
-            }
+
             break;
         case Models::Item::room:
             setRoomAutoJoin(id.account, id.name, subscribe);
-            if (!isConverstationOpened(id)) {
+            if (!isConverstationOpened(id))
                 emit setRoomJoined(id.account, id.name, subscribe);
-            }
+
             break;
         default:
             break;
     }
 }
 
-void Application::subscribeConversation(Conversation* conv)
-{
+void Application::subscribeConversation(Conversation* conv) {
     connect(conv, &Conversation::destroyed, this, &Application::onConversationClosed);
     connect(conv, &Conversation::sendMessage, this, &Application::onConversationMessage);
     connect(conv, &Conversation::replaceMessage, this, &Application::onConversationReplaceMessage);
@@ -568,8 +529,7 @@ void Application::subscribeConversation(Conversation* conv)
     connect(conv, &Conversation::notifyableMessage, this, &Application::notify);
 }
 
-void Application::openConversation(const Models::Roster::ElId& id, const QString& resource)
-{
+void Application::openConversation(const Models::Roster::ElId& id, const QString& resource) {
     Conversations::const_iterator itr = conversations.find(id);
     Models::Account* acc = roster.getAccount(id.account);
     Conversation* conv = nullptr;
@@ -583,9 +543,8 @@ void Application::openConversation(const Models::Roster::ElId& id, const QString
                 created = true;
                 Models::Room* room = static_cast<Models::Room*>(el);
                 conv = new Room(acc, room);
-                if (!room->getJoined()) {
+                if (!room->getJoined())
                     emit setRoomJoined(id.account, id.name, true);
-                }
             } else if (el->type == Models::Item::contact) {
                 created = true;
                 conv = new Chat(acc, static_cast<Models::Contact*>(el));
@@ -604,14 +563,12 @@ void Application::openConversation(const Models::Roster::ElId& id, const QString
         conv->raise();
         conv->activateWindow();
 
-        if (resource.size() > 0) {
+        if (resource.size() > 0)
             conv->setPalResource(resource);
-        }
     }
 }
 
-void Application::onConversationMessage(const Shared::Message& msg)
-{
+void Application::onConversationMessage(const Shared::Message& msg) {
     Conversation* conv = static_cast<Conversation*>(sender());
     QString acc = conv->getAccount();
 
@@ -619,8 +576,7 @@ void Application::onConversationMessage(const Shared::Message& msg)
     emit sendMessage(acc, msg);
 }
 
-void Application::onConversationReplaceMessage(const QString& originalId, const Shared::Message& msg)
-{
+void Application::onConversationReplaceMessage(const QString& originalId, const Shared::Message& msg) {
     Conversation* conv = static_cast<Conversation*>(sender());
     QString acc = conv->getAccount();
 
@@ -630,8 +586,7 @@ void Application::onConversationReplaceMessage(const QString& originalId, const
     emit replaceMessage(acc, originalId, msg);
 }
 
-void Application::onConversationResend(const QString& id)
-{
+void Application::onConversationResend(const QString& id) {
     Conversation* conv = static_cast<Conversation*>(sender());
     QString acc = conv->getAccount();
     QString jid = conv->getJid();
@@ -649,8 +604,7 @@ void Application::onSquawkOpenedConversation() {
     }
 }
 
-void Application::removeAccount(const QString& account)
-{
+void Application::removeAccount(const QString& account) {
     Conversations::const_iterator itr = conversations.begin();
     while (itr != conversations.end()) {
         if (itr->first.account == account) {
@@ -665,23 +619,20 @@ void Application::removeAccount(const QString& account)
         }
     }
 
-    if (squawk != nullptr && squawk->currentConversationId().account == account) {
+    if (squawk != nullptr && squawk->currentConversationId().account == account)
         squawk->closeCurrentConversation();
-    }
 
     roster.removeAccount(account);
 }
 
-void Application::changeAccount(const QString& account, const QMap<QString, QVariant>& data)
-{
+void Application::changeAccount(const QString& account, const QMap<QString, QVariant>& data) {
     for (QMap<QString, QVariant>::const_iterator itr = data.begin(), end = data.end(); itr != end; ++itr) {
         QString attr = itr.key();
         roster.updateAccount(account, attr, *itr);
     }
 }
 
-void Application::addGroup(const QString& account, const QString& name)
-{
+void Application::addGroup(const QString& account, const QString& name) {
     roster.addGroup(account, name);
 
     if (squawk != nullptr) {
@@ -692,9 +643,8 @@ void Application::addGroup(const QString& account, const QString& name)
         if (settings.value("expanded", false).toBool()) {
             QModelIndex ind = roster.getAccountIndex(account);
             squawk->expand(ind);
-            if (settings.value(name + "/expanded", false).toBool()) {
+            if (settings.value(name + "/expanded", false).toBool())
                 squawk->expand(roster.getGroupIndex(account, name));
-            }
         }
         settings.endGroup();
         settings.endGroup();
diff --git a/shared/CMakeLists.txt b/shared/CMakeLists.txt
index 4bcc12f..4ba24ea 100644
--- a/shared/CMakeLists.txt
+++ b/shared/CMakeLists.txt
@@ -37,6 +37,7 @@ set(HEADER_FILES
   info.h
   clientid.h
   trustsummary.h
+  defines.h
 )
 
 target_sources(squawk PRIVATE
diff --git a/shared/defines.h b/shared/defines.h
new file mode 100644
index 0000000..227a714
--- /dev/null
+++ b/shared/defines.h
@@ -0,0 +1,24 @@
+/*
+ * Squawk messenger.
+ * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+ *
+ * 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/>.
+ */
+
+#ifndef SHARED_DEFINES_H
+#define SHARED_DEFINES_H
+
+#define SHARED_UNUSED(x) (void)(x)
+
+#endif
diff --git a/shared/identity.cpp b/shared/identity.cpp
index a0a4f0a..ef616d8 100644
--- a/shared/identity.cpp
+++ b/shared/identity.cpp
@@ -121,6 +121,8 @@ QDataStream & Shared::Identity::operator >> (QDataStream& stream) const {
     stream << type;
     stream << language;
     stream << name;
+
+    return stream;
 }
 
 QDataStream & Shared::Identity::operator << (QDataStream& stream) {
@@ -128,6 +130,8 @@ QDataStream & Shared::Identity::operator << (QDataStream& stream) {
     stream >> type;
     stream >> language;
     stream >> name;
+
+    return stream;
 }
 
 QDataStream & operator >> (QDataStream& stream, Shared::Identity& identity) {
diff --git a/shared/shared.h b/shared/shared.h
index 68e5c8d..ba9a2ed 100644
--- a/shared/shared.h
+++ b/shared/shared.h
@@ -19,6 +19,7 @@
 #ifndef SHARED_H
 #define SHARED_H
 
+#include "defines.h"
 #include "enums.h"
 #include "global.h"
 #include "icons.h"
diff --git a/ui/models/accounts.cpp b/ui/models/accounts.cpp
index 8a9e5d9..e401b3a 100644
--- a/ui/models/accounts.cpp
+++ b/ui/models/accounts.cpp
@@ -18,6 +18,7 @@
 
 #include "accounts.h"
 #include "shared/icons.h"
+#include "shared/defines.h"
 
 #include <QIcon>
 #include <QDebug>
@@ -26,32 +27,24 @@ std::deque<QString> Models::Accounts::columns = {"Name", "Server", "State", "Err
 
 Models::Accounts::Accounts(QObject* parent):
     QAbstractTableModel(parent),
-    accs()
-{
+    accs() {}
 
-}
+Models::Accounts::~Accounts() {}
 
-Models::Accounts::~Accounts()
-{
-
-}
-
-QVariant Models::Accounts::data (const QModelIndex& index, int role) const
-{
+QVariant Models::Accounts::data (const QModelIndex& index, int role) const {
     QVariant answer;
     switch (role) {
         case Qt::DisplayRole: 
             answer = accs[index.row()]->data(index.column());
             break;
         case Qt::DecorationRole:
-            if (index.column() == 2) {
+            if (index.column() == 2)
                 answer = Shared::connectionStateIcon(accs[index.row()]->getState());
-            }
             break;
         case Qt::ForegroundRole:
-            if (!accs[index.row()]->getActive()) {
+            if (!accs[index.row()]->getActive())
                 answer = qApp->palette().brush(QPalette::Disabled, QPalette::Text);
-            }
+            break;
         default:
             break;
     }
@@ -59,53 +52,46 @@ QVariant Models::Accounts::data (const QModelIndex& index, int role) const
     return answer;
 }
 
-int Models::Accounts::columnCount ( const QModelIndex& parent ) const
-{
+int Models::Accounts::columnCount (const QModelIndex& parent) const {
+    SHARED_UNUSED(parent);
     return columns.size();
 }
 
-int Models::Accounts::rowCount ( const QModelIndex& parent ) const
-{
+int Models::Accounts::rowCount (const QModelIndex& parent) const {
+    SHARED_UNUSED(parent);
     return accs.size();
 }
 
-unsigned int Models::Accounts::size() const
-{
+unsigned int Models::Accounts::size() const {
     return rowCount(QModelIndex());
 }
 
-unsigned int Models::Accounts::activeSize() const
-{
+unsigned int Models::Accounts::activeSize() const {
     unsigned int size = 0;
     for (const Models::Account* acc : accs) {
-        if (acc->getActive()) {
+        if (acc->getActive())
             ++size;
-        }
     }
 
     return size;
 }
 
-QVariant Models::Accounts::headerData(int section, Qt::Orientation orientation, int role) const
-{
-    if (role == Qt::DisplayRole && orientation == Qt::Horizontal) {
+QVariant Models::Accounts::headerData(int section, Qt::Orientation orientation, int role) const {
+    if (role == Qt::DisplayRole && orientation == Qt::Horizontal)
         return tr(columns[section].toLatin1());
-    }
+
     return QVariant();
 }
 
 
-void Models::Accounts::addAccount(Account* account)
-{
+void Models::Accounts::addAccount(Account* account) {
     beginInsertRows(QModelIndex(), accs.size(), accs.size());
-    int index = 0;
     std::deque<Account*>::const_iterator before = accs.begin();
     while (before != accs.end()) {
         Account* bfr = *before;
-        if (bfr->getDisplayedName() > account->getDisplayedName()) {
+        if (bfr->getDisplayedName() > account->getDisplayedName())
             break;
-        }
-        index++;
+
         before++;
     }
     
@@ -117,25 +103,23 @@ void Models::Accounts::addAccount(Account* account)
     emit changed();
 }
 
-void Models::Accounts::onAccountChanged(Item* item, int row, int col)
-{
-    if (row < 0) {
+void Models::Accounts::onAccountChanged(Item* item, int row, int col) {
+    if (row < 0)
         return;
-    }
+
     if (static_cast<std::deque<Models::Account*>::size_type>(row) < accs.size()) {
         Account* acc = getAccount(row);
-        if (item != acc) {
+        if (item != acc)
             return;     //it means the signal is emitted by one of accounts' children, not exactly him, this model has no interest in that
-        }
         
         if (col == 0) {
             int newRow = 0;
             std::deque<Account*>::const_iterator before = accs.begin();
             while (before != accs.end()) {
                 Item* bfr = *before;
-                if (bfr->getDisplayedName() > item->getDisplayedName()) {
+                if (bfr->getDisplayedName() > item->getDisplayedName())
                     break;
-                }
+
                 newRow++;
                 before++;
             }
@@ -152,20 +136,18 @@ void Models::Accounts::onAccountChanged(Item* item, int row, int col)
             }
         }
         
-        if (col < columnCount(QModelIndex())) {
+        if (col < columnCount(QModelIndex()))
             emit dataChanged(createIndex(row, col), createIndex(row, col));
-        }
+
         emit changed();
     }
 }
 
-Models::Account * Models::Accounts::getAccount(int index)
-{
+Models::Account * Models::Accounts::getAccount(int index) {
     return accs[index];
 }
 
-void Models::Accounts::removeAccount(int index)
-{
+void Models::Accounts::removeAccount(int index) {
     Account* account = accs[index];
     beginRemoveRows(QModelIndex(), index, index);
     disconnect(account, &Account::childChanged, this, &Accounts::onAccountChanged);
@@ -175,14 +157,13 @@ void Models::Accounts::removeAccount(int index)
     emit sizeChanged(accs.size());
 }
 
-std::deque<QString> Models::Accounts::getActiveNames() const
-{
+std::deque<QString> Models::Accounts::getActiveNames() const {
     std::deque<QString> res;
     
     for (const Models::Account* acc : accs) {
-        if (acc->getActive()) {
+        if (acc->getActive())
             res.push_back(acc->getName());
-        }
+
     }
     
     return res;
diff --git a/ui/models/info/emails.cpp b/ui/models/info/emails.cpp
index 4bb8a17..694c0c9 100644
--- a/ui/models/info/emails.cpp
+++ b/ui/models/info/emails.cpp
@@ -19,6 +19,7 @@
 #include "emails.h"
 
 #include "shared/icons.h"
+#include "shared/defines.h"
 #include <QCoreApplication>
 
 Models::EMails::EMails(bool p_edit, QObject* parent):
@@ -27,10 +28,15 @@ Models::EMails::EMails(bool p_edit, QObject* parent):
     deque() {}
 
 int Models::EMails::columnCount(const QModelIndex& parent) const {
-    return 3;}
+    SHARED_UNUSED(parent);
+    return 3;
+}
 
 int Models::EMails::rowCount(const QModelIndex& parent) const {
-    return deque.size();}
+    SHARED_UNUSED(parent);
+    return deque.size();
+
+}
 
 void Models::EMails::revertPreferred(quint32 row) {
     setData(createIndex(row, 2), !isPreferred(row));}
@@ -83,9 +89,9 @@ QVariant Models::EMails::data(const QModelIndex& index, int role) const {
 
 Qt::ItemFlags Models::EMails::flags(const QModelIndex& index) const {
     Qt::ItemFlags f = QAbstractTableModel::flags(index);
-    if (edit && index.column() != 2) {
+    if (edit && index.column() != 2)
         f = Qt::ItemIsEditable | f;
-    }
+
     return  f;
 }
 
@@ -114,9 +120,9 @@ bool Models::EMails::setData(const QModelIndex& index, const QVariant& value, in
                 return true;
             case 1: {
                 quint8 newRole = value.toUInt();
-                if (newRole > Shared::VCard::Email::work) {
+                if (newRole > Shared::VCard::Email::work)
                     return false;
-                }
+
                 item.role = static_cast<Shared::VCard::Email::Role>(newRole);
                 return true;
             }
@@ -160,19 +166,17 @@ QModelIndex Models::EMails::addNewEmptyLine() {
 }
 
 bool Models::EMails::isPreferred(quint32 row) const {
-    if (row < deque.size()) {
+    if (row < deque.size())
         return deque[row].prefered;
-    } else {
+    else
         return false;
-    }
 }
 
 void Models::EMails::removeLines(quint32 index, quint32 count) {
     if (index < deque.size()) {
         quint32 maxCount = deque.size() - index;
-        if (count > maxCount) {
+        if (count > maxCount)
             count = maxCount;
-        }
         
         if (count > 0) {
             beginRemoveRows(QModelIndex(), index, index + count - 1);
@@ -185,21 +189,19 @@ void Models::EMails::removeLines(quint32 index, quint32 count) {
 }
 
 void Models::EMails::getEmails(std::deque<Shared::VCard::Email>& emails) const {
-    for (const Shared::VCard::Email& my : deque) {
+    for (const Shared::VCard::Email& my : deque)
         emails.emplace_back(my);
-    }
 }
 
 void Models::EMails::setEmails(const std::deque<Shared::VCard::Email>& emails) {
-    if (deque.size() > 0) {
+    if (deque.size() > 0)
         removeLines(0, deque.size());
-    }
     
     if (emails.size() > 0) {
         beginInsertRows(QModelIndex(), 0, emails.size() - 1);
-        for (const Shared::VCard::Email& comming : emails) {
+        for (const Shared::VCard::Email& comming : emails)
             deque.emplace_back(comming);
-        }
+
         endInsertRows();
     }
 }
diff --git a/ui/models/info/omemo/keys.cpp b/ui/models/info/omemo/keys.cpp
index e4dc8d2..5d957b1 100644
--- a/ui/models/info/omemo/keys.cpp
+++ b/ui/models/info/omemo/keys.cpp
@@ -16,6 +16,8 @@
 
 #include "keys.h"
 
+#include "shared/defines.h"
+
 const QHash<int, QByteArray> Models::Keys::roles = {
     {Label, "label"},
     {FingerPrint, "fingerPrint"},
@@ -28,13 +30,11 @@ Models::Keys::Keys(QObject* parent):
     modified() {}
 
 Models::Keys::~Keys() {
-    for (Shared::KeyInfo* key : keys) {
+    for (Shared::KeyInfo* key : keys)
         delete key;
-    }
 
-    for (std::pair<const int, Shared::KeyInfo*>& pair: modified) {
+    for (std::pair<const int, Shared::KeyInfo*>& pair: modified)
         delete pair.second;
-    }
 }
 
 std::deque<Shared::KeyInfo> Models::Keys::modifiedKeys() const {
@@ -92,15 +92,15 @@ QVariant Models::Keys::data(const QModelIndex& index, int role) const {
 }
 
 int Models::Keys::rowCount(const QModelIndex& parent) const {
+    SHARED_UNUSED(parent);
     return keys.size();
 }
 
 QHash<int, QByteArray> Models::Keys::roleNames() const {return roles;}
 
 QModelIndex Models::Keys::index(int row, int column, const QModelIndex& parent) const {
-    if (!hasIndex(row, column, parent)) {
+    if (!hasIndex(row, column, parent))
         return QModelIndex();
-    }
 
     return createIndex(row, column, keys[row]);
 }
diff --git a/ui/models/info/phones.cpp b/ui/models/info/phones.cpp
index 99c52c2..2b6523a 100644
--- a/ui/models/info/phones.cpp
+++ b/ui/models/info/phones.cpp
@@ -19,6 +19,7 @@
 #include "phones.h"
 
 #include "shared/icons.h"
+#include "shared/defines.h"
 #include <QCoreApplication>
 
 Models::Phones::Phones(bool p_edit, QObject* parent):
@@ -27,10 +28,14 @@ Models::Phones::Phones(bool p_edit, QObject* parent):
     deque() {}
 
 int Models::Phones::columnCount(const QModelIndex& parent) const {
-    return 4;}
+    SHARED_UNUSED(parent);
+    return 4;
+}
 
 int Models::Phones::rowCount(const QModelIndex& parent) const {
-    return deque.size();}
+    SHARED_UNUSED(parent);
+    return deque.size();
+}
 
 void Models::Phones::revertPreferred(quint32 row) {
     setData(createIndex(row, 3), !isPreferred(row));
@@ -77,9 +82,9 @@ QVariant Models::Phones::data(const QModelIndex& index, int role) const {
                     case Qt::DisplayRole:
                         return QVariant();
                     case Qt::DecorationRole:
-                        if (deque[index.row()].prefered) {
+                        if (deque[index.row()].prefered)
                             return Shared::icon("favorite", false);
-                        }
+
                         return QVariant();
                     default:
                         return QVariant();
@@ -101,9 +106,9 @@ QModelIndex Models::Phones::addNewEmptyLine() {
 
 Qt::ItemFlags Models::Phones::flags(const QModelIndex& index) const {
     Qt::ItemFlags f = QAbstractTableModel::flags(index);
-    if (edit && index.column() != 3) {
+    if (edit && index.column() != 3)
         f = Qt::ItemIsEditable | f;
-    }
+
     return  f;
 }
 
@@ -139,25 +144,22 @@ bool Models::Phones::dropPrefered() {
 }
 
 void Models::Phones::getPhones(std::deque<Shared::VCard::Phone>& phones) const {
-    for (const Shared::VCard::Phone& my : deque) {
+    for (const Shared::VCard::Phone& my : deque)
         phones.emplace_back(my);
-    }
 }
 
 bool Models::Phones::isPreferred(quint32 row) const {
-    if (row < deque.size()) {
+    if (row < deque.size())
         return deque[row].prefered;
-    } else {
+    else
         return false;
-    }
 }
 
 void Models::Phones::removeLines(quint32 index, quint32 count) {
     if (index < deque.size()) {
         quint32 maxCount = deque.size() - index;
-        if (count > maxCount) {
+        if (count > maxCount)
             count = maxCount;
-        }
         
         if (count > 0) {
             beginRemoveRows(QModelIndex(), index, index + count - 1);
@@ -178,17 +180,17 @@ bool Models::Phones::setData(const QModelIndex& index, const QVariant& value, in
                 return true;
             case 1: {
                 quint8 newRole = value.toUInt();
-                if (newRole > Shared::VCard::Phone::work) {
+                if (newRole > Shared::VCard::Phone::work)
                     return false;
-                }
+
                 item.role = static_cast<Shared::VCard::Phone::Role>(newRole);
                 return true;
             }
             case 2: {
                 quint8 newType = value.toUInt();
-                if (newType > Shared::VCard::Phone::other) {
+                if (newType > Shared::VCard::Phone::other)
                     return false;
-                }
+
                 item.type = static_cast<Shared::VCard::Phone::Type>(newType);
                 return true;
             }
@@ -209,15 +211,15 @@ bool Models::Phones::setData(const QModelIndex& index, const QVariant& value, in
 }
 
 void Models::Phones::setPhones(const std::deque<Shared::VCard::Phone>& phones) {
-    if (deque.size() > 0) {
+    if (deque.size() > 0)
         removeLines(0, deque.size());
-    }
+
     
     if (phones.size() > 0) {
         beginInsertRows(QModelIndex(), 0, phones.size() - 1);
-        for (const Shared::VCard::Phone& comming : phones) {
+        for (const Shared::VCard::Phone& comming : phones)
             deque.emplace_back(comming);
-        }
+
         endInsertRows();
     }
 }
diff --git a/ui/models/roster.cpp b/ui/models/roster.cpp
index 43717f8..0f35baa 100644
--- a/ui/models/roster.cpp
+++ b/ui/models/roster.cpp
@@ -21,6 +21,8 @@
 #include <QIcon>
 #include <QFont>
 
+#include "shared/defines.h"
+
 Models::Roster::Roster(QObject* parent):
     QAbstractItemModel(parent),
     accountsModel(new Accounts()),
@@ -39,14 +41,12 @@ Models::Roster::Roster(QObject* parent):
     connect(root, &Item::childMoved, this, &Roster::onChildMoved);
 }
 
-Models::Roster::~Roster()
-{
+Models::Roster::~Roster() {
     delete accountsModel;
     delete root;
 }
 
-void Models::Roster::addAccount(const QMap<QString, QVariant>& data)
-{
+void Models::Roster::addAccount(const QMap<QString, QVariant>& data) {
     Account* acc = new Account(data);
     connect(acc, &Account::reconnected, this, &Roster::onAccountReconnected);
     root->appendChild(acc);
@@ -56,24 +56,21 @@ void Models::Roster::addAccount(const QMap<QString, QVariant>& data)
     emit addedElement({acc->getId()});
 }
 
-QVariant Models::Roster::data (const QModelIndex& index, int role) const
-{
-    if (!index.isValid()) {
+QVariant Models::Roster::data (const QModelIndex& index, int role) const {
+    if (!index.isValid())
         return QVariant();
-    }
     
     QVariant result;
     
     Item *item = static_cast<Item*>(index.internalPointer());
-    if (item->type == Item::reference) {
+    if (item->type == Item::reference)
         item = static_cast<Reference*>(item)->dereference();
-    }
+
     switch (role) {
-        case Qt::DisplayRole:
-        {
-            if (index.column() != 0) {
+        case Qt::DisplayRole: {
+            if (index.column() != 0)
                 break;
-            }
+
             switch (item->type) {
                 case Item::group: {
                     Group* gr = static_cast<Group*>(item);
@@ -81,9 +78,9 @@ QVariant Models::Roster::data (const QModelIndex& index, int role) const
                     
                     str += gr->getName();
                     unsigned int amount = gr->getUnreadMessages();
-                    if (amount > 0) {
+                    if (amount > 0)
                         str += QString(" (") +  tr("New messages") + ")";
-                    }
+
                     
                     result = str;
                 }
@@ -104,9 +101,8 @@ QVariant Models::Roster::data (const QModelIndex& index, int role) const
                     } else if (col == 1) {
                         QString path = acc->getAvatarPath();
                         
-                        if (path.size() > 0) {
+                        if (path.size() > 0)
                             result = QIcon(path);
-                        }
                     }
                 }
                     break;
@@ -116,16 +112,15 @@ QVariant Models::Roster::data (const QModelIndex& index, int role) const
                     if (col == 0) {
                         result = contact->getStatusIcon(false);
                     } else if (col == 1) {
-                        if (contact->getAvatarState() != Shared::Avatar::empty) {
+                        if (contact->getAvatarState() != Shared::Avatar::empty)
                             result = QIcon(contact->getAvatarPath());
-                        }
                     }
                 }
                     break;
                 case Item::presence: {
-                    if (index.column() != 0) {
+                    if (index.column() != 0)
                         break;
-                    }
+
                     Presence* presence = static_cast<Presence*>(item);
                     result = presence->getStatusIcon(false);
                 }
@@ -138,9 +133,8 @@ QVariant Models::Roster::data (const QModelIndex& index, int role) const
                     } else if (col == 1) {
                         QString path = room->getAvatarPath();
                         
-                        if (path.size() > 0) {
+                        if (path.size() > 0)
                             result = QIcon(path);
-                        }
                     }
                 }
                     break;
@@ -151,14 +145,11 @@ QVariant Models::Roster::data (const QModelIndex& index, int role) const
                         result = p->getStatusIcon(false);
                     } else if (col == 1) {
                         QString path = p->getAvatarPath();
-                        if (path.size() > 0) {
+                        if (path.size() > 0)
                             result = QIcon(path);
-                        }
                     }
-                    if (index.column() != 0) {
+                    if (index.column() != 0)
                         break;
-                    }
-                    
                 }
                     break;
                 default:
@@ -194,9 +185,9 @@ QVariant Models::Roster::data (const QModelIndex& index, int role) const
                     Contact* contact = static_cast<Contact*>(item);
                     QString str("");
                     int mc = contact->getMessagesCount();
-                    if (mc > 0) {
+                    if (mc > 0)
                         str += QString(tr("New messages: ")) + std::to_string(mc).c_str() + "\n";
-                    }
+
                     str += tr("Jabber ID: ") + contact->getJid() + "\n";
                     Shared::SubscriptionState ss = contact->getState();
                     if (ss == Shared::SubscriptionState::both || ss == Shared::SubscriptionState::to) {
@@ -204,9 +195,8 @@ QVariant Models::Roster::data (const QModelIndex& index, int role) const
                         str += tr("Availability: ") + Shared::Global::getName(av);
                         if (av != Shared::Availability::offline) {
                             QString s = contact->getStatus();
-                            if (s.size() > 0) {
+                            if (s.size() > 0)
                                 str += "\n" + tr("Status: ") + s;
-                            }
                         }
                         str += "\n" + tr("Subscription: ") + Shared::Global::getName(ss);
                     } else {
@@ -222,9 +212,9 @@ QVariant Models::Roster::data (const QModelIndex& index, int role) const
                     Shared::Availability av = contact->getAvailability();
                     str += tr("Availability: ") + Shared::Global::getName(av);
                     QString s = contact->getStatus();
-                    if (s.size() > 0) {
+                    if (s.size() > 0)
                         str += "\n" + tr("Status: ") + s;
-                    }
+
                     str += "\n" + tr("Client: ") + contact->getClientNode();
                     
                     result = str;
@@ -236,9 +226,8 @@ QVariant Models::Roster::data (const QModelIndex& index, int role) const
                     Shared::Availability av = p->getAvailability();
                     str += tr("Availability: ") + Shared::Global::getName(av) + "\n";
                     QString s = p->getStatus();
-                    if (s.size() > 0) {
+                    if (s.size() > 0)
                         str += tr("Status: ") + s + "\n";
-                    }
                     
                     str += tr("Affiliation: ") + Shared::Global::getName(p->getAffiliation()) + "\n";
                     str += tr("Role: ") + Shared::Global::getName(p->getRole()) + "\n";
@@ -251,9 +240,9 @@ QVariant Models::Roster::data (const QModelIndex& index, int role) const
                     Group* gr = static_cast<Group*>(item);
                     unsigned int count = gr->getUnreadMessages();
                     QString str("");
-                    if (count > 0) {
+                    if (count > 0)
                         str += tr("New messages: ") + std::to_string(count).c_str() + "\n";
-                    }
+
                     str += tr("Online contacts: ") + std::to_string(gr->getOnlineContacts()).c_str() + "\n";
                     str += tr("Total contacts: ") + std::to_string(gr->childCount()).c_str();
                     result = str;
@@ -263,15 +252,14 @@ QVariant Models::Roster::data (const QModelIndex& index, int role) const
                     Room* rm = static_cast<Room*>(item);
                     unsigned int count = rm->getMessagesCount();
                     QString str("");
-                    if (count > 0) {
+                    if (count > 0)
                         str += tr("New messages: ") + std::to_string(count).c_str() + "\n";
-                    }
                     
                     str += tr("Jabber ID: ") + rm->getJid() + "\n";
                     str += tr("Subscription: ") + rm->getStatusText();
-                    if (rm->getJoined()) {
+                    if (rm->getJoined())
                         str += QString("\n") + tr("Members: ") + std::to_string(rm->childCount()).c_str();
-                    }
+
                     result = str;
                 }
                     break;
@@ -284,9 +272,8 @@ QVariant Models::Roster::data (const QModelIndex& index, int role) const
             switch (item->type) {
                 case Item::account: {
                     Account* acc = static_cast<Account*>(item);
-                    if (!acc->getActive()) {
+                    if (!acc->getActive())
                         result = qApp->palette().brush(QPalette::Disabled, QPalette::Text);
-                    }
                 }
                     break;
                 default:
@@ -299,17 +286,14 @@ QVariant Models::Roster::data (const QModelIndex& index, int role) const
     return result;
 }
 
-int Models::Roster::columnCount (const QModelIndex& parent) const
-{
-    if (parent.isValid()) {
+int Models::Roster::columnCount (const QModelIndex& parent) const {
+    if (parent.isValid())
         return static_cast<Item*>(parent.internalPointer())->columnCount();
-    } else {
+    else
         return root->columnCount();
-    }
 }
 
-void Models::Roster::updateAccount(const QString& account, const QString& field, const QVariant& value)
-{
+void Models::Roster::updateAccount(const QString& account, const QString& field, const QVariant& value) {
     std::map<QString, Account*>::iterator itr = accounts.find(account);
     if (itr != accounts.end()) {
         Account* acc = itr->second;
@@ -317,75 +301,63 @@ void Models::Roster::updateAccount(const QString& account, const QString& field,
     }
 }
 
-
-Qt::ItemFlags Models::Roster::flags(const QModelIndex& index) const
-{
-    if (!index.isValid()) {
+Qt::ItemFlags Models::Roster::flags(const QModelIndex& index) const {
+    if (!index.isValid())
         return Qt::ItemFlags();
-    }
     
     return QAbstractItemModel::flags(index);
 }
 
-
-int Models::Roster::rowCount (const QModelIndex& parent) const
-{
+int Models::Roster::rowCount (const QModelIndex& parent) const{
     Item *parentItem;
     
-    if (!parent.isValid()) {
+    if (!parent.isValid())
         parentItem = root;
-    } else {
+    else
         parentItem = static_cast<Item*>(parent.internalPointer());
-    }
     
     return parentItem->childCount();
 }
 
-QVariant Models::Roster::headerData(int section, Qt::Orientation orientation, int role) const
-{
+QVariant Models::Roster::headerData(int section, Qt::Orientation orientation, int role) const {
+    SHARED_UNUSED(section);
+    SHARED_UNUSED(orientation);
+    SHARED_UNUSED(role);
+
     return QVariant();
 }
 
-QModelIndex Models::Roster::parent (const QModelIndex& child) const
-{
-    if (!child.isValid()) {
+QModelIndex Models::Roster::parent (const QModelIndex& child) const {
+    if (!child.isValid())
         return QModelIndex();
-    }
     
     Item *childItem = static_cast<Item*>(child.internalPointer());
-    if (childItem == root) {
+    if (childItem == root)
         return QModelIndex();
-    }
     
     Item *parentItem = childItem->parentItem();
-    
-    if (parentItem == root) {
+    if (parentItem == root)
         return createIndex(0, 0, parentItem);
-    }
     
     return createIndex(parentItem->row(), 0, parentItem);
 }
 
-QModelIndex Models::Roster::index (int row, int column, const QModelIndex& parent) const
-{
-    if (!hasIndex(row, column, parent)) {
+QModelIndex Models::Roster::index (int row, int column, const QModelIndex& parent) const {
+    if (!hasIndex(row, column, parent))
         return QModelIndex();
-    }
     
     Item *parentItem;
     
-    if (!parent.isValid()) {
+    if (!parent.isValid())
         parentItem = root;
-    } else {
+    else
         parentItem = static_cast<Item*>(parent.internalPointer());
-    }
     
     Item *childItem = parentItem->child(row);
-    if (childItem) {
+    if (childItem)
         return createIndex(row, column, childItem);
-    } else {
+    else
         return QModelIndex();
-    }
 }
 
 Models::Roster::ElId::ElId(const QString& p_account, const QString& p_name):
@@ -393,27 +365,22 @@ Models::Roster::ElId::ElId(const QString& p_account, const QString& p_name):
     name(p_name)
 {}
 
-bool Models::Roster::ElId::operator <(const Models::Roster::ElId& other) const
-{
-    if (account == other.account) {
+bool Models::Roster::ElId::operator <(const Models::Roster::ElId& other) const {
+    if (account == other.account)
         return name < other.name;
-    } else {
+    else
         return account < other.account;
-    }
 }
 
-bool Models::Roster::ElId::operator!=(const Models::Roster::ElId& other) const
-{
+bool Models::Roster::ElId::operator!=(const Models::Roster::ElId& other) const {
     return !(operator == (other));
 }
 
-bool Models::Roster::ElId::operator==(const Models::Roster::ElId& other) const
-{
+bool Models::Roster::ElId::operator==(const Models::Roster::ElId& other) const {
     return (account == other.account) && (name == other.name);
 }
 
-void Models::Roster::onAccountDataChanged(const QModelIndex& tl, const QModelIndex& br, const QVector<int>& roles)
-{
+void Models::Roster::onAccountDataChanged(const QModelIndex& tl, const QModelIndex& br, const QVector<int>& roles) {
     if (tl.column() == 0) {
         emit dataChanged(tl, br, roles);
     } else if (tl.column() == 2) {
@@ -423,8 +390,7 @@ void Models::Roster::onAccountDataChanged(const QModelIndex& tl, const QModelInd
     }
 }
 
-void Models::Roster::addGroup(const QString& account, const QString& name)
-{
+void Models::Roster::addGroup(const QString& account, const QString& name) {
     ElId id(account, name);
     std::map<ElId, Group*>::const_iterator gItr = groups.find(id);
     if (gItr != groups.end()) {
@@ -438,15 +404,13 @@ void Models::Roster::addGroup(const QString& account, const QString& name)
         groups.insert(std::make_pair(id, group));
         acc->appendChild(group);
 
-
         emit addedElement({acc->getId(), group->getId()});
     } else {
         qDebug() << "An attempt to add group " << name << " to non existing account " << account << ", skipping";
     }
 }
 
-void Models::Roster::addContact(const QString& account, const QString& jid, const QString& group, const QMap<QString, QVariant>& data)
-{
+void Models::Roster::addContact(const QString& account, const QString& jid, const QString& group, const QMap<QString, QVariant>& data) {
     Item* parent;
     Account* acc;
     Contact* contact;
@@ -513,16 +477,15 @@ void Models::Roster::addContact(const QString& account, const QString& jid, cons
     }
     path.push_back(contact->getId());
     
-    if (ref == 0) {
+    if (ref == 0)
         ref = new Reference(contact);
-    }
+
     parent->appendChild(ref);
 
     emit addedElement(path);
 }
 
-void Models::Roster::removeGroup(const QString& account, const QString& name)
-{
+void Models::Roster::removeGroup(const QString& account, const QString& name) {
     ElId id(account, name);
     std::map<ElId, Group*>::const_iterator gItr = groups.find(id);
     if (gItr == groups.end()) {
@@ -541,11 +504,10 @@ void Models::Roster::removeGroup(const QString& account, const QString& name)
         item->removeChild(0);
         
         Contact* cont = static_cast<Contact*>(ref->dereference());
-        if (cont->referencesCount() == 1) {
+        if (cont->referencesCount() == 1)
             toInsert.push_back(ref);
-        } else {
+        else
             delete ref;
-        }
     }
     
     if (toInsert.size() > 0) {
@@ -559,8 +521,7 @@ void Models::Roster::removeGroup(const QString& account, const QString& name)
     groups.erase(gItr);
 }
 
-void Models::Roster::changeContact(const QString& account, const QString& jid, const QMap<QString, QVariant>& data)
-{
+void Models::Roster::changeContact(const QString& account, const QString& jid, const QMap<QString, QVariant>& data) {
     Element* el = getElement(ElId(account, jid));
     if (el != nullptr) {
         for (QMap<QString, QVariant>::const_iterator itr = data.begin(), end = data.end(); itr != end; ++itr) {
@@ -569,18 +530,15 @@ void Models::Roster::changeContact(const QString& account, const QString& jid, c
     }
 }
 
-void Models::Roster::changeMessage(const QString& account, const QString& jid, const QString& id, const QMap<QString, QVariant>& data)
-{
+void Models::Roster::changeMessage(const QString& account, const QString& jid, const QString& id, const QMap<QString, QVariant>& data) {
     Element* el = getElement(ElId(account, jid));
-    if (el != nullptr) {
+    if (el != nullptr)
         el->changeMessage(id, data);
-    } else {
+    else
         qDebug() << "A request to change a message of the contact " << jid << " in the account " << account << " but it wasn't found";
-    }
 }
 
-void Models::Roster::removeContact(const QString& account, const QString& jid)
-{
+void Models::Roster::removeContact(const QString& account, const QString& jid) {
     ElId id(account, jid);
     std::map<ElId, Contact*>::iterator itr = contacts.find(id);
     
@@ -592,21 +550,18 @@ void Models::Roster::removeContact(const QString& account, const QString& jid)
         
         std::set<ElId> toRemove;
         for (std::pair<ElId, Group*> pair : groups) {
-            if (pair.second->childCount() == 0) {
+            if (pair.second->childCount() == 0)
                 toRemove.insert(pair.first);
-            }
         }
         
-        for (const ElId& elId : toRemove) {
+        for (const ElId& elId : toRemove)
             removeGroup(elId.account, elId.name);
-        }
     } else {
         qDebug() << "An attempt to remove contact " << jid << " from account " << account <<" which doesn't exist there, skipping";
     }
 }
 
-void Models::Roster::removeContact(const QString& account, const QString& jid, const QString& group)
-{
+void Models::Roster::removeContact(const QString& account, const QString& jid, const QString& group) {
     ElId contactId(account, jid);
     ElId groupId(account, group);
     
@@ -639,20 +594,18 @@ void Models::Roster::removeContact(const QString& account, const QString& jid, c
     } else {
         delete ref;
     }
-    if (gr->childCount() == 0) {
+    if (gr->childCount() == 0)
         removeGroup(account, group);
-    }
 }
 
-void Models::Roster::onChildChanged(Models::Item* item, int row, int col)
-{
+void Models::Roster::onChildChanged(Models::Item* item, int row, int col) {
+    SHARED_UNUSED(col);
     QModelIndex index = createIndex(row, 0, item);
     QModelIndex index2 = createIndex(row, 1, item);
     emit dataChanged(index, index2);
 }
 
-void Models::Roster::onChildIsAboutToBeInserted(Models::Item* parent, int first, int last)
-{
+void Models::Roster::onChildIsAboutToBeInserted(Models::Item* parent, int first, int last) {
     int row = 0;
     if (parent != root) {
         row = parent->row();
@@ -662,45 +615,39 @@ void Models::Roster::onChildIsAboutToBeInserted(Models::Item* parent, int first,
     }
 }
 
-void Models::Roster::onChildIsAboutToBeMoved(Models::Item* source, int first, int last, Models::Item* destination, int newIndex)
-{
+void Models::Roster::onChildIsAboutToBeMoved(Models::Item* source, int first, int last, Models::Item* destination, int newIndex) {
     int oldRow = 0;
-    if (source != root) {
+    if (source != root)
         oldRow = source->row();
-    }
+
     int newRow = 0;
-    if (destination != root) {
+    if (destination != root)
         newRow = destination->row();
-    }
+
     beginMoveRows(createIndex(oldRow, 0, source), first, last, createIndex(newRow, 0, destination), newIndex);
 }
 
-void Models::Roster::onChildIsAboutToBeRemoved(Models::Item* parent, int first, int last)
-{
+void Models::Roster::onChildIsAboutToBeRemoved(Models::Item* parent, int first, int last) {
     int row = 0;
-    if (parent != root) {
+    if (parent != root)
         row = parent->row();
-    }
+
     beginRemoveRows(createIndex(row, 0, parent), first, last);
 }
 
-void Models::Roster::onChildInserted()
-{
+void Models::Roster::onChildInserted() {
     endInsertRows();
 }
 
-void Models::Roster::onChildMoved()
-{
+void Models::Roster::onChildMoved() {
     endMoveRows();
 }
 
-void Models::Roster::onChildRemoved()
-{
+void Models::Roster::onChildRemoved() {
     endRemoveRows();
 }
 
-void Models::Roster::addPresence(const QString& account, const QString& jid, const QString& name, const QMap<QString, QVariant>& data)
-{
+void Models::Roster::addPresence(const QString& account, const QString& jid, const QString& name, const QMap<QString, QVariant>& data) {
     ElId contactId(account, jid);
     std::map<ElId, Contact*>::iterator itr = contacts.find(contactId);
     if (itr != contacts.end()) {
@@ -709,8 +656,7 @@ void Models::Roster::addPresence(const QString& account, const QString& jid, con
 
 }
 
-void Models::Roster::removePresence(const QString& account, const QString& jid, const QString& name)
-{
+void Models::Roster::removePresence(const QString& account, const QString& jid, const QString& name) {
     ElId contactId(account, jid);
     std::map<ElId, Contact*>::iterator itr = contacts.find(contactId);
     if (itr != contacts.end()) {
@@ -718,16 +664,13 @@ void Models::Roster::removePresence(const QString& account, const QString& jid,
     }
 }
 
-void Models::Roster::addMessage(const QString& account, const Shared::Message& data)
-{
+void Models::Roster::addMessage(const QString& account, const Shared::Message& data) {
     Element* el = getElement(ElId(account, data.getPenPalJid()));
-    if (el != nullptr) {
+    if (el != nullptr)
         el->addMessage(data);
-    }
 }
 
-void Models::Roster::removeAccount(const QString& account)
-{
+void Models::Roster::removeAccount(const QString& account) {
     std::map<QString, Account*>::const_iterator itr = accounts.find(account);
     if (itr == accounts.end()) {
         qDebug() << "An attempt to remove non existing account " << account << ", skipping";
@@ -777,26 +720,23 @@ void Models::Roster::removeAccount(const QString& account)
     acc->deleteLater();
 }
 
-QString Models::Roster::getContactName(const QString& account, const QString& jid) const
-{
+QString Models::Roster::getContactName(const QString& account, const QString& jid) const {
     ElId id(account, jid);
     std::map<ElId, Contact*>::const_iterator cItr = contacts.find(id);
     QString name = "";
     if (cItr == contacts.end()) {
         std::map<ElId, Room*>::const_iterator rItr = rooms.find(id);
-        if (rItr == rooms.end()) {
+        if (rItr == rooms.end())
             qDebug() << "An attempt to get a name of non existing contact/room " << account << ":" << jid << ", skipping";
-        } else {
+        else
             name = rItr->second->getRoomName();
-        }
     } else {
         name = cItr->second->getContactName();
     }
     return name;
 }
 
-void Models::Roster::addRoom(const QString& account, const QString jid, const QMap<QString, QVariant>& data)
-{
+void Models::Roster::addRoom(const QString& account, const QString jid, const QMap<QString, QVariant>& data) {
     Account* acc;
     {
         std::map<QString, Account*>::iterator itr = accounts.find(account);
@@ -826,8 +766,7 @@ void Models::Roster::addRoom(const QString& account, const QString jid, const QM
     emit addedElement({acc->getId(), room->getId()});
 }
 
-void Models::Roster::changeRoom(const QString& account, const QString jid, const QMap<QString, QVariant>& data)
-{
+void Models::Roster::changeRoom(const QString& account, const QString jid, const QMap<QString, QVariant>& data) {
     ElId id = {account, jid};
     std::map<ElId, Room*>::const_iterator itr = rooms.find(id);
     if (itr == rooms.end()) {
@@ -840,8 +779,7 @@ void Models::Roster::changeRoom(const QString& account, const QString jid, const
     }
 }
 
-void Models::Roster::removeRoom(const QString& account, const QString jid)
-{
+void Models::Roster::removeRoom(const QString& account, const QString jid) {
     Account* acc;
     {
         std::map<QString, Account*>::iterator itr = accounts.find(account);
@@ -865,8 +803,7 @@ void Models::Roster::removeRoom(const QString& account, const QString jid)
     rooms.erase(itr);
 }
 
-void Models::Roster::addRoomParticipant(const QString& account, const QString& jid, const QString& name, const QMap<QString, QVariant>& data)
-{
+void Models::Roster::addRoomParticipant(const QString& account, const QString& jid, const QString& name, const QMap<QString, QVariant>& data) {
     ElId id = {account, jid};
     std::map<ElId, Room*>::const_iterator itr = rooms.find(id);
     if (itr == rooms.end()) {
@@ -877,8 +814,7 @@ void Models::Roster::addRoomParticipant(const QString& account, const QString& j
     }
 }
 
-void Models::Roster::changeRoomParticipant(const QString& account, const QString& jid, const QString& name, const QMap<QString, QVariant>& data)
-{
+void Models::Roster::changeRoomParticipant(const QString& account, const QString& jid, const QString& name, const QMap<QString, QVariant>& data) {
     ElId id = {account, jid};
     std::map<ElId, Room*>::const_iterator itr = rooms.find(id);
     if (itr == rooms.end()) {
@@ -889,8 +825,7 @@ void Models::Roster::changeRoomParticipant(const QString& account, const QString
     }
 }
 
-void Models::Roster::removeRoomParticipant(const QString& account, const QString& jid, const QString& name)
-{
+void Models::Roster::removeRoomParticipant(const QString& account, const QString& jid, const QString& name) {
     ElId id = {account, jid};
     std::map<ElId, Room*>::const_iterator itr = rooms.find(id);
     if (itr == rooms.end()) {
@@ -901,20 +836,17 @@ void Models::Roster::removeRoomParticipant(const QString& account, const QString
     }
 }
 
-std::deque<QString> Models::Roster::groupList(const QString& account) const
-{
+std::deque<QString> Models::Roster::groupList(const QString& account) const {
     std::deque<QString> answer;
     for (std::pair<ElId, Group*> pair : groups) {
-        if (pair.first.account == account) {
+        if (pair.first.account == account)
             answer.push_back(pair.first.name);
-        }
     }
     
     return answer;
 }
 
-bool Models::Roster::groupHasContact(const QString& account, const QString& group, const QString& contact) const
-{
+bool Models::Roster::groupHasContact(const QString& account, const QString& group, const QString& contact) const {
     ElId grId({account, group});
     std::map<ElId, Group*>::const_iterator gItr = groups.find(grId);
     if (gItr == groups.end()) {
@@ -924,22 +856,19 @@ bool Models::Roster::groupHasContact(const QString& account, const QString& grou
     }
 }
 
-QString Models::Roster::getContactIconPath(const QString& account, const QString& jid, const QString& resource) const
-{
+QString Models::Roster::getContactIconPath(const QString& account, const QString& jid, const QString& resource) const {
     ElId id(account, jid);
     std::map<ElId, Contact*>::const_iterator cItr = contacts.find(id);
     QString path = "";
     if (cItr == contacts.end()) {
         std::map<ElId, Room*>::const_iterator rItr = rooms.find(id);
-        if (rItr == rooms.end()) {
+        if (rItr == rooms.end())
             qDebug() << "An attempt to get an icon path of non existing contact" << account << ":" << jid << ", returning empty value";
-        } else {
+        else
             path = rItr->second->getParticipantIconPath(resource);
-        }
     } else {
-        if (cItr->second->getAvatarState() != Shared::Avatar::empty) {
+        if (cItr->second->getAvatarState() != Shared::Avatar::empty)
             path = cItr->second->getAvatarPath();
-        }
     }
     return path;
 }
@@ -950,34 +879,29 @@ Models::Account * Models::Roster::getAccount(const QString& name) {
 const Models::Account * Models::Roster::getAccountConst(const QString& name) const {
     return accounts.at(name);}
 
-const Models::Element * Models::Roster::getElementConst(const Models::Roster::ElId& id) const
-{
+const Models::Element * Models::Roster::getElementConst(const Models::Roster::ElId& id) const {
     std::map<ElId, Contact*>::const_iterator cItr = contacts.find(id);
 
     if (cItr != contacts.end()) {
         return cItr->second;
     } else {
         std::map<ElId, Room*>::const_iterator rItr = rooms.find(id);
-        if (rItr != rooms.end()) {
+        if (rItr != rooms.end())
             return rItr->second;
-        }
     }
 
     return nullptr;
 }
 
-bool Models::Roster::markMessageAsRead(const Models::Roster::ElId& elementId, const QString& messageId)
-{
+bool Models::Roster::markMessageAsRead(const Models::Roster::ElId& elementId, const QString& messageId) {
     const Element* el = getElementConst(elementId);
-    if (el != nullptr) {
+    if (el != nullptr)
         return el->markMessageAsRead(messageId);
-    } else {
+    else
         return false;
-    }
 }
 
-QModelIndex Models::Roster::getAccountIndex(const QString& name) const
-{
+QModelIndex Models::Roster::getAccountIndex(const QString& name) const {
     std::map<QString, Account*>::const_iterator itr = accounts.find(name);
     if (itr == accounts.end()) {
         return QModelIndex();
@@ -986,8 +910,7 @@ QModelIndex Models::Roster::getAccountIndex(const QString& name) const
     }
 }
 
-QModelIndex Models::Roster::getGroupIndex(const QString& account, const QString& name) const
-{
+QModelIndex Models::Roster::getGroupIndex(const QString& account, const QString& name) const {
     std::map<QString, Account*>::const_iterator itr = accounts.find(account);
     if (itr == accounts.end()) {
         return QModelIndex();
@@ -1002,8 +925,7 @@ QModelIndex Models::Roster::getGroupIndex(const QString& account, const QString&
     }
 }
 
-QModelIndex Models::Roster::getContactIndex(const QString& account, const QString& jid, const QString& resource) const
-{
+QModelIndex Models::Roster::getContactIndex(const QString& account, const QString& jid, const QString& resource) const {
     std::map<QString, Account*>::const_iterator itr = accounts.find(account);
     if (itr == accounts.end()) {
         return QModelIndex();
@@ -1017,11 +939,10 @@ QModelIndex Models::Roster::getContactIndex(const QString& account, const QStrin
                 return contactIndex;
             } else {
                 Presence* pres = cItr->second->getPresence(resource);
-                if (pres != nullptr) {
+                if (pres != nullptr)
                     return index(pres->row(), 0, contactIndex);
-                } else {
+                else
                     return contactIndex;
-                }
             }
         } else {
             std::map<ElId, Room*>::const_iterator rItr = rooms.find(ElId(account, jid));
@@ -1031,11 +952,10 @@ QModelIndex Models::Roster::getContactIndex(const QString& account, const QStrin
                     return roomIndex;
                 } else {
                     Participant* part = rItr->second->getParticipant(resource);
-                    if (part != nullptr) {
+                    if (part != nullptr)
                         return index(part->row(), 0, roomIndex);
-                    } else {
+                    else
                         return roomIndex;
-                    }
                 }
             } else {
                 return QModelIndex();
@@ -1044,93 +964,78 @@ QModelIndex Models::Roster::getContactIndex(const QString& account, const QStrin
     }
 }
 
-void Models::Roster::onElementRequestArchive(const QString& before)
-{
+void Models::Roster::onElementRequestArchive(const QString& before) {
     Element* el = static_cast<Element*>(sender());
     emit requestArchive(el->getAccountName(), el->getJid(), before);
 }
 
-void Models::Roster::responseArchive(const QString& account, const QString& jid, const std::list<Shared::Message>& list, bool last)
-{
+void Models::Roster::responseArchive(const QString& account, const QString& jid, const std::list<Shared::Message>& list, bool last) {
     ElId id(account, jid);
     Element* el = getElement(id);
-    if (el != nullptr) {
+    if (el != nullptr)
         el->responseArchive(list, last);
-    }
 }
 
-void Models::Roster::fileProgress(const std::list<Shared::MessageInfo>& msgs, qreal value, bool up)
-{
+void Models::Roster::fileProgress(const std::list<Shared::MessageInfo>& msgs, qreal value, bool up) {
     for (const Shared::MessageInfo& info : msgs) {
         Element* el = getElement(ElId(info.account, info.jid));
-        if (el != nullptr) {
+        if (el != nullptr)
             el->fileProgress(info.messageId, value, up);
-        }
     }
 }
 
-void Models::Roster::fileComplete(const std::list<Shared::MessageInfo>& msgs, bool up)
-{
+void Models::Roster::fileComplete(const std::list<Shared::MessageInfo>& msgs, bool up) {
     for (const Shared::MessageInfo& info : msgs) {
         Element* el = getElement(ElId(info.account, info.jid));
-        if (el != nullptr) {
+        if (el != nullptr)
             el->fileComplete(info.messageId, up);
-        }
     }
 }
 
-void Models::Roster::fileError(const std::list<Shared::MessageInfo>& msgs, const QString& err, bool up)
-{
+void Models::Roster::fileError(const std::list<Shared::MessageInfo>& msgs, const QString& err, bool up) {
     for (const Shared::MessageInfo& info : msgs) {
         Element* el = getElement(ElId(info.account, info.jid));
-        if (el != nullptr) {
+        if (el != nullptr)
             el->fileError(info.messageId, err, up);
-        }
     }
 }
 
-Models::Element * Models::Roster::getElement(const Models::Roster::ElId& id)
-{
+Models::Element * Models::Roster::getElement(const Models::Roster::ElId& id) {
     return const_cast<Models::Element*>(getElementConst(id));
 }
 
-Models::Item::Type Models::Roster::getContactType(const Models::Roster::ElId& id) const
-{
+Models::Item::Type Models::Roster::getContactType(const Models::Roster::ElId& id) const {
     const Models::Element* el = getElementConst(id);
-    if (el == nullptr) {
+    if (el == nullptr)
         return Item::root;
-    }
+
 
     return el->type;
 }
 
 
-void Models::Roster::onAccountReconnected()
-{
+void Models::Roster::onAccountReconnected() {
     Account* acc = static_cast<Account*>(sender());
     
     QString accName = acc->getName();
     for (const std::pair<const ElId, Contact*>& pair : contacts) {
-        if (pair.first.account == accName) {
+        if (pair.first.account == accName)
             pair.second->handleRecconnect();
-        }
     }
 }
 
-void Models::Roster::recalculateUnreadMessages()
-{
+void Models::Roster::recalculateUnreadMessages() {
     int count(0);
-    for (const std::pair<const ElId, Contact*>& pair : contacts) {
+    for (const std::pair<const ElId, Contact*>& pair : contacts)
         count += pair.second->getMessagesCount();
-    }
-    for (const std::pair<const ElId, Room*>& pair : rooms) {
+
+    for (const std::pair<const ElId, Room*>& pair : rooms)
         count += pair.second->getMessagesCount();
-    }
+
     emit unreadMessagesCountChanged(count);
 }
 
-std::list<QString> Models::Roster::getItemPath(const QModelIndex& index) const
-{
+std::list<QString> Models::Roster::getItemPath(const QModelIndex& index) const {
     std::list<QString> result;
     if (index.isValid() && index.model() == this) {
         Item* item = static_cast<Item*>(index.internalPointer());
@@ -1143,8 +1048,7 @@ std::list<QString> Models::Roster::getItemPath(const QModelIndex& index) const
     return result;
 }
 
-QModelIndex Models::Roster::getIndexByPath(const std::list<QString>& path) const
-{
+QModelIndex Models::Roster::getIndexByPath(const std::list<QString>& path) const {
     if (path.empty())
         return QModelIndex();
 
diff --git a/ui/utils/comboboxdelegate.cpp b/ui/utils/comboboxdelegate.cpp
index 4c96c79..cbaa1e3 100644
--- a/ui/utils/comboboxdelegate.cpp
+++ b/ui/utils/comboboxdelegate.cpp
@@ -18,35 +18,30 @@
 #include "QTimer"
 
 #include "comboboxdelegate.h"
+#include "shared/defines.h"
 
 ComboboxDelegate::ComboboxDelegate(QObject *parent):
     QStyledItemDelegate(parent),
     entries(),
     ff(new FocusFilter())
-{
-}
+{}
 
-
-ComboboxDelegate::~ComboboxDelegate()
-{
+ComboboxDelegate::~ComboboxDelegate() {
     delete ff;
 }
 
-
-QWidget* ComboboxDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
-{
+QWidget* ComboboxDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const {
+    SHARED_UNUSED(option);
+    SHARED_UNUSED(index);
     QComboBox *cb = new QComboBox(parent);
-    
-    for (const std::pair<QString, QIcon>& pair : entries) {
+    for (const std::pair<QString, QIcon>& pair : entries)
         cb->addItem(pair.second, pair.first);
-    }
     
     return cb;
 }
 
 
-void ComboboxDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
-{
+void ComboboxDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const {
     QComboBox *cb = static_cast<QComboBox*>(editor);
     int currentIndex = index.data(Qt::EditRole).toInt();
     if (currentIndex >= 0) {
@@ -56,19 +51,16 @@ void ComboboxDelegate::setEditorData(QWidget *editor, const QModelIndex &index)
 }
 
 
-void ComboboxDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
-{
+void ComboboxDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const {
     QComboBox *cb = static_cast<QComboBox *>(editor);
     model->setData(index, cb->currentIndex(), Qt::EditRole);
 }
 
-void ComboboxDelegate::addEntry(const QString& title, const QIcon& icon)
-{
+void ComboboxDelegate::addEntry(const QString& title, const QIcon& icon) {
     entries.emplace_back(title, icon);
 }
 
-bool ComboboxDelegate::FocusFilter::eventFilter(QObject* src, QEvent* evt)
-{
+bool ComboboxDelegate::FocusFilter::eventFilter(QObject* src, QEvent* evt) {
     if (evt->type() == QEvent::FocusIn) {
         QComboBox* cb = static_cast<QComboBox*>(src);
         cb->removeEventFilter(this);
diff --git a/ui/utils/resizer.cpp b/ui/utils/resizer.cpp
index 8691400..ad62e6d 100644
--- a/ui/utils/resizer.cpp
+++ b/ui/utils/resizer.cpp
@@ -18,14 +18,13 @@
 
 #include "resizer.h"
 
-Resizer::Resizer(QWidget* parent):
-QObject(parent)
-{
-    
-}
+#include "shared/defines.h"
 
-bool Resizer::eventFilter(QObject* obj, QEvent* event)
-{
+Resizer::Resizer(QWidget* parent):
+QObject(parent) {}
+
+bool Resizer::eventFilter(QObject* obj, QEvent* event) {
+    SHARED_UNUSED(obj);
     if (event->type() == QEvent::Resize) {
         QResizeEvent* ev = static_cast<QResizeEvent*>(event);
         emit resized(ev->oldSize(), ev->size());
diff --git a/ui/widgets/chat.cpp b/ui/widgets/chat.cpp
index 301553b..3b9f090 100644
--- a/ui/widgets/chat.cpp
+++ b/ui/widgets/chat.cpp
@@ -19,6 +19,8 @@
 #include "chat.h"
 #include "ui_conversation.h"
 
+#include "shared/defines.h"
+
 Chat::Chat(Models::Account* acc, Models::Contact* p_contact, QWidget* parent):
     Conversation(false, acc, p_contact, p_contact->getJid(), "", parent),
     contact(p_contact)
@@ -39,6 +41,7 @@ Chat::~Chat()
 {}
 
 void Chat::onContactChanged(Models::Item* item, int row, int col) {
+    SHARED_UNUSED(row);
     if (item == contact) {
         switch (col) {
             case 0:
diff --git a/ui/widgets/conversation.cpp b/ui/widgets/conversation.cpp
index c11449f..e6316c7 100644
--- a/ui/widgets/conversation.cpp
+++ b/ui/widgets/conversation.cpp
@@ -115,15 +115,14 @@ Conversation::Conversation(bool muc, Models::Account* acc, Models::Element* el,
     initializeOverlay();
 }
 
-Conversation::~Conversation()
-{
+Conversation::~Conversation() {
     delete contextMenu;
     
     element->feed->decrementObservers();
 }
 
-void Conversation::onAccountChanged(Models::Item* item, int row, int col)
-{
+void Conversation::onAccountChanged(Models::Item* item, int row, int col) {
+    SHARED_UNUSED(row);
     if (item == account) {
         if (col == 2 && account->getState() == Shared::ConnectionState::connected) {        //to request the history when we're back online after reconnect
             //if (!requestingHistory) {
@@ -136,8 +135,7 @@ void Conversation::onAccountChanged(Models::Item* item, int row, int col)
     }
 }
 
-void Conversation::initializeOverlay()
-{
+void Conversation::initializeOverlay() {
     QGridLayout* gr = static_cast<QGridLayout*>(layout());
     QLabel* progressLabel = new QLabel(tr("Drop files here to attach them to your message"));
     gr->addWidget(overlay, 0, 0, 2, 1);
@@ -160,26 +158,22 @@ void Conversation::initializeOverlay()
     overlay->hide();
 }
 
-void Conversation::setName(const QString& name)
-{
+void Conversation::setName(const QString& name) {
     m_ui->nameLabel->setText(name);
     setWindowTitle(name);
 }
 
-QString Conversation::getAccount() const
-{
+QString Conversation::getAccount() const {
     return account->getName();
 }
 
-QString Conversation::getJid() const
-{
+QString Conversation::getJid() const {
     return palJid;
 }
 
 KeyEnterReceiver::KeyEnterReceiver(QObject* parent): QObject(parent), ownEvent(false) {}
 
-bool KeyEnterReceiver::eventFilter(QObject* obj, QEvent* event)
-{
+bool KeyEnterReceiver::eventFilter(QObject* obj, QEvent* event) {
     QEvent::Type type = event->type();
     if (type == QEvent::KeyPress) {
         QKeyEvent* key = static_cast<QKeyEvent*>(event);
@@ -215,18 +209,15 @@ bool Conversation::checkClipboardImage() {
     return !QApplication::clipboard()->image().isNull();
 }
 
-QString Conversation::getPalResource() const
-{
+QString Conversation::getPalResource() const {
     return activePalResource;
 }
 
-void Conversation::setPalResource(const QString& res)
-{
+void Conversation::setPalResource(const QString& res) {
     activePalResource = res;
 }
 
-void Conversation::initiateMessageSending()
-{
+void Conversation::initiateMessageSending() {
     QString body(m_ui->messageEditor->toPlainText());
     
     if (body.size() > 0) {
@@ -245,8 +236,7 @@ void Conversation::initiateMessageSending()
     clear();
 }
 
-void Conversation::initiateMessageSending(const Shared::Message& msg)
-{
+void Conversation::initiateMessageSending(const Shared::Message& msg) {
     if (currentAction == CurrentAction::edit) {
         emit replaceMessage(currentMessageId, msg);
         currentAction = CurrentAction::none;
@@ -255,12 +245,11 @@ void Conversation::initiateMessageSending(const Shared::Message& msg)
     }
 }
 
-void Conversation::onImagePasted()
-{
+void Conversation::onImagePasted() {
     QImage image = QApplication::clipboard()->image();
-    if (image.isNull()) {
+    if (image.isNull())
         return;
-    }
+
     QTemporaryFile *tempFile = new QTemporaryFile(QDir::tempPath() + QStringLiteral("/squawk_img_attach_XXXXXX.png"), QApplication::instance());
     tempFile->open();
     image.save(tempFile, "PNG");
@@ -273,8 +262,7 @@ void Conversation::onImagePasted()
     // See Core::NetworkAccess::onUploadFinished.
 }
 
-void Conversation::onAttach()
-{
+void Conversation::onAttach() {
     QFileDialog* d = new QFileDialog(this, tr("Chose a file to send"));
     d->setFileMode(QFileDialog::ExistingFile);
     
@@ -284,37 +272,31 @@ void Conversation::onAttach()
     d->show();
 }
 
-void Conversation::onFileSelected()
-{
+void Conversation::onFileSelected() {
     QFileDialog* d = static_cast<QFileDialog*>(sender());
-    
-    for (const QString& path : d->selectedFiles()) {
+    for (const QString& path : d->selectedFiles())
         addAttachedFile(path);
-    }
     
     d->deleteLater();
 }
 
-void Conversation::setStatus(const QString& status)
-{
+void Conversation::setStatus(const QString& status) {
     statusLabel->setText(Shared::processMessageBody(status));
 }
 
-Models::Roster::ElId Conversation::getId() const
-{
+Models::Roster::ElId Conversation::getId() const {
     return {getAccount(), getJid()};
 }
 
-void Conversation::addAttachedFile(const QString& path)
-{
+void Conversation::addAttachedFile(const QString& path) {
     QMimeDatabase db;
     QMimeType type = db.mimeTypeForFile(path);
     QFileInfo info(path);
 
     QIcon fileIcon = QIcon::fromTheme(type.iconName());
-    if (fileIcon.isNull()) {
+    if (fileIcon.isNull())
         fileIcon.addFile(QString::fromUtf8(":/images/fallback/dark/big/mail-attachment.svg"), QSize(), QIcon::Normal, QIcon::Off);
-    }
+
     Badge* badge = new Badge(path, info.fileName(), fileIcon);
     
     connect(badge, &Badge::close, this, &Conversation::onBadgeClose);
@@ -331,35 +313,31 @@ void Conversation::addAttachedFile(const QString& path)
     }
 }
 
-void Conversation::removeAttachedFile(Badge* badge)
-{
+void Conversation::removeAttachedFile(Badge* badge) {
     W::Order<Badge*, Badge::Comparator>::const_iterator itr = filesToAttach.find(badge);
     if (itr != filesToAttach.end()) {
         filesToAttach.erase(badge);
-        if (filesLayout->count() == 1) {
+        if (filesLayout->count() == 1)
             filesLayout->setContentsMargins(0, 0, 0, 0);
-        }
+
         badge->deleteLater();
     }
 }
 
-void Conversation::onBadgeClose()
-{
+void Conversation::onBadgeClose() {
     Badge* badge = static_cast<Badge*>(sender());
     removeAttachedFile(badge);
 }
 
-void Conversation::clearAttachedFiles()
-{
-    for (Badge* badge : filesToAttach) {
+void Conversation::clearAttachedFiles() {
+    for (Badge* badge : filesToAttach)
         badge->deleteLater();
-    }
+
     filesToAttach.clear();
     filesLayout->setContentsMargins(0, 0, 0, 0);
 }
 
-void Conversation::clear()
-{
+void Conversation::clear() {
     currentMessageId.clear();
     currentAction = CurrentAction::none;
     m_ui->currentActionBadge->setVisible(false);
@@ -367,8 +345,7 @@ void Conversation::clear()
     m_ui->messageEditor->clear();
 }
 
-void Conversation::setAvatar(const QString& path)
-{
+void Conversation::setAvatar(const QString& path) {
     QPixmap pixmap;
     if (path.size() == 0) {
         pixmap = Shared::icon("user", true).pixmap(avatarSize);
@@ -390,13 +367,11 @@ void Conversation::setAvatar(const QString& path)
     m_ui->avatar->setPixmap(result);
 }
 
-void Conversation::onTextEditDocSizeChanged(const QSizeF& size)
-{
+void Conversation::onTextEditDocSizeChanged(const QSizeF& size) {
     m_ui->messageEditor->setMaximumHeight(int(size.height()));
 }
 
-void Conversation::setFeedFrames(bool top, bool right, bool bottom, bool left)
-{
+void Conversation::setFeedFrames(bool top, bool right, bool bottom, bool left) {
     shadow.setFrames(top, right, bottom, left);
 }
 
@@ -421,13 +396,12 @@ void Conversation::dragEnterEvent(QDragEnterEvent* event)
     }
 }
 
-void Conversation::dragLeaveEvent(QDragLeaveEvent* event)
-{
+void Conversation::dragLeaveEvent(QDragLeaveEvent* event) {
+    SHARED_UNUSED(event);
     overlay->hide();
 }
 
-void Conversation::dropEvent(QDropEvent* event)
-{
+void Conversation::dropEvent(QDropEvent* event) {
     bool accept = false;
     if (event->mimeData()->hasUrls()) {
         QList<QUrl> list = event->mimeData()->urls();
@@ -441,14 +415,13 @@ void Conversation::dropEvent(QDropEvent* event)
             }
         }
     }
-    if (accept) {
+    if (accept)
         event->acceptProposedAction();
-    }
+
     overlay->hide();
 }
 
-Shared::Message Conversation::createMessage() const
-{
+Shared::Message Conversation::createMessage() const {
     Shared::Message msg;
     msg.setOutgoing(true);
     msg.generateRandomId();
@@ -457,23 +430,20 @@ Shared::Message Conversation::createMessage() const
     return msg;
 }
 
-void Conversation::onFeedMessage(const Shared::Message& msg)
-{
+void Conversation::onFeedMessage(const Shared::Message& msg) {
     this->onMessage(msg);
 }
 
-void Conversation::onMessage(const Shared::Message& msg)
-{
+void Conversation::onMessage(const Shared::Message& msg) {
     if (!msg.getForwarded()) {
         QApplication::alert(this);
-        if (window()->windowState().testFlag(Qt::WindowMinimized)) {
+        if (window()->windowState().testFlag(Qt::WindowMinimized))
             emit notifyableMessage(getAccount(), msg);
-        }
+
     }
 }
 
-void Conversation::positionShadow()
-{
+void Conversation::positionShadow() {
     int w = width();
     int h = feed->height();
     
@@ -482,8 +452,7 @@ void Conversation::positionShadow()
     shadow.raise();
 }
 
-void Conversation::onFeedContext(const QPoint& pos)
-{
+void Conversation::onFeedContext(const QPoint& pos) {
     QModelIndex index = feed->indexAt(pos);
     if (index.isValid()) {
         Shared::Message* item = static_cast<Shared::Message*>(index.internalPointer());
@@ -542,14 +511,12 @@ void Conversation::onFeedContext(const QPoint& pos)
             connect(edit, &QAction::triggered, this, std::bind(&Conversation::onMessageEditRequested, this, id));
         }
         
-        if (showMenu) {
+        if (showMenu)
             contextMenu->popup(feed->viewport()->mapToGlobal(pos));
-        }
     }
 }
 
-void Conversation::onMessageEditorContext(const QPoint& pos)
-{
+void Conversation::onMessageEditorContext(const QPoint& pos) {
     pasteImageAction->setEnabled(Conversation::checkClipboardImage());
 
     QMenu *editorMenu = m_ui->messageEditor->createStandardContextMenu();
@@ -559,8 +526,7 @@ void Conversation::onMessageEditorContext(const QPoint& pos)
     editorMenu->exec(this->m_ui->messageEditor->mapToGlobal(pos));
 }
 
-void Conversation::onMessageEditRequested(const QString& id)
-{
+void Conversation::onMessageEditRequested(const QString& id) {
     clear();
 
     try {
@@ -582,8 +548,7 @@ void Conversation::onMessageEditRequested(const QString& id)
     }
 }
 
-void Conversation::showEvent(QShowEvent* event)
-{
+void Conversation::showEvent(QShowEvent* event) {
     QWidget::showEvent(event);
 
     emit shown();
diff --git a/ui/widgets/conversation.h b/ui/widgets/conversation.h
index 0c44bd9..80c8c2f 100644
--- a/ui/widgets/conversation.h
+++ b/ui/widgets/conversation.h
@@ -34,6 +34,7 @@
 #include "shared/icons.h"
 #include "shared/utils.h"
 #include "shared/pathcheck.h"
+#include "shared/defines.h"
 
 #include "ui/models/account.h"
 #include "ui/models/roster.h"
@@ -159,8 +160,6 @@ private:
     static QPainterPath* avatarMask;
     static QPixmap* avatarPixmap;
     static QPainter* avatarPainter;
-
-
 };
 
 #endif // CONVERSATION_H
diff --git a/ui/widgets/messageline/feedview.cpp b/ui/widgets/messageline/feedview.cpp
index 9d222ae..43d0218 100644
--- a/ui/widgets/messageline/feedview.cpp
+++ b/ui/widgets/messageline/feedview.cpp
@@ -79,11 +79,10 @@ QModelIndex FeedView::indexAt(const QPoint& point) const {
     for (std::deque<Hint>::size_type i = 0; i < hints.size(); ++i) {
         const Hint& hint = hints[i];
         if (y <= hint.offset + hint.height) {
-            if (y > hint.offset) {
+            if (y > hint.offset)
                 return model()->index(i, 0, rootIndex());
-            } else {
+            else
                 break;
-            }
         }
     }
     
@@ -156,13 +155,11 @@ void FeedView::updateGeometries() {
         vo = 0;
     } else {
         int verticalMargin = 0;
-        if (st->styleHint(QStyle::SH_ScrollView_FrameOnlyAroundContents)) {
+        if (st->styleHint(QStyle::SH_ScrollView_FrameOnlyAroundContents))
             frameAroundContents = st->pixelMetric(QStyle::PM_DefaultFrameWidth) * 2;
-        }
         
-        if (verticalScrollBarPolicy() == Qt::ScrollBarAsNeeded) {
+        if (verticalScrollBarPolicy() == Qt::ScrollBarAsNeeded)
             verticalMargin = verticalScrollBarExtent + frameAroundContents;
-        }
         
         layoutBounds.rwidth() -= verticalMargin;
         
@@ -176,21 +173,19 @@ void FeedView::updateGeometries() {
             QModelIndex index = m->index(i, 0, rootIndex());
             QDateTime currentDate = index.data(Models::MessageFeed::Date).toDateTime();
             if (i > 0) {
-                if (currentDate.daysTo(lastDate) > 0) {
+                if (currentDate.daysTo(lastDate) > 0)
                     previousOffset += dividerMetrics.height() + dateDeviderMargin * 2;
-                } else {
+                else
                     previousOffset += elementMargin;
-                }
             }
             lastDate = currentDate;
             QSize messageSize = itemDelegate(index)->sizeHint(option, index);
             uint32_t offsetX(0);
             if (specialDelegate) {
-                if (index.data(Models::MessageFeed::SentByMe).toBool()) {
+                if (index.data(Models::MessageFeed::SentByMe).toBool())
                     offsetX = layoutBounds.width() - messageSize.width() - MessageDelegate::avatarHeight - MessageDelegate::margin * 2;
-                } else {
+                else
                     offsetX = MessageDelegate::avatarHeight + MessageDelegate::margin * 2;
-                }
             }
 
             hints.emplace_back(Hint({
@@ -204,9 +199,9 @@ void FeedView::updateGeometries() {
         }
         
         int totalHeight = previousOffset - layoutBounds.height() + dividerMetrics.height() + dateDeviderMargin * 2;
-        if (modelState != Models::MessageFeed::complete) {
+        if (modelState != Models::MessageFeed::complete)
             totalHeight += progressSize;
-        }
+
         vo = qMax(qMin(vo, totalHeight), 0);
         bar->setRange(0, totalHeight);
         bar->setPageStep(layoutBounds.height());
@@ -219,7 +214,6 @@ void FeedView::updateGeometries() {
         clearWidgetsMode = true;
     }
     
-    
     QAbstractItemView::updateGeometries();
 }
 
@@ -230,26 +224,23 @@ bool FeedView::tryToCalculateGeometriesWithNoScrollbars(const QStyleOptionViewIt
         QModelIndex index = m->index(i, 0, rootIndex());
         QDateTime currentDate = index.data(Models::MessageFeed::Date).toDateTime();
         if (i > 0) {
-            if (currentDate.daysTo(lastDate) > 0) {
+            if (currentDate.daysTo(lastDate) > 0)
                 previousOffset += dateDeviderMargin * 2 + dividerMetrics.height();
-            } else {
+            else
                 previousOffset += elementMargin;
-            }
         }
         lastDate = currentDate;
         QSize messageSize = itemDelegate(index)->sizeHint(option, index);
         
-        if (previousOffset + messageSize.height() + elementMargin > totalHeight) {
+        if (previousOffset + messageSize.height() + elementMargin > totalHeight)
             return false;
-        }
 
         uint32_t offsetX(0);
         if (specialDelegate) {
-            if (index.data(Models::MessageFeed::SentByMe).toBool()) {
+            if (index.data(Models::MessageFeed::SentByMe).toBool())
                 offsetX = option.rect.width() - messageSize.width() - MessageDelegate::avatarHeight - MessageDelegate::margin * 2;
-            } else {
+            else
                 offsetX = MessageDelegate::avatarHeight + MessageDelegate::margin * 2;
-            }
         }
         hints.emplace_back(Hint({
             false,
@@ -262,9 +253,8 @@ bool FeedView::tryToCalculateGeometriesWithNoScrollbars(const QStyleOptionViewIt
     }
 
     previousOffset += dateDeviderMargin * 2 + dividerMetrics.height();
-    if (previousOffset > totalHeight) {
+    if (previousOffset > totalHeight)
         return false;
-    }
     
     return true;
 }
@@ -284,13 +274,12 @@ void FeedView::paintEvent(QPaintEvent* event) {
         const Hint& hint = hints[i];
         int32_t relativeY1 = vph - hint.offset - hint.height;
         if (!inZone) {
-            if (y2 > relativeY1) {
+            if (y2 > relativeY1)
                 inZone = true;
-            }
         }
-        if (inZone) {
+        if (inZone)
             toRener.emplace_back(m->index(i, 0, rootIndex()));
-        }
+
         if (y1 > relativeY1) {
             inZone = false;
             break;
@@ -304,9 +293,8 @@ void FeedView::paintEvent(QPaintEvent* event) {
     
     if (specialDelegate) {
         MessageDelegate* del = static_cast<MessageDelegate*>(itemDelegate());
-        if (clearWidgetsMode) {
+        if (clearWidgetsMode)
             del->beginClearWidgets();
-        }
     }
     
     QDateTime lastDate;
@@ -319,9 +307,8 @@ void FeedView::paintEvent(QPaintEvent* event) {
             int ind = index.row() - 1;
             if (ind > 0) {
                 QDateTime underDate = m->index(ind, 0, rootIndex()).data(Models::MessageFeed::Date).toDateTime();
-                if (currentDate.daysTo(underDate) > 0) {
+                if (currentDate.daysTo(underDate) > 0)
                     drawDateDevider(option.rect.bottom(), underDate, painter);
-                }
             }
             first = false;
         }
@@ -332,14 +319,13 @@ void FeedView::paintEvent(QPaintEvent* event) {
         option.state.setFlag(QStyle::State_MouseOver, mouseOver);
         itemDelegate(index)->paint(&painter, option, index);
 
-        if (!lastDate.isNull() && currentDate.daysTo(lastDate) > 0) {
+        if (!lastDate.isNull() && currentDate.daysTo(lastDate) > 0)
             drawDateDevider(option.rect.bottom(), lastDate, painter);
-        }
+
         lastDate = currentDate;
     }
-    if (!lastDate.isNull() && inZone) {     //if after drawing all messages there is still space
+    if (!lastDate.isNull() && inZone)       //if after drawing all messages there is still space
         drawDateDevider(option.rect.top() - dateDeviderMargin * 2 - dividerMetrics.height(), lastDate, painter);
-    }
     
     if (clearWidgetsMode && specialDelegate) {
         MessageDelegate* del = static_cast<MessageDelegate*>(itemDelegate());
@@ -362,13 +348,11 @@ void FeedView::verticalScrollbarValueChanged(int value) {
     
     positionProgress();
     
-    if (specialDelegate) {
+    if (specialDelegate)
         clearWidgetsMode = true;
-    }
     
-    if (modelState == Models::MessageFeed::incomplete && value < progressSize) {
+    if (modelState == Models::MessageFeed::incomplete && value < progressSize)
         model()->fetchMore(rootIndex());
-    }
     
     QAbstractItemView::verticalScrollbarValueChanged(vo);
 }
@@ -391,16 +375,14 @@ void FeedView::setAnchorHovered(Shared::Hover type) {
 }
 
 void FeedView::mouseMoveEvent(QMouseEvent* event) {
-    if (!isVisible()) {
+    if (!isVisible())
         return;
-    }
 
     dragEndPoint = event->localPos().toPoint();
     if (mousePressed) {
         QPoint distance = dragStartPoint - dragEndPoint;
-        if (distance.manhattanLength() > 5) {
+        if (distance.manhattanLength() > 5)
             dragging = true;
-        }
     }
     
     QAbstractItemView::mouseMoveEvent(event);
@@ -423,11 +405,10 @@ void FeedView::mouseMoveEvent(QMouseEvent* event) {
             QModelIndex index = indexAt(dragEndPoint);
             if (index.isValid()) {
                 QRect rect = visualRect(index);
-                if (rect.contains(dragEndPoint)) {
+                if (rect.contains(dragEndPoint))
                     setAnchorHovered(del->hoverType(dragEndPoint, index, rect));
-                } else {
+                else
                     setAnchorHovered(Shared::Hover::nothing);
-                }
             } else {
                 setAnchorHovered(Shared::Hover::nothing);
             }
@@ -447,9 +428,8 @@ void FeedView::mousePressEvent(QMouseEvent* event) {
             if (lastSelectedId.size()) {
                 Models::MessageFeed* feed = static_cast<Models::MessageFeed*>(model());
                 QModelIndex index = feed->modelIndexById(lastSelectedId);
-                if (index.isValid()) {
+                if (index.isValid())
                     setDirtyRegion(visualRect(index));
-                }
             }
         }
     }
@@ -467,18 +447,16 @@ void FeedView::mouseDoubleClickEvent(QMouseEvent* event) {
             if (lastSelectedId.size()) {
                 Models::MessageFeed* feed = static_cast<Models::MessageFeed*>(model());
                 QModelIndex index = feed->modelIndexById(lastSelectedId);
-                if (index.isValid()) {
+                if (index.isValid())
                     setDirtyRegion(visualRect(index));
-                }
             }
 
             QModelIndex index = indexAt(dragStartPoint);
             QRect rect = visualRect(index);
             if (rect.contains(dragStartPoint)) {
                 selectedText = del->leftDoubleClick(dragStartPoint, index, rect);
-                if (selectedText.size() > 0) {
+                if (selectedText.size() > 0)
                     setDirtyRegion(rect);
-                }
             }
         }
     }
@@ -494,9 +472,8 @@ void FeedView::mouseReleaseEvent(QMouseEvent* event) {
             if (index.isValid()) {
                 QRect rect = visualRect(index);
                 MessageDelegate* del = static_cast<MessageDelegate*>(itemDelegate());
-                if (rect.contains(point)) {
+                if (rect.contains(point))
                     del->leftClick(point, index, rect);
-                }
             }
         }
         dragging = false;
diff --git a/ui/widgets/messageline/messagedelegate.cpp b/ui/widgets/messageline/messagedelegate.cpp
index 1830d71..35a73c2 100644
--- a/ui/widgets/messageline/messagedelegate.cpp
+++ b/ui/widgets/messageline/messagedelegate.cpp
@@ -29,6 +29,8 @@
 #include "messagedelegate.h"
 #include "messagefeed.h"
 
+#include "shared/defines.h"
+
 constexpr int textMargin = 2;
 constexpr int statusIconSize = 16;
 
@@ -70,27 +72,21 @@ MessageDelegate::MessageDelegate(QObject* parent):
     barHeight = bar.sizeHint().height();
 }
 
-MessageDelegate::~MessageDelegate()
-{
-    for (const std::pair<const QString, FeedButton*>& pair: *buttons){
+MessageDelegate::~MessageDelegate() {
+    for (const std::pair<const QString, FeedButton*>& pair: *buttons)
         delete pair.second;
-    }
     
-    for (const std::pair<const QString, QProgressBar*>& pair: *bars){
+    for (const std::pair<const QString, QProgressBar*>& pair: *bars)
         delete pair.second;
-    }
     
-    for (const std::pair<const QString, QLabel*>& pair: *statusIcons){
+    for (const std::pair<const QString, QLabel*>& pair: *statusIcons)
         delete pair.second;
-    }
     
-    for (const std::pair<const QString, QLabel*>& pair: *pencilIcons){
+    for (const std::pair<const QString, QLabel*>& pair: *pencilIcons)
         delete pair.second;
-    }
     
-    for (const std::pair<const QString, Preview*>& pair: *previews){
+    for (const std::pair<const QString, Preview*>& pair: *previews)
         delete pair.second;
-    }
     
     delete statusIcons;
     delete pencilIcons;
@@ -101,29 +97,26 @@ MessageDelegate::~MessageDelegate()
     delete bodyRenderer;
 }
 
-void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
-{
+void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const {
     QVariant vi = index.data(Models::MessageFeed::Bulk);
-    if (!vi.isValid()) {
+    if (!vi.isValid())
         return;
-    }
+
     Models::FeedItem data = qvariant_cast<Models::FeedItem>(vi);
     painter->save();
     painter->setRenderHint(QPainter::Antialiasing, true);
     
     paintBubble(data, painter, option);
     bool ntds = needToDrawSender(index, data);
-    if (ntds || option.rect.y() < 1) {
+    if (ntds || option.rect.y() < 1)
         paintAvatar(data, index, option, painter);
-    }
     
     QStyleOptionViewItem opt = option;
     opt.rect = option.rect.adjusted(bubbleMargin, bubbleMargin, -bubbleMargin, -bubbleMargin / 2);
-    if (!data.sentByMe) {
+    if (!data.sentByMe)
         opt.displayAlignment = Qt::AlignLeft | Qt::AlignTop;
-    } else {
+    else
         opt.displayAlignment = Qt::AlignRight | Qt::AlignTop;
-    }
 
     QRect rect;
     if (ntds) {
@@ -189,11 +182,11 @@ void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio
         QLabel* pencilIcon = getPencilIcon(data);
         
         pencilIcon->setParent(vp);
-        if (data.sentByMe) {
+        if (data.sentByMe)
             pencilIcon->move(opt.rect.left() + statusIconSize + margin, currentY);
-        } else {
+        else
             pencilIcon->move(opt.rect.right() - statusIconSize - margin, currentY);
-        }
+
         pencilIcon->show();
     } else {
         std::map<QString, QLabel*>::const_iterator itr = pencilIcons->find(data.id);
@@ -205,27 +198,24 @@ void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio
     
     painter->restore();
     
-    if (clearingWidgets) {
+    if (clearingWidgets)
         idsToKeep->insert(data.id);
-    }
 }
 
-void MessageDelegate::paintBubble(const Models::FeedItem& data, QPainter* painter, const QStyleOptionViewItem& option) const
-{
+void MessageDelegate::paintBubble(const Models::FeedItem& data, QPainter* painter, const QStyleOptionViewItem& option) const {
     painter->save();
-    if (data.sentByMe) {
+    if (data.sentByMe)
         painter->setBrush(option.palette.brush(QPalette::Inactive, QPalette::Highlight));
-    } else {
+    else
         painter->setBrush(option.palette.brush(QPalette::Window));
-    }
+
     painter->setPen(Qt::NoPen);
     painter->drawRoundedRect(option.rect, bubbleBorderRadius, bubbleBorderRadius);
     painter->restore();
 }
 
 
-void MessageDelegate::paintAvatar(const Models::FeedItem& data, const QModelIndex& index, const QStyleOptionViewItem& option, QPainter* painter) const
-{
+void MessageDelegate::paintAvatar(const Models::FeedItem& data, const QModelIndex& index, const QStyleOptionViewItem& option, QPainter* painter) const {
     int currentRow = index.row();
     int y = option.rect.y();
     bool firstAttempt = true;
@@ -255,11 +245,10 @@ void MessageDelegate::paintAvatar(const Models::FeedItem& data, const QModelInde
     QPainterPath path;
     int ax;
 
-    if (data.sentByMe) {
+    if (data.sentByMe)
         ax = option.rect.x() + option.rect.width() + margin;
-    } else {
+    else
         ax = margin;
-    }
 
     path.addEllipse(ax, y + margin / 2, avatarHeight, avatarHeight);
     painter->save();
@@ -268,8 +257,7 @@ void MessageDelegate::paintAvatar(const Models::FeedItem& data, const QModelInde
     painter->restore();
 }
 
-bool MessageDelegate::needToDrawAvatar(const QModelIndex& index, const Models::FeedItem& data, const QStyleOptionViewItem& option) const
-{
+bool MessageDelegate::needToDrawAvatar(const QModelIndex& index, const Models::FeedItem& data, const QStyleOptionViewItem& option) const {
     return (option.rect.y() < 1) || needToDrawSender(index, data);
 }
 
@@ -285,8 +273,7 @@ bool MessageDelegate::needToDrawSender(const QModelIndex& index, const Models::F
     }
 }
 
-QSize MessageDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const
-{
+QSize MessageDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const {
     QRect messageRect = option.rect.adjusted(bubbleMargin, margin / 2 + bubbleMargin, -(avatarHeight + 3 * margin + bubbleMargin), -(margin + bubbleMargin) / 2);
     QStyleOptionViewItem opt = option;
     opt.rect = messageRect;
@@ -360,12 +347,10 @@ QSize MessageDelegate::sizeHint(const QStyleOptionViewItem& option, const QModel
     return messageSize;
 }
 
-QRect MessageDelegate::getHoveredMessageBodyRect(const QModelIndex& index, const Models::FeedItem& data, const QRect& sizeHint) const
-{
+QRect MessageDelegate::getHoveredMessageBodyRect(const QModelIndex& index, const Models::FeedItem& data, const QRect& sizeHint) const {
     QRect localHint = sizeHint.adjusted(bubbleMargin, bubbleMargin + margin, -bubbleMargin, -bubbleMargin / 2);
-    if (needToDrawSender(index, data)) {
+    if (needToDrawSender(index, data))
         localHint.adjust(0, nickMetrics.lineSpacing() + textMargin, 0, 0);
-    }
 
     int attachHeight = 0;
     switch (data.attach.state) {
@@ -405,8 +390,7 @@ QRect MessageDelegate::getHoveredMessageBodyRect(const QModelIndex& index, const
     return localHint;
 }
 
-QString MessageDelegate::getAnchor(const QPoint& point, const QModelIndex& index, const QRect& sizeHint) const
-{
+QString MessageDelegate::getAnchor(const QPoint& point, const QModelIndex& index, const QRect& sizeHint) const {
     QVariant vi = index.data(Models::MessageFeed::Bulk);
     Models::FeedItem data = qvariant_cast<Models::FeedItem>(vi);
     if (data.text.size() > 0) {
@@ -425,16 +409,13 @@ QString MessageDelegate::getAnchor(const QPoint& point, const QModelIndex& index
     return QString();
 }
 
-void MessageDelegate::leftClick(const QPoint& point, const QModelIndex& index, const QRect& sizeHint) const
-{
+void MessageDelegate::leftClick(const QPoint& point, const QModelIndex& index, const QRect& sizeHint) const {
     QString anchor = getAnchor(point, index, sizeHint);
-    if (anchor.size() > 0) {
+    if (anchor.size() > 0)
         emit openLink(anchor);
-    }
 }
 
-QString MessageDelegate::leftDoubleClick(const QPoint& point, const QModelIndex& index, const QRect& sizeHint)
-{
+QString MessageDelegate::leftDoubleClick(const QPoint& point, const QModelIndex& index, const QRect& sizeHint) {
     QVariant vi = index.data(Models::MessageFeed::Bulk);
     Models::FeedItem data = qvariant_cast<Models::FeedItem>(vi);
     if (data.text.size() > 0) {
@@ -466,8 +447,7 @@ QString MessageDelegate::leftDoubleClick(const QPoint& point, const QModelIndex&
     return "";
 }
 
-Shared::Hover MessageDelegate::hoverType(const QPoint& point, const QModelIndex& index, const QRect& sizeHint) const
-{
+Shared::Hover MessageDelegate::hoverType(const QPoint& point, const QModelIndex& index, const QRect& sizeHint) const {
     QVariant vi = index.data(Models::MessageFeed::Bulk);
     Models::FeedItem data = qvariant_cast<Models::FeedItem>(vi);
     if (data.text.size() > 0) {
@@ -486,17 +466,15 @@ Shared::Hover MessageDelegate::hoverType(const QPoint& point, const QModelIndex&
                 return Shared::Hover::anchor;
             } else {
                 int position = lay->hitTest(translated, Qt::HitTestAccuracy::ExactHit);
-                if (position != -1) {
+                if (position != -1)
                     return Shared::Hover::text;
-                }
             }
         }
     }
     return Shared::Hover::nothing;
 }
 
-QString MessageDelegate::mouseDrag(const QPoint& start, const QPoint& end, const QModelIndex& index, const QRect& sizeHint)
-{
+QString MessageDelegate::mouseDrag(const QPoint& start, const QPoint& end, const QModelIndex& index, const QRect& sizeHint) {
     QVariant vi = index.data(Models::MessageFeed::Bulk);
     Models::FeedItem data = qvariant_cast<Models::FeedItem>(vi);
     if (data.text.size() > 0) {
@@ -529,30 +507,24 @@ QString MessageDelegate::mouseDrag(const QPoint& start, const QPoint& end, const
     return "";
 }
 
-QString MessageDelegate::clearSelection()
-{
+QString MessageDelegate::clearSelection() {
     QString lastSelectedId = currentId;
     currentId = "";
     selection = std::pair(0, 0);
     return lastSelectedId;
 }
 
-bool MessageDelegate::editorEvent(QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& index)
-{
+bool MessageDelegate::editorEvent(QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& index) {
     //qDebug() << event->type();
-    
-    
     return QStyledItemDelegate::editorEvent(event, model, option, index);
 }
 
-int MessageDelegate::paintButton(QPushButton* btn, QPainter* painter, bool sentByMe, QStyleOptionViewItem& option) const
-{
+int MessageDelegate::paintButton(QPushButton* btn, QPainter* painter, bool sentByMe, QStyleOptionViewItem& option) const {
     QPoint start;
-    if (sentByMe) {
+    if (sentByMe)
         start = {option.rect.x() + option.rect.width() - btn->width(), option.rect.top()};
-    } else {
+    else
         start = option.rect.topLeft();
-    }
     
     QWidget* vp = static_cast<QWidget*>(painter->device());
     btn->setParent(vp);
@@ -563,8 +535,7 @@ int MessageDelegate::paintButton(QPushButton* btn, QPainter* painter, bool sentB
     return btn->width();
 }
 
-int MessageDelegate::paintComment(const Models::FeedItem& data, QPainter* painter, QStyleOptionViewItem& option) const
-{
+int MessageDelegate::paintComment(const Models::FeedItem& data, QPainter* painter, QStyleOptionViewItem& option) const {
     painter->setFont(dateFont);
     QColor q = painter->pen().color();
     q.setAlpha(180);
@@ -576,8 +547,8 @@ int MessageDelegate::paintComment(const Models::FeedItem& data, QPainter* painte
     return rect.width();
 }
 
-int MessageDelegate::paintBar(QProgressBar* bar, QPainter* painter, bool sentByMe, QStyleOptionViewItem& option) const
-{
+int MessageDelegate::paintBar(QProgressBar* bar, QPainter* painter, bool sentByMe, QStyleOptionViewItem& option) const {
+    SHARED_UNUSED(sentByMe);
     QPoint start = option.rect.topLeft();
     bar->resize(option.rect.width(), barHeight);   
     
@@ -589,8 +560,7 @@ int MessageDelegate::paintBar(QProgressBar* bar, QPainter* painter, bool sentByM
     return option.rect.width();
 }
 
-int MessageDelegate::paintPreview(const Models::FeedItem& data, QPainter* painter, QStyleOptionViewItem& option) const
-{
+int MessageDelegate::paintPreview(const Models::FeedItem& data, QPainter* painter, QStyleOptionViewItem& option) const {
     Preview* preview = 0;
     std::map<QString, Preview*>::iterator itr = previews->find(data.id);
 
@@ -605,18 +575,16 @@ int MessageDelegate::paintPreview(const Models::FeedItem& data, QPainter* painte
         previews->insert(std::make_pair(data.id, preview));
     }
     
-    if (!preview->isFileReachable()) {      //this is the situation when the file preview couldn't be painted because the file was moved 
+    if (!preview->isFileReachable())        //this is the situation when the file preview couldn't be painted because the file was moved
         emit invalidPath(data.id);          //or deleted. This signal notifies the model, and the model notifies the core, preview can 
-    }                                       //handle being invalid for as long as I need and can be even become valid again with a new path
-    
+                                            //handle being invalid for as long as I need and can be even become valid again with a new path
     QSize pSize(preview->size());
     option.rect.adjust(0, pSize.height() + textMargin, 0, 0);
 
     return pSize.width();
 }
 
-QPushButton * MessageDelegate::getButton(const Models::FeedItem& data) const
-{
+QPushButton * MessageDelegate::getButton(const Models::FeedItem& data) const {
     std::map<QString, FeedButton*>::const_iterator itr = buttons->find(data.id);
     FeedButton* result = 0;
     if (itr != buttons->end()) {
@@ -640,8 +608,7 @@ QPushButton * MessageDelegate::getButton(const Models::FeedItem& data) const
     return result;
 }
 
-QProgressBar * MessageDelegate::getBar(const Models::FeedItem& data) const
-{
+QProgressBar * MessageDelegate::getBar(const Models::FeedItem& data) const {
     std::map<QString, QProgressBar*>::const_iterator barItr = bars->find(data.id);
     QProgressBar* result = 0;
     if (barItr != bars->end()) {
@@ -665,8 +632,7 @@ QProgressBar * MessageDelegate::getBar(const Models::FeedItem& data) const
     return result;
 }
 
-QLabel * MessageDelegate::getStatusIcon(const Models::FeedItem& data) const
-{
+QLabel * MessageDelegate::getStatusIcon(const Models::FeedItem& data) const {
     std::map<QString, QLabel*>::const_iterator itr = statusIcons->find(data.id);
     QLabel* result = 0;
     
@@ -680,9 +646,8 @@ QLabel * MessageDelegate::getStatusIcon(const Models::FeedItem& data) const
     QIcon q(Shared::icon(Shared::messageStateThemeIcons[static_cast<uint8_t>(data.state)]));
     QString tt = Shared::Global::getName(data.state);
     if (data.state == Shared::Message::State::error) {
-        if (data.error > 0) {
+        if (data.error > 0)
             tt += ": " + data.error;
-        }
     }
     if (result->toolTip() != tt) {                      //If i just assign pixmap every time unconditionally
         result->setPixmap(q.pixmap(statusIconSize));    //it invokes an infinite cycle of repaint
@@ -692,8 +657,7 @@ QLabel * MessageDelegate::getStatusIcon(const Models::FeedItem& data) const
     return result;
 }
 
-QLabel * MessageDelegate::getPencilIcon(const Models::FeedItem& data) const
-{
+QLabel * MessageDelegate::getPencilIcon(const Models::FeedItem& data) const {
     std::map<QString, QLabel*>::const_iterator itr = pencilIcons->find(data.id);
     QLabel* result = 0;
     
@@ -755,14 +719,12 @@ int MessageDelegate::paintBody(const Models::FeedItem& data, QPainter* painter,
     return 0;
 }
 
-void MessageDelegate::beginClearWidgets()
-{
+void MessageDelegate::beginClearWidgets() {
     idsToKeep->clear();
     clearingWidgets = true;
 }
 
-void MessageDelegate::endClearWidgets()
-{
+void MessageDelegate::endClearWidgets() {
     if (clearingWidgets) {
         removeElements(buttons, idsToKeep);
         removeElements(bars, idsToKeep);
@@ -775,14 +737,12 @@ void MessageDelegate::endClearWidgets()
     }
 }
 
-void MessageDelegate::onButtonPushed() const
-{
+void MessageDelegate::onButtonPushed() const {
     FeedButton* btn = static_cast<FeedButton*>(sender());
     emit buttonPushed(btn->messageId);
 }
 
-void MessageDelegate::clearHelperWidget(const Models::FeedItem& data) const
-{
+void MessageDelegate::clearHelperWidget(const Models::FeedItem& data) const {
     std::map<QString, FeedButton*>::const_iterator itr = buttons->find(data.id);
     if (itr != buttons->end()) {
         delete itr->second;
diff --git a/ui/widgets/messageline/messagedelegate.h b/ui/widgets/messageline/messagedelegate.h
index a05b4cd..16e39ff 100644
--- a/ui/widgets/messageline/messagedelegate.h
+++ b/ui/widgets/messageline/messagedelegate.h
@@ -30,6 +30,8 @@
 #include <QProgressBar>
 #include <QLabel>
 #include <QTextDocument>
+#include <QString>
+#include <QPainter>
 
 #include "shared/icons.h"
 #include "shared/global.h"
diff --git a/ui/widgets/messageline/messagefeed.cpp b/ui/widgets/messageline/messagefeed.cpp
index af772fd..1eba1fa 100644
--- a/ui/widgets/messageline/messagefeed.cpp
+++ b/ui/widgets/messageline/messagefeed.cpp
@@ -20,6 +20,7 @@
 
 #include <ui/models/element.h>
 #include <ui/models/room.h>
+#include <shared/defines.h>
 
 #include <QDebug>
 
@@ -228,24 +229,20 @@ std::set<Models::MessageFeed::MessageRoles> Models::MessageFeed::detectChanges(c
     return roles;
 }
 
-void Models::MessageFeed::removeMessage(const QString& id)
-{
+void Models::MessageFeed::removeMessage(const QString& id) {
     //todo;
 }
 
-Shared::Message Models::MessageFeed::getMessage(const QString& id)
-{
+Shared::Message Models::MessageFeed::getMessage(const QString& id) {
     StorageById::iterator itr = indexById.find(id);
-    if (itr == indexById.end()) {
+    if (itr == indexById.end())
         throw NotFound(id.toStdString(), rosterItem->getJid().toStdString(), rosterItem->getAccountName().toStdString());
-    }
 
     return **itr;
 }
 
 
-QVariant Models::MessageFeed::data(const QModelIndex& index, int role) const
-{
+QVariant Models::MessageFeed::data(const QModelIndex& index, int role) const {
     int i = index.row();
     QVariant answer;
     
@@ -266,11 +263,10 @@ QVariant Models::MessageFeed::data(const QModelIndex& index, int role) const
                 if (sentByMe(*msg)) {
                     answer = rosterItem->getAccountName();
                 } else {
-                    if (rosterItem->isRoom()) {
+                    if (rosterItem->isRoom())
                         answer = msg->getFromResource();
-                    } else {
+                    else
                         answer = rosterItem->getDisplayedName();
-                    }
                 }
                 break;
             case Date: 
@@ -290,19 +286,17 @@ QVariant Models::MessageFeed::data(const QModelIndex& index, int role) const
                 if (sentByMe(*msg)) {
                     path = rosterItem->getAccountAvatarPath();
                 } else if (!rosterItem->isRoom()) {
-                    if (rosterItem->getAvatarState() != Shared::Avatar::empty) {
+                    if (rosterItem->getAvatarState() != Shared::Avatar::empty)
                         path = rosterItem->getAvatarPath();
-                    }
                 } else {
                     const Room* room = static_cast<const Room*>(rosterItem);
                     path = room->getParticipantIconPath(msg->getFromResource());
                 }
                 
-                if (path.size() == 0) {
+                if (path.size() == 0)
                     answer = Shared::iconPath("user", true);
-                } else {
+                else
                     answer = path;
-                }
             }
                 break;
             case Attach: 
@@ -342,15 +336,14 @@ QVariant Models::MessageFeed::data(const QModelIndex& index, int role) const
                         item.avatar = room->getParticipantIconPath(msg->getFromResource());
                     } else {
                         item.sender = rosterItem->getDisplayedName();
-                        if (rosterItem->getAvatarState() != Shared::Avatar::empty) {
+                        if (rosterItem->getAvatarState() != Shared::Avatar::empty)
                             item.avatar = rosterItem->getAvatarPath();
-                        }
                     }
                 }
                 
-                if (item.avatar.size() == 0) {
+                if (item.avatar.size() == 0)
                     item.avatar = Shared::iconPath("user", true);
-                }
+
                 item.attach = fillAttach(*msg);
                 answer.setValue(item);
             }
@@ -363,13 +356,12 @@ QVariant Models::MessageFeed::data(const QModelIndex& index, int role) const
     return answer;
 }
 
-int Models::MessageFeed::rowCount(const QModelIndex& parent) const
-{
+int Models::MessageFeed::rowCount(const QModelIndex& parent) const {
+    SHARED_UNUSED(parent);
     return storage.size();
 }
 
-bool Models::MessageFeed::markMessageAsRead(const QString& id) const
-{
+bool Models::MessageFeed::markMessageAsRead(const QString& id) const {
     std::set<QString>::const_iterator umi = unreadMessages->find(id);
     if (umi != unreadMessages->end()) {
         unreadMessages->erase(umi);
@@ -379,32 +371,29 @@ bool Models::MessageFeed::markMessageAsRead(const QString& id) const
     return false;
 }
 
-unsigned int Models::MessageFeed::unreadMessagesCount() const
-{
+unsigned int Models::MessageFeed::unreadMessagesCount() const {
     return unreadMessages->size();
 }
 
-bool Models::MessageFeed::canFetchMore(const QModelIndex& parent) const
-{
+bool Models::MessageFeed::canFetchMore(const QModelIndex& parent) const {
+    SHARED_UNUSED(parent);
     return syncState == incomplete;
 }
 
-void Models::MessageFeed::fetchMore(const QModelIndex& parent)
-{
+void Models::MessageFeed::fetchMore(const QModelIndex& parent) {
+    SHARED_UNUSED(parent);
     if (syncState == incomplete) {
         syncState = syncing;
         emit syncStateChange(syncState);
         
-        if (storage.size() == 0) {
+        if (storage.size() == 0)
             emit requestArchive("");
-        } else {
+        else
             emit requestArchive((*indexByTime.rbegin())->getId());
-        }
     }
 }
 
-void Models::MessageFeed::responseArchive(const std::list<Shared::Message> list, bool last)
-{
+void Models::MessageFeed::responseArchive(const std::list<Shared::Message> list, bool last) {
     Storage::size_type size = storage.size();
     
     beginInsertRows(QModelIndex(), size, size + list.size() - 1);
@@ -415,21 +404,19 @@ void Models::MessageFeed::responseArchive(const std::list<Shared::Message> list,
     endInsertRows();
     
     if (syncState == syncing) {
-        if (last) {
+        if (last)
             syncState = complete;
-        } else {
+        else
             syncState = incomplete;
-        }
+
         emit syncStateChange(syncState);
     }
 }
 
-QModelIndex Models::MessageFeed::index(int row, int column, const QModelIndex& parent) const
-{
-    if (!hasIndex(row, column, parent)) {
+QModelIndex Models::MessageFeed::index(int row, int column, const QModelIndex& parent) const{
+    if (!hasIndex(row, column, parent))
         return QModelIndex();
-    }
-    
+
     StorageByTime::iterator itr = indexByTime.nth(row);
     if (itr != indexByTime.end()) {
         Shared::Message* msg = *itr;
@@ -442,8 +429,7 @@ QModelIndex Models::MessageFeed::index(int row, int column, const QModelIndex& p
 
 QHash<int, QByteArray> Models::MessageFeed::roleNames() const {return roles;}
 
-bool Models::MessageFeed::sentByMe(const Shared::Message& msg) const
-{
+bool Models::MessageFeed::sentByMe(const Shared::Message& msg) const {
     if (rosterItem->isRoom()) {
         const Room* room = static_cast<const Room*>(rosterItem);
         return room->getNick().toLower() == msg.getFromResource().toLower();
@@ -452,8 +438,7 @@ bool Models::MessageFeed::sentByMe(const Shared::Message& msg) const
     }
 }
 
-Models::Attachment Models::MessageFeed::fillAttach(const Shared::Message& msg) const
-{
+Models::Attachment Models::MessageFeed::fillAttach(const Shared::Message& msg) const {
     ::Models::Attachment att;
     QString id = msg.getId();
     
@@ -501,16 +486,14 @@ Models::Attachment Models::MessageFeed::fillAttach(const Shared::Message& msg) c
     return att;
 }
 
-Models::Edition Models::MessageFeed::fillCorrection(const Shared::Message& msg) const
-{
+Models::Edition Models::MessageFeed::fillCorrection(const Shared::Message& msg) const {
     ::Models::Edition ed({msg.getEdited(), msg.getOriginalBody(), msg.getLastModified()});
     
     return ed;
 }
 
 
-void Models::MessageFeed::downloadAttachment(const QString& messageId)
-{
+void Models::MessageFeed::downloadAttachment(const QString& messageId) {
     bool notify = false;
     Err::const_iterator eitr = failedDownloads.find(messageId);
     if (eitr != failedDownloads.end()) {
@@ -532,13 +515,11 @@ void Models::MessageFeed::downloadAttachment(const QString& messageId)
         qDebug() << "An attempt to download an attachment for the message that doesn't exist. ID:" << messageId;
     }
     
-    if (notify) {
+    if (notify)
         emit dataChanged(ind, ind, {MessageRoles::Attach});
-    }
 }
 
-bool Models::MessageFeed::registerUpload(const QString& messageId)
-{
+bool Models::MessageFeed::registerUpload(const QString& messageId) {
     bool success = uploads.insert(std::make_pair(messageId, 0)).second; 
     
     QVector<int> roles({});
@@ -556,8 +537,7 @@ bool Models::MessageFeed::registerUpload(const QString& messageId)
     return success;
 }
 
-void Models::MessageFeed::fileProgress(const QString& messageId, qreal value, bool up)
-{
+void Models::MessageFeed::fileProgress(const QString& messageId, qreal value, bool up) {
     Progress* pr = 0;
     Err* err = 0;
     if (up) {
@@ -583,13 +563,11 @@ void Models::MessageFeed::fileProgress(const QString& messageId, qreal value, bo
     }
 }
 
-void Models::MessageFeed::fileComplete(const QString& messageId, bool up)
-{
+void Models::MessageFeed::fileComplete(const QString& messageId, bool up) {
     fileProgress(messageId, 1, up);
 }
 
-void Models::MessageFeed::fileError(const QString& messageId, const QString& error, bool up)
-{
+void Models::MessageFeed::fileError(const QString& messageId, const QString& error, bool up) {
     Err* failed;
     Progress* loads;
     if (up) {
@@ -601,33 +579,28 @@ void Models::MessageFeed::fileError(const QString& messageId, const QString& err
     }
     
     Progress::iterator pitr = loads->find(messageId);
-    if (pitr != loads->end()) {
+    if (pitr != loads->end())
         loads->erase(pitr);
-    }
     
     std::pair<Err::iterator, bool> pair = failed->insert(std::make_pair(messageId, error));
-    if (!pair.second) {
+    if (!pair.second)
         pair.first->second = error;
-    }
+
     QModelIndex ind = modelIndexById(messageId);
-    if (ind.isValid()) {
+    if (ind.isValid())
         emit dataChanged(ind, ind, {MessageRoles::Attach});
-    }
 }
 
-void Models::MessageFeed::incrementObservers()
-{
+void Models::MessageFeed::incrementObservers() {
     ++observersAmount;
 }
 
-void Models::MessageFeed::decrementObservers()
-{
+void Models::MessageFeed::decrementObservers() {
     --observersAmount;
 }
 
 
-QModelIndex Models::MessageFeed::modelIndexById(const QString& id) const
-{
+QModelIndex Models::MessageFeed::modelIndexById(const QString& id) const {
     StorageById::const_iterator itr = indexById.find(id);
     if (itr != indexById.end()) {
         Shared::Message* msg = *itr;
@@ -637,8 +610,7 @@ QModelIndex Models::MessageFeed::modelIndexById(const QString& id) const
     return QModelIndex();
 }
 
-QModelIndex Models::MessageFeed::modelIndexByTime(const QString& id, const QDateTime& time) const
-{
+QModelIndex Models::MessageFeed::modelIndexByTime(const QString& id, const QDateTime& time) const {
     if (indexByTime.size() > 0) {
         StorageByTime::const_iterator tItr = indexByTime.lower_bound(time);
         StorageByTime::const_iterator tEnd = indexByTime.upper_bound(time);
@@ -660,8 +632,7 @@ QModelIndex Models::MessageFeed::modelIndexByTime(const QString& id, const QDate
     return QModelIndex();
 }
 
-void Models::MessageFeed::reportLocalPathInvalid(const QString& messageId)
-{
+void Models::MessageFeed::reportLocalPathInvalid(const QString& messageId) {
     StorageById::iterator itr = indexById.find(messageId);
     if (itr == indexById.end()) {
         qDebug() << "received a command to change a message, but the message couldn't be found, skipping";
@@ -679,13 +650,11 @@ void Models::MessageFeed::reportLocalPathInvalid(const QString& messageId)
     emit dataChanged(index, index, {MessageRoles::Attach});
 }
 
-Models::MessageFeed::SyncState Models::MessageFeed::getSyncState() const
-{
+Models::MessageFeed::SyncState Models::MessageFeed::getSyncState() const {
     return syncState;
 }
 
-void Models::MessageFeed::requestLatestMessages()
-{
+void Models::MessageFeed::requestLatestMessages() {
     if (syncState != syncing) {
         syncState = syncing;
         emit syncStateChange(syncState);
diff --git a/ui/widgets/room.cpp b/ui/widgets/room.cpp
index 91d7255..3409a86 100644
--- a/ui/widgets/room.cpp
+++ b/ui/widgets/room.cpp
@@ -31,12 +31,10 @@ Room::Room(Models::Account* acc, Models::Room* p_room, QWidget* parent):
     connect(room, &Models::Room::participantLeft, this, &Room::onParticipantLeft);
 }
 
-Room::~Room()
-{
+Room::~Room() {
 }
 
-Shared::Message Room::createMessage() const
-{
+Shared::Message Room::createMessage() const {
     Shared::Message msg = Conversation::createMessage();
     msg.setType(Shared::Message::groupChat);
     msg.setFromJid(room->getJid());
@@ -45,13 +43,12 @@ Shared::Message Room::createMessage() const
     return msg;
 }
 
-bool Room::autoJoined() const
-{
+bool Room::autoJoined() const {
     return room->getAutoJoin();
 }
 
-void Room::onRoomChanged(Models::Item* item, int row, int col)
-{
+void Room::onRoomChanged(Models::Item* item, int row, int col) {
+    SHARED_UNUSED(row);
     if (item == room) {
         switch (col) {
             case 0:
@@ -67,11 +64,10 @@ void Room::onRoomChanged(Models::Item* item, int row, int col)
     }
 }
 
-void Room::onParticipantJoined(const Models::Participant& participant)
-{
-    
+void Room::onParticipantJoined(const Models::Participant& participant) {
+    SHARED_UNUSED(participant);
 }
 
-void Room::onParticipantLeft(const QString& name)
-{
+void Room::onParticipantLeft(const QString& name) {
+    SHARED_UNUSED(name);
 }

From 9d688e85964c64c20d59fa40d87ce69ea66bd0e1 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Thu, 2 Nov 2023 19:55:11 -0300
Subject: [PATCH 251/281] full transition to lmdbal, DOESNT WORK, DONT TAKE!

---
 CMakeLists.txt                      |   11 +-
 core/account.h                      |    2 +-
 core/components/urlstorage.cpp      |   96 +--
 core/components/urlstorage.h        |    2 +-
 core/handlers/messagehandler.cpp    |    2 +-
 core/handlers/omemohandler.h        |    2 +-
 core/rosteritem.cpp                 |   34 +-
 core/storage/archive.cpp            | 1181 +++++++--------------------
 core/storage/archive.h              |  143 +---
 core/storage/cache.h                |   55 --
 core/storage/cache.hpp              |  102 ---
 core/storage/storage.h              |   70 --
 core/storage/storage.hpp            |  226 -----
 external/qxmpp                      |    2 +-
 external/simpleCrypt/CMakeLists.txt |    2 +-
 main/main.cpp                       |    2 +-
 shared/message.cpp                  |  306 +++----
 shared/message.h                    |   11 +-
 18 files changed, 497 insertions(+), 1752 deletions(-)
 delete mode 100644 core/storage/cache.h
 delete mode 100644 core/storage/cache.hpp
 delete mode 100644 core/storage/storage.h
 delete mode 100644 core/storage/storage.hpp

diff --git a/CMakeLists.txt b/CMakeLists.txt
index d29bb4f..5950c36 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -60,6 +60,7 @@ if (WITH_OMEMO)
   if (PKG_CONFIG_FOUND)
     pkg_check_modules(OMEMO libomemo-c)
     if (OMEMO_FOUND)
+      target_compile_definitions(squawk PRIVATE WITH_OMEMO)
       message("Building with support of OMEMO")
     else ()
       message("libomemo-c package wasn't found, trying to build without OMEMO support")
@@ -73,7 +74,11 @@ endif ()
 
 ## QXmpp
 if (SYSTEM_QXMPP)
-  find_package(QXmpp CONFIG)
+  if (WITH_OMEMO)
+    find_package(QXmpp CONFIG COMPONENTS Omemo)
+  else ()
+    find_package(QXmpp CONFIG)
+  endif ()
 
   if (NOT QXmpp_FOUND)
     set(SYSTEM_QXMPP OFF)
@@ -138,7 +143,6 @@ if (NOT SYSTEM_QXMPP)
     target_include_directories(squawk PRIVATE ${CMAKE_SOURCE_DIR}/external/qxmpp/src/omemo)
     target_include_directories(squawk PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/external/qxmpp/src/omemo)
     set(BUILD_OMEMO ON)
-    target_compile_definitions(squawk PRIVATE WITH_OMEMO)
   else ()
     set(BUILD_OMEMO OFF)
   endif ()
@@ -150,6 +154,9 @@ if (NOT SYSTEM_QXMPP)
   endif ()
 else ()
   target_link_libraries(squawk PRIVATE QXmpp::QXmpp)
+  if (WITH_OMEMO)
+    target_link_libraries(squawk PRIVATE QXmpp::Omemo)
+  endif ()
 endif ()
 
 ## LMDBAL
diff --git a/core/account.h b/core/account.h
index fe3988c..a9425e7 100644
--- a/core/account.h
+++ b/core/account.h
@@ -63,7 +63,7 @@
 #include "handlers/discoveryhandler.h"
 
 #ifdef WITH_OMEMO
-#include <QXmppOmemoManager.h>
+#include <Omemo/QXmppOmemoManager.h>
 #include <QXmppTrustManager.h>
 #include "handlers/trusthandler.h"
 #include "handlers/omemohandler.h"
diff --git a/core/components/urlstorage.cpp b/core/components/urlstorage.cpp
index c745236..31f36ad 100644
--- a/core/components/urlstorage.cpp
+++ b/core/components/urlstorage.cpp
@@ -41,18 +41,12 @@ void Core::UrlStorage::close() {
 }
 
 void Core::UrlStorage::writeInfo(const QString& key, const Core::UrlStorage::UrlInfo& info, bool overwrite) {
-    LMDBAL::TransactionID txn = base.beginTransaction();
-    
-    try {
-        writeInfo(key, info, txn, overwrite);
-        base.commitTransaction(txn);
-    } catch (...) {
-        base.abortTransaction(txn);
-        throw;
-    }
+    LMDBAL::WriteTransaction txn = base.beginTransaction();
+    writeInfo(key, info, txn, overwrite);
+    txn.commit();
 }
 
-void Core::UrlStorage::writeInfo(const QString& key, const Core::UrlStorage::UrlInfo& info, MDB_txn* txn, bool overwrite) {
+void Core::UrlStorage::writeInfo(const QString& key, const Core::UrlStorage::UrlInfo& info, const LMDBAL::WriteTransaction& txn, bool overwrite) {
     if (overwrite)
         urlToInfo->forceRecord(key, info, txn);
     else
@@ -95,15 +89,11 @@ Core::UrlStorage::UrlInfo Core::UrlStorage::addToInfo(
     const QString& path
 ) {
     UrlInfo info;
-    LMDBAL::TransactionID txn = base.beginTransaction();
+    LMDBAL::WriteTransaction txn = base.beginTransaction();
     
     try {
         urlToInfo->getRecord(url, info, txn);
-    } catch (const LMDBAL::NotFound& e) {
-    } catch (...) {
-        base.abortTransaction(txn);
-        throw;
-    }
+    } catch (const LMDBAL::NotFound& e) {}
     
     bool pathChange = false;
     bool listChange = false;
@@ -118,15 +108,8 @@ Core::UrlStorage::UrlInfo Core::UrlStorage::addToInfo(
         listChange = info.addMessage(account, jid, id);
 
     if (pathChange || listChange) {
-        try {
-            writeInfo(url, info, txn, true);
-            base.commitTransaction(txn);
-        } catch (...) {
-            base.abortTransaction(txn);
-            throw;
-        }
-    } else {
-        base.abortTransaction(txn);
+        writeInfo(url, info, txn, true);
+        txn.commit();
     }
     
     return info;
@@ -134,70 +117,51 @@ Core::UrlStorage::UrlInfo Core::UrlStorage::addToInfo(
 
 std::list<Shared::MessageInfo> Core::UrlStorage::setPath(const QString& url, const QString& path) {
     std::list<Shared::MessageInfo> list;
-    LMDBAL::TransactionID txn = base.beginTransaction();
+    LMDBAL::WriteTransaction txn = base.beginTransaction();
     UrlInfo info;
     
     try {
         urlToInfo->getRecord(url, info, txn);
         info.getMessages(list);
-    } catch (const LMDBAL::NotFound& e) {
-    } catch (...) {
-        base.abortTransaction(txn);
-        throw;
-    }
+    } catch (const LMDBAL::NotFound& e) {}
     
     info.setPath(path);
-    try {
-        writeInfo(url, info, txn, true);
-        base.commitTransaction(txn);
-    } catch (...) {
-        base.abortTransaction(txn);
-        throw;
-    }
+    writeInfo(url, info, txn, true);
+    txn.commit();
     
     return list;
 }
 
 std::list<Shared::MessageInfo> Core::UrlStorage::removeFile(const QString& url) {
     std::list<Shared::MessageInfo> list;
-    LMDBAL::TransactionID txn = base.beginTransaction();
+    LMDBAL::WriteTransaction txn = base.beginTransaction();
     UrlInfo info;
     
-    try {
-        urlToInfo->getRecord(url, info, txn);
-        urlToInfo->removeRecord(url);
-        info.getMessages(list);
-        
-        if (info.hasPath())
-            pathToUrl->removeRecord(info.getPath());
+    urlToInfo->getRecord(url, info, txn);
+    urlToInfo->removeRecord(url, txn);
+    info.getMessages(list);
 
-        base.commitTransaction(txn);
-    } catch (...) {
-        base.abortTransaction(txn);
-        throw;
-    }
+    if (info.hasPath())
+        pathToUrl->removeRecord(info.getPath(), txn);
+
+    txn.commit();
     
     return list;
 }
 
 std::list<Shared::MessageInfo> Core::UrlStorage::deletedFile(const QString& path) {
     std::list<Shared::MessageInfo> list;
-    LMDBAL::TransactionID txn = base.beginTransaction();
+    LMDBAL::WriteTransaction txn = base.beginTransaction();
     
-    try {
-        QString url = pathToUrl->getRecord(path, txn);
-        pathToUrl->removeRecord(path);
-        
-        UrlInfo info = urlToInfo->getRecord(url, txn);
-        info.getMessages(list);
-        info.setPath(QString());
-        urlToInfo->changeRecord(url, info, txn);
-        
-        base.commitTransaction(txn);
-    } catch (...) {
-        base.abortTransaction(txn);
-        throw;
-    }
+    QString url = pathToUrl->getRecord(path, txn);
+    pathToUrl->removeRecord(path, txn);
+
+    UrlInfo info = urlToInfo->getRecord(url, txn);
+    info.getMessages(list);
+    info.setPath(QString());
+    urlToInfo->changeRecord(url, info, txn);
+
+    txn.commit();
     
     return list;
 }
diff --git a/core/components/urlstorage.h b/core/components/urlstorage.h
index ee8e30d..fc9d71d 100644
--- a/core/components/urlstorage.h
+++ b/core/components/urlstorage.h
@@ -60,7 +60,7 @@ private:
     
 private:
     void writeInfo(const QString& key, const UrlInfo& info, bool overwrite = false);
-    void writeInfo(const QString& key, const UrlInfo& info, MDB_txn* txn, bool overwrite = false);
+    void writeInfo(const QString& key, const UrlInfo& info, const LMDBAL::WriteTransaction& txn, bool overwrite = false);
     UrlInfo addToInfo(const QString& url, const QString& account, const QString& jid, const QString& id, const QString& path = "-s");
     
 public:
diff --git a/core/handlers/messagehandler.cpp b/core/handlers/messagehandler.cpp
index 3ed8dec..2632848 100644
--- a/core/handlers/messagehandler.cpp
+++ b/core/handlers/messagehandler.cpp
@@ -591,7 +591,7 @@ void Core::MessageHandler::resendMessage(const QString& jid, const QString& id)
             } else {
                 qDebug() << "An attempt to resend a message to" << jid << "by account" << acc->getName() << ", but this message seems to have been normally sent, this method was made to retry sending failed to be sent messages, skipping";
             }
-        } catch (const Archive::NotFound& err) {
+        } catch (const LMDBAL::NotFound& err) {
             qDebug() << "An attempt to resend a message to" << jid << "by account" << acc->getName() << ", but this message wasn't found in history, skipping";
         }
     } else {
diff --git a/core/handlers/omemohandler.h b/core/handlers/omemohandler.h
index 1ea1f26..e626b4c 100644
--- a/core/handlers/omemohandler.h
+++ b/core/handlers/omemohandler.h
@@ -21,7 +21,7 @@
 #include <list>
 #include <functional>
 
-#include <QXmppOmemoStorage.h>
+#include <Omemo/QXmppOmemoStorage.h>
 #include <cache.h>
 
 #include <shared/keyinfo.h>
diff --git a/core/rosteritem.cpp b/core/rosteritem.cpp
index 545e47f..afbf836 100644
--- a/core/rosteritem.cpp
+++ b/core/rosteritem.cpp
@@ -27,7 +27,7 @@ Core::RosterItem::RosterItem(const QString& pJid, const QString& pAccount, QObje
     account(pAccount),
     name(),
     archiveState(empty),
-    archive(new Archive(jid)),
+    archive(new Archive(account, jid)),
     syncronizing(false),
     requestedCount(0),
     requestedBefore(),
@@ -38,7 +38,7 @@ Core::RosterItem::RosterItem(const QString& pJid, const QString& pAccount, QObje
     toCorrect(),
     muc(false)
 {
-    archive->open(account);
+    archive->open();
     
     if (archive->size() != 0) {
         if (archive->isFromTheBeginning())
@@ -126,7 +126,8 @@ void Core::RosterItem::nextRequest() {
                             last = true;
                         }
                     }
-                } catch (const Archive::Empty& e) {
+                //} catch (const Archive::Empty& e) {
+                } catch (const LMDBAL::NotFound& e) {
                     last = true;
                 }
             } else if (archiveState == empty && responseCache.size() == 0) {
@@ -168,7 +169,8 @@ void Core::RosterItem::performRequest(int count, const QString& before) {
             try {
                 Shared::Message msg = archive->newest();
                 emit needHistory("", getId(msg), msg.getTime());
-            } catch (const Archive::Empty& e) {                     //this can happen when the only message in archive is not server stored (error, for example)
+            //} catch (const Archive::Empty& e) {
+            } catch (const LMDBAL::NotFound& e) {   //this can happen when the only message in archive is not server stored (error, for example)
                 emit needHistory(before, "");
             }
         }
@@ -186,14 +188,14 @@ void Core::RosterItem::performRequest(int count, const QString& before) {
                     std::list<Shared::Message> arc = archive->getBefore(requestedCount - responseCache.size(), lBefore);
                     responseCache.insert(responseCache.begin(), arc.begin(), arc.end());
                     found = true;
-                } catch (const Archive::NotFound& e) {
-                    requestCache.emplace_back(requestedCount, before);
-                    requestedCount = -1;
-                    emit needHistory(getId(archive->oldest()), "");
-                } catch (const Archive::Empty& e) {
+                } catch (const LMDBAL::NotFound& e) {
                     requestCache.emplace_back(requestedCount, before);
                     requestedCount = -1;
                     emit needHistory(getId(archive->oldest()), "");
+                // } catch (const Archive::Empty& e) {
+                //     requestCache.emplace_back(requestedCount, before);
+                //     requestedCount = -1;
+                //     emit needHistory(getId(archive->oldest()), "");
                 }
                 
                 if (found) {
@@ -226,10 +228,10 @@ void Core::RosterItem::performRequest(int count, const QString& before) {
             try {
                 std::list<Shared::Message> arc = archive->getBefore(requestedCount - responseCache.size(), before);
                 responseCache.insert(responseCache.begin(), arc.begin(), arc.end());
-            } catch (const Archive::NotFound& e) {
-                qDebug("requesting id hasn't been found in archive, skipping");
-            } catch (const Archive::Empty& e) {
+            } catch (const LMDBAL::NotFound& e) {
                 qDebug("requesting id hasn't been found in archive, skipping");
+            // } catch (const Archive::Empty& e) {
+            //     qDebug("requesting id hasn't been found in archive, skipping");
             }
             nextRequest();
             break;
@@ -311,7 +313,7 @@ bool Core::RosterItem::changeMessage(const QString& id, const QMap<QString, QVar
         try {
             archive->changeMessage(id, data);
             found = true;
-        } catch (const Archive::NotFound& e) {
+        } catch (const LMDBAL::NotFound& e) {
             qDebug() << "An attempt to change state to the message" << id << "but it couldn't be found";
         }
     }
@@ -387,10 +389,8 @@ void Core::RosterItem::flushMessagesToArchive(bool finished, const QString& firs
                     std::list<Shared::Message> arc = archive->getBefore(requestedCount - responseCache.size(), before);
                     responseCache.insert(responseCache.begin(), arc.begin(), arc.end());
                     found = true;
-                } catch (const Archive::NotFound& e) {
-                    
-                } catch (const Archive::Empty& e) {
-                    
+                } catch (const LMDBAL::NotFound& e) {
+                // } catch (const Archive::Empty& e) {
                 }
                 if (!found || requestedCount > int(responseCache.size())) {
                     if (archiveState == complete) {
diff --git a/core/storage/archive.cpp b/core/storage/archive.cpp
index 8330cff..6b00f37 100644
--- a/core/storage/archive.cpp
+++ b/core/storage/archive.cpp
@@ -24,858 +24,310 @@
 #include <QDataStream>
 #include <QDir>
 
-Core::Archive::Archive(const QString& p_jid, QObject* parent):
+Core::Archive::Archive(const QString& account, const QString& p_jid, QObject* parent):
     QObject(parent),
     jid(p_jid),
+    account(account),
     opened(false),
-    fromTheBeginning(false),
-    encryptionEnabled(false),
-    environment(),
-    main(),
-    order(),
-    stats(),
-    avatars()
-{
-}
+    db(account + "/" + jid),
+    messages(db.addStorage<QString, Shared::Message>("messages")),
+    order(db.addStorage<uint64_t, QString>("order")),
+    stats(db.addStorage<QString, QVariant>("stats")),
+    avatars(db.addStorage<QString, AvatarInfo>("avatars")),
+    stanzaIdToId(db.addStorage<QString, QString>("stanzaIdToId")),
+    cursor(order->createCursor())
+{}
 
-Core::Archive::~Archive()
-{
+Core::Archive::~Archive() {
     close();
 }
 
-void Core::Archive::open(const QString& account)
-{
-    if (!opened) {
-        mdb_env_create(&environment);
-        QString path(QStandardPaths::writableLocation(QStandardPaths::CacheLocation));
-        path += "/" + account + "/" + jid;
-        QDir cache(path);
-        
-        if (!cache.exists()) {
-            bool res = cache.mkpath(path);
-            if (!res) {
-                throw Directory(path.toStdString());
-            }
-        }
-        
-        mdb_env_set_maxdbs(environment, 5);
-        mdb_env_set_mapsize(environment,
-#ifdef Q_OS_WIN
-                            // On Windows, the file is immediately allocated.
-                            // So we have to limit the size.
-                            80UL * 1024UL * 1024UL
-#else
-                            512UL * 1024UL * 1024UL
-#endif
-        );
-        mdb_env_open(environment, path.toStdString().c_str(), 0, 0664);
-        
-        MDB_txn *txn;
-        mdb_txn_begin(environment, NULL, 0, &txn);
-        mdb_dbi_open(txn, "main", MDB_CREATE, &main);
-        mdb_dbi_open(txn, "order", MDB_CREATE | MDB_INTEGERKEY | MDB_INTEGERDUP | MDB_DUPSORT, &order);
-        mdb_dbi_open(txn, "stats", MDB_CREATE, &stats);
-        mdb_dbi_open(txn, "avatars", MDB_CREATE, &avatars);
-        mdb_dbi_open(txn, "sid", MDB_CREATE, &sid);
-        mdb_txn_commit(txn);
-        
-        mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn);
-        try {
-            fromTheBeginning = getStatBoolValue("beginning", txn);
-        } catch (const NotFound& e) {
-            fromTheBeginning = false;
-        }
+void Core::Archive::open() {
+    db.open();
+    LMDBAL::WriteTransaction txn = db.beginTransaction();
 
-        try {
-            encryptionEnabled = getStatBoolValue("encryptionEnabled", txn);
-        } catch (const NotFound& e) {
-            encryptionEnabled = false;
-        }
-        
-        std::string sJid = jid.toStdString();
-        AvatarInfo info;
-        bool hasAvatar = readAvatarInfo(info, sJid, txn);
-        mdb_txn_abort(txn);
-        
-        if (hasAvatar) {
-            QFile ava(path + "/" + sJid.c_str() + "." + info.type);
-            if (!ava.exists()) {
-                bool success = dropAvatar(sJid);
-                if (!success) {
-                    qDebug() << "error opening archive" << jid << "for account" << account 
-                    << ". There is supposed to be avatar but the file doesn't exist, couldn't even drop it, it surely will lead to an error";
-                }
-            }
-        }
-        
-        opened = true;
-    }
-}
-
-void Core::Archive::close()
-{
-    if (opened) {
-        mdb_dbi_close(environment, sid);
-        mdb_dbi_close(environment, avatars);
-        mdb_dbi_close(environment, stats);
-        mdb_dbi_close(environment, order);
-        mdb_dbi_close(environment, main);
-        mdb_env_close(environment);
-        opened = false;
-    }
-}
-
-bool Core::Archive::addElement(const Shared::Message& message)
-{
-    if (!opened) {
-        throw Closed("addElement", jid.toStdString());
-    }
-    qDebug() << "Adding message with id " << message.getId();
-    QByteArray ba;
-    QDataStream ds(&ba, QIODevice::WriteOnly);
-    message.serialize(ds);
-    quint64 stamp = message.getTime().toMSecsSinceEpoch();
-    const std::string& id = message.getId().toStdString();
-    
-    MDB_val lmdbKey, lmdbData;
-    lmdbKey.mv_size = id.size();
-    lmdbKey.mv_data = (char*)id.c_str();
-    lmdbData.mv_size = ba.size();
-    lmdbData.mv_data = (uint8_t*)ba.data();
-    MDB_txn *txn;
-    mdb_txn_begin(environment, NULL, 0, &txn);
-    int rc;
-    rc = mdb_put(txn, main, &lmdbKey, &lmdbData, MDB_NOOVERWRITE);
-    if (rc == 0) {
-        MDB_val orderKey;
-        orderKey.mv_size = 8;
-        orderKey.mv_data = (uint8_t*) &stamp;
-        
-        rc = mdb_put(txn, order, &orderKey, &lmdbKey, 0);
-        if (rc) {
-            qDebug() << "An element couldn't be inserted into the index" << mdb_strerror(rc);
-            mdb_txn_abort(txn);
-            return false;
-        } else {
-            if (message.getStanzaId().size() > 0) {
-                const std::string& szid = message.getStanzaId().toStdString();
-                
-                lmdbKey.mv_size = szid.size();
-                lmdbKey.mv_data = (char*)szid.c_str();
-                lmdbData.mv_size = id.size();
-                lmdbData.mv_data = (uint8_t*)id.data();
-                rc = mdb_put(txn, sid, &lmdbKey, &lmdbData, MDB_NOOVERWRITE);
-                
-                if (rc) {
-                    qDebug() << "An element stanzaId to id pair couldn't be inserted into the archive" << mdb_strerror(rc);
-                    mdb_txn_abort(txn);
-                    return false;
-                } else {
-                    rc = mdb_txn_commit(txn);
-                    if (rc) {
-                        qDebug() << "A transaction error: " << mdb_strerror(rc);
-                        return false;
-                    }
-                    return true;
-                }
-                
-            } else {
-                rc = mdb_txn_commit(txn);
-                if (rc) {
-                    qDebug() << "A transaction error: " << mdb_strerror(rc);
-                    return false;
-                }
-                return true;
-            }
-        }
-    } else {
-        qDebug() << "An element couldn't been added to the archive, skipping" << mdb_strerror(rc);
-        mdb_txn_abort(txn);
-        return false;
-    }
-}
-
-void Core::Archive::clear()
-{
-    if (!opened) {
-        throw Closed("clear", jid.toStdString());
-    }
-    
-    MDB_txn *txn;
-    mdb_txn_begin(environment, NULL, 0, &txn);
-    mdb_drop(txn, main, 0);
-    mdb_drop(txn, order, 0);
-    mdb_drop(txn, stats, 0);
-    mdb_drop(txn, avatars, 0);
-    mdb_drop(txn, sid, 0);
-    mdb_txn_commit(txn);
-}
-
-Shared::Message Core::Archive::getElement(const QString& id) const
-{
-    if (!opened) {
-        throw Closed("getElement", jid.toStdString());
-    }
-    
-    MDB_txn *txn;
-    mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn);
-    
+    AvatarInfo info;
+    bool hasAvatar = false;
     try {
-        Shared::Message msg = getMessage(id.toStdString(), txn);
-        mdb_txn_abort(txn);
-        return msg;
+        avatars->getRecord(jid, info, txn);
+        hasAvatar = true;
+    } catch (const LMDBAL::NotFound& e) {}
+
+    if (!hasAvatar)
+        return;
+
+    QFile ava(db.getPath() + "/" + jid + "." + info.type);
+    if (ava.exists())
+        return;
+
+    try {
+        avatars->removeRecord(jid, txn);
+        txn.commit();
+    } catch (const std::exception& e) {
+        qDebug() << e.what();
+        qDebug() << "error opening archive" << jid << "for account" << account
+        << ". There is supposed to be avatar but the file doesn't exist, couldn't even drop it, it surely will lead to an error";
+    }
+}
+
+void Core::Archive::close() {
+    db.close();
+}
+
+bool Core::Archive::addElement(const Shared::Message& message) {
+    QString id = message.getId();
+    qDebug() << "Adding message with id " << id;
+
+    try {
+        LMDBAL::WriteTransaction txn = db.beginTransaction();
+        messages->addRecord(id, message, txn);
+        order->addRecord(message.getTime().toMSecsSinceEpoch(), id, txn);
+        QString stanzaId = message.getStanzaId();
+        if (!stanzaId.isEmpty())
+            stanzaIdToId->addRecord(stanzaId, id);
+
+        txn.commit();
+        return true;
+    } catch (const std::exception& e) {
+        qDebug() << "Could not add message with id " + id;
+        qDebug() << e.what();
+    }
+
+    return false;
+}
+
+void Core::Archive::clear() {
+    db.drop();
+}
+
+Shared::Message Core::Archive::getElement(const QString& id) const {
+    return messages->getRecord(id);
+}
+
+bool Core::Archive::hasElement(const QString& id) const {
+    return messages->checkRecord(id);
+}
+
+void Core::Archive::changeMessage(const QString& id, const QMap<QString, QVariant>& data) {
+    LMDBAL::WriteTransaction txn = db.beginTransaction();
+    Shared::Message msg = messages->getRecord(id, txn);
+
+    bool hadStanzaId = !msg.getStanzaId().isEmpty();
+    QDateTime oTime = msg.getTime();
+    bool idChange = msg.change(data);
+    QString newId = msg.getId();
+    QDateTime nTime = msg.getTime();
+
+    bool orderChange = oTime != nTime;
+    if (idChange || orderChange) {
+        if (idChange)
+            messages->removeRecord(id, txn);
+
+        if (orderChange)
+            order->removeRecord(oTime.toMSecsSinceEpoch(), txn);
+
+        order->forceRecord(nTime.toMSecsSinceEpoch(), newId, txn);
+    }
+
+    QString sid = msg.getStanzaId();
+    if (!sid.isEmpty() && (idChange || !hadStanzaId))
+        stanzaIdToId->forceRecord(sid, newId, txn);
+
+    messages->forceRecord(newId, msg, txn);
+    txn.commit();
+}
+
+Shared::Message Core::Archive::newest() const {
+    LMDBAL::Transaction txn = db.beginReadOnlyTransaction();
+
+    try {
+        cursor.open(txn);
+        while (true) {
+            std::pair<uint64_t, QString> pair = cursor.prev();
+            Shared::Message msg = messages->getRecord(pair.second, txn);
+            if (msg.serverStored()) {
+                cursor.close();
+                return msg;
+            }
+        }
     } catch (...) {
-        mdb_txn_abort(txn);
+        cursor.close();
         throw;
     }
 }
 
-bool Core::Archive::hasElement(const QString& id) const
-{
-    if (!opened) {
-        throw Closed("hasElement", jid.toStdString());
-    }
-    
-    MDB_txn *txn;
-    mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn);
-    
-    bool has;
-    MDB_val lmdbKey, lmdbData;
-    lmdbKey.mv_size = id.size();
-    lmdbKey.mv_data = (char*)id.toStdString().c_str();
-    int rc = mdb_get(txn, main, &lmdbKey, &lmdbData);
-    has = rc == 0;
-    mdb_txn_abort(txn);
-    
-    return has;
-}
-
-Shared::Message Core::Archive::getMessage(const std::string& id, MDB_txn* txn) const
-{
-    MDB_val lmdbKey, lmdbData;
-    lmdbKey.mv_size = id.size();
-    lmdbKey.mv_data = (char*)id.c_str();
-    int rc = mdb_get(txn, main, &lmdbKey, &lmdbData);
-    
-    if (rc == 0) {
-        QByteArray ba((char*)lmdbData.mv_data, lmdbData.mv_size);
-        QDataStream ds(&ba, QIODevice::ReadOnly);
-        
-        Shared::Message msg;
-        msg.deserialize(ds);
-        
-        return msg;
-    } else if (rc == MDB_NOTFOUND) {
-        throw NotFound(id, jid.toStdString());
-    } else {
-        throw Unknown(jid.toStdString(), mdb_strerror(rc));
-    }
-}
-
-void Core::Archive::changeMessage(const QString& id, const QMap<QString, QVariant>& data)
-{
-    if (!opened) {
-        throw Closed("setMessageState", jid.toStdString());
-    }
-    
-    MDB_txn *txn;
-    mdb_txn_begin(environment, NULL, 0, &txn);
-    
-    std::string strId(id.toStdString());
-    try {
-        Shared::Message msg = getMessage(strId, txn);
-        bool hadStanzaId = msg.getStanzaId().size() > 0;
-        QDateTime oTime = msg.getTime();
-        bool idChange = msg.change(data);
-        QDateTime nTime = msg.getTime();
-        bool orderChange = oTime != nTime;
-        
-        MDB_val lmdbKey, lmdbData;
-        QByteArray ba;
-        QDataStream ds(&ba, QIODevice::WriteOnly);
-        msg.serialize(ds);
-        
-        lmdbKey.mv_size = strId.size();
-        lmdbKey.mv_data = (char*)strId.c_str();
-        int rc;
-        if (idChange || orderChange) {
-            if (idChange) {
-                rc = mdb_del(txn, main, &lmdbKey, &lmdbData);
-            } else {
-                quint64 ostamp = oTime.toMSecsSinceEpoch();
-                lmdbData.mv_data = (quint8*)&ostamp;
-                lmdbData.mv_size = 8;
-                rc = mdb_del(txn, order, &lmdbData, &lmdbKey);
-            }
-            if (rc == 0) {
-                strId = msg.getId().toStdString();
-                lmdbKey.mv_size = strId.size();
-                lmdbKey.mv_data = (char*)strId.c_str();
-                
-                quint64 stamp = nTime.toMSecsSinceEpoch();
-                lmdbData.mv_data = (quint8*)&stamp;
-                lmdbData.mv_size = 8;
-                rc = mdb_put(txn, order, &lmdbData, &lmdbKey, 0);
-                if (rc != 0) {
-                    throw Unknown(jid.toStdString(), mdb_strerror(rc));
-                }
-            } else {
-                throw Unknown(jid.toStdString(), mdb_strerror(rc));
-            }
-        }
-        
-        QString qsid = msg.getStanzaId();
-        if (qsid.size() > 0 && (idChange || !hadStanzaId)) {
-            std::string szid = qsid.toStdString();
-            
-            lmdbData.mv_size = szid.size();
-            lmdbData.mv_data = (char*)szid.c_str();
-            rc = mdb_put(txn, sid, &lmdbData, &lmdbKey, 0);
-            
-            if (rc != 0) {
-                throw Unknown(jid.toStdString(), mdb_strerror(rc));
-            }
-        };
-        
-        lmdbData.mv_size = ba.size();
-        lmdbData.mv_data = (uint8_t*)ba.data();
-        rc = mdb_put(txn, main, &lmdbKey, &lmdbData, 0);
-        if (rc == 0) {
-            rc = mdb_txn_commit(txn);
-        } else {
-            throw Unknown(jid.toStdString(), mdb_strerror(rc));
-        }
-        
-    } catch (...) {
-        mdb_txn_abort(txn);
-        throw;
-    }
-}
-
-Shared::Message Core::Archive::newest()
-{
-    return edge(true);
-}
-
-QString Core::Archive::newestId()
-{
-    if (!opened) {
-        throw Closed("newestId", jid.toStdString());
-    }
+QString Core::Archive::newestId() const {
     Shared::Message msg = newest();
     return msg.getId();
 }
 
-QString Core::Archive::oldestId()
-{
-    if (!opened) {
-        throw Closed("oldestId", jid.toStdString());
-    }
+QString Core::Archive::oldestId() const {
     Shared::Message msg = oldest();
     return msg.getId();
 }
 
-Shared::Message Core::Archive::oldest() 
-{
-    return edge(false);
-}
+Shared::Message Core::Archive::oldest() const {
+    LMDBAL::Transaction txn = db.beginReadOnlyTransaction();
 
-Shared::Message Core::Archive::edge(bool end)
-{
-    QString name;
-    MDB_cursor_op begin;
-    MDB_cursor_op iteration;
-    if (end) {
-        name = "newest";
-        begin = MDB_LAST;
-        iteration = MDB_PREV;
-    } else {
-        name = "oldest";
-        begin = MDB_FIRST;
-        iteration = MDB_NEXT;
-    }
-    
-    
-    if (!opened) {
-        throw Closed(name.toStdString(), jid.toStdString());
-    }
-    
-    MDB_txn *txn;
-    MDB_cursor* cursor;
-    MDB_val lmdbKey, lmdbData;
-    int rc;
-    rc = mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn);
-    rc = mdb_cursor_open(txn, order, &cursor);
-    rc = mdb_cursor_get(cursor, &lmdbKey, &lmdbData, begin);
-    
-    Shared::Message msg = getStoredMessage(txn, cursor, iteration, &lmdbKey, &lmdbData, rc);
-    
-    mdb_cursor_close(cursor);
-    mdb_txn_abort(txn);
-    
-    if (rc) {
-        qDebug() << "Error geting" << name << "message" << mdb_strerror(rc);
-        throw Empty(jid.toStdString());
-    } else {
-        return msg;
-    }
-}
-
-Shared::Message Core::Archive::getStoredMessage(MDB_txn *txn, MDB_cursor* cursor, MDB_cursor_op op, MDB_val* key, MDB_val* value, int& rc)
-{
-    Shared::Message msg;
-    std::string sId;
-    while (true) {
-        if (rc) {
-            break;
-        }
-        sId = std::string((char*)value->mv_data, value->mv_size);
-        
-        try {
-            msg = getMessage(sId, txn);
+    try {
+        cursor.open(txn);
+        while (true) {
+            std::pair<uint64_t, QString> pair = cursor.next();
+            Shared::Message msg = messages->getRecord(pair.second, txn);
             if (msg.serverStored()) {
-                break;
-            } else {
-                rc = mdb_cursor_get(cursor, key, value, op);
+                cursor.close();
+                return msg;
             }
-        } catch (...) {
-            break;
         }
+    } catch (...) {
+        cursor.close();
+        throw;
     }
-    
-    return msg;
 }
 
-unsigned int Core::Archive::addElements(const std::list<Shared::Message>& messages)
-{
-    if (!opened) {
-        throw Closed("addElements", jid.toStdString());
-    }
-    
-    int success = 0;
-    int rc = 0;
-    MDB_val lmdbKey, lmdbData;
-    MDB_txn *txn;
-    mdb_txn_begin(environment, NULL, 0, &txn);
-    std::list<Shared::Message>::const_iterator itr = messages.begin();
-    while (rc == 0 && itr != messages.end()) {
-        const Shared::Message& message = *itr;
-        
-        QByteArray ba;
-        QDataStream ds(&ba, QIODevice::WriteOnly);
-        message.serialize(ds);
-        quint64 stamp = message.getTime().toMSecsSinceEpoch();
-        const std::string& id = message.getId().toStdString();
-        
-        lmdbKey.mv_size = id.size();
-        lmdbKey.mv_data = (char*)id.c_str();
-        lmdbData.mv_size = ba.size();
-        lmdbData.mv_data = (uint8_t*)ba.data();
-        
-        rc = mdb_put(txn, main, &lmdbKey, &lmdbData, MDB_NOOVERWRITE);
-        if (rc == 0) {
-            MDB_val orderKey;
-            orderKey.mv_size = 8;
-            orderKey.mv_data = (uint8_t*) &stamp;
-            
-            rc = mdb_put(txn, order, &orderKey, &lmdbKey, 0);
-            if (rc) {
-                qDebug() << "An element couldn't be inserted into the index, aborting the transaction" << mdb_strerror(rc);
-            } else {
-                if (message.getStanzaId().size() > 0) {
-                    const std::string& szid = message.getStanzaId().toStdString();
-                    
-                    lmdbKey.mv_size = szid.size();
-                    lmdbKey.mv_data = (char*)szid.c_str();
-                    lmdbData.mv_size = id.size();
-                    lmdbData.mv_data = (uint8_t*)id.data();
-                    rc = mdb_put(txn, sid, &lmdbKey, &lmdbData, MDB_NOOVERWRITE);
-                    
-                    if (rc) {
-                        qDebug() << "During bulk add an element stanzaId to id pair couldn't be inserted into the archive, continuing without stanzaId" << mdb_strerror(rc);
-                    }
-                    
-                }
-                success++;
-            }
-        } else {
-            if (rc == MDB_KEYEXIST) {
-                rc = 0;
-            } else {
-                qDebug() << "An element couldn't been added to the archive, aborting the transaction" << mdb_strerror(rc);
-            }
-        }
-        itr++;
-    }
-    
-    if (rc != 0) {
-        mdb_txn_abort(txn);
-        success = 0;
-    } else {
-        mdb_txn_commit(txn);
+unsigned int Core::Archive::addElements(const std::list<Shared::Message>& messages) {
+    unsigned int success = 0;
+    LMDBAL::WriteTransaction txn = db.beginTransaction();
+    for (const Shared::Message& message : messages) {
+        QString id = message.getId();
+        bool added = false;
+        try {
+            Core::Archive::messages->addRecord(id, message, txn);
+            added = true;
+        } catch (const LMDBAL::Exist& e) {}
+
+        if (!added)
+            continue;
+
+        order->addRecord(message.getTime().toMSecsSinceEpoch(), id);
+
+        QString sid = message.getStanzaId();
+        if (!sid.isEmpty())
+            stanzaIdToId->addRecord(sid, id);
+
+        ++success;
     }
     
     return success;
 }
 
-long unsigned int Core::Archive::size() const
-{
-    if (!opened) {
-        throw Closed("size", jid.toStdString());
-    }
-    MDB_txn *txn;
-    mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn);
-    MDB_stat stat;
-    mdb_stat(txn, order, &stat);
-    size_t amount = stat.ms_entries;
-    mdb_txn_abort(txn);
-    return amount;
+long unsigned int Core::Archive::size() const {
+    return order->count();
 }
 
-std::list<Shared::Message> Core::Archive::getBefore(int count, const QString& id)
-{
-    if (!opened) {
-        throw Closed("getBefore", jid.toStdString());
-    }
-    std::list<Shared::Message> res;
-    MDB_cursor* cursor;
-    MDB_txn *txn;
-    MDB_val lmdbKey, lmdbData;
-    int rc;
-    rc = mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn);
-    rc = mdb_cursor_open(txn, order, &cursor);
-    if (id == "") {
-        rc = mdb_cursor_get(cursor, &lmdbKey, &lmdbData, MDB_LAST);
-        if (rc) {
-            qDebug() << "Error getting before" << mdb_strerror(rc) << ", id:" << id;
-            mdb_cursor_close(cursor);
-            mdb_txn_abort(txn);
-            
-            throw Empty(jid.toStdString());
-        }
-    } else {
-        std::string stdId(id.toStdString());
-        try {
-            Shared::Message msg = getMessage(stdId, txn);
-            quint64 stamp = msg.getTime().toMSecsSinceEpoch();
-            lmdbKey.mv_data = (quint8*)&stamp;
-            lmdbKey.mv_size = 8;
-            
-            rc = mdb_cursor_get(cursor, &lmdbKey, &lmdbData, MDB_SET);
-            
-            if (rc) {
-                qDebug() << "Error getting before: couldn't set " << mdb_strerror(rc);
-                throw NotFound(stdId, jid.toStdString());
-            } else {
-                rc = mdb_cursor_get(cursor, &lmdbKey, &lmdbData, MDB_PREV);
-                if (rc) {
-                    qDebug() << "Error getting before, couldn't prev " << mdb_strerror(rc);
-                    throw NotFound(stdId, jid.toStdString());
-                }
-            }
-            
-        } catch (...) {
-            mdb_cursor_close(cursor);
-            mdb_txn_abort(txn);
-            throw;
-        }
-    }
-    
-    do {
-        MDB_val dKey, dData;
-        dKey.mv_size = lmdbData.mv_size;
-        dKey.mv_data = lmdbData.mv_data;
-        rc = mdb_get(txn, main, &dKey, &dData);
-        if (rc) {
-            qDebug() <<"Get error: " << mdb_strerror(rc);
-            std::string sId((char*)lmdbData.mv_data, lmdbData.mv_size);
-            mdb_cursor_close(cursor);
-            mdb_txn_abort(txn);
-            throw NotFound(sId, jid.toStdString());
+std::list<Shared::Message> Core::Archive::getBefore(unsigned int count, const QString& id) {
+    LMDBAL::Transaction txn = db.beginReadOnlyTransaction();
+    try {
+        cursor.open(txn);
+        if (id.isEmpty()) {
+            cursor.last();
         } else {
-            QByteArray ba((char*)dData.mv_data, dData.mv_size);
-            QDataStream ds(&ba, QIODevice::ReadOnly);
-            
+            Shared::Message reference = messages->getRecord(id, txn);
+            uint64_t stamp = reference.getTime().toMSecsSinceEpoch();
+            cursor.set(stamp);
+            cursor.prev();
+        }
+
+        std::list<Shared::Message> res;
+        for (unsigned int i = 0; i < count; ++i) {
+            std::pair<uint64_t, QString> pair;
+            if (i == 0)
+                cursor.current(pair.first, pair.second);
+            else
+                cursor.prev(pair.first, pair.second);
+
             res.emplace_back();
             Shared::Message& msg = res.back();
-            msg.deserialize(ds);
+            messages->getRecord(pair.second, msg, txn);
         }
-        
-        --count;
-        
-    } while (count > 0 && mdb_cursor_get(cursor, &lmdbKey, &lmdbData, MDB_PREV) == 0);
-    
-    mdb_cursor_close(cursor);
-    mdb_txn_abort(txn);
-    return res;
-}
+        cursor.close();
 
-bool Core::Archive::isFromTheBeginning() const
-{
-    if (!opened) {
-        throw Closed("isFromTheBeginning", jid.toStdString());
-    }
-    return fromTheBeginning;
-}
-
-void Core::Archive::setFromTheBeginning(bool is)
-{
-    if (!opened) {
-        throw Closed("setFromTheBeginning", jid.toStdString());
-    }
-    if (fromTheBeginning != is) {
-        fromTheBeginning = is;
-        
-        MDB_txn *txn;
-        mdb_txn_begin(environment, NULL, 0, &txn);
-        bool success = setStatValue("beginning", is, txn);
-        if (success) {
-            mdb_txn_commit(txn);
-        } else {
-            mdb_txn_abort(txn);
-        }
-    }
-}
-
-bool Core::Archive::isEncryptionEnabled() const
-{
-    if (!opened) {
-        throw Closed("isEncryptionEnabled", jid.toStdString());
-    }
-    return encryptionEnabled;
-}
-
-bool Core::Archive::setEncryptionEnabled(bool is)
-{
-    if (!opened) {
-        throw Closed("setEncryptionEnabled", jid.toStdString());
-    }
-    if (encryptionEnabled != is) {
-        encryptionEnabled = is;
-
-        MDB_txn *txn;
-        mdb_txn_begin(environment, NULL, 0, &txn);
-        bool success = setStatValue("encryptionEnabled", is, txn);
-        if (success) {
-            mdb_txn_commit(txn);
-            return true;
-        } else {
-            mdb_txn_abort(txn);
-        }
-    }
-    return false;
-}
-
-QString Core::Archive::idByStanzaId(const QString& stanzaId) const
-{
-    if (!opened) {
-        throw Closed("idByStanzaId", jid.toStdString());
-    }
-    QString id;
-    std::string ssid = stanzaId.toStdString();
-    
-    MDB_txn *txn;
-    MDB_val lmdbKey, lmdbData;
-    lmdbKey.mv_size = ssid.size();
-    lmdbKey.mv_data = (char*)ssid.c_str();
-    mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn);
-    int rc = mdb_get(txn, sid, &lmdbKey, &lmdbData);
-    if (rc == 0) {
-        id = QString::fromStdString(std::string((char*)lmdbData.mv_data, lmdbData.mv_size));
-    }
-    mdb_txn_abort(txn);
-    
-    return id;
-}
-
-QString Core::Archive::stanzaIdById(const QString& id) const
-{
-    if (!opened) {
-        throw Closed("stanzaIdById", jid.toStdString());
-    }
-    
-    try {
-        Shared::Message msg = getElement(id);
-        return msg.getStanzaId();
-    } catch (const NotFound& e) {
-        return QString();
-    } catch (const Empty& e) {
-        return QString();
+        return res;
     } catch (...) {
+        cursor.close();
         throw;
     }
 }
 
-void Core::Archive::printOrder()
-{
-    qDebug() << "Printing order";
-    MDB_txn *txn;
-    mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn);
-    MDB_cursor* cursor;
-    mdb_cursor_open(txn, order, &cursor);
-    MDB_val lmdbKey, lmdbData;
-    
-    mdb_cursor_get(cursor, &lmdbKey, &lmdbData, MDB_FIRST);
-    
-    do {
-        std::string sId((char*)lmdbData.mv_data, lmdbData.mv_size);
-        qDebug() << QString(sId.c_str());
-    } while (mdb_cursor_get(cursor, &lmdbKey, &lmdbData, MDB_NEXT) == 0);
-    
-    mdb_cursor_close(cursor);
-    mdb_txn_abort(txn);
-}
-
-void Core::Archive::printKeys()
-{
-    MDB_txn *txn;
-    mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn);
-    MDB_cursor* cursor;
-    mdb_cursor_open(txn, main, &cursor);
-    MDB_val lmdbKey, lmdbData;
-    
-    mdb_cursor_get(cursor, &lmdbKey, &lmdbData, MDB_FIRST);
-    
-    do {
-        std::string sId((char*)lmdbKey.mv_data, lmdbKey.mv_size);
-        qDebug() << QString(sId.c_str());
-    } while (mdb_cursor_get(cursor, &lmdbKey, &lmdbData, MDB_NEXT) == 0);
-    
-    mdb_cursor_close(cursor);
-    mdb_txn_abort(txn);
-}
-
-bool Core::Archive::getStatBoolValue(const std::string& id, MDB_txn* txn)
-{
-    MDB_val lmdbKey, lmdbData;
-    lmdbKey.mv_size = id.size();
-    lmdbKey.mv_data = (char*)id.c_str();
-    
-    int rc;
-    rc = mdb_get(txn, stats, &lmdbKey, &lmdbData);
-    if (rc == MDB_NOTFOUND) {
-        throw NotFound(id, jid.toStdString());
-    } else if (rc) {
-        std::string err(mdb_strerror(rc));
-        qDebug() << "error retrieving" << id.c_str() << "from stats db of" << jid << err.c_str();
-        throw Unknown(jid.toStdString(), err);
-    } else {
-        uint8_t value = *(uint8_t*)(lmdbData.mv_data);
-        bool is;
-        if (value == 144) {
-            is = false;
-        } else if (value == 72) {
-            is = true;
-        } else {
-            qDebug() << "error retrieving boolean stat" << id.c_str() << ": stored value doesn't match any magic number, the answer is most probably wrong";
-            throw NotFound(id, jid.toStdString());
-        }
-        return is;
-    }
-}
-
-std::string Core::Archive::getStatStringValue(const std::string& id, MDB_txn* txn)
-{
-    MDB_val lmdbKey, lmdbData;
-    lmdbKey.mv_size = id.size();
-    lmdbKey.mv_data = (char*)id.c_str();
-    
-    int rc;
-    rc = mdb_get(txn, stats, &lmdbKey, &lmdbData);
-    if (rc == MDB_NOTFOUND) {
-        throw NotFound(id, jid.toStdString());
-    } else if (rc) {
-        std::string err(mdb_strerror(rc));
-        qDebug() << "error retrieving" << id.c_str() << "from stats db of" << jid << err.c_str();
-        throw Unknown(jid.toStdString(), err);
-    } else {
-        std::string value((char*)lmdbData.mv_data, lmdbData.mv_size);
-        return value;
-    }
-}
-
-bool Core::Archive::setStatValue(const std::string& id, bool value, MDB_txn* txn)
-{
-    uint8_t binvalue = 144;
-    if (value) {
-        binvalue = 72;
-    }
-    MDB_val lmdbKey, lmdbData;
-    lmdbKey.mv_size = id.size();
-    lmdbKey.mv_data = (char*)id.c_str();
-    lmdbData.mv_size = sizeof binvalue;
-    lmdbData.mv_data = &binvalue;
-    int rc = mdb_put(txn, stats, &lmdbKey, &lmdbData, 0);
-    if (rc != 0) {
-        qDebug() << "Couldn't store" << id.c_str() << "key into stat database:" << mdb_strerror(rc);
+bool Core::Archive::isFromTheBeginning() const {
+    try {
+        return stats->getRecord("fromTheBeginning").toBool();
+    } catch (const LMDBAL::NotFound& e) {
         return false;
     }
-    return true;
 }
 
-bool Core::Archive::setStatValue(const std::string& id, const std::string& value, MDB_txn* txn)
-{
-    MDB_val lmdbKey, lmdbData;
-    lmdbKey.mv_size = id.size();
-    lmdbKey.mv_data = (char*)id.c_str();
-    lmdbData.mv_size = value.size();
-    lmdbData.mv_data = (char*)value.c_str();
-    int rc = mdb_put(txn, stats, &lmdbKey, &lmdbData, 0);
-    if (rc != 0) {
-        qDebug() << "Couldn't store" << id.c_str() << "key into stat database:" << mdb_strerror(rc);
+void Core::Archive::setFromTheBeginning(bool is) {
+    stats->forceRecord("fromTheBeginning", is);
+}
+
+bool Core::Archive::isEncryptionEnabled() const {
+    try {
+        return stats->getRecord("isEncryptionEnabled").toBool();
+    } catch (const LMDBAL::NotFound& e) {
         return false;
     }
-    return true;
 }
 
-bool Core::Archive::dropAvatar(const std::string& resource)
-{
-    MDB_txn *txn;
-    MDB_val lmdbKey;
-    mdb_txn_begin(environment, NULL, 0, &txn);
-    lmdbKey.mv_size = resource.size();
-    lmdbKey.mv_data = (char*)resource.c_str();
-    int rc = mdb_del(txn, avatars, &lmdbKey, NULL);
-    if (rc != 0) {
-        mdb_txn_abort(txn);
-        return false;
-    } else {
-        mdb_txn_commit(txn);
+bool Core::Archive::setEncryptionEnabled(bool is) {
+    LMDBAL::WriteTransaction txn = db.beginTransaction();
+    bool current = false;
+    try {
+        current = stats->getRecord("isEncryptionEnabled").toBool();
+    } catch (const LMDBAL::NotFound& e) {}
+
+    if (is != current) {
+        stats->forceRecord("isEncryptionEnabled", is, txn);
+        txn.commit();
         return true;
     }
+
+    return false;
 }
 
-bool Core::Archive::setAvatar(const QByteArray& data, AvatarInfo& newInfo, bool generated, const QString& resource)
-{
-    if (!opened) {
-        throw Closed("setAvatar", jid.toStdString());
+QString Core::Archive::idByStanzaId(const QString& stanzaId) const {
+    return stanzaIdToId->getRecord(stanzaId);
+}
+
+QString Core::Archive::stanzaIdById(const QString& id) const {
+    try {
+        Shared::Message msg = getElement(id);
+        return msg.getStanzaId();
+    } catch (const LMDBAL::NotFound& e) {
+        return QString();
     }
-    
+}
+
+bool Core::Archive::setAvatar(const QByteArray& data, AvatarInfo& newInfo, bool generated, const QString& resource) {
+    LMDBAL::WriteTransaction txn = db.beginTransaction();
     AvatarInfo oldInfo;
-    bool hasAvatar = readAvatarInfo(oldInfo, resource);
-    std::string res = resource.size() == 0 ? jid.toStdString() : resource.toStdString();
+    bool haveAvatar = false;
+    QString res = resource.isEmpty() ? jid : resource;
+    try {
+        avatars->getRecord(res, oldInfo, txn);
+        haveAvatar = true;
+    } catch (const LMDBAL::NotFound& e) {}
     
     if (data.size() == 0) {
-        if (!hasAvatar) {
+        if (!haveAvatar)
             return false;
-        } else {
-            return dropAvatar(res);
-        }
+
+        avatars->removeRecord(res, txn);
+        txn.commit();
+        return true;
     } else {
-        const char* cep;
-        mdb_env_get_path(environment, &cep);
-        QString currentPath(cep);
+        QString currentPath = db.getPath();
         bool needToRemoveOld = false;
         QCryptographicHash hash(QCryptographicHash::Sha1);
         hash.addData(data);
         QByteArray newHash(hash.result());
-        if (hasAvatar) {
-            if (!generated && !oldInfo.autogenerated && oldInfo.hash == newHash) {
+        if (haveAvatar) {
+            if (!generated && !oldInfo.autogenerated && oldInfo.hash == newHash)
                 return false;
-            }
-            QFile oldAvatar(currentPath + "/" + res.c_str() + "." + oldInfo.type);
+
+            QFile oldAvatar(currentPath + "/" + res + "." + oldInfo.type);
             if (oldAvatar.exists()) {
-                if (oldAvatar.rename(currentPath + "/" + res.c_str() + "." + oldInfo.type + ".bak")) {
+                if (oldAvatar.rename(currentPath + "/" + res + "." + oldInfo.type + ".bak")) {
                     needToRemoveOld = true;
                 } else {
                     qDebug() << "Can't change avatar: couldn't get rid of the old avatar" << oldAvatar.fileName();
@@ -883,161 +335,86 @@ bool Core::Archive::setAvatar(const QByteArray& data, AvatarInfo& newInfo, bool
                 }
             }
         }
-        QMimeDatabase db;
-        QMimeType type = db.mimeTypeForData(data);
+        QMimeDatabase mimedb;
+        QMimeType type = mimedb.mimeTypeForData(data);
         QString ext = type.preferredSuffix();
-        QFile newAvatar(currentPath + "/" + res.c_str() + "." + ext);
+        QFile newAvatar(currentPath + "/" + res + "." + ext);
         if (newAvatar.open(QFile::WriteOnly)) {
             newAvatar.write(data);
             newAvatar.close();
             
-            MDB_txn *txn;
-            mdb_txn_begin(environment, NULL, 0, &txn);
-            
-            MDB_val lmdbKey, lmdbData;
-            QByteArray value;
             newInfo.type = ext;
             newInfo.hash = newHash;
             newInfo.autogenerated = generated;
-            newInfo.serialize(&value);
-            lmdbKey.mv_size = res.size();
-            lmdbKey.mv_data = (char*)res.c_str();
-            lmdbData.mv_size = value.size();
-            lmdbData.mv_data = value.data();
-            int rc = mdb_put(txn, avatars, &lmdbKey, &lmdbData, 0);
-            
-            if (rc != 0) {
+            try {
+                avatars->forceRecord(res, newInfo, txn);
+                txn.commit();
+            } catch (...) {
                 qDebug() << "Can't change avatar: couldn't store changes to database for" << newAvatar.fileName() << "rolling back to the previous state";
                 if (needToRemoveOld) {
-                    QFile oldAvatar(currentPath + "/" + res.c_str() + "." + oldInfo.type + ".bak");
-                    oldAvatar.rename(currentPath + "/" + res.c_str() + "." + oldInfo.type);
+                    QFile oldAvatar(currentPath + "/" + res + "." + oldInfo.type + ".bak");
+                    oldAvatar.rename(currentPath + "/" + res + "." + oldInfo.type);
                 }
-                mdb_txn_abort(txn);
                 return false;
-            } else {
-                mdb_txn_commit(txn);
-                if (needToRemoveOld) {
-                    QFile oldAvatar(currentPath + "/" + res.c_str() + "." + oldInfo.type + ".bak");
-                    oldAvatar.remove();
-                }
-                return true;
             }
+            
+            if (needToRemoveOld) {
+                QFile oldAvatar(currentPath + "/" + res + "." + oldInfo.type + ".bak");
+                oldAvatar.remove();
+            }
+            return true;
         } else {
             qDebug() << "Can't change avatar: cant open file to write" << newAvatar.fileName() << "rolling back to the previous state";
             if (needToRemoveOld) {
-                QFile oldAvatar(currentPath + "/" + res.c_str() + "." + oldInfo.type + ".bak");
-                oldAvatar.rename(currentPath + "/" + res.c_str() + "." + oldInfo.type);
+                QFile oldAvatar(currentPath + "/" + res + "." + oldInfo.type + ".bak");
+                oldAvatar.rename(currentPath + "/" + res + "." + oldInfo.type);
             }
             return false;
         }
     }
 }
 
-bool Core::Archive::readAvatarInfo(Core::Archive::AvatarInfo& target, const QString& resource) const
-{
-    if (!opened) {
-        throw Closed("readAvatarInfo", jid.toStdString());
-    }
-    std::string res = resource.size() == 0 ? jid.toStdString() : resource.toStdString();
-    
-    MDB_txn *txn;
-    mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn);
-    
+bool Core::Archive::readAvatarInfo(Core::Archive::AvatarInfo& target, const QString& resource) const {
     try {
-        bool success = readAvatarInfo(target, res, txn);
-        mdb_txn_abort(txn);
-        return success;
-    } catch (...) {
-        mdb_txn_abort(txn);
-        throw;
-    }
-    
-}
-
-bool Core::Archive::readAvatarInfo(Core::Archive::AvatarInfo& target, const std::string& res, MDB_txn* txn) const
-{
-    MDB_val lmdbKey, lmdbData;
-    lmdbKey.mv_size = res.size();
-    lmdbKey.mv_data = (char*)res.c_str();
-    
-    int rc;
-    rc = mdb_get(txn, avatars, &lmdbKey, &lmdbData);
-    if (rc == MDB_NOTFOUND) {
-        return false;
-    } else if (rc) {
-        std::string err(mdb_strerror(rc));
-        qDebug() << "error reading avatar info for" << res.c_str() << "resource of" << jid << err.c_str();
-        throw Unknown(jid.toStdString(), err);
-    } else {
-        target.deserialize((char*)lmdbData.mv_data, lmdbData.mv_size);
+        avatars->getRecord(resource.isEmpty() ? jid : resource, target);
         return true;
+    } catch (const LMDBAL::NotFound& e) {
+        return false;
     }
 }
 
-void Core::Archive::readAllResourcesAvatars(std::map<QString, AvatarInfo>& data) const
-{
-    if (!opened) {
-        throw Closed("readAllResourcesAvatars", jid.toStdString());
-    }
-    
-    int rc;
-    MDB_val lmdbKey, lmdbData;
-    MDB_txn *txn;
-    MDB_cursor* cursor;
-    mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn);
-    mdb_cursor_open(txn, avatars, &cursor);
-    rc = mdb_cursor_get(cursor, &lmdbKey, &lmdbData, MDB_FIRST);
-    if (rc == 0) {                                                  //the db might be empty yet
-        do {
-            std::string sId((char*)lmdbKey.mv_data, lmdbKey.mv_size);
-            QString res(sId.c_str());
-            if (res != jid) {
-                data.emplace(res, AvatarInfo());
-                data[res].deserialize((char*)lmdbData.mv_data, lmdbData.mv_size);
-            }
-        } while (mdb_cursor_get(cursor, &lmdbKey, &lmdbData, MDB_NEXT) == 0);
-    }
-    
-    mdb_cursor_close(cursor);
-    mdb_txn_abort(txn);
+void Core::Archive::readAllResourcesAvatars(std::map<QString, AvatarInfo>& data) const {
+    avatars->readAll(data);
 }
 
-Core::Archive::AvatarInfo Core::Archive::getAvatarInfo(const QString& resource) const
-{
-    if (!opened) {
-        throw Closed("readAvatarInfo", jid.toStdString());
-    }
-    
-    AvatarInfo info;
-    bool success = readAvatarInfo(info, resource);
-    if (success) {
-        return info;
-    } else {
-        throw NoAvatar(jid.toStdString(), resource.toStdString());
-    }
+Core::Archive::AvatarInfo Core::Archive::getAvatarInfo(const QString& resource) const {
+    return avatars->getRecord(resource);
 }
 
 Core::Archive::AvatarInfo::AvatarInfo():
-type(),
-hash(),
-autogenerated(false) {}
+    type(),
+    hash(),
+    autogenerated(false)
+{}
 
 Core::Archive::AvatarInfo::AvatarInfo(const QString& p_type, const QByteArray& p_hash, bool p_autogenerated):
-type(p_type),
-hash(p_hash),
-autogenerated(p_autogenerated) {}
+    type(p_type),
+    hash(p_hash),
+    autogenerated(p_autogenerated)
+{}
 
-void Core::Archive::AvatarInfo::deserialize(char* pointer, uint32_t size)
-{
-    QByteArray data = QByteArray::fromRawData(pointer, size);
-    QDataStream in(&data, QIODevice::ReadOnly);
-    
-    in >> type >> hash >> autogenerated;
+QDataStream & operator<<(QDataStream& out, const Core::Archive::AvatarInfo& info) {
+    out << info.type;
+    out << info.hash;
+    out << info.autogenerated;
+
+    return out;
 }
 
-void Core::Archive::AvatarInfo::serialize(QByteArray* ba) const
-{
-    QDataStream out(ba, QIODevice::WriteOnly);
-    
-    out << type << hash << autogenerated;
+QDataStream & operator>>(QDataStream& in, Core::Archive::AvatarInfo& info) {
+    in >> info.type;
+    in >> info.hash;
+    in >> info.autogenerated;
+
+    return in;
 }
diff --git a/core/storage/archive.h b/core/storage/archive.h
index ef10555..6f3c9eb 100644
--- a/core/storage/archive.h
+++ b/core/storage/archive.h
@@ -29,18 +29,21 @@
 #include <lmdb.h>
 #include <list>
 
+#include <lmdbal/base.h>
+#include <lmdbal/storage.h>
+#include <lmdbal/cursor.h>
+
 namespace Core {
 
-class Archive : public QObject
-{
+class Archive : public QObject {
     Q_OBJECT
 public:
     class AvatarInfo;
     
-    Archive(const QString& jid, QObject* parent = 0);
+    Archive(const QString& account, const QString& jid, QObject* parent = 0);
     ~Archive();
     
-    void open(const QString& account);
+    void open();
     void close();
     
     bool addElement(const Shared::Message& message);
@@ -48,13 +51,13 @@ public:
     Shared::Message getElement(const QString& id) const;
     bool hasElement(const QString& id) const;
     void changeMessage(const QString& id, const QMap<QString, QVariant>& data);
-    Shared::Message oldest();
-    QString oldestId();
-    Shared::Message newest();
-    QString newestId();
+    Shared::Message oldest() const;
+    QString oldestId() const;
+    Shared::Message newest() const;
+    QString newestId() const;
     void clear();
     long unsigned int size() const;
-    std::list<Shared::Message> getBefore(int count, const QString& id);
+    std::list<Shared::Message> getBefore(unsigned int count, const QString& id);
     bool isFromTheBeginning() const;
     void setFromTheBeginning(bool is);
     bool isEncryptionEnabled() const;
@@ -68,103 +71,14 @@ public:
     
 public:
     const QString jid;
+    const QString account;
     
 public:
-    class Directory: 
-    public Utils::Exception
-    {
-    public:
-        Directory(const std::string& p_path):Exception(), path(p_path){}
-        
-        std::string getMessage() const{return "Can't create directory for database at " + path;}
-    private:
-        std::string path;
-    };
-    
-    class Closed: 
-    public Utils::Exception
-    {
-    public:
-        Closed(const std::string& op, const std::string& acc):Exception(), operation(op), account(acc){}
-        
-        std::string getMessage() const{return "An attempt to perform operation " + operation + " on closed archive for " + account;}
-    private:
-        std::string operation;
-        std::string account;
-    };
-    
-    class NotFound: 
-    public Utils::Exception
-    {
-    public:
-        NotFound(const std::string& k, const std::string& acc):Exception(), key(k), account(acc){}
-        
-        std::string getMessage() const{return "Element for id " + key + " wasn't found in database " + account;}
-    private:
-        std::string key;
-        std::string account;
-    };
-    
-    class Empty: 
-    public Utils::Exception
-    {
-    public:
-        Empty(const std::string& acc):Exception(), account(acc){}
-        
-        std::string getMessage() const{return "An attempt to read ordered elements from database " + account + " but it's empty";}
-    private:
-        std::string account;
-    };
-    
-    class Exist: 
-    public Utils::Exception
-    {
-    public:
-        Exist(const std::string& acc, const std::string& p_key):Exception(), account(acc), key(p_key){}
-        
-        std::string getMessage() const{return "An attempt to insert element " + key + " to database " + account + " but it already has an element with given id";}
-    private:
-        std::string account;
-        std::string key;
-    };
-    
-    class NoAvatar: 
-    public Utils::Exception
-    {
-    public:
-        NoAvatar(const std::string& el, const std::string& res):Exception(), element(el), resource(res){
-            if (resource.size() == 0) {
-                resource = "for himself";
-            }
-        }
-        
-        std::string getMessage() const{return "Element " + element + " has no avatar for " + resource ;}
-    private:
-        std::string element;
-        std::string resource;
-    };
-    
-    class Unknown:
-    public Utils::Exception
-    {
-    public:
-        Unknown(const std::string& acc, const std::string& message):Exception(), account(acc), msg(message){}
-        
-        std::string getMessage() const{return "Unknown error on database " + account + ": " + msg;}
-    private:
-        std::string account;
-        std::string msg;
-    };
-    
-    
     class AvatarInfo {
     public:
         AvatarInfo();
         AvatarInfo(const QString& type, const QByteArray& hash, bool autogenerated);
         
-        void deserialize(char* pointer, uint32_t size);
-        void serialize(QByteArray* ba) const;
-        
         QString type;
         QByteArray hash;
         bool autogenerated;
@@ -172,29 +86,18 @@ public:
     
 private:
     bool opened;
-    bool fromTheBeginning;
-    bool encryptionEnabled;
-    MDB_env* environment;
-    MDB_dbi main;           //id to message
-    MDB_dbi order;          //time to id
-    MDB_dbi stats;
-    MDB_dbi avatars;        
-    MDB_dbi sid;            //stanzaId to id
-    
-    bool getStatBoolValue(const std::string& id, MDB_txn* txn);
-    std::string getStatStringValue(const std::string& id, MDB_txn* txn);
-    
-    bool setStatValue(const std::string& id, bool value, MDB_txn* txn);
-    bool setStatValue(const std::string& id, const std::string& value, MDB_txn* txn);
-    bool readAvatarInfo(AvatarInfo& target, const std::string& res, MDB_txn* txn) const;
-    void printOrder();
-    void printKeys();
-    bool dropAvatar(const std::string& resource);
-    Shared::Message getMessage(const std::string& id, MDB_txn* txn) const;
-    Shared::Message getStoredMessage(MDB_txn *txn, MDB_cursor* cursor, MDB_cursor_op op, MDB_val* key, MDB_val* value, int& rc);
-    Shared::Message edge(bool end);
+    LMDBAL::Base db;
+    LMDBAL::Storage<QString, Shared::Message>* messages;
+    LMDBAL::Storage<uint64_t, QString>* order;
+    LMDBAL::Storage<QString, QVariant>* stats;
+    LMDBAL::Storage<QString, AvatarInfo>* avatars;
+    LMDBAL::Storage<QString, QString>* stanzaIdToId;
+    mutable LMDBAL::Cursor<uint64_t, QString> cursor;
 };
 
 }
 
+QDataStream& operator << (QDataStream &out, const Core::Archive::AvatarInfo& info);
+QDataStream& operator >> (QDataStream &in, Core::Archive::AvatarInfo& info);
+
 #endif // CORE_ARCHIVE_H
diff --git a/core/storage/cache.h b/core/storage/cache.h
deleted file mode 100644
index 23f72f0..0000000
--- a/core/storage/cache.h
+++ /dev/null
@@ -1,55 +0,0 @@
-// Squawk messenger. 
-// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
-// 
-// 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/>.
-
-#ifndef CORE_CACHE_H
-#define CORE_CACHE_H
-
-#include <map>
-#include <set>
-
-#include <QString>
-
-#include <core/storage/storage.h>
-
-namespace Core {
-
-template <class K, class V>
-class Cache
-{
-public:
-    Cache(const QString& name);
-    ~Cache();
-
-    void open();
-    void close();
-
-    void addRecord(const K& key, const V& value);
-    void changeRecord(const K& key, const V& value);
-    void removeRecord(const K& key);
-    V getRecord(const K& key) const;
-    bool checkRecord(const K& key) const;
-
-private:
-    Core::Storage<K, V> storage;
-    std::map<K, V>* cache;
-    std::set<K>* abscent;
-};
-
-}
-
-#include "cache.hpp"
-
-#endif // CORE_CACHE_H
diff --git a/core/storage/cache.hpp b/core/storage/cache.hpp
deleted file mode 100644
index 9491de2..0000000
--- a/core/storage/cache.hpp
+++ /dev/null
@@ -1,102 +0,0 @@
-// Squawk messenger. 
-// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
-// 
-// 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/>.
-
-#ifndef CORE_CACHE_HPP
-#define CORE_CACHE_HPP
-#include "cache.h"
-
-template <class K, class V>
-Core::Cache<K, V>::Cache(const QString& name):
-    storage(name),
-    cache(new std::map<K, V> ()),
-    abscent(new std::set<K> ()) {}
-
-template <class K, class V>
-Core::Cache<K, V>::~Cache() {
-    close();
-    delete cache;
-    delete abscent;
-}
-
-template <class K, class V>
-void Core::Cache<K, V>::open() {
-    storage.open();}
-
-template <class K, class V>
-void Core::Cache<K, V>::close() {
-    storage.close();}
-
-template <class K, class V>
-void Core::Cache<K, V>::addRecord(const K& key, const V& value) {
-    storage.addRecord(key, value);
-    cache->insert(std::make_pair(key, value));
-    abscent->erase(key);
-}
-
-template <class K, class V>
-V Core::Cache<K, V>::getRecord(const K& key) const {
-    typename std::map<K, V>::const_iterator itr = cache->find(key);
-    if (itr == cache->end()) {
-        if (abscent->count(key) > 0) {
-            throw Archive::NotFound(std::to_string(key), storage.getName().toStdString());
-        }
-
-        try {
-            V value = storage.getRecord(key);
-            itr = cache->insert(std::make_pair(key, value)).first;
-        } catch (const Archive::NotFound& error) {
-            abscent->insert(key);
-            throw error;
-        }
-    }
-
-    return itr->second;
-}
-
-template<class K, class V>
-bool Core::Cache<K, V>::checkRecord(const K& key) const {
-    typename std::map<K, V>::const_iterator itr = cache->find(key);
-    if (itr != cache->end())
-        return true;
-
-    if (abscent->count(key) > 0)
-        return false;
-
-    try {
-        V value = storage.getRecord(key);
-        itr = cache->insert(std::make_pair(key, value)).first;
-    } catch (const Archive::NotFound& error) {
-        return false;
-    }
-
-    return true;
-}
-
-template<class K, class V>
-void Core::Cache<K, V>::changeRecord(const K& key, const V& value) {
-    storage.changeRecord(key, value);   //there is a non straightforward behaviour: if there was no element at the sorage it will be added
-    cache->at(key) = value;
-    abscent->erase(key);                //so... this line here is to make it coherent with the storage
-}
-
-template<class K, class V>
-void Core::Cache<K, V>::removeRecord(const K& key) {
-    storage.removeRecord(key);
-    cache->erase(key);
-    abscent->insert(key);
-}
-
-#endif //CORE_CACHE_HPP
diff --git a/core/storage/storage.h b/core/storage/storage.h
deleted file mode 100644
index e43ae1a..0000000
--- a/core/storage/storage.h
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Squawk messenger. 
- * Copyright (C) 2019 Yury Gubich <blue@macaw.me>
- * 
- * 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/>.
- */
-
-#ifndef CORE_STORAGE_H
-#define CORE_STORAGE_H
-
-#include <QString>
-#include <lmdb.h>
-
-#include "archive.h"
-
-namespace Core {
-
-/**
- * @todo write docs
- */
-template <class K, class V>
-class Storage
-{
-public:
-    Storage(const QString& name);
-    ~Storage();
-    
-    void open();
-    void close();
-    
-    void addRecord(const K& key, const V& value);
-    void changeRecord(const K& key, const V& value);
-    void removeRecord(const K& key);
-    V getRecord(const K& key) const;
-    QString getName() const;
-    
-    
-private:
-    QString name;
-    bool opened;
-    MDB_env* environment;
-    MDB_dbi base;
-};
-
-}
-
-MDB_val& operator << (MDB_val& data, QString& value);
-MDB_val& operator >> (MDB_val& data, QString& value);
-
-MDB_val& operator << (MDB_val& data, uint32_t& value);
-MDB_val& operator >> (MDB_val& data, uint32_t& value);
-
-namespace std {
-    std::string to_string(const QString& str);
-}
-
-#include "storage.hpp"
-
-#endif // CORE_STORAGE_H
diff --git a/core/storage/storage.hpp b/core/storage/storage.hpp
deleted file mode 100644
index 33b8b56..0000000
--- a/core/storage/storage.hpp
+++ /dev/null
@@ -1,226 +0,0 @@
-/*
- * Squawk messenger. 
- * Copyright (C) 2019 Yury Gubich <blue@macaw.me>
- * 
- * 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/>.
- */
-#ifndef CORE_STORAGE_HPP
-#define CORE_STORAGE_HPP
-
-#include <QStandardPaths>
-#include <QDir>
-
-#include "storage.h"
-#include <cstring>
-
-template <class K, class V>
-Core::Storage<K, V>::Storage(const QString& p_name):
-    name(p_name),
-    opened(false),
-    environment(),
-    base()
-{
-}
-
-template <class K, class V>
-Core::Storage<K, V>::~Storage()
-{
-    close();
-}
-
-template <class K, class V>
-void Core::Storage<K, V>::open()
-{
-    if (!opened) {
-        mdb_env_create(&environment);
-        QString path(QStandardPaths::writableLocation(QStandardPaths::CacheLocation));
-        path += "/" + name;
-        QDir cache(path);
-        
-        if (!cache.exists()) {
-            bool res = cache.mkpath(path);
-            if (!res) {
-                throw Archive::Directory(path.toStdString());
-            }
-        }
-        
-        mdb_env_set_maxdbs(environment, 1);
-        mdb_env_set_mapsize(environment, 10UL * 1024UL * 1024UL);
-        mdb_env_open(environment, path.toStdString().c_str(), 0, 0664);
-        
-        MDB_txn *txn;
-        mdb_txn_begin(environment, NULL, 0, &txn);
-        mdb_dbi_open(txn, "base", MDB_CREATE, &base);
-        mdb_txn_commit(txn);
-        opened = true;
-    }
-}
-
-template <class K, class V>
-void Core::Storage<K, V>::close()
-{
-    if (opened) {
-        mdb_dbi_close(environment, base);
-        mdb_env_close(environment);
-        opened = false;
-    }
-}
-
-template <class K, class V>
-void Core::Storage<K, V>::addRecord(const K& key, const V& value)
-{
-    if (!opened) {
-        throw Archive::Closed("addRecord", name.toStdString());
-    }
-    QByteArray ba;
-    QDataStream ds(&ba, QIODevice::WriteOnly);
-    ds << value;
-    
-    MDB_val lmdbKey, lmdbData;
-    lmdbKey << key;
-
-    lmdbData.mv_size = ba.size();
-    lmdbData.mv_data = (uint8_t*)ba.data();
-    MDB_txn *txn;
-    mdb_txn_begin(environment, NULL, 0, &txn);
-    int rc;
-    rc = mdb_put(txn, base, &lmdbKey, &lmdbData, MDB_NOOVERWRITE);
-    if (rc != 0) {
-        mdb_txn_abort(txn);
-        if (rc == MDB_KEYEXIST) {
-            throw Archive::Exist(name.toStdString(), std::to_string(key));
-        } else {
-            throw Archive::Unknown(name.toStdString(), mdb_strerror(rc));
-        }
-    } else {
-        mdb_txn_commit(txn);
-    }
-}
-
-template <class K, class V>
-void Core::Storage<K, V>::changeRecord(const K& key, const V& value)
-{
-    if (!opened) {
-        throw Archive::Closed("changeRecord", name.toStdString());
-    }
-
-    QByteArray ba;
-    QDataStream ds(&ba, QIODevice::WriteOnly);
-    ds << value;
-    
-    MDB_val lmdbKey, lmdbData;
-    lmdbKey << key;
-    lmdbData.mv_size = ba.size();
-    lmdbData.mv_data = (uint8_t*)ba.data();
-    MDB_txn *txn;
-    mdb_txn_begin(environment, NULL, 0, &txn);
-    int rc;
-    rc = mdb_put(txn, base, &lmdbKey, &lmdbData, 0);
-    if (rc != 0) {
-        mdb_txn_abort(txn);
-        if (rc) {
-            throw Archive::Unknown(name.toStdString(), mdb_strerror(rc));
-        }
-    } else {
-        mdb_txn_commit(txn);
-    }
-}
-
-template <class K, class V>
-V Core::Storage<K, V>::getRecord(const K& key) const
-{
-    if (!opened) {
-        throw Archive::Closed("addElement", name.toStdString());
-    }
-    
-    MDB_val lmdbKey, lmdbData;
-    lmdbKey << key;
-    
-    MDB_txn *txn;
-    int rc;
-    mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn);
-    rc = mdb_get(txn, base, &lmdbKey, &lmdbData);
-    if (rc) {
-        mdb_txn_abort(txn);
-        if (rc == MDB_NOTFOUND) {
-            throw Archive::NotFound(std::to_string(key), name.toStdString());
-        } else {
-            throw Archive::Unknown(name.toStdString(), mdb_strerror(rc));
-        }
-    } else {
-        QByteArray ba((char*)lmdbData.mv_data, lmdbData.mv_size);
-        QDataStream ds(&ba, QIODevice::ReadOnly);
-        V value;
-        ds >> value;
-        mdb_txn_abort(txn);
-
-        return value;
-    }
-}
-
-template <class K, class V>
-void Core::Storage<K, V>::removeRecord(const K& key)
-{
-    if (!opened) {
-        throw Archive::Closed("addElement", name.toStdString());
-    }
-    
-    MDB_val lmdbKey;
-    lmdbKey << key;
-    
-    MDB_txn *txn;
-    int rc;
-    mdb_txn_begin(environment, NULL, 0, &txn);
-    rc = mdb_del(txn, base, &lmdbKey, NULL);
-    if (rc) {
-        mdb_txn_abort(txn);
-        if (rc == MDB_NOTFOUND) {
-            throw Archive::NotFound(std::to_string(key), name.toStdString());
-        } else {
-            throw Archive::Unknown(name.toStdString(), mdb_strerror(rc));
-        }
-    } else {
-        mdb_txn_commit(txn);
-    }
-}
-
-template <class K, class V>
-QString Core::Storage<K, V>::getName() const {
-    return name;}
-
-MDB_val& operator << (MDB_val& data, const QString& value) {
-    QByteArray ba = value.toUtf8();
-    data.mv_size = ba.size();
-    data.mv_data = ba.data();
-    return data;
-}
-MDB_val& operator >> (MDB_val& data, QString& value) {
-    value = QString::fromUtf8((const char*)data.mv_data, data.mv_size);
-    return data;
-}
-
-MDB_val& operator << (MDB_val& data, uint32_t& value) {
-    data.mv_size = 4;
-    data.mv_data = &value;
-    return data;
-}
-MDB_val& operator >> (MDB_val& data, uint32_t& value) {
-    std::memcpy(&value, data.mv_data, data.mv_size);
-    return data;
-}
-
-std::string std::to_string(const QString& str) {
-    return str.toStdString();
-}
-#endif //CORE_STORAGE_HPP
diff --git a/external/qxmpp b/external/qxmpp
index ab4bdf2..9e9c22b 160000
--- a/external/qxmpp
+++ b/external/qxmpp
@@ -1 +1 @@
-Subproject commit ab4bdf2da41a26f462fe3a333a34e32c999e2a6d
+Subproject commit 9e9c22b16a39c7370fed31c6deea56d8abf72440
diff --git a/external/simpleCrypt/CMakeLists.txt b/external/simpleCrypt/CMakeLists.txt
index 274d304..5f274ba 100644
--- a/external/simpleCrypt/CMakeLists.txt
+++ b/external/simpleCrypt/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.0)
+cmake_minimum_required(VERSION 3.5)
 project(simplecrypt LANGUAGES CXX)
 
 set(CMAKE_AUTOMOC ON)
diff --git a/main/main.cpp b/main/main.cpp
index d9ad4cc..c8e962f 100644
--- a/main/main.cpp
+++ b/main/main.cpp
@@ -24,7 +24,7 @@
 #include <QObject>
 
 #ifdef WITH_OMEMO
-#include <QXmppOmemoStorage.h>
+#include <Omemo/QXmppOmemoStorage.h>
 #endif
 
 int main(int argc, char *argv[])
diff --git a/shared/message.cpp b/shared/message.cpp
index 0e1b3c5..dab03a3 100644
--- a/shared/message.cpp
+++ b/shared/message.cpp
@@ -61,50 +61,42 @@ Shared::Message::Message():
     attachPath()
     {}
 
-QString Shared::Message::getBody() const
-{
+QString Shared::Message::getBody() const {
     return body;
 }
 
-QString Shared::Message::getFrom() const
-{
+QString Shared::Message::getFrom() const {
     QString from = jFrom;
-    if (rFrom.size() > 0) {
+    if (rFrom.size() > 0)
         from += "/" + rFrom;
-    }
+
     return from;
 }
 
-QString Shared::Message::getTo() const
-{
+QString Shared::Message::getTo() const {
     QString to = jTo;
-    if (rTo.size() > 0) {
+    if (rTo.size() > 0)
         to += "/" + rTo;
-    }
+
     return to;
 }
 
-QString Shared::Message::getId() const
-{
-    if (id.size() > 0) {
+QString Shared::Message::getId() const {
+    if (id.size() > 0)
         return id;
-    } else {
+    else
         return stanzaId;
-    }
 }
 
-QDateTime Shared::Message::getTime() const
-{
+QDateTime Shared::Message::getTime() const {
     return time;
 }
 
-void Shared::Message::setBody(const QString& p_body)
-{
+void Shared::Message::setBody(const QString& p_body) {
     body = p_body;
 }
 
-void Shared::Message::setFrom(const QString& from)
-{
+void Shared::Message::setFrom(const QString& from) {
     QStringList list = from.split("/");
     if (list.size() == 1) {
         jFrom = from.toLower();
@@ -114,8 +106,7 @@ void Shared::Message::setFrom(const QString& from)
     }
 }
 
-void Shared::Message::setTo(const QString& to)
-{
+void Shared::Message::setTo(const QString& to) {
     QStringList list = to.split("/");
     if (list.size() == 1) {
         jTo = to.toLower();
@@ -125,153 +116,122 @@ void Shared::Message::setTo(const QString& to)
     }
 }
 
-void Shared::Message::setId(const QString& p_id)
-{
+void Shared::Message::setId(const QString& p_id) {
     id = p_id;
 }
 
-void Shared::Message::setTime(const QDateTime& p_time)
-{
+void Shared::Message::setTime(const QDateTime& p_time) {
     time = p_time;
 }
 
-QString Shared::Message::getFromJid() const
-{
+QString Shared::Message::getFromJid() const {
     return jFrom;
 }
 
-QString Shared::Message::getFromResource() const
-{
+QString Shared::Message::getFromResource() const {
     return rFrom;
 }
 
-QString Shared::Message::getToJid() const
-{
+QString Shared::Message::getToJid() const {
     return jTo;
 }
 
-QString Shared::Message::getToResource() const
-{
+QString Shared::Message::getToResource() const {
     return rTo;
 }
 
-QString Shared::Message::getErrorText() const
-{
+QString Shared::Message::getErrorText() const {
     return errorText;
 }
 
-QString Shared::Message::getPenPalJid() const
-{
-    if (outgoing) {
+QString Shared::Message::getPenPalJid() const {
+    if (outgoing)
         return jTo;
-    } else {
+    else
         return jFrom;
-    }
 }
 
-QString Shared::Message::getPenPalResource() const
-{
-    if (outgoing) {
+QString Shared::Message::getPenPalResource() const {
+    if (outgoing)
         return rTo;
-    } else {
+    else
         return rFrom;
-    }
 }
 
-Shared::Message::State Shared::Message::getState() const
-{
+Shared::Message::State Shared::Message::getState() const {
     return state;
 }
 
-bool Shared::Message::getEdited() const
-{
+bool Shared::Message::getEdited() const {
     return edited;
 }
 
-void Shared::Message::setFromJid(const QString& from)
-{
+void Shared::Message::setFromJid(const QString& from) {
     jFrom = from.toLower();
 }
 
-void Shared::Message::setFromResource(const QString& from)
-{
+void Shared::Message::setFromResource(const QString& from) {
     rFrom = from;
 }
 
-void Shared::Message::setToJid(const QString& to)
-{
+void Shared::Message::setToJid(const QString& to) {
     jTo = to.toLower();
 }
 
-void Shared::Message::setToResource(const QString& to)
-{
+void Shared::Message::setToResource(const QString& to) {
     rTo = to;
 }
 
-void Shared::Message::setErrorText(const QString& err)
-{
-    if (state == State::error) {
+void Shared::Message::setErrorText(const QString& err) {
+    if (state == State::error)
         errorText = err;
-    }
 }
 
-bool Shared::Message::getOutgoing() const
-{
+bool Shared::Message::getOutgoing() const {
     return outgoing;
 }
 
-void Shared::Message::setOutgoing(bool og)
-{
+void Shared::Message::setOutgoing(bool og) {
     outgoing = og;
 }
 
-bool Shared::Message::getForwarded() const
-{
+bool Shared::Message::getForwarded() const {
     return forwarded;
 }
 
-void Shared::Message::generateRandomId()
-{
+void Shared::Message::generateRandomId() {
     id = generateUUID();
 }
 
-QString Shared::Message::getThread() const
-{
+QString Shared::Message::getThread() const {
     return thread;
 }
 
-void Shared::Message::setForwarded(bool fwd)
-{
+void Shared::Message::setForwarded(bool fwd) {
     forwarded = fwd;
 }
 
-void Shared::Message::setThread(const QString& p_body)
-{
+void Shared::Message::setThread(const QString& p_body) {
     thread = p_body;
 }
 
-QDateTime Shared::Message::getLastModified() const
-{
+QDateTime Shared::Message::getLastModified() const {
     return lastModified;
 }
 
-QString Shared::Message::getOriginalBody() const
-{
+QString Shared::Message::getOriginalBody() const {
     return originalMessage;
 }
 
-Shared::Message::Type Shared::Message::getType() const
-{
+Shared::Message::Type Shared::Message::getType() const {
     return type;
 }
 
-void Shared::Message::setType(Shared::Message::Type t)
-{
+void Shared::Message::setType(Shared::Message::Type t) {
     type = t;
 }
 
-void Shared::Message::setState(Shared::Message::State p_state)
-{
+void Shared::Message::setState(Shared::Message::State p_state) {
     state = p_state;
     
     if (state != State::error) {
@@ -279,96 +239,92 @@ void Shared::Message::setState(Shared::Message::State p_state)
     }
 }
 
-bool Shared::Message::serverStored() const
-{
+bool Shared::Message::serverStored() const {
     return state == State::delivered || state == State::sent;
 }
 
-void Shared::Message::setEdited(bool p_edited)
-{
+void Shared::Message::setEdited(bool p_edited) {
     edited = p_edited;
 }
 
-void Shared::Message::serialize(QDataStream& data) const
-{
-    data << jFrom;
-    data << rFrom;
-    data << jTo;
-    data << rTo;
-    data << id;
-    data << body;
-    data << time;
-    data << thread;
-    data << (quint8)type;
-    data << outgoing;
-    data << forwarded;
-    data << oob;
-    data << (quint8)state;
-    data << edited;
-    if (state == State::error) {
-        data << errorText;
+QDataStream& operator<<(QDataStream& out, const Shared::Message& info) {
+    out << info.jFrom;
+    out << info.rFrom;
+    out << info.jTo;
+    out << info.rTo;
+    out << info.id;
+    out << info.body;
+    out << info.time;
+    out << info.thread;
+    out << (quint8)info.type;
+    out << info.outgoing;
+    out << info.forwarded;
+    out << info.oob;
+    out << (quint8)info.state;
+    out << info.edited;
+    if (info.state == Shared::Message::State::error)
+        out << info.errorText;
+
+    if (info.edited) {
+        out << info.originalMessage;
+        out << info.lastModified;
     }
-    if (edited) {
-        data << originalMessage;
-        data << lastModified;
-    }
-    data << stanzaId;
-    data << attachPath;
+    out << info.stanzaId;
+    out << info.attachPath;
+
+    return out;
 }
 
-void Shared::Message::deserialize(QDataStream& data)
-{
-    data >> jFrom;
-    data >> rFrom;
-    data >> jTo;
-    data >> rTo;
-    data >> id;
-    data >> body;
-    data >> time;
-    data >> thread;
+QDataStream & operator>>(QDataStream& in, Shared::Message& info) {
+    in >> info.jFrom;
+    in >> info.rFrom;
+    in >> info.jTo;
+    in >> info.rTo;
+    in >> info.id;
+    in >> info.body;
+    in >> info.time;
+    in >> info.thread;
     quint8 t;
-    data >> t;
-    type = static_cast<Type>(t);
-    data >> outgoing;
-    data >> forwarded;
-    data >> oob;
+    in >> t;
+    info.type = static_cast<Shared::Message::Type>(t);
+    in >> info.outgoing;
+    in >> info.forwarded;
+    in >> info.oob;
     quint8 s;
-    data >> s;
-    state = static_cast<State>(s);
-    data >> edited;
-    if (state == State::error) {
-        data >> errorText;
+    in >> s;
+    info.state = static_cast<Shared::Message::State>(s);
+    in >> info.edited;
+    if (info.state == Shared::Message::State::error)
+        in >> info.errorText;
+
+    if (info.edited) {
+        in >> info.originalMessage;
+        in >> info.lastModified;
     }
-    if (edited) {
-        data >> originalMessage;
-        data >> lastModified;
-    }
-    data >> stanzaId;
-    data >> attachPath;
+    in >> info.stanzaId;
+    in >> info.attachPath;
+
+    return in;
 }
 
 bool Shared::Message::change(const QMap<QString, QVariant>& data)
 {
     QMap<QString, QVariant>::const_iterator itr = data.find("state");
-    if (itr != data.end()) {
+    if (itr != data.end())
         setState(static_cast<State>(itr.value().toUInt()));
-    }
     
     itr = data.find("outOfBandUrl");
-    if (itr != data.end()) {
+    if (itr != data.end())
         setOutOfBandUrl(itr.value().toString());
-    }
     
     itr = data.find("attachPath");
-    if (itr != data.end()) {
+    if (itr != data.end())
         setAttachPath(itr.value().toString());
-    }
     
     if (state == State::error) {
         itr = data.find("errorText");
-        if (itr != data.end()) {
+        if (itr != data.end())
             setErrorText(itr.value().toString());
-        }
     }
     
     bool idChanged = false;
@@ -386,9 +342,8 @@ bool Shared::Message::change(const QMap<QString, QVariant>& data)
         QString newId = itr.value().toString();
         if (stanzaId != newId) {
             setStanzaId(newId);
-            if (id.size() == 0) {
+            if (id.size() == 0)
                 idChanged = true;
-            }
         }
     }
     
@@ -398,15 +353,15 @@ bool Shared::Message::change(const QMap<QString, QVariant>& data)
         if (body != b) {
             QMap<QString, QVariant>::const_iterator dItr = data.find("stamp");
             QDateTime correctionDate;
-            if (dItr != data.end()) {
+            if (dItr != data.end())
                 correctionDate = dItr.value().toDateTime();
-            } else {
+            else
                 correctionDate = QDateTime::currentDateTimeUtc();      //in case there is no information about time of this correction it's applied
-            }
+
             if (!edited || lastModified < correctionDate) {
-                if (!edited) {
+                if (!edited)
                     originalMessage = body;
-                }
+
                 lastModified = correctionDate;
                 setBody(b);
                 setEdited(true);
@@ -416,57 +371,47 @@ bool Shared::Message::change(const QMap<QString, QVariant>& data)
         QMap<QString, QVariant>::const_iterator dItr = data.find("stamp");
         if (dItr != data.end()) {
             QDateTime ntime = dItr.value().toDateTime();
-            if (time != ntime) {
+            if (time != ntime)
                 setTime(ntime);
-            }
         }
     }
     
     return idChanged;
 }
 
-void Shared::Message::setCurrentTime()
-{
+void Shared::Message::setCurrentTime() {
     time = QDateTime::currentDateTimeUtc();
 }
 
-QString Shared::Message::getOutOfBandUrl() const
-{
+QString Shared::Message::getOutOfBandUrl() const {
     return oob;
 }
 
-bool Shared::Message::hasOutOfBandUrl() const
-{
+bool Shared::Message::hasOutOfBandUrl() const {
     return oob.size() > 0;
 }
 
-void Shared::Message::setOutOfBandUrl(const QString& url)
-{
+void Shared::Message::setOutOfBandUrl(const QString& url) {
     oob = url;
 }
 
-bool Shared::Message::storable() const
-{
+bool Shared::Message::storable() const {
     return id.size() > 0 && (body.size() > 0 || oob.size() > 0 || attachPath.size() > 0);
 }
 
-void Shared::Message::setStanzaId(const QString& sid)
-{
+void Shared::Message::setStanzaId(const QString& sid) {
     stanzaId = sid;
 }
 
-QString Shared::Message::getStanzaId() const
-{
+QString Shared::Message::getStanzaId() const {
     return stanzaId;
 }
 
-QString Shared::Message::getAttachPath() const
-{
+QString Shared::Message::getAttachPath() const {
     return attachPath;
 }
 
-void Shared::Message::setAttachPath(const QString& path)
-{
+void Shared::Message::setAttachPath(const QString& path) {
     attachPath = path;
 }
 
@@ -474,18 +419,15 @@ Shared::Message::Change::Change(const QMap<QString, QVariant>& _data):
     data(_data),
     idModified(false) {}
     
-void Shared::Message::Change::operator()(Shared::Message& msg)
-{
+void Shared::Message::Change::operator()(Shared::Message& msg) {
     idModified = msg.change(data);
 }
 
-void Shared::Message::Change::operator()(Shared::Message* msg)
-{
+void Shared::Message::Change::operator()(Shared::Message* msg) {
     idModified = msg->change(data);
 }
 
-bool Shared::Message::Change::hasIdBeenModified() const
-{
+bool Shared::Message::Change::hasIdBeenModified() const {
     return idModified;
 }
 
diff --git a/shared/message.h b/shared/message.h
index aa91af6..557b1b3 100644
--- a/shared/message.h
+++ b/shared/message.h
@@ -25,12 +25,20 @@
 #include <QMap>
 #include <QDataStream>
 
+namespace Shared {
+    class Message;
+}
+
+QDataStream& operator << (QDataStream& out, const Shared::Message& info);
+QDataStream& operator >> (QDataStream& in, Shared::Message& info);
 namespace Shared {
 
 /**
  * @todo write docs
  */
 class Message {
+    friend QDataStream& ::operator << (QDataStream& out, const Shared::Message& info);
+    friend QDataStream& ::operator >> (QDataStream& in, Shared::Message& info);
 public:
     enum Type {
         error,
@@ -116,9 +124,6 @@ public:
     QString getStanzaId() const;
     QString getAttachPath() const;
     
-    void serialize(QDataStream& data) const;
-    void deserialize(QDataStream& data);
-    
 private:
     QString jFrom;
     QString rFrom;

From 297e08ba412bc656881d3c7bde82922a2db6d3da Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Fri, 3 Nov 2023 20:13:45 -0300
Subject: [PATCH 252/281] somewhat working...

---
 core/storage/archive.cpp | 27 ++++++++++++++-------------
 1 file changed, 14 insertions(+), 13 deletions(-)

diff --git a/core/storage/archive.cpp b/core/storage/archive.cpp
index 6b00f37..0a93850 100644
--- a/core/storage/archive.cpp
+++ b/core/storage/archive.cpp
@@ -84,7 +84,7 @@ bool Core::Archive::addElement(const Shared::Message& message) {
         order->addRecord(message.getTime().toMSecsSinceEpoch(), id, txn);
         QString stanzaId = message.getStanzaId();
         if (!stanzaId.isEmpty())
-            stanzaIdToId->addRecord(stanzaId, id);
+            stanzaIdToId->addRecord(stanzaId, id, txn);
 
         txn.commit();
         return true;
@@ -199,14 +199,15 @@ unsigned int Core::Archive::addElements(const std::list<Shared::Message>& messag
         if (!added)
             continue;
 
-        order->addRecord(message.getTime().toMSecsSinceEpoch(), id);
+        order->addRecord(message.getTime().toMSecsSinceEpoch(), id, txn);
 
         QString sid = message.getStanzaId();
         if (!sid.isEmpty())
-            stanzaIdToId->addRecord(sid, id);
+            stanzaIdToId->addRecord(sid, id, txn);
 
         ++success;
     }
+    txn.commit();
     
     return success;
 }
@@ -217,24 +218,18 @@ long unsigned int Core::Archive::size() const {
 
 std::list<Shared::Message> Core::Archive::getBefore(unsigned int count, const QString& id) {
     LMDBAL::Transaction txn = db.beginReadOnlyTransaction();
+    std::list<Shared::Message> res;
     try {
         cursor.open(txn);
-        if (id.isEmpty()) {
-            cursor.last();
-        } else {
+        if (!id.isEmpty()) {
             Shared::Message reference = messages->getRecord(id, txn);
             uint64_t stamp = reference.getTime().toMSecsSinceEpoch();
             cursor.set(stamp);
-            cursor.prev();
         }
 
-        std::list<Shared::Message> res;
         for (unsigned int i = 0; i < count; ++i) {
             std::pair<uint64_t, QString> pair;
-            if (i == 0)
-                cursor.current(pair.first, pair.second);
-            else
-                cursor.prev(pair.first, pair.second);
+            cursor.prev(pair.first, pair.second);
 
             res.emplace_back();
             Shared::Message& msg = res.back();
@@ -243,6 +238,12 @@ std::list<Shared::Message> Core::Archive::getBefore(unsigned int count, const QS
         cursor.close();
 
         return res;
+    } catch (const LMDBAL::NotFound& e) {
+        cursor.close();
+        if (res.empty())
+            throw e;
+        else
+            return res;
     } catch (...) {
         cursor.close();
         throw;
@@ -273,7 +274,7 @@ bool Core::Archive::setEncryptionEnabled(bool is) {
     LMDBAL::WriteTransaction txn = db.beginTransaction();
     bool current = false;
     try {
-        current = stats->getRecord("isEncryptionEnabled").toBool();
+        current = stats->getRecord("isEncryptionEnabled", txn).toBool();
     } catch (const LMDBAL::NotFound& e) {}
 
     if (is != current) {

From a7d1a28f2932f78d75fb44e88f169617b705b9f5 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Sat, 4 Nov 2023 22:12:15 -0300
Subject: [PATCH 253/281] some work towards encryption

---
 CMakeLists.txt                           |  7 ---
 core/CMakeLists.txt                      |  1 -
 core/account.cpp                         | 11 +++++
 core/account.h                           |  1 +
 core/components/CMakeLists.txt           |  2 +
 core/{storage => components}/archive.cpp | 19 ++++----
 core/{storage => components}/archive.h   |  6 ++-
 core/handlers/rosterhandler.cpp          | 14 +++---
 core/handlers/rosterhandler.h            |  2 +-
 core/rosteritem.cpp                      | 10 ++--
 core/rosteritem.h                        |  8 ++--
 core/squawk.cpp                          |  9 ++++
 core/squawk.h                            |  1 +
 core/storage/CMakeLists.txt              |  4 --
 main/application.cpp                     | 10 ++++
 main/application.h                       |  2 +
 ui/models/contact.cpp                    | 25 +++++++++-
 ui/models/contact.h                      |  3 ++
 ui/squawk.cpp                            | 61 ++++++++++++------------
 ui/squawk.h                              |  4 +-
 ui/widgets/conversation.h                | 10 ++--
 21 files changed, 129 insertions(+), 81 deletions(-)
 rename core/{storage => components}/archive.cpp (96%)
 rename core/{storage => components}/archive.h (94%)
 delete mode 100644 core/storage/CMakeLists.txt

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 5950c36..378aa51 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -46,13 +46,6 @@ endif()
 find_package(Boost COMPONENTS)
 
 target_include_directories(squawk PRIVATE ${Boost_INCLUDE_DIRS})
-target_include_directories(squawk PRIVATE ${Qt${QT_VERSION_MAJOR}_INCLUDE_DIRS})
-target_include_directories(squawk PRIVATE ${Qt${QT_VERSION_MAJOR}Widgets_INCLUDE_DIRS})
-target_include_directories(squawk PRIVATE ${Qt${QT_VERSION_MAJOR}DBus_INCLUDE_DIRS})
-target_include_directories(squawk PRIVATE ${Qt${QT_VERSION_MAJOR}Gui_INCLUDE_DIRS})
-target_include_directories(squawk PRIVATE ${Qt${QT_VERSION_MAJOR}Xml_INCLUDE_DIRS})
-target_include_directories(squawk PRIVATE ${Qt${QT_VERSION_MAJOR}Network_INCLUDE_DIRS})
-target_include_directories(squawk PRIVATE ${Qt${QT_VERSION_MAJOR}Core_INCLUDE_DIRS})
 
 #OMEMO
 if (WITH_OMEMO)
diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt
index 01c6d8f..a02bfe6 100644
--- a/core/CMakeLists.txt
+++ b/core/CMakeLists.txt
@@ -31,7 +31,6 @@ target_sources(squawk PRIVATE
 target_include_directories(squawk PRIVATE ${LMDB_INCLUDE_DIRS})
 
 add_subdirectory(handlers)
-add_subdirectory(storage)
 add_subdirectory(passwordStorageEngines)
 add_subdirectory(components)
 add_subdirectory(delayManager)
diff --git a/core/account.cpp b/core/account.cpp
index b06f51b..96ca4e1 100644
--- a/core/account.cpp
+++ b/core/account.cpp
@@ -669,6 +669,17 @@ void Core::Account::setRoomJoined(const QString& jid, bool joined) {
     conf->setJoined(joined);
 }
 
+void Core::Account::setContactEncryption(const QString& jid, Shared::EncryptionProtocol value) {
+    Contact* cnt = rh->getContact(jid);
+    if (cnt == nullptr) {
+        qDebug() << "An attempt to set encryption to the non-existing contact" << jid << "of the account" << getName() << ", skipping";
+        return;
+    }
+
+    cnt->setEncryption(value);
+}
+
+
 void Core::Account::discoverInfo(const QString& address, const QString& node) {
     if (state == Shared::ConnectionState::connected) {
         dm->requestInfo(address, node);
diff --git a/core/account.h b/core/account.h
index a9425e7..6442abf 100644
--- a/core/account.h
+++ b/core/account.h
@@ -132,6 +132,7 @@ public:
     void removeContactFromGroupRequest(const QString& jid, const QString& groupName);
     void renameContactRequest(const QString& jid, const QString& newName);
     void requestChangeMessage(const QString& jid, const QString& messageId, const QMap<QString, QVariant>& data);
+    void setContactEncryption(const QString& jid, Shared::EncryptionProtocol value);
     
     void setRoomJoined(const QString& jid, bool joined);
     void setRoomAutoJoin(const QString& jid, bool joined);
diff --git a/core/components/CMakeLists.txt b/core/components/CMakeLists.txt
index 86d9fb8..77d290b 100644
--- a/core/components/CMakeLists.txt
+++ b/core/components/CMakeLists.txt
@@ -2,12 +2,14 @@ set(SOURCE_FILES
     networkaccess.cpp
     clientcache.cpp
     urlstorage.cpp
+    archive.cpp
 )
 
 set(HEADER_FILES
     networkaccess.h
     clientcache.h
     urlstorage.h
+    archive.h
 )
 
 target_sources(squawk PRIVATE
diff --git a/core/storage/archive.cpp b/core/components/archive.cpp
similarity index 96%
rename from core/storage/archive.cpp
rename to core/components/archive.cpp
index 0a93850..003cce6 100644
--- a/core/storage/archive.cpp
+++ b/core/components/archive.cpp
@@ -17,12 +17,11 @@
  */
 
 #include "archive.h"
+
 #include <sys/stat.h>
 #include <sys/types.h>
-#include <QStandardPaths>
+
 #include <QDebug>
-#include <QDataStream>
-#include <QDir>
 
 Core::Archive::Archive(const QString& account, const QString& p_jid, QObject* parent):
     QObject(parent),
@@ -262,23 +261,23 @@ void Core::Archive::setFromTheBeginning(bool is) {
     stats->forceRecord("fromTheBeginning", is);
 }
 
-bool Core::Archive::isEncryptionEnabled() const {
+Shared::EncryptionProtocol Core::Archive::encryption() const {
     try {
-        return stats->getRecord("isEncryptionEnabled").toBool();
+        return stats->getRecord("encryption").value<Shared::EncryptionProtocol>();
     } catch (const LMDBAL::NotFound& e) {
-        return false;
+        return Shared::EncryptionProtocol::none;
     }
 }
 
-bool Core::Archive::setEncryptionEnabled(bool is) {
+bool Core::Archive::setEncryption(Shared::EncryptionProtocol is) {
     LMDBAL::WriteTransaction txn = db.beginTransaction();
-    bool current = false;
+    Shared::EncryptionProtocol current = Shared::EncryptionProtocol::none;
     try {
-        current = stats->getRecord("isEncryptionEnabled", txn).toBool();
+        current = stats->getRecord("encryption", txn).value<Shared::EncryptionProtocol>();
     } catch (const LMDBAL::NotFound& e) {}
 
     if (is != current) {
-        stats->forceRecord("isEncryptionEnabled", is, txn);
+        stats->forceRecord("encryption", static_cast<uint8_t>(is), txn);
         txn.commit();
         return true;
     }
diff --git a/core/storage/archive.h b/core/components/archive.h
similarity index 94%
rename from core/storage/archive.h
rename to core/components/archive.h
index 6f3c9eb..08f508a 100644
--- a/core/storage/archive.h
+++ b/core/components/archive.h
@@ -23,7 +23,9 @@
 #include <QCryptographicHash>
 #include <QMimeDatabase>
 #include <QMimeType>
+#include <QDataStream>
 
+#include "shared/enums.h"
 #include "shared/message.h"
 #include "shared/exception.h"
 #include <lmdb.h>
@@ -60,8 +62,8 @@ public:
     std::list<Shared::Message> getBefore(unsigned int count, const QString& id);
     bool isFromTheBeginning() const;
     void setFromTheBeginning(bool is);
-    bool isEncryptionEnabled() const;
-    bool setEncryptionEnabled(bool is);     //returns true if changed, false otherwise
+    Shared::EncryptionProtocol encryption() const;
+    bool setEncryption(Shared::EncryptionProtocol value);     //returns true if changed, false otherwise
     bool setAvatar(const QByteArray& data, AvatarInfo& info, bool generated = false, const QString& resource = "");
     AvatarInfo getAvatarInfo(const QString& resource = "") const;
     bool readAvatarInfo(AvatarInfo& target, const QString& resource = "") const;
diff --git a/core/handlers/rosterhandler.cpp b/core/handlers/rosterhandler.cpp
index 4ce8939..38425ba 100644
--- a/core/handlers/rosterhandler.cpp
+++ b/core/handlers/rosterhandler.cpp
@@ -225,7 +225,7 @@ void Core::RosterHandler::onContactGroupAdded(const QString& group) {
 #if (QXMPP_VERSION) >= QT_VERSION_CHECK(1, 5, 0)
         {"trust", QVariant::fromValue(acc->th->getSummary(contact->jid))},
 #endif
-        {"encryption", contact->isEncryptionEnabled()}
+        {"encryption", QVariant::fromValue(contact->encryption())}
     });
     addToGroup(contact->jid, group);
     emit acc->addContact(contact->jid, group, cData);
@@ -246,9 +246,9 @@ void Core::RosterHandler::onContactNameChanged(const QString& cname) {
     emit acc->changeContact(contact->jid, {{"name", cname}});
 }
 
-void Core::RosterHandler::onContactEncryptionChanged(bool value) {
+void Core::RosterHandler::onContactEncryptionChanged(Shared::EncryptionProtocol value) {
     RosterItem* contact = static_cast<RosterItem*>(sender());
-    emit acc->changeContact(contact->jid, {{"encryption", value}});
+    emit acc->changeContact(contact->jid, {{"encryption", QVariant::fromValue(value)}});
 }
 
 void Core::RosterHandler::onContactSubscriptionStateChanged(Shared::SubscriptionState cstate) {
@@ -328,7 +328,7 @@ Core::Contact * Core::RosterHandler::addOutOfRosterContact(const QString& jid) {
     cnt->setSubscriptionState(Shared::SubscriptionState::unknown);
     emit acc->addContact(lcJid, "", QMap<QString, QVariant>({
         {"state", QVariant::fromValue(Shared::SubscriptionState::unknown)},
-        {"encryption", false}
+        {"encryption", QVariant::fromValue(Shared::EncryptionProtocol::none)}
     }));
     handleNewContact(cnt);
     return cnt;
@@ -361,9 +361,9 @@ void Core::RosterHandler::onRosterItemRemoved(const QString& bareJid) {
     Contact* contact = itr->second;
     contacts.erase(itr);
     QSet<QString> cGroups = contact->getGroups();
-    for (QSet<QString>::const_iterator itr = cGroups.begin(), end = cGroups.end(); itr != end; ++itr) {
-        removeFromGroup(lcJid, *itr);
-    }
+    for (const QString& group : cGroups)
+        removeFromGroup(lcJid, group);
+
     emit acc->removeContact(lcJid);
     
     contact->deleteLater();
diff --git a/core/handlers/rosterhandler.h b/core/handlers/rosterhandler.h
index 61f3d7a..1f8e480 100644
--- a/core/handlers/rosterhandler.h
+++ b/core/handlers/rosterhandler.h
@@ -95,7 +95,7 @@ private slots:
     void onContactNameChanged(const QString& name);
     void onContactSubscriptionStateChanged(Shared::SubscriptionState state);
     void onContactAvatarChanged(Shared::Avatar, const QString& path);
-    void onContactEncryptionChanged(bool value);
+    void onContactEncryptionChanged(Shared::EncryptionProtocol value);
     void onPepSupportedChanged(Shared::Support support);
     
 private:
diff --git a/core/rosteritem.cpp b/core/rosteritem.cpp
index afbf836..a5763e2 100644
--- a/core/rosteritem.cpp
+++ b/core/rosteritem.cpp
@@ -561,12 +561,12 @@ Shared::Message Core::RosterItem::getMessage(const QString& id) {
     return archive->getElement(id);
 }
 
-bool Core::RosterItem::isEncryptionEnabled() const {
-    return archive->isEncryptionEnabled();
+Shared::EncryptionProtocol Core::RosterItem::encryption() const {
+    return archive->encryption();
 }
 
-void Core::RosterItem::enableEncryption(bool value) {
-    bool changed = archive->setEncryptionEnabled(value);
+void Core::RosterItem::setEncryption(Shared::EncryptionProtocol value) {
+    bool changed = archive->setEncryption(value);
     if (changed)
         emit encryptionChanged(value);
 }
@@ -574,7 +574,7 @@ void Core::RosterItem::enableEncryption(bool value) {
 QMap<QString, QVariant> Core::RosterItem::getInfo() const {
     QMap<QString, QVariant> result({
         {"name", name},
-        {"encryption", isEncryptionEnabled()},
+        {"encryption", QVariant::fromValue(encryption())},
     });
     Archive::AvatarInfo info;
     bool hasAvatar = readAvatarInfo(info);
diff --git a/core/rosteritem.h b/core/rosteritem.h
index fa154c0..203ed88 100644
--- a/core/rosteritem.h
+++ b/core/rosteritem.h
@@ -34,7 +34,7 @@
 #include "shared/enums.h"
 #include "shared/message.h"
 #include "shared/vcard.h"
-#include "storage/archive.h"
+#include "components/archive.h"
 #include "adapterfunctions.h"
 
 namespace Core {
@@ -62,8 +62,8 @@ public:
     void setName(const QString& n);
     QString getServer() const;
     bool isMuc() const;
-    bool isEncryptionEnabled() const;
-    void enableEncryption(bool value = true);
+    Shared::EncryptionProtocol encryption() const;
+    void setEncryption(Shared::EncryptionProtocol value);
     
     void addMessageToArchive(const Shared::Message& msg);
     void correctMessageInArchive(const QString& originalId, const Shared::Message& msg);
@@ -91,7 +91,7 @@ signals:
     void needHistory(const QString& before, const QString& after, const QDateTime& afterTime = QDateTime()) const;
     void avatarChanged(Shared::Avatar, const QString& path) const;
     void requestVCard(const QString& jid) const;
-    void encryptionChanged(bool value) const;
+    void encryptionChanged(Shared::EncryptionProtocol value) const;
     
 public:
     const QString jid;
diff --git a/core/squawk.cpp b/core/squawk.cpp
index b7a0aad..1888487 100644
--- a/core/squawk.cpp
+++ b/core/squawk.cpp
@@ -590,6 +590,15 @@ void Core::Squawk::setRoomAutoJoin(const QString& account, const QString& jid, b
     itr->second->setRoomAutoJoin(jid, joined);
 }
 
+void Core::Squawk::setContactEncryption(const QString& account, const QString& jid, Shared::EncryptionProtocol value) {
+    AccountsMap::const_iterator itr = amap.find(account);
+    if (itr == amap.end()) {
+        qDebug() << "An attempt to set encryption to the contact" << jid << "of non existing account" << account << ", skipping";
+        return;
+    }
+    itr->second->setContactEncryption(jid, value);
+}
+
 void Core::Squawk::onAccountAddRoomPresence(const QString& jid, const QString& nick, const QMap<QString, QVariant>& data) {
     Account* acc = static_cast<Account*>(sender());
     emit addRoomParticipant(acc->getName(), jid, nick, data);
diff --git a/core/squawk.h b/core/squawk.h
index eebe917..2ee122e 100644
--- a/core/squawk.h
+++ b/core/squawk.h
@@ -116,6 +116,7 @@ public slots:
     void removeContactRequest(const QString& account, const QString& jid);
     void renameContactRequest(const QString& account, const QString& jid, const QString& newName);
     void addContactRequest(const QString& account, const QString& jid, const QString& name, const QSet<QString>& groups);
+    void setContactEncryption(const QString& account, const QString& jid, Shared::EncryptionProtocol value);
     
     void setRoomJoined(const QString& account, const QString& jid, bool joined);
     void setRoomAutoJoin(const QString& account, const QString& jid, bool joined);
diff --git a/core/storage/CMakeLists.txt b/core/storage/CMakeLists.txt
deleted file mode 100644
index e2de42c..0000000
--- a/core/storage/CMakeLists.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-target_sources(squawk PRIVATE
-    archive.cpp
-    archive.h
-)
diff --git a/main/application.cpp b/main/application.cpp
index 0701a0c..d184e6b 100644
--- a/main/application.cpp
+++ b/main/application.cpp
@@ -50,6 +50,7 @@ Application::Application(Core::Squawk* p_core):
     connect(this, &Application::replaceMessage, core, &Core::Squawk::replaceMessage);
     connect(this, &Application::sendMessage, core, &Core::Squawk::sendMessage);
     connect(this, &Application::resendMessage, core, &Core::Squawk::resendMessage);
+    connect(this, &Application::setEncryption, core, &Core::Squawk::setContactEncryption);
     connect(&roster, &Models::Roster::requestArchive,
             std::bind(&Core::Squawk::requestArchive, core, std::placeholders::_1, std::placeholders::_2, 20, std::placeholders::_3));
 
@@ -526,6 +527,7 @@ void Application::subscribeConversation(Conversation* conv) {
     connect(conv, &Conversation::sendMessage, this, &Application::onConversationMessage);
     connect(conv, &Conversation::replaceMessage, this, &Application::onConversationReplaceMessage);
     connect(conv, &Conversation::resendMessage, this, &Application::onConversationResend);
+    connect(conv, &Conversation::setEncryption, this, &Application::onConversationSetEncryption);
     connect(conv, &Conversation::notifyableMessage, this, &Application::notify);
 }
 
@@ -586,6 +588,14 @@ void Application::onConversationReplaceMessage(const QString& originalId, const
     emit replaceMessage(acc, originalId, msg);
 }
 
+void Application::onConversationSetEncryption(Shared::EncryptionProtocol value) {
+    Conversation* conv = static_cast<Conversation*>(sender());
+    QString acc = conv->getAccount();
+    QString jid = conv->getJid();
+
+    emit setEncryption(acc, jid, value);
+}
+
 void Application::onConversationResend(const QString& id) {
     Conversation* conv = static_cast<Conversation*>(sender());
     QString acc = conv->getAccount();
diff --git a/main/application.h b/main/application.h
index 54c2dbc..408572b 100644
--- a/main/application.h
+++ b/main/application.h
@@ -60,6 +60,7 @@ signals:
     void setRoomAutoJoin(const QString& account, const QString& jid, bool joined);
     void subscribeContact(const QString& account, const QString& jid, const QString& reason);
     void unsubscribeContact(const QString& account, const QString& jid, const QString& reason);
+    void setEncryption(const QString& account, const QString& jid, Shared::EncryptionProtocol value);
 
     void quitting();
     void readyToQuit();
@@ -101,6 +102,7 @@ private slots:
     void onItemExpanded(const QModelIndex& index);
     void onItemCollapsed(const QModelIndex& index);
     void onAddedElement(const std::list<QString>& path);
+    void onConversationSetEncryption(Shared::EncryptionProtocol value);
 
 private:
     void createMainWindow();
diff --git a/ui/models/contact.cpp b/ui/models/contact.cpp
index c27965b..c89724e 100644
--- a/ui/models/contact.cpp
+++ b/ui/models/contact.cpp
@@ -36,6 +36,10 @@ Models::Contact::Contact(const Account* acc, const QString& p_jid ,const QMap<QS
     itr = data.find("trust");
     if (itr != data.end())
         setTrust(itr.value().value<Shared::TrustSummary>());
+
+    itr = data.find("encryption");
+    if (itr != data.end())
+        setEncryption(itr.value().value<Shared::EncryptionProtocol>());
 }
 
 Models::Contact::~Contact() {}
@@ -71,7 +75,7 @@ void Models::Contact::setStatus(const QString& p_state) {
 }
 
 int Models::Contact::columnCount() const {
-    return 9;
+    return 10;
 }
 
 QVariant Models::Contact::data(int column) const {
@@ -94,6 +98,8 @@ QVariant Models::Contact::data(int column) const {
             return getAvatarPath();
         case 8:
             return QVariant::fromValue(getTrust());
+        case 9:
+            return QVariant::fromValue(getEncryption());
         default:
             return QVariant();
     }
@@ -115,6 +121,8 @@ void Models::Contact::update(const QString& field, const QVariant& value) {
         setState(value.toUInt());
     } else if (field == "trust") {
         setTrust(value.value<Shared::TrustSummary>());
+    } else if (field == "encryption") {
+        setEncryption(value.value<Shared::EncryptionProtocol>());
     } else {
         Element::update(field, value);
     }
@@ -254,3 +262,18 @@ bool Models::Contact::hasKeys(Shared::EncryptionProtocol protocol) const {
     return trust.hasKeys(protocol);
 }
 
+void Models::Contact::setEncryption(Shared::EncryptionProtocol p_enc) {
+    if (encryption != p_enc) {
+        encryption = p_enc;
+        changed(9);
+    }
+}
+
+void Models::Contact::setEncryption(unsigned int p_enc) {
+    setEncryption(Shared::Global::fromInt<Shared::EncryptionProtocol>(p_enc));
+}
+
+Shared::EncryptionProtocol Models::Contact::getEncryption() const {
+    return encryption;
+}
+
diff --git a/ui/models/contact.h b/ui/models/contact.h
index dbf33d4..d03e936 100644
--- a/ui/models/contact.h
+++ b/ui/models/contact.h
@@ -43,6 +43,7 @@ public:
     
     Shared::Availability getAvailability() const;
     Shared::SubscriptionState getState() const;
+    Shared::EncryptionProtocol getEncryption() const;
 
     QIcon getStatusIcon(bool big = false) const;
     
@@ -78,6 +79,8 @@ protected:
     void setState(unsigned int p_state);
     void setStatus(const QString& p_state);
     void setTrust(const Shared::TrustSummary& p_trust);
+    void setEncryption(Shared::EncryptionProtocol p_enc);
+    void setEncryption(unsigned int p_enc);
     
 private:
     Shared::Availability availability;
diff --git a/ui/squawk.cpp b/ui/squawk.cpp
index ce759ac..64ea636 100644
--- a/ui/squawk.cpp
+++ b/ui/squawk.cpp
@@ -37,11 +37,11 @@ Squawk::Squawk(Models::Roster& p_rosterModel, QWidget *parent) :
     m_ui->setupUi(this);
     m_ui->roster->setModel(&rosterModel);
     m_ui->roster->setContextMenuPolicy(Qt::CustomContextMenu);
-    if (QApplication::style()->styleHint(QStyle::SH_ScrollBar_Transient) == 1) {
+    if (QApplication::style()->styleHint(QStyle::SH_ScrollBar_Transient) == 1)
         m_ui->roster->setColumnWidth(1, 52);
-    } else {
+    else
         m_ui->roster->setColumnWidth(1, 26);
-    }
+
     m_ui->roster->setIconSize(QSize(20, 20));
     m_ui->roster->header()->setStretchLastSection(false);
     m_ui->roster->header()->setSectionResizeMode(0, QHeaderView::Stretch);
@@ -69,24 +69,24 @@ Squawk::Squawk(Models::Roster& p_rosterModel, QWidget *parent) :
     connect(m_ui->actionAboutSquawk, &QAction::triggered, this, &Squawk::onAboutSquawkCalled);
     //m_ui->mainToolBar->addWidget(m_ui->comboBox);
     
-    if (testAttribute(Qt::WA_TranslucentBackground)) {
+    if (testAttribute(Qt::WA_TranslucentBackground))
         m_ui->roster->viewport()->setAutoFillBackground(false);
-    }
+
     
     QSettings settings;
     settings.beginGroup("ui");
     settings.beginGroup("window");
-    if (settings.contains("geometry")) {
+    if (settings.contains("geometry"))
         restoreGeometry(settings.value("geometry").toByteArray());
-    }
-    if (settings.contains("state")) {
+
+    if (settings.contains("state"))
         restoreState(settings.value("state").toByteArray());
-    }
+
     settings.endGroup();
     
-    if (settings.contains("splitter")) {
+    if (settings.contains("splitter"))
         m_ui->splitter->restoreState(settings.value("splitter").toByteArray());
-    }
+
     settings.endGroup();
 
     onAccountsChanged();
@@ -181,8 +181,10 @@ void Squawk::onJoinConferenceAccepted() {
 void Squawk::closeEvent(QCloseEvent* event) {
     if (accounts != nullptr)
         accounts->close();
+
     if (preferences != nullptr)
         preferences->close();
+
     if (about != nullptr)
         about->close();
 
@@ -221,9 +223,9 @@ void Squawk::stateChanged(Shared::Availability state) {
 void Squawk::onRosterItemDoubleClicked(const QModelIndex& item) {
     if (item.isValid()) {
         Models::Item* node = static_cast<Models::Item*>(item.internalPointer());
-        if (node->type == Models::Item::reference) {
+        if (node->type == Models::Item::reference)
             node = static_cast<Models::Reference*>(node)->dereference();
-        }
+
         Models::Contact* contact = nullptr;
         Models::Room* room = nullptr;
         switch (node->type) {
@@ -258,9 +260,9 @@ void Squawk::onRosterContextMenu(const QPoint& point) {
     QModelIndex index = m_ui->roster->indexAt(point);
     if (index.isValid()) {
         Models::Item* item = static_cast<Models::Item*>(index.internalPointer());
-        if (item->type == Models::Item::reference) {
+        if (item->type == Models::Item::reference)
             item = static_cast<Models::Reference*>(item)->dereference();
-        }
+
         contextMenu->clear();
         bool hasMenu = false;
         bool active = item->getAccountConnectionState() == Shared::ConnectionState::connected;
@@ -320,9 +322,9 @@ void Squawk::onRosterContextMenu(const QPoint& point) {
                     QInputDialog* dialog = new QInputDialog(this);
                     connect(dialog, &QDialog::accepted, [this, dialog, cntName, id]() {
                         QString newName = dialog->textValue();
-                        if (newName != cntName) {
+                        if (newName != cntName)
                             emit renameContactRequest(id.account, id.name, newName);
-                        }
+
                         dialog->deleteLater();
                     });
                     connect(dialog, &QDialog::rejected, dialog, &QObject::deleteLater);
@@ -342,11 +344,10 @@ void Squawk::onRosterContextMenu(const QPoint& point) {
                     gr->setChecked(rosterModel.groupHasContact(id.account, groupName, id.name));
                     gr->setEnabled(active);
                     connect(gr, &QAction::toggled, [this,  groupName, id](bool checked) {
-                        if (checked) {
+                        if (checked)
                             emit addContactToGroupRequest(id.account, id.name, groupName);
-                        } else {
+                        else
                             emit removeContactFromGroupRequest(id.account, id.name, groupName);
-                        }
                     });
                 }
                 QAction* newGroup = groupsMenu->addAction(Shared::icon("group-new"), tr("New group"));
@@ -405,9 +406,8 @@ void Squawk::onRosterContextMenu(const QPoint& point) {
             default:
                 break;
         }
-        if (hasMenu) {
+        if (hasMenu)
             contextMenu->popup(m_ui->roster->viewport()->mapToGlobal(point));
-        }
     }    
 }
 
@@ -515,9 +515,9 @@ void Squawk::onRosterSelectionChanged(const QModelIndex& current, const QModelIn
         }
         
         if (hasContext && QGuiApplication::mouseButtons() & Qt::RightButton) {
-            if (id != nullptr) {
+            if (id != nullptr)
                 delete id;
-            }
+
             needToRestore = true;
             restoreSelection = previous;
             return;
@@ -538,20 +538,19 @@ void Squawk::onRosterSelectionChanged(const QModelIndex& current, const QModelIn
             }
             
             Models::Account* acc = rosterModel.getAccount(id->account);
-            if (contact != nullptr) {
+            if (contact != nullptr)
                 currentConversation = new Chat(acc, contact);
-            } else if (room != nullptr) {
+            else if (room != nullptr)
                 currentConversation = new Room(acc, room);
-            }
-            if (!testAttribute(Qt::WA_TranslucentBackground)) {
+
+            if (!testAttribute(Qt::WA_TranslucentBackground))
                 currentConversation->setFeedFrames(true, false, true, true);
-            }
+
             
             emit openedConversation();
             
-            if (res.size() > 0) {
+            if (res.size() > 0)
                 currentConversation->setPalResource(res);
-            }
             
             m_ui->splitter->insertWidget(1, currentConversation);
             
diff --git a/ui/squawk.h b/ui/squawk.h
index 4e1ca4b..70eae8e 100644
--- a/ui/squawk.h
+++ b/ui/squawk.h
@@ -23,6 +23,7 @@
 #include <QScopedPointer>
 #include <QCloseEvent>
 #include <QSettings>
+#include <QString>
 #include <QInputDialog>
 
 #include <deque>
@@ -50,8 +51,7 @@ class Squawk;
 
 class Application;
 
-class Squawk : public QMainWindow
-{
+class Squawk : public QMainWindow {
     Q_OBJECT
     friend class Application;
 public:
diff --git a/ui/widgets/conversation.h b/ui/widgets/conversation.h
index 80c8c2f..8af1745 100644
--- a/ui/widgets/conversation.h
+++ b/ui/widgets/conversation.h
@@ -46,13 +46,11 @@
 #include "ui/widgets/messageline/feedview.h"
 #include "ui/widgets/messageline/messagedelegate.h"
 
-namespace Ui
-{
+namespace Ui {
 class Conversation;
 }
 
-class KeyEnterReceiver : public QObject
-{
+class KeyEnterReceiver : public QObject {
     Q_OBJECT
 public:
     KeyEnterReceiver(QObject* parent = 0);
@@ -65,8 +63,7 @@ signals:
     void imagePasted();
 };
 
-class Conversation : public QWidget
-{
+class Conversation : public QWidget {
     Q_OBJECT
 public:
     Conversation(bool muc, Models::Account* acc, Models::Element* el, const QString pJid, const QString pRes, QWidget* parent = 0);
@@ -91,6 +88,7 @@ signals:
     void requestLocalFile(const QString& messageId, const QString& url);
     void downloadFile(const QString& messageId, const QString& url);
     void notifyableMessage(const QString& account, const Shared::Message& msg);
+    void setEncryption(Shared::EncryptionProtocol value);
     
 protected:
     virtual void setName(const QString& name);

From 637eb702a895fd163ddead3559cc6db75d425a8b Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Sun, 5 Nov 2023 16:29:44 -0300
Subject: [PATCH 254/281] cant believe it, first ever encrypted messages!

---
 CMakeLists.txt                         |   2 +-
 core/handlers/messagehandler.cpp       | 188 ++++++++++++++++---------
 core/handlers/messagehandler.h         |   8 +-
 shared/message.cpp                     |  17 +++
 shared/message.h                       |   7 +
 ui/widgets/chat.cpp                    |  30 +++-
 ui/widgets/chat.h                      |   2 +
 ui/widgets/conversation.cpp            |   3 +
 ui/widgets/conversation.h              |   1 +
 ui/widgets/messageline/messagefeed.cpp |   1 +
 10 files changed, 190 insertions(+), 69 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 378aa51..1151cc1 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -38,7 +38,7 @@ option(WITH_OMEMO "Build OMEMO support module" ON)
 # Dependencies
 ## Qt
 if (NOT DEFINED QT_VERSION_MAJOR)
-  find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets DBus Gui Xml Network Core)
+  find_package(QT NAMES Qt5 REQUIRED COMPONENTS Widgets DBus Gui Xml Network Core)
 else ()
   find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets DBus Gui Xml Network Core)
 endif()
diff --git a/core/handlers/messagehandler.cpp b/core/handlers/messagehandler.cpp
index 2632848..f939b9e 100644
--- a/core/handlers/messagehandler.cpp
+++ b/core/handlers/messagehandler.cpp
@@ -71,26 +71,12 @@ void Core::MessageHandler::onMessageReceived(const QXmppMessage& msg) {
         case QXmppMessage::GroupChat:
             handled = handleGroupMessage(msg);
             break;
-        case QXmppMessage::Error: {
-            std::tuple<bool, QString, QString> ids = getOriginalPendingMessageId(msg.id());
-            if (std::get<0>(ids)) {
-                QString id = std::get<1>(ids);
-                QString jid = std::get<2>(ids);
-                RosterItem* cnt = acc->rh->getRosterItem(jid);
-                QMap<QString, QVariant> cData = {
-                    {"state", static_cast<uint>(Shared::Message::State::error)},
-                    {"errorText", msg.error().text()}
-                };
-                if (cnt != nullptr)
-                    cnt->changeMessage(id, cData);
-
-                emit acc->changeMessage(jid, id, cData);
-                handled = true;
-            } else {
+        case QXmppMessage::Error:
+            handled = handlePendingMessageError(msg.id(), msg.error().text());
+            if (!handled)
                 qDebug() << "received a message with type \"Error\", not sure what to do with it now, skipping";
-            }
-        }
-        break;
+
+            break;
         case QXmppMessage::Headline:
             qDebug() << "received a message with type \"Headline\", not sure what to do with it now, skipping";
             break;
@@ -99,6 +85,27 @@ void Core::MessageHandler::onMessageReceived(const QXmppMessage& msg) {
         logMessage(msg);
 }
 
+bool Core::MessageHandler::handlePendingMessageError(const QString& id, const QString& errorText) {
+    std::tuple<bool, QString, QString> ids = getOriginalPendingMessageId(id);
+    if (std::get<0>(ids)) {
+        QString id = std::get<1>(ids);
+        QString jid = std::get<2>(ids);
+        RosterItem* ri = acc->rh->getRosterItem(jid);
+        QMap<QString, QVariant> cData = {
+            {"state", static_cast<uint>(Shared::Message::State::error)},
+            {"errorText", errorText}
+        };
+        if (ri != nullptr)
+            ri->changeMessage(id, cData);
+
+        emit acc->changeMessage(jid, id, cData);
+        return true;
+    }
+
+    return false;
+}
+
+
 bool Core::MessageHandler::handleChatMessage(const QXmppMessage& msg, bool outgoing, bool forwarded, bool guessing) {
     if (msg.body().size() != 0 || msg.outOfBandUrl().size() > 0) {
         Shared::Message sMsg(Shared::Message::chat);
@@ -182,9 +189,9 @@ void Core::MessageHandler::initializeMessage(Shared::Message& target, const QXmp
     QString id;
 #if (QXMPP_VERSION) >= QT_VERSION_CHECK(1, 3, 0)
     id = source.originId();
-    if (id.size() == 0) {
+    if (id.size() == 0)
         id = source.id();
-    }
+
     target.setStanzaId(source.stanzaId());
     qDebug() << "initializing message with originId:" << source.originId() << ", id:" << source.id() << ", stansaId:" << source.stanzaId();
 #else
@@ -202,24 +209,19 @@ void Core::MessageHandler::initializeMessage(Shared::Message& target, const QXmp
     target.setBody(source.body());
     target.setForwarded(forwarded);
     
-    if (guessing) {
-        if (target.getFromJid() == acc->getBareJid()) {
-            outgoing = true;
-        } else {
-            outgoing = false;
-        }
-    }
+    if (guessing)
+        outgoing = target.getFromJid() == acc->getBareJid();
+
     target.setOutgoing(outgoing);
-    if (time.isValid()) {
+    if (time.isValid())
         target.setTime(time);
-    } else {
+    else
         target.setCurrentTime();
-    }
-    
+
     QString oob = source.outOfBandUrl();
-    if (oob.size() > 0) {
+    if (oob.size() > 0)
         target.setAttachPath(acc->network->addMessageAndCheckForPath(oob, acc->getName(), target.getPenPalJid(), messageId));
-    }
+
     target.setOutOfBandUrl(oob);
 }
 
@@ -249,7 +251,7 @@ void Core::MessageHandler::onCarbonMessageSent(const QXmppMessage& msg) {
 }
 #endif
 
-std::tuple<bool, QString, QString> Core::MessageHandler::getOriginalPendingMessageId(const QString& id) {
+std::tuple<bool, QString, QString> Core::MessageHandler::getOriginalPendingMessageId(const QString& id, bool clear) {
     std::tuple<bool, QString, QString> result({false, "", ""});
     std::map<QString, QString>::const_iterator itr = pendingStateMessages.find(id);
     if (itr != pendingStateMessages.end()) {
@@ -263,12 +265,14 @@ std::tuple<bool, QString, QString> Core::MessageHandler::getOriginalPendingMessa
             else
                 std::get<1>(result) = itr->first;
 
-            pendingCorrectionMessages.erase(itrC);
+            if (clear)
+                pendingCorrectionMessages.erase(itrC);
         } else {
             std::get<1>(result) = itr->first;
         }
 
-        pendingStateMessages.erase(itr);
+        if (clear)
+            pendingStateMessages.erase(itr);
     }
 
     return result;
@@ -281,9 +285,9 @@ void Core::MessageHandler::onReceiptReceived(const QString& jid, const QString&
         QMap<QString, QVariant> cData = {{"state", static_cast<uint>(Shared::Message::State::delivered)}};
         RosterItem* ri = acc->rh->getRosterItem(std::get<2>(ids));
 
-        if (ri != nullptr) {
+        if (ri != nullptr)
             ri->changeMessage(std::get<1>(ids), cData);
-        }
+
         emit acc->changeMessage(std::get<2>(ids), std::get<1>(ids), cData);
     }
 }
@@ -302,45 +306,25 @@ void Core::MessageHandler::performSending(Shared::Message data, const QString& o
     QString id = data.getId();
     qDebug() << "Sending message with id:" << id;
     if (originalId.size() > 0)
-        qDebug() << "To replace one with id:" << originalId;
+        qDebug() << "To replace the one with id:" << originalId;
 
     RosterItem* ri = acc->rh->getRosterItem(jid);
-    bool sent = false;
     if (newMessage && originalId.size() > 0)
         newMessage = false;
 
     QDateTime sendTime = QDateTime::currentDateTimeUtc();
-    if (acc->state == Shared::ConnectionState::connected) {
-        QXmppMessage msg(createPacket(data, sendTime, originalId));
-        
-        sent = acc->client.sendPacket(msg);
-        if (sent) {
-            data.setState(Shared::Message::State::sent);
-        } else {
-            data.setState(Shared::Message::State::error);
-            data.setErrorText("Couldn't send message: internal QXMPP library error, probably need to check out the logs");
-        }
-        
-    } else {
-        data.setState(Shared::Message::State::error);
-        data.setErrorText("You are is offline or reconnecting");
-    }
+    std::pair<Shared::Message::State, QString> result = scheduleSending(data, sendTime, originalId);
+    data.setState(result.first);
+    data.setErrorText(result.second);
     
     QMap<QString, QVariant> changes(getChanges(data, sendTime, newMessage, originalId));
-    
-    QString realId;
-    if (originalId.size() > 0)
-        realId = originalId;
-    else
-        realId = id;
-
     if (ri != nullptr) {
         if (newMessage)
             ri->appendMessageToArchive(data);
          else
-            ri->changeMessage(realId, changes);
+            ri->changeMessage(originalId.isEmpty() ? id : originalId, changes);
 
-        if (sent) {
+        if (data.getState() != Shared::Message::State::error) {
             pendingStateMessages.insert(std::make_pair(id, jid));
             if (originalId.size() > 0)
                 pendingCorrectionMessages.insert(std::make_pair(id, originalId));
@@ -350,9 +334,81 @@ void Core::MessageHandler::performSending(Shared::Message data, const QString& o
         }
     }
     
-    emit acc->changeMessage(jid, realId, changes);
+    emit acc->changeMessage(jid, originalId.isEmpty() ? id : originalId, changes);
 }
 
+std::pair<Shared::Message::State, QString> Core::MessageHandler::scheduleSending(
+    const Shared::Message& message,
+    const QDateTime& sendTime,
+    const QString& originalId
+) {
+    if (acc->state != Shared::ConnectionState::connected)
+        return {Shared::Message::State::error, "You are is offline or reconnecting"};
+
+    QXmppMessage msg = createPacket(message, sendTime, originalId);
+    QString id = msg.id();
+#ifdef WITH_OMEMO
+    if (message.getEncryption() == Shared::EncryptionProtocol::omemo2) {
+        QXmppTask<QXmppE2eeExtension::MessageEncryptResult> task = acc->om->encryptMessage(std::move(msg), std::nullopt);
+        if (task.isFinished()) {
+            const QXmppE2eeExtension::MessageEncryptResult& res = task.result();
+            if (std::holds_alternative<std::unique_ptr<QXmppMessage>>(res)) {
+                const std::unique_ptr<QXmppMessage>& encrypted = std::get<std::unique_ptr<QXmppMessage>>(res);
+                bool success = acc->client.sendPacket(*encrypted.get());
+                if (success)
+                    return {Shared::Message::State::sent, ""};
+                else
+                    return {Shared::Message::State::error, "Error sending successfully encrypted message"};
+            } else if (std::holds_alternative<QXmppError>(res)) {
+                const QXmppError& err = std::get<QXmppError>(res);
+                return {Shared::Message::State::error, err.description};
+            } else {
+                return {Shared::Message::State::error, "Unexpected error ecryptng the message"};
+            }
+        } else {
+            task.then(this, [this, id] (QXmppE2eeExtension::MessageEncryptResult&& result) {
+                if (std::holds_alternative<std::unique_ptr<QXmppMessage>>(result)) {
+                    const std::unique_ptr<QXmppMessage>& encrypted = std::get<std::unique_ptr<QXmppMessage>>(result);
+                    encrypted->setBody("This message is encrypted with OMEMO 2 but could not be decrypted");
+                    bool success = acc->client.sendPacket(*encrypted.get());
+                    if (success) {
+                        std::tuple<bool, QString, QString> ids = getOriginalPendingMessageId(id, false);
+                        if (std::get<0>(ids)) {
+                            QString id = std::get<1>(ids);
+                            QString jid = std::get<2>(ids);
+                            RosterItem* ri = acc->rh->getRosterItem(jid);
+                            QMap<QString, QVariant> cData = {{"state", static_cast<uint>(Shared::Message::State::sent)}};
+                            if (ri != nullptr)
+                                ri->changeMessage(id, cData);
+
+                            emit acc->changeMessage(jid, id, cData);
+                        } else {
+                            qDebug() << "Encrypted message has been successfully sent, but it couldn't be found to update the sate";
+                        }
+                    } else {
+                        handlePendingMessageError(id, "Error sending successfully encrypted message");
+                    }
+                } else if (std::holds_alternative<QXmppError>(result)) {
+                    const QXmppError& err = std::get<QXmppError>(result);
+                    handlePendingMessageError(id, err.description);
+                } else {
+                    handlePendingMessageError(id, "Unexpected error ecryptng the message");
+                }
+            });
+            return {Shared::Message::State::pending, ""};
+        }
+    } else
+#endif
+    {
+        bool success = acc->client.sendPacket(msg);
+        if (success)
+            return {Shared::Message::State::sent, ""};
+        else
+            return {Shared::Message::State::error, "Error sending message, internal QXMPP error"};
+    }
+}
+
+
 QMap<QString, QVariant> Core::MessageHandler::getChanges(Shared::Message& data, const QDateTime& time, bool newMessage, const QString& originalId) const {
     QMap<QString, QVariant> changes;
 
diff --git a/core/handlers/messagehandler.h b/core/handlers/messagehandler.h
index a08fee8..99a77d3 100644
--- a/core/handlers/messagehandler.h
+++ b/core/handlers/messagehandler.h
@@ -23,9 +23,13 @@
 
 #include <deque>
 #include <map>
+#include <functional>
 
 #include <QXmppMessage.h>
 #include <QXmppHttpUploadIq.h>
+#ifdef WITH_OMEMO
+    #include <QXmppE2eeExtension.h>
+#endif
 
 #include <shared/message.h>
 #include <shared/messageinfo.h>
@@ -74,7 +78,9 @@ private:
     void handleUploadError(const QString& jid, const QString& messageId, const QString& errorText);
     QXmppMessage createPacket(const Shared::Message& data, const QDateTime& time, const QString& originalId) const;
     QMap<QString, QVariant> getChanges(Shared::Message& data, const QDateTime& time, bool newMessage, const QString& originalId) const;
-    std::tuple<bool, QString, QString> getOriginalPendingMessageId(const QString& id);
+    std::tuple<bool, QString, QString> getOriginalPendingMessageId(const QString& id, bool clear = true);
+    bool handlePendingMessageError(const QString& id, const QString& errorText);
+    std::pair<Shared::Message::State, QString> scheduleSending(const Shared::Message& message, const QDateTime& sendTime, const QString& originalId);
     
 private:
     Account* acc;
diff --git a/shared/message.cpp b/shared/message.cpp
index dab03a3..5b535c1 100644
--- a/shared/message.cpp
+++ b/shared/message.cpp
@@ -247,6 +247,19 @@ void Shared::Message::setEdited(bool p_edited) {
     edited = p_edited;
 }
 
+Shared::EncryptionProtocol Shared::Message::getEncryption() const {
+    return encryption;
+}
+
+void Shared::Message::setEncryption(EncryptionProtocol encryption) {
+    Shared::Message::encryption = encryption;
+}
+
+void Shared::Message::setError(const QString& text) {
+    state = State::error;
+    errorText = text;
+}
+
 QDataStream& operator<<(QDataStream& out, const Shared::Message& info) {
     out << info.jFrom;
     out << info.rFrom;
@@ -271,6 +284,7 @@ QDataStream& operator<<(QDataStream& out, const Shared::Message& info) {
     }
     out << info.stanzaId;
     out << info.attachPath;
+    out << (quint8)info.encryption;
 
     return out;
 }
@@ -303,6 +317,9 @@ QDataStream & operator>>(QDataStream& in, Shared::Message& info) {
     }
     in >> info.stanzaId;
     in >> info.attachPath;
+    quint8 e;
+    in >> e;
+    info.encryption = static_cast<Shared::EncryptionProtocol>(e);
 
     return in;
 }
diff --git a/shared/message.h b/shared/message.h
index 557b1b3..d7df9f3 100644
--- a/shared/message.h
+++ b/shared/message.h
@@ -25,6 +25,8 @@
 #include <QMap>
 #include <QDataStream>
 
+#include "enums.h"
+
 namespace Shared {
     class Message;
 }
@@ -94,6 +96,9 @@ public:
     bool change(const QMap<QString, QVariant>& data);
     void setStanzaId(const QString& sid);
     void setAttachPath(const QString& path);
+    void setEncryption(EncryptionProtocol encryption);
+
+    void setError(const QString& text);
     
     QString getFrom() const;
     QString getFromJid() const;
@@ -123,6 +128,7 @@ public:
     QString getOriginalBody() const;
     QString getStanzaId() const;
     QString getAttachPath() const;
+    EncryptionProtocol getEncryption() const;
     
 private:
     QString jFrom;
@@ -144,6 +150,7 @@ private:
     QDateTime lastModified;
     QString stanzaId;
     QString attachPath;
+    EncryptionProtocol encryption;
 };
 
 }
diff --git a/ui/widgets/chat.cpp b/ui/widgets/chat.cpp
index 3b9f090..628f30f 100644
--- a/ui/widgets/chat.cpp
+++ b/ui/widgets/chat.cpp
@@ -31,10 +31,12 @@ Chat::Chat(Models::Account* acc, Models::Contact* p_contact, QWidget* parent):
     setAvatar(p_contact->getAvatarPath());
     
     connect(contact, &Models::Contact::childChanged, this, &Chat::onContactChanged);
+#ifdef WITH_OMEMO
     if (p_contact->hasKeys(Shared::EncryptionProtocol::omemo2)) {
         m_ui->encryptionButton->setVisible(true);
-        //if ()
+        updateEncryptionState();
     }
+#endif
 }
 
 Chat::~Chat()
@@ -56,9 +58,14 @@ void Chat::onContactChanged(Models::Item* item, int row, int col) {
             case 7:
                 setAvatar(contact->getAvatarPath());
                 break;
+#ifdef WITH_OMEMO
             case 8:
                 m_ui->encryptionButton->setVisible(contact->hasKeys(Shared::EncryptionProtocol::omemo2));
                 break;
+            case 9:
+                updateEncryptionState();
+                break;
+#endif
         }
     }
 }
@@ -69,12 +76,25 @@ void Chat::updateState() {
     statusIcon->setToolTip(Shared::Global::getName(av));
 }
 
+void Chat::updateEncryptionState() {
+    m_ui->encryptionButton->setEnabled(true);
+    if (contact->getEncryption() == Shared::EncryptionProtocol::omemo2)
+        m_ui->encryptionButton->setIcon(Shared::icon("lock"));
+    else
+        m_ui->encryptionButton->setIcon(Shared::icon("unlock"));
+}
+
+
 Shared::Message Chat::createMessage() const {
     Shared::Message msg = Conversation::createMessage();
     msg.setType(Shared::Message::chat);
     msg.setFrom(account->getFullJid());
     msg.setToJid(palJid);
     msg.setToResource(activePalResource);
+#ifdef WITH_OMEMO
+    if (contact->getEncryption() == Shared::EncryptionProtocol::omemo2)
+        msg.setEncryption(Shared::EncryptionProtocol::omemo2);
+#endif
     return msg;
 }
 
@@ -87,3 +107,11 @@ void Chat::onMessage(const Shared::Message& data){
             setPalResource(res);
     }
 }
+
+void Chat::onEncryptionButtonClicked() {
+    m_ui->encryptionButton->setEnabled(false);
+    if (contact->getEncryption() == Shared::EncryptionProtocol::omemo2)
+        emit setEncryption(Shared::EncryptionProtocol::none);
+    else
+        emit setEncryption(Shared::EncryptionProtocol::omemo2);
+}
diff --git a/ui/widgets/chat.h b/ui/widgets/chat.h
index 78e6bec..cf7ecd3 100644
--- a/ui/widgets/chat.h
+++ b/ui/widgets/chat.h
@@ -37,6 +37,7 @@ public:
 
 protected slots:
     void onContactChanged(Models::Item* item, int row, int col);
+    void onEncryptionButtonClicked() override;
     
 protected:
     Shared::Message createMessage() const override;
@@ -44,6 +45,7 @@ protected:
     
 private:
     void updateState();
+    void updateEncryptionState();
     
 private:
     Models::Contact* contact;
diff --git a/ui/widgets/conversation.cpp b/ui/widgets/conversation.cpp
index e6316c7..8336d5e 100644
--- a/ui/widgets/conversation.cpp
+++ b/ui/widgets/conversation.cpp
@@ -91,6 +91,7 @@ Conversation::Conversation(bool muc, Models::Account* acc, Models::Element* el,
     connect(m_ui->clearButton, &QPushButton::clicked, this, &Conversation::clear);
     connect(m_ui->messageEditor->document()->documentLayout(), &QAbstractTextDocumentLayout::documentSizeChanged, 
             this, &Conversation::onTextEditDocSizeChanged);
+    connect(m_ui->encryptionButton, &QPushButton::clicked, this, &Conversation::onEncryptionButtonClicked);
     
     m_ui->messageEditor->installEventFilter(&ker);
 
@@ -345,6 +346,8 @@ void Conversation::clear() {
     m_ui->messageEditor->clear();
 }
 
+void Conversation::onEncryptionButtonClicked() {}
+
 void Conversation::setAvatar(const QString& path) {
     QPixmap pixmap;
     if (path.size() == 0) {
diff --git a/ui/widgets/conversation.h b/ui/widgets/conversation.h
index 8af1745..4db5029 100644
--- a/ui/widgets/conversation.h
+++ b/ui/widgets/conversation.h
@@ -119,6 +119,7 @@ protected slots:
     void onFeedContext(const QPoint &pos);
     void onMessageEditorContext(const QPoint &pos);
     void onMessageEditRequested(const QString& id);
+    virtual void onEncryptionButtonClicked();
     
 public:
     const bool isMuc;
diff --git a/ui/widgets/messageline/messagefeed.cpp b/ui/widgets/messageline/messagefeed.cpp
index 1eba1fa..29f51d2 100644
--- a/ui/widgets/messageline/messagefeed.cpp
+++ b/ui/widgets/messageline/messagefeed.cpp
@@ -230,6 +230,7 @@ std::set<Models::MessageFeed::MessageRoles> Models::MessageFeed::detectChanges(c
 }
 
 void Models::MessageFeed::removeMessage(const QString& id) {
+    SHARED_UNUSED(id);
     //todo;
 }
 

From 0a530bfa935ebda733a9c9d4cd965f44e365e26c Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Mon, 6 Nov 2023 20:57:08 -0300
Subject: [PATCH 255/281] encrypted messages now are displayed in the feed

---
 CMakeLists.txt                                |   4 +-
 core/handlers/messagehandler.cpp              |   9 +-
 main/main.cpp                                 |   3 +-
 resources/images/fallback/dark/big/lock.svg   |  13 ++
 resources/images/fallback/dark/big/shield.svg |  13 ++
 resources/images/fallback/dark/big/unlock.svg |  14 ++
 resources/images/fallback/dark/small/lock.svg |  13 ++
 .../images/fallback/dark/small/shield.svg     |  13 ++
 .../images/fallback/dark/small/unlock.svg     |  13 ++
 resources/images/fallback/light/big/lock.svg  |  13 ++
 .../images/fallback/light/big/shield.svg      |  13 ++
 .../images/fallback/light/big/unlock.svg      |  14 ++
 .../images/fallback/light/small/lock.svg      |  13 ++
 .../images/fallback/light/small/shield.svg    |  13 ++
 .../images/fallback/light/small/unlock.svg    |  13 ++
 resources/resources.qrc                       |  12 +
 shared/enums.h                                |   4 +-
 shared/exception.h                            |   5 +-
 shared/global.cpp                             | 104 +++++----
 shared/global.h                               |  10 +-
 shared/icons.h                                |  10 +-
 shared/message.cpp                            |   9 +-
 shared/message.h                              |   5 +-
 shared/pathcheck.h                            |   5 +-
 shared/utils.h                                |   5 +-
 ui/widgets/chat.h                             |   7 +-
 ui/widgets/conversation.cpp                   |   1 +
 ui/widgets/conversation.h                     |   3 +-
 ui/widgets/messageline/CMakeLists.txt         |   1 -
 ui/widgets/messageline/messagedelegate.cpp    | 212 ++++++++++--------
 ui/widgets/messageline/messagedelegate.h      |  50 ++---
 ui/widgets/messageline/messagefeed.cpp        |  54 +++--
 ui/widgets/messageline/messagefeed.h          |   8 +-
 ui/widgets/messageline/preview.h              |   5 +-
 34 files changed, 439 insertions(+), 245 deletions(-)
 create mode 100644 resources/images/fallback/dark/big/lock.svg
 create mode 100644 resources/images/fallback/dark/big/shield.svg
 create mode 100644 resources/images/fallback/dark/big/unlock.svg
 create mode 100644 resources/images/fallback/dark/small/lock.svg
 create mode 100644 resources/images/fallback/dark/small/shield.svg
 create mode 100644 resources/images/fallback/dark/small/unlock.svg
 create mode 100644 resources/images/fallback/light/big/lock.svg
 create mode 100644 resources/images/fallback/light/big/shield.svg
 create mode 100644 resources/images/fallback/light/big/unlock.svg
 create mode 100644 resources/images/fallback/light/small/lock.svg
 create mode 100644 resources/images/fallback/light/small/shield.svg
 create mode 100644 resources/images/fallback/light/small/unlock.svg

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 1151cc1..552cba9 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -38,9 +38,9 @@ option(WITH_OMEMO "Build OMEMO support module" ON)
 # Dependencies
 ## Qt
 if (NOT DEFINED QT_VERSION_MAJOR)
-  find_package(QT NAMES Qt5 REQUIRED COMPONENTS Widgets DBus Gui Xml Network Core)
+  find_package(QT NAMES Qt6 Qt5 CONFIG REQUIRED COMPONENTS Widgets DBus Gui Xml Network Core)
 else ()
-  find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets DBus Gui Xml Network Core)
+  find_package(Qt${QT_VERSION_MAJOR} CONFIG REQUIRED COMPONENTS Widgets DBus Gui Xml Network Core)
 endif()
 
 find_package(Boost COMPONENTS)
diff --git a/core/handlers/messagehandler.cpp b/core/handlers/messagehandler.cpp
index f939b9e..2a546e9 100644
--- a/core/handlers/messagehandler.cpp
+++ b/core/handlers/messagehandler.cpp
@@ -117,9 +117,8 @@ bool Core::MessageHandler::handleChatMessage(const QXmppMessage& msg, bool outgo
             qDebug() << "appending message" << sMsg.getId() << "to an out of roster contact";
         }
         if (sMsg.getOutgoing()) {
-            if (sMsg.getForwarded()) {
+            if (sMsg.getForwarded())
                 sMsg.setState(Shared::Message::State::sent);
-            }
         } else {
             sMsg.setState(Shared::Message::State::delivered);
         }
@@ -208,6 +207,12 @@ void Core::MessageHandler::initializeMessage(Shared::Message& target, const QXmp
     target.setTo(source.to());
     target.setBody(source.body());
     target.setForwarded(forwarded);
+#ifdef WITH_OMEMO
+    #if (QXMPP_VERSION) >= QT_VERSION_CHECK(1, 5, 0)
+    if (source.encryptionMethod() == QXmpp::EncryptionMethod::Omemo2)
+        target.setEncryption(Shared::EncryptionProtocol::omemo2);
+    #endif
+#endif
     
     if (guessing)
         outgoing = target.getFromJid() == acc->getBareJid();
diff --git a/main/main.cpp b/main/main.cpp
index c8e962f..06c2c95 100644
--- a/main/main.cpp
+++ b/main/main.cpp
@@ -27,8 +27,7 @@
 #include <Omemo/QXmppOmemoStorage.h>
 #endif
 
-int main(int argc, char *argv[])
-{
+int main(int argc, char *argv[]) {
     qRegisterMetaType<Shared::Message>("Shared::Message");
     qRegisterMetaType<Shared::MessageInfo>("Shared::MessageInfo");
     qRegisterMetaType<Shared::VCard>("Shared::VCard");
diff --git a/resources/images/fallback/dark/big/lock.svg b/resources/images/fallback/dark/big/lock.svg
new file mode 100644
index 0000000..d7835b3
--- /dev/null
+++ b/resources/images/fallback/dark/big/lock.svg
@@ -0,0 +1,13 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
+  <defs id="defs3051">
+    <style type="text/css" id="current-color-scheme">
+      .ColorScheme-Text {
+        color:#232629;
+      }
+      </style>
+  </defs>
+ <path style="fill:currentColor;fill-opacity:1;stroke:none" 
+     d="M 11,3 C 8.784,3 7,4.784 7,7 l 0,4 -2,0 c 0,2.666667 0,5.333333 0,8 4,0 8,0 12,0 l 0,-8 c -0.666667,0 -1.333333,0 -2,0 L 15,7 C 15,4.784 13.216,3 11,3 m 0,1 c 1.662,0 3,1.561 3,3.5 L 14,11 8,11 8,7.5 C 8,5.561 9.338,4 11,4"
+     class="ColorScheme-Text"
+     />
+</svg>
diff --git a/resources/images/fallback/dark/big/shield.svg b/resources/images/fallback/dark/big/shield.svg
new file mode 100644
index 0000000..c63b275
--- /dev/null
+++ b/resources/images/fallback/dark/big/shield.svg
@@ -0,0 +1,13 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
+  <defs id="defs3051">
+    <style type="text/css" id="current-color-scheme">
+      .ColorScheme-Text {
+        color:#232629;
+      }
+      </style>
+  </defs>
+    <path 
+        style="fill:currentColor;fill-opacity:1;stroke:none" 
+          d="M 8,2 C 8,2 6.5,3.9931391 2,4.4931641 2,4.4931641 2,11.493575 8,14 14,11.493575 14,4.4931641 14,4.4931641 9.5,3.9931391 8,2 8,2 Z m 0,1.0327148 c 1.1902463,1.008525 2.90787,1.6813196 5.134277,2.0200196 C 13.013333,6.1366343 12.897371,6.9523225 12.617188,7.7407227 12.02837,9.3975477 11.341831,10.405496 10.726074,11.130371 9.7719035,12.253646 8.905394,12.708244 8,13.160644 7.094606,12.708244 6.2280961,12.253646 5.2739258,11.130371 4.658169,10.405496 3.97163,9.3975477 3.3828125,7.7407227 3.102629,6.9523225 2.9866669,6.1366343 2.8657227,5.0527344 5.0921299,4.7140344 6.8097538,4.0412398 8,3.0327148 Z M 8,3.9321289 C 6.6923817,4.8398539 5.2233869,5.2995548 3.7490234,5.6123046 4.4471579,9.5738045 5.9510862,11.267813 8,12.328613 10.048914,11.267813 11.552843,9.5738045 12.250977,5.6123046 10.776613,5.2995547 9.3076183,4.8398539 8,3.9321289 Z"
+          class="ColorScheme-Text"/>
+</svg>
diff --git a/resources/images/fallback/dark/big/unlock.svg b/resources/images/fallback/dark/big/unlock.svg
new file mode 100644
index 0000000..a5e4a4c
--- /dev/null
+++ b/resources/images/fallback/dark/big/unlock.svg
@@ -0,0 +1,14 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
+  <defs id="defs3051">
+    <style type="text/css" id="current-color-scheme">
+      .ColorScheme-Text {
+        color:#232629;
+      }
+      </style>
+  </defs>
+ <path 
+    style="fill:currentColor;fill-opacity:1;stroke:none" 
+   d="m11 3c-2.216 0-4 1.784-4 4v1h1v-.5c0-1.939 1.338-3.5 3-3.5 1.662 0 3 1.561 3 3.5v3.5h-5-1-1-1-1v1 7h1 10 1v-8h-1-1v-4c0-2.216-1.784-4-4-4m-5 9h10v6h-10v-6"
+    class="ColorScheme-Text"
+    />
+</svg>
diff --git a/resources/images/fallback/dark/small/lock.svg b/resources/images/fallback/dark/small/lock.svg
new file mode 100644
index 0000000..6d36522
--- /dev/null
+++ b/resources/images/fallback/dark/small/lock.svg
@@ -0,0 +1,13 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
+  <defs id="defs3051">
+    <style type="text/css" id="current-color-scheme">
+      .ColorScheme-Text {
+        color:#232629;
+      }
+      </style>
+  </defs>
+ <path style="fill:currentColor;fill-opacity:1;stroke:none" 
+     d="M 8,2 C 6.3431375,2 5,3.3431372 5,5 l 0,3 -2,0 0,6 10,0 0,-6 -2,0 0,-3 C 11,3.3431372 9.6568625,2 8,2 Z m 0,1 c 1.1045695,0 2,0.8954305 2,2 L 10,8 6,8 6,5 C 6,3.8954305 6.8954305,3 8,3 Z"
+     class="ColorScheme-Text"
+     />
+</svg>
diff --git a/resources/images/fallback/dark/small/shield.svg b/resources/images/fallback/dark/small/shield.svg
new file mode 100644
index 0000000..c63b275
--- /dev/null
+++ b/resources/images/fallback/dark/small/shield.svg
@@ -0,0 +1,13 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
+  <defs id="defs3051">
+    <style type="text/css" id="current-color-scheme">
+      .ColorScheme-Text {
+        color:#232629;
+      }
+      </style>
+  </defs>
+    <path 
+        style="fill:currentColor;fill-opacity:1;stroke:none" 
+          d="M 8,2 C 8,2 6.5,3.9931391 2,4.4931641 2,4.4931641 2,11.493575 8,14 14,11.493575 14,4.4931641 14,4.4931641 9.5,3.9931391 8,2 8,2 Z m 0,1.0327148 c 1.1902463,1.008525 2.90787,1.6813196 5.134277,2.0200196 C 13.013333,6.1366343 12.897371,6.9523225 12.617188,7.7407227 12.02837,9.3975477 11.341831,10.405496 10.726074,11.130371 9.7719035,12.253646 8.905394,12.708244 8,13.160644 7.094606,12.708244 6.2280961,12.253646 5.2739258,11.130371 4.658169,10.405496 3.97163,9.3975477 3.3828125,7.7407227 3.102629,6.9523225 2.9866669,6.1366343 2.8657227,5.0527344 5.0921299,4.7140344 6.8097538,4.0412398 8,3.0327148 Z M 8,3.9321289 C 6.6923817,4.8398539 5.2233869,5.2995548 3.7490234,5.6123046 4.4471579,9.5738045 5.9510862,11.267813 8,12.328613 10.048914,11.267813 11.552843,9.5738045 12.250977,5.6123046 10.776613,5.2995547 9.3076183,4.8398539 8,3.9321289 Z"
+          class="ColorScheme-Text"/>
+</svg>
diff --git a/resources/images/fallback/dark/small/unlock.svg b/resources/images/fallback/dark/small/unlock.svg
new file mode 100644
index 0000000..9f2f121
--- /dev/null
+++ b/resources/images/fallback/dark/small/unlock.svg
@@ -0,0 +1,13 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
+  <defs id="defs3051">
+    <style type="text/css" id="current-color-scheme">
+      .ColorScheme-Text {
+        color:#232629;
+      }
+      </style>
+  </defs>
+ <path style="fill:currentColor;fill-opacity:1;stroke:none" 
+     d="M 8 2 C 6.3431375 2 5 3.3431371 5 5 L 5 6 L 6 6 L 6 5 C 6 3.8954305 6.8954305 3 8 3 C 9.1045695 3 10 3.8954305 10 5 L 10 8 L 7 8 L 6 8 L 5 8 L 3 8 L 3 14 L 13 14 L 13 8 L 11 8 L 11 5 C 11 3.3431371 9.6568625 2 8 2 z M 4 9 L 12 9 L 12 13 L 4 13 L 4 9 z "
+     class="ColorScheme-Text"
+     />
+</svg>
diff --git a/resources/images/fallback/light/big/lock.svg b/resources/images/fallback/light/big/lock.svg
new file mode 100644
index 0000000..5c7f4e1
--- /dev/null
+++ b/resources/images/fallback/light/big/lock.svg
@@ -0,0 +1,13 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
+  <defs id="defs3051">
+    <style type="text/css" id="current-color-scheme">
+      .ColorScheme-Text {
+        color:#eff0f1;
+      }
+      </style>
+  </defs>
+ <path style="fill:currentColor;fill-opacity:1;stroke:none" 
+     d="M 11,3 C 8.784,3 7,4.784 7,7 l 0,4 -2,0 c 0,2.666667 0,5.333333 0,8 4,0 8,0 12,0 l 0,-8 c -0.666667,0 -1.333333,0 -2,0 L 15,7 C 15,4.784 13.216,3 11,3 m 0,1 c 1.662,0 3,1.561 3,3.5 L 14,11 8,11 8,7.5 C 8,5.561 9.338,4 11,4"
+     class="ColorScheme-Text"
+     />
+</svg>
diff --git a/resources/images/fallback/light/big/shield.svg b/resources/images/fallback/light/big/shield.svg
new file mode 100644
index 0000000..52f9c34
--- /dev/null
+++ b/resources/images/fallback/light/big/shield.svg
@@ -0,0 +1,13 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
+  <defs id="defs3051">
+    <style type="text/css" id="current-color-scheme">
+      .ColorScheme-Text {
+        color:#eff0f1;
+      }
+      </style>
+  </defs>
+    <path 
+        style="fill:currentColor;fill-opacity:1;stroke:none" 
+          d="M 8,2 C 8,2 6.5,3.9931391 2,4.4931641 2,4.4931641 2,11.493575 8,14 14,11.493575 14,4.4931641 14,4.4931641 9.5,3.9931391 8,2 8,2 Z m 0,1.0327148 c 1.1902463,1.008525 2.90787,1.6813196 5.134277,2.0200196 C 13.013333,6.1366343 12.897371,6.9523225 12.617188,7.7407227 12.02837,9.3975477 11.341831,10.405496 10.726074,11.130371 9.7719035,12.253646 8.905394,12.708244 8,13.160644 7.094606,12.708244 6.2280961,12.253646 5.2739258,11.130371 4.658169,10.405496 3.97163,9.3975477 3.3828125,7.7407227 3.102629,6.9523225 2.9866669,6.1366343 2.8657227,5.0527344 5.0921299,4.7140344 6.8097538,4.0412398 8,3.0327148 Z M 8,3.9321289 C 6.6923817,4.8398539 5.2233869,5.2995548 3.7490234,5.6123046 4.4471579,9.5738045 5.9510862,11.267813 8,12.328613 10.048914,11.267813 11.552843,9.5738045 12.250977,5.6123046 10.776613,5.2995547 9.3076183,4.8398539 8,3.9321289 Z"
+          class="ColorScheme-Text"/>
+</svg>
diff --git a/resources/images/fallback/light/big/unlock.svg b/resources/images/fallback/light/big/unlock.svg
new file mode 100644
index 0000000..0548efc
--- /dev/null
+++ b/resources/images/fallback/light/big/unlock.svg
@@ -0,0 +1,14 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
+  <defs id="defs3051">
+    <style type="text/css" id="current-color-scheme">
+      .ColorScheme-Text {
+        color:#eff0f1;
+      }
+      </style>
+  </defs>
+ <path 
+    style="fill:currentColor;fill-opacity:1;stroke:none" 
+   d="m11 3c-2.216 0-4 1.784-4 4v1h1v-.5c0-1.939 1.338-3.5 3-3.5 1.662 0 3 1.561 3 3.5v3.5h-5-1-1-1-1v1 7h1 10 1v-8h-1-1v-4c0-2.216-1.784-4-4-4m-5 9h10v6h-10v-6"
+    class="ColorScheme-Text"
+    />
+</svg>
diff --git a/resources/images/fallback/light/small/lock.svg b/resources/images/fallback/light/small/lock.svg
new file mode 100644
index 0000000..a566da0
--- /dev/null
+++ b/resources/images/fallback/light/small/lock.svg
@@ -0,0 +1,13 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
+  <defs id="defs3051">
+    <style type="text/css" id="current-color-scheme">
+      .ColorScheme-Text {
+        color:#eff0f1;
+      }
+      </style>
+  </defs>
+ <path style="fill:currentColor;fill-opacity:1;stroke:none" 
+     d="M 8,2 C 6.3431375,2 5,3.3431372 5,5 l 0,3 -2,0 0,6 10,0 0,-6 -2,0 0,-3 C 11,3.3431372 9.6568625,2 8,2 Z m 0,1 c 1.1045695,0 2,0.8954305 2,2 L 10,8 6,8 6,5 C 6,3.8954305 6.8954305,3 8,3 Z"
+     class="ColorScheme-Text"
+     />
+</svg>
diff --git a/resources/images/fallback/light/small/shield.svg b/resources/images/fallback/light/small/shield.svg
new file mode 100644
index 0000000..52f9c34
--- /dev/null
+++ b/resources/images/fallback/light/small/shield.svg
@@ -0,0 +1,13 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
+  <defs id="defs3051">
+    <style type="text/css" id="current-color-scheme">
+      .ColorScheme-Text {
+        color:#eff0f1;
+      }
+      </style>
+  </defs>
+    <path 
+        style="fill:currentColor;fill-opacity:1;stroke:none" 
+          d="M 8,2 C 8,2 6.5,3.9931391 2,4.4931641 2,4.4931641 2,11.493575 8,14 14,11.493575 14,4.4931641 14,4.4931641 9.5,3.9931391 8,2 8,2 Z m 0,1.0327148 c 1.1902463,1.008525 2.90787,1.6813196 5.134277,2.0200196 C 13.013333,6.1366343 12.897371,6.9523225 12.617188,7.7407227 12.02837,9.3975477 11.341831,10.405496 10.726074,11.130371 9.7719035,12.253646 8.905394,12.708244 8,13.160644 7.094606,12.708244 6.2280961,12.253646 5.2739258,11.130371 4.658169,10.405496 3.97163,9.3975477 3.3828125,7.7407227 3.102629,6.9523225 2.9866669,6.1366343 2.8657227,5.0527344 5.0921299,4.7140344 6.8097538,4.0412398 8,3.0327148 Z M 8,3.9321289 C 6.6923817,4.8398539 5.2233869,5.2995548 3.7490234,5.6123046 4.4471579,9.5738045 5.9510862,11.267813 8,12.328613 10.048914,11.267813 11.552843,9.5738045 12.250977,5.6123046 10.776613,5.2995547 9.3076183,4.8398539 8,3.9321289 Z"
+          class="ColorScheme-Text"/>
+</svg>
diff --git a/resources/images/fallback/light/small/unlock.svg b/resources/images/fallback/light/small/unlock.svg
new file mode 100644
index 0000000..ff91b8e
--- /dev/null
+++ b/resources/images/fallback/light/small/unlock.svg
@@ -0,0 +1,13 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
+  <defs id="defs3051">
+    <style type="text/css" id="current-color-scheme">
+      .ColorScheme-Text {
+        color:#eff0f1;
+      }
+      </style>
+  </defs>
+ <path style="fill:currentColor;fill-opacity:1;stroke:none" 
+     d="M 8 2 C 6.3431375 2 5 3.3431371 5 5 L 5 6 L 6 6 L 6 5 C 6 3.8954305 6.8954305 3 8 3 C 9.1045695 3 10 3.8954305 10 5 L 10 8 L 7 8 L 6 8 L 5 8 L 3 8 L 3 14 L 13 14 L 13 8 L 11 8 L 11 5 C 11 3.3431371 9.6568625 2 8 2 z M 4 9 L 12 9 L 12 13 L 4 13 L 4 9 z "
+     class="ColorScheme-Text"
+     />
+</svg>
diff --git a/resources/resources.qrc b/resources/resources.qrc
index 58565fc..993255b 100644
--- a/resources/resources.qrc
+++ b/resources/resources.qrc
@@ -42,6 +42,9 @@
     <file>images/fallback/dark/big/add.svg</file>
     <file>images/fallback/dark/big/folder.svg</file>
     <file>images/fallback/dark/big/document-preview.svg</file>
+    <file>images/fallback/dark/big/shield.svg</file>
+    <file>images/fallback/dark/big/lock.svg</file>
+    <file>images/fallback/dark/big/unlock.svg</file>
     
     
     <file>images/fallback/dark/small/absent.svg</file>
@@ -84,6 +87,9 @@
     <file>images/fallback/dark/small/add.svg</file>
     <file>images/fallback/dark/small/folder.svg</file>
     <file>images/fallback/dark/small/document-preview.svg</file>
+    <file>images/fallback/dark/small/shield.svg</file>
+    <file>images/fallback/dark/small/lock.svg</file>
+    <file>images/fallback/dark/small/unlock.svg</file>
     
     
     <file>images/fallback/light/big/absent.svg</file>
@@ -126,6 +132,9 @@
     <file>images/fallback/light/big/add.svg</file>
     <file>images/fallback/light/big/folder.svg</file>
     <file>images/fallback/light/big/document-preview.svg</file>
+    <file>images/fallback/light/big/shield.svg</file>
+    <file>images/fallback/light/big/lock.svg</file>
+    <file>images/fallback/light/big/unlock.svg</file>
     
     
     <file>images/fallback/light/small/absent.svg</file>
@@ -168,5 +177,8 @@
     <file>images/fallback/light/small/add.svg</file>
     <file>images/fallback/light/small/folder.svg</file>
     <file>images/fallback/light/small/document-preview.svg</file>
+    <file>images/fallback/light/small/shield.svg</file>
+    <file>images/fallback/light/small/lock.svg</file>
+    <file>images/fallback/light/small/unlock.svg</file>
 </qresource>
 </RCC>
diff --git a/shared/enums.h b/shared/enums.h
index c959883..6b668d6 100644
--- a/shared/enums.h
+++ b/shared/enums.h
@@ -16,8 +16,7 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#ifndef SHARED_ENUMS_H
-#define SHARED_ENUMS_H
+#pragma once
 
 #include <deque>
 
@@ -170,4 +169,3 @@ static const EncryptionProtocol EncryptionProtocolHighest = EncryptionProtocol::
 static const EncryptionProtocol EncryptionProtocolLowest = EncryptionProtocol::omemo2;
 
 }
-#endif // SHARED_ENUMS_H
diff --git a/shared/exception.h b/shared/exception.h
index 4c66c2d..48d7eda 100644
--- a/shared/exception.h
+++ b/shared/exception.h
@@ -16,8 +16,7 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#ifndef EXCEPTION_H
-#define EXCEPTION_H
+#pragma once
 
 #include <stdexcept>
 #include <string>
@@ -36,5 +35,3 @@ namespace Utils
         const char* what() const noexcept( true );
     };
 }
-
-#endif // EXCEPTION_H
diff --git a/shared/global.cpp b/shared/global.cpp
index 95168b2..97f3263 100644
--- a/shared/global.cpp
+++ b/shared/global.cpp
@@ -62,70 +62,78 @@ Shared::Global::ColorSchemeName Shared::Global::colorSchemeName = 0;
 Shared::Global::CreatePalette Shared::Global::createPalette = 0;
 #endif
 
+
+
 Shared::Global::Global():
     availability({
-        tr("Online", "Availability"), 
-        tr("Away", "Availability"), 
-        tr("Absent", "Availability"), 
-        tr("Busy", "Availability"), 
-        tr("Chatty", "Availability"), 
-        tr("Invisible", "Availability"), 
-        tr("Offline", "Availability")
+        QCoreApplication::translate("Global", "Online", "Availability"),
+        QCoreApplication::translate("Global", "Away", "Availability"),
+        QCoreApplication::translate("Global", "Absent", "Availability"),
+        QCoreApplication::translate("Global", "Busy", "Availability"),
+        QCoreApplication::translate("Global", "Chatty", "Availability"),
+        QCoreApplication::translate("Global", "Invisible", "Availability"),
+        QCoreApplication::translate("Global", "Offline", "Availability")
     }),
     connectionState({
-        tr("Disconnected", "ConnectionState"), 
-        tr("Scheduled", "ConnectionState"),
-        tr("Connecting", "ConnectionState"),
-        tr("Connected", "ConnectionState"), 
-        tr("Error", "ConnectionState")
+        QCoreApplication::translate("Global", "Disconnected", "ConnectionState"),
+        QCoreApplication::translate("Global", "Scheduled", "ConnectionState"),
+        QCoreApplication::translate("Global", "Connecting", "ConnectionState"),
+        QCoreApplication::translate("Global", "Connected", "ConnectionState"),
+        QCoreApplication::translate("Global", "Error", "ConnectionState")
     }),
     subscriptionState({
-        tr("None", "SubscriptionState"), 
-        tr("From", "SubscriptionState"), 
-        tr("To", "SubscriptionState"), 
-        tr("Both", "SubscriptionState"), 
-        tr("Unknown", "SubscriptionState")
+        QCoreApplication::translate("Global", "None", "SubscriptionState"),
+        QCoreApplication::translate("Global", "From", "SubscriptionState"),
+        QCoreApplication::translate("Global", "To", "SubscriptionState"),
+        QCoreApplication::translate("Global", "Both", "SubscriptionState"),
+        QCoreApplication::translate("Global", "Unknown", "SubscriptionState")
     }),
     affiliation({
-        tr("Unspecified", "Affiliation"), 
-        tr("Outcast", "Affiliation"), 
-        tr("Nobody", "Affiliation"), 
-        tr("Member", "Affiliation"), 
-        tr("Admin", "Affiliation"), 
-        tr("Owner", "Affiliation")
+        QCoreApplication::translate("Global", "Unspecified", "Affiliation"),
+        QCoreApplication::translate("Global", "Outcast", "Affiliation"),
+        QCoreApplication::translate("Global", "Nobody", "Affiliation"),
+        QCoreApplication::translate("Global", "Member", "Affiliation"),
+        QCoreApplication::translate("Global", "Admin", "Affiliation"),
+        QCoreApplication::translate("Global", "Owner", "Affiliation")
     }),
     role({
-        tr("Unspecified", "Role"), 
-        tr("Nobody", "Role"), 
-        tr("Visitor", "Role"),
-        tr("Participant", "Role"), 
-        tr("Moderator", "Role")
+        QCoreApplication::translate("Global", "Unspecified", "Role"),
+        QCoreApplication::translate("Global", "Nobody", "Role"),
+        QCoreApplication::translate("Global", "Visitor", "Role"),
+        QCoreApplication::translate("Global", "Participant", "Role"),
+        QCoreApplication::translate("Global", "Moderator", "Role")
     }),
     messageState({
-        tr("Pending", "MessageState"), 
-        tr("Sent", "MessageState"), 
-        tr("Delivered", "MessageState"), 
-        tr("Error", "MessageState")
+        QCoreApplication::translate("Global", "Pending", "MessageState"),
+        QCoreApplication::translate("Global", "Sent", "MessageState"),
+        QCoreApplication::translate("Global", "Delivered", "MessageState"),
+        QCoreApplication::translate("Global", "Error", "MessageState")
     }),
     accountPassword({
-        tr("Plain", "AccountPassword"),
-        tr("Jammed", "AccountPassword"),
-        tr("Always Ask", "AccountPassword"),
-        tr("KWallet", "AccountPassword")
+        QCoreApplication::translate("Global", "Plain", "AccountPassword"),
+        QCoreApplication::translate("Global", "Jammed", "AccountPassword"),
+        QCoreApplication::translate("Global", "Always Ask", "AccountPassword"),
+        QCoreApplication::translate("Global", "KWallet", "AccountPassword")
     }),
     trustLevel({
-        tr("Undecided", "TrustLevel"),
-        tr("Automatically distrusted", "TrustLevel"),
-        tr("Manually distrusted", "TrustLevel"),
-        tr("Automatically trusted", "TrustLevel"),
-        tr("Manually trusted", "TrustLevel"),
-        tr("Authenticated", "TrustLevel")
+        QCoreApplication::translate("Global", "Undecided", "TrustLevel"),
+        QCoreApplication::translate("Global", "Automatically distrusted", "TrustLevel"),
+        QCoreApplication::translate("Global", "Manually distrusted", "TrustLevel"),
+        QCoreApplication::translate("Global", "Automatically trusted", "TrustLevel"),
+        QCoreApplication::translate("Global", "Manually trusted", "TrustLevel"),
+        QCoreApplication::translate("Global", "Authenticated", "TrustLevel")
+    }),
+    encryptionProtocols({
+        QCoreApplication::translate("Global", "None", "EncryptionProtocol"),
+        QCoreApplication::translate("Global", "OMEMO 0", "EncryptionProtocol"),
+        QCoreApplication::translate("Global", "OMEMO 1", "EncryptionProtocol"),
+        QCoreApplication::translate("Global", "OMEMO 2", "EncryptionProtocol")
     }),
     accountPasswordDescription({
-        tr("Your password is going to be stored in config file in plain text", "AccountPasswordDescription"),
-        tr("Your password is going to be stored in config file but jammed with constant encryption key you can find in program source code. It might look like encryption but it's not", "AccountPasswordDescription"),
-        tr("Squawk is going to query you for the password on every start of the program", "AccountPasswordDescription"),
-        tr("Your password is going to be stored in KDE wallet storage (KWallet). You're going to be queried for permissions", "AccountPasswordDescription")
+        QCoreApplication::translate("Global", "Your password is going to be stored in config file in plain text", "AccountPasswordDescription"),
+        QCoreApplication::translate("Global", "Your password is going to be stored in config file but jammed with constant encryption key you can find in program source code. It might look like encryption but it's not", "AccountPasswordDescription"),
+        QCoreApplication::translate("Global", "Squawk is going to query you for the password on every start of the program", "AccountPasswordDescription"),
+        QCoreApplication::translate("Global", "Your password is going to be stored in KDE wallet storage (KWallet). You're going to be queried for permissions", "AccountPasswordDescription")
     }),
     defaultSystemStyle(QApplication::style()->objectName()),
     defaultSystemPalette(QApplication::palette()),
@@ -262,6 +270,10 @@ QString Shared::Global::getName(Shared::TrustLevel tl) {
     return instance->trustLevel[static_cast<int>(tl)];
 }
 
+QString Shared::Global::getName(EncryptionProtocol ep) {
+    return instance->encryptionProtocols[static_cast<int>(ep)];
+}
+
 void Shared::Global::setSupported(const QString& pluginName, bool support) {
     std::map<QString, bool>::iterator itr = instance->pluginSupport.find(pluginName);
     if (itr != instance->pluginSupport.end()) {
diff --git a/shared/global.h b/shared/global.h
index 578fc42..6d23c2f 100644
--- a/shared/global.h
+++ b/shared/global.h
@@ -16,8 +16,7 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#ifndef SHARED_GLOBAL_H
-#define SHARED_GLOBAL_H
+#pragma once
 
 #include "enums.h"
 #include "message.h"
@@ -48,7 +47,6 @@
 namespace Shared {
     
     class Global {
-        Q_DECLARE_TR_FUNCTIONS(Global)
     public:
         struct FileInfo {
             enum class Preview {
@@ -75,6 +73,7 @@ namespace Shared {
         static QString getName(Message::State rl);
         static QString getName(AccountPassword ap);
         static QString getName(TrustLevel tl);
+        static QString getName(EncryptionProtocol ep);
         
         static QString getDescription(AccountPassword ap);
         
@@ -86,6 +85,7 @@ namespace Shared {
         const std::deque<QString> messageState;
         const std::deque<QString> accountPassword;
         const std::deque<QString> trustLevel;
+        const std::deque<QString> encryptionProtocols;
         
         const std::deque<QString> accountPasswordDescription;
 
@@ -124,7 +124,7 @@ namespace Shared {
         class EnumOutOfRange: public Utils::Exception {
         public:
             EnumOutOfRange(const std::string& p_name):Exception(), name(p_name) {}
-            
+
             std::string getMessage() const{
                 return "An attempt to get enum " + name + " from integer out of range of that enum";
             }
@@ -161,5 +161,3 @@ namespace Shared {
 #endif
     };
 }
-
-#endif // SHARED_GLOBAL_H
diff --git a/shared/icons.h b/shared/icons.h
index cf31b3b..dadb87e 100644
--- a/shared/icons.h
+++ b/shared/icons.h
@@ -16,8 +16,7 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#ifndef SHARED_ICONS_H
-#define SHARED_ICONS_H
+#pragma once
 
 #include <QIcon>
 
@@ -175,8 +174,9 @@ static const std::map<QString, std::pair<QString, QString>> icons = {
     {"unfavorite", {"draw-star", "unfavorite"}},
     {"list-add", {"list-add", "add"}},
     {"folder", {"folder", "folder"}},
-    {"document-preview", {"document-preview", "document-preview"}}
+    {"document-preview", {"document-preview", "document-preview"}},
+    {"secure", {"security-high", "shield"}},
+    {"lock", {"lock", "lock"}},
+    {"unlock", {"unlock", "unlock"}}
 };
 }
-
-#endif // SHARED_ICONS_H
diff --git a/shared/message.cpp b/shared/message.cpp
index 5b535c1..caf8a97 100644
--- a/shared/message.cpp
+++ b/shared/message.cpp
@@ -37,7 +37,8 @@ Shared::Message::Message(Shared::Message::Type p_type):
     originalMessage(),
     lastModified(),
     stanzaId(),
-    attachPath()
+    attachPath(),
+    encryption(EncryptionProtocol::none)
     {}
 
 Shared::Message::Message():
@@ -58,7 +59,8 @@ Shared::Message::Message():
     originalMessage(),
     lastModified(),
     stanzaId(),
-    attachPath()
+    attachPath(),
+    encryption(EncryptionProtocol::none)
     {}
 
 QString Shared::Message::getBody() const {
@@ -234,9 +236,8 @@ void Shared::Message::setType(Shared::Message::Type t) {
 void Shared::Message::setState(Shared::Message::State p_state) {
     state = p_state;
     
-    if (state != State::error) {
+    if (state != State::error)
         errorText = "";
-    }
 }
 
 bool Shared::Message::serverStored() const {
diff --git a/shared/message.h b/shared/message.h
index d7df9f3..b81881b 100644
--- a/shared/message.h
+++ b/shared/message.h
@@ -16,8 +16,7 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#ifndef SHAPER_MESSAGE_H
-#define SHAPER_MESSAGE_H
+#pragma once
 
 #include <QString>
 #include <QDateTime>
@@ -154,5 +153,3 @@ private:
 };
 
 }
-
-#endif // SHAPER_MESSAGE_H
diff --git a/shared/pathcheck.h b/shared/pathcheck.h
index 3ca612b..1608241 100644
--- a/shared/pathcheck.h
+++ b/shared/pathcheck.h
@@ -16,8 +16,7 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#ifndef PATHCHECK_H
-#define PATHCHECK_H
+#pragma once
 
 #include <QString>
 #include <QStandardPaths>
@@ -40,5 +39,3 @@ QString squawkifyPath(QString path);
 bool isSubdirectoryOfSettings(const QString& path);
 
 }
-
-#endif // PATHCHECK_H
diff --git a/shared/utils.h b/shared/utils.h
index 0329cee..019f611 100644
--- a/shared/utils.h
+++ b/shared/utils.h
@@ -16,8 +16,7 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#ifndef SHARED_UTILS_H
-#define SHARED_UTILS_H
+#pragma once
 
 #include <QString>
 #include <QStringList>
@@ -76,5 +75,3 @@ enum class Hover {
     anchor
 };
 }
-
-#endif // SHARED_UTILS_H
diff --git a/ui/widgets/chat.h b/ui/widgets/chat.h
index cf7ecd3..912299e 100644
--- a/ui/widgets/chat.h
+++ b/ui/widgets/chat.h
@@ -24,12 +24,11 @@
 #include "shared/icons.h"
 #include "shared/global.h"
 
-namespace Ui
-{
+namespace Ui {
 class Chat;
 }
-class Chat : public Conversation
-{
+
+class Chat : public Conversation {
     Q_OBJECT
 public:
     Chat(Models::Account* acc, Models::Contact* p_contact, QWidget* parent = 0);
diff --git a/ui/widgets/conversation.cpp b/ui/widgets/conversation.cpp
index 8336d5e..32c38a8 100644
--- a/ui/widgets/conversation.cpp
+++ b/ui/widgets/conversation.cpp
@@ -120,6 +120,7 @@ Conversation::~Conversation() {
     delete contextMenu;
     
     element->feed->decrementObservers();
+    delete m_ui;
 }
 
 void Conversation::onAccountChanged(Models::Item* item, int row, int col) {
diff --git a/ui/widgets/conversation.h b/ui/widgets/conversation.h
index 4db5029..73e6536 100644
--- a/ui/widgets/conversation.h
+++ b/ui/widgets/conversation.h
@@ -20,6 +20,7 @@
 #define CONVERSATION_H
 
 #include <QWidget>
+#include <QObject>
 #include <QScopedPointer>
 #include <QMap>
 #include <QMimeData>
@@ -134,7 +135,7 @@ protected:
     Models::Element* element;
     QString palJid;
     QString activePalResource;
-    QScopedPointer<Ui::Conversation> m_ui;
+    Ui::Conversation* m_ui;
     KeyEnterReceiver ker;
     QString thread;
     QLabel* statusIcon;
diff --git a/ui/widgets/messageline/CMakeLists.txt b/ui/widgets/messageline/CMakeLists.txt
index 7ded76b..1524509 100644
--- a/ui/widgets/messageline/CMakeLists.txt
+++ b/ui/widgets/messageline/CMakeLists.txt
@@ -14,5 +14,4 @@ set(HEADER_FILES
 
 target_sources(squawk PRIVATE
   ${SOURCE_FILES}
-  ${HEADER_FILES}
 )
diff --git a/ui/widgets/messageline/messagedelegate.cpp b/ui/widgets/messageline/messagedelegate.cpp
index 35a73c2..b89b438 100644
--- a/ui/widgets/messageline/messagedelegate.cpp
+++ b/ui/widgets/messageline/messagedelegate.cpp
@@ -47,22 +47,23 @@ MessageDelegate::MessageDelegate(QObject* parent):
     dateFont(Shared::Global::getInstance()->smallFont),
     nickMetrics(Shared::Global::getInstance()->headerFontMetrics),
     dateMetrics(Shared::Global::getInstance()->smallFontMetrics),
-    bodyRenderer(new QTextDocument()),
+    bodyRenderer(),
     buttonHeight(0),
     buttonWidth(0),
     barHeight(0),
-    buttons(new std::map<QString, FeedButton*>()),
-    bars(new std::map<QString, QProgressBar*>()),
-    statusIcons(new std::map<QString, QLabel*>()),
-    pencilIcons(new std::map<QString, QLabel*>()),
-    previews(new std::map<QString, Preview*>()),
-    idsToKeep(new std::set<QString>()),
+    buttons(),
+    bars(),
+    statusIcons(),
+    pencilIcons(),
+    encryptionIcons(),
+    previews(),
+    idsToKeep(),
     clearingWidgets(false),
     currentId(""),
     selection(0, 0)
 {
-    bodyRenderer->setDocumentMargin(0);
-    bodyRenderer->setDefaultFont(bodyFont);
+    bodyRenderer.setDocumentMargin(0);
+    bodyRenderer.setDefaultFont(bodyFont);
 
     QPushButton btn(QCoreApplication::translate("MessageLine", "Download"));
     buttonHeight = btn.sizeHint().height();
@@ -73,28 +74,23 @@ MessageDelegate::MessageDelegate(QObject* parent):
 }
 
 MessageDelegate::~MessageDelegate() {
-    for (const std::pair<const QString, FeedButton*>& pair: *buttons)
+    for (const std::pair<const QString, FeedButton*>& pair: buttons)
         delete pair.second;
     
-    for (const std::pair<const QString, QProgressBar*>& pair: *bars)
+    for (const std::pair<const QString, QProgressBar*>& pair: bars)
         delete pair.second;
     
-    for (const std::pair<const QString, QLabel*>& pair: *statusIcons)
+    for (const std::pair<const QString, QLabel*>& pair: statusIcons)
         delete pair.second;
     
-    for (const std::pair<const QString, QLabel*>& pair: *pencilIcons)
+    for (const std::pair<const QString, QLabel*>& pair: pencilIcons)
+        delete pair.second;
+
+    for (const std::pair<const QString, QLabel*>& pair: encryptionIcons)
         delete pair.second;
     
-    for (const std::pair<const QString, Preview*>& pair: *previews)
+    for (const std::pair<const QString, Preview*>& pair: previews)
         delete pair.second;
-    
-    delete statusIcons;
-    delete pencilIcons;
-    delete idsToKeep;
-    delete buttons;
-    delete bars;
-    delete previews;
-    delete bodyRenderer;
 }
 
 void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const {
@@ -168,6 +164,7 @@ void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio
     painter->setPen(q);
     painter->drawText(opt.rect, opt.displayAlignment, dateString, &rect);
     int currentY = opt.rect.y();
+    int statusOffset = statusIconSize;
     if (data.sentByMe) {
         QLabel* statusIcon = getStatusIcon(data);
         
@@ -176,30 +173,43 @@ void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio
         statusIcon->show();
         
         opt.rect.adjust(0, statusIconSize + textMargin, 0, 0);
+        statusOffset = statusIconSize + margin;
     }
     
     if (data.correction.corrected) {
         QLabel* pencilIcon = getPencilIcon(data);
-        
         pencilIcon->setParent(vp);
         if (data.sentByMe)
-            pencilIcon->move(opt.rect.left() + statusIconSize + margin, currentY);
+            pencilIcon->move(opt.rect.left() + statusOffset, currentY);
         else
-            pencilIcon->move(opt.rect.right() - statusIconSize - margin, currentY);
+            pencilIcon->move(opt.rect.right() - statusOffset, currentY);
 
         pencilIcon->show();
+        statusOffset += statusIconSize + margin;
     } else {
-        std::map<QString, QLabel*>::const_iterator itr = pencilIcons->find(data.id);
-        if (itr != pencilIcons->end()) {
+        std::map<QString, QLabel*>::const_iterator itr = pencilIcons.find(data.id);
+        if (itr != pencilIcons.end()) {
             delete itr->second;
-            pencilIcons->erase(itr);
+            pencilIcons.erase(itr);
         }
     }
+
+    if (data.encryption != Shared::EncryptionProtocol::none) {
+        QLabel* shieldIcon = getEncryptionIcon(data);
+        shieldIcon->setParent(vp);
+        if (data.sentByMe)
+            shieldIcon->move(opt.rect.left() + statusOffset, currentY);
+        else
+            shieldIcon->move(opt.rect.right() - statusOffset, currentY);
+
+        shieldIcon->show();
+        statusOffset += statusIconSize + margin;
+    }
     
     painter->restore();
     
     if (clearingWidgets)
-        idsToKeep->insert(data.id);
+        idsToKeep.insert(data.id);
 }
 
 void MessageDelegate::paintBubble(const Models::FeedItem& data, QPainter* painter, const QStyleOptionViewItem& option) const {
@@ -281,11 +291,11 @@ QSize MessageDelegate::sizeHint(const QStyleOptionViewItem& option, const QModel
     Models::FeedItem data = qvariant_cast<Models::FeedItem>(vi);
     QSize messageSize(0, 0);
     if (data.text.size() > 0) {
-        bodyRenderer->setPlainText(data.text);
-        bodyRenderer->setTextWidth(messageRect.size().width());
+        bodyRenderer.setPlainText(data.text);
+        bodyRenderer.setTextWidth(messageRect.size().width());
 
-        QSizeF size = bodyRenderer->size();
-        size.setWidth(bodyRenderer->idealWidth());
+        QSizeF size = bodyRenderer.size();
+        size.setWidth(bodyRenderer.idealWidth());
         messageSize = QSize(std::ceil(size.width()), std::ceil(size.height()));
         messageSize.rheight() += textMargin;
     }
@@ -338,9 +348,12 @@ QSize MessageDelegate::sizeHint(const QStyleOptionViewItem& option, const QModel
     messageSize.rheight() += dateSize.height() > statusIconSize ? dateSize.height() : statusIconSize;
 
     int statusWidth = dateSize.width() + statusIconSize + margin;
-    if (data.correction.corrected) {
+    if (data.correction.corrected)
         statusWidth += statusIconSize + margin;
-    }
+
+    if (data.encryption != Shared::EncryptionProtocol::none)
+        statusWidth += statusIconSize + margin;
+
     messageSize.setWidth(std::max(statusWidth, messageSize.width()));
     messageSize.rwidth() += 2 * bubbleMargin;
     
@@ -399,10 +412,10 @@ QString MessageDelegate::getAnchor(const QPoint& point, const QModelIndex& index
         if (localHint.contains(point)) {
             QPoint translated = point - localHint.topLeft();
 
-            bodyRenderer->setHtml(Shared::processMessageBody(data.text));
-            bodyRenderer->setTextWidth(localHint.size().width());
+            bodyRenderer.setHtml(Shared::processMessageBody(data.text));
+            bodyRenderer.setTextWidth(localHint.size().width());
 
-            return bodyRenderer->documentLayout()->anchorAt(translated);
+            return bodyRenderer.documentLayout()->anchorAt(translated);
         }
     }
 
@@ -424,13 +437,13 @@ QString MessageDelegate::leftDoubleClick(const QPoint& point, const QModelIndex&
         if (localHint.contains(point)) {
             QPoint translated = point - localHint.topLeft();
 
-            bodyRenderer->setHtml(Shared::processMessageBody(data.text));
-            bodyRenderer->setTextWidth(localHint.size().width());
+            bodyRenderer.setHtml(Shared::processMessageBody(data.text));
+            bodyRenderer.setTextWidth(localHint.size().width());
 
-            QAbstractTextDocumentLayout* lay = bodyRenderer->documentLayout();
+            QAbstractTextDocumentLayout* lay = bodyRenderer.documentLayout();
 
             int position = lay->hitTest(translated, Qt::HitTestAccuracy::FuzzyHit);
-            QTextCursor cursor(bodyRenderer);
+            QTextCursor cursor(&bodyRenderer);
             cursor.setPosition(position, QTextCursor::MoveAnchor);
             cursor.movePosition(QTextCursor::StartOfWord, QTextCursor::MoveAnchor);
             cursor.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor);
@@ -456,10 +469,10 @@ Shared::Hover MessageDelegate::hoverType(const QPoint& point, const QModelIndex&
         if (localHint.contains(point)) {
             QPoint translated = point - localHint.topLeft();
 
-            bodyRenderer->setHtml(Shared::processMessageBody(data.text));
-            bodyRenderer->setTextWidth(localHint.size().width());
+            bodyRenderer.setHtml(Shared::processMessageBody(data.text));
+            bodyRenderer.setTextWidth(localHint.size().width());
 
-            QAbstractTextDocumentLayout* lay = bodyRenderer->documentLayout();
+            QAbstractTextDocumentLayout* lay = bodyRenderer.documentLayout();
             QString anchor = lay->anchorAt(translated);
 
             if (anchor.size() > 0) {
@@ -489,15 +502,15 @@ QString MessageDelegate::mouseDrag(const QPoint& start, const QPoint& end, const
             last.setY(std::max(last.y(), 0));
             last.setY(std::min(last.y(), localHint.height()));
 
-            bodyRenderer->setHtml(Shared::processMessageBody(data.text));
-            bodyRenderer->setTextWidth(localHint.size().width());
-            selection.first = bodyRenderer->documentLayout()->hitTest(first, Qt::HitTestAccuracy::FuzzyHit);
-            selection.second = bodyRenderer->documentLayout()->hitTest(last, Qt::HitTestAccuracy::FuzzyHit);
+            bodyRenderer.setHtml(Shared::processMessageBody(data.text));
+            bodyRenderer.setTextWidth(localHint.size().width());
+            selection.first = bodyRenderer.documentLayout()->hitTest(first, Qt::HitTestAccuracy::FuzzyHit);
+            selection.second = bodyRenderer.documentLayout()->hitTest(last, Qt::HitTestAccuracy::FuzzyHit);
 
             currentId = data.id;
 
             if (selection.first != selection.second) {
-                QTextCursor cursor(bodyRenderer);
+                QTextCursor cursor(&bodyRenderer);
                 cursor.setPosition(selection.first, QTextCursor::MoveAnchor);
                 cursor.setPosition(selection.second, QTextCursor::KeepAnchor);
                 return cursor.selectedText();
@@ -562,17 +575,17 @@ int MessageDelegate::paintBar(QProgressBar* bar, QPainter* painter, bool sentByM
 
 int MessageDelegate::paintPreview(const Models::FeedItem& data, QPainter* painter, QStyleOptionViewItem& option) const {
     Preview* preview = 0;
-    std::map<QString, Preview*>::iterator itr = previews->find(data.id);
+    std::map<QString, Preview*>::iterator itr = previews.find(data.id);
 
     QSize size = option.rect.size();
     QString path = Shared::resolvePath(data.attach.localPath);
-    if (itr != previews->end()) {
+    if (itr != previews.end()) {
         preview = itr->second;
         preview->actualize(path, size, option.rect.topLeft());
     } else {
         QWidget* vp = static_cast<QWidget*>(painter->device());
         preview = new Preview(path, size, option.rect.topLeft(), vp);
-        previews->insert(std::make_pair(data.id, preview));
+        previews.insert(std::make_pair(data.id, preview));
     }
     
     if (!preview->isFileReachable())        //this is the situation when the file preview couldn't be painted because the file was moved
@@ -585,15 +598,15 @@ int MessageDelegate::paintPreview(const Models::FeedItem& data, QPainter* painte
 }
 
 QPushButton * MessageDelegate::getButton(const Models::FeedItem& data) const {
-    std::map<QString, FeedButton*>::const_iterator itr = buttons->find(data.id);
+    std::map<QString, FeedButton*>::const_iterator itr = buttons.find(data.id);
     FeedButton* result = 0;
-    if (itr != buttons->end()) {
+    if (itr != buttons.end()) {
         result = itr->second;
     } else {
-        std::map<QString, QProgressBar*>::const_iterator barItr = bars->find(data.id);
-        if (barItr != bars->end()) {
+        std::map<QString, QProgressBar*>::const_iterator barItr = bars.find(data.id);
+        if (barItr != bars.end()) {
             delete barItr->second;
-            bars->erase(barItr);
+            bars.erase(barItr);
         }
     }
     
@@ -601,7 +614,7 @@ QPushButton * MessageDelegate::getButton(const Models::FeedItem& data) const {
         result = new FeedButton();
         result->messageId = data.id;
         result->setText(QCoreApplication::translate("MessageLine", "Download"));
-        buttons->insert(std::make_pair(data.id, result));
+        buttons.insert(std::make_pair(data.id, result));
         connect(result, &QPushButton::clicked, this, &MessageDelegate::onButtonPushed);
     }
     
@@ -609,22 +622,22 @@ QPushButton * MessageDelegate::getButton(const Models::FeedItem& data) const {
 }
 
 QProgressBar * MessageDelegate::getBar(const Models::FeedItem& data) const {
-    std::map<QString, QProgressBar*>::const_iterator barItr = bars->find(data.id);
+    std::map<QString, QProgressBar*>::const_iterator barItr = bars.find(data.id);
     QProgressBar* result = 0;
-    if (barItr != bars->end()) {
+    if (barItr != bars.end()) {
         result = barItr->second;
     } else {
-        std::map<QString, FeedButton*>::const_iterator itr = buttons->find(data.id);
-        if (itr != buttons->end()) {
+        std::map<QString, FeedButton*>::const_iterator itr = buttons.find(data.id);
+        if (itr != buttons.end()) {
             delete itr->second;
-            buttons->erase(itr);
+            buttons.erase(itr);
         }
     }
     
     if (result == 0) {
         result = new QProgressBar();
         result->setRange(0, 100);
-        bars->insert(std::make_pair(data.id, result));
+        bars.insert(std::make_pair(data.id, result));
     }
     
     result->setValue(data.attach.progress * 100);
@@ -633,14 +646,14 @@ QProgressBar * MessageDelegate::getBar(const Models::FeedItem& data) const {
 }
 
 QLabel * MessageDelegate::getStatusIcon(const Models::FeedItem& data) const {
-    std::map<QString, QLabel*>::const_iterator itr = statusIcons->find(data.id);
+    std::map<QString, QLabel*>::const_iterator itr = statusIcons.find(data.id);
     QLabel* result = 0;
     
-    if (itr != statusIcons->end()) {
+    if (itr != statusIcons.end()) {
         result = itr->second;
     } else {
         result = new QLabel();
-        statusIcons->insert(std::make_pair(data.id, result));
+        statusIcons.insert(std::make_pair(data.id, result));
     }
     
     QIcon q(Shared::icon(Shared::messageStateThemeIcons[static_cast<uint8_t>(data.state)]));
@@ -657,17 +670,17 @@ QLabel * MessageDelegate::getStatusIcon(const Models::FeedItem& data) const {
     return result;
 }
 
-QLabel * MessageDelegate::getPencilIcon(const Models::FeedItem& data) const {
-    std::map<QString, QLabel*>::const_iterator itr = pencilIcons->find(data.id);
+QLabel* MessageDelegate::getPencilIcon(const Models::FeedItem& data) const {
+    std::map<QString, QLabel*>::const_iterator itr = pencilIcons.find(data.id);
     QLabel* result = 0;
     
-    if (itr != pencilIcons->end()) {
+    if (itr != pencilIcons.end()) {
         result = itr->second;
     } else {
         result = new QLabel();
         QIcon icon = Shared::icon("edit-rename");
         result->setPixmap(icon.pixmap(statusIconSize));
-        pencilIcons->insert(std::make_pair(data.id, result));
+        pencilIcons.insert(std::make_pair(data.id, result));
     }
     
     result->setToolTip("Last time edited: " + data.correction.lastCorrection.toLocalTime().toString() 
@@ -676,30 +689,46 @@ QLabel * MessageDelegate::getPencilIcon(const Models::FeedItem& data) const {
     return result;
 }
 
+QLabel* MessageDelegate::getEncryptionIcon(const Models::FeedItem& data) const {
+    std::map<QString, QLabel*>::const_iterator itr = encryptionIcons.find(data.id);
+    QLabel* result = 0;
+
+    if (itr != encryptionIcons.end()) {
+        result = itr->second;
+    } else {
+        result = new QLabel();
+        QIcon icon = Shared::icon("secure");
+        result->setPixmap(icon.pixmap(statusIconSize));
+        encryptionIcons.insert(std::make_pair(data.id, result));
+        result->setToolTip("Encrypted: " + Shared::Global::getName(data.encryption));
+    }
+
+    return result;
+}
+
 template <typename T>
-void removeElements(std::map<QString, T*>* elements, std::set<QString>* idsToKeep) {
+void removeElements(std::map<QString, T*>& elements, std::set<QString>& idsToKeep) {
     std::set<QString> toRemove;
-    for (const std::pair<const QString, T*>& pair: *elements) {
-        if (idsToKeep->find(pair.first) == idsToKeep->end()) {
+    for (const std::pair<const QString, T*>& pair: elements) {
+        if (idsToKeep.find(pair.first) == idsToKeep.end()) {
             delete pair.second;
             toRemove.insert(pair.first);
         }
     }
-    for (const QString& key : toRemove) {
-        elements->erase(key);
-    }
+    for (const QString& key : toRemove)
+        elements.erase(key);
 }
 
 int MessageDelegate::paintBody(const Models::FeedItem& data, QPainter* painter, QStyleOptionViewItem& option) const
 {
     if (data.text.size() > 0) {
-        bodyRenderer->setHtml(Shared::processMessageBody(data.text));
-        bodyRenderer->setTextWidth(option.rect.size().width());
+        bodyRenderer.setHtml(Shared::processMessageBody(data.text));
+        bodyRenderer.setTextWidth(option.rect.size().width());
         painter->save();
         painter->translate(option.rect.topLeft());
 
         if (data.id == currentId) {
-            QTextCursor cursor(bodyRenderer);
+            QTextCursor cursor(&bodyRenderer);
             cursor.setPosition(selection.first, QTextCursor::MoveAnchor);
             cursor.setPosition(selection.second, QTextCursor::KeepAnchor);
             QTextCharFormat format = cursor.charFormat();
@@ -708,10 +737,10 @@ int MessageDelegate::paintBody(const Models::FeedItem& data, QPainter* painter,
             cursor.setCharFormat(format);
         }
 
-        bodyRenderer->drawContents(painter);
+        bodyRenderer.drawContents(painter);
 
         painter->restore();
-        QSize bodySize(std::ceil(bodyRenderer->idealWidth()), std::ceil(bodyRenderer->size().height()));
+        QSize bodySize(std::ceil(bodyRenderer.idealWidth()), std::ceil(bodyRenderer.size().height()));
 
         option.rect.adjust(0, bodySize.height() + textMargin, 0, 0);
         return bodySize.width();
@@ -720,7 +749,7 @@ int MessageDelegate::paintBody(const Models::FeedItem& data, QPainter* painter,
 }
 
 void MessageDelegate::beginClearWidgets() {
-    idsToKeep->clear();
+    idsToKeep.clear();
     clearingWidgets = true;
 }
 
@@ -730,9 +759,10 @@ void MessageDelegate::endClearWidgets() {
         removeElements(bars, idsToKeep);
         removeElements(statusIcons, idsToKeep);
         removeElements(pencilIcons, idsToKeep);
+        removeElements(encryptionIcons, idsToKeep);
         removeElements(previews, idsToKeep);
         
-        idsToKeep->clear();
+        idsToKeep.clear();
         clearingWidgets = false;
     }
 }
@@ -743,15 +773,15 @@ void MessageDelegate::onButtonPushed() const {
 }
 
 void MessageDelegate::clearHelperWidget(const Models::FeedItem& data) const {
-    std::map<QString, FeedButton*>::const_iterator itr = buttons->find(data.id);
-    if (itr != buttons->end()) {
+    std::map<QString, FeedButton*>::const_iterator itr = buttons.find(data.id);
+    if (itr != buttons.end()) {
         delete itr->second;
-        buttons->erase(itr);
+        buttons.erase(itr);
     } else {
-        std::map<QString, QProgressBar*>::const_iterator barItr = bars->find(data.id);
-        if (barItr != bars->end()) {
+        std::map<QString, QProgressBar*>::const_iterator barItr = bars.find(data.id);
+        if (barItr != bars.end()) {
             delete barItr->second;
-            bars->erase(barItr);
+            bars.erase(barItr);
         }
     }
 }
diff --git a/ui/widgets/messageline/messagedelegate.h b/ui/widgets/messageline/messagedelegate.h
index 16e39ff..90ef819 100644
--- a/ui/widgets/messageline/messagedelegate.h
+++ b/ui/widgets/messageline/messagedelegate.h
@@ -1,23 +1,22 @@
 /*
- * Squawk messenger. 
+ * Squawk messenger.
  * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
- * 
+ *
  * 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/>.
  */
 
-#ifndef MESSAGEDELEGATE_H
-#define MESSAGEDELEGATE_H
+#pragma once
 
 #include <map>
 #include <set>
@@ -44,17 +43,16 @@ namespace Models {
     struct FeedItem;
 };
 
-class MessageDelegate : public QStyledItemDelegate
-{
+class MessageDelegate : public QStyledItemDelegate {
     Q_OBJECT
 public:
     MessageDelegate(QObject *parent = nullptr);
     ~MessageDelegate();
-    
+
     void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override;
     QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const override;
     //void setModelData(QWidget * editor, QAbstractItemModel * model, const QModelIndex & index) const override;
-    
+
     bool editorEvent(QEvent * event, QAbstractItemModel * model, const QStyleOptionViewItem & option, const QModelIndex & index) override;
     void endClearWidgets();
     void beginClearWidgets();
@@ -66,12 +64,12 @@ public:
 
     static int avatarHeight;
     static int margin;
-    
+
 signals:
     void buttonPushed(const QString& messageId) const;
     void invalidPath(const QString& messageId) const;
     void openLink(const QString& href) const;
-    
+
 protected:
     int paintButton(QPushButton* btn, QPainter* painter, bool sentByMe, QStyleOptionViewItem& option) const;
     int paintBar(QProgressBar* bar, QPainter* painter, bool sentByMe, QStyleOptionViewItem& option) const;
@@ -85,6 +83,7 @@ protected:
     QProgressBar* getBar(const Models::FeedItem& data) const;
     QLabel* getStatusIcon(const Models::FeedItem& data) const;
     QLabel* getPencilIcon(const Models::FeedItem& data) const;
+    QLabel* getEncryptionIcon(const Models::FeedItem& data) const;
     void clearHelperWidget(const Models::FeedItem& data) const;
 
     bool needToDrawAvatar(const QModelIndex& index, const Models::FeedItem& data, const QStyleOptionViewItem& option) const;
@@ -92,36 +91,35 @@ protected:
 
     QRect getHoveredMessageBodyRect(const QModelIndex& index, const Models::FeedItem& data, const QRect& sizeHint) const;
     QString getAnchor(const QPoint& point, const QModelIndex& index, const QRect& sizeHint) const;
-    
+
 protected slots:
     void onButtonPushed() const;
-    
+
 private:
     class FeedButton : public QPushButton {
     public:
         QString messageId;
     };
-    
+
     const QFont& bodyFont;
     const QFont& nickFont;
     const QFont& dateFont;
     const QFontMetrics& nickMetrics;
     const QFontMetrics& dateMetrics;
-    QTextDocument* bodyRenderer;
-    
+    mutable QTextDocument bodyRenderer;
+
     int buttonHeight;
     int buttonWidth;
     int barHeight;
-    
-    std::map<QString, FeedButton*>* buttons;
-    std::map<QString, QProgressBar*>* bars;
-    std::map<QString, QLabel*>* statusIcons;
-    std::map<QString, QLabel*>* pencilIcons;
-    std::map<QString, Preview*>* previews;
-    std::set<QString>* idsToKeep;
+
+    mutable std::map<QString, FeedButton*> buttons;
+    mutable std::map<QString, QProgressBar*> bars;
+    mutable std::map<QString, QLabel*> statusIcons;
+    mutable std::map<QString, QLabel*> pencilIcons;
+    mutable std::map<QString, QLabel*> encryptionIcons;
+    mutable std::map<QString, Preview*> previews;
+    mutable std::set<QString> idsToKeep;
     bool clearingWidgets;
     QString currentId;
     std::pair<int, int> selection;
 };
-
-#endif // MESSAGEDELEGATE_H
diff --git a/ui/widgets/messageline/messagefeed.cpp b/ui/widgets/messageline/messagefeed.cpp
index 29f51d2..370af36 100644
--- a/ui/widgets/messageline/messagefeed.cpp
+++ b/ui/widgets/messageline/messagefeed.cpp
@@ -29,6 +29,7 @@ const QHash<int, QByteArray> Models::MessageFeed::roles = {
     {Sender, "sender"},
     {Date, "date"},
     {DeliveryState, "deliveryState"},
+    {Encryption, "encryption"},
     {Correction, "correction"},
     {SentByMe,"sentByMe"},
     {Avatar, "avatar"},
@@ -51,20 +52,16 @@ Models::MessageFeed::MessageFeed(const Element* ri, QObject* parent):
     failedUploads(),
     unreadMessages(new std::set<QString>()),
     observersAmount(0)
-{
-}
+{}
 
-Models::MessageFeed::~MessageFeed()
-{
+Models::MessageFeed::~MessageFeed() {
     delete unreadMessages;
     
-    for (Shared::Message* message : storage) {
+    for (Shared::Message* message : storage)
         delete message;
-    }
 }
 
-void Models::MessageFeed::addMessage(const Shared::Message& msg)
-{
+void Models::MessageFeed::addMessage(const Shared::Message& msg) {
     QString id = msg.getId();
     StorageById::const_iterator itr = indexById.find(id);
     if (itr != indexById.end()) {
@@ -75,11 +72,11 @@ void Models::MessageFeed::addMessage(const Shared::Message& msg)
     Shared::Message* copy = new Shared::Message(msg);
     StorageByTime::const_iterator tItr = indexByTime.upper_bound(msg.getTime());
     int position;
-    if (tItr == indexByTime.end()) {
+    if (tItr == indexByTime.end())
         position = storage.size();
-    } else {
+    else
         position = indexByTime.rank(tItr);
-    }
+
     beginInsertRows(QModelIndex(), position, position);
     storage.insert(copy);
     endInsertRows();
@@ -93,8 +90,7 @@ void Models::MessageFeed::addMessage(const Shared::Message& msg)
     }
 }
 
-void Models::MessageFeed::changeMessage(const QString& id, const QMap<QString, QVariant>& data)
-{
+void Models::MessageFeed::changeMessage(const QString& id, const QMap<QString, QVariant>& data) {
     StorageById::iterator itr = indexById.find(id);
     if (itr == indexById.end()) {
         qDebug() << "received a command to change a message, but the message couldn't be found, skipping";
@@ -159,9 +155,8 @@ void Models::MessageFeed::changeMessage(const QString& id, const QMap<QString, Q
         }
         
         QVector<int> cr;
-        for (MessageRoles role : changeRoles) {
+        for (MessageRoles role : changeRoles)
             cr.push_back(role);
-        }
         
         emit dataChanged(index, index, cr);
 
@@ -173,14 +168,15 @@ void Models::MessageFeed::changeMessage(const QString& id, const QMap<QString, Q
     }
 }
 
-std::set<Models::MessageFeed::MessageRoles> Models::MessageFeed::detectChanges(const Shared::Message& msg, const QMap<QString, QVariant>& data) const
-{
+std::set<Models::MessageFeed::MessageRoles> Models::MessageFeed::detectChanges(
+    const Shared::Message& msg,
+    const QMap<QString, QVariant>& data
+) const {
     std::set<MessageRoles> roles;
     Shared::Message::State state = msg.getState();
     QMap<QString, QVariant>::const_iterator itr = data.find("state");
-    if (itr != data.end() && static_cast<Shared::Message::State>(itr.value().toUInt()) != state) {
+    if (itr != data.end() && static_cast<Shared::Message::State>(itr.value().toUInt()) != state)
         roles.insert(MessageRoles::DeliveryState);
-    }
     
     itr = data.find("outOfBandUrl");
     bool att = false;
@@ -255,9 +251,8 @@ QVariant Models::MessageFeed::data(const QModelIndex& index, int role) const {
             case Qt::DisplayRole:
             case Text: {
                 QString body = msg->getBody();
-                if (body != msg->getOutOfBandUrl()) {
+                if (body != msg->getOutOfBandUrl())
                     answer = body;
-                }
             }
                 break;
             case Sender: 
@@ -276,6 +271,9 @@ QVariant Models::MessageFeed::data(const QModelIndex& index, int role) const {
             case DeliveryState: 
                 answer = static_cast<unsigned int>(msg->getState());
                 break;
+            case Encryption:
+                answer = QVariant::fromValue(msg->getEncryption());
+                break;
             case Correction: 
                 answer.setValue(fillCorrection(*msg));;
                 break;
@@ -318,13 +316,13 @@ QVariant Models::MessageFeed::data(const QModelIndex& index, int role) const {
                 item.sentByMe = sentByMe(*msg);
                 item.date = msg->getTime();
                 item.state = msg->getState();
+                item.encryption = msg->getEncryption();
                 item.error = msg->getErrorText();
                 item.correction = fillCorrection(*msg);
                 
                 QString body = msg->getBody();
-                if (body != msg->getOutOfBandUrl()) {
+                if (body != msg->getOutOfBandUrl())
                     item.text = body;
-                }
                 
                 item.avatar.clear();
                 if (item.sentByMe) {
@@ -419,13 +417,11 @@ QModelIndex Models::MessageFeed::index(int row, int column, const QModelIndex& p
         return QModelIndex();
 
     StorageByTime::iterator itr = indexByTime.nth(row);
-    if (itr != indexByTime.end()) {
-        Shared::Message* msg = *itr;
-        
-        return createIndex(row, column, msg);
-    } else {
+    if (itr == indexByTime.end())
         return QModelIndex();
-    }
+
+    Shared::Message* msg = *itr;
+    return createIndex(row, column, msg);
 }
 
 QHash<int, QByteArray> Models::MessageFeed::roleNames() const {return roles;}
diff --git a/ui/widgets/messageline/messagefeed.h b/ui/widgets/messageline/messagefeed.h
index db174d2..5cceae9 100644
--- a/ui/widgets/messageline/messagefeed.h
+++ b/ui/widgets/messageline/messagefeed.h
@@ -16,12 +16,12 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#ifndef MESSAGEFEED_H
-#define MESSAGEFEED_H
+#pragma once
 
 #include <QAbstractListModel>
 #include <QDateTime>
 #include <QString>
+#include <QHash>
 
 #include <set>
 
@@ -100,6 +100,7 @@ public:
         Sender,
         Date,
         DeliveryState,
+        Encryption,
         Correction,
         SentByMe,
         Avatar,
@@ -216,6 +217,7 @@ struct FeedItem {
     Edition correction;
     QDateTime date;
     Shared::Message::State state;
+    Shared::EncryptionProtocol encryption;
     Attachment attach;
 };
 };
@@ -224,4 +226,4 @@ Q_DECLARE_METATYPE(Models::Attachment);
 Q_DECLARE_METATYPE(Models::Edition);
 Q_DECLARE_METATYPE(Models::FeedItem);
 
-#endif // MESSAGEFEED_H
+#pragma once
diff --git a/ui/widgets/messageline/preview.h b/ui/widgets/messageline/preview.h
index 11a5d7d..96a6530 100644
--- a/ui/widgets/messageline/preview.h
+++ b/ui/widgets/messageline/preview.h
@@ -16,8 +16,7 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#ifndef PREVIEW_H
-#define PREVIEW_H
+#pragma once
 
 #include <QWidget>
 #include <QString>
@@ -75,5 +74,3 @@ private:
     bool fileReachable;
     bool actualPreview;
 };
-
-#endif // PREVIEW_H

From be466fbad12a10de3e14ce6807f3ad9121a3eae5 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Thu, 9 Nov 2023 19:36:30 -0300
Subject: [PATCH 256/281] removed Order, resolved a crash on several files
 being uploaded simultaniuosly

---
 core/account.cpp                 |   9 +-
 core/components/archive.cpp      |   2 +-
 core/handlers/messagehandler.cpp |   6 +-
 main/CMakeLists.txt              |   5 +-
 shared/CMakeLists.txt            |   1 -
 shared/order.h                   | 154 ------------------------------
 ui/utils/badge.cpp               |  38 +++-----
 ui/utils/badge.h                 |  22 +----
 ui/utils/flowlayout.h            |   8 +-
 ui/widgets/conversation.cpp      | 159 ++++++++++++++++---------------
 ui/widgets/conversation.h        |  20 ++--
 ui/widgets/room.cpp              |   2 +
 12 files changed, 122 insertions(+), 304 deletions(-)
 delete mode 100644 shared/order.h

diff --git a/core/account.cpp b/core/account.cpp
index 96ca4e1..a71bf4a 100644
--- a/core/account.cpp
+++ b/core/account.cpp
@@ -141,14 +141,13 @@ Core::Account::Account(
         loadingOmemo = true;
         future.then(this, [this] (bool result) {
             loadingOmemo = false;
-            if (state == Shared::ConnectionState::scheduled) {
+            if (state == Shared::ConnectionState::scheduled)
                 client.connectToServer(config, presence);
-            }
-            if (result) {
+
+            if (result)
                 qDebug() << "successfully loaded OMEMO data for account" << getName();
-            } else {
+            else
                 qDebug() << "couldn't load OMEMO data for account" << getName();
-            }
         });
     }
 #endif
diff --git a/core/components/archive.cpp b/core/components/archive.cpp
index 003cce6..67f6693 100644
--- a/core/components/archive.cpp
+++ b/core/components/archive.cpp
@@ -30,7 +30,7 @@ Core::Archive::Archive(const QString& account, const QString& p_jid, QObject* pa
     opened(false),
     db(account + "/" + jid),
     messages(db.addStorage<QString, Shared::Message>("messages")),
-    order(db.addStorage<uint64_t, QString>("order")),
+    order(db.addStorage<uint64_t, QString>("order", true)),
     stats(db.addStorage<QString, QVariant>("stats")),
     avatars(db.addStorage<QString, AvatarInfo>("avatars")),
     stanzaIdToId(db.addStorage<QString, QString>("stanzaIdToId")),
diff --git a/core/handlers/messagehandler.cpp b/core/handlers/messagehandler.cpp
index 2a546e9..0d0c446 100644
--- a/core/handlers/messagehandler.cpp
+++ b/core/handlers/messagehandler.cpp
@@ -51,8 +51,7 @@ void Core::MessageHandler::onMessageReceived(const QXmppMessage& msg) {
             qDebug() << "Account" << acc->getName() << "received an Omemo1 encrypted message, not supported yet";
             break;                          //let it go the way it is, there is nothing I can do yet
         case QXmpp::Omemo2:
-            qDebug() << "Account" << acc->getName() << "received an Omemo2 encrypted message, not supported yet";
-            break;                          //let it go the way it is, there is nothing I can do yet
+            break;
     }
 #endif
 #endif
@@ -359,6 +358,8 @@ std::pair<Shared::Message::State, QString> Core::MessageHandler::scheduleSending
             const QXmppE2eeExtension::MessageEncryptResult& res = task.result();
             if (std::holds_alternative<std::unique_ptr<QXmppMessage>>(res)) {
                 const std::unique_ptr<QXmppMessage>& encrypted = std::get<std::unique_ptr<QXmppMessage>>(res);
+                encrypted->setBody("This message is encrypted with OMEMO 2 but could not be decrypted");
+                encrypted->setOutOfBandUrl("");
                 bool success = acc->client.sendPacket(*encrypted.get());
                 if (success)
                     return {Shared::Message::State::sent, ""};
@@ -375,6 +376,7 @@ std::pair<Shared::Message::State, QString> Core::MessageHandler::scheduleSending
                 if (std::holds_alternative<std::unique_ptr<QXmppMessage>>(result)) {
                     const std::unique_ptr<QXmppMessage>& encrypted = std::get<std::unique_ptr<QXmppMessage>>(result);
                     encrypted->setBody("This message is encrypted with OMEMO 2 but could not be decrypted");
+                    encrypted->setOutOfBandUrl("");
                     bool success = acc->client.sendPacket(*encrypted.get());
                     if (success) {
                         std::tuple<bool, QString, QString> ids = getOriginalPendingMessageId(id, false);
diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt
index b5bc725..e92710f 100644
--- a/main/CMakeLists.txt
+++ b/main/CMakeLists.txt
@@ -11,7 +11,4 @@ set(HEADER_FILES
     root.h
 )
 
-target_sources(squawk PRIVATE
-    ${SOURCE_FILES}
-    ${HEADER_FILES}
-)
+target_sources(squawk PRIVATE ${SOURCE_FILES})
diff --git a/shared/CMakeLists.txt b/shared/CMakeLists.txt
index 4ba24ea..2ef3970 100644
--- a/shared/CMakeLists.txt
+++ b/shared/CMakeLists.txt
@@ -18,7 +18,6 @@ set(SOURCE_FILES
 )
 
 set(HEADER_FILES
-  order.h
   shared.h
   enums.h
   global.h
diff --git a/shared/order.h b/shared/order.h
deleted file mode 100644
index fa9379b..0000000
--- a/shared/order.h
+++ /dev/null
@@ -1,154 +0,0 @@
-/*
- * Squawk messenger. 
- * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
- *
- * 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/>.
- */
-
-#ifndef ORDER_H
-#define ORDER_H
-
-#include <map>
-#include <list>
-
-#include "exception.h"
-
-namespace W 
-{
-    template <typename data_type, typename comparator = std::less<data_type>>
-    class Order
-    {
-    public:
-        class Duplicates: 
-            public Utils::Exception
-        {
-        public:
-            Duplicates():Exception(){}
-            
-            std::string getMessage() const{return "Inserting element duplicates existing";}
-        };
-        
-        class NotFound: 
-            public Utils::Exception
-        {
-        public:
-            NotFound():Exception(){}
-            
-            std::string getMessage() const{return "Erasing element haven't been found";}
-        };
-        
-    protected:
-        typedef std::list<data_type> List;
-        
-    public:
-        typedef typename List::size_type size_type;
-        typedef typename List::const_iterator const_iterator;
-        typedef typename List::iterator iterator;
-        
-    protected:
-        typedef std::map<data_type, const_iterator, comparator> Map;
-        typedef typename Map::const_iterator m_const_itr;
-        typedef typename Map::iterator m_itr;
-    
-    public:
-        Order():
-            order(),
-            r_map() 
-        {}
-        ~Order() {};
-        
-        size_type size() const {
-            return order.size();
-        }
-        
-        void push_back(data_type element) {
-            m_const_itr m_itr = r_map.find(element);
-            if (m_itr != r_map.end()) {
-                throw Duplicates();
-            }
-            
-            const_iterator itr = order.insert(order.end(), element);
-            r_map.insert(std::make_pair(element, itr));
-        }
-        
-        void erase(data_type element) {
-            m_const_itr itr = r_map.find(element);
-            if (itr == r_map.end()) {
-                throw NotFound();
-            }
-            order.erase(itr->second);
-            r_map.erase(itr);
-            
-        }
-        
-        void clear() {
-            order.clear();
-            r_map.clear();
-        }
-        
-        void insert(const_iterator pos, data_type element) {
-            m_const_itr m_itr = r_map.find(element);
-            if (m_itr != r_map.end()) {
-                throw Duplicates();
-            }
-            
-            const_iterator itr = order.insert(pos, element);
-            r_map.insert(std::make_pair(element, itr));
-        }
-        
-        void insert(iterator pos, data_type element) {
-            m_const_itr m_itr = r_map.find(element);
-            if (m_itr != r_map.end()) {
-                throw Duplicates();
-            }
-            
-            const_iterator itr = order.insert(pos, element);
-            r_map.insert(std::make_pair(element, itr));
-        }
-        
-        const_iterator find(data_type element) const {
-            m_const_itr itr = r_map.find(element);
-            
-            if (itr == r_map.end()) {
-                return end();
-            } else {
-                return itr->second;
-            }
-        }
-        
-        const_iterator begin() const {
-            return order.begin();
-        }
-        
-        const_iterator end() const {
-            return order.end();
-        }
-        
-        iterator begin() {
-            return order.begin();
-        }
-        
-        iterator end() {
-            return order.end();
-        }
-        
-    private:
-        List order;
-        Map r_map;
-    };
-}
-
-
-
-#endif // ORDER_H
diff --git a/ui/utils/badge.cpp b/ui/utils/badge.cpp
index b3b321a..d65b957 100644
--- a/ui/utils/badge.cpp
+++ b/ui/utils/badge.cpp
@@ -18,6 +18,8 @@
 
 #include "badge.h"
 
+#include "shared/utils.h"
+
 Badge::Badge(const QString& p_id, const QString& p_text, const QIcon& icon, QWidget* parent):
     QFrame(parent),
     id(p_id),
@@ -48,8 +50,7 @@ Badge::Badge(QWidget* parent):
     layout->addWidget(closeButton);
 }
 
-void Badge::setIcon(const QIcon& icon)
-{
+void Badge::setIcon(const QIcon& icon) {
     if (image == nullptr) {
         image = new QLabel();
         image->setPixmap(icon.pixmap(25, 25));
@@ -59,8 +60,7 @@ void Badge::setIcon(const QIcon& icon)
     }
 }
 
-void Badge::setText(const QString& p_text)
-{
+void Badge::setText(const QString& p_text) {
     if (text == nullptr) {
         text = new QLabel(p_text);
         int index = 0;
@@ -73,42 +73,30 @@ void Badge::setText(const QString& p_text)
     }
 }
 
-Badge::~Badge()
-{
-    if (image != nullptr) {
+Badge::~Badge() {
+    if (image != nullptr)
         delete image;
-    }
-    if (text != nullptr) {
+
+    if (text != nullptr)
         delete text;
-    }
+
     delete closeButton;
 }
 
-bool Badge::Comparator::operator()(const Badge* a, const Badge* b) const
-{
-    return a->id < b->id;
-}
-
-bool Badge::Comparator::operator()(const Badge& a, const Badge& b) const
-{
-    return a.id < b.id;
-}
-
-void Badge::createMandatoryComponents()
-{
+void Badge::createMandatoryComponents() {
     setBackgroundRole(QPalette::Base);
     //setAutoFillBackground(true);
     setFrameStyle(QFrame::StyledPanel);
     setFrameShadow(QFrame::Raised);
 
     QIcon tabCloseIcon = QIcon::fromTheme("tab-close");
-    if (tabCloseIcon.isNull()) {
+    if (tabCloseIcon.isNull())
         tabCloseIcon.addFile(QString::fromUtf8(":/images/fallback/dark/big/edit-none.svg"), QSize(), QIcon::Normal, QIcon::Off);
-    }
+
     closeButton->setIcon(tabCloseIcon);
 
     closeButton->setMaximumHeight(25);
     closeButton->setMaximumWidth(25);
     layout->setContentsMargins(2, 2, 2, 2);
-    connect(closeButton, &QPushButton::clicked, this, &Badge::close);
+    connect(closeButton, &QPushButton::clicked, this, &Badge::closeClicked);
 }
diff --git a/ui/utils/badge.h b/ui/utils/badge.h
index 52f4747..074eda0 100644
--- a/ui/utils/badge.h
+++ b/ui/utils/badge.h
@@ -16,8 +16,7 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#ifndef BADGE_H
-#define BADGE_H
+#pragma once
 
 #include <QFrame>
 #include <QLabel>
@@ -25,13 +24,7 @@
 #include <QIcon>
 #include <QPushButton>
 
-#include "shared/utils.h"
-
-/**
- * @todo write docs
- */
-class Badge : public QFrame
-{
+class Badge : public QFrame {
     Q_OBJECT
 public:
     Badge(const QString& id, const QString& text, const QIcon& icon, QWidget* parent = nullptr);
@@ -45,7 +38,7 @@ public:
     void setIcon(const QIcon& icon);
     
 signals:
-    void close();
+    void closeClicked();
     
 private:
     QLabel* image;
@@ -55,12 +48,5 @@ private:
 
 private:
     void createMandatoryComponents();
-    
-public:
-    struct Comparator {
-        bool operator()(const Badge& a, const Badge& b) const;
-        bool operator()(const Badge* a, const Badge* b) const;
-    };
-};
 
-#endif // BADGE_H
+};
diff --git a/ui/utils/flowlayout.h b/ui/utils/flowlayout.h
index 0e52c87..24830e7 100644
--- a/ui/utils/flowlayout.h
+++ b/ui/utils/flowlayout.h
@@ -16,8 +16,7 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#ifndef FLOWLAYOUT_H
-#define FLOWLAYOUT_H
+#pragma once
 
 #include <QLayout>
 #include <QWidget>
@@ -26,8 +25,7 @@
 /**
  * @todo write docs
  */
-class FlowLayout : public QLayout
-{
+class FlowLayout : public QLayout {
     Q_OBJECT
 public:
     explicit FlowLayout(QWidget *parent, int margin = -1, int hSpacing = -1, int vSpacing = -1);
@@ -55,5 +53,3 @@ private:
     int m_hSpace;
     int m_vSpace;
 };
-
-#endif // FLOWLAYOUT_H
diff --git a/ui/widgets/conversation.cpp b/ui/widgets/conversation.cpp
index 32c38a8..7214a40 100644
--- a/ui/widgets/conversation.cpp
+++ b/ui/widgets/conversation.cpp
@@ -33,6 +33,12 @@
 #include <QBitmap>
 
 #include <unistd.h>
+#include <algorithm>
+
+#include "shared/icons.h"
+#include "shared/utils.h"
+#include "shared/pathcheck.h"
+#include "shared/defines.h"
 
 constexpr QSize avatarSize(50, 50);
 
@@ -61,58 +67,9 @@ Conversation::Conversation(bool muc, Models::Account* acc, Models::Element* el,
     currentAction(CurrentAction::none),
     currentMessageId()
 {
-    m_ui->setupUi(this);
-    
-    shadow.setFrames(true, false, true, false);
-    
-    feed->setItemDelegate(delegate);
-    feed->setFrameShape(QFrame::NoFrame);
-    feed->setContextMenuPolicy(Qt::CustomContextMenu);
-    feed->setModel(el->feed);
-    el->feed->incrementObservers();
-    m_ui->widget->layout()->addWidget(feed);
-    
-    connect(el->feed, &Models::MessageFeed::newMessage, this, &Conversation::onFeedMessage);
-    connect(feed, &FeedView::resized, this, &Conversation::positionShadow);
-    connect(feed, &FeedView::customContextMenuRequested, this, &Conversation::onFeedContext);
-    
-    connect(acc, &Models::Account::childChanged, this, &Conversation::onAccountChanged);
-    
-    filesLayout = new FlowLayout(m_ui->filesPanel, 0);
-    m_ui->filesPanel->setLayout(filesLayout);
-    
-    statusIcon = m_ui->statusIcon;
-    statusLabel = m_ui->statusLabel;
-    
-    connect(&ker, &KeyEnterReceiver::enterPressed, this, qOverload<>(&Conversation::initiateMessageSending));
-    connect(&ker, &KeyEnterReceiver::imagePasted, this, &Conversation::onImagePasted);
-    connect(m_ui->sendButton, &QPushButton::clicked, this, qOverload<>(&Conversation::initiateMessageSending));
-    connect(m_ui->attachButton, &QPushButton::clicked, this, &Conversation::onAttach);
-    connect(m_ui->clearButton, &QPushButton::clicked, this, &Conversation::clear);
-    connect(m_ui->messageEditor->document()->documentLayout(), &QAbstractTextDocumentLayout::documentSizeChanged, 
-            this, &Conversation::onTextEditDocSizeChanged);
-    connect(m_ui->encryptionButton, &QPushButton::clicked, this, &Conversation::onEncryptionButtonClicked);
-    
-    m_ui->messageEditor->installEventFilter(&ker);
-
-    connect(m_ui->messageEditor, &QTextEdit::customContextMenuRequested, this, &Conversation::onMessageEditorContext);
-    connect(pasteImageAction, &QAction::triggered, this, &Conversation::onImagePasted);
-
-    connect(m_ui->currentActionBadge, &Badge::close, this, &Conversation::clear);
-    m_ui->currentActionBadge->setVisible(false);
-
-    m_ui->encryptionButton->setVisible(false);
-
-    //line->setAutoFillBackground(false);
-    //if (testAttribute(Qt::WA_TranslucentBackground)) {
-        //m_ui->scrollArea->setAutoFillBackground(false);
-    //} else {
-        //m_ui->scrollArea->setBackgroundRole(QPalette::Base);
-    //}
-    
-    //line->setMyAvatarPath(acc->getAvatarPath());
-    //line->setMyName(acc->getName());
-    
+    createUI();
+    createFeed();
+    subscribeEvents();
     initializeOverlay();
 }
 
@@ -123,6 +80,52 @@ Conversation::~Conversation() {
     delete m_ui;
 }
 
+void Conversation::createFeed() {
+    feed->setItemDelegate(delegate);
+    feed->setFrameShape(QFrame::NoFrame);
+    feed->setContextMenuPolicy(Qt::CustomContextMenu);
+    feed->setModel(element->feed);
+    element->feed->incrementObservers();
+    m_ui->widget->layout()->addWidget(feed);
+
+    connect(element->feed, &Models::MessageFeed::newMessage, this, &Conversation::onFeedMessage);
+    connect(feed, &FeedView::resized, this, &Conversation::positionShadow);
+    connect(feed, &FeedView::customContextMenuRequested, this, &Conversation::onFeedContext);
+}
+
+void Conversation::createUI() {
+    m_ui->setupUi(this);
+    statusIcon = m_ui->statusIcon;
+    statusLabel = m_ui->statusLabel;
+
+    filesLayout = new FlowLayout(m_ui->filesPanel, 0);
+    m_ui->filesPanel->setLayout(filesLayout);
+
+    m_ui->currentActionBadge->setVisible(false);
+    m_ui->encryptionButton->setVisible(false);
+
+    shadow.setFrames(true, false, true, false);
+}
+
+void Conversation::subscribeEvents() {
+    connect(account, &Models::Account::childChanged, this, &Conversation::onAccountChanged);
+    connect(&ker, &KeyEnterReceiver::enterPressed, this, qOverload<>(&Conversation::initiateMessageSending));
+    connect(&ker, &KeyEnterReceiver::imagePasted, this, &Conversation::onImagePasted);
+    connect(m_ui->sendButton, &QPushButton::clicked, this, qOverload<>(&Conversation::initiateMessageSending));
+    connect(m_ui->attachButton, &QPushButton::clicked, this, &Conversation::onAttach);
+    connect(m_ui->clearButton, &QPushButton::clicked, this, &Conversation::clear);
+    connect(m_ui->messageEditor->document()->documentLayout(), &QAbstractTextDocumentLayout::documentSizeChanged,
+            this, &Conversation::onTextEditDocSizeChanged);
+    connect(m_ui->encryptionButton, &QPushButton::clicked, this, &Conversation::onEncryptionButtonClicked);
+
+    m_ui->messageEditor->installEventFilter(&ker);
+
+    connect(m_ui->messageEditor, &QTextEdit::customContextMenuRequested, this, &Conversation::onMessageEditorContext);
+    connect(pasteImageAction, &QAction::triggered, this, &Conversation::onImagePasted);
+
+    connect(m_ui->currentActionBadge, &Badge::closeClicked, this, &Conversation::clear);
+}
+
 void Conversation::onAccountChanged(Models::Item* item, int row, int col) {
     SHARED_UNUSED(row);
     if (item == account) {
@@ -228,7 +231,7 @@ void Conversation::initiateMessageSending() {
         initiateMessageSending(msg);
     }
     if (filesToAttach.size() > 0) {
-        for (Badge* badge : filesToAttach) {
+        for (const Badge* badge : filesToAttach) {
             Shared::Message msg = createMessage();
             msg.setAttachPath(badge->id);
             element->feed->registerUpload(msg.getId());
@@ -291,6 +294,14 @@ Models::Roster::ElId Conversation::getId() const {
 }
 
 void Conversation::addAttachedFile(const QString& path) {
+    std::vector<Badge*>::const_iterator itr = std::find_if(
+        filesToAttach.begin(),
+        filesToAttach.end(),
+        [&path] (const Badge* badge) {return badge->id == path;}
+    );
+    if (itr != filesToAttach.end())
+        return;
+
     QMimeDatabase db;
     QMimeType type = db.mimeTypeForFile(path);
     QFileInfo info(path);
@@ -300,25 +311,23 @@ void Conversation::addAttachedFile(const QString& path) {
         fileIcon.addFile(QString::fromUtf8(":/images/fallback/dark/big/mail-attachment.svg"), QSize(), QIcon::Normal, QIcon::Off);
 
     Badge* badge = new Badge(path, info.fileName(), fileIcon);
+    filesToAttach.push_back(badge);
     
-    connect(badge, &Badge::close, this, &Conversation::onBadgeClose);
-    try {
-        filesToAttach.push_back(badge);
-        filesLayout->addWidget(badge);
-        if (filesLayout->count() == 1) {
-            filesLayout->setContentsMargins(3, 3, 3, 3);
-        }
-    } catch (const W::Order<Badge*, Badge::Comparator>::Duplicates& e) {
-        delete badge;
-    } catch (...) {
-        throw;
-    }
+    connect(badge, &Badge::closeClicked, this, &Conversation::onBadgeClose);
+    filesLayout->addWidget(badge);
+    if (filesLayout->count() == 1)
+        filesLayout->setContentsMargins(3, 3, 3, 3);
 }
 
-void Conversation::removeAttachedFile(Badge* badge) {
-    W::Order<Badge*, Badge::Comparator>::const_iterator itr = filesToAttach.find(badge);
+void Conversation::removeAttachedFile(const QString& id) {
+    std::vector<Badge*>::const_iterator itr = std::find_if(
+        filesToAttach.begin(),
+        filesToAttach.end(),
+        [&id] (const Badge* badge) {return badge->id == id;}
+    );
     if (itr != filesToAttach.end()) {
-        filesToAttach.erase(badge);
+        Badge* badge = *itr;
+        filesToAttach.erase(itr);
         if (filesLayout->count() == 1)
             filesLayout->setContentsMargins(0, 0, 0, 0);
 
@@ -328,7 +337,7 @@ void Conversation::removeAttachedFile(Badge* badge) {
 
 void Conversation::onBadgeClose() {
     Badge* badge = static_cast<Badge*>(sender());
-    removeAttachedFile(badge);
+    removeAttachedFile(badge->id);
 }
 
 void Conversation::clearAttachedFiles() {
@@ -351,12 +360,10 @@ void Conversation::onEncryptionButtonClicked() {}
 
 void Conversation::setAvatar(const QString& path) {
     QPixmap pixmap;
-    if (path.size() == 0) {
+    if (path.size() == 0)
         pixmap = Shared::icon("user", true).pixmap(avatarSize);
-    } else {
+    else
         pixmap = QPixmap(path).scaled(avatarSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
-    }
-
 
     QPixmap result(avatarSize);
     result.fill(Qt::transparent);
@@ -379,8 +386,7 @@ void Conversation::setFeedFrames(bool top, bool right, bool bottom, bool left) {
     shadow.setFrames(top, right, bottom, left);
 }
 
-void Conversation::dragEnterEvent(QDragEnterEvent* event)
-{
+void Conversation::dragEnterEvent(QDragEnterEvent* event) {
     bool accept = false;
     if (event->mimeData()->hasUrls()) {
         QList<QUrl> list = event->mimeData()->urls();
@@ -542,9 +548,8 @@ void Conversation::onMessageEditRequested(const QString& id) {
         currentAction = CurrentAction::edit;
         m_ui->messageEditor->setText(msg.getBody());
         QString path = msg.getAttachPath();
-        if (path.size() > 0) {
+        if (path.size() > 0)
             addAttachedFile(path);
-        }
 
     } catch (const Models::MessageFeed::NotFound& e) {
         qDebug() << "The message requested to be edited was not found" << e.getMessage().c_str();
diff --git a/ui/widgets/conversation.h b/ui/widgets/conversation.h
index 73e6536..bece7a8 100644
--- a/ui/widgets/conversation.h
+++ b/ui/widgets/conversation.h
@@ -16,8 +16,7 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#ifndef CONVERSATION_H
-#define CONVERSATION_H
+#pragma once
 
 #include <QWidget>
 #include <QObject>
@@ -30,12 +29,9 @@
 #include <QAction>
 #include <QDesktopServices>
 
+#include <vector>
+
 #include "shared/message.h"
-#include "shared/order.h"
-#include "shared/icons.h"
-#include "shared/utils.h"
-#include "shared/pathcheck.h"
-#include "shared/defines.h"
 
 #include "ui/models/account.h"
 #include "ui/models/roster.h"
@@ -96,7 +92,7 @@ protected:
     virtual Shared::Message createMessage() const;
     void setStatus(const QString& status);
     void addAttachedFile(const QString& path);
-    void removeAttachedFile(Badge* badge);
+    void removeAttachedFile(const QString& id);
     void clearAttachedFiles();
     void dragEnterEvent(QDragEnterEvent* event) override;
     void dragLeaveEvent(QDragLeaveEvent* event) override;
@@ -104,6 +100,10 @@ protected:
     void initializeOverlay();
     virtual void onMessage(const Shared::Message& msg);
     virtual void showEvent(QShowEvent * event) override;
+
+    void createFeed();
+    void createUI();
+    void subscribeEvents();
     
 protected slots:
     void initiateMessageSending();
@@ -142,7 +142,7 @@ protected:
     QLabel* statusLabel;
     FlowLayout* filesLayout;
     QWidget* overlay;
-    W::Order<Badge*, Badge::Comparator> filesToAttach;
+    std::vector<Badge*> filesToAttach;
     FeedView* feed;
     MessageDelegate* delegate;
     bool manualSliderChange;
@@ -161,5 +161,3 @@ private:
     static QPixmap* avatarPixmap;
     static QPainter* avatarPainter;
 };
-
-#endif // CONVERSATION_H
diff --git a/ui/widgets/room.cpp b/ui/widgets/room.cpp
index 3409a86..56ce5a0 100644
--- a/ui/widgets/room.cpp
+++ b/ui/widgets/room.cpp
@@ -18,6 +18,8 @@
 
 #include "room.h"
 
+#include "shared/defines.h"
+
 Room::Room(Models::Account* acc, Models::Room* p_room, QWidget* parent):
     Conversation(true, acc, p_room, p_room->getJid(), "", parent),
     room(p_room)

From e31ef78e7187d216365d27ad0c52ea2b1c3978f8 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Fri, 10 Nov 2023 19:26:16 -0300
Subject: [PATCH 257/281] some refactoring, some improvements

---
 core/account.cpp                 | 20 ++++---
 core/account.h                   |  7 +--
 core/contact.cpp                 |  4 ++
 core/contact.h                   | 13 ++---
 core/delayManager/manager.h      | 41 +++++++-------
 core/handlers/messagehandler.cpp | 10 ++--
 core/handlers/omemohandler.cpp   | 65 +++++++++++++---------
 core/handlers/omemohandler.h     | 38 +++++++------
 core/handlers/trusthandler.cpp   | 93 ++++++++++++++++++--------------
 external/qxmpp                   |  2 +-
 shared/enums.h                   |  8 +++
 shared/message.cpp               | 10 +++-
 ui/widgets/chat.cpp              | 24 ++++-----
 13 files changed, 188 insertions(+), 147 deletions(-)

diff --git a/core/account.cpp b/core/account.cpp
index a71bf4a..efb4a1a 100644
--- a/core/account.cpp
+++ b/core/account.cpp
@@ -266,6 +266,7 @@ void Core::Account::onClientStateChange(QXmppClient::State st) {
 #ifdef WITH_OMEMO
                         if (!oh->hasOwnDevice()) {
                             qDebug() << "setting up OMEMO data for account" << getName();
+                            om->changeDeviceLabel(QGuiApplication::applicationDisplayName() + " - " + QSysInfo::productType());
                             QXmppTask<bool> future = om->setUp();
                             future.then(this, [this] (bool result) {
                                 if (result)
@@ -342,9 +343,8 @@ void Core::Account::setAvailability(Shared::Availability avail) {
         QXmppPresence::AvailableStatusType pres = static_cast<QXmppPresence::AvailableStatusType>(avail);
         
         presence.setAvailableStatusType(pres);
-        if (state != Shared::ConnectionState::disconnected) {
+        if (state != Shared::ConnectionState::disconnected)
             client.setClientPresence(presence);
-        }
     }
 }
 
@@ -437,9 +437,9 @@ void Core::Account::onMamMessageReceived(const QString& queryId, const QXmppMess
 
 void Core::Account::requestArchive(const QString& jid, int count, const QString& before) {
     qDebug() << "An archive request for " << jid << ", before " << before;
-    RosterItem* contact = rh->getRosterItem(jid);
+    RosterItem* item = rh->getRosterItem(jid);
     
-    if (contact == nullptr) {
+    if (item == nullptr) {
         qDebug() << "An attempt to request archive for" << jid << "in account" << name << ", but the contact with such id wasn't found, skipping";
         emit responseArchive(jid, std::list<Shared::Message>(), true);
         return;
@@ -447,10 +447,18 @@ void Core::Account::requestArchive(const QString& jid, int count, const QString&
     
     if (state != Shared::ConnectionState::connected) {
         qDebug() << "An attempt to request archive for" << jid << "in account" << name << ", but the account is not online, skipping";
-        emit responseArchive(contact->jid, std::list<Shared::Message>(), false);
+        emit responseArchive(jid, std::list<Shared::Message>(), false);
+        return;
     }
     
-    contact->requestHistory(count, before);
+#ifdef WITH_OMEMO
+    if (!item->isMuc()) {
+        Contact* contact = static_cast<Contact*>(item);
+        if (contact->omemoBundles == Shared::Possible::unknown)
+            oh->requestBundles(jid);
+    }
+#endif
+    item->requestHistory(count, before);
 }
 
 void Core::Account::onContactNeedHistory(const QString& before, const QString& after, const QDateTime& at) {
diff --git a/core/account.h b/core/account.h
index 6442abf..b156059 100644
--- a/core/account.h
+++ b/core/account.h
@@ -15,9 +15,7 @@
  * You should have received a copy of the GNU General Public License
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
-
-#ifndef CORE_ACCOUNT_H
-#define CORE_ACCOUNT_H
+#pragma once
 
 #include <QObject>
 #include <QCryptographicHash>
@@ -248,6 +246,3 @@ private:
     void runDiscoveryService();
 };
 }
-
-
-#endif // CORE_ACCOUNT_H
diff --git a/core/contact.cpp b/core/contact.cpp
index 93139ef..f18dae3 100644
--- a/core/contact.cpp
+++ b/core/contact.cpp
@@ -24,6 +24,10 @@ Core::Contact::Contact(const QString& pJid, const QString& account, QObject* par
     groups(),
     subscriptionState(Shared::SubscriptionState::unknown),
     pep(Shared::Support::unknown)
+
+#ifdef WITH_OMEMO
+    ,omemoBundles(Shared::Possible::unknown)
+#endif
 {}
 
 Core::Contact::~Contact() {}
diff --git a/core/contact.h b/core/contact.h
index bde95f2..c23d0dc 100644
--- a/core/contact.h
+++ b/core/contact.h
@@ -16,8 +16,7 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#ifndef CORE_CONTACT_H
-#define CORE_CONTACT_H
+#pragma once
 
 #include <QObject>
 #include <QSet>
@@ -28,8 +27,7 @@
 
 namespace Core {
 
-class Contact : public RosterItem
-{
+class Contact : public RosterItem {
     Q_OBJECT
 public:
     Contact(const QString& pJid, const QString& account, QObject* parent = 0);
@@ -56,7 +54,10 @@ private:
     QSet<QString> groups;
     Shared::SubscriptionState subscriptionState;
     Shared::Support pep;
+
+#ifdef WITH_OMEMO
+public:
+    Shared::Possible omemoBundles;
+#endif
 };
 }
-
-#endif // CORE_CONTACT_H
diff --git a/core/delayManager/manager.h b/core/delayManager/manager.h
index 127d25a..5d04a8e 100644
--- a/core/delayManager/manager.h
+++ b/core/delayManager/manager.h
@@ -1,21 +1,21 @@
-// Squawk messenger. 
-// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
-// 
-// 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/>.
-
-#ifndef CORE_DELAYMANAGER_MANAGER_H
-#define CORE_DELAYMANAGER_MANAGER_H
+/*
+ * Squawk messenger.
+ * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+ *
+ * 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 <list>
 #include <set>
@@ -38,8 +38,7 @@
 namespace Core {
 namespace DelayManager {
 
-class Manager : public QObject
-{
+class Manager : public QObject {
     Q_OBJECT
 public:
     Manager(const QString& ownJid, Job::Id maxParallelJobs = 5, QObject* parent = nullptr);
@@ -146,5 +145,3 @@ public:
 
 }
 }
-
-#endif // CORE_DELAYMANAGER_MANAGER_H
diff --git a/core/handlers/messagehandler.cpp b/core/handlers/messagehandler.cpp
index 0d0c446..2c8526a 100644
--- a/core/handlers/messagehandler.cpp
+++ b/core/handlers/messagehandler.cpp
@@ -358,8 +358,8 @@ std::pair<Shared::Message::State, QString> Core::MessageHandler::scheduleSending
             const QXmppE2eeExtension::MessageEncryptResult& res = task.result();
             if (std::holds_alternative<std::unique_ptr<QXmppMessage>>(res)) {
                 const std::unique_ptr<QXmppMessage>& encrypted = std::get<std::unique_ptr<QXmppMessage>>(res);
-                encrypted->setBody("This message is encrypted with OMEMO 2 but could not be decrypted");
-                encrypted->setOutOfBandUrl("");
+                encrypted->setBody(QString());
+                encrypted->setOutOfBandUrl(QString());
                 bool success = acc->client.sendPacket(*encrypted.get());
                 if (success)
                     return {Shared::Message::State::sent, ""};
@@ -375,8 +375,8 @@ std::pair<Shared::Message::State, QString> Core::MessageHandler::scheduleSending
             task.then(this, [this, id] (QXmppE2eeExtension::MessageEncryptResult&& result) {
                 if (std::holds_alternative<std::unique_ptr<QXmppMessage>>(result)) {
                     const std::unique_ptr<QXmppMessage>& encrypted = std::get<std::unique_ptr<QXmppMessage>>(result);
-                    encrypted->setBody("This message is encrypted with OMEMO 2 but could not be decrypted");
-                    encrypted->setOutOfBandUrl("");
+                    encrypted->setBody(QString());
+                    encrypted->setOutOfBandUrl(QString());
                     bool success = acc->client.sendPacket(*encrypted.get());
                     if (success) {
                         std::tuple<bool, QString, QString> ids = getOriginalPendingMessageId(id, false);
@@ -602,7 +602,7 @@ void Core::MessageHandler::onUploadFileComplete(const std::list<Shared::MessageI
 
 void Core::MessageHandler::sendMessageWithLocalUploadedFile(Shared::Message msg, const QString& url, bool newMessage) {
     msg.setOutOfBandUrl(url);
-    if (msg.getBody().size() == 0)      //not sure why, but most messages do that
+    if (msg.getBody().size() == 0)      //not sure why, but most messengers do that
         msg.setBody(url);               //they duplicate oob in body, some of them wouldn't even show an attachment if you don't do that
 
     performSending(msg, pendingCorrectionMessages.at(msg.getId()), newMessage);
diff --git a/core/handlers/omemohandler.cpp b/core/handlers/omemohandler.cpp
index 2f9f70e..41cee76 100644
--- a/core/handlers/omemohandler.cpp
+++ b/core/handlers/omemohandler.cpp
@@ -52,6 +52,7 @@ QXmppTask<QXmppOmemoStorage::OmemoData> Core::OmemoHandler::allData() {
     OmemoData data;
     data.ownDevice = ownDevice;
 
+    // LMDBAL::Transaction txn = db.beginReadOnlyTransaction();     TODO need to enable transaction after fixing LMDBAL
     std::map<uint32_t, QByteArray> pkeys = preKeyPairs->readAll();
     for (const std::pair<const uint32_t, QByteArray>& pair : pkeys) {
         data.preKeyPairs.insert(pair.first, pair.second);
@@ -73,28 +74,30 @@ QXmppTask<QXmppOmemoStorage::OmemoData> Core::OmemoHandler::allData() {
 
 QXmppTask<void> Core::OmemoHandler::addDevice(const QString& jid, uint32_t deviceId, const QXmppOmemoStorage::Device& device) {
     QHash<uint32_t, Device> devs;
+    LMDBAL::WriteTransaction txn = db.beginTransaction();
     bool had = true;
     try {
-        devs = devices->getRecord(jid);
+        devices->getRecord(jid, devs, txn);
     } catch (const LMDBAL::NotFound& error) {
         had = false;
     }
 
-    devs.insert(deviceId, device);
-    if (had) {
-        devices->changeRecord(jid, devs);
-    } else {
-        devices->addRecord(jid, devs);
-    }
+    devs.insert(deviceId, device);  //overwrites
+    if (had)
+        devices->changeRecord(jid, devs, txn);
+    else
+        devices->addRecord(jid, devs, txn);
 
+    txn.commit();
     return Core::makeReadyTask();
 }
 
 QXmppTask<void> Core::OmemoHandler::addPreKeyPairs(const QHash<uint32_t, QByteArray>& keyPairs) {
-    for (QHash<uint32_t, QByteArray>::const_iterator itr = keyPairs.begin(), end = keyPairs.end(); itr != end; ++itr) {
-        preKeyPairs->forceRecord(itr.key(), itr.value());
-    }
+    LMDBAL::WriteTransaction txn = db.beginTransaction();
+    for (QHash<uint32_t, QByteArray>::const_iterator itr = keyPairs.begin(), end = keyPairs.end(); itr != end; ++itr)
+        preKeyPairs->forceRecord(itr.key(), itr.value(), txn);
 
+    txn.commit();
     return Core::makeReadyTask();
 }
 
@@ -104,13 +107,15 @@ QXmppTask<void> Core::OmemoHandler::addSignedPreKeyPair(uint32_t keyId, const QX
 }
 
 QXmppTask<void> Core::OmemoHandler::removeDevice(const QString& jid, uint32_t deviceId) {
-    QHash<uint32_t, Device> devs = devices->getRecord(jid);
+    LMDBAL::WriteTransaction txn = db.beginTransaction();
+    QHash<uint32_t, Device> devs = devices->getRecord(jid, txn);
     devs.remove(deviceId);
-    if (devs.isEmpty()) {
-        devices->removeRecord(jid);
-    } else {
-        devices->changeRecord(jid, devs);
-    }
+    if (devs.isEmpty())
+        devices->removeRecord(jid, txn);
+    else
+        devices->changeRecord(jid, devs, txn);
+
+    txn.commit();
     return Core::makeReadyTask();
 }
 
@@ -120,7 +125,11 @@ QXmppTask<void> Core::OmemoHandler::removeDevices(const QString& jid) {
 }
 
 QXmppTask<void> Core::OmemoHandler::removePreKeyPair(uint32_t keyId) {
-    preKeyPairs->removeRecord(keyId);
+    try {
+        preKeyPairs->removeRecord(keyId);
+    } catch (const LMDBAL::NotFound& e) {
+        qDebug() << "Couldn't remove preKeyPair " << e.what();
+    }
     return Core::makeReadyTask();
 }
 
@@ -135,15 +144,12 @@ QXmppTask<void> Core::OmemoHandler::setOwnDevice(const std::optional<OwnDevice>&
     bool had = ownDevice.has_value();
     ownDevice = device;
     if (ownDevice.has_value()) {
-        if (had) {
+        if (had)
             meta->changeRecord("ownDevice", QVariant::fromValue(ownDevice.value()));
-        } else {
+        else
             meta->addRecord("ownDevice", QVariant::fromValue(ownDevice.value()));
-        }
-    } else {
-        if (had) {
-            meta->removeRecord("ownDevice");
-        }
+    } else if (had) {
+        meta->removeRecord("ownDevice");
     }
     return Core::makeReadyTask();
 }
@@ -158,7 +164,7 @@ QXmppTask<void> Core::OmemoHandler::resetAll() {
 void Core::OmemoHandler::getDevices(const QString& jid, std::list<Shared::KeyInfo>& out) const {
     QHash<uint32_t, Device> devs;
     try {
-        devs = devices->getRecord(jid);
+        devices->getRecord(jid, devs);
     } catch (const LMDBAL::NotFound& error) {}
 
     for (QHash<uint32_t, Device>::const_iterator itr = devs.begin(), end = devs.end(); itr != end; ++itr) {
@@ -169,6 +175,10 @@ void Core::OmemoHandler::getDevices(const QString& jid, std::list<Shared::KeyInf
 
 void Core::OmemoHandler::requestBundles(const QString& jid) {
     QXmppTask<void> task = acc->om->buildMissingSessions({jid});
+    Contact* cnt = acc->rh->getContact(jid);
+    if (cnt)
+        cnt->omemoBundles = Shared::Possible::discovering;
+
     task.then(this, std::bind(&OmemoHandler::onBundlesReceived, this, jid));
 }
 
@@ -191,6 +201,10 @@ void Core::OmemoHandler::onBundlesReceived(const QString& jid) {
         }
     }
 
+    Contact* cnt = acc->rh->getContact(jid);
+    if (cnt)
+        cnt->omemoBundles = Shared::Possible::present;
+
     acc->delay->receivedBundles(jid, keys);
 }
 
@@ -217,7 +231,6 @@ void Core::OmemoHandler::onOmemoDeviceAdded(const QString& jid, uint32_t id) {
     qDebug() << "OMEMO device added for" << jid;
 }
 
-
 QDataStream & operator >> (QDataStream& in, QXmppOmemoStorage::Device& device) {
     in >> device.label;
     in >> device.keyId;
diff --git a/core/handlers/omemohandler.h b/core/handlers/omemohandler.h
index e626b4c..60fd8d1 100644
--- a/core/handlers/omemohandler.h
+++ b/core/handlers/omemohandler.h
@@ -1,21 +1,21 @@
-// Squawk messenger. 
-// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
-// 
-// 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/>.
-
-#ifndef CORE_OMEMOHANDLER_H
-#define CORE_OMEMOHANDLER_H
+/*
+ * Squawk messenger.
+ * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+ *
+ * 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 <map>
 #include <list>
@@ -87,5 +87,3 @@ QDataStream& operator >> (QDataStream &in, QXmppOmemoStorage::Device& device);
 
 QDataStream& operator << (QDataStream &out, const QXmppOmemoStorage::OwnDevice& device);
 QDataStream& operator >> (QDataStream &in, QXmppOmemoStorage::OwnDevice& device);
-
-#endif // CORE_OMEMOHANDLER_H
diff --git a/core/handlers/trusthandler.cpp b/core/handlers/trusthandler.cpp
index 21b3491..566265f 100644
--- a/core/handlers/trusthandler.cpp
+++ b/core/handlers/trusthandler.cpp
@@ -28,9 +28,8 @@ Core::TrustHandler::TrustHandler(Account* account):
     ownKeys(db.addCache<QString, QByteArray>("ownKeys")),
     keysByProtocol()
 {
-    if (!protocols.open(QIODevice::ReadWrite | QIODevice::Text)) {               //never supposed to happen since I have just created a directory;
+    if (!protocols.open(QIODevice::ReadWrite | QIODevice::Text))                //never supposed to happen since I have just created a directory;
         throw LMDBAL::Directory(protocols.fileName().toStdString());
-    }
 
     QTextStream in(&protocols);
     while(!in.atEnd()) {
@@ -53,11 +52,10 @@ Core::TrustHandler::~TrustHandler() {
 
 Core::TrustHandler::KeyCache * Core::TrustHandler::getCache(const QString& encryption) {
     std::map<QString, KeyCache*>::iterator itr = keysByProtocol.find(encryption);
-    if (itr == keysByProtocol.end()) {
+    if (itr == keysByProtocol.end())
         return createNewCache(encryption);
-    } else {
+    else
         return itr->second;
-    }
 }
 
 Core::TrustHandler::KeyCache * Core::TrustHandler::createNewCache(const QString& encryption) {
@@ -65,9 +63,9 @@ Core::TrustHandler::KeyCache * Core::TrustHandler::createNewCache(const QString&
     KeyCache* cache = db.addCache<QString, Keys>(encryption.toStdString());
     keysByProtocol.insert(std::make_pair(encryption, cache));
 
-    if(!protocols.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text)) {
+    if (!protocols.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text))
         throw LMDBAL::Directory(protocols.fileName().toStdString());
-    }
+
     QTextStream out(&protocols);
     out << encryption + "\n";
     protocols.close();
@@ -83,10 +81,12 @@ QXmppTask<void> Core::TrustHandler::resetAll(const QString& encryption) {
     if (itr == keysByProtocol.end())
         return Core::makeReadyTask();
 
+    LMDBAL::WriteTransaction txn = db.beginTransaction();
     KeyCache* cache = itr->second;
-    std::map<QString, Keys> keys = cache->readAll();
-    cache->drop();
+    std::map<QString, Keys> keys = cache->readAll(txn);
+    cache->drop(txn);
 
+    txn.commit();
     for (const std::pair<const QString, Keys>& pair : keys) {
         bool empty = true;
         for (const std::pair<const QByteArray, Shared::TrustLevel>& trust : pair.second) {
@@ -105,8 +105,8 @@ QXmppTask<void> Core::TrustHandler::resetAll(const QString& encryption) {
 QXmppTask<QXmpp::TrustLevel> Core::TrustHandler::trustLevel(
     const QString& encryption,
     const QString& keyOwnerJid,
-    const QByteArray& keyId)
-{
+    const QByteArray& keyId
+) {
     KeyCache* cache = getCache(encryption);
     Shared::TrustLevel level = Shared::TrustLevel::undecided;
     try {
@@ -122,15 +122,17 @@ QXmppTask<QHash<QString, QMultiHash<QString, QByteArray>>> Core::TrustHandler::s
     const QString& encryption,
     const QList<QString>& keyOwnerJids,
     QXmpp::TrustLevel oldTrustLevel,
-    QXmpp::TrustLevel newTrustLevel)
-{
+    QXmpp::TrustLevel newTrustLevel
+) {
     QHash<QString, QMultiHash<QString, QByteArray>> modifiedKeys;
     Shared::TrustLevel oldLevel = convert(oldTrustLevel);
     Shared::TrustLevel newLevel = convert(newTrustLevel);
     std::set<QString> modifiedJids;
     KeyCache* cache = getCache(encryption);
+
+    LMDBAL::WriteTransaction txn = db.beginTransaction();
     for (const QString& keyOwnerJid : keyOwnerJids) {
-        Keys map = cache->getRecord(keyOwnerJid);
+        Keys map = cache->getRecord(keyOwnerJid, txn);
         uint count = 0;
         for (std::pair<const QByteArray, Shared::TrustLevel>& pair : map) {
             Shared::TrustLevel& current = pair.second;
@@ -142,8 +144,9 @@ QXmppTask<QHash<QString, QMultiHash<QString, QByteArray>>> Core::TrustHandler::s
             }
         }
         if (count > 0)
-            cache->changeRecord(keyOwnerJid, map);
+            cache->changeRecord(keyOwnerJid, map, txn);
     }
+    txn.commit();
     for (const QString& jid : modifiedJids)
         emit trustLevelsChanged(jid, getSummary(jid));
 
@@ -153,18 +156,19 @@ QXmppTask<QHash<QString, QMultiHash<QString, QByteArray>>> Core::TrustHandler::s
 QXmppTask<QHash<QString, QMultiHash<QString, QByteArray>>> Core::TrustHandler::setTrustLevel(
     const QString& encryption,
     const QMultiHash<QString, QByteArray>& keyIds,
-    QXmpp::TrustLevel trustLevel)
-{
+    QXmpp::TrustLevel trustLevel
+) {
     QHash<QString, QMultiHash<QString, QByteArray>> modifiedKeys;
     Shared::TrustLevel level = convert(trustLevel);
     std::set<QString> modifiedJids;
     KeyCache* cache = getCache(encryption);
 
+    LMDBAL::WriteTransaction txn = db.beginTransaction();
     for (MultySB::const_iterator itr = keyIds.begin(), end = keyIds.end(); itr != end; ++itr) {
         const QString& keyOwnerJid = itr.key();
         const QByteArray& keyId = itr.value();
         try {
-            Keys map = cache->getRecord(keyOwnerJid);
+            Keys map = cache->getRecord(keyOwnerJid, txn);
             std::pair<Keys::iterator, bool> result = map.insert(std::make_pair(keyId, level));
             bool changed = result.second;
             if (!changed && result.first->second != level) {
@@ -174,15 +178,16 @@ QXmppTask<QHash<QString, QMultiHash<QString, QByteArray>>> Core::TrustHandler::s
             if (changed) {
                 modifiedKeys[encryption].insert(keyOwnerJid, keyId);
                 modifiedJids.insert(keyOwnerJid);
-                cache->changeRecord(keyOwnerJid, map);
+                cache->changeRecord(keyOwnerJid, map, txn);
             }
         } catch (const LMDBAL::NotFound& e) {
             Keys map({{keyId, level}});
             modifiedKeys[encryption].insert(keyOwnerJid, keyId);
             modifiedJids.insert(keyOwnerJid);
-            cache->addRecord(keyOwnerJid, map);
+            cache->addRecord(keyOwnerJid, map, txn);
         }
     }
+    txn.commit();
 
     for (const QString& jid : modifiedJids)
         emit trustLevelsChanged(jid, getSummary(jid));
@@ -190,10 +195,11 @@ QXmppTask<QHash<QString, QMultiHash<QString, QByteArray>>> Core::TrustHandler::s
     return Core::makeReadyTask(std::move(modifiedKeys));
 }
 
-QXmppTask<bool> Core::TrustHandler::hasKey(const QString& encryption,
-                                   const QString& keyOwnerJid,
-                                   QXmpp::TrustLevels trustLevels)
-{
+QXmppTask<bool> Core::TrustHandler::hasKey(
+    const QString& encryption,
+    const QString& keyOwnerJid,
+    QXmpp::TrustLevels trustLevels
+) {
     KeyCache* cache = getCache(encryption);
     bool found = false;
     try {
@@ -211,8 +217,8 @@ QXmppTask<bool> Core::TrustHandler::hasKey(const QString& encryption,
 QXmppTask<QHash<QString, QHash<QByteArray, QXmpp::TrustLevel>>> Core::TrustHandler::keys(
     const QString& encryption,
     const QList<QString>& keyOwnerJids,
-    QXmpp::TrustLevels trustLevels)
-{
+    QXmpp::TrustLevels trustLevels
+) {
     HSHBTL res;
 
     KeyCache* cache = getCache(encryption);
@@ -233,8 +239,8 @@ QXmppTask<QHash<QString, QHash<QByteArray, QXmpp::TrustLevel>>> Core::TrustHandl
 
 QXmppTask<QHash<QXmpp::TrustLevel, QMultiHash<QString, QByteArray>>> Core::TrustHandler::keys(
     const QString& encryption,
-    QXmpp::TrustLevels trustLevels)
-{
+    QXmpp::TrustLevels trustLevels
+) {
     QHash<TL, MultySB> res;
     KeyCache* cache = getCache(encryption);
     std::map<QString, Keys> storage = cache->readAll();
@@ -253,9 +259,11 @@ QXmppTask<void> Core::TrustHandler::removeKeys(const QString& encryption) {
     if (itr == keysByProtocol.end())
         return Core::makeReadyTask();
 
+    LMDBAL::WriteTransaction txn = db.beginTransaction();
     KeyCache* cache = itr->second;
-    std::map<QString, Keys> keys = cache->readAll();
-    cache->drop();
+    std::map<QString, Keys> keys = cache->readAll(txn);
+    cache->drop(txn);
+    txn.commit();
 
     for (const std::pair<const QString, Keys>& pair : keys) {
         bool empty = true;
@@ -292,8 +300,9 @@ QXmppTask<void> Core::TrustHandler::removeKeys(const QString& encryption, const
     for (const QByteArray& keyId : keyIds)
         set.insert(keyId);
 
+    LMDBAL::WriteTransaction txn = db.beginTransaction();
     KeyCache* cache = getCache(encryption);
-    std::map<QString, Keys> data = cache->readAll();
+    std::map<QString, Keys> data = cache->readAll(txn);
     std::set<QString> modifiedJids;
 
     for (std::map<QString, Keys>::iterator cItr = data.begin(), cEnd = data.end(); cItr != cEnd; /*no increment*/) {
@@ -311,10 +320,10 @@ QXmppTask<void> Core::TrustHandler::removeKeys(const QString& encryption, const
         else
             ++cItr;
     }
-    if (modifiedJids.size() > 0) {
-        cache->replaceAll(data);
-    }
+    if (modifiedJids.size() > 0)
+        cache->replaceAll(data, txn);
 
+    txn.commit();
     for (const QString& jid : modifiedJids)
         emit trustLevelsChanged(jid, getSummary(jid));
 
@@ -325,14 +334,15 @@ QXmppTask<void> Core::TrustHandler::addKeys(
     const QString& encryption,
     const QString& keyOwnerJid,
     const QList<QByteArray>& keyIds,
-    QXmpp::TrustLevel trustLevel)
-{
+    QXmpp::TrustLevel trustLevel
+) {
     KeyCache* cache = getCache(encryption);
     Shared::TrustLevel level = convert(trustLevel);
     Keys data;
     bool had = false;
+    LMDBAL::WriteTransaction txn = db.beginTransaction();
     try {
-        data = cache->getRecord(keyOwnerJid);
+        data = cache->getRecord(keyOwnerJid, txn);
         had = true;
     } catch (const LMDBAL::NotFound& e) {}
     for (const QByteArray& keyId : keyIds) {
@@ -342,10 +352,11 @@ QXmppTask<void> Core::TrustHandler::addKeys(
     }
 
     if (had)
-        cache->changeRecord(keyOwnerJid, data);
+        cache->changeRecord(keyOwnerJid, data, txn);
     else
-        cache->addRecord(keyOwnerJid, data);
+        cache->addRecord(keyOwnerJid, data, txn);
 
+    txn.commit();
     emit trustLevelsChanged(keyOwnerJid, getSummary(keyOwnerJid));
 
     return Core::makeReadyTask();
@@ -418,9 +429,9 @@ Shared::TrustSummary Core::TrustHandler::getSummary(const QString& jid) const {
         try {
             Keys keys = pair.second->getRecord(jid);
             Shared::EncryptionProtocol protocol = Shared::TrustSummary::protocolValues.at(pair.first);
-            for (const std::pair<const QByteArray, Shared::TrustLevel>& trust : keys) {
+            for (const std::pair<const QByteArray, Shared::TrustLevel>& trust : keys)
                 result.increment(protocol, trust.second);
-            }
+
         } catch (const LMDBAL::NotFound& e) {}
     }
 
diff --git a/external/qxmpp b/external/qxmpp
index 9e9c22b..8dda3c9 160000
--- a/external/qxmpp
+++ b/external/qxmpp
@@ -1 +1 @@
-Subproject commit 9e9c22b16a39c7370fed31c6deea56d8abf72440
+Subproject commit 8dda3c9921c34b9e33883b0d140e8de12edc0736
diff --git a/shared/enums.h b/shared/enums.h
index 6b668d6..43d1583 100644
--- a/shared/enums.h
+++ b/shared/enums.h
@@ -136,6 +136,14 @@ enum class Support {
 };
 Q_ENUM_NS(Support)
 
+enum class Possible {
+    unknown,
+    discovering,
+    present,
+    abscent
+};
+Q_ENUM_NS(Possible)
+
 enum class TrustLevel {
     /// The key's trust is not decided.
     undecided,
diff --git a/shared/message.cpp b/shared/message.cpp
index caf8a97..70fae63 100644
--- a/shared/message.cpp
+++ b/shared/message.cpp
@@ -268,7 +268,9 @@ QDataStream& operator<<(QDataStream& out, const Shared::Message& info) {
     out << info.rTo;
     out << info.id;
     out << info.body;
-    out << info.time;
+    quint64 msecs = info.time.toMSecsSinceEpoch();
+    out << msecs;
+
     out << info.thread;
     out << (quint8)info.type;
     out << info.outgoing;
@@ -297,7 +299,11 @@ QDataStream & operator>>(QDataStream& in, Shared::Message& info) {
     in >> info.rTo;
     in >> info.id;
     in >> info.body;
-    in >> info.time;
+
+    quint64 msecs;
+    in >> msecs;
+    info.time = QDateTime::fromMSecsSinceEpoch(msecs);
+
     in >> info.thread;
     quint8 t;
     in >> t;
diff --git a/ui/widgets/chat.cpp b/ui/widgets/chat.cpp
index 628f30f..b624a5d 100644
--- a/ui/widgets/chat.cpp
+++ b/ui/widgets/chat.cpp
@@ -32,10 +32,7 @@ Chat::Chat(Models::Account* acc, Models::Contact* p_contact, QWidget* parent):
     
     connect(contact, &Models::Contact::childChanged, this, &Chat::onContactChanged);
 #ifdef WITH_OMEMO
-    if (p_contact->hasKeys(Shared::EncryptionProtocol::omemo2)) {
-        m_ui->encryptionButton->setVisible(true);
-        updateEncryptionState();
-    }
+    updateEncryptionState();
 #endif
 }
 
@@ -59,9 +56,7 @@ void Chat::onContactChanged(Models::Item* item, int row, int col) {
                 setAvatar(contact->getAvatarPath());
                 break;
 #ifdef WITH_OMEMO
-            case 8:
-                m_ui->encryptionButton->setVisible(contact->hasKeys(Shared::EncryptionProtocol::omemo2));
-                break;
+            case 8: //[fallthrough]
             case 9:
                 updateEncryptionState();
                 break;
@@ -77,11 +72,16 @@ void Chat::updateState() {
 }
 
 void Chat::updateEncryptionState() {
-    m_ui->encryptionButton->setEnabled(true);
-    if (contact->getEncryption() == Shared::EncryptionProtocol::omemo2)
-        m_ui->encryptionButton->setIcon(Shared::icon("lock"));
-    else
-        m_ui->encryptionButton->setIcon(Shared::icon("unlock"));
+    if (contact->hasKeys(Shared::EncryptionProtocol::omemo2)) {
+        m_ui->encryptionButton->setVisible(true);
+        m_ui->encryptionButton->setEnabled(true);
+        if (contact->getEncryption() == Shared::EncryptionProtocol::omemo2)
+            m_ui->encryptionButton->setIcon(Shared::icon("lock"));
+        else
+            m_ui->encryptionButton->setIcon(Shared::icon("unlock"));
+    } else {
+        m_ui->encryptionButton->setVisible(false);
+    }
 }
 
 

From 19835af3cf1eaa1e4e60915fd60ebcb271d01900 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Sun, 12 Nov 2023 19:55:32 -0300
Subject: [PATCH 258/281] some debug, fix a crash removing a currently selected
 contact

---
 CMakeLists.txt                      |  5 +++++
 core/account.h                      |  2 +-
 core/delayManager/manager.cpp       |  4 ++--
 core/handlers/messagehandler.cpp    | 15 +++++++++++++--
 core/handlers/omemohandler.cpp      | 10 +++++++++-
 core/handlers/omemohandler.h        |  2 +-
 main/main.cpp                       |  2 +-
 packaging/Archlinux/PKGBUILD        |  2 +-
 ui/widgets/messageline/feedview.cpp | 12 +++++++-----
 9 files changed, 40 insertions(+), 14 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 552cba9..280fb22 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -136,11 +136,16 @@ if (NOT SYSTEM_QXMPP)
     target_include_directories(squawk PRIVATE ${CMAKE_SOURCE_DIR}/external/qxmpp/src/omemo)
     target_include_directories(squawk PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/external/qxmpp/src/omemo)
     set(BUILD_OMEMO ON)
+    set(BUILD_TESTS OFF)
   else ()
     set(BUILD_OMEMO OFF)
   endif ()
   add_subdirectory(external/qxmpp)
 
+  if (WITH_OMEMO)
+    target_include_directories(QXmppOmemoQt${QT_VERSION_MAJOR} PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/external/qxmpp/src)
+  endif()
+
   target_link_libraries(squawk PRIVATE QXmppQt${QT_VERSION_MAJOR})
   if (WITH_OMEMO)
     target_link_libraries(squawk PRIVATE QXmppOmemoQt${QT_VERSION_MAJOR})
diff --git a/core/account.h b/core/account.h
index b156059..47c5c7e 100644
--- a/core/account.h
+++ b/core/account.h
@@ -61,7 +61,7 @@
 #include "handlers/discoveryhandler.h"
 
 #ifdef WITH_OMEMO
-#include <Omemo/QXmppOmemoManager.h>
+#include <QXmppOmemoManager.h>
 #include <QXmppTrustManager.h>
 #include "handlers/trusthandler.h"
 #include "handlers/omemohandler.h"
diff --git a/core/delayManager/manager.cpp b/core/delayManager/manager.cpp
index 3281bc6..07d6431 100644
--- a/core/delayManager/manager.cpp
+++ b/core/delayManager/manager.cpp
@@ -368,9 +368,9 @@ void Core::DelayManager::Manager::receivedBundles(const QString& jid, const std:
     Job::Id jobId = itr->second;
     requestedBundles.erase(itr);
     std::map<Job::Id, Job*>::const_iterator jitr = runningJobs.find(jobId);
-    if (jitr == runningJobs.end()) {
+    if (jitr == runningJobs.end())
         throw JobNotFound(jobId, "receivedBundles");
-    }
+
     Job* jb = jitr->second;
     InfoForUser* job = dynamic_cast<InfoForUser*>(jb);
 
diff --git a/core/handlers/messagehandler.cpp b/core/handlers/messagehandler.cpp
index 2c8526a..f9b331f 100644
--- a/core/handlers/messagehandler.cpp
+++ b/core/handlers/messagehandler.cpp
@@ -357,28 +357,36 @@ std::pair<Shared::Message::State, QString> Core::MessageHandler::scheduleSending
         if (task.isFinished()) {
             const QXmppE2eeExtension::MessageEncryptResult& res = task.result();
             if (std::holds_alternative<std::unique_ptr<QXmppMessage>>(res)) {
+                qDebug() << "Successfully encrypted a message";
                 const std::unique_ptr<QXmppMessage>& encrypted = std::get<std::unique_ptr<QXmppMessage>>(res);
                 encrypted->setBody(QString());
                 encrypted->setOutOfBandUrl(QString());
                 bool success = acc->client.sendPacket(*encrypted.get());
-                if (success)
+                if (success) {
+                    qDebug() << "Successfully sent an encrypted message";
                     return {Shared::Message::State::sent, ""};
-                else
+                } else {
+                    qDebug() << "Couldn't sent an encrypted message";
                     return {Shared::Message::State::error, "Error sending successfully encrypted message"};
+                }
             } else if (std::holds_alternative<QXmppError>(res)) {
+                qDebug() << "Couldn't encrypt a message";
                 const QXmppError& err = std::get<QXmppError>(res);
                 return {Shared::Message::State::error, err.description};
             } else {
+                qDebug() << "Couldn't encrypt a message";
                 return {Shared::Message::State::error, "Unexpected error ecryptng the message"};
             }
         } else {
             task.then(this, [this, id] (QXmppE2eeExtension::MessageEncryptResult&& result) {
                 if (std::holds_alternative<std::unique_ptr<QXmppMessage>>(result)) {
+                    qDebug() << "Successfully encrypted a message";
                     const std::unique_ptr<QXmppMessage>& encrypted = std::get<std::unique_ptr<QXmppMessage>>(result);
                     encrypted->setBody(QString());
                     encrypted->setOutOfBandUrl(QString());
                     bool success = acc->client.sendPacket(*encrypted.get());
                     if (success) {
+                        qDebug() << "Successfully sent an encrypted message";
                         std::tuple<bool, QString, QString> ids = getOriginalPendingMessageId(id, false);
                         if (std::get<0>(ids)) {
                             QString id = std::get<1>(ids);
@@ -393,12 +401,15 @@ std::pair<Shared::Message::State, QString> Core::MessageHandler::scheduleSending
                             qDebug() << "Encrypted message has been successfully sent, but it couldn't be found to update the sate";
                         }
                     } else {
+                        qDebug() << "Couldn't sent an encrypted message";
                         handlePendingMessageError(id, "Error sending successfully encrypted message");
                     }
                 } else if (std::holds_alternative<QXmppError>(result)) {
+                    qDebug() << "Couldn't encrypt a message";
                     const QXmppError& err = std::get<QXmppError>(result);
                     handlePendingMessageError(id, err.description);
                 } else {
+                    qDebug() << "Couldn't encrypt a message";
                     handlePendingMessageError(id, "Unexpected error ecryptng the message");
                 }
             });
diff --git a/core/handlers/omemohandler.cpp b/core/handlers/omemohandler.cpp
index 41cee76..7f7c53b 100644
--- a/core/handlers/omemohandler.cpp
+++ b/core/handlers/omemohandler.cpp
@@ -169,7 +169,15 @@ void Core::OmemoHandler::getDevices(const QString& jid, std::list<Shared::KeyInf
 
     for (QHash<uint32_t, Device>::const_iterator itr = devs.begin(), end = devs.end(); itr != end; ++itr) {
         const Device& dev = itr.value();
-        out.emplace_back(itr.key(), dev.keyId, dev.label, QDateTime(), Shared::TrustLevel::undecided, Shared::EncryptionProtocol::omemo2, false);
+        out.emplace_back(
+            itr.key(),
+            dev.keyId,
+            dev.label,
+            dev.removalFromDeviceListDate,
+            Shared::TrustLevel::undecided,
+            Shared::EncryptionProtocol::omemo2,
+            false
+        );
     }
 }
 
diff --git a/core/handlers/omemohandler.h b/core/handlers/omemohandler.h
index 60fd8d1..2d46546 100644
--- a/core/handlers/omemohandler.h
+++ b/core/handlers/omemohandler.h
@@ -21,7 +21,7 @@
 #include <list>
 #include <functional>
 
-#include <Omemo/QXmppOmemoStorage.h>
+#include <QXmppOmemoStorage.h>
 #include <cache.h>
 
 #include <shared/keyinfo.h>
diff --git a/main/main.cpp b/main/main.cpp
index 06c2c95..f810dbb 100644
--- a/main/main.cpp
+++ b/main/main.cpp
@@ -24,7 +24,7 @@
 #include <QObject>
 
 #ifdef WITH_OMEMO
-#include <Omemo/QXmppOmemoStorage.h>
+#include <QXmppOmemoStorage.h>
 #endif
 
 int main(int argc, char *argv[]) {
diff --git a/packaging/Archlinux/PKGBUILD b/packaging/Archlinux/PKGBUILD
index 6e63901..bd9c981 100644
--- a/packaging/Archlinux/PKGBUILD
+++ b/packaging/Archlinux/PKGBUILD
@@ -1,6 +1,6 @@
 # Maintainer: Yury Gubich <blue@macaw.me>
 pkgname=squawk
-pkgver=0.2.2
+pkgver=0.2.3
 pkgrel=1
 pkgdesc="An XMPP desktop messenger, written on pure c++ (qt)"
 arch=('i686' 'x86_64')
diff --git a/ui/widgets/messageline/feedview.cpp b/ui/widgets/messageline/feedview.cpp
index 43d0218..41e4484 100644
--- a/ui/widgets/messageline/feedview.cpp
+++ b/ui/widgets/messageline/feedview.cpp
@@ -133,10 +133,13 @@ void FeedView::dataChanged(const QModelIndex& topLeft, const QModelIndex& bottom
 
 void FeedView::updateGeometries() {
     //qDebug() << "updateGeometries";
-    QScrollBar* bar = verticalScrollBar();
-    
-    const QStyle* st = style();
     const QAbstractItemModel* m = model();
+    if (m == nullptr)
+        return QAbstractItemView::updateGeometries();
+
+    QScrollBar* bar = verticalScrollBar();
+    const QStyle* st = style();
+
     QSize layoutBounds = maximumViewportSize();
     QStyleOptionViewItem option = viewOptions();
     option.rect.setHeight(maxMessageHeight);
@@ -210,9 +213,8 @@ void FeedView::updateGeometries() {
     
     positionProgress();
     
-    if (specialDelegate) {
+    if (specialDelegate)
         clearWidgetsMode = true;
-    }
     
     QAbstractItemView::updateGeometries();
 }

From 00af58228724e2cfb39467f887ad9220de6cfedd Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Mon, 13 Nov 2023 19:05:26 -0300
Subject: [PATCH 259/281] Own omemo key display, a bit of CMake clean up

---
 CMakeLists.txt                        |  73 +++++++--------
 core/delayManager/manager.cpp         |  70 +++++++-------
 core/handlers/messagehandler.cpp      |   5 +-
 core/handlers/messagehandler.h        |  13 +--
 core/handlers/omemohandler.cpp        |  83 +++++++++--------
 core/handlers/omemohandler.h          |   1 +
 shared/keyinfo.h                      |  40 ++++----
 ui/utils/expandinglist.cpp            |  81 +++++++++-------
 ui/utils/expandinglist.h              |  48 +++++-----
 ui/widgets/info/info.cpp              |  32 ++++---
 ui/widgets/info/info.h                |  37 ++++----
 ui/widgets/info/omemo/keydelegate.cpp |  32 ++++---
 ui/widgets/info/omemo/keydelegate.h   |  46 ++++-----
 ui/widgets/info/omemo/omemo.cpp       |  71 +++++++++-----
 ui/widgets/info/omemo/omemo.h         |  43 +++++----
 ui/widgets/info/omemo/omemo.ui        | 128 +++++++++++++++++---------
 16 files changed, 443 insertions(+), 360 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 280fb22..edf9297 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -47,7 +47,7 @@ find_package(Boost COMPONENTS)
 
 target_include_directories(squawk PRIVATE ${Boost_INCLUDE_DIRS})
 
-#OMEMO
+## OMEMO
 if (WITH_OMEMO)
   find_package(PkgConfig)
   if (PKG_CONFIG_FOUND)
@@ -65,22 +65,6 @@ if (WITH_OMEMO)
   endif ()
 endif ()
 
-## QXmpp
-if (SYSTEM_QXMPP)
-  if (WITH_OMEMO)
-    find_package(QXmpp CONFIG COMPONENTS Omemo)
-  else ()
-    find_package(QXmpp CONFIG)
-  endif ()
-
-  if (NOT QXmpp_FOUND)
-    set(SYSTEM_QXMPP OFF)
-    message("QXmpp package wasn't found, trying to build with bundled QXmpp")
-  else ()
-    message("Building with system QXmpp")
-  endif ()
-endif ()
-
 ## KIO
 if (WITH_KIO)
   find_package(KF5KIO CONFIG)
@@ -107,6 +91,7 @@ if (WITH_KWALLET)
   endif ()
 endif ()
 
+## KConfig
 if (WITH_KCONFIG)
   find_package(KF5Config CONFIG)
   if (NOT KF5Config_FOUND)
@@ -125,7 +110,22 @@ if (WITH_KCONFIG)
   endif()
 endif()
 
-if (NOT SYSTEM_QXMPP)
+## QXmpp
+if (SYSTEM_QXMPP)
+  if (WITH_OMEMO)
+    find_package(QXmpp CONFIG COMPONENTS Omemo)
+  else ()
+    find_package(QXmpp CONFIG)
+  endif ()
+
+  if (NOT QXmpp_FOUND)
+    set(SYSTEM_QXMPP OFF)
+    message("QXmpp package wasn't found, trying to build with bundled QXmpp")
+  else ()
+    message("Building with system QXmpp")
+  endif ()
+endif ()                #it's endif() + if() and not else() because I want it to have a fallback behaviour
+if (NOT SYSTEM_QXMPP)   #we can fail finding system QXmpp and this way we'll check bundled before failing completely
   message("Building with bundled QXmpp")
 
   target_include_directories(squawk PRIVATE ${CMAKE_SOURCE_DIR}/external/qxmpp/src/base)
@@ -141,19 +141,10 @@ if (NOT SYSTEM_QXMPP)
     set(BUILD_OMEMO OFF)
   endif ()
   add_subdirectory(external/qxmpp)
-
+  add_library(QXmpp::QXmpp ALIAS QXmppQt${QT_VERSION_MAJOR})
   if (WITH_OMEMO)
     target_include_directories(QXmppOmemoQt${QT_VERSION_MAJOR} PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/external/qxmpp/src)
-  endif()
-
-  target_link_libraries(squawk PRIVATE QXmppQt${QT_VERSION_MAJOR})
-  if (WITH_OMEMO)
-    target_link_libraries(squawk PRIVATE QXmppOmemoQt${QT_VERSION_MAJOR})
-  endif ()
-else ()
-  target_link_libraries(squawk PRIVATE QXmpp::QXmpp)
-  if (WITH_OMEMO)
-    target_link_libraries(squawk PRIVATE QXmpp::Omemo)
+    add_library(QXmpp::Omemo ALIAS QXmppOmemoQt${QT_VERSION_MAJOR})
   endif ()
 endif ()
 
@@ -166,18 +157,14 @@ if (SYSTEM_LMDBAL)
   else ()
     message("Building with system LMDBAL")
   endif ()
-endif()
-
-if (NOT SYSTEM_LMDBAL)
+else()
   message("Building with bundled LMDBAL")
   set(BUILD_STATIC ON)
   add_subdirectory(external/lmdbal)
   add_library(LMDBAL::LMDBAL ALIAS LMDBAL)
 endif()
 
-target_link_libraries(squawk PRIVATE LMDBAL::LMDBAL)
-
-# Linking
+## Linking
 target_link_libraries(squawk
   PRIVATE
   Qt${QT_VERSION_MAJOR}::Core
@@ -186,17 +173,23 @@ target_link_libraries(squawk
   Qt${QT_VERSION_MAJOR}::Network
   Qt${QT_VERSION_MAJOR}::Gui
   Qt${QT_VERSION_MAJOR}::Xml
+  LMDBAL::LMDBAL
+  QXmpp::QXmpp
+  simpleCrypt
 )
-target_link_libraries(squawk PRIVATE lmdb)
-target_link_libraries(squawk PRIVATE simpleCrypt)
-# Link thread libraries on Linux
+
+if (WITH_OMEMO)
+  target_link_libraries(squawk PRIVATE QXmpp::Omemo)
+endif ()
+
+## Link thread libraries on Linux
 if(UNIX AND NOT APPLE)
   set(THREADS_PREFER_PTHREAD_FLAG ON)
   find_package(Threads REQUIRED)
   target_link_libraries(squawk PRIVATE Threads::Threads)
 endif()
 
-# Build type
+## Build type
 if (NOT CMAKE_BUILD_TYPE)
   set(CMAKE_BUILD_TYPE Debug)
 endif ()
@@ -228,7 +221,7 @@ add_subdirectory(shared)
 add_subdirectory(translations)
 add_subdirectory(ui)
 
-# Install the executable
+## Install the executable
 install(TARGETS squawk DESTINATION ${CMAKE_INSTALL_BINDIR})
 install(FILES README.md DESTINATION ${CMAKE_INSTALL_DATADIR}/macaw.me/squawk)
 install(FILES LICENSE.md DESTINATION ${CMAKE_INSTALL_DATADIR}/macaw.me/squawk)
diff --git a/core/delayManager/manager.cpp b/core/delayManager/manager.cpp
index 07d6431..de37d47 100644
--- a/core/delayManager/manager.cpp
+++ b/core/delayManager/manager.cpp
@@ -1,18 +1,20 @@
-// Squawk messenger. 
-// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
-// 
-// 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/>.
+/*
+ * Squawk messenger.
+ * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+ *
+ * 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 "manager.h"
 
@@ -39,17 +41,14 @@ Core::DelayManager::Manager::Manager(const QString& poj, Job::Id mpj, QObject* p
     requestedBundles(),
 #endif
     ownJid(poj)
-{
-}
+{}
 
 Core::DelayManager::Manager::~Manager() {
-    for (const std::pair<const Job::Id, Job*>& pair : runningJobs) {
+    for (const std::pair<const Job::Id, Job*>& pair : runningJobs)
         delete pair.second;
-    }
 
-    for (Job* job : jobSequence) {
+    for (Job* job : jobSequence)
         delete job;
-    }
 }
 
 Core::DelayManager::Job::Id Core::DelayManager::Manager::getNextJobId() {
@@ -151,11 +150,10 @@ void Core::DelayManager::Manager::preScheduleJob(Job* job) {
 
 void Core::DelayManager::Manager::scheduleJob(Job* job) {
     preScheduleJob(job);
-    if (runningJobs.size() < maxParallelJobs) {
+    if (runningJobs.size() < maxParallelJobs)
         executeJob(job);
-    } else {
+    else
         scheduledJobs.push_back(job);
-    }
 }
 
 void Core::DelayManager::Manager::preExecuteJob(Job* job) {
@@ -190,9 +188,9 @@ void Core::DelayManager::Manager::executeJob(Job* job) {
 
 void Core::DelayManager::Manager::jobIsDone(Job::Id jobId) {
     std::map<Job::Id, Job*>::const_iterator itr = runningJobs.find(jobId);
-    if (itr == runningJobs.end()) {
+    if (itr == runningJobs.end())
         throw JobNotFound(jobId, "jobIsDone");
-    }
+
     Job* job = itr->second;
     delete job;
     runningJobs.erase(itr);
@@ -292,9 +290,9 @@ void Core::DelayManager::Manager::receivedVCard(const QString& jid, const Shared
     Job::Id jobId = cardItr->second;
     requestedVCards.erase(cardItr);
     std::map<Job::Id, Job*>::const_iterator itr = runningJobs.find(jobId);
-    if (itr == runningJobs.end()) {
+    if (itr == runningJobs.end())
         throw JobNotFound(jobId, "receivedVCard");
-    }
+
     Job* job = itr->second;
 
     switch (job->type) {
@@ -330,9 +328,9 @@ void Core::DelayManager::Manager::receivedOwnVCard(const Shared::VCard& card) {
     Job::Id jobId = ownVCardJobId;
     ownVCardJobId = 0;
     std::map<Job::Id, Job*>::const_iterator itr = runningJobs.find(jobId);
-    if (itr == runningJobs.end()) {
+    if (itr == runningJobs.end())
         throw JobNotFound(jobId, "receivedOwnVCard");
-    }
+
     Job* job = itr->second;
     switch (job->type) {
         case Job::Type::ownCardInternal:
@@ -388,9 +386,9 @@ void Core::DelayManager::Manager::receivedOwnBundles(const std::list<Shared::Key
     Job::Id jobId = ownInfoJobId;
     ownInfoJobId = 0;
     std::map<Job::Id, Job*>::const_iterator jitr = runningJobs.find(jobId);
-    if (jitr == runningJobs.end()) {
+    if (jitr == runningJobs.end())
         throw JobNotFound(jobId, "receivedOwnBundles");
-    }
+
     Job* jb = jitr->second;
     OwnInfoForUser* job = dynamic_cast<OwnInfoForUser*>(jb);
 
@@ -414,9 +412,9 @@ Core::DelayManager::Manager::UnexpectedJobType::UnexpectedJobType(Job::Type p_ty
 std::string Core::DelayManager::Manager::UnexpectedJobType::getMessage() const{
     std::string msg("Unexpected job type: ");
     msg += Job::TypeString[static_cast<int>(type)];
-    if (method.size() > 0) {
+    if (method.size() > 0)
         msg += " in method " + method;
-    }
+
     return msg;
 }
 
@@ -430,8 +428,8 @@ std::string Core::DelayManager::Manager::JobNotFound::getMessage() const {
     std::string msg("Job with id ");
     msg += std::to_string(id);
     msg += " was not found";
-    if (method.size() > 0) {
+    if (method.size() > 0)
         msg += " in method " + method;
-    }
+
     return msg;
 }
diff --git a/core/handlers/messagehandler.cpp b/core/handlers/messagehandler.cpp
index f9b331f..8489dba 100644
--- a/core/handlers/messagehandler.cpp
+++ b/core/handlers/messagehandler.cpp
@@ -147,10 +147,9 @@ bool Core::MessageHandler::handleGroupMessage(const QXmppMessage& msg, bool outg
         initializeMessage(sMsg, msg, outgoing, forwarded, guessing);
         QString jid = sMsg.getPenPalJid();
         Conference* cnt = acc->rh->getConference(jid);
-        if (cnt == 0) {
+        if (cnt == 0)
             return false;
-        }
-        
+
         std::tuple<bool, QString, QString> ids = getOriginalPendingMessageId(msg.id());
         if (std::get<0>(ids)) {
             QMap<QString, QVariant> cData = {{"state", static_cast<uint>(Shared::Message::State::delivered)}};
diff --git a/core/handlers/messagehandler.h b/core/handlers/messagehandler.h
index 99a77d3..e3199dc 100644
--- a/core/handlers/messagehandler.h
+++ b/core/handlers/messagehandler.h
@@ -16,8 +16,7 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#ifndef CORE_MESSAGEHANDLER_H
-#define CORE_MESSAGEHANDLER_H
+#pragma once
 
 #include <QObject>
 
@@ -36,15 +35,9 @@
 #include <shared/pathcheck.h>
 
 namespace Core {
-
-/**
- * @todo write docs
- */
-
 class Account;
 
-class MessageHandler : public QObject
-{
+class MessageHandler : public QObject {
     Q_OBJECT
 public:
     MessageHandler(Account* account);
@@ -90,5 +83,3 @@ private:
 };
 
 }
-
-#endif // CORE_MESSAGEHANDLER_H
diff --git a/core/handlers/omemohandler.cpp b/core/handlers/omemohandler.cpp
index 7f7c53b..eaa674f 100644
--- a/core/handlers/omemohandler.cpp
+++ b/core/handlers/omemohandler.cpp
@@ -1,18 +1,20 @@
-// Squawk messenger. 
-// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
-// 
-// 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/>.
+/*
+ * Squawk messenger.
+ * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+ *
+ * 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 <QDebug>
 #include "omemohandler.h"
@@ -196,18 +198,7 @@ void Core::OmemoHandler::requestOwnBundles() {
 }
 
 void Core::OmemoHandler::onBundlesReceived(const QString& jid) {
-    std::list<Shared::KeyInfo> keys;
-    acc->oh->getDevices(jid, keys);
-    std::map<QByteArray, Shared::TrustLevel> trustLevels = acc->th->getKeys(Shared::EncryptionProtocol::omemo2, jid);
-
-    qDebug() << "OMEMO info for " << jid << " devices:" << keys.size() << ", trustLevels:" << trustLevels.size();
-    for (Shared::KeyInfo& key : keys) {
-        std::map<QByteArray, Shared::TrustLevel>::const_iterator itr = trustLevels.find(key.fingerPrint);
-        if (itr != trustLevels.end()) {
-            key.trustLevel = itr->second;
-            qDebug() << "Found a trust level for a device!";
-        }
-    }
+    std::list<Shared::KeyInfo> keys = readKeys(jid);
 
     Contact* cnt = acc->rh->getContact(jid);
     if (cnt)
@@ -217,23 +208,35 @@ void Core::OmemoHandler::onBundlesReceived(const QString& jid) {
 }
 
 void Core::OmemoHandler::onOwnBundlesReceived() {
-    QString jid = acc->getBareJid();
-    std::list<Shared::KeyInfo> keys;
-    acc->oh->getDevices(jid, keys);
-    std::map<QByteArray, Shared::TrustLevel> trustLevels = acc->th->getKeys(Shared::EncryptionProtocol::omemo2, jid);
-
-    qDebug() << "OMEMO info for " << jid << " devices:" << keys.size() << ", trustLevels:" << trustLevels.size();
-    for (Shared::KeyInfo& key : keys) {
-        std::map<QByteArray, Shared::TrustLevel>::const_iterator itr = trustLevels.find(key.fingerPrint);
-        if (itr != trustLevels.end()) {
-            key.trustLevel = itr->second;
-            qDebug() << "Found a trust level for a device!";
-        }
-    }
+    std::list<Shared::KeyInfo> keys = readKeys(acc->getBareJid());
+    if (ownDevice)
+        keys.emplace_front(
+            ownDevice->id,
+            ownDevice->publicIdentityKey,
+            ownDevice->label,
+            QDateTime::currentDateTime(),
+            Shared::TrustLevel::authenticated,
+            Shared::EncryptionProtocol::omemo2,
+            true
+        );
 
     acc->delay->receivedOwnBundles(keys);
 }
 
+std::list<Shared::KeyInfo> Core::OmemoHandler::readKeys(const QString& jid) {
+    std::list<Shared::KeyInfo> keys;
+    getDevices(jid, keys);
+    std::map<QByteArray, Shared::TrustLevel> trustLevels = acc->th->getKeys(Shared::EncryptionProtocol::omemo2, jid);
+
+    for (Shared::KeyInfo& key : keys) {
+        std::map<QByteArray, Shared::TrustLevel>::const_iterator itr = trustLevels.find(key.fingerPrint);
+        if (itr != trustLevels.end())
+            key.trustLevel = itr->second;
+    }
+
+    return keys;
+}
+
 void Core::OmemoHandler::onOmemoDeviceAdded(const QString& jid, uint32_t id) {
     SHARED_UNUSED(id);
     qDebug() << "OMEMO device added for" << jid;
diff --git a/core/handlers/omemohandler.h b/core/handlers/omemohandler.h
index 2d46546..99bfe38 100644
--- a/core/handlers/omemohandler.h
+++ b/core/handlers/omemohandler.h
@@ -69,6 +69,7 @@ public slots:
 private slots:
     void onBundlesReceived(const QString& jid);
     void onOwnBundlesReceived();
+    std::list<Shared::KeyInfo> readKeys(const QString& jid);
 
 private:
     Account* acc;
diff --git a/shared/keyinfo.h b/shared/keyinfo.h
index 8d7db0a..500a2c1 100644
--- a/shared/keyinfo.h
+++ b/shared/keyinfo.h
@@ -1,21 +1,22 @@
-// Squawk messenger. 
-// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
-// 
-// 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/>.
+/*
+ * Squawk messenger.
+ * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+ *
+ * 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/>.
+ */
 
-#ifndef SHARED_KEYINFO_H
-#define SHARED_KEYINFO_H
+#pragma once
 
 #include <QString>
 #include <QByteArray>
@@ -27,8 +28,7 @@
 
 namespace Shared {
 
-class KeyInfo
-{
+class KeyInfo {
 public:
     KeyInfo(
         uint32_t id,
@@ -56,5 +56,3 @@ public:
 };
 
 }
-
-#endif // SHARED_KEYINFO_H
diff --git a/ui/utils/expandinglist.cpp b/ui/utils/expandinglist.cpp
index 6d1546d..5cc9bad 100644
--- a/ui/utils/expandinglist.cpp
+++ b/ui/utils/expandinglist.cpp
@@ -1,43 +1,60 @@
-// Squawk messenger. 
-// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
-// 
-// 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/>.
+/*
+ * Squawk messenger.
+ * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+ *
+ * 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 "expandinglist.h"
 
-QSize ExpandingList::viewportSizeHint() const {
-    if (QAbstractItemView::sizeAdjustPolicy() != QAbstractScrollArea::AdjustToContents)
-        return QListView::viewportSizeHint();
+#include <QApplication>
 
-    if (model() == nullptr)
-        return QSize(0, 0);
-    if (model()->rowCount() == 0)
-        return QSize(0, 0);
+ExpandingList::ExpandingList(QWidget* parent) :
+    QListView(parent),
+    scrollBarExtent(qApp->style()->pixelMetric(QStyle::PM_ScrollBarExtent))
+{
+    setVerticalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAlwaysOff);
+}
 
+bool ExpandingList::hasHeightForWidth() const {
 #if (QT_VERSION < QT_VERSION_CHECK(6, 2, 0))
-    const int rowCount = model()->rowCount();
-    int height = 0;
-    for (int i = 0; i < rowCount; i++) {
-        height += QListView::sizeHintForRow(i);
-    }
-    return QSize(QListView::viewportSizeHint().width(), height);
+    return
+        QAbstractItemView::sizeAdjustPolicy() == QAbstractScrollArea::AdjustToContents &&
+        scrollBarExtent > 0;
 #else
-    return QListView::viewportSizeHint();
+    return false;
 #endif
 }
 
-QSize ExpandingList::minimumSizeHint() const {
-    return viewportSizeHint();
-}
+int ExpandingList::heightForWidth(int width) const {
+    QAbstractItemModel* md = model();
 
+    if (md == nullptr)
+        return 0;
+    if (md->rowCount() == 0)
+        return 0;
+
+    const int rowCount = md->rowCount();
+    int height = 0;
+    for (int i = 0; i < rowCount; i++)
+        height += QListView::sizeHintForRow(i);
+
+    height += frameWidth();
+    int minWidth = sizeHintForColumn(0) + frameWidth() + 1;
+
+    if (width <= minWidth)
+        height += scrollBarExtent + 1;
+
+    return height;
+}
diff --git a/ui/utils/expandinglist.h b/ui/utils/expandinglist.h
index 0b29e89..9084757 100644
--- a/ui/utils/expandinglist.h
+++ b/ui/utils/expandinglist.h
@@ -1,31 +1,35 @@
-// Squawk messenger. 
-// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
-// 
-// 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/>.
+/*
+ * Squawk messenger.
+ * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+ *
+ * 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/>.
+ */
 
-#ifndef EXPANDINGLIST_H
-#define EXPANDINGLIST_H
+#pragma once
 
 #include <QListView>
 
 class ExpandingList : public QListView {
     Q_OBJECT
 public:
-    using QListView::QListView; //explicit constructor inheriatnce
+    ExpandingList(QWidget* parent = nullptr);
 
-    QSize viewportSizeHint() const override;
-    QSize minimumSizeHint() const override;
+    bool hasHeightForWidth() const override;
+    int heightForWidth(int width) const override;
+    // QSize viewportSizeHint() const override;
+    // QSize minimumSizeHint() const override;
+
+private:
+    int scrollBarExtent;
 };
-
-#endif // EXPANDINGLIST_H
diff --git a/ui/widgets/info/info.cpp b/ui/widgets/info/info.cpp
index 6a1105d..948ee27 100644
--- a/ui/widgets/info/info.cpp
+++ b/ui/widgets/info/info.cpp
@@ -1,18 +1,20 @@
-// Squawk messenger. 
-// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
-// 
-// 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/>.
+/*
+ * Squawk messenger.
+ * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+ *
+ * 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 "info.h"
 #include "ui_info.h"
diff --git a/ui/widgets/info/info.h b/ui/widgets/info/info.h
index 10bc063..7d52693 100644
--- a/ui/widgets/info/info.h
+++ b/ui/widgets/info/info.h
@@ -1,21 +1,22 @@
-// Squawk messenger. 
-// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
-// 
-// 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/>.
+/*
+ * Squawk messenger.
+ * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+ *
+ * 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/>.
+ */
 
-#ifndef UI_WIDGETS_INFO_H
-#define UI_WIDGETS_INFO_H
+#pragma once
 
 #include <list>
 
@@ -88,5 +89,3 @@ private:
 };
 
 }
-
-#endif // UI_WIDGETS_INFO_H
diff --git a/ui/widgets/info/omemo/keydelegate.cpp b/ui/widgets/info/omemo/keydelegate.cpp
index fd687ad..074ef1c 100644
--- a/ui/widgets/info/omemo/keydelegate.cpp
+++ b/ui/widgets/info/omemo/keydelegate.cpp
@@ -1,18 +1,20 @@
-// Squawk messenger. 
-// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
-// 
-// 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/>.
+/*
+ * Squawk messenger.
+ * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+ *
+ * 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 "keydelegate.h"
 #include <QPainter>
diff --git a/ui/widgets/info/omemo/keydelegate.h b/ui/widgets/info/omemo/keydelegate.h
index 01b45c1..6d7f067 100644
--- a/ui/widgets/info/omemo/keydelegate.h
+++ b/ui/widgets/info/omemo/keydelegate.h
@@ -1,35 +1,37 @@
-// Squawk messenger. 
-// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
-// 
-// 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/>.
+/*
+ * Squawk messenger.
+ * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+ *
+ * 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/>.
+ */
 
-#ifndef UI_KEYDELEGATE_H
-#define UI_KEYDELEGATE_H
+#pragma once
 
 #include <QStyledItemDelegate>
 #include <QString>
+
 #include <vector>
 
 namespace UI {
 
-class KeyDelegate : public QStyledItemDelegate
-{
+class KeyDelegate : public QStyledItemDelegate {
+    Q_OBJECT
 public:
-    KeyDelegate(QObject *parent = nullptr);
+    KeyDelegate(QObject* parent = nullptr);
     ~KeyDelegate();
 
-    QSize sizeHint(const QStyleOptionViewItem & option, const QModelIndex & index) const override;
+    QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const override;
     void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override;
 
 private:
@@ -46,5 +48,3 @@ private:
 };
 
 }
-
-#endif // UI_KEYDELEGATE_H
diff --git a/ui/widgets/info/omemo/omemo.cpp b/ui/widgets/info/omemo/omemo.cpp
index d3722fe..294fbe2 100644
--- a/ui/widgets/info/omemo/omemo.cpp
+++ b/ui/widgets/info/omemo/omemo.cpp
@@ -1,18 +1,20 @@
-// Squawk messenger. 
-// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
-// 
-// 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/>.
+/*
+ * Squawk messenger.
+ * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+ *
+ * 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 "omemo.h"
 #include "ui_omemo.h"
@@ -23,25 +25,31 @@ constexpr uint8_t fingerprintLength = 32;
 UI::Omemo::Omemo(QWidget* parent):
     QWidget(parent),
     m_ui(new Ui::Omemo()),
+    deviceKeyDelegate(),
     keysDelegate(),
     unusedKeysDelegate(),
+    deviceKeyModel(),
     keysModel(),
     unusedKeysModel(),
     contextMenu(new QMenu())
 {
     m_ui->setupUi(this);
 
+    m_ui->deviceKeyView->setItemDelegate(&deviceKeyDelegate);
+    m_ui->deviceKeyView->setModel(&deviceKeyModel);
     m_ui->keysView->setItemDelegate(&keysDelegate);
     m_ui->keysView->setModel(&keysModel);
     m_ui->unusedKeysView->setItemDelegate(&unusedKeysDelegate);
     m_ui->unusedKeysView->setModel(&unusedKeysModel);
 
+    unusedVisibility(false);
+    deviceKeyVisibility(false);
+
     m_ui->keysView->setContextMenuPolicy(Qt::CustomContextMenu);
     connect(m_ui->keysView, &QWidget::customContextMenuRequested, this, &Omemo::onActiveKeysContextMenu);
 }
 
-UI::Omemo::~Omemo()
-{
+UI::Omemo::~Omemo() {
     contextMenu->deleteLater();
 }
 
@@ -50,9 +58,9 @@ void UI::Omemo::generateMockData() {
     std::uniform_int_distribution<char> dist(CHAR_MIN, CHAR_MAX);
     for (int i = 0; i < 5; ++i) {
         QByteArray fp(fingerprintLength, 0);
-        for (int i = 0; i < fingerprintLength; ++i) {
+        for (int i = 0; i < fingerprintLength; ++i)
             fp[i] = dist(rd);
-        }
+
         Shared::KeyInfo info;
         info.id = i;
         if (i % 3 == 0)
@@ -68,14 +76,21 @@ void UI::Omemo::generateMockData() {
 void UI::Omemo::setData(const std::list<Shared::KeyInfo>& keys) {
     keysModel.clear();
     unusedKeysModel.clear();
+    deviceKeyModel.clear();
     for (const Shared::KeyInfo& key : keys) {
-        keysModel.addKey(key);
+        if (key.currentDevice)
+            deviceKeyModel.addKey(key);
+        else
+            keysModel.addKey(key);
     }
+
+    unusedVisibility(unusedKeysModel.rowCount() > 0);
+    deviceKeyVisibility(deviceKeyModel.rowCount() > 0);
 }
 
 const QString UI::Omemo::title() const {
-    return m_ui->OMEMOHeading->text();}
-
+    return m_ui->OMEMOHeading->text();
+}
 
 void UI::Omemo::onActiveKeysContextMenu(const QPoint& pos) {
     contextMenu->clear();
@@ -119,3 +134,15 @@ void UI::Omemo::onActiveKeysContextMenu(const QPoint& pos) {
 
     contextMenu->popup(m_ui->keysView->viewport()->mapToGlobal(pos));
 }
+
+void UI::Omemo::deviceKeyVisibility(bool visible) {
+    m_ui->deviceKeyHeading->setVisible(visible);
+    m_ui->deviceKeyline->setVisible(visible);
+    m_ui->deviceKeyView->setVisible(visible);
+}
+
+void UI::Omemo::unusedVisibility(bool visible) {
+    m_ui->unusedKeysView->setVisible(visible);
+    m_ui->unusedKeysHeading->setVisible(visible);
+    m_ui->line->setVisible(visible);
+}
diff --git a/ui/widgets/info/omemo/omemo.h b/ui/widgets/info/omemo/omemo.h
index a279492..75b1df2 100644
--- a/ui/widgets/info/omemo/omemo.h
+++ b/ui/widgets/info/omemo/omemo.h
@@ -1,21 +1,22 @@
-// Squawk messenger. 
-// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
-// 
-// 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/>.
+/*
+ * Squawk messenger.
+ * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+ *
+ * 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/>.
+ */
 
-#ifndef VCARD_OMEMO_H
-#define VCARD_OMEMO_H
+#pragma once
 
 #include <list>
 
@@ -29,8 +30,7 @@
 #include "shared/keyinfo.h"
 
 namespace UI {
-namespace Ui
-{
+namespace Ui {
 class Omemo;
 }
 
@@ -48,14 +48,17 @@ private slots:
 
 private:
     void generateMockData();
+    void unusedVisibility(bool visible);
+    void deviceKeyVisibility(bool visible);
 
 private:
     QScopedPointer<Ui::Omemo> m_ui;
+    UI::KeyDelegate deviceKeyDelegate;
     UI::KeyDelegate keysDelegate;
     UI::KeyDelegate unusedKeysDelegate;
+    Models::Keys deviceKeyModel;
     Models::Keys keysModel;
     Models::Keys unusedKeysModel;
     QMenu* contextMenu;
 };
 }
-#endif // VCARD_OMEMO_H
diff --git a/ui/widgets/info/omemo/omemo.ui b/ui/widgets/info/omemo/omemo.ui
index 745b981..64361f9 100644
--- a/ui/widgets/info/omemo/omemo.ui
+++ b/ui/widgets/info/omemo/omemo.ui
@@ -64,7 +64,28 @@
        </rect>
       </property>
       <layout class="QGridLayout" name="gridLayout" columnstretch="1,3,1">
-       <item row="1" column="1">
+       <item row="7" column="1">
+        <widget class="QLabel" name="unusedKeysHeading">
+         <property name="font">
+          <font>
+           <pointsize>16</pointsize>
+           <weight>75</weight>
+           <bold>true</bold>
+          </font>
+         </property>
+         <property name="text">
+          <string>Unused keys</string>
+         </property>
+        </widget>
+       </item>
+       <item row="6" column="1">
+        <widget class="Line" name="line">
+         <property name="orientation">
+          <enum>Qt::Horizontal</enum>
+         </property>
+        </widget>
+       </item>
+       <item row="4" column="1">
         <widget class="ExpandingList" name="keysView">
          <property name="sizePolicy">
           <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
@@ -89,42 +110,7 @@
          </property>
         </widget>
        </item>
-       <item row="3" column="1">
-        <widget class="Line" name="line">
-         <property name="orientation">
-          <enum>Qt::Horizontal</enum>
-         </property>
-        </widget>
-       </item>
-       <item row="0" column="1">
-        <widget class="QLabel" name="keysHeading">
-         <property name="font">
-          <font>
-           <pointsize>16</pointsize>
-           <weight>75</weight>
-           <bold>true</bold>
-          </font>
-         </property>
-         <property name="text">
-          <string>Active keys</string>
-         </property>
-        </widget>
-       </item>
-       <item row="4" column="1">
-        <widget class="QLabel" name="unusedKeysHeading">
-         <property name="font">
-          <font>
-           <pointsize>16</pointsize>
-           <weight>75</weight>
-           <bold>true</bold>
-          </font>
-         </property>
-         <property name="text">
-          <string>Unused keys</string>
-         </property>
-        </widget>
-       </item>
-       <item row="5" column="1">
+       <item row="8" column="1">
         <widget class="ExpandingList" name="unusedKeysView">
          <property name="sizePolicy">
           <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
@@ -149,8 +135,68 @@
          </property>
         </widget>
        </item>
-       <item row="0" column="0" rowspan="6">
-        <spacer name="spacerLeft">
+       <item row="0" column="1">
+        <widget class="QLabel" name="deviceKeyHeading">
+         <property name="font">
+          <font>
+           <pointsize>16</pointsize>
+           <weight>75</weight>
+           <bold>true</bold>
+          </font>
+         </property>
+         <property name="text">
+          <string>Device key</string>
+         </property>
+        </widget>
+       </item>
+       <item row="3" column="1">
+        <widget class="QLabel" name="keysHeading">
+         <property name="font">
+          <font>
+           <pointsize>16</pointsize>
+           <weight>75</weight>
+           <bold>true</bold>
+          </font>
+         </property>
+         <property name="text">
+          <string>Active keys</string>
+         </property>
+        </widget>
+       </item>
+       <item row="1" column="1">
+        <widget class="ExpandingList" name="deviceKeyView">
+         <property name="sizePolicy">
+          <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+           <horstretch>0</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
+         <property name="sizeAdjustPolicy">
+          <enum>QAbstractScrollArea::AdjustToContents</enum>
+         </property>
+         <property name="showDropIndicator" stdset="0">
+          <bool>false</bool>
+         </property>
+         <property name="verticalScrollMode">
+          <enum>QAbstractItemView::ScrollPerPixel</enum>
+         </property>
+         <property name="horizontalScrollMode">
+          <enum>QAbstractItemView::ScrollPerPixel</enum>
+         </property>
+         <property name="uniformItemSizes">
+          <bool>false</bool>
+         </property>
+        </widget>
+       </item>
+       <item row="2" column="1">
+        <widget class="Line" name="deviceKeyline">
+         <property name="orientation">
+          <enum>Qt::Horizontal</enum>
+         </property>
+        </widget>
+       </item>
+       <item row="0" column="2" rowspan="9">
+        <spacer name="spacerRight">
          <property name="orientation">
           <enum>Qt::Horizontal</enum>
          </property>
@@ -162,8 +208,8 @@
          </property>
         </spacer>
        </item>
-       <item row="0" column="2" rowspan="6">
-        <spacer name="spacerRight">
+       <item row="0" column="0" rowspan="9">
+        <spacer name="spacerLeft">
          <property name="orientation">
           <enum>Qt::Horizontal</enum>
          </property>

From 75554c74511e1bb8b74f56cb2f1159a7f177527d Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Tue, 14 Nov 2023 20:23:39 -0300
Subject: [PATCH 260/281] refactorng

---
 cmake/FindLMDB.cmake                  |  52 ----
 cmake/FindSignal.cmake                |  15 --
 core/components/archive.cpp           | 105 ++++----
 core/components/archive.h             |   5 +-
 core/components/clientcache.cpp       |  32 +--
 core/components/clientcache.h         |  38 ++-
 core/components/networkaccess.cpp     |   5 +-
 core/components/networkaccess.h       |   8 +-
 core/components/urlstorage.cpp        |   3 +-
 core/components/urlstorage.h          |   5 +-
 core/delayManager/cardinternal.cpp    |  32 +--
 core/delayManager/cardinternal.h      |  38 ++-
 core/delayManager/contact.cpp         |  32 +--
 core/delayManager/contact.h           |  37 ++-
 core/delayManager/info.cpp            |  32 +--
 core/delayManager/info.h              |  37 ++-
 core/delayManager/infoforuser.cpp     |  32 +--
 core/delayManager/infoforuser.h       |  37 ++-
 core/delayManager/job.cpp             |  32 +--
 core/delayManager/job.h               |  37 ++-
 core/delayManager/owncardinternal.cpp |  32 +--
 core/delayManager/owncardinternal.h   |  37 ++-
 core/delayManager/owninfoforuser.cpp  |  32 +--
 core/delayManager/owninfoforuser.h    |  37 ++-
 core/handlers/messagehandler.cpp      | 340 ++++++++++++--------------
 core/handlers/messagehandler.h        |   4 +-
 core/handlers/omemohandler.cpp        |  14 +-
 core/handlers/rosterhandler.cpp       |  14 +-
 core/handlers/rosterhandler.h         |   9 +-
 shared/messageinfo.h                  |   9 +-
 30 files changed, 515 insertions(+), 627 deletions(-)
 delete mode 100644 cmake/FindLMDB.cmake
 delete mode 100644 cmake/FindSignal.cmake

diff --git a/cmake/FindLMDB.cmake b/cmake/FindLMDB.cmake
deleted file mode 100644
index d6f2cd3..0000000
--- a/cmake/FindLMDB.cmake
+++ /dev/null
@@ -1,52 +0,0 @@
-#This file is taken from here https://gitlab.ralph.or.at/causal-rt/causal-cpp/, it was GPLv3 license
-#Thank you so much, mr. Ralph Alexander Bariz, I hope you don't mind me using your code
-
-# Try to find LMDB headers and library.
-#
-# Usage of this module as follows:
-#
-#     find_package(LMDB)
-#
-# Variables used by this module, they can change the default behaviour and need
-# to be set before calling find_package:
-#
-#  LMDB_ROOT_DIR          Set this variable to the root installation of
-#                            LMDB if the module has problems finding the
-#                            proper installation path.
-#
-# Variables defined by this module:
-#
-#  LMDB_FOUND               System has LMDB library/headers.
-#  LMDB_LIBRARIES           The LMDB library.
-#  LMDB_INCLUDE_DIRS        The location of LMDB headers.
-
-find_path(LMDB_ROOT_DIR
-  NAMES include/lmdb.h
-  )
-
-find_library(LMDB_LIBRARIES
-  NAMES liblmdb.a liblmdb.so liblmdb.so.a liblmdb.dll.a # We want lmdb to be static, if possible
-  HINTS ${LMDB_ROOT_DIR}/lib
-  )
-
-add_library(lmdb UNKNOWN IMPORTED)
-set_target_properties(lmdb PROPERTIES
-    IMPORTED_LOCATION ${LMDB_LIBRARIES}
-    )
-
-find_path(LMDB_INCLUDE_DIRS
-  NAMES lmdb.h
-  HINTS ${LMDB_ROOT_DIR}/include
-  )
-
-include(FindPackageHandleStandardArgs)
-find_package_handle_standard_args(LMDB DEFAULT_MSG
-  LMDB_LIBRARIES
-  LMDB_INCLUDE_DIRS
-  )
-
-mark_as_advanced(
-  LMDB_ROOT_DIR
-  LMDB_LIBRARIES
-  LMDB_INCLUDE_DIRS
-)
diff --git a/cmake/FindSignal.cmake b/cmake/FindSignal.cmake
deleted file mode 100644
index 752fed7..0000000
--- a/cmake/FindSignal.cmake
+++ /dev/null
@@ -1,15 +0,0 @@
-find_path(Signal_INCLUDE_DIR NAMES signal/signal_protocol.h)
-find_library(Signal_LIBRARY signal-protocol-c)
-mark_as_advanced(Signal_INCLUDE_DIR Signal_LIBRARY)
-
-include(FindPackageHandleStandardArgs)
-find_package_handle_standard_args(Signal REQUIRED_VARS Signal_LIBRARY Signal_INCLUDE_DIR)
-
-if (Signal_FOUND AND NOT TARGET Signal::Signal)
-  add_library(Signal::Signal UNKNOWN IMPORTED)
-  set_target_properties(Signal::Signal PROPERTIES
-    IMPORTED_LINK_INTERFACE_LANGUAGES "C"
-    IMPORTED_LOCATION "${Signal_LIBRARY}"
-    INTERFACE_INCLUDE_DIRECTORIES "${Signal_INCLUDE_DIR}"
-    )
-endif ()
diff --git a/core/components/archive.cpp b/core/components/archive.cpp
index 67f6693..2f7139a 100644
--- a/core/components/archive.cpp
+++ b/core/components/archive.cpp
@@ -207,7 +207,6 @@ unsigned int Core::Archive::addElements(const std::list<Shared::Message>& messag
         ++success;
     }
     txn.commit();
-    
     return success;
 }
 
@@ -315,63 +314,63 @@ bool Core::Archive::setAvatar(const QByteArray& data, AvatarInfo& newInfo, bool
         avatars->removeRecord(res, txn);
         txn.commit();
         return true;
-    } else {
-        QString currentPath = db.getPath();
-        bool needToRemoveOld = false;
-        QCryptographicHash hash(QCryptographicHash::Sha1);
-        hash.addData(data);
-        QByteArray newHash(hash.result());
-        if (haveAvatar) {
-            if (!generated && !oldInfo.autogenerated && oldInfo.hash == newHash)
-                return false;
+    }
 
-            QFile oldAvatar(currentPath + "/" + res + "." + oldInfo.type);
-            if (oldAvatar.exists()) {
-                if (oldAvatar.rename(currentPath + "/" + res + "." + oldInfo.type + ".bak")) {
-                    needToRemoveOld = true;
-                } else {
-                    qDebug() << "Can't change avatar: couldn't get rid of the old avatar" << oldAvatar.fileName();
-                    return false;
-                }
-            }
-        }
-        QMimeDatabase mimedb;
-        QMimeType type = mimedb.mimeTypeForData(data);
-        QString ext = type.preferredSuffix();
-        QFile newAvatar(currentPath + "/" + res + "." + ext);
-        if (newAvatar.open(QFile::WriteOnly)) {
-            newAvatar.write(data);
-            newAvatar.close();
-            
-            newInfo.type = ext;
-            newInfo.hash = newHash;
-            newInfo.autogenerated = generated;
-            try {
-                avatars->forceRecord(res, newInfo, txn);
-                txn.commit();
-            } catch (...) {
-                qDebug() << "Can't change avatar: couldn't store changes to database for" << newAvatar.fileName() << "rolling back to the previous state";
-                if (needToRemoveOld) {
-                    QFile oldAvatar(currentPath + "/" + res + "." + oldInfo.type + ".bak");
-                    oldAvatar.rename(currentPath + "/" + res + "." + oldInfo.type);
-                }
+    QString currentPath = db.getPath();
+    bool needToRemoveOld = false;
+    QCryptographicHash hash(QCryptographicHash::Sha1);
+    hash.addData(data);
+    QByteArray newHash(hash.result());
+    if (haveAvatar) {
+        if (!generated && !oldInfo.autogenerated && oldInfo.hash == newHash)
+            return false;
+
+        QFile oldAvatar(currentPath + "/" + res + "." + oldInfo.type);
+        if (oldAvatar.exists()) {
+            if (oldAvatar.rename(currentPath + "/" + res + "." + oldInfo.type + ".bak")) {
+                needToRemoveOld = true;
+            } else {
+                qDebug() << "Can't change avatar: couldn't get rid of the old avatar" << oldAvatar.fileName();
                 return false;
             }
-            
-            if (needToRemoveOld) {
-                QFile oldAvatar(currentPath + "/" + res + "." + oldInfo.type + ".bak");
-                oldAvatar.remove();
-            }
-            return true;
-        } else {
-            qDebug() << "Can't change avatar: cant open file to write" << newAvatar.fileName() << "rolling back to the previous state";
-            if (needToRemoveOld) {
-                QFile oldAvatar(currentPath + "/" + res + "." + oldInfo.type + ".bak");
-                oldAvatar.rename(currentPath + "/" + res + "." + oldInfo.type);
-            }
-            return false;
         }
     }
+    QMimeDatabase mimedb;
+    QMimeType type = mimedb.mimeTypeForData(data);
+    QString ext = type.preferredSuffix();
+    QFile newAvatar(currentPath + "/" + res + "." + ext);
+    if (!newAvatar.open(QFile::WriteOnly)) {
+        qDebug() << "Can't change avatar: cant open file to write" << newAvatar.fileName() << "rolling back to the previous state";
+        if (needToRemoveOld) {
+            QFile oldAvatar(currentPath + "/" + res + "." + oldInfo.type + ".bak");
+            oldAvatar.rename(currentPath + "/" + res + "." + oldInfo.type);
+        }
+        return false;
+    }
+
+    newAvatar.write(data);
+    newAvatar.close();
+
+    newInfo.type = ext;
+    newInfo.hash = newHash;
+    newInfo.autogenerated = generated;
+    try {
+        avatars->forceRecord(res, newInfo, txn);
+        txn.commit();
+    } catch (...) {
+        qDebug() << "Can't change avatar: couldn't store changes to database for" << newAvatar.fileName() << "rolling back to the previous state";
+        if (needToRemoveOld) {
+            QFile oldAvatar(currentPath + "/" + res + "." + oldInfo.type + ".bak");
+            oldAvatar.rename(currentPath + "/" + res + "." + oldInfo.type);
+        }
+        return false;
+    }
+
+    if (needToRemoveOld) {
+        QFile oldAvatar(currentPath + "/" + res + "." + oldInfo.type + ".bak");
+        oldAvatar.remove();
+    }
+    return true;
 }
 
 bool Core::Archive::readAvatarInfo(Core::Archive::AvatarInfo& target, const QString& resource) const {
diff --git a/core/components/archive.h b/core/components/archive.h
index 08f508a..6a2be73 100644
--- a/core/components/archive.h
+++ b/core/components/archive.h
@@ -16,8 +16,7 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#ifndef CORE_ARCHIVE_H
-#define CORE_ARCHIVE_H
+#pragma once
 
 #include <QObject>
 #include <QCryptographicHash>
@@ -101,5 +100,3 @@ private:
 
 QDataStream& operator << (QDataStream &out, const Core::Archive::AvatarInfo& info);
 QDataStream& operator >> (QDataStream &in, Core::Archive::AvatarInfo& info);
-
-#endif // CORE_ARCHIVE_H
diff --git a/core/components/clientcache.cpp b/core/components/clientcache.cpp
index fe1fa64..f1eb886 100644
--- a/core/components/clientcache.cpp
+++ b/core/components/clientcache.cpp
@@ -1,18 +1,20 @@
-// Squawk messenger. 
-// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
-// 
-// 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/>.
+/*
+ * Squawk messenger.
+ * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+ *
+ * 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 "clientcache.h"
 
diff --git a/core/components/clientcache.h b/core/components/clientcache.h
index b9dba73..c68dbfb 100644
--- a/core/components/clientcache.h
+++ b/core/components/clientcache.h
@@ -1,21 +1,22 @@
-// Squawk messenger. 
-// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
-// 
-// 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/>.
+/*
+ * Squawk messenger.
+ * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+ *
+ * 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/>.
+ */
 
-#ifndef CORE_CLIENTCACHE_H
-#define CORE_CLIENTCACHE_H
+#pragma once
 
 #include <map>
 #include <set>
@@ -60,6 +61,3 @@ private:
 };
 
 }
-
-
-#endif // CORE_CLIENTCACHE_H
diff --git a/core/components/networkaccess.cpp b/core/components/networkaccess.cpp
index 862c664..0771dfa 100644
--- a/core/components/networkaccess.cpp
+++ b/core/components/networkaccess.cpp
@@ -16,7 +16,6 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-
 #include <QtWidgets/QApplication>
 #include <QtCore/QDir>
 
@@ -129,9 +128,9 @@ void Core::NetworkAccess::onDownloadError(QNetworkReply::NetworkError code) {
 
 void Core::NetworkAccess::onDownloadSSLError(const QList<QSslError>& errors) {
     qDebug() << "DEBUG: DOWNLOAD SSL ERRORS";
-    for (const QSslError& err : errors) {
+    for (const QSslError& err : errors)
         qDebug() << err.errorString();
-    }
+
     QNetworkReply* rpl = static_cast<QNetworkReply*>(sender());
     QString url = rpl->url().toString();
     std::map<QString, Transfer*>::const_iterator itr = downloads.find(url);
diff --git a/core/components/networkaccess.h b/core/components/networkaccess.h
index 99a15ed..ddb5ba8 100644
--- a/core/components/networkaccess.h
+++ b/core/components/networkaccess.h
@@ -16,8 +16,7 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#ifndef CORE_NETWORKACCESS_H
-#define CORE_NETWORKACCESS_H
+#pragma once
 
 #include <QObject>
 #include <QNetworkAccessManager>
@@ -36,8 +35,7 @@
 namespace Core {
 
 //TODO Need to describe how to get rid of records when file is no longer reachable;
-class NetworkAccess : public QObject
-{
+class NetworkAccess : public QObject {
     Q_OBJECT
     struct Transfer;
 public:
@@ -100,5 +98,3 @@ private:
 };
 
 }
-
-#endif // CORE_NETWORKACCESS_H
diff --git a/core/components/urlstorage.cpp b/core/components/urlstorage.cpp
index 31f36ad..5e34792 100644
--- a/core/components/urlstorage.cpp
+++ b/core/components/urlstorage.cpp
@@ -193,9 +193,8 @@ Core::UrlStorage::UrlInfo::~UrlInfo() {}
  
 bool Core::UrlStorage::UrlInfo::addMessage(const QString& acc, const QString& jid, const QString& id) {
     for (const Shared::MessageInfo& info : messages) {
-        if (info.account == acc && info.jid == jid && info.messageId == id) {
+        if (info.account == acc && info.jid == jid && info.messageId == id)
             return false;
-        }
     }
     messages.emplace_back(acc, jid, id);
     return true;
diff --git a/core/components/urlstorage.h b/core/components/urlstorage.h
index fc9d71d..6fb536a 100644
--- a/core/components/urlstorage.h
+++ b/core/components/urlstorage.h
@@ -16,8 +16,7 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#ifndef CORE_URLSTORAGE_H
-#define CORE_URLSTORAGE_H
+#pragma once
 
 #include <QString>
 #include <QDataStream>
@@ -93,5 +92,3 @@ public:
 
 QDataStream& operator >> (QDataStream &in, Core::UrlStorage::UrlInfo& info);
 QDataStream& operator << (QDataStream &out, const Core::UrlStorage::UrlInfo& info);
-
-#endif // CORE_URLSTORAGE_H
diff --git a/core/delayManager/cardinternal.cpp b/core/delayManager/cardinternal.cpp
index c9ed203..b5be472 100644
--- a/core/delayManager/cardinternal.cpp
+++ b/core/delayManager/cardinternal.cpp
@@ -1,18 +1,20 @@
-// Squawk messenger. 
-// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
-// 
-// 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/>.
+/*
+ * Squawk messenger.
+ * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+ *
+ * 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 "cardinternal.h"
 
diff --git a/core/delayManager/cardinternal.h b/core/delayManager/cardinternal.h
index 17dd1ba..6db7734 100644
--- a/core/delayManager/cardinternal.h
+++ b/core/delayManager/cardinternal.h
@@ -1,22 +1,22 @@
-// Squawk messenger. 
-// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
-// 
-// 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/>.
-
-#ifndef CORE_DELAYMANAGER_CARDINTERNAL_H
-#define CORE_DELAYMANAGER_CARDINTERNAL_H
+/*
+ * Squawk messenger.
+ * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+ *
+ * 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 <QString>
 
@@ -33,5 +33,3 @@ public:
 
 }
 }
-
-#endif // CORE_DELAYMANAGER_CARDINTERNAL_H
diff --git a/core/delayManager/contact.cpp b/core/delayManager/contact.cpp
index 286c8bd..3af1e3f 100644
--- a/core/delayManager/contact.cpp
+++ b/core/delayManager/contact.cpp
@@ -1,18 +1,20 @@
-// Squawk messenger. 
-// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
-// 
-// 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/>.
+/*
+ * Squawk messenger.
+ * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+ *
+ * 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 "contact.h"
 
diff --git a/core/delayManager/contact.h b/core/delayManager/contact.h
index c136525..3f1ab98 100644
--- a/core/delayManager/contact.h
+++ b/core/delayManager/contact.h
@@ -1,21 +1,22 @@
-// Squawk messenger. 
-// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
-// 
-// 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/>.
+/*
+ * Squawk messenger.
+ * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+ *
+ * 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/>.
+ */
 
-#ifndef CORE_DELAYMANAGER_CONTACT_H
-#define CORE_DELAYMANAGER_CONTACT_H
+#pragma once
 
 #include <QString>
 
@@ -35,5 +36,3 @@ public:
 
 }
 }
-
-#endif // CORE_DELAYMANAGER_CONTACT_H
diff --git a/core/delayManager/info.cpp b/core/delayManager/info.cpp
index b5b619d..9953fd4 100644
--- a/core/delayManager/info.cpp
+++ b/core/delayManager/info.cpp
@@ -1,18 +1,20 @@
-// Squawk messenger. 
-// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
-// 
-// 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/>.
+/*
+ * Squawk messenger.
+ * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+ *
+ * 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 "info.h"
 
diff --git a/core/delayManager/info.h b/core/delayManager/info.h
index a6601a1..f03e0c7 100644
--- a/core/delayManager/info.h
+++ b/core/delayManager/info.h
@@ -1,21 +1,22 @@
-// Squawk messenger. 
-// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
-// 
-// 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/>.
+/*
+ * Squawk messenger.
+ * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+ *
+ * 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/>.
+ */
 
-#ifndef CORE_DELAYMANAGER_INFO_H
-#define CORE_DELAYMANAGER_INFO_H
+#pragma once
 
 #include "job.h"
 
@@ -52,5 +53,3 @@ private:
 
 }
 }
-
-#endif // CORE_DELAYMANAGER_INFO_H
diff --git a/core/delayManager/infoforuser.cpp b/core/delayManager/infoforuser.cpp
index 067be80..7d0aed9 100644
--- a/core/delayManager/infoforuser.cpp
+++ b/core/delayManager/infoforuser.cpp
@@ -1,18 +1,20 @@
-// Squawk messenger. 
-// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
-// 
-// 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/>.
+/*
+ * Squawk messenger.
+ * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+ *
+ * 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 "infoforuser.h"
 
diff --git a/core/delayManager/infoforuser.h b/core/delayManager/infoforuser.h
index 651d741..c2f4692 100644
--- a/core/delayManager/infoforuser.h
+++ b/core/delayManager/infoforuser.h
@@ -1,21 +1,22 @@
-// Squawk messenger. 
-// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
-// 
-// 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/>.
+/*
+ * Squawk messenger.
+ * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+ *
+ * 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/>.
+ */
 
-#ifndef CORE_DELAYMANAGER_INFOFORUSER_H
-#define CORE_DELAYMANAGER_INFOFORUSER_H
+#pragma once
 
 #include "contact.h"
 #include "info.h"
@@ -31,5 +32,3 @@ public:
 
 }
 }
-
-#endif // CORE_DELAYMANAGER_INFOFORUSER_H
diff --git a/core/delayManager/job.cpp b/core/delayManager/job.cpp
index b2d74b2..37acc02 100644
--- a/core/delayManager/job.cpp
+++ b/core/delayManager/job.cpp
@@ -1,18 +1,20 @@
-// Squawk messenger. 
-// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
-// 
-// 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/>.
+/*
+ * Squawk messenger.
+ * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+ *
+ * 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 "job.h"
 
diff --git a/core/delayManager/job.h b/core/delayManager/job.h
index 6bfdc1d..26156c0 100644
--- a/core/delayManager/job.h
+++ b/core/delayManager/job.h
@@ -1,21 +1,22 @@
-// Squawk messenger. 
-// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
-// 
-// 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/>.
+/*
+ * Squawk messenger.
+ * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+ *
+ * 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/>.
+ */
 
-#ifndef CORE_DELAYMANAGER_JOB_H
-#define CORE_DELAYMANAGER_JOB_H
+#pragma once
 
 #include <stdint.h>
 
@@ -51,5 +52,3 @@ public:
 
 }
 }
-
-#endif // CORE_DELAYMANAGER_JOB_H
diff --git a/core/delayManager/owncardinternal.cpp b/core/delayManager/owncardinternal.cpp
index 43ed540..d9729ba 100644
--- a/core/delayManager/owncardinternal.cpp
+++ b/core/delayManager/owncardinternal.cpp
@@ -1,18 +1,20 @@
-// Squawk messenger. 
-// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
-// 
-// 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/>.
+/*
+ * Squawk messenger.
+ * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+ *
+ * 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 "owncardinternal.h"
 
diff --git a/core/delayManager/owncardinternal.h b/core/delayManager/owncardinternal.h
index 7cca0a0..30d7c56 100644
--- a/core/delayManager/owncardinternal.h
+++ b/core/delayManager/owncardinternal.h
@@ -1,21 +1,22 @@
-// Squawk messenger. 
-// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
-// 
-// 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/>.
+/*
+ * Squawk messenger.
+ * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+ *
+ * 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/>.
+ */
 
-#ifndef CORE_DELAYMANAGER_OWNCARDINTERNAL_H
-#define CORE_DELAYMANAGER_OWNCARDINTERNAL_H
+#pragma once
 
 #include "job.h"
 
@@ -33,5 +34,3 @@ public:
 
 }
 }
-
-#endif // CORE_DELAYMANAGER_OWNCARDINTERNAL_H
diff --git a/core/delayManager/owninfoforuser.cpp b/core/delayManager/owninfoforuser.cpp
index 396dc49..98cbdfa 100644
--- a/core/delayManager/owninfoforuser.cpp
+++ b/core/delayManager/owninfoforuser.cpp
@@ -1,18 +1,20 @@
-// Squawk messenger. 
-// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
-// 
-// 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/>.
+/*
+ * Squawk messenger.
+ * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+ *
+ * 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 "owninfoforuser.h"
 
diff --git a/core/delayManager/owninfoforuser.h b/core/delayManager/owninfoforuser.h
index 80a13b6..4348b04 100644
--- a/core/delayManager/owninfoforuser.h
+++ b/core/delayManager/owninfoforuser.h
@@ -1,21 +1,22 @@
-// Squawk messenger. 
-// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
-// 
-// 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/>.
+/*
+ * Squawk messenger.
+ * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+ *
+ * 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/>.
+ */
 
-#ifndef CORE_DELAYMANAGER_OWNINFOFORUSER_H
-#define CORE_DELAYMANAGER_OWNINFOFORUSER_H
+#pragma once
 
 #include "contact.h"
 #include "info.h"
@@ -31,5 +32,3 @@ public:
 
 }
 }
-
-#endif // CORE_DELAYMANAGER_OWNINFOFORUSER_H
diff --git a/core/handlers/messagehandler.cpp b/core/handlers/messagehandler.cpp
index 8489dba..094f671 100644
--- a/core/handlers/messagehandler.cpp
+++ b/core/handlers/messagehandler.cpp
@@ -19,6 +19,10 @@
 #include "messagehandler.h"
 #include "core/account.h"
 
+static const QMap<QString, QVariant> statePending({{"state", static_cast<uint8_t>(Shared::Message::State::pending)}});
+static const QMap<QString, QVariant> stateDelivered({{"state", static_cast<uint8_t>(Shared::Message::State::delivered)}});
+static const QMap<QString, QVariant> stateSent({{"state", static_cast<uint8_t>(Shared::Message::State::sent)}});
+
 Core::MessageHandler::MessageHandler(Core::Account* account):
     QObject(),
     acc(account),
@@ -85,102 +89,80 @@ void Core::MessageHandler::onMessageReceived(const QXmppMessage& msg) {
 }
 
 bool Core::MessageHandler::handlePendingMessageError(const QString& id, const QString& errorText) {
-    std::tuple<bool, QString, QString> ids = getOriginalPendingMessageId(id);
-    if (std::get<0>(ids)) {
-        QString id = std::get<1>(ids);
-        QString jid = std::get<2>(ids);
-        RosterItem* ri = acc->rh->getRosterItem(jid);
-        QMap<QString, QVariant> cData = {
-            {"state", static_cast<uint>(Shared::Message::State::error)},
+    return adjustPendingMessage(id, {
+            {"state", static_cast<uint8_t>(Shared::Message::State::error)},
             {"errorText", errorText}
-        };
-        if (ri != nullptr)
-            ri->changeMessage(id, cData);
-
-        emit acc->changeMessage(jid, id, cData);
-        return true;
-    }
-
-    return false;
+        }, true);
 }
 
-
 bool Core::MessageHandler::handleChatMessage(const QXmppMessage& msg, bool outgoing, bool forwarded, bool guessing) {
-    if (msg.body().size() != 0 || msg.outOfBandUrl().size() > 0) {
-        Shared::Message sMsg(Shared::Message::chat);
-        initializeMessage(sMsg, msg, outgoing, forwarded, guessing);
-        QString jid = sMsg.getPenPalJid();
-        Contact* cnt = acc->rh->getContact(jid);
-        if (cnt == 0) {
-            cnt = acc->rh->addOutOfRosterContact(jid);
-            qDebug() << "appending message" << sMsg.getId() << "to an out of roster contact";
-        }
-        if (sMsg.getOutgoing()) {
-            if (sMsg.getForwarded())
-                sMsg.setState(Shared::Message::State::sent);
-        } else {
-            sMsg.setState(Shared::Message::State::delivered);
-        }
-        QString oId = msg.replaceId();
-        if (oId.size() > 0) {
-            QMap<QString, QVariant> cData = {
-                {"body", sMsg.getBody()},
-                {"stamp", sMsg.getTime()}
-            };
-            cnt->correctMessageInArchive(oId, sMsg);
-            emit acc->changeMessage(jid, oId, cData);
-        } else {
-            cnt->appendMessageToArchive(sMsg);
-            emit acc->message(sMsg);
-        }
-        
-        return true;
+    if (msg.body().isEmpty() && msg.outOfBandUrl().isEmpty())
+        return false;
+
+    Shared::Message sMsg(Shared::Message::chat);
+    initializeMessage(sMsg, msg, outgoing, forwarded, guessing);
+    QString jid = sMsg.getPenPalJid();
+    Contact* cnt = acc->rh->getContact(jid);
+    if (cnt == 0) {
+        cnt = acc->rh->addOutOfRosterContact(jid);
+        qDebug() << "appending message" << sMsg.getId() << "to an out of roster contact";
     }
-    return false;
+    if (sMsg.getOutgoing()) {
+        if (sMsg.getForwarded())
+            sMsg.setState(Shared::Message::State::sent);
+    } else {
+        sMsg.setState(Shared::Message::State::delivered);
+    }
+    QString oId = msg.replaceId();
+    if (oId.size() > 0) {
+        QMap<QString, QVariant> cData = {
+            {"body", sMsg.getBody()},
+            {"stamp", sMsg.getTime()}
+        };
+        cnt->correctMessageInArchive(oId, sMsg);
+        emit acc->changeMessage(jid, oId, cData);
+    } else {
+        cnt->appendMessageToArchive(sMsg);
+        emit acc->message(sMsg);
+    }
+
+    return true;
 }
 
 bool Core::MessageHandler::handleGroupMessage(const QXmppMessage& msg, bool outgoing, bool forwarded, bool guessing) {
     const QString& body(msg.body());
-    if (body.size() != 0) {
-        
-        Shared::Message sMsg(Shared::Message::groupChat);
-        initializeMessage(sMsg, msg, outgoing, forwarded, guessing);
-        QString jid = sMsg.getPenPalJid();
-        Conference* cnt = acc->rh->getConference(jid);
-        if (cnt == 0)
-            return false;
+    if (body.isEmpty())
+        return false;
 
-        std::tuple<bool, QString, QString> ids = getOriginalPendingMessageId(msg.id());
-        if (std::get<0>(ids)) {
-            QMap<QString, QVariant> cData = {{"state", static_cast<uint>(Shared::Message::State::delivered)}};
-            cnt->changeMessage(std::get<1>(ids), cData);
-            emit acc->changeMessage(std::get<2>(ids), std::get<1>(ids), cData);
-        } else {
-            QString oId = msg.replaceId();
-            if (oId.size() > 0) {
-                QMap<QString, QVariant> cData = {
-                    {"body", sMsg.getBody()},
-                    {"stamp", sMsg.getTime()}
-                };
-                cnt->correctMessageInArchive(oId, sMsg);
-                emit acc->changeMessage(jid, oId, cData);
-            } else {
-                cnt->appendMessageToArchive(sMsg);
-                QDateTime minAgo = QDateTime::currentDateTimeUtc().addSecs(-60);
-                if (sMsg.getTime() > minAgo) {     //otherwise it's considered a delayed delivery, most probably MUC history receipt
-                    emit acc->message(sMsg);
-                } else {
-                    //qDebug() << "Delayed delivery: ";
-                }
-            }
-        }
-        
-        return true;
-    } 
-    return false;
+    Shared::Message sMsg(Shared::Message::groupChat);
+    initializeMessage(sMsg, msg, outgoing, forwarded, guessing);
+    QString jid = sMsg.getPenPalJid();
+    Conference* cnt = acc->rh->getConference(jid);
+    if (cnt == 0)
+        return false;
+
+    bool result = adjustPendingMessage(msg.id(), stateDelivered, true);
+    if (result)             //then it was an echo of my own sent message, nothing else needs to be done
+        return result;
+
+    QString oId = msg.replaceId();
+    if (oId.size() > 0) {
+        QMap<QString, QVariant> cData = {
+            {"body", sMsg.getBody()},
+            {"stamp", sMsg.getTime()}
+        };
+        cnt->correctMessageInArchive(oId, sMsg);
+        emit acc->changeMessage(jid, oId, cData);
+    } else {
+        cnt->appendMessageToArchive(sMsg);
+        QDateTime minAgo = QDateTime::currentDateTimeUtc().addSecs(-60);
+        if (sMsg.getTime() > minAgo)     //otherwise it's considered a delayed delivery, most probably MUC history initial fetch
+            emit acc->message(sMsg);
+    }
+
+    return true;
 }
 
-
 void Core::MessageHandler::initializeMessage(Shared::Message& target, const QXmppMessage& source, bool outgoing, bool forwarded, bool guessing) const {
     const QDateTime& time(source.stamp());
     QString id;
@@ -254,45 +236,31 @@ void Core::MessageHandler::onCarbonMessageSent(const QXmppMessage& msg) {
 }
 #endif
 
-std::tuple<bool, QString, QString> Core::MessageHandler::getOriginalPendingMessageId(const QString& id, bool clear) {
-    std::tuple<bool, QString, QString> result({false, "", ""});
+std::optional<Shared::MessageInfo> Core::MessageHandler::getOriginalPendingMessageId(const QString& id, bool clear) {
     std::map<QString, QString>::const_iterator itr = pendingStateMessages.find(id);
     if (itr != pendingStateMessages.end()) {
-        std::get<0>(result) = true;
-        std::get<2>(result) = itr->second;
-
+        Shared::MessageInfo info(acc->name, itr->second, itr->first);
         std::map<QString, QString>::const_iterator itrC = pendingCorrectionMessages.find(id);
         if (itrC != pendingCorrectionMessages.end()) {
             if (itrC->second.size() > 0)
-                std::get<1>(result) = itrC->second;
-            else
-                std::get<1>(result) = itr->first;
+                info.jid = itrC->second;
 
             if (clear)
                 pendingCorrectionMessages.erase(itrC);
-        } else {
-            std::get<1>(result) = itr->first;
         }
 
         if (clear)
             pendingStateMessages.erase(itr);
+
+        return info;
     }
 
-    return result;
+    return std::nullopt;
 }
 
 void Core::MessageHandler::onReceiptReceived(const QString& jid, const QString& id) {
     SHARED_UNUSED(jid);
-    std::tuple<bool, QString, QString> ids = getOriginalPendingMessageId(id);
-    if (std::get<0>(ids)) {
-        QMap<QString, QVariant> cData = {{"state", static_cast<uint>(Shared::Message::State::delivered)}};
-        RosterItem* ri = acc->rh->getRosterItem(std::get<2>(ids));
-
-        if (ri != nullptr)
-            ri->changeMessage(std::get<1>(ids), cData);
-
-        emit acc->changeMessage(std::get<2>(ids), std::get<1>(ids), cData);
-    }
+    adjustPendingMessage(id, {{"state", static_cast<uint>(Shared::Message::State::delivered)}}, true);
 }
 
 void Core::MessageHandler::sendMessage(const Shared::Message& data, bool newMessage, QString originalId) {
@@ -386,19 +354,8 @@ std::pair<Shared::Message::State, QString> Core::MessageHandler::scheduleSending
                     bool success = acc->client.sendPacket(*encrypted.get());
                     if (success) {
                         qDebug() << "Successfully sent an encrypted message";
-                        std::tuple<bool, QString, QString> ids = getOriginalPendingMessageId(id, false);
-                        if (std::get<0>(ids)) {
-                            QString id = std::get<1>(ids);
-                            QString jid = std::get<2>(ids);
-                            RosterItem* ri = acc->rh->getRosterItem(jid);
-                            QMap<QString, QVariant> cData = {{"state", static_cast<uint>(Shared::Message::State::sent)}};
-                            if (ri != nullptr)
-                                ri->changeMessage(id, cData);
-
-                            emit acc->changeMessage(jid, id, cData);
-                        } else {
+                        if (!adjustPendingMessage(id, stateSent, false))
                             qDebug() << "Encrypted message has been successfully sent, but it couldn't be found to update the sate";
-                        }
                     } else {
                         qDebug() << "Couldn't sent an encrypted message";
                         handlePendingMessageError(id, "Error sending successfully encrypted message");
@@ -425,6 +382,19 @@ std::pair<Shared::Message::State, QString> Core::MessageHandler::scheduleSending
     }
 }
 
+bool Core::MessageHandler::adjustPendingMessage(const QString& messageId, const QMap<QString, QVariant>& data, bool final) {
+    std::optional<Shared::MessageInfo> info = getOriginalPendingMessageId(messageId, final);
+    if (info) {
+        RosterItem* ri = acc->rh->getRosterItem(info->jid);
+        if (ri != nullptr)
+            ri->changeMessage(info->messageId, data);
+
+        emit acc->changeMessage(info->jid, info->messageId, data);
+        return true;
+    }
+
+    return false;
+}
 
 QMap<QString, QVariant> Core::MessageHandler::getChanges(Shared::Message& data, const QDateTime& time, bool newMessage, const QString& originalId) const {
     QMap<QString, QVariant> changes;
@@ -481,51 +451,53 @@ QXmppMessage Core::MessageHandler::createPacket(const Shared::Message& data, con
 }
 
 void Core::MessageHandler::prepareUpload(const Shared::Message& data, bool newMessage) {
-    if (acc->state == Shared::ConnectionState::connected) {
-        QString jid = data.getPenPalJid();
-        QString id = data.getId();
-        RosterItem* ri = acc->rh->getRosterItem(jid);
-        if (ri == nullptr) {
-            qDebug() << "An attempt to initialize upload in" << acc->name << "for pal" << jid << "but the object for this pal wasn't found, something went terrebly wrong, skipping send";
-            return;
-        }
-        QString path = data.getAttachPath();
-        QString url = acc->network->getFileRemoteUrl(path);
-        if (url.size() != 0) {
-            sendMessageWithLocalUploadedFile(data, url, newMessage);
-        } else {
-            pendingStateMessages.insert(std::make_pair(id, jid));
-            if (newMessage) {
-                ri->appendMessageToArchive(data);
-            } else {
-                QMap<QString, QVariant> changes({
-                    {"state", (uint)Shared::Message::State::pending}
-                });
-                ri->changeMessage(id, changes);
-                emit acc->changeMessage(jid, id, changes);
-            }
-            //this checks if the file is already uploading, and if so it subscribes to it's success, so, i need to do stuff only if the network knows nothing of this file
-            if (!acc->network->checkAndAddToUploading(acc->getName(), jid, id, path)) {
-                if (acc->um->serviceFound()) {
-                    QFileInfo file(path);
-                    if (file.exists() && file.isReadable()) {
-                        pendingStateMessages.insert(std::make_pair(id, jid));
-                        uploadingSlotsQueue.emplace_back(path, id);
-                        if (uploadingSlotsQueue.size() == 1)
-                            acc->um->requestUploadSlot(file);
-                    } else {
-                        handleUploadError(jid, id, "Uploading file no longer exists or your system user has no permission to read it");
-                        qDebug() << "Requested upload slot in account" << acc->name << "for file" << path << "but the file doesn't exist or is not readable";
-                    }
-                } else {
-                    handleUploadError(jid, id, "Your server doesn't support file upload service, or it's prohibited for your account");
-                    qDebug() << "Requested upload slot in account" << acc->name << "for file" << path << "but upload manager didn't discover any upload services";
-                }
-            }
-        }
-    } else {
+    if (acc->state != Shared::ConnectionState::connected) {
         handleUploadError(data.getPenPalJid(), data.getId(), "Account is offline or reconnecting");
         qDebug() << "An attempt to send message with not connected account " << acc->name << ", skipping";
+        return;
+    }
+
+    QString jid = data.getPenPalJid();
+    QString id = data.getId();
+    RosterItem* ri = acc->rh->getRosterItem(jid);
+    if (ri == nullptr) {
+        qDebug() << "An attempt to initialize upload in" << acc->name << "for pal" << jid << "but the object for this pal wasn't found, something went terrebly wrong, skipping send";
+        return;
+    }
+
+    QString path = data.getAttachPath();
+    QString url = acc->network->getFileRemoteUrl(path);
+    if (url.size() != 0)
+        return sendMessageWithLocalUploadedFile(data, url, newMessage);
+
+    pendingStateMessages.insert(std::make_pair(id, jid));
+    if (newMessage) {
+        ri->appendMessageToArchive(data);
+    } else {
+        ri->changeMessage(id, statePending);
+        emit acc->changeMessage(jid, id, statePending);
+    }
+
+    //this checks if the file is already uploading, and if so it subscribes to it's success,
+    //So, I need to do stuff only if the network knows nothing of this file
+    if (acc->network->checkAndAddToUploading(acc->getName(), jid, id, path))
+        return;
+
+    if (!acc->um->serviceFound()) {
+        handleUploadError(jid, id, "Your server doesn't support file upload service, or it's prohibited for your account");
+        qDebug() << "Requested upload slot in account" << acc->name << "for file" << path << "but upload manager didn't discover any upload services";
+        return;
+    }
+
+    QFileInfo file(path);
+    if (file.exists() && file.isReadable()) {
+        pendingStateMessages.insert(std::make_pair(id, jid));
+        uploadingSlotsQueue.emplace_back(path, id);
+        if (uploadingSlotsQueue.size() == 1)
+            acc->um->requestUploadSlot(file);
+    } else {
+        handleUploadError(jid, id, "Uploading file no longer exists or your system user has no permission to read it");
+        qDebug() << "Requested upload slot in account" << acc->name << "for file" << path << "but the file doesn't exist or is not readable";
     }
 }
 
@@ -565,24 +537,25 @@ void Core::MessageHandler::onDownloadFileComplete(const std::list<Shared::Messag
         {"attachPath", path}
     };
     for (const Shared::MessageInfo& info : msgs) {
-        if (info.account == acc->getName()) {
-            RosterItem* cnt = acc->rh->getRosterItem(info.jid);
-            if (cnt != nullptr) {
-                bool changed = cnt->changeMessage(info.messageId, cData);
-                if (changed)
-                    emit acc->changeMessage(info.jid, info.messageId, cData);
-            }
+        if (info.account != acc->getName())
+            continue;
+
+        RosterItem* cnt = acc->rh->getRosterItem(info.jid);
+        if (cnt != nullptr) {
+            bool changed = cnt->changeMessage(info.messageId, cData);
+            if (changed)
+                emit acc->changeMessage(info.jid, info.messageId, cData);
         }
     }
 }
 
 void Core::MessageHandler::onLoadFileError(const std::list<Shared::MessageInfo>& msgs, const QString& text, bool up) {
-    if (up) {
-        for (const Shared::MessageInfo& info : msgs) {
-            if (info.account == acc->getName())
-                handleUploadError(info.jid, info.messageId, text);
-        }
-    }
+    if (!up)
+        return;
+
+    for (const Shared::MessageInfo& info : msgs)
+        if (info.account == acc->getName())
+            handleUploadError(info.jid, info.messageId, text);
 }
 
 void Core::MessageHandler::handleUploadError(const QString& jid, const QString& messageId, const QString& errorText) {
@@ -597,15 +570,16 @@ void Core::MessageHandler::handleUploadError(const QString& jid, const QString&
 
 void Core::MessageHandler::onUploadFileComplete(const std::list<Shared::MessageInfo>& msgs, const QString& url, const QString& path) {
     for (const Shared::MessageInfo& info : msgs) {
-        if (info.account == acc->getName()) {
-            RosterItem* ri = acc->rh->getRosterItem(info.jid);
-            if (ri != nullptr) {
-                Shared::Message msg = ri->getMessage(info.messageId);
-                msg.setAttachPath(path);
-                sendMessageWithLocalUploadedFile(msg, url, false);
-            } else {
-                qDebug() << "A signal received about complete upload to" << acc->name << "for pal" << info.jid << "but the object for this pal wasn't found, something went terrebly wrong, skipping send";
-            }
+        if (info.account != acc->getName())
+            continue;
+
+        RosterItem* ri = acc->rh->getRosterItem(info.jid);
+        if (ri != nullptr) {
+            Shared::Message msg = ri->getMessage(info.messageId);
+            msg.setAttachPath(path);
+            sendMessageWithLocalUploadedFile(msg, url, false);
+        } else {
+            qDebug() << "A signal received about complete upload to" << acc->name << "for pal" << info.jid << "but the object for this pal wasn't found, something went terrebly wrong, skipping send";
         }
     }
 }
@@ -654,7 +628,7 @@ void Core::MessageHandler::resendMessage(const QString& jid, const QString& id)
         try {
             Shared::Message msg = cnt->getMessage(id);
             if (msg.getState() == Shared::Message::State::error) {
-                if (msg.getEdited()){
+                if (msg.getEdited()) {
                     QString originalId = msg.getId();
                     msg.generateRandomId();
                     sendMessage(msg, false, originalId);
diff --git a/core/handlers/messagehandler.h b/core/handlers/messagehandler.h
index e3199dc..15f99bf 100644
--- a/core/handlers/messagehandler.h
+++ b/core/handlers/messagehandler.h
@@ -23,6 +23,7 @@
 #include <deque>
 #include <map>
 #include <functional>
+#include <optional>
 
 #include <QXmppMessage.h>
 #include <QXmppHttpUploadIq.h>
@@ -71,9 +72,10 @@ private:
     void handleUploadError(const QString& jid, const QString& messageId, const QString& errorText);
     QXmppMessage createPacket(const Shared::Message& data, const QDateTime& time, const QString& originalId) const;
     QMap<QString, QVariant> getChanges(Shared::Message& data, const QDateTime& time, bool newMessage, const QString& originalId) const;
-    std::tuple<bool, QString, QString> getOriginalPendingMessageId(const QString& id, bool clear = true);
+    std::optional<Shared::MessageInfo> getOriginalPendingMessageId(const QString& id, bool clear = true);
     bool handlePendingMessageError(const QString& id, const QString& errorText);
     std::pair<Shared::Message::State, QString> scheduleSending(const Shared::Message& message, const QDateTime& sendTime, const QString& originalId);
+    bool adjustPendingMessage(const QString& messageId, const QMap<QString, QVariant>& data, bool final);
     
 private:
     Account* acc;
diff --git a/core/handlers/omemohandler.cpp b/core/handlers/omemohandler.cpp
index eaa674f..b50bed8 100644
--- a/core/handlers/omemohandler.cpp
+++ b/core/handlers/omemohandler.cpp
@@ -54,22 +54,20 @@ QXmppTask<QXmppOmemoStorage::OmemoData> Core::OmemoHandler::allData() {
     OmemoData data;
     data.ownDevice = ownDevice;
 
-    // LMDBAL::Transaction txn = db.beginReadOnlyTransaction();     TODO need to enable transaction after fixing LMDBAL
-    std::map<uint32_t, QByteArray> pkeys = preKeyPairs->readAll();
-    for (const std::pair<const uint32_t, QByteArray>& pair : pkeys) {
+    LMDBAL::Transaction txn = db.beginReadOnlyTransaction();
+    std::map<uint32_t, QByteArray> pkeys = preKeyPairs->readAll(txn);
+    for (const std::pair<const uint32_t, QByteArray>& pair : pkeys)
         data.preKeyPairs.insert(pair.first, pair.second);
-    }
 
-    std::map<uint32_t, SignedPreKeyPair> spre = signedPreKeyPairs->readAll();
+    std::map<uint32_t, SignedPreKeyPair> spre = signedPreKeyPairs->readAll(txn);
     for (const std::pair<const uint32_t, SignedPreKeyPair>& pair : spre) {
         QXmppOmemoStorage::SignedPreKeyPair qxpair = {pair.second.first, pair.second.second};
         data.signedPreKeyPairs.insert(pair.first, qxpair);
     }
 
-    std::map<QString, QHash<uint32_t, Device>> devs = devices->readAll();
-    for (const std::pair<const QString, QHash<uint32_t, Device>>& pair : devs) {
+    std::map<QString, QHash<uint32_t, Device>> devs = devices->readAll(txn);
+    for (const std::pair<const QString, QHash<uint32_t, Device>>& pair : devs)
         data.devices.insert(pair.first, pair.second);
-    }
 
     return Core::makeReadyTask(std::move(data));
 }
diff --git a/core/handlers/rosterhandler.cpp b/core/handlers/rosterhandler.cpp
index 38425ba..940ddbf 100644
--- a/core/handlers/rosterhandler.cpp
+++ b/core/handlers/rosterhandler.cpp
@@ -60,7 +60,6 @@ void Core::RosterHandler::clear() {
     conferences.clear();
 }
 
-
 void Core::RosterHandler::onRosterReceived() {
     QStringList bj = acc->rm->getRosterBareJids();
     for (int i = 0; i < bj.size(); ++i) {
@@ -260,7 +259,6 @@ void Core::RosterHandler::onTrustChanged(const QString& jid, const Shared::Trust
     emit acc->changeContact(jid, {{"trust", QVariant::fromValue(trust)}});
 }
 
-
 void Core::RosterHandler::addToGroup(const QString& jid, const QString& group) {
     std::map<QString, std::set<QString>>::iterator gItr = groups.find(group);
     if (gItr == groups.end()) {
@@ -288,7 +286,7 @@ void Core::RosterHandler::removeFromGroup(const QString& jid, const QString& gro
     }
 }
 
-Core::RosterItem * Core::RosterHandler::getRosterItem(const QString& jid) {
+Core::RosterItem* Core::RosterHandler::getRosterItem(const QString& jid) {
     RosterItem* item = nullptr;
     QString lcJid = jid.toLower();
     std::map<QString, Contact*>::const_iterator citr = contacts.find(lcJid);
@@ -302,7 +300,7 @@ Core::RosterItem * Core::RosterHandler::getRosterItem(const QString& jid) {
     return item;
 }
 
-Core::Conference * Core::RosterHandler::getConference(const QString& jid) {
+Core::Conference* Core::RosterHandler::getConference(const QString& jid) {
     Conference* item = 0;
     std::map<QString, Conference*>::const_iterator coitr = conferences.find(jid.toLower());
     if (coitr != conferences.end())
@@ -311,7 +309,7 @@ Core::Conference * Core::RosterHandler::getConference(const QString& jid) {
     return item;
 }
 
-Core::Contact * Core::RosterHandler::getContact(const QString& jid) {
+Core::Contact* Core::RosterHandler::getContact(const QString& jid) {
     Contact* item = 0;
     std::map<QString, Contact*>::const_iterator citr = contacts.find(jid.toLower());
     if (citr != contacts.end())
@@ -320,7 +318,7 @@ Core::Contact * Core::RosterHandler::getContact(const QString& jid) {
     return item;
 }
 
-Core::Contact * Core::RosterHandler::addOutOfRosterContact(const QString& jid) {
+Core::Contact* Core::RosterHandler::addOutOfRosterContact(const QString& jid) {
     QString lcJid = jid.toLower();
     Contact* cnt = new Contact(lcJid, acc->name);
     contacts.insert(std::make_pair(lcJid, cnt));
@@ -446,9 +444,9 @@ void Core::RosterHandler::clearConferences() {
 void Core::RosterHandler::removeRoomRequest(const QString& jid) {
     QString lcJid = jid.toLower();
     std::map<QString, Conference*>::const_iterator itr = conferences.find(lcJid);
-    if (itr == conferences.end()) {
+    if (itr == conferences.end())
         qDebug() << "An attempt to remove non existing room" << lcJid << "from account" << acc->name << ", skipping";
-    }
+
     itr->second->deleteLater();
     conferences.erase(itr);
     emit acc->removeRoom(lcJid);
diff --git a/core/handlers/rosterhandler.h b/core/handlers/rosterhandler.h
index 1f8e480..5abc416 100644
--- a/core/handlers/rosterhandler.h
+++ b/core/handlers/rosterhandler.h
@@ -16,8 +16,7 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#ifndef CORE_ROSTERHANDLER_H
-#define CORE_ROSTERHANDLER_H
+#pragma once
 
 #include <QObject>
 #include <QSet>
@@ -27,6 +26,7 @@
 #include <list>
 #include <map>
 #include <set>
+#include <optional>
 
 #include <QXmppBookmarkSet.h>
 #include <QXmppMucManager.h>
@@ -45,8 +45,7 @@ namespace Core {
     
 class Account;
     
-class RosterHandler : public QObject
-{
+class RosterHandler : public QObject {
     Q_OBJECT
 public:
     RosterHandler(Account* account);
@@ -119,5 +118,3 @@ private:
 };
 
 }
-
-#endif // CORE_ROSTERHANDLER_H
diff --git a/shared/messageinfo.h b/shared/messageinfo.h
index 942d88c..3cf75bc 100644
--- a/shared/messageinfo.h
+++ b/shared/messageinfo.h
@@ -16,16 +16,11 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#ifndef SHARED_MESSAGEINFO_H
-#define SHARED_MESSAGEINFO_H
+#pragma once
 
 #include <QString>
 
 namespace Shared {
-
-/**
- * @todo write docs
- */
 struct MessageInfo {
     MessageInfo();
     MessageInfo(const QString& acc, const QString& j, const QString& id);
@@ -39,5 +34,3 @@ struct MessageInfo {
 };
 
 }
-
-#endif // SHARED_MESSAGEINFO_H

From 8f5325b291bffb63a738bc6e79d07af4e407ee10 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Thu, 16 Nov 2023 21:08:40 -0300
Subject: [PATCH 261/281] beginning of keys setting

---
 core/account.cpp                |   8 +
 core/account.h                  |   6 +-
 core/handlers/rosterhandler.h   |   2 -
 shared/info.cpp                 | 525 +++++++++++++++++---------------
 shared/info.h                   |  41 +--
 ui/models/info/omemo/keys.cpp   |  62 ++--
 ui/models/info/omemo/keys.h     |  48 ++-
 ui/widgets/info/info.cpp        |   6 +-
 ui/widgets/info/info.h          |   3 +-
 ui/widgets/info/omemo/omemo.cpp |   6 +
 ui/widgets/info/omemo/omemo.h   |   1 +
 11 files changed, 385 insertions(+), 323 deletions(-)

diff --git a/core/account.cpp b/core/account.cpp
index efb4a1a..858c177 100644
--- a/core/account.cpp
+++ b/core/account.cpp
@@ -806,6 +806,14 @@ void Core::Account::updateInfo(const Shared::Info& info) {
     //TODO switch case of what kind of entity this info update is about
     //right now it could be only about myself
     vh->uploadVCard(info.getVCardRef());
+    const std::list<Shared::KeyInfo>& keys = info.getActiveKeysRef();
+    for (const Shared::KeyInfo& info : keys) {
+        qDebug() << "An attempt to save key: ";
+        qDebug() << "id:" << info.id;
+        qDebug() << "label:" << info.label;
+        qDebug() << "current device:" << info.currentDevice;
+        qDebug() << "... but it's not implemented yet, ignoring";
+    }
 }
 
 QString Core::Account::getAvatarPath() const {
diff --git a/core/account.h b/core/account.h
index 47c5c7e..ea1a13d 100644
--- a/core/account.h
+++ b/core/account.h
@@ -67,11 +67,9 @@
 #include "handlers/omemohandler.h"
 #endif
 
-namespace Core
-{
+namespace Core {
 
-class Account : public QObject
-{
+class Account : public QObject {
     Q_OBJECT
     friend class MessageHandler;
     friend class RosterHandler;
diff --git a/core/handlers/rosterhandler.h b/core/handlers/rosterhandler.h
index 5abc416..afbd372 100644
--- a/core/handlers/rosterhandler.h
+++ b/core/handlers/rosterhandler.h
@@ -41,8 +41,6 @@
 #include <core/delayManager/manager.h>
 
 namespace Core {
-
-    
 class Account;
     
 class RosterHandler : public QObject {
diff --git a/shared/info.cpp b/shared/info.cpp
index 36cd4b5..35f6427 100644
--- a/shared/info.cpp
+++ b/shared/info.cpp
@@ -1,353 +1,384 @@
-// Squawk messenger. 
-// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
-// 
-// 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/>.
+/*
+ * Squawk messenger.
+ * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+ *
+ * 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 "info.h"
 
-Shared::Info::Info(const QString& addr, EntryType tp):
-    type(tp),
-    address(addr),
-    vcard(nullptr),
-    activeKeys(nullptr),
-    inactiveKeys(nullptr)
-{
+Shared::Info::Info(const QString &addr, EntryType tp):
+type(tp),
+address(addr),
+vcard(nullptr),
+activeKeys(nullptr),
+inactiveKeys(nullptr) {
     switch (type) {
-        case EntryType::none:
-            break;
-        case EntryType::contact:
-        case EntryType::ownAccount:
-            vcard = new VCard();
-            activeKeys = new std::list<KeyInfo>();
-            inactiveKeys = new std::list<KeyInfo>();
-            break;
-        default:
-            throw 352;
+    case EntryType::none:
+        break;
+    case EntryType::contact:
+    case EntryType::ownAccount:
+        vcard = new VCard();
+        activeKeys = new std::list<KeyInfo>();
+        inactiveKeys = new std::list<KeyInfo>();
+        break;
+    default:
+        throw 352;
     }
 }
 
 Shared::Info::Info():
-    type(EntryType::none),
-    address(""),
-    vcard(nullptr),
-    activeKeys(nullptr),
-    inactiveKeys(nullptr)
-{}
+type(EntryType::none),
+address(""),
+vcard(nullptr),
+activeKeys(nullptr),
+inactiveKeys(nullptr) {}
 
-Shared::Info::Info(const Shared::Info& other):
-    type(other.type),
-    address(other.address),
-    vcard(nullptr),
-    activeKeys(nullptr),
-    inactiveKeys(nullptr)
-{
+Shared::Info::Info(const Shared::Info &other):
+type(other.type),
+address(other.address),
+vcard(nullptr),
+activeKeys(nullptr),
+inactiveKeys(nullptr) {
     switch (type) {
-        case EntryType::none:
-            break;
-        case EntryType::contact:
-        case EntryType::ownAccount:
-            vcard = new VCard(other.getVCardRef());
-            activeKeys = new std::list<KeyInfo>(other.getActiveKeysRef());
-            inactiveKeys = new std::list<KeyInfo>(other.getInactiveKeysRef());
-            break;
-        default:
-            throw 353;
+    case EntryType::none:
+        break;
+    case EntryType::contact:
+    case EntryType::ownAccount:
+        vcard = new VCard(other.getVCardRef());
+        activeKeys = new std::list<KeyInfo>(other.getActiveKeysRef());
+        inactiveKeys = new std::list<KeyInfo>(other.getInactiveKeysRef());
+        break;
+    default:
+        throw 353;
     }
 }
 
+Shared::Info::Info(Info &&other):
+type(other.type),
+address(other.address),
+vcard(other.vcard),
+activeKeys(other.activeKeys),
+inactiveKeys(other.inactiveKeys) {
+    other.type = EntryType::none;
+}
+
+Shared::Info &Shared::Info::operator=(Info &&other) {
+    type = other.type;
+    address = other.address;
+    vcard = other.vcard;
+    activeKeys = other.activeKeys;
+    inactiveKeys = other.inactiveKeys;
+
+    other.type = EntryType::none;
+
+    return *this;
+}
+
+Shared::Info &Shared::Info::operator=(const Info &other) {
+    type = other.type;
+    address = other.address;
+
+    switch (type) {
+    case EntryType::none:
+        break;
+    case EntryType::contact:
+    case EntryType::ownAccount:
+        vcard = new VCard(other.getVCardRef());
+        activeKeys = new std::list<KeyInfo>(other.getActiveKeysRef());
+        inactiveKeys = new std::list<KeyInfo>(other.getInactiveKeysRef());
+        break;
+    default:
+        throw 351;
+    }
+
+    return *this;
+}
+
 Shared::Info::~Info() {
     turnIntoNone();
 }
 
 void Shared::Info::turnIntoNone() {
     switch (type) {
-        case EntryType::none:
-            break;
-        case EntryType::contact:
-        case EntryType::ownAccount:
-            delete vcard;
-            vcard = nullptr;
-            delete activeKeys;
-            activeKeys = nullptr;
-            delete inactiveKeys;
-            inactiveKeys = nullptr;
-            break;
-        default:
-            break;
+    case EntryType::none:
+        break;
+    case EntryType::contact:
+    case EntryType::ownAccount:
+        delete vcard;
+        vcard = nullptr;
+        delete activeKeys;
+        activeKeys = nullptr;
+        delete inactiveKeys;
+        inactiveKeys = nullptr;
+        break;
+    default:
+        break;
     }
     type = EntryType::none;
 }
 
 void Shared::Info::turnIntoContact(
-    const Shared::VCard& crd,
-    const std::list<KeyInfo>& aks,
-    const std::list<KeyInfo>& iaks
+    const Shared::VCard &crd, const std::list<KeyInfo> &aks, const std::list<KeyInfo> &iaks
 ) {
     switch (type) {
-        case EntryType::none:
-            vcard = new VCard(crd);
-            activeKeys = new std::list<KeyInfo>(aks);
-            inactiveKeys = new std::list<KeyInfo>(iaks);
-            break;
-        case EntryType::contact:
-        case EntryType::ownAccount:
-            *vcard = crd;
-            *activeKeys = aks;
-            *inactiveKeys = iaks;
-            break;
-        default:
-            break;
+    case EntryType::none:
+        vcard = new VCard(crd);
+        activeKeys = new std::list<KeyInfo>(aks);
+        inactiveKeys = new std::list<KeyInfo>(iaks);
+        break;
+    case EntryType::contact:
+    case EntryType::ownAccount:
+        *vcard = crd;
+        *activeKeys = aks;
+        *inactiveKeys = iaks;
+        break;
+    default:
+        break;
     }
 
     type = EntryType::contact;
 }
 
-void Shared::Info::turnIntoContact(
-    Shared::VCard* crd,
-    std::list<KeyInfo>* aks,
-    std::list<KeyInfo>* iaks
-) {
+void Shared::Info::turnIntoContact(Shared::VCard *crd, std::list<KeyInfo> *aks, std::list<KeyInfo> *iaks) {
     switch (type) {
-        case EntryType::contact:
-        case EntryType::ownAccount:
-            delete vcard;
-            delete activeKeys;
-            delete inactiveKeys;
-            [[fallthrough]];
-        case EntryType::none:
-            vcard = crd;
-            activeKeys = aks;
-            inactiveKeys = iaks;
-            break;
-        default:
-            break;
+    case EntryType::contact:
+    case EntryType::ownAccount:
+        delete vcard;
+        delete activeKeys;
+        delete inactiveKeys;
+        [[fallthrough]];
+    case EntryType::none:
+        vcard = crd;
+        activeKeys = aks;
+        inactiveKeys = iaks;
+        break;
+    default:
+        break;
     }
 
     type = EntryType::contact;
 }
 
 void Shared::Info::turnIntoOwnAccount(
-    const Shared::VCard& crd,
-    const std::list<KeyInfo>& aks,
-    const std::list<KeyInfo>& iaks
-) {
-        switch (type) {
-        case EntryType::none:
-            vcard = new VCard(crd);
-            activeKeys = new std::list<KeyInfo>(aks);
-            inactiveKeys = new std::list<KeyInfo>(iaks);
-            break;
-        case EntryType::contact:
-        case EntryType::ownAccount:
-            *vcard = crd;
-            *activeKeys = aks;
-            *inactiveKeys = iaks;
-            break;
-        default:
-            break;
-    }
-
-    type = EntryType::ownAccount;
-}
-
-void Shared::Info::turnIntoOwnAccount(
-    Shared::VCard* crd,
-    std::list<KeyInfo>* aks,
-    std::list<KeyInfo>* iaks
+    const Shared::VCard &crd, const std::list<KeyInfo> &aks, const std::list<KeyInfo> &iaks
 ) {
     switch (type) {
-        case EntryType::contact:
-        case EntryType::ownAccount:
-            delete vcard;
-            delete activeKeys;
-            delete inactiveKeys;
-            [[fallthrough]];
-        case EntryType::none:
-            vcard = crd;
-            activeKeys = aks;
-            inactiveKeys = iaks;
-            break;
-        default:
-            break;
+    case EntryType::none:
+        vcard = new VCard(crd);
+        activeKeys = new std::list<KeyInfo>(aks);
+        inactiveKeys = new std::list<KeyInfo>(iaks);
+        break;
+    case EntryType::contact:
+    case EntryType::ownAccount:
+        *vcard = crd;
+        *activeKeys = aks;
+        *inactiveKeys = iaks;
+        break;
+    default:
+        break;
     }
 
     type = EntryType::ownAccount;
 }
 
-void Shared::Info::setAddress(const QString& addr) {
-    address = addr;}
+void Shared::Info::turnIntoOwnAccount(Shared::VCard *crd, std::list<KeyInfo> *aks, std::list<KeyInfo> *iaks) {
+    switch (type) {
+    case EntryType::contact:
+    case EntryType::ownAccount:
+        delete vcard;
+        delete activeKeys;
+        delete inactiveKeys;
+        [[fallthrough]];
+    case EntryType::none:
+        vcard = crd;
+        activeKeys = aks;
+        inactiveKeys = iaks;
+        break;
+    default:
+        break;
+    }
+
+    type = EntryType::ownAccount;
+}
+
+void Shared::Info::setAddress(const QString &addr) {
+    address = addr;
+}
 
 QString Shared::Info::getAddress() const {
-    return address;}
+    return address;
+}
 
-const QString& Shared::Info::getAddressRef() const {
-    return address;}
+const QString &Shared::Info::getAddressRef() const {
+    return address;
+}
 
 Shared::EntryType Shared::Info::getType() const {
-    return type;}
+    return type;
+}
 
-std::list<Shared::KeyInfo> & Shared::Info::getActiveKeysRef() {
+std::list<Shared::KeyInfo> &Shared::Info::getActiveKeysRef() {
     switch (type) {
-        case EntryType::contact:
-        case EntryType::ownAccount:
-            return *activeKeys;
-            break;
-        default:
-            throw 354;
+    case EntryType::contact:
+    case EntryType::ownAccount:
+        return *activeKeys;
+        break;
+    default:
+        throw 354;
     }
 }
 
-const std::list<Shared::KeyInfo> & Shared::Info::getActiveKeysRef() const {
+const std::list<Shared::KeyInfo> &Shared::Info::getActiveKeysRef() const {
     switch (type) {
-        case EntryType::contact:
-        case EntryType::ownAccount:
-            return *activeKeys;
-            break;
-        default:
-            throw 355;
+    case EntryType::contact:
+    case EntryType::ownAccount:
+        return *activeKeys;
+        break;
+    default:
+        throw 355;
     }
 }
 
-std::list<Shared::KeyInfo>* Shared::Info::getActiveKeys() {
+std::list<Shared::KeyInfo> *Shared::Info::getActiveKeys() {
     switch (type) {
-        case EntryType::contact:
-        case EntryType::ownAccount:
-            return activeKeys;
-            break;
-        default:
-            throw 356;
+    case EntryType::contact:
+    case EntryType::ownAccount:
+        return activeKeys;
+        break;
+    default:
+        throw 356;
     }
 }
 
-const std::list<Shared::KeyInfo>* Shared::Info::getActiveKeys() const {
+const std::list<Shared::KeyInfo> *Shared::Info::getActiveKeys() const {
     switch (type) {
-        case EntryType::contact:
-        case EntryType::ownAccount:
-            return activeKeys;
-            break;
-        default:
-            throw 357;
+    case EntryType::contact:
+    case EntryType::ownAccount:
+        return activeKeys;
+        break;
+    default:
+        throw 357;
     }
 }
 
-std::list<Shared::KeyInfo> & Shared::Info::getInactiveKeysRef() {
+std::list<Shared::KeyInfo> &Shared::Info::getInactiveKeysRef() {
     switch (type) {
-        case EntryType::contact:
-        case EntryType::ownAccount:
-            return *inactiveKeys;
-            break;
-        default:
-            throw 358;
+    case EntryType::contact:
+    case EntryType::ownAccount:
+        return *inactiveKeys;
+        break;
+    default:
+        throw 358;
     }
 }
 
-const std::list<Shared::KeyInfo> & Shared::Info::getInactiveKeysRef() const {
+const std::list<Shared::KeyInfo> &Shared::Info::getInactiveKeysRef() const {
     switch (type) {
-        case EntryType::contact:
-        case EntryType::ownAccount:
-            return *inactiveKeys;
-            break;
-        default:
-            throw 359;
+    case EntryType::contact:
+    case EntryType::ownAccount:
+        return *inactiveKeys;
+        break;
+    default:
+        throw 359;
     }
 }
 
-std::list<Shared::KeyInfo>* Shared::Info::getInactiveKeys() {
+std::list<Shared::KeyInfo> *Shared::Info::getInactiveKeys() {
     switch (type) {
-        case EntryType::contact:
-        case EntryType::ownAccount:
-            return inactiveKeys;
-            break;
-        default:
-            throw 360;
+    case EntryType::contact:
+    case EntryType::ownAccount:
+        return inactiveKeys;
+        break;
+    default:
+        throw 360;
     }
 }
 
-const std::list<Shared::KeyInfo>* Shared::Info::getInactiveKeys() const {
+const std::list<Shared::KeyInfo> *Shared::Info::getInactiveKeys() const {
     switch (type) {
-        case EntryType::contact:
-        case EntryType::ownAccount:
-            return inactiveKeys;
-            break;
-        default:
-            throw 361;
+    case EntryType::contact:
+    case EntryType::ownAccount:
+        return inactiveKeys;
+        break;
+    default:
+        throw 361;
     }
 }
 
-const Shared::VCard & Shared::Info::getVCardRef() const {
+const Shared::VCard &Shared::Info::getVCardRef() const {
     switch (type) {
-        case EntryType::contact:
-        case EntryType::ownAccount:
-            return *vcard;
-            break;
-        default:
-            throw 362;
+    case EntryType::contact:
+    case EntryType::ownAccount:
+        return *vcard;
+        break;
+    default:
+        throw 362;
     }
 }
 
-Shared::VCard & Shared::Info::getVCardRef()  {
+Shared::VCard &Shared::Info::getVCardRef() {
     switch (type) {
-        case EntryType::contact:
-        case EntryType::ownAccount:
-            return *vcard;
-            break;
-        default:
-            throw 363;
+    case EntryType::contact:
+    case EntryType::ownAccount:
+        return *vcard;
+        break;
+    default:
+        throw 363;
     }
 }
 
-const Shared::VCard * Shared::Info::getVCard() const {
+const Shared::VCard *Shared::Info::getVCard() const {
     switch (type) {
-        case EntryType::contact:
-        case EntryType::ownAccount:
-            return vcard;
-            break;
-        default:
-            throw 364;
+    case EntryType::contact:
+    case EntryType::ownAccount:
+        return vcard;
+        break;
+    default:
+        throw 364;
     }
 }
 
-Shared::VCard * Shared::Info::getVCard() {
+Shared::VCard *Shared::Info::getVCard() {
     switch (type) {
-        case EntryType::contact:
-        case EntryType::ownAccount:
-            return vcard;
-            break;
-        default:
-            throw 365;
+    case EntryType::contact:
+    case EntryType::ownAccount:
+        return vcard;
+        break;
+    default:
+        throw 365;
     }
 }
 
-void Shared::Info::setActiveKeys(std::list<KeyInfo>* keys) {
+void Shared::Info::setActiveKeys(std::list<KeyInfo> *keys) {
     switch (type) {
-        case EntryType::contact:
-        case EntryType::ownAccount:
-            activeKeys = keys;
-            break;
-        default:
-            throw 366;
+    case EntryType::contact:
+    case EntryType::ownAccount:
+        activeKeys = keys;
+        break;
+    default:
+        throw 366;
     }
 }
 
-void Shared::Info::setVCard(Shared::VCard* card) {
+void Shared::Info::setVCard(Shared::VCard *card) {
     switch (type) {
-        case EntryType::contact:
-        case EntryType::ownAccount:
-            vcard = card;
-            break;
-        default:
-            throw 367;
+    case EntryType::contact:
+    case EntryType::ownAccount:
+        vcard = card;
+        break;
+    default:
+        throw 367;
     }
 }
-
diff --git a/shared/info.h b/shared/info.h
index e42eddb..d5a48b1 100644
--- a/shared/info.h
+++ b/shared/info.h
@@ -1,21 +1,22 @@
-// Squawk messenger. 
-// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
-// 
-// 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/>.
+/*
+ * Squawk messenger.
+ * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+ *
+ * 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/>.
+ */
 
-#ifndef SHARED_INFO_H
-#define SHARED_INFO_H
+#pragma once
 
 #include "vcard.h"
 #include "keyinfo.h"
@@ -36,7 +37,10 @@ public:
     Info();
     Info(const QString& address, EntryType = EntryType::none);
     Info(const Info& other);
+    Info(Info&& other);
     virtual ~Info();
+    Info& operator = (const Info& other);
+    Info& operator = (Info&& other);
 
     QString getAddress() const;
     const QString& getAddressRef() const;
@@ -90,7 +94,4 @@ private:
     std::list<KeyInfo>* activeKeys;
     std::list<KeyInfo>* inactiveKeys;
 };
-
 }
-
-#endif // SHARED_INFO_H
diff --git a/ui/models/info/omemo/keys.cpp b/ui/models/info/omemo/keys.cpp
index 5d957b1..9ef8c73 100644
--- a/ui/models/info/omemo/keys.cpp
+++ b/ui/models/info/omemo/keys.cpp
@@ -1,18 +1,20 @@
-// Squawk messenger. 
-// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
-// 
-// 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/>.
+/*
+ * Squawk messenger.
+ * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+ *
+ * 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 "keys.h"
 
@@ -37,18 +39,34 @@ Models::Keys::~Keys() {
         delete pair.second;
 }
 
-std::deque<Shared::KeyInfo> Models::Keys::modifiedKeys() const {
-    std::deque<Shared::KeyInfo> response(modified.size());
+std::list<Shared::KeyInfo> Models::Keys::modifiedKeys() const {
+    std::list<Shared::KeyInfo> response;
+    for (const std::pair<const int, Shared::KeyInfo*>& pair: modified)
+        response.push_back(*(pair.second));
 
-    int i = 0;
-    for (const std::pair<const int, Shared::KeyInfo*>& pair: modified) {
-        response[i] = *(pair.second);
-        ++i;
-    }
 
     return response;
 }
 
+std::list<Shared::KeyInfo> Models::Keys::finalKeys() const {
+    std::list<Shared::KeyInfo> result;
+    finalKeys(result);
+    return result;
+}
+
+void Models::Keys::finalKeys(std::list<Shared::KeyInfo>& out) const {
+    for (int i = 0; i < rowCount(); ++i)
+        out.push_back(key(i));
+}
+
+const Shared::KeyInfo & Models::Keys::key(unsigned int i) const {
+    std::map<int, Shared::KeyInfo*>::const_iterator itr = modified.find(i);
+    if (itr != modified.end())
+        return*(itr->second);
+    else
+        return *(keys[i]);
+}
+
 void Models::Keys::addKey(const Shared::KeyInfo& info) {
     beginInsertRows(QModelIndex(), keys.size(), keys.size());
     keys.push_back(new Shared::KeyInfo(info));
diff --git a/ui/models/info/omemo/keys.h b/ui/models/info/omemo/keys.h
index 49948a2..1bc79de 100644
--- a/ui/models/info/omemo/keys.h
+++ b/ui/models/info/omemo/keys.h
@@ -1,21 +1,22 @@
-// Squawk messenger. 
-// Copyright (C) 2019  Yury Gubich <blue@macaw.me>
-// 
-// 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/>.
+/*
+ * Squawk messenger.
+ * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+ *
+ * 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/>.
+ */
 
-#ifndef MODELS_KEYS_H
-#define MODELS_KEYS_H
+#pragma once
 
 #include <QAbstractListModel>
 
@@ -23,11 +24,7 @@
 
 namespace Models {
 
-/**
- * @todo write docs
- */
-class Keys : public QAbstractListModel
-{
+class Keys : public QAbstractListModel {
 public:
     Keys(QObject *parent = nullptr);
     ~Keys();
@@ -37,11 +34,14 @@ public:
 
     QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const override;
     int rowCount(const QModelIndex& parent = QModelIndex()) const override;
+    const Shared::KeyInfo& key(unsigned int i) const;
 
     QHash<int, QByteArray> roleNames() const override;
     QModelIndex index(int row, int column, const QModelIndex & parent) const override;
 
-    std::deque<Shared::KeyInfo> modifiedKeys() const;
+    std::list<Shared::KeyInfo> modifiedKeys() const;
+    std::list<Shared::KeyInfo> finalKeys() const;
+    void finalKeys(std::list<Shared::KeyInfo>& out) const;
 
     enum Roles {
         Label = Qt::UserRole + 1,
@@ -64,5 +64,3 @@ private:
 };
 
 }
-
-#endif // MODELS_KEYS_H
diff --git a/ui/widgets/info/info.cpp b/ui/widgets/info/info.cpp
index 948ee27..26f63f1 100644
--- a/ui/widgets/info/info.cpp
+++ b/ui/widgets/info/info.cpp
@@ -114,6 +114,9 @@ void UI::Info::onButtonBoxAccepted() {
         contactGeneral->fillVCard(card);
         contactContacts->fillVCard(card);
         card.setDescription(description->description());
+#ifdef WITH_OMEMO
+        omemo->fillData(info.getActiveKeysRef());
+#endif
         emit saveInfo(info);
         emit close();
     }
@@ -161,7 +164,8 @@ void UI::Info::initializeDescription(const QString& descr, bool editable) {
 }
 
 QString UI::Info::getJid() const {
-    return jid;}
+    return jid;
+}
 
 void UI::Info::clear() {
     if (contactGeneral != nullptr) {
diff --git a/ui/widgets/info/info.h b/ui/widgets/info/info.h
index 7d52693..10b7209 100644
--- a/ui/widgets/info/info.h
+++ b/ui/widgets/info/info.h
@@ -39,8 +39,7 @@
 
 
 namespace UI {
-namespace Ui
-{
+namespace Ui {
 class Info;
 }
 
diff --git a/ui/widgets/info/omemo/omemo.cpp b/ui/widgets/info/omemo/omemo.cpp
index 294fbe2..baa7aa0 100644
--- a/ui/widgets/info/omemo/omemo.cpp
+++ b/ui/widgets/info/omemo/omemo.cpp
@@ -88,6 +88,12 @@ void UI::Omemo::setData(const std::list<Shared::KeyInfo>& keys) {
     deviceKeyVisibility(deviceKeyModel.rowCount() > 0);
 }
 
+void UI::Omemo::fillData(std::list<Shared::KeyInfo>& out) {
+    deviceKeyModel.finalKeys(out);
+    keysModel.finalKeys(out);
+    unusedKeysModel.finalKeys(out);
+}
+
 const QString UI::Omemo::title() const {
     return m_ui->OMEMOHeading->text();
 }
diff --git a/ui/widgets/info/omemo/omemo.h b/ui/widgets/info/omemo/omemo.h
index 75b1df2..64c2486 100644
--- a/ui/widgets/info/omemo/omemo.h
+++ b/ui/widgets/info/omemo/omemo.h
@@ -41,6 +41,7 @@ public:
     ~Omemo();
 
     void setData(const std::list<Shared::KeyInfo>& keys);
+    void fillData(std::list<Shared::KeyInfo>& out);
     const QString title() const;
 
 private slots:

From 93c5be412ea6130971d8ede1e9bb7e6ba65929cc Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Fri, 17 Nov 2023 21:52:33 -0300
Subject: [PATCH 262/281] trying linter settings

---
 .uncrustify.cfg | 3640 +++++++++++++++++++++++++++++++++++++++++++++++
 shared/info.cpp |  475 +++----
 shared/info.h   |   82 +-
 3 files changed, 3903 insertions(+), 294 deletions(-)
 create mode 100644 .uncrustify.cfg

diff --git a/.uncrustify.cfg b/.uncrustify.cfg
new file mode 100644
index 0000000..c333a42
--- /dev/null
+++ b/.uncrustify.cfg
@@ -0,0 +1,3640 @@
+# Uncrustify_d-0.77.1_f
+
+#
+# General options
+#
+
+# The type of line endings.
+#
+# Default: auto
+newlines                        = lf       # lf/crlf/cr/auto
+
+# The original size of tabs in the input.
+#
+# Default: 8
+input_tab_size                  = 8        # unsigned number
+
+# The size of tabs in the output (only used if align_with_tabs=true).
+#
+# Default: 8
+output_tab_size                 = 8        # unsigned number
+
+# The ASCII value of the string escape char, usually 92 (\) or (Pawn) 94 (^).
+#
+# Default: 92
+string_escape_char              = 92       # unsigned number
+
+# Alternate string escape char (usually only used for Pawn).
+# Only works right before the quote char.
+string_escape_char2             = 0        # unsigned number
+
+# Replace tab characters found in string literals with the escape sequence \t
+# instead.
+string_replace_tab_chars        = false    # true/false
+
+# Allow interpreting '>=' and '>>=' as part of a template in code like
+# 'void f(list<list<B>>=val);'. If true, 'assert(x<0 && y>=3)' will be broken.
+# Improvements to template detection may make this option obsolete.
+tok_split_gte                   = false    # true/false
+
+# Disable formatting of NL_CONT ('\\n') ended lines (e.g. multi-line macros).
+disable_processing_nl_cont      = false    # true/false
+
+# Specify the marker used in comments to disable processing of part of the
+# file.
+#
+# Default:  *INDENT-OFF*
+disable_processing_cmt          = " *INDENT-OFF*"      # string
+
+# Specify the marker used in comments to (re)enable processing in a file.
+#
+# Default:  *INDENT-ON*
+enable_processing_cmt           = " *INDENT-ON*"     # string
+
+# Enable parsing of digraphs.
+enable_digraphs                 = false    # true/false
+
+# Option to allow both disable_processing_cmt and enable_processing_cmt
+# strings, if specified, to be interpreted as ECMAScript regular expressions.
+# If true, a regex search will be performed within comments according to the
+# specified patterns in order to disable/enable processing.
+processing_cmt_as_regex         = false    # true/false
+
+# Add or remove the UTF-8 BOM (recommend 'remove').
+utf8_bom                        = ignore   # ignore/add/remove/force/not_defined
+
+# If the file contains bytes with values between 128 and 255, but is not
+# UTF-8, then output as UTF-8.
+utf8_byte                       = false    # true/false
+
+# Force the output encoding to UTF-8.
+utf8_force                      = false    # true/false
+
+#
+# Spacing options
+#
+
+# Add or remove space around non-assignment symbolic operators ('+', '/', '%',
+# '<<', and so forth).
+sp_arith                        = force   # ignore/add/remove/force/not_defined
+
+# Add or remove space around arithmetic operators '+' and '-'.
+#
+# Overrides sp_arith.
+sp_arith_additive               = force   # ignore/add/remove/force/not_defined
+
+# Add or remove space around assignment operator '=', '+=', etc.
+sp_assign                       = force   # ignore/add/remove/force/not_defined
+
+# Add or remove space around '=' in C++11 lambda capture specifications.
+#
+# Overrides sp_assign.
+sp_cpp_lambda_assign            = remove   # ignore/add/remove/force/not_defined
+
+# Add or remove space after the capture specification of a C++11 lambda when
+# an argument list is present, as in '[] <here> (int x){ ... }'.
+sp_cpp_lambda_square_paren      = force   # ignore/add/remove/force/not_defined
+
+# Add or remove space after the capture specification of a C++11 lambda with
+# no argument list is present, as in '[] <here> { ... }'.
+sp_cpp_lambda_square_brace      = force   # ignore/add/remove/force/not_defined
+
+# Add or remove space after the opening parenthesis and before the closing
+# parenthesis of a argument list of a C++11 lambda, as in
+# '[]( <here> int x <here> ){ ... }'.
+sp_cpp_lambda_argument_list     = remove   # ignore/add/remove/force/not_defined
+
+# Add or remove space after the argument list of a C++11 lambda, as in
+# '[](int x) <here> { ... }'.
+sp_cpp_lambda_paren_brace       = force   # ignore/add/remove/force/not_defined
+
+# Add or remove space between a lambda body and its call operator of an
+# immediately invoked lambda, as in '[]( ... ){ ... } <here> ( ... )'.
+sp_cpp_lambda_fparen            = remove   # ignore/add/remove/force/not_defined
+
+# Add or remove space around assignment operator '=' in a prototype.
+#
+# If set to ignore, use sp_assign.
+sp_assign_default               = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove space before assignment operator '=', '+=', etc.
+#
+# Overrides sp_assign.
+sp_before_assign                = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove space after assignment operator '=', '+=', etc.
+#
+# Overrides sp_assign.
+sp_after_assign                 = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove space in 'enum {'.
+#
+# Default: add
+sp_enum_brace                   = force      # ignore/add/remove/force/not_defined
+
+# Add or remove space in 'NS_ENUM ('.
+sp_enum_paren                   = force   # ignore/add/remove/force/not_defined
+
+# Add or remove space around assignment '=' in enum.
+sp_enum_assign                  = force   # ignore/add/remove/force/not_defined
+
+# Add or remove space before assignment '=' in enum.
+#
+# Overrides sp_enum_assign.
+sp_enum_before_assign           = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove space after assignment '=' in enum.
+#
+# Overrides sp_enum_assign.
+sp_enum_after_assign            = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove space around assignment ':' in enum.
+sp_enum_colon                   = add   # ignore/add/remove/force/not_defined
+
+# Add or remove space around preprocessor '##' concatenation operator.
+#
+# Default: add
+sp_pp_concat                    = add      # ignore/add/remove/force/not_defined
+
+# Add or remove space after preprocessor '#' stringify operator.
+# Also affects the '#@' charizing operator.
+sp_pp_stringify                 = remove   # ignore/add/remove/force/not_defined
+
+# Add or remove space before preprocessor '#' stringify operator
+# as in '#define x(y) L#y'.
+sp_before_pp_stringify          = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove space around boolean operators '&&' and '||'.
+sp_bool                         = force   # ignore/add/remove/force/not_defined
+
+# Add or remove space around compare operator '<', '>', '==', etc.
+sp_compare                      = force   # ignore/add/remove/force/not_defined
+
+# Add or remove space inside '(' and ')'.
+sp_inside_paren                 = remove   # ignore/add/remove/force/not_defined
+
+# Add or remove space between nested parentheses, i.e. '((' vs. ') )'.
+sp_paren_paren                  = remove   # ignore/add/remove/force/not_defined
+
+# Add or remove space between back-to-back parentheses, i.e. ')(' vs. ') ('.
+sp_cparen_oparen                = remove   # ignore/add/remove/force/not_defined
+
+# Whether to balance spaces inside nested parentheses.
+sp_balance_nested_parens        = false    # true/false
+
+# Add or remove space between ')' and '{'.
+sp_paren_brace                  = force   # ignore/add/remove/force/not_defined
+
+# Add or remove space between nested braces, i.e. '{{' vs. '{ {'.
+sp_brace_brace                  = remove   # ignore/add/remove/force/not_defined
+
+# Add or remove space before pointer star '*'.
+sp_before_ptr_star              = remove   # ignore/add/remove/force/not_defined
+
+# Add or remove space before pointer star '*' that isn't followed by a
+# variable name. If set to ignore, sp_before_ptr_star is used instead.
+sp_before_unnamed_ptr_star      = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove space between a qualifier and a pointer star '*' that isn't
+# followed by a variable name, as in '(char const *)'. If set to ignore,
+# sp_before_ptr_star is used instead.
+sp_qualifier_unnamed_ptr_star   = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove space between pointer stars '*', as in 'int ***a;'.
+sp_between_ptr_star             = remove   # ignore/add/remove/force/not_defined
+
+# Add or remove space after pointer star '*', if followed by a word.
+#
+# Overrides sp_type_func.
+sp_after_ptr_star               = force   # ignore/add/remove/force/not_defined
+
+# Add or remove space after pointer caret '^', if followed by a word.
+sp_after_ptr_block_caret        = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove space after pointer star '*', if followed by a qualifier.
+sp_after_ptr_star_qualifier     = force   # ignore/add/remove/force/not_defined
+
+# Add or remove space after a pointer star '*', if followed by a function
+# prototype or function definition.
+#
+# Overrides sp_after_ptr_star and sp_type_func.
+sp_after_ptr_star_func          = force   # ignore/add/remove/force/not_defined
+
+# Add or remove space after a pointer star '*' in the trailing return of a
+# function prototype or function definition.
+sp_after_ptr_star_trailing      = force   # ignore/add/remove/force/not_defined
+
+# Add or remove space between the pointer star '*' and the name of the variable
+# in a function pointer definition.
+sp_ptr_star_func_var            = force   # ignore/add/remove/force/not_defined
+
+# Add or remove space between the pointer star '*' and the name of the type
+# in a function pointer type definition.
+sp_ptr_star_func_type           = force   # ignore/add/remove/force/not_defined
+
+# Add or remove space after a pointer star '*', if followed by an open
+# parenthesis, as in 'void* (*)()'.
+sp_ptr_star_paren               = force   # ignore/add/remove/force/not_defined
+
+# Add or remove space before a pointer star '*', if followed by a function
+# prototype or function definition. If set to ignore, sp_before_ptr_star is
+# used instead.
+sp_before_ptr_star_func         = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove space between a qualifier and a pointer star '*' followed by
+# the name of the function in a function prototype or definition, as in
+# 'char const *foo()`. If set to ignore, sp_before_ptr_star is used instead.
+sp_qualifier_ptr_star_func      = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove space before a pointer star '*' in the trailing return of a
+# function prototype or function definition.
+sp_before_ptr_star_trailing     = remove   # ignore/add/remove/force/not_defined
+
+# Add or remove space between a qualifier and a pointer star '*' in the
+# trailing return of a function prototype or function definition, as in
+# 'auto foo() -> char const *'.
+sp_qualifier_ptr_star_trailing  = remove   # ignore/add/remove/force/not_defined
+
+# Add or remove space before a reference sign '&'.
+sp_before_byref                 = remove   # ignore/add/remove/force/not_defined
+
+# Add or remove space before a reference sign '&' that isn't followed by a
+# variable name. If set to ignore, sp_before_byref is used instead.
+sp_before_unnamed_byref         = remove   # ignore/add/remove/force/not_defined
+
+# Add or remove space after reference sign '&', if followed by a word.
+#
+# Overrides sp_type_func.
+sp_after_byref                  = force   # ignore/add/remove/force/not_defined
+
+# Add or remove space after a reference sign '&', if followed by a function
+# prototype or function definition.
+#
+# Overrides sp_after_byref and sp_type_func.
+sp_after_byref_func             = force   # ignore/add/remove/force/not_defined
+
+# Add or remove space before a reference sign '&', if followed by a function
+# prototype or function definition.
+sp_before_byref_func            = remove   # ignore/add/remove/force/not_defined
+
+# Add or remove space after a reference sign '&', if followed by an open
+# parenthesis, as in 'char& (*)()'.
+sp_byref_paren                  = force   # ignore/add/remove/force/not_defined
+
+# Add or remove space between type and word. In cases where total removal of
+# whitespace would be a syntax error, a value of 'remove' is treated the same
+# as 'force'.
+#
+# This also affects some other instances of space following a type that are
+# not covered by other options; for example, between the return type and
+# parenthesis of a function type template argument, between the type and
+# parenthesis of an array parameter, or between 'decltype(...)' and the
+# following word.
+#
+# Default: force
+sp_after_type                   = force    # ignore/add/remove/force/not_defined
+
+# Add or remove space between 'decltype(...)' and word,
+# brace or function call.
+sp_after_decltype               = ignore   # ignore/add/remove/force/not_defined
+
+# (D) Add or remove space before the parenthesis in the D constructs
+# 'template Foo(' and 'class Foo('.
+sp_before_template_paren        = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove space between 'template' and '<'.
+# If set to ignore, sp_before_angle is used.
+sp_template_angle               = force   # ignore/add/remove/force/not_defined
+
+# Add or remove space before '<'.
+sp_before_angle                 = remove   # ignore/add/remove/force/not_defined
+
+# Add or remove space inside '<' and '>'.
+sp_inside_angle                 = remove   # ignore/add/remove/force/not_defined
+
+# Add or remove space inside '<>'.
+sp_inside_angle_empty           = remove   # ignore/add/remove/force/not_defined
+
+# Add or remove space between '>' and ':'.
+sp_angle_colon                  = remove   # ignore/add/remove/force/not_defined
+
+# Add or remove space after '>'.
+sp_after_angle                  = force   # ignore/add/remove/force/not_defined
+
+# Add or remove space between '>' and '(' as found in 'new List<byte>(foo);'.
+sp_angle_paren                  = remove   # ignore/add/remove/force/not_defined
+
+# Add or remove space between '>' and '()' as found in 'new List<byte>();'.
+sp_angle_paren_empty            = remove   # ignore/add/remove/force/not_defined
+
+# Add or remove space between '>' and a word as in 'List<byte> m;' or
+# 'template <typename T> static ...'.
+sp_angle_word                   = force   # ignore/add/remove/force/not_defined
+
+# Add or remove space between '>' and '>' in '>>' (template stuff).
+#
+# Default: add
+sp_angle_shift                  = remove      # ignore/add/remove/force/not_defined
+
+# (C++11) Permit removal of the space between '>>' in 'foo<bar<int> >'. Note
+# that sp_angle_shift cannot remove the space without this option.
+sp_permit_cpp11_shift           = true    # true/false
+
+# Add or remove space before '(' of control statements ('if', 'for', 'switch',
+# 'while', etc.).
+sp_before_sparen                = force   # ignore/add/remove/force/not_defined
+
+# Add or remove space inside '(' and ')' of control statements other than
+# 'for'.
+sp_inside_sparen                = remove   # ignore/add/remove/force/not_defined
+
+# Add or remove space after '(' of control statements other than 'for'.
+#
+# Overrides sp_inside_sparen.
+sp_inside_sparen_open           = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove space before ')' of control statements other than 'for'.
+#
+# Overrides sp_inside_sparen.
+sp_inside_sparen_close          = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove space inside '(' and ')' of 'for' statements.
+sp_inside_for                   = remove   # ignore/add/remove/force/not_defined
+
+# Add or remove space after '(' of 'for' statements.
+#
+# Overrides sp_inside_for.
+sp_inside_for_open              = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove space before ')' of 'for' statements.
+#
+# Overrides sp_inside_for.
+sp_inside_for_close             = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove space between '((' or '))' of control statements.
+sp_sparen_paren                 = remove   # ignore/add/remove/force/not_defined
+
+# Add or remove space after ')' of control statements.
+sp_after_sparen                 = force   # ignore/add/remove/force/not_defined
+
+# Add or remove space between ')' and '{' of control statements.
+sp_sparen_brace                 = force   # ignore/add/remove/force/not_defined
+
+# Add or remove space between 'do' and '{'.
+sp_do_brace_open                = force   # ignore/add/remove/force/not_defined
+
+# Add or remove space between '}' and 'while'.
+sp_brace_close_while            = force   # ignore/add/remove/force/not_defined
+
+# Add or remove space between 'while' and '('. Overrides sp_before_sparen.
+sp_while_paren_open             = force   # ignore/add/remove/force/not_defined
+
+# (D) Add or remove space between 'invariant' and '('.
+sp_invariant_paren              = force   # ignore/add/remove/force/not_defined
+
+# (D) Add or remove space after the ')' in 'invariant (C) c'.
+sp_after_invariant_paren        = force   # ignore/add/remove/force/not_defined
+
+# Add or remove space before empty statement ';' on 'if', 'for' and 'while'.
+sp_special_semi                 = remove   # ignore/add/remove/force/not_defined
+
+# Add or remove space before ';'.
+#
+# Default: remove
+sp_before_semi                  = remove   # ignore/add/remove/force/not_defined
+
+# Add or remove space before ';' in non-empty 'for' statements.
+sp_before_semi_for              = remove   # ignore/add/remove/force/not_defined
+
+# Add or remove space before a semicolon of an empty left part of a for
+# statement, as in 'for ( <here> ; ; )'.
+sp_before_semi_for_empty        = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove space between the semicolons of an empty middle part of a for
+# statement, as in 'for ( ; <here> ; )'.
+sp_between_semi_for_empty       = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove space after ';', except when followed by a comment.
+#
+# Default: add
+sp_after_semi                   = add      # ignore/add/remove/force/not_defined
+
+# Add or remove space after ';' in non-empty 'for' statements.
+#
+# Default: force
+sp_after_semi_for               = force    # ignore/add/remove/force/not_defined
+
+# Add or remove space after the final semicolon of an empty part of a for
+# statement, as in 'for ( ; ; <here> )'.
+sp_after_semi_for_empty         = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove space before '[' (except '[]').
+sp_before_square                = remove   # ignore/add/remove/force/not_defined
+
+# Add or remove space before '[' for a variable definition.
+#
+# Default: remove
+sp_before_vardef_square         = remove   # ignore/add/remove/force/not_defined
+
+# Add or remove space before '[' for asm block.
+sp_before_square_asm_block      = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove space before '[]'.
+sp_before_squares               = remove   # ignore/add/remove/force/not_defined
+
+# Add or remove space before C++17 structured bindings.
+sp_cpp_before_struct_binding    = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove space inside a non-empty '[' and ']'.
+sp_inside_square                = remove   # ignore/add/remove/force/not_defined
+
+# Add or remove space inside '[]'.
+sp_inside_square_empty          = remove   # ignore/add/remove/force/not_defined
+
+# (OC) Add or remove space inside a non-empty Objective-C boxed array '@[' and
+# ']'. If set to ignore, sp_inside_square is used.
+sp_inside_square_oc_array       = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove space after ',', i.e. 'a,b' vs. 'a, b'.
+sp_after_comma                  = force   # ignore/add/remove/force/not_defined
+
+# Add or remove space before ',', i.e. 'a,b' vs. 'a ,b'.
+#
+# Default: remove
+sp_before_comma                 = remove   # ignore/add/remove/force/not_defined
+
+# (C#, Vala) Add or remove space between ',' and ']' in multidimensional array type
+# like 'int[,,]'.
+sp_after_mdatype_commas         = ignore   # ignore/add/remove/force/not_defined
+
+# (C#, Vala) Add or remove space between '[' and ',' in multidimensional array type
+# like 'int[,,]'.
+sp_before_mdatype_commas        = ignore   # ignore/add/remove/force/not_defined
+
+# (C#, Vala) Add or remove space between ',' in multidimensional array type
+# like 'int[,,]'.
+sp_between_mdatype_commas       = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove space between an open parenthesis and comma,
+# i.e. '(,' vs. '( ,'.
+#
+# Default: force
+sp_paren_comma                  = force    # ignore/add/remove/force/not_defined
+
+# Add or remove space between a type and ':'.
+sp_type_colon                   = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove space after the variadic '...' when preceded by a
+# non-punctuator.
+# The value REMOVE will be overridden with FORCE
+sp_after_ellipsis               = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove space before the variadic '...' when preceded by a
+# non-punctuator.
+# The value REMOVE will be overridden with FORCE
+sp_before_ellipsis              = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove space between a type and '...'.
+sp_type_ellipsis                = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove space between a '*' and '...'.
+sp_ptr_type_ellipsis            = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove space between ')' and '...'.
+sp_paren_ellipsis               = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove space between '&&' and '...'.
+sp_byref_ellipsis               = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove space between ')' and a qualifier such as 'const'.
+sp_paren_qualifier              = force   # ignore/add/remove/force/not_defined
+
+# Add or remove space between ')' and 'noexcept'.
+sp_paren_noexcept               = force   # ignore/add/remove/force/not_defined
+
+# Add or remove space after class ':'.
+sp_after_class_colon            = force   # ignore/add/remove/force/not_defined
+
+# Add or remove space before class ':'.
+sp_before_class_colon           = force   # ignore/add/remove/force/not_defined
+
+# Add or remove space after class constructor ':'.
+#
+# Default: add
+sp_after_constr_colon           = add      # ignore/add/remove/force/not_defined
+
+# Add or remove space before class constructor ':'.
+#
+# Default: add
+sp_before_constr_colon          = remove      # ignore/add/remove/force/not_defined
+
+# Add or remove space before case ':'.
+#
+# Default: remove
+sp_before_case_colon            = remove   # ignore/add/remove/force/not_defined
+
+# Add or remove space between 'operator' and operator sign.
+sp_after_operator               = force   # ignore/add/remove/force/not_defined
+
+# Add or remove space between the operator symbol and the open parenthesis, as
+# in 'operator ++('.
+sp_after_operator_sym           = force   # ignore/add/remove/force/not_defined
+
+# Overrides sp_after_operator_sym when the operator has no arguments, as in
+# 'operator *()'.
+sp_after_operator_sym_empty     = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove space after C/D cast, i.e. 'cast(int)a' vs. 'cast(int) a' or
+# '(int)a' vs. '(int) a'.
+sp_after_cast                   = remove   # ignore/add/remove/force/not_defined
+
+# Add or remove spaces inside cast parentheses.
+sp_inside_paren_cast            = remove   # ignore/add/remove/force/not_defined
+
+# Add or remove space between the type and open parenthesis in a C++ cast,
+# i.e. 'int(exp)' vs. 'int (exp)'.
+sp_cpp_cast_paren               = remove   # ignore/add/remove/force/not_defined
+
+# Add or remove space between 'sizeof' and '('.
+sp_sizeof_paren                 = remove   # ignore/add/remove/force/not_defined
+
+# Add or remove space between 'sizeof' and '...'.
+sp_sizeof_ellipsis              = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove space between 'sizeof...' and '('.
+sp_sizeof_ellipsis_paren        = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove space between '...' and a parameter pack.
+sp_ellipsis_parameter_pack      = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove space between a parameter pack and '...'.
+sp_parameter_pack_ellipsis      = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove space between 'decltype' and '('.
+sp_decltype_paren               = ignore   # ignore/add/remove/force/not_defined
+
+# (Pawn) Add or remove space after the tag keyword.
+sp_after_tag                    = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove space inside enum '{' and '}'.
+sp_inside_braces_enum           = remove   # ignore/add/remove/force/not_defined
+
+# Add or remove space inside struct/union '{' and '}'.
+sp_inside_braces_struct         = remove   # ignore/add/remove/force/not_defined
+
+# (OC) Add or remove space inside Objective-C boxed dictionary '{' and '}'
+sp_inside_braces_oc_dict        = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove space after open brace in an unnamed temporary
+# direct-list-initialization
+# if statement is a brace_init_lst
+# works only if sp_brace_brace is set to ignore.
+sp_after_type_brace_init_lst_open = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove space before close brace in an unnamed temporary
+# direct-list-initialization
+# if statement is a brace_init_lst
+# works only if sp_brace_brace is set to ignore.
+sp_before_type_brace_init_lst_close = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove space inside an unnamed temporary direct-list-initialization
+# if statement is a brace_init_lst
+# works only if sp_brace_brace is set to ignore
+# works only if sp_before_type_brace_init_lst_close is set to ignore.
+sp_inside_type_brace_init_lst   = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove space inside '{' and '}'.
+sp_inside_braces                = remove   # ignore/add/remove/force/not_defined
+
+# Add or remove space inside '{}'.
+sp_inside_braces_empty          = remove   # ignore/add/remove/force/not_defined
+
+# Add or remove space around trailing return operator '->'.
+sp_trailing_return              = return   # ignore/add/remove/force/not_defined
+
+# Add or remove space between return type and function name. A minimum of 1
+# is forced except for pointer return types.
+sp_type_func                    = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove space between type and open brace of an unnamed temporary
+# direct-list-initialization.
+sp_type_brace_init_lst          = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove space between function name and '(' on function declaration.
+sp_func_proto_paren             = force   # ignore/add/remove/force/not_defined
+
+# Add or remove space between function name and '()' on function declaration
+# without parameters.
+sp_func_proto_paren_empty       = force   # ignore/add/remove/force/not_defined
+
+# Add or remove space between function name and '(' with a typedef specifier.
+sp_func_type_paren              = force   # ignore/add/remove/force/not_defined
+
+# Add or remove space between alias name and '(' of a non-pointer function type typedef.
+sp_func_def_paren               = force   # ignore/add/remove/force/not_defined
+
+# Add or remove space between function name and '()' on function definition
+# without parameters.
+sp_func_def_paren_empty         = force   # ignore/add/remove/force/not_defined
+
+# Add or remove space inside empty function '()'.
+# Overrides sp_after_angle unless use_sp_after_angle_always is set to true.
+sp_inside_fparens               = remove   # ignore/add/remove/force/not_defined
+
+# Add or remove space inside function '(' and ')'.
+sp_inside_fparen                = remove   # ignore/add/remove/force/not_defined
+
+# Add or remove space inside user functor '(' and ')'.
+sp_func_call_user_inside_rparen = remove   # ignore/add/remove/force/not_defined
+
+# Add or remove space inside empty functor '()'.
+# Overrides sp_after_angle unless use_sp_after_angle_always is set to true.
+sp_inside_rparens               = remove   # ignore/add/remove/force/not_defined
+
+# Add or remove space inside functor '(' and ')'.
+sp_inside_rparen                = remove   # ignore/add/remove/force/not_defined
+
+# Add or remove space inside the first parentheses in a function type, as in
+# 'void (*x)(...)'.
+sp_inside_tparen                = remove   # ignore/add/remove/force/not_defined
+
+# Add or remove space between the ')' and '(' in a function type, as in
+# 'void (*x)(...)'.
+sp_after_tparen_close           = force   # ignore/add/remove/force/not_defined
+
+# Add or remove space between ']' and '(' when part of a function call.
+sp_square_fparen                = remove   # ignore/add/remove/force/not_defined
+
+# Add or remove space between ')' and '{' of function.
+sp_fparen_brace                 = force   # ignore/add/remove/force/not_defined
+
+# Add or remove space between ')' and '{' of a function call in object
+# initialization.
+#
+# Overrides sp_fparen_brace.
+sp_fparen_brace_initializer     = force   # ignore/add/remove/force/not_defined
+
+# (Java) Add or remove space between ')' and '{{' of double brace initializer.
+sp_fparen_dbrace                = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove space between function name and '(' on function calls.
+sp_func_call_paren              = remove   # ignore/add/remove/force/not_defined
+
+# Add or remove space between function name and '()' on function calls without
+# parameters. If set to ignore (the default), sp_func_call_paren is used.
+sp_func_call_paren_empty        = remove   # ignore/add/remove/force/not_defined
+
+# Add or remove space between the user function name and '(' on function
+# calls. You need to set a keyword to be a user function in the config file,
+# like:
+#   set func_call_user tr _ i18n
+sp_func_call_user_paren         = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove space inside user function '(' and ')'.
+sp_func_call_user_inside_fparen = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove space between nested parentheses with user functions,
+# i.e. '((' vs. '( ('.
+sp_func_call_user_paren_paren   = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove space between a constructor/destructor and the open
+# parenthesis.
+sp_func_class_paren             = force   # ignore/add/remove/force/not_defined
+
+# Add or remove space between a constructor without parameters or destructor
+# and '()'.
+sp_func_class_paren_empty       = force   # ignore/add/remove/force/not_defined
+
+# Add or remove space after 'return'.
+#
+# Default: force
+sp_return                       = force    # ignore/add/remove/force/not_defined
+
+# Add or remove space between 'return' and '('.
+sp_return_paren                 = force   # ignore/add/remove/force/not_defined
+
+# Add or remove space between 'return' and '{'.
+sp_return_brace                 = force   # ignore/add/remove/force/not_defined
+
+# Add or remove space between '__attribute__' and '('.
+sp_attribute_paren              = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove space between 'defined' and '(' in '#if defined (FOO)'.
+sp_defined_paren                = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove space between 'throw' and '(' in 'throw (something)'.
+sp_throw_paren                  = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove space between 'throw' and anything other than '(' as in
+# '@throw [...];'.
+sp_after_throw                  = force   # ignore/add/remove/force/not_defined
+
+# Add or remove space between 'catch' and '(' in 'catch (something) { }'.
+# If set to ignore, sp_before_sparen is used.
+sp_catch_paren                  = force   # ignore/add/remove/force/not_defined
+
+# (OC) Add or remove space between '@catch' and '('
+# in '@catch (something) { }'. If set to ignore, sp_catch_paren is used.
+sp_oc_catch_paren               = ignore   # ignore/add/remove/force/not_defined
+
+# (OC) Add or remove space before Objective-C protocol list
+# as in '@protocol Protocol<here><Protocol_A>' or '@interface MyClass : NSObject<here><MyProtocol>'.
+sp_before_oc_proto_list         = ignore   # ignore/add/remove/force/not_defined
+
+# (OC) Add or remove space between class name and '('
+# in '@interface className(categoryName)<ProtocolName>:BaseClass'
+sp_oc_classname_paren           = ignore   # ignore/add/remove/force/not_defined
+
+# (D) Add or remove space between 'version' and '('
+# in 'version (something) { }'. If set to ignore, sp_before_sparen is used.
+sp_version_paren                = ignore   # ignore/add/remove/force/not_defined
+
+# (D) Add or remove space between 'scope' and '('
+# in 'scope (something) { }'. If set to ignore, sp_before_sparen is used.
+sp_scope_paren                  = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove space between 'super' and '(' in 'super (something)'.
+#
+# Default: remove
+sp_super_paren                  = remove   # ignore/add/remove/force/not_defined
+
+# Add or remove space between 'this' and '(' in 'this (something)'.
+#
+# Default: remove
+sp_this_paren                   = remove   # ignore/add/remove/force/not_defined
+
+# Add or remove space between a macro name and its definition.
+sp_macro                        = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove space between a macro function ')' and its definition.
+sp_macro_func                   = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove space between 'else' and '{' if on the same line.
+sp_else_brace                   = force   # ignore/add/remove/force/not_defined
+
+# Add or remove space between '}' and 'else' if on the same line.
+sp_brace_else                   = force   # ignore/add/remove/force/not_defined
+
+# Add or remove space between '}' and the name of a typedef on the same line.
+sp_brace_typedef                = force   # ignore/add/remove/force/not_defined
+
+# Add or remove space before the '{' of a 'catch' statement, if the '{' and
+# 'catch' are on the same line, as in 'catch (decl) <here> {'.
+sp_catch_brace                  = force   # ignore/add/remove/force/not_defined
+
+# (OC) Add or remove space before the '{' of a '@catch' statement, if the '{'
+# and '@catch' are on the same line, as in '@catch (decl) <here> {'.
+# If set to ignore, sp_catch_brace is used.
+sp_oc_catch_brace               = force   # ignore/add/remove/force/not_defined
+
+# Add or remove space between '}' and 'catch' if on the same line.
+sp_brace_catch                  = force   # ignore/add/remove/force/not_defined
+
+# (OC) Add or remove space between '}' and '@catch' if on the same line.
+# If set to ignore, sp_brace_catch is used.
+sp_oc_brace_catch               = force   # ignore/add/remove/force/not_defined
+
+# Add or remove space between 'finally' and '{' if on the same line.
+sp_finally_brace                = force   # ignore/add/remove/force/not_defined
+
+# Add or remove space between '}' and 'finally' if on the same line.
+sp_brace_finally                = force   # ignore/add/remove/force/not_defined
+
+# Add or remove space between 'try' and '{' if on the same line.
+sp_try_brace                    = force   # ignore/add/remove/force/not_defined
+
+# Add or remove space between get/set and '{' if on the same line.
+sp_getset_brace                 = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove space between a variable and '{' for C++ uniform
+# initialization.
+sp_word_brace_init_lst          = remove   # ignore/add/remove/force/not_defined
+
+# Add or remove space between a variable and '{' for a namespace.
+#
+# Default: add
+sp_word_brace_ns                = add      # ignore/add/remove/force/not_defined
+
+# Add or remove space before the '::' operator.
+sp_before_dc                    = remove   # ignore/add/remove/force/not_defined
+
+# Add or remove space after the '::' operator.
+sp_after_dc                     = remove   # ignore/add/remove/force/not_defined
+
+# (D) Add or remove around the D named array initializer ':' operator.
+sp_d_array_colon                = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove space after the '!' (not) unary operator.
+#
+# Default: remove
+sp_not                          = remove   # ignore/add/remove/force/not_defined
+
+# Add or remove space between two '!' (not) unary operators.
+# If set to ignore, sp_not will be used.
+sp_not_not                      = remove   # ignore/add/remove/force/not_defined
+
+# Add or remove space after the '~' (invert) unary operator.
+#
+# Default: remove
+sp_inv                          = remove   # ignore/add/remove/force/not_defined
+
+# Add or remove space after the '&' (address-of) unary operator. This does not
+# affect the spacing after a '&' that is part of a type.
+#
+# Default: remove
+sp_addr                         = remove   # ignore/add/remove/force/not_defined
+
+# Add or remove space around the '.' or '->' operators.
+#
+# Default: remove
+sp_member                       = remove   # ignore/add/remove/force/not_defined
+
+# Add or remove space after the '*' (dereference) unary operator. This does
+# not affect the spacing after a '*' that is part of a type.
+#
+# Default: remove
+sp_deref                        = remove   # ignore/add/remove/force/not_defined
+
+# Add or remove space after '+' or '-', as in 'x = -5' or 'y = +7'.
+#
+# Default: remove
+sp_sign                         = remove   # ignore/add/remove/force/not_defined
+
+# Add or remove space between '++' and '--' the word to which it is being
+# applied, as in '(--x)' or 'y++;'.
+#
+# Default: remove
+sp_incdec                       = remove   # ignore/add/remove/force/not_defined
+
+# Add or remove space before a backslash-newline at the end of a line.
+#
+# Default: add
+sp_before_nl_cont               = add      # ignore/add/remove/force/not_defined
+
+# (OC) Add or remove space after the scope '+' or '-', as in '-(void) foo;'
+# or '+(int) bar;'.
+sp_after_oc_scope               = ignore   # ignore/add/remove/force/not_defined
+
+# (OC) Add or remove space after the colon in message specs,
+# i.e. '-(int) f:(int) x;' vs. '-(int) f: (int) x;'.
+sp_after_oc_colon               = ignore   # ignore/add/remove/force/not_defined
+
+# (OC) Add or remove space before the colon in message specs,
+# i.e. '-(int) f: (int) x;' vs. '-(int) f : (int) x;'.
+sp_before_oc_colon              = ignore   # ignore/add/remove/force/not_defined
+
+# (OC) Add or remove space after the colon in immutable dictionary expression
+# 'NSDictionary *test = @{@"foo" :@"bar"};'.
+sp_after_oc_dict_colon          = ignore   # ignore/add/remove/force/not_defined
+
+# (OC) Add or remove space before the colon in immutable dictionary expression
+# 'NSDictionary *test = @{@"foo" :@"bar"};'.
+sp_before_oc_dict_colon         = ignore   # ignore/add/remove/force/not_defined
+
+# (OC) Add or remove space after the colon in message specs,
+# i.e. '[object setValue:1];' vs. '[object setValue: 1];'.
+sp_after_send_oc_colon          = ignore   # ignore/add/remove/force/not_defined
+
+# (OC) Add or remove space before the colon in message specs,
+# i.e. '[object setValue:1];' vs. '[object setValue :1];'.
+sp_before_send_oc_colon         = ignore   # ignore/add/remove/force/not_defined
+
+# (OC) Add or remove space after the (type) in message specs,
+# i.e. '-(int)f: (int) x;' vs. '-(int)f: (int)x;'.
+sp_after_oc_type                = ignore   # ignore/add/remove/force/not_defined
+
+# (OC) Add or remove space after the first (type) in message specs,
+# i.e. '-(int) f:(int)x;' vs. '-(int)f:(int)x;'.
+sp_after_oc_return_type         = ignore   # ignore/add/remove/force/not_defined
+
+# (OC) Add or remove space between '@selector' and '(',
+# i.e. '@selector(msgName)' vs. '@selector (msgName)'.
+# Also applies to '@protocol()' constructs.
+sp_after_oc_at_sel              = ignore   # ignore/add/remove/force/not_defined
+
+# (OC) Add or remove space between '@selector(x)' and the following word,
+# i.e. '@selector(foo) a:' vs. '@selector(foo)a:'.
+sp_after_oc_at_sel_parens       = ignore   # ignore/add/remove/force/not_defined
+
+# (OC) Add or remove space inside '@selector' parentheses,
+# i.e. '@selector(foo)' vs. '@selector( foo )'.
+# Also applies to '@protocol()' constructs.
+sp_inside_oc_at_sel_parens      = ignore   # ignore/add/remove/force/not_defined
+
+# (OC) Add or remove space before a block pointer caret,
+# i.e. '^int (int arg){...}' vs. ' ^int (int arg){...}'.
+sp_before_oc_block_caret        = ignore   # ignore/add/remove/force/not_defined
+
+# (OC) Add or remove space after a block pointer caret,
+# i.e. '^int (int arg){...}' vs. '^ int (int arg){...}'.
+sp_after_oc_block_caret         = ignore   # ignore/add/remove/force/not_defined
+
+# (OC) Add or remove space between the receiver and selector in a message,
+# as in '[receiver selector ...]'.
+sp_after_oc_msg_receiver        = ignore   # ignore/add/remove/force/not_defined
+
+# (OC) Add or remove space after '@property'.
+sp_after_oc_property            = ignore   # ignore/add/remove/force/not_defined
+
+# (OC) Add or remove space between '@synchronized' and the open parenthesis,
+# i.e. '@synchronized(foo)' vs. '@synchronized (foo)'.
+sp_after_oc_synchronized        = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove space around the ':' in 'b ? t : f'.
+sp_cond_colon                   = force   # ignore/add/remove/force/not_defined
+
+# Add or remove space before the ':' in 'b ? t : f'.
+#
+# Overrides sp_cond_colon.
+sp_cond_colon_before            = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove space after the ':' in 'b ? t : f'.
+#
+# Overrides sp_cond_colon.
+sp_cond_colon_after             = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove space around the '?' in 'b ? t : f'.
+sp_cond_question                = force   # ignore/add/remove/force/not_defined
+
+# Add or remove space before the '?' in 'b ? t : f'.
+#
+# Overrides sp_cond_question.
+sp_cond_question_before         = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove space after the '?' in 'b ? t : f'.
+#
+# Overrides sp_cond_question.
+sp_cond_question_after          = ignore   # ignore/add/remove/force/not_defined
+
+# In the abbreviated ternary form '(a ?: b)', add or remove space between '?'
+# and ':'.
+#
+# Overrides all other sp_cond_* options.
+sp_cond_ternary_short           = ignore   # ignore/add/remove/force/not_defined
+
+# Fix the spacing between 'case' and the label. Only 'ignore' and 'force' make
+# sense here.
+sp_case_label                   = force   # ignore/add/remove/force/not_defined
+
+# (D) Add or remove space around the D '..' operator.
+sp_range                        = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove space after ':' in a Java/C++11 range-based 'for',
+# as in 'for (Type var : <here> expr)'.
+sp_after_for_colon              = force   # ignore/add/remove/force/not_defined
+
+# Add or remove space before ':' in a Java/C++11 range-based 'for',
+# as in 'for (Type var <here> : expr)'.
+sp_before_for_colon             = force   # ignore/add/remove/force/not_defined
+
+# (D) Add or remove space between 'extern' and '(' as in 'extern <here> (C)'.
+sp_extern_paren                 = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove space after the opening of a C++ comment, as in '// <here> A'.
+sp_cmt_cpp_start                = remove   # ignore/add/remove/force/not_defined
+
+# remove space after the '//' and the pvs command '-V1234',
+# only works with sp_cmt_cpp_start set to add or force.
+sp_cmt_cpp_pvs                  = false    # true/false
+
+# remove space after the '//' and the command 'lint',
+# only works with sp_cmt_cpp_start set to add or force.
+sp_cmt_cpp_lint                 = false    # true/false
+
+# Add or remove space in a C++ region marker comment, as in '// <here> BEGIN'.
+# A region marker is defined as a comment which is not preceded by other text
+# (i.e. the comment is the first non-whitespace on the line), and which starts
+# with either 'BEGIN' or 'END'.
+#
+# Overrides sp_cmt_cpp_start.
+sp_cmt_cpp_region               = ignore   # ignore/add/remove/force/not_defined
+
+# If true, space added with sp_cmt_cpp_start will be added after Doxygen
+# sequences like '///', '///<', '//!' and '//!<'.
+sp_cmt_cpp_doxygen              = false    # true/false
+
+# If true, space added with sp_cmt_cpp_start will be added after Qt translator
+# or meta-data comments like '//:', '//=', and '//~'.
+sp_cmt_cpp_qttr                 = false    # true/false
+
+# Add or remove space between #else or #endif and a trailing comment.
+sp_endif_cmt                    = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove space after 'new', 'delete' and 'delete[]'.
+sp_after_new                    = force   # ignore/add/remove/force/not_defined
+
+# Add or remove space between 'new' and '(' in 'new()'.
+sp_between_new_paren            = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove space between ')' and type in 'new(foo) BAR'.
+sp_after_newop_paren            = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove space inside parentheses of the new operator
+# as in 'new(foo) BAR'.
+sp_inside_newop_paren           = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove space after the open parenthesis of the new operator,
+# as in 'new(foo) BAR'.
+#
+# Overrides sp_inside_newop_paren.
+sp_inside_newop_paren_open      = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove space before the close parenthesis of the new operator,
+# as in 'new(foo) BAR'.
+#
+# Overrides sp_inside_newop_paren.
+sp_inside_newop_paren_close     = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove space before a trailing comment.
+sp_before_tr_cmt                = ignore   # ignore/add/remove/force/not_defined
+
+# Number of spaces before a trailing comment.
+sp_num_before_tr_cmt            = 0        # unsigned number
+
+# Add or remove space before an embedded comment.
+#
+# Default: force
+sp_before_emb_cmt               = force    # ignore/add/remove/force/not_defined
+
+# Number of spaces before an embedded comment.
+#
+# Default: 1
+sp_num_before_emb_cmt           = 1        # unsigned number
+
+# Add or remove space after an embedded comment.
+#
+# Default: force
+sp_after_emb_cmt                = force    # ignore/add/remove/force/not_defined
+
+# Number of spaces after an embedded comment.
+#
+# Default: 1
+sp_num_after_emb_cmt            = 1        # unsigned number
+
+# (Java) Add or remove space between an annotation and the open parenthesis.
+sp_annotation_paren             = ignore   # ignore/add/remove/force/not_defined
+
+# If true, vbrace tokens are dropped to the previous token and skipped.
+sp_skip_vbrace_tokens           = false    # true/false
+
+# Add or remove space after 'noexcept'.
+sp_after_noexcept               = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove space after '_'.
+sp_vala_after_translation       = ignore   # ignore/add/remove/force/not_defined
+
+# If true, a <TAB> is inserted after #define.
+force_tab_after_define          = false    # true/false
+
+#
+# Indenting options
+#
+
+# The number of columns to indent per level. Usually 2, 3, 4, or 8.
+#
+# Default: 8
+indent_columns                  = 4        # unsigned number
+
+# Whether to ignore indent for the first continuation line. Subsequent
+# continuation lines will still be indented to match the first.
+indent_ignore_first_continue    = false    # true/false
+
+# The continuation indent. If non-zero, this overrides the indent of '(', '['
+# and '=' continuation indents. Negative values are OK; negative value is
+# absolute and not increased for each '(' or '[' level.
+#
+# For FreeBSD, this is set to 4.
+# Requires indent_ignore_first_continue=false.
+indent_continue                 = 0        # number
+
+# The continuation indent, only for class header line(s). If non-zero, this
+# overrides the indent of 'class' continuation indents.
+# Requires indent_ignore_first_continue=false.
+indent_continue_class_head      = 0        # unsigned number
+
+# Whether to indent empty lines (i.e. lines which contain only spaces before
+# the newline character).
+indent_single_newlines          = false    # true/false
+
+# The continuation indent for func_*_param if they are true. If non-zero, this
+# overrides the indent.
+indent_param                    = 0        # unsigned number
+
+# How to use tabs when indenting code.
+#
+# 0: Spaces only
+# 1: Indent with tabs to brace level, align with spaces (default)
+# 2: Indent and align with tabs, using spaces when not on a tabstop
+#
+# Default: 1
+indent_with_tabs                = 0        # unsigned number
+
+# Whether to indent comments that are not at a brace level with tabs on a
+# tabstop. Requires indent_with_tabs=2. If false, will use spaces.
+indent_cmt_with_tabs            = false    # true/false
+
+# Whether to indent strings broken by '\' so that they line up.
+indent_align_string             = true    # true/false
+
+# The number of spaces to indent multi-line XML strings.
+# Requires indent_align_string=true.
+indent_xml_string               = 2        # unsigned number
+
+# Spaces to indent '{' from level.
+indent_brace                    = 0        # unsigned number
+
+# Whether braces are indented to the body level.
+indent_braces                   = false    # true/false
+
+# Whether to disable indenting function braces if indent_braces=true.
+indent_braces_no_func           = false    # true/false
+
+# Whether to disable indenting class braces if indent_braces=true.
+indent_braces_no_class          = false    # true/false
+
+# Whether to disable indenting struct braces if indent_braces=true.
+indent_braces_no_struct         = false    # true/false
+
+# Whether to indent based on the size of the brace parent,
+# i.e. 'if' => 3 spaces, 'for' => 4 spaces, etc.
+indent_brace_parent             = false    # true/false
+
+# Whether to indent based on the open parenthesis instead of the open brace
+# in '({\n'.
+indent_paren_open_brace         = false    # true/false
+
+# (C#) Whether to indent the brace of a C# delegate by another level.
+indent_cs_delegate_brace        = false    # true/false
+
+# (C#) Whether to indent a C# delegate (to handle delegates with no brace) by
+# another level.
+indent_cs_delegate_body         = false    # true/false
+
+# Whether to indent the body of a 'namespace'.
+indent_namespace                = false    # true/false
+
+# Whether to indent only the first namespace, and not any nested namespaces.
+# Requires indent_namespace=true.
+indent_namespace_single_indent  = false    # true/false
+
+# The number of spaces to indent a namespace block.
+# If set to zero, use the value indent_columns
+indent_namespace_level          = 0        # unsigned number
+
+# If the body of the namespace is longer than this number, it won't be
+# indented. Requires indent_namespace=true. 0 means no limit.
+indent_namespace_limit          = 0        # unsigned number
+
+# Whether to indent only in inner namespaces (nested in other namespaces).
+# Requires indent_namespace=true.
+indent_namespace_inner_only     = false    # true/false
+
+# Whether the 'extern "C"' body is indented.
+indent_extern                   = false    # true/false
+
+# Whether the 'class' body is indented.
+indent_class                    = true    # true/false
+
+# Whether to ignore indent for the leading base class colon.
+indent_ignore_before_class_colon = false    # true/false
+
+# Additional indent before the leading base class colon.
+# Negative values decrease indent down to the first column.
+# Requires indent_ignore_before_class_colon=false and a newline break before
+# the colon (see pos_class_colon and nl_class_colon)
+indent_before_class_colon       = 0        # number
+
+# Whether to indent the stuff after a leading base class colon.
+indent_class_colon              = false    # true/false
+
+# Whether to indent based on a class colon instead of the stuff after the
+# colon. Requires indent_class_colon=true.
+indent_class_on_colon           = false    # true/false
+
+# Whether to ignore indent for a leading class initializer colon.
+indent_ignore_before_constr_colon = false    # true/false
+
+# Whether to indent the stuff after a leading class initializer colon.
+indent_constr_colon             = true    # true/false
+
+# Virtual indent from the ':' for leading member initializers.
+#
+# Default: 2
+indent_ctor_init_leading        = 2        # unsigned number
+
+# Virtual indent from the ':' for following member initializers.
+#
+# Default: 2
+indent_ctor_init_following      = 2        # unsigned number
+
+# Additional indent for constructor initializer list.
+# Negative values decrease indent down to the first column.
+indent_ctor_init                = 0        # number
+
+# Whether to indent 'if' following 'else' as a new block under the 'else'.
+# If false, 'else\nif' is treated as 'else if' for indenting purposes.
+indent_else_if                  = false    # true/false
+
+# Amount to indent variable declarations after a open brace.
+#
+#  <0: Relative
+# >=0: Absolute
+indent_var_def_blk              = 0        # number
+
+# Whether to indent continued variable declarations instead of aligning.
+indent_var_def_cont             = true    # true/false
+
+# How to indent continued shift expressions ('<<' and '>>').
+# Set align_left_shift=false when using this.
+#  0: Align shift operators instead of indenting them (default)
+#  1: Indent by one level
+# -1: Preserve original indentation
+indent_shift                    = 0        # number
+
+# Whether to force indentation of function definitions to start in column 1.
+indent_func_def_force_col1      = false    # true/false
+
+# Whether to indent continued function call parameters one indent level,
+# rather than aligning parameters under the open parenthesis.
+indent_func_call_param          = true    # true/false
+
+# Whether to indent continued function definition parameters one indent level,
+# rather than aligning parameters under the open parenthesis.
+indent_func_def_param           = false    # true/false
+
+# for function definitions, only if indent_func_def_param is false
+# Allows to align params when appropriate and indent them when not
+# behave as if it was true if paren position is more than this value
+# if paren position is more than the option value
+indent_func_def_param_paren_pos_threshold = 0        # unsigned number
+
+# Whether to indent continued function call prototype one indent level,
+# rather than aligning parameters under the open parenthesis.
+indent_func_proto_param         = true    # true/false
+
+# Whether to indent continued function call declaration one indent level,
+# rather than aligning parameters under the open parenthesis.
+indent_func_class_param         = true    # true/false
+
+# Whether to indent continued class variable constructors one indent level,
+# rather than aligning parameters under the open parenthesis.
+indent_func_ctor_var_param      = true    # true/false
+
+# Whether to indent continued template parameter list one indent level,
+# rather than aligning parameters under the open parenthesis.
+indent_template_param           = true    # true/false
+
+# Double the indent for indent_func_xxx_param options.
+# Use both values of the options indent_columns and indent_param.
+indent_func_param_double        = false    # true/false
+
+# Indentation column for standalone 'const' qualifier on a function
+# prototype.
+indent_func_const               = 0        # unsigned number
+
+# Indentation column for standalone 'throw' qualifier on a function
+# prototype.
+indent_func_throw               = 0        # unsigned number
+
+# How to indent within a macro followed by a brace on the same line
+# This allows reducing the indent in macros that have (for example)
+# `do { ... } while (0)` blocks bracketing them.
+#
+# true:  add an indent for the brace on the same line as the macro
+# false: do not add an indent for the brace on the same line as the macro
+#
+# Default: true
+indent_macro_brace              = true     # true/false
+
+# The number of spaces to indent a continued '->' or '.'.
+# Usually set to 0, 1, or indent_columns.
+indent_member                   = 0        # unsigned number
+
+# Whether lines broken at '.' or '->' should be indented by a single indent.
+# The indent_member option will not be effective if this is set to true.
+indent_member_single            = true    # true/false
+
+# Spaces to indent single line ('//') comments on lines before code.
+indent_single_line_comments_before = 0        # unsigned number
+
+# Spaces to indent single line ('//') comments on lines after code.
+indent_single_line_comments_after = 0        # unsigned number
+
+# When opening a paren for a control statement (if, for, while, etc), increase
+# the indent level by this value. Negative values decrease the indent level.
+indent_sparen_extra             = 0        # number
+
+# Whether to indent trailing single line ('//') comments relative to the code
+# instead of trying to keep the same absolute column.
+indent_relative_single_line_comments = false    # true/false
+
+# Spaces to indent 'case' from 'switch'. Usually 0 or indent_columns.
+# It might be wise to choose the same value for the option indent_case_brace.
+indent_switch_case              = indent_columns        # unsigned number
+
+# Spaces to indent the body of a 'switch' before any 'case'.
+# Usually the same as indent_columns or indent_switch_case.
+indent_switch_body              = indent_columns        # unsigned number
+
+# Whether to ignore indent for '{' following 'case'.
+indent_ignore_case_brace        = false    # true/false
+
+# Spaces to indent '{' from 'case'. By default, the brace will appear under
+# the 'c' in case. Usually set to 0 or indent_columns. Negative values are OK.
+# It might be wise to choose the same value for the option indent_switch_case.
+indent_case_brace               = indent_columns        # number
+
+# indent 'break' with 'case' from 'switch'.
+indent_switch_break_with_case   = false    # true/false
+
+# Whether to indent preprocessor statements inside of switch statements.
+#
+# Default: true
+indent_switch_pp                = false     # true/false
+
+# Spaces to shift the 'case' line, without affecting any other lines.
+# Usually 0.
+indent_case_shift               = 0        # unsigned number
+
+# Whether to align comments before 'case' with the 'case'.
+#
+# Default: true
+indent_case_comment             = true     # true/false
+
+# Whether to indent comments not found in first column.
+#
+# Default: true
+indent_comment                  = true     # true/false
+
+# Whether to indent comments found in first column.
+indent_col1_comment             = false    # true/false
+
+# Whether to indent multi string literal in first column.
+indent_col1_multi_string_literal = true    # true/false
+
+# Align comments on adjacent lines that are this many columns apart or less.
+#
+# Default: 3
+indent_comment_align_thresh     = 3        # unsigned number
+
+# Whether to ignore indent for goto labels.
+indent_ignore_label             = false    # true/false
+
+# How to indent goto labels. Requires indent_ignore_label=false.
+#
+#  >0: Absolute column where 1 is the leftmost column
+# <=0: Subtract from brace indent
+#
+# Default: 1
+indent_label                    = 1        # number
+
+# How to indent access specifiers that are followed by a
+# colon.
+#
+#  >0: Absolute column where 1 is the leftmost column
+# <=0: Subtract from brace indent
+#
+# Default: 1
+indent_access_spec              = 1        # number
+
+# Whether to indent the code after an access specifier by one level.
+# If true, this option forces 'indent_access_spec=0'.
+indent_access_spec_body         = false    # true/false
+
+# If an open parenthesis is followed by a newline, whether to indent the next
+# line so that it lines up after the open parenthesis (not recommended).
+indent_paren_nl                 = false    # true/false
+
+# How to indent a close parenthesis after a newline.
+#
+#  0: Indent to body level (default)
+#  1: Align under the open parenthesis
+#  2: Indent to the brace level
+# -1: Preserve original indentation
+indent_paren_close              = 2        # number
+
+# Whether to indent the open parenthesis of a function definition,
+# if the parenthesis is on its own line.
+indent_paren_after_func_def     = false    # true/false
+
+# Whether to indent the open parenthesis of a function declaration,
+# if the parenthesis is on its own line.
+indent_paren_after_func_decl    = false    # true/false
+
+# Whether to indent the open parenthesis of a function call,
+# if the parenthesis is on its own line.
+indent_paren_after_func_call    = false    # true/false
+
+# How to indent a comma when inside braces.
+#  0: Indent by one level (default)
+#  1: Align under the open brace
+# -1: Preserve original indentation
+indent_comma_brace              = 0        # number
+
+# How to indent a comma when inside parentheses.
+#  0: Indent by one level (default)
+#  1: Align under the open parenthesis
+# -1: Preserve original indentation
+indent_comma_paren              = 0        # number
+
+# How to indent a Boolean operator when inside parentheses.
+#  0: Indent by one level (default)
+#  1: Align under the open parenthesis
+# -1: Preserve original indentation
+indent_bool_paren               = 0        # number
+
+# Whether to ignore the indentation of a Boolean operator when outside
+# parentheses.
+indent_ignore_bool              = false    # true/false
+
+# Whether to ignore the indentation of an arithmetic operator.
+indent_ignore_arith             = false    # true/false
+
+# Whether to indent a semicolon when inside a for parenthesis.
+# If true, aligns under the open for parenthesis.
+indent_semicolon_for_paren      = false    # true/false
+
+# Whether to ignore the indentation of a semicolon outside of a 'for'
+# statement.
+indent_ignore_semicolon         = false    # true/false
+
+# Whether to align the first expression to following ones
+# if indent_bool_paren=1.
+indent_first_bool_expr          = false    # true/false
+
+# Whether to align the first expression to following ones
+# if indent_semicolon_for_paren=true.
+indent_first_for_expr           = false    # true/false
+
+# If an open square is followed by a newline, whether to indent the next line
+# so that it lines up after the open square (not recommended).
+indent_square_nl                = false    # true/false
+
+# (ESQL/C) Whether to preserve the relative indent of 'EXEC SQL' bodies.
+indent_preserve_sql             = false    # true/false
+
+# Whether to ignore the indentation of an assignment operator.
+indent_ignore_assign            = false    # true/false
+
+# Whether to align continued statements at the '='. If false or if the '=' is
+# followed by a newline, the next line is indent one tab.
+#
+# Default: true
+indent_align_assign             = true     # true/false
+
+# If true, the indentation of the chunks after a '=' sequence will be set at
+# LHS token indentation column before '='.
+indent_off_after_assign         = false    # true/false
+
+# Whether to align continued statements at the '('. If false or the '(' is
+# followed by a newline, the next line indent is one tab.
+#
+# Default: true
+indent_align_paren              = true     # true/false
+
+# (OC) Whether to indent Objective-C code inside message selectors.
+indent_oc_inside_msg_sel        = false    # true/false
+
+# (OC) Whether to indent Objective-C blocks at brace level instead of usual
+# rules.
+indent_oc_block                 = false    # true/false
+
+# (OC) Indent for Objective-C blocks in a message relative to the parameter
+# name.
+#
+# =0: Use indent_oc_block rules
+# >0: Use specified number of spaces to indent
+indent_oc_block_msg             = 0        # unsigned number
+
+# (OC) Minimum indent for subsequent parameters
+indent_oc_msg_colon             = 0        # unsigned number
+
+# (OC) Whether to prioritize aligning with initial colon (and stripping spaces
+# from lines, if necessary).
+#
+# Default: true
+indent_oc_msg_prioritize_first_colon = true     # true/false
+
+# (OC) Whether to indent blocks the way that Xcode does by default
+# (from the keyword if the parameter is on its own line; otherwise, from the
+# previous indentation level). Requires indent_oc_block_msg=true.
+indent_oc_block_msg_xcode_style = false    # true/false
+
+# (OC) Whether to indent blocks from where the brace is, relative to a
+# message keyword. Requires indent_oc_block_msg=true.
+indent_oc_block_msg_from_keyword = false    # true/false
+
+# (OC) Whether to indent blocks from where the brace is, relative to a message
+# colon. Requires indent_oc_block_msg=true.
+indent_oc_block_msg_from_colon  = false    # true/false
+
+# (OC) Whether to indent blocks from where the block caret is.
+# Requires indent_oc_block_msg=true.
+indent_oc_block_msg_from_caret  = false    # true/false
+
+# (OC) Whether to indent blocks from where the brace caret is.
+# Requires indent_oc_block_msg=true.
+indent_oc_block_msg_from_brace  = false    # true/false
+
+# When indenting after virtual brace open and newline add further spaces to
+# reach this minimum indent.
+indent_min_vbrace_open          = 0        # unsigned number
+
+# Whether to add further spaces after regular indent to reach next tabstop
+# when indenting after virtual brace open and newline.
+indent_vbrace_open_on_tabstop   = false    # true/false
+
+# How to indent after a brace followed by another token (not a newline).
+# true:  indent all contained lines to match the token
+# false: indent all contained lines to match the brace
+#
+# Default: true
+indent_token_after_brace        = true     # true/false
+
+# Whether to indent the body of a C++11 lambda.
+indent_cpp_lambda_body          = true    # true/false
+
+# How to indent compound literals that are being returned.
+# true: add both the indent from return & the compound literal open brace
+#       (i.e. 2 indent levels)
+# false: only indent 1 level, don't add the indent for the open brace, only
+#        add the indent for the return.
+#
+# Default: true
+indent_compound_literal_return  = true     # true/false
+
+# (C#) Whether to indent a 'using' block if no braces are used.
+#
+# Default: true
+indent_using_block              = true     # true/false
+
+# How to indent the continuation of ternary operator.
+#
+# 0: Off (default)
+# 1: When the `if_false` is a continuation, indent it under the `if_true` branch
+# 2: When the `:` is a continuation, indent it under `?`
+indent_ternary_operator         = 1        # unsigned number
+
+# Whether to indent the statements inside ternary operator.
+indent_inside_ternary_operator  = false    # true/false
+
+# If true, the indentation of the chunks after a `return` sequence will be set at return indentation column.
+indent_off_after_return         = false    # true/false
+
+# If true, the indentation of the chunks after a `return new` sequence will be set at return indentation column.
+indent_off_after_return_new     = false    # true/false
+
+# If true, the tokens after return are indented with regular single indentation. By default (false) the indentation is after the return token.
+indent_single_after_return      = false    # true/false
+
+# Whether to ignore indent and alignment for 'asm' blocks (i.e. assume they
+# have their own indentation).
+indent_ignore_asm_block         = false    # true/false
+
+# Don't indent the close parenthesis of a function definition,
+# if the parenthesis is on its own line.
+donot_indent_func_def_close_paren = false    # true/false
+
+#
+# Newline adding and removing options
+#
+
+# Whether to collapse empty blocks between '{' and '}' except for functions.
+# Use nl_collapse_empty_body_functions to specify how empty function braces
+# should be formatted.
+nl_collapse_empty_body          = true    # true/false
+
+# Whether to collapse empty blocks between '{' and '}' for functions only.
+# If true, overrides nl_inside_empty_func.
+nl_collapse_empty_body_functions = true    # true/false
+
+# Don't split one-line braced assignments, as in 'foo_t f = { 1, 2 };'.
+nl_assign_leave_one_liners      = true    # true/false
+
+# Don't split one-line braced statements inside a 'class xx { }' body.
+nl_class_leave_one_liners       = false    # true/false
+
+# Don't split one-line enums, as in 'enum foo { BAR = 15 };'
+nl_enum_leave_one_liners        = false    # true/false
+
+# Don't split one-line get or set functions.
+nl_getset_leave_one_liners      = false    # true/false
+
+# (C#) Don't split one-line property get or set functions.
+nl_cs_property_leave_one_liners = false    # true/false
+
+# Don't split one-line function definitions, as in 'int foo() { return 0; }'.
+# might modify nl_func_type_name
+nl_func_leave_one_liners        = true    # true/false
+
+# Don't split one-line C++11 lambdas, as in '[]() { return 0; }'.
+nl_cpp_lambda_leave_one_liners  = true    # true/false
+
+# Don't split one-line if/else statements, as in 'if(...) b++;'.
+nl_if_leave_one_liners          = false    # true/false
+
+# Don't split one-line while statements, as in 'while(...) b++;'.
+nl_while_leave_one_liners       = false    # true/false
+
+# Don't split one-line do statements, as in 'do { b++; } while(...);'.
+nl_do_leave_one_liners          = false    # true/false
+
+# Don't split one-line for statements, as in 'for(...) b++;'.
+nl_for_leave_one_liners         = false    # true/false
+
+# (OC) Don't split one-line Objective-C messages.
+nl_oc_msg_leave_one_liner       = false    # true/false
+
+# (OC) Add or remove newline between method declaration and '{'.
+nl_oc_mdef_brace                = ignore   # ignore/add/remove/force/not_defined
+
+# (OC) Add or remove newline between Objective-C block signature and '{'.
+nl_oc_block_brace               = ignore   # ignore/add/remove/force/not_defined
+
+# (OC) Add or remove blank line before '@interface' statement.
+nl_oc_before_interface          = ignore   # ignore/add/remove/force/not_defined
+
+# (OC) Add or remove blank line before '@implementation' statement.
+nl_oc_before_implementation     = ignore   # ignore/add/remove/force/not_defined
+
+# (OC) Add or remove blank line before '@end' statement.
+nl_oc_before_end                = ignore   # ignore/add/remove/force/not_defined
+
+# (OC) Add or remove newline between '@interface' and '{'.
+nl_oc_interface_brace           = ignore   # ignore/add/remove/force/not_defined
+
+# (OC) Add or remove newline between '@implementation' and '{'.
+nl_oc_implementation_brace      = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove newlines at the start of the file.
+nl_start_of_file                = remove   # ignore/add/remove/force/not_defined
+
+# The minimum number of newlines at the start of the file (only used if
+# nl_start_of_file is 'add' or 'force').
+nl_start_of_file_min            = 0        # unsigned number
+
+# Add or remove newline at the end of the file.
+nl_end_of_file                  = remove   # ignore/add/remove/force/not_defined
+
+# The minimum number of newlines at the end of the file (only used if
+# nl_end_of_file is 'add' or 'force').
+nl_end_of_file_min              = 0        # unsigned number
+
+# Add or remove newline between '=' and '{'.
+nl_assign_brace                 = remove   # ignore/add/remove/force/not_defined
+
+# (D) Add or remove newline between '=' and '['.
+nl_assign_square                = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove newline between '[]' and '{'.
+nl_tsquare_brace                = remove   # ignore/add/remove/force/not_defined
+
+# (D) Add or remove newline after '= ['. Will also affect the newline before
+# the ']'.
+nl_after_square_assign          = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove newline between a function call's ')' and '{', as in
+# 'list_for_each(item, &list) { }'.
+nl_fcall_brace                  = force   # ignore/add/remove/force/not_defined
+
+# Add or remove newline between 'enum' and '{'.
+nl_enum_brace                   = remove   # ignore/add/remove/force/not_defined
+
+# Add or remove newline between 'enum' and 'class'.
+nl_enum_class                   = remove   # ignore/add/remove/force/not_defined
+
+# Add or remove newline between 'enum class' and the identifier.
+nl_enum_class_identifier        = remove   # ignore/add/remove/force/not_defined
+
+# Add or remove newline between 'enum class' type and ':'.
+nl_enum_identifier_colon        = remove   # ignore/add/remove/force/not_defined
+
+# Add or remove newline between 'enum class identifier :' and type.
+nl_enum_colon_type              = remove   # ignore/add/remove/force/not_defined
+
+# Add or remove newline between 'struct and '{'.
+nl_struct_brace                 = remove   # ignore/add/remove/force/not_defined
+
+# Add or remove newline between 'union' and '{'.
+nl_union_brace                  = remove   # ignore/add/remove/force/not_defined
+
+# Add or remove newline between 'if' and '{'.
+nl_if_brace                     = remove   # ignore/add/remove/force/not_defined
+
+# Add or remove newline between '}' and 'else'.
+nl_brace_else                   = remove   # ignore/add/remove/force/not_defined
+
+# Add or remove newline between 'else if' and '{'. If set to ignore,
+# nl_if_brace is used instead.
+nl_elseif_brace                 = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove newline between 'else' and '{'.
+nl_else_brace                   = remove   # ignore/add/remove/force/not_defined
+
+# Add or remove newline between 'else' and 'if'.
+nl_else_if                      = remove   # ignore/add/remove/force/not_defined
+
+# Add or remove newline before 'if'/'else if' closing parenthesis.
+nl_before_if_closing_paren      = remove   # ignore/add/remove/force/not_defined
+
+# Add or remove newline between '}' and 'finally'.
+nl_brace_finally                = remove   # ignore/add/remove/force/not_defined
+
+# Add or remove newline between 'finally' and '{'.
+nl_finally_brace                = remove   # ignore/add/remove/force/not_defined
+
+# Add or remove newline between 'try' and '{'.
+nl_try_brace                    = remove   # ignore/add/remove/force/not_defined
+
+# Add or remove newline between get/set and '{'.
+nl_getset_brace                 = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove newline between 'for' and '{'.
+nl_for_brace                    = remove   # ignore/add/remove/force/not_defined
+
+# Add or remove newline before the '{' of a 'catch' statement, as in
+# 'catch (decl) <here> {'.
+nl_catch_brace                  = remove   # ignore/add/remove/force/not_defined
+
+# (OC) Add or remove newline before the '{' of a '@catch' statement, as in
+# '@catch (decl) <here> {'. If set to ignore, nl_catch_brace is used.
+nl_oc_catch_brace               = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove newline between '}' and 'catch'.
+nl_brace_catch                  = remove   # ignore/add/remove/force/not_defined
+
+# (OC) Add or remove newline between '}' and '@catch'. If set to ignore,
+# nl_brace_catch is used.
+nl_oc_brace_catch               = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove newline between '}' and ']'.
+nl_brace_square                 = remove   # ignore/add/remove/force/not_defined
+
+# Add or remove newline between '}' and ')' in a function invocation.
+nl_brace_fparen                 = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove newline between 'while' and '{'.
+nl_while_brace                  = remove   # ignore/add/remove/force/not_defined
+
+# (D) Add or remove newline between 'scope (x)' and '{'.
+nl_scope_brace                  = ignore   # ignore/add/remove/force/not_defined
+
+# (D) Add or remove newline between 'unittest' and '{'.
+nl_unittest_brace               = ignore   # ignore/add/remove/force/not_defined
+
+# (D) Add or remove newline between 'version (x)' and '{'.
+nl_version_brace                = ignore   # ignore/add/remove/force/not_defined
+
+# (C#) Add or remove newline between 'using' and '{'.
+nl_using_brace                  = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove newline between two open or close braces. Due to general
+# newline/brace handling, REMOVE may not work.
+nl_brace_brace                  = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove newline between 'do' and '{'.
+nl_do_brace                     = remove   # ignore/add/remove/force/not_defined
+
+# Add or remove newline between '}' and 'while' of 'do' statement.
+nl_brace_while                  = remove   # ignore/add/remove/force/not_defined
+
+# Add or remove newline between 'switch' and '{'.
+nl_switch_brace                 = remove   # ignore/add/remove/force/not_defined
+
+# Add or remove newline between 'synchronized' and '{'.
+nl_synchronized_brace           = remove   # ignore/add/remove/force/not_defined
+
+# Add a newline between ')' and '{' if the ')' is on a different line than the
+# if/for/etc.
+#
+# Overrides nl_for_brace, nl_if_brace, nl_switch_brace, nl_while_switch and
+# nl_catch_brace.
+nl_multi_line_cond              = false    # true/false
+
+# Add a newline after '(' if an if/for/while/switch condition spans multiple
+# lines
+nl_multi_line_sparen_open       = force   # ignore/add/remove/force/not_defined
+
+# Add a newline before ')' if an if/for/while/switch condition spans multiple
+# lines. Overrides nl_before_if_closing_paren if both are specified.
+nl_multi_line_sparen_close      = force   # ignore/add/remove/force/not_defined
+
+# Force a newline in a define after the macro name for multi-line defines.
+nl_multi_line_define            = false    # true/false
+
+# Whether to add a newline before 'case', and a blank line before a 'case'
+# statement that follows a ';' or '}'.
+nl_before_case                  = false    # true/false
+
+# Whether to add a newline after a 'case' statement.
+nl_after_case                   = true    # true/false
+
+# Add or remove newline between a case ':' and '{'.
+#
+# Overrides nl_after_case.
+nl_case_colon_brace             = remove   # ignore/add/remove/force/not_defined
+
+# Add or remove newline between ')' and 'throw'.
+nl_before_throw                 = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove newline between 'namespace' and '{'.
+nl_namespace_brace              = remove   # ignore/add/remove/force/not_defined
+
+# Add or remove newline after 'template<...>' of a template class.
+nl_template_class               = force   # ignore/add/remove/force/not_defined
+
+# Add or remove newline after 'template<...>' of a template class declaration.
+#
+# Overrides nl_template_class.
+nl_template_class_decl          = force   # ignore/add/remove/force/not_defined
+
+# Add or remove newline after 'template<>' of a specialized class declaration.
+#
+# Overrides nl_template_class_decl.
+nl_template_class_decl_special  = force   # ignore/add/remove/force/not_defined
+
+# Add or remove newline after 'template<...>' of a template class definition.
+#
+# Overrides nl_template_class.
+nl_template_class_def           = force   # ignore/add/remove/force/not_defined
+
+# Add or remove newline after 'template<>' of a specialized class definition.
+#
+# Overrides nl_template_class_def.
+nl_template_class_def_special   = force   # ignore/add/remove/force/not_defined
+
+# Add or remove newline after 'template<...>' of a template function.
+nl_template_func                = force   # ignore/add/remove/force/not_defined
+
+# Add or remove newline after 'template<...>' of a template function
+# declaration.
+#
+# Overrides nl_template_func.
+nl_template_func_decl           = force   # ignore/add/remove/force/not_defined
+
+# Add or remove newline after 'template<>' of a specialized function
+# declaration.
+#
+# Overrides nl_template_func_decl.
+nl_template_func_decl_special   = force   # ignore/add/remove/force/not_defined
+
+# Add or remove newline after 'template<...>' of a template function
+# definition.
+#
+# Overrides nl_template_func.
+nl_template_func_def            = force   # ignore/add/remove/force/not_defined
+
+# Add or remove newline after 'template<>' of a specialized function
+# definition.
+#
+# Overrides nl_template_func_def.
+nl_template_func_def_special    = force   # ignore/add/remove/force/not_defined
+
+# Add or remove newline after 'template<...>' of a template variable.
+nl_template_var                 = force   # ignore/add/remove/force/not_defined
+
+# Add or remove newline between 'template<...>' and 'using' of a templated
+# type alias.
+nl_template_using               = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove newline between 'class' and '{'.
+nl_class_brace                  = remove   # ignore/add/remove/force/not_defined
+
+# Add or remove newline before or after (depending on pos_class_comma,
+# may not be IGNORE) each',' in the base class list.
+nl_class_init_args              = force   # ignore/add/remove/force/not_defined
+
+# Add or remove newline after each ',' in the constructor member
+# initialization. Related to nl_constr_colon, pos_constr_colon and
+# pos_constr_comma.
+nl_constr_init_args             = force   # ignore/add/remove/force/not_defined
+
+# Add or remove newline before first element, after comma, and after last
+# element, in 'enum'.
+nl_enum_own_lines               = add   # ignore/add/remove/force/not_defined
+
+# Add or remove newline between return type and function name in a function
+# definition.
+# might be modified by nl_func_leave_one_liners
+nl_func_type_name               = remove   # ignore/add/remove/force/not_defined
+
+# Add or remove newline between return type and function name inside a class
+# definition. If set to ignore, nl_func_type_name or nl_func_proto_type_name
+# is used instead.
+nl_func_type_name_class         = remove   # ignore/add/remove/force/not_defined
+
+# Add or remove newline between class specification and '::'
+# in 'void A::f() { }'. Only appears in separate member implementation (does
+# not appear with in-line implementation).
+nl_func_class_scope             = remove   # ignore/add/remove/force/not_defined
+
+# Add or remove newline between function scope and name, as in
+# 'void A :: <here> f() { }'.
+nl_func_scope_name              = remove   # ignore/add/remove/force/not_defined
+
+# Add or remove newline between return type and function name in a prototype.
+nl_func_proto_type_name         = remove   # ignore/add/remove/force/not_defined
+
+# Add or remove newline between a function name and the opening '(' in the
+# declaration.
+nl_func_paren                   = remove   # ignore/add/remove/force/not_defined
+
+# Overrides nl_func_paren for functions with no parameters.
+nl_func_paren_empty             = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove newline between a function name and the opening '(' in the
+# definition.
+nl_func_def_paren               = remove   # ignore/add/remove/force/not_defined
+
+# Overrides nl_func_def_paren for functions with no parameters.
+nl_func_def_paren_empty         = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove newline between a function name and the opening '(' in the
+# call.
+nl_func_call_paren              = remove   # ignore/add/remove/force/not_defined
+
+# Overrides nl_func_call_paren for functions with no parameters.
+nl_func_call_paren_empty        = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove newline after '(' in a function declaration.
+nl_func_decl_start              = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove newline after '(' in a function definition.
+nl_func_def_start               = ignore   # ignore/add/remove/force/not_defined
+
+# Overrides nl_func_decl_start when there is only one parameter.
+nl_func_decl_start_single       = ignore   # ignore/add/remove/force/not_defined
+
+# Overrides nl_func_def_start when there is only one parameter.
+nl_func_def_start_single        = ignore   # ignore/add/remove/force/not_defined
+
+# Whether to add a newline after '(' in a function declaration if '(' and ')'
+# are in different lines. If false, nl_func_decl_start is used instead.
+nl_func_decl_start_multi_line   = true    # true/false
+
+# Whether to add a newline after '(' in a function definition if '(' and ')'
+# are in different lines. If false, nl_func_def_start is used instead.
+nl_func_def_start_multi_line    = true    # true/false
+
+# Add or remove newline after each ',' in a function declaration.
+nl_func_decl_args               = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove newline after each ',' in a function definition.
+nl_func_def_args                = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove newline after each ',' in a function call.
+nl_func_call_args               = ignore   # ignore/add/remove/force/not_defined
+
+# Whether to add a newline after each ',' in a function declaration if '('
+# and ')' are in different lines. If false, nl_func_decl_args is used instead.
+nl_func_decl_args_multi_line    = true    # true/false
+
+# Whether to add a newline after each ',' in a function definition if '('
+# and ')' are in different lines. If false, nl_func_def_args is used instead.
+nl_func_def_args_multi_line     = true    # true/false
+
+# Add or remove newline before the ')' in a function declaration.
+nl_func_decl_end                = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove newline before the ')' in a function definition.
+nl_func_def_end                 = ignore   # ignore/add/remove/force/not_defined
+
+# Overrides nl_func_decl_end when there is only one parameter.
+nl_func_decl_end_single         = ignore   # ignore/add/remove/force/not_defined
+
+# Overrides nl_func_def_end when there is only one parameter.
+nl_func_def_end_single          = ignore   # ignore/add/remove/force/not_defined
+
+# Whether to add a newline before ')' in a function declaration if '(' and ')'
+# are in different lines. If false, nl_func_decl_end is used instead.
+nl_func_decl_end_multi_line     = true    # true/false
+
+# Whether to add a newline before ')' in a function definition if '(' and ')'
+# are in different lines. If false, nl_func_def_end is used instead.
+nl_func_def_end_multi_line      = true    # true/false
+
+# Add or remove newline between '()' in a function declaration.
+nl_func_decl_empty              = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove newline between '()' in a function definition.
+nl_func_def_empty               = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove newline between '()' in a function call.
+nl_func_call_empty              = ignore   # ignore/add/remove/force/not_defined
+
+# Whether to add a newline after '(' in a function call,
+# has preference over nl_func_call_start_multi_line.
+nl_func_call_start              = ignore   # ignore/add/remove/force/not_defined
+
+# Whether to add a newline before ')' in a function call.
+nl_func_call_end                = ignore   # ignore/add/remove/force/not_defined
+
+# Whether to add a newline after '(' in a function call if '(' and ')' are in
+# different lines.
+nl_func_call_start_multi_line   = true    # true/false
+
+# Whether to add a newline after each ',' in a function call if '(' and ')'
+# are in different lines.
+nl_func_call_args_multi_line    = true    # true/false
+
+# Whether to add a newline before ')' in a function call if '(' and ')' are in
+# different lines.
+nl_func_call_end_multi_line     = true    # true/false
+
+# Whether to respect nl_func_call_XXX option in case of closure args.
+nl_func_call_args_multi_line_ignore_closures = false    # true/false
+
+# Whether to add a newline after '<' of a template parameter list.
+nl_template_start               = false    # true/false
+
+# Whether to add a newline after each ',' in a template parameter list.
+nl_template_args                = false    # true/false
+
+# Whether to add a newline before '>' of a template parameter list.
+nl_template_end                 = false    # true/false
+
+# (OC) Whether to put each Objective-C message parameter on a separate line.
+# See nl_oc_msg_leave_one_liner.
+nl_oc_msg_args                  = false    # true/false
+
+# (OC) Minimum number of Objective-C message parameters before applying nl_oc_msg_args.
+nl_oc_msg_args_min_params       = 0        # unsigned number
+
+# (OC) Max code width of Objective-C message before applying nl_oc_msg_args.
+nl_oc_msg_args_max_code_width   = 0        # unsigned number
+
+# Add or remove newline between function signature and '{'.
+nl_fdef_brace                   = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove newline between function signature and '{',
+# if signature ends with ')'. Overrides nl_fdef_brace.
+nl_fdef_brace_cond              = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove newline before '{' opening brace
+nl_before_opening_brace_func_class_def = force   # ignore/add/remove/force/not_defined
+
+# Add or remove newline between C++11 lambda signature and '{'.
+nl_cpp_ldef_brace               = remove   # ignore/add/remove/force/not_defined
+
+# Add or remove newline between 'return' and the return expression.
+nl_return_expr                  = remove   # ignore/add/remove/force/not_defined
+
+# Add or remove newline between 'throw' and the throw expression.
+nl_throw_expr                   = remove   # ignore/add/remove/force/not_defined
+
+# Whether to add a newline after semicolons, except in 'for' statements.
+nl_after_semicolon              = true    # true/false
+
+# (Java) Add or remove newline between the ')' and '{{' of the double brace
+# initializer.
+nl_paren_dbrace_open            = ignore   # ignore/add/remove/force/not_defined
+
+# Whether to add a newline after the type in an unnamed temporary
+# direct-list-initialization, better:
+# before a direct-list-initialization.
+nl_type_brace_init_lst          = ignore   # ignore/add/remove/force/not_defined
+
+# Whether to add a newline after the open brace in an unnamed temporary
+# direct-list-initialization.
+nl_type_brace_init_lst_open     = ignore   # ignore/add/remove/force/not_defined
+
+# Whether to add a newline before the close brace in an unnamed temporary
+# direct-list-initialization.
+nl_type_brace_init_lst_close    = ignore   # ignore/add/remove/force/not_defined
+
+# Whether to add a newline before '{'.
+nl_before_brace_open            = false    # true/false
+
+# Whether to add a newline after '{'.
+nl_after_brace_open             = false    # true/false
+
+# Whether to add a newline between the open brace and a trailing single-line
+# comment. Requires nl_after_brace_open=true.
+nl_after_brace_open_cmt         = false    # true/false
+
+# Whether to add a newline after a virtual brace open with a non-empty body.
+# These occur in un-braced if/while/do/for statement bodies.
+nl_after_vbrace_open            = false    # true/false
+
+# Whether to add a newline after a virtual brace open with an empty body.
+# These occur in un-braced if/while/do/for statement bodies.
+nl_after_vbrace_open_empty      = false    # true/false
+
+# Whether to add a newline after '}'. Does not apply if followed by a
+# necessary ';'.
+nl_after_brace_close            = true    # true/false
+
+# Whether to add a newline after a virtual brace close,
+# as in 'if (foo) a++; <here> return;'.
+nl_after_vbrace_close           = true    # true/false
+
+# Add or remove newline between the close brace and identifier,
+# as in 'struct { int a; } <here> b;'. Affects enumerations, unions and
+# structures. If set to ignore, uses nl_after_brace_close.
+nl_brace_struct_var             = ignore   # ignore/add/remove/force/not_defined
+
+# Whether to alter newlines in '#define' macros.
+nl_define_macro                 = false    # true/false
+
+# Whether to alter newlines between consecutive parenthesis closes. The number
+# of closing parentheses in a line will depend on respective open parenthesis
+# lines.
+nl_squeeze_paren_close          = false    # true/false
+
+# Whether to remove blanks after '#ifxx' and '#elxx', or before '#elxx' and
+# '#endif'. Does not affect top-level #ifdefs.
+nl_squeeze_ifdef                = false    # true/false
+
+# Makes the nl_squeeze_ifdef option affect the top-level #ifdefs as well.
+nl_squeeze_ifdef_top_level      = false    # true/false
+
+# Add or remove blank line before 'if'.
+nl_before_if                    = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove blank line after 'if' statement. Add/Force work only if the
+# next token is not a closing brace.
+nl_after_if                     = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove blank line before 'for'.
+nl_before_for                   = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove blank line after 'for' statement.
+nl_after_for                    = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove blank line before 'while'.
+nl_before_while                 = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove blank line after 'while' statement.
+nl_after_while                  = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove blank line before 'switch'.
+nl_before_switch                = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove blank line after 'switch' statement.
+nl_after_switch                 = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove blank line before 'synchronized'.
+nl_before_synchronized          = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove blank line after 'synchronized' statement.
+nl_after_synchronized           = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove blank line before 'do'.
+nl_before_do                    = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove blank line after 'do/while' statement.
+nl_after_do                     = ignore   # ignore/add/remove/force/not_defined
+
+# Ignore nl_before_{if,for,switch,do,synchronized} if the control
+# statement is immediately after a case statement.
+# if nl_before_{if,for,switch,do} is set to remove, this option
+# does nothing.
+nl_before_ignore_after_case     = false    # true/false
+
+# Whether to put a blank line before 'return' statements, unless after an open
+# brace.
+nl_before_return                = false    # true/false
+
+# Whether to put a blank line after 'return' statements, unless followed by a
+# close brace.
+nl_after_return                 = false    # true/false
+
+# Whether to put a blank line before a member '.' or '->' operators.
+nl_before_member                = ignore   # ignore/add/remove/force/not_defined
+
+# (Java) Whether to put a blank line after a member '.' or '->' operators.
+nl_after_member                 = ignore   # ignore/add/remove/force/not_defined
+
+# Whether to double-space commented-entries in 'struct'/'union'/'enum'.
+nl_ds_struct_enum_cmt           = false    # true/false
+
+# Whether to force a newline before '}' of a 'struct'/'union'/'enum'.
+# (Lower priority than eat_blanks_before_close_brace.)
+nl_ds_struct_enum_close_brace   = false    # true/false
+
+# Add or remove newline before or after (depending on pos_class_colon) a class
+# colon, as in 'class Foo <here> : <or here> public Bar'.
+nl_class_colon                  = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove newline around a class constructor colon. The exact position
+# depends on nl_constr_init_args, pos_constr_colon and pos_constr_comma.
+nl_constr_colon                 = force   # ignore/add/remove/force/not_defined
+
+# Whether to collapse a two-line namespace, like 'namespace foo\n{ decl; }'
+# into a single line. If true, prevents other brace newline rules from turning
+# such code into four lines. If true, it also preserves one-liner namespaces.
+nl_namespace_two_to_one_liner   = false    # true/false
+
+# Whether to remove a newline in simple unbraced if statements, turning them
+# into one-liners, as in 'if(b)\n i++;' => 'if(b) i++;'.
+nl_create_if_one_liner          = false    # true/false
+
+# Whether to remove a newline in simple unbraced for statements, turning them
+# into one-liners, as in 'for (...)\n stmt;' => 'for (...) stmt;'.
+nl_create_for_one_liner         = false    # true/false
+
+# Whether to remove a newline in simple unbraced while statements, turning
+# them into one-liners, as in 'while (expr)\n stmt;' => 'while (expr) stmt;'.
+nl_create_while_one_liner       = false    # true/false
+
+# Whether to collapse a function definition whose body (not counting braces)
+# is only one line so that the entire definition (prototype, braces, body) is
+# a single line.
+nl_create_func_def_one_liner    = false    # true/false
+
+# Whether to split one-line simple list definitions into three lines by
+# adding newlines, as in 'int a[12] = { <here> 0 <here> };'.
+nl_create_list_one_liner        = false    # true/false
+
+# Whether to split one-line simple unbraced if statements into two lines by
+# adding a newline, as in 'if(b) <here> i++;'.
+nl_split_if_one_liner           = false    # true/false
+
+# Whether to split one-line simple unbraced for statements into two lines by
+# adding a newline, as in 'for (...) <here> stmt;'.
+nl_split_for_one_liner          = false    # true/false
+
+# Whether to split one-line simple unbraced while statements into two lines by
+# adding a newline, as in 'while (expr) <here> stmt;'.
+nl_split_while_one_liner        = false    # true/false
+
+# Don't add a newline before a cpp-comment in a parameter list of a function
+# call.
+donot_add_nl_before_cpp_comment = false    # true/false
+
+#
+# Blank line options
+#
+
+# The maximum number of consecutive newlines (3 = 2 blank lines).
+nl_max                          = 2        # unsigned number
+
+# The maximum number of consecutive newlines in a function.
+nl_max_blank_in_func            = 2        # unsigned number
+
+# The number of newlines inside an empty function body.
+# This option overrides eat_blanks_after_open_brace and
+# eat_blanks_before_close_brace, but is ignored when
+# nl_collapse_empty_body_functions=true
+nl_inside_empty_func            = 0        # unsigned number
+
+# The number of newlines before a function prototype.
+nl_before_func_body_proto       = 0        # unsigned number
+
+# The number of newlines before a multi-line function definition. Where
+# applicable, this option is overridden with eat_blanks_after_open_brace=true
+nl_before_func_body_def         = 0        # unsigned number
+
+# The number of newlines before a class constructor/destructor prototype.
+nl_before_func_class_proto      = 0        # unsigned number
+
+# The number of newlines before a class constructor/destructor definition.
+nl_before_func_class_def        = 0        # unsigned number
+
+# The number of newlines after a function prototype.
+nl_after_func_proto             = 0        # unsigned number
+
+# The number of newlines after a function prototype, if not followed by
+# another function prototype.
+nl_after_func_proto_group       = 2        # unsigned number
+
+# The number of newlines after a class constructor/destructor prototype.
+nl_after_func_class_proto       = 0        # unsigned number
+
+# The number of newlines after a class constructor/destructor prototype,
+# if not followed by another constructor/destructor prototype.
+nl_after_func_class_proto_group = 2        # unsigned number
+
+# Whether one-line method definitions inside a class body should be treated
+# as if they were prototypes for the purposes of adding newlines.
+#
+# Requires nl_class_leave_one_liners=true. Overrides nl_before_func_body_def
+# and nl_before_func_class_def for one-liners.
+nl_class_leave_one_liner_groups = false    # true/false
+
+# The number of newlines after '}' of a multi-line function body.
+nl_after_func_body              = 2        # unsigned number
+
+# The number of newlines after '}' of a multi-line function body in a class
+# declaration. Also affects class constructors/destructors.
+#
+# Overrides nl_after_func_body.
+nl_after_func_body_class        = 2        # unsigned number
+
+# The number of newlines after '}' of a single line function body. Also
+# affects class constructors/destructors.
+#
+# Overrides nl_after_func_body and nl_after_func_body_class.
+nl_after_func_body_one_liner    = 2        # unsigned number
+
+# The number of newlines before a block of typedefs. If nl_after_access_spec
+# is non-zero, that option takes precedence.
+#
+# 0: No change (default).
+nl_typedef_blk_start            = 0        # unsigned number
+
+# The number of newlines after a block of typedefs.
+#
+# 0: No change (default).
+nl_typedef_blk_end              = 0        # unsigned number
+
+# The maximum number of consecutive newlines within a block of typedefs.
+#
+# 0: No change (default).
+nl_typedef_blk_in               = 0        # unsigned number
+
+# The minimum number of blank lines after a block of variable definitions
+# at the top of a function body. If any preprocessor directives appear
+# between the opening brace of the function and the variable block, then
+# it is considered as not at the top of the function.Newlines are added
+# before trailing preprocessor directives, if any exist.
+#
+# 0: No change (default).
+nl_var_def_blk_end_func_top     = 0        # unsigned number
+
+# The minimum number of empty newlines before a block of variable definitions
+# not at the top of a function body. If nl_after_access_spec is non-zero,
+# that option takes precedence. Newlines are not added at the top of the
+# file or just after an opening brace. Newlines are added above any
+# preprocessor directives before the block.
+#
+# 0: No change (default).
+nl_var_def_blk_start            = 0        # unsigned number
+
+# The minimum number of empty newlines after a block of variable definitions
+# not at the top of a function body. Newlines are not added if the block
+# is at the bottom of the file or just before a preprocessor directive.
+#
+# 0: No change (default).
+nl_var_def_blk_end              = 0        # unsigned number
+
+# The maximum number of consecutive newlines within a block of variable
+# definitions.
+#
+# 0: No change (default).
+nl_var_def_blk_in               = 0        # unsigned number
+
+# The minimum number of newlines before a multi-line comment.
+# Doesn't apply if after a brace open or another multi-line comment.
+nl_before_block_comment         = 0        # unsigned number
+
+# The minimum number of newlines before a single-line C comment.
+# Doesn't apply if after a brace open or other single-line C comments.
+nl_before_c_comment             = 0        # unsigned number
+
+# The minimum number of newlines before a CPP comment.
+# Doesn't apply if after a brace open or other CPP comments.
+nl_before_cpp_comment           = 0        # unsigned number
+
+# Whether to force a newline after a multi-line comment.
+nl_after_multiline_comment      = false    # true/false
+
+# Whether to force a newline after a label's colon.
+nl_after_label_colon            = false    # true/false
+
+# The number of newlines before a struct definition.
+nl_before_struct                = 0        # unsigned number
+
+# The number of newlines after '}' or ';' of a struct/enum/union definition.
+nl_after_struct                 = 0        # unsigned number
+
+# The number of newlines before a class definition.
+nl_before_class                 = 0        # unsigned number
+
+# The number of newlines after '}' or ';' of a class definition.
+nl_after_class                  = 0        # unsigned number
+
+# The number of newlines before a namespace.
+nl_before_namespace             = 0        # unsigned number
+
+# The number of newlines after '{' of a namespace. This also adds newlines
+# before the matching '}'.
+#
+# 0: Apply eat_blanks_after_open_brace or eat_blanks_before_close_brace if
+#     applicable, otherwise no change.
+#
+# Overrides eat_blanks_after_open_brace and eat_blanks_before_close_brace.
+nl_inside_namespace             = 0        # unsigned number
+
+# The number of newlines after '}' of a namespace.
+nl_after_namespace              = 0        # unsigned number
+
+# The number of newlines before an access specifier label. This also includes
+# the Qt-specific 'signals:' and 'slots:'. Will not change the newline count
+# if after a brace open.
+#
+# 0: No change (default).
+nl_before_access_spec           = 2        # unsigned number
+
+# The number of newlines after an access specifier label. This also includes
+# the Qt-specific 'signals:' and 'slots:'. Will not change the newline count
+# if after a brace open.
+#
+# 0: No change (default).
+#
+# Overrides nl_typedef_blk_start and nl_var_def_blk_start.
+nl_after_access_spec            = 1        # unsigned number
+
+# The number of newlines between a function definition and the function
+# comment, as in '// comment\n <here> void foo() {...}'.
+#
+# 0: No change (default).
+nl_comment_func_def             = 0        # unsigned number
+
+# The number of newlines after a try-catch-finally block that isn't followed
+# by a brace close.
+#
+# 0: No change (default).
+nl_after_try_catch_finally      = 0        # unsigned number
+
+# (C#) The number of newlines before and after a property, indexer or event
+# declaration.
+#
+# 0: No change (default).
+nl_around_cs_property           = 0        # unsigned number
+
+# (C#) The number of newlines between the get/set/add/remove handlers.
+#
+# 0: No change (default).
+nl_between_get_set              = 0        # unsigned number
+
+# (C#) Add or remove newline between property and the '{'.
+nl_property_brace               = ignore   # ignore/add/remove/force/not_defined
+
+# Whether to remove blank lines after '{'.
+eat_blanks_after_open_brace     = true    # true/false
+
+# Whether to remove blank lines before '}'.
+eat_blanks_before_close_brace   = true    # true/false
+
+# How aggressively to remove extra newlines not in preprocessor.
+#
+# 0: No change (default)
+# 1: Remove most newlines not handled by other config
+# 2: Remove all newlines and reformat completely by config
+nl_remove_extra_newlines        = 2        # unsigned number
+
+# (Java) Add or remove newline after an annotation statement. Only affects
+# annotations that are after a newline.
+nl_after_annotation             = ignore   # ignore/add/remove/force/not_defined
+
+# (Java) Add or remove newline between two annotations.
+nl_between_annotation           = ignore   # ignore/add/remove/force/not_defined
+
+# The number of newlines before a whole-file #ifdef.
+#
+# 0: No change (default).
+nl_before_whole_file_ifdef      = 0        # unsigned number
+
+# The number of newlines after a whole-file #ifdef.
+#
+# 0: No change (default).
+nl_after_whole_file_ifdef       = 0        # unsigned number
+
+# The number of newlines before a whole-file #endif.
+#
+# 0: No change (default).
+nl_before_whole_file_endif      = 0        # unsigned number
+
+# The number of newlines after a whole-file #endif.
+#
+# 0: No change (default).
+nl_after_whole_file_endif       = 0        # unsigned number
+
+#
+# Positioning options
+#
+
+# The position of arithmetic operators in wrapped expressions.
+pos_arith                       = ignore   # ignore/break/force/lead/trail/join/lead_break/lead_force/trail_break/trail_force
+
+# The position of assignment in wrapped expressions. Do not affect '='
+# followed by '{'.
+pos_assign                      = ignore   # ignore/break/force/lead/trail/join/lead_break/lead_force/trail_break/trail_force
+
+# The position of Boolean operators in wrapped expressions.
+pos_bool                        = ignore   # ignore/break/force/lead/trail/join/lead_break/lead_force/trail_break/trail_force
+
+# The position of comparison operators in wrapped expressions.
+pos_compare                     = ignore   # ignore/break/force/lead/trail/join/lead_break/lead_force/trail_break/trail_force
+
+# The position of conditional operators, as in the '?' and ':' of
+# 'expr ? stmt : stmt', in wrapped expressions.
+pos_conditional                 = ignore   # ignore/break/force/lead/trail/join/lead_break/lead_force/trail_break/trail_force
+
+# The position of the comma in wrapped expressions.
+pos_comma                       = ignore   # ignore/break/force/lead/trail/join/lead_break/lead_force/trail_break/trail_force
+
+# The position of the comma in enum entries.
+pos_enum_comma                  = ignore   # ignore/break/force/lead/trail/join/lead_break/lead_force/trail_break/trail_force
+
+# The position of the comma in the base class list if there is more than one
+# line. Affects nl_class_init_args.
+pos_class_comma                 = ignore   # ignore/break/force/lead/trail/join/lead_break/lead_force/trail_break/trail_force
+
+# The position of the comma in the constructor initialization list.
+# Related to nl_constr_colon, nl_constr_init_args and pos_constr_colon.
+pos_constr_comma                = trail_force   # ignore/break/force/lead/trail/join/lead_break/lead_force/trail_break/trail_force
+
+# The position of trailing/leading class colon, between class and base class
+# list. Affects nl_class_colon.
+pos_class_colon                 = ignore   # ignore/break/force/lead/trail/join/lead_break/lead_force/trail_break/trail_force
+
+# The position of colons between constructor and member initialization.
+# Related to nl_constr_colon, nl_constr_init_args and pos_constr_comma.
+pos_constr_colon                = trail_force   # ignore/break/force/lead/trail/join/lead_break/lead_force/trail_break/trail_force
+
+# The position of shift operators in wrapped expressions.
+pos_shift                       = ignore   # ignore/break/force/lead/trail/join/lead_break/lead_force/trail_break/trail_force
+
+#
+# Line splitting options
+#
+
+# Try to limit code width to N columns.
+code_width                      = 0        # unsigned number
+
+# Whether to fully split long 'for' statements at semi-colons.
+ls_for_split_full               = false    # true/false
+
+# Whether to fully split long function prototypes/calls at commas.
+# The option ls_code_width has priority over the option ls_func_split_full.
+ls_func_split_full              = false    # true/false
+
+# Whether to split lines as close to code_width as possible and ignore some
+# groupings.
+# The option ls_code_width has priority over the option ls_func_split_full.
+ls_code_width                   = false    # true/false
+
+#
+# Code alignment options (not left column spaces/tabs)
+#
+
+# Whether to keep non-indenting tabs.
+align_keep_tabs                 = false    # true/false
+
+# Whether to use tabs for aligning.
+align_with_tabs                 = false    # true/false
+
+# Whether to bump out to the next tab when aligning.
+align_on_tabstop                = false    # true/false
+
+# Whether to right-align numbers.
+align_number_right              = false    # true/false
+
+# Whether to keep whitespace not required for alignment.
+align_keep_extra_space          = false    # true/false
+
+# Whether to align variable definitions in prototypes and functions.
+align_func_params               = false    # true/false
+
+# The span for aligning parameter definitions in function on parameter name.
+#
+# 0: Don't align (default).
+align_func_params_span          = 0        # unsigned number
+
+# The threshold for aligning function parameter definitions.
+# Use a negative number for absolute thresholds.
+#
+# 0: No limit (default).
+align_func_params_thresh        = 0        # number
+
+# The gap for aligning function parameter definitions.
+align_func_params_gap           = 0        # unsigned number
+
+# The span for aligning constructor value.
+#
+# 0: Don't align (default).
+align_constr_value_span         = 0        # unsigned number
+
+# The threshold for aligning constructor value.
+# Use a negative number for absolute thresholds.
+#
+# 0: No limit (default).
+align_constr_value_thresh       = 0        # number
+
+# The gap for aligning constructor value.
+align_constr_value_gap          = 0        # unsigned number
+
+# Whether to align parameters in single-line functions that have the same
+# name. The function names must already be aligned with each other.
+align_same_func_call_params     = false    # true/false
+
+# The span for aligning function-call parameters for single line functions.
+#
+# 0: Don't align (default).
+align_same_func_call_params_span = 0        # unsigned number
+
+# The threshold for aligning function-call parameters for single line
+# functions.
+# Use a negative number for absolute thresholds.
+#
+# 0: No limit (default).
+align_same_func_call_params_thresh = 0        # number
+
+# The span for aligning variable definitions.
+#
+# 0: Don't align (default).
+align_var_def_span              = 0        # unsigned number
+
+# How to consider (or treat) the '*' in the alignment of variable definitions.
+#
+# 0: Part of the type     'void *   foo;' (default)
+# 1: Part of the variable 'void     *foo;'
+# 2: Dangling             'void    *foo;'
+# Dangling: the '*' will not be taken into account when aligning.
+align_var_def_star_style        = 0        # unsigned number
+
+# How to consider (or treat) the '&' in the alignment of variable definitions.
+#
+# 0: Part of the type     'long &   foo;' (default)
+# 1: Part of the variable 'long     &foo;'
+# 2: Dangling             'long    &foo;'
+# Dangling: the '&' will not be taken into account when aligning.
+align_var_def_amp_style         = 0        # unsigned number
+
+# The threshold for aligning variable definitions.
+# Use a negative number for absolute thresholds.
+#
+# 0: No limit (default).
+align_var_def_thresh            = 0        # number
+
+# The gap for aligning variable definitions.
+align_var_def_gap               = 0        # unsigned number
+
+# Whether to align the colon in struct bit fields.
+align_var_def_colon             = false    # true/false
+
+# The gap for aligning the colon in struct bit fields.
+align_var_def_colon_gap         = 0        # unsigned number
+
+# Whether to align any attribute after the variable name.
+align_var_def_attribute         = false    # true/false
+
+# Whether to align inline struct/enum/union variable definitions.
+align_var_def_inline            = false    # true/false
+
+# The span for aligning on '=' in assignments.
+#
+# 0: Don't align (default).
+align_assign_span               = 0        # unsigned number
+
+# The span for aligning on '=' in function prototype modifier.
+#
+# 0: Don't align (default).
+align_assign_func_proto_span    = 0        # unsigned number
+
+# The threshold for aligning on '=' in assignments.
+# Use a negative number for absolute thresholds.
+#
+# 0: No limit (default).
+align_assign_thresh             = 0        # number
+
+# Whether to align on the left most assignment when multiple
+# definitions are found on the same line.
+# Depends on 'align_assign_span' and 'align_assign_thresh' settings.
+align_assign_on_multi_var_defs  = false    # true/false
+
+# The span for aligning on '{' in braced init list.
+#
+# 0: Don't align (default).
+align_braced_init_list_span     = 0        # unsigned number
+
+# The threshold for aligning on '{' in braced init list.
+# Use a negative number for absolute thresholds.
+#
+# 0: No limit (default).
+align_braced_init_list_thresh   = 0        # number
+
+# How to apply align_assign_span to function declaration "assignments", i.e.
+# 'virtual void foo() = 0' or '~foo() = {default|delete}'.
+#
+# 0: Align with other assignments (default)
+# 1: Align with each other, ignoring regular assignments
+# 2: Don't align
+align_assign_decl_func          = 0        # unsigned number
+
+# The span for aligning on '=' in enums.
+#
+# 0: Don't align (default).
+align_enum_equ_span             = 0        # unsigned number
+
+# The threshold for aligning on '=' in enums.
+# Use a negative number for absolute thresholds.
+#
+# 0: no limit (default).
+align_enum_equ_thresh           = 0        # number
+
+# The span for aligning class member definitions.
+#
+# 0: Don't align (default).
+align_var_class_span            = 0        # unsigned number
+
+# The threshold for aligning class member definitions.
+# Use a negative number for absolute thresholds.
+#
+# 0: No limit (default).
+align_var_class_thresh          = 0        # number
+
+# The gap for aligning class member definitions.
+align_var_class_gap             = 0        # unsigned number
+
+# The span for aligning struct/union member definitions.
+#
+# 0: Don't align (default).
+align_var_struct_span           = 0        # unsigned number
+
+# The threshold for aligning struct/union member definitions.
+# Use a negative number for absolute thresholds.
+#
+# 0: No limit (default).
+align_var_struct_thresh         = 0        # number
+
+# The gap for aligning struct/union member definitions.
+align_var_struct_gap            = 0        # unsigned number
+
+# The span for aligning struct initializer values.
+#
+# 0: Don't align (default).
+align_struct_init_span          = 0        # unsigned number
+
+# The span for aligning single-line typedefs.
+#
+# 0: Don't align (default).
+align_typedef_span              = 0        # unsigned number
+
+# The minimum space between the type and the synonym of a typedef.
+align_typedef_gap               = 0        # unsigned number
+
+# How to align typedef'd functions with other typedefs.
+#
+# 0: Don't mix them at all (default)
+# 1: Align the open parenthesis with the types
+# 2: Align the function type name with the other type names
+align_typedef_func              = 0        # unsigned number
+
+# How to consider (or treat) the '*' in the alignment of typedefs.
+#
+# 0: Part of the typedef type, 'typedef int * pint;' (default)
+# 1: Part of type name:        'typedef int   *pint;'
+# 2: Dangling:                 'typedef int  *pint;'
+# Dangling: the '*' will not be taken into account when aligning.
+align_typedef_star_style        = 0        # unsigned number
+
+# How to consider (or treat) the '&' in the alignment of typedefs.
+#
+# 0: Part of the typedef type, 'typedef int & intref;' (default)
+# 1: Part of type name:        'typedef int   &intref;'
+# 2: Dangling:                 'typedef int  &intref;'
+# Dangling: the '&' will not be taken into account when aligning.
+align_typedef_amp_style         = 0        # unsigned number
+
+# The span for aligning comments that end lines.
+#
+# 0: Don't align (default).
+align_right_cmt_span            = 0        # unsigned number
+
+# Minimum number of columns between preceding text and a trailing comment in
+# order for the comment to qualify for being aligned. Must be non-zero to have
+# an effect.
+align_right_cmt_gap             = 0        # unsigned number
+
+# If aligning comments, whether to mix with comments after '}' and #endif with
+# less than three spaces before the comment.
+align_right_cmt_mix             = false    # true/false
+
+# Whether to only align trailing comments that are at the same brace level.
+align_right_cmt_same_level      = false    # true/false
+
+# Minimum column at which to align trailing comments. Comments which are
+# aligned beyond this column, but which can be aligned in a lesser column,
+# may be "pulled in".
+#
+# 0: Ignore (default).
+align_right_cmt_at_col          = 0        # unsigned number
+
+# The span for aligning function prototypes.
+#
+# 0: Don't align (default).
+align_func_proto_span           = 0        # unsigned number
+
+# How to consider (or treat) the '*' in the alignment of function prototypes.
+#
+# 0: Part of the type     'void *   foo();' (default)
+# 1: Part of the function 'void     *foo();'
+# 2: Dangling             'void    *foo();'
+# Dangling: the '*' will not be taken into account when aligning.
+align_func_proto_star_style     = 0        # unsigned number
+
+# How to consider (or treat) the '&' in the alignment of function prototypes.
+#
+# 0: Part of the type     'long &   foo();' (default)
+# 1: Part of the function 'long     &foo();'
+# 2: Dangling             'long    &foo();'
+# Dangling: the '&' will not be taken into account when aligning.
+align_func_proto_amp_style      = 0        # unsigned number
+
+# The threshold for aligning function prototypes.
+# Use a negative number for absolute thresholds.
+#
+# 0: No limit (default).
+align_func_proto_thresh         = 0        # number
+
+# Minimum gap between the return type and the function name.
+align_func_proto_gap            = 0        # unsigned number
+
+# Whether to align function prototypes on the 'operator' keyword instead of
+# what follows.
+align_on_operator               = false    # true/false
+
+# Whether to mix aligning prototype and variable declarations. If true,
+# align_var_def_XXX options are used instead of align_func_proto_XXX options.
+align_mix_var_proto             = false    # true/false
+
+# Whether to align single-line functions with function prototypes.
+# Uses align_func_proto_span.
+align_single_line_func          = false    # true/false
+
+# Whether to align the open brace of single-line functions.
+# Requires align_single_line_func=true. Uses align_func_proto_span.
+align_single_line_brace         = false    # true/false
+
+# Gap for align_single_line_brace.
+align_single_line_brace_gap     = 0        # unsigned number
+
+# (OC) The span for aligning Objective-C message specifications.
+#
+# 0: Don't align (default).
+align_oc_msg_spec_span          = 0        # unsigned number
+
+# Whether and how to align backslashes that split a macro onto multiple lines.
+# This will not work right if the macro contains a multi-line comment.
+#
+# 0: Do nothing (default)
+# 1: Align the backslashes in the column at the end of the longest line
+# 2: Align with the backslash that is farthest to the left, or, if that
+#    backslash is farther left than the end of the longest line, at the end of
+#    the longest line
+# 3: Align with the backslash that is farthest to the right
+align_nl_cont                   = 0        # unsigned number
+
+# Whether to align macro functions and variables together.
+align_pp_define_together        = false    # true/false
+
+# The span for aligning on '#define' bodies.
+#
+# =0: Don't align (default)
+# >0: Number of lines (including comments) between blocks
+align_pp_define_span            = 0        # unsigned number
+
+# The minimum space between label and value of a preprocessor define.
+align_pp_define_gap             = 0        # unsigned number
+
+# Whether to align lines that start with '<<' with previous '<<'.
+#
+# Default: true
+align_left_shift                = true     # true/false
+
+# Whether to align comma-separated statements following '<<' (as used to
+# initialize Eigen matrices).
+align_eigen_comma_init          = false    # true/false
+
+# Whether to align text after 'asm volatile ()' colons.
+align_asm_colon                 = false    # true/false
+
+# (OC) Span for aligning parameters in an Objective-C message call
+# on the ':'.
+#
+# 0: Don't align.
+align_oc_msg_colon_span         = 0        # unsigned number
+
+# (OC) Whether to always align with the first parameter, even if it is too
+# short.
+align_oc_msg_colon_first        = false    # true/false
+
+# (OC) Whether to align parameters in an Objective-C '+' or '-' declaration
+# on the ':'.
+align_oc_decl_colon             = false    # true/false
+
+# (OC) Whether to not align parameters in an Objectve-C message call if first
+# colon is not on next line of the message call (the same way Xcode does
+# alignment)
+align_oc_msg_colon_xcode_like   = false    # true/false
+
+#
+# Comment modification options
+#
+
+# Try to wrap comments at N columns.
+cmt_width                       = 0        # unsigned number
+
+# How to reflow comments.
+#
+# 0: No reflowing (apart from the line wrapping due to cmt_width) (default)
+# 1: No touching at all
+# 2: Full reflow (enable cmt_indent_multi for indent with line wrapping due to cmt_width)
+cmt_reflow_mode                 = 0        # unsigned number
+
+# Path to a file that contains regular expressions describing patterns for
+# which the end of one line and the beginning of the next will be folded into
+# the same sentence or paragraph during full comment reflow. The regular
+# expressions are described using ECMAScript syntax. The syntax for this
+# specification is as follows, where "..." indicates the custom regular
+# expression and "n" indicates the nth end_of_prev_line_regex and
+# beg_of_next_line_regex regular expression pair:
+#
+# end_of_prev_line_regex[1] = "...$"
+# beg_of_next_line_regex[1] = "^..."
+# end_of_prev_line_regex[2] = "...$"
+# beg_of_next_line_regex[2] = "^..."
+#             .
+#             .
+#             .
+# end_of_prev_line_regex[n] = "...$"
+# beg_of_next_line_regex[n] = "^..."
+#
+# Note that use of this option overrides the default reflow fold regular
+# expressions, which are internally defined as follows:
+#
+# end_of_prev_line_regex[1] = "[\w,\]\)]$"
+# beg_of_next_line_regex[1] = "^[\w,\[\(]"
+# end_of_prev_line_regex[2] = "\.$"
+# beg_of_next_line_regex[2] = "^[A-Z]"
+cmt_reflow_fold_regex_file      = ""         # string
+
+# Whether to indent wrapped lines to the start of the encompassing paragraph
+# during full comment reflow (cmt_reflow_mode = 2). Overrides the value
+# specified by cmt_sp_after_star_cont.
+#
+# Note that cmt_align_doxygen_javadoc_tags overrides this option for
+# paragraphs associated with javadoc tags
+cmt_reflow_indent_to_paragraph_start = false    # true/false
+
+# Whether to convert all tabs to spaces in comments. If false, tabs in
+# comments are left alone, unless used for indenting.
+cmt_convert_tab_to_spaces       = false    # true/false
+
+# Whether to apply changes to multi-line comments, including cmt_width,
+# keyword substitution and leading chars.
+#
+# Default: true
+cmt_indent_multi                = true     # true/false
+
+# Whether to align doxygen javadoc-style tags ('@param', '@return', etc.)
+# and corresponding fields such that groups of consecutive block tags,
+# parameter names, and descriptions align with one another. Overrides that
+# which is specified by the cmt_sp_after_star_cont. If cmt_width > 0, it may
+# be necessary to enable cmt_indent_multi and set cmt_reflow_mode = 2
+# in order to achieve the desired alignment for line-wrapping.
+cmt_align_doxygen_javadoc_tags  = false    # true/false
+
+# The number of spaces to insert after the star and before doxygen
+# javadoc-style tags (@param, @return, etc). Requires enabling
+# cmt_align_doxygen_javadoc_tags. Overrides that which is specified by the
+# cmt_sp_after_star_cont.
+#
+# Default: 1
+cmt_sp_before_doxygen_javadoc_tags = 1        # unsigned number
+
+# Whether to change trailing, single-line c-comments into cpp-comments.
+cmt_trailing_single_line_c_to_cpp = false    # true/false
+
+# Whether to group c-comments that look like they are in a block.
+cmt_c_group                     = false    # true/false
+
+# Whether to put an empty '/*' on the first line of the combined c-comment.
+cmt_c_nl_start                  = false    # true/false
+
+# Whether to add a newline before the closing '*/' of the combined c-comment.
+cmt_c_nl_end                    = false    # true/false
+
+# Whether to change cpp-comments into c-comments.
+cmt_cpp_to_c                    = false    # true/false
+
+# Whether to group cpp-comments that look like they are in a block. Only
+# meaningful if cmt_cpp_to_c=true.
+cmt_cpp_group                   = false    # true/false
+
+# Whether to put an empty '/*' on the first line of the combined cpp-comment
+# when converting to a c-comment.
+#
+# Requires cmt_cpp_to_c=true and cmt_cpp_group=true.
+cmt_cpp_nl_start                = false    # true/false
+
+# Whether to add a newline before the closing '*/' of the combined cpp-comment
+# when converting to a c-comment.
+#
+# Requires cmt_cpp_to_c=true and cmt_cpp_group=true.
+cmt_cpp_nl_end                  = false    # true/false
+
+# Whether to put a star on subsequent comment lines.
+cmt_star_cont                   = false    # true/false
+
+# The number of spaces to insert at the start of subsequent comment lines.
+cmt_sp_before_star_cont         = 0        # unsigned number
+
+# The number of spaces to insert after the star on subsequent comment lines.
+cmt_sp_after_star_cont          = 0        # unsigned number
+
+# For multi-line comments with a '*' lead, remove leading spaces if the first
+# and last lines of the comment are the same length.
+#
+# Default: true
+cmt_multi_check_last            = true     # true/false
+
+# For multi-line comments with a '*' lead, remove leading spaces if the first
+# and last lines of the comment are the same length AND if the length is
+# bigger as the first_len minimum.
+#
+# Default: 4
+cmt_multi_first_len_minimum     = 4        # unsigned number
+
+# Path to a file that contains text to insert at the beginning of a file if
+# the file doesn't start with a C/C++ comment. If the inserted text contains
+# '$(filename)', that will be replaced with the current file's name.
+cmt_insert_file_header          = ""         # string
+
+# Path to a file that contains text to insert at the end of a file if the
+# file doesn't end with a C/C++ comment. If the inserted text contains
+# '$(filename)', that will be replaced with the current file's name.
+cmt_insert_file_footer          = ""         # string
+
+# Path to a file that contains text to insert before a function definition if
+# the function isn't preceded by a C/C++ comment. If the inserted text
+# contains '$(function)', '$(javaparam)' or '$(fclass)', these will be
+# replaced with, respectively, the name of the function, the javadoc '@param'
+# and '@return' stuff, or the name of the class to which the member function
+# belongs.
+cmt_insert_func_header          = ""         # string
+
+# Path to a file that contains text to insert before a class if the class
+# isn't preceded by a C/C++ comment. If the inserted text contains '$(class)',
+# that will be replaced with the class name.
+cmt_insert_class_header         = ""         # string
+
+# Path to a file that contains text to insert before an Objective-C message
+# specification, if the method isn't preceded by a C/C++ comment. If the
+# inserted text contains '$(message)' or '$(javaparam)', these will be
+# replaced with, respectively, the name of the function, or the javadoc
+# '@param' and '@return' stuff.
+cmt_insert_oc_msg_header        = ""         # string
+
+# Whether a comment should be inserted if a preprocessor is encountered when
+# stepping backwards from a function name.
+#
+# Applies to cmt_insert_oc_msg_header, cmt_insert_func_header and
+# cmt_insert_class_header.
+cmt_insert_before_preproc       = false    # true/false
+
+# Whether a comment should be inserted if a function is declared inline to a
+# class definition.
+#
+# Applies to cmt_insert_func_header.
+#
+# Default: true
+cmt_insert_before_inlines       = true     # true/false
+
+# Whether a comment should be inserted if the function is a class constructor
+# or destructor.
+#
+# Applies to cmt_insert_func_header.
+cmt_insert_before_ctor_dtor     = false    # true/false
+
+#
+# Code modifying options (non-whitespace)
+#
+
+# Add or remove braces on a single-line 'do' statement.
+mod_full_brace_do               = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove braces on a single-line 'for' statement.
+mod_full_brace_for              = ignore   # ignore/add/remove/force/not_defined
+
+# (Pawn) Add or remove braces on a single-line function definition.
+mod_full_brace_function         = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove braces on a single-line 'if' statement. Braces will not be
+# removed if the braced statement contains an 'else'.
+mod_full_brace_if               = ignore   # ignore/add/remove/force/not_defined
+
+# Whether to enforce that all blocks of an 'if'/'else if'/'else' chain either
+# have, or do not have, braces. Overrides mod_full_brace_if.
+#
+# 0: Don't override mod_full_brace_if
+# 1: Add braces to all blocks if any block needs braces and remove braces if
+#    they can be removed from all blocks
+# 2: Add braces to all blocks if any block already has braces, regardless of
+#    whether it needs them
+# 3: Add braces to all blocks if any block needs braces and remove braces if
+#    they can be removed from all blocks, except if all blocks have braces
+#    despite none needing them
+mod_full_brace_if_chain         = 0        # unsigned number
+
+# Whether to add braces to all blocks of an 'if'/'else if'/'else' chain.
+# If true, mod_full_brace_if_chain will only remove braces from an 'if' that
+# does not have an 'else if' or 'else'.
+mod_full_brace_if_chain_only    = false    # true/false
+
+# Add or remove braces on single-line 'while' statement.
+mod_full_brace_while            = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove braces on single-line 'using ()' statement.
+mod_full_brace_using            = ignore   # ignore/add/remove/force/not_defined
+
+# Don't remove braces around statements that span N newlines
+mod_full_brace_nl               = 0        # unsigned number
+
+# Whether to prevent removal of braces from 'if'/'for'/'while'/etc. blocks
+# which span multiple lines.
+#
+# Affects:
+#   mod_full_brace_for
+#   mod_full_brace_if
+#   mod_full_brace_if_chain
+#   mod_full_brace_if_chain_only
+#   mod_full_brace_while
+#   mod_full_brace_using
+#
+# Does not affect:
+#   mod_full_brace_do
+#   mod_full_brace_function
+mod_full_brace_nl_block_rem_mlcond = false    # true/false
+
+# Add or remove unnecessary parentheses on 'return' statement.
+mod_paren_on_return             = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove unnecessary parentheses on 'throw' statement.
+mod_paren_on_throw              = ignore   # ignore/add/remove/force/not_defined
+
+# (Pawn) Whether to change optional semicolons to real semicolons.
+mod_pawn_semicolon              = false    # true/false
+
+# Whether to fully parenthesize Boolean expressions in 'while' and 'if'
+# statement, as in 'if (a && b > c)' => 'if (a && (b > c))'.
+mod_full_paren_if_bool          = false    # true/false
+
+# Whether to fully parenthesize Boolean expressions after '='
+# statement, as in 'x = a && b > c;' => 'x = (a && (b > c));'.
+mod_full_paren_assign_bool      = false    # true/false
+
+# Whether to fully parenthesize Boolean expressions after '='
+# statement, as in 'return  a && b > c;' => 'return (a && (b > c));'.
+mod_full_paren_return_bool      = false    # true/false
+
+# Whether to remove superfluous semicolons.
+mod_remove_extra_semicolon      = false    # true/false
+
+# Whether to remove duplicate include.
+mod_remove_duplicate_include    = false    # true/false
+
+# the following options (mod_XX_closebrace_comment) use different comment,
+# depending of the setting of the next option.
+# false: Use the c comment (default)
+# true : Use the cpp comment
+mod_add_force_c_closebrace_comment = false    # true/false
+
+# If a function body exceeds the specified number of newlines and doesn't have
+# a comment after the close brace, a comment will be added.
+mod_add_long_function_closebrace_comment = 0        # unsigned number
+
+# If a namespace body exceeds the specified number of newlines and doesn't
+# have a comment after the close brace, a comment will be added.
+mod_add_long_namespace_closebrace_comment = 0        # unsigned number
+
+# If a class body exceeds the specified number of newlines and doesn't have a
+# comment after the close brace, a comment will be added.
+mod_add_long_class_closebrace_comment = 0        # unsigned number
+
+# If a switch body exceeds the specified number of newlines and doesn't have a
+# comment after the close brace, a comment will be added.
+mod_add_long_switch_closebrace_comment = 0        # unsigned number
+
+# If an #ifdef body exceeds the specified number of newlines and doesn't have
+# a comment after the #endif, a comment will be added.
+mod_add_long_ifdef_endif_comment = 0        # unsigned number
+
+# If an #ifdef or #else body exceeds the specified number of newlines and
+# doesn't have a comment after the #else, a comment will be added.
+mod_add_long_ifdef_else_comment = 0        # unsigned number
+
+# Whether to take care of the case by the mod_sort_xx options.
+mod_sort_case_sensitive         = false    # true/false
+
+# Whether to sort consecutive single-line 'import' statements.
+mod_sort_import                 = false    # true/false
+
+# (C#) Whether to sort consecutive single-line 'using' statements.
+mod_sort_using                  = false    # true/false
+
+# Whether to sort consecutive single-line '#include' statements (C/C++) and
+# '#import' statements (Objective-C). Be aware that this has the potential to
+# break your code if your includes/imports have ordering dependencies.
+mod_sort_include                = false    # true/false
+
+# Whether to prioritize '#include' and '#import' statements that contain
+# filename without extension when sorting is enabled.
+mod_sort_incl_import_prioritize_filename = false    # true/false
+
+# Whether to prioritize '#include' and '#import' statements that does not
+# contain extensions when sorting is enabled.
+mod_sort_incl_import_prioritize_extensionless = false    # true/false
+
+# Whether to prioritize '#include' and '#import' statements that contain
+# angle over quotes when sorting is enabled.
+mod_sort_incl_import_prioritize_angle_over_quotes = false    # true/false
+
+# Whether to ignore file extension in '#include' and '#import' statements
+# for sorting comparison.
+mod_sort_incl_import_ignore_extension = false    # true/false
+
+# Whether to group '#include' and '#import' statements when sorting is enabled.
+mod_sort_incl_import_grouping_enabled = false    # true/false
+
+# Whether to move a 'break' that appears after a fully braced 'case' before
+# the close brace, as in 'case X: { ... } break;' => 'case X: { ... break; }'.
+mod_move_case_break             = false    # true/false
+
+# Whether to move a 'return' that appears after a fully braced 'case' before
+# the close brace, as in 'case X: { ... } return;' => 'case X: { ... return; }'.
+mod_move_case_return            = false    # true/false
+
+# Add or remove braces around a fully braced case statement. Will only remove
+# braces if there are no variable declarations in the block.
+mod_case_brace                  = ignore   # ignore/add/remove/force/not_defined
+
+# Whether to remove a void 'return;' that appears as the last statement in a
+# function.
+mod_remove_empty_return         = false    # true/false
+
+# Add or remove the comma after the last value of an enumeration.
+mod_enum_last_comma             = ignore   # ignore/add/remove/force/not_defined
+
+# Syntax to use for infinite loops.
+#
+# 0: Leave syntax alone (default)
+# 1: Rewrite as `for(;;)`
+# 2: Rewrite as `while(true)`
+# 3: Rewrite as `do`...`while(true);`
+# 4: Rewrite as `while(1)`
+# 5: Rewrite as `do`...`while(1);`
+#
+# Infinite loops that do not already match one of these syntaxes are ignored.
+# Other options that affect loop formatting will be applied after transforming
+# the syntax.
+mod_infinite_loop               = 0        # unsigned number
+
+# Add or remove the 'int' keyword in 'int short'.
+mod_int_short                   = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove the 'int' keyword in 'short int'.
+mod_short_int                   = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove the 'int' keyword in 'int long'.
+mod_int_long                    = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove the 'int' keyword in 'long int'.
+mod_long_int                    = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove the 'int' keyword in 'int signed'.
+mod_int_signed                  = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove the 'int' keyword in 'signed int'.
+mod_signed_int                  = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove the 'int' keyword in 'int unsigned'.
+mod_int_unsigned                = ignore   # ignore/add/remove/force/not_defined
+
+# Add or remove the 'int' keyword in 'unsigned int'.
+mod_unsigned_int                = ignore   # ignore/add/remove/force/not_defined
+
+# If there is a situation where mod_int_* and mod_*_int would result in
+# multiple int keywords, whether to keep the rightmost int (the default) or the
+# leftmost int.
+mod_int_prefer_int_on_left      = false    # true/false
+
+# (OC) Whether to organize the properties. If true, properties will be
+# rearranged according to the mod_sort_oc_property_*_weight factors.
+mod_sort_oc_properties          = false    # true/false
+
+# (OC) Weight of a class property modifier.
+mod_sort_oc_property_class_weight = 0        # number
+
+# (OC) Weight of 'atomic' and 'nonatomic'.
+mod_sort_oc_property_thread_safe_weight = 0        # number
+
+# (OC) Weight of 'readwrite' when organizing properties.
+mod_sort_oc_property_readwrite_weight = 0        # number
+
+# (OC) Weight of a reference type specifier ('retain', 'copy', 'assign',
+# 'weak', 'strong') when organizing properties.
+mod_sort_oc_property_reference_weight = 0        # number
+
+# (OC) Weight of getter type ('getter=') when organizing properties.
+mod_sort_oc_property_getter_weight = 0        # number
+
+# (OC) Weight of setter type ('setter=') when organizing properties.
+mod_sort_oc_property_setter_weight = 0        # number
+
+# (OC) Weight of nullability type ('nullable', 'nonnull', 'null_unspecified',
+# 'null_resettable') when organizing properties.
+mod_sort_oc_property_nullability_weight = 0        # number
+
+#
+# Preprocessor options
+#
+
+# How to use tabs when indenting preprocessor code.
+#
+# -1: Use 'indent_with_tabs' setting (default)
+#  0: Spaces only
+#  1: Indent with tabs to brace level, align with spaces
+#  2: Indent and align with tabs, using spaces when not on a tabstop
+#
+# Default: -1
+pp_indent_with_tabs             = -1       # number
+
+# Add or remove indentation of preprocessor directives inside #if blocks
+# at brace level 0 (file-level).
+pp_indent                       = ignore   # ignore/add/remove/force/not_defined
+
+# Whether to indent #if/#else/#endif at the brace level. If false, these are
+# indented from column 1.
+pp_indent_at_level              = false    # true/false
+
+# Whether to indent #if/#else/#endif at the parenthesis level if the brace
+# level is 0. If false, these are indented from column 1.
+pp_indent_at_level0             = false    # true/false
+
+# Specifies the number of columns to indent preprocessors per level
+# at brace level 0 (file-level). If pp_indent_at_level=false, also specifies
+# the number of columns to indent preprocessors per level
+# at brace level > 0 (function-level).
+#
+# Default: 1
+pp_indent_count                 = 1        # unsigned number
+
+# Add or remove space after # based on pp level of #if blocks.
+pp_space_after                  = ignore   # ignore/add/remove/force/not_defined
+
+# Sets the number of spaces per level added with pp_space_after.
+pp_space_count                  = 0        # unsigned number
+
+# The indent for '#region' and '#endregion' in C# and '#pragma region' in
+# C/C++. Negative values decrease indent down to the first column.
+pp_indent_region                = 0        # number
+
+# Whether to indent the code between #region and #endregion.
+pp_region_indent_code           = false    # true/false
+
+# If pp_indent_at_level=true, sets the indent for #if, #else and #endif when
+# not at file-level. Negative values decrease indent down to the first column.
+#
+# =0: Indent preprocessors using output_tab_size
+# >0: Column at which all preprocessors will be indented
+pp_indent_if                    = 0        # number
+
+# Whether to indent the code between #if, #else and #endif.
+pp_if_indent_code               = false    # true/false
+
+# Whether to indent the body of an #if that encompasses all the code in the file.
+pp_indent_in_guard              = false    # true/false
+
+# Whether to indent '#define' at the brace level. If false, these are
+# indented from column 1.
+pp_define_at_level              = false    # true/false
+
+# Whether to indent '#include' at the brace level.
+pp_include_at_level             = false    # true/false
+
+# Whether to ignore the '#define' body while formatting.
+pp_ignore_define_body           = false    # true/false
+
+# An offset value that controls the indentation of the body of a multiline #define.
+# 'body' refers to all the lines of a multiline #define except the first line.
+# Requires 'pp_ignore_define_body = false'.
+#
+#  <0: Absolute column: the body indentation starts off at the specified column
+#      (ex. -3 ==> the body is indented starting from column 3)
+# >=0: Relative to the column of the '#' of '#define'
+#      (ex.  3 ==> the body is indented starting 3 columns at the right of '#')
+#
+# Default: 8
+pp_multiline_define_body_indent = 8        # number
+
+# Whether to indent case statements between #if, #else, and #endif.
+# Only applies to the indent of the preprocessor that the case statements
+# directly inside of.
+#
+# Default: true
+pp_indent_case                  = true     # true/false
+
+# Whether to indent whole function definitions between #if, #else, and #endif.
+# Only applies to the indent of the preprocessor that the function definition
+# is directly inside of.
+#
+# Default: true
+pp_indent_func_def              = true     # true/false
+
+# Whether to indent extern C blocks between #if, #else, and #endif.
+# Only applies to the indent of the preprocessor that the extern block is
+# directly inside of.
+#
+# Default: true
+pp_indent_extern                = true     # true/false
+
+# How to indent braces directly inside #if, #else, and #endif.
+# Requires pp_if_indent_code=true and only applies to the indent of the
+# preprocessor that the braces are directly inside of.
+#  0: No extra indent
+#  1: Indent by one level
+# -1: Preserve original indentation
+#
+# Default: 1
+pp_indent_brace                 = 1        # number
+
+# Whether to print warning messages for unbalanced #if and #else blocks.
+# This will print a message in the following cases:
+# - if an #ifdef block ends on a different indent level than
+#   where it started from. Example:
+#
+#    #ifdef TEST
+#      int i;
+#      {
+#        int j;
+#    #endif
+#
+# - an #elif/#else block ends on a different indent level than
+#   the corresponding #ifdef block. Example:
+#
+#    #ifdef TEST
+#        int i;
+#    #else
+#        }
+#      int j;
+#    #endif
+pp_warn_unbalanced_if           = false    # true/false
+
+#
+# Sort includes options
+#
+
+# The regex for include category with priority 0.
+include_category_0              = ""         # string
+
+# The regex for include category with priority 1.
+include_category_1              = ""         # string
+
+# The regex for include category with priority 2.
+include_category_2              = ""         # string
+
+#
+# Use or Do not Use options
+#
+
+# true:  indent_func_call_param will be used (default)
+# false: indent_func_call_param will NOT be used
+#
+# Default: true
+use_indent_func_call_param      = true     # true/false
+
+# The value of the indentation for a continuation line is calculated
+# differently if the statement is:
+# - a declaration: your case with QString fileName ...
+# - an assignment: your case with pSettings = new QSettings( ...
+#
+# At the second case the indentation value might be used twice:
+# - at the assignment
+# - at the function call (if present)
+#
+# To prevent the double use of the indentation value, use this option with the
+# value 'true'.
+#
+# true:  indent_continue will be used only once
+# false: indent_continue will be used every time (default)
+#
+# Requires indent_ignore_first_continue=false.
+use_indent_continue_only_once   = false    # true/false
+
+# The indentation can be:
+# - after the assignment, at the '[' character
+# - at the beginning of the lambda body
+#
+# true:  indentation will be at the beginning of the lambda body
+# false: indentation will be after the assignment (default)
+indent_cpp_lambda_only_once     = false    # true/false
+
+# Whether sp_after_angle takes precedence over sp_inside_fparen. This was the
+# historic behavior, but is probably not the desired behavior, so this is off
+# by default.
+use_sp_after_angle_always       = false    # true/false
+
+# Whether to apply special formatting for Qt SIGNAL/SLOT macros. Essentially,
+# this tries to format these so that they match Qt's normalized form (i.e. the
+# result of QMetaObject::normalizedSignature), which can slightly improve the
+# performance of the QObject::connect call, rather than how they would
+# otherwise be formatted.
+#
+# See options_for_QT.cpp for details.
+#
+# Default: true
+use_options_overriding_for_qt_macros = true     # true/false
+
+# If true: the form feed character is removed from the list of whitespace
+# characters. See https://en.cppreference.com/w/cpp/string/byte/isspace.
+use_form_feed_no_more_as_whitespace_character = false    # true/false
+
+#
+# Warn levels - 1: error, 2: warning (default), 3: note
+#
+
+# (C#) Warning is given if doing tab-to-\t replacement and we have found one
+# in a C# verbatim string literal.
+#
+# Default: 2
+warn_level_tabs_found_in_verbatim_string_literals = 2        # unsigned number
+
+# Limit the number of loops.
+# Used by uncrustify.cpp to exit from infinite loop.
+# 0: no limit.
+debug_max_number_of_loops       = 0        # number
+
+# Set the number of the line to protocol;
+# Used in the function prot_the_line if the 2. parameter is zero.
+# 0: nothing protocol.
+debug_line_number_to_protocol   = 0        # number
+
+# Set the number of second(s) before terminating formatting the current file,
+# 0: no timeout.
+# only for linux
+debug_timeout                   = 0        # number
+
+# Set the number of characters to be printed if the text is too long,
+# 0: do not truncate.
+debug_truncate                  = 0        # unsigned number
+
+# sort (or not) the tracking info.
+#
+# Default: true
+debug_sort_the_tracks           = true     # true/false
+
+# decode (or not) the flags as a new line.
+# only if the -p option is set.
+debug_decode_the_flags          = false    # true/false
+
+# insert the number of the line at the beginning of each line
+set_numbering_for_html_output   = false    # true/false
+
+# Meaning of the settings:
+#   Ignore - do not do any changes
+#   Add    - makes sure there is 1 or more space/brace/newline/etc
+#   Force  - makes sure there is exactly 1 space/brace/newline/etc,
+#            behaves like Add in some contexts
+#   Remove - removes space/brace/newline/etc
+#
+#
+# - Token(s) can be treated as specific type(s) with the 'set' option:
+#     `set tokenType tokenString [tokenString...]`
+#
+#     Example:
+#       `set BOOL __AND__ __OR__`
+#
+#     tokenTypes are defined in src/token_enum.h, use them without the
+#     'CT_' prefix: 'CT_BOOL' => 'BOOL'
+#
+#
+# - Token(s) can be treated as type(s) with the 'type' option.
+#     `type tokenString [tokenString...]`
+#
+#     Example:
+#       `type int c_uint_8 Rectangle`
+#
+#     This can also be achieved with `set TYPE int c_uint_8 Rectangle`
+#
+#
+# To embed whitespace in tokenStrings use the '\' escape character, or quote
+# the tokenStrings. These quotes are supported: "'`
+#
+#
+# - Support for the auto detection of languages through the file ending can be
+#   added using the 'file_ext' command.
+#     `file_ext langType langString [langString..]`
+#
+#     Example:
+#       `file_ext CPP .ch .cxx .cpp.in`
+#
+#     langTypes are defined in uncrusify_types.h in the lang_flag_e enum, use
+#     them without the 'LANG_' prefix: 'LANG_CPP' => 'CPP'
+#
+#
+# - Custom macro-based indentation can be set up using 'macro-open',
+#   'macro-else' and 'macro-close'.
+#     `(macro-open | macro-else | macro-close) tokenString`
+#
+#     Example:
+#       `macro-open  BEGIN_TEMPLATE_MESSAGE_MAP`
+#       `macro-open  BEGIN_MESSAGE_MAP`
+#       `macro-close END_MESSAGE_MAP`
+#
+#
+# option(s) with 'not default' value: 0
+#
diff --git a/shared/info.cpp b/shared/info.cpp
index 35f6427..2dd9181 100644
--- a/shared/info.cpp
+++ b/shared/info.cpp
@@ -17,368 +17,359 @@
  */
 
 #include "info.h"
-
-Shared::Info::Info(const QString &addr, EntryType tp):
-type(tp),
-address(addr),
-vcard(nullptr),
-activeKeys(nullptr),
-inactiveKeys(nullptr) {
+Shared::Info::Info (const QString& addr, EntryType tp):
+    type(tp),
+    address(addr),
+    vcard(nullptr),
+    activeKeys(nullptr),
+    inactiveKeys(nullptr)
+{
     switch (type) {
-    case EntryType::none:
-        break;
-    case EntryType::contact:
-    case EntryType::ownAccount:
-        vcard = new VCard();
-        activeKeys = new std::list<KeyInfo>();
-        inactiveKeys = new std::list<KeyInfo>();
-        break;
-    default:
-        throw 352;
+        case EntryType::none:
+            break;
+        case EntryType::contact:
+        case EntryType::ownAccount:
+            vcard = new VCard();
+            activeKeys = new std::list<KeyInfo>();
+            inactiveKeys = new std::list<KeyInfo>();
+            break;
+        default:
+            throw 352;
     }
 }
 
-Shared::Info::Info():
-type(EntryType::none),
-address(""),
-vcard(nullptr),
-activeKeys(nullptr),
-inactiveKeys(nullptr) {}
+Shared::Info::Info ():
+    type(EntryType::none),
+    address(""),
+    vcard(nullptr),
+    activeKeys(nullptr),
+    inactiveKeys(nullptr) {}
 
-Shared::Info::Info(const Shared::Info &other):
-type(other.type),
-address(other.address),
-vcard(nullptr),
-activeKeys(nullptr),
-inactiveKeys(nullptr) {
+Shared::Info::Info (const Shared::Info& other):
+    type(other.type),
+    address(other.address),
+    vcard(nullptr),
+    activeKeys(nullptr),
+    inactiveKeys(nullptr)
+{
     switch (type) {
-    case EntryType::none:
-        break;
-    case EntryType::contact:
-    case EntryType::ownAccount:
-        vcard = new VCard(other.getVCardRef());
-        activeKeys = new std::list<KeyInfo>(other.getActiveKeysRef());
-        inactiveKeys = new std::list<KeyInfo>(other.getInactiveKeysRef());
-        break;
-    default:
-        throw 353;
+        case EntryType::none:
+            break;
+        case EntryType::contact:
+        case EntryType::ownAccount:
+            vcard = new VCard(other.getVCardRef());
+            activeKeys = new std::list<KeyInfo>(other.getActiveKeysRef());
+            inactiveKeys = new std::list<KeyInfo>(other.getInactiveKeysRef());
+            break;
+        default:
+            throw 353;
     }
 }
 
-Shared::Info::Info(Info &&other):
-type(other.type),
-address(other.address),
-vcard(other.vcard),
-activeKeys(other.activeKeys),
-inactiveKeys(other.inactiveKeys) {
+Shared::Info::Info (Info&& other):
+    type(other.type),
+    address(other.address),
+    vcard(other.vcard),
+    activeKeys(other.activeKeys),
+    inactiveKeys(other.inactiveKeys)
+{
     other.type = EntryType::none;
 }
 
-Shared::Info &Shared::Info::operator=(Info &&other) {
+Shared::Info& Shared::Info::operator = (Info&& other) {
     type = other.type;
     address = other.address;
     vcard = other.vcard;
     activeKeys = other.activeKeys;
     inactiveKeys = other.inactiveKeys;
-
     other.type = EntryType::none;
-
     return *this;
 }
 
-Shared::Info &Shared::Info::operator=(const Info &other) {
+Shared::Info& Shared::Info::operator = (const Info& other) {
     type = other.type;
     address = other.address;
-
     switch (type) {
-    case EntryType::none:
-        break;
-    case EntryType::contact:
-    case EntryType::ownAccount:
-        vcard = new VCard(other.getVCardRef());
-        activeKeys = new std::list<KeyInfo>(other.getActiveKeysRef());
-        inactiveKeys = new std::list<KeyInfo>(other.getInactiveKeysRef());
-        break;
-    default:
-        throw 351;
+        case EntryType::none:
+            break;
+        case EntryType::contact:
+        case EntryType::ownAccount:
+            vcard = new VCard(other.getVCardRef());
+            activeKeys = new std::list<KeyInfo>(other.getActiveKeysRef());
+            inactiveKeys = new std::list<KeyInfo>(other.getInactiveKeysRef());
+            break;
+        default:
+            throw 351;
     }
-
     return *this;
 }
 
-Shared::Info::~Info() {
+Shared::Info::~Info ()
+{
     turnIntoNone();
 }
 
-void Shared::Info::turnIntoNone() {
+void Shared::Info::turnIntoNone () {
     switch (type) {
-    case EntryType::none:
-        break;
-    case EntryType::contact:
-    case EntryType::ownAccount:
-        delete vcard;
-        vcard = nullptr;
-        delete activeKeys;
-        activeKeys = nullptr;
-        delete inactiveKeys;
-        inactiveKeys = nullptr;
-        break;
-    default:
-        break;
+        case EntryType::none:
+            break;
+        case EntryType::contact:
+        case EntryType::ownAccount:
+            delete vcard;
+            vcard = nullptr;
+            delete activeKeys;
+            activeKeys = nullptr;
+            delete inactiveKeys;
+            inactiveKeys = nullptr;
+            break;
+        default:
+            break;
     }
     type = EntryType::none;
 }
 
-void Shared::Info::turnIntoContact(
-    const Shared::VCard &crd, const std::list<KeyInfo> &aks, const std::list<KeyInfo> &iaks
-) {
+void Shared::Info::turnIntoContact (const Shared::VCard& crd, const std::list<KeyInfo>& aks, const std::list<KeyInfo>& iaks) {
     switch (type) {
-    case EntryType::none:
-        vcard = new VCard(crd);
-        activeKeys = new std::list<KeyInfo>(aks);
-        inactiveKeys = new std::list<KeyInfo>(iaks);
-        break;
-    case EntryType::contact:
-    case EntryType::ownAccount:
-        *vcard = crd;
-        *activeKeys = aks;
-        *inactiveKeys = iaks;
-        break;
-    default:
-        break;
+        case EntryType::none:
+            vcard = new VCard(crd);
+            activeKeys = new std::list<KeyInfo>(aks);
+            inactiveKeys = new std::list<KeyInfo>(iaks);
+            break;
+        case EntryType::contact:
+        case EntryType::ownAccount:
+            *vcard = crd;
+            *activeKeys = aks;
+            *inactiveKeys = iaks;
+            break;
+        default:
+            break;
     }
-
     type = EntryType::contact;
 }
 
-void Shared::Info::turnIntoContact(Shared::VCard *crd, std::list<KeyInfo> *aks, std::list<KeyInfo> *iaks) {
+void Shared::Info::turnIntoContact (Shared::VCard* crd, std::list<KeyInfo>* aks, std::list<KeyInfo>* iaks) {
     switch (type) {
-    case EntryType::contact:
-    case EntryType::ownAccount:
-        delete vcard;
-        delete activeKeys;
-        delete inactiveKeys;
-        [[fallthrough]];
-    case EntryType::none:
-        vcard = crd;
-        activeKeys = aks;
-        inactiveKeys = iaks;
-        break;
-    default:
-        break;
+        case EntryType::contact:
+        case EntryType::ownAccount:
+            delete vcard;
+            delete activeKeys;
+            delete inactiveKeys;
+            [[fallthrough]];
+        case EntryType::none:
+            vcard = crd;
+            activeKeys = aks;
+            inactiveKeys = iaks;
+            break;
+        default:
+            break;
     }
-
     type = EntryType::contact;
 }
 
-void Shared::Info::turnIntoOwnAccount(
-    const Shared::VCard &crd, const std::list<KeyInfo> &aks, const std::list<KeyInfo> &iaks
-) {
+void Shared::Info::turnIntoOwnAccount (const Shared::VCard& crd, const std::list<KeyInfo>& aks, const std::list<KeyInfo>& iaks) {
     switch (type) {
-    case EntryType::none:
-        vcard = new VCard(crd);
-        activeKeys = new std::list<KeyInfo>(aks);
-        inactiveKeys = new std::list<KeyInfo>(iaks);
-        break;
-    case EntryType::contact:
-    case EntryType::ownAccount:
-        *vcard = crd;
-        *activeKeys = aks;
-        *inactiveKeys = iaks;
-        break;
-    default:
-        break;
+        case EntryType::none:
+            vcard = new VCard(crd);
+            activeKeys = new std::list<KeyInfo>(aks);
+            inactiveKeys = new std::list<KeyInfo>(iaks);
+            break;
+        case EntryType::contact:
+        case EntryType::ownAccount:
+            *vcard = crd;
+            *activeKeys = aks;
+            *inactiveKeys = iaks;
+            break;
+        default:
+            break;
     }
-
     type = EntryType::ownAccount;
 }
 
-void Shared::Info::turnIntoOwnAccount(Shared::VCard *crd, std::list<KeyInfo> *aks, std::list<KeyInfo> *iaks) {
+void Shared::Info::turnIntoOwnAccount (Shared::VCard* crd, std::list<KeyInfo>* aks, std::list<KeyInfo>* iaks) {
     switch (type) {
-    case EntryType::contact:
-    case EntryType::ownAccount:
-        delete vcard;
-        delete activeKeys;
-        delete inactiveKeys;
-        [[fallthrough]];
-    case EntryType::none:
-        vcard = crd;
-        activeKeys = aks;
-        inactiveKeys = iaks;
-        break;
-    default:
-        break;
+        case EntryType::contact:
+        case EntryType::ownAccount:
+            delete vcard;
+            delete activeKeys;
+            delete inactiveKeys;
+            [[fallthrough]];
+        case EntryType::none:
+            vcard = crd;
+            activeKeys = aks;
+            inactiveKeys = iaks;
+            break;
+        default:
+            break;
     }
-
     type = EntryType::ownAccount;
 }
 
-void Shared::Info::setAddress(const QString &addr) {
+void Shared::Info::setAddress (const QString& addr) {
     address = addr;
 }
 
-QString Shared::Info::getAddress() const {
+QString Shared::Info::getAddress () const {
     return address;
 }
 
-const QString &Shared::Info::getAddressRef() const {
+const QString& Shared::Info::getAddressRef () const {
     return address;
 }
 
-Shared::EntryType Shared::Info::getType() const {
+Shared::EntryType Shared::Info::getType () const {
     return type;
 }
 
-std::list<Shared::KeyInfo> &Shared::Info::getActiveKeysRef() {
+std::list<Shared::KeyInfo>& Shared::Info::getActiveKeysRef () {
     switch (type) {
-    case EntryType::contact:
-    case EntryType::ownAccount:
-        return *activeKeys;
-        break;
-    default:
-        throw 354;
+        case EntryType::contact:
+        case EntryType::ownAccount:
+            return *activeKeys;
+            break;
+        default:
+            throw 354;
     }
 }
 
-const std::list<Shared::KeyInfo> &Shared::Info::getActiveKeysRef() const {
+const std::list<Shared::KeyInfo>& Shared::Info::getActiveKeysRef () const {
     switch (type) {
-    case EntryType::contact:
-    case EntryType::ownAccount:
-        return *activeKeys;
-        break;
-    default:
-        throw 355;
+        case EntryType::contact:
+        case EntryType::ownAccount:
+            return *activeKeys;
+            break;
+        default:
+            throw 355;
     }
 }
 
-std::list<Shared::KeyInfo> *Shared::Info::getActiveKeys() {
+std::list<Shared::KeyInfo>* Shared::Info::getActiveKeys () {
     switch (type) {
-    case EntryType::contact:
-    case EntryType::ownAccount:
-        return activeKeys;
-        break;
-    default:
-        throw 356;
+        case EntryType::contact:
+        case EntryType::ownAccount:
+            return activeKeys;
+            break;
+        default:
+            throw 356;
     }
 }
 
-const std::list<Shared::KeyInfo> *Shared::Info::getActiveKeys() const {
+const std::list<Shared::KeyInfo>* Shared::Info::getActiveKeys () const {
     switch (type) {
-    case EntryType::contact:
-    case EntryType::ownAccount:
-        return activeKeys;
-        break;
-    default:
-        throw 357;
+        case EntryType::contact:
+        case EntryType::ownAccount:
+            return activeKeys;
+            break;
+        default:
+            throw 357;
     }
 }
 
-std::list<Shared::KeyInfo> &Shared::Info::getInactiveKeysRef() {
+std::list<Shared::KeyInfo>& Shared::Info::getInactiveKeysRef () {
     switch (type) {
-    case EntryType::contact:
-    case EntryType::ownAccount:
-        return *inactiveKeys;
-        break;
-    default:
-        throw 358;
+        case EntryType::contact:
+        case EntryType::ownAccount:
+            return *inactiveKeys;
+            break;
+        default:
+            throw 358;
     }
 }
 
-const std::list<Shared::KeyInfo> &Shared::Info::getInactiveKeysRef() const {
+const std::list<Shared::KeyInfo>& Shared::Info::getInactiveKeysRef () const {
     switch (type) {
-    case EntryType::contact:
-    case EntryType::ownAccount:
-        return *inactiveKeys;
-        break;
-    default:
-        throw 359;
+        case EntryType::contact:
+        case EntryType::ownAccount:
+            return *inactiveKeys;
+            break;
+        default:
+            throw 359;
     }
 }
 
-std::list<Shared::KeyInfo> *Shared::Info::getInactiveKeys() {
+std::list<Shared::KeyInfo>* Shared::Info::getInactiveKeys () {
     switch (type) {
-    case EntryType::contact:
-    case EntryType::ownAccount:
-        return inactiveKeys;
-        break;
-    default:
-        throw 360;
+        case EntryType::contact:
+        case EntryType::ownAccount:
+            return inactiveKeys;
+            break;
+        default:
+            throw 360;
     }
 }
 
-const std::list<Shared::KeyInfo> *Shared::Info::getInactiveKeys() const {
+const std::list<Shared::KeyInfo>* Shared::Info::getInactiveKeys () const {
     switch (type) {
-    case EntryType::contact:
-    case EntryType::ownAccount:
-        return inactiveKeys;
-        break;
-    default:
-        throw 361;
+        case EntryType::contact:
+        case EntryType::ownAccount:
+            return inactiveKeys;
+            break;
+        default:
+            throw 361;
     }
 }
 
-const Shared::VCard &Shared::Info::getVCardRef() const {
+const Shared::VCard& Shared::Info::getVCardRef () const {
     switch (type) {
-    case EntryType::contact:
-    case EntryType::ownAccount:
-        return *vcard;
-        break;
-    default:
-        throw 362;
+        case EntryType::contact:
+        case EntryType::ownAccount:
+            return *vcard;
+            break;
+        default:
+            throw 362;
     }
 }
 
-Shared::VCard &Shared::Info::getVCardRef() {
+Shared::VCard& Shared::Info::getVCardRef () {
     switch (type) {
-    case EntryType::contact:
-    case EntryType::ownAccount:
-        return *vcard;
-        break;
-    default:
-        throw 363;
+        case EntryType::contact:
+        case EntryType::ownAccount:
+            return *vcard;
+            break;
+        default:
+            throw 363;
     }
 }
 
-const Shared::VCard *Shared::Info::getVCard() const {
+const Shared::VCard* Shared::Info::getVCard () const {
     switch (type) {
-    case EntryType::contact:
-    case EntryType::ownAccount:
-        return vcard;
-        break;
-    default:
-        throw 364;
+        case EntryType::contact:
+        case EntryType::ownAccount:
+            return vcard;
+            break;
+        default:
+            throw 364;
     }
 }
 
-Shared::VCard *Shared::Info::getVCard() {
+Shared::VCard* Shared::Info::getVCard () {
     switch (type) {
-    case EntryType::contact:
-    case EntryType::ownAccount:
-        return vcard;
-        break;
-    default:
-        throw 365;
+        case EntryType::contact:
+        case EntryType::ownAccount:
+            return vcard;
+            break;
+        default:
+            throw 365;
     }
 }
 
-void Shared::Info::setActiveKeys(std::list<KeyInfo> *keys) {
+void Shared::Info::setActiveKeys (std::list<KeyInfo>* keys) {
     switch (type) {
-    case EntryType::contact:
-    case EntryType::ownAccount:
-        activeKeys = keys;
-        break;
-    default:
-        throw 366;
+        case EntryType::contact:
+        case EntryType::ownAccount:
+            activeKeys = keys;
+            break;
+        default:
+            throw 366;
     }
 }
 
-void Shared::Info::setVCard(Shared::VCard *card) {
+void Shared::Info::setVCard (Shared::VCard* card) {
     switch (type) {
-    case EntryType::contact:
-    case EntryType::ownAccount:
-        vcard = card;
-        break;
-    default:
-        throw 367;
+        case EntryType::contact:
+        case EntryType::ownAccount:
+            vcard = card;
+            break;
+        default:
+            throw 367;
     }
 }
diff --git a/shared/info.h b/shared/info.h
index d5a48b1..90feadb 100644
--- a/shared/info.h
+++ b/shared/info.h
@@ -25,71 +25,49 @@
 #include <list>
 
 namespace Shared {
-
 /**
  * This class should contain all nessesary data to display
  * roster element info (contact, or out of roster contact, or MUC, or MIX in the future)
  *
  * under development yet
  */
-class Info {
+class Info : public QObject, public VCard {
 public:
-    Info();
-    Info(const QString& address, EntryType = EntryType::none);
-    Info(const Info& other);
-    Info(Info&& other);
-    virtual ~Info();
+    Info ();
+    Info (const QString& address, EntryType = EntryType::none);
+    Info (const Info& other);
+    Info (Info&& other);
+    virtual ~Info ();
+
     Info& operator = (const Info& other);
     Info& operator = (Info&& other);
-
-    QString getAddress() const;
-    const QString& getAddressRef() const;
-    void setAddress(const QString& address);
-
-    EntryType getType() const;
-    void turnIntoNone();
-    void turnIntoContact(
-        const VCard& card = VCard(),
-        const std::list<KeyInfo>& activeKeys = {},
-        const std::list<KeyInfo>& inactiveKeys = {}
-    );
-    void turnIntoContact(
-        VCard* card = new VCard,
-        std::list<KeyInfo>* activeKeys = new std::list<KeyInfo>,
-        std::list<KeyInfo>* inactiveKeys = new std::list<KeyInfo>
-    );
-    void turnIntoOwnAccount(
-        const VCard& card = VCard(),
-        const std::list<KeyInfo>& activeKeys = {},
-        const std::list<KeyInfo>& inactiveKeys = {}
-    );
-    void turnIntoOwnAccount(
-        VCard* card = new VCard,
-        std::list<KeyInfo>* activeKeys = new std::list<KeyInfo>,
-        std::list<KeyInfo>* inactiveKeys = new std::list<KeyInfo>
-    );
-
-    const VCard& getVCardRef() const;
-    VCard& getVCardRef();
-    const VCard* getVCard() const;
-    VCard* getVCard();
-    void setVCard(Shared::VCard* card);
-
-    const std::list<KeyInfo>& getActiveKeysRef() const;
-    std::list<KeyInfo>& getActiveKeysRef();
-    const std::list<KeyInfo>* getActiveKeys() const;
-    std::list<KeyInfo>* getActiveKeys();
-    void setActiveKeys(std::list<KeyInfo>* keys);
-
-    const std::list<KeyInfo>& getInactiveKeysRef() const;
-    std::list<KeyInfo>& getInactiveKeysRef();
-    const std::list<KeyInfo>* getInactiveKeys() const;
-    std::list<KeyInfo>* getInactiveKeys();
+    QString getAddress () const;
+    const QString& getAddressRef () const;
+    void setAddress (const QString& address);
+    EntryType getType () const;
+    void turnIntoNone ();
+    void turnIntoContact (const VCard& card = VCard(), const std::list<KeyInfo>& activeKeys = {}, const std::list<KeyInfo>& inactiveKeys = {});
+    void turnIntoContact (VCard* card = new VCard, std::list<KeyInfo>* activeKeys = new std::list<KeyInfo>, std::list<KeyInfo>* inactiveKeys = new std::list<KeyInfo>);
+    void turnIntoOwnAccount (const VCard& card = VCard(), const std::list<KeyInfo>& activeKeys = {}, const std::list<KeyInfo>& inactiveKeys = {});
+    void turnIntoOwnAccount (VCard* card = new VCard, std::list<KeyInfo>* activeKeys = new std::list<KeyInfo>, std::list<KeyInfo>* inactiveKeys = new std::list<KeyInfo>);
+    const VCard& getVCardRef () const;
+    VCard& getVCardRef ();
+    const VCard* getVCard () const;
+    VCard* getVCard ();
+    void setVCard (Shared::VCard* card);
+    const std::list<KeyInfo>& getActiveKeysRef () const;
+    std::list<KeyInfo>& getActiveKeysRef ();
+    const std::list<KeyInfo>* getActiveKeys () const;
+    std::list<KeyInfo>* getActiveKeys ();
+    void setActiveKeys (std::list<KeyInfo>* keys);
+    const std::list<KeyInfo>& getInactiveKeysRef () const;
+    std::list<KeyInfo>& getInactiveKeysRef ();
+    const std::list<KeyInfo>* getInactiveKeys () const;
+    std::list<KeyInfo>* getInactiveKeys ();
 
 private:
     EntryType type;
     QString address;
-
     VCard* vcard;
     std::list<KeyInfo>* activeKeys;
     std::list<KeyInfo>* inactiveKeys;

From 0be264884958228af5294a27ab09680880ba294a Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Wed, 31 Jan 2024 20:22:49 -0300
Subject: [PATCH 263/281] Now avatars are properly autogenerated, reduced vCard
 spam

---
 core/account.cpp                | 17 ++++----
 core/conference.cpp             | 70 +++++++++++++--------------------
 core/conference.h               | 23 ++++++-----
 core/contact.cpp                |  7 ++--
 core/handlers/rosterhandler.cpp | 36 ++++++++---------
 core/rosteritem.h               |  5 +--
 external/lmdbal                 |  2 +-
 external/qxmpp                  |  2 +-
 ui/models/roster.cpp            |  6 +--
 9 files changed, 76 insertions(+), 92 deletions(-)

diff --git a/core/account.cpp b/core/account.cpp
index 858c177..8082aeb 100644
--- a/core/account.cpp
+++ b/core/account.cpp
@@ -367,19 +367,23 @@ void Core::Account::onPresenceReceived(const QXmppPresence& p_presence) {
         vh->handlePresenceOfMyAccountChange(p_presence);
     } else {
         RosterItem* item = rh->getRosterItem(jid);
-        if (item != nullptr)
-            item->handlePresence(p_presence);
+        if (item != nullptr) {
+            if (item->isMuc())  //MUC presence is handled by inner muc events
+                return;
+            else
+                item->handlePresence(p_presence);
+        }
     }
     
     switch (p_presence.type()) {
         case QXmppPresence::Error:
             qDebug() << "An error reported by presence from" << id << p_presence.error().text();
             break;
-        case QXmppPresence::Available:{
+        case QXmppPresence::Available: {
             QDateTime lastInteraction = p_presence.lastUserInteraction();
-            if (!lastInteraction.isValid()) {
+            if (!lastInteraction.isValid())
                 lastInteraction = QDateTime::currentDateTimeUtc();
-            }
+
             emit addPresence(jid, resource, {
                 {"lastActivity", lastInteraction},
                 {"availability", p_presence.availableStatusType()},           //TODO check and handle invisible
@@ -392,8 +396,7 @@ void Core::Account::onPresenceReceived(const QXmppPresence& p_presence) {
                     )
                 }
             });
-        }
-        break;
+        } break;
         case QXmppPresence::Unavailable:
             emit removePresence(jid, resource);
             break;
diff --git a/core/conference.cpp b/core/conference.cpp
index 3904675..48331e9 100644
--- a/core/conference.cpp
+++ b/core/conference.cpp
@@ -132,9 +132,8 @@ void Core::Conference::onRoomParticipantAdded(const QString& p_name) {
     
     if (resource.size() > 0) {
         QDateTime lastInteraction = pres.lastUserInteraction();
-        if (!lastInteraction.isValid()) {
+        if (!lastInteraction.isValid())
             lastInteraction = QDateTime::currentDateTimeUtc();
-        }
         
         QMap<QString, QVariant> cData = {
             {"lastActivity", lastInteraction},
@@ -153,29 +152,37 @@ void Core::Conference::onRoomParticipantAdded(const QString& p_name) {
         careAboutAvatar(hasAvatar, itr->second, cData, resource, p_name);
         
         emit addParticipant(resource, cData);
+
+        if (!hasAvatar) //  because this way vCard is already requested, no need to handle possible avatar update
+            return;
     }
-    
+    handlePossibleAvatarUpdate(pres, resource, hasAvatar, itr->second);
+}
+
+void Core::Conference::handlePossibleAvatarUpdate (
+    const QXmppPresence& pres,
+    const QString& resource,
+    bool hasAvatar,
+    const Archive::AvatarInfo& info
+) {
     switch (pres.vCardUpdateType()) {
         case QXmppPresence::VCardUpdateNone:            //this presence has nothing to do with photo
             break;
         case QXmppPresence::VCardUpdateNotReady:        //let's say the photo didn't change here
             break;
-        case QXmppPresence::VCardUpdateNoPhoto: {       //there is no photo, need to drop if any
-            if (!hasAvatar || !itr->second.autogenerated) {
+        case QXmppPresence::VCardUpdateNoPhoto:         //there is no photo, need to drop if any
+            if (!hasAvatar || !info.autogenerated)
                 setAutoGeneratedAvatar(resource);
-            }
-        }         
-        break;
-        case QXmppPresence::VCardUpdateValidPhoto:{     //there is a photo, need to load
-            if (hasAvatar) {
-                if (itr->second.autogenerated || itr->second.hash != pres.photoHash())
-                    emit requestVCard(p_name);
 
+            break;
+        case QXmppPresence::VCardUpdateValidPhoto:      //there is a photo, need to load
+            if (hasAvatar) {
+                if (info.autogenerated || info.hash != pres.photoHash())
+                    emit requestVCard(pres.from());
             } else {
-                emit requestVCard(p_name);
+                emit requestVCard(pres.from());
             }
             break;
-        }      
     }
 }
 
@@ -235,32 +242,10 @@ void Core::Conference::handlePresence(const QXmppPresence& pres) {
     QString resource("");
     if (comps.size() > 1)
         resource = comps.back();
-    
-    switch (pres.vCardUpdateType()) {
-        case QXmppPresence::VCardUpdateNone:            //this presence has nothing to do with photo
-            break;
-        case QXmppPresence::VCardUpdateNotReady:        //let's say the photo didn't change here
-            break;
-        case QXmppPresence::VCardUpdateNoPhoto: {       //there is no photo, need to drop if any
-            Archive::AvatarInfo info;
-            bool hasAvatar = readAvatarInfo(info, resource);
-            if (!hasAvatar || !info.autogenerated) {
-                setAutoGeneratedAvatar(resource);
-            }
-        }         
-            break;
-        case QXmppPresence::VCardUpdateValidPhoto:{     //there is a photo, need to load
-            Archive::AvatarInfo info;
-            bool hasAvatar = readAvatarInfo(info, resource);
-            if (hasAvatar) {
-                if (info.autogenerated || info.hash != pres.photoHash())
-                    emit requestVCard(id);
-            } else {
-                emit requestVCard(id);
-            }
-            break;
-        }      
-    }
+
+    Archive::AvatarInfo info;
+    bool hasAvatar = readAvatarInfo(info, resource);
+    handlePossibleAvatarUpdate(pres, resource, hasAvatar, info);
 }
 
 bool Core::Conference::setAutoGeneratedAvatar(const QString& resource) {
@@ -272,6 +257,7 @@ bool Core::Conference::setAutoGeneratedAvatar(const QString& resource) {
             exParticipants.insert(std::make_pair(resource, newInfo));
         else
             itr->second = newInfo;
+
         emit changeParticipant(resource, {
             {"avatarState", static_cast<uint>(Shared::Avatar::autocreated)},
             {"avatarPath", avatarPath(resource) + "." + newInfo.type}
@@ -313,18 +299,18 @@ bool Core::Conference::setAvatar(const QByteArray& data, Archive::AvatarInfo& in
 
 void Core::Conference::handleResponseVCard(const QXmppVCardIq& card, const QString &resource, Shared::VCard& out) {
     RosterItem::handleResponseVCard(card, resource, out);
-    if (resource.size() > 0) {
+    if (resource.size() > 0)
         emit changeParticipant(resource, {
             {"avatarState", static_cast<uint>(out.getAvatarType())},
             {"avatarPath", out.getAvatarPath()}
         });
-    }
 }
 
 QMap<QString, QVariant> Core::Conference::getAllAvatars() const {
     QMap<QString, QVariant> result;
     for (const std::pair<const QString, Archive::AvatarInfo>& pair : exParticipants)
         result.insert(pair.first, avatarPath(pair.first) + "." + pair.second.type);
+
     return result;
 }
 
diff --git a/core/conference.h b/core/conference.h
index 3c077e3..501738b 100644
--- a/core/conference.h
+++ b/core/conference.h
@@ -16,8 +16,7 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#ifndef CORE_CONFERENCE_H
-#define CORE_CONFERENCE_H
+#pragma once
 
 #include <QDir>
 
@@ -29,14 +28,8 @@
 #include <shared/global.h>
 #include <shared/clientid.h>
 
-namespace Core
-{
-
-/**
- * @todo write docs
- */
-class Conference : public RosterItem
-{
+namespace Core {
+class Conference : public RosterItem {
     Q_OBJECT
 public:
     Conference(const QString& p_jid, const QString& p_account, bool p_autoJoin, const QString& p_name, const QString& p_nick, QXmppMucRoom* p_room);
@@ -69,6 +62,14 @@ signals:
 protected:
     bool setAvatar(const QByteArray &data, Archive::AvatarInfo& info, const QString &resource = "") override;
     
+private:
+    void handlePossibleAvatarUpdate(
+        const QXmppPresence& pres,
+        const QString& resource,
+        bool hasAvatar,
+        const Archive::AvatarInfo& info
+    );
+
 private:
     QString nick;
     QXmppMucRoom* room;
@@ -91,5 +92,3 @@ private slots:
 };
 
 }
-
-#endif // CORE_CONFERENCE_H
diff --git a/core/contact.cpp b/core/contact.cpp
index f18dae3..aef637b 100644
--- a/core/contact.cpp
+++ b/core/contact.cpp
@@ -73,18 +73,17 @@ void Core::Contact::handlePresence(const QXmppPresence& pres) {
         case QXmppPresence::VCardUpdateNoPhoto: {       //there is no photo, need to drop if any
             Archive::AvatarInfo info;
             bool hasAvatar = readAvatarInfo(info);
-            if (!hasAvatar || !info.autogenerated) {
+            if (!hasAvatar || !info.autogenerated)
                 setAutoGeneratedAvatar();
-            }
+
         }         
         break;
         case QXmppPresence::VCardUpdateValidPhoto:{     //there is a photo, need to load
             Archive::AvatarInfo info;
             bool hasAvatar = readAvatarInfo(info);
             if (hasAvatar) {
-                if (info.autogenerated || info.hash != pres.photoHash()) {
+                if (info.autogenerated || info.hash != pres.photoHash())
                     emit requestVCard(jid);
-                }
             } else {
                 emit requestVCard(jid);
             }
diff --git a/core/handlers/rosterhandler.cpp b/core/handlers/rosterhandler.cpp
index 940ddbf..e3020c8 100644
--- a/core/handlers/rosterhandler.cpp
+++ b/core/handlers/rosterhandler.cpp
@@ -98,13 +98,13 @@ void Core::RosterHandler::addedAccount(const QString& jid) {
     contact->setName(re.name());
     
     if (newContact) {
+        handleNewContact(contact);
         QMap<QString, QVariant> cData = contact->getInfo();
 #if (QXMPP_VERSION) >= QT_VERSION_CHECK(1, 5, 0)
         cData.insert("trust", QVariant::fromValue(acc->th->getSummary(jid)));
 #endif
         int grCount = 0;
-        for (QSet<QString>::const_iterator itr = gr.begin(), end = gr.end(); itr != end; ++itr) {
-            const QString& groupName = *itr;
+        for (const QString& groupName : gr) {
             addToGroup(jid, groupName);
             emit acc->addContact(jid, groupName, cData);
             grCount++;
@@ -117,7 +117,6 @@ void Core::RosterHandler::addedAccount(const QString& jid) {
             acc->dm->requestInfo(jid);
             //acc->dm->requestItems(jid);
         }
-        handleNewContact(contact);
     }
 }
 
@@ -495,22 +494,23 @@ void Core::RosterHandler::removeContactFromGroupRequest(const QString& jid, cons
     if (itr == contacts.end()) {
         qDebug()    << "An attempt to remove non existing contact" << lcJid << "of account" 
                     << acc->name << "from the group" << groupName << ", skipping";
+        return;
+    }
+
+    QXmppRosterIq::Item item = acc->rm->getRosterEntry(lcJid);
+    QSet<QString> groups = item.groups();
+    QSet<QString>::const_iterator gItr = groups.find(groupName);
+    if (gItr != groups.end()) {
+        groups.erase(gItr);
+        item.setGroups(groups);
+
+        QXmppRosterIq iq;
+        iq.setType(QXmppIq::Set);
+        iq.addItem(item);
+        acc->client.sendPacket(iq);
     } else {
-        QXmppRosterIq::Item item = acc->rm->getRosterEntry(lcJid);
-        QSet<QString> groups = item.groups();
-        QSet<QString>::const_iterator gItr = groups.find(groupName);
-        if (gItr != groups.end()) {
-            groups.erase(gItr);
-            item.setGroups(groups);
-            
-            QXmppRosterIq iq;
-            iq.setType(QXmppIq::Set);
-            iq.addItem(item);
-            acc->client.sendPacket(iq);
-        } else {
-            qDebug()    << "An attempt to remove contact" << lcJid << "of account" 
-                        << acc->name << "from the group" << groupName << "but it's not in that group, skipping";
-        }
+        qDebug()    << "An attempt to remove contact" << lcJid << "of account"
+                    << acc->name << "from the group" << groupName << "but it's not in that group, skipping";
     }
 }
 
diff --git a/core/rosteritem.h b/core/rosteritem.h
index 203ed88..33a08f0 100644
--- a/core/rosteritem.h
+++ b/core/rosteritem.h
@@ -16,8 +16,7 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#ifndef CORE_ROSTERITEM_H
-#define CORE_ROSTERITEM_H
+#pragma once
 
 #include <QObject>
 #include <QString>
@@ -130,5 +129,3 @@ private:
 };
 
 }
-
-#endif // CORE_ROSTERITEM_H
diff --git a/external/lmdbal b/external/lmdbal
index c83369f..79240aa 160000
--- a/external/lmdbal
+++ b/external/lmdbal
@@ -1 +1 @@
-Subproject commit c83369f34761e7a053d62312bd07fe5b3db3a519
+Subproject commit 79240aa5354e9fa9d09d3b12ea1077a81bb90809
diff --git a/external/qxmpp b/external/qxmpp
index 8dda3c9..0cd7379 160000
--- a/external/qxmpp
+++ b/external/qxmpp
@@ -1 +1 @@
-Subproject commit 8dda3c9921c34b9e33883b0d140e8de12edc0736
+Subproject commit 0cd7379bd78aa01af7e84f2fad6269ef0c0ba49c
diff --git a/ui/models/roster.cpp b/ui/models/roster.cpp
index 0f35baa..fc6382a 100644
--- a/ui/models/roster.cpp
+++ b/ui/models/roster.cpp
@@ -650,10 +650,10 @@ void Models::Roster::onChildRemoved() {
 void Models::Roster::addPresence(const QString& account, const QString& jid, const QString& name, const QMap<QString, QVariant>& data) {
     ElId contactId(account, jid);
     std::map<ElId, Contact*>::iterator itr = contacts.find(contactId);
-    if (itr != contacts.end()) {
+    if (itr != contacts.end())
         itr->second->addPresence(name, data);
-    }
-
+    else
+        qDebug() << "Received a presence" << jid + "/" + name << "don't know what to do with it";
 }
 
 void Models::Roster::removePresence(const QString& account, const QString& jid, const QString& name) {

From 829777935fb31351486c79f72574a41d0be91323 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Thu, 1 Feb 2024 13:10:52 -0300
Subject: [PATCH 264/281] fixes for bundled LMDBAL build

---
 core/components/archive.h | 7 +++----
 external/lmdbal           | 2 +-
 2 files changed, 4 insertions(+), 5 deletions(-)

diff --git a/core/components/archive.h b/core/components/archive.h
index 6a2be73..67208b4 100644
--- a/core/components/archive.h
+++ b/core/components/archive.h
@@ -27,12 +27,11 @@
 #include "shared/enums.h"
 #include "shared/message.h"
 #include "shared/exception.h"
-#include <lmdb.h>
 #include <list>
 
-#include <lmdbal/base.h>
-#include <lmdbal/storage.h>
-#include <lmdbal/cursor.h>
+#include <base.h>
+#include <storage.h>
+#include <cursor.h>
 
 namespace Core {
 
diff --git a/external/lmdbal b/external/lmdbal
index 79240aa..d62eddc 160000
--- a/external/lmdbal
+++ b/external/lmdbal
@@ -1 +1 @@
-Subproject commit 79240aa5354e9fa9d09d3b12ea1077a81bb90809
+Subproject commit d62eddc47edbec9f8c071459e045578f61ab58df

From acd60eaba2e7a8156635360a8ca81ffd3ba90929 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Sun, 4 Feb 2024 09:44:19 -0300
Subject: [PATCH 265/281] Fixing build without omemo, release preparation,
 unnecessary inheritance removed, info widget fix

---
 .gitea/workflows/release.yml  | 45 +++++++++++++++++++++++++++++++++++
 CHANGELOG.md                  |  4 +++-
 CMakeLists.txt                |  2 +-
 core/account.h                | 12 ++++++----
 core/delayManager/info.cpp    |  3 +++
 core/delayManager/manager.cpp |  4 ++++
 core/delayManager/manager.h   |  6 +++++
 core/handlers/CMakeLists.txt  | 12 +++++-----
 packaging/Archlinux/PKGBUILD  |  6 ++---
 shared/global.cpp             |  2 +-
 shared/info.h                 |  2 +-
 ui/widgets/about.cpp          | 20 +++++++---------
 ui/widgets/about.h            | 14 +++--------
 13 files changed, 92 insertions(+), 40 deletions(-)
 create mode 100644 .gitea/workflows/release.yml

diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml
new file mode 100644
index 0000000..f8bbef7
--- /dev/null
+++ b/.gitea/workflows/release.yml
@@ -0,0 +1,45 @@
+name: Squawk Release workflow
+run-name: ${{ gitea.actor }} is running Squawk Release workflow on release ${{ gitea.event.release.tag_name }}
+on:
+  release:
+    types: [published]
+
+jobs:
+  Archlinux:
+    runs-on: archlinux
+    steps:
+      - name: Download the release tarball
+        run: curl -sL ${{ gitea.server_url }}/${{ gitea.repository }}/archive/${{ gitea.event.release.tag_name }}.tar.gz --output tarball.tar.gz
+
+      - name: Calculate SHA256 for the tarball
+        run: echo "tbSum=$(sha256sum tarball.tar.gz | cut -d ' ' -f 1)" >> $GITHUB_ENV
+
+      - name: Unarchive tarball
+        run: tar -xvzf tarball.tar.gz
+
+      - name: Clone the AUR repository
+        run: |
+          echo "${{ secrets.DEPLOY_TO_AUR_PRIVATE_KEY }}" > key
+          chmod 600 key
+          GIT_SSH_COMMAND="ssh -i key -o 'IdentitiesOnly yes' -o 'StrictHostKeyChecking no'" git clone ssh://aur@aur.archlinux.org/squawk.git aur
+          chmod 777 -R aur
+          cd aur
+          git config user.name ${{ secrets.DEPLOY_TO_AUR_USER_NAME }}
+          git config user.email ${{ secrets.DEPLOY_TO_AUR_EMAIL }}
+
+
+      - name: Copy PKGBUILD to the directory
+        run: cp squawk/packaging/Archlinux/PKGBUILD aur/
+
+      - name: Put SHA256 sum to PKGBUILD file, and generate .SRCINFO
+        working-directory: aur
+        run: |
+          sed -i "/sha256sums=/c\sha256sums=('${{ env.tbSum }}')" PKGBUILD
+          sudo -u build makepkg --printsrcinfo > .SRCINFO
+
+      - name: Commit package to aur
+        working-directory: aur
+        run: |
+          git add PKGBUILD .SRCINFO
+          git commit -m "${{ gitea.event.release.body }}"
+          GIT_SSH_COMMAND="ssh -i ../key -o 'IdentitiesOnly yes' -o 'StrictHostKeyChecking no'" git push
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f554982..0c11404 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,9 +1,10 @@
 # Changelog
 
-## Squawk 0.2.3 (UNRELEASED)
+## Squawk 0.2.3 (February 04, 2024)
 ### Bug fixes
 - "Add contact" and "Join conference" menu are enabled once again (pavavno)!
 - availability is now read from the same section of config file it was stored
+- automatic avatars (if a contact doesn't have one) get generated once again
 
 ### Improvements
 - deactivated accounts now don't appear in combobox of "Add contact" and "Join conference" dialogues
@@ -11,6 +12,7 @@
 - settings file on the disk is not rewritten every roster element expansion or collapse
 - removed unnecessary own vcard request at sturtup (used to do it to discover my own avatar)
 - vcard window now is Info system and it can display more information
+- reduced vcard request spam in MUCs
 
 ### New features
 - now you can enable tray icon from settings!
diff --git a/CMakeLists.txt b/CMakeLists.txt
index edf9297..e2465cf 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -33,7 +33,7 @@ option(SYSTEM_LMDBAL "Use system lmdbal lib" ON)
 option(WITH_KWALLET "Build KWallet support module" ON)
 option(WITH_KIO "Build KIO support module" ON)
 option(WITH_KCONFIG "Build KConfig support module" ON)
-option(WITH_OMEMO "Build OMEMO support module" ON)
+option(WITH_OMEMO "Build OMEMO support module" OFF)   #it should be off by default untill I sourt the problems out
 
 # Dependencies
 ## Qt
diff --git a/core/account.h b/core/account.h
index ea1a13d..4cd3316 100644
--- a/core/account.h
+++ b/core/account.h
@@ -60,11 +60,13 @@
 #include "handlers/vcardhandler.h"
 #include "handlers/discoveryhandler.h"
 
-#ifdef WITH_OMEMO
-#include <QXmppOmemoManager.h>
-#include <QXmppTrustManager.h>
-#include "handlers/trusthandler.h"
-#include "handlers/omemohandler.h"
+#if (QXMPP_VERSION) >= QT_VERSION_CHECK(1, 5, 0)
+    #include <QXmppTrustManager.h>
+    #ifdef WITH_OMEMO
+        #include <QXmppOmemoManager.h>
+        #include "handlers/omemohandler.h"
+    #endif
+    #include "handlers/trusthandler.h"
 #endif
 
 namespace Core {
diff --git a/core/delayManager/info.cpp b/core/delayManager/info.cpp
index 9953fd4..1b8da8d 100644
--- a/core/delayManager/info.cpp
+++ b/core/delayManager/info.cpp
@@ -45,7 +45,10 @@ void Core::DelayManager::Info::receivedVCard(const Shared::VCard& card) {
         throw 245;
 
     info = new Shared::VCard(card);
+
+#ifdef WITH_OMEMO
     stage = Stage::waitingForBundles;
+#endif
 }
 
 Shared::VCard * Core::DelayManager::Info::claim() {
diff --git a/core/delayManager/manager.cpp b/core/delayManager/manager.cpp
index de37d47..efafaf5 100644
--- a/core/delayManager/manager.cpp
+++ b/core/delayManager/manager.cpp
@@ -242,7 +242,9 @@ void Core::DelayManager::Manager::jobIsCanceled(Job* job, bool wasRunning) {
                     emit gotVCard(jb->jid, Shared::VCard());
                     break;
                 case InfoForUser::Stage::waitingForBundles:
+#ifdef WITH_OMEMO
                     requestedBundles.erase(jb->jid);
+#endif
                     break;
                 default:
                     break;
@@ -356,6 +358,7 @@ void Core::DelayManager::Manager::receivedOwnVCard(const Shared::VCard& card) {
     }
 }
 
+#ifdef WITH_OMEMO
 void Core::DelayManager::Manager::receivedBundles(const QString& jid, const std::list<Shared::KeyInfo>& keys) {
     std::map<QString, Job::Id>::const_iterator itr = requestedBundles.find(jid);
     if (itr == requestedBundles.end()) {
@@ -397,6 +400,7 @@ void Core::DelayManager::Manager::receivedOwnBundles(const std::list<Shared::Key
     emit gotOwnInfo(info);
     jobIsDone(jobId);
 }
+#endif
 
 void Core::DelayManager::Manager::setOwnJid(const QString& jid) {
     ownJid = jid;
diff --git a/core/delayManager/manager.h b/core/delayManager/manager.h
index 5d04a8e..8b32139 100644
--- a/core/delayManager/manager.h
+++ b/core/delayManager/manager.h
@@ -56,8 +56,11 @@ public slots:
 signals:
     void requestVCard(const QString& jid);
     void requestOwnVCard();
+
+#ifdef WITH_OMEMO
     void requestBundles(const QString& jid);
     void requestOwnBundles();
+#endif
 
     void gotVCard(const QString& jid, const Shared::VCard& info);
     void gotOwnVCard(const Shared::VCard& info);
@@ -68,8 +71,11 @@ public slots:
     void disconnected();
     void receivedOwnVCard(const Shared::VCard& card);
     void receivedVCard(const QString& jid, const Shared::VCard& card);
+
+#ifdef WITH_OMEMO
     void receivedBundles(const QString& jid, const std::list<Shared::KeyInfo>& keys);
     void receivedOwnBundles(const std::list<Shared::KeyInfo>& keys);
+#endif
 
 private:
     void preScheduleJob(Job* job);
diff --git a/core/handlers/CMakeLists.txt b/core/handlers/CMakeLists.txt
index 11a902d..ed359bd 100644
--- a/core/handlers/CMakeLists.txt
+++ b/core/handlers/CMakeLists.txt
@@ -3,7 +3,6 @@ set(SOURCE_FILES
   rosterhandler.cpp
   vcardhandler.cpp
   discoveryhandler.cpp
-  omemohandler.cpp
   trusthandler.cpp
 )
 
@@ -12,11 +11,12 @@ set(HEADER_FILES
   rosterhandler.h
   vcardhandler.h
   discoveryhandler.h
-  omemohandler.h
   trusthandler.h
 )
 
-target_sources(squawk PRIVATE
-    ${SOURCE_FILES}
-    ${HEADER_FILES}
-)
+if(WITH_OMEMO)
+  list(APPEND SOURCE_FILES omemohandler.cpp)
+  list(APPEND HEADER_FILES omemohandler.h)
+endif()
+
+target_sources(squawk PRIVATE ${SOURCE_FILES})
diff --git a/packaging/Archlinux/PKGBUILD b/packaging/Archlinux/PKGBUILD
index bd9c981..53347dc 100644
--- a/packaging/Archlinux/PKGBUILD
+++ b/packaging/Archlinux/PKGBUILD
@@ -6,15 +6,15 @@ pkgdesc="An XMPP desktop messenger, written on pure c++ (qt)"
 arch=('i686' 'x86_64')
 url="https://git.macaw.me/blue/squawk"
 license=('GPL3')
-depends=('hicolor-icon-theme' 'desktop-file-utils' 'lmdbal' 'qxmpp>=1.1.0')
+depends=('hicolor-icon-theme' 'desktop-file-utils' 'lmdbal' 'qxmpp-qt5')
 makedepends=('cmake>=3.3' 'imagemagick' 'qt5-tools' 'boost')
 optdepends=('kwallet: secure password storage (requires rebuild)'
             'kconfig: system themes support (requires rebuild)'
             'kconfigwidgets: system themes support (requires rebuild)'
             'kio: better show in folder action (requires rebuild)')
 
-source=("$pkgname-$pkgver.tar.gz")
-sha256sums=('e4fa2174a3ba95159cc3b0bac3f00550c9e0ce971c55334e2662696a4543fc7e')
+source=("$pkgname-$pkgver.tar.gz::https://git.macaw.me/blue/$pkgname/archive/$pkgver.tar.gz")
+sha256sums=('SKIP')
 build() {
         cd "$srcdir/squawk"
         cmake . -D CMAKE_INSTALL_PREFIX=/usr -D CMAKE_BUILD_TYPE=Release
diff --git a/shared/global.cpp b/shared/global.cpp
index 97f3263..6618426 100644
--- a/shared/global.cpp
+++ b/shared/global.cpp
@@ -24,7 +24,7 @@
 #ifdef WITH_OMEMO
 constexpr bool OMEMO_SUPPORT = true;
 #else
-constexpr bool OMEMO_SUPPORT = false
+constexpr bool OMEMO_SUPPORT = false;
 #endif
 
 QFont getFont (QFontDatabase::SystemFont type, bool bold = false, bool italic = false, qreal factor = 1.0) {
diff --git a/shared/info.h b/shared/info.h
index 90feadb..ae34ef2 100644
--- a/shared/info.h
+++ b/shared/info.h
@@ -31,7 +31,7 @@ namespace Shared {
  *
  * under development yet
  */
-class Info : public QObject, public VCard {
+class Info {
 public:
     Info ();
     Info (const QString& address, EntryType = EntryType::none);
diff --git a/ui/widgets/about.cpp b/ui/widgets/about.cpp
index f46c661..ca1bfa2 100644
--- a/ui/widgets/about.cpp
+++ b/ui/widgets/about.cpp
@@ -19,13 +19,13 @@
 #include <QXmppGlobal.h>
 #include <QDebug>
 
-#if (QXMPP_VERSION) < QT_VERSION_CHECK(1, 3, 0)
-static const std::string QXMPP_VERSION_PATCH(std::to_string(QXMPP_VERSION & 0xff));
-static const std::string QXMPP_VERSION_MINOR(std::to_string((QXMPP_VERSION & 0xff00) >> 8));
-static const std::string QXMPP_VERSION_MAJOR(std::to_string(QXMPP_VERSION >> 16));
-static const QString QXMPP_VERSION_STRING = QString::fromStdString(QXMPP_VERSION_MAJOR + "." + QXMPP_VERSION_MINOR + "." + QXMPP_VERSION_PATCH);
+#if (QXMPP_VERSION) < QT_VERSION_CHECK(1, 2, 0)
+static const std::string _QXMPP_PATCH_(std::to_string(QXMPP_VERSION & 0xff));
+static const std::string _QXMPP_MINOR_(std::to_string((QXMPP_VERSION & 0xff00) >> 8));
+static const std::string _QXMPP_MAJOR_(std::to_string(QXMPP_VERSION >> 16));
+static const QString SQUAWK_INNER_QXMPP_VERSION_STRING = QString::fromStdString(_QXMPP_MAJOR_ + "." + _QXMPP_MINOR_ + "." + _QXMPP_PATCH_);
 #else
-static const QString QXMPP_VERSION_STRING = QXmppVersion();
+static const QString SQUAWK_INNER_QXMPP_VERSION_STRING = QXmppVersion();
 #endif
 
 About::About(QWidget* parent):
@@ -39,7 +39,7 @@ About::About(QWidget* parent):
     m_ui->qtBuiltAgainstVersion->setText(tr("(built against %1)").arg(QT_VERSION_STR));
 
     m_ui->qxmppVersionValue->setText(QXmppVersion());
-    m_ui->qxmppBuiltAgainstVersion->setText(tr("(built against %1)").arg(QXMPP_VERSION_STRING));
+    m_ui->qxmppBuiltAgainstVersion->setText(tr("(built against %1)").arg(SQUAWK_INNER_QXMPP_VERSION_STRING));
 
     setWindowFlag(Qt::Tool);
 
@@ -52,8 +52,7 @@ About::~About() {
     }
 };
 
-void About::onLicenseActivated()
-{
+void About::onLicenseActivated() {
     if (license == nullptr) {
         QFile file;
         bool found = false;
@@ -106,7 +105,6 @@ void About::onLicenseActivated()
     license->show();
 }
 
-void About::onLicenseClosed()
-{
+void About::onLicenseClosed() {
     license = nullptr;
 }
diff --git a/ui/widgets/about.h b/ui/widgets/about.h
index 1506b7f..35df9f4 100644
--- a/ui/widgets/about.h
+++ b/ui/widgets/about.h
@@ -14,8 +14,7 @@
 // You should have received a copy of the GNU General Public License
 // along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
-#ifndef ABOUT_H
-#define ABOUT_H
+#pragma once
 
 #include <QWidget>
 #include <QScopedPointer>
@@ -24,16 +23,11 @@
 #include <QTextStream>
 #include <QStandardPaths>
 
-namespace Ui
-{
+namespace Ui{
 class About;
 }
 
-/**
- * @todo write docs
- */
-class About : public QWidget
-{
+class About : public QWidget {
     Q_OBJECT
 public:
     About(QWidget* parent = nullptr);
@@ -47,5 +41,3 @@ private:
     QScopedPointer<Ui::About> m_ui;
     QWidget* license;
 };
-
-#endif // ABOUT_H

From 8d82d340a4fa5394a97bab107e0797d5095970d2 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Sun, 4 Feb 2024 13:36:51 -0300
Subject: [PATCH 266/281] 0.2.3 preparation, typo fix, readme changes

---
 CHANGELOG.md                 |  2 +-
 README.md                    | 73 +++++++++++++++++++++---------------
 main/application.cpp         |  2 +-
 packaging/Archlinux/PKGBUILD |  8 ++--
 4 files changed, 48 insertions(+), 37 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0c11404..4ca1542 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -17,7 +17,7 @@
 ### New features
 - now you can enable tray icon from settings!
 - there is a job queue now, this allowes to spread a bit the spam on the server at connection time
-- squawk now querries clients of it's peers, you can see what programs other people use
+- squawk now queries clients of it's peers, you can see what programs other people use
 
 ## Squawk 0.2.2 (May 05, 2022)
 ### Bug fixes
diff --git a/README.md b/README.md
index 121b640..9c835b6 100644
--- a/README.md
+++ b/README.md
@@ -11,7 +11,7 @@
 - QT 5.12 *(lower versions might work but it wasn't tested)*
 - CMake 3.4 or higher
 - qxmpp 1.1.0 or higher
-- LMDBAL (my own [library](https://git.macaw.me/blue/lmdbal) around lmdb)
+- LMDBAL (my own [library](https://git.macaw.me/blue/lmdbal) for lmdb)
 - KDE Frameworks: kwallet (optional)
 - KDE Frameworks: KIO (optional)
 - KDE Frameworks: KConfig (optional)
@@ -33,14 +33,49 @@ $ pacaur -S squawk
 
 ### Building
 
-You can also clone the repo and build it from source
+You can also the repo and build it from source
 
 Squawk requires Qt with SSL enabled. It uses CMake as build system.
 
 Please check the prerequisites and install them before installation.
 
+---
+
+There are several ways to build Squawk. The one you need depends on whether you have `qxmpp` and `lmdbal` installed in your system.
+
+#### Building with system dependencies
+
+This is the easiest way but it requires you to have `qxmpp` and `lmdbal` installed as system packages. Here is what you do:
+
+```
+$ git clone https://git.macaw.me/blue/squawk
+$ cd squawk
+$ mkdir build
+$ cd build
+$ cmake ..
+$ cmake --build .
+```
+
+#### Building with bundled qxmpp
+
+If you don't have any of `qxmpp` or `lmdbal` (or both) installed the process is abit mor complicated.
+On the configuration stage you need to enable one or both entries in the square brackets, depending on what package your system lacks.
+
+Here is what you do
+
+```
+$ git clone --recurse-submodules https://git.macaw.me/blue/squawk
+$ cd squawk
+$ mkdir build
+$ cd build
+$ cmake .. [-D SYSTEM_QXMPP=False] [-D SYSTEM_LMDBAL=False]
+$ cmake --build .
+```
+
 #### For Windows (Mingw-w64) build
 
+**Building for windows is not mainteined, but was possible in the past, you can try, but it probably won't work**
+
 You need Qt for mingw64 (MinGW 64-bit) platform when installing Qt.
 
 The best way to acquire library `lmdb` and `boost` is through Msys2.
@@ -49,54 +84,30 @@ First install Msys2, and then install `mingw-w64-x86_64-lmdb` and `mingw-w64-x86
 
 Then you need to provide the cmake cache entry when calling cmake for configuration:
 
-```
-cmake .. -D LMDB_ROOT_DIR:PATH=<Msys2 Mingw64 Root Directory> -D BOOST_ROOT:PATH=<Msys2 Mingw64 Root Directory>
-```
-
 `<Msys2 Mingw64 Root Directory>`: e.g. `C:/msys64/mingw64`.
 
----
-
-There are two ways to build, it depends whether you have qxmpp installed in your system
-
-#### Building with system qxmpp
-
-Here is what you do
-
-```
-$ git clone https://git.macaw.me/blue/squawk
-$ cd squawk
-$ mkdir build
-$ cd build
-$ cmake .. [-D LMDB_ROOT_DIR:PATH=...] [-D BOOST_ROOT:PATH=...]
-$ cmake --build .
-```
-
-#### Building with bundled qxmpp
-
-Here is what you do
-
 ```
 $ git clone --recurse-submodules https://git.macaw.me/blue/squawk
 $ cd squawk
 $ mkdir build
 $ cd build
-$ cmake .. -D SYSTEM_QXMPP=False [-D LMDB_ROOT_DIR:PATH=...] [-D BOOST_ROOT:PATH=...]
+$ cmake .. -D SYSTEM_QXMPP=False -D SYSTEM_LMDBAL=False -D LMDB_ROOT_DIR:PATH=<Msys2 Mingw64 Root Directory> -D BOOST_ROOT:PATH=<Msys2 Mingw64 Root Directory>
 $ cmake --build .
 ```
 
-You can always refer to `appveyor.yml` to see how AppVeyor build squawk.
+You can always refer to `appveyor.yml` to see how AppVeyor build squawk for windows.
 
 ### List of keys
 
-Here is the list of keys you can pass to configuration phase of `cmake ..`. 
+Here is the list of keys you can pass to configuration phase of `cmake ..`:
+
 - `CMAKE_BUILD_TYPE` - `Debug` just builds showing all warnings, `Release` builds with no warnings and applies optimizations (default is `Debug`)
 - `SYSTEM_QXMPP` - `True` tries to link against `qxmpp` installed in the system, `False` builds bundled `qxmpp` library (default is `True`)
 - `SYSTEM_LMDBAL` - `True` tries to link against `LMDABL` installed in the system, `False` builds bundled `LMDBAL` library (default is `True`)
 - `WITH_KWALLET` - `True` builds the `KWallet` capability module if `KWallet` is installed and if not goes to `False`. `False` disables `KWallet` support (default is `True`)
 - `WITH_KIO` - `True` builds the `KIO` capability module if `KIO` is installed and if not goes to `False`. `False` disables `KIO` support (default is `True`)
 - `WITH_KCONFIG` - `True` builds the `KConfig` and `KConfigWidgets` capability module if such packages are installed and if not goes to `False`. `False` disables `KConfig` and `KConfigWidgets` support (default is `True`)
-- `WITH_OMEMO` - `True` builds the OMEMO encryption, requires `qxmpp` of version >= 1.5.0 built with OMEMO support. `False` disables OMEMO support (default is `True`)
+- `WITH_OMEMO` - `True` builds the OMEMO encryption, requires `qxmpp` of version >= 1.5.0 built with OMEMO support. `False` disables OMEMO support (default is `False`)
 
 ## License
 
diff --git a/main/application.cpp b/main/application.cpp
index d184e6b..15e9208 100644
--- a/main/application.cpp
+++ b/main/application.cpp
@@ -245,7 +245,7 @@ void Application::onChangeTray(bool enabled, bool hide) {
                 trayIcon = nullptr;
             }
         }
-    } else if (trayIcon == nullptr) {
+    } else if (trayIcon != nullptr) {
         trayIcon->deleteLater();
         trayIcon = nullptr;
     }
diff --git a/packaging/Archlinux/PKGBUILD b/packaging/Archlinux/PKGBUILD
index 53347dc..29ed800 100644
--- a/packaging/Archlinux/PKGBUILD
+++ b/packaging/Archlinux/PKGBUILD
@@ -8,10 +8,10 @@ url="https://git.macaw.me/blue/squawk"
 license=('GPL3')
 depends=('hicolor-icon-theme' 'desktop-file-utils' 'lmdbal' 'qxmpp-qt5')
 makedepends=('cmake>=3.3' 'imagemagick' 'qt5-tools' 'boost')
-optdepends=('kwallet: secure password storage (requires rebuild)'
-            'kconfig: system themes support (requires rebuild)'
-            'kconfigwidgets: system themes support (requires rebuild)'
-            'kio: better show in folder action (requires rebuild)')
+optdepends=('kwallet5: secure password storage (requires rebuild)'
+            'kconfig5: system themes support (requires rebuild)'
+            'kconfigwidgets5: system themes support (requires rebuild)'
+            'kio5: better show in folder action (requires rebuild)')
 
 source=("$pkgname-$pkgver.tar.gz::https://git.macaw.me/blue/$pkgname/archive/$pkgver.tar.gz")
 sha256sums=('SKIP')

From fb843a13463e6600f86d9dde5aa728cdcfa57259 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Sun, 4 Feb 2024 13:51:16 -0300
Subject: [PATCH 267/281] ci

---
 .gitea/workflows/release.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml
index f8bbef7..5d4fe58 100644
--- a/.gitea/workflows/release.yml
+++ b/.gitea/workflows/release.yml
@@ -41,5 +41,5 @@ jobs:
         working-directory: aur
         run: |
           git add PKGBUILD .SRCINFO
-          git commit -m "${{ gitea.event.release.body }}"
+          git commit -m "${{ gitea.event.release.body//\"/\\\" }}"
           GIT_SSH_COMMAND="ssh -i ../key -o 'IdentitiesOnly yes' -o 'StrictHostKeyChecking no'" git push

From 2c61b82924747a0b7677a1f5605c3cb064f884ea Mon Sep 17 00:00:00 2001
From: Benson Muite <benson_muite@emailplus.org>
Date: Sun, 6 Oct 2024 19:26:44 +0300
Subject: [PATCH 268/281] Add appdata file

---
 packaging/CMakeLists.txt              |  6 ++++-
 packaging/macaw.me.squawk.appdata.xml | 33 +++++++++++++++++++++++++++
 2 files changed, 38 insertions(+), 1 deletion(-)
 create mode 100644 packaging/macaw.me.squawk.appdata.xml

diff --git a/packaging/CMakeLists.txt b/packaging/CMakeLists.txt
index 4965b37..4bb9c87 100644
--- a/packaging/CMakeLists.txt
+++ b/packaging/CMakeLists.txt
@@ -1,3 +1,7 @@
 configure_file(squawk.desktop squawk.desktop COPYONLY)
 
-install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk.desktop DESTINATION ${CMAKE_INSTALL_DATADIR}/applications)
\ No newline at end of file
+install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk.desktop DESTINATION ${CMAKE_INSTALL_DATADIR}/applications)
+
+configure_file(macaw.me.squawk.appdata.xml macaw.me.squawk.appdata.xml COPYONLY)
+
+install(FILES ${CMAKE_CURRENT_BINARY_DIR}/macaw.me.squawk.appdata.xml DESTINATION ${CMAKE_INSTALL_DATADIR}/metainfo)
diff --git a/packaging/macaw.me.squawk.appdata.xml b/packaging/macaw.me.squawk.appdata.xml
new file mode 100644
index 0000000..c188496
--- /dev/null
+++ b/packaging/macaw.me.squawk.appdata.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<component xmlns:xi="http://www.w3.org/2001/XInclude" xmlns:its="http://www.w3.org/2005/11/its" xmlns="https://specifications.freedesktop.org/metainfo/1.0" type="desktop-application">
+  <id>macaw.me.squawk</id>
+  <metadata_license>CC0-1.0</metadata_license>
+  <project_license>GPL-3.0+</project_license>
+  <name>Squawk</name>
+  <summary>Desktop Qt based XMPP messenger</summary>
+  <description>
+    <p>
+      Squawk is a lightweight XMPP desktop messenger.
+      The primary objective of this project is to offer
+      you a fast and user-friendly messaging experience
+      that closely aligns with your system’s style, while
+      also minimizing resource consumption.
+    </p>
+    <p>
+      Squawk is still at a very early stage and might not suit
+      everyone but you are welcome to try it out.
+    </p>
+  </description>
+  <launchable type="desktop-id">macaw.me.squawk.desktop</launchable>
+  <screenshots>
+    <screenshot type="default">
+      <image>https://macaw.me/projects/squawk/0.2.2.png</image>
+      <caption>View XMPP contacts and conversations</caption>
+    </screenshot>
+  </screenshots>
+  <url type="homepage">https://macaw.me/projects/squawk/</url>
+  <provides>
+    <binary>squawk</binary>
+  </provides>
+  <update_contact>blue@macaw.me</update_contact>
+</component>

From 030c374139e0c6fcab978bd4d4a125ddbb4dff19 Mon Sep 17 00:00:00 2001
From: Benson Muite <benson_muite@emailplus.org>
Date: Sun, 6 Oct 2024 19:29:38 +0300
Subject: [PATCH 269/281] Update image link

---
 README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/README.md b/README.md
index 9c835b6..655304f 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@
 [![AUR version](https://img.shields.io/aur/version/squawk?style=flat-square)](https://aur.archlinux.org/packages/squawk/)
 [![Liberapay patrons](https://img.shields.io/liberapay/patrons/macaw.me?logo=liberapay&style=flat-square)](https://liberapay.com/macaw.me)
 
-![Squawk screenshot](https://macaw.me/images/squawk/0.2.2.png)
+![Squawk screenshot](https://macaw.me/projects/squawk/0.2.2.png)
 
 ### Prerequisites
 

From 80838595413188936f95cfc8a2352824db595d8d Mon Sep 17 00:00:00 2001
From: Benson Muite <benson_muite@emailplus.org>
Date: Mon, 7 Oct 2024 13:45:15 +0300
Subject: [PATCH 270/281] Fix license text error

---
 external/simpleCrypt/simplecrypt.cpp | 2 +-
 external/simpleCrypt/simplecrypt.h   | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/external/simpleCrypt/simplecrypt.cpp b/external/simpleCrypt/simplecrypt.cpp
index 19a96be..093403e 100644
--- a/external/simpleCrypt/simplecrypt.cpp
+++ b/external/simpleCrypt/simplecrypt.cpp
@@ -19,7 +19,7 @@
  DISCLAIMED. IN NO EVENT SHALL ANDRE SOMERS BE LIABLE FOR ANY
  DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- LOSS OF USE, DATA, OR #######; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/external/simpleCrypt/simplecrypt.h b/external/simpleCrypt/simplecrypt.h
index d75bdc2..0052618 100644
--- a/external/simpleCrypt/simplecrypt.h
+++ b/external/simpleCrypt/simplecrypt.h
@@ -19,7 +19,7 @@
  DISCLAIMED. IN NO EVENT SHALL ANDRE SOMERS BE LIABLE FOR ANY
  DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- LOSS OF USE, DATA, OR #######; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

From ff9a591d6de95d75bfbe7ee0d99fad32887d2673 Mon Sep 17 00:00:00 2001
From: Benson Muite <benson_muite@emailplus.org>
Date: Sun, 6 Oct 2024 20:54:18 +0300
Subject: [PATCH 271/281] Private libraries directory

---
 core/passwordStorageEngines/wrappers/CMakeLists.txt | 2 +-
 plugins/CMakeLists.txt                              | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/core/passwordStorageEngines/wrappers/CMakeLists.txt b/core/passwordStorageEngines/wrappers/CMakeLists.txt
index e8420da..432079f 100644
--- a/core/passwordStorageEngines/wrappers/CMakeLists.txt
+++ b/core/passwordStorageEngines/wrappers/CMakeLists.txt
@@ -1,4 +1,4 @@
 add_library(kwalletWrapper SHARED kwallet.cpp)
 target_link_libraries(kwalletWrapper PRIVATE KF5::Wallet)
 
-install(TARGETS kwalletWrapper LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})
+install(TARGETS kwalletWrapper LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}/squawk)
diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt
index 388c258..565651e 100644
--- a/plugins/CMakeLists.txt
+++ b/plugins/CMakeLists.txt
@@ -2,7 +2,7 @@ if (WITH_KIO)
   add_library(openFileManagerWindowJob SHARED openfilemanagerwindowjob.cpp)
   target_link_libraries(openFileManagerWindowJob PRIVATE KF5::KIOWidgets)
 
-  install(TARGETS openFileManagerWindowJob LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})
+  install(TARGETS openFileManagerWindowJob LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}/squawk)
 endif ()
 
 if (WITH_KCONFIG)
@@ -10,5 +10,5 @@ if (WITH_KCONFIG)
   target_link_libraries(colorSchemeTools PRIVATE KF5::ConfigCore)
   target_link_libraries(colorSchemeTools PRIVATE KF5::ConfigWidgets)
 
-  install(TARGETS colorSchemeTools LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})
+  install(TARGETS colorSchemeTools LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}/squawk)
 endif()

From 3cc7db8eff99f8d414a8798f3272fd41a6ad66c8 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Sun, 27 Oct 2024 19:31:47 +0200
Subject: [PATCH 272/281] A workaround to store plugins in a subdirectory

---
 CMakeLists.txt                          | 7 ++++++-
 core/passwordStorageEngines/kwallet.cpp | 3 ++-
 core/squawk.h                           | 6 ++----
 shared/global.cpp                       | 4 ++--
 4 files changed, 12 insertions(+), 8 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index e2465cf..aa028e8 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.5)
+cmake_minimum_required(VERSION 3.16)
 project(squawk VERSION 0.2.3 LANGUAGES CXX)
 
 cmake_policy(SET CMP0076 NEW)
@@ -211,6 +211,11 @@ if(CMAKE_COMPILER_IS_GNUCXX)
   target_compile_options(squawk PRIVATE ${COMPILE_OPTIONS})
 endif(CMAKE_COMPILER_IS_GNUCXX)
 
+# I am not really sure about this solution
+# This should enable plugins to be found in path like /usr/lib/squawk instead of just /usr/lib
+set(PLUGIN_PATH "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}/squawk")
+add_compile_definitions(PLUGIN_PATH="${PLUGIN_PATH}")
+
 add_subdirectory(main)
 add_subdirectory(core)
 add_subdirectory(external/simpleCrypt)
diff --git a/core/passwordStorageEngines/kwallet.cpp b/core/passwordStorageEngines/kwallet.cpp
index 0dfe071..c92085b 100644
--- a/core/passwordStorageEngines/kwallet.cpp
+++ b/core/passwordStorageEngines/kwallet.cpp
@@ -28,7 +28,8 @@ Core::PSE::KWallet::CreateFolder Core::PSE::KWallet::createFolder = 0;
 Core::PSE::KWallet::SetFolder Core::PSE::KWallet::setFolder = 0;
 
 Core::PSE::KWallet::SupportState Core::PSE::KWallet::sState = Core::PSE::KWallet::initial;
-QLibrary Core::PSE::KWallet::lib("kwalletWrapper");
+
+QLibrary Core::PSE::KWallet::lib(QString("%1/kwalletWrapper").arg(PLUGIN_PATH));
 
 Core::PSE::KWallet::KWallet():
     QObject(),
diff --git a/core/squawk.h b/core/squawk.h
index 2ee122e..f00500e 100644
--- a/core/squawk.h
+++ b/core/squawk.h
@@ -42,10 +42,8 @@
 #include "passwordStorageEngines/kwallet.h"
 #endif
 
-namespace Core
-{
-class Squawk : public QObject
-{
+namespace Core {
+class Squawk : public QObject {
     Q_OBJECT
 
 public:
diff --git a/shared/global.cpp b/shared/global.cpp
index 6618426..8277ff5 100644
--- a/shared/global.cpp
+++ b/shared/global.cpp
@@ -50,12 +50,12 @@ Shared::Global* Shared::Global::instance = 0;
 const std::set<QString> Shared::Global::supportedImagesExts = {"png", "jpg", "webp", "jpeg", "gif", "svg"};
 
 #ifdef WITH_KIO
-QLibrary Shared::Global::openFileManagerWindowJob("openFileManagerWindowJob");
+QLibrary Shared::Global::openFileManagerWindowJob(QString("%1/openFileManagerWindowJob").arg(PLUGIN_PATH));
 Shared::Global::HighlightInFileManager Shared::Global::hfm = 0;
 #endif
 
 #ifdef WITH_KCONFIG
-QLibrary Shared::Global::colorSchemeTools("colorSchemeTools");
+QLibrary Shared::Global::colorSchemeTools(QString("%1/colorSchemeTools").arg(PLUGIN_PATH));
 Shared::Global::CreatePreview Shared::Global::createPreview = 0;
 Shared::Global::DeletePreview Shared::Global::deletePreview = 0;
 Shared::Global::ColorSchemeName Shared::Global::colorSchemeName = 0;

From 85ff6c25ba598695ebc0c40e8e889e7118c530f9 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Sun, 27 Oct 2024 20:02:34 +0200
Subject: [PATCH 273/281] find boos cmake new policy magick instead of convert
 rendering images

---
 CMakeLists.txt           |  3 ++-
 main/root.cpp            | 15 +++++++--------
 resources/CMakeLists.txt | 33 +++++++++++++++------------------
 3 files changed, 24 insertions(+), 27 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index aa028e8..7b00bfc 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -4,6 +4,7 @@ project(squawk VERSION 0.2.3 LANGUAGES CXX)
 cmake_policy(SET CMP0076 NEW)
 cmake_policy(SET CMP0077 NEW)
 cmake_policy(SET CMP0079 NEW)
+cmake_policy(SET CMP0167 NEW)
 set(CMAKE_CXX_STANDARD 17)
 
 set(QT_VERSION_MAJOR 5)
@@ -33,7 +34,7 @@ option(SYSTEM_LMDBAL "Use system lmdbal lib" ON)
 option(WITH_KWALLET "Build KWallet support module" ON)
 option(WITH_KIO "Build KIO support module" ON)
 option(WITH_KCONFIG "Build KConfig support module" ON)
-option(WITH_OMEMO "Build OMEMO support module" OFF)   #it should be off by default untill I sourt the problems out
+option(WITH_OMEMO "Build OMEMO support module" OFF)   #it should be off by default untill I sort the problems out
 
 # Dependencies
 ## Qt
diff --git a/main/root.cpp b/main/root.cpp
index d43ae2f..87d97bb 100644
--- a/main/root.cpp
+++ b/main/root.cpp
@@ -58,6 +58,7 @@ Root::~Root() {
         delete gui;
         if (core != nullptr)
             delete core;
+
         delete coreThread;
     }
     delete global;
@@ -72,13 +73,13 @@ void Root::initializeTranslation() {
     bool found = false;
     for (QString share : shares) {
         found = currentTranslator.load(QLocale(), QLatin1String("squawk"), ".", share + "/l10n");
-        if (found) {
+        if (found)
             break;
-        }
     }
-    if (!found) {
+
+    if (!found)
         currentTranslator.load(QLocale(), QLatin1String("squawk"), ".", QCoreApplication::applicationDirPath());
-    }
+
 
     installTranslator(&currentTranslator);
 }
@@ -94,18 +95,16 @@ bool Root::initializeSettings() {
     QVariant vs = settings.value("style");
     if (vs.isValid()) {
         QString style = vs.toString().toLower();
-        if (style != "system") {
+        if (style != "system")
             Shared::Global::setStyle(style);
-        }
     }
 
     if (Shared::Global::supported("colorSchemeTools")) {
         QVariant vt = settings.value("theme");
         if (vt.isValid()) {
             QString theme = vt.toString();
-            if (theme.toLower() != "system") {
+            if (theme.toLower() != "system")
                 Shared::Global::setTheme(theme);
-            }
         }
     }
 
diff --git a/resources/CMakeLists.txt b/resources/CMakeLists.txt
index 9288650..717abf2 100644
--- a/resources/CMakeLists.txt
+++ b/resources/CMakeLists.txt
@@ -3,11 +3,8 @@ target_sources(squawk PRIVATE resources.qrc)
 configure_file(images/logo.svg squawk.svg COPYONLY)
 configure_file(squawk.rc squawk.rc COPYONLY)
 
-if(WIN32)
-    set(CONVERT_BIN magick convert)
-else(WIN32)
-    set(CONVERT_BIN convert)
-endif(WIN32)
+set(CONVERT_BIN magick)
+
 execute_process(COMMAND ${CONVERT_BIN} -background none -size 48x48 squawk.svg squawk48.png WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
 execute_process(COMMAND ${CONVERT_BIN} -background none -size 64x64 squawk.svg squawk64.png WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
 execute_process(COMMAND ${CONVERT_BIN} -background none -size 128x128 squawk.svg squawk128.png WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
@@ -18,20 +15,20 @@ if (WIN32)
     set(SQUAWK_WIN_RC "${CMAKE_CURRENT_BINARY_DIR}/squawk.rc")
     set(SQUAWK_WIN_RC "${SQUAWK_WIN_RC}" PARENT_SCOPE)
     target_sources(squawk PRIVATE ${SQUAWK_WIN_RC})
-endif(WIN32)
+endif (WIN32)
 
 if (APPLE)
     file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/icns.iconset")
-    execute_process(COMMAND convert -background none -size 16x16 squawk.svg icns.iconset/icon_16x16.png WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
-    execute_process(COMMAND convert -background none -resize !32x32 squawk.svg "icns.iconset/icon_16x16@2x.png" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
-    execute_process(COMMAND convert -background none -resize !32x32 squawk.svg "icns.iconset/icon_32x32.png" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
-    execute_process(COMMAND convert -background none -resize !64x64 squawk.svg "icns.iconset/icon_32x32@2x.png" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
-    execute_process(COMMAND convert -background none -resize !128x128 squawk.svg "icns.iconset/icon_128x128.png" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
-    execute_process(COMMAND convert -background none -resize !256x256 squawk.svg "icns.iconset/icon_128x128@2x.png" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
-    execute_process(COMMAND convert -background none -resize !256x256 squawk.svg "icns.iconset/icon_256x256.png" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
-    execute_process(COMMAND convert -background none -resize !512x512 squawk.svg "icns.iconset/icon_256x256@2x.png" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
-    execute_process(COMMAND convert -background none -resize !512x512 squawk.svg "icns.iconset/icon_512x512.png" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
-    execute_process(COMMAND convert -background none -resize !1024x1024 squawk.svg "icns.iconset/icon_512x512@2x.png" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
+    execute_process(COMMAND ${CONVERT_BIN} -background none -size 16x16 squawk.svg icns.iconset/icon_16x16.png WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
+    execute_process(COMMAND ${CONVERT_BIN} -background none -resize !32x32 squawk.svg "icns.iconset/icon_16x16@2x.png" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
+    execute_process(COMMAND ${CONVERT_BIN} -background none -resize !32x32 squawk.svg "icns.iconset/icon_32x32.png" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
+    execute_process(COMMAND ${CONVERT_BIN} -background none -resize !64x64 squawk.svg "icns.iconset/icon_32x32@2x.png" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
+    execute_process(COMMAND ${CONVERT_BIN} -background none -resize !128x128 squawk.svg "icns.iconset/icon_128x128.png" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
+    execute_process(COMMAND ${CONVERT_BIN} -background none -resize !256x256 squawk.svg "icns.iconset/icon_128x128@2x.png" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
+    execute_process(COMMAND ${CONVERT_BIN} -background none -resize !256x256 squawk.svg "icns.iconset/icon_256x256.png" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
+    execute_process(COMMAND ${CONVERT_BIN} -background none -resize !512x512 squawk.svg "icns.iconset/icon_256x256@2x.png" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
+    execute_process(COMMAND ${CONVERT_BIN} -background none -resize !512x512 squawk.svg "icns.iconset/icon_512x512.png" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
+    execute_process(COMMAND ${CONVERT_BIN} -background none -resize !1024x1024 squawk.svg "icns.iconset/icon_512x512@2x.png" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
     execute_process(COMMAND iconutil -c icns "icns.iconset" -o "squawk.icns" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
     set(MACOSX_BUNDLE_ICON_FILE squawk.icns)
     set(MACOSX_BUNDLE_ICON_FILE ${MACOSX_BUNDLE_ICON_FILE} PARENT_SCOPE)
@@ -47,8 +44,8 @@ if (APPLE)
           MACOSX_BUNDLE_ICON_FILE "${MACOSX_BUNDLE_ICON_FILE}" # TODO
           MACOSX_BUNDLE_BUNDLE_NAME "Squawk"
           MACOSX_BUNDLE_INFO_PLIST ${CMAKE_SOURCE_DIR}/cmake/MacOSXBundleInfo.plist.in)
-      endif(APPLE)
-    endif()
+      endif (APPLE)
+    endif ()
 endif (APPLE)
 
 install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk.svg DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/scalable/apps)

From 9a44ae1fa59d09ae83a05149b4f16b4633ab28af Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Sun, 17 Nov 2024 20:25:33 +0200
Subject: [PATCH 274/281] SimpleCrypt password jamming is now optional

---
 CMakeLists.txt                  |  9 +++++--
 core/squawk.cpp                 | 22 ++++++++++++++---
 core/squawk.h                   |  1 -
 shared/global.cpp               | 44 ++++++++++++++++++---------------
 shared/global.h                 |  2 +-
 ui/widgets/accounts/account.cpp | 23 +++++++++--------
 6 files changed, 62 insertions(+), 39 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 7b00bfc..b537fe0 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -35,6 +35,7 @@ option(WITH_KWALLET "Build KWallet support module" ON)
 option(WITH_KIO "Build KIO support module" ON)
 option(WITH_KCONFIG "Build KConfig support module" ON)
 option(WITH_OMEMO "Build OMEMO support module" OFF)   #it should be off by default untill I sort the problems out
+option(WITH_SIMPLE_CRYPT "Builds with SimpleCrypt to obfuscate password" ON)
 
 # Dependencies
 ## Qt
@@ -176,13 +177,18 @@ target_link_libraries(squawk
   Qt${QT_VERSION_MAJOR}::Xml
   LMDBAL::LMDBAL
   QXmpp::QXmpp
-  simpleCrypt
 )
 
 if (WITH_OMEMO)
   target_link_libraries(squawk PRIVATE QXmpp::Omemo)
 endif ()
 
+if (WITH_SIMPLE_CRYPT)
+  target_compile_definitions(squawk PRIVATE WITH_SIMPLE_CRYPT)
+  add_subdirectory(external/simpleCrypt)
+  target_link_libraries(squawk PRIVATE simpleCrypt)
+endif ()
+
 ## Link thread libraries on Linux
 if(UNIX AND NOT APPLE)
   set(THREADS_PREFER_PTHREAD_FLAG ON)
@@ -219,7 +225,6 @@ add_compile_definitions(PLUGIN_PATH="${PLUGIN_PATH}")
 
 add_subdirectory(main)
 add_subdirectory(core)
-add_subdirectory(external/simpleCrypt)
 add_subdirectory(packaging)
 add_subdirectory(plugins)
 add_subdirectory(resources)
diff --git a/core/squawk.cpp b/core/squawk.cpp
index 1888487..5978651 100644
--- a/core/squawk.cpp
+++ b/core/squawk.cpp
@@ -22,6 +22,10 @@
 #include <QDir>
 #include <QStandardPaths>
 
+#ifdef WITH_SIMPLE_CRYPT
+#include "external/simpleCrypt/simplecrypt.h"
+#endif
+
 Core::Squawk::Squawk(QObject* parent):
     QObject(parent),
     accounts(),
@@ -71,7 +75,6 @@ void Core::Squawk::stop() {
         QSettings settings;
         settings.beginGroup("core");
         settings.beginWriteArray("accounts");
-        SimpleCrypt crypto(passwordHash);
         for (std::deque<Account*>::size_type i = 0; i < accounts.size(); ++i) {
             settings.setArrayIndex(i);
             Account* acc = accounts[i];
@@ -84,7 +87,13 @@ void Core::Squawk::stop() {
                     password = acc->getPassword();
                     break;
                 case Shared::AccountPassword::jammed:
-                    password = crypto.encryptToString(acc->getPassword());
+#ifdef WITH_SIMPLE_CRYPT2
+                    password = SimpleCrypt(passwordHash).encryptToString(acc->getPassword());
+#else
+                    qDebug() << "The password for account" << acc->getName() << "is set to be jammed, but Squawk was compiled without SimpleCrypt support";
+                    qDebug("Can not encode password, setting this account to always ask password mode");
+                    ap = Shared::AccountPassword::alwaysAsk;
+#endif
                     break;
                 default:
                     break;
@@ -697,17 +706,24 @@ void Core::Squawk::readSettings() {
                 settings.value("passwordType", static_cast<int>(Shared::AccountPassword::plain)).toInt()
             );
 
+        QString name = settings.value("name").toString();
         QString password = settings.value("password", "").toString();
         if (passwordType == Shared::AccountPassword::jammed) {
+#ifdef WITH_SIMPLE_CRYPT
             SimpleCrypt crypto(passwordHash);
             password = crypto.decryptToString(password);
+#else
+            qDebug() << "The password for account" << name << "is jammed, but Squawk was compiled without SimpleCrypt support";
+            qDebug("Can not decode password, setting this account to always ask password mode");
+            passwordType = Shared::AccountPassword::alwaysAsk;
+#endif
         }
 
         addAccount(
             settings.value("login").toString(), 
             settings.value("server").toString(), 
             password,
-            settings.value("name").toString(),
+            name,
             settings.value("resource").toString(),
             settings.value("active").toBool(),
             passwordType
diff --git a/core/squawk.h b/core/squawk.h
index f00500e..8586498 100644
--- a/core/squawk.h
+++ b/core/squawk.h
@@ -33,7 +33,6 @@
 #include "shared/global.h"
 #include "shared/info.h"
 #include "shared/clientinfo.h"
-#include "external/simpleCrypt/simplecrypt.h"
 
 #include <core/components/clientcache.h>
 #include <core/components/networkaccess.h>
diff --git a/shared/global.cpp b/shared/global.cpp
index 8277ff5..efa85cf 100644
--- a/shared/global.cpp
+++ b/shared/global.cpp
@@ -21,6 +21,13 @@
 
 #include "enums.h"
 #include "ui/models/roster.h"
+
+#ifdef WITH_SIMPLE_CRYPT
+#define SIMPLE_CRYPT_ENABLED true
+#else
+#define SIMPLE_CRYPT_ENABLED false
+#endif
+
 #ifdef WITH_OMEMO
 constexpr bool OMEMO_SUPPORT = true;
 #else
@@ -36,11 +43,10 @@ QFont getFont (QFontDatabase::SystemFont type, bool bold = false, bool italic =
 
     if (factor != 1.0) {
         float ps = font.pointSizeF();
-        if (ps != -1) {
+        if (ps != -1)
             font.setPointSizeF(ps * factor);
-        } else {
+        else
             font.setPointSize(font.pointSize() * factor);
-        }
     }
 
     return font;
@@ -148,10 +154,11 @@ Shared::Global::Global():
     smallFontMetrics(smallFont),
     headerFontMetrics(headerFont),
     titleFontMetrics(titleFont),
-    pluginSupport({
+    optionalFeatures({
         {"KWallet", false},
         {"openFileManagerWindowJob", false},
-        {"colorSchemeTools", false}
+        {"colorSchemeTools", false},
+        {"simpleCryptJammedPassword", SIMPLE_CRYPT_ENABLED}
     }),
     fileCache()
 {
@@ -197,8 +204,7 @@ Shared::Global::Global():
 
 
 static const QSize defaultIconFileInfoHeight(50, 50);
-Shared::Global::FileInfo Shared::Global::getFileInfo(const QString& path)
-{
+Shared::Global::FileInfo Shared::Global::getFileInfo(const QString& path) {
     std::map<QString, FileInfo>::const_iterator itr = instance->fileCache.find(path);
     if (itr == instance->fileCache.end()) {
         QMimeDatabase db;
@@ -275,17 +281,17 @@ QString Shared::Global::getName(EncryptionProtocol ep) {
 }
 
 void Shared::Global::setSupported(const QString& pluginName, bool support) {
-    std::map<QString, bool>::iterator itr = instance->pluginSupport.find(pluginName);
-    if (itr != instance->pluginSupport.end()) {
+    std::map<QString, bool>::iterator itr = instance->optionalFeatures.find(pluginName);
+    if (itr != instance->optionalFeatures.end()) {
         itr->second = support;
     }
 }
 
 bool Shared::Global::supported(const QString& pluginName) {
-    std::map<QString, bool>::iterator itr = instance->pluginSupport.find(pluginName);
-    if (itr != instance->pluginSupport.end()) {
+    std::map<QString, bool>::iterator itr = instance->optionalFeatures.find(pluginName);
+    if (itr != instance->optionalFeatures.end())
         return itr->second;
-    }
+
     return false;
 }
 
@@ -325,11 +331,10 @@ void Shared::Global::highlightInFileManager(const QString& path)
         QString output = proc.readLine().simplified();
         
         QString folder;
-        if (info.isDir()) {
+        if (info.isDir())
             folder = info.canonicalFilePath();
-        } else {
+        else
             folder = info.canonicalPath();
-        }
         
         if (output.contains(dolphinReg)) {
             //there is a bug on current (21.04.0) dolphin, it works correct only if you already have dolphin launched
@@ -389,20 +394,19 @@ void Shared::Global::setTheme(const QString& path) {
 }
 
 void Shared::Global::setStyle(const QString& style) {
-    if (style.toLower() == "system") {
+    if (style.toLower() == "system")
         QApplication::setStyle(getInstance()->defaultSystemStyle);
-    } else {
+    else
         QApplication::setStyle(style);
-    }
 }
 
 #define FROM_INT_INPL(Enum)                                                                 \
 template<>                                                                                  \
 Enum Shared::Global::fromInt(int src)                                                       \
 {                                                                                           \
-    if (src < static_cast<int>(Enum##Lowest) || src > static_cast<int>(Enum##Highest)) {    \
+    if (src < static_cast<int>(Enum##Lowest) || src > static_cast<int>(Enum##Highest))      \
         throw EnumOutOfRange(#Enum);                                                        \
-    }                                                                                       \
+                                                                                            \
     return static_cast<Enum>(src);                                                          \
 }                                                                                           \
 template<>                                                                                  \
diff --git a/shared/global.h b/shared/global.h
index 6d23c2f..627903f 100644
--- a/shared/global.h
+++ b/shared/global.h
@@ -135,7 +135,7 @@ namespace Shared {
     private:
         static Global* instance;
         
-        std::map<QString, bool> pluginSupport;
+        std::map<QString, bool> optionalFeatures;
         std::map<QString, FileInfo> fileCache;
         
 #ifdef WITH_KIO
diff --git a/ui/widgets/accounts/account.cpp b/ui/widgets/accounts/account.cpp
index 164af6c..2d70603 100644
--- a/ui/widgets/accounts/account.cpp
+++ b/ui/widgets/accounts/account.cpp
@@ -26,6 +26,7 @@ Account::Account():
     m_ui->setupUi(this);
     
     connect(m_ui->passwordType, qOverload<int>(&QComboBox::currentIndexChanged), this, &Account::onComboboxChange);
+    QStandardItemModel *model = static_cast<QStandardItemModel*>(m_ui->passwordType->model());
     
     for (int i = static_cast<int>(Shared::AccountPasswordLowest); i < static_cast<int>(Shared::AccountPasswordHighest) + 1; ++i) {
         Shared::AccountPassword ap = static_cast<Shared::AccountPassword>(i);
@@ -34,18 +35,19 @@ Account::Account():
     m_ui->passwordType->setCurrentIndex(static_cast<int>(Shared::AccountPassword::plain));
     
     if (!Shared::Global::supported("KWallet")) {
-        QStandardItemModel *model = static_cast<QStandardItemModel*>(m_ui->passwordType->model());
         QStandardItem *item = model->item(static_cast<int>(Shared::AccountPassword::kwallet));
         item->setFlags(item->flags() & ~Qt::ItemIsEnabled);
     }
+
+    if (!Shared::Global::supported("simpleCryptJammedPassword")) {
+        QStandardItem *item = model->item(static_cast<int>(Shared::AccountPassword::jammed));
+        item->setFlags(item->flags() & ~Qt::ItemIsEnabled);
+    }
 }
 
-Account::~Account()
-{
-}
+Account::~Account() {}
 
-QMap<QString, QVariant> Account::value() const
-{
+QMap<QString, QVariant> Account::value() const {
     QMap<QString, QVariant> map;
     map["login"] = m_ui->login->text();
     map["password"] = m_ui->password->text();
@@ -58,13 +60,11 @@ QMap<QString, QVariant> Account::value() const
     return map;
 }
 
-void Account::lockId()
-{
+void Account::lockId() {
     m_ui->name->setReadOnly(true);;
 }
 
-void Account::setData(const QMap<QString, QVariant>& data)
-{
+void Account::setData(const QMap<QString, QVariant>& data) {
     m_ui->login->setText(data.value("login").toString());
     m_ui->password->setText(data.value("password").toString());
     m_ui->server->setText(data.value("server").toString());
@@ -73,8 +73,7 @@ void Account::setData(const QMap<QString, QVariant>& data)
     m_ui->passwordType->setCurrentIndex(data.value("passwordType").toInt());
 }
 
-void Account::onComboboxChange(int index)
-{
+void Account::onComboboxChange(int index) {
     QString description = Shared::Global::getDescription(Shared::Global::fromInt<Shared::AccountPassword>(index));
     m_ui->comment->setText(description);
 }

From 3cce057545dd654be5e41c71ba091aac512e8c20 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Mon, 18 Nov 2024 22:43:46 +0200
Subject: [PATCH 275/281] fix: omitting from attribute not to wreck the stream
 fix: build without kwallet

---
 core/handlers/messagehandler.cpp | 2 +-
 core/squawk.cpp                  | 4 ++--
 core/squawk.h                    | 3 ++-
 3 files changed, 5 insertions(+), 4 deletions(-)

diff --git a/core/handlers/messagehandler.cpp b/core/handlers/messagehandler.cpp
index 094f671..ae6c695 100644
--- a/core/handlers/messagehandler.cpp
+++ b/core/handlers/messagehandler.cpp
@@ -431,7 +431,7 @@ QMap<QString, QVariant> Core::MessageHandler::getChanges(Shared::Message& data,
 }
 
 QXmppMessage Core::MessageHandler::createPacket(const Shared::Message& data, const QDateTime& time, const QString& originalId) const {
-    QXmppMessage msg(acc->getFullJid(), data.getTo(), data.getBody(), data.getThread());
+    QXmppMessage msg(QString(), data.getTo(), data.getBody(), data.getThread());
     QString id(data.getId());
 
     if (originalId.size() > 0)
diff --git a/core/squawk.cpp b/core/squawk.cpp
index 5978651..1d1abe5 100644
--- a/core/squawk.cpp
+++ b/core/squawk.cpp
@@ -33,10 +33,10 @@ Core::Squawk::Squawk(QObject* parent):
     state(Shared::Availability::offline),
     network(),
     isInitialized(false),
-    clientCache(),
 #ifdef WITH_KWALLET
-    kwallet()
+    kwallet(),
 #endif
+    clientCache()
 {
     connect(&network, &NetworkAccess::loadFileProgress, this, &Squawk::fileProgress);
     connect(&network, &NetworkAccess::loadFileError, this, &Squawk::fileError);
diff --git a/core/squawk.h b/core/squawk.h
index 8586498..983f5ab 100644
--- a/core/squawk.h
+++ b/core/squawk.h
@@ -137,11 +137,12 @@ private:
     Shared::Availability state;
     NetworkAccess network;
     bool isInitialized;
-    ClientCache clientCache;
 
 #ifdef WITH_KWALLET
     PSE::KWallet kwallet;
 #endif
+
+    ClientCache clientCache;
     
 private slots:
     void addAccount(

From a04693e39dec21c95afd147af7022788c0b537ec Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Tue, 19 Nov 2024 18:45:11 +0200
Subject: [PATCH 276/281] fix: build without KConfig is possible once more

---
 shared/defines.h  |  5 +----
 shared/global.cpp | 18 ++++++++++++++----
 2 files changed, 15 insertions(+), 8 deletions(-)

diff --git a/shared/defines.h b/shared/defines.h
index 227a714..8ead3f6 100644
--- a/shared/defines.h
+++ b/shared/defines.h
@@ -16,9 +16,6 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#ifndef SHARED_DEFINES_H
-#define SHARED_DEFINES_H
+#pragma once
 
 #define SHARED_UNUSED(x) (void)(x)
-
-#endif
diff --git a/shared/global.cpp b/shared/global.cpp
index efa85cf..362bf81 100644
--- a/shared/global.cpp
+++ b/shared/global.cpp
@@ -19,6 +19,7 @@
 #include "global.h"
 #include <QFontDatabase>
 
+#include "defines.h"
 #include "enums.h"
 #include "ui/models/roster.h"
 
@@ -361,27 +362,32 @@ void Shared::Global::highlightInFileManager(const QString& path)
 }
 
 QIcon Shared::Global::createThemePreview(const QString& path) {
+#ifdef WITH_KCONFIG
     if (supported("colorSchemeTools")) {
         QIcon* icon = createPreview(path);
         QIcon localIcon = *icon;
         deletePreview(icon);
         return localIcon;
-    } else {
-        return QIcon();
     }
+#endif
+
+    return QIcon();
 }
 
 QString Shared::Global::getColorSchemeName(const QString& path) {
+#ifdef WITH_KCONFIG
     if (supported("colorSchemeTools")) {
         QString res;
         colorSchemeName(path, res);
         return res;
-    } else {
-        return "";
     }
+#endif
+
+    return "";
 }
 
 void Shared::Global::setTheme(const QString& path) {
+#ifdef WITH_KCONFIG
     if (supported("colorSchemeTools")) {
         if (path.toLower() == "system") {
             QApplication::setPalette(getInstance()->defaultSystemPalette);
@@ -391,6 +397,10 @@ void Shared::Global::setTheme(const QString& path) {
             QApplication::setPalette(pallete);
         }
     }
+#else
+    SHARED_UNUSED(path);
+    qDebug("setTheme() was called, but this version of squawk was compiled without KConfig support, ignoring");
+#endif
 }
 
 void Shared::Global::setStyle(const QString& style) {

From d4cec645b5a6f7d6ef490b262c5a43e901013df6 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Sat, 14 Dec 2024 01:53:04 +0200
Subject: [PATCH 277/281] qt6 build

---
 CMakeLists.txt                                |  41 ++-
 core/CMakeLists.txt                           |   6 +-
 core/account.cpp                              |   1 +
 core/components/CMakeLists.txt                |   5 +-
 core/components/networkaccess.cpp             |   4 +-
 core/handlers/messagehandler.cpp              |   8 +-
 core/handlers/messagehandler.h                |   3 +-
 core/passwordStorageEngines/CMakeLists.txt    |   7 +-
 core/passwordStorageEngines/kwallet.h         |   2 +-
 .../wrappers/CMakeLists.txt                   |   3 +-
 .../wrappers/kwallet.cpp                      |   2 +-
 core/squawk.cpp                               |  26 +-
 core/utils/CMakeLists.txt                     |   9 +
 core/utils/jammer.cpp                         |  94 +++++++
 core/utils/jammer.h                           |  44 ++++
 external/simpleCrypt/CMakeLists.txt           |  10 -
 external/simpleCrypt/simplecrypt.cpp          | 248 ------------------
 external/simpleCrypt/simplecrypt.h            | 226 ----------------
 plugins/CMakeLists.txt                        |   6 +-
 plugins/colorschemetools.cpp                  |   6 +-
 shared/CMakeLists.txt                         |   5 +-
 shared/clientid.h                             |   6 +-
 shared/clientinfo.h                           |   6 +-
 shared/enums.h                                |   4 +-
 shared/global.cpp                             |  11 +-
 shared/messageinfo.cpp                        |  16 ++
 shared/messageinfo.h                          |   4 +
 ui/utils/progress.cpp                         |   2 +-
 ui/widgets/accounts/account.cpp               |   5 -
 ui/widgets/conversation.cpp                   |   4 +-
 ui/widgets/messageline/feedview.cpp           |  32 ++-
 ui/widgets/messageline/feedview.h             |  11 +-
 ui/widgets/messageline/messagedelegate.cpp    |   2 +-
 ui/widgets/settings/settingslist.cpp          |  35 +--
 ui/widgets/settings/settingslist.h            |  17 +-
 35 files changed, 279 insertions(+), 632 deletions(-)
 create mode 100644 core/utils/CMakeLists.txt
 create mode 100644 core/utils/jammer.cpp
 create mode 100644 core/utils/jammer.h
 delete mode 100644 external/simpleCrypt/CMakeLists.txt
 delete mode 100644 external/simpleCrypt/simplecrypt.cpp
 delete mode 100644 external/simpleCrypt/simplecrypt.h

diff --git a/CMakeLists.txt b/CMakeLists.txt
index b537fe0..a62c800 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -7,7 +7,6 @@ cmake_policy(SET CMP0079 NEW)
 cmake_policy(SET CMP0167 NEW)
 set(CMAKE_CXX_STANDARD 17)
 
-set(QT_VERSION_MAJOR 5)
 set(CMAKE_AUTOMOC ON)
 set(CMAKE_AUTOUIC ON)
 set(CMAKE_AUTORCC ON)
@@ -35,7 +34,6 @@ option(WITH_KWALLET "Build KWallet support module" ON)
 option(WITH_KIO "Build KIO support module" ON)
 option(WITH_KCONFIG "Build KConfig support module" ON)
 option(WITH_OMEMO "Build OMEMO support module" OFF)   #it should be off by default untill I sort the problems out
-option(WITH_SIMPLE_CRYPT "Builds with SimpleCrypt to obfuscate password" ON)
 
 # Dependencies
 ## Qt
@@ -69,9 +67,9 @@ endif ()
 
 ## KIO
 if (WITH_KIO)
-  find_package(KF5KIO CONFIG)
+  find_package(KF${QT_VERSION_MAJOR}KIO CONFIG)
 
-  if (NOT KF5KIO_FOUND)
+  if (NOT KF${QT_VERSION_MAJOR}KIO_FOUND)
     set(WITH_KIO OFF)
     message("KIO package wasn't found, KIO support modules wouldn't be built")
   else ()
@@ -82,9 +80,9 @@ endif ()
 
 ## KWallet
 if (WITH_KWALLET)
-  find_package(KF5Wallet CONFIG)
+  find_package(KF${QT_VERSION_MAJOR}Wallet CONFIG)
 
-  if (NOT KF5Wallet_FOUND)
+  if (NOT KF${QT_VERSION_MAJOR}Wallet_FOUND)
     set(WITH_KWALLET OFF)
     message("KWallet package wasn't found, KWallet support module wouldn't be built")
   else ()
@@ -95,13 +93,13 @@ endif ()
 
 ## KConfig
 if (WITH_KCONFIG)
-  find_package(KF5Config CONFIG)
-  if (NOT KF5Config_FOUND)
+  find_package(KF${QT_VERSION_MAJOR}Config CONFIG)
+  if (NOT KF${QT_VERSION_MAJOR}Config_FOUND)
     set(WITH_KCONFIG OFF)
     message("KConfig package wasn't found, KConfig support modules wouldn't be built")
   else()
-    find_package(KF5ConfigWidgets CONFIG)
-    if (NOT KF5ConfigWidgets_FOUND)
+    find_package(KF${QT_VERSION_MAJOR}ConfigWidgets CONFIG)
+    if (NOT KF${QT_VERSION_MAJOR}ConfigWidgets_FOUND)
       set(WITH_KCONFIG OFF)
       message("KConfigWidgets package wasn't found, KConfigWidgets support modules wouldn't be built")
     else()
@@ -115,12 +113,12 @@ endif()
 ## QXmpp
 if (SYSTEM_QXMPP)
   if (WITH_OMEMO)
-    find_package(QXmpp CONFIG COMPONENTS Omemo)
+    find_package(QXmppQt${QT_VERSION_MAJOR} CONFIG COMPONENTS Omemo)
   else ()
-    find_package(QXmpp CONFIG)
+    find_package(QXmppQt${QT_VERSION_MAJOR} CONFIG)
   endif ()
 
-  if (NOT QXmpp_FOUND)
+  if (NOT QXmppQt${QT_VERSION_MAJOR}_FOUND)
     set(SYSTEM_QXMPP OFF)
     message("QXmpp package wasn't found, trying to build with bundled QXmpp")
   else ()
@@ -152,8 +150,8 @@ endif ()
 
 ## LMDBAL
 if (SYSTEM_LMDBAL)
-  find_package(lmdbal)
-  if (NOT lmdbal_FOUND)
+  find_package(lmdbalqt${QT_VERSION_MAJOR})
+  if (NOT lmdbalqt${QT_VERSION_MAJOR}_FOUND)
     set(SYSTEM_LMDBAL OFF)
     message("LMDBAL package wasn't found, trying to build with bundled LMDBAL")
   else ()
@@ -163,9 +161,11 @@ else()
   message("Building with bundled LMDBAL")
   set(BUILD_STATIC ON)
   add_subdirectory(external/lmdbal)
-  add_library(LMDBAL::LMDBAL ALIAS LMDBAL)
+  add_library(LMDBALQT${QT_VERSION_MAJOR}::LMDBALQT${QT_VERSION_MAJOR} ALIAS LMDBAL)
 endif()
 
+find_package(OpenSSL REQUIRED)
+
 ## Linking
 target_link_libraries(squawk
   PRIVATE
@@ -175,7 +175,8 @@ target_link_libraries(squawk
   Qt${QT_VERSION_MAJOR}::Network
   Qt${QT_VERSION_MAJOR}::Gui
   Qt${QT_VERSION_MAJOR}::Xml
-  LMDBAL::LMDBAL
+  LMDBALQT${QT_VERSION_MAJOR}::LMDBALQT${QT_VERSION_MAJOR}
+  OpenSSL::Crypto
   QXmpp::QXmpp
 )
 
@@ -183,12 +184,6 @@ if (WITH_OMEMO)
   target_link_libraries(squawk PRIVATE QXmpp::Omemo)
 endif ()
 
-if (WITH_SIMPLE_CRYPT)
-  target_compile_definitions(squawk PRIVATE WITH_SIMPLE_CRYPT)
-  add_subdirectory(external/simpleCrypt)
-  target_link_libraries(squawk PRIVATE simpleCrypt)
-endif ()
-
 ## Link thread libraries on Linux
 if(UNIX AND NOT APPLE)
   set(THREADS_PREFER_PTHREAD_FLAG ON)
diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt
index a02bfe6..8baa5ad 100644
--- a/core/CMakeLists.txt
+++ b/core/CMakeLists.txt
@@ -23,10 +23,7 @@ set(HEADER_FILES
     squawk.h
 )
 
-target_sources(squawk PRIVATE
-    ${SOURCE_FILES}
-    ${HEADER_FILES}
-)
+target_sources(squawk PRIVATE ${SOURCE_FILES})
 
 target_include_directories(squawk PRIVATE ${LMDB_INCLUDE_DIRS})
 
@@ -34,3 +31,4 @@ add_subdirectory(handlers)
 add_subdirectory(passwordStorageEngines)
 add_subdirectory(components)
 add_subdirectory(delayManager)
+add_subdirectory(utils)
diff --git a/core/account.cpp b/core/account.cpp
index 8082aeb..6143fa6 100644
--- a/core/account.cpp
+++ b/core/account.cpp
@@ -81,6 +81,7 @@ Core::Account::Account(
     config.setDomain(p_server);
     config.setPassword(p_password);
     config.setAutoAcceptSubscriptions(true);
+    config.setIgnoreSslErrors(true);
     //config.setAutoReconnectionEnabled(false);
     delay = new DelayManager::Manager(getBareJid());
     QObject::connect(delay, &DelayManager::Manager::gotInfo, this, &Account::infoReady);
diff --git a/core/components/CMakeLists.txt b/core/components/CMakeLists.txt
index 77d290b..751a01f 100644
--- a/core/components/CMakeLists.txt
+++ b/core/components/CMakeLists.txt
@@ -12,7 +12,4 @@ set(HEADER_FILES
     archive.h
 )
 
-target_sources(squawk PRIVATE
-    ${SOURCE_FILES}
-    ${HEADER_FILES}
-)
+target_sources(squawk PRIVATE ${SOURCE_FILES})
diff --git a/core/components/networkaccess.cpp b/core/components/networkaccess.cpp
index 0771dfa..59e2448 100644
--- a/core/components/networkaccess.cpp
+++ b/core/components/networkaccess.cpp
@@ -550,10 +550,8 @@ void Core::NetworkAccess::moveFilesDirectory(const QString& newPath) {
     QDir dir(currentPath);
     bool success = true;
     qDebug() << "moving" << currentPath << "to" << newPath;
-    for (QFileInfo fileInfo : dir.entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot | QDir::Hidden | QDir::System)) {
-        QString fileName = fileInfo.fileName();
+    for (QString fileName : dir.entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot | QDir::Hidden | QDir::System))
         success = dir.rename(fileName, newPath + QDir::separator() + fileName) && success;
-    }
 
     if (!success)
         qDebug() << "couldn't move downloads directory, most probably downloads will be broken";
diff --git a/core/handlers/messagehandler.cpp b/core/handlers/messagehandler.cpp
index ae6c695..b9ae3b6 100644
--- a/core/handlers/messagehandler.cpp
+++ b/core/handlers/messagehandler.cpp
@@ -492,7 +492,7 @@ void Core::MessageHandler::prepareUpload(const Shared::Message& data, bool newMe
     QFileInfo file(path);
     if (file.exists() && file.isReadable()) {
         pendingStateMessages.insert(std::make_pair(id, jid));
-        uploadingSlotsQueue.emplace_back(path, id);
+        uploadingSlotsQueue.emplace_back(file, id);
         if (uploadingSlotsQueue.size() == 1)
             acc->um->requestUploadSlot(file);
     } else {
@@ -505,10 +505,10 @@ void Core::MessageHandler::onUploadSlotReceived(const QXmppHttpUploadSlotIq& slo
     if (uploadingSlotsQueue.size() == 0) {
         qDebug() << "HTTP Upload manager of account" << acc->name << "reports about success requesting upload slot, but none was requested";
     } else {
-        const std::pair<QString, QString>& pair = uploadingSlotsQueue.front();
+        const std::pair<QFileInfo, QString>& pair = uploadingSlotsQueue.front();
         const QString& mId = pair.second;
         QString palJid = pendingStateMessages.at(mId);
-        acc->network->uploadFile({acc->name, palJid, mId}, pair.first, slot.putUrl(), slot.getUrl(), slot.putHeaders());
+        acc->network->uploadFile({acc->name, palJid, mId}, pair.first.path(), slot.putUrl(), slot.getUrl(), slot.putHeaders());
         
         uploadingSlotsQueue.pop_front();
         if (uploadingSlotsQueue.size() > 0)
@@ -522,7 +522,7 @@ void Core::MessageHandler::onUploadSlotRequestFailed(const QXmppHttpUploadReques
         qDebug() << "HTTP Upload manager of account" << acc->name << "reports about an error requesting upload slot, but none was requested";
         qDebug() << err;
     } else {
-        const std::pair<QString, QString>& pair = uploadingSlotsQueue.front();
+        const std::pair<QFileInfo, QString>& pair = uploadingSlotsQueue.front();
         qDebug() << "Error requesting upload slot for file" << pair.first << "in account" << acc->name << ":" << err;
         handleUploadError(pendingStateMessages.at(pair.second), pair.second, err);
         
diff --git a/core/handlers/messagehandler.h b/core/handlers/messagehandler.h
index 15f99bf..917b98d 100644
--- a/core/handlers/messagehandler.h
+++ b/core/handlers/messagehandler.h
@@ -19,6 +19,7 @@
 #pragma once
 
 #include <QObject>
+#include <QFileInfo>
 
 #include <deque>
 #include <map>
@@ -81,7 +82,7 @@ private:
     Account* acc;
     std::map<QString, QString> pendingStateMessages;        //key is message id, value is JID
     std::map<QString, QString> pendingCorrectionMessages;   //key is new mesage, value is originalOne
-    std::deque<std::pair<QString, QString>> uploadingSlotsQueue;
+    std::deque<std::pair<QFileInfo, QString>> uploadingSlotsQueue;
 };
 
 }
diff --git a/core/passwordStorageEngines/CMakeLists.txt b/core/passwordStorageEngines/CMakeLists.txt
index 2afda3f..2a38931 100644
--- a/core/passwordStorageEngines/CMakeLists.txt
+++ b/core/passwordStorageEngines/CMakeLists.txt
@@ -1,9 +1,6 @@
 if (WITH_KWALLET)
-  target_sources(squawk PRIVATE
-    kwallet.cpp
-    kwallet.h
-  )
+  target_sources(squawk PRIVATE kwallet.cpp)
 
   add_subdirectory(wrappers)
-  target_include_directories(squawk PRIVATE $<TARGET_PROPERTY:KF5::Wallet,INTERFACE_INCLUDE_DIRECTORIES>)
+  target_include_directories(squawk PRIVATE $<TARGET_PROPERTY:KF${QT_VERSION_MAJOR}::Wallet,INTERFACE_INCLUDE_DIRECTORIES>)
 endif ()
diff --git a/core/passwordStorageEngines/kwallet.h b/core/passwordStorageEngines/kwallet.h
index 28475d2..1f047e6 100644
--- a/core/passwordStorageEngines/kwallet.h
+++ b/core/passwordStorageEngines/kwallet.h
@@ -27,7 +27,7 @@
 #include <map>
 #include <set>
 
-#include <KF5/KWallet/KWallet>
+#include <KWallet>
 
 namespace Core {
 namespace PSE {
diff --git a/core/passwordStorageEngines/wrappers/CMakeLists.txt b/core/passwordStorageEngines/wrappers/CMakeLists.txt
index 432079f..6280cc0 100644
--- a/core/passwordStorageEngines/wrappers/CMakeLists.txt
+++ b/core/passwordStorageEngines/wrappers/CMakeLists.txt
@@ -1,4 +1,5 @@
 add_library(kwalletWrapper SHARED kwallet.cpp)
-target_link_libraries(kwalletWrapper PRIVATE KF5::Wallet)
+
+target_link_libraries(kwalletWrapper PRIVATE KF${QT_VERSION_MAJOR}::Wallet)
 
 install(TARGETS kwalletWrapper LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}/squawk)
diff --git a/core/passwordStorageEngines/wrappers/kwallet.cpp b/core/passwordStorageEngines/wrappers/kwallet.cpp
index d899985..5c7bc99 100644
--- a/core/passwordStorageEngines/wrappers/kwallet.cpp
+++ b/core/passwordStorageEngines/wrappers/kwallet.cpp
@@ -16,7 +16,7 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#include <KF5/KWallet/KWallet>
+#include <KWallet>
 
 extern "C" KWallet::Wallet* openWallet(const QString &name, WId w, KWallet::Wallet::OpenType ot = KWallet::Wallet::Synchronous) {
     return KWallet::Wallet::openWallet(name, w, ot);
diff --git a/core/squawk.cpp b/core/squawk.cpp
index 1d1abe5..1851eb3 100644
--- a/core/squawk.cpp
+++ b/core/squawk.cpp
@@ -22,9 +22,7 @@
 #include <QDir>
 #include <QStandardPaths>
 
-#ifdef WITH_SIMPLE_CRYPT
-#include "external/simpleCrypt/simplecrypt.h"
-#endif
+#include "utils/jammer.h"
 
 Core::Squawk::Squawk(QObject* parent):
     QObject(parent),
@@ -87,18 +85,14 @@ void Core::Squawk::stop() {
                     password = acc->getPassword();
                     break;
                 case Shared::AccountPassword::jammed:
-#ifdef WITH_SIMPLE_CRYPT2
-                    password = SimpleCrypt(passwordHash).encryptToString(acc->getPassword());
-#else
-                    qDebug() << "The password for account" << acc->getName() << "is set to be jammed, but Squawk was compiled without SimpleCrypt support";
-                    qDebug("Can not encode password, setting this account to always ask password mode");
-                    ap = Shared::AccountPassword::alwaysAsk;
-#endif
+                    password = Jammer::encrypt(acc->getPassword(), passwordHash);
                     break;
                 default:
                     break;
             }
 
+            qDebug() << "Saving password for" << acc->getName() << password;
+
             settings.setValue("name", acc->getName());
             settings.setValue("server", acc->getServer());
             settings.setValue("login", acc->getLogin());
@@ -708,16 +702,8 @@ void Core::Squawk::readSettings() {
 
         QString name = settings.value("name").toString();
         QString password = settings.value("password", "").toString();
-        if (passwordType == Shared::AccountPassword::jammed) {
-#ifdef WITH_SIMPLE_CRYPT
-            SimpleCrypt crypto(passwordHash);
-            password = crypto.decryptToString(password);
-#else
-            qDebug() << "The password for account" << name << "is jammed, but Squawk was compiled without SimpleCrypt support";
-            qDebug("Can not decode password, setting this account to always ask password mode");
-            passwordType = Shared::AccountPassword::alwaysAsk;
-#endif
-        }
+        if (passwordType == Shared::AccountPassword::jammed)
+            password = Jammer::decrypt(password, passwordHash);
 
         addAccount(
             settings.value("login").toString(), 
diff --git a/core/utils/CMakeLists.txt b/core/utils/CMakeLists.txt
new file mode 100644
index 0000000..e030130
--- /dev/null
+++ b/core/utils/CMakeLists.txt
@@ -0,0 +1,9 @@
+set(SOURCE_FILES
+    jammer.cpp
+)
+
+set(HEADER_FILES
+   jammer.h
+)
+
+target_sources(squawk PRIVATE ${SOURCE_FILES})
diff --git a/core/utils/jammer.cpp b/core/utils/jammer.cpp
new file mode 100644
index 0000000..1714c6b
--- /dev/null
+++ b/core/utils/jammer.cpp
@@ -0,0 +1,94 @@
+/*
+ * Squawk messenger.
+ * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+ *
+ * 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 "jammer.h"
+
+#include <memory>
+
+#include <openssl/evp.h>
+#include <openssl/err.h>
+
+struct CipherCtxDeleter {
+    void operator()(EVP_CIPHER_CTX* ctx) const {
+        EVP_CIPHER_CTX_free(ctx);
+    }
+};
+typedef std::unique_ptr<EVP_CIPHER_CTX, CipherCtxDeleter> CipherCtx;
+
+QString Core::Jammer::encrypt(const QString& plaintext, qint64 key) {
+    QByteArray encryptedData = process(plaintext.toUtf8(), intToKey(key), true);
+
+    return QString::fromUtf8(encryptedData.toHex());
+}
+
+QString Core::Jammer::decrypt(const QString& ciphertext, qint64 key) {
+    QByteArray encryptedData = QByteArray::fromHex(ciphertext.toUtf8());
+    QByteArray decryptedData = process(encryptedData, intToKey(key), false);
+
+    return QString::fromUtf8(decryptedData);
+}
+
+std::string Core::Jammer::getOpenSSLErrorString() {
+    unsigned long errCode = ERR_get_error();
+    if (errCode == 0) {
+        return "No OpenSSL error";
+    }
+    const char *errMsg = ERR_reason_error_string(errCode);
+    return errMsg ? std::string(errMsg) : "Unknown OpenSSL error";
+}
+
+QByteArray Core::Jammer::process(const QByteArray& input, const QByteArray& key, bool encrypt) {
+    CipherCtx ctx(EVP_CIPHER_CTX_new());
+    if (!ctx)
+        throw std::runtime_error("Failed to create password jammer context");
+
+    QByteArray output(input.size() + 16, 0);
+    int outputLength = 0;
+    int finalLength = 0;
+
+    if (!ctx)
+        throw std::runtime_error("Failed to create EVP_CIPHER_CTX: " + getOpenSSLErrorString());
+
+    if (EVP_CipherInit_ex(ctx.get(), EVP_chacha20(), nullptr, toUChar(key), nullptr, encrypt) != 1)
+        throw std::runtime_error("EVP_CipherInit_ex failed. " + getOpenSSLErrorString());
+
+    if (EVP_CipherUpdate(ctx.get(), toUChar(output), &outputLength, toUChar(input), input.size()) != 1)
+        throw std::runtime_error("EVP_CipherUpdate failed. " + getOpenSSLErrorString());
+
+    if (EVP_CipherFinal_ex(ctx.get(), toUChar(output) + outputLength, &finalLength) != 1)
+        throw std::runtime_error("EVP_CipherFinal_ex failed. " + getOpenSSLErrorString());
+
+    output.resize(outputLength + finalLength);
+
+    return output;
+}
+
+QByteArray Core::Jammer::intToKey(qint64 key, int keySize) {
+    QByteArray keyBytes(reinterpret_cast<const char *>(&key), sizeof(key));
+    while (keyBytes.size() < keySize)
+        keyBytes.append(keyBytes);
+
+    keyBytes.truncate(keySize);
+    return keyBytes;
+}
+
+unsigned char* Core::Jammer::toUChar(QByteArray& data) {
+    return reinterpret_cast<unsigned char *>(data.data());}
+
+const unsigned char* Core::Jammer::toUChar(const QByteArray& data) {
+    return reinterpret_cast<const unsigned char *>(data.constData());}
diff --git a/core/utils/jammer.h b/core/utils/jammer.h
new file mode 100644
index 0000000..456c14b
--- /dev/null
+++ b/core/utils/jammer.h
@@ -0,0 +1,44 @@
+/*
+ * Squawk messenger.
+ * Copyright (C) 2019  Yury Gubich <blue@macaw.me>
+ *
+ * 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 <stdexcept>
+
+#include <QByteArray>
+#include <QString>
+
+namespace Core {
+
+class Jammer {
+public:
+    static QString encrypt(const QString& plaintext, qint64 key);
+    static QString decrypt(const QString& ciphertext, qint64 key);
+
+private:
+    Jammer() = delete;
+
+    static QByteArray process(const QByteArray& input, const QByteArray& key, bool encrypt);
+    static QByteArray intToKey(qint64 key, int keySize = 32);
+
+    static unsigned char* toUChar(QByteArray& data);
+    static const unsigned char* toUChar(const QByteArray& data);
+    static std::string getOpenSSLErrorString();
+};
+
+}
diff --git a/external/simpleCrypt/CMakeLists.txt b/external/simpleCrypt/CMakeLists.txt
deleted file mode 100644
index 5f274ba..0000000
--- a/external/simpleCrypt/CMakeLists.txt
+++ /dev/null
@@ -1,10 +0,0 @@
-cmake_minimum_required(VERSION 3.5)
-project(simplecrypt LANGUAGES CXX)
-
-set(CMAKE_AUTOMOC ON)
-
-find_package(Qt5 COMPONENTS Core REQUIRED)
-
-add_library(simpleCrypt STATIC simplecrypt.cpp simplecrypt.h)
-
-target_link_libraries(simpleCrypt Qt5::Core)
diff --git a/external/simpleCrypt/simplecrypt.cpp b/external/simpleCrypt/simplecrypt.cpp
deleted file mode 100644
index 093403e..0000000
--- a/external/simpleCrypt/simplecrypt.cpp
+++ /dev/null
@@ -1,248 +0,0 @@
-/*
- Copyright (c) 2011, Andre Somers
- All rights reserved.
- 
- Redistribution and use in source and binary forms, with or without
- modification, are permitted provided that the following conditions are met:
- * Redistributions of source code must retain the above copyright
- notice, this list of conditions and the following disclaimer.
- * Redistributions in binary form must reproduce the above copyright
- notice, this list of conditions and the following disclaimer in the
- documentation and/or other materials provided with the distribution.
- * Neither the name of the Rathenau Instituut, Andre Somers nor the
- names of its contributors may be used to endorse or promote products
- derived from this software without specific prior written permission.
- 
- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
- ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- DISCLAIMED. IN NO EVENT SHALL ANDRE SOMERS BE LIABLE FOR ANY
- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
- SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-#include "simplecrypt.h"
-#include <QByteArray>
-#include <QtDebug>
-#include <QtGlobal>
-#include <QDateTime>
-#include <QCryptographicHash>
-#include <QDataStream>
-
-SimpleCrypt::SimpleCrypt():
-m_key(0),
-m_compressionMode(CompressionAuto),
-m_protectionMode(ProtectionChecksum),
-m_lastError(ErrorNoError) {}
-
-SimpleCrypt::SimpleCrypt(quint64 key):
-m_key(key),
-m_compressionMode(CompressionAuto),
-m_protectionMode(ProtectionChecksum),
-m_lastError(ErrorNoError)
-{
-    splitKey();
-}
-
-void SimpleCrypt::setKey(quint64 key)
-{
-    m_key = key;
-    splitKey();
-}
-
-void SimpleCrypt::splitKey()
-{
-    m_keyParts.clear();
-    m_keyParts.resize(8);
-    for (int i=0;i<8;i++) {
-        quint64 part = m_key;
-        for (int j=i; j>0; j--)
-            part = part >> 8;
-        part = part & 0xff;
-        m_keyParts[i] = static_cast<char>(part);
-    }
-}
-
-QByteArray SimpleCrypt::encryptToByteArray(const QString& plaintext)
-{
-    QByteArray plaintextArray = plaintext.toUtf8();
-    return encryptToByteArray(plaintextArray);
-}
-
-QByteArray SimpleCrypt::encryptToByteArray(QByteArray plaintext)
-{
-    if (m_keyParts.isEmpty()) {
-        qWarning() << "No key set.";
-        m_lastError = ErrorNoKeySet;
-        return QByteArray();
-    }
-    
-    
-    QByteArray ba = plaintext;
-    
-    CryptoFlags flags = CryptoFlagNone;
-    if (m_compressionMode == CompressionAlways) {
-        ba = qCompress(ba, 9); //maximum compression
-        flags |= CryptoFlagCompression;
-    } else if (m_compressionMode == CompressionAuto) {
-        QByteArray compressed = qCompress(ba, 9);
-        if (compressed.count() < ba.count()) {
-            ba = compressed;
-            flags |= CryptoFlagCompression;
-        }
-    }
-    
-    QByteArray integrityProtection;
-    if (m_protectionMode == ProtectionChecksum) {
-        flags |= CryptoFlagChecksum;
-        QDataStream s(&integrityProtection, QIODevice::WriteOnly);
-        s << qChecksum(ba.constData(), ba.length());
-    } else if (m_protectionMode == ProtectionHash) {
-        flags |= CryptoFlagHash;
-        QCryptographicHash hash(QCryptographicHash::Sha1);
-        hash.addData(ba);
-        
-        integrityProtection += hash.result();
-    }
-    
-    //prepend a random char to the string
-    char randomChar = char(QRandomGenerator::global()->generate() & 0xFF);
-    ba = randomChar + integrityProtection + ba;
-    
-    int pos(0);
-    char lastChar(0);
-    
-    int cnt = ba.count();
-    
-    while (pos < cnt) {
-        ba[pos] = ba.at(pos) ^ m_keyParts.at(pos % 8) ^ lastChar;
-        lastChar = ba.at(pos);
-        ++pos;
-    }
-    
-    QByteArray resultArray;
-    resultArray.append(char(0x03));  //version for future updates to algorithm
-    resultArray.append(char(flags)); //encryption flags
-    resultArray.append(ba);
-    
-    m_lastError = ErrorNoError;
-    return resultArray;
-}
-
-QString SimpleCrypt::encryptToString(const QString& plaintext)
-{
-    QByteArray plaintextArray = plaintext.toUtf8();
-    QByteArray cypher = encryptToByteArray(plaintextArray);
-    QString cypherString = QString::fromLatin1(cypher.toBase64());
-    return cypherString;
-}
-
-QString SimpleCrypt::encryptToString(QByteArray plaintext)
-{
-    QByteArray cypher = encryptToByteArray(plaintext);
-    QString cypherString = QString::fromLatin1(cypher.toBase64());
-    return cypherString;
-}
-
-QString SimpleCrypt::decryptToString(const QString &cyphertext)
-{
-    QByteArray cyphertextArray = QByteArray::fromBase64(cyphertext.toLatin1());
-    QByteArray plaintextArray = decryptToByteArray(cyphertextArray);
-    QString plaintext = QString::fromUtf8(plaintextArray, plaintextArray.size());
-    
-    return plaintext;
-}
-
-QString SimpleCrypt::decryptToString(QByteArray cypher)
-{
-    QByteArray ba = decryptToByteArray(cypher);
-    QString plaintext = QString::fromUtf8(ba, ba.size());
-    
-    return plaintext;
-}
-
-QByteArray SimpleCrypt::decryptToByteArray(const QString& cyphertext)
-{
-    QByteArray cyphertextArray = QByteArray::fromBase64(cyphertext.toLatin1());
-    QByteArray ba = decryptToByteArray(cyphertextArray);
-    
-    return ba;
-}
-
-QByteArray SimpleCrypt::decryptToByteArray(QByteArray cypher)
-{
-    if (m_keyParts.isEmpty()) {
-        qWarning() << "No key set.";
-        m_lastError = ErrorNoKeySet;
-        return QByteArray();
-    }
-    
-    QByteArray ba = cypher;
-    
-    if( cypher.count() < 3 )
-        return QByteArray();
-    
-    char version = ba.at(0);
-    
-    if (version !=3) {  //we only work with version 3
-        m_lastError = ErrorUnknownVersion;
-        qWarning() << "Invalid version or not a cyphertext.";
-        return QByteArray();
-    }
-    
-    CryptoFlags flags = CryptoFlags(ba.at(1));
-    
-    ba = ba.mid(2);
-    int pos(0);
-    int cnt(ba.count());
-    char lastChar = 0;
-    
-    while (pos < cnt) {
-        char currentChar = ba[pos];
-        ba[pos] = ba.at(pos) ^ lastChar ^ m_keyParts.at(pos % 8);
-        lastChar = currentChar;
-        ++pos;
-    }
-    
-    ba = ba.mid(1); //chop off the random number at the start
-    
-    bool integrityOk(true);
-    if (flags.testFlag(CryptoFlagChecksum)) {
-        if (ba.length() < 2) {
-            m_lastError = ErrorIntegrityFailed;
-            return QByteArray();
-        }
-        quint16 storedChecksum;
-        {
-            QDataStream s(&ba, QIODevice::ReadOnly);
-            s >> storedChecksum;
-        }
-        ba = ba.mid(2);
-        quint16 checksum = qChecksum(ba.constData(), ba.length());
-        integrityOk = (checksum == storedChecksum);
-    } else if (flags.testFlag(CryptoFlagHash)) {
-        if (ba.length() < 20) {
-            m_lastError = ErrorIntegrityFailed;
-            return QByteArray();
-        }
-        QByteArray storedHash = ba.left(20);
-        ba = ba.mid(20);
-        QCryptographicHash hash(QCryptographicHash::Sha1);
-        hash.addData(ba);
-        integrityOk = (hash.result() == storedHash);
-    }
-    
-    if (!integrityOk) {
-        m_lastError = ErrorIntegrityFailed;
-        return QByteArray();
-    }
-    
-    if (flags.testFlag(CryptoFlagCompression))
-        ba = qUncompress(ba);
-    
-    m_lastError = ErrorNoError;
-    return ba;
-}
diff --git a/external/simpleCrypt/simplecrypt.h b/external/simpleCrypt/simplecrypt.h
deleted file mode 100644
index 0052618..0000000
--- a/external/simpleCrypt/simplecrypt.h
+++ /dev/null
@@ -1,226 +0,0 @@
-/*
- Copyright (c) 2011, Andre Somers
- All rights reserved.
- 
- Redistribution and use in source and binary forms, with or without
- modification, are permitted provided that the following conditions are met:
- * Redistributions of source code must retain the above copyright
- notice, this list of conditions and the following disclaimer.
- * Redistributions in binary form must reproduce the above copyright
- notice, this list of conditions and the following disclaimer in the
- documentation and/or other materials provided with the distribution.
- * Neither the name of the Rathenau Instituut, Andre Somers nor the
- names of its contributors may be used to endorse or promote products
- derived from this software without specific prior written permission.
- 
- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
- ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- DISCLAIMED. IN NO EVENT SHALL ANDRE SOMERS BE LIABLE FOR ANY
- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
- SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-#ifndef SIMPLECRYPT_H
-#define SIMPLECRYPT_H
-#include <QString>
-#include <QVector>
-#include <QFlags>
-#include <QRandomGenerator>
-
-/**
- @ short Simple encrypt*ion and decryption of strings and byte arrays
- 
- This class provides a simple implementation of encryption and decryption
- of strings and byte arrays.
- 
- @warning The encryption provided by this class is NOT strong encryption. It may
- help to shield things from curious eyes, but it will NOT stand up to someone
- determined to break the encryption. Don't say you were not warned.
- 
- The class uses a 64 bit key. Simply create an instance of the class, set the key,
- and use the encryptToString() method to calculate an encrypted version of the input string.
- To decrypt that string again, use an instance of SimpleCrypt initialized with
- the same key, and call the decryptToString() method with the encrypted string. If the key
- matches, the decrypted version of the string will be returned again.
- 
- If you do not provide a key, or if something else is wrong, the encryption and
- decryption function will return an empty string or will return a string containing nonsense.
- lastError() will return a value indicating if the method was succesful, and if not, why not.
- 
- SimpleCrypt is prepared for the case that the encryption and decryption
- algorithm is changed in a later version, by prepending a version identifier to the cypertext.
- */
-class SimpleCrypt
-{
-public:
-    /**
-     CompressionMode describes if compression will be applied to the data to be
-     encrypted.
-     */
-    enum CompressionMode {
-        CompressionAuto,    /*!< Only apply compression if that results in a shorter plaintext. */
-        CompressionAlways,  /*!< Always apply compression. Note that for short inputs, a compression may result in longer data */
-        CompressionNever    /*!< Never apply compression. */
-    };
-    /**
-     IntegrityProtectionMode describes measures taken to make it possible to detect problems with the data
-     or wrong decryption keys.
-     
-     Measures involve adding a checksum or a cryptograhpic hash to the data to be encrypted. This
-     increases the length of the resulting cypertext, but makes it possible to check if the plaintext
-     appears to be valid after decryption.
-     */
-    enum IntegrityProtectionMode {
-        ProtectionNone,    /*!< The integerity of the encrypted data is not protected. It is not really possible to detect a wrong key, for instance. */
-        ProtectionChecksum,/*!< A simple checksum is used to verify that the data is in order. If not, an empty string is returned. */
-        ProtectionHash     /*!< A cryptographic hash is used to verify the integrity of the data. This method produces a much stronger, but longer check */
-    };
-    /**
-     Error describes t*he type of error that occured.
-     */
-    enum Error {
-        ErrorNoError,         /*!< No error occurred. */
-        ErrorNoKeySet,        /*!< No key was set. You can not encrypt or decrypt without a valid key. */
-        ErrorUnknownVersion,  /*!< The version of this data is unknown, or the data is otherwise not valid. */
-        ErrorIntegrityFailed, /*!< The integrity check of the data failed. Perhaps the wrong key was used. */
-    };
-    
-    /**
-     Constructor.     *
-     
-     Constructs a SimpleCrypt instance without a valid key set on it.
-     */
-    SimpleCrypt();
-    /**
-     Constructor.     *
-     
-     Constructs a SimpleCrypt instance and initializes it with the given @arg key.
-     */
-    explicit SimpleCrypt(quint64 key);
-    
-    /**
-     ( Re-) initializes* the key with the given @arg key.
-     */
-    void setKey(quint64 key);
-    /**
-     Returns true if SimpleCrypt has been initialized with a key.
-     */
-    bool hasKey() const {return !m_keyParts.isEmpty();}
-    
-    /**
-     Sets the compress*ion mode to use when encrypting data. The default mode is Auto.
-     
-     Note that decryption is not influenced by this mode, as the decryption recognizes
-     what mode was used when encrypting.
-     */
-    void setCompressionMode(CompressionMode mode) {m_compressionMode = mode;}
-    /**
-     Returns the CompressionMode that is currently in use.
-     */
-    CompressionMode compressionMode() const {return m_compressionMode;}
-    
-    /**
-     Sets the integrity mode to use when encrypting data. The default mode is Checksum.
-     
-     Note that decryption is not influenced by this mode, as the decryption recognizes
-     what mode was used when encrypting.
-     */
-    void setIntegrityProtectionMode(IntegrityProtectionMode mode) {m_protectionMode = mode;}
-    /**
-     Returns the IntegrityProtectionMode that is currently in use.
-     */
-    IntegrityProtectionMode integrityProtectionMode() const {return m_protectionMode;}
-    
-    /**
-     Returns the last *error that occurred.
-     */
-    Error lastError() const {return m_lastError;}
-    
-    /**
-     Encrypts the @arg* plaintext string with the key the class was initialized with, and returns
-     a cyphertext the result. The result is a base64 encoded version of the binary array that is the
-     actual result of the string, so it can be stored easily in a text format.
-     */
-    QString encryptToString(const QString& plaintext) ;
-    /**
-     Encrypts the @arg* plaintext QByteArray with the key the class was initialized with, and returns
-     a cyphertext the result. The result is a base64 encoded version of the binary array that is the
-     actual result of the encryption, so it can be stored easily in a text format.
-     */
-    QString encryptToString(QByteArray plaintext) ;
-    /**
-     Encrypts the @arg* plaintext string with the key the class was initialized with, and returns
-     a binary cyphertext in a QByteArray the result.
-     
-     This method returns a byte array, that is useable for storing a binary format. If you need
-     a string you can store in a text file, use encryptToString() instead.
-     */
-    QByteArray encryptToByteArray(const QString& plaintext) ;
-    /**
-     Encrypts the @arg* plaintext QByteArray with the key the class was initialized with, and returns
-     a binary cyphertext in a QByteArray the result.
-     
-     This method returns a byte array, that is useable for storing a binary format. If you need
-     a string you can store in a text file, use encryptToString() instead.
-     */
-    QByteArray encryptToByteArray(QByteArray plaintext) ;
-    
-    /**
-     Decrypts a cypher*text string encrypted with this class with the set key back to the
-     plain text version.
-     
-     If an error occured, such as non-matching keys between encryption and decryption,
-     an empty string or a string containing nonsense may be returned.
-     */
-    QString decryptToString(const QString& cyphertext) ;
-    /**
-     Decrypts a cypher*text string encrypted with this class with the set key back to the
-     plain text version.
-     
-     If an error occured, such as non-matching keys between encryption and decryption,
-     an empty string or a string containing nonsense may be returned.
-     */
-    QByteArray decryptToByteArray(const QString& cyphertext) ;
-    /**
-     Decrypts a cypher*text binary encrypted with this class with the set key back to the
-     plain text version.
-     
-     If an error occured, such as non-matching keys between encryption and decryption,
-     an empty string or a string containing nonsense may be returned.
-     */
-    QString decryptToString(QByteArray cypher) ;
-    /**
-     Decrypts a cypher*text binary encrypted with this class with the set key back to the
-     plain text version.
-     
-     If an error occured, such as non-matching keys between encryption and decryption,
-     an empty string or a string containing nonsense may be returned.
-     */
-    QByteArray decryptToByteArray(QByteArray cypher) ;
-    
-    //enum to describe options that have been used for the encryption. Currently only one, but
-    //that only leaves room for future extensions like adding a cryptographic hash...
-    enum CryptoFlag{CryptoFlagNone = 0,
-        CryptoFlagCompression = 0x01,
-        CryptoFlagChecksum = 0x02,
-        CryptoFlagHash = 0x04
-    };
-    Q_DECLARE_FLAGS(CryptoFlags, CryptoFlag);
-private:
-    
-    void splitKey();
-    
-    quint64 m_key;
-    QVector<char> m_keyParts;
-    CompressionMode m_compressionMode;
-    IntegrityProtectionMode m_protectionMode;
-    Error m_lastError;
-};
-Q_DECLARE_OPERATORS_FOR_FLAGS(SimpleCrypt::CryptoFlags)
-
-#endif // SimpleCrypt_H
diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt
index 565651e..9eb9070 100644
--- a/plugins/CMakeLists.txt
+++ b/plugins/CMakeLists.txt
@@ -1,14 +1,14 @@
 if (WITH_KIO)
   add_library(openFileManagerWindowJob SHARED openfilemanagerwindowjob.cpp)
-  target_link_libraries(openFileManagerWindowJob PRIVATE KF5::KIOWidgets)
+  target_link_libraries(openFileManagerWindowJob PRIVATE KF${QT_VERSION_MAJOR}::KIOWidgets)
 
   install(TARGETS openFileManagerWindowJob LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}/squawk)
 endif ()
 
 if (WITH_KCONFIG)
   add_library(colorSchemeTools SHARED colorschemetools.cpp)
-  target_link_libraries(colorSchemeTools PRIVATE KF5::ConfigCore)
-  target_link_libraries(colorSchemeTools PRIVATE KF5::ConfigWidgets)
+  target_link_libraries(colorSchemeTools PRIVATE KF${QT_VERSION_MAJOR}::ConfigCore)
+  target_link_libraries(colorSchemeTools PRIVATE KF${QT_VERSION_MAJOR}::ConfigWidgets)
 
   install(TARGETS colorSchemeTools LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}/squawk)
 endif()
diff --git a/plugins/colorschemetools.cpp b/plugins/colorschemetools.cpp
index ea2c23e..76eba9b 100644
--- a/plugins/colorschemetools.cpp
+++ b/plugins/colorschemetools.cpp
@@ -20,9 +20,9 @@
 #include <QPainter>
 #include <QFileInfo>
 
-#include <KConfigCore/KSharedConfig>
-#include <KConfigCore/KConfigGroup>
-#include <KConfigWidgets/KColorScheme>
+#include <KSharedConfig>
+#include <KConfigGroup>
+#include <KColorScheme>
 
 QPixmap createPixmap(int size, const QBrush& window, const QBrush& button, const QBrush& view, const QBrush& selection);
 
diff --git a/shared/CMakeLists.txt b/shared/CMakeLists.txt
index 2ef3970..45fcfd0 100644
--- a/shared/CMakeLists.txt
+++ b/shared/CMakeLists.txt
@@ -39,7 +39,4 @@ set(HEADER_FILES
   defines.h
 )
 
-target_sources(squawk PRIVATE
-    ${SOURCE_FILES}
-    ${HEADER_FILES}
-)
+target_sources(squawk PRIVATE ${SOURCE_FILES} ${HEADER_FILES})
diff --git a/shared/clientid.h b/shared/clientid.h
index 5188b1c..05bed45 100644
--- a/shared/clientid.h
+++ b/shared/clientid.h
@@ -14,11 +14,11 @@
 // You should have received a copy of the GNU General Public License
 // along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
-#ifndef SHARED_CLIENTID_H
-#define SHARED_CLIENTID_H
+#pragma once
 
 #include <QString>
 #include <QDataStream>
+#include <QMetaType>
 
 namespace Shared {
 
@@ -54,5 +54,3 @@ Q_DECLARE_METATYPE(Shared::ClientId)
 
 QDataStream& operator << (QDataStream& stream, const Shared::ClientId& info);
 QDataStream& operator >> (QDataStream& stream, Shared::ClientId& info);
-
-#endif // SHARED_CLIENTID_H
diff --git a/shared/clientinfo.h b/shared/clientinfo.h
index 288e9fa..ca66c6d 100644
--- a/shared/clientinfo.h
+++ b/shared/clientinfo.h
@@ -14,10 +14,10 @@
 // You should have received a copy of the GNU General Public License
 // along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
-#ifndef SHARED_CLIENTINFO_H
-#define SHARED_CLIENTINFO_H
+#pragma once
 
 #include <set>
+#include <map>
 
 #include <QDataStream>
 #include <QString>
@@ -54,5 +54,3 @@ private:
 
 QDataStream& operator << (QDataStream& stream, const Shared::ClientInfo& info);
 QDataStream& operator >> (QDataStream& stream, Shared::ClientInfo& info);
-
-#endif // SHARED_CLIENTINFO_H
diff --git a/shared/enums.h b/shared/enums.h
index 43d1583..6f5e9db 100644
--- a/shared/enums.h
+++ b/shared/enums.h
@@ -101,8 +101,8 @@ enum class Avatar {
     valid
 };
 Q_ENUM_NS(Avatar)
-static const Avatar AvatarHighest = Avatar::valid; 
-static const Avatar AvatarLowest = Avatar::empty; 
+static const Avatar AvatarHighest = Avatar::valid;
+static const Avatar AvatarLowest = Avatar::empty;
 
 
 static const std::deque<QString> messageStateThemeIcons = {"state-offline", "state-sync", "state-ok", "state-error"};
diff --git a/shared/global.cpp b/shared/global.cpp
index 362bf81..f0e3230 100644
--- a/shared/global.cpp
+++ b/shared/global.cpp
@@ -23,12 +23,6 @@
 #include "enums.h"
 #include "ui/models/roster.h"
 
-#ifdef WITH_SIMPLE_CRYPT
-#define SIMPLE_CRYPT_ENABLED true
-#else
-#define SIMPLE_CRYPT_ENABLED false
-#endif
-
 #ifdef WITH_OMEMO
 constexpr bool OMEMO_SUPPORT = true;
 #else
@@ -158,8 +152,7 @@ Shared::Global::Global():
     optionalFeatures({
         {"KWallet", false},
         {"openFileManagerWindowJob", false},
-        {"colorSchemeTools", false},
-        {"simpleCryptJammedPassword", SIMPLE_CRYPT_ENABLED}
+        {"colorSchemeTools", false}
     }),
     fileCache()
 {
@@ -324,7 +317,7 @@ void Shared::Global::highlightInFileManager(const QString& path)
     qDebug() << "requested to highlight in file manager url" << path << "but it's not supported: squawk wasn't compiled to support it, trying fallback";
 #endif
     
-    QFileInfo info = path;
+    QFileInfo info(path);
     if (info.exists()) {
         QProcess proc;
         proc.start("xdg-mime", query);
diff --git a/shared/messageinfo.cpp b/shared/messageinfo.cpp
index 7502a6e..a26f23f 100644
--- a/shared/messageinfo.cpp
+++ b/shared/messageinfo.cpp
@@ -43,3 +43,19 @@ Shared::MessageInfo & Shared::MessageInfo::operator=(const Shared::MessageInfo&
     
     return *this;
 }
+
+QDataStream& operator >> (QDataStream& in, Shared::MessageInfo& info) {
+    in >> info.account;
+    in >> info.jid;
+    in >> info.messageId;
+
+    return in;
+}
+
+QDataStream& operator <<( QDataStream& out, const Shared::MessageInfo& info) {
+    out << info.account;
+    out << info.jid;
+    out << info.messageId;
+
+    return out;
+}
diff --git a/shared/messageinfo.h b/shared/messageinfo.h
index 3cf75bc..f06371b 100644
--- a/shared/messageinfo.h
+++ b/shared/messageinfo.h
@@ -19,6 +19,7 @@
 #pragma once
 
 #include <QString>
+#include <QDataStream>
 
 namespace Shared {
 struct MessageInfo {
@@ -34,3 +35,6 @@ struct MessageInfo {
 };
 
 }
+
+QDataStream& operator << (QDataStream& out, const Shared::MessageInfo& info);
+QDataStream& operator >> (QDataStream& in, Shared::MessageInfo& info);
diff --git a/ui/utils/progress.cpp b/ui/utils/progress.cpp
index 73e4d02..676376b 100644
--- a/ui/utils/progress.cpp
+++ b/ui/utils/progress.cpp
@@ -49,7 +49,7 @@ Progress::Progress(quint16 p_size, QWidget* parent):
     
     QGridLayout* layout = new QGridLayout();
     setLayout(layout);
-    layout->setMargin(0);
+    layout->setContentsMargins(0, 0, 0, 0);
     layout->setVerticalSpacing(0);
     layout->setHorizontalSpacing(0);
     
diff --git a/ui/widgets/accounts/account.cpp b/ui/widgets/accounts/account.cpp
index 2d70603..1e2c4c7 100644
--- a/ui/widgets/accounts/account.cpp
+++ b/ui/widgets/accounts/account.cpp
@@ -38,11 +38,6 @@ Account::Account():
         QStandardItem *item = model->item(static_cast<int>(Shared::AccountPassword::kwallet));
         item->setFlags(item->flags() & ~Qt::ItemIsEnabled);
     }
-
-    if (!Shared::Global::supported("simpleCryptJammedPassword")) {
-        QStandardItem *item = model->item(static_cast<int>(Shared::AccountPassword::jammed));
-        item->setFlags(item->flags() & ~Qt::ItemIsEnabled);
-    }
 }
 
 Account::~Account() {}
diff --git a/ui/widgets/conversation.cpp b/ui/widgets/conversation.cpp
index 7214a40..cedcf21 100644
--- a/ui/widgets/conversation.cpp
+++ b/ui/widgets/conversation.cpp
@@ -500,7 +500,7 @@ void Conversation::onFeedContext(const QPoint& pos) {
         }
         
         QString path = Shared::resolvePath(item->getAttachPath());
-        if (path.size() > 0) {
+        if (!path.isEmpty()) {
             showMenu = true;
             QAction* open = contextMenu->addAction(Shared::icon("document-preview"), tr("Open")); 
             connect(open, &QAction::triggered, [path]() {
@@ -513,7 +513,7 @@ void Conversation::onFeedContext(const QPoint& pos) {
             });
         }
 
-        bool hasAttach = item->getAttachPath() > 0 || item->getOutOfBandUrl() > 0;
+        bool hasAttach = !item->getAttachPath().isEmpty() || !item->getOutOfBandUrl().isEmpty();
         //the only mandatory condition - is for the message to be outgoing, the rest is just a good intention on the server
         if (item->getOutgoing() && !hasAttach  && index.row() < 100 && item->getTime().daysTo(QDateTime::currentDateTimeUtc()) < 20) {
             showMenu = true;
diff --git a/ui/widgets/messageline/feedview.cpp b/ui/widgets/messageline/feedview.cpp
index 41e4484..3c56d84 100644
--- a/ui/widgets/messageline/feedview.cpp
+++ b/ui/widgets/messageline/feedview.cpp
@@ -141,7 +141,15 @@ void FeedView::updateGeometries() {
     const QStyle* st = style();
 
     QSize layoutBounds = maximumViewportSize();
-    QStyleOptionViewItem option = viewOptions();
+
+
+    QStyleOptionViewItem option;
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
+    initViewItemOption(&option);
+#else
+    option = viewOptions();
+#endif
+
     option.rect.setHeight(maxMessageHeight);
     option.rect.setWidth(layoutBounds.width());
     int frameAroundContents = 0;
@@ -182,7 +190,7 @@ void FeedView::updateGeometries() {
                     previousOffset += elementMargin;
             }
             lastDate = currentDate;
-            QSize messageSize = itemDelegate(index)->sizeHint(option, index);
+            QSize messageSize = itemDelegateForIndex(index)->sizeHint(option, index);
             uint32_t offsetX(0);
             if (specialDelegate) {
                 if (index.data(Models::MessageFeed::SentByMe).toBool())
@@ -232,7 +240,7 @@ bool FeedView::tryToCalculateGeometriesWithNoScrollbars(const QStyleOptionViewIt
                 previousOffset += elementMargin;
         }
         lastDate = currentDate;
-        QSize messageSize = itemDelegate(index)->sizeHint(option, index);
+        QSize messageSize = itemDelegateForIndex(index)->sizeHint(option, index);
         
         if (previousOffset + messageSize.height() + elementMargin > totalHeight)
             return false;
@@ -288,8 +296,14 @@ void FeedView::paintEvent(QPaintEvent* event) {
         }
     }
     
+    QStyleOptionViewItem option;
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
+    initViewItemOption(&option);
+#else
+    option = viewOptions();
+#endif
+
     QPainter painter(vp);
-    QStyleOptionViewItem option = viewOptions();
     option.features = QStyleOptionViewItem::WrapText;
     QPoint cursor = vp->mapFromGlobal(QCursor::pos());
     
@@ -319,7 +333,7 @@ void FeedView::paintEvent(QPaintEvent* event) {
         stripe.setWidth(viewportRect.width());
         bool mouseOver = stripe.contains(cursor) && viewportRect.contains(cursor);
         option.state.setFlag(QStyle::State_MouseOver, mouseOver);
-        itemDelegate(index)->paint(&painter, option, index);
+        itemDelegateForIndex(index)->paint(&painter, option, index);
 
         if (!lastDate.isNull() && currentDate.daysTo(lastDate) > 0)
             drawDateDevider(option.rect.bottom(), lastDate, painter);
@@ -380,7 +394,7 @@ void FeedView::mouseMoveEvent(QMouseEvent* event) {
     if (!isVisible())
         return;
 
-    dragEndPoint = event->localPos().toPoint();
+    dragEndPoint = event->position().toPoint();
     if (mousePressed) {
         QPoint distance = dragStartPoint - dragEndPoint;
         if (distance.manhattanLength() > 5)
@@ -423,7 +437,7 @@ void FeedView::mousePressEvent(QMouseEvent* event) {
 
     mousePressed = event->button() == Qt::LeftButton;
     if (mousePressed) {
-        dragStartPoint = event->localPos().toPoint();
+        dragStartPoint = event->position().toPoint();
         if (specialDelegate && specialModel) {
             MessageDelegate* del = static_cast<MessageDelegate*>(itemDelegate());
             QString lastSelectedId = del->clearSelection();
@@ -441,7 +455,7 @@ void FeedView::mouseDoubleClickEvent(QMouseEvent* event) {
     QAbstractItemView::mouseDoubleClickEvent(event);
     mousePressed = event->button() == Qt::LeftButton;
     if (mousePressed) {
-        dragStartPoint = event->localPos().toPoint();
+        dragStartPoint = event->position().toPoint();
         if (specialDelegate && specialModel) {
             MessageDelegate* del = static_cast<MessageDelegate*>(itemDelegate());
             QString lastSelectedId = del->clearSelection();
@@ -469,7 +483,7 @@ void FeedView::mouseReleaseEvent(QMouseEvent* event) {
 
     if (mousePressed) {
         if (!dragging && specialDelegate) {
-            QPoint point = event->localPos().toPoint();
+            QPoint point = event->position().toPoint();
             QModelIndex index = indexAt(point);
             if (index.isValid()) {
                 QRect rect = visualRect(index);
diff --git a/ui/widgets/messageline/feedview.h b/ui/widgets/messageline/feedview.h
index a39afc8..19645c0 100644
--- a/ui/widgets/messageline/feedview.h
+++ b/ui/widgets/messageline/feedview.h
@@ -16,8 +16,7 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#ifndef FEEDVIEW_H
-#define FEEDVIEW_H
+#pragma once
 
 #include <QAbstractItemView>
 #include <QDesktopServices>
@@ -30,11 +29,7 @@
 #include <ui/utils/progress.h>
 #include <shared/utils.h>
 
-/**
- * @todo write docs
- */
-class FeedView : public QAbstractItemView
-{
+class FeedView : public QAbstractItemView {
     Q_OBJECT
 public:
     FeedView(QWidget* parent = nullptr);
@@ -111,5 +106,3 @@ private:
     static const std::set<int> geometryChangingRoles;
     
 };
-
-#endif //FEEDVIEW_H
diff --git a/ui/widgets/messageline/messagedelegate.cpp b/ui/widgets/messageline/messagedelegate.cpp
index b89b438..b82a992 100644
--- a/ui/widgets/messageline/messagedelegate.cpp
+++ b/ui/widgets/messageline/messagedelegate.cpp
@@ -659,7 +659,7 @@ QLabel * MessageDelegate::getStatusIcon(const Models::FeedItem& data) const {
     QIcon q(Shared::icon(Shared::messageStateThemeIcons[static_cast<uint8_t>(data.state)]));
     QString tt = Shared::Global::getName(data.state);
     if (data.state == Shared::Message::State::error) {
-        if (data.error > 0)
+        if (data.error.size() > 0)
             tt += ": " + data.error;
     }
     if (result->toolTip() != tt) {                      //If i just assign pixmap every time unconditionally
diff --git a/ui/widgets/settings/settingslist.cpp b/ui/widgets/settings/settingslist.cpp
index ee2e3ed..ae071ff 100644
--- a/ui/widgets/settings/settingslist.cpp
+++ b/ui/widgets/settings/settingslist.cpp
@@ -21,36 +21,39 @@
 SettingsList::SettingsList(QWidget* parent):
     QListWidget(parent),
     lastWidth(0)
-{
+{}
 
+SettingsList::~SettingsList() {}
+
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
+void SettingsList::initViewItemOption(QStyleOptionViewItem* option) const {
+    QListWidget::initViewItemOption(option);
+    if (!iconSize().isValid())
+        option->decorationSize.setWidth(lastWidth);
+
+    option->rect.setWidth(lastWidth);
 }
-
-SettingsList::~SettingsList()
-{
-}
-
-QStyleOptionViewItem SettingsList::viewOptions() const
-{
+#else
+QStyleOptionViewItem SettingsList::viewOptions() const {
     QStyleOptionViewItem option = QListWidget::viewOptions();
-    if (!iconSize().isValid()) {
+    if (!iconSize().isValid())
         option.decorationSize.setWidth(lastWidth);
-    }
+
     option.rect.setWidth(lastWidth);
     return option;
 }
+#endif
 
-void SettingsList::resizeEvent(QResizeEvent* event)
-{
+void SettingsList::resizeEvent(QResizeEvent* event) {
     lastWidth = event->size().width();
     QListWidget::resizeEvent(event);
 }
 
-QRect SettingsList::visualRect(const QModelIndex& index) const
-{
+QRect SettingsList::visualRect(const QModelIndex& index) const {
     QRect res = QListWidget::visualRect(index);
-    if (index.isValid()) {
+    if (index.isValid())
         res.setWidth(lastWidth);
-    }
+
     return res;
 }
 
diff --git a/ui/widgets/settings/settingslist.h b/ui/widgets/settings/settingslist.h
index 64c9d57..14ebf55 100644
--- a/ui/widgets/settings/settingslist.h
+++ b/ui/widgets/settings/settingslist.h
@@ -16,29 +16,28 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#ifndef UI_SETTINGSLIST_H
-#define UI_SETTINGSLIST_H
+#pragma once
 
 #include <QListWidget>
 #include <QResizeEvent>
+#include <QStyleOptionViewItem>
 
-/**
- * @todo write docs
- */
-class SettingsList : public QListWidget
-{
+class SettingsList : public QListWidget {
     Q_OBJECT
 public:
     SettingsList(QWidget* parent = nullptr);
     ~SettingsList();
 
 protected:
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
+    void initViewItemOption(QStyleOptionViewItem* option) const override;
+#else
     QStyleOptionViewItem viewOptions() const override;
+#endif
+
     void resizeEvent(QResizeEvent * event) override;
     QRect visualRect(const QModelIndex & index) const override;
 
 private:
     int lastWidth;
 };
-
-#endif // UI_SETTINGSLIST_H

From 321f0b03c8200fa166371f5df78d6641b10e7654 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Sat, 14 Dec 2024 18:08:50 +0200
Subject: [PATCH 278/281] Fix build for qt 5, removed some debug messages

---
 CHANGELOG.md                        |  8 ++++++
 CMakeLists.txt                      |  2 +-
 README.md                           |  5 ++--
 core/account.cpp                    |  4 +--
 core/squawk.cpp                     |  2 --
 packaging/Archlinux/PKGBUILD        | 16 ++++++------
 packaging/squawk.desktop            |  2 +-
 ui/widgets/messageline/feedview.cpp | 39 +++++++++++++++++++++++++----
 8 files changed, 57 insertions(+), 21 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4ca1542..6599bb5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,13 @@
 # Changelog
 
+## Squawk 0.2.4 (UNRELEASED)
+### Bug fixes
+- messages to the mucs get sent once again
+
+### Improvements
+- it's possible to build against Qt 6 now
+- got rid of deprecated SimpleCrypt library
+
 ## Squawk 0.2.3 (February 04, 2024)
 ### Bug fixes
 - "Add contact" and "Join conference" menu are enabled once again (pavavno)!
diff --git a/CMakeLists.txt b/CMakeLists.txt
index a62c800..4eb833a 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,5 +1,5 @@
 cmake_minimum_required(VERSION 3.16)
-project(squawk VERSION 0.2.3 LANGUAGES CXX)
+project(squawk VERSION 0.2.4 LANGUAGES CXX)
 
 cmake_policy(SET CMP0076 NEW)
 cmake_policy(SET CMP0077 NEW)
diff --git a/README.md b/README.md
index 655304f..08bf735 100644
--- a/README.md
+++ b/README.md
@@ -8,8 +8,8 @@
 
 ### Prerequisites
 
-- QT 5.12 *(lower versions might work but it wasn't tested)*
-- CMake 3.4 or higher
+- QT 5 or 6
+- CMake 3.10 or higher
 - qxmpp 1.1.0 or higher
 - LMDBAL (my own [library](https://git.macaw.me/blue/lmdbal) for lmdb)
 - KDE Frameworks: kwallet (optional)
@@ -108,6 +108,7 @@ Here is the list of keys you can pass to configuration phase of `cmake ..`:
 - `WITH_KIO` - `True` builds the `KIO` capability module if `KIO` is installed and if not goes to `False`. `False` disables `KIO` support (default is `True`)
 - `WITH_KCONFIG` - `True` builds the `KConfig` and `KConfigWidgets` capability module if such packages are installed and if not goes to `False`. `False` disables `KConfig` and `KConfigWidgets` support (default is `True`)
 - `WITH_OMEMO` - `True` builds the OMEMO encryption, requires `qxmpp` of version >= 1.5.0 built with OMEMO support. `False` disables OMEMO support (default is `False`)
+- `QT_VERSION_MAJOR` - `6` builds against Qt 6, `5` builds against Qt 6, corresponding version of lmdbal and qxmpp should be installed. By default it picks your system default Qt
 
 ## License
 
diff --git a/core/account.cpp b/core/account.cpp
index 6143fa6..e3d14e1 100644
--- a/core/account.cpp
+++ b/core/account.cpp
@@ -81,8 +81,8 @@ Core::Account::Account(
     config.setDomain(p_server);
     config.setPassword(p_password);
     config.setAutoAcceptSubscriptions(true);
-    config.setIgnoreSslErrors(true);
-    //config.setAutoReconnectionEnabled(false);
+    // config.setIgnoreSslErrors(true);
+    // config.setAutoReconnectionEnabled(false);
     delay = new DelayManager::Manager(getBareJid());
     QObject::connect(delay, &DelayManager::Manager::gotInfo, this, &Account::infoReady);
     QObject::connect(delay, &DelayManager::Manager::gotOwnInfo, this, &Account::infoReady);
diff --git a/core/squawk.cpp b/core/squawk.cpp
index 1851eb3..7f04d9a 100644
--- a/core/squawk.cpp
+++ b/core/squawk.cpp
@@ -91,8 +91,6 @@ void Core::Squawk::stop() {
                     break;
             }
 
-            qDebug() << "Saving password for" << acc->getName() << password;
-
             settings.setValue("name", acc->getName());
             settings.setValue("server", acc->getServer());
             settings.setValue("login", acc->getLogin());
diff --git a/packaging/Archlinux/PKGBUILD b/packaging/Archlinux/PKGBUILD
index 29ed800..86f2abf 100644
--- a/packaging/Archlinux/PKGBUILD
+++ b/packaging/Archlinux/PKGBUILD
@@ -1,19 +1,19 @@
 # Maintainer: Yury Gubich <blue@macaw.me>
 pkgname=squawk
-pkgver=0.2.3
+pkgver=0.2.4
 pkgrel=1
 pkgdesc="An XMPP desktop messenger, written on pure c++ (qt)"
 arch=('i686' 'x86_64')
 url="https://git.macaw.me/blue/squawk"
 license=('GPL3')
-depends=('hicolor-icon-theme' 'desktop-file-utils' 'lmdbal' 'qxmpp-qt5')
-makedepends=('cmake>=3.3' 'imagemagick' 'qt5-tools' 'boost')
-optdepends=('kwallet5: secure password storage (requires rebuild)'
-            'kconfig5: system themes support (requires rebuild)'
-            'kconfigwidgets5: system themes support (requires rebuild)'
-            'kio5: better show in folder action (requires rebuild)')
+depends=('hicolor-icon-theme' 'desktop-file-utils' 'lmdbal-qt6' 'qxmpp-qt6')
+makedepends=('cmake>=3.3' 'imagemagick' 'qt6-tools' 'boost')
+optdepends=('kwallet6: secure password storage (requires rebuild)'
+            'kconfig6: system themes support (requires rebuild)'
+            'kconfigwidgets6: system themes support (requires rebuild)'
+            'kio6: better show in folder action (requires rebuild)')
 
-source=("$pkgname-$pkgver.tar.gz::https://git.macaw.me/blue/$pkgname/archive/$pkgver.tar.gz")
+source=("$pkgname-$pkgver-$pkgrel.tar.gz::https://git.macaw.me/blue/$pkgname/archive/$pkgver.tar.gz")
 sha256sums=('SKIP')
 build() {
         cd "$srcdir/squawk"
diff --git a/packaging/squawk.desktop b/packaging/squawk.desktop
index ba0f13c..c64f9ab 100644
--- a/packaging/squawk.desktop
+++ b/packaging/squawk.desktop
@@ -1,7 +1,7 @@
 [Desktop Entry]
 
 Type=Application
-Version=1.0
+Version=0.2.4
 Name=Squawk
 GenericName=Instant Messenger
 GenericName[ru]=Мгновенные сообщения
diff --git a/ui/widgets/messageline/feedview.cpp b/ui/widgets/messageline/feedview.cpp
index 3c56d84..29ec9c6 100644
--- a/ui/widgets/messageline/feedview.cpp
+++ b/ui/widgets/messageline/feedview.cpp
@@ -190,7 +190,11 @@ void FeedView::updateGeometries() {
                     previousOffset += elementMargin;
             }
             lastDate = currentDate;
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
             QSize messageSize = itemDelegateForIndex(index)->sizeHint(option, index);
+#else
+            QSize messageSize = itemDelegate(index)->sizeHint(option, index);
+#endif
             uint32_t offsetX(0);
             if (specialDelegate) {
                 if (index.data(Models::MessageFeed::SentByMe).toBool())
@@ -240,7 +244,11 @@ bool FeedView::tryToCalculateGeometriesWithNoScrollbars(const QStyleOptionViewIt
                 previousOffset += elementMargin;
         }
         lastDate = currentDate;
-        QSize messageSize = itemDelegateForIndex(index)->sizeHint(option, index);
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
+            QSize messageSize = itemDelegateForIndex(index)->sizeHint(option, index);
+#else
+            QSize messageSize = itemDelegate(index)->sizeHint(option, index);
+#endif
         
         if (previousOffset + messageSize.height() + elementMargin > totalHeight)
             return false;
@@ -333,7 +341,12 @@ void FeedView::paintEvent(QPaintEvent* event) {
         stripe.setWidth(viewportRect.width());
         bool mouseOver = stripe.contains(cursor) && viewportRect.contains(cursor);
         option.state.setFlag(QStyle::State_MouseOver, mouseOver);
-        itemDelegateForIndex(index)->paint(&painter, option, index);
+
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
+            itemDelegateForIndex(index)->paint(&painter, option, index);
+#else
+            itemDelegate(index)->paint(&painter, option, index);
+#endif
 
         if (!lastDate.isNull() && currentDate.daysTo(lastDate) > 0)
             drawDateDevider(option.rect.bottom(), lastDate, painter);
@@ -393,8 +406,12 @@ void FeedView::setAnchorHovered(Shared::Hover type) {
 void FeedView::mouseMoveEvent(QMouseEvent* event) {
     if (!isVisible())
         return;
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
+            dragEndPoint = event->position().toPoint();
+#else
+            dragEndPoint = event->localPos().toPoint();
+#endif
 
-    dragEndPoint = event->position().toPoint();
     if (mousePressed) {
         QPoint distance = dragStartPoint - dragEndPoint;
         if (distance.manhattanLength() > 5)
@@ -437,7 +454,11 @@ void FeedView::mousePressEvent(QMouseEvent* event) {
 
     mousePressed = event->button() == Qt::LeftButton;
     if (mousePressed) {
-        dragStartPoint = event->position().toPoint();
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
+            dragStartPoint = event->position().toPoint();
+#else
+            dragStartPoint = event->localPos().toPoint();
+#endif
         if (specialDelegate && specialModel) {
             MessageDelegate* del = static_cast<MessageDelegate*>(itemDelegate());
             QString lastSelectedId = del->clearSelection();
@@ -455,7 +476,11 @@ void FeedView::mouseDoubleClickEvent(QMouseEvent* event) {
     QAbstractItemView::mouseDoubleClickEvent(event);
     mousePressed = event->button() == Qt::LeftButton;
     if (mousePressed) {
-        dragStartPoint = event->position().toPoint();
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
+            dragStartPoint = event->position().toPoint();
+#else
+            dragStartPoint = event->localPos().toPoint();
+#endif
         if (specialDelegate && specialModel) {
             MessageDelegate* del = static_cast<MessageDelegate*>(itemDelegate());
             QString lastSelectedId = del->clearSelection();
@@ -483,7 +508,11 @@ void FeedView::mouseReleaseEvent(QMouseEvent* event) {
 
     if (mousePressed) {
         if (!dragging && specialDelegate) {
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
             QPoint point = event->position().toPoint();
+#else
+            QPoint point = event->localPos().toPoint();
+#endif
             QModelIndex index = indexAt(point);
             if (index.isValid()) {
                 QRect rect = visualRect(index);

From a8060b393ca606e0683f4762c9f0f163bb966430 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Sun, 9 Feb 2025 19:51:30 +0200
Subject: [PATCH 279/281] Updated LMDBAL

---
 external/lmdbal | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/external/lmdbal b/external/lmdbal
index d62eddc..3ae1fd1 160000
--- a/external/lmdbal
+++ b/external/lmdbal
@@ -1 +1 @@
-Subproject commit d62eddc47edbec9f8c071459e045578f61ab58df
+Subproject commit 3ae1fd15c0f4f753227d6fd5bafa4968c7310b92

From 066ab487fcc7cb07b65548bc1785412dccd24708 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Thu, 20 Feb 2025 21:37:38 +0200
Subject: [PATCH 280/281] Defaulted to  qt6, fix some deprecations

---
 CHANGELOG.md                     |  3 +-
 CMakeLists.txt                   | 15 +++---
 README.md                        |  2 +-
 core/handlers/messagehandler.cpp | 49 +++++++++++++-------
 core/handlers/messagehandler.h   |  1 +
 packaging/Archlinux/PKGBUILD     |  2 +-
 ui/utils/flowlayout.cpp          | 78 ++++++++++++--------------------
 7 files changed, 76 insertions(+), 74 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6599bb5..44c00ff 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,8 +5,9 @@
 - messages to the mucs get sent once again
 
 ### Improvements
-- it's possible to build against Qt 6 now
+- it's possible to build against Qt 6 now, Qt6 is the default
 - got rid of deprecated SimpleCrypt library
+- look up for proper stanzaID
 
 ## Squawk 0.2.3 (February 04, 2024)
 ### Bug fixes
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 4eb833a..7d764ac 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -36,15 +36,18 @@ option(WITH_KCONFIG "Build KConfig support module" ON)
 option(WITH_OMEMO "Build OMEMO support module" OFF)   #it should be off by default untill I sort the problems out
 
 # Dependencies
-## Qt
-if (NOT DEFINED QT_VERSION_MAJOR)
-  find_package(QT NAMES Qt6 Qt5 CONFIG REQUIRED COMPONENTS Widgets DBus Gui Xml Network Core)
-else ()
-  find_package(Qt${QT_VERSION_MAJOR} CONFIG REQUIRED COMPONENTS Widgets DBus Gui Xml Network Core)
+## Qt, detect and prefer Qt6 if available
+find_package(Qt6 QUIET CONFIG COMPONENTS Widgets DBus Gui Xml Network Core)
+if (Qt6_FOUND)
+  set(QT_VERSION_MAJOR 6)
+else()
+  find_package(Qt5 REQUIRED CONFIG COMPONENTS Widgets DBus Gui Xml Network Core)
+  set(QT_VERSION_MAJOR 5)
 endif()
 
-find_package(Boost COMPONENTS)
+message(STATUS "Building against Qt${QT_VERSION_MAJOR}")
 
+find_package(Boost COMPONENTS)
 target_include_directories(squawk PRIVATE ${Boost_INCLUDE_DIRS})
 
 ## OMEMO
diff --git a/README.md b/README.md
index 08bf735..b65e5e9 100644
--- a/README.md
+++ b/README.md
@@ -108,7 +108,7 @@ Here is the list of keys you can pass to configuration phase of `cmake ..`:
 - `WITH_KIO` - `True` builds the `KIO` capability module if `KIO` is installed and if not goes to `False`. `False` disables `KIO` support (default is `True`)
 - `WITH_KCONFIG` - `True` builds the `KConfig` and `KConfigWidgets` capability module if such packages are installed and if not goes to `False`. `False` disables `KConfig` and `KConfigWidgets` support (default is `True`)
 - `WITH_OMEMO` - `True` builds the OMEMO encryption, requires `qxmpp` of version >= 1.5.0 built with OMEMO support. `False` disables OMEMO support (default is `False`)
-- `QT_VERSION_MAJOR` - `6` builds against Qt 6, `5` builds against Qt 6, corresponding version of lmdbal and qxmpp should be installed. By default it picks your system default Qt
+- `QT_VERSION_MAJOR` - `6` builds against Qt 6, `5` builds against Qt 6, corresponding version of lmdbal and qxmpp should be installed (default is `6`)
 
 ## License
 
diff --git a/core/handlers/messagehandler.cpp b/core/handlers/messagehandler.cpp
index b9ae3b6..34f7fd1 100644
--- a/core/handlers/messagehandler.cpp
+++ b/core/handlers/messagehandler.cpp
@@ -164,19 +164,7 @@ bool Core::MessageHandler::handleGroupMessage(const QXmppMessage& msg, bool outg
 }
 
 void Core::MessageHandler::initializeMessage(Shared::Message& target, const QXmppMessage& source, bool outgoing, bool forwarded, bool guessing) const {
-    const QDateTime& time(source.stamp());
-    QString id;
-#if (QXMPP_VERSION) >= QT_VERSION_CHECK(1, 3, 0)
-    id = source.originId();
-    if (id.size() == 0)
-        id = source.id();
-
-    target.setStanzaId(source.stanzaId());
-    qDebug() << "initializing message with originId:" << source.originId() << ", id:" << source.id() << ", stansaId:" << source.stanzaId();
-#else
-    id = source.id();
-#endif
-    target.setId(id);
+    initializeIDs(target, source);
     QString messageId = target.getId();
     if (messageId.size() == 0) {
         target.generateRandomId();          //TODO out of desperation, I need at least a random ID
@@ -197,6 +185,7 @@ void Core::MessageHandler::initializeMessage(Shared::Message& target, const QXmp
     if (guessing)
         outgoing = target.getFromJid() == acc->getBareJid();
 
+    const QDateTime& time(source.stamp());
     target.setOutgoing(outgoing);
     if (time.isValid())
         target.setTime(time);
@@ -210,6 +199,37 @@ void Core::MessageHandler::initializeMessage(Shared::Message& target, const QXmp
     target.setOutOfBandUrl(oob);
 }
 
+void Core::MessageHandler::initializeIDs(Shared::Message& target, const QXmppMessage& source) const {
+    QString id;
+#if (QXMPP_VERSION) >= QT_VERSION_CHECK(1, 3, 0)
+    id = source.originId();
+    if (id.size() == 0)
+        id = source.id();
+
+    QString stanzaID;
+#if (QXMPP_VERSION) >= QT_VERSION_CHECK(1, 5, 0)
+    // here I'm looking preferably for id generated by myself, but if there isn't - any is better than nothing
+    QVector<QXmppStanzaId> sIDs = source.stanzaIds();
+    for (const QXmppStanzaId& sID : sIDs) {
+        bool match = sID.by == acc->getBareJid();
+        if (stanzaID.isEmpty() || match)
+            stanzaID = sID.id;
+
+        if (match)
+            break;
+    }
+#else
+    stanzaID = source.stanzaId();
+#endif
+    target.setStanzaId(stanzaID);
+    qDebug() << "initializing message with originId:" << source.originId() << ", id:" << source.id() << ", stanzaId:" << stanzaID;
+#else
+    id = source.id();
+#endif
+    target.setId(id);
+}
+
+
 void Core::MessageHandler::logMessage(const QXmppMessage& msg, const QString& reason) {
     qDebug() << reason;
     qDebug() << "- from: " << msg.from();
@@ -219,9 +239,6 @@ void Core::MessageHandler::logMessage(const QXmppMessage& msg, const QString& re
     qDebug() << "- state: " << msg.state();
     qDebug() << "- stamp: " << msg.stamp();
     qDebug() << "- id: " << msg.id();
-#if (QXMPP_VERSION) >= QT_VERSION_CHECK(1, 3, 0)
-    qDebug() << "- stanzaId: " << msg.stanzaId();
-#endif
     qDebug() << "- outOfBandUrl: " << msg.outOfBandUrl();
     qDebug() << "==============================";
 }
diff --git a/core/handlers/messagehandler.h b/core/handlers/messagehandler.h
index 917b98d..3555548 100644
--- a/core/handlers/messagehandler.h
+++ b/core/handlers/messagehandler.h
@@ -77,6 +77,7 @@ private:
     bool handlePendingMessageError(const QString& id, const QString& errorText);
     std::pair<Shared::Message::State, QString> scheduleSending(const Shared::Message& message, const QDateTime& sendTime, const QString& originalId);
     bool adjustPendingMessage(const QString& messageId, const QMap<QString, QVariant>& data, bool final);
+    void initializeIDs(Shared::Message& target, const QXmppMessage& source) const;
     
 private:
     Account* acc;
diff --git a/packaging/Archlinux/PKGBUILD b/packaging/Archlinux/PKGBUILD
index 86f2abf..0defdc3 100644
--- a/packaging/Archlinux/PKGBUILD
+++ b/packaging/Archlinux/PKGBUILD
@@ -6,7 +6,7 @@ pkgdesc="An XMPP desktop messenger, written on pure c++ (qt)"
 arch=('i686' 'x86_64')
 url="https://git.macaw.me/blue/squawk"
 license=('GPL3')
-depends=('hicolor-icon-theme' 'desktop-file-utils' 'lmdbal-qt6' 'qxmpp-qt6')
+depends=('hicolor-icon-theme' 'desktop-file-utils' 'lmdbal-qt6' 'qxmpp')
 makedepends=('cmake>=3.3' 'imagemagick' 'qt6-tools' 'boost')
 optdepends=('kwallet6: secure password storage (requires rebuild)'
             'kconfig6: system themes support (requires rebuild)'
diff --git a/ui/utils/flowlayout.cpp b/ui/utils/flowlayout.cpp
index ad7715e..34f978a 100644
--- a/ui/utils/flowlayout.cpp
+++ b/ui/utils/flowlayout.cpp
@@ -33,96 +33,78 @@ FlowLayout::FlowLayout(int margin, int hSpacing, int vSpacing):
     setContentsMargins(margin, margin, margin, margin);
 }
 
-FlowLayout::~FlowLayout()
-{
+FlowLayout::~FlowLayout() {
     QLayoutItem *item;
-    while ((item = takeAt(0))) {
+    while ((item = takeAt(0)))
         delete item;
-    }
 }
 
-void FlowLayout::addItem(QLayoutItem *item)
-{
+void FlowLayout::addItem(QLayoutItem *item) {
     itemList.append(item);
 }
 
-int FlowLayout::horizontalSpacing() const
-{
-    if (m_hSpace >= 0) {
+int FlowLayout::horizontalSpacing() const {
+    if (m_hSpace >= 0)
         return m_hSpace;
-    } else {
+    else
         return smartSpacing(QStyle::PM_LayoutHorizontalSpacing);
-    }
 }
 
-int FlowLayout::verticalSpacing() const
-{
-    if (m_vSpace >= 0) {
+int FlowLayout::verticalSpacing() const {
+    if (m_vSpace >= 0)
         return m_vSpace;
-    } else {
+    else
         return smartSpacing(QStyle::PM_LayoutVerticalSpacing);
-    }
 }
 
-int FlowLayout::count() const
-{
+int FlowLayout::count() const {
     return itemList.size();
 }
 
-QLayoutItem *FlowLayout::itemAt(int index) const
-{
+QLayoutItem *FlowLayout::itemAt(int index) const {
     return itemList.value(index);
 }
 
-QLayoutItem *FlowLayout::takeAt(int index)
-{
-    if (index >= 0 && index < itemList.size()) {
+QLayoutItem *FlowLayout::takeAt(int index) {
+    if (index >= 0 && index < itemList.size())
         return itemList.takeAt(index);
-    }
+
     return nullptr;
 }
 
-Qt::Orientations FlowLayout::expandingDirections() const
-{
+Qt::Orientations FlowLayout::expandingDirections() const {
     return Qt::Orientations();
 }
 
-bool FlowLayout::hasHeightForWidth() const
-{
+bool FlowLayout::hasHeightForWidth() const {
     return true;
 }
 
-int FlowLayout::heightForWidth(int width) const
-{
+int FlowLayout::heightForWidth(int width) const {
     int height = doLayout(QRect(0, 0, width, 0), true);
     return height;
 }
 
-void FlowLayout::setGeometry(const QRect &rect)
-{
+void FlowLayout::setGeometry(const QRect &rect) {
     QLayout::setGeometry(rect);
     doLayout(rect, false);
 }
 
-QSize FlowLayout::sizeHint() const
-{
+QSize FlowLayout::sizeHint() const {
     return minimumSize();
 }
 
-QSize FlowLayout::minimumSize() const
-{
+QSize FlowLayout::minimumSize() const {
     QSize size;
-    for (const QLayoutItem *item : qAsConst(itemList)) {
+    for (const QLayoutItem *item : std::as_const(itemList))
         size = size.expandedTo(item->minimumSize());
-    }
 
     const QMargins margins = contentsMargins();
     size += QSize(margins.left() + margins.right(), margins.top() + margins.bottom());
     return size;
 }
 
-int FlowLayout::doLayout(const QRect &rect, bool testOnly) const
-{
+int FlowLayout::doLayout(const QRect &rect, bool testOnly) const {
     int left, top, right, bottom;
     getContentsMargins(&left, &top, &right, &bottom);
     QRect effectiveRect = rect.adjusted(+left, +top, -right, -bottom);
@@ -130,16 +112,16 @@ int FlowLayout::doLayout(const QRect &rect, bool testOnly) const
     int y = effectiveRect.y();
     int lineHeight = 0;
     
-    for (QLayoutItem *item : qAsConst(itemList)) {
+    for (QLayoutItem *item : std::as_const(itemList)) {
         const QWidget *wid = item->widget();
         int spaceX = horizontalSpacing();
-        if (spaceX == -1) {
+        if (spaceX == -1)
             spaceX = wid->style()->layoutSpacing(QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Horizontal);
-        }
+
         int spaceY = verticalSpacing();
-        if (spaceY == -1) {
+        if (spaceY == -1)
             spaceY = wid->style()->layoutSpacing(QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Vertical);
-        }
+
         int nextX = x + item->sizeHint().width() + spaceX;
         if (nextX - spaceX > effectiveRect.right() && lineHeight > 0) {
             x = effectiveRect.x();
@@ -148,9 +130,8 @@ int FlowLayout::doLayout(const QRect &rect, bool testOnly) const
             lineHeight = 0;
         }
 
-        if (!testOnly) {
+        if (!testOnly)
             item->setGeometry(QRect(QPoint(x, y), item->sizeHint()));
-        }
 
         x = nextX;
         lineHeight = qMax(lineHeight, item->sizeHint().height());
@@ -158,8 +139,7 @@ int FlowLayout::doLayout(const QRect &rect, bool testOnly) const
     return y + lineHeight - rect.y() + bottom;
 }
 
-int FlowLayout::smartSpacing(QStyle::PixelMetric pm) const
-{
+int FlowLayout::smartSpacing(QStyle::PixelMetric pm) const {
     QObject *parent = this->parent();
     if (!parent) {
         return -1;

From c147e02187361db659db292211c520c23d18a4c6 Mon Sep 17 00:00:00 2001
From: blue <blue@macaw.me>
Date: Mon, 5 May 2025 18:20:08 +0300
Subject: [PATCH 281/281] Minor changes

---
 {.gitea => .forgejo}/workflows/release.yml |  0
 .gitmodules                                |  7 ++-----
 CMakeLists.txt                             |  3 ++-
 external/lmdbal                            |  2 +-
 external/qxmpp                             |  2 +-
 main/root.cpp                              | 19 +++++++++++++------
 6 files changed, 19 insertions(+), 14 deletions(-)
 rename {.gitea => .forgejo}/workflows/release.yml (100%)

diff --git a/.gitea/workflows/release.yml b/.forgejo/workflows/release.yml
similarity index 100%
rename from .gitea/workflows/release.yml
rename to .forgejo/workflows/release.yml
diff --git a/.gitmodules b/.gitmodules
index 448dae5..3742a90 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,9 +1,6 @@
 [submodule "external/qxmpp"]
 	path = external/qxmpp
-	url = https://github.com/qxmpp-project/qxmpp.git
-[submodule "external/storage"]
-	path = external/storage
-	url = https://git.macaw.me/blue/storage
+	url = https://invent.kde.org/libraries/qxmpp/
 [submodule "external/lmdbal"]
 	path = external/lmdbal
-	url = gitea@git.macaw.me:blue/lmdbal.git
+	url = https://git.macaw.me/blue/lmdbal
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 7d764ac..5eb98a0 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -207,7 +207,8 @@ if(CMAKE_COMPILER_IS_GNUCXX)
     list(APPEND COMPILE_OPTIONS -O3)
   endif()
   if (CMAKE_BUILD_TYPE STREQUAL Debug)
-    list(APPEND COMPILE_OPTIONS -g)
+    list(APPEND COMPILE_OPTIONS -O0)
+    list(APPEND COMPILE_OPTIONS -g3)
     list(APPEND COMPILE_OPTIONS -Wall)
     list(APPEND COMPILE_OPTIONS -Wextra)
   endif()
diff --git a/external/lmdbal b/external/lmdbal
index 3ae1fd1..3701fb9 160000
--- a/external/lmdbal
+++ b/external/lmdbal
@@ -1 +1 @@
-Subproject commit 3ae1fd15c0f4f753227d6fd5bafa4968c7310b92
+Subproject commit 3701fb92a1498bd737828d8d1df63d4c4d8f02c7
diff --git a/external/qxmpp b/external/qxmpp
index 0cd7379..ca1bdb3 160000
--- a/external/qxmpp
+++ b/external/qxmpp
@@ -1 +1 @@
-Subproject commit 0cd7379bd78aa01af7e84f2fad6269ef0c0ba49c
+Subproject commit ca1bdb3e46c71ceb334e6dc52291850f0c96cb50
diff --git a/main/root.cpp b/main/root.cpp
index 87d97bb..f71951c 100644
--- a/main/root.cpp
+++ b/main/root.cpp
@@ -64,9 +64,14 @@ Root::~Root() {
     delete global;
 }
 
-
 void Root::initializeTranslation() {
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
+    bool defaultLoaded = defaultTranslator.load("qt_" + QLocale::system().name(), QLibraryInfo::path(QLibraryInfo::TranslationsPath));
+    if (!defaultLoaded)
+        qDebug() << "Couldn't load default translation";
+#else
     defaultTranslator.load("qt_" + QLocale::system().name(), QLibraryInfo::location(QLibraryInfo::TranslationsPath));
+#endif
     installTranslator(&defaultTranslator);
 
     QStringList shares = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation);
@@ -78,15 +83,17 @@ void Root::initializeTranslation() {
     }
 
     if (!found)
-        currentTranslator.load(QLocale(), QLatin1String("squawk"), ".", QCoreApplication::applicationDirPath());
+        found = currentTranslator.load(QLocale(), QLatin1String("squawk"), ".", QCoreApplication::applicationDirPath());
 
-
-    installTranslator(&currentTranslator);
+    if (found)
+        installTranslator(&currentTranslator);
+    else
+        qDebug() << "Couldn't load current translation";
 }
 
 void Root::initializeAppIcon() {
-    for (std::vector<unsigned int>::size_type i = 0; i < appIconSizes.size(); ++i)
-        appIcon.addFile(":images/logo.svg", QSize(appIconSizes[i], appIconSizes[i]));
+    for (unsigned int appIconSize : appIconSizes)
+        appIcon.addFile(":images/logo.svg", QSize(appIconSize, appIconSize));
 
     Root::setWindowIcon(appIcon);
 }