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