Compare commits

..

No commits in common. "master" and "newMessageFeedPreview1" have entirely different histories.

137 changed files with 3727 additions and 10996 deletions

View File

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

View File

@ -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)
endif()
find_package(Qt5Widgets CONFIG REQUIRED)
find_package(Qt5LinguistTools)
add_executable(squawk ${WIN32_FLAG} ${MACOSX_BUNDLE_FLAG})
target_include_directories(squawk PRIVATE ${CMAKE_SOURCE_DIR})
option(SYSTEM_QXMPP "Use system qxmpp lib" ON)
option(WITH_KWALLET "Build KWallet support module" ON)
option(WITH_KIO "Build KIO support module" ON)
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 ()
message("Building with system QXmpp")
endif ()
endif ()
if (NOT SYSTEM_QXMPP)
target_link_libraries(squawk PRIVATE qxmpp)
add_subdirectory(external/qxmpp)
else ()
target_link_libraries(squawk PRIVATE QXmpp::QXmpp)
endif ()
## KIO
if (WITH_KIO)
find_package(KF5KIO CONFIG)
if (NOT KF5KIO_FOUND)
set(WITH_KIO OFF)
message("KIO package wasn't found, KIO support modules wouldn't be built")
else ()
target_compile_definitions(squawk PRIVATE WITH_KIO)
message("Building with support of KIO")
endif ()
endif ()
## KWallet
if (WITH_KWALLET)
find_package(KF5Wallet CONFIG)
if (NOT KF5Wallet_FOUND)
set(WITH_KWALLET OFF)
message("KWallet package wasn't found, KWallet support module wouldn't be built")
else ()
target_compile_definitions(squawk PRIVATE WITH_KWALLET)
message("Building with support of KWallet")
endif ()
endif ()
if (WITH_KCONFIG)
find_package(KF5Config CONFIG)
if (NOT KF5Config_FOUND)
set(WITH_KCONFIG OFF)
message("KConfig package wasn't found, KConfig support modules wouldn't be built")
else()
find_package(KF5ConfigWidgets CONFIG)
if (NOT KF5ConfigWidgets_FOUND)
set(WITH_KCONFIG OFF)
message("KConfigWidgets package wasn't found, KConfigWidgets support modules wouldn't be built")
else()
target_compile_definitions(squawk PRIVATE WITH_KCONFIG)
message("Building with support of KConfig")
message("Building with support of KConfigWidgets")
endif()
endif()
endif()
## Signal (TODO)
# find_package(Signal REQUIRED)
## LMDB
find_package(LMDB REQUIRED)
# Linking
target_link_libraries(squawk PRIVATE Qt5::Core Qt5::Widgets Qt5::DBus Qt5::Network Qt5::Gui Qt5::Xml)
target_link_libraries(squawk PRIVATE lmdb)
target_link_libraries(squawk PRIVATE simpleCrypt)
# Link thread libraries on Linux
if(UNIX AND NOT APPLE)
set(THREADS_PREFER_PTHREAD_FLAG ON)
find_package(Threads REQUIRED)
target_link_libraries(squawk PRIVATE Threads::Threads)
endif()
# Build type
if (NOT CMAKE_BUILD_TYPE)
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Debug)
endif ()
endif()
set(CMAKE_CXX_FLAGS_DEBUG "-g -Wall -Wextra")
set(CMAKE_CXX_FLAGS_RELEASE "-O3")
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)
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)
if (SYSTEM_QXMPP)
find_package(QXmpp CONFIG)
if (NOT QXmpp_FOUND)
set(SYSTEM_QXMPP OFF)
message("QXmpp package wasn't found, trying to build with bundled QXmpp")
else()
message("Building with system QXmpp")
endif()
endif()
if(NOT SYSTEM_QXMPP)
add_subdirectory(external/qxmpp)
endif()
if (WITH_KWALLET)
find_package(KF5Wallet CONFIG)
if (NOT KF5Wallet_FOUND)
set(WITH_KWALLET OFF)
message("KWallet package wasn't found, KWallet support module wouldn't be built")
else()
add_definitions(-DWITH_KWALLET)
message("Building with support of KWallet")
endif()
endif()
add_executable(squawk ${squawk_SRC} ${squawk_HEAD} ${RCC})
target_link_libraries(squawk Qt5::Widgets)
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()
add_definitions(-DWITH_KIO)
message("Building with support of KIO")
endif()
endif()
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)

View File

@ -595,17 +595,17 @@ pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
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/>.

View File

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

View File

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

View File

