From b7b70bc198ce0fcfb1a26db3c60ee3f10332f581 Mon Sep 17 00:00:00 2001 From: blue Date: Tue, 11 May 2021 00:06:40 +0300 Subject: [PATCH 01/93] segfault fix when trying to send something but the history isn't loaded yet, icon and for attached files which are not previewed --- shared/global.cpp | 4 +++ ui/models/messagefeed.cpp | 29 ++++++++++-------- ui/utils/messagedelegate.cpp | 59 +++++++++++++++++++++++++----------- 3 files changed, 61 insertions(+), 31 deletions(-) diff --git a/shared/global.cpp b/shared/global.cpp index 62843ed..25a1c87 100644 --- a/shared/global.cpp +++ b/shared/global.cpp @@ -112,6 +112,8 @@ Shared::Global::Global(): #endif } + +static const QSize defaultIconFileInfoHeight(50, 50); Shared::Global::FileInfo Shared::Global::getFileInfo(const QString& path) { std::map::const_iterator itr = instance->fileCache.find(path); @@ -131,6 +133,8 @@ Shared::Global::FileInfo Shared::Global::getFileInfo(const QString& path) p = FileInfo::Preview::picture; QImage img(path); size = img.size(); + } else { + size = defaultIconFileInfoHeight; } itr = instance->fileCache.insert(std::make_pair(path, FileInfo({info.fileName(), size, type, p}))).first; diff --git a/ui/models/messagefeed.cpp b/ui/models/messagefeed.cpp index 4187af8..d5fb3bc 100644 --- a/ui/models/messagefeed.cpp +++ b/ui/models/messagefeed.cpp @@ -532,20 +532,23 @@ QModelIndex Models::MessageFeed::modelIndexById(const QString& id) const QModelIndex Models::MessageFeed::modelIndexByTime(const QString& id, const QDateTime& time) const { - StorageByTime::const_iterator tItr = indexByTime.upper_bound(time); - StorageByTime::const_iterator tBeg = indexByTime.begin(); - bool found = false; - while (tItr != tBeg) { - if (id == (*tItr)->getId()) { - found = true; - break; + if (indexByTime.size() > 0) { + StorageByTime::const_iterator tItr = indexByTime.upper_bound(time); + StorageByTime::const_iterator tBeg = indexByTime.begin(); + StorageByTime::const_iterator tEnd = indexByTime.end(); + bool found = false; + while (tItr != tBeg) { + if (tItr != tEnd && id == (*tItr)->getId()) { + found = true; + break; + } + --tItr; + } + + if (found && tItr != tEnd && id == (*tItr)->getId()) { + int position = indexByTime.rank(tItr); + return createIndex(position, 0, *tItr); } - --tItr; - } - - if (found || id == (*tItr)->getId()) { - int position = indexByTime.rank(tItr); - return createIndex(position, 0, *tItr); } return QModelIndex(); diff --git a/ui/utils/messagedelegate.cpp b/ui/utils/messagedelegate.cpp index 8db024d..6b459f2 100644 --- a/ui/utils/messagedelegate.cpp +++ b/ui/utils/messagedelegate.cpp @@ -307,25 +307,48 @@ void MessageDelegate::paintBar(QProgressBar* bar, QPainter* painter, bool sentBy void MessageDelegate::paintPreview(const Models::FeedItem& data, QPainter* painter, QStyleOptionViewItem& option) const { Shared::Global::FileInfo info = Shared::Global::getFileInfo(data.attach.localPath); - if (info.preview == Shared::Global::FileInfo::Preview::picture) { - QSize size = constrainAttachSize(info.size, option.rect.size()); - - QPoint start; - if (data.sentByMe) { - start = {option.rect.width() - size.width(), option.rect.top()}; - start.rx() += margin; - } else { - start = option.rect.topLeft(); - } - QImage img(data.attach.localPath); - if (img.isNull()) { - emit invalidPath(data.id); - } else { - painter->drawImage(QRect(start, size), img); - } - - option.rect.adjust(0, size.height() + textMargin, 0, 0); + QSize size = constrainAttachSize(info.size, option.rect.size()); + + QPoint start; + if (data.sentByMe) { + start = {option.rect.width() - size.width(), option.rect.top()}; + start.rx() += margin; + } else { + start = option.rect.topLeft(); } + QRect rect(start, size); + switch (info.preview) { + case Shared::Global::FileInfo::Preview::picture: { + QImage img(data.attach.localPath); + if (img.isNull()) { + emit invalidPath(data.id); + } else { + painter->drawImage(rect, img); + } + } + break; + default: { + QIcon icon = QIcon::fromTheme(info.mime.iconName()); + + painter->save(); + + painter->setFont(bodyFont); + int labelWidth = option.rect.width() - size.width() - margin; + QString elidedName = bodyMetrics.elidedText(info.name, Qt::ElideMiddle, labelWidth); + QSize nameSize = bodyMetrics.boundingRect(QRect(start, QSize(labelWidth, 0)), 0, elidedName).size(); + if (data.sentByMe) { + start.rx() -= nameSize.width() + margin; + } + painter->drawPixmap({start, size}, icon.pixmap(info.size)); + start.rx() += size.width() + margin; + start.ry() += nameSize.height() + (size.height() - nameSize.height()) / 2; + painter->drawText(start, elidedName); + + painter->restore(); + } + } + + option.rect.adjust(0, size.height() + textMargin, 0, 0); } QPushButton * MessageDelegate::getButton(const Models::FeedItem& data) const From 6e06a1d5bc33046ee5641839f1738256da8d75ec Mon Sep 17 00:00:00 2001 From: vae Date: Tue, 11 May 2021 20:29:08 +0300 Subject: [PATCH 02/93] build: WIP CMakeLists refactoring --- CMakeLists.txt | 83 ++++++++++----------- core/CMakeLists.txt | 73 +++++++----------- core/account.h | 2 +- core/archive.h | 2 +- core/handlers/CMakeLists.txt | 6 ++ main.cpp => core/main.cpp | 18 ++--- core/passwordStorageEngines/CMakeLists.txt | 48 ++++-------- signalcatcher.cpp => core/signalcatcher.cpp | 0 signalcatcher.h => core/signalcatcher.h | 0 shared/CMakeLists.txt | 19 +++++ exception.cpp => shared/exception.cpp | 0 exception.h => shared/exception.h | 0 order.h => shared/order.h | 0 shared.h => shared/shared.h | 14 ++-- ui/CMakeLists.txt | 42 +---------- ui/models/CMakeLists.txt | 28 +++++++ ui/squawk.h | 2 +- ui/utils/CMakeLists.txt | 58 +++++++------- ui/widgets/CMakeLists.txt | 50 ++++++------- ui/widgets/conversation.h | 2 +- ui/widgets/vcard/CMakeLists.txt | 31 +++----- 21 files changed, 214 insertions(+), 264 deletions(-) create mode 100644 core/handlers/CMakeLists.txt rename main.cpp => core/main.cpp (98%) rename signalcatcher.cpp => core/signalcatcher.cpp (100%) rename signalcatcher.h => core/signalcatcher.h (100%) create mode 100644 shared/CMakeLists.txt rename exception.cpp => shared/exception.cpp (100%) rename exception.h => shared/exception.h (100%) rename order.h => shared/order.h (100%) rename shared.h => shared/shared.h (80%) create mode 100644 ui/models/CMakeLists.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index e88fdc8..e1b7f0c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,52 +1,41 @@ cmake_minimum_required(VERSION 3.4) -project(squawk) +project(squawk VERSION 0.1.6 LANGUAGES CXX) -set(CMAKE_INCLUDE_CURRENT_DIR ON) +cmake_policy(SET CMP0076 NEW) +cmake_policy(SET CMP0079 NEW) set(CMAKE_CXX_STANDARD 17) set(CMAKE_AUTOMOC ON) set(CMAKE_AUTOUIC ON) set(CMAKE_AUTORCC ON) -include(GNUInstallDirs) -include_directories(.) +add_executable(squawk) +target_include_directories(squawk PRIVATE ${CMAKE_SOURCE_DIR}) +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake") -find_package(Qt5Widgets CONFIG REQUIRED) +include(GNUInstallDirs) + +find_package(Qt5Widgets CONFIG REQUIRED COMPONENTS Widgets DBus Core) find_package(Qt5LinguistTools) +find_package(Qt5Core CONFIG REQUIRED) +find_package(Qt5Gui CONFIG REQUIRED) +find_package(Qt5Network CONFIG REQUIRED) +find_package(Qt5Xml CONFIG REQUIRED) +find_package(LMDB REQUIRED) if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Debug) endif() -set(CMAKE_CXX_FLAGS_DEBUG "-g -Wall -Wextra") -set(CMAKE_CXX_FLAGS_RELEASE "-O3") message("Build type: ${CMAKE_BUILD_TYPE}") - -set(squawk_SRC - main.cpp - exception.cpp - signalcatcher.cpp - shared/global.cpp - shared/utils.cpp - shared/message.cpp - shared/vcard.cpp - shared/icons.cpp - shared/messageinfo.cpp +target_compile_options(squawk PRIVATE + "-Wall;-Wextra" + "$<$:-g>" + "$<$:-O3>" ) -set(squawk_HEAD - exception.h - signalcatcher.h - shared.h - shared/enums.h - shared/message.h - shared/global.h - shared/utils.h - shared/vcard.h - shared/icons.h - shared/messageinfo.h -) +add_subdirectory(shared) configure_file(resources/images/logo.svg squawk.svg COPYONLY) execute_process(COMMAND convert -background none -size 48x48 squawk.svg squawk48.png WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) @@ -56,21 +45,22 @@ execute_process(COMMAND convert -background none -size 256x256 squawk.svg squawk configure_file(packaging/squawk.desktop squawk.desktop COPYONLY) -set(TS_FILES +set(TS_FILES translations/squawk.ru.ts ) qt5_add_translation(QM_FILES ${TS_FILES}) add_custom_target(translations ALL DEPENDS ${QM_FILES}) +qt5_use_modules(squawk LINK_PUBLIC Core Widgets) qt5_add_resources(RCC resources/resources.qrc) -option(SYSTEM_QXMPP "Use system qxmpp lib" ON) -option(WITH_KWALLET "Build KWallet support module" ON) -option(WITH_KIO "Build KIO support module" ON) +option(SYSTEM_QXMPP "Use system qxmpp lib" ON) +option(WITH_KWALLET "Build KWallet support module" ON) +option(WITH_KIO "Build KIO support module" ON) -if (SYSTEM_QXMPP) +if (SYSTEM_QXMPP) find_package(QXmpp CONFIG) - + if (NOT QXmpp_FOUND) set(SYSTEM_QXMPP OFF) message("QXmpp package wasn't found, trying to build with bundled QXmpp") @@ -85,7 +75,7 @@ endif() if (WITH_KWALLET) find_package(KF5Wallet CONFIG) - + if (NOT KF5Wallet_FOUND) set(WITH_KWALLET OFF) message("KWallet package wasn't found, KWallet support module wouldn't be built") @@ -95,12 +85,19 @@ if (WITH_KWALLET) endif() endif() -add_executable(squawk ${squawk_SRC} ${squawk_HEAD} ${RCC}) -target_link_libraries(squawk Qt5::Widgets) +target_sources(squawk PRIVATE ${RCC}) +target_link_libraries(squawk PRIVATE Qt5::Widgets) +target_link_libraries(squawk PRIVATE Qt5::DBus) +target_link_libraries(squawk PRIVATE Qt5::Network) +target_link_libraries(squawk PRIVATE Qt5::Gui) +target_link_libraries(squawk PRIVATE Qt5::Xml) +target_link_libraries(squawk PRIVATE qxmpp) +target_link_libraries(squawk PRIVATE lmdb) +target_link_libraries(squawk PRIVATE simpleCrypt) if (WITH_KIO) find_package(KF5KIO CONFIG) - + if (NOT KF5KIO_FOUND) set(WITH_KIO OFF) message("KIO package wasn't found, KIO support modules wouldn't be built") @@ -116,11 +113,7 @@ add_subdirectory(plugins) add_subdirectory(external/simpleCrypt) -target_link_libraries(squawk squawkUI) -target_link_libraries(squawk squawkCORE) -target_link_libraries(squawk uuid) - - +target_link_libraries(squawk PRIVATE uuid) add_dependencies(${CMAKE_PROJECT_NAME} translations) diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index f8aa267..3454204 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -1,49 +1,34 @@ -cmake_minimum_required(VERSION 3.3) -project(squawkCORE) - -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake") - -set(CMAKE_AUTOMOC ON) - -find_package(Qt5Core CONFIG REQUIRED) -find_package(Qt5Gui CONFIG REQUIRED) -find_package(Qt5Network CONFIG REQUIRED) -find_package(Qt5Xml CONFIG REQUIRED) -find_package(LMDB REQUIRED) - -set(squawkCORE_SRC - squawk.cpp - account.cpp - archive.cpp - rosteritem.cpp - contact.cpp - conference.cpp - urlstorage.cpp - networkaccess.cpp - adapterFuctions.cpp - handlers/messagehandler.cpp - handlers/rosterhandler.cpp -) +target_sources(squawk PRIVATE + account.cpp + account.h + adapterFuctions.cpp + archive.cpp + archive.h + conference.cpp + conference.h + contact.cpp + contact.h + main.cpp + networkaccess.cpp + networkaccess.h + rosteritem.cpp + rosteritem.h + signalcatcher.cpp + signalcatcher.h + squawk.cpp + squawk.h + storage.cpp + storage.h + urlstorage.cpp + urlstorage.h + ) +add_subdirectory(handlers) add_subdirectory(passwordStorageEngines) -# Tell CMake to create the helloworld executable -add_library(squawkCORE STATIC ${squawkCORE_SRC}) - - -if(SYSTEM_QXMPP) - get_target_property(QXMPP_INTERFACE_INCLUDE_DIRECTORIES QXmpp::QXmpp INTERFACE_INCLUDE_DIRECTORIES) - target_include_directories(squawkCORE PUBLIC ${QXMPP_INTERFACE_INCLUDE_DIRECTORIES}) -endif() +#if(SYSTEM_QXMPP) +# get_target_property(QXMPP_INTERFACE_INCLUDE_DIRECTORIES QXmpp::QXmpp INTERFACE_INCLUDE_DIRECTORIES) +# target_include_directories(squawk PRIVATE ${QXMPP_INTERFACE_INCLUDE_DIRECTORIES}) +#endif() # Use the Widgets module from Qt 5. -target_link_libraries(squawkCORE Qt5::Core) -target_link_libraries(squawkCORE Qt5::Network) -target_link_libraries(squawkCORE Qt5::Gui) -target_link_libraries(squawkCORE Qt5::Xml) -target_link_libraries(squawkCORE qxmpp) -target_link_libraries(squawkCORE lmdb) -target_link_libraries(squawkCORE simpleCrypt) -if (WITH_KWALLET) - target_link_libraries(squawkCORE kwalletPSE) -endif() diff --git a/core/account.h b/core/account.h index ce3b754..a0db9f9 100644 --- a/core/account.h +++ b/core/account.h @@ -43,7 +43,7 @@ #include #include -#include "shared.h" +#include "shared/shared.h" #include "contact.h" #include "conference.h" #include "networkaccess.h" diff --git a/core/archive.h b/core/archive.h index dd7a167..47c62dc 100644 --- a/core/archive.h +++ b/core/archive.h @@ -25,7 +25,7 @@ #include #include "shared/message.h" -#include "exception.h" +#include "shared/exception.h" #include #include diff --git a/core/handlers/CMakeLists.txt b/core/handlers/CMakeLists.txt new file mode 100644 index 0000000..ebae4b3 --- /dev/null +++ b/core/handlers/CMakeLists.txt @@ -0,0 +1,6 @@ +target_sources(squawk PRIVATE + messagehandler.cpp + messagehandler.h + rosterhandler.cpp + rosterhandler.h + ) \ No newline at end of file diff --git a/main.cpp b/core/main.cpp similarity index 98% rename from main.cpp rename to core/main.cpp index 210dd70..0090424 100644 --- a/main.cpp +++ b/core/main.cpp @@ -16,18 +16,18 @@ * along with this program. If not, see . */ -#include "ui/squawk.h" -#include "core/squawk.h" +#include "../shared/global.h" +#include "../shared/messageinfo.h" +#include "../ui/squawk.h" #include "signalcatcher.h" -#include "shared/global.h" -#include "shared/messageinfo.h" -#include -#include -#include -#include -#include +#include "squawk.h" #include +#include #include +#include +#include +#include +#include int main(int argc, char *argv[]) { diff --git a/core/passwordStorageEngines/CMakeLists.txt b/core/passwordStorageEngines/CMakeLists.txt index 735c0ad..7275d4f 100644 --- a/core/passwordStorageEngines/CMakeLists.txt +++ b/core/passwordStorageEngines/CMakeLists.txt @@ -1,37 +1,21 @@ -cmake_minimum_required(VERSION 3.3) -project(pse) +target_sources(squawk PRIVATE + wrappers/kwallet.cpp + kwallet.cpp + kwallet.h + ) -if (WITH_KWALLET) - set(CMAKE_AUTOMOC ON) +if (WITH_KWALLET) +# get_target_property(KWALLET_INTERFACE_INCLUDE_DIRECTORIES KF5::Wallet INTERFACE_INCLUDE_DIRECTORIES) +# get_target_property(Qt5GUI_INTERFACE_INCLUDE_DIRECTORIES Qt5::Gui INTERFACE_INCLUDE_DIRECTORIES) +# +# target_include_directories(squawk PRIVATE ${KWALLET_INTERFACE_INCLUDE_DIRECTORIES}) +# target_include_directories(squawk PRIVATE ${Qt5GUI_INTERFACE_INCLUDE_DIRECTORIES}) - find_package(Qt5Core CONFIG REQUIRED) - find_package(Qt5Gui CONFIG REQUIRED) + target_link_libraries(squawk PUBLIC Qt5::Core Qt5::Gui KF5::Wallet) - get_target_property(KWALLET_INTERFACE_INCLUDE_DIRECTORIES KF5::Wallet INTERFACE_INCLUDE_DIRECTORIES) - get_target_property(Qt5GUI_INTERFACE_INCLUDE_DIRECTORIES Qt5::Gui INTERFACE_INCLUDE_DIRECTORIES) +# target_include_directories(kwalletWrapper PUBLIC ${KWALLET_INTERFACE_INCLUDE_DIRECTORIES}) +# target_include_directories(kwalletWrapper PUBLIC ${Qt5GUI_INTERFACE_INCLUDE_DIRECTORIES}) - set(kwalletPSE_SRC - kwallet.cpp - ) - - add_library(kwalletPSE STATIC ${kwalletPSE_SRC}) - - target_include_directories(kwalletPSE PUBLIC ${KWALLET_INTERFACE_INCLUDE_DIRECTORIES}) - target_include_directories(kwalletPSE PUBLIC ${Qt5GUI_INTERFACE_INCLUDE_DIRECTORIES}) - - target_link_libraries(kwalletPSE Qt5::Core) - - set(kwalletW_SRC - wrappers/kwallet.cpp - ) - - add_library(kwalletWrapper SHARED ${kwalletW_SRC}) - - target_include_directories(kwalletWrapper PUBLIC ${KWALLET_INTERFACE_INCLUDE_DIRECTORIES}) - target_include_directories(kwalletWrapper PUBLIC ${Qt5GUI_INTERFACE_INCLUDE_DIRECTORIES}) - - target_link_libraries(kwalletWrapper KF5::Wallet) - target_link_libraries(kwalletWrapper Qt5::Core) - - install(TARGETS kwalletWrapper DESTINATION ${CMAKE_INSTALL_LIBDIR}) +# target_link_libraries(kwalletWrapper KF5::Wallet) +# target_link_libraries(kwalletWrapper Qt5::Core) endif() diff --git a/signalcatcher.cpp b/core/signalcatcher.cpp similarity index 100% rename from signalcatcher.cpp rename to core/signalcatcher.cpp diff --git a/signalcatcher.h b/core/signalcatcher.h similarity index 100% rename from signalcatcher.h rename to core/signalcatcher.h diff --git a/shared/CMakeLists.txt b/shared/CMakeLists.txt new file mode 100644 index 0000000..edd769a --- /dev/null +++ b/shared/CMakeLists.txt @@ -0,0 +1,19 @@ +target_sources(squawk PRIVATE + ${CMAKE_CURRENT_LIST_DIR}/enums.h + ${CMAKE_CURRENT_LIST_DIR}/global.cpp + ${CMAKE_CURRENT_LIST_DIR}/global.h + ${CMAKE_CURRENT_LIST_DIR}/exception.cpp + ${CMAKE_CURRENT_LIST_DIR}/exception.h + ${CMAKE_CURRENT_LIST_DIR}/icons.cpp + ${CMAKE_CURRENT_LIST_DIR}/icons.h + ${CMAKE_CURRENT_LIST_DIR}/message.cpp + ${CMAKE_CURRENT_LIST_DIR}/message.h + ${CMAKE_CURRENT_LIST_DIR}/messageinfo.cpp + ${CMAKE_CURRENT_LIST_DIR}/messageinfo.h + ${CMAKE_CURRENT_LIST_DIR}/order.h + ${CMAKE_CURRENT_LIST_DIR}/shared.h + ${CMAKE_CURRENT_LIST_DIR}/utils.cpp + ${CMAKE_CURRENT_LIST_DIR}/utils.h + ${CMAKE_CURRENT_LIST_DIR}/vcard.cpp + ${CMAKE_CURRENT_LIST_DIR}/vcard.h +) \ No newline at end of file diff --git a/exception.cpp b/shared/exception.cpp similarity index 100% rename from exception.cpp rename to shared/exception.cpp diff --git a/exception.h b/shared/exception.h similarity index 100% rename from exception.h rename to shared/exception.h diff --git a/order.h b/shared/order.h similarity index 100% rename from order.h rename to shared/order.h diff --git a/shared.h b/shared/shared.h similarity index 80% rename from shared.h rename to shared/shared.h index 3925ce2..1e86c5a 100644 --- a/shared.h +++ b/shared/shared.h @@ -19,12 +19,12 @@ #ifndef SHARED_H #define SHARED_H -#include "shared/enums.h" -#include "shared/utils.h" -#include "shared/icons.h" -#include "shared/message.h" -#include "shared/vcard.h" -#include "shared/global.h" -#include "shared/messageinfo.h" +#include "enums.h" +#include "global.h" +#include "icons.h" +#include "message.h" +#include "messageinfo.h" +#include "utils.h" +#include "vcard.h" #endif // SHARED_H diff --git a/ui/CMakeLists.txt b/ui/CMakeLists.txt index 11b8f3d..36207b6 100644 --- a/ui/CMakeLists.txt +++ b/ui/CMakeLists.txt @@ -1,43 +1,5 @@ -cmake_minimum_required(VERSION 3.3) -project(squawkUI) - -# Instruct CMake to run moc automatically when needed. -set(CMAKE_AUTOMOC ON) -# Instruct CMake to create code from Qt designer ui files -set(CMAKE_AUTOUIC ON) - -# Find the QtWidgets library -find_package(Qt5 CONFIG REQUIRED COMPONENTS Widgets DBus Core) -find_package(Boost 1.36.0 REQUIRED) -if(Boost_FOUND) - include_directories(${Boost_INCLUDE_DIRS}) -endif() +target_sources(squawk PRIVATE squawk.cpp squawk.h squawk.ui) +add_subdirectory(models) add_subdirectory(utils) add_subdirectory(widgets) - -set(squawkUI_SRC - squawk.cpp - models/accounts.cpp - models/roster.cpp - models/item.cpp - models/account.cpp - models/contact.cpp - models/presence.cpp - models/group.cpp - models/room.cpp - models/abstractparticipant.cpp - models/participant.cpp - models/reference.cpp - models/messagefeed.cpp - models/element.cpp -) - -# Tell CMake to create the helloworld executable -add_library(squawkUI STATIC ${squawkUI_SRC}) - -# Use the Widgets module from Qt 5. -target_link_libraries(squawkUI squawkWidgets) -target_link_libraries(squawkUI squawkUIUtils) -target_link_libraries(squawkUI Qt5::Widgets) -target_link_libraries(squawkUI Qt5::DBus) diff --git a/ui/models/CMakeLists.txt b/ui/models/CMakeLists.txt new file mode 100644 index 0000000..fcd80d9 --- /dev/null +++ b/ui/models/CMakeLists.txt @@ -0,0 +1,28 @@ +target_sources(squawk PRIVATE + ${CMAKE_CURRENT_LIST_DIR}/abstractparticipant.cpp + ${CMAKE_CURRENT_LIST_DIR}/abstractparticipant.h + ${CMAKE_CURRENT_LIST_DIR}/account.cpp + ${CMAKE_CURRENT_LIST_DIR}/account.h + ${CMAKE_CURRENT_LIST_DIR}/accounts.cpp + ${CMAKE_CURRENT_LIST_DIR}/accounts.h + ${CMAKE_CURRENT_LIST_DIR}/contact.cpp + ${CMAKE_CURRENT_LIST_DIR}/contact.h + ${CMAKE_CURRENT_LIST_DIR}/element.cpp + ${CMAKE_CURRENT_LIST_DIR}/element.h + ${CMAKE_CURRENT_LIST_DIR}/group.cpp + ${CMAKE_CURRENT_LIST_DIR}/group.h + ${CMAKE_CURRENT_LIST_DIR}/item.cpp + ${CMAKE_CURRENT_LIST_DIR}/item.h + ${CMAKE_CURRENT_LIST_DIR}/messagefeed.cpp + ${CMAKE_CURRENT_LIST_DIR}/messagefeed.h + ${CMAKE_CURRENT_LIST_DIR}/participant.cpp + ${CMAKE_CURRENT_LIST_DIR}/participant.h + ${CMAKE_CURRENT_LIST_DIR}/presence.cpp + ${CMAKE_CURRENT_LIST_DIR}/presence.h + ${CMAKE_CURRENT_LIST_DIR}/reference.cpp + ${CMAKE_CURRENT_LIST_DIR}/reference.h + ${CMAKE_CURRENT_LIST_DIR}/room.cpp + ${CMAKE_CURRENT_LIST_DIR}/room.h + ${CMAKE_CURRENT_LIST_DIR}/roster.cpp + ${CMAKE_CURRENT_LIST_DIR}/roster.h +) \ No newline at end of file diff --git a/ui/squawk.h b/ui/squawk.h index fa92df7..15d3f82 100644 --- a/ui/squawk.h +++ b/ui/squawk.h @@ -39,7 +39,7 @@ #include "models/roster.h" #include "widgets/vcard/vcard.h" -#include "shared.h" +#include "shared/shared.h" namespace Ui { class Squawk; diff --git a/ui/utils/CMakeLists.txt b/ui/utils/CMakeLists.txt index 0c33521..93eb4c7 100644 --- a/ui/utils/CMakeLists.txt +++ b/ui/utils/CMakeLists.txt @@ -1,32 +1,26 @@ -cmake_minimum_required(VERSION 3.3) -project(squawkUIUtils) - -# Instruct CMake to run moc automatically when needed. -set(CMAKE_AUTOMOC ON) -# Instruct CMake to create code from Qt designer ui files -set(CMAKE_AUTOUIC ON) - -# Find the QtWidgets library -find_package(Qt5 CONFIG REQUIRED COMPONENTS Widgets Core) - -set(squawkUIUtils_SRC -# messageline.cpp -# message.cpp - resizer.cpp -# image.cpp - flowlayout.cpp - badge.cpp - progress.cpp - comboboxdelegate.cpp - feedview.cpp - messagedelegate.cpp - exponentialblur.cpp - shadowoverlay.cpp -) - -# Tell CMake to create the helloworld executable -add_library(squawkUIUtils STATIC ${squawkUIUtils_SRC}) - -# Use the Widgets module from Qt 5. -target_link_libraries(squawkUIUtils squawkWidgets) -target_link_libraries(squawkUIUtils Qt5::Widgets) +target_sources(squawk PRIVATE + ${CMAKE_CURRENT_LIST_DIR}/badge.cpp + ${CMAKE_CURRENT_LIST_DIR}/badge.h + ${CMAKE_CURRENT_LIST_DIR}/comboboxdelegate.cpp + ${CMAKE_CURRENT_LIST_DIR}/comboboxdelegate.h + ${CMAKE_CURRENT_LIST_DIR}/exponentialblur.cpp + ${CMAKE_CURRENT_LIST_DIR}/exponentialblur.h + ${CMAKE_CURRENT_LIST_DIR}/feedview.cpp + ${CMAKE_CURRENT_LIST_DIR}/feedview.h + ${CMAKE_CURRENT_LIST_DIR}/flowlayout.cpp + ${CMAKE_CURRENT_LIST_DIR}/flowlayout.h + ${CMAKE_CURRENT_LIST_DIR}/image.cpp + ${CMAKE_CURRENT_LIST_DIR}/image.h + ${CMAKE_CURRENT_LIST_DIR}/message.cpp + ${CMAKE_CURRENT_LIST_DIR}/message.h + ${CMAKE_CURRENT_LIST_DIR}/messagedelegate.cpp + ${CMAKE_CURRENT_LIST_DIR}/messagedelegate.h + ${CMAKE_CURRENT_LIST_DIR}/messageline.cpp + ${CMAKE_CURRENT_LIST_DIR}/messageline.h + ${CMAKE_CURRENT_LIST_DIR}/progress.cpp + ${CMAKE_CURRENT_LIST_DIR}/progress.h + ${CMAKE_CURRENT_LIST_DIR}/resizer.cpp + ${CMAKE_CURRENT_LIST_DIR}/resizer.h + ${CMAKE_CURRENT_LIST_DIR}/shadowoverlay.cpp + ${CMAKE_CURRENT_LIST_DIR}/shadowoverlay.h +) \ No newline at end of file diff --git a/ui/widgets/CMakeLists.txt b/ui/widgets/CMakeLists.txt index 78b4f1a..dd1bf95 100644 --- a/ui/widgets/CMakeLists.txt +++ b/ui/widgets/CMakeLists.txt @@ -1,31 +1,23 @@ -cmake_minimum_required(VERSION 3.0) -project(squawkWidgets) - -# Instruct CMake to run moc automatically when needed. -set(CMAKE_AUTOMOC ON) -# Instruct CMake to create code from Qt designer ui files -set(CMAKE_AUTOUIC ON) - -# Find the QtWidgets library -find_package(Qt5Widgets CONFIG REQUIRED COMPONENTS Widgets Core) +target_sources(squawk PRIVATE + ${CMAKE_CURRENT_LIST_DIR}/account.cpp + ${CMAKE_CURRENT_LIST_DIR}/account.h + ${CMAKE_CURRENT_LIST_DIR}/account.ui + ${CMAKE_CURRENT_LIST_DIR}/accounts.cpp + ${CMAKE_CURRENT_LIST_DIR}/accounts.h + ${CMAKE_CURRENT_LIST_DIR}/accounts.ui + ${CMAKE_CURRENT_LIST_DIR}/chat.cpp + ${CMAKE_CURRENT_LIST_DIR}/chat.h + ${CMAKE_CURRENT_LIST_DIR}/conversation.cpp + ${CMAKE_CURRENT_LIST_DIR}/conversation.h + ${CMAKE_CURRENT_LIST_DIR}/conversation.ui + ${CMAKE_CURRENT_LIST_DIR}/joinconference.cpp + ${CMAKE_CURRENT_LIST_DIR}/joinconference.h + ${CMAKE_CURRENT_LIST_DIR}/joinconference.ui + ${CMAKE_CURRENT_LIST_DIR}/newcontact.cpp + ${CMAKE_CURRENT_LIST_DIR}/newcontact.h + ${CMAKE_CURRENT_LIST_DIR}/newcontact.ui + ${CMAKE_CURRENT_LIST_DIR}/room.cpp + ${CMAKE_CURRENT_LIST_DIR}/room.h + ) add_subdirectory(vcard) - -set(squawkWidgets_SRC - conversation.cpp - chat.cpp - room.cpp - newcontact.cpp - accounts.cpp - account.cpp - joinconference.cpp -) - -add_library(squawkWidgets STATIC ${squawkWidgets_SRC}) - -# Use the Widgets module from Qt 5. -target_link_libraries(squawkWidgets vCardUI) -target_link_libraries(squawkWidgets squawkUIUtils) -target_link_libraries(squawkWidgets Qt5::Widgets) - -qt5_use_modules(squawkWidgets Core Widgets) diff --git a/ui/widgets/conversation.h b/ui/widgets/conversation.h index 690a51c..0b0dcb2 100644 --- a/ui/widgets/conversation.h +++ b/ui/widgets/conversation.h @@ -30,7 +30,7 @@ #include #include "shared/message.h" -#include "order.h" +#include "shared/order.h" #include "ui/models/account.h" #include "ui/models/roster.h" #include "ui/utils/flowlayout.h" diff --git a/ui/widgets/vcard/CMakeLists.txt b/ui/widgets/vcard/CMakeLists.txt index 73b157c..c5c53a3 100644 --- a/ui/widgets/vcard/CMakeLists.txt +++ b/ui/widgets/vcard/CMakeLists.txt @@ -1,22 +1,9 @@ -cmake_minimum_required(VERSION 3.0) -project(vCardUI) - -# Instruct CMake to run moc automatically when needed. -set(CMAKE_AUTOMOC ON) -# Instruct CMake to create code from Qt designer ui files -set(CMAKE_AUTOUIC ON) - -# Find the QtWidgets library -find_package(Qt5Widgets CONFIG REQUIRED) - -set(vCardUI_SRC - vcard.cpp - emailsmodel.cpp - phonesmodel.cpp -) - -# Tell CMake to create the helloworld executable -add_library(vCardUI STATIC ${vCardUI_SRC}) - -# Use the Widgets module from Qt 5. -target_link_libraries(vCardUI Qt5::Widgets) +target_sources(squawk PRIVATE + ${CMAKE_CURRENT_LIST_DIR}/emailsmodel.cpp + ${CMAKE_CURRENT_LIST_DIR}/emailsmodel.h + ${CMAKE_CURRENT_LIST_DIR}/phonesmodel.cpp + ${CMAKE_CURRENT_LIST_DIR}/phonesmodel.h + ${CMAKE_CURRENT_LIST_DIR}/vcard.cpp + ${CMAKE_CURRENT_LIST_DIR}/vcard.h + ${CMAKE_CURRENT_LIST_DIR}/vcard.ui + ) From 0038aca1f6a44f91ae503de78ea625c76eaea74f Mon Sep 17 00:00:00 2001 From: vae Date: Tue, 11 May 2021 21:35:12 +0300 Subject: [PATCH 03/93] build: WIP CMakeLists refactoring continue - add FindSignal --- CMakeLists.txt | 120 +++++++++++---------- cmake/FindSignal.cmake | 15 +++ core/CMakeLists.txt | 7 -- core/handlers/CMakeLists.txt | 2 +- core/passwordStorageEngines/CMakeLists.txt | 26 ++--- plugins/CMakeLists.txt | 30 +----- shared/CMakeLists.txt | 36 +++---- ui/models/CMakeLists.txt | 54 +++++----- ui/utils/CMakeLists.txt | 50 ++++----- ui/widgets/CMakeLists.txt | 38 +++---- ui/widgets/vcard/CMakeLists.txt | 14 +-- 11 files changed, 188 insertions(+), 204 deletions(-) create mode 100644 cmake/FindSignal.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index e1b7f0c..bf6e062 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,13 +15,77 @@ set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake") include(GNUInstallDirs) +option(SYSTEM_QXMPP "Use system qxmpp lib" ON) +option(WITH_KWALLET "Build KWallet support module" ON) +option(WITH_KIO "Build KIO support module" ON) + +# Dependencies + +## Qt find_package(Qt5Widgets CONFIG REQUIRED COMPONENTS Widgets DBus Core) -find_package(Qt5LinguistTools) find_package(Qt5Core CONFIG REQUIRED) find_package(Qt5Gui CONFIG REQUIRED) find_package(Qt5Network CONFIG REQUIRED) find_package(Qt5Xml CONFIG REQUIRED) +find_package(Qt5LinguistTools) + find_package(LMDB REQUIRED) +find_package(Signal REQUIRED) + +## QXmpp +if (SYSTEM_QXMPP) + find_package(QXmpp CONFIG) + + if (NOT QXmpp_FOUND) + set(SYSTEM_QXMPP OFF) + message("QXmpp package wasn't found, trying to build with bundled QXmpp") + else() + message("Building with system QXmpp") + endif() +endif() + +if(NOT SYSTEM_QXMPP) + add_subdirectory(external/qxmpp) +endif() + +## KIO +if (WITH_KIO) + find_package(KF5KIO CONFIG) + + if (NOT KF5KIO_FOUND) + set(WITH_KIO OFF) + message("KIO package wasn't found, KIO support modules wouldn't be built") + else() + add_definitions(-DWITH_KIO) + message("Building with support of KIO") + endif() +endif() + +## KWallet +if (WITH_KWALLET) + find_package(KF5Wallet CONFIG) + + if (NOT KF5Wallet_FOUND) + set(WITH_KWALLET OFF) + message("KWallet package wasn't found, KWallet support module wouldn't be built") + else() + add_definitions(-DWITH_KWALLET) + message("Building with support of KWallet") + endif() +endif() + +# Linking +target_link_libraries(squawk PRIVATE Qt5::Widgets) +target_link_libraries(squawk PRIVATE Qt5::DBus) +target_link_libraries(squawk PRIVATE Qt5::Network) +target_link_libraries(squawk PRIVATE Qt5::Gui) +target_link_libraries(squawk PRIVATE Qt5::Xml) +target_link_libraries(squawk PRIVATE qxmpp) +target_link_libraries(squawk PRIVATE lmdb) +target_link_libraries(squawk PRIVATE simpleCrypt) +target_link_libraries(squawk PRIVATE uuid) + +# Build type if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Debug) @@ -51,61 +115,9 @@ set(TS_FILES qt5_add_translation(QM_FILES ${TS_FILES}) add_custom_target(translations ALL DEPENDS ${QM_FILES}) -qt5_use_modules(squawk LINK_PUBLIC Core Widgets) qt5_add_resources(RCC resources/resources.qrc) -option(SYSTEM_QXMPP "Use system qxmpp lib" ON) -option(WITH_KWALLET "Build KWallet support module" ON) -option(WITH_KIO "Build KIO support module" ON) - -if (SYSTEM_QXMPP) - find_package(QXmpp CONFIG) - - if (NOT QXmpp_FOUND) - set(SYSTEM_QXMPP OFF) - message("QXmpp package wasn't found, trying to build with bundled QXmpp") - else() - message("Building with system QXmpp") - endif() -endif() - -if(NOT SYSTEM_QXMPP) - add_subdirectory(external/qxmpp) -endif() - -if (WITH_KWALLET) - find_package(KF5Wallet CONFIG) - - if (NOT KF5Wallet_FOUND) - set(WITH_KWALLET OFF) - message("KWallet package wasn't found, KWallet support module wouldn't be built") - else() - add_definitions(-DWITH_KWALLET) - message("Building with support of KWallet") - endif() -endif() - target_sources(squawk PRIVATE ${RCC}) -target_link_libraries(squawk PRIVATE Qt5::Widgets) -target_link_libraries(squawk PRIVATE Qt5::DBus) -target_link_libraries(squawk PRIVATE Qt5::Network) -target_link_libraries(squawk PRIVATE Qt5::Gui) -target_link_libraries(squawk PRIVATE Qt5::Xml) -target_link_libraries(squawk PRIVATE qxmpp) -target_link_libraries(squawk PRIVATE lmdb) -target_link_libraries(squawk PRIVATE simpleCrypt) - -if (WITH_KIO) - find_package(KF5KIO CONFIG) - - if (NOT KF5KIO_FOUND) - set(WITH_KIO OFF) - message("KIO package wasn't found, KIO support modules wouldn't be built") - else() - add_definitions(-DWITH_KIO) - message("Building with support of KIO") - endif() -endif() add_subdirectory(ui) add_subdirectory(core) @@ -113,8 +125,6 @@ add_subdirectory(plugins) add_subdirectory(external/simpleCrypt) -target_link_libraries(squawk PRIVATE uuid) - add_dependencies(${CMAKE_PROJECT_NAME} translations) # Install the executable diff --git a/cmake/FindSignal.cmake b/cmake/FindSignal.cmake new file mode 100644 index 0000000..752fed7 --- /dev/null +++ b/cmake/FindSignal.cmake @@ -0,0 +1,15 @@ +find_path(Signal_INCLUDE_DIR NAMES signal/signal_protocol.h) +find_library(Signal_LIBRARY signal-protocol-c) +mark_as_advanced(Signal_INCLUDE_DIR Signal_LIBRARY) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(Signal REQUIRED_VARS Signal_LIBRARY Signal_INCLUDE_DIR) + +if (Signal_FOUND AND NOT TARGET Signal::Signal) + add_library(Signal::Signal UNKNOWN IMPORTED) + set_target_properties(Signal::Signal PROPERTIES + IMPORTED_LINK_INTERFACE_LANGUAGES "C" + IMPORTED_LOCATION "${Signal_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${Signal_INCLUDE_DIR}" + ) +endif () diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index 3454204..3b160e2 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -25,10 +25,3 @@ target_sources(squawk PRIVATE add_subdirectory(handlers) add_subdirectory(passwordStorageEngines) - -#if(SYSTEM_QXMPP) -# get_target_property(QXMPP_INTERFACE_INCLUDE_DIRECTORIES QXmpp::QXmpp INTERFACE_INCLUDE_DIRECTORIES) -# target_include_directories(squawk PRIVATE ${QXMPP_INTERFACE_INCLUDE_DIRECTORIES}) -#endif() - -# Use the Widgets module from Qt 5. diff --git a/core/handlers/CMakeLists.txt b/core/handlers/CMakeLists.txt index ebae4b3..6da2ef3 100644 --- a/core/handlers/CMakeLists.txt +++ b/core/handlers/CMakeLists.txt @@ -3,4 +3,4 @@ target_sources(squawk PRIVATE messagehandler.h rosterhandler.cpp rosterhandler.h - ) \ No newline at end of file + ) diff --git a/core/passwordStorageEngines/CMakeLists.txt b/core/passwordStorageEngines/CMakeLists.txt index 7275d4f..da2834c 100644 --- a/core/passwordStorageEngines/CMakeLists.txt +++ b/core/passwordStorageEngines/CMakeLists.txt @@ -1,21 +1,9 @@ -target_sources(squawk PRIVATE - wrappers/kwallet.cpp - kwallet.cpp - kwallet.h - ) - if (WITH_KWALLET) -# get_target_property(KWALLET_INTERFACE_INCLUDE_DIRECTORIES KF5::Wallet INTERFACE_INCLUDE_DIRECTORIES) -# get_target_property(Qt5GUI_INTERFACE_INCLUDE_DIRECTORIES Qt5::Gui INTERFACE_INCLUDE_DIRECTORIES) -# -# target_include_directories(squawk PRIVATE ${KWALLET_INTERFACE_INCLUDE_DIRECTORIES}) -# target_include_directories(squawk PRIVATE ${Qt5GUI_INTERFACE_INCLUDE_DIRECTORIES}) + target_sources(squawk PRIVATE + wrappers/kwallet.cpp + kwallet.cpp + kwallet.h + ) - target_link_libraries(squawk PUBLIC Qt5::Core Qt5::Gui KF5::Wallet) - -# target_include_directories(kwalletWrapper PUBLIC ${KWALLET_INTERFACE_INCLUDE_DIRECTORIES}) -# target_include_directories(kwalletWrapper PUBLIC ${Qt5GUI_INTERFACE_INCLUDE_DIRECTORIES}) - -# target_link_libraries(kwalletWrapper KF5::Wallet) -# target_link_libraries(kwalletWrapper Qt5::Core) -endif() + target_link_libraries(squawk PUBLIC KF5::Wallet) +endif () diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 69a5e94..97b3b46 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -1,26 +1,4 @@ -cmake_minimum_required(VERSION 3.3) -project(plugins) - -if (WITH_KIO) - set(CMAKE_AUTOMOC ON) - - find_package(Qt5Core CONFIG REQUIRED) - - set(openFileManagerWindowJob_SRC - openfilemanagerwindowjob.cpp - ) - - add_library(openFileManagerWindowJob SHARED ${openFileManagerWindowJob_SRC}) - - get_target_property(Qt5CORE_INTERFACE_INCLUDE_DIRECTORIES Qt5::Core INTERFACE_INCLUDE_DIRECTORIES) - get_target_property(KIO_WIDGETS_INTERFACE_INCLUDE_DIRECTORIES KF5::KIOWidgets INTERFACE_INCLUDE_DIRECTORIES) - get_target_property(CORE_ADDONS_INTERFACE_INCLUDE_DIRECTORIES KF5::CoreAddons INTERFACE_INCLUDE_DIRECTORIES) - target_include_directories(openFileManagerWindowJob PUBLIC ${KIO_WIDGETS_INTERFACE_INCLUDE_DIRECTORIES}) - target_include_directories(openFileManagerWindowJob PUBLIC ${CORE_ADDONS_INTERFACE_INCLUDE_DIRECTORIES}) - target_include_directories(openFileManagerWindowJob PUBLIC ${Qt5CORE_INTERFACE_INCLUDE_DIRECTORIES}) - - target_link_libraries(openFileManagerWindowJob KF5::KIOWidgets) - target_link_libraries(openFileManagerWindowJob Qt5::Core) - - install(TARGETS openFileManagerWindowJob DESTINATION ${CMAKE_INSTALL_LIBDIR}) -endif() +if (WITH_KIO) + target_sources(squawk PRIVATE openfilemanagerwindowjob.cpp) + target_link_libraries(squawk PRIVATE KF5::KIOWidgets) +endif () diff --git a/shared/CMakeLists.txt b/shared/CMakeLists.txt index edd769a..a36b516 100644 --- a/shared/CMakeLists.txt +++ b/shared/CMakeLists.txt @@ -1,19 +1,19 @@ target_sources(squawk PRIVATE - ${CMAKE_CURRENT_LIST_DIR}/enums.h - ${CMAKE_CURRENT_LIST_DIR}/global.cpp - ${CMAKE_CURRENT_LIST_DIR}/global.h - ${CMAKE_CURRENT_LIST_DIR}/exception.cpp - ${CMAKE_CURRENT_LIST_DIR}/exception.h - ${CMAKE_CURRENT_LIST_DIR}/icons.cpp - ${CMAKE_CURRENT_LIST_DIR}/icons.h - ${CMAKE_CURRENT_LIST_DIR}/message.cpp - ${CMAKE_CURRENT_LIST_DIR}/message.h - ${CMAKE_CURRENT_LIST_DIR}/messageinfo.cpp - ${CMAKE_CURRENT_LIST_DIR}/messageinfo.h - ${CMAKE_CURRENT_LIST_DIR}/order.h - ${CMAKE_CURRENT_LIST_DIR}/shared.h - ${CMAKE_CURRENT_LIST_DIR}/utils.cpp - ${CMAKE_CURRENT_LIST_DIR}/utils.h - ${CMAKE_CURRENT_LIST_DIR}/vcard.cpp - ${CMAKE_CURRENT_LIST_DIR}/vcard.h -) \ No newline at end of file + enums.h + global.cpp + global.h + exception.cpp + exception.h + icons.cpp + icons.h + message.cpp + message.h + messageinfo.cpp + messageinfo.h + order.h + shared.h + utils.cpp + utils.h + vcard.cpp + vcard.h + ) diff --git a/ui/models/CMakeLists.txt b/ui/models/CMakeLists.txt index fcd80d9..98ef1c3 100644 --- a/ui/models/CMakeLists.txt +++ b/ui/models/CMakeLists.txt @@ -1,28 +1,28 @@ target_sources(squawk PRIVATE - ${CMAKE_CURRENT_LIST_DIR}/abstractparticipant.cpp - ${CMAKE_CURRENT_LIST_DIR}/abstractparticipant.h - ${CMAKE_CURRENT_LIST_DIR}/account.cpp - ${CMAKE_CURRENT_LIST_DIR}/account.h - ${CMAKE_CURRENT_LIST_DIR}/accounts.cpp - ${CMAKE_CURRENT_LIST_DIR}/accounts.h - ${CMAKE_CURRENT_LIST_DIR}/contact.cpp - ${CMAKE_CURRENT_LIST_DIR}/contact.h - ${CMAKE_CURRENT_LIST_DIR}/element.cpp - ${CMAKE_CURRENT_LIST_DIR}/element.h - ${CMAKE_CURRENT_LIST_DIR}/group.cpp - ${CMAKE_CURRENT_LIST_DIR}/group.h - ${CMAKE_CURRENT_LIST_DIR}/item.cpp - ${CMAKE_CURRENT_LIST_DIR}/item.h - ${CMAKE_CURRENT_LIST_DIR}/messagefeed.cpp - ${CMAKE_CURRENT_LIST_DIR}/messagefeed.h - ${CMAKE_CURRENT_LIST_DIR}/participant.cpp - ${CMAKE_CURRENT_LIST_DIR}/participant.h - ${CMAKE_CURRENT_LIST_DIR}/presence.cpp - ${CMAKE_CURRENT_LIST_DIR}/presence.h - ${CMAKE_CURRENT_LIST_DIR}/reference.cpp - ${CMAKE_CURRENT_LIST_DIR}/reference.h - ${CMAKE_CURRENT_LIST_DIR}/room.cpp - ${CMAKE_CURRENT_LIST_DIR}/room.h - ${CMAKE_CURRENT_LIST_DIR}/roster.cpp - ${CMAKE_CURRENT_LIST_DIR}/roster.h -) \ No newline at end of file + abstractparticipant.cpp + abstractparticipant.h + account.cpp + account.h + accounts.cpp + accounts.h + contact.cpp + contact.h + element.cpp + element.h + group.cpp + group.h + item.cpp + item.h + messagefeed.cpp + messagefeed.h + participant.cpp + participant.h + presence.cpp + presence.h + reference.cpp + reference.h + room.cpp + room.h + roster.cpp + roster.h + ) \ No newline at end of file diff --git a/ui/utils/CMakeLists.txt b/ui/utils/CMakeLists.txt index 93eb4c7..5ad5cb7 100644 --- a/ui/utils/CMakeLists.txt +++ b/ui/utils/CMakeLists.txt @@ -1,26 +1,26 @@ target_sources(squawk PRIVATE - ${CMAKE_CURRENT_LIST_DIR}/badge.cpp - ${CMAKE_CURRENT_LIST_DIR}/badge.h - ${CMAKE_CURRENT_LIST_DIR}/comboboxdelegate.cpp - ${CMAKE_CURRENT_LIST_DIR}/comboboxdelegate.h - ${CMAKE_CURRENT_LIST_DIR}/exponentialblur.cpp - ${CMAKE_CURRENT_LIST_DIR}/exponentialblur.h - ${CMAKE_CURRENT_LIST_DIR}/feedview.cpp - ${CMAKE_CURRENT_LIST_DIR}/feedview.h - ${CMAKE_CURRENT_LIST_DIR}/flowlayout.cpp - ${CMAKE_CURRENT_LIST_DIR}/flowlayout.h - ${CMAKE_CURRENT_LIST_DIR}/image.cpp - ${CMAKE_CURRENT_LIST_DIR}/image.h - ${CMAKE_CURRENT_LIST_DIR}/message.cpp - ${CMAKE_CURRENT_LIST_DIR}/message.h - ${CMAKE_CURRENT_LIST_DIR}/messagedelegate.cpp - ${CMAKE_CURRENT_LIST_DIR}/messagedelegate.h - ${CMAKE_CURRENT_LIST_DIR}/messageline.cpp - ${CMAKE_CURRENT_LIST_DIR}/messageline.h - ${CMAKE_CURRENT_LIST_DIR}/progress.cpp - ${CMAKE_CURRENT_LIST_DIR}/progress.h - ${CMAKE_CURRENT_LIST_DIR}/resizer.cpp - ${CMAKE_CURRENT_LIST_DIR}/resizer.h - ${CMAKE_CURRENT_LIST_DIR}/shadowoverlay.cpp - ${CMAKE_CURRENT_LIST_DIR}/shadowoverlay.h -) \ No newline at end of file + badge.cpp + badge.h + comboboxdelegate.cpp + comboboxdelegate.h + exponentialblur.cpp + exponentialblur.h + feedview.cpp + feedview.h + flowlayout.cpp + flowlayout.h + image.cpp + image.h + message.cpp + message.h + messagedelegate.cpp + messagedelegate.h + messageline.cpp + messageline.h + progress.cpp + progress.h + resizer.cpp + resizer.h + shadowoverlay.cpp + shadowoverlay.h + ) diff --git a/ui/widgets/CMakeLists.txt b/ui/widgets/CMakeLists.txt index dd1bf95..0cacf6f 100644 --- a/ui/widgets/CMakeLists.txt +++ b/ui/widgets/CMakeLists.txt @@ -1,23 +1,23 @@ target_sources(squawk PRIVATE - ${CMAKE_CURRENT_LIST_DIR}/account.cpp - ${CMAKE_CURRENT_LIST_DIR}/account.h - ${CMAKE_CURRENT_LIST_DIR}/account.ui - ${CMAKE_CURRENT_LIST_DIR}/accounts.cpp - ${CMAKE_CURRENT_LIST_DIR}/accounts.h - ${CMAKE_CURRENT_LIST_DIR}/accounts.ui - ${CMAKE_CURRENT_LIST_DIR}/chat.cpp - ${CMAKE_CURRENT_LIST_DIR}/chat.h - ${CMAKE_CURRENT_LIST_DIR}/conversation.cpp - ${CMAKE_CURRENT_LIST_DIR}/conversation.h - ${CMAKE_CURRENT_LIST_DIR}/conversation.ui - ${CMAKE_CURRENT_LIST_DIR}/joinconference.cpp - ${CMAKE_CURRENT_LIST_DIR}/joinconference.h - ${CMAKE_CURRENT_LIST_DIR}/joinconference.ui - ${CMAKE_CURRENT_LIST_DIR}/newcontact.cpp - ${CMAKE_CURRENT_LIST_DIR}/newcontact.h - ${CMAKE_CURRENT_LIST_DIR}/newcontact.ui - ${CMAKE_CURRENT_LIST_DIR}/room.cpp - ${CMAKE_CURRENT_LIST_DIR}/room.h + account.cpp + account.h + account.ui + accounts.cpp + accounts.h + accounts.ui + chat.cpp + chat.h + conversation.cpp + conversation.h + conversation.ui + joinconference.cpp + joinconference.h + joinconference.ui + newcontact.cpp + newcontact.h + newcontact.ui + room.cpp + room.h ) add_subdirectory(vcard) diff --git a/ui/widgets/vcard/CMakeLists.txt b/ui/widgets/vcard/CMakeLists.txt index c5c53a3..51cbaab 100644 --- a/ui/widgets/vcard/CMakeLists.txt +++ b/ui/widgets/vcard/CMakeLists.txt @@ -1,9 +1,9 @@ target_sources(squawk PRIVATE - ${CMAKE_CURRENT_LIST_DIR}/emailsmodel.cpp - ${CMAKE_CURRENT_LIST_DIR}/emailsmodel.h - ${CMAKE_CURRENT_LIST_DIR}/phonesmodel.cpp - ${CMAKE_CURRENT_LIST_DIR}/phonesmodel.h - ${CMAKE_CURRENT_LIST_DIR}/vcard.cpp - ${CMAKE_CURRENT_LIST_DIR}/vcard.h - ${CMAKE_CURRENT_LIST_DIR}/vcard.ui + emailsmodel.cpp + emailsmodel.h + phonesmodel.cpp + phonesmodel.h + vcard.cpp + vcard.h + vcard.ui ) From 7d2688151c2dcef21acfbff7b2a4911965b855b3 Mon Sep 17 00:00:00 2001 From: vae Date: Tue, 11 May 2021 22:21:25 +0300 Subject: [PATCH 04/93] build: finish up CMakeLists refactoring --- CMakeLists.txt | 76 +++++++--------------- core/passwordStorageEngines/CMakeLists.txt | 2 +- external/simpleCrypt/CMakeLists.txt | 12 +--- packaging/CMakeLists.txt | 3 + resources/CMakeLists.txt | 14 ++++ translations/CMakeLists.txt | 8 +++ 6 files changed, 51 insertions(+), 64 deletions(-) create mode 100644 packaging/CMakeLists.txt create mode 100644 resources/CMakeLists.txt create mode 100644 translations/CMakeLists.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index bf6e062..fc6ed1b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,28 +9,19 @@ set(CMAKE_AUTOMOC ON) set(CMAKE_AUTOUIC ON) set(CMAKE_AUTORCC ON) -add_executable(squawk) -target_include_directories(squawk PRIVATE ${CMAKE_SOURCE_DIR}) +include(GNUInstallDirs) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake") -include(GNUInstallDirs) +add_executable(squawk) +target_include_directories(squawk PRIVATE ${CMAKE_SOURCE_DIR}) option(SYSTEM_QXMPP "Use system qxmpp lib" ON) option(WITH_KWALLET "Build KWallet support module" ON) option(WITH_KIO "Build KIO support module" ON) # Dependencies - ## Qt -find_package(Qt5Widgets CONFIG REQUIRED COMPONENTS Widgets DBus Core) -find_package(Qt5Core CONFIG REQUIRED) -find_package(Qt5Gui CONFIG REQUIRED) -find_package(Qt5Network CONFIG REQUIRED) -find_package(Qt5Xml CONFIG REQUIRED) -find_package(Qt5LinguistTools) - -find_package(LMDB REQUIRED) -find_package(Signal REQUIRED) +find_package(Qt5 COMPONENTS Widgets DBus Gui Xml Network Core REQUIRED) ## QXmpp if (SYSTEM_QXMPP) @@ -45,7 +36,10 @@ if (SYSTEM_QXMPP) endif() if(NOT SYSTEM_QXMPP) + target_link_libraries(squawk PRIVATE qxmpp) add_subdirectory(external/qxmpp) +else() + target_link_libraries(squawk PRIVATE QXmpp::QXmpp) endif() ## KIO @@ -56,7 +50,7 @@ if (WITH_KIO) set(WITH_KIO OFF) message("KIO package wasn't found, KIO support modules wouldn't be built") else() - add_definitions(-DWITH_KIO) + target_compile_definitions(squawk PRIVATE WITH_KIO) message("Building with support of KIO") endif() endif() @@ -69,24 +63,24 @@ if (WITH_KWALLET) set(WITH_KWALLET OFF) message("KWallet package wasn't found, KWallet support module wouldn't be built") else() - add_definitions(-DWITH_KWALLET) + target_compile_definitions(squawk PRIVATE WITH_KWALLET) message("Building with support of KWallet") endif() endif() +## Signal (TODO) +# find_package(Signal REQUIRED) + +## LMDB +find_package(LMDB REQUIRED) + # Linking -target_link_libraries(squawk PRIVATE Qt5::Widgets) -target_link_libraries(squawk PRIVATE Qt5::DBus) -target_link_libraries(squawk PRIVATE Qt5::Network) -target_link_libraries(squawk PRIVATE Qt5::Gui) -target_link_libraries(squawk PRIVATE Qt5::Xml) -target_link_libraries(squawk PRIVATE qxmpp) +target_link_libraries(squawk PRIVATE Qt5::Core Qt5::Widgets Qt5::DBus Qt5::Network Qt5::Gui Qt5::Xml) target_link_libraries(squawk PRIVATE lmdb) target_link_libraries(squawk PRIVATE simpleCrypt) target_link_libraries(squawk PRIVATE uuid) # Build type - if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Debug) endif() @@ -99,40 +93,14 @@ target_compile_options(squawk PRIVATE "$<$:-O3>" ) -add_subdirectory(shared) - -configure_file(resources/images/logo.svg squawk.svg COPYONLY) -execute_process(COMMAND convert -background none -size 48x48 squawk.svg squawk48.png WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) -execute_process(COMMAND convert -background none -size 64x64 squawk.svg squawk64.png WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) -execute_process(COMMAND convert -background none -size 128x128 squawk.svg squawk128.png WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) -execute_process(COMMAND convert -background none -size 256x256 squawk.svg squawk256.png WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) - -configure_file(packaging/squawk.desktop squawk.desktop COPYONLY) - -set(TS_FILES - translations/squawk.ru.ts -) -qt5_add_translation(QM_FILES ${TS_FILES}) -add_custom_target(translations ALL DEPENDS ${QM_FILES}) - -qt5_add_resources(RCC resources/resources.qrc) - -target_sources(squawk PRIVATE ${RCC}) - -add_subdirectory(ui) add_subdirectory(core) -add_subdirectory(plugins) - add_subdirectory(external/simpleCrypt) - -add_dependencies(${CMAKE_PROJECT_NAME} translations) +add_subdirectory(packaging) +add_subdirectory(plugins) +add_subdirectory(resources) +add_subdirectory(shared) +add_subdirectory(translations) +add_subdirectory(ui) # Install the executable install(TARGETS squawk DESTINATION ${CMAKE_INSTALL_BINDIR}) -install(FILES ${QM_FILES} DESTINATION ${CMAKE_INSTALL_DATADIR}/squawk/l10n) -install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk.svg DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/scalable/apps) -install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk48.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/48x48/apps RENAME squawk.png) -install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk64.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/64x64/apps RENAME squawk.png) -install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk128.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/128x128/apps RENAME squawk.png) -install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk256.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/256x256/apps RENAME squawk.png) -install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk.desktop DESTINATION ${CMAKE_INSTALL_DATADIR}/applications) diff --git a/core/passwordStorageEngines/CMakeLists.txt b/core/passwordStorageEngines/CMakeLists.txt index da2834c..7cab516 100644 --- a/core/passwordStorageEngines/CMakeLists.txt +++ b/core/passwordStorageEngines/CMakeLists.txt @@ -5,5 +5,5 @@ if (WITH_KWALLET) kwallet.h ) - target_link_libraries(squawk PUBLIC KF5::Wallet) + target_link_libraries(squawk PRIVATE KF5::Wallet) endif () diff --git a/external/simpleCrypt/CMakeLists.txt b/external/simpleCrypt/CMakeLists.txt index 88f5d23..274d304 100644 --- a/external/simpleCrypt/CMakeLists.txt +++ b/external/simpleCrypt/CMakeLists.txt @@ -1,16 +1,10 @@ cmake_minimum_required(VERSION 3.0) -project(simplecrypt) +project(simplecrypt LANGUAGES CXX) set(CMAKE_AUTOMOC ON) -find_package(Qt5Core CONFIG REQUIRED) +find_package(Qt5 COMPONENTS Core REQUIRED) -set(simplecrypt_SRC - simplecrypt.cpp -) +add_library(simpleCrypt STATIC simplecrypt.cpp simplecrypt.h) -# Tell CMake to create the helloworld executable -add_library(simpleCrypt STATIC ${simplecrypt_SRC}) - -# Use the Widgets module from Qt 5. target_link_libraries(simpleCrypt Qt5::Core) diff --git a/packaging/CMakeLists.txt b/packaging/CMakeLists.txt new file mode 100644 index 0000000..4965b37 --- /dev/null +++ b/packaging/CMakeLists.txt @@ -0,0 +1,3 @@ +configure_file(squawk.desktop squawk.desktop COPYONLY) + +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk.desktop DESTINATION ${CMAKE_INSTALL_DATADIR}/applications) \ No newline at end of file diff --git a/resources/CMakeLists.txt b/resources/CMakeLists.txt new file mode 100644 index 0000000..86433f3 --- /dev/null +++ b/resources/CMakeLists.txt @@ -0,0 +1,14 @@ +target_sources(squawk PRIVATE resources.qrc) + +configure_file(images/logo.svg squawk.svg COPYONLY) + +execute_process(COMMAND convert -background none -size 48x48 squawk.svg squawk48.png WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) +execute_process(COMMAND convert -background none -size 64x64 squawk.svg squawk64.png WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) +execute_process(COMMAND convert -background none -size 128x128 squawk.svg squawk128.png WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) +execute_process(COMMAND convert -background none -size 256x256 squawk.svg squawk256.png WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk.svg DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/scalable/apps) +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk48.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/48x48/apps RENAME squawk.png) +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk64.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/64x64/apps RENAME squawk.png) +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk128.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/128x128/apps RENAME squawk.png) +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk256.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/256x256/apps RENAME squawk.png) diff --git a/translations/CMakeLists.txt b/translations/CMakeLists.txt new file mode 100644 index 0000000..c484000 --- /dev/null +++ b/translations/CMakeLists.txt @@ -0,0 +1,8 @@ +find_package(Qt5LinguistTools) + +set(TS_FILES squawk.ru.ts) +qt5_add_translation(QM_FILES ${TS_FILES}) +add_custom_target(translations ALL DEPENDS ${QM_FILES}) +install(FILES ${QM_FILES} DESTINATION ${CMAKE_INSTALL_DATADIR}/squawk/l10n) + +add_dependencies(${CMAKE_PROJECT_NAME} translations) \ No newline at end of file From a184ecafa31d73a55fd314d0eeff8bd03004f757 Mon Sep 17 00:00:00 2001 From: vae Date: Tue, 11 May 2021 22:24:55 +0300 Subject: [PATCH 05/93] build: reformat cmake code --- CMakeLists.txt | 30 +++++++++++++++--------------- cmake/FindLMDB.cmake | 28 ++++++++++++++-------------- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index fc6ed1b..b9349d9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,17 +30,17 @@ if (SYSTEM_QXMPP) if (NOT QXmpp_FOUND) set(SYSTEM_QXMPP OFF) message("QXmpp package wasn't found, trying to build with bundled QXmpp") - else() + else () message("Building with system QXmpp") - endif() -endif() + endif () +endif () -if(NOT SYSTEM_QXMPP) +if (NOT SYSTEM_QXMPP) target_link_libraries(squawk PRIVATE qxmpp) add_subdirectory(external/qxmpp) -else() +else () target_link_libraries(squawk PRIVATE QXmpp::QXmpp) -endif() +endif () ## KIO if (WITH_KIO) @@ -49,11 +49,11 @@ if (WITH_KIO) if (NOT KF5KIO_FOUND) set(WITH_KIO OFF) message("KIO package wasn't found, KIO support modules wouldn't be built") - else() + else () target_compile_definitions(squawk PRIVATE WITH_KIO) message("Building with support of KIO") - endif() -endif() + endif () +endif () ## KWallet if (WITH_KWALLET) @@ -62,11 +62,11 @@ if (WITH_KWALLET) if (NOT KF5Wallet_FOUND) set(WITH_KWALLET OFF) message("KWallet package wasn't found, KWallet support module wouldn't be built") - else() + else () target_compile_definitions(squawk PRIVATE WITH_KWALLET) message("Building with support of KWallet") - endif() -endif() + endif () +endif () ## Signal (TODO) # find_package(Signal REQUIRED) @@ -81,9 +81,9 @@ target_link_libraries(squawk PRIVATE simpleCrypt) target_link_libraries(squawk PRIVATE uuid) # Build type -if(NOT CMAKE_BUILD_TYPE) +if (NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Debug) -endif() +endif () message("Build type: ${CMAKE_BUILD_TYPE}") @@ -91,7 +91,7 @@ target_compile_options(squawk PRIVATE "-Wall;-Wextra" "$<$:-g>" "$<$:-O3>" -) + ) add_subdirectory(core) add_subdirectory(external/simpleCrypt) diff --git a/cmake/FindLMDB.cmake b/cmake/FindLMDB.cmake index 8bf48b4..79788f1 100644 --- a/cmake/FindLMDB.cmake +++ b/cmake/FindLMDB.cmake @@ -21,27 +21,27 @@ # LMDB_INCLUDE_DIRS The location of LMDB headers. find_path(LMDB_ROOT_DIR - NAMES include/lmdb.h -) + NAMES include/lmdb.h + ) find_library(LMDB_LIBRARIES - NAMES lmdb - HINTS ${LMDB_ROOT_DIR}/lib -) + NAMES lmdb + HINTS ${LMDB_ROOT_DIR}/lib + ) find_path(LMDB_INCLUDE_DIRS - NAMES lmdb.h - HINTS ${LMDB_ROOT_DIR}/include -) + NAMES lmdb.h + HINTS ${LMDB_ROOT_DIR}/include + ) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(LMDB DEFAULT_MSG - LMDB_LIBRARIES - LMDB_INCLUDE_DIRS -) + LMDB_LIBRARIES + LMDB_INCLUDE_DIRS + ) mark_as_advanced( - LMDB_ROOT_DIR - LMDB_LIBRARIES - LMDB_INCLUDE_DIRS + LMDB_ROOT_DIR + LMDB_LIBRARIES + LMDB_INCLUDE_DIRS ) From 8e99cc29692bd83fb9ca164df445a4cff52dbdc4 Mon Sep 17 00:00:00 2001 From: vae Date: Wed, 12 May 2021 02:01:02 +0300 Subject: [PATCH 06/93] build: plugins/, passwordStorageEngines/wrappers/ as shared libs --- core/passwordStorageEngines/CMakeLists.txt | 4 ++-- core/passwordStorageEngines/wrappers/CMakeLists.txt | 2 ++ plugins/CMakeLists.txt | 4 ++-- 3 files changed, 6 insertions(+), 4 deletions(-) create mode 100644 core/passwordStorageEngines/wrappers/CMakeLists.txt diff --git a/core/passwordStorageEngines/CMakeLists.txt b/core/passwordStorageEngines/CMakeLists.txt index 7cab516..4da3873 100644 --- a/core/passwordStorageEngines/CMakeLists.txt +++ b/core/passwordStorageEngines/CMakeLists.txt @@ -1,9 +1,9 @@ if (WITH_KWALLET) target_sources(squawk PRIVATE - wrappers/kwallet.cpp kwallet.cpp kwallet.h ) - target_link_libraries(squawk PRIVATE KF5::Wallet) + add_subdirectory(wrappers) + target_include_directories(squawk PRIVATE $) endif () diff --git a/core/passwordStorageEngines/wrappers/CMakeLists.txt b/core/passwordStorageEngines/wrappers/CMakeLists.txt new file mode 100644 index 0000000..6d486c0 --- /dev/null +++ b/core/passwordStorageEngines/wrappers/CMakeLists.txt @@ -0,0 +1,2 @@ +add_library(kwalletWrapper SHARED kwallet.cpp) +target_link_libraries(kwalletWrapper PRIVATE KF5::Wallet) diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 97b3b46..84fc09b 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -1,4 +1,4 @@ if (WITH_KIO) - target_sources(squawk PRIVATE openfilemanagerwindowjob.cpp) - target_link_libraries(squawk PRIVATE KF5::KIOWidgets) + add_library(openFileManagerWindowJob SHARED openfilemanagerwindowjob.cpp) + target_link_libraries(openFileManagerWindowJob PRIVATE KF5::KIOWidgets) endif () From 4307262f6eb4a85b8996cf3bae91ea63976e1b7d Mon Sep 17 00:00:00 2001 From: blue Date: Fri, 14 May 2021 22:49:38 +0300 Subject: [PATCH 07/93] basic error download/upload files handling, need more testing --- core/networkaccess.cpp | 76 +++++++++++++++----- core/networkaccess.h | 1 + ui/models/messagefeed.cpp | 132 ++++++++++++++++++++++++++++------- ui/models/messagefeed.h | 6 +- ui/utils/feedview.cpp | 9 +-- ui/utils/feedview.h | 2 +- ui/utils/messagedelegate.cpp | 54 ++++++++------ ui/utils/messagedelegate.h | 3 +- 8 files changed, 206 insertions(+), 77 deletions(-) diff --git a/core/networkaccess.cpp b/core/networkaccess.cpp index eece379..69fe812 100644 --- a/core/networkaccess.cpp +++ b/core/networkaccess.cpp @@ -70,6 +70,9 @@ void Core::NetworkAccess::start() { if (!running) { manager = new QNetworkAccessManager(); +#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) + manager->setTransferTimeout(); +#endif storage.open(); running = true; } @@ -99,31 +102,56 @@ void Core::NetworkAccess::onDownloadProgress(qint64 bytesReceived, qint64 bytesT qDebug() << "an error downloading" << url << ": the request had some progress but seems like no one is waiting for it, skipping"; } else { Transfer* dwn = itr->second; - qreal received = bytesReceived; - qreal total = bytesTotal; - qreal progress = received/total; - dwn->progress = progress; - emit loadFileProgress(dwn->messages, progress, false); + if (dwn->success) { + qreal received = bytesReceived; + qreal total = bytesTotal; + qreal progress = received/total; + dwn->progress = progress; + emit loadFileProgress(dwn->messages, progress, false); + } } } void Core::NetworkAccess::onDownloadError(QNetworkReply::NetworkError code) { + qDebug() << "DEBUG: DOWNLOAD ERROR"; QNetworkReply* rpl = static_cast(sender()); + qDebug() << rpl->errorString(); QString url = rpl->url().toString(); std::map::const_iterator itr = downloads.find(url); if (itr == downloads.end()) { qDebug() << "an error downloading" << url << ": the request is reporting an error but seems like no one is waiting for it, skipping"; } else { QString errorText = getErrorText(code); - if (errorText.size() > 0) { + //if (errorText.size() > 0) { itr->second->success = false; Transfer* dwn = itr->second; emit loadFileError(dwn->messages, errorText, false); - } + //} } } +void Core::NetworkAccess::onDownloadSSLError(const QList& errors) +{ + qDebug() << "DEBUG: DOWNLOAD SSL ERRORS"; + for (const QSslError& err : errors) { + qDebug() << err.errorString(); + } + QNetworkReply* rpl = static_cast(sender()); + QString url = rpl->url().toString(); + std::map::const_iterator itr = downloads.find(url); + if (itr == downloads.end()) { + qDebug() << "an SSL error downloading" << url << ": the request is reporting an error but seems like no one is waiting for it, skipping"; + } else { + //if (errorText.size() > 0) { + itr->second->success = false; + Transfer* dwn = itr->second; + emit loadFileError(dwn->messages, "SSL errors occured", false); + //} + } +} + + QString Core::NetworkAccess::getErrorText(QNetworkReply::NetworkError code) { QString errorText(""); @@ -146,7 +174,11 @@ QString Core::NetworkAccess::getErrorText(QNetworkReply::NetworkError code) errorText = "Connection was closed because it timed out"; break; case QNetworkReply::OperationCanceledError: - //this means I closed it myself by abort() or close(), don't think I need to notify here + //this means I closed it myself by abort() or close() + //I don't call them directory, but this is the error code + //Qt returns when it can not resume donwload after the network failure + //or when the download is canceled by the timout; + errorText = "Connection lost"; break; case QNetworkReply::SslHandshakeFailedError: errorText = "Security error"; //TODO need to handle sslErrors signal to get a better description here @@ -247,6 +279,7 @@ QString Core::NetworkAccess::getErrorText(QNetworkReply::NetworkError code) void Core::NetworkAccess::onDownloadFinished() { + qDebug() << "DEBUG: DOWNLOAD FINISHED"; QNetworkReply* rpl = static_cast(sender()); QString url = rpl->url().toString(); std::map::const_iterator itr = downloads.find(url); @@ -256,11 +289,14 @@ void Core::NetworkAccess::onDownloadFinished() Transfer* dwn = itr->second; if (dwn->success) { qDebug() << "download success for" << url; + QString err; QStringList hops = url.split("/"); QString fileName = hops.back(); QString jid; if (dwn->messages.size() > 0) { jid = dwn->messages.front().jid; + } else { + qDebug() << "An attempt to save the file but it doesn't seem to belong to any message, download is definately going to be broken"; } QString path = prepareDirectory(jid); if (path.size() > 0) { @@ -274,15 +310,16 @@ void Core::NetworkAccess::onDownloadFinished() qDebug() << "file" << path << "was successfully downloaded"; } else { qDebug() << "couldn't save file" << path; - path = QString(); + err = "Error opening file to write:" + file.errorString(); } + } else { + err = "Couldn't prepare a directory for file"; } if (path.size() > 0) { emit downloadFileComplete(dwn->messages, path); } else { - //TODO do I need to handle the failure here or it's already being handled in error? - //emit loadFileError(dwn->messages, path, false); + emit loadFileError(dwn->messages, "Error saving file " + url + "; " + err, false); } } @@ -298,6 +335,7 @@ void Core::NetworkAccess::startDownload(const std::list& ms QNetworkRequest req(url); dwn->reply = manager->get(req); connect(dwn->reply, &QNetworkReply::downloadProgress, this, &NetworkAccess::onDownloadProgress); + connect(dwn->reply, &QNetworkReply::sslErrors, this, &NetworkAccess::onDownloadSSLError); #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) connect(dwn->reply, qOverload(&QNetworkReply::errorOccurred), this, &NetworkAccess::onDownloadError); #else @@ -317,11 +355,11 @@ void Core::NetworkAccess::onUploadError(QNetworkReply::NetworkError code) qDebug() << "an error uploading" << url << ": the request is reporting an error but there is no record of it being uploading, ignoring"; } else { QString errorText = getErrorText(code); - if (errorText.size() > 0) { + //if (errorText.size() > 0) { itr->second->success = false; Transfer* upl = itr->second; emit loadFileError(upl->messages, errorText, true); - } + //} //TODO deletion? } @@ -360,11 +398,13 @@ void Core::NetworkAccess::onUploadProgress(qint64 bytesReceived, qint64 bytesTot qDebug() << "an error downloading" << url << ": the request had some progress but seems like no one is waiting for it, skipping"; } else { Transfer* upl = itr->second; - qreal received = bytesReceived; - qreal total = bytesTotal; - qreal progress = received/total; - upl->progress = progress; - emit loadFileProgress(upl->messages, progress, true); + if (upl->success) { + qreal received = bytesReceived; + qreal total = bytesTotal; + qreal progress = received/total; + upl->progress = progress; + emit loadFileProgress(upl->messages, progress, true); + } } } diff --git a/core/networkaccess.h b/core/networkaccess.h index 5b9eae2..75c189c 100644 --- a/core/networkaccess.h +++ b/core/networkaccess.h @@ -75,6 +75,7 @@ private: private slots: void onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal); void onDownloadError(QNetworkReply::NetworkError code); + void onDownloadSSLError(const QList &errors); void onDownloadFinished(); void onUploadProgress(qint64 bytesReceived, qint64 bytesTotal); void onUploadError(QNetworkReply::NetworkError code); diff --git a/ui/models/messagefeed.cpp b/ui/models/messagefeed.cpp index d5fb3bc..c64d7ab 100644 --- a/ui/models/messagefeed.cpp +++ b/ui/models/messagefeed.cpp @@ -45,6 +45,8 @@ Models::MessageFeed::MessageFeed(const Element* ri, QObject* parent): syncState(incomplete), uploads(), downloads(), + failedDownloads(), + failedUploads(), unreadMessages(new std::set()), observersAmount(0) { @@ -142,6 +144,18 @@ void Models::MessageFeed::changeMessage(const QString& id, const QMap cr; for (MessageRoles role : changeRoles) { cr.push_back(role); @@ -421,6 +435,7 @@ bool Models::MessageFeed::sentByMe(const Shared::Message& msg) const Models::Attachment Models::MessageFeed::fillAttach(const Shared::Message& msg) const { ::Models::Attachment att; + QString id = msg.getId(); att.localPath = msg.getAttachPath(); att.remotePath = msg.getOutOfBandUrl(); @@ -429,22 +444,34 @@ Models::Attachment Models::MessageFeed::fillAttach(const Shared::Message& msg) c if (att.localPath.size() == 0) { att.state = none; } else { - Progress::const_iterator itr = uploads.find(msg.getId()); - if (itr == uploads.end()) { - att.state = local; + Err::const_iterator eitr = failedUploads.find(id); + if (eitr != failedUploads.end()) { + att.state = errorUpload; + att.error = eitr->second; } else { - att.state = uploading; - att.progress = itr->second; + Progress::const_iterator itr = uploads.find(id); + if (itr == uploads.end()) { + att.state = local; + } else { + att.state = uploading; + att.progress = itr->second; + } } } } else { if (att.localPath.size() == 0) { - Progress::const_iterator itr = downloads.find(msg.getId()); - if (itr == downloads.end()) { - att.state = remote; + Err::const_iterator eitr = failedDownloads.find(id); + if (eitr != failedDownloads.end()) { + att.state = errorDownload; + att.error = eitr->second; } else { - att.state = downloading; - att.progress = itr->second; + Progress::const_iterator itr = downloads.find(id); + if (itr == downloads.end()) { + att.state = remote; + } else { + att.state = downloading; + att.progress = itr->second; + } } } else { att.state = ready; @@ -456,12 +483,19 @@ Models::Attachment Models::MessageFeed::fillAttach(const Shared::Message& msg) c void Models::MessageFeed::downloadAttachment(const QString& messageId) { + bool notify = false; + Err::const_iterator eitr = failedDownloads.find(messageId); + if (eitr != failedDownloads.end()) { + failedDownloads.erase(eitr); + notify = true; + } + QModelIndex ind = modelIndexById(messageId); if (ind.isValid()) { std::pair progressPair = downloads.insert(std::make_pair(messageId, 0)); if (progressPair.second) { //Only to take action if we weren't already downloading it Shared::Message* msg = static_cast(ind.internalPointer()); - emit dataChanged(ind, ind, {MessageRoles::Attach}); + notify = true; emit fileDownloadRequest(msg->getOutOfBandUrl()); } else { qDebug() << "Attachment download for message with id" << messageId << "is already in progress, skipping"; @@ -469,32 +503,55 @@ void Models::MessageFeed::downloadAttachment(const QString& messageId) } else { qDebug() << "An attempt to download an attachment for the message that doesn't exist. ID:" << messageId; } -} - -void Models::MessageFeed::uploadAttachment(const QString& messageId) -{ - qDebug() << "request to upload attachment of the message" << messageId; + + if (notify) { + emit dataChanged(ind, ind, {MessageRoles::Attach}); + } } bool Models::MessageFeed::registerUpload(const QString& messageId) { - return uploads.insert(std::make_pair(messageId, 0)).second; + bool success = uploads.insert(std::make_pair(messageId, 0)).second; + + QVector roles({}); + Err::const_iterator eitr = failedUploads.find(messageId); + if (eitr != failedUploads.end()) { + failedUploads.erase(eitr); + roles.push_back(MessageRoles::Attach); + } else if (success) { + roles.push_back(MessageRoles::Attach); + } + + QModelIndex ind = modelIndexById(messageId); + emit dataChanged(ind, ind, roles); + + return success; } void Models::MessageFeed::fileProgress(const QString& messageId, qreal value, bool up) { Progress* pr = 0; + Err* err = 0; if (up) { pr = &uploads; + err = &failedUploads; } else { pr = &downloads; + err = &failedDownloads; + } + + QVector roles({}); + Err::const_iterator eitr = err->find(messageId); + if (eitr != err->end() && value != 1) { //like I want to clear this state when the download is started anew + err->erase(eitr); + roles.push_back(MessageRoles::Attach); } Progress::iterator itr = pr->find(messageId); if (itr != pr->end()) { itr->second = value; QModelIndex ind = modelIndexById(messageId); - emit dataChanged(ind, ind); //the type of the attach didn't change, so, there is no need to relayout, there is no role in event + emit dataChanged(ind, ind, roles); } } @@ -505,7 +562,29 @@ void Models::MessageFeed::fileComplete(const QString& messageId, bool up) void Models::MessageFeed::fileError(const QString& messageId, const QString& error, bool up) { - //TODO + Err* failed; + Progress* loads; + if (up) { + failed = &failedUploads; + loads = &uploads; + } else { + failed = &failedDownloads; + loads = &downloads; + } + + Progress::iterator pitr = loads->find(messageId); + if (pitr != loads->end()) { + loads->erase(pitr); + } + + std::pair pair = failed->insert(std::make_pair(messageId, error)); + if (!pair.second) { + pair.first->second = error; + } + QModelIndex ind = modelIndexById(messageId); + if (ind.isValid()) { + emit dataChanged(ind, ind, {MessageRoles::Attach}); + } } void Models::MessageFeed::incrementObservers() @@ -533,19 +612,18 @@ QModelIndex Models::MessageFeed::modelIndexById(const QString& id) const QModelIndex Models::MessageFeed::modelIndexByTime(const QString& id, const QDateTime& time) const { if (indexByTime.size() > 0) { - StorageByTime::const_iterator tItr = indexByTime.upper_bound(time); - StorageByTime::const_iterator tBeg = indexByTime.begin(); - StorageByTime::const_iterator tEnd = indexByTime.end(); + StorageByTime::const_iterator tItr = indexByTime.lower_bound(time); + StorageByTime::const_iterator tEnd = indexByTime.upper_bound(time); bool found = false; - while (tItr != tBeg) { - if (tItr != tEnd && id == (*tItr)->getId()) { + while (tItr != tEnd) { + if (id == (*tItr)->getId()) { found = true; break; } - --tItr; + ++tItr; } - if (found && tItr != tEnd && id == (*tItr)->getId()) { + if (found) { int position = indexByTime.rank(tItr); return createIndex(position, 0, *tItr); } @@ -566,7 +644,7 @@ void Models::MessageFeed::reportLocalPathInvalid(const QString& messageId) emit localPathInvalid(msg->getAttachPath()); - //gonna change the message in current model right away, to prevent spam on each attemt to draw element + //gonna change the message in current model right away, to prevent spam on each attempt to draw element QModelIndex index = modelIndexByTime(messageId, msg->getTime()); msg->setAttachPath(""); diff --git a/ui/models/messagefeed.h b/ui/models/messagefeed.h index abf67ee..efb005a 100644 --- a/ui/models/messagefeed.h +++ b/ui/models/messagefeed.h @@ -65,7 +65,6 @@ public: void responseArchive(const std::list list, bool last); void downloadAttachment(const QString& messageId); - void uploadAttachment(const QString& messageId); bool registerUpload(const QString& messageId); void reportLocalPathInvalid(const QString& messageId); @@ -148,12 +147,16 @@ private: SyncState syncState; typedef std::map Progress; + typedef std::map Err; Progress uploads; Progress downloads; + Err failedDownloads; + Err failedUploads; std::set* unreadMessages; uint16_t observersAmount; + static const QHash roles; }; @@ -173,6 +176,7 @@ struct Attachment { qreal progress; QString localPath; QString remotePath; + QString error; }; struct FeedItem { diff --git a/ui/utils/feedview.cpp b/ui/utils/feedview.cpp index 22ef4c4..5f515aa 100644 --- a/ui/utils/feedview.cpp +++ b/ui/utils/feedview.cpp @@ -394,16 +394,11 @@ void FeedView::setModel(QAbstractItemModel* p_model) } } -void FeedView::onMessageButtonPushed(const QString& messageId, bool download) +void FeedView::onMessageButtonPushed(const QString& messageId) { if (specialModel) { Models::MessageFeed* feed = static_cast(model()); - - if (download) { - feed->downloadAttachment(messageId); - } else { - feed->uploadAttachment(messageId); - } + feed->downloadAttachment(messageId); } } diff --git a/ui/utils/feedview.h b/ui/utils/feedview.h index 0b7e7d9..f5509fd 100644 --- a/ui/utils/feedview.h +++ b/ui/utils/feedview.h @@ -58,7 +58,7 @@ protected slots: void rowsInserted(const QModelIndex & parent, int start, int end) override; void verticalScrollbarValueChanged(int value) override; void dataChanged(const QModelIndex & topLeft, const QModelIndex & bottomRight, const QVector & roles) override; - void onMessageButtonPushed(const QString& messageId, bool download); + void onMessageButtonPushed(const QString& messageId); void onMessageInvalidPath(const QString& messageId); void onModelSyncStateChange(Models::MessageFeed::SyncState state); diff --git a/ui/utils/messagedelegate.cpp b/ui/utils/messagedelegate.cpp index 6b459f2..0381ae3 100644 --- a/ui/utils/messagedelegate.cpp +++ b/ui/utils/messagedelegate.cpp @@ -137,19 +137,39 @@ void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio clearHelperWidget(data); //i can't imagine the situation where it's gonna be needed break; //but it's a possible performance problem case Models::uploading: + paintPreview(data, painter, opt); case Models::downloading: paintBar(getBar(data), painter, data.sentByMe, opt); break; case Models::remote: - case Models::local: paintButton(getButton(data), painter, data.sentByMe, opt); break; case Models::ready: + case Models::local: clearHelperWidget(data); paintPreview(data, painter, opt); break; - case Models::errorDownload: - case Models::errorUpload: + case Models::errorDownload: { + paintButton(getButton(data), painter, data.sentByMe, opt); + painter->setFont(dateFont); + QColor q = painter->pen().color(); + q.setAlpha(180); + painter->setPen(q); + painter->drawText(opt.rect, opt.displayAlignment, data.attach.error, &rect); + opt.rect.adjust(0, rect.height() + textMargin, 0, 0); + } + + break; + case Models::errorUpload:{ + clearHelperWidget(data); + paintPreview(data, painter, opt); + painter->setFont(dateFont); + QColor q = painter->pen().color(); + q.setAlpha(180); + painter->setPen(q); + painter->drawText(opt.rect, opt.displayAlignment, data.attach.error, &rect); + opt.rect.adjust(0, rect.height() + textMargin, 0, 0); + } break; } painter->restore(); @@ -212,18 +232,24 @@ QSize MessageDelegate::sizeHint(const QStyleOptionViewItem& option, const QModel case Models::none: break; case Models::uploading: + messageSize.rheight() += calculateAttachSize(attach.localPath, messageRect).height() + textMargin; case Models::downloading: messageSize.rheight() += barHeight + textMargin; break; case Models::remote: - case Models::local: messageSize.rheight() += buttonHeight + textMargin; break; case Models::ready: + case Models::local: messageSize.rheight() += calculateAttachSize(attach.localPath, messageRect).height() + textMargin; break; case Models::errorDownload: + messageSize.rheight() += buttonHeight + textMargin; + messageSize.rheight() += dateMetrics.boundingRect(messageRect, Qt::TextWordWrap, attach.error).size().height() + textMargin; + break; case Models::errorUpload: + messageSize.rheight() += calculateAttachSize(attach.localPath, messageRect).height() + textMargin; + messageSize.rheight() += dateMetrics.boundingRect(messageRect, Qt::TextWordWrap, attach.error).size().height() + textMargin; break; } @@ -356,15 +382,7 @@ QPushButton * MessageDelegate::getButton(const Models::FeedItem& data) const std::map::const_iterator itr = buttons->find(data.id); FeedButton* result = 0; if (itr != buttons->end()) { - if ( - (data.attach.state == Models::remote && itr->second->download) || - (data.attach.state == Models::local && !itr->second->download) - ) { - result = itr->second; - } else { - delete itr->second; - buttons->erase(itr); - } + result = itr->second; } else { std::map::const_iterator barItr = bars->find(data.id); if (barItr != bars->end()) { @@ -376,13 +394,7 @@ QPushButton * MessageDelegate::getButton(const Models::FeedItem& data) const if (result == 0) { result = new FeedButton(); result->messageId = data.id; - if (data.attach.state == Models::remote) { - result->setText(QCoreApplication::translate("MessageLine", "Download")); - result->download = true; - } else { - result->setText(QCoreApplication::translate("MessageLine", "Upload")); - result->download = false; - } + result->setText(QCoreApplication::translate("MessageLine", "Download")); buttons->insert(std::make_pair(data.id, result)); connect(result, &QPushButton::clicked, this, &MessageDelegate::onButtonPushed); } @@ -529,7 +541,7 @@ void MessageDelegate::endClearWidgets() void MessageDelegate::onButtonPushed() const { FeedButton* btn = static_cast(sender()); - emit buttonPushed(btn->messageId, btn->download); + emit buttonPushed(btn->messageId); } void MessageDelegate::clearHelperWidget(const Models::FeedItem& data) const diff --git a/ui/utils/messagedelegate.h b/ui/utils/messagedelegate.h index 6a257b7..3af80e1 100644 --- a/ui/utils/messagedelegate.h +++ b/ui/utils/messagedelegate.h @@ -55,7 +55,7 @@ public: void beginClearWidgets(); signals: - void buttonPushed(const QString& messageId, bool download) const; + void buttonPushed(const QString& messageId) const; void invalidPath(const QString& messageId) const; protected: @@ -77,7 +77,6 @@ private: class FeedButton : public QPushButton { public: QString messageId; - bool download; }; QFont bodyFont; From 0d584c5aba04889854838f7e06e70f9ea1e1cd40 Mon Sep 17 00:00:00 2001 From: blue Date: Sun, 16 May 2021 01:07:49 +0300 Subject: [PATCH 08/93] message preview refactor, several bugs about label size, animations are now playing in previews --- shared/global.cpp | 13 +- shared/global.h | 3 +- ui/models/CMakeLists.txt | 4 +- ui/models/element.h | 3 +- ui/utils/CMakeLists.txt | 8 - ui/widgets/CMakeLists.txt | 1 + ui/widgets/conversation.h | 17 +- ui/widgets/messageline/CMakeLists.txt | 14 + .../messageline}/feedview.cpp | 2 +- ui/{utils => widgets/messageline}/feedview.h | 4 +- ui/{utils => widgets/messageline}/message.cpp | 0 ui/{utils => widgets/messageline}/message.h | 0 .../messageline}/messagedelegate.cpp | 199 ++++-------- .../messageline}/messagedelegate.h | 6 +- .../messageline}/messagefeed.cpp | 5 +- .../messageline}/messagefeed.h | 0 .../messageline}/messageline.cpp | 0 .../messageline}/messageline.h | 0 ui/widgets/messageline/preview.cpp | 304 ++++++++++++++++++ ui/widgets/messageline/preview.h | 79 +++++ 20 files changed, 498 insertions(+), 164 deletions(-) create mode 100644 ui/widgets/messageline/CMakeLists.txt rename ui/{utils => widgets/messageline}/feedview.cpp (99%) rename ui/{utils => widgets/messageline}/feedview.h (97%) rename ui/{utils => widgets/messageline}/message.cpp (100%) rename ui/{utils => widgets/messageline}/message.h (100%) rename ui/{utils => widgets/messageline}/messagedelegate.cpp (73%) rename ui/{utils => widgets/messageline}/messagedelegate.h (94%) rename ui/{models => widgets/messageline}/messagefeed.cpp (99%) rename ui/{models => widgets/messageline}/messagefeed.h (100%) rename ui/{utils => widgets/messageline}/messageline.cpp (100%) rename ui/{utils => widgets/messageline}/messageline.h (100%) create mode 100644 ui/widgets/messageline/preview.cpp create mode 100644 ui/widgets/messageline/preview.h diff --git a/shared/global.cpp b/shared/global.cpp index 25a1c87..0330a00 100644 --- a/shared/global.cpp +++ b/shared/global.cpp @@ -127,12 +127,19 @@ Shared::Global::FileInfo Shared::Global::getFileInfo(const QString& path) FileInfo::Preview p = FileInfo::Preview::none; QSize size; if (big == "image") { - if (parts.back() == "gif") { - //TODO need to consider GIF as a movie + QMovie mov(path); + if (mov.isValid()) { + p = FileInfo::Preview::animation; + } else { + p = FileInfo::Preview::picture; } - p = FileInfo::Preview::picture; QImage img(path); size = img.size(); +// } else if (big == "video") { +// p = FileInfo::Preview::movie; +// QMovie mov(path); +// size = mov.scaledSize(); +// qDebug() << mov.isValid(); } else { size = defaultIconFileInfoHeight; } diff --git a/shared/global.h b/shared/global.h index b6bbe37..03cf84d 100644 --- a/shared/global.h +++ b/shared/global.h @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -51,7 +52,7 @@ namespace Shared { enum class Preview { none, picture, - movie + animation }; QString name; diff --git a/ui/models/CMakeLists.txt b/ui/models/CMakeLists.txt index 98ef1c3..629db32 100644 --- a/ui/models/CMakeLists.txt +++ b/ui/models/CMakeLists.txt @@ -13,8 +13,6 @@ target_sources(squawk PRIVATE group.h item.cpp item.h - messagefeed.cpp - messagefeed.h participant.cpp participant.h presence.cpp @@ -25,4 +23,4 @@ target_sources(squawk PRIVATE room.h roster.cpp roster.h - ) \ No newline at end of file + ) diff --git a/ui/models/element.h b/ui/models/element.h index af44791..94d67cb 100644 --- a/ui/models/element.h +++ b/ui/models/element.h @@ -20,7 +20,8 @@ #define ELEMENT_H #include "item.h" -#include "messagefeed.h" + +#include "ui/widgets/messageline/messagefeed.h" namespace Models { diff --git a/ui/utils/CMakeLists.txt b/ui/utils/CMakeLists.txt index 5ad5cb7..b46d30d 100644 --- a/ui/utils/CMakeLists.txt +++ b/ui/utils/CMakeLists.txt @@ -5,18 +5,10 @@ target_sources(squawk PRIVATE comboboxdelegate.h exponentialblur.cpp exponentialblur.h - feedview.cpp - feedview.h flowlayout.cpp flowlayout.h image.cpp image.h - message.cpp - message.h - messagedelegate.cpp - messagedelegate.h - messageline.cpp - messageline.h progress.cpp progress.h resizer.cpp diff --git a/ui/widgets/CMakeLists.txt b/ui/widgets/CMakeLists.txt index 0cacf6f..c7e47e0 100644 --- a/ui/widgets/CMakeLists.txt +++ b/ui/widgets/CMakeLists.txt @@ -21,3 +21,4 @@ target_sources(squawk PRIVATE ) add_subdirectory(vcard) +add_subdirectory(messageline) diff --git a/ui/widgets/conversation.h b/ui/widgets/conversation.h index 0b0dcb2..3f048fb 100644 --- a/ui/widgets/conversation.h +++ b/ui/widgets/conversation.h @@ -31,16 +31,19 @@ #include "shared/message.h" #include "shared/order.h" -#include "ui/models/account.h" -#include "ui/models/roster.h" -#include "ui/utils/flowlayout.h" -#include "ui/utils/badge.h" -#include "ui/utils/feedview.h" -#include "ui/utils/messagedelegate.h" -#include "ui/utils/shadowoverlay.h" #include "shared/icons.h" #include "shared/utils.h" +#include "ui/models/account.h" +#include "ui/models/roster.h" + +#include "ui/utils/flowlayout.h" +#include "ui/utils/badge.h" +#include "ui/utils/shadowoverlay.h" + +#include "ui/widgets/messageline/feedview.h" +#include "ui/widgets/messageline/messagedelegate.h" + namespace Ui { class Conversation; diff --git a/ui/widgets/messageline/CMakeLists.txt b/ui/widgets/messageline/CMakeLists.txt new file mode 100644 index 0000000..7cace9d --- /dev/null +++ b/ui/widgets/messageline/CMakeLists.txt @@ -0,0 +1,14 @@ +target_sources(squawk PRIVATE + messagedelegate.cpp + messagedelegate.h + #messageline.cpp + #messageline.h + preview.cpp + preview.h + messagefeed.cpp + messagefeed.h + feedview.cpp + feedview.h + #message.cpp + #message.h + ) diff --git a/ui/utils/feedview.cpp b/ui/widgets/messageline/feedview.cpp similarity index 99% rename from ui/utils/feedview.cpp rename to ui/widgets/messageline/feedview.cpp index 5f515aa..6d8c180 100644 --- a/ui/utils/feedview.cpp +++ b/ui/widgets/messageline/feedview.cpp @@ -24,7 +24,7 @@ #include #include "messagedelegate.h" -#include "ui/models/messagefeed.h" +#include "messagefeed.h" constexpr int maxMessageHeight = 10000; constexpr int approximateSingleMessageHeight = 20; diff --git a/ui/utils/feedview.h b/ui/widgets/messageline/feedview.h similarity index 97% rename from ui/utils/feedview.h rename to ui/widgets/messageline/feedview.h index f5509fd..b20276c 100644 --- a/ui/utils/feedview.h +++ b/ui/widgets/messageline/feedview.h @@ -24,8 +24,8 @@ #include #include -#include -#include "progress.h" +#include +#include /** * @todo write docs diff --git a/ui/utils/message.cpp b/ui/widgets/messageline/message.cpp similarity index 100% rename from ui/utils/message.cpp rename to ui/widgets/messageline/message.cpp diff --git a/ui/utils/message.h b/ui/widgets/messageline/message.h similarity index 100% rename from ui/utils/message.h rename to ui/widgets/messageline/message.h diff --git a/ui/utils/messagedelegate.cpp b/ui/widgets/messageline/messagedelegate.cpp similarity index 73% rename from ui/utils/messagedelegate.cpp rename to ui/widgets/messageline/messagedelegate.cpp index 0381ae3..8405964 100644 --- a/ui/utils/messagedelegate.cpp +++ b/ui/widgets/messageline/messagedelegate.cpp @@ -22,13 +22,12 @@ #include #include "messagedelegate.h" -#include "ui/models/messagefeed.h" +#include "messagefeed.h" constexpr int avatarHeight = 50; constexpr int margin = 6; constexpr int textMargin = 2; constexpr int statusIconSize = 16; -constexpr int maxAttachmentHeight = 500; MessageDelegate::MessageDelegate(QObject* parent): QStyledItemDelegate(parent), @@ -44,6 +43,7 @@ MessageDelegate::MessageDelegate(QObject* parent): bars(new std::map()), statusIcons(new std::map()), bodies(new std::map()), + previews(new std::map()), idsToKeep(new std::set()), clearingWidgets(false) { @@ -72,10 +72,15 @@ MessageDelegate::~MessageDelegate() delete pair.second; } + for (const std::pair& pair: *previews){ + delete pair.second; + } + delete idsToKeep; delete buttons; delete bars; delete bodies; + delete previews; } void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const @@ -151,24 +156,14 @@ void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio break; case Models::errorDownload: { paintButton(getButton(data), painter, data.sentByMe, opt); - painter->setFont(dateFont); - QColor q = painter->pen().color(); - q.setAlpha(180); - painter->setPen(q); - painter->drawText(opt.rect, opt.displayAlignment, data.attach.error, &rect); - opt.rect.adjust(0, rect.height() + textMargin, 0, 0); + paintComment(data, painter, opt); } break; case Models::errorUpload:{ clearHelperWidget(data); paintPreview(data, painter, opt); - painter->setFont(dateFont); - QColor q = painter->pen().color(); - q.setAlpha(180); - painter->setPen(q); - painter->drawText(opt.rect, opt.displayAlignment, data.attach.error, &rect); - opt.rect.adjust(0, rect.height() + textMargin, 0, 0); + paintComment(data, painter, opt); } break; } @@ -181,6 +176,8 @@ void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio body->setParent(vp); body->setMaximumWidth(bodySize.width()); body->setMinimumWidth(bodySize.width()); + body->setMinimumHeight(bodySize.height()); + body->setMaximumHeight(bodySize.height()); body->setAlignment(opt.displayAlignment); messageLeft = opt.rect.x(); if (data.sentByMe) { @@ -232,7 +229,7 @@ QSize MessageDelegate::sizeHint(const QStyleOptionViewItem& option, const QModel case Models::none: break; case Models::uploading: - messageSize.rheight() += calculateAttachSize(attach.localPath, messageRect).height() + textMargin; + messageSize.rheight() += Preview::calculateAttachSize(attach.localPath, messageRect).height() + textMargin; case Models::downloading: messageSize.rheight() += barHeight + textMargin; break; @@ -241,14 +238,14 @@ QSize MessageDelegate::sizeHint(const QStyleOptionViewItem& option, const QModel break; case Models::ready: case Models::local: - messageSize.rheight() += calculateAttachSize(attach.localPath, messageRect).height() + textMargin; + messageSize.rheight() += Preview::calculateAttachSize(attach.localPath, messageRect).height() + textMargin; break; case Models::errorDownload: messageSize.rheight() += buttonHeight + textMargin; messageSize.rheight() += dateMetrics.boundingRect(messageRect, Qt::TextWordWrap, attach.error).size().height() + textMargin; break; case Models::errorUpload: - messageSize.rheight() += calculateAttachSize(attach.localPath, messageRect).height() + textMargin; + messageSize.rheight() += Preview::calculateAttachSize(attach.localPath, messageRect).height() + textMargin; messageSize.rheight() += dateMetrics.boundingRect(messageRect, Qt::TextWordWrap, attach.error).size().height() + textMargin; break; } @@ -319,6 +316,17 @@ void MessageDelegate::paintButton(QPushButton* btn, QPainter* painter, bool sent option.rect.adjust(0, buttonHeight + textMargin, 0, 0); } +void MessageDelegate::paintComment(const Models::FeedItem& data, QPainter* painter, QStyleOptionViewItem& option) const +{ + painter->setFont(dateFont); + QColor q = painter->pen().color(); + q.setAlpha(180); + painter->setPen(q); + QRect rect; + painter->drawText(option.rect, option.displayAlignment, data.attach.error, &rect); + option.rect.adjust(0, rect.height() + textMargin, 0, 0); +} + void MessageDelegate::paintBar(QProgressBar* bar, QPainter* painter, bool sentByMe, QStyleOptionViewItem& option) const { QPoint start = option.rect.topLeft(); @@ -332,49 +340,20 @@ void MessageDelegate::paintBar(QProgressBar* bar, QPainter* painter, bool sentBy void MessageDelegate::paintPreview(const Models::FeedItem& data, QPainter* painter, QStyleOptionViewItem& option) const { - Shared::Global::FileInfo info = Shared::Global::getFileInfo(data.attach.localPath); - QSize size = constrainAttachSize(info.size, option.rect.size()); - - QPoint start; - if (data.sentByMe) { - start = {option.rect.width() - size.width(), option.rect.top()}; - start.rx() += margin; + Preview* preview = 0; + std::map::iterator itr = previews->find(data.id); + + QSize size = option.rect.size(); + if (itr != previews->end()) { + preview = itr->second; + preview->actualize(data.attach.localPath, size, option.rect.topLeft()); } else { - start = option.rect.topLeft(); - } - QRect rect(start, size); - switch (info.preview) { - case Shared::Global::FileInfo::Preview::picture: { - QImage img(data.attach.localPath); - if (img.isNull()) { - emit invalidPath(data.id); - } else { - painter->drawImage(rect, img); - } - } - break; - default: { - QIcon icon = QIcon::fromTheme(info.mime.iconName()); - - painter->save(); - - painter->setFont(bodyFont); - int labelWidth = option.rect.width() - size.width() - margin; - QString elidedName = bodyMetrics.elidedText(info.name, Qt::ElideMiddle, labelWidth); - QSize nameSize = bodyMetrics.boundingRect(QRect(start, QSize(labelWidth, 0)), 0, elidedName).size(); - if (data.sentByMe) { - start.rx() -= nameSize.width() + margin; - } - painter->drawPixmap({start, size}, icon.pixmap(info.size)); - start.rx() += size.width() + margin; - start.ry() += nameSize.height() + (size.height() - nameSize.height()) / 2; - painter->drawText(start, elidedName); - - painter->restore(); - } + QWidget* vp = static_cast(painter->device()); + preview = new Preview(data.attach.localPath, size, option.rect.topLeft(), data.sentByMe, vp); + previews->insert(std::make_pair(data.id, preview)); } - option.rect.adjust(0, size.height() + textMargin, 0, 0); + option.rect.adjust(0, preview->size().height() + textMargin, 0, 0); } QPushButton * MessageDelegate::getButton(const Models::FeedItem& data) const @@ -432,6 +411,13 @@ QLabel * MessageDelegate::getStatusIcon(const Models::FeedItem& data) const std::map::const_iterator itr = statusIcons->find(data.id); QLabel* result = 0; + if (itr != statusIcons->end()) { + result = itr->second; + } else { + result = new QLabel(); + statusIcons->insert(std::make_pair(data.id, result)); + } + QIcon q(Shared::icon(Shared::messageStateThemeIcons[static_cast(data.state)])); QString tt = Shared::Global::getName(data.state); if (data.state == Shared::Message::State::error) { @@ -439,25 +425,11 @@ QLabel * MessageDelegate::getStatusIcon(const Models::FeedItem& data) const tt += ": " + data.error; } } - - if (itr != statusIcons->end()) { - result = itr->second; - if (result->toolTip() != tt) { //If i just assign pixmap every time unconditionally - result->setPixmap(q.pixmap(statusIconSize)); //it involves into an infinite cycle of repaint - result->setToolTip(tt); //may be it's better to subclass and store last condition in int? - } - } else { - result = new QLabel(); - statusIcons->insert(std::make_pair(data.id, result)); - result->setPixmap(q.pixmap(statusIconSize)); - result->setToolTip(tt); + if (result->toolTip() != tt) { //If i just assign pixmap every time unconditionally + result->setPixmap(q.pixmap(statusIconSize)); //it invokes an infinite cycle of repaint + result->setToolTip(tt); //may be it's better to subclass and store last condition in int? } - - - result->setToolTip(tt); - //result->setText(std::to_string((int)data.state).c_str()); - return result; } @@ -488,50 +460,28 @@ void MessageDelegate::beginClearWidgets() clearingWidgets = true; } +template +void removeElements(std::map* elements, std::set* idsToKeep) { + std::set toRemove; + for (const std::pair& pair: *elements) { + if (idsToKeep->find(pair.first) == idsToKeep->end()) { + delete pair.second; + toRemove.insert(pair.first); + } + } + for (const QString& key : toRemove) { + elements->erase(key); + } +} + void MessageDelegate::endClearWidgets() { if (clearingWidgets) { - std::set toRemoveButtons; - std::set toRemoveBars; - std::set toRemoveIcons; - std::set toRemoveBodies; - for (const std::pair& pair: *buttons) { - if (idsToKeep->find(pair.first) == idsToKeep->end()) { - delete pair.second; - toRemoveButtons.insert(pair.first); - } - } - for (const std::pair& pair: *bars) { - if (idsToKeep->find(pair.first) == idsToKeep->end()) { - delete pair.second; - toRemoveBars.insert(pair.first); - } - } - for (const std::pair& pair: *statusIcons) { - if (idsToKeep->find(pair.first) == idsToKeep->end()) { - delete pair.second; - toRemoveIcons.insert(pair.first); - } - } - for (const std::pair& pair: *bodies) { - if (idsToKeep->find(pair.first) == idsToKeep->end()) { - delete pair.second; - toRemoveBodies.insert(pair.first); - } - } - - for (const QString& key : toRemoveButtons) { - buttons->erase(key); - } - for (const QString& key : toRemoveBars) { - bars->erase(key); - } - for (const QString& key : toRemoveIcons) { - statusIcons->erase(key); - } - for (const QString& key : toRemoveBodies) { - bodies->erase(key); - } + removeElements(buttons, idsToKeep); + removeElements(bars, idsToKeep); + removeElements(statusIcons, idsToKeep); + removeElements(bodies, idsToKeep); + removeElements(previews, idsToKeep); idsToKeep->clear(); clearingWidgets = false; @@ -559,25 +509,6 @@ void MessageDelegate::clearHelperWidget(const Models::FeedItem& data) const } } -QSize MessageDelegate::calculateAttachSize(const QString& path, const QRect& bounds) const -{ - Shared::Global::FileInfo info = Shared::Global::getFileInfo(path); - - return constrainAttachSize(info.size, bounds.size()); -} - -QSize MessageDelegate::constrainAttachSize(QSize src, QSize bounds) const -{ - bounds.setHeight(maxAttachmentHeight); - - if (src.width() > bounds.width() || src.height() > bounds.height()) { - src.scale(bounds, Qt::KeepAspectRatio); - } - - return src; -} - - // void MessageDelegate::setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const // { // diff --git a/ui/utils/messagedelegate.h b/ui/widgets/messageline/messagedelegate.h similarity index 94% rename from ui/utils/messagedelegate.h rename to ui/widgets/messageline/messagedelegate.h index 3af80e1..5c2989d 100644 --- a/ui/utils/messagedelegate.h +++ b/ui/widgets/messageline/messagedelegate.h @@ -34,6 +34,8 @@ #include "shared/global.h" #include "shared/utils.h" +#include "preview.h" + namespace Models { struct FeedItem; }; @@ -62,13 +64,12 @@ protected: void paintButton(QPushButton* btn, QPainter* painter, bool sentByMe, QStyleOptionViewItem& option) const; void paintBar(QProgressBar* bar, QPainter* painter, bool sentByMe, QStyleOptionViewItem& option) const; void paintPreview(const Models::FeedItem& data, QPainter* painter, QStyleOptionViewItem& option) const; + void paintComment(const Models::FeedItem& data, QPainter* painter, QStyleOptionViewItem& option) const; QPushButton* getButton(const Models::FeedItem& data) const; QProgressBar* getBar(const Models::FeedItem& data) const; QLabel* getStatusIcon(const Models::FeedItem& data) const; QLabel* getBody(const Models::FeedItem& data) const; void clearHelperWidget(const Models::FeedItem& data) const; - QSize calculateAttachSize(const QString& path, const QRect& bounds) const; - QSize constrainAttachSize(QSize src, QSize bounds) const; protected slots: void onButtonPushed() const; @@ -93,6 +94,7 @@ private: std::map* bars; std::map* statusIcons; std::map* bodies; + std::map* previews; std::set* idsToKeep; bool clearingWidgets; diff --git a/ui/models/messagefeed.cpp b/ui/widgets/messageline/messagefeed.cpp similarity index 99% rename from ui/models/messagefeed.cpp rename to ui/widgets/messageline/messagefeed.cpp index c64d7ab..4f22113 100644 --- a/ui/models/messagefeed.cpp +++ b/ui/widgets/messageline/messagefeed.cpp @@ -17,8 +17,9 @@ */ #include "messagefeed.h" -#include "element.h" -#include "room.h" + +#include +#include #include diff --git a/ui/models/messagefeed.h b/ui/widgets/messageline/messagefeed.h similarity index 100% rename from ui/models/messagefeed.h rename to ui/widgets/messageline/messagefeed.h diff --git a/ui/utils/messageline.cpp b/ui/widgets/messageline/messageline.cpp similarity index 100% rename from ui/utils/messageline.cpp rename to ui/widgets/messageline/messageline.cpp diff --git a/ui/utils/messageline.h b/ui/widgets/messageline/messageline.h similarity index 100% rename from ui/utils/messageline.h rename to ui/widgets/messageline/messageline.h diff --git a/ui/widgets/messageline/preview.cpp b/ui/widgets/messageline/preview.cpp new file mode 100644 index 0000000..8c56cbc --- /dev/null +++ b/ui/widgets/messageline/preview.cpp @@ -0,0 +1,304 @@ +/* + * Squawk messenger. + * Copyright (C) 2019 Yury Gubich + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 "preview.h" + + +constexpr int margin = 6; +constexpr int maxAttachmentHeight = 500; + +bool Preview::fontInitialized = false; +QFont Preview::font; +QFontMetrics Preview::metrics(Preview::font); + +Preview::Preview(const QString& pPath, const QSize& pMaxSize, const QPoint& pos, bool pRight, QWidget* pParent): + info(Shared::Global::getFileInfo(pPath)), + path(pPath), + maxSize(pMaxSize), + actualSize(constrainAttachSize(info.size, maxSize)), + cachedLabelSize(0, 0), + position(pos), + widget(0), + label(0), + parent(pParent), + movie(0), + fileReachable(true), + actualPreview(false), + right(pRight) +{ + if (!fontInitialized) { + font.setBold(true); + font.setPixelSize(14); + metrics = QFontMetrics(font); + fontInitialized = true; + } + + initializeElements(); + if (fileReachable) { + positionElements(); + } +} + +Preview::~Preview() +{ + clean(); +} + +void Preview::clean() +{ + if (fileReachable) { + if (info.preview == Shared::Global::FileInfo::Preview::animation) { + delete movie; + } + delete widget; + if (!actualPreview) { + delete label; + } else { + actualPreview = false; + } + } else { + fileReachable = true; + } +} + +void Preview::actualize(const QString& newPath, const QSize& newSize, const QPoint& newPoint) +{ + bool positionChanged = false; + bool sizeChanged = false; + bool maxSizeChanged = false; + + if (maxSize != newSize) { + maxSize = newSize; + maxSizeChanged = true; + QSize ns = constrainAttachSize(info.size, maxSize); + if (actualSize != ns) { + sizeChanged = true; + actualSize = ns; + } + } + if (position != newPoint) { + position = newPoint; + positionChanged = true; + } + + if (!setPath(newPath) && fileReachable) { + if (sizeChanged) { + applyNewSize(); + if (maxSizeChanged && !actualPreview) { + applyNewMaxSize(); + } + } else if (maxSizeChanged) { + applyNewMaxSize(); + } + if (positionChanged || !actualPreview) { + positionElements(); + } + } +} + +void Preview::setSize(const QSize& newSize) +{ + bool sizeChanged = false; + bool maxSizeChanged = false; + + if (maxSize != newSize) { + maxSize = newSize; + maxSizeChanged = true; + QSize ns = constrainAttachSize(info.size, maxSize); + if (actualSize != ns) { + sizeChanged = true; + actualSize = ns; + } + } + + if (fileReachable) { + if (sizeChanged) { + applyNewSize(); + } + if (maxSizeChanged || !actualPreview) { + applyNewMaxSize(); + } + } +} + +void Preview::applyNewSize() +{ + switch (info.preview) { + case Shared::Global::FileInfo::Preview::picture: { + QPixmap img(path); + if (img.isNull()) { + fileReachable = false; + } else { + img = img.scaled(actualSize, Qt::KeepAspectRatio); + widget->resize(actualSize); + widget->setPixmap(img); + } + } + break; + case Shared::Global::FileInfo::Preview::animation:{ + movie->setScaledSize(actualSize); + widget->resize(actualSize); + } + break; + default: { + QIcon icon = QIcon::fromTheme(info.mime.iconName()); + widget->setPixmap(icon.pixmap(actualSize)); + widget->resize(actualSize); + } + } +} + +void Preview::applyNewMaxSize() +{ + switch (info.preview) { + case Shared::Global::FileInfo::Preview::picture: + case Shared::Global::FileInfo::Preview::animation: + break; + default: { + int labelWidth = maxSize.width() - actualSize.width() - margin; + QString elidedName = metrics.elidedText(info.name, Qt::ElideMiddle, labelWidth); + cachedLabelSize = metrics.size(0, elidedName); + label->setText(elidedName); + label->resize(cachedLabelSize); + } + } +} + + +QSize Preview::size() const +{ + if (actualPreview) { + return actualSize; + } else { + return QSize(actualSize.width() + margin + cachedLabelSize.width(), actualSize.height()); + } +} + +bool Preview::isFileReachable() const +{ + return fileReachable; +} + +void Preview::setPosition(const QPoint& newPoint) +{ + if (position != newPoint) { + position = newPoint; + if (fileReachable) { + positionElements(); + } + } +} + +bool Preview::setPath(const QString& newPath) +{ + if (path != newPath) { + path = newPath; + info = Shared::Global::getFileInfo(path); + actualSize = constrainAttachSize(info.size, maxSize); + clean(); + initializeElements(); + if (fileReachable) { + positionElements(); + } + return true; + } else { + return false; + } +} + +void Preview::initializeElements() +{ + switch (info.preview) { + case Shared::Global::FileInfo::Preview::picture: { + QPixmap img(path); + if (img.isNull()) { + fileReachable = false; + } else { + actualPreview = true; + img = img.scaled(actualSize, Qt::KeepAspectRatio); + widget = new QLabel(parent); + widget->setPixmap(img); + widget->show(); + } + } + break; + case Shared::Global::FileInfo::Preview::animation:{ + movie = new QMovie(path); + if (!movie->isValid()) { + fileReachable = false; + delete movie; + } else { + actualPreview = true; + movie->setScaledSize(actualSize); + widget = new QLabel(parent); + widget->setMovie(movie); + movie->start(); + widget->show(); + } + } + break; + default: { + QIcon icon = QIcon::fromTheme(info.mime.iconName()); + widget = new QLabel(parent); + widget->setPixmap(icon.pixmap(actualSize)); + widget->show(); + + label = new QLabel(parent); + label->setFont(font); + int labelWidth = maxSize.width() - actualSize.width() - margin; + QString elidedName = metrics.elidedText(info.name, Qt::ElideMiddle, labelWidth); + cachedLabelSize = metrics.size(0, elidedName); + label->setText(elidedName); + label->show(); + } + } +} + +void Preview::positionElements() +{ + int start = position.x(); + if (right) { + start += maxSize.width() - size().width(); + } + widget->move(start, position.y()); + if (!actualPreview) { + int x = start + actualSize.width() + margin; + int y = position.y() + (actualSize.height() - cachedLabelSize.height()) / 2; + label->move(x, y); + } +} + +QSize Preview::calculateAttachSize(const QString& path, const QRect& bounds) +{ + Shared::Global::FileInfo info = Shared::Global::getFileInfo(path); + + return constrainAttachSize(info.size, bounds.size()); +} + +QSize Preview::constrainAttachSize(QSize src, QSize bounds) +{ + if (bounds.height() > maxAttachmentHeight) { + bounds.setHeight(maxAttachmentHeight); + } + + if (src.width() > bounds.width() || src.height() > bounds.height()) { + src.scale(bounds, Qt::KeepAspectRatio); + } + + return src; +} diff --git a/ui/widgets/messageline/preview.h b/ui/widgets/messageline/preview.h new file mode 100644 index 0000000..3d560d3 --- /dev/null +++ b/ui/widgets/messageline/preview.h @@ -0,0 +1,79 @@ +/* + * Squawk messenger. + * Copyright (C) 2019 Yury Gubich + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 PREVIEW_H +#define PREVIEW_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/** + * @todo write docs + */ +class Preview { +public: + Preview(const QString& pPath, const QSize& pMaxSize, const QPoint& pos, bool pRight, QWidget* parent); + ~Preview(); + + void actualize(const QString& newPath, const QSize& newSize, const QPoint& newPoint); + void setPosition(const QPoint& newPoint); + void setSize(const QSize& newSize); + bool setPath(const QString& newPath); + bool isFileReachable() const; + QSize size() const; + + static QSize constrainAttachSize(QSize src, QSize bounds); + static QSize calculateAttachSize(const QString& path, const QRect& bounds); + static bool fontInitialized; + static QFont font; + static QFontMetrics metrics; + +private: + void initializeElements(); + void positionElements(); + void clean(); + void applyNewSize(); + void applyNewMaxSize(); + +private: + Shared::Global::FileInfo info; + QString path; + QSize maxSize; + QSize actualSize; + QSize cachedLabelSize; + QPoint position; + QLabel* widget; + QLabel* label; + QWidget* parent; + QMovie* movie; + bool fileReachable; + bool actualPreview; + bool right; +}; + +#endif // PREVIEW_H From 721f6daa3653a5a59d75a4b11ad3ff8cf5cbe6ad Mon Sep 17 00:00:00 2001 From: blue Date: Mon, 17 May 2021 00:52:59 +0300 Subject: [PATCH 09/93] fix bug when everything was treated as animation, bug with not working group amount of messages, handled the situation when preview is painted but the file was lost --- ui/models/account.cpp | 18 +++++++++++++++--- ui/models/account.h | 4 ++++ ui/models/contact.cpp | 6 ++++++ ui/models/contact.h | 2 ++ ui/models/reference.cpp | 2 ++ ui/models/roster.cpp | 14 ++++++++++++++ ui/models/roster.h | 1 + ui/widgets/messageline/messagedelegate.cpp | 4 ++++ ui/widgets/messageline/messagefeed.cpp | 14 +++++++++++--- ui/widgets/messageline/messagefeed.h | 5 +++-- 10 files changed, 62 insertions(+), 8 deletions(-) diff --git a/ui/models/account.cpp b/ui/models/account.cpp index f8d0c37..43cb3ed 100644 --- a/ui/models/account.cpp +++ b/ui/models/account.cpp @@ -31,7 +31,8 @@ Models::Account::Account(const QMap& data, Models::Item* pare avatarPath(data.value("avatarPath").toString()), state(Shared::ConnectionState::disconnected), availability(Shared::Availability::offline), - passwordType(Shared::AccountPassword::plain) + passwordType(Shared::AccountPassword::plain), + wasEverConnected(false) { QMap::const_iterator sItr = data.find("state"); if (sItr != data.end()) { @@ -56,8 +57,19 @@ void Models::Account::setState(Shared::ConnectionState p_state) if (state != p_state) { state = p_state; changed(2); - if (state == Shared::ConnectionState::disconnected) { - toOfflineState(); + switch (state) { + case Shared::ConnectionState::disconnected: + toOfflineState(); + break; + case Shared::ConnectionState::connected: + if (wasEverConnected) { + emit reconnected(); + } else { + wasEverConnected = true; + } + break; + default: + break; } } } diff --git a/ui/models/account.h b/ui/models/account.h index 686d4da..3d2310f 100644 --- a/ui/models/account.h +++ b/ui/models/account.h @@ -77,6 +77,9 @@ namespace Models { QString getBareJid() const; QString getFullJid() const; + signals: + void reconnected(); + private: QString login; QString password; @@ -87,6 +90,7 @@ namespace Models { Shared::ConnectionState state; Shared::Availability availability; Shared::AccountPassword passwordType; + bool wasEverConnected; protected slots: void toOfflineState() override; diff --git a/ui/models/contact.cpp b/ui/models/contact.cpp index d54fccf..a0c70ac 100644 --- a/ui/models/contact.cpp +++ b/ui/models/contact.cpp @@ -240,3 +240,9 @@ QString Models::Contact::getDisplayedName() const return getContactName(); } +void Models::Contact::handleRecconnect() +{ + if (getMessagesCount() > 0) { + feed->requestLatestMessages(); + } +} diff --git a/ui/models/contact.h b/ui/models/contact.h index 7e76f5b..a8b80a3 100644 --- a/ui/models/contact.h +++ b/ui/models/contact.h @@ -56,6 +56,8 @@ public: QString getStatus() const; QString getDisplayedName() const override; + void handleRecconnect(); //this is a special method Models::Roster calls when reconnect happens + protected: void _removeChild(int index) override; void _appendChild(Models::Item * child) override; diff --git a/ui/models/reference.cpp b/ui/models/reference.cpp index cb8efad..1aaea15 100644 --- a/ui/models/reference.cpp +++ b/ui/models/reference.cpp @@ -104,6 +104,8 @@ void Models::Reference::onChildChanged(Models::Item* item, int row, int col) { if (item == original) { emit childChanged(this, row, col); + } else { + emit childChanged(item, row, col); } } diff --git a/ui/models/roster.cpp b/ui/models/roster.cpp index d70d9d1..2d5f99f 100644 --- a/ui/models/roster.cpp +++ b/ui/models/roster.cpp @@ -48,6 +48,7 @@ Models::Roster::~Roster() void Models::Roster::addAccount(const QMap& data) { Account* acc = new Account(data); + connect(acc, &Account::reconnected, this, &Roster::onAccountReconnected); root->appendChild(acc); accounts.insert(std::make_pair(acc->getName(), acc)); accountsModel->addAccount(acc); @@ -744,6 +745,7 @@ void Models::Roster::removeAccount(const QString& account) } } + disconnect(acc, &Account::reconnected, this, &Roster::onAccountReconnected); acc->deleteLater(); } @@ -1003,3 +1005,15 @@ Models::Element * Models::Roster::getElement(const Models::Roster::ElId& id) return NULL; } +void Models::Roster::onAccountReconnected() +{ + Account* acc = static_cast(sender()); + + QString accName = acc->getName(); + for (const std::pair& pair : contacts) { + if (pair.first.account == accName) { + pair.second->handleRecconnect(); + } + } +} + diff --git a/ui/models/roster.h b/ui/models/roster.h index 09261cd..08d5afc 100644 --- a/ui/models/roster.h +++ b/ui/models/roster.h @@ -100,6 +100,7 @@ private: private slots: void onAccountDataChanged(const QModelIndex& tl, const QModelIndex& br, const QVector& roles); + void onAccountReconnected(); void onChildChanged(Models::Item* item, int row, int col); void onChildIsAboutToBeInserted(Item* parent, int first, int last); void onChildInserted(); diff --git a/ui/widgets/messageline/messagedelegate.cpp b/ui/widgets/messageline/messagedelegate.cpp index 8405964..81018ac 100644 --- a/ui/widgets/messageline/messagedelegate.cpp +++ b/ui/widgets/messageline/messagedelegate.cpp @@ -353,6 +353,10 @@ void MessageDelegate::paintPreview(const Models::FeedItem& data, QPainter* paint previews->insert(std::make_pair(data.id, preview)); } + if (!preview->isFileReachable()) { //this is the situation when the file preview couldn't be painted because the file was moved + emit invalidPath(data.id); //or deleted. This signal notifies the model, and the model notifies the core, preview can + } //handle being invalid for as long as I need and can be even become valid again with a new path + option.rect.adjust(0, preview->size().height() + textMargin, 0, 0); } diff --git a/ui/widgets/messageline/messagefeed.cpp b/ui/widgets/messageline/messagefeed.cpp index 4f22113..9537ea5 100644 --- a/ui/widgets/messageline/messagefeed.cpp +++ b/ui/widgets/messageline/messagefeed.cpp @@ -304,7 +304,7 @@ QVariant Models::MessageFeed::data(const QModelIndex& index, int role) const std::set::const_iterator umi = unreadMessages->find(item.id); if (umi != unreadMessages->end()) { unreadMessages->erase(umi); - emit unreadMessagesCount(); + emit unreadMessagesCountChanged(); } item.sentByMe = sentByMe(*msg); @@ -370,7 +370,6 @@ void Models::MessageFeed::fetchMore(const QModelIndex& parent) if (syncState == incomplete) { syncState = syncing; emit syncStateChange(syncState); - emit requestStateChange(true); if (storage.size() == 0) { emit requestArchive(""); @@ -398,7 +397,6 @@ void Models::MessageFeed::responseArchive(const std::list list, syncState = incomplete; } emit syncStateChange(syncState); - emit requestStateChange(false); } } @@ -656,3 +654,13 @@ Models::MessageFeed::SyncState Models::MessageFeed::getSyncState() const { return syncState; } + +void Models::MessageFeed::requestLatestMessages() +{ + if (syncState != syncing) { + syncState = syncing; + emit syncStateChange(syncState); + + emit requestArchive(""); + } +} diff --git a/ui/widgets/messageline/messagefeed.h b/ui/widgets/messageline/messagefeed.h index efb005a..b368a3d 100644 --- a/ui/widgets/messageline/messagefeed.h +++ b/ui/widgets/messageline/messagefeed.h @@ -77,11 +77,12 @@ public: void decrementObservers(); SyncState getSyncState() const; + void requestLatestMessages(); //this method is used by Models::Contact to request latest messages after reconnection + signals: void requestArchive(const QString& before); - void requestStateChange(bool requesting); void fileDownloadRequest(const QString& url); - void unreadMessagesCountChanged(); + void unreadMessagesCountChanged() const; void newMessage(const Shared::Message& msg); void unnoticedMessage(const Shared::Message& msg); void localPathInvalid(const QString& path); From ddfaa63a24d60521d71c84ef22378ff781e64758 Mon Sep 17 00:00:00 2001 From: blue Date: Mon, 17 May 2021 23:32:44 +0300 Subject: [PATCH 10/93] big image preview optimisations, preview positioning fix, memory leaks fix --- shared/global.cpp | 4 +- ui/widgets/messageline/messagedelegate.cpp | 2 + ui/widgets/messageline/preview.cpp | 53 +++++++++++++++------- ui/widgets/messageline/preview.h | 4 +- 4 files changed, 43 insertions(+), 20 deletions(-) diff --git a/shared/global.cpp b/shared/global.cpp index 0330a00..67e74d1 100644 --- a/shared/global.cpp +++ b/shared/global.cpp @@ -128,12 +128,12 @@ Shared::Global::FileInfo Shared::Global::getFileInfo(const QString& path) QSize size; if (big == "image") { QMovie mov(path); - if (mov.isValid()) { + if (mov.isValid() && mov.frameCount() > 1) { p = FileInfo::Preview::animation; } else { p = FileInfo::Preview::picture; } - QImage img(path); + QImageReader img(path); size = img.size(); // } else if (big == "video") { // p = FileInfo::Preview::movie; diff --git a/ui/widgets/messageline/messagedelegate.cpp b/ui/widgets/messageline/messagedelegate.cpp index 81018ac..9b46b7a 100644 --- a/ui/widgets/messageline/messagedelegate.cpp +++ b/ui/widgets/messageline/messagedelegate.cpp @@ -289,6 +289,8 @@ void MessageDelegate::initializeFonts(const QFont& font) bodyMetrics = QFontMetrics(bodyFont); nickMetrics = QFontMetrics(nickFont); dateMetrics = QFontMetrics(dateFont); + + Preview::initializeFont(bodyFont); } bool MessageDelegate::editorEvent(QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& index) diff --git a/ui/widgets/messageline/preview.cpp b/ui/widgets/messageline/preview.cpp index 8c56cbc..a64c036 100644 --- a/ui/widgets/messageline/preview.cpp +++ b/ui/widgets/messageline/preview.cpp @@ -22,7 +22,6 @@ constexpr int margin = 6; constexpr int maxAttachmentHeight = 500; -bool Preview::fontInitialized = false; QFont Preview::font; QFontMetrics Preview::metrics(Preview::font); @@ -41,12 +40,6 @@ Preview::Preview(const QString& pPath, const QSize& pMaxSize, const QPoint& pos, actualPreview(false), right(pRight) { - if (!fontInitialized) { - font.setBold(true); - font.setPixelSize(14); - metrics = QFontMetrics(font); - fontInitialized = true; - } initializeElements(); if (fileReachable) { @@ -54,6 +47,13 @@ Preview::Preview(const QString& pPath, const QSize& pMaxSize, const QPoint& pos, } } +void Preview::initializeFont(const QFont& newFont) +{ + font = newFont; + font.setBold(true); + metrics = QFontMetrics(font); +} + Preview::~Preview() { clean(); @@ -104,6 +104,9 @@ void Preview::actualize(const QString& newPath, const QSize& newSize, const QPoi } } else if (maxSizeChanged) { applyNewMaxSize(); + if (right) { + positionChanged = true; + } } if (positionChanged || !actualPreview) { positionElements(); @@ -132,6 +135,9 @@ void Preview::setSize(const QSize& newSize) } if (maxSizeChanged || !actualPreview) { applyNewMaxSize(); + if (right) { + positionElements(); + } } } } @@ -140,13 +146,14 @@ void Preview::applyNewSize() { switch (info.preview) { case Shared::Global::FileInfo::Preview::picture: { - QPixmap img(path); - if (img.isNull()) { + QImageReader img(path); + if (!img.canRead()) { + delete widget; fileReachable = false; } else { - img = img.scaled(actualSize, Qt::KeepAspectRatio); + img.setScaledSize(actualSize); widget->resize(actualSize); - widget->setPixmap(img); + widget->setPixmap(QPixmap::fromImage(img.read())); } } break; @@ -172,7 +179,7 @@ void Preview::applyNewMaxSize() default: { int labelWidth = maxSize.width() - actualSize.width() - margin; QString elidedName = metrics.elidedText(info.name, Qt::ElideMiddle, labelWidth); - cachedLabelSize = metrics.size(0, elidedName); + cachedLabelSize = metrics.boundingRect(elidedName).size(); label->setText(elidedName); label->resize(cachedLabelSize); } @@ -225,20 +232,23 @@ void Preview::initializeElements() { switch (info.preview) { case Shared::Global::FileInfo::Preview::picture: { - QPixmap img(path); - if (img.isNull()) { + QImageReader img(path); + if (!img.canRead()) { fileReachable = false; } else { actualPreview = true; - img = img.scaled(actualSize, Qt::KeepAspectRatio); + img.setScaledSize(actualSize); widget = new QLabel(parent); - widget->setPixmap(img); + widget->setPixmap(QPixmap::fromImage(img.read())); widget->show(); } } break; case Shared::Global::FileInfo::Preview::animation:{ movie = new QMovie(path); + QObject::connect(movie, &QMovie::error, + std::bind(&Preview::handleQMovieError, this, std::placeholders::_1) + ); if (!movie->isValid()) { fileReachable = false; delete movie; @@ -262,7 +272,7 @@ void Preview::initializeElements() label->setFont(font); int labelWidth = maxSize.width() - actualSize.width() - margin; QString elidedName = metrics.elidedText(info.name, Qt::ElideMiddle, labelWidth); - cachedLabelSize = metrics.size(0, elidedName); + cachedLabelSize = metrics.boundingRect(elidedName).size(); label->setText(elidedName); label->show(); } @@ -302,3 +312,12 @@ QSize Preview::constrainAttachSize(QSize src, QSize bounds) return src; } + +void Preview::handleQMovieError(QImageReader::ImageReaderError error) +{ + if (error == QImageReader::FileNotFoundError) { + fileReachable = false; + movie->deleteLater(); + widget->deleteLater(); + } +} diff --git a/ui/widgets/messageline/preview.h b/ui/widgets/messageline/preview.h index 3d560d3..004ed45 100644 --- a/ui/widgets/messageline/preview.h +++ b/ui/widgets/messageline/preview.h @@ -29,6 +29,7 @@ #include #include #include +#include #include @@ -47,9 +48,9 @@ public: bool isFileReachable() const; QSize size() const; + static void initializeFont(const QFont& newFont); static QSize constrainAttachSize(QSize src, QSize bounds); static QSize calculateAttachSize(const QString& path, const QRect& bounds); - static bool fontInitialized; static QFont font; static QFontMetrics metrics; @@ -59,6 +60,7 @@ private: void clean(); void applyNewSize(); void applyNewMaxSize(); + void handleQMovieError(QImageReader::ImageReaderError error); private: Shared::Global::FileInfo info; From 3f1fba4de216b71a7d0966d240126d348a0af70d Mon Sep 17 00:00:00 2001 From: blue Date: Sun, 23 May 2021 01:03:14 +0300 Subject: [PATCH 11/93] doovers for failed messages, some corner cases fixes with handling errors during message sending --- core/account.cpp | 3 ++ core/account.h | 1 + core/archive.cpp | 5 +-- core/handlers/messagehandler.cpp | 56 ++++++++++++++++++++++++-------- core/handlers/messagehandler.h | 5 +-- core/main.cpp | 1 + core/squawk.cpp | 13 +++++++- core/squawk.h | 1 + ui/squawk.cpp | 10 ++++++ ui/squawk.h | 2 ++ ui/widgets/conversation.cpp | 10 ++++++ ui/widgets/conversation.h | 1 + 12 files changed, 89 insertions(+), 19 deletions(-) diff --git a/core/account.cpp b/core/account.cpp index 5ce29ee..6784674 100644 --- a/core/account.cpp +++ b/core/account.cpp @@ -923,3 +923,6 @@ void Core::Account::onContactHistoryResponse(const std::list& l 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);} diff --git a/core/account.h b/core/account.h index a0db9f9..5ba834c 100644 --- a/core/account.h +++ b/core/account.h @@ -103,6 +103,7 @@ public: void removeRoomRequest(const QString& jid); void addRoomRequest(const QString& jid, const QString& nick, const QString& password, bool autoJoin); void uploadVCard(const Shared::VCard& card); + void resendMessage(const QString& jid, const QString& id); public slots: void connect(); diff --git a/core/archive.cpp b/core/archive.cpp index 96a8c0d..2582ff9 100644 --- a/core/archive.cpp +++ b/core/archive.cpp @@ -308,8 +308,9 @@ void Core::Archive::changeMessage(const QString& id, const QMap 0 && (idChange || !hadStanzaId)) { - const std::string& szid = msg.getStanzaId().toStdString(); + QString qsid = msg.getStanzaId(); + if (qsid.size() > 0 && (idChange || !hadStanzaId)) { + std::string szid = qsid.toStdString(); lmdbData.mv_size = szid.size(); lmdbData.mv_data = (char*)szid.c_str(); diff --git a/core/handlers/messagehandler.cpp b/core/handlers/messagehandler.cpp index 54aff53..33b3458 100644 --- a/core/handlers/messagehandler.cpp +++ b/core/handlers/messagehandler.cpp @@ -73,8 +73,7 @@ void Core::MessageHandler::onMessageReceived(const QXmppMessage& msg) bool Core::MessageHandler::handleChatMessage(const QXmppMessage& msg, bool outgoing, bool forwarded, bool guessing) { - const QString& body(msg.body()); - if (body.size() != 0) { + if (msg.body().size() != 0 || msg.outOfBandUrl().size() > 0) { Shared::Message sMsg(Shared::Message::chat); initializeMessage(sMsg, msg, outgoing, forwarded, guessing); QString jid = sMsg.getPenPalJid(); @@ -234,17 +233,17 @@ void Core::MessageHandler::onReceiptReceived(const QString& jid, const QString& if (ri != 0) { ri->changeMessage(id, cData); } - pendingStateMessages.erase(itr); emit acc->changeMessage(itr->second, id, cData); + pendingStateMessages.erase(itr); } } -void Core::MessageHandler::sendMessage(const Shared::Message& data) +void Core::MessageHandler::sendMessage(const Shared::Message& data, bool newMessage) { if (data.getOutOfBandUrl().size() == 0 && data.getAttachPath().size() > 0) { - prepareUpload(data); + prepareUpload(data, newMessage); } else { - performSending(data); + performSending(data, newMessage); } } @@ -256,6 +255,7 @@ void Core::MessageHandler::performSending(Shared::Message data, bool newMessage) RosterItem* ri = acc->rh->getRosterItem(jid); bool sent = false; QMap changes; + QDateTime sendTime = QDateTime::currentDateTimeUtc(); if (acc->state == Shared::ConnectionState::connected) { QXmppMessage msg(acc->getFullJid(), data.getTo(), data.getBody(), data.getThread()); @@ -266,8 +266,10 @@ void Core::MessageHandler::performSending(Shared::Message data, bool newMessage) msg.setType(static_cast(data.getType())); //it is safe here, my type is compatible msg.setOutOfBandUrl(oob); msg.setReceiptRequested(true); + msg.setStamp(sendTime); sent = acc->client.sendPacket(msg); + //sent = false; if (sent) { data.setState(Shared::Message::State::sent); @@ -289,9 +291,10 @@ void Core::MessageHandler::performSending(Shared::Message data, bool newMessage) if (oob.size() > 0) { changes.insert("outOfBandUrl", oob); } - if (!newMessage) { - changes.insert("stamp", data.getTime()); + if (newMessage) { + data.setTime(sendTime); } + changes.insert("stamp", sendTime); if (ri != 0) { if (newMessage) { @@ -309,7 +312,7 @@ void Core::MessageHandler::performSending(Shared::Message data, bool newMessage) emit acc->changeMessage(jid, id, changes); } -void Core::MessageHandler::prepareUpload(const Shared::Message& data) +void Core::MessageHandler::prepareUpload(const Shared::Message& data, bool newMessage) { if (acc->state == Shared::ConnectionState::connected) { QString jid = data.getPenPalJid(); @@ -322,16 +325,23 @@ void Core::MessageHandler::prepareUpload(const Shared::Message& data) QString path = data.getAttachPath(); QString url = acc->network->getFileRemoteUrl(path); if (url.size() != 0) { - sendMessageWithLocalUploadedFile(data, url); + sendMessageWithLocalUploadedFile(data, url, newMessage); } else { - if (acc->network->checkAndAddToUploading(acc->getName(), jid, id, path)) { + pendingStateMessages.insert(std::make_pair(id, jid)); + if (newMessage) { ri->appendMessageToArchive(data); - pendingStateMessages.insert(std::make_pair(id, jid)); } else { + QMap changes({ + {"state", (uint)Shared::Message::State::pending} + }); + ri->changeMessage(id, changes); + emit acc->changeMessage(jid, id, changes); + } + //this checks if the file is already uploading, and if so it subscribes to it's success, so, i need to do stuff only if the network knows nothing of this file + if (!acc->network->checkAndAddToUploading(acc->getName(), jid, id, path)) { if (acc->um->serviceFound()) { QFileInfo file(path); if (file.exists() && file.isReadable()) { - ri->appendMessageToArchive(data); pendingStateMessages.insert(std::make_pair(id, jid)); uploadingSlotsQueue.emplace_back(path, id); if (uploadingSlotsQueue.size() == 1) { @@ -353,7 +363,6 @@ void Core::MessageHandler::prepareUpload(const Shared::Message& data) } } - void Core::MessageHandler::onUploadSlotReceived(const QXmppHttpUploadSlotIq& slot) { if (uploadingSlotsQueue.size() == 0) { @@ -481,3 +490,22 @@ void Core::MessageHandler::requestChangeMessage(const QString& jid, const QStrin } } } + +void Core::MessageHandler::resendMessage(const QString& jid, const QString& id) +{ + RosterItem* cnt = acc->rh->getRosterItem(jid); + if (cnt != 0) { + try { + Shared::Message msg = cnt->getMessage(id); + if (msg.getState() == Shared::Message::State::error) { + sendMessage(msg, false); + } else { + qDebug() << "An attempt to resend a message to" << jid << "by account" << acc->getName() << ", but this message seems to have been normally sent, this method was made to retry sending failed to be sent messages, skipping"; + } + } catch (const Archive::NotFound& err) { + qDebug() << "An attempt to resend a message to" << jid << "by account" << acc->getName() << ", but this message wasn't found in history, skipping"; + } + } else { + qDebug() << "An attempt to resend a message to" << jid << "by account" << acc->getName() << ", but this jid isn't present in account roster, skipping"; + } +} diff --git a/core/handlers/messagehandler.h b/core/handlers/messagehandler.h index 9138245..4eb9265 100644 --- a/core/handlers/messagehandler.h +++ b/core/handlers/messagehandler.h @@ -45,8 +45,9 @@ public: MessageHandler(Account* account); public: - void sendMessage(const Shared::Message& data); + void sendMessage(const Shared::Message& data, bool newMessage = true); void initializeMessage(Shared::Message& target, const QXmppMessage& source, bool outgoing = false, bool forwarded = false, bool guessing = false) const; + void resendMessage(const QString& jid, const QString& id); public slots: void onMessageReceived(const QXmppMessage& message); @@ -66,7 +67,7 @@ private: void logMessage(const QXmppMessage& msg, const QString& reason = "Message wasn't handled: "); void sendMessageWithLocalUploadedFile(Shared::Message msg, const QString& url, bool newMessage = true); void performSending(Shared::Message data, bool newMessage = true); - void prepareUpload(const Shared::Message& data); + void prepareUpload(const Shared::Message& data, bool newMessage = true); void handleUploadError(const QString& jid, const QString& messageId, const QString& errorText); private: diff --git a/core/main.cpp b/core/main.cpp index 0090424..0be020e 100644 --- a/core/main.cpp +++ b/core/main.cpp @@ -100,6 +100,7 @@ int main(int argc, char *argv[]) QObject::connect(&w, &Squawk::disconnectAccount, squawk, &Core::Squawk::disconnectAccount); QObject::connect(&w, &Squawk::changeState, squawk, &Core::Squawk::changeState); QObject::connect(&w, &Squawk::sendMessage, squawk,&Core::Squawk::sendMessage); + QObject::connect(&w, &Squawk::resendMessage, squawk,&Core::Squawk::resendMessage); QObject::connect(&w, &Squawk::requestArchive, squawk, &Core::Squawk::requestArchive); QObject::connect(&w, &Squawk::subscribeContact, squawk, &Core::Squawk::subscribeContact); QObject::connect(&w, &Squawk::unsubscribeContact, squawk, &Core::Squawk::unsubscribeContact); diff --git a/core/squawk.cpp b/core/squawk.cpp index 411d4ab..6b8af49 100644 --- a/core/squawk.cpp +++ b/core/squawk.cpp @@ -328,13 +328,24 @@ void Core::Squawk::sendMessage(const QString& account, const Shared::Message& da { AccountsMap::const_iterator itr = amap.find(account); if (itr == amap.end()) { - qDebug("An attempt to send a message with non existing account, skipping"); + qDebug() << "An attempt to send a message with non existing account" << account << ", skipping"; return; } itr->second->sendMessage(data); } +void Core::Squawk::resendMessage(const QString& account, const QString& jid, const QString& id) +{ + AccountsMap::const_iterator itr = amap.find(account); + if (itr == amap.end()) { + qDebug() << "An attempt to resend a message with non existing account" << account << ", skipping"; + return; + } + + itr->second->resendMessage(jid, id); +} + void Core::Squawk::requestArchive(const QString& account, const QString& jid, int count, const QString& before) { AccountsMap::const_iterator itr = amap.find(account); diff --git a/core/squawk.h b/core/squawk.h index 25fdbda..338eb40 100644 --- a/core/squawk.h +++ b/core/squawk.h @@ -101,6 +101,7 @@ public slots: void changeState(Shared::Availability state); void sendMessage(const QString& account, const Shared::Message& data); + void resendMessage(const QString& account, const QString& jid, const QString& id); void requestArchive(const QString& account, const QString& jid, int count, const QString& before); void subscribeContact(const QString& account, const QString& jid, const QString& reason); diff --git a/ui/squawk.cpp b/ui/squawk.cpp index fb79592..6a0a676 100644 --- a/ui/squawk.cpp +++ b/ui/squawk.cpp @@ -466,6 +466,15 @@ void Squawk::onConversationMessage(const Shared::Message& msg) emit sendMessage(acc, msg); } +void Squawk::onConversationResend(const QString& id) +{ + Conversation* conv = static_cast(sender()); + QString acc = conv->getAccount(); + QString jid = conv->getJid(); + + emit resendMessage(acc, jid, id); +} + void Squawk::onRequestArchive(const QString& account, const QString& jid, const QString& before) { emit requestArchive(account, jid, 20, before); //TODO amount as a settings value @@ -914,6 +923,7 @@ void Squawk::subscribeConversation(Conversation* conv) { connect(conv, &Conversation::destroyed, this, &Squawk::onConversationClosed); connect(conv, &Conversation::sendMessage, this, &Squawk::onConversationMessage); + connect(conv, &Conversation::resendMessage, this, &Squawk::onConversationResend); connect(conv, &Conversation::notifyableMessage, this, &Squawk::notify); } diff --git a/ui/squawk.h b/ui/squawk.h index 15d3f82..28389fa 100644 --- a/ui/squawk.h +++ b/ui/squawk.h @@ -63,6 +63,7 @@ signals: void disconnectAccount(const QString&); void changeState(Shared::Availability state); void sendMessage(const QString& account, const Shared::Message& data); + void resendMessage(const QString& account, const QString& jid, const QString& id); void requestArchive(const QString& account, const QString& jid, int count, const QString& before); void subscribeContact(const QString& account, const QString& jid, const QString& reason); void unsubscribeContact(const QString& account, const QString& jid, const QString& reason); @@ -148,6 +149,7 @@ private slots: void onComboboxActivated(int index); void onRosterItemDoubleClicked(const QModelIndex& item); void onConversationMessage(const Shared::Message& msg); + void onConversationResend(const QString& id); void onRequestArchive(const QString& account, const QString& jid, const QString& before); void onRosterContextMenu(const QPoint& point); void onItemCollepsed(const QModelIndex& index); diff --git a/ui/widgets/conversation.cpp b/ui/widgets/conversation.cpp index 45ce2c5..d003551 100644 --- a/ui/widgets/conversation.cpp +++ b/ui/widgets/conversation.cpp @@ -414,6 +414,16 @@ void Conversation::onFeedContext(const QPoint& pos) contextMenu->clear(); bool showMenu = false; + if (item->getState() == Shared::Message::State::error) { + showMenu = true; + QString id = item->getId(); + QAction* resend = contextMenu->addAction(Shared::icon("view-refresh"), tr("Try sending again")); + connect(resend, &QAction::triggered, [this, id]() { + element->feed->registerUpload(id); + emit resendMessage(id); + }); + } + QString path = item->getAttachPath(); if (path.size() > 0) { showMenu = true; diff --git a/ui/widgets/conversation.h b/ui/widgets/conversation.h index 3f048fb..b0eb745 100644 --- a/ui/widgets/conversation.h +++ b/ui/widgets/conversation.h @@ -80,6 +80,7 @@ public: signals: void sendMessage(const Shared::Message& message); + void resendMessage(const QString& id); void requestArchive(const QString& before); void shown(); void requestLocalFile(const QString& messageId, const QString& url); From 5f925217fc1f2470ccd04b18936f08b879be81f9 Mon Sep 17 00:00:00 2001 From: blue Date: Tue, 25 May 2021 01:06:05 +0300 Subject: [PATCH 12/93] edit icon next to the message showing if the message was edited and what was there a first --- ui/widgets/messageline/messagedelegate.cpp | 66 ++++++++++++++++++++-- ui/widgets/messageline/messagedelegate.h | 2 + ui/widgets/messageline/messagefeed.cpp | 12 +++- ui/widgets/messageline/messagefeed.h | 11 +++- 4 files changed, 84 insertions(+), 7 deletions(-) diff --git a/ui/widgets/messageline/messagedelegate.cpp b/ui/widgets/messageline/messagedelegate.cpp index 9b46b7a..8728ba3 100644 --- a/ui/widgets/messageline/messagedelegate.cpp +++ b/ui/widgets/messageline/messagedelegate.cpp @@ -42,6 +42,7 @@ MessageDelegate::MessageDelegate(QObject* parent): buttons(new std::map()), bars(new std::map()), statusIcons(new std::map()), + pencilIcons(new std::map()), bodies(new std::map()), previews(new std::map()), idsToKeep(new std::set()), @@ -68,6 +69,10 @@ MessageDelegate::~MessageDelegate() delete pair.second; } + for (const std::pair& pair: *pencilIcons){ + delete pair.second; + } + for (const std::pair& pair: *bodies){ delete pair.second; } @@ -76,6 +81,8 @@ MessageDelegate::~MessageDelegate() delete pair.second; } + delete statusIcons; + delete pencilIcons; delete idsToKeep; delete buttons; delete bars; @@ -128,6 +135,18 @@ void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio if (senderSize.width() > messageSize.width()) { messageSize.setWidth(senderSize.width()); } + QSize dateSize = dateMetrics.boundingRect(messageRect, 0, data.date.toLocalTime().toString()).size(); + int addition = 0; + + if (data.correction.corrected) { + addition += margin + statusIconSize; + } + if (data.sentByMe) { + addition += margin + statusIconSize; + } + if (dateSize.width() + addition > messageSize.width()) { + messageSize.setWidth(dateSize.width() + addition); + } } else { messageSize.setWidth(opt.rect.width()); } @@ -170,6 +189,7 @@ void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio painter->restore(); int messageLeft = INT16_MAX; + int messageRight = opt.rect.x() + messageSize.width(); QWidget* vp = static_cast(painter->device()); if (data.text.size() > 0) { QLabel* body = getBody(data); @@ -192,18 +212,35 @@ void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio q.setAlpha(180); painter->setPen(q); painter->drawText(opt.rect, opt.displayAlignment, data.date.toLocalTime().toString(), &rect); + int currentY = opt.rect.y(); if (data.sentByMe) { - if (messageLeft > rect.x() - statusIconSize - margin) { - messageLeft = rect.x() - statusIconSize - margin; - } QLabel* statusIcon = getStatusIcon(data); statusIcon->setParent(vp); - statusIcon->move(messageLeft, opt.rect.y()); + statusIcon->move(opt.rect.topRight().x() - messageSize.width(), currentY); statusIcon->show(); + opt.rect.adjust(0, statusIconSize + textMargin, 0, 0); } + if (data.correction.corrected) { + QLabel* pencilIcon = getPencilIcon(data); + + pencilIcon->setParent(vp); + if (data.sentByMe) { + pencilIcon->move(opt.rect.topRight().x() - messageSize.width() + statusIconSize + margin, currentY); + } else { + pencilIcon->move(messageRight - statusIconSize - margin, currentY); + } + pencilIcon->show(); + } else { + std::map::const_iterator itr = pencilIcons->find(data.id); + if (itr != pencilIcons->end()) { + delete itr->second; + pencilIcons->erase(itr); + } + } + painter->restore(); if (clearingWidgets) { @@ -439,6 +476,26 @@ QLabel * MessageDelegate::getStatusIcon(const Models::FeedItem& data) const return result; } +QLabel * MessageDelegate::getPencilIcon(const Models::FeedItem& data) const +{ + std::map::const_iterator itr = pencilIcons->find(data.id); + QLabel* result = 0; + + if (itr != pencilIcons->end()) { + result = itr->second; + } else { + result = new QLabel(); + QIcon icon = Shared::icon("edit-rename"); + result->setPixmap(icon.pixmap(statusIconSize)); + pencilIcons->insert(std::make_pair(data.id, result)); + } + + result->setToolTip("Last time edited: " + data.correction.lastCorrection.toLocalTime().toString() + + "\nOriginal message: " + data.correction.original); + + return result; +} + QLabel * MessageDelegate::getBody(const Models::FeedItem& data) const { std::map::const_iterator itr = bodies->find(data.id); @@ -486,6 +543,7 @@ void MessageDelegate::endClearWidgets() removeElements(buttons, idsToKeep); removeElements(bars, idsToKeep); removeElements(statusIcons, idsToKeep); + removeElements(pencilIcons, idsToKeep); removeElements(bodies, idsToKeep); removeElements(previews, idsToKeep); diff --git a/ui/widgets/messageline/messagedelegate.h b/ui/widgets/messageline/messagedelegate.h index 5c2989d..7403285 100644 --- a/ui/widgets/messageline/messagedelegate.h +++ b/ui/widgets/messageline/messagedelegate.h @@ -68,6 +68,7 @@ protected: QPushButton* getButton(const Models::FeedItem& data) const; QProgressBar* getBar(const Models::FeedItem& data) const; QLabel* getStatusIcon(const Models::FeedItem& data) const; + QLabel* getPencilIcon(const Models::FeedItem& data) const; QLabel* getBody(const Models::FeedItem& data) const; void clearHelperWidget(const Models::FeedItem& data) const; @@ -93,6 +94,7 @@ private: std::map* buttons; std::map* bars; std::map* statusIcons; + std::map* pencilIcons; std::map* bodies; std::map* previews; std::set* idsToKeep; diff --git a/ui/widgets/messageline/messagefeed.cpp b/ui/widgets/messageline/messagefeed.cpp index 9537ea5..733cf1d 100644 --- a/ui/widgets/messageline/messagefeed.cpp +++ b/ui/widgets/messageline/messagefeed.cpp @@ -262,7 +262,7 @@ QVariant Models::MessageFeed::data(const QModelIndex& index, int role) const answer = static_cast(msg->getState()); break; case Correction: - answer = msg->getEdited(); + answer.setValue(fillCorrection(*msg));; break; case SentByMe: answer = sentByMe(*msg); @@ -311,7 +311,7 @@ QVariant Models::MessageFeed::data(const QModelIndex& index, int role) const item.date = msg->getTime(); item.state = msg->getState(); item.error = msg->getErrorText(); - item.correction = msg->getEdited(); + item.correction = fillCorrection(*msg); QString body = msg->getBody(); if (body != msg->getOutOfBandUrl()) { @@ -480,6 +480,14 @@ Models::Attachment Models::MessageFeed::fillAttach(const Shared::Message& msg) c return att; } +Models::Edition Models::MessageFeed::fillCorrection(const Shared::Message& msg) const +{ + ::Models::Edition ed({msg.getEdited(), msg.getOriginalBody(), msg.getLastModified()}); + + return ed; +} + + void Models::MessageFeed::downloadAttachment(const QString& messageId) { bool notify = false; diff --git a/ui/widgets/messageline/messagefeed.h b/ui/widgets/messageline/messagefeed.h index b368a3d..2273b15 100644 --- a/ui/widgets/messageline/messagefeed.h +++ b/ui/widgets/messageline/messagefeed.h @@ -37,6 +37,7 @@ namespace Models { class Element; struct Attachment; + struct Edition; class MessageFeed : public QAbstractListModel { @@ -106,6 +107,7 @@ public: protected: bool sentByMe(const Shared::Message& msg) const; Attachment fillAttach(const Shared::Message& msg) const; + Edition fillCorrection(const Shared::Message& msg) const; QModelIndex modelIndexById(const QString& id) const; QModelIndex modelIndexByTime(const QString& id, const QDateTime& time) const; std::set detectChanges(const Shared::Message& msg, const QMap& data) const; @@ -180,6 +182,12 @@ struct Attachment { QString error; }; +struct Edition { + bool corrected; + QString original; + QDateTime lastCorrection; +}; + struct FeedItem { QString id; QString text; @@ -187,7 +195,7 @@ struct FeedItem { QString avatar; QString error; bool sentByMe; - bool correction; + Edition correction; QDateTime date; Shared::Message::State state; Attachment attach; @@ -195,6 +203,7 @@ struct FeedItem { }; Q_DECLARE_METATYPE(Models::Attachment); +Q_DECLARE_METATYPE(Models::Edition); Q_DECLARE_METATYPE(Models::FeedItem); #endif // MESSAGEFEED_H From 87c216b491b6965426682d3f2e880740fd9bc3b2 Mon Sep 17 00:00:00 2001 From: Bruno Fontes Date: Wed, 21 Jul 2021 19:37:31 -0300 Subject: [PATCH 13/93] Add Brazilian Portuguese translations --- packaging/squawk.desktop | 2 + translations/squawk.pt_BR.ts | 795 +++++++++++++++++++++++++++++++++++ 2 files changed, 797 insertions(+) create mode 100644 translations/squawk.pt_BR.ts diff --git a/packaging/squawk.desktop b/packaging/squawk.desktop index 0395af1..ba0f13c 100644 --- a/packaging/squawk.desktop +++ b/packaging/squawk.desktop @@ -5,8 +5,10 @@ Version=1.0 Name=Squawk GenericName=Instant Messenger GenericName[ru]=Мгновенные сообщения +GenericName[pt_BR]=Mensageiro instantâneo Comment=XMPP (Jabber) instant messenger client Comment[ru]=XMPP (Jabber) клиент обмена мгновенными сообщениями +Comment[pt_BR]=Cliente de mensagem instantânea XMPP (Jabber) Exec=squawk %u Icon=squawk StartupNotify=true diff --git a/translations/squawk.pt_BR.ts b/translations/squawk.pt_BR.ts new file mode 100644 index 0000000..4080e5d --- /dev/null +++ b/translations/squawk.pt_BR.ts @@ -0,0 +1,795 @@ + + + + + Account + + Account + Conta + + + Your account login + Suas informações de login + + + john_smith1987 + josé_silva1987 + + + Server + Servidor + + + A server address of your account. Like 404.city or macaw.me + O endereço do servidor da sua conta, como o 404.city ou o macaw.me + + + macaw.me + macaw.me + + + Login + Usuário + + + Password + Senha + + + Password of your account + Senha da sua conta + + + Name + Nome + + + 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 + + + John + José + + + Resource + Recurso + + + A resource name like "Home" or "Work" + Um nome de recurso como "Casa" ou "Trabalho" + + + QXmpp + QXmpp + + + Password storage + Armazenamento de senha + + + + Accounts + + Accounts + Contas + + + Delete + Apagar + + + Add + Adicionar + + + Edit + Editar + + + Change password + Alterar senha + + + Connect + Conectar + + + Disconnect + Desconectar + + + + Conversation + + Type your message here... + Digite sua mensagem aqui... + + + Chose a file to send + Escolha um arquivo para enviar + + + <!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;"> +<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 + + + + Global + + Online + Availability + Conectado + + + Away + Availability + Distante + + + Absent + Availability + Ausente + + + Busy + Availability + Ocupado + + + Chatty + Availability + Tagarela + + + Invisible + Availability + Invisível + + + Offline + Availability + Desconectado + + + Disconnected + ConnectionState + Desconectado + + + Connecting + ConnectionState + Connectando + + + Connected + ConnectionState + Conectado + + + Error + ConnectionState + Erro + + + None + SubscriptionState + Nenhum + + + From + SubscriptionState + De + + + To + SubscriptionState + Para + + + Both + SubscriptionState + Ambos + + + Unknown + SubscriptionState + Desconhecido + + + Unspecified + Affiliation + Não especificado + + + Outcast + Affiliation + Rejeitado + + + Nobody + Affiliation + Ninguém + + + Member + Affiliation + Membro + + + Admin + Affiliation + Administrador + + + Owner + Affiliation + Dono + + + Unspecified + Role + Não especificado + + + Nobody + Role + Ninguém + + + Visitor + Role + Visitante + + + Participant + Role + Participante + + + Moderator + Role + Moderador + + + Pending + MessageState + Aguardando + + + Sent + MessageState + Enviada + + + Delivered + MessageState + Entregue + + + Error + MessageState + Erro + + + Plain + AccountPassword + Texto simples + + + Jammed + AccountPassword + Embaralhado + + + Always Ask + AccountPassword + Sempre perguntar + + + KWallet + AccountPassword + KWallet + + + 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 + Sua senha será armazenada em um arquivo de configurações, porém embaralhada com uma chave criptográfica constante que você pode encontrar no código fonte do programa. Parece criptografado, mas não é + + + Squawk is going to query you for the password on every start of the program + AccountPasswordDescription + O Squark vai requisitar sua senha a cada vez que você abrir o programa + + + Your password is going to be stored in config file in plain text + AccountPasswordDescription + Sua senha será armazenada em um arquivo de configurações em texto simples + + + Your password is going to be stored in KDE wallet storage (KWallet). You're going to be queried for permissions + AccountPasswordDescription + Sua senha será armazenada no KDE wallet (KWallet). Sua autorização será requerida + + + + JoinConference + + Join new conference + Заголовок окна + Entrar em uma nova conferência + + + JID + JID + + + Room JID + Sala JID + + + identifier@conference.server.org + identifier@conference.server.org + + + Account + Conta + + + Join on login + Entrar ao se conectar + + + If checked Squawk will try to join this conference on login + Se marcado, o Squawk tentará entrar nesta conferência automaticamente durante o login + + + Nick name + Apelido + + + Your nick name for that conference. If you leave this field empty your account name will be used as a nick name + Seu apelido para essa conferência. Se você deixar este campo em branco, seu nome de usuário será usado como apelido + + + John + José + + + + Message + + Open + Abrir + + + + MessageLine + + Downloading... + Baixando... + + + Download + Baixar + + + Error uploading file: %1 +You can try again + Error fazendo upload do arquivo: %1 +Você pode tentar novamente + + + Upload + Upload + + + Error downloading file: %1 +You can try again + Erro baixando arquivo: %1 +Você pode tentar novamente + + + Uploading... + Fazendo upload... + + + Push the button to download the file + Pressione o botão para baixar o arquivo + + + + Models::Room + + Subscribed + Inscrito + + + Temporarily unsubscribed + Inscrição temporariamente cancelada + + + Temporarily subscribed + Temporariamente inscrito + + + Unsubscribed + Não inscrito + + + + Models::Roster + + New messages + Novas mensagens + + + New messages: + Novas mensagens: + + + Jabber ID: + ID Jabber: + + + Availability: + Disponibilidade: + + + Status: + Status: + + + Subscription: + Inscrição: + + + Affiliation: + Я правда не знаю, как это объяснить, не то что перевести + Afiliação: + + + Role: + Papel: + + + Online contacts: + Contatos online: + + + Total contacts: + Contatos totais: + + + Members: + Membros: + + + + NewContact + + Add new contact + Заголовок окна + Adicionar novo contato + + + Account + Conta + + + An account that is going to have new contact + A conta que terá um novo contato + + + JID + JID + + + Jabber id of your new contact + ID Jabber do seu novo contato + + + name@server.dmn + Placeholder поля ввода JID + nome@servidor.com.br + + + Name + Nome + + + The way this new contact will be labeled in your roster (optional) + A forma com que o novo contato será classificado em sua lista (opcional) + + + John Smith + José Silva + + + + Squawk + + squawk + Squawk + + + Settings + Configurações + + + Squawk + Squawk + + + Accounts + Contas + + + Quit + Sair + + + Add contact + Adicionar contato + + + Add conference + Adicionar conferência + + + Disconnect + Desconectar + + + Connect + Conectar + + + VCard + VCard + + + Remove + Remover + + + Open dialog + Abrir caixa de diálogo + + + Unsubscribe + Cancelar inscrição + + + Subscribe + Inscrever-se + + + Rename + Renomear + + + Input new name for %1 +or leave it empty for the contact +to be displayed as %1 + Digite um novo nome para %1 +ou o deixe em branco para o contato +ser exibido com %1 + + + Renaming %1 + Renomeando %1 + + + Groups + Grupos + + + New group + Novo grupo + + + New group name + Novo nome do grupo + + + Add %1 to a new group + Adicionar %1 a um novo grupo + + + Open conversation + Abrir conversa + + + %1 account card + cartão da conta %1 + + + %1 contact card + cartão de contato %1 + + + Downloading vCard + Baixando vCard + + + Attached file + Arquivo anexado + + + Input the password for account %1 + Digite a senha para a conta %1 + + + Password for account %1 + Senha para a conta %1 + + + Please select a contact to start chatting + Por favor selecione um contato para começar a conversar + + + + VCard + + Received 12.07.2007 at 17.35 + Recebido 12/07/2007 às 17:35 + + + General + Geral + + + Organization + Empresa + + + Middle name + Nome do meio + + + First name + Primeiro nome + + + Last name + Sobrenome + + + Nick name + Apelido + + + Birthday + Data de aniversário + + + Organization name + Nome da empresa + + + Unit / Department + Unidade/Departamento + + + Role / Profession + Profissão + + + Job title + Cargo + + + Full name + Nome completo + + + Personal information + Informações pessoais + + + Addresses + Endereços + + + E-Mail addresses + Endereços de e-mail + + + Phone numbers + Números de telefone + + + Contact + Contato + + + Jabber ID + ID Jabber + + + Web site + Site web + + + Description + Descrição + + + Set avatar + Definir avatar + + + Clear avatar + Apagar avatar + + + Account %1 card + Cartão da conta %1 + + + Contact %1 card + Cartão do contato %1 + + + Received %1 at %2 + Recebido %1 em %2 + + + Chose your new avatar + Escolha um novo avatar + + + Images (*.png *.jpg *.jpeg) + Imagens (*.png *.jpg *.jpeg) + + + Add email address + Adicionar endereço de e-mail + + + Unset this email as preferred + Desmarcar este e-mail como preferido + + + Set this email as preferred + Marcar este e-mail como preferido + + + Remove selected email addresses + Remover endereço de e-mail selecionado + + + Copy selected emails to clipboard + Copiar endereços de e-mails selecionados para a área de transferência + + + Add phone number + Adicionar número de telefone + + + Unset this phone as preferred + Desmarcar este número de telefone como preferido + + + Set this phone as preferred + Marcar este número de telefone como preferido + + + Remove selected phone numbers + Remover os números de telefones selecionados + + + Copy selected phones to clipboard + Copiar os números de telefone selecionados para a área de transferência + + + From 3f09b8f838d78bd7029a82868d1e8edaa5c957b7 Mon Sep 17 00:00:00 2001 From: Blue Date: Wed, 22 Sep 2021 01:17:43 +0300 Subject: [PATCH 14/93] Date dividers between messages from different dates --- CMakeLists.txt | 10 ++++ shared/global.cpp | 2 +- ui/widgets/messageline/feedview.cpp | 62 +++++++++++++++++++++- ui/widgets/messageline/feedview.h | 3 ++ ui/widgets/messageline/messagedelegate.cpp | 5 +- 5 files changed, 78 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b9349d9..b978c33 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,6 +22,16 @@ option(WITH_KIO "Build KIO support module" ON) # Dependencies ## Qt find_package(Qt5 COMPONENTS Widgets DBus Gui Xml Network Core REQUIRED) +find_package(Boost COMPONENTS) + +target_include_directories(squawk PRIVATE ${Boost_INCLUDE_DIRS}) +target_include_directories(squawk PRIVATE ${Qt5_INCLUDE_DIRS}) +target_include_directories(squawk PRIVATE ${Qt5Widgets_INCLUDE_DIRS}) +target_include_directories(squawk PRIVATE ${Qt5DBus_INCLUDE_DIRS}) +target_include_directories(squawk PRIVATE ${Qt5Gui_INCLUDE_DIRS}) +target_include_directories(squawk PRIVATE ${Qt5Xml_INCLUDE_DIRS}) +target_include_directories(squawk PRIVATE ${Qt5Network_INCLUDE_DIRS}) +target_include_directories(squawk PRIVATE ${Qt5Core_INCLUDE_DIRS}) ## QXmpp if (SYSTEM_QXMPP) diff --git a/shared/global.cpp b/shared/global.cpp index 67e74d1..d6f2169 100644 --- a/shared/global.cpp +++ b/shared/global.cpp @@ -95,7 +95,7 @@ Shared::Global::Global(): } instance = this; - + #ifdef WITH_KIO openFileManagerWindowJob.load(); if (openFileManagerWindowJob.isLoaded()) { diff --git a/ui/widgets/messageline/feedview.cpp b/ui/widgets/messageline/feedview.cpp index 6d8c180..7bdfb9e 100644 --- a/ui/widgets/messageline/feedview.cpp +++ b/ui/widgets/messageline/feedview.cpp @@ -29,6 +29,7 @@ constexpr int maxMessageHeight = 10000; constexpr int approximateSingleMessageHeight = 20; constexpr int progressSize = 70; +constexpr int dateDeviderMargin = 10; const std::set FeedView::geometryChangingRoles = { Models::MessageFeed::Attach, @@ -46,7 +47,9 @@ FeedView::FeedView(QWidget* parent): specialModel(false), clearWidgetsMode(false), modelState(Models::MessageFeed::complete), - progress() + progress(), + dividerFont(), + dividerMetrics(dividerFont) { horizontalScrollBar()->setRange(0, 0); verticalScrollBar()->setSingleStep(approximateSingleMessageHeight); @@ -56,6 +59,15 @@ FeedView::FeedView(QWidget* parent): progress.setParent(viewport()); progress.resize(progressSize, progressSize); + + dividerFont = getFont(); + dividerFont.setBold(true); + float ndps = dividerFont.pointSizeF(); + if (ndps != -1) { + dividerFont.setPointSizeF(ndps * 1.2); + } else { + dividerFont.setPointSize(dividerFont.pointSize() + 2); + } } FeedView::~FeedView() @@ -187,8 +199,16 @@ void FeedView::updateGeometries() hints.clear(); uint32_t previousOffset = 0; + QDateTime lastDate; for (int i = 0, size = m->rowCount(); i < size; ++i) { QModelIndex index = m->index(i, 0, rootIndex()); + QDateTime currentDate = index.data(Models::MessageFeed::Date).toDateTime(); + if (i > 0) { + if (currentDate.daysTo(lastDate) > 0) { + previousOffset += dividerMetrics.height() + dateDeviderMargin * 2; + } + } + lastDate = currentDate; int height = itemDelegate(index)->sizeHint(option, index).height(); hints.emplace_back(Hint({ false, @@ -222,8 +242,16 @@ bool FeedView::tryToCalculateGeometriesWithNoScrollbars(const QStyleOptionViewIt { uint32_t previousOffset = 0; bool success = true; + QDateTime lastDate; for (int i = 0, size = m->rowCount(); i < size; ++i) { QModelIndex index = m->index(i, 0, rootIndex()); + QDateTime currentDate = index.data(Models::MessageFeed::Date).toDateTime(); + if (i > 0) { + if (currentDate.daysTo(lastDate) > 0) { + previousOffset += dateDeviderMargin * 2 + dividerMetrics.height(); + } + } + lastDate = currentDate; int height = itemDelegate(index)->sizeHint(option, index).height(); if (previousOffset + height > totalHeight) { @@ -266,6 +294,7 @@ void FeedView::paintEvent(QPaintEvent* event) toRener.emplace_back(m->index(i, 0, rootIndex())); } if (y1 > relativeY1) { + inZone = false; break; } } @@ -282,11 +311,32 @@ void FeedView::paintEvent(QPaintEvent* event) } } + QDateTime lastDate; + bool first = true; for (const QModelIndex& index : toRener) { + QDateTime currentDate = index.data(Models::MessageFeed::Date).toDateTime(); option.rect = visualRect(index); + if (first) { + int ind = index.row() - 1; + if (ind > 0) { + QDateTime underDate = m->index(ind, 0, rootIndex()).data(Models::MessageFeed::Date).toDateTime(); + if (currentDate.daysTo(underDate) > 0) { + drawDateDevider(option.rect.bottom(), underDate, painter); + } + } + first = false; + } bool mouseOver = option.rect.contains(cursor) && vp->rect().contains(cursor); option.state.setFlag(QStyle::State_MouseOver, mouseOver); itemDelegate(index)->paint(&painter, option, index); + + if (!lastDate.isNull() && currentDate.daysTo(lastDate) > 0) { + drawDateDevider(option.rect.bottom(), lastDate, painter); + } + lastDate = currentDate; + } + if (!lastDate.isNull() && inZone) { //if after drawing all messages there is still space + drawDateDevider(option.rect.bottom(), lastDate, painter); } if (clearWidgetsMode && specialDelegate) { @@ -300,6 +350,16 @@ void FeedView::paintEvent(QPaintEvent* event) } } +void FeedView::drawDateDevider(int top, const QDateTime& date, QPainter& painter) +{ + int divisionHeight = dateDeviderMargin * 2 + dividerMetrics.height(); + QRect r(QPoint(0, top), QSize(viewport()->width(), divisionHeight)); + painter.save(); + painter.setFont(dividerFont); + painter.drawText(r, Qt::AlignCenter, date.toString("d MMMM")); + painter.restore(); +} + void FeedView::verticalScrollbarValueChanged(int value) { vo = verticalScrollBar()->maximum() - value; diff --git a/ui/widgets/messageline/feedview.h b/ui/widgets/messageline/feedview.h index b20276c..5e08946 100644 --- a/ui/widgets/messageline/feedview.h +++ b/ui/widgets/messageline/feedview.h @@ -73,6 +73,7 @@ protected: private: bool tryToCalculateGeometriesWithNoScrollbars(const QStyleOptionViewItem& option, const QAbstractItemModel* model, uint32_t totalHeight); void positionProgress(); + void drawDateDevider(int top, const QDateTime& date, QPainter& painter); private: struct Hint { @@ -87,6 +88,8 @@ private: bool clearWidgetsMode; Models::MessageFeed::SyncState modelState; Progress progress; + QFont dividerFont; + QFontMetrics dividerMetrics; static const std::set geometryChangingRoles; diff --git a/ui/widgets/messageline/messagedelegate.cpp b/ui/widgets/messageline/messagedelegate.cpp index 8728ba3..649230e 100644 --- a/ui/widgets/messageline/messagedelegate.cpp +++ b/ui/widgets/messageline/messagedelegate.cpp @@ -130,12 +130,13 @@ void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio } messageSize.rheight() += nickMetrics.lineSpacing(); messageSize.rheight() += dateMetrics.height(); + QString dateString = data.date.toLocalTime().toString("hh:mm"); if (messageSize.width() < opt.rect.width()) { QSize senderSize = nickMetrics.boundingRect(messageRect, 0, data.sender).size(); if (senderSize.width() > messageSize.width()) { messageSize.setWidth(senderSize.width()); } - QSize dateSize = dateMetrics.boundingRect(messageRect, 0, data.date.toLocalTime().toString()).size(); + QSize dateSize = dateMetrics.boundingRect(messageRect, 0, dateString).size(); int addition = 0; if (data.correction.corrected) { @@ -211,7 +212,7 @@ void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio QColor q = painter->pen().color(); q.setAlpha(180); painter->setPen(q); - painter->drawText(opt.rect, opt.displayAlignment, data.date.toLocalTime().toString(), &rect); + painter->drawText(opt.rect, opt.displayAlignment, dateString, &rect); int currentY = opt.rect.y(); if (data.sentByMe) { QLabel* statusIcon = getStatusIcon(data); From d89c030e667a2af23777d915c7610faec636d585 Mon Sep 17 00:00:00 2001 From: blue Date: Wed, 22 Sep 2021 23:43:03 +0300 Subject: [PATCH 15/93] translation verification Portugues Brazil localization added provided by most welcome Bruno Fontes --- translations/CMakeLists.txt | 7 +++++-- translations/squawk.pt_BR.ts | 11 ++++++++--- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/translations/CMakeLists.txt b/translations/CMakeLists.txt index c484000..86d2a8c 100644 --- a/translations/CMakeLists.txt +++ b/translations/CMakeLists.txt @@ -1,8 +1,11 @@ find_package(Qt5LinguistTools) -set(TS_FILES squawk.ru.ts) +set(TS_FILES + squawk.ru.ts + squawk.pt_BR.ts +) qt5_add_translation(QM_FILES ${TS_FILES}) add_custom_target(translations ALL DEPENDS ${QM_FILES}) install(FILES ${QM_FILES} DESTINATION ${CMAKE_INSTALL_DATADIR}/squawk/l10n) -add_dependencies(${CMAKE_PROJECT_NAME} translations) \ No newline at end of file +add_dependencies(${CMAKE_PROJECT_NAME} translations) diff --git a/translations/squawk.pt_BR.ts b/translations/squawk.pt_BR.ts index 4080e5d..4330979 100644 --- a/translations/squawk.pt_BR.ts +++ b/translations/squawk.pt_BR.ts @@ -5,6 +5,7 @@ Account Account + Window header Conta @@ -61,6 +62,7 @@ QXmpp + Default resource QXmpp @@ -384,7 +386,8 @@ p, li { white-space: pre-wrap; } Error uploading file: %1 You can try again - Error fazendo upload do arquivo: %1 + Error fazendo upload do arquivo: +%1 Você pode tentar novamente @@ -394,7 +397,8 @@ Você pode tentar novamente Error downloading file: %1 You can try again - Erro baixando arquivo: %1 + Erro baixando arquivo: +%1 Você pode tentar novamente @@ -582,7 +586,8 @@ or leave it empty for the contact to be displayed as %1 Digite um novo nome para %1 ou o deixe em branco para o contato -ser exibido com %1 +ser exibido com +%1 Renaming %1 From 5fbb96618fb475a296e9f00f04b3bc79758efc4c Mon Sep 17 00:00:00 2001 From: shunf4 Date: Tue, 5 Oct 2021 12:49:06 +0800 Subject: [PATCH 16/93] adjust CMakeLists.txt, to prepare for win32 and macos builds --- CMakeLists.txt | 61 ++++++++++++++++++++++++++++++++++----- core/CMakeLists.txt | 22 +++++++++++++- signalcatcher_win32.cpp | 42 +++++++++++++++++++++++++++ squawk.rc | 1 + ui/CMakeLists.txt | 5 ++++ ui/widgets/CMakeLists.txt | 10 +++++++ 6 files changed, 132 insertions(+), 9 deletions(-) create mode 100644 signalcatcher_win32.cpp create mode 100644 squawk.rc diff --git a/CMakeLists.txt b/CMakeLists.txt index 771481f..194c88b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,15 +18,17 @@ if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Debug) endif() -set(CMAKE_CXX_FLAGS_DEBUG "-g -Wall -Wextra") -set(CMAKE_CXX_FLAGS_RELEASE "-O3") +if(CMAKE_COMPILER_IS_GNUCXX) + set(CMAKE_CXX_FLAGS_DEBUG "-g -Wall -Wextra") + set(CMAKE_CXX_FLAGS_RELEASE "-O3") +endif(CMAKE_COMPILER_IS_GNUCXX) + message("Build type: ${CMAKE_BUILD_TYPE}") set(squawk_SRC main.cpp exception.cpp - signalcatcher.cpp shared/global.cpp shared/utils.cpp shared/message.cpp @@ -34,6 +36,13 @@ set(squawk_SRC shared/icons.cpp ) +if (WIN32) + list(APPEND squawk_SRC signalcatcher_win32.cpp) +else (WIN32) + list(APPEND squawk_SRC signalcatcher.cpp) +endif (WIN32) + + set(squawk_HEAD exception.h signalcatcher.h @@ -47,10 +56,40 @@ set(squawk_HEAD ) configure_file(resources/images/logo.svg squawk.svg COPYONLY) -execute_process(COMMAND convert -background none -size 48x48 squawk.svg squawk48.png WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) -execute_process(COMMAND convert -background none -size 64x64 squawk.svg squawk64.png WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) -execute_process(COMMAND convert -background none -size 128x128 squawk.svg squawk128.png WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) -execute_process(COMMAND convert -background none -size 256x256 squawk.svg squawk256.png WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) +if (WIN32) + set(CONVERT_BIN magick convert) +else(WIN32) + set(CONVERT_BIN convert) +endif(WIN32) + +execute_process(COMMAND ${CONVERT_BIN} -background none -size 48x48 squawk.svg squawk48.png WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) +execute_process(COMMAND ${CONVERT_BIN} -background none -size 64x64 squawk.svg squawk64.png WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) +execute_process(COMMAND ${CONVERT_BIN} -background none -size 128x128 squawk.svg squawk128.png WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) +execute_process(COMMAND ${CONVERT_BIN} -background none -size 256x256 squawk.svg squawk256.png WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + +if (WIN32) + execute_process(COMMAND ${CONVERT_BIN} squawk48.png squawk64.png squawk256.png squawk.ico WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + set(SQUAWK_WIN_RC "${CMAKE_CURRENT_BINARY_DIR}/squawk.rc") +endif(WIN32) + +if (APPLE) + file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/icns.iconset") + execute_process(COMMAND convert -background none -size 16x16 squawk.svg icns.iconset/icon_16x16.png WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + execute_process(COMMAND convert -background none -resize !32x32 squawk.svg "icns.iconset/icon_16x16@2x.png" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + execute_process(COMMAND convert -background none -resize !32x32 squawk.svg "icns.iconset/icon_32x32.png" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + execute_process(COMMAND convert -background none -resize !64x64 squawk.svg "icns.iconset/icon_32x32@2x.png" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + execute_process(COMMAND convert -background none -resize !128x128 squawk.svg "icns.iconset/icon_128x128.png" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + execute_process(COMMAND convert -background none -resize !256x256 squawk.svg "icns.iconset/icon_128x128@2x.png" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + execute_process(COMMAND convert -background none -resize !256x256 squawk.svg "icns.iconset/icon_256x256.png" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + execute_process(COMMAND convert -background none -resize !512x512 squawk.svg "icns.iconset/icon_256x256@2x.png" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + execute_process(COMMAND convert -background none -resize !512x512 squawk.svg "icns.iconset/icon_512x512.png" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + execute_process(COMMAND convert -background none -resize !1024x1024 squawk.svg "icns.iconset/icon_512x512@2x.png" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + execute_process(COMMAND iconutil -c icns "icns.iconset" -o "squawk.icns" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + set(MACOSX_BUNDLE_ICON_FILE squawk.icns) + set(APP_ICON_MACOSX ${CMAKE_CURRENT_BINARY_DIR}/squawk.icns) + set_source_files_properties(${APP_ICON_MACOSX} PROPERTIES + MACOSX_PACKAGE_LOCATION "Resources") +endif (APPLE) configure_file(packaging/squawk.desktop squawk.desktop COPYONLY) @@ -92,7 +131,13 @@ if (WITH_KWALLET) endif() endif() -add_executable(squawk ${squawk_SRC} ${squawk_HEAD} ${RCC}) +set(WIN32_FLAG "") +if (WIN32) + if (CMAKE_BUILD_TYPE STREQUAL "Release") + set(WIN32_FLAG WIN32) + endif() +endif(WIN32) +add_executable(squawk ${WIN32_FLAG} ${squawk_SRC} ${squawk_HEAD} ${RCC} ${SQUAWK_WIN_RC} ${APP_ICON_MACOSX}) target_link_libraries(squawk Qt5::Widgets) add_subdirectory(ui) diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index b74a055..0377620 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -8,6 +8,19 @@ find_package(Qt5Gui CONFIG REQUIRED) find_package(Qt5Network CONFIG REQUIRED) find_package(Qt5Xml CONFIG REQUIRED) +# Find LMDB from system or ${LMDB_DIR} +find_path(LMDB_INCLUDE_DIR NAMES lmdb.h PATHS "${LMDB_DIR}/include") + +if (UNIX AND NOT APPLE) + # Linux + find_library(LMDB_LIBRARIES NAMES liblmdb.a PATHS ${LMDB_DIR}) + set(THREADS_PREFER_PTHREAD_FLAG ON) + find_package(Threads REQUIRED) +else (UNIX AND NOT APPLE) + # Windows / macOS: LMDB_DIR has to be specified + set(LMDB_LIBRARIES "${LMDB_DIR}/lib/liblmdb.a") +endif (UNIX AND NOT APPLE) + set(squawkCORE_SRC squawk.cpp account.cpp @@ -27,6 +40,7 @@ add_subdirectory(passwordStorageEngines) # Tell CMake to create the helloworld executable add_library(squawkCORE ${squawkCORE_SRC}) +target_include_directories(squawkCORE PUBLIC ${LMDB_INCLUDE_DIR}) if(SYSTEM_QXMPP) get_target_property(QXMPP_INTERFACE_INCLUDE_DIRECTORIES QXmpp::QXmpp INTERFACE_INCLUDE_DIRECTORIES) @@ -39,7 +53,13 @@ target_link_libraries(squawkCORE Qt5::Network) target_link_libraries(squawkCORE Qt5::Gui) target_link_libraries(squawkCORE Qt5::Xml) target_link_libraries(squawkCORE qxmpp) -target_link_libraries(squawkCORE lmdb) + +target_link_libraries(squawkCORE ${LMDB_LIBRARIES}) +if (UNIX AND NOT APPLE) + # Linux + target_link_libraries(squawkCORE Threads::Threads) +endif (UNIX AND NOT APPLE) + target_link_libraries(squawkCORE simpleCrypt) if (WITH_KWALLET) target_link_libraries(squawkCORE kwalletPSE) diff --git a/signalcatcher_win32.cpp b/signalcatcher_win32.cpp new file mode 100644 index 0000000..ca7b5a2 --- /dev/null +++ b/signalcatcher_win32.cpp @@ -0,0 +1,42 @@ +/* + * Squawk messenger. + * Copyright (C) 2021 Shunf4 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 "signalcatcher.h" +#include + +SignalCatcher::SignalCatcher(QCoreApplication *p_app, QObject *parent): + QObject(parent), + app(p_app) +{ +} + +SignalCatcher::~SignalCatcher() +{} + +void SignalCatcher::handleSigInt() +{ +} + +void SignalCatcher::intSignalHandler(int unused) +{ +} + +int SignalCatcher::setup_unix_signal_handlers() +{ + return 0; +} diff --git a/squawk.rc b/squawk.rc new file mode 100644 index 0000000..6061f20 --- /dev/null +++ b/squawk.rc @@ -0,0 +1 @@ +IDI_ICON1 ICON "squawk.ico" diff --git a/ui/CMakeLists.txt b/ui/CMakeLists.txt index 52913a8..8fd04ff 100644 --- a/ui/CMakeLists.txt +++ b/ui/CMakeLists.txt @@ -36,6 +36,11 @@ set(squawkUI_SRC utils/dropshadoweffect.cpp ) +# Add squawk.ui to squawkUI_SRC so that the .ui files are displayed in Qt Creator +qt5_wrap_ui(squawkUI_SRC + squawk.ui +) + # Tell CMake to create the helloworld executable add_library(squawkUI ${squawkUI_SRC}) diff --git a/ui/widgets/CMakeLists.txt b/ui/widgets/CMakeLists.txt index 29e2dbc..ffd661e 100644 --- a/ui/widgets/CMakeLists.txt +++ b/ui/widgets/CMakeLists.txt @@ -21,6 +21,16 @@ set(squawkWidgets_SRC joinconference.cpp ) +# Add to squawkUI_SRC so that the .ui files are displayed in Qt Creator +qt5_wrap_ui(squawkWidgets_SRC + account.ui + accounts.ui + conversation.ui + joinconference.ui + newcontact.ui + vcard/vcard.ui +) + # Tell CMake to create the helloworld executable add_library(squawkWidgets ${squawkWidgets_SRC}) From 6764d8ea1104b8cd5787bb87ed3bdc8c5867edf2 Mon Sep 17 00:00:00 2001 From: shunf4 Date: Tue, 5 Oct 2021 12:56:36 +0800 Subject: [PATCH 17/93] remove dependency libuuid --- CMakeLists.txt | 1 - shared/utils.cpp | 8 ++------ shared/utils.h | 1 - 3 files changed, 2 insertions(+), 8 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 194c88b..290568f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -147,7 +147,6 @@ add_subdirectory(external/simpleCrypt) target_link_libraries(squawk squawkUI) target_link_libraries(squawk squawkCORE) -target_link_libraries(squawk uuid) add_dependencies(${CMAKE_PROJECT_NAME} translations) diff --git a/shared/utils.cpp b/shared/utils.cpp index 924be85..a7a4ecb 100644 --- a/shared/utils.cpp +++ b/shared/utils.cpp @@ -17,15 +17,11 @@ */ #include "utils.h" +#include QString Shared::generateUUID() { - uuid_t uuid; - uuid_generate(uuid); - - char uuid_str[36]; - uuid_unparse_lower(uuid, uuid_str); - return uuid_str; + return QUuid::createUuid().toString(); } diff --git a/shared/utils.h b/shared/utils.h index e9e3d29..8e1e6dd 100644 --- a/shared/utils.h +++ b/shared/utils.h @@ -23,7 +23,6 @@ #include #include -#include #include namespace Shared { From c55b7c6baf932c7f8ac82f2fa5f16658bf6889ea Mon Sep 17 00:00:00 2001 From: shunf4 Date: Tue, 5 Oct 2021 15:11:43 +0800 Subject: [PATCH 18/93] update docs for removed uuid dep and LMDB_DIR var --- README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 30c6473..987ca53 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,6 @@ ### Prerequisites - QT 5.12 *(lower versions might work but it wasn't tested)* -- uuid _(usually included in some other package, for example it's ***libutil-linux*** in archlinux)_ - lmdb - CMake 3.0 or higher - qxmpp 1.1.0 or higher @@ -44,7 +43,7 @@ $ git clone https://git.macaw.me/blue/squawk $ cd squawk $ mkdir build $ cd build -$ cmake .. +$ cmake .. [-DLMDB_DIR:PATH=/path/to/lmdb] $ cmake --build . ``` @@ -57,7 +56,7 @@ $ git clone --recurse-submodules https://git.macaw.me/blue/squawk $ cd squawk $ mkdir build $ cd build -$ cmake .. -D SYSTEM_QXMPP=False +$ cmake .. -D SYSTEM_QXMPP=False [-DLMDB_DIR:PATH=/path/to/lmdb] $ cmake --build . ``` From faa7d396a5665e385470f15ebc7841bcabb03f11 Mon Sep 17 00:00:00 2001 From: shunf4 Date: Tue, 5 Oct 2021 16:09:31 +0800 Subject: [PATCH 19/93] add liblmdb.so as possible lmdb lib name --- core/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index 0377620..306b4f5 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -13,7 +13,7 @@ find_path(LMDB_INCLUDE_DIR NAMES lmdb.h PATHS "${LMDB_DIR}/include") if (UNIX AND NOT APPLE) # Linux - find_library(LMDB_LIBRARIES NAMES liblmdb.a PATHS ${LMDB_DIR}) + find_library(sudo rLMDB_LIBRARIES NAMES liblmdb.a liblmdb.so PATHS ${LMDB_DIR}) set(THREADS_PREFER_PTHREAD_FLAG ON) find_package(Threads REQUIRED) else (UNIX AND NOT APPLE) From a1f3c00a5454c7908949625ed4be87bfff966951 Mon Sep 17 00:00:00 2001 From: shunf4 Date: Wed, 6 Oct 2021 00:15:25 +0800 Subject: [PATCH 20/93] remove dependency uuid --- CMakeLists.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5fb09fc..7b71aea 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -94,7 +94,6 @@ find_package(LMDB REQUIRED) target_link_libraries(squawk PRIVATE Qt5::Core Qt5::Widgets Qt5::DBus Qt5::Network Qt5::Gui Qt5::Xml) target_link_libraries(squawk PRIVATE lmdb) target_link_libraries(squawk PRIVATE simpleCrypt) -target_link_libraries(squawk PRIVATE uuid) # Build type if (NOT CMAKE_BUILD_TYPE) From 1e37aa762c7aae7400d2aa3c16b7a76050d8aec7 Mon Sep 17 00:00:00 2001 From: shunf4 Date: Wed, 6 Oct 2021 00:48:25 +0800 Subject: [PATCH 21/93] generate app bundle for macOS --- CMakeLists.txt | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7b71aea..fe4ba01 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,13 +13,27 @@ include(GNUInstallDirs) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake") set(WIN32_FLAG "") -if (WIN32) - if (CMAKE_BUILD_TYPE STREQUAL "Release") - set(WIN32_FLAG WIN32) - endif() -endif(WIN32) -add_executable(squawk ${WIN32_FLAG}) +set(MACOSX_BUNDLE_FLAG "") +if (CMAKE_BUILD_TYPE STREQUAL "Release") + if (WIN32) + set(WIN32_FLAG WIN32) + endif(WIN32) + if (APPLE) + set(MACOSX_BUNDLE_FLAG MACOSX_BUNDLE) + endif(APPLE) +endif() + +add_executable(squawk ${WIN32_FLAG} ${MACOSX_BUNDLE_FLAG}) target_include_directories(squawk PRIVATE ${CMAKE_SOURCE_DIR}) +if (CMAKE_BUILD_TYPE STREQUAL "Release") + if (APPLE) + set_target_properties(squawk PROPERTIES + MACOSX_BUNDLE_EXECUTABLE_NAME "Squawk" + MACOSX_BUNDLE_ICON_FILE "" # TODO + MACOSX_BUNDLE_BUNDLE_NAME "Squawk" + MACOSX_BUNDLE_INFO_PLIST ${CMAKE_SOURCE_DIR}/CMake/Info.plist.in) + endif(APPLE) +endif() option(SYSTEM_QXMPP "Use system qxmpp lib" ON) option(WITH_KWALLET "Build KWallet support module" ON) From d1f108e69dc145579f459f491d7301454ffa0eae Mon Sep 17 00:00:00 2001 From: shunf4 Date: Wed, 6 Oct 2021 00:53:50 +0800 Subject: [PATCH 22/93] update docs for win32 build --- README.md | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 892168c..e94972f 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ - qxmpp 1.1.0 or higher - KDE Frameworks: kwallet (optional) - KDE Frameworks: KIO (optional) +- Boost ### Getting @@ -33,6 +34,26 @@ You can also clone the repo and build it from source Squawk requires Qt with SSL enabled. It uses CMake as build system. +Please check the prerequisites and install them before installation. + +#### For Windows (Mingw-w64) build + +You need Qt for mingw64 (MinGW 64-bit) platform when installing Qt. + +The best way to acquire library `lmdb` and `boost` is through Msys2. + +First install Msys2, and then install `mingw-w64-x86_64-lmdb` and `mingw-w64-x86_64-boost` by pacman. + +Then you need to provide the cmake cache entry when calling cmake for configuration: + +``` +cmake .. -D LMDB_ROOT_DIR:PATH= -D BOOST_ROOT:PATH= +``` + +``: e.g. `C:/msys64/mingw64`. + +--- + There are two ways to build, it depends whether you have qxmpp installed in your system #### Building with system qxmpp @@ -44,7 +65,7 @@ $ git clone https://git.macaw.me/blue/squawk $ cd squawk $ mkdir build $ cd build -$ cmake .. [-DLMDB_DIR:PATH=/path/to/lmdb] +$ cmake .. [-D LMDB_ROOT_DIR:PATH=...] [-D BOOST_ROOT:PATH=...] $ cmake --build . ``` @@ -57,10 +78,12 @@ $ git clone --recurse-submodules https://git.macaw.me/blue/squawk $ cd squawk $ mkdir build $ cd build -$ cmake .. -D SYSTEM_QXMPP=False [-DLMDB_DIR:PATH=/path/to/lmdb] +$ cmake .. -D SYSTEM_QXMPP=False [-D LMDB_ROOT_DIR:PATH=...] [-D BOOST_ROOT:PATH=...] $ cmake --build . ``` +You can always refer to `appveyor.yml` to see how AppVeyor build squawk. + ### List of keys Here is the list of keys you can pass to configuration phase of `cmake ..`. From 261b34b7125ad00ccb244786fe11bfdf6d0373b3 Mon Sep 17 00:00:00 2001 From: shunf4 Date: Wed, 6 Oct 2021 00:55:39 +0800 Subject: [PATCH 23/93] add forgotten cmake/MacOSXBundleInfo.plist.in --- cmake/MacOSXBundleInfo.plist.in | 41 +++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 cmake/MacOSXBundleInfo.plist.in diff --git a/cmake/MacOSXBundleInfo.plist.in b/cmake/MacOSXBundleInfo.plist.in new file mode 100644 index 0000000..ac4bbec --- /dev/null +++ b/cmake/MacOSXBundleInfo.plist.in @@ -0,0 +1,41 @@ + + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + ${MACOSX_BUNDLE_EXECUTABLE_NAME} + CFBundleGetInfoString + ${MACOSX_BUNDLE_INFO_STRING} + CFBundleIconFile + ${MACOSX_BUNDLE_ICON_FILE} + CFBundleIdentifier + ${MACOSX_BUNDLE_GUI_IDENTIFIER} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleLongVersionString + ${MACOSX_BUNDLE_LONG_VERSION_STRING} + CFBundleName + ${MACOSX_BUNDLE_BUNDLE_NAME} + CFBundlePackageType + APPL + CFBundleShortVersionString + ${MACOSX_BUNDLE_SHORT_VERSION_STRING} + CFBundleSignature + ???? + CFBundleVersion + ${MACOSX_BUNDLE_BUNDLE_VERSION} + CSResourcesFileMapped + + NSHumanReadableCopyright + ${MACOSX_BUNDLE_COPYRIGHT} + + NSPrincipalClass + NSApplication + NSHighResolutionCapable + True + + + From 41d9fa7a1cd4029834eb9eb4e5f84b98abd36d4a Mon Sep 17 00:00:00 2001 From: shunf4 Date: Wed, 6 Oct 2021 01:07:47 +0800 Subject: [PATCH 24/93] fix macos bundle build --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index fe4ba01..96df523 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,9 +29,9 @@ if (CMAKE_BUILD_TYPE STREQUAL "Release") if (APPLE) set_target_properties(squawk PROPERTIES MACOSX_BUNDLE_EXECUTABLE_NAME "Squawk" - MACOSX_BUNDLE_ICON_FILE "" # TODO + MACOSX_BUNDLE_ICON_FILE "${MACOSX_BUNDLE_ICON_FILE}" # TODO MACOSX_BUNDLE_BUNDLE_NAME "Squawk" - MACOSX_BUNDLE_INFO_PLIST ${CMAKE_SOURCE_DIR}/CMake/Info.plist.in) + MACOSX_BUNDLE_INFO_PLIST ${CMAKE_SOURCE_DIR}/cmake/MacOSXBundleInfo.plist.in) endif(APPLE) endif() From f3153ef1dbcbf95ed4071aa11ff439532abc23e8 Mon Sep 17 00:00:00 2001 From: shunf4 Date: Wed, 6 Oct 2021 01:17:30 +0800 Subject: [PATCH 25/93] ci: add appveyor.yml --- appveyor.yml | 113 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 appveyor.yml diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 0000000..763a751 --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,113 @@ +image: + - Visual Studio 2019 + - "Previous Ubuntu1804" + - macOS + +branches: + except: + - gh-pages + +for: +- + matrix: + only: + - image: Visual Studio 2019 + + environment: + QTDIR: C:\Qt\5.15.2\mingw81_64 + QTTOOLDIR: C:\Qt\Tools\mingw810_64\bin + QTNINJADIR: C:\Qt\Tools\Ninja + + install: + - set PATH=%QTTOOLDIR%;%QTNINJADIR%;%QTDIR%\bin;%PATH% + - git submodule update --init --recursive + + before_build: + - choco install --yes zstandard + - choco install --yes --version=7.1.0.2 imagemagick.app + - set PATH=C:\Program Files\ImageMagick-7.1.0-Q16-HDRI;%PATH% + + - mkdir lmdb + - cd lmdb + - curl -OL https://mirror.msys2.org/mingw/mingw64/mingw-w64-x86_64-lmdb-0.9.27-1-any.pkg.tar.zst + - zstd -d ./mingw-w64-x86_64-lmdb-0.9.27-1-any.pkg.tar.zst + - tar -xvf ./mingw-w64-x86_64-lmdb-0.9.27-1-any.pkg.tar + - cd .. + + - mkdir boost + - cd boost + - curl -OL https://repo.msys2.org/mingw/mingw64/mingw-w64-x86_64-boost-1.77.0-2-any.pkg.tar.zst + - zstd -d ./mingw-w64-x86_64-boost-1.77.0-2-any.pkg.tar.zst + - tar -xvf ./mingw-w64-x86_64-boost-1.77.0-2-any.pkg.tar + - cd .. + + - mkdir build + - cd build + - cmake -GNinja -DCMAKE_BUILD_TYPE:String=Release -DCMAKE_PREFIX_PATH:STRING=%QTDIR% -DLMDB_ROOT_DIR:PATH=C:/projects/squawk/lmdb/mingw64 -DBOOST_ROOT:PATH=C:/projects/squawk/boost/mingw64 .. + + build_script: + - cmake --build . + - mkdir deploy + - cd deploy + - copy ..\squawk.exe .\ + - copy ..\external\qxmpp\src\libqxmpp.dll .\ + - windeployqt .\squawk.exe + - windeployqt .\libqxmpp.dll + - cd ..\.. + + artifacts: + - path: build/deploy/squawk.exe + name: Squawk executable (Qt 5.15.2) + + - path: build/deploy + name: Squawk deployment + +- + matrix: + only: + - image: macOS + + install: + - brew install lmdb imagemagick boost + - git submodule update --init --recursive + + before_build: + - mkdir build + - cd build + - cmake -DCMAKE_BUILD_TYPE:String=Release -DCMAKE_PREFIX_PATH:STRING=$HOME/Qt/5.15.2/clang_64 .. + + build_script: + - cmake --build . + + artifacts: + - path: build/squawk + name: Squawk executable (Qt 5.15.2) + - path: build/external/qxmpp/src/ + name: QXMPP + - path: build/squawk.app + name: Squawk Bundle (Qt 5.15.2) + +- + matrix: + only: + - image: "Previous Ubuntu1804" + + install: + - ls $HOME/Qt + - sudo apt update + - sudo apt install -y liblmdb-dev liblmdb0 imagemagick mesa-common-dev libglu1-mesa-dev libboost-all-dev + - git submodule update --init --recursive + + before_build: + - mkdir build + - cd build + - cmake -DCMAKE_BUILD_TYPE:String=Release -DCMAKE_PREFIX_PATH:STRING=$HOME/Qt/5.12.10/gcc_64 .. + + build_script: + - cmake --build . + + artifacts: + - path: build/squawk + name: Squawk executable (Qt 5.12) + - path: build/external/qxmpp/src/ + name: QXMPP From 4f6946a5fc0e68b45d2147c2dac00ada01018f25 Mon Sep 17 00:00:00 2001 From: shunf4 Date: Wed, 6 Oct 2021 01:28:08 +0800 Subject: [PATCH 26/93] add LMDB_INCLUDE_DIRS as include dir, fixing win32 ci --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 96df523..1645ffc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -52,6 +52,7 @@ target_include_directories(squawk PRIVATE ${Qt5Gui_INCLUDE_DIRS}) target_include_directories(squawk PRIVATE ${Qt5Xml_INCLUDE_DIRS}) target_include_directories(squawk PRIVATE ${Qt5Network_INCLUDE_DIRS}) target_include_directories(squawk PRIVATE ${Qt5Core_INCLUDE_DIRS}) +target_include_directories(squawk PRIVATE ${LMDB_INCLUDE_DIRS}) ## QXmpp if (SYSTEM_QXMPP) From d84a33e144f5d0e93b5ccd15379ed45a67e3d3de Mon Sep 17 00:00:00 2001 From: shunf4 Date: Wed, 6 Oct 2021 01:33:58 +0800 Subject: [PATCH 27/93] fix linux ci by finding and linking thread libraries --- CMakeLists.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1645ffc..5209974 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -109,6 +109,12 @@ find_package(LMDB REQUIRED) target_link_libraries(squawk PRIVATE Qt5::Core Qt5::Widgets Qt5::DBus Qt5::Network Qt5::Gui Qt5::Xml) target_link_libraries(squawk PRIVATE lmdb) target_link_libraries(squawk PRIVATE simpleCrypt) +# Link thread libraries on Linux +if(UNIX AND NOT APPLE) + set(THREADS_PREFER_PTHREAD_FLAG ON) + find_package(Threads REQUIRED) + target_link_libraries(squawk PRIVATE Threads::Threads) +endif() # Build type if (NOT CMAKE_BUILD_TYPE) From 1c0802c8ca6262f6815fd16119a1e5d412c4d534 Mon Sep 17 00:00:00 2001 From: shunf4 Date: Wed, 6 Oct 2021 01:51:01 +0800 Subject: [PATCH 28/93] fix win32 ci by place LMDB_INCLUDE_DIRS in core/ --- CMakeLists.txt | 1 - core/CMakeLists.txt | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5209974..1b2ca7b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -52,7 +52,6 @@ target_include_directories(squawk PRIVATE ${Qt5Gui_INCLUDE_DIRS}) target_include_directories(squawk PRIVATE ${Qt5Xml_INCLUDE_DIRS}) target_include_directories(squawk PRIVATE ${Qt5Network_INCLUDE_DIRS}) target_include_directories(squawk PRIVATE ${Qt5Core_INCLUDE_DIRS}) -target_include_directories(squawk PRIVATE ${LMDB_INCLUDE_DIRS}) ## QXmpp if (SYSTEM_QXMPP) diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index e30cc7e..9369cb7 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -28,5 +28,7 @@ target_sources(squawk PRIVATE urlstorage.h ) +target_include_directories(squawk PRIVATE ${LMDB_INCLUDE_DIRS}) + add_subdirectory(handlers) add_subdirectory(passwordStorageEngines) From 8c6ac1a21d50cf38be1a796d395998dc79a22385 Mon Sep 17 00:00:00 2001 From: shunf4 Date: Wed, 6 Oct 2021 17:32:46 +0800 Subject: [PATCH 29/93] add icns to macos bundle; use macdeployqt post build --- CMakeLists.txt | 18 ++++++++---------- resources/CMakeLists.txt | 23 +++++++++++++++++++---- 2 files changed, 27 insertions(+), 14 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1b2ca7b..8632b38 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,15 +25,6 @@ endif() add_executable(squawk ${WIN32_FLAG} ${MACOSX_BUNDLE_FLAG}) target_include_directories(squawk PRIVATE ${CMAKE_SOURCE_DIR}) -if (CMAKE_BUILD_TYPE STREQUAL "Release") - if (APPLE) - set_target_properties(squawk PROPERTIES - MACOSX_BUNDLE_EXECUTABLE_NAME "Squawk" - MACOSX_BUNDLE_ICON_FILE "${MACOSX_BUNDLE_ICON_FILE}" # TODO - MACOSX_BUNDLE_BUNDLE_NAME "Squawk" - MACOSX_BUNDLE_INFO_PLIST ${CMAKE_SOURCE_DIR}/cmake/MacOSXBundleInfo.plist.in) - endif(APPLE) -endif() option(SYSTEM_QXMPP "Use system qxmpp lib" ON) option(WITH_KWALLET "Build KWallet support module" ON) @@ -142,4 +133,11 @@ add_subdirectory(ui) # Install the executable install(TARGETS squawk DESTINATION ${CMAKE_INSTALL_BINDIR}) -target_sources(squawk PRIVATE ${SQUAWK_WIN_RC} ${APP_ICON_MACOSX}) +if (CMAKE_BUILD_TYPE STREQUAL "Release") + if (APPLE) + add_custom_command(TARGET squawk POST_BUILD COMMENT "Running macdeployqt..." + COMMAND "${Qt5Widgets_DIR}/../../../bin/macdeployqt" "${CMAKE_CURRENT_BINARY_DIR}/squawk.app" + ) + endif(APPLE) +endif() + diff --git a/resources/CMakeLists.txt b/resources/CMakeLists.txt index 42cb360..9288650 100644 --- a/resources/CMakeLists.txt +++ b/resources/CMakeLists.txt @@ -15,7 +15,9 @@ execute_process(COMMAND ${CONVERT_BIN} -background none -size 256x256 squawk.svg if (WIN32) execute_process(COMMAND ${CONVERT_BIN} squawk48.png squawk64.png squawk256.png squawk.ico WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) - set(SQUAWK_WIN_RC "${CMAKE_CURRENT_BINARY_DIR}/squawk.rc" PARENT_SCOPE) + set(SQUAWK_WIN_RC "${CMAKE_CURRENT_BINARY_DIR}/squawk.rc") + set(SQUAWK_WIN_RC "${SQUAWK_WIN_RC}" PARENT_SCOPE) + target_sources(squawk PRIVATE ${SQUAWK_WIN_RC}) endif(WIN32) if (APPLE) @@ -31,10 +33,22 @@ if (APPLE) execute_process(COMMAND convert -background none -resize !512x512 squawk.svg "icns.iconset/icon_512x512.png" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) execute_process(COMMAND convert -background none -resize !1024x1024 squawk.svg "icns.iconset/icon_512x512@2x.png" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) execute_process(COMMAND iconutil -c icns "icns.iconset" -o "squawk.icns" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) - set(MACOSX_BUNDLE_ICON_FILE squawk.icns PARENT_SCOPE) - set(APP_ICON_MACOSX ${CMAKE_CURRENT_BINARY_DIR}/squawk.icns PARENT_SCOPE) - set_source_files_properties(${APP_ICON_MACOSX} PROPERTIES + set(MACOSX_BUNDLE_ICON_FILE squawk.icns) + set(MACOSX_BUNDLE_ICON_FILE ${MACOSX_BUNDLE_ICON_FILE} PARENT_SCOPE) + set(APP_ICON_MACOSX ${CMAKE_CURRENT_BINARY_DIR}/squawk.icns) + set(APP_ICON_MACOSX ${APP_ICON_MACOSX} PARENT_SCOPE) + target_sources(squawk PRIVATE ${APP_ICON_MACOSX}) + set_source_files_properties(${APP_ICON_MACOSX} TARGET_DIRECTORY squawk PROPERTIES MACOSX_PACKAGE_LOCATION "Resources") + if (CMAKE_BUILD_TYPE STREQUAL "Release") + if (APPLE) + set_target_properties(squawk PROPERTIES + MACOSX_BUNDLE_EXECUTABLE_NAME "Squawk" + MACOSX_BUNDLE_ICON_FILE "${MACOSX_BUNDLE_ICON_FILE}" # TODO + MACOSX_BUNDLE_BUNDLE_NAME "Squawk" + MACOSX_BUNDLE_INFO_PLIST ${CMAKE_SOURCE_DIR}/cmake/MacOSXBundleInfo.plist.in) + endif(APPLE) + endif() endif (APPLE) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk.svg DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/scalable/apps) @@ -42,3 +56,4 @@ install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk48.png DESTINATION ${CMAKE_INSTA install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk64.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/64x64/apps RENAME squawk.png) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk128.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/128x128/apps RENAME squawk.png) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk256.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/256x256/apps RENAME squawk.png) + From 8b3752ef476cc79b8095701b172c9d8f6bea1ac9 Mon Sep 17 00:00:00 2001 From: shunf4 Date: Wed, 6 Oct 2021 18:12:26 +0800 Subject: [PATCH 30/93] fix ci for macos; adjust ci for linux executable with changed rpath --- appveyor.yml | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 763a751..9b20f3b 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,7 +1,7 @@ image: - Visual Studio 2019 - "Previous Ubuntu1804" - - macOS + - macOS-Mojave branches: except: @@ -60,7 +60,7 @@ for: name: Squawk executable (Qt 5.15.2) - path: build/deploy - name: Squawk deployment + name: Squawk deployment with Qt Framework - matrix: @@ -79,13 +79,16 @@ for: build_script: - cmake --build . + after_build: + - zip -r squawk.app.zip squawk.app + artifacts: - - path: build/squawk + - path: build/squawk.app/Contents/MacOS/squawk name: Squawk executable (Qt 5.15.2) - path: build/external/qxmpp/src/ name: QXMPP - - path: build/squawk.app - name: Squawk Bundle (Qt 5.15.2) + - path: build/squawk.app.zip + name: Squawk Bundle with Qt Framework (Qt 5.15.2) - matrix: @@ -101,13 +104,14 @@ for: before_build: - mkdir build - cd build - - cmake -DCMAKE_BUILD_TYPE:String=Release -DCMAKE_PREFIX_PATH:STRING=$HOME/Qt/5.12.10/gcc_64 .. + - cmake -DCMAKE_BUILD_TYPE:String=Release -DCMAKE_PREFIX_PATH:STRING=$HOME/Qt/5.12.10/gcc_64 -DCMAKE_BUILD_RPATH="\$ORIGIN" .. build_script: - cmake --build . + after_build: + - zip -r squawk.zip squawk -j external/qxmpp/src/libqxmpp* + artifacts: - - path: build/squawk - name: Squawk executable (Qt 5.12) - - path: build/external/qxmpp/src/ - name: QXMPP + - path: build/squawk.zip + name: Squawk executable and libraries (Qt 5.12) From d0bdb374a04f93644758439c9d8fad1e81415f13 Mon Sep 17 00:00:00 2001 From: shunf4 Date: Wed, 6 Oct 2021 18:47:59 +0800 Subject: [PATCH 31/93] add flag -fno-sized-deallocation, eliminating _ZdlPvm --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8632b38..da89682 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -118,6 +118,7 @@ target_compile_options(squawk PRIVATE "-Wall;-Wextra" "$<$:-g>" "$<$:-O3>" + "-fno-sized-deallocation" # for eliminating _ZdlPvm ) endif(CMAKE_COMPILER_IS_GNUCXX) From 67e5f9744ef1fc94e347bf3f3684312c56c98ea6 Mon Sep 17 00:00:00 2001 From: shunf4 Date: Wed, 6 Oct 2021 18:50:00 +0800 Subject: [PATCH 32/93] fix ci macos matrix item --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 9b20f3b..30a8125 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -65,7 +65,7 @@ for: - matrix: only: - - image: macOS + - image: macOS-Mojave install: - brew install lmdb imagemagick boost From 7db269acb588f4311f927115f2b5f6973fbd871b Mon Sep 17 00:00:00 2001 From: shunf4 Date: Wed, 6 Oct 2021 19:15:45 +0800 Subject: [PATCH 33/93] Fixes for Windows 1. On Windows, the lmdb file is immediately allocated at full size. So we have to limit the size. 2. Windows need an organization name for QSettings to work. So an organization name is added for Windows target. --- core/archive.cpp | 10 +++++++++- core/main.cpp | 11 ++++++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/core/archive.cpp b/core/archive.cpp index 2582ff9..c67f228 100644 --- a/core/archive.cpp +++ b/core/archive.cpp @@ -58,7 +58,15 @@ void Core::Archive::open(const QString& account) } mdb_env_set_maxdbs(environment, 5); - mdb_env_set_mapsize(environment, 512UL * 1024UL * 1024UL); + mdb_env_set_mapsize(environment, +#ifdef Q_OS_WIN + // On Windows, the file is immediately allocated. + // So we have to limit the size. + 80UL * 1024UL * 1024UL +#else + 512UL * 1024UL * 1024UL +#endif + ); mdb_env_open(environment, path.toStdString().c_str(), 0, 0664); MDB_txn *txn; diff --git a/core/main.cpp b/core/main.cpp index 0be020e..f63d4f8 100644 --- a/core/main.cpp +++ b/core/main.cpp @@ -42,7 +42,16 @@ int main(int argc, char *argv[]) QApplication app(argc, argv); SignalCatcher sc(&app); - +#ifdef Q_OS_WIN + // Windows need an organization name for QSettings to work + // https://doc.qt.io/qt-5/qsettings.html#basic-usage + { + const QString& orgName = QApplication::organizationName(); + if (orgName.isNull() || orgName.isEmpty()) { + QApplication::setOrganizationName("squawk"); + } + } +#endif QApplication::setApplicationName("squawk"); QApplication::setApplicationDisplayName("Squawk"); QApplication::setApplicationVersion("0.1.5"); From a53126d8bca5fc9a6a4848c63e7fe5b241b86ba0 Mon Sep 17 00:00:00 2001 From: shunf4 Date: Wed, 6 Oct 2021 22:04:29 +0800 Subject: [PATCH 34/93] messages may have the same timestamp, put MDB_DUPSORT flag with order db --- core/archive.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/archive.cpp b/core/archive.cpp index 2582ff9..bfb4b20 100644 --- a/core/archive.cpp +++ b/core/archive.cpp @@ -64,7 +64,7 @@ void Core::Archive::open(const QString& account) MDB_txn *txn; mdb_txn_begin(environment, NULL, 0, &txn); mdb_dbi_open(txn, "main", MDB_CREATE, &main); - mdb_dbi_open(txn, "order", MDB_CREATE | MDB_INTEGERKEY, &order); + mdb_dbi_open(txn, "order", MDB_CREATE | MDB_INTEGERKEY | MDB_INTEGERDUP | MDB_DUPSORT, &order); mdb_dbi_open(txn, "stats", MDB_CREATE, &stats); mdb_dbi_open(txn, "avatars", MDB_CREATE, &avatars); mdb_dbi_open(txn, "sid", MDB_CREATE, &sid); From ebeb4089ebf49c1509a0a782cbcc6468643aacb4 Mon Sep 17 00:00:00 2001 From: shunf4 Date: Wed, 6 Oct 2021 22:45:10 +0800 Subject: [PATCH 35/93] add fallback icons for buttons --- ui/squawk.ui | 20 +++++++++++--------- ui/utils/badge.cpp | 7 ++++++- ui/widgets/conversation.cpp | 8 ++++++-- ui/widgets/conversation.ui | 20 +++++++++++--------- ui/widgets/messageline/preview.cpp | 7 +++++++ ui/widgets/vcard/vcard.ui | 14 +++++++------- 6 files changed, 48 insertions(+), 28 deletions(-) diff --git a/ui/squawk.ui b/ui/squawk.ui index f6cb300..a4d0258 100644 --- a/ui/squawk.ui +++ b/ui/squawk.ui @@ -184,8 +184,8 @@ - - .. + + :/images/fallback/dark/big/group.svg:/images/fallback/dark/big/group.svg Accounts @@ -193,8 +193,8 @@ - - .. + + :/images/fallback/dark/big/edit-none.svg:/images/fallback/dark/big/edit-none.svg Quit @@ -205,8 +205,8 @@ false - - .. + + :/images/fallback/dark/big/add.svg:/images/fallback/dark/big/add.svg Add contact @@ -217,14 +217,16 @@ false - - .. + + :/images/fallback/dark/big/group-new.svg:/images/fallback/dark/big/group-new.svg Add conference - + + + diff --git a/ui/utils/badge.cpp b/ui/utils/badge.cpp index ef15bd2..7575afc 100644 --- a/ui/utils/badge.cpp +++ b/ui/utils/badge.cpp @@ -32,7 +32,12 @@ Badge::Badge(const QString& p_id, const QString& p_text, const QIcon& icon, QWid setFrameShadow(QFrame::Raised); image->setPixmap(icon.pixmap(25, 25)); - closeButton->setIcon(QIcon::fromTheme("tab-close")); + QIcon tabCloseIcon = QIcon::fromTheme("tab-close"); + if (tabCloseIcon.isNull()) { + tabCloseIcon.addFile(QString::fromUtf8(":/images/fallback/dark/big/edit-none.svg"), QSize(), QIcon::Normal, QIcon::Off); + } + closeButton->setIcon(tabCloseIcon); + closeButton->setMaximumHeight(25); closeButton->setMaximumWidth(25); diff --git a/ui/widgets/conversation.cpp b/ui/widgets/conversation.cpp index d003551..1276ff9 100644 --- a/ui/widgets/conversation.cpp +++ b/ui/widgets/conversation.cpp @@ -255,8 +255,12 @@ void Conversation::addAttachedFile(const QString& path) QMimeDatabase db; QMimeType type = db.mimeTypeForFile(path); QFileInfo info(path); - - Badge* badge = new Badge(path, info.fileName(), QIcon::fromTheme(type.iconName())); + + QIcon fileIcon = QIcon::fromTheme(type.iconName()); + if (fileIcon.isNull()) { + fileIcon.addFile(QString::fromUtf8(":/images/fallback/dark/big/mail-attachment.svg"), QSize(), QIcon::Normal, QIcon::Off); + } + Badge* badge = new Badge(path, info.fileName(), fileIcon); connect(badge, &Badge::close, this, &Conversation::onBadgeClose); try { diff --git a/ui/widgets/conversation.ui b/ui/widgets/conversation.ui index bb38666..483375a 100644 --- a/ui/widgets/conversation.ui +++ b/ui/widgets/conversation.ui @@ -271,8 +271,8 @@ - - .. + + :/images/fallback/dark/big/unfavorite.svg:/images/fallback/dark/big/unfavorite.svg true @@ -298,8 +298,8 @@ - - .. + + :/images/fallback/dark/big/mail-attachment.svg:/images/fallback/dark/big/mail-attachment.svg true @@ -312,8 +312,8 @@ - - .. + + :/images/fallback/dark/big/clean.svg:/images/fallback/dark/big/clean.svg true @@ -332,8 +332,8 @@ - - .. + + :/images/fallback/dark/big/send.svg:/images/fallback/dark/big/send.svg true @@ -419,6 +419,8 @@ p, li { white-space: pre-wrap; } - + + + diff --git a/ui/widgets/messageline/preview.cpp b/ui/widgets/messageline/preview.cpp index a64c036..e54fce6 100644 --- a/ui/widgets/messageline/preview.cpp +++ b/ui/widgets/messageline/preview.cpp @@ -164,6 +164,9 @@ void Preview::applyNewSize() break; default: { QIcon icon = QIcon::fromTheme(info.mime.iconName()); + if (icon.isNull()) { + icon.addFile(QString::fromUtf8(":/images/fallback/dark/big/mail-attachment.svg"), QSize(), QIcon::Normal, QIcon::Off); + } widget->setPixmap(icon.pixmap(actualSize)); widget->resize(actualSize); } @@ -264,6 +267,10 @@ void Preview::initializeElements() break; default: { QIcon icon = QIcon::fromTheme(info.mime.iconName()); + if (icon.isNull()) { + icon.addFile(QString::fromUtf8(":/images/fallback/dark/big/mail-attachment.svg"), QSize(), QIcon::Normal, QIcon::Off); + } + widget = new QLabel(parent); widget->setPixmap(icon.pixmap(actualSize)); widget->show(); diff --git a/ui/widgets/vcard/vcard.ui b/ui/widgets/vcard/vcard.ui index 26db8f9..b71d262 100644 --- a/ui/widgets/vcard/vcard.ui +++ b/ui/widgets/vcard/vcard.ui @@ -482,8 +482,8 @@ - - .. + + :/images/fallback/dark/big/user.svg:/images/fallback/dark/big/user.svg @@ -852,8 +852,8 @@ - - .. + + :/images/fallback/dark/big/edit-rename.svg:/images/fallback/dark/big/edit-rename.svg Set avatar @@ -861,8 +861,8 @@ - - .. + + :/images/fallback/dark/big/clean.svg:/images/fallback/dark/big/clean.svg Clear avatar @@ -886,7 +886,7 @@ description - + From 5862f1552ba15bd31daf492f21811c5a1b1a4cab Mon Sep 17 00:00:00 2001 From: shunf4 Date: Wed, 6 Oct 2021 22:52:20 +0800 Subject: [PATCH 36/93] don't save settings on quit, if readSettings() not finished --- core/squawk.cpp | 69 ++++++++++++++++++++++++++----------------------- core/squawk.h | 1 + 2 files changed, 38 insertions(+), 32 deletions(-) diff --git a/core/squawk.cpp b/core/squawk.cpp index 6b8af49..d989afc 100644 --- a/core/squawk.cpp +++ b/core/squawk.cpp @@ -27,7 +27,8 @@ Core::Squawk::Squawk(QObject* parent): accounts(), amap(), network(), - waitingForAccounts(0) + waitingForAccounts(0), + isInitialized(false) #ifdef WITH_KWALLET ,kwallet() #endif @@ -66,39 +67,42 @@ void Core::Squawk::stop() { qDebug("Stopping squawk core.."); network.stop(); - QSettings settings; - settings.beginGroup("core"); - settings.beginWriteArray("accounts"); - SimpleCrypt crypto(passwordHash); - for (std::deque::size_type i = 0; i < accounts.size(); ++i) { - settings.setArrayIndex(i); - Account* acc = accounts[i]; - - Shared::AccountPassword ap = acc->getPasswordType(); - QString password; - - switch (ap) { - case Shared::AccountPassword::plain: - password = acc->getPassword(); - break; - case Shared::AccountPassword::jammed: - password = crypto.encryptToString(acc->getPassword()); - break; - default: - break; + + if (isInitialized) { + QSettings settings; + settings.beginGroup("core"); + settings.beginWriteArray("accounts"); + SimpleCrypt crypto(passwordHash); + for (std::deque::size_type i = 0; i < accounts.size(); ++i) { + settings.setArrayIndex(i); + Account* acc = accounts[i]; + + Shared::AccountPassword ap = acc->getPasswordType(); + QString password; + + switch (ap) { + case Shared::AccountPassword::plain: + password = acc->getPassword(); + break; + case Shared::AccountPassword::jammed: + password = crypto.encryptToString(acc->getPassword()); + break; + default: + break; + } + + settings.setValue("name", acc->getName()); + settings.setValue("server", acc->getServer()); + settings.setValue("login", acc->getLogin()); + settings.setValue("password", password); + settings.setValue("resource", acc->getResource()); + settings.setValue("passwordType", static_cast(ap)); } - - settings.setValue("name", acc->getName()); - settings.setValue("server", acc->getServer()); - settings.setValue("login", acc->getLogin()); - settings.setValue("password", password); - settings.setValue("resource", acc->getResource()); - settings.setValue("passwordType", static_cast(ap)); + settings.endArray(); + settings.endGroup(); + + settings.sync(); } - settings.endArray(); - settings.endGroup(); - - settings.sync(); emit quit(); } @@ -108,6 +112,7 @@ void Core::Squawk::start() qDebug("Starting squawk core.."); readSettings(); + isInitialized = true; network.start(); } diff --git a/core/squawk.h b/core/squawk.h index 338eb40..74b1e1a 100644 --- a/core/squawk.h +++ b/core/squawk.h @@ -133,6 +133,7 @@ private: Shared::Availability state; NetworkAccess network; uint8_t waitingForAccounts; + bool isInitialized; #ifdef WITH_KWALLET PSE::KWallet kwallet; From d20fd84d391f7645400c6abb0966097e28a3cc58 Mon Sep 17 00:00:00 2001 From: shunf4 Date: Wed, 6 Oct 2021 22:55:23 +0800 Subject: [PATCH 37/93] respect password type when adding account, preventing loading bad password --- core/squawk.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/squawk.cpp b/core/squawk.cpp index 6b8af49..34cd694 100644 --- a/core/squawk.cpp +++ b/core/squawk.cpp @@ -118,8 +118,9 @@ void Core::Squawk::newAccountRequest(const QMap& map) QString server = map.value("server").toString(); QString password = map.value("password").toString(); QString resource = map.value("resource").toString(); + int passwordType = map.value("passwordType").toInt(); - addAccount(login, server, password, name, resource, Shared::AccountPassword::plain); + addAccount(login, server, password, name, resource, Shared::Global::fromInt(passwordType)); } void Core::Squawk::addAccount( From a24e8382d1a52db4aa73bd8883055960714684d4 Mon Sep 17 00:00:00 2001 From: shunf4 Date: Wed, 6 Oct 2021 23:01:11 +0800 Subject: [PATCH 38/93] correctly retrieve latest archived messages per XEP-0313 --- core/account.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/core/account.cpp b/core/account.cpp index 6784674..035299b 100644 --- a/core/account.cpp +++ b/core/account.cpp @@ -469,6 +469,14 @@ void Core::Account::onContactNeedHistory(const QString& before, const QString& a query.setAfter(after); } } + if (before.size() == 0 && after.size() == 0) { + // https://xmpp.org/extensions/xep-0313.html#sect-idm46556759682304 + // To request the page at the end of the archive + // (i.e. the most recent messages), include just an + // empty element in the RSM part of the query. + // As defined by RSM, this will return the last page of the archive. + query.setBefore(""); + } qDebug() << "Remote query for" << contact->jid << "from" << after << ", to" << before; } From 3a70df21f87f69ce0ea881d47adfb9026779e6d8 Mon Sep 17 00:00:00 2001 From: shunf4 Date: Wed, 6 Oct 2021 23:09:18 +0800 Subject: [PATCH 39/93] feat: paste image in chat --- ui/widgets/conversation.cpp | 45 ++++++++++++++++++++++++++++++++++++- ui/widgets/conversation.h | 5 +++++ 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/ui/widgets/conversation.cpp b/ui/widgets/conversation.cpp index d003551..f0a6be6 100644 --- a/ui/widgets/conversation.cpp +++ b/ui/widgets/conversation.cpp @@ -20,6 +20,7 @@ #include "ui_conversation.h" #include +#include #include #include #include @@ -27,6 +28,9 @@ #include #include #include +#include +#include +#include Conversation::Conversation(bool muc, Models::Account* acc, Models::Element* el, const QString pJid, const QString pRes, QWidget* parent): QWidget(parent), @@ -47,6 +51,7 @@ Conversation::Conversation(bool muc, Models::Account* acc, Models::Element* el, delegate(new MessageDelegate(this)), manualSliderChange(false), tsb(QApplication::style()->styleHint(QStyle::SH_ScrollBar_Transient) == 1), + pasteImageAction(nullptr), shadow(10, 1, Qt::black, this), contextMenu(new QMenu()) { @@ -75,6 +80,7 @@ Conversation::Conversation(bool muc, Models::Account* acc, Models::Element* el, statusLabel = m_ui->statusLabel; connect(&ker, &KeyEnterReceiver::enterPressed, this, &Conversation::onEnterPressed); + connect(&ker, &KeyEnterReceiver::imagePasted, this, &Conversation::onImagePasted); connect(m_ui->sendButton, &QPushButton::clicked, this, &Conversation::onEnterPressed); connect(m_ui->attachButton, &QPushButton::clicked, this, &Conversation::onAttach); connect(m_ui->clearButton, &QPushButton::clicked, this, &Conversation::onClearButton); @@ -82,7 +88,20 @@ Conversation::Conversation(bool muc, Models::Account* acc, Models::Element* el, this, &Conversation::onTextEditDocSizeChanged); m_ui->messageEditor->installEventFilter(&ker); - + + QAction* pasteImageAction = new QAction(tr("Paste Image"), this); + connect(pasteImageAction, &QAction::triggered, this, &Conversation::onImagePasted); + + m_ui->messageEditor->setContextMenuPolicy(Qt::CustomContextMenu); + connect(m_ui->messageEditor, &QTextEdit::customContextMenuRequested, this, [this, pasteImageAction](const QPoint &pos) { + pasteImageAction->setEnabled(Conversation::checkClipboardImage()); + + QMenu *editorMenu = m_ui->messageEditor->createStandardContextMenu(); + editorMenu->addSeparator(); + editorMenu->addAction(pasteImageAction); + + editorMenu->exec(this->m_ui->messageEditor->mapToGlobal(pos)); + }); //line->setAutoFillBackground(false); //if (testAttribute(Qt::WA_TranslucentBackground)) { @@ -183,10 +202,20 @@ bool KeyEnterReceiver::eventFilter(QObject* obj, QEvent* event) } } } + if (k == Qt::Key_V && key->modifiers() & Qt::CTRL) { + if (Conversation::checkClipboardImage()) { + emit imagePasted(); + return true; + } + } } return QObject::eventFilter(obj, event); } +bool Conversation::checkClipboardImage() { + return !QApplication::clipboard()->image().isNull(); +} + QString Conversation::getPalResource() const { return activePalResource; @@ -218,6 +247,20 @@ void Conversation::onEnterPressed() } } +void Conversation::onImagePasted() +{ + QImage image = QApplication::clipboard()->image(); + if (image.isNull()) { + return; + } + QTemporaryFile *tempFile = new QTemporaryFile(QDir::tempPath() + QStringLiteral("/squawk_img_attach_XXXXXX.png"), QApplication::instance()); + tempFile->open(); + image.save(tempFile, "PNG"); + tempFile->close(); + qDebug() << "image on paste temp file: " << tempFile->fileName(); + addAttachedFile(tempFile->fileName()); +} + void Conversation::onAttach() { QFileDialog* d = new QFileDialog(this, tr("Chose a file to send")); diff --git a/ui/widgets/conversation.h b/ui/widgets/conversation.h index b0eb745..5f5d69a 100644 --- a/ui/widgets/conversation.h +++ b/ui/widgets/conversation.h @@ -60,6 +60,7 @@ protected: signals: void enterPressed(); + void imagePasted(); }; class Conversation : public QWidget @@ -77,6 +78,7 @@ public: void setPalResource(const QString& res); virtual void setAvatar(const QString& path); void setFeedFrames(bool top, bool right, bool bottom, bool left); + static bool checkClipboardImage(); signals: void sendMessage(const Shared::Message& message); @@ -102,6 +104,7 @@ protected: protected slots: void onEnterPressed(); + void onImagePasted(); void onAttach(); void onFileSelected(); void onBadgeClose(); @@ -133,6 +136,8 @@ protected: bool manualSliderChange; bool tsb; //transient scroll bars + QAction* pasteImageAction; + ShadowOverlay shadow; QMenu* contextMenu; }; From 50d710de04bfe5f3bffc660a4baae84a10e8b3a1 Mon Sep 17 00:00:00 2001 From: shunf4 Date: Wed, 13 Oct 2021 11:35:43 +0800 Subject: [PATCH 40/93] remove ./signalcatcher_win32.cpp --- signalcatcher_win32.cpp | 42 ----------------------------------------- 1 file changed, 42 deletions(-) delete mode 100644 signalcatcher_win32.cpp diff --git a/signalcatcher_win32.cpp b/signalcatcher_win32.cpp deleted file mode 100644 index ca7b5a2..0000000 --- a/signalcatcher_win32.cpp +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Squawk messenger. - * Copyright (C) 2021 Shunf4 - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General 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 "signalcatcher.h" -#include - -SignalCatcher::SignalCatcher(QCoreApplication *p_app, QObject *parent): - QObject(parent), - app(p_app) -{ -} - -SignalCatcher::~SignalCatcher() -{} - -void SignalCatcher::handleSigInt() -{ -} - -void SignalCatcher::intSignalHandler(int unused) -{ -} - -int SignalCatcher::setup_unix_signal_handlers() -{ - return 0; -} From 52551c1ce0be0a223e388d339389b7a55b159b6c Mon Sep 17 00:00:00 2001 From: shunf4 Date: Wed, 13 Oct 2021 20:06:13 +0800 Subject: [PATCH 41/93] pasteImageAction should be a class member; refactor messageEditor's context menu callback into a member function --- ui/widgets/conversation.cpp | 27 ++++++++++++++------------- ui/widgets/conversation.h | 1 + 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/ui/widgets/conversation.cpp b/ui/widgets/conversation.cpp index f0a6be6..3f07a2c 100644 --- a/ui/widgets/conversation.cpp +++ b/ui/widgets/conversation.cpp @@ -51,7 +51,7 @@ Conversation::Conversation(bool muc, Models::Account* acc, Models::Element* el, delegate(new MessageDelegate(this)), manualSliderChange(false), tsb(QApplication::style()->styleHint(QStyle::SH_ScrollBar_Transient) == 1), - pasteImageAction(nullptr), + pasteImageAction(new QAction(tr("Paste Image"), this)), shadow(10, 1, Qt::black, this), contextMenu(new QMenu()) { @@ -88,21 +88,11 @@ Conversation::Conversation(bool muc, Models::Account* acc, Models::Element* el, this, &Conversation::onTextEditDocSizeChanged); m_ui->messageEditor->installEventFilter(&ker); + m_ui->messageEditor->setContextMenuPolicy(Qt::CustomContextMenu); - QAction* pasteImageAction = new QAction(tr("Paste Image"), this); + connect(m_ui->messageEditor, &QTextEdit::customContextMenuRequested, this, &Conversation::onMessageEditorContext); connect(pasteImageAction, &QAction::triggered, this, &Conversation::onImagePasted); - m_ui->messageEditor->setContextMenuPolicy(Qt::CustomContextMenu); - connect(m_ui->messageEditor, &QTextEdit::customContextMenuRequested, this, [this, pasteImageAction](const QPoint &pos) { - pasteImageAction->setEnabled(Conversation::checkClipboardImage()); - - QMenu *editorMenu = m_ui->messageEditor->createStandardContextMenu(); - editorMenu->addSeparator(); - editorMenu->addAction(pasteImageAction); - - editorMenu->exec(this->m_ui->messageEditor->mapToGlobal(pos)); - }); - //line->setAutoFillBackground(false); //if (testAttribute(Qt::WA_TranslucentBackground)) { //m_ui->scrollArea->setAutoFillBackground(false); @@ -486,3 +476,14 @@ void Conversation::onFeedContext(const QPoint& pos) } } } + +void Conversation::onMessageEditorContext(const QPoint& pos) +{ + pasteImageAction->setEnabled(Conversation::checkClipboardImage()); + + QMenu *editorMenu = m_ui->messageEditor->createStandardContextMenu(); + editorMenu->addSeparator(); + editorMenu->addAction(pasteImageAction); + + editorMenu->exec(this->m_ui->messageEditor->mapToGlobal(pos)); +} diff --git a/ui/widgets/conversation.h b/ui/widgets/conversation.h index 5f5d69a..6b5b4bb 100644 --- a/ui/widgets/conversation.h +++ b/ui/widgets/conversation.h @@ -114,6 +114,7 @@ protected slots: void onFeedMessage(const Shared::Message& msg); void positionShadow(); void onFeedContext(const QPoint &pos); + void onMessageEditorContext(const QPoint &pos); public: const bool isMuc; From 39f2f3d975a1ce38144ef1992506416a934e5005 Mon Sep 17 00:00:00 2001 From: shunf4 Date: Sat, 16 Oct 2021 00:20:31 +0800 Subject: [PATCH 42/93] feat: copy pasted image file to download folder after successful upload --- core/networkaccess.cpp | 27 ++++++++++++++++++++++++++- core/networkaccess.h | 2 +- core/squawk.h | 2 +- ui/squawk.cpp | 2 +- ui/squawk.h | 2 +- ui/widgets/conversation.cpp | 4 ++++ 6 files changed, 34 insertions(+), 5 deletions(-) diff --git a/core/networkaccess.cpp b/core/networkaccess.cpp index 69fe812..c2cd65d 100644 --- a/core/networkaccess.cpp +++ b/core/networkaccess.cpp @@ -378,7 +378,32 @@ void Core::NetworkAccess::onUploadFinished() qDebug() << "upload success for" << url; storage.addFile(upl->messages, upl->url, upl->path); - emit uploadFileComplete(upl->messages, upl->url); + emit uploadFileComplete(upl->messages, upl->url, upl->path); + + // Copy file to Download folder if it is a temp file. See Conversation::onImagePasted. + if (upl->path.startsWith(QDir::tempPath() + QStringLiteral("/squawk_img_attach_")) && upl->path.endsWith(".png")) { + QString err = ""; + QString downloadDirPath = prepareDirectory(upl->messages.front().jid); + if (downloadDirPath.size() > 0) { + QString newPath = downloadDirPath + "/" + upl->path.mid(QDir::tempPath().length() + 1); + + // Copy {TEMPDIR}/squawk_img_attach_XXXXXX.png to Download folder + bool copyResult = QFile::copy(upl->path, newPath); + + if (copyResult) { + // Change storage + storage.setPath(upl->url, newPath); + } else { + err = "copying to " + newPath + " failed"; + } + } else { + err = "Couldn't prepare a directory for file"; + } + + if (err.size() != 0) { + qDebug() << "failed to copy temporary upload file " << upl->path << " to download folder:" << err; + } + } } upl->reply->deleteLater(); diff --git a/core/networkaccess.h b/core/networkaccess.h index 75c189c..89d0633 100644 --- a/core/networkaccess.h +++ b/core/networkaccess.h @@ -58,7 +58,7 @@ public: signals: void loadFileProgress(const std::list& msgs, qreal value, bool up); void loadFileError(const std::list& msgs, const QString& text, bool up); - void uploadFileComplete(const std::list& msgs, const QString& url); + void uploadFileComplete(const std::list& msgs, const QString& url, const QString& path); void downloadFileComplete(const std::list& msgs, const QString& path); public slots: diff --git a/core/squawk.h b/core/squawk.h index 338eb40..3715afe 100644 --- a/core/squawk.h +++ b/core/squawk.h @@ -82,7 +82,7 @@ signals: void fileError(const std::list 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& 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); diff --git a/ui/squawk.cpp b/ui/squawk.cpp index 6a0a676..406ee45 100644 --- a/ui/squawk.cpp +++ b/ui/squawk.cpp @@ -405,7 +405,7 @@ void Squawk::fileError(const std::list msgs, const QString& rosterModel.fileError(msgs, error, up); } -void Squawk::fileUploadComplete(const std::list msgs, const QString& path) +void Squawk::fileUploadComplete(const std::list msgs, const QString& url, const QString& path) { rosterModel.fileComplete(msgs, true); } diff --git a/ui/squawk.h b/ui/squawk.h index 28389fa..cb93259 100644 --- a/ui/squawk.h +++ b/ui/squawk.h @@ -107,7 +107,7 @@ public slots: void fileError(const std::list 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& 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); diff --git a/ui/widgets/conversation.cpp b/ui/widgets/conversation.cpp index 3f07a2c..fcf28c3 100644 --- a/ui/widgets/conversation.cpp +++ b/ui/widgets/conversation.cpp @@ -249,6 +249,10 @@ void Conversation::onImagePasted() tempFile->close(); qDebug() << "image on paste temp file: " << tempFile->fileName(); addAttachedFile(tempFile->fileName()); + + // The file, if successfully uploaded, will be copied to Download folder. + // On application closing, this temporary file will be automatically removed by Qt. + // See Core::NetworkAccess::onUploadFinished. } void Conversation::onAttach() From 7130e674c4d105d3ddefa064ffbf0eb2ef630d20 Mon Sep 17 00:00:00 2001 From: blue Date: Wed, 5 Jan 2022 22:29:34 +0300 Subject: [PATCH 43/93] some warnings fixed, new way of drawing avatars in message line --- core/account.cpp | 5 +- core/adapterFuctions.cpp | 2 +- core/conference.cpp | 2 +- ui/models/accounts.cpp | 5 +- ui/widgets/messageline/feedview.cpp | 56 ++++++++++++++++++++-- ui/widgets/messageline/feedview.h | 1 + ui/widgets/messageline/messagedelegate.cpp | 10 ++-- 7 files changed, 65 insertions(+), 16 deletions(-) diff --git a/core/account.cpp b/core/account.cpp index 035299b..a923690 100644 --- a/core/account.cpp +++ b/core/account.cpp @@ -601,6 +601,9 @@ void Core::Account::onClientError(QXmppClient::Error err) errorText = "Policy violation"; break; #endif + default: + errorText = "Unknown Error"; + break; } errorType = "Client stream error"; @@ -837,7 +840,6 @@ void Core::Account::uploadVCard(const Shared::VCard& card) QXmppVCardIq iq; initializeQXmppVCard(iq, card); - bool avatarChanged = false; if (card.getAvatarType() != Shared::Avatar::empty) { QString newPath = card.getAvatarPath(); QString oldPath = getAvatarPath(); @@ -859,7 +861,6 @@ void Core::Account::uploadVCard(const Shared::VCard& card) } } else { data = avatar.readAll(); - avatarChanged = true; } } else { if (avatarType.size() > 0) { diff --git a/core/adapterFuctions.cpp b/core/adapterFuctions.cpp index e2559d8..3d84dfb 100644 --- a/core/adapterFuctions.cpp +++ b/core/adapterFuctions.cpp @@ -264,7 +264,7 @@ void Core::initializeQXmppVCard(QXmppVCardIq& iq, const Shared::VCard& card) { phone.setType(phone.type() | QXmppVCardPhone::Preferred); } } - for (const std::pair& phone : phones) { + for (const std::pair& phone : phones) { phs.push_back(phone.second); } diff --git a/core/conference.cpp b/core/conference.cpp index cda19fd..55280e2 100644 --- a/core/conference.cpp +++ b/core/conference.cpp @@ -356,7 +356,7 @@ Shared::VCard Core::Conference::handleResponseVCard(const QXmppVCardIq& card, co QMap Core::Conference::getAllAvatars() const { QMap result; - for (const std::pair& pair : exParticipants) { + for (const std::pair& pair : exParticipants) { result.insert(pair.first, avatarPath(pair.first) + "." + pair.second.type); } return result; diff --git a/ui/models/accounts.cpp b/ui/models/accounts.cpp index f5ffce8..4343481 100644 --- a/ui/models/accounts.cpp +++ b/ui/models/accounts.cpp @@ -97,7 +97,10 @@ void Models::Accounts::addAccount(Account* account) void Models::Accounts::onAccountChanged(Item* item, int row, int col) { - if (row < accs.size()) { + if (row < 0) { + return; + } + if (static_cast::size_type>(row) < accs.size()) { Account* acc = getAccount(row); if (item != acc) { return; //it means the signal is emitted by one of accounts' children, not exactly him, this model has no interest in that diff --git a/ui/widgets/messageline/feedview.cpp b/ui/widgets/messageline/feedview.cpp index 7bdfb9e..618ecfb 100644 --- a/ui/widgets/messageline/feedview.cpp +++ b/ui/widgets/messageline/feedview.cpp @@ -31,6 +31,10 @@ constexpr int approximateSingleMessageHeight = 20; constexpr int progressSize = 70; constexpr int dateDeviderMargin = 10; +constexpr int avatarHeight = 50; +constexpr int margin = 6; +constexpr int halfMargin = 3; + const std::set FeedView::geometryChangingRoles = { Models::MessageFeed::Attach, Models::MessageFeed::Text, @@ -334,6 +338,20 @@ void FeedView::paintEvent(QPaintEvent* event) drawDateDevider(option.rect.bottom(), lastDate, painter); } lastDate = currentDate; + + + if ((option.rect.y() < 1) || (index.row() == m->rowCount() - 1)) { + drawAvatar(index, option, painter); + } else { + QString mySender = index.data(Models::MessageFeed::Sender).toString(); + QModelIndex prevIndex = m->index(index.row() + 1, 0, rootIndex()); + if ( + (prevIndex.data(Models::MessageFeed::Sender).toString() != mySender) || + (prevIndex.data(Models::MessageFeed::Date).toDateTime().daysTo(currentDate) != 0) + ) { + drawAvatar(index, option, painter); + } + } } if (!lastDate.isNull() && inZone) { //if after drawing all messages there is still space drawDateDevider(option.rect.bottom(), lastDate, painter); @@ -344,10 +362,6 @@ void FeedView::paintEvent(QPaintEvent* event) del->endClearWidgets(); clearWidgetsMode = false; } - - if (event->rect().height() == vp->height()) { - // draw the blurred drop shadow... - } } void FeedView::drawDateDevider(int top, const QDateTime& date, QPainter& painter) @@ -360,6 +374,40 @@ void FeedView::drawDateDevider(int top, const QDateTime& date, QPainter& painter painter.restore(); } +void FeedView::drawAvatar(const QModelIndex& index, const QStyleOptionViewItem& option, QPainter& painter) +{ + int currentRow = index.row(); + int y = option.rect.y(); + bool firstAttempt = true; + QString mySender = index.data(Models::MessageFeed::Sender).toString(); + QDateTime currentDate = index.data(Models::MessageFeed::Date).toDateTime(); + QIcon icon(index.data(Models::MessageFeed::Avatar).toString()); + while (y < 0 && currentRow > 0) { + QRect rect; + if (firstAttempt) { + firstAttempt = false; + rect = option.rect; + } else { + QModelIndex ci = model()->index(currentRow, 0, rootIndex()); + if ( + (ci.data(Models::MessageFeed::Sender).toString() != mySender) || + (ci.data(Models::MessageFeed::Date).toDateTime().daysTo(currentDate) != 0) + ) { + break; + } + rect = visualRect(ci); + } + y = std::min(0, rect.bottom() - margin - avatarHeight); + --currentRow; + } + if (index.data(Models::MessageFeed::SentByMe).toBool()) { + painter.drawPixmap(option.rect.width() - avatarHeight - margin, y + halfMargin, icon.pixmap(avatarHeight, avatarHeight)); + } else { + painter.drawPixmap(margin, y + halfMargin, icon.pixmap(avatarHeight, avatarHeight)); + } +} + + void FeedView::verticalScrollbarValueChanged(int value) { vo = verticalScrollBar()->maximum() - value; diff --git a/ui/widgets/messageline/feedview.h b/ui/widgets/messageline/feedview.h index 5e08946..e3e57b7 100644 --- a/ui/widgets/messageline/feedview.h +++ b/ui/widgets/messageline/feedview.h @@ -74,6 +74,7 @@ private: bool tryToCalculateGeometriesWithNoScrollbars(const QStyleOptionViewItem& option, const QAbstractItemModel* model, uint32_t totalHeight); void positionProgress(); void drawDateDevider(int top, const QDateTime& date, QPainter& painter); + void drawAvatar(const QModelIndex& index, const QStyleOptionViewItem& option, QPainter& painter); private: struct Hint { diff --git a/ui/widgets/messageline/messagedelegate.cpp b/ui/widgets/messageline/messagedelegate.cpp index 649230e..0fe1ed0 100644 --- a/ui/widgets/messageline/messagedelegate.cpp +++ b/ui/widgets/messageline/messagedelegate.cpp @@ -104,13 +104,7 @@ void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio painter->fillRect(option.rect, option.palette.brush(QPalette::Inactive, QPalette::Highlight)); } - QIcon icon(data.avatar); - - if (data.sentByMe) { - painter->drawPixmap(option.rect.width() - avatarHeight - margin, option.rect.y() + margin / 2, icon.pixmap(avatarHeight, avatarHeight)); - } else { - painter->drawPixmap(margin, option.rect.y() + margin / 2, icon.pixmap(avatarHeight, avatarHeight)); - } + QStyleOptionViewItem opt = option; QRect messageRect = option.rect.adjusted(margin, margin / 2, -(avatarHeight + 2 * margin), -margin / 2); @@ -163,6 +157,7 @@ void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio break; //but it's a possible performance problem case Models::uploading: paintPreview(data, painter, opt); + [[fallthrough]]; case Models::downloading: paintBar(getBar(data), painter, data.sentByMe, opt); break; @@ -268,6 +263,7 @@ QSize MessageDelegate::sizeHint(const QStyleOptionViewItem& option, const QModel break; case Models::uploading: messageSize.rheight() += Preview::calculateAttachSize(attach.localPath, messageRect).height() + textMargin; + [[fallthrough]]; case Models::downloading: messageSize.rheight() += barHeight + textMargin; break; From 9ac0ca10f34046b9774659b772ff97d2b029bc73 Mon Sep 17 00:00:00 2001 From: blue Date: Fri, 7 Jan 2022 17:02:49 +0300 Subject: [PATCH 44/93] avatar painting is returned to delegate; sender names now are not painted in every message --- ui/widgets/messageline/feedview.cpp | 48 --------- ui/widgets/messageline/feedview.h | 1 - ui/widgets/messageline/messagedelegate.cpp | 113 ++++++++++++++++----- ui/widgets/messageline/messagedelegate.h | 4 + 4 files changed, 93 insertions(+), 73 deletions(-) diff --git a/ui/widgets/messageline/feedview.cpp b/ui/widgets/messageline/feedview.cpp index 618ecfb..1296324 100644 --- a/ui/widgets/messageline/feedview.cpp +++ b/ui/widgets/messageline/feedview.cpp @@ -338,20 +338,6 @@ void FeedView::paintEvent(QPaintEvent* event) drawDateDevider(option.rect.bottom(), lastDate, painter); } lastDate = currentDate; - - - if ((option.rect.y() < 1) || (index.row() == m->rowCount() - 1)) { - drawAvatar(index, option, painter); - } else { - QString mySender = index.data(Models::MessageFeed::Sender).toString(); - QModelIndex prevIndex = m->index(index.row() + 1, 0, rootIndex()); - if ( - (prevIndex.data(Models::MessageFeed::Sender).toString() != mySender) || - (prevIndex.data(Models::MessageFeed::Date).toDateTime().daysTo(currentDate) != 0) - ) { - drawAvatar(index, option, painter); - } - } } if (!lastDate.isNull() && inZone) { //if after drawing all messages there is still space drawDateDevider(option.rect.bottom(), lastDate, painter); @@ -374,40 +360,6 @@ void FeedView::drawDateDevider(int top, const QDateTime& date, QPainter& painter painter.restore(); } -void FeedView::drawAvatar(const QModelIndex& index, const QStyleOptionViewItem& option, QPainter& painter) -{ - int currentRow = index.row(); - int y = option.rect.y(); - bool firstAttempt = true; - QString mySender = index.data(Models::MessageFeed::Sender).toString(); - QDateTime currentDate = index.data(Models::MessageFeed::Date).toDateTime(); - QIcon icon(index.data(Models::MessageFeed::Avatar).toString()); - while (y < 0 && currentRow > 0) { - QRect rect; - if (firstAttempt) { - firstAttempt = false; - rect = option.rect; - } else { - QModelIndex ci = model()->index(currentRow, 0, rootIndex()); - if ( - (ci.data(Models::MessageFeed::Sender).toString() != mySender) || - (ci.data(Models::MessageFeed::Date).toDateTime().daysTo(currentDate) != 0) - ) { - break; - } - rect = visualRect(ci); - } - y = std::min(0, rect.bottom() - margin - avatarHeight); - --currentRow; - } - if (index.data(Models::MessageFeed::SentByMe).toBool()) { - painter.drawPixmap(option.rect.width() - avatarHeight - margin, y + halfMargin, icon.pixmap(avatarHeight, avatarHeight)); - } else { - painter.drawPixmap(margin, y + halfMargin, icon.pixmap(avatarHeight, avatarHeight)); - } -} - - void FeedView::verticalScrollbarValueChanged(int value) { vo = verticalScrollBar()->maximum() - value; diff --git a/ui/widgets/messageline/feedview.h b/ui/widgets/messageline/feedview.h index e3e57b7..5e08946 100644 --- a/ui/widgets/messageline/feedview.h +++ b/ui/widgets/messageline/feedview.h @@ -74,7 +74,6 @@ private: bool tryToCalculateGeometriesWithNoScrollbars(const QStyleOptionViewItem& option, const QAbstractItemModel* model, uint32_t totalHeight); void positionProgress(); void drawDateDevider(int top, const QDateTime& date, QPainter& painter); - void drawAvatar(const QModelIndex& index, const QStyleOptionViewItem& option, QPainter& painter); private: struct Hint { diff --git a/ui/widgets/messageline/messagedelegate.cpp b/ui/widgets/messageline/messagedelegate.cpp index 0fe1ed0..0cf449a 100644 --- a/ui/widgets/messageline/messagedelegate.cpp +++ b/ui/widgets/messageline/messagedelegate.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include "messagedelegate.h" #include "messagefeed.h" @@ -104,7 +105,10 @@ void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio painter->fillRect(option.rect, option.palette.brush(QPalette::Inactive, QPalette::Highlight)); } - + bool ntds = needToDrawSender(index, data); + if (ntds || option.rect.y() < 1) { + paintAvatar(data, index, option, painter); + } QStyleOptionViewItem opt = option; QRect messageRect = option.rect.adjusted(margin, margin / 2, -(avatarHeight + 2 * margin), -margin / 2); @@ -122,13 +126,19 @@ void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio messageSize = bodyMetrics.boundingRect(messageRect, Qt::TextWordWrap, data.text).size(); bodySize = messageSize; } - messageSize.rheight() += nickMetrics.lineSpacing(); + + if (ntds) { + messageSize.rheight() += nickMetrics.lineSpacing(); + } messageSize.rheight() += dateMetrics.height(); QString dateString = data.date.toLocalTime().toString("hh:mm"); + if (messageSize.width() < opt.rect.width()) { - QSize senderSize = nickMetrics.boundingRect(messageRect, 0, data.sender).size(); - if (senderSize.width() > messageSize.width()) { - messageSize.setWidth(senderSize.width()); + if (ntds) { + QSize senderSize = nickMetrics.boundingRect(messageRect, 0, data.sender).size(); + if (senderSize.width() > messageSize.width()) { + messageSize.setWidth(senderSize.width()); + } } QSize dateSize = dateMetrics.boundingRect(messageRect, 0, dateString).size(); int addition = 0; @@ -147,9 +157,11 @@ void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio } QRect rect; - painter->setFont(nickFont); - painter->drawText(opt.rect, opt.displayAlignment, data.sender, &rect); - opt.rect.adjust(0, rect.height() + textMargin, 0, 0); + if (ntds) { + painter->setFont(nickFont); + painter->drawText(opt.rect, opt.displayAlignment, data.sender, &rect); + opt.rect.adjust(0, rect.height() + textMargin, 0, 0); + } painter->save(); switch (data.attach.state) { case Models::none: @@ -244,25 +256,75 @@ void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio } } +void MessageDelegate::paintAvatar(const Models::FeedItem& data, const QModelIndex& index, const QStyleOptionViewItem& option, QPainter* painter) const +{ + int currentRow = index.row(); + int y = option.rect.y(); + bool firstAttempt = true; + QIcon icon(data.avatar); + while (y < 0 && currentRow > 0) { + QRect rect; + if (firstAttempt) { + firstAttempt = false; + rect = option.rect; + } else { + QModelIndex ci = index.siblingAtRow(currentRow); + if ( + (ci.data(Models::MessageFeed::Sender).toString() != data.sender) || + (ci.data(Models::MessageFeed::Date).toDateTime().daysTo(data.date) != 0) + ) { + break; + } + //TODO this is really bad, but for now I have no idea how else can I access the view; + const QAbstractItemView* view = static_cast(option.styleObject); + rect = view->visualRect(ci); + } + y = std::min(0, rect.bottom() - margin - avatarHeight); + --currentRow; + } + if (data.sentByMe) { + painter->drawPixmap(option.rect.width() - avatarHeight - margin, y + margin / 2, icon.pixmap(avatarHeight, avatarHeight)); + } else { + painter->drawPixmap(margin, y + margin / 2, icon.pixmap(avatarHeight, avatarHeight)); + } +} + +bool MessageDelegate::needToDrawAvatar(const QModelIndex& index, const Models::FeedItem& data, const QStyleOptionViewItem& option) const +{ + return (option.rect.y() < 1) || needToDrawSender(index, data); +} + +bool MessageDelegate::needToDrawSender(const QModelIndex& index, const Models::FeedItem& data) const +{ + if (index.row() == index.model()->rowCount() - 1) { + return true; + } else { + QModelIndex prevIndex = index.siblingAtRow(index.row() + 1); + + return (prevIndex.data(Models::MessageFeed::Sender).toString() != data.sender) || + (prevIndex.data(Models::MessageFeed::Date).toDateTime().daysTo(data.date) != 0); + } +} + + QSize MessageDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const { QRect messageRect = option.rect.adjusted(0, margin / 2, -(avatarHeight + 3 * margin), -margin / 2); QStyleOptionViewItem opt = option; opt.rect = messageRect; - QVariant va = index.data(Models::MessageFeed::Attach); - Models::Attachment attach = qvariant_cast(va); - QString body = index.data(Models::MessageFeed::Text).toString(); + QVariant vi = index.data(Models::MessageFeed::Bulk); + Models::FeedItem data = qvariant_cast(vi); QSize messageSize(0, 0); - if (body.size() > 0) { - messageSize = bodyMetrics.boundingRect(messageRect, Qt::TextWordWrap, body).size(); + if (data.text.size() > 0) { + messageSize = bodyMetrics.boundingRect(messageRect, Qt::TextWordWrap, data.text).size(); messageSize.rheight() += textMargin; } - switch (attach.state) { + switch (data.attach.state) { case Models::none: break; case Models::uploading: - messageSize.rheight() += Preview::calculateAttachSize(attach.localPath, messageRect).height() + textMargin; + messageSize.rheight() += Preview::calculateAttachSize(data.attach.localPath, messageRect).height() + textMargin; [[fallthrough]]; case Models::downloading: messageSize.rheight() += barHeight + textMargin; @@ -272,25 +334,28 @@ QSize MessageDelegate::sizeHint(const QStyleOptionViewItem& option, const QModel break; case Models::ready: case Models::local: - messageSize.rheight() += Preview::calculateAttachSize(attach.localPath, messageRect).height() + textMargin; + messageSize.rheight() += Preview::calculateAttachSize(data.attach.localPath, messageRect).height() + textMargin; break; case Models::errorDownload: messageSize.rheight() += buttonHeight + textMargin; - messageSize.rheight() += dateMetrics.boundingRect(messageRect, Qt::TextWordWrap, attach.error).size().height() + textMargin; + messageSize.rheight() += dateMetrics.boundingRect(messageRect, Qt::TextWordWrap, data.attach.error).size().height() + textMargin; break; case Models::errorUpload: - messageSize.rheight() += Preview::calculateAttachSize(attach.localPath, messageRect).height() + textMargin; - messageSize.rheight() += dateMetrics.boundingRect(messageRect, Qt::TextWordWrap, attach.error).size().height() + textMargin; + messageSize.rheight() += Preview::calculateAttachSize(data.attach.localPath, messageRect).height() + textMargin; + messageSize.rheight() += dateMetrics.boundingRect(messageRect, Qt::TextWordWrap, data.attach.error).size().height() + textMargin; break; } - messageSize.rheight() += nickMetrics.lineSpacing(); - messageSize.rheight() += textMargin; + if (needToDrawSender(index, data)) { + messageSize.rheight() += nickMetrics.lineSpacing(); + messageSize.rheight() += textMargin; + } + messageSize.rheight() += dateMetrics.height() > statusIconSize ? dateMetrics.height() : statusIconSize; - if (messageSize.height() < avatarHeight) { - messageSize.setHeight(avatarHeight); - } +// if (messageSize.height() < avatarHeight) { +// messageSize.setHeight(avatarHeight); +// } messageSize.rheight() += margin; diff --git a/ui/widgets/messageline/messagedelegate.h b/ui/widgets/messageline/messagedelegate.h index 7403285..5792e01 100644 --- a/ui/widgets/messageline/messagedelegate.h +++ b/ui/widgets/messageline/messagedelegate.h @@ -65,12 +65,16 @@ protected: void paintBar(QProgressBar* bar, QPainter* painter, bool sentByMe, QStyleOptionViewItem& option) const; void paintPreview(const Models::FeedItem& data, QPainter* painter, QStyleOptionViewItem& option) const; void paintComment(const Models::FeedItem& data, QPainter* painter, QStyleOptionViewItem& option) const; + void paintAvatar(const Models::FeedItem& data, const QModelIndex& index, const QStyleOptionViewItem& option, QPainter* painter) const; QPushButton* getButton(const Models::FeedItem& data) const; QProgressBar* getBar(const Models::FeedItem& data) const; QLabel* getStatusIcon(const Models::FeedItem& data) const; QLabel* getPencilIcon(const Models::FeedItem& data) const; QLabel* getBody(const Models::FeedItem& data) const; void clearHelperWidget(const Models::FeedItem& data) const; + + bool needToDrawAvatar(const QModelIndex& index, const Models::FeedItem& data, const QStyleOptionViewItem& option) const; + bool needToDrawSender(const QModelIndex& index, const Models::FeedItem& data) const; protected slots: void onButtonPushed() const; From 8a2658e4fcf0f6e8559057d05152982c21a90e41 Mon Sep 17 00:00:00 2001 From: blue Date: Sun, 9 Jan 2022 01:28:29 +0300 Subject: [PATCH 45/93] message bubbles, avatar rounding, roster adjusments --- ui/squawk.cpp | 6 +- ui/squawk.ui | 23 +++- ui/widgets/conversation.cpp | 24 +++- ui/widgets/conversation.h | 8 ++ ui/widgets/messageline/feedview.cpp | 4 - ui/widgets/messageline/messagedelegate.cpp | 133 +++++++++++++++------ ui/widgets/messageline/messagedelegate.h | 9 +- 7 files changed, 158 insertions(+), 49 deletions(-) diff --git a/ui/squawk.cpp b/ui/squawk.cpp index 406ee45..800f02a 100644 --- a/ui/squawk.cpp +++ b/ui/squawk.cpp @@ -39,7 +39,11 @@ Squawk::Squawk(QWidget *parent) : m_ui->setupUi(this); m_ui->roster->setModel(&rosterModel); m_ui->roster->setContextMenuPolicy(Qt::CustomContextMenu); - m_ui->roster->setColumnWidth(1, 30); + if (QApplication::style()->styleHint(QStyle::SH_ScrollBar_Transient) == 1) { + m_ui->roster->setColumnWidth(1, 52); + } else { + m_ui->roster->setColumnWidth(1, 26); + } m_ui->roster->setIconSize(QSize(20, 20)); m_ui->roster->header()->setStretchLastSection(false); m_ui->roster->header()->setSectionResizeMode(0, QHeaderView::Stretch); diff --git a/ui/squawk.ui b/ui/squawk.ui index a4d0258..01ffbba 100644 --- a/ui/squawk.ui +++ b/ui/squawk.ui @@ -73,6 +73,9 @@ 0 + + 0 + @@ -96,16 +99,31 @@ true + + true + false false + + 10 + + + false + + + + 0 + 30 + + false @@ -115,6 +133,9 @@ -1 + + true + @@ -162,7 +183,7 @@ 0 0 718 - 27 + 32 diff --git a/ui/widgets/conversation.cpp b/ui/widgets/conversation.cpp index ea2f722..69eac19 100644 --- a/ui/widgets/conversation.cpp +++ b/ui/widgets/conversation.cpp @@ -25,12 +25,16 @@ #include #include #include -#include #include #include #include #include #include +#include + +#include + +constexpr QSize avatarSize(50, 50); Conversation::Conversation(bool muc, Models::Account* acc, Models::Element* el, const QString pJid, const QString pRes, QWidget* parent): QWidget(parent), @@ -348,11 +352,25 @@ void Conversation::onClearButton() void Conversation::setAvatar(const QString& path) { + QPixmap pixmap; if (path.size() == 0) { - m_ui->avatar->setPixmap(Shared::icon("user", true).pixmap(QSize(50, 50))); + pixmap = Shared::icon("user", true).pixmap(avatarSize); } else { - m_ui->avatar->setPixmap(path); + pixmap = QPixmap(path).scaled(avatarSize); } + + + QPixmap result(avatarSize); + result.fill(Qt::transparent); + QPainter painter(&result); + painter.setRenderHint(QPainter::Antialiasing); + painter.setRenderHint(QPainter::SmoothPixmapTransform); + QPainterPath maskPath; + maskPath.addEllipse(0, 0, avatarSize.width(), avatarSize.height()); + painter.setClipPath(maskPath); + painter.drawPixmap(0, 0, pixmap); + + m_ui->avatar->setPixmap(result); } void Conversation::onTextEditDocSizeChanged(const QSizeF& size) diff --git a/ui/widgets/conversation.h b/ui/widgets/conversation.h index 6b5b4bb..a758b2c 100644 --- a/ui/widgets/conversation.h +++ b/ui/widgets/conversation.h @@ -141,6 +141,14 @@ protected: ShadowOverlay shadow; QMenu* contextMenu; + +private: + static bool painterInitialized; + static QPainterPath* avatarMask; + static QPixmap* avatarPixmap; + static QPainter* avatarPainter; + + }; #endif // CONVERSATION_H diff --git a/ui/widgets/messageline/feedview.cpp b/ui/widgets/messageline/feedview.cpp index 1296324..d83ca6b 100644 --- a/ui/widgets/messageline/feedview.cpp +++ b/ui/widgets/messageline/feedview.cpp @@ -31,10 +31,6 @@ constexpr int approximateSingleMessageHeight = 20; constexpr int progressSize = 70; constexpr int dateDeviderMargin = 10; -constexpr int avatarHeight = 50; -constexpr int margin = 6; -constexpr int halfMargin = 3; - const std::set FeedView::geometryChangingRoles = { Models::MessageFeed::Attach, Models::MessageFeed::Text, diff --git a/ui/widgets/messageline/messagedelegate.cpp b/ui/widgets/messageline/messagedelegate.cpp index 0cf449a..5dd0dce 100644 --- a/ui/widgets/messageline/messagedelegate.cpp +++ b/ui/widgets/messageline/messagedelegate.cpp @@ -18,6 +18,7 @@ #include #include +#include #include #include #include @@ -29,6 +30,11 @@ constexpr int avatarHeight = 50; constexpr int margin = 6; constexpr int textMargin = 2; constexpr int statusIconSize = 16; +constexpr float nickFontMultiplier = 1.1; +constexpr float dateFontMultiplier = 0.8; + +constexpr int bubbleMargin = 6; +constexpr int bubbleBorderRadius = 3; MessageDelegate::MessageDelegate(QObject* parent): QStyledItemDelegate(parent), @@ -101,9 +107,10 @@ void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio painter->save(); painter->setRenderHint(QPainter::Antialiasing, true); - if (option.state & QStyle::State_MouseOver) { - painter->fillRect(option.rect, option.palette.brush(QPalette::Inactive, QPalette::Highlight)); - } +// if (option.state & QStyle::State_MouseOver) { +// painter->fillRect(option.rect, option.palette.brush(QPalette::Inactive, QPalette::Highlight)); +// } + bool ntds = needToDrawSender(index, data); if (ntds || option.rect.y() < 1) { @@ -118,6 +125,9 @@ void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio } else { opt.displayAlignment = Qt::AlignRight | Qt::AlignTop; } + + QPoint bubbleBegin = messageRect.topLeft(); + messageRect.adjust(bubbleMargin, bubbleMargin, -bubbleMargin, -bubbleMargin / 2); opt.rect = messageRect; QSize messageSize(0, 0); @@ -127,9 +137,6 @@ void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio bodySize = messageSize; } - if (ntds) { - messageSize.rheight() += nickMetrics.lineSpacing(); - } messageSize.rheight() += dateMetrics.height(); QString dateString = data.date.toLocalTime().toString("hh:mm"); @@ -155,46 +162,64 @@ void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio } else { messageSize.setWidth(opt.rect.width()); } - - QRect rect; - if (ntds) { - painter->setFont(nickFont); - painter->drawText(opt.rect, opt.displayAlignment, data.sender, &rect); - opt.rect.adjust(0, rect.height() + textMargin, 0, 0); - } + painter->save(); + + int storedY = opt.rect.y(); + if (ntds) { + opt.rect.adjust(0, nickMetrics.lineSpacing() + textMargin, 0, 0); + } + + int attWidth(0); switch (data.attach.state) { case Models::none: clearHelperWidget(data); //i can't imagine the situation where it's gonna be needed break; //but it's a possible performance problem case Models::uploading: - paintPreview(data, painter, opt); + attWidth = std::max(paintPreview(data, painter, opt), attWidth); [[fallthrough]]; case Models::downloading: - paintBar(getBar(data), painter, data.sentByMe, opt); + messageSize.setWidth(opt.rect.width()); + messageSize.rheight() += barHeight + textMargin + opt.rect.y() - storedY; + paintBubble(data, painter, messageSize, opt, bubbleBegin); + attWidth = std::max(paintBar(getBar(data), painter, data.sentByMe, opt), attWidth); break; case Models::remote: - paintButton(getButton(data), painter, data.sentByMe, opt); + attWidth = std::max(paintButton(getButton(data), painter, data.sentByMe, opt), attWidth); break; case Models::ready: case Models::local: clearHelperWidget(data); - paintPreview(data, painter, opt); + attWidth = std::max(paintPreview(data, painter, opt), attWidth); break; case Models::errorDownload: { - paintButton(getButton(data), painter, data.sentByMe, opt); - paintComment(data, painter, opt); + attWidth = std::max(paintButton(getButton(data), painter, data.sentByMe, opt), attWidth); + attWidth = std::max(paintComment(data, painter, opt), attWidth); } break; case Models::errorUpload:{ clearHelperWidget(data); - paintPreview(data, painter, opt); - paintComment(data, painter, opt); + attWidth = std::max(paintPreview(data, painter, opt), attWidth); + attWidth = std::max(paintComment(data, painter, opt), attWidth); } break; } painter->restore(); + if (data.attach.state != Models::uploading && data.attach.state != Models::downloading) { + messageSize.rheight() += opt.rect.y() - storedY; + messageSize.setWidth(std::max(attWidth, messageSize.width())); + paintBubble(data, painter, messageSize, opt, bubbleBegin); + } + + QRect rect; + if (ntds) { + painter->setFont(nickFont); + int storedY2 = opt.rect.y(); + opt.rect.setY(storedY); + painter->drawText(opt.rect, opt.displayAlignment, data.sender, &rect); + opt.rect.setY(storedY2); + } int messageLeft = INT16_MAX; int messageRight = opt.rect.x() + messageSize.width(); @@ -256,6 +281,23 @@ void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio } } +void MessageDelegate::paintBubble(const Models::FeedItem& data, QPainter* painter, const QSize& messageSize, QStyleOptionViewItem& option, QPoint bubbleBegin) const +{ + painter->save(); + if (data.sentByMe) { + bubbleBegin.setX(option.rect.topRight().x() - messageSize.width() - bubbleMargin); + painter->setBrush(option.palette.brush(QPalette::Inactive, QPalette::Highlight)); + } else { + painter->setBrush(option.palette.brush(QPalette::Window)); + } + QSize bubbleAddition(2 * bubbleMargin, 1.5 * bubbleMargin); + QRect bubble(bubbleBegin, messageSize + bubbleAddition); + painter->setPen(Qt::NoPen); + painter->drawRoundedRect(bubble, bubbleBorderRadius, bubbleBorderRadius); + painter->restore(); +} + + void MessageDelegate::paintAvatar(const Models::FeedItem& data, const QModelIndex& index, const QStyleOptionViewItem& option, QPainter* painter) const { int currentRow = index.row(); @@ -282,11 +324,22 @@ void MessageDelegate::paintAvatar(const Models::FeedItem& data, const QModelInde y = std::min(0, rect.bottom() - margin - avatarHeight); --currentRow; } + + QPixmap pixmap = icon.pixmap(avatarHeight, avatarHeight); + QPainterPath path; + int ax; + if (data.sentByMe) { - painter->drawPixmap(option.rect.width() - avatarHeight - margin, y + margin / 2, icon.pixmap(avatarHeight, avatarHeight)); + ax = option.rect.width() - avatarHeight - margin; } else { - painter->drawPixmap(margin, y + margin / 2, icon.pixmap(avatarHeight, avatarHeight)); + ax = margin; } + + path.addEllipse(ax, y + margin / 2, avatarHeight, avatarHeight); + painter->save(); + painter->setClipPath(path); + painter->drawPixmap(ax, y + margin / 2, pixmap); + painter->restore(); } bool MessageDelegate::needToDrawAvatar(const QModelIndex& index, const Models::FeedItem& data, const QStyleOptionViewItem& option) const @@ -309,7 +362,7 @@ bool MessageDelegate::needToDrawSender(const QModelIndex& index, const Models::F QSize MessageDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const { - QRect messageRect = option.rect.adjusted(0, margin / 2, -(avatarHeight + 3 * margin), -margin / 2); + QRect messageRect = option.rect.adjusted(bubbleMargin, margin / 2 + bubbleMargin, -(avatarHeight + 3 * margin + bubbleMargin), -(margin + bubbleMargin) / 2); QStyleOptionViewItem opt = option; opt.rect = messageRect; QVariant vi = index.data(Models::MessageFeed::Bulk); @@ -347,10 +400,10 @@ QSize MessageDelegate::sizeHint(const QStyleOptionViewItem& option, const QModel } if (needToDrawSender(index, data)) { - messageSize.rheight() += nickMetrics.lineSpacing(); - messageSize.rheight() += textMargin; + messageSize.rheight() += nickMetrics.lineSpacing() + textMargin; } + messageSize.rheight() += bubbleMargin + bubbleMargin / 2; messageSize.rheight() += dateMetrics.height() > statusIconSize ? dateMetrics.height() : statusIconSize; // if (messageSize.height() < avatarHeight) { @@ -372,17 +425,17 @@ void MessageDelegate::initializeFonts(const QFont& font) float ndps = nickFont.pointSizeF(); if (ndps != -1) { - nickFont.setPointSizeF(ndps * 1.2); + nickFont.setPointSizeF(ndps * nickFontMultiplier); } else { - nickFont.setPointSize(nickFont.pointSize() + 2); + nickFont.setPointSize(nickFont.pointSize() * nickFontMultiplier); } dateFont.setItalic(true); float dps = dateFont.pointSizeF(); if (dps != -1) { - dateFont.setPointSizeF(dps * 0.8); + dateFont.setPointSizeF(dps * dateFontMultiplier); } else { - dateFont.setPointSize(dateFont.pointSize() - 2); + dateFont.setPointSize(dateFont.pointSize() * dateFontMultiplier); } bodyMetrics = QFontMetrics(bodyFont); @@ -400,11 +453,11 @@ bool MessageDelegate::editorEvent(QEvent* event, QAbstractItemModel* model, cons return QStyledItemDelegate::editorEvent(event, model, option, index); } -void MessageDelegate::paintButton(QPushButton* btn, QPainter* painter, bool sentByMe, QStyleOptionViewItem& option) const +int MessageDelegate::paintButton(QPushButton* btn, QPainter* painter, bool sentByMe, QStyleOptionViewItem& option) const { QPoint start; if (sentByMe) { - start = {option.rect.width() - btn->width(), option.rect.top()}; + start = {option.rect.x() + option.rect.width() - btn->width(), option.rect.top()}; } else { start = option.rect.topLeft(); } @@ -415,9 +468,10 @@ void MessageDelegate::paintButton(QPushButton* btn, QPainter* painter, bool sent btn->show(); option.rect.adjust(0, buttonHeight + textMargin, 0, 0); + return btn->width(); } -void MessageDelegate::paintComment(const Models::FeedItem& data, QPainter* painter, QStyleOptionViewItem& option) const +int MessageDelegate::paintComment(const Models::FeedItem& data, QPainter* painter, QStyleOptionViewItem& option) const { painter->setFont(dateFont); QColor q = painter->pen().color(); @@ -426,9 +480,11 @@ void MessageDelegate::paintComment(const Models::FeedItem& data, QPainter* paint QRect rect; painter->drawText(option.rect, option.displayAlignment, data.attach.error, &rect); option.rect.adjust(0, rect.height() + textMargin, 0, 0); + + return rect.width(); } -void MessageDelegate::paintBar(QProgressBar* bar, QPainter* painter, bool sentByMe, QStyleOptionViewItem& option) const +int MessageDelegate::paintBar(QProgressBar* bar, QPainter* painter, bool sentByMe, QStyleOptionViewItem& option) const { QPoint start = option.rect.topLeft(); bar->resize(option.rect.width(), barHeight); @@ -437,9 +493,11 @@ void MessageDelegate::paintBar(QProgressBar* bar, QPainter* painter, bool sentBy bar->render(painter, QPoint(), QRegion(), QWidget::DrawChildren); option.rect.adjust(0, barHeight + textMargin, 0, 0); + + return option.rect.width(); } -void MessageDelegate::paintPreview(const Models::FeedItem& data, QPainter* painter, QStyleOptionViewItem& option) const +int MessageDelegate::paintPreview(const Models::FeedItem& data, QPainter* painter, QStyleOptionViewItem& option) const { Preview* preview = 0; std::map::iterator itr = previews->find(data.id); @@ -458,7 +516,10 @@ void MessageDelegate::paintPreview(const Models::FeedItem& data, QPainter* paint emit invalidPath(data.id); //or deleted. This signal notifies the model, and the model notifies the core, preview can } //handle being invalid for as long as I need and can be even become valid again with a new path - option.rect.adjust(0, preview->size().height() + textMargin, 0, 0); + QSize pSize(preview->size()); + option.rect.adjust(0, pSize.height() + textMargin, 0, 0); + + return pSize.width(); } QPushButton * MessageDelegate::getButton(const Models::FeedItem& data) const diff --git a/ui/widgets/messageline/messagedelegate.h b/ui/widgets/messageline/messagedelegate.h index 5792e01..87a79c9 100644 --- a/ui/widgets/messageline/messagedelegate.h +++ b/ui/widgets/messageline/messagedelegate.h @@ -61,11 +61,12 @@ signals: void invalidPath(const QString& messageId) const; protected: - void paintButton(QPushButton* btn, QPainter* painter, bool sentByMe, QStyleOptionViewItem& option) const; - void paintBar(QProgressBar* bar, QPainter* painter, bool sentByMe, QStyleOptionViewItem& option) const; - void paintPreview(const Models::FeedItem& data, QPainter* painter, QStyleOptionViewItem& option) const; - void paintComment(const Models::FeedItem& data, QPainter* painter, QStyleOptionViewItem& option) const; + int paintButton(QPushButton* btn, QPainter* painter, bool sentByMe, QStyleOptionViewItem& option) const; + int paintBar(QProgressBar* bar, QPainter* painter, bool sentByMe, QStyleOptionViewItem& option) const; + int paintPreview(const Models::FeedItem& data, QPainter* painter, QStyleOptionViewItem& option) const; + int paintComment(const Models::FeedItem& data, QPainter* painter, QStyleOptionViewItem& option) const; void paintAvatar(const Models::FeedItem& data, const QModelIndex& index, const QStyleOptionViewItem& option, QPainter* painter) const; + void paintBubble(const Models::FeedItem& data, QPainter* painter, const QSize& messageSize, QStyleOptionViewItem& option, QPoint bubbleBegin) const; QPushButton* getButton(const Models::FeedItem& data) const; QProgressBar* getBar(const Models::FeedItem& data) const; QLabel* getStatusIcon(const Models::FeedItem& data) const; From 4d3ba6b11f5704cf09c404e2307dada7ac665322 Mon Sep 17 00:00:00 2001 From: blue Date: Sun, 9 Jan 2022 17:32:23 +0300 Subject: [PATCH 46/93] 0.2.0 finalization --- CHANGELOG.md | 13 +- README.md | 4 +- core/main.cpp | 2 +- packaging/Archlinux/PKGBUILD | 4 +- shared/global.cpp | 2 +- shared/global.h | 1 + ui/widgets/messageline/feedview.cpp | 58 ++++-- ui/widgets/messageline/feedview.h | 3 + ui/widgets/messageline/messagedelegate.cpp | 220 ++++++++------------- ui/widgets/messageline/messagedelegate.h | 6 +- ui/widgets/messageline/preview.cpp | 55 ++++-- ui/widgets/messageline/preview.h | 5 +- 12 files changed, 194 insertions(+), 179 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bf90231..09c382a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,19 +1,28 @@ # Changelog -## Squawk 0.2.0 (Unreleased) +## Squawk 0.2.0 (Jan 10, 2022) ### Bug fixes - carbon copies switches on again after reconnection - requesting the history of the current chat after reconnection - global availability (in drop down list) gets restored after reconnection - status icon in active chat changes when presence of the pen pal changes - infinite progress when open the dialogue with something that has no history to show +- fallback icons for buttons, when no supported theme is installed (shunf4) +- better handling messages with no id (shunf4) +- removed dependency: uuid, now it's on Qt (shunf4) +- better requesting latest history (shunf4) ### Improvements - slightly reduced the traffic on the startup by not requesting history of all MUCs -- completely rewritten message feed, now it works way faster +- completely rewritten message feed, now it works way faster and looks cooler - OPTIONAL RUNTIME dependency: "KIO Widgets" that is supposed to allow you to open a file in your default file manager - show in folder now is supposed to try it's best to show file in folder, even you don't have KIO installed - once uploaded local files don't get second time uploaded - the remote URL is reused +- way better compilation time (vae) + +### New features +- pasting images from clipboard to attachment (shunf4) +- possible compilation for windows and macOS (shunf4) ## Squawk 0.1.5 (Jul 29, 2020) ### Bug fixes diff --git a/README.md b/README.md index e94972f..ff36f3c 100644 --- a/README.md +++ b/README.md @@ -4,13 +4,13 @@ [![AUR version](https://img.shields.io/aur/version/squawk?style=flat-square)](https://aur.archlinux.org/packages/squawk/) [![Liberapay patrons](https://img.shields.io/liberapay/patrons/macaw.me?logo=liberapay&style=flat-square)](https://liberapay.com/macaw.me) -![Squawk screenshot](https://macaw.me/images/squawk/0.1.4.png) +![Squawk screenshot](https://macaw.me/images/squawk/0.2.0.png) ### Prerequisites - QT 5.12 *(lower versions might work but it wasn't tested)* - lmdb -- CMake 3.0 or higher +- CMake 3.3 or higher - qxmpp 1.1.0 or higher - KDE Frameworks: kwallet (optional) - KDE Frameworks: KIO (optional) diff --git a/core/main.cpp b/core/main.cpp index f63d4f8..d6eea3e 100644 --- a/core/main.cpp +++ b/core/main.cpp @@ -54,7 +54,7 @@ int main(int argc, char *argv[]) #endif QApplication::setApplicationName("squawk"); QApplication::setApplicationDisplayName("Squawk"); - QApplication::setApplicationVersion("0.1.5"); + QApplication::setApplicationVersion("0.2.0"); QTranslator qtTranslator; qtTranslator.load("qt_" + QLocale::system().name(), QLibraryInfo::location(QLibraryInfo::TranslationsPath)); diff --git a/packaging/Archlinux/PKGBUILD b/packaging/Archlinux/PKGBUILD index 20fea99..68e1558 100644 --- a/packaging/Archlinux/PKGBUILD +++ b/packaging/Archlinux/PKGBUILD @@ -1,6 +1,6 @@ # Maintainer: Yury Gubich pkgname=squawk -pkgver=0.1.5 +pkgver=0.2.0 pkgrel=1 pkgdesc="An XMPP desktop messenger, written on pure c++ (qt)" arch=('i686' 'x86_64') @@ -11,7 +11,7 @@ makedepends=('cmake>=3.3' 'imagemagick' 'qt5-tools') optdepends=('kwallet: secure password storage (requires rebuild)') source=("$pkgname-$pkgver.tar.gz") -sha256sums=('e1a4c88be9f0481d2aa21078faf42fd0e9d66b490b6d8af82827d441cb58df25') +sha256sums=('8e93d3dbe1fc35cfecb7783af409c6a264244d11609b2241d4fe77d43d068419') build() { cd "$srcdir/squawk" cmake . -D CMAKE_INSTALL_PREFIX=/usr -D CMAKE_BUILD_TYPE=Release diff --git a/shared/global.cpp b/shared/global.cpp index d6f2169..5f3b4ed 100644 --- a/shared/global.cpp +++ b/shared/global.cpp @@ -144,7 +144,7 @@ Shared::Global::FileInfo Shared::Global::getFileInfo(const QString& path) size = defaultIconFileInfoHeight; } - itr = instance->fileCache.insert(std::make_pair(path, FileInfo({info.fileName(), size, type, p}))).first; + itr = instance->fileCache.insert(std::make_pair(path, FileInfo({info.absoluteFilePath(), info.fileName(), size, type, p}))).first; } return itr->second; diff --git a/shared/global.h b/shared/global.h index 03cf84d..b1ae59c 100644 --- a/shared/global.h +++ b/shared/global.h @@ -55,6 +55,7 @@ namespace Shared { animation }; + QString path; QString name; QSize size; QMimeType mime; diff --git a/ui/widgets/messageline/feedview.cpp b/ui/widgets/messageline/feedview.cpp index d83ca6b..34f9400 100644 --- a/ui/widgets/messageline/feedview.cpp +++ b/ui/widgets/messageline/feedview.cpp @@ -43,6 +43,7 @@ FeedView::FeedView(QWidget* parent): QAbstractItemView(parent), hints(), vo(0), + elementMargin(0), specialDelegate(false), specialModel(false), clearWidgetsMode(false), @@ -106,7 +107,7 @@ QRect FeedView::visualRect(const QModelIndex& index) const } else { const Hint& hint = hints.at(row); const QWidget* vp = viewport(); - return QRect(0, vp->height() - hint.height - hint.offset + vo, vp->width(), hint.height); + return QRect(hint.x, vp->height() - hint.height - hint.offset + vo, hint.width, hint.height); } } @@ -198,7 +199,7 @@ void FeedView::updateGeometries() option.rect.setWidth(layoutBounds.width()); hints.clear(); - uint32_t previousOffset = 0; + uint32_t previousOffset = elementMargin; QDateTime lastDate; for (int i = 0, size = m->rowCount(); i < size; ++i) { QModelIndex index = m->index(i, 0, rootIndex()); @@ -206,19 +207,32 @@ void FeedView::updateGeometries() if (i > 0) { if (currentDate.daysTo(lastDate) > 0) { previousOffset += dividerMetrics.height() + dateDeviderMargin * 2; + } else { + previousOffset += elementMargin; } } lastDate = currentDate; - int height = itemDelegate(index)->sizeHint(option, index).height(); + QSize messageSize = itemDelegate(index)->sizeHint(option, index); + uint32_t offsetX(0); + if (specialDelegate) { + if (index.data(Models::MessageFeed::SentByMe).toBool()) { + offsetX = layoutBounds.width() - messageSize.width() - MessageDelegate::avatarHeight - MessageDelegate::margin * 2; + } else { + offsetX = MessageDelegate::avatarHeight + MessageDelegate::margin * 2; + } + } + hints.emplace_back(Hint({ false, previousOffset, - static_cast(height) + static_cast(messageSize.height()), + static_cast(messageSize.width()), + offsetX })); - previousOffset += height; + previousOffset += messageSize.height(); } - int totalHeight = previousOffset - layoutBounds.height(); + int totalHeight = previousOffset - layoutBounds.height() + dividerMetrics.height() + dateDeviderMargin * 2; if (modelState != Models::MessageFeed::complete) { totalHeight += progressSize; } @@ -240,7 +254,7 @@ void FeedView::updateGeometries() bool FeedView::tryToCalculateGeometriesWithNoScrollbars(const QStyleOptionViewItem& option, const QAbstractItemModel* m, uint32_t totalHeight) { - uint32_t previousOffset = 0; + uint32_t previousOffset = elementMargin; bool success = true; QDateTime lastDate; for (int i = 0, size = m->rowCount(); i < size; ++i) { @@ -249,21 +263,39 @@ bool FeedView::tryToCalculateGeometriesWithNoScrollbars(const QStyleOptionViewIt if (i > 0) { if (currentDate.daysTo(lastDate) > 0) { previousOffset += dateDeviderMargin * 2 + dividerMetrics.height(); + } else { + previousOffset += elementMargin; } } lastDate = currentDate; - int height = itemDelegate(index)->sizeHint(option, index).height(); + QSize messageSize = itemDelegate(index)->sizeHint(option, index); - if (previousOffset + height > totalHeight) { + if (previousOffset + messageSize.height() + elementMargin > totalHeight) { success = false; break; } + + uint32_t offsetX(0); + if (specialDelegate) { + if (index.data(Models::MessageFeed::SentByMe).toBool()) { + offsetX = option.rect.width() - messageSize.width() - MessageDelegate::avatarHeight - MessageDelegate::margin * 2; + } else { + offsetX = MessageDelegate::avatarHeight + MessageDelegate::margin * 2; + } + } hints.emplace_back(Hint({ false, previousOffset, - static_cast(height) + static_cast(messageSize.height()), + static_cast(messageSize.width()), + offsetX })); - previousOffset += height; + previousOffset += messageSize.height(); + } + + previousOffset += dateDeviderMargin * 2 + dividerMetrics.height(); + if (previousOffset > totalHeight) { + success = false; } return success; @@ -336,7 +368,7 @@ void FeedView::paintEvent(QPaintEvent* event) lastDate = currentDate; } if (!lastDate.isNull() && inZone) { //if after drawing all messages there is still space - drawDateDevider(option.rect.bottom(), lastDate, painter); + drawDateDevider(option.rect.top() - dateDeviderMargin * 2 - dividerMetrics.height(), lastDate, painter); } if (clearWidgetsMode && specialDelegate) { @@ -423,10 +455,12 @@ void FeedView::setItemDelegate(QAbstractItemDelegate* delegate) MessageDelegate* del = dynamic_cast(delegate); if (del) { specialDelegate = true; + elementMargin = MessageDelegate::margin; connect(del, &MessageDelegate::buttonPushed, this, &FeedView::onMessageButtonPushed); connect(del, &MessageDelegate::invalidPath, this, &FeedView::onMessageInvalidPath); } else { specialDelegate = false; + elementMargin = 0; } } diff --git a/ui/widgets/messageline/feedview.h b/ui/widgets/messageline/feedview.h index 5e08946..8bcd913 100644 --- a/ui/widgets/messageline/feedview.h +++ b/ui/widgets/messageline/feedview.h @@ -80,9 +80,12 @@ private: bool dirty; uint32_t offset; uint32_t height; + uint32_t width; + uint32_t x; }; std::deque hints; int vo; + int elementMargin; bool specialDelegate; bool specialModel; bool clearWidgetsMode; diff --git a/ui/widgets/messageline/messagedelegate.cpp b/ui/widgets/messageline/messagedelegate.cpp index 5dd0dce..d692752 100644 --- a/ui/widgets/messageline/messagedelegate.cpp +++ b/ui/widgets/messageline/messagedelegate.cpp @@ -26,8 +26,8 @@ #include "messagedelegate.h" #include "messagefeed.h" -constexpr int avatarHeight = 50; -constexpr int margin = 6; +int MessageDelegate::avatarHeight(50); +int MessageDelegate::margin(6); constexpr int textMargin = 2; constexpr int statusIconSize = 16; constexpr float nickFontMultiplier = 1.1; @@ -45,6 +45,7 @@ MessageDelegate::MessageDelegate(QObject* parent): nickMetrics(nickFont), dateMetrics(dateFont), buttonHeight(0), + buttonWidth(0), barHeight(0), buttons(new std::map()), bars(new std::map()), @@ -55,8 +56,9 @@ MessageDelegate::MessageDelegate(QObject* parent): idsToKeep(new std::set()), clearingWidgets(false) { - QPushButton btn; + QPushButton btn(QCoreApplication::translate("MessageLine", "Download")); buttonHeight = btn.sizeHint().height(); + buttonWidth = btn.sizeHint().width(); QProgressBar bar; barHeight = bar.sizeHint().height(); @@ -107,141 +109,79 @@ void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio painter->save(); painter->setRenderHint(QPainter::Antialiasing, true); -// if (option.state & QStyle::State_MouseOver) { -// painter->fillRect(option.rect, option.palette.brush(QPalette::Inactive, QPalette::Highlight)); -// } - - + paintBubble(data, painter, option); bool ntds = needToDrawSender(index, data); if (ntds || option.rect.y() < 1) { paintAvatar(data, index, option, painter); } QStyleOptionViewItem opt = option; - QRect messageRect = option.rect.adjusted(margin, margin / 2, -(avatarHeight + 2 * margin), -margin / 2); + opt.rect = option.rect.adjusted(bubbleMargin, bubbleMargin, -bubbleMargin, -bubbleMargin / 2); if (!data.sentByMe) { opt.displayAlignment = Qt::AlignLeft | Qt::AlignTop; - messageRect.adjust(avatarHeight + margin, 0, avatarHeight + margin, 0); } else { opt.displayAlignment = Qt::AlignRight | Qt::AlignTop; } - QPoint bubbleBegin = messageRect.topLeft(); - messageRect.adjust(bubbleMargin, bubbleMargin, -bubbleMargin, -bubbleMargin / 2); - opt.rect = messageRect; - - QSize messageSize(0, 0); QSize bodySize(0, 0); if (data.text.size() > 0) { - messageSize = bodyMetrics.boundingRect(messageRect, Qt::TextWordWrap, data.text).size(); - bodySize = messageSize; - } - - messageSize.rheight() += dateMetrics.height(); - QString dateString = data.date.toLocalTime().toString("hh:mm"); - - if (messageSize.width() < opt.rect.width()) { - if (ntds) { - QSize senderSize = nickMetrics.boundingRect(messageRect, 0, data.sender).size(); - if (senderSize.width() > messageSize.width()) { - messageSize.setWidth(senderSize.width()); - } - } - QSize dateSize = dateMetrics.boundingRect(messageRect, 0, dateString).size(); - int addition = 0; - - if (data.correction.corrected) { - addition += margin + statusIconSize; - } - if (data.sentByMe) { - addition += margin + statusIconSize; - } - if (dateSize.width() + addition > messageSize.width()) { - messageSize.setWidth(dateSize.width() + addition); - } - } else { - messageSize.setWidth(opt.rect.width()); - } - - painter->save(); - - int storedY = opt.rect.y(); - if (ntds) { - opt.rect.adjust(0, nickMetrics.lineSpacing() + textMargin, 0, 0); - } - - int attWidth(0); - switch (data.attach.state) { - case Models::none: - clearHelperWidget(data); //i can't imagine the situation where it's gonna be needed - break; //but it's a possible performance problem - case Models::uploading: - attWidth = std::max(paintPreview(data, painter, opt), attWidth); - [[fallthrough]]; - case Models::downloading: - messageSize.setWidth(opt.rect.width()); - messageSize.rheight() += barHeight + textMargin + opt.rect.y() - storedY; - paintBubble(data, painter, messageSize, opt, bubbleBegin); - attWidth = std::max(paintBar(getBar(data), painter, data.sentByMe, opt), attWidth); - break; - case Models::remote: - attWidth = std::max(paintButton(getButton(data), painter, data.sentByMe, opt), attWidth); - break; - case Models::ready: - case Models::local: - clearHelperWidget(data); - attWidth = std::max(paintPreview(data, painter, opt), attWidth); - break; - case Models::errorDownload: { - attWidth = std::max(paintButton(getButton(data), painter, data.sentByMe, opt), attWidth); - attWidth = std::max(paintComment(data, painter, opt), attWidth); - } - - break; - case Models::errorUpload:{ - clearHelperWidget(data); - attWidth = std::max(paintPreview(data, painter, opt), attWidth); - attWidth = std::max(paintComment(data, painter, opt), attWidth); - } - break; - } - painter->restore(); - if (data.attach.state != Models::uploading && data.attach.state != Models::downloading) { - messageSize.rheight() += opt.rect.y() - storedY; - messageSize.setWidth(std::max(attWidth, messageSize.width())); - paintBubble(data, painter, messageSize, opt, bubbleBegin); + bodySize = bodyMetrics.boundingRect(opt.rect, Qt::TextWordWrap, data.text).size(); } QRect rect; if (ntds) { painter->setFont(nickFont); - int storedY2 = opt.rect.y(); - opt.rect.setY(storedY); painter->drawText(opt.rect, opt.displayAlignment, data.sender, &rect); - opt.rect.setY(storedY2); + opt.rect.adjust(0, nickMetrics.lineSpacing() + textMargin, 0, 0); } + + painter->save(); + switch (data.attach.state) { + case Models::none: + clearHelperWidget(data); //i can't imagine the situation where it's gonna be needed + break; //but it's a possible performance problem + case Models::uploading: + paintPreview(data, painter, opt); + [[fallthrough]]; + case Models::downloading: + paintBar(getBar(data), painter, data.sentByMe, opt); + break; + case Models::remote: + paintButton(getButton(data), painter, data.sentByMe, opt); + break; + case Models::ready: + case Models::local: + clearHelperWidget(data); + paintPreview(data, painter, opt); + break; + case Models::errorDownload: { + paintButton(getButton(data), painter, data.sentByMe, opt); + paintComment(data, painter, opt); + } + + break; + case Models::errorUpload:{ + clearHelperWidget(data); + paintPreview(data, painter, opt); + paintComment(data, painter, opt); + } + break; + } + painter->restore(); - int messageLeft = INT16_MAX; - int messageRight = opt.rect.x() + messageSize.width(); QWidget* vp = static_cast(painter->device()); if (data.text.size() > 0) { QLabel* body = getBody(data); body->setParent(vp); - body->setMaximumWidth(bodySize.width()); - body->setMinimumWidth(bodySize.width()); - body->setMinimumHeight(bodySize.height()); - body->setMaximumHeight(bodySize.height()); - body->setAlignment(opt.displayAlignment); - messageLeft = opt.rect.x(); - if (data.sentByMe) { - messageLeft = opt.rect.topRight().x() - bodySize.width(); - } - body->move(messageLeft, opt.rect.y()); + body->setMinimumSize(bodySize); + body->setMaximumSize(bodySize); + body->move(opt.rect.left(), opt.rect.y()); body->show(); opt.rect.adjust(0, bodySize.height() + textMargin, 0, 0); } painter->setFont(dateFont); QColor q = painter->pen().color(); + QString dateString = data.date.toLocalTime().toString("hh:mm"); q.setAlpha(180); painter->setPen(q); painter->drawText(opt.rect, opt.displayAlignment, dateString, &rect); @@ -250,7 +190,7 @@ void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio QLabel* statusIcon = getStatusIcon(data); statusIcon->setParent(vp); - statusIcon->move(opt.rect.topRight().x() - messageSize.width(), currentY); + statusIcon->move(opt.rect.left(), currentY); statusIcon->show(); opt.rect.adjust(0, statusIconSize + textMargin, 0, 0); @@ -261,9 +201,9 @@ void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio pencilIcon->setParent(vp); if (data.sentByMe) { - pencilIcon->move(opt.rect.topRight().x() - messageSize.width() + statusIconSize + margin, currentY); + pencilIcon->move(opt.rect.left() + statusIconSize + margin, currentY); } else { - pencilIcon->move(messageRight - statusIconSize - margin, currentY); + pencilIcon->move(opt.rect.right() - statusIconSize - margin, currentY); } pencilIcon->show(); } else { @@ -281,19 +221,16 @@ void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio } } -void MessageDelegate::paintBubble(const Models::FeedItem& data, QPainter* painter, const QSize& messageSize, QStyleOptionViewItem& option, QPoint bubbleBegin) const +void MessageDelegate::paintBubble(const Models::FeedItem& data, QPainter* painter, const QStyleOptionViewItem& option) const { painter->save(); if (data.sentByMe) { - bubbleBegin.setX(option.rect.topRight().x() - messageSize.width() - bubbleMargin); painter->setBrush(option.palette.brush(QPalette::Inactive, QPalette::Highlight)); } else { painter->setBrush(option.palette.brush(QPalette::Window)); } - QSize bubbleAddition(2 * bubbleMargin, 1.5 * bubbleMargin); - QRect bubble(bubbleBegin, messageSize + bubbleAddition); painter->setPen(Qt::NoPen); - painter->drawRoundedRect(bubble, bubbleBorderRadius, bubbleBorderRadius); + painter->drawRoundedRect(option.rect, bubbleBorderRadius, bubbleBorderRadius); painter->restore(); } @@ -330,7 +267,7 @@ void MessageDelegate::paintAvatar(const Models::FeedItem& data, const QModelInde int ax; if (data.sentByMe) { - ax = option.rect.width() - avatarHeight - margin; + ax = option.rect.x() + option.rect.width() + margin; } else { ax = margin; } @@ -381,36 +318,51 @@ QSize MessageDelegate::sizeHint(const QStyleOptionViewItem& option, const QModel [[fallthrough]]; case Models::downloading: messageSize.rheight() += barHeight + textMargin; + messageSize.setWidth(messageRect.width()); break; case Models::remote: messageSize.rheight() += buttonHeight + textMargin; + messageSize.setWidth(std::max(messageSize.width(), buttonWidth)); break; case Models::ready: - case Models::local: - messageSize.rheight() += Preview::calculateAttachSize(data.attach.localPath, messageRect).height() + textMargin; + case Models::local: { + QSize aSize = Preview::calculateAttachSize(data.attach.localPath, messageRect); + messageSize.rheight() += aSize.height() + textMargin; + messageSize.setWidth(std::max(messageSize.width(), aSize.width())); + } break; - case Models::errorDownload: - messageSize.rheight() += buttonHeight + textMargin; - messageSize.rheight() += dateMetrics.boundingRect(messageRect, Qt::TextWordWrap, data.attach.error).size().height() + textMargin; + case Models::errorDownload: { + QSize commentSize = dateMetrics.boundingRect(messageRect, Qt::TextWordWrap, data.attach.error).size(); + messageSize.rheight() += commentSize.height() + buttonHeight + textMargin * 2; + messageSize.setWidth(std::max(messageSize.width(), std::max(commentSize.width(), buttonWidth))); + } break; - case Models::errorUpload: - messageSize.rheight() += Preview::calculateAttachSize(data.attach.localPath, messageRect).height() + textMargin; - messageSize.rheight() += dateMetrics.boundingRect(messageRect, Qt::TextWordWrap, data.attach.error).size().height() + textMargin; + case Models::errorUpload: { + QSize aSize = Preview::calculateAttachSize(data.attach.localPath, messageRect); + QSize commentSize = dateMetrics.boundingRect(messageRect, Qt::TextWordWrap, data.attach.error).size(); + messageSize.rheight() += aSize.height() + commentSize.height() + textMargin * 2; + messageSize.setWidth(std::max(messageSize.width(), std::max(commentSize.width(), aSize.width()))); + } break; } if (needToDrawSender(index, data)) { - messageSize.rheight() += nickMetrics.lineSpacing() + textMargin; + QSize senderSize = nickMetrics.boundingRect(messageRect, 0, data.sender).size(); + messageSize.rheight() += senderSize.height() + textMargin; + messageSize.setWidth(std::max(senderSize.width(), messageSize.width())); } - messageSize.rheight() += bubbleMargin + bubbleMargin / 2; - messageSize.rheight() += dateMetrics.height() > statusIconSize ? dateMetrics.height() : statusIconSize; - -// if (messageSize.height() < avatarHeight) { -// messageSize.setHeight(avatarHeight); -// } - - messageSize.rheight() += margin; + QString dateString = data.date.toLocalTime().toString("hh:mm"); + QSize dateSize = dateMetrics.boundingRect(messageRect, 0, dateString).size(); + messageSize.rheight() += bubbleMargin * 1.5; + messageSize.rheight() += dateSize.height() > statusIconSize ? dateSize.height() : statusIconSize; + + int statusWidth = dateSize.width() + statusIconSize + margin; + if (data.correction.corrected) { + statusWidth += statusIconSize + margin; + } + messageSize.setWidth(std::max(statusWidth, messageSize.width())); + messageSize.rwidth() += 2 * bubbleMargin; return messageSize; } @@ -508,7 +460,7 @@ int MessageDelegate::paintPreview(const Models::FeedItem& data, QPainter* painte preview->actualize(data.attach.localPath, size, option.rect.topLeft()); } else { QWidget* vp = static_cast(painter->device()); - preview = new Preview(data.attach.localPath, size, option.rect.topLeft(), data.sentByMe, vp); + preview = new Preview(data.attach.localPath, size, option.rect.topLeft(), vp); previews->insert(std::make_pair(data.id, preview)); } diff --git a/ui/widgets/messageline/messagedelegate.h b/ui/widgets/messageline/messagedelegate.h index 87a79c9..b58a1bb 100644 --- a/ui/widgets/messageline/messagedelegate.h +++ b/ui/widgets/messageline/messagedelegate.h @@ -55,6 +55,9 @@ public: bool editorEvent(QEvent * event, QAbstractItemModel * model, const QStyleOptionViewItem & option, const QModelIndex & index) override; void endClearWidgets(); void beginClearWidgets(); + + static int avatarHeight; + static int margin; signals: void buttonPushed(const QString& messageId) const; @@ -66,7 +69,7 @@ protected: int paintPreview(const Models::FeedItem& data, QPainter* painter, QStyleOptionViewItem& option) const; int paintComment(const Models::FeedItem& data, QPainter* painter, QStyleOptionViewItem& option) const; void paintAvatar(const Models::FeedItem& data, const QModelIndex& index, const QStyleOptionViewItem& option, QPainter* painter) const; - void paintBubble(const Models::FeedItem& data, QPainter* painter, const QSize& messageSize, QStyleOptionViewItem& option, QPoint bubbleBegin) const; + void paintBubble(const Models::FeedItem& data, QPainter* painter, const QStyleOptionViewItem& option) const; QPushButton* getButton(const Models::FeedItem& data) const; QProgressBar* getBar(const Models::FeedItem& data) const; QLabel* getStatusIcon(const Models::FeedItem& data) const; @@ -94,6 +97,7 @@ private: QFontMetrics dateMetrics; int buttonHeight; + int buttonWidth; int barHeight; std::map* buttons; diff --git a/ui/widgets/messageline/preview.cpp b/ui/widgets/messageline/preview.cpp index e54fce6..137293f 100644 --- a/ui/widgets/messageline/preview.cpp +++ b/ui/widgets/messageline/preview.cpp @@ -25,9 +25,8 @@ constexpr int maxAttachmentHeight = 500; QFont Preview::font; QFontMetrics Preview::metrics(Preview::font); -Preview::Preview(const QString& pPath, const QSize& pMaxSize, const QPoint& pos, bool pRight, QWidget* pParent): +Preview::Preview(const QString& pPath, const QSize& pMaxSize, const QPoint& pos, QWidget* pParent): info(Shared::Global::getFileInfo(pPath)), - path(pPath), maxSize(pMaxSize), actualSize(constrainAttachSize(info.size, maxSize)), cachedLabelSize(0, 0), @@ -37,8 +36,7 @@ Preview::Preview(const QString& pPath, const QSize& pMaxSize, const QPoint& pos, parent(pParent), movie(0), fileReachable(true), - actualPreview(false), - right(pRight) + actualPreview(false) { initializeElements(); @@ -104,9 +102,6 @@ void Preview::actualize(const QString& newPath, const QSize& newSize, const QPoi } } else if (maxSizeChanged) { applyNewMaxSize(); - if (right) { - positionChanged = true; - } } if (positionChanged || !actualPreview) { positionElements(); @@ -135,9 +130,6 @@ void Preview::setSize(const QSize& newSize) } if (maxSizeChanged || !actualPreview) { applyNewMaxSize(); - if (right) { - positionElements(); - } } } } @@ -146,7 +138,7 @@ void Preview::applyNewSize() { switch (info.preview) { case Shared::Global::FileInfo::Preview::picture: { - QImageReader img(path); + QImageReader img(info.path); if (!img.canRead()) { delete widget; fileReachable = false; @@ -216,9 +208,8 @@ void Preview::setPosition(const QPoint& newPoint) bool Preview::setPath(const QString& newPath) { - if (path != newPath) { - path = newPath; - info = Shared::Global::getFileInfo(path); + if (info.path != newPath) { + info = Shared::Global::getFileInfo(newPath); actualSize = constrainAttachSize(info.size, maxSize); clean(); initializeElements(); @@ -235,7 +226,7 @@ void Preview::initializeElements() { switch (info.preview) { case Shared::Global::FileInfo::Preview::picture: { - QImageReader img(path); + QImageReader img(info.path); if (!img.canRead()) { fileReachable = false; } else { @@ -248,7 +239,7 @@ void Preview::initializeElements() } break; case Shared::Global::FileInfo::Preview::animation:{ - movie = new QMovie(path); + movie = new QMovie(info.path); QObject::connect(movie, &QMovie::error, std::bind(&Preview::handleQMovieError, this, std::placeholders::_1) ); @@ -289,9 +280,6 @@ void Preview::initializeElements() void Preview::positionElements() { int start = position.x(); - if (right) { - start += maxSize.width() - size().width(); - } widget->move(start, position.y()); if (!actualPreview) { int x = start + actualSize.width() + margin; @@ -300,11 +288,36 @@ void Preview::positionElements() } } +bool Preview::canVisualize(const Shared::Global::FileInfo& info) +{ + switch (info.preview) { + case Shared::Global::FileInfo::Preview::picture: { + QImageReader img(info.path); + return img.canRead(); + } + break; + case Shared::Global::FileInfo::Preview::animation:{ + QMovie movie(info.path); + return movie.isValid(); + } + break; + default: { + return false; + } + } +} + QSize Preview::calculateAttachSize(const QString& path, const QRect& bounds) { Shared::Global::FileInfo info = Shared::Global::getFileInfo(path); - - return constrainAttachSize(info.size, bounds.size()); + QSize constrained = constrainAttachSize(info.size, bounds.size()); + if (!canVisualize(info)) { + int maxLabelWidth = bounds.width() - info.size.width() - margin; + QString elidedName = metrics.elidedText(info.name, Qt::ElideMiddle, maxLabelWidth); + int labelWidth = metrics.boundingRect(elidedName).size().width(); + constrained.rwidth() += margin + labelWidth; + } + return constrained; } QSize Preview::constrainAttachSize(QSize src, QSize bounds) diff --git a/ui/widgets/messageline/preview.h b/ui/widgets/messageline/preview.h index 004ed45..5165137 100644 --- a/ui/widgets/messageline/preview.h +++ b/ui/widgets/messageline/preview.h @@ -38,7 +38,7 @@ */ class Preview { public: - Preview(const QString& pPath, const QSize& pMaxSize, const QPoint& pos, bool pRight, QWidget* parent); + Preview(const QString& pPath, const QSize& pMaxSize, const QPoint& pos, QWidget* parent); ~Preview(); void actualize(const QString& newPath, const QSize& newSize, const QPoint& newPoint); @@ -51,6 +51,7 @@ public: static void initializeFont(const QFont& newFont); static QSize constrainAttachSize(QSize src, QSize bounds); static QSize calculateAttachSize(const QString& path, const QRect& bounds); + static bool canVisualize(const Shared::Global::FileInfo& info); static QFont font; static QFontMetrics metrics; @@ -64,7 +65,6 @@ private: private: Shared::Global::FileInfo info; - QString path; QSize maxSize; QSize actualSize; QSize cachedLabelSize; @@ -75,7 +75,6 @@ private: QMovie* movie; bool fileReachable; bool actualPreview; - bool right; }; #endif // PREVIEW_H From 296328f12dd9296a8a8d092b4fcd6f8332a15403 Mon Sep 17 00:00:00 2001 From: blue Date: Tue, 11 Jan 2022 23:50:42 +0300 Subject: [PATCH 47/93] a bit of polish --- CMakeLists.txt | 21 ++++++++++++++------- README.md | 5 +++-- packaging/Archlinux/PKGBUILD | 4 ++-- ui/widgets/messageline/feedview.cpp | 8 +++----- 4 files changed, 22 insertions(+), 16 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index da89682..b104667 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.4) -project(squawk VERSION 0.1.6 LANGUAGES CXX) +project(squawk VERSION 0.2.0 LANGUAGES CXX) cmake_policy(SET CMP0076 NEW) cmake_policy(SET CMP0079 NEW) @@ -32,6 +32,7 @@ option(WITH_KIO "Build KIO support module" ON) # Dependencies ## Qt +set(QT_VERSION_MAJOR 5) find_package(Qt5 COMPONENTS Widgets DBus Gui Xml Network Core REQUIRED) find_package(Boost COMPONENTS) @@ -114,12 +115,18 @@ endif () message("Build type: ${CMAKE_BUILD_TYPE}") if(CMAKE_COMPILER_IS_GNUCXX) -target_compile_options(squawk PRIVATE - "-Wall;-Wextra" - "$<$:-g>" - "$<$:-O3>" - "-fno-sized-deallocation" # for eliminating _ZdlPvm - ) + set (COMPILE_OPTIONS -fno-sized-deallocation) # for eliminating _ZdlPvm + if (CMAKE_BUILD_TYPE STREQUAL "Release") + list(APPEND COMPILE_OPTIONS -O3) + endif() + if (CMAKE_BUILD_TYPE STREQUAL Debug) + list(APPEND COMPILE_OPTIONS -g) + list(APPEND COMPILE_OPTIONS -Wall) + list(APPEND COMPILE_OPTIONS -Wextra) + endif() + + message("Compilation options: " ${COMPILE_OPTIONS}) + target_compile_options(squawk PRIVATE ${COMPILE_OPTIONS}) endif(CMAKE_COMPILER_IS_GNUCXX) add_subdirectory(core) diff --git a/README.md b/README.md index ff36f3c..0af201f 100644 --- a/README.md +++ b/README.md @@ -10,11 +10,12 @@ - QT 5.12 *(lower versions might work but it wasn't tested)* - lmdb -- CMake 3.3 or higher +- CMake 3.4 or higher - qxmpp 1.1.0 or higher - KDE Frameworks: kwallet (optional) - KDE Frameworks: KIO (optional) -- Boost +- Boost (just one little hpp from there) +- Imagemagick (for compilation, to rasterize an SVG logo) ### Getting diff --git a/packaging/Archlinux/PKGBUILD b/packaging/Archlinux/PKGBUILD index 68e1558..0d2aaaa 100644 --- a/packaging/Archlinux/PKGBUILD +++ b/packaging/Archlinux/PKGBUILD @@ -7,8 +7,8 @@ arch=('i686' 'x86_64') url="https://git.macaw.me/blue/squawk" license=('GPL3') depends=('hicolor-icon-theme' 'desktop-file-utils' 'lmdb' 'qxmpp>=1.1.0') -makedepends=('cmake>=3.3' 'imagemagick' 'qt5-tools') -optdepends=('kwallet: secure password storage (requires rebuild)') +makedepends=('cmake>=3.3' 'imagemagick' 'qt5-tools' 'boost') +optdepends=('kwallet: secure password storage (requires rebuild)' 'kio: better show in folder action (requires rebuild)') source=("$pkgname-$pkgver.tar.gz") sha256sums=('8e93d3dbe1fc35cfecb7783af409c6a264244d11609b2241d4fe77d43d068419') diff --git a/ui/widgets/messageline/feedview.cpp b/ui/widgets/messageline/feedview.cpp index 34f9400..de7f56f 100644 --- a/ui/widgets/messageline/feedview.cpp +++ b/ui/widgets/messageline/feedview.cpp @@ -255,7 +255,6 @@ void FeedView::updateGeometries() bool FeedView::tryToCalculateGeometriesWithNoScrollbars(const QStyleOptionViewItem& option, const QAbstractItemModel* m, uint32_t totalHeight) { uint32_t previousOffset = elementMargin; - bool success = true; QDateTime lastDate; for (int i = 0, size = m->rowCount(); i < size; ++i) { QModelIndex index = m->index(i, 0, rootIndex()); @@ -271,8 +270,7 @@ bool FeedView::tryToCalculateGeometriesWithNoScrollbars(const QStyleOptionViewIt QSize messageSize = itemDelegate(index)->sizeHint(option, index); if (previousOffset + messageSize.height() + elementMargin > totalHeight) { - success = false; - break; + return false; } uint32_t offsetX(0); @@ -295,10 +293,10 @@ bool FeedView::tryToCalculateGeometriesWithNoScrollbars(const QStyleOptionViewIt previousOffset += dateDeviderMargin * 2 + dividerMetrics.height(); if (previousOffset > totalHeight) { - success = false; + return false; } - return success; + return true; } From 62a59eb7a15f466ad737e9dc0ed6850d3d6a3d50 Mon Sep 17 00:00:00 2001 From: blue Date: Sat, 15 Jan 2022 15:36:49 +0300 Subject: [PATCH 48/93] Added logs for Shura to help me to debug a download attachment issue --- CHANGELOG.md | 7 +++++++ CMakeLists.txt | 2 +- core/archive.cpp | 1 + core/handlers/messagehandler.cpp | 3 +++ core/main.cpp | 2 +- ui/widgets/messageline/messagefeed.cpp | 2 +- 6 files changed, 14 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 09c382a..4052647 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## Squawk 0.2.1 (UNRELEASED) +### Bug fixes + +### Improvements + +### New features + ## Squawk 0.2.0 (Jan 10, 2022) ### Bug fixes - carbon copies switches on again after reconnection diff --git a/CMakeLists.txt b/CMakeLists.txt index b104667..cd9d793 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.4) -project(squawk VERSION 0.2.0 LANGUAGES CXX) +project(squawk VERSION 0.2.1 LANGUAGES CXX) cmake_policy(SET CMP0076 NEW) cmake_policy(SET CMP0079 NEW) diff --git a/core/archive.cpp b/core/archive.cpp index 2ca409c..cb65a53 100644 --- a/core/archive.cpp +++ b/core/archive.cpp @@ -123,6 +123,7 @@ bool Core::Archive::addElement(const Shared::Message& message) if (!opened) { throw Closed("addElement", jid.toStdString()); } + qDebug() << "Adding message with id " << message.getId(); QByteArray ba; QDataStream ds(&ba, QIODevice::WriteOnly); message.serialize(ds); diff --git a/core/handlers/messagehandler.cpp b/core/handlers/messagehandler.cpp index 33b3458..eb840f8 100644 --- a/core/handlers/messagehandler.cpp +++ b/core/handlers/messagehandler.cpp @@ -80,6 +80,7 @@ bool Core::MessageHandler::handleChatMessage(const QXmppMessage& msg, bool outgo Contact* cnt = acc->rh->getContact(jid); if (cnt == 0) { cnt = acc->rh->addOutOfRosterContact(jid); + qDebug() << "appending message" << sMsg.getId() << "to an out of roster contact"; } if (outgoing) { if (forwarded) { @@ -162,6 +163,7 @@ void Core::MessageHandler::initializeMessage(Shared::Message& target, const QXmp id = source.id(); } target.setStanzaId(source.stanzaId()); + qDebug() << "initializing message with originId:" << source.originId() << ", id:" << source.id() << ", stansaId:" << source.stanzaId(); #else id = source.id(); #endif @@ -170,6 +172,7 @@ void Core::MessageHandler::initializeMessage(Shared::Message& target, const QXmp if (messageId.size() == 0) { target.generateRandomId(); //TODO out of desperation, I need at least a random ID messageId = target.getId(); + qDebug() << "Had do initialize a message with no id, assigning autogenerated" << messageId; } target.setFrom(source.from()); target.setTo(source.to()); diff --git a/core/main.cpp b/core/main.cpp index d6eea3e..7c94a12 100644 --- a/core/main.cpp +++ b/core/main.cpp @@ -54,7 +54,7 @@ int main(int argc, char *argv[]) #endif QApplication::setApplicationName("squawk"); QApplication::setApplicationDisplayName("Squawk"); - QApplication::setApplicationVersion("0.2.0"); + QApplication::setApplicationVersion("0.2.1"); QTranslator qtTranslator; qtTranslator.load("qt_" + QLocale::system().name(), QLibraryInfo::location(QLibraryInfo::TranslationsPath)); diff --git a/ui/widgets/messageline/messagefeed.cpp b/ui/widgets/messageline/messagefeed.cpp index 733cf1d..4803dce 100644 --- a/ui/widgets/messageline/messagefeed.cpp +++ b/ui/widgets/messageline/messagefeed.cpp @@ -86,7 +86,7 @@ void Models::MessageFeed::addMessage(const Shared::Message& msg) emit newMessage(msg); if (observersAmount == 0 && !msg.getForwarded()) { //not to notify when the message is delivered by the carbon copy - unreadMessages->insert(msg.getId()); //cuz it could be my own one or the one I read on another device + unreadMessages->insert(id); //cuz it could be my own one or the one I read on another device emit unreadMessagesCountChanged(); emit unnoticedMessage(msg); } From 6bee149e6b13d340483654316a3d391bfa6b29a0 Mon Sep 17 00:00:00 2001 From: blue Date: Sun, 16 Jan 2022 22:54:57 +0300 Subject: [PATCH 49/93] started to work on settings --- ui/squawk.cpp | 28 ++++++- ui/squawk.h | 6 +- ui/squawk.ui | 9 ++ ui/widgets/CMakeLists.txt | 1 + ui/widgets/settings/CMakeLists.txt | 7 ++ ui/widgets/settings/settings.cpp | 14 ++++ ui/widgets/settings/settings.h | 26 ++++++ ui/widgets/settings/settings.ui | 119 +++++++++++++++++++++++++++ ui/widgets/settings/settingslist.cpp | 27 ++++++ ui/widgets/settings/settingslist.h | 25 ++++++ 10 files changed, 260 insertions(+), 2 deletions(-) create mode 100644 ui/widgets/settings/CMakeLists.txt create mode 100644 ui/widgets/settings/settings.cpp create mode 100644 ui/widgets/settings/settings.h create mode 100644 ui/widgets/settings/settings.ui create mode 100644 ui/widgets/settings/settingslist.cpp create mode 100644 ui/widgets/settings/settingslist.h diff --git a/ui/squawk.cpp b/ui/squawk.cpp index 800f02a..4d22b34 100644 --- a/ui/squawk.cpp +++ b/ui/squawk.cpp @@ -25,6 +25,7 @@ Squawk::Squawk(QWidget *parent) : QMainWindow(parent), m_ui(new Ui::Squawk), accounts(0), + preferences(0), rosterModel(), conversations(), contextMenu(new QMenu()), @@ -55,6 +56,7 @@ Squawk::Squawk(QWidget *parent) : m_ui->comboBox->setCurrentIndex(static_cast(Shared::Availability::offline)); connect(m_ui->actionAccounts, &QAction::triggered, this, &Squawk::onAccounts); + connect(m_ui->actionPreferences, &QAction::triggered, this, &Squawk::onPreferences); connect(m_ui->actionAddContact, &QAction::triggered, this, &Squawk::onNewContact); connect(m_ui->actionAddConference, &QAction::triggered, this, &Squawk::onNewConference); connect(m_ui->actionQuit, &QAction::triggered, this, &Squawk::close); @@ -117,6 +119,22 @@ void Squawk::onAccounts() } } +void Squawk::onPreferences() +{ + if (preferences == 0) { + preferences = new Settings(); + preferences->setAttribute(Qt::WA_DeleteOnClose); + connect(preferences, &Settings::destroyed, this, &Squawk::onPreferencesClosed); + + preferences->show(); + } else { + preferences->show(); + preferences->raise(); + preferences->activateWindow(); + } +} + + void Squawk::onAccountsSizeChanged(unsigned int size) { if (size > 0) { @@ -173,6 +191,9 @@ void Squawk::closeEvent(QCloseEvent* event) if (accounts != 0) { accounts->close(); } + if (preferences != 0) { + preferences->close(); + } for (Conversations::const_iterator itr = conversations.begin(), end = conversations.end(); itr != end; ++itr) { disconnect(itr->second, &Conversation::destroyed, this, &Squawk::onConversationClosed); @@ -190,11 +211,16 @@ void Squawk::closeEvent(QCloseEvent* event) } -void Squawk::onAccountsClosed(QObject* parent) +void Squawk::onAccountsClosed() { accounts = 0; } +void Squawk::onPreferencesClosed() +{ + preferences = 0; +} + void Squawk::newAccount(const QMap& account) { rosterModel.addAccount(account); diff --git a/ui/squawk.h b/ui/squawk.h index cb93259..26dc0c9 100644 --- a/ui/squawk.h +++ b/ui/squawk.h @@ -38,6 +38,7 @@ #include "widgets/joinconference.h" #include "models/roster.h" #include "widgets/vcard/vcard.h" +#include "widgets/settings/settings.h" #include "shared/shared.h" @@ -117,6 +118,7 @@ private: QScopedPointer m_ui; Accounts* accounts; + Settings* preferences; Models::Roster rosterModel; Conversations conversations; QMenu* contextMenu; @@ -136,12 +138,14 @@ protected slots: private slots: void onAccounts(); + void onPreferences(); void onNewContact(); void onNewConference(); void onNewContactAccepted(); void onJoinConferenceAccepted(); void onAccountsSizeChanged(unsigned int size); - void onAccountsClosed(QObject* parent = 0); + void onAccountsClosed(); + void onPreferencesClosed(); void onConversationClosed(QObject* parent = 0); void onVCardClosed(); void onVCardSave(const Shared::VCard& card, const QString& account); diff --git a/ui/squawk.ui b/ui/squawk.ui index 01ffbba..840dfee 100644 --- a/ui/squawk.ui +++ b/ui/squawk.ui @@ -191,6 +191,7 @@ Settings + @@ -245,6 +246,14 @@ Add conference + + + + + + Preferences + + diff --git a/ui/widgets/CMakeLists.txt b/ui/widgets/CMakeLists.txt index c7e47e0..f3a2afe 100644 --- a/ui/widgets/CMakeLists.txt +++ b/ui/widgets/CMakeLists.txt @@ -22,3 +22,4 @@ target_sources(squawk PRIVATE add_subdirectory(vcard) add_subdirectory(messageline) +add_subdirectory(settings) diff --git a/ui/widgets/settings/CMakeLists.txt b/ui/widgets/settings/CMakeLists.txt new file mode 100644 index 0000000..9f0fa76 --- /dev/null +++ b/ui/widgets/settings/CMakeLists.txt @@ -0,0 +1,7 @@ +target_sources(squawk PRIVATE + settingslist.h + settingslist.cpp + settings.h + settings.cpp + settings.ui +) diff --git a/ui/widgets/settings/settings.cpp b/ui/widgets/settings/settings.cpp new file mode 100644 index 0000000..0397d0c --- /dev/null +++ b/ui/widgets/settings/settings.cpp @@ -0,0 +1,14 @@ +#include "settings.h" +#include "ui_settings.h" + +Settings::Settings(QWidget* parent): + QWidget(parent), + m_ui(new Ui::Settings()) +{ + m_ui->setupUi(this); +} + +Settings::~Settings() +{ +} + diff --git a/ui/widgets/settings/settings.h b/ui/widgets/settings/settings.h new file mode 100644 index 0000000..61129d8 --- /dev/null +++ b/ui/widgets/settings/settings.h @@ -0,0 +1,26 @@ +#ifndef SETTINGS_H +#define SETTINGS_H + +#include +#include + +namespace Ui +{ +class Settings; +} + +/** + * @todo write docs + */ +class Settings : public QWidget +{ + Q_OBJECT +public: + Settings(QWidget* parent = nullptr); + ~Settings(); + +private: + QScopedPointer m_ui; +}; + +#endif // SETTINGS_H diff --git a/ui/widgets/settings/settings.ui b/ui/widgets/settings/settings.ui new file mode 100644 index 0000000..f5c4680 --- /dev/null +++ b/ui/widgets/settings/settings.ui @@ -0,0 +1,119 @@ + + + Settings + + + + 0 + 0 + 520 + 363 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + QFrame::NoFrame + + + 0 + + + Qt::ScrollBarAlwaysOff + + + QAbstractScrollArea::AdjustToContents + + + QAbstractItemView::NoEditTriggers + + + false + + + QAbstractItemView::NoDragDrop + + + QAbstractItemView::ScrollPerPixel + + + QListView::Static + + + QListView::TopToBottom + + + false + + + QListView::Adjust + + + QListView::Batched + + + QListView::IconMode + + + true + + + Qt::AlignHCenter + + + 0 + + + + General + + + + .. + + + ItemIsSelectable|ItemIsEnabled + + + + + Appearance + + + + .. + + + ItemIsSelectable|ItemIsEnabled + + + + + + + + + SettingsList + QListWidget +
ui/widgets/settings/settingslist.h
+
+
+ + +
diff --git a/ui/widgets/settings/settingslist.cpp b/ui/widgets/settings/settingslist.cpp new file mode 100644 index 0000000..1925632 --- /dev/null +++ b/ui/widgets/settings/settingslist.cpp @@ -0,0 +1,27 @@ +#include "settingslist.h" + +SettingsList::SettingsList(QWidget* parent): + QListWidget(parent), + lastWidth(0) +{ +} + +SettingsList::~SettingsList() +{ +} + +QStyleOptionViewItem SettingsList::viewOptions() const +{ + QStyleOptionViewItem option = QListWidget::viewOptions(); + if (!iconSize().isValid()) { + option.decorationSize.setWidth(lastWidth); + } + option.rect.setWidth(lastWidth); + return option; +} + +void SettingsList::resizeEvent(QResizeEvent* event) +{ + lastWidth = event->size().width(); + QListWidget::resizeEvent(event); +} diff --git a/ui/widgets/settings/settingslist.h b/ui/widgets/settings/settingslist.h new file mode 100644 index 0000000..9621c67 --- /dev/null +++ b/ui/widgets/settings/settingslist.h @@ -0,0 +1,25 @@ +#ifndef UI_SETTINGSLIST_H +#define UI_SETTINGSLIST_H + +#include +#include + +/** + * @todo write docs + */ +class SettingsList : public QListWidget +{ + Q_OBJECT +public: + SettingsList(QWidget* parent = nullptr); + ~SettingsList(); + +protected: + QStyleOptionViewItem viewOptions() const override; + void resizeEvent(QResizeEvent * event) override; + +private: + int lastWidth; +}; + +#endif // UI_SETTINGSLIST_H From 841e526e59fd7a12d6835de1bdee8967aaa3b5a1 Mon Sep 17 00:00:00 2001 From: blue Date: Mon, 17 Jan 2022 23:52:07 +0300 Subject: [PATCH 50/93] just some toying with designer --- ui/widgets/settings/settings.ui | 64 +++++++++++++++++++++++++++++++-- 1 file changed, 61 insertions(+), 3 deletions(-) diff --git a/ui/widgets/settings/settings.ui b/ui/widgets/settings/settings.ui index f5c4680..ca9946e 100644 --- a/ui/widgets/settings/settings.ui +++ b/ui/widgets/settings/settings.ui @@ -6,7 +6,7 @@ 0 0 - 520 + 465 363 @@ -21,13 +21,51 @@ 0 - 0 + 7 0 - + + + + Apply + + + + + + + + + + Ok + + + + + + + + + + 0 + 0 + + + + + 120 + 0 + + + + + 120 + 16777215 + + QFrame::NoFrame @@ -105,6 +143,26 @@ + + + + + + + General + + + + + + + Cancel + + + + + + From a8a7ce2538887e6a4875ffa7f68a3f2859bd7dab Mon Sep 17 00:00:00 2001 From: blue Date: Wed, 19 Jan 2022 23:46:42 +0300 Subject: [PATCH 51/93] some more thoughts about settings widgets --- ui/widgets/settings/CMakeLists.txt | 6 ++ ui/widgets/settings/pageappearance.cpp | 13 +++ ui/widgets/settings/pageappearance.h | 26 +++++ ui/widgets/settings/pageappearance.ui | 28 ++++++ ui/widgets/settings/pagegeneral.cpp | 13 +++ ui/widgets/settings/pagegeneral.h | 26 +++++ ui/widgets/settings/pagegeneral.ui | 28 ++++++ ui/widgets/settings/settings.cpp | 10 ++ ui/widgets/settings/settings.h | 4 + ui/widgets/settings/settings.ui | 127 ++++++++++++++++--------- ui/widgets/settings/settingslist.cpp | 11 +++ ui/widgets/settings/settingslist.h | 1 + 12 files changed, 248 insertions(+), 45 deletions(-) create mode 100644 ui/widgets/settings/pageappearance.cpp create mode 100644 ui/widgets/settings/pageappearance.h create mode 100644 ui/widgets/settings/pageappearance.ui create mode 100644 ui/widgets/settings/pagegeneral.cpp create mode 100644 ui/widgets/settings/pagegeneral.h create mode 100644 ui/widgets/settings/pagegeneral.ui diff --git a/ui/widgets/settings/CMakeLists.txt b/ui/widgets/settings/CMakeLists.txt index 9f0fa76..e100bfe 100644 --- a/ui/widgets/settings/CMakeLists.txt +++ b/ui/widgets/settings/CMakeLists.txt @@ -4,4 +4,10 @@ target_sources(squawk PRIVATE settings.h settings.cpp settings.ui + pagegeneral.h + pagegeneral.cpp + pagegeneral.ui + pageappearance.h + pageappearance.cpp + pageappearance.ui ) diff --git a/ui/widgets/settings/pageappearance.cpp b/ui/widgets/settings/pageappearance.cpp new file mode 100644 index 0000000..725f452 --- /dev/null +++ b/ui/widgets/settings/pageappearance.cpp @@ -0,0 +1,13 @@ +#include "pageappearance.h" +#include "ui_pageappearance.h" + +PageAppearance::PageAppearance(QWidget* parent): + QWidget(parent), + m_ui(new Ui::PageAppearance()) +{ + m_ui->setupUi(this); +} + +PageAppearance::~PageAppearance() +{ +} diff --git a/ui/widgets/settings/pageappearance.h b/ui/widgets/settings/pageappearance.h new file mode 100644 index 0000000..85d45a1 --- /dev/null +++ b/ui/widgets/settings/pageappearance.h @@ -0,0 +1,26 @@ +#ifndef PAGEAPPEARANCE_H +#define PAGEAPPEARANCE_H + +#include +#include + +namespace Ui +{ +class PageAppearance; +} + +/** + * @todo write docs + */ +class PageAppearance : public QWidget +{ + Q_OBJECT +public: + PageAppearance(QWidget* parent = nullptr); + ~PageAppearance(); + +private: + QScopedPointer m_ui; +}; + +#endif // PAGEAPPEARANCE_H diff --git a/ui/widgets/settings/pageappearance.ui b/ui/widgets/settings/pageappearance.ui new file mode 100644 index 0000000..1199347 --- /dev/null +++ b/ui/widgets/settings/pageappearance.ui @@ -0,0 +1,28 @@ + + + PageAppearance + + + + 0 + 0 + 400 + 300 + + + + + + + Theme + + + + + + + + + + + diff --git a/ui/widgets/settings/pagegeneral.cpp b/ui/widgets/settings/pagegeneral.cpp new file mode 100644 index 0000000..e448f80 --- /dev/null +++ b/ui/widgets/settings/pagegeneral.cpp @@ -0,0 +1,13 @@ +#include "pagegeneral.h" +#include "ui_pagegeneral.h" + +PageGeneral::PageGeneral(QWidget* parent): + QWidget(parent), + m_ui(new Ui::PageGeneral()) +{ + m_ui->setupUi(this); +} + +PageGeneral::~PageGeneral() +{ +} diff --git a/ui/widgets/settings/pagegeneral.h b/ui/widgets/settings/pagegeneral.h new file mode 100644 index 0000000..77c0c3a --- /dev/null +++ b/ui/widgets/settings/pagegeneral.h @@ -0,0 +1,26 @@ +#ifndef PAGEGENERAL_H +#define PAGEGENERAL_H + +#include +#include + +namespace Ui +{ +class PageGeneral; +} + +/** + * @todo write docs + */ +class PageGeneral : public QWidget +{ + Q_OBJECT +public: + PageGeneral(QWidget* parent = nullptr); + ~PageGeneral(); + +private: + QScopedPointer m_ui; +}; + +#endif // PAGEGENERAL_H diff --git a/ui/widgets/settings/pagegeneral.ui b/ui/widgets/settings/pagegeneral.ui new file mode 100644 index 0000000..9921715 --- /dev/null +++ b/ui/widgets/settings/pagegeneral.ui @@ -0,0 +1,28 @@ + + + PageGeneral + + + + 0 + 0 + 400 + 300 + + + + + + + Downloads path + + + + + + + + + + + diff --git a/ui/widgets/settings/settings.cpp b/ui/widgets/settings/settings.cpp index 0397d0c..cdcf0cc 100644 --- a/ui/widgets/settings/settings.cpp +++ b/ui/widgets/settings/settings.cpp @@ -6,9 +6,19 @@ Settings::Settings(QWidget* parent): m_ui(new Ui::Settings()) { m_ui->setupUi(this); + + connect(m_ui->list, &QListWidget::currentItemChanged, this, &Settings::onCurrentPageChanged); } Settings::~Settings() { } +void Settings::onCurrentPageChanged(QListWidgetItem* current) +{ + if (current != nullptr) { + m_ui->header->setText(current->text()); + + m_ui->content->setCurrentIndex(m_ui->list->currentRow()); + } +} diff --git a/ui/widgets/settings/settings.h b/ui/widgets/settings/settings.h index 61129d8..f961e08 100644 --- a/ui/widgets/settings/settings.h +++ b/ui/widgets/settings/settings.h @@ -2,6 +2,7 @@ #define SETTINGS_H #include +#include #include namespace Ui @@ -19,6 +20,9 @@ public: Settings(QWidget* parent = nullptr); ~Settings(); +protected slots: + void onCurrentPageChanged(QListWidgetItem* current); + private: QScopedPointer m_ui; }; diff --git a/ui/widgets/settings/settings.ui b/ui/widgets/settings/settings.ui index ca9946e..fe092dc 100644 --- a/ui/widgets/settings/settings.ui +++ b/ui/widgets/settings/settings.ui @@ -6,7 +6,7 @@ 0 0 - 465 + 502 363 @@ -21,33 +21,13 @@ 0 - 7 + 0 0 - - - - Apply - - - - - - - - - - Ok - - - - - - - - + + 0 @@ -66,12 +46,12 @@ 16777215 + + + QFrame::NoFrame - - 0 - Qt::ScrollBarAlwaysOff @@ -103,7 +83,7 @@ QListView::Adjust - QListView::Batched + QListView::SinglePass QListView::IconMode @@ -143,24 +123,71 @@ - - - - - - - General - - - - - - - Cancel - - - + + + + + 0 + 0 + + + + + + Apply + + + + .. + + + + + + + Cancel + + + + .. + + + + + + + Ok + + + + .. + + + + + + + font-size: 14pt; + + + General + + + + + + + + 0 + 0 + + + + + + + @@ -171,6 +198,16 @@ QListWidget
ui/widgets/settings/settingslist.h
+ + PageGeneral + QWidget +
ui/widgets/settings/pagegeneral.h
+
+ + PageAppearance + QWidget +
ui/widgets/settings/pageappearance.h
+
diff --git a/ui/widgets/settings/settingslist.cpp b/ui/widgets/settings/settingslist.cpp index 1925632..3a5e2cb 100644 --- a/ui/widgets/settings/settingslist.cpp +++ b/ui/widgets/settings/settingslist.cpp @@ -4,6 +4,7 @@ SettingsList::SettingsList(QWidget* parent): QListWidget(parent), lastWidth(0) { + } SettingsList::~SettingsList() @@ -25,3 +26,13 @@ void SettingsList::resizeEvent(QResizeEvent* event) lastWidth = event->size().width(); QListWidget::resizeEvent(event); } + +QRect SettingsList::visualRect(const QModelIndex& index) const +{ + QRect res = QListWidget::visualRect(index); + if (index.isValid()) { + res.setWidth(lastWidth); + } + return res; +} + diff --git a/ui/widgets/settings/settingslist.h b/ui/widgets/settings/settingslist.h index 9621c67..a51fc3a 100644 --- a/ui/widgets/settings/settingslist.h +++ b/ui/widgets/settings/settingslist.h @@ -17,6 +17,7 @@ public: protected: QStyleOptionViewItem viewOptions() const override; void resizeEvent(QResizeEvent * event) override; + QRect visualRect(const QModelIndex & index) const override; private: int lastWidth; From c708c33a92153e820cd37f266bf8995682148404 Mon Sep 17 00:00:00 2001 From: blue Date: Fri, 21 Jan 2022 22:02:50 +0300 Subject: [PATCH 52/93] basic theme changing --- core/main.cpp | 11 ++++++- shared/global.cpp | 1 + shared/global.h | 5 +++- ui/widgets/settings/pageappearance.cpp | 31 +++++++++++++++++++- ui/widgets/settings/pageappearance.h | 11 +++++++ ui/widgets/settings/pagegeneral.h | 4 +++ ui/widgets/settings/settings.cpp | 40 +++++++++++++++++++++++++- ui/widgets/settings/settings.h | 9 ++++++ 8 files changed, 108 insertions(+), 4 deletions(-) diff --git a/core/main.cpp b/core/main.cpp index 7c94a12..03827d3 100644 --- a/core/main.cpp +++ b/core/main.cpp @@ -55,7 +55,7 @@ int main(int argc, char *argv[]) QApplication::setApplicationName("squawk"); QApplication::setApplicationDisplayName("Squawk"); QApplication::setApplicationVersion("0.2.1"); - + QTranslator qtTranslator; qtTranslator.load("qt_" + QLocale::system().name(), QLibraryInfo::location(QLibraryInfo::TranslationsPath)); app.installTranslator(&qtTranslator); @@ -88,6 +88,15 @@ int main(int argc, char *argv[]) QApplication::setWindowIcon(icon); new Shared::Global(); //translates enums + + QSettings settings; + QVariant vtheme = settings.value("theme"); + if (vtheme.isValid()) { + QString theme = vtheme.toString().toLower(); + if (theme != "system") { + QApplication::setStyle(theme); + } + } Squawk w; w.show(); diff --git a/shared/global.cpp b/shared/global.cpp index 5f3b4ed..7ee9599 100644 --- a/shared/global.cpp +++ b/shared/global.cpp @@ -84,6 +84,7 @@ Shared::Global::Global(): tr("Squawk is going to query you for the password on every start of the program", "AccountPasswordDescription"), tr("Your password is going to be stored in KDE wallet storage (KWallet). You're going to be queried for permissions", "AccountPasswordDescription") }), + defaultSystemStyle(QApplication::style()->objectName()), pluginSupport({ {"KWallet", false}, {"openFileManagerWindowJob", false} diff --git a/shared/global.h b/shared/global.h index b1ae59c..a87b9bc 100644 --- a/shared/global.h +++ b/shared/global.h @@ -27,7 +27,8 @@ #include #include -#include +#include +#include #include #include #include @@ -84,6 +85,8 @@ namespace Shared { const std::deque accountPassword; const std::deque accountPasswordDescription; + + const QString defaultSystemStyle; static bool supported(const QString& pluginName); static void setSupported(const QString& pluginName, bool support); diff --git a/ui/widgets/settings/pageappearance.cpp b/ui/widgets/settings/pageappearance.cpp index 725f452..2fb8dc8 100644 --- a/ui/widgets/settings/pageappearance.cpp +++ b/ui/widgets/settings/pageappearance.cpp @@ -1,13 +1,42 @@ #include "pageappearance.h" #include "ui_pageappearance.h" +#include + PageAppearance::PageAppearance(QWidget* parent): QWidget(parent), - m_ui(new Ui::PageAppearance()) + m_ui(new Ui::PageAppearance()), + styles() { m_ui->setupUi(this); + + m_ui->themeInput->addItem(tr("System")); + styles.push_back("system"); + QStringList themes = QStyleFactory::keys(); + for (const QString& key : themes) { + m_ui->themeInput->addItem(key); + styles.push_back(key); + } + + QSettings settings; + QVariant vtheme = settings.value("theme"); + if (vtheme.isValid()) { + QString theme = vtheme.toString(); + m_ui->themeInput->setCurrentText(theme); + } else { + m_ui->themeInput->setCurrentText("System"); + } + + connect(m_ui->themeInput, qOverload(&QComboBox::currentIndexChanged), this, &PageAppearance::onThemeChanged); } PageAppearance::~PageAppearance() { } + +void PageAppearance::onThemeChanged(int index) +{ + if (index >= 0) { + emit variableModified("theme", styles[index]); + } +} diff --git a/ui/widgets/settings/pageappearance.h b/ui/widgets/settings/pageappearance.h index 85d45a1..9cb1830 100644 --- a/ui/widgets/settings/pageappearance.h +++ b/ui/widgets/settings/pageappearance.h @@ -3,6 +3,10 @@ #include #include +#include +#include +#include +#include namespace Ui { @@ -19,8 +23,15 @@ public: PageAppearance(QWidget* parent = nullptr); ~PageAppearance(); +signals: + void variableModified(const QString& key, const QVariant& value); + +protected slots: + void onThemeChanged(int index); + private: QScopedPointer m_ui; + std::vector styles; }; #endif // PAGEAPPEARANCE_H diff --git a/ui/widgets/settings/pagegeneral.h b/ui/widgets/settings/pagegeneral.h index 77c0c3a..b8c30c5 100644 --- a/ui/widgets/settings/pagegeneral.h +++ b/ui/widgets/settings/pagegeneral.h @@ -3,6 +3,7 @@ #include #include +#include namespace Ui { @@ -19,6 +20,9 @@ public: PageGeneral(QWidget* parent = nullptr); ~PageGeneral(); +signals: + void variableModified(const QString& key, const QVariant& value); + private: QScopedPointer m_ui; }; diff --git a/ui/widgets/settings/settings.cpp b/ui/widgets/settings/settings.cpp index cdcf0cc..75a7780 100644 --- a/ui/widgets/settings/settings.cpp +++ b/ui/widgets/settings/settings.cpp @@ -3,11 +3,19 @@ Settings::Settings(QWidget* parent): QWidget(parent), - m_ui(new Ui::Settings()) + m_ui(new Ui::Settings()), + modifiedSettings() { m_ui->setupUi(this); connect(m_ui->list, &QListWidget::currentItemChanged, this, &Settings::onCurrentPageChanged); + + connect(m_ui->General, &PageGeneral::variableModified, this, &Settings::onVariableModified); + connect(m_ui->Appearance, &PageAppearance::variableModified, this, &Settings::onVariableModified); + + connect(m_ui->applyButton, &QPushButton::clicked, this, &Settings::apply); + connect(m_ui->okButton, &QPushButton::clicked, this, &Settings::confirm); + connect(m_ui->cancelButton, &QPushButton::clicked, this, &Settings::close); } Settings::~Settings() @@ -22,3 +30,33 @@ void Settings::onCurrentPageChanged(QListWidgetItem* current) m_ui->content->setCurrentIndex(m_ui->list->currentRow()); } } + +void Settings::onVariableModified(const QString& key, const QVariant& value) +{ + modifiedSettings[key] = value; +} + +void Settings::apply() +{ + QSettings settings; + for (const std::pair& pair: modifiedSettings) { + if (pair.first == "theme") { + QString theme = pair.second.toString(); + if (theme.toLower() == "system") { + QApplication::setStyle(Shared::Global::getInstance()->defaultSystemStyle); + } else { + QApplication::setStyle(theme); + } + } + + settings.setValue(pair.first, pair.second); + } + + modifiedSettings.clear(); +} + +void Settings::confirm() +{ + apply(); + close(); +} diff --git a/ui/widgets/settings/settings.h b/ui/widgets/settings/settings.h index f961e08..5031139 100644 --- a/ui/widgets/settings/settings.h +++ b/ui/widgets/settings/settings.h @@ -4,6 +4,9 @@ #include #include #include +#include + +#include "shared/global.h" namespace Ui { @@ -20,11 +23,17 @@ public: Settings(QWidget* parent = nullptr); ~Settings(); +public slots: + void apply(); + void confirm(); + protected slots: void onCurrentPageChanged(QListWidgetItem* current); + void onVariableModified(const QString& key, const QVariant& value); private: QScopedPointer m_ui; + std::map modifiedSettings; }; #endif // SETTINGS_H From 802e2f11a1a990dc720490ce098c864ee21edf61 Mon Sep 17 00:00:00 2001 From: blue Date: Tue, 25 Jan 2022 23:35:55 +0300 Subject: [PATCH 53/93] may be a bit better quit handling --- core/main.cpp | 21 ++++++++++++--------- core/signalcatcher.cpp | 2 +- core/signalcatcher.h | 3 +++ ui/squawk.cpp | 4 ++++ ui/squawk.h | 3 +-- ui/widgets/settings/pageappearance.cpp | 15 +++++++++++++++ ui/widgets/settings/pageappearance.ui | 10 ++++++++++ 7 files changed, 46 insertions(+), 12 deletions(-) diff --git a/core/main.cpp b/core/main.cpp index 03827d3..c7bb820 100644 --- a/core/main.cpp +++ b/core/main.cpp @@ -105,11 +105,15 @@ int main(int argc, char *argv[]) QThread* coreThread = new QThread(); squawk->moveToThread(coreThread); + QObject::connect(&sc, &SignalCatcher::interrupt, &app, &QApplication::closeAllWindows); + QObject::connect(coreThread, &QThread::started, squawk, &Core::Squawk::start); - QObject::connect(&app, &QApplication::aboutToQuit, squawk, &Core::Squawk::stop); - QObject::connect(&app, &QApplication::aboutToQuit, &w, &QMainWindow::close); - QObject::connect(squawk, &Core::Squawk::quit, coreThread, &QThread::quit); - QObject::connect(coreThread, &QThread::finished, squawk, &Core::Squawk::deleteLater); + QObject::connect(&app, &QApplication::lastWindowClosed, squawk, &Core::Squawk::stop); + QObject::connect(&app, &QApplication::lastWindowClosed, &w, &Squawk::writeSettings); + //QObject::connect(&app, &QApplication::aboutToQuit, &w, &QMainWindow::close); + QObject::connect(squawk, &Core::Squawk::quit, squawk, &Core::Squawk::deleteLater); + QObject::connect(squawk, &Core::Squawk::quit, coreThread, &QThread::quit, Qt::QueuedConnection); + QObject::connect(coreThread, &QThread::finished, &app, &QApplication::quit, Qt::QueuedConnection); QObject::connect(&w, &Squawk::newAccountRequest, squawk, &Core::Squawk::newAccountRequest); QObject::connect(&w, &Squawk::modifyAccountRequest, squawk, &Core::Squawk::modifyAccountRequest); @@ -169,12 +173,11 @@ int main(int argc, char *argv[]) QObject::connect(squawk, &Core::Squawk::ready, &w, &Squawk::readSettings); coreThread->start(); - int result = app.exec(); - - w.writeSettings(); - coreThread->wait(500); //TODO hate doing that but settings for some reason don't get saved to the disk - + + if (coreThread->isRunning()) { + coreThread->wait(); + } return result; } diff --git a/core/signalcatcher.cpp b/core/signalcatcher.cpp index dcdcb88..046c67e 100644 --- a/core/signalcatcher.cpp +++ b/core/signalcatcher.cpp @@ -50,7 +50,7 @@ void SignalCatcher::handleSigInt() char tmp; ssize_t s = ::read(sigintFd[1], &tmp, sizeof(tmp)); - app->quit(); + emit interrupt(); snInt->setEnabled(true); } diff --git a/core/signalcatcher.h b/core/signalcatcher.h index 7d75260..0c8e757 100644 --- a/core/signalcatcher.h +++ b/core/signalcatcher.h @@ -33,6 +33,9 @@ public: static void intSignalHandler(int unused); +signals: + void interrupt(); + public slots: void handleSigInt(); diff --git a/ui/squawk.cpp b/ui/squawk.cpp index 4d22b34..7acda3d 100644 --- a/ui/squawk.cpp +++ b/ui/squawk.cpp @@ -883,6 +883,10 @@ void Squawk::writeSettings() } settings.endGroup(); settings.endGroup(); + + settings.sync(); + + qDebug() << "Saved settings"; } void Squawk::onItemCollepsed(const QModelIndex& index) diff --git a/ui/squawk.h b/ui/squawk.h index 26dc0c9..b419057 100644 --- a/ui/squawk.h +++ b/ui/squawk.h @@ -54,8 +54,6 @@ public: explicit Squawk(QWidget *parent = nullptr); ~Squawk() override; - void writeSettings(); - signals: void newAccountRequest(const QMap&); void modifyAccountRequest(const QString&, const QMap&); @@ -84,6 +82,7 @@ signals: void localPathInvalid(const QString& path); public slots: + void writeSettings(); void readSettings(); void newAccount(const QMap& account); void changeAccount(const QString& account, const QMap& data); diff --git a/ui/widgets/settings/pageappearance.cpp b/ui/widgets/settings/pageappearance.cpp index 2fb8dc8..1c558f2 100644 --- a/ui/widgets/settings/pageappearance.cpp +++ b/ui/widgets/settings/pageappearance.cpp @@ -2,6 +2,10 @@ #include "ui_pageappearance.h" #include +#include +#include + +static const QStringList filters = {"*.colors"}; PageAppearance::PageAppearance(QWidget* parent): QWidget(parent), @@ -28,6 +32,17 @@ PageAppearance::PageAppearance(QWidget* parent): } connect(m_ui->themeInput, qOverload(&QComboBox::currentIndexChanged), this, &PageAppearance::onThemeChanged); + + m_ui->colorInput->addItem(tr("System")); + const QStringList dirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, "color-schemes", QStandardPaths::LocateDirectory); + QStringList schemeFiles; + for (const QString &dir : dirs) { + const QStringList fileNames = QDir(dir).entryList(filters); + for (const QString &file : fileNames) { + m_ui->colorInput->addItem(dir + QDir::separator() + file); + } + } + } PageAppearance::~PageAppearance() diff --git a/ui/widgets/settings/pageappearance.ui b/ui/widgets/settings/pageappearance.ui index 1199347..afcb3a8 100644 --- a/ui/widgets/settings/pageappearance.ui +++ b/ui/widgets/settings/pageappearance.ui @@ -21,6 +21,16 @@ + + + + + + + Color scheme + + + From 0ff9f12157a8223de3d324ed1e4e82c5166df624 Mon Sep 17 00:00:00 2001 From: blue Date: Wed, 26 Jan 2022 23:53:44 +0300 Subject: [PATCH 54/93] new optional KDE Frameworks plugin to support system color schemes --- CMakeLists.txt | 19 +++++++++++++ core/main.cpp | 5 +++- plugins/CMakeLists.txt | 6 ++++ plugins/colorschemetools.cpp | 39 ++++++++++++++++++++++++++ shared/global.cpp | 37 +++++++++++++++++++++++- shared/global.h | 11 ++++++++ ui/widgets/settings/pageappearance.cpp | 21 ++++++++------ ui/widgets/settings/pageappearance.h | 2 ++ 8 files changed, 130 insertions(+), 10 deletions(-) create mode 100644 plugins/colorschemetools.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index cd9d793..717cf91 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,6 +29,7 @@ target_include_directories(squawk PRIVATE ${CMAKE_SOURCE_DIR}) option(SYSTEM_QXMPP "Use system qxmpp lib" ON) option(WITH_KWALLET "Build KWallet support module" ON) option(WITH_KIO "Build KIO support module" ON) +option(WITH_KCONFIG "Build KConfig support module" ON) # Dependencies ## Qt @@ -90,6 +91,24 @@ if (WITH_KWALLET) endif () endif () +if (WITH_KCONFIG) + find_package(KF5Config CONFIG) + if (NOT KF5Config_FOUND) + set(WITH_KCONFIG OFF) + message("KConfig package wasn't found, KConfig support modules wouldn't be built") + else() + find_package(KF5ConfigWidgets CONFIG) + if (NOT KF5ConfigWidgets_FOUND) + set(WITH_KCONFIG OFF) + message("KConfigWidgets package wasn't found, KConfigWidgets support modules wouldn't be built") + else() + target_compile_definitions(squawk PRIVATE WITH_KCONFIG) + message("Building with support of KConfig") + message("Building with support of KConfigWidgets") + endif() + endif() +endif() + ## Signal (TODO) # find_package(Signal REQUIRED) diff --git a/core/main.cpp b/core/main.cpp index c7bb820..03de307 100644 --- a/core/main.cpp +++ b/core/main.cpp @@ -176,7 +176,10 @@ int main(int argc, char *argv[]) int result = app.exec(); if (coreThread->isRunning()) { - coreThread->wait(); + //coreThread->wait(); + //todo if I uncomment that, the app will no quit if it has reconnected at least once + //it feels like a symptom of something badly desinged in the core coreThread + //need to investigate; } return result; diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 84fc09b..5a5a41d 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -2,3 +2,9 @@ if (WITH_KIO) add_library(openFileManagerWindowJob SHARED openfilemanagerwindowjob.cpp) target_link_libraries(openFileManagerWindowJob PRIVATE KF5::KIOWidgets) endif () + +if (WITH_KCONFIG) + add_library(colorSchemeTools SHARED colorschemetools.cpp) + target_link_libraries(colorSchemeTools PRIVATE KF5::ConfigCore) + target_link_libraries(colorSchemeTools PRIVATE KF5::ConfigWidgets) +endif() diff --git a/plugins/colorschemetools.cpp b/plugins/colorschemetools.cpp new file mode 100644 index 0000000..0ad8e24 --- /dev/null +++ b/plugins/colorschemetools.cpp @@ -0,0 +1,39 @@ +#include +#include + +#include +#include + +QPixmap createPixmap(int size, const QBrush& window, const QBrush& button, const QBrush& view, const QBrush& selection); + +extern "C" QIcon* createPreview(const QString& path) { + KSharedConfigPtr schemeConfig = KSharedConfig::openConfig(path); + QIcon* result = new QIcon(); + KColorScheme activeWindow(QPalette::Active, KColorScheme::Window, schemeConfig); + KColorScheme activeButton(QPalette::Active, KColorScheme::Button, schemeConfig); + KColorScheme activeView(QPalette::Active, KColorScheme::View, schemeConfig); + KColorScheme activeSelection(QPalette::Active, KColorScheme::Selection, schemeConfig); + + result->addPixmap(createPixmap(16, activeWindow.background(), activeButton.background(), activeView.background(), activeSelection.background())); + result->addPixmap(createPixmap(24, activeWindow.background(), activeButton.background(), activeView.background(), activeSelection.background())); + + return result; +} + +extern "C" void deletePreview(QIcon* icon) { + delete icon; +} + +QPixmap createPixmap(int size, const QBrush& window, const QBrush& button, const QBrush& view, const QBrush& selection) { + QPixmap pix(size, size); + pix.fill(Qt::black); + QPainter p; + p.begin(&pix); + const int itemSize = size / 2 - 1; + p.fillRect(1, 1, itemSize, itemSize, window); + p.fillRect(1 + itemSize, 1, itemSize, itemSize, button); + p.fillRect(1, 1 + itemSize, itemSize, itemSize, view); + p.fillRect(1 + itemSize, 1 + itemSize, itemSize, itemSize, selection); + p.end(); + return pix; +} diff --git a/shared/global.cpp b/shared/global.cpp index 7ee9599..7dafed6 100644 --- a/shared/global.cpp +++ b/shared/global.cpp @@ -28,6 +28,12 @@ QLibrary Shared::Global::openFileManagerWindowJob("openFileManagerWindowJob"); Shared::Global::HighlightInFileManager Shared::Global::hfm = 0; #endif +#ifdef WITH_KCONFIG +QLibrary Shared::Global::colorSchemeTools("colorSchemeTools"); +Shared::Global::CreatePreview Shared::Global::createPreview = 0; +Shared::Global::DeletePreview Shared::Global::deletePreview = 0; +#endif + Shared::Global::Global(): availability({ tr("Online", "Availability"), @@ -87,7 +93,8 @@ Shared::Global::Global(): defaultSystemStyle(QApplication::style()->objectName()), pluginSupport({ {"KWallet", false}, - {"openFileManagerWindowJob", false} + {"openFileManagerWindowJob", false}, + {"colorSchemeTools", false} }), fileCache() { @@ -111,6 +118,22 @@ Shared::Global::Global(): qDebug() << "KIO::OpenFileManagerWindow support disabled: couldn't load the library" << openFileManagerWindowJob.errorString(); } #endif + +#ifdef WITH_KCONFIG + colorSchemeTools.load(); + if (colorSchemeTools.isLoaded()) { + createPreview = (CreatePreview) colorSchemeTools.resolve("createPreview"); + deletePreview = (DeletePreview) colorSchemeTools.resolve("deletePreview"); + if (createPreview && deletePreview) { + setSupported("colorSchemeTools", true); + qDebug() << "Color Schemes support enabled"; + } else { + qDebug() << "Color Schemes support disabled: couldn't resolve required methods in the library"; + } + } else { + qDebug() << "Color Schemes support disabled: couldn't load the library" << colorSchemeTools.errorString(); + } +#endif } @@ -276,6 +299,18 @@ void Shared::Global::highlightInFileManager(const QString& path) } } +QIcon Shared::Global::createThemePreview(const QString& path) +{ + if (supported("colorSchemeTools")) { + QIcon* icon = createPreview(path); + QIcon localIcon = *icon; + deletePreview(icon); + return localIcon; + } else { + return QIcon(); + } +} + #define FROM_INT_INPL(Enum) \ template<> \ diff --git a/shared/global.h b/shared/global.h index a87b9bc..bd3c9a6 100644 --- a/shared/global.h +++ b/shared/global.h @@ -95,6 +95,7 @@ namespace Shared { static FileInfo getFileInfo(const QString& path); static void highlightInFileManager(const QString& path); + static QIcon createThemePreview(const QString& path); template static T fromInt(int src); @@ -128,6 +129,16 @@ namespace Shared { static HighlightInFileManager hfm; #endif + +#ifdef WITH_KCONFIG + static QLibrary colorSchemeTools; + + typedef QIcon* (*CreatePreview)(const QString&); + typedef void (*DeletePreview)(QIcon*); + + static CreatePreview createPreview; + static DeletePreview deletePreview; +#endif }; } diff --git a/ui/widgets/settings/pageappearance.cpp b/ui/widgets/settings/pageappearance.cpp index 1c558f2..57da7aa 100644 --- a/ui/widgets/settings/pageappearance.cpp +++ b/ui/widgets/settings/pageappearance.cpp @@ -33,16 +33,19 @@ PageAppearance::PageAppearance(QWidget* parent): connect(m_ui->themeInput, qOverload(&QComboBox::currentIndexChanged), this, &PageAppearance::onThemeChanged); - m_ui->colorInput->addItem(tr("System")); - const QStringList dirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, "color-schemes", QStandardPaths::LocateDirectory); - QStringList schemeFiles; - for (const QString &dir : dirs) { - const QStringList fileNames = QDir(dir).entryList(filters); - for (const QString &file : fileNames) { - m_ui->colorInput->addItem(dir + QDir::separator() + file); + if (Shared::Global::supported("colorSchemeTools")) { + m_ui->colorInput->addItem(tr("System")); + const QStringList dirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, "color-schemes", QStandardPaths::LocateDirectory); + QStringList schemeFiles; + for (const QString &dir : dirs) { + const QStringList fileNames = QDir(dir).entryList(filters); + for (const QString &file : fileNames) { + m_ui->colorInput->addItem(Shared::Global::createThemePreview(dir + QDir::separator() + file), file); + } } + } else { + m_ui->colorInput->setEnabled(false); } - } PageAppearance::~PageAppearance() @@ -55,3 +58,5 @@ void PageAppearance::onThemeChanged(int index) emit variableModified("theme", styles[index]); } } + + diff --git a/ui/widgets/settings/pageappearance.h b/ui/widgets/settings/pageappearance.h index 9cb1830..b31f3c1 100644 --- a/ui/widgets/settings/pageappearance.h +++ b/ui/widgets/settings/pageappearance.h @@ -8,6 +8,8 @@ #include #include +#include "shared/global.h" + namespace Ui { class PageAppearance; From da19eb86bbb9fa6e52189e44524bf8d16f73b84b Mon Sep 17 00:00:00 2001 From: blue Date: Thu, 27 Jan 2022 20:44:32 +0300 Subject: [PATCH 55/93] color theme setting is now working --- CHANGELOG.md | 5 ++ README.md | 3 ++ core/main.cpp | 21 ++++++-- .../wrappers/CMakeLists.txt | 2 + packaging/Archlinux/PKGBUILD | 5 +- plugins/CMakeLists.txt | 4 ++ plugins/colorschemetools.cpp | 13 +++++ shared/global.cpp | 39 +++++++++++++- shared/global.h | 8 +++ ui/widgets/settings/pageappearance.cpp | 54 +++++++++++++------ ui/widgets/settings/pageappearance.h | 2 + ui/widgets/settings/pageappearance.ui | 8 +-- ui/widgets/settings/settings.cpp | 11 ++-- 13 files changed, 142 insertions(+), 33 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4052647..7b1d75a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,10 +2,15 @@ ## Squawk 0.2.1 (UNRELEASED) ### Bug fixes +- build in release mode now no longer spams warnings +- build now correctly installs all build plugin libs ### Improvements ### New features +- the settings are here! You con config different stuff from there +- now it's possible to set up different qt styles from settings +- if you have KConfig nad KConfigWidgets packages installed - you can chose from global color schemes ## Squawk 0.2.0 (Jan 10, 2022) ### Bug fixes diff --git a/README.md b/README.md index 0af201f..486d4fe 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,8 @@ - qxmpp 1.1.0 or higher - KDE Frameworks: kwallet (optional) - KDE Frameworks: KIO (optional) +- KDE Frameworks: KConfig (optional) +- KDE Frameworks: KConfigWidgets (optional) - Boost (just one little hpp from there) - Imagemagick (for compilation, to rasterize an SVG logo) @@ -92,6 +94,7 @@ Here is the list of keys you can pass to configuration phase of `cmake ..`. - `SYSTEM_QXMPP` - `True` tries to link against `qxmpp` installed in the system, `False` builds bundled `qxmpp` library (default is `True`) - `WITH_KWALLET` - `True` builds the `KWallet` capability module if `KWallet` is installed and if not goes to `False`. `False` disables `KWallet` support (default is `True`) - `WITH_KIO` - `True` builds the `KIO` capability module if `KIO` is installed and if not goes to `False`. `False` disables `KIO` support (default is `True`) +- `WITH_KCONFIG` - `True` builds the `KConfig` and `KConfigWidgets` capability module if such packages are installed and if not goes to `False`. `False` disables `KConfig` and `KConfigWidgets` support (default is `True`) ## License diff --git a/core/main.cpp b/core/main.cpp index 03de307..cdfca7e 100644 --- a/core/main.cpp +++ b/core/main.cpp @@ -90,13 +90,24 @@ int main(int argc, char *argv[]) new Shared::Global(); //translates enums QSettings settings; - QVariant vtheme = settings.value("theme"); - if (vtheme.isValid()) { - QString theme = vtheme.toString().toLower(); - if (theme != "system") { - QApplication::setStyle(theme); + QVariant vs = settings.value("style"); + if (vs.isValid()) { + QString style = vs.toString().toLower(); + if (style != "system") { + Shared::Global::setStyle(style); } } + if (Shared::Global::supported("colorSchemeTools")) { + QVariant vt = settings.value("theme"); + if (vt.isValid()) { + QString theme = vt.toString(); + if (theme.toLower() != "system") { + Shared::Global::setTheme(theme); + } + } + } + + Squawk w; w.show(); diff --git a/core/passwordStorageEngines/wrappers/CMakeLists.txt b/core/passwordStorageEngines/wrappers/CMakeLists.txt index 6d486c0..e8420da 100644 --- a/core/passwordStorageEngines/wrappers/CMakeLists.txt +++ b/core/passwordStorageEngines/wrappers/CMakeLists.txt @@ -1,2 +1,4 @@ add_library(kwalletWrapper SHARED kwallet.cpp) target_link_libraries(kwalletWrapper PRIVATE KF5::Wallet) + +install(TARGETS kwalletWrapper LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}) diff --git a/packaging/Archlinux/PKGBUILD b/packaging/Archlinux/PKGBUILD index 0d2aaaa..a8da388 100644 --- a/packaging/Archlinux/PKGBUILD +++ b/packaging/Archlinux/PKGBUILD @@ -8,7 +8,10 @@ url="https://git.macaw.me/blue/squawk" license=('GPL3') depends=('hicolor-icon-theme' 'desktop-file-utils' 'lmdb' 'qxmpp>=1.1.0') makedepends=('cmake>=3.3' 'imagemagick' 'qt5-tools' 'boost') -optdepends=('kwallet: secure password storage (requires rebuild)' 'kio: better show in folder action (requires rebuild)') +optdepends=('kwallet: secure password storage (requires rebuild)' + 'kconfig: system themes support (requires rebuild)' + 'kconfigwidgets: system themes support (requires rebuild)' + 'kio: better show in folder action (requires rebuild)') source=("$pkgname-$pkgver.tar.gz") sha256sums=('8e93d3dbe1fc35cfecb7783af409c6a264244d11609b2241d4fe77d43d068419') diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 5a5a41d..388c258 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -1,10 +1,14 @@ if (WITH_KIO) add_library(openFileManagerWindowJob SHARED openfilemanagerwindowjob.cpp) target_link_libraries(openFileManagerWindowJob PRIVATE KF5::KIOWidgets) + + install(TARGETS openFileManagerWindowJob LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}) endif () if (WITH_KCONFIG) add_library(colorSchemeTools SHARED colorschemetools.cpp) target_link_libraries(colorSchemeTools PRIVATE KF5::ConfigCore) target_link_libraries(colorSchemeTools PRIVATE KF5::ConfigWidgets) + + install(TARGETS colorSchemeTools LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}) endif() diff --git a/plugins/colorschemetools.cpp b/plugins/colorschemetools.cpp index 0ad8e24..0288b28 100644 --- a/plugins/colorschemetools.cpp +++ b/plugins/colorschemetools.cpp @@ -1,7 +1,9 @@ #include #include +#include #include +#include #include QPixmap createPixmap(int size, const QBrush& window, const QBrush& button, const QBrush& view, const QBrush& selection); @@ -24,6 +26,17 @@ extern "C" void deletePreview(QIcon* icon) { delete icon; } +extern "C" void colorSchemeName(const QString& path, QString& answer) { + KSharedConfigPtr config = KSharedConfig::openConfig(path); + KConfigGroup group(config, QStringLiteral("General")); + answer = group.readEntry("Name", QFileInfo(path).baseName()); +} + +extern "C" void createPalette(const QString& path, QPalette& answer) { + KSharedConfigPtr config = KSharedConfig::openConfig(path); + answer = KColorScheme::createApplicationPalette(config); +} + QPixmap createPixmap(int size, const QBrush& window, const QBrush& button, const QBrush& view, const QBrush& selection) { QPixmap pix(size, size); pix.fill(Qt::black); diff --git a/shared/global.cpp b/shared/global.cpp index 7dafed6..122bc79 100644 --- a/shared/global.cpp +++ b/shared/global.cpp @@ -32,6 +32,8 @@ Shared::Global::HighlightInFileManager Shared::Global::hfm = 0; QLibrary Shared::Global::colorSchemeTools("colorSchemeTools"); Shared::Global::CreatePreview Shared::Global::createPreview = 0; Shared::Global::DeletePreview Shared::Global::deletePreview = 0; +Shared::Global::ColorSchemeName Shared::Global::colorSchemeName = 0; +Shared::Global::CreatePalette Shared::Global::createPalette = 0; #endif Shared::Global::Global(): @@ -91,6 +93,7 @@ Shared::Global::Global(): tr("Your password is going to be stored in KDE wallet storage (KWallet). You're going to be queried for permissions", "AccountPasswordDescription") }), defaultSystemStyle(QApplication::style()->objectName()), + defaultSystemPalette(QApplication::palette()), pluginSupport({ {"KWallet", false}, {"openFileManagerWindowJob", false}, @@ -124,7 +127,9 @@ Shared::Global::Global(): if (colorSchemeTools.isLoaded()) { createPreview = (CreatePreview) colorSchemeTools.resolve("createPreview"); deletePreview = (DeletePreview) colorSchemeTools.resolve("deletePreview"); - if (createPreview && deletePreview) { + colorSchemeName = (ColorSchemeName) colorSchemeTools.resolve("colorSchemeName"); + createPalette = (CreatePalette) colorSchemeTools.resolve("createPalette"); + if (createPreview && deletePreview && colorSchemeName && createPalette) { setSupported("colorSchemeTools", true); qDebug() << "Color Schemes support enabled"; } else { @@ -311,6 +316,38 @@ QIcon Shared::Global::createThemePreview(const QString& path) } } +QString Shared::Global::getColorSchemeName(const QString& path) +{ + if (supported("colorSchemeTools")) { + QString res; + colorSchemeName(path, res); + return res; + } else { + return ""; + } +} + +void Shared::Global::setTheme(const QString& path) +{ + if (supported("colorSchemeTools")) { + if (path.toLower() == "system") { + QApplication::setPalette(getInstance()->defaultSystemPalette); + } else { + QPalette pallete; + createPalette(path, pallete); + QApplication::setPalette(pallete); + } + } +} + +void Shared::Global::setStyle(const QString& style) +{ + if (style.toLower() == "system") { + QApplication::setStyle(getInstance()->defaultSystemStyle); + } else { + QApplication::setStyle(style); + } +} #define FROM_INT_INPL(Enum) \ template<> \ diff --git a/shared/global.h b/shared/global.h index bd3c9a6..2056639 100644 --- a/shared/global.h +++ b/shared/global.h @@ -87,6 +87,7 @@ namespace Shared { const std::deque accountPasswordDescription; const QString defaultSystemStyle; + const QPalette defaultSystemPalette; static bool supported(const QString& pluginName); static void setSupported(const QString& pluginName, bool support); @@ -96,6 +97,9 @@ namespace Shared { static FileInfo getFileInfo(const QString& path); static void highlightInFileManager(const QString& path); static QIcon createThemePreview(const QString& path); + static QString getColorSchemeName(const QString& path); + static void setTheme(const QString& path); + static void setStyle(const QString& style); template static T fromInt(int src); @@ -135,9 +139,13 @@ namespace Shared { typedef QIcon* (*CreatePreview)(const QString&); typedef void (*DeletePreview)(QIcon*); + typedef void (*ColorSchemeName)(const QString&, QString&); + typedef void (*CreatePalette)(const QString&, QPalette&); static CreatePreview createPreview; static DeletePreview deletePreview; + static ColorSchemeName colorSchemeName; + static CreatePalette createPalette; #endif }; } diff --git a/ui/widgets/settings/pageappearance.cpp b/ui/widgets/settings/pageappearance.cpp index 57da7aa..f2bf53e 100644 --- a/ui/widgets/settings/pageappearance.cpp +++ b/ui/widgets/settings/pageappearance.cpp @@ -10,41 +10,59 @@ static const QStringList filters = {"*.colors"}; PageAppearance::PageAppearance(QWidget* parent): QWidget(parent), m_ui(new Ui::PageAppearance()), - styles() + styles(), + themes() { m_ui->setupUi(this); - m_ui->themeInput->addItem(tr("System")); + m_ui->styleInput->addItem(tr("System")); styles.push_back("system"); - QStringList themes = QStyleFactory::keys(); - for (const QString& key : themes) { - m_ui->themeInput->addItem(key); + QStringList systemStyles = QStyleFactory::keys(); + for (const QString& key : systemStyles) { + m_ui->styleInput->addItem(key); styles.push_back(key); } QSettings settings; - QVariant vtheme = settings.value("theme"); - if (vtheme.isValid()) { - QString theme = vtheme.toString(); - m_ui->themeInput->setCurrentText(theme); + QVariant vs = settings.value("style"); + if (vs.isValid()) { + m_ui->styleInput->setCurrentText(vs.toString()); } else { - m_ui->themeInput->setCurrentText("System"); + m_ui->styleInput->setCurrentText(tr("System")); } - connect(m_ui->themeInput, qOverload(&QComboBox::currentIndexChanged), this, &PageAppearance::onThemeChanged); + connect(m_ui->styleInput, qOverload(&QComboBox::currentIndexChanged), this, &PageAppearance::onStyleChanged); if (Shared::Global::supported("colorSchemeTools")) { - m_ui->colorInput->addItem(tr("System")); + themes.push_back("system"); + m_ui->themeInput->addItem(tr("System")); const QStringList dirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, "color-schemes", QStandardPaths::LocateDirectory); QStringList schemeFiles; + QString currentTheme(tr("System")); + QString requestedTheme(""); + QVariant vtheme = settings.value("theme"); + if (vtheme.isValid()) { + requestedTheme = vtheme.toString(); + } for (const QString &dir : dirs) { const QStringList fileNames = QDir(dir).entryList(filters); for (const QString &file : fileNames) { - m_ui->colorInput->addItem(Shared::Global::createThemePreview(dir + QDir::separator() + file), file); + QString path = dir + QDir::separator() + file; + themes.push_back(path); + QString themeName = Shared::Global::getColorSchemeName(path); + m_ui->themeInput->addItem(Shared::Global::createThemePreview(path), themeName); + + if (path == requestedTheme) { + currentTheme = themeName; + } } } + + m_ui->themeInput->setCurrentText(currentTheme); + + connect(m_ui->themeInput, qOverload(&QComboBox::currentIndexChanged), this, &PageAppearance::onThemeChanged); } else { - m_ui->colorInput->setEnabled(false); + m_ui->themeInput->setEnabled(false); } } @@ -55,8 +73,14 @@ PageAppearance::~PageAppearance() void PageAppearance::onThemeChanged(int index) { if (index >= 0) { - emit variableModified("theme", styles[index]); + emit variableModified("theme", themes[index]); } } +void PageAppearance::onStyleChanged(int index) +{ + if (index >= 0) { + emit variableModified("style", styles[index]); + } +} diff --git a/ui/widgets/settings/pageappearance.h b/ui/widgets/settings/pageappearance.h index b31f3c1..80efd85 100644 --- a/ui/widgets/settings/pageappearance.h +++ b/ui/widgets/settings/pageappearance.h @@ -29,11 +29,13 @@ signals: void variableModified(const QString& key, const QVariant& value); protected slots: + void onStyleChanged(int index); void onThemeChanged(int index); private: QScopedPointer m_ui; std::vector styles; + std::vector themes; }; #endif // PAGEAPPEARANCE_H diff --git a/ui/widgets/settings/pageappearance.ui b/ui/widgets/settings/pageappearance.ui index afcb3a8..570eefa 100644 --- a/ui/widgets/settings/pageappearance.ui +++ b/ui/widgets/settings/pageappearance.ui @@ -12,20 +12,20 @@ - + Theme - + - + - + Color scheme diff --git a/ui/widgets/settings/settings.cpp b/ui/widgets/settings/settings.cpp index 75a7780..3ab38bb 100644 --- a/ui/widgets/settings/settings.cpp +++ b/ui/widgets/settings/settings.cpp @@ -40,13 +40,10 @@ void Settings::apply() { QSettings settings; for (const std::pair& pair: modifiedSettings) { - if (pair.first == "theme") { - QString theme = pair.second.toString(); - if (theme.toLower() == "system") { - QApplication::setStyle(Shared::Global::getInstance()->defaultSystemStyle); - } else { - QApplication::setStyle(theme); - } + if (pair.first == "style") { + Shared::Global::setStyle(pair.second.toString()); + } else if (pair.first == "theme") { + Shared::Global::setTheme(pair.second.toString()); } settings.setValue(pair.first, pair.second); From 243edff8bdf13fa04a00c35a05a3bb6a374cbfc3 Mon Sep 17 00:00:00 2001 From: blue Date: Thu, 17 Feb 2022 20:26:15 +0300 Subject: [PATCH 56/93] first thoughts about downloads path changing --- core/CMakeLists.txt | 1 + core/main.cpp | 11 ++++++- core/networkaccess.cpp | 13 ++++++-- core/networkaccess.h | 3 ++ core/utils/CMakeLists.txt | 4 +++ core/utils/pathcheck.cpp | 47 +++++++++++++++++++++++++++++ core/utils/pathcheck.h | 21 +++++++++++++ shared/utils.h | 2 -- ui/widgets/settings/pagegeneral.cpp | 3 ++ ui/widgets/settings/pagegeneral.h | 1 + ui/widgets/settings/pagegeneral.ui | 26 +++++++++++++--- 11 files changed, 122 insertions(+), 10 deletions(-) create mode 100644 core/utils/CMakeLists.txt create mode 100644 core/utils/pathcheck.cpp create mode 100644 core/utils/pathcheck.h diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index 9369cb7..1836349 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -32,3 +32,4 @@ target_include_directories(squawk PRIVATE ${LMDB_INCLUDE_DIRS}) add_subdirectory(handlers) add_subdirectory(passwordStorageEngines) +add_subdirectory(utils) diff --git a/core/main.cpp b/core/main.cpp index cdfca7e..570bad4 100644 --- a/core/main.cpp +++ b/core/main.cpp @@ -21,6 +21,8 @@ #include "../ui/squawk.h" #include "signalcatcher.h" #include "squawk.h" +#include "utils/pathcheck.h" + #include #include #include @@ -28,6 +30,7 @@ #include #include #include +#include int main(int argc, char *argv[]) { @@ -106,7 +109,13 @@ int main(int argc, char *argv[]) } } } - + QString path = Utils::downloadsPathCheck(); + if (path.size() > 0) { + settings.setValue("downloadsPath", path); + } else { + qDebug() << "couldn't initialize directory for downloads, quitting"; + return -1; + } Squawk w; diff --git a/core/networkaccess.cpp b/core/networkaccess.cpp index c2cd65d..48d26aa 100644 --- a/core/networkaccess.cpp +++ b/core/networkaccess.cpp @@ -28,8 +28,11 @@ Core::NetworkAccess::NetworkAccess(QObject* parent): manager(0), storage("fileURLStorage"), downloads(), - uploads() + uploads(), + currentPath() { + QSettings settings; + currentPath = settings.value("downloadsPath").toString(); } Core::NetworkAccess::~NetworkAccess() @@ -515,8 +518,7 @@ bool Core::NetworkAccess::checkAndAddToUploading(const QString& acc, const QStri QString Core::NetworkAccess::prepareDirectory(const QString& jid) { - QString path = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation); - path += "/" + QApplication::applicationName(); + QString path = currentPath; if (jid.size() > 0) { path += "/" + jid; } @@ -563,3 +565,8 @@ std::list Core::NetworkAccess::reportPathInvalid(const QStr { return storage.deletedFile(path); } + +void Core::NetworkAccess::moveFilesDirectory(const QString& newPath) +{ + +} diff --git a/core/networkaccess.h b/core/networkaccess.h index 89d0633..cf24fc4 100644 --- a/core/networkaccess.h +++ b/core/networkaccess.h @@ -26,6 +26,7 @@ #include #include #include +#include #include @@ -65,6 +66,7 @@ public slots: void downladFile(const QString& url); void registerFile(const QString& url, const QString& account, const QString& jid, const QString& id); void registerFile(const QString& url, const QString& path, const QString& account, const QString& jid, const QString& id); + void moveFilesDirectory(const QString& newPath); private: void startDownload(const std::list& msgs, const QString& url); @@ -87,6 +89,7 @@ private: UrlStorage storage; std::map downloads; std::map uploads; + QString currentPath; struct Transfer { std::list messages; diff --git a/core/utils/CMakeLists.txt b/core/utils/CMakeLists.txt new file mode 100644 index 0000000..6722da7 --- /dev/null +++ b/core/utils/CMakeLists.txt @@ -0,0 +1,4 @@ +target_sources(squawk PRIVATE + pathcheck.h + pathcheck.cpp +) diff --git a/core/utils/pathcheck.cpp b/core/utils/pathcheck.cpp new file mode 100644 index 0000000..3f1b86a --- /dev/null +++ b/core/utils/pathcheck.cpp @@ -0,0 +1,47 @@ +#include "pathcheck.h" + +QString Utils::downloadsPathCheck() +{ + QSettings settings; + QVariant dpv = settings.value("downloadsPath"); + QString path; + if (!dpv.isValid()) { + path = defaultDownloadsPath(); + qDebug() << "no downloadsPath variable in config, using default" << path; + path = getCanonicalWritablePath(path); + return path; + } else { + path = dpv.toString(); + path = getCanonicalWritablePath(path); + if (path.size() == 0) { + path = defaultDownloadsPath(); + qDebug() << "falling back to the default downloads path" << path; + path = getCanonicalWritablePath(path); + } + return path; + } +} + +QString Utils::defaultDownloadsPath() +{ + return QStandardPaths::writableLocation(QStandardPaths::DownloadLocation) + "/" + QApplication::applicationName(); +} + +QString Utils::getCanonicalWritablePath(const QString& path) +{ + QDir location(path); + if (!location.exists()) { + bool res = location.mkpath(location.canonicalPath()); + if (!res) { + qDebug() << "couldn't create directory" << path; + return ""; + } + } + QFileInfo info(location.canonicalPath()); + if (info.isWritable()) { + return location.canonicalPath(); + } else { + qDebug() << "directory" << path << "is not writable"; + return ""; + } +} diff --git a/core/utils/pathcheck.h b/core/utils/pathcheck.h new file mode 100644 index 0000000..8618012 --- /dev/null +++ b/core/utils/pathcheck.h @@ -0,0 +1,21 @@ +#ifndef PATHCHECK_H +#define PATHCHECK_H + +#include +#include +#include +#include +#include +#include +#include + +namespace Utils { + +QString downloadsPathCheck(); +QString defaultDownloadsPath(); + +QString getCanonicalWritablePath(const QString& path); + +} + +#endif // PATHCHECK_H diff --git a/shared/utils.h b/shared/utils.h index 6dcb141..564e2e6 100644 --- a/shared/utils.h +++ b/shared/utils.h @@ -24,8 +24,6 @@ #include #include -// #include "KIO/OpenFileManagerWindowJob" - #include namespace Shared { diff --git a/ui/widgets/settings/pagegeneral.cpp b/ui/widgets/settings/pagegeneral.cpp index e448f80..56cb610 100644 --- a/ui/widgets/settings/pagegeneral.cpp +++ b/ui/widgets/settings/pagegeneral.cpp @@ -6,6 +6,9 @@ PageGeneral::PageGeneral(QWidget* parent): m_ui(new Ui::PageGeneral()) { m_ui->setupUi(this); + + QSettings settings; + m_ui->downloadsPathInput->setText(settings.value("downloadsPath").toString()); } PageGeneral::~PageGeneral() diff --git a/ui/widgets/settings/pagegeneral.h b/ui/widgets/settings/pagegeneral.h index b8c30c5..cb89bfa 100644 --- a/ui/widgets/settings/pagegeneral.h +++ b/ui/widgets/settings/pagegeneral.h @@ -4,6 +4,7 @@ #include #include #include +#include namespace Ui { diff --git a/ui/widgets/settings/pagegeneral.ui b/ui/widgets/settings/pagegeneral.ui index 9921715..e010980 100644 --- a/ui/widgets/settings/pagegeneral.ui +++ b/ui/widgets/settings/pagegeneral.ui @@ -11,15 +11,33 @@ - - + + Downloads path - - + + + + 6 + + + + + false + + + + + + + PushButton + + + + From d8b5ccb2dadb1cb64dd4c4927ea99ecda7e0deeb Mon Sep 17 00:00:00 2001 From: blue Date: Sat, 19 Feb 2022 00:27:09 +0300 Subject: [PATCH 57/93] downloaded files now stored with squawk:// prefix, that way I can move downloads folder without messing up the database --- core/CMakeLists.txt | 1 - core/handlers/messagehandler.cpp | 13 ++++++- core/handlers/messagehandler.h | 2 +- core/main.cpp | 4 +- core/networkaccess.cpp | 45 ++++++++++++++-------- core/networkaccess.h | 1 + core/utils/CMakeLists.txt | 4 -- shared/CMakeLists.txt | 2 + {core/utils => shared}/pathcheck.cpp | 26 ++++++++----- {core/utils => shared}/pathcheck.h | 6 ++- ui/widgets/conversation.cpp | 2 +- ui/widgets/conversation.h | 1 + ui/widgets/messageline/messagedelegate.cpp | 11 +++--- ui/widgets/messageline/messagedelegate.h | 1 + 14 files changed, 75 insertions(+), 44 deletions(-) delete mode 100644 core/utils/CMakeLists.txt rename {core/utils => shared}/pathcheck.cpp (59%) rename {core/utils => shared}/pathcheck.h (66%) diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index 1836349..9369cb7 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -32,4 +32,3 @@ target_include_directories(squawk PRIVATE ${LMDB_INCLUDE_DIRS}) add_subdirectory(handlers) add_subdirectory(passwordStorageEngines) -add_subdirectory(utils) diff --git a/core/handlers/messagehandler.cpp b/core/handlers/messagehandler.cpp index eb840f8..fc05a67 100644 --- a/core/handlers/messagehandler.cpp +++ b/core/handlers/messagehandler.cpp @@ -298,6 +298,14 @@ void Core::MessageHandler::performSending(Shared::Message data, bool newMessage) data.setTime(sendTime); } changes.insert("stamp", sendTime); + + //sometimes (when the image is pasted with ctrl+v) + //I start sending message with one path, then copy it to downloads directory + //so, the final path changes. Let's assume it changes always since it costs me close to nothing + QString attachPath = data.getAttachPath(); + if (attachPath.size() > 0) { + changes.insert("attachPath", attachPath); + } if (ri != 0) { if (newMessage) { @@ -439,14 +447,15 @@ void Core::MessageHandler::handleUploadError(const QString& jid, const QString& }); } -void Core::MessageHandler::onUploadFileComplete(const std::list& msgs, const QString& path) +void Core::MessageHandler::onUploadFileComplete(const std::list& msgs, const QString& url, const QString& path) { for (const Shared::MessageInfo& info : msgs) { if (info.account == acc->getName()) { RosterItem* ri = acc->rh->getRosterItem(info.jid); if (ri != 0) { Shared::Message msg = ri->getMessage(info.messageId); - sendMessageWithLocalUploadedFile(msg, path, false); + msg.setAttachPath(path); + sendMessageWithLocalUploadedFile(msg, url, false); } else { qDebug() << "A signal received about complete upload to" << acc->name << "for pal" << info.jid << "but the object for this pal wasn't found, something went terrebly wrong, skipping send"; } diff --git a/core/handlers/messagehandler.h b/core/handlers/messagehandler.h index 4eb9265..b88c46a 100644 --- a/core/handlers/messagehandler.h +++ b/core/handlers/messagehandler.h @@ -57,7 +57,7 @@ public slots: void onUploadSlotReceived(const QXmppHttpUploadSlotIq& slot); void onUploadSlotRequestFailed(const QXmppHttpUploadRequestIq& request); void onDownloadFileComplete(const std::list& msgs, const QString& path); - void onUploadFileComplete(const std::list& msgs, const QString& path); + void onUploadFileComplete(const std::list& msgs, const QString& url, const QString& path); void onLoadFileError(const std::list& msgs, const QString& path, bool up); void requestChangeMessage(const QString& jid, const QString& messageId, const QMap& data); diff --git a/core/main.cpp b/core/main.cpp index 570bad4..9a10062 100644 --- a/core/main.cpp +++ b/core/main.cpp @@ -18,10 +18,10 @@ #include "../shared/global.h" #include "../shared/messageinfo.h" +#include "../shared/pathcheck.h" #include "../ui/squawk.h" #include "signalcatcher.h" #include "squawk.h" -#include "utils/pathcheck.h" #include #include @@ -109,7 +109,7 @@ int main(int argc, char *argv[]) } } } - QString path = Utils::downloadsPathCheck(); + QString path = Shared::downloadsPathCheck(); if (path.size() > 0) { settings.setValue("downloadsPath", path); } else { diff --git a/core/networkaccess.cpp b/core/networkaccess.cpp index 48d26aa..25fafc8 100644 --- a/core/networkaccess.cpp +++ b/core/networkaccess.cpp @@ -305,7 +305,7 @@ void Core::NetworkAccess::onDownloadFinished() if (path.size() > 0) { path = checkFileName(fileName, path); - QFile file(path); + QFile file(Shared::resolvePath(path)); if (file.open(QIODevice::WriteOnly)) { file.write(dwn->reply->readAll()); file.close(); @@ -379,23 +379,20 @@ void Core::NetworkAccess::onUploadFinished() Transfer* upl = itr->second; if (upl->success) { qDebug() << "upload success for" << url; - - storage.addFile(upl->messages, upl->url, upl->path); - emit uploadFileComplete(upl->messages, upl->url, upl->path); // Copy file to Download folder if it is a temp file. See Conversation::onImagePasted. - if (upl->path.startsWith(QDir::tempPath() + QStringLiteral("/squawk_img_attach_")) && upl->path.endsWith(".png")) { + if (upl->path.startsWith(QDir::tempPath() + QDir::separator() + QStringLiteral("squawk_img_attach_")) && upl->path.endsWith(".png")) { QString err = ""; QString downloadDirPath = prepareDirectory(upl->messages.front().jid); if (downloadDirPath.size() > 0) { - QString newPath = downloadDirPath + "/" + upl->path.mid(QDir::tempPath().length() + 1); + QString newPath = downloadDirPath + QDir::separator() + upl->path.mid(QDir::tempPath().length() + 1); // Copy {TEMPDIR}/squawk_img_attach_XXXXXX.png to Download folder - bool copyResult = QFile::copy(upl->path, newPath); + bool copyResult = QFile::copy(upl->path, Shared::resolvePath(newPath)); if (copyResult) { // Change storage - storage.setPath(upl->url, newPath); + upl->path = newPath; } else { err = "copying to " + newPath + " failed"; } @@ -407,6 +404,9 @@ void Core::NetworkAccess::onUploadFinished() qDebug() << "failed to copy temporary upload file " << upl->path << " to download folder:" << err; } } + + storage.addFile(upl->messages, upl->url, upl->path); + emit uploadFileComplete(upl->messages, upl->url, upl->path); } upl->reply->deleteLater(); @@ -519,9 +519,12 @@ bool Core::NetworkAccess::checkAndAddToUploading(const QString& acc, const QStri QString Core::NetworkAccess::prepareDirectory(const QString& jid) { QString path = currentPath; + QString addition; if (jid.size() > 0) { - path += "/" + jid; + addition = jid; + path += QDir::separator() + jid; } + QDir location(path); if (!location.exists()) { @@ -529,10 +532,10 @@ QString Core::NetworkAccess::prepareDirectory(const QString& jid) if (!res) { return ""; } else { - return path; + return "squawk://" + addition; } } - return path; + return "squawk://" + addition; } QString Core::NetworkAccess::checkFileName(const QString& name, const QString& path) @@ -546,14 +549,17 @@ QString Core::NetworkAccess::checkFileName(const QString& name, const QString& p suffix += "." + (*sItr); } QString postfix(""); - QFileInfo proposedName(path + "/" + realName + suffix); + QString resolvedPath = Shared::resolvePath(path); + QString count(""); + QFileInfo proposedName(resolvedPath + QDir::separator() + realName + count + suffix); + int counter = 0; while (proposedName.exists()) { - QString count = QString("(") + std::to_string(++counter).c_str() + ")"; - proposedName = QFileInfo(path + "/" + realName + count + suffix); + count = QString("(") + std::to_string(++counter).c_str() + ")"; + proposedName = QFileInfo(resolvedPath + QDir::separator() + realName + count + suffix); } - - return proposedName.absoluteFilePath(); + + return path + QDir::separator() + realName + count + suffix; } QString Core::NetworkAccess::addMessageAndCheckForPath(const QString& url, const QString& account, const QString& jid, const QString& id) @@ -568,5 +574,10 @@ std::list Core::NetworkAccess::reportPathInvalid(const QStr void Core::NetworkAccess::moveFilesDirectory(const QString& newPath) { - + QDir dir; + if (dir.rename(currentPath, newPath)) { + currentPath = newPath; + } else { + qDebug() << "couldn't move downloads directory, most probably downloads will be broken"; + } } diff --git a/core/networkaccess.h b/core/networkaccess.h index cf24fc4..0b7bb7d 100644 --- a/core/networkaccess.h +++ b/core/networkaccess.h @@ -31,6 +31,7 @@ #include #include "urlstorage.h" +#include "shared/pathcheck.h" namespace Core { diff --git a/core/utils/CMakeLists.txt b/core/utils/CMakeLists.txt deleted file mode 100644 index 6722da7..0000000 --- a/core/utils/CMakeLists.txt +++ /dev/null @@ -1,4 +0,0 @@ -target_sources(squawk PRIVATE - pathcheck.h - pathcheck.cpp -) diff --git a/shared/CMakeLists.txt b/shared/CMakeLists.txt index a36b516..0ab7dbd 100644 --- a/shared/CMakeLists.txt +++ b/shared/CMakeLists.txt @@ -16,4 +16,6 @@ target_sources(squawk PRIVATE utils.h vcard.cpp vcard.h + pathcheck.cpp + pathcheck.h ) diff --git a/core/utils/pathcheck.cpp b/shared/pathcheck.cpp similarity index 59% rename from core/utils/pathcheck.cpp rename to shared/pathcheck.cpp index 3f1b86a..0850742 100644 --- a/core/utils/pathcheck.cpp +++ b/shared/pathcheck.cpp @@ -1,6 +1,7 @@ #include "pathcheck.h" -QString Utils::downloadsPathCheck() +QRegularExpression squawk("^squawk:\\/\\/"); +QString Shared::downloadsPathCheck() { QSettings settings; QVariant dpv = settings.value("downloadsPath"); @@ -8,40 +9,47 @@ QString Utils::downloadsPathCheck() if (!dpv.isValid()) { path = defaultDownloadsPath(); qDebug() << "no downloadsPath variable in config, using default" << path; - path = getCanonicalWritablePath(path); + path = getAbsoluteWritablePath(path); return path; } else { path = dpv.toString(); - path = getCanonicalWritablePath(path); + path = getAbsoluteWritablePath(path); if (path.size() == 0) { path = defaultDownloadsPath(); qDebug() << "falling back to the default downloads path" << path; - path = getCanonicalWritablePath(path); + path = getAbsoluteWritablePath(path); } return path; } } -QString Utils::defaultDownloadsPath() +QString Shared::defaultDownloadsPath() { return QStandardPaths::writableLocation(QStandardPaths::DownloadLocation) + "/" + QApplication::applicationName(); } -QString Utils::getCanonicalWritablePath(const QString& path) +QString Shared::getAbsoluteWritablePath(const QString& path) { QDir location(path); if (!location.exists()) { - bool res = location.mkpath(location.canonicalPath()); + bool res = location.mkpath(location.absolutePath()); if (!res) { qDebug() << "couldn't create directory" << path; return ""; } } - QFileInfo info(location.canonicalPath()); + QFileInfo info(location.absolutePath()); if (info.isWritable()) { - return location.canonicalPath(); + return location.absolutePath(); } else { qDebug() << "directory" << path << "is not writable"; return ""; } } + +QString Shared::resolvePath(QString path) +{ + QSettings settings; + QVariant dpv = settings.value("downloadsPath"); + return path.replace(squawk, dpv.toString() + "/"); +} diff --git a/core/utils/pathcheck.h b/shared/pathcheck.h similarity index 66% rename from core/utils/pathcheck.h rename to shared/pathcheck.h index 8618012..6e7cd39 100644 --- a/core/utils/pathcheck.h +++ b/shared/pathcheck.h @@ -8,13 +8,15 @@ #include #include #include +#include -namespace Utils { +namespace Shared { QString downloadsPathCheck(); QString defaultDownloadsPath(); -QString getCanonicalWritablePath(const QString& path); +QString getAbsoluteWritablePath(const QString& path); +QString resolvePath(QString path); } diff --git a/ui/widgets/conversation.cpp b/ui/widgets/conversation.cpp index 69eac19..1c82024 100644 --- a/ui/widgets/conversation.cpp +++ b/ui/widgets/conversation.cpp @@ -483,7 +483,7 @@ void Conversation::onFeedContext(const QPoint& pos) }); } - QString path = item->getAttachPath(); + QString path = Shared::resolvePath(item->getAttachPath()); if (path.size() > 0) { showMenu = true; QAction* open = contextMenu->addAction(Shared::icon("document-preview"), tr("Open")); diff --git a/ui/widgets/conversation.h b/ui/widgets/conversation.h index a758b2c..76a8dd5 100644 --- a/ui/widgets/conversation.h +++ b/ui/widgets/conversation.h @@ -33,6 +33,7 @@ #include "shared/order.h" #include "shared/icons.h" #include "shared/utils.h" +#include "shared/pathcheck.h" #include "ui/models/account.h" #include "ui/models/roster.h" diff --git a/ui/widgets/messageline/messagedelegate.cpp b/ui/widgets/messageline/messagedelegate.cpp index d692752..22e8dcb 100644 --- a/ui/widgets/messageline/messagedelegate.cpp +++ b/ui/widgets/messageline/messagedelegate.cpp @@ -314,7 +314,7 @@ QSize MessageDelegate::sizeHint(const QStyleOptionViewItem& option, const QModel case Models::none: break; case Models::uploading: - messageSize.rheight() += Preview::calculateAttachSize(data.attach.localPath, messageRect).height() + textMargin; + messageSize.rheight() += Preview::calculateAttachSize(Shared::resolvePath(data.attach.localPath), messageRect).height() + textMargin; [[fallthrough]]; case Models::downloading: messageSize.rheight() += barHeight + textMargin; @@ -326,7 +326,7 @@ QSize MessageDelegate::sizeHint(const QStyleOptionViewItem& option, const QModel break; case Models::ready: case Models::local: { - QSize aSize = Preview::calculateAttachSize(data.attach.localPath, messageRect); + QSize aSize = Preview::calculateAttachSize(Shared::resolvePath(data.attach.localPath), messageRect); messageSize.rheight() += aSize.height() + textMargin; messageSize.setWidth(std::max(messageSize.width(), aSize.width())); } @@ -338,7 +338,7 @@ QSize MessageDelegate::sizeHint(const QStyleOptionViewItem& option, const QModel } break; case Models::errorUpload: { - QSize aSize = Preview::calculateAttachSize(data.attach.localPath, messageRect); + QSize aSize = Preview::calculateAttachSize(Shared::resolvePath(data.attach.localPath), messageRect); QSize commentSize = dateMetrics.boundingRect(messageRect, Qt::TextWordWrap, data.attach.error).size(); messageSize.rheight() += aSize.height() + commentSize.height() + textMargin * 2; messageSize.setWidth(std::max(messageSize.width(), std::max(commentSize.width(), aSize.width()))); @@ -455,12 +455,13 @@ int MessageDelegate::paintPreview(const Models::FeedItem& data, QPainter* painte std::map::iterator itr = previews->find(data.id); QSize size = option.rect.size(); + QString path = Shared::resolvePath(data.attach.localPath); if (itr != previews->end()) { preview = itr->second; - preview->actualize(data.attach.localPath, size, option.rect.topLeft()); + preview->actualize(path, size, option.rect.topLeft()); } else { QWidget* vp = static_cast(painter->device()); - preview = new Preview(data.attach.localPath, size, option.rect.topLeft(), vp); + preview = new Preview(path, size, option.rect.topLeft(), vp); previews->insert(std::make_pair(data.id, preview)); } diff --git a/ui/widgets/messageline/messagedelegate.h b/ui/widgets/messageline/messagedelegate.h index b58a1bb..9225412 100644 --- a/ui/widgets/messageline/messagedelegate.h +++ b/ui/widgets/messageline/messagedelegate.h @@ -33,6 +33,7 @@ #include "shared/icons.h" #include "shared/global.h" #include "shared/utils.h" +#include "shared/pathcheck.h" #include "preview.h" From 73b1b58a966469ec5b28ebc9899fcebf1df567b3 Mon Sep 17 00:00:00 2001 From: blue Date: Sat, 19 Feb 2022 21:31:49 +0300 Subject: [PATCH 58/93] Downloads folder now is movable --- core/handlers/messagehandler.cpp | 6 +++- core/handlers/messagehandler.h | 1 + core/main.cpp | 1 + core/networkaccess.cpp | 18 +++++++---- core/squawk.cpp | 6 ++++ core/squawk.h | 1 + shared/pathcheck.cpp | 23 +++++++++++++++ shared/pathcheck.h | 3 ++ ui/squawk.cpp | 1 + ui/squawk.h | 1 + ui/widgets/settings/pagegeneral.cpp | 46 ++++++++++++++++++++++++++++- ui/widgets/settings/pagegeneral.h | 7 +++++ ui/widgets/settings/pagegeneral.ui | 2 +- ui/widgets/settings/settings.cpp | 12 +++++++- ui/widgets/settings/settings.h | 5 ++++ ui/widgets/settings/settings.ui | 20 +++++++++++-- 16 files changed, 141 insertions(+), 12 deletions(-) diff --git a/core/handlers/messagehandler.cpp b/core/handlers/messagehandler.cpp index fc05a67..1e89dd6 100644 --- a/core/handlers/messagehandler.cpp +++ b/core/handlers/messagehandler.cpp @@ -304,7 +304,11 @@ void Core::MessageHandler::performSending(Shared::Message data, bool newMessage) //so, the final path changes. Let's assume it changes always since it costs me close to nothing QString attachPath = data.getAttachPath(); if (attachPath.size() > 0) { - changes.insert("attachPath", attachPath); + QString squawkified = Shared::squawkifyPath(attachPath); + changes.insert("attachPath", squawkified); + if (attachPath != squawkified) { + data.setAttachPath(squawkified); + } } if (ri != 0) { diff --git a/core/handlers/messagehandler.h b/core/handlers/messagehandler.h index b88c46a..4f03484 100644 --- a/core/handlers/messagehandler.h +++ b/core/handlers/messagehandler.h @@ -29,6 +29,7 @@ #include #include +#include namespace Core { diff --git a/core/main.cpp b/core/main.cpp index 9a10062..79ca648 100644 --- a/core/main.cpp +++ b/core/main.cpp @@ -160,6 +160,7 @@ int main(int argc, char *argv[]) QObject::connect(&w, &Squawk::uploadVCard, squawk, &Core::Squawk::uploadVCard); QObject::connect(&w, &Squawk::responsePassword, squawk, &Core::Squawk::responsePassword); QObject::connect(&w, &Squawk::localPathInvalid, squawk, &Core::Squawk::onLocalPathInvalid); + QObject::connect(&w, &Squawk::changeDownloadsPath, squawk, &Core::Squawk::changeDownloadsPath); QObject::connect(squawk, &Core::Squawk::newAccount, &w, &Squawk::newAccount); QObject::connect(squawk, &Core::Squawk::addContact, &w, &Squawk::addContact); diff --git a/core/networkaccess.cpp b/core/networkaccess.cpp index 25fafc8..7c55e19 100644 --- a/core/networkaccess.cpp +++ b/core/networkaccess.cpp @@ -438,10 +438,10 @@ void Core::NetworkAccess::onUploadProgress(qint64 bytesReceived, qint64 bytesTot QString Core::NetworkAccess::getFileRemoteUrl(const QString& path) { - QString p; + QString p = Shared::squawkifyPath(path); try { - p = storage.getUrl(path); + p = storage.getUrl(p); } catch (const Archive::NotFound& err) { } catch (...) { @@ -574,10 +574,16 @@ std::list Core::NetworkAccess::reportPathInvalid(const QStr void Core::NetworkAccess::moveFilesDirectory(const QString& newPath) { - QDir dir; - if (dir.rename(currentPath, newPath)) { - currentPath = newPath; - } else { + QDir dir(currentPath); + bool success = true; + qDebug() << "moving" << currentPath << "to" << newPath; + for (QFileInfo fileInfo : dir.entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot | QDir::Hidden | QDir::System)) { + QString fileName = fileInfo.fileName(); + success = dir.rename(fileName, newPath + QDir::separator() + fileName) && success; + } + + if (!success) { qDebug() << "couldn't move downloads directory, most probably downloads will be broken"; } + currentPath = newPath; } diff --git a/core/squawk.cpp b/core/squawk.cpp index 69d2eef..9f2b445 100644 --- a/core/squawk.cpp +++ b/core/squawk.cpp @@ -785,3 +785,9 @@ void Core::Squawk::onLocalPathInvalid(const QString& path) } } } + +void Core::Squawk::changeDownloadsPath(const QString& path) +{ + network.moveFilesDirectory(path); +} + diff --git a/core/squawk.h b/core/squawk.h index 5db9fa8..738a957 100644 --- a/core/squawk.h +++ b/core/squawk.h @@ -123,6 +123,7 @@ public slots: void uploadVCard(const QString& account, const Shared::VCard& card); void responsePassword(const QString& account, const QString& password); void onLocalPathInvalid(const QString& path); + void changeDownloadsPath(const QString& path); private: typedef std::deque Accounts; diff --git a/shared/pathcheck.cpp b/shared/pathcheck.cpp index 0850742..1929387 100644 --- a/shared/pathcheck.cpp +++ b/shared/pathcheck.cpp @@ -53,3 +53,26 @@ QString Shared::resolvePath(QString path) QVariant dpv = settings.value("downloadsPath"); return path.replace(squawk, dpv.toString() + "/"); } + +QString Shared::squawkifyPath(QString path) +{ + QSettings settings; + QString current = settings.value("downloadsPath").toString(); + + if (path.startsWith(current)) { + path.replace(0, current.size() + 1, "squawk://"); + } + + return path; +} + +bool Shared::isSubdirectoryOfSettings(const QString& path) +{ + + QSettings settings; + QDir oldPath(settings.value("downloadsPath").toString()); + QDir newPath(path); + + return newPath.canonicalPath().startsWith(oldPath.canonicalPath()); +} + diff --git a/shared/pathcheck.h b/shared/pathcheck.h index 6e7cd39..62dcaeb 100644 --- a/shared/pathcheck.h +++ b/shared/pathcheck.h @@ -13,10 +13,13 @@ namespace Shared { QString downloadsPathCheck(); +QString downloadsPathCheck(QString path); QString defaultDownloadsPath(); QString getAbsoluteWritablePath(const QString& path); QString resolvePath(QString path); +QString squawkifyPath(QString path); +bool isSubdirectoryOfSettings(const QString& path); } diff --git a/ui/squawk.cpp b/ui/squawk.cpp index 7acda3d..e24640a 100644 --- a/ui/squawk.cpp +++ b/ui/squawk.cpp @@ -125,6 +125,7 @@ void Squawk::onPreferences() preferences = new Settings(); preferences->setAttribute(Qt::WA_DeleteOnClose); connect(preferences, &Settings::destroyed, this, &Squawk::onPreferencesClosed); + connect(preferences, &Settings::changeDownloadsPath, this, &Squawk::changeDownloadsPath); preferences->show(); } else { diff --git a/ui/squawk.h b/ui/squawk.h index b419057..7551f66 100644 --- a/ui/squawk.h +++ b/ui/squawk.h @@ -80,6 +80,7 @@ signals: void uploadVCard(const QString& account, const Shared::VCard& card); void responsePassword(const QString& account, const QString& password); void localPathInvalid(const QString& path); + void changeDownloadsPath(const QString& path); public slots: void writeSettings(); diff --git a/ui/widgets/settings/pagegeneral.cpp b/ui/widgets/settings/pagegeneral.cpp index 56cb610..a546bd0 100644 --- a/ui/widgets/settings/pagegeneral.cpp +++ b/ui/widgets/settings/pagegeneral.cpp @@ -3,14 +3,58 @@ PageGeneral::PageGeneral(QWidget* parent): QWidget(parent), - m_ui(new Ui::PageGeneral()) + m_ui(new Ui::PageGeneral()), + dialog(nullptr) { m_ui->setupUi(this); QSettings settings; m_ui->downloadsPathInput->setText(settings.value("downloadsPath").toString()); + connect(m_ui->downloadsPathButton, &QPushButton::clicked, this, &PageGeneral::onBrowseButtonClicked); } PageGeneral::~PageGeneral() { + if (dialog != nullptr) { + dialog->deleteLater(); + } +} + +void PageGeneral::onBrowseButtonClicked() +{ + if (dialog == nullptr) { + QSettings settings; + dialog = new QFileDialog(this, tr("Select where downloads folder is going to be"), settings.value("downloadsPath").toString()); + dialog->setAttribute(Qt::WA_DeleteOnClose); + dialog->setAcceptMode(QFileDialog::AcceptSave); //I find it the most convinient way + dialog->setFileMode(QFileDialog::AnyFile); //Otherwise the directory is supposed to be + dialog->setOption(QFileDialog::ShowDirsOnly, true); //selected and not to be navigated + dialog->setOption(QFileDialog::DontConfirmOverwrite, true); + dialog->setModal(true); + connect(dialog, &QFileDialog::accepted, this, &PageGeneral::onDialogAccepted); + connect(dialog, &QFileDialog::destroyed, this, &PageGeneral::onDialogDestroyed); + dialog->show(); + } else { + dialog->show(); + dialog->raise(); + dialog->activateWindow(); + } +} + +void PageGeneral::onDialogAccepted() +{ + QStringList files = dialog->selectedFiles(); + QString path; + if (files.size() > 0) { + path = files[0]; + } else { + path = dialog->directory().canonicalPath(); + } + m_ui->downloadsPathInput->setText(path); + emit variableModified("downloadsPath", path); +} + +void PageGeneral::onDialogDestroyed() +{ + dialog = nullptr; } diff --git a/ui/widgets/settings/pagegeneral.h b/ui/widgets/settings/pagegeneral.h index cb89bfa..ec00bba 100644 --- a/ui/widgets/settings/pagegeneral.h +++ b/ui/widgets/settings/pagegeneral.h @@ -5,6 +5,7 @@ #include #include #include +#include namespace Ui { @@ -24,8 +25,14 @@ public: signals: void variableModified(const QString& key, const QVariant& value); +private slots: + void onBrowseButtonClicked(); + void onDialogAccepted(); + void onDialogDestroyed(); + private: QScopedPointer m_ui; + QFileDialog* dialog; }; #endif // PAGEGENERAL_H diff --git a/ui/widgets/settings/pagegeneral.ui b/ui/widgets/settings/pagegeneral.ui index e010980..e412668 100644 --- a/ui/widgets/settings/pagegeneral.ui +++ b/ui/widgets/settings/pagegeneral.ui @@ -33,7 +33,7 @@ - PushButton + Browse diff --git a/ui/widgets/settings/settings.cpp b/ui/widgets/settings/settings.cpp index 3ab38bb..27401bb 100644 --- a/ui/widgets/settings/settings.cpp +++ b/ui/widgets/settings/settings.cpp @@ -42,11 +42,21 @@ void Settings::apply() for (const std::pair& pair: modifiedSettings) { if (pair.first == "style") { Shared::Global::setStyle(pair.second.toString()); + settings.setValue(pair.first, pair.second); } else if (pair.first == "theme") { Shared::Global::setTheme(pair.second.toString()); + settings.setValue(pair.first, pair.second); + } else if (pair.first == "downloadsPath") { + QString path = pair.second.toString(); + if (!Shared::isSubdirectoryOfSettings(path)) { + path = Shared::getAbsoluteWritablePath(path); + if (path.size() > 0) { + settings.setValue(pair.first, path); + emit changeDownloadsPath(path); + } + } } - settings.setValue(pair.first, pair.second); } modifiedSettings.clear(); diff --git a/ui/widgets/settings/settings.h b/ui/widgets/settings/settings.h index 5031139..5a6b37c 100644 --- a/ui/widgets/settings/settings.h +++ b/ui/widgets/settings/settings.h @@ -5,8 +5,10 @@ #include #include #include +#include #include "shared/global.h" +#include "shared/pathcheck.h" namespace Ui { @@ -23,6 +25,9 @@ public: Settings(QWidget* parent = nullptr); ~Settings(); +signals: + void changeDownloadsPath(const QString& path); + public slots: void apply(); void confirm(); diff --git a/ui/widgets/settings/settings.ui b/ui/widgets/settings/settings.ui index fe092dc..d97a3f2 100644 --- a/ui/widgets/settings/settings.ui +++ b/ui/widgets/settings/settings.ui @@ -10,6 +10,9 @@ 363 + + Preferences + 0 @@ -183,10 +186,23 @@ 0 - - + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + From 0823b35148d690d60c12ec5a19821cfe1f9fbe1b Mon Sep 17 00:00:00 2001 From: blue Date: Sun, 20 Feb 2022 22:10:09 +0300 Subject: [PATCH 59/93] removed unused old message line files, first thoughts on message edition --- CHANGELOG.md | 2 + ui/utils/badge.cpp | 81 +++- ui/utils/badge.h | 10 + ui/widgets/conversation.cpp | 8 +- ui/widgets/conversation.h | 6 + ui/widgets/conversation.ui | 34 +- ui/widgets/messageline/CMakeLists.txt | 4 - ui/widgets/messageline/message.cpp | 344 ----------------- ui/widgets/messageline/message.h | 103 ----- ui/widgets/messageline/messageline.cpp | 504 ------------------------- ui/widgets/messageline/messageline.h | 108 ------ 11 files changed, 121 insertions(+), 1083 deletions(-) delete mode 100644 ui/widgets/messageline/message.cpp delete mode 100644 ui/widgets/messageline/message.h delete mode 100644 ui/widgets/messageline/messageline.cpp delete mode 100644 ui/widgets/messageline/messageline.h diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b1d75a..8a6fdfa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,11 +6,13 @@ - build now correctly installs all build plugin libs ### Improvements +- reduced amount of places where platform specific path separator is used ### New features - the settings are here! You con config different stuff from there - now it's possible to set up different qt styles from settings - if you have KConfig nad KConfigWidgets packages installed - you can chose from global color schemes +- it's possible now to chose a folder where squawk is going to store downloaded files ## Squawk 0.2.0 (Jan 10, 2022) ### Bug fixes diff --git a/ui/utils/badge.cpp b/ui/utils/badge.cpp index 7575afc..b3b321a 100644 --- a/ui/utils/badge.cpp +++ b/ui/utils/badge.cpp @@ -26,32 +26,62 @@ Badge::Badge(const QString& p_id, const QString& p_text, const QIcon& icon, QWid closeButton(new QPushButton()), layout(new QHBoxLayout(this)) { - setBackgroundRole(QPalette::Base); - //setAutoFillBackground(true); - setFrameStyle(QFrame::StyledPanel); - setFrameShadow(QFrame::Raised); + createMandatoryComponents(); image->setPixmap(icon.pixmap(25, 25)); - QIcon tabCloseIcon = QIcon::fromTheme("tab-close"); - if (tabCloseIcon.isNull()) { - tabCloseIcon.addFile(QString::fromUtf8(":/images/fallback/dark/big/edit-none.svg"), QSize(), QIcon::Normal, QIcon::Off); - } - closeButton->setIcon(tabCloseIcon); - closeButton->setMaximumHeight(25); - closeButton->setMaximumWidth(25); - layout->addWidget(image); layout->addWidget(text); layout->addWidget(closeButton); - - layout->setContentsMargins(2, 2, 2, 2); - - connect(closeButton, &QPushButton::clicked, this, &Badge::close); +} + +Badge::Badge(QWidget* parent): + QFrame(parent), + id(Shared::generateUUID()), + image(nullptr), + text(nullptr), + closeButton(new QPushButton()), + layout(new QHBoxLayout(this)) +{ + createMandatoryComponents(); + + layout->addWidget(closeButton); +} + +void Badge::setIcon(const QIcon& icon) +{ + if (image == nullptr) { + image = new QLabel(); + image->setPixmap(icon.pixmap(25, 25)); + layout->insertWidget(0, image); + } else { + image->setPixmap(icon.pixmap(25, 25)); + } +} + +void Badge::setText(const QString& p_text) +{ + if (text == nullptr) { + text = new QLabel(p_text); + int index = 0; + if (image != nullptr) { + index = 1; + } + layout->insertWidget(index, text); + } else { + text->setText(p_text); + } } Badge::~Badge() { + if (image != nullptr) { + delete image; + } + if (text != nullptr) { + delete text; + } + delete closeButton; } bool Badge::Comparator::operator()(const Badge* a, const Badge* b) const @@ -63,3 +93,22 @@ bool Badge::Comparator::operator()(const Badge& a, const Badge& b) const { return a.id < b.id; } + +void Badge::createMandatoryComponents() +{ + setBackgroundRole(QPalette::Base); + //setAutoFillBackground(true); + setFrameStyle(QFrame::StyledPanel); + setFrameShadow(QFrame::Raised); + + QIcon tabCloseIcon = QIcon::fromTheme("tab-close"); + if (tabCloseIcon.isNull()) { + tabCloseIcon.addFile(QString::fromUtf8(":/images/fallback/dark/big/edit-none.svg"), QSize(), QIcon::Normal, QIcon::Off); + } + closeButton->setIcon(tabCloseIcon); + + closeButton->setMaximumHeight(25); + closeButton->setMaximumWidth(25); + layout->setContentsMargins(2, 2, 2, 2); + connect(closeButton, &QPushButton::clicked, this, &Badge::close); +} diff --git a/ui/utils/badge.h b/ui/utils/badge.h index 93a7f00..52f4747 100644 --- a/ui/utils/badge.h +++ b/ui/utils/badge.h @@ -25,6 +25,8 @@ #include #include +#include "shared/utils.h" + /** * @todo write docs */ @@ -33,9 +35,14 @@ class Badge : public QFrame Q_OBJECT public: Badge(const QString& id, const QString& text, const QIcon& icon, QWidget* parent = nullptr); + Badge(QWidget* parent = nullptr); ~Badge(); const QString id; + +public: + void setText(const QString& text); + void setIcon(const QIcon& icon); signals: void close(); @@ -45,6 +52,9 @@ private: QLabel* text; QPushButton* closeButton; QHBoxLayout* layout; + +private: + void createMandatoryComponents(); public: struct Comparator { diff --git a/ui/widgets/conversation.cpp b/ui/widgets/conversation.cpp index 1c82024..07c599e 100644 --- a/ui/widgets/conversation.cpp +++ b/ui/widgets/conversation.cpp @@ -57,7 +57,8 @@ Conversation::Conversation(bool muc, Models::Account* acc, Models::Element* el, tsb(QApplication::style()->styleHint(QStyle::SH_ScrollBar_Transient) == 1), pasteImageAction(new QAction(tr("Paste Image"), this)), shadow(10, 1, Qt::black, this), - contextMenu(new QMenu()) + contextMenu(new QMenu()), + currentAction(CurrentAction::none) { m_ui->setupUi(this); @@ -108,6 +109,9 @@ Conversation::Conversation(bool muc, Models::Account* acc, Models::Element* el, //line->setMyName(acc->getName()); initializeOverlay(); + + m_ui->currentActionBadge->setVisible(false);; +// m_ui->currentActionBadge->setText(tr("Editing message...")); } Conversation::~Conversation() @@ -237,7 +241,7 @@ void Conversation::onEnterPressed() element->feed->registerUpload(msg.getId()); emit sendMessage(msg); } - clearAttachedFiles(); + clearAttachedFiles(); } } diff --git a/ui/widgets/conversation.h b/ui/widgets/conversation.h index 76a8dd5..c43d7a0 100644 --- a/ui/widgets/conversation.h +++ b/ui/widgets/conversation.h @@ -121,6 +121,11 @@ public: const bool isMuc; protected: + enum class CurrentAction { + none, + edit + }; + Models::Account* account; Models::Element* element; QString palJid; @@ -142,6 +147,7 @@ protected: ShadowOverlay shadow; QMenu* contextMenu; + CurrentAction currentAction; private: static bool painterInitialized; diff --git a/ui/widgets/conversation.ui b/ui/widgets/conversation.ui index 483375a..ce9ad66 100644 --- a/ui/widgets/conversation.ui +++ b/ui/widgets/conversation.ui @@ -279,6 +279,29 @@ + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + @@ -400,8 +423,8 @@ background-color: transparent <!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;"> -<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> +</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; font-family:'Liberation Sans'; font-size:10pt;"><br /></p></body></html> false @@ -419,6 +442,13 @@ p, li { white-space: pre-wrap; } + + + Badge + QFrame +
ui/utils/badge.h
+
+
diff --git a/ui/widgets/messageline/CMakeLists.txt b/ui/widgets/messageline/CMakeLists.txt index 7cace9d..7a850da 100644 --- a/ui/widgets/messageline/CMakeLists.txt +++ b/ui/widgets/messageline/CMakeLists.txt @@ -1,14 +1,10 @@ target_sources(squawk PRIVATE messagedelegate.cpp messagedelegate.h - #messageline.cpp - #messageline.h preview.cpp preview.h messagefeed.cpp messagefeed.h feedview.cpp feedview.h - #message.cpp - #message.h ) diff --git a/ui/widgets/messageline/message.cpp b/ui/widgets/messageline/message.cpp deleted file mode 100644 index 7a004bb..0000000 --- a/ui/widgets/messageline/message.cpp +++ /dev/null @@ -1,344 +0,0 @@ -/* - * Squawk messenger. - * Copyright (C) 2019 Yury Gubich - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General 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 "message.h" -#include -#include -#include -#include -#include - -Message::Message(const Shared::Message& source, bool p_outgoing, const QString& p_sender, const QString& avatarPath, QWidget* parent): - QWidget(parent), - outgoing(p_outgoing), - msg(source), - body(new QWidget()), - statusBar(new QWidget()), - bodyLayout(new QVBoxLayout(body)), - layout(new QHBoxLayout(this)), - date(new QLabel(msg.getTime().toLocalTime().toString())), - sender(new QLabel(p_sender)), - text(new QLabel()), - shadow(new QGraphicsDropShadowEffect()), - button(0), - file(0), - progress(0), - fileComment(new QLabel()), - statusIcon(0), - editedLabel(0), - avatar(new Image(avatarPath.size() == 0 ? Shared::iconPath("user", true) : avatarPath, 60)), - hasButton(false), - hasProgress(false), - hasFile(false), - commentAdded(false), - hasStatusIcon(false), - hasEditedLabel(false) -{ - setContentsMargins(0, 0, 0, 0); - layout->setContentsMargins(10, 5, 10, 5); - body->setBackgroundRole(QPalette::AlternateBase); - body->setAutoFillBackground(true); - - QString bd = Shared::processMessageBody(msg.getBody()); - text->setTextFormat(Qt::RichText); - text->setText(bd);; - text->setTextInteractionFlags(text->textInteractionFlags() | Qt::TextSelectableByMouse | Qt::LinksAccessibleByMouse); - text->setWordWrap(true); - text->setOpenExternalLinks(true); - if (bd.size() == 0) { - text->hide(); - } - - QFont dFont = date->font(); - dFont.setItalic(true); - dFont.setPointSize(dFont.pointSize() - 2); - date->setFont(dFont); - - QFont f; - f.setBold(true); - sender->setFont(f); - - bodyLayout->addWidget(sender); - bodyLayout->addWidget(text); - - shadow->setBlurRadius(10); - shadow->setXOffset(1); - shadow->setYOffset(1); - shadow->setColor(Qt::black); - body->setGraphicsEffect(shadow); - avatar->setMaximumHeight(60); - avatar->setMaximumWidth(60); - - statusBar->setContentsMargins(0, 0, 0, 0); - QHBoxLayout* statusLay = new QHBoxLayout(); - statusLay->setContentsMargins(0, 0, 0, 0); - statusBar->setLayout(statusLay); - - if (outgoing) { - sender->setAlignment(Qt::AlignRight); - date->setAlignment(Qt::AlignRight); - statusIcon = new QLabel(); - setState(); - statusLay->addWidget(statusIcon); - statusLay->addWidget(date); - layout->addStretch(); - layout->addWidget(body); - layout->addWidget(avatar); - hasStatusIcon = true; - } else { - layout->addWidget(avatar); - layout->addWidget(body); - layout->addStretch(); - statusLay->addWidget(date); - } - if (msg.getEdited()) { - setEdited(); - } - - bodyLayout->addWidget(statusBar); - layout->setAlignment(avatar, Qt::AlignTop); -} - -Message::~Message() -{ - if (!commentAdded) { - delete fileComment; - } - //delete body; //not sure if I should delete it here, it's probably already owned by the infrastructure and gonna die with the rest of the widget - //delete avatar; -} - -QString Message::getId() const -{ - return msg.getId(); -} - -QString Message::getSenderJid() const -{ - return msg.getFromJid(); -} - -QString Message::getSenderResource() const -{ - return msg.getFromResource(); -} - -QString Message::getFileUrl() const -{ - return msg.getOutOfBandUrl(); -} - -void Message::setSender(const QString& p_sender) -{ - sender->setText(p_sender); -} - -void Message::addButton(const QIcon& icon, const QString& buttonText, const QString& tooltip) -{ - hideFile(); - hideProgress(); - if (!hasButton) { - hideComment(); - if (msg.getBody() == msg.getOutOfBandUrl()) { - text->setText(""); - text->hide(); - } - button = new QPushButton(icon, buttonText); - button->setToolTip(tooltip); - connect(button, &QPushButton::clicked, this, &Message::buttonClicked); - bodyLayout->insertWidget(2, button); - hasButton = true; - } -} - -void Message::setProgress(qreal value) -{ - hideFile(); - hideButton(); - if (!hasProgress) { - hideComment(); - if (msg.getBody() == msg.getOutOfBandUrl()) { - text->setText(""); - text->hide(); - } - progress = new QProgressBar(); - progress->setRange(0, 100); - bodyLayout->insertWidget(2, progress); - hasProgress = true; - } - progress->setValue(value * 100); -} - -void Message::showFile(const QString& path) -{ - hideButton(); - hideProgress(); - if (!hasFile) { - hideComment(); - if (msg.getBody() == msg.getOutOfBandUrl()) { - text->setText(""); - text->hide(); - } - QMimeDatabase db; - QMimeType type = db.mimeTypeForFile(path); - QStringList parts = type.name().split("/"); - QString big = parts.front(); - QFileInfo info(path); - if (big == "image") { - file = new Image(path); - } else { - file = new QLabel(); - file->setPixmap(QIcon::fromTheme(type.iconName()).pixmap(50)); - file->setAlignment(Qt::AlignCenter); - showComment(info.fileName(), true); - } - file->setContextMenuPolicy(Qt::ActionsContextMenu); - QAction* openAction = new QAction(QIcon::fromTheme("document-new-from-template"), tr("Open"), file); - connect(openAction, &QAction::triggered, [path]() { //TODO need to get rid of this shame - QDesktopServices::openUrl(QUrl::fromLocalFile(path)); - }); - file->addAction(openAction); - bodyLayout->insertWidget(2, file); - hasFile = true; - } -} - -void Message::hideComment() -{ - if (commentAdded) { - bodyLayout->removeWidget(fileComment); - fileComment->hide(); - fileComment->setWordWrap(false); - commentAdded = false; - } -} - -void Message::hideButton() -{ - if (hasButton) { - button->deleteLater(); - button = 0; - hasButton = false; - } -} - -void Message::hideFile() -{ - if (hasFile) { - file->deleteLater(); - file = 0; - hasFile = false; - } -} - -void Message::hideProgress() -{ - if (hasProgress) { - progress->deleteLater(); - progress = 0; - hasProgress = false;; - } -} -void Message::showComment(const QString& comment, bool wordWrap) -{ - if (!commentAdded) { - int index = 2; - if (hasFile) { - index++; - } - if (hasButton) { - index++; - } - if (hasProgress) { - index++; - } - bodyLayout->insertWidget(index, fileComment); - fileComment->show(); - commentAdded = true; - } - fileComment->setWordWrap(wordWrap); - fileComment->setText(comment); -} - -const Shared::Message & Message::getMessage() const -{ - return msg; -} - -void Message::setAvatarPath(const QString& p_path) -{ - if (p_path.size() == 0) { - avatar->setPath(Shared::iconPath("user", true)); - } else { - avatar->setPath(p_path); - } -} - -bool Message::change(const QMap& data) -{ - bool idChanged = msg.change(data); - - QString body = msg.getBody(); - QString bd = Shared::processMessageBody(body); - if (body.size() > 0) { - text->setText(bd); - text->show(); - } else { - text->setText(body); - text->hide(); - } - if (msg.getEdited()) { - setEdited(); - } - if (hasStatusIcon) { - setState(); - } - - - return idChanged; -} - -void Message::setEdited() -{ - if (!hasEditedLabel) { - editedLabel = new QLabel(); - hasEditedLabel = true; - QIcon q(Shared::icon("edit-rename")); - editedLabel->setPixmap(q.pixmap(12, 12)); - QHBoxLayout* statusLay = static_cast(statusBar->layout()); - statusLay->insertWidget(1, editedLabel); - } - editedLabel->setToolTip("Last time edited: " + msg.getLastModified().toLocalTime().toString() - + "\nOriginal message: " + msg.getOriginalBody()); -} - -void Message::setState() -{ - Shared::Message::State state = msg.getState(); - QIcon q(Shared::icon(Shared::messageStateThemeIcons[static_cast(state)])); - QString tt = Shared::Global::getName(state); - if (state == Shared::Message::State::error) { - QString errText = msg.getErrorText(); - if (errText.size() > 0) { - tt += ": " + errText; - } - } - statusIcon->setToolTip(tt); - statusIcon->setPixmap(q.pixmap(12, 12)); -} - diff --git a/ui/widgets/messageline/message.h b/ui/widgets/messageline/message.h deleted file mode 100644 index eef93a1..0000000 --- a/ui/widgets/messageline/message.h +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Squawk messenger. - * Copyright (C) 2019 Yury Gubich - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General 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 MESSAGE_H -#define MESSAGE_H - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "shared/message.h" -#include "shared/icons.h" -#include "shared/global.h" -#include "shared/utils.h" -#include "resizer.h" -#include "image.h" - -/** - * @todo write docs - */ -class Message : public QWidget -{ - Q_OBJECT -public: - Message(const Shared::Message& source, bool outgoing, const QString& sender, const QString& avatarPath = "", QWidget* parent = nullptr); - ~Message(); - - void setSender(const QString& sender); - QString getId() const; - QString getSenderJid() const; - QString getSenderResource() const; - QString getFileUrl() const; - const Shared::Message& getMessage() const; - - void addButton(const QIcon& icon, const QString& buttonText, const QString& tooltip = ""); - void showComment(const QString& comment, bool wordWrap = false); - void hideComment(); - void showFile(const QString& path); - void setProgress(qreal value); - void setAvatarPath(const QString& p_path); - bool change(const QMap& data); - - bool const outgoing; - -signals: - void buttonClicked(); - -private: - Shared::Message msg; - QWidget* body; - QWidget* statusBar; - QVBoxLayout* bodyLayout; - QHBoxLayout* layout; - QLabel* date; - QLabel* sender; - QLabel* text; - QGraphicsDropShadowEffect* shadow; - QPushButton* button; - QLabel* file; - QProgressBar* progress; - QLabel* fileComment; - QLabel* statusIcon; - QLabel* editedLabel; - Image* avatar; - bool hasButton; - bool hasProgress; - bool hasFile; - bool commentAdded; - bool hasStatusIcon; - bool hasEditedLabel; - -private: - void hideButton(); - void hideProgress(); - void hideFile(); - void setState(); - void setEdited(); -}; - -#endif // MESSAGE_H diff --git a/ui/widgets/messageline/messageline.cpp b/ui/widgets/messageline/messageline.cpp deleted file mode 100644 index fec0037..0000000 --- a/ui/widgets/messageline/messageline.cpp +++ /dev/null @@ -1,504 +0,0 @@ -/* - * Squawk messenger. - * Copyright (C) 2019 Yury Gubich - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General 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 "messageline.h" -#include -#include -#include - -MessageLine::MessageLine(bool p_room, QWidget* parent): - QWidget(parent), - messageIndex(), - messageOrder(), - myMessages(), - palMessages(), - uploadPaths(), - palAvatars(), - exPalAvatars(), - layout(new QVBoxLayout(this)), - myName(), - myAvatarPath(), - palNames(), - uploading(), - downloading(), - room(p_room), - busyShown(false), - progress() -{ - setContentsMargins(0, 0, 0, 0); - layout->setContentsMargins(0, 0, 0, 0); - layout->setSpacing(0); - layout->addStretch(); -} - -MessageLine::~MessageLine() -{ - for (Index::const_iterator itr = messageIndex.begin(), end = messageIndex.end(); itr != end; ++itr) { - delete itr->second; - } -} - -MessageLine::Position MessageLine::message(const Shared::Message& msg, bool forceOutgoing) -{ - QString id = msg.getId(); - Index::iterator itr = messageIndex.find(id); - if (itr != messageIndex.end()) { - qDebug() << "received more then one message with the same id, skipping yet the new one"; - return invalid; - } - - QString sender; - QString aPath; - bool outgoing; - - if (forceOutgoing) { - sender = myName; - aPath = myAvatarPath; - outgoing = true; - } else { - if (room) { - if (msg.getFromResource() == myName) { - sender = myName; - aPath = myAvatarPath; - outgoing = true; - } else { - sender = msg.getFromResource(); - std::map::iterator aItr = palAvatars.find(sender); - if (aItr != palAvatars.end()) { - aPath = aItr->second; - } else { - aItr = exPalAvatars.find(sender); - if (aItr != exPalAvatars.end()) { - aPath = aItr->second; - } - } - outgoing = false; - } - } else { - if (msg.getOutgoing()) { - sender = myName; - aPath = myAvatarPath; - outgoing = true; - } else { - QString jid = msg.getFromJid(); - std::map::iterator itr = palNames.find(jid); - if (itr != palNames.end()) { - sender = itr->second; - } else { - sender = jid; - } - - std::map::iterator aItr = palAvatars.find(jid); - if (aItr != palAvatars.end()) { - aPath = aItr->second; - } - - outgoing = false; - } - } - } - - Message* message = new Message(msg, outgoing, sender, aPath); - - std::pair result = messageOrder.insert(std::make_pair(msg.getTime(), message)); - if (!result.second) { - qDebug() << "Error appending a message into a message list - seems like the time of that message exactly matches the time of some other message, can't put them in order, skipping yet"; - delete message; - return invalid; - } - if (outgoing) { - myMessages.insert(std::make_pair(id, message)); - } else { - QString senderId; - if (room) { - senderId = sender; - } else { - senderId = msg.getFromJid(); - } - - std::map::iterator pItr = palMessages.find(senderId); - if (pItr == palMessages.end()) { - pItr = palMessages.insert(std::make_pair(senderId, Index())).first; - } - pItr->second.insert(std::make_pair(id, message)); - } - messageIndex.insert(std::make_pair(id, message)); - unsigned long index = std::distance(messageOrder.begin(), result.first); //need to make with binary indexed tree - Position res = invalid; - if (index == 0) { - res = beggining; - } else if (index == messageIndex.size() - 1) { - res = end; - } else { - res = middle; - } - - if (busyShown) { - index += 1; - } - - - if (res == end) { - layout->addWidget(message); - } else { - layout->insertWidget(index + 1, message); - } - - if (msg.hasOutOfBandUrl()) { - emit requestLocalFile(msg.getId(), msg.getOutOfBandUrl()); - connect(message, &Message::buttonClicked, this, &MessageLine::onDownload); - } - - return res; -} - -void MessageLine::changeMessage(const QString& id, const QMap& data) -{ - Index::const_iterator itr = messageIndex.find(id); - if (itr != messageIndex.end()) { - Message* msg = itr->second; - if (msg->change(data)) { //if ID changed (stanza in replace of another) - QString newId = msg->getId(); //need to updated IDs of that message in all maps - messageIndex.erase(itr); - messageIndex.insert(std::make_pair(newId, msg)); - if (msg->outgoing) { - QString senderId; - if (room) { - senderId = msg->getSenderResource(); - } else { - senderId = msg->getSenderJid(); - } - - std::map::iterator pItr = palMessages.find(senderId); - if (pItr != palMessages.end()) { - Index::const_iterator sItr = pItr->second.find(id); - if (sItr != pItr->second.end()) { - pItr->second.erase(sItr); - pItr->second.insert(std::make_pair(newId, msg)); - } else { - qDebug() << "Was trying to replace message in open conversations, couldn't find it among pal's messages, probably an error"; - } - } else { - qDebug() << "Was trying to replace message in open conversations, couldn't find pal messages, probably an error"; - } - } else { - Index::const_iterator mItr = myMessages.find(id); - if (mItr != myMessages.end()) { - myMessages.erase(mItr); - myMessages.insert(std::make_pair(newId, msg)); - } else { - qDebug() << "Was trying to replace message in open conversations, couldn't find it among my messages, probably an error"; - } - } - } - } -} - -void MessageLine::onDownload() -{ - Message* msg = static_cast(sender()); - QString messageId = msg->getId(); - Index::const_iterator itr = downloading.find(messageId); - if (itr == downloading.end()) { - downloading.insert(std::make_pair(messageId, msg)); - msg->setProgress(0); - msg->showComment(tr("Downloading...")); - emit downloadFile(messageId, msg->getFileUrl()); - } else { - qDebug() << "An attempt to initiate download for already downloading file" << msg->getFileUrl() << ", skipping"; - } -} - -void MessageLine::setMyName(const QString& name) -{ - myName = name; - for (Index::const_iterator itr = myMessages.begin(), end = myMessages.end(); itr != end; ++itr) { - itr->second->setSender(name); - } -} - -void MessageLine::setPalName(const QString& jid, const QString& name) -{ - std::map::iterator itr = palNames.find(jid); - if (itr == palNames.end()) { - palNames.insert(std::make_pair(jid, name)); - } else { - itr->second = name; - } - - std::map::iterator pItr = palMessages.find(jid); - if (pItr != palMessages.end()) { - for (Index::const_iterator itr = pItr->second.begin(), end = pItr->second.end(); itr != end; ++itr) { - itr->second->setSender(name); - } - } -} - -void MessageLine::setPalAvatar(const QString& jid, const QString& path) -{ - std::map::iterator itr = palAvatars.find(jid); - if (itr == palAvatars.end()) { - palAvatars.insert(std::make_pair(jid, path)); - - std::map::const_iterator eitr = exPalAvatars.find(jid); - if (eitr != exPalAvatars.end()) { - exPalAvatars.erase(eitr); - } - } else { - itr->second = path; - } - - std::map::iterator pItr = palMessages.find(jid); - if (pItr != palMessages.end()) { - for (Index::const_iterator itr = pItr->second.begin(), end = pItr->second.end(); itr != end; ++itr) { - itr->second->setAvatarPath(path); - } - } -} - -void MessageLine::dropPalAvatar(const QString& jid) -{ - std::map::iterator itr = palAvatars.find(jid); - if (itr != palAvatars.end()) { - palAvatars.erase(itr); - } - - std::map::const_iterator eitr = exPalAvatars.find(jid); - if (eitr != exPalAvatars.end()) { - exPalAvatars.erase(eitr); - } - - std::map::iterator pItr = palMessages.find(jid); - if (pItr != palMessages.end()) { - for (Index::const_iterator itr = pItr->second.begin(), end = pItr->second.end(); itr != end; ++itr) { - itr->second->setAvatarPath(""); - } - } -} - -void MessageLine::movePalAvatarToEx(const QString& name) -{ - std::map::iterator itr = palAvatars.find(name); - if (itr != palAvatars.end()) { - std::map::iterator eitr = exPalAvatars.find(name); - if (eitr != exPalAvatars.end()) { - eitr->second = itr->second; - } else { - exPalAvatars.insert(std::make_pair(name, itr->second)); - } - - palAvatars.erase(itr); - } -} - -void MessageLine::resizeEvent(QResizeEvent* event) -{ - QWidget::resizeEvent(event); - emit resize(event->size().height() - event->oldSize().height()); -} - - -QString MessageLine::firstMessageId() const -{ - if (messageOrder.size() == 0) { - return ""; - } else { - return messageOrder.begin()->second->getId(); - } -} - -void MessageLine::showBusyIndicator() -{ - if (!busyShown) { - layout->insertWidget(0, &progress); - progress.start(); - busyShown = true; - } -} - -void MessageLine::hideBusyIndicator() -{ - if (busyShown) { - progress.stop(); - layout->removeWidget(&progress); - busyShown = false; - } -} - -void MessageLine::fileProgress(const QString& messageId, qreal progress) -{ - Index::const_iterator itr = messageIndex.find(messageId); - if (itr == messageIndex.end()) { - //TODO may be some logging, that's not normal - } else { - itr->second->setProgress(progress); - } -} - -void MessageLine::responseLocalFile(const QString& messageId, const QString& path) -{ - Index::const_iterator itr = messageIndex.find(messageId); - if (itr == messageIndex.end()) { - - } else { - Index::const_iterator uItr = uploading.find(messageId); - if (path.size() > 0) { - Index::const_iterator dItr = downloading.find(messageId); - if (dItr != downloading.end()) { - downloading.erase(dItr); - itr->second->showFile(path); - } else { - if (uItr != uploading.end()) { - uploading.erase(uItr); - std::map::const_iterator muItr = uploadPaths.find(messageId); - if (muItr != uploadPaths.end()) { - uploadPaths.erase(muItr); - } - Shared::Message msg = itr->second->getMessage(); - removeMessage(messageId); - msg.setCurrentTime(); - message(msg); - itr = messageIndex.find(messageId); - itr->second->showFile(path); - } else { - itr->second->showFile(path); //then it is already cached file - } - } - } else { - if (uItr == uploading.end()) { - const Shared::Message& msg = itr->second->getMessage(); - itr->second->addButton(QIcon::fromTheme("download"), tr("Download"), "" + msg.getOutOfBandUrl() + ""); - itr->second->showComment(tr("Push the button to download the file")); - } else { - qDebug() << "An unhandled state for file uploading - empty path"; - } - } - } -} - -void MessageLine::removeMessage(const QString& messageId) -{ - Index::const_iterator itr = messageIndex.find(messageId); - if (itr != messageIndex.end()) { - Message* ui = itr->second; - const Shared::Message& msg = ui->getMessage(); - messageIndex.erase(itr); - Order::const_iterator oItr = messageOrder.find(msg.getTime()); - if (oItr != messageOrder.end()) { - messageOrder.erase(oItr); - } else { - qDebug() << "An attempt to remove message from messageLine, but it wasn't found in order"; - } - if (msg.getOutgoing()) { - Index::const_iterator mItr = myMessages.find(messageId); - if (mItr != myMessages.end()) { - myMessages.erase(mItr); - } else { - qDebug() << "Error removing message: it seems to be outgoing yet it wasn't found in outgoing messages"; - } - } else { - if (room) { - - } else { - QString jid = msg.getFromJid(); - std::map::iterator pItr = palMessages.find(jid); - if (pItr != palMessages.end()) { - Index& pMsgs = pItr->second; - Index::const_iterator pmitr = pMsgs.find(messageId); - if (pmitr != pMsgs.end()) { - pMsgs.erase(pmitr); - } else { - qDebug() << "Error removing message: it seems to be incoming yet it wasn't found among messages from that penpal"; - } - } - } - } - ui->deleteLater(); - qDebug() << "message" << messageId << "has been removed"; - } else { - qDebug() << "An attempt to remove non existing message from messageLine"; - } -} - -void MessageLine::fileError(const QString& messageId, const QString& error) -{ - Index::const_iterator itr = downloading.find(messageId); - if (itr == downloading.end()) { - Index::const_iterator itr = uploading.find(messageId); - if (itr == uploading.end()) { - //TODO may be some logging, that's not normal - } else { - itr->second->showComment(tr("Error uploading file: %1\nYou can try again").arg(QCoreApplication::translate("NetworkErrors", error.toLatin1())), true); - itr->second->addButton(QIcon::fromTheme("upload"), tr("Upload")); - } - } else { - const Shared::Message& msg = itr->second->getMessage(); - itr->second->addButton(QIcon::fromTheme("download"), tr("Download"), "" + msg.getOutOfBandUrl() + ""); - itr->second->showComment(tr("Error downloading file: %1\nYou can try again").arg(QCoreApplication::translate("NetworkErrors", error.toLatin1())), true); - } -} - -void MessageLine::appendMessageWithUpload(const Shared::Message& msg, const QString& path) -{ - appendMessageWithUploadNoSiganl(msg, path); - emit uploadFile(msg, path); -} - -void MessageLine::appendMessageWithUploadNoSiganl(const Shared::Message& msg, const QString& path) -{ - message(msg, true); - QString id = msg.getId(); - Message* ui = messageIndex.find(id)->second; - connect(ui, &Message::buttonClicked, this, &MessageLine::onUpload); //this is in case of retry; - ui->setProgress(0); - ui->showComment(tr("Uploading...")); - uploading.insert(std::make_pair(id, ui)); - uploadPaths.insert(std::make_pair(id, path)); -} - - -void MessageLine::onUpload() -{ - //TODO retry -} - -void MessageLine::setMyAvatarPath(const QString& p_path) -{ - if (myAvatarPath != p_path) { - myAvatarPath = p_path; - for (std::pair pair : myMessages) { - pair.second->setAvatarPath(myAvatarPath); - } - } -} - -void MessageLine::setExPalAvatars(const std::map& data) -{ - exPalAvatars = data; - - for (const std::pair& pair : palMessages) { - if (palAvatars.find(pair.first) == palAvatars.end()) { - std::map::const_iterator eitr = exPalAvatars.find(pair.first); - if (eitr != exPalAvatars.end()) { - for (const std::pair& mp : pair.second) { - mp.second->setAvatarPath(eitr->second); - } - } - } - } -} diff --git a/ui/widgets/messageline/messageline.h b/ui/widgets/messageline/messageline.h deleted file mode 100644 index a0a7b6c..0000000 --- a/ui/widgets/messageline/messageline.h +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Squawk messenger. - * Copyright (C) 2019 Yury Gubich - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General 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 MESSAGELINE_H -#define MESSAGELINE_H - -#include -#include -#include -#include -#include -#include - -#include "shared/message.h" -#include "message.h" -#include "progress.h" - -class MessageLine : public QWidget -{ - Q_OBJECT -public: - enum Position { - beggining, - middle, - end, - invalid - }; - MessageLine(bool p_room, QWidget* parent = 0); - ~MessageLine(); - - Position message(const Shared::Message& msg, bool forceOutgoing = false); - void setMyName(const QString& name); - void setPalName(const QString& jid, const QString& name); - QString firstMessageId() const; - void showBusyIndicator(); - void hideBusyIndicator(); - void responseLocalFile(const QString& messageId, const QString& path); - void fileError(const QString& messageId, const QString& error); - void fileProgress(const QString& messageId, qreal progress); - void appendMessageWithUpload(const Shared::Message& msg, const QString& path); - void appendMessageWithUploadNoSiganl(const Shared::Message& msg, const QString& path); - void removeMessage(const QString& messageId); - void setMyAvatarPath(const QString& p_path); - void setPalAvatar(const QString& jid, const QString& path); - void dropPalAvatar(const QString& jid); - void changeMessage(const QString& id, const QMap& data); - void setExPalAvatars(const std::map& data); - void movePalAvatarToEx(const QString& name); - -signals: - void resize(int amount); - void downloadFile(const QString& messageId, const QString& url); - void uploadFile(const Shared::Message& msg, const QString& path); - void requestLocalFile(const QString& messageId, const QString& url); - -protected: - void resizeEvent(QResizeEvent * event) override; - -protected: - void onDownload(); - void onUpload(); - -private: - struct Comparator { - bool operator()(const Shared::Message& a, const Shared::Message& b) const { - return a.getTime() < b.getTime(); - } - bool operator()(const Shared::Message* a, const Shared::Message* b) const { - return a->getTime() < b->getTime(); - } - }; - typedef std::map Order; - typedef std::map Index; - Index messageIndex; - Order messageOrder; - Index myMessages; - std::map palMessages; - std::map uploadPaths; - std::map palAvatars; - std::map exPalAvatars; - QVBoxLayout* layout; - - QString myName; - QString myAvatarPath; - std::map palNames; - Index uploading; - Index downloading; - bool room; - bool busyShown; - Progress progress; -}; - -#endif // MESSAGELINE_H From bf4a27f35d2761485c201083d210ce3aca11559c Mon Sep 17 00:00:00 2001 From: blue Date: Sun, 27 Mar 2022 22:05:31 +0300 Subject: [PATCH 60/93] Bug with the edited message fixed, some further work on message correction --- shared/message.cpp | 2 +- ui/models/roster.cpp | 2 ++ ui/widgets/conversation.cpp | 30 ++++++++++++++++++++++++-- ui/widgets/conversation.h | 1 + ui/widgets/messageline/messagefeed.cpp | 12 +++++++++++ ui/widgets/messageline/messagefeed.h | 17 +++++++++++++++ 6 files changed, 61 insertions(+), 3 deletions(-) diff --git a/shared/message.cpp b/shared/message.cpp index e6b47b2..f3f6b45 100644 --- a/shared/message.cpp +++ b/shared/message.cpp @@ -406,7 +406,7 @@ bool Shared::Message::change(const QMap& data) if (!edited || lastModified < correctionDate) { originalMessage = body; lastModified = correctionDate; - setBody(body); + setBody(b); setEdited(true); } } diff --git a/ui/models/roster.cpp b/ui/models/roster.cpp index 2d5f99f..588fb1d 100644 --- a/ui/models/roster.cpp +++ b/ui/models/roster.cpp @@ -549,6 +549,8 @@ void Models::Roster::changeMessage(const QString& account, const QString& jid, c Element* el = getElement({account, jid}); if (el != NULL) { el->changeMessage(id, data); + } else { + qDebug() << "A request to change a message of the contact " << jid << " in the account " << account << " but it wasn't found"; } } diff --git a/ui/widgets/conversation.cpp b/ui/widgets/conversation.cpp index 07c599e..608faf3 100644 --- a/ui/widgets/conversation.cpp +++ b/ui/widgets/conversation.cpp @@ -110,7 +110,7 @@ Conversation::Conversation(bool muc, Models::Account* acc, Models::Element* el, initializeOverlay(); - m_ui->currentActionBadge->setVisible(false);; + m_ui->currentActionBadge->setVisible(false); // m_ui->currentActionBadge->setText(tr("Editing message...")); } @@ -476,10 +476,10 @@ void Conversation::onFeedContext(const QPoint& pos) Shared::Message* item = static_cast(index.internalPointer()); contextMenu->clear(); + QString id = item->getId(); bool showMenu = false; if (item->getState() == Shared::Message::State::error) { showMenu = true; - QString id = item->getId(); QAction* resend = contextMenu->addAction(Shared::icon("view-refresh"), tr("Try sending again")); connect(resend, &QAction::triggered, [this, id]() { element->feed->registerUpload(id); @@ -500,6 +500,12 @@ void Conversation::onFeedContext(const QPoint& pos) Shared::Global::highlightInFileManager(path); }); } + + if (item->getOutgoing()) { + showMenu = true; + QAction* edit = contextMenu->addAction(Shared::icon("edit-rename"), tr("Edit")); + connect(edit, &QAction::triggered, this, std::bind(&Conversation::onMessageEditRequested, this, id)); + } if (showMenu) { contextMenu->popup(feed->viewport()->mapToGlobal(pos)); @@ -517,3 +523,23 @@ void Conversation::onMessageEditorContext(const QPoint& pos) editorMenu->exec(this->m_ui->messageEditor->mapToGlobal(pos)); } + +void Conversation::onMessageEditRequested(const QString& id) +{ + if (currentAction == CurrentAction::edit) { + //todo; + } + + try { + Shared::Message msg = element->feed->getMessage(id); + + m_ui->currentActionBadge->setVisible(true); + m_ui->currentActionBadge->setText(tr("Editing message...")); + currentAction = CurrentAction::edit; + m_ui->messageEditor->setText(msg.getBody()); + + } catch (const Models::MessageFeed::NotFound& e) { + qDebug() << "The message requested to be edited was not found" << e.getMessage().c_str(); + qDebug() << "Ignoring"; + } +} diff --git a/ui/widgets/conversation.h b/ui/widgets/conversation.h index c43d7a0..4bccdfc 100644 --- a/ui/widgets/conversation.h +++ b/ui/widgets/conversation.h @@ -116,6 +116,7 @@ protected slots: void positionShadow(); void onFeedContext(const QPoint &pos); void onMessageEditorContext(const QPoint &pos); + void onMessageEditRequested(const QString& id); public: const bool isMuc; diff --git a/ui/widgets/messageline/messagefeed.cpp b/ui/widgets/messageline/messagefeed.cpp index 4803dce..33fbdd4 100644 --- a/ui/widgets/messageline/messagefeed.cpp +++ b/ui/widgets/messageline/messagefeed.cpp @@ -224,8 +224,20 @@ std::set Models::MessageFeed::detectChanges(c void Models::MessageFeed::removeMessage(const QString& id) { + //todo; } +Shared::Message Models::MessageFeed::getMessage(const QString& id) +{ + StorageById::iterator itr = indexById.find(id); + if (itr == indexById.end()) { + throw NotFound(id.toStdString(), rosterItem->getJid().toStdString(), rosterItem->getAccountName().toStdString()); + } + + return **itr; +} + + QVariant Models::MessageFeed::data(const QModelIndex& index, int role) const { int i = index.row(); diff --git a/ui/widgets/messageline/messagefeed.h b/ui/widgets/messageline/messagefeed.h index 2273b15..c9701ae 100644 --- a/ui/widgets/messageline/messagefeed.h +++ b/ui/widgets/messageline/messagefeed.h @@ -32,6 +32,7 @@ #include #include +#include namespace Models { @@ -55,6 +56,7 @@ public: void addMessage(const Shared::Message& msg); void changeMessage(const QString& id, const QMap& data); void removeMessage(const QString& id); + Shared::Message getMessage(const QString& id); QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const override; int rowCount(const QModelIndex& parent = QModelIndex()) const override; @@ -103,6 +105,21 @@ public: Error, Bulk }; + + class NotFound: + public Utils::Exception + { + public: + NotFound(const std::string& k, const std::string& j, const std::string& acc):Exception(), key(k), jid(j), account(acc){} + + std::string getMessage() const { + return "Message with id " + key + " wasn't found in messageFeed " + account + " of the chat with " + jid; + } + private: + std::string key; + std::string jid; + std::string account; + }; protected: bool sentByMe(const Shared::Message& msg) const; From 788c6ca5567563322d3d5afd7943e672e7cdc0f1 Mon Sep 17 00:00:00 2001 From: blue Date: Mon, 28 Mar 2022 23:25:33 +0300 Subject: [PATCH 61/93] now it's possible to fix your messages --- CHANGELOG.md | 2 + core/account.cpp | 4 + core/account.h | 1 + core/handlers/messagehandler.cpp | 192 ++++++++++++++++++++----------- core/handlers/messagehandler.h | 8 +- core/main.cpp | 1 + core/squawk.cpp | 11 ++ core/squawk.h | 1 + shared/message.cpp | 4 +- ui/squawk.cpp | 12 ++ ui/squawk.h | 2 + ui/widgets/conversation.cpp | 44 ++++--- ui/widgets/conversation.h | 7 +- 13 files changed, 204 insertions(+), 85 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a6fdfa..efb159c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Bug fixes - build in release mode now no longer spams warnings - build now correctly installs all build plugin libs +- a bug where the correction message was received, the indication was on but the text didn't actually change ### Improvements - reduced amount of places where platform specific path separator is used @@ -13,6 +14,7 @@ - now it's possible to set up different qt styles from settings - if you have KConfig nad KConfigWidgets packages installed - you can chose from global color schemes - it's possible now to chose a folder where squawk is going to store downloaded files +- now you can correct your message ## Squawk 0.2.0 (Jan 10, 2022) ### Bug fixes diff --git a/core/account.cpp b/core/account.cpp index a923690..91d0f2b 100644 --- a/core/account.cpp +++ b/core/account.cpp @@ -935,3 +935,7 @@ void Core::Account::requestChangeMessage(const QString& jid, const QString& mess void Core::Account::resendMessage(const QString& jid, const QString& id) { mh->resendMessage(jid, id);} + +void Core::Account::replaceMessage(const QString& originalId, const Shared::Message& data) { + mh->sendMessage(data, false, originalId);} + diff --git a/core/account.h b/core/account.h index 5ba834c..664b547 100644 --- a/core/account.h +++ b/core/account.h @@ -104,6 +104,7 @@ public: void addRoomRequest(const QString& jid, const QString& nick, const QString& password, bool autoJoin); void uploadVCard(const Shared::VCard& card); void resendMessage(const QString& jid, const QString& id); + void replaceMessage(const QString& originalId, const Shared::Message& data); public slots: void connect(); diff --git a/core/handlers/messagehandler.cpp b/core/handlers/messagehandler.cpp index 1e89dd6..559bee3 100644 --- a/core/handlers/messagehandler.cpp +++ b/core/handlers/messagehandler.cpp @@ -41,10 +41,10 @@ void Core::MessageHandler::onMessageReceived(const QXmppMessage& msg) handled = handleGroupMessage(msg); break; case QXmppMessage::Error: { - QString id = msg.id(); - std::map::const_iterator itr = pendingStateMessages.find(id); - if (itr != pendingStateMessages.end()) { - QString jid = itr->second; + std::tuple ids = getOriginalPendingMessageId(msg.id()); + if (std::get<0>(ids)) { + QString id = std::get<1>(ids); + QString jid = std::get<2>(ids); RosterItem* cnt = acc->rh->getRosterItem(jid); QMap cData = { {"state", static_cast(Shared::Message::State::error)}, @@ -53,9 +53,7 @@ void Core::MessageHandler::onMessageReceived(const QXmppMessage& msg) if (cnt != 0) { cnt->changeMessage(id, cData); } - ; emit acc->changeMessage(jid, id, cData); - pendingStateMessages.erase(itr); handled = true; } else { qDebug() << "received a message with type \"Error\", not sure what to do with it now, skipping"; @@ -111,7 +109,6 @@ bool Core::MessageHandler::handleGroupMessage(const QXmppMessage& msg, bool outg { const QString& body(msg.body()); if (body.size() != 0) { - QString id = msg.id(); Shared::Message sMsg(Shared::Message::groupChat); initializeMessage(sMsg, msg, outgoing, forwarded, guessing); @@ -121,12 +118,11 @@ bool Core::MessageHandler::handleGroupMessage(const QXmppMessage& msg, bool outg return false; } - std::map::const_iterator pItr = pendingStateMessages.find(id); - if (pItr != pendingStateMessages.end()) { + std::tuple ids = getOriginalPendingMessageId(msg.id()); + if (std::get<0>(ids)) { QMap cData = {{"state", static_cast(Shared::Message::State::delivered)}}; - cnt->changeMessage(id, cData); - pendingStateMessages.erase(pItr); - emit acc->changeMessage(jid, id, cData); + cnt->changeMessage(std::get<1>(ids), cData); + emit acc->changeMessage(std::get<2>(ids), std::get<1>(ids), cData); } else { QString oId = msg.replaceId(); if (oId.size() > 0) { @@ -227,53 +223,70 @@ void Core::MessageHandler::onCarbonMessageSent(const QXmppMessage& msg) handleChatMessage(msg, true, true); } -void Core::MessageHandler::onReceiptReceived(const QString& jid, const QString& id) +std::tuple Core::MessageHandler::getOriginalPendingMessageId(const QString& id) { + std::tuple result({false, "", ""}); std::map::const_iterator itr = pendingStateMessages.find(id); if (itr != pendingStateMessages.end()) { - QMap cData = {{"state", static_cast(Shared::Message::State::delivered)}}; - RosterItem* ri = acc->rh->getRosterItem(itr->second); - if (ri != 0) { - ri->changeMessage(id, cData); + std::get<0>(result) = true; + std::get<2>(result) = itr->second; + + std::map::const_iterator itrC = pendingCorrectionMessages.find(id); + if (itrC != pendingCorrectionMessages.end()) { + std::get<1>(result) = itrC->second; + pendingCorrectionMessages.erase(itrC); + } else { + std::get<1>(result) = itr->first; } - emit acc->changeMessage(itr->second, id, cData); + pendingStateMessages.erase(itr); } + + return result; } -void Core::MessageHandler::sendMessage(const Shared::Message& data, bool newMessage) +void Core::MessageHandler::onReceiptReceived(const QString& jid, const QString& id) { - if (data.getOutOfBandUrl().size() == 0 && data.getAttachPath().size() > 0) { - prepareUpload(data, newMessage); - } else { - performSending(data, newMessage); + std::tuple ids = getOriginalPendingMessageId(id); + if (std::get<0>(ids)) { + QMap cData = {{"state", static_cast(Shared::Message::State::delivered)}}; + RosterItem* ri = acc->rh->getRosterItem(std::get<2>(ids)); + + if (ri != 0) { + ri->changeMessage(std::get<1>(ids), cData); + } + emit acc->changeMessage(std::get<2>(ids), std::get<1>(ids), cData); } } -void Core::MessageHandler::performSending(Shared::Message data, bool newMessage) +void Core::MessageHandler::sendMessage(const Shared::Message& data, bool newMessage, QString originalId) +{ + if (data.getOutOfBandUrl().size() == 0 && data.getAttachPath().size() > 0) { + pendingCorrectionMessages.insert(std::make_pair(data.getId(), originalId)); + prepareUpload(data, newMessage); + } else { + performSending(data, originalId, newMessage); + } +} + +void Core::MessageHandler::performSending(Shared::Message data, const QString& originalId, bool newMessage) { QString jid = data.getPenPalJid(); QString id = data.getId(); - QString oob = data.getOutOfBandUrl(); + qDebug() << "Sending message with id:" << id; + if (originalId.size() > 0) { + qDebug() << "To replace one with id:" << originalId; + } RosterItem* ri = acc->rh->getRosterItem(jid); bool sent = false; - QMap changes; + if (newMessage && originalId.size() > 0) { + newMessage = false; + } QDateTime sendTime = QDateTime::currentDateTimeUtc(); if (acc->state == Shared::ConnectionState::connected) { - QXmppMessage msg(acc->getFullJid(), data.getTo(), data.getBody(), data.getThread()); - -#if (QXMPP_VERSION) >= QT_VERSION_CHECK(1, 3, 0) - msg.setOriginId(id); -#endif - msg.setId(id); - msg.setType(static_cast(data.getType())); //it is safe here, my type is compatible - msg.setOutOfBandUrl(oob); - msg.setReceiptRequested(true); - msg.setStamp(sendTime); + QXmppMessage msg(createPacket(data, sendTime, originalId)); sent = acc->client.sendPacket(msg); - //sent = false; - if (sent) { data.setState(Shared::Message::State::sent); } else { @@ -286,6 +299,39 @@ void Core::MessageHandler::performSending(Shared::Message data, bool newMessage) data.setErrorText("You are is offline or reconnecting"); } + QMap changes(getChanges(data, sendTime, newMessage, originalId)); + + QString realId; + if (originalId.size() > 0) { + realId = originalId; + } else { + realId = id; + } + if (ri != 0) { + if (newMessage) { + ri->appendMessageToArchive(data); + } else { + ri->changeMessage(realId, changes); + } + if (sent) { + pendingStateMessages.insert(std::make_pair(id, jid)); + if (originalId.size() > 0) { + pendingCorrectionMessages.insert(std::make_pair(id, originalId)); + } + } else { + pendingStateMessages.erase(id); + pendingCorrectionMessages.erase(id); + } + } + + emit acc->changeMessage(jid, realId, changes); +} + +QMap Core::MessageHandler::getChanges(Shared::Message& data, const QDateTime& time, bool newMessage, const QString& originalId) const +{ + QMap changes; + + QString oob = data.getOutOfBandUrl(); Shared::Message::State mstate = data.getState(); changes.insert("state", static_cast(mstate)); if (mstate == Shared::Message::State::error) { @@ -295,9 +341,12 @@ void Core::MessageHandler::performSending(Shared::Message data, bool newMessage) changes.insert("outOfBandUrl", oob); } if (newMessage) { - data.setTime(sendTime); + data.setTime(time); } - changes.insert("stamp", sendTime); + if (originalId.size() > 0) { + changes.insert("body", data.getBody()); + } + changes.insert("stamp", time); //sometimes (when the image is pasted with ctrl+v) //I start sending message with one path, then copy it to downloads directory @@ -310,21 +359,29 @@ void Core::MessageHandler::performSending(Shared::Message data, bool newMessage) data.setAttachPath(squawkified); } } - - if (ri != 0) { - if (newMessage) { - ri->appendMessageToArchive(data); - } else { - ri->changeMessage(id, changes); - } - if (sent) { - pendingStateMessages.insert(std::make_pair(id, jid)); - } else { - pendingStateMessages.erase(id); - } + + return changes; +} + +QXmppMessage Core::MessageHandler::createPacket(const Shared::Message& data, const QDateTime& time, const QString& originalId) const +{ + QXmppMessage msg(acc->getFullJid(), data.getTo(), data.getBody(), data.getThread()); + QString id(data.getId()); + + if (originalId.size() > 0) { + msg.setReplaceId(originalId); } - - emit acc->changeMessage(jid, id, changes); + +#if (QXMPP_VERSION) >= QT_VERSION_CHECK(1, 3, 0) + msg.setOriginId(id); +#endif + msg.setId(id); + msg.setType(static_cast(data.getType())); //it is safe here, my type is compatible + msg.setOutOfBandUrl(data.getOutOfBandUrl()); + msg.setReceiptRequested(true); + msg.setStamp(time); + + return msg; } void Core::MessageHandler::prepareUpload(const Shared::Message& data, bool newMessage) @@ -444,7 +501,8 @@ void Core::MessageHandler::onLoadFileError(const std::list& void Core::MessageHandler::handleUploadError(const QString& jid, const QString& messageId, const QString& errorText) { emit acc->uploadFileError(jid, messageId, "Error requesting slot to upload file: " + errorText); - pendingStateMessages.erase(jid); + pendingStateMessages.erase(messageId); + pendingCorrectionMessages.erase(messageId); requestChangeMessage(jid, messageId, { {"state", static_cast(Shared::Message::State::error)}, {"errorText", errorText} @@ -473,11 +531,11 @@ void Core::MessageHandler::sendMessageWithLocalUploadedFile(Shared::Message msg, if (msg.getBody().size() == 0) { //not sure why, but most messages do that msg.setBody(url); //they duplicate oob in body, some of them wouldn't even show an attachment if you don't do that } - performSending(msg, newMessage); + performSending(msg, pendingCorrectionMessages.at(msg.getId()), newMessage); //TODO removal/progress update } -static const std::set allowerToChangeKeys({ +static const std::set allowedToChangeKeys({ "attachPath", "outOfBandUrl", "state", @@ -490,12 +548,12 @@ void Core::MessageHandler::requestChangeMessage(const QString& jid, const QStrin if (cnt != 0) { bool allSupported = true; QString unsupportedString; - for (QMap::const_iterator itr = data.begin(); itr != data.end(); ++itr) { //I need all this madness - if (allowerToChangeKeys.count(itr.key()) != 1) { //to not allow this method - allSupported = false; //to make a message to look like if it was edited - unsupportedString = itr.key(); //basically I needed to control who exaclty calls this method - break; //because the underlying tech assumes that the change is initiated by user - } //not by system + for (QMap::const_iterator itr = data.begin(); itr != data.end(); ++itr) { //I need all this madness + if (allowedToChangeKeys.count(itr.key()) != 1) { //to not allow this method + allSupported = false; //to make a message to look like if it was edited + unsupportedString = itr.key(); //basically I needed to control who exaclty calls this method + break; //because the underlying tech assumes that + } //the change is initiated by user, not by system } if (allSupported) { cnt->changeMessage(messageId, data); @@ -514,7 +572,13 @@ void Core::MessageHandler::resendMessage(const QString& jid, const QString& id) try { Shared::Message msg = cnt->getMessage(id); if (msg.getState() == Shared::Message::State::error) { - sendMessage(msg, false); + if (msg.getEdited()){ + QString originalId = msg.getId(); + msg.generateRandomId(); + sendMessage(msg, false, originalId); + } else { + sendMessage(msg, false); + } } else { qDebug() << "An attempt to resend a message to" << jid << "by account" << acc->getName() << ", but this message seems to have been normally sent, this method was made to retry sending failed to be sent messages, skipping"; } diff --git a/core/handlers/messagehandler.h b/core/handlers/messagehandler.h index 4f03484..1ab2d0d 100644 --- a/core/handlers/messagehandler.h +++ b/core/handlers/messagehandler.h @@ -46,7 +46,7 @@ public: MessageHandler(Account* account); public: - void sendMessage(const Shared::Message& data, bool newMessage = true); + void sendMessage(const Shared::Message& data, bool newMessage = true, QString originalId = ""); void initializeMessage(Shared::Message& target, const QXmppMessage& source, bool outgoing = false, bool forwarded = false, bool guessing = false) const; void resendMessage(const QString& jid, const QString& id); @@ -67,13 +67,17 @@ private: bool handleGroupMessage(const QXmppMessage& msg, bool outgoing = false, bool forwarded = false, bool guessing = false); void logMessage(const QXmppMessage& msg, const QString& reason = "Message wasn't handled: "); void sendMessageWithLocalUploadedFile(Shared::Message msg, const QString& url, bool newMessage = true); - void performSending(Shared::Message data, bool newMessage = true); + void performSending(Shared::Message data, const QString& originalId, bool newMessage = true); void prepareUpload(const Shared::Message& data, bool newMessage = true); void handleUploadError(const QString& jid, const QString& messageId, const QString& errorText); + QXmppMessage createPacket(const Shared::Message& data, const QDateTime& time, const QString& originalId) const; + QMap getChanges(Shared::Message& data, const QDateTime& time, bool newMessage, const QString& originalId) const; + std::tuple getOriginalPendingMessageId(const QString& id); private: Account* acc; std::map pendingStateMessages; //key is message id, value is JID + std::map pendingCorrectionMessages; //key is new mesage, value is originalOne std::deque> uploadingSlotsQueue; }; diff --git a/core/main.cpp b/core/main.cpp index 79ca648..f842c80 100644 --- a/core/main.cpp +++ b/core/main.cpp @@ -142,6 +142,7 @@ int main(int argc, char *argv[]) QObject::connect(&w, &Squawk::disconnectAccount, squawk, &Core::Squawk::disconnectAccount); QObject::connect(&w, &Squawk::changeState, squawk, &Core::Squawk::changeState); QObject::connect(&w, &Squawk::sendMessage, squawk,&Core::Squawk::sendMessage); + QObject::connect(&w, &Squawk::replaceMessage, squawk,&Core::Squawk::replaceMessage); QObject::connect(&w, &Squawk::resendMessage, squawk,&Core::Squawk::resendMessage); QObject::connect(&w, &Squawk::requestArchive, squawk, &Core::Squawk::requestArchive); QObject::connect(&w, &Squawk::subscribeContact, squawk, &Core::Squawk::subscribeContact); diff --git a/core/squawk.cpp b/core/squawk.cpp index 9f2b445..af131d5 100644 --- a/core/squawk.cpp +++ b/core/squawk.cpp @@ -341,6 +341,17 @@ void Core::Squawk::sendMessage(const QString& account, const Shared::Message& da itr->second->sendMessage(data); } +void Core::Squawk::replaceMessage(const QString& account, const QString& originalId, const Shared::Message& data) +{ + AccountsMap::const_iterator itr = amap.find(account); + if (itr == amap.end()) { + qDebug() << "An attempt to replace a message with non existing account" << account << ", skipping"; + return; + } + + itr->second->replaceMessage(originalId, data); +} + void Core::Squawk::resendMessage(const QString& account, const QString& jid, const QString& id) { AccountsMap::const_iterator itr = amap.find(account); diff --git a/core/squawk.h b/core/squawk.h index 738a957..6cd251f 100644 --- a/core/squawk.h +++ b/core/squawk.h @@ -101,6 +101,7 @@ public slots: void changeState(Shared::Availability state); void sendMessage(const QString& account, const Shared::Message& data); + void replaceMessage(const QString& account, const QString& originalId, const Shared::Message& data); void resendMessage(const QString& account, const QString& jid, const QString& id); void requestArchive(const QString& account, const QString& jid, int count, const QString& before); diff --git a/shared/message.cpp b/shared/message.cpp index f3f6b45..0e1b3c5 100644 --- a/shared/message.cpp +++ b/shared/message.cpp @@ -404,7 +404,9 @@ bool Shared::Message::change(const QMap& data) correctionDate = QDateTime::currentDateTimeUtc(); //in case there is no information about time of this correction it's applied } if (!edited || lastModified < correctionDate) { - originalMessage = body; + if (!edited) { + originalMessage = body; + } lastModified = correctionDate; setBody(b); setEdited(true); diff --git a/ui/squawk.cpp b/ui/squawk.cpp index e24640a..3ebb6a5 100644 --- a/ui/squawk.cpp +++ b/ui/squawk.cpp @@ -497,6 +497,17 @@ void Squawk::onConversationMessage(const Shared::Message& msg) emit sendMessage(acc, msg); } +void Squawk::onConversationReplaceMessage(const QString& originalId, const Shared::Message& msg) +{ + Conversation* conv = static_cast(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()); @@ -958,6 +969,7 @@ void Squawk::subscribeConversation(Conversation* conv) { connect(conv, &Conversation::destroyed, this, &Squawk::onConversationClosed); connect(conv, &Conversation::sendMessage, this, &Squawk::onConversationMessage); + connect(conv, &Conversation::replaceMessage, this, &Squawk::onConversationReplaceMessage); connect(conv, &Conversation::resendMessage, this, &Squawk::onConversationResend); connect(conv, &Conversation::notifyableMessage, this, &Squawk::notify); } diff --git a/ui/squawk.h b/ui/squawk.h index 7551f66..7bd2e10 100644 --- a/ui/squawk.h +++ b/ui/squawk.h @@ -62,6 +62,7 @@ signals: void disconnectAccount(const QString&); void changeState(Shared::Availability state); void sendMessage(const QString& account, const Shared::Message& data); + void replaceMessage(const QString& account, const QString& originalId, const Shared::Message& data); void resendMessage(const QString& account, const QString& jid, const QString& id); void requestArchive(const QString& account, const QString& jid, int count, const QString& before); void subscribeContact(const QString& account, const QString& jid, const QString& reason); @@ -153,6 +154,7 @@ private slots: void onComboboxActivated(int index); void onRosterItemDoubleClicked(const QModelIndex& item); void onConversationMessage(const Shared::Message& msg); + void onConversationReplaceMessage(const QString& originalId, const Shared::Message& msg); void onConversationResend(const QString& id); void onRequestArchive(const QString& account, const QString& jid, const QString& before); void onRosterContextMenu(const QPoint& point); diff --git a/ui/widgets/conversation.cpp b/ui/widgets/conversation.cpp index 608faf3..02aefb4 100644 --- a/ui/widgets/conversation.cpp +++ b/ui/widgets/conversation.cpp @@ -58,7 +58,8 @@ Conversation::Conversation(bool muc, Models::Account* acc, Models::Element* el, pasteImageAction(new QAction(tr("Paste Image"), this)), shadow(10, 1, Qt::black, this), contextMenu(new QMenu()), - currentAction(CurrentAction::none) + currentAction(CurrentAction::none), + currentMessageId() { m_ui->setupUi(this); @@ -84,11 +85,11 @@ Conversation::Conversation(bool muc, Models::Account* acc, Models::Element* el, statusIcon = m_ui->statusIcon; statusLabel = m_ui->statusLabel; - connect(&ker, &KeyEnterReceiver::enterPressed, this, &Conversation::onEnterPressed); + connect(&ker, &KeyEnterReceiver::enterPressed, this, qOverload<>(&Conversation::initiateMessageSending)); connect(&ker, &KeyEnterReceiver::imagePasted, this, &Conversation::onImagePasted); - connect(m_ui->sendButton, &QPushButton::clicked, this, &Conversation::onEnterPressed); + connect(m_ui->sendButton, &QPushButton::clicked, this, qOverload<>(&Conversation::initiateMessageSending)); connect(m_ui->attachButton, &QPushButton::clicked, this, &Conversation::onAttach); - connect(m_ui->clearButton, &QPushButton::clicked, this, &Conversation::onClearButton); + connect(m_ui->clearButton, &QPushButton::clicked, this, &Conversation::clear); connect(m_ui->messageEditor->document()->documentLayout(), &QAbstractTextDocumentLayout::documentSizeChanged, this, &Conversation::onTextEditDocSizeChanged); @@ -98,6 +99,9 @@ Conversation::Conversation(bool muc, Models::Account* acc, Models::Element* el, connect(m_ui->messageEditor, &QTextEdit::customContextMenuRequested, this, &Conversation::onMessageEditorContext); connect(pasteImageAction, &QAction::triggered, this, &Conversation::onImagePasted); + connect(m_ui->currentActionBadge, &Badge::close, this, &Conversation::clear); + m_ui->currentActionBadge->setVisible(false); + //line->setAutoFillBackground(false); //if (testAttribute(Qt::WA_TranslucentBackground)) { //m_ui->scrollArea->setAutoFillBackground(false); @@ -109,9 +113,6 @@ Conversation::Conversation(bool muc, Models::Account* acc, Models::Element* el, //line->setMyName(acc->getName()); initializeOverlay(); - - m_ui->currentActionBadge->setVisible(false); -// m_ui->currentActionBadge->setText(tr("Editing message...")); } Conversation::~Conversation() @@ -224,24 +225,33 @@ void Conversation::setPalResource(const QString& res) activePalResource = res; } -void Conversation::onEnterPressed() +void Conversation::initiateMessageSending() { QString body(m_ui->messageEditor->toPlainText()); if (body.size() > 0) { - m_ui->messageEditor->clear(); Shared::Message msg = createMessage(); msg.setBody(body); - emit sendMessage(msg); + initiateMessageSending(msg); } if (filesToAttach.size() > 0) { for (Badge* badge : filesToAttach) { Shared::Message msg = createMessage(); msg.setAttachPath(badge->id); element->feed->registerUpload(msg.getId()); - emit sendMessage(msg); + initiateMessageSending(msg); } - clearAttachedFiles(); + } + clear(); +} + +void Conversation::initiateMessageSending(const Shared::Message& msg) +{ + if (currentAction == CurrentAction::edit) { + emit replaceMessage(currentMessageId, msg); + currentAction = CurrentAction::none; + } else { + emit sendMessage(msg); } } @@ -348,8 +358,11 @@ void Conversation::clearAttachedFiles() filesLayout->setContentsMargins(0, 0, 0, 0); } -void Conversation::onClearButton() +void Conversation::clear() { + currentMessageId.clear(); + currentAction = CurrentAction::none; + m_ui->currentActionBadge->setVisible(false); clearAttachedFiles(); m_ui->messageEditor->clear(); } @@ -526,13 +539,12 @@ void Conversation::onMessageEditorContext(const QPoint& pos) void Conversation::onMessageEditRequested(const QString& id) { - if (currentAction == CurrentAction::edit) { - //todo; - } + clear(); try { Shared::Message msg = element->feed->getMessage(id); + currentMessageId = id; m_ui->currentActionBadge->setVisible(true); m_ui->currentActionBadge->setText(tr("Editing message...")); currentAction = CurrentAction::edit; diff --git a/ui/widgets/conversation.h b/ui/widgets/conversation.h index 4bccdfc..743df71 100644 --- a/ui/widgets/conversation.h +++ b/ui/widgets/conversation.h @@ -83,6 +83,7 @@ public: signals: void sendMessage(const Shared::Message& message); + void replaceMessage(const QString& originalId, const Shared::Message& message); void resendMessage(const QString& id); void requestArchive(const QString& before); void shown(); @@ -104,12 +105,13 @@ protected: virtual void onMessage(const Shared::Message& msg); protected slots: - void onEnterPressed(); + void initiateMessageSending(); + void initiateMessageSending(const Shared::Message& msg); void onImagePasted(); void onAttach(); void onFileSelected(); void onBadgeClose(); - void onClearButton(); + void clear(); void onTextEditDocSizeChanged(const QSizeF& size); void onAccountChanged(Models::Item* item, int row, int col); void onFeedMessage(const Shared::Message& msg); @@ -149,6 +151,7 @@ protected: ShadowOverlay shadow; QMenu* contextMenu; CurrentAction currentAction; + QString currentMessageId; private: static bool painterInitialized; From 5f6691067a18e503caf84122d59b59409bfbb5b4 Mon Sep 17 00:00:00 2001 From: blue Date: Tue, 29 Mar 2022 19:05:24 +0300 Subject: [PATCH 62/93] minor bugfixes about message body, automatic focus and that quirk with font becomming bigger --- CHANGELOG.md | 3 +++ ui/widgets/conversation.cpp | 9 ++++++++- ui/widgets/conversation.h | 1 + ui/widgets/conversation.ui | 6 +++++- ui/widgets/messageline/messagedelegate.cpp | 1 + 5 files changed, 18 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index efb159c..83c759e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,9 +5,12 @@ - build in release mode now no longer spams warnings - build now correctly installs all build plugin libs - a bug where the correction message was received, the indication was on but the text didn't actually change +- message body now doesn't intecept context menu from the whole message +- message input now doesn't increase font when you remove everything from it ### Improvements - reduced amount of places where platform specific path separator is used +- now message input is automatically focused when you open a dialog or a room ### New features - the settings are here! You con config different stuff from there diff --git a/ui/widgets/conversation.cpp b/ui/widgets/conversation.cpp index 02aefb4..da3d187 100644 --- a/ui/widgets/conversation.cpp +++ b/ui/widgets/conversation.cpp @@ -94,7 +94,6 @@ Conversation::Conversation(bool muc, Models::Account* acc, Models::Element* el, this, &Conversation::onTextEditDocSizeChanged); m_ui->messageEditor->installEventFilter(&ker); - m_ui->messageEditor->setContextMenuPolicy(Qt::CustomContextMenu); connect(m_ui->messageEditor, &QTextEdit::customContextMenuRequested, this, &Conversation::onMessageEditorContext); connect(pasteImageAction, &QAction::triggered, this, &Conversation::onImagePasted); @@ -555,3 +554,11 @@ void Conversation::onMessageEditRequested(const QString& id) qDebug() << "Ignoring"; } } + +void Conversation::showEvent(QShowEvent* event) +{ + QWidget::showEvent(event); + + emit shown(); + m_ui->messageEditor->setFocus(); +} diff --git a/ui/widgets/conversation.h b/ui/widgets/conversation.h index 743df71..0c44bd9 100644 --- a/ui/widgets/conversation.h +++ b/ui/widgets/conversation.h @@ -103,6 +103,7 @@ protected: void dropEvent(QDropEvent* event) override; void initializeOverlay(); virtual void onMessage(const Shared::Message& msg); + virtual void showEvent(QShowEvent * event) override; protected slots: void initiateMessageSending(); diff --git a/ui/widgets/conversation.ui b/ui/widgets/conversation.ui index ce9ad66..1f8b483 100644 --- a/ui/widgets/conversation.ui +++ b/ui/widgets/conversation.ui @@ -402,6 +402,9 @@ 30 + + Qt::CustomContextMenu + false @@ -424,7 +427,7 @@ background-color: transparent <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; font-family:'Liberation Sans'; font-size:10pt;"><br /></p></body></html> +<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> false @@ -447,6 +450,7 @@ p, li { white-space: pre-wrap; } Badge QFrame
ui/utils/badge.h
+ 1 diff --git a/ui/widgets/messageline/messagedelegate.cpp b/ui/widgets/messageline/messagedelegate.cpp index 22e8dcb..15a5e46 100644 --- a/ui/widgets/messageline/messagedelegate.cpp +++ b/ui/widgets/messageline/messagedelegate.cpp @@ -582,6 +582,7 @@ QLabel * MessageDelegate::getBody(const Models::FeedItem& data) const } else { result = new QLabel(); result->setFont(bodyFont); + result->setContextMenuPolicy(Qt::NoContextMenu); result->setWordWrap(true); result->setOpenExternalLinks(true); result->setTextInteractionFlags(result->textInteractionFlags() | Qt::TextSelectableByMouse | Qt::LinksAccessibleByMouse); From 1fcd403dbaa841ceb2f3f70bfad439440810bd30 Mon Sep 17 00:00:00 2001 From: blue Date: Fri, 1 Apr 2022 00:32:22 +0300 Subject: [PATCH 63/93] testing, solved unhandled exception, conditions to restrict old message to be edited, license un some files that used to miss them --- core/handlers/messagehandler.cpp | 6 +++++- core/networkaccess.cpp | 2 +- .../wrappers/kwallet.cpp | 18 ++++++++++++++++++ plugins/colorschemetools.cpp | 18 ++++++++++++++++++ plugins/openfilemanagerwindowjob.cpp | 18 ++++++++++++++++++ shared/exception.cpp | 3 ++- shared/pathcheck.cpp | 18 ++++++++++++++++++ shared/pathcheck.h | 18 ++++++++++++++++++ ui/widgets/conversation.cpp | 3 ++- ui/widgets/settings/pageappearance.cpp | 18 ++++++++++++++++++ ui/widgets/settings/pageappearance.h | 18 ++++++++++++++++++ ui/widgets/settings/pagegeneral.cpp | 18 ++++++++++++++++++ ui/widgets/settings/pagegeneral.h | 18 ++++++++++++++++++ ui/widgets/settings/settings.cpp | 18 ++++++++++++++++++ ui/widgets/settings/settings.h | 18 ++++++++++++++++++ ui/widgets/settings/settingslist.cpp | 18 ++++++++++++++++++ ui/widgets/settings/settingslist.h | 18 ++++++++++++++++++ 17 files changed, 244 insertions(+), 4 deletions(-) diff --git a/core/handlers/messagehandler.cpp b/core/handlers/messagehandler.cpp index 559bee3..0555873 100644 --- a/core/handlers/messagehandler.cpp +++ b/core/handlers/messagehandler.cpp @@ -233,7 +233,11 @@ std::tuple Core::MessageHandler::getOriginalPendingMessa std::map::const_iterator itrC = pendingCorrectionMessages.find(id); if (itrC != pendingCorrectionMessages.end()) { - std::get<1>(result) = itrC->second; + if (itrC->second.size() > 0) { + std::get<1>(result) = itrC->second; + } else { + std::get<1>(result) = itr->first; + } pendingCorrectionMessages.erase(itrC); } else { std::get<1>(result) = itr->first; diff --git a/core/networkaccess.cpp b/core/networkaccess.cpp index 7c55e19..22bb7a2 100644 --- a/core/networkaccess.cpp +++ b/core/networkaccess.cpp @@ -443,7 +443,7 @@ QString Core::NetworkAccess::getFileRemoteUrl(const QString& path) try { p = storage.getUrl(p); } catch (const Archive::NotFound& err) { - + p = ""; } catch (...) { throw; } diff --git a/core/passwordStorageEngines/wrappers/kwallet.cpp b/core/passwordStorageEngines/wrappers/kwallet.cpp index f5e7cb5..d899985 100644 --- a/core/passwordStorageEngines/wrappers/kwallet.cpp +++ b/core/passwordStorageEngines/wrappers/kwallet.cpp @@ -1,3 +1,21 @@ +/* + * Squawk messenger. + * Copyright (C) 2019 Yury Gubich + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 extern "C" KWallet::Wallet* openWallet(const QString &name, WId w, KWallet::Wallet::OpenType ot = KWallet::Wallet::Synchronous) { diff --git a/plugins/colorschemetools.cpp b/plugins/colorschemetools.cpp index 0288b28..ea2c23e 100644 --- a/plugins/colorschemetools.cpp +++ b/plugins/colorschemetools.cpp @@ -1,3 +1,21 @@ +/* + * Squawk messenger. + * Copyright (C) 2019 Yury Gubich + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 #include #include diff --git a/plugins/openfilemanagerwindowjob.cpp b/plugins/openfilemanagerwindowjob.cpp index 904fbcf..4335410 100644 --- a/plugins/openfilemanagerwindowjob.cpp +++ b/plugins/openfilemanagerwindowjob.cpp @@ -1,3 +1,21 @@ +/* + * Squawk messenger. + * Copyright (C) 2019 Yury Gubich + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 #include #include diff --git a/shared/exception.cpp b/shared/exception.cpp index 342593c..3dee9b3 100644 --- a/shared/exception.cpp +++ b/shared/exception.cpp @@ -28,5 +28,6 @@ Utils::Exception::~Exception() const char* Utils::Exception::what() const noexcept( true ) { - return getMessage().c_str(); + std::string* msg = new std::string(getMessage()); + return msg->c_str(); } diff --git a/shared/pathcheck.cpp b/shared/pathcheck.cpp index 1929387..c32f96c 100644 --- a/shared/pathcheck.cpp +++ b/shared/pathcheck.cpp @@ -1,3 +1,21 @@ +/* + * Squawk messenger. + * Copyright (C) 2019 Yury Gubich + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 "pathcheck.h" QRegularExpression squawk("^squawk:\\/\\/"); diff --git a/shared/pathcheck.h b/shared/pathcheck.h index 62dcaeb..3ca612b 100644 --- a/shared/pathcheck.h +++ b/shared/pathcheck.h @@ -1,3 +1,21 @@ +/* + * Squawk messenger. + * Copyright (C) 2019 Yury Gubich + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 PATHCHECK_H #define PATHCHECK_H diff --git a/ui/widgets/conversation.cpp b/ui/widgets/conversation.cpp index da3d187..ab5f0c5 100644 --- a/ui/widgets/conversation.cpp +++ b/ui/widgets/conversation.cpp @@ -513,7 +513,8 @@ void Conversation::onFeedContext(const QPoint& pos) }); } - if (item->getOutgoing()) { + //the only mandatory condition - is for the message to be outgoing, the rest is just a good intention on the server + if (item->getOutgoing() && index.row() < 100 && item->getTime().daysTo(QDateTime::currentDateTimeUtc()) < 20) { showMenu = true; QAction* edit = contextMenu->addAction(Shared::icon("edit-rename"), tr("Edit")); connect(edit, &QAction::triggered, this, std::bind(&Conversation::onMessageEditRequested, this, id)); diff --git a/ui/widgets/settings/pageappearance.cpp b/ui/widgets/settings/pageappearance.cpp index f2bf53e..64d6de4 100644 --- a/ui/widgets/settings/pageappearance.cpp +++ b/ui/widgets/settings/pageappearance.cpp @@ -1,3 +1,21 @@ +/* + * Squawk messenger. + * Copyright (C) 2019 Yury Gubich + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 "pageappearance.h" #include "ui_pageappearance.h" diff --git a/ui/widgets/settings/pageappearance.h b/ui/widgets/settings/pageappearance.h index 80efd85..c182ea2 100644 --- a/ui/widgets/settings/pageappearance.h +++ b/ui/widgets/settings/pageappearance.h @@ -1,3 +1,21 @@ +/* + * Squawk messenger. + * Copyright (C) 2019 Yury Gubich + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 PAGEAPPEARANCE_H #define PAGEAPPEARANCE_H diff --git a/ui/widgets/settings/pagegeneral.cpp b/ui/widgets/settings/pagegeneral.cpp index a546bd0..9ed46a2 100644 --- a/ui/widgets/settings/pagegeneral.cpp +++ b/ui/widgets/settings/pagegeneral.cpp @@ -1,3 +1,21 @@ +/* + * Squawk messenger. + * Copyright (C) 2019 Yury Gubich + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 "pagegeneral.h" #include "ui_pagegeneral.h" diff --git a/ui/widgets/settings/pagegeneral.h b/ui/widgets/settings/pagegeneral.h index ec00bba..7f58d71 100644 --- a/ui/widgets/settings/pagegeneral.h +++ b/ui/widgets/settings/pagegeneral.h @@ -1,3 +1,21 @@ +/* + * Squawk messenger. + * Copyright (C) 2019 Yury Gubich + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 PAGEGENERAL_H #define PAGEGENERAL_H diff --git a/ui/widgets/settings/settings.cpp b/ui/widgets/settings/settings.cpp index 27401bb..cf5e905 100644 --- a/ui/widgets/settings/settings.cpp +++ b/ui/widgets/settings/settings.cpp @@ -1,3 +1,21 @@ +/* + * Squawk messenger. + * Copyright (C) 2019 Yury Gubich + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 "settings.h" #include "ui_settings.h" diff --git a/ui/widgets/settings/settings.h b/ui/widgets/settings/settings.h index 5a6b37c..689e0ce 100644 --- a/ui/widgets/settings/settings.h +++ b/ui/widgets/settings/settings.h @@ -1,3 +1,21 @@ +/* + * Squawk messenger. + * Copyright (C) 2019 Yury Gubich + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 SETTINGS_H #define SETTINGS_H diff --git a/ui/widgets/settings/settingslist.cpp b/ui/widgets/settings/settingslist.cpp index 3a5e2cb..ee2e3ed 100644 --- a/ui/widgets/settings/settingslist.cpp +++ b/ui/widgets/settings/settingslist.cpp @@ -1,3 +1,21 @@ +/* + * Squawk messenger. + * Copyright (C) 2019 Yury Gubich + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 "settingslist.h" SettingsList::SettingsList(QWidget* parent): diff --git a/ui/widgets/settings/settingslist.h b/ui/widgets/settings/settingslist.h index a51fc3a..64c9d57 100644 --- a/ui/widgets/settings/settingslist.h +++ b/ui/widgets/settings/settingslist.h @@ -1,3 +1,21 @@ +/* + * Squawk messenger. + * Copyright (C) 2019 Yury Gubich + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General 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 UI_SETTINGSLIST_H #define UI_SETTINGSLIST_H From 62f02c18d785582e493a8aedd9a46413ab14212a Mon Sep 17 00:00:00 2001 From: blue Date: Sat, 2 Apr 2022 15:34:36 +0300 Subject: [PATCH 64/93] now you can't edit messages with attachments: no other client actually allowes that, and if I edit they don't handle it properly anyway --- ui/widgets/conversation.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/ui/widgets/conversation.cpp b/ui/widgets/conversation.cpp index ab5f0c5..4e5e007 100644 --- a/ui/widgets/conversation.cpp +++ b/ui/widgets/conversation.cpp @@ -513,8 +513,9 @@ void Conversation::onFeedContext(const QPoint& pos) }); } + bool hasAttach = item->getAttachPath() > 0 || item->getOutOfBandUrl() > 0; //the only mandatory condition - is for the message to be outgoing, the rest is just a good intention on the server - if (item->getOutgoing() && index.row() < 100 && item->getTime().daysTo(QDateTime::currentDateTimeUtc()) < 20) { + if (item->getOutgoing() && !hasAttach && index.row() < 100 && item->getTime().daysTo(QDateTime::currentDateTimeUtc()) < 20) { showMenu = true; QAction* edit = contextMenu->addAction(Shared::icon("edit-rename"), tr("Edit")); connect(edit, &QAction::triggered, this, std::bind(&Conversation::onMessageEditRequested, this, id)); @@ -549,6 +550,10 @@ void Conversation::onMessageEditRequested(const QString& id) m_ui->currentActionBadge->setText(tr("Editing message...")); currentAction = CurrentAction::edit; m_ui->messageEditor->setText(msg.getBody()); + QString path = msg.getAttachPath(); + if (path.size() > 0) { + addAttachedFile(path); + } } catch (const Models::MessageFeed::NotFound& e) { qDebug() << "The message requested to be edited was not found" << e.getMessage().c_str(); From 4786388822f74f225e993ac1d0f86cb2b5df9f3b Mon Sep 17 00:00:00 2001 From: blue Date: Sat, 2 Apr 2022 15:53:23 +0300 Subject: [PATCH 65/93] 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 } From 4baa3bccbf80d4b861661d4986b94a4114f9edba Mon Sep 17 00:00:00 2001 From: blue Date: Sat, 2 Apr 2022 16:09:11 +0300 Subject: [PATCH 66/93] new screenshot --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 486d4fe..5845c46 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ [![AUR version](https://img.shields.io/aur/version/squawk?style=flat-square)](https://aur.archlinux.org/packages/squawk/) [![Liberapay patrons](https://img.shields.io/liberapay/patrons/macaw.me?logo=liberapay&style=flat-square)](https://liberapay.com/macaw.me) -![Squawk screenshot](https://macaw.me/images/squawk/0.2.0.png) +![Squawk screenshot](https://macaw.me/images/squawk/0.2.1.png) ### Prerequisites From 27377e0ec51fad161165649bd0fe92d23f25bbd0 Mon Sep 17 00:00:00 2001 From: blue Date: Sun, 3 Apr 2022 23:53:46 +0300 Subject: [PATCH 67/93] 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 + + + + + + + + + + From 9f746d203b6af51431587c38f81870b83cc3081f Mon Sep 17 00:00:00 2001 From: blue Date: Mon, 4 Apr 2022 23:49:01 +0300 Subject: [PATCH 68/93] 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 From 1b66fda3181b38bf4864fb1591291e3391aa03fb Mon Sep 17 00:00:00 2001 From: blue Date: Tue, 5 Apr 2022 22:00:56 +0300 Subject: [PATCH 69/93] 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; From 82d54ba4df869981559ba835c836c62112f8f642 Mon Sep 17 00:00:00 2001 From: blue Date: Thu, 7 Apr 2022 18:26:43 +0300 Subject: [PATCH 70/93] 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 + + + + + + + From 69e0c88d8d278925c005418fca0b2caa33ce0405 Mon Sep 17 00:00:00 2001 From: blue Date: Fri, 8 Apr 2022 19:18:15 +0300 Subject: [PATCH 71/93] 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 { From 2c26c7e264922532b37ed94eb366472c85c75062 Mon Sep 17 00:00:00 2001 From: blue Date: Mon, 11 Apr 2022 18:45:12 +0300 Subject: [PATCH 72/93] 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 From f64e5c2df079aedcfb452be226930339f5fdac72 Mon Sep 17 00:00:00 2001 From: blue Date: Tue, 12 Apr 2022 23:33:10 +0300 Subject: [PATCH 73/93] 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); } From ce686e121b7f29b3fe5d9c05c85f205185734089 Mon Sep 17 00:00:00 2001 From: blue Date: Wed, 13 Apr 2022 22:02:48 +0300 Subject: [PATCH 74/93] account removal bugfix, some testing --- CHANGELOG.md | 9 ++++++++- core/squawk.cpp | 12 ++++++++---- ui/squawk.cpp | 2 +- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f563c85..410ff11 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,11 +2,18 @@ ## Squawk 0.2.2 (UNRELEASED) ### Bug fixes +- now when you remove an account it actually gets removed +- segfault on unitialized Availability in some rare occesions ### Improvements +- there is a way to disable an account and it wouldn't connect when you change availability +- if you cancel password query an account becomes inactive and doesn't annoy you anymore +- if you filled password field and chose KWallet as a storage Squawk wouldn't ask you again for the same password +- if left the password field empty and chose KWallet as a storage Squawk will try to get that passord from KWallet before asking you to input it +- accounts now connect to the server asyncronously - if one is stopped on password prompt another is connecting ### New features - +- new "About" window with links, license, gratitudes ## Squawk 0.2.1 (Apr 02, 2022) ### Bug fixes diff --git a/core/squawk.cpp b/core/squawk.cpp index d594553..49a2b34 100644 --- a/core/squawk.cpp +++ b/core/squawk.cpp @@ -139,8 +139,10 @@ void Core::Squawk::addAccount( bool active, Shared::AccountPassword passwordType) { - QSettings settings; - + if (amap.count(name) > 0) { + qDebug() << "An attempt to add account" << name << "but an account with such name already exist, ignoring"; + return; + } Account* acc = new Account(login, server, password, name, active, &network); acc->setResource(resource); acc->setPasswordType(passwordType); @@ -198,8 +200,10 @@ void Core::Squawk::addAccount( switch (passwordType) { case Shared::AccountPassword::alwaysAsk: case Shared::AccountPassword::kwallet: - acc->invalidatePassword(); - break; + if (password == "") { + acc->invalidatePassword(); + break; + } default: break; } diff --git a/ui/squawk.cpp b/ui/squawk.cpp index 3a3d1d9..a447458 100644 --- a/ui/squawk.cpp +++ b/ui/squawk.cpp @@ -564,7 +564,7 @@ void Squawk::onRosterContextMenu(const QPoint& point) connect(card, &QAction::triggered, std::bind(&Squawk::onActivateVCard, this, name, acc->getBareJid(), true)); QAction* remove = contextMenu->addAction(Shared::icon("edit-delete"), tr("Remove")); - connect(remove, &QAction::triggered, std::bind(&Squawk::removeAccount, this, name)); + connect(remove, &QAction::triggered, std::bind(&Squawk::removeAccountRequest, this, name)); } break; case Models::Item::contact: { From 8f949277f68be1f4baeb282b3c483948400dd6ca Mon Sep 17 00:00:00 2001 From: blue Date: Thu, 14 Apr 2022 11:13:27 +0300 Subject: [PATCH 75/93] 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 + + + + + From 51ac1ac709042cb61f122860de5f47c8f82e8e63 Mon Sep 17 00:00:00 2001 From: blue Date: Sun, 17 Apr 2022 14:58:46 +0300 Subject: [PATCH 76/93] 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; From 4c20a314f050236fbcb79fa2d43322ec9045eaa1 Mon Sep 17 00:00:00 2001 From: blue Date: Sun, 17 Apr 2022 16:25:15 +0300 Subject: [PATCH 77/93] a crash fix on one of archive corner cases --- CHANGELOG.md | 1 + core/rosteritem.cpp | 28 ++++++++++++++++++---------- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 55ef1f1..4daf652 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Bug fixes - now when you remove an account it actually gets removed - segfault on unitialized Availability in some rare occesions +- fixed crash when you open a dialog with someone that has only error messages in archive ### Improvements - there is a way to disable an account and it wouldn't connect when you change availability diff --git a/core/rosteritem.cpp b/core/rosteritem.cpp index b1951d6..1b8d1e6 100644 --- a/core/rosteritem.cpp +++ b/core/rosteritem.cpp @@ -124,15 +124,19 @@ void Core::RosterItem::nextRequest() if (requestedCount != -1) { bool last = false; if (archiveState == beginning || archiveState == complete) { - QString firstId = archive->oldestId(); - if (responseCache.size() == 0) { - if (requestedBefore == firstId) { - last = true; - } - } else { - if (responseCache.front().getId() == firstId) { - last = true; + try { + QString firstId = archive->oldestId(); + if (responseCache.size() == 0) { + if (requestedBefore == firstId) { + last = true; + } + } else { + if (responseCache.front().getId() == firstId) { + last = true; + } } + } catch (const Archive::Empty& e) { + last = true; } } else if (archiveState == empty && responseCache.size() == 0) { last = true; @@ -171,8 +175,12 @@ void Core::RosterItem::performRequest(int count, const QString& before) requestCache.emplace_back(requestedCount, before); requestedCount = -1; } - Shared::Message msg = archive->newest(); - emit needHistory("", getId(msg), msg.getTime()); + try { + Shared::Message msg = archive->newest(); + emit needHistory("", getId(msg), msg.getTime()); + } catch (const Archive::Empty& e) { //this can happen when the only message in archive is not server stored (error, for example) + emit needHistory(before, ""); + } } break; case end: From 18859cb960aa6de0dbb393186392b3449b5177ff Mon Sep 17 00:00:00 2001 From: blue Date: Mon, 18 Apr 2022 19:54:42 +0300 Subject: [PATCH 78/93] first ideas for notifications --- shared/global.cpp | 35 +++++++++++++++++++++++++++++++++++ shared/global.h | 14 ++++++++++++-- ui/models/roster.cpp | 4 ++-- ui/models/roster.h | 4 ++-- ui/squawk.cpp | 31 ++----------------------------- ui/squawk.h | 5 ++--- 6 files changed, 55 insertions(+), 38 deletions(-) diff --git a/shared/global.cpp b/shared/global.cpp index 122bc79..14ae90d 100644 --- a/shared/global.cpp +++ b/shared/global.cpp @@ -19,6 +19,7 @@ #include "global.h" #include "enums.h" +#include "ui/models/roster.h" Shared::Global* Shared::Global::instance = 0; const std::set Shared::Global::supportedImagesExts = {"png", "jpg", "webp", "jpeg", "gif", "svg"}; @@ -94,6 +95,8 @@ Shared::Global::Global(): }), defaultSystemStyle(QApplication::style()->objectName()), defaultSystemPalette(QApplication::palette()), + rosterModel(new Models::Roster()), + dbus("org.freedesktop.Notifications", "/org/freedesktop/Notifications", "org.freedesktop.Notifications", QDBusConnection::sessionBus()), pluginSupport({ {"KWallet", false}, {"openFileManagerWindowJob", false}, @@ -349,6 +352,38 @@ void Shared::Global::setStyle(const QString& style) } } +void Shared::Global::notify(const QString& account, const Shared::Message& msg) +{ + QString name = QString(instance->rosterModel->getContactName(account, msg.getPenPalJid())); + QString path = QString(instance->rosterModel->getContactIconPath(account, msg.getPenPalJid(), msg.getPenPalResource())); + QVariantList args; + args << QString(QCoreApplication::applicationName()); + args << QVariant(QVariant::UInt); //TODO some normal id + if (path.size() > 0) { + args << path; + } else { + args << QString("mail-message"); //TODO should here better be unknown user icon? + } + if (msg.getType() == Shared::Message::groupChat) { + args << msg.getFromResource() + " from " + name; + } else { + args << name; + } + + QString body(msg.getBody()); + QString oob(msg.getOutOfBandUrl()); + if (body == oob) { + body = tr("Attached file"); + } + + args << body; + args << QStringList(); + args << QVariantMap(); + args << 3000; + instance->dbus.callWithArgumentList(QDBus::AutoDetect, "Notify", args); +} + + #define FROM_INT_INPL(Enum) \ template<> \ Enum Shared::Global::fromInt(int src) \ diff --git a/shared/global.h b/shared/global.h index 2056639..fcd8105 100644 --- a/shared/global.h +++ b/shared/global.h @@ -42,12 +42,18 @@ #include #include #include +#include + +class Squawk; +namespace Models { + class Roster; +} namespace Shared { class Global { Q_DECLARE_TR_FUNCTIONS(Global) - + friend class ::Squawk; public: struct FileInfo { enum class Preview { @@ -64,7 +70,9 @@ namespace Shared { }; Global(); - + + static void notify(const QString& account, const Shared::Message& msg); + static Global* getInstance(); static QString getName(Availability av); static QString getName(ConnectionState cs); @@ -122,6 +130,8 @@ namespace Shared { private: static Global* instance; + Models::Roster* rosterModel; + QDBusInterface dbus; std::map pluginSupport; std::map fileCache; diff --git a/ui/models/roster.cpp b/ui/models/roster.cpp index 1355fe3..e5ada43 100644 --- a/ui/models/roster.cpp +++ b/ui/models/roster.cpp @@ -763,7 +763,7 @@ void Models::Roster::removeAccount(const QString& account) acc->deleteLater(); } -QString Models::Roster::getContactName(const QString& account, const QString& jid) +QString Models::Roster::getContactName(const QString& account, const QString& jid) const { ElId id(account, jid); std::map::const_iterator cItr = contacts.find(id); @@ -907,7 +907,7 @@ bool Models::Roster::groupHasContact(const QString& account, const QString& grou } } -QString Models::Roster::getContactIconPath(const QString& account, const QString& jid, const QString& resource) +QString Models::Roster::getContactIconPath(const QString& account, const QString& jid, const QString& resource) const { ElId id(account, jid); std::map::const_iterator cItr = contacts.find(id); diff --git a/ui/models/roster.h b/ui/models/roster.h index 08d5afc..28f4d30 100644 --- a/ui/models/roster.h +++ b/ui/models/roster.h @@ -65,7 +65,7 @@ public: void addRoomParticipant(const QString& account, const QString& jid, const QString& name, const QMap& data); void changeRoomParticipant(const QString& account, const QString& jid, const QString& name, const QMap& data); void removeRoomParticipant(const QString& account, const QString& jid, const QString& name); - QString getContactName(const QString& account, const QString& jid); + QString getContactName(const QString& account, const QString& jid) const; QVariant data ( const QModelIndex& index, int role ) const override; Qt::ItemFlags flags(const QModelIndex &index) const override; @@ -77,7 +77,7 @@ public: std::deque groupList(const QString& account) const; bool groupHasContact(const QString& account, const QString& group, const QString& contactJID) const; - QString getContactIconPath(const QString& account, const QString& jid, const QString& resource); + QString getContactIconPath(const QString& account, const QString& jid, const QString& resource) const; Account* getAccount(const QString& name); QModelIndex getAccountIndex(const QString& name); QModelIndex getGroupIndex(const QString& account, const QString& name); diff --git a/ui/squawk.cpp b/ui/squawk.cpp index a0f16b2..a08f38b 100644 --- a/ui/squawk.cpp +++ b/ui/squawk.cpp @@ -28,10 +28,9 @@ Squawk::Squawk(QWidget *parent) : preferences(nullptr), about(nullptr), dialogueQueue(this), - rosterModel(), + rosterModel(*(Shared::Global::getInstance()->rosterModel)), conversations(), contextMenu(new QMenu()), - dbus("org.freedesktop.Notifications", "/org/freedesktop/Notifications", "org.freedesktop.Notifications", QDBusConnection::sessionBus()), vCards(), currentConversation(nullptr), restoreSelection(), @@ -441,33 +440,7 @@ void Squawk::changeMessage(const QString& account, const QString& jid, const QSt void Squawk::notify(const QString& account, const Shared::Message& msg) { - QString name = QString(rosterModel.getContactName(account, msg.getPenPalJid())); - QString path = QString(rosterModel.getContactIconPath(account, msg.getPenPalJid(), msg.getPenPalResource())); - QVariantList args; - args << QString(QCoreApplication::applicationName()); - args << QVariant(QVariant::UInt); //TODO some normal id - if (path.size() > 0) { - args << path; - } else { - args << QString("mail-message"); //TODO should here better be unknown user icon? - } - if (msg.getType() == Shared::Message::groupChat) { - args << msg.getFromResource() + " from " + name; - } else { - args << name; - } - - QString body(msg.getBody()); - QString oob(msg.getOutOfBandUrl()); - if (body == oob) { - body = tr("Attached file"); - } - - args << body; - args << QStringList(); - args << QVariantMap(); - args << 3000; - dbus.callWithArgumentList(QDBus::AutoDetect, "Notify", args); + Shared::Global::notify(account, msg); } void Squawk::onConversationMessage(const Shared::Message& msg) diff --git a/ui/squawk.h b/ui/squawk.h index 5a77f17..aa52153 100644 --- a/ui/squawk.h +++ b/ui/squawk.h @@ -22,7 +22,6 @@ #include #include #include -#include #include #include @@ -43,6 +42,7 @@ #include "dialogqueue.h" #include "shared/shared.h" +#include "shared/global.h" namespace Ui { class Squawk; @@ -124,10 +124,9 @@ private: Settings* preferences; About* about; DialogQueue dialogueQueue; - Models::Roster rosterModel; + Models::Roster& rosterModel; Conversations conversations; QMenu* contextMenu; - QDBusInterface dbus; std::map vCards; Conversation* currentConversation; QModelIndex restoreSelection; From 83cb2201752e225b0bfb9ce5d85caafa3e8cf97e Mon Sep 17 00:00:00 2001 From: blue Date: Tue, 19 Apr 2022 20:24:41 +0300 Subject: [PATCH 79/93] better notification sending, edited message now modifies notification (or sends), little structure change --- core/CMakeLists.txt | 7 +------ core/networkaccess.h | 2 +- core/rosteritem.h | 2 +- core/storage/CMakeLists.txt | 8 ++++++++ core/{ => storage}/archive.cpp | 0 core/{ => storage}/archive.h | 0 core/{ => storage}/storage.cpp | 0 core/{ => storage}/storage.h | 0 core/{ => storage}/urlstorage.cpp | 0 core/{ => storage}/urlstorage.h | 0 shared/global.cpp | 14 ++++++++++---- ui/models/roster.cpp | 8 ++++---- ui/widgets/messageline/messagefeed.cpp | 6 ++++++ 13 files changed, 31 insertions(+), 16 deletions(-) create mode 100644 core/storage/CMakeLists.txt rename core/{ => storage}/archive.cpp (100%) rename core/{ => storage}/archive.h (100%) rename core/{ => storage}/storage.cpp (100%) rename core/{ => storage}/storage.h (100%) rename core/{ => storage}/urlstorage.cpp (100%) rename core/{ => storage}/urlstorage.h (100%) diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index 8b6fa69..8f49b11 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -8,8 +8,6 @@ target_sources(squawk PRIVATE account.h adapterfunctions.cpp adapterfunctions.h - archive.cpp - archive.h conference.cpp conference.h contact.cpp @@ -23,13 +21,10 @@ target_sources(squawk PRIVATE signalcatcher.h squawk.cpp squawk.h - storage.cpp - storage.h - urlstorage.cpp - urlstorage.h ) target_include_directories(squawk PRIVATE ${LMDB_INCLUDE_DIRS}) add_subdirectory(handlers) +add_subdirectory(storage) add_subdirectory(passwordStorageEngines) diff --git a/core/networkaccess.h b/core/networkaccess.h index 0b7bb7d..6ddfa99 100644 --- a/core/networkaccess.h +++ b/core/networkaccess.h @@ -30,7 +30,7 @@ #include -#include "urlstorage.h" +#include "storage/urlstorage.h" #include "shared/pathcheck.h" namespace Core { diff --git a/core/rosteritem.h b/core/rosteritem.h index d422e3f..5f99017 100644 --- a/core/rosteritem.h +++ b/core/rosteritem.h @@ -34,7 +34,7 @@ #include "shared/enums.h" #include "shared/message.h" #include "shared/vcard.h" -#include "archive.h" +#include "storage/archive.h" #include "adapterfunctions.h" namespace Core { diff --git a/core/storage/CMakeLists.txt b/core/storage/CMakeLists.txt new file mode 100644 index 0000000..4c263d5 --- /dev/null +++ b/core/storage/CMakeLists.txt @@ -0,0 +1,8 @@ +target_sources(squawk PRIVATE + archive.cpp + archive.h + storage.cpp + storage.h + urlstorage.cpp + urlstorage.h +) diff --git a/core/archive.cpp b/core/storage/archive.cpp similarity index 100% rename from core/archive.cpp rename to core/storage/archive.cpp diff --git a/core/archive.h b/core/storage/archive.h similarity index 100% rename from core/archive.h rename to core/storage/archive.h diff --git a/core/storage.cpp b/core/storage/storage.cpp similarity index 100% rename from core/storage.cpp rename to core/storage/storage.cpp diff --git a/core/storage.h b/core/storage/storage.h similarity index 100% rename from core/storage.h rename to core/storage/storage.h diff --git a/core/urlstorage.cpp b/core/storage/urlstorage.cpp similarity index 100% rename from core/urlstorage.cpp rename to core/storage/urlstorage.cpp diff --git a/core/urlstorage.h b/core/storage/urlstorage.h similarity index 100% rename from core/urlstorage.h rename to core/storage/urlstorage.h diff --git a/shared/global.cpp b/shared/global.cpp index 14ae90d..be660bd 100644 --- a/shared/global.cpp +++ b/shared/global.cpp @@ -357,8 +357,9 @@ void Shared::Global::notify(const QString& account, const Shared::Message& msg) QString name = QString(instance->rosterModel->getContactName(account, msg.getPenPalJid())); QString path = QString(instance->rosterModel->getContactIconPath(account, msg.getPenPalJid(), msg.getPenPalResource())); QVariantList args; - args << QString(QCoreApplication::applicationName()); - args << QVariant(QVariant::UInt); //TODO some normal id + args << QString(); + + args << qHash(msg.getId()); if (path.size() > 0) { args << path; } else { @@ -378,8 +379,13 @@ void Shared::Global::notify(const QString& account, const Shared::Message& msg) args << body; args << QStringList(); - args << QVariantMap(); - args << 3000; + args << QVariantMap({ + {"desktop-entry", QString(QCoreApplication::applicationName())}, + {"category", QString("message")}, + // {"sound-file", "/path/to/macaw/squawk"}, + {"sound-name", QString("message-new-instant")} + }); + args << -1; instance->dbus.callWithArgumentList(QDBus::AutoDetect, "Notify", args); } diff --git a/ui/models/roster.cpp b/ui/models/roster.cpp index e5ada43..b96ddda 100644 --- a/ui/models/roster.cpp +++ b/ui/models/roster.cpp @@ -801,10 +801,10 @@ void Models::Roster::addRoom(const QString& account, const QString jid, const QM } Room* room = new Room(acc, jid, data); - connect(room, &Contact::requestArchive, this, &Roster::onElementRequestArchive); - connect(room, &Contact::fileDownloadRequest, this, &Roster::fileDownloadRequest); - connect(room, &Contact::unnoticedMessage, this, &Roster::unnoticedMessage); - connect(room, &Contact::localPathInvalid, this, &Roster::localPathInvalid); + connect(room, &Room::requestArchive, this, &Roster::onElementRequestArchive); + connect(room, &Room::fileDownloadRequest, this, &Roster::fileDownloadRequest); + connect(room, &Room::unnoticedMessage, this, &Roster::unnoticedMessage); + connect(room, &Room::localPathInvalid, this, &Roster::localPathInvalid); rooms.insert(std::make_pair(id, room)); acc->appendChild(room); } diff --git a/ui/widgets/messageline/messagefeed.cpp b/ui/widgets/messageline/messagefeed.cpp index 33fbdd4..521e981 100644 --- a/ui/widgets/messageline/messagefeed.cpp +++ b/ui/widgets/messageline/messagefeed.cpp @@ -163,6 +163,12 @@ void Models::MessageFeed::changeMessage(const QString& id, const QMapgetForwarded() && changeRoles.count(MessageRoles::Text) > 0) { + unreadMessages->insert(id); + emit unreadMessagesCountChanged(); + emit unnoticedMessage(*msg); + } } } From 721d3a1a89fd4daf4b0ab0643aa48f3155f23954 Mon Sep 17 00:00:00 2001 From: blue Date: Fri, 22 Apr 2022 18:26:18 +0300 Subject: [PATCH 80/93] refactoring: UI squawk now belongs to a new class, it enables me doing trayed mode, when main window is destroyed --- CMakeLists.txt | 1 + core/CMakeLists.txt | 1 - core/main.cpp | 201 -------------- main/CMakeLists.txt | 7 + main/application.cpp | 476 ++++++++++++++++++++++++++++++++ main/application.h | 111 ++++++++ {ui => main}/dialogqueue.cpp | 31 ++- {ui => main}/dialogqueue.h | 18 +- main/main.cpp | 139 ++++++++++ shared/global.cpp | 40 --- shared/global.h | 11 - ui/CMakeLists.txt | 2 - ui/models/roster.cpp | 46 +++- ui/models/roster.h | 10 +- ui/squawk.cpp | 509 ++++++----------------------------- ui/squawk.h | 74 ++--- 16 files changed, 908 insertions(+), 769 deletions(-) delete mode 100644 core/main.cpp create mode 100644 main/CMakeLists.txt create mode 100644 main/application.cpp create mode 100644 main/application.h rename {ui => main}/dialogqueue.cpp (87%) rename {ui => main}/dialogqueue.h (83%) create mode 100644 main/main.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 85aa98a..75011d8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -148,6 +148,7 @@ if(CMAKE_COMPILER_IS_GNUCXX) target_compile_options(squawk PRIVATE ${COMPILE_OPTIONS}) endif(CMAKE_COMPILER_IS_GNUCXX) +add_subdirectory(main) add_subdirectory(core) add_subdirectory(external/simpleCrypt) add_subdirectory(packaging) diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index 8f49b11..6c7a3b5 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -12,7 +12,6 @@ target_sources(squawk PRIVATE conference.h contact.cpp contact.h - main.cpp networkaccess.cpp networkaccess.h rosteritem.cpp diff --git a/core/main.cpp b/core/main.cpp deleted file mode 100644 index 4fbb1f7..0000000 --- a/core/main.cpp +++ /dev/null @@ -1,201 +0,0 @@ -/* - * Squawk messenger. - * Copyright (C) 2019 Yury Gubich - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include "../shared/global.h" -#include "../shared/messageinfo.h" -#include "../shared/pathcheck.h" -#include "../ui/squawk.h" -#include "signalcatcher.h" -#include "squawk.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -int main(int argc, char *argv[]) -{ - qRegisterMetaType("Shared::Message"); - qRegisterMetaType("Shared::MessageInfo"); - qRegisterMetaType("Shared::VCard"); - qRegisterMetaType>("std::list"); - qRegisterMetaType>("std::list"); - qRegisterMetaType>("QSet"); - qRegisterMetaType("Shared::ConnectionState"); - qRegisterMetaType("Shared::Availability"); - - QApplication app(argc, argv); - SignalCatcher sc(&app); - - QApplication::setApplicationName("squawk"); - QApplication::setOrganizationName("macaw.me"); - QApplication::setApplicationDisplayName("Squawk"); - QApplication::setApplicationVersion("0.2.2"); - - QTranslator qtTranslator; - qtTranslator.load("qt_" + QLocale::system().name(), QLibraryInfo::location(QLibraryInfo::TranslationsPath)); - app.installTranslator(&qtTranslator); - - QTranslator myappTranslator; - QStringList shares = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation); - bool found = false; - for (QString share : shares) { - found = myappTranslator.load(QLocale(), QLatin1String("squawk"), ".", share + "/l10n"); - if (found) { - break; - } - } - if (!found) { - myappTranslator.load(QLocale(), QLatin1String("squawk"), ".", QCoreApplication::applicationDirPath()); - } - - app.installTranslator(&myappTranslator); - - QIcon icon; - icon.addFile(":images/logo.svg", QSize(16, 16)); - icon.addFile(":images/logo.svg", QSize(24, 24)); - icon.addFile(":images/logo.svg", QSize(32, 32)); - icon.addFile(":images/logo.svg", QSize(48, 48)); - icon.addFile(":images/logo.svg", QSize(64, 64)); - icon.addFile(":images/logo.svg", QSize(96, 96)); - icon.addFile(":images/logo.svg", QSize(128, 128)); - icon.addFile(":images/logo.svg", QSize(256, 256)); - icon.addFile(":images/logo.svg", QSize(512, 512)); - QApplication::setWindowIcon(icon); - - new Shared::Global(); //translates enums - - QSettings settings; - QVariant vs = settings.value("style"); - if (vs.isValid()) { - QString style = vs.toString().toLower(); - if (style != "system") { - Shared::Global::setStyle(style); - } - } - if (Shared::Global::supported("colorSchemeTools")) { - QVariant vt = settings.value("theme"); - if (vt.isValid()) { - QString theme = vt.toString(); - if (theme.toLower() != "system") { - Shared::Global::setTheme(theme); - } - } - } - QString path = Shared::downloadsPathCheck(); - if (path.size() > 0) { - settings.setValue("downloadsPath", path); - } else { - qDebug() << "couldn't initialize directory for downloads, quitting"; - return -1; - } - - - Squawk w; - w.show(); - - Core::Squawk* squawk = new Core::Squawk(); - QThread* coreThread = new QThread(); - squawk->moveToThread(coreThread); - - QObject::connect(&sc, &SignalCatcher::interrupt, &app, &QApplication::closeAllWindows); - - QObject::connect(coreThread, &QThread::started, squawk, &Core::Squawk::start); - QObject::connect(&app, &QApplication::lastWindowClosed, squawk, &Core::Squawk::stop); - QObject::connect(&app, &QApplication::lastWindowClosed, &w, &Squawk::writeSettings); - //QObject::connect(&app, &QApplication::aboutToQuit, &w, &QMainWindow::close); - QObject::connect(squawk, &Core::Squawk::quit, squawk, &Core::Squawk::deleteLater); - QObject::connect(squawk, &Core::Squawk::quit, coreThread, &QThread::quit, Qt::QueuedConnection); - QObject::connect(coreThread, &QThread::finished, &app, &QApplication::quit, Qt::QueuedConnection); - - QObject::connect(&w, &Squawk::newAccountRequest, squawk, &Core::Squawk::newAccountRequest); - QObject::connect(&w, &Squawk::modifyAccountRequest, squawk, &Core::Squawk::modifyAccountRequest); - QObject::connect(&w, &Squawk::removeAccountRequest, squawk, &Core::Squawk::removeAccountRequest); - QObject::connect(&w, &Squawk::connectAccount, squawk, &Core::Squawk::connectAccount); - QObject::connect(&w, &Squawk::disconnectAccount, squawk, &Core::Squawk::disconnectAccount); - QObject::connect(&w, &Squawk::changeState, squawk, &Core::Squawk::changeState); - QObject::connect(&w, &Squawk::sendMessage, squawk,&Core::Squawk::sendMessage); - QObject::connect(&w, &Squawk::replaceMessage, squawk,&Core::Squawk::replaceMessage); - QObject::connect(&w, &Squawk::resendMessage, squawk,&Core::Squawk::resendMessage); - QObject::connect(&w, &Squawk::requestArchive, squawk, &Core::Squawk::requestArchive); - QObject::connect(&w, &Squawk::subscribeContact, squawk, &Core::Squawk::subscribeContact); - QObject::connect(&w, &Squawk::unsubscribeContact, squawk, &Core::Squawk::unsubscribeContact); - QObject::connect(&w, &Squawk::addContactRequest, squawk, &Core::Squawk::addContactRequest); - QObject::connect(&w, &Squawk::removeContactRequest, squawk, &Core::Squawk::removeContactRequest); - QObject::connect(&w, &Squawk::setRoomJoined, squawk, &Core::Squawk::setRoomJoined); - QObject::connect(&w, &Squawk::setRoomAutoJoin, squawk, &Core::Squawk::setRoomAutoJoin); - QObject::connect(&w, &Squawk::removeRoomRequest, squawk, &Core::Squawk::removeRoomRequest); - QObject::connect(&w, &Squawk::addRoomRequest, squawk, &Core::Squawk::addRoomRequest); - QObject::connect(&w, &Squawk::fileDownloadRequest, squawk, &Core::Squawk::fileDownloadRequest); - QObject::connect(&w, &Squawk::addContactToGroupRequest, squawk, &Core::Squawk::addContactToGroupRequest); - QObject::connect(&w, &Squawk::removeContactFromGroupRequest, squawk, &Core::Squawk::removeContactFromGroupRequest); - QObject::connect(&w, &Squawk::renameContactRequest, squawk, &Core::Squawk::renameContactRequest); - QObject::connect(&w, &Squawk::requestVCard, squawk, &Core::Squawk::requestVCard); - QObject::connect(&w, &Squawk::uploadVCard, squawk, &Core::Squawk::uploadVCard); - QObject::connect(&w, &Squawk::responsePassword, squawk, &Core::Squawk::responsePassword); - QObject::connect(&w, &Squawk::localPathInvalid, squawk, &Core::Squawk::onLocalPathInvalid); - QObject::connect(&w, &Squawk::changeDownloadsPath, squawk, &Core::Squawk::changeDownloadsPath); - - QObject::connect(squawk, &Core::Squawk::newAccount, &w, &Squawk::newAccount); - QObject::connect(squawk, &Core::Squawk::addContact, &w, &Squawk::addContact); - QObject::connect(squawk, &Core::Squawk::changeAccount, &w, &Squawk::changeAccount); - QObject::connect(squawk, &Core::Squawk::removeAccount, &w, &Squawk::removeAccount); - QObject::connect(squawk, &Core::Squawk::addGroup, &w, &Squawk::addGroup); - QObject::connect(squawk, &Core::Squawk::removeGroup, &w, &Squawk::removeGroup); - QObject::connect(squawk, qOverload(&Core::Squawk::removeContact), - &w, qOverload(&Squawk::removeContact)); - QObject::connect(squawk, qOverload(&Core::Squawk::removeContact), - &w, qOverload(&Squawk::removeContact)); - QObject::connect(squawk, &Core::Squawk::changeContact, &w, &Squawk::changeContact); - QObject::connect(squawk, &Core::Squawk::addPresence, &w, &Squawk::addPresence); - QObject::connect(squawk, &Core::Squawk::removePresence, &w, &Squawk::removePresence); - QObject::connect(squawk, &Core::Squawk::stateChanged, &w, &Squawk::stateChanged); - QObject::connect(squawk, &Core::Squawk::accountMessage, &w, &Squawk::accountMessage); - QObject::connect(squawk, &Core::Squawk::changeMessage, &w, &Squawk::changeMessage); - QObject::connect(squawk, &Core::Squawk::responseArchive, &w, &Squawk::responseArchive); - QObject::connect(squawk, &Core::Squawk::addRoom, &w, &Squawk::addRoom); - QObject::connect(squawk, &Core::Squawk::changeRoom, &w, &Squawk::changeRoom); - QObject::connect(squawk, &Core::Squawk::removeRoom, &w, &Squawk::removeRoom); - QObject::connect(squawk, &Core::Squawk::addRoomParticipant, &w, &Squawk::addRoomParticipant); - QObject::connect(squawk, &Core::Squawk::changeRoomParticipant, &w, &Squawk::changeRoomParticipant); - QObject::connect(squawk, &Core::Squawk::removeRoomParticipant, &w, &Squawk::removeRoomParticipant); - QObject::connect(squawk, &Core::Squawk::fileDownloadComplete, &w, &Squawk::fileDownloadComplete); - QObject::connect(squawk, &Core::Squawk::fileUploadComplete, &w, &Squawk::fileUploadComplete); - QObject::connect(squawk, &Core::Squawk::fileProgress, &w, &Squawk::fileProgress); - QObject::connect(squawk, &Core::Squawk::fileError, &w, &Squawk::fileError); - QObject::connect(squawk, &Core::Squawk::responseVCard, &w, &Squawk::responseVCard); - QObject::connect(squawk, &Core::Squawk::requestPassword, &w, &Squawk::requestPassword); - QObject::connect(squawk, &Core::Squawk::ready, &w, &Squawk::readSettings); - - coreThread->start(); - int result = app.exec(); - - if (coreThread->isRunning()) { - //coreThread->wait(); - //todo if I uncomment that, the app will not quit if it has reconnected at least once - //it feels like a symptom of something badly desinged in the core thread - //need to investigate; - } - - return result; -} - diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt new file mode 100644 index 0000000..3c23932 --- /dev/null +++ b/main/CMakeLists.txt @@ -0,0 +1,7 @@ +target_sources(squawk PRIVATE + main.cpp + application.cpp + application.h + dialogqueue.cpp + dialogqueue.h +) diff --git a/main/application.cpp b/main/application.cpp new file mode 100644 index 0000000..f6ffe07 --- /dev/null +++ b/main/application.cpp @@ -0,0 +1,476 @@ +// Squawk messenger. +// Copyright (C) 2019 Yury Gubich +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "application.h" + +Application::Application(Core::Squawk* p_core): + QObject(), + availability(Shared::Availability::offline), + core(p_core), + squawk(nullptr), + notifications("org.freedesktop.Notifications", "/org/freedesktop/Notifications", "org.freedesktop.Notifications", QDBusConnection::sessionBus()), + roster(), + conversations(), + dialogueQueue(roster), + nowQuitting(false), + destroyingSquawk(false) +{ + connect(&roster, &Models::Roster::unnoticedMessage, this, &Application::notify); + + + //connecting myself to the backed + connect(this, &Application::changeState, core, &Core::Squawk::changeState); + connect(this, &Application::setRoomJoined, core, &Core::Squawk::setRoomJoined); + connect(this, &Application::setRoomAutoJoin, core, &Core::Squawk::setRoomAutoJoin); + connect(this, &Application::subscribeContact, core, &Core::Squawk::subscribeContact); + connect(this, &Application::unsubscribeContact, core, &Core::Squawk::unsubscribeContact); + connect(this, &Application::replaceMessage, core, &Core::Squawk::replaceMessage); + connect(this, &Application::sendMessage, core, &Core::Squawk::sendMessage); + connect(this, &Application::resendMessage, core, &Core::Squawk::resendMessage); + connect(&roster, &Models::Roster::requestArchive, + std::bind(&Core::Squawk::requestArchive, core, std::placeholders::_1, std::placeholders::_2, 20, std::placeholders::_3)); + + connect(&dialogueQueue, &DialogQueue::modifyAccountRequest, core, &Core::Squawk::modifyAccountRequest); + connect(&dialogueQueue, &DialogQueue::responsePassword, core, &Core::Squawk::responsePassword); + connect(&dialogueQueue, &DialogQueue::disconnectAccount, core, &Core::Squawk::disconnectAccount); + + connect(&roster, &Models::Roster::fileDownloadRequest, core, &Core::Squawk::fileDownloadRequest); + connect(&roster, &Models::Roster::localPathInvalid, core, &Core::Squawk::onLocalPathInvalid); + + + //coonecting backend to myself + connect(core, &Core::Squawk::stateChanged, this, &Application::stateChanged); + + connect(core, &Core::Squawk::accountMessage, &roster, &Models::Roster::addMessage); + connect(core, &Core::Squawk::responseArchive, &roster, &Models::Roster::responseArchive); + connect(core, &Core::Squawk::changeMessage, &roster, &Models::Roster::changeMessage); + + connect(core, &Core::Squawk::newAccount, &roster, &Models::Roster::addAccount); + connect(core, &Core::Squawk::changeAccount, this, &Application::changeAccount); + connect(core, &Core::Squawk::removeAccount, this, &Application::removeAccount); + + connect(core, &Core::Squawk::addContact, this, &Application::addContact); + connect(core, &Core::Squawk::addGroup, this, &Application::addGroup); + connect(core, &Core::Squawk::removeGroup, &roster, &Models::Roster::removeGroup); + connect(core, qOverload(&Core::Squawk::removeContact), + &roster, qOverload(&Models::Roster::removeContact)); + connect(core, qOverload(&Core::Squawk::removeContact), + &roster, qOverload(&Models::Roster::removeContact)); + connect(core, &Core::Squawk::changeContact, &roster, &Models::Roster::changeContact); + connect(core, &Core::Squawk::addPresence, &roster, &Models::Roster::addPresence); + connect(core, &Core::Squawk::removePresence, &roster, &Models::Roster::removePresence); + + connect(core, &Core::Squawk::addRoom, &roster, &Models::Roster::addRoom); + connect(core, &Core::Squawk::changeRoom, &roster, &Models::Roster::changeRoom); + connect(core, &Core::Squawk::removeRoom, &roster, &Models::Roster::removeRoom); + connect(core, &Core::Squawk::addRoomParticipant, &roster, &Models::Roster::addRoomParticipant); + connect(core, &Core::Squawk::changeRoomParticipant, &roster, &Models::Roster::changeRoomParticipant); + connect(core, &Core::Squawk::removeRoomParticipant, &roster, &Models::Roster::removeRoomParticipant); + + + connect(core, &Core::Squawk::fileDownloadComplete, std::bind(&Models::Roster::fileComplete, &roster, std::placeholders::_1, false)); + connect(core, &Core::Squawk::fileUploadComplete, std::bind(&Models::Roster::fileComplete, &roster, std::placeholders::_1, true)); + connect(core, &Core::Squawk::fileProgress, &roster, &Models::Roster::fileProgress); + connect(core, &Core::Squawk::fileError, &roster, &Models::Roster::fileError); + + connect(core, &Core::Squawk::requestPassword, this, &Application::requestPassword); + connect(core, &Core::Squawk::ready, this, &Application::readSettings); + +} + +Application::~Application() {} + +void Application::quit() +{ + if (!nowQuitting) { + nowQuitting = true; + emit quitting(); + + writeSettings(); + for (Conversations::const_iterator itr = conversations.begin(), end = conversations.end(); itr != end; ++itr) { + disconnect(itr->second, &Conversation::destroyed, this, &Application::onConversationClosed); + itr->second->close(); + } + conversations.clear(); + dialogueQueue.quit(); + + if (squawk != nullptr) { + squawk->close(); + } + if (!destroyingSquawk) { + checkForTheLastWindow(); + } + } +} + +void Application::checkForTheLastWindow() +{ + if (QApplication::topLevelWidgets().size() > 0) { + emit readyToQuit(); + } else { + connect(qApp, &QApplication::lastWindowClosed, this, &Application::readyToQuit); + } +} + +void Application::createMainWindow() +{ + if (squawk == nullptr) { + squawk = new Squawk(roster); + + connect(squawk, &Squawk::notify, this, &Application::notify); + connect(squawk, &Squawk::changeSubscription, this, &Application::changeSubscription); + connect(squawk, &Squawk::openedConversation, this, &Application::onSquawkOpenedConversation); + connect(squawk, &Squawk::openConversation, this, &Application::openConversation); + connect(squawk, &Squawk::changeState, this, &Application::setState); + connect(squawk, &Squawk::closing, this, &Application::onSquawkClosing); + + connect(squawk, &Squawk::modifyAccountRequest, core, &Core::Squawk::modifyAccountRequest); + connect(squawk, &Squawk::newAccountRequest, core, &Core::Squawk::newAccountRequest); + connect(squawk, &Squawk::removeAccountRequest, core, &Core::Squawk::removeAccountRequest); + connect(squawk, &Squawk::connectAccount, core, &Core::Squawk::connectAccount); + connect(squawk, &Squawk::disconnectAccount, core, &Core::Squawk::disconnectAccount); + + connect(squawk, &Squawk::addContactRequest, core, &Core::Squawk::addContactRequest); + connect(squawk, &Squawk::removeContactRequest, core, &Core::Squawk::removeContactRequest); + connect(squawk, &Squawk::removeRoomRequest, core, &Core::Squawk::removeRoomRequest); + connect(squawk, &Squawk::addRoomRequest, core, &Core::Squawk::addRoomRequest); + connect(squawk, &Squawk::addContactToGroupRequest, core, &Core::Squawk::addContactToGroupRequest); + connect(squawk, &Squawk::removeContactFromGroupRequest, core, &Core::Squawk::removeContactFromGroupRequest); + connect(squawk, &Squawk::renameContactRequest, core, &Core::Squawk::renameContactRequest); + connect(squawk, &Squawk::requestVCard, core, &Core::Squawk::requestVCard); + connect(squawk, &Squawk::uploadVCard, core, &Core::Squawk::uploadVCard); + connect(squawk, &Squawk::changeDownloadsPath, core, &Core::Squawk::changeDownloadsPath); + + connect(core, &Core::Squawk::responseVCard, squawk, &Squawk::responseVCard); + + dialogueQueue.setParentWidnow(squawk); + squawk->stateChanged(availability); + squawk->show(); + } +} + +void Application::onSquawkClosing() +{ + dialogueQueue.setParentWidnow(nullptr); + + disconnect(core, &Core::Squawk::responseVCard, squawk, &Squawk::responseVCard); + + destroyingSquawk = true; + squawk->deleteLater(); + squawk = nullptr; + + //for now + quit(); +} + +void Application::onSquawkDestroyed() { + destroyingSquawk = false; + if (nowQuitting) { + checkForTheLastWindow(); + } +} + + +void Application::notify(const QString& account, const Shared::Message& msg) +{ + QString name = QString(roster.getContactName(account, msg.getPenPalJid())); + QString path = QString(roster.getContactIconPath(account, msg.getPenPalJid(), msg.getPenPalResource())); + QVariantList args; + args << QString(); + + args << qHash(msg.getId()); + if (path.size() > 0) { + args << path; + } else { + args << QString("mail-message"); //TODO should here better be unknown user icon? + } + if (msg.getType() == Shared::Message::groupChat) { + args << msg.getFromResource() + " from " + name; + } else { + args << name; + } + + QString body(msg.getBody()); + QString oob(msg.getOutOfBandUrl()); + if (body == oob) { + body = tr("Attached file"); + } + + args << body; + args << QStringList(); + args << QVariantMap({ + {"desktop-entry", QString(QCoreApplication::applicationName())}, + {"category", QString("message")}, + // {"sound-file", "/path/to/macaw/squawk"}, + {"sound-name", QString("message-new-instant")} + }); + args << -1; + notifications.callWithArgumentList(QDBus::AutoDetect, "Notify", args); + + if (squawk != nullptr) { + QApplication::alert(squawk); + } +} + +void Application::setState(Shared::Availability p_availability) +{ + if (availability != p_availability) { + availability = p_availability; + emit changeState(availability); + } +} + +void Application::stateChanged(Shared::Availability state) +{ + availability = state; + if (squawk != nullptr) { + squawk->stateChanged(state); + } +} + +void Application::readSettings() +{ + QSettings settings; + settings.beginGroup("ui"); + int avail; + if (settings.contains("availability")) { + avail = settings.value("availability").toInt(); + } else { + avail = static_cast(Shared::Availability::online); + } + settings.endGroup(); + + setState(Shared::Global::fromInt(avail)); + createMainWindow(); +} + +void Application::writeSettings() +{ + QSettings settings; + settings.setValue("availability", static_cast(availability)); +} + +void Application::requestPassword(const QString& account, bool authenticationError) { + if (authenticationError) { + dialogueQueue.addAction(account, DialogQueue::askCredentials); + } else { + dialogueQueue.addAction(account, DialogQueue::askPassword); + } + +} +void Application::onConversationClosed() +{ + Conversation* conv = static_cast(sender()); + Models::Roster::ElId id(conv->getAccount(), conv->getJid()); + Conversations::const_iterator itr = conversations.find(id); + if (itr != conversations.end()) { + conversations.erase(itr); + } + if (conv->isMuc) { + Room* room = static_cast(conv); + if (!room->autoJoined()) { + emit setRoomJoined(id.account, id.name, false); + } + } +} + +void Application::changeSubscription(const Models::Roster::ElId& id, bool subscribe) +{ + Models::Item::Type type = roster.getContactType(id); + + switch (type) { + case Models::Item::contact: + if (subscribe) { + emit subscribeContact(id.account, id.name, ""); + } else { + emit unsubscribeContact(id.account, id.name, ""); + } + break; + case Models::Item::room: + setRoomAutoJoin(id.account, id.name, subscribe); + if (!isConverstationOpened(id)) { + emit setRoomJoined(id.account, id.name, subscribe); + } + break; + default: + break; + } +} + +void Application::subscribeConversation(Conversation* conv) +{ + connect(conv, &Conversation::destroyed, this, &Application::onConversationClosed); + connect(conv, &Conversation::sendMessage, this, &Application::onConversationMessage); + connect(conv, &Conversation::replaceMessage, this, &Application::onConversationReplaceMessage); + connect(conv, &Conversation::resendMessage, this, &Application::onConversationResend); + connect(conv, &Conversation::notifyableMessage, this, &Application::notify); +} + +void Application::openConversation(const Models::Roster::ElId& id, const QString& resource) +{ + Conversations::const_iterator itr = conversations.find(id); + Models::Account* acc = roster.getAccount(id.account); + Conversation* conv = nullptr; + bool created = false; + if (itr != conversations.end()) { + conv = itr->second; + } else { + Models::Element* el = roster.getElement(id); + if (el != NULL) { + if (el->type == Models::Item::room) { + created = true; + Models::Room* room = static_cast(el); + conv = new Room(acc, room); + if (!room->getJoined()) { + emit setRoomJoined(id.account, id.name, true); + } + } else if (el->type == Models::Item::contact) { + created = true; + conv = new Chat(acc, static_cast(el)); + } + } + } + + if (conv != nullptr) { + if (created) { + conv->setAttribute(Qt::WA_DeleteOnClose); + subscribeConversation(conv); + conversations.insert(std::make_pair(id, conv)); + } + + conv->show(); + conv->raise(); + conv->activateWindow(); + + if (resource.size() > 0) { + conv->setPalResource(resource); + } + } +} + +void Application::onConversationMessage(const Shared::Message& msg) +{ + Conversation* conv = static_cast(sender()); + QString acc = conv->getAccount(); + + roster.addMessage(acc, msg); + emit sendMessage(acc, msg); +} + +void Application::onConversationReplaceMessage(const QString& originalId, const Shared::Message& msg) +{ + Conversation* conv = static_cast(sender()); + QString acc = conv->getAccount(); + + roster.changeMessage(acc, msg.getPenPalJid(), originalId, { + {"state", static_cast(Shared::Message::State::pending)} + }); + emit replaceMessage(acc, originalId, msg); +} + +void Application::onConversationResend(const QString& id) +{ + Conversation* conv = static_cast(sender()); + QString acc = conv->getAccount(); + QString jid = conv->getJid(); + + emit resendMessage(acc, jid, id); +} + +void Application::onSquawkOpenedConversation() { + subscribeConversation(squawk->currentConversation); + Models::Roster::ElId id = squawk->currentConversationId(); + + const Models::Element* el = roster.getElementConst(id); + if (el != NULL && el->isRoom() && !static_cast(el)->getJoined()) { + emit setRoomJoined(id.account, id.name, true); + } +} + +void Application::removeAccount(const QString& account) +{ + Conversations::const_iterator itr = conversations.begin(); + while (itr != conversations.end()) { + if (itr->first.account == account) { + Conversations::const_iterator lItr = itr; + ++itr; + Conversation* conv = lItr->second; + disconnect(conv, &Conversation::destroyed, this, &Application::onConversationClosed); + conv->close(); + conversations.erase(lItr); + } else { + ++itr; + } + } + + if (squawk != nullptr && squawk->currentConversationId().account == account) { + squawk->closeCurrentConversation(); + } + + roster.removeAccount(account); +} + +void Application::changeAccount(const QString& account, const QMap& data) +{ + for (QMap::const_iterator itr = data.begin(), end = data.end(); itr != end; ++itr) { + QString attr = itr.key(); + roster.updateAccount(account, attr, *itr); + } +} + +void Application::addContact(const QString& account, const QString& jid, const QString& group, const QMap& data) +{ + roster.addContact(account, jid, group, data); + + if (squawk != nullptr) { + QSettings settings; + settings.beginGroup("ui"); + settings.beginGroup("roster"); + settings.beginGroup(account); + if (settings.value("expanded", false).toBool()) { + QModelIndex ind = roster.getAccountIndex(account); + squawk->expand(ind); + } + settings.endGroup(); + settings.endGroup(); + settings.endGroup(); + } +} + +void Application::addGroup(const QString& account, const QString& name) +{ + roster.addGroup(account, name); + + if (squawk != nullptr) { + QSettings settings; + settings.beginGroup("ui"); + settings.beginGroup("roster"); + settings.beginGroup(account); + if (settings.value("expanded", false).toBool()) { + QModelIndex ind = roster.getAccountIndex(account); + squawk->expand(ind); + if (settings.value(name + "/expanded", false).toBool()) { + squawk->expand(roster.getGroupIndex(account, name)); + } + } + settings.endGroup(); + settings.endGroup(); + settings.endGroup(); + } +} + +bool Application::isConverstationOpened(const Models::Roster::ElId& id) const { + return (conversations.count(id) > 0) || (squawk != nullptr && squawk->currentConversationId() == id);} diff --git a/main/application.h b/main/application.h new file mode 100644 index 0000000..15adce7 --- /dev/null +++ b/main/application.h @@ -0,0 +1,111 @@ +// Squawk messenger. +// Copyright (C) 2019 Yury Gubich +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#ifndef APPLICATION_H +#define APPLICATION_H + +#include + +#include +#include + +#include "dialogqueue.h" +#include "core/squawk.h" + +#include "ui/squawk.h" +#include "ui/models/roster.h" +#include "ui/widgets/conversation.h" + +#include "shared/message.h" +#include "shared/enums.h" + +/** + * @todo write docs + */ +class Application : public QObject +{ + Q_OBJECT +public: + Application(Core::Squawk* core); + ~Application(); + + bool isConverstationOpened(const Models::Roster::ElId& id) const; + +signals: + void sendMessage(const QString& account, const Shared::Message& data); + void replaceMessage(const QString& account, const QString& originalId, const Shared::Message& data); + void resendMessage(const QString& account, const QString& jid, const QString& id); + + void changeState(Shared::Availability state); + + void setRoomJoined(const QString& account, const QString& jid, bool joined); + void setRoomAutoJoin(const QString& account, const QString& jid, bool joined); + void subscribeContact(const QString& account, const QString& jid, const QString& reason); + void unsubscribeContact(const QString& account, const QString& jid, const QString& reason); + + void quitting(); + void readyToQuit(); + +public slots: + void readSettings(); + void quit(); + +protected slots: + void notify(const QString& account, const Shared::Message& msg); + void setState(Shared::Availability availability); + + void changeAccount(const QString& account, const QMap& data); + void removeAccount(const QString& account); + void openConversation(const Models::Roster::ElId& id, const QString& resource = ""); + + void addGroup(const QString& account, const QString& name); + void addContact(const QString& account, const QString& jid, const QString& group, const QMap& data); + + void requestPassword(const QString& account, bool authenticationError); + + void writeSettings(); + +private slots: + void onConversationClosed(); + void changeSubscription(const Models::Roster::ElId& id, bool subscribe); + void onSquawkOpenedConversation(); + void onConversationMessage(const Shared::Message& msg); + void onConversationReplaceMessage(const QString& originalId, const Shared::Message& msg); + void onConversationResend(const QString& id); + void stateChanged(Shared::Availability state); + void onSquawkClosing(); + void onSquawkDestroyed(); + +private: + void createMainWindow(); + void subscribeConversation(Conversation* conv); + void checkForTheLastWindow(); + +private: + typedef std::map Conversations; + + Shared::Availability availability; + Core::Squawk* core; + Squawk* squawk; + QDBusInterface notifications; + Models::Roster roster; + Conversations conversations; + DialogQueue dialogueQueue; + bool nowQuitting; + bool destroyingSquawk; +}; + +#endif // APPLICATION_H diff --git a/ui/dialogqueue.cpp b/main/dialogqueue.cpp similarity index 87% rename from ui/dialogqueue.cpp rename to main/dialogqueue.cpp index 02f8688..d7b4570 100644 --- a/ui/dialogqueue.cpp +++ b/main/dialogqueue.cpp @@ -15,10 +15,9 @@ // along with this program. If not, see . #include "dialogqueue.h" -#include "squawk.h" #include -DialogQueue::DialogQueue(Squawk* p_squawk): +DialogQueue::DialogQueue(const Models::Roster& p_roster): QObject(), currentSource(), currentAction(none), @@ -26,7 +25,8 @@ DialogQueue::DialogQueue(Squawk* p_squawk): collection(queue.get<0>()), sequence(queue.get<1>()), prompt(nullptr), - squawk(p_squawk) + parent(nullptr), + roster(p_roster) { } @@ -34,6 +34,19 @@ DialogQueue::~DialogQueue() { } +void DialogQueue::quit() +{ + queue.clear(); + if (currentAction != none) { + actionDone(); + } +} + +void DialogQueue::setParentWidnow(QMainWindow* p_parent) +{ + parent = p_parent; +} + bool DialogQueue::addAction(const QString& source, DialogQueue::Action action) { if (action == none) { @@ -77,7 +90,7 @@ void DialogQueue::performNextAction() actionDone(); break; case askPassword: { - QInputDialog* dialog = new QInputDialog(squawk); + QInputDialog* dialog = new QInputDialog(parent); prompt = dialog; connect(dialog, &QDialog::accepted, this, &DialogQueue::onPropmtAccepted); connect(dialog, &QDialog::rejected, this, &DialogQueue::onPropmtRejected); @@ -90,11 +103,11 @@ void DialogQueue::performNextAction() } break; case askCredentials: { - CredentialsPrompt* dialog = new CredentialsPrompt(squawk); + CredentialsPrompt* dialog = new CredentialsPrompt(parent); prompt = dialog; connect(dialog, &QDialog::accepted, this, &DialogQueue::onPropmtAccepted); connect(dialog, &QDialog::rejected, this, &DialogQueue::onPropmtRejected); - Models::Account* acc = squawk->rosterModel.getAccount(currentSource); + const Models::Account* acc = roster.getAccountConst(currentSource); dialog->setAccount(currentSource); dialog->setLogin(acc->getLogin()); dialog->setPassword(acc->getPassword()); @@ -111,12 +124,12 @@ void DialogQueue::onPropmtAccepted() break; case askPassword: { QInputDialog* dialog = static_cast(prompt); - emit squawk->responsePassword(currentSource, dialog->textValue()); + emit responsePassword(currentSource, dialog->textValue()); } break; case askCredentials: { CredentialsPrompt* dialog = static_cast(prompt); - emit squawk->modifyAccountRequest(currentSource, { + emit modifyAccountRequest(currentSource, { {"login", dialog->getLogin()}, {"password", dialog->getPassword()} }); @@ -133,7 +146,7 @@ void DialogQueue::onPropmtRejected() break; case askPassword: case askCredentials: - emit squawk->disconnectAccount(currentSource); + emit disconnectAccount(currentSource); break; } actionDone(); diff --git a/ui/dialogqueue.h b/main/dialogqueue.h similarity index 83% rename from ui/dialogqueue.h rename to main/dialogqueue.h index bfc1f21..b0da9dc 100644 --- a/ui/dialogqueue.h +++ b/main/dialogqueue.h @@ -19,14 +19,14 @@ #include #include +#include #include #include #include #include - -class Squawk; +#include class DialogQueue : public QObject { @@ -38,12 +38,21 @@ public: askCredentials }; - DialogQueue(Squawk* squawk); + DialogQueue(const Models::Roster& roster); ~DialogQueue(); bool addAction(const QString& source, Action action); bool cancelAction(const QString& source, Action action); +signals: + void modifyAccountRequest(const QString&, const QMap&); + void responsePassword(const QString& account, const QString& password); + void disconnectAccount(const QString&); + +public: + void setParentWidnow(QMainWindow* parent); + void quit(); + private: void performNextAction(); void actionDone(); @@ -85,7 +94,8 @@ private: Sequence& sequence; QDialog* prompt; - Squawk* squawk; + QMainWindow* parent; + const Models::Roster& roster; }; #endif // DIALOGQUEUE_H diff --git a/main/main.cpp b/main/main.cpp new file mode 100644 index 0000000..77719a2 --- /dev/null +++ b/main/main.cpp @@ -0,0 +1,139 @@ +/* + * Squawk messenger. + * Copyright (C) 2019 Yury Gubich + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "shared/global.h" +#include "shared/messageinfo.h" +#include "shared/pathcheck.h" +#include "main/application.h" +#include "core/signalcatcher.h" +#include "core/squawk.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +int main(int argc, char *argv[]) +{ + qRegisterMetaType("Shared::Message"); + qRegisterMetaType("Shared::MessageInfo"); + qRegisterMetaType("Shared::VCard"); + qRegisterMetaType>("std::list"); + qRegisterMetaType>("std::list"); + qRegisterMetaType>("QSet"); + qRegisterMetaType("Shared::ConnectionState"); + qRegisterMetaType("Shared::Availability"); + + QApplication app(argc, argv); + SignalCatcher sc(&app); + + QApplication::setApplicationName("squawk"); + QApplication::setOrganizationName("macaw.me"); + QApplication::setApplicationDisplayName("Squawk"); + QApplication::setApplicationVersion("0.2.2"); + + QTranslator qtTranslator; + qtTranslator.load("qt_" + QLocale::system().name(), QLibraryInfo::location(QLibraryInfo::TranslationsPath)); + app.installTranslator(&qtTranslator); + + QTranslator myappTranslator; + QStringList shares = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation); + bool found = false; + for (QString share : shares) { + found = myappTranslator.load(QLocale(), QLatin1String("squawk"), ".", share + "/l10n"); + if (found) { + break; + } + } + if (!found) { + myappTranslator.load(QLocale(), QLatin1String("squawk"), ".", QCoreApplication::applicationDirPath()); + } + + app.installTranslator(&myappTranslator); + + QIcon icon; + icon.addFile(":images/logo.svg", QSize(16, 16)); + icon.addFile(":images/logo.svg", QSize(24, 24)); + icon.addFile(":images/logo.svg", QSize(32, 32)); + icon.addFile(":images/logo.svg", QSize(48, 48)); + icon.addFile(":images/logo.svg", QSize(64, 64)); + icon.addFile(":images/logo.svg", QSize(96, 96)); + icon.addFile(":images/logo.svg", QSize(128, 128)); + icon.addFile(":images/logo.svg", QSize(256, 256)); + icon.addFile(":images/logo.svg", QSize(512, 512)); + QApplication::setWindowIcon(icon); + + new Shared::Global(); //translates enums + + QSettings settings; + QVariant vs = settings.value("style"); + if (vs.isValid()) { + QString style = vs.toString().toLower(); + if (style != "system") { + Shared::Global::setStyle(style); + } + } + if (Shared::Global::supported("colorSchemeTools")) { + QVariant vt = settings.value("theme"); + if (vt.isValid()) { + QString theme = vt.toString(); + if (theme.toLower() != "system") { + Shared::Global::setTheme(theme); + } + } + } + QString path = Shared::downloadsPathCheck(); + if (path.size() > 0) { + settings.setValue("downloadsPath", path); + } else { + qDebug() << "couldn't initialize directory for downloads, quitting"; + return -1; + } + + Core::Squawk* squawk = new Core::Squawk(); + QThread* coreThread = new QThread(); + squawk->moveToThread(coreThread); + + Application application(squawk); + + QObject::connect(&sc, &SignalCatcher::interrupt, &application, &Application::quit); + + QObject::connect(coreThread, &QThread::started, squawk, &Core::Squawk::start); + QObject::connect(&application, &Application::quitting, squawk, &Core::Squawk::stop); + //QObject::connect(&app, &QApplication::aboutToQuit, &w, &QMainWindow::close); + QObject::connect(squawk, &Core::Squawk::quit, squawk, &Core::Squawk::deleteLater); + QObject::connect(squawk, &Core::Squawk::destroyed, coreThread, &QThread::quit, Qt::QueuedConnection); + QObject::connect(coreThread, &QThread::finished, &app, &QApplication::quit, Qt::QueuedConnection); + + coreThread->start(); + int result = app.exec(); + + if (coreThread->isRunning()) { + //coreThread->wait(); + //todo if I uncomment that, the app will not quit if it has reconnected at least once + //it feels like a symptom of something badly desinged in the core thread + //need to investigate; + } + + return result; +} + diff --git a/shared/global.cpp b/shared/global.cpp index be660bd..6519952 100644 --- a/shared/global.cpp +++ b/shared/global.cpp @@ -95,8 +95,6 @@ Shared::Global::Global(): }), defaultSystemStyle(QApplication::style()->objectName()), defaultSystemPalette(QApplication::palette()), - rosterModel(new Models::Roster()), - dbus("org.freedesktop.Notifications", "/org/freedesktop/Notifications", "org.freedesktop.Notifications", QDBusConnection::sessionBus()), pluginSupport({ {"KWallet", false}, {"openFileManagerWindowJob", false}, @@ -352,44 +350,6 @@ void Shared::Global::setStyle(const QString& style) } } -void Shared::Global::notify(const QString& account, const Shared::Message& msg) -{ - QString name = QString(instance->rosterModel->getContactName(account, msg.getPenPalJid())); - QString path = QString(instance->rosterModel->getContactIconPath(account, msg.getPenPalJid(), msg.getPenPalResource())); - QVariantList args; - args << QString(); - - args << qHash(msg.getId()); - if (path.size() > 0) { - args << path; - } else { - args << QString("mail-message"); //TODO should here better be unknown user icon? - } - if (msg.getType() == Shared::Message::groupChat) { - args << msg.getFromResource() + " from " + name; - } else { - args << name; - } - - QString body(msg.getBody()); - QString oob(msg.getOutOfBandUrl()); - if (body == oob) { - body = tr("Attached file"); - } - - args << body; - args << QStringList(); - args << QVariantMap({ - {"desktop-entry", QString(QCoreApplication::applicationName())}, - {"category", QString("message")}, - // {"sound-file", "/path/to/macaw/squawk"}, - {"sound-name", QString("message-new-instant")} - }); - args << -1; - instance->dbus.callWithArgumentList(QDBus::AutoDetect, "Notify", args); -} - - #define FROM_INT_INPL(Enum) \ template<> \ Enum Shared::Global::fromInt(int src) \ diff --git a/shared/global.h b/shared/global.h index fcd8105..ebed931 100644 --- a/shared/global.h +++ b/shared/global.h @@ -42,18 +42,11 @@ #include #include #include -#include - -class Squawk; -namespace Models { - class Roster; -} namespace Shared { class Global { Q_DECLARE_TR_FUNCTIONS(Global) - friend class ::Squawk; public: struct FileInfo { enum class Preview { @@ -71,8 +64,6 @@ namespace Shared { Global(); - static void notify(const QString& account, const Shared::Message& msg); - static Global* getInstance(); static QString getName(Availability av); static QString getName(ConnectionState cs); @@ -130,8 +121,6 @@ namespace Shared { private: static Global* instance; - Models::Roster* rosterModel; - QDBusInterface dbus; std::map pluginSupport; std::map fileCache; diff --git a/ui/CMakeLists.txt b/ui/CMakeLists.txt index fcbb24c..296c289 100644 --- a/ui/CMakeLists.txt +++ b/ui/CMakeLists.txt @@ -2,8 +2,6 @@ target_sources(squawk PRIVATE squawk.cpp squawk.h squawk.ui - dialogqueue.cpp - dialogqueue.h ) add_subdirectory(models) diff --git a/ui/models/roster.cpp b/ui/models/roster.cpp index b96ddda..fef3e43 100644 --- a/ui/models/roster.cpp +++ b/ui/models/roster.cpp @@ -927,11 +927,29 @@ QString Models::Roster::getContactIconPath(const QString& account, const QString return path; } -Models::Account * Models::Roster::getAccount(const QString& name) +Models::Account * Models::Roster::getAccount(const QString& name) { + return const_cast(getAccountConst(name));} + +const Models::Account * Models::Roster::getAccountConst(const QString& name) const { + return accounts.at(name);} + +const Models::Element * Models::Roster::getElementConst(const Models::Roster::ElId& id) const { - return accounts.find(name)->second; + std::map::const_iterator cItr = contacts.find(id); + + if (cItr != contacts.end()) { + return cItr->second; + } else { + std::map::const_iterator rItr = rooms.find(id); + if (rItr != rooms.end()) { + return rItr->second; + } + } + + return NULL; } + QModelIndex Models::Roster::getAccountIndex(const QString& name) { std::map::const_iterator itr = accounts.find(name); @@ -1005,20 +1023,20 @@ void Models::Roster::fileError(const std::list& msgs, const Models::Element * Models::Roster::getElement(const Models::Roster::ElId& id) { - std::map::iterator cItr = contacts.find(id); - - if (cItr != contacts.end()) { - return cItr->second; - } else { - std::map::iterator rItr = rooms.find(id); - if (rItr != rooms.end()) { - return rItr->second; - } - } - - return NULL; + return const_cast(getElementConst(id)); } +Models::Item::Type Models::Roster::getContactType(const Models::Roster::ElId& id) const +{ + const Models::Element* el = getElementConst(id); + if (el == NULL) { + return Item::root; + } + + return el->type; +} + + void Models::Roster::onAccountReconnected() { Account* acc = static_cast(sender()); diff --git a/ui/models/roster.h b/ui/models/roster.h index 28f4d30..60adf13 100644 --- a/ui/models/roster.h +++ b/ui/models/roster.h @@ -46,6 +46,7 @@ public: Roster(QObject* parent = 0); ~Roster(); +public slots: void addAccount(const QMap &data); void updateAccount(const QString& account, const QString& field, const QVariant& value); void removeAccount(const QString& account); @@ -65,7 +66,12 @@ public: void addRoomParticipant(const QString& account, const QString& jid, const QString& name, const QMap& data); void changeRoomParticipant(const QString& account, const QString& jid, const QString& name, const QMap& data); void removeRoomParticipant(const QString& account, const QString& jid, const QString& name); + +public: QString getContactName(const QString& account, const QString& jid) const; + Item::Type getContactType(const Models::Roster::ElId& id) const; + const Element* getElementConst(const ElId& id) const; + Element* getElement(const ElId& id); QVariant data ( const QModelIndex& index, int role ) const override; Qt::ItemFlags flags(const QModelIndex &index) const override; @@ -79,6 +85,7 @@ public: bool groupHasContact(const QString& account, const QString& group, const QString& contactJID) const; QString getContactIconPath(const QString& account, const QString& jid, const QString& resource) const; Account* getAccount(const QString& name); + const Account* getAccountConst(const QString& name) const; QModelIndex getAccountIndex(const QString& name); QModelIndex getGroupIndex(const QString& account, const QString& name); void responseArchive(const QString& account, const QString& jid, const std::list& list, bool last); @@ -95,9 +102,6 @@ signals: void unnoticedMessage(const QString& account, const Shared::Message& msg); void localPathInvalid(const QString& path); -private: - Element* getElement(const ElId& id); - private slots: void onAccountDataChanged(const QModelIndex& tl, const QModelIndex& br, const QVector& roles); void onAccountReconnected(); diff --git a/ui/squawk.cpp b/ui/squawk.cpp index a08f38b..434b442 100644 --- a/ui/squawk.cpp +++ b/ui/squawk.cpp @@ -21,15 +21,13 @@ #include #include -Squawk::Squawk(QWidget *parent) : +Squawk::Squawk(Models::Roster& p_rosterModel, QWidget *parent) : QMainWindow(parent), m_ui(new Ui::Squawk), accounts(nullptr), preferences(nullptr), about(nullptr), - dialogueQueue(this), - rosterModel(*(Shared::Global::getInstance()->rosterModel)), - conversations(), + rosterModel(p_rosterModel), contextMenu(new QMenu()), vCards(), currentConversation(nullptr), @@ -64,12 +62,8 @@ Squawk::Squawk(QWidget *parent) : connect(m_ui->roster, &QTreeView::customContextMenuRequested, this, &Squawk::onRosterContextMenu); connect(m_ui->roster, &QTreeView::collapsed, this, &Squawk::onItemCollepsed); connect(m_ui->roster->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &Squawk::onRosterSelectionChanged); - connect(&rosterModel, &Models::Roster::unnoticedMessage, this, &Squawk::onUnnoticedMessage); connect(rosterModel.accountsModel, &Models::Accounts::sizeChanged, this, &Squawk::onAccountsSizeChanged); - connect(&rosterModel, &Models::Roster::requestArchive, this, &Squawk::onRequestArchive); - connect(&rosterModel, &Models::Roster::fileDownloadRequest, this, &Squawk::fileDownloadRequest); - connect(&rosterModel, &Models::Roster::localPathInvalid, this, &Squawk::localPathInvalid); connect(contextMenu, &QMenu::aboutToHide, this, &Squawk::onContextAboutToHide); connect(m_ui->actionAboutSquawk, &QAction::triggered, this, &Squawk::onAboutSquawkCalled); //m_ui->mainToolBar->addWidget(m_ui->comboBox); @@ -199,36 +193,25 @@ void Squawk::closeEvent(QCloseEvent* event) about->close(); } - for (Conversations::const_iterator itr = conversations.begin(), end = conversations.end(); itr != end; ++itr) { - disconnect(itr->second, &Conversation::destroyed, this, &Squawk::onConversationClosed); - itr->second->close(); - } - conversations.clear(); - for (std::map::const_iterator itr = vCards.begin(), end = vCards.end(); itr != end; ++itr) { disconnect(itr->second, &VCard::destroyed, this, &Squawk::onVCardClosed); itr->second->close(); } vCards.clear(); - + writeSettings(); + emit closing();; + QMainWindow::closeEvent(event); } +void Squawk::onAccountsClosed() { + accounts = nullptr;} -void Squawk::onAccountsClosed() -{ - accounts = nullptr; -} +void Squawk::onPreferencesClosed() { + preferences = nullptr;} -void Squawk::onPreferencesClosed() -{ - preferences = nullptr; -} - -void Squawk::newAccount(const QMap& account) -{ - rosterModel.addAccount(account); -} +void Squawk::onAboutSquawkClosed() { + about = nullptr;} void Squawk::onComboboxActivated(int index) { @@ -236,85 +219,11 @@ void Squawk::onComboboxActivated(int index) emit changeState(av); } -void Squawk::changeAccount(const QString& account, const QMap& data) -{ - for (QMap::const_iterator itr = data.begin(), end = data.end(); itr != end; ++itr) { - QString attr = itr.key(); - rosterModel.updateAccount(account, attr, *itr); - } -} +void Squawk::expand(const QModelIndex& index) { + m_ui->roster->expand(index);} -void Squawk::addContact(const QString& account, const QString& jid, const QString& group, const QMap& data) -{ - rosterModel.addContact(account, jid, group, data); - - QSettings settings; - settings.beginGroup("ui"); - settings.beginGroup("roster"); - settings.beginGroup(account); - if (settings.value("expanded", false).toBool()) { - QModelIndex ind = rosterModel.getAccountIndex(account); - m_ui->roster->expand(ind); - } - settings.endGroup(); - settings.endGroup(); - settings.endGroup(); -} - -void Squawk::addGroup(const QString& account, const QString& name) -{ - rosterModel.addGroup(account, name); - - QSettings settings; - settings.beginGroup("ui"); - settings.beginGroup("roster"); - settings.beginGroup(account); - if (settings.value("expanded", false).toBool()) { - QModelIndex ind = rosterModel.getAccountIndex(account); - m_ui->roster->expand(ind); - if (settings.value(name + "/expanded", false).toBool()) { - m_ui->roster->expand(rosterModel.getGroupIndex(account, name)); - } - } - settings.endGroup(); - settings.endGroup(); - settings.endGroup(); -} - -void Squawk::removeGroup(const QString& account, const QString& name) -{ - rosterModel.removeGroup(account, name); -} - -void Squawk::changeContact(const QString& account, const QString& jid, const QMap& data) -{ - rosterModel.changeContact(account, jid, data); -} - -void Squawk::removeContact(const QString& account, const QString& jid) -{ - rosterModel.removeContact(account, jid); -} - -void Squawk::removeContact(const QString& account, const QString& jid, const QString& group) -{ - rosterModel.removeContact(account, jid, group); -} - -void Squawk::addPresence(const QString& account, const QString& jid, const QString& name, const QMap& data) -{ - rosterModel.addPresence(account, jid, name, data); -} - -void Squawk::removePresence(const QString& account, const QString& jid, const QString& name) -{ - rosterModel.removePresence(account, jid, name); -} - -void Squawk::stateChanged(Shared::Availability state) -{ - m_ui->comboBox->setCurrentIndex(static_cast(state)); -} +void Squawk::stateChanged(Shared::Availability state) { + m_ui->comboBox->setCurrentIndex(static_cast(state));} void Squawk::onRosterItemDoubleClicked(const QModelIndex& item) { @@ -325,186 +234,33 @@ void Squawk::onRosterItemDoubleClicked(const QModelIndex& item) } Models::Contact* contact = nullptr; Models::Room* room = nullptr; - QString res; - Models::Roster::ElId* id = nullptr; switch (node->type) { case Models::Item::contact: contact = static_cast(node); - id = new Models::Roster::ElId(contact->getAccountName(), contact->getJid()); + emit openConversation(Models::Roster::ElId(contact->getAccountName(), contact->getJid())); break; case Models::Item::presence: contact = static_cast(node->parentItem()); - id = new Models::Roster::ElId(contact->getAccountName(), contact->getJid()); - res = node->getName(); + emit openConversation(Models::Roster::ElId(contact->getAccountName(), contact->getJid()), node->getName()); break; case Models::Item::room: room = static_cast(node); - id = new Models::Roster::ElId(room->getAccountName(), room->getJid()); + emit openConversation(Models::Roster::ElId(room->getAccountName(), room->getJid())); break; default: m_ui->roster->expand(item); break; } - - if (id != nullptr) { - Conversations::const_iterator itr = conversations.find(*id); - Models::Account* acc = rosterModel.getAccount(id->account); - Conversation* conv = nullptr; - bool created = false; - if (itr != conversations.end()) { - conv = itr->second; - } else if (contact != nullptr) { - created = true; - conv = new Chat(acc, contact); - } else if (room != nullptr) { - created = true; - conv = new Room(acc, room); - - if (!room->getJoined()) { - emit setRoomJoined(id->account, id->name, true); - } - } - - if (conv != nullptr) { - if (created) { - conv->setAttribute(Qt::WA_DeleteOnClose); - subscribeConversation(conv); - conversations.insert(std::make_pair(*id, conv)); - } - - conv->show(); - conv->raise(); - conv->activateWindow(); - - if (res.size() > 0) { - conv->setPalResource(res); - } - } - - delete id; - } } } -void Squawk::onConversationClosed(QObject* parent) +void Squawk::closeCurrentConversation() { - Conversation* conv = static_cast(sender()); - Models::Roster::ElId id(conv->getAccount(), conv->getJid()); - Conversations::const_iterator itr = conversations.find(id); - if (itr != conversations.end()) { - conversations.erase(itr); - } - if (conv->isMuc) { - Room* room = static_cast(conv); - if (!room->autoJoined()) { - emit setRoomJoined(id.account, id.name, false); - } - } -} - -void Squawk::fileProgress(const std::list msgs, qreal value, bool up) -{ - rosterModel.fileProgress(msgs, value, up); -} - -void Squawk::fileDownloadComplete(const std::list msgs, const QString& path) -{ - rosterModel.fileComplete(msgs, false); -} - -void Squawk::fileError(const std::list msgs, const QString& error, bool up) -{ - rosterModel.fileError(msgs, error, up); -} - -void Squawk::fileUploadComplete(const std::list msgs, const QString& url, const QString& path) -{ - rosterModel.fileComplete(msgs, true); -} - -void Squawk::accountMessage(const QString& account, const Shared::Message& data) -{ - rosterModel.addMessage(account, data); -} - -void Squawk::onUnnoticedMessage(const QString& account, const Shared::Message& msg) -{ - notify(account, msg); //Telegram does this way - notifies even if the app is visible - QApplication::alert(this); -} - -void Squawk::changeMessage(const QString& account, const QString& jid, const QString& id, const QMap& data) -{ - rosterModel.changeMessage(account, jid, id, data); -} - -void Squawk::notify(const QString& account, const Shared::Message& msg) -{ - Shared::Global::notify(account, msg); -} - -void Squawk::onConversationMessage(const Shared::Message& msg) -{ - Conversation* conv = static_cast(sender()); - QString acc = conv->getAccount(); - - rosterModel.addMessage(acc, msg); - emit sendMessage(acc, msg); -} - -void Squawk::onConversationReplaceMessage(const QString& originalId, const Shared::Message& msg) -{ - Conversation* conv = static_cast(sender()); - QString acc = conv->getAccount(); - - rosterModel.changeMessage(acc, msg.getPenPalJid(), originalId, { - {"state", static_cast(Shared::Message::State::pending)} - }); - emit replaceMessage(acc, originalId, msg); -} - -void Squawk::onConversationResend(const QString& id) -{ - Conversation* conv = static_cast(sender()); - QString acc = conv->getAccount(); - QString jid = conv->getJid(); - - emit resendMessage(acc, jid, id); -} - -void Squawk::onRequestArchive(const QString& account, const QString& jid, const QString& before) -{ - emit requestArchive(account, jid, 20, before); //TODO amount as a settings value -} - -void Squawk::responseArchive(const QString& account, const QString& jid, const std::list& list, bool last) -{ - rosterModel.responseArchive(account, jid, list, last); -} - -void Squawk::removeAccount(const QString& account) -{ - Conversations::const_iterator itr = conversations.begin(); - while (itr != conversations.end()) { - if (itr->first.account == account) { - Conversations::const_iterator lItr = itr; - ++itr; - Conversation* conv = lItr->second; - disconnect(conv, &Conversation::destroyed, this, &Squawk::onConversationClosed); - conv->close(); - conversations.erase(lItr); - } else { - ++itr; - } - } - - if (currentConversation != nullptr && currentConversation->getAccount() == account) { + if (currentConversation != nullptr) { currentConversation->deleteLater(); currentConversation = nullptr; m_ui->filler->show(); } - - rosterModel.removeAccount(account); } void Squawk::onRosterContextMenu(const QPoint& point) @@ -542,13 +298,13 @@ void Squawk::onRosterContextMenu(const QPoint& point) break; case Models::Item::contact: { Models::Contact* cnt = static_cast(item); + Models::Roster::ElId id(cnt->getAccountName(), cnt->getJid()); + QString cntName = cnt->getName(); hasMenu = true; QAction* dialog = contextMenu->addAction(Shared::icon("mail-message"), tr("Open dialog")); dialog->setEnabled(active); - connect(dialog, &QAction::triggered, [this, index]() { - onRosterItemDoubleClicked(index); - }); + connect(dialog, &QAction::triggered, std::bind(&Squawk::onRosterItemDoubleClicked, this, index)); Shared::SubscriptionState state = cnt->getState(); switch (state) { @@ -556,9 +312,7 @@ void Squawk::onRosterContextMenu(const QPoint& point) case Shared::SubscriptionState::to: { QAction* unsub = contextMenu->addAction(Shared::icon("news-unsubscribe"), tr("Unsubscribe")); unsub->setEnabled(active); - connect(unsub, &QAction::triggered, [this, cnt]() { - emit unsubscribeContact(cnt->getAccountName(), cnt->getJid(), ""); - }); + connect(unsub, &QAction::triggered, std::bind(&Squawk::changeSubscription, this, id, false)); } break; case Shared::SubscriptionState::from: @@ -566,75 +320,68 @@ void Squawk::onRosterContextMenu(const QPoint& point) case Shared::SubscriptionState::none: { QAction* sub = contextMenu->addAction(Shared::icon("news-subscribe"), tr("Subscribe")); sub->setEnabled(active); - connect(sub, &QAction::triggered, [this, cnt]() { - emit subscribeContact(cnt->getAccountName(), cnt->getJid(), ""); - }); + connect(sub, &QAction::triggered, std::bind(&Squawk::changeSubscription, this, id, true)); } } - QString accName = cnt->getAccountName(); - QString cntJID = cnt->getJid(); - QString cntName = cnt->getName(); QAction* rename = contextMenu->addAction(Shared::icon("edit-rename"), tr("Rename")); rename->setEnabled(active); - connect(rename, &QAction::triggered, [this, cntName, accName, cntJID]() { + connect(rename, &QAction::triggered, [this, cntName, id]() { QInputDialog* dialog = new QInputDialog(this); - connect(dialog, &QDialog::accepted, [this, dialog, cntName, accName, cntJID]() { + connect(dialog, &QDialog::accepted, [this, dialog, cntName, id]() { QString newName = dialog->textValue(); if (newName != cntName) { - emit renameContactRequest(accName, cntJID, newName); + emit renameContactRequest(id.account, id.name, newName); } dialog->deleteLater(); }); connect(dialog, &QDialog::rejected, dialog, &QObject::deleteLater); dialog->setInputMode(QInputDialog::TextInput); - dialog->setLabelText(tr("Input new name for %1\nor leave it empty for the contact \nto be displayed as %1").arg(cntJID)); - dialog->setWindowTitle(tr("Renaming %1").arg(cntJID)); + dialog->setLabelText(tr("Input new name for %1\nor leave it empty for the contact \nto be displayed as %1").arg(id.name)); + dialog->setWindowTitle(tr("Renaming %1").arg(id.name)); dialog->setTextValue(cntName); dialog->exec(); }); QMenu* groupsMenu = contextMenu->addMenu(Shared::icon("group"), tr("Groups")); - std::deque groupList = rosterModel.groupList(accName); + std::deque groupList = rosterModel.groupList(id.account); for (QString groupName : groupList) { QAction* gr = groupsMenu->addAction(groupName); gr->setCheckable(true); - gr->setChecked(rosterModel.groupHasContact(accName, groupName, cntJID)); + gr->setChecked(rosterModel.groupHasContact(id.account, groupName, id.name)); gr->setEnabled(active); - connect(gr, &QAction::toggled, [this, accName, groupName, cntJID](bool checked) { + connect(gr, &QAction::toggled, [this, groupName, id](bool checked) { if (checked) { - emit addContactToGroupRequest(accName, cntJID, groupName); + emit addContactToGroupRequest(id.account, id.name, groupName); } else { - emit removeContactFromGroupRequest(accName, cntJID, groupName); + emit removeContactFromGroupRequest(id.account, id.name, groupName); } }); } QAction* newGroup = groupsMenu->addAction(Shared::icon("group-new"), tr("New group")); newGroup->setEnabled(active); - connect(newGroup, &QAction::triggered, [this, accName, cntJID]() { + connect(newGroup, &QAction::triggered, [this, id]() { QInputDialog* dialog = new QInputDialog(this); - connect(dialog, &QDialog::accepted, [this, dialog, accName, cntJID]() { - emit addContactToGroupRequest(accName, cntJID, dialog->textValue()); + connect(dialog, &QDialog::accepted, [this, dialog, id]() { + emit addContactToGroupRequest(id.account, id.name, dialog->textValue()); dialog->deleteLater(); }); connect(dialog, &QDialog::rejected, dialog, &QObject::deleteLater); dialog->setInputMode(QInputDialog::TextInput); dialog->setLabelText(tr("New group name")); - dialog->setWindowTitle(tr("Add %1 to a new group").arg(cntJID)); + dialog->setWindowTitle(tr("Add %1 to a new group").arg(id.name)); dialog->exec(); }); QAction* card = contextMenu->addAction(Shared::icon("user-properties"), tr("VCard")); card->setEnabled(active); - connect(card, &QAction::triggered, std::bind(&Squawk::onActivateVCard, this, accName, cnt->getJid(), false)); + connect(card, &QAction::triggered, std::bind(&Squawk::onActivateVCard, this, id.account, id.name, false)); QAction* remove = contextMenu->addAction(Shared::icon("edit-delete"), tr("Remove")); remove->setEnabled(active); - connect(remove, &QAction::triggered, [this, cnt]() { - emit removeContactRequest(cnt->getAccountName(), cnt->getJid()); - }); + connect(remove, &QAction::triggered, std::bind(&Squawk::removeContactRequest, this, id.account, id.name)); } break; @@ -653,32 +400,16 @@ void Squawk::onRosterContextMenu(const QPoint& point) if (room->getAutoJoin()) { QAction* unsub = contextMenu->addAction(Shared::icon("news-unsubscribe"), tr("Unsubscribe")); unsub->setEnabled(active); - connect(unsub, &QAction::triggered, [this, id]() { - emit setRoomAutoJoin(id.account, id.name, false); - if (conversations.find(id) == conversations.end() - && (currentConversation == nullptr || currentConversation->getId() != id) - ) { //to leave the room if it's not opened in a conversation window - emit setRoomJoined(id.account, id.name, false); - } - }); + connect(unsub, &QAction::triggered, std::bind(&Squawk::changeSubscription, this, id, false)); } else { - QAction* unsub = contextMenu->addAction(Shared::icon("news-subscribe"), tr("Subscribe")); - unsub->setEnabled(active); - connect(unsub, &QAction::triggered, [this, id]() { - emit setRoomAutoJoin(id.account, id.name, true); - if (conversations.find(id) == conversations.end() - && (currentConversation == nullptr || currentConversation->getId() != id) - ) { //to join the room if it's not already joined - emit setRoomJoined(id.account, id.name, true); - } - }); + QAction* sub = contextMenu->addAction(Shared::icon("news-subscribe"), tr("Subscribe")); + sub->setEnabled(active); + connect(sub, &QAction::triggered, std::bind(&Squawk::changeSubscription, this, id, true)); } QAction* remove = contextMenu->addAction(Shared::icon("edit-delete"), tr("Remove")); remove->setEnabled(active); - connect(remove, &QAction::triggered, [this, id]() { - emit removeRoomRequest(id.account, id.name); - }); + connect(remove, &QAction::triggered, std::bind(&Squawk::removeRoomRequest, this, id.account, id.name)); } break; default: @@ -690,36 +421,6 @@ void Squawk::onRosterContextMenu(const QPoint& point) } } -void Squawk::addRoom(const QString& account, const QString jid, const QMap& data) -{ - rosterModel.addRoom(account, jid, data); -} - -void Squawk::changeRoom(const QString& account, const QString jid, const QMap& data) -{ - rosterModel.changeRoom(account, jid, data); -} - -void Squawk::removeRoom(const QString& account, const QString jid) -{ - rosterModel.removeRoom(account, jid); -} - -void Squawk::addRoomParticipant(const QString& account, const QString& jid, const QString& name, const QMap& data) -{ - rosterModel.addRoomParticipant(account, jid, name, data); -} - -void Squawk::changeRoomParticipant(const QString& account, const QString& jid, const QString& name, const QMap& data) -{ - rosterModel.changeRoomParticipant(account, jid, name, data); -} - -void Squawk::removeRoomParticipant(const QString& account, const QString& jid, const QString& name) -{ - rosterModel.removeRoomParticipant(account, jid, name); -} - void Squawk::responseVCard(const QString& jid, const Shared::VCard& card) { std::map::const_iterator itr = vCards.find(jid); @@ -777,61 +478,40 @@ void Squawk::onVCardSave(const Shared::VCard& card, const QString& account) widget->deleteLater(); } -void Squawk::readSettings() -{ - QSettings settings; - settings.beginGroup("ui"); - int avail; - if (settings.contains("availability")) { - avail = settings.value("availability").toInt(); - } else { - avail = static_cast(Shared::Availability::online); - } - settings.endGroup(); - m_ui->comboBox->setCurrentIndex(avail); - - emit changeState(Shared::Global::fromInt(avail)); -} - void Squawk::writeSettings() { QSettings settings; settings.beginGroup("ui"); - settings.beginGroup("window"); - settings.setValue("geometry", saveGeometry()); - settings.setValue("state", saveState()); - settings.endGroup(); - - settings.setValue("splitter", m_ui->splitter->saveState()); - - settings.setValue("availability", m_ui->comboBox->currentIndex()); - - settings.remove("roster"); - settings.beginGroup("roster"); - int size = rosterModel.accountsModel->rowCount(QModelIndex()); - for (int i = 0; i < size; ++i) { - QModelIndex acc = rosterModel.index(i, 0, QModelIndex()); - Models::Account* account = rosterModel.accountsModel->getAccount(i); - QString accName = account->getName(); - settings.beginGroup(accName); - - settings.setValue("expanded", m_ui->roster->isExpanded(acc)); - std::deque groups = rosterModel.groupList(accName); - for (const QString& groupName : groups) { - settings.beginGroup(groupName); - QModelIndex gIndex = rosterModel.getGroupIndex(accName, groupName); - settings.setValue("expanded", m_ui->roster->isExpanded(gIndex)); - settings.endGroup(); - } - + settings.beginGroup("window"); + settings.setValue("geometry", saveGeometry()); + settings.setValue("state", saveState()); + settings.endGroup(); + + settings.setValue("splitter", m_ui->splitter->saveState()); + settings.remove("roster"); + settings.beginGroup("roster"); + int size = rosterModel.accountsModel->rowCount(QModelIndex()); + for (int i = 0; i < size; ++i) { + QModelIndex acc = rosterModel.index(i, 0, QModelIndex()); + Models::Account* account = rosterModel.accountsModel->getAccount(i); + QString accName = account->getName(); + settings.beginGroup(accName); + + settings.setValue("expanded", m_ui->roster->isExpanded(acc)); + std::deque groups = rosterModel.groupList(accName); + for (const QString& groupName : groups) { + settings.beginGroup(groupName); + QModelIndex gIndex = rosterModel.getGroupIndex(accName, groupName); + settings.setValue("expanded", m_ui->roster->isExpanded(gIndex)); + settings.endGroup(); + } + + settings.endGroup(); + } settings.endGroup(); - } - settings.endGroup(); settings.endGroup(); settings.sync(); - - qDebug() << "Saved settings"; } void Squawk::onItemCollepsed(const QModelIndex& index) @@ -853,24 +533,6 @@ void Squawk::onItemCollepsed(const QModelIndex& index) } } -void Squawk::requestPassword(const QString& account, bool authenticationError) { - if (authenticationError) { - dialogueQueue.addAction(account, DialogQueue::askCredentials); - } else { - dialogueQueue.addAction(account, DialogQueue::askPassword); - } - -} - -void Squawk::subscribeConversation(Conversation* conv) -{ - connect(conv, &Conversation::destroyed, this, &Squawk::onConversationClosed); - connect(conv, &Conversation::sendMessage, this, &Squawk::onConversationMessage); - connect(conv, &Conversation::replaceMessage, this, &Squawk::onConversationReplaceMessage); - connect(conv, &Conversation::resendMessage, this, &Squawk::onConversationResend); - connect(conv, &Conversation::notifyableMessage, this, &Squawk::notify); -} - void Squawk::onRosterSelectionChanged(const QModelIndex& current, const QModelIndex& previous) { if (restoreSelection.isValid() && restoreSelection == current) { @@ -942,16 +604,12 @@ void Squawk::onRosterSelectionChanged(const QModelIndex& current, const QModelIn currentConversation = new Chat(acc, contact); } else if (room != nullptr) { currentConversation = new Room(acc, room); - - if (!room->getJoined()) { - emit setRoomJoined(id->account, id->name, true); - } } if (!testAttribute(Qt::WA_TranslucentBackground)) { currentConversation->setFeedFrames(true, false, true, true); } - subscribeConversation(currentConversation); + emit openedConversation(); if (res.size() > 0) { currentConversation->setPalResource(res); @@ -961,18 +619,10 @@ void Squawk::onRosterSelectionChanged(const QModelIndex& current, const QModelIn delete id; } else { - if (currentConversation != nullptr) { - currentConversation->deleteLater(); - currentConversation = nullptr; - m_ui->filler->show(); - } + closeCurrentConversation(); } } else { - if (currentConversation != nullptr) { - currentConversation->deleteLater(); - currentConversation = nullptr; - m_ui->filler->show(); - } + closeCurrentConversation(); } } @@ -997,7 +647,12 @@ void Squawk::onAboutSquawkCalled() about->show(); } -void Squawk::onAboutSquawkClosed() +Models::Roster::ElId Squawk::currentConversationId() const { - about = nullptr; + if (currentConversation == nullptr) { + return Models::Roster::ElId(); + } else { + return Models::Roster::ElId(currentConversation->getAccount(), currentConversation->getJid()); + } } + diff --git a/ui/squawk.h b/ui/squawk.h index aa52153..5ffe090 100644 --- a/ui/squawk.h +++ b/ui/squawk.h @@ -39,7 +39,6 @@ #include "widgets/vcard/vcard.h" #include "widgets/settings/settings.h" #include "widgets/about.h" -#include "dialogqueue.h" #include "shared/shared.h" #include "shared/global.h" @@ -48,84 +47,57 @@ namespace Ui { class Squawk; } +class Application; + class Squawk : public QMainWindow { Q_OBJECT - friend class DialogQueue; + friend class Application; public: - explicit Squawk(QWidget *parent = nullptr); + explicit Squawk(Models::Roster& rosterModel, QWidget *parent = nullptr); ~Squawk() override; signals: + void closing(); void newAccountRequest(const QMap&); - void modifyAccountRequest(const QString&, const QMap&); void removeAccountRequest(const QString&); void connectAccount(const QString&); void disconnectAccount(const QString&); void changeState(Shared::Availability state); - void sendMessage(const QString& account, const Shared::Message& data); - void replaceMessage(const QString& account, const QString& originalId, const Shared::Message& data); - void resendMessage(const QString& account, const QString& jid, const QString& id); - void requestArchive(const QString& account, const QString& jid, int count, const QString& before); - void subscribeContact(const QString& account, const QString& jid, const QString& reason); - void unsubscribeContact(const QString& account, const QString& jid, const QString& reason); void removeContactRequest(const QString& account, const QString& jid); void addContactRequest(const QString& account, const QString& jid, const QString& name, const QSet& groups); void addContactToGroupRequest(const QString& account, const QString& jid, const QString& groupName); void removeContactFromGroupRequest(const QString& account, const QString& jid, const QString& groupName); void renameContactRequest(const QString& account, const QString& jid, const QString& newName); - void setRoomJoined(const QString& account, const QString& jid, bool joined); - void setRoomAutoJoin(const QString& account, const QString& jid, bool joined); void addRoomRequest(const QString& account, const QString& jid, const QString& nick, const QString& password, bool autoJoin); void removeRoomRequest(const QString& account, const QString& jid); - void fileDownloadRequest(const QString& url); void requestVCard(const QString& account, const QString& jid); void uploadVCard(const QString& account, const Shared::VCard& card); - void responsePassword(const QString& account, const QString& password); - void localPathInvalid(const QString& path); void changeDownloadsPath(const QString& path); + + void notify(const QString& account, const Shared::Message& msg); + void changeSubscription(const Models::Roster::ElId& id, bool subscribe); + void openedConversation(); + void openConversation(const Models::Roster::ElId& id, const QString& resource = ""); + + void modifyAccountRequest(const QString&, const QMap&); +public: + Models::Roster::ElId currentConversationId() const; + void closeCurrentConversation(); + public slots: void writeSettings(); - void readSettings(); - void newAccount(const QMap& account); - void changeAccount(const QString& account, const QMap& data); - void removeAccount(const QString& account); - void addGroup(const QString& account, const QString& name); - void removeGroup(const QString& account, const QString& name); - void addContact(const QString& account, const QString& jid, const QString& group, const QMap& data); - void removeContact(const QString& account, const QString& jid, const QString& group); - void removeContact(const QString& account, const QString& jid); - void changeContact(const QString& account, const QString& jid, const QMap& data); - void addPresence(const QString& account, const QString& jid, const QString& name, const QMap& data); - void removePresence(const QString& account, const QString& jid, const QString& name); void stateChanged(Shared::Availability state); - void accountMessage(const QString& account, const Shared::Message& data); - void responseArchive(const QString& account, const QString& jid, const std::list& list, bool last); - void addRoom(const QString& account, const QString jid, const QMap& data); - void changeRoom(const QString& account, const QString jid, const QMap& data); - void removeRoom(const QString& account, const QString jid); - void addRoomParticipant(const QString& account, const QString& jid, const QString& name, const QMap& data); - void changeRoomParticipant(const QString& account, const QString& jid, const QString& name, const QMap& data); - void removeRoomParticipant(const QString& account, const QString& jid, const QString& name); - void fileError(const std::list msgs, const QString& error, bool up); - void fileProgress(const std::list msgs, qreal value, bool up); - void fileDownloadComplete(const std::list msgs, const QString& path); - void fileUploadComplete(const std::list msgs, const QString& url, const QString& path); void responseVCard(const QString& jid, const Shared::VCard& card); - void changeMessage(const QString& account, const QString& jid, const QString& id, const QMap& data); - void requestPassword(const QString& account, bool authenticationError); private: - typedef std::map Conversations; QScopedPointer m_ui; Accounts* accounts; Settings* preferences; About* about; - DialogQueue dialogueQueue; Models::Roster& rosterModel; - Conversations conversations; QMenu* contextMenu; std::map vCards; Conversation* currentConversation; @@ -134,9 +106,7 @@ private: protected: void closeEvent(QCloseEvent * event) override; - -protected slots: - void notify(const QString& account, const Shared::Message& msg); + void expand(const QModelIndex& index); private slots: void onAccounts(); @@ -148,27 +118,17 @@ private slots: void onAccountsSizeChanged(unsigned int size); void onAccountsClosed(); void onPreferencesClosed(); - void onConversationClosed(QObject* parent = 0); void onVCardClosed(); void onVCardSave(const Shared::VCard& card, const QString& account); void onActivateVCard(const QString& account, const QString& jid, bool edition = false); void onComboboxActivated(int index); void onRosterItemDoubleClicked(const QModelIndex& item); - void onConversationMessage(const Shared::Message& msg); - void onConversationReplaceMessage(const QString& originalId, const Shared::Message& msg); - void onConversationResend(const QString& id); - void onRequestArchive(const QString& account, const QString& jid, const QString& before); void onRosterContextMenu(const QPoint& point); void onItemCollepsed(const QModelIndex& index); void onRosterSelectionChanged(const QModelIndex& current, const QModelIndex& previous); void onContextAboutToHide(); void onAboutSquawkCalled(); void onAboutSquawkClosed(); - - void onUnnoticedMessage(const QString& account, const Shared::Message& msg); - -private: - void subscribeConversation(Conversation* conv); }; #endif // SQUAWK_H From 3916aec358cb9ec81e7d55930bdc3ae55c38bc79 Mon Sep 17 00:00:00 2001 From: blue Date: Sat, 23 Apr 2022 16:58:08 +0300 Subject: [PATCH 81/93] unread messages count now is displayed on the launcher icon --- main/application.cpp | 18 ++++++++++++++++-- main/application.h | 1 + main/main.cpp | 1 + ui/models/element.cpp | 1 + ui/models/element.h | 1 + ui/models/roster.cpp | 13 +++++++++++++ ui/models/roster.h | 2 ++ 7 files changed, 35 insertions(+), 2 deletions(-) diff --git a/main/application.cpp b/main/application.cpp index f6ffe07..696ec03 100644 --- a/main/application.cpp +++ b/main/application.cpp @@ -21,7 +21,7 @@ Application::Application(Core::Squawk* p_core): availability(Shared::Availability::offline), core(p_core), squawk(nullptr), - notifications("org.freedesktop.Notifications", "/org/freedesktop/Notifications", "org.freedesktop.Notifications", QDBusConnection::sessionBus()), + notifications("org.freedesktop.Notifications", "/org/freedesktop/Notifications", "org.freedesktop.Notifications"), roster(), conversations(), dialogueQueue(roster), @@ -29,6 +29,7 @@ Application::Application(Core::Squawk* p_core): destroyingSquawk(false) { connect(&roster, &Models::Roster::unnoticedMessage, this, &Application::notify); + connect(&roster, &Models::Roster::unreadMessagesCountChanged, this, &Application::unreadMessagesCountChanged); //connecting myself to the backed @@ -100,6 +101,7 @@ void Application::quit() emit quitting(); writeSettings(); + unreadMessagesCountChanged(0); //this notification persist in the desktop, for now I'll zero it on quit not to confuse people for (Conversations::const_iterator itr = conversations.begin(), end = conversations.end(); itr != end; ++itr) { disconnect(itr->second, &Conversation::destroyed, this, &Application::onConversationClosed); itr->second->close(); @@ -212,7 +214,7 @@ void Application::notify(const QString& account, const Shared::Message& msg) args << body; args << QStringList(); args << QVariantMap({ - {"desktop-entry", QString(QCoreApplication::applicationName())}, + {"desktop-entry", qApp->desktopFileName()}, {"category", QString("message")}, // {"sound-file", "/path/to/macaw/squawk"}, {"sound-name", QString("message-new-instant")} @@ -225,6 +227,18 @@ void Application::notify(const QString& account, const Shared::Message& msg) } } +void Application::unreadMessagesCountChanged(int count) +{ + QDBusMessage signal = QDBusMessage::createSignal("/", "com.canonical.Unity.LauncherEntry", "Update"); + signal << qApp->desktopFileName() + QLatin1String(".desktop"); + signal << QVariantMap ({ + {"count-visible", count != 0}, + {"count", count} + }); + QDBusConnection::sessionBus().send(signal); +} + + void Application::setState(Shared::Availability p_availability) { if (availability != p_availability) { diff --git a/main/application.h b/main/application.h index 15adce7..7d70877 100644 --- a/main/application.h +++ b/main/application.h @@ -65,6 +65,7 @@ public slots: protected slots: void notify(const QString& account, const Shared::Message& msg); + void unreadMessagesCountChanged(int count); void setState(Shared::Availability availability); void changeAccount(const QString& account, const QMap& data); diff --git a/main/main.cpp b/main/main.cpp index 77719a2..60b3c83 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -50,6 +50,7 @@ int main(int argc, char *argv[]) QApplication::setOrganizationName("macaw.me"); QApplication::setApplicationDisplayName("Squawk"); QApplication::setApplicationVersion("0.2.2"); + app.setDesktopFileName("squawk"); QTranslator qtTranslator; qtTranslator.load("qt_" + QLocale::system().name(), QLibraryInfo::location(QLibraryInfo::TranslationsPath)); diff --git a/ui/models/element.cpp b/ui/models/element.cpp index 4e741a4..0c709ab 100644 --- a/ui/models/element.cpp +++ b/ui/models/element.cpp @@ -171,6 +171,7 @@ void Models::Element::fileError(const QString& messageId, const QString& error, void Models::Element::onFeedUnreadMessagesCountChanged() { + emit unreadMessagesCountChanged(); if (type == contact) { changed(4); } else if (type == room) { diff --git a/ui/models/element.h b/ui/models/element.h index 94d67cb..dd90ea1 100644 --- a/ui/models/element.h +++ b/ui/models/element.h @@ -52,6 +52,7 @@ signals: void requestArchive(const QString& before); void fileDownloadRequest(const QString& url); void unnoticedMessage(const QString& account, const Shared::Message& msg); + void unreadMessagesCountChanged(); void localPathInvalid(const QString& path); protected: diff --git a/ui/models/roster.cpp b/ui/models/roster.cpp index fef3e43..b2caf6b 100644 --- a/ui/models/roster.cpp +++ b/ui/models/roster.cpp @@ -463,6 +463,7 @@ void Models::Roster::addContact(const QString& account, const QString& jid, cons connect(contact, &Contact::fileDownloadRequest, this, &Roster::fileDownloadRequest); connect(contact, &Contact::unnoticedMessage, this, &Roster::unnoticedMessage); connect(contact, &Contact::localPathInvalid, this, &Roster::localPathInvalid); + connect(contact, &Contact::unreadMessagesCountChanged, this, &Roster::recalculateUnreadMessages); contacts.insert(std::make_pair(id, contact)); } else { contact = itr->second; @@ -805,6 +806,7 @@ void Models::Roster::addRoom(const QString& account, const QString jid, const QM connect(room, &Room::fileDownloadRequest, this, &Roster::fileDownloadRequest); connect(room, &Room::unnoticedMessage, this, &Roster::unnoticedMessage); connect(room, &Room::localPathInvalid, this, &Roster::localPathInvalid); + connect(room, &Room::unreadMessagesCountChanged, this, &Roster::recalculateUnreadMessages); rooms.insert(std::make_pair(id, room)); acc->appendChild(room); } @@ -1049,3 +1051,14 @@ void Models::Roster::onAccountReconnected() } } +void Models::Roster::recalculateUnreadMessages() +{ + int count(0); + for (const std::pair& pair : contacts) { + count += pair.second->getMessagesCount(); + } + for (const std::pair& pair : rooms) { + count += pair.second->getMessagesCount(); + } + emit unreadMessagesCountChanged(count); +} diff --git a/ui/models/roster.h b/ui/models/roster.h index 60adf13..249947b 100644 --- a/ui/models/roster.h +++ b/ui/models/roster.h @@ -99,6 +99,7 @@ public: signals: void requestArchive(const QString& account, const QString& jid, const QString& before); void fileDownloadRequest(const QString& url); + void unreadMessagesCountChanged(int count); void unnoticedMessage(const QString& account, const Shared::Message& msg); void localPathInvalid(const QString& path); @@ -113,6 +114,7 @@ private slots: void onChildIsAboutToBeMoved(Item* source, int first, int last, Item* destination, int newIndex); void onChildMoved(); void onElementRequestArchive(const QString& before); + void recalculateUnreadMessages(); private: Item* root; From e58213b2943871f4013dedc4c8577fd373437270 Mon Sep 17 00:00:00 2001 From: blue Date: Sun, 24 Apr 2022 18:52:29 +0300 Subject: [PATCH 82/93] Now notifications have actions! Some more usefull functions to roster model --- CHANGELOG.md | 2 + main/application.cpp | 87 +++++++++++++++++++++++--- main/application.h | 6 ++ ui/models/contact.cpp | 10 +++ ui/models/contact.h | 1 + ui/models/element.cpp | 5 ++ ui/models/element.h | 1 + ui/models/room.cpp | 10 +++ ui/models/room.h | 1 + ui/models/roster.cpp | 83 +++++++++++++++++++----- ui/models/roster.h | 4 +- ui/squawk.cpp | 5 ++ ui/squawk.h | 1 + ui/widgets/messageline/messagefeed.cpp | 18 ++++-- ui/widgets/messageline/messagefeed.h | 1 + 15 files changed, 205 insertions(+), 30 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4daf652..241d61d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,8 @@ ### New features - new "About" window with links, license, gratitudes - if the authentication failed Squawk will ask againg for your password and login +- now there is an amount of unread messages showing on top of Squawk launcher icon +- notifications now have buttons to open a conversation or to mark that message as read ## Squawk 0.2.1 (Apr 02, 2022) ### Bug fixes diff --git a/main/application.cpp b/main/application.cpp index 696ec03..ddf17b3 100644 --- a/main/application.cpp +++ b/main/application.cpp @@ -26,7 +26,8 @@ Application::Application(Core::Squawk* p_core): conversations(), dialogueQueue(roster), nowQuitting(false), - destroyingSquawk(false) + destroyingSquawk(false), + storage() { connect(&roster, &Models::Roster::unnoticedMessage, this, &Application::notify); connect(&roster, &Models::Roster::unreadMessagesCountChanged, this, &Application::unreadMessagesCountChanged); @@ -90,6 +91,23 @@ Application::Application(Core::Squawk* p_core): connect(core, &Core::Squawk::requestPassword, this, &Application::requestPassword); connect(core, &Core::Squawk::ready, this, &Application::readSettings); + QDBusConnection sys = QDBusConnection::sessionBus(); + sys.connect( + "org.freedesktop.Notifications", + "/org/freedesktop/Notifications", + "org.freedesktop.Notifications", + "NotificationClosed", + this, + SLOT(onNotificationClosed(quint32, quint32)) + ); + sys.connect( + "org.freedesktop.Notifications", + "/org/freedesktop/Notifications", + "org.freedesktop.Notifications", + "ActionInvoked", + this, + SLOT(onNotificationInvoked(quint32, const QString&)) + ); } Application::~Application() {} @@ -188,12 +206,14 @@ void Application::onSquawkDestroyed() { void Application::notify(const QString& account, const Shared::Message& msg) { - QString name = QString(roster.getContactName(account, msg.getPenPalJid())); - QString path = QString(roster.getContactIconPath(account, msg.getPenPalJid(), msg.getPenPalResource())); + QString jid = msg.getPenPalJid(); + QString name = QString(roster.getContactName(account, jid)); + QString path = QString(roster.getContactIconPath(account, jid, msg.getPenPalResource())); QVariantList args; args << QString(); - args << qHash(msg.getId()); + uint32_t notificationId = qHash(msg.getId()); + args << notificationId; if (path.size() > 0) { args << path; } else { @@ -212,7 +232,10 @@ void Application::notify(const QString& account, const Shared::Message& msg) } args << body; - args << QStringList(); + args << QStringList({ + "markAsRead", tr("Mark as Read"), + "openConversation", tr("Open conversation") + }); args << QVariantMap({ {"desktop-entry", qApp->desktopFileName()}, {"category", QString("message")}, @@ -222,11 +245,40 @@ void Application::notify(const QString& account, const Shared::Message& msg) args << -1; notifications.callWithArgumentList(QDBus::AutoDetect, "Notify", args); + storage.insert(std::make_pair(notificationId, std::make_pair(Models::Roster::ElId(account, name), msg.getId()))); + if (squawk != nullptr) { QApplication::alert(squawk); } } +void Application::onNotificationClosed(quint32 id, quint32 reason) +{ + Notifications::const_iterator itr = storage.find(id); + if (itr != storage.end()) { + if (reason == 2) { //dissmissed by user (https://specifications.freedesktop.org/notification-spec/latest/ar01s09.html) + //TODO may ba also mark as read? + } + if (reason != 1) { //just expired, can be activated again from history, so no removing for now + storage.erase(id); + qDebug() << "Notification" << id << "was closed"; + } + } +} + +void Application::onNotificationInvoked(quint32 id, const QString& action) +{ + qDebug() << "Notification" << id << action << "request"; + Notifications::const_iterator itr = storage.find(id); + if (itr != storage.end()) { + if (action == "markAsRead") { + roster.markMessageAsRead(itr->second.first, itr->second.second); + } else if (action == "openConversation") { + focusConversation(itr->second.first, "", itr->second.second); + } + } +} + void Application::unreadMessagesCountChanged(int count) { QDBusMessage signal = QDBusMessage::createSignal("/", "com.canonical.Unity.LauncherEntry", "Update"); @@ -238,6 +290,27 @@ void Application::unreadMessagesCountChanged(int count) QDBusConnection::sessionBus().send(signal); } +void Application::focusConversation(const Models::Roster::ElId& id, const QString& resource, const QString& messageId) +{ + if (squawk != nullptr) { + if (squawk->currentConversationId() != id) { + QModelIndex index = roster.getContactIndex(id.account, id.name, resource); + squawk->select(index); + } + + if (squawk->isMinimized()) { + squawk->showNormal(); + } else { + squawk->show(); + } + squawk->raise(); + squawk->activateWindow(); + } else { + openConversation(id, resource); + } + + //TODO focus messageId; +} void Application::setState(Shared::Availability p_availability) { @@ -343,7 +416,7 @@ void Application::openConversation(const Models::Roster::ElId& id, const QString conv = itr->second; } else { Models::Element* el = roster.getElement(id); - if (el != NULL) { + if (el != nullptr) { if (el->type == Models::Item::room) { created = true; Models::Room* room = static_cast(el); @@ -409,7 +482,7 @@ void Application::onSquawkOpenedConversation() { Models::Roster::ElId id = squawk->currentConversationId(); const Models::Element* el = roster.getElementConst(id); - if (el != NULL && el->isRoom() && !static_cast(el)->getJoined()) { + if (el != nullptr && el->isRoom() && !static_cast(el)->getJoined()) { emit setRoomJoined(id.account, id.name, true); } } diff --git a/main/application.h b/main/application.h index 7d70877..301edc4 100644 --- a/main/application.h +++ b/main/application.h @@ -89,14 +89,19 @@ private slots: void stateChanged(Shared::Availability state); void onSquawkClosing(); void onSquawkDestroyed(); + void onNotificationClosed(quint32 id, quint32 reason); + void onNotificationInvoked(quint32 id, const QString& action); + private: void createMainWindow(); void subscribeConversation(Conversation* conv); void checkForTheLastWindow(); + void focusConversation(const Models::Roster::ElId& id, const QString& resource = "", const QString& messageId = ""); private: typedef std::map Conversations; + typedef std::map> Notifications; Shared::Availability availability; Core::Squawk* core; @@ -107,6 +112,7 @@ private: DialogQueue dialogueQueue; bool nowQuitting; bool destroyingSquawk; + Notifications storage; }; #endif // APPLICATION_H diff --git a/ui/models/contact.cpp b/ui/models/contact.cpp index a0c70ac..d5c7dc4 100644 --- a/ui/models/contact.cpp +++ b/ui/models/contact.cpp @@ -155,6 +155,16 @@ void Models::Contact::removePresence(const QString& name) } } +Models::Presence * Models::Contact::getPresence(const QString& name) +{ + QMap::iterator itr = presences.find(name); + if (itr == presences.end()) { + return nullptr; + } else { + return itr.value(); + } +} + void Models::Contact::refresh() { QDateTime lastActivity; diff --git a/ui/models/contact.h b/ui/models/contact.h index a8b80a3..c4fc131 100644 --- a/ui/models/contact.h +++ b/ui/models/contact.h @@ -51,6 +51,7 @@ public: void addPresence(const QString& name, const QMap& data); void removePresence(const QString& name); + Presence* getPresence(const QString& name); QString getContactName() const; QString getStatus() const; diff --git a/ui/models/element.cpp b/ui/models/element.cpp index 0c709ab..acea46f 100644 --- a/ui/models/element.cpp +++ b/ui/models/element.cpp @@ -134,6 +134,11 @@ unsigned int Models::Element::getMessagesCount() const return feed->unreadMessagesCount(); } +bool Models::Element::markMessageAsRead(const QString& id) const +{ + return feed->markMessageAsRead(id); +} + void Models::Element::addMessage(const Shared::Message& data) { feed->addMessage(data); diff --git a/ui/models/element.h b/ui/models/element.h index dd90ea1..c6d3d6e 100644 --- a/ui/models/element.h +++ b/ui/models/element.h @@ -42,6 +42,7 @@ public: void addMessage(const Shared::Message& data); void changeMessage(const QString& id, const QMap& data); unsigned int getMessagesCount() const; + bool markMessageAsRead(const QString& id) const; void responseArchive(const std::list list, bool last); bool isRoom() const; void fileProgress(const QString& messageId, qreal value, bool up); diff --git a/ui/models/room.cpp b/ui/models/room.cpp index a6a36d0..4aaa07e 100644 --- a/ui/models/room.cpp +++ b/ui/models/room.cpp @@ -264,6 +264,16 @@ void Models::Room::removeParticipant(const QString& p_name) } } +Models::Participant * Models::Room::getParticipant(const QString& p_name) +{ + std::map::const_iterator itr = participants.find(p_name); + if (itr == participants.end()) { + return nullptr; + } else { + return itr->second; + } +} + void Models::Room::handleParticipantUpdate(std::map::const_iterator itr, const QMap& data) { Participant* part = itr->second; diff --git a/ui/models/room.h b/ui/models/room.h index a51a537..707b35b 100644 --- a/ui/models/room.h +++ b/ui/models/room.h @@ -58,6 +58,7 @@ public: void addParticipant(const QString& name, const QMap& data); void changeParticipant(const QString& name, const QMap& data); void removeParticipant(const QString& name); + Participant* getParticipant(const QString& name); void toOfflineState() override; QString getDisplayedName() const override; diff --git a/ui/models/roster.cpp b/ui/models/roster.cpp index b2caf6b..fbb7e52 100644 --- a/ui/models/roster.cpp +++ b/ui/models/roster.cpp @@ -549,8 +549,8 @@ void Models::Roster::removeGroup(const QString& account, const QString& name) void Models::Roster::changeContact(const QString& account, const QString& jid, const QMap& data) { - Element* el = getElement({account, jid}); - if (el != NULL) { + Element* el = getElement(ElId(account, jid)); + if (el != nullptr) { for (QMap::const_iterator itr = data.begin(), end = data.end(); itr != end; ++itr) { el->update(itr.key(), itr.value()); } @@ -559,8 +559,8 @@ void Models::Roster::changeContact(const QString& account, const QString& jid, c void Models::Roster::changeMessage(const QString& account, const QString& jid, const QString& id, const QMap& data) { - Element* el = getElement({account, jid}); - if (el != NULL) { + Element* el = getElement(ElId(account, jid)); + if (el != nullptr) { el->changeMessage(id, data); } else { qDebug() << "A request to change a message of the contact " << jid << " in the account " << account << " but it wasn't found"; @@ -707,8 +707,8 @@ void Models::Roster::removePresence(const QString& account, const QString& jid, void Models::Roster::addMessage(const QString& account, const Shared::Message& data) { - Element* el = getElement({account, data.getPenPalJid()}); - if (el != NULL) { + Element* el = getElement(ElId(account, data.getPenPalJid())); + if (el != nullptr) { el->addMessage(data); } } @@ -948,9 +948,18 @@ const Models::Element * Models::Roster::getElementConst(const Models::Roster::El } } - return NULL; + return nullptr; } +bool Models::Roster::markMessageAsRead(const Models::Roster::ElId& elementId, const QString& messageId) +{ + const Element* el = getElementConst(elementId); + if (el != nullptr) { + return el->markMessageAsRead(messageId); + } else { + return false; + } +} QModelIndex Models::Roster::getAccountIndex(const QString& name) { @@ -968,7 +977,7 @@ QModelIndex Models::Roster::getGroupIndex(const QString& account, const QString& if (itr == accounts.end()) { return QModelIndex(); } else { - std::map::const_iterator gItr = groups.find({account, name}); + std::map::const_iterator gItr = groups.find(ElId(account, name)); if (gItr == groups.end()) { return QModelIndex(); } else { @@ -978,6 +987,48 @@ QModelIndex Models::Roster::getGroupIndex(const QString& account, const QString& } } +QModelIndex Models::Roster::getContactIndex(const QString& account, const QString& jid, const QString& resource) +{ + std::map::const_iterator itr = accounts.find(account); + if (itr == accounts.end()) { + return QModelIndex(); + } else { + Account* acc = itr->second; + QModelIndex accIndex = index(acc->row(), 0, QModelIndex()); + std::map::const_iterator cItr = contacts.find(ElId(account, jid)); + if (cItr != contacts.end()) { + QModelIndex contactIndex = index(acc->getContact(jid), 0, accIndex); + if (resource.size() == 0) { + return contactIndex; + } else { + Presence* pres = cItr->second->getPresence(resource); + if (pres != nullptr) { + return index(pres->row(), 0, contactIndex); + } else { + return contactIndex; + } + } + } else { + std::map::const_iterator rItr = rooms.find(ElId(account, jid)); + if (rItr != rooms.end()) { + QModelIndex roomIndex = index(rItr->second->row(), 0, accIndex); + if (resource.size() == 0) { + return roomIndex; + } else { + Participant* part = rItr->second->getParticipant(resource); + if (part != nullptr) { + return index(part->row(), 0, roomIndex); + } else { + return roomIndex; + } + } + } else { + return QModelIndex(); + } + } + } +} + void Models::Roster::onElementRequestArchive(const QString& before) { Element* el = static_cast(sender()); @@ -988,7 +1039,7 @@ void Models::Roster::responseArchive(const QString& account, const QString& jid, { ElId id(account, jid); Element* el = getElement(id); - if (el != NULL) { + if (el != nullptr) { el->responseArchive(list, last); } } @@ -996,8 +1047,8 @@ void Models::Roster::responseArchive(const QString& account, const QString& jid, void Models::Roster::fileProgress(const std::list& msgs, qreal value, bool up) { for (const Shared::MessageInfo& info : msgs) { - Element* el = getElement({info.account, info.jid}); - if (el != NULL) { + Element* el = getElement(ElId(info.account, info.jid)); + if (el != nullptr) { el->fileProgress(info.messageId, value, up); } } @@ -1006,8 +1057,8 @@ void Models::Roster::fileProgress(const std::list& msgs, qr void Models::Roster::fileComplete(const std::list& msgs, bool up) { for (const Shared::MessageInfo& info : msgs) { - Element* el = getElement({info.account, info.jid}); - if (el != NULL) { + Element* el = getElement(ElId(info.account, info.jid)); + if (el != nullptr) { el->fileComplete(info.messageId, up); } } @@ -1016,8 +1067,8 @@ void Models::Roster::fileComplete(const std::list& msgs, bo void Models::Roster::fileError(const std::list& msgs, const QString& err, bool up) { for (const Shared::MessageInfo& info : msgs) { - Element* el = getElement({info.account, info.jid}); - if (el != NULL) { + Element* el = getElement(ElId(info.account, info.jid)); + if (el != nullptr) { el->fileError(info.messageId, err, up); } } @@ -1031,7 +1082,7 @@ Models::Element * Models::Roster::getElement(const Models::Roster::ElId& id) Models::Item::Type Models::Roster::getContactType(const Models::Roster::ElId& id) const { const Models::Element* el = getElementConst(id); - if (el == NULL) { + if (el == nullptr) { return Item::root; } diff --git a/ui/models/roster.h b/ui/models/roster.h index 249947b..efc50f2 100644 --- a/ui/models/roster.h +++ b/ui/models/roster.h @@ -88,6 +88,8 @@ public: const Account* getAccountConst(const QString& name) const; QModelIndex getAccountIndex(const QString& name); QModelIndex getGroupIndex(const QString& account, const QString& name); + QModelIndex getContactIndex(const QString& account, const QString& jid, const QString& resource = ""); + bool markMessageAsRead(const ElId& elementId, const QString& messageId); void responseArchive(const QString& account, const QString& jid, const std::list& list, bool last); void fileProgress(const std::list& msgs, qreal value, bool up); @@ -115,7 +117,7 @@ private slots: void onChildMoved(); void onElementRequestArchive(const QString& before); void recalculateUnreadMessages(); - + private: Item* root; std::map accounts; diff --git a/ui/squawk.cpp b/ui/squawk.cpp index 434b442..9b6158c 100644 --- a/ui/squawk.cpp +++ b/ui/squawk.cpp @@ -656,3 +656,8 @@ Models::Roster::ElId Squawk::currentConversationId() const } } +void Squawk::select(QModelIndex index) +{ + m_ui->roster->scrollTo(index, QAbstractItemView::EnsureVisible); + m_ui->roster->selectionModel()->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect); +} diff --git a/ui/squawk.h b/ui/squawk.h index 5ffe090..15a73dd 100644 --- a/ui/squawk.h +++ b/ui/squawk.h @@ -90,6 +90,7 @@ public slots: void writeSettings(); void stateChanged(Shared::Availability state); void responseVCard(const QString& jid, const Shared::VCard& card); + void select(QModelIndex index); private: QScopedPointer m_ui; diff --git a/ui/widgets/messageline/messagefeed.cpp b/ui/widgets/messageline/messagefeed.cpp index 521e981..ad67bb3 100644 --- a/ui/widgets/messageline/messagefeed.cpp +++ b/ui/widgets/messageline/messagefeed.cpp @@ -318,12 +318,7 @@ QVariant Models::MessageFeed::data(const QModelIndex& index, int role) const case Bulk: { FeedItem item; item.id = msg->getId(); - - std::set::const_iterator umi = unreadMessages->find(item.id); - if (umi != unreadMessages->end()) { - unreadMessages->erase(umi); - emit unreadMessagesCountChanged(); - } + markMessageAsRead(item.id); item.sentByMe = sentByMe(*msg); item.date = msg->getTime(); @@ -373,6 +368,17 @@ int Models::MessageFeed::rowCount(const QModelIndex& parent) const return storage.size(); } +bool Models::MessageFeed::markMessageAsRead(const QString& id) const +{ + std::set::const_iterator umi = unreadMessages->find(id); + if (umi != unreadMessages->end()) { + unreadMessages->erase(umi); + emit unreadMessagesCountChanged(); + return true; + } + return false; +} + unsigned int Models::MessageFeed::unreadMessagesCount() const { return unreadMessages->size(); diff --git a/ui/widgets/messageline/messagefeed.h b/ui/widgets/messageline/messagefeed.h index c9701ae..f362989 100644 --- a/ui/widgets/messageline/messagefeed.h +++ b/ui/widgets/messageline/messagefeed.h @@ -72,6 +72,7 @@ public: void reportLocalPathInvalid(const QString& messageId); unsigned int unreadMessagesCount() const; + bool markMessageAsRead(const QString& id) const; void fileProgress(const QString& messageId, qreal value, bool up); void fileError(const QString& messageId, const QString& error, bool up); void fileComplete(const QString& messageId, bool up); From 2fcc432aef6e1656ffff967aad7bbcbb9c2e2cfd Mon Sep 17 00:00:00 2001 From: blue Date: Tue, 26 Apr 2022 23:08:25 +0300 Subject: [PATCH 83/93] 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; } From d86e2c28a02955135759fe3835676c1a16b4396f Mon Sep 17 00:00:00 2001 From: blue Date: Wed, 27 Apr 2022 01:17:53 +0300 Subject: [PATCH 84/93] 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; From eac87e713f72f51f1f24ac11d4fc52a57c861f8f Mon Sep 17 00:00:00 2001 From: blue Date: Thu, 28 Apr 2022 00:08:59 +0300 Subject: [PATCH 85/93] 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; From 7ba94e9deb786393e78880ee279554410662a168 Mon Sep 17 00:00:00 2001 From: blue Date: Fri, 29 Apr 2022 00:29:44 +0300 Subject: [PATCH 86/93] 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; From 0340db7f2fcff211f99d6ccc92bd98669a406581 Mon Sep 17 00:00:00 2001 From: blue Date: Sun, 1 May 2022 23:19:52 +0300 Subject: [PATCH 87/93] 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 From 3c48577eeed23e5f21af70c0ae90695e91893ac7 Mon Sep 17 00:00:00 2001 From: blue Date: Mon, 2 May 2022 22:25:50 +0300 Subject: [PATCH 88/93] 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: From 1f065f23e65d3d85c994a9d3d342cac14a9bccc1 Mon Sep 17 00:00:00 2001 From: blue Date: Tue, 3 May 2022 12:17:08 +0300 Subject: [PATCH 89/93] 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(); From 80c5e2f2b4214656ea6b62a9c3e981c79e3c122b Mon Sep 17 00:00:00 2001 From: blue Date: Wed, 4 May 2022 19:20:30 +0300 Subject: [PATCH 90/93] 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 From 645b92ba51124006d61cb2f20059b186d2213b93 Mon Sep 17 00:00:00 2001 From: blue Date: Thu, 5 May 2022 20:46:49 +0300 Subject: [PATCH 91/93] 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 From ea7dcc5f18930d56fd2915074f93712521b7c4f5 Mon Sep 17 00:00:00 2001 From: antonpavanvo Date: Thu, 26 May 2022 19:00:18 +0400 Subject: [PATCH 92/93] fix: About window now is a dialog --- ui/squawk.cpp | 7 +++---- ui/widgets/about.cpp | 8 ++++++-- ui/widgets/about.h | 4 ++-- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/ui/squawk.cpp b/ui/squawk.cpp index 9b6158c..8547267 100644 --- a/ui/squawk.cpp +++ b/ui/squawk.cpp @@ -637,14 +637,13 @@ void Squawk::onContextAboutToHide() void Squawk::onAboutSquawkCalled() { if (about == nullptr) { - about = new About(); + about = new About(this); about->setAttribute(Qt::WA_DeleteOnClose); connect(about, &Settings::destroyed, this, &Squawk::onAboutSquawkClosed); - } else { - about->raise(); - about->activateWindow(); } about->show(); + about->raise(); + about->activateWindow(); } Models::Roster::ElId Squawk::currentConversationId() const diff --git a/ui/widgets/about.cpp b/ui/widgets/about.cpp index 3782a94..b40594b 100644 --- a/ui/widgets/about.cpp +++ b/ui/widgets/about.cpp @@ -25,7 +25,7 @@ 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), + QDialog(parent), m_ui(new Ui::About), license(nullptr) { @@ -37,7 +37,11 @@ About::About(QWidget* parent): m_ui->qxmppVersionValue->setText(QXmppVersion()); m_ui->qxmppBuiltAgainstVersion->setText(tr("(built against %1)").arg(QXMPP_VERSION_STRING)); - setWindowFlag(Qt::Tool); + setWindowFlag(Qt::WindowStaysOnTopHint); + move( + parent->window()->frameGeometry().topLeft() + + parent->window()->rect().center() - rect().center() + ); connect(m_ui->licenceLink, &QLabel::linkActivated, this, &About::onLicenseActivated); } diff --git a/ui/widgets/about.h b/ui/widgets/about.h index 1506b7f..8965179 100644 --- a/ui/widgets/about.h +++ b/ui/widgets/about.h @@ -17,7 +17,7 @@ #ifndef ABOUT_H #define ABOUT_H -#include +#include #include #include #include @@ -32,7 +32,7 @@ class About; /** * @todo write docs */ -class About : public QWidget +class About : public QDialog { Q_OBJECT public: From 1af20e27f2dc050b319d269bad448f44848785e9 Mon Sep 17 00:00:00 2001 From: antonpavanvo Date: Fri, 27 May 2022 10:02:36 +0400 Subject: [PATCH 93/93] fix: Pop up windows: About, Accounts, Account, Settings - opening on centr of parent --- ui/squawk.cpp | 22 ++++++++-------------- ui/widgets/about.cpp | 7 +++---- ui/widgets/accounts/account.cpp | 8 ++++++-- ui/widgets/accounts/account.h | 2 +- ui/widgets/accounts/accounts.cpp | 10 +++++++--- ui/widgets/accounts/accounts.h | 4 ++-- ui/widgets/settings/settings.cpp | 6 +++++- ui/widgets/settings/settings.h | 4 ++-- 8 files changed, 34 insertions(+), 29 deletions(-) diff --git a/ui/squawk.cpp b/ui/squawk.cpp index 8547267..82066d9 100644 --- a/ui/squawk.cpp +++ b/ui/squawk.cpp @@ -96,7 +96,7 @@ Squawk::~Squawk() { void Squawk::onAccounts() { if (accounts == nullptr) { - accounts = new Accounts(rosterModel.accountsModel); + accounts = new Accounts(rosterModel.accountsModel, this); accounts->setAttribute(Qt::WA_DeleteOnClose); connect(accounts, &Accounts::destroyed, this, &Squawk::onAccountsClosed); connect(accounts, &Accounts::newAccount, this, &Squawk::newAccountRequest); @@ -104,29 +104,23 @@ void Squawk::onAccounts() connect(accounts, &Accounts::connectAccount, this, &Squawk::connectAccount); connect(accounts, &Accounts::disconnectAccount, this, &Squawk::disconnectAccount); connect(accounts, &Accounts::removeAccount, this, &Squawk::removeAccountRequest); - - accounts->show(); - } else { - accounts->show(); - accounts->raise(); - accounts->activateWindow(); } + accounts->show(); + accounts->raise(); + accounts->activateWindow(); } void Squawk::onPreferences() { if (preferences == nullptr) { - preferences = new Settings(); + preferences = new Settings(this); preferences->setAttribute(Qt::WA_DeleteOnClose); connect(preferences, &Settings::destroyed, this, &Squawk::onPreferencesClosed); connect(preferences, &Settings::changeDownloadsPath, this, &Squawk::changeDownloadsPath); - - preferences->show(); - } else { - preferences->show(); - preferences->raise(); - preferences->activateWindow(); } + preferences->show(); + preferences->raise(); + preferences->activateWindow(); } diff --git a/ui/widgets/about.cpp b/ui/widgets/about.cpp index b40594b..320c2c4 100644 --- a/ui/widgets/about.cpp +++ b/ui/widgets/about.cpp @@ -38,10 +38,9 @@ About::About(QWidget* parent): m_ui->qxmppBuiltAgainstVersion->setText(tr("(built against %1)").arg(QXMPP_VERSION_STRING)); setWindowFlag(Qt::WindowStaysOnTopHint); - move( - parent->window()->frameGeometry().topLeft() + - parent->window()->rect().center() - rect().center() - ); + if (parent) + move(parent->window()->frameGeometry().topLeft() + + parent->window()->rect().center() - rect().center()); connect(m_ui->licenceLink, &QLabel::linkActivated, this, &About::onLicenseActivated); } diff --git a/ui/widgets/accounts/account.cpp b/ui/widgets/accounts/account.cpp index 164af6c..16a2625 100644 --- a/ui/widgets/accounts/account.cpp +++ b/ui/widgets/accounts/account.cpp @@ -19,8 +19,8 @@ #include "account.h" #include "ui_account.h" -Account::Account(): - QDialog(), +Account::Account(QWidget *parent): + QDialog(parent), m_ui(new Ui::Account) { m_ui->setupUi(this); @@ -38,6 +38,10 @@ Account::Account(): QStandardItem *item = model->item(static_cast(Shared::AccountPassword::kwallet)); item->setFlags(item->flags() & ~Qt::ItemIsEnabled); } + + if (parent) + move(parent->window()->frameGeometry().topLeft() + + parent->window()->rect().center() - rect().center()); } Account::~Account() diff --git a/ui/widgets/accounts/account.h b/ui/widgets/accounts/account.h index cbe8b9c..6cc85dc 100644 --- a/ui/widgets/accounts/account.h +++ b/ui/widgets/accounts/account.h @@ -38,7 +38,7 @@ class Account : public QDialog Q_OBJECT public: - Account(); + Account(QWidget *parent = nullptr); ~Account(); QMap value() const; diff --git a/ui/widgets/accounts/accounts.cpp b/ui/widgets/accounts/accounts.cpp index 82a8ca0..0dd46a0 100644 --- a/ui/widgets/accounts/accounts.cpp +++ b/ui/widgets/accounts/accounts.cpp @@ -22,7 +22,7 @@ #include Accounts::Accounts(Models::Accounts* p_model, QWidget *parent) : - QWidget(parent), + QDialog(parent), m_ui(new Ui::Accounts), model(p_model), editing(false), @@ -38,13 +38,17 @@ Accounts::Accounts(Models::Accounts* p_model, QWidget *parent) : connect(m_ui->tableView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &Accounts::onSelectionChanged); connect(p_model, &Models::Accounts::changed, this, &Accounts::updateConnectButton); connect(m_ui->tableView, &QTableView::doubleClicked, this, &Accounts::onEditButton); + + if (parent) + move(parent->window()->frameGeometry().topLeft() + + parent->window()->rect().center() - rect().center()); } Accounts::~Accounts() = default; void Accounts::onAddButton() { - Account* acc = new Account(); + Account* acc = new Account(this); connect(acc, &Account::accepted, this, &Accounts::onAccountAccepted); connect(acc, &Account::rejected, this, &Accounts::onAccountRejected); acc->exec(); @@ -74,7 +78,7 @@ void Accounts::onAccountRejected() void Accounts::onEditButton() { - Account* acc = new Account(); + Account* acc = new Account(this); const Models::Account* mAcc = model->getAccount(m_ui->tableView->selectionModel()->selectedRows().at(0).row()); acc->setData({ diff --git a/ui/widgets/accounts/accounts.h b/ui/widgets/accounts/accounts.h index 6d5eb95..111a97a 100644 --- a/ui/widgets/accounts/accounts.h +++ b/ui/widgets/accounts/accounts.h @@ -19,7 +19,7 @@ #ifndef ACCOUNTS_H #define ACCOUNTS_H -#include +#include #include #include @@ -31,7 +31,7 @@ namespace Ui class Accounts; } -class Accounts : public QWidget +class Accounts : public QDialog { Q_OBJECT public: diff --git a/ui/widgets/settings/settings.cpp b/ui/widgets/settings/settings.cpp index cf5e905..86cd883 100644 --- a/ui/widgets/settings/settings.cpp +++ b/ui/widgets/settings/settings.cpp @@ -20,7 +20,7 @@ #include "ui_settings.h" Settings::Settings(QWidget* parent): - QWidget(parent), + QDialog(parent), m_ui(new Ui::Settings()), modifiedSettings() { @@ -34,6 +34,10 @@ Settings::Settings(QWidget* parent): connect(m_ui->applyButton, &QPushButton::clicked, this, &Settings::apply); connect(m_ui->okButton, &QPushButton::clicked, this, &Settings::confirm); connect(m_ui->cancelButton, &QPushButton::clicked, this, &Settings::close); + + if (parent) + move(parent->window()->frameGeometry().topLeft() + + parent->window()->rect().center() - rect().center()); } Settings::~Settings() diff --git a/ui/widgets/settings/settings.h b/ui/widgets/settings/settings.h index 689e0ce..22be36e 100644 --- a/ui/widgets/settings/settings.h +++ b/ui/widgets/settings/settings.h @@ -19,7 +19,7 @@ #ifndef SETTINGS_H #define SETTINGS_H -#include +#include #include #include #include @@ -36,7 +36,7 @@ class Settings; /** * @todo write docs */ -class Settings : public QWidget +class Settings : public QDialog { Q_OBJECT public: