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 01/43] 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 02/43] 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 03/43] 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 04/43] 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 05/43] 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 06/43] 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 07/43] 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 08/43] 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 09/43] 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 10/43] 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 11/43] 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 12/43] 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 13/43] 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 14/43] 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 15/43] 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 16/43] 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 17/43] 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 18/43] 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 19/43] 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 20/43] 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 21/43] 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 22/43] 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 23/43] 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 24/43] 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 25/43] 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 26/43] 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 27/43] 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 28/43] 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 29/43] 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 30/43] 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 31/43] 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 32/43] 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 33/43] 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 34/43] 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 35/43] 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 36/43] 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 37/43] 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 38/43] 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 39/43] 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 40/43] 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 41/43] 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 42/43] 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 43/43] 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