@ -21,32 +21,27 @@
# LMDB_INCLUDE_DIRS The location of LMDB headers.
find_path(LMDB_ROOT_DIR
NAMES include/lmdb.h
)
NAMES include/lmdb.h
)
find_library(LMDB_LIBRARIES
NAMES liblmdb.a liblmdb.so liblmdb.so.a liblmdb.dll.a # We want lmdb to be static, if possible
HINTS ${LMDB_ROOT_DIR}/lib
)
add_library(lmdb UNKNOWN IMPORTED)
set_target_properties(lmdb PROPERTIES
IMPORTED_LOCATION ${LMDB_LIBRARIES}
)
NAMES lmdb
HINTS ${LMDB_ROOT_DIR}/lib
)
find_path(LMDB_INCLUDE_DIRS
NAMES lmdb.h
HINTS ${LMDB_ROOT_DIR}/include
)
NAMES lmdb.h
HINTS ${LMDB_ROOT_DIR}/include
)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(LMDB DEFAULT_MSG
LMDB_LIBRARIES
LMDB_INCLUDE_DIRS
)
LMDB_LIBRARIES
LMDB_INCLUDE_DIRS
)
mark_as_advanced(
LMDB_ROOT_DIR
LMDB_LIBRARIES
LMDB_INCLUDE_DIRS
LMDB_ROOT_DIR
LMDB_LIBRARIES
LMDB_INCLUDE_DIRS
)

View File

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

View File

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

View File

@ -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
squawk.cpp
squawk.h
)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake")
target_include_directories(squawk PRIVATE ${LMDB_INCLUDE_DIRS})
set(CMAKE_AUTOMOC ON)
find_package(Qt5Core CONFIG REQUIRED)
find_package(Qt5Gui CONFIG REQUIRED)
find_package(Qt5Network CONFIG REQUIRED)
find_package(Qt5Xml CONFIG REQUIRED)
find_package(LMDB REQUIRED)
set(squawkCORE_SRC
squawk.cpp
account.cpp
archive.cpp
rosteritem.cpp
contact.cpp
conference.cpp
urlstorage.cpp
networkaccess.cpp
adapterFuctions.cpp
handlers/messagehandler.cpp
handlers/rosterhandler.cpp
)
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()

View File

@ -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);
}
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) {
reconnectScheduled = true;
reconnectTimer->start(500);
client.disconnectFromServer();
} else {
qDebug() << "An attempt to reconnect account" << getName() << "which was not 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);
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);
}
Core::Account::Error Core::Account::getLastError() const {
return lastError;}
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);}

View File

@ -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);
@ -193,6 +177,9 @@ private slots:
void onMamResultsReceived(const QString &queryId, const QXmppResultSetReply &resultSetReply, bool complete);
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);
@ -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);
}

View File

