Compare commits

...

66 Commits

Author SHA1 Message Date
c41abe903d correctly handle messages without id 2021-10-06 23:10:12 +08:00
8b3752ef47 fix ci for macos; adjust ci for linux executable with changed rpath 2021-10-06 18:12:26 +08:00
8c6ac1a21d add icns to macos bundle; use macdeployqt post build 2021-10-06 17:34:33 +08:00
1c0802c8ca fix win32 ci by place LMDB_INCLUDE_DIRS in core/ 2021-10-06 01:51:01 +08:00
d84a33e144 fix linux ci by finding and linking thread libraries 2021-10-06 01:33:58 +08:00
4f6946a5fc add LMDB_INCLUDE_DIRS as include dir, fixing win32 ci 2021-10-06 01:28:08 +08:00
f3153ef1db ci: add appveyor.yml 2021-10-06 01:17:30 +08:00
41d9fa7a1c fix macos bundle build 2021-10-06 01:07:47 +08:00
261b34b712 add forgotten cmake/MacOSXBundleInfo.plist.in 2021-10-06 00:55:39 +08:00
d1f108e69d update docs for win32 build 2021-10-06 00:53:50 +08:00
1e37aa762c generate app bundle for macOS 2021-10-06 00:48:25 +08:00
a1f3c00a54 remove dependency uuid 2021-10-06 00:15:25 +08:00
923afe2420 [Fix] Merge branch 'feat/adapt_win_osx' into upstream_messageFeed 2021-10-05 23:45:37 +08:00
faa7d396a5 add liblmdb.so as possible lmdb lib name 2021-10-05 16:09:31 +08:00
c55b7c6baf update docs for removed uuid dep and LMDB_DIR var 2021-10-05 15:20:25 +08:00
6764d8ea11 remove dependency libuuid 2021-10-05 12:56:36 +08:00
5fbb96618f adjust CMakeLists.txt, to prepare for win32 and macos builds 2021-10-05 12:49:06 +08:00
d89c030e66
translation verification
Portugues Brazil localization added provided by most welcome Bruno Fontes
2021-09-22 23:43:03 +03:00
5787af8a4f
Merge remote-tracking branch 'origin/master' into messageFeed 2021-09-22 23:09:48 +03:00
1706b93b59 Merge pull request 'Add Brazilian Portuguese translations' (#49) from brunofontes/squawk:ptBR_translations into master
Reviewed-on: blue/squawk#49
2021-09-22 20:08:54 +00:00
3f09b8f838
Date dividers between messages from different dates 2021-09-22 01:17:43 +03:00
87c216b491
Add Brazilian Portuguese translations 2021-07-21 19:37:31 -03:00
5f925217fc
edit icon next to the message showing if the message was edited and what was there a first 2021-05-25 01:06:05 +03:00
3f1fba4de2
doovers for failed messages, some corner cases fixes with handling errors during message sending 2021-05-23 01:03:14 +03:00
ddfaa63a24
big image preview optimisations, preview positioning fix, memory leaks fix 2021-05-17 23:32:44 +03:00
721f6daa36
fix bug when everything was treated as animation, bug with not working group amount of messages, handled the situation when preview is painted but the file was lost 2021-05-17 00:52:59 +03:00
0d584c5aba
message preview refactor, several bugs about label size, animations are now playing in previews 2021-05-16 01:07:49 +03:00
4307262f6e basic error download/upload files handling, need more testing 2021-05-14 22:49:38 +03:00
bd07c49755 Merge pull request 'Refactor CMakeLists' (#46) from vae/squawk:build-refactor into messageFeed
Reviewed-on: blue/squawk#46
2021-05-11 23:31:59 +00:00
vae
8e99cc2969
build: plugins/, passwordStorageEngines/wrappers/ as shared libs 2021-05-12 02:01:02 +03:00
vae
a184ecafa3
build: reformat cmake code 2021-05-11 22:24:55 +03:00
vae
7d2688151c
build: finish up CMakeLists refactoring 2021-05-11 22:21:25 +03:00
vae
0038aca1f6
build: WIP CMakeLists refactoring continue - add FindSignal 2021-05-11 21:35:12 +03:00
vae
6e06a1d5bc
build: WIP CMakeLists refactoring 2021-05-11 20:29:08 +03:00
b7b70bc198 segfault fix when trying to send something but the history isn't loaded yet, icon and for attached files which are not previewed 2021-05-11 00:06:40 +03:00
ce047db787 patches from Vae about making libraries static, and about boost, findLMDB CMake script, drop dependency for qtquickcontrols 2021-05-09 02:12:17 +03:00
f45319de25 now instead of storing uploading message in ram I store them in database to be able to recover unsent ones on the next statrt. Found and fixed bug with spam repaints in feedview because of icons 2021-05-07 21:26:02 +03:00
ebf0c64ffb highlight in directory now is optional runtime plugin to KIO, several more file managers to fallback, refactor, 2 new icons 2021-05-06 17:44:43 +03:00
d514db9c4a message context menu began, open and show in folder features 2021-05-04 17:09:41 +03:00
f34289399e text inside of message is selectable again, links are clickable, some refactor, some bugfixes 2021-05-03 14:23:41 +03:00
05d6761baa a bit of refactor, fix the time to request next portion of messages in ui, fancy shadows are back! 2021-05-03 03:35:43 +03:00
216dcd29e9 bug fixing, better progres indicator positioning 2021-05-02 02:03:08 +03:00
0973cb2991 first lousy attempt to make load indicator in feedView 2021-04-30 23:07:00 +03:00
50190f3eac handled a case when user removes downloaded file, minor optimizations on message changing 2021-04-28 23:26:19 +03:00
b44873d587 pal resourse sticking, notifications of unread messages, new message icons 2021-04-27 22:29:15 +03:00
4c5efad9dc CMake build error, status icon text tooltip 2021-04-26 19:37:36 +03:00
8310708c92 First attemtps to upload files, debug, reused of once uploaded or downloaded files 2021-04-23 14:53:48 +03:00
d936c0302d bug with downloads in group chats, status icons in messages, visuals, feedView optimisations 2021-04-23 01:41:32 +03:00
0e937199b0 debug and actual first way to display pictures in messageFeed 2021-04-21 00:56:47 +03:00
48e498be25 some debug, message changing in messageFeed 2021-04-20 00:49:24 +03:00
3a7735b192 First steps on the new idea of file up/downloading 2021-04-18 15:49:20 +03:00
8f914c02a7 temp url storage commit 2021-04-13 16:27:31 +03:00
50bb3f5fd7 started progress bars, changed gcc standard to 17 2021-03-22 21:04:26 +03:00
a0348b8fd2 file progress events delivery methonds 2021-02-27 15:21:27 +03:00
85555da81f just a temp one 2021-02-07 20:02:11 +03:00
ebe5addfb5 just proxying button event from feed view delegate to the feed model 2021-02-06 14:02:42 +03:00
b3c6860e25 seems like i have found solution how to properly render buttons 2021-02-02 01:55:15 +03:00
00ffbac6b0 initial attempt to paint buttons in the messagefeed 2021-01-14 14:22:02 +03:00
ff4124d1f0 Resolved the bug about crash with an empty history chat 2021-01-12 20:15:21 +03:00
15342f3c53 self nick in the chat fix, hovering message feature 2021-01-08 00:50:12 +03:00
270a32db9e achive from the beginning memorizing bugfix, limitation of the requests in the model 2020-08-21 23:57:48 +03:00
e0ef1ef797 Some basic message painting 2020-08-21 00:32:30 +03:00
e1eea2f3a2 made the first prototype, scrolling and word wrapping seems to be working 2020-08-17 13:27:14 +03:00
e54cff0f0c changed my mind, gonna implement the feed on qt instead of qml, first tries, nothing working yet 2020-08-16 00:48:28 +03:00
4e6bd04b02 experimenting with qml 2020-08-12 19:55:01 +03:00
38159eafeb first compiling prototype, doesnt work yet 2020-08-12 01:49:51 +03:00
111 changed files with 6616 additions and 1974 deletions

View File

@ -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

View File

@ -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()

View File

@ -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
View 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
View 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
View 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 ()

View 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>

View File

@ -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()

View File

@ -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);}

