forked from blue/squawk
Compare commits
No commits in common. "master" and "newMessageFeedPreview1" have entirely different histories.
master
...
newMessage
55
CHANGELOG.md
55
CHANGELOG.md
@ -1,70 +1,19 @@
|
||||
# Changelog
|
||||
|
||||
## 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)
|
||||
## Squawk 0.2.0 (Unreleased)
|
||||
### Bug fixes
|
||||
- carbon copies switches on again after reconnection
|
||||
- requesting the history of the current chat after reconnection
|
||||
- global availability (in drop down list) gets restored after reconnection
|
||||
- status icon in active chat changes when presence of the pen pal changes
|
||||
- infinite progress when open the dialogue with something that has no history to show
|
||||
- fallback icons for buttons, when no supported theme is installed (shunf4)
|
||||
- better handling messages with no id (shunf4)
|
||||
- removed dependency: uuid, now it's on Qt (shunf4)
|
||||
- better requesting latest history (shunf4)
|
||||
|
||||
### Improvements
|
||||
- slightly reduced the traffic on the startup by not requesting history of all MUCs
|
||||
- completely rewritten message feed, now it works way faster and looks cooler
|
||||
- completely rewritten message feed, now it works way faster
|
||||
- OPTIONAL RUNTIME dependency: "KIO Widgets" that is supposed to allow you to open a file in your default file manager
|
||||
- show in folder now is supposed to try it's best to show file in folder, even you don't have KIO installed
|
||||
- once uploaded local files don't get second time uploaded - the remote URL is reused
|
||||
- way better compilation time (vae)
|
||||
|
||||
### New features
|
||||
- pasting images from clipboard to attachment (shunf4)
|
||||
- possible compilation for windows and macOS (shunf4)
|
||||
|
||||
## Squawk 0.1.5 (Jul 29, 2020)
|
||||
### Bug fixes
|
||||
|
218
CMakeLists.txt
218
CMakeLists.txt
@ -1,8 +1,7 @@
|
||||
cmake_minimum_required(VERSION 3.4)
|
||||
project(squawk VERSION 0.2.2 LANGUAGES CXX)
|
||||
project(squawk)
|
||||
|
||||
cmake_policy(SET CMP0076 NEW)
|
||||
cmake_policy(SET CMP0079 NEW)
|
||||
set(CMAKE_INCLUDE_CURRENT_DIR ON)
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
|
||||
set(CMAKE_AUTOMOC ON)
|
||||
@ -10,164 +9,127 @@ set(CMAKE_AUTOUIC ON)
|
||||
set(CMAKE_AUTORCC ON)
|
||||
|
||||
include(GNUInstallDirs)
|
||||
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake")
|
||||
include_directories(.)
|
||||
|
||||
set(WIN32_FLAG "")
|
||||
set(MACOSX_BUNDLE_FLAG "")
|
||||
if (CMAKE_BUILD_TYPE STREQUAL "Release")
|
||||
if (WIN32)
|
||||
set(WIN32_FLAG WIN32)
|
||||
endif(WIN32)
|
||||
if (APPLE)
|
||||
set(MACOSX_BUNDLE_FLAG MACOSX_BUNDLE)
|
||||
endif(APPLE)
|
||||
find_package(Qt5Widgets CONFIG REQUIRED)
|
||||
find_package(Qt5LinguistTools)
|
||||
|
||||
if(NOT CMAKE_BUILD_TYPE)
|
||||
set(CMAKE_BUILD_TYPE Debug)
|
||||
endif()
|
||||
|
||||
add_executable(squawk ${WIN32_FLAG} ${MACOSX_BUNDLE_FLAG})
|
||||
target_include_directories(squawk PRIVATE ${CMAKE_SOURCE_DIR})
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "-g -Wall -Wextra")
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "-O3")
|
||||
message("Build type: ${CMAKE_BUILD_TYPE}")
|
||||
|
||||
|
||||
set(squawk_SRC
|
||||
main.cpp
|
||||
exception.cpp
|
||||
signalcatcher.cpp
|
||||
shared/global.cpp
|
||||
shared/utils.cpp
|
||||
shared/message.cpp
|
||||
shared/vcard.cpp
|
||||
shared/icons.cpp
|
||||
shared/messageinfo.cpp
|
||||
)
|
||||
|
||||
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(WITH_KWALLET "Build KWallet support module" ON)
|
||||
option(WITH_KIO "Build KIO support module" ON)
|
||||
option(WITH_KCONFIG "Build KConfig support module" ON)
|
||||
|
||||
# Dependencies
|
||||
## Qt
|
||||
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)
|
||||
find_package(QXmpp CONFIG)
|
||||
|
||||
if (NOT QXmpp_FOUND)
|
||||
set(SYSTEM_QXMPP OFF)
|
||||
message("QXmpp package wasn't found, trying to build with bundled QXmpp")
|
||||
else ()
|
||||
else()
|
||||
message("Building with system QXmpp")
|
||||
endif ()
|
||||
endif ()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (NOT SYSTEM_QXMPP)
|
||||
target_link_libraries(squawk PRIVATE qxmpp)
|
||||
if(NOT SYSTEM_QXMPP)
|
||||
add_subdirectory(external/qxmpp)
|
||||
else ()
|
||||
target_link_libraries(squawk PRIVATE QXmpp::QXmpp)
|
||||
endif ()
|
||||
endif()
|
||||
|
||||
## KIO
|
||||
if (WITH_KIO)
|
||||
find_package(KF5KIO CONFIG)
|
||||
|
||||
if (NOT KF5KIO_FOUND)
|
||||
set(WITH_KIO OFF)
|
||||
message("KIO package wasn't found, KIO support modules wouldn't be built")
|
||||
else ()
|
||||
target_compile_definitions(squawk PRIVATE WITH_KIO)
|
||||
message("Building with support of KIO")
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
## KWallet
|
||||
if (WITH_KWALLET)
|
||||
find_package(KF5Wallet CONFIG)
|
||||
|
||||
if (NOT KF5Wallet_FOUND)
|
||||
set(WITH_KWALLET OFF)
|
||||
message("KWallet package wasn't found, KWallet support module wouldn't be built")
|
||||
else ()
|
||||
target_compile_definitions(squawk PRIVATE WITH_KWALLET)
|
||||
else()
|
||||
add_definitions(-DWITH_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)
|
||||
add_executable(squawk ${squawk_SRC} ${squawk_HEAD} ${RCC})
|
||||
target_link_libraries(squawk Qt5::Widgets)
|
||||
|
||||
## LMDB
|
||||
find_package(LMDB REQUIRED)
|
||||
if (WITH_KIO)
|
||||
find_package(KF5KIO CONFIG)
|
||||
|
||||
# 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)
|
||||
if (NOT KF5KIO_FOUND)
|
||||
set(WITH_KIO OFF)
|
||||
message("KIO package wasn't found, KIO support modules wouldn't be built")
|
||||
else()
|
||||
add_definitions(-DWITH_KIO)
|
||||
message("Building with support of KIO")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# 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(external/simpleCrypt)
|
||||
add_subdirectory(packaging)
|
||||
add_subdirectory(plugins)
|
||||
add_subdirectory(resources)
|
||||
add_subdirectory(shared)
|
||||
add_subdirectory(translations)
|
||||
add_subdirectory(ui)
|
||||
add_subdirectory(core)
|
||||
add_subdirectory(plugins)
|
||||
|
||||
add_subdirectory(external/simpleCrypt)
|
||||
|
||||
target_link_libraries(squawk squawkUI)
|
||||
target_link_libraries(squawk squawkCORE)
|
||||
target_link_libraries(squawk uuid)
|
||||
|
||||
|
||||
|
||||
add_dependencies(${CMAKE_PROJECT_NAME} translations)
|
||||
|
||||
# Install the executable
|
||||
install(TARGETS squawk DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||
install(FILES README.md DESTINATION ${CMAKE_INSTALL_DATADIR}/macaw.me/squawk)
|
||||
install(FILES LICENSE.md DESTINATION ${CMAKE_INSTALL_DATADIR}/macaw.me/squawk)
|
||||
|
||||
if (CMAKE_BUILD_TYPE STREQUAL "Release")
|
||||
if (APPLE)
|
||||
add_custom_command(TARGET squawk POST_BUILD COMMENT "Running macdeployqt..."
|
||||
COMMAND "${Qt5Widgets_DIR}/../../../bin/macdeployqt" "${CMAKE_CURRENT_BINARY_DIR}/squawk.app"
|
||||
)
|
||||
endif(APPLE)
|
||||
endif()
|
||||
|
||||
install(FILES ${QM_FILES} DESTINATION ${CMAKE_INSTALL_DATADIR}/squawk/l10n)
|
||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk.svg DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/scalable/apps)
|
||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk48.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/48x48/apps RENAME squawk.png)
|
||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk64.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/64x64/apps RENAME squawk.png)
|
||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk128.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/128x128/apps RENAME squawk.png)
|
||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk256.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/256x256/apps RENAME squawk.png)
|
||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk.desktop DESTINATION ${CMAKE_INSTALL_DATADIR}/applications)
|
||||
|
36
README.md
36
README.md
@ -4,20 +4,17 @@
|
||||
[![AUR version](https://img.shields.io/aur/version/squawk?style=flat-square)](https://aur.archlinux.org/packages/squawk/)
|
||||
[![Liberapay patrons](https://img.shields.io/liberapay/patrons/macaw.me?logo=liberapay&style=flat-square)](https://liberapay.com/macaw.me)
|
||||
|
||||
![Squawk screenshot](https://macaw.me/images/squawk/0.2.2.png)
|
||||
![Squawk screenshot](https://macaw.me/images/squawk/0.1.4.png)
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- QT 5.12 *(lower versions might work but it wasn't tested)*
|
||||
- uuid _(usually included in some other package, for example it's ***libutil-linux*** in archlinux)_
|
||||
- lmdb
|
||||
- CMake 3.4 or higher
|
||||
- CMake 3.0 or higher
|
||||
- qxmpp 1.1.0 or higher
|
||||
- KDE Frameworks: kwallet (optional)
|
||||
- KDE Frameworks: KIO (optional)
|
||||
- KDE Frameworks: KConfig (optional)
|
||||
- KDE Frameworks: KConfigWidgets (optional)
|
||||
- Boost (just one little hpp from there)
|
||||
- Imagemagick (for compilation, to rasterize an SVG logo)
|
||||
|
||||
### Getting
|
||||
|
||||
@ -37,26 +34,6 @@ You can also clone the repo and build it from source
|
||||
|
||||
Squawk requires Qt with SSL enabled. It uses CMake as build system.
|
||||
|
||||
Please check the prerequisites and install them before installation.
|
||||
|
||||
#### For Windows (Mingw-w64) build
|
||||
|
||||
You need Qt for mingw64 (MinGW 64-bit) platform when installing Qt.
|
||||
|
||||
The best way to acquire library `lmdb` and `boost` is through Msys2.
|
||||
|
||||
First install Msys2, and then install `mingw-w64-x86_64-lmdb` and `mingw-w64-x86_64-boost` by pacman.
|
||||
|
||||
Then you need to provide the cmake cache entry when calling cmake for configuration:
|
||||
|
||||
```
|
||||
cmake .. -D LMDB_ROOT_DIR:PATH=<Msys2 Mingw64 Root Directory> -D BOOST_ROOT:PATH=<Msys2 Mingw64 Root Directory>
|
||||
```
|
||||
|
||||
`<Msys2 Mingw64 Root Directory>`: e.g. `C:/msys64/mingw64`.
|
||||
|
||||
---
|
||||
|
||||
There are two ways to build, it depends whether you have qxmpp installed in your system
|
||||
|
||||
#### Building with system qxmpp
|
||||
@ -68,7 +45,7 @@ $ git clone https://git.macaw.me/blue/squawk
|
||||
$ cd squawk
|
||||
$ mkdir build
|
||||
$ cd build
|
||||
$ cmake .. [-D LMDB_ROOT_DIR:PATH=...] [-D BOOST_ROOT:PATH=...]
|
||||
$ cmake ..
|
||||
$ cmake --build .
|
||||
```
|
||||
|
||||
@ -81,12 +58,10 @@ $ git clone --recurse-submodules https://git.macaw.me/blue/squawk
|
||||
$ cd squawk
|
||||
$ mkdir build
|
||||
$ cd build
|
||||
$ cmake .. -D SYSTEM_QXMPP=False [-D LMDB_ROOT_DIR:PATH=...] [-D BOOST_ROOT:PATH=...]
|
||||
$ cmake .. -D SYSTEM_QXMPP=False
|
||||
$ cmake --build .
|
||||
```
|
||||
|
||||
You can always refer to `appveyor.yml` to see how AppVeyor build squawk.
|
||||
|
||||
### List of keys
|
||||
|
||||
Here is the list of keys you can pass to configuration phase of `cmake ..`.
|
||||
@ -94,7 +69,6 @@ Here is the list of keys you can pass to configuration phase of `cmake ..`.
|
||||
- `SYSTEM_QXMPP` - `True` tries to link against `qxmpp` installed in the system, `False` builds bundled `qxmpp` library (default is `True`)
|
||||
- `WITH_KWALLET` - `True` builds the `KWallet` capability module if `KWallet` is installed and if not goes to `False`. `False` disables `KWallet` support (default is `True`)
|
||||
- `WITH_KIO` - `True` builds the `KIO` capability module if `KIO` is installed and if not goes to `False`. `False` disables `KIO` support (default is `True`)
|
||||
- `WITH_KCONFIG` - `True` builds the `KConfig` and `KConfigWidgets` capability module if such packages are installed and if not goes to `False`. `False` disables `KConfig` and `KConfigWidgets` support (default is `True`)
|
||||
|
||||
## License
|
||||
|
||||
|
117
appveyor.yml
117
appveyor.yml
@ -1,117 +0,0 @@
|
||||
image:
|
||||
- Visual Studio 2019
|
||||
- "Previous Ubuntu1804"
|
||||
- macOS-Mojave
|
||||
|
||||
branches:
|
||||
except:
|
||||
- gh-pages
|
||||
|
||||
for:
|
||||
-
|
||||
matrix:
|
||||
only:
|
||||
- image: Visual Studio 2019
|
||||
|
||||
environment:
|
||||
QTDIR: C:\Qt\5.15.2\mingw81_64
|
||||
QTTOOLDIR: C:\Qt\Tools\mingw810_64\bin
|
||||
QTNINJADIR: C:\Qt\Tools\Ninja
|
||||
|
||||
install:
|
||||
- set PATH=%QTTOOLDIR%;%QTNINJADIR%;%QTDIR%\bin;%PATH%
|
||||
- git submodule update --init --recursive
|
||||
|
||||
before_build:
|
||||
- choco install --yes zstandard
|
||||
- choco install --yes --version=7.1.0.2 imagemagick.app
|
||||
- set PATH=C:\Program Files\ImageMagick-7.1.0-Q16-HDRI;%PATH%
|
||||
|
||||
- mkdir lmdb
|
||||
- cd lmdb
|
||||
- curl -OL https://mirror.msys2.org/mingw/mingw64/mingw-w64-x86_64-lmdb-0.9.27-1-any.pkg.tar.zst
|
||||
- zstd -d ./mingw-w64-x86_64-lmdb-0.9.27-1-any.pkg.tar.zst
|
||||
- tar -xvf ./mingw-w64-x86_64-lmdb-0.9.27-1-any.pkg.tar
|
||||
- cd ..
|
||||
|
||||
- mkdir boost
|
||||
- cd boost
|
||||
- curl -OL https://repo.msys2.org/mingw/mingw64/mingw-w64-x86_64-boost-1.77.0-2-any.pkg.tar.zst
|
||||
- zstd -d ./mingw-w64-x86_64-boost-1.77.0-2-any.pkg.tar.zst
|
||||
- tar -xvf ./mingw-w64-x86_64-boost-1.77.0-2-any.pkg.tar
|
||||
- cd ..
|
||||
|
||||
- mkdir build
|
||||
- cd build
|
||||
- cmake -GNinja -DCMAKE_BUILD_TYPE:String=Release -DCMAKE_PREFIX_PATH:STRING=%QTDIR% -DLMDB_ROOT_DIR:PATH=C:/projects/squawk/lmdb/mingw64 -DBOOST_ROOT:PATH=C:/projects/squawk/boost/mingw64 ..
|
||||
|
||||
build_script:
|
||||
- cmake --build .
|
||||
- mkdir deploy
|
||||
- cd deploy
|
||||
- copy ..\squawk.exe .\
|
||||
- copy ..\external\qxmpp\src\libqxmpp.dll .\
|
||||
- windeployqt .\squawk.exe
|
||||
- windeployqt .\libqxmpp.dll
|
||||
- cd ..\..
|
||||
|
||||
artifacts:
|
||||
- path: build/deploy/squawk.exe
|
||||
name: Squawk executable (Qt 5.15.2)
|
||||
|
||||
- path: build/deploy
|
||||
name: Squawk deployment with Qt Framework
|
||||
|
||||
-
|
||||
matrix:
|
||||
only:
|
||||
- image: macOS-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)
|
@ -22,28 +22,23 @@
|
||||
|
||||
find_path(LMDB_ROOT_DIR
|
||||
NAMES include/lmdb.h
|
||||
)
|
||||
)
|
||||
|
||||
find_library(LMDB_LIBRARIES
|
||||
NAMES liblmdb.a liblmdb.so liblmdb.so.a liblmdb.dll.a # We want lmdb to be static, if possible
|
||||
NAMES lmdb
|
||||
HINTS ${LMDB_ROOT_DIR}/lib
|
||||
)
|
||||
|
||||
add_library(lmdb UNKNOWN IMPORTED)
|
||||
set_target_properties(lmdb PROPERTIES
|
||||
IMPORTED_LOCATION ${LMDB_LIBRARIES}
|
||||
)
|
||||
)
|
||||
|
||||
find_path(LMDB_INCLUDE_DIRS
|
||||
NAMES lmdb.h
|
||||
HINTS ${LMDB_ROOT_DIR}/include
|
||||
)
|
||||
)
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(LMDB DEFAULT_MSG
|
||||
LMDB_LIBRARIES
|
||||
LMDB_INCLUDE_DIRS
|
||||
)
|
||||
)
|
||||
|
||||
mark_as_advanced(
|
||||
LMDB_ROOT_DIR
|
||||
|
@ -1,15 +0,0 @@
|
||||
find_path(Signal_INCLUDE_DIR NAMES signal/signal_protocol.h)
|
||||
find_library(Signal_LIBRARY signal-protocol-c)
|
||||
mark_as_advanced(Signal_INCLUDE_DIR Signal_LIBRARY)
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(Signal REQUIRED_VARS Signal_LIBRARY Signal_INCLUDE_DIR)
|
||||
|
||||
if (Signal_FOUND AND NOT TARGET Signal::Signal)
|
||||
add_library(Signal::Signal UNKNOWN IMPORTED)
|
||||
set_target_properties(Signal::Signal PROPERTIES
|
||||
IMPORTED_LINK_INTERFACE_LANGUAGES "C"
|
||||
IMPORTED_LOCATION "${Signal_LIBRARY}"
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${Signal_INCLUDE_DIR}"
|
||||
)
|
||||
endif ()
|
@ -1,41 +0,0 @@
|
||||
<?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,29 +1,49 @@
|
||||
set(SIGNALCATCHER_SOURCE signalcatcher.cpp)
|
||||
if(WIN32)
|
||||
set(SIGNALCATCHER_SOURCE signalcatcher_win32.cpp)
|
||||
endif(WIN32)
|
||||
cmake_minimum_required(VERSION 3.3)
|
||||
project(squawkCORE)
|
||||
|
||||
target_sources(squawk PRIVATE
|
||||
account.cpp
|
||||
account.h
|
||||
adapterfunctions.cpp
|
||||
adapterfunctions.h
|
||||
conference.cpp
|
||||
conference.h
|
||||
contact.cpp
|
||||
contact.h
|
||||
networkaccess.cpp
|
||||
networkaccess.h
|
||||
rosteritem.cpp
|
||||
rosteritem.h
|
||||
${SIGNALCATCHER_SOURCE}
|
||||
signalcatcher.h
|
||||
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake")
|
||||
|
||||
set(CMAKE_AUTOMOC ON)
|
||||
|
||||
find_package(Qt5Core CONFIG REQUIRED)
|
||||
find_package(Qt5Gui CONFIG REQUIRED)
|
||||
find_package(Qt5Network CONFIG REQUIRED)
|
||||
find_package(Qt5Xml CONFIG REQUIRED)
|
||||
find_package(LMDB REQUIRED)
|
||||
|
||||
set(squawkCORE_SRC
|
||||
squawk.cpp
|
||||
squawk.h
|
||||
)
|
||||
account.cpp
|
||||
archive.cpp
|
||||
rosteritem.cpp
|
||||
contact.cpp
|
||||
conference.cpp
|
||||
urlstorage.cpp
|
||||
networkaccess.cpp
|
||||
adapterFuctions.cpp
|
||||
handlers/messagehandler.cpp
|
||||
handlers/rosterhandler.cpp
|
||||
)
|
||||
|
||||
target_include_directories(squawk PRIVATE ${LMDB_INCLUDE_DIRS})
|
||||
|
||||
add_subdirectory(handlers)
|
||||
add_subdirectory(storage)
|
||||
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;
|
||||
|
||||
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):
|
||||
Account::Account(const QString& p_login, const QString& p_server, const QString& p_password, const QString& p_name, NetworkAccess* p_net, QObject* parent):
|
||||
QObject(parent),
|
||||
name(p_name),
|
||||
archiveQueries(),
|
||||
@ -41,15 +41,13 @@ Account::Account(const QString& p_login, const QString& p_server, const QString&
|
||||
rcpm(new QXmppMessageReceiptManager()),
|
||||
reconnectScheduled(false),
|
||||
reconnectTimer(new QTimer),
|
||||
avatarHash(),
|
||||
avatarType(),
|
||||
ownVCardRequestInProgress(false),
|
||||
network(p_net),
|
||||
passwordType(Shared::AccountPassword::plain),
|
||||
lastError(Error::none),
|
||||
pepSupport(false),
|
||||
active(p_active),
|
||||
notReadyPassword(false),
|
||||
mh(new MessageHandler(this)),
|
||||
rh(new RosterHandler(this)),
|
||||
vh(new VCardHandler(this))
|
||||
rh(new RosterHandler(this))
|
||||
{
|
||||
config.setUser(p_login);
|
||||
config.setDomain(p_server);
|
||||
@ -75,6 +73,10 @@ Account::Account(const QString& p_login, const QString& p_server, const QString&
|
||||
|
||||
client.addExtension(mm);
|
||||
client.addExtension(bm);
|
||||
|
||||
QObject::connect(vm, &QXmppVCardManager::vCardReceived, this, &Account::onVCardReceived);
|
||||
//QObject::connect(&vm, &QXmppVCardManager::clientVCardReceived, this, &Account::onOwnVCardReceived); //for some reason it doesn't work, launching from common handler
|
||||
|
||||
client.addExtension(um);
|
||||
QObject::connect(um, &QXmppUploadRequestManager::slotReceived, mh, &MessageHandler::onUploadSlotReceived);
|
||||
QObject::connect(um, &QXmppUploadRequestManager::requestFailed, mh, &MessageHandler::onUploadSlotRequestFailed);
|
||||
@ -89,18 +91,62 @@ Account::Account(const QString& p_login, const QString& p_server, const QString&
|
||||
client.addExtension(rcpm);
|
||||
QObject::connect(rcpm, &QXmppMessageReceiptManager::messageDelivered, mh, &MessageHandler::onReceiptReceived);
|
||||
|
||||
|
||||
QString path(QStandardPaths::writableLocation(QStandardPaths::CacheLocation));
|
||||
path += "/" + name;
|
||||
QDir dir(path);
|
||||
|
||||
if (!dir.exists()) {
|
||||
bool res = dir.mkpath(path);
|
||||
if (!res) {
|
||||
qDebug() << "Couldn't create a cache directory for account" << name;
|
||||
throw 22;
|
||||
}
|
||||
}
|
||||
|
||||
QFile* avatar = new QFile(path + "/avatar.png");
|
||||
QString type = "png";
|
||||
if (!avatar->exists()) {
|
||||
delete avatar;
|
||||
avatar = new QFile(path + "/avatar.jpg");
|
||||
type = "jpg";
|
||||
if (!avatar->exists()) {
|
||||
delete avatar;
|
||||
avatar = new QFile(path + "/avatar.jpeg");
|
||||
type = "jpeg";
|
||||
if (!avatar->exists()) {
|
||||
delete avatar;
|
||||
avatar = new QFile(path + "/avatar.gif");
|
||||
type = "gif";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (avatar->exists()) {
|
||||
if (avatar->open(QFile::ReadOnly)) {
|
||||
QCryptographicHash sha1(QCryptographicHash::Sha1);
|
||||
sha1.addData(avatar);
|
||||
avatarHash = sha1.result();
|
||||
avatarType = type;
|
||||
}
|
||||
}
|
||||
if (avatarType.size() != 0) {
|
||||
presence.setVCardUpdateType(QXmppPresence::VCardUpdateValidPhoto);
|
||||
presence.setPhotoHash(avatarHash.toUtf8());
|
||||
} else {
|
||||
presence.setVCardUpdateType(QXmppPresence::VCardUpdateNotReady);
|
||||
}
|
||||
|
||||
reconnectTimer->setSingleShot(true);
|
||||
QObject::connect(reconnectTimer, &QTimer::timeout, this, &Account::onReconnectTimer);
|
||||
|
||||
if (name == "Test") {
|
||||
QXmppLogger* logger = new QXmppLogger(this);
|
||||
logger->setLoggingType(QXmppLogger::SignalLogging);
|
||||
client.setLogger(logger);
|
||||
|
||||
QObject::connect(logger, &QXmppLogger::message, this, [](QXmppLogger::MessageType type, const QString& text){
|
||||
qDebug() << text;
|
||||
});
|
||||
}
|
||||
// QXmppLogger* logger = new QXmppLogger(this);
|
||||
// logger->setLoggingType(QXmppLogger::SignalLogging);
|
||||
// client.setLogger(logger);
|
||||
//
|
||||
// QObject::connect(logger, &QXmppLogger::message, this, [](QXmppLogger::MessageType type, const QString& text){
|
||||
// qDebug() << text;
|
||||
// });
|
||||
}
|
||||
|
||||
Account::~Account()
|
||||
@ -114,7 +160,6 @@ Account::~Account()
|
||||
QObject::disconnect(network, &NetworkAccess::downloadFileComplete, mh, &MessageHandler::onDownloadFileComplete);
|
||||
QObject::disconnect(network, &NetworkAccess::loadFileError, mh, &MessageHandler::onLoadFileError);
|
||||
|
||||
delete vh;
|
||||
delete mh;
|
||||
delete rh;
|
||||
|
||||
@ -140,12 +185,7 @@ void Core::Account::connect()
|
||||
reconnectTimer->stop();
|
||||
}
|
||||
if (state == Shared::ConnectionState::disconnected) {
|
||||
if (notReadyPassword) {
|
||||
emit needPassword();
|
||||
} else {
|
||||
client.connectToServer(config, presence);
|
||||
}
|
||||
|
||||
} else {
|
||||
qDebug("An attempt to connect an account which is already connected, skipping");
|
||||
}
|
||||
@ -184,7 +224,6 @@ void Core::Account::onClientStateChange(QXmppClient::State st)
|
||||
dm->requestItems(getServer());
|
||||
dm->requestInfo(getServer());
|
||||
}
|
||||
lastError = Error::none;
|
||||
emit connectionStateChanged(state);
|
||||
}
|
||||
} else {
|
||||
@ -216,17 +255,45 @@ void Core::Account::onClientStateChange(QXmppClient::State st)
|
||||
|
||||
void Core::Account::reconnect()
|
||||
{
|
||||
if (!reconnectScheduled) { //TODO define behavior if It was connection or disconnecting
|
||||
if (state == Shared::ConnectionState::connected) {
|
||||
if (state == Shared::ConnectionState::connected && !reconnectScheduled) {
|
||||
reconnectScheduled = true;
|
||||
reconnectTimer->start(500);
|
||||
client.disconnectFromServer();
|
||||
} else {
|
||||
qDebug() << "An attempt to reconnect account" << getName() << "which was not connected";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QString Core::Account::getName() const {
|
||||
return name;}
|
||||
|
||||
QString Core::Account::getLogin() const {
|
||||
return config.user();}
|
||||
|
||||
QString Core::Account::getPassword() const {
|
||||
return config.password();}
|
||||
|
||||
QString Core::Account::getServer() const {
|
||||
return config.domain();}
|
||||
|
||||
Shared::AccountPassword Core::Account::getPasswordType() const {
|
||||
return passwordType;}
|
||||
|
||||
void Core::Account::setPasswordType(Shared::AccountPassword pt) {
|
||||
passwordType = pt; }
|
||||
|
||||
void Core::Account::setLogin(const QString& p_login) {
|
||||
config.setUser(p_login);}
|
||||
|
||||
void Core::Account::setName(const QString& p_name) {
|
||||
name = p_name;}
|
||||
|
||||
void Core::Account::setPassword(const QString& p_password) {
|
||||
config.setPassword(p_password);}
|
||||
|
||||
void Core::Account::setServer(const QString& p_server) {
|
||||
config.setDomain(p_server);}
|
||||
|
||||
Shared::Availability Core::Account::getAvailability() const
|
||||
{
|
||||
if (state == Shared::ConnectionState::connected) {
|
||||
@ -258,11 +325,32 @@ void Core::Account::onPresenceReceived(const QXmppPresence& p_presence)
|
||||
QString jid = comps.front().toLower();
|
||||
QString resource = comps.back();
|
||||
|
||||
if (jid == getBareJid()) {
|
||||
QString myJid = getLogin() + "@" + getServer();
|
||||
|
||||
if (jid == myJid) {
|
||||
if (resource == getResource()) {
|
||||
emit availabilityChanged(static_cast<Shared::Availability>(p_presence.availableStatusType()));
|
||||
} else {
|
||||
vh->handleOtherPresenceOfMyAccountChange(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) {
|
||||
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 {
|
||||
RosterItem* item = rh->getRosterItem(jid);
|
||||
@ -304,6 +392,18 @@ void Core::Account::onPresenceReceived(const QXmppPresence& p_presence)
|
||||
}
|
||||
}
|
||||
|
||||
QString Core::Account::getResource() const {
|
||||
return config.resource();}
|
||||
|
||||
void Core::Account::setResource(const QString& p_resource) {
|
||||
config.setResource(p_resource);}
|
||||
|
||||
QString Core::Account::getFullJid() const {
|
||||
return getLogin() + "@" + getServer() + "/" + getResource();}
|
||||
|
||||
void Core::Account::sendMessage(const Shared::Message& data) {
|
||||
mh->sendMessage(data);}
|
||||
|
||||
void Core::Account::onMamMessageReceived(const QString& queryId, const QXmppMessage& msg)
|
||||
{
|
||||
if (msg.id().size() > 0 && (msg.body().size() > 0 || msg.outOfBandUrl().size() > 0)) {
|
||||
@ -369,14 +469,6 @@ void Core::Account::onContactNeedHistory(const QString& before, const QString& a
|
||||
query.setAfter(after);
|
||||
}
|
||||
}
|
||||
if (before.size() == 0 && after.size() == 0) {
|
||||
// https://xmpp.org/extensions/xep-0313.html#sect-idm46556759682304
|
||||
// To request the page at the end of the archive
|
||||
// (i.e. the most recent messages), include just an
|
||||
// empty <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;
|
||||
}
|
||||
|
||||
@ -417,7 +509,6 @@ void Core::Account::onClientError(QXmppClient::Error err)
|
||||
qDebug() << "Error";
|
||||
QString errorText;
|
||||
QString errorType;
|
||||
lastError = Error::other;
|
||||
switch (err) {
|
||||
case QXmppClient::SocketError:
|
||||
errorText = client.socketErrorString();
|
||||
@ -459,7 +550,6 @@ void Core::Account::onClientError(QXmppClient::Error err)
|
||||
break;
|
||||
case QXmppStanza::Error::NotAuthorized:
|
||||
errorText = "Authentication error";
|
||||
lastError = Error::authentication;
|
||||
break;
|
||||
#if (QXMPP_VERSION) < QT_VERSION_CHECK(1, 3, 0)
|
||||
case QXmppStanza::Error::PaymentRequired:
|
||||
@ -503,9 +593,6 @@ void Core::Account::onClientError(QXmppClient::Error err)
|
||||
errorText = "Policy violation";
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
errorText = "Unknown Error";
|
||||
break;
|
||||
}
|
||||
|
||||
errorType = "Client stream error";
|
||||
@ -569,166 +656,6 @@ void Core::Account::setRoomJoined(const QString& jid, bool joined)
|
||||
conf->setJoined(joined);
|
||||
}
|
||||
|
||||
void Core::Account::onDiscoveryItemsReceived(const QXmppDiscoveryIq& items)
|
||||
{
|
||||
if (items.from() == getServer()) {
|
||||
std::set<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){
|
||||
rh->removeRoomRequest(jid);}
|
||||
|
||||
@ -751,9 +678,248 @@ void Core::Account::renameContactRequest(const QString& jid, const QString& newN
|
||||
}
|
||||
}
|
||||
|
||||
void Core::Account::invalidatePassword() {
|
||||
notReadyPassword = true;}
|
||||
void Core::Account::onVCardReceived(const QXmppVCardIq& card)
|
||||
{
|
||||
QString id = card.from();
|
||||
QStringList comps = id.split("/");
|
||||
QString jid = comps.front().toLower();
|
||||
QString resource("");
|
||||
if (comps.size() > 1) {
|
||||
resource = comps.back();
|
||||
}
|
||||
pendingVCardRequests.erase(id);
|
||||
RosterItem* item = rh->getRosterItem(jid);
|
||||
|
||||
Core::Account::Error Core::Account::getLastError() const {
|
||||
return lastError;}
|
||||
if (item == 0) {
|
||||
if (jid == getLogin() + "@" + getServer()) {
|
||||
onOwnVCardReceived(card);
|
||||
} else {
|
||||
qDebug() << "received vCard" << jid << "doesn't belong to any of known contacts or conferences, skipping";
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
Shared::VCard vCard = item->handleResponseVCard(card, resource);
|
||||
|
||||
emit receivedVCard(jid, vCard);
|
||||
}
|
||||
|
||||
void Core::Account::onOwnVCardReceived(const QXmppVCardIq& card)
|
||||
{
|
||||
QByteArray ava = card.photo();
|
||||
bool avaChanged = false;
|
||||
QString path = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/" + name + "/";
|
||||
if (ava.size() > 0) {
|
||||
QCryptographicHash sha1(QCryptographicHash::Sha1);
|
||||
sha1.addData(ava);
|
||||
QString newHash(sha1.result());
|
||||
QMimeDatabase db;
|
||||
QMimeType newType = db.mimeTypeForData(ava);
|
||||
if (avatarType.size() > 0) {
|
||||
if (avatarHash != newHash) {
|
||||
QString oldPath = path + "avatar." + avatarType;
|
||||
QFile oldAvatar(oldPath);
|
||||
bool oldToRemove = false;
|
||||
if (oldAvatar.exists()) {
|
||||
if (oldAvatar.rename(oldPath + ".bak")) {
|
||||
oldToRemove = true;
|
||||
} else {
|
||||
qDebug() << "Received new avatar for account" << name << "but can't get rid of the old one, doing nothing";
|
||||
}
|
||||
}
|
||||
QFile newAvatar(path + "avatar." + newType.preferredSuffix());
|
||||
if (newAvatar.open(QFile::WriteOnly)) {
|
||||
newAvatar.write(ava);
|
||||
newAvatar.close();
|
||||
avatarHash = newHash;
|
||||
avatarType = newType.preferredSuffix();
|
||||
avaChanged = true;
|
||||
} else {
|
||||
qDebug() << "Received new avatar for account" << name << "but can't save it";
|
||||
if (oldToRemove) {
|
||||
qDebug() << "rolling back to the old avatar";
|
||||
if (!oldAvatar.rename(oldPath)) {
|
||||
qDebug() << "Couldn't roll back to the old avatar in account" << name;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
QFile newAvatar(path + "avatar." + newType.preferredSuffix());
|
||||
if (newAvatar.open(QFile::WriteOnly)) {
|
||||
newAvatar.write(ava);
|
||||
newAvatar.close();
|
||||
avatarHash = newHash;
|
||||
avatarType = newType.preferredSuffix();
|
||||
avaChanged = true;
|
||||
} else {
|
||||
qDebug() << "Received new avatar for account" << name << "but can't save it";
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (avatarType.size() > 0) {
|
||||
QFile oldAvatar(path + "avatar." + avatarType);
|
||||
if (!oldAvatar.remove()) {
|
||||
qDebug() << "Received vCard for account" << name << "without avatar, but can't get rid of the file, doing nothing";
|
||||
} else {
|
||||
avatarType = "";
|
||||
avatarHash = "";
|
||||
avaChanged = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (avaChanged) {
|
||||
QMap<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 <QXmppBookmarkSet.h>
|
||||
#include <QXmppUploadRequestManager.h>
|
||||
#include <QXmppVCardIq.h>
|
||||
#include <QXmppVCardManager.h>
|
||||
#include <QXmppMessageReceiptManager.h>
|
||||
|
||||
#include "shared/shared.h"
|
||||
#include "shared.h"
|
||||
#include "contact.h"
|
||||
#include "conference.h"
|
||||
#include "networkaccess.h"
|
||||
|
||||
#include "handlers/messagehandler.h"
|
||||
#include "handlers/rosterhandler.h"
|
||||
#include "handlers/vcardhandler.h"
|
||||
|
||||
namespace Core
|
||||
{
|
||||
@ -59,20 +59,12 @@ class Account : public QObject
|
||||
Q_OBJECT
|
||||
friend class MessageHandler;
|
||||
friend class RosterHandler;
|
||||
friend class VCardHandler;
|
||||
public:
|
||||
enum class Error {
|
||||
authentication,
|
||||
other,
|
||||
none
|
||||
};
|
||||
|
||||
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 = 0);
|
||||
~Account();
|
||||
@ -84,12 +76,8 @@ public:
|
||||
QString getPassword() const;
|
||||
QString getResource() const;
|
||||
QString getAvatarPath() const;
|
||||
QString getBareJid() const;
|
||||
QString getFullJid() const;
|
||||
Shared::Availability getAvailability() const;
|
||||
Shared::AccountPassword getPasswordType() const;
|
||||
Error getLastError() const;
|
||||
bool getActive() const;
|
||||
|
||||
void setName(const QString& p_name);
|
||||
void setLogin(const QString& p_login);
|
||||
@ -98,8 +86,8 @@ public:
|
||||
void setResource(const QString& p_resource);
|
||||
void setAvailability(Shared::Availability avail);
|
||||
void setPasswordType(Shared::AccountPassword pt);
|
||||
QString getFullJid() const;
|
||||
void sendMessage(const Shared::Message& data);
|
||||
void setActive(bool p_active);
|
||||
void requestArchive(const QString& jid, int count, const QString& before);
|
||||
void subscribeToContact(const QString& jid, const QString& reason);
|
||||
void unsubscribeFromContact(const QString& jid, const QString& reason);
|
||||
@ -115,9 +103,6 @@ public:
|
||||
void removeRoomRequest(const QString& jid);
|
||||
void addRoomRequest(const QString& jid, const QString& nick, const QString& password, bool autoJoin);
|
||||
void uploadVCard(const Shared::VCard& card);
|
||||
void resendMessage(const QString& jid, const QString& id);
|
||||
void replaceMessage(const QString& originalId, const Shared::Message& data);
|
||||
void invalidatePassword();
|
||||
|
||||
public slots:
|
||||
void connect();
|
||||
@ -150,7 +135,6 @@ signals:
|
||||
void receivedVCard(const QString& jid, const Shared::VCard& card);
|
||||
void uploadFile(const QFileInfo& file, const QUrl& set, const QUrl& get, QMap<QString, QString> headers);
|
||||
void uploadFileError(const QString& jid, const QString& messageId, const QString& error);
|
||||
void needPassword();
|
||||
|
||||
private:
|
||||
QString name;
|
||||
@ -171,16 +155,16 @@ private:
|
||||
bool reconnectScheduled;
|
||||
QTimer* reconnectTimer;
|
||||
|
||||
std::set<QString> pendingVCardRequests;
|
||||
|
||||
QString avatarHash;
|
||||
QString avatarType;
|
||||
bool ownVCardRequestInProgress;
|
||||
NetworkAccess* network;
|
||||
Shared::AccountPassword passwordType;
|
||||
Error lastError;
|
||||
bool pepSupport;
|
||||
bool active;
|
||||
bool notReadyPassword;
|
||||
|
||||
MessageHandler* mh;
|
||||
RosterHandler* rh;
|
||||
VCardHandler* vh;
|
||||
|
||||
private slots:
|
||||
void onClientStateChange(QXmppClient::State state);
|
||||
@ -194,6 +178,9 @@ private slots:
|
||||
|
||||
void onMamLog(QXmppLogger::MessageType type, const QString &msg);
|
||||
|
||||
void onVCardReceived(const QXmppVCardIq& card);
|
||||
void onOwnVCardReceived(const QXmppVCardIq& card);
|
||||
|
||||
void onDiscoveryItemsReceived (const QXmppDiscoveryIq& items);
|
||||
void onDiscoveryInfoReceived (const QXmppDiscoveryIq& info);
|
||||
void onContactHistoryResponse(const std::list<Shared::Message>& list, bool last);
|
||||
@ -202,6 +189,9 @@ private:
|
||||
void handleDisconnection();
|
||||
void onReconnectTimer();
|
||||
};
|
||||
|
||||
void initializeVCard(Shared::VCard& vCard, const QXmppVCardIq& card);
|
||||
void initializeQXmppVCard(QXmppVCardIq& card, const Shared::VCard& vCard);
|
||||
}
|
||||
|
||||
|
||||
|
@ -15,8 +15,10 @@
|
||||
* 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 "adapterfunctions.h"
|
||||
#include "account.h"
|
||||
|
||||
void Core::initializeVCard(Shared::VCard& vCard, const QXmppVCardIq& card)
|
||||
{
|
||||
@ -262,10 +264,12 @@ void Core::initializeQXmppVCard(QXmppVCardIq& iq, const Shared::VCard& card) {
|
||||
phone.setType(phone.type() | QXmppVCardPhone::Preferred);
|
||||
}
|
||||
}
|
||||
for (const std::pair<const QString, QXmppVCardPhone>& phone : phones) {
|
||||
for (const std::pair<QString, QXmppVCardPhone>& phone : phones) {
|
||||
phs.push_back(phone.second);
|
||||
}
|
||||
|
||||
iq.setEmails(emails);
|
||||
iq.setPhones(phs);
|
||||
}
|
||||
|
||||
#endif // CORE_ADAPTER_FUNCTIONS_H
|
@ -1,32 +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 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
|
@ -58,21 +58,13 @@ void Core::Archive::open(const QString& account)
|
||||
}
|
||||
|
||||
mdb_env_set_maxdbs(environment, 5);
|
||||
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_set_mapsize(environment, 512UL * 1024UL * 1024UL);
|
||||
mdb_env_open(environment, path.toStdString().c_str(), 0, 0664);
|
||||
|
||||
MDB_txn *txn;
|
||||
mdb_txn_begin(environment, NULL, 0, &txn);
|
||||
mdb_dbi_open(txn, "main", MDB_CREATE, &main);
|
||||
mdb_dbi_open(txn, "order", MDB_CREATE | MDB_INTEGERKEY | MDB_INTEGERDUP | MDB_DUPSORT, &order);
|
||||
mdb_dbi_open(txn, "order", MDB_CREATE | MDB_INTEGERKEY, &order);
|
||||
mdb_dbi_open(txn, "stats", MDB_CREATE, &stats);
|
||||
mdb_dbi_open(txn, "avatars", MDB_CREATE, &avatars);
|
||||
mdb_dbi_open(txn, "sid", MDB_CREATE, &sid);
|
||||
@ -123,7 +115,6 @@ bool Core::Archive::addElement(const Shared::Message& message)
|
||||
if (!opened) {
|
||||
throw Closed("addElement", jid.toStdString());
|
||||
}
|
||||
qDebug() << "Adding message with id " << message.getId();
|
||||
QByteArray ba;
|
||||
QDataStream ds(&ba, QIODevice::WriteOnly);
|
||||
message.serialize(ds);
|
||||
@ -317,9 +308,8 @@ void Core::Archive::changeMessage(const QString& id, const QMap<QString, QVarian
|
||||
}
|
||||
}
|
||||
|
||||
QString qsid = msg.getStanzaId();
|
||||
if (qsid.size() > 0 && (idChange || !hadStanzaId)) {
|
||||
std::string szid = qsid.toStdString();
|
||||
if (msg.getStanzaId().size() > 0 && (idChange || !hadStanzaId)) {
|
||||
const std::string& szid = msg.getStanzaId().toStdString();
|
||||
|
||||
lmdbData.mv_size = szid.size();
|
||||
lmdbData.mv_data = (char*)szid.c_str();
|
@ -25,7 +25,7 @@
|
||||
#include <QMimeType>
|
||||
|
||||
#include "shared/message.h"
|
||||
#include "shared/exception.h"
|
||||
#include "exception.h"
|
||||
#include <lmdb.h>
|
||||
#include <list>
|
||||
|
@ -356,7 +356,7 @@ Shared::VCard Core::Conference::handleResponseVCard(const QXmppVCardIq& card, co
|
||||
QMap<QString, QVariant> Core::Conference::getAllAvatars() const
|
||||
{
|
||||
QMap<QString, QVariant> result;
|
||||
for (const std::pair<const QString, Archive::AvatarInfo>& pair : exParticipants) {
|
||||
for (const std::pair<QString, Archive::AvatarInfo>& pair : exParticipants) {
|
||||
result.insert(pair.first, avatarPath(pair.first) + "." + pair.second.type);
|
||||
}
|
||||
return result;
|
||||
|
@ -1,8 +0,0 @@
|
||||
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);
|
||||
break;
|
||||
case QXmppMessage::Error: {
|
||||
std::tuple<bool, QString, QString> ids = getOriginalPendingMessageId(msg.id());
|
||||
if (std::get<0>(ids)) {
|
||||
QString id = std::get<1>(ids);
|
||||
QString jid = std::get<2>(ids);
|
||||
QString id = msg.id();
|
||||
std::map<QString, QString>::const_iterator itr = pendingStateMessages.find(id);
|
||||
if (itr != pendingStateMessages.end()) {
|
||||
QString jid = itr->second;
|
||||
RosterItem* cnt = acc->rh->getRosterItem(jid);
|
||||
QMap<QString, QVariant> cData = {
|
||||
{"state", static_cast<uint>(Shared::Message::State::error)},
|
||||
@ -53,7 +53,9 @@ void Core::MessageHandler::onMessageReceived(const QXmppMessage& msg)
|
||||
if (cnt != 0) {
|
||||
cnt->changeMessage(id, cData);
|
||||
}
|
||||
;
|
||||
emit acc->changeMessage(jid, id, cData);
|
||||
pendingStateMessages.erase(itr);
|
||||
handled = true;
|
||||
} else {
|
||||
qDebug() << "received a message with type \"Error\", not sure what to do with it now, skipping";
|
||||
@ -71,14 +73,14 @@ void Core::MessageHandler::onMessageReceived(const QXmppMessage& msg)
|
||||
|
||||
bool Core::MessageHandler::handleChatMessage(const QXmppMessage& msg, bool outgoing, bool forwarded, bool guessing)
|
||||
{
|
||||
if (msg.body().size() != 0 || msg.outOfBandUrl().size() > 0) {
|
||||
const QString& body(msg.body());
|
||||
if (body.size() != 0) {
|
||||
Shared::Message sMsg(Shared::Message::chat);
|
||||
initializeMessage(sMsg, msg, outgoing, forwarded, guessing);
|
||||
QString jid = sMsg.getPenPalJid();
|
||||
Contact* cnt = acc->rh->getContact(jid);
|
||||
if (cnt == 0) {
|
||||
cnt = acc->rh->addOutOfRosterContact(jid);
|
||||
qDebug() << "appending message" << sMsg.getId() << "to an out of roster contact";
|
||||
}
|
||||
if (outgoing) {
|
||||
if (forwarded) {
|
||||
@ -109,6 +111,7 @@ bool Core::MessageHandler::handleGroupMessage(const QXmppMessage& msg, bool outg
|
||||
{
|
||||
const QString& body(msg.body());
|
||||
if (body.size() != 0) {
|
||||
QString id = msg.id();
|
||||
|
||||
Shared::Message sMsg(Shared::Message::groupChat);
|
||||
initializeMessage(sMsg, msg, outgoing, forwarded, guessing);
|
||||
@ -118,11 +121,12 @@ bool Core::MessageHandler::handleGroupMessage(const QXmppMessage& msg, bool outg
|
||||
return false;
|
||||
}
|
||||
|
||||
std::tuple<bool, QString, QString> ids = getOriginalPendingMessageId(msg.id());
|
||||
if (std::get<0>(ids)) {
|
||||
std::map<QString, QString>::const_iterator pItr = pendingStateMessages.find(id);
|
||||
if (pItr != pendingStateMessages.end()) {
|
||||
QMap<QString, QVariant> cData = {{"state", static_cast<uint>(Shared::Message::State::delivered)}};
|
||||
cnt->changeMessage(std::get<1>(ids), cData);
|
||||
emit acc->changeMessage(std::get<2>(ids), std::get<1>(ids), cData);
|
||||
cnt->changeMessage(id, cData);
|
||||
pendingStateMessages.erase(pItr);
|
||||
emit acc->changeMessage(jid, id, cData);
|
||||
} else {
|
||||
QString oId = msg.replaceId();
|
||||
if (oId.size() > 0) {
|
||||
@ -159,7 +163,6 @@ void Core::MessageHandler::initializeMessage(Shared::Message& target, const QXmp
|
||||
id = source.id();
|
||||
}
|
||||
target.setStanzaId(source.stanzaId());
|
||||
qDebug() << "initializing message with originId:" << source.originId() << ", id:" << source.id() << ", stansaId:" << source.stanzaId();
|
||||
#else
|
||||
id = source.id();
|
||||
#endif
|
||||
@ -168,7 +171,6 @@ void Core::MessageHandler::initializeMessage(Shared::Message& target, const QXmp
|
||||
if (messageId.size() == 0) {
|
||||
target.generateRandomId(); //TODO out of desperation, I need at least a random ID
|
||||
messageId = target.getId();
|
||||
qDebug() << "Had do initialize a message with no id, assigning autogenerated" << messageId;
|
||||
}
|
||||
target.setFrom(source.from());
|
||||
target.setTo(source.to());
|
||||
@ -176,7 +178,7 @@ void Core::MessageHandler::initializeMessage(Shared::Message& target, const QXmp
|
||||
target.setForwarded(forwarded);
|
||||
|
||||
if (guessing) {
|
||||
if (target.getFromJid() == acc->getBareJid()) {
|
||||
if (target.getFromJid() == acc->getLogin() + "@" + acc->getServer()) {
|
||||
outgoing = true;
|
||||
} else {
|
||||
outgoing = false;
|
||||
@ -223,74 +225,50 @@ void Core::MessageHandler::onCarbonMessageSent(const QXmppMessage& msg)
|
||||
handleChatMessage(msg, true, true);
|
||||
}
|
||||
|
||||
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);
|
||||
if (itr != pendingStateMessages.end()) {
|
||||
std::get<0>(result) = true;
|
||||
std::get<2>(result) = itr->second;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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)) {
|
||||
std::map<QString, QString>::const_iterator itr = pendingStateMessages.find(id);
|
||||
if (itr != pendingStateMessages.end()) {
|
||||
QMap<QString, QVariant> cData = {{"state", static_cast<uint>(Shared::Message::State::delivered)}};
|
||||
RosterItem* ri = acc->rh->getRosterItem(std::get<2>(ids));
|
||||
|
||||
RosterItem* ri = acc->rh->getRosterItem(itr->second);
|
||||
if (ri != 0) {
|
||||
ri->changeMessage(std::get<1>(ids), cData);
|
||||
ri->changeMessage(id, cData);
|
||||
}
|
||||
emit acc->changeMessage(std::get<2>(ids), std::get<1>(ids), cData);
|
||||
pendingStateMessages.erase(itr);
|
||||
emit acc->changeMessage(itr->second, id, cData);
|
||||
}
|
||||
}
|
||||
|
||||
void Core::MessageHandler::sendMessage(const Shared::Message& data, bool newMessage, QString originalId)
|
||||
void Core::MessageHandler::sendMessage(const Shared::Message& data)
|
||||
{
|
||||
if (data.getOutOfBandUrl().size() == 0 && data.getAttachPath().size() > 0) {
|
||||
pendingCorrectionMessages.insert(std::make_pair(data.getId(), originalId));
|
||||
prepareUpload(data, newMessage);
|
||||
prepareUpload(data);
|
||||
} else {
|
||||
performSending(data, originalId, newMessage);
|
||||
performSending(data);
|
||||
}
|
||||
}
|
||||
|
||||
void Core::MessageHandler::performSending(Shared::Message data, const QString& originalId, bool newMessage)
|
||||
void Core::MessageHandler::performSending(Shared::Message data, bool newMessage)
|
||||
{
|
||||
QString jid = data.getPenPalJid();
|
||||
QString id = data.getId();
|
||||
qDebug() << "Sending message with id:" << id;
|
||||
if (originalId.size() > 0) {
|
||||
qDebug() << "To replace one with id:" << originalId;
|
||||
}
|
||||
QString oob = data.getOutOfBandUrl();
|
||||
RosterItem* ri = acc->rh->getRosterItem(jid);
|
||||
bool sent = false;
|
||||
if (newMessage && originalId.size() > 0) {
|
||||
newMessage = false;
|
||||
}
|
||||
QDateTime sendTime = QDateTime::currentDateTimeUtc();
|
||||
QMap<QString, QVariant> changes;
|
||||
if (acc->state == Shared::ConnectionState::connected) {
|
||||
QXmppMessage msg(createPacket(data, sendTime, originalId));
|
||||
QXmppMessage msg(acc->getFullJid(), data.getTo(), data.getBody(), data.getThread());
|
||||
|
||||
#if (QXMPP_VERSION) >= QT_VERSION_CHECK(1, 3, 0)
|
||||
msg.setOriginId(id);
|
||||
#endif
|
||||
msg.setId(id);
|
||||
msg.setType(static_cast<QXmppMessage::Type>(data.getType())); //it is safe here, my type is compatible
|
||||
msg.setOutOfBandUrl(oob);
|
||||
msg.setReceiptRequested(true);
|
||||
|
||||
sent = acc->client.sendPacket(msg);
|
||||
|
||||
if (sent) {
|
||||
data.setState(Shared::Message::State::sent);
|
||||
} else {
|
||||
@ -303,39 +281,6 @@ void Core::MessageHandler::performSending(Shared::Message data, const QString& o
|
||||
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();
|
||||
changes.insert("state", static_cast<uint>(mstate));
|
||||
if (mstate == Shared::Message::State::error) {
|
||||
@ -344,51 +289,27 @@ QMap<QString, QVariant> Core::MessageHandler::getChanges(Shared::Message& data,
|
||||
if (oob.size() > 0) {
|
||||
changes.insert("outOfBandUrl", oob);
|
||||
}
|
||||
if (!newMessage) {
|
||||
changes.insert("stamp", data.getTime());
|
||||
}
|
||||
|
||||
if (ri != 0) {
|
||||
if (newMessage) {
|
||||
data.setTime(time);
|
||||
ri->appendMessageToArchive(data);
|
||||
} else {
|
||||
ri->changeMessage(id, changes);
|
||||
}
|
||||
if (originalId.size() > 0) {
|
||||
changes.insert("body", data.getBody());
|
||||
}
|
||||
changes.insert("stamp", time);
|
||||
|
||||
//sometimes (when the image is pasted with ctrl+v)
|
||||
//I start sending message with one path, then copy it to downloads directory
|
||||
//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);
|
||||
if (sent) {
|
||||
pendingStateMessages.insert(std::make_pair(id, jid));
|
||||
} else {
|
||||
pendingStateMessages.erase(id);
|
||||
}
|
||||
}
|
||||
|
||||
return changes;
|
||||
emit acc->changeMessage(jid, id, changes);
|
||||
}
|
||||
|
||||
QXmppMessage Core::MessageHandler::createPacket(const Shared::Message& data, const QDateTime& time, const QString& originalId) const
|
||||
{
|
||||
QXmppMessage msg(acc->getFullJid(), data.getTo(), data.getBody(), data.getThread());
|
||||
QString id(data.getId());
|
||||
|
||||
if (originalId.size() > 0) {
|
||||
msg.setReplaceId(originalId);
|
||||
}
|
||||
|
||||
#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)
|
||||
void Core::MessageHandler::prepareUpload(const Shared::Message& data)
|
||||
{
|
||||
if (acc->state == Shared::ConnectionState::connected) {
|
||||
QString jid = data.getPenPalJid();
|
||||
@ -401,23 +322,16 @@ void Core::MessageHandler::prepareUpload(const Shared::Message& data, bool newMe
|
||||
QString path = data.getAttachPath();
|
||||
QString url = acc->network->getFileRemoteUrl(path);
|
||||
if (url.size() != 0) {
|
||||
sendMessageWithLocalUploadedFile(data, url, newMessage);
|
||||
sendMessageWithLocalUploadedFile(data, url);
|
||||
} else {
|
||||
pendingStateMessages.insert(std::make_pair(id, jid));
|
||||
if (newMessage) {
|
||||
if (acc->network->checkAndAddToUploading(acc->getName(), jid, id, path)) {
|
||||
ri->appendMessageToArchive(data);
|
||||
pendingStateMessages.insert(std::make_pair(id, jid));
|
||||
} else {
|
||||
QMap<QString, QVariant> changes({
|
||||
{"state", (uint)Shared::Message::State::pending}
|
||||
});
|
||||
ri->changeMessage(id, changes);
|
||||
emit acc->changeMessage(jid, id, changes);
|
||||
}
|
||||
//this checks if the file is already uploading, and if so it subscribes to it's success, so, i need to do stuff only if the network knows nothing of this file
|
||||
if (!acc->network->checkAndAddToUploading(acc->getName(), jid, id, path)) {
|
||||
if (acc->um->serviceFound()) {
|
||||
QFileInfo file(path);
|
||||
if (file.exists() && file.isReadable()) {
|
||||
ri->appendMessageToArchive(data);
|
||||
pendingStateMessages.insert(std::make_pair(id, jid));
|
||||
uploadingSlotsQueue.emplace_back(path, id);
|
||||
if (uploadingSlotsQueue.size() == 1) {
|
||||
@ -439,6 +353,7 @@ void Core::MessageHandler::prepareUpload(const Shared::Message& data, bool newMe
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Core::MessageHandler::onUploadSlotReceived(const QXmppHttpUploadSlotIq& slot)
|
||||
{
|
||||
if (uploadingSlotsQueue.size() == 0) {
|
||||
@ -505,23 +420,21 @@ void Core::MessageHandler::onLoadFileError(const std::list<Shared::MessageInfo>&
|
||||
void Core::MessageHandler::handleUploadError(const QString& jid, const QString& messageId, const QString& errorText)
|
||||
{
|
||||
emit acc->uploadFileError(jid, messageId, "Error requesting slot to upload file: " + errorText);
|
||||
pendingStateMessages.erase(messageId);
|
||||
pendingCorrectionMessages.erase(messageId);
|
||||
pendingStateMessages.erase(jid);
|
||||
requestChangeMessage(jid, messageId, {
|
||||
{"state", static_cast<uint>(Shared::Message::State::error)},
|
||||
{"errorText", errorText}
|
||||
});
|
||||
}
|
||||
|
||||
void Core::MessageHandler::onUploadFileComplete(const std::list<Shared::MessageInfo>& msgs, const QString& url, const QString& path)
|
||||
void Core::MessageHandler::onUploadFileComplete(const std::list<Shared::MessageInfo>& msgs, const QString& path)
|
||||
{
|
||||
for (const Shared::MessageInfo& info : msgs) {
|
||||
if (info.account == acc->getName()) {
|
||||
RosterItem* ri = acc->rh->getRosterItem(info.jid);
|
||||
if (ri != 0) {
|
||||
Shared::Message msg = ri->getMessage(info.messageId);
|
||||
msg.setAttachPath(path);
|
||||
sendMessageWithLocalUploadedFile(msg, url, false);
|
||||
sendMessageWithLocalUploadedFile(msg, path, false);
|
||||
} else {
|
||||
qDebug() << "A signal received about complete upload to" << acc->name << "for pal" << info.jid << "but the object for this pal wasn't found, something went terrebly wrong, skipping send";
|
||||
}
|
||||
@ -535,11 +448,11 @@ void Core::MessageHandler::sendMessageWithLocalUploadedFile(Shared::Message msg,
|
||||
if (msg.getBody().size() == 0) { //not sure why, but most messages do that
|
||||
msg.setBody(url); //they duplicate oob in body, some of them wouldn't even show an attachment if you don't do that
|
||||
}
|
||||
performSending(msg, pendingCorrectionMessages.at(msg.getId()), newMessage);
|
||||
performSending(msg, newMessage);
|
||||
//TODO removal/progress update
|
||||
}
|
||||
|
||||
static const std::set<QString> allowedToChangeKeys({
|
||||
static const std::set<QString> allowerToChangeKeys({
|
||||
"attachPath",
|
||||
"outOfBandUrl",
|
||||
"state",
|
||||
@ -553,11 +466,11 @@ void Core::MessageHandler::requestChangeMessage(const QString& jid, const QStrin
|
||||
bool allSupported = true;
|
||||
QString unsupportedString;
|
||||
for (QMap<QString, QVariant>::const_iterator itr = data.begin(); itr != data.end(); ++itr) { //I need all this madness
|
||||
if (allowedToChangeKeys.count(itr.key()) != 1) { //to not allow this method
|
||||
if (allowerToChangeKeys.count(itr.key()) != 1) { //to not allow this method
|
||||
allSupported = false; //to make a message to look like if it was edited
|
||||
unsupportedString = itr.key(); //basically I needed to control who exaclty calls this method
|
||||
break; //because the underlying tech assumes that
|
||||
} //the change is initiated by user, not by system
|
||||
break; //because the underlying tech assumes that the change is initiated by user
|
||||
} //not by system
|
||||
}
|
||||
if (allSupported) {
|
||||
cnt->changeMessage(messageId, data);
|
||||
@ -568,28 +481,3 @@ 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,7 +29,6 @@
|
||||
|
||||
#include <shared/message.h>
|
||||
#include <shared/messageinfo.h>
|
||||
#include <shared/pathcheck.h>
|
||||
|
||||
namespace Core {
|
||||
|
||||
@ -46,9 +45,8 @@ public:
|
||||
MessageHandler(Account* account);
|
||||
|
||||
public:
|
||||
void sendMessage(const Shared::Message& data, bool newMessage = true, QString originalId = "");
|
||||
void sendMessage(const Shared::Message& data);
|
||||
void initializeMessage(Shared::Message& target, const QXmppMessage& source, bool outgoing = false, bool forwarded = false, bool guessing = false) const;
|
||||
void resendMessage(const QString& jid, const QString& id);
|
||||
|
||||
public slots:
|
||||
void onMessageReceived(const QXmppMessage& message);
|
||||
@ -58,7 +56,7 @@ public slots:
|
||||
void onUploadSlotReceived(const QXmppHttpUploadSlotIq& slot);
|
||||
void onUploadSlotRequestFailed(const QXmppHttpUploadRequestIq& request);
|
||||
void onDownloadFileComplete(const std::list<Shared::MessageInfo>& msgs, const QString& path);
|
||||
void onUploadFileComplete(const std::list<Shared::MessageInfo>& msgs, const QString& url, const QString& path);
|
||||
void onUploadFileComplete(const std::list<Shared::MessageInfo>& msgs, const QString& path);
|
||||
void onLoadFileError(const std::list<Shared::MessageInfo>& msgs, const QString& path, bool up);
|
||||
void requestChangeMessage(const QString& jid, const QString& messageId, const QMap<QString, QVariant>& data);
|
||||
|
||||
@ -67,17 +65,13 @@ private:
|
||||
bool handleGroupMessage(const QXmppMessage& msg, bool outgoing = false, bool forwarded = false, bool guessing = false);
|
||||
void logMessage(const QXmppMessage& msg, const QString& reason = "Message wasn't handled: ");
|
||||
void sendMessageWithLocalUploadedFile(Shared::Message msg, const QString& url, bool newMessage = true);
|
||||
void performSending(Shared::Message data, const QString& originalId, bool newMessage = true);
|
||||
void prepareUpload(const Shared::Message& data, bool newMessage = true);
|
||||
void performSending(Shared::Message data, bool newMessage = true);
|
||||
void prepareUpload(const Shared::Message& data);
|
||||
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:
|
||||
Account* acc;
|
||||
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;
|
||||
};
|
||||
|
||||
|
@ -26,8 +26,7 @@ Core::RosterHandler::RosterHandler(Core::Account* account):
|
||||
conferences(),
|
||||
groups(),
|
||||
queuedContacts(),
|
||||
outOfRosterContacts(),
|
||||
pepSupport(false)
|
||||
outOfRosterContacts()
|
||||
{
|
||||
connect(acc->rm, &QXmppRosterManager::rosterReceived, this, &RosterHandler::onRosterReceived);
|
||||
connect(acc->rm, &QXmppRosterManager::itemAdded, this, &RosterHandler::onRosterItemAdded);
|
||||
@ -52,7 +51,8 @@ Core::RosterHandler::~RosterHandler()
|
||||
|
||||
void Core::RosterHandler::onRosterReceived()
|
||||
{
|
||||
acc->requestVCard(acc->getBareJid()); //TODO need to make sure server actually supports vCards
|
||||
acc->vm->requestClientVCard(); //TODO need to make sure server actually supports vCards
|
||||
acc->ownVCardRequestInProgress = true;
|
||||
|
||||
QStringList bj = acc->rm->getRosterBareJids();
|
||||
for (int i = 0; i < bj.size(); ++i) {
|
||||
@ -588,13 +588,4 @@ void Core::RosterHandler::handleOffline()
|
||||
pair.second->clearArchiveRequests();
|
||||
pair.second->downgradeDatabaseState();
|
||||
}
|
||||
setPepSupport(false);
|
||||
}
|
||||
|
||||
|
||||
void Core::RosterHandler::setPepSupport(bool support)
|
||||
{
|
||||
if (pepSupport != support) {
|
||||
pepSupport = support;
|
||||
}
|
||||
}
|
||||
|
@ -64,7 +64,6 @@ public:
|
||||
|
||||
void storeConferences();
|
||||
void clearConferences();
|
||||
void setPepSupport(bool support);
|
||||
|
||||
private slots:
|
||||
void onRosterReceived();
|
||||
@ -108,7 +107,6 @@ private:
|
||||
std::map<QString, std::set<QString>> groups;
|
||||
std::map<QString, QString> queuedContacts;
|
||||
std::set<QString> outOfRosterContacts;
|
||||
bool pepSupport;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -1,312 +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 "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;
|
||||
}
|
||||
}
|
@ -1,65 +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 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,11 +28,8 @@ Core::NetworkAccess::NetworkAccess(QObject* parent):
|
||||
manager(0),
|
||||
storage("fileURLStorage"),
|
||||
downloads(),
|
||||
uploads(),
|
||||
currentPath()
|
||||
uploads()
|
||||
{
|
||||
QSettings settings;
|
||||
currentPath = settings.value("downloadsPath").toString();
|
||||
}
|
||||
|
||||
Core::NetworkAccess::~NetworkAccess()
|
||||
@ -73,9 +70,6 @@ void Core::NetworkAccess::start()
|
||||
{
|
||||
if (!running) {
|
||||
manager = new QNetworkAccessManager();
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
|
||||
manager->setTransferTimeout();
|
||||
#endif
|
||||
storage.open();
|
||||
running = true;
|
||||
}
|
||||
@ -105,56 +99,31 @@ void Core::NetworkAccess::onDownloadProgress(qint64 bytesReceived, qint64 bytesT
|
||||
qDebug() << "an error downloading" << url << ": the request had some progress but seems like no one is waiting for it, skipping";
|
||||
} else {
|
||||
Transfer* dwn = itr->second;
|
||||
if (dwn->success) {
|
||||
qreal received = bytesReceived;
|
||||
qreal total = bytesTotal;
|
||||
qreal progress = received/total;
|
||||
dwn->progress = progress;
|
||||
emit loadFileProgress(dwn->messages, progress, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Core::NetworkAccess::onDownloadError(QNetworkReply::NetworkError code)
|
||||
{
|
||||
qDebug() << "DEBUG: DOWNLOAD ERROR";
|
||||
QNetworkReply* rpl = static_cast<QNetworkReply*>(sender());
|
||||
qDebug() << rpl->errorString();
|
||||
QString url = rpl->url().toString();
|
||||
std::map<QString, Transfer*>::const_iterator itr = downloads.find(url);
|
||||
if (itr == downloads.end()) {
|
||||
qDebug() << "an error downloading" << url << ": the request is reporting an error but seems like no one is waiting for it, skipping";
|
||||
} else {
|
||||
QString errorText = getErrorText(code);
|
||||
//if (errorText.size() > 0) {
|
||||
if (errorText.size() > 0) {
|
||||
itr->second->success = false;
|
||||
Transfer* dwn = itr->second;
|
||||
emit loadFileError(dwn->messages, errorText, false);
|
||||
//}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Core::NetworkAccess::onDownloadSSLError(const QList<QSslError>& errors)
|
||||
{
|
||||
qDebug() << "DEBUG: DOWNLOAD SSL ERRORS";
|
||||
for (const QSslError& err : errors) {
|
||||
qDebug() << err.errorString();
|
||||
}
|
||||
QNetworkReply* rpl = static_cast<QNetworkReply*>(sender());
|
||||
QString url = rpl->url().toString();
|
||||
std::map<QString, Transfer*>::const_iterator itr = downloads.find(url);
|
||||
if (itr == downloads.end()) {
|
||||
qDebug() << "an SSL error downloading" << url << ": the request is reporting an error but seems like no one is waiting for it, skipping";
|
||||
} else {
|
||||
//if (errorText.size() > 0) {
|
||||
itr->second->success = false;
|
||||
Transfer* dwn = itr->second;
|
||||
emit loadFileError(dwn->messages, "SSL errors occured", false);
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
QString Core::NetworkAccess::getErrorText(QNetworkReply::NetworkError code)
|
||||
{
|
||||
QString errorText("");
|
||||
@ -177,11 +146,7 @@ QString Core::NetworkAccess::getErrorText(QNetworkReply::NetworkError code)
|
||||
errorText = "Connection was closed because it timed out";
|
||||
break;
|
||||
case QNetworkReply::OperationCanceledError:
|
||||
//this means I closed it myself by abort() or close()
|
||||
//I don't call them directory, but this is the error code
|
||||
//Qt returns when it can not resume donwload after the network failure
|
||||
//or when the download is canceled by the timout;
|
||||
errorText = "Connection lost";
|
||||
//this means I closed it myself by abort() or close(), don't think I need to notify here
|
||||
break;
|
||||
case QNetworkReply::SslHandshakeFailedError:
|
||||
errorText = "Security error"; //TODO need to handle sslErrors signal to get a better description here
|
||||
@ -282,7 +247,6 @@ QString Core::NetworkAccess::getErrorText(QNetworkReply::NetworkError code)
|
||||
|
||||
void Core::NetworkAccess::onDownloadFinished()
|
||||
{
|
||||
qDebug() << "DEBUG: DOWNLOAD FINISHED";
|
||||
QNetworkReply* rpl = static_cast<QNetworkReply*>(sender());
|
||||
QString url = rpl->url().toString();
|
||||
std::map<QString, Transfer*>::const_iterator itr = downloads.find(url);
|
||||
@ -292,20 +256,17 @@ void Core::NetworkAccess::onDownloadFinished()
|
||||
Transfer* dwn = itr->second;
|
||||
if (dwn->success) {
|
||||
qDebug() << "download success for" << url;
|
||||
QString err;
|
||||
QStringList hops = url.split("/");
|
||||
QString fileName = hops.back();
|
||||
QString jid;
|
||||
if (dwn->messages.size() > 0) {
|
||||
jid = dwn->messages.front().jid;
|
||||
} else {
|
||||
qDebug() << "An attempt to save the file but it doesn't seem to belong to any message, download is definately going to be broken";
|
||||
}
|
||||
QString path = prepareDirectory(jid);
|
||||
if (path.size() > 0) {
|
||||
path = checkFileName(fileName, path);
|
||||
|
||||
QFile file(Shared::resolvePath(path));
|
||||
QFile file(path);
|
||||
if (file.open(QIODevice::WriteOnly)) {
|
||||
file.write(dwn->reply->readAll());
|
||||
file.close();
|
||||
@ -313,16 +274,15 @@ void Core::NetworkAccess::onDownloadFinished()
|
||||
qDebug() << "file" << path << "was successfully downloaded";
|
||||
} else {
|
||||
qDebug() << "couldn't save file" << path;
|
||||
err = "Error opening file to write:" + file.errorString();
|
||||
path = QString();
|
||||
}
|
||||
} else {
|
||||
err = "Couldn't prepare a directory for file";
|
||||
}
|
||||
|
||||
if (path.size() > 0) {
|
||||
emit downloadFileComplete(dwn->messages, path);
|
||||
} else {
|
||||
emit loadFileError(dwn->messages, "Error saving file " + url + "; " + err, false);
|
||||
//TODO do I need to handle the failure here or it's already being handled in error?
|
||||
//emit loadFileError(dwn->messages, path, false);
|
||||
}
|
||||
}
|
||||
|
||||
@ -338,7 +298,6 @@ void Core::NetworkAccess::startDownload(const std::list<Shared::MessageInfo>& ms
|
||||
QNetworkRequest req(url);
|
||||
dwn->reply = manager->get(req);
|
||||
connect(dwn->reply, &QNetworkReply::downloadProgress, this, &NetworkAccess::onDownloadProgress);
|
||||
connect(dwn->reply, &QNetworkReply::sslErrors, this, &NetworkAccess::onDownloadSSLError);
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
|
||||
connect(dwn->reply, qOverload<QNetworkReply::NetworkError>(&QNetworkReply::errorOccurred), this, &NetworkAccess::onDownloadError);
|
||||
#else
|
||||
@ -358,11 +317,11 @@ void Core::NetworkAccess::onUploadError(QNetworkReply::NetworkError code)
|
||||
qDebug() << "an error uploading" << url << ": the request is reporting an error but there is no record of it being uploading, ignoring";
|
||||
} else {
|
||||
QString errorText = getErrorText(code);
|
||||
//if (errorText.size() > 0) {
|
||||
if (errorText.size() > 0) {
|
||||
itr->second->success = false;
|
||||
Transfer* upl = itr->second;
|
||||
emit loadFileError(upl->messages, errorText, true);
|
||||
//}
|
||||
}
|
||||
|
||||
//TODO deletion?
|
||||
}
|
||||
@ -380,33 +339,8 @@ void Core::NetworkAccess::onUploadFinished()
|
||||
if (upl->success) {
|
||||
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);
|
||||
emit uploadFileComplete(upl->messages, upl->url, upl->path);
|
||||
emit uploadFileComplete(upl->messages, upl->url);
|
||||
}
|
||||
|
||||
upl->reply->deleteLater();
|
||||
@ -426,24 +360,22 @@ void Core::NetworkAccess::onUploadProgress(qint64 bytesReceived, qint64 bytesTot
|
||||
qDebug() << "an error downloading" << url << ": the request had some progress but seems like no one is waiting for it, skipping";
|
||||
} else {
|
||||
Transfer* upl = itr->second;
|
||||
if (upl->success) {
|
||||
qreal received = bytesReceived;
|
||||
qreal total = bytesTotal;
|
||||
qreal progress = received/total;
|
||||
upl->progress = progress;
|
||||
emit loadFileProgress(upl->messages, progress, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QString Core::NetworkAccess::getFileRemoteUrl(const QString& path)
|
||||
{
|
||||
QString p = Shared::squawkifyPath(path);
|
||||
QString p;
|
||||
|
||||
try {
|
||||
p = storage.getUrl(p);
|
||||
p = storage.getUrl(path);
|
||||
} catch (const Archive::NotFound& err) {
|
||||
p = "";
|
||||
|
||||
} catch (...) {
|
||||
throw;
|
||||
}
|
||||
@ -518,13 +450,11 @@ bool Core::NetworkAccess::checkAndAddToUploading(const QString& acc, const QStri
|
||||
|
||||
QString Core::NetworkAccess::prepareDirectory(const QString& jid)
|
||||
{
|
||||
QString path = currentPath;
|
||||
QString addition;
|
||||
QString path = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation);
|
||||
path += "/" + QApplication::applicationName();
|
||||
if (jid.size() > 0) {
|
||||
addition = jid;
|
||||
path += QDir::separator() + jid;
|
||||
path += "/" + jid;
|
||||
}
|
||||
|
||||
QDir location(path);
|
||||
|
||||
if (!location.exists()) {
|
||||
@ -532,10 +462,10 @@ QString Core::NetworkAccess::prepareDirectory(const QString& jid)
|
||||
if (!res) {
|
||||
return "";
|
||||
} else {
|
||||
return "squawk://" + addition;
|
||||
return path;
|
||||
}
|
||||
}
|
||||
return "squawk://" + addition;
|
||||
return path;
|
||||
}
|
||||
|
||||
QString Core::NetworkAccess::checkFileName(const QString& name, const QString& path)
|
||||
@ -549,17 +479,14 @@ QString Core::NetworkAccess::checkFileName(const QString& name, const QString& p
|
||||
suffix += "." + (*sItr);
|
||||
}
|
||||
QString postfix("");
|
||||
QString resolvedPath = Shared::resolvePath(path);
|
||||
QString count("");
|
||||
QFileInfo proposedName(resolvedPath + QDir::separator() + realName + count + suffix);
|
||||
|
||||
QFileInfo proposedName(path + "/" + realName + suffix);
|
||||
int counter = 0;
|
||||
while (proposedName.exists()) {
|
||||
count = QString("(") + std::to_string(++counter).c_str() + ")";
|
||||
proposedName = QFileInfo(resolvedPath + QDir::separator() + realName + count + suffix);
|
||||
QString count = QString("(") + std::to_string(++counter).c_str() + ")";
|
||||
proposedName = QFileInfo(path + "/" + realName + count + suffix);
|
||||
}
|
||||
|
||||
return path + QDir::separator() + realName + count + suffix;
|
||||
return proposedName.absoluteFilePath();
|
||||
}
|
||||
|
||||
QString Core::NetworkAccess::addMessageAndCheckForPath(const QString& url, const QString& account, const QString& jid, const QString& id)
|
||||
@ -571,19 +498,3 @@ std::list<Shared::MessageInfo> Core::NetworkAccess::reportPathInvalid(const QStr
|
||||
{
|
||||
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,12 +26,10 @@
|
||||
#include <QFileInfo>
|
||||
#include <QFile>
|
||||
#include <QStandardPaths>
|
||||
#include <QSettings>
|
||||
|
||||
#include <set>
|
||||
|
||||
#include "storage/urlstorage.h"
|
||||
#include "shared/pathcheck.h"
|
||||
#include "urlstorage.h"
|
||||
|
||||
namespace Core {
|
||||
|
||||
@ -60,14 +58,13 @@ public:
|
||||
signals:
|
||||
void loadFileProgress(const std::list<Shared::MessageInfo>& msgs, qreal value, bool up);
|
||||
void loadFileError(const std::list<Shared::MessageInfo>& msgs, const QString& text, bool up);
|
||||
void uploadFileComplete(const std::list<Shared::MessageInfo>& msgs, const QString& url, const QString& path);
|
||||
void uploadFileComplete(const std::list<Shared::MessageInfo>& msgs, const QString& url);
|
||||
void downloadFileComplete(const std::list<Shared::MessageInfo>& msgs, const QString& path);
|
||||
|
||||
public slots:
|
||||
void downladFile(const QString& url);
|
||||
void registerFile(const QString& url, const QString& account, const QString& jid, const QString& id);
|
||||
void registerFile(const QString& url, const QString& path, const QString& account, const QString& jid, const QString& id);
|
||||
void moveFilesDirectory(const QString& newPath);
|
||||
|
||||
private:
|
||||
void startDownload(const std::list<Shared::MessageInfo>& msgs, const QString& url);
|
||||
@ -78,7 +75,6 @@ private:
|
||||
private slots:
|
||||
void onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal);
|
||||
void onDownloadError(QNetworkReply::NetworkError code);
|
||||
void onDownloadSSLError(const QList<QSslError> &errors);
|
||||
void onDownloadFinished();
|
||||
void onUploadProgress(qint64 bytesReceived, qint64 bytesTotal);
|
||||
void onUploadError(QNetworkReply::NetworkError code);
|
||||
@ -90,7 +86,6 @@ private:
|
||||
UrlStorage storage;
|
||||
std::map<QString, Transfer*> downloads;
|
||||
std::map<QString, Transfer*> uploads;
|
||||
QString currentPath;
|
||||
|
||||
struct Transfer {
|
||||
std::list<Shared::MessageInfo> messages;
|
||||
|
@ -1,9 +1,37 @@
|
||||
cmake_minimum_required(VERSION 3.3)
|
||||
project(pse)
|
||||
|
||||
if (WITH_KWALLET)
|
||||
target_sources(squawk PRIVATE
|
||||
set(CMAKE_AUTOMOC ON)
|
||||
|
||||
find_package(Qt5Core CONFIG REQUIRED)
|
||||
find_package(Qt5Gui CONFIG REQUIRED)
|
||||
|
||||
get_target_property(KWALLET_INTERFACE_INCLUDE_DIRECTORIES KF5::Wallet INTERFACE_INCLUDE_DIRECTORIES)
|
||||
get_target_property(Qt5GUI_INTERFACE_INCLUDE_DIRECTORIES Qt5::Gui INTERFACE_INCLUDE_DIRECTORIES)
|
||||
|
||||
set(kwalletPSE_SRC
|
||||
kwallet.cpp
|
||||
kwallet.h
|
||||
)
|
||||
|
||||
add_subdirectory(wrappers)
|
||||
target_include_directories(squawk PRIVATE $<TARGET_PROPERTY:KF5::Wallet,INTERFACE_INCLUDE_DIRECTORIES>)
|
||||
endif ()
|
||||
add_library(kwalletPSE STATIC ${kwalletPSE_SRC})
|
||||
|
||||
target_include_directories(kwalletPSE PUBLIC ${KWALLET_INTERFACE_INCLUDE_DIRECTORIES})
|
||||
target_include_directories(kwalletPSE PUBLIC ${Qt5GUI_INTERFACE_INCLUDE_DIRECTORIES})
|
||||
|
||||
target_link_libraries(kwalletPSE Qt5::Core)
|
||||
|
||||
set(kwalletW_SRC
|
||||
wrappers/kwallet.cpp
|
||||
)
|
||||
|
||||
add_library(kwalletWrapper SHARED ${kwalletW_SRC})
|
||||
|
||||
target_include_directories(kwalletWrapper PUBLIC ${KWALLET_INTERFACE_INCLUDE_DIRECTORIES})
|
||||
target_include_directories(kwalletWrapper PUBLIC ${Qt5GUI_INTERFACE_INCLUDE_DIRECTORIES})
|
||||
|
||||
target_link_libraries(kwalletWrapper KF5::Wallet)
|
||||
target_link_libraries(kwalletWrapper Qt5::Core)
|
||||
|
||||
install(TARGETS kwalletWrapper DESTINATION ${CMAKE_INSTALL_LIBDIR})
|
||||
endif()
|
||||
|
@ -1,4 +0,0 @@
|
||||
add_library(kwalletWrapper SHARED kwallet.cpp)
|
||||
target_link_libraries(kwalletWrapper PRIVATE KF5::Wallet)
|
||||
|
||||
install(TARGETS kwalletWrapper LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})
|
@ -1,21 +1,3 @@
|
||||
/*
|
||||
* 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>
|
||||
|
||||
extern "C" KWallet::Wallet* openWallet(const QString &name, WId w, KWallet::Wallet::OpenType ot = KWallet::Wallet::Synchronous) {
|
||||
|
@ -124,7 +124,6 @@ void Core::RosterItem::nextRequest()
|
||||
if (requestedCount != -1) {
|
||||
bool last = false;
|
||||
if (archiveState == beginning || archiveState == complete) {
|
||||
try {
|
||||
QString firstId = archive->oldestId();
|
||||
if (responseCache.size() == 0) {
|
||||
if (requestedBefore == firstId) {
|
||||
@ -135,9 +134,6 @@ void Core::RosterItem::nextRequest()
|
||||
last = true;
|
||||
}
|
||||
}
|
||||
} catch (const Archive::Empty& e) {
|
||||
last = true;
|
||||
}
|
||||
} else if (archiveState == empty && responseCache.size() == 0) {
|
||||
last = true;
|
||||
}
|
||||
@ -175,12 +171,8 @@ void Core::RosterItem::performRequest(int count, const QString& before)
|
||||
requestCache.emplace_back(requestedCount, before);
|
||||
requestedCount = -1;
|
||||
}
|
||||
try {
|
||||
Shared::Message msg = archive->newest();
|
||||
emit needHistory("", getId(msg), msg.getTime());
|
||||
} catch (const Archive::Empty& e) { //this can happen when the only message in archive is not server stored (error, for example)
|
||||
emit needHistory(before, "");
|
||||
}
|
||||
}
|
||||
break;
|
||||
case end:
|
||||
|
@ -34,8 +34,7 @@
|
||||
#include "shared/enums.h"
|
||||
#include "shared/message.h"
|
||||
#include "shared/vcard.h"
|
||||
#include "storage/archive.h"
|
||||
#include "adapterfunctions.h"
|
||||
#include "archive.h"
|
||||
|
||||
namespace Core {
|
||||
|
||||
|
@ -1,42 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
303
core/squawk.cpp
303
core/squawk.cpp
@ -26,9 +26,8 @@ Core::Squawk::Squawk(QObject* parent):
|
||||
QObject(parent),
|
||||
accounts(),
|
||||
amap(),
|
||||
state(Shared::Availability::offline),
|
||||
network(),
|
||||
isInitialized(false)
|
||||
waitingForAccounts(0)
|
||||
#ifdef WITH_KWALLET
|
||||
,kwallet()
|
||||
#endif
|
||||
@ -42,7 +41,7 @@ Core::Squawk::Squawk(QObject* parent):
|
||||
if (kwallet.supportState() == PSE::KWallet::success) {
|
||||
connect(&kwallet, &PSE::KWallet::opened, this, &Squawk::onWalletOpened);
|
||||
connect(&kwallet, &PSE::KWallet::rejectPassword, this, &Squawk::onWalletRejectPassword);
|
||||
connect(&kwallet, &PSE::KWallet::responsePassword, this, &Squawk::responsePassword);
|
||||
connect(&kwallet, &PSE::KWallet::responsePassword, this, &Squawk::onWalletResponsePassword);
|
||||
|
||||
Shared::Global::setSupported("KWallet", true);
|
||||
}
|
||||
@ -67,8 +66,6 @@ void Core::Squawk::stop()
|
||||
{
|
||||
qDebug("Stopping squawk core..");
|
||||
network.stop();
|
||||
|
||||
if (isInitialized) {
|
||||
QSettings settings;
|
||||
settings.beginGroup("core");
|
||||
settings.beginWriteArray("accounts");
|
||||
@ -97,13 +94,11 @@ void Core::Squawk::stop()
|
||||
settings.setValue("password", password);
|
||||
settings.setValue("resource", acc->getResource());
|
||||
settings.setValue("passwordType", static_cast<int>(ap));
|
||||
settings.setValue("active", acc->getActive());
|
||||
}
|
||||
settings.endArray();
|
||||
settings.endGroup();
|
||||
|
||||
settings.sync();
|
||||
}
|
||||
|
||||
emit quit();
|
||||
}
|
||||
@ -113,7 +108,6 @@ void Core::Squawk::start()
|
||||
qDebug("Starting squawk core..");
|
||||
|
||||
readSettings();
|
||||
isInitialized = true;
|
||||
network.start();
|
||||
}
|
||||
|
||||
@ -124,10 +118,8 @@ void Core::Squawk::newAccountRequest(const QMap<QString, QVariant>& map)
|
||||
QString server = map.value("server").toString();
|
||||
QString password = map.value("password").toString();
|
||||
QString resource = map.value("resource").toString();
|
||||
int passwordType = map.value("passwordType").toInt();
|
||||
bool active = map.value("active").toBool();
|
||||
|
||||
addAccount(login, server, password, name, resource, active, Shared::Global::fromInt<Shared::AccountPassword>(passwordType));
|
||||
addAccount(login, server, password, name, resource, Shared::AccountPassword::plain);
|
||||
}
|
||||
|
||||
void Core::Squawk::addAccount(
|
||||
@ -136,14 +128,12 @@ void Core::Squawk::addAccount(
|
||||
const QString& password,
|
||||
const QString& name,
|
||||
const QString& resource,
|
||||
bool active,
|
||||
Shared::AccountPassword passwordType)
|
||||
Shared::AccountPassword passwordType
|
||||
)
|
||||
{
|
||||
if (amap.count(name) > 0) {
|
||||
qDebug() << "An attempt to add account" << name << "but an account with such name already exist, ignoring";
|
||||
return;
|
||||
}
|
||||
Account* acc = new Account(login, server, password, name, active, &network);
|
||||
QSettings settings;
|
||||
|
||||
Account* acc = new Account(login, server, password, name, &network);
|
||||
acc->setResource(resource);
|
||||
acc->setPasswordType(passwordType);
|
||||
accounts.push_back(acc);
|
||||
@ -152,8 +142,6 @@ void Core::Squawk::addAccount(
|
||||
connect(acc, &Account::connectionStateChanged, this, &Squawk::onAccountConnectionStateChanged);
|
||||
connect(acc, &Account::changed, this, &Squawk::onAccountChanged);
|
||||
connect(acc, &Account::error, this, &Squawk::onAccountError);
|
||||
connect(acc, &Account::needPassword, this, &Squawk::onAccountNeedPassword);
|
||||
|
||||
connect(acc, &Account::availabilityChanged, this, &Squawk::onAccountAvailabilityChanged);
|
||||
connect(acc, &Account::addContact, this, &Squawk::onAccountAddContact);
|
||||
connect(acc, &Account::addGroup, this, &Squawk::onAccountAddGroup);
|
||||
@ -191,44 +179,20 @@ void Core::Squawk::addAccount(
|
||||
{"offline", QVariant::fromValue(Shared::Availability::offline)},
|
||||
{"error", ""},
|
||||
{"avatarPath", acc->getAvatarPath()},
|
||||
{"passwordType", QVariant::fromValue(passwordType)},
|
||||
{"active", active}
|
||||
{"passwordType", QVariant::fromValue(passwordType)}
|
||||
};
|
||||
|
||||
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)
|
||||
{
|
||||
if (state != p_state) {
|
||||
for (std::deque<Account*>::iterator itr = accounts.begin(), end = accounts.end(); itr != end; ++itr) {
|
||||
Account* acc = *itr;
|
||||
acc->setAvailability(p_state);
|
||||
if (state == Shared::Availability::offline && acc->getActive()) {
|
||||
acc->connect();
|
||||
}
|
||||
}
|
||||
state = p_state;
|
||||
}
|
||||
|
||||
emit stateChanged(p_state);
|
||||
for (std::deque<Account*>::iterator itr = accounts.begin(), end = accounts.end(); itr != end; ++itr) {
|
||||
(*itr)->setAvailability(state);
|
||||
}
|
||||
}
|
||||
|
||||
@ -239,10 +203,7 @@ void Core::Squawk::connectAccount(const QString& account)
|
||||
qDebug("An attempt to connect non existing account, skipping");
|
||||
return;
|
||||
}
|
||||
itr->second->setActive(true);
|
||||
if (state != Shared::Availability::offline) {
|
||||
itr->second->connect();
|
||||
}
|
||||
}
|
||||
|
||||
void Core::Squawk::disconnectAccount(const QString& account)
|
||||
@ -253,17 +214,13 @@ void Core::Squawk::disconnectAccount(const QString& account)
|
||||
return;
|
||||
}
|
||||
|
||||
itr->second->setActive(false);
|
||||
itr->second->disconnect();
|
||||
}
|
||||
|
||||
void Core::Squawk::onAccountConnectionStateChanged(Shared::ConnectionState p_state)
|
||||
{
|
||||
Account* acc = static_cast<Account*>(sender());
|
||||
emit changeAccount(acc->getName(), {
|
||||
{"state", QVariant::fromValue(p_state)},
|
||||
{"error", ""}
|
||||
});
|
||||
emit changeAccount(acc->getName(), {{"state", QVariant::fromValue(p_state)}});
|
||||
|
||||
#ifdef WITH_KWALLET
|
||||
if (p_state == Shared::ConnectionState::connected) {
|
||||
@ -272,6 +229,33 @@ void Core::Squawk::onAccountConnectionStateChanged(Shared::ConnectionState p_sta
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
Accounts::const_iterator itr = accounts.begin();
|
||||
bool es = true;
|
||||
bool ea = true;
|
||||
Shared::ConnectionState cs = (*itr)->getState();
|
||||
Shared::Availability av = (*itr)->getAvailability();
|
||||
itr++;
|
||||
for (Accounts::const_iterator end = accounts.end(); itr != end; itr++) {
|
||||
Account* item = *itr;
|
||||
if (item->getState() != cs) {
|
||||
es = false;
|
||||
}
|
||||
if (item->getAvailability() != av) {
|
||||
ea = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (es) {
|
||||
if (cs == Shared::ConnectionState::disconnected) {
|
||||
state = Shared::Availability::offline;
|
||||
emit stateChanged(state);
|
||||
} else if (ea) {
|
||||
state = av;
|
||||
emit stateChanged(state);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void Core::Squawk::onAccountAddContact(const QString& jid, const QString& group, const QMap<QString, QVariant>& data)
|
||||
@ -344,35 +328,13 @@ void Core::Squawk::sendMessage(const QString& account, const Shared::Message& da
|
||||
{
|
||||
AccountsMap::const_iterator itr = amap.find(account);
|
||||
if (itr == amap.end()) {
|
||||
qDebug() << "An attempt to send a message with non existing account" << account << ", skipping";
|
||||
qDebug("An attempt to send a message with non existing account, skipping");
|
||||
return;
|
||||
}
|
||||
|
||||
itr->second->sendMessage(data);
|
||||
}
|
||||
|
||||
void Core::Squawk::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)
|
||||
{
|
||||
AccountsMap::const_iterator itr = amap.find(account);
|
||||
@ -401,7 +363,6 @@ void Core::Squawk::modifyAccountRequest(const QString& name, const QMap<QString,
|
||||
Shared::ConnectionState st = acc->getState();
|
||||
QMap<QString, QVariant>::const_iterator mItr;
|
||||
bool needToReconnect = false;
|
||||
bool wentReconnecting = false;
|
||||
|
||||
mItr = map.find("login");
|
||||
if (mItr != map.end()) {
|
||||
@ -427,16 +388,8 @@ 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) {
|
||||
acc->reconnect();
|
||||
wentReconnecting = true;
|
||||
}
|
||||
} else {
|
||||
acc->setActive(mItr->toBool());
|
||||
activeChanged = true;
|
||||
}
|
||||
|
||||
mItr = map.find("login");
|
||||
@ -473,14 +426,6 @@ void Core::Squawk::modifyAccountRequest(const QString& name, const QMap<QString,
|
||||
}
|
||||
#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);
|
||||
}
|
||||
|
||||
@ -488,10 +433,6 @@ void Core::Squawk::onAccountError(const QString& text)
|
||||
{
|
||||
Account* acc = static_cast<Account*>(sender());
|
||||
emit changeAccount(acc->getName(), {{"error", text}});
|
||||
|
||||
if (acc->getLastError() == Account::Error::authentication) {
|
||||
emit requestPassword(acc->getName(), true);
|
||||
}
|
||||
}
|
||||
|
||||
void Core::Squawk::removeAccountRequest(const QString& name)
|
||||
@ -706,70 +647,6 @@ void Core::Squawk::uploadVCard(const QString& account, const Shared::VCard& 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)
|
||||
{
|
||||
AccountsMap::const_iterator itr = amap.find(account);
|
||||
@ -777,12 +654,96 @@ void Core::Squawk::responsePassword(const QString& account, const QString& passw
|
||||
qDebug() << "An attempt to set password to non existing account" << account << ", skipping";
|
||||
return;
|
||||
}
|
||||
Account* acc = itr->second;
|
||||
acc->setPassword(password);
|
||||
itr->second->setPassword(password);
|
||||
emit changeAccount(account, {{"password", password}});
|
||||
if (state != Shared::Availability::offline && acc->getActive()) {
|
||||
acc->connect();
|
||||
accountReady();
|
||||
}
|
||||
|
||||
void Core::Squawk::readSettings()
|
||||
{
|
||||
QSettings settings;
|
||||
settings.beginGroup("core");
|
||||
int size = settings.beginReadArray("accounts");
|
||||
waitingForAccounts = size;
|
||||
for (int i = 0; i < size; ++i) {
|
||||
settings.setArrayIndex(i);
|
||||
parseAccount(
|
||||
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)
|
||||
@ -807,9 +768,3 @@ 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 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& url, const QString& path);
|
||||
void fileUploadComplete(const std::list<Shared::MessageInfo> msgs, const QString& path);
|
||||
|
||||
void responseVCard(const QString& jid, const Shared::VCard& card);
|
||||
void changeMessage(const QString& account, const QString& jid, const QString& id, const QMap<QString, QVariant>& data);
|
||||
void requestPassword(const QString& account, bool authernticationError);
|
||||
void requestPassword(const QString& account);
|
||||
|
||||
public slots:
|
||||
void start();
|
||||
@ -101,8 +101,6 @@ public slots:
|
||||
void changeState(Shared::Availability state);
|
||||
|
||||
void sendMessage(const QString& account, const Shared::Message& data);
|
||||
void replaceMessage(const QString& account, const QString& originalId, const Shared::Message& data);
|
||||
void resendMessage(const QString& account, const QString& jid, const QString& id);
|
||||
void requestArchive(const QString& account, const QString& jid, int count, const QString& before);
|
||||
|
||||
void subscribeContact(const QString& account, const QString& jid, const QString& reason);
|
||||
@ -124,7 +122,6 @@ public slots:
|
||||
void uploadVCard(const QString& account, const Shared::VCard& card);
|
||||
void responsePassword(const QString& account, const QString& password);
|
||||
void onLocalPathInvalid(const QString& path);
|
||||
void changeDownloadsPath(const QString& path);
|
||||
|
||||
private:
|
||||
typedef std::deque<Account*> Accounts;
|
||||
@ -134,7 +131,7 @@ private:
|
||||
AccountsMap amap;
|
||||
Shared::Availability state;
|
||||
NetworkAccess network;
|
||||
bool isInitialized;
|
||||
uint8_t waitingForAccounts;
|
||||
|
||||
#ifdef WITH_KWALLET
|
||||
PSE::KWallet kwallet;
|
||||
@ -147,7 +144,6 @@ private slots:
|
||||
const QString& password,
|
||||
const QString& name,
|
||||
const QString& resource,
|
||||
bool active,
|
||||
Shared::AccountPassword passwordType
|
||||
);
|
||||
|
||||
@ -172,22 +168,22 @@ private slots:
|
||||
void onAccountChangeRoomPresence(const QString& jid, const QString& nick, const QMap<QString, QVariant>& data);
|
||||
void onAccountRemoveRoomPresence(const QString& jid, const QString& nick);
|
||||
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 onWalletOpened(bool success);
|
||||
void onWalletResponsePassword(const QString& login, const QString& password);
|
||||
void onWalletRejectPassword(const QString& login);
|
||||
|
||||
private:
|
||||
void readSettings();
|
||||
void accountReady();
|
||||
void parseAccount(
|
||||
const QString& login,
|
||||
const QString& server,
|
||||
const QString& password,
|
||||
const QString& name,
|
||||
const QString& resource,
|
||||
bool active,
|
||||
Shared::AccountPassword passwordType
|
||||
);
|
||||
|
||||
|
@ -1,8 +0,0 @@
|
||||
target_sources(squawk PRIVATE
|
||||
archive.cpp
|
||||
archive.h
|
||||
storage.cpp
|
||||
storage.h
|
||||
urlstorage.cpp
|
||||
urlstorage.h
|
||||
)
|
@ -28,6 +28,5 @@ Utils::Exception::~Exception()
|
||||
|
||||
const char* Utils::Exception::what() const noexcept( true )
|
||||
{
|
||||
std::string* msg = new std::string(getMessage());
|
||||
return msg->c_str();
|
||||
return getMessage().c_str();
|
||||
}
|
12
external/simpleCrypt/CMakeLists.txt
vendored
12
external/simpleCrypt/CMakeLists.txt
vendored
@ -1,10 +1,16 @@
|
||||
cmake_minimum_required(VERSION 3.0)
|
||||
project(simplecrypt LANGUAGES CXX)
|
||||
project(simplecrypt)
|
||||
|
||||
set(CMAKE_AUTOMOC ON)
|
||||
|
||||
find_package(Qt5 COMPONENTS Core REQUIRED)
|
||||
find_package(Qt5Core CONFIG REQUIRED)
|
||||
|
||||
add_library(simpleCrypt STATIC simplecrypt.cpp simplecrypt.h)
|
||||
set(simplecrypt_SRC
|
||||
simplecrypt.cpp
|
||||
)
|
||||
|
||||
# Tell CMake to create the helloworld executable
|
||||
add_library(simpleCrypt STATIC ${simplecrypt_SRC})
|
||||
|
||||
# Use the Widgets module from Qt 5.
|
||||
target_link_libraries(simpleCrypt Qt5::Core)
|
||||
|
162
main.cpp
Normal file
162
main.cpp
Normal file
@ -0,0 +1,162 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
|
@ -1,7 +0,0 @@
|
||||
target_sources(squawk PRIVATE
|
||||
main.cpp
|
||||
application.cpp
|
||||
application.h
|
||||
dialogqueue.cpp
|
||||
dialogqueue.h
|
||||
)
|
@ -1,566 +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 "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);}
|
@ -1,118 +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 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
|
@ -1,187 +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 "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) {}
|
@ -1,101 +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 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
140
main/main.cpp
@ -1,140 +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 "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,20 +1,17 @@
|
||||
# Maintainer: Yury Gubich <blue@macaw.me>
|
||||
pkgname=squawk
|
||||
pkgver=0.2.2
|
||||
pkgver=0.1.5
|
||||
pkgrel=1
|
||||
pkgdesc="An XMPP desktop messenger, written on pure c++ (qt)"
|
||||
arch=('i686' 'x86_64')
|
||||
url="https://git.macaw.me/blue/squawk"
|
||||
license=('GPL3')
|
||||
depends=('hicolor-icon-theme' 'desktop-file-utils' 'lmdb' 'qxmpp>=1.1.0')
|
||||
makedepends=('cmake>=3.3' 'imagemagick' 'qt5-tools' 'boost')
|
||||
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)')
|
||||
makedepends=('cmake>=3.3' 'imagemagick' 'qt5-tools')
|
||||
optdepends=('kwallet: secure password storage (requires rebuild)')
|
||||
|
||||
source=("$pkgname-$pkgver.tar.gz")
|
||||
sha256sums=('e4fa2174a3ba95159cc3b0bac3f00550c9e0ce971c55334e2662696a4543fc7e')
|
||||
sha256sums=('e1a4c88be9f0481d2aa21078faf42fd0e9d66b490b6d8af82827d441cb58df25')
|
||||
build() {
|
||||
cd "$srcdir/squawk"
|
||||
cmake . -D CMAKE_INSTALL_PREFIX=/usr -D CMAKE_BUILD_TYPE=Release
|
||||
|
@ -1,3 +0,0 @@
|
||||
configure_file(squawk.desktop squawk.desktop COPYONLY)
|
||||
|
||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk.desktop DESTINATION ${CMAKE_INSTALL_DATADIR}/applications)
|
@ -5,10 +5,8 @@ Version=1.0
|
||||
Name=Squawk
|
||||
GenericName=Instant Messenger
|
||||
GenericName[ru]=Мгновенные сообщения
|
||||
GenericName[pt_BR]=Mensageiro instantâneo
|
||||
Comment=XMPP (Jabber) instant messenger client
|
||||
Comment[ru]=XMPP (Jabber) клиент обмена мгновенными сообщениями
|
||||
Comment[pt_BR]=Cliente de mensagem instantânea XMPP (Jabber)
|
||||
Exec=squawk %u
|
||||
Icon=squawk
|
||||
StartupNotify=true
|
||||
|
@ -1,14 +1,26 @@
|
||||
cmake_minimum_required(VERSION 3.3)
|
||||
project(plugins)
|
||||
|
||||
if (WITH_KIO)
|
||||
add_library(openFileManagerWindowJob SHARED openfilemanagerwindowjob.cpp)
|
||||
target_link_libraries(openFileManagerWindowJob PRIVATE KF5::KIOWidgets)
|
||||
set(CMAKE_AUTOMOC ON)
|
||||
|
||||
install(TARGETS openFileManagerWindowJob LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})
|
||||
endif ()
|
||||
find_package(Qt5Core CONFIG REQUIRED)
|
||||
|
||||
if (WITH_KCONFIG)
|
||||
add_library(colorSchemeTools SHARED colorschemetools.cpp)
|
||||
target_link_libraries(colorSchemeTools PRIVATE KF5::ConfigCore)
|
||||
target_link_libraries(colorSchemeTools PRIVATE KF5::ConfigWidgets)
|
||||
set(openFileManagerWindowJob_SRC
|
||||
openfilemanagerwindowjob.cpp
|
||||
)
|
||||
|
||||
install(TARGETS colorSchemeTools LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})
|
||||
add_library(openFileManagerWindowJob SHARED ${openFileManagerWindowJob_SRC})
|
||||
|
||||
get_target_property(Qt5CORE_INTERFACE_INCLUDE_DIRECTORIES Qt5::Core INTERFACE_INCLUDE_DIRECTORIES)
|
||||
get_target_property(KIO_WIDGETS_INTERFACE_INCLUDE_DIRECTORIES KF5::KIOWidgets INTERFACE_INCLUDE_DIRECTORIES)
|
||||
get_target_property(CORE_ADDONS_INTERFACE_INCLUDE_DIRECTORIES KF5::CoreAddons INTERFACE_INCLUDE_DIRECTORIES)
|
||||
target_include_directories(openFileManagerWindowJob PUBLIC ${KIO_WIDGETS_INTERFACE_INCLUDE_DIRECTORIES})
|
||||
target_include_directories(openFileManagerWindowJob PUBLIC ${CORE_ADDONS_INTERFACE_INCLUDE_DIRECTORIES})
|
||||
target_include_directories(openFileManagerWindowJob PUBLIC ${Qt5CORE_INTERFACE_INCLUDE_DIRECTORIES})
|
||||
|
||||
target_link_libraries(openFileManagerWindowJob KF5::KIOWidgets)
|
||||
target_link_libraries(openFileManagerWindowJob Qt5::Core)
|
||||
|
||||
install(TARGETS openFileManagerWindowJob DESTINATION ${CMAKE_INSTALL_LIBDIR})
|
||||
endif()
|
||||
|
@ -1,70 +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 <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,21 +1,3 @@
|
||||
/*
|
||||
* 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 <QObject>
|
||||
#include <KIO/OpenFileManagerWindowJob>
|
||||
|
@ -1,59 +0,0 @@
|
||||
target_sources(squawk PRIVATE resources.qrc)
|
||||
|
||||
configure_file(images/logo.svg squawk.svg COPYONLY)
|
||||
configure_file(squawk.rc squawk.rc COPYONLY)
|
||||
|
||||
if(WIN32)
|
||||
set(CONVERT_BIN magick convert)
|
||||
else(WIN32)
|
||||
set(CONVERT_BIN convert)
|
||||
endif(WIN32)
|
||||
execute_process(COMMAND ${CONVERT_BIN} -background none -size 48x48 squawk.svg squawk48.png WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
|
||||
execute_process(COMMAND ${CONVERT_BIN} -background none -size 64x64 squawk.svg squawk64.png WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
|
||||
execute_process(COMMAND ${CONVERT_BIN} -background none -size 128x128 squawk.svg squawk128.png WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
|
||||
execute_process(COMMAND ${CONVERT_BIN} -background none -size 256x256 squawk.svg squawk256.png WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
|
||||
|
||||
if (WIN32)
|
||||
execute_process(COMMAND ${CONVERT_BIN} squawk48.png squawk64.png squawk256.png squawk.ico WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
|
||||
set(SQUAWK_WIN_RC "${CMAKE_CURRENT_BINARY_DIR}/squawk.rc")
|
||||
set(SQUAWK_WIN_RC "${SQUAWK_WIN_RC}" PARENT_SCOPE)
|
||||
target_sources(squawk PRIVATE ${SQUAWK_WIN_RC})
|
||||
endif(WIN32)
|
||||
|
||||
if (APPLE)
|
||||
file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/icns.iconset")
|
||||
execute_process(COMMAND convert -background none -size 16x16 squawk.svg icns.iconset/icon_16x16.png WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
|
||||
execute_process(COMMAND convert -background none -resize !32x32 squawk.svg "icns.iconset/icon_16x16@2x.png" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
|
||||
execute_process(COMMAND convert -background none -resize !32x32 squawk.svg "icns.iconset/icon_32x32.png" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
|
||||
execute_process(COMMAND convert -background none -resize !64x64 squawk.svg "icns.iconset/icon_32x32@2x.png" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
|
||||
execute_process(COMMAND convert -background none -resize !128x128 squawk.svg "icns.iconset/icon_128x128.png" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
|
||||
execute_process(COMMAND convert -background none -resize !256x256 squawk.svg "icns.iconset/icon_128x128@2x.png" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
|
||||
execute_process(COMMAND convert -background none -resize !256x256 squawk.svg "icns.iconset/icon_256x256.png" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
|
||||
execute_process(COMMAND convert -background none -resize !512x512 squawk.svg "icns.iconset/icon_256x256@2x.png" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
|
||||
execute_process(COMMAND convert -background none -resize !512x512 squawk.svg "icns.iconset/icon_512x512.png" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
|
||||
execute_process(COMMAND convert -background none -resize !1024x1024 squawk.svg "icns.iconset/icon_512x512@2x.png" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
|
||||
execute_process(COMMAND iconutil -c icns "icns.iconset" -o "squawk.icns" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
|
||||
set(MACOSX_BUNDLE_ICON_FILE squawk.icns)
|
||||
set(MACOSX_BUNDLE_ICON_FILE ${MACOSX_BUNDLE_ICON_FILE} PARENT_SCOPE)
|
||||
set(APP_ICON_MACOSX ${CMAKE_CURRENT_BINARY_DIR}/squawk.icns)
|
||||
set(APP_ICON_MACOSX ${APP_ICON_MACOSX} PARENT_SCOPE)
|
||||
target_sources(squawk PRIVATE ${APP_ICON_MACOSX})
|
||||
set_source_files_properties(${APP_ICON_MACOSX} TARGET_DIRECTORY squawk PROPERTIES
|
||||
MACOSX_PACKAGE_LOCATION "Resources")
|
||||
if (CMAKE_BUILD_TYPE STREQUAL "Release")
|
||||
if (APPLE)
|
||||
set_target_properties(squawk PROPERTIES
|
||||
MACOSX_BUNDLE_EXECUTABLE_NAME "Squawk"
|
||||
MACOSX_BUNDLE_ICON_FILE "${MACOSX_BUNDLE_ICON_FILE}" # TODO
|
||||
MACOSX_BUNDLE_BUNDLE_NAME "Squawk"
|
||||
MACOSX_BUNDLE_INFO_PLIST ${CMAKE_SOURCE_DIR}/cmake/MacOSXBundleInfo.plist.in)
|
||||
endif(APPLE)
|
||||
endif()
|
||||
endif (APPLE)
|
||||
|
||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk.svg DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/scalable/apps)
|
||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk48.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/48x48/apps RENAME squawk.png)
|
||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk64.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/64x64/apps RENAME squawk.png)
|
||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk128.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/128x128/apps RENAME squawk.png)
|
||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk256.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/256x256/apps RENAME squawk.png)
|
||||
|
@ -1 +0,0 @@
|
||||
IDI_ICON1 ICON "squawk.ico"
|
@ -19,12 +19,12 @@
|
||||
#ifndef SHARED_H
|
||||
#define SHARED_H
|
||||
|
||||
#include "enums.h"
|
||||
#include "global.h"
|
||||
#include "icons.h"
|
||||
#include "message.h"
|
||||
#include "messageinfo.h"
|
||||
#include "utils.h"
|
||||
#include "vcard.h"
|
||||
#include "shared/enums.h"
|
||||
#include "shared/utils.h"
|
||||
#include "shared/icons.h"
|
||||
#include "shared/message.h"
|
||||
#include "shared/vcard.h"
|
||||
#include "shared/global.h"
|
||||
#include "shared/messageinfo.h"
|
||||
|
||||
#endif // SHARED_H
|
@ -1,21 +0,0 @@
|
||||
target_sources(squawk PRIVATE
|
||||
enums.h
|
||||
global.cpp
|
||||
global.h
|
||||
exception.cpp
|
||||
exception.h
|
||||
icons.cpp
|
||||
icons.h
|
||||
message.cpp
|
||||
message.h
|
||||
messageinfo.cpp
|
||||
messageinfo.h
|
||||
order.h
|
||||
shared.h
|
||||
utils.cpp
|
||||
utils.h
|
||||
vcard.cpp
|
||||
vcard.h
|
||||
pathcheck.cpp
|
||||
pathcheck.h
|
||||
)
|
@ -19,7 +19,6 @@
|
||||
#include "global.h"
|
||||
|
||||
#include "enums.h"
|
||||
#include "ui/models/roster.h"
|
||||
|
||||
Shared::Global* Shared::Global::instance = 0;
|
||||
const std::set<QString> Shared::Global::supportedImagesExts = {"png", "jpg", "webp", "jpeg", "gif", "svg"};
|
||||
@ -29,14 +28,6 @@ QLibrary Shared::Global::openFileManagerWindowJob("openFileManagerWindowJob");
|
||||
Shared::Global::HighlightInFileManager Shared::Global::hfm = 0;
|
||||
#endif
|
||||
|
||||
#ifdef WITH_KCONFIG
|
||||
QLibrary Shared::Global::colorSchemeTools("colorSchemeTools");
|
||||
Shared::Global::CreatePreview Shared::Global::createPreview = 0;
|
||||
Shared::Global::DeletePreview Shared::Global::deletePreview = 0;
|
||||
Shared::Global::ColorSchemeName Shared::Global::colorSchemeName = 0;
|
||||
Shared::Global::CreatePalette Shared::Global::createPalette = 0;
|
||||
#endif
|
||||
|
||||
Shared::Global::Global():
|
||||
availability({
|
||||
tr("Online", "Availability"),
|
||||
@ -93,12 +84,9 @@ Shared::Global::Global():
|
||||
tr("Squawk is going to query you for the password on every start of the program", "AccountPasswordDescription"),
|
||||
tr("Your password is going to be stored in KDE wallet storage (KWallet). You're going to be queried for permissions", "AccountPasswordDescription")
|
||||
}),
|
||||
defaultSystemStyle(QApplication::style()->objectName()),
|
||||
defaultSystemPalette(QApplication::palette()),
|
||||
pluginSupport({
|
||||
{"KWallet", false},
|
||||
{"openFileManagerWindowJob", false},
|
||||
{"colorSchemeTools", false}
|
||||
{"openFileManagerWindowJob", false}
|
||||
}),
|
||||
fileCache()
|
||||
{
|
||||
@ -122,28 +110,8 @@ Shared::Global::Global():
|
||||
qDebug() << "KIO::OpenFileManagerWindow support disabled: couldn't load the library" << openFileManagerWindowJob.errorString();
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef WITH_KCONFIG
|
||||
colorSchemeTools.load();
|
||||
if (colorSchemeTools.isLoaded()) {
|
||||
createPreview = (CreatePreview) colorSchemeTools.resolve("createPreview");
|
||||
deletePreview = (DeletePreview) colorSchemeTools.resolve("deletePreview");
|
||||
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)
|
||||
{
|
||||
std::map<QString, FileInfo>::const_iterator itr = instance->fileCache.find(path);
|
||||
@ -157,24 +125,15 @@ Shared::Global::FileInfo Shared::Global::getFileInfo(const QString& path)
|
||||
FileInfo::Preview p = FileInfo::Preview::none;
|
||||
QSize size;
|
||||
if (big == "image") {
|
||||
QMovie mov(path);
|
||||
if (mov.isValid() && mov.frameCount() > 1) {
|
||||
p = FileInfo::Preview::animation;
|
||||
} else {
|
||||
p = FileInfo::Preview::picture;
|
||||
if (parts.back() == "gif") {
|
||||
//TODO need to consider GIF as a movie
|
||||
}
|
||||
QImageReader img(path);
|
||||
p = FileInfo::Preview::picture;
|
||||
QImage img(path);
|
||||
size = img.size();
|
||||
// } else if (big == "video") {
|
||||
// p = FileInfo::Preview::movie;
|
||||
// QMovie mov(path);
|
||||
// size = mov.scaledSize();
|
||||
// qDebug() << mov.isValid();
|
||||
} else {
|
||||
size = defaultIconFileInfoHeight;
|
||||
}
|
||||
|
||||
itr = instance->fileCache.insert(std::make_pair(path, FileInfo({info.absoluteFilePath(), info.fileName(), size, type, p}))).first;
|
||||
itr = instance->fileCache.insert(std::make_pair(path, FileInfo({info.fileName(), size, type, p}))).first;
|
||||
}
|
||||
|
||||
return itr->second;
|
||||
@ -305,50 +264,6 @@ 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) \
|
||||
template<> \
|
||||
|
@ -27,14 +27,12 @@
|
||||
#include <set>
|
||||
#include <deque>
|
||||
|
||||
#include <QApplication>
|
||||
#include <QStyle>
|
||||
#include <QCoreApplication>
|
||||
#include <QDebug>
|
||||
#include <QMimeType>
|
||||
#include <QMimeDatabase>
|
||||
#include <QFileInfo>
|
||||
#include <QImage>
|
||||
#include <QMovie>
|
||||
#include <QSize>
|
||||
#include <QUrl>
|
||||
#include <QLibrary>
|
||||
@ -47,15 +45,15 @@ namespace Shared {
|
||||
|
||||
class Global {
|
||||
Q_DECLARE_TR_FUNCTIONS(Global)
|
||||
|
||||
public:
|
||||
struct FileInfo {
|
||||
enum class Preview {
|
||||
none,
|
||||
picture,
|
||||
animation
|
||||
movie
|
||||
};
|
||||
|
||||
QString path;
|
||||
QString name;
|
||||
QSize size;
|
||||
QMimeType mime;
|
||||
@ -85,9 +83,6 @@ namespace Shared {
|
||||
|
||||
const std::deque<QString> accountPasswordDescription;
|
||||
|
||||
const QString defaultSystemStyle;
|
||||
const QPalette defaultSystemPalette;
|
||||
|
||||
static bool supported(const QString& pluginName);
|
||||
static void setSupported(const QString& pluginName, bool support);
|
||||
|
||||
@ -95,10 +90,6 @@ namespace Shared {
|
||||
|
||||
static FileInfo getFileInfo(const QString& path);
|
||||
static void highlightInFileManager(const QString& path);
|
||||
static QIcon createThemePreview(const QString& path);
|
||||
static QString getColorSchemeName(const QString& path);
|
||||
static void setTheme(const QString& path);
|
||||
static void setStyle(const QString& style);
|
||||
|
||||
template<typename T>
|
||||
static T fromInt(int src);
|
||||
@ -132,20 +123,6 @@ namespace Shared {
|
||||
|
||||
static HighlightInFileManager hfm;
|
||||
#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,11 +404,9 @@ 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
|
||||
}
|
||||
if (!edited || lastModified < correctionDate) {
|
||||
if (!edited) {
|
||||
originalMessage = body;
|
||||
}
|
||||
lastModified = correctionDate;
|
||||
setBody(b);
|
||||
setBody(body);
|
||||
setEdited(true);
|
||||
}
|
||||
}
|
||||
|
@ -1,96 +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 "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());
|
||||
}
|
||||
|
@ -1,44 +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 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
|
@ -17,11 +17,15 @@
|
||||
*/
|
||||
|
||||
#include "utils.h"
|
||||
#include <QUuid>
|
||||
|
||||
QString Shared::generateUUID()
|
||||
{
|
||||
return QUuid::createUuid().toString();
|
||||
uuid_t uuid;
|
||||
uuid_generate(uuid);
|
||||
|
||||
char uuid_str[36];
|
||||
uuid_unparse_lower(uuid, uuid_str);
|
||||
return uuid_str;
|
||||
}
|
||||
|
||||
|
||||
@ -40,5 +44,5 @@ QString Shared::processMessageBody(const QString& msg)
|
||||
{
|
||||
QString processed = msg.toHtmlEscaped();
|
||||
processed.replace(urlReg, "<a href=\"\\1\">\\1</a>");
|
||||
return "<p style=\"white-space: pre-wrap; line-height: 1em;\">" + processed + "</p>";
|
||||
return "<p style=\"white-space: pre-wrap;\">" + processed + "</p>";
|
||||
}
|
||||
|
@ -24,6 +24,9 @@
|
||||
#include <QColor>
|
||||
#include <QRegularExpression>
|
||||
|
||||
//#include "KIO/OpenFileManagerWindowJob"
|
||||
|
||||
#include <uuid/uuid.h>
|
||||
#include <vector>
|
||||
|
||||
namespace Shared {
|
||||
@ -69,12 +72,6 @@ static const std::vector<QColor> colorPalette = {
|
||||
QColor(17, 17, 80),
|
||||
QColor(54, 54, 94)
|
||||
};
|
||||
|
||||
enum class Hover {
|
||||
nothing,
|
||||
text,
|
||||
anchor
|
||||
};
|
||||
}
|
||||
|
||||
#endif // SHARED_UTILS_H
|
||||
|
@ -50,7 +50,7 @@ void SignalCatcher::handleSigInt()
|
||||
char tmp;
|
||||
ssize_t s = ::read(sigintFd[1], &tmp, sizeof(tmp));
|
||||
|
||||
emit interrupt();
|
||||
app->quit();
|
||||
|
||||
snInt->setEnabled(true);
|
||||
}
|
@ -33,9 +33,6 @@ public:
|
||||
|
||||
static void intSignalHandler(int unused);
|
||||
|
||||
signals:
|
||||
void interrupt();
|
||||
|
||||
public slots:
|
||||
void handleSigInt();
|
||||
|
@ -1,12 +0,0 @@
|
||||
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)
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,108 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE TS>
|
||||
<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>
|
||||
<name>Account</name>
|
||||
<message>
|
||||
@ -112,12 +10,10 @@ To report bugs you can use:</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>Your account login</source>
|
||||
<translatorcomment>Tooltip</translatorcomment>
|
||||
<translation>Имя пользователя Вашей учетной записи</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>john_smith1987</source>
|
||||
<translatorcomment>Login placeholder</translatorcomment>
|
||||
<translation>ivan_ivanov1987</translation>
|
||||
</message>
|
||||
<message>
|
||||
@ -126,12 +22,10 @@ To report bugs you can use:</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>A server address of your account. Like 404.city or macaw.me</source>
|
||||
<translatorcomment>Tooltip</translatorcomment>
|
||||
<translation>Адресс сервера вашей учетной записи (выглядит как 404.city или macaw.me)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>macaw.me</source>
|
||||
<translatorcomment>Placeholder</translatorcomment>
|
||||
<translation>macaw.me</translation>
|
||||
</message>
|
||||
<message>
|
||||
@ -144,7 +38,6 @@ To report bugs you can use:</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>Password of your account</source>
|
||||
<translatorcomment>Tooltip</translatorcomment>
|
||||
<translation>Пароль вашей учетной записи</translation>
|
||||
</message>
|
||||
<message>
|
||||
@ -153,11 +46,10 @@ To report bugs you can use:</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>Just a name how would you call this account, doesn't affect anything</source>
|
||||
<translation>Просто имя, то как Вы называете свою учетную запись, может быть любым (нельзя поменять)</translation>
|
||||
<translation>Просто имя, то как Вы называете свою учетную запись, может быть любым</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>John</source>
|
||||
<translatorcomment>Placeholder</translatorcomment>
|
||||
<translation>Иван</translation>
|
||||
</message>
|
||||
<message>
|
||||
@ -166,7 +58,6 @@ To report bugs you can use:</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>A resource name like "Home" or "Work"</source>
|
||||
<translatorcomment>Tooltip</translatorcomment>
|
||||
<translation>Имя этой программы для ваших контактов, может быть "Home" или "Phone"</translation>
|
||||
</message>
|
||||
<message>
|
||||
@ -178,14 +69,6 @@ To report bugs you can use:</source>
|
||||
<source>Password storage</source>
|
||||
<translation>Хранение пароля</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Active</source>
|
||||
<translation>Активен</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>enable</source>
|
||||
<translation>включен</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Accounts</name>
|
||||
@ -215,139 +98,30 @@ To report bugs you can use:</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>Disconnect</source>
|
||||
<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>
|
||||
<translation>Отключить</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Conversation</name>
|
||||
<message>
|
||||
<source>Type your message here...</source>
|
||||
<translatorcomment>Placeholder</translatorcomment>
|
||||
<translation>Введите сообщение...</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Chose a file to send</source>
|
||||
<translation>Выберите файл для отправки</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Drop files here to attach them to your message</source>
|
||||
<translation>Бросьте файлы сюда для того что бы прикрепить их к сообщению</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
|
||||
<html><head><meta name="qrichtext" content="1" /><style type="text/css">
|
||||
p, li { white-space: pre-wrap; }
|
||||
</style></head><body style=" font-family:'Noto Sans'; font-size:8pt; font-weight:400; font-style:normal;">
|
||||
</style></head><body style=" font-family:'Liberation Sans'; font-size:10pt; font-weight:400; font-style:normal;">
|
||||
<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html></source>
|
||||
<translation><!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>
|
||||
<translation></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Paste Image</source>
|
||||
<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>
|
||||
<source>Drop files here to attach them to your message</source>
|
||||
<translation>Бросьте файлы сюда для того что бы прикрепить их к сообщению</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@ -596,14 +370,14 @@ Would you like to check them and try again?</source>
|
||||
<name>Message</name>
|
||||
<message>
|
||||
<source>Open</source>
|
||||
<translation type="vanished">Открыть</translation>
|
||||
<translation>Открыть</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>MessageLine</name>
|
||||
<message>
|
||||
<source>Downloading...</source>
|
||||
<translation type="vanished">Скачивается...</translation>
|
||||
<translation>Скачивается...</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Download</source>
|
||||
@ -612,28 +386,28 @@ Would you like to check them and try again?</source>
|
||||
<message>
|
||||
<source>Error uploading file: %1
|
||||
You can try again</source>
|
||||
<translation type="vanished">Ошибка загрузки файла на сервер:
|
||||
<translation>Ошибка загрузки файла на сервер:
|
||||
%1
|
||||
Для того, что бы попробовать снова нажмите на кнопку</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Upload</source>
|
||||
<translation type="vanished">Загрузить</translation>
|
||||
<translation>Загрузить</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Error downloading file: %1
|
||||
You can try again</source>
|
||||
<translation type="vanished">Ошибка скачивания файла:
|
||||
<translation>Ошибка скачивания файла:
|
||||
%1
|
||||
Вы можете попробовать снова</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Uploading...</source>
|
||||
<translation type="vanished">Загружается...</translation>
|
||||
<translation>Загружается...</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Push the button to download the file</source>
|
||||
<translation type="vanished">Нажмите на кнопку что бы загрузить файл</translation>
|
||||
<translation>Нажмите на кнопку что бы загрузить файл</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@ -707,7 +481,7 @@ You can try again</source>
|
||||
<name>NewContact</name>
|
||||
<message>
|
||||
<source>Add new contact</source>
|
||||
<translatorcomment>Window title</translatorcomment>
|
||||
<translatorcomment>Заголовок окна</translatorcomment>
|
||||
<translation>Добавление нового контакта</translation>
|
||||
</message>
|
||||
<message>
|
||||
@ -728,7 +502,7 @@ You can try again</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>name@server.dmn</source>
|
||||
<translatorcomment>Placeholder</translatorcomment>
|
||||
<translatorcomment>Placeholder поля ввода JID</translatorcomment>
|
||||
<translation>name@server.dmn</translation>
|
||||
</message>
|
||||
<message>
|
||||
@ -744,64 +518,6 @@ You can try again</source>
|
||||
<translation>Иван Иванов</translation>
|
||||
</message>
|
||||
</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>
|
||||
<name>Squawk</name>
|
||||
<message>
|
||||
@ -814,7 +530,6 @@ You can try again</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>Squawk</source>
|
||||
<translatorcomment>Menu bar entry</translatorcomment>
|
||||
<translation>Squawk</translation>
|
||||
</message>
|
||||
<message>
|
||||
@ -835,11 +550,11 @@ You can try again</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>Disconnect</source>
|
||||
<translation type="vanished">Отключить</translation>
|
||||
<translation>Отключить</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Connect</source>
|
||||
<translation type="vanished">Подключить</translation>
|
||||
<translation>Подключить</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>VCard</source>
|
||||
@ -912,40 +627,20 @@ to be displayed as %1</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>Attached file</source>
|
||||
<translation type="vanished">Прикрепленный файл</translation>
|
||||
<translation>Прикрепленный файл</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Input the password for account %1</source>
|
||||
<translation type="vanished">Введите пароль для учетной записи %1</translation>
|
||||
<translation>Введите пароль для учетной записи %1</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Password for account %1</source>
|
||||
<translation type="vanished">Пароль для учетной записи %1</translation>
|
||||
<translation>Пароль для учетной записи %1</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Please select a contact to start chatting</source>
|
||||
<translation>Выберите контакт или группу что бы начать переписку</translation>
|
||||
</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>
|
||||
<name>VCard</name>
|
||||
|
@ -1,9 +1,43 @@
|
||||
target_sources(squawk PRIVATE
|
||||
squawk.cpp
|
||||
squawk.h
|
||||
squawk.ui
|
||||
)
|
||||
cmake_minimum_required(VERSION 3.3)
|
||||
project(squawkUI)
|
||||
|
||||
# Instruct CMake to run moc automatically when needed.
|
||||
set(CMAKE_AUTOMOC ON)
|
||||
# Instruct CMake to create code from Qt designer ui files
|
||||
set(CMAKE_AUTOUIC ON)
|
||||
|
||||
# Find the QtWidgets library
|
||||
find_package(Qt5 CONFIG REQUIRED COMPONENTS Widgets DBus Core)
|
||||
find_package(Boost 1.36.0 REQUIRED)
|
||||
if(Boost_FOUND)
|
||||
include_directories(${Boost_INCLUDE_DIRS})
|
||||
endif()
|
||||
|
||||
add_subdirectory(models)
|
||||
add_subdirectory(utils)
|
||||
add_subdirectory(widgets)
|
||||
|
||||
set(squawkUI_SRC
|
||||
squawk.cpp
|
||||
models/accounts.cpp
|
||||
models/roster.cpp
|
||||
models/item.cpp
|
||||
models/account.cpp
|
||||
models/contact.cpp
|
||||
models/presence.cpp
|
||||
models/group.cpp
|
||||
models/room.cpp
|
||||
models/abstractparticipant.cpp
|
||||
models/participant.cpp
|
||||
models/reference.cpp
|
||||
models/messagefeed.cpp
|
||||
models/element.cpp
|
||||
)
|
||||
|
||||
# Tell CMake to create the helloworld executable
|
||||
add_library(squawkUI STATIC ${squawkUI_SRC})
|
||||
|
||||
# Use the Widgets module from Qt 5.
|
||||
target_link_libraries(squawkUI squawkWidgets)
|
||||
target_link_libraries(squawkUI squawkUIUtils)
|
||||
target_link_libraries(squawkUI Qt5::Widgets)
|
||||
target_link_libraries(squawkUI Qt5::DBus)
|
||||
|
@ -1,26 +0,0 @@
|
||||
target_sources(squawk PRIVATE
|
||||
abstractparticipant.cpp
|
||||
abstractparticipant.h
|
||||
account.cpp
|
||||
account.h
|
||||
accounts.cpp
|
||||
accounts.h
|
||||
contact.cpp
|
||||
contact.h
|
||||
element.cpp
|
||||
element.h
|
||||
group.cpp
|
||||
group.h
|
||||
item.cpp
|
||||
item.h
|
||||
participant.cpp
|
||||
participant.h
|
||||
presence.cpp
|
||||
presence.h
|
||||
reference.cpp
|
||||
reference.h
|
||||
room.cpp
|
||||
room.h
|
||||
roster.cpp
|
||||
roster.h
|
||||
)
|
@ -31,9 +31,7 @@ Models::Account::Account(const QMap<QString, QVariant>& data, Models::Item* pare
|
||||
avatarPath(data.value("avatarPath").toString()),
|
||||
state(Shared::ConnectionState::disconnected),
|
||||
availability(Shared::Availability::offline),
|
||||
passwordType(Shared::AccountPassword::plain),
|
||||
wasEverConnected(false),
|
||||
active(false)
|
||||
passwordType(Shared::AccountPassword::plain)
|
||||
{
|
||||
QMap<QString, QVariant>::const_iterator sItr = data.find("state");
|
||||
if (sItr != data.end()) {
|
||||
@ -47,10 +45,6 @@ Models::Account::Account(const QMap<QString, QVariant>& data, Models::Item* pare
|
||||
if (pItr != data.end()) {
|
||||
setPasswordType(pItr.value().toUInt());
|
||||
}
|
||||
QMap<QString, QVariant>::const_iterator acItr = data.find("active");
|
||||
if (acItr != data.end()) {
|
||||
setActive(acItr.value().toBool());
|
||||
}
|
||||
}
|
||||
|
||||
Models::Account::~Account()
|
||||
@ -62,19 +56,8 @@ void Models::Account::setState(Shared::ConnectionState p_state)
|
||||
if (state != p_state) {
|
||||
state = p_state;
|
||||
changed(2);
|
||||
switch (state) {
|
||||
case Shared::ConnectionState::disconnected:
|
||||
if (state == Shared::ConnectionState::disconnected) {
|
||||
toOfflineState();
|
||||
break;
|
||||
case Shared::ConnectionState::connected:
|
||||
if (wasEverConnected) {
|
||||
emit reconnected();
|
||||
} else {
|
||||
wasEverConnected = true;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -181,8 +164,6 @@ QVariant Models::Account::data(int column) const
|
||||
return avatarPath;
|
||||
case 9:
|
||||
return Shared::Global::getName(passwordType);
|
||||
case 10:
|
||||
return active;
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
@ -190,7 +171,7 @@ QVariant Models::Account::data(int column) const
|
||||
|
||||
int Models::Account::columnCount() const
|
||||
{
|
||||
return 11;
|
||||
return 10;
|
||||
}
|
||||
|
||||
void Models::Account::update(const QString& field, const QVariant& value)
|
||||
@ -215,8 +196,6 @@ void Models::Account::update(const QString& field, const QVariant& value)
|
||||
setAvatarPath(value.toString());
|
||||
} else if (field == "passwordType") {
|
||||
setPasswordType(value.toUInt());
|
||||
} else if (field == "active") {
|
||||
setActive(value.toBool());
|
||||
}
|
||||
}
|
||||
|
||||
@ -290,16 +269,3 @@ void Models::Account::setPasswordType(unsigned int 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,9 +59,6 @@ namespace Models {
|
||||
void setAvatarPath(const QString& path);
|
||||
QString getAvatarPath() const;
|
||||
|
||||
void setActive(bool active);
|
||||
bool getActive() const;
|
||||
|
||||
void setAvailability(Shared::Availability p_avail);
|
||||
void setAvailability(unsigned int p_avail);
|
||||
Shared::Availability getAvailability() const;
|
||||
@ -80,9 +77,6 @@ namespace Models {
|
||||
QString getBareJid() const;
|
||||
QString getFullJid() const;
|
||||
|
||||
signals:
|
||||
void reconnected();
|
||||
|
||||
private:
|
||||
QString login;
|
||||
QString password;
|
||||
@ -93,8 +87,6 @@ namespace Models {
|
||||
Shared::ConnectionState state;
|
||||
Shared::Availability availability;
|
||||
Shared::AccountPassword passwordType;
|
||||
bool wasEverConnected;
|
||||
bool active;
|
||||
|
||||
protected slots:
|
||||
void toOfflineState() override;
|
||||
|
@ -48,10 +48,6 @@ QVariant Models::Accounts::data (const QModelIndex& index, int role) const
|
||||
answer = Shared::connectionStateIcon(accs[index.row()]->getState());
|
||||
}
|
||||
break;
|
||||
case Qt::ForegroundRole:
|
||||
if (!accs[index.row()]->getActive()) {
|
||||
answer = qApp->palette().brush(QPalette::Disabled, QPalette::Text);
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@ -101,10 +97,7 @@ void Models::Accounts::addAccount(Account* account)
|
||||
|
||||
void Models::Accounts::onAccountChanged(Item* item, int row, int col)
|
||||
{
|
||||
if (row < 0) {
|
||||
return;
|
||||
}
|
||||
if (static_cast<std::deque<Models::Account*>::size_type>(row) < accs.size()) {
|
||||
if (row < accs.size()) {
|
||||
Account* acc = getAccount(row);
|
||||
if (item != acc) {
|
||||
return; //it means the signal is emitted by one of accounts' children, not exactly him, this model has no interest in that
|
||||
|
@ -155,16 +155,6 @@ 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()
|
||||
{
|
||||
QDateTime lastActivity;
|
||||
@ -250,9 +240,3 @@ QString Models::Contact::getDisplayedName() const
|
||||
return getContactName();
|
||||
}
|
||||
|
||||
void Models::Contact::handleRecconnect()
|
||||
{
|
||||
if (getMessagesCount() > 0) {
|
||||
feed->requestLatestMessages();
|
||||
}
|
||||
}
|
||||
|
@ -51,14 +51,11 @@ public:
|
||||
|
||||
void addPresence(const QString& name, const QMap<QString, QVariant>& data);
|
||||
void removePresence(const QString& name);
|
||||
Presence* getPresence(const QString& name);
|
||||
|
||||
QString getContactName() const;
|
||||
QString getStatus() const;
|
||||
QString getDisplayedName() const override;
|
||||
|
||||
void handleRecconnect(); //this is a special method Models::Roster calls when reconnect happens
|
||||
|
||||
protected:
|
||||
void _removeChild(int index) override;
|
||||
void _appendChild(Models::Item * child) override;
|
||||
|
@ -134,11 +134,6 @@ unsigned int Models::Element::getMessagesCount() const
|
||||
return feed->unreadMessagesCount();
|
||||
}
|
||||
|
||||
bool Models::Element::markMessageAsRead(const QString& id) const
|
||||
{
|
||||
return feed->markMessageAsRead(id);
|
||||
}
|
||||
|
||||
void Models::Element::addMessage(const Shared::Message& data)
|
||||
{
|
||||
feed->addMessage(data);
|
||||
@ -176,7 +171,6 @@ void Models::Element::fileError(const QString& messageId, const QString& error,
|
||||
|
||||
void Models::Element::onFeedUnreadMessagesCountChanged()
|
||||
{
|
||||
emit unreadMessagesCountChanged();
|
||||
if (type == contact) {
|
||||
changed(4);
|
||||
} else if (type == room) {
|
||||
|
@ -20,8 +20,7 @@
|
||||
#define ELEMENT_H
|
||||
|
||||
#include "item.h"
|
||||
|
||||
#include "ui/widgets/messageline/messagefeed.h"
|
||||
#include "messagefeed.h"
|
||||
|
||||
namespace Models {
|
||||
|
||||
@ -42,7 +41,6 @@ public:
|
||||
void addMessage(const Shared::Message& data);
|
||||
void changeMessage(const QString& id, const QMap<QString, QVariant>& data);
|
||||
unsigned int getMessagesCount() const;
|
||||
bool markMessageAsRead(const QString& id) const;
|
||||
void responseArchive(const std::list<Shared::Message> list, bool last);
|
||||
bool isRoom() const;
|
||||
void fileProgress(const QString& messageId, qreal value, bool up);
|
||||
@ -53,7 +51,6 @@ signals:
|
||||
void requestArchive(const QString& before);
|
||||
void fileDownloadRequest(const QString& url);
|
||||
void unnoticedMessage(const QString& account, const Shared::Message& msg);
|
||||
void unreadMessagesCountChanged();
|
||||
void localPathInvalid(const QString& path);
|
||||
|
||||
protected:
|
||||
|
@ -17,9 +17,8 @@
|
||||
*/
|
||||
|
||||
#include "messagefeed.h"
|
||||
|
||||
#include <ui/models/element.h>
|
||||
#include <ui/models/room.h>
|
||||
#include "element.h"
|
||||
#include "room.h"
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
@ -46,8 +45,6 @@ Models::MessageFeed::MessageFeed(const Element* ri, QObject* parent):
|
||||
syncState(incomplete),
|
||||
uploads(),
|
||||
downloads(),
|
||||
failedDownloads(),
|
||||
failedUploads(),
|
||||
unreadMessages(new std::set<QString>()),
|
||||
observersAmount(0)
|
||||
{
|
||||
@ -86,7 +83,7 @@ void Models::MessageFeed::addMessage(const Shared::Message& msg)
|
||||
emit newMessage(msg);
|
||||
|
||||
if (observersAmount == 0 && !msg.getForwarded()) { //not to notify when the message is delivered by the carbon copy
|
||||
unreadMessages->insert(id); //cuz it could be my own one or the one I read on another device
|
||||
unreadMessages->insert(msg.getId()); //cuz it could be my own one or the one I read on another device
|
||||
emit unreadMessagesCountChanged();
|
||||
emit unnoticedMessage(msg);
|
||||
}
|
||||
@ -145,30 +142,12 @@ void Models::MessageFeed::changeMessage(const QString& id, const QMap<QString, Q
|
||||
}
|
||||
}
|
||||
|
||||
Err::const_iterator eitr = failedDownloads.find(id);
|
||||
if (eitr != failedDownloads.end()) {
|
||||
failedDownloads.erase(eitr);
|
||||
changeRoles.insert(MessageRoles::Attach);
|
||||
} else {
|
||||
eitr = failedUploads.find(id);
|
||||
if (eitr != failedUploads.end()) {
|
||||
failedUploads.erase(eitr);
|
||||
changeRoles.insert(MessageRoles::Attach);
|
||||
}
|
||||
}
|
||||
|
||||
QVector<int> cr;
|
||||
for (MessageRoles role : changeRoles) {
|
||||
cr.push_back(role);
|
||||
}
|
||||
|
||||
emit dataChanged(index, index, cr);
|
||||
|
||||
if (observersAmount == 0 && !msg->getForwarded() && changeRoles.count(MessageRoles::Text) > 0) {
|
||||
unreadMessages->insert(id);
|
||||
emit unreadMessagesCountChanged();
|
||||
emit unnoticedMessage(*msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -230,20 +209,8 @@ std::set<Models::MessageFeed::MessageRoles> Models::MessageFeed::detectChanges(c
|
||||
|
||||
void Models::MessageFeed::removeMessage(const QString& id)
|
||||
{
|
||||
//todo;
|
||||
}
|
||||
|
||||
Shared::Message Models::MessageFeed::getMessage(const QString& id)
|
||||
{
|
||||
StorageById::iterator itr = indexById.find(id);
|
||||
if (itr == indexById.end()) {
|
||||
throw NotFound(id.toStdString(), rosterItem->getJid().toStdString(), rosterItem->getAccountName().toStdString());
|
||||
}
|
||||
|
||||
return **itr;
|
||||
}
|
||||
|
||||
|
||||
QVariant Models::MessageFeed::data(const QModelIndex& index, int role) const
|
||||
{
|
||||
int i = index.row();
|
||||
@ -280,7 +247,7 @@ QVariant Models::MessageFeed::data(const QModelIndex& index, int role) const
|
||||
answer = static_cast<unsigned int>(msg->getState());
|
||||
break;
|
||||
case Correction:
|
||||
answer.setValue(fillCorrection(*msg));;
|
||||
answer = msg->getEdited();
|
||||
break;
|
||||
case SentByMe:
|
||||
answer = sentByMe(*msg);
|
||||
@ -318,13 +285,18 @@ QVariant Models::MessageFeed::data(const QModelIndex& index, int role) const
|
||||
case Bulk: {
|
||||
FeedItem item;
|
||||
item.id = msg->getId();
|
||||
markMessageAsRead(item.id);
|
||||
|
||||
std::set<QString>::const_iterator umi = unreadMessages->find(item.id);
|
||||
if (umi != unreadMessages->end()) {
|
||||
unreadMessages->erase(umi);
|
||||
emit unreadMessagesCount();
|
||||
}
|
||||
|
||||
item.sentByMe = sentByMe(*msg);
|
||||
item.date = msg->getTime();
|
||||
item.state = msg->getState();
|
||||
item.error = msg->getErrorText();
|
||||
item.correction = fillCorrection(*msg);
|
||||
item.correction = msg->getEdited();
|
||||
|
||||
QString body = msg->getBody();
|
||||
if (body != msg->getOutOfBandUrl()) {
|
||||
@ -368,17 +340,6 @@ int Models::MessageFeed::rowCount(const QModelIndex& parent) const
|
||||
return storage.size();
|
||||
}
|
||||
|
||||
bool Models::MessageFeed::markMessageAsRead(const QString& id) const
|
||||
{
|
||||
std::set<QString>::const_iterator umi = unreadMessages->find(id);
|
||||
if (umi != unreadMessages->end()) {
|
||||
unreadMessages->erase(umi);
|
||||
emit unreadMessagesCountChanged();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
unsigned int Models::MessageFeed::unreadMessagesCount() const
|
||||
{
|
||||
return unreadMessages->size();
|
||||
@ -394,6 +355,7 @@ void Models::MessageFeed::fetchMore(const QModelIndex& parent)
|
||||
if (syncState == incomplete) {
|
||||
syncState = syncing;
|
||||
emit syncStateChange(syncState);
|
||||
emit requestStateChange(true);
|
||||
|
||||
if (storage.size() == 0) {
|
||||
emit requestArchive("");
|
||||
@ -421,6 +383,7 @@ void Models::MessageFeed::responseArchive(const std::list<Shared::Message> list,
|
||||
syncState = incomplete;
|
||||
}
|
||||
emit syncStateChange(syncState);
|
||||
emit requestStateChange(false);
|
||||
}
|
||||
}
|
||||
|
||||
@ -458,7 +421,6 @@ bool Models::MessageFeed::sentByMe(const Shared::Message& msg) const
|
||||
Models::Attachment Models::MessageFeed::fillAttach(const Shared::Message& msg) const
|
||||
{
|
||||
::Models::Attachment att;
|
||||
QString id = msg.getId();
|
||||
|
||||
att.localPath = msg.getAttachPath();
|
||||
att.remotePath = msg.getOutOfBandUrl();
|
||||
@ -467,12 +429,7 @@ Models::Attachment Models::MessageFeed::fillAttach(const Shared::Message& msg) c
|
||||
if (att.localPath.size() == 0) {
|
||||
att.state = none;
|
||||
} else {
|
||||
Err::const_iterator eitr = failedUploads.find(id);
|
||||
if (eitr != failedUploads.end()) {
|
||||
att.state = errorUpload;
|
||||
att.error = eitr->second;
|
||||
} else {
|
||||
Progress::const_iterator itr = uploads.find(id);
|
||||
Progress::const_iterator itr = uploads.find(msg.getId());
|
||||
if (itr == uploads.end()) {
|
||||
att.state = local;
|
||||
} else {
|
||||
@ -480,22 +437,15 @@ Models::Attachment Models::MessageFeed::fillAttach(const Shared::Message& msg) c
|
||||
att.progress = itr->second;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (att.localPath.size() == 0) {
|
||||
Err::const_iterator eitr = failedDownloads.find(id);
|
||||
if (eitr != failedDownloads.end()) {
|
||||
att.state = errorDownload;
|
||||
att.error = eitr->second;
|
||||
} else {
|
||||
Progress::const_iterator itr = downloads.find(id);
|
||||
Progress::const_iterator itr = downloads.find(msg.getId());
|
||||
if (itr == downloads.end()) {
|
||||
att.state = remote;
|
||||
} else {
|
||||
att.state = downloading;
|
||||
att.progress = itr->second;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
att.state = ready;
|
||||
}
|
||||
@ -504,29 +454,14 @@ Models::Attachment Models::MessageFeed::fillAttach(const Shared::Message& msg) c
|
||||
return att;
|
||||
}
|
||||
|
||||
Models::Edition Models::MessageFeed::fillCorrection(const Shared::Message& msg) const
|
||||
{
|
||||
::Models::Edition ed({msg.getEdited(), msg.getOriginalBody(), msg.getLastModified()});
|
||||
|
||||
return ed;
|
||||
}
|
||||
|
||||
|
||||
void Models::MessageFeed::downloadAttachment(const QString& messageId)
|
||||
{
|
||||
bool notify = false;
|
||||
Err::const_iterator eitr = failedDownloads.find(messageId);
|
||||
if (eitr != failedDownloads.end()) {
|
||||
failedDownloads.erase(eitr);
|
||||
notify = true;
|
||||
}
|
||||
|
||||
QModelIndex ind = modelIndexById(messageId);
|
||||
if (ind.isValid()) {
|
||||
std::pair<Progress::iterator, bool> progressPair = downloads.insert(std::make_pair(messageId, 0));
|
||||
if (progressPair.second) { //Only to take action if we weren't already downloading it
|
||||
Shared::Message* msg = static_cast<Shared::Message*>(ind.internalPointer());
|
||||
notify = true;
|
||||
emit dataChanged(ind, ind, {MessageRoles::Attach});
|
||||
emit fileDownloadRequest(msg->getOutOfBandUrl());
|
||||
} else {
|
||||
qDebug() << "Attachment download for message with id" << messageId << "is already in progress, skipping";
|
||||
@ -534,55 +469,32 @@ void Models::MessageFeed::downloadAttachment(const QString& messageId)
|
||||
} else {
|
||||
qDebug() << "An attempt to download an attachment for the message that doesn't exist. ID:" << messageId;
|
||||
}
|
||||
}
|
||||
|
||||
if (notify) {
|
||||
emit dataChanged(ind, ind, {MessageRoles::Attach});
|
||||
}
|
||||
void Models::MessageFeed::uploadAttachment(const QString& messageId)
|
||||
{
|
||||
qDebug() << "request to upload attachment of the message" << messageId;
|
||||
}
|
||||
|
||||
bool Models::MessageFeed::registerUpload(const QString& messageId)
|
||||
{
|
||||
bool success = uploads.insert(std::make_pair(messageId, 0)).second;
|
||||
|
||||
QVector<int> roles({});
|
||||
Err::const_iterator eitr = failedUploads.find(messageId);
|
||||
if (eitr != failedUploads.end()) {
|
||||
failedUploads.erase(eitr);
|
||||
roles.push_back(MessageRoles::Attach);
|
||||
} else if (success) {
|
||||
roles.push_back(MessageRoles::Attach);
|
||||
}
|
||||
|
||||
QModelIndex ind = modelIndexById(messageId);
|
||||
emit dataChanged(ind, ind, roles);
|
||||
|
||||
return success;
|
||||
return uploads.insert(std::make_pair(messageId, 0)).second;
|
||||
}
|
||||
|
||||
void Models::MessageFeed::fileProgress(const QString& messageId, qreal value, bool up)
|
||||
{
|
||||
Progress* pr = 0;
|
||||
Err* err = 0;
|
||||
if (up) {
|
||||
pr = &uploads;
|
||||
err = &failedUploads;
|
||||
} else {
|
||||
pr = &downloads;
|
||||
err = &failedDownloads;
|
||||
}
|
||||
|
||||
QVector<int> roles({});
|
||||
Err::const_iterator eitr = err->find(messageId);
|
||||
if (eitr != err->end() && value != 1) { //like I want to clear this state when the download is started anew
|
||||
err->erase(eitr);
|
||||
roles.push_back(MessageRoles::Attach);
|
||||
}
|
||||
|
||||
Progress::iterator itr = pr->find(messageId);
|
||||
if (itr != pr->end()) {
|
||||
itr->second = value;
|
||||
QModelIndex ind = modelIndexById(messageId);
|
||||
emit dataChanged(ind, ind, roles);
|
||||
emit dataChanged(ind, ind); //the type of the attach didn't change, so, there is no need to relayout, there is no role in event
|
||||
}
|
||||
}
|
||||
|
||||
@ -593,29 +505,7 @@ void Models::MessageFeed::fileComplete(const QString& messageId, bool up)
|
||||
|
||||
void Models::MessageFeed::fileError(const QString& messageId, const QString& error, bool up)
|
||||
{
|
||||
Err* failed;
|
||||
Progress* loads;
|
||||
if (up) {
|
||||
failed = &failedUploads;
|
||||
loads = &uploads;
|
||||
} else {
|
||||
failed = &failedDownloads;
|
||||
loads = &downloads;
|
||||
}
|
||||
|
||||
Progress::iterator pitr = loads->find(messageId);
|
||||
if (pitr != loads->end()) {
|
||||
loads->erase(pitr);
|
||||
}
|
||||
|
||||
std::pair<Err::iterator, bool> pair = failed->insert(std::make_pair(messageId, error));
|
||||
if (!pair.second) {
|
||||
pair.first->second = error;
|
||||
}
|
||||
QModelIndex ind = modelIndexById(messageId);
|
||||
if (ind.isValid()) {
|
||||
emit dataChanged(ind, ind, {MessageRoles::Attach});
|
||||
}
|
||||
//TODO
|
||||
}
|
||||
|
||||
void Models::MessageFeed::incrementObservers()
|
||||
@ -642,23 +532,21 @@ QModelIndex Models::MessageFeed::modelIndexById(const QString& id) const
|
||||
|
||||
QModelIndex Models::MessageFeed::modelIndexByTime(const QString& id, const QDateTime& time) const
|
||||
{
|
||||
if (indexByTime.size() > 0) {
|
||||
StorageByTime::const_iterator tItr = indexByTime.lower_bound(time);
|
||||
StorageByTime::const_iterator tEnd = indexByTime.upper_bound(time);
|
||||
StorageByTime::const_iterator tItr = indexByTime.upper_bound(time);
|
||||
StorageByTime::const_iterator tBeg = indexByTime.begin();
|
||||
bool found = false;
|
||||
while (tItr != tEnd) {
|
||||
while (tItr != tBeg) {
|
||||
if (id == (*tItr)->getId()) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
++tItr;
|
||||
--tItr;
|
||||
}
|
||||
|
||||
if (found) {
|
||||
if (found || id == (*tItr)->getId()) {
|
||||
int position = indexByTime.rank(tItr);
|
||||
return createIndex(position, 0, *tItr);
|
||||
}
|
||||
}
|
||||
|
||||
return QModelIndex();
|
||||
}
|
||||
@ -675,7 +563,7 @@ void Models::MessageFeed::reportLocalPathInvalid(const QString& messageId)
|
||||
|
||||
emit localPathInvalid(msg->getAttachPath());
|
||||
|
||||
//gonna change the message in current model right away, to prevent spam on each attempt to draw element
|
||||
//gonna change the message in current model right away, to prevent spam on each attemt to draw element
|
||||
QModelIndex index = modelIndexByTime(messageId, msg->getTime());
|
||||
msg->setAttachPath("");
|
||||
|
||||
@ -686,13 +574,3 @@ Models::MessageFeed::SyncState Models::MessageFeed::getSyncState() const
|
||||
{
|
||||
return syncState;
|
||||
}
|
||||
|
||||
void Models::MessageFeed::requestLatestMessages()
|
||||
{
|
||||
if (syncState != syncing) {
|
||||
syncState = syncing;
|
||||
emit syncStateChange(syncState);
|
||||
|
||||
emit requestArchive("");
|
||||
}
|
||||
}
|
@ -32,13 +32,11 @@
|
||||
|
||||
#include <shared/message.h>
|
||||
#include <shared/icons.h>
|
||||
#include <shared/exception.h>
|
||||
|
||||
|
||||
namespace Models {
|
||||
class Element;
|
||||
struct Attachment;
|
||||
struct Edition;
|
||||
|
||||
class MessageFeed : public QAbstractListModel
|
||||
{
|
||||
@ -56,9 +54,6 @@ public:
|
||||
void addMessage(const Shared::Message& msg);
|
||||
void changeMessage(const QString& id, const QMap<QString, QVariant>& data);
|
||||
void removeMessage(const QString& id);
|
||||
Shared::Message getMessage(const QString& id);
|
||||
QModelIndex modelIndexById(const QString& id) const;
|
||||
QModelIndex modelIndexByTime(const QString& id, const QDateTime& time) const;
|
||||
|
||||
QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const override;
|
||||
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
|
||||
@ -70,11 +65,11 @@ public:
|
||||
|
||||
void responseArchive(const std::list<Shared::Message> list, bool last);
|
||||
void downloadAttachment(const QString& messageId);
|
||||
void uploadAttachment(const QString& messageId);
|
||||
bool registerUpload(const QString& messageId);
|
||||
void reportLocalPathInvalid(const QString& messageId);
|
||||
|
||||
unsigned int unreadMessagesCount() const;
|
||||
bool markMessageAsRead(const QString& id) const;
|
||||
void fileProgress(const QString& messageId, qreal value, bool up);
|
||||
void fileError(const QString& messageId, const QString& error, bool up);
|
||||
void fileComplete(const QString& messageId, bool up);
|
||||
@ -83,12 +78,11 @@ public:
|
||||
void decrementObservers();
|
||||
SyncState getSyncState() const;
|
||||
|
||||
void requestLatestMessages(); //this method is used by Models::Contact to request latest messages after reconnection
|
||||
|
||||
signals:
|
||||
void requestArchive(const QString& before);
|
||||
void requestStateChange(bool requesting);
|
||||
void fileDownloadRequest(const QString& url);
|
||||
void unreadMessagesCountChanged() const;
|
||||
void unreadMessagesCountChanged();
|
||||
void newMessage(const Shared::Message& msg);
|
||||
void unnoticedMessage(const Shared::Message& msg);
|
||||
void localPathInvalid(const QString& path);
|
||||
@ -109,25 +103,11 @@ public:
|
||||
Bulk
|
||||
};
|
||||
|
||||
class NotFound:
|
||||
public Utils::Exception
|
||||
{
|
||||
public:
|
||||
NotFound(const std::string& k, const std::string& j, const std::string& acc):Exception(), key(k), jid(j), account(acc){}
|
||||
|
||||
std::string getMessage() const {
|
||||
return "Message with id " + key + " wasn't found in messageFeed " + account + " of the chat with " + jid;
|
||||
}
|
||||
private:
|
||||
std::string key;
|
||||
std::string jid;
|
||||
std::string account;
|
||||
};
|
||||
|
||||
protected:
|
||||
bool sentByMe(const Shared::Message& msg) const;
|
||||
Attachment fillAttach(const Shared::Message& msg) const;
|
||||
Edition fillCorrection(const Shared::Message& msg) const;
|
||||
QModelIndex modelIndexById(const QString& id) const;
|
||||
QModelIndex modelIndexByTime(const QString& id, const QDateTime& time) const;
|
||||
std::set<MessageRoles> detectChanges(const Shared::Message& msg, const QMap<QString, QVariant>& data) const;
|
||||
|
||||
private:
|
||||
@ -168,16 +148,12 @@ private:
|
||||
SyncState syncState;
|
||||
|
||||
typedef std::map<QString, qreal> Progress;
|
||||
typedef std::map<QString, QString> Err;
|
||||
Progress uploads;
|
||||
Progress downloads;
|
||||
Err failedDownloads;
|
||||
Err failedUploads;
|
||||
|
||||
std::set<QString>* unreadMessages;
|
||||
uint16_t observersAmount;
|
||||
|
||||
|
||||
static const QHash<int, QByteArray> roles;
|
||||
};
|
||||
|
||||
@ -197,13 +173,6 @@ struct Attachment {
|
||||
qreal progress;
|
||||
QString localPath;
|
||||
QString remotePath;
|
||||
QString error;
|
||||
};
|
||||
|
||||
struct Edition {
|
||||
bool corrected;
|
||||
QString original;
|
||||
QDateTime lastCorrection;
|
||||
};
|
||||
|
||||
struct FeedItem {
|
||||
@ -213,7 +182,7 @@ struct FeedItem {
|
||||
QString avatar;
|
||||
QString error;
|
||||
bool sentByMe;
|
||||
Edition correction;
|
||||
bool correction;
|
||||
QDateTime date;
|
||||
Shared::Message::State state;
|
||||
Attachment attach;
|
||||
@ -221,7 +190,6 @@ struct FeedItem {
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE(Models::Attachment);
|
||||
Q_DECLARE_METATYPE(Models::Edition);
|
||||
Q_DECLARE_METATYPE(Models::FeedItem);
|
||||
|
||||
#endif // MESSAGEFEED_H
|
@ -104,8 +104,6 @@ void Models::Reference::onChildChanged(Models::Item* item, int row, int col)
|
||||
{
|
||||
if (item == original) {
|
||||
emit childChanged(this, row, col);
|
||||
} else {
|
||||
emit childChanged(item, row, col);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -264,16 +264,6 @@ 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)
|
||||
{
|
||||
Participant* part = itr->second;
|
||||
|
@ -58,7 +58,6 @@ public:
|
||||
void addParticipant(const QString& name, const QMap<QString, QVariant>& data);
|
||||
void changeParticipant(const QString& name, const QMap<QString, QVariant>& data);
|
||||
void removeParticipant(const QString& name);
|
||||
Participant* getParticipant(const QString& name);
|
||||
|
||||
void toOfflineState() override;
|
||||
QString getDisplayedName() const override;
|
||||
|
@ -48,7 +48,6 @@ Models::Roster::~Roster()
|
||||
void Models::Roster::addAccount(const QMap<QString, QVariant>& data)
|
||||
{
|
||||
Account* acc = new Account(data);
|
||||
connect(acc, &Account::reconnected, this, &Roster::onAccountReconnected);
|
||||
root->appendChild(acc);
|
||||
accounts.insert(std::make_pair(acc->getName(), acc));
|
||||
accountsModel->addAccount(acc);
|
||||
@ -276,18 +275,6 @@ QVariant Models::Roster::data (const QModelIndex& index, int role) const
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case Qt::ForegroundRole:
|
||||
switch (item->type) {
|
||||
case Item::account: {
|
||||
Account* acc = static_cast<Account*>(item);
|
||||
if (!acc->getActive()) {
|
||||
result = qApp->palette().brush(QPalette::Disabled, QPalette::Text);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@ -463,7 +450,6 @@ void Models::Roster::addContact(const QString& account, const QString& jid, cons
|
||||
connect(contact, &Contact::fileDownloadRequest, this, &Roster::fileDownloadRequest);
|
||||
connect(contact, &Contact::unnoticedMessage, this, &Roster::unnoticedMessage);
|
||||
connect(contact, &Contact::localPathInvalid, this, &Roster::localPathInvalid);
|
||||
connect(contact, &Contact::unreadMessagesCountChanged, this, &Roster::recalculateUnreadMessages);
|
||||
contacts.insert(std::make_pair(id, contact));
|
||||
} else {
|
||||
contact = itr->second;
|
||||
@ -549,8 +535,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)
|
||||
{
|
||||
Element* el = getElement(ElId(account, jid));
|
||||
if (el != nullptr) {
|
||||
Element* el = getElement({account, jid});
|
||||
if (el != NULL) {
|
||||
for (QMap<QString, QVariant>::const_iterator itr = data.begin(), end = data.end(); itr != end; ++itr) {
|
||||
el->update(itr.key(), itr.value());
|
||||
}
|
||||
@ -559,11 +545,9 @@ 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)
|
||||
{
|
||||
Element* el = getElement(ElId(account, jid));
|
||||
if (el != nullptr) {
|
||||
Element* el = getElement({account, jid});
|
||||
if (el != NULL) {
|
||||
el->changeMessage(id, data);
|
||||
} else {
|
||||
qDebug() << "A request to change a message of the contact " << jid << " in the account " << account << " but it wasn't found";
|
||||
}
|
||||
}
|
||||
|
||||
@ -707,8 +691,8 @@ void Models::Roster::removePresence(const QString& account, const QString& jid,
|
||||
|
||||
void Models::Roster::addMessage(const QString& account, const Shared::Message& data)
|
||||
{
|
||||
Element* el = getElement(ElId(account, data.getPenPalJid()));
|
||||
if (el != nullptr) {
|
||||
Element* el = getElement({account, data.getPenPalJid()});
|
||||
if (el != NULL) {
|
||||
el->addMessage(data);
|
||||
}
|
||||
}
|
||||
@ -760,11 +744,10 @@ void Models::Roster::removeAccount(const QString& account)
|
||||
}
|
||||
}
|
||||
|
||||
disconnect(acc, &Account::reconnected, this, &Roster::onAccountReconnected);
|
||||
acc->deleteLater();
|
||||
}
|
||||
|
||||
QString Models::Roster::getContactName(const QString& account, const QString& jid) const
|
||||
QString Models::Roster::getContactName(const QString& account, const QString& jid)
|
||||
{
|
||||
ElId id(account, jid);
|
||||
std::map<ElId, Contact*>::const_iterator cItr = contacts.find(id);
|
||||
@ -802,11 +785,10 @@ void Models::Roster::addRoom(const QString& account, const QString jid, const QM
|
||||
}
|
||||
|
||||
Room* room = new Room(acc, jid, data);
|
||||
connect(room, &Room::requestArchive, this, &Roster::onElementRequestArchive);
|
||||
connect(room, &Room::fileDownloadRequest, this, &Roster::fileDownloadRequest);
|
||||
connect(room, &Room::unnoticedMessage, this, &Roster::unnoticedMessage);
|
||||
connect(room, &Room::localPathInvalid, this, &Roster::localPathInvalid);
|
||||
connect(room, &Room::unreadMessagesCountChanged, this, &Roster::recalculateUnreadMessages);
|
||||
connect(room, &Contact::requestArchive, this, &Roster::onElementRequestArchive);
|
||||
connect(room, &Contact::fileDownloadRequest, this, &Roster::fileDownloadRequest);
|
||||
connect(room, &Contact::unnoticedMessage, this, &Roster::unnoticedMessage);
|
||||
connect(room, &Contact::localPathInvalid, this, &Roster::localPathInvalid);
|
||||
rooms.insert(std::make_pair(id, room));
|
||||
acc->appendChild(room);
|
||||
}
|
||||
@ -909,7 +891,7 @@ bool Models::Roster::groupHasContact(const QString& account, const QString& grou
|
||||
}
|
||||
}
|
||||
|
||||
QString Models::Roster::getContactIconPath(const QString& account, const QString& jid, const QString& resource) const
|
||||
QString Models::Roster::getContactIconPath(const QString& account, const QString& jid, const QString& resource)
|
||||
{
|
||||
ElId id(account, jid);
|
||||
std::map<ElId, Contact*>::const_iterator cItr = contacts.find(id);
|
||||
@ -929,36 +911,9 @@ QString Models::Roster::getContactIconPath(const QString& account, const QString
|
||||
return path;
|
||||
}
|
||||
|
||||
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
|
||||
Models::Account * Models::Roster::getAccount(const QString& name)
|
||||
{
|
||||
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;
|
||||
}
|
||||
return accounts.find(name)->second;
|
||||
}
|
||||
|
||||
QModelIndex Models::Roster::getAccountIndex(const QString& name)
|
||||
@ -977,7 +932,7 @@ QModelIndex Models::Roster::getGroupIndex(const QString& account, const QString&
|
||||
if (itr == accounts.end()) {
|
||||
return QModelIndex();
|
||||
} else {
|
||||
std::map<ElId, Group*>::const_iterator gItr = groups.find(ElId(account, name));
|
||||
std::map<ElId, Group*>::const_iterator gItr = groups.find({account, name});
|
||||
if (gItr == groups.end()) {
|
||||
return QModelIndex();
|
||||
} else {
|
||||
@ -987,48 +942,6 @@ 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)
|
||||
{
|
||||
Element* el = static_cast<Element*>(sender());
|
||||
@ -1039,7 +952,7 @@ void Models::Roster::responseArchive(const QString& account, const QString& jid,
|
||||
{
|
||||
ElId id(account, jid);
|
||||
Element* el = getElement(id);
|
||||
if (el != nullptr) {
|
||||
if (el != NULL) {
|
||||
el->responseArchive(list, last);
|
||||
}
|
||||
}
|
||||
@ -1047,8 +960,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)
|
||||
{
|
||||
for (const Shared::MessageInfo& info : msgs) {
|
||||
Element* el = getElement(ElId(info.account, info.jid));
|
||||
if (el != nullptr) {
|
||||
Element* el = getElement({info.account, info.jid});
|
||||
if (el != NULL) {
|
||||
el->fileProgress(info.messageId, value, up);
|
||||
}
|
||||
}
|
||||
@ -1057,8 +970,8 @@ void Models::Roster::fileProgress(const std::list<Shared::MessageInfo>& msgs, qr
|
||||
void Models::Roster::fileComplete(const std::list<Shared::MessageInfo>& msgs, bool up)
|
||||
{
|
||||
for (const Shared::MessageInfo& info : msgs) {
|
||||
Element* el = getElement(ElId(info.account, info.jid));
|
||||
if (el != nullptr) {
|
||||
Element* el = getElement({info.account, info.jid});
|
||||
if (el != NULL) {
|
||||
el->fileComplete(info.messageId, up);
|
||||
}
|
||||
}
|
||||
@ -1067,8 +980,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)
|
||||
{
|
||||
for (const Shared::MessageInfo& info : msgs) {
|
||||
Element* el = getElement(ElId(info.account, info.jid));
|
||||
if (el != nullptr) {
|
||||
Element* el = getElement({info.account, info.jid});
|
||||
if (el != NULL) {
|
||||
el->fileError(info.messageId, err, up);
|
||||
}
|
||||
}
|
||||
@ -1076,40 +989,17 @@ void Models::Roster::fileError(const std::list<Shared::MessageInfo>& msgs, const
|
||||
|
||||
Models::Element * Models::Roster::getElement(const Models::Roster::ElId& id)
|
||||
{
|
||||
return const_cast<Models::Element*>(getElementConst(id));
|
||||
std::map<ElId, Contact*>::iterator cItr = contacts.find(id);
|
||||
|
||||
if (cItr != contacts.end()) {
|
||||
return cItr->second;
|
||||
} else {
|
||||
std::map<ElId, Room*>::iterator rItr = rooms.find(id);
|
||||
if (rItr != rooms.end()) {
|
||||
return rItr->second;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Models::Item::Type Models::Roster::getContactType(const Models::Roster::ElId& id) const
|
||||
{
|
||||
const Models::Element* el = getElementConst(id);
|
||||
if (el == nullptr) {
|
||||
return Item::root;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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,7 +46,6 @@ public:
|
||||
Roster(QObject* parent = 0);
|
||||
~Roster();
|
||||
|
||||
public slots:
|
||||
void addAccount(const QMap<QString, QVariant> &data);
|
||||
void updateAccount(const QString& account, const QString& field, const QVariant& value);
|
||||
void removeAccount(const QString& account);
|
||||
@ -66,12 +65,7 @@ public slots:
|
||||
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);
|
||||
|
||||
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);
|
||||
QString getContactName(const QString& account, const QString& jid);
|
||||
|
||||
QVariant data ( const QModelIndex& index, int role ) const override;
|
||||
Qt::ItemFlags flags(const QModelIndex &index) const override;
|
||||
@ -83,13 +77,10 @@ public:
|
||||
|
||||
std::deque<QString> groupList(const QString& account) const;
|
||||
bool groupHasContact(const QString& account, const QString& group, const QString& contactJID) const;
|
||||
QString getContactIconPath(const QString& account, const QString& jid, const QString& resource) const;
|
||||
QString getContactIconPath(const QString& account, const QString& jid, const QString& resource);
|
||||
Account* getAccount(const QString& name);
|
||||
const Account* getAccountConst(const QString& name) const;
|
||||
QModelIndex getAccountIndex(const QString& name);
|
||||
QModelIndex getGroupIndex(const QString& account, const QString& name);
|
||||
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 fileProgress(const std::list<Shared::MessageInfo>& msgs, qreal value, bool up);
|
||||
@ -101,13 +92,14 @@ public:
|
||||
signals:
|
||||
void requestArchive(const QString& account, const QString& jid, const QString& before);
|
||||
void fileDownloadRequest(const QString& url);
|
||||
void unreadMessagesCountChanged(int count);
|
||||
void unnoticedMessage(const QString& account, const Shared::Message& msg);
|
||||
void localPathInvalid(const QString& path);
|
||||
|
||||
private:
|
||||
Element* getElement(const ElId& id);
|
||||
|
||||
private slots:
|
||||
void onAccountDataChanged(const QModelIndex& tl, const QModelIndex& br, const QVector<int>& roles);
|
||||
void onAccountReconnected();
|
||||
void onChildChanged(Models::Item* item, int row, int col);
|
||||
void onChildIsAboutToBeInserted(Item* parent, int first, int last);
|
||||
void onChildInserted();
|
||||
@ -116,7 +108,6 @@ private slots:
|
||||
void onChildIsAboutToBeMoved(Item* source, int first, int last, Item* destination, int newIndex);
|
||||
void onChildMoved();
|
||||
void onElementRequestArchive(const QString& before);
|
||||
void recalculateUnreadMessages();
|
||||
|
||||
private:
|
||||
Item* root;
|
||||
|
633
ui/squawk.cpp
633
ui/squawk.cpp
@ -21,27 +21,25 @@
|
||||
#include <QDebug>
|
||||
#include <QIcon>
|
||||
|
||||
Squawk::Squawk(Models::Roster& p_rosterModel, QWidget *parent) :
|
||||
Squawk::Squawk(QWidget *parent) :
|
||||
QMainWindow(parent),
|
||||
m_ui(new Ui::Squawk),
|
||||
accounts(nullptr),
|
||||
preferences(nullptr),
|
||||
about(nullptr),
|
||||
rosterModel(p_rosterModel),
|
||||
accounts(0),
|
||||
rosterModel(),
|
||||
conversations(),
|
||||
contextMenu(new QMenu()),
|
||||
dbus("org.freedesktop.Notifications", "/org/freedesktop/Notifications", "org.freedesktop.Notifications", QDBusConnection::sessionBus()),
|
||||
vCards(),
|
||||
currentConversation(nullptr),
|
||||
requestedAccountsForPasswords(),
|
||||
prompt(0),
|
||||
currentConversation(0),
|
||||
restoreSelection(),
|
||||
needToRestore(false)
|
||||
{
|
||||
m_ui->setupUi(this);
|
||||
m_ui->roster->setModel(&rosterModel);
|
||||
m_ui->roster->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
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->setColumnWidth(1, 30);
|
||||
m_ui->roster->setIconSize(QSize(20, 20));
|
||||
m_ui->roster->header()->setStretchLastSection(false);
|
||||
m_ui->roster->header()->setSectionResizeMode(0, QHeaderView::Stretch);
|
||||
@ -53,7 +51,6 @@ Squawk::Squawk(Models::Roster& p_rosterModel, QWidget *parent) :
|
||||
m_ui->comboBox->setCurrentIndex(static_cast<int>(Shared::Availability::offline));
|
||||
|
||||
connect(m_ui->actionAccounts, &QAction::triggered, this, &Squawk::onAccounts);
|
||||
connect(m_ui->actionPreferences, &QAction::triggered, this, &Squawk::onPreferences);
|
||||
connect(m_ui->actionAddContact, &QAction::triggered, this, &Squawk::onNewContact);
|
||||
connect(m_ui->actionAddConference, &QAction::triggered, this, &Squawk::onNewConference);
|
||||
connect(m_ui->actionQuit, &QAction::triggered, this, &Squawk::close);
|
||||
@ -62,10 +59,13 @@ Squawk::Squawk(Models::Roster& p_rosterModel, QWidget *parent) :
|
||||
connect(m_ui->roster, &QTreeView::customContextMenuRequested, this, &Squawk::onRosterContextMenu);
|
||||
connect(m_ui->roster, &QTreeView::collapsed, this, &Squawk::onItemCollepsed);
|
||||
connect(m_ui->roster->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &Squawk::onRosterSelectionChanged);
|
||||
connect(&rosterModel, &Models::Roster::unnoticedMessage, this, &Squawk::onUnnoticedMessage);
|
||||
|
||||
connect(rosterModel.accountsModel, &Models::Accounts::sizeChanged, this, &Squawk::onAccountsSizeChanged);
|
||||
connect(&rosterModel, &Models::Roster::requestArchive, this, &Squawk::onRequestArchive);
|
||||
connect(&rosterModel, &Models::Roster::fileDownloadRequest, this, &Squawk::fileDownloadRequest);
|
||||
connect(&rosterModel, &Models::Roster::localPathInvalid, this, &Squawk::localPathInvalid);
|
||||
connect(contextMenu, &QMenu::aboutToHide, this, &Squawk::onContextAboutToHide);
|
||||
connect(m_ui->actionAboutSquawk, &QAction::triggered, this, &Squawk::onAboutSquawkCalled);
|
||||
//m_ui->mainToolBar->addWidget(m_ui->comboBox);
|
||||
|
||||
if (testAttribute(Qt::WA_TranslucentBackground)) {
|
||||
@ -95,8 +95,8 @@ Squawk::~Squawk() {
|
||||
|
||||
void Squawk::onAccounts()
|
||||
{
|
||||
if (accounts == nullptr) {
|
||||
accounts = new Accounts(rosterModel.accountsModel, this);
|
||||
if (accounts == 0) {
|
||||
accounts = new Accounts(rosterModel.accountsModel);
|
||||
accounts->setAttribute(Qt::WA_DeleteOnClose);
|
||||
connect(accounts, &Accounts::destroyed, this, &Squawk::onAccountsClosed);
|
||||
connect(accounts, &Accounts::newAccount, this, &Squawk::newAccountRequest);
|
||||
@ -104,26 +104,15 @@ void Squawk::onAccounts()
|
||||
connect(accounts, &Accounts::connectAccount, this, &Squawk::connectAccount);
|
||||
connect(accounts, &Accounts::disconnectAccount, this, &Squawk::disconnectAccount);
|
||||
connect(accounts, &Accounts::removeAccount, this, &Squawk::removeAccountRequest);
|
||||
}
|
||||
|
||||
accounts->show();
|
||||
} else {
|
||||
accounts->show();
|
||||
accounts->raise();
|
||||
accounts->activateWindow();
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
if (size > 0) {
|
||||
@ -177,47 +166,143 @@ void Squawk::onJoinConferenceAccepted()
|
||||
|
||||
void Squawk::closeEvent(QCloseEvent* event)
|
||||
{
|
||||
if (accounts != nullptr) {
|
||||
if (accounts != 0) {
|
||||
accounts->close();
|
||||
}
|
||||
if (preferences != nullptr) {
|
||||
preferences->close();
|
||||
}
|
||||
if (about != nullptr) {
|
||||
about->close();
|
||||
|
||||
for (Conversations::const_iterator itr = conversations.begin(), end = conversations.end(); itr != end; ++itr) {
|
||||
disconnect(itr->second, &Conversation::destroyed, this, &Squawk::onConversationClosed);
|
||||
itr->second->close();
|
||||
}
|
||||
conversations.clear();
|
||||
|
||||
for (std::map<QString, VCard*>::const_iterator itr = vCards.begin(), end = vCards.end(); itr != end; ++itr) {
|
||||
disconnect(itr->second, &VCard::destroyed, this, &Squawk::onVCardClosed);
|
||||
itr->second->close();
|
||||
}
|
||||
vCards.clear();
|
||||
writeSettings();
|
||||
emit closing();;
|
||||
|
||||
QMainWindow::closeEvent(event);
|
||||
}
|
||||
|
||||
void Squawk::onAccountsClosed() {
|
||||
accounts = nullptr;}
|
||||
|
||||
void Squawk::onPreferencesClosed() {
|
||||
preferences = nullptr;}
|
||||
void Squawk::onAccountsClosed(QObject* parent)
|
||||
{
|
||||
accounts = 0;
|
||||
}
|
||||
|
||||
void Squawk::onAboutSquawkClosed() {
|
||||
about = nullptr;}
|
||||
void Squawk::newAccount(const QMap<QString, QVariant>& account)
|
||||
{
|
||||
rosterModel.addAccount(account);
|
||||
}
|
||||
|
||||
void Squawk::onComboboxActivated(int 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);
|
||||
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::expand(const QModelIndex& index) {
|
||||
m_ui->roster->expand(index);}
|
||||
void Squawk::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();
|
||||
rosterModel.updateAccount(account, attr, *itr);
|
||||
}
|
||||
}
|
||||
|
||||
void Squawk::stateChanged(Shared::Availability state) {
|
||||
m_ui->comboBox->setCurrentIndex(static_cast<int>(state));}
|
||||
void Squawk::addContact(const QString& account, const QString& jid, const QString& group, const QMap<QString, QVariant>& data)
|
||||
{
|
||||
rosterModel.addContact(account, jid, group, data);
|
||||
|
||||
QSettings settings;
|
||||
settings.beginGroup("ui");
|
||||
settings.beginGroup("roster");
|
||||
settings.beginGroup(account);
|
||||
if (settings.value("expanded", false).toBool()) {
|
||||
QModelIndex ind = rosterModel.getAccountIndex(account);
|
||||
m_ui->roster->expand(ind);
|
||||
}
|
||||
settings.endGroup();
|
||||
settings.endGroup();
|
||||
settings.endGroup();
|
||||
}
|
||||
|
||||
void Squawk::addGroup(const QString& account, const QString& name)
|
||||
{
|
||||
rosterModel.addGroup(account, name);
|
||||
|
||||
QSettings settings;
|
||||
settings.beginGroup("ui");
|
||||
settings.beginGroup("roster");
|
||||
settings.beginGroup(account);
|
||||
if (settings.value("expanded", false).toBool()) {
|
||||
QModelIndex ind = rosterModel.getAccountIndex(account);
|
||||
m_ui->roster->expand(ind);
|
||||
if (settings.value(name + "/expanded", false).toBool()) {
|
||||
m_ui->roster->expand(rosterModel.getGroupIndex(account, name));
|
||||
}
|
||||
}
|
||||
settings.endGroup();
|
||||
settings.endGroup();
|
||||
settings.endGroup();
|
||||
}
|
||||
|
||||
void Squawk::removeGroup(const QString& account, const QString& name)
|
||||
{
|
||||
rosterModel.removeGroup(account, name);
|
||||
}
|
||||
|
||||
void Squawk::changeContact(const QString& account, const QString& jid, const QMap<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)
|
||||
{
|
||||
@ -226,35 +311,194 @@ void Squawk::onRosterItemDoubleClicked(const QModelIndex& item)
|
||||
if (node->type == Models::Item::reference) {
|
||||
node = static_cast<Models::Reference*>(node)->dereference();
|
||||
}
|
||||
Models::Contact* contact = nullptr;
|
||||
Models::Room* room = nullptr;
|
||||
Models::Contact* contact = 0;
|
||||
Models::Room* room = 0;
|
||||
QString res;
|
||||
Models::Roster::ElId* id = 0;
|
||||
switch (node->type) {
|
||||
case Models::Item::contact:
|
||||
contact = static_cast<Models::Contact*>(node);
|
||||
emit openConversation(Models::Roster::ElId(contact->getAccountName(), contact->getJid()));
|
||||
id = new Models::Roster::ElId(contact->getAccountName(), contact->getJid());
|
||||
break;
|
||||
case Models::Item::presence:
|
||||
contact = static_cast<Models::Contact*>(node->parentItem());
|
||||
emit openConversation(Models::Roster::ElId(contact->getAccountName(), contact->getJid()), node->getName());
|
||||
id = new Models::Roster::ElId(contact->getAccountName(), contact->getJid());
|
||||
res = node->getName();
|
||||
break;
|
||||
case Models::Item::room:
|
||||
room = static_cast<Models::Room*>(node);
|
||||
emit openConversation(Models::Roster::ElId(room->getAccountName(), room->getJid()));
|
||||
id = new Models::Roster::ElId(room->getAccountName(), room->getJid());
|
||||
break;
|
||||
default:
|
||||
m_ui->roster->expand(item);
|
||||
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) {
|
||||
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::closeCurrentConversation()
|
||||
void Squawk::onConversationClosed(QObject* parent)
|
||||
{
|
||||
if (currentConversation != nullptr) {
|
||||
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 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 = nullptr;
|
||||
currentConversation = 0;
|
||||
m_ui->filler->show();
|
||||
}
|
||||
|
||||
rosterModel.removeAccount(account);
|
||||
}
|
||||
|
||||
void Squawk::onRosterContextMenu(const QPoint& point)
|
||||
@ -274,12 +518,17 @@ void Squawk::onRosterContextMenu(const QPoint& point)
|
||||
hasMenu = true;
|
||||
QString name = acc->getName();
|
||||
|
||||
if (acc->getActive()) {
|
||||
QAction* con = contextMenu->addAction(Shared::icon("network-disconnect"), tr("Deactivate"));
|
||||
connect(con, &QAction::triggered, std::bind(&Squawk::disconnectAccount, this, name));
|
||||
if (acc->getState() != Shared::ConnectionState::disconnected) {
|
||||
QAction* con = contextMenu->addAction(Shared::icon("network-disconnect"), tr("Disconnect"));
|
||||
con->setEnabled(active);
|
||||
connect(con, &QAction::triggered, [this, name]() {
|
||||
emit disconnectAccount(name);
|
||||
});
|
||||
} else {
|
||||
QAction* con = contextMenu->addAction(Shared::icon("network-connect"), tr("Activate"));
|
||||
connect(con, &QAction::triggered, std::bind(&Squawk::connectAccount, this, name));
|
||||
QAction* con = contextMenu->addAction(Shared::icon("network-connect"), tr("Connect"));
|
||||
connect(con, &QAction::triggered, [this, name]() {
|
||||
emit connectAccount(name);
|
||||
});
|
||||
}
|
||||
|
||||
QAction* card = contextMenu->addAction(Shared::icon("user-properties"), tr("VCard"));
|
||||
@ -287,18 +536,22 @@ void Squawk::onRosterContextMenu(const QPoint& point)
|
||||
connect(card, &QAction::triggered, std::bind(&Squawk::onActivateVCard, this, name, acc->getBareJid(), true));
|
||||
|
||||
QAction* remove = contextMenu->addAction(Shared::icon("edit-delete"), tr("Remove"));
|
||||
connect(remove, &QAction::triggered, std::bind(&Squawk::removeAccountRequest, this, name));
|
||||
remove->setEnabled(active);
|
||||
connect(remove, &QAction::triggered, [this, name]() {
|
||||
emit removeAccount(name);
|
||||
});
|
||||
|
||||
}
|
||||
break;
|
||||
case Models::Item::contact: {
|
||||
Models::Contact* cnt = static_cast<Models::Contact*>(item);
|
||||
Models::Roster::ElId id(cnt->getAccountName(), cnt->getJid());
|
||||
QString cntName = cnt->getName();
|
||||
hasMenu = true;
|
||||
|
||||
QAction* dialog = contextMenu->addAction(Shared::icon("mail-message"), tr("Open dialog"));
|
||||
dialog->setEnabled(active);
|
||||
connect(dialog, &QAction::triggered, std::bind(&Squawk::onRosterItemDoubleClicked, this, index));
|
||||
connect(dialog, &QAction::triggered, [this, index]() {
|
||||
onRosterItemDoubleClicked(index);
|
||||
});
|
||||
|
||||
Shared::SubscriptionState state = cnt->getState();
|
||||
switch (state) {
|
||||
@ -306,7 +559,9 @@ void Squawk::onRosterContextMenu(const QPoint& point)
|
||||
case Shared::SubscriptionState::to: {
|
||||
QAction* unsub = contextMenu->addAction(Shared::icon("news-unsubscribe"), tr("Unsubscribe"));
|
||||
unsub->setEnabled(active);
|
||||
connect(unsub, &QAction::triggered, std::bind(&Squawk::changeSubscription, this, id, false));
|
||||
connect(unsub, &QAction::triggered, [this, cnt]() {
|
||||
emit unsubscribeContact(cnt->getAccountName(), cnt->getJid(), "");
|
||||
});
|
||||
}
|
||||
break;
|
||||
case Shared::SubscriptionState::from:
|
||||
@ -314,68 +569,75 @@ void Squawk::onRosterContextMenu(const QPoint& point)
|
||||
case Shared::SubscriptionState::none: {
|
||||
QAction* sub = contextMenu->addAction(Shared::icon("news-subscribe"), tr("Subscribe"));
|
||||
sub->setEnabled(active);
|
||||
connect(sub, &QAction::triggered, std::bind(&Squawk::changeSubscription, this, id, true));
|
||||
connect(sub, &QAction::triggered, [this, cnt]() {
|
||||
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"));
|
||||
rename->setEnabled(active);
|
||||
connect(rename, &QAction::triggered, [this, cntName, id]() {
|
||||
connect(rename, &QAction::triggered, [this, cntName, accName, cntJID]() {
|
||||
QInputDialog* dialog = new QInputDialog(this);
|
||||
connect(dialog, &QDialog::accepted, [this, dialog, cntName, id]() {
|
||||
connect(dialog, &QDialog::accepted, [this, dialog, cntName, accName, cntJID]() {
|
||||
QString newName = dialog->textValue();
|
||||
if (newName != cntName) {
|
||||
emit renameContactRequest(id.account, id.name, newName);
|
||||
emit renameContactRequest(accName, cntJID, newName);
|
||||
}
|
||||
dialog->deleteLater();
|
||||
});
|
||||
connect(dialog, &QDialog::rejected, dialog, &QObject::deleteLater);
|
||||
dialog->setInputMode(QInputDialog::TextInput);
|
||||
dialog->setLabelText(tr("Input new name for %1\nor leave it empty for the contact \nto be displayed as %1").arg(id.name));
|
||||
dialog->setWindowTitle(tr("Renaming %1").arg(id.name));
|
||||
dialog->setLabelText(tr("Input new name for %1\nor leave it empty for the contact \nto be displayed as %1").arg(cntJID));
|
||||
dialog->setWindowTitle(tr("Renaming %1").arg(cntJID));
|
||||
dialog->setTextValue(cntName);
|
||||
dialog->exec();
|
||||
});
|
||||
|
||||
|
||||
QMenu* groupsMenu = contextMenu->addMenu(Shared::icon("group"), tr("Groups"));
|
||||
std::deque<QString> groupList = rosterModel.groupList(id.account);
|
||||
std::deque<QString> groupList = rosterModel.groupList(accName);
|
||||
for (QString groupName : groupList) {
|
||||
QAction* gr = groupsMenu->addAction(groupName);
|
||||
gr->setCheckable(true);
|
||||
gr->setChecked(rosterModel.groupHasContact(id.account, groupName, id.name));
|
||||
gr->setChecked(rosterModel.groupHasContact(accName, groupName, cntJID));
|
||||
gr->setEnabled(active);
|
||||
connect(gr, &QAction::toggled, [this, groupName, id](bool checked) {
|
||||
connect(gr, &QAction::toggled, [this, accName, groupName, cntJID](bool checked) {
|
||||
if (checked) {
|
||||
emit addContactToGroupRequest(id.account, id.name, groupName);
|
||||
emit addContactToGroupRequest(accName, cntJID, groupName);
|
||||
} else {
|
||||
emit removeContactFromGroupRequest(id.account, id.name, groupName);
|
||||
emit removeContactFromGroupRequest(accName, cntJID, groupName);
|
||||
}
|
||||
});
|
||||
}
|
||||
QAction* newGroup = groupsMenu->addAction(Shared::icon("group-new"), tr("New group"));
|
||||
newGroup->setEnabled(active);
|
||||
connect(newGroup, &QAction::triggered, [this, id]() {
|
||||
connect(newGroup, &QAction::triggered, [this, accName, cntJID]() {
|
||||
QInputDialog* dialog = new QInputDialog(this);
|
||||
connect(dialog, &QDialog::accepted, [this, dialog, id]() {
|
||||
emit addContactToGroupRequest(id.account, id.name, dialog->textValue());
|
||||
connect(dialog, &QDialog::accepted, [this, dialog, accName, cntJID]() {
|
||||
emit addContactToGroupRequest(accName, cntJID, dialog->textValue());
|
||||
dialog->deleteLater();
|
||||
});
|
||||
connect(dialog, &QDialog::rejected, dialog, &QObject::deleteLater);
|
||||
dialog->setInputMode(QInputDialog::TextInput);
|
||||
dialog->setLabelText(tr("New group name"));
|
||||
dialog->setWindowTitle(tr("Add %1 to a new group").arg(id.name));
|
||||
dialog->setWindowTitle(tr("Add %1 to a new group").arg(cntJID));
|
||||
dialog->exec();
|
||||
});
|
||||
|
||||
|
||||
QAction* card = contextMenu->addAction(Shared::icon("user-properties"), tr("VCard"));
|
||||
card->setEnabled(active);
|
||||
connect(card, &QAction::triggered, std::bind(&Squawk::onActivateVCard, this, id.account, id.name, false));
|
||||
connect(card, &QAction::triggered, std::bind(&Squawk::onActivateVCard, this, accName, cnt->getJid(), false));
|
||||
|
||||
QAction* remove = contextMenu->addAction(Shared::icon("edit-delete"), tr("Remove"));
|
||||
remove->setEnabled(active);
|
||||
connect(remove, &QAction::triggered, std::bind(&Squawk::removeContactRequest, this, id.account, id.name));
|
||||
connect(remove, &QAction::triggered, [this, cnt]() {
|
||||
emit removeContactRequest(cnt->getAccountName(), cnt->getJid());
|
||||
});
|
||||
|
||||
}
|
||||
break;
|
||||
@ -394,16 +656,32 @@ void Squawk::onRosterContextMenu(const QPoint& point)
|
||||
if (room->getAutoJoin()) {
|
||||
QAction* unsub = contextMenu->addAction(Shared::icon("news-unsubscribe"), tr("Unsubscribe"));
|
||||
unsub->setEnabled(active);
|
||||
connect(unsub, &QAction::triggered, std::bind(&Squawk::changeSubscription, this, id, false));
|
||||
connect(unsub, &QAction::triggered, [this, id]() {
|
||||
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 {
|
||||
QAction* sub = contextMenu->addAction(Shared::icon("news-subscribe"), tr("Subscribe"));
|
||||
sub->setEnabled(active);
|
||||
connect(sub, &QAction::triggered, std::bind(&Squawk::changeSubscription, this, id, true));
|
||||
QAction* unsub = contextMenu->addAction(Shared::icon("news-subscribe"), tr("Subscribe"));
|
||||
unsub->setEnabled(active);
|
||||
connect(unsub, &QAction::triggered, [this, id]() {
|
||||
emit setRoomAutoJoin(id.account, id.name, true);
|
||||
if (conversations.find(id) == conversations.end()
|
||||
&& (currentConversation == 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"));
|
||||
remove->setEnabled(active);
|
||||
connect(remove, &QAction::triggered, std::bind(&Squawk::removeRoomRequest, this, id.account, id.name));
|
||||
connect(remove, &QAction::triggered, [this, id]() {
|
||||
emit removeRoomRequest(id.account, id.name);
|
||||
});
|
||||
}
|
||||
break;
|
||||
default:
|
||||
@ -415,6 +693,36 @@ 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)
|
||||
{
|
||||
std::map<QString, VCard*>::const_iterator itr = vCards.find(jid);
|
||||
@ -472,6 +780,26 @@ void Squawk::onVCardSave(const Shared::VCard& card, const QString& account)
|
||||
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()
|
||||
{
|
||||
QSettings settings;
|
||||
@ -482,9 +810,21 @@ void Squawk::writeSettings()
|
||||
settings.endGroup();
|
||||
|
||||
settings.setValue("splitter", m_ui->splitter->saveState());
|
||||
|
||||
settings.setValue("availability", m_ui->comboBox->currentIndex());
|
||||
settings.beginWriteArray("connectedAccounts");
|
||||
int size = rosterModel.accountsModel->rowCount(QModelIndex());
|
||||
for (int i = 0; i < size; ++i) {
|
||||
Models::Account* acc = rosterModel.accountsModel->getAccount(i);
|
||||
if (acc->getState() != Shared::ConnectionState::disconnected) {
|
||||
settings.setArrayIndex(i);
|
||||
settings.setValue("name", acc->getName());
|
||||
}
|
||||
}
|
||||
settings.endArray();
|
||||
|
||||
settings.remove("roster");
|
||||
settings.beginGroup("roster");
|
||||
int size = rosterModel.accountsModel->rowCount(QModelIndex());
|
||||
for (int i = 0; i < size; ++i) {
|
||||
QModelIndex acc = rosterModel.index(i, 0, QModelIndex());
|
||||
Models::Account* account = rosterModel.accountsModel->getAccount(i);
|
||||
@ -504,8 +844,6 @@ void Squawk::writeSettings()
|
||||
}
|
||||
settings.endGroup();
|
||||
settings.endGroup();
|
||||
|
||||
settings.sync();
|
||||
}
|
||||
|
||||
void Squawk::onItemCollepsed(const QModelIndex& index)
|
||||
@ -527,6 +865,58 @@ 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)
|
||||
{
|
||||
if (restoreSelection.isValid() && restoreSelection == current) {
|
||||
@ -539,10 +929,10 @@ void Squawk::onRosterSelectionChanged(const QModelIndex& current, const QModelIn
|
||||
if (node->type == Models::Item::reference) {
|
||||
node = static_cast<Models::Reference*>(node)->dereference();
|
||||
}
|
||||
Models::Contact* contact = nullptr;
|
||||
Models::Room* room = nullptr;
|
||||
Models::Contact* contact = 0;
|
||||
Models::Room* room = 0;
|
||||
QString res;
|
||||
Models::Roster::ElId* id = nullptr;
|
||||
Models::Roster::ElId* id = 0;
|
||||
bool hasContext = true;
|
||||
switch (node->type) {
|
||||
case Models::Item::contact:
|
||||
@ -571,7 +961,7 @@ void Squawk::onRosterSelectionChanged(const QModelIndex& current, const QModelIn
|
||||
}
|
||||
|
||||
if (hasContext && QGuiApplication::mouseButtons() & Qt::RightButton) {
|
||||
if (id != nullptr) {
|
||||
if (id != 0) {
|
||||
delete id;
|
||||
}
|
||||
needToRestore = true;
|
||||
@ -579,10 +969,10 @@ void Squawk::onRosterSelectionChanged(const QModelIndex& current, const QModelIn
|
||||
return;
|
||||
}
|
||||
|
||||
if (id != nullptr) {
|
||||
if (currentConversation != nullptr) {
|
||||
if (id != 0) {
|
||||
if (currentConversation != 0) {
|
||||
if (currentConversation->getId() == *id) {
|
||||
if (contact != nullptr) {
|
||||
if (contact != 0) {
|
||||
currentConversation->setPalResource(res);
|
||||
}
|
||||
return;
|
||||
@ -594,16 +984,20 @@ void Squawk::onRosterSelectionChanged(const QModelIndex& current, const QModelIn
|
||||
}
|
||||
|
||||
Models::Account* acc = rosterModel.getAccount(id->account);
|
||||
if (contact != nullptr) {
|
||||
if (contact != 0) {
|
||||
currentConversation = new Chat(acc, contact);
|
||||
} else if (room != nullptr) {
|
||||
} else if (room != 0) {
|
||||
currentConversation = new Room(acc, room);
|
||||
|
||||
if (!room->getJoined()) {
|
||||
emit setRoomJoined(id->account, id->name, true);
|
||||
}
|
||||
}
|
||||
if (!testAttribute(Qt::WA_TranslucentBackground)) {
|
||||
currentConversation->setFeedFrames(true, false, true, true);
|
||||
}
|
||||
|
||||
emit openedConversation();
|
||||
subscribeConversation(currentConversation);
|
||||
|
||||
if (res.size() > 0) {
|
||||
currentConversation->setPalResource(res);
|
||||
@ -613,10 +1007,18 @@ void Squawk::onRosterSelectionChanged(const QModelIndex& current, const QModelIn
|
||||
|
||||
delete id;
|
||||
} else {
|
||||
closeCurrentConversation();
|
||||
if (currentConversation != 0) {
|
||||
currentConversation->deleteLater();
|
||||
currentConversation = 0;
|
||||
m_ui->filler->show();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
closeCurrentConversation();
|
||||
if (currentConversation != 0) {
|
||||
currentConversation->deleteLater();
|
||||
currentConversation = 0;
|
||||
m_ui->filler->show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -627,30 +1029,3 @@ void Squawk::onContextAboutToHide()
|
||||
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,6 +22,7 @@
|
||||
#include <QMainWindow>
|
||||
#include <QScopedPointer>
|
||||
#include <QCloseEvent>
|
||||
#include <QtDBus/QDBusInterface>
|
||||
#include <QSettings>
|
||||
#include <QInputDialog>
|
||||
|
||||
@ -30,106 +31,137 @@
|
||||
#include <set>
|
||||
#include <list>
|
||||
|
||||
#include "widgets/accounts/accounts.h"
|
||||
#include "widgets/accounts.h"
|
||||
#include "widgets/chat.h"
|
||||
#include "widgets/room.h"
|
||||
#include "widgets/newcontact.h"
|
||||
#include "widgets/joinconference.h"
|
||||
#include "models/roster.h"
|
||||
#include "widgets/vcard/vcard.h"
|
||||
#include "widgets/settings/settings.h"
|
||||
#include "widgets/about.h"
|
||||
|
||||
#include "shared/shared.h"
|
||||
#include "shared/global.h"
|
||||
#include "shared.h"
|
||||
|
||||
namespace Ui {
|
||||
class Squawk;
|
||||
}
|
||||
|
||||
class Application;
|
||||
|
||||
class Squawk : public QMainWindow
|
||||
{
|
||||
Q_OBJECT
|
||||
friend class Application;
|
||||
|
||||
public:
|
||||
explicit Squawk(Models::Roster& rosterModel, QWidget *parent = nullptr);
|
||||
explicit Squawk(QWidget *parent = nullptr);
|
||||
~Squawk() override;
|
||||
|
||||
void writeSettings();
|
||||
|
||||
signals:
|
||||
void closing();
|
||||
void newAccountRequest(const QMap<QString, QVariant>&);
|
||||
void modifyAccountRequest(const QString&, const QMap<QString, QVariant>&);
|
||||
void removeAccountRequest(const QString&);
|
||||
void connectAccount(const QString&);
|
||||
void disconnectAccount(const QString&);
|
||||
void changeState(Shared::Availability state);
|
||||
void sendMessage(const QString& account, const Shared::Message& data);
|
||||
void requestArchive(const QString& account, const QString& jid, int count, const QString& before);
|
||||
void subscribeContact(const QString& account, const QString& jid, const QString& reason);
|
||||
void unsubscribeContact(const QString& account, const QString& jid, const QString& reason);
|
||||
void removeContactRequest(const QString& account, const QString& jid);
|
||||
void addContactRequest(const QString& account, const QString& jid, const QString& name, const QSet<QString>& groups);
|
||||
void addContactToGroupRequest(const QString& account, const QString& jid, const QString& groupName);
|
||||
void removeContactFromGroupRequest(const QString& account, const QString& jid, const QString& groupName);
|
||||
void renameContactRequest(const QString& account, const QString& jid, const QString& newName);
|
||||
void setRoomJoined(const QString& account, const QString& jid, bool joined);
|
||||
void setRoomAutoJoin(const QString& account, const QString& jid, bool joined);
|
||||
void addRoomRequest(const QString& account, const QString& jid, const QString& nick, const QString& password, bool autoJoin);
|
||||
void removeRoomRequest(const QString& account, const QString& jid);
|
||||
void fileDownloadRequest(const QString& url);
|
||||
void requestVCard(const QString& account, const QString& jid);
|
||||
void uploadVCard(const QString& account, const Shared::VCard& card);
|
||||
void changeDownloadsPath(const QString& path);
|
||||
|
||||
void notify(const QString& account, const Shared::Message& msg);
|
||||
void changeSubscription(const Models::Roster::ElId& id, bool subscribe);
|
||||
void openedConversation();
|
||||
void openConversation(const Models::Roster::ElId& id, const QString& resource = "");
|
||||
|
||||
void modifyAccountRequest(const QString&, const QMap<QString, QVariant>&);
|
||||
|
||||
public:
|
||||
Models::Roster::ElId currentConversationId() const;
|
||||
void closeCurrentConversation();
|
||||
void responsePassword(const QString& account, const QString& password);
|
||||
void localPathInvalid(const QString& path);
|
||||
|
||||
public slots:
|
||||
void writeSettings();
|
||||
void readSettings();
|
||||
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 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 select(QModelIndex index);
|
||||
void changeMessage(const QString& account, const QString& jid, const QString& id, const QMap<QString, QVariant>& data);
|
||||
void requestPassword(const QString& account);
|
||||
|
||||
private:
|
||||
typedef std::map<Models::Roster::ElId, Conversation*> Conversations;
|
||||
QScopedPointer<Ui::Squawk> m_ui;
|
||||
|
||||
Accounts* accounts;
|
||||
Settings* preferences;
|
||||
About* about;
|
||||
Models::Roster& rosterModel;
|
||||
Models::Roster rosterModel;
|
||||
Conversations conversations;
|
||||
QMenu* contextMenu;
|
||||
QDBusInterface dbus;
|
||||
std::map<QString, VCard*> vCards;
|
||||
std::deque<QString> requestedAccountsForPasswords;
|
||||
QInputDialog* prompt;
|
||||
Conversation* currentConversation;
|
||||
QModelIndex restoreSelection;
|
||||
bool needToRestore;
|
||||
|
||||
protected:
|
||||
void closeEvent(QCloseEvent * event) override;
|
||||
void expand(const QModelIndex& index);
|
||||
|
||||
protected slots:
|
||||
void notify(const QString& account, const Shared::Message& msg);
|
||||
|
||||
private slots:
|
||||
void onAccounts();
|
||||
void onPreferences();
|
||||
void onNewContact();
|
||||
void onNewConference();
|
||||
void onNewContactAccepted();
|
||||
void onJoinConferenceAccepted();
|
||||
void onAccountsSizeChanged(unsigned int size);
|
||||
void onAccountsClosed();
|
||||
void onPreferencesClosed();
|
||||
void onAccountsClosed(QObject* parent = 0);
|
||||
void onConversationClosed(QObject* parent = 0);
|
||||
void onVCardClosed();
|
||||
void onVCardSave(const Shared::VCard& card, const QString& account);
|
||||
void onActivateVCard(const QString& account, const QString& jid, bool edition = false);
|
||||
void onComboboxActivated(int index);
|
||||
void onRosterItemDoubleClicked(const QModelIndex& item);
|
||||
void onConversationMessage(const Shared::Message& msg);
|
||||
void onRequestArchive(const QString& account, const QString& jid, const QString& before);
|
||||
void onRosterContextMenu(const QPoint& point);
|
||||
void onItemCollepsed(const QModelIndex& index);
|
||||
void onPasswordPromptAccepted();
|
||||
void onPasswordPromptRejected();
|
||||
void onRosterSelectionChanged(const QModelIndex& current, const QModelIndex& previous);
|
||||
void onContextAboutToHide();
|
||||
void onAboutSquawkCalled();
|
||||
void onAboutSquawkClosed();
|
||||
|
||||
void onUnnoticedMessage(const QString& account, const Shared::Message& msg);
|
||||
|
||||
private:
|
||||
void checkNextAccountForPassword();
|
||||
void onPasswordPromptDone();
|
||||
void subscribeConversation(Conversation* conv);
|
||||
};
|
||||
|
||||
#endif // SQUAWK_H
|
||||
|
65
ui/squawk.ui
65
ui/squawk.ui
@ -73,9 +73,6 @@
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item row="2" column="1">
|
||||
<widget class="QTreeView" name="roster">
|
||||
<property name="frameShape">
|
||||
@ -99,31 +96,16 @@
|
||||
<property name="allColumnsShowFocus">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="headerHidden">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="expandsOnDoubleClick">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<attribute name="headerVisible">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<attribute name="headerMinimumSectionSize">
|
||||
<number>10</number>
|
||||
</attribute>
|
||||
<attribute name="headerStretchLastSection">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QComboBox" name="comboBox">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>30</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="editable">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
@ -133,9 +115,6 @@
|
||||
<property name="currentIndex">
|
||||
<number>-1</number>
|
||||
</property>
|
||||
<property name="frame">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
@ -183,7 +162,7 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>718</width>
|
||||
<height>32</height>
|
||||
<height>27</height>
|
||||
</rect>
|
||||
</property>
|
||||
<widget class="QMenu" name="menuSettings">
|
||||
@ -191,7 +170,6 @@
|
||||
<string>Settings</string>
|
||||
</property>
|
||||
<addaction name="actionAccounts"/>
|
||||
<addaction name="actionPreferences"/>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menuFile">
|
||||
<property name="title">
|
||||
@ -201,20 +179,13 @@
|
||||
<addaction name="actionAddConference"/>
|
||||
<addaction name="actionQuit"/>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menuHelp">
|
||||
<property name="title">
|
||||
<string>Help</string>
|
||||
</property>
|
||||
<addaction name="actionAboutSquawk"/>
|
||||
</widget>
|
||||
<addaction name="menuFile"/>
|
||||
<addaction name="menuSettings"/>
|
||||
<addaction name="menuHelp"/>
|
||||
</widget>
|
||||
<action name="actionAccounts">
|
||||
<property name="icon">
|
||||
<iconset theme="system-users" resource="../resources/resources.qrc">
|
||||
<normaloff>:/images/fallback/dark/big/group.svg</normaloff>:/images/fallback/dark/big/group.svg</iconset>
|
||||
<iconset theme="system-users">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Accounts</string>
|
||||
@ -222,8 +193,8 @@
|
||||
</action>
|
||||
<action name="actionQuit">
|
||||
<property name="icon">
|
||||
<iconset theme="application-exit" resource="../resources/resources.qrc">
|
||||
<normaloff>:/images/fallback/dark/big/edit-none.svg</normaloff>:/images/fallback/dark/big/edit-none.svg</iconset>
|
||||
<iconset theme="application-exit">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Quit</string>
|
||||
@ -234,8 +205,8 @@
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="list-add-user" resource="../resources/resources.qrc">
|
||||
<normaloff>:/images/fallback/dark/big/add.svg</normaloff>:/images/fallback/dark/big/add.svg</iconset>
|
||||
<iconset theme="list-add-user">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Add contact</string>
|
||||
@ -246,30 +217,14 @@
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="resource-group-new" resource="../resources/resources.qrc">
|
||||
<normaloff>:/images/fallback/dark/big/group-new.svg</normaloff>:/images/fallback/dark/big/group-new.svg</iconset>
|
||||
<iconset theme="resource-group-new">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Add conference</string>
|
||||
</property>
|
||||
</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>
|
||||
<resources>
|
||||
<include location="../resources/resources.qrc"/>
|
||||
</resources>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
|
@ -1,18 +1,32 @@
|
||||
target_sources(squawk PRIVATE
|
||||
badge.cpp
|
||||
badge.h
|
||||
comboboxdelegate.cpp
|
||||
comboboxdelegate.h
|
||||
exponentialblur.cpp
|
||||
exponentialblur.h
|
||||
flowlayout.cpp
|
||||
flowlayout.h
|
||||
image.cpp
|
||||
image.h
|
||||
progress.cpp
|
||||
progress.h
|
||||
cmake_minimum_required(VERSION 3.3)
|
||||
project(squawkUIUtils)
|
||||
|
||||
# Instruct CMake to run moc automatically when needed.
|
||||
set(CMAKE_AUTOMOC ON)
|
||||
# Instruct CMake to create code from Qt designer ui files
|
||||
set(CMAKE_AUTOUIC ON)
|
||||
|
||||
# Find the QtWidgets library
|
||||
find_package(Qt5 CONFIG REQUIRED COMPONENTS Widgets Core)
|
||||
|
||||
set(squawkUIUtils_SRC
|
||||
# messageline.cpp
|
||||
# message.cpp
|
||||
resizer.cpp
|
||||
resizer.h
|
||||
# image.cpp
|
||||
flowlayout.cpp
|
||||
badge.cpp
|
||||
progress.cpp
|
||||
comboboxdelegate.cpp
|
||||
feedview.cpp
|
||||
messagedelegate.cpp
|
||||
exponentialblur.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,62 +26,27 @@ Badge::Badge(const QString& p_id, const QString& p_text, const QIcon& icon, QWid
|
||||
closeButton(new QPushButton()),
|
||||
layout(new QHBoxLayout(this))
|
||||
{
|
||||
createMandatoryComponents();
|
||||
setBackgroundRole(QPalette::Base);
|
||||
//setAutoFillBackground(true);
|
||||
setFrameStyle(QFrame::StyledPanel);
|
||||
setFrameShadow(QFrame::Raised);
|
||||
|
||||
image->setPixmap(icon.pixmap(25, 25));
|
||||
closeButton->setIcon(QIcon::fromTheme("tab-close"));
|
||||
closeButton->setMaximumHeight(25);
|
||||
closeButton->setMaximumWidth(25);
|
||||
|
||||
layout->addWidget(image);
|
||||
layout->addWidget(text);
|
||||
layout->addWidget(closeButton);
|
||||
}
|
||||
|
||||
Badge::Badge(QWidget* parent):
|
||||
QFrame(parent),
|
||||
id(Shared::generateUUID()),
|
||||
image(nullptr),
|
||||
text(nullptr),
|
||||
closeButton(new QPushButton()),
|
||||
layout(new QHBoxLayout(this))
|
||||
{
|
||||
createMandatoryComponents();
|
||||
layout->setContentsMargins(2, 2, 2, 2);
|
||||
|
||||
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);
|
||||
}
|
||||
connect(closeButton, &QPushButton::clicked, this, &Badge::close);
|
||||
}
|
||||
|
||||
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
|
||||
@ -93,22 +58,3 @@ bool Badge::Comparator::operator()(const Badge& a, const Badge& b) const
|
||||
{
|
||||
return a.id < b.id;
|
||||
}
|
||||
|
||||
void Badge::createMandatoryComponents()
|
||||
{
|
||||
setBackgroundRole(QPalette::Base);
|
||||
//setAutoFillBackground(true);
|
||||
setFrameStyle(QFrame::StyledPanel);
|
||||
setFrameShadow(QFrame::Raised);
|
||||
|
||||
QIcon tabCloseIcon = QIcon::fromTheme("tab-close");
|
||||
if (tabCloseIcon.isNull()) {
|
||||
tabCloseIcon.addFile(QString::fromUtf8(":/images/fallback/dark/big/edit-none.svg"), QSize(), QIcon::Normal, QIcon::Off);
|
||||
}
|
||||
closeButton->setIcon(tabCloseIcon);
|
||||
|
||||
closeButton->setMaximumHeight(25);
|
||||
closeButton->setMaximumWidth(25);
|
||||
layout->setContentsMargins(2, 2, 2, 2);
|
||||
connect(closeButton, &QPushButton::clicked, this, &Badge::close);
|
||||
}
|
||||
|
@ -25,8 +25,6 @@
|
||||
#include <QIcon>
|
||||
#include <QPushButton>
|
||||
|
||||
#include "shared/utils.h"
|
||||
|
||||
/**
|
||||
* @todo write docs
|
||||
*/
|
||||
@ -35,15 +33,10 @@ class Badge : public QFrame
|
||||
Q_OBJECT
|
||||
public:
|
||||
Badge(const QString& id, const QString& text, const QIcon& icon, QWidget* parent = nullptr);
|
||||
Badge(QWidget* parent = nullptr);
|
||||
~Badge();
|
||||
|
||||
const QString id;
|
||||
|
||||
public:
|
||||
void setText(const QString& text);
|
||||
void setIcon(const QIcon& icon);
|
||||
|
||||
signals:
|
||||
void close();
|
||||
|
||||
@ -53,9 +46,6 @@ private:
|
||||
QPushButton* closeButton;
|
||||
QHBoxLayout* layout;
|
||||
|
||||
private:
|
||||
void createMandatoryComponents();
|
||||
|
||||
public:
|
||||
struct Comparator {
|
||||
bool operator()(const Badge& a, const Badge& b) const;
|
||||
|
@ -21,17 +21,14 @@
|
||||
#include <QPaintEvent>
|
||||
#include <QPainter>
|
||||
#include <QScrollBar>
|
||||
#include <QApplication>
|
||||
#include <QClipboard>
|
||||
#include <QDebug>
|
||||
|
||||
#include "messagedelegate.h"
|
||||
#include "messagefeed.h"
|
||||
#include "ui/models/messagefeed.h"
|
||||
|
||||
constexpr int maxMessageHeight = 10000;
|
||||
constexpr int approximateSingleMessageHeight = 20;
|
||||
constexpr int progressSize = 70;
|
||||
constexpr int dateDeviderMargin = 10;
|
||||
|
||||
const std::set<int> FeedView::geometryChangingRoles = {
|
||||
Models::MessageFeed::Attach,
|
||||
@ -45,20 +42,11 @@ FeedView::FeedView(QWidget* parent):
|
||||
QAbstractItemView(parent),
|
||||
hints(),
|
||||
vo(0),
|
||||
elementMargin(0),
|
||||
specialDelegate(false),
|
||||
specialModel(false),
|
||||
clearWidgetsMode(false),
|
||||
modelState(Models::MessageFeed::complete),
|
||||
progress(),
|
||||
dividerFont(),
|
||||
dividerMetrics(dividerFont),
|
||||
mousePressed(false),
|
||||
dragging(false),
|
||||
hovered(Shared::Hover::nothing),
|
||||
dragStartPoint(),
|
||||
dragEndPoint(),
|
||||
selectedText()
|
||||
progress()
|
||||
{
|
||||
horizontalScrollBar()->setRange(0, 0);
|
||||
verticalScrollBar()->setSingleStep(approximateSingleMessageHeight);
|
||||
@ -68,15 +56,6 @@ FeedView::FeedView(QWidget* parent):
|
||||
|
||||
progress.setParent(viewport());
|
||||
progress.resize(progressSize, progressSize);
|
||||
|
||||
dividerFont = getFont();
|
||||
dividerFont.setBold(true);
|
||||
float ndps = dividerFont.pointSizeF();
|
||||
if (ndps != -1) {
|
||||
dividerFont.setPointSizeF(ndps * 1.2);
|
||||
} else {
|
||||
dividerFont.setPointSize(dividerFont.pointSize() + 2);
|
||||
}
|
||||
}
|
||||
|
||||
FeedView::~FeedView()
|
||||
@ -115,7 +94,7 @@ QRect FeedView::visualRect(const QModelIndex& index) const
|
||||
} else {
|
||||
const Hint& hint = hints.at(row);
|
||||
const QWidget* vp = viewport();
|
||||
return QRect(hint.x, vp->height() - hint.height - hint.offset + vo, hint.width, hint.height);
|
||||
return QRect(0, vp->height() - hint.height - hint.offset + vo, vp->width(), hint.height);
|
||||
}
|
||||
}
|
||||
|
||||
@ -170,7 +149,7 @@ void FeedView::dataChanged(const QModelIndex& topLeft, const QModelIndex& bottom
|
||||
|
||||
void FeedView::updateGeometries()
|
||||
{
|
||||
//qDebug() << "updateGeometries";
|
||||
qDebug() << "updateGeometries";
|
||||
QScrollBar* bar = verticalScrollBar();
|
||||
|
||||
const QStyle* st = style();
|
||||
@ -207,40 +186,19 @@ void FeedView::updateGeometries()
|
||||
option.rect.setWidth(layoutBounds.width());
|
||||
|
||||
hints.clear();
|
||||
uint32_t previousOffset = elementMargin;
|
||||
QDateTime lastDate;
|
||||
uint32_t previousOffset = 0;
|
||||
for (int i = 0, size = m->rowCount(); i < size; ++i) {
|
||||
QModelIndex index = m->index(i, 0, rootIndex());
|
||||
QDateTime currentDate = index.data(Models::MessageFeed::Date).toDateTime();
|
||||
if (i > 0) {
|
||||
if (currentDate.daysTo(lastDate) > 0) {
|
||||
previousOffset += dividerMetrics.height() + dateDeviderMargin * 2;
|
||||
} else {
|
||||
previousOffset += elementMargin;
|
||||
}
|
||||
}
|
||||
lastDate = currentDate;
|
||||
QSize messageSize = itemDelegate(index)->sizeHint(option, index);
|
||||
uint32_t offsetX(0);
|
||||
if (specialDelegate) {
|
||||
if (index.data(Models::MessageFeed::SentByMe).toBool()) {
|
||||
offsetX = layoutBounds.width() - messageSize.width() - MessageDelegate::avatarHeight - MessageDelegate::margin * 2;
|
||||
} else {
|
||||
offsetX = MessageDelegate::avatarHeight + MessageDelegate::margin * 2;
|
||||
}
|
||||
}
|
||||
|
||||
int height = itemDelegate(index)->sizeHint(option, index).height();
|
||||
hints.emplace_back(Hint({
|
||||
false,
|
||||
previousOffset,
|
||||
static_cast<uint32_t>(messageSize.height()),
|
||||
static_cast<uint32_t>(messageSize.width()),
|
||||
offsetX
|
||||
static_cast<uint32_t>(height)
|
||||
}));
|
||||
previousOffset += messageSize.height();
|
||||
previousOffset += height;
|
||||
}
|
||||
|
||||
int totalHeight = previousOffset - layoutBounds.height() + dividerMetrics.height() + dateDeviderMargin * 2;
|
||||
int totalHeight = previousOffset - layoutBounds.height();
|
||||
if (modelState != Models::MessageFeed::complete) {
|
||||
totalHeight += progressSize;
|
||||
}
|
||||
@ -262,49 +220,25 @@ void FeedView::updateGeometries()
|
||||
|
||||
bool FeedView::tryToCalculateGeometriesWithNoScrollbars(const QStyleOptionViewItem& option, const QAbstractItemModel* m, uint32_t totalHeight)
|
||||
{
|
||||
uint32_t previousOffset = elementMargin;
|
||||
QDateTime lastDate;
|
||||
uint32_t previousOffset = 0;
|
||||
bool success = true;
|
||||
for (int i = 0, size = m->rowCount(); i < size; ++i) {
|
||||
QModelIndex index = m->index(i, 0, rootIndex());
|
||||
QDateTime currentDate = index.data(Models::MessageFeed::Date).toDateTime();
|
||||
if (i > 0) {
|
||||
if (currentDate.daysTo(lastDate) > 0) {
|
||||
previousOffset += dateDeviderMargin * 2 + dividerMetrics.height();
|
||||
} else {
|
||||
previousOffset += elementMargin;
|
||||
}
|
||||
}
|
||||
lastDate = currentDate;
|
||||
QSize messageSize = itemDelegate(index)->sizeHint(option, index);
|
||||
int height = itemDelegate(index)->sizeHint(option, index).height();
|
||||
|
||||
if (previousOffset + messageSize.height() + elementMargin > totalHeight) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t offsetX(0);
|
||||
if (specialDelegate) {
|
||||
if (index.data(Models::MessageFeed::SentByMe).toBool()) {
|
||||
offsetX = option.rect.width() - messageSize.width() - MessageDelegate::avatarHeight - MessageDelegate::margin * 2;
|
||||
} else {
|
||||
offsetX = MessageDelegate::avatarHeight + MessageDelegate::margin * 2;
|
||||
}
|
||||
if (previousOffset + height > totalHeight) {
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
hints.emplace_back(Hint({
|
||||
false,
|
||||
previousOffset,
|
||||
static_cast<uint32_t>(messageSize.height()),
|
||||
static_cast<uint32_t>(messageSize.width()),
|
||||
offsetX
|
||||
static_cast<uint32_t>(height)
|
||||
}));
|
||||
previousOffset += messageSize.height();
|
||||
previousOffset += height;
|
||||
}
|
||||
|
||||
previousOffset += dateDeviderMargin * 2 + dividerMetrics.height();
|
||||
if (previousOffset > totalHeight) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
return success;
|
||||
}
|
||||
|
||||
|
||||
@ -332,7 +266,6 @@ void FeedView::paintEvent(QPaintEvent* event)
|
||||
toRener.emplace_back(m->index(i, 0, rootIndex()));
|
||||
}
|
||||
if (y1 > relativeY1) {
|
||||
inZone = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -349,36 +282,11 @@ void FeedView::paintEvent(QPaintEvent* event)
|
||||
}
|
||||
}
|
||||
|
||||
QDateTime lastDate;
|
||||
bool first = true;
|
||||
QRect viewportRect = vp->rect();
|
||||
for (const QModelIndex& index : toRener) {
|
||||
QDateTime currentDate = index.data(Models::MessageFeed::Date).toDateTime();
|
||||
option.rect = visualRect(index);
|
||||
if (first) {
|
||||
int ind = index.row() - 1;
|
||||
if (ind > 0) {
|
||||
QDateTime underDate = m->index(ind, 0, rootIndex()).data(Models::MessageFeed::Date).toDateTime();
|
||||
if (currentDate.daysTo(underDate) > 0) {
|
||||
drawDateDevider(option.rect.bottom(), underDate, painter);
|
||||
}
|
||||
}
|
||||
first = false;
|
||||
}
|
||||
QRect stripe = option.rect;
|
||||
stripe.setLeft(0);
|
||||
stripe.setWidth(viewportRect.width());
|
||||
bool mouseOver = stripe.contains(cursor) && viewportRect.contains(cursor);
|
||||
bool mouseOver = option.rect.contains(cursor) && vp->rect().contains(cursor);
|
||||
option.state.setFlag(QStyle::State_MouseOver, mouseOver);
|
||||
itemDelegate(index)->paint(&painter, option, index);
|
||||
|
||||
if (!lastDate.isNull() && currentDate.daysTo(lastDate) > 0) {
|
||||
drawDateDevider(option.rect.bottom(), lastDate, painter);
|
||||
}
|
||||
lastDate = currentDate;
|
||||
}
|
||||
if (!lastDate.isNull() && inZone) { //if after drawing all messages there is still space
|
||||
drawDateDevider(option.rect.top() - dateDeviderMargin * 2 - dividerMetrics.height(), lastDate, painter);
|
||||
}
|
||||
|
||||
if (clearWidgetsMode && specialDelegate) {
|
||||
@ -386,16 +294,10 @@ void FeedView::paintEvent(QPaintEvent* event)
|
||||
del->endClearWidgets();
|
||||
clearWidgetsMode = false;
|
||||
}
|
||||
}
|
||||
|
||||
void FeedView::drawDateDevider(int top, const QDateTime& date, QPainter& painter)
|
||||
{
|
||||
int divisionHeight = dateDeviderMargin * 2 + dividerMetrics.height();
|
||||
QRect r(QPoint(0, top), QSize(viewport()->width(), divisionHeight));
|
||||
painter.save();
|
||||
painter.setFont(dividerFont);
|
||||
painter.drawText(r, Qt::AlignCenter, date.toString("d MMMM"));
|
||||
painter.restore();
|
||||
if (event->rect().height() == vp->height()) {
|
||||
// draw the blurred drop shadow...
|
||||
}
|
||||
}
|
||||
|
||||
void FeedView::verticalScrollbarValueChanged(int value)
|
||||
@ -415,151 +317,13 @@ void FeedView::verticalScrollbarValueChanged(int value)
|
||||
QAbstractItemView::verticalScrollbarValueChanged(vo);
|
||||
}
|
||||
|
||||
void FeedView::setAnchorHovered(Shared::Hover type)
|
||||
{
|
||||
if (hovered != type) {
|
||||
hovered = type;
|
||||
switch (hovered) {
|
||||
case Shared::Hover::nothing:
|
||||
setCursor(Qt::ArrowCursor);
|
||||
break;
|
||||
case Shared::Hover::text:
|
||||
setCursor(Qt::IBeamCursor);
|
||||
break;
|
||||
case Shared::Hover::anchor:
|
||||
setCursor(Qt::PointingHandCursor);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FeedView::mouseMoveEvent(QMouseEvent* event)
|
||||
{
|
||||
if (!isVisible()) {
|
||||
return;
|
||||
}
|
||||
|
||||
dragEndPoint = event->localPos().toPoint();
|
||||
if (mousePressed) {
|
||||
QPoint distance = dragStartPoint - dragEndPoint;
|
||||
if (distance.manhattanLength() > 5) {
|
||||
dragging = true;
|
||||
}
|
||||
}
|
||||
|
||||
QAbstractItemView::mouseMoveEvent(event);
|
||||
|
||||
if (specialDelegate) {
|
||||
MessageDelegate* del = static_cast<MessageDelegate*>(itemDelegate());
|
||||
if (dragging) {
|
||||
QModelIndex index = indexAt(dragStartPoint);
|
||||
if (index.isValid()) {
|
||||
QRect rect = visualRect(index);
|
||||
if (rect.contains(dragStartPoint)) {
|
||||
QString selected = del->mouseDrag(dragStartPoint, dragEndPoint, index, rect);
|
||||
if (selectedText != selected) {
|
||||
selectedText = selected;
|
||||
setDirtyRegion(rect);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
QModelIndex index = indexAt(dragEndPoint);
|
||||
if (index.isValid()) {
|
||||
QRect rect = visualRect(index);
|
||||
if (rect.contains(dragEndPoint)) {
|
||||
setAnchorHovered(del->hoverType(dragEndPoint, index, rect));
|
||||
} else {
|
||||
setAnchorHovered(Shared::Hover::nothing);
|
||||
}
|
||||
} else {
|
||||
setAnchorHovered(Shared::Hover::nothing);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FeedView::mousePressEvent(QMouseEvent* event)
|
||||
{
|
||||
QAbstractItemView::mousePressEvent(event);
|
||||
|
||||
mousePressed = event->button() == Qt::LeftButton;
|
||||
if (mousePressed) {
|
||||
dragStartPoint = event->localPos().toPoint();
|
||||
if (specialDelegate && specialModel) {
|
||||
MessageDelegate* del = static_cast<MessageDelegate*>(itemDelegate());
|
||||
QString lastSelectedId = del->clearSelection();
|
||||
if (lastSelectedId.size()) {
|
||||
Models::MessageFeed* feed = static_cast<Models::MessageFeed*>(model());
|
||||
QModelIndex index = feed->modelIndexById(lastSelectedId);
|
||||
if (index.isValid()) {
|
||||
setDirtyRegion(visualRect(index));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FeedView::mouseDoubleClickEvent(QMouseEvent* event)
|
||||
{
|
||||
QAbstractItemView::mouseDoubleClickEvent(event);
|
||||
mousePressed = event->button() == Qt::LeftButton;
|
||||
if (mousePressed) {
|
||||
dragStartPoint = event->localPos().toPoint();
|
||||
if (specialDelegate && specialModel) {
|
||||
MessageDelegate* del = static_cast<MessageDelegate*>(itemDelegate());
|
||||
QString lastSelectedId = del->clearSelection();
|
||||
selectedText = "";
|
||||
if (lastSelectedId.size()) {
|
||||
Models::MessageFeed* feed = static_cast<Models::MessageFeed*>(model());
|
||||
QModelIndex index = feed->modelIndexById(lastSelectedId);
|
||||
if (index.isValid()) {
|
||||
setDirtyRegion(visualRect(index));
|
||||
}
|
||||
}
|
||||
|
||||
QModelIndex index = indexAt(dragStartPoint);
|
||||
QRect rect = visualRect(index);
|
||||
if (rect.contains(dragStartPoint)) {
|
||||
selectedText = del->leftDoubleClick(dragStartPoint, index, rect);
|
||||
if (selectedText.size() > 0) {
|
||||
setDirtyRegion(rect);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FeedView::mouseReleaseEvent(QMouseEvent* event)
|
||||
{
|
||||
QAbstractItemView::mouseReleaseEvent(event);
|
||||
|
||||
if (mousePressed) {
|
||||
if (!dragging && specialDelegate) {
|
||||
QPoint point = event->localPos().toPoint();
|
||||
QModelIndex index = indexAt(point);
|
||||
if (index.isValid()) {
|
||||
QRect rect = visualRect(index);
|
||||
MessageDelegate* del = static_cast<MessageDelegate*>(itemDelegate());
|
||||
if (rect.contains(point)) {
|
||||
del->leftClick(point, index, rect);
|
||||
}
|
||||
}
|
||||
}
|
||||
dragging = false;
|
||||
mousePressed = false;
|
||||
}
|
||||
}
|
||||
|
||||
void FeedView::keyPressEvent(QKeyEvent* event)
|
||||
{
|
||||
QKeyEvent *key_event = static_cast<QKeyEvent*>(event);
|
||||
if (key_event->matches(QKeySequence::Copy)) {
|
||||
if (selectedText.size() > 0) {
|
||||
QClipboard* cb = QApplication::clipboard();
|
||||
cb->setText(selectedText);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FeedView::resizeEvent(QResizeEvent* event)
|
||||
@ -603,13 +367,10 @@ void FeedView::setItemDelegate(QAbstractItemDelegate* delegate)
|
||||
MessageDelegate* del = dynamic_cast<MessageDelegate*>(delegate);
|
||||
if (del) {
|
||||
specialDelegate = true;
|
||||
elementMargin = MessageDelegate::margin;
|
||||
connect(del, &MessageDelegate::buttonPushed, this, &FeedView::onMessageButtonPushed);
|
||||
connect(del, &MessageDelegate::invalidPath, this, &FeedView::onMessageInvalidPath);
|
||||
connect(del, &MessageDelegate::openLink, &QDesktopServices::openUrl);
|
||||
} else {
|
||||
specialDelegate = false;
|
||||
elementMargin = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@ -633,11 +394,16 @@ void FeedView::setModel(QAbstractItemModel* p_model)
|
||||
}
|
||||
}
|
||||
|
||||
void FeedView::onMessageButtonPushed(const QString& messageId)
|
||||
void FeedView::onMessageButtonPushed(const QString& messageId, bool download)
|
||||
{
|
||||
if (specialModel) {
|
||||
Models::MessageFeed* feed = static_cast<Models::MessageFeed*>(model());
|
||||
|
||||
if (download) {
|
||||
feed->downloadAttachment(messageId);
|
||||
} else {
|
||||
feed->uploadAttachment(messageId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -671,8 +437,3 @@ void FeedView::onModelSyncStateChange(Models::MessageFeed::SyncState state)
|
||||
scheduleDelayedItemsLayout();
|
||||
}
|
||||
}
|
||||
|
||||
QString FeedView::getSelectedText() const
|
||||
{
|
||||
return selectedText;
|
||||
}
|
@ -20,15 +20,12 @@
|
||||
#define FEEDVIEW_H
|
||||
|
||||
#include <QAbstractItemView>
|
||||
#include <QDesktopServices>
|
||||
#include <QUrl>
|
||||
|
||||
#include <deque>
|
||||
#include <set>
|
||||
|
||||
#include <ui/widgets/messageline/messagefeed.h>
|
||||
#include <ui/utils/progress.h>
|
||||
#include <shared/utils.h>
|
||||
#include <ui/models/messagefeed.h>
|
||||
#include "progress.h"
|
||||
|
||||
/**
|
||||
* @todo write docs
|
||||
@ -51,7 +48,6 @@ public:
|
||||
void setModel(QAbstractItemModel * model) override;
|
||||
|
||||
QFont getFont() const;
|
||||
QString getSelectedText() const;
|
||||
|
||||
signals:
|
||||
void resized();
|
||||
@ -62,7 +58,7 @@ protected slots:
|
||||
void rowsInserted(const QModelIndex & parent, int start, int end) override;
|
||||
void verticalScrollbarValueChanged(int value) override;
|
||||
void dataChanged(const QModelIndex & topLeft, const QModelIndex & bottomRight, const QVector<int> & roles) override;
|
||||
void onMessageButtonPushed(const QString& messageId);
|
||||
void onMessageButtonPushed(const QString& messageId, bool download);
|
||||
void onMessageInvalidPath(const QString& messageId);
|
||||
void onModelSyncStateChange(Models::MessageFeed::SyncState state);
|
||||
|
||||
@ -72,42 +68,25 @@ protected:
|
||||
void paintEvent(QPaintEvent * event) override;
|
||||
void updateGeometries() override;
|
||||
void mouseMoveEvent(QMouseEvent * event) override;
|
||||
void mousePressEvent(QMouseEvent * event) override;
|
||||
void mouseReleaseEvent(QMouseEvent * event) override;
|
||||
void mouseDoubleClickEvent(QMouseEvent * event) override;
|
||||
void keyPressEvent(QKeyEvent * event) override;
|
||||
void resizeEvent(QResizeEvent * event) override;
|
||||
|
||||
private:
|
||||
bool tryToCalculateGeometriesWithNoScrollbars(const QStyleOptionViewItem& option, const QAbstractItemModel* model, uint32_t totalHeight);
|
||||
void positionProgress();
|
||||
void drawDateDevider(int top, const QDateTime& date, QPainter& painter);
|
||||
void setAnchorHovered(Shared::Hover type);
|
||||
|
||||
private:
|
||||
struct Hint {
|
||||
bool dirty;
|
||||
uint32_t offset;
|
||||
uint32_t height;
|
||||
uint32_t width;
|
||||
uint32_t x;
|
||||
};
|
||||
std::deque<Hint> hints;
|
||||
int vo;
|
||||
int elementMargin;
|
||||
bool specialDelegate;
|
||||
bool specialModel;
|
||||
bool clearWidgetsMode;
|
||||
Models::MessageFeed::SyncState modelState;
|
||||
Progress progress;
|
||||
QFont dividerFont;
|
||||
QFontMetrics dividerMetrics;
|
||||
bool mousePressed;
|
||||
bool dragging;
|
||||
Shared::Hover hovered;
|
||||
QPoint dragStartPoint;
|
||||
QPoint dragEndPoint;
|
||||
QString selectedText;
|
||||
|
||||
static const std::set<int> geometryChangingRoles;
|
||||
|
344
ui/utils/message.cpp
Normal file
344
ui/utils/message.cpp
Normal file
@ -0,0 +1,344 @@
|
||||
/*
|
||||
* 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));
|
||||
}
|
||||
|
103
ui/utils/message.h
Normal file
103
ui/utils/message.h
Normal file
@ -0,0 +1,103 @@
|
||||
/*
|
||||
* 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
|
549
ui/utils/messagedelegate.cpp
Normal file
549
ui/utils/messagedelegate.cpp
Normal file
@ -0,0 +1,549 @@
|
||||
/*
|
||||
* 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
|
||||
// {
|
||||
//
|
||||
// }
|
@ -29,14 +29,10 @@
|
||||
#include <QPushButton>
|
||||
#include <QProgressBar>
|
||||
#include <QLabel>
|
||||
#include <QTextDocument>
|
||||
|
||||
#include "shared/icons.h"
|
||||
#include "shared/global.h"
|
||||
#include "shared/utils.h"
|
||||
#include "shared/pathcheck.h"
|
||||
|
||||
#include "preview.h"
|
||||
|
||||
namespace Models {
|
||||
struct FeedItem;
|
||||
@ -57,40 +53,22 @@ public:
|
||||
bool editorEvent(QEvent * event, QAbstractItemModel * model, const QStyleOptionViewItem & option, const QModelIndex & index) override;
|
||||
void endClearWidgets();
|
||||
void beginClearWidgets();
|
||||
void leftClick(const QPoint& point, const QModelIndex& index, const QRect& sizeHint) const;
|
||||
QString leftDoubleClick(const QPoint& point, const QModelIndex& index, const QRect& sizeHint);
|
||||
Shared::Hover hoverType(const QPoint& point, const QModelIndex& index, const QRect& sizeHint) const;
|
||||
QString mouseDrag(const QPoint& start, const QPoint& end, const QModelIndex& index, const QRect& sizeHint);
|
||||
QString clearSelection();
|
||||
|
||||
static int avatarHeight;
|
||||
static int margin;
|
||||
|
||||
signals:
|
||||
void buttonPushed(const QString& messageId) const;
|
||||
void buttonPushed(const QString& messageId, bool download) const;
|
||||
void invalidPath(const QString& messageId) const;
|
||||
void openLink(const QString& href) const;
|
||||
|
||||
protected:
|
||||
int paintButton(QPushButton* btn, QPainter* painter, bool sentByMe, QStyleOptionViewItem& option) const;
|
||||
int paintBar(QProgressBar* bar, QPainter* painter, bool sentByMe, QStyleOptionViewItem& option) const;
|
||||
int paintPreview(const Models::FeedItem& data, QPainter* painter, QStyleOptionViewItem& option) const;
|
||||
int paintComment(const Models::FeedItem& data, QPainter* painter, QStyleOptionViewItem& option) const;
|
||||
int paintBody(const Models::FeedItem& data, QPainter* painter, QStyleOptionViewItem& option) const;
|
||||
void paintAvatar(const Models::FeedItem& data, const QModelIndex& index, const QStyleOptionViewItem& option, QPainter* painter) const;
|
||||
void paintBubble(const Models::FeedItem& data, QPainter* painter, const QStyleOptionViewItem& option) const;
|
||||
|
||||
void paintButton(QPushButton* btn, QPainter* painter, bool sentByMe, QStyleOptionViewItem& option) const;
|
||||
void paintBar(QProgressBar* bar, QPainter* painter, bool sentByMe, QStyleOptionViewItem& option) const;
|
||||
void paintPreview(const Models::FeedItem& data, QPainter* painter, QStyleOptionViewItem& option) const;
|
||||
QPushButton* getButton(const Models::FeedItem& data) const;
|
||||
QProgressBar* getBar(const Models::FeedItem& data) const;
|
||||
QLabel* getStatusIcon(const Models::FeedItem& data) const;
|
||||
QLabel* getPencilIcon(const Models::FeedItem& data) const;
|
||||
QLabel* getBody(const Models::FeedItem& data) const;
|
||||
void clearHelperWidget(const Models::FeedItem& data) const;
|
||||
|
||||
bool needToDrawAvatar(const QModelIndex& index, const Models::FeedItem& data, const QStyleOptionViewItem& option) const;
|
||||
bool needToDrawSender(const QModelIndex& index, const Models::FeedItem& data) const;
|
||||
|
||||
QRect getHoveredMessageBodyRect(const QModelIndex& index, const Models::FeedItem& data, const QRect& sizeHint) const;
|
||||
QString getAnchor(const QPoint& point, const QModelIndex& index, const QRect& sizeHint) const;
|
||||
QSize calculateAttachSize(const QString& path, const QRect& bounds) const;
|
||||
QSize constrainAttachSize(QSize src, QSize bounds) const;
|
||||
|
||||
protected slots:
|
||||
void onButtonPushed() const;
|
||||
@ -99,28 +77,26 @@ private:
|
||||
class FeedButton : public QPushButton {
|
||||
public:
|
||||
QString messageId;
|
||||
bool download;
|
||||
};
|
||||
|
||||
QFont bodyFont;
|
||||
QFont nickFont;
|
||||
QFont dateFont;
|
||||
QTextDocument* bodyRenderer;
|
||||
QFontMetrics bodyMetrics;
|
||||
QFontMetrics nickMetrics;
|
||||
QFontMetrics dateMetrics;
|
||||
|
||||
int buttonHeight;
|
||||
int buttonWidth;
|
||||
int barHeight;
|
||||
|
||||
std::map<QString, FeedButton*>* buttons;
|
||||
std::map<QString, QProgressBar*>* bars;
|
||||
std::map<QString, QLabel*>* statusIcons;
|
||||
std::map<QString, QLabel*>* pencilIcons;
|
||||
std::map<QString, Preview*>* previews;
|
||||
std::map<QString, QLabel*>* bodies;
|
||||
std::set<QString>* idsToKeep;
|
||||
bool clearingWidgets;
|
||||
QString currentId;
|
||||
std::pair<int, int> selection;
|
||||
|
||||
};
|
||||
|
||||
#endif // MESSAGEDELEGATE_H
|
504
ui/utils/messageline.cpp
Normal file
504
ui/utils/messageline.cpp
Normal file
@ -0,0 +1,504 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user