@ -1,5 +1,5 @@
/*
* Squawk messenger.
* Squawk messenger.
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
*
* This program is free software: you can redistribute it and/or modify
@ -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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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) {
data.setTime(time);
if (!newMessage) {
changes.insert("stamp", data.getTime());
}
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 (ri != 0) {
if (newMessage) {
ri->appendMessageToArchive(data);
} else {
ri->changeMessage(id, changes);
}
if (sent) {
pendingStateMessages.insert(std::make_pair(id, jid));
} else {
pendingStateMessages.erase(id);
}
}
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",
@ -552,12 +465,12 @@ void Core::MessageHandler::requestChangeMessage(const QString& jid, const QStrin
if (cnt != 0) {
bool allSupported = true;
QString unsupportedString;
for (QMap<QString, QVariant>::const_iterator itr = data.begin(); itr != data.end(); ++itr) { //I need all this madness
if (allowedToChangeKeys.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
for (QMap<QString, QVariant>::const_iterator itr = data.begin(); itr != data.end(); ++itr) { //I need all this madness
if (allowerToChangeKeys.count(itr.key()) != 1) { //to not allow this method
allSupported = false; //to make a message to look like if it was edited
unsupportedString = itr.key(); //basically I needed to control who exaclty calls this method
break; //because the underlying tech assumes that the change is initiated by user
} //not by system
}
if (allSupported) {
cnt->changeMessage(messageId, data);
@ -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";
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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);
}
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?
}
@ -379,34 +338,9 @@ void Core::NetworkAccess::onUploadFinished()
Transfer* upl = itr->second;
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);
}
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;
}

View File

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

View File

@ -1,9 +1,37 @@
if (WITH_KWALLET)
target_sources(squawk PRIVATE
kwallet.cpp
kwallet.h
cmake_minimum_required(VERSION 3.3)
project(pse)
if (WITH_KWALLET)
set(CMAKE_AUTOMOC ON)
find_package(Qt5Core CONFIG REQUIRED)
find_package(Qt5Gui CONFIG REQUIRED)
get_target_property(KWALLET_INTERFACE_INCLUDE_DIRECTORIES KF5::Wallet INTERFACE_INCLUDE_DIRECTORIES)
get_target_property(Qt5GUI_INTERFACE_INCLUDE_DIRECTORIES Qt5::Gui INTERFACE_INCLUDE_DIRECTORIES)
set(kwalletPSE_SRC
kwallet.cpp
)
add_library(kwalletPSE 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_subdirectory(wrappers)
target_include_directories(squawk PRIVATE $<TARGET_PROPERTY:KF5::Wallet,INTERFACE_INCLUDE_DIRECTORIES>)
endif ()
add_library(kwalletWrapper SHARED ${kwalletW_SRC})
target_include_directories(kwalletWrapper PUBLIC ${KWALLET_INTERFACE_INCLUDE_DIRECTORIES})
target_include_directories(kwalletWrapper PUBLIC ${Qt5GUI_INTERFACE_INCLUDE_DIRECTORIES})
target_link_libraries(kwalletWrapper KF5::Wallet)
target_link_libraries(kwalletWrapper Qt5::Core)
install(TARGETS kwalletWrapper DESTINATION ${CMAKE_INSTALL_LIBDIR})
endif()

View File

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

View File

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

View File

@ -124,19 +124,15 @@ 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) {
last = true;
}
} else {
if (responseCache.front().getId() == firstId) {
last = true;
}
QString firstId = archive->oldestId();
if (responseCache.size() == 0) {
if (requestedBefore == firstId) {
last = true;
}
} else {
if (responseCache.front().getId() == firstId) {
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, "");
}
Shared::Message msg = archive->newest();
emit needHistory("", getId(msg), msg.getTime());
}
break;
case end:

View File

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

View File

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

View File

@ -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,43 +66,39 @@ void Core::Squawk::stop()
{
qDebug("Stopping squawk core..");
network.stop();
if (isInitialized) {
QSettings settings;
settings.beginGroup("core");
settings.beginWriteArray("accounts");
SimpleCrypt crypto(passwordHash);
for (std::deque<Account*>::size_type i = 0; i < accounts.size(); ++i) {
settings.setArrayIndex(i);
Account* acc = accounts[i];
Shared::AccountPassword ap = acc->getPasswordType();
QString password;
switch (ap) {
case Shared::AccountPassword::plain:
password = acc->getPassword();
break;
case Shared::AccountPassword::jammed:
password = crypto.encryptToString(acc->getPassword());
break;
default:
break;
}
settings.setValue("name", acc->getName());
settings.setValue("server", acc->getServer());
settings.setValue("login", acc->getLogin());
settings.setValue("password", password);
settings.setValue("resource", acc->getResource());
settings.setValue("passwordType", static_cast<int>(ap));
settings.setValue("active", acc->getActive());
QSettings settings;
settings.beginGroup("core");
settings.beginWriteArray("accounts");
SimpleCrypt crypto(passwordHash);
for (std::deque<Account*>::size_type i = 0; i < accounts.size(); ++i) {
settings.setArrayIndex(i);
Account* acc = accounts[i];
Shared::AccountPassword ap = acc->getPasswordType();
QString password;
switch (ap) {
case Shared::AccountPassword::plain:
password = acc->getPassword();
break;
case Shared::AccountPassword::jammed:
password = crypto.encryptToString(acc->getPassword());
break;
default:
break;
}
settings.endArray();
settings.endGroup();
settings.sync();
settings.setValue("name", acc->getName());
settings.setValue("server", acc->getServer());
settings.setValue("login", acc->getLogin());
settings.setValue("password", password);
settings.setValue("resource", acc->getResource());
settings.setValue("passwordType", static_cast<int>(ap));
}
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(
@ -135,15 +127,13 @@ void Core::Squawk::addAccount(
const QString& server,
const QString& password,
const QString& name,
const QString& resource,
bool active,
Shared::AccountPassword passwordType)
const QString& resource,
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();
}
itr->second->connect();
}
void Core::Squawk::disconnectAccount(const QString& account)
@ -253,18 +214,14 @@ 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) {
if (acc->getPasswordType() == Shared::AccountPassword::kwallet && kwallet.supportState() == PSE::KWallet::success) {
@ -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;
if (needToReconnect && st != Shared::ConnectionState::disconnected) {
acc->reconnect();
}
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);
}

View File

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

View File

@ -1,8 +0,0 @@
target_sources(squawk PRIVATE
archive.cpp
archive.h
storage.cpp
storage.h
urlstorage.cpp
urlstorage.h
)

View File

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

View File

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

View File

@ -1,7 +0,0 @@
target_sources(squawk PRIVATE
main.cpp
application.cpp
application.h
dialogqueue.cpp
dialogqueue.h
)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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
@ -22,5 +19,5 @@ build() {
}
package() {
cd "$srcdir/squawk"
DESTDIR="$pkgdir/" cmake --build . --target install
DESTDIR="$pkgdir/" cmake --build . --target install
}

View File

@ -1,3 +0,0 @@
configure_file(squawk.desktop squawk.desktop COPYONLY)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk.desktop DESTINATION ${CMAKE_INSTALL_DATADIR}/applications)

View File

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

View File

@ -1,14 +1,26 @@
if (WITH_KIO)
add_library(openFileManagerWindowJob SHARED openfilemanagerwindowjob.cpp)
target_link_libraries(openFileManagerWindowJob PRIVATE KF5::KIOWidgets)
cmake_minimum_required(VERSION 3.3)
project(plugins)
install(TARGETS openFileManagerWindowJob LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})
endif ()
if (WITH_KIO)
set(CMAKE_AUTOMOC ON)
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()

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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()
{
@ -107,7 +95,7 @@ Shared::Global::Global():
}
instance = this;
#ifdef WITH_KIO
openFileManagerWindowJob.load();
if (openFileManagerWindowJob.isLoaded()) {
@ -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<> \

View File

@ -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;
@ -63,7 +61,7 @@ namespace Shared {
};
Global();
static Global* getInstance();
static QString getName(Availability av);
static QString getName(ConnectionState cs);
@ -84,9 +82,6 @@ namespace Shared {
const std::deque<QString> accountPassword;
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
};
}

View File

@ -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;
}
originalMessage = body;
lastModified = correctionDate;
setBody(b);
setBody(body);
setEdited(true);
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -33,9 +33,6 @@ public:
static void intSignalHandler(int unused);
signals:
void interrupt();
public slots:
void handleSigInt();

View File

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

View File

@ -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>&lt;a href=&quot;https://git.macaw.me/blue/squawk&quot;&gt;Project site&lt;/a&gt;</source>
<translation>&lt;a href=&quot;https://git.macaw.me/blue/squawk&quot;&gt;Сайт проекта&lt;/a&gt;</translation>
</message>
<message>
<source>&lt;a href=&quot;https://git.macaw.me/blue/squawk/src/branch/master/LICENSE.md&quot;&gt;License: GNU General Public License version 3&lt;/a&gt;</source>
<translation>&lt;a href=&quot;https://git.macaw.me/blue/squawk/src/branch/master/LICENSE.md&quot;&gt;Лицензия: GNU General Public License версия 3&lt;/a&gt;</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>&lt;a href=&quot;https://git.macaw.me/blue/squawk/issues&quot;&gt;Project bug tracker&lt;/&gt;</source>
<translation>&lt;a href=&quot;https://git.macaw.me/blue/squawk/issues&quot;&gt;Баг-трекер проекта&lt;/&gt;</translation>
</message>
<message>
<source>XMPP (&lt;a href=&quot;xmpp:blue@macaw.me&quot;&gt;blue@macaw.me&lt;/a&gt;)</source>
<translation>XMPP (&lt;a href=&quot;xmpp:blue@macaw.me&quot;&gt;blue@macaw.me&lt;/a&gt;)</translation>
</message>
<message>
<source>E-Mail (&lt;a href=&quot;mailto:blue@macaw.me&quot;&gt;blue@macaw.me&lt;/a&gt;)</source>
<translation>E-Mail (&lt;a href=&quot;mailto:blue@macaw.me&quot;&gt;blue@macaw.me&lt;/a&gt;)</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&apos;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 &quot;Home&quot; or &quot;Work&quot;</source>
<translatorcomment>Tooltip</translatorcomment>
<translation>Имя этой программы для ваших контактов, может быть &quot;Home&quot; или &quot;Phone&quot;</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>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;Noto Sans&apos;; font-size:8pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;Liberation Sans&apos;; font-size:10pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<translation>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;Noto Sans&apos;; font-size:8pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</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&apos;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>

View File

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

View File

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

View File

@ -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:
toOfflineState();
break;
case Shared::ConnectionState::connected:
if (wasEverConnected) {
emit reconnected();
} else {
wasEverConnected = true;
}
break;
default:
break;
if (state == Shared::ConnectionState::disconnected) {
toOfflineState();
}
}
}
@ -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);
}
}

View File

@ -58,9 +58,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);
@ -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;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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,34 +429,22 @@ 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;
Progress::const_iterator itr = uploads.find(msg.getId());
if (itr == uploads.end()) {
att.state = local;
} else {
Progress::const_iterator itr = uploads.find(id);
if (itr == uploads.end()) {
att.state = local;
} else {
att.state = uploading;
att.progress = itr->second;
}
att.state = uploading;
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;
Progress::const_iterator itr = downloads.find(msg.getId());
if (itr == downloads.end()) {
att.state = remote;
} else {
Progress::const_iterator itr = downloads.find(id);
if (itr == downloads.end()) {
att.state = remote;
} else {
att.state = downloading;
att.progress = itr->second;
}
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,22 +532,20 @@ 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);
bool found = false;
while (tItr != tEnd) {
if (id == (*tItr)->getId()) {
found = true;
break;
}
++tItr;
}
if (found) {
int position = indexByTime.rank(tItr);
return createIndex(position, 0, *tItr);
StorageByTime::const_iterator tItr = indexByTime.upper_bound(time);
StorageByTime::const_iterator tBeg = indexByTime.begin();
bool found = false;
while (tItr != tBeg) {
if (id == (*tItr)->getId()) {
found = true;
break;
}
--tItr;
}
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("");
}
}

View File

@ -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);
@ -108,26 +102,12 @@ public:
Error,
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

View File

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

View File

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

View File

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

View File

@ -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));
}
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());
std::map<ElId, Contact*>::iterator cItr = contacts.find(id);
QString accName = acc->getName();
for (const std::pair<const ElId, Contact*>& pair : contacts) {
if (pair.first.account == accName) {
pair.second->handleRecconnect();
if (cItr != contacts.end()) {
return cItr->second;
} else {
std::map<ElId, Room*>::iterator rItr = rooms.find(id);
if (rItr != rooms.end()) {
return rItr->second;
}
}
return NULL;
}
void Models::Roster::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);
}

View File

@ -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,8 +108,7 @@ 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;
std::map<QString, Account*> accounts;

View File

@ -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();
}
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);
emit changeState(av);
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,40 +780,70 @@ 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;
settings.beginGroup("ui");
settings.beginGroup("window");
settings.setValue("geometry", saveGeometry());
settings.setValue("state", saveState());
settings.endGroup();
settings.setValue("splitter", m_ui->splitter->saveState());
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);
QString accName = account->getName();
settings.beginGroup(accName);
settings.setValue("expanded", m_ui->roster->isExpanded(acc));
std::deque<QString> groups = rosterModel.groupList(accName);
for (const QString& groupName : groups) {
settings.beginGroup(groupName);
QModelIndex gIndex = rosterModel.getGroupIndex(accName, groupName);
settings.setValue("expanded", m_ui->roster->isExpanded(gIndex));
settings.endGroup();
}
settings.endGroup();
}
settings.endGroup();
settings.beginGroup("window");
settings.setValue("geometry", saveGeometry());
settings.setValue("state", saveState());
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");
for (int i = 0; i < size; ++i) {
QModelIndex acc = rosterModel.index(i, 0, QModelIndex());
Models::Account* account = rosterModel.accountsModel->getAccount(i);
QString accName = account->getName();
settings.beginGroup(accName);
settings.setValue("expanded", m_ui->roster->isExpanded(acc));
std::deque<QString> groups = rosterModel.groupList(accName);
for (const QString& groupName : groups) {
settings.beginGroup(groupName);
QModelIndex gIndex = rosterModel.getGroupIndex(accName, groupName);
settings.setValue("expanded", m_ui->roster->isExpanded(gIndex));
settings.endGroup();
}
settings.endGroup();
}
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);
}

View File

@ -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>&);
void responsePassword(const QString& account, const QString& password);
void localPathInvalid(const QString& path);
public:
Models::Roster::ElId currentConversationId() const;
void closeCurrentConversation();
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

View File

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

View File

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

View File

@ -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->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);
}
layout->setContentsMargins(2, 2, 2, 2);
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);
}

View File

@ -25,8 +25,6 @@
#include <QIcon>
#include <QPushButton>
#include "shared/utils.h"
/**
* @todo write docs
*/
@ -35,14 +33,9 @@ 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();
@ -52,9 +45,6 @@ private:
QLabel* text;
QPushButton* closeButton;
QHBoxLayout* layout;
private:
void createMandatoryComponents();
public:
struct Comparator {

View File

@ -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 += dateDeviderMargin * 2 + dividerMetrics.height();
if (previousOffset > totalHeight) {
return false;
previousOffset += height;
}
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());
feed->downloadAttachment(messageId);
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;
}

View File

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

View 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
// {
//
// }

View File

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

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