forked from blue/squawk
Compare commits
107 Commits
newMessage
...
master
Author | SHA1 | Date | |
---|---|---|---|
1af20e27f2 | |||
ea7dcc5f18 | |||
645b92ba51 | |||
80c5e2f2b4 | |||
1f065f23e6 | |||
3c48577eee | |||
0340db7f2f | |||
c3a45ec58c | |||
7ba94e9deb | |||
eac87e713f | |||
d86e2c28a0 | |||
2fcc432aef | |||
e58213b294 | |||
3916aec358 | |||
721d3a1a89 | |||
83cb220175 | |||
18859cb960 | |||
4c20a314f0 | |||
51ac1ac709 | |||
8f949277f6 | |||
ce686e121b | |||
f64e5c2df0 | |||
2c26c7e264 | |||
69e0c88d8d | |||
82d54ba4df | |||
1b66fda318 | |||
9f746d203b | |||
27377e0ec5 | |||
4baa3bccbf | |||
4786388822 | |||
62f02c18d7 | |||
1fcd403dba | |||
5f6691067a | |||
788c6ca556 | |||
bf4a27f35d | |||
0823b35148 | |||
73b1b58a96 | |||
d8b5ccb2da | |||
243edff8bd | |||
da19eb86bb | |||
0ff9f12157 | |||
802e2f11a1 | |||
c708c33a92 | |||
a8a7ce2538 | |||
841e526e59 | |||
6bee149e6b | |||
62a59eb7a1 | |||
296328f12d | |||
4d3ba6b11f | |||
8a2658e4fc | |||
9ac0ca10f3 | |||
7130e674c4 | |||
e27ae1a82f | |||
1aa2b5a539 | |||
43bfaf9b7e | |||
b19dafef33 | |||
aeaa6b1b28 | |||
e47ba603e0 | |||
8fece95aa2 | |||
893ff53aa8 | |||
39f2f3d975 | |||
52551c1ce0 | |||
50d710de04 | |||
332131796c | |||
3a70df21f8 | |||
a24e8382d1 | |||
d20fd84d39 | |||
5862f1552b | |||
ebeb4089eb | |||
a53126d8bc | |||
7db269acb5 | |||
67e5f9744e | |||
d0bdb374a0 | |||
8b3752ef47 | |||
8c6ac1a21d | |||
1c0802c8ca | |||
d84a33e144 | |||
4f6946a5fc | |||
f3153ef1db | |||
41d9fa7a1c | |||
261b34b712 | |||
d1f108e69d | |||
1e37aa762c | |||
a1f3c00a54 | |||
923afe2420 | |||
faa7d396a5 | |||
c55b7c6baf | |||
6764d8ea11 | |||
5fbb96618f | |||
d89c030e66 | |||
5787af8a4f | |||
1706b93b59 | |||
3f09b8f838 | |||
87c216b491 | |||
5f925217fc | |||
3f1fba4de2 | |||
ddfaa63a24 | |||
721f6daa36 | |||
0d584c5aba | |||
4307262f6e | |||
bd07c49755 | |||
8e99cc2969 | |||
a184ecafa3 | |||
7d2688151c | |||
0038aca1f6 | |||
6e06a1d5bc | |||
b7b70bc198 |
55
CHANGELOG.md
55
CHANGELOG.md
@ -1,19 +1,70 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## Squawk 0.2.0 (Unreleased)
|
## Squawk 0.2.2 (May 05, 2022)
|
||||||
|
### Bug fixes
|
||||||
|
- now when you remove an account it actually gets removed
|
||||||
|
- segfault on unitialized Availability in some rare occesions
|
||||||
|
- fixed crash when you open a dialog with someone that has only error messages in archive
|
||||||
|
- message height is now calculated correctly on Chinese and Japanese paragraphs
|
||||||
|
- the app doesn't crash on SIGINT anymore
|
||||||
|
|
||||||
|
### Improvements
|
||||||
|
- there is a way to disable an account and it wouldn't connect when you change availability
|
||||||
|
- if you cancel password query an account becomes inactive and doesn't annoy you anymore
|
||||||
|
- if you filled password field and chose KWallet as a storage Squawk wouldn't ask you again for the same password
|
||||||
|
- if left the password field empty and chose KWallet as a storage Squawk will try to get that passord from KWallet before asking you to input it
|
||||||
|
- accounts now connect to the server asyncronously - if one is stopped on password prompt another is connecting
|
||||||
|
- actualized translations, added English localization file
|
||||||
|
|
||||||
|
### New features
|
||||||
|
- new "About" window with links, license, gratitudes
|
||||||
|
- if the authentication failed Squawk will ask againg for your password and login
|
||||||
|
- now there is an amount of unread messages showing on top of Squawk launcher icon
|
||||||
|
- notifications now have buttons to open a conversation or to mark that message as read
|
||||||
|
|
||||||
|
## Squawk 0.2.1 (Apr 02, 2022)
|
||||||
|
### Bug fixes
|
||||||
|
- build in release mode now no longer spams warnings
|
||||||
|
- build now correctly installs all build plugin libs
|
||||||
|
- a bug where the correction message was received, the indication was on but the text didn't actually change
|
||||||
|
- message body now doesn't intecept context menu from the whole message
|
||||||
|
- message input now doesn't increase font when you remove everything from it
|
||||||
|
|
||||||
|
### Improvements
|
||||||
|
- reduced amount of places where platform specific path separator is used
|
||||||
|
- now message input is automatically focused when you open a dialog or a room
|
||||||
|
- what() method on unhandled exception now actually tells what happened
|
||||||
|
|
||||||
|
### New features
|
||||||
|
- the settings are here! You con config different stuff from there
|
||||||
|
- now it's possible to set up different qt styles from settings
|
||||||
|
- if you have KConfig nad KConfigWidgets packages installed - you can choose from global color schemes
|
||||||
|
- it's possible now to choose a folder where squawk is going to store downloaded files
|
||||||
|
- now you can correct your message
|
||||||
|
|
||||||
|
## Squawk 0.2.0 (Jan 10, 2022)
|
||||||
### Bug fixes
|
### Bug fixes
|
||||||
- carbon copies switches on again after reconnection
|
- carbon copies switches on again after reconnection
|
||||||
- requesting the history of the current chat after reconnection
|
- requesting the history of the current chat after reconnection
|
||||||
- global availability (in drop down list) gets restored 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
|
- 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
|
- infinite progress when open the dialogue with something that has no history to show
|
||||||
|
- fallback icons for buttons, when no supported theme is installed (shunf4)
|
||||||
|
- better handling messages with no id (shunf4)
|
||||||
|
- removed dependency: uuid, now it's on Qt (shunf4)
|
||||||
|
- better requesting latest history (shunf4)
|
||||||
|
|
||||||
### Improvements
|
### Improvements
|
||||||
- slightly reduced the traffic on the startup by not requesting history of all MUCs
|
- slightly reduced the traffic on the startup by not requesting history of all MUCs
|
||||||
- completely rewritten message feed, now it works way faster
|
- completely rewritten message feed, now it works way faster and looks cooler
|
||||||
- OPTIONAL RUNTIME dependency: "KIO Widgets" that is supposed to allow you to open a file in your default file manager
|
- 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
|
- 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
|
- once uploaded local files don't get second time uploaded - the remote URL is reused
|
||||||
|
- way better compilation time (vae)
|
||||||
|
|
||||||
|
### New features
|
||||||
|
- pasting images from clipboard to attachment (shunf4)
|
||||||
|
- possible compilation for windows and macOS (shunf4)
|
||||||
|
|
||||||
## Squawk 0.1.5 (Jul 29, 2020)
|
## Squawk 0.1.5 (Jul 29, 2020)
|
||||||
### Bug fixes
|
### Bug fixes
|
||||||
|
210
CMakeLists.txt
210
CMakeLists.txt
@ -1,7 +1,8 @@
|
|||||||
cmake_minimum_required(VERSION 3.4)
|
cmake_minimum_required(VERSION 3.4)
|
||||||
project(squawk)
|
project(squawk VERSION 0.2.2 LANGUAGES CXX)
|
||||||
|
|
||||||
set(CMAKE_INCLUDE_CURRENT_DIR ON)
|
cmake_policy(SET CMP0076 NEW)
|
||||||
|
cmake_policy(SET CMP0079 NEW)
|
||||||
set(CMAKE_CXX_STANDARD 17)
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
|
|
||||||
set(CMAKE_AUTOMOC ON)
|
set(CMAKE_AUTOMOC ON)
|
||||||
@ -9,65 +10,43 @@ set(CMAKE_AUTOUIC ON)
|
|||||||
set(CMAKE_AUTORCC ON)
|
set(CMAKE_AUTORCC ON)
|
||||||
|
|
||||||
include(GNUInstallDirs)
|
include(GNUInstallDirs)
|
||||||
include_directories(.)
|
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake")
|
||||||
|
|
||||||
find_package(Qt5Widgets CONFIG REQUIRED)
|
set(WIN32_FLAG "")
|
||||||
find_package(Qt5LinguistTools)
|
set(MACOSX_BUNDLE_FLAG "")
|
||||||
|
if (CMAKE_BUILD_TYPE STREQUAL "Release")
|
||||||
if(NOT CMAKE_BUILD_TYPE)
|
if (WIN32)
|
||||||
set(CMAKE_BUILD_TYPE Debug)
|
set(WIN32_FLAG WIN32)
|
||||||
|
endif(WIN32)
|
||||||
|
if (APPLE)
|
||||||
|
set(MACOSX_BUNDLE_FLAG MACOSX_BUNDLE)
|
||||||
|
endif(APPLE)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
set(CMAKE_CXX_FLAGS_DEBUG "-g -Wall -Wextra")
|
add_executable(squawk ${WIN32_FLAG} ${MACOSX_BUNDLE_FLAG})
|
||||||
set(CMAKE_CXX_FLAGS_RELEASE "-O3")
|
target_include_directories(squawk PRIVATE ${CMAKE_SOURCE_DIR})
|
||||||
message("Build type: ${CMAKE_BUILD_TYPE}")
|
|
||||||
|
|
||||||
|
|
||||||
set(squawk_SRC
|
|
||||||
main.cpp
|
|
||||||
exception.cpp
|
|
||||||
signalcatcher.cpp
|
|
||||||
shared/global.cpp
|
|
||||||
shared/utils.cpp
|
|
||||||
shared/message.cpp
|
|
||||||
shared/vcard.cpp
|
|
||||||
shared/icons.cpp
|
|
||||||
shared/messageinfo.cpp
|
|
||||||
)
|
|
||||||
|
|
||||||
set(squawk_HEAD
|
|
||||||
exception.h
|
|
||||||
signalcatcher.h
|
|
||||||
shared.h
|
|
||||||
shared/enums.h
|
|
||||||
shared/message.h
|
|
||||||
shared/global.h
|
|
||||||
shared/utils.h
|
|
||||||
shared/vcard.h
|
|
||||||
shared/icons.h
|
|
||||||
shared/messageinfo.h
|
|
||||||
)
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
option(SYSTEM_QXMPP "Use system qxmpp lib" ON)
|
option(SYSTEM_QXMPP "Use system qxmpp lib" ON)
|
||||||
option(WITH_KWALLET "Build KWallet support module" ON)
|
option(WITH_KWALLET "Build KWallet support module" ON)
|
||||||
option(WITH_KIO "Build KIO support module" ON)
|
option(WITH_KIO "Build KIO support module" ON)
|
||||||
|
option(WITH_KCONFIG "Build KConfig support module" ON)
|
||||||
|
|
||||||
|
# Dependencies
|
||||||
|
## Qt
|
||||||
|
set(QT_VERSION_MAJOR 5)
|
||||||
|
find_package(Qt5 COMPONENTS Widgets DBus Gui Xml Network Core REQUIRED)
|
||||||
|
find_package(Boost COMPONENTS)
|
||||||
|
|
||||||
|
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)
|
if (SYSTEM_QXMPP)
|
||||||
find_package(QXmpp CONFIG)
|
find_package(QXmpp CONFIG)
|
||||||
|
|
||||||
@ -80,24 +59,13 @@ if (SYSTEM_QXMPP)
|
|||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
if (NOT SYSTEM_QXMPP)
|
if (NOT SYSTEM_QXMPP)
|
||||||
|
target_link_libraries(squawk PRIVATE qxmpp)
|
||||||
add_subdirectory(external/qxmpp)
|
add_subdirectory(external/qxmpp)
|
||||||
endif()
|
|
||||||
|
|
||||||
if (WITH_KWALLET)
|
|
||||||
find_package(KF5Wallet CONFIG)
|
|
||||||
|
|
||||||
if (NOT KF5Wallet_FOUND)
|
|
||||||
set(WITH_KWALLET OFF)
|
|
||||||
message("KWallet package wasn't found, KWallet support module wouldn't be built")
|
|
||||||
else ()
|
else ()
|
||||||
add_definitions(-DWITH_KWALLET)
|
target_link_libraries(squawk PRIVATE QXmpp::QXmpp)
|
||||||
message("Building with support of KWallet")
|
|
||||||
endif()
|
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
add_executable(squawk ${squawk_SRC} ${squawk_HEAD} ${RCC})
|
## KIO
|
||||||
target_link_libraries(squawk Qt5::Widgets)
|
|
||||||
|
|
||||||
if (WITH_KIO)
|
if (WITH_KIO)
|
||||||
find_package(KF5KIO CONFIG)
|
find_package(KF5KIO CONFIG)
|
||||||
|
|
||||||
@ -105,31 +73,101 @@ if (WITH_KIO)
|
|||||||
set(WITH_KIO OFF)
|
set(WITH_KIO OFF)
|
||||||
message("KIO package wasn't found, KIO support modules wouldn't be built")
|
message("KIO package wasn't found, KIO support modules wouldn't be built")
|
||||||
else ()
|
else ()
|
||||||
add_definitions(-DWITH_KIO)
|
target_compile_definitions(squawk PRIVATE WITH_KIO)
|
||||||
message("Building with support of KIO")
|
message("Building with support of KIO")
|
||||||
endif ()
|
endif ()
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
add_subdirectory(ui)
|
## KWallet
|
||||||
|
if (WITH_KWALLET)
|
||||||
|
find_package(KF5Wallet CONFIG)
|
||||||
|
|
||||||
|
if (NOT KF5Wallet_FOUND)
|
||||||
|
set(WITH_KWALLET OFF)
|
||||||
|
message("KWallet package wasn't found, KWallet support module wouldn't be built")
|
||||||
|
else ()
|
||||||
|
target_compile_definitions(squawk PRIVATE WITH_KWALLET)
|
||||||
|
message("Building with support of KWallet")
|
||||||
|
endif ()
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
if (WITH_KCONFIG)
|
||||||
|
find_package(KF5Config CONFIG)
|
||||||
|
if (NOT KF5Config_FOUND)
|
||||||
|
set(WITH_KCONFIG OFF)
|
||||||
|
message("KConfig package wasn't found, KConfig support modules wouldn't be built")
|
||||||
|
else()
|
||||||
|
find_package(KF5ConfigWidgets CONFIG)
|
||||||
|
if (NOT KF5ConfigWidgets_FOUND)
|
||||||
|
set(WITH_KCONFIG OFF)
|
||||||
|
message("KConfigWidgets package wasn't found, KConfigWidgets support modules wouldn't be built")
|
||||||
|
else()
|
||||||
|
target_compile_definitions(squawk PRIVATE WITH_KCONFIG)
|
||||||
|
message("Building with support of KConfig")
|
||||||
|
message("Building with support of KConfigWidgets")
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
## Signal (TODO)
|
||||||
|
# find_package(Signal REQUIRED)
|
||||||
|
|
||||||
|
## 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)
|
||||||
|
set (COMPILE_OPTIONS -fno-sized-deallocation) # for eliminating _ZdlPvm
|
||||||
|
if (CMAKE_BUILD_TYPE STREQUAL "Release")
|
||||||
|
list(APPEND COMPILE_OPTIONS -O3)
|
||||||
|
endif()
|
||||||
|
if (CMAKE_BUILD_TYPE STREQUAL Debug)
|
||||||
|
list(APPEND COMPILE_OPTIONS -g)
|
||||||
|
list(APPEND COMPILE_OPTIONS -Wall)
|
||||||
|
list(APPEND COMPILE_OPTIONS -Wextra)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
message("Compilation options: " ${COMPILE_OPTIONS})
|
||||||
|
target_compile_options(squawk PRIVATE ${COMPILE_OPTIONS})
|
||||||
|
endif(CMAKE_COMPILER_IS_GNUCXX)
|
||||||
|
|
||||||
|
add_subdirectory(main)
|
||||||
add_subdirectory(core)
|
add_subdirectory(core)
|
||||||
add_subdirectory(plugins)
|
|
||||||
|
|
||||||
add_subdirectory(external/simpleCrypt)
|
add_subdirectory(external/simpleCrypt)
|
||||||
|
add_subdirectory(packaging)
|
||||||
target_link_libraries(squawk squawkUI)
|
add_subdirectory(plugins)
|
||||||
target_link_libraries(squawk squawkCORE)
|
add_subdirectory(resources)
|
||||||
target_link_libraries(squawk uuid)
|
add_subdirectory(shared)
|
||||||
|
add_subdirectory(translations)
|
||||||
|
add_subdirectory(ui)
|
||||||
|
|
||||||
add_dependencies(${CMAKE_PROJECT_NAME} translations)
|
|
||||||
|
|
||||||
# Install the executable
|
# Install the executable
|
||||||
install(TARGETS squawk DESTINATION ${CMAKE_INSTALL_BINDIR})
|
install(TARGETS squawk DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||||
install(FILES ${QM_FILES} DESTINATION ${CMAKE_INSTALL_DATADIR}/squawk/l10n)
|
install(FILES README.md DESTINATION ${CMAKE_INSTALL_DATADIR}/macaw.me/squawk)
|
||||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk.svg DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/scalable/apps)
|
install(FILES LICENSE.md DESTINATION ${CMAKE_INSTALL_DATADIR}/macaw.me/squawk)
|
||||||
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)
|
if (CMAKE_BUILD_TYPE STREQUAL "Release")
|
||||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk128.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/128x128/apps RENAME squawk.png)
|
if (APPLE)
|
||||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk256.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/256x256/apps RENAME squawk.png)
|
add_custom_command(TARGET squawk POST_BUILD COMMENT "Running macdeployqt..."
|
||||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk.desktop DESTINATION ${CMAKE_INSTALL_DATADIR}/applications)
|
COMMAND "${Qt5Widgets_DIR}/../../../bin/macdeployqt" "${CMAKE_CURRENT_BINARY_DIR}/squawk.app"
|
||||||
|
)
|
||||||
|
endif(APPLE)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
36
README.md
36
README.md
@ -4,17 +4,20 @@
|
|||||||
[![AUR version](https://img.shields.io/aur/version/squawk?style=flat-square)](https://aur.archlinux.org/packages/squawk/)
|
[![AUR version](https://img.shields.io/aur/version/squawk?style=flat-square)](https://aur.archlinux.org/packages/squawk/)
|
||||||
[![Liberapay patrons](https://img.shields.io/liberapay/patrons/macaw.me?logo=liberapay&style=flat-square)](https://liberapay.com/macaw.me)
|
[![Liberapay patrons](https://img.shields.io/liberapay/patrons/macaw.me?logo=liberapay&style=flat-square)](https://liberapay.com/macaw.me)
|
||||||
|
|
||||||
![Squawk screenshot](https://macaw.me/images/squawk/0.1.4.png)
|
![Squawk screenshot](https://macaw.me/images/squawk/0.2.2.png)
|
||||||
|
|
||||||
### Prerequisites
|
### Prerequisites
|
||||||
|
|
||||||
- QT 5.12 *(lower versions might work but it wasn't tested)*
|
- 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
|
- lmdb
|
||||||
- CMake 3.0 or higher
|
- CMake 3.4 or higher
|
||||||
- qxmpp 1.1.0 or higher
|
- qxmpp 1.1.0 or higher
|
||||||
- KDE Frameworks: kwallet (optional)
|
- KDE Frameworks: kwallet (optional)
|
||||||
- KDE Frameworks: KIO (optional)
|
- KDE Frameworks: KIO (optional)
|
||||||
|
- KDE Frameworks: KConfig (optional)
|
||||||
|
- KDE Frameworks: KConfigWidgets (optional)
|
||||||
|
- Boost (just one little hpp from there)
|
||||||
|
- Imagemagick (for compilation, to rasterize an SVG logo)
|
||||||
|
|
||||||
### Getting
|
### Getting
|
||||||
|
|
||||||
@ -34,6 +37,26 @@ You can also clone the repo and build it from source
|
|||||||
|
|
||||||
Squawk requires Qt with SSL enabled. It uses CMake as build system.
|
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
|
There are two ways to build, it depends whether you have qxmpp installed in your system
|
||||||
|
|
||||||
#### Building with system qxmpp
|
#### Building with system qxmpp
|
||||||
@ -45,7 +68,7 @@ $ git clone https://git.macaw.me/blue/squawk
|
|||||||
$ cd squawk
|
$ cd squawk
|
||||||
$ mkdir build
|
$ mkdir build
|
||||||
$ cd build
|
$ cd build
|
||||||
$ cmake ..
|
$ cmake .. [-D LMDB_ROOT_DIR:PATH=...] [-D BOOST_ROOT:PATH=...]
|
||||||
$ cmake --build .
|
$ cmake --build .
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -58,10 +81,12 @@ $ git clone --recurse-submodules https://git.macaw.me/blue/squawk
|
|||||||
$ cd squawk
|
$ cd squawk
|
||||||
$ mkdir build
|
$ mkdir build
|
||||||
$ cd build
|
$ cd build
|
||||||
$ cmake .. -D SYSTEM_QXMPP=False
|
$ cmake .. -D SYSTEM_QXMPP=False [-D LMDB_ROOT_DIR:PATH=...] [-D BOOST_ROOT:PATH=...]
|
||||||
$ cmake --build .
|
$ cmake --build .
|
||||||
```
|
```
|
||||||
|
|
||||||
|
You can always refer to `appveyor.yml` to see how AppVeyor build squawk.
|
||||||
|
|
||||||
### List of keys
|
### List of keys
|
||||||
|
|
||||||
Here is the list of keys you can pass to configuration phase of `cmake ..`.
|
Here is the list of keys you can pass to configuration phase of `cmake ..`.
|
||||||
@ -69,6 +94,7 @@ Here is the list of keys you can pass to configuration phase of `cmake ..`.
|
|||||||
- `SYSTEM_QXMPP` - `True` tries to link against `qxmpp` installed in the system, `False` builds bundled `qxmpp` library (default is `True`)
|
- `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_KWALLET` - `True` builds the `KWallet` capability module if `KWallet` is installed and if not goes to `False`. `False` disables `KWallet` support (default is `True`)
|
||||||
- `WITH_KIO` - `True` builds the `KIO` capability module if `KIO` is installed and if not goes to `False`. `False` disables `KIO` support (default is `True`)
|
- `WITH_KIO` - `True` builds the `KIO` capability module if `KIO` is installed and if not goes to `False`. `False` disables `KIO` support (default is `True`)
|
||||||
|
- `WITH_KCONFIG` - `True` builds the `KConfig` and `KConfigWidgets` capability module if such packages are installed and if not goes to `False`. `False` disables `KConfig` and `KConfigWidgets` support (default is `True`)
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
117
appveyor.yml
Normal file
117
appveyor.yml
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
image:
|
||||||
|
- Visual Studio 2019
|
||||||
|
- "Previous Ubuntu1804"
|
||||||
|
- macOS-Mojave
|
||||||
|
|
||||||
|
branches:
|
||||||
|
except:
|
||||||
|
- gh-pages
|
||||||
|
|
||||||
|
for:
|
||||||
|
-
|
||||||
|
matrix:
|
||||||
|
only:
|
||||||
|
- image: Visual Studio 2019
|
||||||
|
|
||||||
|
environment:
|
||||||
|
QTDIR: C:\Qt\5.15.2\mingw81_64
|
||||||
|
QTTOOLDIR: C:\Qt\Tools\mingw810_64\bin
|
||||||
|
QTNINJADIR: C:\Qt\Tools\Ninja
|
||||||
|
|
||||||
|
install:
|
||||||
|
- set PATH=%QTTOOLDIR%;%QTNINJADIR%;%QTDIR%\bin;%PATH%
|
||||||
|
- git submodule update --init --recursive
|
||||||
|
|
||||||
|
before_build:
|
||||||
|
- choco install --yes zstandard
|
||||||
|
- choco install --yes --version=7.1.0.2 imagemagick.app
|
||||||
|
- set PATH=C:\Program Files\ImageMagick-7.1.0-Q16-HDRI;%PATH%
|
||||||
|
|
||||||
|
- mkdir lmdb
|
||||||
|
- cd lmdb
|
||||||
|
- curl -OL https://mirror.msys2.org/mingw/mingw64/mingw-w64-x86_64-lmdb-0.9.27-1-any.pkg.tar.zst
|
||||||
|
- zstd -d ./mingw-w64-x86_64-lmdb-0.9.27-1-any.pkg.tar.zst
|
||||||
|
- tar -xvf ./mingw-w64-x86_64-lmdb-0.9.27-1-any.pkg.tar
|
||||||
|
- cd ..
|
||||||
|
|
||||||
|
- mkdir boost
|
||||||
|
- cd boost
|
||||||
|
- curl -OL https://repo.msys2.org/mingw/mingw64/mingw-w64-x86_64-boost-1.77.0-2-any.pkg.tar.zst
|
||||||
|
- zstd -d ./mingw-w64-x86_64-boost-1.77.0-2-any.pkg.tar.zst
|
||||||
|
- tar -xvf ./mingw-w64-x86_64-boost-1.77.0-2-any.pkg.tar
|
||||||
|
- cd ..
|
||||||
|
|
||||||
|
- mkdir build
|
||||||
|
- cd build
|
||||||
|
- cmake -GNinja -DCMAKE_BUILD_TYPE:String=Release -DCMAKE_PREFIX_PATH:STRING=%QTDIR% -DLMDB_ROOT_DIR:PATH=C:/projects/squawk/lmdb/mingw64 -DBOOST_ROOT:PATH=C:/projects/squawk/boost/mingw64 ..
|
||||||
|
|
||||||
|
build_script:
|
||||||
|
- cmake --build .
|
||||||
|
- mkdir deploy
|
||||||
|
- cd deploy
|
||||||
|
- copy ..\squawk.exe .\
|
||||||
|
- copy ..\external\qxmpp\src\libqxmpp.dll .\
|
||||||
|
- windeployqt .\squawk.exe
|
||||||
|
- windeployqt .\libqxmpp.dll
|
||||||
|
- cd ..\..
|
||||||
|
|
||||||
|
artifacts:
|
||||||
|
- path: build/deploy/squawk.exe
|
||||||
|
name: Squawk executable (Qt 5.15.2)
|
||||||
|
|
||||||
|
- path: build/deploy
|
||||||
|
name: Squawk deployment with Qt Framework
|
||||||
|
|
||||||
|
-
|
||||||
|
matrix:
|
||||||
|
only:
|
||||||
|
- image: macOS-Mojave
|
||||||
|
|
||||||
|
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)
|
@ -25,10 +25,15 @@ find_path(LMDB_ROOT_DIR
|
|||||||
)
|
)
|
||||||
|
|
||||||
find_library(LMDB_LIBRARIES
|
find_library(LMDB_LIBRARIES
|
||||||
NAMES lmdb
|
NAMES liblmdb.a liblmdb.so liblmdb.so.a liblmdb.dll.a # We want lmdb to be static, if possible
|
||||||
HINTS ${LMDB_ROOT_DIR}/lib
|
HINTS ${LMDB_ROOT_DIR}/lib
|
||||||
)
|
)
|
||||||
|
|
||||||
|
add_library(lmdb UNKNOWN IMPORTED)
|
||||||
|
set_target_properties(lmdb PROPERTIES
|
||||||
|
IMPORTED_LOCATION ${LMDB_LIBRARIES}
|
||||||
|
)
|
||||||
|
|
||||||
find_path(LMDB_INCLUDE_DIRS
|
find_path(LMDB_INCLUDE_DIRS
|
||||||
NAMES lmdb.h
|
NAMES lmdb.h
|
||||||
HINTS ${LMDB_ROOT_DIR}/include
|
HINTS ${LMDB_ROOT_DIR}/include
|
||||||
|
15
cmake/FindSignal.cmake
Normal file
15
cmake/FindSignal.cmake
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
find_path(Signal_INCLUDE_DIR NAMES signal/signal_protocol.h)
|
||||||
|
find_library(Signal_LIBRARY signal-protocol-c)
|
||||||
|
mark_as_advanced(Signal_INCLUDE_DIR Signal_LIBRARY)
|
||||||
|
|
||||||
|
include(FindPackageHandleStandardArgs)
|
||||||
|
find_package_handle_standard_args(Signal REQUIRED_VARS Signal_LIBRARY Signal_INCLUDE_DIR)
|
||||||
|
|
||||||
|
if (Signal_FOUND AND NOT TARGET Signal::Signal)
|
||||||
|
add_library(Signal::Signal UNKNOWN IMPORTED)
|
||||||
|
set_target_properties(Signal::Signal PROPERTIES
|
||||||
|
IMPORTED_LINK_INTERFACE_LANGUAGES "C"
|
||||||
|
IMPORTED_LOCATION "${Signal_LIBRARY}"
|
||||||
|
INTERFACE_INCLUDE_DIRECTORIES "${Signal_INCLUDE_DIR}"
|
||||||
|
)
|
||||||
|
endif ()
|
41
cmake/MacOSXBundleInfo.plist.in
Normal file
41
cmake/MacOSXBundleInfo.plist.in
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
|
||||||
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
|
<string>English</string>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>${MACOSX_BUNDLE_EXECUTABLE_NAME}</string>
|
||||||
|
<key>CFBundleGetInfoString</key>
|
||||||
|
<string>${MACOSX_BUNDLE_INFO_STRING}</string>
|
||||||
|
<key>CFBundleIconFile</key>
|
||||||
|
<string>${MACOSX_BUNDLE_ICON_FILE}</string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>${MACOSX_BUNDLE_GUI_IDENTIFIER}</string>
|
||||||
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
|
<string>6.0</string>
|
||||||
|
<key>CFBundleLongVersionString</key>
|
||||||
|
<string>${MACOSX_BUNDLE_LONG_VERSION_STRING}</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>${MACOSX_BUNDLE_BUNDLE_NAME}</string>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>APPL</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>${MACOSX_BUNDLE_SHORT_VERSION_STRING}</string>
|
||||||
|
<key>CFBundleSignature</key>
|
||||||
|
<string>????</string>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>${MACOSX_BUNDLE_BUNDLE_VERSION}</string>
|
||||||
|
<key>CSResourcesFileMapped</key>
|
||||||
|
<true/>
|
||||||
|
<key>NSHumanReadableCopyright</key>
|
||||||
|
<string>${MACOSX_BUNDLE_COPYRIGHT}</string>
|
||||||
|
|
||||||
|
<key>NSPrincipalClass</key>
|
||||||
|
<string>NSApplication</string>
|
||||||
|
<key>NSHighResolutionCapable</key>
|
||||||
|
<string>True</string>
|
||||||
|
|
||||||
|
</dict>
|
||||||
|
</plist>
|
@ -1,49 +1,29 @@
|
|||||||
cmake_minimum_required(VERSION 3.3)
|
set(SIGNALCATCHER_SOURCE signalcatcher.cpp)
|
||||||
project(squawkCORE)
|
if(WIN32)
|
||||||
|
set(SIGNALCATCHER_SOURCE signalcatcher_win32.cpp)
|
||||||
|
endif(WIN32)
|
||||||
|
|
||||||
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake")
|
target_sources(squawk PRIVATE
|
||||||
|
|
||||||
set(CMAKE_AUTOMOC ON)
|
|
||||||
|
|
||||||
find_package(Qt5Core CONFIG REQUIRED)
|
|
||||||
find_package(Qt5Gui CONFIG REQUIRED)
|
|
||||||
find_package(Qt5Network CONFIG REQUIRED)
|
|
||||||
find_package(Qt5Xml CONFIG REQUIRED)
|
|
||||||
find_package(LMDB REQUIRED)
|
|
||||||
|
|
||||||
set(squawkCORE_SRC
|
|
||||||
squawk.cpp
|
|
||||||
account.cpp
|
account.cpp
|
||||||
archive.cpp
|
account.h
|
||||||
rosteritem.cpp
|
adapterfunctions.cpp
|
||||||
contact.cpp
|
adapterfunctions.h
|
||||||
conference.cpp
|
conference.cpp
|
||||||
urlstorage.cpp
|
conference.h
|
||||||
|
contact.cpp
|
||||||
|
contact.h
|
||||||
networkaccess.cpp
|
networkaccess.cpp
|
||||||
adapterFuctions.cpp
|
networkaccess.h
|
||||||
handlers/messagehandler.cpp
|
rosteritem.cpp
|
||||||
handlers/rosterhandler.cpp
|
rosteritem.h
|
||||||
|
${SIGNALCATCHER_SOURCE}
|
||||||
|
signalcatcher.h
|
||||||
|
squawk.cpp
|
||||||
|
squawk.h
|
||||||
)
|
)
|
||||||
|
|
||||||
|
target_include_directories(squawk PRIVATE ${LMDB_INCLUDE_DIRS})
|
||||||
|
|
||||||
|
add_subdirectory(handlers)
|
||||||
|
add_subdirectory(storage)
|
||||||
add_subdirectory(passwordStorageEngines)
|
add_subdirectory(passwordStorageEngines)
|
||||||
|
|
||||||
# Tell CMake to create the helloworld executable
|
|
||||||
add_library(squawkCORE STATIC ${squawkCORE_SRC})
|
|
||||||
|
|
||||||
|
|
||||||
if(SYSTEM_QXMPP)
|
|
||||||
get_target_property(QXMPP_INTERFACE_INCLUDE_DIRECTORIES QXmpp::QXmpp INTERFACE_INCLUDE_DIRECTORIES)
|
|
||||||
target_include_directories(squawkCORE PUBLIC ${QXMPP_INTERFACE_INCLUDE_DIRECTORIES})
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# 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()
|
|
||||||
|
576
core/account.cpp
576
core/account.cpp
@ -22,7 +22,7 @@
|
|||||||
|
|
||||||
using namespace Core;
|
using namespace Core;
|
||||||
|
|
||||||
Account::Account(const QString& p_login, const QString& p_server, const QString& p_password, const QString& p_name, NetworkAccess* p_net, QObject* parent):
|
Account::Account(const QString& p_login, const QString& p_server, const QString& p_password, const QString& p_name, bool p_active, NetworkAccess* p_net, QObject* parent):
|
||||||
QObject(parent),
|
QObject(parent),
|
||||||
name(p_name),
|
name(p_name),
|
||||||
archiveQueries(),
|
archiveQueries(),
|
||||||
@ -41,13 +41,15 @@ Account::Account(const QString& p_login, const QString& p_server, const QString&
|
|||||||
rcpm(new QXmppMessageReceiptManager()),
|
rcpm(new QXmppMessageReceiptManager()),
|
||||||
reconnectScheduled(false),
|
reconnectScheduled(false),
|
||||||
reconnectTimer(new QTimer),
|
reconnectTimer(new QTimer),
|
||||||
avatarHash(),
|
|
||||||
avatarType(),
|
|
||||||
ownVCardRequestInProgress(false),
|
|
||||||
network(p_net),
|
network(p_net),
|
||||||
passwordType(Shared::AccountPassword::plain),
|
passwordType(Shared::AccountPassword::plain),
|
||||||
|
lastError(Error::none),
|
||||||
|
pepSupport(false),
|
||||||
|
active(p_active),
|
||||||
|
notReadyPassword(false),
|
||||||
mh(new MessageHandler(this)),
|
mh(new MessageHandler(this)),
|
||||||
rh(new RosterHandler(this))
|
rh(new RosterHandler(this)),
|
||||||
|
vh(new VCardHandler(this))
|
||||||
{
|
{
|
||||||
config.setUser(p_login);
|
config.setUser(p_login);
|
||||||
config.setDomain(p_server);
|
config.setDomain(p_server);
|
||||||
@ -73,10 +75,6 @@ Account::Account(const QString& p_login, const QString& p_server, const QString&
|
|||||||
|
|
||||||
client.addExtension(mm);
|
client.addExtension(mm);
|
||||||
client.addExtension(bm);
|
client.addExtension(bm);
|
||||||
|
|
||||||
QObject::connect(vm, &QXmppVCardManager::vCardReceived, this, &Account::onVCardReceived);
|
|
||||||
//QObject::connect(&vm, &QXmppVCardManager::clientVCardReceived, this, &Account::onOwnVCardReceived); //for some reason it doesn't work, launching from common handler
|
|
||||||
|
|
||||||
client.addExtension(um);
|
client.addExtension(um);
|
||||||
QObject::connect(um, &QXmppUploadRequestManager::slotReceived, mh, &MessageHandler::onUploadSlotReceived);
|
QObject::connect(um, &QXmppUploadRequestManager::slotReceived, mh, &MessageHandler::onUploadSlotReceived);
|
||||||
QObject::connect(um, &QXmppUploadRequestManager::requestFailed, mh, &MessageHandler::onUploadSlotRequestFailed);
|
QObject::connect(um, &QXmppUploadRequestManager::requestFailed, mh, &MessageHandler::onUploadSlotRequestFailed);
|
||||||
@ -91,62 +89,18 @@ Account::Account(const QString& p_login, const QString& p_server, const QString&
|
|||||||
client.addExtension(rcpm);
|
client.addExtension(rcpm);
|
||||||
QObject::connect(rcpm, &QXmppMessageReceiptManager::messageDelivered, mh, &MessageHandler::onReceiptReceived);
|
QObject::connect(rcpm, &QXmppMessageReceiptManager::messageDelivered, mh, &MessageHandler::onReceiptReceived);
|
||||||
|
|
||||||
|
|
||||||
QString path(QStandardPaths::writableLocation(QStandardPaths::CacheLocation));
|
|
||||||
path += "/" + name;
|
|
||||||
QDir dir(path);
|
|
||||||
|
|
||||||
if (!dir.exists()) {
|
|
||||||
bool res = dir.mkpath(path);
|
|
||||||
if (!res) {
|
|
||||||
qDebug() << "Couldn't create a cache directory for account" << name;
|
|
||||||
throw 22;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QFile* avatar = new QFile(path + "/avatar.png");
|
|
||||||
QString type = "png";
|
|
||||||
if (!avatar->exists()) {
|
|
||||||
delete avatar;
|
|
||||||
avatar = new QFile(path + "/avatar.jpg");
|
|
||||||
type = "jpg";
|
|
||||||
if (!avatar->exists()) {
|
|
||||||
delete avatar;
|
|
||||||
avatar = new QFile(path + "/avatar.jpeg");
|
|
||||||
type = "jpeg";
|
|
||||||
if (!avatar->exists()) {
|
|
||||||
delete avatar;
|
|
||||||
avatar = new QFile(path + "/avatar.gif");
|
|
||||||
type = "gif";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (avatar->exists()) {
|
|
||||||
if (avatar->open(QFile::ReadOnly)) {
|
|
||||||
QCryptographicHash sha1(QCryptographicHash::Sha1);
|
|
||||||
sha1.addData(avatar);
|
|
||||||
avatarHash = sha1.result();
|
|
||||||
avatarType = type;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (avatarType.size() != 0) {
|
|
||||||
presence.setVCardUpdateType(QXmppPresence::VCardUpdateValidPhoto);
|
|
||||||
presence.setPhotoHash(avatarHash.toUtf8());
|
|
||||||
} else {
|
|
||||||
presence.setVCardUpdateType(QXmppPresence::VCardUpdateNotReady);
|
|
||||||
}
|
|
||||||
|
|
||||||
reconnectTimer->setSingleShot(true);
|
reconnectTimer->setSingleShot(true);
|
||||||
QObject::connect(reconnectTimer, &QTimer::timeout, this, &Account::onReconnectTimer);
|
QObject::connect(reconnectTimer, &QTimer::timeout, this, &Account::onReconnectTimer);
|
||||||
|
|
||||||
// QXmppLogger* logger = new QXmppLogger(this);
|
if (name == "Test") {
|
||||||
// logger->setLoggingType(QXmppLogger::SignalLogging);
|
QXmppLogger* logger = new QXmppLogger(this);
|
||||||
// client.setLogger(logger);
|
logger->setLoggingType(QXmppLogger::SignalLogging);
|
||||||
//
|
client.setLogger(logger);
|
||||||
// QObject::connect(logger, &QXmppLogger::message, this, [](QXmppLogger::MessageType type, const QString& text){
|
|
||||||
// qDebug() << text;
|
QObject::connect(logger, &QXmppLogger::message, this, [](QXmppLogger::MessageType type, const QString& text){
|
||||||
// });
|
qDebug() << text;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Account::~Account()
|
Account::~Account()
|
||||||
@ -160,6 +114,7 @@ Account::~Account()
|
|||||||
QObject::disconnect(network, &NetworkAccess::downloadFileComplete, mh, &MessageHandler::onDownloadFileComplete);
|
QObject::disconnect(network, &NetworkAccess::downloadFileComplete, mh, &MessageHandler::onDownloadFileComplete);
|
||||||
QObject::disconnect(network, &NetworkAccess::loadFileError, mh, &MessageHandler::onLoadFileError);
|
QObject::disconnect(network, &NetworkAccess::loadFileError, mh, &MessageHandler::onLoadFileError);
|
||||||
|
|
||||||
|
delete vh;
|
||||||
delete mh;
|
delete mh;
|
||||||
delete rh;
|
delete rh;
|
||||||
|
|
||||||
@ -185,7 +140,12 @@ void Core::Account::connect()
|
|||||||
reconnectTimer->stop();
|
reconnectTimer->stop();
|
||||||
}
|
}
|
||||||
if (state == Shared::ConnectionState::disconnected) {
|
if (state == Shared::ConnectionState::disconnected) {
|
||||||
|
if (notReadyPassword) {
|
||||||
|
emit needPassword();
|
||||||
|
} else {
|
||||||
client.connectToServer(config, presence);
|
client.connectToServer(config, presence);
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
qDebug("An attempt to connect an account which is already connected, skipping");
|
qDebug("An attempt to connect an account which is already connected, skipping");
|
||||||
}
|
}
|
||||||
@ -224,6 +184,7 @@ void Core::Account::onClientStateChange(QXmppClient::State st)
|
|||||||
dm->requestItems(getServer());
|
dm->requestItems(getServer());
|
||||||
dm->requestInfo(getServer());
|
dm->requestInfo(getServer());
|
||||||
}
|
}
|
||||||
|
lastError = Error::none;
|
||||||
emit connectionStateChanged(state);
|
emit connectionStateChanged(state);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -255,7 +216,8 @@ void Core::Account::onClientStateChange(QXmppClient::State st)
|
|||||||
|
|
||||||
void Core::Account::reconnect()
|
void Core::Account::reconnect()
|
||||||
{
|
{
|
||||||
if (state == Shared::ConnectionState::connected && !reconnectScheduled) {
|
if (!reconnectScheduled) { //TODO define behavior if It was connection or disconnecting
|
||||||
|
if (state == Shared::ConnectionState::connected) {
|
||||||
reconnectScheduled = true;
|
reconnectScheduled = true;
|
||||||
reconnectTimer->start(500);
|
reconnectTimer->start(500);
|
||||||
client.disconnectFromServer();
|
client.disconnectFromServer();
|
||||||
@ -263,36 +225,7 @@ void Core::Account::reconnect()
|
|||||||
qDebug() << "An attempt to reconnect account" << getName() << "which was not connected";
|
qDebug() << "An attempt to reconnect account" << getName() << "which was not connected";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
QString Core::Account::getName() const {
|
|
||||||
return name;}
|
|
||||||
|
|
||||||
QString Core::Account::getLogin() const {
|
|
||||||
return config.user();}
|
|
||||||
|
|
||||||
QString Core::Account::getPassword() const {
|
|
||||||
return config.password();}
|
|
||||||
|
|
||||||
QString Core::Account::getServer() const {
|
|
||||||
return config.domain();}
|
|
||||||
|
|
||||||
Shared::AccountPassword Core::Account::getPasswordType() const {
|
|
||||||
return passwordType;}
|
|
||||||
|
|
||||||
void Core::Account::setPasswordType(Shared::AccountPassword pt) {
|
|
||||||
passwordType = pt; }
|
|
||||||
|
|
||||||
void Core::Account::setLogin(const QString& p_login) {
|
|
||||||
config.setUser(p_login);}
|
|
||||||
|
|
||||||
void Core::Account::setName(const QString& p_name) {
|
|
||||||
name = p_name;}
|
|
||||||
|
|
||||||
void Core::Account::setPassword(const QString& p_password) {
|
|
||||||
config.setPassword(p_password);}
|
|
||||||
|
|
||||||
void Core::Account::setServer(const QString& p_server) {
|
|
||||||
config.setDomain(p_server);}
|
|
||||||
|
|
||||||
Shared::Availability Core::Account::getAvailability() const
|
Shared::Availability Core::Account::getAvailability() const
|
||||||
{
|
{
|
||||||
@ -325,32 +258,11 @@ void Core::Account::onPresenceReceived(const QXmppPresence& p_presence)
|
|||||||
QString jid = comps.front().toLower();
|
QString jid = comps.front().toLower();
|
||||||
QString resource = comps.back();
|
QString resource = comps.back();
|
||||||
|
|
||||||
QString myJid = getLogin() + "@" + getServer();
|
if (jid == getBareJid()) {
|
||||||
|
|
||||||
if (jid == myJid) {
|
|
||||||
if (resource == getResource()) {
|
if (resource == getResource()) {
|
||||||
emit availabilityChanged(static_cast<Shared::Availability>(p_presence.availableStatusType()));
|
emit availabilityChanged(static_cast<Shared::Availability>(p_presence.availableStatusType()));
|
||||||
} else {
|
} else {
|
||||||
if (!ownVCardRequestInProgress) {
|
vh->handleOtherPresenceOfMyAccountChange(p_presence);
|
||||||
switch (p_presence.vCardUpdateType()) {
|
|
||||||
case QXmppPresence::VCardUpdateNone: //this presence has nothing to do with photo
|
|
||||||
break;
|
|
||||||
case QXmppPresence::VCardUpdateNotReady: //let's say the photo didn't change here
|
|
||||||
break;
|
|
||||||
case QXmppPresence::VCardUpdateNoPhoto: //there is no photo, need to drop if any
|
|
||||||
if (avatarType.size() > 0) {
|
|
||||||
vm->requestClientVCard();
|
|
||||||
ownVCardRequestInProgress = true;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case QXmppPresence::VCardUpdateValidPhoto: //there is a photo, need to load
|
|
||||||
if (avatarHash != p_presence.photoHash()) {
|
|
||||||
vm->requestClientVCard();
|
|
||||||
ownVCardRequestInProgress = true;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
RosterItem* item = rh->getRosterItem(jid);
|
RosterItem* item = rh->getRosterItem(jid);
|
||||||
@ -392,18 +304,6 @@ void Core::Account::onPresenceReceived(const QXmppPresence& p_presence)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QString Core::Account::getResource() const {
|
|
||||||
return config.resource();}
|
|
||||||
|
|
||||||
void Core::Account::setResource(const QString& p_resource) {
|
|
||||||
config.setResource(p_resource);}
|
|
||||||
|
|
||||||
QString Core::Account::getFullJid() const {
|
|
||||||
return getLogin() + "@" + getServer() + "/" + getResource();}
|
|
||||||
|
|
||||||
void Core::Account::sendMessage(const Shared::Message& data) {
|
|
||||||
mh->sendMessage(data);}
|
|
||||||
|
|
||||||
void Core::Account::onMamMessageReceived(const QString& queryId, const QXmppMessage& msg)
|
void Core::Account::onMamMessageReceived(const QString& queryId, const QXmppMessage& msg)
|
||||||
{
|
{
|
||||||
if (msg.id().size() > 0 && (msg.body().size() > 0 || msg.outOfBandUrl().size() > 0)) {
|
if (msg.id().size() > 0 && (msg.body().size() > 0 || msg.outOfBandUrl().size() > 0)) {
|
||||||
@ -469,6 +369,14 @@ void Core::Account::onContactNeedHistory(const QString& before, const QString& a
|
|||||||
query.setAfter(after);
|
query.setAfter(after);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (before.size() == 0 && after.size() == 0) {
|
||||||
|
// https://xmpp.org/extensions/xep-0313.html#sect-idm46556759682304
|
||||||
|
// To request the page at the end of the archive
|
||||||
|
// (i.e. the most recent messages), include just an
|
||||||
|
// empty <before/> element in the RSM part of the query.
|
||||||
|
// As defined by RSM, this will return the last page of the archive.
|
||||||
|
query.setBefore("");
|
||||||
|
}
|
||||||
qDebug() << "Remote query for" << contact->jid << "from" << after << ", to" << before;
|
qDebug() << "Remote query for" << contact->jid << "from" << after << ", to" << before;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -509,6 +417,7 @@ void Core::Account::onClientError(QXmppClient::Error err)
|
|||||||
qDebug() << "Error";
|
qDebug() << "Error";
|
||||||
QString errorText;
|
QString errorText;
|
||||||
QString errorType;
|
QString errorType;
|
||||||
|
lastError = Error::other;
|
||||||
switch (err) {
|
switch (err) {
|
||||||
case QXmppClient::SocketError:
|
case QXmppClient::SocketError:
|
||||||
errorText = client.socketErrorString();
|
errorText = client.socketErrorString();
|
||||||
@ -550,6 +459,7 @@ void Core::Account::onClientError(QXmppClient::Error err)
|
|||||||
break;
|
break;
|
||||||
case QXmppStanza::Error::NotAuthorized:
|
case QXmppStanza::Error::NotAuthorized:
|
||||||
errorText = "Authentication error";
|
errorText = "Authentication error";
|
||||||
|
lastError = Error::authentication;
|
||||||
break;
|
break;
|
||||||
#if (QXMPP_VERSION) < QT_VERSION_CHECK(1, 3, 0)
|
#if (QXMPP_VERSION) < QT_VERSION_CHECK(1, 3, 0)
|
||||||
case QXmppStanza::Error::PaymentRequired:
|
case QXmppStanza::Error::PaymentRequired:
|
||||||
@ -593,6 +503,9 @@ void Core::Account::onClientError(QXmppClient::Error err)
|
|||||||
errorText = "Policy violation";
|
errorText = "Policy violation";
|
||||||
break;
|
break;
|
||||||
#endif
|
#endif
|
||||||
|
default:
|
||||||
|
errorText = "Unknown Error";
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
errorType = "Client stream error";
|
errorType = "Client stream error";
|
||||||
@ -656,6 +569,166 @@ void Core::Account::setRoomJoined(const QString& jid, bool joined)
|
|||||||
conf->setJoined(joined);
|
conf->setJoined(joined);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Core::Account::onDiscoveryItemsReceived(const QXmppDiscoveryIq& items)
|
||||||
|
{
|
||||||
|
if (items.from() == getServer()) {
|
||||||
|
std::set<QString> needToRequest;
|
||||||
|
qDebug() << "Server items list received for account " << name << ":";
|
||||||
|
for (QXmppDiscoveryIq::Item item : items.items()) {
|
||||||
|
QString jid = item.jid();
|
||||||
|
if (jid != getServer()) {
|
||||||
|
qDebug() << " Node" << jid;
|
||||||
|
needToRequest.insert(jid);
|
||||||
|
} else {
|
||||||
|
qDebug() << " " << item.node().toStdString().c_str();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const QString& jid : needToRequest) {
|
||||||
|
dm->requestInfo(jid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Core::Account::onDiscoveryInfoReceived(const QXmppDiscoveryIq& info)
|
||||||
|
{
|
||||||
|
if (info.from() == getServer()) {
|
||||||
|
bool enableCC = false;
|
||||||
|
qDebug() << "Server info received for account" << name;
|
||||||
|
QStringList features = info.features();
|
||||||
|
qDebug() << "List of supported features of the server " << getServer() << ":";
|
||||||
|
for (const QString& feature : features) {
|
||||||
|
qDebug() << " " << feature.toStdString().c_str();
|
||||||
|
if (feature == "urn:xmpp:carbons:2") {
|
||||||
|
enableCC = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (enableCC) {
|
||||||
|
qDebug() << "Enabling carbon copies for account" << name;
|
||||||
|
cm->setCarbonsEnabled(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug() << "Requesting account" << name << "capabilities";
|
||||||
|
dm->requestInfo(getBareJid());
|
||||||
|
} else if (info.from() == getBareJid()) {
|
||||||
|
qDebug() << "Received capabilities for account" << name << ":";
|
||||||
|
QList<QXmppDiscoveryIq::Identity> identities = info.identities();
|
||||||
|
bool pepSupported = false;
|
||||||
|
for (const QXmppDiscoveryIq::Identity& identity : identities) {
|
||||||
|
QString type = identity.type();
|
||||||
|
qDebug() << " " << identity.category() << type;
|
||||||
|
if (type == "pep") {
|
||||||
|
pepSupported = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rh->setPepSupport(pepSupported);
|
||||||
|
} else {
|
||||||
|
qDebug() << "Received info for account" << name << "about" << info.from();
|
||||||
|
QList<QXmppDiscoveryIq::Identity> identities = info.identities();
|
||||||
|
for (const QXmppDiscoveryIq::Identity& identity : identities) {
|
||||||
|
qDebug() << " " << identity.name() << identity.category() << identity.type();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Core::Account::handleDisconnection()
|
||||||
|
{
|
||||||
|
cm->setCarbonsEnabled(false);
|
||||||
|
rh->handleOffline();
|
||||||
|
vh->handleOffline();
|
||||||
|
archiveQueries.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Core::Account::onContactHistoryResponse(const std::list<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);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Core::Account::getActive() const {
|
||||||
|
return active;}
|
||||||
|
|
||||||
|
void Core::Account::setActive(bool p_active) {
|
||||||
|
if (active != p_active) {
|
||||||
|
active = p_active;
|
||||||
|
|
||||||
|
emit changed({
|
||||||
|
{"active", active}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QString Core::Account::getResource() const {
|
||||||
|
return config.resource();}
|
||||||
|
|
||||||
|
void Core::Account::setResource(const QString& p_resource) {
|
||||||
|
config.setResource(p_resource);}
|
||||||
|
|
||||||
|
QString Core::Account::getBareJid() const {
|
||||||
|
return getLogin() + "@" + getServer();}
|
||||||
|
|
||||||
|
QString Core::Account::getFullJid() const {
|
||||||
|
return getBareJid() + "/" + getResource();}
|
||||||
|
|
||||||
|
QString Core::Account::getName() const {
|
||||||
|
return name;}
|
||||||
|
|
||||||
|
QString Core::Account::getLogin() const {
|
||||||
|
return config.user();}
|
||||||
|
|
||||||
|
QString Core::Account::getPassword() const {
|
||||||
|
return config.password();}
|
||||||
|
|
||||||
|
QString Core::Account::getServer() const {
|
||||||
|
return config.domain();}
|
||||||
|
|
||||||
|
Shared::AccountPassword Core::Account::getPasswordType() const {
|
||||||
|
return passwordType;}
|
||||||
|
|
||||||
|
void Core::Account::setPasswordType(Shared::AccountPassword pt) {
|
||||||
|
passwordType = pt; }
|
||||||
|
|
||||||
|
void Core::Account::setLogin(const QString& p_login) {
|
||||||
|
config.setUser(p_login);}
|
||||||
|
|
||||||
|
void Core::Account::setName(const QString& p_name) {
|
||||||
|
name = p_name;}
|
||||||
|
|
||||||
|
void Core::Account::setPassword(const QString& p_password) {
|
||||||
|
config.setPassword(p_password);
|
||||||
|
notReadyPassword = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Core::Account::setServer(const QString& p_server) {
|
||||||
|
config.setDomain(p_server);}
|
||||||
|
|
||||||
|
void Core::Account::sendMessage(const Shared::Message& data) {
|
||||||
|
mh->sendMessage(data);}
|
||||||
|
|
||||||
|
void Core::Account::requestChangeMessage(const QString& jid, const QString& messageId, const QMap<QString, QVariant>& data){
|
||||||
|
mh->requestChangeMessage(jid, messageId, data);}
|
||||||
|
|
||||||
|
void Core::Account::resendMessage(const QString& jid, const QString& id) {
|
||||||
|
mh->resendMessage(jid, id);}
|
||||||
|
|
||||||
|
void Core::Account::replaceMessage(const QString& originalId, const Shared::Message& data) {
|
||||||
|
mh->sendMessage(data, false, originalId);}
|
||||||
|
|
||||||
|
void Core::Account::requestVCard(const QString& jid) {
|
||||||
|
vh->requestVCard(jid);}
|
||||||
|
|
||||||
|
void Core::Account::uploadVCard(const Shared::VCard& card) {
|
||||||
|
vh->uploadVCard(card);}
|
||||||
|
|
||||||
|
QString Core::Account::getAvatarPath() const {
|
||||||
|
return vh->getAvatarPath();}
|
||||||
|
|
||||||
void Core::Account::removeRoomRequest(const QString& jid){
|
void Core::Account::removeRoomRequest(const QString& jid){
|
||||||
rh->removeRoomRequest(jid);}
|
rh->removeRoomRequest(jid);}
|
||||||
|
|
||||||
@ -678,248 +751,9 @@ void Core::Account::renameContactRequest(const QString& jid, const QString& newN
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Core::Account::onVCardReceived(const QXmppVCardIq& card)
|
void Core::Account::invalidatePassword() {
|
||||||
{
|
notReadyPassword = true;}
|
||||||
QString id = card.from();
|
|
||||||
QStringList comps = id.split("/");
|
|
||||||
QString jid = comps.front().toLower();
|
|
||||||
QString resource("");
|
|
||||||
if (comps.size() > 1) {
|
|
||||||
resource = comps.back();
|
|
||||||
}
|
|
||||||
pendingVCardRequests.erase(id);
|
|
||||||
RosterItem* item = rh->getRosterItem(jid);
|
|
||||||
|
|
||||||
if (item == 0) {
|
Core::Account::Error Core::Account::getLastError() const {
|
||||||
if (jid == getLogin() + "@" + getServer()) {
|
return lastError;}
|
||||||
onOwnVCardReceived(card);
|
|
||||||
} else {
|
|
||||||
qDebug() << "received vCard" << jid << "doesn't belong to any of known contacts or conferences, skipping";
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Shared::VCard vCard = item->handleResponseVCard(card, resource);
|
|
||||||
|
|
||||||
emit receivedVCard(jid, vCard);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Core::Account::onOwnVCardReceived(const QXmppVCardIq& card)
|
|
||||||
{
|
|
||||||
QByteArray ava = card.photo();
|
|
||||||
bool avaChanged = false;
|
|
||||||
QString path = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/" + name + "/";
|
|
||||||
if (ava.size() > 0) {
|
|
||||||
QCryptographicHash sha1(QCryptographicHash::Sha1);
|
|
||||||
sha1.addData(ava);
|
|
||||||
QString newHash(sha1.result());
|
|
||||||
QMimeDatabase db;
|
|
||||||
QMimeType newType = db.mimeTypeForData(ava);
|
|
||||||
if (avatarType.size() > 0) {
|
|
||||||
if (avatarHash != newHash) {
|
|
||||||
QString oldPath = path + "avatar." + avatarType;
|
|
||||||
QFile oldAvatar(oldPath);
|
|
||||||
bool oldToRemove = false;
|
|
||||||
if (oldAvatar.exists()) {
|
|
||||||
if (oldAvatar.rename(oldPath + ".bak")) {
|
|
||||||
oldToRemove = true;
|
|
||||||
} else {
|
|
||||||
qDebug() << "Received new avatar for account" << name << "but can't get rid of the old one, doing nothing";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
QFile newAvatar(path + "avatar." + newType.preferredSuffix());
|
|
||||||
if (newAvatar.open(QFile::WriteOnly)) {
|
|
||||||
newAvatar.write(ava);
|
|
||||||
newAvatar.close();
|
|
||||||
avatarHash = newHash;
|
|
||||||
avatarType = newType.preferredSuffix();
|
|
||||||
avaChanged = true;
|
|
||||||
} else {
|
|
||||||
qDebug() << "Received new avatar for account" << name << "but can't save it";
|
|
||||||
if (oldToRemove) {
|
|
||||||
qDebug() << "rolling back to the old avatar";
|
|
||||||
if (!oldAvatar.rename(oldPath)) {
|
|
||||||
qDebug() << "Couldn't roll back to the old avatar in account" << name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
QFile newAvatar(path + "avatar." + newType.preferredSuffix());
|
|
||||||
if (newAvatar.open(QFile::WriteOnly)) {
|
|
||||||
newAvatar.write(ava);
|
|
||||||
newAvatar.close();
|
|
||||||
avatarHash = newHash;
|
|
||||||
avatarType = newType.preferredSuffix();
|
|
||||||
avaChanged = true;
|
|
||||||
} else {
|
|
||||||
qDebug() << "Received new avatar for account" << name << "but can't save it";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (avatarType.size() > 0) {
|
|
||||||
QFile oldAvatar(path + "avatar." + avatarType);
|
|
||||||
if (!oldAvatar.remove()) {
|
|
||||||
qDebug() << "Received vCard for account" << name << "without avatar, but can't get rid of the file, doing nothing";
|
|
||||||
} else {
|
|
||||||
avatarType = "";
|
|
||||||
avatarHash = "";
|
|
||||||
avaChanged = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (avaChanged) {
|
|
||||||
QMap<QString, QVariant> change;
|
|
||||||
if (avatarType.size() > 0) {
|
|
||||||
presence.setPhotoHash(avatarHash.toUtf8());
|
|
||||||
presence.setVCardUpdateType(QXmppPresence::VCardUpdateValidPhoto);
|
|
||||||
change.insert("avatarPath", path + "avatar." + avatarType);
|
|
||||||
} else {
|
|
||||||
presence.setPhotoHash("");
|
|
||||||
presence.setVCardUpdateType(QXmppPresence::VCardUpdateNoPhoto);
|
|
||||||
change.insert("avatarPath", "");
|
|
||||||
}
|
|
||||||
client.setClientPresence(presence);
|
|
||||||
emit changed(change);
|
|
||||||
}
|
|
||||||
|
|
||||||
ownVCardRequestInProgress = false;
|
|
||||||
|
|
||||||
Shared::VCard vCard;
|
|
||||||
initializeVCard(vCard, card);
|
|
||||||
|
|
||||||
if (avatarType.size() > 0) {
|
|
||||||
vCard.setAvatarType(Shared::Avatar::valid);
|
|
||||||
vCard.setAvatarPath(path + "avatar." + avatarType);
|
|
||||||
} else {
|
|
||||||
vCard.setAvatarType(Shared::Avatar::empty);
|
|
||||||
}
|
|
||||||
|
|
||||||
emit receivedVCard(getLogin() + "@" + getServer(), vCard);
|
|
||||||
}
|
|
||||||
|
|
||||||
QString Core::Account::getAvatarPath() const
|
|
||||||
{
|
|
||||||
if (avatarType.size() == 0) {
|
|
||||||
return "";
|
|
||||||
} else {
|
|
||||||
return QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/" + name + "/" + "avatar." + avatarType;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Core::Account::requestVCard(const QString& jid)
|
|
||||||
{
|
|
||||||
if (pendingVCardRequests.find(jid) == pendingVCardRequests.end()) {
|
|
||||||
qDebug() << "requesting vCard" << jid;
|
|
||||||
if (jid == getLogin() + "@" + getServer()) {
|
|
||||||
if (!ownVCardRequestInProgress) {
|
|
||||||
vm->requestClientVCard();
|
|
||||||
ownVCardRequestInProgress = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
vm->requestVCard(jid);
|
|
||||||
pendingVCardRequests.insert(jid);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Core::Account::uploadVCard(const Shared::VCard& card)
|
|
||||||
{
|
|
||||||
QXmppVCardIq iq;
|
|
||||||
initializeQXmppVCard(iq, card);
|
|
||||||
|
|
||||||
bool avatarChanged = false;
|
|
||||||
if (card.getAvatarType() != Shared::Avatar::empty) {
|
|
||||||
QString newPath = card.getAvatarPath();
|
|
||||||
QString oldPath = getAvatarPath();
|
|
||||||
QByteArray data;
|
|
||||||
QString type;
|
|
||||||
if (newPath != oldPath) {
|
|
||||||
QFile avatar(newPath);
|
|
||||||
if (!avatar.open(QFile::ReadOnly)) {
|
|
||||||
qDebug() << "An attempt to upload new vCard to account" << name
|
|
||||||
<< "but it wasn't possible to read file" << newPath
|
|
||||||
<< "which was supposed to be new avatar, uploading old avatar";
|
|
||||||
if (avatarType.size() > 0) {
|
|
||||||
QFile oA(oldPath);
|
|
||||||
if (!oA.open(QFile::ReadOnly)) {
|
|
||||||
qDebug() << "Couldn't read old avatar of account" << name << ", uploading empty avatar";
|
|
||||||
} else {
|
|
||||||
data = oA.readAll();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
data = avatar.readAll();
|
|
||||||
avatarChanged = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (avatarType.size() > 0) {
|
|
||||||
QFile oA(oldPath);
|
|
||||||
if (!oA.open(QFile::ReadOnly)) {
|
|
||||||
qDebug() << "Couldn't read old avatar of account" << name << ", uploading empty avatar";
|
|
||||||
} else {
|
|
||||||
data = oA.readAll();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.size() > 0) {
|
|
||||||
QMimeDatabase db;
|
|
||||||
type = db.mimeTypeForData(data).name();
|
|
||||||
iq.setPhoto(data);
|
|
||||||
iq.setPhotoType(type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
vm->setClientVCard(iq);
|
|
||||||
onOwnVCardReceived(iq);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Core::Account::onDiscoveryItemsReceived(const QXmppDiscoveryIq& items)
|
|
||||||
{
|
|
||||||
for (QXmppDiscoveryIq::Item item : items.items()) {
|
|
||||||
if (item.jid() != getServer()) {
|
|
||||||
dm->requestInfo(item.jid());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Core::Account::onDiscoveryInfoReceived(const QXmppDiscoveryIq& info)
|
|
||||||
{
|
|
||||||
qDebug() << "Discovery info received for account" << name;
|
|
||||||
if (info.from() == getServer()) {
|
|
||||||
if (info.features().contains("urn:xmpp:carbons:2")) {
|
|
||||||
qDebug() << "Enabling carbon copies for account" << name;
|
|
||||||
cm->setCarbonsEnabled(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Core::Account::handleDisconnection()
|
|
||||||
{
|
|
||||||
cm->setCarbonsEnabled(false);
|
|
||||||
rh->handleOffline();
|
|
||||||
archiveQueries.clear();
|
|
||||||
pendingVCardRequests.clear();
|
|
||||||
Shared::VCard vCard; //just to show, that there is now more pending request
|
|
||||||
for (const QString& jid : pendingVCardRequests) {
|
|
||||||
emit receivedVCard(jid, vCard); //need to show it better in the future, like with an error
|
|
||||||
}
|
|
||||||
pendingVCardRequests.clear();
|
|
||||||
ownVCardRequestInProgress = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Core::Account::onContactHistoryResponse(const std::list<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);}
|
|
||||||
|
@ -39,17 +39,17 @@
|
|||||||
#include <QXmppBookmarkManager.h>
|
#include <QXmppBookmarkManager.h>
|
||||||
#include <QXmppBookmarkSet.h>
|
#include <QXmppBookmarkSet.h>
|
||||||
#include <QXmppUploadRequestManager.h>
|
#include <QXmppUploadRequestManager.h>
|
||||||
#include <QXmppVCardIq.h>
|
|
||||||
#include <QXmppVCardManager.h>
|
#include <QXmppVCardManager.h>
|
||||||
#include <QXmppMessageReceiptManager.h>
|
#include <QXmppMessageReceiptManager.h>
|
||||||
|
|
||||||
#include "shared.h"
|
#include "shared/shared.h"
|
||||||
#include "contact.h"
|
#include "contact.h"
|
||||||
#include "conference.h"
|
#include "conference.h"
|
||||||
#include "networkaccess.h"
|
#include "networkaccess.h"
|
||||||
|
|
||||||
#include "handlers/messagehandler.h"
|
#include "handlers/messagehandler.h"
|
||||||
#include "handlers/rosterhandler.h"
|
#include "handlers/rosterhandler.h"
|
||||||
|
#include "handlers/vcardhandler.h"
|
||||||
|
|
||||||
namespace Core
|
namespace Core
|
||||||
{
|
{
|
||||||
@ -59,12 +59,20 @@ class Account : public QObject
|
|||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
friend class MessageHandler;
|
friend class MessageHandler;
|
||||||
friend class RosterHandler;
|
friend class RosterHandler;
|
||||||
|
friend class VCardHandler;
|
||||||
public:
|
public:
|
||||||
|
enum class Error {
|
||||||
|
authentication,
|
||||||
|
other,
|
||||||
|
none
|
||||||
|
};
|
||||||
|
|
||||||
Account(
|
Account(
|
||||||
const QString& p_login,
|
const QString& p_login,
|
||||||
const QString& p_server,
|
const QString& p_server,
|
||||||
const QString& p_password,
|
const QString& p_password,
|
||||||
const QString& p_name,
|
const QString& p_name,
|
||||||
|
bool p_active,
|
||||||
NetworkAccess* p_net,
|
NetworkAccess* p_net,
|
||||||
QObject* parent = 0);
|
QObject* parent = 0);
|
||||||
~Account();
|
~Account();
|
||||||
@ -76,8 +84,12 @@ public:
|
|||||||
QString getPassword() const;
|
QString getPassword() const;
|
||||||
QString getResource() const;
|
QString getResource() const;
|
||||||
QString getAvatarPath() const;
|
QString getAvatarPath() const;
|
||||||
|
QString getBareJid() const;
|
||||||
|
QString getFullJid() const;
|
||||||
Shared::Availability getAvailability() const;
|
Shared::Availability getAvailability() const;
|
||||||
Shared::AccountPassword getPasswordType() const;
|
Shared::AccountPassword getPasswordType() const;
|
||||||
|
Error getLastError() const;
|
||||||
|
bool getActive() const;
|
||||||
|
|
||||||
void setName(const QString& p_name);
|
void setName(const QString& p_name);
|
||||||
void setLogin(const QString& p_login);
|
void setLogin(const QString& p_login);
|
||||||
@ -86,8 +98,8 @@ public:
|
|||||||
void setResource(const QString& p_resource);
|
void setResource(const QString& p_resource);
|
||||||
void setAvailability(Shared::Availability avail);
|
void setAvailability(Shared::Availability avail);
|
||||||
void setPasswordType(Shared::AccountPassword pt);
|
void setPasswordType(Shared::AccountPassword pt);
|
||||||
QString getFullJid() const;
|
|
||||||
void sendMessage(const Shared::Message& data);
|
void sendMessage(const Shared::Message& data);
|
||||||
|
void setActive(bool p_active);
|
||||||
void requestArchive(const QString& jid, int count, const QString& before);
|
void requestArchive(const QString& jid, int count, const QString& before);
|
||||||
void subscribeToContact(const QString& jid, const QString& reason);
|
void subscribeToContact(const QString& jid, const QString& reason);
|
||||||
void unsubscribeFromContact(const QString& jid, const QString& reason);
|
void unsubscribeFromContact(const QString& jid, const QString& reason);
|
||||||
@ -103,6 +115,9 @@ public:
|
|||||||
void removeRoomRequest(const QString& jid);
|
void removeRoomRequest(const QString& jid);
|
||||||
void addRoomRequest(const QString& jid, const QString& nick, const QString& password, bool autoJoin);
|
void addRoomRequest(const QString& jid, const QString& nick, const QString& password, bool autoJoin);
|
||||||
void uploadVCard(const Shared::VCard& card);
|
void uploadVCard(const Shared::VCard& card);
|
||||||
|
void resendMessage(const QString& jid, const QString& id);
|
||||||
|
void replaceMessage(const QString& originalId, const Shared::Message& data);
|
||||||
|
void invalidatePassword();
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void connect();
|
void connect();
|
||||||
@ -135,6 +150,7 @@ signals:
|
|||||||
void receivedVCard(const QString& jid, const Shared::VCard& card);
|
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 uploadFile(const QFileInfo& file, const QUrl& set, const QUrl& get, QMap<QString, QString> headers);
|
||||||
void uploadFileError(const QString& jid, const QString& messageId, const QString& error);
|
void uploadFileError(const QString& jid, const QString& messageId, const QString& error);
|
||||||
|
void needPassword();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QString name;
|
QString name;
|
||||||
@ -155,16 +171,16 @@ private:
|
|||||||
bool reconnectScheduled;
|
bool reconnectScheduled;
|
||||||
QTimer* reconnectTimer;
|
QTimer* reconnectTimer;
|
||||||
|
|
||||||
std::set<QString> pendingVCardRequests;
|
|
||||||
|
|
||||||
QString avatarHash;
|
|
||||||
QString avatarType;
|
|
||||||
bool ownVCardRequestInProgress;
|
|
||||||
NetworkAccess* network;
|
NetworkAccess* network;
|
||||||
Shared::AccountPassword passwordType;
|
Shared::AccountPassword passwordType;
|
||||||
|
Error lastError;
|
||||||
|
bool pepSupport;
|
||||||
|
bool active;
|
||||||
|
bool notReadyPassword;
|
||||||
|
|
||||||
MessageHandler* mh;
|
MessageHandler* mh;
|
||||||
RosterHandler* rh;
|
RosterHandler* rh;
|
||||||
|
VCardHandler* vh;
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void onClientStateChange(QXmppClient::State state);
|
void onClientStateChange(QXmppClient::State state);
|
||||||
@ -178,9 +194,6 @@ private slots:
|
|||||||
|
|
||||||
void onMamLog(QXmppLogger::MessageType type, const QString &msg);
|
void onMamLog(QXmppLogger::MessageType type, const QString &msg);
|
||||||
|
|
||||||
void onVCardReceived(const QXmppVCardIq& card);
|
|
||||||
void onOwnVCardReceived(const QXmppVCardIq& card);
|
|
||||||
|
|
||||||
void onDiscoveryItemsReceived (const QXmppDiscoveryIq& items);
|
void onDiscoveryItemsReceived (const QXmppDiscoveryIq& items);
|
||||||
void onDiscoveryInfoReceived (const QXmppDiscoveryIq& info);
|
void onDiscoveryInfoReceived (const QXmppDiscoveryIq& info);
|
||||||
void onContactHistoryResponse(const std::list<Shared::Message>& list, bool last);
|
void onContactHistoryResponse(const std::list<Shared::Message>& list, bool last);
|
||||||
@ -189,9 +202,6 @@ private:
|
|||||||
void handleDisconnection();
|
void handleDisconnection();
|
||||||
void onReconnectTimer();
|
void onReconnectTimer();
|
||||||
};
|
};
|
||||||
|
|
||||||
void initializeVCard(Shared::VCard& vCard, const QXmppVCardIq& card);
|
|
||||||
void initializeQXmppVCard(QXmppVCardIq& card, const Shared::VCard& vCard);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -15,10 +15,8 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
#ifndef CORE_ADAPTER_FUNCTIONS_H
|
|
||||||
#define CORE_ADAPTER_FUNCTIONS_H
|
|
||||||
|
|
||||||
#include "account.h"
|
#include "adapterfunctions.h"
|
||||||
|
|
||||||
void Core::initializeVCard(Shared::VCard& vCard, const QXmppVCardIq& card)
|
void Core::initializeVCard(Shared::VCard& vCard, const QXmppVCardIq& card)
|
||||||
{
|
{
|
||||||
@ -264,12 +262,10 @@ void Core::initializeQXmppVCard(QXmppVCardIq& iq, const Shared::VCard& card) {
|
|||||||
phone.setType(phone.type() | QXmppVCardPhone::Preferred);
|
phone.setType(phone.type() | QXmppVCardPhone::Preferred);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (const std::pair<QString, QXmppVCardPhone>& phone : phones) {
|
for (const std::pair<const QString, QXmppVCardPhone>& phone : phones) {
|
||||||
phs.push_back(phone.second);
|
phs.push_back(phone.second);
|
||||||
}
|
}
|
||||||
|
|
||||||
iq.setEmails(emails);
|
iq.setEmails(emails);
|
||||||
iq.setPhones(phs);
|
iq.setPhones(phs);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // CORE_ADAPTER_FUNCTIONS_H
|
|
32
core/adapterfunctions.h
Normal file
32
core/adapterfunctions.h
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
* 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_ADAPTER_FUNCTIONS_H
|
||||||
|
#define CORE_ADAPTER_FUNCTIONS_H
|
||||||
|
|
||||||
|
#include <QXmppVCardIq.h>
|
||||||
|
#include <shared/vcard.h>
|
||||||
|
|
||||||
|
namespace Core {
|
||||||
|
|
||||||
|
void initializeVCard(Shared::VCard& vCard, const QXmppVCardIq& card);
|
||||||
|
void initializeQXmppVCard(QXmppVCardIq& card, const Shared::VCard& vCard);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#endif // CORE_ADAPTER_FUNCTIONS_H
|
@ -356,7 +356,7 @@ Shared::VCard Core::Conference::handleResponseVCard(const QXmppVCardIq& card, co
|
|||||||
QMap<QString, QVariant> Core::Conference::getAllAvatars() const
|
QMap<QString, QVariant> Core::Conference::getAllAvatars() const
|
||||||
{
|
{
|
||||||
QMap<QString, QVariant> result;
|
QMap<QString, QVariant> result;
|
||||||
for (const std::pair<QString, Archive::AvatarInfo>& pair : exParticipants) {
|
for (const std::pair<const QString, Archive::AvatarInfo>& pair : exParticipants) {
|
||||||
result.insert(pair.first, avatarPath(pair.first) + "." + pair.second.type);
|
result.insert(pair.first, avatarPath(pair.first) + "." + pair.second.type);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
|
8
core/handlers/CMakeLists.txt
Normal file
8
core/handlers/CMakeLists.txt
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
target_sources(squawk PRIVATE
|
||||||
|
messagehandler.cpp
|
||||||
|
messagehandler.h
|
||||||
|
rosterhandler.cpp
|
||||||
|
rosterhandler.h
|
||||||
|
vcardhandler.cpp
|
||||||
|
vcardhandler.h
|
||||||
|
)
|
@ -41,10 +41,10 @@ void Core::MessageHandler::onMessageReceived(const QXmppMessage& msg)
|
|||||||
handled = handleGroupMessage(msg);
|
handled = handleGroupMessage(msg);
|
||||||
break;
|
break;
|
||||||
case QXmppMessage::Error: {
|
case QXmppMessage::Error: {
|
||||||
QString id = msg.id();
|
std::tuple<bool, QString, QString> ids = getOriginalPendingMessageId(msg.id());
|
||||||
std::map<QString, QString>::const_iterator itr = pendingStateMessages.find(id);
|
if (std::get<0>(ids)) {
|
||||||
if (itr != pendingStateMessages.end()) {
|
QString id = std::get<1>(ids);
|
||||||
QString jid = itr->second;
|
QString jid = std::get<2>(ids);
|
||||||
RosterItem* cnt = acc->rh->getRosterItem(jid);
|
RosterItem* cnt = acc->rh->getRosterItem(jid);
|
||||||
QMap<QString, QVariant> cData = {
|
QMap<QString, QVariant> cData = {
|
||||||
{"state", static_cast<uint>(Shared::Message::State::error)},
|
{"state", static_cast<uint>(Shared::Message::State::error)},
|
||||||
@ -53,9 +53,7 @@ void Core::MessageHandler::onMessageReceived(const QXmppMessage& msg)
|
|||||||
if (cnt != 0) {
|
if (cnt != 0) {
|
||||||
cnt->changeMessage(id, cData);
|
cnt->changeMessage(id, cData);
|
||||||
}
|
}
|
||||||
;
|
|
||||||
emit acc->changeMessage(jid, id, cData);
|
emit acc->changeMessage(jid, id, cData);
|
||||||
pendingStateMessages.erase(itr);
|
|
||||||
handled = true;
|
handled = true;
|
||||||
} else {
|
} else {
|
||||||
qDebug() << "received a message with type \"Error\", not sure what to do with it now, skipping";
|
qDebug() << "received a message with type \"Error\", not sure what to do with it now, skipping";
|
||||||
@ -73,14 +71,14 @@ void Core::MessageHandler::onMessageReceived(const QXmppMessage& msg)
|
|||||||
|
|
||||||
bool Core::MessageHandler::handleChatMessage(const QXmppMessage& msg, bool outgoing, bool forwarded, bool guessing)
|
bool Core::MessageHandler::handleChatMessage(const QXmppMessage& msg, bool outgoing, bool forwarded, bool guessing)
|
||||||
{
|
{
|
||||||
const QString& body(msg.body());
|
if (msg.body().size() != 0 || msg.outOfBandUrl().size() > 0) {
|
||||||
if (body.size() != 0) {
|
|
||||||
Shared::Message sMsg(Shared::Message::chat);
|
Shared::Message sMsg(Shared::Message::chat);
|
||||||
initializeMessage(sMsg, msg, outgoing, forwarded, guessing);
|
initializeMessage(sMsg, msg, outgoing, forwarded, guessing);
|
||||||
QString jid = sMsg.getPenPalJid();
|
QString jid = sMsg.getPenPalJid();
|
||||||
Contact* cnt = acc->rh->getContact(jid);
|
Contact* cnt = acc->rh->getContact(jid);
|
||||||
if (cnt == 0) {
|
if (cnt == 0) {
|
||||||
cnt = acc->rh->addOutOfRosterContact(jid);
|
cnt = acc->rh->addOutOfRosterContact(jid);
|
||||||
|
qDebug() << "appending message" << sMsg.getId() << "to an out of roster contact";
|
||||||
}
|
}
|
||||||
if (outgoing) {
|
if (outgoing) {
|
||||||
if (forwarded) {
|
if (forwarded) {
|
||||||
@ -111,7 +109,6 @@ bool Core::MessageHandler::handleGroupMessage(const QXmppMessage& msg, bool outg
|
|||||||
{
|
{
|
||||||
const QString& body(msg.body());
|
const QString& body(msg.body());
|
||||||
if (body.size() != 0) {
|
if (body.size() != 0) {
|
||||||
QString id = msg.id();
|
|
||||||
|
|
||||||
Shared::Message sMsg(Shared::Message::groupChat);
|
Shared::Message sMsg(Shared::Message::groupChat);
|
||||||
initializeMessage(sMsg, msg, outgoing, forwarded, guessing);
|
initializeMessage(sMsg, msg, outgoing, forwarded, guessing);
|
||||||
@ -121,12 +118,11 @@ bool Core::MessageHandler::handleGroupMessage(const QXmppMessage& msg, bool outg
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::map<QString, QString>::const_iterator pItr = pendingStateMessages.find(id);
|
std::tuple<bool, QString, QString> ids = getOriginalPendingMessageId(msg.id());
|
||||||
if (pItr != pendingStateMessages.end()) {
|
if (std::get<0>(ids)) {
|
||||||
QMap<QString, QVariant> cData = {{"state", static_cast<uint>(Shared::Message::State::delivered)}};
|
QMap<QString, QVariant> cData = {{"state", static_cast<uint>(Shared::Message::State::delivered)}};
|
||||||
cnt->changeMessage(id, cData);
|
cnt->changeMessage(std::get<1>(ids), cData);
|
||||||
pendingStateMessages.erase(pItr);
|
emit acc->changeMessage(std::get<2>(ids), std::get<1>(ids), cData);
|
||||||
emit acc->changeMessage(jid, id, cData);
|
|
||||||
} else {
|
} else {
|
||||||
QString oId = msg.replaceId();
|
QString oId = msg.replaceId();
|
||||||
if (oId.size() > 0) {
|
if (oId.size() > 0) {
|
||||||
@ -163,6 +159,7 @@ void Core::MessageHandler::initializeMessage(Shared::Message& target, const QXmp
|
|||||||
id = source.id();
|
id = source.id();
|
||||||
}
|
}
|
||||||
target.setStanzaId(source.stanzaId());
|
target.setStanzaId(source.stanzaId());
|
||||||
|
qDebug() << "initializing message with originId:" << source.originId() << ", id:" << source.id() << ", stansaId:" << source.stanzaId();
|
||||||
#else
|
#else
|
||||||
id = source.id();
|
id = source.id();
|
||||||
#endif
|
#endif
|
||||||
@ -171,6 +168,7 @@ void Core::MessageHandler::initializeMessage(Shared::Message& target, const QXmp
|
|||||||
if (messageId.size() == 0) {
|
if (messageId.size() == 0) {
|
||||||
target.generateRandomId(); //TODO out of desperation, I need at least a random ID
|
target.generateRandomId(); //TODO out of desperation, I need at least a random ID
|
||||||
messageId = target.getId();
|
messageId = target.getId();
|
||||||
|
qDebug() << "Had do initialize a message with no id, assigning autogenerated" << messageId;
|
||||||
}
|
}
|
||||||
target.setFrom(source.from());
|
target.setFrom(source.from());
|
||||||
target.setTo(source.to());
|
target.setTo(source.to());
|
||||||
@ -178,7 +176,7 @@ void Core::MessageHandler::initializeMessage(Shared::Message& target, const QXmp
|
|||||||
target.setForwarded(forwarded);
|
target.setForwarded(forwarded);
|
||||||
|
|
||||||
if (guessing) {
|
if (guessing) {
|
||||||
if (target.getFromJid() == acc->getLogin() + "@" + acc->getServer()) {
|
if (target.getFromJid() == acc->getBareJid()) {
|
||||||
outgoing = true;
|
outgoing = true;
|
||||||
} else {
|
} else {
|
||||||
outgoing = false;
|
outgoing = false;
|
||||||
@ -225,50 +223,74 @@ void Core::MessageHandler::onCarbonMessageSent(const QXmppMessage& msg)
|
|||||||
handleChatMessage(msg, true, true);
|
handleChatMessage(msg, true, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Core::MessageHandler::onReceiptReceived(const QString& jid, const QString& id)
|
std::tuple<bool, QString, QString> Core::MessageHandler::getOriginalPendingMessageId(const QString& id)
|
||||||
{
|
{
|
||||||
|
std::tuple<bool, QString, QString> result({false, "", ""});
|
||||||
std::map<QString, QString>::const_iterator itr = pendingStateMessages.find(id);
|
std::map<QString, QString>::const_iterator itr = pendingStateMessages.find(id);
|
||||||
if (itr != pendingStateMessages.end()) {
|
if (itr != pendingStateMessages.end()) {
|
||||||
QMap<QString, QVariant> cData = {{"state", static_cast<uint>(Shared::Message::State::delivered)}};
|
std::get<0>(result) = true;
|
||||||
RosterItem* ri = acc->rh->getRosterItem(itr->second);
|
std::get<2>(result) = itr->second;
|
||||||
if (ri != 0) {
|
|
||||||
ri->changeMessage(id, cData);
|
std::map<QString, QString>::const_iterator itrC = pendingCorrectionMessages.find(id);
|
||||||
|
if (itrC != pendingCorrectionMessages.end()) {
|
||||||
|
if (itrC->second.size() > 0) {
|
||||||
|
std::get<1>(result) = itrC->second;
|
||||||
|
} else {
|
||||||
|
std::get<1>(result) = itr->first;
|
||||||
}
|
}
|
||||||
|
pendingCorrectionMessages.erase(itrC);
|
||||||
|
} else {
|
||||||
|
std::get<1>(result) = itr->first;
|
||||||
|
}
|
||||||
|
|
||||||
pendingStateMessages.erase(itr);
|
pendingStateMessages.erase(itr);
|
||||||
emit acc->changeMessage(itr->second, id, cData);
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Core::MessageHandler::onReceiptReceived(const QString& jid, const QString& id)
|
||||||
|
{
|
||||||
|
std::tuple<bool, QString, QString> ids = getOriginalPendingMessageId(id);
|
||||||
|
if (std::get<0>(ids)) {
|
||||||
|
QMap<QString, QVariant> cData = {{"state", static_cast<uint>(Shared::Message::State::delivered)}};
|
||||||
|
RosterItem* ri = acc->rh->getRosterItem(std::get<2>(ids));
|
||||||
|
|
||||||
|
if (ri != 0) {
|
||||||
|
ri->changeMessage(std::get<1>(ids), cData);
|
||||||
|
}
|
||||||
|
emit acc->changeMessage(std::get<2>(ids), std::get<1>(ids), cData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Core::MessageHandler::sendMessage(const Shared::Message& data)
|
void Core::MessageHandler::sendMessage(const Shared::Message& data, bool newMessage, QString originalId)
|
||||||
{
|
{
|
||||||
if (data.getOutOfBandUrl().size() == 0 && data.getAttachPath().size() > 0) {
|
if (data.getOutOfBandUrl().size() == 0 && data.getAttachPath().size() > 0) {
|
||||||
prepareUpload(data);
|
pendingCorrectionMessages.insert(std::make_pair(data.getId(), originalId));
|
||||||
|
prepareUpload(data, newMessage);
|
||||||
} else {
|
} else {
|
||||||
performSending(data);
|
performSending(data, originalId, newMessage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Core::MessageHandler::performSending(Shared::Message data, bool newMessage)
|
void Core::MessageHandler::performSending(Shared::Message data, const QString& originalId, bool newMessage)
|
||||||
{
|
{
|
||||||
QString jid = data.getPenPalJid();
|
QString jid = data.getPenPalJid();
|
||||||
QString id = data.getId();
|
QString id = data.getId();
|
||||||
QString oob = data.getOutOfBandUrl();
|
qDebug() << "Sending message with id:" << id;
|
||||||
|
if (originalId.size() > 0) {
|
||||||
|
qDebug() << "To replace one with id:" << originalId;
|
||||||
|
}
|
||||||
RosterItem* ri = acc->rh->getRosterItem(jid);
|
RosterItem* ri = acc->rh->getRosterItem(jid);
|
||||||
bool sent = false;
|
bool sent = false;
|
||||||
QMap<QString, QVariant> changes;
|
if (newMessage && originalId.size() > 0) {
|
||||||
|
newMessage = false;
|
||||||
|
}
|
||||||
|
QDateTime sendTime = QDateTime::currentDateTimeUtc();
|
||||||
if (acc->state == Shared::ConnectionState::connected) {
|
if (acc->state == Shared::ConnectionState::connected) {
|
||||||
QXmppMessage msg(acc->getFullJid(), data.getTo(), data.getBody(), data.getThread());
|
QXmppMessage msg(createPacket(data, sendTime, originalId));
|
||||||
|
|
||||||
#if (QXMPP_VERSION) >= QT_VERSION_CHECK(1, 3, 0)
|
|
||||||
msg.setOriginId(id);
|
|
||||||
#endif
|
|
||||||
msg.setId(id);
|
|
||||||
msg.setType(static_cast<QXmppMessage::Type>(data.getType())); //it is safe here, my type is compatible
|
|
||||||
msg.setOutOfBandUrl(oob);
|
|
||||||
msg.setReceiptRequested(true);
|
|
||||||
|
|
||||||
sent = acc->client.sendPacket(msg);
|
sent = acc->client.sendPacket(msg);
|
||||||
|
|
||||||
if (sent) {
|
if (sent) {
|
||||||
data.setState(Shared::Message::State::sent);
|
data.setState(Shared::Message::State::sent);
|
||||||
} else {
|
} else {
|
||||||
@ -281,6 +303,39 @@ void Core::MessageHandler::performSending(Shared::Message data, bool newMessage)
|
|||||||
data.setErrorText("You are is offline or reconnecting");
|
data.setErrorText("You are is offline or reconnecting");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QMap<QString, QVariant> changes(getChanges(data, sendTime, newMessage, originalId));
|
||||||
|
|
||||||
|
QString realId;
|
||||||
|
if (originalId.size() > 0) {
|
||||||
|
realId = originalId;
|
||||||
|
} else {
|
||||||
|
realId = id;
|
||||||
|
}
|
||||||
|
if (ri != 0) {
|
||||||
|
if (newMessage) {
|
||||||
|
ri->appendMessageToArchive(data);
|
||||||
|
} else {
|
||||||
|
ri->changeMessage(realId, changes);
|
||||||
|
}
|
||||||
|
if (sent) {
|
||||||
|
pendingStateMessages.insert(std::make_pair(id, jid));
|
||||||
|
if (originalId.size() > 0) {
|
||||||
|
pendingCorrectionMessages.insert(std::make_pair(id, originalId));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
pendingStateMessages.erase(id);
|
||||||
|
pendingCorrectionMessages.erase(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
emit acc->changeMessage(jid, realId, changes);
|
||||||
|
}
|
||||||
|
|
||||||
|
QMap<QString, QVariant> Core::MessageHandler::getChanges(Shared::Message& data, const QDateTime& time, bool newMessage, const QString& originalId) const
|
||||||
|
{
|
||||||
|
QMap<QString, QVariant> changes;
|
||||||
|
|
||||||
|
QString oob = data.getOutOfBandUrl();
|
||||||
Shared::Message::State mstate = data.getState();
|
Shared::Message::State mstate = data.getState();
|
||||||
changes.insert("state", static_cast<uint>(mstate));
|
changes.insert("state", static_cast<uint>(mstate));
|
||||||
if (mstate == Shared::Message::State::error) {
|
if (mstate == Shared::Message::State::error) {
|
||||||
@ -289,27 +344,51 @@ void Core::MessageHandler::performSending(Shared::Message data, bool newMessage)
|
|||||||
if (oob.size() > 0) {
|
if (oob.size() > 0) {
|
||||||
changes.insert("outOfBandUrl", oob);
|
changes.insert("outOfBandUrl", oob);
|
||||||
}
|
}
|
||||||
if (!newMessage) {
|
|
||||||
changes.insert("stamp", data.getTime());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ri != 0) {
|
|
||||||
if (newMessage) {
|
if (newMessage) {
|
||||||
ri->appendMessageToArchive(data);
|
data.setTime(time);
|
||||||
} else {
|
|
||||||
ri->changeMessage(id, changes);
|
|
||||||
}
|
}
|
||||||
if (sent) {
|
if (originalId.size() > 0) {
|
||||||
pendingStateMessages.insert(std::make_pair(id, jid));
|
changes.insert("body", data.getBody());
|
||||||
} else {
|
}
|
||||||
pendingStateMessages.erase(id);
|
changes.insert("stamp", time);
|
||||||
|
|
||||||
|
//sometimes (when the image is pasted with ctrl+v)
|
||||||
|
//I start sending message with one path, then copy it to downloads directory
|
||||||
|
//so, the final path changes. Let's assume it changes always since it costs me close to nothing
|
||||||
|
QString attachPath = data.getAttachPath();
|
||||||
|
if (attachPath.size() > 0) {
|
||||||
|
QString squawkified = Shared::squawkifyPath(attachPath);
|
||||||
|
changes.insert("attachPath", squawkified);
|
||||||
|
if (attachPath != squawkified) {
|
||||||
|
data.setAttachPath(squawkified);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
emit acc->changeMessage(jid, id, changes);
|
return changes;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Core::MessageHandler::prepareUpload(const Shared::Message& data)
|
QXmppMessage Core::MessageHandler::createPacket(const Shared::Message& data, const QDateTime& time, const QString& originalId) const
|
||||||
|
{
|
||||||
|
QXmppMessage msg(acc->getFullJid(), data.getTo(), data.getBody(), data.getThread());
|
||||||
|
QString id(data.getId());
|
||||||
|
|
||||||
|
if (originalId.size() > 0) {
|
||||||
|
msg.setReplaceId(originalId);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if (QXMPP_VERSION) >= QT_VERSION_CHECK(1, 3, 0)
|
||||||
|
msg.setOriginId(id);
|
||||||
|
#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.setReceiptRequested(true);
|
||||||
|
msg.setStamp(time);
|
||||||
|
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Core::MessageHandler::prepareUpload(const Shared::Message& data, bool newMessage)
|
||||||
{
|
{
|
||||||
if (acc->state == Shared::ConnectionState::connected) {
|
if (acc->state == Shared::ConnectionState::connected) {
|
||||||
QString jid = data.getPenPalJid();
|
QString jid = data.getPenPalJid();
|
||||||
@ -322,16 +401,23 @@ void Core::MessageHandler::prepareUpload(const Shared::Message& data)
|
|||||||
QString path = data.getAttachPath();
|
QString path = data.getAttachPath();
|
||||||
QString url = acc->network->getFileRemoteUrl(path);
|
QString url = acc->network->getFileRemoteUrl(path);
|
||||||
if (url.size() != 0) {
|
if (url.size() != 0) {
|
||||||
sendMessageWithLocalUploadedFile(data, url);
|
sendMessageWithLocalUploadedFile(data, url, newMessage);
|
||||||
} else {
|
} else {
|
||||||
if (acc->network->checkAndAddToUploading(acc->getName(), jid, id, path)) {
|
|
||||||
ri->appendMessageToArchive(data);
|
|
||||||
pendingStateMessages.insert(std::make_pair(id, jid));
|
pendingStateMessages.insert(std::make_pair(id, jid));
|
||||||
|
if (newMessage) {
|
||||||
|
ri->appendMessageToArchive(data);
|
||||||
} else {
|
} 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()) {
|
if (acc->um->serviceFound()) {
|
||||||
QFileInfo file(path);
|
QFileInfo file(path);
|
||||||
if (file.exists() && file.isReadable()) {
|
if (file.exists() && file.isReadable()) {
|
||||||
ri->appendMessageToArchive(data);
|
|
||||||
pendingStateMessages.insert(std::make_pair(id, jid));
|
pendingStateMessages.insert(std::make_pair(id, jid));
|
||||||
uploadingSlotsQueue.emplace_back(path, id);
|
uploadingSlotsQueue.emplace_back(path, id);
|
||||||
if (uploadingSlotsQueue.size() == 1) {
|
if (uploadingSlotsQueue.size() == 1) {
|
||||||
@ -353,7 +439,6 @@ void Core::MessageHandler::prepareUpload(const Shared::Message& data)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void Core::MessageHandler::onUploadSlotReceived(const QXmppHttpUploadSlotIq& slot)
|
void Core::MessageHandler::onUploadSlotReceived(const QXmppHttpUploadSlotIq& slot)
|
||||||
{
|
{
|
||||||
if (uploadingSlotsQueue.size() == 0) {
|
if (uploadingSlotsQueue.size() == 0) {
|
||||||
@ -420,21 +505,23 @@ void Core::MessageHandler::onLoadFileError(const std::list<Shared::MessageInfo>&
|
|||||||
void Core::MessageHandler::handleUploadError(const QString& jid, const QString& messageId, const QString& errorText)
|
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);
|
emit acc->uploadFileError(jid, messageId, "Error requesting slot to upload file: " + errorText);
|
||||||
pendingStateMessages.erase(jid);
|
pendingStateMessages.erase(messageId);
|
||||||
|
pendingCorrectionMessages.erase(messageId);
|
||||||
requestChangeMessage(jid, messageId, {
|
requestChangeMessage(jid, messageId, {
|
||||||
{"state", static_cast<uint>(Shared::Message::State::error)},
|
{"state", static_cast<uint>(Shared::Message::State::error)},
|
||||||
{"errorText", errorText}
|
{"errorText", errorText}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void Core::MessageHandler::onUploadFileComplete(const std::list<Shared::MessageInfo>& msgs, const QString& path)
|
void Core::MessageHandler::onUploadFileComplete(const std::list<Shared::MessageInfo>& msgs, const QString& url, const QString& path)
|
||||||
{
|
{
|
||||||
for (const Shared::MessageInfo& info : msgs) {
|
for (const Shared::MessageInfo& info : msgs) {
|
||||||
if (info.account == acc->getName()) {
|
if (info.account == acc->getName()) {
|
||||||
RosterItem* ri = acc->rh->getRosterItem(info.jid);
|
RosterItem* ri = acc->rh->getRosterItem(info.jid);
|
||||||
if (ri != 0) {
|
if (ri != 0) {
|
||||||
Shared::Message msg = ri->getMessage(info.messageId);
|
Shared::Message msg = ri->getMessage(info.messageId);
|
||||||
sendMessageWithLocalUploadedFile(msg, path, false);
|
msg.setAttachPath(path);
|
||||||
|
sendMessageWithLocalUploadedFile(msg, url, false);
|
||||||
} else {
|
} 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";
|
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";
|
||||||
}
|
}
|
||||||
@ -448,11 +535,11 @@ void Core::MessageHandler::sendMessageWithLocalUploadedFile(Shared::Message msg,
|
|||||||
if (msg.getBody().size() == 0) { //not sure why, but most messages do that
|
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
|
msg.setBody(url); //they duplicate oob in body, some of them wouldn't even show an attachment if you don't do that
|
||||||
}
|
}
|
||||||
performSending(msg, newMessage);
|
performSending(msg, pendingCorrectionMessages.at(msg.getId()), newMessage);
|
||||||
//TODO removal/progress update
|
//TODO removal/progress update
|
||||||
}
|
}
|
||||||
|
|
||||||
static const std::set<QString> allowerToChangeKeys({
|
static const std::set<QString> allowedToChangeKeys({
|
||||||
"attachPath",
|
"attachPath",
|
||||||
"outOfBandUrl",
|
"outOfBandUrl",
|
||||||
"state",
|
"state",
|
||||||
@ -466,11 +553,11 @@ void Core::MessageHandler::requestChangeMessage(const QString& jid, const QStrin
|
|||||||
bool allSupported = true;
|
bool allSupported = true;
|
||||||
QString unsupportedString;
|
QString unsupportedString;
|
||||||
for (QMap<QString, QVariant>::const_iterator itr = data.begin(); itr != data.end(); ++itr) { //I need all this madness
|
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
|
if (allowedToChangeKeys.count(itr.key()) != 1) { //to not allow this method
|
||||||
allSupported = false; //to make a message to look like if it was edited
|
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
|
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
|
break; //because the underlying tech assumes that
|
||||||
} //not by system
|
} //the change is initiated by user, not by system
|
||||||
}
|
}
|
||||||
if (allSupported) {
|
if (allSupported) {
|
||||||
cnt->changeMessage(messageId, data);
|
cnt->changeMessage(messageId, data);
|
||||||
@ -481,3 +568,28 @@ void Core::MessageHandler::requestChangeMessage(const QString& jid, const QStrin
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Core::MessageHandler::resendMessage(const QString& jid, const QString& id)
|
||||||
|
{
|
||||||
|
RosterItem* cnt = acc->rh->getRosterItem(jid);
|
||||||
|
if (cnt != 0) {
|
||||||
|
try {
|
||||||
|
Shared::Message msg = cnt->getMessage(id);
|
||||||
|
if (msg.getState() == Shared::Message::State::error) {
|
||||||
|
if (msg.getEdited()){
|
||||||
|
QString originalId = msg.getId();
|
||||||
|
msg.generateRandomId();
|
||||||
|
sendMessage(msg, false, originalId);
|
||||||
|
} else {
|
||||||
|
sendMessage(msg, false);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
qDebug() << "An attempt to resend a message to" << jid << "by account" << acc->getName() << ", but this message seems to have been normally sent, this method was made to retry sending failed to be sent messages, skipping";
|
||||||
|
}
|
||||||
|
} 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";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -29,6 +29,7 @@
|
|||||||
|
|
||||||
#include <shared/message.h>
|
#include <shared/message.h>
|
||||||
#include <shared/messageinfo.h>
|
#include <shared/messageinfo.h>
|
||||||
|
#include <shared/pathcheck.h>
|
||||||
|
|
||||||
namespace Core {
|
namespace Core {
|
||||||
|
|
||||||
@ -45,8 +46,9 @@ public:
|
|||||||
MessageHandler(Account* account);
|
MessageHandler(Account* account);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
void sendMessage(const Shared::Message& data);
|
void sendMessage(const Shared::Message& data, bool newMessage = true, QString originalId = "");
|
||||||
void initializeMessage(Shared::Message& target, const QXmppMessage& source, bool outgoing = false, bool forwarded = false, bool guessing = false) const;
|
void 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:
|
public slots:
|
||||||
void onMessageReceived(const QXmppMessage& message);
|
void onMessageReceived(const QXmppMessage& message);
|
||||||
@ -56,7 +58,7 @@ public slots:
|
|||||||
void onUploadSlotReceived(const QXmppHttpUploadSlotIq& slot);
|
void onUploadSlotReceived(const QXmppHttpUploadSlotIq& slot);
|
||||||
void onUploadSlotRequestFailed(const QXmppHttpUploadRequestIq& request);
|
void onUploadSlotRequestFailed(const QXmppHttpUploadRequestIq& request);
|
||||||
void onDownloadFileComplete(const std::list<Shared::MessageInfo>& msgs, const QString& path);
|
void onDownloadFileComplete(const std::list<Shared::MessageInfo>& msgs, const QString& path);
|
||||||
void onUploadFileComplete(const std::list<Shared::MessageInfo>& msgs, const QString& path);
|
void onUploadFileComplete(const std::list<Shared::MessageInfo>& msgs, const QString& url, const QString& path);
|
||||||
void onLoadFileError(const std::list<Shared::MessageInfo>& msgs, const QString& path, bool up);
|
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);
|
void requestChangeMessage(const QString& jid, const QString& messageId, const QMap<QString, QVariant>& data);
|
||||||
|
|
||||||
@ -65,13 +67,17 @@ private:
|
|||||||
bool handleGroupMessage(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 logMessage(const QXmppMessage& msg, const QString& reason = "Message wasn't handled: ");
|
||||||
void sendMessageWithLocalUploadedFile(Shared::Message msg, const QString& url, bool newMessage = true);
|
void sendMessageWithLocalUploadedFile(Shared::Message msg, const QString& url, bool newMessage = true);
|
||||||
void performSending(Shared::Message data, bool newMessage = true);
|
void performSending(Shared::Message data, const QString& originalId, bool newMessage = true);
|
||||||
void prepareUpload(const Shared::Message& data);
|
void prepareUpload(const Shared::Message& data, bool newMessage = true);
|
||||||
void handleUploadError(const QString& jid, const QString& messageId, const QString& errorText);
|
void handleUploadError(const QString& jid, const QString& messageId, const QString& errorText);
|
||||||
|
QXmppMessage createPacket(const Shared::Message& data, const QDateTime& time, const QString& originalId) const;
|
||||||
|
QMap<QString, QVariant> getChanges(Shared::Message& data, const QDateTime& time, bool newMessage, const QString& originalId) const;
|
||||||
|
std::tuple<bool, QString, QString> getOriginalPendingMessageId(const QString& id);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Account* acc;
|
Account* acc;
|
||||||
std::map<QString, QString> pendingStateMessages; //key is message id, value is JID
|
std::map<QString, QString> pendingStateMessages; //key is message id, value is JID
|
||||||
|
std::map<QString, QString> pendingCorrectionMessages; //key is new mesage, value is originalOne
|
||||||
std::deque<std::pair<QString, QString>> uploadingSlotsQueue;
|
std::deque<std::pair<QString, QString>> uploadingSlotsQueue;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -26,7 +26,8 @@ Core::RosterHandler::RosterHandler(Core::Account* account):
|
|||||||
conferences(),
|
conferences(),
|
||||||
groups(),
|
groups(),
|
||||||
queuedContacts(),
|
queuedContacts(),
|
||||||
outOfRosterContacts()
|
outOfRosterContacts(),
|
||||||
|
pepSupport(false)
|
||||||
{
|
{
|
||||||
connect(acc->rm, &QXmppRosterManager::rosterReceived, this, &RosterHandler::onRosterReceived);
|
connect(acc->rm, &QXmppRosterManager::rosterReceived, this, &RosterHandler::onRosterReceived);
|
||||||
connect(acc->rm, &QXmppRosterManager::itemAdded, this, &RosterHandler::onRosterItemAdded);
|
connect(acc->rm, &QXmppRosterManager::itemAdded, this, &RosterHandler::onRosterItemAdded);
|
||||||
@ -51,8 +52,7 @@ Core::RosterHandler::~RosterHandler()
|
|||||||
|
|
||||||
void Core::RosterHandler::onRosterReceived()
|
void Core::RosterHandler::onRosterReceived()
|
||||||
{
|
{
|
||||||
acc->vm->requestClientVCard(); //TODO need to make sure server actually supports vCards
|
acc->requestVCard(acc->getBareJid()); //TODO need to make sure server actually supports vCards
|
||||||
acc->ownVCardRequestInProgress = true;
|
|
||||||
|
|
||||||
QStringList bj = acc->rm->getRosterBareJids();
|
QStringList bj = acc->rm->getRosterBareJids();
|
||||||
for (int i = 0; i < bj.size(); ++i) {
|
for (int i = 0; i < bj.size(); ++i) {
|
||||||
@ -588,4 +588,13 @@ void Core::RosterHandler::handleOffline()
|
|||||||
pair.second->clearArchiveRequests();
|
pair.second->clearArchiveRequests();
|
||||||
pair.second->downgradeDatabaseState();
|
pair.second->downgradeDatabaseState();
|
||||||
}
|
}
|
||||||
|
setPepSupport(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Core::RosterHandler::setPepSupport(bool support)
|
||||||
|
{
|
||||||
|
if (pepSupport != support) {
|
||||||
|
pepSupport = support;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -64,6 +64,7 @@ public:
|
|||||||
|
|
||||||
void storeConferences();
|
void storeConferences();
|
||||||
void clearConferences();
|
void clearConferences();
|
||||||
|
void setPepSupport(bool support);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void onRosterReceived();
|
void onRosterReceived();
|
||||||
@ -107,6 +108,7 @@ private:
|
|||||||
std::map<QString, std::set<QString>> groups;
|
std::map<QString, std::set<QString>> groups;
|
||||||
std::map<QString, QString> queuedContacts;
|
std::map<QString, QString> queuedContacts;
|
||||||
std::set<QString> outOfRosterContacts;
|
std::set<QString> outOfRosterContacts;
|
||||||
|
bool pepSupport;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
312
core/handlers/vcardhandler.cpp
Normal file
312
core/handlers/vcardhandler.cpp
Normal file
@ -0,0 +1,312 @@
|
|||||||
|
// 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 "vcardhandler.h"
|
||||||
|
#include "core/account.h"
|
||||||
|
|
||||||
|
Core::VCardHandler::VCardHandler(Account* account):
|
||||||
|
QObject(),
|
||||||
|
acc(account),
|
||||||
|
ownVCardRequestInProgress(false),
|
||||||
|
pendingVCardRequests(),
|
||||||
|
avatarHash(),
|
||||||
|
avatarType()
|
||||||
|
{
|
||||||
|
connect(acc->vm, &QXmppVCardManager::vCardReceived, this, &VCardHandler::onVCardReceived);
|
||||||
|
//for some reason it doesn't work, launching from common handler
|
||||||
|
//connect(acc->vm, &QXmppVCardManager::clientVCardReceived, this, &VCardHandler::onOwnVCardReceived);
|
||||||
|
|
||||||
|
QString path(QStandardPaths::writableLocation(QStandardPaths::CacheLocation));
|
||||||
|
path += "/" + acc->name;
|
||||||
|
QDir dir(path);
|
||||||
|
|
||||||
|
if (!dir.exists()) {
|
||||||
|
bool res = dir.mkpath(path);
|
||||||
|
if (!res) {
|
||||||
|
qDebug() << "Couldn't create a cache directory for account" << acc->name;
|
||||||
|
throw 22;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QFile* avatar = new QFile(path + "/avatar.png");
|
||||||
|
QString type = "png";
|
||||||
|
if (!avatar->exists()) {
|
||||||
|
delete avatar;
|
||||||
|
avatar = new QFile(path + "/avatar.jpg");
|
||||||
|
type = "jpg";
|
||||||
|
if (!avatar->exists()) {
|
||||||
|
delete avatar;
|
||||||
|
avatar = new QFile(path + "/avatar.jpeg");
|
||||||
|
type = "jpeg";
|
||||||
|
if (!avatar->exists()) {
|
||||||
|
delete avatar;
|
||||||
|
avatar = new QFile(path + "/avatar.gif");
|
||||||
|
type = "gif";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (avatar->exists()) {
|
||||||
|
if (avatar->open(QFile::ReadOnly)) {
|
||||||
|
QCryptographicHash sha1(QCryptographicHash::Sha1);
|
||||||
|
sha1.addData(avatar);
|
||||||
|
avatarHash = sha1.result();
|
||||||
|
avatarType = type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (avatarType.size() != 0) {
|
||||||
|
acc->presence.setVCardUpdateType(QXmppPresence::VCardUpdateValidPhoto);
|
||||||
|
acc->presence.setPhotoHash(avatarHash.toUtf8());
|
||||||
|
} else {
|
||||||
|
acc->presence.setVCardUpdateType(QXmppPresence::VCardUpdateNotReady);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Core::VCardHandler::~VCardHandler()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void Core::VCardHandler::onVCardReceived(const QXmppVCardIq& card)
|
||||||
|
{
|
||||||
|
QString id = card.from();
|
||||||
|
QStringList comps = id.split("/");
|
||||||
|
QString jid = comps.front().toLower();
|
||||||
|
QString resource("");
|
||||||
|
if (comps.size() > 1) {
|
||||||
|
resource = comps.back();
|
||||||
|
}
|
||||||
|
pendingVCardRequests.erase(id);
|
||||||
|
RosterItem* item = acc->rh->getRosterItem(jid);
|
||||||
|
|
||||||
|
if (item == 0) {
|
||||||
|
if (jid == acc->getBareJid()) {
|
||||||
|
onOwnVCardReceived(card);
|
||||||
|
} else {
|
||||||
|
qDebug() << "received vCard" << jid << "doesn't belong to any of known contacts or conferences, skipping";
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Shared::VCard vCard = item->handleResponseVCard(card, resource);
|
||||||
|
|
||||||
|
emit acc->receivedVCard(jid, vCard);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Core::VCardHandler::onOwnVCardReceived(const QXmppVCardIq& card)
|
||||||
|
{
|
||||||
|
QByteArray ava = card.photo();
|
||||||
|
bool avaChanged = false;
|
||||||
|
QString path = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/" + acc->name + "/";
|
||||||
|
if (ava.size() > 0) {
|
||||||
|
QCryptographicHash sha1(QCryptographicHash::Sha1);
|
||||||
|
sha1.addData(ava);
|
||||||
|
QString newHash(sha1.result());
|
||||||
|
QMimeDatabase db;
|
||||||
|
QMimeType newType = db.mimeTypeForData(ava);
|
||||||
|
if (avatarType.size() > 0) {
|
||||||
|
if (avatarHash != newHash) {
|
||||||
|
QString oldPath = path + "avatar." + avatarType;
|
||||||
|
QFile oldAvatar(oldPath);
|
||||||
|
bool oldToRemove = false;
|
||||||
|
if (oldAvatar.exists()) {
|
||||||
|
if (oldAvatar.rename(oldPath + ".bak")) {
|
||||||
|
oldToRemove = true;
|
||||||
|
} else {
|
||||||
|
qDebug() << "Received new avatar for account" << acc->name << "but can't get rid of the old one, doing nothing";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
QFile newAvatar(path + "avatar." + newType.preferredSuffix());
|
||||||
|
if (newAvatar.open(QFile::WriteOnly)) {
|
||||||
|
newAvatar.write(ava);
|
||||||
|
newAvatar.close();
|
||||||
|
avatarHash = newHash;
|
||||||
|
avatarType = newType.preferredSuffix();
|
||||||
|
avaChanged = true;
|
||||||
|
} else {
|
||||||
|
qDebug() << "Received new avatar for account" << acc->name << "but can't save it";
|
||||||
|
if (oldToRemove) {
|
||||||
|
qDebug() << "rolling back to the old avatar";
|
||||||
|
if (!oldAvatar.rename(oldPath)) {
|
||||||
|
qDebug() << "Couldn't roll back to the old avatar in account" << acc->name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
QFile newAvatar(path + "avatar." + newType.preferredSuffix());
|
||||||
|
if (newAvatar.open(QFile::WriteOnly)) {
|
||||||
|
newAvatar.write(ava);
|
||||||
|
newAvatar.close();
|
||||||
|
avatarHash = newHash;
|
||||||
|
avatarType = newType.preferredSuffix();
|
||||||
|
avaChanged = true;
|
||||||
|
} else {
|
||||||
|
qDebug() << "Received new avatar for account" << acc->name << "but can't save it";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (avatarType.size() > 0) {
|
||||||
|
QFile oldAvatar(path + "avatar." + avatarType);
|
||||||
|
if (!oldAvatar.remove()) {
|
||||||
|
qDebug() << "Received vCard for account" << acc->name << "without avatar, but can't get rid of the file, doing nothing";
|
||||||
|
} else {
|
||||||
|
avatarType = "";
|
||||||
|
avatarHash = "";
|
||||||
|
avaChanged = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (avaChanged) {
|
||||||
|
QMap<QString, QVariant> change;
|
||||||
|
if (avatarType.size() > 0) {
|
||||||
|
acc->presence.setPhotoHash(avatarHash.toUtf8());
|
||||||
|
acc->presence.setVCardUpdateType(QXmppPresence::VCardUpdateValidPhoto);
|
||||||
|
change.insert("avatarPath", path + "avatar." + avatarType);
|
||||||
|
} else {
|
||||||
|
acc->presence.setPhotoHash("");
|
||||||
|
acc->presence.setVCardUpdateType(QXmppPresence::VCardUpdateNoPhoto);
|
||||||
|
change.insert("avatarPath", "");
|
||||||
|
}
|
||||||
|
acc->client.setClientPresence(acc->presence);
|
||||||
|
emit acc->changed(change);
|
||||||
|
}
|
||||||
|
|
||||||
|
ownVCardRequestInProgress = false;
|
||||||
|
|
||||||
|
Shared::VCard vCard;
|
||||||
|
initializeVCard(vCard, card);
|
||||||
|
|
||||||
|
if (avatarType.size() > 0) {
|
||||||
|
vCard.setAvatarType(Shared::Avatar::valid);
|
||||||
|
vCard.setAvatarPath(path + "avatar." + avatarType);
|
||||||
|
} else {
|
||||||
|
vCard.setAvatarType(Shared::Avatar::empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
emit acc->receivedVCard(acc->getBareJid(), vCard);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Core::VCardHandler::handleOffline()
|
||||||
|
{
|
||||||
|
pendingVCardRequests.clear();
|
||||||
|
Shared::VCard vCard; //just to show, that there is now more pending request
|
||||||
|
for (const QString& jid : pendingVCardRequests) {
|
||||||
|
emit acc->receivedVCard(jid, vCard); //need to show it better in the future, like with an error
|
||||||
|
}
|
||||||
|
pendingVCardRequests.clear();
|
||||||
|
ownVCardRequestInProgress = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Core::VCardHandler::requestVCard(const QString& jid)
|
||||||
|
{
|
||||||
|
if (pendingVCardRequests.find(jid) == pendingVCardRequests.end()) {
|
||||||
|
qDebug() << "requesting vCard" << jid;
|
||||||
|
if (jid == acc->getBareJid()) {
|
||||||
|
if (!ownVCardRequestInProgress) {
|
||||||
|
acc->vm->requestClientVCard();
|
||||||
|
ownVCardRequestInProgress = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
acc->vm->requestVCard(jid);
|
||||||
|
pendingVCardRequests.insert(jid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Core::VCardHandler::handleOtherPresenceOfMyAccountChange(const QXmppPresence& p_presence)
|
||||||
|
{
|
||||||
|
if (!ownVCardRequestInProgress) {
|
||||||
|
switch (p_presence.vCardUpdateType()) {
|
||||||
|
case QXmppPresence::VCardUpdateNone: //this presence has nothing to do with photo
|
||||||
|
break;
|
||||||
|
case QXmppPresence::VCardUpdateNotReady: //let's say the photo didn't change here
|
||||||
|
break;
|
||||||
|
case QXmppPresence::VCardUpdateNoPhoto: //there is no photo, need to drop if any
|
||||||
|
if (avatarType.size() > 0) {
|
||||||
|
acc->vm->requestClientVCard();
|
||||||
|
ownVCardRequestInProgress = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case QXmppPresence::VCardUpdateValidPhoto: //there is a photo, need to load
|
||||||
|
if (avatarHash != p_presence.photoHash()) {
|
||||||
|
acc->vm->requestClientVCard();
|
||||||
|
ownVCardRequestInProgress = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Core::VCardHandler::uploadVCard(const Shared::VCard& card)
|
||||||
|
{
|
||||||
|
QXmppVCardIq iq;
|
||||||
|
initializeQXmppVCard(iq, card);
|
||||||
|
|
||||||
|
if (card.getAvatarType() != Shared::Avatar::empty) {
|
||||||
|
QString newPath = card.getAvatarPath();
|
||||||
|
QString oldPath = getAvatarPath();
|
||||||
|
QByteArray data;
|
||||||
|
QString type;
|
||||||
|
if (newPath != oldPath) {
|
||||||
|
QFile avatar(newPath);
|
||||||
|
if (!avatar.open(QFile::ReadOnly)) {
|
||||||
|
qDebug() << "An attempt to upload new vCard to account" << acc->name
|
||||||
|
<< "but it wasn't possible to read file" << newPath
|
||||||
|
<< "which was supposed to be new avatar, uploading old avatar";
|
||||||
|
if (avatarType.size() > 0) {
|
||||||
|
QFile oA(oldPath);
|
||||||
|
if (!oA.open(QFile::ReadOnly)) {
|
||||||
|
qDebug() << "Couldn't read old avatar of account" << acc->name << ", uploading empty avatar";
|
||||||
|
} else {
|
||||||
|
data = oA.readAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
data = avatar.readAll();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (avatarType.size() > 0) {
|
||||||
|
QFile oA(oldPath);
|
||||||
|
if (!oA.open(QFile::ReadOnly)) {
|
||||||
|
qDebug() << "Couldn't read old avatar of account" << acc->name << ", uploading empty avatar";
|
||||||
|
} else {
|
||||||
|
data = oA.readAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.size() > 0) {
|
||||||
|
QMimeDatabase db;
|
||||||
|
type = db.mimeTypeForData(data).name();
|
||||||
|
iq.setPhoto(data);
|
||||||
|
iq.setPhotoType(type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
acc->vm->setClientVCard(iq);
|
||||||
|
onOwnVCardReceived(iq);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString Core::VCardHandler::getAvatarPath() const
|
||||||
|
{
|
||||||
|
if (avatarType.size() == 0) {
|
||||||
|
return "";
|
||||||
|
} else {
|
||||||
|
return QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/" + acc->name + "/" + "avatar." + avatarType;
|
||||||
|
}
|
||||||
|
}
|
65
core/handlers/vcardhandler.h
Normal file
65
core/handlers/vcardhandler.h
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
// 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_VCARDHANDLER_H
|
||||||
|
#define CORE_VCARDHANDLER_H
|
||||||
|
|
||||||
|
#include <set>
|
||||||
|
|
||||||
|
#include <QXmppVCardIq.h>
|
||||||
|
#include <QXmppPresence.h>
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
|
||||||
|
#include <shared/vcard.h>
|
||||||
|
#include <core/adapterfunctions.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @todo write docs
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Core {
|
||||||
|
|
||||||
|
class Account;
|
||||||
|
|
||||||
|
class VCardHandler : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
VCardHandler(Account* account);
|
||||||
|
~VCardHandler();
|
||||||
|
|
||||||
|
void handleOffline();
|
||||||
|
void requestVCard(const QString& jid);
|
||||||
|
void handleOtherPresenceOfMyAccountChange(const QXmppPresence& p_presence);
|
||||||
|
void uploadVCard(const Shared::VCard& card);
|
||||||
|
QString getAvatarPath() const;
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void onVCardReceived(const QXmppVCardIq& card);
|
||||||
|
void onOwnVCardReceived(const QXmppVCardIq& card);
|
||||||
|
|
||||||
|
private:
|
||||||
|
Account* acc;
|
||||||
|
|
||||||
|
bool ownVCardRequestInProgress;
|
||||||
|
std::set<QString> pendingVCardRequests;
|
||||||
|
QString avatarHash;
|
||||||
|
QString avatarType;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // CORE_VCARDHANDLER_H
|
@ -28,8 +28,11 @@ Core::NetworkAccess::NetworkAccess(QObject* parent):
|
|||||||
manager(0),
|
manager(0),
|
||||||
storage("fileURLStorage"),
|
storage("fileURLStorage"),
|
||||||
downloads(),
|
downloads(),
|
||||||
uploads()
|
uploads(),
|
||||||
|
currentPath()
|
||||||
{
|
{
|
||||||
|
QSettings settings;
|
||||||
|
currentPath = settings.value("downloadsPath").toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
Core::NetworkAccess::~NetworkAccess()
|
Core::NetworkAccess::~NetworkAccess()
|
||||||
@ -70,6 +73,9 @@ void Core::NetworkAccess::start()
|
|||||||
{
|
{
|
||||||
if (!running) {
|
if (!running) {
|
||||||
manager = new QNetworkAccessManager();
|
manager = new QNetworkAccessManager();
|
||||||
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
|
||||||
|
manager->setTransferTimeout();
|
||||||
|
#endif
|
||||||
storage.open();
|
storage.open();
|
||||||
running = true;
|
running = true;
|
||||||
}
|
}
|
||||||
@ -99,6 +105,7 @@ 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";
|
qDebug() << "an error downloading" << url << ": the request had some progress but seems like no one is waiting for it, skipping";
|
||||||
} else {
|
} else {
|
||||||
Transfer* dwn = itr->second;
|
Transfer* dwn = itr->second;
|
||||||
|
if (dwn->success) {
|
||||||
qreal received = bytesReceived;
|
qreal received = bytesReceived;
|
||||||
qreal total = bytesTotal;
|
qreal total = bytesTotal;
|
||||||
qreal progress = received/total;
|
qreal progress = received/total;
|
||||||
@ -106,23 +113,47 @@ void Core::NetworkAccess::onDownloadProgress(qint64 bytesReceived, qint64 bytesT
|
|||||||
emit loadFileProgress(dwn->messages, progress, false);
|
emit loadFileProgress(dwn->messages, progress, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Core::NetworkAccess::onDownloadError(QNetworkReply::NetworkError code)
|
void Core::NetworkAccess::onDownloadError(QNetworkReply::NetworkError code)
|
||||||
{
|
{
|
||||||
|
qDebug() << "DEBUG: DOWNLOAD ERROR";
|
||||||
QNetworkReply* rpl = static_cast<QNetworkReply*>(sender());
|
QNetworkReply* rpl = static_cast<QNetworkReply*>(sender());
|
||||||
|
qDebug() << rpl->errorString();
|
||||||
QString url = rpl->url().toString();
|
QString url = rpl->url().toString();
|
||||||
std::map<QString, Transfer*>::const_iterator itr = downloads.find(url);
|
std::map<QString, Transfer*>::const_iterator itr = downloads.find(url);
|
||||||
if (itr == downloads.end()) {
|
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";
|
qDebug() << "an error downloading" << url << ": the request is reporting an error but seems like no one is waiting for it, skipping";
|
||||||
} else {
|
} else {
|
||||||
QString errorText = getErrorText(code);
|
QString errorText = getErrorText(code);
|
||||||
if (errorText.size() > 0) {
|
//if (errorText.size() > 0) {
|
||||||
itr->second->success = false;
|
itr->second->success = false;
|
||||||
Transfer* dwn = itr->second;
|
Transfer* dwn = itr->second;
|
||||||
emit loadFileError(dwn->messages, errorText, false);
|
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 Core::NetworkAccess::getErrorText(QNetworkReply::NetworkError code)
|
||||||
{
|
{
|
||||||
@ -146,7 +177,11 @@ QString Core::NetworkAccess::getErrorText(QNetworkReply::NetworkError code)
|
|||||||
errorText = "Connection was closed because it timed out";
|
errorText = "Connection was closed because it timed out";
|
||||||
break;
|
break;
|
||||||
case QNetworkReply::OperationCanceledError:
|
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;
|
break;
|
||||||
case QNetworkReply::SslHandshakeFailedError:
|
case QNetworkReply::SslHandshakeFailedError:
|
||||||
errorText = "Security error"; //TODO need to handle sslErrors signal to get a better description here
|
errorText = "Security error"; //TODO need to handle sslErrors signal to get a better description here
|
||||||
@ -247,6 +282,7 @@ QString Core::NetworkAccess::getErrorText(QNetworkReply::NetworkError code)
|
|||||||
|
|
||||||
void Core::NetworkAccess::onDownloadFinished()
|
void Core::NetworkAccess::onDownloadFinished()
|
||||||
{
|
{
|
||||||
|
qDebug() << "DEBUG: DOWNLOAD FINISHED";
|
||||||
QNetworkReply* rpl = static_cast<QNetworkReply*>(sender());
|
QNetworkReply* rpl = static_cast<QNetworkReply*>(sender());
|
||||||
QString url = rpl->url().toString();
|
QString url = rpl->url().toString();
|
||||||
std::map<QString, Transfer*>::const_iterator itr = downloads.find(url);
|
std::map<QString, Transfer*>::const_iterator itr = downloads.find(url);
|
||||||
@ -256,17 +292,20 @@ void Core::NetworkAccess::onDownloadFinished()
|
|||||||
Transfer* dwn = itr->second;
|
Transfer* dwn = itr->second;
|
||||||
if (dwn->success) {
|
if (dwn->success) {
|
||||||
qDebug() << "download success for" << url;
|
qDebug() << "download success for" << url;
|
||||||
|
QString err;
|
||||||
QStringList hops = url.split("/");
|
QStringList hops = url.split("/");
|
||||||
QString fileName = hops.back();
|
QString fileName = hops.back();
|
||||||
QString jid;
|
QString jid;
|
||||||
if (dwn->messages.size() > 0) {
|
if (dwn->messages.size() > 0) {
|
||||||
jid = dwn->messages.front().jid;
|
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);
|
QString path = prepareDirectory(jid);
|
||||||
if (path.size() > 0) {
|
if (path.size() > 0) {
|
||||||
path = checkFileName(fileName, path);
|
path = checkFileName(fileName, path);
|
||||||
|
|
||||||
QFile file(path);
|
QFile file(Shared::resolvePath(path));
|
||||||
if (file.open(QIODevice::WriteOnly)) {
|
if (file.open(QIODevice::WriteOnly)) {
|
||||||
file.write(dwn->reply->readAll());
|
file.write(dwn->reply->readAll());
|
||||||
file.close();
|
file.close();
|
||||||
@ -274,15 +313,16 @@ void Core::NetworkAccess::onDownloadFinished()
|
|||||||
qDebug() << "file" << path << "was successfully downloaded";
|
qDebug() << "file" << path << "was successfully downloaded";
|
||||||
} else {
|
} else {
|
||||||
qDebug() << "couldn't save file" << path;
|
qDebug() << "couldn't save file" << path;
|
||||||
path = QString();
|
err = "Error opening file to write:" + file.errorString();
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
err = "Couldn't prepare a directory for file";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (path.size() > 0) {
|
if (path.size() > 0) {
|
||||||
emit downloadFileComplete(dwn->messages, path);
|
emit downloadFileComplete(dwn->messages, path);
|
||||||
} else {
|
} else {
|
||||||
//TODO do I need to handle the failure here or it's already being handled in error?
|
emit loadFileError(dwn->messages, "Error saving file " + url + "; " + err, false);
|
||||||
//emit loadFileError(dwn->messages, path, false);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -298,6 +338,7 @@ void Core::NetworkAccess::startDownload(const std::list<Shared::MessageInfo>& ms
|
|||||||
QNetworkRequest req(url);
|
QNetworkRequest req(url);
|
||||||
dwn->reply = manager->get(req);
|
dwn->reply = manager->get(req);
|
||||||
connect(dwn->reply, &QNetworkReply::downloadProgress, this, &NetworkAccess::onDownloadProgress);
|
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)
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
|
||||||
connect(dwn->reply, qOverload<QNetworkReply::NetworkError>(&QNetworkReply::errorOccurred), this, &NetworkAccess::onDownloadError);
|
connect(dwn->reply, qOverload<QNetworkReply::NetworkError>(&QNetworkReply::errorOccurred), this, &NetworkAccess::onDownloadError);
|
||||||
#else
|
#else
|
||||||
@ -317,11 +358,11 @@ void Core::NetworkAccess::onUploadError(QNetworkReply::NetworkError code)
|
|||||||
qDebug() << "an error uploading" << url << ": the request is reporting an error but there is no record of it being uploading, ignoring";
|
qDebug() << "an error uploading" << url << ": the request is reporting an error but there is no record of it being uploading, ignoring";
|
||||||
} else {
|
} else {
|
||||||
QString errorText = getErrorText(code);
|
QString errorText = getErrorText(code);
|
||||||
if (errorText.size() > 0) {
|
//if (errorText.size() > 0) {
|
||||||
itr->second->success = false;
|
itr->second->success = false;
|
||||||
Transfer* upl = itr->second;
|
Transfer* upl = itr->second;
|
||||||
emit loadFileError(upl->messages, errorText, true);
|
emit loadFileError(upl->messages, errorText, true);
|
||||||
}
|
//}
|
||||||
|
|
||||||
//TODO deletion?
|
//TODO deletion?
|
||||||
}
|
}
|
||||||
@ -339,8 +380,33 @@ void Core::NetworkAccess::onUploadFinished()
|
|||||||
if (upl->success) {
|
if (upl->success) {
|
||||||
qDebug() << "upload success for" << url;
|
qDebug() << "upload success for" << url;
|
||||||
|
|
||||||
|
// Copy file to Download folder if it is a temp file. See Conversation::onImagePasted.
|
||||||
|
if (upl->path.startsWith(QDir::tempPath() + QDir::separator() + QStringLiteral("squawk_img_attach_")) && upl->path.endsWith(".png")) {
|
||||||
|
QString err = "";
|
||||||
|
QString downloadDirPath = prepareDirectory(upl->messages.front().jid);
|
||||||
|
if (downloadDirPath.size() > 0) {
|
||||||
|
QString newPath = downloadDirPath + QDir::separator() + upl->path.mid(QDir::tempPath().length() + 1);
|
||||||
|
|
||||||
|
// Copy {TEMPDIR}/squawk_img_attach_XXXXXX.png to Download folder
|
||||||
|
bool copyResult = QFile::copy(upl->path, Shared::resolvePath(newPath));
|
||||||
|
|
||||||
|
if (copyResult) {
|
||||||
|
// Change storage
|
||||||
|
upl->path = newPath;
|
||||||
|
} else {
|
||||||
|
err = "copying to " + newPath + " failed";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = "Couldn't prepare a directory for file";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (err.size() != 0) {
|
||||||
|
qDebug() << "failed to copy temporary upload file " << upl->path << " to download folder:" << err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
storage.addFile(upl->messages, upl->url, upl->path);
|
storage.addFile(upl->messages, upl->url, upl->path);
|
||||||
emit uploadFileComplete(upl->messages, upl->url);
|
emit uploadFileComplete(upl->messages, upl->url, upl->path);
|
||||||
}
|
}
|
||||||
|
|
||||||
upl->reply->deleteLater();
|
upl->reply->deleteLater();
|
||||||
@ -360,6 +426,7 @@ 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";
|
qDebug() << "an error downloading" << url << ": the request had some progress but seems like no one is waiting for it, skipping";
|
||||||
} else {
|
} else {
|
||||||
Transfer* upl = itr->second;
|
Transfer* upl = itr->second;
|
||||||
|
if (upl->success) {
|
||||||
qreal received = bytesReceived;
|
qreal received = bytesReceived;
|
||||||
qreal total = bytesTotal;
|
qreal total = bytesTotal;
|
||||||
qreal progress = received/total;
|
qreal progress = received/total;
|
||||||
@ -367,15 +434,16 @@ void Core::NetworkAccess::onUploadProgress(qint64 bytesReceived, qint64 bytesTot
|
|||||||
emit loadFileProgress(upl->messages, progress, true);
|
emit loadFileProgress(upl->messages, progress, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
QString Core::NetworkAccess::getFileRemoteUrl(const QString& path)
|
QString Core::NetworkAccess::getFileRemoteUrl(const QString& path)
|
||||||
{
|
{
|
||||||
QString p;
|
QString p = Shared::squawkifyPath(path);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
p = storage.getUrl(path);
|
p = storage.getUrl(p);
|
||||||
} catch (const Archive::NotFound& err) {
|
} catch (const Archive::NotFound& err) {
|
||||||
|
p = "";
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
@ -450,11 +518,13 @@ bool Core::NetworkAccess::checkAndAddToUploading(const QString& acc, const QStri
|
|||||||
|
|
||||||
QString Core::NetworkAccess::prepareDirectory(const QString& jid)
|
QString Core::NetworkAccess::prepareDirectory(const QString& jid)
|
||||||
{
|
{
|
||||||
QString path = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation);
|
QString path = currentPath;
|
||||||
path += "/" + QApplication::applicationName();
|
QString addition;
|
||||||
if (jid.size() > 0) {
|
if (jid.size() > 0) {
|
||||||
path += "/" + jid;
|
addition = jid;
|
||||||
|
path += QDir::separator() + jid;
|
||||||
}
|
}
|
||||||
|
|
||||||
QDir location(path);
|
QDir location(path);
|
||||||
|
|
||||||
if (!location.exists()) {
|
if (!location.exists()) {
|
||||||
@ -462,10 +532,10 @@ QString Core::NetworkAccess::prepareDirectory(const QString& jid)
|
|||||||
if (!res) {
|
if (!res) {
|
||||||
return "";
|
return "";
|
||||||
} else {
|
} else {
|
||||||
return path;
|
return "squawk://" + addition;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return path;
|
return "squawk://" + addition;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString Core::NetworkAccess::checkFileName(const QString& name, const QString& path)
|
QString Core::NetworkAccess::checkFileName(const QString& name, const QString& path)
|
||||||
@ -479,14 +549,17 @@ QString Core::NetworkAccess::checkFileName(const QString& name, const QString& p
|
|||||||
suffix += "." + (*sItr);
|
suffix += "." + (*sItr);
|
||||||
}
|
}
|
||||||
QString postfix("");
|
QString postfix("");
|
||||||
QFileInfo proposedName(path + "/" + realName + suffix);
|
QString resolvedPath = Shared::resolvePath(path);
|
||||||
|
QString count("");
|
||||||
|
QFileInfo proposedName(resolvedPath + QDir::separator() + realName + count + suffix);
|
||||||
|
|
||||||
int counter = 0;
|
int counter = 0;
|
||||||
while (proposedName.exists()) {
|
while (proposedName.exists()) {
|
||||||
QString count = QString("(") + std::to_string(++counter).c_str() + ")";
|
count = QString("(") + std::to_string(++counter).c_str() + ")";
|
||||||
proposedName = QFileInfo(path + "/" + realName + count + suffix);
|
proposedName = QFileInfo(resolvedPath + QDir::separator() + realName + count + suffix);
|
||||||
}
|
}
|
||||||
|
|
||||||
return proposedName.absoluteFilePath();
|
return path + QDir::separator() + realName + count + suffix;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString Core::NetworkAccess::addMessageAndCheckForPath(const QString& url, const QString& account, const QString& jid, const QString& id)
|
QString Core::NetworkAccess::addMessageAndCheckForPath(const QString& url, const QString& account, const QString& jid, const QString& id)
|
||||||
@ -498,3 +571,19 @@ std::list<Shared::MessageInfo> Core::NetworkAccess::reportPathInvalid(const QStr
|
|||||||
{
|
{
|
||||||
return storage.deletedFile(path);
|
return storage.deletedFile(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Core::NetworkAccess::moveFilesDirectory(const QString& newPath)
|
||||||
|
{
|
||||||
|
QDir dir(currentPath);
|
||||||
|
bool success = true;
|
||||||
|
qDebug() << "moving" << currentPath << "to" << newPath;
|
||||||
|
for (QFileInfo fileInfo : dir.entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot | QDir::Hidden | QDir::System)) {
|
||||||
|
QString fileName = fileInfo.fileName();
|
||||||
|
success = dir.rename(fileName, newPath + QDir::separator() + fileName) && success;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!success) {
|
||||||
|
qDebug() << "couldn't move downloads directory, most probably downloads will be broken";
|
||||||
|
}
|
||||||
|
currentPath = newPath;
|
||||||
|
}
|
||||||
|
@ -26,10 +26,12 @@
|
|||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include <QStandardPaths>
|
#include <QStandardPaths>
|
||||||
|
#include <QSettings>
|
||||||
|
|
||||||
#include <set>
|
#include <set>
|
||||||
|
|
||||||
#include "urlstorage.h"
|
#include "storage/urlstorage.h"
|
||||||
|
#include "shared/pathcheck.h"
|
||||||
|
|
||||||
namespace Core {
|
namespace Core {
|
||||||
|
|
||||||
@ -58,13 +60,14 @@ public:
|
|||||||
signals:
|
signals:
|
||||||
void loadFileProgress(const std::list<Shared::MessageInfo>& msgs, qreal value, bool up);
|
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 loadFileError(const std::list<Shared::MessageInfo>& msgs, const QString& text, bool up);
|
||||||
void uploadFileComplete(const std::list<Shared::MessageInfo>& msgs, const QString& url);
|
void uploadFileComplete(const std::list<Shared::MessageInfo>& msgs, const QString& url, const QString& path);
|
||||||
void downloadFileComplete(const std::list<Shared::MessageInfo>& msgs, const QString& path);
|
void downloadFileComplete(const std::list<Shared::MessageInfo>& msgs, const QString& path);
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void downladFile(const QString& url);
|
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& account, const QString& jid, const QString& id);
|
||||||
void registerFile(const QString& url, const QString& path, const QString& account, const QString& jid, const QString& id);
|
void registerFile(const QString& url, const QString& path, const QString& account, const QString& jid, const QString& id);
|
||||||
|
void moveFilesDirectory(const QString& newPath);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void startDownload(const std::list<Shared::MessageInfo>& msgs, const QString& url);
|
void startDownload(const std::list<Shared::MessageInfo>& msgs, const QString& url);
|
||||||
@ -75,6 +78,7 @@ private:
|
|||||||
private slots:
|
private slots:
|
||||||
void onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal);
|
void onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal);
|
||||||
void onDownloadError(QNetworkReply::NetworkError code);
|
void onDownloadError(QNetworkReply::NetworkError code);
|
||||||
|
void onDownloadSSLError(const QList<QSslError> &errors);
|
||||||
void onDownloadFinished();
|
void onDownloadFinished();
|
||||||
void onUploadProgress(qint64 bytesReceived, qint64 bytesTotal);
|
void onUploadProgress(qint64 bytesReceived, qint64 bytesTotal);
|
||||||
void onUploadError(QNetworkReply::NetworkError code);
|
void onUploadError(QNetworkReply::NetworkError code);
|
||||||
@ -86,6 +90,7 @@ private:
|
|||||||
UrlStorage storage;
|
UrlStorage storage;
|
||||||
std::map<QString, Transfer*> downloads;
|
std::map<QString, Transfer*> downloads;
|
||||||
std::map<QString, Transfer*> uploads;
|
std::map<QString, Transfer*> uploads;
|
||||||
|
QString currentPath;
|
||||||
|
|
||||||
struct Transfer {
|
struct Transfer {
|
||||||
std::list<Shared::MessageInfo> messages;
|
std::list<Shared::MessageInfo> messages;
|
||||||
|
@ -1,37 +1,9 @@
|
|||||||
cmake_minimum_required(VERSION 3.3)
|
|
||||||
project(pse)
|
|
||||||
|
|
||||||
if (WITH_KWALLET)
|
if (WITH_KWALLET)
|
||||||
set(CMAKE_AUTOMOC ON)
|
target_sources(squawk PRIVATE
|
||||||
|
|
||||||
find_package(Qt5Core CONFIG REQUIRED)
|
|
||||||
find_package(Qt5Gui CONFIG REQUIRED)
|
|
||||||
|
|
||||||
get_target_property(KWALLET_INTERFACE_INCLUDE_DIRECTORIES KF5::Wallet INTERFACE_INCLUDE_DIRECTORIES)
|
|
||||||
get_target_property(Qt5GUI_INTERFACE_INCLUDE_DIRECTORIES Qt5::Gui INTERFACE_INCLUDE_DIRECTORIES)
|
|
||||||
|
|
||||||
set(kwalletPSE_SRC
|
|
||||||
kwallet.cpp
|
kwallet.cpp
|
||||||
|
kwallet.h
|
||||||
)
|
)
|
||||||
|
|
||||||
add_library(kwalletPSE STATIC ${kwalletPSE_SRC})
|
add_subdirectory(wrappers)
|
||||||
|
target_include_directories(squawk PRIVATE $<TARGET_PROPERTY:KF5::Wallet,INTERFACE_INCLUDE_DIRECTORIES>)
|
||||||
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})
|
|
||||||
endif ()
|
endif ()
|
||||||
|
4
core/passwordStorageEngines/wrappers/CMakeLists.txt
Normal file
4
core/passwordStorageEngines/wrappers/CMakeLists.txt
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
add_library(kwalletWrapper SHARED kwallet.cpp)
|
||||||
|
target_link_libraries(kwalletWrapper PRIVATE KF5::Wallet)
|
||||||
|
|
||||||
|
install(TARGETS kwalletWrapper LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})
|
@ -1,3 +1,21 @@
|
|||||||
|
/*
|
||||||
|
* 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 <KF5/KWallet/KWallet>
|
#include <KF5/KWallet/KWallet>
|
||||||
|
|
||||||
extern "C" KWallet::Wallet* openWallet(const QString &name, WId w, KWallet::Wallet::OpenType ot = KWallet::Wallet::Synchronous) {
|
extern "C" KWallet::Wallet* openWallet(const QString &name, WId w, KWallet::Wallet::OpenType ot = KWallet::Wallet::Synchronous) {
|
||||||
|
@ -124,6 +124,7 @@ void Core::RosterItem::nextRequest()
|
|||||||
if (requestedCount != -1) {
|
if (requestedCount != -1) {
|
||||||
bool last = false;
|
bool last = false;
|
||||||
if (archiveState == beginning || archiveState == complete) {
|
if (archiveState == beginning || archiveState == complete) {
|
||||||
|
try {
|
||||||
QString firstId = archive->oldestId();
|
QString firstId = archive->oldestId();
|
||||||
if (responseCache.size() == 0) {
|
if (responseCache.size() == 0) {
|
||||||
if (requestedBefore == firstId) {
|
if (requestedBefore == firstId) {
|
||||||
@ -134,6 +135,9 @@ void Core::RosterItem::nextRequest()
|
|||||||
last = true;
|
last = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} catch (const Archive::Empty& e) {
|
||||||
|
last = true;
|
||||||
|
}
|
||||||
} else if (archiveState == empty && responseCache.size() == 0) {
|
} else if (archiveState == empty && responseCache.size() == 0) {
|
||||||
last = true;
|
last = true;
|
||||||
}
|
}
|
||||||
@ -171,8 +175,12 @@ void Core::RosterItem::performRequest(int count, const QString& before)
|
|||||||
requestCache.emplace_back(requestedCount, before);
|
requestCache.emplace_back(requestedCount, before);
|
||||||
requestedCount = -1;
|
requestedCount = -1;
|
||||||
}
|
}
|
||||||
|
try {
|
||||||
Shared::Message msg = archive->newest();
|
Shared::Message msg = archive->newest();
|
||||||
emit needHistory("", getId(msg), msg.getTime());
|
emit needHistory("", getId(msg), msg.getTime());
|
||||||
|
} catch (const Archive::Empty& e) { //this can happen when the only message in archive is not server stored (error, for example)
|
||||||
|
emit needHistory(before, "");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case end:
|
case end:
|
||||||
|
@ -34,7 +34,8 @@
|
|||||||
#include "shared/enums.h"
|
#include "shared/enums.h"
|
||||||
#include "shared/message.h"
|
#include "shared/message.h"
|
||||||
#include "shared/vcard.h"
|
#include "shared/vcard.h"
|
||||||
#include "archive.h"
|
#include "storage/archive.h"
|
||||||
|
#include "adapterfunctions.h"
|
||||||
|
|
||||||
namespace Core {
|
namespace Core {
|
||||||
|
|
||||||
|
@ -50,7 +50,7 @@ void SignalCatcher::handleSigInt()
|
|||||||
char tmp;
|
char tmp;
|
||||||
ssize_t s = ::read(sigintFd[1], &tmp, sizeof(tmp));
|
ssize_t s = ::read(sigintFd[1], &tmp, sizeof(tmp));
|
||||||
|
|
||||||
app->quit();
|
emit interrupt();
|
||||||
|
|
||||||
snInt->setEnabled(true);
|
snInt->setEnabled(true);
|
||||||
}
|
}
|
@ -33,6 +33,9 @@ public:
|
|||||||
|
|
||||||
static void intSignalHandler(int unused);
|
static void intSignalHandler(int unused);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void interrupt();
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void handleSigInt();
|
void handleSigInt();
|
||||||
|
|
42
core/signalcatcher_win32.cpp
Normal file
42
core/signalcatcher_win32.cpp
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* Squawk messenger.
|
||||||
|
* Copyright (C) 2021 Shunf4 <shun1048576@gmail.com>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "signalcatcher.h"
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
SignalCatcher::SignalCatcher(QCoreApplication *p_app, QObject *parent):
|
||||||
|
QObject(parent),
|
||||||
|
app(p_app)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
SignalCatcher::~SignalCatcher()
|
||||||
|
{}
|
||||||
|
|
||||||
|
void SignalCatcher::handleSigInt()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void SignalCatcher::intSignalHandler(int unused)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
int SignalCatcher::setup_unix_signal_handlers()
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
305
core/squawk.cpp
305
core/squawk.cpp
@ -26,8 +26,9 @@ Core::Squawk::Squawk(QObject* parent):
|
|||||||
QObject(parent),
|
QObject(parent),
|
||||||
accounts(),
|
accounts(),
|
||||||
amap(),
|
amap(),
|
||||||
|
state(Shared::Availability::offline),
|
||||||
network(),
|
network(),
|
||||||
waitingForAccounts(0)
|
isInitialized(false)
|
||||||
#ifdef WITH_KWALLET
|
#ifdef WITH_KWALLET
|
||||||
,kwallet()
|
,kwallet()
|
||||||
#endif
|
#endif
|
||||||
@ -41,7 +42,7 @@ Core::Squawk::Squawk(QObject* parent):
|
|||||||
if (kwallet.supportState() == PSE::KWallet::success) {
|
if (kwallet.supportState() == PSE::KWallet::success) {
|
||||||
connect(&kwallet, &PSE::KWallet::opened, this, &Squawk::onWalletOpened);
|
connect(&kwallet, &PSE::KWallet::opened, this, &Squawk::onWalletOpened);
|
||||||
connect(&kwallet, &PSE::KWallet::rejectPassword, this, &Squawk::onWalletRejectPassword);
|
connect(&kwallet, &PSE::KWallet::rejectPassword, this, &Squawk::onWalletRejectPassword);
|
||||||
connect(&kwallet, &PSE::KWallet::responsePassword, this, &Squawk::onWalletResponsePassword);
|
connect(&kwallet, &PSE::KWallet::responsePassword, this, &Squawk::responsePassword);
|
||||||
|
|
||||||
Shared::Global::setSupported("KWallet", true);
|
Shared::Global::setSupported("KWallet", true);
|
||||||
}
|
}
|
||||||
@ -66,6 +67,8 @@ void Core::Squawk::stop()
|
|||||||
{
|
{
|
||||||
qDebug("Stopping squawk core..");
|
qDebug("Stopping squawk core..");
|
||||||
network.stop();
|
network.stop();
|
||||||
|
|
||||||
|
if (isInitialized) {
|
||||||
QSettings settings;
|
QSettings settings;
|
||||||
settings.beginGroup("core");
|
settings.beginGroup("core");
|
||||||
settings.beginWriteArray("accounts");
|
settings.beginWriteArray("accounts");
|
||||||
@ -94,11 +97,13 @@ void Core::Squawk::stop()
|
|||||||
settings.setValue("password", password);
|
settings.setValue("password", password);
|
||||||
settings.setValue("resource", acc->getResource());
|
settings.setValue("resource", acc->getResource());
|
||||||
settings.setValue("passwordType", static_cast<int>(ap));
|
settings.setValue("passwordType", static_cast<int>(ap));
|
||||||
|
settings.setValue("active", acc->getActive());
|
||||||
}
|
}
|
||||||
settings.endArray();
|
settings.endArray();
|
||||||
settings.endGroup();
|
settings.endGroup();
|
||||||
|
|
||||||
settings.sync();
|
settings.sync();
|
||||||
|
}
|
||||||
|
|
||||||
emit quit();
|
emit quit();
|
||||||
}
|
}
|
||||||
@ -108,6 +113,7 @@ void Core::Squawk::start()
|
|||||||
qDebug("Starting squawk core..");
|
qDebug("Starting squawk core..");
|
||||||
|
|
||||||
readSettings();
|
readSettings();
|
||||||
|
isInitialized = true;
|
||||||
network.start();
|
network.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -118,8 +124,10 @@ void Core::Squawk::newAccountRequest(const QMap<QString, QVariant>& map)
|
|||||||
QString server = map.value("server").toString();
|
QString server = map.value("server").toString();
|
||||||
QString password = map.value("password").toString();
|
QString password = map.value("password").toString();
|
||||||
QString resource = map.value("resource").toString();
|
QString resource = map.value("resource").toString();
|
||||||
|
int passwordType = map.value("passwordType").toInt();
|
||||||
|
bool active = map.value("active").toBool();
|
||||||
|
|
||||||
addAccount(login, server, password, name, resource, Shared::AccountPassword::plain);
|
addAccount(login, server, password, name, resource, active, Shared::Global::fromInt<Shared::AccountPassword>(passwordType));
|
||||||
}
|
}
|
||||||
|
|
||||||
void Core::Squawk::addAccount(
|
void Core::Squawk::addAccount(
|
||||||
@ -128,12 +136,14 @@ void Core::Squawk::addAccount(
|
|||||||
const QString& password,
|
const QString& password,
|
||||||
const QString& name,
|
const QString& name,
|
||||||
const QString& resource,
|
const QString& resource,
|
||||||
Shared::AccountPassword passwordType
|
bool active,
|
||||||
)
|
Shared::AccountPassword passwordType)
|
||||||
{
|
{
|
||||||
QSettings settings;
|
if (amap.count(name) > 0) {
|
||||||
|
qDebug() << "An attempt to add account" << name << "but an account with such name already exist, ignoring";
|
||||||
Account* acc = new Account(login, server, password, name, &network);
|
return;
|
||||||
|
}
|
||||||
|
Account* acc = new Account(login, server, password, name, active, &network);
|
||||||
acc->setResource(resource);
|
acc->setResource(resource);
|
||||||
acc->setPasswordType(passwordType);
|
acc->setPasswordType(passwordType);
|
||||||
accounts.push_back(acc);
|
accounts.push_back(acc);
|
||||||
@ -142,6 +152,8 @@ void Core::Squawk::addAccount(
|
|||||||
connect(acc, &Account::connectionStateChanged, this, &Squawk::onAccountConnectionStateChanged);
|
connect(acc, &Account::connectionStateChanged, this, &Squawk::onAccountConnectionStateChanged);
|
||||||
connect(acc, &Account::changed, this, &Squawk::onAccountChanged);
|
connect(acc, &Account::changed, this, &Squawk::onAccountChanged);
|
||||||
connect(acc, &Account::error, this, &Squawk::onAccountError);
|
connect(acc, &Account::error, this, &Squawk::onAccountError);
|
||||||
|
connect(acc, &Account::needPassword, this, &Squawk::onAccountNeedPassword);
|
||||||
|
|
||||||
connect(acc, &Account::availabilityChanged, this, &Squawk::onAccountAvailabilityChanged);
|
connect(acc, &Account::availabilityChanged, this, &Squawk::onAccountAvailabilityChanged);
|
||||||
connect(acc, &Account::addContact, this, &Squawk::onAccountAddContact);
|
connect(acc, &Account::addContact, this, &Squawk::onAccountAddContact);
|
||||||
connect(acc, &Account::addGroup, this, &Squawk::onAccountAddGroup);
|
connect(acc, &Account::addGroup, this, &Squawk::onAccountAddGroup);
|
||||||
@ -179,20 +191,44 @@ void Core::Squawk::addAccount(
|
|||||||
{"offline", QVariant::fromValue(Shared::Availability::offline)},
|
{"offline", QVariant::fromValue(Shared::Availability::offline)},
|
||||||
{"error", ""},
|
{"error", ""},
|
||||||
{"avatarPath", acc->getAvatarPath()},
|
{"avatarPath", acc->getAvatarPath()},
|
||||||
{"passwordType", QVariant::fromValue(passwordType)}
|
{"passwordType", QVariant::fromValue(passwordType)},
|
||||||
|
{"active", active}
|
||||||
};
|
};
|
||||||
|
|
||||||
emit newAccount(map);
|
emit newAccount(map);
|
||||||
|
|
||||||
|
switch (passwordType) {
|
||||||
|
case Shared::AccountPassword::alwaysAsk:
|
||||||
|
case Shared::AccountPassword::kwallet:
|
||||||
|
if (password == "") {
|
||||||
|
acc->invalidatePassword();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state != Shared::Availability::offline) {
|
||||||
|
acc->setAvailability(state);
|
||||||
|
if (acc->getActive()) {
|
||||||
|
acc->connect();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Core::Squawk::changeState(Shared::Availability p_state)
|
void Core::Squawk::changeState(Shared::Availability p_state)
|
||||||
{
|
{
|
||||||
if (state != p_state) {
|
if (state != p_state) {
|
||||||
state = p_state;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (std::deque<Account*>::iterator itr = accounts.begin(), end = accounts.end(); itr != end; ++itr) {
|
for (std::deque<Account*>::iterator itr = accounts.begin(), end = accounts.end(); itr != end; ++itr) {
|
||||||
(*itr)->setAvailability(state);
|
Account* acc = *itr;
|
||||||
|
acc->setAvailability(p_state);
|
||||||
|
if (state == Shared::Availability::offline && acc->getActive()) {
|
||||||
|
acc->connect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
state = p_state;
|
||||||
|
|
||||||
|
emit stateChanged(p_state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -203,8 +239,11 @@ void Core::Squawk::connectAccount(const QString& account)
|
|||||||
qDebug("An attempt to connect non existing account, skipping");
|
qDebug("An attempt to connect non existing account, skipping");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
itr->second->setActive(true);
|
||||||
|
if (state != Shared::Availability::offline) {
|
||||||
itr->second->connect();
|
itr->second->connect();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Core::Squawk::disconnectAccount(const QString& account)
|
void Core::Squawk::disconnectAccount(const QString& account)
|
||||||
{
|
{
|
||||||
@ -214,13 +253,17 @@ void Core::Squawk::disconnectAccount(const QString& account)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
itr->second->setActive(false);
|
||||||
itr->second->disconnect();
|
itr->second->disconnect();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Core::Squawk::onAccountConnectionStateChanged(Shared::ConnectionState p_state)
|
void Core::Squawk::onAccountConnectionStateChanged(Shared::ConnectionState p_state)
|
||||||
{
|
{
|
||||||
Account* acc = static_cast<Account*>(sender());
|
Account* acc = static_cast<Account*>(sender());
|
||||||
emit changeAccount(acc->getName(), {{"state", QVariant::fromValue(p_state)}});
|
emit changeAccount(acc->getName(), {
|
||||||
|
{"state", QVariant::fromValue(p_state)},
|
||||||
|
{"error", ""}
|
||||||
|
});
|
||||||
|
|
||||||
#ifdef WITH_KWALLET
|
#ifdef WITH_KWALLET
|
||||||
if (p_state == Shared::ConnectionState::connected) {
|
if (p_state == Shared::ConnectionState::connected) {
|
||||||
@ -229,33 +272,6 @@ void Core::Squawk::onAccountConnectionStateChanged(Shared::ConnectionState p_sta
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
Accounts::const_iterator itr = accounts.begin();
|
|
||||||
bool es = true;
|
|
||||||
bool ea = true;
|
|
||||||
Shared::ConnectionState cs = (*itr)->getState();
|
|
||||||
Shared::Availability av = (*itr)->getAvailability();
|
|
||||||
itr++;
|
|
||||||
for (Accounts::const_iterator end = accounts.end(); itr != end; itr++) {
|
|
||||||
Account* item = *itr;
|
|
||||||
if (item->getState() != cs) {
|
|
||||||
es = false;
|
|
||||||
}
|
|
||||||
if (item->getAvailability() != av) {
|
|
||||||
ea = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (es) {
|
|
||||||
if (cs == Shared::ConnectionState::disconnected) {
|
|
||||||
state = Shared::Availability::offline;
|
|
||||||
emit stateChanged(state);
|
|
||||||
} else if (ea) {
|
|
||||||
state = av;
|
|
||||||
emit stateChanged(state);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Core::Squawk::onAccountAddContact(const QString& jid, const QString& group, const QMap<QString, QVariant>& data)
|
void Core::Squawk::onAccountAddContact(const QString& jid, const QString& group, const QMap<QString, QVariant>& data)
|
||||||
@ -328,13 +344,35 @@ void Core::Squawk::sendMessage(const QString& account, const Shared::Message& da
|
|||||||
{
|
{
|
||||||
AccountsMap::const_iterator itr = amap.find(account);
|
AccountsMap::const_iterator itr = amap.find(account);
|
||||||
if (itr == amap.end()) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
itr->second->sendMessage(data);
|
itr->second->sendMessage(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Core::Squawk::replaceMessage(const QString& account, const QString& originalId, const Shared::Message& data)
|
||||||
|
{
|
||||||
|
AccountsMap::const_iterator itr = amap.find(account);
|
||||||
|
if (itr == amap.end()) {
|
||||||
|
qDebug() << "An attempt to replace a message with non existing account" << account << ", skipping";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
itr->second->replaceMessage(originalId, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Core::Squawk::resendMessage(const QString& account, const QString& jid, const QString& id)
|
||||||
|
{
|
||||||
|
AccountsMap::const_iterator itr = amap.find(account);
|
||||||
|
if (itr == amap.end()) {
|
||||||
|
qDebug() << "An attempt to resend a message with non existing account" << account << ", skipping";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
itr->second->resendMessage(jid, id);
|
||||||
|
}
|
||||||
|
|
||||||
void Core::Squawk::requestArchive(const QString& account, const QString& jid, int count, const QString& before)
|
void Core::Squawk::requestArchive(const QString& account, const QString& jid, int count, const QString& before)
|
||||||
{
|
{
|
||||||
AccountsMap::const_iterator itr = amap.find(account);
|
AccountsMap::const_iterator itr = amap.find(account);
|
||||||
@ -363,6 +401,7 @@ void Core::Squawk::modifyAccountRequest(const QString& name, const QMap<QString,
|
|||||||
Shared::ConnectionState st = acc->getState();
|
Shared::ConnectionState st = acc->getState();
|
||||||
QMap<QString, QVariant>::const_iterator mItr;
|
QMap<QString, QVariant>::const_iterator mItr;
|
||||||
bool needToReconnect = false;
|
bool needToReconnect = false;
|
||||||
|
bool wentReconnecting = false;
|
||||||
|
|
||||||
mItr = map.find("login");
|
mItr = map.find("login");
|
||||||
if (mItr != map.end()) {
|
if (mItr != map.end()) {
|
||||||
@ -388,8 +427,16 @@ void Core::Squawk::modifyAccountRequest(const QString& name, const QMap<QString,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool activeChanged = false;
|
||||||
|
mItr = map.find("active");
|
||||||
|
if (mItr == map.end() || mItr->toBool() == acc->getActive()) {
|
||||||
if (needToReconnect && st != Shared::ConnectionState::disconnected) {
|
if (needToReconnect && st != Shared::ConnectionState::disconnected) {
|
||||||
acc->reconnect();
|
acc->reconnect();
|
||||||
|
wentReconnecting = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
acc->setActive(mItr->toBool());
|
||||||
|
activeChanged = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
mItr = map.find("login");
|
mItr = map.find("login");
|
||||||
@ -426,6 +473,14 @@ void Core::Squawk::modifyAccountRequest(const QString& name, const QMap<QString,
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
if (state != Shared::Availability::offline) {
|
||||||
|
if (activeChanged && acc->getActive()) {
|
||||||
|
acc->connect();
|
||||||
|
} else if (!wentReconnecting && acc->getActive() && acc->getLastError() == Account::Error::authentication) {
|
||||||
|
acc->connect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
emit changeAccount(name, map);
|
emit changeAccount(name, map);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -433,6 +488,10 @@ void Core::Squawk::onAccountError(const QString& text)
|
|||||||
{
|
{
|
||||||
Account* acc = static_cast<Account*>(sender());
|
Account* acc = static_cast<Account*>(sender());
|
||||||
emit changeAccount(acc->getName(), {{"error", text}});
|
emit changeAccount(acc->getName(), {{"error", text}});
|
||||||
|
|
||||||
|
if (acc->getLastError() == Account::Error::authentication) {
|
||||||
|
emit requestPassword(acc->getName(), true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Core::Squawk::removeAccountRequest(const QString& name)
|
void Core::Squawk::removeAccountRequest(const QString& name)
|
||||||
@ -647,6 +706,70 @@ void Core::Squawk::uploadVCard(const QString& account, const Shared::VCard& card
|
|||||||
itr->second->uploadVCard(card);
|
itr->second->uploadVCard(card);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Core::Squawk::readSettings()
|
||||||
|
{
|
||||||
|
QSettings settings;
|
||||||
|
settings.beginGroup("core");
|
||||||
|
int size = settings.beginReadArray("accounts");
|
||||||
|
for (int i = 0; i < size; ++i) {
|
||||||
|
settings.setArrayIndex(i);
|
||||||
|
Shared::AccountPassword passwordType =
|
||||||
|
Shared::Global::fromInt<Shared::AccountPassword>(
|
||||||
|
settings.value("passwordType", static_cast<int>(Shared::AccountPassword::plain)).toInt()
|
||||||
|
);
|
||||||
|
|
||||||
|
QString password = settings.value("password", "").toString();
|
||||||
|
if (passwordType == Shared::AccountPassword::jammed) {
|
||||||
|
SimpleCrypt crypto(passwordHash);
|
||||||
|
password = crypto.decryptToString(password);
|
||||||
|
}
|
||||||
|
|
||||||
|
addAccount(
|
||||||
|
settings.value("login").toString(),
|
||||||
|
settings.value("server").toString(),
|
||||||
|
password,
|
||||||
|
settings.value("name").toString(),
|
||||||
|
settings.value("resource").toString(),
|
||||||
|
settings.value("active").toBool(),
|
||||||
|
passwordType
|
||||||
|
);
|
||||||
|
}
|
||||||
|
settings.endArray();
|
||||||
|
settings.endGroup();
|
||||||
|
|
||||||
|
qDebug() << "Squawk core is ready";
|
||||||
|
emit ready();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Core::Squawk::onAccountNeedPassword()
|
||||||
|
{
|
||||||
|
Account* acc = static_cast<Account*>(sender());
|
||||||
|
switch (acc->getPasswordType()) {
|
||||||
|
case Shared::AccountPassword::alwaysAsk:
|
||||||
|
emit requestPassword(acc->getName(), false);
|
||||||
|
break;
|
||||||
|
case Shared::AccountPassword::kwallet: {
|
||||||
|
#ifdef WITH_KWALLET
|
||||||
|
if (kwallet.supportState() == PSE::KWallet::success) {
|
||||||
|
kwallet.requestReadPassword(acc->getName());
|
||||||
|
} else {
|
||||||
|
#endif
|
||||||
|
emit requestPassword(acc->getName(), false);
|
||||||
|
#ifdef WITH_KWALLET
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break; //should never happen;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Core::Squawk::onWalletRejectPassword(const QString& login)
|
||||||
|
{
|
||||||
|
emit requestPassword(login, false);
|
||||||
|
}
|
||||||
|
|
||||||
void Core::Squawk::responsePassword(const QString& account, const QString& password)
|
void Core::Squawk::responsePassword(const QString& account, const QString& password)
|
||||||
{
|
{
|
||||||
AccountsMap::const_iterator itr = amap.find(account);
|
AccountsMap::const_iterator itr = amap.find(account);
|
||||||
@ -654,96 +777,12 @@ void Core::Squawk::responsePassword(const QString& account, const QString& passw
|
|||||||
qDebug() << "An attempt to set password to non existing account" << account << ", skipping";
|
qDebug() << "An attempt to set password to non existing account" << account << ", skipping";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
itr->second->setPassword(password);
|
Account* acc = itr->second;
|
||||||
|
acc->setPassword(password);
|
||||||
emit changeAccount(account, {{"password", password}});
|
emit changeAccount(account, {{"password", password}});
|
||||||
accountReady();
|
if (state != Shared::Availability::offline && acc->getActive()) {
|
||||||
|
acc->connect();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Core::Squawk::readSettings()
|
|
||||||
{
|
|
||||||
QSettings settings;
|
|
||||||
settings.beginGroup("core");
|
|
||||||
int size = settings.beginReadArray("accounts");
|
|
||||||
waitingForAccounts = size;
|
|
||||||
for (int i = 0; i < size; ++i) {
|
|
||||||
settings.setArrayIndex(i);
|
|
||||||
parseAccount(
|
|
||||||
settings.value("login").toString(),
|
|
||||||
settings.value("server").toString(),
|
|
||||||
settings.value("password", "").toString(),
|
|
||||||
settings.value("name").toString(),
|
|
||||||
settings.value("resource").toString(),
|
|
||||||
Shared::Global::fromInt<Shared::AccountPassword>(settings.value("passwordType", static_cast<int>(Shared::AccountPassword::plain)).toInt())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
settings.endArray();
|
|
||||||
settings.endGroup();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Core::Squawk::accountReady()
|
|
||||||
{
|
|
||||||
--waitingForAccounts;
|
|
||||||
|
|
||||||
if (waitingForAccounts == 0) {
|
|
||||||
emit ready();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Core::Squawk::parseAccount(
|
|
||||||
const QString& login,
|
|
||||||
const QString& server,
|
|
||||||
const QString& password,
|
|
||||||
const QString& name,
|
|
||||||
const QString& resource,
|
|
||||||
Shared::AccountPassword passwordType
|
|
||||||
)
|
|
||||||
{
|
|
||||||
switch (passwordType) {
|
|
||||||
case Shared::AccountPassword::plain:
|
|
||||||
addAccount(login, server, password, name, resource, passwordType);
|
|
||||||
accountReady();
|
|
||||||
break;
|
|
||||||
case Shared::AccountPassword::jammed: {
|
|
||||||
SimpleCrypt crypto(passwordHash);
|
|
||||||
QString decrypted = crypto.decryptToString(password);
|
|
||||||
addAccount(login, server, decrypted, name, resource, passwordType);
|
|
||||||
accountReady();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case Shared::AccountPassword::alwaysAsk:
|
|
||||||
addAccount(login, server, QString(), name, resource, passwordType);
|
|
||||||
emit requestPassword(name);
|
|
||||||
break;
|
|
||||||
case Shared::AccountPassword::kwallet: {
|
|
||||||
addAccount(login, server, QString(), name, resource, passwordType);
|
|
||||||
#ifdef WITH_KWALLET
|
|
||||||
if (kwallet.supportState() == PSE::KWallet::success) {
|
|
||||||
kwallet.requestReadPassword(name);
|
|
||||||
} else {
|
|
||||||
#endif
|
|
||||||
emit requestPassword(name);
|
|
||||||
#ifdef WITH_KWALLET
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Core::Squawk::onWalletRejectPassword(const QString& login)
|
|
||||||
{
|
|
||||||
emit requestPassword(login);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Core::Squawk::onWalletResponsePassword(const QString& login, const QString& password)
|
|
||||||
{
|
|
||||||
AccountsMap::const_iterator itr = amap.find(login);
|
|
||||||
if (itr == amap.end()) {
|
|
||||||
qDebug() << "An attempt to set password to non existing account" << login << ", skipping";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
itr->second->setPassword(password);
|
|
||||||
emit changeAccount(login, {{"password", password}});
|
|
||||||
accountReady();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Core::Squawk::onAccountUploadFileError(const QString& jid, const QString id, const QString& errorText)
|
void Core::Squawk::onAccountUploadFileError(const QString& jid, const QString id, const QString& errorText)
|
||||||
@ -768,3 +807,9 @@ void Core::Squawk::onLocalPathInvalid(const QString& path)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Core::Squawk::changeDownloadsPath(const QString& path)
|
||||||
|
{
|
||||||
|
network.moveFilesDirectory(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -82,11 +82,11 @@ signals:
|
|||||||
void fileError(const std::list<Shared::MessageInfo> msgs, const QString& error, bool up);
|
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 fileProgress(const std::list<Shared::MessageInfo> msgs, qreal value, bool up);
|
||||||
void fileDownloadComplete(const std::list<Shared::MessageInfo> msgs, const QString& path);
|
void fileDownloadComplete(const std::list<Shared::MessageInfo> msgs, const QString& path);
|
||||||
void fileUploadComplete(const std::list<Shared::MessageInfo> msgs, const QString& path);
|
void fileUploadComplete(const std::list<Shared::MessageInfo> msgs, const QString& url, const QString& path);
|
||||||
|
|
||||||
void responseVCard(const QString& jid, const Shared::VCard& card);
|
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 changeMessage(const QString& account, const QString& jid, const QString& id, const QMap<QString, QVariant>& data);
|
||||||
void requestPassword(const QString& account);
|
void requestPassword(const QString& account, bool authernticationError);
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void start();
|
void start();
|
||||||
@ -101,6 +101,8 @@ public slots:
|
|||||||
void changeState(Shared::Availability state);
|
void changeState(Shared::Availability state);
|
||||||
|
|
||||||
void sendMessage(const QString& account, const Shared::Message& data);
|
void sendMessage(const QString& account, const Shared::Message& data);
|
||||||
|
void replaceMessage(const QString& account, const QString& originalId, const Shared::Message& data);
|
||||||
|
void resendMessage(const QString& account, const QString& jid, const QString& id);
|
||||||
void requestArchive(const QString& account, const QString& jid, int count, const QString& before);
|
void requestArchive(const QString& account, const QString& jid, int count, const QString& before);
|
||||||
|
|
||||||
void subscribeContact(const QString& account, const QString& jid, const QString& reason);
|
void subscribeContact(const QString& account, const QString& jid, const QString& reason);
|
||||||
@ -122,6 +124,7 @@ public slots:
|
|||||||
void uploadVCard(const QString& account, const Shared::VCard& card);
|
void uploadVCard(const QString& account, const Shared::VCard& card);
|
||||||
void responsePassword(const QString& account, const QString& password);
|
void responsePassword(const QString& account, const QString& password);
|
||||||
void onLocalPathInvalid(const QString& path);
|
void onLocalPathInvalid(const QString& path);
|
||||||
|
void changeDownloadsPath(const QString& path);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
typedef std::deque<Account*> Accounts;
|
typedef std::deque<Account*> Accounts;
|
||||||
@ -131,7 +134,7 @@ private:
|
|||||||
AccountsMap amap;
|
AccountsMap amap;
|
||||||
Shared::Availability state;
|
Shared::Availability state;
|
||||||
NetworkAccess network;
|
NetworkAccess network;
|
||||||
uint8_t waitingForAccounts;
|
bool isInitialized;
|
||||||
|
|
||||||
#ifdef WITH_KWALLET
|
#ifdef WITH_KWALLET
|
||||||
PSE::KWallet kwallet;
|
PSE::KWallet kwallet;
|
||||||
@ -144,6 +147,7 @@ private slots:
|
|||||||
const QString& password,
|
const QString& password,
|
||||||
const QString& name,
|
const QString& name,
|
||||||
const QString& resource,
|
const QString& resource,
|
||||||
|
bool active,
|
||||||
Shared::AccountPassword passwordType
|
Shared::AccountPassword passwordType
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -168,22 +172,22 @@ private slots:
|
|||||||
void onAccountChangeRoomPresence(const QString& jid, const QString& nick, const QMap<QString, QVariant>& data);
|
void onAccountChangeRoomPresence(const QString& jid, const QString& nick, const QMap<QString, QVariant>& data);
|
||||||
void onAccountRemoveRoomPresence(const QString& jid, const QString& nick);
|
void onAccountRemoveRoomPresence(const QString& jid, const QString& nick);
|
||||||
void onAccountChangeMessage(const QString& jid, const QString& id, const QMap<QString, QVariant>& data);
|
void onAccountChangeMessage(const QString& jid, const QString& id, const QMap<QString, QVariant>& data);
|
||||||
|
void onAccountNeedPassword();
|
||||||
|
|
||||||
void onAccountUploadFileError(const QString& jid, const QString id, const QString& errorText);
|
void onAccountUploadFileError(const QString& jid, const QString id, const QString& errorText);
|
||||||
|
|
||||||
void onWalletOpened(bool success);
|
void onWalletOpened(bool success);
|
||||||
void onWalletResponsePassword(const QString& login, const QString& password);
|
|
||||||
void onWalletRejectPassword(const QString& login);
|
void onWalletRejectPassword(const QString& login);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void readSettings();
|
void readSettings();
|
||||||
void accountReady();
|
|
||||||
void parseAccount(
|
void parseAccount(
|
||||||
const QString& login,
|
const QString& login,
|
||||||
const QString& server,
|
const QString& server,
|
||||||
const QString& password,
|
const QString& password,
|
||||||
const QString& name,
|
const QString& name,
|
||||||
const QString& resource,
|
const QString& resource,
|
||||||
|
bool active,
|
||||||
Shared::AccountPassword passwordType
|
Shared::AccountPassword passwordType
|
||||||
);
|
);
|
||||||
|
|
||||||
|
8
core/storage/CMakeLists.txt
Normal file
8
core/storage/CMakeLists.txt
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
target_sources(squawk PRIVATE
|
||||||
|
archive.cpp
|
||||||
|
archive.h
|
||||||
|
storage.cpp
|
||||||
|
storage.h
|
||||||
|
urlstorage.cpp
|
||||||
|
urlstorage.h
|
||||||
|
)
|
@ -58,13 +58,21 @@ void Core::Archive::open(const QString& account)
|
|||||||
}
|
}
|
||||||
|
|
||||||
mdb_env_set_maxdbs(environment, 5);
|
mdb_env_set_maxdbs(environment, 5);
|
||||||
mdb_env_set_mapsize(environment, 512UL * 1024UL * 1024UL);
|
mdb_env_set_mapsize(environment,
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
// On Windows, the file is immediately allocated.
|
||||||
|
// So we have to limit the size.
|
||||||
|
80UL * 1024UL * 1024UL
|
||||||
|
#else
|
||||||
|
512UL * 1024UL * 1024UL
|
||||||
|
#endif
|
||||||
|
);
|
||||||
mdb_env_open(environment, path.toStdString().c_str(), 0, 0664);
|
mdb_env_open(environment, path.toStdString().c_str(), 0, 0664);
|
||||||
|
|
||||||
MDB_txn *txn;
|
MDB_txn *txn;
|
||||||
mdb_txn_begin(environment, NULL, 0, &txn);
|
mdb_txn_begin(environment, NULL, 0, &txn);
|
||||||
mdb_dbi_open(txn, "main", MDB_CREATE, &main);
|
mdb_dbi_open(txn, "main", MDB_CREATE, &main);
|
||||||
mdb_dbi_open(txn, "order", MDB_CREATE | MDB_INTEGERKEY, &order);
|
mdb_dbi_open(txn, "order", MDB_CREATE | MDB_INTEGERKEY | MDB_INTEGERDUP | MDB_DUPSORT, &order);
|
||||||
mdb_dbi_open(txn, "stats", MDB_CREATE, &stats);
|
mdb_dbi_open(txn, "stats", MDB_CREATE, &stats);
|
||||||
mdb_dbi_open(txn, "avatars", MDB_CREATE, &avatars);
|
mdb_dbi_open(txn, "avatars", MDB_CREATE, &avatars);
|
||||||
mdb_dbi_open(txn, "sid", MDB_CREATE, &sid);
|
mdb_dbi_open(txn, "sid", MDB_CREATE, &sid);
|
||||||
@ -115,6 +123,7 @@ bool Core::Archive::addElement(const Shared::Message& message)
|
|||||||
if (!opened) {
|
if (!opened) {
|
||||||
throw Closed("addElement", jid.toStdString());
|
throw Closed("addElement", jid.toStdString());
|
||||||
}
|
}
|
||||||
|
qDebug() << "Adding message with id " << message.getId();
|
||||||
QByteArray ba;
|
QByteArray ba;
|
||||||
QDataStream ds(&ba, QIODevice::WriteOnly);
|
QDataStream ds(&ba, QIODevice::WriteOnly);
|
||||||
message.serialize(ds);
|
message.serialize(ds);
|
||||||
@ -308,8 +317,9 @@ void Core::Archive::changeMessage(const QString& id, const QMap<QString, QVarian
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (msg.getStanzaId().size() > 0 && (idChange || !hadStanzaId)) {
|
QString qsid = msg.getStanzaId();
|
||||||
const std::string& szid = msg.getStanzaId().toStdString();
|
if (qsid.size() > 0 && (idChange || !hadStanzaId)) {
|
||||||
|
std::string szid = qsid.toStdString();
|
||||||
|
|
||||||
lmdbData.mv_size = szid.size();
|
lmdbData.mv_size = szid.size();
|
||||||
lmdbData.mv_data = (char*)szid.c_str();
|
lmdbData.mv_data = (char*)szid.c_str();
|
@ -25,7 +25,7 @@
|
|||||||
#include <QMimeType>
|
#include <QMimeType>
|
||||||
|
|
||||||
#include "shared/message.h"
|
#include "shared/message.h"
|
||||||
#include "exception.h"
|
#include "shared/exception.h"
|
||||||
#include <lmdb.h>
|
#include <lmdb.h>
|
||||||
#include <list>
|
#include <list>
|
||||||
|
|
12
external/simpleCrypt/CMakeLists.txt
vendored
12
external/simpleCrypt/CMakeLists.txt
vendored
@ -1,16 +1,10 @@
|
|||||||
cmake_minimum_required(VERSION 3.0)
|
cmake_minimum_required(VERSION 3.0)
|
||||||
project(simplecrypt)
|
project(simplecrypt LANGUAGES CXX)
|
||||||
|
|
||||||
set(CMAKE_AUTOMOC ON)
|
set(CMAKE_AUTOMOC ON)
|
||||||
|
|
||||||
find_package(Qt5Core CONFIG REQUIRED)
|
find_package(Qt5 COMPONENTS Core REQUIRED)
|
||||||
|
|
||||||
set(simplecrypt_SRC
|
add_library(simpleCrypt STATIC simplecrypt.cpp simplecrypt.h)
|
||||||
simplecrypt.cpp
|
|
||||||
)
|
|
||||||
|
|
||||||
# Tell CMake to create the helloworld executable
|
|
||||||
add_library(simpleCrypt STATIC ${simplecrypt_SRC})
|
|
||||||
|
|
||||||
# Use the Widgets module from Qt 5.
|
|
||||||
target_link_libraries(simpleCrypt Qt5::Core)
|
target_link_libraries(simpleCrypt Qt5::Core)
|
||||||
|
162
main.cpp
162
main.cpp
@ -1,162 +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/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "ui/squawk.h"
|
|
||||||
#include "core/squawk.h"
|
|
||||||
#include "signalcatcher.h"
|
|
||||||
#include "shared/global.h"
|
|
||||||
#include "shared/messageinfo.h"
|
|
||||||
#include <QtWidgets/QApplication>
|
|
||||||
#include <QtCore/QThread>
|
|
||||||
#include <QtCore/QObject>
|
|
||||||
#include <QSettings>
|
|
||||||
#include <QTranslator>
|
|
||||||
#include <QLibraryInfo>
|
|
||||||
#include <QStandardPaths>
|
|
||||||
|
|
||||||
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");
|
|
||||||
|
|
||||||
QApplication app(argc, argv);
|
|
||||||
SignalCatcher sc(&app);
|
|
||||||
|
|
||||||
QApplication::setApplicationName("squawk");
|
|
||||||
QApplication::setApplicationDisplayName("Squawk");
|
|
||||||
QApplication::setApplicationVersion("0.1.5");
|
|
||||||
|
|
||||||
QTranslator qtTranslator;
|
|
||||||
qtTranslator.load("qt_" + QLocale::system().name(), QLibraryInfo::location(QLibraryInfo::TranslationsPath));
|
|
||||||
app.installTranslator(&qtTranslator);
|
|
||||||
|
|
||||||
QTranslator myappTranslator;
|
|
||||||
QStringList shares = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation);
|
|
||||||
bool found = false;
|
|
||||||
for (QString share : shares) {
|
|
||||||
found = myappTranslator.load(QLocale(), QLatin1String("squawk"), ".", share + "/l10n");
|
|
||||||
if (found) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!found) {
|
|
||||||
myappTranslator.load(QLocale(), QLatin1String("squawk"), ".", QCoreApplication::applicationDirPath());
|
|
||||||
}
|
|
||||||
|
|
||||||
app.installTranslator(&myappTranslator);
|
|
||||||
|
|
||||||
QIcon icon;
|
|
||||||
icon.addFile(":images/logo.svg", QSize(16, 16));
|
|
||||||
icon.addFile(":images/logo.svg", QSize(24, 24));
|
|
||||||
icon.addFile(":images/logo.svg", QSize(32, 32));
|
|
||||||
icon.addFile(":images/logo.svg", QSize(48, 48));
|
|
||||||
icon.addFile(":images/logo.svg", QSize(64, 64));
|
|
||||||
icon.addFile(":images/logo.svg", QSize(96, 96));
|
|
||||||
icon.addFile(":images/logo.svg", QSize(128, 128));
|
|
||||||
icon.addFile(":images/logo.svg", QSize(256, 256));
|
|
||||||
icon.addFile(":images/logo.svg", QSize(512, 512));
|
|
||||||
QApplication::setWindowIcon(icon);
|
|
||||||
|
|
||||||
new Shared::Global(); //translates enums
|
|
||||||
|
|
||||||
Squawk w;
|
|
||||||
w.show();
|
|
||||||
|
|
||||||
Core::Squawk* squawk = new Core::Squawk();
|
|
||||||
QThread* coreThread = new QThread();
|
|
||||||
squawk->moveToThread(coreThread);
|
|
||||||
|
|
||||||
QObject::connect(coreThread, &QThread::started, squawk, &Core::Squawk::start);
|
|
||||||
QObject::connect(&app, &QApplication::aboutToQuit, squawk, &Core::Squawk::stop);
|
|
||||||
QObject::connect(&app, &QApplication::aboutToQuit, &w, &QMainWindow::close);
|
|
||||||
QObject::connect(squawk, &Core::Squawk::quit, coreThread, &QThread::quit);
|
|
||||||
QObject::connect(coreThread, &QThread::finished, squawk, &Core::Squawk::deleteLater);
|
|
||||||
|
|
||||||
QObject::connect(&w, &Squawk::newAccountRequest, squawk, &Core::Squawk::newAccountRequest);
|
|
||||||
QObject::connect(&w, &Squawk::modifyAccountRequest, squawk, &Core::Squawk::modifyAccountRequest);
|
|
||||||
QObject::connect(&w, &Squawk::removeAccountRequest, squawk, &Core::Squawk::removeAccountRequest);
|
|
||||||
QObject::connect(&w, &Squawk::connectAccount, squawk, &Core::Squawk::connectAccount);
|
|
||||||
QObject::connect(&w, &Squawk::disconnectAccount, squawk, &Core::Squawk::disconnectAccount);
|
|
||||||
QObject::connect(&w, &Squawk::changeState, squawk, &Core::Squawk::changeState);
|
|
||||||
QObject::connect(&w, &Squawk::sendMessage, squawk,&Core::Squawk::sendMessage);
|
|
||||||
QObject::connect(&w, &Squawk::requestArchive, squawk, &Core::Squawk::requestArchive);
|
|
||||||
QObject::connect(&w, &Squawk::subscribeContact, squawk, &Core::Squawk::subscribeContact);
|
|
||||||
QObject::connect(&w, &Squawk::unsubscribeContact, squawk, &Core::Squawk::unsubscribeContact);
|
|
||||||
QObject::connect(&w, &Squawk::addContactRequest, squawk, &Core::Squawk::addContactRequest);
|
|
||||||
QObject::connect(&w, &Squawk::removeContactRequest, squawk, &Core::Squawk::removeContactRequest);
|
|
||||||
QObject::connect(&w, &Squawk::setRoomJoined, squawk, &Core::Squawk::setRoomJoined);
|
|
||||||
QObject::connect(&w, &Squawk::setRoomAutoJoin, squawk, &Core::Squawk::setRoomAutoJoin);
|
|
||||||
QObject::connect(&w, &Squawk::removeRoomRequest, squawk, &Core::Squawk::removeRoomRequest);
|
|
||||||
QObject::connect(&w, &Squawk::addRoomRequest, squawk, &Core::Squawk::addRoomRequest);
|
|
||||||
QObject::connect(&w, &Squawk::fileDownloadRequest, squawk, &Core::Squawk::fileDownloadRequest);
|
|
||||||
QObject::connect(&w, &Squawk::addContactToGroupRequest, squawk, &Core::Squawk::addContactToGroupRequest);
|
|
||||||
QObject::connect(&w, &Squawk::removeContactFromGroupRequest, squawk, &Core::Squawk::removeContactFromGroupRequest);
|
|
||||||
QObject::connect(&w, &Squawk::renameContactRequest, squawk, &Core::Squawk::renameContactRequest);
|
|
||||||
QObject::connect(&w, &Squawk::requestVCard, squawk, &Core::Squawk::requestVCard);
|
|
||||||
QObject::connect(&w, &Squawk::uploadVCard, squawk, &Core::Squawk::uploadVCard);
|
|
||||||
QObject::connect(&w, &Squawk::responsePassword, squawk, &Core::Squawk::responsePassword);
|
|
||||||
QObject::connect(&w, &Squawk::localPathInvalid, squawk, &Core::Squawk::onLocalPathInvalid);
|
|
||||||
|
|
||||||
QObject::connect(squawk, &Core::Squawk::newAccount, &w, &Squawk::newAccount);
|
|
||||||
QObject::connect(squawk, &Core::Squawk::addContact, &w, &Squawk::addContact);
|
|
||||||
QObject::connect(squawk, &Core::Squawk::changeAccount, &w, &Squawk::changeAccount);
|
|
||||||
QObject::connect(squawk, &Core::Squawk::removeAccount, &w, &Squawk::removeAccount);
|
|
||||||
QObject::connect(squawk, &Core::Squawk::addGroup, &w, &Squawk::addGroup);
|
|
||||||
QObject::connect(squawk, &Core::Squawk::removeGroup, &w, &Squawk::removeGroup);
|
|
||||||
QObject::connect(squawk, qOverload<const QString&, const QString&>(&Core::Squawk::removeContact),
|
|
||||||
&w, qOverload<const QString&, const QString&>(&Squawk::removeContact));
|
|
||||||
QObject::connect(squawk, qOverload<const QString&, const QString&, const QString&>(&Core::Squawk::removeContact),
|
|
||||||
&w, qOverload<const QString&, const QString&, const QString&>(&Squawk::removeContact));
|
|
||||||
QObject::connect(squawk, &Core::Squawk::changeContact, &w, &Squawk::changeContact);
|
|
||||||
QObject::connect(squawk, &Core::Squawk::addPresence, &w, &Squawk::addPresence);
|
|
||||||
QObject::connect(squawk, &Core::Squawk::removePresence, &w, &Squawk::removePresence);
|
|
||||||
QObject::connect(squawk, &Core::Squawk::stateChanged, &w, &Squawk::stateChanged);
|
|
||||||
QObject::connect(squawk, &Core::Squawk::accountMessage, &w, &Squawk::accountMessage);
|
|
||||||
QObject::connect(squawk, &Core::Squawk::changeMessage, &w, &Squawk::changeMessage);
|
|
||||||
QObject::connect(squawk, &Core::Squawk::responseArchive, &w, &Squawk::responseArchive);
|
|
||||||
QObject::connect(squawk, &Core::Squawk::addRoom, &w, &Squawk::addRoom);
|
|
||||||
QObject::connect(squawk, &Core::Squawk::changeRoom, &w, &Squawk::changeRoom);
|
|
||||||
QObject::connect(squawk, &Core::Squawk::removeRoom, &w, &Squawk::removeRoom);
|
|
||||||
QObject::connect(squawk, &Core::Squawk::addRoomParticipant, &w, &Squawk::addRoomParticipant);
|
|
||||||
QObject::connect(squawk, &Core::Squawk::changeRoomParticipant, &w, &Squawk::changeRoomParticipant);
|
|
||||||
QObject::connect(squawk, &Core::Squawk::removeRoomParticipant, &w, &Squawk::removeRoomParticipant);
|
|
||||||
QObject::connect(squawk, &Core::Squawk::fileDownloadComplete, &w, &Squawk::fileDownloadComplete);
|
|
||||||
QObject::connect(squawk, &Core::Squawk::fileUploadComplete, &w, &Squawk::fileUploadComplete);
|
|
||||||
QObject::connect(squawk, &Core::Squawk::fileProgress, &w, &Squawk::fileProgress);
|
|
||||||
QObject::connect(squawk, &Core::Squawk::fileError, &w, &Squawk::fileError);
|
|
||||||
QObject::connect(squawk, &Core::Squawk::responseVCard, &w, &Squawk::responseVCard);
|
|
||||||
QObject::connect(squawk, &Core::Squawk::requestPassword, &w, &Squawk::requestPassword);
|
|
||||||
QObject::connect(squawk, &Core::Squawk::ready, &w, &Squawk::readSettings);
|
|
||||||
|
|
||||||
coreThread->start();
|
|
||||||
|
|
||||||
int result = app.exec();
|
|
||||||
|
|
||||||
w.writeSettings();
|
|
||||||
coreThread->wait(500); //TODO hate doing that but settings for some reason don't get saved to the disk
|
|
||||||
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
7
main/CMakeLists.txt
Normal file
7
main/CMakeLists.txt
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
target_sources(squawk PRIVATE
|
||||||
|
main.cpp
|
||||||
|
application.cpp
|
||||||
|
application.h
|
||||||
|
dialogqueue.cpp
|
||||||
|
dialogqueue.h
|
||||||
|
)
|
566
main/application.cpp
Normal file
566
main/application.cpp
Normal file
@ -0,0 +1,566 @@
|
|||||||
|
// 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 "application.h"
|
||||||
|
|
||||||
|
Application::Application(Core::Squawk* p_core):
|
||||||
|
QObject(),
|
||||||
|
availability(Shared::Availability::offline),
|
||||||
|
core(p_core),
|
||||||
|
squawk(nullptr),
|
||||||
|
notifications("org.freedesktop.Notifications", "/org/freedesktop/Notifications", "org.freedesktop.Notifications"),
|
||||||
|
roster(),
|
||||||
|
conversations(),
|
||||||
|
dialogueQueue(roster),
|
||||||
|
nowQuitting(false),
|
||||||
|
destroyingSquawk(false),
|
||||||
|
storage()
|
||||||
|
{
|
||||||
|
connect(&roster, &Models::Roster::unnoticedMessage, this, &Application::notify);
|
||||||
|
connect(&roster, &Models::Roster::unreadMessagesCountChanged, this, &Application::unreadMessagesCountChanged);
|
||||||
|
|
||||||
|
|
||||||
|
//connecting myself to the backed
|
||||||
|
connect(this, &Application::changeState, core, &Core::Squawk::changeState);
|
||||||
|
connect(this, &Application::setRoomJoined, core, &Core::Squawk::setRoomJoined);
|
||||||
|
connect(this, &Application::setRoomAutoJoin, core, &Core::Squawk::setRoomAutoJoin);
|
||||||
|
connect(this, &Application::subscribeContact, core, &Core::Squawk::subscribeContact);
|
||||||
|
connect(this, &Application::unsubscribeContact, core, &Core::Squawk::unsubscribeContact);
|
||||||
|
connect(this, &Application::replaceMessage, core, &Core::Squawk::replaceMessage);
|
||||||
|
connect(this, &Application::sendMessage, core, &Core::Squawk::sendMessage);
|
||||||
|
connect(this, &Application::resendMessage, core, &Core::Squawk::resendMessage);
|
||||||
|
connect(&roster, &Models::Roster::requestArchive,
|
||||||
|
std::bind(&Core::Squawk::requestArchive, core, std::placeholders::_1, std::placeholders::_2, 20, std::placeholders::_3));
|
||||||
|
|
||||||
|
connect(&dialogueQueue, &DialogQueue::modifyAccountRequest, core, &Core::Squawk::modifyAccountRequest);
|
||||||
|
connect(&dialogueQueue, &DialogQueue::responsePassword, core, &Core::Squawk::responsePassword);
|
||||||
|
connect(&dialogueQueue, &DialogQueue::disconnectAccount, core, &Core::Squawk::disconnectAccount);
|
||||||
|
|
||||||
|
connect(&roster, &Models::Roster::fileDownloadRequest, core, &Core::Squawk::fileDownloadRequest);
|
||||||
|
connect(&roster, &Models::Roster::localPathInvalid, core, &Core::Squawk::onLocalPathInvalid);
|
||||||
|
|
||||||
|
|
||||||
|
//coonecting backend to myself
|
||||||
|
connect(core, &Core::Squawk::stateChanged, this, &Application::stateChanged);
|
||||||
|
|
||||||
|
connect(core, &Core::Squawk::accountMessage, &roster, &Models::Roster::addMessage);
|
||||||
|
connect(core, &Core::Squawk::responseArchive, &roster, &Models::Roster::responseArchive);
|
||||||
|
connect(core, &Core::Squawk::changeMessage, &roster, &Models::Roster::changeMessage);
|
||||||
|
|
||||||
|
connect(core, &Core::Squawk::newAccount, &roster, &Models::Roster::addAccount);
|
||||||
|
connect(core, &Core::Squawk::changeAccount, this, &Application::changeAccount);
|
||||||
|
connect(core, &Core::Squawk::removeAccount, this, &Application::removeAccount);
|
||||||
|
|
||||||
|
connect(core, &Core::Squawk::addContact, this, &Application::addContact);
|
||||||
|
connect(core, &Core::Squawk::addGroup, this, &Application::addGroup);
|
||||||
|
connect(core, &Core::Squawk::removeGroup, &roster, &Models::Roster::removeGroup);
|
||||||
|
connect(core, qOverload<const QString&, const QString&>(&Core::Squawk::removeContact),
|
||||||
|
&roster, qOverload<const QString&, const QString&>(&Models::Roster::removeContact));
|
||||||
|
connect(core, qOverload<const QString&, const QString&, const QString&>(&Core::Squawk::removeContact),
|
||||||
|
&roster, qOverload<const QString&, const QString&, const QString&>(&Models::Roster::removeContact));
|
||||||
|
connect(core, &Core::Squawk::changeContact, &roster, &Models::Roster::changeContact);
|
||||||
|
connect(core, &Core::Squawk::addPresence, &roster, &Models::Roster::addPresence);
|
||||||
|
connect(core, &Core::Squawk::removePresence, &roster, &Models::Roster::removePresence);
|
||||||
|
|
||||||
|
connect(core, &Core::Squawk::addRoom, &roster, &Models::Roster::addRoom);
|
||||||
|
connect(core, &Core::Squawk::changeRoom, &roster, &Models::Roster::changeRoom);
|
||||||
|
connect(core, &Core::Squawk::removeRoom, &roster, &Models::Roster::removeRoom);
|
||||||
|
connect(core, &Core::Squawk::addRoomParticipant, &roster, &Models::Roster::addRoomParticipant);
|
||||||
|
connect(core, &Core::Squawk::changeRoomParticipant, &roster, &Models::Roster::changeRoomParticipant);
|
||||||
|
connect(core, &Core::Squawk::removeRoomParticipant, &roster, &Models::Roster::removeRoomParticipant);
|
||||||
|
|
||||||
|
|
||||||
|
connect(core, &Core::Squawk::fileDownloadComplete, std::bind(&Models::Roster::fileComplete, &roster, std::placeholders::_1, false));
|
||||||
|
connect(core, &Core::Squawk::fileUploadComplete, std::bind(&Models::Roster::fileComplete, &roster, std::placeholders::_1, true));
|
||||||
|
connect(core, &Core::Squawk::fileProgress, &roster, &Models::Roster::fileProgress);
|
||||||
|
connect(core, &Core::Squawk::fileError, &roster, &Models::Roster::fileError);
|
||||||
|
|
||||||
|
connect(core, &Core::Squawk::requestPassword, this, &Application::requestPassword);
|
||||||
|
connect(core, &Core::Squawk::ready, this, &Application::readSettings);
|
||||||
|
|
||||||
|
QDBusConnection sys = QDBusConnection::sessionBus();
|
||||||
|
sys.connect(
|
||||||
|
"org.freedesktop.Notifications",
|
||||||
|
"/org/freedesktop/Notifications",
|
||||||
|
"org.freedesktop.Notifications",
|
||||||
|
"NotificationClosed",
|
||||||
|
this,
|
||||||
|
SLOT(onNotificationClosed(quint32, quint32))
|
||||||
|
);
|
||||||
|
sys.connect(
|
||||||
|
"org.freedesktop.Notifications",
|
||||||
|
"/org/freedesktop/Notifications",
|
||||||
|
"org.freedesktop.Notifications",
|
||||||
|
"ActionInvoked",
|
||||||
|
this,
|
||||||
|
SLOT(onNotificationInvoked(quint32, const QString&))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Application::~Application() {}
|
||||||
|
|
||||||
|
void Application::quit()
|
||||||
|
{
|
||||||
|
if (!nowQuitting) {
|
||||||
|
nowQuitting = true;
|
||||||
|
emit quitting();
|
||||||
|
|
||||||
|
writeSettings();
|
||||||
|
unreadMessagesCountChanged(0); //this notification persist in the desktop, for now I'll zero it on quit not to confuse people
|
||||||
|
for (Conversations::const_iterator itr = conversations.begin(), end = conversations.end(); itr != end; ++itr) {
|
||||||
|
disconnect(itr->second, &Conversation::destroyed, this, &Application::onConversationClosed);
|
||||||
|
itr->second->close();
|
||||||
|
}
|
||||||
|
conversations.clear();
|
||||||
|
dialogueQueue.quit();
|
||||||
|
|
||||||
|
if (squawk != nullptr) {
|
||||||
|
squawk->close();
|
||||||
|
}
|
||||||
|
if (!destroyingSquawk) {
|
||||||
|
checkForTheLastWindow();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::checkForTheLastWindow()
|
||||||
|
{
|
||||||
|
if (QApplication::topLevelWidgets().size() > 0) {
|
||||||
|
emit readyToQuit();
|
||||||
|
} else {
|
||||||
|
connect(qApp, &QApplication::lastWindowClosed, this, &Application::readyToQuit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::createMainWindow()
|
||||||
|
{
|
||||||
|
if (squawk == nullptr) {
|
||||||
|
squawk = new Squawk(roster);
|
||||||
|
|
||||||
|
connect(squawk, &Squawk::notify, this, &Application::notify);
|
||||||
|
connect(squawk, &Squawk::changeSubscription, this, &Application::changeSubscription);
|
||||||
|
connect(squawk, &Squawk::openedConversation, this, &Application::onSquawkOpenedConversation);
|
||||||
|
connect(squawk, &Squawk::openConversation, this, &Application::openConversation);
|
||||||
|
connect(squawk, &Squawk::changeState, this, &Application::setState);
|
||||||
|
connect(squawk, &Squawk::closing, this, &Application::onSquawkClosing);
|
||||||
|
|
||||||
|
connect(squawk, &Squawk::modifyAccountRequest, core, &Core::Squawk::modifyAccountRequest);
|
||||||
|
connect(squawk, &Squawk::newAccountRequest, core, &Core::Squawk::newAccountRequest);
|
||||||
|
connect(squawk, &Squawk::removeAccountRequest, core, &Core::Squawk::removeAccountRequest);
|
||||||
|
connect(squawk, &Squawk::connectAccount, core, &Core::Squawk::connectAccount);
|
||||||
|
connect(squawk, &Squawk::disconnectAccount, core, &Core::Squawk::disconnectAccount);
|
||||||
|
|
||||||
|
connect(squawk, &Squawk::addContactRequest, core, &Core::Squawk::addContactRequest);
|
||||||
|
connect(squawk, &Squawk::removeContactRequest, core, &Core::Squawk::removeContactRequest);
|
||||||
|
connect(squawk, &Squawk::removeRoomRequest, core, &Core::Squawk::removeRoomRequest);
|
||||||
|
connect(squawk, &Squawk::addRoomRequest, core, &Core::Squawk::addRoomRequest);
|
||||||
|
connect(squawk, &Squawk::addContactToGroupRequest, core, &Core::Squawk::addContactToGroupRequest);
|
||||||
|
connect(squawk, &Squawk::removeContactFromGroupRequest, core, &Core::Squawk::removeContactFromGroupRequest);
|
||||||
|
connect(squawk, &Squawk::renameContactRequest, core, &Core::Squawk::renameContactRequest);
|
||||||
|
connect(squawk, &Squawk::requestVCard, core, &Core::Squawk::requestVCard);
|
||||||
|
connect(squawk, &Squawk::uploadVCard, core, &Core::Squawk::uploadVCard);
|
||||||
|
connect(squawk, &Squawk::changeDownloadsPath, core, &Core::Squawk::changeDownloadsPath);
|
||||||
|
|
||||||
|
connect(core, &Core::Squawk::responseVCard, squawk, &Squawk::responseVCard);
|
||||||
|
|
||||||
|
dialogueQueue.setParentWidnow(squawk);
|
||||||
|
squawk->stateChanged(availability);
|
||||||
|
squawk->show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::onSquawkClosing()
|
||||||
|
{
|
||||||
|
dialogueQueue.setParentWidnow(nullptr);
|
||||||
|
|
||||||
|
if (!nowQuitting) {
|
||||||
|
disconnect(core, &Core::Squawk::responseVCard, squawk, &Squawk::responseVCard);
|
||||||
|
}
|
||||||
|
|
||||||
|
destroyingSquawk = true;
|
||||||
|
squawk->deleteLater();
|
||||||
|
squawk = nullptr;
|
||||||
|
|
||||||
|
//for now
|
||||||
|
quit();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::onSquawkDestroyed() {
|
||||||
|
destroyingSquawk = false;
|
||||||
|
if (nowQuitting) {
|
||||||
|
checkForTheLastWindow();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Application::notify(const QString& account, const Shared::Message& msg)
|
||||||
|
{
|
||||||
|
QString jid = msg.getPenPalJid();
|
||||||
|
QString name = QString(roster.getContactName(account, jid));
|
||||||
|
QString path = QString(roster.getContactIconPath(account, jid, msg.getPenPalResource()));
|
||||||
|
QVariantList args;
|
||||||
|
args << QString();
|
||||||
|
|
||||||
|
uint32_t notificationId = qHash(msg.getId());
|
||||||
|
args << notificationId;
|
||||||
|
if (path.size() > 0) {
|
||||||
|
args << path;
|
||||||
|
} else {
|
||||||
|
args << QString("mail-message"); //TODO should here better be unknown user icon?
|
||||||
|
}
|
||||||
|
if (msg.getType() == Shared::Message::groupChat) {
|
||||||
|
args << msg.getFromResource() + tr(" from ") + name;
|
||||||
|
} else {
|
||||||
|
args << name;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString body(msg.getBody());
|
||||||
|
QString oob(msg.getOutOfBandUrl());
|
||||||
|
if (body == oob) {
|
||||||
|
body = tr("Attached file");
|
||||||
|
}
|
||||||
|
|
||||||
|
args << body;
|
||||||
|
args << QStringList({
|
||||||
|
"markAsRead", tr("Mark as Read"),
|
||||||
|
"openConversation", tr("Open conversation")
|
||||||
|
});
|
||||||
|
args << QVariantMap({
|
||||||
|
{"desktop-entry", qApp->desktopFileName()},
|
||||||
|
{"category", QString("message")},
|
||||||
|
{"urgency", 1},
|
||||||
|
// {"sound-file", "/path/to/macaw/squawk"},
|
||||||
|
{"sound-name", QString("message-new-instant")}
|
||||||
|
});
|
||||||
|
args << -1;
|
||||||
|
notifications.callWithArgumentList(QDBus::AutoDetect, "Notify", args);
|
||||||
|
|
||||||
|
storage.insert(std::make_pair(notificationId, std::make_pair(Models::Roster::ElId(account, name), msg.getId())));
|
||||||
|
|
||||||
|
if (squawk != nullptr) {
|
||||||
|
QApplication::alert(squawk);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::onNotificationClosed(quint32 id, quint32 reason)
|
||||||
|
{
|
||||||
|
Notifications::const_iterator itr = storage.find(id);
|
||||||
|
if (itr != storage.end()) {
|
||||||
|
if (reason == 2) { //dissmissed by user (https://specifications.freedesktop.org/notification-spec/latest/ar01s09.html)
|
||||||
|
//TODO may ba also mark as read?
|
||||||
|
}
|
||||||
|
if (reason != 1) { //just expired, can be activated again from history, so no removing for now
|
||||||
|
storage.erase(id);
|
||||||
|
qDebug() << "Notification" << id << "was closed";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::onNotificationInvoked(quint32 id, const QString& action)
|
||||||
|
{
|
||||||
|
qDebug() << "Notification" << id << action << "request";
|
||||||
|
Notifications::const_iterator itr = storage.find(id);
|
||||||
|
if (itr != storage.end()) {
|
||||||
|
if (action == "markAsRead") {
|
||||||
|
roster.markMessageAsRead(itr->second.first, itr->second.second);
|
||||||
|
} else if (action == "openConversation") {
|
||||||
|
focusConversation(itr->second.first, "", itr->second.second);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::unreadMessagesCountChanged(int count)
|
||||||
|
{
|
||||||
|
QDBusMessage signal = QDBusMessage::createSignal("/", "com.canonical.Unity.LauncherEntry", "Update");
|
||||||
|
signal << qApp->desktopFileName() + QLatin1String(".desktop");
|
||||||
|
signal << QVariantMap ({
|
||||||
|
{"count-visible", count != 0},
|
||||||
|
{"count", count}
|
||||||
|
});
|
||||||
|
QDBusConnection::sessionBus().send(signal);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::focusConversation(const Models::Roster::ElId& id, const QString& resource, const QString& messageId)
|
||||||
|
{
|
||||||
|
if (squawk != nullptr) {
|
||||||
|
if (squawk->currentConversationId() != id) {
|
||||||
|
QModelIndex index = roster.getContactIndex(id.account, id.name, resource);
|
||||||
|
squawk->select(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (squawk->isMinimized()) {
|
||||||
|
squawk->showNormal();
|
||||||
|
} else {
|
||||||
|
squawk->show();
|
||||||
|
}
|
||||||
|
squawk->raise();
|
||||||
|
squawk->activateWindow();
|
||||||
|
} else {
|
||||||
|
openConversation(id, resource);
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO focus messageId;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::setState(Shared::Availability p_availability)
|
||||||
|
{
|
||||||
|
if (availability != p_availability) {
|
||||||
|
availability = p_availability;
|
||||||
|
emit changeState(availability);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::stateChanged(Shared::Availability state)
|
||||||
|
{
|
||||||
|
availability = state;
|
||||||
|
if (squawk != nullptr) {
|
||||||
|
squawk->stateChanged(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::readSettings()
|
||||||
|
{
|
||||||
|
QSettings settings;
|
||||||
|
settings.beginGroup("ui");
|
||||||
|
int avail;
|
||||||
|
if (settings.contains("availability")) {
|
||||||
|
avail = settings.value("availability").toInt();
|
||||||
|
} else {
|
||||||
|
avail = static_cast<int>(Shared::Availability::online);
|
||||||
|
}
|
||||||
|
settings.endGroup();
|
||||||
|
|
||||||
|
setState(Shared::Global::fromInt<Shared::Availability>(avail));
|
||||||
|
createMainWindow();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::writeSettings()
|
||||||
|
{
|
||||||
|
QSettings settings;
|
||||||
|
settings.setValue("availability", static_cast<int>(availability));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::requestPassword(const QString& account, bool authenticationError) {
|
||||||
|
if (authenticationError) {
|
||||||
|
dialogueQueue.addAction(account, DialogQueue::askCredentials);
|
||||||
|
} else {
|
||||||
|
dialogueQueue.addAction(account, DialogQueue::askPassword);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
void Application::onConversationClosed()
|
||||||
|
{
|
||||||
|
Conversation* conv = static_cast<Conversation*>(sender());
|
||||||
|
Models::Roster::ElId id(conv->getAccount(), conv->getJid());
|
||||||
|
Conversations::const_iterator itr = conversations.find(id);
|
||||||
|
if (itr != conversations.end()) {
|
||||||
|
conversations.erase(itr);
|
||||||
|
}
|
||||||
|
if (conv->isMuc) {
|
||||||
|
Room* room = static_cast<Room*>(conv);
|
||||||
|
if (!room->autoJoined()) {
|
||||||
|
emit setRoomJoined(id.account, id.name, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::changeSubscription(const Models::Roster::ElId& id, bool subscribe)
|
||||||
|
{
|
||||||
|
Models::Item::Type type = roster.getContactType(id);
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case Models::Item::contact:
|
||||||
|
if (subscribe) {
|
||||||
|
emit subscribeContact(id.account, id.name, "");
|
||||||
|
} else {
|
||||||
|
emit unsubscribeContact(id.account, id.name, "");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Models::Item::room:
|
||||||
|
setRoomAutoJoin(id.account, id.name, subscribe);
|
||||||
|
if (!isConverstationOpened(id)) {
|
||||||
|
emit setRoomJoined(id.account, id.name, subscribe);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::subscribeConversation(Conversation* conv)
|
||||||
|
{
|
||||||
|
connect(conv, &Conversation::destroyed, this, &Application::onConversationClosed);
|
||||||
|
connect(conv, &Conversation::sendMessage, this, &Application::onConversationMessage);
|
||||||
|
connect(conv, &Conversation::replaceMessage, this, &Application::onConversationReplaceMessage);
|
||||||
|
connect(conv, &Conversation::resendMessage, this, &Application::onConversationResend);
|
||||||
|
connect(conv, &Conversation::notifyableMessage, this, &Application::notify);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::openConversation(const Models::Roster::ElId& id, const QString& resource)
|
||||||
|
{
|
||||||
|
Conversations::const_iterator itr = conversations.find(id);
|
||||||
|
Models::Account* acc = roster.getAccount(id.account);
|
||||||
|
Conversation* conv = nullptr;
|
||||||
|
bool created = false;
|
||||||
|
if (itr != conversations.end()) {
|
||||||
|
conv = itr->second;
|
||||||
|
} else {
|
||||||
|
Models::Element* el = roster.getElement(id);
|
||||||
|
if (el != nullptr) {
|
||||||
|
if (el->type == Models::Item::room) {
|
||||||
|
created = true;
|
||||||
|
Models::Room* room = static_cast<Models::Room*>(el);
|
||||||
|
conv = new Room(acc, room);
|
||||||
|
if (!room->getJoined()) {
|
||||||
|
emit setRoomJoined(id.account, id.name, true);
|
||||||
|
}
|
||||||
|
} else if (el->type == Models::Item::contact) {
|
||||||
|
created = true;
|
||||||
|
conv = new Chat(acc, static_cast<Models::Contact*>(el));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (conv != nullptr) {
|
||||||
|
if (created) {
|
||||||
|
conv->setAttribute(Qt::WA_DeleteOnClose);
|
||||||
|
subscribeConversation(conv);
|
||||||
|
conversations.insert(std::make_pair(id, conv));
|
||||||
|
}
|
||||||
|
|
||||||
|
conv->show();
|
||||||
|
conv->raise();
|
||||||
|
conv->activateWindow();
|
||||||
|
|
||||||
|
if (resource.size() > 0) {
|
||||||
|
conv->setPalResource(resource);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::onConversationMessage(const Shared::Message& msg)
|
||||||
|
{
|
||||||
|
Conversation* conv = static_cast<Conversation*>(sender());
|
||||||
|
QString acc = conv->getAccount();
|
||||||
|
|
||||||
|
roster.addMessage(acc, msg);
|
||||||
|
emit sendMessage(acc, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::onConversationReplaceMessage(const QString& originalId, const Shared::Message& msg)
|
||||||
|
{
|
||||||
|
Conversation* conv = static_cast<Conversation*>(sender());
|
||||||
|
QString acc = conv->getAccount();
|
||||||
|
|
||||||
|
roster.changeMessage(acc, msg.getPenPalJid(), originalId, {
|
||||||
|
{"state", static_cast<uint>(Shared::Message::State::pending)}
|
||||||
|
});
|
||||||
|
emit replaceMessage(acc, originalId, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::onConversationResend(const QString& id)
|
||||||
|
{
|
||||||
|
Conversation* conv = static_cast<Conversation*>(sender());
|
||||||
|
QString acc = conv->getAccount();
|
||||||
|
QString jid = conv->getJid();
|
||||||
|
|
||||||
|
emit resendMessage(acc, jid, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::onSquawkOpenedConversation() {
|
||||||
|
subscribeConversation(squawk->currentConversation);
|
||||||
|
Models::Roster::ElId id = squawk->currentConversationId();
|
||||||
|
|
||||||
|
const Models::Element* el = roster.getElementConst(id);
|
||||||
|
if (el != nullptr && el->isRoom() && !static_cast<const Models::Room*>(el)->getJoined()) {
|
||||||
|
emit setRoomJoined(id.account, id.name, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::removeAccount(const QString& account)
|
||||||
|
{
|
||||||
|
Conversations::const_iterator itr = conversations.begin();
|
||||||
|
while (itr != conversations.end()) {
|
||||||
|
if (itr->first.account == account) {
|
||||||
|
Conversations::const_iterator lItr = itr;
|
||||||
|
++itr;
|
||||||
|
Conversation* conv = lItr->second;
|
||||||
|
disconnect(conv, &Conversation::destroyed, this, &Application::onConversationClosed);
|
||||||
|
conv->close();
|
||||||
|
conversations.erase(lItr);
|
||||||
|
} else {
|
||||||
|
++itr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (squawk != nullptr && squawk->currentConversationId().account == account) {
|
||||||
|
squawk->closeCurrentConversation();
|
||||||
|
}
|
||||||
|
|
||||||
|
roster.removeAccount(account);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::changeAccount(const QString& account, const QMap<QString, QVariant>& data)
|
||||||
|
{
|
||||||
|
for (QMap<QString, QVariant>::const_iterator itr = data.begin(), end = data.end(); itr != end; ++itr) {
|
||||||
|
QString attr = itr.key();
|
||||||
|
roster.updateAccount(account, attr, *itr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::addContact(const QString& account, const QString& jid, const QString& group, const QMap<QString, QVariant>& data)
|
||||||
|
{
|
||||||
|
roster.addContact(account, jid, group, data);
|
||||||
|
|
||||||
|
if (squawk != nullptr) {
|
||||||
|
QSettings settings;
|
||||||
|
settings.beginGroup("ui");
|
||||||
|
settings.beginGroup("roster");
|
||||||
|
settings.beginGroup(account);
|
||||||
|
if (settings.value("expanded", false).toBool()) {
|
||||||
|
QModelIndex ind = roster.getAccountIndex(account);
|
||||||
|
squawk->expand(ind);
|
||||||
|
}
|
||||||
|
settings.endGroup();
|
||||||
|
settings.endGroup();
|
||||||
|
settings.endGroup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::addGroup(const QString& account, const QString& name)
|
||||||
|
{
|
||||||
|
roster.addGroup(account, name);
|
||||||
|
|
||||||
|
if (squawk != nullptr) {
|
||||||
|
QSettings settings;
|
||||||
|
settings.beginGroup("ui");
|
||||||
|
settings.beginGroup("roster");
|
||||||
|
settings.beginGroup(account);
|
||||||
|
if (settings.value("expanded", false).toBool()) {
|
||||||
|
QModelIndex ind = roster.getAccountIndex(account);
|
||||||
|
squawk->expand(ind);
|
||||||
|
if (settings.value(name + "/expanded", false).toBool()) {
|
||||||
|
squawk->expand(roster.getGroupIndex(account, name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
settings.endGroup();
|
||||||
|
settings.endGroup();
|
||||||
|
settings.endGroup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Application::isConverstationOpened(const Models::Roster::ElId& id) const {
|
||||||
|
return (conversations.count(id) > 0) || (squawk != nullptr && squawk->currentConversationId() == id);}
|
118
main/application.h
Normal file
118
main/application.h
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
// 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 APPLICATION_H
|
||||||
|
#define APPLICATION_H
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QDBusInterface>
|
||||||
|
|
||||||
|
#include "dialogqueue.h"
|
||||||
|
#include "core/squawk.h"
|
||||||
|
|
||||||
|
#include "ui/squawk.h"
|
||||||
|
#include "ui/models/roster.h"
|
||||||
|
#include "ui/widgets/conversation.h"
|
||||||
|
|
||||||
|
#include "shared/message.h"
|
||||||
|
#include "shared/enums.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @todo write docs
|
||||||
|
*/
|
||||||
|
class Application : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
Application(Core::Squawk* core);
|
||||||
|
~Application();
|
||||||
|
|
||||||
|
bool isConverstationOpened(const Models::Roster::ElId& id) const;
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void sendMessage(const QString& account, const Shared::Message& data);
|
||||||
|
void replaceMessage(const QString& account, const QString& originalId, const Shared::Message& data);
|
||||||
|
void resendMessage(const QString& account, const QString& jid, const QString& id);
|
||||||
|
|
||||||
|
void changeState(Shared::Availability state);
|
||||||
|
|
||||||
|
void setRoomJoined(const QString& account, const QString& jid, bool joined);
|
||||||
|
void setRoomAutoJoin(const QString& account, const QString& jid, bool joined);
|
||||||
|
void subscribeContact(const QString& account, const QString& jid, const QString& reason);
|
||||||
|
void unsubscribeContact(const QString& account, const QString& jid, const QString& reason);
|
||||||
|
|
||||||
|
void quitting();
|
||||||
|
void readyToQuit();
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void readSettings();
|
||||||
|
void quit();
|
||||||
|
|
||||||
|
protected slots:
|
||||||
|
void notify(const QString& account, const Shared::Message& msg);
|
||||||
|
void unreadMessagesCountChanged(int count);
|
||||||
|
void setState(Shared::Availability availability);
|
||||||
|
|
||||||
|
void changeAccount(const QString& account, const QMap<QString, QVariant>& data);
|
||||||
|
void removeAccount(const QString& account);
|
||||||
|
void openConversation(const Models::Roster::ElId& id, const QString& resource = "");
|
||||||
|
|
||||||
|
void addGroup(const QString& account, const QString& name);
|
||||||
|
void addContact(const QString& account, const QString& jid, const QString& group, const QMap<QString, QVariant>& data);
|
||||||
|
|
||||||
|
void requestPassword(const QString& account, bool authenticationError);
|
||||||
|
|
||||||
|
void writeSettings();
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void onConversationClosed();
|
||||||
|
void changeSubscription(const Models::Roster::ElId& id, bool subscribe);
|
||||||
|
void onSquawkOpenedConversation();
|
||||||
|
void onConversationMessage(const Shared::Message& msg);
|
||||||
|
void onConversationReplaceMessage(const QString& originalId, const Shared::Message& msg);
|
||||||
|
void onConversationResend(const QString& id);
|
||||||
|
void stateChanged(Shared::Availability state);
|
||||||
|
void onSquawkClosing();
|
||||||
|
void onSquawkDestroyed();
|
||||||
|
void onNotificationClosed(quint32 id, quint32 reason);
|
||||||
|
void onNotificationInvoked(quint32 id, const QString& action);
|
||||||
|
|
||||||
|
|
||||||
|
private:
|
||||||
|
void createMainWindow();
|
||||||
|
void subscribeConversation(Conversation* conv);
|
||||||
|
void checkForTheLastWindow();
|
||||||
|
void focusConversation(const Models::Roster::ElId& id, const QString& resource = "", const QString& messageId = "");
|
||||||
|
|
||||||
|
private:
|
||||||
|
typedef std::map<Models::Roster::ElId, Conversation*> Conversations;
|
||||||
|
typedef std::map<uint32_t, std::pair<Models::Roster::ElId, QString>> Notifications;
|
||||||
|
|
||||||
|
Shared::Availability availability;
|
||||||
|
Core::Squawk* core;
|
||||||
|
Squawk* squawk;
|
||||||
|
QDBusInterface notifications;
|
||||||
|
Models::Roster roster;
|
||||||
|
Conversations conversations;
|
||||||
|
DialogQueue dialogueQueue;
|
||||||
|
bool nowQuitting;
|
||||||
|
bool destroyingSquawk;
|
||||||
|
Notifications storage;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // APPLICATION_H
|
187
main/dialogqueue.cpp
Normal file
187
main/dialogqueue.cpp
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
// 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 "dialogqueue.h"
|
||||||
|
#include <QDebug>
|
||||||
|
|
||||||
|
DialogQueue::DialogQueue(const Models::Roster& p_roster):
|
||||||
|
QObject(),
|
||||||
|
currentSource(),
|
||||||
|
currentAction(none),
|
||||||
|
queue(),
|
||||||
|
collection(queue.get<0>()),
|
||||||
|
sequence(queue.get<1>()),
|
||||||
|
prompt(nullptr),
|
||||||
|
parent(nullptr),
|
||||||
|
roster(p_roster)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
DialogQueue::~DialogQueue()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void DialogQueue::quit()
|
||||||
|
{
|
||||||
|
queue.clear();
|
||||||
|
if (currentAction != none) {
|
||||||
|
actionDone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DialogQueue::setParentWidnow(QMainWindow* p_parent)
|
||||||
|
{
|
||||||
|
parent = p_parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DialogQueue::addAction(const QString& source, DialogQueue::Action action)
|
||||||
|
{
|
||||||
|
if (action == none) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (currentAction == none) {
|
||||||
|
currentAction = action;
|
||||||
|
currentSource = source;
|
||||||
|
performNextAction();
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
if (currentAction != action || currentSource != source) {
|
||||||
|
std::pair<Queue::iterator, bool> result = queue.emplace(source, action);
|
||||||
|
return result.second;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DialogQueue::cancelAction(const QString& source, DialogQueue::Action action)
|
||||||
|
{
|
||||||
|
if (source == currentSource && action == currentAction) {
|
||||||
|
actionDone();
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
Collection::iterator itr = collection.find(ActionId{source, action});
|
||||||
|
if (itr != collection.end()) {
|
||||||
|
collection.erase(itr);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DialogQueue::performNextAction()
|
||||||
|
{
|
||||||
|
switch (currentAction) {
|
||||||
|
case none:
|
||||||
|
actionDone();
|
||||||
|
break;
|
||||||
|
case askPassword: {
|
||||||
|
QInputDialog* dialog = new QInputDialog(parent);
|
||||||
|
prompt = dialog;
|
||||||
|
connect(dialog, &QDialog::accepted, this, &DialogQueue::onPropmtAccepted);
|
||||||
|
connect(dialog, &QDialog::rejected, this, &DialogQueue::onPropmtRejected);
|
||||||
|
dialog->setInputMode(QInputDialog::TextInput);
|
||||||
|
dialog->setTextEchoMode(QLineEdit::Password);
|
||||||
|
dialog->setLabelText(tr("Input the password for account %1").arg(currentSource));
|
||||||
|
dialog->setWindowTitle(tr("Password for account %1").arg(currentSource));
|
||||||
|
dialog->setTextValue("");
|
||||||
|
dialog->exec();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case askCredentials: {
|
||||||
|
CredentialsPrompt* dialog = new CredentialsPrompt(parent);
|
||||||
|
prompt = dialog;
|
||||||
|
connect(dialog, &QDialog::accepted, this, &DialogQueue::onPropmtAccepted);
|
||||||
|
connect(dialog, &QDialog::rejected, this, &DialogQueue::onPropmtRejected);
|
||||||
|
const Models::Account* acc = roster.getAccountConst(currentSource);
|
||||||
|
dialog->setAccount(currentSource);
|
||||||
|
dialog->setLogin(acc->getLogin());
|
||||||
|
dialog->setPassword(acc->getPassword());
|
||||||
|
dialog->exec();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DialogQueue::onPropmtAccepted()
|
||||||
|
{
|
||||||
|
switch (currentAction) {
|
||||||
|
case none:
|
||||||
|
break;
|
||||||
|
case askPassword: {
|
||||||
|
QInputDialog* dialog = static_cast<QInputDialog*>(prompt);
|
||||||
|
emit responsePassword(currentSource, dialog->textValue());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case askCredentials: {
|
||||||
|
CredentialsPrompt* dialog = static_cast<CredentialsPrompt*>(prompt);
|
||||||
|
emit modifyAccountRequest(currentSource, {
|
||||||
|
{"login", dialog->getLogin()},
|
||||||
|
{"password", dialog->getPassword()}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
actionDone();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DialogQueue::onPropmtRejected()
|
||||||
|
{
|
||||||
|
switch (currentAction) {
|
||||||
|
case none:
|
||||||
|
break;
|
||||||
|
case askPassword:
|
||||||
|
case askCredentials:
|
||||||
|
emit disconnectAccount(currentSource);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
actionDone();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DialogQueue::actionDone()
|
||||||
|
{
|
||||||
|
prompt->deleteLater();
|
||||||
|
prompt = nullptr;
|
||||||
|
|
||||||
|
if (queue.empty()) {
|
||||||
|
currentAction = none;
|
||||||
|
currentSource = "";
|
||||||
|
} else {
|
||||||
|
Sequence::iterator itr = sequence.begin();
|
||||||
|
currentAction = itr->action;
|
||||||
|
currentSource = itr->source;
|
||||||
|
sequence.erase(itr);
|
||||||
|
performNextAction();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DialogQueue::ActionId::ActionId(const QString& p_source, DialogQueue::Action p_action):
|
||||||
|
source(p_source),
|
||||||
|
action(p_action) {}
|
||||||
|
|
||||||
|
bool DialogQueue::ActionId::operator < (const DialogQueue::ActionId& other) const
|
||||||
|
{
|
||||||
|
if (action == other.action) {
|
||||||
|
return source < other.source;
|
||||||
|
} else {
|
||||||
|
return action < other.action;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DialogQueue::ActionId::ActionId(const DialogQueue::ActionId& other):
|
||||||
|
source(other.source),
|
||||||
|
action(other.action) {}
|
101
main/dialogqueue.h
Normal file
101
main/dialogqueue.h
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
// 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 DIALOGQUEUE_H
|
||||||
|
#define DIALOGQUEUE_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QInputDialog>
|
||||||
|
#include <QMainWindow>
|
||||||
|
|
||||||
|
#include <boost/multi_index_container.hpp>
|
||||||
|
#include <boost/multi_index/ordered_index.hpp>
|
||||||
|
#include <boost/multi_index/sequenced_index.hpp>
|
||||||
|
|
||||||
|
#include <ui/widgets/accounts/credentialsprompt.h>
|
||||||
|
#include <ui/models/roster.h>
|
||||||
|
|
||||||
|
class DialogQueue : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
enum Action {
|
||||||
|
none,
|
||||||
|
askPassword,
|
||||||
|
askCredentials
|
||||||
|
};
|
||||||
|
|
||||||
|
DialogQueue(const Models::Roster& roster);
|
||||||
|
~DialogQueue();
|
||||||
|
|
||||||
|
bool addAction(const QString& source, Action action);
|
||||||
|
bool cancelAction(const QString& source, Action action);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void modifyAccountRequest(const QString&, const QMap<QString, QVariant>&);
|
||||||
|
void responsePassword(const QString& account, const QString& password);
|
||||||
|
void disconnectAccount(const QString&);
|
||||||
|
|
||||||
|
public:
|
||||||
|
void setParentWidnow(QMainWindow* parent);
|
||||||
|
void quit();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void performNextAction();
|
||||||
|
void actionDone();
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void onPropmtAccepted();
|
||||||
|
void onPropmtRejected();
|
||||||
|
|
||||||
|
private:
|
||||||
|
QString currentSource;
|
||||||
|
Action currentAction;
|
||||||
|
|
||||||
|
struct ActionId {
|
||||||
|
public:
|
||||||
|
ActionId(const QString& p_source, Action p_action);
|
||||||
|
ActionId(const ActionId& other);
|
||||||
|
|
||||||
|
const QString source;
|
||||||
|
const Action action;
|
||||||
|
|
||||||
|
bool operator < (const ActionId& other) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef boost::multi_index_container <
|
||||||
|
ActionId,
|
||||||
|
boost::multi_index::indexed_by <
|
||||||
|
boost::multi_index::ordered_unique <
|
||||||
|
boost::multi_index::identity <ActionId>
|
||||||
|
>,
|
||||||
|
boost::multi_index::sequenced<>
|
||||||
|
>
|
||||||
|
> Queue;
|
||||||
|
|
||||||
|
typedef Queue::nth_index<0>::type Collection;
|
||||||
|
typedef Queue::nth_index<1>::type Sequence;
|
||||||
|
|
||||||
|
Queue queue;
|
||||||
|
Collection& collection;
|
||||||
|
Sequence& sequence;
|
||||||
|
|
||||||
|
QDialog* prompt;
|
||||||
|
QMainWindow* parent;
|
||||||
|
const Models::Roster& roster;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // DIALOGQUEUE_H
|
140
main/main.cpp
Normal file
140
main/main.cpp
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
/*
|
||||||
|
* 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 "shared/global.h"
|
||||||
|
#include "shared/messageinfo.h"
|
||||||
|
#include "shared/pathcheck.h"
|
||||||
|
#include "main/application.h"
|
||||||
|
#include "core/signalcatcher.h"
|
||||||
|
#include "core/squawk.h"
|
||||||
|
|
||||||
|
#include <QLibraryInfo>
|
||||||
|
#include <QSettings>
|
||||||
|
#include <QStandardPaths>
|
||||||
|
#include <QTranslator>
|
||||||
|
#include <QtCore/QObject>
|
||||||
|
#include <QtCore/QThread>
|
||||||
|
#include <QtWidgets/QApplication>
|
||||||
|
#include <QDir>
|
||||||
|
|
||||||
|
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");
|
||||||
|
|
||||||
|
QApplication app(argc, argv);
|
||||||
|
SignalCatcher sc(&app);
|
||||||
|
|
||||||
|
QApplication::setApplicationName("squawk");
|
||||||
|
QApplication::setOrganizationName("macaw.me");
|
||||||
|
QApplication::setApplicationDisplayName("Squawk");
|
||||||
|
QApplication::setApplicationVersion("0.2.2");
|
||||||
|
app.setDesktopFileName("squawk");
|
||||||
|
|
||||||
|
QTranslator qtTranslator;
|
||||||
|
qtTranslator.load("qt_" + QLocale::system().name(), QLibraryInfo::location(QLibraryInfo::TranslationsPath));
|
||||||
|
app.installTranslator(&qtTranslator);
|
||||||
|
|
||||||
|
QTranslator myappTranslator;
|
||||||
|
QStringList shares = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation);
|
||||||
|
bool found = false;
|
||||||
|
for (QString share : shares) {
|
||||||
|
found = myappTranslator.load(QLocale(), QLatin1String("squawk"), ".", share + "/l10n");
|
||||||
|
if (found) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!found) {
|
||||||
|
myappTranslator.load(QLocale(), QLatin1String("squawk"), ".", QCoreApplication::applicationDirPath());
|
||||||
|
}
|
||||||
|
|
||||||
|
app.installTranslator(&myappTranslator);
|
||||||
|
|
||||||
|
QIcon icon;
|
||||||
|
icon.addFile(":images/logo.svg", QSize(16, 16));
|
||||||
|
icon.addFile(":images/logo.svg", QSize(24, 24));
|
||||||
|
icon.addFile(":images/logo.svg", QSize(32, 32));
|
||||||
|
icon.addFile(":images/logo.svg", QSize(48, 48));
|
||||||
|
icon.addFile(":images/logo.svg", QSize(64, 64));
|
||||||
|
icon.addFile(":images/logo.svg", QSize(96, 96));
|
||||||
|
icon.addFile(":images/logo.svg", QSize(128, 128));
|
||||||
|
icon.addFile(":images/logo.svg", QSize(256, 256));
|
||||||
|
icon.addFile(":images/logo.svg", QSize(512, 512));
|
||||||
|
QApplication::setWindowIcon(icon);
|
||||||
|
|
||||||
|
new Shared::Global(); //translates enums
|
||||||
|
|
||||||
|
QSettings settings;
|
||||||
|
QVariant vs = settings.value("style");
|
||||||
|
if (vs.isValid()) {
|
||||||
|
QString style = vs.toString().toLower();
|
||||||
|
if (style != "system") {
|
||||||
|
Shared::Global::setStyle(style);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (Shared::Global::supported("colorSchemeTools")) {
|
||||||
|
QVariant vt = settings.value("theme");
|
||||||
|
if (vt.isValid()) {
|
||||||
|
QString theme = vt.toString();
|
||||||
|
if (theme.toLower() != "system") {
|
||||||
|
Shared::Global::setTheme(theme);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
QString path = Shared::downloadsPathCheck();
|
||||||
|
if (path.size() > 0) {
|
||||||
|
settings.setValue("downloadsPath", path);
|
||||||
|
} else {
|
||||||
|
qDebug() << "couldn't initialize directory for downloads, quitting";
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Core::Squawk* squawk = new Core::Squawk();
|
||||||
|
QThread* coreThread = new QThread();
|
||||||
|
squawk->moveToThread(coreThread);
|
||||||
|
|
||||||
|
Application application(squawk);
|
||||||
|
|
||||||
|
QObject::connect(&sc, &SignalCatcher::interrupt, &application, &Application::quit);
|
||||||
|
|
||||||
|
QObject::connect(coreThread, &QThread::started, squawk, &Core::Squawk::start);
|
||||||
|
QObject::connect(&application, &Application::quitting, squawk, &Core::Squawk::stop);
|
||||||
|
//QObject::connect(&app, &QApplication::aboutToQuit, &w, &QMainWindow::close);
|
||||||
|
QObject::connect(squawk, &Core::Squawk::quit, squawk, &Core::Squawk::deleteLater);
|
||||||
|
QObject::connect(squawk, &Core::Squawk::destroyed, coreThread, &QThread::quit, Qt::QueuedConnection);
|
||||||
|
QObject::connect(coreThread, &QThread::finished, &app, &QApplication::quit, Qt::QueuedConnection);
|
||||||
|
|
||||||
|
coreThread->start();
|
||||||
|
int result = app.exec();
|
||||||
|
|
||||||
|
if (coreThread->isRunning()) {
|
||||||
|
//coreThread->wait();
|
||||||
|
//todo if I uncomment that, the app will not quit if it has reconnected at least once
|
||||||
|
//it feels like a symptom of something badly desinged in the core thread
|
||||||
|
//need to investigate;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
@ -1,17 +1,20 @@
|
|||||||
# Maintainer: Yury Gubich <blue@macaw.me>
|
# Maintainer: Yury Gubich <blue@macaw.me>
|
||||||
pkgname=squawk
|
pkgname=squawk
|
||||||
pkgver=0.1.5
|
pkgver=0.2.2
|
||||||
pkgrel=1
|
pkgrel=1
|
||||||
pkgdesc="An XMPP desktop messenger, written on pure c++ (qt)"
|
pkgdesc="An XMPP desktop messenger, written on pure c++ (qt)"
|
||||||
arch=('i686' 'x86_64')
|
arch=('i686' 'x86_64')
|
||||||
url="https://git.macaw.me/blue/squawk"
|
url="https://git.macaw.me/blue/squawk"
|
||||||
license=('GPL3')
|
license=('GPL3')
|
||||||
depends=('hicolor-icon-theme' 'desktop-file-utils' 'lmdb' 'qxmpp>=1.1.0')
|
depends=('hicolor-icon-theme' 'desktop-file-utils' 'lmdb' 'qxmpp>=1.1.0')
|
||||||
makedepends=('cmake>=3.3' 'imagemagick' 'qt5-tools')
|
makedepends=('cmake>=3.3' 'imagemagick' 'qt5-tools' 'boost')
|
||||||
optdepends=('kwallet: secure password storage (requires rebuild)')
|
optdepends=('kwallet: secure password storage (requires rebuild)'
|
||||||
|
'kconfig: system themes support (requires rebuild)'
|
||||||
|
'kconfigwidgets: system themes support (requires rebuild)'
|
||||||
|
'kio: better show in folder action (requires rebuild)')
|
||||||
|
|
||||||
source=("$pkgname-$pkgver.tar.gz")
|
source=("$pkgname-$pkgver.tar.gz")
|
||||||
sha256sums=('e1a4c88be9f0481d2aa21078faf42fd0e9d66b490b6d8af82827d441cb58df25')
|
sha256sums=('e4fa2174a3ba95159cc3b0bac3f00550c9e0ce971c55334e2662696a4543fc7e')
|
||||||
build() {
|
build() {
|
||||||
cd "$srcdir/squawk"
|
cd "$srcdir/squawk"
|
||||||
cmake . -D CMAKE_INSTALL_PREFIX=/usr -D CMAKE_BUILD_TYPE=Release
|
cmake . -D CMAKE_INSTALL_PREFIX=/usr -D CMAKE_BUILD_TYPE=Release
|
||||||
|
3
packaging/CMakeLists.txt
Normal file
3
packaging/CMakeLists.txt
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
configure_file(squawk.desktop squawk.desktop COPYONLY)
|
||||||
|
|
||||||
|
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk.desktop DESTINATION ${CMAKE_INSTALL_DATADIR}/applications)
|
@ -5,8 +5,10 @@ Version=1.0
|
|||||||
Name=Squawk
|
Name=Squawk
|
||||||
GenericName=Instant Messenger
|
GenericName=Instant Messenger
|
||||||
GenericName[ru]=Мгновенные сообщения
|
GenericName[ru]=Мгновенные сообщения
|
||||||
|
GenericName[pt_BR]=Mensageiro instantâneo
|
||||||
Comment=XMPP (Jabber) instant messenger client
|
Comment=XMPP (Jabber) instant messenger client
|
||||||
Comment[ru]=XMPP (Jabber) клиент обмена мгновенными сообщениями
|
Comment[ru]=XMPP (Jabber) клиент обмена мгновенными сообщениями
|
||||||
|
Comment[pt_BR]=Cliente de mensagem instantânea XMPP (Jabber)
|
||||||
Exec=squawk %u
|
Exec=squawk %u
|
||||||
Icon=squawk
|
Icon=squawk
|
||||||
StartupNotify=true
|
StartupNotify=true
|
||||||
|
@ -1,26 +1,14 @@
|
|||||||
cmake_minimum_required(VERSION 3.3)
|
|
||||||
project(plugins)
|
|
||||||
|
|
||||||
if (WITH_KIO)
|
if (WITH_KIO)
|
||||||
set(CMAKE_AUTOMOC ON)
|
add_library(openFileManagerWindowJob SHARED openfilemanagerwindowjob.cpp)
|
||||||
|
target_link_libraries(openFileManagerWindowJob PRIVATE KF5::KIOWidgets)
|
||||||
|
|
||||||
find_package(Qt5Core CONFIG REQUIRED)
|
install(TARGETS openFileManagerWindowJob LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})
|
||||||
|
endif ()
|
||||||
set(openFileManagerWindowJob_SRC
|
|
||||||
openfilemanagerwindowjob.cpp
|
if (WITH_KCONFIG)
|
||||||
)
|
add_library(colorSchemeTools SHARED colorschemetools.cpp)
|
||||||
|
target_link_libraries(colorSchemeTools PRIVATE KF5::ConfigCore)
|
||||||
add_library(openFileManagerWindowJob SHARED ${openFileManagerWindowJob_SRC})
|
target_link_libraries(colorSchemeTools PRIVATE KF5::ConfigWidgets)
|
||||||
|
|
||||||
get_target_property(Qt5CORE_INTERFACE_INCLUDE_DIRECTORIES Qt5::Core INTERFACE_INCLUDE_DIRECTORIES)
|
install(TARGETS colorSchemeTools LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})
|
||||||
get_target_property(KIO_WIDGETS_INTERFACE_INCLUDE_DIRECTORIES KF5::KIOWidgets INTERFACE_INCLUDE_DIRECTORIES)
|
|
||||||
get_target_property(CORE_ADDONS_INTERFACE_INCLUDE_DIRECTORIES KF5::CoreAddons INTERFACE_INCLUDE_DIRECTORIES)
|
|
||||||
target_include_directories(openFileManagerWindowJob PUBLIC ${KIO_WIDGETS_INTERFACE_INCLUDE_DIRECTORIES})
|
|
||||||
target_include_directories(openFileManagerWindowJob PUBLIC ${CORE_ADDONS_INTERFACE_INCLUDE_DIRECTORIES})
|
|
||||||
target_include_directories(openFileManagerWindowJob PUBLIC ${Qt5CORE_INTERFACE_INCLUDE_DIRECTORIES})
|
|
||||||
|
|
||||||
target_link_libraries(openFileManagerWindowJob KF5::KIOWidgets)
|
|
||||||
target_link_libraries(openFileManagerWindowJob Qt5::Core)
|
|
||||||
|
|
||||||
install(TARGETS openFileManagerWindowJob DESTINATION ${CMAKE_INSTALL_LIBDIR})
|
|
||||||
endif()
|
endif()
|
||||||
|
70
plugins/colorschemetools.cpp
Normal file
70
plugins/colorschemetools.cpp
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
/*
|
||||||
|
* 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 <QIcon>
|
||||||
|
#include <QPainter>
|
||||||
|
#include <QFileInfo>
|
||||||
|
|
||||||
|
#include <KConfigCore/KSharedConfig>
|
||||||
|
#include <KConfigCore/KConfigGroup>
|
||||||
|
#include <KConfigWidgets/KColorScheme>
|
||||||
|
|
||||||
|
QPixmap createPixmap(int size, const QBrush& window, const QBrush& button, const QBrush& view, const QBrush& selection);
|
||||||
|
|
||||||
|
extern "C" QIcon* createPreview(const QString& path) {
|
||||||
|
KSharedConfigPtr schemeConfig = KSharedConfig::openConfig(path);
|
||||||
|
QIcon* result = new QIcon();
|
||||||
|
KColorScheme activeWindow(QPalette::Active, KColorScheme::Window, schemeConfig);
|
||||||
|
KColorScheme activeButton(QPalette::Active, KColorScheme::Button, schemeConfig);
|
||||||
|
KColorScheme activeView(QPalette::Active, KColorScheme::View, schemeConfig);
|
||||||
|
KColorScheme activeSelection(QPalette::Active, KColorScheme::Selection, schemeConfig);
|
||||||
|
|
||||||
|
result->addPixmap(createPixmap(16, activeWindow.background(), activeButton.background(), activeView.background(), activeSelection.background()));
|
||||||
|
result->addPixmap(createPixmap(24, activeWindow.background(), activeButton.background(), activeView.background(), activeSelection.background()));
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" void deletePreview(QIcon* icon) {
|
||||||
|
delete icon;
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" void colorSchemeName(const QString& path, QString& answer) {
|
||||||
|
KSharedConfigPtr config = KSharedConfig::openConfig(path);
|
||||||
|
KConfigGroup group(config, QStringLiteral("General"));
|
||||||
|
answer = group.readEntry("Name", QFileInfo(path).baseName());
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" void createPalette(const QString& path, QPalette& answer) {
|
||||||
|
KSharedConfigPtr config = KSharedConfig::openConfig(path);
|
||||||
|
answer = KColorScheme::createApplicationPalette(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
QPixmap createPixmap(int size, const QBrush& window, const QBrush& button, const QBrush& view, const QBrush& selection) {
|
||||||
|
QPixmap pix(size, size);
|
||||||
|
pix.fill(Qt::black);
|
||||||
|
QPainter p;
|
||||||
|
p.begin(&pix);
|
||||||
|
const int itemSize = size / 2 - 1;
|
||||||
|
p.fillRect(1, 1, itemSize, itemSize, window);
|
||||||
|
p.fillRect(1 + itemSize, 1, itemSize, itemSize, button);
|
||||||
|
p.fillRect(1, 1 + itemSize, itemSize, itemSize, view);
|
||||||
|
p.fillRect(1 + itemSize, 1 + itemSize, itemSize, itemSize, selection);
|
||||||
|
p.end();
|
||||||
|
return pix;
|
||||||
|
}
|
@ -1,3 +1,21 @@
|
|||||||
|
/*
|
||||||
|
* 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 <QUrl>
|
#include <QUrl>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <KIO/OpenFileManagerWindowJob>
|
#include <KIO/OpenFileManagerWindowJob>
|
||||||
|
59
resources/CMakeLists.txt
Normal file
59
resources/CMakeLists.txt
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
target_sources(squawk PRIVATE resources.qrc)
|
||||||
|
|
||||||
|
configure_file(images/logo.svg squawk.svg COPYONLY)
|
||||||
|
configure_file(squawk.rc squawk.rc COPYONLY)
|
||||||
|
|
||||||
|
if(WIN32)
|
||||||
|
set(CONVERT_BIN magick convert)
|
||||||
|
else(WIN32)
|
||||||
|
set(CONVERT_BIN convert)
|
||||||
|
endif(WIN32)
|
||||||
|
execute_process(COMMAND ${CONVERT_BIN} -background none -size 48x48 squawk.svg squawk48.png WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
|
||||||
|
execute_process(COMMAND ${CONVERT_BIN} -background none -size 64x64 squawk.svg squawk64.png WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
|
||||||
|
execute_process(COMMAND ${CONVERT_BIN} -background none -size 128x128 squawk.svg squawk128.png WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
|
||||||
|
execute_process(COMMAND ${CONVERT_BIN} -background none -size 256x256 squawk.svg squawk256.png WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
|
||||||
|
|
||||||
|
if (WIN32)
|
||||||
|
execute_process(COMMAND ${CONVERT_BIN} squawk48.png squawk64.png squawk256.png squawk.ico WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
|
||||||
|
set(SQUAWK_WIN_RC "${CMAKE_CURRENT_BINARY_DIR}/squawk.rc")
|
||||||
|
set(SQUAWK_WIN_RC "${SQUAWK_WIN_RC}" PARENT_SCOPE)
|
||||||
|
target_sources(squawk PRIVATE ${SQUAWK_WIN_RC})
|
||||||
|
endif(WIN32)
|
||||||
|
|
||||||
|
if (APPLE)
|
||||||
|
file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/icns.iconset")
|
||||||
|
execute_process(COMMAND convert -background none -size 16x16 squawk.svg icns.iconset/icon_16x16.png WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
|
||||||
|
execute_process(COMMAND convert -background none -resize !32x32 squawk.svg "icns.iconset/icon_16x16@2x.png" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
|
||||||
|
execute_process(COMMAND convert -background none -resize !32x32 squawk.svg "icns.iconset/icon_32x32.png" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
|
||||||
|
execute_process(COMMAND convert -background none -resize !64x64 squawk.svg "icns.iconset/icon_32x32@2x.png" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
|
||||||
|
execute_process(COMMAND convert -background none -resize !128x128 squawk.svg "icns.iconset/icon_128x128.png" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
|
||||||
|
execute_process(COMMAND convert -background none -resize !256x256 squawk.svg "icns.iconset/icon_128x128@2x.png" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
|
||||||
|
execute_process(COMMAND convert -background none -resize !256x256 squawk.svg "icns.iconset/icon_256x256.png" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
|
||||||
|
execute_process(COMMAND convert -background none -resize !512x512 squawk.svg "icns.iconset/icon_256x256@2x.png" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
|
||||||
|
execute_process(COMMAND convert -background none -resize !512x512 squawk.svg "icns.iconset/icon_512x512.png" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
|
||||||
|
execute_process(COMMAND convert -background none -resize !1024x1024 squawk.svg "icns.iconset/icon_512x512@2x.png" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
|
||||||
|
execute_process(COMMAND iconutil -c icns "icns.iconset" -o "squawk.icns" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
|
||||||
|
set(MACOSX_BUNDLE_ICON_FILE squawk.icns)
|
||||||
|
set(MACOSX_BUNDLE_ICON_FILE ${MACOSX_BUNDLE_ICON_FILE} PARENT_SCOPE)
|
||||||
|
set(APP_ICON_MACOSX ${CMAKE_CURRENT_BINARY_DIR}/squawk.icns)
|
||||||
|
set(APP_ICON_MACOSX ${APP_ICON_MACOSX} PARENT_SCOPE)
|
||||||
|
target_sources(squawk PRIVATE ${APP_ICON_MACOSX})
|
||||||
|
set_source_files_properties(${APP_ICON_MACOSX} TARGET_DIRECTORY squawk PROPERTIES
|
||||||
|
MACOSX_PACKAGE_LOCATION "Resources")
|
||||||
|
if (CMAKE_BUILD_TYPE STREQUAL "Release")
|
||||||
|
if (APPLE)
|
||||||
|
set_target_properties(squawk PROPERTIES
|
||||||
|
MACOSX_BUNDLE_EXECUTABLE_NAME "Squawk"
|
||||||
|
MACOSX_BUNDLE_ICON_FILE "${MACOSX_BUNDLE_ICON_FILE}" # TODO
|
||||||
|
MACOSX_BUNDLE_BUNDLE_NAME "Squawk"
|
||||||
|
MACOSX_BUNDLE_INFO_PLIST ${CMAKE_SOURCE_DIR}/cmake/MacOSXBundleInfo.plist.in)
|
||||||
|
endif(APPLE)
|
||||||
|
endif()
|
||||||
|
endif (APPLE)
|
||||||
|
|
||||||
|
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk.svg DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/scalable/apps)
|
||||||
|
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk48.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/48x48/apps RENAME squawk.png)
|
||||||
|
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk64.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/64x64/apps RENAME squawk.png)
|
||||||
|
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk128.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/128x128/apps RENAME squawk.png)
|
||||||
|
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk256.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/256x256/apps RENAME squawk.png)
|
||||||
|
|
1
resources/squawk.rc
Normal file
1
resources/squawk.rc
Normal file
@ -0,0 +1 @@
|
|||||||
|
IDI_ICON1 ICON "squawk.ico"
|
21
shared/CMakeLists.txt
Normal file
21
shared/CMakeLists.txt
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
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
|
||||||
|
pathcheck.cpp
|
||||||
|
pathcheck.h
|
||||||
|
)
|
@ -28,5 +28,6 @@ Utils::Exception::~Exception()
|
|||||||
|
|
||||||
const char* Utils::Exception::what() const noexcept( true )
|
const char* Utils::Exception::what() const noexcept( true )
|
||||||
{
|
{
|
||||||
return getMessage().c_str();
|
std::string* msg = new std::string(getMessage());
|
||||||
|
return msg->c_str();
|
||||||
}
|
}
|
@ -19,6 +19,7 @@
|
|||||||
#include "global.h"
|
#include "global.h"
|
||||||
|
|
||||||
#include "enums.h"
|
#include "enums.h"
|
||||||
|
#include "ui/models/roster.h"
|
||||||
|
|
||||||
Shared::Global* Shared::Global::instance = 0;
|
Shared::Global* Shared::Global::instance = 0;
|
||||||
const std::set<QString> Shared::Global::supportedImagesExts = {"png", "jpg", "webp", "jpeg", "gif", "svg"};
|
const std::set<QString> Shared::Global::supportedImagesExts = {"png", "jpg", "webp", "jpeg", "gif", "svg"};
|
||||||
@ -28,6 +29,14 @@ QLibrary Shared::Global::openFileManagerWindowJob("openFileManagerWindowJob");
|
|||||||
Shared::Global::HighlightInFileManager Shared::Global::hfm = 0;
|
Shared::Global::HighlightInFileManager Shared::Global::hfm = 0;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef WITH_KCONFIG
|
||||||
|
QLibrary Shared::Global::colorSchemeTools("colorSchemeTools");
|
||||||
|
Shared::Global::CreatePreview Shared::Global::createPreview = 0;
|
||||||
|
Shared::Global::DeletePreview Shared::Global::deletePreview = 0;
|
||||||
|
Shared::Global::ColorSchemeName Shared::Global::colorSchemeName = 0;
|
||||||
|
Shared::Global::CreatePalette Shared::Global::createPalette = 0;
|
||||||
|
#endif
|
||||||
|
|
||||||
Shared::Global::Global():
|
Shared::Global::Global():
|
||||||
availability({
|
availability({
|
||||||
tr("Online", "Availability"),
|
tr("Online", "Availability"),
|
||||||
@ -84,9 +93,12 @@ Shared::Global::Global():
|
|||||||
tr("Squawk is going to query you for the password on every start of the program", "AccountPasswordDescription"),
|
tr("Squawk is going to query you for the password on every start of the program", "AccountPasswordDescription"),
|
||||||
tr("Your password is going to be stored in KDE wallet storage (KWallet). You're going to be queried for permissions", "AccountPasswordDescription")
|
tr("Your password is going to be stored in KDE wallet storage (KWallet). You're going to be queried for permissions", "AccountPasswordDescription")
|
||||||
}),
|
}),
|
||||||
|
defaultSystemStyle(QApplication::style()->objectName()),
|
||||||
|
defaultSystemPalette(QApplication::palette()),
|
||||||
pluginSupport({
|
pluginSupport({
|
||||||
{"KWallet", false},
|
{"KWallet", false},
|
||||||
{"openFileManagerWindowJob", false}
|
{"openFileManagerWindowJob", false},
|
||||||
|
{"colorSchemeTools", false}
|
||||||
}),
|
}),
|
||||||
fileCache()
|
fileCache()
|
||||||
{
|
{
|
||||||
@ -110,8 +122,28 @@ Shared::Global::Global():
|
|||||||
qDebug() << "KIO::OpenFileManagerWindow support disabled: couldn't load the library" << openFileManagerWindowJob.errorString();
|
qDebug() << "KIO::OpenFileManagerWindow support disabled: couldn't load the library" << openFileManagerWindowJob.errorString();
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef WITH_KCONFIG
|
||||||
|
colorSchemeTools.load();
|
||||||
|
if (colorSchemeTools.isLoaded()) {
|
||||||
|
createPreview = (CreatePreview) colorSchemeTools.resolve("createPreview");
|
||||||
|
deletePreview = (DeletePreview) colorSchemeTools.resolve("deletePreview");
|
||||||
|
colorSchemeName = (ColorSchemeName) colorSchemeTools.resolve("colorSchemeName");
|
||||||
|
createPalette = (CreatePalette) colorSchemeTools.resolve("createPalette");
|
||||||
|
if (createPreview && deletePreview && colorSchemeName && createPalette) {
|
||||||
|
setSupported("colorSchemeTools", true);
|
||||||
|
qDebug() << "Color Schemes support enabled";
|
||||||
|
} else {
|
||||||
|
qDebug() << "Color Schemes support disabled: couldn't resolve required methods in the library";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
qDebug() << "Color Schemes support disabled: couldn't load the library" << colorSchemeTools.errorString();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static const QSize defaultIconFileInfoHeight(50, 50);
|
||||||
Shared::Global::FileInfo Shared::Global::getFileInfo(const QString& path)
|
Shared::Global::FileInfo Shared::Global::getFileInfo(const QString& path)
|
||||||
{
|
{
|
||||||
std::map<QString, FileInfo>::const_iterator itr = instance->fileCache.find(path);
|
std::map<QString, FileInfo>::const_iterator itr = instance->fileCache.find(path);
|
||||||
@ -125,15 +157,24 @@ Shared::Global::FileInfo Shared::Global::getFileInfo(const QString& path)
|
|||||||
FileInfo::Preview p = FileInfo::Preview::none;
|
FileInfo::Preview p = FileInfo::Preview::none;
|
||||||
QSize size;
|
QSize size;
|
||||||
if (big == "image") {
|
if (big == "image") {
|
||||||
if (parts.back() == "gif") {
|
QMovie mov(path);
|
||||||
//TODO need to consider GIF as a movie
|
if (mov.isValid() && mov.frameCount() > 1) {
|
||||||
}
|
p = FileInfo::Preview::animation;
|
||||||
|
} else {
|
||||||
p = FileInfo::Preview::picture;
|
p = FileInfo::Preview::picture;
|
||||||
QImage img(path);
|
}
|
||||||
|
QImageReader img(path);
|
||||||
size = img.size();
|
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;
|
itr = instance->fileCache.insert(std::make_pair(path, FileInfo({info.absoluteFilePath(), info.fileName(), size, type, p}))).first;
|
||||||
}
|
}
|
||||||
|
|
||||||
return itr->second;
|
return itr->second;
|
||||||
@ -264,6 +305,50 @@ void Shared::Global::highlightInFileManager(const QString& path)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QIcon Shared::Global::createThemePreview(const QString& path)
|
||||||
|
{
|
||||||
|
if (supported("colorSchemeTools")) {
|
||||||
|
QIcon* icon = createPreview(path);
|
||||||
|
QIcon localIcon = *icon;
|
||||||
|
deletePreview(icon);
|
||||||
|
return localIcon;
|
||||||
|
} else {
|
||||||
|
return QIcon();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QString Shared::Global::getColorSchemeName(const QString& path)
|
||||||
|
{
|
||||||
|
if (supported("colorSchemeTools")) {
|
||||||
|
QString res;
|
||||||
|
colorSchemeName(path, res);
|
||||||
|
return res;
|
||||||
|
} else {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Shared::Global::setTheme(const QString& path)
|
||||||
|
{
|
||||||
|
if (supported("colorSchemeTools")) {
|
||||||
|
if (path.toLower() == "system") {
|
||||||
|
QApplication::setPalette(getInstance()->defaultSystemPalette);
|
||||||
|
} else {
|
||||||
|
QPalette pallete;
|
||||||
|
createPalette(path, pallete);
|
||||||
|
QApplication::setPalette(pallete);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Shared::Global::setStyle(const QString& style)
|
||||||
|
{
|
||||||
|
if (style.toLower() == "system") {
|
||||||
|
QApplication::setStyle(getInstance()->defaultSystemStyle);
|
||||||
|
} else {
|
||||||
|
QApplication::setStyle(style);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#define FROM_INT_INPL(Enum) \
|
#define FROM_INT_INPL(Enum) \
|
||||||
template<> \
|
template<> \
|
||||||
|
@ -27,12 +27,14 @@
|
|||||||
#include <set>
|
#include <set>
|
||||||
#include <deque>
|
#include <deque>
|
||||||
|
|
||||||
#include <QCoreApplication>
|
#include <QApplication>
|
||||||
|
#include <QStyle>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QMimeType>
|
#include <QMimeType>
|
||||||
#include <QMimeDatabase>
|
#include <QMimeDatabase>
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
#include <QImage>
|
#include <QImage>
|
||||||
|
#include <QMovie>
|
||||||
#include <QSize>
|
#include <QSize>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
#include <QLibrary>
|
#include <QLibrary>
|
||||||
@ -45,15 +47,15 @@ namespace Shared {
|
|||||||
|
|
||||||
class Global {
|
class Global {
|
||||||
Q_DECLARE_TR_FUNCTIONS(Global)
|
Q_DECLARE_TR_FUNCTIONS(Global)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
struct FileInfo {
|
struct FileInfo {
|
||||||
enum class Preview {
|
enum class Preview {
|
||||||
none,
|
none,
|
||||||
picture,
|
picture,
|
||||||
movie
|
animation
|
||||||
};
|
};
|
||||||
|
|
||||||
|
QString path;
|
||||||
QString name;
|
QString name;
|
||||||
QSize size;
|
QSize size;
|
||||||
QMimeType mime;
|
QMimeType mime;
|
||||||
@ -83,6 +85,9 @@ namespace Shared {
|
|||||||
|
|
||||||
const std::deque<QString> accountPasswordDescription;
|
const std::deque<QString> accountPasswordDescription;
|
||||||
|
|
||||||
|
const QString defaultSystemStyle;
|
||||||
|
const QPalette defaultSystemPalette;
|
||||||
|
|
||||||
static bool supported(const QString& pluginName);
|
static bool supported(const QString& pluginName);
|
||||||
static void setSupported(const QString& pluginName, bool support);
|
static void setSupported(const QString& pluginName, bool support);
|
||||||
|
|
||||||
@ -90,6 +95,10 @@ namespace Shared {
|
|||||||
|
|
||||||
static FileInfo getFileInfo(const QString& path);
|
static FileInfo getFileInfo(const QString& path);
|
||||||
static void highlightInFileManager(const QString& path);
|
static void highlightInFileManager(const QString& path);
|
||||||
|
static QIcon createThemePreview(const QString& path);
|
||||||
|
static QString getColorSchemeName(const QString& path);
|
||||||
|
static void setTheme(const QString& path);
|
||||||
|
static void setStyle(const QString& style);
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
static T fromInt(int src);
|
static T fromInt(int src);
|
||||||
@ -123,6 +132,20 @@ namespace Shared {
|
|||||||
|
|
||||||
static HighlightInFileManager hfm;
|
static HighlightInFileManager hfm;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef WITH_KCONFIG
|
||||||
|
static QLibrary colorSchemeTools;
|
||||||
|
|
||||||
|
typedef QIcon* (*CreatePreview)(const QString&);
|
||||||
|
typedef void (*DeletePreview)(QIcon*);
|
||||||
|
typedef void (*ColorSchemeName)(const QString&, QString&);
|
||||||
|
typedef void (*CreatePalette)(const QString&, QPalette&);
|
||||||
|
|
||||||
|
static CreatePreview createPreview;
|
||||||
|
static DeletePreview deletePreview;
|
||||||
|
static ColorSchemeName colorSchemeName;
|
||||||
|
static CreatePalette createPalette;
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -404,9 +404,11 @@ bool Shared::Message::change(const QMap<QString, QVariant>& data)
|
|||||||
correctionDate = QDateTime::currentDateTimeUtc(); //in case there is no information about time of this correction it's applied
|
correctionDate = QDateTime::currentDateTimeUtc(); //in case there is no information about time of this correction it's applied
|
||||||
}
|
}
|
||||||
if (!edited || lastModified < correctionDate) {
|
if (!edited || lastModified < correctionDate) {
|
||||||
|
if (!edited) {
|
||||||
originalMessage = body;
|
originalMessage = body;
|
||||||
|
}
|
||||||
lastModified = correctionDate;
|
lastModified = correctionDate;
|
||||||
setBody(body);
|
setBody(b);
|
||||||
setEdited(true);
|
setEdited(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
96
shared/pathcheck.cpp
Normal file
96
shared/pathcheck.cpp
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
/*
|
||||||
|
* 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 "pathcheck.h"
|
||||||
|
|
||||||
|
QRegularExpression squawk("^squawk:\\/\\/");
|
||||||
|
QString Shared::downloadsPathCheck()
|
||||||
|
{
|
||||||
|
QSettings settings;
|
||||||
|
QVariant dpv = settings.value("downloadsPath");
|
||||||
|
QString path;
|
||||||
|
if (!dpv.isValid()) {
|
||||||
|
path = defaultDownloadsPath();
|
||||||
|
qDebug() << "no downloadsPath variable in config, using default" << path;
|
||||||
|
path = getAbsoluteWritablePath(path);
|
||||||
|
return path;
|
||||||
|
} else {
|
||||||
|
path = dpv.toString();
|
||||||
|
path = getAbsoluteWritablePath(path);
|
||||||
|
if (path.size() == 0) {
|
||||||
|
path = defaultDownloadsPath();
|
||||||
|
qDebug() << "falling back to the default downloads path" << path;
|
||||||
|
path = getAbsoluteWritablePath(path);
|
||||||
|
}
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QString Shared::defaultDownloadsPath()
|
||||||
|
{
|
||||||
|
return QStandardPaths::writableLocation(QStandardPaths::DownloadLocation) + "/" + QApplication::applicationName();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString Shared::getAbsoluteWritablePath(const QString& path)
|
||||||
|
{
|
||||||
|
QDir location(path);
|
||||||
|
if (!location.exists()) {
|
||||||
|
bool res = location.mkpath(location.absolutePath());
|
||||||
|
if (!res) {
|
||||||
|
qDebug() << "couldn't create directory" << path;
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
QFileInfo info(location.absolutePath());
|
||||||
|
if (info.isWritable()) {
|
||||||
|
return location.absolutePath();
|
||||||
|
} else {
|
||||||
|
qDebug() << "directory" << path << "is not writable";
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QString Shared::resolvePath(QString path)
|
||||||
|
{
|
||||||
|
QSettings settings;
|
||||||
|
QVariant dpv = settings.value("downloadsPath");
|
||||||
|
return path.replace(squawk, dpv.toString() + "/");
|
||||||
|
}
|
||||||
|
|
||||||
|
QString Shared::squawkifyPath(QString path)
|
||||||
|
{
|
||||||
|
QSettings settings;
|
||||||
|
QString current = settings.value("downloadsPath").toString();
|
||||||
|
|
||||||
|
if (path.startsWith(current)) {
|
||||||
|
path.replace(0, current.size() + 1, "squawk://");
|
||||||
|
}
|
||||||
|
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Shared::isSubdirectoryOfSettings(const QString& path)
|
||||||
|
{
|
||||||
|
|
||||||
|
QSettings settings;
|
||||||
|
QDir oldPath(settings.value("downloadsPath").toString());
|
||||||
|
QDir newPath(path);
|
||||||
|
|
||||||
|
return newPath.canonicalPath().startsWith(oldPath.canonicalPath());
|
||||||
|
}
|
||||||
|
|
44
shared/pathcheck.h
Normal file
44
shared/pathcheck.h
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* 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 PATHCHECK_H
|
||||||
|
#define PATHCHECK_H
|
||||||
|
|
||||||
|
#include <QString>
|
||||||
|
#include <QStandardPaths>
|
||||||
|
#include <QSettings>
|
||||||
|
#include <QApplication>
|
||||||
|
#include <QDir>
|
||||||
|
#include <QFileInfo>
|
||||||
|
#include <QDebug>
|
||||||
|
#include <QRegularExpression>
|
||||||
|
|
||||||
|
namespace Shared {
|
||||||
|
|
||||||
|
QString downloadsPathCheck();
|
||||||
|
QString downloadsPathCheck(QString path);
|
||||||
|
QString defaultDownloadsPath();
|
||||||
|
|
||||||
|
QString getAbsoluteWritablePath(const QString& path);
|
||||||
|
QString resolvePath(QString path);
|
||||||
|
QString squawkifyPath(QString path);
|
||||||
|
bool isSubdirectoryOfSettings(const QString& path);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // PATHCHECK_H
|
@ -19,12 +19,12 @@
|
|||||||
#ifndef SHARED_H
|
#ifndef SHARED_H
|
||||||
#define SHARED_H
|
#define SHARED_H
|
||||||
|
|
||||||
#include "shared/enums.h"
|
#include "enums.h"
|
||||||
#include "shared/utils.h"
|
#include "global.h"
|
||||||
#include "shared/icons.h"
|
#include "icons.h"
|
||||||
#include "shared/message.h"
|
#include "message.h"
|
||||||
#include "shared/vcard.h"
|
#include "messageinfo.h"
|
||||||
#include "shared/global.h"
|
#include "utils.h"
|
||||||
#include "shared/messageinfo.h"
|
#include "vcard.h"
|
||||||
|
|
||||||
#endif // SHARED_H
|
#endif // SHARED_H
|
@ -17,15 +17,11 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
|
#include <QUuid>
|
||||||
|
|
||||||
QString Shared::generateUUID()
|
QString Shared::generateUUID()
|
||||||
{
|
{
|
||||||
uuid_t uuid;
|
return QUuid::createUuid().toString();
|
||||||
uuid_generate(uuid);
|
|
||||||
|
|
||||||
char uuid_str[36];
|
|
||||||
uuid_unparse_lower(uuid, uuid_str);
|
|
||||||
return uuid_str;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -44,5 +40,5 @@ QString Shared::processMessageBody(const QString& msg)
|
|||||||
{
|
{
|
||||||
QString processed = msg.toHtmlEscaped();
|
QString processed = msg.toHtmlEscaped();
|
||||||
processed.replace(urlReg, "<a href=\"\\1\">\\1</a>");
|
processed.replace(urlReg, "<a href=\"\\1\">\\1</a>");
|
||||||
return "<p style=\"white-space: pre-wrap;\">" + processed + "</p>";
|
return "<p style=\"white-space: pre-wrap; line-height: 1em;\">" + processed + "</p>";
|
||||||
}
|
}
|
||||||
|
@ -24,9 +24,6 @@
|
|||||||
#include <QColor>
|
#include <QColor>
|
||||||
#include <QRegularExpression>
|
#include <QRegularExpression>
|
||||||
|
|
||||||
//#include "KIO/OpenFileManagerWindowJob"
|
|
||||||
|
|
||||||
#include <uuid/uuid.h>
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace Shared {
|
namespace Shared {
|
||||||
@ -72,6 +69,12 @@ static const std::vector<QColor> colorPalette = {
|
|||||||
QColor(17, 17, 80),
|
QColor(17, 17, 80),
|
||||||
QColor(54, 54, 94)
|
QColor(54, 54, 94)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum class Hover {
|
||||||
|
nothing,
|
||||||
|
text,
|
||||||
|
anchor
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // SHARED_UTILS_H
|
#endif // SHARED_UTILS_H
|
||||||
|
12
translations/CMakeLists.txt
Normal file
12
translations/CMakeLists.txt
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
find_package(Qt5LinguistTools)
|
||||||
|
|
||||||
|
set(TS_FILES
|
||||||
|
squawk.en.ts
|
||||||
|
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}/macaw.me/squawk/l10n)
|
||||||
|
|
||||||
|
add_dependencies(${CMAKE_PROJECT_NAME} translations)
|
1420
translations/squawk.en.ts
Normal file
1420
translations/squawk.en.ts
Normal file
File diff suppressed because it is too large
Load Diff
1103
translations/squawk.pt_BR.ts
Normal file
1103
translations/squawk.pt_BR.ts
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,108 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<!DOCTYPE TS>
|
<!DOCTYPE TS>
|
||||||
<TS version="2.1" language="ru_RU">
|
<TS version="2.1" language="ru_RU">
|
||||||
|
<context>
|
||||||
|
<name>About</name>
|
||||||
|
<message>
|
||||||
|
<source>About Squawk</source>
|
||||||
|
<translation>О Программе Squawk</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Squawk</source>
|
||||||
|
<translation>Squawk</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>About</source>
|
||||||
|
<translatorcomment>Tab title</translatorcomment>
|
||||||
|
<translation>Общее</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>XMPP (jabber) messenger</source>
|
||||||
|
<translation>XMPP (jabber) мессенджер</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>(c) 2019 - 2022, Yury Gubich</source>
|
||||||
|
<translation>(c) 2019 - 2022, Юрий Губич</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source><a href="https://git.macaw.me/blue/squawk">Project site</a></source>
|
||||||
|
<translation><a href="https://git.macaw.me/blue/squawk">Сайт проекта</a></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source><a href="https://git.macaw.me/blue/squawk/src/branch/master/LICENSE.md">License: GNU General Public License version 3</a></source>
|
||||||
|
<translation><a href="https://git.macaw.me/blue/squawk/src/branch/master/LICENSE.md">Лицензия: GNU General Public License версия 3</a></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Components</source>
|
||||||
|
<translation>Компоненты</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Version</source>
|
||||||
|
<translation>Версия</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>0.0.0</source>
|
||||||
|
<translation>0.0.0</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Report Bugs</source>
|
||||||
|
<translation>Сообщать об ошибках</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Please report any bug you find!
|
||||||
|
To report bugs you can use:</source>
|
||||||
|
<translation>Пожалуйста, сообщайте о любых ошибках!
|
||||||
|
Способы сообщить об ошибках:</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source><a href="https://git.macaw.me/blue/squawk/issues">Project bug tracker</></source>
|
||||||
|
<translation><a href="https://git.macaw.me/blue/squawk/issues">Баг-трекер проекта</></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>XMPP (<a href="xmpp:blue@macaw.me">blue@macaw.me</a>)</source>
|
||||||
|
<translation>XMPP (<a href="xmpp:blue@macaw.me">blue@macaw.me</a>)</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>E-Mail (<a href="mailto:blue@macaw.me">blue@macaw.me</a>)</source>
|
||||||
|
<translation>E-Mail (<a href="mailto:blue@macaw.me">blue@macaw.me</a>)</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Thanks To</source>
|
||||||
|
<translation>Благодарности</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Vae</source>
|
||||||
|
<translation>Vae</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Major refactoring, bug fixes, constructive criticism</source>
|
||||||
|
<translation>Крупный рефакторинг, исправление ошибок, конструктивная критика</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Shunf4</source>
|
||||||
|
<translation>Shunf4</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Major refactoring, bug fixes, build adaptations for Windows and MacOS</source>
|
||||||
|
<translation>Крупный рефакторинг, исправление ошибок, адаптация сборки под Windows and MacOS</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Bruno F. Fontes</source>
|
||||||
|
<translation>Bruno F. Fontes</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Brazilian Portuguese translation</source>
|
||||||
|
<translation>Перевод на Португальский (Бразилия)</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>(built against %1)</source>
|
||||||
|
<translation>(версия при сборке %1)</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>License</source>
|
||||||
|
<translation>Лицензия</translation>
|
||||||
|
</message>
|
||||||
|
</context>
|
||||||
<context>
|
<context>
|
||||||
<name>Account</name>
|
<name>Account</name>
|
||||||
<message>
|
<message>
|
||||||
@ -10,10 +112,12 @@
|
|||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<source>Your account login</source>
|
<source>Your account login</source>
|
||||||
|
<translatorcomment>Tooltip</translatorcomment>
|
||||||
<translation>Имя пользователя Вашей учетной записи</translation>
|
<translation>Имя пользователя Вашей учетной записи</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<source>john_smith1987</source>
|
<source>john_smith1987</source>
|
||||||
|
<translatorcomment>Login placeholder</translatorcomment>
|
||||||
<translation>ivan_ivanov1987</translation>
|
<translation>ivan_ivanov1987</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
@ -22,10 +126,12 @@
|
|||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<source>A server address of your account. Like 404.city or macaw.me</source>
|
<source>A server address of your account. Like 404.city or macaw.me</source>
|
||||||
|
<translatorcomment>Tooltip</translatorcomment>
|
||||||
<translation>Адресс сервера вашей учетной записи (выглядит как 404.city или macaw.me)</translation>
|
<translation>Адресс сервера вашей учетной записи (выглядит как 404.city или macaw.me)</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<source>macaw.me</source>
|
<source>macaw.me</source>
|
||||||
|
<translatorcomment>Placeholder</translatorcomment>
|
||||||
<translation>macaw.me</translation>
|
<translation>macaw.me</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
@ -38,6 +144,7 @@
|
|||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<source>Password of your account</source>
|
<source>Password of your account</source>
|
||||||
|
<translatorcomment>Tooltip</translatorcomment>
|
||||||
<translation>Пароль вашей учетной записи</translation>
|
<translation>Пароль вашей учетной записи</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
@ -46,10 +153,11 @@
|
|||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<source>Just a name how would you call this account, doesn't affect anything</source>
|
<source>Just a name how would you call this account, doesn't affect anything</source>
|
||||||
<translation>Просто имя, то как Вы называете свою учетную запись, может быть любым</translation>
|
<translation>Просто имя, то как Вы называете свою учетную запись, может быть любым (нельзя поменять)</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<source>John</source>
|
<source>John</source>
|
||||||
|
<translatorcomment>Placeholder</translatorcomment>
|
||||||
<translation>Иван</translation>
|
<translation>Иван</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
@ -58,6 +166,7 @@
|
|||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<source>A resource name like "Home" or "Work"</source>
|
<source>A resource name like "Home" or "Work"</source>
|
||||||
|
<translatorcomment>Tooltip</translatorcomment>
|
||||||
<translation>Имя этой программы для ваших контактов, может быть "Home" или "Phone"</translation>
|
<translation>Имя этой программы для ваших контактов, может быть "Home" или "Phone"</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
@ -69,6 +178,14 @@
|
|||||||
<source>Password storage</source>
|
<source>Password storage</source>
|
||||||
<translation>Хранение пароля</translation>
|
<translation>Хранение пароля</translation>
|
||||||
</message>
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Active</source>
|
||||||
|
<translation>Активен</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>enable</source>
|
||||||
|
<translation>включен</translation>
|
||||||
|
</message>
|
||||||
</context>
|
</context>
|
||||||
<context>
|
<context>
|
||||||
<name>Accounts</name>
|
<name>Accounts</name>
|
||||||
@ -98,30 +215,139 @@
|
|||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<source>Disconnect</source>
|
<source>Disconnect</source>
|
||||||
<translation>Отключить</translation>
|
<translation type="vanished">Отключить</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Deactivate</source>
|
||||||
|
<translation>Деактивировать</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Activate</source>
|
||||||
|
<translation>Активировать</translation>
|
||||||
|
</message>
|
||||||
|
</context>
|
||||||
|
<context>
|
||||||
|
<name>Application</name>
|
||||||
|
<message>
|
||||||
|
<source> from </source>
|
||||||
|
<translation> от </translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Attached file</source>
|
||||||
|
<translation>Прикрепленный файл</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Mark as Read</source>
|
||||||
|
<translation>Пометить прочитанным</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Open conversation</source>
|
||||||
|
<translation>Открыть окно беседы</translation>
|
||||||
</message>
|
</message>
|
||||||
</context>
|
</context>
|
||||||
<context>
|
<context>
|
||||||
<name>Conversation</name>
|
<name>Conversation</name>
|
||||||
<message>
|
<message>
|
||||||
<source>Type your message here...</source>
|
<source>Type your message here...</source>
|
||||||
|
<translatorcomment>Placeholder</translatorcomment>
|
||||||
<translation>Введите сообщение...</translation>
|
<translation>Введите сообщение...</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<source>Chose a file to send</source>
|
<source>Chose a file to send</source>
|
||||||
<translation>Выберите файл для отправки</translation>
|
<translation>Выберите файл для отправки</translation>
|
||||||
</message>
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Drop files here to attach them to your message</source>
|
||||||
|
<translation>Бросьте файлы сюда для того что бы прикрепить их к сообщению</translation>
|
||||||
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<source><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
|
<source><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
|
||||||
<html><head><meta name="qrichtext" content="1" /><style type="text/css">
|
<html><head><meta name="qrichtext" content="1" /><style type="text/css">
|
||||||
p, li { white-space: pre-wrap; }
|
p, li { white-space: pre-wrap; }
|
||||||
</style></head><body style=" font-family:'Liberation Sans'; font-size:10pt; font-weight:400; font-style:normal;">
|
</style></head><body style=" font-family:'Noto Sans'; font-size:8pt; font-weight:400; font-style:normal;">
|
||||||
<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html></source>
|
<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html></source>
|
||||||
<translation></translation>
|
<translation><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
|
||||||
|
<html><head><meta name="qrichtext" content="1" /><style type="text/css">
|
||||||
|
p, li { white-space: pre-wrap; }
|
||||||
|
</style></head><body style=" font-family:'Noto Sans'; font-size:8pt; font-weight:400; font-style:normal;">
|
||||||
|
<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html></translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<source>Drop files here to attach them to your message</source>
|
<source>Paste Image</source>
|
||||||
<translation>Бросьте файлы сюда для того что бы прикрепить их к сообщению</translation>
|
<translation>Вставить изображение</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Try sending again</source>
|
||||||
|
<translation>Отправить снова</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Copy selected</source>
|
||||||
|
<translation>Скопировать выделенное</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Copy message</source>
|
||||||
|
<translation>Скопировать сообщение</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Open</source>
|
||||||
|
<translation>Открыть</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Show in folder</source>
|
||||||
|
<translation>Показать в проводнике</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Edit</source>
|
||||||
|
<translation>Редактировать</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Editing message...</source>
|
||||||
|
<translation>Сообщение редактируется...</translation>
|
||||||
|
</message>
|
||||||
|
</context>
|
||||||
|
<context>
|
||||||
|
<name>CredentialsPrompt</name>
|
||||||
|
<message>
|
||||||
|
<source>Authentication error: %1</source>
|
||||||
|
<translatorcomment>Window title</translatorcomment>
|
||||||
|
<translation>Ошибка аутентификации: %1</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Couldn't authenticate account %1: login or password is icorrect.
|
||||||
|
Would you like to check them and try again?</source>
|
||||||
|
<translation>Не получилось аутентифицировать
|
||||||
|
учетную запись %1:
|
||||||
|
имя пользователя или пароль введены неверно.
|
||||||
|
Желаете ли проверить их и
|
||||||
|
попробовать аутентифицироваться еще раз?</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Login</source>
|
||||||
|
<translation>Имя учетной записи</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Your account login (without @server.domain)</source>
|
||||||
|
<translatorcomment>Tooltip</translatorcomment>
|
||||||
|
<translation>Имя вашей учтетной записи (без @server.domain)</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Password</source>
|
||||||
|
<translation>Пароль</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Your password</source>
|
||||||
|
<translation>Ваш пароль</translation>
|
||||||
|
</message>
|
||||||
|
</context>
|
||||||
|
<context>
|
||||||
|
<name>DialogQueue</name>
|
||||||
|
<message>
|
||||||
|
<source>Input the password for account %1</source>
|
||||||
|
<translation>Введите пароль для учетной записи %1</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Password for account %1</source>
|
||||||
|
<translation>Пароль для учетной записи %1</translation>
|
||||||
</message>
|
</message>
|
||||||
</context>
|
</context>
|
||||||
<context>
|
<context>
|
||||||
@ -370,14 +596,14 @@ p, li { white-space: pre-wrap; }
|
|||||||
<name>Message</name>
|
<name>Message</name>
|
||||||
<message>
|
<message>
|
||||||
<source>Open</source>
|
<source>Open</source>
|
||||||
<translation>Открыть</translation>
|
<translation type="vanished">Открыть</translation>
|
||||||
</message>
|
</message>
|
||||||
</context>
|
</context>
|
||||||
<context>
|
<context>
|
||||||
<name>MessageLine</name>
|
<name>MessageLine</name>
|
||||||
<message>
|
<message>
|
||||||
<source>Downloading...</source>
|
<source>Downloading...</source>
|
||||||
<translation>Скачивается...</translation>
|
<translation type="vanished">Скачивается...</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<source>Download</source>
|
<source>Download</source>
|
||||||
@ -386,28 +612,28 @@ p, li { white-space: pre-wrap; }
|
|||||||
<message>
|
<message>
|
||||||
<source>Error uploading file: %1
|
<source>Error uploading file: %1
|
||||||
You can try again</source>
|
You can try again</source>
|
||||||
<translation>Ошибка загрузки файла на сервер:
|
<translation type="vanished">Ошибка загрузки файла на сервер:
|
||||||
%1
|
%1
|
||||||
Для того, что бы попробовать снова нажмите на кнопку</translation>
|
Для того, что бы попробовать снова нажмите на кнопку</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<source>Upload</source>
|
<source>Upload</source>
|
||||||
<translation>Загрузить</translation>
|
<translation type="vanished">Загрузить</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<source>Error downloading file: %1
|
<source>Error downloading file: %1
|
||||||
You can try again</source>
|
You can try again</source>
|
||||||
<translation>Ошибка скачивания файла:
|
<translation type="vanished">Ошибка скачивания файла:
|
||||||
%1
|
%1
|
||||||
Вы можете попробовать снова</translation>
|
Вы можете попробовать снова</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<source>Uploading...</source>
|
<source>Uploading...</source>
|
||||||
<translation>Загружается...</translation>
|
<translation type="vanished">Загружается...</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<source>Push the button to download the file</source>
|
<source>Push the button to download the file</source>
|
||||||
<translation>Нажмите на кнопку что бы загрузить файл</translation>
|
<translation type="vanished">Нажмите на кнопку что бы загрузить файл</translation>
|
||||||
</message>
|
</message>
|
||||||
</context>
|
</context>
|
||||||
<context>
|
<context>
|
||||||
@ -481,7 +707,7 @@ You can try again</source>
|
|||||||
<name>NewContact</name>
|
<name>NewContact</name>
|
||||||
<message>
|
<message>
|
||||||
<source>Add new contact</source>
|
<source>Add new contact</source>
|
||||||
<translatorcomment>Заголовок окна</translatorcomment>
|
<translatorcomment>Window title</translatorcomment>
|
||||||
<translation>Добавление нового контакта</translation>
|
<translation>Добавление нового контакта</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
@ -502,7 +728,7 @@ You can try again</source>
|
|||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<source>name@server.dmn</source>
|
<source>name@server.dmn</source>
|
||||||
<translatorcomment>Placeholder поля ввода JID</translatorcomment>
|
<translatorcomment>Placeholder</translatorcomment>
|
||||||
<translation>name@server.dmn</translation>
|
<translation>name@server.dmn</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
@ -518,6 +744,64 @@ You can try again</source>
|
|||||||
<translation>Иван Иванов</translation>
|
<translation>Иван Иванов</translation>
|
||||||
</message>
|
</message>
|
||||||
</context>
|
</context>
|
||||||
|
<context>
|
||||||
|
<name>PageAppearance</name>
|
||||||
|
<message>
|
||||||
|
<source>Theme</source>
|
||||||
|
<translation>Оформление</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Color scheme</source>
|
||||||
|
<translation>Цветовая схема</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>System</source>
|
||||||
|
<translation>Системная</translation>
|
||||||
|
</message>
|
||||||
|
</context>
|
||||||
|
<context>
|
||||||
|
<name>PageGeneral</name>
|
||||||
|
<message>
|
||||||
|
<source>Downloads path</source>
|
||||||
|
<translation>Папка для сохраненных файлов</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Browse</source>
|
||||||
|
<translation>Выбрать</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Select where downloads folder is going to be</source>
|
||||||
|
<translation>Выберете папку, в которую будут сохраняться файлы</translation>
|
||||||
|
</message>
|
||||||
|
</context>
|
||||||
|
<context>
|
||||||
|
<name>Settings</name>
|
||||||
|
<message>
|
||||||
|
<source>Preferences</source>
|
||||||
|
<translatorcomment>Window title</translatorcomment>
|
||||||
|
<translation>Настройки</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>General</source>
|
||||||
|
<translation>Общее</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Appearance</source>
|
||||||
|
<translation>Внешний вид</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Apply</source>
|
||||||
|
<translation>Применить</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Cancel</source>
|
||||||
|
<translation>Отменить</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Ok</source>
|
||||||
|
<translation>Готово</translation>
|
||||||
|
</message>
|
||||||
|
</context>
|
||||||
<context>
|
<context>
|
||||||
<name>Squawk</name>
|
<name>Squawk</name>
|
||||||
<message>
|
<message>
|
||||||
@ -530,6 +814,7 @@ You can try again</source>
|
|||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<source>Squawk</source>
|
<source>Squawk</source>
|
||||||
|
<translatorcomment>Menu bar entry</translatorcomment>
|
||||||
<translation>Squawk</translation>
|
<translation>Squawk</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
@ -550,11 +835,11 @@ You can try again</source>
|
|||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<source>Disconnect</source>
|
<source>Disconnect</source>
|
||||||
<translation>Отключить</translation>
|
<translation type="vanished">Отключить</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<source>Connect</source>
|
<source>Connect</source>
|
||||||
<translation>Подключить</translation>
|
<translation type="vanished">Подключить</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<source>VCard</source>
|
<source>VCard</source>
|
||||||
@ -627,20 +912,40 @@ to be displayed as %1</source>
|
|||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<source>Attached file</source>
|
<source>Attached file</source>
|
||||||
<translation>Прикрепленный файл</translation>
|
<translation type="vanished">Прикрепленный файл</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<source>Input the password for account %1</source>
|
<source>Input the password for account %1</source>
|
||||||
<translation>Введите пароль для учетной записи %1</translation>
|
<translation type="vanished">Введите пароль для учетной записи %1</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<source>Password for account %1</source>
|
<source>Password for account %1</source>
|
||||||
<translation>Пароль для учетной записи %1</translation>
|
<translation type="vanished">Пароль для учетной записи %1</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<source>Please select a contact to start chatting</source>
|
<source>Please select a contact to start chatting</source>
|
||||||
<translation>Выберите контакт или группу что бы начать переписку</translation>
|
<translation>Выберите контакт или группу что бы начать переписку</translation>
|
||||||
</message>
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Help</source>
|
||||||
|
<translation>Помощь</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Preferences</source>
|
||||||
|
<translation>Настройки</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>About Squawk</source>
|
||||||
|
<translation>О Программе Squawk</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Deactivate</source>
|
||||||
|
<translation>Деактивировать</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Activate</source>
|
||||||
|
<translation>Активировать</translation>
|
||||||
|
</message>
|
||||||
</context>
|
</context>
|
||||||
<context>
|
<context>
|
||||||
<name>VCard</name>
|
<name>VCard</name>
|
||||||
|
@ -1,43 +1,9 @@
|
|||||||
cmake_minimum_required(VERSION 3.3)
|
target_sources(squawk PRIVATE
|
||||||
project(squawkUI)
|
|
||||||
|
|
||||||
# Instruct CMake to run moc automatically when needed.
|
|
||||||
set(CMAKE_AUTOMOC ON)
|
|
||||||
# Instruct CMake to create code from Qt designer ui files
|
|
||||||
set(CMAKE_AUTOUIC ON)
|
|
||||||
|
|
||||||
# Find the QtWidgets library
|
|
||||||
find_package(Qt5 CONFIG REQUIRED COMPONENTS Widgets DBus Core)
|
|
||||||
find_package(Boost 1.36.0 REQUIRED)
|
|
||||||
if(Boost_FOUND)
|
|
||||||
include_directories(${Boost_INCLUDE_DIRS})
|
|
||||||
endif()
|
|
||||||
|
|
||||||
add_subdirectory(utils)
|
|
||||||
add_subdirectory(widgets)
|
|
||||||
|
|
||||||
set(squawkUI_SRC
|
|
||||||
squawk.cpp
|
squawk.cpp
|
||||||
models/accounts.cpp
|
squawk.h
|
||||||
models/roster.cpp
|
squawk.ui
|
||||||
models/item.cpp
|
|
||||||
models/account.cpp
|
|
||||||
models/contact.cpp
|
|
||||||
models/presence.cpp
|
|
||||||
models/group.cpp
|
|
||||||
models/room.cpp
|
|
||||||
models/abstractparticipant.cpp
|
|
||||||
models/participant.cpp
|
|
||||||
models/reference.cpp
|
|
||||||
models/messagefeed.cpp
|
|
||||||
models/element.cpp
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Tell CMake to create the helloworld executable
|
add_subdirectory(models)
|
||||||
add_library(squawkUI STATIC ${squawkUI_SRC})
|
add_subdirectory(utils)
|
||||||
|
add_subdirectory(widgets)
|
||||||
# Use the Widgets module from Qt 5.
|
|
||||||
target_link_libraries(squawkUI squawkWidgets)
|
|
||||||
target_link_libraries(squawkUI squawkUIUtils)
|
|
||||||
target_link_libraries(squawkUI Qt5::Widgets)
|
|
||||||
target_link_libraries(squawkUI Qt5::DBus)
|
|
||||||
|
26
ui/models/CMakeLists.txt
Normal file
26
ui/models/CMakeLists.txt
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
target_sources(squawk PRIVATE
|
||||||
|
abstractparticipant.cpp
|
||||||
|
abstractparticipant.h
|
||||||
|
account.cpp
|
||||||
|
account.h
|
||||||
|
accounts.cpp
|
||||||
|
accounts.h
|
||||||
|
contact.cpp
|
||||||
|
contact.h
|
||||||
|
element.cpp
|
||||||
|
element.h
|
||||||
|
group.cpp
|
||||||
|
group.h
|
||||||
|
item.cpp
|
||||||
|
item.h
|
||||||
|
participant.cpp
|
||||||
|
participant.h
|
||||||
|
presence.cpp
|
||||||
|
presence.h
|
||||||
|
reference.cpp
|
||||||
|
reference.h
|
||||||
|
room.cpp
|
||||||
|
room.h
|
||||||
|
roster.cpp
|
||||||
|
roster.h
|
||||||
|
)
|
@ -31,7 +31,9 @@ Models::Account::Account(const QMap<QString, QVariant>& data, Models::Item* pare
|
|||||||
avatarPath(data.value("avatarPath").toString()),
|
avatarPath(data.value("avatarPath").toString()),
|
||||||
state(Shared::ConnectionState::disconnected),
|
state(Shared::ConnectionState::disconnected),
|
||||||
availability(Shared::Availability::offline),
|
availability(Shared::Availability::offline),
|
||||||
passwordType(Shared::AccountPassword::plain)
|
passwordType(Shared::AccountPassword::plain),
|
||||||
|
wasEverConnected(false),
|
||||||
|
active(false)
|
||||||
{
|
{
|
||||||
QMap<QString, QVariant>::const_iterator sItr = data.find("state");
|
QMap<QString, QVariant>::const_iterator sItr = data.find("state");
|
||||||
if (sItr != data.end()) {
|
if (sItr != data.end()) {
|
||||||
@ -45,6 +47,10 @@ Models::Account::Account(const QMap<QString, QVariant>& data, Models::Item* pare
|
|||||||
if (pItr != data.end()) {
|
if (pItr != data.end()) {
|
||||||
setPasswordType(pItr.value().toUInt());
|
setPasswordType(pItr.value().toUInt());
|
||||||
}
|
}
|
||||||
|
QMap<QString, QVariant>::const_iterator acItr = data.find("active");
|
||||||
|
if (acItr != data.end()) {
|
||||||
|
setActive(acItr.value().toBool());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Models::Account::~Account()
|
Models::Account::~Account()
|
||||||
@ -56,8 +62,19 @@ void Models::Account::setState(Shared::ConnectionState p_state)
|
|||||||
if (state != p_state) {
|
if (state != p_state) {
|
||||||
state = p_state;
|
state = p_state;
|
||||||
changed(2);
|
changed(2);
|
||||||
if (state == Shared::ConnectionState::disconnected) {
|
switch (state) {
|
||||||
|
case Shared::ConnectionState::disconnected:
|
||||||
toOfflineState();
|
toOfflineState();
|
||||||
|
break;
|
||||||
|
case Shared::ConnectionState::connected:
|
||||||
|
if (wasEverConnected) {
|
||||||
|
emit reconnected();
|
||||||
|
} else {
|
||||||
|
wasEverConnected = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -164,6 +181,8 @@ QVariant Models::Account::data(int column) const
|
|||||||
return avatarPath;
|
return avatarPath;
|
||||||
case 9:
|
case 9:
|
||||||
return Shared::Global::getName(passwordType);
|
return Shared::Global::getName(passwordType);
|
||||||
|
case 10:
|
||||||
|
return active;
|
||||||
default:
|
default:
|
||||||
return QVariant();
|
return QVariant();
|
||||||
}
|
}
|
||||||
@ -171,7 +190,7 @@ QVariant Models::Account::data(int column) const
|
|||||||
|
|
||||||
int Models::Account::columnCount() const
|
int Models::Account::columnCount() const
|
||||||
{
|
{
|
||||||
return 10;
|
return 11;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Models::Account::update(const QString& field, const QVariant& value)
|
void Models::Account::update(const QString& field, const QVariant& value)
|
||||||
@ -196,6 +215,8 @@ void Models::Account::update(const QString& field, const QVariant& value)
|
|||||||
setAvatarPath(value.toString());
|
setAvatarPath(value.toString());
|
||||||
} else if (field == "passwordType") {
|
} else if (field == "passwordType") {
|
||||||
setPasswordType(value.toUInt());
|
setPasswordType(value.toUInt());
|
||||||
|
} else if (field == "active") {
|
||||||
|
setActive(value.toBool());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -269,3 +290,16 @@ void Models::Account::setPasswordType(unsigned int pt)
|
|||||||
{
|
{
|
||||||
setPasswordType(Shared::Global::fromInt<Shared::AccountPassword>(pt));
|
setPasswordType(Shared::Global::fromInt<Shared::AccountPassword>(pt));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Models::Account::getActive() const
|
||||||
|
{
|
||||||
|
return active;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Models::Account::setActive(bool p_active)
|
||||||
|
{
|
||||||
|
if (active != p_active) {
|
||||||
|
active = p_active;
|
||||||
|
changed(10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -59,6 +59,9 @@ namespace Models {
|
|||||||
void setAvatarPath(const QString& path);
|
void setAvatarPath(const QString& path);
|
||||||
QString getAvatarPath() const;
|
QString getAvatarPath() const;
|
||||||
|
|
||||||
|
void setActive(bool active);
|
||||||
|
bool getActive() const;
|
||||||
|
|
||||||
void setAvailability(Shared::Availability p_avail);
|
void setAvailability(Shared::Availability p_avail);
|
||||||
void setAvailability(unsigned int p_avail);
|
void setAvailability(unsigned int p_avail);
|
||||||
Shared::Availability getAvailability() const;
|
Shared::Availability getAvailability() const;
|
||||||
@ -77,6 +80,9 @@ namespace Models {
|
|||||||
QString getBareJid() const;
|
QString getBareJid() const;
|
||||||
QString getFullJid() const;
|
QString getFullJid() const;
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void reconnected();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QString login;
|
QString login;
|
||||||
QString password;
|
QString password;
|
||||||
@ -87,6 +93,8 @@ namespace Models {
|
|||||||
Shared::ConnectionState state;
|
Shared::ConnectionState state;
|
||||||
Shared::Availability availability;
|
Shared::Availability availability;
|
||||||
Shared::AccountPassword passwordType;
|
Shared::AccountPassword passwordType;
|
||||||
|
bool wasEverConnected;
|
||||||
|
bool active;
|
||||||
|
|
||||||
protected slots:
|
protected slots:
|
||||||
void toOfflineState() override;
|
void toOfflineState() override;
|
||||||
|
@ -48,6 +48,10 @@ QVariant Models::Accounts::data (const QModelIndex& index, int role) const
|
|||||||
answer = Shared::connectionStateIcon(accs[index.row()]->getState());
|
answer = Shared::connectionStateIcon(accs[index.row()]->getState());
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case Qt::ForegroundRole:
|
||||||
|
if (!accs[index.row()]->getActive()) {
|
||||||
|
answer = qApp->palette().brush(QPalette::Disabled, QPalette::Text);
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -97,7 +101,10 @@ void Models::Accounts::addAccount(Account* account)
|
|||||||
|
|
||||||
void Models::Accounts::onAccountChanged(Item* item, int row, int col)
|
void Models::Accounts::onAccountChanged(Item* item, int row, int col)
|
||||||
{
|
{
|
||||||
if (row < accs.size()) {
|
if (row < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (static_cast<std::deque<Models::Account*>::size_type>(row) < accs.size()) {
|
||||||
Account* acc = getAccount(row);
|
Account* acc = getAccount(row);
|
||||||
if (item != acc) {
|
if (item != acc) {
|
||||||
return; //it means the signal is emitted by one of accounts' children, not exactly him, this model has no interest in that
|
return; //it means the signal is emitted by one of accounts' children, not exactly him, this model has no interest in that
|
||||||
|
@ -155,6 +155,16 @@ void Models::Contact::removePresence(const QString& name)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Models::Presence * Models::Contact::getPresence(const QString& name)
|
||||||
|
{
|
||||||
|
QMap<QString, Presence*>::iterator itr = presences.find(name);
|
||||||
|
if (itr == presences.end()) {
|
||||||
|
return nullptr;
|
||||||
|
} else {
|
||||||
|
return itr.value();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Models::Contact::refresh()
|
void Models::Contact::refresh()
|
||||||
{
|
{
|
||||||
QDateTime lastActivity;
|
QDateTime lastActivity;
|
||||||
@ -240,3 +250,9 @@ QString Models::Contact::getDisplayedName() const
|
|||||||
return getContactName();
|
return getContactName();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Models::Contact::handleRecconnect()
|
||||||
|
{
|
||||||
|
if (getMessagesCount() > 0) {
|
||||||
|
feed->requestLatestMessages();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -51,11 +51,14 @@ public:
|
|||||||
|
|
||||||
void addPresence(const QString& name, const QMap<QString, QVariant>& data);
|
void addPresence(const QString& name, const QMap<QString, QVariant>& data);
|
||||||
void removePresence(const QString& name);
|
void removePresence(const QString& name);
|
||||||
|
Presence* getPresence(const QString& name);
|
||||||
|
|
||||||
QString getContactName() const;
|
QString getContactName() const;
|
||||||
QString getStatus() const;
|
QString getStatus() const;
|
||||||
QString getDisplayedName() const override;
|
QString getDisplayedName() const override;
|
||||||
|
|
||||||
|
void handleRecconnect(); //this is a special method Models::Roster calls when reconnect happens
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void _removeChild(int index) override;
|
void _removeChild(int index) override;
|
||||||
void _appendChild(Models::Item * child) override;
|
void _appendChild(Models::Item * child) override;
|
||||||
|
@ -134,6 +134,11 @@ unsigned int Models::Element::getMessagesCount() const
|
|||||||
return feed->unreadMessagesCount();
|
return feed->unreadMessagesCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Models::Element::markMessageAsRead(const QString& id) const
|
||||||
|
{
|
||||||
|
return feed->markMessageAsRead(id);
|
||||||
|
}
|
||||||
|
|
||||||
void Models::Element::addMessage(const Shared::Message& data)
|
void Models::Element::addMessage(const Shared::Message& data)
|
||||||
{
|
{
|
||||||
feed->addMessage(data);
|
feed->addMessage(data);
|
||||||
@ -171,6 +176,7 @@ void Models::Element::fileError(const QString& messageId, const QString& error,
|
|||||||
|
|
||||||
void Models::Element::onFeedUnreadMessagesCountChanged()
|
void Models::Element::onFeedUnreadMessagesCountChanged()
|
||||||
{
|
{
|
||||||
|
emit unreadMessagesCountChanged();
|
||||||
if (type == contact) {
|
if (type == contact) {
|
||||||
changed(4);
|
changed(4);
|
||||||
} else if (type == room) {
|
} else if (type == room) {
|
||||||
|
@ -20,7 +20,8 @@
|
|||||||
#define ELEMENT_H
|
#define ELEMENT_H
|
||||||
|
|
||||||
#include "item.h"
|
#include "item.h"
|
||||||
#include "messagefeed.h"
|
|
||||||
|
#include "ui/widgets/messageline/messagefeed.h"
|
||||||
|
|
||||||
namespace Models {
|
namespace Models {
|
||||||
|
|
||||||
@ -41,6 +42,7 @@ public:
|
|||||||
void addMessage(const Shared::Message& data);
|
void addMessage(const Shared::Message& data);
|
||||||
void changeMessage(const QString& id, const QMap<QString, QVariant>& data);
|
void changeMessage(const QString& id, const QMap<QString, QVariant>& data);
|
||||||
unsigned int getMessagesCount() const;
|
unsigned int getMessagesCount() const;
|
||||||
|
bool markMessageAsRead(const QString& id) const;
|
||||||
void responseArchive(const std::list<Shared::Message> list, bool last);
|
void responseArchive(const std::list<Shared::Message> list, bool last);
|
||||||
bool isRoom() const;
|
bool isRoom() const;
|
||||||
void fileProgress(const QString& messageId, qreal value, bool up);
|
void fileProgress(const QString& messageId, qreal value, bool up);
|
||||||
@ -51,6 +53,7 @@ signals:
|
|||||||
void requestArchive(const QString& before);
|
void requestArchive(const QString& before);
|
||||||
void fileDownloadRequest(const QString& url);
|
void fileDownloadRequest(const QString& url);
|
||||||
void unnoticedMessage(const QString& account, const Shared::Message& msg);
|
void unnoticedMessage(const QString& account, const Shared::Message& msg);
|
||||||
|
void unreadMessagesCountChanged();
|
||||||
void localPathInvalid(const QString& path);
|
void localPathInvalid(const QString& path);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
@ -104,6 +104,8 @@ void Models::Reference::onChildChanged(Models::Item* item, int row, int col)
|
|||||||
{
|
{
|
||||||
if (item == original) {
|
if (item == original) {
|
||||||
emit childChanged(this, row, col);
|
emit childChanged(this, row, col);
|
||||||
|
} else {
|
||||||
|
emit childChanged(item, row, col);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -264,6 +264,16 @@ void Models::Room::removeParticipant(const QString& p_name)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Models::Participant * Models::Room::getParticipant(const QString& p_name)
|
||||||
|
{
|
||||||
|
std::map<QString, Participant*>::const_iterator itr = participants.find(p_name);
|
||||||
|
if (itr == participants.end()) {
|
||||||
|
return nullptr;
|
||||||
|
} else {
|
||||||
|
return itr->second;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Models::Room::handleParticipantUpdate(std::map<QString, Participant*>::const_iterator itr, const QMap<QString, QVariant>& data)
|
void Models::Room::handleParticipantUpdate(std::map<QString, Participant*>::const_iterator itr, const QMap<QString, QVariant>& data)
|
||||||
{
|
{
|
||||||
Participant* part = itr->second;
|
Participant* part = itr->second;
|
||||||
|
@ -58,6 +58,7 @@ public:
|
|||||||
void addParticipant(const QString& name, const QMap<QString, QVariant>& data);
|
void addParticipant(const QString& name, const QMap<QString, QVariant>& data);
|
||||||
void changeParticipant(const QString& name, const QMap<QString, QVariant>& data);
|
void changeParticipant(const QString& name, const QMap<QString, QVariant>& data);
|
||||||
void removeParticipant(const QString& name);
|
void removeParticipant(const QString& name);
|
||||||
|
Participant* getParticipant(const QString& name);
|
||||||
|
|
||||||
void toOfflineState() override;
|
void toOfflineState() override;
|
||||||
QString getDisplayedName() const override;
|
QString getDisplayedName() const override;
|
||||||
|
@ -48,6 +48,7 @@ Models::Roster::~Roster()
|
|||||||
void Models::Roster::addAccount(const QMap<QString, QVariant>& data)
|
void Models::Roster::addAccount(const QMap<QString, QVariant>& data)
|
||||||
{
|
{
|
||||||
Account* acc = new Account(data);
|
Account* acc = new Account(data);
|
||||||
|
connect(acc, &Account::reconnected, this, &Roster::onAccountReconnected);
|
||||||
root->appendChild(acc);
|
root->appendChild(acc);
|
||||||
accounts.insert(std::make_pair(acc->getName(), acc));
|
accounts.insert(std::make_pair(acc->getName(), acc));
|
||||||
accountsModel->addAccount(acc);
|
accountsModel->addAccount(acc);
|
||||||
@ -275,6 +276,18 @@ QVariant Models::Roster::data (const QModelIndex& index, int role) const
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case Qt::ForegroundRole:
|
||||||
|
switch (item->type) {
|
||||||
|
case Item::account: {
|
||||||
|
Account* acc = static_cast<Account*>(item);
|
||||||
|
if (!acc->getActive()) {
|
||||||
|
result = qApp->palette().brush(QPalette::Disabled, QPalette::Text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -450,6 +463,7 @@ void Models::Roster::addContact(const QString& account, const QString& jid, cons
|
|||||||
connect(contact, &Contact::fileDownloadRequest, this, &Roster::fileDownloadRequest);
|
connect(contact, &Contact::fileDownloadRequest, this, &Roster::fileDownloadRequest);
|
||||||
connect(contact, &Contact::unnoticedMessage, this, &Roster::unnoticedMessage);
|
connect(contact, &Contact::unnoticedMessage, this, &Roster::unnoticedMessage);
|
||||||
connect(contact, &Contact::localPathInvalid, this, &Roster::localPathInvalid);
|
connect(contact, &Contact::localPathInvalid, this, &Roster::localPathInvalid);
|
||||||
|
connect(contact, &Contact::unreadMessagesCountChanged, this, &Roster::recalculateUnreadMessages);
|
||||||
contacts.insert(std::make_pair(id, contact));
|
contacts.insert(std::make_pair(id, contact));
|
||||||
} else {
|
} else {
|
||||||
contact = itr->second;
|
contact = itr->second;
|
||||||
@ -535,8 +549,8 @@ void Models::Roster::removeGroup(const QString& account, const QString& name)
|
|||||||
|
|
||||||
void Models::Roster::changeContact(const QString& account, const QString& jid, const QMap<QString, QVariant>& data)
|
void Models::Roster::changeContact(const QString& account, const QString& jid, const QMap<QString, QVariant>& data)
|
||||||
{
|
{
|
||||||
Element* el = getElement({account, jid});
|
Element* el = getElement(ElId(account, jid));
|
||||||
if (el != NULL) {
|
if (el != nullptr) {
|
||||||
for (QMap<QString, QVariant>::const_iterator itr = data.begin(), end = data.end(); itr != end; ++itr) {
|
for (QMap<QString, QVariant>::const_iterator itr = data.begin(), end = data.end(); itr != end; ++itr) {
|
||||||
el->update(itr.key(), itr.value());
|
el->update(itr.key(), itr.value());
|
||||||
}
|
}
|
||||||
@ -545,9 +559,11 @@ void Models::Roster::changeContact(const QString& account, const QString& jid, c
|
|||||||
|
|
||||||
void Models::Roster::changeMessage(const QString& account, const QString& jid, const QString& id, const QMap<QString, QVariant>& data)
|
void Models::Roster::changeMessage(const QString& account, const QString& jid, const QString& id, const QMap<QString, QVariant>& data)
|
||||||
{
|
{
|
||||||
Element* el = getElement({account, jid});
|
Element* el = getElement(ElId(account, jid));
|
||||||
if (el != NULL) {
|
if (el != nullptr) {
|
||||||
el->changeMessage(id, data);
|
el->changeMessage(id, data);
|
||||||
|
} else {
|
||||||
|
qDebug() << "A request to change a message of the contact " << jid << " in the account " << account << " but it wasn't found";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -691,8 +707,8 @@ void Models::Roster::removePresence(const QString& account, const QString& jid,
|
|||||||
|
|
||||||
void Models::Roster::addMessage(const QString& account, const Shared::Message& data)
|
void Models::Roster::addMessage(const QString& account, const Shared::Message& data)
|
||||||
{
|
{
|
||||||
Element* el = getElement({account, data.getPenPalJid()});
|
Element* el = getElement(ElId(account, data.getPenPalJid()));
|
||||||
if (el != NULL) {
|
if (el != nullptr) {
|
||||||
el->addMessage(data);
|
el->addMessage(data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -744,10 +760,11 @@ void Models::Roster::removeAccount(const QString& account)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
disconnect(acc, &Account::reconnected, this, &Roster::onAccountReconnected);
|
||||||
acc->deleteLater();
|
acc->deleteLater();
|
||||||
}
|
}
|
||||||
|
|
||||||
QString Models::Roster::getContactName(const QString& account, const QString& jid)
|
QString Models::Roster::getContactName(const QString& account, const QString& jid) const
|
||||||
{
|
{
|
||||||
ElId id(account, jid);
|
ElId id(account, jid);
|
||||||
std::map<ElId, Contact*>::const_iterator cItr = contacts.find(id);
|
std::map<ElId, Contact*>::const_iterator cItr = contacts.find(id);
|
||||||
@ -785,10 +802,11 @@ void Models::Roster::addRoom(const QString& account, const QString jid, const QM
|
|||||||
}
|
}
|
||||||
|
|
||||||
Room* room = new Room(acc, jid, data);
|
Room* room = new Room(acc, jid, data);
|
||||||
connect(room, &Contact::requestArchive, this, &Roster::onElementRequestArchive);
|
connect(room, &Room::requestArchive, this, &Roster::onElementRequestArchive);
|
||||||
connect(room, &Contact::fileDownloadRequest, this, &Roster::fileDownloadRequest);
|
connect(room, &Room::fileDownloadRequest, this, &Roster::fileDownloadRequest);
|
||||||
connect(room, &Contact::unnoticedMessage, this, &Roster::unnoticedMessage);
|
connect(room, &Room::unnoticedMessage, this, &Roster::unnoticedMessage);
|
||||||
connect(room, &Contact::localPathInvalid, this, &Roster::localPathInvalid);
|
connect(room, &Room::localPathInvalid, this, &Roster::localPathInvalid);
|
||||||
|
connect(room, &Room::unreadMessagesCountChanged, this, &Roster::recalculateUnreadMessages);
|
||||||
rooms.insert(std::make_pair(id, room));
|
rooms.insert(std::make_pair(id, room));
|
||||||
acc->appendChild(room);
|
acc->appendChild(room);
|
||||||
}
|
}
|
||||||
@ -891,7 +909,7 @@ bool Models::Roster::groupHasContact(const QString& account, const QString& grou
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QString Models::Roster::getContactIconPath(const QString& account, const QString& jid, const QString& resource)
|
QString Models::Roster::getContactIconPath(const QString& account, const QString& jid, const QString& resource) const
|
||||||
{
|
{
|
||||||
ElId id(account, jid);
|
ElId id(account, jid);
|
||||||
std::map<ElId, Contact*>::const_iterator cItr = contacts.find(id);
|
std::map<ElId, Contact*>::const_iterator cItr = contacts.find(id);
|
||||||
@ -911,9 +929,36 @@ QString Models::Roster::getContactIconPath(const QString& account, const QString
|
|||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
Models::Account * Models::Roster::getAccount(const QString& name)
|
Models::Account * Models::Roster::getAccount(const QString& name) {
|
||||||
|
return const_cast<Models::Account*>(getAccountConst(name));}
|
||||||
|
|
||||||
|
const Models::Account * Models::Roster::getAccountConst(const QString& name) const {
|
||||||
|
return accounts.at(name);}
|
||||||
|
|
||||||
|
const Models::Element * Models::Roster::getElementConst(const Models::Roster::ElId& id) const
|
||||||
{
|
{
|
||||||
return accounts.find(name)->second;
|
std::map<ElId, Contact*>::const_iterator cItr = contacts.find(id);
|
||||||
|
|
||||||
|
if (cItr != contacts.end()) {
|
||||||
|
return cItr->second;
|
||||||
|
} else {
|
||||||
|
std::map<ElId, Room*>::const_iterator rItr = rooms.find(id);
|
||||||
|
if (rItr != rooms.end()) {
|
||||||
|
return rItr->second;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Models::Roster::markMessageAsRead(const Models::Roster::ElId& elementId, const QString& messageId)
|
||||||
|
{
|
||||||
|
const Element* el = getElementConst(elementId);
|
||||||
|
if (el != nullptr) {
|
||||||
|
return el->markMessageAsRead(messageId);
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QModelIndex Models::Roster::getAccountIndex(const QString& name)
|
QModelIndex Models::Roster::getAccountIndex(const QString& name)
|
||||||
@ -932,7 +977,7 @@ QModelIndex Models::Roster::getGroupIndex(const QString& account, const QString&
|
|||||||
if (itr == accounts.end()) {
|
if (itr == accounts.end()) {
|
||||||
return QModelIndex();
|
return QModelIndex();
|
||||||
} else {
|
} else {
|
||||||
std::map<ElId, Group*>::const_iterator gItr = groups.find({account, name});
|
std::map<ElId, Group*>::const_iterator gItr = groups.find(ElId(account, name));
|
||||||
if (gItr == groups.end()) {
|
if (gItr == groups.end()) {
|
||||||
return QModelIndex();
|
return QModelIndex();
|
||||||
} else {
|
} else {
|
||||||
@ -942,6 +987,48 @@ QModelIndex Models::Roster::getGroupIndex(const QString& account, const QString&
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QModelIndex Models::Roster::getContactIndex(const QString& account, const QString& jid, const QString& resource)
|
||||||
|
{
|
||||||
|
std::map<QString, Account*>::const_iterator itr = accounts.find(account);
|
||||||
|
if (itr == accounts.end()) {
|
||||||
|
return QModelIndex();
|
||||||
|
} else {
|
||||||
|
Account* acc = itr->second;
|
||||||
|
QModelIndex accIndex = index(acc->row(), 0, QModelIndex());
|
||||||
|
std::map<ElId, Contact*>::const_iterator cItr = contacts.find(ElId(account, jid));
|
||||||
|
if (cItr != contacts.end()) {
|
||||||
|
QModelIndex contactIndex = index(acc->getContact(jid), 0, accIndex);
|
||||||
|
if (resource.size() == 0) {
|
||||||
|
return contactIndex;
|
||||||
|
} else {
|
||||||
|
Presence* pres = cItr->second->getPresence(resource);
|
||||||
|
if (pres != nullptr) {
|
||||||
|
return index(pres->row(), 0, contactIndex);
|
||||||
|
} else {
|
||||||
|
return contactIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
std::map<ElId, Room*>::const_iterator rItr = rooms.find(ElId(account, jid));
|
||||||
|
if (rItr != rooms.end()) {
|
||||||
|
QModelIndex roomIndex = index(rItr->second->row(), 0, accIndex);
|
||||||
|
if (resource.size() == 0) {
|
||||||
|
return roomIndex;
|
||||||
|
} else {
|
||||||
|
Participant* part = rItr->second->getParticipant(resource);
|
||||||
|
if (part != nullptr) {
|
||||||
|
return index(part->row(), 0, roomIndex);
|
||||||
|
} else {
|
||||||
|
return roomIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return QModelIndex();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Models::Roster::onElementRequestArchive(const QString& before)
|
void Models::Roster::onElementRequestArchive(const QString& before)
|
||||||
{
|
{
|
||||||
Element* el = static_cast<Element*>(sender());
|
Element* el = static_cast<Element*>(sender());
|
||||||
@ -952,7 +1039,7 @@ void Models::Roster::responseArchive(const QString& account, const QString& jid,
|
|||||||
{
|
{
|
||||||
ElId id(account, jid);
|
ElId id(account, jid);
|
||||||
Element* el = getElement(id);
|
Element* el = getElement(id);
|
||||||
if (el != NULL) {
|
if (el != nullptr) {
|
||||||
el->responseArchive(list, last);
|
el->responseArchive(list, last);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -960,8 +1047,8 @@ void Models::Roster::responseArchive(const QString& account, const QString& jid,
|
|||||||
void Models::Roster::fileProgress(const std::list<Shared::MessageInfo>& msgs, qreal value, bool up)
|
void Models::Roster::fileProgress(const std::list<Shared::MessageInfo>& msgs, qreal value, bool up)
|
||||||
{
|
{
|
||||||
for (const Shared::MessageInfo& info : msgs) {
|
for (const Shared::MessageInfo& info : msgs) {
|
||||||
Element* el = getElement({info.account, info.jid});
|
Element* el = getElement(ElId(info.account, info.jid));
|
||||||
if (el != NULL) {
|
if (el != nullptr) {
|
||||||
el->fileProgress(info.messageId, value, up);
|
el->fileProgress(info.messageId, value, up);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -970,8 +1057,8 @@ void Models::Roster::fileProgress(const std::list<Shared::MessageInfo>& msgs, qr
|
|||||||
void Models::Roster::fileComplete(const std::list<Shared::MessageInfo>& msgs, bool up)
|
void Models::Roster::fileComplete(const std::list<Shared::MessageInfo>& msgs, bool up)
|
||||||
{
|
{
|
||||||
for (const Shared::MessageInfo& info : msgs) {
|
for (const Shared::MessageInfo& info : msgs) {
|
||||||
Element* el = getElement({info.account, info.jid});
|
Element* el = getElement(ElId(info.account, info.jid));
|
||||||
if (el != NULL) {
|
if (el != nullptr) {
|
||||||
el->fileComplete(info.messageId, up);
|
el->fileComplete(info.messageId, up);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -980,8 +1067,8 @@ void Models::Roster::fileComplete(const std::list<Shared::MessageInfo>& msgs, bo
|
|||||||
void Models::Roster::fileError(const std::list<Shared::MessageInfo>& msgs, const QString& err, bool up)
|
void Models::Roster::fileError(const std::list<Shared::MessageInfo>& msgs, const QString& err, bool up)
|
||||||
{
|
{
|
||||||
for (const Shared::MessageInfo& info : msgs) {
|
for (const Shared::MessageInfo& info : msgs) {
|
||||||
Element* el = getElement({info.account, info.jid});
|
Element* el = getElement(ElId(info.account, info.jid));
|
||||||
if (el != NULL) {
|
if (el != nullptr) {
|
||||||
el->fileError(info.messageId, err, up);
|
el->fileError(info.messageId, err, up);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -989,17 +1076,40 @@ void Models::Roster::fileError(const std::list<Shared::MessageInfo>& msgs, const
|
|||||||
|
|
||||||
Models::Element * Models::Roster::getElement(const Models::Roster::ElId& id)
|
Models::Element * Models::Roster::getElement(const Models::Roster::ElId& id)
|
||||||
{
|
{
|
||||||
std::map<ElId, Contact*>::iterator cItr = contacts.find(id);
|
return const_cast<Models::Element*>(getElementConst(id));
|
||||||
|
}
|
||||||
|
|
||||||
if (cItr != contacts.end()) {
|
Models::Item::Type Models::Roster::getContactType(const Models::Roster::ElId& id) const
|
||||||
return cItr->second;
|
{
|
||||||
} else {
|
const Models::Element* el = getElementConst(id);
|
||||||
std::map<ElId, Room*>::iterator rItr = rooms.find(id);
|
if (el == nullptr) {
|
||||||
if (rItr != rooms.end()) {
|
return Item::root;
|
||||||
return rItr->second;
|
}
|
||||||
|
|
||||||
|
return el->type;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return NULL;
|
void Models::Roster::recalculateUnreadMessages()
|
||||||
|
{
|
||||||
|
int count(0);
|
||||||
|
for (const std::pair<const ElId, Contact*>& pair : contacts) {
|
||||||
|
count += pair.second->getMessagesCount();
|
||||||
|
}
|
||||||
|
for (const std::pair<const ElId, Room*>& pair : rooms) {
|
||||||
|
count += pair.second->getMessagesCount();
|
||||||
|
}
|
||||||
|
emit unreadMessagesCountChanged(count);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,6 +46,7 @@ public:
|
|||||||
Roster(QObject* parent = 0);
|
Roster(QObject* parent = 0);
|
||||||
~Roster();
|
~Roster();
|
||||||
|
|
||||||
|
public slots:
|
||||||
void addAccount(const QMap<QString, QVariant> &data);
|
void addAccount(const QMap<QString, QVariant> &data);
|
||||||
void updateAccount(const QString& account, const QString& field, const QVariant& value);
|
void updateAccount(const QString& account, const QString& field, const QVariant& value);
|
||||||
void removeAccount(const QString& account);
|
void removeAccount(const QString& account);
|
||||||
@ -65,7 +66,12 @@ public:
|
|||||||
void addRoomParticipant(const QString& account, const QString& jid, const QString& name, const QMap<QString, QVariant>& data);
|
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 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 removeRoomParticipant(const QString& account, const QString& jid, const QString& name);
|
||||||
QString getContactName(const QString& account, const QString& jid);
|
|
||||||
|
public:
|
||||||
|
QString getContactName(const QString& account, const QString& jid) const;
|
||||||
|
Item::Type getContactType(const Models::Roster::ElId& id) const;
|
||||||
|
const Element* getElementConst(const ElId& id) const;
|
||||||
|
Element* getElement(const ElId& id);
|
||||||
|
|
||||||
QVariant data ( const QModelIndex& index, int role ) const override;
|
QVariant data ( const QModelIndex& index, int role ) const override;
|
||||||
Qt::ItemFlags flags(const QModelIndex &index) const override;
|
Qt::ItemFlags flags(const QModelIndex &index) const override;
|
||||||
@ -77,10 +83,13 @@ public:
|
|||||||
|
|
||||||
std::deque<QString> groupList(const QString& account) const;
|
std::deque<QString> groupList(const QString& account) const;
|
||||||
bool groupHasContact(const QString& account, const QString& group, const QString& contactJID) const;
|
bool groupHasContact(const QString& account, const QString& group, const QString& contactJID) const;
|
||||||
QString getContactIconPath(const QString& account, const QString& jid, const QString& resource);
|
QString getContactIconPath(const QString& account, const QString& jid, const QString& resource) const;
|
||||||
Account* getAccount(const QString& name);
|
Account* getAccount(const QString& name);
|
||||||
|
const Account* getAccountConst(const QString& name) const;
|
||||||
QModelIndex getAccountIndex(const QString& name);
|
QModelIndex getAccountIndex(const QString& name);
|
||||||
QModelIndex getGroupIndex(const QString& account, const QString& name);
|
QModelIndex getGroupIndex(const QString& account, const QString& name);
|
||||||
|
QModelIndex getContactIndex(const QString& account, const QString& jid, const QString& resource = "");
|
||||||
|
bool markMessageAsRead(const ElId& elementId, const QString& messageId);
|
||||||
void responseArchive(const QString& account, const QString& jid, const std::list<Shared::Message>& list, bool last);
|
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 fileProgress(const std::list<Shared::MessageInfo>& msgs, qreal value, bool up);
|
||||||
@ -92,14 +101,13 @@ public:
|
|||||||
signals:
|
signals:
|
||||||
void requestArchive(const QString& account, const QString& jid, const QString& before);
|
void requestArchive(const QString& account, const QString& jid, const QString& before);
|
||||||
void fileDownloadRequest(const QString& url);
|
void fileDownloadRequest(const QString& url);
|
||||||
|
void unreadMessagesCountChanged(int count);
|
||||||
void unnoticedMessage(const QString& account, const Shared::Message& msg);
|
void unnoticedMessage(const QString& account, const Shared::Message& msg);
|
||||||
void localPathInvalid(const QString& path);
|
void localPathInvalid(const QString& path);
|
||||||
|
|
||||||
private:
|
|
||||||
Element* getElement(const ElId& id);
|
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void onAccountDataChanged(const QModelIndex& tl, const QModelIndex& br, const QVector<int>& roles);
|
void onAccountDataChanged(const QModelIndex& tl, const QModelIndex& br, const QVector<int>& roles);
|
||||||
|
void onAccountReconnected();
|
||||||
void onChildChanged(Models::Item* item, int row, int col);
|
void onChildChanged(Models::Item* item, int row, int col);
|
||||||
void onChildIsAboutToBeInserted(Item* parent, int first, int last);
|
void onChildIsAboutToBeInserted(Item* parent, int first, int last);
|
||||||
void onChildInserted();
|
void onChildInserted();
|
||||||
@ -108,6 +116,7 @@ private slots:
|
|||||||
void onChildIsAboutToBeMoved(Item* source, int first, int last, Item* destination, int newIndex);
|
void onChildIsAboutToBeMoved(Item* source, int first, int last, Item* destination, int newIndex);
|
||||||
void onChildMoved();
|
void onChildMoved();
|
||||||
void onElementRequestArchive(const QString& before);
|
void onElementRequestArchive(const QString& before);
|
||||||
|
void recalculateUnreadMessages();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Item* root;
|
Item* root;
|
||||||
|
633
ui/squawk.cpp
633
ui/squawk.cpp
@ -21,25 +21,27 @@
|
|||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QIcon>
|
#include <QIcon>
|
||||||
|
|
||||||
Squawk::Squawk(QWidget *parent) :
|
Squawk::Squawk(Models::Roster& p_rosterModel, QWidget *parent) :
|
||||||
QMainWindow(parent),
|
QMainWindow(parent),
|
||||||
m_ui(new Ui::Squawk),
|
m_ui(new Ui::Squawk),
|
||||||
accounts(0),
|
accounts(nullptr),
|
||||||
rosterModel(),
|
preferences(nullptr),
|
||||||
conversations(),
|
about(nullptr),
|
||||||
|
rosterModel(p_rosterModel),
|
||||||
contextMenu(new QMenu()),
|
contextMenu(new QMenu()),
|
||||||
dbus("org.freedesktop.Notifications", "/org/freedesktop/Notifications", "org.freedesktop.Notifications", QDBusConnection::sessionBus()),
|
|
||||||
vCards(),
|
vCards(),
|
||||||
requestedAccountsForPasswords(),
|
currentConversation(nullptr),
|
||||||
prompt(0),
|
|
||||||
currentConversation(0),
|
|
||||||
restoreSelection(),
|
restoreSelection(),
|
||||||
needToRestore(false)
|
needToRestore(false)
|
||||||
{
|
{
|
||||||
m_ui->setupUi(this);
|
m_ui->setupUi(this);
|
||||||
m_ui->roster->setModel(&rosterModel);
|
m_ui->roster->setModel(&rosterModel);
|
||||||
m_ui->roster->setContextMenuPolicy(Qt::CustomContextMenu);
|
m_ui->roster->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||||
m_ui->roster->setColumnWidth(1, 30);
|
if (QApplication::style()->styleHint(QStyle::SH_ScrollBar_Transient) == 1) {
|
||||||
|
m_ui->roster->setColumnWidth(1, 52);
|
||||||
|
} else {
|
||||||
|
m_ui->roster->setColumnWidth(1, 26);
|
||||||
|
}
|
||||||
m_ui->roster->setIconSize(QSize(20, 20));
|
m_ui->roster->setIconSize(QSize(20, 20));
|
||||||
m_ui->roster->header()->setStretchLastSection(false);
|
m_ui->roster->header()->setStretchLastSection(false);
|
||||||
m_ui->roster->header()->setSectionResizeMode(0, QHeaderView::Stretch);
|
m_ui->roster->header()->setSectionResizeMode(0, QHeaderView::Stretch);
|
||||||
@ -51,6 +53,7 @@ Squawk::Squawk(QWidget *parent) :
|
|||||||
m_ui->comboBox->setCurrentIndex(static_cast<int>(Shared::Availability::offline));
|
m_ui->comboBox->setCurrentIndex(static_cast<int>(Shared::Availability::offline));
|
||||||
|
|
||||||
connect(m_ui->actionAccounts, &QAction::triggered, this, &Squawk::onAccounts);
|
connect(m_ui->actionAccounts, &QAction::triggered, this, &Squawk::onAccounts);
|
||||||
|
connect(m_ui->actionPreferences, &QAction::triggered, this, &Squawk::onPreferences);
|
||||||
connect(m_ui->actionAddContact, &QAction::triggered, this, &Squawk::onNewContact);
|
connect(m_ui->actionAddContact, &QAction::triggered, this, &Squawk::onNewContact);
|
||||||
connect(m_ui->actionAddConference, &QAction::triggered, this, &Squawk::onNewConference);
|
connect(m_ui->actionAddConference, &QAction::triggered, this, &Squawk::onNewConference);
|
||||||
connect(m_ui->actionQuit, &QAction::triggered, this, &Squawk::close);
|
connect(m_ui->actionQuit, &QAction::triggered, this, &Squawk::close);
|
||||||
@ -59,13 +62,10 @@ Squawk::Squawk(QWidget *parent) :
|
|||||||
connect(m_ui->roster, &QTreeView::customContextMenuRequested, this, &Squawk::onRosterContextMenu);
|
connect(m_ui->roster, &QTreeView::customContextMenuRequested, this, &Squawk::onRosterContextMenu);
|
||||||
connect(m_ui->roster, &QTreeView::collapsed, this, &Squawk::onItemCollepsed);
|
connect(m_ui->roster, &QTreeView::collapsed, this, &Squawk::onItemCollepsed);
|
||||||
connect(m_ui->roster->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &Squawk::onRosterSelectionChanged);
|
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.accountsModel, &Models::Accounts::sizeChanged, this, &Squawk::onAccountsSizeChanged);
|
||||||
connect(&rosterModel, &Models::Roster::requestArchive, this, &Squawk::onRequestArchive);
|
|
||||||
connect(&rosterModel, &Models::Roster::fileDownloadRequest, this, &Squawk::fileDownloadRequest);
|
|
||||||
connect(&rosterModel, &Models::Roster::localPathInvalid, this, &Squawk::localPathInvalid);
|
|
||||||
connect(contextMenu, &QMenu::aboutToHide, this, &Squawk::onContextAboutToHide);
|
connect(contextMenu, &QMenu::aboutToHide, this, &Squawk::onContextAboutToHide);
|
||||||
|
connect(m_ui->actionAboutSquawk, &QAction::triggered, this, &Squawk::onAboutSquawkCalled);
|
||||||
//m_ui->mainToolBar->addWidget(m_ui->comboBox);
|
//m_ui->mainToolBar->addWidget(m_ui->comboBox);
|
||||||
|
|
||||||
if (testAttribute(Qt::WA_TranslucentBackground)) {
|
if (testAttribute(Qt::WA_TranslucentBackground)) {
|
||||||
@ -95,8 +95,8 @@ Squawk::~Squawk() {
|
|||||||
|
|
||||||
void Squawk::onAccounts()
|
void Squawk::onAccounts()
|
||||||
{
|
{
|
||||||
if (accounts == 0) {
|
if (accounts == nullptr) {
|
||||||
accounts = new Accounts(rosterModel.accountsModel);
|
accounts = new Accounts(rosterModel.accountsModel, this);
|
||||||
accounts->setAttribute(Qt::WA_DeleteOnClose);
|
accounts->setAttribute(Qt::WA_DeleteOnClose);
|
||||||
connect(accounts, &Accounts::destroyed, this, &Squawk::onAccountsClosed);
|
connect(accounts, &Accounts::destroyed, this, &Squawk::onAccountsClosed);
|
||||||
connect(accounts, &Accounts::newAccount, this, &Squawk::newAccountRequest);
|
connect(accounts, &Accounts::newAccount, this, &Squawk::newAccountRequest);
|
||||||
@ -104,14 +104,25 @@ void Squawk::onAccounts()
|
|||||||
connect(accounts, &Accounts::connectAccount, this, &Squawk::connectAccount);
|
connect(accounts, &Accounts::connectAccount, this, &Squawk::connectAccount);
|
||||||
connect(accounts, &Accounts::disconnectAccount, this, &Squawk::disconnectAccount);
|
connect(accounts, &Accounts::disconnectAccount, this, &Squawk::disconnectAccount);
|
||||||
connect(accounts, &Accounts::removeAccount, this, &Squawk::removeAccountRequest);
|
connect(accounts, &Accounts::removeAccount, this, &Squawk::removeAccountRequest);
|
||||||
|
}
|
||||||
accounts->show();
|
|
||||||
} else {
|
|
||||||
accounts->show();
|
accounts->show();
|
||||||
accounts->raise();
|
accounts->raise();
|
||||||
accounts->activateWindow();
|
accounts->activateWindow();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Squawk::onPreferences()
|
||||||
|
{
|
||||||
|
if (preferences == nullptr) {
|
||||||
|
preferences = new Settings(this);
|
||||||
|
preferences->setAttribute(Qt::WA_DeleteOnClose);
|
||||||
|
connect(preferences, &Settings::destroyed, this, &Squawk::onPreferencesClosed);
|
||||||
|
connect(preferences, &Settings::changeDownloadsPath, this, &Squawk::changeDownloadsPath);
|
||||||
}
|
}
|
||||||
|
preferences->show();
|
||||||
|
preferences->raise();
|
||||||
|
preferences->activateWindow();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void Squawk::onAccountsSizeChanged(unsigned int size)
|
void Squawk::onAccountsSizeChanged(unsigned int size)
|
||||||
{
|
{
|
||||||
@ -166,143 +177,47 @@ void Squawk::onJoinConferenceAccepted()
|
|||||||
|
|
||||||
void Squawk::closeEvent(QCloseEvent* event)
|
void Squawk::closeEvent(QCloseEvent* event)
|
||||||
{
|
{
|
||||||
if (accounts != 0) {
|
if (accounts != nullptr) {
|
||||||
accounts->close();
|
accounts->close();
|
||||||
}
|
}
|
||||||
|
if (preferences != nullptr) {
|
||||||
for (Conversations::const_iterator itr = conversations.begin(), end = conversations.end(); itr != end; ++itr) {
|
preferences->close();
|
||||||
disconnect(itr->second, &Conversation::destroyed, this, &Squawk::onConversationClosed);
|
}
|
||||||
itr->second->close();
|
if (about != nullptr) {
|
||||||
|
about->close();
|
||||||
}
|
}
|
||||||
conversations.clear();
|
|
||||||
|
|
||||||
for (std::map<QString, VCard*>::const_iterator itr = vCards.begin(), end = vCards.end(); itr != end; ++itr) {
|
for (std::map<QString, VCard*>::const_iterator itr = vCards.begin(), end = vCards.end(); itr != end; ++itr) {
|
||||||
disconnect(itr->second, &VCard::destroyed, this, &Squawk::onVCardClosed);
|
disconnect(itr->second, &VCard::destroyed, this, &Squawk::onVCardClosed);
|
||||||
itr->second->close();
|
itr->second->close();
|
||||||
}
|
}
|
||||||
vCards.clear();
|
vCards.clear();
|
||||||
|
writeSettings();
|
||||||
|
emit closing();;
|
||||||
|
|
||||||
QMainWindow::closeEvent(event);
|
QMainWindow::closeEvent(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Squawk::onAccountsClosed() {
|
||||||
|
accounts = nullptr;}
|
||||||
|
|
||||||
void Squawk::onAccountsClosed(QObject* parent)
|
void Squawk::onPreferencesClosed() {
|
||||||
{
|
preferences = nullptr;}
|
||||||
accounts = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Squawk::newAccount(const QMap<QString, QVariant>& account)
|
void Squawk::onAboutSquawkClosed() {
|
||||||
{
|
about = nullptr;}
|
||||||
rosterModel.addAccount(account);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Squawk::onComboboxActivated(int index)
|
void Squawk::onComboboxActivated(int index)
|
||||||
{
|
{
|
||||||
Shared::Availability av = Shared::Global::fromInt<Shared::Availability>(index);
|
Shared::Availability av = Shared::Global::fromInt<Shared::Availability>(index);
|
||||||
if (av != Shared::Availability::offline) {
|
|
||||||
int size = rosterModel.accountsModel->rowCount(QModelIndex());
|
|
||||||
if (size > 0) {
|
|
||||||
emit changeState(av);
|
emit changeState(av);
|
||||||
for (int i = 0; i < size; ++i) {
|
|
||||||
Models::Account* acc = rosterModel.accountsModel->getAccount(i);
|
|
||||||
if (acc->getState() == Shared::ConnectionState::disconnected) {
|
|
||||||
emit connectAccount(acc->getName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
m_ui->comboBox->setCurrentIndex(static_cast<int>(Shared::Availability::offline));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
emit changeState(av);
|
|
||||||
int size = rosterModel.accountsModel->rowCount(QModelIndex());
|
|
||||||
for (int i = 0; i != size; ++i) {
|
|
||||||
Models::Account* acc = rosterModel.accountsModel->getAccount(i);
|
|
||||||
if (acc->getState() != Shared::ConnectionState::disconnected) {
|
|
||||||
emit disconnectAccount(acc->getName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Squawk::changeAccount(const QString& account, const QMap<QString, QVariant>& data)
|
void Squawk::expand(const QModelIndex& index) {
|
||||||
{
|
m_ui->roster->expand(index);}
|
||||||
for (QMap<QString, QVariant>::const_iterator itr = data.begin(), end = data.end(); itr != end; ++itr) {
|
|
||||||
QString attr = itr.key();
|
|
||||||
rosterModel.updateAccount(account, attr, *itr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Squawk::addContact(const QString& account, const QString& jid, const QString& group, const QMap<QString, QVariant>& data)
|
void Squawk::stateChanged(Shared::Availability state) {
|
||||||
{
|
m_ui->comboBox->setCurrentIndex(static_cast<int>(state));}
|
||||||
rosterModel.addContact(account, jid, group, data);
|
|
||||||
|
|
||||||
QSettings settings;
|
|
||||||
settings.beginGroup("ui");
|
|
||||||
settings.beginGroup("roster");
|
|
||||||
settings.beginGroup(account);
|
|
||||||
if (settings.value("expanded", false).toBool()) {
|
|
||||||
QModelIndex ind = rosterModel.getAccountIndex(account);
|
|
||||||
m_ui->roster->expand(ind);
|
|
||||||
}
|
|
||||||
settings.endGroup();
|
|
||||||
settings.endGroup();
|
|
||||||
settings.endGroup();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Squawk::addGroup(const QString& account, const QString& name)
|
|
||||||
{
|
|
||||||
rosterModel.addGroup(account, name);
|
|
||||||
|
|
||||||
QSettings settings;
|
|
||||||
settings.beginGroup("ui");
|
|
||||||
settings.beginGroup("roster");
|
|
||||||
settings.beginGroup(account);
|
|
||||||
if (settings.value("expanded", false).toBool()) {
|
|
||||||
QModelIndex ind = rosterModel.getAccountIndex(account);
|
|
||||||
m_ui->roster->expand(ind);
|
|
||||||
if (settings.value(name + "/expanded", false).toBool()) {
|
|
||||||
m_ui->roster->expand(rosterModel.getGroupIndex(account, name));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
settings.endGroup();
|
|
||||||
settings.endGroup();
|
|
||||||
settings.endGroup();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Squawk::removeGroup(const QString& account, const QString& name)
|
|
||||||
{
|
|
||||||
rosterModel.removeGroup(account, name);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Squawk::changeContact(const QString& account, const QString& jid, const QMap<QString, QVariant>& data)
|
|
||||||
{
|
|
||||||
rosterModel.changeContact(account, jid, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Squawk::removeContact(const QString& account, const QString& jid)
|
|
||||||
{
|
|
||||||
rosterModel.removeContact(account, jid);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Squawk::removeContact(const QString& account, const QString& jid, const QString& group)
|
|
||||||
{
|
|
||||||
rosterModel.removeContact(account, jid, group);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Squawk::addPresence(const QString& account, const QString& jid, const QString& name, const QMap<QString, QVariant>& data)
|
|
||||||
{
|
|
||||||
rosterModel.addPresence(account, jid, name, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Squawk::removePresence(const QString& account, const QString& jid, const QString& name)
|
|
||||||
{
|
|
||||||
rosterModel.removePresence(account, jid, name);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Squawk::stateChanged(Shared::Availability state)
|
|
||||||
{
|
|
||||||
m_ui->comboBox->setCurrentIndex(static_cast<int>(state));
|
|
||||||
}
|
|
||||||
|
|
||||||
void Squawk::onRosterItemDoubleClicked(const QModelIndex& item)
|
void Squawk::onRosterItemDoubleClicked(const QModelIndex& item)
|
||||||
{
|
{
|
||||||
@ -311,194 +226,35 @@ void Squawk::onRosterItemDoubleClicked(const QModelIndex& item)
|
|||||||
if (node->type == Models::Item::reference) {
|
if (node->type == Models::Item::reference) {
|
||||||
node = static_cast<Models::Reference*>(node)->dereference();
|
node = static_cast<Models::Reference*>(node)->dereference();
|
||||||
}
|
}
|
||||||
Models::Contact* contact = 0;
|
Models::Contact* contact = nullptr;
|
||||||
Models::Room* room = 0;
|
Models::Room* room = nullptr;
|
||||||
QString res;
|
|
||||||
Models::Roster::ElId* id = 0;
|
|
||||||
switch (node->type) {
|
switch (node->type) {
|
||||||
case Models::Item::contact:
|
case Models::Item::contact:
|
||||||
contact = static_cast<Models::Contact*>(node);
|
contact = static_cast<Models::Contact*>(node);
|
||||||
id = new Models::Roster::ElId(contact->getAccountName(), contact->getJid());
|
emit openConversation(Models::Roster::ElId(contact->getAccountName(), contact->getJid()));
|
||||||
break;
|
break;
|
||||||
case Models::Item::presence:
|
case Models::Item::presence:
|
||||||
contact = static_cast<Models::Contact*>(node->parentItem());
|
contact = static_cast<Models::Contact*>(node->parentItem());
|
||||||
id = new Models::Roster::ElId(contact->getAccountName(), contact->getJid());
|
emit openConversation(Models::Roster::ElId(contact->getAccountName(), contact->getJid()), node->getName());
|
||||||
res = node->getName();
|
|
||||||
break;
|
break;
|
||||||
case Models::Item::room:
|
case Models::Item::room:
|
||||||
room = static_cast<Models::Room*>(node);
|
room = static_cast<Models::Room*>(node);
|
||||||
id = new Models::Roster::ElId(room->getAccountName(), room->getJid());
|
emit openConversation(Models::Roster::ElId(room->getAccountName(), room->getJid()));
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
m_ui->roster->expand(item);
|
m_ui->roster->expand(item);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (id != 0) {
|
|
||||||
Conversations::const_iterator itr = conversations.find(*id);
|
|
||||||
Models::Account* acc = rosterModel.getAccount(id->account);
|
|
||||||
Conversation* conv = 0;
|
|
||||||
bool created = false;
|
|
||||||
if (itr != conversations.end()) {
|
|
||||||
conv = itr->second;
|
|
||||||
} else if (contact != 0) {
|
|
||||||
created = true;
|
|
||||||
conv = new Chat(acc, contact);
|
|
||||||
} else if (room != 0) {
|
|
||||||
created = true;
|
|
||||||
conv = new Room(acc, room);
|
|
||||||
|
|
||||||
if (!room->getJoined()) {
|
|
||||||
emit setRoomJoined(id->account, id->name, true);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (conv != 0) {
|
void Squawk::closeCurrentConversation()
|
||||||
if (created) {
|
|
||||||
conv->setAttribute(Qt::WA_DeleteOnClose);
|
|
||||||
subscribeConversation(conv);
|
|
||||||
conversations.insert(std::make_pair(*id, conv));
|
|
||||||
}
|
|
||||||
|
|
||||||
conv->show();
|
|
||||||
conv->raise();
|
|
||||||
conv->activateWindow();
|
|
||||||
|
|
||||||
if (res.size() > 0) {
|
|
||||||
conv->setPalResource(res);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
delete id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Squawk::onConversationClosed(QObject* parent)
|
|
||||||
{
|
{
|
||||||
Conversation* conv = static_cast<Conversation*>(sender());
|
if (currentConversation != nullptr) {
|
||||||
Models::Roster::ElId id(conv->getAccount(), conv->getJid());
|
|
||||||
Conversations::const_iterator itr = conversations.find(id);
|
|
||||||
if (itr != conversations.end()) {
|
|
||||||
conversations.erase(itr);
|
|
||||||
}
|
|
||||||
if (conv->isMuc) {
|
|
||||||
Room* room = static_cast<Room*>(conv);
|
|
||||||
if (!room->autoJoined()) {
|
|
||||||
emit setRoomJoined(id.account, id.name, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Squawk::fileProgress(const std::list<Shared::MessageInfo> msgs, qreal value, bool up)
|
|
||||||
{
|
|
||||||
rosterModel.fileProgress(msgs, value, up);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Squawk::fileDownloadComplete(const std::list<Shared::MessageInfo> msgs, const QString& path)
|
|
||||||
{
|
|
||||||
rosterModel.fileComplete(msgs, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Squawk::fileError(const std::list<Shared::MessageInfo> msgs, const QString& error, bool up)
|
|
||||||
{
|
|
||||||
rosterModel.fileError(msgs, error, up);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Squawk::fileUploadComplete(const std::list<Shared::MessageInfo> msgs, const QString& path)
|
|
||||||
{
|
|
||||||
rosterModel.fileComplete(msgs, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Squawk::accountMessage(const QString& account, const Shared::Message& data)
|
|
||||||
{
|
|
||||||
rosterModel.addMessage(account, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Squawk::onUnnoticedMessage(const QString& account, const Shared::Message& msg)
|
|
||||||
{
|
|
||||||
notify(account, msg); //Telegram does this way - notifies even if the app is visible
|
|
||||||
QApplication::alert(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Squawk::changeMessage(const QString& account, const QString& jid, const QString& id, const QMap<QString, QVariant>& data)
|
|
||||||
{
|
|
||||||
rosterModel.changeMessage(account, jid, id, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Squawk::notify(const QString& account, const Shared::Message& msg)
|
|
||||||
{
|
|
||||||
QString name = QString(rosterModel.getContactName(account, msg.getPenPalJid()));
|
|
||||||
QString path = QString(rosterModel.getContactIconPath(account, msg.getPenPalJid(), msg.getPenPalResource()));
|
|
||||||
QVariantList args;
|
|
||||||
args << QString(QCoreApplication::applicationName());
|
|
||||||
args << QVariant(QVariant::UInt); //TODO some normal id
|
|
||||||
if (path.size() > 0) {
|
|
||||||
args << path;
|
|
||||||
} else {
|
|
||||||
args << QString("mail-message"); //TODO should here better be unknown user icon?
|
|
||||||
}
|
|
||||||
if (msg.getType() == Shared::Message::groupChat) {
|
|
||||||
args << msg.getFromResource() + " from " + name;
|
|
||||||
} else {
|
|
||||||
args << name;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString body(msg.getBody());
|
|
||||||
QString oob(msg.getOutOfBandUrl());
|
|
||||||
if (body == oob) {
|
|
||||||
body = tr("Attached file");
|
|
||||||
}
|
|
||||||
|
|
||||||
args << body;
|
|
||||||
args << QStringList();
|
|
||||||
args << QVariantMap();
|
|
||||||
args << 3000;
|
|
||||||
dbus.callWithArgumentList(QDBus::AutoDetect, "Notify", args);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Squawk::onConversationMessage(const Shared::Message& msg)
|
|
||||||
{
|
|
||||||
Conversation* conv = static_cast<Conversation*>(sender());
|
|
||||||
QString acc = conv->getAccount();
|
|
||||||
|
|
||||||
rosterModel.addMessage(acc, msg);
|
|
||||||
emit sendMessage(acc, msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Squawk::onRequestArchive(const QString& account, const QString& jid, const QString& before)
|
|
||||||
{
|
|
||||||
emit requestArchive(account, jid, 20, before); //TODO amount as a settings value
|
|
||||||
}
|
|
||||||
|
|
||||||
void Squawk::responseArchive(const QString& account, const QString& jid, const std::list<Shared::Message>& list, bool last)
|
|
||||||
{
|
|
||||||
rosterModel.responseArchive(account, jid, list, last);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Squawk::removeAccount(const QString& account)
|
|
||||||
{
|
|
||||||
Conversations::const_iterator itr = conversations.begin();
|
|
||||||
while (itr != conversations.end()) {
|
|
||||||
if (itr->first.account == account) {
|
|
||||||
Conversations::const_iterator lItr = itr;
|
|
||||||
++itr;
|
|
||||||
Conversation* conv = lItr->second;
|
|
||||||
disconnect(conv, &Conversation::destroyed, this, &Squawk::onConversationClosed);
|
|
||||||
conv->close();
|
|
||||||
conversations.erase(lItr);
|
|
||||||
} else {
|
|
||||||
++itr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentConversation != 0 && currentConversation->getAccount() == account) {
|
|
||||||
currentConversation->deleteLater();
|
currentConversation->deleteLater();
|
||||||
currentConversation = 0;
|
currentConversation = nullptr;
|
||||||
m_ui->filler->show();
|
m_ui->filler->show();
|
||||||
}
|
}
|
||||||
|
|
||||||
rosterModel.removeAccount(account);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Squawk::onRosterContextMenu(const QPoint& point)
|
void Squawk::onRosterContextMenu(const QPoint& point)
|
||||||
@ -518,17 +274,12 @@ void Squawk::onRosterContextMenu(const QPoint& point)
|
|||||||
hasMenu = true;
|
hasMenu = true;
|
||||||
QString name = acc->getName();
|
QString name = acc->getName();
|
||||||
|
|
||||||
if (acc->getState() != Shared::ConnectionState::disconnected) {
|
if (acc->getActive()) {
|
||||||
QAction* con = contextMenu->addAction(Shared::icon("network-disconnect"), tr("Disconnect"));
|
QAction* con = contextMenu->addAction(Shared::icon("network-disconnect"), tr("Deactivate"));
|
||||||
con->setEnabled(active);
|
connect(con, &QAction::triggered, std::bind(&Squawk::disconnectAccount, this, name));
|
||||||
connect(con, &QAction::triggered, [this, name]() {
|
|
||||||
emit disconnectAccount(name);
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
QAction* con = contextMenu->addAction(Shared::icon("network-connect"), tr("Connect"));
|
QAction* con = contextMenu->addAction(Shared::icon("network-connect"), tr("Activate"));
|
||||||
connect(con, &QAction::triggered, [this, name]() {
|
connect(con, &QAction::triggered, std::bind(&Squawk::connectAccount, this, name));
|
||||||
emit connectAccount(name);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QAction* card = contextMenu->addAction(Shared::icon("user-properties"), tr("VCard"));
|
QAction* card = contextMenu->addAction(Shared::icon("user-properties"), tr("VCard"));
|
||||||
@ -536,22 +287,18 @@ void Squawk::onRosterContextMenu(const QPoint& point)
|
|||||||
connect(card, &QAction::triggered, std::bind(&Squawk::onActivateVCard, this, name, acc->getBareJid(), true));
|
connect(card, &QAction::triggered, std::bind(&Squawk::onActivateVCard, this, name, acc->getBareJid(), true));
|
||||||
|
|
||||||
QAction* remove = contextMenu->addAction(Shared::icon("edit-delete"), tr("Remove"));
|
QAction* remove = contextMenu->addAction(Shared::icon("edit-delete"), tr("Remove"));
|
||||||
remove->setEnabled(active);
|
connect(remove, &QAction::triggered, std::bind(&Squawk::removeAccountRequest, this, name));
|
||||||
connect(remove, &QAction::triggered, [this, name]() {
|
|
||||||
emit removeAccount(name);
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case Models::Item::contact: {
|
case Models::Item::contact: {
|
||||||
Models::Contact* cnt = static_cast<Models::Contact*>(item);
|
Models::Contact* cnt = static_cast<Models::Contact*>(item);
|
||||||
|
Models::Roster::ElId id(cnt->getAccountName(), cnt->getJid());
|
||||||
|
QString cntName = cnt->getName();
|
||||||
hasMenu = true;
|
hasMenu = true;
|
||||||
|
|
||||||
QAction* dialog = contextMenu->addAction(Shared::icon("mail-message"), tr("Open dialog"));
|
QAction* dialog = contextMenu->addAction(Shared::icon("mail-message"), tr("Open dialog"));
|
||||||
dialog->setEnabled(active);
|
dialog->setEnabled(active);
|
||||||
connect(dialog, &QAction::triggered, [this, index]() {
|
connect(dialog, &QAction::triggered, std::bind(&Squawk::onRosterItemDoubleClicked, this, index));
|
||||||
onRosterItemDoubleClicked(index);
|
|
||||||
});
|
|
||||||
|
|
||||||
Shared::SubscriptionState state = cnt->getState();
|
Shared::SubscriptionState state = cnt->getState();
|
||||||
switch (state) {
|
switch (state) {
|
||||||
@ -559,9 +306,7 @@ void Squawk::onRosterContextMenu(const QPoint& point)
|
|||||||
case Shared::SubscriptionState::to: {
|
case Shared::SubscriptionState::to: {
|
||||||
QAction* unsub = contextMenu->addAction(Shared::icon("news-unsubscribe"), tr("Unsubscribe"));
|
QAction* unsub = contextMenu->addAction(Shared::icon("news-unsubscribe"), tr("Unsubscribe"));
|
||||||
unsub->setEnabled(active);
|
unsub->setEnabled(active);
|
||||||
connect(unsub, &QAction::triggered, [this, cnt]() {
|
connect(unsub, &QAction::triggered, std::bind(&Squawk::changeSubscription, this, id, false));
|
||||||
emit unsubscribeContact(cnt->getAccountName(), cnt->getJid(), "");
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case Shared::SubscriptionState::from:
|
case Shared::SubscriptionState::from:
|
||||||
@ -569,75 +314,68 @@ void Squawk::onRosterContextMenu(const QPoint& point)
|
|||||||
case Shared::SubscriptionState::none: {
|
case Shared::SubscriptionState::none: {
|
||||||
QAction* sub = contextMenu->addAction(Shared::icon("news-subscribe"), tr("Subscribe"));
|
QAction* sub = contextMenu->addAction(Shared::icon("news-subscribe"), tr("Subscribe"));
|
||||||
sub->setEnabled(active);
|
sub->setEnabled(active);
|
||||||
connect(sub, &QAction::triggered, [this, cnt]() {
|
connect(sub, &QAction::triggered, std::bind(&Squawk::changeSubscription, this, id, true));
|
||||||
emit subscribeContact(cnt->getAccountName(), cnt->getJid(), "");
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
QString accName = cnt->getAccountName();
|
|
||||||
QString cntJID = cnt->getJid();
|
|
||||||
QString cntName = cnt->getName();
|
|
||||||
|
|
||||||
QAction* rename = contextMenu->addAction(Shared::icon("edit-rename"), tr("Rename"));
|
QAction* rename = contextMenu->addAction(Shared::icon("edit-rename"), tr("Rename"));
|
||||||
rename->setEnabled(active);
|
rename->setEnabled(active);
|
||||||
connect(rename, &QAction::triggered, [this, cntName, accName, cntJID]() {
|
connect(rename, &QAction::triggered, [this, cntName, id]() {
|
||||||
QInputDialog* dialog = new QInputDialog(this);
|
QInputDialog* dialog = new QInputDialog(this);
|
||||||
connect(dialog, &QDialog::accepted, [this, dialog, cntName, accName, cntJID]() {
|
connect(dialog, &QDialog::accepted, [this, dialog, cntName, id]() {
|
||||||
QString newName = dialog->textValue();
|
QString newName = dialog->textValue();
|
||||||
if (newName != cntName) {
|
if (newName != cntName) {
|
||||||
emit renameContactRequest(accName, cntJID, newName);
|
emit renameContactRequest(id.account, id.name, newName);
|
||||||
}
|
}
|
||||||
dialog->deleteLater();
|
dialog->deleteLater();
|
||||||
});
|
});
|
||||||
connect(dialog, &QDialog::rejected, dialog, &QObject::deleteLater);
|
connect(dialog, &QDialog::rejected, dialog, &QObject::deleteLater);
|
||||||
dialog->setInputMode(QInputDialog::TextInput);
|
dialog->setInputMode(QInputDialog::TextInput);
|
||||||
dialog->setLabelText(tr("Input new name for %1\nor leave it empty for the contact \nto be displayed as %1").arg(cntJID));
|
dialog->setLabelText(tr("Input new name for %1\nor leave it empty for the contact \nto be displayed as %1").arg(id.name));
|
||||||
dialog->setWindowTitle(tr("Renaming %1").arg(cntJID));
|
dialog->setWindowTitle(tr("Renaming %1").arg(id.name));
|
||||||
dialog->setTextValue(cntName);
|
dialog->setTextValue(cntName);
|
||||||
dialog->exec();
|
dialog->exec();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
QMenu* groupsMenu = contextMenu->addMenu(Shared::icon("group"), tr("Groups"));
|
QMenu* groupsMenu = contextMenu->addMenu(Shared::icon("group"), tr("Groups"));
|
||||||
std::deque<QString> groupList = rosterModel.groupList(accName);
|
std::deque<QString> groupList = rosterModel.groupList(id.account);
|
||||||
for (QString groupName : groupList) {
|
for (QString groupName : groupList) {
|
||||||
QAction* gr = groupsMenu->addAction(groupName);
|
QAction* gr = groupsMenu->addAction(groupName);
|
||||||
gr->setCheckable(true);
|
gr->setCheckable(true);
|
||||||
gr->setChecked(rosterModel.groupHasContact(accName, groupName, cntJID));
|
gr->setChecked(rosterModel.groupHasContact(id.account, groupName, id.name));
|
||||||
gr->setEnabled(active);
|
gr->setEnabled(active);
|
||||||
connect(gr, &QAction::toggled, [this, accName, groupName, cntJID](bool checked) {
|
connect(gr, &QAction::toggled, [this, groupName, id](bool checked) {
|
||||||
if (checked) {
|
if (checked) {
|
||||||
emit addContactToGroupRequest(accName, cntJID, groupName);
|
emit addContactToGroupRequest(id.account, id.name, groupName);
|
||||||
} else {
|
} else {
|
||||||
emit removeContactFromGroupRequest(accName, cntJID, groupName);
|
emit removeContactFromGroupRequest(id.account, id.name, groupName);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
QAction* newGroup = groupsMenu->addAction(Shared::icon("group-new"), tr("New group"));
|
QAction* newGroup = groupsMenu->addAction(Shared::icon("group-new"), tr("New group"));
|
||||||
newGroup->setEnabled(active);
|
newGroup->setEnabled(active);
|
||||||
connect(newGroup, &QAction::triggered, [this, accName, cntJID]() {
|
connect(newGroup, &QAction::triggered, [this, id]() {
|
||||||
QInputDialog* dialog = new QInputDialog(this);
|
QInputDialog* dialog = new QInputDialog(this);
|
||||||
connect(dialog, &QDialog::accepted, [this, dialog, accName, cntJID]() {
|
connect(dialog, &QDialog::accepted, [this, dialog, id]() {
|
||||||
emit addContactToGroupRequest(accName, cntJID, dialog->textValue());
|
emit addContactToGroupRequest(id.account, id.name, dialog->textValue());
|
||||||
dialog->deleteLater();
|
dialog->deleteLater();
|
||||||
});
|
});
|
||||||
connect(dialog, &QDialog::rejected, dialog, &QObject::deleteLater);
|
connect(dialog, &QDialog::rejected, dialog, &QObject::deleteLater);
|
||||||
dialog->setInputMode(QInputDialog::TextInput);
|
dialog->setInputMode(QInputDialog::TextInput);
|
||||||
dialog->setLabelText(tr("New group name"));
|
dialog->setLabelText(tr("New group name"));
|
||||||
dialog->setWindowTitle(tr("Add %1 to a new group").arg(cntJID));
|
dialog->setWindowTitle(tr("Add %1 to a new group").arg(id.name));
|
||||||
dialog->exec();
|
dialog->exec();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
QAction* card = contextMenu->addAction(Shared::icon("user-properties"), tr("VCard"));
|
QAction* card = contextMenu->addAction(Shared::icon("user-properties"), tr("VCard"));
|
||||||
card->setEnabled(active);
|
card->setEnabled(active);
|
||||||
connect(card, &QAction::triggered, std::bind(&Squawk::onActivateVCard, this, accName, cnt->getJid(), false));
|
connect(card, &QAction::triggered, std::bind(&Squawk::onActivateVCard, this, id.account, id.name, false));
|
||||||
|
|
||||||
QAction* remove = contextMenu->addAction(Shared::icon("edit-delete"), tr("Remove"));
|
QAction* remove = contextMenu->addAction(Shared::icon("edit-delete"), tr("Remove"));
|
||||||
remove->setEnabled(active);
|
remove->setEnabled(active);
|
||||||
connect(remove, &QAction::triggered, [this, cnt]() {
|
connect(remove, &QAction::triggered, std::bind(&Squawk::removeContactRequest, this, id.account, id.name));
|
||||||
emit removeContactRequest(cnt->getAccountName(), cnt->getJid());
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -656,32 +394,16 @@ void Squawk::onRosterContextMenu(const QPoint& point)
|
|||||||
if (room->getAutoJoin()) {
|
if (room->getAutoJoin()) {
|
||||||
QAction* unsub = contextMenu->addAction(Shared::icon("news-unsubscribe"), tr("Unsubscribe"));
|
QAction* unsub = contextMenu->addAction(Shared::icon("news-unsubscribe"), tr("Unsubscribe"));
|
||||||
unsub->setEnabled(active);
|
unsub->setEnabled(active);
|
||||||
connect(unsub, &QAction::triggered, [this, id]() {
|
connect(unsub, &QAction::triggered, std::bind(&Squawk::changeSubscription, this, id, false));
|
||||||
emit setRoomAutoJoin(id.account, id.name, false);
|
|
||||||
if (conversations.find(id) == conversations.end()
|
|
||||||
&& (currentConversation == 0 || currentConversation->getId() != id)
|
|
||||||
) { //to leave the room if it's not opened in a conversation window
|
|
||||||
emit setRoomJoined(id.account, id.name, false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
QAction* unsub = contextMenu->addAction(Shared::icon("news-subscribe"), tr("Subscribe"));
|
QAction* sub = contextMenu->addAction(Shared::icon("news-subscribe"), tr("Subscribe"));
|
||||||
unsub->setEnabled(active);
|
sub->setEnabled(active);
|
||||||
connect(unsub, &QAction::triggered, [this, id]() {
|
connect(sub, &QAction::triggered, std::bind(&Squawk::changeSubscription, this, id, true));
|
||||||
emit setRoomAutoJoin(id.account, id.name, true);
|
|
||||||
if (conversations.find(id) == conversations.end()
|
|
||||||
&& (currentConversation == 0 || currentConversation->getId() != id)
|
|
||||||
) { //to join the room if it's not already joined
|
|
||||||
emit setRoomJoined(id.account, id.name, true);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QAction* remove = contextMenu->addAction(Shared::icon("edit-delete"), tr("Remove"));
|
QAction* remove = contextMenu->addAction(Shared::icon("edit-delete"), tr("Remove"));
|
||||||
remove->setEnabled(active);
|
remove->setEnabled(active);
|
||||||
connect(remove, &QAction::triggered, [this, id]() {
|
connect(remove, &QAction::triggered, std::bind(&Squawk::removeRoomRequest, this, id.account, id.name));
|
||||||
emit removeRoomRequest(id.account, id.name);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@ -693,36 +415,6 @@ void Squawk::onRosterContextMenu(const QPoint& point)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Squawk::addRoom(const QString& account, const QString jid, const QMap<QString, QVariant>& data)
|
|
||||||
{
|
|
||||||
rosterModel.addRoom(account, jid, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Squawk::changeRoom(const QString& account, const QString jid, const QMap<QString, QVariant>& data)
|
|
||||||
{
|
|
||||||
rosterModel.changeRoom(account, jid, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Squawk::removeRoom(const QString& account, const QString jid)
|
|
||||||
{
|
|
||||||
rosterModel.removeRoom(account, jid);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Squawk::addRoomParticipant(const QString& account, const QString& jid, const QString& name, const QMap<QString, QVariant>& data)
|
|
||||||
{
|
|
||||||
rosterModel.addRoomParticipant(account, jid, name, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Squawk::changeRoomParticipant(const QString& account, const QString& jid, const QString& name, const QMap<QString, QVariant>& data)
|
|
||||||
{
|
|
||||||
rosterModel.changeRoomParticipant(account, jid, name, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Squawk::removeRoomParticipant(const QString& account, const QString& jid, const QString& name)
|
|
||||||
{
|
|
||||||
rosterModel.removeRoomParticipant(account, jid, name);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Squawk::responseVCard(const QString& jid, const Shared::VCard& card)
|
void Squawk::responseVCard(const QString& jid, const Shared::VCard& card)
|
||||||
{
|
{
|
||||||
std::map<QString, VCard*>::const_iterator itr = vCards.find(jid);
|
std::map<QString, VCard*>::const_iterator itr = vCards.find(jid);
|
||||||
@ -780,26 +472,6 @@ void Squawk::onVCardSave(const Shared::VCard& card, const QString& account)
|
|||||||
widget->deleteLater();
|
widget->deleteLater();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Squawk::readSettings()
|
|
||||||
{
|
|
||||||
QSettings settings;
|
|
||||||
settings.beginGroup("ui");
|
|
||||||
|
|
||||||
if (settings.contains("availability")) {
|
|
||||||
int avail = settings.value("availability").toInt();
|
|
||||||
m_ui->comboBox->setCurrentIndex(avail);
|
|
||||||
emit stateChanged(Shared::Global::fromInt<Shared::Availability>(avail));
|
|
||||||
|
|
||||||
int size = settings.beginReadArray("connectedAccounts");
|
|
||||||
for (int i = 0; i < size; ++i) {
|
|
||||||
settings.setArrayIndex(i);
|
|
||||||
emit connectAccount(settings.value("name").toString()); //TODO this is actually not needed, stateChanged event already connects everything you have
|
|
||||||
} // need to fix that
|
|
||||||
settings.endArray();
|
|
||||||
}
|
|
||||||
settings.endGroup();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Squawk::writeSettings()
|
void Squawk::writeSettings()
|
||||||
{
|
{
|
||||||
QSettings settings;
|
QSettings settings;
|
||||||
@ -810,21 +482,9 @@ void Squawk::writeSettings()
|
|||||||
settings.endGroup();
|
settings.endGroup();
|
||||||
|
|
||||||
settings.setValue("splitter", m_ui->splitter->saveState());
|
settings.setValue("splitter", m_ui->splitter->saveState());
|
||||||
|
|
||||||
settings.setValue("availability", m_ui->comboBox->currentIndex());
|
|
||||||
settings.beginWriteArray("connectedAccounts");
|
|
||||||
int size = rosterModel.accountsModel->rowCount(QModelIndex());
|
|
||||||
for (int i = 0; i < size; ++i) {
|
|
||||||
Models::Account* acc = rosterModel.accountsModel->getAccount(i);
|
|
||||||
if (acc->getState() != Shared::ConnectionState::disconnected) {
|
|
||||||
settings.setArrayIndex(i);
|
|
||||||
settings.setValue("name", acc->getName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
settings.endArray();
|
|
||||||
|
|
||||||
settings.remove("roster");
|
settings.remove("roster");
|
||||||
settings.beginGroup("roster");
|
settings.beginGroup("roster");
|
||||||
|
int size = rosterModel.accountsModel->rowCount(QModelIndex());
|
||||||
for (int i = 0; i < size; ++i) {
|
for (int i = 0; i < size; ++i) {
|
||||||
QModelIndex acc = rosterModel.index(i, 0, QModelIndex());
|
QModelIndex acc = rosterModel.index(i, 0, QModelIndex());
|
||||||
Models::Account* account = rosterModel.accountsModel->getAccount(i);
|
Models::Account* account = rosterModel.accountsModel->getAccount(i);
|
||||||
@ -844,6 +504,8 @@ void Squawk::writeSettings()
|
|||||||
}
|
}
|
||||||
settings.endGroup();
|
settings.endGroup();
|
||||||
settings.endGroup();
|
settings.endGroup();
|
||||||
|
|
||||||
|
settings.sync();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Squawk::onItemCollepsed(const QModelIndex& index)
|
void Squawk::onItemCollepsed(const QModelIndex& index)
|
||||||
@ -865,58 +527,6 @@ void Squawk::onItemCollepsed(const QModelIndex& index)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Squawk::requestPassword(const QString& account)
|
|
||||||
{
|
|
||||||
requestedAccountsForPasswords.push_back(account);
|
|
||||||
checkNextAccountForPassword();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Squawk::checkNextAccountForPassword()
|
|
||||||
{
|
|
||||||
if (prompt == 0 && requestedAccountsForPasswords.size() > 0) {
|
|
||||||
prompt = new QInputDialog(this);
|
|
||||||
QString accName = requestedAccountsForPasswords.front();
|
|
||||||
connect(prompt, &QDialog::accepted, this, &Squawk::onPasswordPromptAccepted);
|
|
||||||
connect(prompt, &QDialog::rejected, this, &Squawk::onPasswordPromptRejected);
|
|
||||||
prompt->setInputMode(QInputDialog::TextInput);
|
|
||||||
prompt->setTextEchoMode(QLineEdit::Password);
|
|
||||||
prompt->setLabelText(tr("Input the password for account %1").arg(accName));
|
|
||||||
prompt->setWindowTitle(tr("Password for account %1").arg(accName));
|
|
||||||
prompt->setTextValue("");
|
|
||||||
prompt->exec();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Squawk::onPasswordPromptAccepted()
|
|
||||||
{
|
|
||||||
emit responsePassword(requestedAccountsForPasswords.front(), prompt->textValue());
|
|
||||||
onPasswordPromptDone();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Squawk::onPasswordPromptDone()
|
|
||||||
{
|
|
||||||
prompt->deleteLater();
|
|
||||||
prompt = 0;
|
|
||||||
requestedAccountsForPasswords.pop_front();
|
|
||||||
checkNextAccountForPassword();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Squawk::onPasswordPromptRejected()
|
|
||||||
{
|
|
||||||
//for now it's the same on reject and on accept, but one day I'm gonna make
|
|
||||||
//"Asking for the password again on the authentication failure" feature
|
|
||||||
//and here I'll be able to break the circle of password requests
|
|
||||||
emit responsePassword(requestedAccountsForPasswords.front(), prompt->textValue());
|
|
||||||
onPasswordPromptDone();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Squawk::subscribeConversation(Conversation* conv)
|
|
||||||
{
|
|
||||||
connect(conv, &Conversation::destroyed, this, &Squawk::onConversationClosed);
|
|
||||||
connect(conv, &Conversation::sendMessage, this, &Squawk::onConversationMessage);
|
|
||||||
connect(conv, &Conversation::notifyableMessage, this, &Squawk::notify);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Squawk::onRosterSelectionChanged(const QModelIndex& current, const QModelIndex& previous)
|
void Squawk::onRosterSelectionChanged(const QModelIndex& current, const QModelIndex& previous)
|
||||||
{
|
{
|
||||||
if (restoreSelection.isValid() && restoreSelection == current) {
|
if (restoreSelection.isValid() && restoreSelection == current) {
|
||||||
@ -929,10 +539,10 @@ void Squawk::onRosterSelectionChanged(const QModelIndex& current, const QModelIn
|
|||||||
if (node->type == Models::Item::reference) {
|
if (node->type == Models::Item::reference) {
|
||||||
node = static_cast<Models::Reference*>(node)->dereference();
|
node = static_cast<Models::Reference*>(node)->dereference();
|
||||||
}
|
}
|
||||||
Models::Contact* contact = 0;
|
Models::Contact* contact = nullptr;
|
||||||
Models::Room* room = 0;
|
Models::Room* room = nullptr;
|
||||||
QString res;
|
QString res;
|
||||||
Models::Roster::ElId* id = 0;
|
Models::Roster::ElId* id = nullptr;
|
||||||
bool hasContext = true;
|
bool hasContext = true;
|
||||||
switch (node->type) {
|
switch (node->type) {
|
||||||
case Models::Item::contact:
|
case Models::Item::contact:
|
||||||
@ -961,7 +571,7 @@ void Squawk::onRosterSelectionChanged(const QModelIndex& current, const QModelIn
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (hasContext && QGuiApplication::mouseButtons() & Qt::RightButton) {
|
if (hasContext && QGuiApplication::mouseButtons() & Qt::RightButton) {
|
||||||
if (id != 0) {
|
if (id != nullptr) {
|
||||||
delete id;
|
delete id;
|
||||||
}
|
}
|
||||||
needToRestore = true;
|
needToRestore = true;
|
||||||
@ -969,10 +579,10 @@ void Squawk::onRosterSelectionChanged(const QModelIndex& current, const QModelIn
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (id != 0) {
|
if (id != nullptr) {
|
||||||
if (currentConversation != 0) {
|
if (currentConversation != nullptr) {
|
||||||
if (currentConversation->getId() == *id) {
|
if (currentConversation->getId() == *id) {
|
||||||
if (contact != 0) {
|
if (contact != nullptr) {
|
||||||
currentConversation->setPalResource(res);
|
currentConversation->setPalResource(res);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@ -984,20 +594,16 @@ void Squawk::onRosterSelectionChanged(const QModelIndex& current, const QModelIn
|
|||||||
}
|
}
|
||||||
|
|
||||||
Models::Account* acc = rosterModel.getAccount(id->account);
|
Models::Account* acc = rosterModel.getAccount(id->account);
|
||||||
if (contact != 0) {
|
if (contact != nullptr) {
|
||||||
currentConversation = new Chat(acc, contact);
|
currentConversation = new Chat(acc, contact);
|
||||||
} else if (room != 0) {
|
} else if (room != nullptr) {
|
||||||
currentConversation = new Room(acc, room);
|
currentConversation = new Room(acc, room);
|
||||||
|
|
||||||
if (!room->getJoined()) {
|
|
||||||
emit setRoomJoined(id->account, id->name, true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (!testAttribute(Qt::WA_TranslucentBackground)) {
|
if (!testAttribute(Qt::WA_TranslucentBackground)) {
|
||||||
currentConversation->setFeedFrames(true, false, true, true);
|
currentConversation->setFeedFrames(true, false, true, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
subscribeConversation(currentConversation);
|
emit openedConversation();
|
||||||
|
|
||||||
if (res.size() > 0) {
|
if (res.size() > 0) {
|
||||||
currentConversation->setPalResource(res);
|
currentConversation->setPalResource(res);
|
||||||
@ -1007,18 +613,10 @@ void Squawk::onRosterSelectionChanged(const QModelIndex& current, const QModelIn
|
|||||||
|
|
||||||
delete id;
|
delete id;
|
||||||
} else {
|
} else {
|
||||||
if (currentConversation != 0) {
|
closeCurrentConversation();
|
||||||
currentConversation->deleteLater();
|
|
||||||
currentConversation = 0;
|
|
||||||
m_ui->filler->show();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (currentConversation != 0) {
|
closeCurrentConversation();
|
||||||
currentConversation->deleteLater();
|
|
||||||
currentConversation = 0;
|
|
||||||
m_ui->filler->show();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1029,3 +627,30 @@ void Squawk::onContextAboutToHide()
|
|||||||
m_ui->roster->selectionModel()->setCurrentIndex(restoreSelection, QItemSelectionModel::ClearAndSelect);
|
m_ui->roster->selectionModel()->setCurrentIndex(restoreSelection, QItemSelectionModel::ClearAndSelect);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Squawk::onAboutSquawkCalled()
|
||||||
|
{
|
||||||
|
if (about == nullptr) {
|
||||||
|
about = new About(this);
|
||||||
|
about->setAttribute(Qt::WA_DeleteOnClose);
|
||||||
|
connect(about, &Settings::destroyed, this, &Squawk::onAboutSquawkClosed);
|
||||||
|
}
|
||||||
|
about->show();
|
||||||
|
about->raise();
|
||||||
|
about->activateWindow();
|
||||||
|
}
|
||||||
|
|
||||||
|
Models::Roster::ElId Squawk::currentConversationId() const
|
||||||
|
{
|
||||||
|
if (currentConversation == nullptr) {
|
||||||
|
return Models::Roster::ElId();
|
||||||
|
} else {
|
||||||
|
return Models::Roster::ElId(currentConversation->getAccount(), currentConversation->getJid());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Squawk::select(QModelIndex index)
|
||||||
|
{
|
||||||
|
m_ui->roster->scrollTo(index, QAbstractItemView::EnsureVisible);
|
||||||
|
m_ui->roster->selectionModel()->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect);
|
||||||
|
}
|
||||||
|
98
ui/squawk.h
98
ui/squawk.h
@ -22,7 +22,6 @@
|
|||||||
#include <QMainWindow>
|
#include <QMainWindow>
|
||||||
#include <QScopedPointer>
|
#include <QScopedPointer>
|
||||||
#include <QCloseEvent>
|
#include <QCloseEvent>
|
||||||
#include <QtDBus/QDBusInterface>
|
|
||||||
#include <QSettings>
|
#include <QSettings>
|
||||||
#include <QInputDialog>
|
#include <QInputDialog>
|
||||||
|
|
||||||
@ -31,137 +30,106 @@
|
|||||||
#include <set>
|
#include <set>
|
||||||
#include <list>
|
#include <list>
|
||||||
|
|
||||||
#include "widgets/accounts.h"
|
#include "widgets/accounts/accounts.h"
|
||||||
#include "widgets/chat.h"
|
#include "widgets/chat.h"
|
||||||
#include "widgets/room.h"
|
#include "widgets/room.h"
|
||||||
#include "widgets/newcontact.h"
|
#include "widgets/newcontact.h"
|
||||||
#include "widgets/joinconference.h"
|
#include "widgets/joinconference.h"
|
||||||
#include "models/roster.h"
|
#include "models/roster.h"
|
||||||
#include "widgets/vcard/vcard.h"
|
#include "widgets/vcard/vcard.h"
|
||||||
|
#include "widgets/settings/settings.h"
|
||||||
|
#include "widgets/about.h"
|
||||||
|
|
||||||
#include "shared.h"
|
#include "shared/shared.h"
|
||||||
|
#include "shared/global.h"
|
||||||
|
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
class Squawk;
|
class Squawk;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class Application;
|
||||||
|
|
||||||
class Squawk : public QMainWindow
|
class Squawk : public QMainWindow
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
friend class Application;
|
||||||
public:
|
public:
|
||||||
explicit Squawk(QWidget *parent = nullptr);
|
explicit Squawk(Models::Roster& rosterModel, QWidget *parent = nullptr);
|
||||||
~Squawk() override;
|
~Squawk() override;
|
||||||
|
|
||||||
void writeSettings();
|
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
|
void closing();
|
||||||
void newAccountRequest(const QMap<QString, QVariant>&);
|
void newAccountRequest(const QMap<QString, QVariant>&);
|
||||||
void modifyAccountRequest(const QString&, const QMap<QString, QVariant>&);
|
|
||||||
void removeAccountRequest(const QString&);
|
void removeAccountRequest(const QString&);
|
||||||
void connectAccount(const QString&);
|
void connectAccount(const QString&);
|
||||||
void disconnectAccount(const QString&);
|
void disconnectAccount(const QString&);
|
||||||
void changeState(Shared::Availability state);
|
void changeState(Shared::Availability state);
|
||||||
void sendMessage(const QString& account, const Shared::Message& data);
|
|
||||||
void requestArchive(const QString& account, const QString& jid, int count, const QString& before);
|
|
||||||
void subscribeContact(const QString& account, const QString& jid, const QString& reason);
|
|
||||||
void unsubscribeContact(const QString& account, const QString& jid, const QString& reason);
|
|
||||||
void removeContactRequest(const QString& account, const QString& jid);
|
void removeContactRequest(const QString& account, const QString& jid);
|
||||||
void addContactRequest(const QString& account, const QString& jid, const QString& name, const QSet<QString>& groups);
|
void addContactRequest(const QString& account, const QString& jid, const QString& name, const QSet<QString>& groups);
|
||||||
void addContactToGroupRequest(const QString& account, const QString& jid, const QString& groupName);
|
void addContactToGroupRequest(const QString& account, const QString& jid, const QString& groupName);
|
||||||
void removeContactFromGroupRequest(const QString& account, const QString& jid, const QString& groupName);
|
void removeContactFromGroupRequest(const QString& account, const QString& jid, const QString& groupName);
|
||||||
void renameContactRequest(const QString& account, const QString& jid, const QString& newName);
|
void renameContactRequest(const QString& account, const QString& jid, const QString& newName);
|
||||||
void setRoomJoined(const QString& account, const QString& jid, bool joined);
|
|
||||||
void setRoomAutoJoin(const QString& account, const QString& jid, bool joined);
|
|
||||||
void addRoomRequest(const QString& account, const QString& jid, const QString& nick, const QString& password, bool autoJoin);
|
void addRoomRequest(const QString& account, const QString& jid, const QString& nick, const QString& password, bool autoJoin);
|
||||||
void removeRoomRequest(const QString& account, const QString& jid);
|
void removeRoomRequest(const QString& account, const QString& jid);
|
||||||
void fileDownloadRequest(const QString& url);
|
|
||||||
void requestVCard(const QString& account, const QString& jid);
|
void requestVCard(const QString& account, const QString& jid);
|
||||||
void uploadVCard(const QString& account, const Shared::VCard& card);
|
void uploadVCard(const QString& account, const Shared::VCard& card);
|
||||||
void responsePassword(const QString& account, const QString& password);
|
void changeDownloadsPath(const QString& path);
|
||||||
void localPathInvalid(const QString& path);
|
|
||||||
|
void notify(const QString& account, const Shared::Message& msg);
|
||||||
|
void changeSubscription(const Models::Roster::ElId& id, bool subscribe);
|
||||||
|
void openedConversation();
|
||||||
|
void openConversation(const Models::Roster::ElId& id, const QString& resource = "");
|
||||||
|
|
||||||
|
void modifyAccountRequest(const QString&, const QMap<QString, QVariant>&);
|
||||||
|
|
||||||
|
public:
|
||||||
|
Models::Roster::ElId currentConversationId() const;
|
||||||
|
void closeCurrentConversation();
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void readSettings();
|
void writeSettings();
|
||||||
void newAccount(const QMap<QString, QVariant>& account);
|
|
||||||
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, const QString& group);
|
|
||||||
void removeContact(const QString& account, const QString& jid);
|
|
||||||
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 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, 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 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 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 select(QModelIndex index);
|
||||||
void requestPassword(const QString& account);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
typedef std::map<Models::Roster::ElId, Conversation*> Conversations;
|
|
||||||
QScopedPointer<Ui::Squawk> m_ui;
|
QScopedPointer<Ui::Squawk> m_ui;
|
||||||
|
|
||||||
Accounts* accounts;
|
Accounts* accounts;
|
||||||
Models::Roster rosterModel;
|
Settings* preferences;
|
||||||
Conversations conversations;
|
About* about;
|
||||||
|
Models::Roster& rosterModel;
|
||||||
QMenu* contextMenu;
|
QMenu* contextMenu;
|
||||||
QDBusInterface dbus;
|
|
||||||
std::map<QString, VCard*> vCards;
|
std::map<QString, VCard*> vCards;
|
||||||
std::deque<QString> requestedAccountsForPasswords;
|
|
||||||
QInputDialog* prompt;
|
|
||||||
Conversation* currentConversation;
|
Conversation* currentConversation;
|
||||||
QModelIndex restoreSelection;
|
QModelIndex restoreSelection;
|
||||||
bool needToRestore;
|
bool needToRestore;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void closeEvent(QCloseEvent * event) override;
|
void closeEvent(QCloseEvent * event) override;
|
||||||
|
void expand(const QModelIndex& index);
|
||||||
protected slots:
|
|
||||||
void notify(const QString& account, const Shared::Message& msg);
|
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void onAccounts();
|
void onAccounts();
|
||||||
|
void onPreferences();
|
||||||
void onNewContact();
|
void onNewContact();
|
||||||
void onNewConference();
|
void onNewConference();
|
||||||
void onNewContactAccepted();
|
void onNewContactAccepted();
|
||||||
void onJoinConferenceAccepted();
|
void onJoinConferenceAccepted();
|
||||||
void onAccountsSizeChanged(unsigned int size);
|
void onAccountsSizeChanged(unsigned int size);
|
||||||
void onAccountsClosed(QObject* parent = 0);
|
void onAccountsClosed();
|
||||||
void onConversationClosed(QObject* parent = 0);
|
void onPreferencesClosed();
|
||||||
void onVCardClosed();
|
void onVCardClosed();
|
||||||
void onVCardSave(const Shared::VCard& card, const QString& account);
|
void onVCardSave(const Shared::VCard& card, const QString& account);
|
||||||
void onActivateVCard(const QString& account, const QString& jid, bool edition = false);
|
void onActivateVCard(const QString& account, const QString& jid, bool edition = false);
|
||||||
void onComboboxActivated(int index);
|
void onComboboxActivated(int index);
|
||||||
void onRosterItemDoubleClicked(const QModelIndex& item);
|
void onRosterItemDoubleClicked(const QModelIndex& item);
|
||||||
void onConversationMessage(const Shared::Message& msg);
|
|
||||||
void onRequestArchive(const QString& account, const QString& jid, const QString& before);
|
|
||||||
void onRosterContextMenu(const QPoint& point);
|
void onRosterContextMenu(const QPoint& point);
|
||||||
void onItemCollepsed(const QModelIndex& index);
|
void onItemCollepsed(const QModelIndex& index);
|
||||||
void onPasswordPromptAccepted();
|
|
||||||
void onPasswordPromptRejected();
|
|
||||||
void onRosterSelectionChanged(const QModelIndex& current, const QModelIndex& previous);
|
void onRosterSelectionChanged(const QModelIndex& current, const QModelIndex& previous);
|
||||||
void onContextAboutToHide();
|
void onContextAboutToHide();
|
||||||
|
void onAboutSquawkCalled();
|
||||||
void onUnnoticedMessage(const QString& account, const Shared::Message& msg);
|
void onAboutSquawkClosed();
|
||||||
|
|
||||||
private:
|
|
||||||
void checkNextAccountForPassword();
|
|
||||||
void onPasswordPromptDone();
|
|
||||||
void subscribeConversation(Conversation* conv);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // SQUAWK_H
|
#endif // SQUAWK_H
|
||||||
|
65
ui/squawk.ui
65
ui/squawk.ui
@ -73,6 +73,9 @@
|
|||||||
<property name="bottomMargin">
|
<property name="bottomMargin">
|
||||||
<number>0</number>
|
<number>0</number>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="spacing">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
<item row="2" column="1">
|
<item row="2" column="1">
|
||||||
<widget class="QTreeView" name="roster">
|
<widget class="QTreeView" name="roster">
|
||||||
<property name="frameShape">
|
<property name="frameShape">
|
||||||
@ -96,16 +99,31 @@
|
|||||||
<property name="allColumnsShowFocus">
|
<property name="allColumnsShowFocus">
|
||||||
<bool>true</bool>
|
<bool>true</bool>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="headerHidden">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
<property name="expandsOnDoubleClick">
|
<property name="expandsOnDoubleClick">
|
||||||
<bool>false</bool>
|
<bool>false</bool>
|
||||||
</property>
|
</property>
|
||||||
<attribute name="headerVisible">
|
<attribute name="headerVisible">
|
||||||
<bool>false</bool>
|
<bool>false</bool>
|
||||||
</attribute>
|
</attribute>
|
||||||
|
<attribute name="headerMinimumSectionSize">
|
||||||
|
<number>10</number>
|
||||||
|
</attribute>
|
||||||
|
<attribute name="headerStretchLastSection">
|
||||||
|
<bool>false</bool>
|
||||||
|
</attribute>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="1" column="1">
|
<item row="1" column="1">
|
||||||
<widget class="QComboBox" name="comboBox">
|
<widget class="QComboBox" name="comboBox">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>0</width>
|
||||||
|
<height>30</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
<property name="editable">
|
<property name="editable">
|
||||||
<bool>false</bool>
|
<bool>false</bool>
|
||||||
</property>
|
</property>
|
||||||
@ -115,6 +133,9 @@
|
|||||||
<property name="currentIndex">
|
<property name="currentIndex">
|
||||||
<number>-1</number>
|
<number>-1</number>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="frame">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
@ -162,7 +183,7 @@
|
|||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>718</width>
|
<width>718</width>
|
||||||
<height>27</height>
|
<height>32</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<widget class="QMenu" name="menuSettings">
|
<widget class="QMenu" name="menuSettings">
|
||||||
@ -170,6 +191,7 @@
|
|||||||
<string>Settings</string>
|
<string>Settings</string>
|
||||||
</property>
|
</property>
|
||||||
<addaction name="actionAccounts"/>
|
<addaction name="actionAccounts"/>
|
||||||
|
<addaction name="actionPreferences"/>
|
||||||
</widget>
|
</widget>
|
||||||
<widget class="QMenu" name="menuFile">
|
<widget class="QMenu" name="menuFile">
|
||||||
<property name="title">
|
<property name="title">
|
||||||
@ -179,13 +201,20 @@
|
|||||||
<addaction name="actionAddConference"/>
|
<addaction name="actionAddConference"/>
|
||||||
<addaction name="actionQuit"/>
|
<addaction name="actionQuit"/>
|
||||||
</widget>
|
</widget>
|
||||||
|
<widget class="QMenu" name="menuHelp">
|
||||||
|
<property name="title">
|
||||||
|
<string>Help</string>
|
||||||
|
</property>
|
||||||
|
<addaction name="actionAboutSquawk"/>
|
||||||
|
</widget>
|
||||||
<addaction name="menuFile"/>
|
<addaction name="menuFile"/>
|
||||||
<addaction name="menuSettings"/>
|
<addaction name="menuSettings"/>
|
||||||
|
<addaction name="menuHelp"/>
|
||||||
</widget>
|
</widget>
|
||||||
<action name="actionAccounts">
|
<action name="actionAccounts">
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset theme="system-users">
|
<iconset theme="system-users" resource="../resources/resources.qrc">
|
||||||
<normaloff>.</normaloff>.</iconset>
|
<normaloff>:/images/fallback/dark/big/group.svg</normaloff>:/images/fallback/dark/big/group.svg</iconset>
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Accounts</string>
|
<string>Accounts</string>
|
||||||
@ -193,8 +222,8 @@
|
|||||||
</action>
|
</action>
|
||||||
<action name="actionQuit">
|
<action name="actionQuit">
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset theme="application-exit">
|
<iconset theme="application-exit" resource="../resources/resources.qrc">
|
||||||
<normaloff>.</normaloff>.</iconset>
|
<normaloff>:/images/fallback/dark/big/edit-none.svg</normaloff>:/images/fallback/dark/big/edit-none.svg</iconset>
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Quit</string>
|
<string>Quit</string>
|
||||||
@ -205,8 +234,8 @@
|
|||||||
<bool>false</bool>
|
<bool>false</bool>
|
||||||
</property>
|
</property>
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset theme="list-add-user">
|
<iconset theme="list-add-user" resource="../resources/resources.qrc">
|
||||||
<normaloff>.</normaloff>.</iconset>
|
<normaloff>:/images/fallback/dark/big/add.svg</normaloff>:/images/fallback/dark/big/add.svg</iconset>
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Add contact</string>
|
<string>Add contact</string>
|
||||||
@ -217,14 +246,30 @@
|
|||||||
<bool>false</bool>
|
<bool>false</bool>
|
||||||
</property>
|
</property>
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset theme="resource-group-new">
|
<iconset theme="resource-group-new" resource="../resources/resources.qrc">
|
||||||
<normaloff>.</normaloff>.</iconset>
|
<normaloff>:/images/fallback/dark/big/group-new.svg</normaloff>:/images/fallback/dark/big/group-new.svg</iconset>
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Add conference</string>
|
<string>Add conference</string>
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
|
<action name="actionPreferences">
|
||||||
|
<property name="icon">
|
||||||
|
<iconset theme="settings-configure">
|
||||||
|
<normaloff>.</normaloff>.</iconset>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Preferences</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
|
<action name="actionAboutSquawk">
|
||||||
|
<property name="text">
|
||||||
|
<string>About Squawk</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
</widget>
|
</widget>
|
||||||
<resources/>
|
<resources>
|
||||||
|
<include location="../resources/resources.qrc"/>
|
||||||
|
</resources>
|
||||||
<connections/>
|
<connections/>
|
||||||
</ui>
|
</ui>
|
||||||
|
@ -1,32 +1,18 @@
|
|||||||
cmake_minimum_required(VERSION 3.3)
|
target_sources(squawk PRIVATE
|
||||||
project(squawkUIUtils)
|
|
||||||
|
|
||||||
# Instruct CMake to run moc automatically when needed.
|
|
||||||
set(CMAKE_AUTOMOC ON)
|
|
||||||
# Instruct CMake to create code from Qt designer ui files
|
|
||||||
set(CMAKE_AUTOUIC ON)
|
|
||||||
|
|
||||||
# Find the QtWidgets library
|
|
||||||
find_package(Qt5 CONFIG REQUIRED COMPONENTS Widgets Core)
|
|
||||||
|
|
||||||
set(squawkUIUtils_SRC
|
|
||||||
# messageline.cpp
|
|
||||||
# message.cpp
|
|
||||||
resizer.cpp
|
|
||||||
# image.cpp
|
|
||||||
flowlayout.cpp
|
|
||||||
badge.cpp
|
badge.cpp
|
||||||
progress.cpp
|
badge.h
|
||||||
comboboxdelegate.cpp
|
comboboxdelegate.cpp
|
||||||
feedview.cpp
|
comboboxdelegate.h
|
||||||
messagedelegate.cpp
|
|
||||||
exponentialblur.cpp
|
exponentialblur.cpp
|
||||||
|
exponentialblur.h
|
||||||
|
flowlayout.cpp
|
||||||
|
flowlayout.h
|
||||||
|
image.cpp
|
||||||
|
image.h
|
||||||
|
progress.cpp
|
||||||
|
progress.h
|
||||||
|
resizer.cpp
|
||||||
|
resizer.h
|
||||||
shadowoverlay.cpp
|
shadowoverlay.cpp
|
||||||
|
shadowoverlay.h
|
||||||
)
|
)
|
||||||
|
|
||||||
# Tell CMake to create the helloworld executable
|
|
||||||
add_library(squawkUIUtils STATIC ${squawkUIUtils_SRC})
|
|
||||||
|
|
||||||
# Use the Widgets module from Qt 5.
|
|
||||||
target_link_libraries(squawkUIUtils squawkWidgets)
|
|
||||||
target_link_libraries(squawkUIUtils Qt5::Widgets)
|
|
||||||
|
@ -26,27 +26,62 @@ Badge::Badge(const QString& p_id, const QString& p_text, const QIcon& icon, QWid
|
|||||||
closeButton(new QPushButton()),
|
closeButton(new QPushButton()),
|
||||||
layout(new QHBoxLayout(this))
|
layout(new QHBoxLayout(this))
|
||||||
{
|
{
|
||||||
setBackgroundRole(QPalette::Base);
|
createMandatoryComponents();
|
||||||
//setAutoFillBackground(true);
|
|
||||||
setFrameStyle(QFrame::StyledPanel);
|
|
||||||
setFrameShadow(QFrame::Raised);
|
|
||||||
|
|
||||||
image->setPixmap(icon.pixmap(25, 25));
|
image->setPixmap(icon.pixmap(25, 25));
|
||||||
closeButton->setIcon(QIcon::fromTheme("tab-close"));
|
|
||||||
closeButton->setMaximumHeight(25);
|
|
||||||
closeButton->setMaximumWidth(25);
|
|
||||||
|
|
||||||
layout->addWidget(image);
|
layout->addWidget(image);
|
||||||
layout->addWidget(text);
|
layout->addWidget(text);
|
||||||
layout->addWidget(closeButton);
|
layout->addWidget(closeButton);
|
||||||
|
}
|
||||||
|
|
||||||
layout->setContentsMargins(2, 2, 2, 2);
|
Badge::Badge(QWidget* parent):
|
||||||
|
QFrame(parent),
|
||||||
|
id(Shared::generateUUID()),
|
||||||
|
image(nullptr),
|
||||||
|
text(nullptr),
|
||||||
|
closeButton(new QPushButton()),
|
||||||
|
layout(new QHBoxLayout(this))
|
||||||
|
{
|
||||||
|
createMandatoryComponents();
|
||||||
|
|
||||||
connect(closeButton, &QPushButton::clicked, this, &Badge::close);
|
layout->addWidget(closeButton);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Badge::setIcon(const QIcon& icon)
|
||||||
|
{
|
||||||
|
if (image == nullptr) {
|
||||||
|
image = new QLabel();
|
||||||
|
image->setPixmap(icon.pixmap(25, 25));
|
||||||
|
layout->insertWidget(0, image);
|
||||||
|
} else {
|
||||||
|
image->setPixmap(icon.pixmap(25, 25));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Badge::setText(const QString& p_text)
|
||||||
|
{
|
||||||
|
if (text == nullptr) {
|
||||||
|
text = new QLabel(p_text);
|
||||||
|
int index = 0;
|
||||||
|
if (image != nullptr) {
|
||||||
|
index = 1;
|
||||||
|
}
|
||||||
|
layout->insertWidget(index, text);
|
||||||
|
} else {
|
||||||
|
text->setText(p_text);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Badge::~Badge()
|
Badge::~Badge()
|
||||||
{
|
{
|
||||||
|
if (image != nullptr) {
|
||||||
|
delete image;
|
||||||
|
}
|
||||||
|
if (text != nullptr) {
|
||||||
|
delete text;
|
||||||
|
}
|
||||||
|
delete closeButton;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Badge::Comparator::operator()(const Badge* a, const Badge* b) const
|
bool Badge::Comparator::operator()(const Badge* a, const Badge* b) const
|
||||||
@ -58,3 +93,22 @@ bool Badge::Comparator::operator()(const Badge& a, const Badge& b) const
|
|||||||
{
|
{
|
||||||
return a.id < b.id;
|
return a.id < b.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Badge::createMandatoryComponents()
|
||||||
|
{
|
||||||
|
setBackgroundRole(QPalette::Base);
|
||||||
|
//setAutoFillBackground(true);
|
||||||
|
setFrameStyle(QFrame::StyledPanel);
|
||||||
|
setFrameShadow(QFrame::Raised);
|
||||||
|
|
||||||
|
QIcon tabCloseIcon = QIcon::fromTheme("tab-close");
|
||||||
|
if (tabCloseIcon.isNull()) {
|
||||||
|
tabCloseIcon.addFile(QString::fromUtf8(":/images/fallback/dark/big/edit-none.svg"), QSize(), QIcon::Normal, QIcon::Off);
|
||||||
|
}
|
||||||
|
closeButton->setIcon(tabCloseIcon);
|
||||||
|
|
||||||
|
closeButton->setMaximumHeight(25);
|
||||||
|
closeButton->setMaximumWidth(25);
|
||||||
|
layout->setContentsMargins(2, 2, 2, 2);
|
||||||
|
connect(closeButton, &QPushButton::clicked, this, &Badge::close);
|
||||||
|
}
|
||||||
|
@ -25,6 +25,8 @@
|
|||||||
#include <QIcon>
|
#include <QIcon>
|
||||||
#include <QPushButton>
|
#include <QPushButton>
|
||||||
|
|
||||||
|
#include "shared/utils.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @todo write docs
|
* @todo write docs
|
||||||
*/
|
*/
|
||||||
@ -33,10 +35,15 @@ class Badge : public QFrame
|
|||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
Badge(const QString& id, const QString& text, const QIcon& icon, QWidget* parent = nullptr);
|
Badge(const QString& id, const QString& text, const QIcon& icon, QWidget* parent = nullptr);
|
||||||
|
Badge(QWidget* parent = nullptr);
|
||||||
~Badge();
|
~Badge();
|
||||||
|
|
||||||
const QString id;
|
const QString id;
|
||||||
|
|
||||||
|
public:
|
||||||
|
void setText(const QString& text);
|
||||||
|
void setIcon(const QIcon& icon);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void close();
|
void close();
|
||||||
|
|
||||||
@ -46,6 +53,9 @@ private:
|
|||||||
QPushButton* closeButton;
|
QPushButton* closeButton;
|
||||||
QHBoxLayout* layout;
|
QHBoxLayout* layout;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void createMandatoryComponents();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
struct Comparator {
|
struct Comparator {
|
||||||
bool operator()(const Badge& a, const Badge& b) const;
|
bool operator()(const Badge& a, const Badge& b) const;
|
||||||
|
@ -1,344 +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/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "message.h"
|
|
||||||
#include <QDebug>
|
|
||||||
#include <QMimeDatabase>
|
|
||||||
#include <QPixmap>
|
|
||||||
#include <QFileInfo>
|
|
||||||
#include <QRegularExpression>
|
|
||||||
|
|
||||||
Message::Message(const Shared::Message& source, bool p_outgoing, const QString& p_sender, const QString& avatarPath, QWidget* parent):
|
|
||||||
QWidget(parent),
|
|
||||||
outgoing(p_outgoing),
|
|
||||||
msg(source),
|
|
||||||
body(new QWidget()),
|
|
||||||
statusBar(new QWidget()),
|
|
||||||
bodyLayout(new QVBoxLayout(body)),
|
|
||||||
layout(new QHBoxLayout(this)),
|
|
||||||
date(new QLabel(msg.getTime().toLocalTime().toString())),
|
|
||||||
sender(new QLabel(p_sender)),
|
|
||||||
text(new QLabel()),
|
|
||||||
shadow(new QGraphicsDropShadowEffect()),
|
|
||||||
button(0),
|
|
||||||
file(0),
|
|
||||||
progress(0),
|
|
||||||
fileComment(new QLabel()),
|
|
||||||
statusIcon(0),
|
|
||||||
editedLabel(0),
|
|
||||||
avatar(new Image(avatarPath.size() == 0 ? Shared::iconPath("user", true) : avatarPath, 60)),
|
|
||||||
hasButton(false),
|
|
||||||
hasProgress(false),
|
|
||||||
hasFile(false),
|
|
||||||
commentAdded(false),
|
|
||||||
hasStatusIcon(false),
|
|
||||||
hasEditedLabel(false)
|
|
||||||
{
|
|
||||||
setContentsMargins(0, 0, 0, 0);
|
|
||||||
layout->setContentsMargins(10, 5, 10, 5);
|
|
||||||
body->setBackgroundRole(QPalette::AlternateBase);
|
|
||||||
body->setAutoFillBackground(true);
|
|
||||||
|
|
||||||
QString bd = Shared::processMessageBody(msg.getBody());
|
|
||||||
text->setTextFormat(Qt::RichText);
|
|
||||||
text->setText(bd);;
|
|
||||||
text->setTextInteractionFlags(text->textInteractionFlags() | Qt::TextSelectableByMouse | Qt::LinksAccessibleByMouse);
|
|
||||||
text->setWordWrap(true);
|
|
||||||
text->setOpenExternalLinks(true);
|
|
||||||
if (bd.size() == 0) {
|
|
||||||
text->hide();
|
|
||||||
}
|
|
||||||
|
|
||||||
QFont dFont = date->font();
|
|
||||||
dFont.setItalic(true);
|
|
||||||
dFont.setPointSize(dFont.pointSize() - 2);
|
|
||||||
date->setFont(dFont);
|
|
||||||
|
|
||||||
QFont f;
|
|
||||||
f.setBold(true);
|
|
||||||
sender->setFont(f);
|
|
||||||
|
|
||||||
bodyLayout->addWidget(sender);
|
|
||||||
bodyLayout->addWidget(text);
|
|
||||||
|
|
||||||
shadow->setBlurRadius(10);
|
|
||||||
shadow->setXOffset(1);
|
|
||||||
shadow->setYOffset(1);
|
|
||||||
shadow->setColor(Qt::black);
|
|
||||||
body->setGraphicsEffect(shadow);
|
|
||||||
avatar->setMaximumHeight(60);
|
|
||||||
avatar->setMaximumWidth(60);
|
|
||||||
|
|
||||||
statusBar->setContentsMargins(0, 0, 0, 0);
|
|
||||||
QHBoxLayout* statusLay = new QHBoxLayout();
|
|
||||||
statusLay->setContentsMargins(0, 0, 0, 0);
|
|
||||||
statusBar->setLayout(statusLay);
|
|
||||||
|
|
||||||
if (outgoing) {
|
|
||||||
sender->setAlignment(Qt::AlignRight);
|
|
||||||
date->setAlignment(Qt::AlignRight);
|
|
||||||
statusIcon = new QLabel();
|
|
||||||
setState();
|
|
||||||
statusLay->addWidget(statusIcon);
|
|
||||||
statusLay->addWidget(date);
|
|
||||||
layout->addStretch();
|
|
||||||
layout->addWidget(body);
|
|
||||||
layout->addWidget(avatar);
|
|
||||||
hasStatusIcon = true;
|
|
||||||
} else {
|
|
||||||
layout->addWidget(avatar);
|
|
||||||
layout->addWidget(body);
|
|
||||||
layout->addStretch();
|
|
||||||
statusLay->addWidget(date);
|
|
||||||
}
|
|
||||||
if (msg.getEdited()) {
|
|
||||||
setEdited();
|
|
||||||
}
|
|
||||||
|
|
||||||
bodyLayout->addWidget(statusBar);
|
|
||||||
layout->setAlignment(avatar, Qt::AlignTop);
|
|
||||||
}
|
|
||||||
|
|
||||||
Message::~Message()
|
|
||||||
{
|
|
||||||
if (!commentAdded) {
|
|
||||||
delete fileComment;
|
|
||||||
}
|
|
||||||
//delete body; //not sure if I should delete it here, it's probably already owned by the infrastructure and gonna die with the rest of the widget
|
|
||||||
//delete avatar;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString Message::getId() const
|
|
||||||
{
|
|
||||||
return msg.getId();
|
|
||||||
}
|
|
||||||
|
|
||||||
QString Message::getSenderJid() const
|
|
||||||
{
|
|
||||||
return msg.getFromJid();
|
|
||||||
}
|
|
||||||
|
|
||||||
QString Message::getSenderResource() const
|
|
||||||
{
|
|
||||||
return msg.getFromResource();
|
|
||||||
}
|
|
||||||
|
|
||||||
QString Message::getFileUrl() const
|
|
||||||
{
|
|
||||||
return msg.getOutOfBandUrl();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Message::setSender(const QString& p_sender)
|
|
||||||
{
|
|
||||||
sender->setText(p_sender);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Message::addButton(const QIcon& icon, const QString& buttonText, const QString& tooltip)
|
|
||||||
{
|
|
||||||
hideFile();
|
|
||||||
hideProgress();
|
|
||||||
if (!hasButton) {
|
|
||||||
hideComment();
|
|
||||||
if (msg.getBody() == msg.getOutOfBandUrl()) {
|
|
||||||
text->setText("");
|
|
||||||
text->hide();
|
|
||||||
}
|
|
||||||
button = new QPushButton(icon, buttonText);
|
|
||||||
button->setToolTip(tooltip);
|
|
||||||
connect(button, &QPushButton::clicked, this, &Message::buttonClicked);
|
|
||||||
bodyLayout->insertWidget(2, button);
|
|
||||||
hasButton = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Message::setProgress(qreal value)
|
|
||||||
{
|
|
||||||
hideFile();
|
|
||||||
hideButton();
|
|
||||||
if (!hasProgress) {
|
|
||||||
hideComment();
|
|
||||||
if (msg.getBody() == msg.getOutOfBandUrl()) {
|
|
||||||
text->setText("");
|
|
||||||
text->hide();
|
|
||||||
}
|
|
||||||
progress = new QProgressBar();
|
|
||||||
progress->setRange(0, 100);
|
|
||||||
bodyLayout->insertWidget(2, progress);
|
|
||||||
hasProgress = true;
|
|
||||||
}
|
|
||||||
progress->setValue(value * 100);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Message::showFile(const QString& path)
|
|
||||||
{
|
|
||||||
hideButton();
|
|
||||||
hideProgress();
|
|
||||||
if (!hasFile) {
|
|
||||||
hideComment();
|
|
||||||
if (msg.getBody() == msg.getOutOfBandUrl()) {
|
|
||||||
text->setText("");
|
|
||||||
text->hide();
|
|
||||||
}
|
|
||||||
QMimeDatabase db;
|
|
||||||
QMimeType type = db.mimeTypeForFile(path);
|
|
||||||
QStringList parts = type.name().split("/");
|
|
||||||
QString big = parts.front();
|
|
||||||
QFileInfo info(path);
|
|
||||||
if (big == "image") {
|
|
||||||
file = new Image(path);
|
|
||||||
} else {
|
|
||||||
file = new QLabel();
|
|
||||||
file->setPixmap(QIcon::fromTheme(type.iconName()).pixmap(50));
|
|
||||||
file->setAlignment(Qt::AlignCenter);
|
|
||||||
showComment(info.fileName(), true);
|
|
||||||
}
|
|
||||||
file->setContextMenuPolicy(Qt::ActionsContextMenu);
|
|
||||||
QAction* openAction = new QAction(QIcon::fromTheme("document-new-from-template"), tr("Open"), file);
|
|
||||||
connect(openAction, &QAction::triggered, [path]() { //TODO need to get rid of this shame
|
|
||||||
QDesktopServices::openUrl(QUrl::fromLocalFile(path));
|
|
||||||
});
|
|
||||||
file->addAction(openAction);
|
|
||||||
bodyLayout->insertWidget(2, file);
|
|
||||||
hasFile = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Message::hideComment()
|
|
||||||
{
|
|
||||||
if (commentAdded) {
|
|
||||||
bodyLayout->removeWidget(fileComment);
|
|
||||||
fileComment->hide();
|
|
||||||
fileComment->setWordWrap(false);
|
|
||||||
commentAdded = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Message::hideButton()
|
|
||||||
{
|
|
||||||
if (hasButton) {
|
|
||||||
button->deleteLater();
|
|
||||||
button = 0;
|
|
||||||
hasButton = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Message::hideFile()
|
|
||||||
{
|
|
||||||
if (hasFile) {
|
|
||||||
file->deleteLater();
|
|
||||||
file = 0;
|
|
||||||
hasFile = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Message::hideProgress()
|
|
||||||
{
|
|
||||||
if (hasProgress) {
|
|
||||||
progress->deleteLater();
|
|
||||||
progress = 0;
|
|
||||||
hasProgress = false;;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
void Message::showComment(const QString& comment, bool wordWrap)
|
|
||||||
{
|
|
||||||
if (!commentAdded) {
|
|
||||||
int index = 2;
|
|
||||||
if (hasFile) {
|
|
||||||
index++;
|
|
||||||
}
|
|
||||||
if (hasButton) {
|
|
||||||
index++;
|
|
||||||
}
|
|
||||||
if (hasProgress) {
|
|
||||||
index++;
|
|
||||||
}
|
|
||||||
bodyLayout->insertWidget(index, fileComment);
|
|
||||||
fileComment->show();
|
|
||||||
commentAdded = true;
|
|
||||||
}
|
|
||||||
fileComment->setWordWrap(wordWrap);
|
|
||||||
fileComment->setText(comment);
|
|
||||||
}
|
|
||||||
|
|
||||||
const Shared::Message & Message::getMessage() const
|
|
||||||
{
|
|
||||||
return msg;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Message::setAvatarPath(const QString& p_path)
|
|
||||||
{
|
|
||||||
if (p_path.size() == 0) {
|
|
||||||
avatar->setPath(Shared::iconPath("user", true));
|
|
||||||
} else {
|
|
||||||
avatar->setPath(p_path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Message::change(const QMap<QString, QVariant>& data)
|
|
||||||
{
|
|
||||||
bool idChanged = msg.change(data);
|
|
||||||
|
|
||||||
QString body = msg.getBody();
|
|
||||||
QString bd = Shared::processMessageBody(body);
|
|
||||||
if (body.size() > 0) {
|
|
||||||
text->setText(bd);
|
|
||||||
text->show();
|
|
||||||
} else {
|
|
||||||
text->setText(body);
|
|
||||||
text->hide();
|
|
||||||
}
|
|
||||||
if (msg.getEdited()) {
|
|
||||||
setEdited();
|
|
||||||
}
|
|
||||||
if (hasStatusIcon) {
|
|
||||||
setState();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
return idChanged;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Message::setEdited()
|
|
||||||
{
|
|
||||||
if (!hasEditedLabel) {
|
|
||||||
editedLabel = new QLabel();
|
|
||||||
hasEditedLabel = true;
|
|
||||||
QIcon q(Shared::icon("edit-rename"));
|
|
||||||
editedLabel->setPixmap(q.pixmap(12, 12));
|
|
||||||
QHBoxLayout* statusLay = static_cast<QHBoxLayout*>(statusBar->layout());
|
|
||||||
statusLay->insertWidget(1, editedLabel);
|
|
||||||
}
|
|
||||||
editedLabel->setToolTip("Last time edited: " + msg.getLastModified().toLocalTime().toString()
|
|
||||||
+ "\nOriginal message: " + msg.getOriginalBody());
|
|
||||||
}
|
|
||||||
|
|
||||||
void Message::setState()
|
|
||||||
{
|
|
||||||
Shared::Message::State state = msg.getState();
|
|
||||||
QIcon q(Shared::icon(Shared::messageStateThemeIcons[static_cast<uint8_t>(state)]));
|
|
||||||
QString tt = Shared::Global::getName(state);
|
|
||||||
if (state == Shared::Message::State::error) {
|
|
||||||
QString errText = msg.getErrorText();
|
|
||||||
if (errText.size() > 0) {
|
|
||||||
tt += ": " + errText;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
statusIcon->setToolTip(tt);
|
|
||||||
statusIcon->setPixmap(q.pixmap(12, 12));
|
|
||||||
}
|
|
||||||
|
|
@ -1,103 +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 MESSAGE_H
|
|
||||||
#define MESSAGE_H
|
|
||||||
|
|
||||||
#include <QWidget>
|
|
||||||
#include <QHBoxLayout>
|
|
||||||
#include <QVBoxLayout>
|
|
||||||
#include <QLabel>
|
|
||||||
#include <QGraphicsDropShadowEffect>
|
|
||||||
#include <QPushButton>
|
|
||||||
#include <QProgressBar>
|
|
||||||
#include <QAction>
|
|
||||||
#include <QDesktopServices>
|
|
||||||
#include <QUrl>
|
|
||||||
#include <QMap>
|
|
||||||
|
|
||||||
#include "shared/message.h"
|
|
||||||
#include "shared/icons.h"
|
|
||||||
#include "shared/global.h"
|
|
||||||
#include "shared/utils.h"
|
|
||||||
#include "resizer.h"
|
|
||||||
#include "image.h"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @todo write docs
|
|
||||||
*/
|
|
||||||
class Message : public QWidget
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
Message(const Shared::Message& source, bool outgoing, const QString& sender, const QString& avatarPath = "", QWidget* parent = nullptr);
|
|
||||||
~Message();
|
|
||||||
|
|
||||||
void setSender(const QString& sender);
|
|
||||||
QString getId() const;
|
|
||||||
QString getSenderJid() const;
|
|
||||||
QString getSenderResource() const;
|
|
||||||
QString getFileUrl() const;
|
|
||||||
const Shared::Message& getMessage() const;
|
|
||||||
|
|
||||||
void addButton(const QIcon& icon, const QString& buttonText, const QString& tooltip = "");
|
|
||||||
void showComment(const QString& comment, bool wordWrap = false);
|
|
||||||
void hideComment();
|
|
||||||
void showFile(const QString& path);
|
|
||||||
void setProgress(qreal value);
|
|
||||||
void setAvatarPath(const QString& p_path);
|
|
||||||
bool change(const QMap<QString, QVariant>& data);
|
|
||||||
|
|
||||||
bool const outgoing;
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void buttonClicked();
|
|
||||||
|
|
||||||
private:
|
|
||||||
Shared::Message msg;
|
|
||||||
QWidget* body;
|
|
||||||
QWidget* statusBar;
|
|
||||||
QVBoxLayout* bodyLayout;
|
|
||||||
QHBoxLayout* layout;
|
|
||||||
QLabel* date;
|
|
||||||
QLabel* sender;
|
|
||||||
QLabel* text;
|
|
||||||
QGraphicsDropShadowEffect* shadow;
|
|
||||||
QPushButton* button;
|
|
||||||
QLabel* file;
|
|
||||||
QProgressBar* progress;
|
|
||||||
QLabel* fileComment;
|
|
||||||
QLabel* statusIcon;
|
|
||||||
QLabel* editedLabel;
|
|
||||||
Image* avatar;
|
|
||||||
bool hasButton;
|
|
||||||
bool hasProgress;
|
|
||||||
bool hasFile;
|
|
||||||
bool commentAdded;
|
|
||||||
bool hasStatusIcon;
|
|
||||||
bool hasEditedLabel;
|
|
||||||
|
|
||||||
private:
|
|
||||||
void hideButton();
|
|
||||||
void hideProgress();
|
|
||||||
void hideFile();
|
|
||||||
void setState();
|
|
||||||
void setEdited();
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // MESSAGE_H
|
|
@ -1,549 +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/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <QDebug>
|
|
||||||
#include <QPainter>
|
|
||||||
#include <QApplication>
|
|
||||||
#include <QMouseEvent>
|
|
||||||
|
|
||||||
#include "messagedelegate.h"
|
|
||||||
#include "ui/models/messagefeed.h"
|
|
||||||
|
|
||||||
constexpr int avatarHeight = 50;
|
|
||||||
constexpr int margin = 6;
|
|
||||||
constexpr int textMargin = 2;
|
|
||||||
constexpr int statusIconSize = 16;
|
|
||||||
constexpr int maxAttachmentHeight = 500;
|
|
||||||
|
|
||||||
MessageDelegate::MessageDelegate(QObject* parent):
|
|
||||||
QStyledItemDelegate(parent),
|
|
||||||
bodyFont(),
|
|
||||||
nickFont(),
|
|
||||||
dateFont(),
|
|
||||||
bodyMetrics(bodyFont),
|
|
||||||
nickMetrics(nickFont),
|
|
||||||
dateMetrics(dateFont),
|
|
||||||
buttonHeight(0),
|
|
||||||
barHeight(0),
|
|
||||||
buttons(new std::map<QString, FeedButton*>()),
|
|
||||||
bars(new std::map<QString, QProgressBar*>()),
|
|
||||||
statusIcons(new std::map<QString, QLabel*>()),
|
|
||||||
bodies(new std::map<QString, QLabel*>()),
|
|
||||||
idsToKeep(new std::set<QString>()),
|
|
||||||
clearingWidgets(false)
|
|
||||||
{
|
|
||||||
QPushButton btn;
|
|
||||||
buttonHeight = btn.sizeHint().height();
|
|
||||||
|
|
||||||
QProgressBar bar;
|
|
||||||
barHeight = bar.sizeHint().height();
|
|
||||||
}
|
|
||||||
|
|
||||||
MessageDelegate::~MessageDelegate()
|
|
||||||
{
|
|
||||||
for (const std::pair<const QString, FeedButton*>& pair: *buttons){
|
|
||||||
delete pair.second;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const std::pair<const QString, QProgressBar*>& pair: *bars){
|
|
||||||
delete pair.second;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const std::pair<const QString, QLabel*>& pair: *statusIcons){
|
|
||||||
delete pair.second;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const std::pair<const QString, QLabel*>& pair: *bodies){
|
|
||||||
delete pair.second;
|
|
||||||
}
|
|
||||||
|
|
||||||
delete idsToKeep;
|
|
||||||
delete buttons;
|
|
||||||
delete bars;
|
|
||||||
delete bodies;
|
|
||||||
}
|
|
||||||
|
|
||||||
void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
|
|
||||||
{
|
|
||||||
QVariant vi = index.data(Models::MessageFeed::Bulk);
|
|
||||||
if (!vi.isValid()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Models::FeedItem data = qvariant_cast<Models::FeedItem>(vi);
|
|
||||||
painter->save();
|
|
||||||
painter->setRenderHint(QPainter::Antialiasing, true);
|
|
||||||
|
|
||||||
if (option.state & QStyle::State_MouseOver) {
|
|
||||||
painter->fillRect(option.rect, option.palette.brush(QPalette::Inactive, QPalette::Highlight));
|
|
||||||
}
|
|
||||||
|
|
||||||
QIcon icon(data.avatar);
|
|
||||||
|
|
||||||
if (data.sentByMe) {
|
|
||||||
painter->drawPixmap(option.rect.width() - avatarHeight - margin, option.rect.y() + margin / 2, icon.pixmap(avatarHeight, avatarHeight));
|
|
||||||
} else {
|
|
||||||
painter->drawPixmap(margin, option.rect.y() + margin / 2, icon.pixmap(avatarHeight, avatarHeight));
|
|
||||||
}
|
|
||||||
|
|
||||||
QStyleOptionViewItem opt = option;
|
|
||||||
QRect messageRect = option.rect.adjusted(margin, margin / 2, -(avatarHeight + 2 * margin), -margin / 2);
|
|
||||||
if (!data.sentByMe) {
|
|
||||||
opt.displayAlignment = Qt::AlignLeft | Qt::AlignTop;
|
|
||||||
messageRect.adjust(avatarHeight + margin, 0, avatarHeight + margin, 0);
|
|
||||||
} else {
|
|
||||||
opt.displayAlignment = Qt::AlignRight | Qt::AlignTop;
|
|
||||||
}
|
|
||||||
opt.rect = messageRect;
|
|
||||||
|
|
||||||
QSize messageSize(0, 0);
|
|
||||||
QSize bodySize(0, 0);
|
|
||||||
if (data.text.size() > 0) {
|
|
||||||
messageSize = bodyMetrics.boundingRect(messageRect, Qt::TextWordWrap, data.text).size();
|
|
||||||
bodySize = messageSize;
|
|
||||||
}
|
|
||||||
messageSize.rheight() += nickMetrics.lineSpacing();
|
|
||||||
messageSize.rheight() += dateMetrics.height();
|
|
||||||
if (messageSize.width() < opt.rect.width()) {
|
|
||||||
QSize senderSize = nickMetrics.boundingRect(messageRect, 0, data.sender).size();
|
|
||||||
if (senderSize.width() > messageSize.width()) {
|
|
||||||
messageSize.setWidth(senderSize.width());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
messageSize.setWidth(opt.rect.width());
|
|
||||||
}
|
|
||||||
|
|
||||||
QRect rect;
|
|
||||||
painter->setFont(nickFont);
|
|
||||||
painter->drawText(opt.rect, opt.displayAlignment, data.sender, &rect);
|
|
||||||
opt.rect.adjust(0, rect.height() + textMargin, 0, 0);
|
|
||||||
painter->save();
|
|
||||||
switch (data.attach.state) {
|
|
||||||
case Models::none:
|
|
||||||
clearHelperWidget(data); //i can't imagine the situation where it's gonna be needed
|
|
||||||
break; //but it's a possible performance problem
|
|
||||||
case Models::uploading:
|
|
||||||
case Models::downloading:
|
|
||||||
paintBar(getBar(data), painter, data.sentByMe, opt);
|
|
||||||
break;
|
|
||||||
case Models::remote:
|
|
||||||
case Models::local:
|
|
||||||
paintButton(getButton(data), painter, data.sentByMe, opt);
|
|
||||||
break;
|
|
||||||
case Models::ready:
|
|
||||||
clearHelperWidget(data);
|
|
||||||
paintPreview(data, painter, opt);
|
|
||||||
break;
|
|
||||||
case Models::errorDownload:
|
|
||||||
case Models::errorUpload:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
painter->restore();
|
|
||||||
|
|
||||||
int messageLeft = INT16_MAX;
|
|
||||||
QWidget* vp = static_cast<QWidget*>(painter->device());
|
|
||||||
if (data.text.size() > 0) {
|
|
||||||
QLabel* body = getBody(data);
|
|
||||||
body->setParent(vp);
|
|
||||||
body->setMaximumWidth(bodySize.width());
|
|
||||||
body->setMinimumWidth(bodySize.width());
|
|
||||||
body->setAlignment(opt.displayAlignment);
|
|
||||||
messageLeft = opt.rect.x();
|
|
||||||
if (data.sentByMe) {
|
|
||||||
messageLeft = opt.rect.topRight().x() - bodySize.width();
|
|
||||||
}
|
|
||||||
body->move(messageLeft, opt.rect.y());
|
|
||||||
body->show();
|
|
||||||
opt.rect.adjust(0, bodySize.height() + textMargin, 0, 0);
|
|
||||||
}
|
|
||||||
painter->setFont(dateFont);
|
|
||||||
QColor q = painter->pen().color();
|
|
||||||
q.setAlpha(180);
|
|
||||||
painter->setPen(q);
|
|
||||||
painter->drawText(opt.rect, opt.displayAlignment, data.date.toLocalTime().toString(), &rect);
|
|
||||||
if (data.sentByMe) {
|
|
||||||
if (messageLeft > rect.x() - statusIconSize - margin) {
|
|
||||||
messageLeft = rect.x() - statusIconSize - margin;
|
|
||||||
}
|
|
||||||
QLabel* statusIcon = getStatusIcon(data);
|
|
||||||
|
|
||||||
statusIcon->setParent(vp);
|
|
||||||
statusIcon->move(messageLeft, opt.rect.y());
|
|
||||||
statusIcon->show();
|
|
||||||
opt.rect.adjust(0, statusIconSize + textMargin, 0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
painter->restore();
|
|
||||||
|
|
||||||
if (clearingWidgets) {
|
|
||||||
idsToKeep->insert(data.id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QSize MessageDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const
|
|
||||||
{
|
|
||||||
QRect messageRect = option.rect.adjusted(0, margin / 2, -(avatarHeight + 3 * margin), -margin / 2);
|
|
||||||
QStyleOptionViewItem opt = option;
|
|
||||||
opt.rect = messageRect;
|
|
||||||
QVariant va = index.data(Models::MessageFeed::Attach);
|
|
||||||
Models::Attachment attach = qvariant_cast<Models::Attachment>(va);
|
|
||||||
QString body = index.data(Models::MessageFeed::Text).toString();
|
|
||||||
QSize messageSize(0, 0);
|
|
||||||
if (body.size() > 0) {
|
|
||||||
messageSize = bodyMetrics.boundingRect(messageRect, Qt::TextWordWrap, body).size();
|
|
||||||
messageSize.rheight() += textMargin;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (attach.state) {
|
|
||||||
case Models::none:
|
|
||||||
break;
|
|
||||||
case Models::uploading:
|
|
||||||
case Models::downloading:
|
|
||||||
messageSize.rheight() += barHeight + textMargin;
|
|
||||||
break;
|
|
||||||
case Models::remote:
|
|
||||||
case Models::local:
|
|
||||||
messageSize.rheight() += buttonHeight + textMargin;
|
|
||||||
break;
|
|
||||||
case Models::ready:
|
|
||||||
messageSize.rheight() += calculateAttachSize(attach.localPath, messageRect).height() + textMargin;
|
|
||||||
break;
|
|
||||||
case Models::errorDownload:
|
|
||||||
case Models::errorUpload:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
messageSize.rheight() += nickMetrics.lineSpacing();
|
|
||||||
messageSize.rheight() += textMargin;
|
|
||||||
messageSize.rheight() += dateMetrics.height() > statusIconSize ? dateMetrics.height() : statusIconSize;
|
|
||||||
|
|
||||||
if (messageSize.height() < avatarHeight) {
|
|
||||||
messageSize.setHeight(avatarHeight);
|
|
||||||
}
|
|
||||||
|
|
||||||
messageSize.rheight() += margin;
|
|
||||||
|
|
||||||
return messageSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
void MessageDelegate::initializeFonts(const QFont& font)
|
|
||||||
{
|
|
||||||
bodyFont = font;
|
|
||||||
nickFont = font;
|
|
||||||
dateFont = font;
|
|
||||||
|
|
||||||
nickFont.setBold(true);
|
|
||||||
|
|
||||||
float ndps = nickFont.pointSizeF();
|
|
||||||
if (ndps != -1) {
|
|
||||||
nickFont.setPointSizeF(ndps * 1.2);
|
|
||||||
} else {
|
|
||||||
nickFont.setPointSize(nickFont.pointSize() + 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
dateFont.setItalic(true);
|
|
||||||
float dps = dateFont.pointSizeF();
|
|
||||||
if (dps != -1) {
|
|
||||||
dateFont.setPointSizeF(dps * 0.8);
|
|
||||||
} else {
|
|
||||||
dateFont.setPointSize(dateFont.pointSize() - 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
bodyMetrics = QFontMetrics(bodyFont);
|
|
||||||
nickMetrics = QFontMetrics(nickFont);
|
|
||||||
dateMetrics = QFontMetrics(dateFont);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool MessageDelegate::editorEvent(QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& index)
|
|
||||||
{
|
|
||||||
//qDebug() << event->type();
|
|
||||||
|
|
||||||
|
|
||||||
return QStyledItemDelegate::editorEvent(event, model, option, index);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MessageDelegate::paintButton(QPushButton* btn, QPainter* painter, bool sentByMe, QStyleOptionViewItem& option) const
|
|
||||||
{
|
|
||||||
QPoint start;
|
|
||||||
if (sentByMe) {
|
|
||||||
start = {option.rect.width() - btn->width(), option.rect.top()};
|
|
||||||
} else {
|
|
||||||
start = option.rect.topLeft();
|
|
||||||
}
|
|
||||||
|
|
||||||
QWidget* vp = static_cast<QWidget*>(painter->device());
|
|
||||||
btn->setParent(vp);
|
|
||||||
btn->move(start);
|
|
||||||
btn->show();
|
|
||||||
|
|
||||||
option.rect.adjust(0, buttonHeight + textMargin, 0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MessageDelegate::paintBar(QProgressBar* bar, QPainter* painter, bool sentByMe, QStyleOptionViewItem& option) const
|
|
||||||
{
|
|
||||||
QPoint start = option.rect.topLeft();
|
|
||||||
bar->resize(option.rect.width(), barHeight);
|
|
||||||
|
|
||||||
painter->translate(start);
|
|
||||||
bar->render(painter, QPoint(), QRegion(), QWidget::DrawChildren);
|
|
||||||
|
|
||||||
option.rect.adjust(0, barHeight + textMargin, 0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MessageDelegate::paintPreview(const Models::FeedItem& data, QPainter* painter, QStyleOptionViewItem& option) const
|
|
||||||
{
|
|
||||||
Shared::Global::FileInfo info = Shared::Global::getFileInfo(data.attach.localPath);
|
|
||||||
if (info.preview == Shared::Global::FileInfo::Preview::picture) {
|
|
||||||
QSize size = constrainAttachSize(info.size, option.rect.size());
|
|
||||||
|
|
||||||
QPoint start;
|
|
||||||
if (data.sentByMe) {
|
|
||||||
start = {option.rect.width() - size.width(), option.rect.top()};
|
|
||||||
start.rx() += margin;
|
|
||||||
} else {
|
|
||||||
start = option.rect.topLeft();
|
|
||||||
}
|
|
||||||
QImage img(data.attach.localPath);
|
|
||||||
if (img.isNull()) {
|
|
||||||
emit invalidPath(data.id);
|
|
||||||
} else {
|
|
||||||
painter->drawImage(QRect(start, size), img);
|
|
||||||
}
|
|
||||||
|
|
||||||
option.rect.adjust(0, size.height() + textMargin, 0, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QPushButton * MessageDelegate::getButton(const Models::FeedItem& data) const
|
|
||||||
{
|
|
||||||
std::map<QString, FeedButton*>::const_iterator itr = buttons->find(data.id);
|
|
||||||
FeedButton* result = 0;
|
|
||||||
if (itr != buttons->end()) {
|
|
||||||
if (
|
|
||||||
(data.attach.state == Models::remote && itr->second->download) ||
|
|
||||||
(data.attach.state == Models::local && !itr->second->download)
|
|
||||||
) {
|
|
||||||
result = itr->second;
|
|
||||||
} else {
|
|
||||||
delete itr->second;
|
|
||||||
buttons->erase(itr);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
std::map<QString, QProgressBar*>::const_iterator barItr = bars->find(data.id);
|
|
||||||
if (barItr != bars->end()) {
|
|
||||||
delete barItr->second;
|
|
||||||
bars->erase(barItr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result == 0) {
|
|
||||||
result = new FeedButton();
|
|
||||||
result->messageId = data.id;
|
|
||||||
if (data.attach.state == Models::remote) {
|
|
||||||
result->setText(QCoreApplication::translate("MessageLine", "Download"));
|
|
||||||
result->download = true;
|
|
||||||
} else {
|
|
||||||
result->setText(QCoreApplication::translate("MessageLine", "Upload"));
|
|
||||||
result->download = false;
|
|
||||||
}
|
|
||||||
buttons->insert(std::make_pair(data.id, result));
|
|
||||||
connect(result, &QPushButton::clicked, this, &MessageDelegate::onButtonPushed);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
QProgressBar * MessageDelegate::getBar(const Models::FeedItem& data) const
|
|
||||||
{
|
|
||||||
std::map<QString, QProgressBar*>::const_iterator barItr = bars->find(data.id);
|
|
||||||
QProgressBar* result = 0;
|
|
||||||
if (barItr != bars->end()) {
|
|
||||||
result = barItr->second;
|
|
||||||
} else {
|
|
||||||
std::map<QString, FeedButton*>::const_iterator itr = buttons->find(data.id);
|
|
||||||
if (itr != buttons->end()) {
|
|
||||||
delete itr->second;
|
|
||||||
buttons->erase(itr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result == 0) {
|
|
||||||
result = new QProgressBar();
|
|
||||||
result->setRange(0, 100);
|
|
||||||
bars->insert(std::make_pair(data.id, result));
|
|
||||||
}
|
|
||||||
|
|
||||||
result->setValue(data.attach.progress * 100);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
QLabel * MessageDelegate::getStatusIcon(const Models::FeedItem& data) const
|
|
||||||
{
|
|
||||||
std::map<QString, QLabel*>::const_iterator itr = statusIcons->find(data.id);
|
|
||||||
QLabel* result = 0;
|
|
||||||
|
|
||||||
QIcon q(Shared::icon(Shared::messageStateThemeIcons[static_cast<uint8_t>(data.state)]));
|
|
||||||
QString tt = Shared::Global::getName(data.state);
|
|
||||||
if (data.state == Shared::Message::State::error) {
|
|
||||||
if (data.error > 0) {
|
|
||||||
tt += ": " + data.error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (itr != statusIcons->end()) {
|
|
||||||
result = itr->second;
|
|
||||||
if (result->toolTip() != tt) { //If i just assign pixmap every time unconditionally
|
|
||||||
result->setPixmap(q.pixmap(statusIconSize)); //it involves into an infinite cycle of repaint
|
|
||||||
result->setToolTip(tt); //may be it's better to subclass and store last condition in int?
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
result = new QLabel();
|
|
||||||
statusIcons->insert(std::make_pair(data.id, result));
|
|
||||||
result->setPixmap(q.pixmap(statusIconSize));
|
|
||||||
result->setToolTip(tt);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
result->setToolTip(tt);
|
|
||||||
//result->setText(std::to_string((int)data.state).c_str());
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
QLabel * MessageDelegate::getBody(const Models::FeedItem& data) const
|
|
||||||
{
|
|
||||||
std::map<QString, QLabel*>::const_iterator itr = bodies->find(data.id);
|
|
||||||
QLabel* result = 0;
|
|
||||||
|
|
||||||
if (itr != bodies->end()) {
|
|
||||||
result = itr->second;
|
|
||||||
} else {
|
|
||||||
result = new QLabel();
|
|
||||||
result->setFont(bodyFont);
|
|
||||||
result->setWordWrap(true);
|
|
||||||
result->setOpenExternalLinks(true);
|
|
||||||
result->setTextInteractionFlags(result->textInteractionFlags() | Qt::TextSelectableByMouse | Qt::LinksAccessibleByMouse);
|
|
||||||
bodies->insert(std::make_pair(data.id, result));
|
|
||||||
}
|
|
||||||
|
|
||||||
result->setText(Shared::processMessageBody(data.text));
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
void MessageDelegate::beginClearWidgets()
|
|
||||||
{
|
|
||||||
idsToKeep->clear();
|
|
||||||
clearingWidgets = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void MessageDelegate::endClearWidgets()
|
|
||||||
{
|
|
||||||
if (clearingWidgets) {
|
|
||||||
std::set<QString> toRemoveButtons;
|
|
||||||
std::set<QString> toRemoveBars;
|
|
||||||
std::set<QString> toRemoveIcons;
|
|
||||||
std::set<QString> toRemoveBodies;
|
|
||||||
for (const std::pair<const QString, FeedButton*>& pair: *buttons) {
|
|
||||||
if (idsToKeep->find(pair.first) == idsToKeep->end()) {
|
|
||||||
delete pair.second;
|
|
||||||
toRemoveButtons.insert(pair.first);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (const std::pair<const QString, QProgressBar*>& pair: *bars) {
|
|
||||||
if (idsToKeep->find(pair.first) == idsToKeep->end()) {
|
|
||||||
delete pair.second;
|
|
||||||
toRemoveBars.insert(pair.first);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (const std::pair<const QString, QLabel*>& pair: *statusIcons) {
|
|
||||||
if (idsToKeep->find(pair.first) == idsToKeep->end()) {
|
|
||||||
delete pair.second;
|
|
||||||
toRemoveIcons.insert(pair.first);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (const std::pair<const QString, QLabel*>& pair: *bodies) {
|
|
||||||
if (idsToKeep->find(pair.first) == idsToKeep->end()) {
|
|
||||||
delete pair.second;
|
|
||||||
toRemoveBodies.insert(pair.first);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const QString& key : toRemoveButtons) {
|
|
||||||
buttons->erase(key);
|
|
||||||
}
|
|
||||||
for (const QString& key : toRemoveBars) {
|
|
||||||
bars->erase(key);
|
|
||||||
}
|
|
||||||
for (const QString& key : toRemoveIcons) {
|
|
||||||
statusIcons->erase(key);
|
|
||||||
}
|
|
||||||
for (const QString& key : toRemoveBodies) {
|
|
||||||
bodies->erase(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
idsToKeep->clear();
|
|
||||||
clearingWidgets = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MessageDelegate::onButtonPushed() const
|
|
||||||
{
|
|
||||||
FeedButton* btn = static_cast<FeedButton*>(sender());
|
|
||||||
emit buttonPushed(btn->messageId, btn->download);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MessageDelegate::clearHelperWidget(const Models::FeedItem& data) const
|
|
||||||
{
|
|
||||||
std::map<QString, FeedButton*>::const_iterator itr = buttons->find(data.id);
|
|
||||||
if (itr != buttons->end()) {
|
|
||||||
delete itr->second;
|
|
||||||
buttons->erase(itr);
|
|
||||||
} else {
|
|
||||||
std::map<QString, QProgressBar*>::const_iterator barItr = bars->find(data.id);
|
|
||||||
if (barItr != bars->end()) {
|
|
||||||
delete barItr->second;
|
|
||||||
bars->erase(barItr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QSize MessageDelegate::calculateAttachSize(const QString& path, const QRect& bounds) const
|
|
||||||
{
|
|
||||||
Shared::Global::FileInfo info = Shared::Global::getFileInfo(path);
|
|
||||||
|
|
||||||
return constrainAttachSize(info.size, bounds.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
QSize MessageDelegate::constrainAttachSize(QSize src, QSize bounds) const
|
|
||||||
{
|
|
||||||
bounds.setHeight(maxAttachmentHeight);
|
|
||||||
|
|
||||||
if (src.width() > bounds.width() || src.height() > bounds.height()) {
|
|
||||||
src.scale(bounds, Qt::KeepAspectRatio);
|
|
||||||
}
|
|
||||||
|
|
||||||
return src;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// void MessageDelegate::setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const
|
|
||||||
// {
|
|
||||||
//
|
|
||||||
// }
|
|
@ -1,504 +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/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "messageline.h"
|
|
||||||
#include <QDebug>
|
|
||||||
#include <QCoreApplication>
|
|
||||||
#include <cmath>
|
|
||||||
|
|
||||||
MessageLine::MessageLine(bool p_room, QWidget* parent):
|
|
||||||
QWidget(parent),
|
|
||||||
messageIndex(),
|
|
||||||
messageOrder(),
|
|
||||||
myMessages(),
|
|
||||||
palMessages(),
|
|
||||||
uploadPaths(),
|
|
||||||
palAvatars(),
|
|
||||||
exPalAvatars(),
|
|
||||||
layout(new QVBoxLayout(this)),
|
|
||||||
myName(),
|
|
||||||
myAvatarPath(),
|
|
||||||
palNames(),
|
|
||||||
uploading(),
|
|
||||||
downloading(),
|
|
||||||
room(p_room),
|
|
||||||
busyShown(false),
|
|
||||||
progress()
|
|
||||||
{
|
|
||||||
setContentsMargins(0, 0, 0, 0);
|
|
||||||
layout->setContentsMargins(0, 0, 0, 0);
|
|
||||||
layout->setSpacing(0);
|
|
||||||
layout->addStretch();
|
|
||||||
}
|
|
||||||
|
|
||||||
MessageLine::~MessageLine()
|
|
||||||
{
|
|
||||||
for (Index::const_iterator itr = messageIndex.begin(), end = messageIndex.end(); itr != end; ++itr) {
|
|
||||||
delete itr->second;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MessageLine::Position MessageLine::message(const Shared::Message& msg, bool forceOutgoing)
|
|
||||||
{
|
|
||||||
QString id = msg.getId();
|
|
||||||
Index::iterator itr = messageIndex.find(id);
|
|
||||||
if (itr != messageIndex.end()) {
|
|
||||||
qDebug() << "received more then one message with the same id, skipping yet the new one";
|
|
||||||
return invalid;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString sender;
|
|
||||||
QString aPath;
|
|
||||||
bool outgoing;
|
|
||||||
|
|
||||||
if (forceOutgoing) {
|
|
||||||
sender = myName;
|
|
||||||
aPath = myAvatarPath;
|
|
||||||
outgoing = true;
|
|
||||||
} else {
|
|
||||||
if (room) {
|
|
||||||
if (msg.getFromResource() == myName) {
|
|
||||||
sender = myName;
|
|
||||||
aPath = myAvatarPath;
|
|
||||||
outgoing = true;
|
|
||||||
} else {
|
|
||||||
sender = msg.getFromResource();
|
|
||||||
std::map<QString, QString>::iterator aItr = palAvatars.find(sender);
|
|
||||||
if (aItr != palAvatars.end()) {
|
|
||||||
aPath = aItr->second;
|
|
||||||
} else {
|
|
||||||
aItr = exPalAvatars.find(sender);
|
|
||||||
if (aItr != exPalAvatars.end()) {
|
|
||||||
aPath = aItr->second;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
outgoing = false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (msg.getOutgoing()) {
|
|
||||||
sender = myName;
|
|
||||||
aPath = myAvatarPath;
|
|
||||||
outgoing = true;
|
|
||||||
} else {
|
|
||||||
QString jid = msg.getFromJid();
|
|
||||||
std::map<QString, QString>::iterator itr = palNames.find(jid);
|
|
||||||
if (itr != palNames.end()) {
|
|
||||||
sender = itr->second;
|
|
||||||
} else {
|
|
||||||
sender = jid;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::map<QString, QString>::iterator aItr = palAvatars.find(jid);
|
|
||||||
if (aItr != palAvatars.end()) {
|
|
||||||
aPath = aItr->second;
|
|
||||||
}
|
|
||||||
|
|
||||||
outgoing = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Message* message = new Message(msg, outgoing, sender, aPath);
|
|
||||||
|
|
||||||
std::pair<Order::const_iterator, bool> result = messageOrder.insert(std::make_pair(msg.getTime(), message));
|
|
||||||
if (!result.second) {
|
|
||||||
qDebug() << "Error appending a message into a message list - seems like the time of that message exactly matches the time of some other message, can't put them in order, skipping yet";
|
|
||||||
delete message;
|
|
||||||
return invalid;
|
|
||||||
}
|
|
||||||
if (outgoing) {
|
|
||||||
myMessages.insert(std::make_pair(id, message));
|
|
||||||
} else {
|
|
||||||
QString senderId;
|
|
||||||
if (room) {
|
|
||||||
senderId = sender;
|
|
||||||
} else {
|
|
||||||
senderId = msg.getFromJid();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::map<QString, Index>::iterator pItr = palMessages.find(senderId);
|
|
||||||
if (pItr == palMessages.end()) {
|
|
||||||
pItr = palMessages.insert(std::make_pair(senderId, Index())).first;
|
|
||||||
}
|
|
||||||
pItr->second.insert(std::make_pair(id, message));
|
|
||||||
}
|
|
||||||
messageIndex.insert(std::make_pair(id, message));
|
|
||||||
unsigned long index = std::distance<Order::const_iterator>(messageOrder.begin(), result.first); //need to make with binary indexed tree
|
|
||||||
Position res = invalid;
|
|
||||||
if (index == 0) {
|
|
||||||
res = beggining;
|
|
||||||
} else if (index == messageIndex.size() - 1) {
|
|
||||||
res = end;
|
|
||||||
} else {
|
|
||||||
res = middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (busyShown) {
|
|
||||||
index += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (res == end) {
|
|
||||||
layout->addWidget(message);
|
|
||||||
} else {
|
|
||||||
layout->insertWidget(index + 1, message);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (msg.hasOutOfBandUrl()) {
|
|
||||||
emit requestLocalFile(msg.getId(), msg.getOutOfBandUrl());
|
|
||||||
connect(message, &Message::buttonClicked, this, &MessageLine::onDownload);
|
|
||||||
}
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
void MessageLine::changeMessage(const QString& id, const QMap<QString, QVariant>& data)
|
|
||||||
{
|
|
||||||
Index::const_iterator itr = messageIndex.find(id);
|
|
||||||
if (itr != messageIndex.end()) {
|
|
||||||
Message* msg = itr->second;
|
|
||||||
if (msg->change(data)) { //if ID changed (stanza in replace of another)
|
|
||||||
QString newId = msg->getId(); //need to updated IDs of that message in all maps
|
|
||||||
messageIndex.erase(itr);
|
|
||||||
messageIndex.insert(std::make_pair(newId, msg));
|
|
||||||
if (msg->outgoing) {
|
|
||||||
QString senderId;
|
|
||||||
if (room) {
|
|
||||||
senderId = msg->getSenderResource();
|
|
||||||
} else {
|
|
||||||
senderId = msg->getSenderJid();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::map<QString, Index>::iterator pItr = palMessages.find(senderId);
|
|
||||||
if (pItr != palMessages.end()) {
|
|
||||||
Index::const_iterator sItr = pItr->second.find(id);
|
|
||||||
if (sItr != pItr->second.end()) {
|
|
||||||
pItr->second.erase(sItr);
|
|
||||||
pItr->second.insert(std::make_pair(newId, msg));
|
|
||||||
} else {
|
|
||||||
qDebug() << "Was trying to replace message in open conversations, couldn't find it among pal's messages, probably an error";
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
qDebug() << "Was trying to replace message in open conversations, couldn't find pal messages, probably an error";
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Index::const_iterator mItr = myMessages.find(id);
|
|
||||||
if (mItr != myMessages.end()) {
|
|
||||||
myMessages.erase(mItr);
|
|
||||||
myMessages.insert(std::make_pair(newId, msg));
|
|
||||||
} else {
|
|
||||||
qDebug() << "Was trying to replace message in open conversations, couldn't find it among my messages, probably an error";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MessageLine::onDownload()
|
|
||||||
{
|
|
||||||
Message* msg = static_cast<Message*>(sender());
|
|
||||||
QString messageId = msg->getId();
|
|
||||||
Index::const_iterator itr = downloading.find(messageId);
|
|
||||||
if (itr == downloading.end()) {
|
|
||||||
downloading.insert(std::make_pair(messageId, msg));
|
|
||||||
msg->setProgress(0);
|
|
||||||
msg->showComment(tr("Downloading..."));
|
|
||||||
emit downloadFile(messageId, msg->getFileUrl());
|
|
||||||
} else {
|
|
||||||
qDebug() << "An attempt to initiate download for already downloading file" << msg->getFileUrl() << ", skipping";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MessageLine::setMyName(const QString& name)
|
|
||||||
{
|
|
||||||
myName = name;
|
|
||||||
for (Index::const_iterator itr = myMessages.begin(), end = myMessages.end(); itr != end; ++itr) {
|
|
||||||
itr->second->setSender(name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MessageLine::setPalName(const QString& jid, const QString& name)
|
|
||||||
{
|
|
||||||
std::map<QString, QString>::iterator itr = palNames.find(jid);
|
|
||||||
if (itr == palNames.end()) {
|
|
||||||
palNames.insert(std::make_pair(jid, name));
|
|
||||||
} else {
|
|
||||||
itr->second = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::map<QString, Index>::iterator pItr = palMessages.find(jid);
|
|
||||||
if (pItr != palMessages.end()) {
|
|
||||||
for (Index::const_iterator itr = pItr->second.begin(), end = pItr->second.end(); itr != end; ++itr) {
|
|
||||||
itr->second->setSender(name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MessageLine::setPalAvatar(const QString& jid, const QString& path)
|
|
||||||
{
|
|
||||||
std::map<QString, QString>::iterator itr = palAvatars.find(jid);
|
|
||||||
if (itr == palAvatars.end()) {
|
|
||||||
palAvatars.insert(std::make_pair(jid, path));
|
|
||||||
|
|
||||||
std::map<QString, QString>::const_iterator eitr = exPalAvatars.find(jid);
|
|
||||||
if (eitr != exPalAvatars.end()) {
|
|
||||||
exPalAvatars.erase(eitr);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
itr->second = path;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::map<QString, Index>::iterator pItr = palMessages.find(jid);
|
|
||||||
if (pItr != palMessages.end()) {
|
|
||||||
for (Index::const_iterator itr = pItr->second.begin(), end = pItr->second.end(); itr != end; ++itr) {
|
|
||||||
itr->second->setAvatarPath(path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MessageLine::dropPalAvatar(const QString& jid)
|
|
||||||
{
|
|
||||||
std::map<QString, QString>::iterator itr = palAvatars.find(jid);
|
|
||||||
if (itr != palAvatars.end()) {
|
|
||||||
palAvatars.erase(itr);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::map<QString, QString>::const_iterator eitr = exPalAvatars.find(jid);
|
|
||||||
if (eitr != exPalAvatars.end()) {
|
|
||||||
exPalAvatars.erase(eitr);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::map<QString, Index>::iterator pItr = palMessages.find(jid);
|
|
||||||
if (pItr != palMessages.end()) {
|
|
||||||
for (Index::const_iterator itr = pItr->second.begin(), end = pItr->second.end(); itr != end; ++itr) {
|
|
||||||
itr->second->setAvatarPath("");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MessageLine::movePalAvatarToEx(const QString& name)
|
|
||||||
{
|
|
||||||
std::map<QString, QString>::iterator itr = palAvatars.find(name);
|
|
||||||
if (itr != palAvatars.end()) {
|
|
||||||
std::map<QString, QString>::iterator eitr = exPalAvatars.find(name);
|
|
||||||
if (eitr != exPalAvatars.end()) {
|
|
||||||
eitr->second = itr->second;
|
|
||||||
} else {
|
|
||||||
exPalAvatars.insert(std::make_pair(name, itr->second));
|
|
||||||
}
|
|
||||||
|
|
||||||
palAvatars.erase(itr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MessageLine::resizeEvent(QResizeEvent* event)
|
|
||||||
{
|
|
||||||
QWidget::resizeEvent(event);
|
|
||||||
emit resize(event->size().height() - event->oldSize().height());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
QString MessageLine::firstMessageId() const
|
|
||||||
{
|
|
||||||
if (messageOrder.size() == 0) {
|
|
||||||
return "";
|
|
||||||
} else {
|
|
||||||
return messageOrder.begin()->second->getId();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MessageLine::showBusyIndicator()
|
|
||||||
{
|
|
||||||
if (!busyShown) {
|
|
||||||
layout->insertWidget(0, &progress);
|
|
||||||
progress.start();
|
|
||||||
busyShown = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MessageLine::hideBusyIndicator()
|
|
||||||
{
|
|
||||||
if (busyShown) {
|
|
||||||
progress.stop();
|
|
||||||
layout->removeWidget(&progress);
|
|
||||||
busyShown = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MessageLine::fileProgress(const QString& messageId, qreal progress)
|
|
||||||
{
|
|
||||||
Index::const_iterator itr = messageIndex.find(messageId);
|
|
||||||
if (itr == messageIndex.end()) {
|
|
||||||
//TODO may be some logging, that's not normal
|
|
||||||
} else {
|
|
||||||
itr->second->setProgress(progress);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MessageLine::responseLocalFile(const QString& messageId, const QString& path)
|
|
||||||
{
|
|
||||||
Index::const_iterator itr = messageIndex.find(messageId);
|
|
||||||
if (itr == messageIndex.end()) {
|
|
||||||
|
|
||||||
} else {
|
|
||||||
Index::const_iterator uItr = uploading.find(messageId);
|
|
||||||
if (path.size() > 0) {
|
|
||||||
Index::const_iterator dItr = downloading.find(messageId);
|
|
||||||
if (dItr != downloading.end()) {
|
|
||||||
downloading.erase(dItr);
|
|
||||||
itr->second->showFile(path);
|
|
||||||
} else {
|
|
||||||
if (uItr != uploading.end()) {
|
|
||||||
uploading.erase(uItr);
|
|
||||||
std::map<QString, QString>::const_iterator muItr = uploadPaths.find(messageId);
|
|
||||||
if (muItr != uploadPaths.end()) {
|
|
||||||
uploadPaths.erase(muItr);
|
|
||||||
}
|
|
||||||
Shared::Message msg = itr->second->getMessage();
|
|
||||||
removeMessage(messageId);
|
|
||||||
msg.setCurrentTime();
|
|
||||||
message(msg);
|
|
||||||
itr = messageIndex.find(messageId);
|
|
||||||
itr->second->showFile(path);
|
|
||||||
} else {
|
|
||||||
itr->second->showFile(path); //then it is already cached file
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (uItr == uploading.end()) {
|
|
||||||
const Shared::Message& msg = itr->second->getMessage();
|
|
||||||
itr->second->addButton(QIcon::fromTheme("download"), tr("Download"), "<a href=\"" + msg.getOutOfBandUrl() + "\">" + msg.getOutOfBandUrl() + "</a>");
|
|
||||||
itr->second->showComment(tr("Push the button to download the file"));
|
|
||||||
} else {
|
|
||||||
qDebug() << "An unhandled state for file uploading - empty path";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MessageLine::removeMessage(const QString& messageId)
|
|
||||||
{
|
|
||||||
Index::const_iterator itr = messageIndex.find(messageId);
|
|
||||||
if (itr != messageIndex.end()) {
|
|
||||||
Message* ui = itr->second;
|
|
||||||
const Shared::Message& msg = ui->getMessage();
|
|
||||||
messageIndex.erase(itr);
|
|
||||||
Order::const_iterator oItr = messageOrder.find(msg.getTime());
|
|
||||||
if (oItr != messageOrder.end()) {
|
|
||||||
messageOrder.erase(oItr);
|
|
||||||
} else {
|
|
||||||
qDebug() << "An attempt to remove message from messageLine, but it wasn't found in order";
|
|
||||||
}
|
|
||||||
if (msg.getOutgoing()) {
|
|
||||||
Index::const_iterator mItr = myMessages.find(messageId);
|
|
||||||
if (mItr != myMessages.end()) {
|
|
||||||
myMessages.erase(mItr);
|
|
||||||
} else {
|
|
||||||
qDebug() << "Error removing message: it seems to be outgoing yet it wasn't found in outgoing messages";
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (room) {
|
|
||||||
|
|
||||||
} else {
|
|
||||||
QString jid = msg.getFromJid();
|
|
||||||
std::map<QString, Index>::iterator pItr = palMessages.find(jid);
|
|
||||||
if (pItr != palMessages.end()) {
|
|
||||||
Index& pMsgs = pItr->second;
|
|
||||||
Index::const_iterator pmitr = pMsgs.find(messageId);
|
|
||||||
if (pmitr != pMsgs.end()) {
|
|
||||||
pMsgs.erase(pmitr);
|
|
||||||
} else {
|
|
||||||
qDebug() << "Error removing message: it seems to be incoming yet it wasn't found among messages from that penpal";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ui->deleteLater();
|
|
||||||
qDebug() << "message" << messageId << "has been removed";
|
|
||||||
} else {
|
|
||||||
qDebug() << "An attempt to remove non existing message from messageLine";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MessageLine::fileError(const QString& messageId, const QString& error)
|
|
||||||
{
|
|
||||||
Index::const_iterator itr = downloading.find(messageId);
|
|
||||||
if (itr == downloading.end()) {
|
|
||||||
Index::const_iterator itr = uploading.find(messageId);
|
|
||||||
if (itr == uploading.end()) {
|
|
||||||
//TODO may be some logging, that's not normal
|
|
||||||
} else {
|
|
||||||
itr->second->showComment(tr("Error uploading file: %1\nYou can try again").arg(QCoreApplication::translate("NetworkErrors", error.toLatin1())), true);
|
|
||||||
itr->second->addButton(QIcon::fromTheme("upload"), tr("Upload"));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const Shared::Message& msg = itr->second->getMessage();
|
|
||||||
itr->second->addButton(QIcon::fromTheme("download"), tr("Download"), "<a href=\"" + msg.getOutOfBandUrl() + "\">" + msg.getOutOfBandUrl() + "</a>");
|
|
||||||
itr->second->showComment(tr("Error downloading file: %1\nYou can try again").arg(QCoreApplication::translate("NetworkErrors", error.toLatin1())), true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MessageLine::appendMessageWithUpload(const Shared::Message& msg, const QString& path)
|
|
||||||
{
|
|
||||||
appendMessageWithUploadNoSiganl(msg, path);
|
|
||||||
emit uploadFile(msg, path);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MessageLine::appendMessageWithUploadNoSiganl(const Shared::Message& msg, const QString& path)
|
|
||||||
{
|
|
||||||
message(msg, true);
|
|
||||||
QString id = msg.getId();
|
|
||||||
Message* ui = messageIndex.find(id)->second;
|
|
||||||
connect(ui, &Message::buttonClicked, this, &MessageLine::onUpload); //this is in case of retry;
|
|
||||||
ui->setProgress(0);
|
|
||||||
ui->showComment(tr("Uploading..."));
|
|
||||||
uploading.insert(std::make_pair(id, ui));
|
|
||||||
uploadPaths.insert(std::make_pair(id, path));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void MessageLine::onUpload()
|
|
||||||
{
|
|
||||||
//TODO retry
|
|
||||||
}
|
|
||||||
|
|
||||||
void MessageLine::setMyAvatarPath(const QString& p_path)
|
|
||||||
{
|
|
||||||
if (myAvatarPath != p_path) {
|
|
||||||
myAvatarPath = p_path;
|
|
||||||
for (std::pair<QString, Message*> pair : myMessages) {
|
|
||||||
pair.second->setAvatarPath(myAvatarPath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MessageLine::setExPalAvatars(const std::map<QString, QString>& data)
|
|
||||||
{
|
|
||||||
exPalAvatars = data;
|
|
||||||
|
|
||||||
for (const std::pair<QString, Index>& pair : palMessages) {
|
|
||||||
if (palAvatars.find(pair.first) == palAvatars.end()) {
|
|
||||||
std::map<QString, QString>::const_iterator eitr = exPalAvatars.find(pair.first);
|
|
||||||
if (eitr != exPalAvatars.end()) {
|
|
||||||
for (const std::pair<QString, Message*>& mp : pair.second) {
|
|
||||||
mp.second->setAvatarPath(eitr->second);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,108 +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 MESSAGELINE_H
|
|
||||||
#define MESSAGELINE_H
|
|
||||||
|
|
||||||
#include <QWidget>
|
|
||||||
#include <QVBoxLayout>
|
|
||||||
#include <QHBoxLayout>
|
|
||||||
#include <QLabel>
|
|
||||||
#include <QResizeEvent>
|
|
||||||
#include <QIcon>
|
|
||||||
|
|
||||||
#include "shared/message.h"
|
|
||||||
#include "message.h"
|
|
||||||
#include "progress.h"
|
|
||||||
|
|
||||||
class MessageLine : public QWidget
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
enum Position {
|
|
||||||
beggining,
|
|
||||||
middle,
|
|
||||||
end,
|
|
||||||
invalid
|
|
||||||
};
|
|
||||||
MessageLine(bool p_room, QWidget* parent = 0);
|
|
||||||
~MessageLine();
|
|
||||||
|
|
||||||
Position message(const Shared::Message& msg, bool forceOutgoing = false);
|
|
||||||
void setMyName(const QString& name);
|
|
||||||
void setPalName(const QString& jid, const QString& name);
|
|
||||||
QString firstMessageId() const;
|
|
||||||
void showBusyIndicator();
|
|
||||||
void hideBusyIndicator();
|
|
||||||
void responseLocalFile(const QString& messageId, const QString& path);
|
|
||||||
void fileError(const QString& messageId, const QString& error);
|
|
||||||
void fileProgress(const QString& messageId, qreal progress);
|
|
||||||
void appendMessageWithUpload(const Shared::Message& msg, const QString& path);
|
|
||||||
void appendMessageWithUploadNoSiganl(const Shared::Message& msg, const QString& path);
|
|
||||||
void removeMessage(const QString& messageId);
|
|
||||||
void setMyAvatarPath(const QString& p_path);
|
|
||||||
void setPalAvatar(const QString& jid, const QString& path);
|
|
||||||
void dropPalAvatar(const QString& jid);
|
|
||||||
void changeMessage(const QString& id, const QMap<QString, QVariant>& data);
|
|
||||||
void setExPalAvatars(const std::map<QString, QString>& data);
|
|
||||||
void movePalAvatarToEx(const QString& name);
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void resize(int amount);
|
|
||||||
void downloadFile(const QString& messageId, const QString& url);
|
|
||||||
void uploadFile(const Shared::Message& msg, const QString& path);
|
|
||||||
void requestLocalFile(const QString& messageId, const QString& url);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
void resizeEvent(QResizeEvent * event) override;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
void onDownload();
|
|
||||||
void onUpload();
|
|
||||||
|
|
||||||
private:
|
|
||||||
struct Comparator {
|
|
||||||
bool operator()(const Shared::Message& a, const Shared::Message& b) const {
|
|
||||||
return a.getTime() < b.getTime();
|
|
||||||
}
|
|
||||||
bool operator()(const Shared::Message* a, const Shared::Message* b) const {
|
|
||||||
return a->getTime() < b->getTime();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
typedef std::map<QDateTime, Message*> Order;
|
|
||||||
typedef std::map<QString, Message*> Index;
|
|
||||||
Index messageIndex;
|
|
||||||
Order messageOrder;
|
|
||||||
Index myMessages;
|
|
||||||
std::map<QString, Index> palMessages;
|
|
||||||
std::map<QString, QString> uploadPaths;
|
|
||||||
std::map<QString, QString> palAvatars;
|
|
||||||
std::map<QString, QString> exPalAvatars;
|
|
||||||
QVBoxLayout* layout;
|
|
||||||
|
|
||||||
QString myName;
|
|
||||||
QString myAvatarPath;
|
|
||||||
std::map<QString, QString> palNames;
|
|
||||||
Index uploading;
|
|
||||||
Index downloading;
|
|
||||||
bool room;
|
|
||||||
bool busyShown;
|
|
||||||
Progress progress;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // MESSAGELINE_H
|
|
@ -1,31 +1,23 @@
|
|||||||
cmake_minimum_required(VERSION 3.0)
|
target_sources(squawk PRIVATE
|
||||||
project(squawkWidgets)
|
|
||||||
|
|
||||||
# Instruct CMake to run moc automatically when needed.
|
|
||||||
set(CMAKE_AUTOMOC ON)
|
|
||||||
# Instruct CMake to create code from Qt designer ui files
|
|
||||||
set(CMAKE_AUTOUIC ON)
|
|
||||||
|
|
||||||
# Find the QtWidgets library
|
|
||||||
find_package(Qt5Widgets CONFIG REQUIRED COMPONENTS Widgets Core)
|
|
||||||
|
|
||||||
add_subdirectory(vcard)
|
|
||||||
|
|
||||||
set(squawkWidgets_SRC
|
|
||||||
conversation.cpp
|
|
||||||
chat.cpp
|
chat.cpp
|
||||||
room.cpp
|
chat.h
|
||||||
newcontact.cpp
|
conversation.cpp
|
||||||
accounts.cpp
|
conversation.h
|
||||||
account.cpp
|
conversation.ui
|
||||||
joinconference.cpp
|
joinconference.cpp
|
||||||
|
joinconference.h
|
||||||
|
joinconference.ui
|
||||||
|
newcontact.cpp
|
||||||
|
newcontact.h
|
||||||
|
newcontact.ui
|
||||||
|
room.cpp
|
||||||
|
room.h
|
||||||
|
about.cpp
|
||||||
|
about.h
|
||||||
|
about.ui
|
||||||
)
|
)
|
||||||
|
|
||||||
add_library(squawkWidgets STATIC ${squawkWidgets_SRC})
|
add_subdirectory(vcard)
|
||||||
|
add_subdirectory(messageline)
|
||||||
# Use the Widgets module from Qt 5.
|
add_subdirectory(settings)
|
||||||
target_link_libraries(squawkWidgets vCardUI)
|
add_subdirectory(accounts)
|
||||||
target_link_libraries(squawkWidgets squawkUIUtils)
|
|
||||||
target_link_libraries(squawkWidgets Qt5::Widgets)
|
|
||||||
|
|
||||||
qt5_use_modules(squawkWidgets Core Widgets)
|
|
||||||
|
111
ui/widgets/about.cpp
Normal file
111
ui/widgets/about.cpp
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
// 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 "about.h"
|
||||||
|
#include "ui_about.h"
|
||||||
|
#include <QXmppGlobal.h>
|
||||||
|
#include <QDebug>
|
||||||
|
|
||||||
|
static const std::string QXMPP_VERSION_PATCH(std::to_string(QXMPP_VERSION & 0xff));
|
||||||
|
static const std::string QXMPP_VERSION_MINOR(std::to_string((QXMPP_VERSION & 0xff00) >> 8));
|
||||||
|
static const std::string QXMPP_VERSION_MAJOR(std::to_string(QXMPP_VERSION >> 16));
|
||||||
|
static const QString QXMPP_VERSION_STRING = QString::fromStdString(QXMPP_VERSION_MAJOR + "." + QXMPP_VERSION_MINOR + "." + QXMPP_VERSION_PATCH);
|
||||||
|
|
||||||
|
About::About(QWidget* parent):
|
||||||
|
QDialog(parent),
|
||||||
|
m_ui(new Ui::About),
|
||||||
|
license(nullptr)
|
||||||
|
{
|
||||||
|
m_ui->setupUi(this);
|
||||||
|
m_ui->versionValue->setText(QApplication::applicationVersion());
|
||||||
|
m_ui->qtVersionValue->setText(qVersion());
|
||||||
|
m_ui->qtBuiltAgainstVersion->setText(tr("(built against %1)").arg(QT_VERSION_STR));
|
||||||
|
|
||||||
|
m_ui->qxmppVersionValue->setText(QXmppVersion());
|
||||||
|
m_ui->qxmppBuiltAgainstVersion->setText(tr("(built against %1)").arg(QXMPP_VERSION_STRING));
|
||||||
|
|
||||||
|
setWindowFlag(Qt::WindowStaysOnTopHint);
|
||||||
|
if (parent)
|
||||||
|
move(parent->window()->frameGeometry().topLeft() +
|
||||||
|
parent->window()->rect().center() - rect().center());
|
||||||
|
|
||||||
|
connect(m_ui->licenceLink, &QLabel::linkActivated, this, &About::onLicenseActivated);
|
||||||
|
}
|
||||||
|
|
||||||
|
About::~About() {
|
||||||
|
if (license != nullptr) {
|
||||||
|
license->deleteLater();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
void About::onLicenseActivated()
|
||||||
|
{
|
||||||
|
if (license == nullptr) {
|
||||||
|
QFile file;
|
||||||
|
bool found = false;
|
||||||
|
QStringList shares = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation);
|
||||||
|
for (const QString& path : shares) {
|
||||||
|
file.setFileName(path + "/LICENSE.md");
|
||||||
|
|
||||||
|
if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!found) {
|
||||||
|
qDebug() << "couldn't read license file, bailing";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
license = new QWidget();
|
||||||
|
license->setWindowTitle(tr("License"));
|
||||||
|
QVBoxLayout* layout = new QVBoxLayout(license);
|
||||||
|
QLabel* text = new QLabel(license);
|
||||||
|
QScrollArea* area = new QScrollArea(license);
|
||||||
|
text->setTextFormat(Qt::MarkdownText);
|
||||||
|
text->setWordWrap(true);
|
||||||
|
text->setOpenExternalLinks(true);
|
||||||
|
text->setMargin(5);
|
||||||
|
area->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
|
||||||
|
|
||||||
|
layout->addWidget(area);
|
||||||
|
license->setAttribute(Qt::WA_DeleteOnClose);
|
||||||
|
connect(license, &QWidget::destroyed, this, &About::onLicenseClosed);
|
||||||
|
|
||||||
|
QTextStream in(&file);
|
||||||
|
QString line;
|
||||||
|
QString licenseText("");
|
||||||
|
while (!in.atEnd()) {
|
||||||
|
line = in.readLine();
|
||||||
|
licenseText.append(line + "\n");
|
||||||
|
}
|
||||||
|
text->setText(licenseText);
|
||||||
|
file.close();
|
||||||
|
|
||||||
|
area->setWidget(text);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
license->raise();
|
||||||
|
license->activateWindow();
|
||||||
|
}
|
||||||
|
|
||||||
|
license->show();
|
||||||
|
}
|
||||||
|
|
||||||
|
void About::onLicenseClosed()
|
||||||
|
{
|
||||||
|
license = nullptr;
|
||||||
|
}
|
51
ui/widgets/about.h
Normal file
51
ui/widgets/about.h
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
// 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 ABOUT_H
|
||||||
|
#define ABOUT_H
|
||||||
|
|
||||||
|
#include <QDialog>
|
||||||
|
#include <QScopedPointer>
|
||||||
|
#include <QApplication>
|
||||||
|
#include <QFile>
|
||||||
|
#include <QTextStream>
|
||||||
|
#include <QStandardPaths>
|
||||||
|
|
||||||
|
namespace Ui
|
||||||
|
{
|
||||||
|
class About;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @todo write docs
|
||||||
|
*/
|
||||||
|
class About : public QDialog
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
About(QWidget* parent = nullptr);
|
||||||
|
~About();
|
||||||
|
|
||||||
|
protected slots:
|
||||||
|
void onLicenseActivated();
|
||||||
|
void onLicenseClosed();
|
||||||
|
|
||||||
|
private:
|
||||||
|
QScopedPointer<Ui::About> m_ui;
|
||||||
|
QWidget* license;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // ABOUT_H
|
680
ui/widgets/about.ui
Normal file
680
ui/widgets/about.ui
Normal file
@ -0,0 +1,680 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>About</class>
|
||||||
|
<widget class="QWidget" name="About">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>375</width>
|
||||||
|
<height>290</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>About Squawk</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QGridLayout" name="gridLayout">
|
||||||
|
<property name="verticalSpacing">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<item row="2" column="0" colspan="4">
|
||||||
|
<spacer name="verticalSpacer">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Vertical</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeType">
|
||||||
|
<enum>QSizePolicy::Fixed</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>20</width>
|
||||||
|
<height>10</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="1" colspan="2">
|
||||||
|
<widget class="QLabel" name="header">
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<pointsize>12</pointsize>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Squawk</string>
|
||||||
|
</property>
|
||||||
|
<property name="alignment">
|
||||||
|
<set>Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="0" rowspan="2">
|
||||||
|
<widget class="QLabel" name="squawkIcon">
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>50</width>
|
||||||
|
<height>50</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string/>
|
||||||
|
</property>
|
||||||
|
<property name="pixmap">
|
||||||
|
<pixmap resource="../../resources/resources.qrc">:/images/logo.svg</pixmap>
|
||||||
|
</property>
|
||||||
|
<property name="scaledContents">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="alignment">
|
||||||
|
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="3" rowspan="2">
|
||||||
|
<spacer name="horizontalSpacer">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item row="4" column="0" colspan="4">
|
||||||
|
<widget class="QTabWidget" name="tabs">
|
||||||
|
<property name="currentIndex">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="usesScrollButtons">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<widget class="QWidget" name="aboutTab">
|
||||||
|
<attribute name="title">
|
||||||
|
<string>About</string>
|
||||||
|
</attribute>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<item>
|
||||||
|
<spacer name="verticalSpacer_2">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Vertical</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>20</width>
|
||||||
|
<height>40</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="description">
|
||||||
|
<property name="text">
|
||||||
|
<string>XMPP (jabber) messenger</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="copyright">
|
||||||
|
<property name="text">
|
||||||
|
<string>(c) 2019 - 2022, Yury Gubich</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="siteLink">
|
||||||
|
<property name="text">
|
||||||
|
<string><a href="https://git.macaw.me/blue/squawk">Project site</a></string>
|
||||||
|
</property>
|
||||||
|
<property name="textFormat">
|
||||||
|
<enum>Qt::RichText</enum>
|
||||||
|
</property>
|
||||||
|
<property name="openExternalLinks">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="licenceLink">
|
||||||
|
<property name="text">
|
||||||
|
<string><a href="https://git.macaw.me/blue/squawk/src/branch/master/LICENSE.md">License: GNU General Public License version 3</a></string>
|
||||||
|
</property>
|
||||||
|
<property name="textFormat">
|
||||||
|
<enum>Qt::RichText</enum>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="verticalSpacer_3">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Vertical</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>20</width>
|
||||||
|
<height>40</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<widget class="QScrollArea" name="componentsTab">
|
||||||
|
<property name="frameShape">
|
||||||
|
<enum>QFrame::NoFrame</enum>
|
||||||
|
</property>
|
||||||
|
<property name="horizontalScrollBarPolicy">
|
||||||
|
<enum>Qt::ScrollBarAlwaysOff</enum>
|
||||||
|
</property>
|
||||||
|
<property name="widgetResizable">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<attribute name="title">
|
||||||
|
<string>Components</string>
|
||||||
|
</attribute>
|
||||||
|
<widget class="QWidget" name="scrollAreaWidgetContents">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>355</width>
|
||||||
|
<height>181</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_1">
|
||||||
|
<item>
|
||||||
|
<widget class="QWidget" name="widget" native="true">
|
||||||
|
<layout class="QGridLayout" name="gridLayout_2">
|
||||||
|
<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>
|
||||||
|
<property name="horizontalSpacing">
|
||||||
|
<number>2</number>
|
||||||
|
</property>
|
||||||
|
<property name="verticalSpacing">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<item row="1" column="0">
|
||||||
|
<widget class="QLabel" name="label_2">
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<italic>true</italic>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Version</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="1">
|
||||||
|
<widget class="QLabel" name="qtVersionValue">
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<italic>true</italic>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>0.0.0</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="2">
|
||||||
|
<widget class="QLabel" name="qtBuiltAgainstVersion">
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<italic>true</italic>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string notr="true">(built against 0.0.0)</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="0" colspan="3">
|
||||||
|
<widget class="QLabel" name="label">
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<weight>75</weight>
|
||||||
|
<bold>true</bold>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string notr="true">Qt</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="3" rowspan="2">
|
||||||
|
<spacer name="horizontalSpacer_2">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="0" colspan="4">
|
||||||
|
<widget class="QLabel" name="label_3">
|
||||||
|
<property name="text">
|
||||||
|
<string notr="true"><a href="https://www.qt.io/">www.qt.io</a></string>
|
||||||
|
</property>
|
||||||
|
<property name="openExternalLinks">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QWidget" name="widget_2" native="true">
|
||||||
|
<layout class="QGridLayout" name="gridLayout_3">
|
||||||
|
<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>
|
||||||
|
<property name="horizontalSpacing">
|
||||||
|
<number>2</number>
|
||||||
|
</property>
|
||||||
|
<property name="verticalSpacing">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<item row="1" column="0">
|
||||||
|
<widget class="QLabel" name="label_4">
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<italic>true</italic>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Version</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="1">
|
||||||
|
<widget class="QLabel" name="qxmppVersionValue">
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<italic>true</italic>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>0.0.0</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="2">
|
||||||
|
<widget class="QLabel" name="qxmppBuiltAgainstVersion">
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<italic>true</italic>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string notr="true">(built against 0.0.0)</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="0" colspan="3">
|
||||||
|
<widget class="QLabel" name="label_5">
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<weight>75</weight>
|
||||||
|
<bold>true</bold>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string notr="true">QXmpp</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="3" rowspan="2">
|
||||||
|
<spacer name="horizontalSpacer_3">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="0" colspan="4">
|
||||||
|
<widget class="QLabel" name="label_6">
|
||||||
|
<property name="text">
|
||||||
|
<string notr="true"><a href="https://github.com/qxmpp-project/qxmpp">github.com/qxmpp-project/qxmpp</a></string>
|
||||||
|
</property>
|
||||||
|
<property name="openExternalLinks">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="verticalSpacer_4">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Vertical</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>20</width>
|
||||||
|
<height>40</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</widget>
|
||||||
|
<widget class="QWidget" name="reportTab">
|
||||||
|
<attribute name="title">
|
||||||
|
<string>Report Bugs</string>
|
||||||
|
</attribute>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||||
|
<item>
|
||||||
|
<spacer name="verticalSpacer_6">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Vertical</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>20</width>
|
||||||
|
<height>40</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_8">
|
||||||
|
<property name="text">
|
||||||
|
<string>Please report any bug you find!
|
||||||
|
To report bugs you can use:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_7">
|
||||||
|
<property name="text">
|
||||||
|
<string><a href="https://git.macaw.me/blue/squawk/issues">Project bug tracker</></string>
|
||||||
|
</property>
|
||||||
|
<property name="openExternalLinks">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_10">
|
||||||
|
<property name="text">
|
||||||
|
<string>XMPP (<a href="xmpp:blue@macaw.me">blue@macaw.me</a>)</string>
|
||||||
|
</property>
|
||||||
|
<property name="openExternalLinks">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_9">
|
||||||
|
<property name="text">
|
||||||
|
<string>E-Mail (<a href="mailto:blue@macaw.me">blue@macaw.me</a>)</string>
|
||||||
|
</property>
|
||||||
|
<property name="openExternalLinks">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="verticalSpacer_5">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Vertical</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>20</width>
|
||||||
|
<height>40</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<widget class="QScrollArea" name="thanksTab">
|
||||||
|
<property name="frameShape">
|
||||||
|
<enum>QFrame::NoFrame</enum>
|
||||||
|
</property>
|
||||||
|
<property name="horizontalScrollBarPolicy">
|
||||||
|
<enum>Qt::ScrollBarAlwaysOff</enum>
|
||||||
|
</property>
|
||||||
|
<property name="widgetResizable">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<attribute name="title">
|
||||||
|
<string>Thanks To</string>
|
||||||
|
</attribute>
|
||||||
|
<widget class="QWidget" name="thanksTabContent">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>355</width>
|
||||||
|
<height>181</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||||
|
<property name="spacing">
|
||||||
|
<number>10</number>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<widget class="QWidget" name="widget_3" native="true">
|
||||||
|
<layout class="QGridLayout" name="gridLayout_4">
|
||||||
|
<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>
|
||||||
|
<property name="spacing">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<item row="0" column="0">
|
||||||
|
<widget class="QLabel" name="label_11">
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<weight>75</weight>
|
||||||
|
<bold>true</bold>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Vae</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="0">
|
||||||
|
<widget class="QLabel" name="label_12">
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<italic>true</italic>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Major refactoring, bug fixes, constructive criticism</string>
|
||||||
|
</property>
|
||||||
|
<property name="wordWrap">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QWidget" name="widget_4" native="true">
|
||||||
|
<layout class="QGridLayout" name="gridLayout_5">
|
||||||
|
<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>
|
||||||
|
<property name="spacing">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<item row="0" column="0">
|
||||||
|
<widget class="QLabel" name="label_13">
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<weight>75</weight>
|
||||||
|
<bold>true</bold>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Shunf4</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="0">
|
||||||
|
<widget class="QLabel" name="label_14">
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<italic>true</italic>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Major refactoring, bug fixes, build adaptations for Windows and MacOS</string>
|
||||||
|
</property>
|
||||||
|
<property name="wordWrap">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QWidget" name="widget_5" native="true">
|
||||||
|
<layout class="QGridLayout" name="gridLayout_6">
|
||||||
|
<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>
|
||||||
|
<property name="spacing">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<item row="0" column="0">
|
||||||
|
<widget class="QLabel" name="label_15">
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<weight>75</weight>
|
||||||
|
<bold>true</bold>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Bruno F. Fontes</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="0">
|
||||||
|
<widget class="QLabel" name="label_16">
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<italic>true</italic>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Brazilian Portuguese translation</string>
|
||||||
|
</property>
|
||||||
|
<property name="wordWrap">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="verticalSpacer_7">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Vertical</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>20</width>
|
||||||
|
<height>0</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</widget>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="1">
|
||||||
|
<widget class="QLabel" name="versionLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>Version</string>
|
||||||
|
</property>
|
||||||
|
<property name="alignment">
|
||||||
|
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="2">
|
||||||
|
<widget class="QLabel" name="versionValue">
|
||||||
|
<property name="text">
|
||||||
|
<string>0.0.0</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<resources>
|
||||||
|
<include location="../../resources/resources.qrc"/>
|
||||||
|
</resources>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user