From 4786388822f74f225e993ac1d0f86cb2b5df9f3b Mon Sep 17 00:00:00 2001 From: blue Date: Sat, 2 Apr 2022 15:53:23 +0300 Subject: [PATCH 01/29] 0.2.1 --- CHANGELOG.md | 7 ++++--- packaging/Archlinux/PKGBUILD | 6 +++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 83c759e..a6445ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Squawk 0.2.1 (UNRELEASED) +## Squawk 0.2.1 (Apr 02, 2022) ### Bug fixes - build in release mode now no longer spams warnings - build now correctly installs all build plugin libs @@ -11,12 +11,13 @@ ### Improvements - reduced amount of places where platform specific path separator is used - now message input is automatically focused when you open a dialog or a room +- what() method on unhandled exception now actually tells what happened ### New features - the settings are here! You con config different stuff from there - now it's possible to set up different qt styles from settings -- if you have KConfig nad KConfigWidgets packages installed - you can chose from global color schemes -- it's possible now to chose a folder where squawk is going to store downloaded files +- if you have KConfig nad KConfigWidgets packages installed - you can choose from global color schemes +- it's possible now to choose a folder where squawk is going to store downloaded files - now you can correct your message ## Squawk 0.2.0 (Jan 10, 2022) diff --git a/packaging/Archlinux/PKGBUILD b/packaging/Archlinux/PKGBUILD index a8da388..899f058 100644 --- a/packaging/Archlinux/PKGBUILD +++ b/packaging/Archlinux/PKGBUILD @@ -1,6 +1,6 @@ # Maintainer: Yury Gubich pkgname=squawk -pkgver=0.2.0 +pkgver=0.2.1 pkgrel=1 pkgdesc="An XMPP desktop messenger, written on pure c++ (qt)" arch=('i686' 'x86_64') @@ -14,7 +14,7 @@ optdepends=('kwallet: secure password storage (requires rebuild)' 'kio: better show in folder action (requires rebuild)') source=("$pkgname-$pkgver.tar.gz") -sha256sums=('8e93d3dbe1fc35cfecb7783af409c6a264244d11609b2241d4fe77d43d068419') +sha256sums=('c00dad1e441601acabb5200dc394f53abfc9876f3902a7dd4ad2fee3232ee84d') build() { cd "$srcdir/squawk" cmake . -D CMAKE_INSTALL_PREFIX=/usr -D CMAKE_BUILD_TYPE=Release @@ -22,5 +22,5 @@ build() { } package() { cd "$srcdir/squawk" - DESTDIR="$pkgdir/" cmake --build . --target install + DESTDIR="$pkgdir/" cmake --build . --target install } -- 2.45.1 From 4baa3bccbf80d4b861661d4986b94a4114f9edba Mon Sep 17 00:00:00 2001 From: blue Date: Sat, 2 Apr 2022 16:09:11 +0300 Subject: [PATCH 02/29] new screenshot --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 486d4fe..5845c46 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ [![AUR version](https://img.shields.io/aur/version/squawk?style=flat-square)](https://aur.archlinux.org/packages/squawk/) [![Liberapay patrons](https://img.shields.io/liberapay/patrons/macaw.me?logo=liberapay&style=flat-square)](https://liberapay.com/macaw.me) -![Squawk screenshot](https://macaw.me/images/squawk/0.2.0.png) +![Squawk screenshot](https://macaw.me/images/squawk/0.2.1.png) ### Prerequisites -- 2.45.1 From 27377e0ec51fad161165649bd0fe92d23f25bbd0 Mon Sep 17 00:00:00 2001 From: blue Date: Sun, 3 Apr 2022 23:53:46 +0300 Subject: [PATCH 03/29] first attempt to make About window --- CHANGELOG.md | 8 ++ CMakeLists.txt | 2 +- core/main.cpp | 18 +--- ui/squawk.cpp | 98 ++++++++++++-------- ui/squawk.h | 4 + ui/squawk.ui | 15 ++- ui/widgets/CMakeLists.txt | 3 + ui/widgets/about.cpp | 29 ++++++ ui/widgets/about.h | 43 +++++++++ ui/widgets/about.ui | 186 ++++++++++++++++++++++++++++++++++++++ 10 files changed, 354 insertions(+), 52 deletions(-) create mode 100644 ui/widgets/about.cpp create mode 100644 ui/widgets/about.h create mode 100644 ui/widgets/about.ui diff --git a/CHANGELOG.md b/CHANGELOG.md index a6445ec..f563c85 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## Squawk 0.2.2 (UNRELEASED) +### Bug fixes + +### Improvements + +### New features + + ## Squawk 0.2.1 (Apr 02, 2022) ### Bug fixes - build in release mode now no longer spams warnings diff --git a/CMakeLists.txt b/CMakeLists.txt index 717cf91..7d0ee7f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.4) -project(squawk VERSION 0.2.1 LANGUAGES CXX) +project(squawk VERSION 0.2.2 LANGUAGES CXX) cmake_policy(SET CMP0076 NEW) cmake_policy(SET CMP0079 NEW) diff --git a/core/main.cpp b/core/main.cpp index f842c80..7d3c3ab 100644 --- a/core/main.cpp +++ b/core/main.cpp @@ -45,19 +45,11 @@ int main(int argc, char *argv[]) QApplication app(argc, argv); SignalCatcher sc(&app); -#ifdef Q_OS_WIN - // Windows need an organization name for QSettings to work - // https://doc.qt.io/qt-5/qsettings.html#basic-usage - { - const QString& orgName = QApplication::organizationName(); - if (orgName.isNull() || orgName.isEmpty()) { - QApplication::setOrganizationName("squawk"); - } - } -#endif + QApplication::setApplicationName("squawk"); + QApplication::setOrganizationName("macaw.me"); QApplication::setApplicationDisplayName("Squawk"); - QApplication::setApplicationVersion("0.2.1"); + QApplication::setApplicationVersion("0.2.2"); QTranslator qtTranslator; qtTranslator.load("qt_" + QLocale::system().name(), QLibraryInfo::location(QLibraryInfo::TranslationsPath)); @@ -199,8 +191,8 @@ int main(int argc, char *argv[]) if (coreThread->isRunning()) { //coreThread->wait(); - //todo if I uncomment that, the app will no quit if it has reconnected at least once - //it feels like a symptom of something badly desinged in the core coreThread + //todo if I uncomment that, the app will not quit if it has reconnected at least once + //it feels like a symptom of something badly desinged in the core thread //need to investigate; } diff --git a/ui/squawk.cpp b/ui/squawk.cpp index 3ebb6a5..4594c01 100644 --- a/ui/squawk.cpp +++ b/ui/squawk.cpp @@ -24,16 +24,17 @@ Squawk::Squawk(QWidget *parent) : QMainWindow(parent), m_ui(new Ui::Squawk), - accounts(0), - preferences(0), + accounts(nullptr), + preferences(nullptr), + about(nullptr), rosterModel(), conversations(), contextMenu(new QMenu()), dbus("org.freedesktop.Notifications", "/org/freedesktop/Notifications", "org.freedesktop.Notifications", QDBusConnection::sessionBus()), vCards(), requestedAccountsForPasswords(), - prompt(0), - currentConversation(0), + prompt(nullptr), + currentConversation(nullptr), restoreSelection(), needToRestore(false) { @@ -72,6 +73,7 @@ Squawk::Squawk(QWidget *parent) : connect(&rosterModel, &Models::Roster::fileDownloadRequest, this, &Squawk::fileDownloadRequest); connect(&rosterModel, &Models::Roster::localPathInvalid, this, &Squawk::localPathInvalid); connect(contextMenu, &QMenu::aboutToHide, this, &Squawk::onContextAboutToHide); + connect(m_ui->actionAboutSquawk, &QAction::triggered, this, &Squawk::onAboutSquawkCalled); //m_ui->mainToolBar->addWidget(m_ui->comboBox); if (testAttribute(Qt::WA_TranslucentBackground)) { @@ -101,7 +103,7 @@ Squawk::~Squawk() { void Squawk::onAccounts() { - if (accounts == 0) { + if (accounts == nullptr) { accounts = new Accounts(rosterModel.accountsModel); accounts->setAttribute(Qt::WA_DeleteOnClose); connect(accounts, &Accounts::destroyed, this, &Squawk::onAccountsClosed); @@ -121,7 +123,7 @@ void Squawk::onAccounts() void Squawk::onPreferences() { - if (preferences == 0) { + if (preferences == nullptr) { preferences = new Settings(); preferences->setAttribute(Qt::WA_DeleteOnClose); connect(preferences, &Settings::destroyed, this, &Squawk::onPreferencesClosed); @@ -189,12 +191,15 @@ void Squawk::onJoinConferenceAccepted() void Squawk::closeEvent(QCloseEvent* event) { - if (accounts != 0) { + if (accounts != nullptr) { accounts->close(); } - if (preferences != 0) { + if (preferences != nullptr) { preferences->close(); } + if (about != nullptr) { + about->close(); + } for (Conversations::const_iterator itr = conversations.begin(), end = conversations.end(); itr != end; ++itr) { disconnect(itr->second, &Conversation::destroyed, this, &Squawk::onConversationClosed); @@ -214,12 +219,12 @@ void Squawk::closeEvent(QCloseEvent* event) void Squawk::onAccountsClosed() { - accounts = 0; + accounts = nullptr; } void Squawk::onPreferencesClosed() { - preferences = 0; + preferences = nullptr; } void Squawk::newAccount(const QMap& account) @@ -342,10 +347,10 @@ void Squawk::onRosterItemDoubleClicked(const QModelIndex& item) if (node->type == Models::Item::reference) { node = static_cast(node)->dereference(); } - Models::Contact* contact = 0; - Models::Room* room = 0; + Models::Contact* contact = nullptr; + Models::Room* room = nullptr; QString res; - Models::Roster::ElId* id = 0; + Models::Roster::ElId* id = nullptr; switch (node->type) { case Models::Item::contact: contact = static_cast(node); @@ -365,17 +370,17 @@ void Squawk::onRosterItemDoubleClicked(const QModelIndex& item) break; } - if (id != 0) { + if (id != nullptr) { Conversations::const_iterator itr = conversations.find(*id); Models::Account* acc = rosterModel.getAccount(id->account); - Conversation* conv = 0; + Conversation* conv = nullptr; bool created = false; if (itr != conversations.end()) { conv = itr->second; - } else if (contact != 0) { + } else if (contact != nullptr) { created = true; conv = new Chat(acc, contact); - } else if (room != 0) { + } else if (room != nullptr) { created = true; conv = new Room(acc, room); @@ -384,7 +389,7 @@ void Squawk::onRosterItemDoubleClicked(const QModelIndex& item) } } - if (conv != 0) { + if (conv != nullptr) { if (created) { conv->setAttribute(Qt::WA_DeleteOnClose); subscribeConversation(conv); @@ -543,9 +548,9 @@ void Squawk::removeAccount(const QString& account) } } - if (currentConversation != 0 && currentConversation->getAccount() == account) { + if (currentConversation != nullptr && currentConversation->getAccount() == account) { currentConversation->deleteLater(); - currentConversation = 0; + currentConversation = nullptr; m_ui->filler->show(); } @@ -710,7 +715,7 @@ void Squawk::onRosterContextMenu(const QPoint& point) connect(unsub, &QAction::triggered, [this, id]() { emit setRoomAutoJoin(id.account, id.name, false); if (conversations.find(id) == conversations.end() - && (currentConversation == 0 || currentConversation->getId() != id) + && (currentConversation == nullptr || currentConversation->getId() != id) ) { //to leave the room if it's not opened in a conversation window emit setRoomJoined(id.account, id.name, false); } @@ -721,7 +726,7 @@ void Squawk::onRosterContextMenu(const QPoint& point) connect(unsub, &QAction::triggered, [this, id]() { emit setRoomAutoJoin(id.account, id.name, true); if (conversations.find(id) == conversations.end() - && (currentConversation == 0 || currentConversation->getId() != id) + && (currentConversation == nullptr || currentConversation->getId() != id) ) { //to join the room if it's not already joined emit setRoomJoined(id.account, id.name, true); } @@ -928,7 +933,7 @@ void Squawk::requestPassword(const QString& account) void Squawk::checkNextAccountForPassword() { - if (prompt == 0 && requestedAccountsForPasswords.size() > 0) { + if (prompt == nullptr && requestedAccountsForPasswords.size() > 0) { prompt = new QInputDialog(this); QString accName = requestedAccountsForPasswords.front(); connect(prompt, &QDialog::accepted, this, &Squawk::onPasswordPromptAccepted); @@ -951,7 +956,7 @@ void Squawk::onPasswordPromptAccepted() void Squawk::onPasswordPromptDone() { prompt->deleteLater(); - prompt = 0; + prompt = nullptr; requestedAccountsForPasswords.pop_front(); checkNextAccountForPassword(); } @@ -986,10 +991,10 @@ void Squawk::onRosterSelectionChanged(const QModelIndex& current, const QModelIn if (node->type == Models::Item::reference) { node = static_cast(node)->dereference(); } - Models::Contact* contact = 0; - Models::Room* room = 0; + Models::Contact* contact = nullptr; + Models::Room* room = nullptr; QString res; - Models::Roster::ElId* id = 0; + Models::Roster::ElId* id = nullptr; bool hasContext = true; switch (node->type) { case Models::Item::contact: @@ -1018,7 +1023,7 @@ void Squawk::onRosterSelectionChanged(const QModelIndex& current, const QModelIn } if (hasContext && QGuiApplication::mouseButtons() & Qt::RightButton) { - if (id != 0) { + if (id != nullptr) { delete id; } needToRestore = true; @@ -1026,10 +1031,10 @@ void Squawk::onRosterSelectionChanged(const QModelIndex& current, const QModelIn return; } - if (id != 0) { - if (currentConversation != 0) { + if (id != nullptr) { + if (currentConversation != nullptr) { if (currentConversation->getId() == *id) { - if (contact != 0) { + if (contact != nullptr) { currentConversation->setPalResource(res); } return; @@ -1041,9 +1046,9 @@ void Squawk::onRosterSelectionChanged(const QModelIndex& current, const QModelIn } Models::Account* acc = rosterModel.getAccount(id->account); - if (contact != 0) { + if (contact != nullptr) { currentConversation = new Chat(acc, contact); - } else if (room != 0) { + } else if (room != nullptr) { currentConversation = new Room(acc, room); if (!room->getJoined()) { @@ -1064,16 +1069,16 @@ void Squawk::onRosterSelectionChanged(const QModelIndex& current, const QModelIn delete id; } else { - if (currentConversation != 0) { + if (currentConversation != nullptr) { currentConversation->deleteLater(); - currentConversation = 0; + currentConversation = nullptr; m_ui->filler->show(); } } } else { - if (currentConversation != 0) { + if (currentConversation != nullptr) { currentConversation->deleteLater(); - currentConversation = 0; + currentConversation = nullptr; m_ui->filler->show(); } } @@ -1086,3 +1091,22 @@ void Squawk::onContextAboutToHide() m_ui->roster->selectionModel()->setCurrentIndex(restoreSelection, QItemSelectionModel::ClearAndSelect); } } + +void Squawk::onAboutSquawkCalled() +{ + if (about == nullptr) { + about = new About(); + about->setAttribute(Qt::WA_DeleteOnClose); + connect(about, &Settings::destroyed, this, &Squawk::onAboutSquawkClosed); + about->show(); + } else { + about->raise(); + about->activateWindow(); + about->show(); + } +} + +void Squawk::onAboutSquawkClosed() +{ + about = nullptr; +} diff --git a/ui/squawk.h b/ui/squawk.h index 7bd2e10..95c5ce3 100644 --- a/ui/squawk.h +++ b/ui/squawk.h @@ -39,6 +39,7 @@ #include "models/roster.h" #include "widgets/vcard/vcard.h" #include "widgets/settings/settings.h" +#include "widgets/about.h" #include "shared/shared.h" @@ -120,6 +121,7 @@ private: Accounts* accounts; Settings* preferences; + About* about; Models::Roster rosterModel; Conversations conversations; QMenu* contextMenu; @@ -163,6 +165,8 @@ private slots: void onPasswordPromptRejected(); void onRosterSelectionChanged(const QModelIndex& current, const QModelIndex& previous); void onContextAboutToHide(); + void onAboutSquawkCalled(); + void onAboutSquawkClosed(); void onUnnoticedMessage(const QString& account, const Shared::Message& msg); diff --git a/ui/squawk.ui b/ui/squawk.ui index 840dfee..a8b0730 100644 --- a/ui/squawk.ui +++ b/ui/squawk.ui @@ -201,8 +201,15 @@ + + + Help + + + + @@ -248,12 +255,18 @@ - + + .. Preferences + + + About Squawk + + diff --git a/ui/widgets/CMakeLists.txt b/ui/widgets/CMakeLists.txt index f3a2afe..7ba83d2 100644 --- a/ui/widgets/CMakeLists.txt +++ b/ui/widgets/CMakeLists.txt @@ -18,6 +18,9 @@ target_sources(squawk PRIVATE newcontact.ui room.cpp room.h + about.cpp + about.h + about.ui ) add_subdirectory(vcard) diff --git a/ui/widgets/about.cpp b/ui/widgets/about.cpp new file mode 100644 index 0000000..4631065 --- /dev/null +++ b/ui/widgets/about.cpp @@ -0,0 +1,29 @@ +// Squawk messenger. +// Copyright (C) 2019 Yury Gubich +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General 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 "about.h" +#include "ui_about.h" + +About::About(QWidget* parent): + QWidget(parent), + m_ui(new Ui::About) +{ + m_ui->setupUi(this); + m_ui->versionValue->setText(QApplication::applicationVersion()); + setWindowFlag(Qt::Tool); +} + +About::~About() = default; diff --git a/ui/widgets/about.h b/ui/widgets/about.h new file mode 100644 index 0000000..89d879d --- /dev/null +++ b/ui/widgets/about.h @@ -0,0 +1,43 @@ +// Squawk messenger. +// Copyright (C) 2019 Yury Gubich +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General 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 ABOUT_H +#define ABOUT_H + +#include +#include +#include + +namespace Ui +{ +class About; +} + +/** + * @todo write docs + */ +class About : public QWidget +{ + Q_OBJECT +public: + About(QWidget* parent = nullptr); + ~About(); + +private: + QScopedPointer m_ui; +}; + +#endif // ABOUT_H diff --git a/ui/widgets/about.ui b/ui/widgets/about.ui new file mode 100644 index 0000000..ab54df5 --- /dev/null +++ b/ui/widgets/about.ui @@ -0,0 +1,186 @@ + + + About + + + + 0 + 0 + 334 + 321 + + + + About Squawk + + + + 0 + + + + + + 12 + + + + Squawk + + + Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + 0 + + + + About + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + XMPP (jabber) messenger + + + + + + + (c) 2019 - 2022, Yury Gubich + + + + + + + <a href="https://git.macaw.me/blue/squawk">Project site</a> + + + Qt::RichText + + + true + + + + + + + <a href="https://git.macaw.me/blue/squawk/src/branch/master/LICENSE.md">License: GNU General Public License version 3</a> + + + Qt::RichText + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + 0.0.0 + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 10 + + + + + + + + Version + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + + + + 50 + 50 + + + + + + + :/images/logo.svg + + + true + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + + + + + + -- 2.45.1 From 9f746d203b6af51431587c38f81870b83cc3081f Mon Sep 17 00:00:00 2001 From: blue Date: Mon, 4 Apr 2022 23:49:01 +0300 Subject: [PATCH 04/29] new tab in About: components --- ui/widgets/about.cpp | 15 +- ui/widgets/about.h | 1 + ui/widgets/about.ui | 317 +++++++++++++++++++++++++++++++++++++------ 3 files changed, 289 insertions(+), 44 deletions(-) diff --git a/ui/widgets/about.cpp b/ui/widgets/about.cpp index 4631065..3366284 100644 --- a/ui/widgets/about.cpp +++ b/ui/widgets/about.cpp @@ -16,13 +16,26 @@ #include "about.h" #include "ui_about.h" +#include + +static const std::string QXMPP_VERSION_PATCH(std::to_string(QXMPP_VERSION & 0xff)); +static const std::string QXMPP_VERSION_MINOR(std::to_string((QXMPP_VERSION & 0xff00) >> 8)); +static const std::string QXMPP_VERSION_MAJOR(std::to_string(QXMPP_VERSION >> 16)); +static const QString QXMPP_VERSION_STRING = QString::fromStdString(QXMPP_VERSION_MAJOR + "." + QXMPP_VERSION_MINOR + "." + QXMPP_VERSION_PATCH); About::About(QWidget* parent): QWidget(parent), - m_ui(new Ui::About) + m_ui(new Ui::About), + license(nullptr) { m_ui->setupUi(this); m_ui->versionValue->setText(QApplication::applicationVersion()); + m_ui->qtVersionValue->setText(qVersion()); + m_ui->qtBuiltAgainstVersion->setText(tr("(built against %1)").arg(QT_VERSION_STR)); + + m_ui->qxmppVersionValue->setText(QXmppVersion()); + m_ui->qxmppBuiltAgainstVersion->setText(tr("(built against %1)").arg(QXMPP_VERSION_STRING)); + setWindowFlag(Qt::Tool); } diff --git a/ui/widgets/about.h b/ui/widgets/about.h index 89d879d..e28b362 100644 --- a/ui/widgets/about.h +++ b/ui/widgets/about.h @@ -38,6 +38,7 @@ public: private: QScopedPointer m_ui; + QWidget* license; }; #endif // ABOUT_H diff --git a/ui/widgets/about.ui b/ui/widgets/about.ui index ab54df5..58c136b 100644 --- a/ui/widgets/about.ui +++ b/ui/widgets/about.ui @@ -6,8 +6,8 @@ 0 0 - 334 - 321 + 354 + 349 @@ -17,6 +17,22 @@ 0 + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 10 + + + + @@ -32,6 +48,28 @@ + + + + + 50 + 50 + + + + + + + :/images/logo.svg + + + true + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + @@ -120,31 +158,239 @@ + + + QFrame::NoFrame + + + Qt::ScrollBarAlwaysOff + + + true + + + Components + + + + + 0 + 0 + 334 + 240 + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 2 + + + 0 + + + + + + true + + + + Version + + + + + + + + true + + + + 0.0.0 + + + + + + + + true + + + + (built against 0.0.0) + + + + + + + + 75 + true + + + + Qt + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + <a href="https://www.qt.io/">www.qt.io</a> + + + true + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 2 + + + 0 + + + + + + true + + + + Version + + + + + + + + true + + + + 0.0.0 + + + + + + + + true + + + + (built against 0.0.0) + + + + + + + + 75 + true + + + + QXmpp + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + <a href="https://github.com/qxmpp-project/qxmpp">github.com/qxmpp-project/qxmpp</a> + + + true + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + - - - - 0.0.0 - - - - - - - Qt::Vertical - - - QSizePolicy::Fixed - - - - 20 - 10 - - - - @@ -155,25 +401,10 @@ - - - - - 50 - 50 - - + + - - - - :/images/logo.svg - - - true - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + 0.0.0 -- 2.45.1 From 1b66fda3181b38bf4864fb1591291e3391aa03fb Mon Sep 17 00:00:00 2001 From: blue Date: Tue, 5 Apr 2022 22:00:56 +0300 Subject: [PATCH 05/29] License is now can be viewed locally, some organization name packaging issies --- CMakeLists.txt | 2 ++ LICENSE.md | 6 ++-- core/main.cpp | 2 +- translations/CMakeLists.txt | 2 +- ui/squawk.cpp | 3 +- ui/widgets/about.cpp | 68 ++++++++++++++++++++++++++++++++++++- ui/widgets/about.h | 7 ++++ 7 files changed, 82 insertions(+), 8 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7d0ee7f..85aa98a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -159,6 +159,8 @@ add_subdirectory(ui) # Install the executable install(TARGETS squawk DESTINATION ${CMAKE_INSTALL_BINDIR}) +install(FILES README.md DESTINATION ${CMAKE_INSTALL_DATADIR}/macaw.me/squawk) +install(FILES LICENSE.md DESTINATION ${CMAKE_INSTALL_DATADIR}/macaw.me/squawk) if (CMAKE_BUILD_TYPE STREQUAL "Release") if (APPLE) diff --git a/LICENSE.md b/LICENSE.md index 85c7c69..32b38d4 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -595,17 +595,17 @@ pointer to where the full notice is found. Copyright (C) - + This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. - + This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - + You should have received a copy of the GNU General Public License along with this program. If not, see . diff --git a/core/main.cpp b/core/main.cpp index 7d3c3ab..4fbb1f7 100644 --- a/core/main.cpp +++ b/core/main.cpp @@ -54,7 +54,7 @@ int main(int argc, char *argv[]) QTranslator qtTranslator; qtTranslator.load("qt_" + QLocale::system().name(), QLibraryInfo::location(QLibraryInfo::TranslationsPath)); app.installTranslator(&qtTranslator); - + QTranslator myappTranslator; QStringList shares = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation); bool found = false; diff --git a/translations/CMakeLists.txt b/translations/CMakeLists.txt index 86d2a8c..eee4f98 100644 --- a/translations/CMakeLists.txt +++ b/translations/CMakeLists.txt @@ -6,6 +6,6 @@ set(TS_FILES ) qt5_add_translation(QM_FILES ${TS_FILES}) add_custom_target(translations ALL DEPENDS ${QM_FILES}) -install(FILES ${QM_FILES} DESTINATION ${CMAKE_INSTALL_DATADIR}/squawk/l10n) +install(FILES ${QM_FILES} DESTINATION ${CMAKE_INSTALL_DATADIR}/macaw.me/squawk/l10n) add_dependencies(${CMAKE_PROJECT_NAME} translations) diff --git a/ui/squawk.cpp b/ui/squawk.cpp index 4594c01..4c7320b 100644 --- a/ui/squawk.cpp +++ b/ui/squawk.cpp @@ -1098,12 +1098,11 @@ void Squawk::onAboutSquawkCalled() about = new About(); about->setAttribute(Qt::WA_DeleteOnClose); connect(about, &Settings::destroyed, this, &Squawk::onAboutSquawkClosed); - about->show(); } else { about->raise(); about->activateWindow(); - about->show(); } + about->show(); } void Squawk::onAboutSquawkClosed() diff --git a/ui/widgets/about.cpp b/ui/widgets/about.cpp index 3366284..3782a94 100644 --- a/ui/widgets/about.cpp +++ b/ui/widgets/about.cpp @@ -17,6 +17,7 @@ #include "about.h" #include "ui_about.h" #include +#include static const std::string QXMPP_VERSION_PATCH(std::to_string(QXMPP_VERSION & 0xff)); static const std::string QXMPP_VERSION_MINOR(std::to_string((QXMPP_VERSION & 0xff00) >> 8)); @@ -37,6 +38,71 @@ About::About(QWidget* parent): m_ui->qxmppBuiltAgainstVersion->setText(tr("(built against %1)").arg(QXMPP_VERSION_STRING)); setWindowFlag(Qt::Tool); + + connect(m_ui->licenceLink, &QLabel::linkActivated, this, &About::onLicenseActivated); } -About::~About() = default; +About::~About() { + if (license != nullptr) { + license->deleteLater(); + } +}; + +void About::onLicenseActivated() +{ + if (license == nullptr) { + QFile file; + bool found = false; + QStringList shares = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation); + for (const QString& path : shares) { + file.setFileName(path + "/LICENSE.md"); + + if (file.open(QIODevice::ReadOnly | QIODevice::Text)) { + found = true; + break; + } + } + if (!found) { + qDebug() << "couldn't read license file, bailing"; + return; + } + + license = new QWidget(); + license->setWindowTitle(tr("License")); + QVBoxLayout* layout = new QVBoxLayout(license); + QLabel* text = new QLabel(license); + QScrollArea* area = new QScrollArea(license); + text->setTextFormat(Qt::MarkdownText); + text->setWordWrap(true); + text->setOpenExternalLinks(true); + text->setMargin(5); + area->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); + + layout->addWidget(area); + license->setAttribute(Qt::WA_DeleteOnClose); + connect(license, &QWidget::destroyed, this, &About::onLicenseClosed); + + QTextStream in(&file); + QString line; + QString licenseText(""); + while (!in.atEnd()) { + line = in.readLine(); + licenseText.append(line + "\n"); + } + text->setText(licenseText); + file.close(); + + area->setWidget(text); + + } else { + license->raise(); + license->activateWindow(); + } + + license->show(); +} + +void About::onLicenseClosed() +{ + license = nullptr; +} diff --git a/ui/widgets/about.h b/ui/widgets/about.h index e28b362..1506b7f 100644 --- a/ui/widgets/about.h +++ b/ui/widgets/about.h @@ -20,6 +20,9 @@ #include #include #include +#include +#include +#include namespace Ui { @@ -36,6 +39,10 @@ public: About(QWidget* parent = nullptr); ~About(); +protected slots: + void onLicenseActivated(); + void onLicenseClosed(); + private: QScopedPointer m_ui; QWidget* license; -- 2.45.1 From 82d54ba4df869981559ba835c836c62112f8f642 Mon Sep 17 00:00:00 2001 From: blue Date: Thu, 7 Apr 2022 18:26:43 +0300 Subject: [PATCH 06/29] Report bugs tab and thanks to tab in about widget --- ui/widgets/about.ui | 271 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 267 insertions(+), 4 deletions(-) diff --git a/ui/widgets/about.ui b/ui/widgets/about.ui index 58c136b..e7b9ce4 100644 --- a/ui/widgets/about.ui +++ b/ui/widgets/about.ui @@ -6,8 +6,8 @@ 0 0 - 354 - 349 + 375 + 290 @@ -88,6 +88,9 @@ 0 + + false + About @@ -176,8 +179,8 @@ 0 0 - 334 - 240 + 355 + 181 @@ -389,6 +392,266 @@ + + + Report Bugs + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Please report any bug you find! +To report bugs you can use: + + + + + + + <a href="https://git.macaw.me/blue/squawk/issues">Project bug tracker</> + + + true + + + + + + + XMPP (<a href="xmpp:blue@macaw.me">blue@macaw.me</a>) + + + true + + + + + + + E-Mail (<a href="mailto:blue@macaw.me">blue@macaw.me</a>) + + + true + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + QFrame::NoFrame + + + Qt::ScrollBarAlwaysOff + + + true + + + Thanks To + + + + + 0 + 0 + 355 + 181 + + + + + 10 + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 75 + true + + + + Vae + + + + + + + + true + + + + Major refactoring, bug fixes, constructive criticism + + + true + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 75 + true + + + + Shunf4 + + + + + + + + true + + + + Major refactoring, bug fixes, build adaptations for Windows and MacOS + + + true + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 75 + true + + + + Bruno F. Fontes + + + + + + + + true + + + + Brazilian Portuguese translation + + + true + + + + + + + + + + Qt::Vertical + + + + 20 + 0 + + + + + + + -- 2.45.1 From 69e0c88d8d278925c005418fca0b2caa33ce0405 Mon Sep 17 00:00:00 2001 From: blue Date: Fri, 8 Apr 2022 19:18:15 +0300 Subject: [PATCH 07/29] account refactoring, pep support discovery started --- core/CMakeLists.txt | 3 +- core/account.cpp | 521 +++++------------- core/account.h | 19 +- ...apterFuctions.cpp => adapterfunctions.cpp} | 8 +- core/adapterfunctions.h | 32 ++ core/handlers/CMakeLists.txt | 2 + core/handlers/messagehandler.cpp | 2 +- core/handlers/rosterhandler.cpp | 15 +- core/handlers/rosterhandler.h | 2 + core/handlers/vcardhandler.cpp | 312 +++++++++++ core/handlers/vcardhandler.h | 65 +++ core/rosteritem.h | 1 + 12 files changed, 588 insertions(+), 394 deletions(-) rename core/{adapterFuctions.cpp => adapterfunctions.cpp} (98%) create mode 100644 core/adapterfunctions.h create mode 100644 core/handlers/vcardhandler.cpp create mode 100644 core/handlers/vcardhandler.h diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index 9369cb7..8b6fa69 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -6,7 +6,8 @@ endif(WIN32) target_sources(squawk PRIVATE account.cpp account.h - adapterFuctions.cpp + adapterfunctions.cpp + adapterfunctions.h archive.cpp archive.h conference.cpp diff --git a/core/account.cpp b/core/account.cpp index 91d0f2b..3d782cd 100644 --- a/core/account.cpp +++ b/core/account.cpp @@ -41,13 +41,12 @@ Account::Account(const QString& p_login, const QString& p_server, const QString& rcpm(new QXmppMessageReceiptManager()), reconnectScheduled(false), reconnectTimer(new QTimer), - avatarHash(), - avatarType(), - ownVCardRequestInProgress(false), network(p_net), passwordType(Shared::AccountPassword::plain), + pepSupport(false), mh(new MessageHandler(this)), - rh(new RosterHandler(this)) + rh(new RosterHandler(this)), + vh(new VCardHandler(this)) { config.setUser(p_login); config.setDomain(p_server); @@ -73,10 +72,6 @@ Account::Account(const QString& p_login, const QString& p_server, const QString& client.addExtension(mm); client.addExtension(bm); - - QObject::connect(vm, &QXmppVCardManager::vCardReceived, this, &Account::onVCardReceived); - //QObject::connect(&vm, &QXmppVCardManager::clientVCardReceived, this, &Account::onOwnVCardReceived); //for some reason it doesn't work, launching from common handler - client.addExtension(um); QObject::connect(um, &QXmppUploadRequestManager::slotReceived, mh, &MessageHandler::onUploadSlotReceived); QObject::connect(um, &QXmppUploadRequestManager::requestFailed, mh, &MessageHandler::onUploadSlotRequestFailed); @@ -91,52 +86,6 @@ Account::Account(const QString& p_login, const QString& p_server, const QString& client.addExtension(rcpm); QObject::connect(rcpm, &QXmppMessageReceiptManager::messageDelivered, mh, &MessageHandler::onReceiptReceived); - - QString path(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)); - path += "/" + name; - QDir dir(path); - - if (!dir.exists()) { - bool res = dir.mkpath(path); - if (!res) { - qDebug() << "Couldn't create a cache directory for account" << name; - throw 22; - } - } - - QFile* avatar = new QFile(path + "/avatar.png"); - QString type = "png"; - if (!avatar->exists()) { - delete avatar; - avatar = new QFile(path + "/avatar.jpg"); - type = "jpg"; - if (!avatar->exists()) { - delete avatar; - avatar = new QFile(path + "/avatar.jpeg"); - type = "jpeg"; - if (!avatar->exists()) { - delete avatar; - avatar = new QFile(path + "/avatar.gif"); - type = "gif"; - } - } - } - - if (avatar->exists()) { - if (avatar->open(QFile::ReadOnly)) { - QCryptographicHash sha1(QCryptographicHash::Sha1); - sha1.addData(avatar); - avatarHash = sha1.result(); - avatarType = type; - } - } - if (avatarType.size() != 0) { - presence.setVCardUpdateType(QXmppPresence::VCardUpdateValidPhoto); - presence.setPhotoHash(avatarHash.toUtf8()); - } else { - presence.setVCardUpdateType(QXmppPresence::VCardUpdateNotReady); - } - reconnectTimer->setSingleShot(true); QObject::connect(reconnectTimer, &QTimer::timeout, this, &Account::onReconnectTimer); @@ -160,6 +109,7 @@ Account::~Account() QObject::disconnect(network, &NetworkAccess::downloadFileComplete, mh, &MessageHandler::onDownloadFileComplete); QObject::disconnect(network, &NetworkAccess::loadFileError, mh, &MessageHandler::onLoadFileError); + delete vh; delete mh; delete rh; @@ -264,36 +214,6 @@ void Core::Account::reconnect() } } -QString Core::Account::getName() const { - return name;} - -QString Core::Account::getLogin() const { - return config.user();} - -QString Core::Account::getPassword() const { - return config.password();} - -QString Core::Account::getServer() const { - return config.domain();} - -Shared::AccountPassword Core::Account::getPasswordType() const { - return passwordType;} - -void Core::Account::setPasswordType(Shared::AccountPassword pt) { - passwordType = pt; } - -void Core::Account::setLogin(const QString& p_login) { - config.setUser(p_login);} - -void Core::Account::setName(const QString& p_name) { - name = p_name;} - -void Core::Account::setPassword(const QString& p_password) { - config.setPassword(p_password);} - -void Core::Account::setServer(const QString& p_server) { - config.setDomain(p_server);} - Shared::Availability Core::Account::getAvailability() const { if (state == Shared::ConnectionState::connected) { @@ -325,32 +245,11 @@ void Core::Account::onPresenceReceived(const QXmppPresence& p_presence) QString jid = comps.front().toLower(); QString resource = comps.back(); - QString myJid = getLogin() + "@" + getServer(); - - if (jid == myJid) { + if (jid == getBareJid()) { if (resource == getResource()) { emit availabilityChanged(static_cast(p_presence.availableStatusType())); } else { - if (!ownVCardRequestInProgress) { - switch (p_presence.vCardUpdateType()) { - case QXmppPresence::VCardUpdateNone: //this presence has nothing to do with photo - break; - case QXmppPresence::VCardUpdateNotReady: //let's say the photo didn't change here - break; - case QXmppPresence::VCardUpdateNoPhoto: //there is no photo, need to drop if any - if (avatarType.size() > 0) { - vm->requestClientVCard(); - ownVCardRequestInProgress = true; - } - break; - case QXmppPresence::VCardUpdateValidPhoto: //there is a photo, need to load - if (avatarHash != p_presence.photoHash()) { - vm->requestClientVCard(); - ownVCardRequestInProgress = true; - } - break; - } - } + vh->handleOtherPresenceOfMyAccountChange(p_presence); } } else { RosterItem* item = rh->getRosterItem(jid); @@ -392,18 +291,6 @@ void Core::Account::onPresenceReceived(const QXmppPresence& p_presence) } } -QString Core::Account::getResource() const { - return config.resource();} - -void Core::Account::setResource(const QString& p_resource) { - config.setResource(p_resource);} - -QString Core::Account::getFullJid() const { - return getLogin() + "@" + getServer() + "/" + getResource();} - -void Core::Account::sendMessage(const Shared::Message& data) { - mh->sendMessage(data);} - void Core::Account::onMamMessageReceived(const QString& queryId, const QXmppMessage& msg) { if (msg.id().size() > 0 && (msg.body().size() > 0 || msg.outOfBandUrl().size() > 0)) { @@ -667,6 +554,151 @@ void Core::Account::setRoomJoined(const QString& jid, bool joined) conf->setJoined(joined); } +void Core::Account::onDiscoveryItemsReceived(const QXmppDiscoveryIq& items) +{ + if (items.from() == getServer()) { + std::set needToRequest; + qDebug() << "Server items list received for account " << name << ":"; + for (QXmppDiscoveryIq::Item item : items.items()) { + QString jid = item.jid(); + if (jid != getServer()) { + qDebug() << " Node" << jid; + needToRequest.insert(jid); + } else { + qDebug() << " " << item.node().toStdString().c_str(); + } + } + + for (const QString& jid : needToRequest) { + dm->requestInfo(jid); + } + } +} + +void Core::Account::onDiscoveryInfoReceived(const QXmppDiscoveryIq& info) +{ + if (info.from() == getServer()) { + bool enableCC = false; + qDebug() << "Server info received for account" << name; + QStringList features = info.features(); + qDebug() << "List of supported features of the server " << getServer() << ":"; + for (const QString& feature : features) { + qDebug() << " " << feature.toStdString().c_str(); + if (feature == "urn:xmpp:carbons:2") { + enableCC = true; + } + } + + if (enableCC) { + qDebug() << "Enabling carbon copies for account" << name; + cm->setCarbonsEnabled(true); + } + + qDebug() << "Requesting account" << name << "capabilities"; + dm->requestInfo(getBareJid()); + } else if (info.from() == getBareJid()) { + qDebug() << "Received capabilities for account" << name << ":"; + QList identities = info.identities(); + bool pepSupported = false; + for (const QXmppDiscoveryIq::Identity& identity : identities) { + QString type = identity.type(); + qDebug() << " " << identity.category() << type; + if (type == "pep") { + pepSupported = true; + } + } + rh->setPepSupport(pepSupported); + } else { + qDebug() << "Received info for account" << name << "about" << info.from(); + QList identities = info.identities(); + for (const QXmppDiscoveryIq::Identity& identity : identities) { + qDebug() << " " << identity.name() << identity.category() << identity.type(); + } + } +} + +void Core::Account::handleDisconnection() +{ + cm->setCarbonsEnabled(false); + rh->handleOffline(); + vh->handleOffline(); + archiveQueries.clear(); +} + +void Core::Account::onContactHistoryResponse(const std::list& list, bool last) +{ + RosterItem* contact = static_cast(sender()); + + qDebug() << "Collected history for contact " << contact->jid << list.size() << "elements"; + if (last) { + qDebug() << "The response contains the first accounted message"; + } + emit responseArchive(contact->jid, list, last); +} + +QString Core::Account::getResource() const { + return config.resource();} + +void Core::Account::setResource(const QString& p_resource) { + config.setResource(p_resource);} + +QString Core::Account::getBareJid() const { + return getLogin() + "@" + getServer();} + +QString Core::Account::getFullJid() const { + return getBareJid() + "/" + getResource();} + +QString Core::Account::getName() const { + return name;} + +QString Core::Account::getLogin() const { + return config.user();} + +QString Core::Account::getPassword() const { + return config.password();} + +QString Core::Account::getServer() const { + return config.domain();} + +Shared::AccountPassword Core::Account::getPasswordType() const { + return passwordType;} + +void Core::Account::setPasswordType(Shared::AccountPassword pt) { + passwordType = pt; } + +void Core::Account::setLogin(const QString& p_login) { + config.setUser(p_login);} + +void Core::Account::setName(const QString& p_name) { + name = p_name;} + +void Core::Account::setPassword(const QString& p_password) { + config.setPassword(p_password);} + +void Core::Account::setServer(const QString& p_server) { + config.setDomain(p_server);} + +void Core::Account::sendMessage(const Shared::Message& data) { + mh->sendMessage(data);} + +void Core::Account::requestChangeMessage(const QString& jid, const QString& messageId, const QMap& data){ + mh->requestChangeMessage(jid, messageId, data);} + +void Core::Account::resendMessage(const QString& jid, const QString& id) { + mh->resendMessage(jid, id);} + +void Core::Account::replaceMessage(const QString& originalId, const Shared::Message& data) { + mh->sendMessage(data, false, originalId);} + +void Core::Account::requestVCard(const QString& jid) { + vh->requestVCard(jid);} + +void Core::Account::uploadVCard(const Shared::VCard& card) { + vh->uploadVCard(card);} + +QString Core::Account::getAvatarPath() const { + return vh->getAvatarPath();} + void Core::Account::removeRoomRequest(const QString& jid){ rh->removeRoomRequest(jid);} @@ -688,254 +720,3 @@ void Core::Account::renameContactRequest(const QString& jid, const QString& newN rm->renameItem(jid, newName); } } - -void Core::Account::onVCardReceived(const QXmppVCardIq& card) -{ - QString id = card.from(); - QStringList comps = id.split("/"); - QString jid = comps.front().toLower(); - QString resource(""); - if (comps.size() > 1) { - resource = comps.back(); - } - pendingVCardRequests.erase(id); - RosterItem* item = rh->getRosterItem(jid); - - if (item == 0) { - if (jid == getLogin() + "@" + getServer()) { - onOwnVCardReceived(card); - } else { - qDebug() << "received vCard" << jid << "doesn't belong to any of known contacts or conferences, skipping"; - } - return; - } - - Shared::VCard vCard = item->handleResponseVCard(card, resource); - - emit receivedVCard(jid, vCard); -} - -void Core::Account::onOwnVCardReceived(const QXmppVCardIq& card) -{ - QByteArray ava = card.photo(); - bool avaChanged = false; - QString path = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/" + name + "/"; - if (ava.size() > 0) { - QCryptographicHash sha1(QCryptographicHash::Sha1); - sha1.addData(ava); - QString newHash(sha1.result()); - QMimeDatabase db; - QMimeType newType = db.mimeTypeForData(ava); - if (avatarType.size() > 0) { - if (avatarHash != newHash) { - QString oldPath = path + "avatar." + avatarType; - QFile oldAvatar(oldPath); - bool oldToRemove = false; - if (oldAvatar.exists()) { - if (oldAvatar.rename(oldPath + ".bak")) { - oldToRemove = true; - } else { - qDebug() << "Received new avatar for account" << name << "but can't get rid of the old one, doing nothing"; - } - } - QFile newAvatar(path + "avatar." + newType.preferredSuffix()); - if (newAvatar.open(QFile::WriteOnly)) { - newAvatar.write(ava); - newAvatar.close(); - avatarHash = newHash; - avatarType = newType.preferredSuffix(); - avaChanged = true; - } else { - qDebug() << "Received new avatar for account" << name << "but can't save it"; - if (oldToRemove) { - qDebug() << "rolling back to the old avatar"; - if (!oldAvatar.rename(oldPath)) { - qDebug() << "Couldn't roll back to the old avatar in account" << name; - } - } - } - } - } else { - QFile newAvatar(path + "avatar." + newType.preferredSuffix()); - if (newAvatar.open(QFile::WriteOnly)) { - newAvatar.write(ava); - newAvatar.close(); - avatarHash = newHash; - avatarType = newType.preferredSuffix(); - avaChanged = true; - } else { - qDebug() << "Received new avatar for account" << name << "but can't save it"; - } - } - } else { - if (avatarType.size() > 0) { - QFile oldAvatar(path + "avatar." + avatarType); - if (!oldAvatar.remove()) { - qDebug() << "Received vCard for account" << name << "without avatar, but can't get rid of the file, doing nothing"; - } else { - avatarType = ""; - avatarHash = ""; - avaChanged = true; - } - } - } - - if (avaChanged) { - QMap change; - if (avatarType.size() > 0) { - presence.setPhotoHash(avatarHash.toUtf8()); - presence.setVCardUpdateType(QXmppPresence::VCardUpdateValidPhoto); - change.insert("avatarPath", path + "avatar." + avatarType); - } else { - presence.setPhotoHash(""); - presence.setVCardUpdateType(QXmppPresence::VCardUpdateNoPhoto); - change.insert("avatarPath", ""); - } - client.setClientPresence(presence); - emit changed(change); - } - - ownVCardRequestInProgress = false; - - Shared::VCard vCard; - initializeVCard(vCard, card); - - if (avatarType.size() > 0) { - vCard.setAvatarType(Shared::Avatar::valid); - vCard.setAvatarPath(path + "avatar." + avatarType); - } else { - vCard.setAvatarType(Shared::Avatar::empty); - } - - emit receivedVCard(getLogin() + "@" + getServer(), vCard); -} - -QString Core::Account::getAvatarPath() const -{ - if (avatarType.size() == 0) { - return ""; - } else { - return QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/" + name + "/" + "avatar." + avatarType; - } -} - -void Core::Account::requestVCard(const QString& jid) -{ - if (pendingVCardRequests.find(jid) == pendingVCardRequests.end()) { - qDebug() << "requesting vCard" << jid; - if (jid == getLogin() + "@" + getServer()) { - if (!ownVCardRequestInProgress) { - vm->requestClientVCard(); - ownVCardRequestInProgress = true; - } - } else { - vm->requestVCard(jid); - pendingVCardRequests.insert(jid); - } - } -} - -void Core::Account::uploadVCard(const Shared::VCard& card) -{ - QXmppVCardIq iq; - initializeQXmppVCard(iq, card); - - if (card.getAvatarType() != Shared::Avatar::empty) { - QString newPath = card.getAvatarPath(); - QString oldPath = getAvatarPath(); - QByteArray data; - QString type; - if (newPath != oldPath) { - QFile avatar(newPath); - if (!avatar.open(QFile::ReadOnly)) { - qDebug() << "An attempt to upload new vCard to account" << name - << "but it wasn't possible to read file" << newPath - << "which was supposed to be new avatar, uploading old avatar"; - if (avatarType.size() > 0) { - QFile oA(oldPath); - if (!oA.open(QFile::ReadOnly)) { - qDebug() << "Couldn't read old avatar of account" << name << ", uploading empty avatar"; - } else { - data = oA.readAll(); - } - } - } else { - data = avatar.readAll(); - } - } else { - if (avatarType.size() > 0) { - QFile oA(oldPath); - if (!oA.open(QFile::ReadOnly)) { - qDebug() << "Couldn't read old avatar of account" << name << ", uploading empty avatar"; - } else { - data = oA.readAll(); - } - } - } - - if (data.size() > 0) { - QMimeDatabase db; - type = db.mimeTypeForData(data).name(); - iq.setPhoto(data); - iq.setPhotoType(type); - } - } - - vm->setClientVCard(iq); - onOwnVCardReceived(iq); -} - -void Core::Account::onDiscoveryItemsReceived(const QXmppDiscoveryIq& items) -{ - for (QXmppDiscoveryIq::Item item : items.items()) { - if (item.jid() != getServer()) { - dm->requestInfo(item.jid()); - } - } -} - -void Core::Account::onDiscoveryInfoReceived(const QXmppDiscoveryIq& info) -{ - qDebug() << "Discovery info received for account" << name; - if (info.from() == getServer()) { - if (info.features().contains("urn:xmpp:carbons:2")) { - qDebug() << "Enabling carbon copies for account" << name; - cm->setCarbonsEnabled(true); - } - } -} - -void Core::Account::handleDisconnection() -{ - cm->setCarbonsEnabled(false); - rh->handleOffline(); - archiveQueries.clear(); - pendingVCardRequests.clear(); - Shared::VCard vCard; //just to show, that there is now more pending request - for (const QString& jid : pendingVCardRequests) { - emit receivedVCard(jid, vCard); //need to show it better in the future, like with an error - } - pendingVCardRequests.clear(); - ownVCardRequestInProgress = false; -} - -void Core::Account::onContactHistoryResponse(const std::list& list, bool last) -{ - RosterItem* contact = static_cast(sender()); - - qDebug() << "Collected history for contact " << contact->jid << list.size() << "elements"; - if (last) { - qDebug() << "The response contains the first accounted message"; - } - emit responseArchive(contact->jid, list, last); -} - -void Core::Account::requestChangeMessage(const QString& jid, const QString& messageId, const QMap& data){ - mh->requestChangeMessage(jid, messageId, data);} - -void Core::Account::resendMessage(const QString& jid, const QString& id) { - mh->resendMessage(jid, id);} - -void Core::Account::replaceMessage(const QString& originalId, const Shared::Message& data) { - mh->sendMessage(data, false, originalId);} - diff --git a/core/account.h b/core/account.h index 664b547..c8e6e41 100644 --- a/core/account.h +++ b/core/account.h @@ -39,7 +39,6 @@ #include #include #include -#include #include #include @@ -50,6 +49,7 @@ #include "handlers/messagehandler.h" #include "handlers/rosterhandler.h" +#include "handlers/vcardhandler.h" namespace Core { @@ -59,6 +59,7 @@ class Account : public QObject Q_OBJECT friend class MessageHandler; friend class RosterHandler; + friend class VCardHandler; public: Account( const QString& p_login, @@ -76,6 +77,8 @@ public: QString getPassword() const; QString getResource() const; QString getAvatarPath() const; + QString getBareJid() const; + QString getFullJid() const; Shared::Availability getAvailability() const; Shared::AccountPassword getPasswordType() const; @@ -86,7 +89,6 @@ public: void setResource(const QString& p_resource); void setAvailability(Shared::Availability avail); void setPasswordType(Shared::AccountPassword pt); - QString getFullJid() const; void sendMessage(const Shared::Message& data); void requestArchive(const QString& jid, int count, const QString& before); void subscribeToContact(const QString& jid, const QString& reason); @@ -157,16 +159,13 @@ private: bool reconnectScheduled; QTimer* reconnectTimer; - std::set pendingVCardRequests; - - QString avatarHash; - QString avatarType; - bool ownVCardRequestInProgress; NetworkAccess* network; Shared::AccountPassword passwordType; + bool pepSupport; MessageHandler* mh; RosterHandler* rh; + VCardHandler* vh; private slots: void onClientStateChange(QXmppClient::State state); @@ -179,9 +178,6 @@ private slots: void onMamResultsReceived(const QString &queryId, const QXmppResultSetReply &resultSetReply, bool complete); void onMamLog(QXmppLogger::MessageType type, const QString &msg); - - void onVCardReceived(const QXmppVCardIq& card); - void onOwnVCardReceived(const QXmppVCardIq& card); void onDiscoveryItemsReceived (const QXmppDiscoveryIq& items); void onDiscoveryInfoReceived (const QXmppDiscoveryIq& info); @@ -191,9 +187,6 @@ private: void handleDisconnection(); void onReconnectTimer(); }; - -void initializeVCard(Shared::VCard& vCard, const QXmppVCardIq& card); -void initializeQXmppVCard(QXmppVCardIq& card, const Shared::VCard& vCard); } diff --git a/core/adapterFuctions.cpp b/core/adapterfunctions.cpp similarity index 98% rename from core/adapterFuctions.cpp rename to core/adapterfunctions.cpp index 3d84dfb..eec5a9f 100644 --- a/core/adapterFuctions.cpp +++ b/core/adapterfunctions.cpp @@ -1,5 +1,5 @@ /* - * Squawk messenger. + * Squawk messenger. * Copyright (C) 2019 Yury Gubich * * This program is free software: you can redistribute it and/or modify @@ -15,10 +15,8 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#ifndef CORE_ADAPTER_FUNCTIONS_H -#define CORE_ADAPTER_FUNCTIONS_H -#include "account.h" +#include "adapterfunctions.h" void Core::initializeVCard(Shared::VCard& vCard, const QXmppVCardIq& card) { @@ -271,5 +269,3 @@ void Core::initializeQXmppVCard(QXmppVCardIq& iq, const Shared::VCard& card) { iq.setEmails(emails); iq.setPhones(phs); } - -#endif // CORE_ADAPTER_FUNCTIONS_H diff --git a/core/adapterfunctions.h b/core/adapterfunctions.h new file mode 100644 index 0000000..6e50a75 --- /dev/null +++ b/core/adapterfunctions.h @@ -0,0 +1,32 @@ +/* + * Squawk messenger. + * Copyright (C) 2019 Yury Gubich + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 CORE_ADAPTER_FUNCTIONS_H +#define CORE_ADAPTER_FUNCTIONS_H + +#include +#include + +namespace Core { + +void initializeVCard(Shared::VCard& vCard, const QXmppVCardIq& card); +void initializeQXmppVCard(QXmppVCardIq& card, const Shared::VCard& vCard); + +} + + +#endif // CORE_ADAPTER_FUNCTIONS_H diff --git a/core/handlers/CMakeLists.txt b/core/handlers/CMakeLists.txt index 6da2ef3..fb67953 100644 --- a/core/handlers/CMakeLists.txt +++ b/core/handlers/CMakeLists.txt @@ -3,4 +3,6 @@ target_sources(squawk PRIVATE messagehandler.h rosterhandler.cpp rosterhandler.h + vcardhandler.cpp + vcardhandler.h ) diff --git a/core/handlers/messagehandler.cpp b/core/handlers/messagehandler.cpp index 0555873..b6d32b9 100644 --- a/core/handlers/messagehandler.cpp +++ b/core/handlers/messagehandler.cpp @@ -176,7 +176,7 @@ void Core::MessageHandler::initializeMessage(Shared::Message& target, const QXmp target.setForwarded(forwarded); if (guessing) { - if (target.getFromJid() == acc->getLogin() + "@" + acc->getServer()) { + if (target.getFromJid() == acc->getBareJid()) { outgoing = true; } else { outgoing = false; diff --git a/core/handlers/rosterhandler.cpp b/core/handlers/rosterhandler.cpp index ce5f1b7..6a233d6 100644 --- a/core/handlers/rosterhandler.cpp +++ b/core/handlers/rosterhandler.cpp @@ -26,7 +26,8 @@ Core::RosterHandler::RosterHandler(Core::Account* account): conferences(), groups(), queuedContacts(), - outOfRosterContacts() + outOfRosterContacts(), + pepSupport(false) { connect(acc->rm, &QXmppRosterManager::rosterReceived, this, &RosterHandler::onRosterReceived); connect(acc->rm, &QXmppRosterManager::itemAdded, this, &RosterHandler::onRosterItemAdded); @@ -51,8 +52,7 @@ Core::RosterHandler::~RosterHandler() void Core::RosterHandler::onRosterReceived() { - acc->vm->requestClientVCard(); //TODO need to make sure server actually supports vCards - acc->ownVCardRequestInProgress = true; + acc->requestVCard(acc->getBareJid()); //TODO need to make sure server actually supports vCards QStringList bj = acc->rm->getRosterBareJids(); for (int i = 0; i < bj.size(); ++i) { @@ -588,4 +588,13 @@ void Core::RosterHandler::handleOffline() pair.second->clearArchiveRequests(); pair.second->downgradeDatabaseState(); } + setPepSupport(false); +} + + +void Core::RosterHandler::setPepSupport(bool support) +{ + if (pepSupport != support) { + pepSupport = support; + } } diff --git a/core/handlers/rosterhandler.h b/core/handlers/rosterhandler.h index b1dfc45..02bbc98 100644 --- a/core/handlers/rosterhandler.h +++ b/core/handlers/rosterhandler.h @@ -64,6 +64,7 @@ public: void storeConferences(); void clearConferences(); + void setPepSupport(bool support); private slots: void onRosterReceived(); @@ -107,6 +108,7 @@ private: std::map> groups; std::map queuedContacts; std::set outOfRosterContacts; + bool pepSupport; }; } diff --git a/core/handlers/vcardhandler.cpp b/core/handlers/vcardhandler.cpp new file mode 100644 index 0000000..2a8d65c --- /dev/null +++ b/core/handlers/vcardhandler.cpp @@ -0,0 +1,312 @@ +// Squawk messenger. +// Copyright (C) 2019 Yury Gubich +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General 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 "vcardhandler.h" +#include "core/account.h" + +Core::VCardHandler::VCardHandler(Account* account): + QObject(), + acc(account), + ownVCardRequestInProgress(false), + pendingVCardRequests(), + avatarHash(), + avatarType() +{ + connect(acc->vm, &QXmppVCardManager::vCardReceived, this, &VCardHandler::onVCardReceived); + //for some reason it doesn't work, launching from common handler + //connect(acc->vm, &QXmppVCardManager::clientVCardReceived, this, &VCardHandler::onOwnVCardReceived); + + QString path(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)); + path += "/" + acc->name; + QDir dir(path); + + if (!dir.exists()) { + bool res = dir.mkpath(path); + if (!res) { + qDebug() << "Couldn't create a cache directory for account" << acc->name; + throw 22; + } + } + + QFile* avatar = new QFile(path + "/avatar.png"); + QString type = "png"; + if (!avatar->exists()) { + delete avatar; + avatar = new QFile(path + "/avatar.jpg"); + type = "jpg"; + if (!avatar->exists()) { + delete avatar; + avatar = new QFile(path + "/avatar.jpeg"); + type = "jpeg"; + if (!avatar->exists()) { + delete avatar; + avatar = new QFile(path + "/avatar.gif"); + type = "gif"; + } + } + } + + if (avatar->exists()) { + if (avatar->open(QFile::ReadOnly)) { + QCryptographicHash sha1(QCryptographicHash::Sha1); + sha1.addData(avatar); + avatarHash = sha1.result(); + avatarType = type; + } + } + if (avatarType.size() != 0) { + acc->presence.setVCardUpdateType(QXmppPresence::VCardUpdateValidPhoto); + acc->presence.setPhotoHash(avatarHash.toUtf8()); + } else { + acc->presence.setVCardUpdateType(QXmppPresence::VCardUpdateNotReady); + } +} + +Core::VCardHandler::~VCardHandler() +{ + +} + +void Core::VCardHandler::onVCardReceived(const QXmppVCardIq& card) +{ + QString id = card.from(); + QStringList comps = id.split("/"); + QString jid = comps.front().toLower(); + QString resource(""); + if (comps.size() > 1) { + resource = comps.back(); + } + pendingVCardRequests.erase(id); + RosterItem* item = acc->rh->getRosterItem(jid); + + if (item == 0) { + if (jid == acc->getBareJid()) { + onOwnVCardReceived(card); + } else { + qDebug() << "received vCard" << jid << "doesn't belong to any of known contacts or conferences, skipping"; + } + return; + } + + Shared::VCard vCard = item->handleResponseVCard(card, resource); + + emit acc->receivedVCard(jid, vCard); +} + +void Core::VCardHandler::onOwnVCardReceived(const QXmppVCardIq& card) +{ + QByteArray ava = card.photo(); + bool avaChanged = false; + QString path = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/" + acc->name + "/"; + if (ava.size() > 0) { + QCryptographicHash sha1(QCryptographicHash::Sha1); + sha1.addData(ava); + QString newHash(sha1.result()); + QMimeDatabase db; + QMimeType newType = db.mimeTypeForData(ava); + if (avatarType.size() > 0) { + if (avatarHash != newHash) { + QString oldPath = path + "avatar." + avatarType; + QFile oldAvatar(oldPath); + bool oldToRemove = false; + if (oldAvatar.exists()) { + if (oldAvatar.rename(oldPath + ".bak")) { + oldToRemove = true; + } else { + qDebug() << "Received new avatar for account" << acc->name << "but can't get rid of the old one, doing nothing"; + } + } + QFile newAvatar(path + "avatar." + newType.preferredSuffix()); + if (newAvatar.open(QFile::WriteOnly)) { + newAvatar.write(ava); + newAvatar.close(); + avatarHash = newHash; + avatarType = newType.preferredSuffix(); + avaChanged = true; + } else { + qDebug() << "Received new avatar for account" << acc->name << "but can't save it"; + if (oldToRemove) { + qDebug() << "rolling back to the old avatar"; + if (!oldAvatar.rename(oldPath)) { + qDebug() << "Couldn't roll back to the old avatar in account" << acc->name; + } + } + } + } + } else { + QFile newAvatar(path + "avatar." + newType.preferredSuffix()); + if (newAvatar.open(QFile::WriteOnly)) { + newAvatar.write(ava); + newAvatar.close(); + avatarHash = newHash; + avatarType = newType.preferredSuffix(); + avaChanged = true; + } else { + qDebug() << "Received new avatar for account" << acc->name << "but can't save it"; + } + } + } else { + if (avatarType.size() > 0) { + QFile oldAvatar(path + "avatar." + avatarType); + if (!oldAvatar.remove()) { + qDebug() << "Received vCard for account" << acc->name << "without avatar, but can't get rid of the file, doing nothing"; + } else { + avatarType = ""; + avatarHash = ""; + avaChanged = true; + } + } + } + + if (avaChanged) { + QMap change; + if (avatarType.size() > 0) { + acc->presence.setPhotoHash(avatarHash.toUtf8()); + acc->presence.setVCardUpdateType(QXmppPresence::VCardUpdateValidPhoto); + change.insert("avatarPath", path + "avatar." + avatarType); + } else { + acc->presence.setPhotoHash(""); + acc->presence.setVCardUpdateType(QXmppPresence::VCardUpdateNoPhoto); + change.insert("avatarPath", ""); + } + acc->client.setClientPresence(acc->presence); + emit acc->changed(change); + } + + ownVCardRequestInProgress = false; + + Shared::VCard vCard; + initializeVCard(vCard, card); + + if (avatarType.size() > 0) { + vCard.setAvatarType(Shared::Avatar::valid); + vCard.setAvatarPath(path + "avatar." + avatarType); + } else { + vCard.setAvatarType(Shared::Avatar::empty); + } + + emit acc->receivedVCard(acc->getBareJid(), vCard); +} + +void Core::VCardHandler::handleOffline() +{ + pendingVCardRequests.clear(); + Shared::VCard vCard; //just to show, that there is now more pending request + for (const QString& jid : pendingVCardRequests) { + emit acc->receivedVCard(jid, vCard); //need to show it better in the future, like with an error + } + pendingVCardRequests.clear(); + ownVCardRequestInProgress = false; +} + +void Core::VCardHandler::requestVCard(const QString& jid) +{ + if (pendingVCardRequests.find(jid) == pendingVCardRequests.end()) { + qDebug() << "requesting vCard" << jid; + if (jid == acc->getBareJid()) { + if (!ownVCardRequestInProgress) { + acc->vm->requestClientVCard(); + ownVCardRequestInProgress = true; + } + } else { + acc->vm->requestVCard(jid); + pendingVCardRequests.insert(jid); + } + } +} + +void Core::VCardHandler::handleOtherPresenceOfMyAccountChange(const QXmppPresence& p_presence) +{ + if (!ownVCardRequestInProgress) { + switch (p_presence.vCardUpdateType()) { + case QXmppPresence::VCardUpdateNone: //this presence has nothing to do with photo + break; + case QXmppPresence::VCardUpdateNotReady: //let's say the photo didn't change here + break; + case QXmppPresence::VCardUpdateNoPhoto: //there is no photo, need to drop if any + if (avatarType.size() > 0) { + acc->vm->requestClientVCard(); + ownVCardRequestInProgress = true; + } + break; + case QXmppPresence::VCardUpdateValidPhoto: //there is a photo, need to load + if (avatarHash != p_presence.photoHash()) { + acc->vm->requestClientVCard(); + ownVCardRequestInProgress = true; + } + break; + } + } +} + +void Core::VCardHandler::uploadVCard(const Shared::VCard& card) +{ + QXmppVCardIq iq; + initializeQXmppVCard(iq, card); + + if (card.getAvatarType() != Shared::Avatar::empty) { + QString newPath = card.getAvatarPath(); + QString oldPath = getAvatarPath(); + QByteArray data; + QString type; + if (newPath != oldPath) { + QFile avatar(newPath); + if (!avatar.open(QFile::ReadOnly)) { + qDebug() << "An attempt to upload new vCard to account" << acc->name + << "but it wasn't possible to read file" << newPath + << "which was supposed to be new avatar, uploading old avatar"; + if (avatarType.size() > 0) { + QFile oA(oldPath); + if (!oA.open(QFile::ReadOnly)) { + qDebug() << "Couldn't read old avatar of account" << acc->name << ", uploading empty avatar"; + } else { + data = oA.readAll(); + } + } + } else { + data = avatar.readAll(); + } + } else { + if (avatarType.size() > 0) { + QFile oA(oldPath); + if (!oA.open(QFile::ReadOnly)) { + qDebug() << "Couldn't read old avatar of account" << acc->name << ", uploading empty avatar"; + } else { + data = oA.readAll(); + } + } + } + + if (data.size() > 0) { + QMimeDatabase db; + type = db.mimeTypeForData(data).name(); + iq.setPhoto(data); + iq.setPhotoType(type); + } + } + + acc->vm->setClientVCard(iq); + onOwnVCardReceived(iq); +} + +QString Core::VCardHandler::getAvatarPath() const +{ + if (avatarType.size() == 0) { + return ""; + } else { + return QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/" + acc->name + "/" + "avatar." + avatarType; + } +} diff --git a/core/handlers/vcardhandler.h b/core/handlers/vcardhandler.h new file mode 100644 index 0000000..4febb69 --- /dev/null +++ b/core/handlers/vcardhandler.h @@ -0,0 +1,65 @@ +// Squawk messenger. +// Copyright (C) 2019 Yury Gubich +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General 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 CORE_VCARDHANDLER_H +#define CORE_VCARDHANDLER_H + +#include + +#include +#include + +#include + +#include +#include + +/** + * @todo write docs + */ + +namespace Core { + +class Account; + +class VCardHandler : public QObject +{ + Q_OBJECT +public: + VCardHandler(Account* account); + ~VCardHandler(); + + void handleOffline(); + void requestVCard(const QString& jid); + void handleOtherPresenceOfMyAccountChange(const QXmppPresence& p_presence); + void uploadVCard(const Shared::VCard& card); + QString getAvatarPath() const; + +private slots: + void onVCardReceived(const QXmppVCardIq& card); + void onOwnVCardReceived(const QXmppVCardIq& card); + +private: + Account* acc; + + bool ownVCardRequestInProgress; + std::set pendingVCardRequests; + QString avatarHash; + QString avatarType; +}; +} + +#endif // CORE_VCARDHANDLER_H diff --git a/core/rosteritem.h b/core/rosteritem.h index 237a46a..d422e3f 100644 --- a/core/rosteritem.h +++ b/core/rosteritem.h @@ -35,6 +35,7 @@ #include "shared/message.h" #include "shared/vcard.h" #include "archive.h" +#include "adapterfunctions.h" namespace Core { -- 2.45.1 From 2c26c7e264922532b37ed94eb366472c85c75062 Mon Sep 17 00:00:00 2001 From: blue Date: Mon, 11 Apr 2022 18:45:12 +0300 Subject: [PATCH 08/29] ui squawk refactoring --- ui/CMakeLists.txt | 8 +- ui/dialogqueue.cpp | 148 +++++++++++++++++++++++++ ui/dialogqueue.h | 92 +++++++++++++++ ui/squawk.cpp | 49 +------- ui/squawk.h | 10 +- ui/widgets/CMakeLists.txt | 7 +- ui/widgets/accounts/CMakeLists.txt | 8 ++ ui/widgets/{ => accounts}/account.cpp | 0 ui/widgets/{ => accounts}/account.h | 0 ui/widgets/{ => accounts}/account.ui | 0 ui/widgets/{ => accounts}/accounts.cpp | 0 ui/widgets/{ => accounts}/accounts.h | 2 +- ui/widgets/{ => accounts}/accounts.ui | 0 13 files changed, 263 insertions(+), 61 deletions(-) create mode 100644 ui/dialogqueue.cpp create mode 100644 ui/dialogqueue.h create mode 100644 ui/widgets/accounts/CMakeLists.txt rename ui/widgets/{ => accounts}/account.cpp (100%) rename ui/widgets/{ => accounts}/account.h (100%) rename ui/widgets/{ => accounts}/account.ui (100%) rename ui/widgets/{ => accounts}/accounts.cpp (100%) rename ui/widgets/{ => accounts}/accounts.h (98%) rename ui/widgets/{ => accounts}/accounts.ui (100%) diff --git a/ui/CMakeLists.txt b/ui/CMakeLists.txt index 36207b6..fcbb24c 100644 --- a/ui/CMakeLists.txt +++ b/ui/CMakeLists.txt @@ -1,4 +1,10 @@ -target_sources(squawk PRIVATE squawk.cpp squawk.h squawk.ui) +target_sources(squawk PRIVATE + squawk.cpp + squawk.h + squawk.ui + dialogqueue.cpp + dialogqueue.h +) add_subdirectory(models) add_subdirectory(utils) diff --git a/ui/dialogqueue.cpp b/ui/dialogqueue.cpp new file mode 100644 index 0000000..1887b28 --- /dev/null +++ b/ui/dialogqueue.cpp @@ -0,0 +1,148 @@ +// Squawk messenger. +// Copyright (C) 2019 Yury Gubich +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General 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 "dialogqueue.h" +#include "squawk.h" +#include + +DialogQueue::DialogQueue(Squawk* p_squawk): + QObject(), + currentSource(), + currentAction(none), + queue(), + collection(queue.get<0>()), + sequence(queue.get<1>()), + prompt(nullptr), + squawk(p_squawk) +{ +} + +DialogQueue::~DialogQueue() +{ +} + +bool DialogQueue::addAction(const QString& source, DialogQueue::Action action) +{ + if (action == none) { + return false; + } + if (currentAction == none) { + currentAction = action; + currentSource = source; + performNextAction(); + return true; + } else { + if (currentAction != action || currentSource != source) { + std::pair result = queue.emplace(source, action); + return result.second; + } else { + return false; + } + } +} + +bool DialogQueue::cancelAction(const QString& source, DialogQueue::Action action) +{ + if (source == currentSource && action == currentAction) { + actionDone(); + return true; + } else { + Collection::iterator itr = collection.find(ActionId{source, action}); + if (itr != collection.end()) { + collection.erase(itr); + return true; + } else { + return false; + } + } +} + +void DialogQueue::performNextAction() +{ + switch (currentAction) { + case none: + actionDone(); + break; + case askPassword: + prompt = new QInputDialog(squawk); + connect(prompt, &QDialog::accepted, this, &DialogQueue::onPropmtAccepted); + connect(prompt, &QDialog::rejected, this, &DialogQueue::onPropmtRejected); + prompt->setInputMode(QInputDialog::TextInput); + prompt->setTextEchoMode(QLineEdit::Password); + prompt->setLabelText(tr("Input the password for account %1").arg(currentSource)); + prompt->setWindowTitle(tr("Password for account %1").arg(currentSource)); + prompt->setTextValue(""); + prompt->exec(); + } +} + +void DialogQueue::onPropmtAccepted() +{ + switch (currentAction) { + case none: + break; + case askPassword: + emit squawk->responsePassword(currentSource, prompt->textValue()); + break; + } + actionDone(); +} + +void DialogQueue::onPropmtRejected() +{ + switch (currentAction) { + case none: + break; + case askPassword: + emit squawk->responsePassword(currentSource, prompt->textValue()); + break; + } + actionDone(); +} + +void DialogQueue::actionDone() +{ + prompt->deleteLater(); + prompt = nullptr; + + if (queue.empty()) { + currentAction = none; + currentSource = ""; + } else { + Sequence::iterator itr = sequence.begin(); + currentAction = itr->action; + currentSource = itr->source; + sequence.erase(itr); + performNextAction(); + } +} + +DialogQueue::ActionId::ActionId(const QString& p_source, DialogQueue::Action p_action): + source(p_source), + action(p_action) {} + +bool DialogQueue::ActionId::operator < (const DialogQueue::ActionId& other) const +{ + if (action == other.action) { + return source < other.source; + } else { + return action < other.action; + } +} + +DialogQueue::ActionId::ActionId(const DialogQueue::ActionId& other): + source(other.source), + action(other.action) {} diff --git a/ui/dialogqueue.h b/ui/dialogqueue.h new file mode 100644 index 0000000..c5bf011 --- /dev/null +++ b/ui/dialogqueue.h @@ -0,0 +1,92 @@ +// Squawk messenger. +// Copyright (C) 2019 Yury Gubich +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General 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 DIALOGQUEUE_H +#define DIALOGQUEUE_H + +#include +#include + +#include +#include +#include + +/** + * @todo write docs + */ + +class Squawk; + +class DialogQueue : public QObject +{ + Q_OBJECT +public: + enum Action { + none, + askPassword + }; + + DialogQueue(Squawk* squawk); + ~DialogQueue(); + + bool addAction(const QString& source, Action action); + bool cancelAction(const QString& source, Action action); + +private: + void performNextAction(); + void actionDone(); + +private slots: + void onPropmtAccepted(); + void onPropmtRejected(); + +private: + QString currentSource; + Action currentAction; + + struct ActionId { + public: + ActionId(const QString& p_source, Action p_action); + ActionId(const ActionId& other); + + const QString source; + const Action action; + + bool operator < (const ActionId& other) const; + }; + + typedef boost::multi_index_container < + ActionId, + boost::multi_index::indexed_by < + boost::multi_index::ordered_unique < + boost::multi_index::identity + >, + boost::multi_index::sequenced<> + > + > Queue; + + typedef Queue::nth_index<0>::type Collection; + typedef Queue::nth_index<1>::type Sequence; + + Queue queue; + Collection& collection; + Sequence& sequence; + + QInputDialog* prompt; + Squawk* squawk; +}; + +#endif // DIALOGQUEUE_H diff --git a/ui/squawk.cpp b/ui/squawk.cpp index 4c7320b..335b8d0 100644 --- a/ui/squawk.cpp +++ b/ui/squawk.cpp @@ -27,13 +27,12 @@ Squawk::Squawk(QWidget *parent) : accounts(nullptr), preferences(nullptr), about(nullptr), + dialogueQueue(this), rosterModel(), conversations(), contextMenu(new QMenu()), dbus("org.freedesktop.Notifications", "/org/freedesktop/Notifications", "org.freedesktop.Notifications", QDBusConnection::sessionBus()), vCards(), - requestedAccountsForPasswords(), - prompt(nullptr), currentConversation(nullptr), restoreSelection(), needToRestore(false) @@ -925,50 +924,8 @@ void Squawk::onItemCollepsed(const QModelIndex& index) } } -void Squawk::requestPassword(const QString& account) -{ - requestedAccountsForPasswords.push_back(account); - checkNextAccountForPassword(); -} - -void Squawk::checkNextAccountForPassword() -{ - if (prompt == nullptr && requestedAccountsForPasswords.size() > 0) { - prompt = new QInputDialog(this); - QString accName = requestedAccountsForPasswords.front(); - connect(prompt, &QDialog::accepted, this, &Squawk::onPasswordPromptAccepted); - connect(prompt, &QDialog::rejected, this, &Squawk::onPasswordPromptRejected); - prompt->setInputMode(QInputDialog::TextInput); - prompt->setTextEchoMode(QLineEdit::Password); - prompt->setLabelText(tr("Input the password for account %1").arg(accName)); - prompt->setWindowTitle(tr("Password for account %1").arg(accName)); - prompt->setTextValue(""); - prompt->exec(); - } -} - -void Squawk::onPasswordPromptAccepted() -{ - emit responsePassword(requestedAccountsForPasswords.front(), prompt->textValue()); - onPasswordPromptDone(); -} - -void Squawk::onPasswordPromptDone() -{ - prompt->deleteLater(); - prompt = nullptr; - requestedAccountsForPasswords.pop_front(); - checkNextAccountForPassword(); -} - -void Squawk::onPasswordPromptRejected() -{ - //for now it's the same on reject and on accept, but one day I'm gonna make - //"Asking for the password again on the authentication failure" feature - //and here I'll be able to break the circle of password requests - emit responsePassword(requestedAccountsForPasswords.front(), prompt->textValue()); - onPasswordPromptDone(); -} +void Squawk::requestPassword(const QString& account) { + dialogueQueue.addAction(account, DialogQueue::askPassword);} void Squawk::subscribeConversation(Conversation* conv) { diff --git a/ui/squawk.h b/ui/squawk.h index 95c5ce3..6ee666c 100644 --- a/ui/squawk.h +++ b/ui/squawk.h @@ -31,7 +31,7 @@ #include #include -#include "widgets/accounts.h" +#include "widgets/accounts/accounts.h" #include "widgets/chat.h" #include "widgets/room.h" #include "widgets/newcontact.h" @@ -40,6 +40,7 @@ #include "widgets/vcard/vcard.h" #include "widgets/settings/settings.h" #include "widgets/about.h" +#include "dialogqueue.h" #include "shared/shared.h" @@ -122,13 +123,12 @@ private: Accounts* accounts; Settings* preferences; About* about; + DialogQueue dialogueQueue; Models::Roster rosterModel; Conversations conversations; QMenu* contextMenu; QDBusInterface dbus; std::map vCards; - std::deque requestedAccountsForPasswords; - QInputDialog* prompt; Conversation* currentConversation; QModelIndex restoreSelection; bool needToRestore; @@ -161,8 +161,6 @@ private slots: void onRequestArchive(const QString& account, const QString& jid, const QString& before); void onRosterContextMenu(const QPoint& point); void onItemCollepsed(const QModelIndex& index); - void onPasswordPromptAccepted(); - void onPasswordPromptRejected(); void onRosterSelectionChanged(const QModelIndex& current, const QModelIndex& previous); void onContextAboutToHide(); void onAboutSquawkCalled(); @@ -171,8 +169,6 @@ private slots: void onUnnoticedMessage(const QString& account, const Shared::Message& msg); private: - void checkNextAccountForPassword(); - void onPasswordPromptDone(); void subscribeConversation(Conversation* conv); }; diff --git a/ui/widgets/CMakeLists.txt b/ui/widgets/CMakeLists.txt index 7ba83d2..21d9504 100644 --- a/ui/widgets/CMakeLists.txt +++ b/ui/widgets/CMakeLists.txt @@ -1,10 +1,4 @@ target_sources(squawk PRIVATE - account.cpp - account.h - account.ui - accounts.cpp - accounts.h - accounts.ui chat.cpp chat.h conversation.cpp @@ -26,3 +20,4 @@ target_sources(squawk PRIVATE add_subdirectory(vcard) add_subdirectory(messageline) add_subdirectory(settings) +add_subdirectory(accounts) diff --git a/ui/widgets/accounts/CMakeLists.txt b/ui/widgets/accounts/CMakeLists.txt new file mode 100644 index 0000000..ad2f117 --- /dev/null +++ b/ui/widgets/accounts/CMakeLists.txt @@ -0,0 +1,8 @@ +target_sources(squawk PRIVATE + account.cpp + account.h + account.ui + accounts.cpp + accounts.h + accounts.ui + ) diff --git a/ui/widgets/account.cpp b/ui/widgets/accounts/account.cpp similarity index 100% rename from ui/widgets/account.cpp rename to ui/widgets/accounts/account.cpp diff --git a/ui/widgets/account.h b/ui/widgets/accounts/account.h similarity index 100% rename from ui/widgets/account.h rename to ui/widgets/accounts/account.h diff --git a/ui/widgets/account.ui b/ui/widgets/accounts/account.ui similarity index 100% rename from ui/widgets/account.ui rename to ui/widgets/accounts/account.ui diff --git a/ui/widgets/accounts.cpp b/ui/widgets/accounts/accounts.cpp similarity index 100% rename from ui/widgets/accounts.cpp rename to ui/widgets/accounts/accounts.cpp diff --git a/ui/widgets/accounts.h b/ui/widgets/accounts/accounts.h similarity index 98% rename from ui/widgets/accounts.h rename to ui/widgets/accounts/accounts.h index 9fd0b57..6d5eb95 100644 --- a/ui/widgets/accounts.h +++ b/ui/widgets/accounts/accounts.h @@ -24,7 +24,7 @@ #include #include "account.h" -#include "../models/accounts.h" +#include "ui/models/accounts.h" namespace Ui { diff --git a/ui/widgets/accounts.ui b/ui/widgets/accounts/accounts.ui similarity index 100% rename from ui/widgets/accounts.ui rename to ui/widgets/accounts/accounts.ui -- 2.45.1 From f64e5c2df079aedcfb452be226930339f5fdac72 Mon Sep 17 00:00:00 2001 From: blue Date: Tue, 12 Apr 2022 23:33:10 +0300 Subject: [PATCH 09/29] account connect/disconnect now activate/deactivate, it's a bit less contraversial; async account password asking new concept --- core/account.cpp | 62 +++++++--- core/account.h | 7 ++ core/squawk.cpp | 194 +++++++++++++++---------------- core/squawk.h | 6 +- ui/dialogqueue.cpp | 2 +- ui/models/account.cpp | 26 ++++- ui/models/account.h | 4 + ui/models/accounts.cpp | 4 + ui/models/roster.cpp | 12 ++ ui/squawk.cpp | 74 +++--------- ui/widgets/accounts/account.cpp | 1 + ui/widgets/accounts/account.ui | 45 ++++--- ui/widgets/accounts/accounts.cpp | 11 +- 13 files changed, 248 insertions(+), 200 deletions(-) diff --git a/core/account.cpp b/core/account.cpp index 3d782cd..4d0480f 100644 --- a/core/account.cpp +++ b/core/account.cpp @@ -22,7 +22,7 @@ using namespace Core; -Account::Account(const QString& p_login, const QString& p_server, const QString& p_password, const QString& p_name, NetworkAccess* p_net, QObject* parent): +Account::Account(const QString& p_login, const QString& p_server, const QString& p_password, const QString& p_name, bool p_active, NetworkAccess* p_net, QObject* parent): QObject(parent), name(p_name), archiveQueries(), @@ -44,6 +44,8 @@ Account::Account(const QString& p_login, const QString& p_server, const QString& network(p_net), passwordType(Shared::AccountPassword::plain), pepSupport(false), + active(p_active), + notReadyPassword(false), mh(new MessageHandler(this)), rh(new RosterHandler(this)), vh(new VCardHandler(this)) @@ -89,13 +91,15 @@ Account::Account(const QString& p_login, const QString& p_server, const QString& reconnectTimer->setSingleShot(true); QObject::connect(reconnectTimer, &QTimer::timeout, this, &Account::onReconnectTimer); -// QXmppLogger* logger = new QXmppLogger(this); -// logger->setLoggingType(QXmppLogger::SignalLogging); -// client.setLogger(logger); -// -// QObject::connect(logger, &QXmppLogger::message, this, [](QXmppLogger::MessageType type, const QString& text){ -// qDebug() << text; -// }); + if (name == "Test") { + QXmppLogger* logger = new QXmppLogger(this); + logger->setLoggingType(QXmppLogger::SignalLogging); + client.setLogger(logger); + + QObject::connect(logger, &QXmppLogger::message, this, [](QXmppLogger::MessageType type, const QString& text){ + qDebug() << text; + }); + } } Account::~Account() @@ -135,7 +139,12 @@ void Core::Account::connect() reconnectTimer->stop(); } if (state == Shared::ConnectionState::disconnected) { - client.connectToServer(config, presence); + if (notReadyPassword) { + emit needPassword(); + } else { + client.connectToServer(config, presence); + } + } else { qDebug("An attempt to connect an account which is already connected, skipping"); } @@ -205,12 +214,14 @@ void Core::Account::onClientStateChange(QXmppClient::State st) void Core::Account::reconnect() { - if (state == Shared::ConnectionState::connected && !reconnectScheduled) { - reconnectScheduled = true; - reconnectTimer->start(500); - client.disconnectFromServer(); - } else { - qDebug() << "An attempt to reconnect account" << getName() << "which was not connected"; + if (!reconnectScheduled) { //TODO define behavior if It was connection or disconnecting + if (state == Shared::ConnectionState::connected) { + reconnectScheduled = true; + reconnectTimer->start(500); + client.disconnectFromServer(); + } else { + qDebug() << "An attempt to reconnect account" << getName() << "which was not connected"; + } } } @@ -636,6 +647,19 @@ void Core::Account::onContactHistoryResponse(const std::list& l emit responseArchive(contact->jid, list, last); } +bool Core::Account::getActive() const { + return active;} + +void Core::Account::setActive(bool p_active) { + if (active != p_active) { + active = p_active; + + emit changed({ + {"active", active} + }); + } +} + QString Core::Account::getResource() const { return config.resource();} @@ -673,7 +697,9 @@ void Core::Account::setName(const QString& p_name) { name = p_name;} void Core::Account::setPassword(const QString& p_password) { - config.setPassword(p_password);} + config.setPassword(p_password); + notReadyPassword = false; +} void Core::Account::setServer(const QString& p_server) { config.setDomain(p_server);} @@ -720,3 +746,7 @@ void Core::Account::renameContactRequest(const QString& jid, const QString& newN rm->renameItem(jid, newName); } } + +void Core::Account::invalidatePassword() { + notReadyPassword = true;} + diff --git a/core/account.h b/core/account.h index c8e6e41..aa65b27 100644 --- a/core/account.h +++ b/core/account.h @@ -66,6 +66,7 @@ public: const QString& p_server, const QString& p_password, const QString& p_name, + bool p_active, NetworkAccess* p_net, QObject* parent = 0); ~Account(); @@ -81,6 +82,7 @@ public: QString getFullJid() const; Shared::Availability getAvailability() const; Shared::AccountPassword getPasswordType() const; + bool getActive() const; void setName(const QString& p_name); void setLogin(const QString& p_login); @@ -90,6 +92,7 @@ public: void setAvailability(Shared::Availability avail); void setPasswordType(Shared::AccountPassword pt); void sendMessage(const Shared::Message& data); + void setActive(bool p_active); void requestArchive(const QString& jid, int count, const QString& before); void subscribeToContact(const QString& jid, const QString& reason); void unsubscribeFromContact(const QString& jid, const QString& reason); @@ -107,6 +110,7 @@ public: void uploadVCard(const Shared::VCard& card); void resendMessage(const QString& jid, const QString& id); void replaceMessage(const QString& originalId, const Shared::Message& data); + void invalidatePassword(); public slots: void connect(); @@ -139,6 +143,7 @@ signals: void receivedVCard(const QString& jid, const Shared::VCard& card); void uploadFile(const QFileInfo& file, const QUrl& set, const QUrl& get, QMap headers); void uploadFileError(const QString& jid, const QString& messageId, const QString& error); + void needPassword(); private: QString name; @@ -162,6 +167,8 @@ private: NetworkAccess* network; Shared::AccountPassword passwordType; bool pepSupport; + bool active; + bool notReadyPassword; MessageHandler* mh; RosterHandler* rh; diff --git a/core/squawk.cpp b/core/squawk.cpp index af131d5..d594553 100644 --- a/core/squawk.cpp +++ b/core/squawk.cpp @@ -26,8 +26,8 @@ Core::Squawk::Squawk(QObject* parent): QObject(parent), accounts(), amap(), + state(Shared::Availability::offline), network(), - waitingForAccounts(0), isInitialized(false) #ifdef WITH_KWALLET ,kwallet() @@ -42,7 +42,7 @@ Core::Squawk::Squawk(QObject* parent): if (kwallet.supportState() == PSE::KWallet::success) { connect(&kwallet, &PSE::KWallet::opened, this, &Squawk::onWalletOpened); connect(&kwallet, &PSE::KWallet::rejectPassword, this, &Squawk::onWalletRejectPassword); - connect(&kwallet, &PSE::KWallet::responsePassword, this, &Squawk::onWalletResponsePassword); + connect(&kwallet, &PSE::KWallet::responsePassword, this, &Squawk::responsePassword); Shared::Global::setSupported("KWallet", true); } @@ -97,6 +97,7 @@ void Core::Squawk::stop() settings.setValue("password", password); settings.setValue("resource", acc->getResource()); settings.setValue("passwordType", static_cast(ap)); + settings.setValue("active", acc->getActive()); } settings.endArray(); settings.endGroup(); @@ -124,8 +125,9 @@ void Core::Squawk::newAccountRequest(const QMap& map) QString password = map.value("password").toString(); QString resource = map.value("resource").toString(); int passwordType = map.value("passwordType").toInt(); + bool active = map.value("active").toBool(); - addAccount(login, server, password, name, resource, Shared::Global::fromInt(passwordType)); + addAccount(login, server, password, name, resource, active, Shared::Global::fromInt(passwordType)); } void Core::Squawk::addAccount( @@ -133,13 +135,13 @@ void Core::Squawk::addAccount( const QString& server, const QString& password, const QString& name, - const QString& resource, - Shared::AccountPassword passwordType -) + const QString& resource, + bool active, + Shared::AccountPassword passwordType) { QSettings settings; - Account* acc = new Account(login, server, password, name, &network); + Account* acc = new Account(login, server, password, name, active, &network); acc->setResource(resource); acc->setPasswordType(passwordType); accounts.push_back(acc); @@ -148,6 +150,8 @@ void Core::Squawk::addAccount( connect(acc, &Account::connectionStateChanged, this, &Squawk::onAccountConnectionStateChanged); connect(acc, &Account::changed, this, &Squawk::onAccountChanged); connect(acc, &Account::error, this, &Squawk::onAccountError); + connect(acc, &Account::needPassword, this, &Squawk::onAccountNeedPassword); + connect(acc, &Account::availabilityChanged, this, &Squawk::onAccountAvailabilityChanged); connect(acc, &Account::addContact, this, &Squawk::onAccountAddContact); connect(acc, &Account::addGroup, this, &Squawk::onAccountAddGroup); @@ -185,20 +189,42 @@ void Core::Squawk::addAccount( {"offline", QVariant::fromValue(Shared::Availability::offline)}, {"error", ""}, {"avatarPath", acc->getAvatarPath()}, - {"passwordType", QVariant::fromValue(passwordType)} + {"passwordType", QVariant::fromValue(passwordType)}, + {"active", active} }; emit newAccount(map); + + switch (passwordType) { + case Shared::AccountPassword::alwaysAsk: + case Shared::AccountPassword::kwallet: + acc->invalidatePassword(); + break; + default: + break; + } + + if (state != Shared::Availability::offline) { + acc->setAvailability(state); + if (acc->getActive()) { + acc->connect(); + } + } } void Core::Squawk::changeState(Shared::Availability p_state) { if (state != p_state) { + for (std::deque::iterator itr = accounts.begin(), end = accounts.end(); itr != end; ++itr) { + Account* acc = *itr; + acc->setAvailability(p_state); + if (state == Shared::Availability::offline && acc->getActive()) { + acc->connect(); + } + } state = p_state; - } - - for (std::deque::iterator itr = accounts.begin(), end = accounts.end(); itr != end; ++itr) { - (*itr)->setAvailability(state); + + emit stateChanged(p_state); } } @@ -209,7 +235,10 @@ void Core::Squawk::connectAccount(const QString& account) qDebug("An attempt to connect non existing account, skipping"); return; } - itr->second->connect(); + itr->second->setActive(true); + if (state != Shared::Availability::offline) { + itr->second->connect(); + } } void Core::Squawk::disconnectAccount(const QString& account) @@ -220,6 +249,7 @@ void Core::Squawk::disconnectAccount(const QString& account) return; } + itr->second->setActive(false); itr->second->disconnect(); } @@ -227,7 +257,7 @@ void Core::Squawk::onAccountConnectionStateChanged(Shared::ConnectionState p_sta { Account* acc = static_cast(sender()); emit changeAccount(acc->getName(), {{"state", QVariant::fromValue(p_state)}}); - + #ifdef WITH_KWALLET if (p_state == Shared::ConnectionState::connected) { if (acc->getPasswordType() == Shared::AccountPassword::kwallet && kwallet.supportState() == PSE::KWallet::success) { @@ -235,33 +265,6 @@ void Core::Squawk::onAccountConnectionStateChanged(Shared::ConnectionState p_sta } } #endif - - Accounts::const_iterator itr = accounts.begin(); - bool es = true; - bool ea = true; - Shared::ConnectionState cs = (*itr)->getState(); - Shared::Availability av = (*itr)->getAvailability(); - itr++; - for (Accounts::const_iterator end = accounts.end(); itr != end; itr++) { - Account* item = *itr; - if (item->getState() != cs) { - es = false; - } - if (item->getAvailability() != av) { - ea = false; - } - } - - if (es) { - if (cs == Shared::ConnectionState::disconnected) { - state = Shared::Availability::offline; - emit stateChanged(state); - } else if (ea) { - state = av; - emit stateChanged(state); - } - } - } void Core::Squawk::onAccountAddContact(const QString& jid, const QString& group, const QMap& data) @@ -416,8 +419,15 @@ void Core::Squawk::modifyAccountRequest(const QString& name, const QMapreconnect(); + bool activeChanged = false; + mItr = map.find("active"); + if (mItr == map.end() || mItr->toBool() == acc->getActive()) { + if (needToReconnect && st != Shared::ConnectionState::disconnected) { + acc->reconnect(); + } + } else { + acc->setActive(mItr->toBool()); + activeChanged = true; } mItr = map.find("login"); @@ -454,6 +464,10 @@ void Core::Squawk::modifyAccountRequest(const QString& name, const QMapgetActive() && state != Shared::Availability::offline) { + acc->connect(); + } + emit changeAccount(name, map); } @@ -675,85 +689,62 @@ void Core::Squawk::uploadVCard(const QString& account, const Shared::VCard& card itr->second->uploadVCard(card); } -void Core::Squawk::responsePassword(const QString& account, const QString& password) -{ - AccountsMap::const_iterator itr = amap.find(account); - if (itr == amap.end()) { - qDebug() << "An attempt to set password to non existing account" << account << ", skipping"; - return; - } - itr->second->setPassword(password); - emit changeAccount(account, {{"password", password}}); - accountReady(); -} - void Core::Squawk::readSettings() { QSettings settings; settings.beginGroup("core"); int size = settings.beginReadArray("accounts"); - waitingForAccounts = size; for (int i = 0; i < size; ++i) { settings.setArrayIndex(i); - parseAccount( + Shared::AccountPassword passwordType = + Shared::Global::fromInt( + settings.value("passwordType", static_cast(Shared::AccountPassword::plain)).toInt() + ); + + QString password = settings.value("password", "").toString(); + if (passwordType == Shared::AccountPassword::jammed) { + SimpleCrypt crypto(passwordHash); + password = crypto.decryptToString(password); + } + + addAccount( settings.value("login").toString(), settings.value("server").toString(), - settings.value("password", "").toString(), + password, settings.value("name").toString(), settings.value("resource").toString(), - Shared::Global::fromInt(settings.value("passwordType", static_cast(Shared::AccountPassword::plain)).toInt()) + settings.value("active").toBool(), + passwordType ); } settings.endArray(); settings.endGroup(); + + qDebug() << "Squawk core is ready"; + emit ready(); } -void Core::Squawk::accountReady() +void Core::Squawk::onAccountNeedPassword() { - --waitingForAccounts; - - if (waitingForAccounts == 0) { - emit ready(); - } -} - -void Core::Squawk::parseAccount( - const QString& login, - const QString& server, - const QString& password, - const QString& name, - const QString& resource, - Shared::AccountPassword passwordType -) -{ - switch (passwordType) { - case Shared::AccountPassword::plain: - addAccount(login, server, password, name, resource, passwordType); - accountReady(); - break; - case Shared::AccountPassword::jammed: { - SimpleCrypt crypto(passwordHash); - QString decrypted = crypto.decryptToString(password); - addAccount(login, server, decrypted, name, resource, passwordType); - accountReady(); - } - break; - case Shared::AccountPassword::alwaysAsk: - addAccount(login, server, QString(), name, resource, passwordType); - emit requestPassword(name); + Account* acc = static_cast(sender()); + switch (acc->getPasswordType()) { + case Shared::AccountPassword::alwaysAsk: + emit requestPassword(acc->getName()); break; case Shared::AccountPassword::kwallet: { - addAccount(login, server, QString(), name, resource, passwordType); #ifdef WITH_KWALLET if (kwallet.supportState() == PSE::KWallet::success) { - kwallet.requestReadPassword(name); + kwallet.requestReadPassword(acc->getName()); } else { #endif - emit requestPassword(name); + emit requestPassword(acc->getName()); #ifdef WITH_KWALLET } #endif + break; } + default: + break; //should never happen; } } @@ -762,16 +753,19 @@ void Core::Squawk::onWalletRejectPassword(const QString& login) emit requestPassword(login); } -void Core::Squawk::onWalletResponsePassword(const QString& login, const QString& password) +void Core::Squawk::responsePassword(const QString& account, const QString& password) { - AccountsMap::const_iterator itr = amap.find(login); + AccountsMap::const_iterator itr = amap.find(account); if (itr == amap.end()) { - qDebug() << "An attempt to set password to non existing account" << login << ", skipping"; + qDebug() << "An attempt to set password to non existing account" << account << ", skipping"; return; } - itr->second->setPassword(password); - emit changeAccount(login, {{"password", password}}); - accountReady(); + Account* acc = itr->second; + acc->setPassword(password); + emit changeAccount(account, {{"password", password}}); + if (state != Shared::Availability::offline && acc->getActive()) { + acc->connect(); + } } void Core::Squawk::onAccountUploadFileError(const QString& jid, const QString id, const QString& errorText) diff --git a/core/squawk.h b/core/squawk.h index 6cd251f..6cb3115 100644 --- a/core/squawk.h +++ b/core/squawk.h @@ -134,7 +134,6 @@ private: AccountsMap amap; Shared::Availability state; NetworkAccess network; - uint8_t waitingForAccounts; bool isInitialized; #ifdef WITH_KWALLET @@ -148,6 +147,7 @@ private slots: const QString& password, const QString& name, const QString& resource, + bool active, Shared::AccountPassword passwordType ); @@ -172,22 +172,22 @@ private slots: void onAccountChangeRoomPresence(const QString& jid, const QString& nick, const QMap& data); void onAccountRemoveRoomPresence(const QString& jid, const QString& nick); void onAccountChangeMessage(const QString& jid, const QString& id, const QMap& data); + void onAccountNeedPassword(); void onAccountUploadFileError(const QString& jid, const QString id, const QString& errorText); void onWalletOpened(bool success); - void onWalletResponsePassword(const QString& login, const QString& password); void onWalletRejectPassword(const QString& login); private: void readSettings(); - void accountReady(); void parseAccount( const QString& login, const QString& server, const QString& password, const QString& name, const QString& resource, + bool active, Shared::AccountPassword passwordType ); diff --git a/ui/dialogqueue.cpp b/ui/dialogqueue.cpp index 1887b28..f5be82b 100644 --- a/ui/dialogqueue.cpp +++ b/ui/dialogqueue.cpp @@ -107,7 +107,7 @@ void DialogQueue::onPropmtRejected() case none: break; case askPassword: - emit squawk->responsePassword(currentSource, prompt->textValue()); + emit squawk->disconnectAccount(currentSource); break; } actionDone(); diff --git a/ui/models/account.cpp b/ui/models/account.cpp index 43cb3ed..cf1efb4 100644 --- a/ui/models/account.cpp +++ b/ui/models/account.cpp @@ -32,7 +32,8 @@ Models::Account::Account(const QMap& data, Models::Item* pare state(Shared::ConnectionState::disconnected), availability(Shared::Availability::offline), passwordType(Shared::AccountPassword::plain), - wasEverConnected(false) + wasEverConnected(false), + active(false) { QMap::const_iterator sItr = data.find("state"); if (sItr != data.end()) { @@ -46,6 +47,10 @@ Models::Account::Account(const QMap& data, Models::Item* pare if (pItr != data.end()) { setPasswordType(pItr.value().toUInt()); } + QMap::const_iterator acItr = data.find("active"); + if (acItr != data.end()) { + setActive(acItr.value().toBool()); + } } Models::Account::~Account() @@ -176,6 +181,8 @@ QVariant Models::Account::data(int column) const return avatarPath; case 9: return Shared::Global::getName(passwordType); + case 10: + return active; default: return QVariant(); } @@ -183,7 +190,7 @@ QVariant Models::Account::data(int column) const int Models::Account::columnCount() const { - return 10; + return 11; } void Models::Account::update(const QString& field, const QVariant& value) @@ -208,6 +215,8 @@ void Models::Account::update(const QString& field, const QVariant& value) setAvatarPath(value.toString()); } else if (field == "passwordType") { setPasswordType(value.toUInt()); + } else if (field == "active") { + setActive(value.toBool()); } } @@ -281,3 +290,16 @@ void Models::Account::setPasswordType(unsigned int pt) { setPasswordType(Shared::Global::fromInt(pt)); } + +bool Models::Account::getActive() const +{ + return active; +} + +void Models::Account::setActive(bool p_active) +{ + if (active != p_active) { + active = p_active; + changed(10); + } +} diff --git a/ui/models/account.h b/ui/models/account.h index 3d2310f..ab2b629 100644 --- a/ui/models/account.h +++ b/ui/models/account.h @@ -58,6 +58,9 @@ namespace Models { void setAvatarPath(const QString& path); QString getAvatarPath() const; + + void setActive(bool active); + bool getActive() const; void setAvailability(Shared::Availability p_avail); void setAvailability(unsigned int p_avail); @@ -91,6 +94,7 @@ namespace Models { Shared::Availability availability; Shared::AccountPassword passwordType; bool wasEverConnected; + bool active; protected slots: void toOfflineState() override; diff --git a/ui/models/accounts.cpp b/ui/models/accounts.cpp index 4343481..463ab40 100644 --- a/ui/models/accounts.cpp +++ b/ui/models/accounts.cpp @@ -48,6 +48,10 @@ QVariant Models::Accounts::data (const QModelIndex& index, int role) const answer = Shared::connectionStateIcon(accs[index.row()]->getState()); } break; + case Qt::ForegroundRole: + if (!accs[index.row()]->getActive()) { + answer = qApp->palette().brush(QPalette::Disabled, QPalette::Text); + } default: break; } diff --git a/ui/models/roster.cpp b/ui/models/roster.cpp index 588fb1d..1355fe3 100644 --- a/ui/models/roster.cpp +++ b/ui/models/roster.cpp @@ -276,6 +276,18 @@ QVariant Models::Roster::data (const QModelIndex& index, int role) const break; } break; + case Qt::ForegroundRole: + switch (item->type) { + case Item::account: { + Account* acc = static_cast(item); + if (!acc->getActive()) { + result = qApp->palette().brush(QPalette::Disabled, QPalette::Text); + } + } + break; + default: + break; + } default: break; } diff --git a/ui/squawk.cpp b/ui/squawk.cpp index 335b8d0..3a3d1d9 100644 --- a/ui/squawk.cpp +++ b/ui/squawk.cpp @@ -234,29 +234,7 @@ void Squawk::newAccount(const QMap& account) void Squawk::onComboboxActivated(int index) { Shared::Availability av = Shared::Global::fromInt(index); - if (av != Shared::Availability::offline) { - int size = rosterModel.accountsModel->rowCount(QModelIndex()); - if (size > 0) { - emit changeState(av); - for (int i = 0; i < size; ++i) { - Models::Account* acc = rosterModel.accountsModel->getAccount(i); - if (acc->getState() == Shared::ConnectionState::disconnected) { - emit connectAccount(acc->getName()); - } - } - } else { - m_ui->comboBox->setCurrentIndex(static_cast(Shared::Availability::offline)); - } - } else { - emit changeState(av); - int size = rosterModel.accountsModel->rowCount(QModelIndex()); - for (int i = 0; i != size; ++i) { - Models::Account* acc = rosterModel.accountsModel->getAccount(i); - if (acc->getState() != Shared::ConnectionState::disconnected) { - emit disconnectAccount(acc->getName()); - } - } - } + emit changeState(av); } void Squawk::changeAccount(const QString& account, const QMap& data) @@ -573,17 +551,12 @@ void Squawk::onRosterContextMenu(const QPoint& point) hasMenu = true; QString name = acc->getName(); - if (acc->getState() != Shared::ConnectionState::disconnected) { - QAction* con = contextMenu->addAction(Shared::icon("network-disconnect"), tr("Disconnect")); - con->setEnabled(active); - connect(con, &QAction::triggered, [this, name]() { - emit disconnectAccount(name); - }); + if (acc->getActive()) { + QAction* con = contextMenu->addAction(Shared::icon("network-disconnect"), tr("Deactivate")); + connect(con, &QAction::triggered, std::bind(&Squawk::disconnectAccount, this, name)); } else { - QAction* con = contextMenu->addAction(Shared::icon("network-connect"), tr("Connect")); - connect(con, &QAction::triggered, [this, name]() { - emit connectAccount(name); - }); + QAction* con = contextMenu->addAction(Shared::icon("network-connect"), tr("Activate")); + connect(con, &QAction::triggered, std::bind(&Squawk::connectAccount, this, name)); } QAction* card = contextMenu->addAction(Shared::icon("user-properties"), tr("VCard")); @@ -591,11 +564,7 @@ void Squawk::onRosterContextMenu(const QPoint& point) connect(card, &QAction::triggered, std::bind(&Squawk::onActivateVCard, this, name, acc->getBareJid(), true)); QAction* remove = contextMenu->addAction(Shared::icon("edit-delete"), tr("Remove")); - remove->setEnabled(active); - connect(remove, &QAction::triggered, [this, name]() { - emit removeAccount(name); - }); - + connect(remove, &QAction::triggered, std::bind(&Squawk::removeAccount, this, name)); } break; case Models::Item::contact: { @@ -839,20 +808,16 @@ void Squawk::readSettings() { QSettings settings; settings.beginGroup("ui"); - + int avail; if (settings.contains("availability")) { - int avail = settings.value("availability").toInt(); - m_ui->comboBox->setCurrentIndex(avail); - emit stateChanged(Shared::Global::fromInt(avail)); - - int size = settings.beginReadArray("connectedAccounts"); - for (int i = 0; i < size; ++i) { - settings.setArrayIndex(i); - emit connectAccount(settings.value("name").toString()); //TODO this is actually not needed, stateChanged event already connects everything you have - } // need to fix that - settings.endArray(); + avail = settings.value("availability").toInt(); + } else { + avail = static_cast(Shared::Availability::online); } settings.endGroup(); + m_ui->comboBox->setCurrentIndex(avail); + + emit changeState(Shared::Global::fromInt(avail)); } void Squawk::writeSettings() @@ -867,19 +832,10 @@ void Squawk::writeSettings() settings.setValue("splitter", m_ui->splitter->saveState()); settings.setValue("availability", m_ui->comboBox->currentIndex()); - settings.beginWriteArray("connectedAccounts"); - int size = rosterModel.accountsModel->rowCount(QModelIndex()); - for (int i = 0; i < size; ++i) { - Models::Account* acc = rosterModel.accountsModel->getAccount(i); - if (acc->getState() != Shared::ConnectionState::disconnected) { - settings.setArrayIndex(i); - settings.setValue("name", acc->getName()); - } - } - settings.endArray(); settings.remove("roster"); settings.beginGroup("roster"); + int size = rosterModel.accountsModel->rowCount(QModelIndex()); for (int i = 0; i < size; ++i) { QModelIndex acc = rosterModel.index(i, 0, QModelIndex()); Models::Account* account = rosterModel.accountsModel->getAccount(i); diff --git a/ui/widgets/accounts/account.cpp b/ui/widgets/accounts/account.cpp index ba3af6b..164af6c 100644 --- a/ui/widgets/accounts/account.cpp +++ b/ui/widgets/accounts/account.cpp @@ -53,6 +53,7 @@ QMap Account::value() const map["name"] = m_ui->name->text(); map["resource"] = m_ui->resource->text(); map["passwordType"] = m_ui->passwordType->currentIndex(); + map["active"] = m_ui->active->isChecked(); return map; } diff --git a/ui/widgets/accounts/account.ui b/ui/widgets/accounts/account.ui index a1879bc..b7f9f26 100644 --- a/ui/widgets/accounts/account.ui +++ b/ui/widgets/accounts/account.ui @@ -7,7 +7,7 @@ 0 0 438 - 342 + 345 @@ -34,7 +34,7 @@ 6 - + Your account login @@ -44,14 +44,14 @@ - + Server - + A server address of your account. Like 404.city or macaw.me @@ -61,21 +61,21 @@ - + Login - + Password - + Password of your account @@ -97,14 +97,14 @@ - + Name - + Just a name how would you call this account, doesn't affect anything @@ -114,14 +114,14 @@ - + Resource - + A resource name like "Home" or "Work" @@ -131,17 +131,17 @@ - + Password storage - + - + @@ -157,6 +157,23 @@ + + + + Active + + + + + + + enable + + + true + + + diff --git a/ui/widgets/accounts/accounts.cpp b/ui/widgets/accounts/accounts.cpp index 7f4a135..82a8ca0 100644 --- a/ui/widgets/accounts/accounts.cpp +++ b/ui/widgets/accounts/accounts.cpp @@ -83,7 +83,8 @@ void Accounts::onEditButton() {"server", mAcc->getServer()}, {"name", mAcc->getName()}, {"resource", mAcc->getResource()}, - {"passwordType", QVariant::fromValue(mAcc->getPasswordType())} + {"passwordType", QVariant::fromValue(mAcc->getPasswordType())}, + {"active", mAcc->getActive()} }); acc->lockId(); connect(acc, &Account::accepted, this, &Accounts::onAccountAccepted); @@ -118,17 +119,17 @@ void Accounts::updateConnectButton() bool allConnected = true; for (int i = 0; i < selectionSize && allConnected; ++i) { const Models::Account* mAcc = model->getAccount(sm->selectedRows().at(i).row()); - allConnected = mAcc->getState() == Shared::ConnectionState::connected; + allConnected = allConnected && mAcc->getActive(); } if (allConnected) { toDisconnect = true; - m_ui->connectButton->setText(tr("Disconnect")); + m_ui->connectButton->setText(tr("Deactivate")); } else { toDisconnect = false; - m_ui->connectButton->setText(tr("Connect")); + m_ui->connectButton->setText(tr("Activate")); } } else { - m_ui->connectButton->setText(tr("Connect")); + m_ui->connectButton->setText(tr("Activate")); toDisconnect = false; m_ui->connectButton->setEnabled(false); } -- 2.45.1 From ce686e121b7f29b3fe5d9c05c85f205185734089 Mon Sep 17 00:00:00 2001 From: blue Date: Wed, 13 Apr 2022 22:02:48 +0300 Subject: [PATCH 10/29] account removal bugfix, some testing --- CHANGELOG.md | 9 ++++++++- core/squawk.cpp | 12 ++++++++---- ui/squawk.cpp | 2 +- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f563c85..410ff11 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,11 +2,18 @@ ## Squawk 0.2.2 (UNRELEASED) ### Bug fixes +- now when you remove an account it actually gets removed +- segfault on unitialized Availability in some rare occesions ### Improvements +- there is a way to disable an account and it wouldn't connect when you change availability +- if you cancel password query an account becomes inactive and doesn't annoy you anymore +- if you filled password field and chose KWallet as a storage Squawk wouldn't ask you again for the same password +- if left the password field empty and chose KWallet as a storage Squawk will try to get that passord from KWallet before asking you to input it +- accounts now connect to the server asyncronously - if one is stopped on password prompt another is connecting ### New features - +- new "About" window with links, license, gratitudes ## Squawk 0.2.1 (Apr 02, 2022) ### Bug fixes diff --git a/core/squawk.cpp b/core/squawk.cpp index d594553..49a2b34 100644 --- a/core/squawk.cpp +++ b/core/squawk.cpp @@ -139,8 +139,10 @@ void Core::Squawk::addAccount( bool active, Shared::AccountPassword passwordType) { - QSettings settings; - + if (amap.count(name) > 0) { + qDebug() << "An attempt to add account" << name << "but an account with such name already exist, ignoring"; + return; + } Account* acc = new Account(login, server, password, name, active, &network); acc->setResource(resource); acc->setPasswordType(passwordType); @@ -198,8 +200,10 @@ void Core::Squawk::addAccount( switch (passwordType) { case Shared::AccountPassword::alwaysAsk: case Shared::AccountPassword::kwallet: - acc->invalidatePassword(); - break; + if (password == "") { + acc->invalidatePassword(); + break; + } default: break; } diff --git a/ui/squawk.cpp b/ui/squawk.cpp index 3a3d1d9..a447458 100644 --- a/ui/squawk.cpp +++ b/ui/squawk.cpp @@ -564,7 +564,7 @@ void Squawk::onRosterContextMenu(const QPoint& point) connect(card, &QAction::triggered, std::bind(&Squawk::onActivateVCard, this, name, acc->getBareJid(), true)); QAction* remove = contextMenu->addAction(Shared::icon("edit-delete"), tr("Remove")); - connect(remove, &QAction::triggered, std::bind(&Squawk::removeAccount, this, name)); + connect(remove, &QAction::triggered, std::bind(&Squawk::removeAccountRequest, this, name)); } break; case Models::Item::contact: { -- 2.45.1 From 8f949277f68be1f4baeb282b3c483948400dd6ca Mon Sep 17 00:00:00 2001 From: blue Date: Thu, 14 Apr 2022 11:13:27 +0300 Subject: [PATCH 11/29] actual pasword reasking on failed authentication --- CHANGELOG.md | 1 + core/account.cpp | 7 ++ core/account.h | 8 ++ core/squawk.cpp | 25 +++- core/squawk.h | 2 +- ui/dialogqueue.cpp | 50 ++++++-- ui/dialogqueue.h | 9 +- ui/squawk.cpp | 10 +- ui/squawk.h | 4 +- ui/widgets/accounts/CMakeLists.txt | 3 + ui/widgets/accounts/credentialsprompt.cpp | 60 +++++++++ ui/widgets/accounts/credentialsprompt.h | 52 ++++++++ ui/widgets/accounts/credentialsprompt.ui | 144 ++++++++++++++++++++++ 13 files changed, 347 insertions(+), 28 deletions(-) create mode 100644 ui/widgets/accounts/credentialsprompt.cpp create mode 100644 ui/widgets/accounts/credentialsprompt.h create mode 100644 ui/widgets/accounts/credentialsprompt.ui diff --git a/CHANGELOG.md b/CHANGELOG.md index 410ff11..55ef1f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ ### New features - new "About" window with links, license, gratitudes +- if the authentication failed Squawk will ask againg for your password and login ## Squawk 0.2.1 (Apr 02, 2022) ### Bug fixes diff --git a/core/account.cpp b/core/account.cpp index 4d0480f..3b9d7ec 100644 --- a/core/account.cpp +++ b/core/account.cpp @@ -43,6 +43,7 @@ Account::Account(const QString& p_login, const QString& p_server, const QString& reconnectTimer(new QTimer), network(p_net), passwordType(Shared::AccountPassword::plain), + lastError(Error::none), pepSupport(false), active(p_active), notReadyPassword(false), @@ -183,6 +184,7 @@ void Core::Account::onClientStateChange(QXmppClient::State st) dm->requestItems(getServer()); dm->requestInfo(getServer()); } + lastError = Error::none; emit connectionStateChanged(state); } } else { @@ -415,6 +417,7 @@ void Core::Account::onClientError(QXmppClient::Error err) qDebug() << "Error"; QString errorText; QString errorType; + lastError = Error::other; switch (err) { case QXmppClient::SocketError: errorText = client.socketErrorString(); @@ -456,6 +459,7 @@ void Core::Account::onClientError(QXmppClient::Error err) break; case QXmppStanza::Error::NotAuthorized: errorText = "Authentication error"; + lastError = Error::authentication; break; #if (QXMPP_VERSION) < QT_VERSION_CHECK(1, 3, 0) case QXmppStanza::Error::PaymentRequired: @@ -750,3 +754,6 @@ void Core::Account::renameContactRequest(const QString& jid, const QString& newN void Core::Account::invalidatePassword() { notReadyPassword = true;} +Core::Account::Error Core::Account::getLastError() const { + return lastError;} + diff --git a/core/account.h b/core/account.h index aa65b27..2c9ec70 100644 --- a/core/account.h +++ b/core/account.h @@ -61,6 +61,12 @@ class Account : public QObject friend class RosterHandler; friend class VCardHandler; public: + enum class Error { + authentication, + other, + none + }; + Account( const QString& p_login, const QString& p_server, @@ -82,6 +88,7 @@ public: QString getFullJid() const; Shared::Availability getAvailability() const; Shared::AccountPassword getPasswordType() const; + Error getLastError() const; bool getActive() const; void setName(const QString& p_name); @@ -166,6 +173,7 @@ private: NetworkAccess* network; Shared::AccountPassword passwordType; + Error lastError; bool pepSupport; bool active; bool notReadyPassword; diff --git a/core/squawk.cpp b/core/squawk.cpp index 49a2b34..0f8fe9f 100644 --- a/core/squawk.cpp +++ b/core/squawk.cpp @@ -260,7 +260,10 @@ void Core::Squawk::disconnectAccount(const QString& account) void Core::Squawk::onAccountConnectionStateChanged(Shared::ConnectionState p_state) { Account* acc = static_cast(sender()); - emit changeAccount(acc->getName(), {{"state", QVariant::fromValue(p_state)}}); + emit changeAccount(acc->getName(), { + {"state", QVariant::fromValue(p_state)}, + {"error", ""} + }); #ifdef WITH_KWALLET if (p_state == Shared::ConnectionState::connected) { @@ -398,6 +401,7 @@ void Core::Squawk::modifyAccountRequest(const QString& name, const QMapgetState(); QMap::const_iterator mItr; bool needToReconnect = false; + bool wentReconnecting = false; mItr = map.find("login"); if (mItr != map.end()) { @@ -428,6 +432,7 @@ void Core::Squawk::modifyAccountRequest(const QString& name, const QMaptoBool() == acc->getActive()) { if (needToReconnect && st != Shared::ConnectionState::disconnected) { acc->reconnect(); + wentReconnecting = true; } } else { acc->setActive(mItr->toBool()); @@ -468,8 +473,12 @@ void Core::Squawk::modifyAccountRequest(const QString& name, const QMapgetActive() && state != Shared::Availability::offline) { - acc->connect(); + if (state != Shared::Availability::offline) { + if (activeChanged && acc->getActive()) { + acc->connect(); + } else if (!wentReconnecting && acc->getActive() && acc->getLastError() == Account::Error::authentication) { + acc->connect(); + } } emit changeAccount(name, map); @@ -479,6 +488,10 @@ void Core::Squawk::onAccountError(const QString& text) { Account* acc = static_cast(sender()); emit changeAccount(acc->getName(), {{"error", text}}); + + if (acc->getLastError() == Account::Error::authentication) { + emit requestPassword(acc->getName(), true); + } } void Core::Squawk::removeAccountRequest(const QString& name) @@ -733,7 +746,7 @@ void Core::Squawk::onAccountNeedPassword() Account* acc = static_cast(sender()); switch (acc->getPasswordType()) { case Shared::AccountPassword::alwaysAsk: - emit requestPassword(acc->getName()); + emit requestPassword(acc->getName(), false); break; case Shared::AccountPassword::kwallet: { #ifdef WITH_KWALLET @@ -741,7 +754,7 @@ void Core::Squawk::onAccountNeedPassword() kwallet.requestReadPassword(acc->getName()); } else { #endif - emit requestPassword(acc->getName()); + emit requestPassword(acc->getName(), false); #ifdef WITH_KWALLET } #endif @@ -754,7 +767,7 @@ void Core::Squawk::onAccountNeedPassword() void Core::Squawk::onWalletRejectPassword(const QString& login) { - emit requestPassword(login); + emit requestPassword(login, false); } void Core::Squawk::responsePassword(const QString& account, const QString& password) diff --git a/core/squawk.h b/core/squawk.h index 6cb3115..c82b1c8 100644 --- a/core/squawk.h +++ b/core/squawk.h @@ -86,7 +86,7 @@ signals: void responseVCard(const QString& jid, const Shared::VCard& card); void changeMessage(const QString& account, const QString& jid, const QString& id, const QMap& data); - void requestPassword(const QString& account); + void requestPassword(const QString& account, bool authernticationError); public slots: void start(); diff --git a/ui/dialogqueue.cpp b/ui/dialogqueue.cpp index f5be82b..02f8688 100644 --- a/ui/dialogqueue.cpp +++ b/ui/dialogqueue.cpp @@ -76,16 +76,31 @@ void DialogQueue::performNextAction() case none: actionDone(); break; - case askPassword: - prompt = new QInputDialog(squawk); - connect(prompt, &QDialog::accepted, this, &DialogQueue::onPropmtAccepted); - connect(prompt, &QDialog::rejected, this, &DialogQueue::onPropmtRejected); - prompt->setInputMode(QInputDialog::TextInput); - prompt->setTextEchoMode(QLineEdit::Password); - prompt->setLabelText(tr("Input the password for account %1").arg(currentSource)); - prompt->setWindowTitle(tr("Password for account %1").arg(currentSource)); - prompt->setTextValue(""); - prompt->exec(); + case askPassword: { + QInputDialog* dialog = new QInputDialog(squawk); + prompt = dialog; + connect(dialog, &QDialog::accepted, this, &DialogQueue::onPropmtAccepted); + connect(dialog, &QDialog::rejected, this, &DialogQueue::onPropmtRejected); + dialog->setInputMode(QInputDialog::TextInput); + dialog->setTextEchoMode(QLineEdit::Password); + dialog->setLabelText(tr("Input the password for account %1").arg(currentSource)); + dialog->setWindowTitle(tr("Password for account %1").arg(currentSource)); + dialog->setTextValue(""); + dialog->exec(); + } + break; + case askCredentials: { + CredentialsPrompt* dialog = new CredentialsPrompt(squawk); + prompt = dialog; + connect(dialog, &QDialog::accepted, this, &DialogQueue::onPropmtAccepted); + connect(dialog, &QDialog::rejected, this, &DialogQueue::onPropmtRejected); + Models::Account* acc = squawk->rosterModel.getAccount(currentSource); + dialog->setAccount(currentSource); + dialog->setLogin(acc->getLogin()); + dialog->setPassword(acc->getPassword()); + dialog->exec(); + } + break; } } @@ -94,8 +109,18 @@ void DialogQueue::onPropmtAccepted() switch (currentAction) { case none: break; - case askPassword: - emit squawk->responsePassword(currentSource, prompt->textValue()); + case askPassword: { + QInputDialog* dialog = static_cast(prompt); + emit squawk->responsePassword(currentSource, dialog->textValue()); + } + break; + case askCredentials: { + CredentialsPrompt* dialog = static_cast(prompt); + emit squawk->modifyAccountRequest(currentSource, { + {"login", dialog->getLogin()}, + {"password", dialog->getPassword()} + }); + } break; } actionDone(); @@ -107,6 +132,7 @@ void DialogQueue::onPropmtRejected() case none: break; case askPassword: + case askCredentials: emit squawk->disconnectAccount(currentSource); break; } diff --git a/ui/dialogqueue.h b/ui/dialogqueue.h index c5bf011..bfc1f21 100644 --- a/ui/dialogqueue.h +++ b/ui/dialogqueue.h @@ -24,9 +24,7 @@ #include #include -/** - * @todo write docs - */ +#include class Squawk; @@ -36,7 +34,8 @@ class DialogQueue : public QObject public: enum Action { none, - askPassword + askPassword, + askCredentials }; DialogQueue(Squawk* squawk); @@ -85,7 +84,7 @@ private: Collection& collection; Sequence& sequence; - QInputDialog* prompt; + QDialog* prompt; Squawk* squawk; }; diff --git a/ui/squawk.cpp b/ui/squawk.cpp index a447458..a0f16b2 100644 --- a/ui/squawk.cpp +++ b/ui/squawk.cpp @@ -880,8 +880,14 @@ void Squawk::onItemCollepsed(const QModelIndex& index) } } -void Squawk::requestPassword(const QString& account) { - dialogueQueue.addAction(account, DialogQueue::askPassword);} +void Squawk::requestPassword(const QString& account, bool authenticationError) { + if (authenticationError) { + dialogueQueue.addAction(account, DialogQueue::askCredentials); + } else { + dialogueQueue.addAction(account, DialogQueue::askPassword); + } + +} void Squawk::subscribeConversation(Conversation* conv) { diff --git a/ui/squawk.h b/ui/squawk.h index 6ee666c..5a77f17 100644 --- a/ui/squawk.h +++ b/ui/squawk.h @@ -51,7 +51,7 @@ class Squawk; class Squawk : public QMainWindow { Q_OBJECT - + friend class DialogQueue; public: explicit Squawk(QWidget *parent = nullptr); ~Squawk() override; @@ -114,7 +114,7 @@ public slots: void fileUploadComplete(const std::list 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); + void requestPassword(const QString& account, bool authenticationError); private: typedef std::map Conversations; diff --git a/ui/widgets/accounts/CMakeLists.txt b/ui/widgets/accounts/CMakeLists.txt index ad2f117..970985d 100644 --- a/ui/widgets/accounts/CMakeLists.txt +++ b/ui/widgets/accounts/CMakeLists.txt @@ -5,4 +5,7 @@ target_sources(squawk PRIVATE accounts.cpp accounts.h accounts.ui + credentialsprompt.cpp + credentialsprompt.h + credentialsprompt.ui ) diff --git a/ui/widgets/accounts/credentialsprompt.cpp b/ui/widgets/accounts/credentialsprompt.cpp new file mode 100644 index 0000000..3d1bafa --- /dev/null +++ b/ui/widgets/accounts/credentialsprompt.cpp @@ -0,0 +1,60 @@ +// Squawk messenger. +// Copyright (C) 2019 Yury Gubich +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General 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 "credentialsprompt.h" +#include "ui_credentialsprompt.h" + +CredentialsPrompt::CredentialsPrompt(QWidget* parent): + QDialog(parent), + m_ui(new Ui::CredentialsPrompt), + title(), + message() +{ + m_ui->setupUi(this); + + title = windowTitle(); + message = m_ui->message->text(); +} + +CredentialsPrompt::~CredentialsPrompt() +{ +} + +void CredentialsPrompt::setAccount(const QString& account) +{ + m_ui->message->setText(message.arg(account)); + setWindowTitle(title.arg(account)); +} + +QString CredentialsPrompt::getLogin() const +{ + return m_ui->login->text(); +} + +QString CredentialsPrompt::getPassword() const +{ + return m_ui->password->text(); +} + +void CredentialsPrompt::setLogin(const QString& login) +{ + m_ui->login->setText(login); +} + +void CredentialsPrompt::setPassword(const QString& password) +{ + m_ui->password->setText(password); +} diff --git a/ui/widgets/accounts/credentialsprompt.h b/ui/widgets/accounts/credentialsprompt.h new file mode 100644 index 0000000..ce9a791 --- /dev/null +++ b/ui/widgets/accounts/credentialsprompt.h @@ -0,0 +1,52 @@ +// Squawk messenger. +// Copyright (C) 2019 Yury Gubich +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General 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 CREDENTIALSPROMPT_H +#define CREDENTIALSPROMPT_H + +#include +#include + +namespace Ui +{ +class CredentialsPrompt; +} + +/** + * @todo write docs + */ +class CredentialsPrompt : public QDialog +{ + Q_OBJECT + +public: + CredentialsPrompt(QWidget* parent = nullptr); + ~CredentialsPrompt(); + + void setAccount(const QString& account); + void setLogin(const QString& login); + void setPassword(const QString& password); + + QString getLogin() const; + QString getPassword() const; + +private: + QScopedPointer m_ui; + QString title; + QString message; +}; + +#endif // CREDENTIALSPROMPT_H diff --git a/ui/widgets/accounts/credentialsprompt.ui b/ui/widgets/accounts/credentialsprompt.ui new file mode 100644 index 0000000..2ad4d8d --- /dev/null +++ b/ui/widgets/accounts/credentialsprompt.ui @@ -0,0 +1,144 @@ + + + CredentialsPrompt + + + + 0 + 0 + 318 + 229 + + + + Authentication error: %1 + + + + + + true + + + + 0 + 0 + + + + Couldn't authenticate account %1: login or password is icorrect. +Would you like to check them and try again? + + + Qt::AlignCenter + + + true + + + + + + + + + Login + + + + + + + Your account login (without @server.domain) + + + + + + + Password + + + + + + + Your password + + + Qt::ImhHiddenText|Qt::ImhNoAutoUppercase|Qt::ImhNoPredictiveText|Qt::ImhSensitiveData + + + + + + QLineEdit::Password + + + false + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + CredentialsPrompt + accept() + + + 20 + 20 + + + 20 + 20 + + + + + buttonBox + rejected() + CredentialsPrompt + reject() + + + 20 + 20 + + + 20 + 20 + + + + + -- 2.45.1 From 51ac1ac709042cb61f122860de5f47c8f82e8e63 Mon Sep 17 00:00:00 2001 From: blue Date: Sun, 17 Apr 2022 14:58:46 +0300 Subject: [PATCH 12/29] first attempt --- ui/utils/CMakeLists.txt | 2 + ui/utils/textmeter.cpp | 233 +++++++++++++++++++++ ui/utils/textmeter.h | 68 ++++++ ui/widgets/messageline/messagedelegate.cpp | 11 +- ui/widgets/messageline/messagedelegate.h | 2 + 5 files changed, 311 insertions(+), 5 deletions(-) create mode 100644 ui/utils/textmeter.cpp create mode 100644 ui/utils/textmeter.h diff --git a/ui/utils/CMakeLists.txt b/ui/utils/CMakeLists.txt index b46d30d..823287d 100644 --- a/ui/utils/CMakeLists.txt +++ b/ui/utils/CMakeLists.txt @@ -15,4 +15,6 @@ target_sources(squawk PRIVATE resizer.h shadowoverlay.cpp shadowoverlay.h + textmeter.cpp + textmeter.h ) diff --git a/ui/utils/textmeter.cpp b/ui/utils/textmeter.cpp new file mode 100644 index 0000000..51c6d54 --- /dev/null +++ b/ui/utils/textmeter.cpp @@ -0,0 +1,233 @@ +// Squawk messenger. +// Copyright (C) 2019 Yury Gubich +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General 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 "textmeter.h" +#include +#include + +TextMeter::TextMeter(): + base(), + fonts() +{ +} + +TextMeter::~TextMeter() +{ +} + +void TextMeter::initializeFonts(const QFont& font) +{ + fonts.clear(); + QList supported = base.writingSystems(font.family()); + std::set sup; + std::set added({font.family()}); + for (const QFontDatabase::WritingSystem& system : supported) { + sup.insert(system); + } + fonts.push_back(QFontMetrics(font)); + QString style = base.styleString(font); + + QList systems = base.writingSystems(); + for (const QFontDatabase::WritingSystem& system : systems) { + if (sup.count(system) == 0) { + QStringList families = base.families(system); + if (!families.empty() && added.count(families.first()) == 0) { + QString family(families.first()); + QFont nfont = base.font(family, style, font.pointSize()); + if (added.count(nfont.family()) == 0) { + added.insert(family); + fonts.push_back(QFontMetrics(nfont)); + qDebug() << "Added font" << nfont.family() << "for" << system; + } + } + } + } +} + +QSize TextMeter::boundingSize(const QString& text, const QSize& limits) const +{ +// QString str("ridiculus mus. Suspendisse potenti. Cras pretium venenatis enim, faucibus accumsan ex"); +// bool first = true; +// int width = 0; +// QStringList list = str.split(" "); +// QFontMetrics m = fonts.front(); +// for (const QString& word : list) { +// if (first) { +// first = false; +// } else { +// width += m.horizontalAdvance(QChar::Space); +// } +// width += m.horizontalAdvance(word); +// for (const QChar& ch : word) { +// width += m.horizontalAdvance(ch); +// } +// } +// qDebug() << "together:" << m.horizontalAdvance(str); +// qDebug() << "apart:" << width; +// I cant measure or wrap text this way, this simple example shows that even this gives differen result +// The Qt implementation under it is thousands and thousands lines of code in QTextEngine +// I simply can't get though it + + if (text.size() == 0) { + return QSize (0, 0); + } + Helper current(limits.width()); + for (const QChar& ch : text) { + if (newLine(ch)) { + current.computeNewWord(); + if (current.height == 0) { + current.height = fonts.front().lineSpacing(); + } + current.beginNewLine(); + } else if (visible(ch)) { + bool found = false; + for (const QFontMetrics& metrics : fonts) { + if (metrics.inFont(ch)) { + current.computeChar(ch, metrics); + found = true; + break; + } + } + + if (!found) { + current.computeChar(ch, fonts.front()); + } + } + } + current.computeNewWord(); + current.beginNewLine(); + + int& height = current.size.rheight(); + if (height > 0) { + height -= fonts.front().leading(); + } + + return current.size; +} + +void TextMeter::Helper::computeChar(const QChar& ch, const QFontMetrics& metrics) +{ + int ha = metrics.horizontalAdvance(ch); + if (newWord(ch)) { + if (printOnLineBreak(ch)) { + if (!lineOverflow(metrics, ha, ch)){ + computeNewWord(); + } + } else { + computeNewWord(); + delayedSpaceWidth = ha; + lastSpace = ch; + } + } else { + lineOverflow(metrics, ha, ch); + } +} + +void TextMeter::Helper::computeNewLine(const QFontMetrics& metrics, int horizontalAdvance, const QChar& ch) +{ + if (wordBeganWithTheLine) { + text = word.chopped(1); + width = wordWidth - horizontalAdvance; + height = wordHeight; + } + if (width != metrics.horizontalAdvance(text)) { + qDebug() << "Kerning Error" << width - metrics.horizontalAdvance(text); + } + beginNewLine(); + if (wordBeganWithTheLine) { + word = ch; + wordWidth = horizontalAdvance; + wordHeight = metrics.lineSpacing(); + } + + wordBeganWithTheLine = true; + delayedSpaceWidth = 0; + lastSpace = QChar::Null; +} + +void TextMeter::Helper::beginNewLine() +{ + size.rheight() += height; + size.rwidth() = qMax(size.width(), width); + qDebug() << text; + text = ""; + width = 0; + height = 0; +} + +void TextMeter::Helper::computeNewWord() +{ + width += wordWidth + delayedSpaceWidth; + height = qMax(height, wordHeight); + if (lastSpace != QChar::Null) { + text += lastSpace; + } + text += word; + word = ""; + wordWidth = 0; + wordHeight = 0; + delayedSpaceWidth = 0; + lastSpace = QChar::Null; + wordBeganWithTheLine = false; +} + +bool TextMeter::Helper::lineOverflow(const QFontMetrics& metrics, int horizontalAdvance, const QChar& ch) +{ + wordHeight = qMax(wordHeight, metrics.lineSpacing()); + wordWidth += horizontalAdvance; + word += ch; + if (width + delayedSpaceWidth + wordWidth > maxWidth) { + computeNewLine(metrics, horizontalAdvance, ch); + return true; + } + return false; +} + + +bool TextMeter::newLine(const QChar& ch) +{ + return ch == QChar::LineFeed; +} + +bool TextMeter::newWord(const QChar& ch) +{ + return ch.isSpace() || ch.isPunct(); +} + +bool TextMeter::printOnLineBreak(const QChar& ch) +{ + return ch != QChar::Space; +} + +bool TextMeter::visible(const QChar& ch) +{ + return true; +} + +TextMeter::Helper::Helper(int p_maxWidth): + width(0), + height(0), + wordWidth(0), + wordHeight(0), + delayedSpaceWidth(0), + maxWidth(p_maxWidth), + wordBeganWithTheLine(true), + text(""), + word(""), + lastSpace(QChar::Null), + size(0, 0) +{ +} diff --git a/ui/utils/textmeter.h b/ui/utils/textmeter.h new file mode 100644 index 0000000..243d989 --- /dev/null +++ b/ui/utils/textmeter.h @@ -0,0 +1,68 @@ +// Squawk messenger. +// Copyright (C) 2019 Yury Gubich +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General 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 TEXTMETER_H +#define TEXTMETER_H + +#include +#include + +#include +#include +#include +#include + +class TextMeter +{ +public: + TextMeter(); + ~TextMeter(); + void initializeFonts(const QFont& font); + QSize boundingSize(const QString& text, const QSize& limits) const; + +private: + QFontDatabase base; + std::list fonts; + + struct Helper { + Helper(int maxWidth); + int width; + int height; + int wordWidth; + int wordHeight; + int delayedSpaceWidth; + int maxWidth; + bool wordBeganWithTheLine; + QString text; + QString word; + QChar lastSpace; + QSize size; + + void computeNewLine(const QFontMetrics& metrics, int horizontalAdvance, const QChar& ch); + void computeChar(const QChar& ch, const QFontMetrics& metrics); + void computeNewWord(); + void beginNewLine(); + bool lineOverflow(const QFontMetrics& metrics, int horizontalAdvance, const QChar& ch); + }; + + static bool newLine(const QChar& ch); + static bool newWord(const QChar& ch); + static bool visible(const QChar& ch); + static bool printOnLineBreak(const QChar& ch); + +}; + +#endif // TEXTMETER_H diff --git a/ui/widgets/messageline/messagedelegate.cpp b/ui/widgets/messageline/messagedelegate.cpp index 15a5e46..4ddecee 100644 --- a/ui/widgets/messageline/messagedelegate.cpp +++ b/ui/widgets/messageline/messagedelegate.cpp @@ -42,6 +42,7 @@ MessageDelegate::MessageDelegate(QObject* parent): nickFont(), dateFont(), bodyMetrics(bodyFont), + bodyMeter(), nickMetrics(nickFont), dateMetrics(dateFont), buttonHeight(0), @@ -123,10 +124,7 @@ void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio opt.displayAlignment = Qt::AlignRight | Qt::AlignTop; } - QSize bodySize(0, 0); - if (data.text.size() > 0) { - bodySize = bodyMetrics.boundingRect(opt.rect, Qt::TextWordWrap, data.text).size(); - } + QSize bodySize = bodyMeter.boundingSize(data.text, opt.rect.size()); QRect rect; if (ntds) { @@ -306,7 +304,7 @@ QSize MessageDelegate::sizeHint(const QStyleOptionViewItem& option, const QModel Models::FeedItem data = qvariant_cast(vi); QSize messageSize(0, 0); if (data.text.size() > 0) { - messageSize = bodyMetrics.boundingRect(messageRect, Qt::TextWordWrap, data.text).size(); + messageSize = bodyMeter.boundingSize(data.text, messageRect.size()); messageSize.rheight() += textMargin; } @@ -390,9 +388,12 @@ void MessageDelegate::initializeFonts(const QFont& font) dateFont.setPointSize(dateFont.pointSize() * dateFontMultiplier); } + bodyFont.setKerning(false); bodyMetrics = QFontMetrics(bodyFont); nickMetrics = QFontMetrics(nickFont); dateMetrics = QFontMetrics(dateFont); + + bodyMeter.initializeFonts(bodyFont); Preview::initializeFont(bodyFont); } diff --git a/ui/widgets/messageline/messagedelegate.h b/ui/widgets/messageline/messagedelegate.h index 9225412..38ec0ee 100644 --- a/ui/widgets/messageline/messagedelegate.h +++ b/ui/widgets/messageline/messagedelegate.h @@ -34,6 +34,7 @@ #include "shared/global.h" #include "shared/utils.h" #include "shared/pathcheck.h" +#include "ui/utils/textmeter.h" #include "preview.h" @@ -94,6 +95,7 @@ private: QFont nickFont; QFont dateFont; QFontMetrics bodyMetrics; + TextMeter bodyMeter; QFontMetrics nickMetrics; QFontMetrics dateMetrics; -- 2.45.1 From 4c20a314f050236fbcb79fa2d43322ec9045eaa1 Mon Sep 17 00:00:00 2001 From: blue Date: Sun, 17 Apr 2022 16:25:15 +0300 Subject: [PATCH 13/29] 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: -- 2.45.1 From 18859cb960aa6de0dbb393186392b3449b5177ff Mon Sep 17 00:00:00 2001 From: blue Date: Mon, 18 Apr 2022 19:54:42 +0300 Subject: [PATCH 14/29] 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; -- 2.45.1 From 83cb2201752e225b0bfb9ce5d85caafa3e8cf97e Mon Sep 17 00:00:00 2001 From: blue Date: Tue, 19 Apr 2022 20:24:41 +0300 Subject: [PATCH 15/29] 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); + } } } -- 2.45.1 From 721d3a1a89fd4daf4b0ab0643aa48f3155f23954 Mon Sep 17 00:00:00 2001 From: blue Date: Fri, 22 Apr 2022 18:26:18 +0300 Subject: [PATCH 16/29] 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 -- 2.45.1 From 3916aec358cb9ec81e7d55930bdc3ae55c38bc79 Mon Sep 17 00:00:00 2001 From: blue Date: Sat, 23 Apr 2022 16:58:08 +0300 Subject: [PATCH 17/29] 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; -- 2.45.1 From e58213b2943871f4013dedc4c8577fd373437270 Mon Sep 17 00:00:00 2001 From: blue Date: Sun, 24 Apr 2022 18:52:29 +0300 Subject: [PATCH 18/29] 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); -- 2.45.1 From 2fcc432aef6e1656ffff967aad7bbcbb9c2e2cfd Mon Sep 17 00:00:00 2001 From: blue Date: Tue, 26 Apr 2022 23:08:25 +0300 Subject: [PATCH 19/29] 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; } -- 2.45.1 From d86e2c28a02955135759fe3835676c1a16b4396f Mon Sep 17 00:00:00 2001 From: blue Date: Wed, 27 Apr 2022 01:17:53 +0300 Subject: [PATCH 20/29] an attempt to display text in a better way with QTextDocument + QTextBrowser --- shared/utils.cpp | 2 +- ui/utils/CMakeLists.txt | 2 - ui/utils/textmeter.cpp | 233 --------------------- ui/utils/textmeter.h | 68 ------ ui/widgets/messageline/feedview.cpp | 6 +- ui/widgets/messageline/messagedelegate.cpp | 104 ++++++--- ui/widgets/messageline/messagedelegate.h | 11 +- 7 files changed, 88 insertions(+), 338 deletions(-) delete mode 100644 ui/utils/textmeter.cpp delete mode 100644 ui/utils/textmeter.h diff --git a/shared/utils.cpp b/shared/utils.cpp index a7a4ecb..518d288 100644 --- a/shared/utils.cpp +++ b/shared/utils.cpp @@ -40,5 +40,5 @@ QString Shared::processMessageBody(const QString& msg) { QString processed = msg.toHtmlEscaped(); processed.replace(urlReg, "\\1"); - return "