View File

@ -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();

View File

@ -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);
}
}
}

View File

@ -25,7 +25,7 @@
#include <QMimeType>
#include "shared/message.h"
#include "exception.h"
#include "shared/exception.h"
#include <lmdb.h>
#include <list>

View File

@ -0,0 +1,6 @@
target_sources(squawk PRIVATE
messagehandler.cpp
messagehandler.h
rosterhandler.cpp
rosterhandler.h
)

View File

@ -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";
}
}

View File

@ -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;
};
}

View File

@ -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;

View File

@ -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:

View File

@ -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);

View File

@ -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);
}

View File

@ -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;

View File

@ -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 ()

View File

@ -0,0 +1,2 @@
add_library(kwalletWrapper SHARED kwallet.cpp)
target_link_libraries(kwalletWrapper PRIVATE KF5::Wallet)

View File

@ -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);
}

View File

@ -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);

View 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;
}

View File

@ -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";
}
}
}

View File

@ -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
View 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
View 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

View File

@ -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
View 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)

View File

@ -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
View File

@ -0,0 +1,4 @@
if (WITH_KIO)
add_library(openFileManagerWindowJob SHARED openfilemanagerwindowjob.cpp)
target_link_libraries(openFileManagerWindowJob PRIVATE KF5::KIOWidgets)
endif ()

View 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
View 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)

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View File

@ -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
View File

@ -0,0 +1 @@
IDI_ICON1 ICON "squawk.ico"

19
shared/CMakeLists.txt Normal file
View 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
)

View File

@ -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) \

View File

@ -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
};
}

View File

@ -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"}}
};
}

View File

@ -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;
}

View File

@ -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
View 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
View 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

View File

@ -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

View File

@ -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();
}

View File

@ -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
View 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;
}

View 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)

View 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&apos;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 &quot;Home&quot; or &quot;Work&quot;</source>
<translation>Um nome de recurso como &quot;Casa&quot; ou &quot;Trabalho&quot;</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>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;Liberation Sans&apos;; font-size:10pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</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&apos;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&apos;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>

View File

@ -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
View 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
)

View File

@ -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;
}

View File

@ -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;

View File

@ -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;
}

View File

@ -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
View 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
View 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

View File

@ -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;

View File

@ -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;

View File

@ -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;
}

View File

@ -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;
};
}

View File

@ -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);
}
}

View File

@ -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();
}

View File

@ -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;
};
}

View File

@ -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();
}
}
}

View File

@ -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 {

View File

@ -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);

View File

@ -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
View 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
)

View File

@ -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);
}

View File

@ -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

View File

@ -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);
}

View 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

View 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
View 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

View File

@ -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)

View File

@ -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);
}
}

View File

@ -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();

View File

@ -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));
}
}
}

View File

@ -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

View File

@ -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">

View 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
)

View 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();
}
}

View 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

Some files were not shown because too many files have changed in this diff Show More