From 4c20a314f050236fbcb79fa2d43322ec9045eaa1 Mon Sep 17 00:00:00 2001 From: blue Date: Sun, 17 Apr 2022 16:25:15 +0300 Subject: [PATCH 1/7] a crash fix on one of archive corner cases --- CHANGELOG.md | 1 + core/rosteritem.cpp | 28 ++++++++++++++++++---------- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 55ef1f1..4daf652 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Bug fixes - now when you remove an account it actually gets removed - segfault on unitialized Availability in some rare occesions +- fixed crash when you open a dialog with someone that has only error messages in archive ### Improvements - there is a way to disable an account and it wouldn't connect when you change availability diff --git a/core/rosteritem.cpp b/core/rosteritem.cpp index b1951d6..1b8d1e6 100644 --- a/core/rosteritem.cpp +++ b/core/rosteritem.cpp @@ -124,15 +124,19 @@ void Core::RosterItem::nextRequest() if (requestedCount != -1) { bool last = false; if (archiveState == beginning || archiveState == complete) { - QString firstId = archive->oldestId(); - if (responseCache.size() == 0) { - if (requestedBefore == firstId) { - last = true; - } - } else { - if (responseCache.front().getId() == firstId) { - last = true; + try { + QString firstId = archive->oldestId(); + if (responseCache.size() == 0) { + if (requestedBefore == firstId) { + last = true; + } + } else { + if (responseCache.front().getId() == firstId) { + last = true; + } } + } catch (const Archive::Empty& e) { + last = true; } } else if (archiveState == empty && responseCache.size() == 0) { last = true; @@ -171,8 +175,12 @@ void Core::RosterItem::performRequest(int count, const QString& before) requestCache.emplace_back(requestedCount, before); requestedCount = -1; } - Shared::Message msg = archive->newest(); - emit needHistory("", getId(msg), msg.getTime()); + try { + Shared::Message msg = archive->newest(); + emit needHistory("", getId(msg), msg.getTime()); + } catch (const Archive::Empty& e) { //this can happen when the only message in archive is not server stored (error, for example) + emit needHistory(before, ""); + } } break; case end: From 18859cb960aa6de0dbb393186392b3449b5177ff Mon Sep 17 00:00:00 2001 From: blue Date: Mon, 18 Apr 2022 19:54:42 +0300 Subject: [PATCH 2/7] first ideas for notifications --- shared/global.cpp | 35 +++++++++++++++++++++++++++++++++++ shared/global.h | 14 ++++++++++++-- ui/models/roster.cpp | 4 ++-- ui/models/roster.h | 4 ++-- ui/squawk.cpp | 31 ++----------------------------- ui/squawk.h | 5 ++--- 6 files changed, 55 insertions(+), 38 deletions(-) diff --git a/shared/global.cpp b/shared/global.cpp index 122bc79..14ae90d 100644 --- a/shared/global.cpp +++ b/shared/global.cpp @@ -19,6 +19,7 @@ #include "global.h" #include "enums.h" +#include "ui/models/roster.h" Shared::Global* Shared::Global::instance = 0; const std::set Shared::Global::supportedImagesExts = {"png", "jpg", "webp", "jpeg", "gif", "svg"}; @@ -94,6 +95,8 @@ Shared::Global::Global(): }), defaultSystemStyle(QApplication::style()->objectName()), defaultSystemPalette(QApplication::palette()), + rosterModel(new Models::Roster()), + dbus("org.freedesktop.Notifications", "/org/freedesktop/Notifications", "org.freedesktop.Notifications", QDBusConnection::sessionBus()), pluginSupport({ {"KWallet", false}, {"openFileManagerWindowJob", false}, @@ -349,6 +352,38 @@ void Shared::Global::setStyle(const QString& style) } } +void Shared::Global::notify(const QString& account, const Shared::Message& msg) +{ + QString name = QString(instance->rosterModel->getContactName(account, msg.getPenPalJid())); + QString path = QString(instance->rosterModel->getContactIconPath(account, msg.getPenPalJid(), msg.getPenPalResource())); + QVariantList args; + args << QString(QCoreApplication::applicationName()); + args << QVariant(QVariant::UInt); //TODO some normal id + if (path.size() > 0) { + args << path; + } else { + args << QString("mail-message"); //TODO should here better be unknown user icon? + } + if (msg.getType() == Shared::Message::groupChat) { + args << msg.getFromResource() + " from " + name; + } else { + args << name; + } + + QString body(msg.getBody()); + QString oob(msg.getOutOfBandUrl()); + if (body == oob) { + body = tr("Attached file"); + } + + args << body; + args << QStringList(); + args << QVariantMap(); + args << 3000; + instance->dbus.callWithArgumentList(QDBus::AutoDetect, "Notify", args); +} + + #define FROM_INT_INPL(Enum) \ template<> \ Enum Shared::Global::fromInt(int src) \ diff --git a/shared/global.h b/shared/global.h index 2056639..fcd8105 100644 --- a/shared/global.h +++ b/shared/global.h @@ -42,12 +42,18 @@ #include #include #include +#include + +class Squawk; +namespace Models { + class Roster; +} namespace Shared { class Global { Q_DECLARE_TR_FUNCTIONS(Global) - + friend class ::Squawk; public: struct FileInfo { enum class Preview { @@ -64,7 +70,9 @@ namespace Shared { }; Global(); - + + static void notify(const QString& account, const Shared::Message& msg); + static Global* getInstance(); static QString getName(Availability av); static QString getName(ConnectionState cs); @@ -122,6 +130,8 @@ namespace Shared { private: static Global* instance; + Models::Roster* rosterModel; + QDBusInterface dbus; std::map pluginSupport; std::map fileCache; diff --git a/ui/models/roster.cpp b/ui/models/roster.cpp index 1355fe3..e5ada43 100644 --- a/ui/models/roster.cpp +++ b/ui/models/roster.cpp @@ -763,7 +763,7 @@ void Models::Roster::removeAccount(const QString& account) acc->deleteLater(); } -QString Models::Roster::getContactName(const QString& account, const QString& jid) +QString Models::Roster::getContactName(const QString& account, const QString& jid) const { ElId id(account, jid); std::map::const_iterator cItr = contacts.find(id); @@ -907,7 +907,7 @@ bool Models::Roster::groupHasContact(const QString& account, const QString& grou } } -QString Models::Roster::getContactIconPath(const QString& account, const QString& jid, const QString& resource) +QString Models::Roster::getContactIconPath(const QString& account, const QString& jid, const QString& resource) const { ElId id(account, jid); std::map::const_iterator cItr = contacts.find(id); diff --git a/ui/models/roster.h b/ui/models/roster.h index 08d5afc..28f4d30 100644 --- a/ui/models/roster.h +++ b/ui/models/roster.h @@ -65,7 +65,7 @@ public: void addRoomParticipant(const QString& account, const QString& jid, const QString& name, const QMap& data); void changeRoomParticipant(const QString& account, const QString& jid, const QString& name, const QMap& data); void removeRoomParticipant(const QString& account, const QString& jid, const QString& name); - QString getContactName(const QString& account, const QString& jid); + QString getContactName(const QString& account, const QString& jid) const; QVariant data ( const QModelIndex& index, int role ) const override; Qt::ItemFlags flags(const QModelIndex &index) const override; @@ -77,7 +77,7 @@ public: std::deque groupList(const QString& account) const; bool groupHasContact(const QString& account, const QString& group, const QString& contactJID) const; - QString getContactIconPath(const QString& account, const QString& jid, const QString& resource); + QString getContactIconPath(const QString& account, const QString& jid, const QString& resource) const; Account* getAccount(const QString& name); QModelIndex getAccountIndex(const QString& name); QModelIndex getGroupIndex(const QString& account, const QString& name); diff --git a/ui/squawk.cpp b/ui/squawk.cpp index a0f16b2..a08f38b 100644 --- a/ui/squawk.cpp +++ b/ui/squawk.cpp @@ -28,10 +28,9 @@ Squawk::Squawk(QWidget *parent) : preferences(nullptr), about(nullptr), dialogueQueue(this), - rosterModel(), + rosterModel(*(Shared::Global::getInstance()->rosterModel)), conversations(), contextMenu(new QMenu()), - dbus("org.freedesktop.Notifications", "/org/freedesktop/Notifications", "org.freedesktop.Notifications", QDBusConnection::sessionBus()), vCards(), currentConversation(nullptr), restoreSelection(), @@ -441,33 +440,7 @@ void Squawk::changeMessage(const QString& account, const QString& jid, const QSt void Squawk::notify(const QString& account, const Shared::Message& msg) { - QString name = QString(rosterModel.getContactName(account, msg.getPenPalJid())); - QString path = QString(rosterModel.getContactIconPath(account, msg.getPenPalJid(), msg.getPenPalResource())); - QVariantList args; - args << QString(QCoreApplication::applicationName()); - args << QVariant(QVariant::UInt); //TODO some normal id - if (path.size() > 0) { - args << path; - } else { - args << QString("mail-message"); //TODO should here better be unknown user icon? - } - if (msg.getType() == Shared::Message::groupChat) { - args << msg.getFromResource() + " from " + name; - } else { - args << name; - } - - QString body(msg.getBody()); - QString oob(msg.getOutOfBandUrl()); - if (body == oob) { - body = tr("Attached file"); - } - - args << body; - args << QStringList(); - args << QVariantMap(); - args << 3000; - dbus.callWithArgumentList(QDBus::AutoDetect, "Notify", args); + Shared::Global::notify(account, msg); } void Squawk::onConversationMessage(const Shared::Message& msg) diff --git a/ui/squawk.h b/ui/squawk.h index 5a77f17..aa52153 100644 --- a/ui/squawk.h +++ b/ui/squawk.h @@ -22,7 +22,6 @@ #include #include #include -#include #include #include @@ -43,6 +42,7 @@ #include "dialogqueue.h" #include "shared/shared.h" +#include "shared/global.h" namespace Ui { class Squawk; @@ -124,10 +124,9 @@ private: Settings* preferences; About* about; DialogQueue dialogueQueue; - Models::Roster rosterModel; + Models::Roster& rosterModel; Conversations conversations; QMenu* contextMenu; - QDBusInterface dbus; std::map vCards; Conversation* currentConversation; QModelIndex restoreSelection; From 83cb2201752e225b0bfb9ce5d85caafa3e8cf97e Mon Sep 17 00:00:00 2001 From: blue Date: Tue, 19 Apr 2022 20:24:41 +0300 Subject: [PATCH 3/7] better notification sending, edited message now modifies notification (or sends), little structure change --- core/CMakeLists.txt | 7 +------ core/networkaccess.h | 2 +- core/rosteritem.h | 2 +- core/storage/CMakeLists.txt | 8 ++++++++ core/{ => storage}/archive.cpp | 0 core/{ => storage}/archive.h | 0 core/{ => storage}/storage.cpp | 0 core/{ => storage}/storage.h | 0 core/{ => storage}/urlstorage.cpp | 0 core/{ => storage}/urlstorage.h | 0 shared/global.cpp | 14 ++++++++++---- ui/models/roster.cpp | 8 ++++---- ui/widgets/messageline/messagefeed.cpp | 6 ++++++ 13 files changed, 31 insertions(+), 16 deletions(-) create mode 100644 core/storage/CMakeLists.txt rename core/{ => storage}/archive.cpp (100%) rename core/{ => storage}/archive.h (100%) rename core/{ => storage}/storage.cpp (100%) rename core/{ => storage}/storage.h (100%) rename core/{ => storage}/urlstorage.cpp (100%) rename core/{ => storage}/urlstorage.h (100%) diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index 8b6fa69..8f49b11 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -8,8 +8,6 @@ target_sources(squawk PRIVATE account.h adapterfunctions.cpp adapterfunctions.h - archive.cpp - archive.h conference.cpp conference.h contact.cpp @@ -23,13 +21,10 @@ target_sources(squawk PRIVATE signalcatcher.h squawk.cpp squawk.h - storage.cpp - storage.h - urlstorage.cpp - urlstorage.h ) target_include_directories(squawk PRIVATE ${LMDB_INCLUDE_DIRS}) add_subdirectory(handlers) +add_subdirectory(storage) add_subdirectory(passwordStorageEngines) diff --git a/core/networkaccess.h b/core/networkaccess.h index 0b7bb7d..6ddfa99 100644 --- a/core/networkaccess.h +++ b/core/networkaccess.h @@ -30,7 +30,7 @@ #include -#include "urlstorage.h" +#include "storage/urlstorage.h" #include "shared/pathcheck.h" namespace Core { diff --git a/core/rosteritem.h b/core/rosteritem.h index d422e3f..5f99017 100644 --- a/core/rosteritem.h +++ b/core/rosteritem.h @@ -34,7 +34,7 @@ #include "shared/enums.h" #include "shared/message.h" #include "shared/vcard.h" -#include "archive.h" +#include "storage/archive.h" #include "adapterfunctions.h" namespace Core { diff --git a/core/storage/CMakeLists.txt b/core/storage/CMakeLists.txt new file mode 100644 index 0000000..4c263d5 --- /dev/null +++ b/core/storage/CMakeLists.txt @@ -0,0 +1,8 @@ +target_sources(squawk PRIVATE + archive.cpp + archive.h + storage.cpp + storage.h + urlstorage.cpp + urlstorage.h +) diff --git a/core/archive.cpp b/core/storage/archive.cpp similarity index 100% rename from core/archive.cpp rename to core/storage/archive.cpp diff --git a/core/archive.h b/core/storage/archive.h similarity index 100% rename from core/archive.h rename to core/storage/archive.h diff --git a/core/storage.cpp b/core/storage/storage.cpp similarity index 100% rename from core/storage.cpp rename to core/storage/storage.cpp diff --git a/core/storage.h b/core/storage/storage.h similarity index 100% rename from core/storage.h rename to core/storage/storage.h diff --git a/core/urlstorage.cpp b/core/storage/urlstorage.cpp similarity index 100% rename from core/urlstorage.cpp rename to core/storage/urlstorage.cpp diff --git a/core/urlstorage.h b/core/storage/urlstorage.h similarity index 100% rename from core/urlstorage.h rename to core/storage/urlstorage.h diff --git a/shared/global.cpp b/shared/global.cpp index 14ae90d..be660bd 100644 --- a/shared/global.cpp +++ b/shared/global.cpp @@ -357,8 +357,9 @@ void Shared::Global::notify(const QString& account, const Shared::Message& msg) QString name = QString(instance->rosterModel->getContactName(account, msg.getPenPalJid())); QString path = QString(instance->rosterModel->getContactIconPath(account, msg.getPenPalJid(), msg.getPenPalResource())); QVariantList args; - args << QString(QCoreApplication::applicationName()); - args << QVariant(QVariant::UInt); //TODO some normal id + args << QString(); + + args << qHash(msg.getId()); if (path.size() > 0) { args << path; } else { @@ -378,8 +379,13 @@ void Shared::Global::notify(const QString& account, const Shared::Message& msg) args << body; args << QStringList(); - args << QVariantMap(); - args << 3000; + args << QVariantMap({ + {"desktop-entry", QString(QCoreApplication::applicationName())}, + {"category", QString("message")}, + // {"sound-file", "/path/to/macaw/squawk"}, + {"sound-name", QString("message-new-instant")} + }); + args << -1; instance->dbus.callWithArgumentList(QDBus::AutoDetect, "Notify", args); } diff --git a/ui/models/roster.cpp b/ui/models/roster.cpp index e5ada43..b96ddda 100644 --- a/ui/models/roster.cpp +++ b/ui/models/roster.cpp @@ -801,10 +801,10 @@ void Models::Roster::addRoom(const QString& account, const QString jid, const QM } Room* room = new Room(acc, jid, data); - connect(room, &Contact::requestArchive, this, &Roster::onElementRequestArchive); - connect(room, &Contact::fileDownloadRequest, this, &Roster::fileDownloadRequest); - connect(room, &Contact::unnoticedMessage, this, &Roster::unnoticedMessage); - connect(room, &Contact::localPathInvalid, this, &Roster::localPathInvalid); + connect(room, &Room::requestArchive, this, &Roster::onElementRequestArchive); + connect(room, &Room::fileDownloadRequest, this, &Roster::fileDownloadRequest); + connect(room, &Room::unnoticedMessage, this, &Roster::unnoticedMessage); + connect(room, &Room::localPathInvalid, this, &Roster::localPathInvalid); rooms.insert(std::make_pair(id, room)); acc->appendChild(room); } diff --git a/ui/widgets/messageline/messagefeed.cpp b/ui/widgets/messageline/messagefeed.cpp index 33fbdd4..521e981 100644 --- a/ui/widgets/messageline/messagefeed.cpp +++ b/ui/widgets/messageline/messagefeed.cpp @@ -163,6 +163,12 @@ void Models::MessageFeed::changeMessage(const QString& id, const QMapgetForwarded() && changeRoles.count(MessageRoles::Text) > 0) { + unreadMessages->insert(id); + emit unreadMessagesCountChanged(); + emit unnoticedMessage(*msg); + } } } From 721d3a1a89fd4daf4b0ab0643aa48f3155f23954 Mon Sep 17 00:00:00 2001 From: blue Date: Fri, 22 Apr 2022 18:26:18 +0300 Subject: [PATCH 4/7] refactoring: UI squawk now belongs to a new class, it enables me doing trayed mode, when main window is destroyed --- CMakeLists.txt | 1 + core/CMakeLists.txt | 1 - core/main.cpp | 201 -------------- main/CMakeLists.txt | 7 + main/application.cpp | 476 ++++++++++++++++++++++++++++++++ main/application.h | 111 ++++++++ {ui => main}/dialogqueue.cpp | 31 ++- {ui => main}/dialogqueue.h | 18 +- main/main.cpp | 139 ++++++++++ shared/global.cpp | 40 --- shared/global.h | 11 - ui/CMakeLists.txt | 2 - ui/models/roster.cpp | 46 +++- ui/models/roster.h | 10 +- ui/squawk.cpp | 509 ++++++----------------------------- ui/squawk.h | 74 ++--- 16 files changed, 908 insertions(+), 769 deletions(-) delete mode 100644 core/main.cpp create mode 100644 main/CMakeLists.txt create mode 100644 main/application.cpp create mode 100644 main/application.h rename {ui => main}/dialogqueue.cpp (87%) rename {ui => main}/dialogqueue.h (83%) create mode 100644 main/main.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 85aa98a..75011d8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -148,6 +148,7 @@ if(CMAKE_COMPILER_IS_GNUCXX) target_compile_options(squawk PRIVATE ${COMPILE_OPTIONS}) endif(CMAKE_COMPILER_IS_GNUCXX) +add_subdirectory(main) add_subdirectory(core) add_subdirectory(external/simpleCrypt) add_subdirectory(packaging) diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index 8f49b11..6c7a3b5 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -12,7 +12,6 @@ target_sources(squawk PRIVATE conference.h contact.cpp contact.h - main.cpp networkaccess.cpp networkaccess.h rosteritem.cpp diff --git a/core/main.cpp b/core/main.cpp deleted file mode 100644 index 4fbb1f7..0000000 --- a/core/main.cpp +++ /dev/null @@ -1,201 +0,0 @@ -/* - * Squawk messenger. - * Copyright (C) 2019 Yury Gubich - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include "../shared/global.h" -#include "../shared/messageinfo.h" -#include "../shared/pathcheck.h" -#include "../ui/squawk.h" -#include "signalcatcher.h" -#include "squawk.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -int main(int argc, char *argv[]) -{ - qRegisterMetaType("Shared::Message"); - qRegisterMetaType("Shared::MessageInfo"); - qRegisterMetaType("Shared::VCard"); - qRegisterMetaType>("std::list"); - qRegisterMetaType>("std::list"); - qRegisterMetaType>("QSet"); - qRegisterMetaType("Shared::ConnectionState"); - qRegisterMetaType("Shared::Availability"); - - QApplication app(argc, argv); - SignalCatcher sc(&app); - - QApplication::setApplicationName("squawk"); - QApplication::setOrganizationName("macaw.me"); - QApplication::setApplicationDisplayName("Squawk"); - QApplication::setApplicationVersion("0.2.2"); - - QTranslator qtTranslator; - qtTranslator.load("qt_" + QLocale::system().name(), QLibraryInfo::location(QLibraryInfo::TranslationsPath)); - app.installTranslator(&qtTranslator); - - QTranslator myappTranslator; - QStringList shares = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation); - bool found = false; - for (QString share : shares) { - found = myappTranslator.load(QLocale(), QLatin1String("squawk"), ".", share + "/l10n"); - if (found) { - break; - } - } - if (!found) { - myappTranslator.load(QLocale(), QLatin1String("squawk"), ".", QCoreApplication::applicationDirPath()); - } - - app.installTranslator(&myappTranslator); - - QIcon icon; - icon.addFile(":images/logo.svg", QSize(16, 16)); - icon.addFile(":images/logo.svg", QSize(24, 24)); - icon.addFile(":images/logo.svg", QSize(32, 32)); - icon.addFile(":images/logo.svg", QSize(48, 48)); - icon.addFile(":images/logo.svg", QSize(64, 64)); - icon.addFile(":images/logo.svg", QSize(96, 96)); - icon.addFile(":images/logo.svg", QSize(128, 128)); - icon.addFile(":images/logo.svg", QSize(256, 256)); - icon.addFile(":images/logo.svg", QSize(512, 512)); - QApplication::setWindowIcon(icon); - - new Shared::Global(); //translates enums - - QSettings settings; - QVariant vs = settings.value("style"); - if (vs.isValid()) { - QString style = vs.toString().toLower(); - if (style != "system") { - Shared::Global::setStyle(style); - } - } - if (Shared::Global::supported("colorSchemeTools")) { - QVariant vt = settings.value("theme"); - if (vt.isValid()) { - QString theme = vt.toString(); - if (theme.toLower() != "system") { - Shared::Global::setTheme(theme); - } - } - } - QString path = Shared::downloadsPathCheck(); - if (path.size() > 0) { - settings.setValue("downloadsPath", path); - } else { - qDebug() << "couldn't initialize directory for downloads, quitting"; - return -1; - } - - - Squawk w; - w.show(); - - Core::Squawk* squawk = new Core::Squawk(); - QThread* coreThread = new QThread(); - squawk->moveToThread(coreThread); - - QObject::connect(&sc, &SignalCatcher::interrupt, &app, &QApplication::closeAllWindows); - - QObject::connect(coreThread, &QThread::started, squawk, &Core::Squawk::start); - QObject::connect(&app, &QApplication::lastWindowClosed, squawk, &Core::Squawk::stop); - QObject::connect(&app, &QApplication::lastWindowClosed, &w, &Squawk::writeSettings); - //QObject::connect(&app, &QApplication::aboutToQuit, &w, &QMainWindow::close); - QObject::connect(squawk, &Core::Squawk::quit, squawk, &Core::Squawk::deleteLater); - QObject::connect(squawk, &Core::Squawk::quit, coreThread, &QThread::quit, Qt::QueuedConnection); - QObject::connect(coreThread, &QThread::finished, &app, &QApplication::quit, Qt::QueuedConnection); - - QObject::connect(&w, &Squawk::newAccountRequest, squawk, &Core::Squawk::newAccountRequest); - QObject::connect(&w, &Squawk::modifyAccountRequest, squawk, &Core::Squawk::modifyAccountRequest); - QObject::connect(&w, &Squawk::removeAccountRequest, squawk, &Core::Squawk::removeAccountRequest); - QObject::connect(&w, &Squawk::connectAccount, squawk, &Core::Squawk::connectAccount); - QObject::connect(&w, &Squawk::disconnectAccount, squawk, &Core::Squawk::disconnectAccount); - QObject::connect(&w, &Squawk::changeState, squawk, &Core::Squawk::changeState); - QObject::connect(&w, &Squawk::sendMessage, squawk,&Core::Squawk::sendMessage); - QObject::connect(&w, &Squawk::replaceMessage, squawk,&Core::Squawk::replaceMessage); - QObject::connect(&w, &Squawk::resendMessage, squawk,&Core::Squawk::resendMessage); - QObject::connect(&w, &Squawk::requestArchive, squawk, &Core::Squawk::requestArchive); - QObject::connect(&w, &Squawk::subscribeContact, squawk, &Core::Squawk::subscribeContact); - QObject::connect(&w, &Squawk::unsubscribeContact, squawk, &Core::Squawk::unsubscribeContact); - QObject::connect(&w, &Squawk::addContactRequest, squawk, &Core::Squawk::addContactRequest); - QObject::connect(&w, &Squawk::removeContactRequest, squawk, &Core::Squawk::removeContactRequest); - QObject::connect(&w, &Squawk::setRoomJoined, squawk, &Core::Squawk::setRoomJoined); - QObject::connect(&w, &Squawk::setRoomAutoJoin, squawk, &Core::Squawk::setRoomAutoJoin); - QObject::connect(&w, &Squawk::removeRoomRequest, squawk, &Core::Squawk::removeRoomRequest); - QObject::connect(&w, &Squawk::addRoomRequest, squawk, &Core::Squawk::addRoomRequest); - QObject::connect(&w, &Squawk::fileDownloadRequest, squawk, &Core::Squawk::fileDownloadRequest); - QObject::connect(&w, &Squawk::addContactToGroupRequest, squawk, &Core::Squawk::addContactToGroupRequest); - QObject::connect(&w, &Squawk::removeContactFromGroupRequest, squawk, &Core::Squawk::removeContactFromGroupRequest); - QObject::connect(&w, &Squawk::renameContactRequest, squawk, &Core::Squawk::renameContactRequest); - QObject::connect(&w, &Squawk::requestVCard, squawk, &Core::Squawk::requestVCard); - QObject::connect(&w, &Squawk::uploadVCard, squawk, &Core::Squawk::uploadVCard); - QObject::connect(&w, &Squawk::responsePassword, squawk, &Core::Squawk::responsePassword); - QObject::connect(&w, &Squawk::localPathInvalid, squawk, &Core::Squawk::onLocalPathInvalid); - QObject::connect(&w, &Squawk::changeDownloadsPath, squawk, &Core::Squawk::changeDownloadsPath); - - QObject::connect(squawk, &Core::Squawk::newAccount, &w, &Squawk::newAccount); - QObject::connect(squawk, &Core::Squawk::addContact, &w, &Squawk::addContact); - QObject::connect(squawk, &Core::Squawk::changeAccount, &w, &Squawk::changeAccount); - QObject::connect(squawk, &Core::Squawk::removeAccount, &w, &Squawk::removeAccount); - QObject::connect(squawk, &Core::Squawk::addGroup, &w, &Squawk::addGroup); - QObject::connect(squawk, &Core::Squawk::removeGroup, &w, &Squawk::removeGroup); - QObject::connect(squawk, qOverload(&Core::Squawk::removeContact), - &w, qOverload(&Squawk::removeContact)); - QObject::connect(squawk, qOverload(&Core::Squawk::removeContact), - &w, qOverload(&Squawk::removeContact)); - QObject::connect(squawk, &Core::Squawk::changeContact, &w, &Squawk::changeContact); - QObject::connect(squawk, &Core::Squawk::addPresence, &w, &Squawk::addPresence); - QObject::connect(squawk, &Core::Squawk::removePresence, &w, &Squawk::removePresence); - QObject::connect(squawk, &Core::Squawk::stateChanged, &w, &Squawk::stateChanged); - QObject::connect(squawk, &Core::Squawk::accountMessage, &w, &Squawk::accountMessage); - QObject::connect(squawk, &Core::Squawk::changeMessage, &w, &Squawk::changeMessage); - QObject::connect(squawk, &Core::Squawk::responseArchive, &w, &Squawk::responseArchive); - QObject::connect(squawk, &Core::Squawk::addRoom, &w, &Squawk::addRoom); - QObject::connect(squawk, &Core::Squawk::changeRoom, &w, &Squawk::changeRoom); - QObject::connect(squawk, &Core::Squawk::removeRoom, &w, &Squawk::removeRoom); - QObject::connect(squawk, &Core::Squawk::addRoomParticipant, &w, &Squawk::addRoomParticipant); - QObject::connect(squawk, &Core::Squawk::changeRoomParticipant, &w, &Squawk::changeRoomParticipant); - QObject::connect(squawk, &Core::Squawk::removeRoomParticipant, &w, &Squawk::removeRoomParticipant); - QObject::connect(squawk, &Core::Squawk::fileDownloadComplete, &w, &Squawk::fileDownloadComplete); - QObject::connect(squawk, &Core::Squawk::fileUploadComplete, &w, &Squawk::fileUploadComplete); - QObject::connect(squawk, &Core::Squawk::fileProgress, &w, &Squawk::fileProgress); - QObject::connect(squawk, &Core::Squawk::fileError, &w, &Squawk::fileError); - QObject::connect(squawk, &Core::Squawk::responseVCard, &w, &Squawk::responseVCard); - QObject::connect(squawk, &Core::Squawk::requestPassword, &w, &Squawk::requestPassword); - QObject::connect(squawk, &Core::Squawk::ready, &w, &Squawk::readSettings); - - coreThread->start(); - int result = app.exec(); - - if (coreThread->isRunning()) { - //coreThread->wait(); - //todo if I uncomment that, the app will not quit if it has reconnected at least once - //it feels like a symptom of something badly desinged in the core thread - //need to investigate; - } - - return result; -} - diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt new file mode 100644 index 0000000..3c23932 --- /dev/null +++ b/main/CMakeLists.txt @@ -0,0 +1,7 @@ +target_sources(squawk PRIVATE + main.cpp + application.cpp + application.h + dialogqueue.cpp + dialogqueue.h +) diff --git a/main/application.cpp b/main/application.cpp new file mode 100644 index 0000000..f6ffe07 --- /dev/null +++ b/main/application.cpp @@ -0,0 +1,476 @@ +// Squawk messenger. +// Copyright (C) 2019 Yury Gubich +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "application.h" + +Application::Application(Core::Squawk* p_core): + QObject(), + availability(Shared::Availability::offline), + core(p_core), + squawk(nullptr), + notifications("org.freedesktop.Notifications", "/org/freedesktop/Notifications", "org.freedesktop.Notifications", QDBusConnection::sessionBus()), + roster(), + conversations(), + dialogueQueue(roster), + nowQuitting(false), + destroyingSquawk(false) +{ + connect(&roster, &Models::Roster::unnoticedMessage, this, &Application::notify); + + + //connecting myself to the backed + connect(this, &Application::changeState, core, &Core::Squawk::changeState); + connect(this, &Application::setRoomJoined, core, &Core::Squawk::setRoomJoined); + connect(this, &Application::setRoomAutoJoin, core, &Core::Squawk::setRoomAutoJoin); + connect(this, &Application::subscribeContact, core, &Core::Squawk::subscribeContact); + connect(this, &Application::unsubscribeContact, core, &Core::Squawk::unsubscribeContact); + connect(this, &Application::replaceMessage, core, &Core::Squawk::replaceMessage); + connect(this, &Application::sendMessage, core, &Core::Squawk::sendMessage); + connect(this, &Application::resendMessage, core, &Core::Squawk::resendMessage); + connect(&roster, &Models::Roster::requestArchive, + std::bind(&Core::Squawk::requestArchive, core, std::placeholders::_1, std::placeholders::_2, 20, std::placeholders::_3)); + + connect(&dialogueQueue, &DialogQueue::modifyAccountRequest, core, &Core::Squawk::modifyAccountRequest); + connect(&dialogueQueue, &DialogQueue::responsePassword, core, &Core::Squawk::responsePassword); + connect(&dialogueQueue, &DialogQueue::disconnectAccount, core, &Core::Squawk::disconnectAccount); + + connect(&roster, &Models::Roster::fileDownloadRequest, core, &Core::Squawk::fileDownloadRequest); + connect(&roster, &Models::Roster::localPathInvalid, core, &Core::Squawk::onLocalPathInvalid); + + + //coonecting backend to myself + connect(core, &Core::Squawk::stateChanged, this, &Application::stateChanged); + + connect(core, &Core::Squawk::accountMessage, &roster, &Models::Roster::addMessage); + connect(core, &Core::Squawk::responseArchive, &roster, &Models::Roster::responseArchive); + connect(core, &Core::Squawk::changeMessage, &roster, &Models::Roster::changeMessage); + + connect(core, &Core::Squawk::newAccount, &roster, &Models::Roster::addAccount); + connect(core, &Core::Squawk::changeAccount, this, &Application::changeAccount); + connect(core, &Core::Squawk::removeAccount, this, &Application::removeAccount); + + connect(core, &Core::Squawk::addContact, this, &Application::addContact); + connect(core, &Core::Squawk::addGroup, this, &Application::addGroup); + connect(core, &Core::Squawk::removeGroup, &roster, &Models::Roster::removeGroup); + connect(core, qOverload(&Core::Squawk::removeContact), + &roster, qOverload(&Models::Roster::removeContact)); + connect(core, qOverload(&Core::Squawk::removeContact), + &roster, qOverload(&Models::Roster::removeContact)); + connect(core, &Core::Squawk::changeContact, &roster, &Models::Roster::changeContact); + connect(core, &Core::Squawk::addPresence, &roster, &Models::Roster::addPresence); + connect(core, &Core::Squawk::removePresence, &roster, &Models::Roster::removePresence); + + connect(core, &Core::Squawk::addRoom, &roster, &Models::Roster::addRoom); + connect(core, &Core::Squawk::changeRoom, &roster, &Models::Roster::changeRoom); + connect(core, &Core::Squawk::removeRoom, &roster, &Models::Roster::removeRoom); + connect(core, &Core::Squawk::addRoomParticipant, &roster, &Models::Roster::addRoomParticipant); + connect(core, &Core::Squawk::changeRoomParticipant, &roster, &Models::Roster::changeRoomParticipant); + connect(core, &Core::Squawk::removeRoomParticipant, &roster, &Models::Roster::removeRoomParticipant); + + + connect(core, &Core::Squawk::fileDownloadComplete, std::bind(&Models::Roster::fileComplete, &roster, std::placeholders::_1, false)); + connect(core, &Core::Squawk::fileUploadComplete, std::bind(&Models::Roster::fileComplete, &roster, std::placeholders::_1, true)); + connect(core, &Core::Squawk::fileProgress, &roster, &Models::Roster::fileProgress); + connect(core, &Core::Squawk::fileError, &roster, &Models::Roster::fileError); + + connect(core, &Core::Squawk::requestPassword, this, &Application::requestPassword); + connect(core, &Core::Squawk::ready, this, &Application::readSettings); + +} + +Application::~Application() {} + +void Application::quit() +{ + if (!nowQuitting) { + nowQuitting = true; + emit quitting(); + + writeSettings(); + for (Conversations::const_iterator itr = conversations.begin(), end = conversations.end(); itr != end; ++itr) { + disconnect(itr->second, &Conversation::destroyed, this, &Application::onConversationClosed); + itr->second->close(); + } + conversations.clear(); + dialogueQueue.quit(); + + if (squawk != nullptr) { + squawk->close(); + } + if (!destroyingSquawk) { + checkForTheLastWindow(); + } + } +} + +void Application::checkForTheLastWindow() +{ + if (QApplication::topLevelWidgets().size() > 0) { + emit readyToQuit(); + } else { + connect(qApp, &QApplication::lastWindowClosed, this, &Application::readyToQuit); + } +} + +void Application::createMainWindow() +{ + if (squawk == nullptr) { + squawk = new Squawk(roster); + + connect(squawk, &Squawk::notify, this, &Application::notify); + connect(squawk, &Squawk::changeSubscription, this, &Application::changeSubscription); + connect(squawk, &Squawk::openedConversation, this, &Application::onSquawkOpenedConversation); + connect(squawk, &Squawk::openConversation, this, &Application::openConversation); + connect(squawk, &Squawk::changeState, this, &Application::setState); + connect(squawk, &Squawk::closing, this, &Application::onSquawkClosing); + + connect(squawk, &Squawk::modifyAccountRequest, core, &Core::Squawk::modifyAccountRequest); + connect(squawk, &Squawk::newAccountRequest, core, &Core::Squawk::newAccountRequest); + connect(squawk, &Squawk::removeAccountRequest, core, &Core::Squawk::removeAccountRequest); + connect(squawk, &Squawk::connectAccount, core, &Core::Squawk::connectAccount); + connect(squawk, &Squawk::disconnectAccount, core, &Core::Squawk::disconnectAccount); + + connect(squawk, &Squawk::addContactRequest, core, &Core::Squawk::addContactRequest); + connect(squawk, &Squawk::removeContactRequest, core, &Core::Squawk::removeContactRequest); + connect(squawk, &Squawk::removeRoomRequest, core, &Core::Squawk::removeRoomRequest); + connect(squawk, &Squawk::addRoomRequest, core, &Core::Squawk::addRoomRequest); + connect(squawk, &Squawk::addContactToGroupRequest, core, &Core::Squawk::addContactToGroupRequest); + connect(squawk, &Squawk::removeContactFromGroupRequest, core, &Core::Squawk::removeContactFromGroupRequest); + connect(squawk, &Squawk::renameContactRequest, core, &Core::Squawk::renameContactRequest); + connect(squawk, &Squawk::requestVCard, core, &Core::Squawk::requestVCard); + connect(squawk, &Squawk::uploadVCard, core, &Core::Squawk::uploadVCard); + connect(squawk, &Squawk::changeDownloadsPath, core, &Core::Squawk::changeDownloadsPath); + + connect(core, &Core::Squawk::responseVCard, squawk, &Squawk::responseVCard); + + dialogueQueue.setParentWidnow(squawk); + squawk->stateChanged(availability); + squawk->show(); + } +} + +void Application::onSquawkClosing() +{ + dialogueQueue.setParentWidnow(nullptr); + + disconnect(core, &Core::Squawk::responseVCard, squawk, &Squawk::responseVCard); + + destroyingSquawk = true; + squawk->deleteLater(); + squawk = nullptr; + + //for now + quit(); +} + +void Application::onSquawkDestroyed() { + destroyingSquawk = false; + if (nowQuitting) { + checkForTheLastWindow(); + } +} + + +void Application::notify(const QString& account, const Shared::Message& msg) +{ + QString name = QString(roster.getContactName(account, msg.getPenPalJid())); + QString path = QString(roster.getContactIconPath(account, msg.getPenPalJid(), msg.getPenPalResource())); + QVariantList args; + args << QString(); + + args << qHash(msg.getId()); + if (path.size() > 0) { + args << path; + } else { + args << QString("mail-message"); //TODO should here better be unknown user icon? + } + if (msg.getType() == Shared::Message::groupChat) { + args << msg.getFromResource() + " from " + name; + } else { + args << name; + } + + QString body(msg.getBody()); + QString oob(msg.getOutOfBandUrl()); + if (body == oob) { + body = tr("Attached file"); + } + + args << body; + args << QStringList(); + args << QVariantMap({ + {"desktop-entry", QString(QCoreApplication::applicationName())}, + {"category", QString("message")}, + // {"sound-file", "/path/to/macaw/squawk"}, + {"sound-name", QString("message-new-instant")} + }); + args << -1; + notifications.callWithArgumentList(QDBus::AutoDetect, "Notify", args); + + if (squawk != nullptr) { + QApplication::alert(squawk); + } +} + +void Application::setState(Shared::Availability p_availability) +{ + if (availability != p_availability) { + availability = p_availability; + emit changeState(availability); + } +} + +void Application::stateChanged(Shared::Availability state) +{ + availability = state; + if (squawk != nullptr) { + squawk->stateChanged(state); + } +} + +void Application::readSettings() +{ + QSettings settings; + settings.beginGroup("ui"); + int avail; + if (settings.contains("availability")) { + avail = settings.value("availability").toInt(); + } else { + avail = static_cast(Shared::Availability::online); + } + settings.endGroup(); + + setState(Shared::Global::fromInt(avail)); + createMainWindow(); +} + +void Application::writeSettings() +{ + QSettings settings; + settings.setValue("availability", static_cast(availability)); +} + +void Application::requestPassword(const QString& account, bool authenticationError) { + if (authenticationError) { + dialogueQueue.addAction(account, DialogQueue::askCredentials); + } else { + dialogueQueue.addAction(account, DialogQueue::askPassword); + } + +} +void Application::onConversationClosed() +{ + Conversation* conv = static_cast(sender()); + Models::Roster::ElId id(conv->getAccount(), conv->getJid()); + Conversations::const_iterator itr = conversations.find(id); + if (itr != conversations.end()) { + conversations.erase(itr); + } + if (conv->isMuc) { + Room* room = static_cast(conv); + if (!room->autoJoined()) { + emit setRoomJoined(id.account, id.name, false); + } + } +} + +void Application::changeSubscription(const Models::Roster::ElId& id, bool subscribe) +{ + Models::Item::Type type = roster.getContactType(id); + + switch (type) { + case Models::Item::contact: + if (subscribe) { + emit subscribeContact(id.account, id.name, ""); + } else { + emit unsubscribeContact(id.account, id.name, ""); + } + break; + case Models::Item::room: + setRoomAutoJoin(id.account, id.name, subscribe); + if (!isConverstationOpened(id)) { + emit setRoomJoined(id.account, id.name, subscribe); + } + break; + default: + break; + } +} + +void Application::subscribeConversation(Conversation* conv) +{ + connect(conv, &Conversation::destroyed, this, &Application::onConversationClosed); + connect(conv, &Conversation::sendMessage, this, &Application::onConversationMessage); + connect(conv, &Conversation::replaceMessage, this, &Application::onConversationReplaceMessage); + connect(conv, &Conversation::resendMessage, this, &Application::onConversationResend); + connect(conv, &Conversation::notifyableMessage, this, &Application::notify); +} + +void Application::openConversation(const Models::Roster::ElId& id, const QString& resource) +{ + Conversations::const_iterator itr = conversations.find(id); + Models::Account* acc = roster.getAccount(id.account); + Conversation* conv = nullptr; + bool created = false; + if (itr != conversations.end()) { + conv = itr->second; + } else { + Models::Element* el = roster.getElement(id); + if (el != NULL) { + if (el->type == Models::Item::room) { + created = true; + Models::Room* room = static_cast(el); + conv = new Room(acc, room); + if (!room->getJoined()) { + emit setRoomJoined(id.account, id.name, true); + } + } else if (el->type == Models::Item::contact) { + created = true; + conv = new Chat(acc, static_cast(el)); + } + } + } + + if (conv != nullptr) { + if (created) { + conv->setAttribute(Qt::WA_DeleteOnClose); + subscribeConversation(conv); + conversations.insert(std::make_pair(id, conv)); + } + + conv->show(); + conv->raise(); + conv->activateWindow(); + + if (resource.size() > 0) { + conv->setPalResource(resource); + } + } +} + +void Application::onConversationMessage(const Shared::Message& msg) +{ + Conversation* conv = static_cast(sender()); + QString acc = conv->getAccount(); + + roster.addMessage(acc, msg); + emit sendMessage(acc, msg); +} + +void Application::onConversationReplaceMessage(const QString& originalId, const Shared::Message& msg) +{ + Conversation* conv = static_cast(sender()); + QString acc = conv->getAccount(); + + roster.changeMessage(acc, msg.getPenPalJid(), originalId, { + {"state", static_cast(Shared::Message::State::pending)} + }); + emit replaceMessage(acc, originalId, msg); +} + +void Application::onConversationResend(const QString& id) +{ + Conversation* conv = static_cast(sender()); + QString acc = conv->getAccount(); + QString jid = conv->getJid(); + + emit resendMessage(acc, jid, id); +} + +void Application::onSquawkOpenedConversation() { + subscribeConversation(squawk->currentConversation); + Models::Roster::ElId id = squawk->currentConversationId(); + + const Models::Element* el = roster.getElementConst(id); + if (el != NULL && el->isRoom() && !static_cast(el)->getJoined()) { + emit setRoomJoined(id.account, id.name, true); + } +} + +void Application::removeAccount(const QString& account) +{ + Conversations::const_iterator itr = conversations.begin(); + while (itr != conversations.end()) { + if (itr->first.account == account) { + Conversations::const_iterator lItr = itr; + ++itr; + Conversation* conv = lItr->second; + disconnect(conv, &Conversation::destroyed, this, &Application::onConversationClosed); + conv->close(); + conversations.erase(lItr); + } else { + ++itr; + } + } + + if (squawk != nullptr && squawk->currentConversationId().account == account) { + squawk->closeCurrentConversation(); + } + + roster.removeAccount(account); +} + +void Application::changeAccount(const QString& account, const QMap& data) +{ + for (QMap::const_iterator itr = data.begin(), end = data.end(); itr != end; ++itr) { + QString attr = itr.key(); + roster.updateAccount(account, attr, *itr); + } +} + +void Application::addContact(const QString& account, const QString& jid, const QString& group, const QMap& data) +{ + roster.addContact(account, jid, group, data); + + if (squawk != nullptr) { + QSettings settings; + settings.beginGroup("ui"); + settings.beginGroup("roster"); + settings.beginGroup(account); + if (settings.value("expanded", false).toBool()) { + QModelIndex ind = roster.getAccountIndex(account); + squawk->expand(ind); + } + settings.endGroup(); + settings.endGroup(); + settings.endGroup(); + } +} + +void Application::addGroup(const QString& account, const QString& name) +{ + roster.addGroup(account, name); + + if (squawk != nullptr) { + QSettings settings; + settings.beginGroup("ui"); + settings.beginGroup("roster"); + settings.beginGroup(account); + if (settings.value("expanded", false).toBool()) { + QModelIndex ind = roster.getAccountIndex(account); + squawk->expand(ind); + if (settings.value(name + "/expanded", false).toBool()) { + squawk->expand(roster.getGroupIndex(account, name)); + } + } + settings.endGroup(); + settings.endGroup(); + settings.endGroup(); + } +} + +bool Application::isConverstationOpened(const Models::Roster::ElId& id) const { + return (conversations.count(id) > 0) || (squawk != nullptr && squawk->currentConversationId() == id);} diff --git a/main/application.h b/main/application.h new file mode 100644 index 0000000..15adce7 --- /dev/null +++ b/main/application.h @@ -0,0 +1,111 @@ +// Squawk messenger. +// Copyright (C) 2019 Yury Gubich +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#ifndef APPLICATION_H +#define APPLICATION_H + +#include + +#include +#include + +#include "dialogqueue.h" +#include "core/squawk.h" + +#include "ui/squawk.h" +#include "ui/models/roster.h" +#include "ui/widgets/conversation.h" + +#include "shared/message.h" +#include "shared/enums.h" + +/** + * @todo write docs + */ +class Application : public QObject +{ + Q_OBJECT +public: + Application(Core::Squawk* core); + ~Application(); + + bool isConverstationOpened(const Models::Roster::ElId& id) const; + +signals: + void sendMessage(const QString& account, const Shared::Message& data); + void replaceMessage(const QString& account, const QString& originalId, const Shared::Message& data); + void resendMessage(const QString& account, const QString& jid, const QString& id); + + void changeState(Shared::Availability state); + + void setRoomJoined(const QString& account, const QString& jid, bool joined); + void setRoomAutoJoin(const QString& account, const QString& jid, bool joined); + void subscribeContact(const QString& account, const QString& jid, const QString& reason); + void unsubscribeContact(const QString& account, const QString& jid, const QString& reason); + + void quitting(); + void readyToQuit(); + +public slots: + void readSettings(); + void quit(); + +protected slots: + void notify(const QString& account, const Shared::Message& msg); + void setState(Shared::Availability availability); + + void changeAccount(const QString& account, const QMap& data); + void removeAccount(const QString& account); + void openConversation(const Models::Roster::ElId& id, const QString& resource = ""); + + void addGroup(const QString& account, const QString& name); + void addContact(const QString& account, const QString& jid, const QString& group, const QMap& data); + + void requestPassword(const QString& account, bool authenticationError); + + void writeSettings(); + +private slots: + void onConversationClosed(); + void changeSubscription(const Models::Roster::ElId& id, bool subscribe); + void onSquawkOpenedConversation(); + void onConversationMessage(const Shared::Message& msg); + void onConversationReplaceMessage(const QString& originalId, const Shared::Message& msg); + void onConversationResend(const QString& id); + void stateChanged(Shared::Availability state); + void onSquawkClosing(); + void onSquawkDestroyed(); + +private: + void createMainWindow(); + void subscribeConversation(Conversation* conv); + void checkForTheLastWindow(); + +private: + typedef std::map Conversations; + + Shared::Availability availability; + Core::Squawk* core; + Squawk* squawk; + QDBusInterface notifications; + Models::Roster roster; + Conversations conversations; + DialogQueue dialogueQueue; + bool nowQuitting; + bool destroyingSquawk; +}; + +#endif // APPLICATION_H diff --git a/ui/dialogqueue.cpp b/main/dialogqueue.cpp similarity index 87% rename from ui/dialogqueue.cpp rename to main/dialogqueue.cpp index 02f8688..d7b4570 100644 --- a/ui/dialogqueue.cpp +++ b/main/dialogqueue.cpp @@ -15,10 +15,9 @@ // along with this program. If not, see . #include "dialogqueue.h" -#include "squawk.h" #include -DialogQueue::DialogQueue(Squawk* p_squawk): +DialogQueue::DialogQueue(const Models::Roster& p_roster): QObject(), currentSource(), currentAction(none), @@ -26,7 +25,8 @@ DialogQueue::DialogQueue(Squawk* p_squawk): collection(queue.get<0>()), sequence(queue.get<1>()), prompt(nullptr), - squawk(p_squawk) + parent(nullptr), + roster(p_roster) { } @@ -34,6 +34,19 @@ DialogQueue::~DialogQueue() { } +void DialogQueue::quit() +{ + queue.clear(); + if (currentAction != none) { + actionDone(); + } +} + +void DialogQueue::setParentWidnow(QMainWindow* p_parent) +{ + parent = p_parent; +} + bool DialogQueue::addAction(const QString& source, DialogQueue::Action action) { if (action == none) { @@ -77,7 +90,7 @@ void DialogQueue::performNextAction() actionDone(); break; case askPassword: { - QInputDialog* dialog = new QInputDialog(squawk); + QInputDialog* dialog = new QInputDialog(parent); prompt = dialog; connect(dialog, &QDialog::accepted, this, &DialogQueue::onPropmtAccepted); connect(dialog, &QDialog::rejected, this, &DialogQueue::onPropmtRejected); @@ -90,11 +103,11 @@ void DialogQueue::performNextAction() } break; case askCredentials: { - CredentialsPrompt* dialog = new CredentialsPrompt(squawk); + CredentialsPrompt* dialog = new CredentialsPrompt(parent); prompt = dialog; connect(dialog, &QDialog::accepted, this, &DialogQueue::onPropmtAccepted); connect(dialog, &QDialog::rejected, this, &DialogQueue::onPropmtRejected); - Models::Account* acc = squawk->rosterModel.getAccount(currentSource); + const Models::Account* acc = roster.getAccountConst(currentSource); dialog->setAccount(currentSource); dialog->setLogin(acc->getLogin()); dialog->setPassword(acc->getPassword()); @@ -111,12 +124,12 @@ void DialogQueue::onPropmtAccepted() break; case askPassword: { QInputDialog* dialog = static_cast(prompt); - emit squawk->responsePassword(currentSource, dialog->textValue()); + emit responsePassword(currentSource, dialog->textValue()); } break; case askCredentials: { CredentialsPrompt* dialog = static_cast(prompt); - emit squawk->modifyAccountRequest(currentSource, { + emit modifyAccountRequest(currentSource, { {"login", dialog->getLogin()}, {"password", dialog->getPassword()} }); @@ -133,7 +146,7 @@ void DialogQueue::onPropmtRejected() break; case askPassword: case askCredentials: - emit squawk->disconnectAccount(currentSource); + emit disconnectAccount(currentSource); break; } actionDone(); diff --git a/ui/dialogqueue.h b/main/dialogqueue.h similarity index 83% rename from ui/dialogqueue.h rename to main/dialogqueue.h index bfc1f21..b0da9dc 100644 --- a/ui/dialogqueue.h +++ b/main/dialogqueue.h @@ -19,14 +19,14 @@ #include #include +#include #include #include #include #include - -class Squawk; +#include class DialogQueue : public QObject { @@ -38,12 +38,21 @@ public: askCredentials }; - DialogQueue(Squawk* squawk); + DialogQueue(const Models::Roster& roster); ~DialogQueue(); bool addAction(const QString& source, Action action); bool cancelAction(const QString& source, Action action); +signals: + void modifyAccountRequest(const QString&, const QMap&); + void responsePassword(const QString& account, const QString& password); + void disconnectAccount(const QString&); + +public: + void setParentWidnow(QMainWindow* parent); + void quit(); + private: void performNextAction(); void actionDone(); @@ -85,7 +94,8 @@ private: Sequence& sequence; QDialog* prompt; - Squawk* squawk; + QMainWindow* parent; + const Models::Roster& roster; }; #endif // DIALOGQUEUE_H diff --git a/main/main.cpp b/main/main.cpp new file mode 100644 index 0000000..77719a2 --- /dev/null +++ b/main/main.cpp @@ -0,0 +1,139 @@ +/* + * Squawk messenger. + * Copyright (C) 2019 Yury Gubich + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "shared/global.h" +#include "shared/messageinfo.h" +#include "shared/pathcheck.h" +#include "main/application.h" +#include "core/signalcatcher.h" +#include "core/squawk.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +int main(int argc, char *argv[]) +{ + qRegisterMetaType("Shared::Message"); + qRegisterMetaType("Shared::MessageInfo"); + qRegisterMetaType("Shared::VCard"); + qRegisterMetaType>("std::list"); + qRegisterMetaType>("std::list"); + qRegisterMetaType>("QSet"); + qRegisterMetaType("Shared::ConnectionState"); + qRegisterMetaType("Shared::Availability"); + + QApplication app(argc, argv); + SignalCatcher sc(&app); + + QApplication::setApplicationName("squawk"); + QApplication::setOrganizationName("macaw.me"); + QApplication::setApplicationDisplayName("Squawk"); + QApplication::setApplicationVersion("0.2.2"); + + QTranslator qtTranslator; + qtTranslator.load("qt_" + QLocale::system().name(), QLibraryInfo::location(QLibraryInfo::TranslationsPath)); + app.installTranslator(&qtTranslator); + + QTranslator myappTranslator; + QStringList shares = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation); + bool found = false; + for (QString share : shares) { + found = myappTranslator.load(QLocale(), QLatin1String("squawk"), ".", share + "/l10n"); + if (found) { + break; + } + } + if (!found) { + myappTranslator.load(QLocale(), QLatin1String("squawk"), ".", QCoreApplication::applicationDirPath()); + } + + app.installTranslator(&myappTranslator); + + QIcon icon; + icon.addFile(":images/logo.svg", QSize(16, 16)); + icon.addFile(":images/logo.svg", QSize(24, 24)); + icon.addFile(":images/logo.svg", QSize(32, 32)); + icon.addFile(":images/logo.svg", QSize(48, 48)); + icon.addFile(":images/logo.svg", QSize(64, 64)); + icon.addFile(":images/logo.svg", QSize(96, 96)); + icon.addFile(":images/logo.svg", QSize(128, 128)); + icon.addFile(":images/logo.svg", QSize(256, 256)); + icon.addFile(":images/logo.svg", QSize(512, 512)); + QApplication::setWindowIcon(icon); + + new Shared::Global(); //translates enums + + QSettings settings; + QVariant vs = settings.value("style"); + if (vs.isValid()) { + QString style = vs.toString().toLower(); + if (style != "system") { + Shared::Global::setStyle(style); + } + } + if (Shared::Global::supported("colorSchemeTools")) { + QVariant vt = settings.value("theme"); + if (vt.isValid()) { + QString theme = vt.toString(); + if (theme.toLower() != "system") { + Shared::Global::setTheme(theme); + } + } + } + QString path = Shared::downloadsPathCheck(); + if (path.size() > 0) { + settings.setValue("downloadsPath", path); + } else { + qDebug() << "couldn't initialize directory for downloads, quitting"; + return -1; + } + + Core::Squawk* squawk = new Core::Squawk(); + QThread* coreThread = new QThread(); + squawk->moveToThread(coreThread); + + Application application(squawk); + + QObject::connect(&sc, &SignalCatcher::interrupt, &application, &Application::quit); + + QObject::connect(coreThread, &QThread::started, squawk, &Core::Squawk::start); + QObject::connect(&application, &Application::quitting, squawk, &Core::Squawk::stop); + //QObject::connect(&app, &QApplication::aboutToQuit, &w, &QMainWindow::close); + QObject::connect(squawk, &Core::Squawk::quit, squawk, &Core::Squawk::deleteLater); + QObject::connect(squawk, &Core::Squawk::destroyed, coreThread, &QThread::quit, Qt::QueuedConnection); + QObject::connect(coreThread, &QThread::finished, &app, &QApplication::quit, Qt::QueuedConnection); + + coreThread->start(); + int result = app.exec(); + + if (coreThread->isRunning()) { + //coreThread->wait(); + //todo if I uncomment that, the app will not quit if it has reconnected at least once + //it feels like a symptom of something badly desinged in the core thread + //need to investigate; + } + + return result; +} + diff --git a/shared/global.cpp b/shared/global.cpp index be660bd..6519952 100644 --- a/shared/global.cpp +++ b/shared/global.cpp @@ -95,8 +95,6 @@ Shared::Global::Global(): }), defaultSystemStyle(QApplication::style()->objectName()), defaultSystemPalette(QApplication::palette()), - rosterModel(new Models::Roster()), - dbus("org.freedesktop.Notifications", "/org/freedesktop/Notifications", "org.freedesktop.Notifications", QDBusConnection::sessionBus()), pluginSupport({ {"KWallet", false}, {"openFileManagerWindowJob", false}, @@ -352,44 +350,6 @@ void Shared::Global::setStyle(const QString& style) } } -void Shared::Global::notify(const QString& account, const Shared::Message& msg) -{ - QString name = QString(instance->rosterModel->getContactName(account, msg.getPenPalJid())); - QString path = QString(instance->rosterModel->getContactIconPath(account, msg.getPenPalJid(), msg.getPenPalResource())); - QVariantList args; - args << QString(); - - args << qHash(msg.getId()); - if (path.size() > 0) { - args << path; - } else { - args << QString("mail-message"); //TODO should here better be unknown user icon? - } - if (msg.getType() == Shared::Message::groupChat) { - args << msg.getFromResource() + " from " + name; - } else { - args << name; - } - - QString body(msg.getBody()); - QString oob(msg.getOutOfBandUrl()); - if (body == oob) { - body = tr("Attached file"); - } - - args << body; - args << QStringList(); - args << QVariantMap({ - {"desktop-entry", QString(QCoreApplication::applicationName())}, - {"category", QString("message")}, - // {"sound-file", "/path/to/macaw/squawk"}, - {"sound-name", QString("message-new-instant")} - }); - args << -1; - instance->dbus.callWithArgumentList(QDBus::AutoDetect, "Notify", args); -} - - #define FROM_INT_INPL(Enum) \ template<> \ Enum Shared::Global::fromInt(int src) \ diff --git a/shared/global.h b/shared/global.h index fcd8105..ebed931 100644 --- a/shared/global.h +++ b/shared/global.h @@ -42,18 +42,11 @@ #include #include #include -#include - -class Squawk; -namespace Models { - class Roster; -} namespace Shared { class Global { Q_DECLARE_TR_FUNCTIONS(Global) - friend class ::Squawk; public: struct FileInfo { enum class Preview { @@ -71,8 +64,6 @@ namespace Shared { Global(); - static void notify(const QString& account, const Shared::Message& msg); - static Global* getInstance(); static QString getName(Availability av); static QString getName(ConnectionState cs); @@ -130,8 +121,6 @@ namespace Shared { private: static Global* instance; - Models::Roster* rosterModel; - QDBusInterface dbus; std::map pluginSupport; std::map fileCache; diff --git a/ui/CMakeLists.txt b/ui/CMakeLists.txt index fcbb24c..296c289 100644 --- a/ui/CMakeLists.txt +++ b/ui/CMakeLists.txt @@ -2,8 +2,6 @@ target_sources(squawk PRIVATE squawk.cpp squawk.h squawk.ui - dialogqueue.cpp - dialogqueue.h ) add_subdirectory(models) diff --git a/ui/models/roster.cpp b/ui/models/roster.cpp index b96ddda..fef3e43 100644 --- a/ui/models/roster.cpp +++ b/ui/models/roster.cpp @@ -927,11 +927,29 @@ QString Models::Roster::getContactIconPath(const QString& account, const QString return path; } -Models::Account * Models::Roster::getAccount(const QString& name) +Models::Account * Models::Roster::getAccount(const QString& name) { + return const_cast(getAccountConst(name));} + +const Models::Account * Models::Roster::getAccountConst(const QString& name) const { + return accounts.at(name);} + +const Models::Element * Models::Roster::getElementConst(const Models::Roster::ElId& id) const { - return accounts.find(name)->second; + std::map::const_iterator cItr = contacts.find(id); + + if (cItr != contacts.end()) { + return cItr->second; + } else { + std::map::const_iterator rItr = rooms.find(id); + if (rItr != rooms.end()) { + return rItr->second; + } + } + + return NULL; } + QModelIndex Models::Roster::getAccountIndex(const QString& name) { std::map::const_iterator itr = accounts.find(name); @@ -1005,20 +1023,20 @@ void Models::Roster::fileError(const std::list& msgs, const Models::Element * Models::Roster::getElement(const Models::Roster::ElId& id) { - std::map::iterator cItr = contacts.find(id); - - if (cItr != contacts.end()) { - return cItr->second; - } else { - std::map::iterator rItr = rooms.find(id); - if (rItr != rooms.end()) { - return rItr->second; - } - } - - return NULL; + return const_cast(getElementConst(id)); } +Models::Item::Type Models::Roster::getContactType(const Models::Roster::ElId& id) const +{ + const Models::Element* el = getElementConst(id); + if (el == NULL) { + return Item::root; + } + + return el->type; +} + + void Models::Roster::onAccountReconnected() { Account* acc = static_cast(sender()); diff --git a/ui/models/roster.h b/ui/models/roster.h index 28f4d30..60adf13 100644 --- a/ui/models/roster.h +++ b/ui/models/roster.h @@ -46,6 +46,7 @@ public: Roster(QObject* parent = 0); ~Roster(); +public slots: void addAccount(const QMap &data); void updateAccount(const QString& account, const QString& field, const QVariant& value); void removeAccount(const QString& account); @@ -65,7 +66,12 @@ public: void addRoomParticipant(const QString& account, const QString& jid, const QString& name, const QMap& data); void changeRoomParticipant(const QString& account, const QString& jid, const QString& name, const QMap& data); void removeRoomParticipant(const QString& account, const QString& jid, const QString& name); + +public: QString getContactName(const QString& account, const QString& jid) const; + Item::Type getContactType(const Models::Roster::ElId& id) const; + const Element* getElementConst(const ElId& id) const; + Element* getElement(const ElId& id); QVariant data ( const QModelIndex& index, int role ) const override; Qt::ItemFlags flags(const QModelIndex &index) const override; @@ -79,6 +85,7 @@ public: bool groupHasContact(const QString& account, const QString& group, const QString& contactJID) const; QString getContactIconPath(const QString& account, const QString& jid, const QString& resource) const; Account* getAccount(const QString& name); + const Account* getAccountConst(const QString& name) const; QModelIndex getAccountIndex(const QString& name); QModelIndex getGroupIndex(const QString& account, const QString& name); void responseArchive(const QString& account, const QString& jid, const std::list& list, bool last); @@ -95,9 +102,6 @@ signals: void unnoticedMessage(const QString& account, const Shared::Message& msg); void localPathInvalid(const QString& path); -private: - Element* getElement(const ElId& id); - private slots: void onAccountDataChanged(const QModelIndex& tl, const QModelIndex& br, const QVector& roles); void onAccountReconnected(); diff --git a/ui/squawk.cpp b/ui/squawk.cpp index a08f38b..434b442 100644 --- a/ui/squawk.cpp +++ b/ui/squawk.cpp @@ -21,15 +21,13 @@ #include #include -Squawk::Squawk(QWidget *parent) : +Squawk::Squawk(Models::Roster& p_rosterModel, QWidget *parent) : QMainWindow(parent), m_ui(new Ui::Squawk), accounts(nullptr), preferences(nullptr), about(nullptr), - dialogueQueue(this), - rosterModel(*(Shared::Global::getInstance()->rosterModel)), - conversations(), + rosterModel(p_rosterModel), contextMenu(new QMenu()), vCards(), currentConversation(nullptr), @@ -64,12 +62,8 @@ Squawk::Squawk(QWidget *parent) : connect(m_ui->roster, &QTreeView::customContextMenuRequested, this, &Squawk::onRosterContextMenu); connect(m_ui->roster, &QTreeView::collapsed, this, &Squawk::onItemCollepsed); connect(m_ui->roster->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &Squawk::onRosterSelectionChanged); - connect(&rosterModel, &Models::Roster::unnoticedMessage, this, &Squawk::onUnnoticedMessage); connect(rosterModel.accountsModel, &Models::Accounts::sizeChanged, this, &Squawk::onAccountsSizeChanged); - connect(&rosterModel, &Models::Roster::requestArchive, this, &Squawk::onRequestArchive); - connect(&rosterModel, &Models::Roster::fileDownloadRequest, this, &Squawk::fileDownloadRequest); - connect(&rosterModel, &Models::Roster::localPathInvalid, this, &Squawk::localPathInvalid); connect(contextMenu, &QMenu::aboutToHide, this, &Squawk::onContextAboutToHide); connect(m_ui->actionAboutSquawk, &QAction::triggered, this, &Squawk::onAboutSquawkCalled); //m_ui->mainToolBar->addWidget(m_ui->comboBox); @@ -199,36 +193,25 @@ void Squawk::closeEvent(QCloseEvent* event) about->close(); } - for (Conversations::const_iterator itr = conversations.begin(), end = conversations.end(); itr != end; ++itr) { - disconnect(itr->second, &Conversation::destroyed, this, &Squawk::onConversationClosed); - itr->second->close(); - } - conversations.clear(); - for (std::map::const_iterator itr = vCards.begin(), end = vCards.end(); itr != end; ++itr) { disconnect(itr->second, &VCard::destroyed, this, &Squawk::onVCardClosed); itr->second->close(); } vCards.clear(); - + writeSettings(); + emit closing();; + QMainWindow::closeEvent(event); } +void Squawk::onAccountsClosed() { + accounts = nullptr;} -void Squawk::onAccountsClosed() -{ - accounts = nullptr; -} +void Squawk::onPreferencesClosed() { + preferences = nullptr;} -void Squawk::onPreferencesClosed() -{ - preferences = nullptr; -} - -void Squawk::newAccount(const QMap& account) -{ - rosterModel.addAccount(account); -} +void Squawk::onAboutSquawkClosed() { + about = nullptr;} void Squawk::onComboboxActivated(int index) { @@ -236,85 +219,11 @@ void Squawk::onComboboxActivated(int index) emit changeState(av); } -void Squawk::changeAccount(const QString& account, const QMap& data) -{ - for (QMap::const_iterator itr = data.begin(), end = data.end(); itr != end; ++itr) { - QString attr = itr.key(); - rosterModel.updateAccount(account, attr, *itr); - } -} +void Squawk::expand(const QModelIndex& index) { + m_ui->roster->expand(index);} -void Squawk::addContact(const QString& account, const QString& jid, const QString& group, const QMap& data) -{ - rosterModel.addContact(account, jid, group, data); - - QSettings settings; - settings.beginGroup("ui"); - settings.beginGroup("roster"); - settings.beginGroup(account); - if (settings.value("expanded", false).toBool()) { - QModelIndex ind = rosterModel.getAccountIndex(account); - m_ui->roster->expand(ind); - } - settings.endGroup(); - settings.endGroup(); - settings.endGroup(); -} - -void Squawk::addGroup(const QString& account, const QString& name) -{ - rosterModel.addGroup(account, name); - - QSettings settings; - settings.beginGroup("ui"); - settings.beginGroup("roster"); - settings.beginGroup(account); - if (settings.value("expanded", false).toBool()) { - QModelIndex ind = rosterModel.getAccountIndex(account); - m_ui->roster->expand(ind); - if (settings.value(name + "/expanded", false).toBool()) { - m_ui->roster->expand(rosterModel.getGroupIndex(account, name)); - } - } - settings.endGroup(); - settings.endGroup(); - settings.endGroup(); -} - -void Squawk::removeGroup(const QString& account, const QString& name) -{ - rosterModel.removeGroup(account, name); -} - -void Squawk::changeContact(const QString& account, const QString& jid, const QMap& data) -{ - rosterModel.changeContact(account, jid, data); -} - -void Squawk::removeContact(const QString& account, const QString& jid) -{ - rosterModel.removeContact(account, jid); -} - -void Squawk::removeContact(const QString& account, const QString& jid, const QString& group) -{ - rosterModel.removeContact(account, jid, group); -} - -void Squawk::addPresence(const QString& account, const QString& jid, const QString& name, const QMap& data) -{ - rosterModel.addPresence(account, jid, name, data); -} - -void Squawk::removePresence(const QString& account, const QString& jid, const QString& name) -{ - rosterModel.removePresence(account, jid, name); -} - -void Squawk::stateChanged(Shared::Availability state) -{ - m_ui->comboBox->setCurrentIndex(static_cast(state)); -} +void Squawk::stateChanged(Shared::Availability state) { + m_ui->comboBox->setCurrentIndex(static_cast(state));} void Squawk::onRosterItemDoubleClicked(const QModelIndex& item) { @@ -325,186 +234,33 @@ void Squawk::onRosterItemDoubleClicked(const QModelIndex& item) } Models::Contact* contact = nullptr; Models::Room* room = nullptr; - QString res; - Models::Roster::ElId* id = nullptr; switch (node->type) { case Models::Item::contact: contact = static_cast(node); - id = new Models::Roster::ElId(contact->getAccountName(), contact->getJid()); + emit openConversation(Models::Roster::ElId(contact->getAccountName(), contact->getJid())); break; case Models::Item::presence: contact = static_cast(node->parentItem()); - id = new Models::Roster::ElId(contact->getAccountName(), contact->getJid()); - res = node->getName(); + emit openConversation(Models::Roster::ElId(contact->getAccountName(), contact->getJid()), node->getName()); break; case Models::Item::room: room = static_cast(node); - id = new Models::Roster::ElId(room->getAccountName(), room->getJid()); + emit openConversation(Models::Roster::ElId(room->getAccountName(), room->getJid())); break; default: m_ui->roster->expand(item); break; } - - if (id != nullptr) { - Conversations::const_iterator itr = conversations.find(*id); - Models::Account* acc = rosterModel.getAccount(id->account); - Conversation* conv = nullptr; - bool created = false; - if (itr != conversations.end()) { - conv = itr->second; - } else if (contact != nullptr) { - created = true; - conv = new Chat(acc, contact); - } else if (room != nullptr) { - created = true; - conv = new Room(acc, room); - - if (!room->getJoined()) { - emit setRoomJoined(id->account, id->name, true); - } - } - - if (conv != nullptr) { - if (created) { - conv->setAttribute(Qt::WA_DeleteOnClose); - subscribeConversation(conv); - conversations.insert(std::make_pair(*id, conv)); - } - - conv->show(); - conv->raise(); - conv->activateWindow(); - - if (res.size() > 0) { - conv->setPalResource(res); - } - } - - delete id; - } } } -void Squawk::onConversationClosed(QObject* parent) +void Squawk::closeCurrentConversation() { - Conversation* conv = static_cast(sender()); - Models::Roster::ElId id(conv->getAccount(), conv->getJid()); - Conversations::const_iterator itr = conversations.find(id); - if (itr != conversations.end()) { - conversations.erase(itr); - } - if (conv->isMuc) { - Room* room = static_cast(conv); - if (!room->autoJoined()) { - emit setRoomJoined(id.account, id.name, false); - } - } -} - -void Squawk::fileProgress(const std::list msgs, qreal value, bool up) -{ - rosterModel.fileProgress(msgs, value, up); -} - -void Squawk::fileDownloadComplete(const std::list msgs, const QString& path) -{ - rosterModel.fileComplete(msgs, false); -} - -void Squawk::fileError(const std::list msgs, const QString& error, bool up) -{ - rosterModel.fileError(msgs, error, up); -} - -void Squawk::fileUploadComplete(const std::list msgs, const QString& url, const QString& path) -{ - rosterModel.fileComplete(msgs, true); -} - -void Squawk::accountMessage(const QString& account, const Shared::Message& data) -{ - rosterModel.addMessage(account, data); -} - -void Squawk::onUnnoticedMessage(const QString& account, const Shared::Message& msg) -{ - notify(account, msg); //Telegram does this way - notifies even if the app is visible - QApplication::alert(this); -} - -void Squawk::changeMessage(const QString& account, const QString& jid, const QString& id, const QMap& data) -{ - rosterModel.changeMessage(account, jid, id, data); -} - -void Squawk::notify(const QString& account, const Shared::Message& msg) -{ - Shared::Global::notify(account, msg); -} - -void Squawk::onConversationMessage(const Shared::Message& msg) -{ - Conversation* conv = static_cast(sender()); - QString acc = conv->getAccount(); - - rosterModel.addMessage(acc, msg); - emit sendMessage(acc, msg); -} - -void Squawk::onConversationReplaceMessage(const QString& originalId, const Shared::Message& msg) -{ - Conversation* conv = static_cast(sender()); - QString acc = conv->getAccount(); - - rosterModel.changeMessage(acc, msg.getPenPalJid(), originalId, { - {"state", static_cast(Shared::Message::State::pending)} - }); - emit replaceMessage(acc, originalId, msg); -} - -void Squawk::onConversationResend(const QString& id) -{ - Conversation* conv = static_cast(sender()); - QString acc = conv->getAccount(); - QString jid = conv->getJid(); - - emit resendMessage(acc, jid, id); -} - -void Squawk::onRequestArchive(const QString& account, const QString& jid, const QString& before) -{ - emit requestArchive(account, jid, 20, before); //TODO amount as a settings value -} - -void Squawk::responseArchive(const QString& account, const QString& jid, const std::list& list, bool last) -{ - rosterModel.responseArchive(account, jid, list, last); -} - -void Squawk::removeAccount(const QString& account) -{ - Conversations::const_iterator itr = conversations.begin(); - while (itr != conversations.end()) { - if (itr->first.account == account) { - Conversations::const_iterator lItr = itr; - ++itr; - Conversation* conv = lItr->second; - disconnect(conv, &Conversation::destroyed, this, &Squawk::onConversationClosed); - conv->close(); - conversations.erase(lItr); - } else { - ++itr; - } - } - - if (currentConversation != nullptr && currentConversation->getAccount() == account) { + if (currentConversation != nullptr) { currentConversation->deleteLater(); currentConversation = nullptr; m_ui->filler->show(); } - - rosterModel.removeAccount(account); } void Squawk::onRosterContextMenu(const QPoint& point) @@ -542,13 +298,13 @@ void Squawk::onRosterContextMenu(const QPoint& point) break; case Models::Item::contact: { Models::Contact* cnt = static_cast(item); + Models::Roster::ElId id(cnt->getAccountName(), cnt->getJid()); + QString cntName = cnt->getName(); hasMenu = true; QAction* dialog = contextMenu->addAction(Shared::icon("mail-message"), tr("Open dialog")); dialog->setEnabled(active); - connect(dialog, &QAction::triggered, [this, index]() { - onRosterItemDoubleClicked(index); - }); + connect(dialog, &QAction::triggered, std::bind(&Squawk::onRosterItemDoubleClicked, this, index)); Shared::SubscriptionState state = cnt->getState(); switch (state) { @@ -556,9 +312,7 @@ void Squawk::onRosterContextMenu(const QPoint& point) case Shared::SubscriptionState::to: { QAction* unsub = contextMenu->addAction(Shared::icon("news-unsubscribe"), tr("Unsubscribe")); unsub->setEnabled(active); - connect(unsub, &QAction::triggered, [this, cnt]() { - emit unsubscribeContact(cnt->getAccountName(), cnt->getJid(), ""); - }); + connect(unsub, &QAction::triggered, std::bind(&Squawk::changeSubscription, this, id, false)); } break; case Shared::SubscriptionState::from: @@ -566,75 +320,68 @@ void Squawk::onRosterContextMenu(const QPoint& point) case Shared::SubscriptionState::none: { QAction* sub = contextMenu->addAction(Shared::icon("news-subscribe"), tr("Subscribe")); sub->setEnabled(active); - connect(sub, &QAction::triggered, [this, cnt]() { - emit subscribeContact(cnt->getAccountName(), cnt->getJid(), ""); - }); + connect(sub, &QAction::triggered, std::bind(&Squawk::changeSubscription, this, id, true)); } } - QString accName = cnt->getAccountName(); - QString cntJID = cnt->getJid(); - QString cntName = cnt->getName(); QAction* rename = contextMenu->addAction(Shared::icon("edit-rename"), tr("Rename")); rename->setEnabled(active); - connect(rename, &QAction::triggered, [this, cntName, accName, cntJID]() { + connect(rename, &QAction::triggered, [this, cntName, id]() { QInputDialog* dialog = new QInputDialog(this); - connect(dialog, &QDialog::accepted, [this, dialog, cntName, accName, cntJID]() { + connect(dialog, &QDialog::accepted, [this, dialog, cntName, id]() { QString newName = dialog->textValue(); if (newName != cntName) { - emit renameContactRequest(accName, cntJID, newName); + emit renameContactRequest(id.account, id.name, newName); } dialog->deleteLater(); }); connect(dialog, &QDialog::rejected, dialog, &QObject::deleteLater); dialog->setInputMode(QInputDialog::TextInput); - dialog->setLabelText(tr("Input new name for %1\nor leave it empty for the contact \nto be displayed as %1").arg(cntJID)); - dialog->setWindowTitle(tr("Renaming %1").arg(cntJID)); + dialog->setLabelText(tr("Input new name for %1\nor leave it empty for the contact \nto be displayed as %1").arg(id.name)); + dialog->setWindowTitle(tr("Renaming %1").arg(id.name)); dialog->setTextValue(cntName); dialog->exec(); }); QMenu* groupsMenu = contextMenu->addMenu(Shared::icon("group"), tr("Groups")); - std::deque groupList = rosterModel.groupList(accName); + std::deque groupList = rosterModel.groupList(id.account); for (QString groupName : groupList) { QAction* gr = groupsMenu->addAction(groupName); gr->setCheckable(true); - gr->setChecked(rosterModel.groupHasContact(accName, groupName, cntJID)); + gr->setChecked(rosterModel.groupHasContact(id.account, groupName, id.name)); gr->setEnabled(active); - connect(gr, &QAction::toggled, [this, accName, groupName, cntJID](bool checked) { + connect(gr, &QAction::toggled, [this, groupName, id](bool checked) { if (checked) { - emit addContactToGroupRequest(accName, cntJID, groupName); + emit addContactToGroupRequest(id.account, id.name, groupName); } else { - emit removeContactFromGroupRequest(accName, cntJID, groupName); + emit removeContactFromGroupRequest(id.account, id.name, groupName); } }); } QAction* newGroup = groupsMenu->addAction(Shared::icon("group-new"), tr("New group")); newGroup->setEnabled(active); - connect(newGroup, &QAction::triggered, [this, accName, cntJID]() { + connect(newGroup, &QAction::triggered, [this, id]() { QInputDialog* dialog = new QInputDialog(this); - connect(dialog, &QDialog::accepted, [this, dialog, accName, cntJID]() { - emit addContactToGroupRequest(accName, cntJID, dialog->textValue()); + connect(dialog, &QDialog::accepted, [this, dialog, id]() { + emit addContactToGroupRequest(id.account, id.name, dialog->textValue()); dialog->deleteLater(); }); connect(dialog, &QDialog::rejected, dialog, &QObject::deleteLater); dialog->setInputMode(QInputDialog::TextInput); dialog->setLabelText(tr("New group name")); - dialog->setWindowTitle(tr("Add %1 to a new group").arg(cntJID)); + dialog->setWindowTitle(tr("Add %1 to a new group").arg(id.name)); dialog->exec(); }); QAction* card = contextMenu->addAction(Shared::icon("user-properties"), tr("VCard")); card->setEnabled(active); - connect(card, &QAction::triggered, std::bind(&Squawk::onActivateVCard, this, accName, cnt->getJid(), false)); + connect(card, &QAction::triggered, std::bind(&Squawk::onActivateVCard, this, id.account, id.name, false)); QAction* remove = contextMenu->addAction(Shared::icon("edit-delete"), tr("Remove")); remove->setEnabled(active); - connect(remove, &QAction::triggered, [this, cnt]() { - emit removeContactRequest(cnt->getAccountName(), cnt->getJid()); - }); + connect(remove, &QAction::triggered, std::bind(&Squawk::removeContactRequest, this, id.account, id.name)); } break; @@ -653,32 +400,16 @@ void Squawk::onRosterContextMenu(const QPoint& point) if (room->getAutoJoin()) { QAction* unsub = contextMenu->addAction(Shared::icon("news-unsubscribe"), tr("Unsubscribe")); unsub->setEnabled(active); - connect(unsub, &QAction::triggered, [this, id]() { - emit setRoomAutoJoin(id.account, id.name, false); - if (conversations.find(id) == conversations.end() - && (currentConversation == nullptr || currentConversation->getId() != id) - ) { //to leave the room if it's not opened in a conversation window - emit setRoomJoined(id.account, id.name, false); - } - }); + connect(unsub, &QAction::triggered, std::bind(&Squawk::changeSubscription, this, id, false)); } else { - QAction* unsub = contextMenu->addAction(Shared::icon("news-subscribe"), tr("Subscribe")); - unsub->setEnabled(active); - connect(unsub, &QAction::triggered, [this, id]() { - emit setRoomAutoJoin(id.account, id.name, true); - if (conversations.find(id) == conversations.end() - && (currentConversation == nullptr || currentConversation->getId() != id) - ) { //to join the room if it's not already joined - emit setRoomJoined(id.account, id.name, true); - } - }); + QAction* sub = contextMenu->addAction(Shared::icon("news-subscribe"), tr("Subscribe")); + sub->setEnabled(active); + connect(sub, &QAction::triggered, std::bind(&Squawk::changeSubscription, this, id, true)); } QAction* remove = contextMenu->addAction(Shared::icon("edit-delete"), tr("Remove")); remove->setEnabled(active); - connect(remove, &QAction::triggered, [this, id]() { - emit removeRoomRequest(id.account, id.name); - }); + connect(remove, &QAction::triggered, std::bind(&Squawk::removeRoomRequest, this, id.account, id.name)); } break; default: @@ -690,36 +421,6 @@ void Squawk::onRosterContextMenu(const QPoint& point) } } -void Squawk::addRoom(const QString& account, const QString jid, const QMap& data) -{ - rosterModel.addRoom(account, jid, data); -} - -void Squawk::changeRoom(const QString& account, const QString jid, const QMap& data) -{ - rosterModel.changeRoom(account, jid, data); -} - -void Squawk::removeRoom(const QString& account, const QString jid) -{ - rosterModel.removeRoom(account, jid); -} - -void Squawk::addRoomParticipant(const QString& account, const QString& jid, const QString& name, const QMap& data) -{ - rosterModel.addRoomParticipant(account, jid, name, data); -} - -void Squawk::changeRoomParticipant(const QString& account, const QString& jid, const QString& name, const QMap& data) -{ - rosterModel.changeRoomParticipant(account, jid, name, data); -} - -void Squawk::removeRoomParticipant(const QString& account, const QString& jid, const QString& name) -{ - rosterModel.removeRoomParticipant(account, jid, name); -} - void Squawk::responseVCard(const QString& jid, const Shared::VCard& card) { std::map::const_iterator itr = vCards.find(jid); @@ -777,61 +478,40 @@ void Squawk::onVCardSave(const Shared::VCard& card, const QString& account) widget->deleteLater(); } -void Squawk::readSettings() -{ - QSettings settings; - settings.beginGroup("ui"); - int avail; - if (settings.contains("availability")) { - avail = settings.value("availability").toInt(); - } else { - avail = static_cast(Shared::Availability::online); - } - settings.endGroup(); - m_ui->comboBox->setCurrentIndex(avail); - - emit changeState(Shared::Global::fromInt(avail)); -} - void Squawk::writeSettings() { QSettings settings; settings.beginGroup("ui"); - settings.beginGroup("window"); - settings.setValue("geometry", saveGeometry()); - settings.setValue("state", saveState()); - settings.endGroup(); - - settings.setValue("splitter", m_ui->splitter->saveState()); - - settings.setValue("availability", m_ui->comboBox->currentIndex()); - - settings.remove("roster"); - settings.beginGroup("roster"); - int size = rosterModel.accountsModel->rowCount(QModelIndex()); - for (int i = 0; i < size; ++i) { - QModelIndex acc = rosterModel.index(i, 0, QModelIndex()); - Models::Account* account = rosterModel.accountsModel->getAccount(i); - QString accName = account->getName(); - settings.beginGroup(accName); - - settings.setValue("expanded", m_ui->roster->isExpanded(acc)); - std::deque groups = rosterModel.groupList(accName); - for (const QString& groupName : groups) { - settings.beginGroup(groupName); - QModelIndex gIndex = rosterModel.getGroupIndex(accName, groupName); - settings.setValue("expanded", m_ui->roster->isExpanded(gIndex)); - settings.endGroup(); - } - + settings.beginGroup("window"); + settings.setValue("geometry", saveGeometry()); + settings.setValue("state", saveState()); + settings.endGroup(); + + settings.setValue("splitter", m_ui->splitter->saveState()); + settings.remove("roster"); + settings.beginGroup("roster"); + int size = rosterModel.accountsModel->rowCount(QModelIndex()); + for (int i = 0; i < size; ++i) { + QModelIndex acc = rosterModel.index(i, 0, QModelIndex()); + Models::Account* account = rosterModel.accountsModel->getAccount(i); + QString accName = account->getName(); + settings.beginGroup(accName); + + settings.setValue("expanded", m_ui->roster->isExpanded(acc)); + std::deque groups = rosterModel.groupList(accName); + for (const QString& groupName : groups) { + settings.beginGroup(groupName); + QModelIndex gIndex = rosterModel.getGroupIndex(accName, groupName); + settings.setValue("expanded", m_ui->roster->isExpanded(gIndex)); + settings.endGroup(); + } + + settings.endGroup(); + } settings.endGroup(); - } - settings.endGroup(); settings.endGroup(); settings.sync(); - - qDebug() << "Saved settings"; } void Squawk::onItemCollepsed(const QModelIndex& index) @@ -853,24 +533,6 @@ void Squawk::onItemCollepsed(const QModelIndex& index) } } -void Squawk::requestPassword(const QString& account, bool authenticationError) { - if (authenticationError) { - dialogueQueue.addAction(account, DialogQueue::askCredentials); - } else { - dialogueQueue.addAction(account, DialogQueue::askPassword); - } - -} - -void Squawk::subscribeConversation(Conversation* conv) -{ - connect(conv, &Conversation::destroyed, this, &Squawk::onConversationClosed); - connect(conv, &Conversation::sendMessage, this, &Squawk::onConversationMessage); - connect(conv, &Conversation::replaceMessage, this, &Squawk::onConversationReplaceMessage); - connect(conv, &Conversation::resendMessage, this, &Squawk::onConversationResend); - connect(conv, &Conversation::notifyableMessage, this, &Squawk::notify); -} - void Squawk::onRosterSelectionChanged(const QModelIndex& current, const QModelIndex& previous) { if (restoreSelection.isValid() && restoreSelection == current) { @@ -942,16 +604,12 @@ void Squawk::onRosterSelectionChanged(const QModelIndex& current, const QModelIn currentConversation = new Chat(acc, contact); } else if (room != nullptr) { currentConversation = new Room(acc, room); - - if (!room->getJoined()) { - emit setRoomJoined(id->account, id->name, true); - } } if (!testAttribute(Qt::WA_TranslucentBackground)) { currentConversation->setFeedFrames(true, false, true, true); } - subscribeConversation(currentConversation); + emit openedConversation(); if (res.size() > 0) { currentConversation->setPalResource(res); @@ -961,18 +619,10 @@ void Squawk::onRosterSelectionChanged(const QModelIndex& current, const QModelIn delete id; } else { - if (currentConversation != nullptr) { - currentConversation->deleteLater(); - currentConversation = nullptr; - m_ui->filler->show(); - } + closeCurrentConversation(); } } else { - if (currentConversation != nullptr) { - currentConversation->deleteLater(); - currentConversation = nullptr; - m_ui->filler->show(); - } + closeCurrentConversation(); } } @@ -997,7 +647,12 @@ void Squawk::onAboutSquawkCalled() about->show(); } -void Squawk::onAboutSquawkClosed() +Models::Roster::ElId Squawk::currentConversationId() const { - about = nullptr; + if (currentConversation == nullptr) { + return Models::Roster::ElId(); + } else { + return Models::Roster::ElId(currentConversation->getAccount(), currentConversation->getJid()); + } } + diff --git a/ui/squawk.h b/ui/squawk.h index aa52153..5ffe090 100644 --- a/ui/squawk.h +++ b/ui/squawk.h @@ -39,7 +39,6 @@ #include "widgets/vcard/vcard.h" #include "widgets/settings/settings.h" #include "widgets/about.h" -#include "dialogqueue.h" #include "shared/shared.h" #include "shared/global.h" @@ -48,84 +47,57 @@ namespace Ui { class Squawk; } +class Application; + class Squawk : public QMainWindow { Q_OBJECT - friend class DialogQueue; + friend class Application; public: - explicit Squawk(QWidget *parent = nullptr); + explicit Squawk(Models::Roster& rosterModel, QWidget *parent = nullptr); ~Squawk() override; signals: + void closing(); void newAccountRequest(const QMap&); - void modifyAccountRequest(const QString&, const QMap&); void removeAccountRequest(const QString&); void connectAccount(const QString&); void disconnectAccount(const QString&); void changeState(Shared::Availability state); - void sendMessage(const QString& account, const Shared::Message& data); - void replaceMessage(const QString& account, const QString& originalId, const Shared::Message& data); - void resendMessage(const QString& account, const QString& jid, const QString& id); - void requestArchive(const QString& account, const QString& jid, int count, const QString& before); - void subscribeContact(const QString& account, const QString& jid, const QString& reason); - void unsubscribeContact(const QString& account, const QString& jid, const QString& reason); void removeContactRequest(const QString& account, const QString& jid); void addContactRequest(const QString& account, const QString& jid, const QString& name, const QSet& groups); void addContactToGroupRequest(const QString& account, const QString& jid, const QString& groupName); void removeContactFromGroupRequest(const QString& account, const QString& jid, const QString& groupName); void renameContactRequest(const QString& account, const QString& jid, const QString& newName); - void setRoomJoined(const QString& account, const QString& jid, bool joined); - void setRoomAutoJoin(const QString& account, const QString& jid, bool joined); void addRoomRequest(const QString& account, const QString& jid, const QString& nick, const QString& password, bool autoJoin); void removeRoomRequest(const QString& account, const QString& jid); - void fileDownloadRequest(const QString& url); void requestVCard(const QString& account, const QString& jid); void uploadVCard(const QString& account, const Shared::VCard& card); - void responsePassword(const QString& account, const QString& password); - void localPathInvalid(const QString& path); void changeDownloadsPath(const QString& path); + + void notify(const QString& account, const Shared::Message& msg); + void changeSubscription(const Models::Roster::ElId& id, bool subscribe); + void openedConversation(); + void openConversation(const Models::Roster::ElId& id, const QString& resource = ""); + + void modifyAccountRequest(const QString&, const QMap&); +public: + Models::Roster::ElId currentConversationId() const; + void closeCurrentConversation(); + public slots: void writeSettings(); - void readSettings(); - void newAccount(const QMap& account); - void changeAccount(const QString& account, const QMap& 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& data); - void removeContact(const QString& account, const QString& jid, const QString& group); - void removeContact(const QString& account, const QString& jid); - void changeContact(const QString& account, const QString& jid, const QMap& data); - void addPresence(const QString& account, const QString& jid, const QString& name, const QMap& 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& list, bool last); - void addRoom(const QString& account, const QString jid, const QMap& data); - void changeRoom(const QString& account, const QString jid, const QMap& data); - void removeRoom(const QString& account, const QString jid); - void addRoomParticipant(const QString& account, const QString& jid, const QString& name, const QMap& data); - void changeRoomParticipant(const QString& account, const QString& jid, const QString& name, const QMap& data); - void removeRoomParticipant(const QString& account, const QString& jid, const QString& name); - void fileError(const std::list msgs, const QString& error, bool up); - void fileProgress(const std::list msgs, qreal value, bool up); - void fileDownloadComplete(const std::list msgs, const QString& path); - void fileUploadComplete(const std::list msgs, const QString& url, const QString& path); void responseVCard(const QString& jid, const Shared::VCard& card); - void changeMessage(const QString& account, const QString& jid, const QString& id, const QMap& data); - void requestPassword(const QString& account, bool authenticationError); private: - typedef std::map Conversations; QScopedPointer m_ui; Accounts* accounts; Settings* preferences; About* about; - DialogQueue dialogueQueue; Models::Roster& rosterModel; - Conversations conversations; QMenu* contextMenu; std::map vCards; Conversation* currentConversation; @@ -134,9 +106,7 @@ private: protected: void closeEvent(QCloseEvent * event) override; - -protected slots: - void notify(const QString& account, const Shared::Message& msg); + void expand(const QModelIndex& index); private slots: void onAccounts(); @@ -148,27 +118,17 @@ private slots: void onAccountsSizeChanged(unsigned int size); void onAccountsClosed(); void onPreferencesClosed(); - void onConversationClosed(QObject* parent = 0); void onVCardClosed(); void onVCardSave(const Shared::VCard& card, const QString& account); void onActivateVCard(const QString& account, const QString& jid, bool edition = false); void onComboboxActivated(int index); void onRosterItemDoubleClicked(const QModelIndex& item); - void onConversationMessage(const Shared::Message& msg); - void onConversationReplaceMessage(const QString& originalId, const Shared::Message& msg); - void onConversationResend(const QString& id); - void onRequestArchive(const QString& account, const QString& jid, const QString& before); void onRosterContextMenu(const QPoint& point); void onItemCollepsed(const QModelIndex& index); void onRosterSelectionChanged(const QModelIndex& current, const QModelIndex& previous); void onContextAboutToHide(); void onAboutSquawkCalled(); void onAboutSquawkClosed(); - - void onUnnoticedMessage(const QString& account, const Shared::Message& msg); - -private: - void subscribeConversation(Conversation* conv); }; #endif // SQUAWK_H From 3916aec358cb9ec81e7d55930bdc3ae55c38bc79 Mon Sep 17 00:00:00 2001 From: blue Date: Sat, 23 Apr 2022 16:58:08 +0300 Subject: [PATCH 5/7] unread messages count now is displayed on the launcher icon --- main/application.cpp | 18 ++++++++++++++++-- main/application.h | 1 + main/main.cpp | 1 + ui/models/element.cpp | 1 + ui/models/element.h | 1 + ui/models/roster.cpp | 13 +++++++++++++ ui/models/roster.h | 2 ++ 7 files changed, 35 insertions(+), 2 deletions(-) diff --git a/main/application.cpp b/main/application.cpp index f6ffe07..696ec03 100644 --- a/main/application.cpp +++ b/main/application.cpp @@ -21,7 +21,7 @@ Application::Application(Core::Squawk* p_core): availability(Shared::Availability::offline), core(p_core), squawk(nullptr), - notifications("org.freedesktop.Notifications", "/org/freedesktop/Notifications", "org.freedesktop.Notifications", QDBusConnection::sessionBus()), + notifications("org.freedesktop.Notifications", "/org/freedesktop/Notifications", "org.freedesktop.Notifications"), roster(), conversations(), dialogueQueue(roster), @@ -29,6 +29,7 @@ Application::Application(Core::Squawk* p_core): destroyingSquawk(false) { connect(&roster, &Models::Roster::unnoticedMessage, this, &Application::notify); + connect(&roster, &Models::Roster::unreadMessagesCountChanged, this, &Application::unreadMessagesCountChanged); //connecting myself to the backed @@ -100,6 +101,7 @@ void Application::quit() emit quitting(); writeSettings(); + unreadMessagesCountChanged(0); //this notification persist in the desktop, for now I'll zero it on quit not to confuse people for (Conversations::const_iterator itr = conversations.begin(), end = conversations.end(); itr != end; ++itr) { disconnect(itr->second, &Conversation::destroyed, this, &Application::onConversationClosed); itr->second->close(); @@ -212,7 +214,7 @@ void Application::notify(const QString& account, const Shared::Message& msg) args << body; args << QStringList(); args << QVariantMap({ - {"desktop-entry", QString(QCoreApplication::applicationName())}, + {"desktop-entry", qApp->desktopFileName()}, {"category", QString("message")}, // {"sound-file", "/path/to/macaw/squawk"}, {"sound-name", QString("message-new-instant")} @@ -225,6 +227,18 @@ void Application::notify(const QString& account, const Shared::Message& msg) } } +void Application::unreadMessagesCountChanged(int count) +{ + QDBusMessage signal = QDBusMessage::createSignal("/", "com.canonical.Unity.LauncherEntry", "Update"); + signal << qApp->desktopFileName() + QLatin1String(".desktop"); + signal << QVariantMap ({ + {"count-visible", count != 0}, + {"count", count} + }); + QDBusConnection::sessionBus().send(signal); +} + + void Application::setState(Shared::Availability p_availability) { if (availability != p_availability) { diff --git a/main/application.h b/main/application.h index 15adce7..7d70877 100644 --- a/main/application.h +++ b/main/application.h @@ -65,6 +65,7 @@ public slots: protected slots: void notify(const QString& account, const Shared::Message& msg); + void unreadMessagesCountChanged(int count); void setState(Shared::Availability availability); void changeAccount(const QString& account, const QMap& data); diff --git a/main/main.cpp b/main/main.cpp index 77719a2..60b3c83 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -50,6 +50,7 @@ int main(int argc, char *argv[]) QApplication::setOrganizationName("macaw.me"); QApplication::setApplicationDisplayName("Squawk"); QApplication::setApplicationVersion("0.2.2"); + app.setDesktopFileName("squawk"); QTranslator qtTranslator; qtTranslator.load("qt_" + QLocale::system().name(), QLibraryInfo::location(QLibraryInfo::TranslationsPath)); diff --git a/ui/models/element.cpp b/ui/models/element.cpp index 4e741a4..0c709ab 100644 --- a/ui/models/element.cpp +++ b/ui/models/element.cpp @@ -171,6 +171,7 @@ void Models::Element::fileError(const QString& messageId, const QString& error, void Models::Element::onFeedUnreadMessagesCountChanged() { + emit unreadMessagesCountChanged(); if (type == contact) { changed(4); } else if (type == room) { diff --git a/ui/models/element.h b/ui/models/element.h index 94d67cb..dd90ea1 100644 --- a/ui/models/element.h +++ b/ui/models/element.h @@ -52,6 +52,7 @@ signals: void requestArchive(const QString& before); void fileDownloadRequest(const QString& url); void unnoticedMessage(const QString& account, const Shared::Message& msg); + void unreadMessagesCountChanged(); void localPathInvalid(const QString& path); protected: diff --git a/ui/models/roster.cpp b/ui/models/roster.cpp index fef3e43..b2caf6b 100644 --- a/ui/models/roster.cpp +++ b/ui/models/roster.cpp @@ -463,6 +463,7 @@ void Models::Roster::addContact(const QString& account, const QString& jid, cons connect(contact, &Contact::fileDownloadRequest, this, &Roster::fileDownloadRequest); connect(contact, &Contact::unnoticedMessage, this, &Roster::unnoticedMessage); connect(contact, &Contact::localPathInvalid, this, &Roster::localPathInvalid); + connect(contact, &Contact::unreadMessagesCountChanged, this, &Roster::recalculateUnreadMessages); contacts.insert(std::make_pair(id, contact)); } else { contact = itr->second; @@ -805,6 +806,7 @@ void Models::Roster::addRoom(const QString& account, const QString jid, const QM connect(room, &Room::fileDownloadRequest, this, &Roster::fileDownloadRequest); connect(room, &Room::unnoticedMessage, this, &Roster::unnoticedMessage); connect(room, &Room::localPathInvalid, this, &Roster::localPathInvalid); + connect(room, &Room::unreadMessagesCountChanged, this, &Roster::recalculateUnreadMessages); rooms.insert(std::make_pair(id, room)); acc->appendChild(room); } @@ -1049,3 +1051,14 @@ void Models::Roster::onAccountReconnected() } } +void Models::Roster::recalculateUnreadMessages() +{ + int count(0); + for (const std::pair& pair : contacts) { + count += pair.second->getMessagesCount(); + } + for (const std::pair& pair : rooms) { + count += pair.second->getMessagesCount(); + } + emit unreadMessagesCountChanged(count); +} diff --git a/ui/models/roster.h b/ui/models/roster.h index 60adf13..249947b 100644 --- a/ui/models/roster.h +++ b/ui/models/roster.h @@ -99,6 +99,7 @@ public: signals: void requestArchive(const QString& account, const QString& jid, const QString& before); void fileDownloadRequest(const QString& url); + void unreadMessagesCountChanged(int count); void unnoticedMessage(const QString& account, const Shared::Message& msg); void localPathInvalid(const QString& path); @@ -113,6 +114,7 @@ private slots: void onChildIsAboutToBeMoved(Item* source, int first, int last, Item* destination, int newIndex); void onChildMoved(); void onElementRequestArchive(const QString& before); + void recalculateUnreadMessages(); private: Item* root; From e58213b2943871f4013dedc4c8577fd373437270 Mon Sep 17 00:00:00 2001 From: blue Date: Sun, 24 Apr 2022 18:52:29 +0300 Subject: [PATCH 6/7] Now notifications have actions! Some more usefull functions to roster model --- CHANGELOG.md | 2 + main/application.cpp | 87 +++++++++++++++++++++++--- main/application.h | 6 ++ ui/models/contact.cpp | 10 +++ ui/models/contact.h | 1 + ui/models/element.cpp | 5 ++ ui/models/element.h | 1 + ui/models/room.cpp | 10 +++ ui/models/room.h | 1 + ui/models/roster.cpp | 83 +++++++++++++++++++----- ui/models/roster.h | 4 +- ui/squawk.cpp | 5 ++ ui/squawk.h | 1 + ui/widgets/messageline/messagefeed.cpp | 18 ++++-- ui/widgets/messageline/messagefeed.h | 1 + 15 files changed, 205 insertions(+), 30 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4daf652..241d61d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,8 @@ ### New features - new "About" window with links, license, gratitudes - if the authentication failed Squawk will ask againg for your password and login +- now there is an amount of unread messages showing on top of Squawk launcher icon +- notifications now have buttons to open a conversation or to mark that message as read ## Squawk 0.2.1 (Apr 02, 2022) ### Bug fixes diff --git a/main/application.cpp b/main/application.cpp index 696ec03..ddf17b3 100644 --- a/main/application.cpp +++ b/main/application.cpp @@ -26,7 +26,8 @@ Application::Application(Core::Squawk* p_core): conversations(), dialogueQueue(roster), nowQuitting(false), - destroyingSquawk(false) + destroyingSquawk(false), + storage() { connect(&roster, &Models::Roster::unnoticedMessage, this, &Application::notify); connect(&roster, &Models::Roster::unreadMessagesCountChanged, this, &Application::unreadMessagesCountChanged); @@ -90,6 +91,23 @@ Application::Application(Core::Squawk* p_core): connect(core, &Core::Squawk::requestPassword, this, &Application::requestPassword); connect(core, &Core::Squawk::ready, this, &Application::readSettings); + QDBusConnection sys = QDBusConnection::sessionBus(); + sys.connect( + "org.freedesktop.Notifications", + "/org/freedesktop/Notifications", + "org.freedesktop.Notifications", + "NotificationClosed", + this, + SLOT(onNotificationClosed(quint32, quint32)) + ); + sys.connect( + "org.freedesktop.Notifications", + "/org/freedesktop/Notifications", + "org.freedesktop.Notifications", + "ActionInvoked", + this, + SLOT(onNotificationInvoked(quint32, const QString&)) + ); } Application::~Application() {} @@ -188,12 +206,14 @@ void Application::onSquawkDestroyed() { void Application::notify(const QString& account, const Shared::Message& msg) { - QString name = QString(roster.getContactName(account, msg.getPenPalJid())); - QString path = QString(roster.getContactIconPath(account, msg.getPenPalJid(), msg.getPenPalResource())); + QString jid = msg.getPenPalJid(); + QString name = QString(roster.getContactName(account, jid)); + QString path = QString(roster.getContactIconPath(account, jid, msg.getPenPalResource())); QVariantList args; args << QString(); - args << qHash(msg.getId()); + uint32_t notificationId = qHash(msg.getId()); + args << notificationId; if (path.size() > 0) { args << path; } else { @@ -212,7 +232,10 @@ void Application::notify(const QString& account, const Shared::Message& msg) } args << body; - args << QStringList(); + args << QStringList({ + "markAsRead", tr("Mark as Read"), + "openConversation", tr("Open conversation") + }); args << QVariantMap({ {"desktop-entry", qApp->desktopFileName()}, {"category", QString("message")}, @@ -222,11 +245,40 @@ void Application::notify(const QString& account, const Shared::Message& msg) args << -1; notifications.callWithArgumentList(QDBus::AutoDetect, "Notify", args); + storage.insert(std::make_pair(notificationId, std::make_pair(Models::Roster::ElId(account, name), msg.getId()))); + if (squawk != nullptr) { QApplication::alert(squawk); } } +void Application::onNotificationClosed(quint32 id, quint32 reason) +{ + Notifications::const_iterator itr = storage.find(id); + if (itr != storage.end()) { + if (reason == 2) { //dissmissed by user (https://specifications.freedesktop.org/notification-spec/latest/ar01s09.html) + //TODO may ba also mark as read? + } + if (reason != 1) { //just expired, can be activated again from history, so no removing for now + storage.erase(id); + qDebug() << "Notification" << id << "was closed"; + } + } +} + +void Application::onNotificationInvoked(quint32 id, const QString& action) +{ + qDebug() << "Notification" << id << action << "request"; + Notifications::const_iterator itr = storage.find(id); + if (itr != storage.end()) { + if (action == "markAsRead") { + roster.markMessageAsRead(itr->second.first, itr->second.second); + } else if (action == "openConversation") { + focusConversation(itr->second.first, "", itr->second.second); + } + } +} + void Application::unreadMessagesCountChanged(int count) { QDBusMessage signal = QDBusMessage::createSignal("/", "com.canonical.Unity.LauncherEntry", "Update"); @@ -238,6 +290,27 @@ void Application::unreadMessagesCountChanged(int count) QDBusConnection::sessionBus().send(signal); } +void Application::focusConversation(const Models::Roster::ElId& id, const QString& resource, const QString& messageId) +{ + if (squawk != nullptr) { + if (squawk->currentConversationId() != id) { + QModelIndex index = roster.getContactIndex(id.account, id.name, resource); + squawk->select(index); + } + + if (squawk->isMinimized()) { + squawk->showNormal(); + } else { + squawk->show(); + } + squawk->raise(); + squawk->activateWindow(); + } else { + openConversation(id, resource); + } + + //TODO focus messageId; +} void Application::setState(Shared::Availability p_availability) { @@ -343,7 +416,7 @@ void Application::openConversation(const Models::Roster::ElId& id, const QString conv = itr->second; } else { Models::Element* el = roster.getElement(id); - if (el != NULL) { + if (el != nullptr) { if (el->type == Models::Item::room) { created = true; Models::Room* room = static_cast(el); @@ -409,7 +482,7 @@ void Application::onSquawkOpenedConversation() { Models::Roster::ElId id = squawk->currentConversationId(); const Models::Element* el = roster.getElementConst(id); - if (el != NULL && el->isRoom() && !static_cast(el)->getJoined()) { + if (el != nullptr && el->isRoom() && !static_cast(el)->getJoined()) { emit setRoomJoined(id.account, id.name, true); } } diff --git a/main/application.h b/main/application.h index 7d70877..301edc4 100644 --- a/main/application.h +++ b/main/application.h @@ -89,14 +89,19 @@ private slots: void stateChanged(Shared::Availability state); void onSquawkClosing(); void onSquawkDestroyed(); + void onNotificationClosed(quint32 id, quint32 reason); + void onNotificationInvoked(quint32 id, const QString& action); + private: void createMainWindow(); void subscribeConversation(Conversation* conv); void checkForTheLastWindow(); + void focusConversation(const Models::Roster::ElId& id, const QString& resource = "", const QString& messageId = ""); private: typedef std::map Conversations; + typedef std::map> Notifications; Shared::Availability availability; Core::Squawk* core; @@ -107,6 +112,7 @@ private: DialogQueue dialogueQueue; bool nowQuitting; bool destroyingSquawk; + Notifications storage; }; #endif // APPLICATION_H diff --git a/ui/models/contact.cpp b/ui/models/contact.cpp index a0c70ac..d5c7dc4 100644 --- a/ui/models/contact.cpp +++ b/ui/models/contact.cpp @@ -155,6 +155,16 @@ void Models::Contact::removePresence(const QString& name) } } +Models::Presence * Models::Contact::getPresence(const QString& name) +{ + QMap::iterator itr = presences.find(name); + if (itr == presences.end()) { + return nullptr; + } else { + return itr.value(); + } +} + void Models::Contact::refresh() { QDateTime lastActivity; diff --git a/ui/models/contact.h b/ui/models/contact.h index a8b80a3..c4fc131 100644 --- a/ui/models/contact.h +++ b/ui/models/contact.h @@ -51,6 +51,7 @@ public: void addPresence(const QString& name, const QMap& data); void removePresence(const QString& name); + Presence* getPresence(const QString& name); QString getContactName() const; QString getStatus() const; diff --git a/ui/models/element.cpp b/ui/models/element.cpp index 0c709ab..acea46f 100644 --- a/ui/models/element.cpp +++ b/ui/models/element.cpp @@ -134,6 +134,11 @@ unsigned int Models::Element::getMessagesCount() const return feed->unreadMessagesCount(); } +bool Models::Element::markMessageAsRead(const QString& id) const +{ + return feed->markMessageAsRead(id); +} + void Models::Element::addMessage(const Shared::Message& data) { feed->addMessage(data); diff --git a/ui/models/element.h b/ui/models/element.h index dd90ea1..c6d3d6e 100644 --- a/ui/models/element.h +++ b/ui/models/element.h @@ -42,6 +42,7 @@ public: void addMessage(const Shared::Message& data); void changeMessage(const QString& id, const QMap& data); unsigned int getMessagesCount() const; + bool markMessageAsRead(const QString& id) const; void responseArchive(const std::list list, bool last); bool isRoom() const; void fileProgress(const QString& messageId, qreal value, bool up); diff --git a/ui/models/room.cpp b/ui/models/room.cpp index a6a36d0..4aaa07e 100644 --- a/ui/models/room.cpp +++ b/ui/models/room.cpp @@ -264,6 +264,16 @@ void Models::Room::removeParticipant(const QString& p_name) } } +Models::Participant * Models::Room::getParticipant(const QString& p_name) +{ + std::map::const_iterator itr = participants.find(p_name); + if (itr == participants.end()) { + return nullptr; + } else { + return itr->second; + } +} + void Models::Room::handleParticipantUpdate(std::map::const_iterator itr, const QMap& data) { Participant* part = itr->second; diff --git a/ui/models/room.h b/ui/models/room.h index a51a537..707b35b 100644 --- a/ui/models/room.h +++ b/ui/models/room.h @@ -58,6 +58,7 @@ public: void addParticipant(const QString& name, const QMap& data); void changeParticipant(const QString& name, const QMap& data); void removeParticipant(const QString& name); + Participant* getParticipant(const QString& name); void toOfflineState() override; QString getDisplayedName() const override; diff --git a/ui/models/roster.cpp b/ui/models/roster.cpp index b2caf6b..fbb7e52 100644 --- a/ui/models/roster.cpp +++ b/ui/models/roster.cpp @@ -549,8 +549,8 @@ void Models::Roster::removeGroup(const QString& account, const QString& name) void Models::Roster::changeContact(const QString& account, const QString& jid, const QMap& data) { - Element* el = getElement({account, jid}); - if (el != NULL) { + Element* el = getElement(ElId(account, jid)); + if (el != nullptr) { for (QMap::const_iterator itr = data.begin(), end = data.end(); itr != end; ++itr) { el->update(itr.key(), itr.value()); } @@ -559,8 +559,8 @@ void Models::Roster::changeContact(const QString& account, const QString& jid, c void Models::Roster::changeMessage(const QString& account, const QString& jid, const QString& id, const QMap& data) { - Element* el = getElement({account, jid}); - if (el != NULL) { + Element* el = getElement(ElId(account, jid)); + if (el != nullptr) { el->changeMessage(id, data); } else { qDebug() << "A request to change a message of the contact " << jid << " in the account " << account << " but it wasn't found"; @@ -707,8 +707,8 @@ void Models::Roster::removePresence(const QString& account, const QString& jid, void Models::Roster::addMessage(const QString& account, const Shared::Message& data) { - Element* el = getElement({account, data.getPenPalJid()}); - if (el != NULL) { + Element* el = getElement(ElId(account, data.getPenPalJid())); + if (el != nullptr) { el->addMessage(data); } } @@ -948,9 +948,18 @@ const Models::Element * Models::Roster::getElementConst(const Models::Roster::El } } - return NULL; + return nullptr; } +bool Models::Roster::markMessageAsRead(const Models::Roster::ElId& elementId, const QString& messageId) +{ + const Element* el = getElementConst(elementId); + if (el != nullptr) { + return el->markMessageAsRead(messageId); + } else { + return false; + } +} QModelIndex Models::Roster::getAccountIndex(const QString& name) { @@ -968,7 +977,7 @@ QModelIndex Models::Roster::getGroupIndex(const QString& account, const QString& if (itr == accounts.end()) { return QModelIndex(); } else { - std::map::const_iterator gItr = groups.find({account, name}); + std::map::const_iterator gItr = groups.find(ElId(account, name)); if (gItr == groups.end()) { return QModelIndex(); } else { @@ -978,6 +987,48 @@ QModelIndex Models::Roster::getGroupIndex(const QString& account, const QString& } } +QModelIndex Models::Roster::getContactIndex(const QString& account, const QString& jid, const QString& resource) +{ + std::map::const_iterator itr = accounts.find(account); + if (itr == accounts.end()) { + return QModelIndex(); + } else { + Account* acc = itr->second; + QModelIndex accIndex = index(acc->row(), 0, QModelIndex()); + std::map::const_iterator cItr = contacts.find(ElId(account, jid)); + if (cItr != contacts.end()) { + QModelIndex contactIndex = index(acc->getContact(jid), 0, accIndex); + if (resource.size() == 0) { + return contactIndex; + } else { + Presence* pres = cItr->second->getPresence(resource); + if (pres != nullptr) { + return index(pres->row(), 0, contactIndex); + } else { + return contactIndex; + } + } + } else { + std::map::const_iterator rItr = rooms.find(ElId(account, jid)); + if (rItr != rooms.end()) { + QModelIndex roomIndex = index(rItr->second->row(), 0, accIndex); + if (resource.size() == 0) { + return roomIndex; + } else { + Participant* part = rItr->second->getParticipant(resource); + if (part != nullptr) { + return index(part->row(), 0, roomIndex); + } else { + return roomIndex; + } + } + } else { + return QModelIndex(); + } + } + } +} + void Models::Roster::onElementRequestArchive(const QString& before) { Element* el = static_cast(sender()); @@ -988,7 +1039,7 @@ void Models::Roster::responseArchive(const QString& account, const QString& jid, { ElId id(account, jid); Element* el = getElement(id); - if (el != NULL) { + if (el != nullptr) { el->responseArchive(list, last); } } @@ -996,8 +1047,8 @@ void Models::Roster::responseArchive(const QString& account, const QString& jid, void Models::Roster::fileProgress(const std::list& msgs, qreal value, bool up) { for (const Shared::MessageInfo& info : msgs) { - Element* el = getElement({info.account, info.jid}); - if (el != NULL) { + Element* el = getElement(ElId(info.account, info.jid)); + if (el != nullptr) { el->fileProgress(info.messageId, value, up); } } @@ -1006,8 +1057,8 @@ void Models::Roster::fileProgress(const std::list& msgs, qr void Models::Roster::fileComplete(const std::list& msgs, bool up) { for (const Shared::MessageInfo& info : msgs) { - Element* el = getElement({info.account, info.jid}); - if (el != NULL) { + Element* el = getElement(ElId(info.account, info.jid)); + if (el != nullptr) { el->fileComplete(info.messageId, up); } } @@ -1016,8 +1067,8 @@ void Models::Roster::fileComplete(const std::list& msgs, bo void Models::Roster::fileError(const std::list& msgs, const QString& err, bool up) { for (const Shared::MessageInfo& info : msgs) { - Element* el = getElement({info.account, info.jid}); - if (el != NULL) { + Element* el = getElement(ElId(info.account, info.jid)); + if (el != nullptr) { el->fileError(info.messageId, err, up); } } @@ -1031,7 +1082,7 @@ Models::Element * Models::Roster::getElement(const Models::Roster::ElId& id) Models::Item::Type Models::Roster::getContactType(const Models::Roster::ElId& id) const { const Models::Element* el = getElementConst(id); - if (el == NULL) { + if (el == nullptr) { return Item::root; } diff --git a/ui/models/roster.h b/ui/models/roster.h index 249947b..efc50f2 100644 --- a/ui/models/roster.h +++ b/ui/models/roster.h @@ -88,6 +88,8 @@ public: const Account* getAccountConst(const QString& name) const; QModelIndex getAccountIndex(const QString& name); QModelIndex getGroupIndex(const QString& account, const QString& name); + QModelIndex getContactIndex(const QString& account, const QString& jid, const QString& resource = ""); + bool markMessageAsRead(const ElId& elementId, const QString& messageId); void responseArchive(const QString& account, const QString& jid, const std::list& list, bool last); void fileProgress(const std::list& msgs, qreal value, bool up); @@ -115,7 +117,7 @@ private slots: void onChildMoved(); void onElementRequestArchive(const QString& before); void recalculateUnreadMessages(); - + private: Item* root; std::map accounts; diff --git a/ui/squawk.cpp b/ui/squawk.cpp index 434b442..9b6158c 100644 --- a/ui/squawk.cpp +++ b/ui/squawk.cpp @@ -656,3 +656,8 @@ Models::Roster::ElId Squawk::currentConversationId() const } } +void Squawk::select(QModelIndex index) +{ + m_ui->roster->scrollTo(index, QAbstractItemView::EnsureVisible); + m_ui->roster->selectionModel()->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect); +} diff --git a/ui/squawk.h b/ui/squawk.h index 5ffe090..15a73dd 100644 --- a/ui/squawk.h +++ b/ui/squawk.h @@ -90,6 +90,7 @@ public slots: void writeSettings(); void stateChanged(Shared::Availability state); void responseVCard(const QString& jid, const Shared::VCard& card); + void select(QModelIndex index); private: QScopedPointer m_ui; diff --git a/ui/widgets/messageline/messagefeed.cpp b/ui/widgets/messageline/messagefeed.cpp index 521e981..ad67bb3 100644 --- a/ui/widgets/messageline/messagefeed.cpp +++ b/ui/widgets/messageline/messagefeed.cpp @@ -318,12 +318,7 @@ QVariant Models::MessageFeed::data(const QModelIndex& index, int role) const case Bulk: { FeedItem item; item.id = msg->getId(); - - std::set::const_iterator umi = unreadMessages->find(item.id); - if (umi != unreadMessages->end()) { - unreadMessages->erase(umi); - emit unreadMessagesCountChanged(); - } + markMessageAsRead(item.id); item.sentByMe = sentByMe(*msg); item.date = msg->getTime(); @@ -373,6 +368,17 @@ int Models::MessageFeed::rowCount(const QModelIndex& parent) const return storage.size(); } +bool Models::MessageFeed::markMessageAsRead(const QString& id) const +{ + std::set::const_iterator umi = unreadMessages->find(id); + if (umi != unreadMessages->end()) { + unreadMessages->erase(umi); + emit unreadMessagesCountChanged(); + return true; + } + return false; +} + unsigned int Models::MessageFeed::unreadMessagesCount() const { return unreadMessages->size(); diff --git a/ui/widgets/messageline/messagefeed.h b/ui/widgets/messageline/messagefeed.h index c9701ae..f362989 100644 --- a/ui/widgets/messageline/messagefeed.h +++ b/ui/widgets/messageline/messagefeed.h @@ -72,6 +72,7 @@ public: void reportLocalPathInvalid(const QString& messageId); unsigned int unreadMessagesCount() const; + bool markMessageAsRead(const QString& id) const; void fileProgress(const QString& messageId, qreal value, bool up); void fileError(const QString& messageId, const QString& error, bool up); void fileComplete(const QString& messageId, bool up); From 2fcc432aef6e1656ffff967aad7bbcbb9c2e2cfd Mon Sep 17 00:00:00 2001 From: blue Date: Tue, 26 Apr 2022 23:08:25 +0300 Subject: [PATCH 7/7] some polish --- main/application.cpp | 3 ++- shared/utils.cpp | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/main/application.cpp b/main/application.cpp index ddf17b3..410cd81 100644 --- a/main/application.cpp +++ b/main/application.cpp @@ -220,7 +220,7 @@ void Application::notify(const QString& account, const Shared::Message& msg) args << QString("mail-message"); //TODO should here better be unknown user icon? } if (msg.getType() == Shared::Message::groupChat) { - args << msg.getFromResource() + " from " + name; + args << msg.getFromResource() + tr(" from ") + name; } else { args << name; } @@ -239,6 +239,7 @@ void Application::notify(const QString& account, const Shared::Message& msg) args << QVariantMap({ {"desktop-entry", qApp->desktopFileName()}, {"category", QString("message")}, + {"urgency", 1}, // {"sound-file", "/path/to/macaw/squawk"}, {"sound-name", QString("message-new-instant")} }); diff --git a/shared/utils.cpp b/shared/utils.cpp index a7a4ecb..fdaf9a4 100644 --- a/shared/utils.cpp +++ b/shared/utils.cpp @@ -40,5 +40,5 @@ QString Shared::processMessageBody(const QString& msg) { QString processed = msg.toHtmlEscaped(); processed.replace(urlReg, "\\1"); - return "

" + processed + "

"; + return processed; }