" + processed + "

"; + return "

" + processed + "

"; } diff --git a/ui/utils/CMakeLists.txt b/ui/utils/CMakeLists.txt index 823287d..b46d30d 100644 --- a/ui/utils/CMakeLists.txt +++ b/ui/utils/CMakeLists.txt @@ -15,6 +15,4 @@ target_sources(squawk PRIVATE resizer.h shadowoverlay.cpp shadowoverlay.h - textmeter.cpp - textmeter.h ) diff --git a/ui/utils/textmeter.cpp b/ui/utils/textmeter.cpp deleted file mode 100644 index 51c6d54..0000000 --- a/ui/utils/textmeter.cpp +++ /dev/null @@ -1,233 +0,0 @@ -// Squawk messenger. -// Copyright (C) 2019 Yury Gubich -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General 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 "textmeter.h" -#include -#include - -TextMeter::TextMeter(): - base(), - fonts() -{ -} - -TextMeter::~TextMeter() -{ -} - -void TextMeter::initializeFonts(const QFont& font) -{ - fonts.clear(); - QList supported = base.writingSystems(font.family()); - std::set sup; - std::set added({font.family()}); - for (const QFontDatabase::WritingSystem& system : supported) { - sup.insert(system); - } - fonts.push_back(QFontMetrics(font)); - QString style = base.styleString(font); - - QList systems = base.writingSystems(); - for (const QFontDatabase::WritingSystem& system : systems) { - if (sup.count(system) == 0) { - QStringList families = base.families(system); - if (!families.empty() && added.count(families.first()) == 0) { - QString family(families.first()); - QFont nfont = base.font(family, style, font.pointSize()); - if (added.count(nfont.family()) == 0) { - added.insert(family); - fonts.push_back(QFontMetrics(nfont)); - qDebug() << "Added font" << nfont.family() << "for" << system; - } - } - } - } -} - -QSize TextMeter::boundingSize(const QString& text, const QSize& limits) const -{ -// QString str("ridiculus mus. Suspendisse potenti. Cras pretium venenatis enim, faucibus accumsan ex"); -// bool first = true; -// int width = 0; -// QStringList list = str.split(" "); -// QFontMetrics m = fonts.front(); -// for (const QString& word : list) { -// if (first) { -// first = false; -// } else { -// width += m.horizontalAdvance(QChar::Space); -// } -// width += m.horizontalAdvance(word); -// for (const QChar& ch : word) { -// width += m.horizontalAdvance(ch); -// } -// } -// qDebug() << "together:" << m.horizontalAdvance(str); -// qDebug() << "apart:" << width; -// I cant measure or wrap text this way, this simple example shows that even this gives differen result -// The Qt implementation under it is thousands and thousands lines of code in QTextEngine -// I simply can't get though it - - if (text.size() == 0) { - return QSize (0, 0); - } - Helper current(limits.width()); - for (const QChar& ch : text) { - if (newLine(ch)) { - current.computeNewWord(); - if (current.height == 0) { - current.height = fonts.front().lineSpacing(); - } - current.beginNewLine(); - } else if (visible(ch)) { - bool found = false; - for (const QFontMetrics& metrics : fonts) { - if (metrics.inFont(ch)) { - current.computeChar(ch, metrics); - found = true; - break; - } - } - - if (!found) { - current.computeChar(ch, fonts.front()); - } - } - } - current.computeNewWord(); - current.beginNewLine(); - - int& height = current.size.rheight(); - if (height > 0) { - height -= fonts.front().leading(); - } - - return current.size; -} - -void TextMeter::Helper::computeChar(const QChar& ch, const QFontMetrics& metrics) -{ - int ha = metrics.horizontalAdvance(ch); - if (newWord(ch)) { - if (printOnLineBreak(ch)) { - if (!lineOverflow(metrics, ha, ch)){ - computeNewWord(); - } - } else { - computeNewWord(); - delayedSpaceWidth = ha; - lastSpace = ch; - } - } else { - lineOverflow(metrics, ha, ch); - } -} - -void TextMeter::Helper::computeNewLine(const QFontMetrics& metrics, int horizontalAdvance, const QChar& ch) -{ - if (wordBeganWithTheLine) { - text = word.chopped(1); - width = wordWidth - horizontalAdvance; - height = wordHeight; - } - if (width != metrics.horizontalAdvance(text)) { - qDebug() << "Kerning Error" << width - metrics.horizontalAdvance(text); - } - beginNewLine(); - if (wordBeganWithTheLine) { - word = ch; - wordWidth = horizontalAdvance; - wordHeight = metrics.lineSpacing(); - } - - wordBeganWithTheLine = true; - delayedSpaceWidth = 0; - lastSpace = QChar::Null; -} - -void TextMeter::Helper::beginNewLine() -{ - size.rheight() += height; - size.rwidth() = qMax(size.width(), width); - qDebug() << text; - text = ""; - width = 0; - height = 0; -} - -void TextMeter::Helper::computeNewWord() -{ - width += wordWidth + delayedSpaceWidth; - height = qMax(height, wordHeight); - if (lastSpace != QChar::Null) { - text += lastSpace; - } - text += word; - word = ""; - wordWidth = 0; - wordHeight = 0; - delayedSpaceWidth = 0; - lastSpace = QChar::Null; - wordBeganWithTheLine = false; -} - -bool TextMeter::Helper::lineOverflow(const QFontMetrics& metrics, int horizontalAdvance, const QChar& ch) -{ - wordHeight = qMax(wordHeight, metrics.lineSpacing()); - wordWidth += horizontalAdvance; - word += ch; - if (width + delayedSpaceWidth + wordWidth > maxWidth) { - computeNewLine(metrics, horizontalAdvance, ch); - return true; - } - return false; -} - - -bool TextMeter::newLine(const QChar& ch) -{ - return ch == QChar::LineFeed; -} - -bool TextMeter::newWord(const QChar& ch) -{ - return ch.isSpace() || ch.isPunct(); -} - -bool TextMeter::printOnLineBreak(const QChar& ch) -{ - return ch != QChar::Space; -} - -bool TextMeter::visible(const QChar& ch) -{ - return true; -} - -TextMeter::Helper::Helper(int p_maxWidth): - width(0), - height(0), - wordWidth(0), - wordHeight(0), - delayedSpaceWidth(0), - maxWidth(p_maxWidth), - wordBeganWithTheLine(true), - text(""), - word(""), - lastSpace(QChar::Null), - size(0, 0) -{ -} diff --git a/ui/utils/textmeter.h b/ui/utils/textmeter.h deleted file mode 100644 index 243d989..0000000 --- a/ui/utils/textmeter.h +++ /dev/null @@ -1,68 +0,0 @@ -// Squawk messenger. -// Copyright (C) 2019 Yury Gubich -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General 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 TEXTMETER_H -#define TEXTMETER_H - -#include -#include - -#include -#include -#include -#include - -class TextMeter -{ -public: - TextMeter(); - ~TextMeter(); - void initializeFonts(const QFont& font); - QSize boundingSize(const QString& text, const QSize& limits) const; - -private: - QFontDatabase base; - std::list fonts; - - struct Helper { - Helper(int maxWidth); - int width; - int height; - int wordWidth; - int wordHeight; - int delayedSpaceWidth; - int maxWidth; - bool wordBeganWithTheLine; - QString text; - QString word; - QChar lastSpace; - QSize size; - - void computeNewLine(const QFontMetrics& metrics, int horizontalAdvance, const QChar& ch); - void computeChar(const QChar& ch, const QFontMetrics& metrics); - void computeNewWord(); - void beginNewLine(); - bool lineOverflow(const QFontMetrics& metrics, int horizontalAdvance, const QChar& ch); - }; - - static bool newLine(const QChar& ch); - static bool newWord(const QChar& ch); - static bool visible(const QChar& ch); - static bool printOnLineBreak(const QChar& ch); - -}; - -#endif // TEXTMETER_H diff --git a/ui/widgets/messageline/feedview.cpp b/ui/widgets/messageline/feedview.cpp index de7f56f..e0c1477 100644 --- a/ui/widgets/messageline/feedview.cpp +++ b/ui/widgets/messageline/feedview.cpp @@ -343,6 +343,7 @@ void FeedView::paintEvent(QPaintEvent* event) QDateTime lastDate; bool first = true; + QRect viewportRect = vp->rect(); for (const QModelIndex& index : toRener) { QDateTime currentDate = index.data(Models::MessageFeed::Date).toDateTime(); option.rect = visualRect(index); @@ -356,7 +357,10 @@ void FeedView::paintEvent(QPaintEvent* event) } first = false; } - bool mouseOver = option.rect.contains(cursor) && vp->rect().contains(cursor); + QRect stripe = option.rect; + stripe.setLeft(0); + stripe.setWidth(viewportRect.width()); + bool mouseOver = stripe.contains(cursor) && viewportRect.contains(cursor); option.state.setFlag(QStyle::State_MouseOver, mouseOver); itemDelegate(index)->paint(&painter, option, index); diff --git a/ui/widgets/messageline/messagedelegate.cpp b/ui/widgets/messageline/messagedelegate.cpp index 4ddecee..1d094fa 100644 --- a/ui/widgets/messageline/messagedelegate.cpp +++ b/ui/widgets/messageline/messagedelegate.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include "messagedelegate.h" #include "messagefeed.h" @@ -42,7 +43,7 @@ MessageDelegate::MessageDelegate(QObject* parent): nickFont(), dateFont(), bodyMetrics(bodyFont), - bodyMeter(), + bodyRenderer(new QTextDocument()), nickMetrics(nickFont), dateMetrics(dateFont), buttonHeight(0), @@ -52,11 +53,13 @@ MessageDelegate::MessageDelegate(QObject* parent): bars(new std::map()), statusIcons(new std::map()), pencilIcons(new std::map()), - bodies(new std::map()), + bodies(new std::map()), previews(new std::map()), idsToKeep(new std::set()), clearingWidgets(false) { + bodyRenderer->setDocumentMargin(0); + QPushButton btn(QCoreApplication::translate("MessageLine", "Download")); buttonHeight = btn.sizeHint().height(); buttonWidth = btn.sizeHint().width(); @@ -83,7 +86,7 @@ MessageDelegate::~MessageDelegate() delete pair.second; } - for (const std::pair& pair: *bodies){ + for (const std::pair& pair: *bodies){ delete pair.second; } @@ -98,6 +101,7 @@ MessageDelegate::~MessageDelegate() delete bars; delete bodies; delete previews; + delete bodyRenderer; } void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const @@ -124,8 +128,6 @@ void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio opt.displayAlignment = Qt::AlignRight | Qt::AlignTop; } - QSize bodySize = bodyMeter.boundingSize(data.text, opt.rect.size()); - QRect rect; if (ntds) { painter->setFont(nickFont); @@ -168,15 +170,7 @@ void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio painter->restore(); QWidget* vp = static_cast(painter->device()); - if (data.text.size() > 0) { - QLabel* body = getBody(data); - body->setParent(vp); - body->setMinimumSize(bodySize); - body->setMaximumSize(bodySize); - body->move(opt.rect.left(), opt.rect.y()); - body->show(); - opt.rect.adjust(0, bodySize.height() + textMargin, 0, 0); - } + paintBody(data, painter, opt); painter->setFont(dateFont); QColor q = painter->pen().color(); QString dateString = data.date.toLocalTime().toString("hh:mm"); @@ -304,7 +298,12 @@ QSize MessageDelegate::sizeHint(const QStyleOptionViewItem& option, const QModel Models::FeedItem data = qvariant_cast(vi); QSize messageSize(0, 0); if (data.text.size() > 0) { - messageSize = bodyMeter.boundingSize(data.text, messageRect.size()); + bodyRenderer->setPlainText(data.text); + bodyRenderer->setTextWidth(messageRect.size().width()); + + QSizeF size = bodyRenderer->size(); + size.setWidth(bodyRenderer->idealWidth()); + messageSize = QSize(qCeil(size.width()), qCeil(size.height())); messageSize.rheight() += textMargin; } @@ -393,7 +392,7 @@ void MessageDelegate::initializeFonts(const QFont& font) nickMetrics = QFontMetrics(nickFont); dateMetrics = QFontMetrics(dateFont); - bodyMeter.initializeFonts(bodyFont); + bodyRenderer->setDefaultFont(bodyFont); Preview::initializeFont(bodyFont); } @@ -573,34 +572,36 @@ QLabel * MessageDelegate::getPencilIcon(const Models::FeedItem& data) const return result; } -QLabel * MessageDelegate::getBody(const Models::FeedItem& data) const +QTextBrowser * MessageDelegate::getBody(const Models::FeedItem& data) const { - std::map::const_iterator itr = bodies->find(data.id); - QLabel* result = 0; + std::map::const_iterator itr = bodies->find(data.id); + QTextBrowser* result = 0; if (itr != bodies->end()) { result = itr->second; } else { - result = new QLabel(); + result = new QTextBrowser(); result->setFont(bodyFont); result->setContextMenuPolicy(Qt::NoContextMenu); - result->setWordWrap(true); + result->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + result->setContentsMargins(0, 0, 0, 0); + //result->viewport()->setAutoFillBackground(false); + result->document()->setDocumentMargin(0); + result->setFrameStyle(0); + result->setLineWidth(0); + //result->setAutoFillBackground(false); + //->setWordWrap(true); + result->setOpenExternalLinks(true); + //result->setTextInteractionFlags(result->textInteractionFlags() | Qt::TextSelectableByMouse | Qt::LinksAccessibleByMouse); result->setOpenExternalLinks(true); - result->setTextInteractionFlags(result->textInteractionFlags() | Qt::TextSelectableByMouse | Qt::LinksAccessibleByMouse); bodies->insert(std::make_pair(data.id, result)); } - result->setText(Shared::processMessageBody(data.text)); + result->setHtml(Shared::processMessageBody(data.text)); return result; } -void MessageDelegate::beginClearWidgets() -{ - idsToKeep->clear(); - clearingWidgets = true; -} - template void removeElements(std::map* elements, std::set* idsToKeep) { std::set toRemove; @@ -615,6 +616,51 @@ void removeElements(std::map* elements, std::set* idsToKee } } +int MessageDelegate::paintBody(const Models::FeedItem& data, QPainter* painter, QStyleOptionViewItem& option) const +{ + if (data.text.size() > 0) { + bodyRenderer->setHtml(Shared::processMessageBody(data.text)); + bodyRenderer->setTextWidth(option.rect.size().width()); + painter->save(); + painter->translate(option.rect.topLeft()); + bodyRenderer->drawContents(painter); + painter->restore(); + QSize bodySize(qCeil(bodyRenderer->idealWidth()), qCeil(bodyRenderer->size().height())); + + + QTextBrowser* editor = nullptr; + if (option.state.testFlag(QStyle::State_MouseOver)) { + std::set ids({data.id}); + removeElements(bodies, &ids); + editor = getBody(data); + editor->setParent(static_cast(painter->device())); + } else { + std::map::const_iterator itr = bodies->find(data.id); + if (itr != bodies->end()) { + editor = itr->second; + } + } + if (editor != nullptr) { + editor->setMinimumSize(bodySize); + editor->setMaximumSize(bodySize); + editor->move(option.rect.left(), option.rect.y()); + editor->show(); + } + + option.rect.adjust(0, bodySize.height() + textMargin, 0, 0); + return bodySize.width(); + } + return 0; +} + +void MessageDelegate::beginClearWidgets() +{ + idsToKeep->clear(); + clearingWidgets = true; +} + + + void MessageDelegate::endClearWidgets() { if (clearingWidgets) { diff --git a/ui/widgets/messageline/messagedelegate.h b/ui/widgets/messageline/messagedelegate.h index 38ec0ee..b49410f 100644 --- a/ui/widgets/messageline/messagedelegate.h +++ b/ui/widgets/messageline/messagedelegate.h @@ -29,12 +29,13 @@ #include #include #include +#include +#include #include "shared/icons.h" #include "shared/global.h" #include "shared/utils.h" #include "shared/pathcheck.h" -#include "ui/utils/textmeter.h" #include "preview.h" @@ -70,13 +71,15 @@ protected: int paintBar(QProgressBar* bar, QPainter* painter, bool sentByMe, QStyleOptionViewItem& option) const; int paintPreview(const Models::FeedItem& data, QPainter* painter, QStyleOptionViewItem& option) const; int paintComment(const Models::FeedItem& data, QPainter* painter, QStyleOptionViewItem& option) const; + int paintBody(const Models::FeedItem& data, QPainter* painter, QStyleOptionViewItem& option) const; void paintAvatar(const Models::FeedItem& data, const QModelIndex& index, const QStyleOptionViewItem& option, QPainter* painter) const; void paintBubble(const Models::FeedItem& data, QPainter* painter, const QStyleOptionViewItem& option) const; + QPushButton* getButton(const Models::FeedItem& data) const; QProgressBar* getBar(const Models::FeedItem& data) const; QLabel* getStatusIcon(const Models::FeedItem& data) const; QLabel* getPencilIcon(const Models::FeedItem& data) const; - QLabel* getBody(const Models::FeedItem& data) const; + QTextBrowser* getBody(const Models::FeedItem& data) const; void clearHelperWidget(const Models::FeedItem& data) const; bool needToDrawAvatar(const QModelIndex& index, const Models::FeedItem& data, const QStyleOptionViewItem& option) const; @@ -95,7 +98,7 @@ private: QFont nickFont; QFont dateFont; QFontMetrics bodyMetrics; - TextMeter bodyMeter; + QTextDocument* bodyRenderer; QFontMetrics nickMetrics; QFontMetrics dateMetrics; @@ -107,7 +110,7 @@ private: std::map* bars; std::map* statusIcons; std::map* pencilIcons; - std::map* bodies; + std::map* bodies; std::map* previews; std::set* idsToKeep; bool clearingWidgets; -- 2.45.1 From eac87e713f72f51f1f24ac11d4fc52a57c861f8f Mon Sep 17 00:00:00 2001 From: blue Date: Thu, 28 Apr 2022 00:08:59 +0300 Subject: [PATCH 21/29] seem to have found a text block, to activate with the click later --- ui/widgets/messageline/feedview.cpp | 29 ++++++++- ui/widgets/messageline/feedview.h | 3 + ui/widgets/messageline/messagedelegate.cpp | 74 ++++++++++++++++++++-- ui/widgets/messageline/messagedelegate.h | 1 + 4 files changed, 101 insertions(+), 6 deletions(-) diff --git a/ui/widgets/messageline/feedview.cpp b/ui/widgets/messageline/feedview.cpp index e0c1477..bf1da61 100644 --- a/ui/widgets/messageline/feedview.cpp +++ b/ui/widgets/messageline/feedview.cpp @@ -50,7 +50,8 @@ FeedView::FeedView(QWidget* parent): modelState(Models::MessageFeed::complete), progress(), dividerFont(), - dividerMetrics(dividerFont) + dividerMetrics(dividerFont), + mousePressed(false) { horizontalScrollBar()->setRange(0, 0); verticalScrollBar()->setSingleStep(approximateSingleMessageHeight); @@ -412,10 +413,36 @@ void FeedView::mouseMoveEvent(QMouseEvent* event) if (!isVisible()) { return; } + + mousePressed = false; + //qDebug() << event; QAbstractItemView::mouseMoveEvent(event); } +void FeedView::mousePressEvent(QMouseEvent* event) +{ + QAbstractItemView::mousePressEvent(event); + mousePressed = event->button() == Qt::LeftButton; +} + +void FeedView::mouseReleaseEvent(QMouseEvent* event) +{ + QAbstractItemView::mouseReleaseEvent(event); + + if (mousePressed && specialDelegate) { + QPoint point = event->localPos().toPoint(); + QModelIndex index = indexAt(point); + if (index.isValid()) { + QRect rect = visualRect(index); + MessageDelegate* del = static_cast(itemDelegate()); + if (rect.contains(point)) { + del->leftClick(point, index, rect); + } + } + } +} + void FeedView::resizeEvent(QResizeEvent* event) { QAbstractItemView::resizeEvent(event); diff --git a/ui/widgets/messageline/feedview.h b/ui/widgets/messageline/feedview.h index 8bcd913..c757986 100644 --- a/ui/widgets/messageline/feedview.h +++ b/ui/widgets/messageline/feedview.h @@ -68,6 +68,8 @@ protected: void paintEvent(QPaintEvent * event) override; void updateGeometries() override; void mouseMoveEvent(QMouseEvent * event) override; + void mousePressEvent(QMouseEvent * event) override; + void mouseReleaseEvent(QMouseEvent * event) override; void resizeEvent(QResizeEvent * event) override; private: @@ -93,6 +95,7 @@ private: Progress progress; QFont dividerFont; QFontMetrics dividerMetrics; + bool mousePressed; static const std::set geometryChangingRoles; diff --git a/ui/widgets/messageline/messagedelegate.cpp b/ui/widgets/messageline/messagedelegate.cpp index 1d094fa..c787cfa 100644 --- a/ui/widgets/messageline/messagedelegate.cpp +++ b/ui/widgets/messageline/messagedelegate.cpp @@ -22,7 +22,9 @@ #include #include #include -#include +#include +#include +#include #include "messagedelegate.h" #include "messagefeed.h" @@ -303,7 +305,7 @@ QSize MessageDelegate::sizeHint(const QStyleOptionViewItem& option, const QModel QSizeF size = bodyRenderer->size(); size.setWidth(bodyRenderer->idealWidth()); - messageSize = QSize(qCeil(size.width()), qCeil(size.height())); + messageSize = QSize(std::ceil(size.width()), std::ceil(size.height())); messageSize.rheight() += textMargin; } @@ -364,6 +366,68 @@ QSize MessageDelegate::sizeHint(const QStyleOptionViewItem& option, const QModel return messageSize; } +void MessageDelegate::leftClick(const QPoint& point, const QModelIndex& index, const QRect& sizeHint) const +{ + QVariant vi = index.data(Models::MessageFeed::Bulk); + Models::FeedItem data = qvariant_cast(vi); + if (data.text.size() > 0) { + QRect localHint = sizeHint.adjusted(bubbleMargin, bubbleMargin + margin, -bubbleMargin, -bubbleMargin / 2); + if (needToDrawSender(index, data)) { + localHint.adjust(0, nickMetrics.lineSpacing() + textMargin, 0, 0); + } + + int attachHeight = 0; + switch (data.attach.state) { + case Models::none: + break; + case Models::uploading: + attachHeight += Preview::calculateAttachSize(Shared::resolvePath(data.attach.localPath), localHint).height() + textMargin; + [[fallthrough]]; + case Models::downloading: + attachHeight += barHeight + textMargin; + break; + case Models::remote: + attachHeight += buttonHeight + textMargin; + break; + case Models::ready: + case Models::local: { + QSize aSize = Preview::calculateAttachSize(Shared::resolvePath(data.attach.localPath), localHint); + attachHeight += aSize.height() + textMargin; + } + break; + case Models::errorDownload: { + QSize commentSize = dateMetrics.boundingRect(localHint, Qt::TextWordWrap, data.attach.error).size(); + attachHeight += commentSize.height() + buttonHeight + textMargin * 2; + } + break; + case Models::errorUpload: { + QSize aSize = Preview::calculateAttachSize(Shared::resolvePath(data.attach.localPath), localHint); + QSize commentSize = dateMetrics.boundingRect(localHint, Qt::TextWordWrap, data.attach.error).size(); + attachHeight += aSize.height() + commentSize.height() + textMargin * 2; + } + break; + } + + int bottomSize = std::max(dateMetrics.lineSpacing(), statusIconSize); + localHint.adjust(0, attachHeight, 0, -(bottomSize + textMargin)); + + if (localHint.contains(point)) { + qDebug() << "MESSAGE CLICKED"; + QPoint translated = point - localHint.topLeft(); + + bodyRenderer->setPlainText(data.text); + bodyRenderer->setTextWidth(localHint.size().width()); + + int pos = bodyRenderer->documentLayout()->hitTest(translated, Qt::FuzzyHit); + QTextBlock block = bodyRenderer->findBlock(pos); + QString text = block.text(); + if (text.size() > 0) { + qDebug() << text; + } + } + } +} + void MessageDelegate::initializeFonts(const QFont& font) { bodyFont = font; @@ -625,9 +689,9 @@ int MessageDelegate::paintBody(const Models::FeedItem& data, QPainter* painter, painter->translate(option.rect.topLeft()); bodyRenderer->drawContents(painter); painter->restore(); - QSize bodySize(qCeil(bodyRenderer->idealWidth()), qCeil(bodyRenderer->size().height())); - + QSize bodySize(std::ceil(bodyRenderer->idealWidth()), std::ceil(bodyRenderer->size().height())); +/* QTextBrowser* editor = nullptr; if (option.state.testFlag(QStyle::State_MouseOver)) { std::set ids({data.id}); @@ -645,7 +709,7 @@ int MessageDelegate::paintBody(const Models::FeedItem& data, QPainter* painter, editor->setMaximumSize(bodySize); editor->move(option.rect.left(), option.rect.y()); editor->show(); - } + }*/ option.rect.adjust(0, bodySize.height() + textMargin, 0, 0); return bodySize.width(); diff --git a/ui/widgets/messageline/messagedelegate.h b/ui/widgets/messageline/messagedelegate.h index b49410f..9333d45 100644 --- a/ui/widgets/messageline/messagedelegate.h +++ b/ui/widgets/messageline/messagedelegate.h @@ -58,6 +58,7 @@ public: bool editorEvent(QEvent * event, QAbstractItemModel * model, const QStyleOptionViewItem & option, const QModelIndex & index) override; void endClearWidgets(); void beginClearWidgets(); + void leftClick(const QPoint& point, const QModelIndex& index, const QRect& sizeHint) const; static int avatarHeight; static int margin; -- 2.45.1 From 7ba94e9deb786393e78880ee279554410662a168 Mon Sep 17 00:00:00 2001 From: blue Date: Fri, 29 Apr 2022 00:29:44 +0300 Subject: [PATCH 22/29] link clicking and hovering in message body now works! --- ui/widgets/messageline/feedview.cpp | 32 +++- ui/widgets/messageline/feedview.h | 4 + ui/widgets/messageline/messagedelegate.cpp | 179 ++++++++------------- ui/widgets/messageline/messagedelegate.h | 10 +- 4 files changed, 105 insertions(+), 120 deletions(-) diff --git a/ui/widgets/messageline/feedview.cpp b/ui/widgets/messageline/feedview.cpp index bf1da61..0758dd9 100644 --- a/ui/widgets/messageline/feedview.cpp +++ b/ui/widgets/messageline/feedview.cpp @@ -51,7 +51,8 @@ FeedView::FeedView(QWidget* parent): progress(), dividerFont(), dividerMetrics(dividerFont), - mousePressed(false) + mousePressed(false), + anchorHovered(false) { horizontalScrollBar()->setRange(0, 0); verticalScrollBar()->setSingleStep(approximateSingleMessageHeight); @@ -408,6 +409,18 @@ void FeedView::verticalScrollbarValueChanged(int value) QAbstractItemView::verticalScrollbarValueChanged(vo); } +void FeedView::setAnchorHovered(bool hovered) +{ + if (anchorHovered != hovered) { + anchorHovered = hovered; + if (anchorHovered) { + setCursor(Qt::PointingHandCursor); + } else { + setCursor(Qt::ArrowCursor); + } + } +} + void FeedView::mouseMoveEvent(QMouseEvent* event) { if (!isVisible()) { @@ -418,6 +431,22 @@ void FeedView::mouseMoveEvent(QMouseEvent* event) //qDebug() << event; QAbstractItemView::mouseMoveEvent(event); + + if (specialDelegate) { + QPoint point = event->localPos().toPoint(); + QModelIndex index = indexAt(point); + if (index.isValid()) { + QRect rect = visualRect(index); + MessageDelegate* del = static_cast(itemDelegate()); + if (rect.contains(point)) { + setAnchorHovered(del->isAnchorHovered(point, index, rect)); + } else { + setAnchorHovered(false); + } + } else { + setAnchorHovered(false); + } + } } void FeedView::mousePressEvent(QMouseEvent* event) @@ -487,6 +516,7 @@ void FeedView::setItemDelegate(QAbstractItemDelegate* delegate) elementMargin = MessageDelegate::margin; connect(del, &MessageDelegate::buttonPushed, this, &FeedView::onMessageButtonPushed); connect(del, &MessageDelegate::invalidPath, this, &FeedView::onMessageInvalidPath); + connect(del, &MessageDelegate::openLink, &QDesktopServices::openUrl); } else { specialDelegate = false; elementMargin = 0; diff --git a/ui/widgets/messageline/feedview.h b/ui/widgets/messageline/feedview.h index c757986..7a00dd7 100644 --- a/ui/widgets/messageline/feedview.h +++ b/ui/widgets/messageline/feedview.h @@ -20,6 +20,8 @@ #define FEEDVIEW_H #include +#include +#include #include #include @@ -76,6 +78,7 @@ private: bool tryToCalculateGeometriesWithNoScrollbars(const QStyleOptionViewItem& option, const QAbstractItemModel* model, uint32_t totalHeight); void positionProgress(); void drawDateDevider(int top, const QDateTime& date, QPainter& painter); + void setAnchorHovered(bool hovered); private: struct Hint { @@ -96,6 +99,7 @@ private: QFont dividerFont; QFontMetrics dividerMetrics; bool mousePressed; + bool anchorHovered; static const std::set geometryChangingRoles; diff --git a/ui/widgets/messageline/messagedelegate.cpp b/ui/widgets/messageline/messagedelegate.cpp index c787cfa..22a575b 100644 --- a/ui/widgets/messageline/messagedelegate.cpp +++ b/ui/widgets/messageline/messagedelegate.cpp @@ -44,7 +44,6 @@ MessageDelegate::MessageDelegate(QObject* parent): bodyFont(), nickFont(), dateFont(), - bodyMetrics(bodyFont), bodyRenderer(new QTextDocument()), nickMetrics(nickFont), dateMetrics(dateFont), @@ -55,7 +54,6 @@ MessageDelegate::MessageDelegate(QObject* parent): bars(new std::map()), statusIcons(new std::map()), pencilIcons(new std::map()), - bodies(new std::map()), previews(new std::map()), idsToKeep(new std::set()), clearingWidgets(false) @@ -88,10 +86,6 @@ MessageDelegate::~MessageDelegate() delete pair.second; } - for (const std::pair& pair: *bodies){ - delete pair.second; - } - for (const std::pair& pair: *previews){ delete pair.second; } @@ -101,7 +95,6 @@ MessageDelegate::~MessageDelegate() delete idsToKeep; delete buttons; delete bars; - delete bodies; delete previews; delete bodyRenderer; } @@ -366,66 +359,83 @@ QSize MessageDelegate::sizeHint(const QStyleOptionViewItem& option, const QModel return messageSize; } -void MessageDelegate::leftClick(const QPoint& point, const QModelIndex& index, const QRect& sizeHint) const +QRect MessageDelegate::getHoveredMessageBodyRect(const QModelIndex& index, const Models::FeedItem& data, const QRect& sizeHint) const +{ + QRect localHint = sizeHint.adjusted(bubbleMargin, bubbleMargin + margin, -bubbleMargin, -bubbleMargin / 2); + if (needToDrawSender(index, data)) { + localHint.adjust(0, nickMetrics.lineSpacing() + textMargin, 0, 0); + } + + int attachHeight = 0; + switch (data.attach.state) { + case Models::none: + break; + case Models::uploading: + attachHeight += Preview::calculateAttachSize(Shared::resolvePath(data.attach.localPath), localHint).height() + textMargin; + [[fallthrough]]; + case Models::downloading: + attachHeight += barHeight + textMargin; + break; + case Models::remote: + attachHeight += buttonHeight + textMargin; + break; + case Models::ready: + case Models::local: { + QSize aSize = Preview::calculateAttachSize(Shared::resolvePath(data.attach.localPath), localHint); + attachHeight += aSize.height() + textMargin; + } + break; + case Models::errorDownload: { + QSize commentSize = dateMetrics.boundingRect(localHint, Qt::TextWordWrap, data.attach.error).size(); + attachHeight += commentSize.height() + buttonHeight + textMargin * 2; + } + break; + case Models::errorUpload: { + QSize aSize = Preview::calculateAttachSize(Shared::resolvePath(data.attach.localPath), localHint); + QSize commentSize = dateMetrics.boundingRect(localHint, Qt::TextWordWrap, data.attach.error).size(); + attachHeight += aSize.height() + commentSize.height() + textMargin * 2; + } + break; + } + + int bottomSize = std::max(dateMetrics.lineSpacing(), statusIconSize); + localHint.adjust(0, attachHeight, 0, -(bottomSize + textMargin)); + + return localHint; +} + +QString MessageDelegate::getAnchor(const QPoint& point, const QModelIndex& index, const QRect& sizeHint) const { QVariant vi = index.data(Models::MessageFeed::Bulk); Models::FeedItem data = qvariant_cast(vi); if (data.text.size() > 0) { - QRect localHint = sizeHint.adjusted(bubbleMargin, bubbleMargin + margin, -bubbleMargin, -bubbleMargin / 2); - if (needToDrawSender(index, data)) { - localHint.adjust(0, nickMetrics.lineSpacing() + textMargin, 0, 0); - } - - int attachHeight = 0; - switch (data.attach.state) { - case Models::none: - break; - case Models::uploading: - attachHeight += Preview::calculateAttachSize(Shared::resolvePath(data.attach.localPath), localHint).height() + textMargin; - [[fallthrough]]; - case Models::downloading: - attachHeight += barHeight + textMargin; - break; - case Models::remote: - attachHeight += buttonHeight + textMargin; - break; - case Models::ready: - case Models::local: { - QSize aSize = Preview::calculateAttachSize(Shared::resolvePath(data.attach.localPath), localHint); - attachHeight += aSize.height() + textMargin; - } - break; - case Models::errorDownload: { - QSize commentSize = dateMetrics.boundingRect(localHint, Qt::TextWordWrap, data.attach.error).size(); - attachHeight += commentSize.height() + buttonHeight + textMargin * 2; - } - break; - case Models::errorUpload: { - QSize aSize = Preview::calculateAttachSize(Shared::resolvePath(data.attach.localPath), localHint); - QSize commentSize = dateMetrics.boundingRect(localHint, Qt::TextWordWrap, data.attach.error).size(); - attachHeight += aSize.height() + commentSize.height() + textMargin * 2; - } - break; - } - - int bottomSize = std::max(dateMetrics.lineSpacing(), statusIconSize); - localHint.adjust(0, attachHeight, 0, -(bottomSize + textMargin)); + QRect localHint = getHoveredMessageBodyRect(index, data, sizeHint); if (localHint.contains(point)) { - qDebug() << "MESSAGE CLICKED"; QPoint translated = point - localHint.topLeft(); - bodyRenderer->setPlainText(data.text); + bodyRenderer->setHtml(Shared::processMessageBody(data.text)); bodyRenderer->setTextWidth(localHint.size().width()); - int pos = bodyRenderer->documentLayout()->hitTest(translated, Qt::FuzzyHit); - QTextBlock block = bodyRenderer->findBlock(pos); - QString text = block.text(); - if (text.size() > 0) { - qDebug() << text; - } + return bodyRenderer->documentLayout()->anchorAt(translated); } } + + return QString(); +} + +void MessageDelegate::leftClick(const QPoint& point, const QModelIndex& index, const QRect& sizeHint) const +{ + QString anchor = getAnchor(point, index, sizeHint); + if (anchor.size() > 0) { + emit openLink(anchor); + } +} + +bool MessageDelegate::isAnchorHovered(const QPoint& point, const QModelIndex& index, const QRect& sizeHint) const +{ + QString anchor = getAnchor(point, index, sizeHint); + return anchor.size() > 0; } void MessageDelegate::initializeFonts(const QFont& font) @@ -452,7 +462,6 @@ void MessageDelegate::initializeFonts(const QFont& font) } bodyFont.setKerning(false); - bodyMetrics = QFontMetrics(bodyFont); nickMetrics = QFontMetrics(nickFont); dateMetrics = QFontMetrics(dateFont); @@ -636,36 +645,6 @@ QLabel * MessageDelegate::getPencilIcon(const Models::FeedItem& data) const return result; } -QTextBrowser * MessageDelegate::getBody(const Models::FeedItem& data) const -{ - std::map::const_iterator itr = bodies->find(data.id); - QTextBrowser* result = 0; - - if (itr != bodies->end()) { - result = itr->second; - } else { - result = new QTextBrowser(); - result->setFont(bodyFont); - result->setContextMenuPolicy(Qt::NoContextMenu); - result->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - result->setContentsMargins(0, 0, 0, 0); - //result->viewport()->setAutoFillBackground(false); - result->document()->setDocumentMargin(0); - result->setFrameStyle(0); - result->setLineWidth(0); - //result->setAutoFillBackground(false); - //->setWordWrap(true); - result->setOpenExternalLinks(true); - //result->setTextInteractionFlags(result->textInteractionFlags() | Qt::TextSelectableByMouse | Qt::LinksAccessibleByMouse); - result->setOpenExternalLinks(true); - bodies->insert(std::make_pair(data.id, result)); - } - - result->setHtml(Shared::processMessageBody(data.text)); - - return result; -} - template void removeElements(std::map* elements, std::set* idsToKeep) { std::set toRemove; @@ -691,26 +670,6 @@ int MessageDelegate::paintBody(const Models::FeedItem& data, QPainter* painter, painter->restore(); QSize bodySize(std::ceil(bodyRenderer->idealWidth()), std::ceil(bodyRenderer->size().height())); -/* - QTextBrowser* editor = nullptr; - if (option.state.testFlag(QStyle::State_MouseOver)) { - std::set ids({data.id}); - removeElements(bodies, &ids); - editor = getBody(data); - editor->setParent(static_cast(painter->device())); - } else { - std::map::const_iterator itr = bodies->find(data.id); - if (itr != bodies->end()) { - editor = itr->second; - } - } - if (editor != nullptr) { - editor->setMinimumSize(bodySize); - editor->setMaximumSize(bodySize); - editor->move(option.rect.left(), option.rect.y()); - editor->show(); - }*/ - option.rect.adjust(0, bodySize.height() + textMargin, 0, 0); return bodySize.width(); } @@ -723,8 +682,6 @@ void MessageDelegate::beginClearWidgets() clearingWidgets = true; } - - void MessageDelegate::endClearWidgets() { if (clearingWidgets) { @@ -732,7 +689,6 @@ void MessageDelegate::endClearWidgets() removeElements(bars, idsToKeep); removeElements(statusIcons, idsToKeep); removeElements(pencilIcons, idsToKeep); - removeElements(bodies, idsToKeep); removeElements(previews, idsToKeep); idsToKeep->clear(); @@ -760,8 +716,3 @@ void MessageDelegate::clearHelperWidget(const Models::FeedItem& data) const } } } - -// void MessageDelegate::setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const -// { -// -// } diff --git a/ui/widgets/messageline/messagedelegate.h b/ui/widgets/messageline/messagedelegate.h index 9333d45..d871a52 100644 --- a/ui/widgets/messageline/messagedelegate.h +++ b/ui/widgets/messageline/messagedelegate.h @@ -29,8 +29,6 @@ #include #include #include -#include -#include #include "shared/icons.h" #include "shared/global.h" @@ -59,6 +57,7 @@ public: void endClearWidgets(); void beginClearWidgets(); void leftClick(const QPoint& point, const QModelIndex& index, const QRect& sizeHint) const; + bool isAnchorHovered(const QPoint& point, const QModelIndex& index, const QRect& sizeHint) const; static int avatarHeight; static int margin; @@ -66,6 +65,7 @@ public: signals: void buttonPushed(const QString& messageId) const; void invalidPath(const QString& messageId) const; + void openLink(const QString& href) const; protected: int paintButton(QPushButton* btn, QPainter* painter, bool sentByMe, QStyleOptionViewItem& option) const; @@ -80,11 +80,13 @@ protected: QProgressBar* getBar(const Models::FeedItem& data) const; QLabel* getStatusIcon(const Models::FeedItem& data) const; QLabel* getPencilIcon(const Models::FeedItem& data) const; - QTextBrowser* getBody(const Models::FeedItem& data) const; void clearHelperWidget(const Models::FeedItem& data) const; bool needToDrawAvatar(const QModelIndex& index, const Models::FeedItem& data, const QStyleOptionViewItem& option) const; bool needToDrawSender(const QModelIndex& index, const Models::FeedItem& data) const; + + QRect getHoveredMessageBodyRect(const QModelIndex& index, const Models::FeedItem& data, const QRect& sizeHint) const; + QString getAnchor(const QPoint& point, const QModelIndex& index, const QRect& sizeHint) const; protected slots: void onButtonPushed() const; @@ -98,7 +100,6 @@ private: QFont bodyFont; QFont nickFont; QFont dateFont; - QFontMetrics bodyMetrics; QTextDocument* bodyRenderer; QFontMetrics nickMetrics; QFontMetrics dateMetrics; @@ -111,7 +112,6 @@ private: std::map* bars; std::map* statusIcons; std::map* pencilIcons; - std::map* bodies; std::map* previews; std::set* idsToKeep; bool clearingWidgets; -- 2.45.1 From 0340db7f2fcff211f99d6ccc92bd98669a406581 Mon Sep 17 00:00:00 2001 From: blue Date: Sun, 1 May 2022 23:19:52 +0300 Subject: [PATCH 23/29] first successfull attempt to visualize selection on message body --- ui/widgets/messageline/feedview.cpp | 56 +++++++++++------ ui/widgets/messageline/feedview.h | 3 + ui/widgets/messageline/messagedelegate.cpp | 70 ++++++++++++++-------- ui/widgets/messageline/messagedelegate.h | 5 +- 4 files changed, 92 insertions(+), 42 deletions(-) diff --git a/ui/widgets/messageline/feedview.cpp b/ui/widgets/messageline/feedview.cpp index 0758dd9..f467f43 100644 --- a/ui/widgets/messageline/feedview.cpp +++ b/ui/widgets/messageline/feedview.cpp @@ -52,7 +52,10 @@ FeedView::FeedView(QWidget* parent): dividerFont(), dividerMetrics(dividerFont), mousePressed(false), - anchorHovered(false) + dragging(false), + anchorHovered(false), + dragStartPoint(), + dragEndPoint() { horizontalScrollBar()->setRange(0, 0); verticalScrollBar()->setSingleStep(approximateSingleMessageHeight); @@ -304,7 +307,7 @@ bool FeedView::tryToCalculateGeometriesWithNoScrollbars(const QStyleOptionViewIt void FeedView::paintEvent(QPaintEvent* event) { - //qDebug() << "paint" << event->rect(); + qDebug() << "paint" << event->rect(); const QAbstractItemModel* m = model(); QWidget* vp = viewport(); QRect zone = event->rect().translated(0, -vo); @@ -427,19 +430,31 @@ void FeedView::mouseMoveEvent(QMouseEvent* event) return; } - mousePressed = false; - //qDebug() << event; + dragEndPoint = event->localPos().toPoint(); + if (mousePressed) { + QPoint distance = dragStartPoint - dragEndPoint; + if (distance.manhattanLength() > 5) { + dragging = true; + } + } QAbstractItemView::mouseMoveEvent(event); if (specialDelegate) { - QPoint point = event->localPos().toPoint(); - QModelIndex index = indexAt(point); + QModelIndex index = indexAt(dragEndPoint); if (index.isValid()) { QRect rect = visualRect(index); - MessageDelegate* del = static_cast(itemDelegate()); - if (rect.contains(point)) { - setAnchorHovered(del->isAnchorHovered(point, index, rect)); + if (rect.contains(dragEndPoint)) { + MessageDelegate* del = static_cast(itemDelegate()); + if (dragging) { + setAnchorHovered(false); + if (del->mouseDrag(dragStartPoint, dragEndPoint, index, rect)) { + qDebug() << "asking to repaint" << rect; + setDirtyRegion(rect); + } + } else { + setAnchorHovered(del->isAnchorHovered(dragEndPoint, index, rect)); + } } else { setAnchorHovered(false); } @@ -453,22 +468,29 @@ void FeedView::mousePressEvent(QMouseEvent* event) { QAbstractItemView::mousePressEvent(event); mousePressed = event->button() == Qt::LeftButton; + if (mousePressed) { + dragStartPoint = event->localPos().toPoint(); + } } void FeedView::mouseReleaseEvent(QMouseEvent* event) { QAbstractItemView::mouseReleaseEvent(event); - if (mousePressed && specialDelegate) { - QPoint point = event->localPos().toPoint(); - QModelIndex index = indexAt(point); - if (index.isValid()) { - QRect rect = visualRect(index); - MessageDelegate* del = static_cast(itemDelegate()); - if (rect.contains(point)) { - del->leftClick(point, index, rect); + if (mousePressed) { + if (!dragging && specialDelegate) { + QPoint point = event->localPos().toPoint(); + QModelIndex index = indexAt(point); + if (index.isValid()) { + QRect rect = visualRect(index); + MessageDelegate* del = static_cast(itemDelegate()); + if (rect.contains(point)) { + del->leftClick(point, index, rect); + } } } + dragging = false; + mousePressed = false; } } diff --git a/ui/widgets/messageline/feedview.h b/ui/widgets/messageline/feedview.h index 7a00dd7..c0d6254 100644 --- a/ui/widgets/messageline/feedview.h +++ b/ui/widgets/messageline/feedview.h @@ -99,7 +99,10 @@ private: QFont dividerFont; QFontMetrics dividerMetrics; bool mousePressed; + bool dragging; bool anchorHovered; + QPoint dragStartPoint; + QPoint dragEndPoint; static const std::set geometryChangingRoles; diff --git a/ui/widgets/messageline/messagedelegate.cpp b/ui/widgets/messageline/messagedelegate.cpp index ca2e0a6..197248a 100644 --- a/ui/widgets/messageline/messagedelegate.cpp +++ b/ui/widgets/messageline/messagedelegate.cpp @@ -56,7 +56,9 @@ MessageDelegate::MessageDelegate(QObject* parent): pencilIcons(new std::map()), previews(new std::map()), idsToKeep(new std::set()), - clearingWidgets(false) + clearingWidgets(false), + currentId(""), + selection(0, 0) { bodyRenderer->setDocumentMargin(0); @@ -438,6 +440,37 @@ bool MessageDelegate::isAnchorHovered(const QPoint& point, const QModelIndex& in return anchor.size() > 0; } +bool MessageDelegate::mouseDrag(const QPoint& start, const QPoint& end, const QModelIndex& index, const QRect& sizeHint) +{ + QVariant vi = index.data(Models::MessageFeed::Bulk); + Models::FeedItem data = qvariant_cast(vi); + if (data.text.size() > 0) { + QRect localHint = getHoveredMessageBodyRect(index, data, sizeHint); + + if (localHint.contains(start)) { + QPoint translated = start - localHint.topLeft(); + + bodyRenderer->setHtml(Shared::processMessageBody(data.text)); + bodyRenderer->setTextWidth(localHint.size().width()); + selection.first = bodyRenderer->documentLayout()->hitTest(translated, Qt::HitTestAccuracy::FuzzyHit); + selection.second = bodyRenderer->documentLayout()->hitTest(end - localHint.topLeft(), Qt::HitTestAccuracy::FuzzyHit); + + currentId = data.id; + + return true; + } + } + return false; +} + +QString MessageDelegate::clearSelection() +{ + QString lastSelectedId = currentId; + currentId = ""; + selection = std::pair(0, 0); + return lastSelectedId; +} + void MessageDelegate::initializeFonts(const QFont& font) { bodyFont = font; @@ -664,32 +697,21 @@ int MessageDelegate::paintBody(const Models::FeedItem& data, QPainter* painter, if (data.text.size() > 0) { bodyRenderer->setHtml(Shared::processMessageBody(data.text)); bodyRenderer->setTextWidth(option.rect.size().width()); - painter->setBackgroundMode(Qt::BGMode::OpaqueMode); painter->save(); -// QTextCursor cursor(bodyRenderer); -// cursor.setPosition(2, QTextCursor::KeepAnchor); painter->translate(option.rect.topLeft()); -// QTextFrameFormat format = bodyRenderer->rootFrame()->frameFormat(); -// format.setBackground(option.palette.brush(QPalette::Active, QPalette::Highlight)); -// bodyRenderer->rootFrame()->setFrameFormat(format); + + if (data.id == currentId) { + QTextCursor cursor(bodyRenderer); + cursor.setPosition(selection.first, QTextCursor::MoveAnchor); + cursor.setPosition(selection.second, QTextCursor::KeepAnchor); + QTextCharFormat format = cursor.charFormat(); + format.setBackground(option.palette.color(QPalette::Active, QPalette::Highlight)); + format.setForeground(option.palette.color(QPalette::Active, QPalette::HighlightedText)); + cursor.setCharFormat(format); + } + bodyRenderer->drawContents(painter); -// QColor c = option.palette.color(QPalette::Active, QPalette::Highlight); -// QTextBlock b = bodyRenderer->begin(); -// QTextBlockFormat format = b.blockFormat(); -// format.setBackground(option.palette.brush(QPalette::Active, QPalette::Highlight)); -// format.setProperty(QTextFormat::BackgroundBrush, option.palette.brush(QPalette::Active, QPalette::Highlight)); -// QTextCursor cursor(bodyRenderer); -// cursor.setBlockFormat(format); -// b = bodyRenderer->begin(); -// while (b.isValid() > 0) { -// QTextLayout* lay = b.layout(); -// QTextLayout::FormatRange range; -// range.format = b.charFormat(); -// range.start = 0; -// range.length = 2; -// lay->draw(painter, option.rect.topLeft(), {range}); -// b = b.next(); -// } + painter->restore(); QSize bodySize(std::ceil(bodyRenderer->idealWidth()), std::ceil(bodyRenderer->size().height())); diff --git a/ui/widgets/messageline/messagedelegate.h b/ui/widgets/messageline/messagedelegate.h index df883f7..2aea240 100644 --- a/ui/widgets/messageline/messagedelegate.h +++ b/ui/widgets/messageline/messagedelegate.h @@ -59,6 +59,8 @@ public: void beginClearWidgets(); void leftClick(const QPoint& point, const QModelIndex& index, const QRect& sizeHint) const; bool isAnchorHovered(const QPoint& point, const QModelIndex& index, const QRect& sizeHint) const; + bool mouseDrag(const QPoint& start, const QPoint& end, const QModelIndex& index, const QRect& sizeHint); + QString clearSelection(); static int avatarHeight; static int margin; @@ -116,7 +118,8 @@ private: std::map* previews; std::set* idsToKeep; bool clearingWidgets; - + QString currentId; + std::pair selection; }; #endif // MESSAGEDELEGATE_H -- 2.45.1 From 3c48577eeed23e5f21af70c0ae90695e91893ac7 Mon Sep 17 00:00:00 2001 From: blue Date: Mon, 2 May 2022 22:25:50 +0300 Subject: [PATCH 24/29] selection message body now actually working --- shared/utils.h | 6 ++ ui/widgets/conversation.cpp | 10 +++ ui/widgets/messageline/feedview.cpp | 92 ++++++++++++++++------ ui/widgets/messageline/feedview.h | 8 +- ui/widgets/messageline/messagedelegate.cpp | 54 ++++++++++--- ui/widgets/messageline/messagedelegate.h | 4 +- ui/widgets/messageline/messagefeed.h | 4 +- 7 files changed, 138 insertions(+), 40 deletions(-) diff --git a/shared/utils.h b/shared/utils.h index 564e2e6..0329cee 100644 --- a/shared/utils.h +++ b/shared/utils.h @@ -69,6 +69,12 @@ static const std::vector colorPalette = { QColor(17, 17, 80), QColor(54, 54, 94) }; + +enum class Hover { + nothing, + text, + anchor +}; } #endif // SHARED_UTILS_H diff --git a/ui/widgets/conversation.cpp b/ui/widgets/conversation.cpp index 70a468c..b2c7a5f 100644 --- a/ui/widgets/conversation.cpp +++ b/ui/widgets/conversation.cpp @@ -499,6 +499,16 @@ void Conversation::onFeedContext(const QPoint& pos) }); } + QString selected = feed->getSelectedText(); + if (selected.size() > 0) { + showMenu = true; + QAction* copy = contextMenu->addAction(Shared::icon("edit-copy"), tr("Copy selected")); + connect(copy, &QAction::triggered, [selected] () { + QClipboard* cb = QApplication::clipboard(); + cb->setText(selected); + }); + } + QString body = item->getBody(); if (body.size() > 0) { showMenu = true; diff --git a/ui/widgets/messageline/feedview.cpp b/ui/widgets/messageline/feedview.cpp index f467f43..353d851 100644 --- a/ui/widgets/messageline/feedview.cpp +++ b/ui/widgets/messageline/feedview.cpp @@ -21,6 +21,8 @@ #include #include #include +#include +#include #include #include "messagedelegate.h" @@ -53,9 +55,10 @@ FeedView::FeedView(QWidget* parent): dividerMetrics(dividerFont), mousePressed(false), dragging(false), - anchorHovered(false), + hovered(Shared::Hover::nothing), dragStartPoint(), - dragEndPoint() + dragEndPoint(), + selectedText() { horizontalScrollBar()->setRange(0, 0); verticalScrollBar()->setSingleStep(approximateSingleMessageHeight); @@ -167,7 +170,7 @@ void FeedView::dataChanged(const QModelIndex& topLeft, const QModelIndex& bottom void FeedView::updateGeometries() { - qDebug() << "updateGeometries"; + //qDebug() << "updateGeometries"; QScrollBar* bar = verticalScrollBar(); const QStyle* st = style(); @@ -307,7 +310,7 @@ bool FeedView::tryToCalculateGeometriesWithNoScrollbars(const QStyleOptionViewIt void FeedView::paintEvent(QPaintEvent* event) { - qDebug() << "paint" << event->rect(); + //qDebug() << "paint" << event->rect(); const QAbstractItemModel* m = model(); QWidget* vp = viewport(); QRect zone = event->rect().translated(0, -vo); @@ -412,14 +415,20 @@ void FeedView::verticalScrollbarValueChanged(int value) QAbstractItemView::verticalScrollbarValueChanged(vo); } -void FeedView::setAnchorHovered(bool hovered) +void FeedView::setAnchorHovered(Shared::Hover type) { - if (anchorHovered != hovered) { - anchorHovered = hovered; - if (anchorHovered) { - setCursor(Qt::PointingHandCursor); - } else { - setCursor(Qt::ArrowCursor); + if (hovered != type) { + hovered = type; + switch (hovered) { + case Shared::Hover::nothing: + setCursor(Qt::ArrowCursor); + break; + case Shared::Hover::text: + setCursor(Qt::IBeamCursor); + break; + case Shared::Hover::anchor: + setCursor(Qt::PointingHandCursor); + break; } } } @@ -441,25 +450,31 @@ void FeedView::mouseMoveEvent(QMouseEvent* event) QAbstractItemView::mouseMoveEvent(event); if (specialDelegate) { - QModelIndex index = indexAt(dragEndPoint); - if (index.isValid()) { - QRect rect = visualRect(index); - if (rect.contains(dragEndPoint)) { - MessageDelegate* del = static_cast(itemDelegate()); - if (dragging) { - setAnchorHovered(false); - if (del->mouseDrag(dragStartPoint, dragEndPoint, index, rect)) { - qDebug() << "asking to repaint" << rect; + MessageDelegate* del = static_cast(itemDelegate()); + if (dragging) { + QModelIndex index = indexAt(dragStartPoint); + if (index.isValid()) { + QRect rect = visualRect(index); + if (rect.contains(dragStartPoint)) { + QString selected = del->mouseDrag(dragStartPoint, dragEndPoint, index, rect); + if (selectedText != selected) { + selectedText = selected; setDirtyRegion(rect); } - } else { - setAnchorHovered(del->isAnchorHovered(dragEndPoint, index, rect)); } - } else { - setAnchorHovered(false); } } else { - setAnchorHovered(false); + QModelIndex index = indexAt(dragEndPoint); + if (index.isValid()) { + QRect rect = visualRect(index); + if (rect.contains(dragEndPoint)) { + setAnchorHovered(del->hoverType(dragEndPoint, index, rect)); + } else { + setAnchorHovered(Shared::Hover::nothing); + } + } else { + setAnchorHovered(Shared::Hover::nothing); + } } } } @@ -470,6 +485,17 @@ void FeedView::mousePressEvent(QMouseEvent* event) mousePressed = event->button() == Qt::LeftButton; if (mousePressed) { dragStartPoint = event->localPos().toPoint(); + if (specialDelegate && specialModel) { + MessageDelegate* del = static_cast(itemDelegate()); + QString lastSelectedId = del->clearSelection(); + if (lastSelectedId.size()) { + Models::MessageFeed* feed = static_cast(model()); + QModelIndex index = feed->modelIndexById(lastSelectedId); + if (index.isValid()) { + setDirtyRegion(visualRect(index)); + } + } + } } } @@ -494,6 +520,17 @@ void FeedView::mouseReleaseEvent(QMouseEvent* event) } } +void FeedView::keyPressEvent(QKeyEvent* event) +{ + QKeyEvent *key_event = static_cast(event); + if (key_event->matches(QKeySequence::Copy)) { + if (selectedText.size() > 0) { + QClipboard* cb = QApplication::clipboard(); + cb->setText(selectedText); + } + } +} + void FeedView::resizeEvent(QResizeEvent* event) { QAbstractItemView::resizeEvent(event); @@ -603,3 +640,8 @@ void FeedView::onModelSyncStateChange(Models::MessageFeed::SyncState state) scheduleDelayedItemsLayout(); } } + +QString FeedView::getSelectedText() const +{ + return selectedText; +} diff --git a/ui/widgets/messageline/feedview.h b/ui/widgets/messageline/feedview.h index c0d6254..d0763a5 100644 --- a/ui/widgets/messageline/feedview.h +++ b/ui/widgets/messageline/feedview.h @@ -28,6 +28,7 @@ #include #include +#include /** * @todo write docs @@ -50,6 +51,7 @@ public: void setModel(QAbstractItemModel * model) override; QFont getFont() const; + QString getSelectedText() const; signals: void resized(); @@ -72,13 +74,14 @@ protected: void mouseMoveEvent(QMouseEvent * event) override; void mousePressEvent(QMouseEvent * event) override; void mouseReleaseEvent(QMouseEvent * event) override; + void keyPressEvent(QKeyEvent * event) override; void resizeEvent(QResizeEvent * event) override; private: bool tryToCalculateGeometriesWithNoScrollbars(const QStyleOptionViewItem& option, const QAbstractItemModel* model, uint32_t totalHeight); void positionProgress(); void drawDateDevider(int top, const QDateTime& date, QPainter& painter); - void setAnchorHovered(bool hovered); + void setAnchorHovered(Shared::Hover type); private: struct Hint { @@ -100,9 +103,10 @@ private: QFontMetrics dividerMetrics; bool mousePressed; bool dragging; - bool anchorHovered; + Shared::Hover hovered; QPoint dragStartPoint; QPoint dragEndPoint; + QString selectedText; static const std::set geometryChangingRoles; diff --git a/ui/widgets/messageline/messagedelegate.cpp b/ui/widgets/messageline/messagedelegate.cpp index 197248a..840ef5c 100644 --- a/ui/widgets/messageline/messagedelegate.cpp +++ b/ui/widgets/messageline/messagedelegate.cpp @@ -434,13 +434,37 @@ void MessageDelegate::leftClick(const QPoint& point, const QModelIndex& index, c } } -bool MessageDelegate::isAnchorHovered(const QPoint& point, const QModelIndex& index, const QRect& sizeHint) const +Shared::Hover MessageDelegate::hoverType(const QPoint& point, const QModelIndex& index, const QRect& sizeHint) const { - QString anchor = getAnchor(point, index, sizeHint); - return anchor.size() > 0; + QVariant vi = index.data(Models::MessageFeed::Bulk); + Models::FeedItem data = qvariant_cast(vi); + if (data.text.size() > 0) { + QRect localHint = getHoveredMessageBodyRect(index, data, sizeHint); + + if (localHint.contains(point)) { + QPoint translated = point - localHint.topLeft(); + + bodyRenderer->setHtml(Shared::processMessageBody(data.text)); + bodyRenderer->setTextWidth(localHint.size().width()); + + QAbstractTextDocumentLayout* lay = bodyRenderer->documentLayout(); + QString anchor = lay->anchorAt(translated); + + if (anchor.size() > 0) { + return Shared::Hover::anchor; + } else { + int position = lay->hitTest(translated, Qt::HitTestAccuracy::ExactHit); + if (position != -1) { //this is a bad way, it's false positive on the end of the last + return Shared::Hover::text; //line of a multiline block, so it's not better the checking the rect + } + //return Shared::Hover::text; + } + } + } + return Shared::Hover::nothing; } -bool MessageDelegate::mouseDrag(const QPoint& start, const QPoint& end, const QModelIndex& index, const QRect& sizeHint) +QString MessageDelegate::mouseDrag(const QPoint& start, const QPoint& end, const QModelIndex& index, const QRect& sizeHint) { QVariant vi = index.data(Models::MessageFeed::Bulk); Models::FeedItem data = qvariant_cast(vi); @@ -448,19 +472,31 @@ bool MessageDelegate::mouseDrag(const QPoint& start, const QPoint& end, const QM QRect localHint = getHoveredMessageBodyRect(index, data, sizeHint); if (localHint.contains(start)) { - QPoint translated = start - localHint.topLeft(); + QPoint tl = localHint.topLeft(); + QPoint first = start - tl; + QPoint last = end - tl; + last.setX(std::max(last.x(), 0)); + last.setX(std::min(last.x(), localHint.width() - 1)); + last.setY(std::max(last.y(), 0)); + last.setY(std::min(last.y(), localHint.height())); + bodyRenderer->setHtml(Shared::processMessageBody(data.text)); bodyRenderer->setTextWidth(localHint.size().width()); - selection.first = bodyRenderer->documentLayout()->hitTest(translated, Qt::HitTestAccuracy::FuzzyHit); - selection.second = bodyRenderer->documentLayout()->hitTest(end - localHint.topLeft(), Qt::HitTestAccuracy::FuzzyHit); + selection.first = bodyRenderer->documentLayout()->hitTest(first, Qt::HitTestAccuracy::FuzzyHit); + selection.second = bodyRenderer->documentLayout()->hitTest(last, Qt::HitTestAccuracy::FuzzyHit); currentId = data.id; - return true; + if (selection.first != selection.second) { + QTextCursor cursor(bodyRenderer); + cursor.setPosition(selection.first, QTextCursor::MoveAnchor); + cursor.setPosition(selection.second, QTextCursor::KeepAnchor); + return cursor.selectedText(); + } } } - return false; + return ""; } QString MessageDelegate::clearSelection() diff --git a/ui/widgets/messageline/messagedelegate.h b/ui/widgets/messageline/messagedelegate.h index 2aea240..dc0fb49 100644 --- a/ui/widgets/messageline/messagedelegate.h +++ b/ui/widgets/messageline/messagedelegate.h @@ -58,8 +58,8 @@ public: void endClearWidgets(); void beginClearWidgets(); void leftClick(const QPoint& point, const QModelIndex& index, const QRect& sizeHint) const; - bool isAnchorHovered(const QPoint& point, const QModelIndex& index, const QRect& sizeHint) const; - bool mouseDrag(const QPoint& start, const QPoint& end, const QModelIndex& index, const QRect& sizeHint); + Shared::Hover hoverType(const QPoint& point, const QModelIndex& index, const QRect& sizeHint) const; + QString mouseDrag(const QPoint& start, const QPoint& end, const QModelIndex& index, const QRect& sizeHint); QString clearSelection(); static int avatarHeight; diff --git a/ui/widgets/messageline/messagefeed.h b/ui/widgets/messageline/messagefeed.h index f362989..db174d2 100644 --- a/ui/widgets/messageline/messagefeed.h +++ b/ui/widgets/messageline/messagefeed.h @@ -57,6 +57,8 @@ public: void changeMessage(const QString& id, const QMap& data); void removeMessage(const QString& id); Shared::Message getMessage(const QString& id); + QModelIndex modelIndexById(const QString& id) const; + QModelIndex modelIndexByTime(const QString& id, const QDateTime& time) const; QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const override; int rowCount(const QModelIndex& parent = QModelIndex()) const override; @@ -126,8 +128,6 @@ protected: bool sentByMe(const Shared::Message& msg) const; Attachment fillAttach(const Shared::Message& msg) const; Edition fillCorrection(const Shared::Message& msg) const; - QModelIndex modelIndexById(const QString& id) const; - QModelIndex modelIndexByTime(const QString& id, const QDateTime& time) const; std::set detectChanges(const Shared::Message& msg, const QMap& data) const; private: -- 2.45.1 From 1f065f23e65d3d85c994a9d3d342cac14a9bccc1 Mon Sep 17 00:00:00 2001 From: blue Date: Tue, 3 May 2022 12:17:08 +0300 Subject: [PATCH 25/29] double click word selection handle, sigint sermentation fault fix --- CHANGELOG.md | 2 ++ main/application.cpp | 4 ++- ui/widgets/messageline/feedview.cpp | 31 +++++++++++++++++ ui/widgets/messageline/feedview.h | 1 + ui/widgets/messageline/messagedelegate.cpp | 39 +++++++++++++++++++--- ui/widgets/messageline/messagedelegate.h | 1 + 6 files changed, 73 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 241d61d..01fa4a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ - now when you remove an account it actually gets removed - segfault on unitialized Availability in some rare occesions - fixed crash when you open a dialog with someone that has only error messages in archive +- message height is now calculated correctly on Chinese and Japanese paragraphs +- the app doesn't crash on SIGINT anymore ### Improvements - there is a way to disable an account and it wouldn't connect when you change availability diff --git a/main/application.cpp b/main/application.cpp index 410cd81..216e322 100644 --- a/main/application.cpp +++ b/main/application.cpp @@ -186,7 +186,9 @@ void Application::onSquawkClosing() { dialogueQueue.setParentWidnow(nullptr); - disconnect(core, &Core::Squawk::responseVCard, squawk, &Squawk::responseVCard); + if (!nowQuitting) { + disconnect(core, &Core::Squawk::responseVCard, squawk, &Squawk::responseVCard); + } destroyingSquawk = true; squawk->deleteLater(); diff --git a/ui/widgets/messageline/feedview.cpp b/ui/widgets/messageline/feedview.cpp index 353d851..69b5093 100644 --- a/ui/widgets/messageline/feedview.cpp +++ b/ui/widgets/messageline/feedview.cpp @@ -482,6 +482,7 @@ void FeedView::mouseMoveEvent(QMouseEvent* event) void FeedView::mousePressEvent(QMouseEvent* event) { QAbstractItemView::mousePressEvent(event); + mousePressed = event->button() == Qt::LeftButton; if (mousePressed) { dragStartPoint = event->localPos().toPoint(); @@ -499,6 +500,36 @@ void FeedView::mousePressEvent(QMouseEvent* event) } } +void FeedView::mouseDoubleClickEvent(QMouseEvent* event) +{ + QAbstractItemView::mouseDoubleClickEvent(event); + mousePressed = event->button() == Qt::LeftButton; + if (mousePressed) { + dragStartPoint = event->localPos().toPoint(); + if (specialDelegate && specialModel) { + MessageDelegate* del = static_cast(itemDelegate()); + QString lastSelectedId = del->clearSelection(); + selectedText = ""; + if (lastSelectedId.size()) { + Models::MessageFeed* feed = static_cast(model()); + QModelIndex index = feed->modelIndexById(lastSelectedId); + if (index.isValid()) { + setDirtyRegion(visualRect(index)); + } + } + + QModelIndex index = indexAt(dragStartPoint); + QRect rect = visualRect(index); + if (rect.contains(dragStartPoint)) { + selectedText = del->leftDoubleClick(dragStartPoint, index, rect); + if (selectedText.size() > 0) { + setDirtyRegion(rect); + } + } + } + } +} + void FeedView::mouseReleaseEvent(QMouseEvent* event) { QAbstractItemView::mouseReleaseEvent(event); diff --git a/ui/widgets/messageline/feedview.h b/ui/widgets/messageline/feedview.h index d0763a5..4194849 100644 --- a/ui/widgets/messageline/feedview.h +++ b/ui/widgets/messageline/feedview.h @@ -74,6 +74,7 @@ protected: void mouseMoveEvent(QMouseEvent * event) override; void mousePressEvent(QMouseEvent * event) override; void mouseReleaseEvent(QMouseEvent * event) override; + void mouseDoubleClickEvent(QMouseEvent * event) override; void keyPressEvent(QKeyEvent * event) override; void resizeEvent(QResizeEvent * event) override; diff --git a/ui/widgets/messageline/messagedelegate.cpp b/ui/widgets/messageline/messagedelegate.cpp index 840ef5c..f935057 100644 --- a/ui/widgets/messageline/messagedelegate.cpp +++ b/ui/widgets/messageline/messagedelegate.cpp @@ -434,6 +434,39 @@ void MessageDelegate::leftClick(const QPoint& point, const QModelIndex& index, c } } +QString MessageDelegate::leftDoubleClick(const QPoint& point, const QModelIndex& index, const QRect& sizeHint) +{ + QVariant vi = index.data(Models::MessageFeed::Bulk); + Models::FeedItem data = qvariant_cast(vi); + if (data.text.size() > 0) { + QRect localHint = getHoveredMessageBodyRect(index, data, sizeHint); + + if (localHint.contains(point)) { + QPoint translated = point - localHint.topLeft(); + + bodyRenderer->setHtml(Shared::processMessageBody(data.text)); + bodyRenderer->setTextWidth(localHint.size().width()); + + QAbstractTextDocumentLayout* lay = bodyRenderer->documentLayout(); + + int position = lay->hitTest(translated, Qt::HitTestAccuracy::FuzzyHit); + QTextCursor cursor(bodyRenderer); + cursor.setPosition(position, QTextCursor::MoveAnchor); + cursor.movePosition(QTextCursor::StartOfWord, QTextCursor::MoveAnchor); + cursor.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor); + + selection.first = cursor.anchor(); + selection.second = cursor.position(); + currentId = data.id; + + if (selection.first != selection.second) { + return cursor.selectedText(); + } + } + } + return ""; +} + Shared::Hover MessageDelegate::hoverType(const QPoint& point, const QModelIndex& index, const QRect& sizeHint) const { QVariant vi = index.data(Models::MessageFeed::Bulk); @@ -454,10 +487,9 @@ Shared::Hover MessageDelegate::hoverType(const QPoint& point, const QModelIndex& return Shared::Hover::anchor; } else { int position = lay->hitTest(translated, Qt::HitTestAccuracy::ExactHit); - if (position != -1) { //this is a bad way, it's false positive on the end of the last - return Shared::Hover::text; //line of a multiline block, so it's not better the checking the rect + if (position != -1) { + return Shared::Hover::text; } - //return Shared::Hover::text; } } } @@ -480,7 +512,6 @@ QString MessageDelegate::mouseDrag(const QPoint& start, const QPoint& end, const last.setY(std::max(last.y(), 0)); last.setY(std::min(last.y(), localHint.height())); - bodyRenderer->setHtml(Shared::processMessageBody(data.text)); bodyRenderer->setTextWidth(localHint.size().width()); selection.first = bodyRenderer->documentLayout()->hitTest(first, Qt::HitTestAccuracy::FuzzyHit); diff --git a/ui/widgets/messageline/messagedelegate.h b/ui/widgets/messageline/messagedelegate.h index dc0fb49..322fb76 100644 --- a/ui/widgets/messageline/messagedelegate.h +++ b/ui/widgets/messageline/messagedelegate.h @@ -58,6 +58,7 @@ public: void endClearWidgets(); void beginClearWidgets(); void leftClick(const QPoint& point, const QModelIndex& index, const QRect& sizeHint) const; + QString leftDoubleClick(const QPoint& point, const QModelIndex& index, const QRect& sizeHint); Shared::Hover hoverType(const QPoint& point, const QModelIndex& index, const QRect& sizeHint) const; QString mouseDrag(const QPoint& start, const QPoint& end, const QModelIndex& index, const QRect& sizeHint); QString clearSelection(); -- 2.45.1 From 80c5e2f2b4214656ea6b62a9c3e981c79e3c122b Mon Sep 17 00:00:00 2001 From: blue Date: Wed, 4 May 2022 19:20:30 +0300 Subject: [PATCH 26/29] added en lolcalization file, actualized localizations --- CHANGELOG.md | 1 + translations/CMakeLists.txt | 1 + translations/squawk.en.ts | 1420 ++++++++++++++++++++++++++++++++++ translations/squawk.pt_BR.ts | 347 ++++++++- translations/squawk.ru.ts | 345 ++++++++- 5 files changed, 2072 insertions(+), 42 deletions(-) create mode 100644 translations/squawk.en.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 01fa4a2..f34bf3c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ - if you filled password field and chose KWallet as a storage Squawk wouldn't ask you again for the same password - if left the password field empty and chose KWallet as a storage Squawk will try to get that passord from KWallet before asking you to input it - accounts now connect to the server asyncronously - if one is stopped on password prompt another is connecting +- actualized translations, added English localization file ### New features - new "About" window with links, license, gratitudes diff --git a/translations/CMakeLists.txt b/translations/CMakeLists.txt index eee4f98..f70fe2b 100644 --- a/translations/CMakeLists.txt +++ b/translations/CMakeLists.txt @@ -1,6 +1,7 @@ find_package(Qt5LinguistTools) set(TS_FILES + squawk.en.ts squawk.ru.ts squawk.pt_BR.ts ) diff --git a/translations/squawk.en.ts b/translations/squawk.en.ts new file mode 100644 index 0000000..b219af0 --- /dev/null +++ b/translations/squawk.en.ts @@ -0,0 +1,1420 @@ + + + + + About + + + + About Squawk + About window header + About Squawk + + + + + Squawk + Squawk + + + + + About + Tab title + About + + + + + XMPP (jabber) messenger + XMPP (jabber) messenger + + + + + (c) 2019 - 2022, Yury Gubich + (c) 2019 - 2022, Yury Gubich + + + + + <a href="https://git.macaw.me/blue/squawk">Project site</a> + <a href="https://git.macaw.me/blue/squawk">Project site</a> + + + + + <a href="https://git.macaw.me/blue/squawk/src/branch/master/LICENSE.md">License: GNU General Public License version 3</a> + <a href="https://git.macaw.me/blue/squawk/src/branch/master/LICENSE.md">License: GNU General Public License version 3</a> + + + + + Components + Tab header + Components + + + + + + + + + Version + Version + + + + + + + + + 0.0.0 + 0.0.0 + + + + + Report Bugs + Report Bugs + + + + + Please report any bug you find! +To report bugs you can use: + Please report any bug you find! +To report bugs you can use: + + + + + <a href="https://git.macaw.me/blue/squawk/issues">Project bug tracker</> + <a href="https://git.macaw.me/blue/squawk/issues">Project bug tracker</> + + + + + XMPP (<a href="xmpp:blue@macaw.me">blue@macaw.me</a>) + XMPP (<a href="xmpp:blue@macaw.me">blue@macaw.me</a>) + + + + + E-Mail (<a href="mailto:blue@macaw.me">blue@macaw.me</a>) + E-Mail (<a href="mailto:blue@macaw.me">blue@macaw.me</a>) + + + + + Thanks To + Thanks to + + + + + Vae + Vae + + + + + Major refactoring, bug fixes, constructive criticism + Major refactoring, bug fixes, constructive criticism + + + + + Shunf4 + Shunf4 + + + + + Major refactoring, bug fixes, build adaptations for Windows and MacOS + Major refactoring, bug fixes, build adaptations for Windows and MacOS + + + + + Bruno F. Fontes + Bruno F. Fontes + + + + + Brazilian Portuguese translation + Brazilian Portuguese translation + + + + + (built against %1) + (built against %1) + + + + License + License + + + + Account + + + + Account + Window title + Account + + + + + Your account login + Tooltip + Your account login + + + + + john_smith1987 + Login placeholder + john_smith1987 + + + + + Server + Server + + + + + A server address of your account. Like 404.city or macaw.me + Tooltip + A server address of your account. Like 404.city or macaw.me + + + + + macaw.me + Placeholder + macaw.me + + + + + Login + Login + + + + + Password + Password + + + + + Password of your account + Tooltip + Password of your account + + + + + Name + Name + + + + + Just a name how would you call this account, doesn't affect anything + Just a name how would you call this account, doesn't affect anything (cant be changed) + + + + + John + Placeholder + John + + + + + Resource + Resource + + + + + A resource name like "Home" or "Work" + Tooltip + A resource name like "Home" or "Work" + + + + + QXmpp + Default resource + QXmpp + + + + + Password storage + Password storage + + + + + Active + Active + + + + + enable + enable + + + + Accounts + + + + Accounts + Accounts + + + + + Delete + Delete + + + + + Add + Add + + + + + Edit + Edit + + + + + Change password + Change password + + + + + Connect + Connect + + + + Deactivate + Deactivate + + + + + Activate + Activate + + + + Application + + + from + from + + + + Attached file + Attached file + + + + Mark as Read + Mark as Read + + + + Open conversation + Open conversation + + + + Conversation + + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Noto Sans'; font-size:8pt; font-weight:400; font-style:normal;"> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Noto Sans'; font-size:8pt; font-weight:400; font-style:normal;"> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html> + + + + + Type your message here... + Placeholder + Type your message here... + + + + Paste Image + Paste Image + + + + Drop files here to attach them to your message + Drop files here to attach them to your message + + + + Chose a file to send + Chose a file to send + + + + Try sending again + Try sending again + + + + Copy selected + Copy selected + + + + Copy message + Copy message + + + + Open + Open + + + + Show in folder + Show in folder + + + + Edit + Edit + + + + Editing message... + Editing message... + + + + CredentialsPrompt + + + + Authentication error: %1 + Window title + Authentication error: %1 + + + + + Couldn't authenticate account %1: login or password is icorrect. +Would you like to check them and try again? + Couldn't authenticate account %1: login or password is incorrect. +Would you like to check them and try again? + + + + + Login + Login + + + + + Your account login (without @server.domain) + Tooltip + Your account login (without @server.domain) + + + + + Password + Password + + + + + Your password + Password + + + + DialogQueue + + + Input the password for account %1 + Input the password for account %1 + + + + Password for account %1 + Password for account %1 + + + + Global + + + Online + Availability + Online + + + + Away + Availability + Away + + + + Absent + Availability + Absent + + + + Busy + Availability + Busy + + + + Chatty + Availability + Chatty + + + + Invisible + Availability + Invisible + + + + Offline + Availability + Offline + + + + Disconnected + ConnectionState + Disconnected + + + + Connecting + ConnectionState + Connecting + + + + Connected + ConnectionState + Connected + + + + Error + ConnectionState + Error + + + + None + SubscriptionState + None + + + + From + SubscriptionState + From + + + + To + SubscriptionState + To + + + + Both + SubscriptionState + Both + + + + Unknown + SubscriptionState + Unknown + + + + Unspecified + Affiliation + Unspecified + + + + Outcast + Affiliation + Outcast + + + + Nobody + Affiliation + Nobody + + + + Member + Affiliation + Member + + + + Admin + Affiliation + Admin + + + + Owner + Affiliation + Owner + + + + Unspecified + Role + Unspecified + + + + Nobody + Role + Nobody + + + + Visitor + Role + Visitor + + + + Participant + Role + Participant + + + + Moderator + Role + Moderator + + + + Pending + MessageState + Pending + + + + Sent + MessageState + Sent + + + + Delivered + MessageState + Delivered + + + + Error + MessageState + Error + + + + Plain + AccountPassword + Plain + + + + Jammed + AccountPassword + Jammed + + + + Always Ask + AccountPassword + Always Ask + + + + KWallet + AccountPassword + KWallet + + + + Your password is going to be stored in config file in plain text + AccountPasswordDescription + Your password is going to be stored in config file in plain text + + + + Your password is going to be stored in config file but jammed with constant encryption key you can find in program source code. It might look like encryption but it's not + AccountPasswordDescription + Your password is going to be stored in config file but jammed with constant encryption key you can find in program source code. It might look like encryption but it's not + + + + Squawk is going to query you for the password on every start of the program + AccountPasswordDescription + Squawk is going to query you for the password on every start of the program + + + + Your password is going to be stored in KDE wallet storage (KWallet). You're going to be queried for permissions + AccountPasswordDescription + Your password is going to be stored in KDE wallet storage (KWallet). You're going to be queried for permissions + + + + JoinConference + + + + Join new conference + Join new conference + + + + + JID + JID + + + + + Room JID + Room JID + + + + + identifier@conference.server.org + identifier@conference.server.org + + + + + Account + Account + + + + + Join on login + Join on login + + + + + If checked Squawk will try to join this conference on login + If checked Squawk will try to join this conference on login + + + + + Nick name + Nick name + + + + + Your nick name for that conference. If you leave this field empty your account name will be used as a nick name + Your nick name for that conference. If you leave this field empty your account name will be used as a nick name + + + + + John + John + + + + MessageLine + + + + Download + Download + + + + Models::Room + + + Subscribed + Subscribed + + + + Temporarily unsubscribed + Temporarily unsubscribed + + + + Temporarily subscribed + Temporarily subscribed + + + + Unsubscribed + Unsubscribed + + + + Models::Roster + + + New messages + New messages + + + + + + New messages: + New messages: + + + + + Jabber ID: + Jabber ID: + + + + + + Availability: + Availability: + + + + + + Status: + Status: + + + + + + Subscription: + Subscription: + + + + Affiliation: + Affiliation: + + + + Role: + Role: + + + + Online contacts: + Online contacts: + + + + Total contacts: + Total contacts: + + + + Members: + Members: + + + + NewContact + + + + Add new contact + Window title + Add new contact + + + + + Account + Account + + + + + An account that is going to have new contact + An account that is going to have new contact + + + + + JID + JID + + + + + Jabber id of your new contact + Jabber id of your new contact + + + + + name@server.dmn + Placeholder + name@server.dmn + + + + + Name + Name + + + + + The way this new contact will be labeled in your roster (optional) + The way this new contact will be labeled in your roster (optional) + + + + + John Smith + John Smith + + + + PageAppearance + + + + Theme + Style + + + + + Color scheme + Color scheme + + + + + + + System + System + + + + PageGeneral + + + + Downloads path + Downloads path + + + + + Browse + Browse + + + + Select where downloads folder is going to be + Select where downloads folder is going to be + + + + Settings + + + + Preferences + Window title + Preferences + + + + + + + General + General + + + + + Appearance + Appearance + + + + + Apply + Apply + + + + + Cancel + Cancel + + + + + Ok + Ok + + + + Squawk + + + + squawk + Squawk + + + + + Please select a contact to start chatting + Please select a contact to start chatting + + + + + Settings + Settings + + + + + Squawk + Menu bar entry + Squawk + + + + + Help + Help + + + + + Accounts + Accounts + + + + + Quit + Quit + + + + + Add contact + Add contact + + + + + Add conference + Join conference + + + + + Preferences + Preferences + + + + + About Squawk + About Squawk + + + + Deactivate + Deactivate + + + + Activate + Activate + + + + + VCard + VCard + + + + + + Remove + Remove + + + + Open dialog + Open dialog + + + + + Unsubscribe + Unsubscribe + + + + + Subscribe + Subscribe + + + + Rename + Rename + + + + Input new name for %1 +or leave it empty for the contact +to be displayed as %1 + Input new name for %1 +or leave it empty for the contact +to be displayed as %1 + + + + Renaming %1 + Renaming %1 + + + + Groups + Groups + + + + New group + New group + + + + New group name + New group name + + + + Add %1 to a new group + Add %1 to a new group + + + + Open conversation + Open conversation + + + + %1 account card + %1 account card + + + + %1 contact card + %1 contact card + + + + Downloading vCard + Downloading vCard + + + + VCard + + + + Received 12.07.2007 at 17.35 + Never updated + + + + + + + General + General + + + + + Organization + Organization + + + + + Middle name + Middle name + + + + + First name + First name + + + + + Last name + Last name + + + + + Nick name + Nick name + + + + + Birthday + Birthday + + + + + Organization name + Organization name + + + + + Unit / Department + Unit / Department + + + + + Role / Profession + Role / Profession + + + + + Job title + Job title + + + + + Full name + Full name + + + + + Personal information + Personal information + + + + + + + Contact + Contact + + + + + Addresses + Addresses + + + + + E-Mail addresses + E-Mail addresses + + + + + Jabber ID + Jabber ID + + + + + Web site + Web site + + + + + Phone numbers + Phone numbers + + + + + + + Description + Description + + + + + Set avatar + Set avatar + + + + + Clear avatar + Clear avatar + + + + Account %1 card + Account %1 card + + + + Contact %1 card + Contact %1 card + + + + Received %1 at %2 + Received %1 at %2 + + + + Chose your new avatar + Chose your new avatar + + + + Images (*.png *.jpg *.jpeg) + Images (*.png *.jpg *.jpeg) + + + + Add email address + Add email address + + + + Unset this email as preferred + Unset this email as preferred + + + + Set this email as preferred + Set this email as preferred + + + + Remove selected email addresses + Remove selected email addresses + + + + Copy selected emails to clipboard + Copy selected emails to clipboard + + + + Add phone number + Add phone number + + + + Unset this phone as preferred + Unset this phone as preferred + + + + Set this phone as preferred + Set this phone as preferred + + + + Remove selected phone numbers + Remove selected phone numbers + + + + Copy selected phones to clipboard + Copy selected phones to clipboard + + + diff --git a/translations/squawk.pt_BR.ts b/translations/squawk.pt_BR.ts index 4330979..6041678 100644 --- a/translations/squawk.pt_BR.ts +++ b/translations/squawk.pt_BR.ts @@ -1,6 +1,108 @@ + + About + + About Squawk + Sorbe Squawk + + + Squawk + Squawk + + + About + Tab title + Sobre + + + XMPP (jabber) messenger + XMPP (jabber) mensageiro + + + (c) 2019 - 2022, Yury Gubich + (c) 2019 - 2022, Yury Gubich + + + <a href="https://git.macaw.me/blue/squawk">Project site</a> + <a href="https://git.macaw.me/blue/squawk">Site do projeto</a> + + + <a href="https://git.macaw.me/blue/squawk/src/branch/master/LICENSE.md">License: GNU General Public License version 3</a> + <a href="https://git.macaw.me/blue/squawk/src/branch/master/LICENSE.md">Licença: GNU General Public License versão 3</a> + + + Components + Componentes + + + Version + Versão + + + 0.0.0 + 0.0.0 + + + Report Bugs + Relatório de erros + + + Please report any bug you find! +To report bugs you can use: + Por favor reportar qualquer erro que você encontrar! +Para relatar bugs você pode usar: + + + <a href="https://git.macaw.me/blue/squawk/issues">Project bug tracker</> + <a href="https://git.macaw.me/blue/squawk/issues">Rastreador de bugs do projeto</> + + + XMPP (<a href="xmpp:blue@macaw.me">blue@macaw.me</a>) + XMPP (<a href="xmpp:blue@macaw.me">blue@macaw.me</a>) + + + E-Mail (<a href="mailto:blue@macaw.me">blue@macaw.me</a>) + E-Mail (<a href="mailto:blue@macaw.me">blue@macaw.me</a>) + + + Thanks To + Graças ao + + + Vae + Vae + + + Major refactoring, bug fixes, constructive criticism + Refatoração importante, correção de erros, críticas construtivas + + + Shunf4 + Shunf4 + + + Major refactoring, bug fixes, build adaptations for Windows and MacOS + Refatoração importante, correção de erros, adaptações de construção para Windows e MacOS + + + Bruno F. Fontes + Bruno F. Fontes + + + Brazilian Portuguese translation + Tradução para o português do Brasil + + + (built against %1) + (Versão durante a compilação %1) + + + License + Licença + + Account @@ -10,10 +112,12 @@ Your account login + Tooltip Suas informações de login john_smith1987 + Login placeholder josé_silva1987 @@ -22,10 +126,12 @@ A server address of your account. Like 404.city or macaw.me + Tooltip O endereço do servidor da sua conta, como o 404.city ou o macaw.me macaw.me + Placeholder macaw.me @@ -38,6 +144,7 @@ Password of your account + Tooltip Senha da sua conta @@ -46,10 +153,11 @@ Just a name how would you call this account, doesn't affect anything - Apenas um nome para identificar esta conta. Não influencia em nada + Apenas um nome para identificar esta conta. Não influencia em nada (não pode ser mudado) John + Placeholder José @@ -58,6 +166,7 @@ A resource name like "Home" or "Work" + Tooltip Um nome de recurso como "Casa" ou "Trabalho" @@ -69,6 +178,14 @@ Password storage Armazenamento de senha + + Active + Ativo + + + enable + habilitar + Accounts @@ -98,30 +215,135 @@ Disconnect - Desconectar + Desconectar + + + Deactivate + Desativar + + + Activate + Ativar + + + + Application + + from + de + + + Attached file + Arquivo anexado + + + Mark as Read + Marcar como lido + + + Open conversation + Abrir conversa Conversation Type your message here... + Placeholder Digite sua mensagem aqui... Chose a file to send Escolha um arquivo para enviar + + Drop files here to attach them to your message + Arraste seus arquivos aqui para anexá-los a sua mensagem + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'Liberation Sans'; font-size:10pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'Noto Sans'; font-size:8pt; font-weight:400; font-style:normal;"> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html> - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Noto Sans'; font-size:8pt; font-weight:400; font-style:normal;"> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html> - Drop files here to attach them to your message - Arraste seus arquivos aqui para anexá-los a sua mensagem + Paste Image + Colar imagem + + + Try sending again + Tente enviar de novo + + + Copy selected + Copiar selecionado + + + Copy message + Copiar mensagem + + + Open + Abrir + + + Show in folder + Show in explorer + + + Edit + Editar + + + Editing message... + Messae está sendo editado... + + + + CredentialsPrompt + + Authentication error: %1 + Window title + Erro de autenticação: %1 + + + Couldn't authenticate account %1: login or password is icorrect. +Would you like to check them and try again? + Não foi possível autenticar a conta %1: login ou senha incorretos. +Deseja verificá-los e tentar novamente? + + + Login + Usuário + + + Your account login (without @server.domain) + Suas informações de login (sem @server.domain) + + + Password + Senha + + + Your password + Senha da sua conta + + + + DialogQueue + + Input the password for account %1 + Digite a senha para a conta %1 + + + Password for account %1 + Senha para a conta %1 @@ -370,14 +592,14 @@ p, li { white-space: pre-wrap; } Message Open - Abrir + Abrir MessageLine Downloading... - Baixando... + Baixando... Download @@ -386,28 +608,28 @@ p, li { white-space: pre-wrap; } Error uploading file: %1 You can try again - Error fazendo upload do arquivo: + Error fazendo upload do arquivo: %1 Você pode tentar novamente Upload - Upload + Upload Error downloading file: %1 You can try again - Erro baixando arquivo: + Erro baixando arquivo: %1 Você pode tentar novamente Uploading... - Fazendo upload... + Fazendo upload... Push the button to download the file - Pressione o botão para baixar o arquivo + Pressione o botão para baixar o arquivo @@ -481,7 +703,7 @@ Você pode tentar novamente NewContact Add new contact - Заголовок окна + Window title Adicionar novo contato @@ -502,7 +724,7 @@ Você pode tentar novamente name@server.dmn - Placeholder поля ввода JID + Placeholder nome@servidor.com.br @@ -518,6 +740,66 @@ Você pode tentar novamente José Silva + + PageAppearance + + Theme + Estilo + + + Color scheme + Esquema de cores + + + System + Do sistema + + + + PageGeneral + + Downloads path + Pasta de downloads + + + Browse + 6 / 5,000 +Translation results +Navegar + + + Select where downloads folder is going to be + Selecione onde a pasta de downloads ficará + + + + Settings + + Preferences + Window title + Preferências + + + General + Geral + + + Appearance + Aparência + + + Apply + Aplicar + + + Cancel + Cancelar + + + Ok + Feito + + Squawk @@ -530,6 +812,7 @@ Você pode tentar novamente Squawk + Menu bar entry Squawk @@ -550,11 +833,11 @@ Você pode tentar novamente Disconnect - Desconectar + Desconectar Connect - Conectar + Conectar VCard @@ -627,26 +910,46 @@ ser exibido com Attached file - Arquivo anexado + Arquivo anexado Input the password for account %1 - Digite a senha para a conta %1 + Digite a senha para a conta %1 Password for account %1 - Senha para a conta %1 + Senha para a conta %1 Please select a contact to start chatting Por favor selecione um contato para começar a conversar + + Help + Ajuda + + + Preferences + Preferências + + + About Squawk + Sorbe Squawk + + + Deactivate + Desativar + + + Activate + Ativar + VCard Received 12.07.2007 at 17.35 - Recebido 12/07/2007 às 17:35 + Nunca atualizado General @@ -682,7 +985,7 @@ ser exibido com Unit / Department - Unidade/Departamento + Unidade / Departamento Role / Profession diff --git a/translations/squawk.ru.ts b/translations/squawk.ru.ts index e3b4d52..39dbfac 100644 --- a/translations/squawk.ru.ts +++ b/translations/squawk.ru.ts @@ -1,6 +1,108 @@ + + About + + About Squawk + О Программе Squawk + + + Squawk + Squawk + + + About + Tab title + Общее + + + XMPP (jabber) messenger + XMPP (jabber) мессенджер + + + (c) 2019 - 2022, Yury Gubich + (c) 2019 - 2022, Юрий Губич + + + <a href="https://git.macaw.me/blue/squawk">Project site</a> + <a href="https://git.macaw.me/blue/squawk">Сайт проекта</a> + + + <a href="https://git.macaw.me/blue/squawk/src/branch/master/LICENSE.md">License: GNU General Public License version 3</a> + <a href="https://git.macaw.me/blue/squawk/src/branch/master/LICENSE.md">Лицензия: GNU General Public License версия 3</a> + + + Components + Компоненты + + + Version + Версия + + + 0.0.0 + 0.0.0 + + + Report Bugs + Сообщать об ошибках + + + Please report any bug you find! +To report bugs you can use: + Пожалуйста, сообщайте о любых ошибках! +Способы сообщить об ошибках: + + + <a href="https://git.macaw.me/blue/squawk/issues">Project bug tracker</> + <a href="https://git.macaw.me/blue/squawk/issues">Баг-трекер проекта</> + + + XMPP (<a href="xmpp:blue@macaw.me">blue@macaw.me</a>) + XMPP (<a href="xmpp:blue@macaw.me">blue@macaw.me</a>) + + + E-Mail (<a href="mailto:blue@macaw.me">blue@macaw.me</a>) + E-Mail (<a href="mailto:blue@macaw.me">blue@macaw.me</a>) + + + Thanks To + Благодарности + + + Vae + Vae + + + Major refactoring, bug fixes, constructive criticism + Крупный рефакторинг, исправление ошибок, конструктивная критика + + + Shunf4 + Shunf4 + + + Major refactoring, bug fixes, build adaptations for Windows and MacOS + Крупный рефакторинг, исправление ошибок, адаптация сборки под Windows and MacOS + + + Bruno F. Fontes + Bruno F. Fontes + + + Brazilian Portuguese translation + Перевод на Португальский (Бразилия) + + + (built against %1) + (версия при сборке %1) + + + License + Лицензия + + Account @@ -10,10 +112,12 @@ Your account login + Tooltip Имя пользователя Вашей учетной записи john_smith1987 + Login placeholder ivan_ivanov1987 @@ -22,10 +126,12 @@ A server address of your account. Like 404.city or macaw.me + Tooltip Адресс сервера вашей учетной записи (выглядит как 404.city или macaw.me) macaw.me + Placeholder macaw.me @@ -38,6 +144,7 @@ Password of your account + Tooltip Пароль вашей учетной записи @@ -46,10 +153,11 @@ Just a name how would you call this account, doesn't affect anything - Просто имя, то как Вы называете свою учетную запись, может быть любым + Просто имя, то как Вы называете свою учетную запись, может быть любым (нельзя поменять) John + Placeholder Иван @@ -58,6 +166,7 @@ A resource name like "Home" or "Work" + Tooltip Имя этой программы для ваших контактов, может быть "Home" или "Phone" @@ -69,6 +178,14 @@ Password storage Хранение пароля + + Active + Активен + + + enable + включен + Accounts @@ -98,30 +215,139 @@ Disconnect - Отключить + Отключить + + + Deactivate + Деактивировать + + + Activate + Активировать + + + + Application + + from + от + + + Attached file + Прикрепленный файл + + + Mark as Read + Пометить прочитанным + + + Open conversation + Открыть окно беседы Conversation Type your message here... + Placeholder Введите сообщение... Chose a file to send Выберите файл для отправки + + Drop files here to attach them to your message + Бросьте файлы сюда для того что бы прикрепить их к сообщению + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'Liberation Sans'; font-size:10pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'Noto Sans'; font-size:8pt; font-weight:400; font-style:normal;"> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html> - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Noto Sans'; font-size:8pt; font-weight:400; font-style:normal;"> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html> - Drop files here to attach them to your message - Бросьте файлы сюда для того что бы прикрепить их к сообщению + Paste Image + Вставить изображение + + + Try sending again + Отправить снова + + + Copy selected + Скопировать выделенное + + + Copy message + Скопировать сообщение + + + Open + Открыть + + + Show in folder + Показать в проводнике + + + Edit + Редактировать + + + Editing message... + Сообщение редактируется... + + + + CredentialsPrompt + + Authentication error: %1 + Window title + Ошибка аутентификации: %1 + + + Couldn't authenticate account %1: login or password is icorrect. +Would you like to check them and try again? + Не получилось аутентифицировать +учетную запись %1: +имя пользователя или пароль введены неверно. +Желаете ли проверить их и +попробовать аутентифицироваться еще раз? + + + Login + Имя учетной записи + + + Your account login (without @server.domain) + Tooltip + Имя вашей учтетной записи (без @server.domain) + + + Password + Пароль + + + Your password + Ваш пароль + + + + DialogQueue + + Input the password for account %1 + Введите пароль для учетной записи %1 + + + Password for account %1 + Пароль для учетной записи %1 @@ -370,14 +596,14 @@ p, li { white-space: pre-wrap; } Message Open - Открыть + Открыть MessageLine Downloading... - Скачивается... + Скачивается... Download @@ -386,28 +612,28 @@ p, li { white-space: pre-wrap; } Error uploading file: %1 You can try again - Ошибка загрузки файла на сервер: + Ошибка загрузки файла на сервер: %1 Для того, что бы попробовать снова нажмите на кнопку Upload - Загрузить + Загрузить Error downloading file: %1 You can try again - Ошибка скачивания файла: + Ошибка скачивания файла: %1 Вы можете попробовать снова Uploading... - Загружается... + Загружается... Push the button to download the file - Нажмите на кнопку что бы загрузить файл + Нажмите на кнопку что бы загрузить файл @@ -481,7 +707,7 @@ You can try again NewContact Add new contact - Заголовок окна + Window title Добавление нового контакта @@ -502,7 +728,7 @@ You can try again name@server.dmn - Placeholder поля ввода JID + Placeholder name@server.dmn @@ -518,6 +744,64 @@ You can try again Иван Иванов + + PageAppearance + + Theme + Оформление + + + Color scheme + Цветовая схема + + + System + Системная + + + + PageGeneral + + Downloads path + Папка для сохраненных файлов + + + Browse + Выбрать + + + Select where downloads folder is going to be + Выберете папку, в которую будут сохраняться файлы + + + + Settings + + Preferences + Window title + Настройки + + + General + Общее + + + Appearance + Внешний вид + + + Apply + Применить + + + Cancel + Отменить + + + Ok + Готово + + Squawk @@ -530,6 +814,7 @@ You can try again Squawk + Menu bar entry Squawk @@ -550,11 +835,11 @@ You can try again Disconnect - Отключить + Отключить Connect - Подключить + Подключить VCard @@ -627,20 +912,40 @@ to be displayed as %1 Attached file - Прикрепленный файл + Прикрепленный файл Input the password for account %1 - Введите пароль для учетной записи %1 + Введите пароль для учетной записи %1 Password for account %1 - Пароль для учетной записи %1 + Пароль для учетной записи %1 Please select a contact to start chatting Выберите контакт или группу что бы начать переписку + + Help + Помощь + + + Preferences + Настройки + + + About Squawk + О Программе Squawk + + + Deactivate + Деактивировать + + + Activate + Активировать + VCard -- 2.45.1 From 645b92ba51124006d61cb2f20059b186d2213b93 Mon Sep 17 00:00:00 2001 From: blue Date: Thu, 5 May 2022 20:46:49 +0300 Subject: [PATCH 27/29] release 0.2.2 preparation --- CHANGELOG.md | 2 +- README.md | 2 +- packaging/Archlinux/PKGBUILD | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f34bf3c..f4fb579 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Squawk 0.2.2 (UNRELEASED) +## Squawk 0.2.2 (May 05, 2022) ### Bug fixes - now when you remove an account it actually gets removed - segfault on unitialized Availability in some rare occesions diff --git a/README.md b/README.md index 5845c46..3e20568 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ [![AUR version](https://img.shields.io/aur/version/squawk?style=flat-square)](https://aur.archlinux.org/packages/squawk/) [![Liberapay patrons](https://img.shields.io/liberapay/patrons/macaw.me?logo=liberapay&style=flat-square)](https://liberapay.com/macaw.me) -![Squawk screenshot](https://macaw.me/images/squawk/0.2.1.png) +![Squawk screenshot](https://macaw.me/images/squawk/0.2.2.png) ### Prerequisites diff --git a/packaging/Archlinux/PKGBUILD b/packaging/Archlinux/PKGBUILD index 899f058..7db43ff 100644 --- a/packaging/Archlinux/PKGBUILD +++ b/packaging/Archlinux/PKGBUILD @@ -1,6 +1,6 @@ # Maintainer: Yury Gubich pkgname=squawk -pkgver=0.2.1 +pkgver=0.2.2 pkgrel=1 pkgdesc="An XMPP desktop messenger, written on pure c++ (qt)" arch=('i686' 'x86_64') @@ -14,7 +14,7 @@ optdepends=('kwallet: secure password storage (requires rebuild)' 'kio: better show in folder action (requires rebuild)') source=("$pkgname-$pkgver.tar.gz") -sha256sums=('c00dad1e441601acabb5200dc394f53abfc9876f3902a7dd4ad2fee3232ee84d') +sha256sums=('e4fa2174a3ba95159cc3b0bac3f00550c9e0ce971c55334e2662696a4543fc7e') build() { cd "$srcdir/squawk" cmake . -D CMAKE_INSTALL_PREFIX=/usr -D CMAKE_BUILD_TYPE=Release -- 2.45.1 From 93e6af3e2098008a0949be5466f917fda78f498d Mon Sep 17 00:00:00 2001 From: antonpavanvo Date: Tue, 31 May 2022 22:04:36 +0400 Subject: [PATCH 28/29] feat: hide to tray on closing --- ui/squawk.cpp | 74 +++++++++++++++++++++-------- ui/squawk.h | 4 ++ ui/widgets/settings/pagegeneral.cpp | 10 ++++ ui/widgets/settings/pagegeneral.h | 1 + ui/widgets/settings/pagegeneral.ui | 13 +++++ 5 files changed, 82 insertions(+), 20 deletions(-) diff --git a/ui/squawk.cpp b/ui/squawk.cpp index 9b6158c..34ff4b2 100644 --- a/ui/squawk.cpp +++ b/ui/squawk.cpp @@ -20,6 +20,7 @@ #include "ui_squawk.h" #include #include +#include Squawk::Squawk(Models::Roster& p_rosterModel, QWidget *parent) : QMainWindow(parent), @@ -51,12 +52,13 @@ Squawk::Squawk(Models::Roster& p_rosterModel, QWidget *parent) : m_ui->comboBox->addItem(Shared::availabilityIcon(av), Shared::Global::getName(av)); } m_ui->comboBox->setCurrentIndex(static_cast(Shared::Availability::offline)); - + createTrayIcon(); + connect(m_ui->actionAccounts, &QAction::triggered, this, &Squawk::onAccounts); connect(m_ui->actionPreferences, &QAction::triggered, this, &Squawk::onPreferences); connect(m_ui->actionAddContact, &QAction::triggered, this, &Squawk::onNewContact); connect(m_ui->actionAddConference, &QAction::triggered, this, &Squawk::onNewConference); - connect(m_ui->actionQuit, &QAction::triggered, this, &Squawk::close); + connect(m_ui->actionQuit, &QAction::triggered, [this]() { hide(); close(); }); // Actually closing connect(m_ui->comboBox, qOverload(&QComboBox::activated), this, &Squawk::onComboboxActivated); //connect(m_ui->roster, &QTreeView::doubleClicked, this, &Squawk::onRosterItemDoubleClicked); connect(m_ui->roster, &QTreeView::customContextMenuRequested, this, &Squawk::onRosterContextMenu); @@ -89,10 +91,35 @@ Squawk::Squawk(Models::Roster& p_rosterModel, QWidget *parent) : settings.endGroup(); } +QSystemTrayIcon* Squawk::trayIcon; + Squawk::~Squawk() { delete contextMenu; + delete trayIcon; } +void Squawk::createTrayIcon() +{ + QSettings settings; + trayIcon = new QSystemTrayIcon(this); + trayIcon->setIcon(QApplication::windowIcon()); + + QMenu * menu = new QMenu(this); + QAction * viewWindow = new QAction("Open Main Window", this);//TODO add translations + QAction * quitAction = new QAction("Quit", this); + + connect(viewWindow, SIGNAL(triggered()), this, SLOT(show())); + connect(quitAction, &QAction::triggered, [this]() { hide(); close(); }); // Actually closing + + menu->addAction(viewWindow); + menu->addAction(quitAction); + + trayIcon->setContextMenu(menu); + if(settings.value("trayIconCheckbox").toBool()) + trayIcon->show(); +} + + void Squawk::onAccounts() { if (accounts == nullptr) { @@ -183,25 +210,32 @@ void Squawk::onJoinConferenceAccepted() void Squawk::closeEvent(QCloseEvent* event) { - if (accounts != nullptr) { - accounts->close(); - } - if (preferences != nullptr) { - preferences->close(); - } - if (about != nullptr) { - about->close(); - } - - 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();; + QSettings settings; + if(this->isVisible() && settings.value("trayIconCheckbox").toBool()){ + event->ignore(); + this->hide(); - QMainWindow::closeEvent(event); + } else { + if (accounts != nullptr) { + accounts->close(); + } + if (preferences != nullptr) { + preferences->close(); + } + if (about != nullptr) { + about->close(); + } + + 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() { diff --git a/ui/squawk.h b/ui/squawk.h index 15a73dd..40b7f61 100644 --- a/ui/squawk.h +++ b/ui/squawk.h @@ -24,6 +24,7 @@ #include #include #include +#include #include #include @@ -85,6 +86,7 @@ signals: public: Models::Roster::ElId currentConversationId() const; void closeCurrentConversation(); + static QSystemTrayIcon *trayIcon; public slots: void writeSettings(); @@ -93,6 +95,8 @@ public slots: void select(QModelIndex index); private: + void createTrayIcon(); + QScopedPointer m_ui; Accounts* accounts; diff --git a/ui/widgets/settings/pagegeneral.cpp b/ui/widgets/settings/pagegeneral.cpp index 9ed46a2..7608fcb 100644 --- a/ui/widgets/settings/pagegeneral.cpp +++ b/ui/widgets/settings/pagegeneral.cpp @@ -18,6 +18,7 @@ #include "pagegeneral.h" #include "ui_pagegeneral.h" +#include "ui/squawk.h" PageGeneral::PageGeneral(QWidget* parent): QWidget(parent), @@ -28,7 +29,10 @@ PageGeneral::PageGeneral(QWidget* parent): QSettings settings; m_ui->downloadsPathInput->setText(settings.value("downloadsPath").toString()); + m_ui->trayIconCheckbox->setChecked(settings.value("trayIconCheckbox").toBool()); + connect(m_ui->downloadsPathButton, &QPushButton::clicked, this, &PageGeneral::onBrowseButtonClicked); + connect(m_ui->trayIconCheckbox, &QCheckBox::stateChanged, this, &PageGeneral::onTrayIconCheckboxChecked); } PageGeneral::~PageGeneral() @@ -76,3 +80,9 @@ void PageGeneral::onDialogDestroyed() { dialog = nullptr; } + +void PageGeneral::onTrayIconCheckboxChecked(){ + QSettings settings; + settings.setValue("trayIconCheckbox", m_ui->trayIconCheckbox->isChecked()); + Squawk::trayIcon->setVisible(m_ui->trayIconCheckbox->isChecked()); +} diff --git a/ui/widgets/settings/pagegeneral.h b/ui/widgets/settings/pagegeneral.h index 7f58d71..4f6197e 100644 --- a/ui/widgets/settings/pagegeneral.h +++ b/ui/widgets/settings/pagegeneral.h @@ -47,6 +47,7 @@ private slots: void onBrowseButtonClicked(); void onDialogAccepted(); void onDialogDestroyed(); + void onTrayIconCheckboxChecked(); private: QScopedPointer m_ui; diff --git a/ui/widgets/settings/pagegeneral.ui b/ui/widgets/settings/pagegeneral.ui index e412668..af944d7 100644 --- a/ui/widgets/settings/pagegeneral.ui +++ b/ui/widgets/settings/pagegeneral.ui @@ -39,6 +39,19 @@
+ + + + Qt::LeftToRight + + + Hide Squawk to tray + + + false + + + -- 2.45.1 From 21c0fe728d9b3cb6995bbb1e59b078302c95d307 Mon Sep 17 00:00:00 2001 From: antonpavanvo Date: Tue, 31 May 2022 23:16:39 +0400 Subject: [PATCH 29/29] style: remove useless code --- ui/squawk.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/ui/squawk.cpp b/ui/squawk.cpp index 34ff4b2..a118f9c 100644 --- a/ui/squawk.cpp +++ b/ui/squawk.cpp @@ -20,7 +20,6 @@ #include "ui_squawk.h" #include #include -#include Squawk::Squawk(Models::Roster& p_rosterModel, QWidget *parent) : QMainWindow(parent), -- 2.45.1