diff --git a/CHANGELOG.md b/CHANGELOG.md index bf90231..06c4ce1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,14 +6,10 @@ - requesting the history of the current chat after reconnection - global availability (in drop down list) gets restored after reconnection - status icon in active chat changes when presence of the pen pal changes -- infinite progress when open the dialogue with something that has no history to show ### Improvements - slightly reduced the traffic on the startup by not requesting history of all MUCs -- completely rewritten message feed, now it works way faster -- OPTIONAL RUNTIME dependency: "KIO Widgets" that is supposed to allow you to open a file in your default file manager -- show in folder now is supposed to try it's best to show file in folder, even you don't have KIO installed -- once uploaded local files don't get second time uploaded - the remote URL is reused + ## Squawk 0.1.5 (Jul 29, 2020) ### Bug fixes diff --git a/CMakeLists.txt b/CMakeLists.txt index 8632b38..7e39591 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,143 +1,158 @@ -cmake_minimum_required(VERSION 3.4) -project(squawk VERSION 0.1.6 LANGUAGES CXX) +cmake_minimum_required(VERSION 3.0) +project(squawk) -cmake_policy(SET CMP0076 NEW) -cmake_policy(SET CMP0079 NEW) -set(CMAKE_CXX_STANDARD 17) +set(CMAKE_INCLUDE_CURRENT_DIR ON) +set(CMAKE_CXX_STANDARD 14) set(CMAKE_AUTOMOC ON) set(CMAKE_AUTOUIC ON) set(CMAKE_AUTORCC ON) include(GNUInstallDirs) -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake") +include_directories(.) -set(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() +find_package(Qt5Widgets CONFIG REQUIRED) +find_package(Qt5LinguistTools) -add_executable(squawk ${WIN32_FLAG} ${MACOSX_BUNDLE_FLAG}) -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(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) - 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) - target_link_libraries(squawk PRIVATE qxmpp) - add_subdirectory(external/qxmpp) -else () - target_link_libraries(squawk PRIVATE QXmpp::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 () - target_compile_definitions(squawk PRIVATE WITH_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 () - 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::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) +if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Debug) -endif () +endif() + +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}") -if(CMAKE_COMPILER_IS_GNUCXX) -target_compile_options(squawk PRIVATE - "-Wall;-Wextra" - "$<$:-g>" - "$<$:-O3>" - ) -endif(CMAKE_COMPILER_IS_GNUCXX) -add_subdirectory(core) -add_subdirectory(external/simpleCrypt) -add_subdirectory(packaging) -add_subdirectory(plugins) -add_subdirectory(resources) -add_subdirectory(shared) -add_subdirectory(translations) +set(squawk_SRC + main.cpp + exception.cpp + shared/global.cpp + shared/utils.cpp + shared/message.cpp + shared/vcard.cpp + 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 + shared.h + shared/enums.h + shared/message.h + shared/global.h + shared/utils.h + shared/vcard.h + shared/icons.h +) + +configure_file(resources/images/logo.svg squawk.svg COPYONLY) +configure_file(squawk.rc squawk.rc COPYONLY) +if (WIN32) + execute_process(COMMAND magick convert -background none -size 48x48 squawk.svg squawk48.png WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + execute_process(COMMAND magick convert -background none -size 64x64 squawk.svg squawk64.png WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + execute_process(COMMAND magick convert -background none -size 128x128 squawk.svg squawk128.png WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + execute_process(COMMAND magick convert -background none -size 256x256 squawk.svg squawk256.png WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + execute_process(COMMAND magick convert squawk48.png squawk64.png squawk256.png squawk.ico WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + set(SQUAWK_WIN_RC "${CMAKE_CURRENT_BINARY_DIR}/squawk.rc") +else (WIN32) + 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}) +endif (WIN32) + +if (APPLE) + file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/icns.iconset") + execute_process(COMMAND which convert WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} RESULT_VARIABLE ret) + message("AAA ${ret}") + execute_process(COMMAND echo $PATH WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} RESULT_VARIABLE ret) + message("AAA ${ret} $ENV{PATH}") + execute_process(COMMAND convert -background none -size 16x16 squawk.svg icns.iconset/icon_16x16.png WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} RESULT_VARIABLE ret) + message("AAA ${ret}") + 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) + +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) + +option(SYSTEM_QXMPP "Use system qxmpp lib" ON) +option(WITH_KWALLET "Build KWallet 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() + +add_executable(squawk WIN32 ${squawk_SRC} ${squawk_HEAD} ${RCC} ${SQUAWK_WIN_RC} ${APP_ICON_MACOSX}) +target_link_libraries(squawk Qt5::Widgets) + add_subdirectory(ui) +add_subdirectory(core) + +add_subdirectory(external/simpleCrypt) + +target_link_libraries(squawk squawkUI) +target_link_libraries(squawk squawkCORE) + +add_dependencies(${CMAKE_PROJECT_NAME} translations) # Install the executable install(TARGETS squawk DESTINATION ${CMAKE_INSTALL_BINDIR}) - -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() - +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/README.md b/README.md index e94972f..30c6473 100644 --- a/README.md +++ b/README.md @@ -9,12 +9,11 @@ ### 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 -- KDE Frameworks: kwallet (optional) -- KDE Frameworks: KIO (optional) -- Boost +- kwallet (optional) ### Getting @@ -34,26 +33,6 @@ 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 @@ -65,7 +44,7 @@ $ git clone https://git.macaw.me/blue/squawk $ cd squawk $ mkdir build $ cd build -$ cmake .. [-D LMDB_ROOT_DIR:PATH=...] [-D BOOST_ROOT:PATH=...] +$ cmake .. $ cmake --build . ``` @@ -78,19 +57,16 @@ $ git clone --recurse-submodules https://git.macaw.me/blue/squawk $ cd squawk $ mkdir build $ cd build -$ cmake .. -D SYSTEM_QXMPP=False [-D LMDB_ROOT_DIR:PATH=...] [-D BOOST_ROOT:PATH=...] +$ cmake .. -D SYSTEM_QXMPP=False $ 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 ..`. - `CMAKE_BUILD_TYPE` - `Debug` just builds showing all warnings, `Release` builds with no warnings and applies optimizations (default is `Debug`) - `SYSTEM_QXMPP` - `True` tries to link against `qxmpp` installed in the system, `False` builds bundled `qxmpp` library (default is `True`) - `WITH_KWALLET` - `True` builds the `KWallet` capability module if `KWallet` is installed and if not goes to `False`. `False` disables `KWallet` support (default is `True`) -- `WITH_KIO` - `True` builds the `KIO` capability module if `KIO` is installed and if not goes to `False`. `False` disables `KIO` support (default is `True`) ## License diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 9b20f3b..0000000 --- a/appveyor.yml +++ /dev/null @@ -1,117 +0,0 @@ -image: - - Visual Studio 2019 - - "Previous Ubuntu1804" - - macOS-Mojave - -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 with Qt Framework - -- - 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 . - - after_build: - - zip -r squawk.app.zip squawk.app - - artifacts: - - 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.zip - name: Squawk Bundle with Qt Framework (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 -DCMAKE_BUILD_RPATH="\$ORIGIN" .. - - build_script: - - cmake --build . - - after_build: - - zip -r squawk.zip squawk -j external/qxmpp/src/libqxmpp* - - artifacts: - - path: build/squawk.zip - name: Squawk executable and libraries (Qt 5.12) diff --git a/cmake/FindLMDB.cmake b/cmake/FindLMDB.cmake deleted file mode 100644 index d6f2cd3..0000000 --- a/cmake/FindLMDB.cmake +++ /dev/null @@ -1,52 +0,0 @@ -#This file is taken from here https://gitlab.ralph.or.at/causal-rt/causal-cpp/, it was GPLv3 license -#Thank you so much, mr. Ralph Alexander Bariz, I hope you don't mind me using your code - -# Try to find LMDB headers and library. -# -# Usage of this module as follows: -# -# find_package(LMDB) -# -# Variables used by this module, they can change the default behaviour and need -# to be set before calling find_package: -# -# LMDB_ROOT_DIR Set this variable to the root installation of -# LMDB if the module has problems finding the -# proper installation path. -# -# Variables defined by this module: -# -# LMDB_FOUND System has LMDB library/headers. -# LMDB_LIBRARIES The LMDB library. -# LMDB_INCLUDE_DIRS The location of LMDB headers. - -find_path(LMDB_ROOT_DIR - NAMES include/lmdb.h - ) - -find_library(LMDB_LIBRARIES - NAMES liblmdb.a liblmdb.so liblmdb.so.a liblmdb.dll.a # We want lmdb to be static, if possible - HINTS ${LMDB_ROOT_DIR}/lib - ) - -add_library(lmdb UNKNOWN IMPORTED) -set_target_properties(lmdb PROPERTIES - IMPORTED_LOCATION ${LMDB_LIBRARIES} - ) - -find_path(LMDB_INCLUDE_DIRS - NAMES lmdb.h - HINTS ${LMDB_ROOT_DIR}/include - ) - -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(LMDB DEFAULT_MSG - LMDB_LIBRARIES - LMDB_INCLUDE_DIRS - ) - -mark_as_advanced( - LMDB_ROOT_DIR - LMDB_LIBRARIES - LMDB_INCLUDE_DIRS -) diff --git a/cmake/FindSignal.cmake b/cmake/FindSignal.cmake deleted file mode 100644 index 752fed7..0000000 --- a/cmake/FindSignal.cmake +++ /dev/null @@ -1,15 +0,0 @@ -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/cmake/MacOSXBundleInfo.plist.in b/cmake/MacOSXBundleInfo.plist.in deleted file mode 100644 index ac4bbec..0000000 --- a/cmake/MacOSXBundleInfo.plist.in +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - 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 - - - diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index 9369cb7..a5ba6fd 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -1,34 +1,54 @@ -set(SIGNALCATCHER_SOURCE signalcatcher.cpp) -if(WIN32) - set(SIGNALCATCHER_SOURCE signalcatcher_win32.cpp) -endif(WIN32) +cmake_minimum_required(VERSION 3.0) +project(squawkCORE) -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_SOURCE} - signalcatcher.h - squawk.cpp - squawk.h - storage.cpp - storage.h - urlstorage.cpp - urlstorage.h - ) +set(CMAKE_AUTOMOC ON) -target_include_directories(squawk PRIVATE ${LMDB_INCLUDE_DIRS}) +find_package(Qt5Core CONFIG REQUIRED) +find_package(Qt5Gui CONFIG REQUIRED) +find_package(Qt5Network CONFIG REQUIRED) +find_package(Qt5Xml CONFIG REQUIRED) + +find_path(LMDB_INCLUDE_DIR NAMES lmdb.h PATHS "${LMDB_DIR}/include") +set(LMDB_LIBRARIES "${LMDB_DIR}/lib/liblmdb.a" ) + +message(${LMDB_INCLUDE_DIR}) +message(${LMDB_LIBRARIES}) + +set(squawkCORE_SRC + squawk.cpp + account.cpp + archive.cpp + rosteritem.cpp + contact.cpp + conference.cpp + storage.cpp + networkaccess.cpp + adapterFuctions.cpp + handlers/messagehandler.cpp + handlers/rosterhandler.cpp +) -add_subdirectory(handlers) 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) + target_include_directories(squawkCORE PUBLIC ${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 ${LMDB_LIBRARIES}) +target_link_libraries(squawkCORE simpleCrypt) +if (WITH_KWALLET) + target_link_libraries(squawkCORE kwalletPSE) +endif() diff --git a/core/account.cpp b/core/account.cpp index 66b31f6..ab7403b 100644 --- a/core/account.cpp +++ b/core/account.cpp @@ -85,9 +85,8 @@ Account::Account(const QString& p_login, const QString& p_server, const QString& QObject::connect(dm, &QXmppDiscoveryManager::itemsReceived, this, &Account::onDiscoveryItemsReceived); QObject::connect(dm, &QXmppDiscoveryManager::infoReceived, this, &Account::onDiscoveryInfoReceived); - QObject::connect(network, &NetworkAccess::uploadFileComplete, mh, &MessageHandler::onUploadFileComplete); - QObject::connect(network, &NetworkAccess::downloadFileComplete, mh, &MessageHandler::onDownloadFileComplete); - QObject::connect(network, &NetworkAccess::loadFileError, mh, &MessageHandler::onLoadFileError); + QObject::connect(network, &NetworkAccess::uploadFileComplete, mh, &MessageHandler::onFileUploaded); + QObject::connect(network, &NetworkAccess::uploadFileError, mh, &MessageHandler::onFileUploadError); client.addExtension(rcpm); QObject::connect(rcpm, &QXmppMessageReceiptManager::messageDelivered, mh, &MessageHandler::onReceiptReceived); @@ -157,9 +156,8 @@ Account::~Account() reconnectTimer->stop(); } - QObject::disconnect(network, &NetworkAccess::uploadFileComplete, mh, &MessageHandler::onUploadFileComplete); - QObject::disconnect(network, &NetworkAccess::downloadFileComplete, mh, &MessageHandler::onDownloadFileComplete); - QObject::disconnect(network, &NetworkAccess::loadFileError, mh, &MessageHandler::onLoadFileError); + QObject::disconnect(network, &NetworkAccess::uploadFileComplete, mh, &MessageHandler::onFileUploaded); + QObject::disconnect(network, &NetworkAccess::uploadFileError, mh, &MessageHandler::onFileUploadError); delete mh; delete rh; @@ -405,6 +403,9 @@ QString Core::Account::getFullJid() const { void Core::Account::sendMessage(const Shared::Message& data) { mh->sendMessage(data);} +void Core::Account::sendMessage(const Shared::Message& data, const QString& path) { + mh->sendMessage(data, path);} + void Core::Account::onMamMessageReceived(const QString& queryId, const QXmppMessage& msgConst) { QXmppMessage msg(msgConst); @@ -438,13 +439,13 @@ void Core::Account::requestArchive(const QString& jid, int count, const QString& if (contact == 0) { qDebug() << "An attempt to request archive for" << jid << "in account" << name << ", but the contact with such id wasn't found, skipping"; - emit responseArchive(jid, std::list(), true); + emit responseArchive(jid, std::list()); return; } if (state != Shared::ConnectionState::connected) { qDebug() << "An attempt to request archive for" << jid << "in account" << name << ", but the account is not online, skipping"; - emit responseArchive(contact->jid, std::list(), false); + emit responseArchive(contact->jid, std::list()); } contact->requestHistory(count, before); @@ -465,11 +466,6 @@ void Core::Account::onContactNeedHistory(const QString& before, const QString& a qDebug() << "Requesting remote history from empty for" << contact->jid; } else { if (before.size() > 0) { - if (before.endsWith("-squawkgenerated")) { - qDebug() << "Can't query history before an squawk-generated ID, making fake empty result"; - contact->flushMessagesToArchive(true, before, before); - return; - } query.setBefore(before); } if (after.size() > 0) { //there is some strange behavior of ejabberd server returning empty result set @@ -478,12 +474,6 @@ void Core::Account::onContactNeedHistory(const QString& before, const QString& a } else { query.setAfter(after); } - - if (after.endsWith("-squawkgenerated")) { - qDebug() << "Can't query history after an squawk-generated ID, making fake empty result"; - contact->flushMessagesToArchive(true, after, after); - return; - } } qDebug() << "Remote query for" << contact->jid << "from" << after << ", to" << before; } @@ -567,11 +557,9 @@ void Core::Account::onClientError(QXmppClient::Error err) case QXmppStanza::Error::NotAuthorized: errorText = "Authentication error"; break; -#if (QXMPP_VERSION) < QT_VERSION_CHECK(1, 3, 0) case QXmppStanza::Error::PaymentRequired: errorText = "Payment is required"; break; -#endif case QXmppStanza::Error::RecipientUnavailable: errorText = "Recipient is unavailable"; break; @@ -926,19 +914,3 @@ void Core::Account::handleDisconnection() 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);} diff --git a/core/account.h b/core/account.h index 5ba834c..49c7ca9 100644 --- a/core/account.h +++ b/core/account.h @@ -43,7 +43,7 @@ #include #include -#include "shared/shared.h" +#include "shared.h" #include "contact.h" #include "conference.h" #include "networkaccess.h" @@ -88,6 +88,7 @@ public: void setPasswordType(Shared::AccountPassword pt); QString getFullJid() const; void sendMessage(const Shared::Message& data); + void sendMessage(const Shared::Message& data, const QString& path); void requestArchive(const QString& jid, int count, const QString& before); void subscribeToContact(const QString& jid, const QString& reason); void unsubscribeFromContact(const QString& jid, const QString& reason); @@ -96,14 +97,12 @@ public: void addContactToGroupRequest(const QString& jid, const QString& groupName); void removeContactFromGroupRequest(const QString& jid, const QString& groupName); void renameContactRequest(const QString& jid, const QString& newName); - void requestChangeMessage(const QString& jid, const QString& messageId, const QMap& data); void setRoomJoined(const QString& jid, bool joined); void setRoomAutoJoin(const QString& jid, bool joined); 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(); @@ -128,14 +127,14 @@ signals: void removePresence(const QString& jid, const QString& name); void message(const Shared::Message& data); void changeMessage(const QString& jid, const QString& id, const QMap& data); - void responseArchive(const QString& jid, const std::list& list, bool last); + void responseArchive(const QString& jid, const std::list& list); void error(const QString& text); void addRoomParticipant(const QString& jid, const QString& nickName, const QMap& data); void changeRoomParticipant(const QString& jid, const QString& nickName, const QMap& data); void removeRoomParticipant(const QString& jid, const QString& nickName); void receivedVCard(const QString& jid, const Shared::VCard& card); void uploadFile(const QFileInfo& file, const QUrl& set, const QUrl& get, QMap headers); - void uploadFileError(const QString& jid, const QString& messageId, const QString& error); + void uploadFileError(const QString& messageId, const QString& error); private: QString name; @@ -184,7 +183,6 @@ private slots: void onDiscoveryItemsReceived (const QXmppDiscoveryIq& items); void onDiscoveryInfoReceived (const QXmppDiscoveryIq& info); - void onContactHistoryResponse(const std::list& list, bool last); private: void handleDisconnection(); diff --git a/core/archive.cpp b/core/archive.cpp index 2582ff9..08b12c8 100644 --- a/core/archive.cpp +++ b/core/archive.cpp @@ -58,7 +58,7 @@ 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, 16UL * 1024UL * 1024UL); mdb_env_open(environment, path.toStdString().c_str(), 0, 0664); MDB_txn *txn; @@ -271,8 +271,6 @@ void Core::Archive::changeMessage(const QString& id, const QMap 0; QDateTime oTime = msg.getTime(); bool idChange = msg.change(data); - QDateTime nTime = msg.getTime(); - bool orderChange = oTime != nTime; MDB_val lmdbKey, lmdbData; QByteArray ba; @@ -282,21 +280,15 @@ void Core::Archive::changeMessage(const QString& id, const QMap 0 && (idChange || !hadStanzaId)) { - std::string szid = qsid.toStdString(); + if (msg.getStanzaId().size() > 0 && (idChange || !hadStanzaId)) { + const std::string& szid = msg.getStanzaId().toStdString(); lmdbData.mv_size = szid.size(); lmdbData.mv_data = (char*)szid.c_str(); @@ -511,9 +502,8 @@ long unsigned int Core::Archive::size() const mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn); MDB_stat stat; mdb_stat(txn, order, &stat); - size_t amount = stat.ms_entries; mdb_txn_abort(txn); - return amount; + return stat.ms_entries; } std::list Core::Archive::getBefore(int count, const QString& id) @@ -613,10 +603,10 @@ void Core::Archive::setFromTheBeginning(bool is) MDB_txn *txn; mdb_txn_begin(environment, NULL, 0, &txn); bool success = setStatValue("beginning", is, txn); - if (success) { - mdb_txn_commit(txn); - } else { + if (success != 0) { mdb_txn_abort(txn); + } else { + mdb_txn_commit(txn); } } } diff --git a/core/archive.h b/core/archive.h index 47c62dc..dd7a167 100644 --- a/core/archive.h +++ b/core/archive.h @@ -25,7 +25,7 @@ #include #include "shared/message.h" -#include "shared/exception.h" +#include "exception.h" #include #include diff --git a/core/handlers/CMakeLists.txt b/core/handlers/CMakeLists.txt deleted file mode 100644 index 6da2ef3..0000000 --- a/core/handlers/CMakeLists.txt +++ /dev/null @@ -1,6 +0,0 @@ -target_sources(squawk PRIVATE - messagehandler.cpp - messagehandler.h - rosterhandler.cpp - rosterhandler.h - ) diff --git a/core/handlers/messagehandler.cpp b/core/handlers/messagehandler.cpp index 33b3458..0f0e09d 100644 --- a/core/handlers/messagehandler.cpp +++ b/core/handlers/messagehandler.cpp @@ -23,6 +23,7 @@ Core::MessageHandler::MessageHandler(Core::Account* account): QObject(), acc(account), pendingStateMessages(), + pendingMessages(), uploadingSlotsQueue() { } @@ -73,7 +74,8 @@ void Core::MessageHandler::onMessageReceived(const QXmppMessage& msg) bool Core::MessageHandler::handleChatMessage(const QXmppMessage& msg, bool outgoing, bool forwarded, bool guessing) { - if (msg.body().size() != 0 || msg.outOfBandUrl().size() > 0) { + const QString& body(msg.body()); + if (body.size() != 0) { Shared::Message sMsg(Shared::Message::chat); initializeMessage(sMsg, msg, outgoing, forwarded, guessing); QString jid = sMsg.getPenPalJid(); @@ -166,16 +168,14 @@ void Core::MessageHandler::initializeMessage(Shared::Message& target, const QXmp id = source.id(); #endif target.setId(id); - QString messageId = target.getId(); - if (messageId.size() == 0) { + if (target.getId().size() == 0) { target.generateRandomId(); //TODO out of desperation, I need at least a random ID - messageId = target.getId(); } target.setFrom(source.from()); target.setTo(source.to()); target.setBody(source.body()); target.setForwarded(forwarded); - + target.setOutOfBandUrl(source.outOfBandUrl()); if (guessing) { if (target.getFromJid() == acc->getLogin() + "@" + acc->getServer()) { outgoing = true; @@ -189,12 +189,6 @@ void Core::MessageHandler::initializeMessage(Shared::Message& target, const QXmp } else { target.setCurrentTime(); } - - QString oob = source.outOfBandUrl(); - if (oob.size() > 0) { - target.setAttachPath(acc->network->addMessageAndCheckForPath(oob, acc->getName(), target.getPenPalJid(), messageId)); - } - target.setOutOfBandUrl(oob); } void Core::MessageHandler::logMessage(const QXmppMessage& msg, const QString& reason) @@ -233,29 +227,16 @@ void Core::MessageHandler::onReceiptReceived(const QString& jid, const QString& if (ri != 0) { ri->changeMessage(id, cData); } - emit acc->changeMessage(itr->second, id, cData); pendingStateMessages.erase(itr); + emit acc->changeMessage(itr->second, id, cData); } } -void Core::MessageHandler::sendMessage(const Shared::Message& data, bool newMessage) -{ - if (data.getOutOfBandUrl().size() == 0 && data.getAttachPath().size() > 0) { - prepareUpload(data, newMessage); - } else { - performSending(data, newMessage); - } -} - -void Core::MessageHandler::performSending(Shared::Message data, bool newMessage) +void Core::MessageHandler::sendMessage(Shared::Message data) { QString jid = data.getPenPalJid(); QString id = data.getId(); - QString oob = data.getOutOfBandUrl(); 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()); @@ -264,18 +245,23 @@ void Core::MessageHandler::performSending(Shared::Message data, bool newMessage) #endif msg.setId(id); msg.setType(static_cast(data.getType())); //it is safe here, my type is compatible - msg.setOutOfBandUrl(oob); + msg.setOutOfBandUrl(data.getOutOfBandUrl()); msg.setReceiptRequested(true); - msg.setStamp(sendTime); - sent = acc->client.sendPacket(msg); - //sent = false; + bool sent = acc->client.sendPacket(msg); if (sent) { data.setState(Shared::Message::State::sent); } else { data.setState(Shared::Message::State::error); - data.setErrorText("Couldn't send message: internal QXMPP library error, probably need to check out the logs"); + data.setErrorText("Couldn't send message via QXMPP library check out logs"); + } + + if (ri != 0) { + ri->appendMessageToArchive(data); + if (sent) { + pendingStateMessages.insert(std::make_pair(id, jid)); + } } } else { @@ -283,97 +269,57 @@ void Core::MessageHandler::performSending(Shared::Message data, bool newMessage) data.setErrorText("You are is offline or reconnecting"); } - Shared::Message::State mstate = data.getState(); - changes.insert("state", static_cast(mstate)); - if (mstate == Shared::Message::State::error) { - changes.insert("errorText", data.getErrorText()); - } - if (oob.size() > 0) { - changes.insert("outOfBandUrl", oob); - } - if (newMessage) { - data.setTime(sendTime); - } - changes.insert("stamp", sendTime); - - if (ri != 0) { - if (newMessage) { - ri->appendMessageToArchive(data); - } else { - ri->changeMessage(id, changes); - } - if (sent) { - pendingStateMessages.insert(std::make_pair(id, jid)); - } else { - pendingStateMessages.erase(id); - } - } - - emit acc->changeMessage(jid, id, changes); + emit acc->changeMessage(jid, id, { + {"state", static_cast(data.getState())}, + {"errorText", data.getErrorText()} + }); } -void Core::MessageHandler::prepareUpload(const Shared::Message& data, bool newMessage) +void Core::MessageHandler::sendMessage(const Shared::Message& data, const QString& path) { if (acc->state == Shared::ConnectionState::connected) { - QString jid = data.getPenPalJid(); - QString id = data.getId(); - RosterItem* ri = acc->rh->getRosterItem(jid); - if (!ri) { - qDebug() << "An attempt to initialize upload in" << acc->name << "for pal" << jid << "but the object for this pal wasn't found, something went terrebly wrong, skipping send"; - return; - } - QString path = data.getAttachPath(); QString url = acc->network->getFileRemoteUrl(path); if (url.size() != 0) { - sendMessageWithLocalUploadedFile(data, url, newMessage); + sendMessageWithLocalUploadedFile(data, url); } else { - pendingStateMessages.insert(std::make_pair(id, jid)); - if (newMessage) { - ri->appendMessageToArchive(data); + if (acc->network->isUploading(path, data.getId())) { + pendingMessages.emplace(data.getId(), data); } 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()) { - pendingStateMessages.insert(std::make_pair(id, jid)); - uploadingSlotsQueue.emplace_back(path, id); + uploadingSlotsQueue.emplace_back(path, data); if (uploadingSlotsQueue.size() == 1) { acc->um->requestUploadSlot(file); } } else { - handleUploadError(jid, id, "Uploading file no longer exists or your system user has no permission to read it"); + onFileUploadError(data.getId(), "Uploading file no longer exists or your system user has no permission to read it"); qDebug() << "Requested upload slot in account" << acc->name << "for file" << path << "but the file doesn't exist or is not readable"; } } else { - handleUploadError(jid, id, "Your server doesn't support file upload service, or it's prohibited for your account"); + onFileUploadError(data.getId(), "Your server doesn't support file upload service, or it's prohibited for your account"); qDebug() << "Requested upload slot in account" << acc->name << "for file" << path << "but upload manager didn't discover any upload services"; } } } } else { - handleUploadError(data.getPenPalJid(), data.getId(), "Account is offline or reconnecting"); + onFileUploadError(data.getId(), "Account is offline or reconnecting"); qDebug() << "An attempt to send message with not connected account " << acc->name << ", skipping"; } } + void Core::MessageHandler::onUploadSlotReceived(const QXmppHttpUploadSlotIq& slot) { if (uploadingSlotsQueue.size() == 0) { qDebug() << "HTTP Upload manager of account" << acc->name << "reports about success requesting upload slot, but none was requested"; } else { - const std::pair& pair = uploadingSlotsQueue.front(); - const QString& mId = pair.second; - QString palJid = pendingStateMessages.at(mId); - acc->network->uploadFile({acc->name, palJid, mId}, pair.first, slot.putUrl(), slot.getUrl(), slot.putHeaders()); - + const std::pair& pair = uploadingSlotsQueue.front(); + const QString& mId = pair.second.getId(); + acc->network->uploadFile(mId, pair.first, slot.putUrl(), slot.getUrl(), slot.putHeaders()); + pendingMessages.emplace(mId, pair.second); uploadingSlotsQueue.pop_front(); + if (uploadingSlotsQueue.size() > 0) { acc->um->requestUploadSlot(uploadingSlotsQueue.front().first); } @@ -382,130 +328,44 @@ void Core::MessageHandler::onUploadSlotReceived(const QXmppHttpUploadSlotIq& slo void Core::MessageHandler::onUploadSlotRequestFailed(const QXmppHttpUploadRequestIq& request) { - QString err(request.error().text()); if (uploadingSlotsQueue.size() == 0) { qDebug() << "HTTP Upload manager of account" << acc->name << "reports about an error requesting upload slot, but none was requested"; - qDebug() << err; + qDebug() << request.error().text(); } else { - const std::pair& pair = uploadingSlotsQueue.front(); - qDebug() << "Error requesting upload slot for file" << pair.first << "in account" << acc->name << ":" << err; - handleUploadError(pendingStateMessages.at(pair.second), pair.second, err); + const std::pair& pair = uploadingSlotsQueue.front(); + qDebug() << "Error requesting upload slot for file" << pair.first << "in account" << acc->name << ":" << request.error().text(); + emit acc->uploadFileError(pair.second.getId(), "Error requesting slot to upload file: " + request.error().text()); - uploadingSlotsQueue.pop_front(); if (uploadingSlotsQueue.size() > 0) { acc->um->requestUploadSlot(uploadingSlotsQueue.front().first); } + uploadingSlotsQueue.pop_front(); } } -void Core::MessageHandler::onDownloadFileComplete(const std::list& msgs, const QString& path) +void Core::MessageHandler::onFileUploaded(const QString& messageId, const QString& url) { - QMap cData = { - {"attachPath", path} - }; - for (const Shared::MessageInfo& info : msgs) { - if (info.account == acc->getName()) { - RosterItem* cnt = acc->rh->getRosterItem(info.jid); - if (cnt != 0) { - if (cnt->changeMessage(info.messageId, cData)) { - emit acc->changeMessage(info.jid, info.messageId, cData); - } - } - } + std::map::const_iterator itr = pendingMessages.find(messageId); + if (itr != pendingMessages.end()) { + sendMessageWithLocalUploadedFile(itr->second, url); + pendingMessages.erase(itr); } } -void Core::MessageHandler::onLoadFileError(const std::list& msgs, const QString& text, bool up) +void Core::MessageHandler::onFileUploadError(const QString& messageId, const QString& errMsg) { - if (up) { - for (const Shared::MessageInfo& info : msgs) { - if (info.account == acc->getName()) { - handleUploadError(info.jid, info.messageId, text); - } - } + std::map::const_iterator itr = pendingMessages.find(messageId); + if (itr != pendingMessages.end()) { + pendingMessages.erase(itr); } } -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); - requestChangeMessage(jid, messageId, { - {"state", static_cast(Shared::Message::State::error)}, - {"errorText", errorText} - }); -} - -void Core::MessageHandler::onUploadFileComplete(const std::list& msgs, 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); - } else { - qDebug() << "A signal received about complete upload to" << acc->name << "for pal" << info.jid << "but the object for this pal wasn't found, something went terrebly wrong, skipping send"; - } - } - } -} - -void Core::MessageHandler::sendMessageWithLocalUploadedFile(Shared::Message msg, const QString& url, bool newMessage) +void Core::MessageHandler::sendMessageWithLocalUploadedFile(Shared::Message msg, const QString& url) { msg.setOutOfBandUrl(url); - 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); + if (msg.getBody().size() == 0) { + msg.setBody(url); + } + sendMessage(msg); //TODO removal/progress update } - -static const std::set allowerToChangeKeys({ - "attachPath", - "outOfBandUrl", - "state", - "errorText" -}); - -void Core::MessageHandler::requestChangeMessage(const QString& jid, const QString& messageId, const QMap& data) -{ - RosterItem* cnt = acc->rh->getRosterItem(jid); - 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 - } - if (allSupported) { - cnt->changeMessage(messageId, data); - emit acc->changeMessage(jid, messageId, data); - } else { - qDebug() << "A request to change message" << messageId << "of conversation" << jid << "with following data" << data; - qDebug() << "only limited set of dataFields are supported yet here, and" << unsupportedString << "isn't one of them, skipping"; - } - } -} - -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 4eb9265..be1545f 100644 --- a/core/handlers/messagehandler.h +++ b/core/handlers/messagehandler.h @@ -28,7 +28,6 @@ #include #include -#include namespace Core { @@ -45,9 +44,9 @@ public: MessageHandler(Account* account); public: - void sendMessage(const Shared::Message& data, bool newMessage = true); + void sendMessage(Shared::Message data); + void sendMessage(const Shared::Message& data, const QString& path); 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); @@ -56,24 +55,20 @@ public slots: void onReceiptReceived(const QString& jid, const QString& id); 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 onLoadFileError(const std::list& msgs, const QString& path, bool up); - void requestChangeMessage(const QString& jid, const QString& messageId, const QMap& data); + void onFileUploaded(const QString& messageId, const QString& url); + void onFileUploadError(const QString& messageId, const QString& errMsg); private: bool handleChatMessage(const QXmppMessage& msg, bool outgoing = false, bool forwarded = false, bool guessing = false); bool handleGroupMessage(const QXmppMessage& msg, bool outgoing = false, bool forwarded = false, bool guessing = false); void logMessage(const QXmppMessage& msg, const QString& reason = "Message wasn't handled: "); - void sendMessageWithLocalUploadedFile(Shared::Message msg, const QString& url, bool newMessage = true); - void performSending(Shared::Message data, bool newMessage = true); - void prepareUpload(const Shared::Message& data, bool newMessage = true); - void handleUploadError(const QString& jid, const QString& messageId, const QString& errorText); + void sendMessageWithLocalUploadedFile(Shared::Message msg, const QString& url); private: Account* acc; - std::map pendingStateMessages; //key is message id, value is JID - std::deque> uploadingSlotsQueue; + std::map pendingStateMessages; + std::map pendingMessages; + std::deque> uploadingSlotsQueue; }; } diff --git a/core/handlers/rosterhandler.cpp b/core/handlers/rosterhandler.cpp index ce5f1b7..82ca8c3 100644 --- a/core/handlers/rosterhandler.cpp +++ b/core/handlers/rosterhandler.cpp @@ -190,7 +190,7 @@ void Core::RosterHandler::removeContactRequest(const QString& jid) void Core::RosterHandler::handleNewRosterItem(Core::RosterItem* contact) { connect(contact, &RosterItem::needHistory, this->acc, &Account::onContactNeedHistory); - connect(contact, &RosterItem::historyResponse, this->acc, &Account::onContactHistoryResponse); + connect(contact, &RosterItem::historyResponse, this, &RosterHandler::onContactHistoryResponse); connect(contact, &RosterItem::nameChanged, this, &RosterHandler::onContactNameChanged); connect(contact, &RosterItem::avatarChanged, this, &RosterHandler::onContactAvatarChanged); connect(contact, &RosterItem::requestVCard, this->acc, &Account::requestVCard); @@ -315,6 +315,14 @@ void Core::RosterHandler::removeFromGroup(const QString& jid, const QString& gro } } +void Core::RosterHandler::onContactHistoryResponse(const std::list& list) +{ + RosterItem* contact = static_cast(sender()); + + qDebug() << "Collected history for contact " << contact->jid << list.size() << "elements"; + emit acc->responseArchive(contact->jid, list); +} + Core::RosterItem * Core::RosterHandler::getRosterItem(const QString& jid) { RosterItem* item = 0; diff --git a/core/handlers/rosterhandler.h b/core/handlers/rosterhandler.h index b1dfc45..c01f396 100644 --- a/core/handlers/rosterhandler.h +++ b/core/handlers/rosterhandler.h @@ -86,6 +86,7 @@ private slots: void onContactGroupRemoved(const QString& group); void onContactNameChanged(const QString& name); void onContactSubscriptionStateChanged(Shared::SubscriptionState state); + void onContactHistoryResponse(const std::list& list); void onContactAvatarChanged(Shared::Avatar, const QString& path); private: diff --git a/core/networkaccess.cpp b/core/networkaccess.cpp index 69fe812..2d66a70 100644 --- a/core/networkaccess.cpp +++ b/core/networkaccess.cpp @@ -16,17 +16,13 @@ * along with this program. If not, see . */ - -#include -#include - #include "networkaccess.h" Core::NetworkAccess::NetworkAccess(QObject* parent): QObject(parent), running(false), manager(0), - storage("fileURLStorage"), + files("files"), downloads(), uploads() { @@ -37,31 +33,60 @@ Core::NetworkAccess::~NetworkAccess() stop(); } -void Core::NetworkAccess::downladFile(const QString& url) +void Core::NetworkAccess::fileLocalPathRequest(const QString& messageId, const QString& url) { std::map::iterator itr = downloads.find(url); if (itr != downloads.end()) { - qDebug() << "NetworkAccess received a request to download a file" << url << ", but the file is currently downloading, skipping"; + Transfer* dwn = itr->second; + std::set::const_iterator mItr = dwn->messages.find(messageId); + if (mItr == dwn->messages.end()) { + dwn->messages.insert(messageId); + } + emit downloadFileProgress(messageId, dwn->progress); } else { try { - std::pair> p = storage.getPath(url); - if (p.first.size() > 0) { - QFileInfo info(p.first); - if (info.exists() && info.isFile()) { - emit downloadFileComplete(p.second, p.first); - } else { - startDownload(p.second, url); - } + QString path = files.getRecord(url); + QFileInfo info(path); + if (info.exists() && info.isFile()) { + emit fileLocalPathResponse(messageId, path); } else { - startDownload(p.second, url); + files.removeRecord(url); + emit fileLocalPathResponse(messageId, ""); } } catch (const Archive::NotFound& e) { - qDebug() << "NetworkAccess received a request to download a file" << url << ", but there is now record of which message uses that file, downloading anyway"; - storage.addFile(url); - startDownload(std::list(), url); + emit fileLocalPathResponse(messageId, ""); } catch (const Archive::Unknown& e) { qDebug() << "Error requesting file path:" << e.what(); - emit loadFileError(std::list(), QString("Database error: ") + e.what(), false); + emit fileLocalPathResponse(messageId, ""); + } + } +} + +void Core::NetworkAccess::downladFileRequest(const QString& messageId, const QString& url) +{ + std::map::iterator itr = downloads.find(url); + if (itr != downloads.end()) { + Transfer* dwn = itr->second; + std::set::const_iterator mItr = dwn->messages.find(messageId); + if (mItr == dwn->messages.end()) { + dwn->messages.insert(messageId); + } + emit downloadFileProgress(messageId, dwn->progress); + } else { + try { + QString path = files.getRecord(url); + QFileInfo info(path); + if (info.exists() && info.isFile()) { + emit fileLocalPathResponse(messageId, path); + } else { + files.removeRecord(url); + startDownload(messageId, url); + } + } catch (const Archive::NotFound& e) { + startDownload(messageId, url); + } catch (const Archive::Unknown& e) { + qDebug() << "Error requesting file path:" << e.what(); + emit downloadFileError(messageId, QString("Database error: ") + e.what()); } } } @@ -70,10 +95,7 @@ void Core::NetworkAccess::start() { if (!running) { manager = new QNetworkAccessManager(); -#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) - manager->setTransferTimeout(); -#endif - storage.open(); + files.open(); running = true; } } @@ -81,7 +103,7 @@ void Core::NetworkAccess::start() void Core::NetworkAccess::stop() { if (running) { - storage.close(); + files.close(); manager->deleteLater(); manager = 0; running = false; @@ -102,56 +124,35 @@ 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; - if (dwn->success) { - qreal received = bytesReceived; - qreal total = bytesTotal; - qreal progress = received/total; - dwn->progress = progress; - emit loadFileProgress(dwn->messages, progress, false); + qreal received = bytesReceived; + qreal total = bytesTotal; + qreal progress = received/total; + dwn->progress = progress; + for (std::set::const_iterator mItr = dwn->messages.begin(), end = dwn->messages.end(); mItr != end; ++mItr) { + emit downloadFileProgress(*mItr, progress); } } } 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); - //} + for (std::set::const_iterator mItr = dwn->messages.begin(), end = dwn->messages.end(); mItr != end; ++mItr) { + emit downloadFileError(*mItr, errorText); + } + } } } -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(""); @@ -174,11 +175,7 @@ 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() - //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"; + //this means I closed it myself by abort() or close(), don't think I need to notify here break; case QNetworkReply::SslHandshakeFailedError: errorText = "Security error"; //TODO need to handle sslErrors signal to get a better description here @@ -279,63 +276,64 @@ QString Core::NetworkAccess::getErrorText(QNetworkReply::NetworkError code) void Core::NetworkAccess::onDownloadFinished() { - qDebug() << "DEBUG: DOWNLOAD FINISHED"; + QString path(""); QNetworkReply* rpl = static_cast(sender()); 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 done but there is no record of it being downloaded, ignoring"; + qDebug() << "an error downloading" << url << ": the request is done but seems like noone is waiting for it, skipping"; } else { 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"; + QStringList parts = fileName.split("."); + path = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation) + "/"; + QString suffix(""); + QStringList::const_iterator sItr = parts.begin(); + QString realName = *sItr; + ++sItr; + for (QStringList::const_iterator sEnd = parts.end(); sItr != sEnd; ++sItr) { + suffix += "." + (*sItr); } - QString path = prepareDirectory(jid); - if (path.size() > 0) { - path = checkFileName(fileName, path); - - QFile file(path); - if (file.open(QIODevice::WriteOnly)) { - file.write(dwn->reply->readAll()); - file.close(); - storage.setPath(url, path); - qDebug() << "file" << path << "was successfully downloaded"; - } else { - qDebug() << "couldn't save file" << path; - err = "Error opening file to write:" + file.errorString(); - } - } else { - err = "Couldn't prepare a directory for file"; + QString postfix(""); + QFileInfo proposedName(path + realName + postfix + suffix); + int counter = 0; + while (proposedName.exists()) { + postfix = QString("(") + std::to_string(++counter).c_str() + ")"; + proposedName = QFileInfo(path + realName + postfix + suffix); } - if (path.size() > 0) { - emit downloadFileComplete(dwn->messages, path); + path = proposedName.absoluteFilePath(); + QFile file(path); + if (file.open(QIODevice::WriteOnly)) { + file.write(dwn->reply->readAll()); + file.close(); + files.addRecord(url, path); + qDebug() << "file" << path << "was successfully downloaded"; } else { - emit loadFileError(dwn->messages, "Error saving file " + url + "; " + err, false); + qDebug() << "couldn't save file" << path; + path = ""; } } + for (std::set::const_iterator mItr = dwn->messages.begin(), end = dwn->messages.end(); mItr != end; ++mItr) { + emit fileLocalPathResponse(*mItr, path); + } + dwn->reply->deleteLater(); delete dwn; downloads.erase(itr); } } -void Core::NetworkAccess::startDownload(const std::list& msgs, const QString& url) +void Core::NetworkAccess::startDownload(const QString& messageId, const QString& url) { - Transfer* dwn = new Transfer({msgs, 0, 0, true, "", url, 0}); + Transfer* dwn = new Transfer({{messageId}, 0, 0, true, "", url, 0}); 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 @@ -343,7 +341,7 @@ void Core::NetworkAccess::startDownload(const std::list& ms #endif connect(dwn->reply, &QNetworkReply::finished, this, &NetworkAccess::onDownloadFinished); downloads.insert(std::make_pair(url, dwn)); - emit loadFileProgress(dwn->messages, 0, false); + emit downloadFileProgress(messageId, 0); } void Core::NetworkAccess::onUploadError(QNetworkReply::NetworkError code) @@ -352,16 +350,16 @@ void Core::NetworkAccess::onUploadError(QNetworkReply::NetworkError code) QString url = rpl->url().toString(); std::map::const_iterator itr = uploads.find(url); if (itr == uploads.end()) { - qDebug() << "an error uploading" << url << ": the request is reporting an error but there is no record of it being uploading, ignoring"; + qDebug() << "an error uploading" << url << ": the request is reporting an error but seems like noone is waiting for it, skipping"; } 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? + for (std::set::const_iterator mItr = upl->messages.begin(), end = upl->messages.end(); mItr != end; ++mItr) { + emit uploadFileError(*mItr, errorText); + } + } } } @@ -371,14 +369,17 @@ void Core::NetworkAccess::onUploadFinished() QString url = rpl->url().toString(); std::map::const_iterator itr = uploads.find(url); if (itr == downloads.end()) { - qDebug() << "an error uploading" << url << ": the request is done there is no record of it being uploading, ignoring"; + qDebug() << "an error uploading" << url << ": the request is done but seems like no one is waiting for it, skipping"; } else { 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); + files.addRecord(upl->url, upl->path); + + for (std::set::const_iterator mItr = upl->messages.begin(), end = upl->messages.end(); mItr != end; ++mItr) { + emit fileLocalPathResponse(*mItr, upl->path); + emit uploadFileComplete(*mItr, upl->url); + } } upl->reply->deleteLater(); @@ -398,35 +399,98 @@ 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; - if (upl->success) { - qreal received = bytesReceived; - qreal total = bytesTotal; - qreal progress = received/total; - upl->progress = progress; - emit loadFileProgress(upl->messages, progress, true); + qreal received = bytesReceived; + qreal total = bytesTotal; + qreal progress = received/total; + upl->progress = progress; + for (std::set::const_iterator mItr = upl->messages.begin(), end = upl->messages.end(); mItr != end; ++mItr) { + emit uploadFileProgress(*mItr, progress); + } + } +} + +void Core::NetworkAccess::startUpload(const QString& messageId, const QString& url, const QString& path) +{ + Transfer* upl = new Transfer({{messageId}, 0, 0, true, path, url, 0}); + QNetworkRequest req(url); + QFile* file = new QFile(path); + if (file->open(QIODevice::ReadOnly)) { + upl->reply = manager->put(req, file); + + connect(upl->reply, &QNetworkReply::uploadProgress, this, &NetworkAccess::onUploadProgress); +#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) + connect(upl->reply, qOverload(&QNetworkReply::errorOccurred), this, &NetworkAccess::onUploadError); +#else + connect(upl->reply, qOverload(&QNetworkReply::error), this, &NetworkAccess::onUploadError); +#endif + + connect(upl->reply, &QNetworkReply::finished, this, &NetworkAccess::onUploadFinished); + uploads.insert(std::make_pair(url, upl)); + emit downloadFileProgress(messageId, 0); + } else { + qDebug() << "couldn't upload file" << path; + emit uploadFileError(messageId, "Error opening file"); + delete file; + } +} + +void Core::NetworkAccess::uploadFileRequest(const QString& messageId, const QString& url, const QString& path) +{ + std::map::iterator itr = uploads.find(url); + if (itr != uploads.end()) { + Transfer* upl = itr->second; + std::set::const_iterator mItr = upl->messages.find(messageId); + if (mItr == upl->messages.end()) { + upl->messages.insert(messageId); + } + emit uploadFileProgress(messageId, upl->progress); + } else { + try { + QString ePath = files.getRecord(url); + if (ePath == path) { + QFileInfo info(path); + if (info.exists() && info.isFile()) { + emit fileLocalPathResponse(messageId, path); + } else { + files.removeRecord(url); + startUpload(messageId, url, path); + } + } else { + QFileInfo info(path); + if (info.exists() && info.isFile()) { + files.changeRecord(url, path); + emit fileLocalPathResponse(messageId, path); + } else { + files.removeRecord(url); + startUpload(messageId, url, path); + } + } + } catch (const Archive::NotFound& e) { + startUpload(messageId, url, path); + } catch (const Archive::Unknown& e) { + qDebug() << "Error requesting file path on upload:" << e.what(); + emit uploadFileError(messageId, QString("Database error: ") + e.what()); } } } QString Core::NetworkAccess::getFileRemoteUrl(const QString& path) { - QString p; - - try { - p = storage.getUrl(path); - } catch (const Archive::NotFound& err) { - - } catch (...) { - throw; - } - - return p; + return ""; //TODO this is a way not to upload some file more then 1 time, here I'm supposed to return that file GET url } -void Core::NetworkAccess::uploadFile(const Shared::MessageInfo& info, const QString& path, const QUrl& put, const QUrl& get, const QMap headers) +bool Core::NetworkAccess::isUploading(const QString& path, const QString& messageId) +{ + return false; //TODO this is a way to avoid parallel uploading of the same files by different chats + // message is is supposed to be added to the uploading messageids list + // the result should be true if there was an uploading file with this path + // message id can be empty, then it's just to check and not to add +} + +void Core::NetworkAccess::uploadFile(const QString& messageId, const QString& path, const QUrl& put, const QUrl& get, const QMap headers) { QFile* file = new QFile(path); - Transfer* upl = new Transfer({{info}, 0, 0, true, path, get.toString(), file}); + Transfer* upl = new Transfer({{messageId}, 0, 0, true, path, get.toString(), file}); QNetworkRequest req(put); for (QMap::const_iterator itr = headers.begin(), end = headers.end(); itr != end; itr++) { req.setRawHeader(itr.key().toUtf8(), itr.value().toUtf8()); @@ -442,99 +506,10 @@ void Core::NetworkAccess::uploadFile(const Shared::MessageInfo& info, const QStr #endif connect(upl->reply, &QNetworkReply::finished, this, &NetworkAccess::onUploadFinished); uploads.insert(std::make_pair(put.toString(), upl)); - emit loadFileProgress(upl->messages, 0, true); + emit downloadFileProgress(messageId, 0); } else { qDebug() << "couldn't upload file" << path; - emit loadFileError(upl->messages, "Error opening file", true); + emit uploadFileError(messageId, "Error opening file"); delete file; - delete upl; } } - -void Core::NetworkAccess::registerFile(const QString& url, const QString& account, const QString& jid, const QString& id) -{ - storage.addFile(url, account, jid, id); - std::map::iterator itr = downloads.find(url); - if (itr != downloads.end()) { - itr->second->messages.emplace_back(account, jid, id); //TODO notification is going to happen the next tick, is that okay? - } -} - -void Core::NetworkAccess::registerFile(const QString& url, const QString& path, const QString& account, const QString& jid, const QString& id) -{ - storage.addFile(url, path, account, jid, id); -} - -bool Core::NetworkAccess::checkAndAddToUploading(const QString& acc, const QString& jid, const QString id, const QString path) -{ - for (const std::pair& pair : uploads) { - Transfer* info = pair.second; - if (pair.second->path == path) { - std::list& messages = info->messages; - bool dup = false; - for (const Shared::MessageInfo& info : messages) { - if (info.account == acc && info.jid == jid && info.messageId == id) { - dup = true; - break; - } - } - if (!dup) { - info->messages.emplace_back(acc, jid, id); //TODO notification is going to happen the next tick, is that okay? - return true; - } - } - } - - return false; -} - -QString Core::NetworkAccess::prepareDirectory(const QString& jid) -{ - QString path = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation); - path += "/" + QApplication::applicationName(); - if (jid.size() > 0) { - path += "/" + jid; - } - QDir location(path); - - if (!location.exists()) { - bool res = location.mkpath(path); - if (!res) { - return ""; - } else { - return path; - } - } - return path; -} - -QString Core::NetworkAccess::checkFileName(const QString& name, const QString& path) -{ - QStringList parts = name.split("."); - QString suffix(""); - QStringList::const_iterator sItr = parts.begin(); - QString realName = *sItr; - ++sItr; - for (QStringList::const_iterator sEnd = parts.end(); sItr != sEnd; ++sItr) { - suffix += "." + (*sItr); - } - QString postfix(""); - QFileInfo proposedName(path + "/" + realName + suffix); - int counter = 0; - while (proposedName.exists()) { - QString count = QString("(") + std::to_string(++counter).c_str() + ")"; - proposedName = QFileInfo(path + "/" + realName + count + suffix); - } - - return proposedName.absoluteFilePath(); -} - -QString Core::NetworkAccess::addMessageAndCheckForPath(const QString& url, const QString& account, const QString& jid, const QString& id) -{ - return storage.addMessageAndCheckForPath(url, account, jid, id); -} - -std::list Core::NetworkAccess::reportPathInvalid(const QString& path) -{ - return storage.deletedFile(path); -} diff --git a/core/networkaccess.h b/core/networkaccess.h index 75c189c..824b1af 100644 --- a/core/networkaccess.h +++ b/core/networkaccess.h @@ -29,15 +29,13 @@ #include -#include "urlstorage.h" +#include "storage.h" namespace Core { /** * @todo write docs */ - -//TODO Need to describe how to get rid of records when file is no longer reachable; class NetworkAccess : public QObject { Q_OBJECT @@ -50,32 +48,30 @@ public: void stop(); QString getFileRemoteUrl(const QString& path); - QString addMessageAndCheckForPath(const QString& url, const QString& account, const QString& jid, const QString& id); - void uploadFile(const Shared::MessageInfo& info, const QString& path, const QUrl& put, const QUrl& get, const QMap headers); - bool checkAndAddToUploading(const QString& acc, const QString& jid, const QString id, const QString path); - std::list reportPathInvalid(const QString& path); + bool isUploading(const QString& path, const QString& messageId = ""); + void uploadFile(const QString& messageId, const QString& path, const QUrl& put, const QUrl& get, const QMap headers); 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 downloadFileComplete(const std::list& msgs, const QString& path); + void fileLocalPathResponse(const QString& messageId, const QString& path); + void downloadFileProgress(const QString& messageId, qreal value); + void downloadFileError(const QString& messageId, const QString& path); + void uploadFileProgress(const QString& messageId, qreal value); + void uploadFileError(const QString& messageId, const QString& path); + void uploadFileComplete(const QString& messageId, const QString& url); 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 fileLocalPathRequest(const QString& messageId, const QString& url); + void downladFileRequest(const QString& messageId, const QString& url); + void uploadFileRequest(const QString& messageId, const QString& url, const QString& path); private: - void startDownload(const std::list& msgs, const QString& url); + void startDownload(const QString& messageId, const QString& url); + void startUpload(const QString& messageId, const QString& url, const QString& path); QString getErrorText(QNetworkReply::NetworkError code); - QString prepareDirectory(const QString& jid); - QString checkFileName(const QString& name, const QString& path); private slots: void onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal); void onDownloadError(QNetworkReply::NetworkError code); - void onDownloadSSLError(const QList &errors); void onDownloadFinished(); void onUploadProgress(qint64 bytesReceived, qint64 bytesTotal); void onUploadError(QNetworkReply::NetworkError code); @@ -84,12 +80,12 @@ private slots: private: bool running; QNetworkAccessManager* manager; - UrlStorage storage; + Storage files; std::map downloads; std::map uploads; struct Transfer { - std::list messages; + std::set messages; qreal progress; QNetworkReply* reply; bool success; diff --git a/core/passwordStorageEngines/CMakeLists.txt b/core/passwordStorageEngines/CMakeLists.txt index 4da3873..e824f77 100644 --- a/core/passwordStorageEngines/CMakeLists.txt +++ b/core/passwordStorageEngines/CMakeLists.txt @@ -1,9 +1,37 @@ -if (WITH_KWALLET) - target_sources(squawk PRIVATE - kwallet.cpp - kwallet.h +cmake_minimum_required(VERSION 3.0) +project(pse) + +if (WITH_KWALLET) + set(CMAKE_AUTOMOC ON) + + find_package(Qt5Core CONFIG REQUIRED) + find_package(Qt5Gui CONFIG REQUIRED) + + get_target_property(KWALLET_INTERFACE_INCLUDE_DIRECTORIES KF5::Wallet INTERFACE_INCLUDE_DIRECTORIES) + get_target_property(Qt5GUI_INTERFACE_INCLUDE_DIRECTORIES Qt5::Gui INTERFACE_INCLUDE_DIRECTORIES) + + set(kwalletPSE_SRC + kwallet.cpp + ) + + add_library(kwalletPSE ${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_subdirectory(wrappers) - target_include_directories(squawk PRIVATE $) -endif () + 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}) +endif() diff --git a/core/passwordStorageEngines/wrappers/CMakeLists.txt b/core/passwordStorageEngines/wrappers/CMakeLists.txt deleted file mode 100644 index 6d486c0..0000000 --- a/core/passwordStorageEngines/wrappers/CMakeLists.txt +++ /dev/null @@ -1,2 +0,0 @@ -add_library(kwalletWrapper SHARED kwallet.cpp) -target_link_libraries(kwalletWrapper PRIVATE KF5::Wallet) diff --git a/core/rosteritem.cpp b/core/rosteritem.cpp index b1951d6..32b70f4 100644 --- a/core/rosteritem.cpp +++ b/core/rosteritem.cpp @@ -122,22 +122,7 @@ void Core::RosterItem::nextRequest() { if (syncronizing) { 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; - } - } - } else if (archiveState == empty && responseCache.size() == 0) { - last = true; - } - emit historyResponse(responseCache, last); + emit historyResponse(responseCache); } } if (requestCache.size() > 0) { @@ -375,11 +360,6 @@ void Core::RosterItem::flushMessagesToArchive(bool finished, const QString& firs archiveState = complete; archive->setFromTheBeginning(true); } - if (added == 0 && wasEmpty) { - archiveState = empty; - nextRequest(); - break; - } if (requestedCount != -1) { QString before; if (responseCache.size() > 0) { @@ -398,7 +378,7 @@ void Core::RosterItem::flushMessagesToArchive(bool finished, const QString& firs } catch (const Archive::Empty& e) { } - if (!found || requestedCount > int(responseCache.size())) { + if (!found || requestedCount > responseCache.size()) { if (archiveState == complete) { nextRequest(); } else { @@ -549,7 +529,7 @@ void Core::RosterItem::clearArchiveRequests() requestedBefore = ""; for (const std::pair& pair : requestCache) { if (pair.first != -1) { - emit historyResponse(responseCache, false); //just to notify those who still waits with whatever happened to be left in caches yet + emit historyResponse(responseCache); //just to notify those who still waits with whatever happened to be left in caches yet } responseCache.clear(); } @@ -569,20 +549,3 @@ void Core::RosterItem::downgradeDatabaseState() archiveState = ArchiveState::chunk; } } - -Shared::Message Core::RosterItem::getMessage(const QString& id) -{ - for (const Shared::Message& msg : appendCache) { - if (msg.getId() == id) { - return msg; - } - } - - for (Shared::Message& msg : hisoryCache) { - if (msg.getId() == id) { - return msg; - } - } - - return archive->getElement(id); -} diff --git a/core/rosteritem.h b/core/rosteritem.h index 237a46a..4113b37 100644 --- a/core/rosteritem.h +++ b/core/rosteritem.h @@ -78,12 +78,10 @@ public: void clearArchiveRequests(); void downgradeDatabaseState(); - Shared::Message getMessage(const QString& id); - signals: void nameChanged(const QString& name); void subscriptionStateChanged(Shared::SubscriptionState state); - void historyResponse(const std::list& messages, bool last); + void historyResponse(const std::list& messages); void needHistory(const QString& before, const QString& after, const QDateTime& afterTime = QDateTime()); void avatarChanged(Shared::Avatar, const QString& path); void requestVCard(const QString& jid); diff --git a/core/signalcatcher_win32.cpp b/core/signalcatcher_win32.cpp deleted file mode 100644 index ca7b5a2..0000000 --- a/core/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; -} diff --git a/core/squawk.cpp b/core/squawk.cpp index 6b8af49..42e5cee 100644 --- a/core/squawk.cpp +++ b/core/squawk.cpp @@ -32,10 +32,11 @@ Core::Squawk::Squawk(QObject* parent): ,kwallet() #endif { - connect(&network, &NetworkAccess::loadFileProgress, this, &Squawk::fileProgress); - connect(&network, &NetworkAccess::loadFileError, this, &Squawk::fileError); - connect(&network, &NetworkAccess::downloadFileComplete, this, &Squawk::fileDownloadComplete); - connect(&network, &NetworkAccess::uploadFileComplete, this, &Squawk::fileUploadComplete); + connect(&network, &NetworkAccess::fileLocalPathResponse, this, &Squawk::fileLocalPathResponse); + connect(&network, &NetworkAccess::downloadFileProgress, this, &Squawk::downloadFileProgress); + connect(&network, &NetworkAccess::downloadFileError, this, &Squawk::downloadFileError); + connect(&network, &NetworkAccess::uploadFileProgress, this, &Squawk::uploadFileProgress); + connect(&network, &NetworkAccess::uploadFileError, this, &Squawk::uploadFileError); #ifdef WITH_KWALLET if (kwallet.supportState() == PSE::KWallet::success) { @@ -118,8 +119,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( @@ -167,7 +169,7 @@ void Core::Squawk::addAccount( connect(acc, &Account::receivedVCard, this, &Squawk::responseVCard); - connect(acc, &Account::uploadFileError, this, &Squawk::onAccountUploadFileError); + connect(acc, &Account::uploadFileError, this, &Squawk::uploadFileError); QMap map = { {"login", login}, @@ -328,22 +330,22 @@ 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" << account << ", skipping"; + qDebug("An attempt to send a message with non existing account, skipping"); return; } itr->second->sendMessage(data); } -void Core::Squawk::resendMessage(const QString& account, const QString& jid, const QString& id) +void Core::Squawk::sendMessage(const QString& account, const Shared::Message& data, const QString& path) { AccountsMap::const_iterator itr = amap.find(account); if (itr == amap.end()) { - qDebug() << "An attempt to resend a message with non existing account" << account << ", skipping"; + qDebug("An attempt to send a message with non existing account, skipping"); return; } - itr->second->resendMessage(jid, id); + itr->second->sendMessage(data, path); } void Core::Squawk::requestArchive(const QString& account, const QString& jid, int count, const QString& before) @@ -356,10 +358,10 @@ void Core::Squawk::requestArchive(const QString& account, const QString& jid, in itr->second->requestArchive(jid, count, before); } -void Core::Squawk::onAccountResponseArchive(const QString& jid, const std::list& list, bool last) +void Core::Squawk::onAccountResponseArchive(const QString& jid, const std::list& list) { Account* acc = static_cast(sender()); - emit responseArchive(acc->getName(), jid, list, last); + emit responseArchive(acc->getName(), jid, list); } void Core::Squawk::modifyAccountRequest(const QString& name, const QMap& map) @@ -603,9 +605,14 @@ void Core::Squawk::addRoomRequest(const QString& account, const QString& jid, co itr->second->addRoomRequest(jid, nick, password, autoJoin); } -void Core::Squawk::fileDownloadRequest(const QString& url) +void Core::Squawk::fileLocalPathRequest(const QString& messageId, const QString& url) { - network.downladFile(url); + network.fileLocalPathRequest(messageId, url); +} + +void Core::Squawk::downloadFileRequest(const QString& messageId, const QString& url) +{ + network.downladFileRequest(messageId, url); } void Core::Squawk::addContactToGroupRequest(const QString& account, const QString& jid, const QString& groupName) @@ -682,7 +689,7 @@ void Core::Squawk::readSettings() settings.value("login").toString(), settings.value("server").toString(), settings.value("password", "").toString(), - settings.value("name").toString(), + settings.value("name").toString(), settings.value("resource").toString(), Shared::Global::fromInt(settings.value("passwordType", static_cast(Shared::AccountPassword::plain)).toInt()) ); @@ -756,26 +763,3 @@ void Core::Squawk::onWalletResponsePassword(const QString& login, const QString& emit changeAccount(login, {{"password", password}}); accountReady(); } - -void Core::Squawk::onAccountUploadFileError(const QString& jid, const QString id, const QString& errorText) -{ - Account* acc = static_cast(sender()); - emit fileError({{acc->getName(), jid, id}}, errorText, true); -} - -void Core::Squawk::onLocalPathInvalid(const QString& path) -{ - std::list list = network.reportPathInvalid(path); - - QMap data({ - {"attachPath", ""} - }); - for (const Shared::MessageInfo& info : list) { - AccountsMap::const_iterator itr = amap.find(info.account); - if (itr != amap.end()) { - itr->second->requestChangeMessage(info.jid, info.messageId, data); - } else { - qDebug() << "Reacting on failure to reach file" << path << "there was an attempt to change message in account" << info.account << "which doesn't exist, skipping"; - } - } -} diff --git a/core/squawk.h b/core/squawk.h index 338eb40..604a941 100644 --- a/core/squawk.h +++ b/core/squawk.h @@ -51,39 +51,31 @@ public: signals: void quit(); void ready(); - void newAccount(const QMap&); 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); void removeContact(const QString& account, const QString& jid, const QString& group); 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 responseArchive(const QString& account, const QString& jid, const std::list& list); 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& path); - + void fileLocalPathResponse(const QString& messageId, const QString& path); + void downloadFileError(const QString& messageId, const QString& error); + void downloadFileProgress(const QString& messageId, qreal value); + void uploadFileError(const QString& messageId, const QString& error); + void uploadFileProgress(const QString& messageId, qreal value); 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); @@ -91,19 +83,15 @@ signals: public slots: void start(); void stop(); - void newAccountRequest(const QMap& map); void modifyAccountRequest(const QString& name, const QMap& map); void removeAccountRequest(const QString& name); void connectAccount(const QString& account); void disconnectAccount(const QString& account); - void changeState(Shared::Availability state); - void sendMessage(const QString& account, const Shared::Message& data); - void resendMessage(const QString& account, const QString& jid, const QString& id); + void sendMessage(const QString& account, const Shared::Message& data, const QString& path); void requestArchive(const QString& account, const QString& jid, int count, const QString& before); - void subscribeContact(const QString& account, const QString& jid, const QString& reason); void unsubscribeContact(const QString& account, const QString& jid, const QString& reason); void addContactToGroupRequest(const QString& account, const QString& jid, const QString& groupName); @@ -111,18 +99,15 @@ public slots: void removeContactRequest(const QString& account, const QString& jid); void renameContactRequest(const QString& account, const QString& jid, const QString& newName); void addContactRequest(const QString& account, const QString& jid, const QString& name, const QSet& groups); - void setRoomJoined(const QString& account, const QString& jid, bool joined); void setRoomAutoJoin(const QString& account, const QString& jid, bool joined); void addRoomRequest(const QString& account, const QString& jid, const QString& nick, const QString& password, bool autoJoin); void removeRoomRequest(const QString& account, const QString& jid); - - void fileDownloadRequest(const QString& url); - + void fileLocalPathRequest(const QString& messageId, const QString& url); + void downloadFileRequest(const QString& messageId, 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 onLocalPathInvalid(const QString& path); private: typedef std::deque Accounts; @@ -161,7 +146,7 @@ private slots: void onAccountAddPresence(const QString& jid, const QString& name, const QMap& data); void onAccountRemovePresence(const QString& jid, const QString& name); void onAccountMessage(const Shared::Message& data); - void onAccountResponseArchive(const QString& jid, const std::list& list, bool last); + void onAccountResponseArchive(const QString& jid, const std::list& list); void onAccountAddRoom(const QString jid, const QMap& data); void onAccountChangeRoom(const QString jid, const QMap& data); void onAccountRemoveRoom(const QString jid); @@ -170,8 +155,6 @@ private slots: void onAccountRemoveRoomPresence(const QString& jid, const QString& nick); void onAccountChangeMessage(const QString& jid, const QString& id, const QMap& data); - void onAccountUploadFileError(const QString& jid, const QString id, const QString& errorText); - void onWalletOpened(bool success); void onWalletResponsePassword(const QString& login, const QString& password); void onWalletRejectPassword(const QString& login); @@ -188,7 +171,7 @@ private: Shared::AccountPassword passwordType ); - static const quint64 passwordHash = 0x08d054225ac4871d; + static const quint64 passwordHash = 0x3c5bb27570f50e11; }; } diff --git a/core/urlstorage.cpp b/core/urlstorage.cpp deleted file mode 100644 index f59ff62..0000000 --- a/core/urlstorage.cpp +++ /dev/null @@ -1,491 +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 -#include -#include - -#include "urlstorage.h" - -Core::UrlStorage::UrlStorage(const QString& p_name): - name(p_name), - opened(false), - environment(), - base(), - map() -{ -} - -Core::UrlStorage::~UrlStorage() -{ - close(); -} - -void Core::UrlStorage::open() -{ - if (!opened) { - mdb_env_create(&environment); - QString path(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)); - path += "/" + name; - QDir cache(path); - - if (!cache.exists()) { - bool res = cache.mkpath(path); - if (!res) { - throw Archive::Directory(path.toStdString()); - } - } - - mdb_env_set_maxdbs(environment, 2); - mdb_env_set_mapsize(environment, 10UL * 1024UL * 1024UL); - mdb_env_open(environment, path.toStdString().c_str(), 0, 0664); - - MDB_txn *txn; - mdb_txn_begin(environment, NULL, 0, &txn); - mdb_dbi_open(txn, "base", MDB_CREATE, &base); - mdb_dbi_open(txn, "map", MDB_CREATE, &map); - mdb_txn_commit(txn); - opened = true; - } -} - -void Core::UrlStorage::close() -{ - if (opened) { - mdb_dbi_close(environment, map); - mdb_dbi_close(environment, base); - mdb_env_close(environment); - opened = false; - } -} - -void Core::UrlStorage::writeInfo(const QString& key, const Core::UrlStorage::UrlInfo& info, bool overwrite) -{ - MDB_txn *txn; - mdb_txn_begin(environment, NULL, 0, &txn); - - try { - writeInfo(key, info, txn, overwrite); - mdb_txn_commit(txn); - } catch (...) { - mdb_txn_abort(txn); - throw; - } -} - -void Core::UrlStorage::writeInfo(const QString& key, const Core::UrlStorage::UrlInfo& info, MDB_txn* txn, bool overwrite) -{ - QByteArray ba; - QDataStream ds(&ba, QIODevice::WriteOnly); - info.serialize(ds); - - const std::string& id = key.toStdString(); - MDB_val lmdbKey, lmdbData; - lmdbKey.mv_size = id.size(); - lmdbKey.mv_data = (char*)id.c_str(); - lmdbData.mv_size = ba.size(); - lmdbData.mv_data = (uint8_t*)ba.data(); - - int rc; - rc = mdb_put(txn, base, &lmdbKey, &lmdbData, overwrite ? 0 : MDB_NOOVERWRITE); - - if (rc != 0) { - if (rc == MDB_KEYEXIST) { - if (!overwrite) { - throw Archive::Exist(name.toStdString(), id); - } - } else { - throw Archive::Unknown(name.toStdString(), mdb_strerror(rc)); - } - } - - if (info.hasPath()) { - std::string sp = info.getPath().toStdString(); - lmdbData.mv_size = sp.size(); - lmdbData.mv_data = (char*)sp.c_str(); - rc = mdb_put(txn, map, &lmdbData, &lmdbKey, 0); - if (rc != 0) { - throw Archive::Unknown(name.toStdString(), mdb_strerror(rc)); - } - } -} - -void Core::UrlStorage::readInfo(const QString& key, Core::UrlStorage::UrlInfo& info, MDB_txn* txn) -{ - const std::string& id = key.toStdString(); - MDB_val lmdbKey, lmdbData; - lmdbKey.mv_size = id.size(); - lmdbKey.mv_data = (char*)id.c_str(); - int rc = mdb_get(txn, base, &lmdbKey, &lmdbData); - - if (rc == 0) { - QByteArray ba((char*)lmdbData.mv_data, lmdbData.mv_size); - QDataStream ds(&ba, QIODevice::ReadOnly); - - info.deserialize(ds); - } else if (rc == MDB_NOTFOUND) { - throw Archive::NotFound(id, name.toStdString()); - } else { - throw Archive::Unknown(name.toStdString(), mdb_strerror(rc)); - } -} - -void Core::UrlStorage::readInfo(const QString& key, Core::UrlStorage::UrlInfo& info) -{ - MDB_txn *txn; - mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn); - - try { - readInfo(key, info, txn); - mdb_txn_commit(txn); - } catch (...) { - mdb_txn_abort(txn); - throw; - } -} - -void Core::UrlStorage::addFile(const QString& url) -{ - if (!opened) { - throw Archive::Closed("addFile(no message, no path)", name.toStdString()); - } - - addToInfo(url, "", "", ""); -} - -void Core::UrlStorage::addFile(const QString& url, const QString& path) -{ - if (!opened) { - throw Archive::Closed("addFile(no message, with path)", name.toStdString()); - } - - addToInfo(url, "", "", "", path); -} - -void Core::UrlStorage::addFile(const QString& url, const QString& account, const QString& jid, const QString& id) -{ - if (!opened) { - throw Archive::Closed("addFile(with message, no path)", name.toStdString()); - } - - addToInfo(url, account, jid, id); -} - -void Core::UrlStorage::addFile(const QString& url, const QString& path, const QString& account, const QString& jid, const QString& id) -{ - if (!opened) { - throw Archive::Closed("addFile(with message, with path)", name.toStdString()); - } - - addToInfo(url, account, jid, id, path); -} - -void Core::UrlStorage::addFile(const std::list& msgs, const QString& url, const QString& path) -{ - if (!opened) { - throw Archive::Closed("addFile(with list)", name.toStdString()); - } - - UrlInfo info (path, msgs); - writeInfo(url, info, true);; -} - -QString Core::UrlStorage::addMessageAndCheckForPath(const QString& url, const QString& account, const QString& jid, const QString& id) -{ - if (!opened) { - throw Archive::Closed("addMessageAndCheckForPath", name.toStdString()); - } - - return addToInfo(url, account, jid, id).getPath(); -} - -Core::UrlStorage::UrlInfo Core::UrlStorage::addToInfo(const QString& url, const QString& account, const QString& jid, const QString& id, const QString& path) -{ - UrlInfo info; - MDB_txn *txn; - mdb_txn_begin(environment, NULL, 0, &txn); - - try { - readInfo(url, info, txn); - } catch (const Archive::NotFound& e) { - - } catch (...) { - mdb_txn_abort(txn); - throw; - } - - bool pathChange = false; - bool listChange = false; - if (path != "-s") { - if (info.getPath() != path) { - info.setPath(path); - pathChange = true; - } - } - - if (account.size() > 0 && jid.size() > 0 && id.size() > 0) { - listChange = info.addMessage(account, jid, id); - } - - if (pathChange || listChange) { - try { - writeInfo(url, info, txn, true); - mdb_txn_commit(txn); - } catch (...) { - mdb_txn_abort(txn); - throw; - } - } else { - mdb_txn_abort(txn); - } - - return info; -} - -std::list Core::UrlStorage::setPath(const QString& url, const QString& path) -{ - std::list list; - - MDB_txn *txn; - mdb_txn_begin(environment, NULL, 0, &txn); - UrlInfo info; - - try { - readInfo(url, info, txn); - info.getMessages(list); - } catch (const Archive::NotFound& e) { - } catch (...) { - mdb_txn_abort(txn); - throw; - } - - info.setPath(path); - try { - writeInfo(url, info, txn, true); - mdb_txn_commit(txn); - } catch (...) { - mdb_txn_abort(txn); - throw; - } - - return list; -} - -std::list Core::UrlStorage::removeFile(const QString& url) -{ - std::list list; - - MDB_txn *txn; - mdb_txn_begin(environment, NULL, 0, &txn); - UrlInfo info; - - try { - std::string id = url.toStdString(); - readInfo(url, info, txn); - info.getMessages(list); - - MDB_val lmdbKey; - lmdbKey.mv_size = id.size(); - lmdbKey.mv_data = (char*)id.c_str(); - int rc = mdb_del(txn, base, &lmdbKey, NULL); - if (rc != 0) { - throw Archive::Unknown(name.toStdString(), mdb_strerror(rc)); - } - - if (info.hasPath()) { - std::string path = info.getPath().toStdString(); - lmdbKey.mv_size = path.size(); - lmdbKey.mv_data = (char*)path.c_str(); - - int rc = mdb_del(txn, map, &lmdbKey, NULL); - if (rc != 0) { - throw Archive::Unknown(name.toStdString(), mdb_strerror(rc)); - } - } - mdb_txn_commit(txn); - } catch (...) { - mdb_txn_abort(txn); - throw; - } - - return list; -} - -std::list Core::UrlStorage::deletedFile(const QString& path) -{ - std::list list; - - MDB_txn *txn; - mdb_txn_begin(environment, NULL, 0, &txn); - - try { - std::string spath = path.toStdString(); - - MDB_val lmdbKey, lmdbData; - lmdbKey.mv_size = spath.size(); - lmdbKey.mv_data = (char*)spath.c_str(); - - QString url; - int rc = mdb_get(txn, map, &lmdbKey, &lmdbData); - - if (rc == 0) { - std::string surl((char*)lmdbData.mv_data, lmdbData.mv_size); - url = QString(surl.c_str()); - } else if (rc == MDB_NOTFOUND) { - qDebug() << "Have been asked to remove file" << path << ", which isn't in the database, skipping"; - mdb_txn_abort(txn); - return list; - } else { - throw Archive::Unknown(name.toStdString(), mdb_strerror(rc)); - } - - UrlInfo info; - std::string id = url.toStdString(); - readInfo(url, info, txn); - info.getMessages(list); - info.setPath(QString()); - writeInfo(url, info, txn, true); - - rc = mdb_del(txn, map, &lmdbKey, NULL); - if (rc != 0) { - throw Archive::Unknown(name.toStdString(), mdb_strerror(rc)); - } - - mdb_txn_commit(txn); - } catch (...) { - mdb_txn_abort(txn); - throw; - } - - return list; -} - - -QString Core::UrlStorage::getUrl(const QString& path) -{ - std::list list; - - MDB_txn *txn; - mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn); - - std::string spath = path.toStdString(); - - MDB_val lmdbKey, lmdbData; - lmdbKey.mv_size = spath.size(); - lmdbKey.mv_data = (char*)spath.c_str(); - - QString url; - int rc = mdb_get(txn, map, &lmdbKey, &lmdbData); - - if (rc == 0) { - std::string surl((char*)lmdbData.mv_data, lmdbData.mv_size); - url = QString(surl.c_str()); - - mdb_txn_abort(txn); - return url; - } else if (rc == MDB_NOTFOUND) { - mdb_txn_abort(txn); - throw Archive::NotFound(spath, name.toStdString()); - } else { - mdb_txn_abort(txn); - throw Archive::Unknown(name.toStdString(), mdb_strerror(rc)); - } -} - -std::pair> Core::UrlStorage::getPath(const QString& url) -{ - UrlInfo info; - readInfo(url, info); - std::list container; - info.getMessages(container); - return std::make_pair(info.getPath(), container); -} - -Core::UrlStorage::UrlInfo::UrlInfo(): - localPath(), - messages() {} - -Core::UrlStorage::UrlInfo::UrlInfo(const QString& path): - localPath(path), - messages() {} - -Core::UrlStorage::UrlInfo::UrlInfo(const QString& path, const std::list& msgs): - localPath(path), - messages(msgs) {} - -Core::UrlStorage::UrlInfo::~UrlInfo() {} - -bool Core::UrlStorage::UrlInfo::addMessage(const QString& acc, const QString& jid, const QString& id) -{ - for (const Shared::MessageInfo& info : messages) { - if (info.account == acc && info.jid == jid && info.messageId == id) { - return false; - } - } - messages.emplace_back(acc, jid, id); - return true; -} - -void Core::UrlStorage::UrlInfo::serialize(QDataStream& data) const -{ - data << localPath; - std::list::size_type size = messages.size(); - data << quint32(size); - for (const Shared::MessageInfo& info : messages) { - data << info.account; - data << info.jid; - data << info.messageId; - } -} - -void Core::UrlStorage::UrlInfo::deserialize(QDataStream& data) -{ - data >> localPath; - quint32 size; - data >> size; - for (quint32 i = 0; i < size; ++i) { - messages.emplace_back(); - Shared::MessageInfo& info = messages.back(); - data >> info.account; - data >> info.jid; - data >> info.messageId; - } -} - -void Core::UrlStorage::UrlInfo::getMessages(std::list& container) const -{ - for (const Shared::MessageInfo& info : messages) { - container.emplace_back(info); - } -} - -QString Core::UrlStorage::UrlInfo::getPath() const -{ - return localPath; -} - -bool Core::UrlStorage::UrlInfo::hasPath() const -{ - return localPath.size() > 0; -} - - -void Core::UrlStorage::UrlInfo::setPath(const QString& path) -{ - localPath = path; -} diff --git a/core/urlstorage.h b/core/urlstorage.h deleted file mode 100644 index 3dc5c21..0000000 --- a/core/urlstorage.h +++ /dev/null @@ -1,99 +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 CORE_URLSTORAGE_H -#define CORE_URLSTORAGE_H - -#include -#include -#include -#include - -#include "archive.h" -#include - -namespace Core { - -/** - * @todo write docs - */ -class UrlStorage -{ - class UrlInfo; -public: - UrlStorage(const QString& name); - ~UrlStorage(); - - void open(); - void close(); - - void addFile(const QString& url); - void addFile(const QString& url, const QString& path); - void addFile(const QString& url, const QString& account, const QString& jid, const QString& id); - void addFile(const QString& url, const QString& path, const QString& account, const QString& jid, const QString& id); - void addFile(const std::list& msgs, const QString& url, const QString& path); //this one overwrites all that was - std::list removeFile(const QString& url); //removes entry like it never was in the database, returns affected message infos - std::list deletedFile(const QString& path); //empties the localPath of the entry, returns affected message infos - std::list setPath(const QString& url, const QString& path); - QString getUrl(const QString& path); - QString addMessageAndCheckForPath(const QString& url, const QString& account, const QString& jid, const QString& id); - std::pair> getPath(const QString& url); - -private: - QString name; - bool opened; - MDB_env* environment; - MDB_dbi base; - MDB_dbi map; - -private: - void writeInfo(const QString& key, const UrlInfo& info, bool overwrite = false); - void writeInfo(const QString& key, const UrlInfo& info, MDB_txn* txn, bool overwrite = false); - void readInfo(const QString& key, UrlInfo& info); - void readInfo(const QString& key, UrlInfo& info, MDB_txn* txn); - UrlInfo addToInfo(const QString& url, const QString& account, const QString& jid, const QString& id, const QString& path = "-s"); - -private: - class UrlInfo { - public: - UrlInfo(const QString& path); - UrlInfo(const QString& path, const std::list& msgs); - UrlInfo(); - ~UrlInfo(); - - void serialize(QDataStream& data) const; - void deserialize(QDataStream& data); - - QString getPath() const; - bool hasPath() const; - void setPath(const QString& path); - - bool addMessage(const QString& acc, const QString& jid, const QString& id); - void getMessages(std::list& container) const; - - private: - QString localPath; - std::list messages; - }; - - -}; - -} - -#endif // CORE_URLSTORAGE_H diff --git a/shared/exception.cpp b/exception.cpp similarity index 100% rename from shared/exception.cpp rename to exception.cpp diff --git a/shared/exception.h b/exception.h similarity index 100% rename from shared/exception.h rename to exception.h diff --git a/external/simpleCrypt/CMakeLists.txt b/external/simpleCrypt/CMakeLists.txt index 274d304..bdb62c6 100644 --- a/external/simpleCrypt/CMakeLists.txt +++ b/external/simpleCrypt/CMakeLists.txt @@ -1,10 +1,16 @@ cmake_minimum_required(VERSION 3.0) -project(simplecrypt LANGUAGES CXX) +project(simplecrypt) set(CMAKE_AUTOMOC ON) -find_package(Qt5 COMPONENTS Core REQUIRED) +find_package(Qt5Core CONFIG REQUIRED) -add_library(simpleCrypt STATIC simplecrypt.cpp simplecrypt.h) +set(simplecrypt_SRC + simplecrypt.cpp +) +# Tell CMake to create the helloworld executable +add_library(simpleCrypt ${simplecrypt_SRC}) + +# Use the Widgets module from Qt 5. target_link_libraries(simpleCrypt Qt5::Core) diff --git a/core/main.cpp b/main.cpp similarity index 86% rename from core/main.cpp rename to main.cpp index 0be020e..4ab6253 100644 --- a/core/main.cpp +++ b/main.cpp @@ -16,26 +16,23 @@ * along with this program. If not, see . */ -#include "../shared/global.h" -#include "../shared/messageinfo.h" -#include "../ui/squawk.h" +#include "ui/squawk.h" +#include "core/squawk.h" #include "signalcatcher.h" -#include "squawk.h" -#include -#include -#include -#include -#include -#include +#include "shared/global.h" #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"); @@ -43,6 +40,7 @@ int main(int argc, char *argv[]) QApplication app(argc, argv); SignalCatcher sc(&app); + QApplication::setOrganizationName("squawk"); QApplication::setApplicationName("squawk"); QApplication::setApplicationDisplayName("Squawk"); QApplication::setApplicationVersion("0.1.5"); @@ -99,8 +97,10 @@ int main(int argc, char *argv[]) QObject::connect(&w, &Squawk::connectAccount, squawk, &Core::Squawk::connectAccount); QObject::connect(&w, &Squawk::disconnectAccount, squawk, &Core::Squawk::disconnectAccount); QObject::connect(&w, &Squawk::changeState, squawk, &Core::Squawk::changeState); - QObject::connect(&w, &Squawk::sendMessage, squawk,&Core::Squawk::sendMessage); - QObject::connect(&w, &Squawk::resendMessage, squawk,&Core::Squawk::resendMessage); + QObject::connect(&w, qOverload(&Squawk::sendMessage), + squawk, qOverload(&Core::Squawk::sendMessage)); + QObject::connect(&w, qOverload(&Squawk::sendMessage), + squawk, qOverload(&Core::Squawk::sendMessage)); QObject::connect(&w, &Squawk::requestArchive, squawk, &Core::Squawk::requestArchive); QObject::connect(&w, &Squawk::subscribeContact, squawk, &Core::Squawk::subscribeContact); QObject::connect(&w, &Squawk::unsubscribeContact, squawk, &Core::Squawk::unsubscribeContact); @@ -110,14 +110,14 @@ int main(int argc, char *argv[]) QObject::connect(&w, &Squawk::setRoomAutoJoin, squawk, &Core::Squawk::setRoomAutoJoin); QObject::connect(&w, &Squawk::removeRoomRequest, squawk, &Core::Squawk::removeRoomRequest); QObject::connect(&w, &Squawk::addRoomRequest, squawk, &Core::Squawk::addRoomRequest); - QObject::connect(&w, &Squawk::fileDownloadRequest, squawk, &Core::Squawk::fileDownloadRequest); + QObject::connect(&w, &Squawk::fileLocalPathRequest, squawk, &Core::Squawk::fileLocalPathRequest); + QObject::connect(&w, &Squawk::downloadFileRequest, squawk, &Core::Squawk::downloadFileRequest); 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(squawk, &Core::Squawk::newAccount, &w, &Squawk::newAccount); QObject::connect(squawk, &Core::Squawk::addContact, &w, &Squawk::addContact); @@ -142,10 +142,11 @@ int main(int argc, char *argv[]) QObject::connect(squawk, &Core::Squawk::addRoomParticipant, &w, &Squawk::addRoomParticipant); QObject::connect(squawk, &Core::Squawk::changeRoomParticipant, &w, &Squawk::changeRoomParticipant); QObject::connect(squawk, &Core::Squawk::removeRoomParticipant, &w, &Squawk::removeRoomParticipant); - QObject::connect(squawk, &Core::Squawk::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::fileLocalPathResponse, &w, &Squawk::fileLocalPathResponse); + QObject::connect(squawk, &Core::Squawk::downloadFileProgress, &w, &Squawk::fileProgress); + QObject::connect(squawk, &Core::Squawk::downloadFileError, &w, &Squawk::fileError); + QObject::connect(squawk, &Core::Squawk::uploadFileProgress, &w, &Squawk::fileProgress); + QObject::connect(squawk, &Core::Squawk::uploadFileError, &w, &Squawk::fileError); QObject::connect(squawk, &Core::Squawk::responseVCard, &w, &Squawk::responseVCard); QObject::connect(squawk, &Core::Squawk::requestPassword, &w, &Squawk::requestPassword); QObject::connect(squawk, &Core::Squawk::ready, &w, &Squawk::readSettings); diff --git a/shared/order.h b/order.h similarity index 100% rename from shared/order.h rename to order.h diff --git a/packaging/CMakeLists.txt b/packaging/CMakeLists.txt deleted file mode 100644 index 4965b37..0000000 --- a/packaging/CMakeLists.txt +++ /dev/null @@ -1,3 +0,0 @@ -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/packaging/squawk.desktop b/packaging/squawk.desktop index ba0f13c..0395af1 100644 --- a/packaging/squawk.desktop +++ b/packaging/squawk.desktop @@ -5,10 +5,8 @@ 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/plugins/CMakeLists.txt b/plugins/CMakeLists.txt deleted file mode 100644 index 84fc09b..0000000 --- a/plugins/CMakeLists.txt +++ /dev/null @@ -1,4 +0,0 @@ -if (WITH_KIO) - add_library(openFileManagerWindowJob SHARED openfilemanagerwindowjob.cpp) - target_link_libraries(openFileManagerWindowJob PRIVATE KF5::KIOWidgets) -endif () diff --git a/plugins/openfilemanagerwindowjob.cpp b/plugins/openfilemanagerwindowjob.cpp deleted file mode 100644 index 904fbcf..0000000 --- a/plugins/openfilemanagerwindowjob.cpp +++ /dev/null @@ -1,8 +0,0 @@ -#include -#include -#include - -extern "C" void highlightInFileManager(const QUrl& url) { - KIO::OpenFileManagerWindowJob* job = KIO::highlightInFileManager({url}); - QObject::connect(job, &KIO::OpenFileManagerWindowJob::result, job, &KIO::OpenFileManagerWindowJob::deleteLater); -} diff --git a/resources/CMakeLists.txt b/resources/CMakeLists.txt deleted file mode 100644 index 9288650..0000000 --- a/resources/CMakeLists.txt +++ /dev/null @@ -1,59 +0,0 @@ -target_sources(squawk PRIVATE resources.qrc) - -configure_file(images/logo.svg squawk.svg COPYONLY) -configure_file(squawk.rc squawk.rc COPYONLY) - -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") - set(SQUAWK_WIN_RC "${SQUAWK_WIN_RC}" PARENT_SCOPE) - target_sources(squawk PRIVATE ${SQUAWK_WIN_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(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) -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/resources/images/fallback/dark/big/document-preview.svg b/resources/images/fallback/dark/big/document-preview.svg deleted file mode 100644 index 49a3feb..0000000 --- a/resources/images/fallback/dark/big/document-preview.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - diff --git a/resources/images/fallback/dark/big/folder.svg b/resources/images/fallback/dark/big/folder.svg deleted file mode 100644 index 2acb4ab..0000000 --- a/resources/images/fallback/dark/big/folder.svg +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/resources/images/fallback/dark/small/document-preview.svg b/resources/images/fallback/dark/small/document-preview.svg deleted file mode 100644 index 43d19bf..0000000 --- a/resources/images/fallback/dark/small/document-preview.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - diff --git a/resources/images/fallback/dark/small/folder.svg b/resources/images/fallback/dark/small/folder.svg deleted file mode 100644 index 1061f4d..0000000 --- a/resources/images/fallback/dark/small/folder.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - diff --git a/resources/images/fallback/light/big/document-preview.svg b/resources/images/fallback/light/big/document-preview.svg deleted file mode 100644 index 6f6e346..0000000 --- a/resources/images/fallback/light/big/document-preview.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - diff --git a/resources/images/fallback/light/big/folder.svg b/resources/images/fallback/light/big/folder.svg deleted file mode 100644 index 2acb4ab..0000000 --- a/resources/images/fallback/light/big/folder.svg +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/resources/images/fallback/light/small/document-preview.svg b/resources/images/fallback/light/small/document-preview.svg deleted file mode 100644 index f40fcdf..0000000 --- a/resources/images/fallback/light/small/document-preview.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - diff --git a/resources/images/fallback/light/small/folder.svg b/resources/images/fallback/light/small/folder.svg deleted file mode 100644 index a5f66cd..0000000 --- a/resources/images/fallback/light/small/folder.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - diff --git a/resources/resources.qrc b/resources/resources.qrc index 58565fc..4fb3e5b 100644 --- a/resources/resources.qrc +++ b/resources/resources.qrc @@ -40,8 +40,6 @@ images/fallback/dark/big/favorite.svg images/fallback/dark/big/unfavorite.svg images/fallback/dark/big/add.svg - images/fallback/dark/big/folder.svg - images/fallback/dark/big/document-preview.svg images/fallback/dark/small/absent.svg @@ -82,8 +80,6 @@ images/fallback/dark/small/favorite.svg images/fallback/dark/small/unfavorite.svg images/fallback/dark/small/add.svg - images/fallback/dark/small/folder.svg - images/fallback/dark/small/document-preview.svg images/fallback/light/big/absent.svg @@ -124,8 +120,6 @@ images/fallback/light/big/favorite.svg images/fallback/light/big/unfavorite.svg images/fallback/light/big/add.svg - images/fallback/light/big/folder.svg - images/fallback/light/big/document-preview.svg images/fallback/light/small/absent.svg @@ -166,7 +160,5 @@ images/fallback/light/small/favorite.svg images/fallback/light/small/unfavorite.svg images/fallback/light/small/add.svg - images/fallback/light/small/folder.svg - images/fallback/light/small/document-preview.svg diff --git a/resources/squawk.rc b/resources/squawk.rc deleted file mode 100644 index 6061f20..0000000 --- a/resources/squawk.rc +++ /dev/null @@ -1 +0,0 @@ -IDI_ICON1 ICON "squawk.ico" diff --git a/shared/shared.h b/shared.h similarity index 83% rename from shared/shared.h rename to shared.h index 1e86c5a..83bcd76 100644 --- a/shared/shared.h +++ b/shared.h @@ -19,12 +19,11 @@ #ifndef SHARED_H #define SHARED_H -#include "enums.h" -#include "global.h" -#include "icons.h" -#include "message.h" -#include "messageinfo.h" -#include "utils.h" -#include "vcard.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" #endif // SHARED_H diff --git a/shared/CMakeLists.txt b/shared/CMakeLists.txt deleted file mode 100644 index a36b516..0000000 --- a/shared/CMakeLists.txt +++ /dev/null @@ -1,19 +0,0 @@ -target_sources(squawk PRIVATE - 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/shared/global.cpp b/shared/global.cpp index d6f2169..a6b7b60 100644 --- a/shared/global.cpp +++ b/shared/global.cpp @@ -23,11 +23,6 @@ Shared::Global* Shared::Global::instance = 0; const std::set Shared::Global::supportedImagesExts = {"png", "jpg", "webp", "jpeg", "gif", "svg"}; -#ifdef WITH_KIO -QLibrary Shared::Global::openFileManagerWindowJob("openFileManagerWindowJob"); -Shared::Global::HighlightInFileManager Shared::Global::hfm = 0; -#endif - Shared::Global::Global(): availability({ tr("Online", "Availability"), @@ -85,72 +80,16 @@ Shared::Global::Global(): tr("Your password is going to be stored in KDE wallet storage (KWallet). You're going to be queried for permissions", "AccountPasswordDescription") }), pluginSupport({ - {"KWallet", false}, - {"openFileManagerWindowJob", false} - }), - fileCache() + {"KWallet", false} + }) { if (instance != 0) { throw 551; } instance = this; - -#ifdef WITH_KIO - openFileManagerWindowJob.load(); - if (openFileManagerWindowJob.isLoaded()) { - hfm = (HighlightInFileManager) openFileManagerWindowJob.resolve("highlightInFileManager"); - if (hfm) { - setSupported("openFileManagerWindowJob", true); - qDebug() << "KIO::OpenFileManagerWindow support enabled"; - } else { - qDebug() << "KIO::OpenFileManagerWindow support disabled: couldn't resolve required methods in the library"; - } - } else { - qDebug() << "KIO::OpenFileManagerWindow support disabled: couldn't load the library" << openFileManagerWindowJob.errorString(); - } -#endif } - -static const QSize defaultIconFileInfoHeight(50, 50); -Shared::Global::FileInfo Shared::Global::getFileInfo(const QString& path) -{ - std::map::const_iterator itr = instance->fileCache.find(path); - if (itr == instance->fileCache.end()) { - QMimeDatabase db; - QMimeType type = db.mimeTypeForFile(path); - QStringList parts = type.name().split("/"); - QString big = parts.front(); - QFileInfo info(path); - - FileInfo::Preview p = FileInfo::Preview::none; - QSize size; - if (big == "image") { - QMovie mov(path); - if (mov.isValid() && mov.frameCount() > 1) { - p = FileInfo::Preview::animation; - } else { - p = FileInfo::Preview::picture; - } - QImageReader 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; - } - - itr = instance->fileCache.insert(std::make_pair(path, FileInfo({info.fileName(), size, type, p}))).first; - } - - return itr->second; -} - - Shared::Global * Shared::Global::getInstance() { return instance; @@ -213,69 +152,6 @@ QString Shared::Global::getDescription(Shared::AccountPassword ap) return instance->accountPasswordDescription[static_cast(ap)]; } - -static const QStringList query = {"query", "default", "inode/directory"}; -static const QRegularExpression dolphinReg("[Dd]olphin"); -static const QRegularExpression nautilusReg("[Nn]autilus"); -static const QRegularExpression cajaReg("[Cc]aja"); -static const QRegularExpression nemoReg("[Nn]emo"); -static const QRegularExpression konquerorReg("kfmclient"); -static const QRegularExpression pcmanfmQtReg("pcmanfm-qt"); -static const QRegularExpression pcmanfmReg("pcmanfm"); -static const QRegularExpression thunarReg("thunar"); - -void Shared::Global::highlightInFileManager(const QString& path) -{ -#ifdef WITH_KIO - if (supported("openFileManagerWindowJob")) { - hfm(path); - return; - } else { - qDebug() << "requested to highlight in file manager url" << path << "but it's not supported: KIO plugin isn't loaded, trying fallback"; - } -#else - qDebug() << "requested to highlight in file manager url" << path << "but it's not supported: squawk wasn't compiled to support it, trying fallback"; -#endif - - QFileInfo info = path; - if (info.exists()) { - QProcess proc; - proc.start("xdg-mime", query); - proc.waitForFinished(); - QString output = proc.readLine().simplified(); - - QString folder; - if (info.isDir()) { - folder = info.canonicalFilePath(); - } else { - folder = info.canonicalPath(); - } - - if (output.contains(dolphinReg)) { - //there is a bug on current (21.04.0) dolphin, it works correct only if you already have dolphin launched - proc.startDetached("dolphin", QStringList() << "--select" << info.canonicalFilePath()); - //KIO::highlightInFileManager({QUrl(info.canonicalFilePath())}); - } else if (output.contains(nautilusReg)) { - proc.startDetached("nautilus", QStringList() << "--select" << info.canonicalFilePath()); //this worked on nautilus - } else if (output.contains(cajaReg)) { - proc.startDetached("caja", QStringList() << folder); //caja doesn't seem to support file selection command line, gonna just open directory - } else if (output.contains(nemoReg)) { - proc.startDetached("nemo", QStringList() << info.canonicalFilePath()); //nemo supports selecting files without keys - } else if (output.contains(konquerorReg)) { - proc.startDetached("konqueror", QStringList() << "--select" << info.canonicalFilePath()); //this worked on konqueror - } else if (output.contains(pcmanfmQtReg)) { - proc.startDetached("pcmanfm-qt", QStringList() << folder); //pcmanfm-qt doesn't seem to support open with selection, gonna just open directory - } else if (output.contains(pcmanfmReg)) { - proc.startDetached("pcmanfm", QStringList() << folder); //pcmanfm also doesn't seem to support open with selection, gonna just open directory - } else if (output.contains(thunarReg)) { - proc.startDetached("thunar", QStringList() << folder); //thunar doesn't seem to support open with selection, gonna just open directory - } else { - QDesktopServices::openUrl(QUrl::fromLocalFile(folder)); - } - } -} - - #define FROM_INT_INPL(Enum) \ template<> \ Enum Shared::Global::fromInt(int src) \ diff --git a/shared/global.h b/shared/global.h index 03cf84d..54e1584 100644 --- a/shared/global.h +++ b/shared/global.h @@ -29,18 +29,6 @@ #include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include namespace Shared { @@ -48,19 +36,6 @@ namespace Shared { Q_DECLARE_TR_FUNCTIONS(Global) public: - struct FileInfo { - enum class Preview { - none, - picture, - animation - }; - - QString name; - QSize size; - QMimeType mime; - Preview preview; - }; - Global(); static Global* getInstance(); @@ -89,9 +64,6 @@ namespace Shared { static const std::set supportedImagesExts; - static FileInfo getFileInfo(const QString& path); - static void highlightInFileManager(const QString& path); - template static T fromInt(int src); @@ -115,15 +87,6 @@ namespace Shared { static Global* instance; std::map pluginSupport; - std::map fileCache; - -#ifdef WITH_KIO - static QLibrary openFileManagerWindowJob; - - typedef void (*HighlightInFileManager)(const QUrl &); - - static HighlightInFileManager hfm; -#endif }; } diff --git a/shared/icons.h b/shared/icons.h index 540d3e9..48ecc37 100644 --- a/shared/icons.h +++ b/shared/icons.h @@ -170,8 +170,6 @@ static const std::map> icons = { {"favorite", {"favorite", "favorite"}}, {"unfavorite", {"draw-star", "unfavorite"}}, {"list-add", {"list-add", "add"}}, - {"folder", {"folder", "folder"}}, - {"document-preview", {"document-preview", "document-preview"}} }; } diff --git a/shared/message.cpp b/shared/message.cpp index e6b47b2..af4f9e0 100644 --- a/shared/message.cpp +++ b/shared/message.cpp @@ -36,8 +36,7 @@ Shared::Message::Message(Shared::Message::Type p_type): errorText(), originalMessage(), lastModified(), - stanzaId(), - attachPath() + stanzaId() {} Shared::Message::Message(): @@ -57,8 +56,7 @@ Shared::Message::Message(): errorText(), originalMessage(), lastModified(), - stanzaId(), - attachPath() + stanzaId() {} QString Shared::Message::getBody() const @@ -313,7 +311,6 @@ void Shared::Message::serialize(QDataStream& data) const data << lastModified; } data << stanzaId; - data << attachPath; } void Shared::Message::deserialize(QDataStream& data) @@ -344,7 +341,6 @@ void Shared::Message::deserialize(QDataStream& data) data >> lastModified; } data >> stanzaId; - data >> attachPath; } bool Shared::Message::change(const QMap& data) @@ -354,16 +350,6 @@ bool Shared::Message::change(const QMap& data) setState(static_cast(itr.value().toUInt())); } - itr = data.find("outOfBandUrl"); - if (itr != data.end()) { - setOutOfBandUrl(itr.value().toString()); - } - - itr = data.find("attachPath"); - if (itr != data.end()) { - setAttachPath(itr.value().toString()); - } - if (state == State::error) { itr = data.find("errorText"); if (itr != data.end()) { @@ -394,29 +380,18 @@ bool Shared::Message::change(const QMap& data) itr = data.find("body"); if (itr != data.end()) { - QString b = itr.value().toString(); - if (body != b) { - QMap::const_iterator dItr = data.find("stamp"); - QDateTime correctionDate; - if (dItr != data.end()) { - correctionDate = dItr.value().toDateTime(); - } else { - correctionDate = QDateTime::currentDateTimeUtc(); //in case there is no information about time of this correction it's applied - } - if (!edited || lastModified < correctionDate) { - originalMessage = body; - lastModified = correctionDate; - setBody(body); - setEdited(true); - } - } - } else { QMap::const_iterator dItr = data.find("stamp"); + QDateTime correctionDate; if (dItr != data.end()) { - QDateTime ntime = dItr.value().toDateTime(); - if (time != ntime) { - setTime(ntime); - } + correctionDate = dItr.value().toDateTime(); + } else { + correctionDate = QDateTime::currentDateTimeUtc(); //in case there is no information about time of this correction it's applied + } + if (!edited || lastModified < correctionDate) { + originalMessage = body; + lastModified = correctionDate; + setBody(itr.value().toString()); + setEdited(true); } } @@ -445,7 +420,7 @@ void Shared::Message::setOutOfBandUrl(const QString& url) bool Shared::Message::storable() const { - return id.size() > 0 && (body.size() > 0 || oob.size() > 0 || attachPath.size() > 0); + return id.size() > 0 && (body.size() > 0 || oob.size()) > 0; } void Shared::Message::setStanzaId(const QString& sid) @@ -457,33 +432,3 @@ QString Shared::Message::getStanzaId() const { return stanzaId; } - -QString Shared::Message::getAttachPath() const -{ - return attachPath; -} - -void Shared::Message::setAttachPath(const QString& path) -{ - attachPath = path; -} - -Shared::Message::Change::Change(const QMap& _data): - data(_data), - idModified(false) {} - -void Shared::Message::Change::operator()(Shared::Message& msg) -{ - idModified = msg.change(data); -} - -void Shared::Message::Change::operator()(Shared::Message* msg) -{ - idModified = msg->change(data); -} - -bool Shared::Message::Change::hasIdBeenModified() const -{ - return idModified; -} - diff --git a/shared/message.h b/shared/message.h index aa91af6..d84053f 100644 --- a/shared/message.h +++ b/shared/message.h @@ -16,15 +16,15 @@ * along with this program. If not, see . */ -#ifndef SHAPER_MESSAGE_H -#define SHAPER_MESSAGE_H - #include #include #include #include #include +#ifndef SHAPER_MESSAGE_H +#define SHAPER_MESSAGE_H + namespace Shared { /** @@ -46,22 +46,9 @@ public: delivered, error }; - static const State StateHighest = State::error; static const State StateLowest = State::pending; - struct Change //change functor, stores in idModified if ID has been modified during change - { - Change(const QMap& _data); - void operator() (Message& msg); - void operator() (Message* msg); - bool hasIdBeenModified() const; - - private: - const QMap& data; - bool idModified; - }; - Message(Type p_type); Message(); @@ -85,7 +72,6 @@ public: void setErrorText(const QString& err); bool change(const QMap& data); void setStanzaId(const QString& sid); - void setAttachPath(const QString& path); QString getFrom() const; QString getFromJid() const; @@ -114,7 +100,6 @@ public: QDateTime getLastModified() const; QString getOriginalBody() const; QString getStanzaId() const; - QString getAttachPath() const; void serialize(QDataStream& data) const; void deserialize(QDataStream& data); @@ -138,7 +123,6 @@ private: QString originalMessage; QDateTime lastModified; QString stanzaId; - QString attachPath; }; } diff --git a/shared/messageinfo.cpp b/shared/messageinfo.cpp deleted file mode 100644 index 7502a6e..0000000 --- a/shared/messageinfo.cpp +++ /dev/null @@ -1,45 +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 "messageinfo.h" - -using namespace Shared; - -Shared::MessageInfo::MessageInfo(): - account(), - jid(), - messageId() {} - -Shared::MessageInfo::MessageInfo(const QString& acc, const QString& j, const QString& id): - account(acc), - jid(j), - messageId(id) {} - -Shared::MessageInfo::MessageInfo(const Shared::MessageInfo& other): - account(other.account), - jid(other.jid), - messageId(other.messageId) {} - -Shared::MessageInfo & Shared::MessageInfo::operator=(const Shared::MessageInfo& other) -{ - account = other.account; - jid = other.jid; - messageId = other.messageId; - - return *this; -} diff --git a/shared/messageinfo.h b/shared/messageinfo.h deleted file mode 100644 index 942d88c..0000000 --- a/shared/messageinfo.h +++ /dev/null @@ -1,43 +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 SHARED_MESSAGEINFO_H -#define SHARED_MESSAGEINFO_H - -#include - -namespace Shared { - -/** - * @todo write docs - */ -struct MessageInfo { - MessageInfo(); - MessageInfo(const QString& acc, const QString& j, const QString& id); - MessageInfo(const MessageInfo& other); - - QString account; - QString jid; - QString messageId; - - MessageInfo& operator=(const MessageInfo& other); -}; - -} - -#endif // SHARED_MESSAGEINFO_H diff --git a/shared/utils.h b/shared/utils.h index 6dcb141..8e1e6dd 100644 --- a/shared/utils.h +++ b/shared/utils.h @@ -20,13 +20,10 @@ #define SHARED_UTILS_H #include -#include #include #include -// #include "KIO/OpenFileManagerWindowJob" - -#include +#include namespace Shared { diff --git a/core/signalcatcher.cpp b/signalcatcher.cpp similarity index 100% rename from core/signalcatcher.cpp rename to signalcatcher.cpp diff --git a/core/signalcatcher.h b/signalcatcher.h similarity index 100% rename from core/signalcatcher.h rename to signalcatcher.h diff --git a/signalcatcher_win32.cpp b/signalcatcher_win32.cpp index ca7b5a2..61d43df 100644 --- a/signalcatcher_win32.cpp +++ b/signalcatcher_win32.cpp @@ -1,6 +1,6 @@ /* - * Squawk messenger. - * Copyright (C) 2021 Shunf4 + * 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 diff --git a/squawk.rc b/squawk.rc new file mode 100644 index 0000000..bf45d51 --- /dev/null +++ b/squawk.rc @@ -0,0 +1 @@ +IDI_ICON1 ICON "squawk.ico" \ No newline at end of file diff --git a/translations/CMakeLists.txt b/translations/CMakeLists.txt deleted file mode 100644 index 86d2a8c..0000000 --- a/translations/CMakeLists.txt +++ /dev/null @@ -1,11 +0,0 @@ -find_package(Qt5LinguistTools) - -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) diff --git a/translations/squawk.pt_BR.ts b/translations/squawk.pt_BR.ts deleted file mode 100644 index 4330979..0000000 --- a/translations/squawk.pt_BR.ts +++ /dev/null @@ -1,800 +0,0 @@ - - - - - Account - - Account - Window header - 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 - Default resource - 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 - - - diff --git a/ui/CMakeLists.txt b/ui/CMakeLists.txt index 36207b6..a68ff16 100644 --- a/ui/CMakeLists.txt +++ b/ui/CMakeLists.txt @@ -1,5 +1,49 @@ -target_sources(squawk PRIVATE squawk.cpp squawk.h squawk.ui) +cmake_minimum_required(VERSION 3.0) +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(Qt5Widgets CONFIG REQUIRED) +find_package(Qt5DBus CONFIG REQUIRED) -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 + utils/messageline.cpp + utils//message.cpp + utils/resizer.cpp + utils/image.cpp + utils/flowlayout.cpp + utils/badge.cpp + utils/progress.cpp + utils/comboboxdelegate.cpp + utils/dropshadoweffect.cpp +) + +qt5_wrap_ui(squawkUI_SRC + squawk.ui +) + +# Tell CMake to create the helloworld executable +add_library(squawkUI ${squawkUI_SRC}) + +# Use the Widgets module from Qt 5. +target_link_libraries(squawkUI squawkWidgets) +target_link_libraries(squawkUI Qt5::Widgets) +target_link_libraries(squawkUI Qt5::DBus) diff --git a/ui/models/CMakeLists.txt b/ui/models/CMakeLists.txt deleted file mode 100644 index 629db32..0000000 --- a/ui/models/CMakeLists.txt +++ /dev/null @@ -1,26 +0,0 @@ -target_sources(squawk PRIVATE - 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 - participant.cpp - participant.h - presence.cpp - presence.h - reference.cpp - reference.h - room.cpp - room.h - roster.cpp - roster.h - ) diff --git a/ui/models/account.cpp b/ui/models/account.cpp index 43cb3ed..00dd6b2 100644 --- a/ui/models/account.cpp +++ b/ui/models/account.cpp @@ -31,8 +31,7 @@ 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), - wasEverConnected(false) + passwordType(Shared::AccountPassword::plain) { QMap::const_iterator sItr = data.find("state"); if (sItr != data.end()) { @@ -57,19 +56,8 @@ void Models::Account::setState(Shared::ConnectionState p_state) if (state != p_state) { state = p_state; changed(2); - switch (state) { - case Shared::ConnectionState::disconnected: - toOfflineState(); - break; - case Shared::ConnectionState::connected: - if (wasEverConnected) { - emit reconnected(); - } else { - wasEverConnected = true; - } - break; - default: - break; + if (state == Shared::ConnectionState::disconnected) { + toOfflineState(); } } } @@ -243,7 +231,7 @@ void Models::Account::toOfflineState() Item::toOfflineState(); } -QString Models::Account::getAvatarPath() const +QString Models::Account::getAvatarPath() { return avatarPath; } diff --git a/ui/models/account.h b/ui/models/account.h index 3d2310f..2563382 100644 --- a/ui/models/account.h +++ b/ui/models/account.h @@ -57,7 +57,7 @@ namespace Models { QString getError() const; void setAvatarPath(const QString& path); - QString getAvatarPath() const; + QString getAvatarPath(); void setAvailability(Shared::Availability p_avail); void setAvailability(unsigned int p_avail); @@ -77,9 +77,6 @@ namespace Models { QString getBareJid() const; QString getFullJid() const; - signals: - void reconnected(); - private: QString login; QString password; @@ -90,7 +87,6 @@ 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 a0c70ac..57744d8 100644 --- a/ui/models/contact.cpp +++ b/ui/models/contact.cpp @@ -17,26 +17,55 @@ */ #include "contact.h" +#include "account.h" #include Models::Contact::Contact(const Account* acc, const QString& p_jid ,const QMap &data, Item *parentItem): - Element(Item::contact, acc, p_jid, data, parentItem), + Item(Item::contact, data, parentItem), + jid(p_jid), availability(Shared::Availability::offline), state(Shared::SubscriptionState::none), + avatarState(Shared::Avatar::empty), presences(), - status() + messages(), + childMessages(0), + status(), + avatarPath(), + account(acc) { QMap::const_iterator itr = data.find("state"); if (itr != data.end()) { setState(itr.value().toUInt()); } + + itr = data.find("avatarState"); + if (itr != data.end()) { + setAvatarState(itr.value().toUInt()); + } + itr = data.find("avatarPath"); + if (itr != data.end()) { + setAvatarPath(itr.value().toString()); + } } Models::Contact::~Contact() { } +QString Models::Contact::getJid() const +{ + return jid; +} + +void Models::Contact::setJid(const QString p_jid) +{ + if (jid != p_jid) { + jid = p_jid; + changed(1); + } +} + void Models::Contact::setAvailability(unsigned int p_state) { setAvailability(Shared::Global::fromInt(p_state)); @@ -115,12 +144,16 @@ void Models::Contact::update(const QString& field, const QVariant& value) { if (field == "name") { setName(value.toString()); + } else if (field == "jid") { + setJid(value.toString()); } else if (field == "availability") { setAvailability(value.toUInt()); } else if (field == "state") { setState(value.toUInt()); - } else { - Element::update(field, value); + } else if (field == "avatarState") { + setAvatarState(value.toUInt()); + } else if (field == "avatarPath") { + setAvatarPath(value.toString()); } } @@ -159,9 +192,11 @@ void Models::Contact::refresh() { QDateTime lastActivity; Presence* presence = 0; + unsigned int count = 0; for (QMap::iterator itr = presences.begin(), end = presences.end(); itr != end; ++itr) { Presence* pr = itr.value(); QDateTime la = pr->getLastActivity(); + count += pr->getMessagesCount(); if (la > lastActivity) { lastActivity = la; @@ -176,6 +211,11 @@ void Models::Contact::refresh() setAvailability(Shared::Availability::offline); setStatus(""); } + + if (childMessages != count) { + childMessages = count; + changed(4); + } } void Models::Contact::_removeChild(int index) @@ -217,6 +257,81 @@ QIcon Models::Contact::getStatusIcon(bool big) const } } +void Models::Contact::addMessage(const Shared::Message& data) +{ + const QString& res = data.getPenPalResource(); + if (res.size() > 0) { + QMap::iterator itr = presences.find(res); + if (itr == presences.end()) { + // this is actually the place when I can spot someone's invisible presence, and there is nothing criminal in it, cuz the sender sent us a message + // therefore he have revealed himself + // the only issue is to find out when the sender is gone offline + Presence* pr = new Presence({}); + pr->setName(res); + pr->setAvailability(Shared::Availability::invisible); + pr->setLastActivity(QDateTime::currentDateTimeUtc()); + presences.insert(res, pr); + appendChild(pr); + pr->addMessage(data); + return; + } + itr.value()->addMessage(data); + } else { + messages.emplace_back(data); + changed(4); + } +} + +void Models::Contact::changeMessage(const QString& id, const QMap& data) +{ + + bool found = false; + for (Shared::Message& msg : messages) { + if (msg.getId() == id) { + msg.change(data); + found = true; + break; + } + } + if (!found) { + for (Presence* pr : presences) { + found = pr->changeMessage(id, data); + if (found) { + break; + } + } + } +} + +unsigned int Models::Contact::getMessagesCount() const +{ + return messages.size() + childMessages; +} + +void Models::Contact::dropMessages() +{ + if (messages.size() > 0) { + messages.clear(); + changed(4); + } + + for (QMap::iterator itr = presences.begin(), end = presences.end(); itr != end; ++itr) { + itr.value()->dropMessages(); + } +} + +void Models::Contact::getMessages(Models::Contact::Messages& container) const +{ + for (Messages::const_iterator itr = messages.begin(), end = messages.end(); itr != end; ++itr) { + const Shared::Message& msg = *itr; + container.push_back(msg); + } + + for (QMap::const_iterator itr = presences.begin(), end = presences.end(); itr != end; ++itr) { + itr.value()->getMessages(container); + } +} + void Models::Contact::toOfflineState() { std::deque::size_type size = childItems.size(); @@ -240,9 +355,75 @@ QString Models::Contact::getDisplayedName() const return getContactName(); } -void Models::Contact::handleRecconnect() +bool Models::Contact::columnInvolvedInDisplay(int col) { - if (getMessagesCount() > 0) { - feed->requestLatestMessages(); + return Item::columnInvolvedInDisplay(col) && col == 1; +} + +Models::Contact * Models::Contact::copy() const +{ + Contact* cnt = new Contact(*this); + return cnt; +} + +Models::Contact::Contact(const Models::Contact& other): + Item(other), + jid(other.jid), + availability(other.availability), + state(other.state), + presences(), + messages(other.messages), + childMessages(0), + account(other.account) +{ + for (const Presence* pres : other.presences) { + Presence* pCopy = new Presence(*pres); + presences.insert(pCopy->getName(), pCopy); + Item::appendChild(pCopy); + connect(pCopy, &Item::childChanged, this, &Contact::refresh); + } + + refresh(); +} + +QString Models::Contact::getAvatarPath() const +{ + return avatarPath; +} + +Shared::Avatar Models::Contact::getAvatarState() const +{ + return avatarState; +} + +void Models::Contact::setAvatarPath(const QString& path) +{ + if (path != avatarPath) { + avatarPath = path; + changed(7); } } + +void Models::Contact::setAvatarState(Shared::Avatar p_state) +{ + if (avatarState != p_state) { + avatarState = p_state; + changed(6); + } +} + +void Models::Contact::setAvatarState(unsigned int p_state) +{ + if (p_state <= static_cast(Shared::Avatar::valid)) { + Shared::Avatar state = static_cast(p_state); + setAvatarState(state); + } else { + qDebug() << "An attempt to set invalid avatar state" << p_state << "to the contact" << jid << ", skipping"; + } +} + +const Models::Account * Models::Contact::getParentAccount() const +{ + return account; +} + diff --git a/ui/models/contact.h b/ui/models/contact.h index a8b80a3..c8c99b5 100644 --- a/ui/models/contact.h +++ b/ui/models/contact.h @@ -19,7 +19,7 @@ #ifndef MODELS_CONTACT_H #define MODELS_CONTACT_H -#include "element.h" +#include "item.h" #include "presence.h" #include "shared/enums.h" #include "shared/message.h" @@ -31,36 +31,49 @@ #include namespace Models { +class Account; -class Contact : public Element +class Contact : public Item { Q_OBJECT public: + typedef std::deque Messages; Contact(const Account* acc, const QString& p_jid, const QMap &data, Item *parentItem = 0); + Contact(const Contact& other); ~Contact(); + QString getJid() const; Shared::Availability getAvailability() const; Shared::SubscriptionState getState() const; - + Shared::Avatar getAvatarState() const; + QString getAvatarPath() const; QIcon getStatusIcon(bool big = false) const; int columnCount() const override; QVariant data(int column) const override; - void update(const QString& field, const QVariant& value) override; + void update(const QString& field, const QVariant& value); void addPresence(const QString& name, const QMap& data); void removePresence(const QString& name); QString getContactName() const; QString getStatus() const; + + void addMessage(const Shared::Message& data); + void changeMessage(const QString& id, const QMap& data); + unsigned int getMessagesCount() const; + void dropMessages(); + void getMessages(Messages& container) const; QString getDisplayedName() const override; - void handleRecconnect(); //this is a special method Models::Roster calls when reconnect happens + Contact* copy() const; protected: void _removeChild(int index) override; void _appendChild(Models::Item * child) override; + bool columnInvolvedInDisplay(int col) override; + const Account* getParentAccount() const override; protected slots: void refresh(); @@ -71,13 +84,23 @@ protected: void setAvailability(unsigned int p_state); void setState(Shared::SubscriptionState p_state); void setState(unsigned int p_state); + void setAvatarState(Shared::Avatar p_state); + void setAvatarState(unsigned int p_state); + void setAvatarPath(const QString& path); + void setJid(const QString p_jid); void setStatus(const QString& p_state); private: + QString jid; Shared::Availability availability; Shared::SubscriptionState state; + Shared::Avatar avatarState; QMap presences; + Messages messages; + unsigned int childMessages; QString status; + QString avatarPath; + const Account* account; }; } diff --git a/ui/models/element.cpp b/ui/models/element.cpp deleted file mode 100644 index 4e741a4..0000000 --- a/ui/models/element.cpp +++ /dev/null @@ -1,184 +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 "element.h" -#include "account.h" - -#include - -Models::Element::Element(Type p_type, const Models::Account* acc, const QString& p_jid, const QMap& data, Models::Item* parentItem): - Item(p_type, data, parentItem), - jid(p_jid), - avatarPath(), - avatarState(Shared::Avatar::empty), - account(acc), - feed(new MessageFeed(this)) -{ - connect(feed, &MessageFeed::requestArchive, this, &Element::requestArchive); - connect(feed, &MessageFeed::fileDownloadRequest, this, &Element::fileDownloadRequest); - connect(feed, &MessageFeed::unreadMessagesCountChanged, this, &Element::onFeedUnreadMessagesCountChanged); - connect(feed, &MessageFeed::unnoticedMessage, this, &Element::onFeedUnnoticedMessage); - connect(feed, &MessageFeed::localPathInvalid, this, &Element::localPathInvalid); - - QMap::const_iterator itr = data.find("avatarState"); - if (itr != data.end()) { - setAvatarState(itr.value().toUInt()); - } - itr = data.find("avatarPath"); - if (itr != data.end()) { - setAvatarPath(itr.value().toString()); - } -} - -Models::Element::~Element() -{ - delete feed; -} - - -QString Models::Element::getJid() const -{ - return jid; -} - -void Models::Element::setJid(const QString& p_jid) -{ - if (jid != p_jid) { - jid = p_jid; - changed(1); - } -} - -void Models::Element::update(const QString& field, const QVariant& value) -{ - if (field == "jid") { - setJid(value.toString()); - } else if (field == "avatarState") { - setAvatarState(value.toUInt()); - } else if (field == "avatarPath") { - setAvatarPath(value.toString()); - } -} - -QString Models::Element::getAvatarPath() const -{ - return avatarPath; -} - -Shared::Avatar Models::Element::getAvatarState() const -{ - return avatarState; -} - -void Models::Element::setAvatarPath(const QString& path) -{ - if (path != avatarPath) { - avatarPath = path; - if (type == contact) { - changed(7); - } else if (type == room) { - changed(8); - } - } -} - -void Models::Element::setAvatarState(Shared::Avatar p_state) -{ - if (avatarState != p_state) { - avatarState = p_state; - if (type == contact) { - changed(6); - } else if (type == room) { - changed(7); - } - } -} - -void Models::Element::setAvatarState(unsigned int p_state) -{ - if (p_state <= static_cast(Shared::Avatar::valid)) { - Shared::Avatar state = static_cast(p_state); - setAvatarState(state); - } else { - qDebug() << "An attempt to set invalid avatar state" << p_state << "to the element" << jid << ", skipping"; - } -} - -bool Models::Element::columnInvolvedInDisplay(int col) -{ - return Item::columnInvolvedInDisplay(col) && col == 1; -} - -const Models::Account * Models::Element::getParentAccount() const -{ - return account; -} - -unsigned int Models::Element::getMessagesCount() const -{ - return feed->unreadMessagesCount(); -} - -void Models::Element::addMessage(const Shared::Message& data) -{ - feed->addMessage(data); -} - -void Models::Element::changeMessage(const QString& id, const QMap& data) -{ - feed->changeMessage(id, data); -} - -void Models::Element::responseArchive(const std::list list, bool last) -{ - feed->responseArchive(list, last); -} - -bool Models::Element::isRoom() const -{ - return type != contact; -} - -void Models::Element::fileProgress(const QString& messageId, qreal value, bool up) -{ - feed->fileProgress(messageId, value, up); -} - -void Models::Element::fileComplete(const QString& messageId, bool up) -{ - feed->fileComplete(messageId, up); -} - -void Models::Element::fileError(const QString& messageId, const QString& error, bool up) -{ - feed->fileError(messageId, error, up); -} - -void Models::Element::onFeedUnreadMessagesCountChanged() -{ - if (type == contact) { - changed(4); - } else if (type == room) { - changed(5); - } -} - -void Models::Element::onFeedUnnoticedMessage(const Shared::Message& msg) -{ - emit unnoticedMessage(getAccountName(), msg); -} diff --git a/ui/models/element.h b/ui/models/element.h deleted file mode 100644 index 94d67cb..0000000 --- a/ui/models/element.h +++ /dev/null @@ -1,82 +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 ELEMENT_H -#define ELEMENT_H - -#include "item.h" - -#include "ui/widgets/messageline/messagefeed.h" - -namespace Models { - -class Element : public Item -{ - Q_OBJECT -protected: - Element(Type p_type, const Account* acc, const QString& p_jid, const QMap &data, Item *parentItem = 0); - ~Element(); - -public: - QString getJid() const; - Shared::Avatar getAvatarState() const; - QString getAvatarPath() const; - - virtual void update(const QString& field, const QVariant& value); - - void addMessage(const Shared::Message& data); - void changeMessage(const QString& id, const QMap& data); - unsigned int getMessagesCount() const; - void responseArchive(const std::list list, bool last); - bool isRoom() 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); - -signals: - void requestArchive(const QString& before); - void fileDownloadRequest(const QString& url); - void unnoticedMessage(const QString& account, const Shared::Message& msg); - void localPathInvalid(const QString& path); - -protected: - void setJid(const QString& p_jid); - void setAvatarState(Shared::Avatar p_state); - void setAvatarState(unsigned int p_state); - void setAvatarPath(const QString& path); - bool columnInvolvedInDisplay(int col) override; - const Account* getParentAccount() const override; - -protected slots: - void onFeedUnreadMessagesCountChanged(); - void onFeedUnnoticedMessage(const Shared::Message& msg); - -protected: - QString jid; - QString avatarPath; - Shared::Avatar avatarState; - - const Account* account; - -public: - MessageFeed* feed; -}; - -} - -#endif // ELEMENT_H diff --git a/ui/models/item.cpp b/ui/models/item.cpp index 4a88dd2..e006ad0 100644 --- a/ui/models/item.cpp +++ b/ui/models/item.cpp @@ -283,15 +283,6 @@ Shared::ConnectionState Models::Item::getAccountConnectionState() const return acc->getState(); } -QString Models::Item::getAccountAvatarPath() const -{ - const Account* acc = getParentAccount(); - if (acc == nullptr) { - return ""; - } - return acc->getAvatarPath(); -} - QString Models::Item::getDisplayedName() const { return name; diff --git a/ui/models/item.h b/ui/models/item.h index 4661479..4f3e29a 100644 --- a/ui/models/item.h +++ b/ui/models/item.h @@ -80,7 +80,6 @@ class Item : public QObject{ QString getAccountName() const; QString getAccountJid() const; QString getAccountResource() const; - QString getAccountAvatarPath() const; Shared::ConnectionState getAccountConnectionState() const; Shared::Availability getAccountAvailability() const; diff --git a/ui/models/presence.cpp b/ui/models/presence.cpp index 8ba7c47..bf931e9 100644 --- a/ui/models/presence.cpp +++ b/ui/models/presence.cpp @@ -20,15 +20,82 @@ #include "shared/icons.h" Models::Presence::Presence(const QMap& data, Item* parentItem): - AbstractParticipant(Item::presence, data, parentItem) + AbstractParticipant(Item::presence, data, parentItem), + messages() { } +Models::Presence::Presence(const Models::Presence& other): + AbstractParticipant(other), + messages(other.messages) +{ +} + + Models::Presence::~Presence() { } int Models::Presence::columnCount() const { - return 4; + return 5; +} + +QVariant Models::Presence::data(int column) const +{ + switch (column) { + case 4: + return getMessagesCount(); + default: + return AbstractParticipant::data(column); + } +} + +unsigned int Models::Presence::getMessagesCount() const +{ + return messages.size(); +} + +void Models::Presence::addMessage(const Shared::Message& data) +{ + messages.emplace_back(data); + changed(4); +} + +bool Models::Presence::changeMessage(const QString& id, const QMap& data) +{ + bool found = false; + for (Shared::Message& msg : messages) { + if (msg.getId() == id) { + msg.change(data); + found = true; + break; + } + } + return found; +} + +void Models::Presence::dropMessages() +{ + if (messages.size() > 0) { + messages.clear(); + changed(4); + } +} + +QIcon Models::Presence::getStatusIcon(bool big) const +{ + if (getMessagesCount() > 0) { + return Shared::icon("mail-message", big); + } else { + return AbstractParticipant::getStatusIcon(); + } +} + +void Models::Presence::getMessages(Models::Presence::Messages& container) const +{ + for (Messages::const_iterator itr = messages.begin(), end = messages.end(); itr != end; ++itr) { + const Shared::Message& msg = *itr; + container.push_back(msg); + } } diff --git a/ui/models/presence.h b/ui/models/presence.h index fb1a31c..fc430f0 100644 --- a/ui/models/presence.h +++ b/ui/models/presence.h @@ -32,10 +32,25 @@ class Presence : public Models::AbstractParticipant { Q_OBJECT public: + typedef std::deque Messages; explicit Presence(const QMap &data, Item *parentItem = 0); + Presence(const Presence& other); ~Presence(); int columnCount() const override; + QVariant data(int column) const override; + + QIcon getStatusIcon(bool big = false) const override; + + unsigned int getMessagesCount() const; + void dropMessages(); + void addMessage(const Shared::Message& data); + bool changeMessage(const QString& id, const QMap& data); + + void getMessages(Messages& container) const; + +private: + Messages messages; }; } diff --git a/ui/models/reference.cpp b/ui/models/reference.cpp index 1aaea15..cb8efad 100644 --- a/ui/models/reference.cpp +++ b/ui/models/reference.cpp @@ -104,8 +104,6 @@ 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/room.cpp b/ui/models/room.cpp index a6a36d0..cc19d2c 100644 --- a/ui/models/room.cpp +++ b/ui/models/room.cpp @@ -22,12 +22,16 @@ #include #include -Models::Room::Room(const Account* acc, const QString& p_jid, const QMap& data, Models::Item* parentItem): - Element(room, acc, p_jid, data, parentItem), +Models::Room::Room(const QString& p_jid, const QMap& data, Models::Item* parentItem): + Item(room, data, parentItem), autoJoin(false), joined(false), + jid(p_jid), nick(""), subject(""), + avatarState(Shared::Avatar::empty), + avatarPath(""), + messages(), participants(), exParticipantAvatars() { @@ -51,6 +55,16 @@ Models::Room::Room(const Account* acc, const QString& p_jid, const QMap avs = itr.value().toMap(); @@ -64,11 +78,21 @@ Models::Room::~Room() { } +unsigned int Models::Room::getUnreadMessagesCount() const +{ + return messages.size(); +} + int Models::Room::columnCount() const { return 7; } +QString Models::Room::getJid() const +{ + return jid; +} + bool Models::Room::getAutoJoin() const { return autoJoin; @@ -127,6 +151,14 @@ void Models::Room::setAutoJoin(bool p_autoJoin) } } +void Models::Room::setJid(const QString& p_jid) +{ + if (jid != p_jid) { + jid = p_jid; + changed(1); + } +} + void Models::Room::setJoined(bool p_joined) { if (joined != p_joined) { @@ -150,6 +182,8 @@ void Models::Room::update(const QString& field, const QVariant& value) { if (field == "name") { setName(value.toString()); + } else if (field == "jid") { + setJid(value.toString()); } else if (field == "joined") { setJoined(value.toBool()); } else if (field == "autoJoin") { @@ -158,14 +192,16 @@ void Models::Room::update(const QString& field, const QVariant& value) setNick(value.toString()); } else if (field == "subject") { setSubject(value.toString()); - } else { - Element::update(field, value); + } else if (field == "avatarState") { + setAvatarState(value.toUInt()); + } else if (field == "avatarPath") { + setAvatarPath(value.toString()); } } QIcon Models::Room::getStatusIcon(bool big) const { - if (getMessagesCount() > 0) { + if (messages.size() > 0) { return Shared::icon("mail-message", big); } else { if (autoJoin) { @@ -201,6 +237,42 @@ QString Models::Room::getStatusText() const } } +unsigned int Models::Room::getMessagesCount() const +{ + return messages.size(); +} + +void Models::Room::addMessage(const Shared::Message& data) +{ + messages.emplace_back(data); + changed(5); +} + +void Models::Room::changeMessage(const QString& id, const QMap& data) +{ + for (Shared::Message& msg : messages) { + if (msg.getId() == id) { + msg.change(data); + break; + } + } +} + +void Models::Room::dropMessages() +{ + if (messages.size() > 0) { + messages.clear(); + changed(5); + } +} + +void Models::Room::getMessages(Models::Room::Messages& container) const +{ + for (Messages::const_iterator itr = messages.begin(), end = messages.end(); itr != end; ++itr) { + const Shared::Message& msg = *itr; + container.push_back(msg); + } +} void Models::Room::toOfflineState() { @@ -295,6 +367,47 @@ QString Models::Room::getDisplayedName() const return getRoomName(); } +bool Models::Room::columnInvolvedInDisplay(int col) +{ + return Item::columnInvolvedInDisplay(col) && col == 1; +} + +QString Models::Room::getAvatarPath() const +{ + return avatarPath; +} + +Shared::Avatar Models::Room::getAvatarState() const +{ + return avatarState; +} + +void Models::Room::setAvatarPath(const QString& path) +{ + if (avatarPath != path) { + avatarPath = path; + changed(8); + } +} + +void Models::Room::setAvatarState(Shared::Avatar p_state) +{ + if (avatarState != p_state) { + avatarState = p_state; + changed(7); + } +} + +void Models::Room::setAvatarState(unsigned int p_state) +{ + if (p_state <= static_cast(Shared::Avatar::valid)) { + Shared::Avatar state = static_cast(p_state); + setAvatarState(state); + } else { + qDebug() << "An attempt to set invalid avatar state" << p_state << "to the room" << jid << ", skipping"; + } +} + std::map Models::Room::getParticipants() const { std::map result; @@ -310,12 +423,7 @@ QString Models::Room::getParticipantIconPath(const QString& name) const { std::map::const_iterator itr = participants.find(name); if (itr == participants.end()) { - std::map::const_iterator eitr = exParticipantAvatars.find(name); - if (eitr != exParticipantAvatars.end()) { - return eitr->second; - } else { - return ""; - } + return ""; } return itr->second->getAvatarPath(); diff --git a/ui/models/room.h b/ui/models/room.h index a51a537..9ea70bf 100644 --- a/ui/models/room.h +++ b/ui/models/room.h @@ -19,7 +19,7 @@ #ifndef MODELS_ROOM_H #define MODELS_ROOM_H -#include "element.h" +#include "item.h" #include "participant.h" #include "shared/enums.h" #include "shared/message.h" @@ -29,18 +29,21 @@ namespace Models { /** * @todo write docs */ -class Room : public Element +class Room : public Models::Item { Q_OBJECT public: - Room(const Account* acc, const QString& p_jid, const QMap &data, Item *parentItem = 0); + typedef std::deque Messages; + Room(const QString& p_jid, const QMap &data, Item *parentItem = 0); ~Room(); int columnCount() const override; QVariant data(int column) const override; + unsigned int getUnreadMessagesCount() const; bool getJoined() const; bool getAutoJoin() const; + QString getJid() const; QString getNick() const; QString getRoomName() const; QString getSubject() const; @@ -50,10 +53,17 @@ public: void setJoined(bool p_joined); void setAutoJoin(bool p_autoJoin); + void setJid(const QString& p_jid); void setNick(const QString& p_nick); void setSubject(const QString& sub); - void update(const QString& field, const QVariant& value) override; + void update(const QString& field, const QVariant& value); + + void addMessage(const Shared::Message& data); + void changeMessage(const QString& id, const QMap& data); + unsigned int getMessagesCount() const; + void dropMessages(); + void getMessages(Messages& container) const; void addParticipant(const QString& name, const QMap& data); void changeParticipant(const QString& name, const QMap& data); @@ -61,6 +71,8 @@ public: void toOfflineState() override; QString getDisplayedName() const override; + Shared::Avatar getAvatarState() const; + QString getAvatarPath() const; std::map getParticipants() const; QString getParticipantIconPath(const QString& name) const; std::map getExParticipantAvatars() const; @@ -72,14 +84,24 @@ signals: private: void handleParticipantUpdate(std::map::const_iterator itr, const QMap& data); +protected: + bool columnInvolvedInDisplay(int col) override; + void setAvatarState(Shared::Avatar p_state); + void setAvatarState(unsigned int p_state); + void setAvatarPath(const QString& path); + private: bool autoJoin; bool joined; QString jid; QString nick; QString subject; + Shared::Avatar avatarState; + QString avatarPath; + Messages messages; std::map participants; std::map exParticipantAvatars; + }; } diff --git a/ui/models/roster.cpp b/ui/models/roster.cpp index 2d5f99f..ac90a50 100644 --- a/ui/models/roster.cpp +++ b/ui/models/roster.cpp @@ -48,7 +48,6 @@ 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); @@ -216,7 +215,11 @@ QVariant Models::Roster::data (const QModelIndex& index, int role) const break; case Item::presence: { Presence* contact = static_cast(item); - QString str; + QString str(""); + int mc = contact->getMessagesCount(); + if (mc > 0) { + str += tr("New messages: ") + std::to_string(mc).c_str() + "\n"; + } Shared::Availability av = contact->getAvailability(); str += tr("Availability: ") + Shared::Global::getName(av); QString s = contact->getStatus(); @@ -229,7 +232,7 @@ QVariant Models::Roster::data (const QModelIndex& index, int role) const break; case Item::participant: { Participant* p = static_cast(item); - QString str; + QString str(""); Shared::Availability av = p->getAvailability(); str += tr("Availability: ") + Shared::Global::getName(av) + "\n"; QString s = p->getStatus(); @@ -257,7 +260,7 @@ QVariant Models::Roster::data (const QModelIndex& index, int role) const break; case Item::room: { Room* rm = static_cast(item); - unsigned int count = rm->getMessagesCount(); + unsigned int count = rm->getUnreadMessagesCount(); QString str(""); if (count > 0) { str += tr("New messages: ") + std::to_string(count).c_str() + "\n"; @@ -447,10 +450,6 @@ void Models::Roster::addContact(const QString& account, const QString& jid, cons std::map::iterator itr = contacts.find(id); if (itr == contacts.end()) { contact = new Contact(acc, jid, data); - connect(contact, &Contact::requestArchive, this, &Roster::onElementRequestArchive); - connect(contact, &Contact::fileDownloadRequest, this, &Roster::fileDownloadRequest); - connect(contact, &Contact::unnoticedMessage, this, &Roster::unnoticedMessage); - connect(contact, &Contact::localPathInvalid, this, &Roster::localPathInvalid); contacts.insert(std::make_pair(id, contact)); } else { contact = itr->second; @@ -536,19 +535,35 @@ 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) { + ElId id(account, jid); + std::map::iterator cItr = contacts.find(id); + + if (cItr != contacts.end()) { for (QMap::const_iterator itr = data.begin(), end = data.end(); itr != end; ++itr) { - el->update(itr.key(), itr.value()); + cItr->second->update(itr.key(), itr.value()); + } + } else { + std::map::iterator rItr = rooms.find(id); + if (rItr != rooms.end()) { + for (QMap::const_iterator itr = data.begin(), end = data.end(); itr != end; ++itr) { + rItr->second->update(itr.key(), itr.value()); + } } } } void Models::Roster::changeMessage(const QString& account, const QString& jid, const QString& id, const QMap& data) { - Element* el = getElement({account, jid}); - if (el != NULL) { - el->changeMessage(id, data); + ElId elid(account, jid); + std::map::iterator cItr = contacts.find(elid); + + if (cItr != contacts.end()) { + cItr->second->changeMessage(id, data); + } else { + std::map::iterator rItr = rooms.find(elid); + if (rItr != rooms.end()) { + rItr->second->changeMessage(id, data); + } } } @@ -612,6 +627,7 @@ void Models::Roster::removeContact(const QString& account, const QString& jid, c } else { delete ref; } + if (gr->childCount() == 0) { removeGroup(account, group); } @@ -692,9 +708,29 @@ 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) { - el->addMessage(data); + ElId id(account, data.getPenPalJid()); + std::map::iterator itr = contacts.find(id); + if (itr != contacts.end()) { + itr->second->addMessage(data); + } else { + std::map::const_iterator rItr = rooms.find(id); + if (rItr != rooms.end()) { + rItr->second->addMessage(data); + } + } +} + +void Models::Roster::dropMessages(const QString& account, const QString& jid) +{ + ElId id(account, jid); + std::map::iterator itr = contacts.find(id); + if (itr != contacts.end()) { + itr->second->dropMessages(); + } else { + std::map::const_iterator rItr = rooms.find(id); + if (rItr != rooms.end()) { + rItr->second->dropMessages(); + } } } @@ -745,7 +781,6 @@ void Models::Roster::removeAccount(const QString& account) } } - disconnect(acc, &Account::reconnected, this, &Roster::onAccountReconnected); acc->deleteLater(); } @@ -786,11 +821,7 @@ void Models::Roster::addRoom(const QString& account, const QString jid, const QM return; } - 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); + Room* room = new Room(jid, data); rooms.insert(std::make_pair(id, room)); acc->appendChild(room); } @@ -943,77 +974,3 @@ QModelIndex Models::Roster::getGroupIndex(const QString& account, const QString& } } } - -void Models::Roster::onElementRequestArchive(const QString& before) -{ - Element* el = static_cast(sender()); - emit requestArchive(el->getAccountName(), el->getJid(), before); -} - -void Models::Roster::responseArchive(const QString& account, const QString& jid, const std::list& list, bool last) -{ - ElId id(account, jid); - Element* el = getElement(id); - if (el != NULL) { - el->responseArchive(list, last); - } -} - -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) { - el->fileProgress(info.messageId, value, up); - } - } -} - -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) { - el->fileComplete(info.messageId, up); - } - } -} - -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) { - el->fileError(info.messageId, err, up); - } - } -} - -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; -} - -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 08d5afc..d866b6d 100644 --- a/ui/models/roster.h +++ b/ui/models/roster.h @@ -26,7 +26,6 @@ #include "shared/message.h" #include "shared/global.h" -#include "shared/messageinfo.h" #include "accounts.h" #include "item.h" #include "account.h" @@ -59,6 +58,7 @@ public: void removePresence(const QString& account, const QString& jid, const QString& name); void addMessage(const QString& account, const Shared::Message& data); void changeMessage(const QString& account, const QString& jid, const QString& id, const QMap& data); + void dropMessages(const QString& account, const QString& jid); 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); @@ -81,35 +81,9 @@ public: Account* getAccount(const QString& name); QModelIndex getAccountIndex(const QString& name); QModelIndex getGroupIndex(const QString& account, const QString& name); - void responseArchive(const QString& account, const QString& jid, const std::list& list, bool last); - - void fileProgress(const std::list& msgs, qreal value, bool up); - void fileError(const std::list& msgs, const QString& err, bool up); - void fileComplete(const std::list& msgs, bool up); Accounts* accountsModel; -signals: - void requestArchive(const QString& account, const QString& jid, const QString& before); - void fileDownloadRequest(const QString& url); - void unnoticedMessage(const QString& account, const Shared::Message& msg); - void localPathInvalid(const QString& path); - -private: - Element* getElement(const ElId& id); - -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(); - void onChildIsAboutToBeRemoved(Item* parent, int first, int last); - void onChildRemoved(); - void onChildIsAboutToBeMoved(Item* source, int first, int last, Item* destination, int newIndex); - void onChildMoved(); - void onElementRequestArchive(const QString& before); - private: Item* root; std::map accounts; @@ -117,6 +91,16 @@ private: std::map contacts; std::map rooms; +private slots: + void onAccountDataChanged(const QModelIndex& tl, const QModelIndex& br, const QVector& roles); + void onChildChanged(Models::Item* item, int row, int col); + void onChildIsAboutToBeInserted(Item* parent, int first, int last); + void onChildInserted(); + void onChildIsAboutToBeRemoved(Item* parent, int first, int last); + void onChildRemoved(); + void onChildIsAboutToBeMoved(Item* source, int first, int last, Item* destination, int newIndex); + void onChildMoved(); + public: class ElId { public: diff --git a/ui/squawk.cpp b/ui/squawk.cpp index 6a0a676..2058e3d 100644 --- a/ui/squawk.cpp +++ b/ui/squawk.cpp @@ -29,6 +29,7 @@ Squawk::Squawk(QWidget *parent) : conversations(), contextMenu(new QMenu()), dbus("org.freedesktop.Notifications", "/org/freedesktop/Notifications", "org.freedesktop.Notifications", QDBusConnection::sessionBus()), + requestedFiles(), vCards(), requestedAccountsForPasswords(), prompt(0), @@ -59,12 +60,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); //m_ui->mainToolBar->addWidget(m_ui->comboBox); @@ -339,14 +336,17 @@ void Squawk::onRosterItemDoubleClicked(const QModelIndex& item) Models::Account* acc = rosterModel.getAccount(id->account); Conversation* conv = 0; bool created = false; + Models::Contact::Messages deque; if (itr != conversations.end()) { conv = itr->second; } else if (contact != 0) { created = true; conv = new Chat(acc, contact); + contact->getMessages(deque); } else if (room != 0) { created = true; conv = new Room(acc, room); + room->getMessages(deque); if (!room->getJoined()) { emit setRoomJoined(id->account, id->name, true); @@ -358,6 +358,12 @@ void Squawk::onRosterItemDoubleClicked(const QModelIndex& item) conv->setAttribute(Qt::WA_DeleteOnClose); subscribeConversation(conv); conversations.insert(std::make_pair(*id, conv)); + + if (created) { + for (Models::Contact::Messages::const_iterator itr = deque.begin(), end = deque.end(); itr != end; ++itr) { + conv->addMessage(*itr); + } + } } conv->show(); @@ -374,6 +380,12 @@ void Squawk::onRosterItemDoubleClicked(const QModelIndex& item) } } +void Squawk::onConversationShown() +{ + Conversation* conv = static_cast(sender()); + rosterModel.dropMessages(conv->getAccount(), conv->getJid()); +} + void Squawk::onConversationClosed(QObject* parent) { Conversation* conv = static_cast(sender()); @@ -390,40 +402,164 @@ void Squawk::onConversationClosed(QObject* parent) } } -void Squawk::fileProgress(const std::list msgs, qreal value, bool up) +void Squawk::onConversationDownloadFile(const QString& messageId, const QString& url) { - rosterModel.fileProgress(msgs, value, up); + Conversation* conv = static_cast(sender()); + std::map>::iterator itr = requestedFiles.find(messageId); + bool created = false; + if (itr == requestedFiles.end()) { + itr = requestedFiles.insert(std::make_pair(messageId, std::set())).first; + created = true; + } + itr->second.insert(Models::Roster::ElId(conv->getAccount(), conv->getJid())); + if (created) { + emit downloadFileRequest(messageId, url); + } } -void Squawk::fileDownloadComplete(const std::list msgs, const QString& path) +void Squawk::fileProgress(const QString& messageId, qreal value) { - rosterModel.fileComplete(msgs, false); + std::map>::const_iterator itr = requestedFiles.find(messageId); + if (itr == requestedFiles.end()) { + qDebug() << "fileProgress in UI Squawk but there is nobody waiting for that id" << messageId << ", skipping"; + return; + } else { + const std::set& convs = itr->second; + for (std::set::const_iterator cItr = convs.begin(), cEnd = convs.end(); cItr != cEnd; ++cItr) { + const Models::Roster::ElId& id = *cItr; + Conversations::const_iterator c = conversations.find(id); + if (c != conversations.end()) { + c->second->responseFileProgress(messageId, value); + } + if (currentConversation != 0 && currentConversation->getId() == id) { + currentConversation->responseFileProgress(messageId, value); + } + } + } } -void Squawk::fileError(const std::list msgs, const QString& error, bool up) +void Squawk::fileError(const QString& messageId, const QString& error) { - rosterModel.fileError(msgs, error, up); + std::map>::const_iterator itr = requestedFiles.find(messageId); + if (itr == requestedFiles.end()) { + qDebug() << "fileError in UI Squawk but there is nobody waiting for that id" << messageId << ", skipping"; + return; + } else { + const std::set& convs = itr->second; + for (std::set::const_iterator cItr = convs.begin(), cEnd = convs.end(); cItr != cEnd; ++cItr) { + const Models::Roster::ElId& id = *cItr; + Conversations::const_iterator c = conversations.find(id); + if (c != conversations.end()) { + c->second->fileError(messageId, error); + } + if (currentConversation != 0 && currentConversation->getId() == id) { + currentConversation->fileError(messageId, error); + } + } + requestedFiles.erase(itr); + } } -void Squawk::fileUploadComplete(const std::list msgs, const QString& path) +void Squawk::fileLocalPathResponse(const QString& messageId, const QString& path) { - rosterModel.fileComplete(msgs, true); + std::map>::const_iterator itr = requestedFiles.find(messageId); + if (itr == requestedFiles.end()) { + qDebug() << "fileLocalPathResponse in UI Squawk but there is nobody waiting for that path, skipping"; + return; + } else { + const std::set& convs = itr->second; + for (std::set::const_iterator cItr = convs.begin(), cEnd = convs.end(); cItr != cEnd; ++cItr) { + const Models::Roster::ElId& id = *cItr; + Conversations::const_iterator c = conversations.find(id); + if (c != conversations.end()) { + c->second->responseLocalFile(messageId, path); + } + if (currentConversation != 0 && currentConversation->getId() == id) { + currentConversation->responseLocalFile(messageId, path); + } + } + + requestedFiles.erase(itr); + } +} + +void Squawk::onConversationRequestLocalFile(const QString& messageId, const QString& url) +{ + Conversation* conv = static_cast(sender()); + std::map>::iterator itr = requestedFiles.find(messageId); + bool created = false; + if (itr == requestedFiles.end()) { + itr = requestedFiles.insert(std::make_pair(messageId, std::set())).first; + created = true; + } + itr->second.insert(Models::Roster::ElId(conv->getAccount(), conv->getJid())); + if (created) { + emit fileLocalPathRequest(messageId, url); + } } 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); + const QString& from = data.getPenPalJid(); + Models::Roster::ElId id({account, from}); + Conversations::iterator itr = conversations.find(id); + bool found = false; + + if (currentConversation != 0 && currentConversation->getId() == id) { + currentConversation->addMessage(data); + QApplication::alert(this); + if (!isVisible() && !data.getForwarded()) { + notify(account, data); + } + found = true; + } + + if (itr != conversations.end()) { + Conversation* conv = itr->second; + conv->addMessage(data); + QApplication::alert(conv); + if (!found && conv->isMinimized()) { + rosterModel.addMessage(account, data); + } + if (!conv->isVisible() && !data.getForwarded()) { + notify(account, data); + } + found = true; + } + + if (!found) { + rosterModel.addMessage(account, data); + if (!data.getForwarded()) { + QApplication::alert(this); + notify(account, data); + } + } } void Squawk::changeMessage(const QString& account, const QString& jid, const QString& id, const QMap& data) { - rosterModel.changeMessage(account, jid, id, data); + Models::Roster::ElId eid({account, jid}); + bool found = false; + + if (currentConversation != 0 && currentConversation->getId() == eid) { + currentConversation->changeMessage(id, data); + QApplication::alert(this); + found = true; + } + + Conversations::iterator itr = conversations.find(eid); + if (itr != conversations.end()) { + Conversation* conv = itr->second; + conv->changeMessage(id, data); + if (!found && conv->isMinimized()) { + rosterModel.changeMessage(account, jid, id, data); + } + found = true; + } + + if (!found) { + rosterModel.changeMessage(account, jid, id, data); + } } void Squawk::notify(const QString& account, const Shared::Message& msg) @@ -460,29 +596,60 @@ void Squawk::notify(const QString& account, const Shared::Message& msg) void Squawk::onConversationMessage(const Shared::Message& msg) { Conversation* conv = static_cast(sender()); - QString acc = conv->getAccount(); + emit sendMessage(conv->getAccount(), msg); + Models::Roster::ElId id = conv->getId(); - rosterModel.addMessage(acc, msg); - emit sendMessage(acc, msg); + if (currentConversation != 0 && currentConversation->getId() == id) { + if (conv == currentConversation) { + Conversations::iterator itr = conversations.find(id); + if (itr != conversations.end()) { + itr->second->addMessage(msg); + } + } else { + currentConversation->addMessage(msg); + } + } } -void Squawk::onConversationResend(const QString& id) +void Squawk::onConversationMessage(const Shared::Message& msg, const QString& path) { Conversation* conv = static_cast(sender()); - QString acc = conv->getAccount(); - QString jid = conv->getJid(); + Models::Roster::ElId id = conv->getId(); + std::map>::iterator itr = requestedFiles.insert(std::make_pair(msg.getId(), std::set())).first; + itr->second.insert(id); - emit resendMessage(acc, jid, id); + if (currentConversation != 0 && currentConversation->getId() == id) { + if (conv == currentConversation) { + Conversations::iterator itr = conversations.find(id); + if (itr != conversations.end()) { + itr->second->appendMessageWithUpload(msg, path); + } + } else { + currentConversation->appendMessageWithUpload(msg, path); + } + } + + emit sendMessage(conv->getAccount(), msg, path); } -void Squawk::onRequestArchive(const QString& account, const QString& jid, const QString& before) +void Squawk::onConversationRequestArchive(const QString& before) { - emit requestArchive(account, jid, 20, before); //TODO amount as a settings value + Conversation* conv = static_cast(sender()); + requestArchive(conv->getAccount(), conv->getJid(), 20, before); //TODO amount as a settings value } -void Squawk::responseArchive(const QString& account, const QString& jid, const std::list& list, bool last) +void Squawk::responseArchive(const QString& account, const QString& jid, const std::list& list) { - rosterModel.responseArchive(account, jid, list, last); + Models::Roster::ElId id(account, jid); + + if (currentConversation != 0 && currentConversation->getId() == id) { + currentConversation->responseArchive(list); + } + + Conversations::const_iterator itr = conversations.find(id); + if (itr != conversations.end()) { + itr->second->responseArchive(list); + } } void Squawk::removeAccount(const QString& account) @@ -494,6 +661,8 @@ void Squawk::removeAccount(const QString& account) ++itr; Conversation* conv = lItr->second; disconnect(conv, &Conversation::destroyed, this, &Squawk::onConversationClosed); + disconnect(conv, &Conversation::requestArchive, this, &Squawk::onConversationRequestArchive); + disconnect(conv, &Conversation::shown, this, &Squawk::onConversationShown); conv->close(); conversations.erase(lItr); } else { @@ -757,6 +926,7 @@ void Squawk::onActivateVCard(const QString& account, const QString& jid, bool ed { std::map::const_iterator itr = vCards.find(jid); VCard* card; + Models::Contact::Messages deque; if (itr != vCards.end()) { card = itr->second; } else { @@ -812,6 +982,7 @@ void Squawk::readSettings() void Squawk::writeSettings() { QSettings settings; + qDebug() << "settings.fileName: " << settings.fileName(); settings.beginGroup("ui"); settings.beginGroup("window"); settings.setValue("geometry", saveGeometry()); @@ -853,6 +1024,7 @@ void Squawk::writeSettings() } settings.endGroup(); settings.endGroup(); + } void Squawk::onItemCollepsed(const QModelIndex& index) @@ -922,9 +1094,13 @@ void Squawk::onPasswordPromptRejected() 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); + connect(conv, qOverload(&Conversation::sendMessage), this, qOverload(&Squawk::onConversationMessage)); + connect(conv, qOverload(&Conversation::sendMessage), + this, qOverload(&Squawk::onConversationMessage)); + connect(conv, &Conversation::requestArchive, this, &Squawk::onConversationRequestArchive); + connect(conv, &Conversation::requestLocalFile, this, &Squawk::onConversationRequestLocalFile); + connect(conv, &Conversation::downloadFile, this, &Squawk::onConversationDownloadFile); + connect(conv, &Conversation::shown, this, &Squawk::onConversationShown); } void Squawk::onRosterSelectionChanged(const QModelIndex& current, const QModelIndex& previous) @@ -994,10 +1170,13 @@ void Squawk::onRosterSelectionChanged(const QModelIndex& current, const QModelIn } Models::Account* acc = rosterModel.getAccount(id->account); + Models::Contact::Messages deque; if (contact != 0) { currentConversation = new Chat(acc, contact); + contact->getMessages(deque); } else if (room != 0) { currentConversation = new Room(acc, room); + room->getMessages(deque); if (!room->getJoined()) { emit setRoomJoined(id->account, id->name, true); @@ -1008,6 +1187,9 @@ void Squawk::onRosterSelectionChanged(const QModelIndex& current, const QModelIn } subscribeConversation(currentConversation); + for (Models::Contact::Messages::const_iterator itr = deque.begin(), end = deque.end(); itr != end; ++itr) { + currentConversation->addMessage(*itr); + } if (res.size() > 0) { currentConversation->setPalResource(res); diff --git a/ui/squawk.h b/ui/squawk.h index 28389fa..a6a27c0 100644 --- a/ui/squawk.h +++ b/ui/squawk.h @@ -39,7 +39,7 @@ #include "models/roster.h" #include "widgets/vcard/vcard.h" -#include "shared/shared.h" +#include "shared.h" namespace Ui { class Squawk; @@ -63,7 +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 sendMessage(const QString& account, const Shared::Message& data, const QString& path); void requestArchive(const QString& account, const QString& jid, int count, const QString& before); void subscribeContact(const QString& account, const QString& jid, const QString& reason); void unsubscribeContact(const QString& account, const QString& jid, const QString& reason); @@ -76,11 +76,11 @@ signals: void setRoomAutoJoin(const QString& account, const QString& jid, bool joined); void addRoomRequest(const QString& account, const QString& jid, const QString& nick, const QString& password, bool autoJoin); void removeRoomRequest(const QString& account, const QString& jid); - void fileDownloadRequest(const QString& url); + void fileLocalPathRequest(const QString& messageId, const QString& url); + void downloadFileRequest(const QString& messageId, 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); public slots: void readSettings(); @@ -97,17 +97,16 @@ public slots: void removePresence(const QString& account, const QString& jid, const QString& name); void stateChanged(Shared::Availability state); void accountMessage(const QString& account, const Shared::Message& data); - void responseArchive(const QString& account, const QString& jid, const std::list& list, bool last); + void responseArchive(const QString& account, const QString& jid, const std::list& list); 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& path); + void fileLocalPathResponse(const QString& messageId, const QString& path); + void fileError(const QString& messageId, const QString& error); + void fileProgress(const QString& messageId, qreal value); 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); @@ -121,6 +120,7 @@ private: Conversations conversations; QMenu* contextMenu; QDBusInterface dbus; + std::map> requestedFiles; std::map vCards; std::deque requestedAccountsForPasswords; QInputDialog* prompt; @@ -130,8 +130,6 @@ private: protected: void closeEvent(QCloseEvent * event) override; - -protected slots: void notify(const QString& account, const Shared::Message& msg); private slots: @@ -149,17 +147,18 @@ 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 onConversationMessage(const Shared::Message& msg, const QString& path); + void onConversationRequestArchive(const QString& before); void onRosterContextMenu(const QPoint& point); + void onConversationShown(); + void onConversationRequestLocalFile(const QString& messageId, const QString& url); + void onConversationDownloadFile(const QString& messageId, const QString& url); void onItemCollepsed(const QModelIndex& index); void onPasswordPromptAccepted(); void onPasswordPromptRejected(); void onRosterSelectionChanged(const QModelIndex& current, const QModelIndex& previous); void onContextAboutToHide(); - void onUnnoticedMessage(const QString& account, const Shared::Message& msg); - private: void checkNextAccountForPassword(); void onPasswordPromptDone(); diff --git a/ui/utils/CMakeLists.txt b/ui/utils/CMakeLists.txt deleted file mode 100644 index b46d30d..0000000 --- a/ui/utils/CMakeLists.txt +++ /dev/null @@ -1,18 +0,0 @@ -target_sources(squawk PRIVATE - badge.cpp - badge.h - comboboxdelegate.cpp - comboboxdelegate.h - exponentialblur.cpp - exponentialblur.h - flowlayout.cpp - flowlayout.h - image.cpp - image.h - progress.cpp - progress.h - resizer.cpp - resizer.h - shadowoverlay.cpp - shadowoverlay.h - ) diff --git a/ui/utils/badge.cpp b/ui/utils/badge.cpp index ef15bd2..164e0ad 100644 --- a/ui/utils/badge.cpp +++ b/ui/utils/badge.cpp @@ -33,6 +33,9 @@ Badge::Badge(const QString& p_id, const QString& p_text, const QIcon& icon, QWid image->setPixmap(icon.pixmap(25, 25)); closeButton->setIcon(QIcon::fromTheme("tab-close")); + QIcon qIcon; + qIcon.addFile(QString::fromUtf8(":/images/fallback/dark/big/edit-none.svg"), QSize(), QIcon::Normal, QIcon::Off); + closeButton->setIcon(qIcon); closeButton->setMaximumHeight(25); closeButton->setMaximumWidth(25); diff --git a/ui/utils/comboboxdelegate.cpp b/ui/utils/comboboxdelegate.cpp index 4c96c79..7153405 100644 --- a/ui/utils/comboboxdelegate.cpp +++ b/ui/utils/comboboxdelegate.cpp @@ -37,7 +37,7 @@ QWidget* ComboboxDelegate::createEditor(QWidget *parent, const QStyleOptionViewI { QComboBox *cb = new QComboBox(parent); - for (const std::pair& pair : entries) { + for (const std::pair pair : entries) { cb->addItem(pair.second, pair.first); } diff --git a/ui/utils/exponentialblur.cpp b/ui/utils/dropshadoweffect.cpp similarity index 85% rename from ui/utils/exponentialblur.cpp rename to ui/utils/dropshadoweffect.cpp index cb222dc..91a0258 100644 --- a/ui/utils/exponentialblur.cpp +++ b/ui/utils/dropshadoweffect.cpp @@ -16,7 +16,8 @@ * along with this program. If not, see . */ -#include "exponentialblur.h" +#include "dropshadoweffect.h" +#include "QtMath" static const int tileSize = 32; template @@ -573,7 +574,128 @@ void expblur(QImage &img, qreal radius, bool improvedQuality = false, int transp } } -void Utils::exponentialblur(QImage& img, qreal radius, bool improvedQuality, int transposed) +PixmapFilter::PixmapFilter(QObject* parent):QObject(parent) {} +PixmapFilter::~PixmapFilter(){} +QRectF PixmapFilter::boundingRectFor(const QRectF &rect) const {return rect;} + +PixmapDropShadowFilter::PixmapDropShadowFilter(QObject *parent): + PixmapFilter(parent), + mColor(63, 63, 63, 180), + mRadius(1), + mThickness(2), + top(true), + right(true), + bottom(true), + left(true){} + +PixmapDropShadowFilter::~PixmapDropShadowFilter() {} +qreal PixmapDropShadowFilter::blurRadius() const {return mRadius;} +void PixmapDropShadowFilter::setBlurRadius(qreal radius) {mRadius = radius;} +QColor PixmapDropShadowFilter::color() const {return mColor;} +void PixmapDropShadowFilter::setColor(const QColor &color) {mColor = color;} +qreal PixmapDropShadowFilter::thickness() const {return mThickness;} +void PixmapDropShadowFilter::setThickness(qreal thickness) {mThickness = thickness;} +void PixmapDropShadowFilter::setFrame(bool ptop, bool pright, bool pbottom, bool pleft) { - expblur<12, 10, false>(img, radius, improvedQuality, transposed); + top = ptop; + right = pright; + bottom = pbottom; + left = pleft; +} + +void DropShadowEffect::setThickness(qreal thickness) +{ + if (filter.thickness() == thickness) + return; + + filter.setThickness(thickness); + update(); +} + + +void PixmapDropShadowFilter::draw(QPainter *p, const QPointF &pos, const QPixmap &px, const QRectF &src) const +{ + if (px.isNull()) + return; + + QImage tmp({px.width(), px.height() + int(mThickness)}, QImage::Format_ARGB32_Premultiplied); + tmp.setDevicePixelRatio(px.devicePixelRatioF()); + tmp.fill(0); + QPainter tmpPainter(&tmp); + tmpPainter.setCompositionMode(QPainter::CompositionMode_Source); + if (top) { + QRectF shadow(0, 0, px.width(), mThickness); + tmpPainter.fillRect(shadow, mColor); + } + if (right) { + QRectF shadow(px.width() - mThickness, 0, mThickness, px.height()); + tmpPainter.fillRect(shadow, mColor); + } + if (bottom) { + QRectF shadow(0, px.height() - mThickness, px.width(), mThickness * 2); //i have no idea why, but it leaves some unpainted stripe without some spare space + tmpPainter.fillRect(shadow, mColor); + } + if (left) { + QRectF shadow(0, 0, mThickness, px.height()); + tmpPainter.fillRect(shadow, mColor); + } + + expblur<12, 10, false>(tmp, mRadius, false, 0); + tmpPainter.end(); + + // Draw the actual pixmap... + p->drawPixmap(pos, px, src); + + // draw the blurred drop shadow... + p->drawImage(pos, tmp); +} + +qreal DropShadowEffect::blurRadius() const {return filter.blurRadius();} +void DropShadowEffect::setBlurRadius(qreal blurRadius) +{ + if (qFuzzyCompare(filter.blurRadius(), blurRadius)) + return; + + filter.setBlurRadius(blurRadius); + updateBoundingRect(); + emit blurRadiusChanged(blurRadius); +} + +void DropShadowEffect::setFrame(bool top, bool right, bool bottom, bool left) +{ + filter.setFrame(top, right, bottom, left); + update(); +} + + +QColor DropShadowEffect::color() const {return filter.color();} +void DropShadowEffect::setColor(const QColor &color) +{ + if (filter.color() == color) + return; + + filter.setColor(color); + update(); + emit colorChanged(color); +} + +void DropShadowEffect::draw(QPainter* painter) +{ + if (filter.blurRadius() <= 0 && filter.thickness() == 0) { + drawSource(painter); + return; + } + + PixmapPadMode mode = PadToEffectiveBoundingRect; + + // Draw pixmap in device coordinates to avoid pixmap scaling. + QPoint offset; + const QPixmap pixmap = sourcePixmap(Qt::DeviceCoordinates, &offset, mode); + if (pixmap.isNull()) + return; + + QTransform restoreTransform = painter->worldTransform(); + painter->setWorldTransform(QTransform()); + filter.draw(painter, offset, pixmap); + painter->setWorldTransform(restoreTransform); } diff --git a/ui/utils/dropshadoweffect.h b/ui/utils/dropshadoweffect.h new file mode 100644 index 0000000..b2768b7 --- /dev/null +++ b/ui/utils/dropshadoweffect.h @@ -0,0 +1,93 @@ +/* + * 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 DROPSHADOWEFFECT_H +#define DROPSHADOWEFFECT_H + +#include +#include +#include +#include + +class PixmapFilter : public QObject +{ + Q_OBJECT +public: + PixmapFilter(QObject *parent = nullptr); + virtual ~PixmapFilter() = 0; + + virtual QRectF boundingRectFor(const QRectF &rect) const; + virtual void draw(QPainter *painter, const QPointF &p, const QPixmap &src, const QRectF &srcRect = QRectF()) const = 0; +}; + +class PixmapDropShadowFilter : public PixmapFilter +{ + Q_OBJECT + +public: + PixmapDropShadowFilter(QObject *parent = nullptr); + ~PixmapDropShadowFilter(); + + void draw(QPainter *p, const QPointF &pos, const QPixmap &px, const QRectF &src = QRectF()) const override; + + qreal blurRadius() const; + void setBlurRadius(qreal radius); + + QColor color() const; + void setColor(const QColor &color); + + qreal thickness() const; + void setThickness(qreal thickness); + void setFrame(bool top, bool right, bool bottom, bool left); + +protected: + QColor mColor; + qreal mRadius; + qreal mThickness; + bool top; + bool right; + bool bottom; + bool left; +}; + +class DropShadowEffect : public QGraphicsEffect +{ + Q_OBJECT +public: + qreal blurRadius() const; + QColor color() const; + void setFrame(bool top, bool right, bool bottom, bool left); + void setThickness(qreal thickness); + +signals: + void blurRadiusChanged(qreal blurRadius); + void colorChanged(const QColor &color); + +public slots: + void setBlurRadius(qreal blurRadius); + void setColor(const QColor &color); + +protected: + void draw(QPainter * painter) override; + +protected: + PixmapDropShadowFilter filter; + +}; + +#endif // DROPSHADOWEFFECT_H diff --git a/ui/utils/exponentialblur.h b/ui/utils/exponentialblur.h deleted file mode 100644 index 0a5df8a..0000000 --- a/ui/utils/exponentialblur.h +++ /dev/null @@ -1,34 +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 EXPONENTIALBLUR_H -#define EXPONENTIALBLUR_H - -#include -#include -#include - -/** - * @todo write docs - */ - -namespace Utils { - void exponentialblur(QImage &img, qreal radius, bool improvedQuality = false, int transposed = 0); -}; - -#endif // EXPONENTIALBLUR_H diff --git a/ui/widgets/messageline/message.cpp b/ui/utils/message.cpp similarity index 95% rename from ui/widgets/messageline/message.cpp rename to ui/utils/message.cpp index 7a004bb..ae354ac 100644 --- a/ui/widgets/messageline/message.cpp +++ b/ui/utils/message.cpp @@ -203,16 +203,23 @@ void Message::showFile(const QString& path) file = new Image(path); } else { file = new QLabel(); - file->setPixmap(QIcon::fromTheme(type.iconName()).pixmap(50)); + QIcon qIcon = QIcon::fromTheme(type.iconName()); + if (qIcon.isNull()) { + qIcon.addFile(QString::fromUtf8(":/images/fallback/dark/big/mail-attachment.svg"), QSize(), QIcon::Normal, QIcon::Off); + } + file->setPixmap(qIcon.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); + QAction* openAction = new QAction(QIcon::fromTheme("document-new-from-template"), tr("Open"), this); connect(openAction, &QAction::triggered, [path]() { //TODO need to get rid of this shame QDesktopServices::openUrl(QUrl::fromLocalFile(path)); }); file->addAction(openAction); + fileComment->setContextMenuPolicy(Qt::ActionsContextMenu); + fileComment->addAction(openAction); + bodyLayout->insertWidget(2, file); hasFile = true; } diff --git a/ui/widgets/messageline/message.h b/ui/utils/message.h similarity index 100% rename from ui/widgets/messageline/message.h rename to ui/utils/message.h diff --git a/ui/widgets/messageline/messageline.cpp b/ui/utils/messageline.cpp similarity index 97% rename from ui/widgets/messageline/messageline.cpp rename to ui/utils/messageline.cpp index fec0037..0f55bdf 100644 --- a/ui/widgets/messageline/messageline.cpp +++ b/ui/utils/messageline.cpp @@ -38,7 +38,8 @@ MessageLine::MessageLine(bool p_room, QWidget* parent): downloading(), room(p_room), busyShown(false), - progress() + progress(), + lastHeight(0) { setContentsMargins(0, 0, 0, 0); layout->setContentsMargins(0, 0, 0, 0); @@ -163,6 +164,7 @@ MessageLine::Position MessageLine::message(const Shared::Message& msg, bool forc emit requestLocalFile(msg.getId(), msg.getOutOfBandUrl()); connect(message, &Message::buttonClicked, this, &MessageLine::onDownload); } + qDebug() << "inserted message " << id; return res; } @@ -309,7 +311,11 @@ void MessageLine::movePalAvatarToEx(const QString& name) void MessageLine::resizeEvent(QResizeEvent* event) { QWidget::resizeEvent(event); - emit resize(event->size().height() - event->oldSize().height()); + + qDebug() << "Resize(unordered): " << event->size().height() << event->oldSize().height(); + qDebug() << "Resize: " << height() << lastHeight; + emit resize(height() - lastHeight); + lastHeight = height(); } @@ -328,6 +334,7 @@ void MessageLine::showBusyIndicator() layout->insertWidget(0, &progress); progress.start(); busyShown = true; + qDebug() << "showBusyIndicator"; } } @@ -337,6 +344,7 @@ void MessageLine::hideBusyIndicator() progress.stop(); layout->removeWidget(&progress); busyShown = false; + qDebug() << "hideBusyIndicator"; } } diff --git a/ui/widgets/messageline/messageline.h b/ui/utils/messageline.h similarity index 99% rename from ui/widgets/messageline/messageline.h rename to ui/utils/messageline.h index a0a7b6c..2a25ac0 100644 --- a/ui/widgets/messageline/messageline.h +++ b/ui/utils/messageline.h @@ -103,6 +103,7 @@ private: bool room; bool busyShown; Progress progress; + int lastHeight; }; #endif // MESSAGELINE_H diff --git a/ui/utils/shadowoverlay.cpp b/ui/utils/shadowoverlay.cpp deleted file mode 100644 index 3c28a15..0000000 --- a/ui/utils/shadowoverlay.cpp +++ /dev/null @@ -1,91 +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 "shadowoverlay.h" - -ShadowOverlay::ShadowOverlay(unsigned int r, unsigned int t, const QColor& c, QWidget* parent): - QWidget(parent), - top(false), - right(false), - bottom(false), - left(false), - thickness(t), - radius(r), - color(c), - shadow(1, 1, QImage::Format_ARGB32_Premultiplied) -{ - setAttribute(Qt::WA_NoSystemBackground); - setAttribute(Qt::WA_TransparentForMouseEvents); -} - -void ShadowOverlay::paintEvent(QPaintEvent* event) -{ - QWidget::paintEvent(event); - - QPainter painter(this); - - painter.drawImage(0, 0, shadow); -} - -void ShadowOverlay::resizeEvent(QResizeEvent* event) -{ - QWidget::resizeEvent(event); - - updateImage(); -} - -void ShadowOverlay::updateImage() -{ - int w = width(); - int h = height(); - shadow = QImage({w, h + int(thickness)}, QImage::Format_ARGB32_Premultiplied); - shadow.fill(0); - - QPainter tmpPainter(&shadow); - tmpPainter.setCompositionMode(QPainter::CompositionMode_Source); - if (top) { - QRectF shadow(0, 0, w, thickness); - tmpPainter.fillRect(shadow, color); - } - if (right) { - QRectF shadow(w - thickness, 0, thickness, h); - tmpPainter.fillRect(shadow, color); - } - if (bottom) { - QRectF shadow(0, h - thickness, w, thickness * 2); //i have no idea why, but it leaves some unpainted stripe without some spare space - tmpPainter.fillRect(shadow, color); - } - if (left) { - QRectF shadow(0, 0, thickness, h); - tmpPainter.fillRect(shadow, color); - } - - Utils::exponentialblur(shadow, radius, false, 0); - tmpPainter.end(); -} - -void ShadowOverlay::setFrames(bool t, bool r, bool b, bool l) -{ - top = t; - right = r; - bottom = b; - left = l; - - updateImage(); - update(); -} diff --git a/ui/utils/shadowoverlay.h b/ui/utils/shadowoverlay.h deleted file mode 100644 index 524115a..0000000 --- a/ui/utils/shadowoverlay.h +++ /dev/null @@ -1,58 +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 SHADOWOVERLAY_H -#define SHADOWOVERLAY_H - -#include -#include -#include -#include -#include -#include - -#include - -/** - * @todo write docs - */ -class ShadowOverlay : public QWidget { - -public: - ShadowOverlay(unsigned int radius = 10, unsigned int thickness = 1, const QColor& color = Qt::black, QWidget* parent = nullptr); - - void setFrames(bool top, bool right, bool bottom, bool left); - -protected: - void updateImage(); - - void paintEvent(QPaintEvent * event) override; - void resizeEvent(QResizeEvent * event) override; - -private: - bool top; - bool right; - bool bottom; - bool left; - unsigned int thickness; - unsigned int radius; - QColor color; - QImage shadow; -}; - -#endif // SHADOWOVERLAY_H diff --git a/ui/widgets/CMakeLists.txt b/ui/widgets/CMakeLists.txt index c7e47e0..0338778 100644 --- a/ui/widgets/CMakeLists.txt +++ b/ui/widgets/CMakeLists.txt @@ -1,24 +1,38 @@ -target_sources(squawk PRIVATE - 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 - ) +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) add_subdirectory(vcard) -add_subdirectory(messageline) + +set(squawkWidgets_SRC + conversation.cpp + chat.cpp + room.cpp + newcontact.cpp + accounts.cpp + account.cpp + joinconference.cpp +) + +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}) + +# Use the Widgets module from Qt 5. +target_link_libraries(squawkWidgets vCardUI) +target_link_libraries(squawkWidgets Qt5::Widgets) diff --git a/ui/widgets/chat.cpp b/ui/widgets/chat.cpp index 052d83d..acbcac1 100644 --- a/ui/widgets/chat.cpp +++ b/ui/widgets/chat.cpp @@ -19,7 +19,7 @@ #include "chat.h" Chat::Chat(Models::Account* acc, Models::Contact* p_contact, QWidget* parent): - Conversation(false, acc, p_contact, p_contact->getJid(), "", parent), + Conversation(false, acc, p_contact->getJid(), "", parent), contact(p_contact) { setName(p_contact->getContactName()); @@ -71,14 +71,31 @@ Shared::Message Chat::createMessage() const return msg; } -void Chat::onMessage(const Shared::Message& data) +void Chat::addMessage(const Shared::Message& data) { - Conversation::onMessage(data); + Conversation::addMessage(data); - if (!data.getOutgoing()) { + if (!data.getOutgoing()) { //TODO need to check if that was the last message const QString& res = data.getPenPalResource(); if (res.size() > 0) { setPalResource(res); } } } + +void Chat::setName(const QString& name) +{ + Conversation::setName(name); + line->setPalName(getJid(), name); +} + +void Chat::setAvatar(const QString& path) +{ + Conversation::setAvatar(path); + + if (path.size() == 0) { + line->dropPalAvatar(contact->getJid()); + } else { + line->setPalAvatar(contact->getJid(), path); + } +} diff --git a/ui/widgets/chat.h b/ui/widgets/chat.h index 78e6bec..f05b0fa 100644 --- a/ui/widgets/chat.h +++ b/ui/widgets/chat.h @@ -34,13 +34,16 @@ class Chat : public Conversation public: Chat(Models::Account* acc, Models::Contact* p_contact, QWidget* parent = 0); ~Chat(); + + void addMessage(const Shared::Message & data) override; + void setAvatar(const QString& path) override; protected slots: void onContactChanged(Models::Item* item, int row, int col); protected: + void setName(const QString & name) override; Shared::Message createMessage() const override; - void onMessage(const Shared::Message& msg) override; private: void updateState(); diff --git a/ui/widgets/conversation.cpp b/ui/widgets/conversation.cpp index d003551..cfc3a52 100644 --- a/ui/widgets/conversation.cpp +++ b/ui/widgets/conversation.cpp @@ -18,8 +18,10 @@ #include "conversation.h" #include "ui_conversation.h" +#include "ui/utils/dropshadoweffect.h" #include +#include #include #include #include @@ -27,45 +29,36 @@ #include #include #include +#include +#include +#include -Conversation::Conversation(bool muc, Models::Account* acc, Models::Element* el, const QString pJid, const QString pRes, QWidget* parent): +Conversation::Conversation(bool muc, Models::Account* acc, const QString pJid, const QString pRes, QWidget* parent): QWidget(parent), isMuc(muc), account(acc), - element(el), palJid(pJid), activePalResource(pRes), + line(new MessageLine(muc)), m_ui(new Ui::Conversation()), ker(), + scrollResizeCatcher(), + vis(), thread(), statusIcon(0), statusLabel(0), filesLayout(0), overlay(new QWidget()), filesToAttach(), - feed(new FeedView()), - delegate(new MessageDelegate(this)), + scroll(down), manualSliderChange(false), + requestingHistory(false), + everShown(false), tsb(QApplication::style()->styleHint(QStyle::SH_ScrollBar_Transient) == 1), - shadow(10, 1, Qt::black, this), - contextMenu(new QMenu()) + pasteImageAction(nullptr) { m_ui->setupUi(this); - shadow.setFrames(true, false, true, false); - - feed->setItemDelegate(delegate); - feed->setFrameShape(QFrame::NoFrame); - feed->setContextMenuPolicy(Qt::CustomContextMenu); - delegate->initializeFonts(feed->getFont()); - feed->setModel(el->feed); - el->feed->incrementObservers(); - m_ui->widget->layout()->addWidget(feed); - - connect(el->feed, &Models::MessageFeed::newMessage, this, &Conversation::onFeedMessage); - connect(feed, &FeedView::resized, this, &Conversation::positionShadow); - connect(feed, &FeedView::customContextMenuRequested, this, &Conversation::onFeedContext); - connect(acc, &Models::Account::childChanged, this, &Conversation::onAccountChanged); filesLayout = new FlowLayout(m_ui->filesPanel, 0); @@ -75,51 +68,53 @@ 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(&scrollResizeCatcher, &Resizer::resized, this, &Conversation::onScrollResize); + connect(&vis, &VisibilityCatcher::shown, this, &Conversation::onScrollResize); + connect(&vis, &VisibilityCatcher::hidden, this, &Conversation::onScrollResize); connect(m_ui->sendButton, &QPushButton::clicked, this, &Conversation::onEnterPressed); + connect(line, &MessageLine::resize, this, &Conversation::onMessagesResize); + connect(line, &MessageLine::downloadFile, this, &Conversation::downloadFile); + connect(line, &MessageLine::uploadFile, this, qOverload(&Conversation::sendMessage)); + connect(line, &MessageLine::requestLocalFile, this, &Conversation::requestLocalFile); connect(m_ui->attachButton, &QPushButton::clicked, this, &Conversation::onAttach); connect(m_ui->clearButton, &QPushButton::clicked, this, &Conversation::onClearButton); connect(m_ui->messageEditor->document()->documentLayout(), &QAbstractTextDocumentLayout::documentSizeChanged, this, &Conversation::onTextEditDocSizeChanged); m_ui->messageEditor->installEventFilter(&ker); - - - //line->setAutoFillBackground(false); - //if (testAttribute(Qt::WA_TranslucentBackground)) { - //m_ui->scrollArea->setAutoFillBackground(false); - //} else { - //m_ui->scrollArea->setBackgroundRole(QPalette::Base); - //} - - //line->setMyAvatarPath(acc->getAvatarPath()); - //line->setMyName(acc->getName()); - - initializeOverlay(); -} -Conversation::~Conversation() -{ - delete contextMenu; - - element->feed->decrementObservers(); -} + QAction* pasteImageAction = new QAction(tr("Paste Image"), this); + connect(pasteImageAction, &QAction::triggered, this, &Conversation::onImagePasted); -void Conversation::onAccountChanged(Models::Item* item, int row, int col) -{ - if (item == account) { - if (col == 2 && account->getState() == Shared::ConnectionState::connected) { //to request the history when we're back online after reconnect - //if (!requestingHistory) { - //requestingHistory = true; - //line->showBusyIndicator(); - //emit requestArchive(""); - //scroll = down; - //} - } + 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)); + }); + + QScrollBar* vs = m_ui->scrollArea->verticalScrollBar(); + m_ui->scrollArea->setWidget(line); + vs->installEventFilter(&vis); + + line->setAutoFillBackground(false); + if (testAttribute(Qt::WA_TranslucentBackground)) { + m_ui->scrollArea->setAutoFillBackground(false); + } else { + m_ui->scrollArea->setBackgroundRole(QPalette::Base); } -} - -void Conversation::initializeOverlay() -{ + + connect(vs, &QScrollBar::valueChanged, this, &Conversation::onSliderValueChanged); + m_ui->scrollArea->installEventFilter(&scrollResizeCatcher); + + line->setMyAvatarPath(acc->getAvatarPath()); + line->setMyName(acc->getName()); + QGridLayout* gr = static_cast(layout()); QLabel* progressLabel = new QLabel(tr("Drop files here to attach them to your message")); gr->addWidget(overlay, 0, 0, 2, 1); @@ -140,6 +135,36 @@ void Conversation::initializeOverlay() nl->addWidget(progressLabel); nl->addStretch(); overlay->hide(); + + applyVisualEffects(); +} + +Conversation::~Conversation() +{ +} + +void Conversation::onAccountChanged(Models::Item* item, int row, int col) +{ + if (item == account) { + if (col == 2 && account->getState() == Shared::ConnectionState::connected) { + if (!requestingHistory) { + requestingHistory = true; + line->showBusyIndicator(); + emit requestArchive(""); + scroll = down; + } + } + } +} + +void Conversation::applyVisualEffects() +{ + DropShadowEffect *e1 = new DropShadowEffect; + e1->setBlurRadius(10); + e1->setColor(Qt::black); + e1->setThickness(1); + e1->setFrame(true, false, true, false); + m_ui->scrollArea->setGraphicsEffect(e1); } void Conversation::setName(const QString& name) @@ -158,6 +183,22 @@ QString Conversation::getJid() const return palJid; } +void Conversation::addMessage(const Shared::Message& data) +{ + int pos = m_ui->scrollArea->verticalScrollBar()->sliderPosition(); + int max = m_ui->scrollArea->verticalScrollBar()->maximum(); + + MessageLine::Position place = line->message(data); + if (place == MessageLine::invalid) { + return; + } +} + +void Conversation::changeMessage(const QString& id, const QMap& data) +{ + line->changeMessage(id, data); +} + KeyEnterReceiver::KeyEnterReceiver(QObject* parent): QObject(parent), ownEvent(false) {} bool KeyEnterReceiver::eventFilter(QObject* obj, QEvent* event) @@ -183,10 +224,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; @@ -205,19 +256,122 @@ void Conversation::onEnterPressed() m_ui->messageEditor->clear(); Shared::Message msg = createMessage(); msg.setBody(body); + addMessage(msg); emit sendMessage(msg); } if (filesToAttach.size() > 0) { for (Badge* badge : filesToAttach) { Shared::Message msg = createMessage(); - msg.setAttachPath(badge->id); - element->feed->registerUpload(msg.getId()); - emit sendMessage(msg); + line->appendMessageWithUpload(msg, badge->id); + usleep(1000); //this is required for the messages not to have equal time when appending into messageline } - clearAttachedFiles(); + clearAttachedFiles(); } } +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::appendMessageWithUpload(const Shared::Message& data, const QString& path) +{ + line->appendMessageWithUploadNoSiganl(data, path); +} + +void Conversation::onMessagesResize(int amount) +{ + manualSliderChange = true; + qDebug() << "Scroll: " << scroll; + switch (scroll) { + case down: + qDebug() << "setValue 1: " << m_ui->scrollArea->verticalScrollBar()->maximum(); + m_ui->scrollArea->verticalScrollBar()->setValue(m_ui->scrollArea->verticalScrollBar()->maximum()); + break; + case keep: { + int max = m_ui->scrollArea->verticalScrollBar()->maximum(); + int value = m_ui->scrollArea->verticalScrollBar()->value() + amount; + m_ui->scrollArea->verticalScrollBar()->setValue(value); + qDebug() << "setValue 2 max: " << max; + qDebug() << "setValue 2 value: " << m_ui->scrollArea->verticalScrollBar()->value(); + qDebug() << "setValue 2 amount: " << amount; + qDebug() << "setValue 2: " << value; + qDebug() << "isMax: " << (value > max); + + if (value > max) { + qDebug() << "setValue 2 scroll = down"; + scroll = down; + m_ui->scrollArea->verticalScrollBar()->setValue(max); + } else { + qDebug() << "setValue 2 scroll = nothing"; + scroll = nothing; + } + } + break; + default: + break; + } + manualSliderChange = false; +} + +void Conversation::onSliderValueChanged(int value) +{ + if (!manualSliderChange) { + if (value == m_ui->scrollArea->verticalScrollBar()->maximum()) { + qDebug() << "onSliderValueChanged: scroll = down"; + scroll = down; + } else { + if (!requestingHistory && value == 0) { + requestingHistory = true; + line->showBusyIndicator(); + emit requestArchive(line->firstMessageId()); + qDebug() << "onSliderValueChanged: scroll = keep"; + scroll = keep; + } else { + qDebug() << "onSliderValueChanged: scroll = nothing"; + scroll = nothing; + } + } + } +} + +void Conversation::responseArchive(const std::list list) +{ + requestingHistory = false; + qDebug() << "responseArchive scroll = keep"; + scroll = keep; + + line->hideBusyIndicator(); + for (std::list::const_iterator itr = list.begin(), end = list.end(); itr != end; ++itr) { + addMessage(*itr); + } +} + +void Conversation::showEvent(QShowEvent* event) +{ + if (!everShown) { + everShown = true; + line->showBusyIndicator(); + requestingHistory = true; + qDebug() << "showEvent scroll = keep"; + scroll = keep; + emit requestArchive(line->firstMessageId()); + } + emit shown(); + + QWidget::showEvent(event); + +} + void Conversation::onAttach() { QFileDialog* d = new QFileDialog(this, tr("Chose a file to send")); @@ -245,6 +399,34 @@ void Conversation::setStatus(const QString& status) statusLabel->setText(Shared::processMessageBody(status)); } +void Conversation::onScrollResize() +{ + if (everShown) { + int size = m_ui->scrollArea->width(); + QScrollBar* bar = m_ui->scrollArea->verticalScrollBar(); + if (bar->isVisible() && !tsb) { + size -= bar->width(); + + } + line->setMaximumWidth(size); + } +} + +void Conversation::responseFileProgress(const QString& messageId, qreal progress) +{ + line->fileProgress(messageId, progress); +} + +void Conversation::fileError(const QString& messageId, const QString& error) +{ + line->fileError(messageId, error); +} + +void Conversation::responseLocalFile(const QString& messageId, const QString& path) +{ + line->responseLocalFile(messageId, path); +} + Models::Roster::ElId Conversation::getId() const { return {getAccount(), getJid()}; @@ -321,7 +503,7 @@ void Conversation::onTextEditDocSizeChanged(const QSizeF& size) void Conversation::setFeedFrames(bool top, bool right, bool bottom, bool left) { - shadow.setFrames(top, right, bottom, left); + static_cast(m_ui->scrollArea->graphicsEffect())->setFrame(top, right, bottom, left); } void Conversation::dragEnterEvent(QDragEnterEvent* event) @@ -381,65 +563,21 @@ Shared::Message Conversation::createMessage() const return msg; } -void Conversation::onFeedMessage(const Shared::Message& msg) +bool VisibilityCatcher::eventFilter(QObject* obj, QEvent* event) { - this->onMessage(msg); -} - -void Conversation::onMessage(const Shared::Message& msg) -{ - if (!msg.getForwarded()) { - QApplication::alert(this); - if (window()->windowState().testFlag(Qt::WindowMinimized)) { - emit notifyableMessage(getAccount(), msg); - } + if (event->type() == QEvent::Show) { + emit shown(); } -} - -void Conversation::positionShadow() -{ - int w = width(); - int h = feed->height(); - shadow.resize(w, h); - shadow.move(feed->pos()); - shadow.raise(); + if (event->type() == QEvent::Hide) { + emit hidden(); + } + + return false; } -void Conversation::onFeedContext(const QPoint& pos) +VisibilityCatcher::VisibilityCatcher(QWidget* parent): +QObject(parent) { - QModelIndex index = feed->indexAt(pos); - if (index.isValid()) { - Shared::Message* item = static_cast(index.internalPointer()); - - 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; - QAction* open = contextMenu->addAction(Shared::icon("document-preview"), tr("Open")); - connect(open, &QAction::triggered, [path]() { - QDesktopServices::openUrl(QUrl::fromLocalFile(path)); - }); - - QAction* show = contextMenu->addAction(Shared::icon("folder"), tr("Show in folder")); - connect(show, &QAction::triggered, [path]() { - Shared::Global::highlightInFileManager(path); - }); - } - - if (showMenu) { - contextMenu->popup(feed->viewport()->mapToGlobal(pos)); - } - } } + diff --git a/ui/widgets/conversation.h b/ui/widgets/conversation.h index b0eb745..0a366a8 100644 --- a/ui/widgets/conversation.h +++ b/ui/widgets/conversation.h @@ -24,25 +24,17 @@ #include #include #include -#include -#include -#include -#include #include "shared/message.h" -#include "shared/order.h" -#include "shared/icons.h" -#include "shared/utils.h" - +#include "order.h" #include "ui/models/account.h" #include "ui/models/roster.h" - +#include "ui/utils/messageline.h" +#include "ui/utils/resizer.h" #include "ui/utils/flowlayout.h" #include "ui/utils/badge.h" -#include "ui/utils/shadowoverlay.h" - -#include "ui/widgets/messageline/feedview.h" -#include "ui/widgets/messageline/messagedelegate.h" +#include "shared/icons.h" +#include "shared/utils.h" namespace Ui { @@ -60,35 +52,58 @@ protected: signals: void enterPressed(); + void imagePasted(); +}; + +class VisibilityCatcher : public QObject { + Q_OBJECT +public: + VisibilityCatcher(QWidget* parent = nullptr); + +protected: + bool eventFilter(QObject* obj, QEvent* event) override; + +signals: + void hidden(); + void shown(); }; class Conversation : public QWidget { Q_OBJECT public: - Conversation(bool muc, Models::Account* acc, Models::Element* el, const QString pJid, const QString pRes, QWidget* parent = 0); + Conversation(bool muc, Models::Account* acc, const QString pJid, const QString pRes, QWidget* parent = 0); ~Conversation(); QString getJid() const; QString getAccount() const; QString getPalResource() const; Models::Roster::ElId getId() const; + virtual void addMessage(const Shared::Message& data); void setPalResource(const QString& res); + void responseArchive(const std::list list); + void showEvent(QShowEvent * event) override; + void responseLocalFile(const QString& messageId, const QString& path); + void fileError(const QString& messageId, const QString& error); + void responseFileProgress(const QString& messageId, qreal progress); virtual void setAvatar(const QString& path); + void changeMessage(const QString& id, const QMap& data); void setFeedFrames(bool top, bool right, bool bottom, bool left); + virtual void appendMessageWithUpload(const Shared::Message& data, const QString& path); + static bool checkClipboardImage(); signals: void sendMessage(const Shared::Message& message); - void resendMessage(const QString& id); + void sendMessage(const Shared::Message& message, const QString& path); void requestArchive(const QString& before); void shown(); void requestLocalFile(const QString& messageId, const QString& url); void downloadFile(const QString& messageId, const QString& url); - void notifyableMessage(const QString& account, const Shared::Message& msg); protected: virtual void setName(const QString& name); + void applyVisualEffects(); virtual Shared::Message createMessage() const; void setStatus(const QString& status); void addAttachedFile(const QString& path); @@ -97,44 +112,49 @@ protected: void dragEnterEvent(QDragEnterEvent* event) override; void dragLeaveEvent(QDragLeaveEvent* event) override; void dropEvent(QDropEvent* event) override; - void initializeOverlay(); - virtual void onMessage(const Shared::Message& msg); protected slots: void onEnterPressed(); + void onImagePasted(); + void onMessagesResize(int amount); + void onSliderValueChanged(int value); void onAttach(); void onFileSelected(); + void onScrollResize(); void onBadgeClose(); void onClearButton(); void onTextEditDocSizeChanged(const QSizeF& size); void onAccountChanged(Models::Item* item, int row, int col); - void onFeedMessage(const Shared::Message& msg); - void positionShadow(); - void onFeedContext(const QPoint &pos); public: const bool isMuc; protected: + enum Scroll { + nothing, + keep, + down + }; Models::Account* account; - Models::Element* element; QString palJid; QString activePalResource; + MessageLine* line; QScopedPointer m_ui; KeyEnterReceiver ker; + Resizer scrollResizeCatcher; + VisibilityCatcher vis; QString thread; QLabel* statusIcon; QLabel* statusLabel; FlowLayout* filesLayout; QWidget* overlay; W::Order filesToAttach; - FeedView* feed; - MessageDelegate* delegate; + Scroll scroll; bool manualSliderChange; + bool requestingHistory; + bool everShown; bool tsb; //transient scroll bars - - ShadowOverlay shadow; - QMenu* contextMenu; + QAction *pasteImageAction; }; #endif // CONVERSATION_H diff --git a/ui/widgets/conversation.ui b/ui/widgets/conversation.ui index bb38666..902ed86 100644 --- a/ui/widgets/conversation.ui +++ b/ui/widgets/conversation.ui @@ -214,7 +214,61 @@ + + + + true + + + QFrame::NoFrame + + + 0 + + + 0 + + + Qt::ScrollBarAlwaysOff + + + QAbstractScrollArea::AdjustIgnored + + + true + + + + + 0 + 0 + 520 + 380 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + scrollArea + widget_3 @@ -271,8 +325,8 @@ - - .. + + :/images/fallback/dark/big/unfavorite.svg:/images/fallback/dark/big/unfavorite.svg true @@ -298,8 +352,8 @@ - - .. + + :/images/fallback/dark/big/mail-attachment.svg:/images/fallback/dark/big/mail-attachment.svg true @@ -312,8 +366,8 @@ - - .. + + :/images/fallback/dark/big/clean.svg:/images/fallback/dark/big/clean.svg true @@ -332,8 +386,8 @@ - - .. + + :/images/fallback/dark/big/send.svg:/images/fallback/dark/big/send.svg true @@ -400,8 +454,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:'SimSun'; font-size:9pt; 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 +473,8 @@ p, li { white-space: pre-wrap; } - + + + diff --git a/ui/widgets/messageline/CMakeLists.txt b/ui/widgets/messageline/CMakeLists.txt deleted file mode 100644 index 7cace9d..0000000 --- a/ui/widgets/messageline/CMakeLists.txt +++ /dev/null @@ -1,14 +0,0 @@ -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/feedview.cpp b/ui/widgets/messageline/feedview.cpp deleted file mode 100644 index 7bdfb9e..0000000 --- a/ui/widgets/messageline/feedview.cpp +++ /dev/null @@ -1,494 +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 "feedview.h" - -#include -#include -#include -#include - -#include "messagedelegate.h" -#include "messagefeed.h" - -constexpr int maxMessageHeight = 10000; -constexpr int approximateSingleMessageHeight = 20; -constexpr int progressSize = 70; -constexpr int dateDeviderMargin = 10; - -const std::set FeedView::geometryChangingRoles = { - Models::MessageFeed::Attach, - Models::MessageFeed::Text, - Models::MessageFeed::Id, - Models::MessageFeed::Error, - Models::MessageFeed::Date -}; - -FeedView::FeedView(QWidget* parent): - QAbstractItemView(parent), - hints(), - vo(0), - specialDelegate(false), - specialModel(false), - clearWidgetsMode(false), - modelState(Models::MessageFeed::complete), - progress(), - dividerFont(), - dividerMetrics(dividerFont) -{ - horizontalScrollBar()->setRange(0, 0); - verticalScrollBar()->setSingleStep(approximateSingleMessageHeight); - setMouseTracking(true); - setSelectionBehavior(SelectItems); -// viewport()->setAttribute(Qt::WA_Hover, true); - - 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() -{ -} - -QModelIndex FeedView::indexAt(const QPoint& point) const -{ - int32_t vh = viewport()->height(); - uint32_t y = vh - point.y() + vo; - - for (std::deque::size_type i = 0; i < hints.size(); ++i) { - const Hint& hint = hints[i]; - if (y <= hint.offset + hint.height) { - if (y > hint.offset) { - return model()->index(i, 0, rootIndex()); - } else { - break; - } - } - } - - return QModelIndex(); -} - -void FeedView::scrollTo(const QModelIndex& index, QAbstractItemView::ScrollHint hint) -{ -} - -QRect FeedView::visualRect(const QModelIndex& index) const -{ - unsigned int row = index.row(); - if (!index.isValid() || row >= hints.size()) { - qDebug() << "visualRect for" << row; - return QRect(); - } 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); - } -} - -int FeedView::horizontalOffset() const -{ - return 0; -} - -bool FeedView::isIndexHidden(const QModelIndex& index) const -{ - return false; -} - -QModelIndex FeedView::moveCursor(QAbstractItemView::CursorAction cursorAction, Qt::KeyboardModifiers modifiers) -{ - return QModelIndex(); -} - -void FeedView::setSelection(const QRect& rect, QItemSelectionModel::SelectionFlags command) -{ -} - -int FeedView::verticalOffset() const -{ - return vo; -} - -QRegion FeedView::visualRegionForSelection(const QItemSelection& selection) const -{ - return QRegion(); -} - -void FeedView::rowsInserted(const QModelIndex& parent, int start, int end) -{ - QAbstractItemView::rowsInserted(parent, start, end); - - scheduleDelayedItemsLayout(); -} - -void FeedView::dataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight, const QVector& roles) -{ - if (specialDelegate) { - for (int role : roles) { - if (geometryChangingRoles.count(role) != 0) { - scheduleDelayedItemsLayout(); //to recalculate layout only if there are some geometry changing modifications - break; - } - } - } - QAbstractItemView::dataChanged(topLeft, bottomRight, roles); -} - -void FeedView::updateGeometries() -{ - qDebug() << "updateGeometries"; - QScrollBar* bar = verticalScrollBar(); - - const QStyle* st = style(); - const QAbstractItemModel* m = model(); - QSize layoutBounds = maximumViewportSize(); - QStyleOptionViewItem option = viewOptions(); - option.rect.setHeight(maxMessageHeight); - option.rect.setWidth(layoutBounds.width()); - int frameAroundContents = 0; - int verticalScrollBarExtent = st->pixelMetric(QStyle::PM_ScrollBarExtent, 0, bar); - - bool layedOut = false; - if (verticalScrollBarExtent != 0 && verticalScrollBarPolicy() == Qt::ScrollBarAsNeeded && m->rowCount() * approximateSingleMessageHeight < layoutBounds.height()) { - hints.clear(); - layedOut = tryToCalculateGeometriesWithNoScrollbars(option, m, layoutBounds.height()); - } - - if (layedOut) { - bar->setRange(0, 0); - vo = 0; - } else { - int verticalMargin = 0; - if (st->styleHint(QStyle::SH_ScrollView_FrameOnlyAroundContents)) { - frameAroundContents = st->pixelMetric(QStyle::PM_DefaultFrameWidth) * 2; - } - - if (verticalScrollBarPolicy() == Qt::ScrollBarAsNeeded) { - verticalMargin = verticalScrollBarExtent + frameAroundContents; - } - - layoutBounds.rwidth() -= verticalMargin; - - option.features |= QStyleOptionViewItem::WrapText; - option.rect.setWidth(layoutBounds.width()); - - 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, - previousOffset, - static_cast(height) - })); - previousOffset += height; - } - - int totalHeight = previousOffset - layoutBounds.height(); - if (modelState != Models::MessageFeed::complete) { - totalHeight += progressSize; - } - vo = qMax(qMin(vo, totalHeight), 0); - bar->setRange(0, totalHeight); - bar->setPageStep(layoutBounds.height()); - bar->setValue(totalHeight - vo); - } - - positionProgress(); - - if (specialDelegate) { - clearWidgetsMode = true; - } - - - QAbstractItemView::updateGeometries(); -} - -bool FeedView::tryToCalculateGeometriesWithNoScrollbars(const QStyleOptionViewItem& option, const QAbstractItemModel* m, uint32_t totalHeight) -{ - 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) { - success = false; - break; - } - hints.emplace_back(Hint({ - false, - previousOffset, - static_cast(height) - })); - previousOffset += height; - } - - return success; -} - - -void FeedView::paintEvent(QPaintEvent* event) -{ - //qDebug() << "paint" << event->rect(); - const QAbstractItemModel* m = model(); - QWidget* vp = viewport(); - QRect zone = event->rect().translated(0, -vo); - uint32_t vph = vp->height(); - int32_t y1 = zone.y(); - int32_t y2 = y1 + zone.height(); - - bool inZone = false; - std::deque toRener; - for (std::deque::size_type i = 0; i < hints.size(); ++i) { - const Hint& hint = hints[i]; - int32_t relativeY1 = vph - hint.offset - hint.height; - if (!inZone) { - if (y2 > relativeY1) { - inZone = true; - } - } - if (inZone) { - toRener.emplace_back(m->index(i, 0, rootIndex())); - } - if (y1 > relativeY1) { - inZone = false; - break; - } - } - - QPainter painter(vp); - QStyleOptionViewItem option = viewOptions(); - option.features = QStyleOptionViewItem::WrapText; - QPoint cursor = vp->mapFromGlobal(QCursor::pos()); - - if (specialDelegate) { - MessageDelegate* del = static_cast(itemDelegate()); - if (clearWidgetsMode) { - del->beginClearWidgets(); - } - } - - 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) { - MessageDelegate* del = static_cast(itemDelegate()); - 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) -{ - 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; - - positionProgress(); - - if (specialDelegate) { - clearWidgetsMode = true; - } - - if (modelState == Models::MessageFeed::incomplete && value < progressSize) { - model()->fetchMore(rootIndex()); - } - - QAbstractItemView::verticalScrollbarValueChanged(vo); -} - -void FeedView::mouseMoveEvent(QMouseEvent* event) -{ - if (!isVisible()) { - return; - } - - QAbstractItemView::mouseMoveEvent(event); -} - -void FeedView::resizeEvent(QResizeEvent* event) -{ - QAbstractItemView::resizeEvent(event); - - positionProgress(); - emit resized(); -} - -void FeedView::positionProgress() -{ - QSize layoutBounds = maximumViewportSize(); - int progressPosition = layoutBounds.height() - progressSize; - std::deque::size_type size = hints.size(); - if (size > 0) { - const Hint& hint = hints[size - 1]; - progressPosition -= hint.offset + hint.height; - } - progressPosition += vo; - progressPosition = qMin(progressPosition, 0); - - progress.move((width() - progressSize) / 2, progressPosition); -} - -QFont FeedView::getFont() const -{ - return viewOptions().font; -} - -void FeedView::setItemDelegate(QAbstractItemDelegate* delegate) -{ - if (specialDelegate) { - MessageDelegate* del = static_cast(itemDelegate()); - disconnect(del, &MessageDelegate::buttonPushed, this, &FeedView::onMessageButtonPushed); - disconnect(del, &MessageDelegate::invalidPath, this, &FeedView::onMessageInvalidPath); - } - - QAbstractItemView::setItemDelegate(delegate); - - MessageDelegate* del = dynamic_cast(delegate); - if (del) { - specialDelegate = true; - connect(del, &MessageDelegate::buttonPushed, this, &FeedView::onMessageButtonPushed); - connect(del, &MessageDelegate::invalidPath, this, &FeedView::onMessageInvalidPath); - } else { - specialDelegate = false; - } -} - -void FeedView::setModel(QAbstractItemModel* p_model) -{ - if (specialModel) { - Models::MessageFeed* feed = static_cast(model()); - disconnect(feed, &Models::MessageFeed::syncStateChange, this, &FeedView::onModelSyncStateChange); - } - - QAbstractItemView::setModel(p_model); - - Models::MessageFeed* feed = dynamic_cast(p_model); - if (feed) { - onModelSyncStateChange(feed->getSyncState()); - specialModel = true; - connect(feed, &Models::MessageFeed::syncStateChange, this, &FeedView::onModelSyncStateChange); - } else { - onModelSyncStateChange(Models::MessageFeed::complete); - specialModel = false; - } -} - -void FeedView::onMessageButtonPushed(const QString& messageId) -{ - if (specialModel) { - Models::MessageFeed* feed = static_cast(model()); - feed->downloadAttachment(messageId); - } -} - -void FeedView::onMessageInvalidPath(const QString& messageId) -{ - if (specialModel) { - Models::MessageFeed* feed = static_cast(model()); - feed->reportLocalPathInvalid(messageId); - } -} - -void FeedView::onModelSyncStateChange(Models::MessageFeed::SyncState state) -{ - bool needToUpdateGeometry = false; - if (modelState != state) { - if (state == Models::MessageFeed::complete || modelState == Models::MessageFeed::complete) { - needToUpdateGeometry = true; - } - modelState = state; - - if (state == Models::MessageFeed::syncing) { - progress.show(); - progress.start(); - } else { - progress.stop(); - progress.hide(); - } - } - - if (needToUpdateGeometry) { - scheduleDelayedItemsLayout(); - } -} diff --git a/ui/widgets/messageline/feedview.h b/ui/widgets/messageline/feedview.h deleted file mode 100644 index 5e08946..0000000 --- a/ui/widgets/messageline/feedview.h +++ /dev/null @@ -1,98 +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 FEEDVIEW_H -#define FEEDVIEW_H - -#include - -#include -#include - -#include -#include - -/** - * @todo write docs - */ -class FeedView : public QAbstractItemView -{ - Q_OBJECT -public: - FeedView(QWidget* parent = nullptr); - ~FeedView(); - - QModelIndex indexAt(const QPoint & point) const override; - void scrollTo(const QModelIndex & index, QAbstractItemView::ScrollHint hint) override; - QRect visualRect(const QModelIndex & index) const override; - bool isIndexHidden(const QModelIndex & index) const override; - QModelIndex moveCursor(QAbstractItemView::CursorAction cursorAction, Qt::KeyboardModifiers modifiers) override; - void setSelection(const QRect & rect, QItemSelectionModel::SelectionFlags command) override; - QRegion visualRegionForSelection(const QItemSelection & selection) const override; - void setItemDelegate(QAbstractItemDelegate* delegate); - void setModel(QAbstractItemModel * model) override; - - QFont getFont() const; - -signals: - void resized(); - -public slots: - -protected slots: - void rowsInserted(const QModelIndex & parent, int start, int end) override; - void verticalScrollbarValueChanged(int value) override; - void dataChanged(const QModelIndex & topLeft, const QModelIndex & bottomRight, const QVector & roles) override; - void onMessageButtonPushed(const QString& messageId); - void onMessageInvalidPath(const QString& messageId); - void onModelSyncStateChange(Models::MessageFeed::SyncState state); - -protected: - int verticalOffset() const override; - int horizontalOffset() const override; - void paintEvent(QPaintEvent * event) override; - void updateGeometries() override; - void mouseMoveEvent(QMouseEvent * event) override; - void resizeEvent(QResizeEvent * event) override; - -private: - bool tryToCalculateGeometriesWithNoScrollbars(const QStyleOptionViewItem& option, const QAbstractItemModel* model, uint32_t totalHeight); - void positionProgress(); - void drawDateDevider(int top, const QDateTime& date, QPainter& painter); - -private: - struct Hint { - bool dirty; - uint32_t offset; - uint32_t height; - }; - std::deque hints; - int vo; - bool specialDelegate; - bool specialModel; - bool clearWidgetsMode; - Models::MessageFeed::SyncState modelState; - Progress progress; - QFont dividerFont; - QFontMetrics dividerMetrics; - - static const std::set geometryChangingRoles; - -}; - -#endif //FEEDVIEW_H diff --git a/ui/widgets/messageline/messagedelegate.cpp b/ui/widgets/messageline/messagedelegate.cpp deleted file mode 100644 index 649230e..0000000 --- a/ui/widgets/messageline/messagedelegate.cpp +++ /dev/null @@ -1,580 +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 -#include -#include -#include - -#include "messagedelegate.h" -#include "messagefeed.h" - -constexpr int avatarHeight = 50; -constexpr int margin = 6; -constexpr int textMargin = 2; -constexpr int statusIconSize = 16; - -MessageDelegate::MessageDelegate(QObject* parent): - QStyledItemDelegate(parent), - bodyFont(), - nickFont(), - dateFont(), - bodyMetrics(bodyFont), - nickMetrics(nickFont), - dateMetrics(dateFont), - buttonHeight(0), - barHeight(0), - 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()), - clearingWidgets(false) -{ - QPushButton btn; - buttonHeight = btn.sizeHint().height(); - - QProgressBar bar; - barHeight = bar.sizeHint().height(); -} - -MessageDelegate::~MessageDelegate() -{ - for (const std::pair& pair: *buttons){ - delete pair.second; - } - - for (const std::pair& pair: *bars){ - delete pair.second; - } - - for (const std::pair& pair: *statusIcons){ - delete pair.second; - } - - for (const std::pair& pair: *pencilIcons){ - delete pair.second; - } - - for (const std::pair& pair: *bodies){ - delete pair.second; - } - - for (const std::pair& pair: *previews){ - delete pair.second; - } - - delete statusIcons; - delete pencilIcons; - delete idsToKeep; - delete buttons; - delete bars; - delete bodies; - delete previews; -} - -void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const -{ - QVariant vi = index.data(Models::MessageFeed::Bulk); - if (!vi.isValid()) { - return; - } - Models::FeedItem data = qvariant_cast(vi); - painter->save(); - painter->setRenderHint(QPainter::Antialiasing, true); - - if (option.state & QStyle::State_MouseOver) { - painter->fillRect(option.rect, option.palette.brush(QPalette::Inactive, QPalette::Highlight)); - } - - QIcon icon(data.avatar); - - if (data.sentByMe) { - painter->drawPixmap(option.rect.width() - avatarHeight - margin, option.rect.y() + margin / 2, icon.pixmap(avatarHeight, avatarHeight)); - } else { - painter->drawPixmap(margin, option.rect.y() + margin / 2, icon.pixmap(avatarHeight, avatarHeight)); - } - - QStyleOptionViewItem opt = option; - QRect messageRect = option.rect.adjusted(margin, margin / 2, -(avatarHeight + 2 * margin), -margin / 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; - } - opt.rect = messageRect; - - QSize messageSize(0, 0); - QSize bodySize(0, 0); - if (data.text.size() > 0) { - messageSize = bodyMetrics.boundingRect(messageRect, Qt::TextWordWrap, data.text).size(); - bodySize = messageSize; - } - messageSize.rheight() += nickMetrics.lineSpacing(); - messageSize.rheight() += dateMetrics.height(); - 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, 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()); - } - - QRect rect; - painter->setFont(nickFont); - painter->drawText(opt.rect, opt.displayAlignment, data.sender, &rect); - opt.rect.adjust(0, rect.height() + textMargin, 0, 0); - painter->save(); - switch (data.attach.state) { - 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); - 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->show(); - opt.rect.adjust(0, bodySize.height() + textMargin, 0, 0); - } - painter->setFont(dateFont); - QColor q = painter->pen().color(); - q.setAlpha(180); - painter->setPen(q); - painter->drawText(opt.rect, opt.displayAlignment, dateString, &rect); - int currentY = opt.rect.y(); - if (data.sentByMe) { - QLabel* statusIcon = getStatusIcon(data); - - statusIcon->setParent(vp); - 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) { - idsToKeep->insert(data.id); - } -} - -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(); - QSize messageSize(0, 0); - if (body.size() > 0) { - messageSize = bodyMetrics.boundingRect(messageRect, Qt::TextWordWrap, body).size(); - messageSize.rheight() += textMargin; - } - - switch (attach.state) { - case Models::none: - break; - case Models::uploading: - messageSize.rheight() += Preview::calculateAttachSize(attach.localPath, messageRect).height() + textMargin; - case Models::downloading: - messageSize.rheight() += barHeight + textMargin; - break; - case Models::remote: - messageSize.rheight() += buttonHeight + textMargin; - break; - case Models::ready: - case Models::local: - 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() += Preview::calculateAttachSize(attach.localPath, messageRect).height() + textMargin; - messageSize.rheight() += dateMetrics.boundingRect(messageRect, Qt::TextWordWrap, attach.error).size().height() + textMargin; - break; - } - - messageSize.rheight() += nickMetrics.lineSpacing(); - messageSize.rheight() += textMargin; - messageSize.rheight() += dateMetrics.height() > statusIconSize ? dateMetrics.height() : statusIconSize; - - if (messageSize.height() < avatarHeight) { - messageSize.setHeight(avatarHeight); - } - - messageSize.rheight() += margin; - - return messageSize; -} - -void MessageDelegate::initializeFonts(const QFont& font) -{ - bodyFont = font; - nickFont = font; - dateFont = font; - - nickFont.setBold(true); - - float ndps = nickFont.pointSizeF(); - if (ndps != -1) { - nickFont.setPointSizeF(ndps * 1.2); - } else { - nickFont.setPointSize(nickFont.pointSize() + 2); - } - - dateFont.setItalic(true); - float dps = dateFont.pointSizeF(); - if (dps != -1) { - dateFont.setPointSizeF(dps * 0.8); - } else { - dateFont.setPointSize(dateFont.pointSize() - 2); - } - - 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) -{ - //qDebug() << event->type(); - - - return QStyledItemDelegate::editorEvent(event, model, option, index); -} - -void MessageDelegate::paintButton(QPushButton* btn, QPainter* painter, bool sentByMe, QStyleOptionViewItem& option) const -{ - QPoint start; - if (sentByMe) { - start = {option.rect.width() - btn->width(), option.rect.top()}; - } else { - start = option.rect.topLeft(); - } - - QWidget* vp = static_cast(painter->device()); - btn->setParent(vp); - btn->move(start); - btn->show(); - - 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(); - bar->resize(option.rect.width(), barHeight); - - painter->translate(start); - bar->render(painter, QPoint(), QRegion(), QWidget::DrawChildren); - - option.rect.adjust(0, barHeight + textMargin, 0, 0); -} - -void MessageDelegate::paintPreview(const Models::FeedItem& data, QPainter* painter, QStyleOptionViewItem& option) const -{ - 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 { - 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)); - } - - 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); -} - -QPushButton * MessageDelegate::getButton(const Models::FeedItem& data) const -{ - std::map::const_iterator itr = buttons->find(data.id); - FeedButton* result = 0; - if (itr != buttons->end()) { - result = itr->second; - } else { - std::map::const_iterator barItr = bars->find(data.id); - if (barItr != bars->end()) { - delete barItr->second; - bars->erase(barItr); - } - } - - if (result == 0) { - result = new FeedButton(); - result->messageId = data.id; - result->setText(QCoreApplication::translate("MessageLine", "Download")); - buttons->insert(std::make_pair(data.id, result)); - connect(result, &QPushButton::clicked, this, &MessageDelegate::onButtonPushed); - } - - return result; -} - -QProgressBar * MessageDelegate::getBar(const Models::FeedItem& data) const -{ - std::map::const_iterator barItr = bars->find(data.id); - QProgressBar* result = 0; - if (barItr != bars->end()) { - result = barItr->second; - } else { - std::map::const_iterator itr = buttons->find(data.id); - if (itr != buttons->end()) { - delete itr->second; - buttons->erase(itr); - } - } - - if (result == 0) { - result = new QProgressBar(); - result->setRange(0, 100); - bars->insert(std::make_pair(data.id, result)); - } - - result->setValue(data.attach.progress * 100); - - return result; -} - -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) { - if (data.error > 0) { - tt += ": " + data.error; - } - } - 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? - } - - 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); - QLabel* result = 0; - - if (itr != bodies->end()) { - result = itr->second; - } else { - result = new QLabel(); - result->setFont(bodyFont); - result->setWordWrap(true); - result->setOpenExternalLinks(true); - result->setTextInteractionFlags(result->textInteractionFlags() | Qt::TextSelectableByMouse | Qt::LinksAccessibleByMouse); - bodies->insert(std::make_pair(data.id, result)); - } - - result->setText(Shared::processMessageBody(data.text)); - - return result; -} - -void MessageDelegate::beginClearWidgets() -{ - idsToKeep->clear(); - 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) { - removeElements(buttons, idsToKeep); - removeElements(bars, idsToKeep); - removeElements(statusIcons, idsToKeep); - removeElements(pencilIcons, idsToKeep); - removeElements(bodies, idsToKeep); - removeElements(previews, idsToKeep); - - idsToKeep->clear(); - clearingWidgets = false; - } -} - -void MessageDelegate::onButtonPushed() const -{ - FeedButton* btn = static_cast(sender()); - emit buttonPushed(btn->messageId); -} - -void MessageDelegate::clearHelperWidget(const Models::FeedItem& data) const -{ - std::map::const_iterator itr = buttons->find(data.id); - if (itr != buttons->end()) { - delete itr->second; - buttons->erase(itr); - } else { - std::map::const_iterator barItr = bars->find(data.id); - if (barItr != bars->end()) { - delete barItr->second; - bars->erase(barItr); - } - } -} - -// void MessageDelegate::setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const -// { -// -// } diff --git a/ui/widgets/messageline/messagedelegate.h b/ui/widgets/messageline/messagedelegate.h deleted file mode 100644 index 7403285..0000000 --- a/ui/widgets/messageline/messagedelegate.h +++ /dev/null @@ -1,105 +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 MESSAGEDELEGATE_H -#define MESSAGEDELEGATE_H - -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -#include "shared/icons.h" -#include "shared/global.h" -#include "shared/utils.h" - -#include "preview.h" - -namespace Models { - struct FeedItem; -}; - -class MessageDelegate : public QStyledItemDelegate -{ - Q_OBJECT -public: - MessageDelegate(QObject *parent = nullptr); - ~MessageDelegate(); - - void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override; - QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const override; - //void setModelData(QWidget * editor, QAbstractItemModel * model, const QModelIndex & index) const override; - - void initializeFonts(const QFont& font); - bool editorEvent(QEvent * event, QAbstractItemModel * model, const QStyleOptionViewItem & option, const QModelIndex & index) override; - void endClearWidgets(); - void beginClearWidgets(); - -signals: - void buttonPushed(const QString& messageId) const; - 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; - 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; - -protected slots: - void onButtonPushed() const; - -private: - class FeedButton : public QPushButton { - public: - QString messageId; - }; - - QFont bodyFont; - QFont nickFont; - QFont dateFont; - QFontMetrics bodyMetrics; - QFontMetrics nickMetrics; - QFontMetrics dateMetrics; - - int buttonHeight; - int barHeight; - - std::map* buttons; - std::map* bars; - std::map* statusIcons; - std::map* pencilIcons; - std::map* bodies; - std::map* previews; - std::set* idsToKeep; - bool clearingWidgets; - -}; - -#endif // MESSAGEDELEGATE_H diff --git a/ui/widgets/messageline/messagefeed.cpp b/ui/widgets/messageline/messagefeed.cpp deleted file mode 100644 index 733cf1d..0000000 --- a/ui/widgets/messageline/messagefeed.cpp +++ /dev/null @@ -1,674 +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 "messagefeed.h" - -#include -#include - -#include - -const QHash Models::MessageFeed::roles = { - {Text, "text"}, - {Sender, "sender"}, - {Date, "date"}, - {DeliveryState, "deliveryState"}, - {Correction, "correction"}, - {SentByMe,"sentByMe"}, - {Avatar, "avatar"}, - {Attach, "attach"}, - {Id, "id"}, - {Error, "error"}, - {Bulk, "bulk"} -}; - -Models::MessageFeed::MessageFeed(const Element* ri, QObject* parent): - QAbstractListModel(parent), - storage(), - indexById(storage.get()), - indexByTime(storage.get