Compare commits
66 Commits
master
...
fix/handle
Author | SHA1 | Date | |
---|---|---|---|
c41abe903d | |||
8b3752ef47 | |||
8c6ac1a21d | |||
1c0802c8ca | |||
d84a33e144 | |||
4f6946a5fc | |||
f3153ef1db | |||
41d9fa7a1c | |||
261b34b712 | |||
d1f108e69d | |||
1e37aa762c | |||
a1f3c00a54 | |||
923afe2420 | |||
faa7d396a5 | |||
c55b7c6baf | |||
6764d8ea11 | |||
5fbb96618f | |||
d89c030e66 | |||
5787af8a4f | |||
1706b93b59 | |||
3f09b8f838 | |||
87c216b491 | |||
5f925217fc | |||
3f1fba4de2 | |||
ddfaa63a24 | |||
721f6daa36 | |||
0d584c5aba | |||
4307262f6e | |||
bd07c49755 | |||
8e99cc2969 | |||
a184ecafa3 | |||
7d2688151c | |||
0038aca1f6 | |||
6e06a1d5bc | |||
b7b70bc198 | |||
ce047db787 | |||
f45319de25 | |||
ebf0c64ffb | |||
d514db9c4a | |||
f34289399e | |||
05d6761baa | |||
216dcd29e9 | |||
0973cb2991 | |||
50190f3eac | |||
b44873d587 | |||
4c5efad9dc | |||
8310708c92 | |||
d936c0302d | |||
0e937199b0 | |||
48e498be25 | |||
3a7735b192 | |||
8f914c02a7 | |||
50bb3f5fd7 | |||
a0348b8fd2 | |||
85555da81f | |||
ebe5addfb5 | |||
b3c6860e25 | |||
00ffbac6b0 | |||
ff4124d1f0 | |||
15342f3c53 | |||
270a32db9e | |||
e0ef1ef797 | |||
e1eea2f3a2 | |||
e54cff0f0c | |||
4e6bd04b02 | |||
38159eafeb |
@ -6,10 +6,14 @@
|
||||
- requesting the history of the current chat after reconnection
|
||||
- global availability (in drop down list) gets restored after reconnection
|
||||
- status icon in active chat changes when presence of the pen pal changes
|
||||
- infinite progress when open the dialogue with something that has no history to show
|
||||
|
||||
### Improvements
|
||||
- slightly reduced the traffic on the startup by not requesting history of all MUCs
|
||||
|
||||
- completely rewritten message feed, now it works way faster
|
||||
- OPTIONAL RUNTIME dependency: "KIO Widgets" that is supposed to allow you to open a file in your default file manager
|
||||
- show in folder now is supposed to try it's best to show file in folder, even you don't have KIO installed
|
||||
- once uploaded local files don't get second time uploaded - the remote URL is reused
|
||||
|
||||
## Squawk 0.1.5 (Jul 29, 2020)
|
||||
### Bug fixes
|
||||
|
168
CMakeLists.txt
@ -1,70 +1,50 @@
|
||||
cmake_minimum_required(VERSION 3.0)
|
||||
project(squawk)
|
||||
cmake_minimum_required(VERSION 3.4)
|
||||
project(squawk VERSION 0.1.6 LANGUAGES CXX)
|
||||
|
||||
set(CMAKE_INCLUDE_CURRENT_DIR ON)
|
||||
set(CMAKE_CXX_STANDARD 14)
|
||||
cmake_policy(SET CMP0076 NEW)
|
||||
cmake_policy(SET CMP0079 NEW)
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
|
||||
set(CMAKE_AUTOMOC ON)
|
||||
set(CMAKE_AUTOUIC ON)
|
||||
set(CMAKE_AUTORCC ON)
|
||||
|
||||
include(GNUInstallDirs)
|
||||
include_directories(.)
|
||||
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake")
|
||||
|
||||
find_package(Qt5Widgets CONFIG REQUIRED)
|
||||
find_package(Qt5LinguistTools)
|
||||
|
||||
if(NOT CMAKE_BUILD_TYPE)
|
||||
set(CMAKE_BUILD_TYPE Debug)
|
||||
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()
|
||||
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "-g -Wall -Wextra")
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "-O3")
|
||||
message("Build type: ${CMAKE_BUILD_TYPE}")
|
||||
|
||||
|
||||
set(squawk_SRC
|
||||
main.cpp
|
||||
exception.cpp
|
||||
signalcatcher.cpp
|
||||
shared/global.cpp
|
||||
shared/utils.cpp
|
||||
shared/message.cpp
|
||||
shared/vcard.cpp
|
||||
shared/icons.cpp
|
||||
)
|
||||
|
||||
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)
|
||||
execute_process(COMMAND convert -background none -size 48x48 squawk.svg squawk48.png WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
|
||||
execute_process(COMMAND convert -background none -size 64x64 squawk.svg squawk64.png WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
|
||||
execute_process(COMMAND convert -background none -size 128x128 squawk.svg squawk128.png WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
|
||||
execute_process(COMMAND convert -background none -size 256x256 squawk.svg squawk256.png WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
|
||||
|
||||
configure_file(packaging/squawk.desktop squawk.desktop COPYONLY)
|
||||
|
||||
set(TS_FILES
|
||||
translations/squawk.ru.ts
|
||||
)
|
||||
qt5_add_translation(QM_FILES ${TS_FILES})
|
||||
add_custom_target(translations ALL DEPENDS ${QM_FILES})
|
||||
|
||||
qt5_add_resources(RCC resources/resources.qrc)
|
||||
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)
|
||||
|
||||
@ -77,9 +57,26 @@ if (SYSTEM_QXMPP)
|
||||
endif ()
|
||||
|
||||
if (NOT SYSTEM_QXMPP)
|
||||
target_link_libraries(squawk PRIVATE qxmpp)
|
||||
add_subdirectory(external/qxmpp)
|
||||
else ()
|
||||
target_link_libraries(squawk PRIVATE QXmpp::QXmpp)
|
||||
endif ()
|
||||
|
||||
## KIO
|
||||
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)
|
||||
|
||||
@ -87,31 +84,60 @@ if (WITH_KWALLET)
|
||||
set(WITH_KWALLET OFF)
|
||||
message("KWallet package wasn't found, KWallet support module wouldn't be built")
|
||||
else ()
|
||||
add_definitions(-DWITH_KWALLET)
|
||||
target_compile_definitions(squawk PRIVATE WITH_KWALLET)
|
||||
message("Building with support of KWallet")
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
add_executable(squawk ${squawk_SRC} ${squawk_HEAD} ${RCC})
|
||||
target_link_libraries(squawk Qt5::Widgets)
|
||||
## 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)
|
||||
set(CMAKE_BUILD_TYPE Debug)
|
||||
endif ()
|
||||
|
||||
message("Build type: ${CMAKE_BUILD_TYPE}")
|
||||
|
||||
if(CMAKE_COMPILER_IS_GNUCXX)
|
||||
target_compile_options(squawk PRIVATE
|
||||
"-Wall;-Wextra"
|
||||
"$<$<CONFIG:DEBUG>:-g>"
|
||||
"$<$<CONFIG:RELEASE>:-O3>"
|
||||
)
|
||||
endif(CMAKE_COMPILER_IS_GNUCXX)
|
||||
|
||||
add_subdirectory(ui)
|
||||
add_subdirectory(core)
|
||||
|
||||
add_subdirectory(external/simpleCrypt)
|
||||
|
||||
target_link_libraries(squawk squawkUI)
|
||||
target_link_libraries(squawk squawkCORE)
|
||||
target_link_libraries(squawk uuid)
|
||||
|
||||
add_dependencies(${CMAKE_PROJECT_NAME} translations)
|
||||
add_subdirectory(packaging)
|
||||
add_subdirectory(plugins)
|
||||
add_subdirectory(resources)
|
||||
add_subdirectory(shared)
|
||||
add_subdirectory(translations)
|
||||
add_subdirectory(ui)
|
||||
|
||||
# Install the executable
|
||||
install(TARGETS squawk DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||
install(FILES ${QM_FILES} DESTINATION ${CMAKE_INSTALL_DATADIR}/squawk/l10n)
|
||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk.svg DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/scalable/apps)
|
||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk48.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/48x48/apps RENAME squawk.png)
|
||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk64.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/64x64/apps RENAME squawk.png)
|
||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk128.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/128x128/apps RENAME squawk.png)
|
||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk256.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/256x256/apps RENAME squawk.png)
|
||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk.desktop DESTINATION ${CMAKE_INSTALL_DATADIR}/applications)
|
||||
|
||||
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()
|
||||
|
||||
|
32
README.md
@ -9,11 +9,12 @@
|
||||
### 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
|
||||
- kwallet (optional)
|
||||
- KDE Frameworks: kwallet (optional)
|
||||
- KDE Frameworks: KIO (optional)
|
||||
- Boost
|
||||
|
||||
### Getting
|
||||
|
||||
@ -33,6 +34,26 @@ You can also clone the repo and build it from source
|
||||
|
||||
Squawk requires Qt with SSL enabled. It uses CMake as build system.
|
||||
|
||||
Please check the prerequisites and install them before installation.
|
||||
|
||||
#### For Windows (Mingw-w64) build
|
||||
|
||||
You need Qt for mingw64 (MinGW 64-bit) platform when installing Qt.
|
||||
|
||||
The best way to acquire library `lmdb` and `boost` is through Msys2.
|
||||
|
||||
First install Msys2, and then install `mingw-w64-x86_64-lmdb` and `mingw-w64-x86_64-boost` by pacman.
|
||||
|
||||
Then you need to provide the cmake cache entry when calling cmake for configuration:
|
||||
|
||||
```
|
||||
cmake .. -D LMDB_ROOT_DIR:PATH=<Msys2 Mingw64 Root Directory> -D BOOST_ROOT:PATH=<Msys2 Mingw64 Root Directory>
|
||||
```
|
||||
|
||||
`<Msys2 Mingw64 Root Directory>`: e.g. `C:/msys64/mingw64`.
|
||||
|
||||
---
|
||||
|
||||
There are two ways to build, it depends whether you have qxmpp installed in your system
|
||||
|
||||
#### Building with system qxmpp
|
||||
@ -44,7 +65,7 @@ $ git clone https://git.macaw.me/blue/squawk
|
||||
$ cd squawk
|
||||
$ mkdir build
|
||||
$ cd build
|
||||
$ cmake ..
|
||||
$ cmake .. [-D LMDB_ROOT_DIR:PATH=...] [-D BOOST_ROOT:PATH=...]
|
||||
$ cmake --build .
|
||||
```
|
||||
|
||||
@ -57,16 +78,19 @@ $ git clone --recurse-submodules https://git.macaw.me/blue/squawk
|
||||
$ cd squawk
|
||||
$ mkdir build
|
||||
$ cd build
|
||||
$ cmake .. -D SYSTEM_QXMPP=False
|
||||
$ cmake .. -D SYSTEM_QXMPP=False [-D LMDB_ROOT_DIR:PATH=...] [-D BOOST_ROOT:PATH=...]
|
||||
$ cmake --build .
|
||||
```
|
||||
|
||||
You can always refer to `appveyor.yml` to see how AppVeyor build squawk.
|
||||
|
||||
### List of keys
|
||||
|
||||
Here is the list of keys you can pass to configuration phase of `cmake ..`.
|
||||
- `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
|
||||
|
||||
|
117
appveyor.yml
Normal file
@ -0,0 +1,117 @@
|
||||
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)
|
52
cmake/FindLMDB.cmake
Normal file
@ -0,0 +1,52 @@
|
||||
#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
|
||||
)
|
15
cmake/FindSignal.cmake
Normal file
@ -0,0 +1,15 @@
|
||||
find_path(Signal_INCLUDE_DIR NAMES signal/signal_protocol.h)
|
||||
find_library(Signal_LIBRARY signal-protocol-c)
|
||||
mark_as_advanced(Signal_INCLUDE_DIR Signal_LIBRARY)
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(Signal REQUIRED_VARS Signal_LIBRARY Signal_INCLUDE_DIR)
|
||||
|
||||
if (Signal_FOUND AND NOT TARGET Signal::Signal)
|
||||
add_library(Signal::Signal UNKNOWN IMPORTED)
|
||||
set_target_properties(Signal::Signal PROPERTIES
|
||||
IMPORTED_LINK_INTERFACE_LANGUAGES "C"
|
||||
IMPORTED_LOCATION "${Signal_LIBRARY}"
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${Signal_INCLUDE_DIR}"
|
||||
)
|
||||
endif ()
|
41
cmake/MacOSXBundleInfo.plist.in
Normal file
@ -0,0 +1,41 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>English</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>${MACOSX_BUNDLE_EXECUTABLE_NAME}</string>
|
||||
<key>CFBundleGetInfoString</key>
|
||||
<string>${MACOSX_BUNDLE_INFO_STRING}</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>${MACOSX_BUNDLE_ICON_FILE}</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>${MACOSX_BUNDLE_GUI_IDENTIFIER}</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleLongVersionString</key>
|
||||
<string>${MACOSX_BUNDLE_LONG_VERSION_STRING}</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>${MACOSX_BUNDLE_BUNDLE_NAME}</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>${MACOSX_BUNDLE_SHORT_VERSION_STRING}</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>${MACOSX_BUNDLE_BUNDLE_VERSION}</string>
|
||||
<key>CSResourcesFileMapped</key>
|
||||
<true/>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>${MACOSX_BUNDLE_COPYRIGHT}</string>
|
||||
|
||||
<key>NSPrincipalClass</key>
|
||||
<string>NSApplication</string>
|
||||
<key>NSHighResolutionCapable</key>
|
||||
<string>True</string>
|
||||
|
||||
</dict>
|
||||
</plist>
|
@ -1,46 +1,34 @@
|
||||
cmake_minimum_required(VERSION 3.0)
|
||||
project(squawkCORE)
|
||||
set(SIGNALCATCHER_SOURCE signalcatcher.cpp)
|
||||
if(WIN32)
|
||||
set(SIGNALCATCHER_SOURCE signalcatcher_win32.cpp)
|
||||
endif(WIN32)
|
||||
|
||||
set(CMAKE_AUTOMOC ON)
|
||||
|
||||
find_package(Qt5Core CONFIG REQUIRED)
|
||||
find_package(Qt5Gui CONFIG REQUIRED)
|
||||
find_package(Qt5Network CONFIG REQUIRED)
|
||||
find_package(Qt5Xml CONFIG REQUIRED)
|
||||
|
||||
set(squawkCORE_SRC
|
||||
squawk.cpp
|
||||
target_sources(squawk PRIVATE
|
||||
account.cpp
|
||||
archive.cpp
|
||||
rosteritem.cpp
|
||||
contact.cpp
|
||||
conference.cpp
|
||||
storage.cpp
|
||||
networkaccess.cpp
|
||||
account.h
|
||||
adapterFuctions.cpp
|
||||
handlers/messagehandler.cpp
|
||||
handlers/rosterhandler.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
|
||||
)
|
||||
|
||||
target_include_directories(squawk PRIVATE ${LMDB_INCLUDE_DIRS})
|
||||
|
||||
add_subdirectory(handlers)
|
||||
add_subdirectory(passwordStorageEngines)
|
||||
|
||||
# Tell CMake to create the helloworld executable
|
||||
add_library(squawkCORE ${squawkCORE_SRC})
|
||||
|
||||
|
||||
if(SYSTEM_QXMPP)
|
||||
get_target_property(QXMPP_INTERFACE_INCLUDE_DIRECTORIES QXmpp::QXmpp INTERFACE_INCLUDE_DIRECTORIES)
|
||||
target_include_directories(squawkCORE PUBLIC ${QXMPP_INTERFACE_INCLUDE_DIRECTORIES})
|
||||
endif()
|
||||
|
||||
# Use the Widgets module from Qt 5.
|
||||
target_link_libraries(squawkCORE Qt5::Core)
|
||||
target_link_libraries(squawkCORE Qt5::Network)
|
||||
target_link_libraries(squawkCORE Qt5::Gui)
|
||||
target_link_libraries(squawkCORE Qt5::Xml)
|
||||
target_link_libraries(squawkCORE qxmpp)
|
||||
target_link_libraries(squawkCORE lmdb)
|
||||
target_link_libraries(squawkCORE simpleCrypt)
|
||||
if (WITH_KWALLET)
|
||||
target_link_libraries(squawkCORE kwalletPSE)
|
||||
endif()
|
||||
|
@ -19,6 +19,7 @@
|
||||
#include "account.h"
|
||||
#include <QXmppMessage.h>
|
||||
#include <QDateTime>
|
||||
#include "shared/utils.h"
|
||||
|
||||
using namespace Core;
|
||||
|
||||
@ -84,8 +85,9 @@ Account::Account(const QString& p_login, const QString& p_server, const QString&
|
||||
QObject::connect(dm, &QXmppDiscoveryManager::itemsReceived, this, &Account::onDiscoveryItemsReceived);
|
||||
QObject::connect(dm, &QXmppDiscoveryManager::infoReceived, this, &Account::onDiscoveryInfoReceived);
|
||||
|
||||
QObject::connect(network, &NetworkAccess::uploadFileComplete, mh, &MessageHandler::onFileUploaded);
|
||||
QObject::connect(network, &NetworkAccess::uploadFileError, mh, &MessageHandler::onFileUploadError);
|
||||
QObject::connect(network, &NetworkAccess::uploadFileComplete, mh, &MessageHandler::onUploadFileComplete);
|
||||
QObject::connect(network, &NetworkAccess::downloadFileComplete, mh, &MessageHandler::onDownloadFileComplete);
|
||||
QObject::connect(network, &NetworkAccess::loadFileError, mh, &MessageHandler::onLoadFileError);
|
||||
|
||||
client.addExtension(rcpm);
|
||||
QObject::connect(rcpm, &QXmppMessageReceiptManager::messageDelivered, mh, &MessageHandler::onReceiptReceived);
|
||||
@ -155,8 +157,9 @@ Account::~Account()
|
||||
reconnectTimer->stop();
|
||||
}
|
||||
|
||||
QObject::disconnect(network, &NetworkAccess::uploadFileComplete, mh, &MessageHandler::onFileUploaded);
|
||||
QObject::disconnect(network, &NetworkAccess::uploadFileError, mh, &MessageHandler::onFileUploadError);
|
||||
QObject::disconnect(network, &NetworkAccess::uploadFileComplete, mh, &MessageHandler::onUploadFileComplete);
|
||||
QObject::disconnect(network, &NetworkAccess::downloadFileComplete, mh, &MessageHandler::onDownloadFileComplete);
|
||||
QObject::disconnect(network, &NetworkAccess::loadFileError, mh, &MessageHandler::onLoadFileError);
|
||||
|
||||
delete mh;
|
||||
delete rh;
|
||||
@ -402,12 +405,13 @@ QString Core::Account::getFullJid() const {
|
||||
void Core::Account::sendMessage(const Shared::Message& data) {
|
||||
mh->sendMessage(data);}
|
||||
|
||||
void Core::Account::sendMessage(const Shared::Message& data, const QString& path) {
|
||||
mh->sendMessage(data, path);}
|
||||
|
||||
void Core::Account::onMamMessageReceived(const QString& queryId, const QXmppMessage& msg)
|
||||
void Core::Account::onMamMessageReceived(const QString& queryId, const QXmppMessage& msgConst)
|
||||
{
|
||||
if (msg.id().size() > 0 && (msg.body().size() > 0 || msg.outOfBandUrl().size() > 0)) {
|
||||
QXmppMessage msg(msgConst);
|
||||
if (msg.id().size() == 0) {
|
||||
msg.setId(Shared::generateUUID() + QStringLiteral("-squawkgenerated"));
|
||||
}
|
||||
if ((msg.body().size() > 0 || msg.outOfBandUrl().size() > 0)) {
|
||||
std::map<QString, QString>::const_iterator itr = archiveQueries.find(queryId);
|
||||
if (itr != archiveQueries.end()) {
|
||||
QString jid = itr->second;
|
||||
@ -434,13 +438,13 @@ void Core::Account::requestArchive(const QString& jid, int count, const QString&
|
||||
|
||||
if (contact == 0) {
|
||||
qDebug() << "An attempt to request archive for" << jid << "in account" << name << ", but the contact with such id wasn't found, skipping";
|
||||
emit responseArchive(jid, std::list<Shared::Message>());
|
||||
emit responseArchive(jid, std::list<Shared::Message>(), true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (state != Shared::ConnectionState::connected) {
|
||||
qDebug() << "An attempt to request archive for" << jid << "in account" << name << ", but the account is not online, skipping";
|
||||
emit responseArchive(contact->jid, std::list<Shared::Message>());
|
||||
emit responseArchive(contact->jid, std::list<Shared::Message>(), false);
|
||||
}
|
||||
|
||||
contact->requestHistory(count, before);
|
||||
@ -461,6 +465,11 @@ 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
|
||||
@ -469,6 +478,12 @@ 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;
|
||||
}
|
||||
@ -552,9 +567,11 @@ void Core::Account::onClientError(QXmppClient::Error err)
|
||||
case QXmppStanza::Error::NotAuthorized:
|
||||
errorText = "Authentication error";
|
||||
break;
|
||||
#if (QXMPP_VERSION) < QT_VERSION_CHECK(1, 3, 0)
|
||||
case QXmppStanza::Error::PaymentRequired:
|
||||
errorText = "Payment is required";
|
||||
break;
|
||||
#endif
|
||||
case QXmppStanza::Error::RecipientUnavailable:
|
||||
errorText = "Recipient is unavailable";
|
||||
break;
|
||||
@ -909,3 +926,19 @@ void Core::Account::handleDisconnection()
|
||||
ownVCardRequestInProgress = false;
|
||||
}
|
||||
|
||||
void Core::Account::onContactHistoryResponse(const std::list<Shared::Message>& list, bool last)
|
||||
{
|
||||
RosterItem* contact = static_cast<RosterItem*>(sender());
|
||||
|
||||
qDebug() << "Collected history for contact " << contact->jid << list.size() << "elements";
|
||||
if (last) {
|
||||
qDebug() << "The response contains the first accounted message";
|
||||
}
|
||||
emit responseArchive(contact->jid, list, last);
|
||||
}
|
||||
|
||||
void Core::Account::requestChangeMessage(const QString& jid, const QString& messageId, const QMap<QString, QVariant>& data){
|
||||
mh->requestChangeMessage(jid, messageId, data);}
|
||||
|
||||
void Core::Account::resendMessage(const QString& jid, const QString& id) {
|
||||
mh->resendMessage(jid, id);}
|
||||
|
@ -43,7 +43,7 @@
|
||||
#include <QXmppVCardManager.h>
|
||||
#include <QXmppMessageReceiptManager.h>
|
||||
|
||||
#include "shared.h"
|
||||
#include "shared/shared.h"
|
||||
#include "contact.h"
|
||||
#include "conference.h"
|
||||
#include "networkaccess.h"
|
||||
@ -88,7 +88,6 @@ public:
|
||||
void setPasswordType(Shared::AccountPassword pt);
|
||||
QString getFullJid() const;
|
||||
void sendMessage(const Shared::Message& data);
|
||||
void sendMessage(const Shared::Message& data, const QString& path);
|
||||
void requestArchive(const QString& jid, int count, const QString& before);
|
||||
void subscribeToContact(const QString& jid, const QString& reason);
|
||||
void unsubscribeFromContact(const QString& jid, const QString& reason);
|
||||
@ -97,12 +96,14 @@ public:
|
||||
void addContactToGroupRequest(const QString& jid, const QString& groupName);
|
||||
void removeContactFromGroupRequest(const QString& jid, const QString& groupName);
|
||||
void renameContactRequest(const QString& jid, const QString& newName);
|
||||
void requestChangeMessage(const QString& jid, const QString& messageId, const QMap<QString, QVariant>& data);
|
||||
|
||||
void setRoomJoined(const QString& jid, bool joined);
|
||||
void setRoomAutoJoin(const QString& jid, bool joined);
|
||||
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();
|
||||
@ -127,14 +128,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<QString, QVariant>& data);
|
||||
void responseArchive(const QString& jid, const std::list<Shared::Message>& list);
|
||||
void responseArchive(const QString& jid, const std::list<Shared::Message>& list, bool last);
|
||||
void error(const QString& text);
|
||||
void addRoomParticipant(const QString& jid, const QString& nickName, const QMap<QString, QVariant>& data);
|
||||
void changeRoomParticipant(const QString& jid, const QString& nickName, const QMap<QString, QVariant>& data);
|
||||
void removeRoomParticipant(const QString& jid, const QString& nickName);
|
||||
void receivedVCard(const QString& jid, const Shared::VCard& card);
|
||||
void uploadFile(const QFileInfo& file, const QUrl& set, const QUrl& get, QMap<QString, QString> headers);
|
||||
void uploadFileError(const QString& messageId, const QString& error);
|
||||
void uploadFileError(const QString& jid, const QString& messageId, const QString& error);
|
||||
|
||||
private:
|
||||
QString name;
|
||||
@ -183,6 +184,7 @@ private slots:
|
||||
|
||||
void onDiscoveryItemsReceived (const QXmppDiscoveryIq& items);
|
||||
void onDiscoveryInfoReceived (const QXmppDiscoveryIq& info);
|
||||
void onContactHistoryResponse(const std::list<Shared::Message>& list, bool last);
|
||||
|
||||
private:
|
||||
void handleDisconnection();
|
||||
|
@ -271,6 +271,8 @@ void Core::Archive::changeMessage(const QString& id, const QMap<QString, QVarian
|
||||
bool hadStanzaId = msg.getStanzaId().size() > 0;
|
||||
QDateTime oTime = msg.getTime();
|
||||
bool idChange = msg.change(data);
|
||||
QDateTime nTime = msg.getTime();
|
||||
bool orderChange = oTime != nTime;
|
||||
|
||||
MDB_val lmdbKey, lmdbData;
|
||||
QByteArray ba;
|
||||
@ -280,15 +282,21 @@ void Core::Archive::changeMessage(const QString& id, const QMap<QString, QVarian
|
||||
lmdbKey.mv_size = strId.size();
|
||||
lmdbKey.mv_data = (char*)strId.c_str();
|
||||
int rc;
|
||||
if (idChange || orderChange) {
|
||||
if (idChange) {
|
||||
rc = mdb_del(txn, main, &lmdbKey, &lmdbData);
|
||||
} else {
|
||||
quint64 ostamp = oTime.toMSecsSinceEpoch();
|
||||
lmdbData.mv_data = (quint8*)&ostamp;
|
||||
lmdbData.mv_size = 8;
|
||||
rc = mdb_del(txn, order, &lmdbData, &lmdbKey);
|
||||
}
|
||||
if (rc == 0) {
|
||||
strId = msg.getId().toStdString();
|
||||
lmdbKey.mv_size = strId.size();
|
||||
lmdbKey.mv_data = (char*)strId.c_str();
|
||||
|
||||
|
||||
quint64 stamp = oTime.toMSecsSinceEpoch();
|
||||
quint64 stamp = nTime.toMSecsSinceEpoch();
|
||||
lmdbData.mv_data = (quint8*)&stamp;
|
||||
lmdbData.mv_size = 8;
|
||||
rc = mdb_put(txn, order, &lmdbData, &lmdbKey, 0);
|
||||
@ -300,8 +308,9 @@ void Core::Archive::changeMessage(const QString& id, const QMap<QString, QVarian
|
||||
}
|
||||
}
|
||||
|
||||
if (msg.getStanzaId().size() > 0 && (idChange || !hadStanzaId)) {
|
||||
const std::string& szid = msg.getStanzaId().toStdString();
|
||||
QString qsid = msg.getStanzaId();
|
||||
if (qsid.size() > 0 && (idChange || !hadStanzaId)) {
|
||||
std::string szid = qsid.toStdString();
|
||||
|
||||
lmdbData.mv_size = szid.size();
|
||||
lmdbData.mv_data = (char*)szid.c_str();
|
||||
@ -502,8 +511,9 @@ long unsigned int Core::Archive::size() const
|
||||
mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn);
|
||||
MDB_stat stat;
|
||||
mdb_stat(txn, order, &stat);
|
||||
size_t amount = stat.ms_entries;
|
||||
mdb_txn_abort(txn);
|
||||
return stat.ms_entries;
|
||||
return amount;
|
||||
}
|
||||
|
||||
std::list<Shared::Message> Core::Archive::getBefore(int count, const QString& id)
|
||||
@ -603,10 +613,10 @@ void Core::Archive::setFromTheBeginning(bool is)
|
||||
MDB_txn *txn;
|
||||
mdb_txn_begin(environment, NULL, 0, &txn);
|
||||
bool success = setStatValue("beginning", is, txn);
|
||||
if (success != 0) {
|
||||
mdb_txn_abort(txn);
|
||||
} else {
|
||||
if (success) {
|
||||
mdb_txn_commit(txn);
|
||||
} else {
|
||||
mdb_txn_abort(txn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -25,7 +25,7 @@
|
||||
#include <QMimeType>
|
||||
|
||||
#include "shared/message.h"
|
||||
#include "exception.h"
|
||||
#include "shared/exception.h"
|
||||
#include <lmdb.h>
|
||||
#include <list>
|
||||
|
||||
|
6
core/handlers/CMakeLists.txt
Normal file
@ -0,0 +1,6 @@
|
||||
target_sources(squawk PRIVATE
|
||||
messagehandler.cpp
|
||||
messagehandler.h
|
||||
rosterhandler.cpp
|
||||
rosterhandler.h
|
||||
)
|
@ -23,7 +23,6 @@ Core::MessageHandler::MessageHandler(Core::Account* account):
|
||||
QObject(),
|
||||
acc(account),
|
||||
pendingStateMessages(),
|
||||
pendingMessages(),
|
||||
uploadingSlotsQueue()
|
||||
{
|
||||
}
|
||||
@ -74,8 +73,7 @@ void Core::MessageHandler::onMessageReceived(const QXmppMessage& msg)
|
||||
|
||||
bool Core::MessageHandler::handleChatMessage(const QXmppMessage& msg, bool outgoing, bool forwarded, bool guessing)
|
||||
{
|
||||
const QString& body(msg.body());
|
||||
if (body.size() != 0) {
|
||||
if (msg.body().size() != 0 || msg.outOfBandUrl().size() > 0) {
|
||||
Shared::Message sMsg(Shared::Message::chat);
|
||||
initializeMessage(sMsg, msg, outgoing, forwarded, guessing);
|
||||
QString jid = sMsg.getPenPalJid();
|
||||
@ -168,14 +166,16 @@ void Core::MessageHandler::initializeMessage(Shared::Message& target, const QXmp
|
||||
id = source.id();
|
||||
#endif
|
||||
target.setId(id);
|
||||
if (target.getId().size() == 0) {
|
||||
QString messageId = target.getId();
|
||||
if (messageId.size() == 0) {
|
||||
target.generateRandomId(); //TODO out of desperation, I need at least a random ID
|
||||
messageId = target.getId();
|
||||
}
|
||||
target.setFrom(source.from());
|
||||
target.setTo(source.to());
|
||||
target.setBody(source.body());
|
||||
target.setForwarded(forwarded);
|
||||
target.setOutOfBandUrl(source.outOfBandUrl());
|
||||
|
||||
if (guessing) {
|
||||
if (target.getFromJid() == acc->getLogin() + "@" + acc->getServer()) {
|
||||
outgoing = true;
|
||||
@ -189,6 +189,12 @@ void Core::MessageHandler::initializeMessage(Shared::Message& target, const QXmp
|
||||
} else {
|
||||
target.setCurrentTime();
|
||||
}
|
||||
|
||||
QString oob = source.outOfBandUrl();
|
||||
if (oob.size() > 0) {
|
||||
target.setAttachPath(acc->network->addMessageAndCheckForPath(oob, acc->getName(), target.getPenPalJid(), messageId));
|
||||
}
|
||||
target.setOutOfBandUrl(oob);
|
||||
}
|
||||
|
||||
void Core::MessageHandler::logMessage(const QXmppMessage& msg, const QString& reason)
|
||||
@ -227,16 +233,29 @@ void Core::MessageHandler::onReceiptReceived(const QString& jid, const QString&
|
||||
if (ri != 0) {
|
||||
ri->changeMessage(id, cData);
|
||||
}
|
||||
pendingStateMessages.erase(itr);
|
||||
emit acc->changeMessage(itr->second, id, cData);
|
||||
pendingStateMessages.erase(itr);
|
||||
}
|
||||
}
|
||||
|
||||
void Core::MessageHandler::sendMessage(Shared::Message data)
|
||||
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)
|
||||
{
|
||||
QString jid = data.getPenPalJid();
|
||||
QString id = data.getId();
|
||||
QString oob = data.getOutOfBandUrl();
|
||||
RosterItem* ri = acc->rh->getRosterItem(jid);
|
||||
bool sent = false;
|
||||
QMap<QString, QVariant> changes;
|
||||
QDateTime sendTime = QDateTime::currentDateTimeUtc();
|
||||
if (acc->state == Shared::ConnectionState::connected) {
|
||||
QXmppMessage msg(acc->getFullJid(), data.getTo(), data.getBody(), data.getThread());
|
||||
|
||||
@ -245,23 +264,18 @@ void Core::MessageHandler::sendMessage(Shared::Message data)
|
||||
#endif
|
||||
msg.setId(id);
|
||||
msg.setType(static_cast<QXmppMessage::Type>(data.getType())); //it is safe here, my type is compatible
|
||||
msg.setOutOfBandUrl(data.getOutOfBandUrl());
|
||||
msg.setOutOfBandUrl(oob);
|
||||
msg.setReceiptRequested(true);
|
||||
msg.setStamp(sendTime);
|
||||
|
||||
bool sent = acc->client.sendPacket(msg);
|
||||
sent = acc->client.sendPacket(msg);
|
||||
//sent = false;
|
||||
|
||||
if (sent) {
|
||||
data.setState(Shared::Message::State::sent);
|
||||
} else {
|
||||
data.setState(Shared::Message::State::error);
|
||||
data.setErrorText("Couldn't send message via QXMPP library check out logs");
|
||||
}
|
||||
|
||||
if (ri != 0) {
|
||||
ri->appendMessageToArchive(data);
|
||||
if (sent) {
|
||||
pendingStateMessages.insert(std::make_pair(id, jid));
|
||||
}
|
||||
data.setErrorText("Couldn't send message: internal QXMPP library error, probably need to check out the logs");
|
||||
}
|
||||
|
||||
} else {
|
||||
@ -269,57 +283,97 @@ void Core::MessageHandler::sendMessage(Shared::Message data)
|
||||
data.setErrorText("You are is offline or reconnecting");
|
||||
}
|
||||
|
||||
emit acc->changeMessage(jid, id, {
|
||||
{"state", static_cast<uint>(data.getState())},
|
||||
{"errorText", data.getErrorText()}
|
||||
});
|
||||
Shared::Message::State mstate = data.getState();
|
||||
changes.insert("state", static_cast<uint>(mstate));
|
||||
if (mstate == Shared::Message::State::error) {
|
||||
changes.insert("errorText", data.getErrorText());
|
||||
}
|
||||
if (oob.size() > 0) {
|
||||
changes.insert("outOfBandUrl", oob);
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
void Core::MessageHandler::sendMessage(const Shared::Message& data, const QString& path)
|
||||
emit acc->changeMessage(jid, id, changes);
|
||||
}
|
||||
|
||||
void Core::MessageHandler::prepareUpload(const Shared::Message& data, bool newMessage)
|
||||
{
|
||||
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);
|
||||
sendMessageWithLocalUploadedFile(data, url, newMessage);
|
||||
} else {
|
||||
if (acc->network->isUploading(path, data.getId())) {
|
||||
pendingMessages.emplace(data.getId(), data);
|
||||
pendingStateMessages.insert(std::make_pair(id, jid));
|
||||
if (newMessage) {
|
||||
ri->appendMessageToArchive(data);
|
||||
} else {
|
||||
QMap<QString, QVariant> changes({
|
||||
{"state", (uint)Shared::Message::State::pending}
|
||||
});
|
||||
ri->changeMessage(id, changes);
|
||||
emit acc->changeMessage(jid, id, changes);
|
||||
}
|
||||
//this checks if the file is already uploading, and if so it subscribes to it's success, so, i need to do stuff only if the network knows nothing of this file
|
||||
if (!acc->network->checkAndAddToUploading(acc->getName(), jid, id, path)) {
|
||||
if (acc->um->serviceFound()) {
|
||||
QFileInfo file(path);
|
||||
if (file.exists() && file.isReadable()) {
|
||||
uploadingSlotsQueue.emplace_back(path, data);
|
||||
pendingStateMessages.insert(std::make_pair(id, jid));
|
||||
uploadingSlotsQueue.emplace_back(path, id);
|
||||
if (uploadingSlotsQueue.size() == 1) {
|
||||
acc->um->requestUploadSlot(file);
|
||||
}
|
||||
} else {
|
||||
onFileUploadError(data.getId(), "Uploading file no longer exists or your system user has no permission to read it");
|
||||
handleUploadError(jid, id, "Uploading file no longer exists or your system user has no permission to read it");
|
||||
qDebug() << "Requested upload slot in account" << acc->name << "for file" << path << "but the file doesn't exist or is not readable";
|
||||
}
|
||||
} else {
|
||||
onFileUploadError(data.getId(), "Your server doesn't support file upload service, or it's prohibited for your account");
|
||||
handleUploadError(jid, id, "Your server doesn't support file upload service, or it's prohibited for your account");
|
||||
qDebug() << "Requested upload slot in account" << acc->name << "for file" << path << "but upload manager didn't discover any upload services";
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
onFileUploadError(data.getId(), "Account is offline or reconnecting");
|
||||
handleUploadError(data.getPenPalJid(), data.getId(), "Account is offline or reconnecting");
|
||||
qDebug() << "An attempt to send message with not connected account " << acc->name << ", skipping";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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<QString, Shared::Message>& 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();
|
||||
const std::pair<QString, QString>& pair = uploadingSlotsQueue.front();
|
||||
const QString& mId = pair.second;
|
||||
QString palJid = pendingStateMessages.at(mId);
|
||||
acc->network->uploadFile({acc->name, palJid, mId}, pair.first, slot.putUrl(), slot.getUrl(), slot.putHeaders());
|
||||
|
||||
uploadingSlotsQueue.pop_front();
|
||||
if (uploadingSlotsQueue.size() > 0) {
|
||||
acc->um->requestUploadSlot(uploadingSlotsQueue.front().first);
|
||||
}
|
||||
@ -328,44 +382,130 @@ void Core::MessageHandler::onUploadSlotReceived(const QXmppHttpUploadSlotIq& slo
|
||||
|
||||
void Core::MessageHandler::onUploadSlotRequestFailed(const QXmppHttpUploadRequestIq& request)
|
||||
{
|
||||
QString err(request.error().text());
|
||||
if (uploadingSlotsQueue.size() == 0) {
|
||||
qDebug() << "HTTP Upload manager of account" << acc->name << "reports about an error requesting upload slot, but none was requested";
|
||||
qDebug() << request.error().text();
|
||||
qDebug() << err;
|
||||
} else {
|
||||
const std::pair<QString, Shared::Message>& pair = uploadingSlotsQueue.front();
|
||||
qDebug() << "Error requesting upload slot for file" << pair.first << "in account" << acc->name << ":" << request.error().text();
|
||||
emit acc->uploadFileError(pair.second.getId(), "Error requesting slot to upload file: " + request.error().text());
|
||||
const std::pair<QString, QString>& 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);
|
||||
|
||||
uploadingSlotsQueue.pop_front();
|
||||
if (uploadingSlotsQueue.size() > 0) {
|
||||
acc->um->requestUploadSlot(uploadingSlotsQueue.front().first);
|
||||
}
|
||||
uploadingSlotsQueue.pop_front();
|
||||
}
|
||||
}
|
||||
|
||||
void Core::MessageHandler::onFileUploaded(const QString& messageId, const QString& url)
|
||||
void Core::MessageHandler::onDownloadFileComplete(const std::list<Shared::MessageInfo>& msgs, const QString& path)
|
||||
{
|
||||
std::map<QString, Shared::Message>::const_iterator itr = pendingMessages.find(messageId);
|
||||
if (itr != pendingMessages.end()) {
|
||||
sendMessageWithLocalUploadedFile(itr->second, url);
|
||||
pendingMessages.erase(itr);
|
||||
QMap<QString, QVariant> cData = {
|
||||
{"attachPath", path}
|
||||
};
|
||||
for (const Shared::MessageInfo& info : msgs) {
|
||||
if (info.account == acc->getName()) {
|
||||
RosterItem* cnt = acc->rh->getRosterItem(info.jid);
|
||||
if (cnt != 0) {
|
||||
if (cnt->changeMessage(info.messageId, cData)) {
|
||||
emit acc->changeMessage(info.jid, info.messageId, cData);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Core::MessageHandler::onFileUploadError(const QString& messageId, const QString& errMsg)
|
||||
void Core::MessageHandler::onLoadFileError(const std::list<Shared::MessageInfo>& msgs, const QString& text, bool up)
|
||||
{
|
||||
std::map<QString, Shared::Message>::const_iterator itr = pendingMessages.find(messageId);
|
||||
if (itr != pendingMessages.end()) {
|
||||
pendingMessages.erase(itr);
|
||||
if (up) {
|
||||
for (const Shared::MessageInfo& info : msgs) {
|
||||
if (info.account == acc->getName()) {
|
||||
handleUploadError(info.jid, info.messageId, text);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Core::MessageHandler::sendMessageWithLocalUploadedFile(Shared::Message msg, const QString& url)
|
||||
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<uint>(Shared::Message::State::error)},
|
||||
{"errorText", errorText}
|
||||
});
|
||||
}
|
||||
|
||||
void Core::MessageHandler::onUploadFileComplete(const std::list<Shared::MessageInfo>& msgs, const QString& path)
|
||||
{
|
||||
for (const Shared::MessageInfo& info : msgs) {
|
||||
if (info.account == acc->getName()) {
|
||||
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)
|
||||
{
|
||||
msg.setOutOfBandUrl(url);
|
||||
if (msg.getBody().size() == 0) {
|
||||
msg.setBody(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
|
||||
}
|
||||
sendMessage(msg);
|
||||
performSending(msg, newMessage);
|
||||
//TODO removal/progress update
|
||||
}
|
||||
|
||||
static const std::set<QString> allowerToChangeKeys({
|
||||
"attachPath",
|
||||
"outOfBandUrl",
|
||||
"state",
|
||||
"errorText"
|
||||
});
|
||||
|
||||
void Core::MessageHandler::requestChangeMessage(const QString& jid, const QString& messageId, const QMap<QString, QVariant>& data)
|
||||
{
|
||||
RosterItem* cnt = acc->rh->getRosterItem(jid);
|
||||
if (cnt != 0) {
|
||||
bool allSupported = true;
|
||||
QString unsupportedString;
|
||||
for (QMap<QString, QVariant>::const_iterator itr = data.begin(); itr != data.end(); ++itr) { //I need all this madness
|
||||
if (allowerToChangeKeys.count(itr.key()) != 1) { //to not allow this method
|
||||
allSupported = false; //to make a message to look like if it was edited
|
||||
unsupportedString = itr.key(); //basically I needed to control who exaclty calls this method
|
||||
break; //because the underlying tech assumes that the change is initiated by user
|
||||
} //not by system
|
||||
}
|
||||
if (allSupported) {
|
||||
cnt->changeMessage(messageId, data);
|
||||
emit acc->changeMessage(jid, messageId, data);
|
||||
} else {
|
||||
qDebug() << "A request to change message" << messageId << "of conversation" << jid << "with following data" << data;
|
||||
qDebug() << "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";
|
||||
}
|
||||
}
|
||||
|
@ -28,6 +28,7 @@
|
||||
#include <QXmppHttpUploadIq.h>
|
||||
|
||||
#include <shared/message.h>
|
||||
#include <shared/messageinfo.h>
|
||||
|
||||
namespace Core {
|
||||
|
||||
@ -44,9 +45,9 @@ public:
|
||||
MessageHandler(Account* account);
|
||||
|
||||
public:
|
||||
void sendMessage(Shared::Message data);
|
||||
void sendMessage(const Shared::Message& data, const QString& path);
|
||||
void sendMessage(const Shared::Message& data, bool newMessage = true);
|
||||
void initializeMessage(Shared::Message& target, const QXmppMessage& source, bool outgoing = false, bool forwarded = false, bool guessing = false) const;
|
||||
void resendMessage(const QString& jid, const QString& id);
|
||||
|
||||
public slots:
|
||||
void onMessageReceived(const QXmppMessage& message);
|
||||
@ -55,20 +56,24 @@ public slots:
|
||||
void onReceiptReceived(const QString& jid, const QString& id);
|
||||
void onUploadSlotReceived(const QXmppHttpUploadSlotIq& slot);
|
||||
void onUploadSlotRequestFailed(const QXmppHttpUploadRequestIq& request);
|
||||
void onFileUploaded(const QString& messageId, const QString& url);
|
||||
void onFileUploadError(const QString& messageId, const QString& errMsg);
|
||||
void onDownloadFileComplete(const std::list<Shared::MessageInfo>& msgs, const QString& path);
|
||||
void onUploadFileComplete(const std::list<Shared::MessageInfo>& msgs, const QString& path);
|
||||
void onLoadFileError(const std::list<Shared::MessageInfo>& msgs, const QString& path, bool up);
|
||||
void requestChangeMessage(const QString& jid, const QString& messageId, const QMap<QString, QVariant>& data);
|
||||
|
||||
private:
|
||||
bool handleChatMessage(const QXmppMessage& msg, bool outgoing = false, bool forwarded = false, bool guessing = false);
|
||||
bool handleGroupMessage(const QXmppMessage& msg, bool outgoing = false, bool forwarded = false, bool guessing = false);
|
||||
void logMessage(const QXmppMessage& msg, const QString& reason = "Message wasn't handled: ");
|
||||
void sendMessageWithLocalUploadedFile(Shared::Message msg, const QString& url);
|
||||
void 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);
|
||||
|
||||
private:
|
||||
Account* acc;
|
||||
std::map<QString, QString> pendingStateMessages;
|
||||
std::map<QString, Shared::Message> pendingMessages;
|
||||
std::deque<std::pair<QString, Shared::Message>> uploadingSlotsQueue;
|
||||
std::map<QString, QString> pendingStateMessages; //key is message id, value is JID
|
||||
std::deque<std::pair<QString, QString>> uploadingSlotsQueue;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -190,7 +190,7 @@ void Core::RosterHandler::removeContactRequest(const QString& jid)
|
||||
void Core::RosterHandler::handleNewRosterItem(Core::RosterItem* contact)
|
||||
{
|
||||
connect(contact, &RosterItem::needHistory, this->acc, &Account::onContactNeedHistory);
|
||||
connect(contact, &RosterItem::historyResponse, this, &RosterHandler::onContactHistoryResponse);
|
||||
connect(contact, &RosterItem::historyResponse, this->acc, &Account::onContactHistoryResponse);
|
||||
connect(contact, &RosterItem::nameChanged, this, &RosterHandler::onContactNameChanged);
|
||||
connect(contact, &RosterItem::avatarChanged, this, &RosterHandler::onContactAvatarChanged);
|
||||
connect(contact, &RosterItem::requestVCard, this->acc, &Account::requestVCard);
|
||||
@ -315,14 +315,6 @@ void Core::RosterHandler::removeFromGroup(const QString& jid, const QString& gro
|
||||
}
|
||||
}
|
||||
|
||||
void Core::RosterHandler::onContactHistoryResponse(const std::list<Shared::Message>& list)
|
||||
{
|
||||
RosterItem* contact = static_cast<RosterItem*>(sender());
|
||||
|
||||
qDebug() << "Collected history for contact " << contact->jid << list.size() << "elements";
|
||||
emit acc->responseArchive(contact->jid, list);
|
||||
}
|
||||
|
||||
Core::RosterItem * Core::RosterHandler::getRosterItem(const QString& jid)
|
||||
{
|
||||
RosterItem* item = 0;
|
||||
|
@ -86,7 +86,6 @@ private slots:
|
||||
void onContactGroupRemoved(const QString& group);
|
||||
void onContactNameChanged(const QString& name);
|
||||
void onContactSubscriptionStateChanged(Shared::SubscriptionState state);
|
||||
void onContactHistoryResponse(const std::list<Shared::Message>& list);
|
||||
void onContactAvatarChanged(Shared::Avatar, const QString& path);
|
||||
|
||||
private:
|
||||
|
@ -16,23 +16,26 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "ui/squawk.h"
|
||||
#include "core/squawk.h"
|
||||
#include "../shared/global.h"
|
||||
#include "../shared/messageinfo.h"
|
||||
#include "../ui/squawk.h"
|
||||
#include "signalcatcher.h"
|
||||
#include "shared/global.h"
|
||||
#include <QtWidgets/QApplication>
|
||||
#include <QtCore/QThread>
|
||||
#include <QtCore/QObject>
|
||||
#include <QSettings>
|
||||
#include <QTranslator>
|
||||
#include "squawk.h"
|
||||
#include <QLibraryInfo>
|
||||
#include <QSettings>
|
||||
#include <QStandardPaths>
|
||||
#include <QTranslator>
|
||||
#include <QtCore/QObject>
|
||||
#include <QtCore/QThread>
|
||||
#include <QtWidgets/QApplication>
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
qRegisterMetaType<Shared::Message>("Shared::Message");
|
||||
qRegisterMetaType<Shared::MessageInfo>("Shared::MessageInfo");
|
||||
qRegisterMetaType<Shared::VCard>("Shared::VCard");
|
||||
qRegisterMetaType<std::list<Shared::Message>>("std::list<Shared::Message>");
|
||||
qRegisterMetaType<std::list<Shared::MessageInfo>>("std::list<Shared::MessageInfo>");
|
||||
qRegisterMetaType<QSet<QString>>("QSet<QString>");
|
||||
qRegisterMetaType<Shared::ConnectionState>("Shared::ConnectionState");
|
||||
qRegisterMetaType<Shared::Availability>("Shared::Availability");
|
||||
@ -96,10 +99,8 @@ int main(int argc, char *argv[])
|
||||
QObject::connect(&w, &Squawk::connectAccount, squawk, &Core::Squawk::connectAccount);
|
||||
QObject::connect(&w, &Squawk::disconnectAccount, squawk, &Core::Squawk::disconnectAccount);
|
||||
QObject::connect(&w, &Squawk::changeState, squawk, &Core::Squawk::changeState);
|
||||
QObject::connect(&w, qOverload<const QString&, const Shared::Message&>(&Squawk::sendMessage),
|
||||
squawk, qOverload<const QString&, const Shared::Message&>(&Core::Squawk::sendMessage));
|
||||
QObject::connect(&w, qOverload<const QString&, const Shared::Message&, const QString&>(&Squawk::sendMessage),
|
||||
squawk, qOverload<const QString&, const Shared::Message&, const QString&>(&Core::Squawk::sendMessage));
|
||||
QObject::connect(&w, &Squawk::sendMessage, squawk,&Core::Squawk::sendMessage);
|
||||
QObject::connect(&w, &Squawk::resendMessage, squawk,&Core::Squawk::resendMessage);
|
||||
QObject::connect(&w, &Squawk::requestArchive, squawk, &Core::Squawk::requestArchive);
|
||||
QObject::connect(&w, &Squawk::subscribeContact, squawk, &Core::Squawk::subscribeContact);
|
||||
QObject::connect(&w, &Squawk::unsubscribeContact, squawk, &Core::Squawk::unsubscribeContact);
|
||||
@ -109,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::fileLocalPathRequest, squawk, &Core::Squawk::fileLocalPathRequest);
|
||||
QObject::connect(&w, &Squawk::downloadFileRequest, squawk, &Core::Squawk::downloadFileRequest);
|
||||
QObject::connect(&w, &Squawk::fileDownloadRequest, squawk, &Core::Squawk::fileDownloadRequest);
|
||||
QObject::connect(&w, &Squawk::addContactToGroupRequest, squawk, &Core::Squawk::addContactToGroupRequest);
|
||||
QObject::connect(&w, &Squawk::removeContactFromGroupRequest, squawk, &Core::Squawk::removeContactFromGroupRequest);
|
||||
QObject::connect(&w, &Squawk::renameContactRequest, squawk, &Core::Squawk::renameContactRequest);
|
||||
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);
|
||||
@ -141,11 +142,10 @@ int main(int argc, char *argv[])
|
||||
QObject::connect(squawk, &Core::Squawk::addRoomParticipant, &w, &Squawk::addRoomParticipant);
|
||||
QObject::connect(squawk, &Core::Squawk::changeRoomParticipant, &w, &Squawk::changeRoomParticipant);
|
||||
QObject::connect(squawk, &Core::Squawk::removeRoomParticipant, &w, &Squawk::removeRoomParticipant);
|
||||
QObject::connect(squawk, &Core::Squawk::fileLocalPathResponse, &w, &Squawk::fileLocalPathResponse);
|
||||
QObject::connect(squawk, &Core::Squawk::downloadFileProgress, &w, &Squawk::fileProgress);
|
||||
QObject::connect(squawk, &Core::Squawk::downloadFileError, &w, &Squawk::fileError);
|
||||
QObject::connect(squawk, &Core::Squawk::uploadFileProgress, &w, &Squawk::fileProgress);
|
||||
QObject::connect(squawk, &Core::Squawk::uploadFileError, &w, &Squawk::fileError);
|
||||
QObject::connect(squawk, &Core::Squawk::fileDownloadComplete, &w, &Squawk::fileDownloadComplete);
|
||||
QObject::connect(squawk, &Core::Squawk::fileUploadComplete, &w, &Squawk::fileUploadComplete);
|
||||
QObject::connect(squawk, &Core::Squawk::fileProgress, &w, &Squawk::fileProgress);
|
||||
QObject::connect(squawk, &Core::Squawk::fileError, &w, &Squawk::fileError);
|
||||
QObject::connect(squawk, &Core::Squawk::responseVCard, &w, &Squawk::responseVCard);
|
||||
QObject::connect(squawk, &Core::Squawk::requestPassword, &w, &Squawk::requestPassword);
|
||||
QObject::connect(squawk, &Core::Squawk::ready, &w, &Squawk::readSettings);
|
@ -16,13 +16,17 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#include <QtWidgets/QApplication>
|
||||
#include <QtCore/QDir>
|
||||
|
||||
#include "networkaccess.h"
|
||||
|
||||
Core::NetworkAccess::NetworkAccess(QObject* parent):
|
||||
QObject(parent),
|
||||
running(false),
|
||||
manager(0),
|
||||
files("files"),
|
||||
storage("fileURLStorage"),
|
||||
downloads(),
|
||||
uploads()
|
||||
{
|
||||
@ -33,60 +37,31 @@ Core::NetworkAccess::~NetworkAccess()
|
||||
stop();
|
||||
}
|
||||
|
||||
void Core::NetworkAccess::fileLocalPathRequest(const QString& messageId, const QString& url)
|
||||
void Core::NetworkAccess::downladFile(const QString& url)
|
||||
{
|
||||
std::map<QString, Transfer*>::iterator itr = downloads.find(url);
|
||||
if (itr != downloads.end()) {
|
||||
Transfer* dwn = itr->second;
|
||||
std::set<QString>::const_iterator mItr = dwn->messages.find(messageId);
|
||||
if (mItr == dwn->messages.end()) {
|
||||
dwn->messages.insert(messageId);
|
||||
}
|
||||
emit downloadFileProgress(messageId, dwn->progress);
|
||||
qDebug() << "NetworkAccess received a request to download a file" << url << ", but the file is currently downloading, skipping";
|
||||
} else {
|
||||
try {
|
||||
QString path = files.getRecord(url);
|
||||
QFileInfo info(path);
|
||||
std::pair<QString, std::list<Shared::MessageInfo>> p = storage.getPath(url);
|
||||
if (p.first.size() > 0) {
|
||||
QFileInfo info(p.first);
|
||||
if (info.exists() && info.isFile()) {
|
||||
emit fileLocalPathResponse(messageId, path);
|
||||
emit downloadFileComplete(p.second, p.first);
|
||||
} else {
|
||||
files.removeRecord(url);
|
||||
emit fileLocalPathResponse(messageId, "");
|
||||
startDownload(p.second, url);
|
||||
}
|
||||
} else {
|
||||
startDownload(p.second, url);
|
||||
}
|
||||
} catch (const Archive::NotFound& e) {
|
||||
emit fileLocalPathResponse(messageId, "");
|
||||
qDebug() << "NetworkAccess received a request to download a file" << url << ", but there is now record of which message uses that file, downloading anyway";
|
||||
storage.addFile(url);
|
||||
startDownload(std::list<Shared::MessageInfo>(), url);
|
||||
} catch (const Archive::Unknown& e) {
|
||||
qDebug() << "Error requesting file path:" << e.what();
|
||||
emit fileLocalPathResponse(messageId, "");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Core::NetworkAccess::downladFileRequest(const QString& messageId, const QString& url)
|
||||
{
|
||||
std::map<QString, Transfer*>::iterator itr = downloads.find(url);
|
||||
if (itr != downloads.end()) {
|
||||
Transfer* dwn = itr->second;
|
||||
std::set<QString>::const_iterator mItr = dwn->messages.find(messageId);
|
||||
if (mItr == dwn->messages.end()) {
|
||||
dwn->messages.insert(messageId);
|
||||
}
|
||||
emit downloadFileProgress(messageId, dwn->progress);
|
||||
} else {
|
||||
try {
|
||||
QString path = files.getRecord(url);
|
||||
QFileInfo info(path);
|
||||
if (info.exists() && info.isFile()) {
|
||||
emit fileLocalPathResponse(messageId, path);
|
||||
} else {
|
||||
files.removeRecord(url);
|
||||
startDownload(messageId, url);
|
||||
}
|
||||
} catch (const Archive::NotFound& e) {
|
||||
startDownload(messageId, url);
|
||||
} catch (const Archive::Unknown& e) {
|
||||
qDebug() << "Error requesting file path:" << e.what();
|
||||
emit downloadFileError(messageId, QString("Database error: ") + e.what());
|
||||
emit loadFileError(std::list<Shared::MessageInfo>(), QString("Database error: ") + e.what(), false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -95,7 +70,10 @@ void Core::NetworkAccess::start()
|
||||
{
|
||||
if (!running) {
|
||||
manager = new QNetworkAccessManager();
|
||||
files.open();
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
|
||||
manager->setTransferTimeout();
|
||||
#endif
|
||||
storage.open();
|
||||
running = true;
|
||||
}
|
||||
}
|
||||
@ -103,7 +81,7 @@ void Core::NetworkAccess::start()
|
||||
void Core::NetworkAccess::stop()
|
||||
{
|
||||
if (running) {
|
||||
files.close();
|
||||
storage.close();
|
||||
manager->deleteLater();
|
||||
manager = 0;
|
||||
running = false;
|
||||
@ -124,35 +102,56 @@ void Core::NetworkAccess::onDownloadProgress(qint64 bytesReceived, qint64 bytesT
|
||||
qDebug() << "an error downloading" << url << ": the request had some progress but seems like no one is waiting for it, skipping";
|
||||
} else {
|
||||
Transfer* dwn = itr->second;
|
||||
if (dwn->success) {
|
||||
qreal received = bytesReceived;
|
||||
qreal total = bytesTotal;
|
||||
qreal progress = received/total;
|
||||
dwn->progress = progress;
|
||||
for (std::set<QString>::const_iterator mItr = dwn->messages.begin(), end = dwn->messages.end(); mItr != end; ++mItr) {
|
||||
emit downloadFileProgress(*mItr, progress);
|
||||
emit loadFileProgress(dwn->messages, progress, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Core::NetworkAccess::onDownloadError(QNetworkReply::NetworkError code)
|
||||
{
|
||||
qDebug() << "DEBUG: DOWNLOAD ERROR";
|
||||
QNetworkReply* rpl = static_cast<QNetworkReply*>(sender());
|
||||
qDebug() << rpl->errorString();
|
||||
QString url = rpl->url().toString();
|
||||
std::map<QString, Transfer*>::const_iterator itr = downloads.find(url);
|
||||
if (itr == downloads.end()) {
|
||||
qDebug() << "an error downloading" << url << ": the request is reporting an error but seems like no one is waiting for it, skipping";
|
||||
} else {
|
||||
QString errorText = getErrorText(code);
|
||||
if (errorText.size() > 0) {
|
||||
//if (errorText.size() > 0) {
|
||||
itr->second->success = false;
|
||||
Transfer* dwn = itr->second;
|
||||
for (std::set<QString>::const_iterator mItr = dwn->messages.begin(), end = dwn->messages.end(); mItr != end; ++mItr) {
|
||||
emit downloadFileError(*mItr, errorText);
|
||||
emit loadFileError(dwn->messages, errorText, false);
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
void Core::NetworkAccess::onDownloadSSLError(const QList<QSslError>& errors)
|
||||
{
|
||||
qDebug() << "DEBUG: DOWNLOAD SSL ERRORS";
|
||||
for (const QSslError& err : errors) {
|
||||
qDebug() << err.errorString();
|
||||
}
|
||||
QNetworkReply* rpl = static_cast<QNetworkReply*>(sender());
|
||||
QString url = rpl->url().toString();
|
||||
std::map<QString, Transfer*>::const_iterator itr = downloads.find(url);
|
||||
if (itr == downloads.end()) {
|
||||
qDebug() << "an SSL error downloading" << url << ": the request is reporting an error but seems like no one is waiting for it, skipping";
|
||||
} else {
|
||||
//if (errorText.size() > 0) {
|
||||
itr->second->success = false;
|
||||
Transfer* dwn = itr->second;
|
||||
emit loadFileError(dwn->messages, "SSL errors occured", false);
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
QString Core::NetworkAccess::getErrorText(QNetworkReply::NetworkError code)
|
||||
{
|
||||
QString errorText("");
|
||||
@ -175,7 +174,11 @@ QString Core::NetworkAccess::getErrorText(QNetworkReply::NetworkError code)
|
||||
errorText = "Connection was closed because it timed out";
|
||||
break;
|
||||
case QNetworkReply::OperationCanceledError:
|
||||
//this means I closed it myself by abort() or close(), don't think I need to notify here
|
||||
//this means I closed it myself by abort() or close()
|
||||
//I don't call them directory, but this is the error code
|
||||
//Qt returns when it can not resume donwload after the network failure
|
||||
//or when the download is canceled by the timout;
|
||||
errorText = "Connection lost";
|
||||
break;
|
||||
case QNetworkReply::SslHandshakeFailedError:
|
||||
errorText = "Security error"; //TODO need to handle sslErrors signal to get a better description here
|
||||
@ -276,50 +279,48 @@ QString Core::NetworkAccess::getErrorText(QNetworkReply::NetworkError code)
|
||||
|
||||
void Core::NetworkAccess::onDownloadFinished()
|
||||
{
|
||||
QString path("");
|
||||
qDebug() << "DEBUG: DOWNLOAD FINISHED";
|
||||
QNetworkReply* rpl = static_cast<QNetworkReply*>(sender());
|
||||
QString url = rpl->url().toString();
|
||||
std::map<QString, Transfer*>::const_iterator itr = downloads.find(url);
|
||||
if (itr == downloads.end()) {
|
||||
qDebug() << "an error downloading" << url << ": the request is done but seems like noone is waiting for it, skipping";
|
||||
qDebug() << "an error downloading" << url << ": the request is done but there is no record of it being downloaded, ignoring";
|
||||
} else {
|
||||
Transfer* dwn = itr->second;
|
||||
if (dwn->success) {
|
||||
qDebug() << "download success for" << url;
|
||||
QString err;
|
||||
QStringList hops = url.split("/");
|
||||
QString fileName = hops.back();
|
||||
QStringList parts = fileName.split(".");
|
||||
path = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation) + "/";
|
||||
QString suffix("");
|
||||
QStringList::const_iterator sItr = parts.begin();
|
||||
QString realName = *sItr;
|
||||
++sItr;
|
||||
for (QStringList::const_iterator sEnd = parts.end(); sItr != sEnd; ++sItr) {
|
||||
suffix += "." + (*sItr);
|
||||
}
|
||||
QString postfix("");
|
||||
QFileInfo proposedName(path + realName + postfix + suffix);
|
||||
int counter = 0;
|
||||
while (proposedName.exists()) {
|
||||
postfix = QString("(") + std::to_string(++counter).c_str() + ")";
|
||||
proposedName = QFileInfo(path + realName + postfix + suffix);
|
||||
QString jid;
|
||||
if (dwn->messages.size() > 0) {
|
||||
jid = dwn->messages.front().jid;
|
||||
} else {
|
||||
qDebug() << "An attempt to save the file but it doesn't seem to belong to any message, download is definately going to be broken";
|
||||
}
|
||||
QString path = prepareDirectory(jid);
|
||||
if (path.size() > 0) {
|
||||
path = checkFileName(fileName, path);
|
||||
|
||||
path = proposedName.absoluteFilePath();
|
||||
QFile file(path);
|
||||
if (file.open(QIODevice::WriteOnly)) {
|
||||
file.write(dwn->reply->readAll());
|
||||
file.close();
|
||||
files.addRecord(url, path);
|
||||
storage.setPath(url, path);
|
||||
qDebug() << "file" << path << "was successfully downloaded";
|
||||
} else {
|
||||
qDebug() << "couldn't save file" << path;
|
||||
path = "";
|
||||
err = "Error opening file to write:" + file.errorString();
|
||||
}
|
||||
} else {
|
||||
err = "Couldn't prepare a directory for file";
|
||||
}
|
||||
|
||||
for (std::set<QString>::const_iterator mItr = dwn->messages.begin(), end = dwn->messages.end(); mItr != end; ++mItr) {
|
||||
emit fileLocalPathResponse(*mItr, path);
|
||||
if (path.size() > 0) {
|
||||
emit downloadFileComplete(dwn->messages, path);
|
||||
} else {
|
||||
emit loadFileError(dwn->messages, "Error saving file " + url + "; " + err, false);
|
||||
}
|
||||
}
|
||||
|
||||
dwn->reply->deleteLater();
|
||||
@ -328,12 +329,13 @@ void Core::NetworkAccess::onDownloadFinished()
|
||||
}
|
||||
}
|
||||
|
||||
void Core::NetworkAccess::startDownload(const QString& messageId, const QString& url)
|
||||
void Core::NetworkAccess::startDownload(const std::list<Shared::MessageInfo>& msgs, const QString& url)
|
||||
{
|
||||
Transfer* dwn = new Transfer({{messageId}, 0, 0, true, "", url, 0});
|
||||
Transfer* dwn = new Transfer({msgs, 0, 0, true, "", url, 0});
|
||||
QNetworkRequest req(url);
|
||||
dwn->reply = manager->get(req);
|
||||
connect(dwn->reply, &QNetworkReply::downloadProgress, this, &NetworkAccess::onDownloadProgress);
|
||||
connect(dwn->reply, &QNetworkReply::sslErrors, this, &NetworkAccess::onDownloadSSLError);
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
|
||||
connect(dwn->reply, qOverload<QNetworkReply::NetworkError>(&QNetworkReply::errorOccurred), this, &NetworkAccess::onDownloadError);
|
||||
#else
|
||||
@ -341,7 +343,7 @@ void Core::NetworkAccess::startDownload(const QString& messageId, const QString&
|
||||
#endif
|
||||
connect(dwn->reply, &QNetworkReply::finished, this, &NetworkAccess::onDownloadFinished);
|
||||
downloads.insert(std::make_pair(url, dwn));
|
||||
emit downloadFileProgress(messageId, 0);
|
||||
emit loadFileProgress(dwn->messages, 0, false);
|
||||
}
|
||||
|
||||
void Core::NetworkAccess::onUploadError(QNetworkReply::NetworkError code)
|
||||
@ -350,16 +352,16 @@ void Core::NetworkAccess::onUploadError(QNetworkReply::NetworkError code)
|
||||
QString url = rpl->url().toString();
|
||||
std::map<QString, Transfer*>::const_iterator itr = uploads.find(url);
|
||||
if (itr == uploads.end()) {
|
||||
qDebug() << "an error uploading" << url << ": the request is reporting an error but seems like noone is waiting for it, skipping";
|
||||
qDebug() << "an error uploading" << url << ": the request is reporting an error but there is no record of it being uploading, ignoring";
|
||||
} else {
|
||||
QString errorText = getErrorText(code);
|
||||
if (errorText.size() > 0) {
|
||||
//if (errorText.size() > 0) {
|
||||
itr->second->success = false;
|
||||
Transfer* upl = itr->second;
|
||||
for (std::set<QString>::const_iterator mItr = upl->messages.begin(), end = upl->messages.end(); mItr != end; ++mItr) {
|
||||
emit uploadFileError(*mItr, errorText);
|
||||
}
|
||||
}
|
||||
emit loadFileError(upl->messages, errorText, true);
|
||||
//}
|
||||
|
||||
//TODO deletion?
|
||||
}
|
||||
}
|
||||
|
||||
@ -369,17 +371,14 @@ void Core::NetworkAccess::onUploadFinished()
|
||||
QString url = rpl->url().toString();
|
||||
std::map<QString, Transfer*>::const_iterator itr = uploads.find(url);
|
||||
if (itr == downloads.end()) {
|
||||
qDebug() << "an error uploading" << url << ": the request is done but seems like no one is waiting for it, skipping";
|
||||
qDebug() << "an error uploading" << url << ": the request is done there is no record of it being uploading, ignoring";
|
||||
} else {
|
||||
Transfer* upl = itr->second;
|
||||
if (upl->success) {
|
||||
qDebug() << "upload success for" << url;
|
||||
files.addRecord(upl->url, upl->path);
|
||||
|
||||
for (std::set<QString>::const_iterator mItr = upl->messages.begin(), end = upl->messages.end(); mItr != end; ++mItr) {
|
||||
emit fileLocalPathResponse(*mItr, upl->path);
|
||||
emit uploadFileComplete(*mItr, upl->url);
|
||||
}
|
||||
storage.addFile(upl->messages, upl->url, upl->path);
|
||||
emit uploadFileComplete(upl->messages, upl->url);
|
||||
}
|
||||
|
||||
upl->reply->deleteLater();
|
||||
@ -399,98 +398,35 @@ 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;
|
||||
for (std::set<QString>::const_iterator mItr = upl->messages.begin(), end = upl->messages.end(); mItr != end; ++mItr) {
|
||||
emit uploadFileProgress(*mItr, progress);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Core::NetworkAccess::startUpload(const QString& messageId, const QString& url, const QString& path)
|
||||
{
|
||||
Transfer* upl = new Transfer({{messageId}, 0, 0, true, path, url, 0});
|
||||
QNetworkRequest req(url);
|
||||
QFile* file = new QFile(path);
|
||||
if (file->open(QIODevice::ReadOnly)) {
|
||||
upl->reply = manager->put(req, file);
|
||||
|
||||
connect(upl->reply, &QNetworkReply::uploadProgress, this, &NetworkAccess::onUploadProgress);
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
|
||||
connect(upl->reply, qOverload<QNetworkReply::NetworkError>(&QNetworkReply::errorOccurred), this, &NetworkAccess::onUploadError);
|
||||
#else
|
||||
connect(upl->reply, qOverload<QNetworkReply::NetworkError>(&QNetworkReply::error), this, &NetworkAccess::onUploadError);
|
||||
#endif
|
||||
|
||||
connect(upl->reply, &QNetworkReply::finished, this, &NetworkAccess::onUploadFinished);
|
||||
uploads.insert(std::make_pair(url, upl));
|
||||
emit downloadFileProgress(messageId, 0);
|
||||
} else {
|
||||
qDebug() << "couldn't upload file" << path;
|
||||
emit uploadFileError(messageId, "Error opening file");
|
||||
delete file;
|
||||
}
|
||||
}
|
||||
|
||||
void Core::NetworkAccess::uploadFileRequest(const QString& messageId, const QString& url, const QString& path)
|
||||
{
|
||||
std::map<QString, Transfer*>::iterator itr = uploads.find(url);
|
||||
if (itr != uploads.end()) {
|
||||
Transfer* upl = itr->second;
|
||||
std::set<QString>::const_iterator mItr = upl->messages.find(messageId);
|
||||
if (mItr == upl->messages.end()) {
|
||||
upl->messages.insert(messageId);
|
||||
}
|
||||
emit uploadFileProgress(messageId, upl->progress);
|
||||
} else {
|
||||
try {
|
||||
QString ePath = files.getRecord(url);
|
||||
if (ePath == path) {
|
||||
QFileInfo info(path);
|
||||
if (info.exists() && info.isFile()) {
|
||||
emit fileLocalPathResponse(messageId, path);
|
||||
} else {
|
||||
files.removeRecord(url);
|
||||
startUpload(messageId, url, path);
|
||||
}
|
||||
} else {
|
||||
QFileInfo info(path);
|
||||
if (info.exists() && info.isFile()) {
|
||||
files.changeRecord(url, path);
|
||||
emit fileLocalPathResponse(messageId, path);
|
||||
} else {
|
||||
files.removeRecord(url);
|
||||
startUpload(messageId, url, path);
|
||||
}
|
||||
}
|
||||
} catch (const Archive::NotFound& e) {
|
||||
startUpload(messageId, url, path);
|
||||
} catch (const Archive::Unknown& e) {
|
||||
qDebug() << "Error requesting file path on upload:" << e.what();
|
||||
emit uploadFileError(messageId, QString("Database error: ") + e.what());
|
||||
emit loadFileProgress(upl->messages, progress, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QString Core::NetworkAccess::getFileRemoteUrl(const QString& path)
|
||||
{
|
||||
return ""; //TODO this is a way not to upload some file more then 1 time, here I'm supposed to return that file GET url
|
||||
QString p;
|
||||
|
||||
try {
|
||||
p = storage.getUrl(path);
|
||||
} catch (const Archive::NotFound& err) {
|
||||
|
||||
} catch (...) {
|
||||
throw;
|
||||
}
|
||||
|
||||
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
|
||||
return p;
|
||||
}
|
||||
|
||||
void Core::NetworkAccess::uploadFile(const QString& messageId, const QString& path, const QUrl& put, const QUrl& get, const QMap<QString, QString> headers)
|
||||
void Core::NetworkAccess::uploadFile(const Shared::MessageInfo& info, const QString& path, const QUrl& put, const QUrl& get, const QMap<QString, QString> headers)
|
||||
{
|
||||
QFile* file = new QFile(path);
|
||||
Transfer* upl = new Transfer({{messageId}, 0, 0, true, path, get.toString(), file});
|
||||
Transfer* upl = new Transfer({{info}, 0, 0, true, path, get.toString(), file});
|
||||
QNetworkRequest req(put);
|
||||
for (QMap<QString, QString>::const_iterator itr = headers.begin(), end = headers.end(); itr != end; itr++) {
|
||||
req.setRawHeader(itr.key().toUtf8(), itr.value().toUtf8());
|
||||
@ -506,10 +442,99 @@ void Core::NetworkAccess::uploadFile(const QString& messageId, const QString& pa
|
||||
#endif
|
||||
connect(upl->reply, &QNetworkReply::finished, this, &NetworkAccess::onUploadFinished);
|
||||
uploads.insert(std::make_pair(put.toString(), upl));
|
||||
emit downloadFileProgress(messageId, 0);
|
||||
emit loadFileProgress(upl->messages, 0, true);
|
||||
} else {
|
||||
qDebug() << "couldn't upload file" << path;
|
||||
emit uploadFileError(messageId, "Error opening file");
|
||||
emit loadFileError(upl->messages, "Error opening file", true);
|
||||
delete file;
|
||||
delete upl;
|
||||
}
|
||||
}
|
||||
|
||||
void Core::NetworkAccess::registerFile(const QString& url, const QString& account, const QString& jid, const QString& id)
|
||||
{
|
||||
storage.addFile(url, account, jid, id);
|
||||
std::map<QString, Transfer*>::iterator itr = downloads.find(url);
|
||||
if (itr != downloads.end()) {
|
||||
itr->second->messages.emplace_back(account, jid, id); //TODO notification is going to happen the next tick, is that okay?
|
||||
}
|
||||
}
|
||||
|
||||
void Core::NetworkAccess::registerFile(const QString& url, const QString& path, const QString& account, const QString& jid, const QString& id)
|
||||
{
|
||||
storage.addFile(url, path, account, jid, id);
|
||||
}
|
||||
|
||||
bool Core::NetworkAccess::checkAndAddToUploading(const QString& acc, const QString& jid, const QString id, const QString path)
|
||||
{
|
||||
for (const std::pair<const QString, Transfer*>& pair : uploads) {
|
||||
Transfer* info = pair.second;
|
||||
if (pair.second->path == path) {
|
||||
std::list<Shared::MessageInfo>& messages = info->messages;
|
||||
bool dup = false;
|
||||
for (const Shared::MessageInfo& info : messages) {
|
||||
if (info.account == acc && info.jid == jid && info.messageId == id) {
|
||||
dup = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!dup) {
|
||||
info->messages.emplace_back(acc, jid, id); //TODO notification is going to happen the next tick, is that okay?
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
QString Core::NetworkAccess::prepareDirectory(const QString& jid)
|
||||
{
|
||||
QString path = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation);
|
||||
path += "/" + QApplication::applicationName();
|
||||
if (jid.size() > 0) {
|
||||
path += "/" + jid;
|
||||
}
|
||||
QDir location(path);
|
||||
|
||||
if (!location.exists()) {
|
||||
bool res = location.mkpath(path);
|
||||
if (!res) {
|
||||
return "";
|
||||
} else {
|
||||
return path;
|
||||
}
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
QString Core::NetworkAccess::checkFileName(const QString& name, const QString& path)
|
||||
{
|
||||
QStringList parts = name.split(".");
|
||||
QString suffix("");
|
||||
QStringList::const_iterator sItr = parts.begin();
|
||||
QString realName = *sItr;
|
||||
++sItr;
|
||||
for (QStringList::const_iterator sEnd = parts.end(); sItr != sEnd; ++sItr) {
|
||||
suffix += "." + (*sItr);
|
||||
}
|
||||
QString postfix("");
|
||||
QFileInfo proposedName(path + "/" + realName + suffix);
|
||||
int counter = 0;
|
||||
while (proposedName.exists()) {
|
||||
QString count = QString("(") + std::to_string(++counter).c_str() + ")";
|
||||
proposedName = QFileInfo(path + "/" + realName + count + suffix);
|
||||
}
|
||||
|
||||
return proposedName.absoluteFilePath();
|
||||
}
|
||||
|
||||
QString Core::NetworkAccess::addMessageAndCheckForPath(const QString& url, const QString& account, const QString& jid, const QString& id)
|
||||
{
|
||||
return storage.addMessageAndCheckForPath(url, account, jid, id);
|
||||
}
|
||||
|
||||
std::list<Shared::MessageInfo> Core::NetworkAccess::reportPathInvalid(const QString& path)
|
||||
{
|
||||
return storage.deletedFile(path);
|
||||
}
|
||||
|
@ -29,13 +29,15 @@
|
||||
|
||||
#include <set>
|
||||
|
||||
#include "storage.h"
|
||||
#include "urlstorage.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
|
||||
@ -48,30 +50,32 @@ public:
|
||||
void stop();
|
||||
|
||||
QString getFileRemoteUrl(const QString& path);
|
||||
bool isUploading(const QString& path, const QString& messageId = "");
|
||||
void uploadFile(const QString& messageId, const QString& path, const QUrl& put, const QUrl& get, const QMap<QString, QString> headers);
|
||||
QString addMessageAndCheckForPath(const QString& url, const QString& account, const QString& jid, const QString& id);
|
||||
void uploadFile(const Shared::MessageInfo& info, const QString& path, const QUrl& put, const QUrl& get, const QMap<QString, QString> headers);
|
||||
bool checkAndAddToUploading(const QString& acc, const QString& jid, const QString id, const QString path);
|
||||
std::list<Shared::MessageInfo> reportPathInvalid(const QString& path);
|
||||
|
||||
signals:
|
||||
void fileLocalPathResponse(const QString& messageId, const QString& path);
|
||||
void downloadFileProgress(const QString& messageId, qreal value);
|
||||
void downloadFileError(const QString& messageId, const QString& path);
|
||||
void uploadFileProgress(const QString& messageId, qreal value);
|
||||
void uploadFileError(const QString& messageId, const QString& path);
|
||||
void uploadFileComplete(const QString& messageId, const QString& url);
|
||||
void loadFileProgress(const std::list<Shared::MessageInfo>& msgs, qreal value, bool up);
|
||||
void loadFileError(const std::list<Shared::MessageInfo>& msgs, const QString& text, bool up);
|
||||
void uploadFileComplete(const std::list<Shared::MessageInfo>& msgs, const QString& url);
|
||||
void downloadFileComplete(const std::list<Shared::MessageInfo>& msgs, const QString& path);
|
||||
|
||||
public slots:
|
||||
void fileLocalPathRequest(const QString& messageId, const QString& url);
|
||||
void downladFileRequest(const QString& messageId, const QString& url);
|
||||
void uploadFileRequest(const QString& messageId, const QString& url, const QString& path);
|
||||
void downladFile(const QString& url);
|
||||
void registerFile(const QString& url, const QString& account, const QString& jid, const QString& id);
|
||||
void registerFile(const QString& url, const QString& path, const QString& account, const QString& jid, const QString& id);
|
||||
|
||||
private:
|
||||
void startDownload(const QString& messageId, const QString& url);
|
||||
void startUpload(const QString& messageId, const QString& url, const QString& path);
|
||||
void startDownload(const std::list<Shared::MessageInfo>& msgs, const QString& url);
|
||||
QString getErrorText(QNetworkReply::NetworkError code);
|
||||
QString prepareDirectory(const QString& jid);
|
||||
QString checkFileName(const QString& name, const QString& path);
|
||||
|
||||
private slots:
|
||||
void onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal);
|
||||
void onDownloadError(QNetworkReply::NetworkError code);
|
||||
void onDownloadSSLError(const QList<QSslError> &errors);
|
||||
void onDownloadFinished();
|
||||
void onUploadProgress(qint64 bytesReceived, qint64 bytesTotal);
|
||||
void onUploadError(QNetworkReply::NetworkError code);
|
||||
@ -80,12 +84,12 @@ private slots:
|
||||
private:
|
||||
bool running;
|
||||
QNetworkAccessManager* manager;
|
||||
Storage files;
|
||||
UrlStorage storage;
|
||||
std::map<QString, Transfer*> downloads;
|
||||
std::map<QString, Transfer*> uploads;
|
||||
|
||||
struct Transfer {
|
||||
std::set<QString> messages;
|
||||
std::list<Shared::MessageInfo> messages;
|
||||
qreal progress;
|
||||
QNetworkReply* reply;
|
||||
bool success;
|
||||
|
@ -1,37 +1,9 @@
|
||||
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
|
||||
target_sources(squawk PRIVATE
|
||||
kwallet.cpp
|
||||
kwallet.h
|
||||
)
|
||||
|
||||
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_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})
|
||||
add_subdirectory(wrappers)
|
||||
target_include_directories(squawk PRIVATE $<TARGET_PROPERTY:KF5::Wallet,INTERFACE_INCLUDE_DIRECTORIES>)
|
||||
endif ()
|
||||
|
2
core/passwordStorageEngines/wrappers/CMakeLists.txt
Normal file
@ -0,0 +1,2 @@
|
||||
add_library(kwalletWrapper SHARED kwallet.cpp)
|
||||
target_link_libraries(kwalletWrapper PRIVATE KF5::Wallet)
|
@ -122,7 +122,22 @@ void Core::RosterItem::nextRequest()
|
||||
{
|
||||
if (syncronizing) {
|
||||
if (requestedCount != -1) {
|
||||
emit historyResponse(responseCache);
|
||||
bool last = false;
|
||||
if (archiveState == beginning || archiveState == complete) {
|
||||
QString firstId = archive->oldestId();
|
||||
if (responseCache.size() == 0) {
|
||||
if (requestedBefore == firstId) {
|
||||
last = true;
|
||||
}
|
||||
} else {
|
||||
if (responseCache.front().getId() == firstId) {
|
||||
last = true;
|
||||
}
|
||||
}
|
||||
} else if (archiveState == empty && responseCache.size() == 0) {
|
||||
last = true;
|
||||
}
|
||||
emit historyResponse(responseCache, last);
|
||||
}
|
||||
}
|
||||
if (requestCache.size() > 0) {
|
||||
@ -360,6 +375,11 @@ 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) {
|
||||
@ -378,7 +398,7 @@ void Core::RosterItem::flushMessagesToArchive(bool finished, const QString& firs
|
||||
} catch (const Archive::Empty& e) {
|
||||
|
||||
}
|
||||
if (!found || requestedCount > responseCache.size()) {
|
||||
if (!found || requestedCount > int(responseCache.size())) {
|
||||
if (archiveState == complete) {
|
||||
nextRequest();
|
||||
} else {
|
||||
@ -529,7 +549,7 @@ void Core::RosterItem::clearArchiveRequests()
|
||||
requestedBefore = "";
|
||||
for (const std::pair<int, QString>& pair : requestCache) {
|
||||
if (pair.first != -1) {
|
||||
emit historyResponse(responseCache); //just to notify those who still waits with whatever happened to be left in caches yet
|
||||
emit historyResponse(responseCache, false); //just to notify those who still waits with whatever happened to be left in caches yet
|
||||
}
|
||||
responseCache.clear();
|
||||
}
|
||||
@ -549,3 +569,20 @@ void Core::RosterItem::downgradeDatabaseState()
|
||||
archiveState = ArchiveState::chunk;
|
||||
}
|
||||
}
|
||||
|
||||
Shared::Message Core::RosterItem::getMessage(const QString& id)
|
||||
{
|
||||
for (const Shared::Message& msg : appendCache) {
|
||||
if (msg.getId() == id) {
|
||||
return msg;
|
||||
}
|
||||
}
|
||||
|
||||
for (Shared::Message& msg : hisoryCache) {
|
||||
if (msg.getId() == id) {
|
||||
return msg;
|
||||
}
|
||||
}
|
||||
|
||||
return archive->getElement(id);
|
||||
}
|
||||
|
@ -78,10 +78,12 @@ 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<Shared::Message>& messages);
|
||||
void historyResponse(const std::list<Shared::Message>& messages, bool last);
|
||||
void needHistory(const QString& before, const QString& after, const QDateTime& afterTime = QDateTime());
|
||||
void avatarChanged(Shared::Avatar, const QString& path);
|
||||
void requestVCard(const QString& jid);
|
||||
|
42
core/signalcatcher_win32.cpp
Normal file
@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Squawk messenger.
|
||||
* Copyright (C) 2021 Shunf4 <shun1048576@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "signalcatcher.h"
|
||||
#include <unistd.h>
|
||||
|
||||
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;
|
||||
}
|
@ -32,11 +32,10 @@ Core::Squawk::Squawk(QObject* parent):
|
||||
,kwallet()
|
||||
#endif
|
||||
{
|
||||
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);
|
||||
connect(&network, &NetworkAccess::loadFileProgress, this, &Squawk::fileProgress);
|
||||
connect(&network, &NetworkAccess::loadFileError, this, &Squawk::fileError);
|
||||
connect(&network, &NetworkAccess::downloadFileComplete, this, &Squawk::fileDownloadComplete);
|
||||
connect(&network, &NetworkAccess::uploadFileComplete, this, &Squawk::fileUploadComplete);
|
||||
|
||||
#ifdef WITH_KWALLET
|
||||
if (kwallet.supportState() == PSE::KWallet::success) {
|
||||
@ -168,7 +167,7 @@ void Core::Squawk::addAccount(
|
||||
|
||||
connect(acc, &Account::receivedVCard, this, &Squawk::responseVCard);
|
||||
|
||||
connect(acc, &Account::uploadFileError, this, &Squawk::uploadFileError);
|
||||
connect(acc, &Account::uploadFileError, this, &Squawk::onAccountUploadFileError);
|
||||
|
||||
QMap<QString, QVariant> map = {
|
||||
{"login", login},
|
||||
@ -329,22 +328,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, skipping");
|
||||
qDebug() << "An attempt to send a message with non existing account" << account << ", skipping";
|
||||
return;
|
||||
}
|
||||
|
||||
itr->second->sendMessage(data);
|
||||
}
|
||||
|
||||
void Core::Squawk::sendMessage(const QString& account, const Shared::Message& data, const QString& path)
|
||||
void Core::Squawk::resendMessage(const QString& account, const QString& jid, const QString& id)
|
||||
{
|
||||
AccountsMap::const_iterator itr = amap.find(account);
|
||||
if (itr == amap.end()) {
|
||||
qDebug("An attempt to send a message with non existing account, skipping");
|
||||
qDebug() << "An attempt to resend a message with non existing account" << account << ", skipping";
|
||||
return;
|
||||
}
|
||||
|
||||
itr->second->sendMessage(data, path);
|
||||
itr->second->resendMessage(jid, id);
|
||||
}
|
||||
|
||||
void Core::Squawk::requestArchive(const QString& account, const QString& jid, int count, const QString& before)
|
||||
@ -357,10 +356,10 @@ void Core::Squawk::requestArchive(const QString& account, const QString& jid, in
|
||||
itr->second->requestArchive(jid, count, before);
|
||||
}
|
||||
|
||||
void Core::Squawk::onAccountResponseArchive(const QString& jid, const std::list<Shared::Message>& list)
|
||||
void Core::Squawk::onAccountResponseArchive(const QString& jid, const std::list<Shared::Message>& list, bool last)
|
||||
{
|
||||
Account* acc = static_cast<Account*>(sender());
|
||||
emit responseArchive(acc->getName(), jid, list);
|
||||
emit responseArchive(acc->getName(), jid, list, last);
|
||||
}
|
||||
|
||||
void Core::Squawk::modifyAccountRequest(const QString& name, const QMap<QString, QVariant>& map)
|
||||
@ -604,14 +603,9 @@ void Core::Squawk::addRoomRequest(const QString& account, const QString& jid, co
|
||||
itr->second->addRoomRequest(jid, nick, password, autoJoin);
|
||||
}
|
||||
|
||||
void Core::Squawk::fileLocalPathRequest(const QString& messageId, const QString& url)
|
||||
void Core::Squawk::fileDownloadRequest(const QString& url)
|
||||
{
|
||||
network.fileLocalPathRequest(messageId, url);
|
||||
}
|
||||
|
||||
void Core::Squawk::downloadFileRequest(const QString& messageId, const QString& url)
|
||||
{
|
||||
network.downladFileRequest(messageId, url);
|
||||
network.downladFile(url);
|
||||
}
|
||||
|
||||
void Core::Squawk::addContactToGroupRequest(const QString& account, const QString& jid, const QString& groupName)
|
||||
@ -762,3 +756,26 @@ 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<Account*>(sender());
|
||||
emit fileError({{acc->getName(), jid, id}}, errorText, true);
|
||||
}
|
||||
|
||||
void Core::Squawk::onLocalPathInvalid(const QString& path)
|
||||
{
|
||||
std::list<Shared::MessageInfo> list = network.reportPathInvalid(path);
|
||||
|
||||
QMap<QString, QVariant> data({
|
||||
{"attachPath", ""}
|
||||
});
|
||||
for (const Shared::MessageInfo& info : list) {
|
||||
AccountsMap::const_iterator itr = amap.find(info.account);
|
||||
if (itr != amap.end()) {
|
||||
itr->second->requestChangeMessage(info.jid, info.messageId, data);
|
||||
} else {
|
||||
qDebug() << "Reacting on failure to reach file" << path << "there was an attempt to change message in account" << info.account << "which doesn't exist, skipping";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -51,31 +51,39 @@ public:
|
||||
signals:
|
||||
void quit();
|
||||
void ready();
|
||||
|
||||
void newAccount(const QMap<QString, QVariant>&);
|
||||
void changeAccount(const QString& account, const QMap<QString, QVariant>& data);
|
||||
void removeAccount(const QString& account);
|
||||
|
||||
void addGroup(const QString& account, const QString& name);
|
||||
void removeGroup(const QString& account, const QString& name);
|
||||
|
||||
void addContact(const QString& account, const QString& jid, const QString& group, const QMap<QString, QVariant>& data);
|
||||
void removeContact(const QString& account, const QString& jid);
|
||||
void removeContact(const QString& account, const QString& jid, const QString& group);
|
||||
void changeContact(const QString& account, const QString& jid, const QMap<QString, QVariant>& data);
|
||||
|
||||
void addPresence(const QString& account, const QString& jid, const QString& name, const QMap<QString, QVariant>& data);
|
||||
void removePresence(const QString& account, const QString& jid, const QString& name);
|
||||
|
||||
void stateChanged(Shared::Availability state);
|
||||
|
||||
void accountMessage(const QString& account, const Shared::Message& data);
|
||||
void responseArchive(const QString& account, const QString& jid, const std::list<Shared::Message>& list);
|
||||
void responseArchive(const QString& account, const QString& jid, const std::list<Shared::Message>& list, bool last);
|
||||
|
||||
void addRoom(const QString& account, const QString jid, const QMap<QString, QVariant>& data);
|
||||
void changeRoom(const QString& account, const QString jid, const QMap<QString, QVariant>& data);
|
||||
void removeRoom(const QString& account, const QString jid);
|
||||
void addRoomParticipant(const QString& account, const QString& jid, const QString& name, const QMap<QString, QVariant>& data);
|
||||
void changeRoomParticipant(const QString& account, const QString& jid, const QString& name, const QMap<QString, QVariant>& data);
|
||||
void removeRoomParticipant(const QString& account, const QString& jid, const QString& name);
|
||||
void 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 fileError(const std::list<Shared::MessageInfo> msgs, const QString& error, bool up);
|
||||
void fileProgress(const std::list<Shared::MessageInfo> msgs, qreal value, bool up);
|
||||
void fileDownloadComplete(const std::list<Shared::MessageInfo> msgs, const QString& path);
|
||||
void fileUploadComplete(const std::list<Shared::MessageInfo> msgs, const QString& path);
|
||||
|
||||
void responseVCard(const QString& jid, const Shared::VCard& card);
|
||||
void changeMessage(const QString& account, const QString& jid, const QString& id, const QMap<QString, QVariant>& data);
|
||||
void requestPassword(const QString& account);
|
||||
@ -83,15 +91,19 @@ signals:
|
||||
public slots:
|
||||
void start();
|
||||
void stop();
|
||||
|
||||
void newAccountRequest(const QMap<QString, QVariant>& map);
|
||||
void modifyAccountRequest(const QString& name, const QMap<QString, QVariant>& map);
|
||||
void removeAccountRequest(const QString& name);
|
||||
void connectAccount(const QString& account);
|
||||
void disconnectAccount(const QString& account);
|
||||
|
||||
void changeState(Shared::Availability state);
|
||||
|
||||
void sendMessage(const QString& account, const Shared::Message& data);
|
||||
void sendMessage(const QString& account, const Shared::Message& data, const QString& path);
|
||||
void resendMessage(const QString& account, const QString& jid, const QString& id);
|
||||
void requestArchive(const QString& account, const QString& jid, int count, const QString& before);
|
||||
|
||||
void subscribeContact(const QString& account, const QString& jid, const QString& reason);
|
||||
void unsubscribeContact(const QString& account, const QString& jid, const QString& reason);
|
||||
void addContactToGroupRequest(const QString& account, const QString& jid, const QString& groupName);
|
||||
@ -99,15 +111,18 @@ public slots:
|
||||
void removeContactRequest(const QString& account, const QString& jid);
|
||||
void renameContactRequest(const QString& account, const QString& jid, const QString& newName);
|
||||
void addContactRequest(const QString& account, const QString& jid, const QString& name, const QSet<QString>& groups);
|
||||
|
||||
void setRoomJoined(const QString& account, const QString& jid, bool joined);
|
||||
void setRoomAutoJoin(const QString& account, const QString& jid, bool joined);
|
||||
void addRoomRequest(const QString& account, const QString& jid, const QString& nick, const QString& password, bool autoJoin);
|
||||
void removeRoomRequest(const QString& account, const QString& jid);
|
||||
void fileLocalPathRequest(const QString& messageId, const QString& url);
|
||||
void downloadFileRequest(const QString& messageId, const QString& url);
|
||||
|
||||
void fileDownloadRequest(const QString& url);
|
||||
|
||||
void requestVCard(const QString& account, const QString& jid);
|
||||
void uploadVCard(const QString& account, const Shared::VCard& card);
|
||||
void responsePassword(const QString& account, const QString& password);
|
||||
void onLocalPathInvalid(const QString& path);
|
||||
|
||||
private:
|
||||
typedef std::deque<Account*> Accounts;
|
||||
@ -146,7 +161,7 @@ private slots:
|
||||
void onAccountAddPresence(const QString& jid, const QString& name, const QMap<QString, QVariant>& data);
|
||||
void onAccountRemovePresence(const QString& jid, const QString& name);
|
||||
void onAccountMessage(const Shared::Message& data);
|
||||
void onAccountResponseArchive(const QString& jid, const std::list<Shared::Message>& list);
|
||||
void onAccountResponseArchive(const QString& jid, const std::list<Shared::Message>& list, bool last);
|
||||
void onAccountAddRoom(const QString jid, const QMap<QString, QVariant>& data);
|
||||
void onAccountChangeRoom(const QString jid, const QMap<QString, QVariant>& data);
|
||||
void onAccountRemoveRoom(const QString jid);
|
||||
@ -155,6 +170,8 @@ private slots:
|
||||
void onAccountRemoveRoomPresence(const QString& jid, const QString& nick);
|
||||
void onAccountChangeMessage(const QString& jid, const QString& id, const QMap<QString, QVariant>& data);
|
||||
|
||||
void onAccountUploadFileError(const QString& jid, const QString id, const QString& errorText);
|
||||
|
||||
void onWalletOpened(bool success);
|
||||
void onWalletResponsePassword(const QString& login, const QString& password);
|
||||
void onWalletRejectPassword(const QString& login);
|
||||
|
491
core/urlstorage.cpp
Normal file
@ -0,0 +1,491 @@
|
||||
/*
|
||||
* Squawk messenger.
|
||||
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <QStandardPaths>
|
||||
#include <QDir>
|
||||
#include <QDebug>
|
||||
|
||||
#include "urlstorage.h"
|
||||
|
||||
Core::UrlStorage::UrlStorage(const QString& p_name):
|
||||
name(p_name),
|
||||
opened(false),
|
||||
environment(),
|
||||
base(),
|
||||
map()
|
||||
{
|
||||
}
|
||||
|
||||
Core::UrlStorage::~UrlStorage()
|
||||
{
|
||||
close();
|
||||
}
|
||||
|
||||
void Core::UrlStorage::open()
|
||||
{
|
||||
if (!opened) {
|
||||
mdb_env_create(&environment);
|
||||
QString path(QStandardPaths::writableLocation(QStandardPaths::CacheLocation));
|
||||
path += "/" + name;
|
||||
QDir cache(path);
|
||||
|
||||
if (!cache.exists()) {
|
||||
bool res = cache.mkpath(path);
|
||||
if (!res) {
|
||||
throw Archive::Directory(path.toStdString());
|
||||
}
|
||||
}
|
||||
|
||||
mdb_env_set_maxdbs(environment, 2);
|
||||
mdb_env_set_mapsize(environment, 10UL * 1024UL * 1024UL);
|
||||
mdb_env_open(environment, path.toStdString().c_str(), 0, 0664);
|
||||
|
||||
MDB_txn *txn;
|
||||
mdb_txn_begin(environment, NULL, 0, &txn);
|
||||
mdb_dbi_open(txn, "base", MDB_CREATE, &base);
|
||||
mdb_dbi_open(txn, "map", MDB_CREATE, &map);
|
||||
mdb_txn_commit(txn);
|
||||
opened = true;
|
||||
}
|
||||
}
|
||||
|
||||
void Core::UrlStorage::close()
|
||||
{
|
||||
if (opened) {
|
||||
mdb_dbi_close(environment, map);
|
||||
mdb_dbi_close(environment, base);
|
||||
mdb_env_close(environment);
|
||||
opened = false;
|
||||
}
|
||||
}
|
||||
|
||||
void Core::UrlStorage::writeInfo(const QString& key, const Core::UrlStorage::UrlInfo& info, bool overwrite)
|
||||
{
|
||||
MDB_txn *txn;
|
||||
mdb_txn_begin(environment, NULL, 0, &txn);
|
||||
|
||||
try {
|
||||
writeInfo(key, info, txn, overwrite);
|
||||
mdb_txn_commit(txn);
|
||||
} catch (...) {
|
||||
mdb_txn_abort(txn);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
void Core::UrlStorage::writeInfo(const QString& key, const Core::UrlStorage::UrlInfo& info, MDB_txn* txn, bool overwrite)
|
||||
{
|
||||
QByteArray ba;
|
||||
QDataStream ds(&ba, QIODevice::WriteOnly);
|
||||
info.serialize(ds);
|
||||
|
||||
const std::string& id = key.toStdString();
|
||||
MDB_val lmdbKey, lmdbData;
|
||||
lmdbKey.mv_size = id.size();
|
||||
lmdbKey.mv_data = (char*)id.c_str();
|
||||
lmdbData.mv_size = ba.size();
|
||||
lmdbData.mv_data = (uint8_t*)ba.data();
|
||||
|
||||
int rc;
|
||||
rc = mdb_put(txn, base, &lmdbKey, &lmdbData, overwrite ? 0 : MDB_NOOVERWRITE);
|
||||
|
||||
if (rc != 0) {
|
||||
if (rc == MDB_KEYEXIST) {
|
||||
if (!overwrite) {
|
||||
throw Archive::Exist(name.toStdString(), id);
|
||||
}
|
||||
} else {
|
||||
throw Archive::Unknown(name.toStdString(), mdb_strerror(rc));
|
||||
}
|
||||
}
|
||||
|
||||
if (info.hasPath()) {
|
||||
std::string sp = info.getPath().toStdString();
|
||||
lmdbData.mv_size = sp.size();
|
||||
lmdbData.mv_data = (char*)sp.c_str();
|
||||
rc = mdb_put(txn, map, &lmdbData, &lmdbKey, 0);
|
||||
if (rc != 0) {
|
||||
throw Archive::Unknown(name.toStdString(), mdb_strerror(rc));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Core::UrlStorage::readInfo(const QString& key, Core::UrlStorage::UrlInfo& info, MDB_txn* txn)
|
||||
{
|
||||
const std::string& id = key.toStdString();
|
||||
MDB_val lmdbKey, lmdbData;
|
||||
lmdbKey.mv_size = id.size();
|
||||
lmdbKey.mv_data = (char*)id.c_str();
|
||||
int rc = mdb_get(txn, base, &lmdbKey, &lmdbData);
|
||||
|
||||
if (rc == 0) {
|
||||
QByteArray ba((char*)lmdbData.mv_data, lmdbData.mv_size);
|
||||
QDataStream ds(&ba, QIODevice::ReadOnly);
|
||||
|
||||
info.deserialize(ds);
|
||||
} else if (rc == MDB_NOTFOUND) {
|
||||
throw Archive::NotFound(id, name.toStdString());
|
||||
} else {
|
||||
throw Archive::Unknown(name.toStdString(), mdb_strerror(rc));
|
||||
}
|
||||
}
|
||||
|
||||
void Core::UrlStorage::readInfo(const QString& key, Core::UrlStorage::UrlInfo& info)
|
||||
{
|
||||
MDB_txn *txn;
|
||||
mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn);
|
||||
|
||||
try {
|
||||
readInfo(key, info, txn);
|
||||
mdb_txn_commit(txn);
|
||||
} catch (...) {
|
||||
mdb_txn_abort(txn);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
void Core::UrlStorage::addFile(const QString& url)
|
||||
{
|
||||
if (!opened) {
|
||||
throw Archive::Closed("addFile(no message, no path)", name.toStdString());
|
||||
}
|
||||
|
||||
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<Shared::MessageInfo>& msgs, const QString& url, const QString& path)
|
||||
{
|
||||
if (!opened) {
|
||||
throw Archive::Closed("addFile(with list)", name.toStdString());
|
||||
}
|
||||
|
||||
UrlInfo info (path, msgs);
|
||||
writeInfo(url, info, true);;
|
||||
}
|
||||
|
||||
QString Core::UrlStorage::addMessageAndCheckForPath(const QString& url, const QString& account, const QString& jid, const QString& id)
|
||||
{
|
||||
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<Shared::MessageInfo> Core::UrlStorage::setPath(const QString& url, const QString& path)
|
||||
{
|
||||
std::list<Shared::MessageInfo> 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<Shared::MessageInfo> Core::UrlStorage::removeFile(const QString& url)
|
||||
{
|
||||
std::list<Shared::MessageInfo> list;
|
||||
|
||||
MDB_txn *txn;
|
||||
mdb_txn_begin(environment, NULL, 0, &txn);
|
||||
UrlInfo info;
|
||||
|
||||
try {
|
||||
std::string id = url.toStdString();
|
||||
readInfo(url, info, txn);
|
||||
info.getMessages(list);
|
||||
|
||||
MDB_val lmdbKey;
|
||||
lmdbKey.mv_size = id.size();
|
||||
lmdbKey.mv_data = (char*)id.c_str();
|
||||
int rc = mdb_del(txn, base, &lmdbKey, NULL);
|
||||
if (rc != 0) {
|
||||
throw Archive::Unknown(name.toStdString(), mdb_strerror(rc));
|
||||
}
|
||||
|
||||
if (info.hasPath()) {
|
||||
std::string path = info.getPath().toStdString();
|
||||
lmdbKey.mv_size = path.size();
|
||||
lmdbKey.mv_data = (char*)path.c_str();
|
||||
|
||||
int rc = mdb_del(txn, map, &lmdbKey, NULL);
|
||||
if (rc != 0) {
|
||||
throw Archive::Unknown(name.toStdString(), mdb_strerror(rc));
|
||||
}
|
||||
}
|
||||
mdb_txn_commit(txn);
|
||||
} catch (...) {
|
||||
mdb_txn_abort(txn);
|
||||
throw;
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
std::list<Shared::MessageInfo> Core::UrlStorage::deletedFile(const QString& path)
|
||||
{
|
||||
std::list<Shared::MessageInfo> list;
|
||||
|
||||
MDB_txn *txn;
|
||||
mdb_txn_begin(environment, NULL, 0, &txn);
|
||||
|
||||
try {
|
||||
std::string spath = path.toStdString();
|
||||
|
||||
MDB_val lmdbKey, lmdbData;
|
||||
lmdbKey.mv_size = spath.size();
|
||||
lmdbKey.mv_data = (char*)spath.c_str();
|
||||
|
||||
QString url;
|
||||
int rc = mdb_get(txn, map, &lmdbKey, &lmdbData);
|
||||
|
||||
if (rc == 0) {
|
||||
std::string surl((char*)lmdbData.mv_data, lmdbData.mv_size);
|
||||
url = QString(surl.c_str());
|
||||
} else if (rc == MDB_NOTFOUND) {
|
||||
qDebug() << "Have been asked to remove file" << path << ", which isn't in the database, skipping";
|
||||
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<Shared::MessageInfo> list;
|
||||
|
||||
MDB_txn *txn;
|
||||
mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn);
|
||||
|
||||
std::string spath = path.toStdString();
|
||||
|
||||
MDB_val lmdbKey, lmdbData;
|
||||
lmdbKey.mv_size = spath.size();
|
||||
lmdbKey.mv_data = (char*)spath.c_str();
|
||||
|
||||
QString url;
|
||||
int rc = mdb_get(txn, map, &lmdbKey, &lmdbData);
|
||||
|
||||
if (rc == 0) {
|
||||
std::string surl((char*)lmdbData.mv_data, lmdbData.mv_size);
|
||||
url = QString(surl.c_str());
|
||||
|
||||
mdb_txn_abort(txn);
|
||||
return url;
|
||||
} else if (rc == MDB_NOTFOUND) {
|
||||
mdb_txn_abort(txn);
|
||||
throw Archive::NotFound(spath, name.toStdString());
|
||||
} else {
|
||||
mdb_txn_abort(txn);
|
||||
throw Archive::Unknown(name.toStdString(), mdb_strerror(rc));
|
||||
}
|
||||
}
|
||||
|
||||
std::pair<QString, std::list<Shared::MessageInfo>> Core::UrlStorage::getPath(const QString& url)
|
||||
{
|
||||
UrlInfo info;
|
||||
readInfo(url, info);
|
||||
std::list<Shared::MessageInfo> container;
|
||||
info.getMessages(container);
|
||||
return std::make_pair(info.getPath(), container);
|
||||
}
|
||||
|
||||
Core::UrlStorage::UrlInfo::UrlInfo():
|
||||
localPath(),
|
||||
messages() {}
|
||||
|
||||
Core::UrlStorage::UrlInfo::UrlInfo(const QString& path):
|
||||
localPath(path),
|
||||
messages() {}
|
||||
|
||||
Core::UrlStorage::UrlInfo::UrlInfo(const QString& path, const std::list<Shared::MessageInfo>& msgs):
|
||||
localPath(path),
|
||||
messages(msgs) {}
|
||||
|
||||
Core::UrlStorage::UrlInfo::~UrlInfo() {}
|
||||
|
||||
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<Shared::MessageInfo>::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<Shared::MessageInfo>& 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;
|
||||
}
|
99
core/urlstorage.h
Normal file
@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Squawk messenger.
|
||||
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef CORE_URLSTORAGE_H
|
||||
#define CORE_URLSTORAGE_H
|
||||
|
||||
#include <QString>
|
||||
#include <QDataStream>
|
||||
#include <lmdb.h>
|
||||
#include <list>
|
||||
|
||||
#include "archive.h"
|
||||
#include <shared/messageinfo.h>
|
||||
|
||||
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<Shared::MessageInfo>& msgs, const QString& url, const QString& path); //this one overwrites all that was
|
||||
std::list<Shared::MessageInfo> removeFile(const QString& url); //removes entry like it never was in the database, returns affected message infos
|
||||
std::list<Shared::MessageInfo> deletedFile(const QString& path); //empties the localPath of the entry, returns affected message infos
|
||||
std::list<Shared::MessageInfo> setPath(const QString& url, const QString& path);
|
||||
QString getUrl(const QString& path);
|
||||
QString addMessageAndCheckForPath(const QString& url, const QString& account, const QString& jid, const QString& id);
|
||||
std::pair<QString, std::list<Shared::MessageInfo>> getPath(const QString& url);
|
||||
|
||||
private:
|
||||
QString name;
|
||||
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<Shared::MessageInfo>& 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<Shared::MessageInfo>& container) const;
|
||||
|
||||
private:
|
||||
QString localPath;
|
||||
std::list<Shared::MessageInfo> messages;
|
||||
};
|
||||
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // CORE_URLSTORAGE_H
|
12
external/simpleCrypt/CMakeLists.txt
vendored
@ -1,16 +1,10 @@
|
||||
cmake_minimum_required(VERSION 3.0)
|
||||
project(simplecrypt)
|
||||
project(simplecrypt LANGUAGES CXX)
|
||||
|
||||
set(CMAKE_AUTOMOC ON)
|
||||
|
||||
find_package(Qt5Core CONFIG REQUIRED)
|
||||
find_package(Qt5 COMPONENTS Core REQUIRED)
|
||||
|
||||
set(simplecrypt_SRC
|
||||
simplecrypt.cpp
|
||||
)
|
||||
add_library(simpleCrypt STATIC simplecrypt.cpp simplecrypt.h)
|
||||
|
||||
# Tell CMake to create the helloworld executable
|
||||
add_library(simpleCrypt ${simplecrypt_SRC})
|
||||
|
||||
# Use the Widgets module from Qt 5.
|
||||
target_link_libraries(simpleCrypt Qt5::Core)
|
||||
|
3
packaging/CMakeLists.txt
Normal file
@ -0,0 +1,3 @@
|
||||
configure_file(squawk.desktop squawk.desktop COPYONLY)
|
||||
|
||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk.desktop DESTINATION ${CMAKE_INSTALL_DATADIR}/applications)
|
@ -5,8 +5,10 @@ Version=1.0
|
||||
Name=Squawk
|
||||
GenericName=Instant Messenger
|
||||
GenericName[ru]=Мгновенные сообщения
|
||||
GenericName[pt_BR]=Mensageiro instantâneo
|
||||
Comment=XMPP (Jabber) instant messenger client
|
||||
Comment[ru]=XMPP (Jabber) клиент обмена мгновенными сообщениями
|
||||
Comment[pt_BR]=Cliente de mensagem instantânea XMPP (Jabber)
|
||||
Exec=squawk %u
|
||||
Icon=squawk
|
||||
StartupNotify=true
|
||||
|
4
plugins/CMakeLists.txt
Normal file
@ -0,0 +1,4 @@
|
||||
if (WITH_KIO)
|
||||
add_library(openFileManagerWindowJob SHARED openfilemanagerwindowjob.cpp)
|
||||
target_link_libraries(openFileManagerWindowJob PRIVATE KF5::KIOWidgets)
|
||||
endif ()
|
8
plugins/openfilemanagerwindowjob.cpp
Normal file
@ -0,0 +1,8 @@
|
||||
#include <QUrl>
|
||||
#include <QObject>
|
||||
#include <KIO/OpenFileManagerWindowJob>
|
||||
|
||||
extern "C" void highlightInFileManager(const QUrl& url) {
|
||||
KIO::OpenFileManagerWindowJob* job = KIO::highlightInFileManager({url});
|
||||
QObject::connect(job, &KIO::OpenFileManagerWindowJob::result, job, &KIO::OpenFileManagerWindowJob::deleteLater);
|
||||
}
|
59
resources/CMakeLists.txt
Normal file
@ -0,0 +1,59 @@
|
||||
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)
|
||||
|
11
resources/images/fallback/dark/big/document-preview.svg
Normal file
@ -0,0 +1,11 @@
|
||||
<!DOCTYPE svg>
|
||||
<svg viewBox="0 0 22 22" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#232629;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path class="ColorScheme-Text" style="fill:currentColor; fill-opacity:1; stroke:none" d="M 4 3 L 4 19 L 15 19 L 15 18 L 5 18 L 5 4 L 13 4 L 13 8 L 17 8 L 17 16 L 18 16 L 18 7 L 14 3 L 4 3 Z M 13 11 C 11.338 11 10 12.338 10 14 C 10 15.662 11.338 17 13 17 C 13.6494 17 14.2464 16.7914 14.7363 16.4434 L 17.293 19 L 18 18.293 L 15.4434 15.7363 C 15.7914 15.2464 16 14.6494 16 14 C 16 12.338 14.662 11 13 11 Z M 13 12 C 14.108 12 15 12.892 15 14 C 15 15.108 14.108 16 13 16 C 11.892 16 11 15.108 11 14 C 11 12.892 11.892 12 13 12 Z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 807 B |
21
resources/images/fallback/dark/big/folder.svg
Normal file
@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
<svg width="32" version="1.1" xmlns="http://www.w3.org/2000/svg" height="32" viewBox="0 0 32 32" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape">
|
||||
<defs id="defs5455">
|
||||
<linearGradient inkscape:collect="always" id="linearGradient4172-5">
|
||||
<stop style="stop-color:#3daee9" id="stop4174-6"/>
|
||||
<stop offset="1" style="stop-color:#6cc1ef" id="stop4176-6"/>
|
||||
</linearGradient>
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient4172-5" id="linearGradient4342" y1="29" y2="8" x2="0" gradientUnits="userSpaceOnUse"/>
|
||||
</defs>
|
||||
<metadata id="metadata5458"/>
|
||||
<g inkscape:label="Capa 1" inkscape:groupmode="layer" id="layer1" transform="matrix(1 0 0 1 -384.57143 -515.798)">
|
||||
<path inkscape:connector-curvature="0" style="fill:#147eb8;fill-rule:evenodd" id="path4308" d="m 386.57144,518.798 0,7 0,1 28,0 0,-6 -14.00001,0 -2,-2 z"/>
|
||||
<path inkscape:connector-curvature="0" style="fill-opacity:0.235294;fill-rule:evenodd" id="path4306" d="m 397.57143,523.798 -1.99999,1 -9,0 0,1 6.99999,0 3,0 z"/>
|
||||
<path style="fill:url(#linearGradient4342)" id="rect4294" d="M 13 8 L 11 10 L 2 10 L 1 10 L 1 29 L 12 29 L 13 29 L 31 29 L 31 8 L 13 8 z " transform="matrix(1 0 0 1 384.57143 515.798)"/>
|
||||
<path inkscape:connector-curvature="0" style="fill:#ffffff;fill-opacity:0.235294;fill-rule:evenodd" id="path4304" d="m 397.57143,523.798 -2,2 -10,0 0,1 11,0 z"/>
|
||||
<path inkscape:connector-curvature="0" style="fill:#ffffff;fill-opacity:0.235294;fill-rule:evenodd" id="path4310" d="m 398.57143,518.798 1,3 15.00001,0 0,-1 -14.00001,0 z"/>
|
||||
<rect width="30" x="385.57144" y="543.79797" height="1" style="fill-opacity:0.235294" id="rect4292"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.8 KiB |
12
resources/images/fallback/dark/small/document-preview.svg
Normal file
@ -0,0 +1,12 @@
|
||||
<!DOCTYPE svg>
|
||||
<svg viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#232629;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path class="ColorScheme-Text" style="fill:currentColor; fill-opacity:1; stroke:none" d="M 3 2 L 3 14 L 10 14 L 10 13 L 4 13 L 4 3 L 9 3 L 9 6 L 12 6 L 12 11 L 13 11 L 13 5 L 10 2 L 3 2 Z"/>
|
||||
<path class="ColorScheme-Text" style="fill:currentColor; fill-opacity:1; stroke:none" d="M 8.48828 7 C 7.10757 7 5.98828 8.11929 5.98828 9.5 C 5.98828 10.8807 7.10757 12 8.48828 12 C 8.97811 11.9992 9.45691 11.8546 9.86523 11.584 L 12.2813 14 L 12.9883 13.293 L 10.5723 10.877 C 10.8428 10.4686 10.9875 9.98983 10.9883 9.5 C 10.9883 8.11929 9.86899 7 8.48828 7 Z M 8.48828 8 C 9.31671 8 9.98828 8.67157 9.98828 9.5 C 9.98828 10.3284 9.31671 11 8.48828 11 C 7.65985 11 6.98828 10.3284 6.98828 9.5 C 6.98828 8.67157 7.65985 8 8.48828 8 Z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1010 B |
13
resources/images/fallback/dark/small/folder.svg
Normal file
@ -0,0 +1,13 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||
<defs id="defs3051">
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#232629;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path style="fill:currentColor;fill-opacity:1;stroke:none"
|
||||
d="M 2 2 L 2 3 L 2 6 L 2 7 L 2 13 L 2 14 L 14 14 L 14 13 L 14 6 L 14 5 L 14 4 L 9.0078125 4 L 7.0078125 2 L 7 2.0078125 L 7 2 L 3 2 L 2 2 z M 3 3 L 6.5917969 3 L 7.59375 4 L 7 4 L 7 4.0078125 L 6.9921875 4 L 4.9921875 6 L 3 6 L 3 3 z M 3 7 L 13 7 L 13 13 L 3 13 L 3 7 z "
|
||||
class="ColorScheme-Text"
|
||||
/>
|
||||
</svg>
|
After Width: | Height: | Size: 609 B |
11
resources/images/fallback/light/big/document-preview.svg
Normal file
@ -0,0 +1,11 @@
|
||||
<!DOCTYPE svg>
|
||||
<svg viewBox="0 0 22 22" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#eff0f1;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path class="ColorScheme-Text" style="fill:currentColor; fill-opacity:1; stroke:none" d="M 4 3 L 4 19 L 15 19 L 15 18 L 5 18 L 5 4 L 13 4 L 13 8 L 17 8 L 17 16 L 18 16 L 18 7 L 14 3 L 4 3 Z M 13 11 C 11.338 11 10 12.338 10 14 C 10 15.662 11.338 17 13 17 C 13.6494 17 14.2464 16.7914 14.7363 16.4434 L 17.293 19 L 18 18.293 L 15.4434 15.7363 C 15.7914 15.2464 16 14.6494 16 14 C 16 12.338 14.662 11 13 11 Z M 13 12 C 14.108 12 15 12.892 15 14 C 15 15.108 14.108 16 13 16 C 11.892 16 11 15.108 11 14 C 11 12.892 11.892 12 13 12 Z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 807 B |
21
resources/images/fallback/light/big/folder.svg
Normal file
@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
<svg width="32" version="1.1" xmlns="http://www.w3.org/2000/svg" height="32" viewBox="0 0 32 32" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape">
|
||||
<defs id="defs5455">
|
||||
<linearGradient inkscape:collect="always" id="linearGradient4172-5">
|
||||
<stop style="stop-color:#3daee9" id="stop4174-6"/>
|
||||
<stop offset="1" style="stop-color:#6cc1ef" id="stop4176-6"/>
|
||||
</linearGradient>
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient4172-5" id="linearGradient4342" y1="29" y2="8" x2="0" gradientUnits="userSpaceOnUse"/>
|
||||
</defs>
|
||||
<metadata id="metadata5458"/>
|
||||
<g inkscape:label="Capa 1" inkscape:groupmode="layer" id="layer1" transform="matrix(1 0 0 1 -384.57143 -515.798)">
|
||||
<path inkscape:connector-curvature="0" style="fill:#147eb8;fill-rule:evenodd" id="path4308" d="m 386.57144,518.798 0,7 0,1 28,0 0,-6 -14.00001,0 -2,-2 z"/>
|
||||
<path inkscape:connector-curvature="0" style="fill-opacity:0.235294;fill-rule:evenodd" id="path4306" d="m 397.57143,523.798 -1.99999,1 -9,0 0,1 6.99999,0 3,0 z"/>
|
||||
<path style="fill:url(#linearGradient4342)" id="rect4294" d="M 13 8 L 11 10 L 2 10 L 1 10 L 1 29 L 12 29 L 13 29 L 31 29 L 31 8 L 13 8 z " transform="matrix(1 0 0 1 384.57143 515.798)"/>
|
||||
<path inkscape:connector-curvature="0" style="fill:#ffffff;fill-opacity:0.235294;fill-rule:evenodd" id="path4304" d="m 397.57143,523.798 -2,2 -10,0 0,1 11,0 z"/>
|
||||
<path inkscape:connector-curvature="0" style="fill:#ffffff;fill-opacity:0.235294;fill-rule:evenodd" id="path4310" d="m 398.57143,518.798 1,3 15.00001,0 0,-1 -14.00001,0 z"/>
|
||||
<rect width="30" x="385.57144" y="543.79797" height="1" style="fill-opacity:0.235294" id="rect4292"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.8 KiB |
12
resources/images/fallback/light/small/document-preview.svg
Normal file
@ -0,0 +1,12 @@
|
||||
<!DOCTYPE svg>
|
||||
<svg viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#eff0f1;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path class="ColorScheme-Text" style="fill:currentColor; fill-opacity:1; stroke:none" d="M 3 2 L 3 14 L 10 14 L 10 13 L 4 13 L 4 3 L 9 3 L 9 6 L 12 6 L 12 11 L 13 11 L 13 5 L 10 2 L 3 2 Z"/>
|
||||
<path class="ColorScheme-Text" style="fill:currentColor; fill-opacity:1; stroke:none" d="M 8.48828 7 C 7.10757 7 5.98828 8.11929 5.98828 9.5 C 5.98828 10.8807 7.10757 12 8.48828 12 C 8.97811 11.9992 9.45691 11.8546 9.86523 11.584 L 12.2813 14 L 12.9883 13.293 L 10.5723 10.877 C 10.8428 10.4686 10.9875 9.98983 10.9883 9.5 C 10.9883 8.11929 9.86899 7 8.48828 7 Z M 8.48828 8 C 9.31671 8 9.98828 8.67157 9.98828 9.5 C 9.98828 10.3284 9.31671 11 8.48828 11 C 7.65985 11 6.98828 10.3284 6.98828 9.5 C 6.98828 8.67157 7.65985 8 8.48828 8 Z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1010 B |
13
resources/images/fallback/light/small/folder.svg
Normal file
@ -0,0 +1,13 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||
<defs id="defs3051">
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#eff0f1;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path style="fill:currentColor;fill-opacity:1;stroke:none"
|
||||
d="M 2 2 L 2 3 L 2 6 L 2 7 L 2 13 L 2 14 L 14 14 L 14 13 L 14 6 L 14 5 L 14 4 L 9.0078125 4 L 7.0078125 2 L 7 2.0078125 L 7 2 L 3 2 L 2 2 z M 3 3 L 6.5917969 3 L 7.59375 4 L 7 4 L 7 4.0078125 L 6.9921875 4 L 4.9921875 6 L 3 6 L 3 3 z M 3 7 L 13 7 L 13 13 L 3 13 L 3 7 z "
|
||||
class="ColorScheme-Text"
|
||||
/>
|
||||
</svg>
|
After Width: | Height: | Size: 609 B |
@ -40,6 +40,8 @@
|
||||
<file>images/fallback/dark/big/favorite.svg</file>
|
||||
<file>images/fallback/dark/big/unfavorite.svg</file>
|
||||
<file>images/fallback/dark/big/add.svg</file>
|
||||
<file>images/fallback/dark/big/folder.svg</file>
|
||||
<file>images/fallback/dark/big/document-preview.svg</file>
|
||||
|
||||
|
||||
<file>images/fallback/dark/small/absent.svg</file>
|
||||
@ -80,6 +82,8 @@
|
||||
<file>images/fallback/dark/small/favorite.svg</file>
|
||||
<file>images/fallback/dark/small/unfavorite.svg</file>
|
||||
<file>images/fallback/dark/small/add.svg</file>
|
||||
<file>images/fallback/dark/small/folder.svg</file>
|
||||
<file>images/fallback/dark/small/document-preview.svg</file>
|
||||
|
||||
|
||||
<file>images/fallback/light/big/absent.svg</file>
|
||||
@ -120,6 +124,8 @@
|
||||
<file>images/fallback/light/big/favorite.svg</file>
|
||||
<file>images/fallback/light/big/unfavorite.svg</file>
|
||||
<file>images/fallback/light/big/add.svg</file>
|
||||
<file>images/fallback/light/big/folder.svg</file>
|
||||
<file>images/fallback/light/big/document-preview.svg</file>
|
||||
|
||||
|
||||
<file>images/fallback/light/small/absent.svg</file>
|
||||
@ -160,5 +166,7 @@
|
||||
<file>images/fallback/light/small/favorite.svg</file>
|
||||
<file>images/fallback/light/small/unfavorite.svg</file>
|
||||
<file>images/fallback/light/small/add.svg</file>
|
||||
<file>images/fallback/light/small/folder.svg</file>
|
||||
<file>images/fallback/light/small/document-preview.svg</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
1
resources/squawk.rc
Normal file
@ -0,0 +1 @@
|
||||
IDI_ICON1 ICON "squawk.ico"
|
19
shared/CMakeLists.txt
Normal file
@ -0,0 +1,19 @@
|
||||
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
|
||||
)
|
@ -23,6 +23,11 @@
|
||||
Shared::Global* Shared::Global::instance = 0;
|
||||
const std::set<QString> Shared::Global::supportedImagesExts = {"png", "jpg", "webp", "jpeg", "gif", "svg"};
|
||||
|
||||
#ifdef WITH_KIO
|
||||
QLibrary Shared::Global::openFileManagerWindowJob("openFileManagerWindowJob");
|
||||
Shared::Global::HighlightInFileManager Shared::Global::hfm = 0;
|
||||
#endif
|
||||
|
||||
Shared::Global::Global():
|
||||
availability({
|
||||
tr("Online", "Availability"),
|
||||
@ -80,15 +85,71 @@ Shared::Global::Global():
|
||||
tr("Your password is going to be stored in KDE wallet storage (KWallet). You're going to be queried for permissions", "AccountPasswordDescription")
|
||||
}),
|
||||
pluginSupport({
|
||||
{"KWallet", false}
|
||||
})
|
||||
{"KWallet", false},
|
||||
{"openFileManagerWindowJob", false}
|
||||
}),
|
||||
fileCache()
|
||||
{
|
||||
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<QString, FileInfo>::const_iterator itr = instance->fileCache.find(path);
|
||||
if (itr == instance->fileCache.end()) {
|
||||
QMimeDatabase db;
|
||||
QMimeType type = db.mimeTypeForFile(path);
|
||||
QStringList parts = type.name().split("/");
|
||||
QString big = parts.front();
|
||||
QFileInfo info(path);
|
||||
|
||||
FileInfo::Preview p = FileInfo::Preview::none;
|
||||
QSize size;
|
||||
if (big == "image") {
|
||||
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()
|
||||
{
|
||||
@ -152,6 +213,69 @@ QString Shared::Global::getDescription(Shared::AccountPassword ap)
|
||||
return instance->accountPasswordDescription[static_cast<int>(ap)];
|
||||
}
|
||||
|
||||
|
||||
static const QStringList query = {"query", "default", "inode/directory"};
|
||||
static const QRegularExpression dolphinReg("[Dd]olphin");
|
||||
static const QRegularExpression nautilusReg("[Nn]autilus");
|
||||
static const QRegularExpression cajaReg("[Cc]aja");
|
||||
static const QRegularExpression nemoReg("[Nn]emo");
|
||||
static const QRegularExpression konquerorReg("kfmclient");
|
||||
static const QRegularExpression pcmanfmQtReg("pcmanfm-qt");
|
||||
static const QRegularExpression pcmanfmReg("pcmanfm");
|
||||
static const QRegularExpression thunarReg("thunar");
|
||||
|
||||
void Shared::Global::highlightInFileManager(const QString& path)
|
||||
{
|
||||
#ifdef WITH_KIO
|
||||
if (supported("openFileManagerWindowJob")) {
|
||||
hfm(path);
|
||||
return;
|
||||
} else {
|
||||
qDebug() << "requested to highlight in file manager url" << path << "but it's not supported: KIO plugin isn't loaded, trying fallback";
|
||||
}
|
||||
#else
|
||||
qDebug() << "requested to highlight in file manager url" << path << "but it's not supported: squawk wasn't compiled to support it, trying fallback";
|
||||
#endif
|
||||
|
||||
QFileInfo info = path;
|
||||
if (info.exists()) {
|
||||
QProcess proc;
|
||||
proc.start("xdg-mime", query);
|
||||
proc.waitForFinished();
|
||||
QString output = proc.readLine().simplified();
|
||||
|
||||
QString folder;
|
||||
if (info.isDir()) {
|
||||
folder = info.canonicalFilePath();
|
||||
} else {
|
||||
folder = info.canonicalPath();
|
||||
}
|
||||
|
||||
if (output.contains(dolphinReg)) {
|
||||
//there is a bug on current (21.04.0) dolphin, it works correct only if you already have dolphin launched
|
||||
proc.startDetached("dolphin", QStringList() << "--select" << info.canonicalFilePath());
|
||||
//KIO::highlightInFileManager({QUrl(info.canonicalFilePath())});
|
||||
} else if (output.contains(nautilusReg)) {
|
||||
proc.startDetached("nautilus", QStringList() << "--select" << info.canonicalFilePath()); //this worked on nautilus
|
||||
} else if (output.contains(cajaReg)) {
|
||||
proc.startDetached("caja", QStringList() << folder); //caja doesn't seem to support file selection command line, gonna just open directory
|
||||
} else if (output.contains(nemoReg)) {
|
||||
proc.startDetached("nemo", QStringList() << info.canonicalFilePath()); //nemo supports selecting files without keys
|
||||
} else if (output.contains(konquerorReg)) {
|
||||
proc.startDetached("konqueror", QStringList() << "--select" << info.canonicalFilePath()); //this worked on konqueror
|
||||
} else if (output.contains(pcmanfmQtReg)) {
|
||||
proc.startDetached("pcmanfm-qt", QStringList() << folder); //pcmanfm-qt doesn't seem to support open with selection, gonna just open directory
|
||||
} else if (output.contains(pcmanfmReg)) {
|
||||
proc.startDetached("pcmanfm", QStringList() << folder); //pcmanfm also doesn't seem to support open with selection, gonna just open directory
|
||||
} else if (output.contains(thunarReg)) {
|
||||
proc.startDetached("thunar", QStringList() << folder); //thunar doesn't seem to support open with selection, gonna just open directory
|
||||
} else {
|
||||
QDesktopServices::openUrl(QUrl::fromLocalFile(folder));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#define FROM_INT_INPL(Enum) \
|
||||
template<> \
|
||||
Enum Shared::Global::fromInt(int src) \
|
||||
|
@ -29,6 +29,18 @@
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QDebug>
|
||||
#include <QMimeType>
|
||||
#include <QMimeDatabase>
|
||||
#include <QFileInfo>
|
||||
#include <QImage>
|
||||
#include <QMovie>
|
||||
#include <QSize>
|
||||
#include <QUrl>
|
||||
#include <QLibrary>
|
||||
#include <QFileInfo>
|
||||
#include <QProcess>
|
||||
#include <QDesktopServices>
|
||||
#include <QRegularExpression>
|
||||
|
||||
namespace Shared {
|
||||
|
||||
@ -36,6 +48,19 @@ 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();
|
||||
@ -64,6 +89,9 @@ namespace Shared {
|
||||
|
||||
static const std::set<QString> supportedImagesExts;
|
||||
|
||||
static FileInfo getFileInfo(const QString& path);
|
||||
static void highlightInFileManager(const QString& path);
|
||||
|
||||
template<typename T>
|
||||
static T fromInt(int src);
|
||||
|
||||
@ -87,6 +115,15 @@ namespace Shared {
|
||||
static Global* instance;
|
||||
|
||||
std::map<QString, bool> pluginSupport;
|
||||
std::map<QString, FileInfo> fileCache;
|
||||
|
||||
#ifdef WITH_KIO
|
||||
static QLibrary openFileManagerWindowJob;
|
||||
|
||||
typedef void (*HighlightInFileManager)(const QUrl &);
|
||||
|
||||
static HighlightInFileManager hfm;
|
||||
#endif
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -170,6 +170,8 @@ static const std::map<QString, std::pair<QString, QString>> icons = {
|
||||
{"favorite", {"favorite", "favorite"}},
|
||||
{"unfavorite", {"draw-star", "unfavorite"}},
|
||||
{"list-add", {"list-add", "add"}},
|
||||
{"folder", {"folder", "folder"}},
|
||||
{"document-preview", {"document-preview", "document-preview"}}
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -36,7 +36,8 @@ Shared::Message::Message(Shared::Message::Type p_type):
|
||||
errorText(),
|
||||
originalMessage(),
|
||||
lastModified(),
|
||||
stanzaId()
|
||||
stanzaId(),
|
||||
attachPath()
|
||||
{}
|
||||
|
||||
Shared::Message::Message():
|
||||
@ -56,7 +57,8 @@ Shared::Message::Message():
|
||||
errorText(),
|
||||
originalMessage(),
|
||||
lastModified(),
|
||||
stanzaId()
|
||||
stanzaId(),
|
||||
attachPath()
|
||||
{}
|
||||
|
||||
QString Shared::Message::getBody() const
|
||||
@ -311,6 +313,7 @@ void Shared::Message::serialize(QDataStream& data) const
|
||||
data << lastModified;
|
||||
}
|
||||
data << stanzaId;
|
||||
data << attachPath;
|
||||
}
|
||||
|
||||
void Shared::Message::deserialize(QDataStream& data)
|
||||
@ -341,6 +344,7 @@ void Shared::Message::deserialize(QDataStream& data)
|
||||
data >> lastModified;
|
||||
}
|
||||
data >> stanzaId;
|
||||
data >> attachPath;
|
||||
}
|
||||
|
||||
bool Shared::Message::change(const QMap<QString, QVariant>& data)
|
||||
@ -350,6 +354,16 @@ bool Shared::Message::change(const QMap<QString, QVariant>& data)
|
||||
setState(static_cast<State>(itr.value().toUInt()));
|
||||
}
|
||||
|
||||
itr = data.find("outOfBandUrl");
|
||||
if (itr != data.end()) {
|
||||
setOutOfBandUrl(itr.value().toString());
|
||||
}
|
||||
|
||||
itr = data.find("attachPath");
|
||||
if (itr != data.end()) {
|
||||
setAttachPath(itr.value().toString());
|
||||
}
|
||||
|
||||
if (state == State::error) {
|
||||
itr = data.find("errorText");
|
||||
if (itr != data.end()) {
|
||||
@ -380,6 +394,8 @@ bool Shared::Message::change(const QMap<QString, QVariant>& data)
|
||||
|
||||
itr = data.find("body");
|
||||
if (itr != data.end()) {
|
||||
QString b = itr.value().toString();
|
||||
if (body != b) {
|
||||
QMap<QString, QVariant>::const_iterator dItr = data.find("stamp");
|
||||
QDateTime correctionDate;
|
||||
if (dItr != data.end()) {
|
||||
@ -390,10 +406,19 @@ bool Shared::Message::change(const QMap<QString, QVariant>& data)
|
||||
if (!edited || lastModified < correctionDate) {
|
||||
originalMessage = body;
|
||||
lastModified = correctionDate;
|
||||
setBody(itr.value().toString());
|
||||
setBody(body);
|
||||
setEdited(true);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
QMap<QString, QVariant>::const_iterator dItr = data.find("stamp");
|
||||
if (dItr != data.end()) {
|
||||
QDateTime ntime = dItr.value().toDateTime();
|
||||
if (time != ntime) {
|
||||
setTime(ntime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return idChanged;
|
||||
}
|
||||
@ -420,7 +445,7 @@ void Shared::Message::setOutOfBandUrl(const QString& url)
|
||||
|
||||
bool Shared::Message::storable() const
|
||||
{
|
||||
return id.size() > 0 && (body.size() > 0 || oob.size()) > 0;
|
||||
return id.size() > 0 && (body.size() > 0 || oob.size() > 0 || attachPath.size() > 0);
|
||||
}
|
||||
|
||||
void Shared::Message::setStanzaId(const QString& sid)
|
||||
@ -432,3 +457,33 @@ 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<QString, QVariant>& _data):
|
||||
data(_data),
|
||||
idModified(false) {}
|
||||
|
||||
void Shared::Message::Change::operator()(Shared::Message& msg)
|
||||
{
|
||||
idModified = msg.change(data);
|
||||
}
|
||||
|
||||
void Shared::Message::Change::operator()(Shared::Message* msg)
|
||||
{
|
||||
idModified = msg->change(data);
|
||||
}
|
||||
|
||||
bool Shared::Message::Change::hasIdBeenModified() const
|
||||
{
|
||||
return idModified;
|
||||
}
|
||||
|
||||
|
@ -16,15 +16,15 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef SHAPER_MESSAGE_H
|
||||
#define SHAPER_MESSAGE_H
|
||||
|
||||
#include <QString>
|
||||
#include <QDateTime>
|
||||
#include <QVariant>
|
||||
#include <QMap>
|
||||
#include <QDataStream>
|
||||
|
||||
#ifndef SHAPER_MESSAGE_H
|
||||
#define SHAPER_MESSAGE_H
|
||||
|
||||
namespace Shared {
|
||||
|
||||
/**
|
||||
@ -46,9 +46,22 @@ 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<QString, QVariant>& _data);
|
||||
void operator() (Message& msg);
|
||||
void operator() (Message* msg);
|
||||
bool hasIdBeenModified() const;
|
||||
|
||||
private:
|
||||
const QMap<QString, QVariant>& data;
|
||||
bool idModified;
|
||||
};
|
||||
|
||||
Message(Type p_type);
|
||||
Message();
|
||||
|
||||
@ -72,6 +85,7 @@ public:
|
||||
void setErrorText(const QString& err);
|
||||
bool change(const QMap<QString, QVariant>& data);
|
||||
void setStanzaId(const QString& sid);
|
||||
void setAttachPath(const QString& path);
|
||||
|
||||
QString getFrom() const;
|
||||
QString getFromJid() const;
|
||||
@ -100,6 +114,7 @@ public:
|
||||
QDateTime getLastModified() const;
|
||||
QString getOriginalBody() const;
|
||||
QString getStanzaId() const;
|
||||
QString getAttachPath() const;
|
||||
|
||||
void serialize(QDataStream& data) const;
|
||||
void deserialize(QDataStream& data);
|
||||
@ -123,6 +138,7 @@ private:
|
||||
QString originalMessage;
|
||||
QDateTime lastModified;
|
||||
QString stanzaId;
|
||||
QString attachPath;
|
||||
};
|
||||
|
||||
}
|
||||
|
45
shared/messageinfo.cpp
Normal file
@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Squawk messenger.
|
||||
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "messageinfo.h"
|
||||
|
||||
using namespace Shared;
|
||||
|
||||
Shared::MessageInfo::MessageInfo():
|
||||
account(),
|
||||
jid(),
|
||||
messageId() {}
|
||||
|
||||
Shared::MessageInfo::MessageInfo(const QString& acc, const QString& j, const QString& id):
|
||||
account(acc),
|
||||
jid(j),
|
||||
messageId(id) {}
|
||||
|
||||
Shared::MessageInfo::MessageInfo(const Shared::MessageInfo& other):
|
||||
account(other.account),
|
||||
jid(other.jid),
|
||||
messageId(other.messageId) {}
|
||||
|
||||
Shared::MessageInfo & Shared::MessageInfo::operator=(const Shared::MessageInfo& other)
|
||||
{
|
||||
account = other.account;
|
||||
jid = other.jid;
|
||||
messageId = other.messageId;
|
||||
|
||||
return *this;
|
||||
}
|
43
shared/messageinfo.h
Normal file
@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Squawk messenger.
|
||||
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef SHARED_MESSAGEINFO_H
|
||||
#define SHARED_MESSAGEINFO_H
|
||||
|
||||
#include <QString>
|
||||
|
||||
namespace Shared {
|
||||
|
||||
/**
|
||||
* @todo write docs
|
||||
*/
|
||||
struct MessageInfo {
|
||||
MessageInfo();
|
||||
MessageInfo(const QString& acc, const QString& j, const QString& id);
|
||||
MessageInfo(const MessageInfo& other);
|
||||
|
||||
QString account;
|
||||
QString jid;
|
||||
QString messageId;
|
||||
|
||||
MessageInfo& operator=(const MessageInfo& other);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // SHARED_MESSAGEINFO_H
|
@ -19,11 +19,12 @@
|
||||
#ifndef SHARED_H
|
||||
#define SHARED_H
|
||||
|
||||
#include "shared/enums.h"
|
||||
#include "shared/utils.h"
|
||||
#include "shared/icons.h"
|
||||
#include "shared/message.h"
|
||||
#include "shared/vcard.h"
|
||||
#include "shared/global.h"
|
||||
#include "enums.h"
|
||||
#include "global.h"
|
||||
#include "icons.h"
|
||||
#include "message.h"
|
||||
#include "messageinfo.h"
|
||||
#include "utils.h"
|
||||
#include "vcard.h"
|
||||
|
||||
#endif // SHARED_H
|
@ -17,15 +17,11 @@
|
||||
*/
|
||||
|
||||
#include "utils.h"
|
||||
#include <QUuid>
|
||||
|
||||
QString Shared::generateUUID()
|
||||
{
|
||||
uuid_t uuid;
|
||||
uuid_generate(uuid);
|
||||
|
||||
char uuid_str[36];
|
||||
uuid_unparse_lower(uuid, uuid_str);
|
||||
return uuid_str;
|
||||
return QUuid::createUuid().toString();
|
||||
}
|
||||
|
||||
|
||||
|
@ -20,10 +20,12 @@
|
||||
#define SHARED_UTILS_H
|
||||
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <QColor>
|
||||
#include <QRegularExpression>
|
||||
|
||||
#include <uuid/uuid.h>
|
||||
// #include "KIO/OpenFileManagerWindowJob"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace Shared {
|
||||
|
42
signalcatcher_win32.cpp
Normal file
@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Squawk messenger.
|
||||
* Copyright (C) 2021 Shunf4 <shun1048576@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "signalcatcher.h"
|
||||
#include <unistd.h>
|
||||
|
||||
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;
|
||||
}
|
11
translations/CMakeLists.txt
Normal file
@ -0,0 +1,11 @@
|
||||
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)
|
800
translations/squawk.pt_BR.ts
Normal file
@ -0,0 +1,800 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE TS>
|
||||
<TS version="2.1" language="pt_BR">
|
||||
<context>
|
||||
<name>Account</name>
|
||||
<message>
|
||||
<source>Account</source>
|
||||
<translatorcomment>Window header</translatorcomment>
|
||||
<translation>Conta</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Your account login</source>
|
||||
<translation>Suas informações de login</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>john_smith1987</source>
|
||||
<translation>josé_silva1987</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Server</source>
|
||||
<translation>Servidor</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>A server address of your account. Like 404.city or macaw.me</source>
|
||||
<translation>O endereço do servidor da sua conta, como o 404.city ou o macaw.me</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>macaw.me</source>
|
||||
<translation>macaw.me</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Login</source>
|
||||
<translation>Usuário</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Password</source>
|
||||
<translation>Senha</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Password of your account</source>
|
||||
<translation>Senha da sua conta</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Name</source>
|
||||
<translation>Nome</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Just a name how would you call this account, doesn't affect anything</source>
|
||||
<translation>Apenas um nome para identificar esta conta. Não influencia em nada</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>John</source>
|
||||
<translation>José</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Resource</source>
|
||||
<translation>Recurso</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>A resource name like "Home" or "Work"</source>
|
||||
<translation>Um nome de recurso como "Casa" ou "Trabalho"</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>QXmpp</source>
|
||||
<translatorcomment>Default resource</translatorcomment>
|
||||
<translation>QXmpp</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Password storage</source>
|
||||
<translation>Armazenamento de senha</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Accounts</name>
|
||||
<message>
|
||||
<source>Accounts</source>
|
||||
<translation>Contas</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Delete</source>
|
||||
<translation>Apagar</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Add</source>
|
||||
<translation>Adicionar</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Edit</source>
|
||||
<translation>Editar</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Change password</source>
|
||||
<translation>Alterar senha</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Connect</source>
|
||||
<translation>Conectar</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Disconnect</source>
|
||||
<translation>Desconectar</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Conversation</name>
|
||||
<message>
|
||||
<source>Type your message here...</source>
|
||||
<translation>Digite sua mensagem aqui...</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Chose a file to send</source>
|
||||
<translation>Escolha um arquivo para enviar</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source><!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></source>
|
||||
<translation></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Drop files here to attach them to your message</source>
|
||||
<translation>Arraste seus arquivos aqui para anexá-los a sua mensagem</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Global</name>
|
||||
<message>
|
||||
<source>Online</source>
|
||||
<comment>Availability</comment>
|
||||
<translation>Conectado</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Away</source>
|
||||
<comment>Availability</comment>
|
||||
<translation>Distante</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Absent</source>
|
||||
<comment>Availability</comment>
|
||||
<translation>Ausente</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Busy</source>
|
||||
<comment>Availability</comment>
|
||||
<translation>Ocupado</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Chatty</source>
|
||||
<comment>Availability</comment>
|
||||
<translation>Tagarela</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Invisible</source>
|
||||
<comment>Availability</comment>
|
||||
<translation>Invisível</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Offline</source>
|
||||
<comment>Availability</comment>
|
||||
<translation>Desconectado</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Disconnected</source>
|
||||
<comment>ConnectionState</comment>
|
||||
<translation>Desconectado</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Connecting</source>
|
||||
<comment>ConnectionState</comment>
|
||||
<translation>Connectando</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Connected</source>
|
||||
<comment>ConnectionState</comment>
|
||||
<translation>Conectado</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Error</source>
|
||||
<comment>ConnectionState</comment>
|
||||
<translation>Erro</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>None</source>
|
||||
<comment>SubscriptionState</comment>
|
||||
<translation>Nenhum</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>From</source>
|
||||
<comment>SubscriptionState</comment>
|
||||
<translation>De</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>To</source>
|
||||
<comment>SubscriptionState</comment>
|
||||
<translation>Para</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Both</source>
|
||||
<comment>SubscriptionState</comment>
|
||||
<translation>Ambos</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Unknown</source>
|
||||
<comment>SubscriptionState</comment>
|
||||
<translation>Desconhecido</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Unspecified</source>
|
||||
<comment>Affiliation</comment>
|
||||
<translation>Não especificado</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Outcast</source>
|
||||
<comment>Affiliation</comment>
|
||||
<translation>Rejeitado</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Nobody</source>
|
||||
<comment>Affiliation</comment>
|
||||
<translation>Ninguém</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Member</source>
|
||||
<comment>Affiliation</comment>
|
||||
<translation>Membro</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Admin</source>
|
||||
<comment>Affiliation</comment>
|
||||
<translation>Administrador</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Owner</source>
|
||||
<comment>Affiliation</comment>
|
||||
<translation>Dono</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Unspecified</source>
|
||||
<comment>Role</comment>
|
||||
<translation>Não especificado</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Nobody</source>
|
||||
<comment>Role</comment>
|
||||
<translation>Ninguém</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Visitor</source>
|
||||
<comment>Role</comment>
|
||||
<translation>Visitante</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Participant</source>
|
||||
<comment>Role</comment>
|
||||
<translation>Participante</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Moderator</source>
|
||||
<comment>Role</comment>
|
||||
<translation>Moderador</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Pending</source>
|
||||
<comment>MessageState</comment>
|
||||
<translation>Aguardando</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Sent</source>
|
||||
<comment>MessageState</comment>
|
||||
<translation>Enviada</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Delivered</source>
|
||||
<comment>MessageState</comment>
|
||||
<translation>Entregue</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Error</source>
|
||||
<comment>MessageState</comment>
|
||||
<translation>Erro</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Plain</source>
|
||||
<comment>AccountPassword</comment>
|
||||
<translation>Texto simples</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Jammed</source>
|
||||
<comment>AccountPassword</comment>
|
||||
<translation>Embaralhado</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Always Ask</source>
|
||||
<comment>AccountPassword</comment>
|
||||
<translation>Sempre perguntar</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>KWallet</source>
|
||||
<comment>AccountPassword</comment>
|
||||
<translation>KWallet</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>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</source>
|
||||
<comment>AccountPasswordDescription</comment>
|
||||
<translation>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 é</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Squawk is going to query you for the password on every start of the program</source>
|
||||
<comment>AccountPasswordDescription</comment>
|
||||
<translation>O Squark vai requisitar sua senha a cada vez que você abrir o programa</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Your password is going to be stored in config file in plain text</source>
|
||||
<comment>AccountPasswordDescription</comment>
|
||||
<translation>Sua senha será armazenada em um arquivo de configurações em texto simples</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Your password is going to be stored in KDE wallet storage (KWallet). You're going to be queried for permissions</source>
|
||||
<comment>AccountPasswordDescription</comment>
|
||||
<translation>Sua senha será armazenada no KDE wallet (KWallet). Sua autorização será requerida</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>JoinConference</name>
|
||||
<message>
|
||||
<source>Join new conference</source>
|
||||
<translatorcomment>Заголовок окна</translatorcomment>
|
||||
<translation>Entrar em uma nova conferência</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>JID</source>
|
||||
<translation>JID</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Room JID</source>
|
||||
<translation>Sala JID</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>identifier@conference.server.org</source>
|
||||
<translation>identifier@conference.server.org</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Account</source>
|
||||
<translation>Conta</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Join on login</source>
|
||||
<translation>Entrar ao se conectar</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>If checked Squawk will try to join this conference on login</source>
|
||||
<translation>Se marcado, o Squawk tentará entrar nesta conferência automaticamente durante o login</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Nick name</source>
|
||||
<translation>Apelido</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Your nick name for that conference. If you leave this field empty your account name will be used as a nick name</source>
|
||||
<translation>Seu apelido para essa conferência. Se você deixar este campo em branco, seu nome de usuário será usado como apelido</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>John</source>
|
||||
<translation>José</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Message</name>
|
||||
<message>
|
||||
<source>Open</source>
|
||||
<translation>Abrir</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>MessageLine</name>
|
||||
<message>
|
||||
<source>Downloading...</source>
|
||||
<translation>Baixando...</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Download</source>
|
||||
<translation>Baixar</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Error uploading file: %1
|
||||
You can try again</source>
|
||||
<translation>Error fazendo upload do arquivo:
|
||||
%1
|
||||
Você pode tentar novamente</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Upload</source>
|
||||
<translation>Upload</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Error downloading file: %1
|
||||
You can try again</source>
|
||||
<translation>Erro baixando arquivo:
|
||||
%1
|
||||
Você pode tentar novamente</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Uploading...</source>
|
||||
<translation>Fazendo upload...</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Push the button to download the file</source>
|
||||
<translation>Pressione o botão para baixar o arquivo</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Models::Room</name>
|
||||
<message>
|
||||
<source>Subscribed</source>
|
||||
<translation>Inscrito</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Temporarily unsubscribed</source>
|
||||
<translation>Inscrição temporariamente cancelada</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Temporarily subscribed</source>
|
||||
<translation>Temporariamente inscrito</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Unsubscribed</source>
|
||||
<translation>Não inscrito</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Models::Roster</name>
|
||||
<message>
|
||||
<source>New messages</source>
|
||||
<translation>Novas mensagens</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>New messages: </source>
|
||||
<translation>Novas mensagens: </translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Jabber ID: </source>
|
||||
<translation>ID Jabber: </translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Availability: </source>
|
||||
<translation>Disponibilidade: </translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Status: </source>
|
||||
<translation>Status: </translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Subscription: </source>
|
||||
<translation>Inscrição: </translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Affiliation: </source>
|
||||
<translatorcomment>Я правда не знаю, как это объяснить, не то что перевести</translatorcomment>
|
||||
<translation>Afiliação: </translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Role: </source>
|
||||
<translation>Papel: </translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Online contacts: </source>
|
||||
<translation>Contatos online: </translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Total contacts: </source>
|
||||
<translation>Contatos totais: </translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Members: </source>
|
||||
<translation>Membros: </translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>NewContact</name>
|
||||
<message>
|
||||
<source>Add new contact</source>
|
||||
<translatorcomment>Заголовок окна</translatorcomment>
|
||||
<translation>Adicionar novo contato</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Account</source>
|
||||
<translation>Conta</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>An account that is going to have new contact</source>
|
||||
<translation>A conta que terá um novo contato</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>JID</source>
|
||||
<translation>JID</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Jabber id of your new contact</source>
|
||||
<translation>ID Jabber do seu novo contato</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>name@server.dmn</source>
|
||||
<translatorcomment>Placeholder поля ввода JID</translatorcomment>
|
||||
<translation>nome@servidor.com.br</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Name</source>
|
||||
<translation>Nome</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>The way this new contact will be labeled in your roster (optional)</source>
|
||||
<translation>A forma com que o novo contato será classificado em sua lista (opcional)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>John Smith</source>
|
||||
<translation>José Silva</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Squawk</name>
|
||||
<message>
|
||||
<source>squawk</source>
|
||||
<translation>Squawk</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Settings</source>
|
||||
<translation>Configurações</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Squawk</source>
|
||||
<translation>Squawk</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Accounts</source>
|
||||
<translation>Contas</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Quit</source>
|
||||
<translation>Sair</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Add contact</source>
|
||||
<translation>Adicionar contato</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Add conference</source>
|
||||
<translation>Adicionar conferência</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Disconnect</source>
|
||||
<translation>Desconectar</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Connect</source>
|
||||
<translation>Conectar</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>VCard</source>
|
||||
<translation>VCard</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Remove</source>
|
||||
<translation>Remover</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Open dialog</source>
|
||||
<translation>Abrir caixa de diálogo</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Unsubscribe</source>
|
||||
<translation>Cancelar inscrição</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Subscribe</source>
|
||||
<translation>Inscrever-se</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Rename</source>
|
||||
<translation>Renomear</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Input new name for %1
|
||||
or leave it empty for the contact
|
||||
to be displayed as %1</source>
|
||||
<translation>Digite um novo nome para %1
|
||||
ou o deixe em branco para o contato
|
||||
ser exibido com
|
||||
%1</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Renaming %1</source>
|
||||
<translation>Renomeando %1</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Groups</source>
|
||||
<translation>Grupos</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>New group</source>
|
||||
<translation>Novo grupo</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>New group name</source>
|
||||
<translation>Novo nome do grupo</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Add %1 to a new group</source>
|
||||
<translation>Adicionar %1 a um novo grupo</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Open conversation</source>
|
||||
<translation>Abrir conversa</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>%1 account card</source>
|
||||
<translation>cartão da conta %1</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>%1 contact card</source>
|
||||
<translation>cartão de contato %1</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Downloading vCard</source>
|
||||
<translation>Baixando vCard</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Attached file</source>
|
||||
<translation>Arquivo anexado</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Input the password for account %1</source>
|
||||
<translation>Digite a senha para a conta %1</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Password for account %1</source>
|
||||
<translation>Senha para a conta %1</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Please select a contact to start chatting</source>
|
||||
<translation>Por favor selecione um contato para começar a conversar</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>VCard</name>
|
||||
<message>
|
||||
<source>Received 12.07.2007 at 17.35</source>
|
||||
<translation>Recebido 12/07/2007 às 17:35</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>General</source>
|
||||
<translation>Geral</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Organization</source>
|
||||
<translation>Empresa</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Middle name</source>
|
||||
<translation>Nome do meio</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>First name</source>
|
||||
<translation>Primeiro nome</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Last name</source>
|
||||
<translation>Sobrenome</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Nick name</source>
|
||||
<translation>Apelido</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Birthday</source>
|
||||
<translation>Data de aniversário</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Organization name</source>
|
||||
<translation>Nome da empresa</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Unit / Department</source>
|
||||
<translation>Unidade/Departamento</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Role / Profession</source>
|
||||
<translation>Profissão</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Job title</source>
|
||||
<translation>Cargo</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Full name</source>
|
||||
<translation>Nome completo</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Personal information</source>
|
||||
<translation>Informações pessoais</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Addresses</source>
|
||||
<translation>Endereços</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>E-Mail addresses</source>
|
||||
<translation>Endereços de e-mail</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Phone numbers</source>
|
||||
<translation>Números de telefone</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Contact</source>
|
||||
<translation>Contato</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Jabber ID</source>
|
||||
<translation>ID Jabber</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Web site</source>
|
||||
<translation>Site web</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Description</source>
|
||||
<translation>Descrição</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Set avatar</source>
|
||||
<translation>Definir avatar</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Clear avatar</source>
|
||||
<translation>Apagar avatar</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Account %1 card</source>
|
||||
<translation>Cartão da conta %1</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Contact %1 card</source>
|
||||
<translation>Cartão do contato %1</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Received %1 at %2</source>
|
||||
<translation>Recebido %1 em %2</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Chose your new avatar</source>
|
||||
<translation>Escolha um novo avatar</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Images (*.png *.jpg *.jpeg)</source>
|
||||
<translation>Imagens (*.png *.jpg *.jpeg)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Add email address</source>
|
||||
<translation>Adicionar endereço de e-mail</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Unset this email as preferred</source>
|
||||
<translation>Desmarcar este e-mail como preferido</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Set this email as preferred</source>
|
||||
<translation>Marcar este e-mail como preferido</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Remove selected email addresses</source>
|
||||
<translation>Remover endereço de e-mail selecionado</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Copy selected emails to clipboard</source>
|
||||
<translation>Copiar endereços de e-mails selecionados para a área de transferência</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Add phone number</source>
|
||||
<translation>Adicionar número de telefone</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Unset this phone as preferred</source>
|
||||
<translation>Desmarcar este número de telefone como preferido</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Set this phone as preferred</source>
|
||||
<translation>Marcar este número de telefone como preferido</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Remove selected phone numbers</source>
|
||||
<translation>Remover os números de telefones selecionados</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Copy selected phones to clipboard</source>
|
||||
<translation>Copiar os números de telefone selecionados para a área de transferência</translation>
|
||||
</message>
|
||||
</context>
|
||||
</TS>
|
@ -1,45 +1,5 @@
|
||||
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)
|
||||
target_sources(squawk PRIVATE squawk.cpp squawk.h squawk.ui)
|
||||
|
||||
add_subdirectory(models)
|
||||
add_subdirectory(utils)
|
||||
add_subdirectory(widgets)
|
||||
|
||||
set(squawkUI_SRC
|
||||
squawk.cpp
|
||||
models/accounts.cpp
|
||||
models/roster.cpp
|
||||
models/item.cpp
|
||||
models/account.cpp
|
||||
models/contact.cpp
|
||||
models/presence.cpp
|
||||
models/group.cpp
|
||||
models/room.cpp
|
||||
models/abstractparticipant.cpp
|
||||
models/participant.cpp
|
||||
models/reference.cpp
|
||||
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
|
||||
)
|
||||
|
||||
# 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)
|
||||
|
26
ui/models/CMakeLists.txt
Normal file
@ -0,0 +1,26 @@
|
||||
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
|
||||
)
|
@ -31,7 +31,8 @@ Models::Account::Account(const QMap<QString, QVariant>& data, Models::Item* pare
|
||||
avatarPath(data.value("avatarPath").toString()),
|
||||
state(Shared::ConnectionState::disconnected),
|
||||
availability(Shared::Availability::offline),
|
||||
passwordType(Shared::AccountPassword::plain)
|
||||
passwordType(Shared::AccountPassword::plain),
|
||||
wasEverConnected(false)
|
||||
{
|
||||
QMap<QString, QVariant>::const_iterator sItr = data.find("state");
|
||||
if (sItr != data.end()) {
|
||||
@ -56,8 +57,19 @@ void Models::Account::setState(Shared::ConnectionState p_state)
|
||||
if (state != p_state) {
|
||||
state = p_state;
|
||||
changed(2);
|
||||
if (state == Shared::ConnectionState::disconnected) {
|
||||
switch (state) {
|
||||
case Shared::ConnectionState::disconnected:
|
||||
toOfflineState();
|
||||
break;
|
||||
case Shared::ConnectionState::connected:
|
||||
if (wasEverConnected) {
|
||||
emit reconnected();
|
||||
} else {
|
||||
wasEverConnected = true;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -231,7 +243,7 @@ void Models::Account::toOfflineState()
|
||||
Item::toOfflineState();
|
||||
}
|
||||
|
||||
QString Models::Account::getAvatarPath()
|
||||
QString Models::Account::getAvatarPath() const
|
||||
{
|
||||
return avatarPath;
|
||||
}
|
||||
|
@ -57,7 +57,7 @@ namespace Models {
|
||||
QString getError() const;
|
||||
|
||||
void setAvatarPath(const QString& path);
|
||||
QString getAvatarPath();
|
||||
QString getAvatarPath() const;
|
||||
|
||||
void setAvailability(Shared::Availability p_avail);
|
||||
void setAvailability(unsigned int p_avail);
|
||||
@ -77,6 +77,9 @@ namespace Models {
|
||||
QString getBareJid() const;
|
||||
QString getFullJid() const;
|
||||
|
||||
signals:
|
||||
void reconnected();
|
||||
|
||||
private:
|
||||
QString login;
|
||||
QString password;
|
||||
@ -87,6 +90,7 @@ namespace Models {
|
||||
Shared::ConnectionState state;
|
||||
Shared::Availability availability;
|
||||
Shared::AccountPassword passwordType;
|
||||
bool wasEverConnected;
|
||||
|
||||
protected slots:
|
||||
void toOfflineState() override;
|
||||
|
@ -17,55 +17,26 @@
|
||||
*/
|
||||
|
||||
#include "contact.h"
|
||||
#include "account.h"
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
Models::Contact::Contact(const Account* acc, const QString& p_jid ,const QMap<QString, QVariant> &data, Item *parentItem):
|
||||
Item(Item::contact, data, parentItem),
|
||||
jid(p_jid),
|
||||
Element(Item::contact, acc, p_jid, data, parentItem),
|
||||
availability(Shared::Availability::offline),
|
||||
state(Shared::SubscriptionState::none),
|
||||
avatarState(Shared::Avatar::empty),
|
||||
presences(),
|
||||
messages(),
|
||||
childMessages(0),
|
||||
status(),
|
||||
avatarPath(),
|
||||
account(acc)
|
||||
status()
|
||||
{
|
||||
QMap<QString, QVariant>::const_iterator itr = data.find("state");
|
||||
if (itr != data.end()) {
|
||||
setState(itr.value().toUInt());
|
||||
}
|
||||
|
||||
itr = data.find("avatarState");
|
||||
if (itr != data.end()) {
|
||||
setAvatarState(itr.value().toUInt());
|
||||
}
|
||||
itr = data.find("avatarPath");
|
||||
if (itr != data.end()) {
|
||||
setAvatarPath(itr.value().toString());
|
||||
}
|
||||
}
|
||||
|
||||
Models::Contact::~Contact()
|
||||
{
|
||||
}
|
||||
|
||||
QString Models::Contact::getJid() const
|
||||
{
|
||||
return jid;
|
||||
}
|
||||
|
||||
void Models::Contact::setJid(const QString p_jid)
|
||||
{
|
||||
if (jid != p_jid) {
|
||||
jid = p_jid;
|
||||
changed(1);
|
||||
}
|
||||
}
|
||||
|
||||
void Models::Contact::setAvailability(unsigned int p_state)
|
||||
{
|
||||
setAvailability(Shared::Global::fromInt<Shared::Availability>(p_state));
|
||||
@ -144,16 +115,12 @@ void Models::Contact::update(const QString& field, const QVariant& value)
|
||||
{
|
||||
if (field == "name") {
|
||||
setName(value.toString());
|
||||
} else if (field == "jid") {
|
||||
setJid(value.toString());
|
||||
} else if (field == "availability") {
|
||||
setAvailability(value.toUInt());
|
||||
} else if (field == "state") {
|
||||
setState(value.toUInt());
|
||||
} else if (field == "avatarState") {
|
||||
setAvatarState(value.toUInt());
|
||||
} else if (field == "avatarPath") {
|
||||
setAvatarPath(value.toString());
|
||||
} else {
|
||||
Element::update(field, value);
|
||||
}
|
||||
}
|
||||
|
||||
@ -192,11 +159,9 @@ void Models::Contact::refresh()
|
||||
{
|
||||
QDateTime lastActivity;
|
||||
Presence* presence = 0;
|
||||
unsigned int count = 0;
|
||||
for (QMap<QString, Presence*>::iterator itr = presences.begin(), end = presences.end(); itr != end; ++itr) {
|
||||
Presence* pr = itr.value();
|
||||
QDateTime la = pr->getLastActivity();
|
||||
count += pr->getMessagesCount();
|
||||
|
||||
if (la > lastActivity) {
|
||||
lastActivity = la;
|
||||
@ -211,11 +176,6 @@ void Models::Contact::refresh()
|
||||
setAvailability(Shared::Availability::offline);
|
||||
setStatus("");
|
||||
}
|
||||
|
||||
if (childMessages != count) {
|
||||
childMessages = count;
|
||||
changed(4);
|
||||
}
|
||||
}
|
||||
|
||||
void Models::Contact::_removeChild(int index)
|
||||
@ -257,81 +217,6 @@ QIcon Models::Contact::getStatusIcon(bool big) const
|
||||
}
|
||||
}
|
||||
|
||||
void Models::Contact::addMessage(const Shared::Message& data)
|
||||
{
|
||||
const QString& res = data.getPenPalResource();
|
||||
if (res.size() > 0) {
|
||||
QMap<QString, Presence*>::iterator itr = presences.find(res);
|
||||
if (itr == presences.end()) {
|
||||
// this is actually the place when I can spot someone's invisible presence, and there is nothing criminal in it, cuz the sender sent us a message
|
||||
// therefore he have revealed himself
|
||||
// the only issue is to find out when the sender is gone offline
|
||||
Presence* pr = new Presence({});
|
||||
pr->setName(res);
|
||||
pr->setAvailability(Shared::Availability::invisible);
|
||||
pr->setLastActivity(QDateTime::currentDateTimeUtc());
|
||||
presences.insert(res, pr);
|
||||
appendChild(pr);
|
||||
pr->addMessage(data);
|
||||
return;
|
||||
}
|
||||
itr.value()->addMessage(data);
|
||||
} else {
|
||||
messages.emplace_back(data);
|
||||
changed(4);
|
||||
}
|
||||
}
|
||||
|
||||
void Models::Contact::changeMessage(const QString& id, const QMap<QString, QVariant>& data)
|
||||
{
|
||||
|
||||
bool found = false;
|
||||
for (Shared::Message& msg : messages) {
|
||||
if (msg.getId() == id) {
|
||||
msg.change(data);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
for (Presence* pr : presences) {
|
||||
found = pr->changeMessage(id, data);
|
||||
if (found) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int Models::Contact::getMessagesCount() const
|
||||
{
|
||||
return messages.size() + childMessages;
|
||||
}
|
||||
|
||||
void Models::Contact::dropMessages()
|
||||
{
|
||||
if (messages.size() > 0) {
|
||||
messages.clear();
|
||||
changed(4);
|
||||
}
|
||||
|
||||
for (QMap<QString, Presence*>::iterator itr = presences.begin(), end = presences.end(); itr != end; ++itr) {
|
||||
itr.value()->dropMessages();
|
||||
}
|
||||
}
|
||||
|
||||
void Models::Contact::getMessages(Models::Contact::Messages& container) const
|
||||
{
|
||||
for (Messages::const_iterator itr = messages.begin(), end = messages.end(); itr != end; ++itr) {
|
||||
const Shared::Message& msg = *itr;
|
||||
container.push_back(msg);
|
||||
}
|
||||
|
||||
for (QMap<QString, Presence*>::const_iterator itr = presences.begin(), end = presences.end(); itr != end; ++itr) {
|
||||
itr.value()->getMessages(container);
|
||||
}
|
||||
}
|
||||
|
||||
void Models::Contact::toOfflineState()
|
||||
{
|
||||
std::deque<Item*>::size_type size = childItems.size();
|
||||
@ -355,75 +240,9 @@ QString Models::Contact::getDisplayedName() const
|
||||
return getContactName();
|
||||
}
|
||||
|
||||
bool Models::Contact::columnInvolvedInDisplay(int col)
|
||||
void Models::Contact::handleRecconnect()
|
||||
{
|
||||
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);
|
||||
if (getMessagesCount() > 0) {
|
||||
feed->requestLatestMessages();
|
||||
}
|
||||
}
|
||||
|
||||
void Models::Contact::setAvatarState(Shared::Avatar p_state)
|
||||
{
|
||||
if (avatarState != p_state) {
|
||||
avatarState = p_state;
|
||||
changed(6);
|
||||
}
|
||||
}
|
||||
|
||||
void Models::Contact::setAvatarState(unsigned int p_state)
|
||||
{
|
||||
if (p_state <= static_cast<quint8>(Shared::Avatar::valid)) {
|
||||
Shared::Avatar state = static_cast<Shared::Avatar>(p_state);
|
||||
setAvatarState(state);
|
||||
} else {
|
||||
qDebug() << "An attempt to set invalid avatar state" << p_state << "to the contact" << jid << ", skipping";
|
||||
}
|
||||
}
|
||||
|
||||
const Models::Account * Models::Contact::getParentAccount() const
|
||||
{
|
||||
return account;
|
||||
}
|
||||
|
||||
|
@ -19,7 +19,7 @@
|
||||
#ifndef MODELS_CONTACT_H
|
||||
#define MODELS_CONTACT_H
|
||||
|
||||
#include "item.h"
|
||||
#include "element.h"
|
||||
#include "presence.h"
|
||||
#include "shared/enums.h"
|
||||
#include "shared/message.h"
|
||||
@ -31,49 +31,36 @@
|
||||
#include <deque>
|
||||
|
||||
namespace Models {
|
||||
class Account;
|
||||
|
||||
class Contact : public Item
|
||||
class Contact : public Element
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
typedef std::deque<Shared::Message> Messages;
|
||||
Contact(const Account* acc, const QString& p_jid, const QMap<QString, QVariant> &data, Item *parentItem = 0);
|
||||
Contact(const Contact& other);
|
||||
~Contact();
|
||||
|
||||
QString getJid() const;
|
||||
Shared::Availability getAvailability() const;
|
||||
Shared::SubscriptionState getState() const;
|
||||
Shared::Avatar getAvatarState() const;
|
||||
QString getAvatarPath() const;
|
||||
|
||||
QIcon getStatusIcon(bool big = false) const;
|
||||
|
||||
int columnCount() const override;
|
||||
QVariant data(int column) const override;
|
||||
|
||||
void update(const QString& field, const QVariant& value);
|
||||
void update(const QString& field, const QVariant& value) override;
|
||||
|
||||
void addPresence(const QString& name, const QMap<QString, QVariant>& data);
|
||||
void removePresence(const QString& name);
|
||||
|
||||
QString getContactName() const;
|
||||
QString getStatus() const;
|
||||
|
||||
void addMessage(const Shared::Message& data);
|
||||
void changeMessage(const QString& id, const QMap<QString, QVariant>& data);
|
||||
unsigned int getMessagesCount() const;
|
||||
void dropMessages();
|
||||
void getMessages(Messages& container) const;
|
||||
QString getDisplayedName() const override;
|
||||
|
||||
Contact* copy() const;
|
||||
void handleRecconnect(); //this is a special method Models::Roster calls when reconnect happens
|
||||
|
||||
protected:
|
||||
void _removeChild(int index) override;
|
||||
void _appendChild(Models::Item * child) override;
|
||||
bool columnInvolvedInDisplay(int col) override;
|
||||
const Account* getParentAccount() const override;
|
||||
|
||||
protected slots:
|
||||
void refresh();
|
||||
@ -84,23 +71,13 @@ protected:
|
||||
void setAvailability(unsigned int p_state);
|
||||
void setState(Shared::SubscriptionState p_state);
|
||||
void setState(unsigned int p_state);
|
||||
void setAvatarState(Shared::Avatar p_state);
|
||||
void setAvatarState(unsigned int p_state);
|
||||
void setAvatarPath(const QString& path);
|
||||
void setJid(const QString p_jid);
|
||||
void setStatus(const QString& p_state);
|
||||
|
||||
private:
|
||||
QString jid;
|
||||
Shared::Availability availability;
|
||||
Shared::SubscriptionState state;
|
||||
Shared::Avatar avatarState;
|
||||
QMap<QString, Presence*> presences;
|
||||
Messages messages;
|
||||
unsigned int childMessages;
|
||||
QString status;
|
||||
QString avatarPath;
|
||||
const Account* account;
|
||||
};
|
||||
|
||||
}
|
||||
|
184
ui/models/element.cpp
Normal file
@ -0,0 +1,184 @@
|
||||
/*
|
||||
* Squawk messenger.
|
||||
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "element.h"
|
||||
#include "account.h"
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
Models::Element::Element(Type p_type, const Models::Account* acc, const QString& p_jid, const QMap<QString, QVariant>& data, Models::Item* parentItem):
|
||||
Item(p_type, data, parentItem),
|
||||
jid(p_jid),
|
||||
avatarPath(),
|
||||
avatarState(Shared::Avatar::empty),
|
||||
account(acc),
|
||||
feed(new MessageFeed(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<QString, QVariant>::const_iterator itr = data.find("avatarState");
|
||||
if (itr != data.end()) {
|
||||
setAvatarState(itr.value().toUInt());
|
||||
}
|
||||
itr = data.find("avatarPath");
|
||||
if (itr != data.end()) {
|
||||
setAvatarPath(itr.value().toString());
|
||||
}
|
||||
}
|
||||
|
||||
Models::Element::~Element()
|
||||
{
|
||||
delete feed;
|
||||
}
|
||||
|
||||
|
||||
QString Models::Element::getJid() const
|
||||
{
|
||||
return jid;
|
||||
}
|
||||
|
||||
void Models::Element::setJid(const QString& p_jid)
|
||||
{
|
||||
if (jid != p_jid) {
|
||||
jid = p_jid;
|
||||
changed(1);
|
||||
}
|
||||
}
|
||||
|
||||
void Models::Element::update(const QString& field, const QVariant& value)
|
||||
{
|
||||
if (field == "jid") {
|
||||
setJid(value.toString());
|
||||
} else if (field == "avatarState") {
|
||||
setAvatarState(value.toUInt());
|
||||
} else if (field == "avatarPath") {
|
||||
setAvatarPath(value.toString());
|
||||
}
|
||||
}
|
||||
|
||||
QString Models::Element::getAvatarPath() const
|
||||
{
|
||||
return avatarPath;
|
||||
}
|
||||
|
||||
Shared::Avatar Models::Element::getAvatarState() const
|
||||
{
|
||||
return avatarState;
|
||||
}
|
||||
|
||||
void Models::Element::setAvatarPath(const QString& path)
|
||||
{
|
||||
if (path != avatarPath) {
|
||||
avatarPath = path;
|
||||
if (type == contact) {
|
||||
changed(7);
|
||||
} else if (type == room) {
|
||||
changed(8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Models::Element::setAvatarState(Shared::Avatar p_state)
|
||||
{
|
||||
if (avatarState != p_state) {
|
||||
avatarState = p_state;
|
||||
if (type == contact) {
|
||||
changed(6);
|
||||
} else if (type == room) {
|
||||
changed(7);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Models::Element::setAvatarState(unsigned int p_state)
|
||||
{
|
||||
if (p_state <= static_cast<quint8>(Shared::Avatar::valid)) {
|
||||
Shared::Avatar state = static_cast<Shared::Avatar>(p_state);
|
||||
setAvatarState(state);
|
||||
} else {
|
||||
qDebug() << "An attempt to set invalid avatar state" << p_state << "to the element" << jid << ", skipping";
|
||||
}
|
||||
}
|
||||
|
||||
bool Models::Element::columnInvolvedInDisplay(int col)
|
||||
{
|
||||
return Item::columnInvolvedInDisplay(col) && col == 1;
|
||||
}
|
||||
|
||||
const Models::Account * Models::Element::getParentAccount() const
|
||||
{
|
||||
return account;
|
||||
}
|
||||
|
||||
unsigned int Models::Element::getMessagesCount() const
|
||||
{
|
||||
return feed->unreadMessagesCount();
|
||||
}
|
||||
|
||||
void Models::Element::addMessage(const Shared::Message& data)
|
||||
{
|
||||
feed->addMessage(data);
|
||||
}
|
||||
|
||||
void Models::Element::changeMessage(const QString& id, const QMap<QString, QVariant>& data)
|
||||
{
|
||||
feed->changeMessage(id, data);
|
||||
}
|
||||
|
||||
void Models::Element::responseArchive(const std::list<Shared::Message> list, bool last)
|
||||
{
|
||||
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);
|
||||
}
|
82
ui/models/element.h
Normal file
@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Squawk messenger.
|
||||
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef ELEMENT_H
|
||||
#define ELEMENT_H
|
||||
|
||||
#include "item.h"
|
||||
|
||||
#include "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<QString, QVariant> &data, Item *parentItem = 0);
|
||||
~Element();
|
||||
|
||||
public:
|
||||
QString getJid() const;
|
||||
Shared::Avatar getAvatarState() const;
|
||||
QString getAvatarPath() const;
|
||||
|
||||
virtual void update(const QString& field, const QVariant& value);
|
||||
|
||||
void addMessage(const Shared::Message& data);
|
||||
void changeMessage(const QString& id, const QMap<QString, QVariant>& data);
|
||||
unsigned int getMessagesCount() const;
|
||||
void responseArchive(const std::list<Shared::Message> list, 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
|
@ -283,6 +283,15 @@ Shared::ConnectionState Models::Item::getAccountConnectionState() const
|
||||
return acc->getState();
|
||||
}
|
||||
|
||||
QString Models::Item::getAccountAvatarPath() const
|
||||
{
|
||||
const Account* acc = getParentAccount();
|
||||
if (acc == nullptr) {
|
||||
return "";
|
||||
}
|
||||
return acc->getAvatarPath();
|
||||
}
|
||||
|
||||
QString Models::Item::getDisplayedName() const
|
||||
{
|
||||
return name;
|
||||
|
@ -80,6 +80,7 @@ class Item : public QObject{
|
||||
QString getAccountName() const;
|
||||
QString getAccountJid() const;
|
||||
QString getAccountResource() const;
|
||||
QString getAccountAvatarPath() const;
|
||||
Shared::ConnectionState getAccountConnectionState() const;
|
||||
Shared::Availability getAccountAvailability() const;
|
||||
|
||||
|
@ -20,82 +20,15 @@
|
||||
#include "shared/icons.h"
|
||||
|
||||
Models::Presence::Presence(const QMap<QString, QVariant>& data, Item* parentItem):
|
||||
AbstractParticipant(Item::presence, data, parentItem),
|
||||
messages()
|
||||
AbstractParticipant(Item::presence, data, parentItem)
|
||||
{
|
||||
}
|
||||
|
||||
Models::Presence::Presence(const Models::Presence& other):
|
||||
AbstractParticipant(other),
|
||||
messages(other.messages)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
Models::Presence::~Presence()
|
||||
{
|
||||
}
|
||||
|
||||
int Models::Presence::columnCount() const
|
||||
{
|
||||
return 5;
|
||||
}
|
||||
|
||||
QVariant Models::Presence::data(int column) const
|
||||
{
|
||||
switch (column) {
|
||||
case 4:
|
||||
return getMessagesCount();
|
||||
default:
|
||||
return AbstractParticipant::data(column);
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int Models::Presence::getMessagesCount() const
|
||||
{
|
||||
return messages.size();
|
||||
}
|
||||
|
||||
void Models::Presence::addMessage(const Shared::Message& data)
|
||||
{
|
||||
messages.emplace_back(data);
|
||||
changed(4);
|
||||
}
|
||||
|
||||
bool Models::Presence::changeMessage(const QString& id, const QMap<QString, QVariant>& data)
|
||||
{
|
||||
bool found = false;
|
||||
for (Shared::Message& msg : messages) {
|
||||
if (msg.getId() == id) {
|
||||
msg.change(data);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
void Models::Presence::dropMessages()
|
||||
{
|
||||
if (messages.size() > 0) {
|
||||
messages.clear();
|
||||
changed(4);
|
||||
}
|
||||
}
|
||||
|
||||
QIcon Models::Presence::getStatusIcon(bool big) const
|
||||
{
|
||||
if (getMessagesCount() > 0) {
|
||||
return Shared::icon("mail-message", big);
|
||||
} else {
|
||||
return AbstractParticipant::getStatusIcon();
|
||||
}
|
||||
}
|
||||
|
||||
void Models::Presence::getMessages(Models::Presence::Messages& container) const
|
||||
{
|
||||
for (Messages::const_iterator itr = messages.begin(), end = messages.end(); itr != end; ++itr) {
|
||||
const Shared::Message& msg = *itr;
|
||||
container.push_back(msg);
|
||||
}
|
||||
return 4;
|
||||
}
|
||||
|
@ -32,25 +32,10 @@ class Presence : public Models::AbstractParticipant
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
typedef std::deque<Shared::Message> Messages;
|
||||
explicit Presence(const QMap<QString, QVariant> &data, Item *parentItem = 0);
|
||||
Presence(const Presence& other);
|
||||
~Presence();
|
||||
|
||||
int columnCount() const override;
|
||||
QVariant data(int column) const override;
|
||||
|
||||
QIcon getStatusIcon(bool big = false) const override;
|
||||
|
||||
unsigned int getMessagesCount() const;
|
||||
void dropMessages();
|
||||
void addMessage(const Shared::Message& data);
|
||||
bool changeMessage(const QString& id, const QMap<QString, QVariant>& data);
|
||||
|
||||
void getMessages(Messages& container) const;
|
||||
|
||||
private:
|
||||
Messages messages;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -104,6 +104,8 @@ void Models::Reference::onChildChanged(Models::Item* item, int row, int col)
|
||||
{
|
||||
if (item == original) {
|
||||
emit childChanged(this, row, col);
|
||||
} else {
|
||||
emit childChanged(item, row, col);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -22,16 +22,12 @@
|
||||
#include <QIcon>
|
||||
#include <QDebug>
|
||||
|
||||
Models::Room::Room(const QString& p_jid, const QMap<QString, QVariant>& data, Models::Item* parentItem):
|
||||
Item(room, data, parentItem),
|
||||
Models::Room::Room(const Account* acc, const QString& p_jid, const QMap<QString, QVariant>& data, Models::Item* parentItem):
|
||||
Element(room, acc, p_jid, data, parentItem),
|
||||
autoJoin(false),
|
||||
joined(false),
|
||||
jid(p_jid),
|
||||
nick(""),
|
||||
subject(""),
|
||||
avatarState(Shared::Avatar::empty),
|
||||
avatarPath(""),
|
||||
messages(),
|
||||
participants(),
|
||||
exParticipantAvatars()
|
||||
{
|
||||
@ -55,16 +51,6 @@ Models::Room::Room(const QString& p_jid, const QMap<QString, QVariant>& data, Mo
|
||||
setSubject(itr.value().toString());
|
||||
}
|
||||
|
||||
itr = data.find("avatarState");
|
||||
if (itr != data.end()) {
|
||||
setAvatarState(itr.value().toUInt());
|
||||
}
|
||||
itr = data.find("avatarPath");
|
||||
if (itr != data.end()) {
|
||||
setAvatarPath(itr.value().toString());
|
||||
}
|
||||
|
||||
|
||||
itr = data.find("avatars");
|
||||
if (itr != data.end()) {
|
||||
QMap<QString, QVariant> avs = itr.value().toMap();
|
||||
@ -78,21 +64,11 @@ Models::Room::~Room()
|
||||
{
|
||||
}
|
||||
|
||||
unsigned int Models::Room::getUnreadMessagesCount() const
|
||||
{
|
||||
return messages.size();
|
||||
}
|
||||
|
||||
int Models::Room::columnCount() const
|
||||
{
|
||||
return 7;
|
||||
}
|
||||
|
||||
QString Models::Room::getJid() const
|
||||
{
|
||||
return jid;
|
||||
}
|
||||
|
||||
bool Models::Room::getAutoJoin() const
|
||||
{
|
||||
return autoJoin;
|
||||
@ -151,14 +127,6 @@ void Models::Room::setAutoJoin(bool p_autoJoin)
|
||||
}
|
||||
}
|
||||
|
||||
void Models::Room::setJid(const QString& p_jid)
|
||||
{
|
||||
if (jid != p_jid) {
|
||||
jid = p_jid;
|
||||
changed(1);
|
||||
}
|
||||
}
|
||||
|
||||
void Models::Room::setJoined(bool p_joined)
|
||||
{
|
||||
if (joined != p_joined) {
|
||||
@ -182,8 +150,6 @@ void Models::Room::update(const QString& field, const QVariant& value)
|
||||
{
|
||||
if (field == "name") {
|
||||
setName(value.toString());
|
||||
} else if (field == "jid") {
|
||||
setJid(value.toString());
|
||||
} else if (field == "joined") {
|
||||
setJoined(value.toBool());
|
||||
} else if (field == "autoJoin") {
|
||||
@ -192,16 +158,14 @@ void Models::Room::update(const QString& field, const QVariant& value)
|
||||
setNick(value.toString());
|
||||
} else if (field == "subject") {
|
||||
setSubject(value.toString());
|
||||
} else if (field == "avatarState") {
|
||||
setAvatarState(value.toUInt());
|
||||
} else if (field == "avatarPath") {
|
||||
setAvatarPath(value.toString());
|
||||
} else {
|
||||
Element::update(field, value);
|
||||
}
|
||||
}
|
||||
|
||||
QIcon Models::Room::getStatusIcon(bool big) const
|
||||
{
|
||||
if (messages.size() > 0) {
|
||||
if (getMessagesCount() > 0) {
|
||||
return Shared::icon("mail-message", big);
|
||||
} else {
|
||||
if (autoJoin) {
|
||||
@ -237,42 +201,6 @@ QString Models::Room::getStatusText() const
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int Models::Room::getMessagesCount() const
|
||||
{
|
||||
return messages.size();
|
||||
}
|
||||
|
||||
void Models::Room::addMessage(const Shared::Message& data)
|
||||
{
|
||||
messages.emplace_back(data);
|
||||
changed(5);
|
||||
}
|
||||
|
||||
void Models::Room::changeMessage(const QString& id, const QMap<QString, QVariant>& data)
|
||||
{
|
||||
for (Shared::Message& msg : messages) {
|
||||
if (msg.getId() == id) {
|
||||
msg.change(data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Models::Room::dropMessages()
|
||||
{
|
||||
if (messages.size() > 0) {
|
||||
messages.clear();
|
||||
changed(5);
|
||||
}
|
||||
}
|
||||
|
||||
void Models::Room::getMessages(Models::Room::Messages& container) const
|
||||
{
|
||||
for (Messages::const_iterator itr = messages.begin(), end = messages.end(); itr != end; ++itr) {
|
||||
const Shared::Message& msg = *itr;
|
||||
container.push_back(msg);
|
||||
}
|
||||
}
|
||||
|
||||
void Models::Room::toOfflineState()
|
||||
{
|
||||
@ -367,47 +295,6 @@ QString Models::Room::getDisplayedName() const
|
||||
return getRoomName();
|
||||
}
|
||||
|
||||
bool Models::Room::columnInvolvedInDisplay(int col)
|
||||
{
|
||||
return Item::columnInvolvedInDisplay(col) && col == 1;
|
||||
}
|
||||
|
||||
QString Models::Room::getAvatarPath() const
|
||||
{
|
||||
return avatarPath;
|
||||
}
|
||||
|
||||
Shared::Avatar Models::Room::getAvatarState() const
|
||||
{
|
||||
return avatarState;
|
||||
}
|
||||
|
||||
void Models::Room::setAvatarPath(const QString& path)
|
||||
{
|
||||
if (avatarPath != path) {
|
||||
avatarPath = path;
|
||||
changed(8);
|
||||
}
|
||||
}
|
||||
|
||||
void Models::Room::setAvatarState(Shared::Avatar p_state)
|
||||
{
|
||||
if (avatarState != p_state) {
|
||||
avatarState = p_state;
|
||||
changed(7);
|
||||
}
|
||||
}
|
||||
|
||||
void Models::Room::setAvatarState(unsigned int p_state)
|
||||
{
|
||||
if (p_state <= static_cast<quint8>(Shared::Avatar::valid)) {
|
||||
Shared::Avatar state = static_cast<Shared::Avatar>(p_state);
|
||||
setAvatarState(state);
|
||||
} else {
|
||||
qDebug() << "An attempt to set invalid avatar state" << p_state << "to the room" << jid << ", skipping";
|
||||
}
|
||||
}
|
||||
|
||||
std::map<QString, const Models::Participant &> Models::Room::getParticipants() const
|
||||
{
|
||||
std::map<QString, const Models::Participant&> result;
|
||||
@ -423,8 +310,13 @@ QString Models::Room::getParticipantIconPath(const QString& name) const
|
||||
{
|
||||
std::map<QString, Models::Participant*>::const_iterator itr = participants.find(name);
|
||||
if (itr == participants.end()) {
|
||||
std::map<QString, QString>::const_iterator eitr = exParticipantAvatars.find(name);
|
||||
if (eitr != exParticipantAvatars.end()) {
|
||||
return eitr->second;
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
return itr->second->getAvatarPath();
|
||||
}
|
||||
|
@ -19,7 +19,7 @@
|
||||
#ifndef MODELS_ROOM_H
|
||||
#define MODELS_ROOM_H
|
||||
|
||||
#include "item.h"
|
||||
#include "element.h"
|
||||
#include "participant.h"
|
||||
#include "shared/enums.h"
|
||||
#include "shared/message.h"
|
||||
@ -29,21 +29,18 @@ namespace Models {
|
||||
/**
|
||||
* @todo write docs
|
||||
*/
|
||||
class Room : public Models::Item
|
||||
class Room : public Element
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
typedef std::deque<Shared::Message> Messages;
|
||||
Room(const QString& p_jid, const QMap<QString, QVariant> &data, Item *parentItem = 0);
|
||||
Room(const Account* acc, const QString& p_jid, const QMap<QString, QVariant> &data, Item *parentItem = 0);
|
||||
~Room();
|
||||
|
||||
int columnCount() const override;
|
||||
QVariant data(int column) const override;
|
||||
|
||||
unsigned int getUnreadMessagesCount() const;
|
||||
bool getJoined() const;
|
||||
bool getAutoJoin() const;
|
||||
QString getJid() const;
|
||||
QString getNick() const;
|
||||
QString getRoomName() const;
|
||||
QString getSubject() const;
|
||||
@ -53,17 +50,10 @@ public:
|
||||
|
||||
void setJoined(bool p_joined);
|
||||
void setAutoJoin(bool p_autoJoin);
|
||||
void setJid(const QString& p_jid);
|
||||
void setNick(const QString& p_nick);
|
||||
void setSubject(const QString& sub);
|
||||
|
||||
void update(const QString& field, const QVariant& value);
|
||||
|
||||
void addMessage(const Shared::Message& data);
|
||||
void changeMessage(const QString& id, const QMap<QString, QVariant>& data);
|
||||
unsigned int getMessagesCount() const;
|
||||
void dropMessages();
|
||||
void getMessages(Messages& container) const;
|
||||
void update(const QString& field, const QVariant& value) override;
|
||||
|
||||
void addParticipant(const QString& name, const QMap<QString, QVariant>& data);
|
||||
void changeParticipant(const QString& name, const QMap<QString, QVariant>& data);
|
||||
@ -71,8 +61,6 @@ public:
|
||||
|
||||
void toOfflineState() override;
|
||||
QString getDisplayedName() const override;
|
||||
Shared::Avatar getAvatarState() const;
|
||||
QString getAvatarPath() const;
|
||||
std::map<QString, const Participant&> getParticipants() const;
|
||||
QString getParticipantIconPath(const QString& name) const;
|
||||
std::map<QString, QString> getExParticipantAvatars() const;
|
||||
@ -84,24 +72,14 @@ signals:
|
||||
private:
|
||||
void handleParticipantUpdate(std::map<QString, Participant*>::const_iterator itr, const QMap<QString, QVariant>& data);
|
||||
|
||||
protected:
|
||||
bool columnInvolvedInDisplay(int col) override;
|
||||
void setAvatarState(Shared::Avatar p_state);
|
||||
void setAvatarState(unsigned int p_state);
|
||||
void setAvatarPath(const QString& path);
|
||||
|
||||
private:
|
||||
bool autoJoin;
|
||||
bool joined;
|
||||
QString jid;
|
||||
QString nick;
|
||||
QString subject;
|
||||
Shared::Avatar avatarState;
|
||||
QString avatarPath;
|
||||
Messages messages;
|
||||
std::map<QString, Participant*> participants;
|
||||
std::map<QString, QString> exParticipantAvatars;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -48,6 +48,7 @@ Models::Roster::~Roster()
|
||||
void Models::Roster::addAccount(const QMap<QString, QVariant>& data)
|
||||
{
|
||||
Account* acc = new Account(data);
|
||||
connect(acc, &Account::reconnected, this, &Roster::onAccountReconnected);
|
||||
root->appendChild(acc);
|
||||
accounts.insert(std::make_pair(acc->getName(), acc));
|
||||
accountsModel->addAccount(acc);
|
||||
@ -215,11 +216,7 @@ QVariant Models::Roster::data (const QModelIndex& index, int role) const
|
||||
break;
|
||||
case Item::presence: {
|
||||
Presence* contact = static_cast<Presence*>(item);
|
||||
QString str("");
|
||||
int mc = contact->getMessagesCount();
|
||||
if (mc > 0) {
|
||||
str += tr("New messages: ") + std::to_string(mc).c_str() + "\n";
|
||||
}
|
||||
QString str;
|
||||
Shared::Availability av = contact->getAvailability();
|
||||
str += tr("Availability: ") + Shared::Global::getName(av);
|
||||
QString s = contact->getStatus();
|
||||
@ -232,7 +229,7 @@ QVariant Models::Roster::data (const QModelIndex& index, int role) const
|
||||
break;
|
||||
case Item::participant: {
|
||||
Participant* p = static_cast<Participant*>(item);
|
||||
QString str("");
|
||||
QString str;
|
||||
Shared::Availability av = p->getAvailability();
|
||||
str += tr("Availability: ") + Shared::Global::getName(av) + "\n";
|
||||
QString s = p->getStatus();
|
||||
@ -260,7 +257,7 @@ QVariant Models::Roster::data (const QModelIndex& index, int role) const
|
||||
break;
|
||||
case Item::room: {
|
||||
Room* rm = static_cast<Room*>(item);
|
||||
unsigned int count = rm->getUnreadMessagesCount();
|
||||
unsigned int count = rm->getMessagesCount();
|
||||
QString str("");
|
||||
if (count > 0) {
|
||||
str += tr("New messages: ") + std::to_string(count).c_str() + "\n";
|
||||
@ -450,6 +447,10 @@ void Models::Roster::addContact(const QString& account, const QString& jid, cons
|
||||
std::map<ElId, Contact*>::iterator itr = contacts.find(id);
|
||||
if (itr == contacts.end()) {
|
||||
contact = new Contact(acc, jid, data);
|
||||
connect(contact, &Contact::requestArchive, this, &Roster::onElementRequestArchive);
|
||||
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;
|
||||
@ -535,35 +536,19 @@ void Models::Roster::removeGroup(const QString& account, const QString& name)
|
||||
|
||||
void Models::Roster::changeContact(const QString& account, const QString& jid, const QMap<QString, QVariant>& data)
|
||||
{
|
||||
ElId id(account, jid);
|
||||
std::map<ElId, Contact*>::iterator cItr = contacts.find(id);
|
||||
|
||||
if (cItr != contacts.end()) {
|
||||
Element* el = getElement({account, jid});
|
||||
if (el != NULL) {
|
||||
for (QMap<QString, QVariant>::const_iterator itr = data.begin(), end = data.end(); itr != end; ++itr) {
|
||||
cItr->second->update(itr.key(), itr.value());
|
||||
}
|
||||
} else {
|
||||
std::map<ElId, Room*>::iterator rItr = rooms.find(id);
|
||||
if (rItr != rooms.end()) {
|
||||
for (QMap<QString, QVariant>::const_iterator itr = data.begin(), end = data.end(); itr != end; ++itr) {
|
||||
rItr->second->update(itr.key(), itr.value());
|
||||
}
|
||||
el->update(itr.key(), itr.value());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Models::Roster::changeMessage(const QString& account, const QString& jid, const QString& id, const QMap<QString, QVariant>& data)
|
||||
{
|
||||
ElId elid(account, jid);
|
||||
std::map<ElId, Contact*>::iterator cItr = contacts.find(elid);
|
||||
|
||||
if (cItr != contacts.end()) {
|
||||
cItr->second->changeMessage(id, data);
|
||||
} else {
|
||||
std::map<ElId, Room*>::iterator rItr = rooms.find(elid);
|
||||
if (rItr != rooms.end()) {
|
||||
rItr->second->changeMessage(id, data);
|
||||
}
|
||||
Element* el = getElement({account, jid});
|
||||
if (el != NULL) {
|
||||
el->changeMessage(id, data);
|
||||
}
|
||||
}
|
||||
|
||||
@ -627,7 +612,6 @@ void Models::Roster::removeContact(const QString& account, const QString& jid, c
|
||||
} else {
|
||||
delete ref;
|
||||
}
|
||||
|
||||
if (gr->childCount() == 0) {
|
||||
removeGroup(account, group);
|
||||
}
|
||||
@ -708,29 +692,9 @@ void Models::Roster::removePresence(const QString& account, const QString& jid,
|
||||
|
||||
void Models::Roster::addMessage(const QString& account, const Shared::Message& data)
|
||||
{
|
||||
ElId id(account, data.getPenPalJid());
|
||||
std::map<ElId, Contact*>::iterator itr = contacts.find(id);
|
||||
if (itr != contacts.end()) {
|
||||
itr->second->addMessage(data);
|
||||
} else {
|
||||
std::map<ElId, Room*>::const_iterator rItr = rooms.find(id);
|
||||
if (rItr != rooms.end()) {
|
||||
rItr->second->addMessage(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Models::Roster::dropMessages(const QString& account, const QString& jid)
|
||||
{
|
||||
ElId id(account, jid);
|
||||
std::map<ElId, Contact*>::iterator itr = contacts.find(id);
|
||||
if (itr != contacts.end()) {
|
||||
itr->second->dropMessages();
|
||||
} else {
|
||||
std::map<ElId, Room*>::const_iterator rItr = rooms.find(id);
|
||||
if (rItr != rooms.end()) {
|
||||
rItr->second->dropMessages();
|
||||
}
|
||||
Element* el = getElement({account, data.getPenPalJid()});
|
||||
if (el != NULL) {
|
||||
el->addMessage(data);
|
||||
}
|
||||
}
|
||||
|
||||
@ -781,6 +745,7 @@ void Models::Roster::removeAccount(const QString& account)
|
||||
}
|
||||
}
|
||||
|
||||
disconnect(acc, &Account::reconnected, this, &Roster::onAccountReconnected);
|
||||
acc->deleteLater();
|
||||
}
|
||||
|
||||
@ -821,7 +786,11 @@ void Models::Roster::addRoom(const QString& account, const QString jid, const QM
|
||||
return;
|
||||
}
|
||||
|
||||
Room* room = new Room(jid, data);
|
||||
Room* room = new Room(acc, jid, data);
|
||||
connect(room, &Contact::requestArchive, this, &Roster::onElementRequestArchive);
|
||||
connect(room, &Contact::fileDownloadRequest, this, &Roster::fileDownloadRequest);
|
||||
connect(room, &Contact::unnoticedMessage, this, &Roster::unnoticedMessage);
|
||||
connect(room, &Contact::localPathInvalid, this, &Roster::localPathInvalid);
|
||||
rooms.insert(std::make_pair(id, room));
|
||||
acc->appendChild(room);
|
||||
}
|
||||
@ -974,3 +943,77 @@ QModelIndex Models::Roster::getGroupIndex(const QString& account, const QString&
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Models::Roster::onElementRequestArchive(const QString& before)
|
||||
{
|
||||
Element* el = static_cast<Element*>(sender());
|
||||
emit requestArchive(el->getAccountName(), el->getJid(), before);
|
||||
}
|
||||
|
||||
void Models::Roster::responseArchive(const QString& account, const QString& jid, const std::list<Shared::Message>& list, bool last)
|
||||
{
|
||||
ElId id(account, jid);
|
||||
Element* el = getElement(id);
|
||||
if (el != NULL) {
|
||||
el->responseArchive(list, last);
|
||||
}
|
||||
}
|
||||
|
||||
void Models::Roster::fileProgress(const std::list<Shared::MessageInfo>& msgs, qreal value, bool up)
|
||||
{
|
||||
for (const Shared::MessageInfo& info : msgs) {
|
||||
Element* el = getElement({info.account, info.jid});
|
||||
if (el != NULL) {
|
||||
el->fileProgress(info.messageId, value, up);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Models::Roster::fileComplete(const std::list<Shared::MessageInfo>& msgs, bool up)
|
||||
{
|
||||
for (const Shared::MessageInfo& info : msgs) {
|
||||
Element* el = getElement({info.account, info.jid});
|
||||
if (el != NULL) {
|
||||
el->fileComplete(info.messageId, up);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Models::Roster::fileError(const std::list<Shared::MessageInfo>& msgs, const QString& err, bool up)
|
||||
{
|
||||
for (const Shared::MessageInfo& info : msgs) {
|
||||
Element* el = getElement({info.account, info.jid});
|
||||
if (el != NULL) {
|
||||
el->fileError(info.messageId, err, up);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Models::Element * Models::Roster::getElement(const Models::Roster::ElId& id)
|
||||
{
|
||||
std::map<ElId, Contact*>::iterator cItr = contacts.find(id);
|
||||
|
||||
if (cItr != contacts.end()) {
|
||||
return cItr->second;
|
||||
} else {
|
||||
std::map<ElId, Room*>::iterator rItr = rooms.find(id);
|
||||
if (rItr != rooms.end()) {
|
||||
return rItr->second;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void Models::Roster::onAccountReconnected()
|
||||
{
|
||||
Account* acc = static_cast<Account*>(sender());
|
||||
|
||||
QString accName = acc->getName();
|
||||
for (const std::pair<const ElId, Contact*>& pair : contacts) {
|
||||
if (pair.first.account == accName) {
|
||||
pair.second->handleRecconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -26,6 +26,7 @@
|
||||
|
||||
#include "shared/message.h"
|
||||
#include "shared/global.h"
|
||||
#include "shared/messageinfo.h"
|
||||
#include "accounts.h"
|
||||
#include "item.h"
|
||||
#include "account.h"
|
||||
@ -58,7 +59,6 @@ public:
|
||||
void removePresence(const QString& account, const QString& jid, const QString& name);
|
||||
void addMessage(const QString& account, const Shared::Message& data);
|
||||
void changeMessage(const QString& account, const QString& jid, const QString& id, const QMap<QString, QVariant>& data);
|
||||
void dropMessages(const QString& account, const QString& jid);
|
||||
void addRoom(const QString& account, const QString jid, const QMap<QString, QVariant>& data);
|
||||
void changeRoom(const QString& account, const QString jid, const QMap<QString, QVariant>& data);
|
||||
void removeRoom(const QString& account, const QString jid);
|
||||
@ -81,18 +81,26 @@ public:
|
||||
Account* getAccount(const QString& name);
|
||||
QModelIndex getAccountIndex(const QString& name);
|
||||
QModelIndex getGroupIndex(const QString& account, const QString& name);
|
||||
void responseArchive(const QString& account, const QString& jid, const std::list<Shared::Message>& list, bool last);
|
||||
|
||||
void fileProgress(const std::list<Shared::MessageInfo>& msgs, qreal value, bool up);
|
||||
void fileError(const std::list<Shared::MessageInfo>& msgs, const QString& err, bool up);
|
||||
void fileComplete(const std::list<Shared::MessageInfo>& msgs, bool up);
|
||||
|
||||
Accounts* accountsModel;
|
||||
|
||||
signals:
|
||||
void requestArchive(const QString& account, const QString& jid, const QString& before);
|
||||
void fileDownloadRequest(const QString& url);
|
||||
void unnoticedMessage(const QString& account, const Shared::Message& msg);
|
||||
void localPathInvalid(const QString& path);
|
||||
|
||||
private:
|
||||
Item* root;
|
||||
std::map<QString, Account*> accounts;
|
||||
std::map<ElId, Group*> groups;
|
||||
std::map<ElId, Contact*> contacts;
|
||||
std::map<ElId, Room*> rooms;
|
||||
Element* getElement(const ElId& id);
|
||||
|
||||
private slots:
|
||||
void onAccountDataChanged(const QModelIndex& tl, const QModelIndex& br, const QVector<int>& roles);
|
||||
void onAccountReconnected();
|
||||
void onChildChanged(Models::Item* item, int row, int col);
|
||||
void onChildIsAboutToBeInserted(Item* parent, int first, int last);
|
||||
void onChildInserted();
|
||||
@ -100,6 +108,14 @@ private slots:
|
||||
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<QString, Account*> accounts;
|
||||
std::map<ElId, Group*> groups;
|
||||
std::map<ElId, Contact*> contacts;
|
||||
std::map<ElId, Room*> rooms;
|
||||
|
||||
public:
|
||||
class ElId {
|
||||
|
238
ui/squawk.cpp
@ -29,7 +29,6 @@ Squawk::Squawk(QWidget *parent) :
|
||||
conversations(),
|
||||
contextMenu(new QMenu()),
|
||||
dbus("org.freedesktop.Notifications", "/org/freedesktop/Notifications", "org.freedesktop.Notifications", QDBusConnection::sessionBus()),
|
||||
requestedFiles(),
|
||||
vCards(),
|
||||
requestedAccountsForPasswords(),
|
||||
prompt(0),
|
||||
@ -60,8 +59,12 @@ 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);
|
||||
|
||||
@ -336,17 +339,14 @@ void Squawk::onRosterItemDoubleClicked(const QModelIndex& item)
|
||||
Models::Account* acc = rosterModel.getAccount(id->account);
|
||||
Conversation* conv = 0;
|
||||
bool created = false;
|
||||
Models::Contact::Messages deque;
|
||||
if (itr != conversations.end()) {
|
||||
conv = itr->second;
|
||||
} else if (contact != 0) {
|
||||
created = true;
|
||||
conv = new Chat(acc, contact);
|
||||
contact->getMessages(deque);
|
||||
} else if (room != 0) {
|
||||
created = true;
|
||||
conv = new Room(acc, room);
|
||||
room->getMessages(deque);
|
||||
|
||||
if (!room->getJoined()) {
|
||||
emit setRoomJoined(id->account, id->name, true);
|
||||
@ -358,12 +358,6 @@ void Squawk::onRosterItemDoubleClicked(const QModelIndex& item)
|
||||
conv->setAttribute(Qt::WA_DeleteOnClose);
|
||||
subscribeConversation(conv);
|
||||
conversations.insert(std::make_pair(*id, conv));
|
||||
|
||||
if (created) {
|
||||
for (Models::Contact::Messages::const_iterator itr = deque.begin(), end = deque.end(); itr != end; ++itr) {
|
||||
conv->addMessage(*itr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
conv->show();
|
||||
@ -380,12 +374,6 @@ void Squawk::onRosterItemDoubleClicked(const QModelIndex& item)
|
||||
}
|
||||
}
|
||||
|
||||
void Squawk::onConversationShown()
|
||||
{
|
||||
Conversation* conv = static_cast<Conversation*>(sender());
|
||||
rosterModel.dropMessages(conv->getAccount(), conv->getJid());
|
||||
}
|
||||
|
||||
void Squawk::onConversationClosed(QObject* parent)
|
||||
{
|
||||
Conversation* conv = static_cast<Conversation*>(sender());
|
||||
@ -402,165 +390,41 @@ void Squawk::onConversationClosed(QObject* parent)
|
||||
}
|
||||
}
|
||||
|
||||
void Squawk::onConversationDownloadFile(const QString& messageId, const QString& url)
|
||||
void Squawk::fileProgress(const std::list<Shared::MessageInfo> msgs, qreal value, bool up)
|
||||
{
|
||||
Conversation* conv = static_cast<Conversation*>(sender());
|
||||
std::map<QString, std::set<Models::Roster::ElId>>::iterator itr = requestedFiles.find(messageId);
|
||||
bool created = false;
|
||||
if (itr == requestedFiles.end()) {
|
||||
itr = requestedFiles.insert(std::make_pair(messageId, std::set<Models::Roster::ElId>())).first;
|
||||
created = true;
|
||||
}
|
||||
itr->second.insert(Models::Roster::ElId(conv->getAccount(), conv->getJid()));
|
||||
if (created) {
|
||||
emit downloadFileRequest(messageId, url);
|
||||
}
|
||||
rosterModel.fileProgress(msgs, value, up);
|
||||
}
|
||||
|
||||
void Squawk::fileProgress(const QString& messageId, qreal value)
|
||||
void Squawk::fileDownloadComplete(const std::list<Shared::MessageInfo> msgs, const QString& path)
|
||||
{
|
||||
std::map<QString, std::set<Models::Roster::ElId>>::const_iterator itr = requestedFiles.find(messageId);
|
||||
if (itr == requestedFiles.end()) {
|
||||
qDebug() << "fileProgress in UI Squawk but there is nobody waiting for that id" << messageId << ", skipping";
|
||||
return;
|
||||
} else {
|
||||
const std::set<Models::Roster::ElId>& convs = itr->second;
|
||||
for (std::set<Models::Roster::ElId>::const_iterator cItr = convs.begin(), cEnd = convs.end(); cItr != cEnd; ++cItr) {
|
||||
const Models::Roster::ElId& id = *cItr;
|
||||
Conversations::const_iterator c = conversations.find(id);
|
||||
if (c != conversations.end()) {
|
||||
c->second->responseFileProgress(messageId, value);
|
||||
}
|
||||
if (currentConversation != 0 && currentConversation->getId() == id) {
|
||||
currentConversation->responseFileProgress(messageId, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
rosterModel.fileComplete(msgs, false);
|
||||
}
|
||||
|
||||
void Squawk::fileError(const QString& messageId, const QString& error)
|
||||
void Squawk::fileError(const std::list<Shared::MessageInfo> msgs, const QString& error, bool up)
|
||||
{
|
||||
std::map<QString, std::set<Models::Roster::ElId>>::const_iterator itr = requestedFiles.find(messageId);
|
||||
if (itr == requestedFiles.end()) {
|
||||
qDebug() << "fileError in UI Squawk but there is nobody waiting for that id" << messageId << ", skipping";
|
||||
return;
|
||||
} else {
|
||||
const std::set<Models::Roster::ElId>& convs = itr->second;
|
||||
for (std::set<Models::Roster::ElId>::const_iterator cItr = convs.begin(), cEnd = convs.end(); cItr != cEnd; ++cItr) {
|
||||
const Models::Roster::ElId& id = *cItr;
|
||||
Conversations::const_iterator c = conversations.find(id);
|
||||
if (c != conversations.end()) {
|
||||
c->second->fileError(messageId, error);
|
||||
}
|
||||
if (currentConversation != 0 && currentConversation->getId() == id) {
|
||||
currentConversation->fileError(messageId, error);
|
||||
}
|
||||
}
|
||||
requestedFiles.erase(itr);
|
||||
}
|
||||
rosterModel.fileError(msgs, error, up);
|
||||
}
|
||||
|
||||
void Squawk::fileLocalPathResponse(const QString& messageId, const QString& path)
|
||||
void Squawk::fileUploadComplete(const std::list<Shared::MessageInfo> msgs, const QString& path)
|
||||
{
|
||||
std::map<QString, std::set<Models::Roster::ElId>>::const_iterator itr = requestedFiles.find(messageId);
|
||||
if (itr == requestedFiles.end()) {
|
||||
qDebug() << "fileLocalPathResponse in UI Squawk but there is nobody waiting for that path, skipping";
|
||||
return;
|
||||
} else {
|
||||
const std::set<Models::Roster::ElId>& convs = itr->second;
|
||||
for (std::set<Models::Roster::ElId>::const_iterator cItr = convs.begin(), cEnd = convs.end(); cItr != cEnd; ++cItr) {
|
||||
const Models::Roster::ElId& id = *cItr;
|
||||
Conversations::const_iterator c = conversations.find(id);
|
||||
if (c != conversations.end()) {
|
||||
c->second->responseLocalFile(messageId, path);
|
||||
}
|
||||
if (currentConversation != 0 && currentConversation->getId() == id) {
|
||||
currentConversation->responseLocalFile(messageId, path);
|
||||
}
|
||||
}
|
||||
|
||||
requestedFiles.erase(itr);
|
||||
}
|
||||
}
|
||||
|
||||
void Squawk::onConversationRequestLocalFile(const QString& messageId, const QString& url)
|
||||
{
|
||||
Conversation* conv = static_cast<Conversation*>(sender());
|
||||
std::map<QString, std::set<Models::Roster::ElId>>::iterator itr = requestedFiles.find(messageId);
|
||||
bool created = false;
|
||||
if (itr == requestedFiles.end()) {
|
||||
itr = requestedFiles.insert(std::make_pair(messageId, std::set<Models::Roster::ElId>())).first;
|
||||
created = true;
|
||||
}
|
||||
itr->second.insert(Models::Roster::ElId(conv->getAccount(), conv->getJid()));
|
||||
if (created) {
|
||||
emit fileLocalPathRequest(messageId, url);
|
||||
}
|
||||
rosterModel.fileComplete(msgs, true);
|
||||
}
|
||||
|
||||
void Squawk::accountMessage(const QString& account, const Shared::Message& data)
|
||||
{
|
||||
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()) {
|
||||
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);
|
||||
notify(account, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Squawk::changeMessage(const QString& account, const QString& jid, const QString& id, const QMap<QString, QVariant>& data)
|
||||
{
|
||||
Models::Roster::ElId eid({account, jid});
|
||||
bool found = false;
|
||||
|
||||
if (currentConversation != 0 && currentConversation->getId() == eid) {
|
||||
currentConversation->changeMessage(id, data);
|
||||
QApplication::alert(this);
|
||||
found = true;
|
||||
}
|
||||
|
||||
Conversations::iterator itr = conversations.find(eid);
|
||||
if (itr != conversations.end()) {
|
||||
Conversation* conv = itr->second;
|
||||
conv->changeMessage(id, data);
|
||||
if (!found && conv->isMinimized()) {
|
||||
rosterModel.changeMessage(account, jid, id, data);
|
||||
}
|
||||
found = true;
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
rosterModel.changeMessage(account, jid, id, data);
|
||||
}
|
||||
}
|
||||
|
||||
void Squawk::notify(const QString& account, const Shared::Message& msg)
|
||||
{
|
||||
@ -596,60 +460,29 @@ void Squawk::notify(const QString& account, const Shared::Message& msg)
|
||||
void Squawk::onConversationMessage(const Shared::Message& msg)
|
||||
{
|
||||
Conversation* conv = static_cast<Conversation*>(sender());
|
||||
emit sendMessage(conv->getAccount(), msg);
|
||||
Models::Roster::ElId id = conv->getId();
|
||||
QString acc = conv->getAccount();
|
||||
|
||||
if (currentConversation != 0 && currentConversation->getId() == id) {
|
||||
if (conv == currentConversation) {
|
||||
Conversations::iterator itr = conversations.find(id);
|
||||
if (itr != conversations.end()) {
|
||||
itr->second->addMessage(msg);
|
||||
}
|
||||
} else {
|
||||
currentConversation->addMessage(msg);
|
||||
}
|
||||
}
|
||||
rosterModel.addMessage(acc, msg);
|
||||
emit sendMessage(acc, msg);
|
||||
}
|
||||
|
||||
void Squawk::onConversationMessage(const Shared::Message& msg, const QString& path)
|
||||
void Squawk::onConversationResend(const QString& id)
|
||||
{
|
||||
Conversation* conv = static_cast<Conversation*>(sender());
|
||||
Models::Roster::ElId id = conv->getId();
|
||||
std::map<QString, std::set<Models::Roster::ElId>>::iterator itr = requestedFiles.insert(std::make_pair(msg.getId(), std::set<Models::Roster::ElId>())).first;
|
||||
itr->second.insert(id);
|
||||
QString acc = conv->getAccount();
|
||||
QString jid = conv->getJid();
|
||||
|
||||
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 resendMessage(acc, jid, id);
|
||||
}
|
||||
|
||||
emit sendMessage(conv->getAccount(), msg, path);
|
||||
}
|
||||
|
||||
void Squawk::onConversationRequestArchive(const QString& before)
|
||||
void Squawk::onRequestArchive(const QString& account, const QString& jid, const QString& before)
|
||||
{
|
||||
Conversation* conv = static_cast<Conversation*>(sender());
|
||||
requestArchive(conv->getAccount(), conv->getJid(), 20, before); //TODO amount as a settings value
|
||||
emit requestArchive(account, jid, 20, before); //TODO amount as a settings value
|
||||
}
|
||||
|
||||
void Squawk::responseArchive(const QString& account, const QString& jid, const std::list<Shared::Message>& list)
|
||||
void Squawk::responseArchive(const QString& account, const QString& jid, const std::list<Shared::Message>& list, bool 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);
|
||||
}
|
||||
rosterModel.responseArchive(account, jid, list, last);
|
||||
}
|
||||
|
||||
void Squawk::removeAccount(const QString& account)
|
||||
@ -661,8 +494,6 @@ void Squawk::removeAccount(const QString& account)
|
||||
++itr;
|
||||
Conversation* conv = lItr->second;
|
||||
disconnect(conv, &Conversation::destroyed, this, &Squawk::onConversationClosed);
|
||||
disconnect(conv, &Conversation::requestArchive, this, &Squawk::onConversationRequestArchive);
|
||||
disconnect(conv, &Conversation::shown, this, &Squawk::onConversationShown);
|
||||
conv->close();
|
||||
conversations.erase(lItr);
|
||||
} else {
|
||||
@ -926,7 +757,6 @@ void Squawk::onActivateVCard(const QString& account, const QString& jid, bool ed
|
||||
{
|
||||
std::map<QString, VCard*>::const_iterator itr = vCards.find(jid);
|
||||
VCard* card;
|
||||
Models::Contact::Messages deque;
|
||||
if (itr != vCards.end()) {
|
||||
card = itr->second;
|
||||
} else {
|
||||
@ -1092,13 +922,9 @@ void Squawk::onPasswordPromptRejected()
|
||||
void Squawk::subscribeConversation(Conversation* conv)
|
||||
{
|
||||
connect(conv, &Conversation::destroyed, this, &Squawk::onConversationClosed);
|
||||
connect(conv, qOverload<const Shared::Message&>(&Conversation::sendMessage), this, qOverload<const Shared::Message&>(&Squawk::onConversationMessage));
|
||||
connect(conv, qOverload<const Shared::Message&, const QString&>(&Conversation::sendMessage),
|
||||
this, qOverload<const Shared::Message&, const QString&>(&Squawk::onConversationMessage));
|
||||
connect(conv, &Conversation::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);
|
||||
connect(conv, &Conversation::sendMessage, this, &Squawk::onConversationMessage);
|
||||
connect(conv, &Conversation::resendMessage, this, &Squawk::onConversationResend);
|
||||
connect(conv, &Conversation::notifyableMessage, this, &Squawk::notify);
|
||||
}
|
||||
|
||||
void Squawk::onRosterSelectionChanged(const QModelIndex& current, const QModelIndex& previous)
|
||||
@ -1168,13 +994,10 @@ void Squawk::onRosterSelectionChanged(const QModelIndex& current, const QModelIn
|
||||
}
|
||||
|
||||
Models::Account* acc = rosterModel.getAccount(id->account);
|
||||
Models::Contact::Messages deque;
|
||||
if (contact != 0) {
|
||||
currentConversation = new Chat(acc, contact);
|
||||
contact->getMessages(deque);
|
||||
} else if (room != 0) {
|
||||
currentConversation = new Room(acc, room);
|
||||
room->getMessages(deque);
|
||||
|
||||
if (!room->getJoined()) {
|
||||
emit setRoomJoined(id->account, id->name, true);
|
||||
@ -1185,9 +1008,6 @@ void Squawk::onRosterSelectionChanged(const QModelIndex& current, const QModelIn
|
||||
}
|
||||
|
||||
subscribeConversation(currentConversation);
|
||||
for (Models::Contact::Messages::const_iterator itr = deque.begin(), end = deque.end(); itr != end; ++itr) {
|
||||
currentConversation->addMessage(*itr);
|
||||
}
|
||||
|
||||
if (res.size() > 0) {
|
||||
currentConversation->setPalResource(res);
|
||||
|
29
ui/squawk.h
@ -39,7 +39,7 @@
|
||||
#include "models/roster.h"
|
||||
#include "widgets/vcard/vcard.h"
|
||||
|
||||
#include "shared.h"
|
||||
#include "shared/shared.h"
|
||||
|
||||
namespace Ui {
|
||||
class Squawk;
|
||||
@ -63,7 +63,7 @@ signals:
|
||||
void disconnectAccount(const QString&);
|
||||
void changeState(Shared::Availability state);
|
||||
void sendMessage(const QString& account, const Shared::Message& data);
|
||||
void sendMessage(const QString& account, const Shared::Message& data, const QString& path);
|
||||
void resendMessage(const QString& account, const QString& jid, const QString& id);
|
||||
void requestArchive(const QString& account, const QString& jid, int count, const QString& before);
|
||||
void subscribeContact(const QString& account, const QString& jid, const QString& reason);
|
||||
void unsubscribeContact(const QString& account, const QString& jid, const QString& reason);
|
||||
@ -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 fileLocalPathRequest(const QString& messageId, const QString& url);
|
||||
void downloadFileRequest(const QString& messageId, const QString& url);
|
||||
void fileDownloadRequest(const QString& url);
|
||||
void requestVCard(const QString& account, const QString& jid);
|
||||
void uploadVCard(const QString& account, const Shared::VCard& card);
|
||||
void responsePassword(const QString& account, const QString& password);
|
||||
void localPathInvalid(const QString& path);
|
||||
|
||||
public slots:
|
||||
void readSettings();
|
||||
@ -97,16 +97,17 @@ public slots:
|
||||
void removePresence(const QString& account, const QString& jid, const QString& name);
|
||||
void stateChanged(Shared::Availability state);
|
||||
void accountMessage(const QString& account, const Shared::Message& data);
|
||||
void responseArchive(const QString& account, const QString& jid, const std::list<Shared::Message>& list);
|
||||
void responseArchive(const QString& account, const QString& jid, const std::list<Shared::Message>& list, bool last);
|
||||
void addRoom(const QString& account, const QString jid, const QMap<QString, QVariant>& data);
|
||||
void changeRoom(const QString& account, const QString jid, const QMap<QString, QVariant>& data);
|
||||
void removeRoom(const QString& account, const QString jid);
|
||||
void addRoomParticipant(const QString& account, const QString& jid, const QString& name, const QMap<QString, QVariant>& data);
|
||||
void changeRoomParticipant(const QString& account, const QString& jid, const QString& name, const QMap<QString, QVariant>& data);
|
||||
void removeRoomParticipant(const QString& account, const QString& jid, const QString& name);
|
||||
void fileLocalPathResponse(const QString& messageId, const QString& path);
|
||||
void fileError(const QString& messageId, const QString& error);
|
||||
void fileProgress(const QString& messageId, qreal value);
|
||||
void fileError(const std::list<Shared::MessageInfo> msgs, const QString& error, bool up);
|
||||
void fileProgress(const std::list<Shared::MessageInfo> msgs, qreal value, bool up);
|
||||
void fileDownloadComplete(const std::list<Shared::MessageInfo> msgs, const QString& path);
|
||||
void fileUploadComplete(const std::list<Shared::MessageInfo> msgs, const QString& path);
|
||||
void responseVCard(const QString& jid, const Shared::VCard& card);
|
||||
void changeMessage(const QString& account, const QString& jid, const QString& id, const QMap<QString, QVariant>& data);
|
||||
void requestPassword(const QString& account);
|
||||
@ -120,7 +121,6 @@ private:
|
||||
Conversations conversations;
|
||||
QMenu* contextMenu;
|
||||
QDBusInterface dbus;
|
||||
std::map<QString, std::set<Models::Roster::ElId>> requestedFiles;
|
||||
std::map<QString, VCard*> vCards;
|
||||
std::deque<QString> requestedAccountsForPasswords;
|
||||
QInputDialog* prompt;
|
||||
@ -130,6 +130,8 @@ private:
|
||||
|
||||
protected:
|
||||
void closeEvent(QCloseEvent * event) override;
|
||||
|
||||
protected slots:
|
||||
void notify(const QString& account, const Shared::Message& msg);
|
||||
|
||||
private slots:
|
||||
@ -147,18 +149,17 @@ private slots:
|
||||
void onComboboxActivated(int index);
|
||||
void onRosterItemDoubleClicked(const QModelIndex& item);
|
||||
void onConversationMessage(const Shared::Message& msg);
|
||||
void onConversationMessage(const Shared::Message& msg, const QString& path);
|
||||
void onConversationRequestArchive(const QString& before);
|
||||
void onConversationResend(const QString& id);
|
||||
void onRequestArchive(const QString& account, const QString& jid, const QString& before);
|
||||
void onRosterContextMenu(const QPoint& point);
|
||||
void onConversationShown();
|
||||
void onConversationRequestLocalFile(const QString& messageId, const QString& url);
|
||||
void onConversationDownloadFile(const QString& messageId, const QString& url);
|
||||
void onItemCollepsed(const QModelIndex& index);
|
||||
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();
|
||||
|
18
ui/utils/CMakeLists.txt
Normal file
@ -0,0 +1,18 @@
|
||||
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
|
||||
)
|
@ -37,7 +37,7 @@ QWidget* ComboboxDelegate::createEditor(QWidget *parent, const QStyleOptionViewI
|
||||
{
|
||||
QComboBox *cb = new QComboBox(parent);
|
||||
|
||||
for (const std::pair<QString, QIcon> pair : entries) {
|
||||
for (const std::pair<QString, QIcon>& pair : entries) {
|
||||
cb->addItem(pair.second, pair.first);
|
||||
}
|
||||
|
||||
|
@ -1,93 +0,0 @@
|
||||
/*
|
||||
* Squawk messenger.
|
||||
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef DROPSHADOWEFFECT_H
|
||||
#define DROPSHADOWEFFECT_H
|
||||
|
||||
#include <QGraphicsEffect>
|
||||
#include <QPainter>
|
||||
#include <QPointF>
|
||||
#include <QColor>
|
||||
|
||||
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
|
@ -16,8 +16,7 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "dropshadoweffect.h"
|
||||
#include "QtMath"
|
||||
#include "exponentialblur.h"
|
||||
|
||||
static const int tileSize = 32;
|
||||
template <class T>
|
||||
@ -574,128 +573,7 @@ void expblur(QImage &img, qreal radius, bool improvedQuality = false, int transp
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
void Utils::exponentialblur(QImage& img, qreal radius, bool improvedQuality, int 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);
|
||||
expblur<12, 10, false>(img, radius, improvedQuality, transposed);
|
||||
}
|
34
ui/utils/exponentialblur.h
Normal file
@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Squawk messenger.
|
||||
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef EXPONENTIALBLUR_H
|
||||
#define EXPONENTIALBLUR_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QImage>
|
||||
#include <QtMath>
|
||||
|
||||
/**
|
||||
* @todo write docs
|
||||
*/
|
||||
|
||||
namespace Utils {
|
||||
void exponentialblur(QImage &img, qreal radius, bool improvedQuality = false, int transposed = 0);
|
||||
};
|
||||
|
||||
#endif // EXPONENTIALBLUR_H
|
91
ui/utils/shadowoverlay.cpp
Normal file
@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Squawk messenger.
|
||||
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "shadowoverlay.h"
|
||||
|
||||
ShadowOverlay::ShadowOverlay(unsigned int r, unsigned int t, const QColor& c, QWidget* parent):
|
||||
QWidget(parent),
|
||||
top(false),
|
||||
right(false),
|
||||
bottom(false),
|
||||
left(false),
|
||||
thickness(t),
|
||||
radius(r),
|
||||
color(c),
|
||||
shadow(1, 1, QImage::Format_ARGB32_Premultiplied)
|
||||
{
|
||||
setAttribute(Qt::WA_NoSystemBackground);
|
||||
setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
}
|
||||
|
||||
void ShadowOverlay::paintEvent(QPaintEvent* event)
|
||||
{
|
||||
QWidget::paintEvent(event);
|
||||
|
||||
QPainter painter(this);
|
||||
|
||||
painter.drawImage(0, 0, shadow);
|
||||
}
|
||||
|
||||
void ShadowOverlay::resizeEvent(QResizeEvent* event)
|
||||
{
|
||||
QWidget::resizeEvent(event);
|
||||
|
||||
updateImage();
|
||||
}
|
||||
|
||||
void ShadowOverlay::updateImage()
|
||||
{
|
||||
int w = width();
|
||||
int h = height();
|
||||
shadow = QImage({w, h + int(thickness)}, QImage::Format_ARGB32_Premultiplied);
|
||||
shadow.fill(0);
|
||||
|
||||
QPainter tmpPainter(&shadow);
|
||||
tmpPainter.setCompositionMode(QPainter::CompositionMode_Source);
|
||||
if (top) {
|
||||
QRectF shadow(0, 0, w, thickness);
|
||||
tmpPainter.fillRect(shadow, color);
|
||||
}
|
||||
if (right) {
|
||||
QRectF shadow(w - thickness, 0, thickness, h);
|
||||
tmpPainter.fillRect(shadow, color);
|
||||
}
|
||||
if (bottom) {
|
||||
QRectF shadow(0, h - thickness, w, thickness * 2); //i have no idea why, but it leaves some unpainted stripe without some spare space
|
||||
tmpPainter.fillRect(shadow, color);
|
||||
}
|
||||
if (left) {
|
||||
QRectF shadow(0, 0, thickness, h);
|
||||
tmpPainter.fillRect(shadow, color);
|
||||
}
|
||||
|
||||
Utils::exponentialblur(shadow, radius, false, 0);
|
||||
tmpPainter.end();
|
||||
}
|
||||
|
||||
void ShadowOverlay::setFrames(bool t, bool r, bool b, bool l)
|
||||
{
|
||||
top = t;
|
||||
right = r;
|
||||
bottom = b;
|
||||
left = l;
|
||||
|
||||
updateImage();
|
||||
update();
|
||||
}
|
58
ui/utils/shadowoverlay.h
Normal file
@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Squawk messenger.
|
||||
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef SHADOWOVERLAY_H
|
||||
#define SHADOWOVERLAY_H
|
||||
|
||||
#include <QWidget>
|
||||
#include <QImage>
|
||||
#include <QPainter>
|
||||
#include <QColor>
|
||||
#include <QPaintEvent>
|
||||
#include <QResizeEvent>
|
||||
|
||||
#include <ui/utils/exponentialblur.h>
|
||||
|
||||
/**
|
||||
* @todo write docs
|
||||
*/
|
||||
class ShadowOverlay : public QWidget {
|
||||
|
||||
public:
|
||||
ShadowOverlay(unsigned int radius = 10, unsigned int thickness = 1, const QColor& color = Qt::black, QWidget* parent = nullptr);
|
||||
|
||||
void setFrames(bool top, bool right, bool bottom, bool left);
|
||||
|
||||
protected:
|
||||
void updateImage();
|
||||
|
||||
void paintEvent(QPaintEvent * event) override;
|
||||
void resizeEvent(QResizeEvent * event) override;
|
||||
|
||||
private:
|
||||
bool top;
|
||||
bool right;
|
||||
bool bottom;
|
||||
bool left;
|
||||
unsigned int thickness;
|
||||
unsigned int radius;
|
||||
QColor color;
|
||||
QImage shadow;
|
||||
};
|
||||
|
||||
#endif // SHADOWOVERLAY_H
|
@ -1,29 +1,24 @@
|
||||
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)
|
||||
|
||||
set(squawkWidgets_SRC
|
||||
conversation.cpp
|
||||
chat.cpp
|
||||
room.cpp
|
||||
newcontact.cpp
|
||||
accounts.cpp
|
||||
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
|
||||
)
|
||||
|
||||
# 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)
|
||||
add_subdirectory(vcard)
|
||||
add_subdirectory(messageline)
|
||||
|
@ -19,7 +19,7 @@
|
||||
#include "chat.h"
|
||||
|
||||
Chat::Chat(Models::Account* acc, Models::Contact* p_contact, QWidget* parent):
|
||||
Conversation(false, acc, p_contact->getJid(), "", parent),
|
||||
Conversation(false, acc, p_contact, p_contact->getJid(), "", parent),
|
||||
contact(p_contact)
|
||||
{
|
||||
setName(p_contact->getContactName());
|
||||
@ -71,31 +71,14 @@ Shared::Message Chat::createMessage() const
|
||||
return msg;
|
||||
}
|
||||
|
||||
void Chat::addMessage(const Shared::Message& data)
|
||||
void Chat::onMessage(const Shared::Message& data)
|
||||
{
|
||||
Conversation::addMessage(data);
|
||||
Conversation::onMessage(data);
|
||||
|
||||
if (!data.getOutgoing()) { //TODO need to check if that was the last message
|
||||
if (!data.getOutgoing()) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -35,15 +35,12 @@ public:
|
||||
Chat(Models::Account* acc, Models::Contact* p_contact, QWidget* parent = 0);
|
||||
~Chat();
|
||||
|
||||
void addMessage(const Shared::Message & data) override;
|
||||
void setAvatar(const QString& path) override;
|
||||
|
||||
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();
|
||||
|
@ -18,7 +18,6 @@
|
||||
|
||||
#include "conversation.h"
|
||||
#include "ui_conversation.h"
|
||||
#include "ui/utils/dropshadoweffect.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QScrollBar>
|
||||
@ -29,31 +28,44 @@
|
||||
#include <QAbstractTextDocumentLayout>
|
||||
#include <QCoreApplication>
|
||||
|
||||
Conversation::Conversation(bool muc, Models::Account* acc, const QString pJid, const QString pRes, QWidget* parent):
|
||||
Conversation::Conversation(bool muc, Models::Account* acc, Models::Element* el, const QString pJid, const QString pRes, QWidget* parent):
|
||||
QWidget(parent),
|
||||
isMuc(muc),
|
||||
account(acc),
|
||||
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(),
|
||||
scroll(down),
|
||||
feed(new FeedView()),
|
||||
delegate(new MessageDelegate(this)),
|
||||
manualSliderChange(false),
|
||||
requestingHistory(false),
|
||||
everShown(false),
|
||||
tsb(QApplication::style()->styleHint(QStyle::SH_ScrollBar_Transient) == 1)
|
||||
tsb(QApplication::style()->styleHint(QStyle::SH_ScrollBar_Transient) == 1),
|
||||
shadow(10, 1, Qt::black, this),
|
||||
contextMenu(new QMenu())
|
||||
{
|
||||
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);
|
||||
@ -63,14 +75,7 @@ Conversation::Conversation(bool muc, Models::Account* acc, const QString pJid, c
|
||||
statusLabel = m_ui->statusLabel;
|
||||
|
||||
connect(&ker, &KeyEnterReceiver::enterPressed, this, &Conversation::onEnterPressed);
|
||||
connect(&scrollResizeCatcher, &Resizer::resized, this, &Conversation::onScrollResize);
|
||||
connect(&vis, &VisibilityCatcher::shown, this, &Conversation::onScrollResize);
|
||||
connect(&vis, &VisibilityCatcher::hidden, this, &Conversation::onScrollResize);
|
||||
connect(m_ui->sendButton, &QPushButton::clicked, this, &Conversation::onEnterPressed);
|
||||
connect(line, &MessageLine::resize, this, &Conversation::onMessagesResize);
|
||||
connect(line, &MessageLine::downloadFile, this, &Conversation::downloadFile);
|
||||
connect(line, &MessageLine::uploadFile, this, qOverload<const Shared::Message&, const QString&>(&Conversation::sendMessage));
|
||||
connect(line, &MessageLine::requestLocalFile, this, &Conversation::requestLocalFile);
|
||||
connect(m_ui->attachButton, &QPushButton::clicked, this, &Conversation::onAttach);
|
||||
connect(m_ui->clearButton, &QPushButton::clicked, this, &Conversation::onClearButton);
|
||||
connect(m_ui->messageEditor->document()->documentLayout(), &QAbstractTextDocumentLayout::documentSizeChanged,
|
||||
@ -78,23 +83,43 @@ Conversation::Conversation(bool muc, Models::Account* acc, const QString pJid, c
|
||||
|
||||
m_ui->messageEditor->installEventFilter(&ker);
|
||||
|
||||
QScrollBar* vs = m_ui->scrollArea->verticalScrollBar();
|
||||
m_ui->scrollArea->setWidget(line);
|
||||
vs->installEventFilter(&vis);
|
||||
|
||||
line->setAutoFillBackground(false);
|
||||
if (testAttribute(Qt::WA_TranslucentBackground)) {
|
||||
m_ui->scrollArea->setAutoFillBackground(false);
|
||||
} else {
|
||||
m_ui->scrollArea->setBackgroundRole(QPalette::Base);
|
||||
//line->setAutoFillBackground(false);
|
||||
//if (testAttribute(Qt::WA_TranslucentBackground)) {
|
||||
//m_ui->scrollArea->setAutoFillBackground(false);
|
||||
//} else {
|
||||
//m_ui->scrollArea->setBackgroundRole(QPalette::Base);
|
||||
//}
|
||||
|
||||
//line->setMyAvatarPath(acc->getAvatarPath());
|
||||
//line->setMyName(acc->getName());
|
||||
|
||||
initializeOverlay();
|
||||
}
|
||||
|
||||
connect(vs, &QScrollBar::valueChanged, this, &Conversation::onSliderValueChanged);
|
||||
m_ui->scrollArea->installEventFilter(&scrollResizeCatcher);
|
||||
Conversation::~Conversation()
|
||||
{
|
||||
delete contextMenu;
|
||||
|
||||
line->setMyAvatarPath(acc->getAvatarPath());
|
||||
line->setMyName(acc->getName());
|
||||
element->feed->decrementObservers();
|
||||
}
|
||||
|
||||
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;
|
||||
//}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Conversation::initializeOverlay()
|
||||
{
|
||||
QGridLayout* gr = static_cast<QGridLayout*>(layout());
|
||||
QLabel* progressLabel = new QLabel(tr("Drop files here to attach them to your message"));
|
||||
gr->addWidget(overlay, 0, 0, 2, 1);
|
||||
@ -115,36 +140,6 @@ Conversation::Conversation(bool muc, Models::Account* acc, const QString pJid, c
|
||||
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)
|
||||
@ -163,22 +158,6 @@ QString Conversation::getJid() const
|
||||
return palJid;
|
||||
}
|
||||
|
||||
void Conversation::addMessage(const Shared::Message& data)
|
||||
{
|
||||
int pos = m_ui->scrollArea->verticalScrollBar()->sliderPosition();
|
||||
int max = m_ui->scrollArea->verticalScrollBar()->maximum();
|
||||
|
||||
MessageLine::Position place = line->message(data);
|
||||
if (place == MessageLine::invalid) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void Conversation::changeMessage(const QString& id, const QMap<QString, QVariant>& data)
|
||||
{
|
||||
line->changeMessage(id, data);
|
||||
}
|
||||
|
||||
KeyEnterReceiver::KeyEnterReceiver(QObject* parent): QObject(parent), ownEvent(false) {}
|
||||
|
||||
bool KeyEnterReceiver::eventFilter(QObject* obj, QEvent* event)
|
||||
@ -226,93 +205,19 @@ void Conversation::onEnterPressed()
|
||||
m_ui->messageEditor->clear();
|
||||
Shared::Message msg = createMessage();
|
||||
msg.setBody(body);
|
||||
addMessage(msg);
|
||||
emit sendMessage(msg);
|
||||
}
|
||||
if (filesToAttach.size() > 0) {
|
||||
for (Badge* badge : filesToAttach) {
|
||||
Shared::Message msg = createMessage();
|
||||
line->appendMessageWithUpload(msg, badge->id);
|
||||
usleep(1000); //this is required for the messages not to have equal time when appending into messageline
|
||||
msg.setAttachPath(badge->id);
|
||||
element->feed->registerUpload(msg.getId());
|
||||
emit sendMessage(msg);
|
||||
}
|
||||
clearAttachedFiles();
|
||||
}
|
||||
}
|
||||
|
||||
void Conversation::appendMessageWithUpload(const Shared::Message& data, const QString& path)
|
||||
{
|
||||
line->appendMessageWithUploadNoSiganl(data, path);
|
||||
}
|
||||
|
||||
void Conversation::onMessagesResize(int amount)
|
||||
{
|
||||
manualSliderChange = true;
|
||||
switch (scroll) {
|
||||
case down:
|
||||
m_ui->scrollArea->verticalScrollBar()->setValue(m_ui->scrollArea->verticalScrollBar()->maximum());
|
||||
break;
|
||||
case keep: {
|
||||
int max = m_ui->scrollArea->verticalScrollBar()->maximum();
|
||||
int value = m_ui->scrollArea->verticalScrollBar()->value() + amount;
|
||||
m_ui->scrollArea->verticalScrollBar()->setValue(value);
|
||||
|
||||
if (value == max) {
|
||||
scroll = down;
|
||||
} else {
|
||||
scroll = nothing;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
manualSliderChange = false;
|
||||
}
|
||||
|
||||
void Conversation::onSliderValueChanged(int value)
|
||||
{
|
||||
if (!manualSliderChange) {
|
||||
if (value == m_ui->scrollArea->verticalScrollBar()->maximum()) {
|
||||
scroll = down;
|
||||
} else {
|
||||
if (!requestingHistory && value == 0) {
|
||||
requestingHistory = true;
|
||||
line->showBusyIndicator();
|
||||
emit requestArchive(line->firstMessageId());
|
||||
scroll = keep;
|
||||
} else {
|
||||
scroll = nothing;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Conversation::responseArchive(const std::list<Shared::Message> list)
|
||||
{
|
||||
requestingHistory = false;
|
||||
scroll = keep;
|
||||
|
||||
line->hideBusyIndicator();
|
||||
for (std::list<Shared::Message>::const_iterator itr = list.begin(), end = list.end(); itr != end; ++itr) {
|
||||
addMessage(*itr);
|
||||
}
|
||||
}
|
||||
|
||||
void Conversation::showEvent(QShowEvent* event)
|
||||
{
|
||||
if (!everShown) {
|
||||
everShown = true;
|
||||
line->showBusyIndicator();
|
||||
requestingHistory = true;
|
||||
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"));
|
||||
@ -340,34 +245,6 @@ void Conversation::setStatus(const QString& status)
|
||||
statusLabel->setText(Shared::processMessageBody(status));
|
||||
}
|
||||
|
||||
void Conversation::onScrollResize()
|
||||
{
|
||||
if (everShown) {
|
||||
int size = m_ui->scrollArea->width();
|
||||
QScrollBar* bar = m_ui->scrollArea->verticalScrollBar();
|
||||
if (bar->isVisible() && !tsb) {
|
||||
size -= bar->width();
|
||||
|
||||
}
|
||||
line->setMaximumWidth(size);
|
||||
}
|
||||
}
|
||||
|
||||
void Conversation::responseFileProgress(const QString& messageId, qreal progress)
|
||||
{
|
||||
line->fileProgress(messageId, progress);
|
||||
}
|
||||
|
||||
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()};
|
||||
@ -444,7 +321,7 @@ void Conversation::onTextEditDocSizeChanged(const QSizeF& size)
|
||||
|
||||
void Conversation::setFeedFrames(bool top, bool right, bool bottom, bool left)
|
||||
{
|
||||
static_cast<DropShadowEffect*>(m_ui->scrollArea->graphicsEffect())->setFrame(top, right, bottom, left);
|
||||
shadow.setFrames(top, right, bottom, left);
|
||||
}
|
||||
|
||||
void Conversation::dragEnterEvent(QDragEnterEvent* event)
|
||||
@ -504,21 +381,65 @@ Shared::Message Conversation::createMessage() const
|
||||
return msg;
|
||||
}
|
||||
|
||||
bool VisibilityCatcher::eventFilter(QObject* obj, QEvent* event)
|
||||
void Conversation::onFeedMessage(const Shared::Message& msg)
|
||||
{
|
||||
if (event->type() == QEvent::Show) {
|
||||
emit shown();
|
||||
this->onMessage(msg);
|
||||
}
|
||||
|
||||
if (event->type() == QEvent::Hide) {
|
||||
emit hidden();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
VisibilityCatcher::VisibilityCatcher(QWidget* parent):
|
||||
QObject(parent)
|
||||
void Conversation::onMessage(const Shared::Message& msg)
|
||||
{
|
||||
if (!msg.getForwarded()) {
|
||||
QApplication::alert(this);
|
||||
if (window()->windowState().testFlag(Qt::WindowMinimized)) {
|
||||
emit notifyableMessage(getAccount(), msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Conversation::positionShadow()
|
||||
{
|
||||
int w = width();
|
||||
int h = feed->height();
|
||||
|
||||
shadow.resize(w, h);
|
||||
shadow.move(feed->pos());
|
||||
shadow.raise();
|
||||
}
|
||||
|
||||
void Conversation::onFeedContext(const QPoint& pos)
|
||||
{
|
||||
QModelIndex index = feed->indexAt(pos);
|
||||
if (index.isValid()) {
|
||||
Shared::Message* item = static_cast<Shared::Message*>(index.internalPointer());
|
||||
|
||||
contextMenu->clear();
|
||||
bool showMenu = false;
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -24,18 +24,26 @@
|
||||
#include <QMap>
|
||||
#include <QMimeData>
|
||||
#include <QFileInfo>
|
||||
#include <QGraphicsOpacityEffect>
|
||||
#include <QMenu>
|
||||
#include <QAction>
|
||||
#include <QDesktopServices>
|
||||
|
||||
#include "shared/message.h"
|
||||
#include "order.h"
|
||||
#include "ui/models/account.h"
|
||||
#include "ui/models/roster.h"
|
||||
#include "ui/utils/messageline.h"
|
||||
#include "ui/utils/resizer.h"
|
||||
#include "ui/utils/flowlayout.h"
|
||||
#include "ui/utils/badge.h"
|
||||
#include "shared/order.h"
|
||||
#include "shared/icons.h"
|
||||
#include "shared/utils.h"
|
||||
|
||||
#include "ui/models/account.h"
|
||||
#include "ui/models/roster.h"
|
||||
|
||||
#include "ui/utils/flowlayout.h"
|
||||
#include "ui/utils/badge.h"
|
||||
#include "ui/utils/shadowoverlay.h"
|
||||
|
||||
#include "ui/widgets/messageline/feedview.h"
|
||||
#include "ui/widgets/messageline/messagedelegate.h"
|
||||
|
||||
namespace Ui
|
||||
{
|
||||
class Conversation;
|
||||
@ -54,54 +62,33 @@ signals:
|
||||
void enterPressed();
|
||||
};
|
||||
|
||||
class VisibilityCatcher : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
VisibilityCatcher(QWidget* parent = nullptr);
|
||||
|
||||
protected:
|
||||
bool eventFilter(QObject* obj, QEvent* event) override;
|
||||
|
||||
signals:
|
||||
void hidden();
|
||||
void shown();
|
||||
};
|
||||
|
||||
class Conversation : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
Conversation(bool muc, Models::Account* acc, const QString pJid, const QString pRes, QWidget* parent = 0);
|
||||
Conversation(bool muc, Models::Account* acc, Models::Element* el, const QString pJid, const QString pRes, QWidget* parent = 0);
|
||||
~Conversation();
|
||||
|
||||
QString getJid() const;
|
||||
QString getAccount() const;
|
||||
QString getPalResource() const;
|
||||
Models::Roster::ElId getId() const;
|
||||
virtual void addMessage(const Shared::Message& data);
|
||||
|
||||
void setPalResource(const QString& res);
|
||||
void responseArchive(const std::list<Shared::Message> list);
|
||||
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<QString, QVariant>& data);
|
||||
void setFeedFrames(bool top, bool right, bool bottom, bool left);
|
||||
virtual void appendMessageWithUpload(const Shared::Message& data, const QString& path);
|
||||
|
||||
signals:
|
||||
void sendMessage(const Shared::Message& message);
|
||||
void sendMessage(const Shared::Message& message, const QString& path);
|
||||
void resendMessage(const QString& id);
|
||||
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);
|
||||
@ -110,47 +97,44 @@ 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 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<Ui::Conversation> m_ui;
|
||||
KeyEnterReceiver ker;
|
||||
Resizer scrollResizeCatcher;
|
||||
VisibilityCatcher vis;
|
||||
QString thread;
|
||||
QLabel* statusIcon;
|
||||
QLabel* statusLabel;
|
||||
FlowLayout* filesLayout;
|
||||
QWidget* overlay;
|
||||
W::Order<Badge*, Badge::Comparator> filesToAttach;
|
||||
Scroll scroll;
|
||||
FeedView* feed;
|
||||
MessageDelegate* delegate;
|
||||
bool manualSliderChange;
|
||||
bool requestingHistory;
|
||||
bool everShown;
|
||||
bool tsb; //transient scroll bars
|
||||
|
||||
ShadowOverlay shadow;
|
||||
QMenu* contextMenu;
|
||||
};
|
||||
|
||||
#endif // CONVERSATION_H
|
||||
|
@ -214,62 +214,8 @@
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QScrollArea" name="scrollArea">
|
||||
<property name="autoFillBackground">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::NoFrame</enum>
|
||||
</property>
|
||||
<property name="lineWidth">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="midLineWidth">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="horizontalScrollBarPolicy">
|
||||
<enum>Qt::ScrollBarAlwaysOff</enum>
|
||||
</property>
|
||||
<property name="sizeAdjustPolicy">
|
||||
<enum>QAbstractScrollArea::AdjustIgnored</enum>
|
||||
</property>
|
||||
<property name="widgetResizable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<widget class="QWidget" name="scrollAreaWidgetContents">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>520</width>
|
||||
<height>385</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
<zorder>scrollArea</zorder>
|
||||
<zorder>widget_3</zorder>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QWidget" name="widget_2" native="true">
|
||||
|
14
ui/widgets/messageline/CMakeLists.txt
Normal file
@ -0,0 +1,14 @@
|
||||
target_sources(squawk PRIVATE
|
||||
messagedelegate.cpp
|
||||
messagedelegate.h
|
||||
#messageline.cpp
|
||||
#messageline.h
|
||||
preview.cpp
|
||||
preview.h
|
||||
messagefeed.cpp
|
||||
messagefeed.h
|
||||
feedview.cpp
|
||||
feedview.h
|
||||
#message.cpp
|
||||
#message.h
|
||||
)
|
494
ui/widgets/messageline/feedview.cpp
Normal file
@ -0,0 +1,494 @@
|
||||
/*
|
||||
* Squawk messenger.
|
||||
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "feedview.h"
|
||||
|
||||
#include <QPaintEvent>
|
||||
#include <QPainter>
|
||||
#include <QScrollBar>
|
||||
#include <QDebug>
|
||||
|
||||
#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<int> 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<Hint>::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<int>& 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<uint32_t>(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<uint32_t>(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<QModelIndex> toRener;
|
||||
for (std::deque<Hint>::size_type i = 0; i < hints.size(); ++i) {
|
||||
const Hint& hint = hints[i];
|
||||
int32_t relativeY1 = vph - hint.offset - hint.height;
|
||||
if (!inZone) {
|
||||
if (y2 > relativeY1) {
|
||||
inZone = true;
|
||||
}
|
||||
}
|
||||
if (inZone) {
|
||||
toRener.emplace_back(m->index(i, 0, rootIndex()));
|
||||
}
|
||||
if (y1 > relativeY1) {
|
||||
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<MessageDelegate*>(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<MessageDelegate*>(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<Hint>::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<MessageDelegate*>(itemDelegate());
|
||||
disconnect(del, &MessageDelegate::buttonPushed, this, &FeedView::onMessageButtonPushed);
|
||||
disconnect(del, &MessageDelegate::invalidPath, this, &FeedView::onMessageInvalidPath);
|
||||
}
|
||||
|
||||
QAbstractItemView::setItemDelegate(delegate);
|
||||
|
||||
MessageDelegate* del = dynamic_cast<MessageDelegate*>(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<Models::MessageFeed*>(model());
|
||||
disconnect(feed, &Models::MessageFeed::syncStateChange, this, &FeedView::onModelSyncStateChange);
|
||||
}
|
||||
|
||||
QAbstractItemView::setModel(p_model);
|
||||
|
||||
Models::MessageFeed* feed = dynamic_cast<Models::MessageFeed*>(p_model);
|
||||
if (feed) {
|
||||
onModelSyncStateChange(feed->getSyncState());
|
||||
specialModel = true;
|
||||
connect(feed, &Models::MessageFeed::syncStateChange, this, &FeedView::onModelSyncStateChange);
|
||||
} else {
|
||||
onModelSyncStateChange(Models::MessageFeed::complete);
|
||||
specialModel = false;
|
||||
}
|
||||
}
|
||||
|
||||
void FeedView::onMessageButtonPushed(const QString& messageId)
|
||||
{
|
||||
if (specialModel) {
|
||||
Models::MessageFeed* feed = static_cast<Models::MessageFeed*>(model());
|
||||
feed->downloadAttachment(messageId);
|
||||
}
|
||||
}
|
||||
|
||||
void FeedView::onMessageInvalidPath(const QString& messageId)
|
||||
{
|
||||
if (specialModel) {
|
||||
Models::MessageFeed* feed = static_cast<Models::MessageFeed*>(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();
|
||||
}
|
||||
}
|
98
ui/widgets/messageline/feedview.h
Normal file
@ -0,0 +1,98 @@
|
||||
/*
|
||||
* Squawk messenger.
|
||||
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef FEEDVIEW_H
|
||||
#define FEEDVIEW_H
|
||||
|
||||
#include <QAbstractItemView>
|
||||
|
||||
#include <deque>
|
||||
#include <set>
|
||||
|
||||
#include <ui/widgets/messageline/messagefeed.h>
|
||||
#include <ui/utils/progress.h>
|
||||
|
||||
/**
|
||||
* @todo write docs
|
||||
*/
|
||||
class FeedView : public QAbstractItemView
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
FeedView(QWidget* parent = nullptr);
|
||||
~FeedView();
|
||||
|
||||
QModelIndex indexAt(const QPoint & point) const override;
|
||||
void scrollTo(const QModelIndex & index, QAbstractItemView::ScrollHint hint) override;
|
||||
QRect visualRect(const QModelIndex & index) const override;
|
||||
bool isIndexHidden(const QModelIndex & index) const override;
|
||||
QModelIndex moveCursor(QAbstractItemView::CursorAction cursorAction, Qt::KeyboardModifiers modifiers) override;
|
||||
void setSelection(const QRect & rect, QItemSelectionModel::SelectionFlags command) override;
|
||||
QRegion visualRegionForSelection(const QItemSelection & selection) const override;
|
||||
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<int> & 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<Hint> hints;
|
||||
int vo;
|
||||
bool specialDelegate;
|
||||
bool specialModel;
|
||||
bool clearWidgetsMode;
|
||||
Models::MessageFeed::SyncState modelState;
|
||||
Progress progress;
|
||||
QFont dividerFont;
|
||||
QFontMetrics dividerMetrics;
|
||||
|
||||
static const std::set<int> geometryChangingRoles;
|
||||
|
||||
};
|
||||
|
||||
#endif //FEEDVIEW_H
|