Compare commits
34 Commits
master
...
various-fi
Author | SHA1 | Date | |
---|---|---|---|
0c93aa55f7 | |||
8d88aa5d09 | |||
849159d5a5 | |||
ce047db787 | |||
f45319de25 | |||
ebf0c64ffb | |||
d514db9c4a | |||
f34289399e | |||
05d6761baa | |||
216dcd29e9 | |||
0973cb2991 | |||
50190f3eac | |||
b44873d587 | |||
4c5efad9dc | |||
8310708c92 | |||
d936c0302d | |||
0e937199b0 | |||
48e498be25 | |||
3a7735b192 | |||
8f914c02a7 | |||
50bb3f5fd7 | |||
a0348b8fd2 | |||
85555da81f | |||
ebe5addfb5 | |||
b3c6860e25 | |||
00ffbac6b0 | |||
ff4124d1f0 | |||
15342f3c53 | |||
270a32db9e | |||
e0ef1ef797 | |||
e1eea2f3a2 | |||
e54cff0f0c | |||
4e6bd04b02 | |||
38159eafeb |
@ -6,10 +6,14 @@
|
|||||||
- requesting the history of the current chat after reconnection
|
- requesting the history of the current chat after reconnection
|
||||||
- global availability (in drop down list) gets restored after reconnection
|
- global availability (in drop down list) gets restored after reconnection
|
||||||
- status icon in active chat changes when presence of the pen pal changes
|
- status icon in active chat changes when presence of the pen pal changes
|
||||||
|
- infinite progress when open the dialogue with something that has no history to show
|
||||||
|
|
||||||
### Improvements
|
### Improvements
|
||||||
- slightly reduced the traffic on the startup by not requesting history of all MUCs
|
- slightly reduced the traffic on the startup by not requesting history of all MUCs
|
||||||
|
- completely rewritten message feed, now it works way faster
|
||||||
|
- OPTIONAL RUNTIME dependency: "KIO Widgets" that is supposed to allow you to open a file in your default file manager
|
||||||
|
- show in folder now is supposed to try it's best to show file in folder, even you don't have KIO installed
|
||||||
|
- once uploaded local files don't get second time uploaded - the remote URL is reused
|
||||||
|
|
||||||
## Squawk 0.1.5 (Jul 29, 2020)
|
## Squawk 0.1.5 (Jul 29, 2020)
|
||||||
### Bug fixes
|
### Bug fixes
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
cmake_minimum_required(VERSION 3.0)
|
cmake_minimum_required(VERSION 3.4)
|
||||||
project(squawk)
|
project(squawk)
|
||||||
|
|
||||||
set(CMAKE_INCLUDE_CURRENT_DIR ON)
|
set(CMAKE_INCLUDE_CURRENT_DIR ON)
|
||||||
set(CMAKE_CXX_STANDARD 14)
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
|
|
||||||
set(CMAKE_AUTOMOC ON)
|
set(CMAKE_AUTOMOC ON)
|
||||||
set(CMAKE_AUTOUIC ON)
|
set(CMAKE_AUTOUIC ON)
|
||||||
@ -32,6 +32,7 @@ set(squawk_SRC
|
|||||||
shared/message.cpp
|
shared/message.cpp
|
||||||
shared/vcard.cpp
|
shared/vcard.cpp
|
||||||
shared/icons.cpp
|
shared/icons.cpp
|
||||||
|
shared/messageinfo.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
set(squawk_HEAD
|
set(squawk_HEAD
|
||||||
@ -44,6 +45,7 @@ set(squawk_HEAD
|
|||||||
shared/utils.h
|
shared/utils.h
|
||||||
shared/vcard.h
|
shared/vcard.h
|
||||||
shared/icons.h
|
shared/icons.h
|
||||||
|
shared/messageinfo.h
|
||||||
)
|
)
|
||||||
|
|
||||||
configure_file(resources/images/logo.svg squawk.svg COPYONLY)
|
configure_file(resources/images/logo.svg squawk.svg COPYONLY)
|
||||||
@ -64,6 +66,7 @@ qt5_add_resources(RCC resources/resources.qrc)
|
|||||||
|
|
||||||
option(SYSTEM_QXMPP "Use system qxmpp lib" ON)
|
option(SYSTEM_QXMPP "Use system qxmpp lib" ON)
|
||||||
option(WITH_KWALLET "Build KWallet support module" ON)
|
option(WITH_KWALLET "Build KWallet support module" ON)
|
||||||
|
option(WITH_KIO "Build KIO support module" ON)
|
||||||
|
|
||||||
if (SYSTEM_QXMPP)
|
if (SYSTEM_QXMPP)
|
||||||
find_package(QXmpp CONFIG)
|
find_package(QXmpp CONFIG)
|
||||||
@ -95,8 +98,21 @@ endif()
|
|||||||
add_executable(squawk ${squawk_SRC} ${squawk_HEAD} ${RCC})
|
add_executable(squawk ${squawk_SRC} ${squawk_HEAD} ${RCC})
|
||||||
target_link_libraries(squawk Qt5::Widgets)
|
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(ui)
|
add_subdirectory(ui)
|
||||||
add_subdirectory(core)
|
add_subdirectory(core)
|
||||||
|
add_subdirectory(plugins)
|
||||||
|
|
||||||
add_subdirectory(external/simpleCrypt)
|
add_subdirectory(external/simpleCrypt)
|
||||||
|
|
||||||
@ -104,6 +120,8 @@ target_link_libraries(squawk squawkUI)
|
|||||||
target_link_libraries(squawk squawkCORE)
|
target_link_libraries(squawk squawkCORE)
|
||||||
target_link_libraries(squawk uuid)
|
target_link_libraries(squawk uuid)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
add_dependencies(${CMAKE_PROJECT_NAME} translations)
|
add_dependencies(${CMAKE_PROJECT_NAME} translations)
|
||||||
|
|
||||||
# Install the executable
|
# Install the executable
|
||||||
|
@ -13,7 +13,8 @@
|
|||||||
- lmdb
|
- lmdb
|
||||||
- CMake 3.0 or higher
|
- CMake 3.0 or higher
|
||||||
- qxmpp 1.1.0 or higher
|
- qxmpp 1.1.0 or higher
|
||||||
- kwallet (optional)
|
- KDE Frameworks: kwallet (optional)
|
||||||
|
- KDE Frameworks: KIO (optional)
|
||||||
|
|
||||||
### Getting
|
### Getting
|
||||||
|
|
||||||
@ -67,6 +68,7 @@ Here is the list of keys you can pass to configuration phase of `cmake ..`.
|
|||||||
- `CMAKE_BUILD_TYPE` - `Debug` just builds showing all warnings, `Release` builds with no warnings and applies optimizations (default is `Debug`)
|
- `CMAKE_BUILD_TYPE` - `Debug` just builds showing all warnings, `Release` builds with no warnings and applies optimizations (default is `Debug`)
|
||||||
- `SYSTEM_QXMPP` - `True` tries to link against `qxmpp` installed in the system, `False` builds bundled `qxmpp` library (default is `True`)
|
- `SYSTEM_QXMPP` - `True` tries to link against `qxmpp` installed in the system, `False` builds bundled `qxmpp` library (default is `True`)
|
||||||
- `WITH_KWALLET` - `True` builds the `KWallet` capability module if `KWallet` is installed and if not goes to `False`. `False` disables `KWallet` support (default is `True`)
|
- `WITH_KWALLET` - `True` builds the `KWallet` capability module if `KWallet` is installed and if not goes to `False`. `False` disables `KWallet` support (default is `True`)
|
||||||
|
- `WITH_KIO` - `True` builds the `KIO` capability module if `KIO` is installed and if not goes to `False`. `False` disables `KIO` support (default is `True`)
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
47
cmake/FindLMDB.cmake
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
#This file is taken from here https://gitlab.ralph.or.at/causal-rt/causal-cpp/, it was GPLv3 license
|
||||||
|
#Thank you so much, mr. Ralph Alexander Bariz, I hope you don't mind me using your code
|
||||||
|
|
||||||
|
# Try to find LMDB headers and library.
|
||||||
|
#
|
||||||
|
# Usage of this module as follows:
|
||||||
|
#
|
||||||
|
# find_package(LMDB)
|
||||||
|
#
|
||||||
|
# Variables used by this module, they can change the default behaviour and need
|
||||||
|
# to be set before calling find_package:
|
||||||
|
#
|
||||||
|
# LMDB_ROOT_DIR Set this variable to the root installation of
|
||||||
|
# LMDB if the module has problems finding the
|
||||||
|
# proper installation path.
|
||||||
|
#
|
||||||
|
# Variables defined by this module:
|
||||||
|
#
|
||||||
|
# LMDB_FOUND System has LMDB library/headers.
|
||||||
|
# LMDB_LIBRARIES The LMDB library.
|
||||||
|
# LMDB_INCLUDE_DIRS The location of LMDB headers.
|
||||||
|
|
||||||
|
find_path(LMDB_ROOT_DIR
|
||||||
|
NAMES include/lmdb.h
|
||||||
|
)
|
||||||
|
|
||||||
|
find_library(LMDB_LIBRARIES
|
||||||
|
NAMES lmdb
|
||||||
|
HINTS ${LMDB_ROOT_DIR}/lib
|
||||||
|
)
|
||||||
|
|
||||||
|
find_path(LMDB_INCLUDE_DIRS
|
||||||
|
NAMES lmdb.h
|
||||||
|
HINTS ${LMDB_ROOT_DIR}/include
|
||||||
|
)
|
||||||
|
|
||||||
|
include(FindPackageHandleStandardArgs)
|
||||||
|
find_package_handle_standard_args(LMDB DEFAULT_MSG
|
||||||
|
LMDB_LIBRARIES
|
||||||
|
LMDB_INCLUDE_DIRS
|
||||||
|
)
|
||||||
|
|
||||||
|
mark_as_advanced(
|
||||||
|
LMDB_ROOT_DIR
|
||||||
|
LMDB_LIBRARIES
|
||||||
|
LMDB_INCLUDE_DIRS
|
||||||
|
)
|
@ -1,12 +1,15 @@
|
|||||||
cmake_minimum_required(VERSION 3.0)
|
cmake_minimum_required(VERSION 3.3)
|
||||||
project(squawkCORE)
|
project(squawkCORE)
|
||||||
|
|
||||||
|
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake")
|
||||||
|
|
||||||
set(CMAKE_AUTOMOC ON)
|
set(CMAKE_AUTOMOC ON)
|
||||||
|
|
||||||
find_package(Qt5Core CONFIG REQUIRED)
|
find_package(Qt5Core CONFIG REQUIRED)
|
||||||
find_package(Qt5Gui CONFIG REQUIRED)
|
find_package(Qt5Gui CONFIG REQUIRED)
|
||||||
find_package(Qt5Network CONFIG REQUIRED)
|
find_package(Qt5Network CONFIG REQUIRED)
|
||||||
find_package(Qt5Xml CONFIG REQUIRED)
|
find_package(Qt5Xml CONFIG REQUIRED)
|
||||||
|
find_package(LMDB REQUIRED)
|
||||||
|
|
||||||
set(squawkCORE_SRC
|
set(squawkCORE_SRC
|
||||||
squawk.cpp
|
squawk.cpp
|
||||||
@ -15,7 +18,7 @@ set(squawkCORE_SRC
|
|||||||
rosteritem.cpp
|
rosteritem.cpp
|
||||||
contact.cpp
|
contact.cpp
|
||||||
conference.cpp
|
conference.cpp
|
||||||
storage.cpp
|
urlstorage.cpp
|
||||||
networkaccess.cpp
|
networkaccess.cpp
|
||||||
adapterFuctions.cpp
|
adapterFuctions.cpp
|
||||||
handlers/messagehandler.cpp
|
handlers/messagehandler.cpp
|
||||||
@ -25,7 +28,7 @@ set(squawkCORE_SRC
|
|||||||
add_subdirectory(passwordStorageEngines)
|
add_subdirectory(passwordStorageEngines)
|
||||||
|
|
||||||
# Tell CMake to create the helloworld executable
|
# Tell CMake to create the helloworld executable
|
||||||
add_library(squawkCORE ${squawkCORE_SRC})
|
add_library(squawkCORE STATIC ${squawkCORE_SRC})
|
||||||
|
|
||||||
|
|
||||||
if(SYSTEM_QXMPP)
|
if(SYSTEM_QXMPP)
|
||||||
|
@ -84,8 +84,9 @@ Account::Account(const QString& p_login, const QString& p_server, const QString&
|
|||||||
QObject::connect(dm, &QXmppDiscoveryManager::itemsReceived, this, &Account::onDiscoveryItemsReceived);
|
QObject::connect(dm, &QXmppDiscoveryManager::itemsReceived, this, &Account::onDiscoveryItemsReceived);
|
||||||
QObject::connect(dm, &QXmppDiscoveryManager::infoReceived, this, &Account::onDiscoveryInfoReceived);
|
QObject::connect(dm, &QXmppDiscoveryManager::infoReceived, this, &Account::onDiscoveryInfoReceived);
|
||||||
|
|
||||||
QObject::connect(network, &NetworkAccess::uploadFileComplete, mh, &MessageHandler::onFileUploaded);
|
QObject::connect(network, &NetworkAccess::uploadFileComplete, mh, &MessageHandler::onUploadFileComplete);
|
||||||
QObject::connect(network, &NetworkAccess::uploadFileError, mh, &MessageHandler::onFileUploadError);
|
QObject::connect(network, &NetworkAccess::downloadFileComplete, mh, &MessageHandler::onDownloadFileComplete);
|
||||||
|
QObject::connect(network, &NetworkAccess::loadFileError, mh, &MessageHandler::onLoadFileError);
|
||||||
|
|
||||||
client.addExtension(rcpm);
|
client.addExtension(rcpm);
|
||||||
QObject::connect(rcpm, &QXmppMessageReceiptManager::messageDelivered, mh, &MessageHandler::onReceiptReceived);
|
QObject::connect(rcpm, &QXmppMessageReceiptManager::messageDelivered, mh, &MessageHandler::onReceiptReceived);
|
||||||
@ -155,8 +156,9 @@ Account::~Account()
|
|||||||
reconnectTimer->stop();
|
reconnectTimer->stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
QObject::disconnect(network, &NetworkAccess::uploadFileComplete, mh, &MessageHandler::onFileUploaded);
|
QObject::disconnect(network, &NetworkAccess::uploadFileComplete, mh, &MessageHandler::onUploadFileComplete);
|
||||||
QObject::disconnect(network, &NetworkAccess::uploadFileError, mh, &MessageHandler::onFileUploadError);
|
QObject::disconnect(network, &NetworkAccess::downloadFileComplete, mh, &MessageHandler::onDownloadFileComplete);
|
||||||
|
QObject::disconnect(network, &NetworkAccess::loadFileError, mh, &MessageHandler::onLoadFileError);
|
||||||
|
|
||||||
delete mh;
|
delete mh;
|
||||||
delete rh;
|
delete rh;
|
||||||
@ -402,9 +404,6 @@ QString Core::Account::getFullJid() const {
|
|||||||
void Core::Account::sendMessage(const Shared::Message& data) {
|
void Core::Account::sendMessage(const Shared::Message& data) {
|
||||||
mh->sendMessage(data);}
|
mh->sendMessage(data);}
|
||||||
|
|
||||||
void Core::Account::sendMessage(const Shared::Message& data, const QString& path) {
|
|
||||||
mh->sendMessage(data, path);}
|
|
||||||
|
|
||||||
void Core::Account::onMamMessageReceived(const QString& queryId, const QXmppMessage& msg)
|
void Core::Account::onMamMessageReceived(const QString& queryId, const QXmppMessage& msg)
|
||||||
{
|
{
|
||||||
if (msg.id().size() > 0 && (msg.body().size() > 0 || msg.outOfBandUrl().size() > 0)) {
|
if (msg.id().size() > 0 && (msg.body().size() > 0 || msg.outOfBandUrl().size() > 0)) {
|
||||||
@ -434,13 +433,13 @@ void Core::Account::requestArchive(const QString& jid, int count, const QString&
|
|||||||
|
|
||||||
if (contact == 0) {
|
if (contact == 0) {
|
||||||
qDebug() << "An attempt to request archive for" << jid << "in account" << name << ", but the contact with such id wasn't found, skipping";
|
qDebug() << "An attempt to request archive for" << jid << "in account" << name << ", but the contact with such id wasn't found, skipping";
|
||||||
emit responseArchive(jid, std::list<Shared::Message>());
|
emit responseArchive(jid, std::list<Shared::Message>(), true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state != Shared::ConnectionState::connected) {
|
if (state != Shared::ConnectionState::connected) {
|
||||||
qDebug() << "An attempt to request archive for" << jid << "in account" << name << ", but the account is not online, skipping";
|
qDebug() << "An attempt to request archive for" << jid << "in account" << name << ", but the account is not online, skipping";
|
||||||
emit responseArchive(contact->jid, std::list<Shared::Message>());
|
emit responseArchive(contact->jid, std::list<Shared::Message>(), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
contact->requestHistory(count, before);
|
contact->requestHistory(count, before);
|
||||||
@ -552,9 +551,11 @@ void Core::Account::onClientError(QXmppClient::Error err)
|
|||||||
case QXmppStanza::Error::NotAuthorized:
|
case QXmppStanza::Error::NotAuthorized:
|
||||||
errorText = "Authentication error";
|
errorText = "Authentication error";
|
||||||
break;
|
break;
|
||||||
|
#if (QXMPP_VERSION) < QT_VERSION_CHECK(1, 3, 0)
|
||||||
case QXmppStanza::Error::PaymentRequired:
|
case QXmppStanza::Error::PaymentRequired:
|
||||||
errorText = "Payment is required";
|
errorText = "Payment is required";
|
||||||
break;
|
break;
|
||||||
|
#endif
|
||||||
case QXmppStanza::Error::RecipientUnavailable:
|
case QXmppStanza::Error::RecipientUnavailable:
|
||||||
errorText = "Recipient is unavailable";
|
errorText = "Recipient is unavailable";
|
||||||
break;
|
break;
|
||||||
@ -909,3 +910,16 @@ void Core::Account::handleDisconnection()
|
|||||||
ownVCardRequestInProgress = false;
|
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);}
|
||||||
|
@ -88,7 +88,6 @@ public:
|
|||||||
void setPasswordType(Shared::AccountPassword pt);
|
void setPasswordType(Shared::AccountPassword pt);
|
||||||
QString getFullJid() const;
|
QString getFullJid() const;
|
||||||
void sendMessage(const Shared::Message& data);
|
void sendMessage(const Shared::Message& data);
|
||||||
void sendMessage(const Shared::Message& data, const QString& path);
|
|
||||||
void requestArchive(const QString& jid, int count, const QString& before);
|
void requestArchive(const QString& jid, int count, const QString& before);
|
||||||
void subscribeToContact(const QString& jid, const QString& reason);
|
void subscribeToContact(const QString& jid, const QString& reason);
|
||||||
void unsubscribeFromContact(const QString& jid, const QString& reason);
|
void unsubscribeFromContact(const QString& jid, const QString& reason);
|
||||||
@ -97,6 +96,7 @@ public:
|
|||||||
void addContactToGroupRequest(const QString& jid, const QString& groupName);
|
void addContactToGroupRequest(const QString& jid, const QString& groupName);
|
||||||
void removeContactFromGroupRequest(const QString& jid, const QString& groupName);
|
void removeContactFromGroupRequest(const QString& jid, const QString& groupName);
|
||||||
void renameContactRequest(const QString& jid, const QString& newName);
|
void renameContactRequest(const QString& jid, const QString& newName);
|
||||||
|
void requestChangeMessage(const QString& jid, const QString& messageId, const QMap<QString, QVariant>& data);
|
||||||
|
|
||||||
void setRoomJoined(const QString& jid, bool joined);
|
void setRoomJoined(const QString& jid, bool joined);
|
||||||
void setRoomAutoJoin(const QString& jid, bool joined);
|
void setRoomAutoJoin(const QString& jid, bool joined);
|
||||||
@ -127,14 +127,14 @@ signals:
|
|||||||
void removePresence(const QString& jid, const QString& name);
|
void removePresence(const QString& jid, const QString& name);
|
||||||
void message(const Shared::Message& data);
|
void message(const Shared::Message& data);
|
||||||
void changeMessage(const QString& jid, const QString& id, const QMap<QString, QVariant>& data);
|
void changeMessage(const QString& jid, const QString& id, const QMap<QString, QVariant>& data);
|
||||||
void responseArchive(const QString& jid, const std::list<Shared::Message>& list);
|
void responseArchive(const QString& jid, const std::list<Shared::Message>& list, bool last);
|
||||||
void error(const QString& text);
|
void error(const QString& text);
|
||||||
void addRoomParticipant(const QString& jid, const QString& nickName, const QMap<QString, QVariant>& data);
|
void addRoomParticipant(const QString& jid, const QString& nickName, const QMap<QString, QVariant>& data);
|
||||||
void changeRoomParticipant(const QString& jid, const QString& nickName, const QMap<QString, QVariant>& data);
|
void changeRoomParticipant(const QString& jid, const QString& nickName, const QMap<QString, QVariant>& data);
|
||||||
void removeRoomParticipant(const QString& jid, const QString& nickName);
|
void removeRoomParticipant(const QString& jid, const QString& nickName);
|
||||||
void receivedVCard(const QString& jid, const Shared::VCard& card);
|
void receivedVCard(const QString& jid, const Shared::VCard& card);
|
||||||
void uploadFile(const QFileInfo& file, const QUrl& set, const QUrl& get, QMap<QString, QString> headers);
|
void uploadFile(const QFileInfo& file, const QUrl& set, const QUrl& get, QMap<QString, QString> headers);
|
||||||
void uploadFileError(const QString& messageId, const QString& error);
|
void uploadFileError(const QString& jid, const QString& messageId, const QString& error);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QString name;
|
QString name;
|
||||||
@ -183,6 +183,7 @@ private slots:
|
|||||||
|
|
||||||
void onDiscoveryItemsReceived (const QXmppDiscoveryIq& items);
|
void onDiscoveryItemsReceived (const QXmppDiscoveryIq& items);
|
||||||
void onDiscoveryInfoReceived (const QXmppDiscoveryIq& info);
|
void onDiscoveryInfoReceived (const QXmppDiscoveryIq& info);
|
||||||
|
void onContactHistoryResponse(const std::list<Shared::Message>& list, bool last);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void handleDisconnection();
|
void handleDisconnection();
|
||||||
|
@ -271,6 +271,8 @@ void Core::Archive::changeMessage(const QString& id, const QMap<QString, QVarian
|
|||||||
bool hadStanzaId = msg.getStanzaId().size() > 0;
|
bool hadStanzaId = msg.getStanzaId().size() > 0;
|
||||||
QDateTime oTime = msg.getTime();
|
QDateTime oTime = msg.getTime();
|
||||||
bool idChange = msg.change(data);
|
bool idChange = msg.change(data);
|
||||||
|
QDateTime nTime = msg.getTime();
|
||||||
|
bool orderChange = oTime != nTime;
|
||||||
|
|
||||||
MDB_val lmdbKey, lmdbData;
|
MDB_val lmdbKey, lmdbData;
|
||||||
QByteArray ba;
|
QByteArray ba;
|
||||||
@ -280,15 +282,21 @@ void Core::Archive::changeMessage(const QString& id, const QMap<QString, QVarian
|
|||||||
lmdbKey.mv_size = strId.size();
|
lmdbKey.mv_size = strId.size();
|
||||||
lmdbKey.mv_data = (char*)strId.c_str();
|
lmdbKey.mv_data = (char*)strId.c_str();
|
||||||
int rc;
|
int rc;
|
||||||
|
if (idChange || orderChange) {
|
||||||
if (idChange) {
|
if (idChange) {
|
||||||
rc = mdb_del(txn, main, &lmdbKey, &lmdbData);
|
rc = mdb_del(txn, main, &lmdbKey, &lmdbData);
|
||||||
|
} else {
|
||||||
|
quint64 ostamp = oTime.toMSecsSinceEpoch();
|
||||||
|
lmdbData.mv_data = (quint8*)&ostamp;
|
||||||
|
lmdbData.mv_size = 8;
|
||||||
|
rc = mdb_del(txn, order, &lmdbData, &lmdbKey);
|
||||||
|
}
|
||||||
if (rc == 0) {
|
if (rc == 0) {
|
||||||
strId = msg.getId().toStdString();
|
strId = msg.getId().toStdString();
|
||||||
lmdbKey.mv_size = strId.size();
|
lmdbKey.mv_size = strId.size();
|
||||||
lmdbKey.mv_data = (char*)strId.c_str();
|
lmdbKey.mv_data = (char*)strId.c_str();
|
||||||
|
|
||||||
|
quint64 stamp = nTime.toMSecsSinceEpoch();
|
||||||
quint64 stamp = oTime.toMSecsSinceEpoch();
|
|
||||||
lmdbData.mv_data = (quint8*)&stamp;
|
lmdbData.mv_data = (quint8*)&stamp;
|
||||||
lmdbData.mv_size = 8;
|
lmdbData.mv_size = 8;
|
||||||
rc = mdb_put(txn, order, &lmdbData, &lmdbKey, 0);
|
rc = mdb_put(txn, order, &lmdbData, &lmdbKey, 0);
|
||||||
@ -310,7 +318,7 @@ void Core::Archive::changeMessage(const QString& id, const QMap<QString, QVarian
|
|||||||
if (rc != 0) {
|
if (rc != 0) {
|
||||||
throw Unknown(jid.toStdString(), mdb_strerror(rc));
|
throw Unknown(jid.toStdString(), mdb_strerror(rc));
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
lmdbData.mv_size = ba.size();
|
lmdbData.mv_size = ba.size();
|
||||||
lmdbData.mv_data = (uint8_t*)ba.data();
|
lmdbData.mv_data = (uint8_t*)ba.data();
|
||||||
@ -502,8 +510,9 @@ long unsigned int Core::Archive::size() const
|
|||||||
mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn);
|
mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn);
|
||||||
MDB_stat stat;
|
MDB_stat stat;
|
||||||
mdb_stat(txn, order, &stat);
|
mdb_stat(txn, order, &stat);
|
||||||
|
size_t amount = stat.ms_entries;
|
||||||
mdb_txn_abort(txn);
|
mdb_txn_abort(txn);
|
||||||
return stat.ms_entries;
|
return amount;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::list<Shared::Message> Core::Archive::getBefore(int count, const QString& id)
|
std::list<Shared::Message> Core::Archive::getBefore(int count, const QString& id)
|
||||||
@ -603,10 +612,10 @@ void Core::Archive::setFromTheBeginning(bool is)
|
|||||||
MDB_txn *txn;
|
MDB_txn *txn;
|
||||||
mdb_txn_begin(environment, NULL, 0, &txn);
|
mdb_txn_begin(environment, NULL, 0, &txn);
|
||||||
bool success = setStatValue("beginning", is, txn);
|
bool success = setStatValue("beginning", is, txn);
|
||||||
if (success != 0) {
|
if (success) {
|
||||||
mdb_txn_abort(txn);
|
|
||||||
} else {
|
|
||||||
mdb_txn_commit(txn);
|
mdb_txn_commit(txn);
|
||||||
|
} else {
|
||||||
|
mdb_txn_abort(txn);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,6 @@ Core::MessageHandler::MessageHandler(Core::Account* account):
|
|||||||
QObject(),
|
QObject(),
|
||||||
acc(account),
|
acc(account),
|
||||||
pendingStateMessages(),
|
pendingStateMessages(),
|
||||||
pendingMessages(),
|
|
||||||
uploadingSlotsQueue()
|
uploadingSlotsQueue()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@ -54,7 +53,7 @@ void Core::MessageHandler::onMessageReceived(const QXmppMessage& msg)
|
|||||||
if (cnt != 0) {
|
if (cnt != 0) {
|
||||||
cnt->changeMessage(id, cData);
|
cnt->changeMessage(id, cData);
|
||||||
}
|
}
|
||||||
;
|
|
||||||
emit acc->changeMessage(jid, id, cData);
|
emit acc->changeMessage(jid, id, cData);
|
||||||
pendingStateMessages.erase(itr);
|
pendingStateMessages.erase(itr);
|
||||||
handled = true;
|
handled = true;
|
||||||
@ -168,14 +167,16 @@ void Core::MessageHandler::initializeMessage(Shared::Message& target, const QXmp
|
|||||||
id = source.id();
|
id = source.id();
|
||||||
#endif
|
#endif
|
||||||
target.setId(id);
|
target.setId(id);
|
||||||
if (target.getId().size() == 0) {
|
QString messageId = target.getId();
|
||||||
|
if (messageId.size() == 0) {
|
||||||
target.generateRandomId(); //TODO out of desperation, I need at least a random ID
|
target.generateRandomId(); //TODO out of desperation, I need at least a random ID
|
||||||
|
messageId = target.getId();
|
||||||
}
|
}
|
||||||
target.setFrom(source.from());
|
target.setFrom(source.from());
|
||||||
target.setTo(source.to());
|
target.setTo(source.to());
|
||||||
target.setBody(source.body());
|
target.setBody(source.body());
|
||||||
target.setForwarded(forwarded);
|
target.setForwarded(forwarded);
|
||||||
target.setOutOfBandUrl(source.outOfBandUrl());
|
|
||||||
if (guessing) {
|
if (guessing) {
|
||||||
if (target.getFromJid() == acc->getLogin() + "@" + acc->getServer()) {
|
if (target.getFromJid() == acc->getLogin() + "@" + acc->getServer()) {
|
||||||
outgoing = true;
|
outgoing = true;
|
||||||
@ -189,6 +190,12 @@ void Core::MessageHandler::initializeMessage(Shared::Message& target, const QXmp
|
|||||||
} else {
|
} else {
|
||||||
target.setCurrentTime();
|
target.setCurrentTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString oob = source.outOfBandUrl();
|
||||||
|
if (oob.size() > 0) {
|
||||||
|
target.setAttachPath(acc->network->addMessageAndCheckForPath(oob, acc->getName(), target.getPenPalJid(), messageId));
|
||||||
|
}
|
||||||
|
target.setOutOfBandUrl(oob);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Core::MessageHandler::logMessage(const QXmppMessage& msg, const QString& reason)
|
void Core::MessageHandler::logMessage(const QXmppMessage& msg, const QString& reason)
|
||||||
@ -232,11 +239,23 @@ void Core::MessageHandler::onReceiptReceived(const QString& jid, const QString&
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Core::MessageHandler::sendMessage(Shared::Message data)
|
void Core::MessageHandler::sendMessage(const Shared::Message& data)
|
||||||
|
{
|
||||||
|
if (data.getOutOfBandUrl().size() == 0 && data.getAttachPath().size() > 0) {
|
||||||
|
prepareUpload(data);
|
||||||
|
} else {
|
||||||
|
performSending(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Core::MessageHandler::performSending(Shared::Message data, bool newMessage)
|
||||||
{
|
{
|
||||||
QString jid = data.getPenPalJid();
|
QString jid = data.getPenPalJid();
|
||||||
QString id = data.getId();
|
QString id = data.getId();
|
||||||
|
QString oob = data.getOutOfBandUrl();
|
||||||
RosterItem* ri = acc->rh->getRosterItem(jid);
|
RosterItem* ri = acc->rh->getRosterItem(jid);
|
||||||
|
bool sent = false;
|
||||||
|
QMap<QString, QVariant> changes;
|
||||||
if (acc->state == Shared::ConnectionState::connected) {
|
if (acc->state == Shared::ConnectionState::connected) {
|
||||||
QXmppMessage msg(acc->getFullJid(), data.getTo(), data.getBody(), data.getThread());
|
QXmppMessage msg(acc->getFullJid(), data.getTo(), data.getBody(), data.getThread());
|
||||||
|
|
||||||
@ -245,23 +264,16 @@ void Core::MessageHandler::sendMessage(Shared::Message data)
|
|||||||
#endif
|
#endif
|
||||||
msg.setId(id);
|
msg.setId(id);
|
||||||
msg.setType(static_cast<QXmppMessage::Type>(data.getType())); //it is safe here, my type is compatible
|
msg.setType(static_cast<QXmppMessage::Type>(data.getType())); //it is safe here, my type is compatible
|
||||||
msg.setOutOfBandUrl(data.getOutOfBandUrl());
|
msg.setOutOfBandUrl(oob);
|
||||||
msg.setReceiptRequested(true);
|
msg.setReceiptRequested(true);
|
||||||
|
|
||||||
bool sent = acc->client.sendPacket(msg);
|
sent = acc->client.sendPacket(msg);
|
||||||
|
|
||||||
if (sent) {
|
if (sent) {
|
||||||
data.setState(Shared::Message::State::sent);
|
data.setState(Shared::Message::State::sent);
|
||||||
} else {
|
} else {
|
||||||
data.setState(Shared::Message::State::error);
|
data.setState(Shared::Message::State::error);
|
||||||
data.setErrorText("Couldn't send message via QXMPP library check out logs");
|
data.setErrorText("Couldn't send message: internal QXMPP library error, probably need to check out the logs");
|
||||||
}
|
|
||||||
|
|
||||||
if (ri != 0) {
|
|
||||||
ri->appendMessageToArchive(data);
|
|
||||||
if (sent) {
|
|
||||||
pendingStateMessages.insert(std::make_pair(id, jid));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
@ -269,41 +281,74 @@ void Core::MessageHandler::sendMessage(Shared::Message data)
|
|||||||
data.setErrorText("You are is offline or reconnecting");
|
data.setErrorText("You are is offline or reconnecting");
|
||||||
}
|
}
|
||||||
|
|
||||||
emit acc->changeMessage(jid, id, {
|
Shared::Message::State mstate = data.getState();
|
||||||
{"state", static_cast<uint>(data.getState())},
|
changes.insert("state", static_cast<uint>(mstate));
|
||||||
{"errorText", data.getErrorText()}
|
if (mstate == Shared::Message::State::error) {
|
||||||
});
|
changes.insert("errorText", data.getErrorText());
|
||||||
|
}
|
||||||
|
if (oob.size() > 0) {
|
||||||
|
changes.insert("outOfBandUrl", oob);
|
||||||
|
}
|
||||||
|
if (!newMessage) {
|
||||||
|
changes.insert("stamp", data.getTime());
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
emit acc->changeMessage(jid, id, changes);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Core::MessageHandler::sendMessage(const Shared::Message& data, const QString& path)
|
void Core::MessageHandler::prepareUpload(const Shared::Message& data)
|
||||||
{
|
{
|
||||||
if (acc->state == Shared::ConnectionState::connected) {
|
if (acc->state == Shared::ConnectionState::connected) {
|
||||||
|
QString jid = data.getPenPalJid();
|
||||||
|
QString id = data.getId();
|
||||||
|
RosterItem* ri = acc->rh->getRosterItem(jid);
|
||||||
|
if (!ri) {
|
||||||
|
qDebug() << "An attempt to initialize upload in" << acc->name << "for pal" << jid << "but the object for this pal wasn't found, something went terrebly wrong, skipping send";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
QString path = data.getAttachPath();
|
||||||
QString url = acc->network->getFileRemoteUrl(path);
|
QString url = acc->network->getFileRemoteUrl(path);
|
||||||
if (url.size() != 0) {
|
if (url.size() != 0) {
|
||||||
sendMessageWithLocalUploadedFile(data, url);
|
sendMessageWithLocalUploadedFile(data, url);
|
||||||
} else {
|
} else {
|
||||||
if (acc->network->isUploading(path, data.getId())) {
|
if (acc->network->checkAndAddToUploading(acc->getName(), jid, id, path)) {
|
||||||
pendingMessages.emplace(data.getId(), data);
|
ri->appendMessageToArchive(data);
|
||||||
|
pendingStateMessages.insert(std::make_pair(id, jid));
|
||||||
} else {
|
} else {
|
||||||
if (acc->um->serviceFound()) {
|
if (acc->um->serviceFound()) {
|
||||||
QFileInfo file(path);
|
QFileInfo file(path);
|
||||||
if (file.exists() && file.isReadable()) {
|
if (file.exists() && file.isReadable()) {
|
||||||
uploadingSlotsQueue.emplace_back(path, data);
|
ri->appendMessageToArchive(data);
|
||||||
|
pendingStateMessages.insert(std::make_pair(id, jid));
|
||||||
|
uploadingSlotsQueue.emplace_back(path, id);
|
||||||
if (uploadingSlotsQueue.size() == 1) {
|
if (uploadingSlotsQueue.size() == 1) {
|
||||||
acc->um->requestUploadSlot(file);
|
acc->um->requestUploadSlot(file);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
onFileUploadError(data.getId(), "Uploading file no longer exists or your system user has no permission to read it");
|
handleUploadError(jid, id, "Uploading file no longer exists or your system user has no permission to read it");
|
||||||
qDebug() << "Requested upload slot in account" << acc->name << "for file" << path << "but the file doesn't exist or is not readable";
|
qDebug() << "Requested upload slot in account" << acc->name << "for file" << path << "but the file doesn't exist or is not readable";
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
onFileUploadError(data.getId(), "Your server doesn't support file upload service, or it's prohibited for your account");
|
handleUploadError(jid, id, "Your server doesn't support file upload service, or it's prohibited for your account");
|
||||||
qDebug() << "Requested upload slot in account" << acc->name << "for file" << path << "but upload manager didn't discover any upload services";
|
qDebug() << "Requested upload slot in account" << acc->name << "for file" << path << "but upload manager didn't discover any upload services";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
onFileUploadError(data.getId(), "Account is offline or reconnecting");
|
handleUploadError(data.getPenPalJid(), data.getId(), "Account is offline or reconnecting");
|
||||||
qDebug() << "An attempt to send message with not connected account " << acc->name << ", skipping";
|
qDebug() << "An attempt to send message with not connected account " << acc->name << ", skipping";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -314,12 +359,12 @@ void Core::MessageHandler::onUploadSlotReceived(const QXmppHttpUploadSlotIq& slo
|
|||||||
if (uploadingSlotsQueue.size() == 0) {
|
if (uploadingSlotsQueue.size() == 0) {
|
||||||
qDebug() << "HTTP Upload manager of account" << acc->name << "reports about success requesting upload slot, but none was requested";
|
qDebug() << "HTTP Upload manager of account" << acc->name << "reports about success requesting upload slot, but none was requested";
|
||||||
} else {
|
} else {
|
||||||
const std::pair<QString, Shared::Message>& pair = uploadingSlotsQueue.front();
|
const std::pair<QString, QString>& pair = uploadingSlotsQueue.front();
|
||||||
const QString& mId = pair.second.getId();
|
const QString& mId = pair.second;
|
||||||
acc->network->uploadFile(mId, pair.first, slot.putUrl(), slot.getUrl(), slot.putHeaders());
|
QString palJid = pendingStateMessages.at(mId);
|
||||||
pendingMessages.emplace(mId, pair.second);
|
acc->network->uploadFile({acc->name, palJid, mId}, pair.first, slot.putUrl(), slot.getUrl(), slot.putHeaders());
|
||||||
uploadingSlotsQueue.pop_front();
|
|
||||||
|
|
||||||
|
uploadingSlotsQueue.pop_front();
|
||||||
if (uploadingSlotsQueue.size() > 0) {
|
if (uploadingSlotsQueue.size() > 0) {
|
||||||
acc->um->requestUploadSlot(uploadingSlotsQueue.front().first);
|
acc->um->requestUploadSlot(uploadingSlotsQueue.front().first);
|
||||||
}
|
}
|
||||||
@ -328,44 +373,111 @@ void Core::MessageHandler::onUploadSlotReceived(const QXmppHttpUploadSlotIq& slo
|
|||||||
|
|
||||||
void Core::MessageHandler::onUploadSlotRequestFailed(const QXmppHttpUploadRequestIq& request)
|
void Core::MessageHandler::onUploadSlotRequestFailed(const QXmppHttpUploadRequestIq& request)
|
||||||
{
|
{
|
||||||
|
QString err(request.error().text());
|
||||||
if (uploadingSlotsQueue.size() == 0) {
|
if (uploadingSlotsQueue.size() == 0) {
|
||||||
qDebug() << "HTTP Upload manager of account" << acc->name << "reports about an error requesting upload slot, but none was requested";
|
qDebug() << "HTTP Upload manager of account" << acc->name << "reports about an error requesting upload slot, but none was requested";
|
||||||
qDebug() << request.error().text();
|
qDebug() << err;
|
||||||
} else {
|
} else {
|
||||||
const std::pair<QString, Shared::Message>& pair = uploadingSlotsQueue.front();
|
const std::pair<QString, QString>& pair = uploadingSlotsQueue.front();
|
||||||
qDebug() << "Error requesting upload slot for file" << pair.first << "in account" << acc->name << ":" << request.error().text();
|
qDebug() << "Error requesting upload slot for file" << pair.first << "in account" << acc->name << ":" << err;
|
||||||
emit acc->uploadFileError(pair.second.getId(), "Error requesting slot to upload file: " + request.error().text());
|
handleUploadError(pendingStateMessages.at(pair.second), pair.second, err);
|
||||||
|
|
||||||
|
uploadingSlotsQueue.pop_front();
|
||||||
if (uploadingSlotsQueue.size() > 0) {
|
if (uploadingSlotsQueue.size() > 0) {
|
||||||
acc->um->requestUploadSlot(uploadingSlotsQueue.front().first);
|
acc->um->requestUploadSlot(uploadingSlotsQueue.front().first);
|
||||||
}
|
}
|
||||||
uploadingSlotsQueue.pop_front();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Core::MessageHandler::onFileUploaded(const QString& messageId, const QString& url)
|
void Core::MessageHandler::onDownloadFileComplete(const std::list<Shared::MessageInfo>& msgs, const QString& path)
|
||||||
{
|
{
|
||||||
std::map<QString, Shared::Message>::const_iterator itr = pendingMessages.find(messageId);
|
QMap<QString, QVariant> cData = {
|
||||||
if (itr != pendingMessages.end()) {
|
{"attachPath", path}
|
||||||
sendMessageWithLocalUploadedFile(itr->second, url);
|
};
|
||||||
pendingMessages.erase(itr);
|
for (const Shared::MessageInfo& info : msgs) {
|
||||||
|
if (info.account == acc->getName()) {
|
||||||
|
RosterItem* cnt = acc->rh->getRosterItem(info.jid);
|
||||||
|
if (cnt != 0) {
|
||||||
|
if (cnt->changeMessage(info.messageId, cData)) {
|
||||||
|
emit acc->changeMessage(info.jid, info.messageId, cData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Core::MessageHandler::onFileUploadError(const QString& messageId, const QString& errMsg)
|
void Core::MessageHandler::onLoadFileError(const std::list<Shared::MessageInfo>& msgs, const QString& text, bool up)
|
||||||
{
|
{
|
||||||
std::map<QString, Shared::Message>::const_iterator itr = pendingMessages.find(messageId);
|
if (up) {
|
||||||
if (itr != pendingMessages.end()) {
|
for (const Shared::MessageInfo& info : msgs) {
|
||||||
pendingMessages.erase(itr);
|
if (info.account == acc->getName()) {
|
||||||
|
handleUploadError(info.jid, info.messageId, text);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Core::MessageHandler::sendMessageWithLocalUploadedFile(Shared::Message msg, const QString& url)
|
void Core::MessageHandler::handleUploadError(const QString& jid, const QString& messageId, const QString& errorText)
|
||||||
|
{
|
||||||
|
emit acc->uploadFileError(jid, messageId, "Error requesting slot to upload file: " + errorText);
|
||||||
|
pendingStateMessages.erase(jid);
|
||||||
|
requestChangeMessage(jid, messageId, {
|
||||||
|
{"state", static_cast<uint>(Shared::Message::State::error)},
|
||||||
|
{"errorText", errorText}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void Core::MessageHandler::onUploadFileComplete(const std::list<Shared::MessageInfo>& msgs, const QString& path)
|
||||||
|
{
|
||||||
|
for (const Shared::MessageInfo& info : msgs) {
|
||||||
|
if (info.account == acc->getName()) {
|
||||||
|
RosterItem* ri = acc->rh->getRosterItem(info.jid);
|
||||||
|
if (ri != 0) {
|
||||||
|
Shared::Message msg = ri->getMessage(info.messageId);
|
||||||
|
sendMessageWithLocalUploadedFile(msg, path, false);
|
||||||
|
} else {
|
||||||
|
qDebug() << "A signal received about complete upload to" << acc->name << "for pal" << info.jid << "but the object for this pal wasn't found, something went terrebly wrong, skipping send";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Core::MessageHandler::sendMessageWithLocalUploadedFile(Shared::Message msg, const QString& url, bool newMessage)
|
||||||
{
|
{
|
||||||
msg.setOutOfBandUrl(url);
|
msg.setOutOfBandUrl(url);
|
||||||
if (msg.getBody().size() == 0) {
|
if (msg.getBody().size() == 0) { //not sure why, but most messages do that
|
||||||
msg.setBody(url);
|
msg.setBody(url); //they duplicate oob in body, some of them wouldn't even show an attachment if you don't do that
|
||||||
}
|
}
|
||||||
sendMessage(msg);
|
performSending(msg, newMessage);
|
||||||
//TODO removal/progress update
|
//TODO removal/progress update
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const std::set<QString> allowerToChangeKeys({
|
||||||
|
"attachPath",
|
||||||
|
"outOfBandUrl",
|
||||||
|
"state",
|
||||||
|
"errorText"
|
||||||
|
});
|
||||||
|
|
||||||
|
void Core::MessageHandler::requestChangeMessage(const QString& jid, const QString& messageId, const QMap<QString, QVariant>& data)
|
||||||
|
{
|
||||||
|
RosterItem* cnt = acc->rh->getRosterItem(jid);
|
||||||
|
if (cnt != 0) {
|
||||||
|
bool allSupported = true;
|
||||||
|
QString unsupportedString;
|
||||||
|
for (QMap<QString, QVariant>::const_iterator itr = data.begin(); itr != data.end(); ++itr) { //I need all this madness
|
||||||
|
if (allowerToChangeKeys.count(itr.key()) != 1) { //to not allow this method
|
||||||
|
allSupported = false; //to make a message to look like if it was edited
|
||||||
|
unsupportedString = itr.key(); //basically I needed to control who exaclty calls this method
|
||||||
|
break; //because the underlying tech assumes that the change is initiated by user
|
||||||
|
} //not by system
|
||||||
|
}
|
||||||
|
if (allSupported) {
|
||||||
|
cnt->changeMessage(messageId, data);
|
||||||
|
emit acc->changeMessage(jid, messageId, data);
|
||||||
|
} else {
|
||||||
|
qDebug() << "A request to change message" << messageId << "of conversation" << jid << "with following data" << data;
|
||||||
|
qDebug() << "only limited set of dataFields are supported yet here, and" << unsupportedString << "isn't one of them, skipping";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -28,6 +28,7 @@
|
|||||||
#include <QXmppHttpUploadIq.h>
|
#include <QXmppHttpUploadIq.h>
|
||||||
|
|
||||||
#include <shared/message.h>
|
#include <shared/message.h>
|
||||||
|
#include <shared/messageinfo.h>
|
||||||
|
|
||||||
namespace Core {
|
namespace Core {
|
||||||
|
|
||||||
@ -44,8 +45,7 @@ public:
|
|||||||
MessageHandler(Account* account);
|
MessageHandler(Account* account);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
void sendMessage(Shared::Message data);
|
void sendMessage(const Shared::Message& data);
|
||||||
void sendMessage(const Shared::Message& data, const QString& path);
|
|
||||||
void initializeMessage(Shared::Message& target, const QXmppMessage& source, bool outgoing = false, bool forwarded = false, bool guessing = false) const;
|
void initializeMessage(Shared::Message& target, const QXmppMessage& source, bool outgoing = false, bool forwarded = false, bool guessing = false) const;
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
@ -55,20 +55,24 @@ public slots:
|
|||||||
void onReceiptReceived(const QString& jid, const QString& id);
|
void onReceiptReceived(const QString& jid, const QString& id);
|
||||||
void onUploadSlotReceived(const QXmppHttpUploadSlotIq& slot);
|
void onUploadSlotReceived(const QXmppHttpUploadSlotIq& slot);
|
||||||
void onUploadSlotRequestFailed(const QXmppHttpUploadRequestIq& request);
|
void onUploadSlotRequestFailed(const QXmppHttpUploadRequestIq& request);
|
||||||
void onFileUploaded(const QString& messageId, const QString& url);
|
void onDownloadFileComplete(const std::list<Shared::MessageInfo>& msgs, const QString& path);
|
||||||
void onFileUploadError(const QString& messageId, const QString& errMsg);
|
void onUploadFileComplete(const std::list<Shared::MessageInfo>& msgs, const QString& path);
|
||||||
|
void onLoadFileError(const std::list<Shared::MessageInfo>& msgs, const QString& path, bool up);
|
||||||
|
void requestChangeMessage(const QString& jid, const QString& messageId, const QMap<QString, QVariant>& data);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool handleChatMessage(const QXmppMessage& msg, bool outgoing = false, bool forwarded = false, bool guessing = false);
|
bool handleChatMessage(const QXmppMessage& msg, bool outgoing = false, bool forwarded = false, bool guessing = false);
|
||||||
bool handleGroupMessage(const QXmppMessage& msg, bool outgoing = false, bool forwarded = false, bool guessing = false);
|
bool handleGroupMessage(const QXmppMessage& msg, bool outgoing = false, bool forwarded = false, bool guessing = false);
|
||||||
void logMessage(const QXmppMessage& msg, const QString& reason = "Message wasn't handled: ");
|
void logMessage(const QXmppMessage& msg, const QString& reason = "Message wasn't handled: ");
|
||||||
void sendMessageWithLocalUploadedFile(Shared::Message msg, const QString& url);
|
void sendMessageWithLocalUploadedFile(Shared::Message msg, const QString& url, bool newMessage = true);
|
||||||
|
void performSending(Shared::Message data, bool newMessage = true);
|
||||||
|
void prepareUpload(const Shared::Message& data);
|
||||||
|
void handleUploadError(const QString& jid, const QString& messageId, const QString& errorText);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Account* acc;
|
Account* acc;
|
||||||
std::map<QString, QString> pendingStateMessages;
|
std::map<QString, QString> pendingStateMessages; //key is message id, value is JID
|
||||||
std::map<QString, Shared::Message> pendingMessages;
|
std::deque<std::pair<QString, QString>> uploadingSlotsQueue;
|
||||||
std::deque<std::pair<QString, Shared::Message>> uploadingSlotsQueue;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -190,7 +190,7 @@ void Core::RosterHandler::removeContactRequest(const QString& jid)
|
|||||||
void Core::RosterHandler::handleNewRosterItem(Core::RosterItem* contact)
|
void Core::RosterHandler::handleNewRosterItem(Core::RosterItem* contact)
|
||||||
{
|
{
|
||||||
connect(contact, &RosterItem::needHistory, this->acc, &Account::onContactNeedHistory);
|
connect(contact, &RosterItem::needHistory, this->acc, &Account::onContactNeedHistory);
|
||||||
connect(contact, &RosterItem::historyResponse, this, &RosterHandler::onContactHistoryResponse);
|
connect(contact, &RosterItem::historyResponse, this->acc, &Account::onContactHistoryResponse);
|
||||||
connect(contact, &RosterItem::nameChanged, this, &RosterHandler::onContactNameChanged);
|
connect(contact, &RosterItem::nameChanged, this, &RosterHandler::onContactNameChanged);
|
||||||
connect(contact, &RosterItem::avatarChanged, this, &RosterHandler::onContactAvatarChanged);
|
connect(contact, &RosterItem::avatarChanged, this, &RosterHandler::onContactAvatarChanged);
|
||||||
connect(contact, &RosterItem::requestVCard, this->acc, &Account::requestVCard);
|
connect(contact, &RosterItem::requestVCard, this->acc, &Account::requestVCard);
|
||||||
@ -315,14 +315,6 @@ void Core::RosterHandler::removeFromGroup(const QString& jid, const QString& gro
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Core::RosterHandler::onContactHistoryResponse(const std::list<Shared::Message>& list)
|
|
||||||
{
|
|
||||||
RosterItem* contact = static_cast<RosterItem*>(sender());
|
|
||||||
|
|
||||||
qDebug() << "Collected history for contact " << contact->jid << list.size() << "elements";
|
|
||||||
emit acc->responseArchive(contact->jid, list);
|
|
||||||
}
|
|
||||||
|
|
||||||
Core::RosterItem * Core::RosterHandler::getRosterItem(const QString& jid)
|
Core::RosterItem * Core::RosterHandler::getRosterItem(const QString& jid)
|
||||||
{
|
{
|
||||||
RosterItem* item = 0;
|
RosterItem* item = 0;
|
||||||
|
@ -86,7 +86,6 @@ private slots:
|
|||||||
void onContactGroupRemoved(const QString& group);
|
void onContactGroupRemoved(const QString& group);
|
||||||
void onContactNameChanged(const QString& name);
|
void onContactNameChanged(const QString& name);
|
||||||
void onContactSubscriptionStateChanged(Shared::SubscriptionState state);
|
void onContactSubscriptionStateChanged(Shared::SubscriptionState state);
|
||||||
void onContactHistoryResponse(const std::list<Shared::Message>& list);
|
|
||||||
void onContactAvatarChanged(Shared::Avatar, const QString& path);
|
void onContactAvatarChanged(Shared::Avatar, const QString& path);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -16,13 +16,17 @@
|
|||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#include <QtWidgets/QApplication>
|
||||||
|
#include <QtCore/QDir>
|
||||||
|
|
||||||
#include "networkaccess.h"
|
#include "networkaccess.h"
|
||||||
|
|
||||||
Core::NetworkAccess::NetworkAccess(QObject* parent):
|
Core::NetworkAccess::NetworkAccess(QObject* parent):
|
||||||
QObject(parent),
|
QObject(parent),
|
||||||
running(false),
|
running(false),
|
||||||
manager(0),
|
manager(0),
|
||||||
files("files"),
|
storage("fileURLStorage"),
|
||||||
downloads(),
|
downloads(),
|
||||||
uploads()
|
uploads()
|
||||||
{
|
{
|
||||||
@ -33,60 +37,31 @@ Core::NetworkAccess::~NetworkAccess()
|
|||||||
stop();
|
stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Core::NetworkAccess::fileLocalPathRequest(const QString& messageId, const QString& url)
|
void Core::NetworkAccess::downladFile(const QString& url)
|
||||||
{
|
{
|
||||||
std::map<QString, Transfer*>::iterator itr = downloads.find(url);
|
std::map<QString, Transfer*>::iterator itr = downloads.find(url);
|
||||||
if (itr != downloads.end()) {
|
if (itr != downloads.end()) {
|
||||||
Transfer* dwn = itr->second;
|
qDebug() << "NetworkAccess received a request to download a file" << url << ", but the file is currently downloading, skipping";
|
||||||
std::set<QString>::const_iterator mItr = dwn->messages.find(messageId);
|
|
||||||
if (mItr == dwn->messages.end()) {
|
|
||||||
dwn->messages.insert(messageId);
|
|
||||||
}
|
|
||||||
emit downloadFileProgress(messageId, dwn->progress);
|
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
QString path = files.getRecord(url);
|
std::pair<QString, std::list<Shared::MessageInfo>> p = storage.getPath(url);
|
||||||
QFileInfo info(path);
|
if (p.first.size() > 0) {
|
||||||
|
QFileInfo info(p.first);
|
||||||
if (info.exists() && info.isFile()) {
|
if (info.exists() && info.isFile()) {
|
||||||
emit fileLocalPathResponse(messageId, path);
|
emit downloadFileComplete(p.second, p.first);
|
||||||
} else {
|
} else {
|
||||||
files.removeRecord(url);
|
startDownload(p.second, url);
|
||||||
emit fileLocalPathResponse(messageId, "");
|
}
|
||||||
|
} else {
|
||||||
|
startDownload(p.second, url);
|
||||||
}
|
}
|
||||||
} catch (const Archive::NotFound& e) {
|
} catch (const Archive::NotFound& e) {
|
||||||
emit fileLocalPathResponse(messageId, "");
|
qDebug() << "NetworkAccess received a request to download a file" << url << ", but there is now record of which message uses that file, downloading anyway";
|
||||||
|
storage.addFile(url);
|
||||||
|
startDownload(std::list<Shared::MessageInfo>(), url);
|
||||||
} catch (const Archive::Unknown& e) {
|
} catch (const Archive::Unknown& e) {
|
||||||
qDebug() << "Error requesting file path:" << e.what();
|
qDebug() << "Error requesting file path:" << e.what();
|
||||||
emit fileLocalPathResponse(messageId, "");
|
emit loadFileError(std::list<Shared::MessageInfo>(), QString("Database error: ") + e.what(), false);
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Core::NetworkAccess::downladFileRequest(const QString& messageId, const QString& url)
|
|
||||||
{
|
|
||||||
std::map<QString, Transfer*>::iterator itr = downloads.find(url);
|
|
||||||
if (itr != downloads.end()) {
|
|
||||||
Transfer* dwn = itr->second;
|
|
||||||
std::set<QString>::const_iterator mItr = dwn->messages.find(messageId);
|
|
||||||
if (mItr == dwn->messages.end()) {
|
|
||||||
dwn->messages.insert(messageId);
|
|
||||||
}
|
|
||||||
emit downloadFileProgress(messageId, dwn->progress);
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
QString path = files.getRecord(url);
|
|
||||||
QFileInfo info(path);
|
|
||||||
if (info.exists() && info.isFile()) {
|
|
||||||
emit fileLocalPathResponse(messageId, path);
|
|
||||||
} else {
|
|
||||||
files.removeRecord(url);
|
|
||||||
startDownload(messageId, url);
|
|
||||||
}
|
|
||||||
} catch (const Archive::NotFound& e) {
|
|
||||||
startDownload(messageId, url);
|
|
||||||
} catch (const Archive::Unknown& e) {
|
|
||||||
qDebug() << "Error requesting file path:" << e.what();
|
|
||||||
emit downloadFileError(messageId, QString("Database error: ") + e.what());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -95,7 +70,7 @@ void Core::NetworkAccess::start()
|
|||||||
{
|
{
|
||||||
if (!running) {
|
if (!running) {
|
||||||
manager = new QNetworkAccessManager();
|
manager = new QNetworkAccessManager();
|
||||||
files.open();
|
storage.open();
|
||||||
running = true;
|
running = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -103,7 +78,7 @@ void Core::NetworkAccess::start()
|
|||||||
void Core::NetworkAccess::stop()
|
void Core::NetworkAccess::stop()
|
||||||
{
|
{
|
||||||
if (running) {
|
if (running) {
|
||||||
files.close();
|
storage.close();
|
||||||
manager->deleteLater();
|
manager->deleteLater();
|
||||||
manager = 0;
|
manager = 0;
|
||||||
running = false;
|
running = false;
|
||||||
@ -128,9 +103,7 @@ void Core::NetworkAccess::onDownloadProgress(qint64 bytesReceived, qint64 bytesT
|
|||||||
qreal total = bytesTotal;
|
qreal total = bytesTotal;
|
||||||
qreal progress = received/total;
|
qreal progress = received/total;
|
||||||
dwn->progress = progress;
|
dwn->progress = progress;
|
||||||
for (std::set<QString>::const_iterator mItr = dwn->messages.begin(), end = dwn->messages.end(); mItr != end; ++mItr) {
|
emit loadFileProgress(dwn->messages, progress, false);
|
||||||
emit downloadFileProgress(*mItr, progress);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -146,9 +119,7 @@ void Core::NetworkAccess::onDownloadError(QNetworkReply::NetworkError code)
|
|||||||
if (errorText.size() > 0) {
|
if (errorText.size() > 0) {
|
||||||
itr->second->success = false;
|
itr->second->success = false;
|
||||||
Transfer* dwn = itr->second;
|
Transfer* dwn = itr->second;
|
||||||
for (std::set<QString>::const_iterator mItr = dwn->messages.begin(), end = dwn->messages.end(); mItr != end; ++mItr) {
|
emit loadFileError(dwn->messages, errorText, false);
|
||||||
emit downloadFileError(*mItr, errorText);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -276,50 +247,43 @@ QString Core::NetworkAccess::getErrorText(QNetworkReply::NetworkError code)
|
|||||||
|
|
||||||
void Core::NetworkAccess::onDownloadFinished()
|
void Core::NetworkAccess::onDownloadFinished()
|
||||||
{
|
{
|
||||||
QString path("");
|
|
||||||
QNetworkReply* rpl = static_cast<QNetworkReply*>(sender());
|
QNetworkReply* rpl = static_cast<QNetworkReply*>(sender());
|
||||||
QString url = rpl->url().toString();
|
QString url = rpl->url().toString();
|
||||||
std::map<QString, Transfer*>::const_iterator itr = downloads.find(url);
|
std::map<QString, Transfer*>::const_iterator itr = downloads.find(url);
|
||||||
if (itr == downloads.end()) {
|
if (itr == downloads.end()) {
|
||||||
qDebug() << "an error downloading" << url << ": the request is done but seems like noone is waiting for it, skipping";
|
qDebug() << "an error downloading" << url << ": the request is done but there is no record of it being downloaded, ignoring";
|
||||||
} else {
|
} else {
|
||||||
Transfer* dwn = itr->second;
|
Transfer* dwn = itr->second;
|
||||||
if (dwn->success) {
|
if (dwn->success) {
|
||||||
qDebug() << "download success for" << url;
|
qDebug() << "download success for" << url;
|
||||||
QStringList hops = url.split("/");
|
QStringList hops = url.split("/");
|
||||||
QString fileName = hops.back();
|
QString fileName = hops.back();
|
||||||
QStringList parts = fileName.split(".");
|
QString jid;
|
||||||
path = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation) + "/";
|
if (dwn->messages.size() > 0) {
|
||||||
QString suffix("");
|
jid = dwn->messages.front().jid;
|
||||||
QStringList::const_iterator sItr = parts.begin();
|
|
||||||
QString realName = *sItr;
|
|
||||||
++sItr;
|
|
||||||
for (QStringList::const_iterator sEnd = parts.end(); sItr != sEnd; ++sItr) {
|
|
||||||
suffix += "." + (*sItr);
|
|
||||||
}
|
|
||||||
QString postfix("");
|
|
||||||
QFileInfo proposedName(path + realName + postfix + suffix);
|
|
||||||
int counter = 0;
|
|
||||||
while (proposedName.exists()) {
|
|
||||||
postfix = QString("(") + std::to_string(++counter).c_str() + ")";
|
|
||||||
proposedName = QFileInfo(path + realName + postfix + suffix);
|
|
||||||
}
|
}
|
||||||
|
QString path = prepareDirectory(jid);
|
||||||
|
if (path.size() > 0) {
|
||||||
|
path = checkFileName(fileName, path);
|
||||||
|
|
||||||
path = proposedName.absoluteFilePath();
|
|
||||||
QFile file(path);
|
QFile file(path);
|
||||||
if (file.open(QIODevice::WriteOnly)) {
|
if (file.open(QIODevice::WriteOnly)) {
|
||||||
file.write(dwn->reply->readAll());
|
file.write(dwn->reply->readAll());
|
||||||
file.close();
|
file.close();
|
||||||
files.addRecord(url, path);
|
storage.setPath(url, path);
|
||||||
qDebug() << "file" << path << "was successfully downloaded";
|
qDebug() << "file" << path << "was successfully downloaded";
|
||||||
} else {
|
} else {
|
||||||
qDebug() << "couldn't save file" << path;
|
qDebug() << "couldn't save file" << path;
|
||||||
path = "";
|
path = QString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (std::set<QString>::const_iterator mItr = dwn->messages.begin(), end = dwn->messages.end(); mItr != end; ++mItr) {
|
if (path.size() > 0) {
|
||||||
emit fileLocalPathResponse(*mItr, path);
|
emit downloadFileComplete(dwn->messages, path);
|
||||||
|
} else {
|
||||||
|
//TODO do I need to handle the failure here or it's already being handled in error?
|
||||||
|
//emit loadFileError(dwn->messages, path, false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dwn->reply->deleteLater();
|
dwn->reply->deleteLater();
|
||||||
@ -328,9 +292,9 @@ void Core::NetworkAccess::onDownloadFinished()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Core::NetworkAccess::startDownload(const QString& messageId, const QString& url)
|
void Core::NetworkAccess::startDownload(const std::list<Shared::MessageInfo>& msgs, const QString& url)
|
||||||
{
|
{
|
||||||
Transfer* dwn = new Transfer({{messageId}, 0, 0, true, "", url, 0});
|
Transfer* dwn = new Transfer({msgs, 0, 0, true, "", url, 0});
|
||||||
QNetworkRequest req(url);
|
QNetworkRequest req(url);
|
||||||
dwn->reply = manager->get(req);
|
dwn->reply = manager->get(req);
|
||||||
connect(dwn->reply, &QNetworkReply::downloadProgress, this, &NetworkAccess::onDownloadProgress);
|
connect(dwn->reply, &QNetworkReply::downloadProgress, this, &NetworkAccess::onDownloadProgress);
|
||||||
@ -341,7 +305,7 @@ void Core::NetworkAccess::startDownload(const QString& messageId, const QString&
|
|||||||
#endif
|
#endif
|
||||||
connect(dwn->reply, &QNetworkReply::finished, this, &NetworkAccess::onDownloadFinished);
|
connect(dwn->reply, &QNetworkReply::finished, this, &NetworkAccess::onDownloadFinished);
|
||||||
downloads.insert(std::make_pair(url, dwn));
|
downloads.insert(std::make_pair(url, dwn));
|
||||||
emit downloadFileProgress(messageId, 0);
|
emit loadFileProgress(dwn->messages, 0, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Core::NetworkAccess::onUploadError(QNetworkReply::NetworkError code)
|
void Core::NetworkAccess::onUploadError(QNetworkReply::NetworkError code)
|
||||||
@ -350,16 +314,16 @@ void Core::NetworkAccess::onUploadError(QNetworkReply::NetworkError code)
|
|||||||
QString url = rpl->url().toString();
|
QString url = rpl->url().toString();
|
||||||
std::map<QString, Transfer*>::const_iterator itr = uploads.find(url);
|
std::map<QString, Transfer*>::const_iterator itr = uploads.find(url);
|
||||||
if (itr == uploads.end()) {
|
if (itr == uploads.end()) {
|
||||||
qDebug() << "an error uploading" << url << ": the request is reporting an error but seems like noone is waiting for it, skipping";
|
qDebug() << "an error uploading" << url << ": the request is reporting an error but there is no record of it being uploading, ignoring";
|
||||||
} else {
|
} else {
|
||||||
QString errorText = getErrorText(code);
|
QString errorText = getErrorText(code);
|
||||||
if (errorText.size() > 0) {
|
if (errorText.size() > 0) {
|
||||||
itr->second->success = false;
|
itr->second->success = false;
|
||||||
Transfer* upl = itr->second;
|
Transfer* upl = itr->second;
|
||||||
for (std::set<QString>::const_iterator mItr = upl->messages.begin(), end = upl->messages.end(); mItr != end; ++mItr) {
|
emit loadFileError(upl->messages, errorText, true);
|
||||||
emit uploadFileError(*mItr, errorText);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//TODO deletion?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -369,17 +333,14 @@ void Core::NetworkAccess::onUploadFinished()
|
|||||||
QString url = rpl->url().toString();
|
QString url = rpl->url().toString();
|
||||||
std::map<QString, Transfer*>::const_iterator itr = uploads.find(url);
|
std::map<QString, Transfer*>::const_iterator itr = uploads.find(url);
|
||||||
if (itr == downloads.end()) {
|
if (itr == downloads.end()) {
|
||||||
qDebug() << "an error uploading" << url << ": the request is done but seems like no one is waiting for it, skipping";
|
qDebug() << "an error uploading" << url << ": the request is done there is no record of it being uploading, ignoring";
|
||||||
} else {
|
} else {
|
||||||
Transfer* upl = itr->second;
|
Transfer* upl = itr->second;
|
||||||
if (upl->success) {
|
if (upl->success) {
|
||||||
qDebug() << "upload success for" << url;
|
qDebug() << "upload success for" << url;
|
||||||
files.addRecord(upl->url, upl->path);
|
|
||||||
|
|
||||||
for (std::set<QString>::const_iterator mItr = upl->messages.begin(), end = upl->messages.end(); mItr != end; ++mItr) {
|
storage.addFile(upl->messages, upl->url, upl->path);
|
||||||
emit fileLocalPathResponse(*mItr, upl->path);
|
emit uploadFileComplete(upl->messages, upl->url);
|
||||||
emit uploadFileComplete(*mItr, upl->url);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
upl->reply->deleteLater();
|
upl->reply->deleteLater();
|
||||||
@ -403,94 +364,29 @@ void Core::NetworkAccess::onUploadProgress(qint64 bytesReceived, qint64 bytesTot
|
|||||||
qreal total = bytesTotal;
|
qreal total = bytesTotal;
|
||||||
qreal progress = received/total;
|
qreal progress = received/total;
|
||||||
upl->progress = progress;
|
upl->progress = progress;
|
||||||
for (std::set<QString>::const_iterator mItr = upl->messages.begin(), end = upl->messages.end(); mItr != end; ++mItr) {
|
emit loadFileProgress(upl->messages, progress, true);
|
||||||
emit uploadFileProgress(*mItr, progress);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Core::NetworkAccess::startUpload(const QString& messageId, const QString& url, const QString& path)
|
|
||||||
{
|
|
||||||
Transfer* upl = new Transfer({{messageId}, 0, 0, true, path, url, 0});
|
|
||||||
QNetworkRequest req(url);
|
|
||||||
QFile* file = new QFile(path);
|
|
||||||
if (file->open(QIODevice::ReadOnly)) {
|
|
||||||
upl->reply = manager->put(req, file);
|
|
||||||
|
|
||||||
connect(upl->reply, &QNetworkReply::uploadProgress, this, &NetworkAccess::onUploadProgress);
|
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
|
|
||||||
connect(upl->reply, qOverload<QNetworkReply::NetworkError>(&QNetworkReply::errorOccurred), this, &NetworkAccess::onUploadError);
|
|
||||||
#else
|
|
||||||
connect(upl->reply, qOverload<QNetworkReply::NetworkError>(&QNetworkReply::error), this, &NetworkAccess::onUploadError);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
connect(upl->reply, &QNetworkReply::finished, this, &NetworkAccess::onUploadFinished);
|
|
||||||
uploads.insert(std::make_pair(url, upl));
|
|
||||||
emit downloadFileProgress(messageId, 0);
|
|
||||||
} else {
|
|
||||||
qDebug() << "couldn't upload file" << path;
|
|
||||||
emit uploadFileError(messageId, "Error opening file");
|
|
||||||
delete file;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Core::NetworkAccess::uploadFileRequest(const QString& messageId, const QString& url, const QString& path)
|
|
||||||
{
|
|
||||||
std::map<QString, Transfer*>::iterator itr = uploads.find(url);
|
|
||||||
if (itr != uploads.end()) {
|
|
||||||
Transfer* upl = itr->second;
|
|
||||||
std::set<QString>::const_iterator mItr = upl->messages.find(messageId);
|
|
||||||
if (mItr == upl->messages.end()) {
|
|
||||||
upl->messages.insert(messageId);
|
|
||||||
}
|
|
||||||
emit uploadFileProgress(messageId, upl->progress);
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
QString ePath = files.getRecord(url);
|
|
||||||
if (ePath == path) {
|
|
||||||
QFileInfo info(path);
|
|
||||||
if (info.exists() && info.isFile()) {
|
|
||||||
emit fileLocalPathResponse(messageId, path);
|
|
||||||
} else {
|
|
||||||
files.removeRecord(url);
|
|
||||||
startUpload(messageId, url, path);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
QFileInfo info(path);
|
|
||||||
if (info.exists() && info.isFile()) {
|
|
||||||
files.changeRecord(url, path);
|
|
||||||
emit fileLocalPathResponse(messageId, path);
|
|
||||||
} else {
|
|
||||||
files.removeRecord(url);
|
|
||||||
startUpload(messageId, url, path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (const Archive::NotFound& e) {
|
|
||||||
startUpload(messageId, url, path);
|
|
||||||
} catch (const Archive::Unknown& e) {
|
|
||||||
qDebug() << "Error requesting file path on upload:" << e.what();
|
|
||||||
emit uploadFileError(messageId, QString("Database error: ") + e.what());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QString Core::NetworkAccess::getFileRemoteUrl(const QString& path)
|
QString Core::NetworkAccess::getFileRemoteUrl(const QString& path)
|
||||||
{
|
{
|
||||||
return ""; //TODO this is a way not to upload some file more then 1 time, here I'm supposed to return that file GET url
|
QString p;
|
||||||
|
|
||||||
|
try {
|
||||||
|
p = storage.getUrl(path);
|
||||||
|
} catch (const Archive::NotFound& err) {
|
||||||
|
|
||||||
|
} catch (...) {
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
return p;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Core::NetworkAccess::isUploading(const QString& path, const QString& messageId)
|
void Core::NetworkAccess::uploadFile(const Shared::MessageInfo& info, const QString& path, const QUrl& put, const QUrl& get, const QMap<QString, QString> headers)
|
||||||
{
|
|
||||||
return false; //TODO this is a way to avoid parallel uploading of the same files by different chats
|
|
||||||
// message is is supposed to be added to the uploading messageids list
|
|
||||||
// the result should be true if there was an uploading file with this path
|
|
||||||
// message id can be empty, then it's just to check and not to add
|
|
||||||
}
|
|
||||||
|
|
||||||
void Core::NetworkAccess::uploadFile(const QString& messageId, const QString& path, const QUrl& put, const QUrl& get, const QMap<QString, QString> headers)
|
|
||||||
{
|
{
|
||||||
QFile* file = new QFile(path);
|
QFile* file = new QFile(path);
|
||||||
Transfer* upl = new Transfer({{messageId}, 0, 0, true, path, get.toString(), file});
|
Transfer* upl = new Transfer({{info}, 0, 0, true, path, get.toString(), file});
|
||||||
QNetworkRequest req(put);
|
QNetworkRequest req(put);
|
||||||
for (QMap<QString, QString>::const_iterator itr = headers.begin(), end = headers.end(); itr != end; itr++) {
|
for (QMap<QString, QString>::const_iterator itr = headers.begin(), end = headers.end(); itr != end; itr++) {
|
||||||
req.setRawHeader(itr.key().toUtf8(), itr.value().toUtf8());
|
req.setRawHeader(itr.key().toUtf8(), itr.value().toUtf8());
|
||||||
@ -506,10 +402,99 @@ void Core::NetworkAccess::uploadFile(const QString& messageId, const QString& pa
|
|||||||
#endif
|
#endif
|
||||||
connect(upl->reply, &QNetworkReply::finished, this, &NetworkAccess::onUploadFinished);
|
connect(upl->reply, &QNetworkReply::finished, this, &NetworkAccess::onUploadFinished);
|
||||||
uploads.insert(std::make_pair(put.toString(), upl));
|
uploads.insert(std::make_pair(put.toString(), upl));
|
||||||
emit downloadFileProgress(messageId, 0);
|
emit loadFileProgress(upl->messages, 0, true);
|
||||||
} else {
|
} else {
|
||||||
qDebug() << "couldn't upload file" << path;
|
qDebug() << "couldn't upload file" << path;
|
||||||
emit uploadFileError(messageId, "Error opening file");
|
emit loadFileError(upl->messages, "Error opening file", true);
|
||||||
delete file;
|
delete file;
|
||||||
|
delete upl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Core::NetworkAccess::registerFile(const QString& url, const QString& account, const QString& jid, const QString& id)
|
||||||
|
{
|
||||||
|
storage.addFile(url, account, jid, id);
|
||||||
|
std::map<QString, Transfer*>::iterator itr = downloads.find(url);
|
||||||
|
if (itr != downloads.end()) {
|
||||||
|
itr->second->messages.emplace_back(account, jid, id); //TODO notification is going to happen the next tick, is that okay?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Core::NetworkAccess::registerFile(const QString& url, const QString& path, const QString& account, const QString& jid, const QString& id)
|
||||||
|
{
|
||||||
|
storage.addFile(url, path, account, jid, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Core::NetworkAccess::checkAndAddToUploading(const QString& acc, const QString& jid, const QString id, const QString path)
|
||||||
|
{
|
||||||
|
for (const std::pair<const QString, Transfer*>& pair : uploads) {
|
||||||
|
Transfer* info = pair.second;
|
||||||
|
if (pair.second->path == path) {
|
||||||
|
std::list<Shared::MessageInfo>& messages = info->messages;
|
||||||
|
bool dup = false;
|
||||||
|
for (const Shared::MessageInfo& info : messages) {
|
||||||
|
if (info.account == acc && info.jid == jid && info.messageId == id) {
|
||||||
|
dup = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!dup) {
|
||||||
|
info->messages.emplace_back(acc, jid, id); //TODO notification is going to happen the next tick, is that okay?
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString Core::NetworkAccess::prepareDirectory(const QString& jid)
|
||||||
|
{
|
||||||
|
QString path = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation);
|
||||||
|
path += "/" + QApplication::applicationName();
|
||||||
|
if (jid.size() > 0) {
|
||||||
|
path += "/" + jid;
|
||||||
|
}
|
||||||
|
QDir location(path);
|
||||||
|
|
||||||
|
if (!location.exists()) {
|
||||||
|
bool res = location.mkpath(path);
|
||||||
|
if (!res) {
|
||||||
|
return "";
|
||||||
|
} else {
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString Core::NetworkAccess::checkFileName(const QString& name, const QString& path)
|
||||||
|
{
|
||||||
|
QStringList parts = name.split(".");
|
||||||
|
QString suffix("");
|
||||||
|
QStringList::const_iterator sItr = parts.begin();
|
||||||
|
QString realName = *sItr;
|
||||||
|
++sItr;
|
||||||
|
for (QStringList::const_iterator sEnd = parts.end(); sItr != sEnd; ++sItr) {
|
||||||
|
suffix += "." + (*sItr);
|
||||||
|
}
|
||||||
|
QString postfix("");
|
||||||
|
QFileInfo proposedName(path + "/" + realName + suffix);
|
||||||
|
int counter = 0;
|
||||||
|
while (proposedName.exists()) {
|
||||||
|
QString count = QString("(") + std::to_string(++counter).c_str() + ")";
|
||||||
|
proposedName = QFileInfo(path + "/" + realName + count + suffix);
|
||||||
|
}
|
||||||
|
|
||||||
|
return proposedName.absoluteFilePath();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString Core::NetworkAccess::addMessageAndCheckForPath(const QString& url, const QString& account, const QString& jid, const QString& id)
|
||||||
|
{
|
||||||
|
return storage.addMessageAndCheckForPath(url, account, jid, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::list<Shared::MessageInfo> Core::NetworkAccess::reportPathInvalid(const QString& path)
|
||||||
|
{
|
||||||
|
return storage.deletedFile(path);
|
||||||
|
}
|
||||||
|
@ -29,13 +29,15 @@
|
|||||||
|
|
||||||
#include <set>
|
#include <set>
|
||||||
|
|
||||||
#include "storage.h"
|
#include "urlstorage.h"
|
||||||
|
|
||||||
namespace Core {
|
namespace Core {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @todo write docs
|
* @todo write docs
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
//TODO Need to describe how to get rid of records when file is no longer reachable;
|
||||||
class NetworkAccess : public QObject
|
class NetworkAccess : public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
@ -48,26 +50,27 @@ public:
|
|||||||
void stop();
|
void stop();
|
||||||
|
|
||||||
QString getFileRemoteUrl(const QString& path);
|
QString getFileRemoteUrl(const QString& path);
|
||||||
bool isUploading(const QString& path, const QString& messageId = "");
|
QString addMessageAndCheckForPath(const QString& url, const QString& account, const QString& jid, const QString& id);
|
||||||
void uploadFile(const QString& messageId, const QString& path, const QUrl& put, const QUrl& get, const QMap<QString, QString> headers);
|
void uploadFile(const Shared::MessageInfo& info, const QString& path, const QUrl& put, const QUrl& get, const QMap<QString, QString> headers);
|
||||||
|
bool checkAndAddToUploading(const QString& acc, const QString& jid, const QString id, const QString path);
|
||||||
|
std::list<Shared::MessageInfo> reportPathInvalid(const QString& path);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void fileLocalPathResponse(const QString& messageId, const QString& path);
|
void loadFileProgress(const std::list<Shared::MessageInfo>& msgs, qreal value, bool up);
|
||||||
void downloadFileProgress(const QString& messageId, qreal value);
|
void loadFileError(const std::list<Shared::MessageInfo>& msgs, const QString& text, bool up);
|
||||||
void downloadFileError(const QString& messageId, const QString& path);
|
void uploadFileComplete(const std::list<Shared::MessageInfo>& msgs, const QString& url);
|
||||||
void uploadFileProgress(const QString& messageId, qreal value);
|
void downloadFileComplete(const std::list<Shared::MessageInfo>& msgs, const QString& path);
|
||||||
void uploadFileError(const QString& messageId, const QString& path);
|
|
||||||
void uploadFileComplete(const QString& messageId, const QString& url);
|
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void fileLocalPathRequest(const QString& messageId, const QString& url);
|
void downladFile(const QString& url);
|
||||||
void downladFileRequest(const QString& messageId, const QString& url);
|
void registerFile(const QString& url, const QString& account, const QString& jid, const QString& id);
|
||||||
void uploadFileRequest(const QString& messageId, const QString& url, const QString& path);
|
void registerFile(const QString& url, const QString& path, const QString& account, const QString& jid, const QString& id);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void startDownload(const QString& messageId, const QString& url);
|
void startDownload(const std::list<Shared::MessageInfo>& msgs, const QString& url);
|
||||||
void startUpload(const QString& messageId, const QString& url, const QString& path);
|
|
||||||
QString getErrorText(QNetworkReply::NetworkError code);
|
QString getErrorText(QNetworkReply::NetworkError code);
|
||||||
|
QString prepareDirectory(const QString& jid);
|
||||||
|
QString checkFileName(const QString& name, const QString& path);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal);
|
void onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal);
|
||||||
@ -80,12 +83,12 @@ private slots:
|
|||||||
private:
|
private:
|
||||||
bool running;
|
bool running;
|
||||||
QNetworkAccessManager* manager;
|
QNetworkAccessManager* manager;
|
||||||
Storage files;
|
UrlStorage storage;
|
||||||
std::map<QString, Transfer*> downloads;
|
std::map<QString, Transfer*> downloads;
|
||||||
std::map<QString, Transfer*> uploads;
|
std::map<QString, Transfer*> uploads;
|
||||||
|
|
||||||
struct Transfer {
|
struct Transfer {
|
||||||
std::set<QString> messages;
|
std::list<Shared::MessageInfo> messages;
|
||||||
qreal progress;
|
qreal progress;
|
||||||
QNetworkReply* reply;
|
QNetworkReply* reply;
|
||||||
bool success;
|
bool success;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
cmake_minimum_required(VERSION 3.0)
|
cmake_minimum_required(VERSION 3.3)
|
||||||
project(pse)
|
project(pse)
|
||||||
|
|
||||||
if (WITH_KWALLET)
|
if (WITH_KWALLET)
|
||||||
@ -14,7 +14,7 @@ if (WITH_KWALLET)
|
|||||||
kwallet.cpp
|
kwallet.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
add_library(kwalletPSE ${kwalletPSE_SRC})
|
add_library(kwalletPSE STATIC ${kwalletPSE_SRC})
|
||||||
|
|
||||||
target_include_directories(kwalletPSE PUBLIC ${KWALLET_INTERFACE_INCLUDE_DIRECTORIES})
|
target_include_directories(kwalletPSE PUBLIC ${KWALLET_INTERFACE_INCLUDE_DIRECTORIES})
|
||||||
target_include_directories(kwalletPSE PUBLIC ${Qt5GUI_INTERFACE_INCLUDE_DIRECTORIES})
|
target_include_directories(kwalletPSE PUBLIC ${Qt5GUI_INTERFACE_INCLUDE_DIRECTORIES})
|
||||||
|
@ -122,7 +122,22 @@ void Core::RosterItem::nextRequest()
|
|||||||
{
|
{
|
||||||
if (syncronizing) {
|
if (syncronizing) {
|
||||||
if (requestedCount != -1) {
|
if (requestedCount != -1) {
|
||||||
emit historyResponse(responseCache);
|
bool last = false;
|
||||||
|
if (archiveState == beginning || archiveState == complete) {
|
||||||
|
QString firstId = archive->oldestId();
|
||||||
|
if (responseCache.size() == 0) {
|
||||||
|
if (requestedBefore == firstId) {
|
||||||
|
last = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (responseCache.front().getId() == firstId) {
|
||||||
|
last = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (archiveState == empty && responseCache.size() == 0) {
|
||||||
|
last = true;
|
||||||
|
}
|
||||||
|
emit historyResponse(responseCache, last);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (requestCache.size() > 0) {
|
if (requestCache.size() > 0) {
|
||||||
@ -360,6 +375,11 @@ void Core::RosterItem::flushMessagesToArchive(bool finished, const QString& firs
|
|||||||
archiveState = complete;
|
archiveState = complete;
|
||||||
archive->setFromTheBeginning(true);
|
archive->setFromTheBeginning(true);
|
||||||
}
|
}
|
||||||
|
if (added == 0 && wasEmpty) {
|
||||||
|
archiveState = empty;
|
||||||
|
nextRequest();
|
||||||
|
break;
|
||||||
|
}
|
||||||
if (requestedCount != -1) {
|
if (requestedCount != -1) {
|
||||||
QString before;
|
QString before;
|
||||||
if (responseCache.size() > 0) {
|
if (responseCache.size() > 0) {
|
||||||
@ -378,7 +398,7 @@ void Core::RosterItem::flushMessagesToArchive(bool finished, const QString& firs
|
|||||||
} catch (const Archive::Empty& e) {
|
} catch (const Archive::Empty& e) {
|
||||||
|
|
||||||
}
|
}
|
||||||
if (!found || requestedCount > responseCache.size()) {
|
if (!found || requestedCount > int(responseCache.size())) {
|
||||||
if (archiveState == complete) {
|
if (archiveState == complete) {
|
||||||
nextRequest();
|
nextRequest();
|
||||||
} else {
|
} else {
|
||||||
@ -529,7 +549,7 @@ void Core::RosterItem::clearArchiveRequests()
|
|||||||
requestedBefore = "";
|
requestedBefore = "";
|
||||||
for (const std::pair<int, QString>& pair : requestCache) {
|
for (const std::pair<int, QString>& pair : requestCache) {
|
||||||
if (pair.first != -1) {
|
if (pair.first != -1) {
|
||||||
emit historyResponse(responseCache); //just to notify those who still waits with whatever happened to be left in caches yet
|
emit historyResponse(responseCache, false); //just to notify those who still waits with whatever happened to be left in caches yet
|
||||||
}
|
}
|
||||||
responseCache.clear();
|
responseCache.clear();
|
||||||
}
|
}
|
||||||
@ -549,3 +569,20 @@ void Core::RosterItem::downgradeDatabaseState()
|
|||||||
archiveState = ArchiveState::chunk;
|
archiveState = ArchiveState::chunk;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Shared::Message Core::RosterItem::getMessage(const QString& id)
|
||||||
|
{
|
||||||
|
for (const Shared::Message& msg : appendCache) {
|
||||||
|
if (msg.getId() == id) {
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Shared::Message& msg : hisoryCache) {
|
||||||
|
if (msg.getId() == id) {
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return archive->getElement(id);
|
||||||
|
}
|
||||||
|
@ -78,10 +78,12 @@ public:
|
|||||||
void clearArchiveRequests();
|
void clearArchiveRequests();
|
||||||
void downgradeDatabaseState();
|
void downgradeDatabaseState();
|
||||||
|
|
||||||
|
Shared::Message getMessage(const QString& id);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void nameChanged(const QString& name);
|
void nameChanged(const QString& name);
|
||||||
void subscriptionStateChanged(Shared::SubscriptionState state);
|
void subscriptionStateChanged(Shared::SubscriptionState state);
|
||||||
void historyResponse(const std::list<Shared::Message>& messages);
|
void historyResponse(const std::list<Shared::Message>& messages, bool last);
|
||||||
void needHistory(const QString& before, const QString& after, const QDateTime& afterTime = QDateTime());
|
void needHistory(const QString& before, const QString& after, const QDateTime& afterTime = QDateTime());
|
||||||
void avatarChanged(Shared::Avatar, const QString& path);
|
void avatarChanged(Shared::Avatar, const QString& path);
|
||||||
void requestVCard(const QString& jid);
|
void requestVCard(const QString& jid);
|
||||||
|
@ -32,11 +32,10 @@ Core::Squawk::Squawk(QObject* parent):
|
|||||||
,kwallet()
|
,kwallet()
|
||||||
#endif
|
#endif
|
||||||
{
|
{
|
||||||
connect(&network, &NetworkAccess::fileLocalPathResponse, this, &Squawk::fileLocalPathResponse);
|
connect(&network, &NetworkAccess::loadFileProgress, this, &Squawk::fileProgress);
|
||||||
connect(&network, &NetworkAccess::downloadFileProgress, this, &Squawk::downloadFileProgress);
|
connect(&network, &NetworkAccess::loadFileError, this, &Squawk::fileError);
|
||||||
connect(&network, &NetworkAccess::downloadFileError, this, &Squawk::downloadFileError);
|
connect(&network, &NetworkAccess::downloadFileComplete, this, &Squawk::fileDownloadComplete);
|
||||||
connect(&network, &NetworkAccess::uploadFileProgress, this, &Squawk::uploadFileProgress);
|
connect(&network, &NetworkAccess::uploadFileComplete, this, &Squawk::fileUploadComplete);
|
||||||
connect(&network, &NetworkAccess::uploadFileError, this, &Squawk::uploadFileError);
|
|
||||||
|
|
||||||
#ifdef WITH_KWALLET
|
#ifdef WITH_KWALLET
|
||||||
if (kwallet.supportState() == PSE::KWallet::success) {
|
if (kwallet.supportState() == PSE::KWallet::success) {
|
||||||
@ -168,7 +167,7 @@ void Core::Squawk::addAccount(
|
|||||||
|
|
||||||
connect(acc, &Account::receivedVCard, this, &Squawk::responseVCard);
|
connect(acc, &Account::receivedVCard, this, &Squawk::responseVCard);
|
||||||
|
|
||||||
connect(acc, &Account::uploadFileError, this, &Squawk::uploadFileError);
|
connect(acc, &Account::uploadFileError, this, &Squawk::onAccountUploadFileError);
|
||||||
|
|
||||||
QMap<QString, QVariant> map = {
|
QMap<QString, QVariant> map = {
|
||||||
{"login", login},
|
{"login", login},
|
||||||
@ -336,17 +335,6 @@ void Core::Squawk::sendMessage(const QString& account, const Shared::Message& da
|
|||||||
itr->second->sendMessage(data);
|
itr->second->sendMessage(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Core::Squawk::sendMessage(const QString& account, const Shared::Message& data, const QString& path)
|
|
||||||
{
|
|
||||||
AccountsMap::const_iterator itr = amap.find(account);
|
|
||||||
if (itr == amap.end()) {
|
|
||||||
qDebug("An attempt to send a message with non existing account, skipping");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
itr->second->sendMessage(data, path);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Core::Squawk::requestArchive(const QString& account, const QString& jid, int count, const QString& before)
|
void Core::Squawk::requestArchive(const QString& account, const QString& jid, int count, const QString& before)
|
||||||
{
|
{
|
||||||
AccountsMap::const_iterator itr = amap.find(account);
|
AccountsMap::const_iterator itr = amap.find(account);
|
||||||
@ -357,10 +345,10 @@ void Core::Squawk::requestArchive(const QString& account, const QString& jid, in
|
|||||||
itr->second->requestArchive(jid, count, before);
|
itr->second->requestArchive(jid, count, before);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Core::Squawk::onAccountResponseArchive(const QString& jid, const std::list<Shared::Message>& list)
|
void Core::Squawk::onAccountResponseArchive(const QString& jid, const std::list<Shared::Message>& list, bool last)
|
||||||
{
|
{
|
||||||
Account* acc = static_cast<Account*>(sender());
|
Account* acc = static_cast<Account*>(sender());
|
||||||
emit responseArchive(acc->getName(), jid, list);
|
emit responseArchive(acc->getName(), jid, list, last);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Core::Squawk::modifyAccountRequest(const QString& name, const QMap<QString, QVariant>& map)
|
void Core::Squawk::modifyAccountRequest(const QString& name, const QMap<QString, QVariant>& map)
|
||||||
@ -604,14 +592,9 @@ void Core::Squawk::addRoomRequest(const QString& account, const QString& jid, co
|
|||||||
itr->second->addRoomRequest(jid, nick, password, autoJoin);
|
itr->second->addRoomRequest(jid, nick, password, autoJoin);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Core::Squawk::fileLocalPathRequest(const QString& messageId, const QString& url)
|
void Core::Squawk::fileDownloadRequest(const QString& url)
|
||||||
{
|
{
|
||||||
network.fileLocalPathRequest(messageId, url);
|
network.downladFile(url);
|
||||||
}
|
|
||||||
|
|
||||||
void Core::Squawk::downloadFileRequest(const QString& messageId, const QString& url)
|
|
||||||
{
|
|
||||||
network.downladFileRequest(messageId, url);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Core::Squawk::addContactToGroupRequest(const QString& account, const QString& jid, const QString& groupName)
|
void Core::Squawk::addContactToGroupRequest(const QString& account, const QString& jid, const QString& groupName)
|
||||||
@ -762,3 +745,26 @@ void Core::Squawk::onWalletResponsePassword(const QString& login, const QString&
|
|||||||
emit changeAccount(login, {{"password", password}});
|
emit changeAccount(login, {{"password", password}});
|
||||||
accountReady();
|
accountReady();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Core::Squawk::onAccountUploadFileError(const QString& jid, const QString id, const QString& errorText)
|
||||||
|
{
|
||||||
|
Account* acc = static_cast<Account*>(sender());
|
||||||
|
emit fileError({{acc->getName(), jid, id}}, errorText, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Core::Squawk::onLocalPathInvalid(const QString& path)
|
||||||
|
{
|
||||||
|
std::list<Shared::MessageInfo> list = network.reportPathInvalid(path);
|
||||||
|
|
||||||
|
QMap<QString, QVariant> data({
|
||||||
|
{"attachPath", ""}
|
||||||
|
});
|
||||||
|
for (const Shared::MessageInfo& info : list) {
|
||||||
|
AccountsMap::const_iterator itr = amap.find(info.account);
|
||||||
|
if (itr != amap.end()) {
|
||||||
|
itr->second->requestChangeMessage(info.jid, info.messageId, data);
|
||||||
|
} else {
|
||||||
|
qDebug() << "Reacting on failure to reach file" << path << "there was an attempt to change message in account" << info.account << "which doesn't exist, skipping";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -51,31 +51,39 @@ public:
|
|||||||
signals:
|
signals:
|
||||||
void quit();
|
void quit();
|
||||||
void ready();
|
void ready();
|
||||||
|
|
||||||
void newAccount(const QMap<QString, QVariant>&);
|
void newAccount(const QMap<QString, QVariant>&);
|
||||||
void changeAccount(const QString& account, const QMap<QString, QVariant>& data);
|
void changeAccount(const QString& account, const QMap<QString, QVariant>& data);
|
||||||
void removeAccount(const QString& account);
|
void removeAccount(const QString& account);
|
||||||
|
|
||||||
void addGroup(const QString& account, const QString& name);
|
void addGroup(const QString& account, const QString& name);
|
||||||
void removeGroup(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 addContact(const QString& account, const QString& jid, const QString& group, const QMap<QString, QVariant>& data);
|
||||||
void removeContact(const QString& account, const QString& jid);
|
void removeContact(const QString& account, const QString& jid);
|
||||||
void removeContact(const QString& account, const QString& jid, const QString& group);
|
void removeContact(const QString& account, const QString& jid, const QString& group);
|
||||||
void changeContact(const QString& account, const QString& jid, const QMap<QString, QVariant>& data);
|
void 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 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 removePresence(const QString& account, const QString& jid, const QString& name);
|
||||||
|
|
||||||
void stateChanged(Shared::Availability state);
|
void stateChanged(Shared::Availability state);
|
||||||
|
|
||||||
void accountMessage(const QString& account, const Shared::Message& data);
|
void accountMessage(const QString& account, const Shared::Message& data);
|
||||||
void responseArchive(const QString& account, const QString& jid, const std::list<Shared::Message>& list);
|
void responseArchive(const QString& account, const QString& jid, const std::list<Shared::Message>& list, bool last);
|
||||||
|
|
||||||
void addRoom(const QString& account, const QString jid, const QMap<QString, QVariant>& data);
|
void 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 changeRoom(const QString& account, const QString jid, const QMap<QString, QVariant>& data);
|
||||||
void removeRoom(const QString& account, const QString jid);
|
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 addRoomParticipant(const QString& account, const QString& jid, const QString& name, const QMap<QString, QVariant>& data);
|
||||||
void changeRoomParticipant(const QString& account, const QString& jid, const QString& name, const QMap<QString, QVariant>& data);
|
void changeRoomParticipant(const QString& account, const QString& jid, const QString& name, const QMap<QString, QVariant>& data);
|
||||||
void removeRoomParticipant(const QString& account, const QString& jid, const QString& name);
|
void removeRoomParticipant(const QString& account, const QString& jid, const QString& name);
|
||||||
void fileLocalPathResponse(const QString& messageId, const QString& path);
|
|
||||||
void downloadFileError(const QString& messageId, const QString& error);
|
void fileError(const std::list<Shared::MessageInfo> msgs, const QString& error, bool up);
|
||||||
void downloadFileProgress(const QString& messageId, qreal value);
|
void fileProgress(const std::list<Shared::MessageInfo> msgs, qreal value, bool up);
|
||||||
void uploadFileError(const QString& messageId, const QString& error);
|
void fileDownloadComplete(const std::list<Shared::MessageInfo> msgs, const QString& path);
|
||||||
void uploadFileProgress(const QString& messageId, qreal value);
|
void fileUploadComplete(const std::list<Shared::MessageInfo> msgs, const QString& path);
|
||||||
|
|
||||||
void responseVCard(const QString& jid, const Shared::VCard& card);
|
void responseVCard(const QString& jid, const Shared::VCard& card);
|
||||||
void changeMessage(const QString& account, const QString& jid, const QString& id, const QMap<QString, QVariant>& data);
|
void changeMessage(const QString& account, const QString& jid, const QString& id, const QMap<QString, QVariant>& data);
|
||||||
void requestPassword(const QString& account);
|
void requestPassword(const QString& account);
|
||||||
@ -83,15 +91,18 @@ signals:
|
|||||||
public slots:
|
public slots:
|
||||||
void start();
|
void start();
|
||||||
void stop();
|
void stop();
|
||||||
|
|
||||||
void newAccountRequest(const QMap<QString, QVariant>& map);
|
void newAccountRequest(const QMap<QString, QVariant>& map);
|
||||||
void modifyAccountRequest(const QString& name, const QMap<QString, QVariant>& map);
|
void modifyAccountRequest(const QString& name, const QMap<QString, QVariant>& map);
|
||||||
void removeAccountRequest(const QString& name);
|
void removeAccountRequest(const QString& name);
|
||||||
void connectAccount(const QString& account);
|
void connectAccount(const QString& account);
|
||||||
void disconnectAccount(const QString& account);
|
void disconnectAccount(const QString& account);
|
||||||
|
|
||||||
void changeState(Shared::Availability state);
|
void changeState(Shared::Availability state);
|
||||||
|
|
||||||
void sendMessage(const QString& account, const Shared::Message& data);
|
void sendMessage(const QString& account, const Shared::Message& data);
|
||||||
void sendMessage(const QString& account, const Shared::Message& data, const QString& path);
|
|
||||||
void requestArchive(const QString& account, const QString& jid, int count, const QString& before);
|
void requestArchive(const QString& account, const QString& jid, int count, const QString& before);
|
||||||
|
|
||||||
void subscribeContact(const QString& account, const QString& jid, const QString& reason);
|
void subscribeContact(const QString& account, const QString& jid, const QString& reason);
|
||||||
void unsubscribeContact(const QString& account, const QString& jid, const QString& reason);
|
void unsubscribeContact(const QString& account, const QString& jid, const QString& reason);
|
||||||
void addContactToGroupRequest(const QString& account, const QString& jid, const QString& groupName);
|
void addContactToGroupRequest(const QString& account, const QString& jid, const QString& groupName);
|
||||||
@ -99,15 +110,18 @@ public slots:
|
|||||||
void removeContactRequest(const QString& account, const QString& jid);
|
void removeContactRequest(const QString& account, const QString& jid);
|
||||||
void renameContactRequest(const QString& account, const QString& jid, const QString& newName);
|
void renameContactRequest(const QString& account, const QString& jid, const QString& newName);
|
||||||
void addContactRequest(const QString& account, const QString& jid, const QString& name, const QSet<QString>& groups);
|
void addContactRequest(const QString& account, const QString& jid, const QString& name, const QSet<QString>& groups);
|
||||||
|
|
||||||
void setRoomJoined(const QString& account, const QString& jid, bool joined);
|
void setRoomJoined(const QString& account, const QString& jid, bool joined);
|
||||||
void setRoomAutoJoin(const QString& account, const QString& jid, bool joined);
|
void setRoomAutoJoin(const QString& account, const QString& jid, bool joined);
|
||||||
void addRoomRequest(const QString& account, const QString& jid, const QString& nick, const QString& password, bool autoJoin);
|
void addRoomRequest(const QString& account, const QString& jid, const QString& nick, const QString& password, bool autoJoin);
|
||||||
void removeRoomRequest(const QString& account, const QString& jid);
|
void removeRoomRequest(const QString& account, const QString& jid);
|
||||||
void fileLocalPathRequest(const QString& messageId, const QString& url);
|
|
||||||
void downloadFileRequest(const QString& messageId, const QString& url);
|
void fileDownloadRequest(const QString& url);
|
||||||
|
|
||||||
void requestVCard(const QString& account, const QString& jid);
|
void requestVCard(const QString& account, const QString& jid);
|
||||||
void uploadVCard(const QString& account, const Shared::VCard& card);
|
void uploadVCard(const QString& account, const Shared::VCard& card);
|
||||||
void responsePassword(const QString& account, const QString& password);
|
void responsePassword(const QString& account, const QString& password);
|
||||||
|
void onLocalPathInvalid(const QString& path);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
typedef std::deque<Account*> Accounts;
|
typedef std::deque<Account*> Accounts;
|
||||||
@ -146,7 +160,7 @@ private slots:
|
|||||||
void onAccountAddPresence(const QString& jid, const QString& name, const QMap<QString, QVariant>& data);
|
void onAccountAddPresence(const QString& jid, const QString& name, const QMap<QString, QVariant>& data);
|
||||||
void onAccountRemovePresence(const QString& jid, const QString& name);
|
void onAccountRemovePresence(const QString& jid, const QString& name);
|
||||||
void onAccountMessage(const Shared::Message& data);
|
void onAccountMessage(const Shared::Message& data);
|
||||||
void onAccountResponseArchive(const QString& jid, const std::list<Shared::Message>& list);
|
void onAccountResponseArchive(const QString& jid, const std::list<Shared::Message>& list, bool last);
|
||||||
void onAccountAddRoom(const QString jid, const QMap<QString, QVariant>& data);
|
void onAccountAddRoom(const QString jid, const QMap<QString, QVariant>& data);
|
||||||
void onAccountChangeRoom(const QString jid, const QMap<QString, QVariant>& data);
|
void onAccountChangeRoom(const QString jid, const QMap<QString, QVariant>& data);
|
||||||
void onAccountRemoveRoom(const QString jid);
|
void onAccountRemoveRoom(const QString jid);
|
||||||
@ -155,6 +169,8 @@ private slots:
|
|||||||
void onAccountRemoveRoomPresence(const QString& jid, const QString& nick);
|
void onAccountRemoveRoomPresence(const QString& jid, const QString& nick);
|
||||||
void onAccountChangeMessage(const QString& jid, const QString& id, const QMap<QString, QVariant>& data);
|
void onAccountChangeMessage(const QString& jid, const QString& id, const QMap<QString, QVariant>& data);
|
||||||
|
|
||||||
|
void onAccountUploadFileError(const QString& jid, const QString id, const QString& errorText);
|
||||||
|
|
||||||
void onWalletOpened(bool success);
|
void onWalletOpened(bool success);
|
||||||
void onWalletResponsePassword(const QString& login, const QString& password);
|
void onWalletResponsePassword(const QString& login, const QString& password);
|
||||||
void onWalletRejectPassword(const QString& login);
|
void onWalletRejectPassword(const QString& login);
|
||||||
|
491
core/urlstorage.cpp
Normal file
@ -0,0 +1,491 @@
|
|||||||
|
/*
|
||||||
|
* Squawk messenger.
|
||||||
|
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <QStandardPaths>
|
||||||
|
#include <QDir>
|
||||||
|
#include <QDebug>
|
||||||
|
|
||||||
|
#include "urlstorage.h"
|
||||||
|
|
||||||
|
Core::UrlStorage::UrlStorage(const QString& p_name):
|
||||||
|
name(p_name),
|
||||||
|
opened(false),
|
||||||
|
environment(),
|
||||||
|
base(),
|
||||||
|
map()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Core::UrlStorage::~UrlStorage()
|
||||||
|
{
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Core::UrlStorage::open()
|
||||||
|
{
|
||||||
|
if (!opened) {
|
||||||
|
mdb_env_create(&environment);
|
||||||
|
QString path(QStandardPaths::writableLocation(QStandardPaths::CacheLocation));
|
||||||
|
path += "/" + name;
|
||||||
|
QDir cache(path);
|
||||||
|
|
||||||
|
if (!cache.exists()) {
|
||||||
|
bool res = cache.mkpath(path);
|
||||||
|
if (!res) {
|
||||||
|
throw Archive::Directory(path.toStdString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mdb_env_set_maxdbs(environment, 2);
|
||||||
|
mdb_env_set_mapsize(environment, 10UL * 1024UL * 1024UL);
|
||||||
|
mdb_env_open(environment, path.toStdString().c_str(), 0, 0664);
|
||||||
|
|
||||||
|
MDB_txn *txn;
|
||||||
|
mdb_txn_begin(environment, NULL, 0, &txn);
|
||||||
|
mdb_dbi_open(txn, "base", MDB_CREATE, &base);
|
||||||
|
mdb_dbi_open(txn, "map", MDB_CREATE, &map);
|
||||||
|
mdb_txn_commit(txn);
|
||||||
|
opened = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Core::UrlStorage::close()
|
||||||
|
{
|
||||||
|
if (opened) {
|
||||||
|
mdb_dbi_close(environment, map);
|
||||||
|
mdb_dbi_close(environment, base);
|
||||||
|
mdb_env_close(environment);
|
||||||
|
opened = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Core::UrlStorage::writeInfo(const QString& key, const Core::UrlStorage::UrlInfo& info, bool overwrite)
|
||||||
|
{
|
||||||
|
MDB_txn *txn;
|
||||||
|
mdb_txn_begin(environment, NULL, 0, &txn);
|
||||||
|
|
||||||
|
try {
|
||||||
|
writeInfo(key, info, txn, overwrite);
|
||||||
|
mdb_txn_commit(txn);
|
||||||
|
} catch (...) {
|
||||||
|
mdb_txn_abort(txn);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Core::UrlStorage::writeInfo(const QString& key, const Core::UrlStorage::UrlInfo& info, MDB_txn* txn, bool overwrite)
|
||||||
|
{
|
||||||
|
QByteArray ba;
|
||||||
|
QDataStream ds(&ba, QIODevice::WriteOnly);
|
||||||
|
info.serialize(ds);
|
||||||
|
|
||||||
|
const std::string& id = key.toStdString();
|
||||||
|
MDB_val lmdbKey, lmdbData;
|
||||||
|
lmdbKey.mv_size = id.size();
|
||||||
|
lmdbKey.mv_data = (char*)id.c_str();
|
||||||
|
lmdbData.mv_size = ba.size();
|
||||||
|
lmdbData.mv_data = (uint8_t*)ba.data();
|
||||||
|
|
||||||
|
int rc;
|
||||||
|
rc = mdb_put(txn, base, &lmdbKey, &lmdbData, overwrite ? 0 : MDB_NOOVERWRITE);
|
||||||
|
|
||||||
|
if (rc != 0) {
|
||||||
|
if (rc == MDB_KEYEXIST) {
|
||||||
|
if (!overwrite) {
|
||||||
|
throw Archive::Exist(name.toStdString(), id);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw Archive::Unknown(name.toStdString(), mdb_strerror(rc));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (info.hasPath()) {
|
||||||
|
std::string sp = info.getPath().toStdString();
|
||||||
|
lmdbData.mv_size = sp.size();
|
||||||
|
lmdbData.mv_data = (char*)sp.c_str();
|
||||||
|
rc = mdb_put(txn, map, &lmdbData, &lmdbKey, 0);
|
||||||
|
if (rc != 0) {
|
||||||
|
throw Archive::Unknown(name.toStdString(), mdb_strerror(rc));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Core::UrlStorage::readInfo(const QString& key, Core::UrlStorage::UrlInfo& info, MDB_txn* txn)
|
||||||
|
{
|
||||||
|
const std::string& id = key.toStdString();
|
||||||
|
MDB_val lmdbKey, lmdbData;
|
||||||
|
lmdbKey.mv_size = id.size();
|
||||||
|
lmdbKey.mv_data = (char*)id.c_str();
|
||||||
|
int rc = mdb_get(txn, base, &lmdbKey, &lmdbData);
|
||||||
|
|
||||||
|
if (rc == 0) {
|
||||||
|
QByteArray ba((char*)lmdbData.mv_data, lmdbData.mv_size);
|
||||||
|
QDataStream ds(&ba, QIODevice::ReadOnly);
|
||||||
|
|
||||||
|
info.deserialize(ds);
|
||||||
|
} else if (rc == MDB_NOTFOUND) {
|
||||||
|
throw Archive::NotFound(id, name.toStdString());
|
||||||
|
} else {
|
||||||
|
throw Archive::Unknown(name.toStdString(), mdb_strerror(rc));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Core::UrlStorage::readInfo(const QString& key, Core::UrlStorage::UrlInfo& info)
|
||||||
|
{
|
||||||
|
MDB_txn *txn;
|
||||||
|
mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn);
|
||||||
|
|
||||||
|
try {
|
||||||
|
readInfo(key, info, txn);
|
||||||
|
mdb_txn_commit(txn);
|
||||||
|
} catch (...) {
|
||||||
|
mdb_txn_abort(txn);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Core::UrlStorage::addFile(const QString& url)
|
||||||
|
{
|
||||||
|
if (!opened) {
|
||||||
|
throw Archive::Closed("addFile(no message, no path)", name.toStdString());
|
||||||
|
}
|
||||||
|
|
||||||
|
addToInfo(url, "", "", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
void Core::UrlStorage::addFile(const QString& url, const QString& path)
|
||||||
|
{
|
||||||
|
if (!opened) {
|
||||||
|
throw Archive::Closed("addFile(no message, with path)", name.toStdString());
|
||||||
|
}
|
||||||
|
|
||||||
|
addToInfo(url, "", "", "", path);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Core::UrlStorage::addFile(const QString& url, const QString& account, const QString& jid, const QString& id)
|
||||||
|
{
|
||||||
|
if (!opened) {
|
||||||
|
throw Archive::Closed("addFile(with message, no path)", name.toStdString());
|
||||||
|
}
|
||||||
|
|
||||||
|
addToInfo(url, account, jid, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Core::UrlStorage::addFile(const QString& url, const QString& path, const QString& account, const QString& jid, const QString& id)
|
||||||
|
{
|
||||||
|
if (!opened) {
|
||||||
|
throw Archive::Closed("addFile(with message, with path)", name.toStdString());
|
||||||
|
}
|
||||||
|
|
||||||
|
addToInfo(url, account, jid, id, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Core::UrlStorage::addFile(const std::list<Shared::MessageInfo>& msgs, const QString& url, const QString& path)
|
||||||
|
{
|
||||||
|
if (!opened) {
|
||||||
|
throw Archive::Closed("addFile(with list)", name.toStdString());
|
||||||
|
}
|
||||||
|
|
||||||
|
UrlInfo info (path, msgs);
|
||||||
|
writeInfo(url, info, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString Core::UrlStorage::addMessageAndCheckForPath(const QString& url, const QString& account, const QString& jid, const QString& id)
|
||||||
|
{
|
||||||
|
if (!opened) {
|
||||||
|
throw Archive::Closed("addMessageAndCheckForPath", name.toStdString());
|
||||||
|
}
|
||||||
|
|
||||||
|
return addToInfo(url, account, jid, id).getPath();
|
||||||
|
}
|
||||||
|
|
||||||
|
Core::UrlStorage::UrlInfo Core::UrlStorage::addToInfo(const QString& url, const QString& account, const QString& jid, const QString& id, const QString& path)
|
||||||
|
{
|
||||||
|
UrlInfo info;
|
||||||
|
MDB_txn *txn;
|
||||||
|
mdb_txn_begin(environment, NULL, 0, &txn);
|
||||||
|
|
||||||
|
try {
|
||||||
|
readInfo(url, info, txn);
|
||||||
|
} catch (const Archive::NotFound& e) {
|
||||||
|
|
||||||
|
} catch (...) {
|
||||||
|
mdb_txn_abort(txn);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool pathChange = false;
|
||||||
|
bool listChange = false;
|
||||||
|
if (path != "-s") {
|
||||||
|
if (info.getPath() != path) {
|
||||||
|
info.setPath(path);
|
||||||
|
pathChange = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (account.size() > 0 && jid.size() > 0 && id.size() > 0) {
|
||||||
|
listChange = info.addMessage(account, jid, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pathChange || listChange) {
|
||||||
|
try {
|
||||||
|
writeInfo(url, info, txn, true);
|
||||||
|
mdb_txn_commit(txn);
|
||||||
|
} catch (...) {
|
||||||
|
mdb_txn_abort(txn);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
mdb_txn_abort(txn);
|
||||||
|
}
|
||||||
|
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::list<Shared::MessageInfo> Core::UrlStorage::setPath(const QString& url, const QString& path)
|
||||||
|
{
|
||||||
|
std::list<Shared::MessageInfo> list;
|
||||||
|
|
||||||
|
MDB_txn *txn;
|
||||||
|
mdb_txn_begin(environment, NULL, 0, &txn);
|
||||||
|
UrlInfo info;
|
||||||
|
|
||||||
|
try {
|
||||||
|
readInfo(url, info, txn);
|
||||||
|
info.getMessages(list);
|
||||||
|
} catch (const Archive::NotFound& e) {
|
||||||
|
} catch (...) {
|
||||||
|
mdb_txn_abort(txn);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
info.setPath(path);
|
||||||
|
try {
|
||||||
|
writeInfo(url, info, txn, true);
|
||||||
|
mdb_txn_commit(txn);
|
||||||
|
} catch (...) {
|
||||||
|
mdb_txn_abort(txn);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::list<Shared::MessageInfo> Core::UrlStorage::removeFile(const QString& url)
|
||||||
|
{
|
||||||
|
std::list<Shared::MessageInfo> list;
|
||||||
|
|
||||||
|
MDB_txn *txn;
|
||||||
|
mdb_txn_begin(environment, NULL, 0, &txn);
|
||||||
|
UrlInfo info;
|
||||||
|
|
||||||
|
try {
|
||||||
|
std::string id = url.toStdString();
|
||||||
|
readInfo(url, info, txn);
|
||||||
|
info.getMessages(list);
|
||||||
|
|
||||||
|
MDB_val lmdbKey;
|
||||||
|
lmdbKey.mv_size = id.size();
|
||||||
|
lmdbKey.mv_data = (char*)id.c_str();
|
||||||
|
int rc = mdb_del(txn, base, &lmdbKey, NULL);
|
||||||
|
if (rc != 0) {
|
||||||
|
throw Archive::Unknown(name.toStdString(), mdb_strerror(rc));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (info.hasPath()) {
|
||||||
|
std::string path = info.getPath().toStdString();
|
||||||
|
lmdbKey.mv_size = path.size();
|
||||||
|
lmdbKey.mv_data = (char*)path.c_str();
|
||||||
|
|
||||||
|
int rc = mdb_del(txn, map, &lmdbKey, NULL);
|
||||||
|
if (rc != 0) {
|
||||||
|
throw Archive::Unknown(name.toStdString(), mdb_strerror(rc));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mdb_txn_commit(txn);
|
||||||
|
} catch (...) {
|
||||||
|
mdb_txn_abort(txn);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::list<Shared::MessageInfo> Core::UrlStorage::deletedFile(const QString& path)
|
||||||
|
{
|
||||||
|
std::list<Shared::MessageInfo> list;
|
||||||
|
|
||||||
|
MDB_txn *txn;
|
||||||
|
mdb_txn_begin(environment, NULL, 0, &txn);
|
||||||
|
|
||||||
|
try {
|
||||||
|
std::string spath = path.toStdString();
|
||||||
|
|
||||||
|
MDB_val lmdbKey, lmdbData;
|
||||||
|
lmdbKey.mv_size = spath.size();
|
||||||
|
lmdbKey.mv_data = (char*)spath.c_str();
|
||||||
|
|
||||||
|
QString url;
|
||||||
|
int rc = mdb_get(txn, map, &lmdbKey, &lmdbData);
|
||||||
|
|
||||||
|
if (rc == 0) {
|
||||||
|
std::string surl((char*)lmdbData.mv_data, lmdbData.mv_size);
|
||||||
|
url = QString(surl.c_str());
|
||||||
|
} else if (rc == MDB_NOTFOUND) {
|
||||||
|
qDebug() << "Have been asked to remove file" << path << ", which isn't in the database, skipping";
|
||||||
|
mdb_txn_abort(txn);
|
||||||
|
return list;
|
||||||
|
} else {
|
||||||
|
throw Archive::Unknown(name.toStdString(), mdb_strerror(rc));
|
||||||
|
}
|
||||||
|
|
||||||
|
UrlInfo info;
|
||||||
|
std::string id = url.toStdString();
|
||||||
|
readInfo(url, info, txn);
|
||||||
|
info.getMessages(list);
|
||||||
|
info.setPath(QString());
|
||||||
|
writeInfo(url, info, txn, true);
|
||||||
|
|
||||||
|
rc = mdb_del(txn, map, &lmdbKey, NULL);
|
||||||
|
if (rc != 0) {
|
||||||
|
throw Archive::Unknown(name.toStdString(), mdb_strerror(rc));
|
||||||
|
}
|
||||||
|
|
||||||
|
mdb_txn_commit(txn);
|
||||||
|
} catch (...) {
|
||||||
|
mdb_txn_abort(txn);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
QString Core::UrlStorage::getUrl(const QString& path)
|
||||||
|
{
|
||||||
|
std::list<Shared::MessageInfo> list;
|
||||||
|
|
||||||
|
MDB_txn *txn;
|
||||||
|
mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn);
|
||||||
|
|
||||||
|
std::string spath = path.toStdString();
|
||||||
|
|
||||||
|
MDB_val lmdbKey, lmdbData;
|
||||||
|
lmdbKey.mv_size = spath.size();
|
||||||
|
lmdbKey.mv_data = (char*)spath.c_str();
|
||||||
|
|
||||||
|
QString url;
|
||||||
|
int rc = mdb_get(txn, map, &lmdbKey, &lmdbData);
|
||||||
|
|
||||||
|
if (rc == 0) {
|
||||||
|
std::string surl((char*)lmdbData.mv_data, lmdbData.mv_size);
|
||||||
|
url = QString(surl.c_str());
|
||||||
|
|
||||||
|
mdb_txn_abort(txn);
|
||||||
|
return url;
|
||||||
|
} else if (rc == MDB_NOTFOUND) {
|
||||||
|
mdb_txn_abort(txn);
|
||||||
|
throw Archive::NotFound(spath, name.toStdString());
|
||||||
|
} else {
|
||||||
|
mdb_txn_abort(txn);
|
||||||
|
throw Archive::Unknown(name.toStdString(), mdb_strerror(rc));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<QString, std::list<Shared::MessageInfo>> Core::UrlStorage::getPath(const QString& url)
|
||||||
|
{
|
||||||
|
UrlInfo info;
|
||||||
|
readInfo(url, info);
|
||||||
|
std::list<Shared::MessageInfo> container;
|
||||||
|
info.getMessages(container);
|
||||||
|
return std::make_pair(info.getPath(), container);
|
||||||
|
}
|
||||||
|
|
||||||
|
Core::UrlStorage::UrlInfo::UrlInfo():
|
||||||
|
localPath(),
|
||||||
|
messages() {}
|
||||||
|
|
||||||
|
Core::UrlStorage::UrlInfo::UrlInfo(const QString& path):
|
||||||
|
localPath(path),
|
||||||
|
messages() {}
|
||||||
|
|
||||||
|
Core::UrlStorage::UrlInfo::UrlInfo(const QString& path, const std::list<Shared::MessageInfo>& msgs):
|
||||||
|
localPath(path),
|
||||||
|
messages(msgs) {}
|
||||||
|
|
||||||
|
Core::UrlStorage::UrlInfo::~UrlInfo() {}
|
||||||
|
|
||||||
|
bool Core::UrlStorage::UrlInfo::addMessage(const QString& acc, const QString& jid, const QString& id)
|
||||||
|
{
|
||||||
|
for (const Shared::MessageInfo& info : messages) {
|
||||||
|
if (info.account == acc && info.jid == jid && info.messageId == id) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
messages.emplace_back(acc, jid, id);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Core::UrlStorage::UrlInfo::serialize(QDataStream& data) const
|
||||||
|
{
|
||||||
|
data << localPath;
|
||||||
|
std::list<Shared::MessageInfo>::size_type size = messages.size();
|
||||||
|
data << quint32(size);
|
||||||
|
for (const Shared::MessageInfo& info : messages) {
|
||||||
|
data << info.account;
|
||||||
|
data << info.jid;
|
||||||
|
data << info.messageId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Core::UrlStorage::UrlInfo::deserialize(QDataStream& data)
|
||||||
|
{
|
||||||
|
data >> localPath;
|
||||||
|
quint32 size;
|
||||||
|
data >> size;
|
||||||
|
for (quint32 i = 0; i < size; ++i) {
|
||||||
|
messages.emplace_back();
|
||||||
|
Shared::MessageInfo& info = messages.back();
|
||||||
|
data >> info.account;
|
||||||
|
data >> info.jid;
|
||||||
|
data >> info.messageId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Core::UrlStorage::UrlInfo::getMessages(std::list<Shared::MessageInfo>& container) const
|
||||||
|
{
|
||||||
|
for (const Shared::MessageInfo& info : messages) {
|
||||||
|
container.emplace_back(info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QString Core::UrlStorage::UrlInfo::getPath() const
|
||||||
|
{
|
||||||
|
return localPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Core::UrlStorage::UrlInfo::hasPath() const
|
||||||
|
{
|
||||||
|
return localPath.size() > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Core::UrlStorage::UrlInfo::setPath(const QString& path)
|
||||||
|
{
|
||||||
|
localPath = path;
|
||||||
|
}
|
99
core/urlstorage.h
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
/*
|
||||||
|
* Squawk messenger.
|
||||||
|
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef CORE_URLSTORAGE_H
|
||||||
|
#define CORE_URLSTORAGE_H
|
||||||
|
|
||||||
|
#include <QString>
|
||||||
|
#include <QDataStream>
|
||||||
|
#include <lmdb.h>
|
||||||
|
#include <list>
|
||||||
|
|
||||||
|
#include "archive.h"
|
||||||
|
#include <shared/messageinfo.h>
|
||||||
|
|
||||||
|
namespace Core {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @todo write docs
|
||||||
|
*/
|
||||||
|
class UrlStorage
|
||||||
|
{
|
||||||
|
class UrlInfo;
|
||||||
|
public:
|
||||||
|
UrlStorage(const QString& name);
|
||||||
|
~UrlStorage();
|
||||||
|
|
||||||
|
void open();
|
||||||
|
void close();
|
||||||
|
|
||||||
|
void addFile(const QString& url);
|
||||||
|
void addFile(const QString& url, const QString& path);
|
||||||
|
void addFile(const QString& url, const QString& account, const QString& jid, const QString& id);
|
||||||
|
void addFile(const QString& url, const QString& path, const QString& account, const QString& jid, const QString& id);
|
||||||
|
void addFile(const std::list<Shared::MessageInfo>& msgs, const QString& url, const QString& path); //this one overwrites all that was
|
||||||
|
std::list<Shared::MessageInfo> removeFile(const QString& url); //removes entry like it never was in the database, returns affected message infos
|
||||||
|
std::list<Shared::MessageInfo> deletedFile(const QString& path); //empties the localPath of the entry, returns affected message infos
|
||||||
|
std::list<Shared::MessageInfo> setPath(const QString& url, const QString& path);
|
||||||
|
QString getUrl(const QString& path);
|
||||||
|
QString addMessageAndCheckForPath(const QString& url, const QString& account, const QString& jid, const QString& id);
|
||||||
|
std::pair<QString, std::list<Shared::MessageInfo>> getPath(const QString& url);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QString name;
|
||||||
|
bool opened;
|
||||||
|
MDB_env* environment;
|
||||||
|
MDB_dbi base;
|
||||||
|
MDB_dbi map;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void writeInfo(const QString& key, const UrlInfo& info, bool overwrite = false);
|
||||||
|
void writeInfo(const QString& key, const UrlInfo& info, MDB_txn* txn, bool overwrite = false);
|
||||||
|
void readInfo(const QString& key, UrlInfo& info);
|
||||||
|
void readInfo(const QString& key, UrlInfo& info, MDB_txn* txn);
|
||||||
|
UrlInfo addToInfo(const QString& url, const QString& account, const QString& jid, const QString& id, const QString& path = "-s");
|
||||||
|
|
||||||
|
private:
|
||||||
|
class UrlInfo {
|
||||||
|
public:
|
||||||
|
UrlInfo(const QString& path);
|
||||||
|
UrlInfo(const QString& path, const std::list<Shared::MessageInfo>& msgs);
|
||||||
|
UrlInfo();
|
||||||
|
~UrlInfo();
|
||||||
|
|
||||||
|
void serialize(QDataStream& data) const;
|
||||||
|
void deserialize(QDataStream& data);
|
||||||
|
|
||||||
|
QString getPath() const;
|
||||||
|
bool hasPath() const;
|
||||||
|
void setPath(const QString& path);
|
||||||
|
|
||||||
|
bool addMessage(const QString& acc, const QString& jid, const QString& id);
|
||||||
|
void getMessages(std::list<Shared::MessageInfo>& container) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
QString localPath;
|
||||||
|
std::list<Shared::MessageInfo> messages;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // CORE_URLSTORAGE_H
|
2
external/simpleCrypt/CMakeLists.txt
vendored
@ -10,7 +10,7 @@ set(simplecrypt_SRC
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Tell CMake to create the helloworld executable
|
# Tell CMake to create the helloworld executable
|
||||||
add_library(simpleCrypt ${simplecrypt_SRC})
|
add_library(simpleCrypt STATIC ${simplecrypt_SRC})
|
||||||
|
|
||||||
# Use the Widgets module from Qt 5.
|
# Use the Widgets module from Qt 5.
|
||||||
target_link_libraries(simpleCrypt Qt5::Core)
|
target_link_libraries(simpleCrypt Qt5::Core)
|
||||||
|
21
main.cpp
@ -20,6 +20,7 @@
|
|||||||
#include "core/squawk.h"
|
#include "core/squawk.h"
|
||||||
#include "signalcatcher.h"
|
#include "signalcatcher.h"
|
||||||
#include "shared/global.h"
|
#include "shared/global.h"
|
||||||
|
#include "shared/messageinfo.h"
|
||||||
#include <QtWidgets/QApplication>
|
#include <QtWidgets/QApplication>
|
||||||
#include <QtCore/QThread>
|
#include <QtCore/QThread>
|
||||||
#include <QtCore/QObject>
|
#include <QtCore/QObject>
|
||||||
@ -31,8 +32,10 @@
|
|||||||
int main(int argc, char *argv[])
|
int main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
qRegisterMetaType<Shared::Message>("Shared::Message");
|
qRegisterMetaType<Shared::Message>("Shared::Message");
|
||||||
|
qRegisterMetaType<Shared::MessageInfo>("Shared::MessageInfo");
|
||||||
qRegisterMetaType<Shared::VCard>("Shared::VCard");
|
qRegisterMetaType<Shared::VCard>("Shared::VCard");
|
||||||
qRegisterMetaType<std::list<Shared::Message>>("std::list<Shared::Message>");
|
qRegisterMetaType<std::list<Shared::Message>>("std::list<Shared::Message>");
|
||||||
|
qRegisterMetaType<std::list<Shared::MessageInfo>>("std::list<Shared::MessageInfo>");
|
||||||
qRegisterMetaType<QSet<QString>>("QSet<QString>");
|
qRegisterMetaType<QSet<QString>>("QSet<QString>");
|
||||||
qRegisterMetaType<Shared::ConnectionState>("Shared::ConnectionState");
|
qRegisterMetaType<Shared::ConnectionState>("Shared::ConnectionState");
|
||||||
qRegisterMetaType<Shared::Availability>("Shared::Availability");
|
qRegisterMetaType<Shared::Availability>("Shared::Availability");
|
||||||
@ -96,10 +99,7 @@ int main(int argc, char *argv[])
|
|||||||
QObject::connect(&w, &Squawk::connectAccount, squawk, &Core::Squawk::connectAccount);
|
QObject::connect(&w, &Squawk::connectAccount, squawk, &Core::Squawk::connectAccount);
|
||||||
QObject::connect(&w, &Squawk::disconnectAccount, squawk, &Core::Squawk::disconnectAccount);
|
QObject::connect(&w, &Squawk::disconnectAccount, squawk, &Core::Squawk::disconnectAccount);
|
||||||
QObject::connect(&w, &Squawk::changeState, squawk, &Core::Squawk::changeState);
|
QObject::connect(&w, &Squawk::changeState, squawk, &Core::Squawk::changeState);
|
||||||
QObject::connect(&w, qOverload<const QString&, const Shared::Message&>(&Squawk::sendMessage),
|
QObject::connect(&w, &Squawk::sendMessage, squawk,&Core::Squawk::sendMessage);
|
||||||
squawk, qOverload<const QString&, const Shared::Message&>(&Core::Squawk::sendMessage));
|
|
||||||
QObject::connect(&w, qOverload<const QString&, const Shared::Message&, const QString&>(&Squawk::sendMessage),
|
|
||||||
squawk, qOverload<const QString&, const Shared::Message&, const QString&>(&Core::Squawk::sendMessage));
|
|
||||||
QObject::connect(&w, &Squawk::requestArchive, squawk, &Core::Squawk::requestArchive);
|
QObject::connect(&w, &Squawk::requestArchive, squawk, &Core::Squawk::requestArchive);
|
||||||
QObject::connect(&w, &Squawk::subscribeContact, squawk, &Core::Squawk::subscribeContact);
|
QObject::connect(&w, &Squawk::subscribeContact, squawk, &Core::Squawk::subscribeContact);
|
||||||
QObject::connect(&w, &Squawk::unsubscribeContact, squawk, &Core::Squawk::unsubscribeContact);
|
QObject::connect(&w, &Squawk::unsubscribeContact, squawk, &Core::Squawk::unsubscribeContact);
|
||||||
@ -109,14 +109,14 @@ int main(int argc, char *argv[])
|
|||||||
QObject::connect(&w, &Squawk::setRoomAutoJoin, squawk, &Core::Squawk::setRoomAutoJoin);
|
QObject::connect(&w, &Squawk::setRoomAutoJoin, squawk, &Core::Squawk::setRoomAutoJoin);
|
||||||
QObject::connect(&w, &Squawk::removeRoomRequest, squawk, &Core::Squawk::removeRoomRequest);
|
QObject::connect(&w, &Squawk::removeRoomRequest, squawk, &Core::Squawk::removeRoomRequest);
|
||||||
QObject::connect(&w, &Squawk::addRoomRequest, squawk, &Core::Squawk::addRoomRequest);
|
QObject::connect(&w, &Squawk::addRoomRequest, squawk, &Core::Squawk::addRoomRequest);
|
||||||
QObject::connect(&w, &Squawk::fileLocalPathRequest, squawk, &Core::Squawk::fileLocalPathRequest);
|
QObject::connect(&w, &Squawk::fileDownloadRequest, squawk, &Core::Squawk::fileDownloadRequest);
|
||||||
QObject::connect(&w, &Squawk::downloadFileRequest, squawk, &Core::Squawk::downloadFileRequest);
|
|
||||||
QObject::connect(&w, &Squawk::addContactToGroupRequest, squawk, &Core::Squawk::addContactToGroupRequest);
|
QObject::connect(&w, &Squawk::addContactToGroupRequest, squawk, &Core::Squawk::addContactToGroupRequest);
|
||||||
QObject::connect(&w, &Squawk::removeContactFromGroupRequest, squawk, &Core::Squawk::removeContactFromGroupRequest);
|
QObject::connect(&w, &Squawk::removeContactFromGroupRequest, squawk, &Core::Squawk::removeContactFromGroupRequest);
|
||||||
QObject::connect(&w, &Squawk::renameContactRequest, squawk, &Core::Squawk::renameContactRequest);
|
QObject::connect(&w, &Squawk::renameContactRequest, squawk, &Core::Squawk::renameContactRequest);
|
||||||
QObject::connect(&w, &Squawk::requestVCard, squawk, &Core::Squawk::requestVCard);
|
QObject::connect(&w, &Squawk::requestVCard, squawk, &Core::Squawk::requestVCard);
|
||||||
QObject::connect(&w, &Squawk::uploadVCard, squawk, &Core::Squawk::uploadVCard);
|
QObject::connect(&w, &Squawk::uploadVCard, squawk, &Core::Squawk::uploadVCard);
|
||||||
QObject::connect(&w, &Squawk::responsePassword, squawk, &Core::Squawk::responsePassword);
|
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::newAccount, &w, &Squawk::newAccount);
|
||||||
QObject::connect(squawk, &Core::Squawk::addContact, &w, &Squawk::addContact);
|
QObject::connect(squawk, &Core::Squawk::addContact, &w, &Squawk::addContact);
|
||||||
@ -141,11 +141,10 @@ int main(int argc, char *argv[])
|
|||||||
QObject::connect(squawk, &Core::Squawk::addRoomParticipant, &w, &Squawk::addRoomParticipant);
|
QObject::connect(squawk, &Core::Squawk::addRoomParticipant, &w, &Squawk::addRoomParticipant);
|
||||||
QObject::connect(squawk, &Core::Squawk::changeRoomParticipant, &w, &Squawk::changeRoomParticipant);
|
QObject::connect(squawk, &Core::Squawk::changeRoomParticipant, &w, &Squawk::changeRoomParticipant);
|
||||||
QObject::connect(squawk, &Core::Squawk::removeRoomParticipant, &w, &Squawk::removeRoomParticipant);
|
QObject::connect(squawk, &Core::Squawk::removeRoomParticipant, &w, &Squawk::removeRoomParticipant);
|
||||||
QObject::connect(squawk, &Core::Squawk::fileLocalPathResponse, &w, &Squawk::fileLocalPathResponse);
|
QObject::connect(squawk, &Core::Squawk::fileDownloadComplete, &w, &Squawk::fileDownloadComplete);
|
||||||
QObject::connect(squawk, &Core::Squawk::downloadFileProgress, &w, &Squawk::fileProgress);
|
QObject::connect(squawk, &Core::Squawk::fileUploadComplete, &w, &Squawk::fileUploadComplete);
|
||||||
QObject::connect(squawk, &Core::Squawk::downloadFileError, &w, &Squawk::fileError);
|
QObject::connect(squawk, &Core::Squawk::fileProgress, &w, &Squawk::fileProgress);
|
||||||
QObject::connect(squawk, &Core::Squawk::uploadFileProgress, &w, &Squawk::fileProgress);
|
QObject::connect(squawk, &Core::Squawk::fileError, &w, &Squawk::fileError);
|
||||||
QObject::connect(squawk, &Core::Squawk::uploadFileError, &w, &Squawk::fileError);
|
|
||||||
QObject::connect(squawk, &Core::Squawk::responseVCard, &w, &Squawk::responseVCard);
|
QObject::connect(squawk, &Core::Squawk::responseVCard, &w, &Squawk::responseVCard);
|
||||||
QObject::connect(squawk, &Core::Squawk::requestPassword, &w, &Squawk::requestPassword);
|
QObject::connect(squawk, &Core::Squawk::requestPassword, &w, &Squawk::requestPassword);
|
||||||
QObject::connect(squawk, &Core::Squawk::ready, &w, &Squawk::readSettings);
|
QObject::connect(squawk, &Core::Squawk::ready, &w, &Squawk::readSettings);
|
||||||
|
26
plugins/CMakeLists.txt
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.3)
|
||||||
|
project(plugins)
|
||||||
|
|
||||||
|
if (WITH_KIO)
|
||||||
|
set(CMAKE_AUTOMOC ON)
|
||||||
|
|
||||||
|
find_package(Qt5Core CONFIG REQUIRED)
|
||||||
|
|
||||||
|
set(openFileManagerWindowJob_SRC
|
||||||
|
openfilemanagerwindowjob.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
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()
|
8
plugins/openfilemanagerwindowjob.cpp
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
#include <QUrl>
|
||||||
|
#include <QObject>
|
||||||
|
#include <KIO/OpenFileManagerWindowJob>
|
||||||
|
|
||||||
|
extern "C" void highlightInFileManager(const QUrl& url) {
|
||||||
|
KIO::OpenFileManagerWindowJob* job = KIO::highlightInFileManager({url});
|
||||||
|
QObject::connect(job, &KIO::OpenFileManagerWindowJob::result, job, &KIO::OpenFileManagerWindowJob::deleteLater);
|
||||||
|
}
|
11
resources/images/fallback/dark/big/document-preview.svg
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<!DOCTYPE svg>
|
||||||
|
<svg viewBox="0 0 22 22" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<defs>
|
||||||
|
<style type="text/css" id="current-color-scheme">
|
||||||
|
.ColorScheme-Text {
|
||||||
|
color:#232629;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</defs>
|
||||||
|
<path class="ColorScheme-Text" style="fill:currentColor; fill-opacity:1; stroke:none" d="M 4 3 L 4 19 L 15 19 L 15 18 L 5 18 L 5 4 L 13 4 L 13 8 L 17 8 L 17 16 L 18 16 L 18 7 L 14 3 L 4 3 Z M 13 11 C 11.338 11 10 12.338 10 14 C 10 15.662 11.338 17 13 17 C 13.6494 17 14.2464 16.7914 14.7363 16.4434 L 17.293 19 L 18 18.293 L 15.4434 15.7363 C 15.7914 15.2464 16 14.6494 16 14 C 16 12.338 14.662 11 13 11 Z M 13 12 C 14.108 12 15 12.892 15 14 C 15 15.108 14.108 16 13 16 C 11.892 16 11 15.108 11 14 C 11 12.892 11.892 12 13 12 Z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 807 B |
21
resources/images/fallback/dark/big/folder.svg
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
<svg width="32" version="1.1" xmlns="http://www.w3.org/2000/svg" height="32" viewBox="0 0 32 32" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape">
|
||||||
|
<defs id="defs5455">
|
||||||
|
<linearGradient inkscape:collect="always" id="linearGradient4172-5">
|
||||||
|
<stop style="stop-color:#3daee9" id="stop4174-6"/>
|
||||||
|
<stop offset="1" style="stop-color:#6cc1ef" id="stop4176-6"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient inkscape:collect="always" xlink:href="#linearGradient4172-5" id="linearGradient4342" y1="29" y2="8" x2="0" gradientUnits="userSpaceOnUse"/>
|
||||||
|
</defs>
|
||||||
|
<metadata id="metadata5458"/>
|
||||||
|
<g inkscape:label="Capa 1" inkscape:groupmode="layer" id="layer1" transform="matrix(1 0 0 1 -384.57143 -515.798)">
|
||||||
|
<path inkscape:connector-curvature="0" style="fill:#147eb8;fill-rule:evenodd" id="path4308" d="m 386.57144,518.798 0,7 0,1 28,0 0,-6 -14.00001,0 -2,-2 z"/>
|
||||||
|
<path inkscape:connector-curvature="0" style="fill-opacity:0.235294;fill-rule:evenodd" id="path4306" d="m 397.57143,523.798 -1.99999,1 -9,0 0,1 6.99999,0 3,0 z"/>
|
||||||
|
<path style="fill:url(#linearGradient4342)" id="rect4294" d="M 13 8 L 11 10 L 2 10 L 1 10 L 1 29 L 12 29 L 13 29 L 31 29 L 31 8 L 13 8 z " transform="matrix(1 0 0 1 384.57143 515.798)"/>
|
||||||
|
<path inkscape:connector-curvature="0" style="fill:#ffffff;fill-opacity:0.235294;fill-rule:evenodd" id="path4304" d="m 397.57143,523.798 -2,2 -10,0 0,1 11,0 z"/>
|
||||||
|
<path inkscape:connector-curvature="0" style="fill:#ffffff;fill-opacity:0.235294;fill-rule:evenodd" id="path4310" d="m 398.57143,518.798 1,3 15.00001,0 0,-1 -14.00001,0 z"/>
|
||||||
|
<rect width="30" x="385.57144" y="543.79797" height="1" style="fill-opacity:0.235294" id="rect4292"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.8 KiB |
12
resources/images/fallback/dark/small/document-preview.svg
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<!DOCTYPE svg>
|
||||||
|
<svg viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<defs>
|
||||||
|
<style type="text/css" id="current-color-scheme">
|
||||||
|
.ColorScheme-Text {
|
||||||
|
color:#232629;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</defs>
|
||||||
|
<path class="ColorScheme-Text" style="fill:currentColor; fill-opacity:1; stroke:none" d="M 3 2 L 3 14 L 10 14 L 10 13 L 4 13 L 4 3 L 9 3 L 9 6 L 12 6 L 12 11 L 13 11 L 13 5 L 10 2 L 3 2 Z"/>
|
||||||
|
<path class="ColorScheme-Text" style="fill:currentColor; fill-opacity:1; stroke:none" d="M 8.48828 7 C 7.10757 7 5.98828 8.11929 5.98828 9.5 C 5.98828 10.8807 7.10757 12 8.48828 12 C 8.97811 11.9992 9.45691 11.8546 9.86523 11.584 L 12.2813 14 L 12.9883 13.293 L 10.5723 10.877 C 10.8428 10.4686 10.9875 9.98983 10.9883 9.5 C 10.9883 8.11929 9.86899 7 8.48828 7 Z M 8.48828 8 C 9.31671 8 9.98828 8.67157 9.98828 9.5 C 9.98828 10.3284 9.31671 11 8.48828 11 C 7.65985 11 6.98828 10.3284 6.98828 9.5 C 6.98828 8.67157 7.65985 8 8.48828 8 Z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1010 B |
13
resources/images/fallback/dark/small/folder.svg
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||||
|
<defs id="defs3051">
|
||||||
|
<style type="text/css" id="current-color-scheme">
|
||||||
|
.ColorScheme-Text {
|
||||||
|
color:#232629;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</defs>
|
||||||
|
<path style="fill:currentColor;fill-opacity:1;stroke:none"
|
||||||
|
d="M 2 2 L 2 3 L 2 6 L 2 7 L 2 13 L 2 14 L 14 14 L 14 13 L 14 6 L 14 5 L 14 4 L 9.0078125 4 L 7.0078125 2 L 7 2.0078125 L 7 2 L 3 2 L 2 2 z M 3 3 L 6.5917969 3 L 7.59375 4 L 7 4 L 7 4.0078125 L 6.9921875 4 L 4.9921875 6 L 3 6 L 3 3 z M 3 7 L 13 7 L 13 13 L 3 13 L 3 7 z "
|
||||||
|
class="ColorScheme-Text"
|
||||||
|
/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 609 B |
11
resources/images/fallback/light/big/document-preview.svg
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<!DOCTYPE svg>
|
||||||
|
<svg viewBox="0 0 22 22" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<defs>
|
||||||
|
<style type="text/css" id="current-color-scheme">
|
||||||
|
.ColorScheme-Text {
|
||||||
|
color:#eff0f1;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</defs>
|
||||||
|
<path class="ColorScheme-Text" style="fill:currentColor; fill-opacity:1; stroke:none" d="M 4 3 L 4 19 L 15 19 L 15 18 L 5 18 L 5 4 L 13 4 L 13 8 L 17 8 L 17 16 L 18 16 L 18 7 L 14 3 L 4 3 Z M 13 11 C 11.338 11 10 12.338 10 14 C 10 15.662 11.338 17 13 17 C 13.6494 17 14.2464 16.7914 14.7363 16.4434 L 17.293 19 L 18 18.293 L 15.4434 15.7363 C 15.7914 15.2464 16 14.6494 16 14 C 16 12.338 14.662 11 13 11 Z M 13 12 C 14.108 12 15 12.892 15 14 C 15 15.108 14.108 16 13 16 C 11.892 16 11 15.108 11 14 C 11 12.892 11.892 12 13 12 Z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 807 B |
21
resources/images/fallback/light/big/folder.svg
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
<svg width="32" version="1.1" xmlns="http://www.w3.org/2000/svg" height="32" viewBox="0 0 32 32" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape">
|
||||||
|
<defs id="defs5455">
|
||||||
|
<linearGradient inkscape:collect="always" id="linearGradient4172-5">
|
||||||
|
<stop style="stop-color:#3daee9" id="stop4174-6"/>
|
||||||
|
<stop offset="1" style="stop-color:#6cc1ef" id="stop4176-6"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient inkscape:collect="always" xlink:href="#linearGradient4172-5" id="linearGradient4342" y1="29" y2="8" x2="0" gradientUnits="userSpaceOnUse"/>
|
||||||
|
</defs>
|
||||||
|
<metadata id="metadata5458"/>
|
||||||
|
<g inkscape:label="Capa 1" inkscape:groupmode="layer" id="layer1" transform="matrix(1 0 0 1 -384.57143 -515.798)">
|
||||||
|
<path inkscape:connector-curvature="0" style="fill:#147eb8;fill-rule:evenodd" id="path4308" d="m 386.57144,518.798 0,7 0,1 28,0 0,-6 -14.00001,0 -2,-2 z"/>
|
||||||
|
<path inkscape:connector-curvature="0" style="fill-opacity:0.235294;fill-rule:evenodd" id="path4306" d="m 397.57143,523.798 -1.99999,1 -9,0 0,1 6.99999,0 3,0 z"/>
|
||||||
|
<path style="fill:url(#linearGradient4342)" id="rect4294" d="M 13 8 L 11 10 L 2 10 L 1 10 L 1 29 L 12 29 L 13 29 L 31 29 L 31 8 L 13 8 z " transform="matrix(1 0 0 1 384.57143 515.798)"/>
|
||||||
|
<path inkscape:connector-curvature="0" style="fill:#ffffff;fill-opacity:0.235294;fill-rule:evenodd" id="path4304" d="m 397.57143,523.798 -2,2 -10,0 0,1 11,0 z"/>
|
||||||
|
<path inkscape:connector-curvature="0" style="fill:#ffffff;fill-opacity:0.235294;fill-rule:evenodd" id="path4310" d="m 398.57143,518.798 1,3 15.00001,0 0,-1 -14.00001,0 z"/>
|
||||||
|
<rect width="30" x="385.57144" y="543.79797" height="1" style="fill-opacity:0.235294" id="rect4292"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.8 KiB |
12
resources/images/fallback/light/small/document-preview.svg
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<!DOCTYPE svg>
|
||||||
|
<svg viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<defs>
|
||||||
|
<style type="text/css" id="current-color-scheme">
|
||||||
|
.ColorScheme-Text {
|
||||||
|
color:#eff0f1;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</defs>
|
||||||
|
<path class="ColorScheme-Text" style="fill:currentColor; fill-opacity:1; stroke:none" d="M 3 2 L 3 14 L 10 14 L 10 13 L 4 13 L 4 3 L 9 3 L 9 6 L 12 6 L 12 11 L 13 11 L 13 5 L 10 2 L 3 2 Z"/>
|
||||||
|
<path class="ColorScheme-Text" style="fill:currentColor; fill-opacity:1; stroke:none" d="M 8.48828 7 C 7.10757 7 5.98828 8.11929 5.98828 9.5 C 5.98828 10.8807 7.10757 12 8.48828 12 C 8.97811 11.9992 9.45691 11.8546 9.86523 11.584 L 12.2813 14 L 12.9883 13.293 L 10.5723 10.877 C 10.8428 10.4686 10.9875 9.98983 10.9883 9.5 C 10.9883 8.11929 9.86899 7 8.48828 7 Z M 8.48828 8 C 9.31671 8 9.98828 8.67157 9.98828 9.5 C 9.98828 10.3284 9.31671 11 8.48828 11 C 7.65985 11 6.98828 10.3284 6.98828 9.5 C 6.98828 8.67157 7.65985 8 8.48828 8 Z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1010 B |
13
resources/images/fallback/light/small/folder.svg
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||||
|
<defs id="defs3051">
|
||||||
|
<style type="text/css" id="current-color-scheme">
|
||||||
|
.ColorScheme-Text {
|
||||||
|
color:#eff0f1;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</defs>
|
||||||
|
<path style="fill:currentColor;fill-opacity:1;stroke:none"
|
||||||
|
d="M 2 2 L 2 3 L 2 6 L 2 7 L 2 13 L 2 14 L 14 14 L 14 13 L 14 6 L 14 5 L 14 4 L 9.0078125 4 L 7.0078125 2 L 7 2.0078125 L 7 2 L 3 2 L 2 2 z M 3 3 L 6.5917969 3 L 7.59375 4 L 7 4 L 7 4.0078125 L 6.9921875 4 L 4.9921875 6 L 3 6 L 3 3 z M 3 7 L 13 7 L 13 13 L 3 13 L 3 7 z "
|
||||||
|
class="ColorScheme-Text"
|
||||||
|
/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 609 B |
@ -40,6 +40,8 @@
|
|||||||
<file>images/fallback/dark/big/favorite.svg</file>
|
<file>images/fallback/dark/big/favorite.svg</file>
|
||||||
<file>images/fallback/dark/big/unfavorite.svg</file>
|
<file>images/fallback/dark/big/unfavorite.svg</file>
|
||||||
<file>images/fallback/dark/big/add.svg</file>
|
<file>images/fallback/dark/big/add.svg</file>
|
||||||
|
<file>images/fallback/dark/big/folder.svg</file>
|
||||||
|
<file>images/fallback/dark/big/document-preview.svg</file>
|
||||||
|
|
||||||
|
|
||||||
<file>images/fallback/dark/small/absent.svg</file>
|
<file>images/fallback/dark/small/absent.svg</file>
|
||||||
@ -80,6 +82,8 @@
|
|||||||
<file>images/fallback/dark/small/favorite.svg</file>
|
<file>images/fallback/dark/small/favorite.svg</file>
|
||||||
<file>images/fallback/dark/small/unfavorite.svg</file>
|
<file>images/fallback/dark/small/unfavorite.svg</file>
|
||||||
<file>images/fallback/dark/small/add.svg</file>
|
<file>images/fallback/dark/small/add.svg</file>
|
||||||
|
<file>images/fallback/dark/small/folder.svg</file>
|
||||||
|
<file>images/fallback/dark/small/document-preview.svg</file>
|
||||||
|
|
||||||
|
|
||||||
<file>images/fallback/light/big/absent.svg</file>
|
<file>images/fallback/light/big/absent.svg</file>
|
||||||
@ -120,6 +124,8 @@
|
|||||||
<file>images/fallback/light/big/favorite.svg</file>
|
<file>images/fallback/light/big/favorite.svg</file>
|
||||||
<file>images/fallback/light/big/unfavorite.svg</file>
|
<file>images/fallback/light/big/unfavorite.svg</file>
|
||||||
<file>images/fallback/light/big/add.svg</file>
|
<file>images/fallback/light/big/add.svg</file>
|
||||||
|
<file>images/fallback/light/big/folder.svg</file>
|
||||||
|
<file>images/fallback/light/big/document-preview.svg</file>
|
||||||
|
|
||||||
|
|
||||||
<file>images/fallback/light/small/absent.svg</file>
|
<file>images/fallback/light/small/absent.svg</file>
|
||||||
@ -160,5 +166,7 @@
|
|||||||
<file>images/fallback/light/small/favorite.svg</file>
|
<file>images/fallback/light/small/favorite.svg</file>
|
||||||
<file>images/fallback/light/small/unfavorite.svg</file>
|
<file>images/fallback/light/small/unfavorite.svg</file>
|
||||||
<file>images/fallback/light/small/add.svg</file>
|
<file>images/fallback/light/small/add.svg</file>
|
||||||
|
<file>images/fallback/light/small/folder.svg</file>
|
||||||
|
<file>images/fallback/light/small/document-preview.svg</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
</RCC>
|
</RCC>
|
||||||
|
1
shared.h
@ -25,5 +25,6 @@
|
|||||||
#include "shared/message.h"
|
#include "shared/message.h"
|
||||||
#include "shared/vcard.h"
|
#include "shared/vcard.h"
|
||||||
#include "shared/global.h"
|
#include "shared/global.h"
|
||||||
|
#include "shared/messageinfo.h"
|
||||||
|
|
||||||
#endif // SHARED_H
|
#endif // SHARED_H
|
||||||
|
@ -23,6 +23,11 @@
|
|||||||
Shared::Global* Shared::Global::instance = 0;
|
Shared::Global* Shared::Global::instance = 0;
|
||||||
const std::set<QString> Shared::Global::supportedImagesExts = {"png", "jpg", "webp", "jpeg", "gif", "svg"};
|
const std::set<QString> Shared::Global::supportedImagesExts = {"png", "jpg", "webp", "jpeg", "gif", "svg"};
|
||||||
|
|
||||||
|
#ifdef WITH_KIO
|
||||||
|
QLibrary Shared::Global::openFileManagerWindowJob("openFileManagerWindowJob");
|
||||||
|
Shared::Global::HighlightInFileManager Shared::Global::hfm = 0;
|
||||||
|
#endif
|
||||||
|
|
||||||
Shared::Global::Global():
|
Shared::Global::Global():
|
||||||
availability({
|
availability({
|
||||||
tr("Online", "Availability"),
|
tr("Online", "Availability"),
|
||||||
@ -80,16 +85,61 @@ Shared::Global::Global():
|
|||||||
tr("Your password is going to be stored in KDE wallet storage (KWallet). You're going to be queried for permissions", "AccountPasswordDescription")
|
tr("Your password is going to be stored in KDE wallet storage (KWallet). You're going to be queried for permissions", "AccountPasswordDescription")
|
||||||
}),
|
}),
|
||||||
pluginSupport({
|
pluginSupport({
|
||||||
{"KWallet", false}
|
{"KWallet", false},
|
||||||
})
|
{"openFileManagerWindowJob", false}
|
||||||
|
}),
|
||||||
|
fileCache()
|
||||||
{
|
{
|
||||||
if (instance != 0) {
|
if (instance != 0) {
|
||||||
throw 551;
|
throw 551;
|
||||||
}
|
}
|
||||||
|
|
||||||
instance = this;
|
instance = this;
|
||||||
|
|
||||||
|
#ifdef WITH_KIO
|
||||||
|
openFileManagerWindowJob.load();
|
||||||
|
if (openFileManagerWindowJob.isLoaded()) {
|
||||||
|
hfm = (HighlightInFileManager) openFileManagerWindowJob.resolve("highlightInFileManager");
|
||||||
|
if (hfm) {
|
||||||
|
setSupported("openFileManagerWindowJob", true);
|
||||||
|
qDebug() << "KIO::OpenFileManagerWindow support enabled";
|
||||||
|
} else {
|
||||||
|
qDebug() << "KIO::OpenFileManagerWindow support disabled: couldn't resolve required methods in the library";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
qDebug() << "KIO::OpenFileManagerWindow support disabled: couldn't load the library" << openFileManagerWindowJob.errorString();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Shared::Global::FileInfo Shared::Global::getFileInfo(const QString& path)
|
||||||
|
{
|
||||||
|
std::map<QString, FileInfo>::const_iterator itr = instance->fileCache.find(path);
|
||||||
|
if (itr == instance->fileCache.end()) {
|
||||||
|
QMimeDatabase db;
|
||||||
|
QMimeType type = db.mimeTypeForFile(path);
|
||||||
|
QStringList parts = type.name().split("/");
|
||||||
|
QString big = parts.front();
|
||||||
|
QFileInfo info(path);
|
||||||
|
|
||||||
|
FileInfo::Preview p = FileInfo::Preview::none;
|
||||||
|
QSize size;
|
||||||
|
if (big == "image") {
|
||||||
|
if (parts.back() == "gif") {
|
||||||
|
//TODO need to consider GIF as a movie
|
||||||
|
}
|
||||||
|
p = FileInfo::Preview::picture;
|
||||||
|
QImage img(path);
|
||||||
|
size = img.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
itr = instance->fileCache.insert(std::make_pair(path, FileInfo({info.fileName(), size, type, p}))).first;
|
||||||
|
}
|
||||||
|
|
||||||
|
return itr->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
Shared::Global * Shared::Global::getInstance()
|
Shared::Global * Shared::Global::getInstance()
|
||||||
{
|
{
|
||||||
return instance;
|
return instance;
|
||||||
@ -152,6 +202,69 @@ QString Shared::Global::getDescription(Shared::AccountPassword ap)
|
|||||||
return instance->accountPasswordDescription[static_cast<int>(ap)];
|
return instance->accountPasswordDescription[static_cast<int>(ap)];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static const QStringList query = {"query", "default", "inode/directory"};
|
||||||
|
static const QRegularExpression dolphinReg("[Dd]olphin");
|
||||||
|
static const QRegularExpression nautilusReg("[Nn]autilus");
|
||||||
|
static const QRegularExpression cajaReg("[Cc]aja");
|
||||||
|
static const QRegularExpression nemoReg("[Nn]emo");
|
||||||
|
static const QRegularExpression konquerorReg("kfmclient");
|
||||||
|
static const QRegularExpression pcmanfmQtReg("pcmanfm-qt");
|
||||||
|
static const QRegularExpression pcmanfmReg("pcmanfm");
|
||||||
|
static const QRegularExpression thunarReg("thunar");
|
||||||
|
|
||||||
|
void Shared::Global::highlightInFileManager(const QString& path)
|
||||||
|
{
|
||||||
|
#ifdef WITH_KIO
|
||||||
|
if (supported("openFileManagerWindowJob")) {
|
||||||
|
hfm(path);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
qDebug() << "requested to highlight in file manager url" << path << "but it's not supported: KIO plugin isn't loaded, trying fallback";
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
qDebug() << "requested to highlight in file manager url" << path << "but it's not supported: squawk wasn't compiled to support it, trying fallback";
|
||||||
|
#endif
|
||||||
|
|
||||||
|
QFileInfo info = path;
|
||||||
|
if (info.exists()) {
|
||||||
|
QProcess proc;
|
||||||
|
proc.start("xdg-mime", query);
|
||||||
|
proc.waitForFinished();
|
||||||
|
QString output = proc.readLine().simplified();
|
||||||
|
|
||||||
|
QString folder;
|
||||||
|
if (info.isDir()) {
|
||||||
|
folder = info.canonicalFilePath();
|
||||||
|
} else {
|
||||||
|
folder = info.canonicalPath();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (output.contains(dolphinReg)) {
|
||||||
|
//there is a bug on current (21.04.0) dolphin, it works correct only if you already have dolphin launched
|
||||||
|
proc.startDetached("dolphin", QStringList() << "--select" << info.canonicalFilePath());
|
||||||
|
//KIO::highlightInFileManager({QUrl(info.canonicalFilePath())});
|
||||||
|
} else if (output.contains(nautilusReg)) {
|
||||||
|
proc.startDetached("nautilus", QStringList() << "--select" << info.canonicalFilePath()); //this worked on nautilus
|
||||||
|
} else if (output.contains(cajaReg)) {
|
||||||
|
proc.startDetached("caja", QStringList() << folder); //caja doesn't seem to support file selection command line, gonna just open directory
|
||||||
|
} else if (output.contains(nemoReg)) {
|
||||||
|
proc.startDetached("nemo", QStringList() << info.canonicalFilePath()); //nemo supports selecting files without keys
|
||||||
|
} else if (output.contains(konquerorReg)) {
|
||||||
|
proc.startDetached("konqueror", QStringList() << "--select" << info.canonicalFilePath()); //this worked on konqueror
|
||||||
|
} else if (output.contains(pcmanfmQtReg)) {
|
||||||
|
proc.startDetached("pcmanfm-qt", QStringList() << folder); //pcmanfm-qt doesn't seem to support open with selection, gonna just open directory
|
||||||
|
} else if (output.contains(pcmanfmReg)) {
|
||||||
|
proc.startDetached("pcmanfm", QStringList() << folder); //pcmanfm also doesn't seem to support open with selection, gonna just open directory
|
||||||
|
} else if (output.contains(thunarReg)) {
|
||||||
|
proc.startDetached("thunar", QStringList() << folder); //thunar doesn't seem to support open with selection, gonna just open directory
|
||||||
|
} else {
|
||||||
|
QDesktopServices::openUrl(QUrl::fromLocalFile(folder));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#define FROM_INT_INPL(Enum) \
|
#define FROM_INT_INPL(Enum) \
|
||||||
template<> \
|
template<> \
|
||||||
Enum Shared::Global::fromInt(int src) \
|
Enum Shared::Global::fromInt(int src) \
|
||||||
|
@ -29,6 +29,17 @@
|
|||||||
|
|
||||||
#include <QCoreApplication>
|
#include <QCoreApplication>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
|
#include <QMimeType>
|
||||||
|
#include <QMimeDatabase>
|
||||||
|
#include <QFileInfo>
|
||||||
|
#include <QImage>
|
||||||
|
#include <QSize>
|
||||||
|
#include <QUrl>
|
||||||
|
#include <QLibrary>
|
||||||
|
#include <QFileInfo>
|
||||||
|
#include <QProcess>
|
||||||
|
#include <QDesktopServices>
|
||||||
|
#include <QRegularExpression>
|
||||||
|
|
||||||
namespace Shared {
|
namespace Shared {
|
||||||
|
|
||||||
@ -36,6 +47,19 @@ namespace Shared {
|
|||||||
Q_DECLARE_TR_FUNCTIONS(Global)
|
Q_DECLARE_TR_FUNCTIONS(Global)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
struct FileInfo {
|
||||||
|
enum class Preview {
|
||||||
|
none,
|
||||||
|
picture,
|
||||||
|
movie
|
||||||
|
};
|
||||||
|
|
||||||
|
QString name;
|
||||||
|
QSize size;
|
||||||
|
QMimeType mime;
|
||||||
|
Preview preview;
|
||||||
|
};
|
||||||
|
|
||||||
Global();
|
Global();
|
||||||
|
|
||||||
static Global* getInstance();
|
static Global* getInstance();
|
||||||
@ -64,6 +88,9 @@ namespace Shared {
|
|||||||
|
|
||||||
static const std::set<QString> supportedImagesExts;
|
static const std::set<QString> supportedImagesExts;
|
||||||
|
|
||||||
|
static FileInfo getFileInfo(const QString& path);
|
||||||
|
static void highlightInFileManager(const QString& path);
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
static T fromInt(int src);
|
static T fromInt(int src);
|
||||||
|
|
||||||
@ -87,6 +114,15 @@ namespace Shared {
|
|||||||
static Global* instance;
|
static Global* instance;
|
||||||
|
|
||||||
std::map<QString, bool> pluginSupport;
|
std::map<QString, bool> pluginSupport;
|
||||||
|
std::map<QString, FileInfo> fileCache;
|
||||||
|
|
||||||
|
#ifdef WITH_KIO
|
||||||
|
static QLibrary openFileManagerWindowJob;
|
||||||
|
|
||||||
|
typedef void (*HighlightInFileManager)(const QUrl &);
|
||||||
|
|
||||||
|
static HighlightInFileManager hfm;
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -170,6 +170,8 @@ static const std::map<QString, std::pair<QString, QString>> icons = {
|
|||||||
{"favorite", {"favorite", "favorite"}},
|
{"favorite", {"favorite", "favorite"}},
|
||||||
{"unfavorite", {"draw-star", "unfavorite"}},
|
{"unfavorite", {"draw-star", "unfavorite"}},
|
||||||
{"list-add", {"list-add", "add"}},
|
{"list-add", {"list-add", "add"}},
|
||||||
|
{"folder", {"folder", "folder"}},
|
||||||
|
{"document-preview", {"document-preview", "document-preview"}}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,7 +36,8 @@ Shared::Message::Message(Shared::Message::Type p_type):
|
|||||||
errorText(),
|
errorText(),
|
||||||
originalMessage(),
|
originalMessage(),
|
||||||
lastModified(),
|
lastModified(),
|
||||||
stanzaId()
|
stanzaId(),
|
||||||
|
attachPath()
|
||||||
{}
|
{}
|
||||||
|
|
||||||
Shared::Message::Message():
|
Shared::Message::Message():
|
||||||
@ -56,7 +57,8 @@ Shared::Message::Message():
|
|||||||
errorText(),
|
errorText(),
|
||||||
originalMessage(),
|
originalMessage(),
|
||||||
lastModified(),
|
lastModified(),
|
||||||
stanzaId()
|
stanzaId(),
|
||||||
|
attachPath()
|
||||||
{}
|
{}
|
||||||
|
|
||||||
QString Shared::Message::getBody() const
|
QString Shared::Message::getBody() const
|
||||||
@ -311,6 +313,7 @@ void Shared::Message::serialize(QDataStream& data) const
|
|||||||
data << lastModified;
|
data << lastModified;
|
||||||
}
|
}
|
||||||
data << stanzaId;
|
data << stanzaId;
|
||||||
|
data << attachPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Shared::Message::deserialize(QDataStream& data)
|
void Shared::Message::deserialize(QDataStream& data)
|
||||||
@ -341,6 +344,7 @@ void Shared::Message::deserialize(QDataStream& data)
|
|||||||
data >> lastModified;
|
data >> lastModified;
|
||||||
}
|
}
|
||||||
data >> stanzaId;
|
data >> stanzaId;
|
||||||
|
data >> attachPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Shared::Message::change(const QMap<QString, QVariant>& data)
|
bool Shared::Message::change(const QMap<QString, QVariant>& data)
|
||||||
@ -350,6 +354,16 @@ bool Shared::Message::change(const QMap<QString, QVariant>& data)
|
|||||||
setState(static_cast<State>(itr.value().toUInt()));
|
setState(static_cast<State>(itr.value().toUInt()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
itr = data.find("outOfBandUrl");
|
||||||
|
if (itr != data.end()) {
|
||||||
|
setOutOfBandUrl(itr.value().toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
itr = data.find("attachPath");
|
||||||
|
if (itr != data.end()) {
|
||||||
|
setAttachPath(itr.value().toString());
|
||||||
|
}
|
||||||
|
|
||||||
if (state == State::error) {
|
if (state == State::error) {
|
||||||
itr = data.find("errorText");
|
itr = data.find("errorText");
|
||||||
if (itr != data.end()) {
|
if (itr != data.end()) {
|
||||||
@ -380,6 +394,8 @@ bool Shared::Message::change(const QMap<QString, QVariant>& data)
|
|||||||
|
|
||||||
itr = data.find("body");
|
itr = data.find("body");
|
||||||
if (itr != data.end()) {
|
if (itr != data.end()) {
|
||||||
|
QString b = itr.value().toString();
|
||||||
|
if (body != b) {
|
||||||
QMap<QString, QVariant>::const_iterator dItr = data.find("stamp");
|
QMap<QString, QVariant>::const_iterator dItr = data.find("stamp");
|
||||||
QDateTime correctionDate;
|
QDateTime correctionDate;
|
||||||
if (dItr != data.end()) {
|
if (dItr != data.end()) {
|
||||||
@ -390,10 +406,19 @@ bool Shared::Message::change(const QMap<QString, QVariant>& data)
|
|||||||
if (!edited || lastModified < correctionDate) {
|
if (!edited || lastModified < correctionDate) {
|
||||||
originalMessage = body;
|
originalMessage = body;
|
||||||
lastModified = correctionDate;
|
lastModified = correctionDate;
|
||||||
setBody(itr.value().toString());
|
setBody(body);
|
||||||
setEdited(true);
|
setEdited(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
QMap<QString, QVariant>::const_iterator dItr = data.find("stamp");
|
||||||
|
if (dItr != data.end()) {
|
||||||
|
QDateTime ntime = dItr.value().toDateTime();
|
||||||
|
if (time != ntime) {
|
||||||
|
setTime(ntime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return idChanged;
|
return idChanged;
|
||||||
}
|
}
|
||||||
@ -420,7 +445,7 @@ void Shared::Message::setOutOfBandUrl(const QString& url)
|
|||||||
|
|
||||||
bool Shared::Message::storable() const
|
bool Shared::Message::storable() const
|
||||||
{
|
{
|
||||||
return id.size() > 0 && (body.size() > 0 || oob.size()) > 0;
|
return id.size() > 0 && (body.size() > 0 || oob.size() > 0 || attachPath.size() > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Shared::Message::setStanzaId(const QString& sid)
|
void Shared::Message::setStanzaId(const QString& sid)
|
||||||
@ -432,3 +457,33 @@ QString Shared::Message::getStanzaId() const
|
|||||||
{
|
{
|
||||||
return stanzaId;
|
return stanzaId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString Shared::Message::getAttachPath() const
|
||||||
|
{
|
||||||
|
return attachPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Shared::Message::setAttachPath(const QString& path)
|
||||||
|
{
|
||||||
|
attachPath = path;
|
||||||
|
}
|
||||||
|
|
||||||
|
Shared::Message::Change::Change(const QMap<QString, QVariant>& _data):
|
||||||
|
data(_data),
|
||||||
|
idModified(false) {}
|
||||||
|
|
||||||
|
void Shared::Message::Change::operator()(Shared::Message& msg)
|
||||||
|
{
|
||||||
|
idModified = msg.change(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Shared::Message::Change::operator()(Shared::Message* msg)
|
||||||
|
{
|
||||||
|
idModified = msg->change(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Shared::Message::Change::hasIdBeenModified() const
|
||||||
|
{
|
||||||
|
return idModified;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -16,15 +16,15 @@
|
|||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#ifndef SHAPER_MESSAGE_H
|
||||||
|
#define SHAPER_MESSAGE_H
|
||||||
|
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
#include <QVariant>
|
#include <QVariant>
|
||||||
#include <QMap>
|
#include <QMap>
|
||||||
#include <QDataStream>
|
#include <QDataStream>
|
||||||
|
|
||||||
#ifndef SHAPER_MESSAGE_H
|
|
||||||
#define SHAPER_MESSAGE_H
|
|
||||||
|
|
||||||
namespace Shared {
|
namespace Shared {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -46,9 +46,22 @@ public:
|
|||||||
delivered,
|
delivered,
|
||||||
error
|
error
|
||||||
};
|
};
|
||||||
|
|
||||||
static const State StateHighest = State::error;
|
static const State StateHighest = State::error;
|
||||||
static const State StateLowest = State::pending;
|
static const State StateLowest = State::pending;
|
||||||
|
|
||||||
|
struct Change //change functor, stores in idModified if ID has been modified during change
|
||||||
|
{
|
||||||
|
Change(const QMap<QString, QVariant>& _data);
|
||||||
|
void operator() (Message& msg);
|
||||||
|
void operator() (Message* msg);
|
||||||
|
bool hasIdBeenModified() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
const QMap<QString, QVariant>& data;
|
||||||
|
bool idModified;
|
||||||
|
};
|
||||||
|
|
||||||
Message(Type p_type);
|
Message(Type p_type);
|
||||||
Message();
|
Message();
|
||||||
|
|
||||||
@ -72,6 +85,7 @@ public:
|
|||||||
void setErrorText(const QString& err);
|
void setErrorText(const QString& err);
|
||||||
bool change(const QMap<QString, QVariant>& data);
|
bool change(const QMap<QString, QVariant>& data);
|
||||||
void setStanzaId(const QString& sid);
|
void setStanzaId(const QString& sid);
|
||||||
|
void setAttachPath(const QString& path);
|
||||||
|
|
||||||
QString getFrom() const;
|
QString getFrom() const;
|
||||||
QString getFromJid() const;
|
QString getFromJid() const;
|
||||||
@ -100,6 +114,7 @@ public:
|
|||||||
QDateTime getLastModified() const;
|
QDateTime getLastModified() const;
|
||||||
QString getOriginalBody() const;
|
QString getOriginalBody() const;
|
||||||
QString getStanzaId() const;
|
QString getStanzaId() const;
|
||||||
|
QString getAttachPath() const;
|
||||||
|
|
||||||
void serialize(QDataStream& data) const;
|
void serialize(QDataStream& data) const;
|
||||||
void deserialize(QDataStream& data);
|
void deserialize(QDataStream& data);
|
||||||
@ -123,6 +138,7 @@ private:
|
|||||||
QString originalMessage;
|
QString originalMessage;
|
||||||
QDateTime lastModified;
|
QDateTime lastModified;
|
||||||
QString stanzaId;
|
QString stanzaId;
|
||||||
|
QString attachPath;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
45
shared/messageinfo.cpp
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
/*
|
||||||
|
* Squawk messenger.
|
||||||
|
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "messageinfo.h"
|
||||||
|
|
||||||
|
using namespace Shared;
|
||||||
|
|
||||||
|
Shared::MessageInfo::MessageInfo():
|
||||||
|
account(),
|
||||||
|
jid(),
|
||||||
|
messageId() {}
|
||||||
|
|
||||||
|
Shared::MessageInfo::MessageInfo(const QString& acc, const QString& j, const QString& id):
|
||||||
|
account(acc),
|
||||||
|
jid(j),
|
||||||
|
messageId(id) {}
|
||||||
|
|
||||||
|
Shared::MessageInfo::MessageInfo(const Shared::MessageInfo& other):
|
||||||
|
account(other.account),
|
||||||
|
jid(other.jid),
|
||||||
|
messageId(other.messageId) {}
|
||||||
|
|
||||||
|
Shared::MessageInfo & Shared::MessageInfo::operator=(const Shared::MessageInfo& other)
|
||||||
|
{
|
||||||
|
account = other.account;
|
||||||
|
jid = other.jid;
|
||||||
|
messageId = other.messageId;
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
43
shared/messageinfo.h
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* Squawk messenger.
|
||||||
|
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef SHARED_MESSAGEINFO_H
|
||||||
|
#define SHARED_MESSAGEINFO_H
|
||||||
|
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
namespace Shared {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @todo write docs
|
||||||
|
*/
|
||||||
|
struct MessageInfo {
|
||||||
|
MessageInfo();
|
||||||
|
MessageInfo(const QString& acc, const QString& j, const QString& id);
|
||||||
|
MessageInfo(const MessageInfo& other);
|
||||||
|
|
||||||
|
QString account;
|
||||||
|
QString jid;
|
||||||
|
QString messageId;
|
||||||
|
|
||||||
|
MessageInfo& operator=(const MessageInfo& other);
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // SHARED_MESSAGEINFO_H
|
@ -20,9 +20,12 @@
|
|||||||
#define SHARED_UTILS_H
|
#define SHARED_UTILS_H
|
||||||
|
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
#include <QStringList>
|
||||||
#include <QColor>
|
#include <QColor>
|
||||||
#include <QRegularExpression>
|
#include <QRegularExpression>
|
||||||
|
|
||||||
|
//#include "KIO/OpenFileManagerWindowJob"
|
||||||
|
|
||||||
#include <uuid/uuid.h>
|
#include <uuid/uuid.h>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
cmake_minimum_required(VERSION 3.0)
|
cmake_minimum_required(VERSION 3.3)
|
||||||
project(squawkUI)
|
project(squawkUI)
|
||||||
|
|
||||||
# Instruct CMake to run moc automatically when needed.
|
# Instruct CMake to run moc automatically when needed.
|
||||||
@ -7,9 +7,13 @@ set(CMAKE_AUTOMOC ON)
|
|||||||
set(CMAKE_AUTOUIC ON)
|
set(CMAKE_AUTOUIC ON)
|
||||||
|
|
||||||
# Find the QtWidgets library
|
# Find the QtWidgets library
|
||||||
find_package(Qt5Widgets CONFIG REQUIRED)
|
find_package(Qt5 CONFIG REQUIRED COMPONENTS Widgets DBus Core)
|
||||||
find_package(Qt5DBus CONFIG REQUIRED)
|
find_package(Boost 1.36.0 REQUIRED)
|
||||||
|
if(Boost_FOUND)
|
||||||
|
include_directories(${Boost_INCLUDE_DIRS})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
add_subdirectory(utils)
|
||||||
add_subdirectory(widgets)
|
add_subdirectory(widgets)
|
||||||
|
|
||||||
set(squawkUI_SRC
|
set(squawkUI_SRC
|
||||||
@ -25,21 +29,15 @@ set(squawkUI_SRC
|
|||||||
models/abstractparticipant.cpp
|
models/abstractparticipant.cpp
|
||||||
models/participant.cpp
|
models/participant.cpp
|
||||||
models/reference.cpp
|
models/reference.cpp
|
||||||
utils/messageline.cpp
|
models/messagefeed.cpp
|
||||||
utils//message.cpp
|
models/element.cpp
|
||||||
utils/resizer.cpp
|
|
||||||
utils/image.cpp
|
|
||||||
utils/flowlayout.cpp
|
|
||||||
utils/badge.cpp
|
|
||||||
utils/progress.cpp
|
|
||||||
utils/comboboxdelegate.cpp
|
|
||||||
utils/dropshadoweffect.cpp
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Tell CMake to create the helloworld executable
|
# Tell CMake to create the helloworld executable
|
||||||
add_library(squawkUI ${squawkUI_SRC})
|
add_library(squawkUI STATIC ${squawkUI_SRC})
|
||||||
|
|
||||||
# Use the Widgets module from Qt 5.
|
# Use the Widgets module from Qt 5.
|
||||||
target_link_libraries(squawkUI squawkWidgets)
|
target_link_libraries(squawkUI squawkWidgets)
|
||||||
|
target_link_libraries(squawkUI squawkUIUtils)
|
||||||
target_link_libraries(squawkUI Qt5::Widgets)
|
target_link_libraries(squawkUI Qt5::Widgets)
|
||||||
target_link_libraries(squawkUI Qt5::DBus)
|
target_link_libraries(squawkUI Qt5::DBus)
|
||||||
|
@ -231,7 +231,7 @@ void Models::Account::toOfflineState()
|
|||||||
Item::toOfflineState();
|
Item::toOfflineState();
|
||||||
}
|
}
|
||||||
|
|
||||||
QString Models::Account::getAvatarPath()
|
QString Models::Account::getAvatarPath() const
|
||||||
{
|
{
|
||||||
return avatarPath;
|
return avatarPath;
|
||||||
}
|
}
|
||||||
|
@ -57,7 +57,7 @@ namespace Models {
|
|||||||
QString getError() const;
|
QString getError() const;
|
||||||
|
|
||||||
void setAvatarPath(const QString& path);
|
void setAvatarPath(const QString& path);
|
||||||
QString getAvatarPath();
|
QString getAvatarPath() const;
|
||||||
|
|
||||||
void setAvailability(Shared::Availability p_avail);
|
void setAvailability(Shared::Availability p_avail);
|
||||||
void setAvailability(unsigned int p_avail);
|
void setAvailability(unsigned int p_avail);
|
||||||
|
@ -17,55 +17,26 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "contact.h"
|
#include "contact.h"
|
||||||
#include "account.h"
|
|
||||||
|
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
|
|
||||||
Models::Contact::Contact(const Account* acc, const QString& p_jid ,const QMap<QString, QVariant> &data, Item *parentItem):
|
Models::Contact::Contact(const Account* acc, const QString& p_jid ,const QMap<QString, QVariant> &data, Item *parentItem):
|
||||||
Item(Item::contact, data, parentItem),
|
Element(Item::contact, acc, p_jid, data, parentItem),
|
||||||
jid(p_jid),
|
|
||||||
availability(Shared::Availability::offline),
|
availability(Shared::Availability::offline),
|
||||||
state(Shared::SubscriptionState::none),
|
state(Shared::SubscriptionState::none),
|
||||||
avatarState(Shared::Avatar::empty),
|
|
||||||
presences(),
|
presences(),
|
||||||
messages(),
|
status()
|
||||||
childMessages(0),
|
|
||||||
status(),
|
|
||||||
avatarPath(),
|
|
||||||
account(acc)
|
|
||||||
{
|
{
|
||||||
QMap<QString, QVariant>::const_iterator itr = data.find("state");
|
QMap<QString, QVariant>::const_iterator itr = data.find("state");
|
||||||
if (itr != data.end()) {
|
if (itr != data.end()) {
|
||||||
setState(itr.value().toUInt());
|
setState(itr.value().toUInt());
|
||||||
}
|
}
|
||||||
|
|
||||||
itr = data.find("avatarState");
|
|
||||||
if (itr != data.end()) {
|
|
||||||
setAvatarState(itr.value().toUInt());
|
|
||||||
}
|
|
||||||
itr = data.find("avatarPath");
|
|
||||||
if (itr != data.end()) {
|
|
||||||
setAvatarPath(itr.value().toString());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Models::Contact::~Contact()
|
Models::Contact::~Contact()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
QString Models::Contact::getJid() const
|
|
||||||
{
|
|
||||||
return jid;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Models::Contact::setJid(const QString p_jid)
|
|
||||||
{
|
|
||||||
if (jid != p_jid) {
|
|
||||||
jid = p_jid;
|
|
||||||
changed(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Models::Contact::setAvailability(unsigned int p_state)
|
void Models::Contact::setAvailability(unsigned int p_state)
|
||||||
{
|
{
|
||||||
setAvailability(Shared::Global::fromInt<Shared::Availability>(p_state));
|
setAvailability(Shared::Global::fromInt<Shared::Availability>(p_state));
|
||||||
@ -144,16 +115,12 @@ void Models::Contact::update(const QString& field, const QVariant& value)
|
|||||||
{
|
{
|
||||||
if (field == "name") {
|
if (field == "name") {
|
||||||
setName(value.toString());
|
setName(value.toString());
|
||||||
} else if (field == "jid") {
|
|
||||||
setJid(value.toString());
|
|
||||||
} else if (field == "availability") {
|
} else if (field == "availability") {
|
||||||
setAvailability(value.toUInt());
|
setAvailability(value.toUInt());
|
||||||
} else if (field == "state") {
|
} else if (field == "state") {
|
||||||
setState(value.toUInt());
|
setState(value.toUInt());
|
||||||
} else if (field == "avatarState") {
|
} else {
|
||||||
setAvatarState(value.toUInt());
|
Element::update(field, value);
|
||||||
} else if (field == "avatarPath") {
|
|
||||||
setAvatarPath(value.toString());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -192,11 +159,9 @@ void Models::Contact::refresh()
|
|||||||
{
|
{
|
||||||
QDateTime lastActivity;
|
QDateTime lastActivity;
|
||||||
Presence* presence = 0;
|
Presence* presence = 0;
|
||||||
unsigned int count = 0;
|
|
||||||
for (QMap<QString, Presence*>::iterator itr = presences.begin(), end = presences.end(); itr != end; ++itr) {
|
for (QMap<QString, Presence*>::iterator itr = presences.begin(), end = presences.end(); itr != end; ++itr) {
|
||||||
Presence* pr = itr.value();
|
Presence* pr = itr.value();
|
||||||
QDateTime la = pr->getLastActivity();
|
QDateTime la = pr->getLastActivity();
|
||||||
count += pr->getMessagesCount();
|
|
||||||
|
|
||||||
if (la > lastActivity) {
|
if (la > lastActivity) {
|
||||||
lastActivity = la;
|
lastActivity = la;
|
||||||
@ -211,11 +176,6 @@ void Models::Contact::refresh()
|
|||||||
setAvailability(Shared::Availability::offline);
|
setAvailability(Shared::Availability::offline);
|
||||||
setStatus("");
|
setStatus("");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (childMessages != count) {
|
|
||||||
childMessages = count;
|
|
||||||
changed(4);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Models::Contact::_removeChild(int index)
|
void Models::Contact::_removeChild(int index)
|
||||||
@ -251,87 +211,12 @@ QIcon Models::Contact::getStatusIcon(bool big) const
|
|||||||
if (getMessagesCount() > 0) {
|
if (getMessagesCount() > 0) {
|
||||||
return Shared::icon("mail-message", big);
|
return Shared::icon("mail-message", big);
|
||||||
} else if (state == Shared::SubscriptionState::both || state == Shared::SubscriptionState::to) {
|
} else if (state == Shared::SubscriptionState::both || state == Shared::SubscriptionState::to) {
|
||||||
return Shared::availabilityIcon(availability, big);;
|
return Shared::availabilityIcon(availability, big);
|
||||||
} else {
|
} else {
|
||||||
return Shared::subscriptionStateIcon(state, big);
|
return Shared::subscriptionStateIcon(state, big);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Models::Contact::addMessage(const Shared::Message& data)
|
|
||||||
{
|
|
||||||
const QString& res = data.getPenPalResource();
|
|
||||||
if (res.size() > 0) {
|
|
||||||
QMap<QString, Presence*>::iterator itr = presences.find(res);
|
|
||||||
if (itr == presences.end()) {
|
|
||||||
// this is actually the place when I can spot someone's invisible presence, and there is nothing criminal in it, cuz the sender sent us a message
|
|
||||||
// therefore he have revealed himself
|
|
||||||
// the only issue is to find out when the sender is gone offline
|
|
||||||
Presence* pr = new Presence({});
|
|
||||||
pr->setName(res);
|
|
||||||
pr->setAvailability(Shared::Availability::invisible);
|
|
||||||
pr->setLastActivity(QDateTime::currentDateTimeUtc());
|
|
||||||
presences.insert(res, pr);
|
|
||||||
appendChild(pr);
|
|
||||||
pr->addMessage(data);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
itr.value()->addMessage(data);
|
|
||||||
} else {
|
|
||||||
messages.emplace_back(data);
|
|
||||||
changed(4);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Models::Contact::changeMessage(const QString& id, const QMap<QString, QVariant>& data)
|
|
||||||
{
|
|
||||||
|
|
||||||
bool found = false;
|
|
||||||
for (Shared::Message& msg : messages) {
|
|
||||||
if (msg.getId() == id) {
|
|
||||||
msg.change(data);
|
|
||||||
found = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!found) {
|
|
||||||
for (Presence* pr : presences) {
|
|
||||||
found = pr->changeMessage(id, data);
|
|
||||||
if (found) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned int Models::Contact::getMessagesCount() const
|
|
||||||
{
|
|
||||||
return messages.size() + childMessages;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Models::Contact::dropMessages()
|
|
||||||
{
|
|
||||||
if (messages.size() > 0) {
|
|
||||||
messages.clear();
|
|
||||||
changed(4);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (QMap<QString, Presence*>::iterator itr = presences.begin(), end = presences.end(); itr != end; ++itr) {
|
|
||||||
itr.value()->dropMessages();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Models::Contact::getMessages(Models::Contact::Messages& container) const
|
|
||||||
{
|
|
||||||
for (Messages::const_iterator itr = messages.begin(), end = messages.end(); itr != end; ++itr) {
|
|
||||||
const Shared::Message& msg = *itr;
|
|
||||||
container.push_back(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (QMap<QString, Presence*>::const_iterator itr = presences.begin(), end = presences.end(); itr != end; ++itr) {
|
|
||||||
itr.value()->getMessages(container);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Models::Contact::toOfflineState()
|
void Models::Contact::toOfflineState()
|
||||||
{
|
{
|
||||||
std::deque<Item*>::size_type size = childItems.size();
|
std::deque<Item*>::size_type size = childItems.size();
|
||||||
@ -355,75 +240,3 @@ QString Models::Contact::getDisplayedName() const
|
|||||||
return getContactName();
|
return getContactName();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Models::Contact::columnInvolvedInDisplay(int col)
|
|
||||||
{
|
|
||||||
return Item::columnInvolvedInDisplay(col) && col == 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
Models::Contact * Models::Contact::copy() const
|
|
||||||
{
|
|
||||||
Contact* cnt = new Contact(*this);
|
|
||||||
return cnt;
|
|
||||||
}
|
|
||||||
|
|
||||||
Models::Contact::Contact(const Models::Contact& other):
|
|
||||||
Item(other),
|
|
||||||
jid(other.jid),
|
|
||||||
availability(other.availability),
|
|
||||||
state(other.state),
|
|
||||||
presences(),
|
|
||||||
messages(other.messages),
|
|
||||||
childMessages(0),
|
|
||||||
account(other.account)
|
|
||||||
{
|
|
||||||
for (const Presence* pres : other.presences) {
|
|
||||||
Presence* pCopy = new Presence(*pres);
|
|
||||||
presences.insert(pCopy->getName(), pCopy);
|
|
||||||
Item::appendChild(pCopy);
|
|
||||||
connect(pCopy, &Item::childChanged, this, &Contact::refresh);
|
|
||||||
}
|
|
||||||
|
|
||||||
refresh();
|
|
||||||
}
|
|
||||||
|
|
||||||
QString Models::Contact::getAvatarPath() const
|
|
||||||
{
|
|
||||||
return avatarPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
Shared::Avatar Models::Contact::getAvatarState() const
|
|
||||||
{
|
|
||||||
return avatarState;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Models::Contact::setAvatarPath(const QString& path)
|
|
||||||
{
|
|
||||||
if (path != avatarPath) {
|
|
||||||
avatarPath = path;
|
|
||||||
changed(7);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Models::Contact::setAvatarState(Shared::Avatar p_state)
|
|
||||||
{
|
|
||||||
if (avatarState != p_state) {
|
|
||||||
avatarState = p_state;
|
|
||||||
changed(6);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Models::Contact::setAvatarState(unsigned int p_state)
|
|
||||||
{
|
|
||||||
if (p_state <= static_cast<quint8>(Shared::Avatar::valid)) {
|
|
||||||
Shared::Avatar state = static_cast<Shared::Avatar>(p_state);
|
|
||||||
setAvatarState(state);
|
|
||||||
} else {
|
|
||||||
qDebug() << "An attempt to set invalid avatar state" << p_state << "to the contact" << jid << ", skipping";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const Models::Account * Models::Contact::getParentAccount() const
|
|
||||||
{
|
|
||||||
return account;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
#ifndef MODELS_CONTACT_H
|
#ifndef MODELS_CONTACT_H
|
||||||
#define MODELS_CONTACT_H
|
#define MODELS_CONTACT_H
|
||||||
|
|
||||||
#include "item.h"
|
#include "element.h"
|
||||||
#include "presence.h"
|
#include "presence.h"
|
||||||
#include "shared/enums.h"
|
#include "shared/enums.h"
|
||||||
#include "shared/message.h"
|
#include "shared/message.h"
|
||||||
@ -31,49 +31,34 @@
|
|||||||
#include <deque>
|
#include <deque>
|
||||||
|
|
||||||
namespace Models {
|
namespace Models {
|
||||||
class Account;
|
|
||||||
|
|
||||||
class Contact : public Item
|
class Contact : public Element
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
typedef std::deque<Shared::Message> Messages;
|
|
||||||
Contact(const Account* acc, const QString& p_jid, const QMap<QString, QVariant> &data, Item *parentItem = 0);
|
Contact(const Account* acc, const QString& p_jid, const QMap<QString, QVariant> &data, Item *parentItem = 0);
|
||||||
Contact(const Contact& other);
|
|
||||||
~Contact();
|
~Contact();
|
||||||
|
|
||||||
QString getJid() const;
|
|
||||||
Shared::Availability getAvailability() const;
|
Shared::Availability getAvailability() const;
|
||||||
Shared::SubscriptionState getState() const;
|
Shared::SubscriptionState getState() const;
|
||||||
Shared::Avatar getAvatarState() const;
|
|
||||||
QString getAvatarPath() const;
|
|
||||||
QIcon getStatusIcon(bool big = false) const;
|
QIcon getStatusIcon(bool big = false) const;
|
||||||
|
|
||||||
int columnCount() const override;
|
int columnCount() const override;
|
||||||
QVariant data(int column) const override;
|
QVariant data(int column) const override;
|
||||||
|
|
||||||
void update(const QString& field, const QVariant& value);
|
void update(const QString& field, const QVariant& value) override;
|
||||||
|
|
||||||
void addPresence(const QString& name, const QMap<QString, QVariant>& data);
|
void addPresence(const QString& name, const QMap<QString, QVariant>& data);
|
||||||
void removePresence(const QString& name);
|
void removePresence(const QString& name);
|
||||||
|
|
||||||
QString getContactName() const;
|
QString getContactName() const;
|
||||||
QString getStatus() const;
|
QString getStatus() const;
|
||||||
|
|
||||||
void addMessage(const Shared::Message& data);
|
|
||||||
void changeMessage(const QString& id, const QMap<QString, QVariant>& data);
|
|
||||||
unsigned int getMessagesCount() const;
|
|
||||||
void dropMessages();
|
|
||||||
void getMessages(Messages& container) const;
|
|
||||||
QString getDisplayedName() const override;
|
QString getDisplayedName() const override;
|
||||||
|
|
||||||
Contact* copy() const;
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void _removeChild(int index) override;
|
void _removeChild(int index) override;
|
||||||
void _appendChild(Models::Item * child) override;
|
void _appendChild(Models::Item * child) override;
|
||||||
bool columnInvolvedInDisplay(int col) override;
|
|
||||||
const Account* getParentAccount() const override;
|
|
||||||
|
|
||||||
protected slots:
|
protected slots:
|
||||||
void refresh();
|
void refresh();
|
||||||
@ -84,23 +69,13 @@ protected:
|
|||||||
void setAvailability(unsigned int p_state);
|
void setAvailability(unsigned int p_state);
|
||||||
void setState(Shared::SubscriptionState p_state);
|
void setState(Shared::SubscriptionState p_state);
|
||||||
void setState(unsigned int p_state);
|
void setState(unsigned int p_state);
|
||||||
void setAvatarState(Shared::Avatar p_state);
|
|
||||||
void setAvatarState(unsigned int p_state);
|
|
||||||
void setAvatarPath(const QString& path);
|
|
||||||
void setJid(const QString p_jid);
|
|
||||||
void setStatus(const QString& p_state);
|
void setStatus(const QString& p_state);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QString jid;
|
|
||||||
Shared::Availability availability;
|
Shared::Availability availability;
|
||||||
Shared::SubscriptionState state;
|
Shared::SubscriptionState state;
|
||||||
Shared::Avatar avatarState;
|
|
||||||
QMap<QString, Presence*> presences;
|
QMap<QString, Presence*> presences;
|
||||||
Messages messages;
|
|
||||||
unsigned int childMessages;
|
|
||||||
QString status;
|
QString status;
|
||||||
QString avatarPath;
|
|
||||||
const Account* account;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
184
ui/models/element.cpp
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
/*
|
||||||
|
* Squawk messenger.
|
||||||
|
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "element.h"
|
||||||
|
#include "account.h"
|
||||||
|
|
||||||
|
#include <QDebug>
|
||||||
|
|
||||||
|
Models::Element::Element(Type p_type, const Models::Account* acc, const QString& p_jid, const QMap<QString, QVariant>& data, Models::Item* parentItem):
|
||||||
|
Item(p_type, data, parentItem),
|
||||||
|
jid(p_jid),
|
||||||
|
avatarPath(),
|
||||||
|
avatarState(Shared::Avatar::empty),
|
||||||
|
account(acc),
|
||||||
|
feed(new MessageFeed(this))
|
||||||
|
{
|
||||||
|
connect(feed, &MessageFeed::requestArchive, this, &Element::requestArchive);
|
||||||
|
connect(feed, &MessageFeed::fileDownloadRequest, this, &Element::fileDownloadRequest);
|
||||||
|
connect(feed, &MessageFeed::unreadMessagesCountChanged, this, &Element::onFeedUnreadMessagesCountChanged);
|
||||||
|
connect(feed, &MessageFeed::unnoticedMessage, this, &Element::onFeedUnnoticedMessage);
|
||||||
|
connect(feed, &MessageFeed::localPathInvalid, this, &Element::localPathInvalid);
|
||||||
|
|
||||||
|
QMap<QString, QVariant>::const_iterator itr = data.find("avatarState");
|
||||||
|
if (itr != data.end()) {
|
||||||
|
setAvatarState(itr.value().toUInt());
|
||||||
|
}
|
||||||
|
itr = data.find("avatarPath");
|
||||||
|
if (itr != data.end()) {
|
||||||
|
setAvatarPath(itr.value().toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Models::Element::~Element()
|
||||||
|
{
|
||||||
|
delete feed;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
QString Models::Element::getJid() const
|
||||||
|
{
|
||||||
|
return jid;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Models::Element::setJid(const QString& p_jid)
|
||||||
|
{
|
||||||
|
if (jid != p_jid) {
|
||||||
|
jid = p_jid;
|
||||||
|
changed(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Models::Element::update(const QString& field, const QVariant& value)
|
||||||
|
{
|
||||||
|
if (field == "jid") {
|
||||||
|
setJid(value.toString());
|
||||||
|
} else if (field == "avatarState") {
|
||||||
|
setAvatarState(value.toUInt());
|
||||||
|
} else if (field == "avatarPath") {
|
||||||
|
setAvatarPath(value.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QString Models::Element::getAvatarPath() const
|
||||||
|
{
|
||||||
|
return avatarPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
Shared::Avatar Models::Element::getAvatarState() const
|
||||||
|
{
|
||||||
|
return avatarState;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Models::Element::setAvatarPath(const QString& path)
|
||||||
|
{
|
||||||
|
if (path != avatarPath) {
|
||||||
|
avatarPath = path;
|
||||||
|
if (type == contact) {
|
||||||
|
changed(7);
|
||||||
|
} else if (type == room) {
|
||||||
|
changed(8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Models::Element::setAvatarState(Shared::Avatar p_state)
|
||||||
|
{
|
||||||
|
if (avatarState != p_state) {
|
||||||
|
avatarState = p_state;
|
||||||
|
if (type == contact) {
|
||||||
|
changed(6);
|
||||||
|
} else if (type == room) {
|
||||||
|
changed(7);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Models::Element::setAvatarState(unsigned int p_state)
|
||||||
|
{
|
||||||
|
if (p_state <= static_cast<quint8>(Shared::Avatar::valid)) {
|
||||||
|
Shared::Avatar state = static_cast<Shared::Avatar>(p_state);
|
||||||
|
setAvatarState(state);
|
||||||
|
} else {
|
||||||
|
qDebug() << "An attempt to set invalid avatar state" << p_state << "to the element" << jid << ", skipping";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Models::Element::columnInvolvedInDisplay(int col)
|
||||||
|
{
|
||||||
|
return Item::columnInvolvedInDisplay(col) && col == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Models::Account * Models::Element::getParentAccount() const
|
||||||
|
{
|
||||||
|
return account;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int Models::Element::getMessagesCount() const
|
||||||
|
{
|
||||||
|
return feed->unreadMessagesCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Models::Element::addMessage(const Shared::Message& data)
|
||||||
|
{
|
||||||
|
feed->addMessage(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Models::Element::changeMessage(const QString& id, const QMap<QString, QVariant>& data)
|
||||||
|
{
|
||||||
|
feed->changeMessage(id, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Models::Element::responseArchive(const std::list<Shared::Message> list, bool last)
|
||||||
|
{
|
||||||
|
feed->responseArchive(list, last);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Models::Element::isRoom() const
|
||||||
|
{
|
||||||
|
return type != contact;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Models::Element::fileProgress(const QString& messageId, qreal value, bool up)
|
||||||
|
{
|
||||||
|
feed->fileProgress(messageId, value, up);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Models::Element::fileComplete(const QString& messageId, bool up)
|
||||||
|
{
|
||||||
|
feed->fileComplete(messageId, up);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Models::Element::fileError(const QString& messageId, const QString& error, bool up)
|
||||||
|
{
|
||||||
|
feed->fileError(messageId, error, up);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Models::Element::onFeedUnreadMessagesCountChanged()
|
||||||
|
{
|
||||||
|
if (type == contact) {
|
||||||
|
changed(4);
|
||||||
|
} else if (type == room) {
|
||||||
|
changed(5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Models::Element::onFeedUnnoticedMessage(const Shared::Message& msg)
|
||||||
|
{
|
||||||
|
emit unnoticedMessage(getAccountName(), msg);
|
||||||
|
}
|
81
ui/models/element.h
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
/*
|
||||||
|
* Squawk messenger.
|
||||||
|
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef ELEMENT_H
|
||||||
|
#define ELEMENT_H
|
||||||
|
|
||||||
|
#include "item.h"
|
||||||
|
#include "messagefeed.h"
|
||||||
|
|
||||||
|
namespace Models {
|
||||||
|
|
||||||
|
class Element : public Item
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
protected:
|
||||||
|
Element(Type p_type, const Account* acc, const QString& p_jid, const QMap<QString, QVariant> &data, Item *parentItem = 0);
|
||||||
|
~Element();
|
||||||
|
|
||||||
|
public:
|
||||||
|
QString getJid() const;
|
||||||
|
Shared::Avatar getAvatarState() const;
|
||||||
|
QString getAvatarPath() const;
|
||||||
|
|
||||||
|
virtual void update(const QString& field, const QVariant& value);
|
||||||
|
|
||||||
|
void addMessage(const Shared::Message& data);
|
||||||
|
void changeMessage(const QString& id, const QMap<QString, QVariant>& data);
|
||||||
|
unsigned int getMessagesCount() const;
|
||||||
|
void responseArchive(const std::list<Shared::Message> list, bool last);
|
||||||
|
bool isRoom() const;
|
||||||
|
void fileProgress(const QString& messageId, qreal value, bool up);
|
||||||
|
void fileError(const QString& messageId, const QString& error, bool up);
|
||||||
|
void fileComplete(const QString& messageId, bool up);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void requestArchive(const QString& before);
|
||||||
|
void fileDownloadRequest(const QString& url);
|
||||||
|
void unnoticedMessage(const QString& account, const Shared::Message& msg);
|
||||||
|
void localPathInvalid(const QString& path);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void setJid(const QString& p_jid);
|
||||||
|
void setAvatarState(Shared::Avatar p_state);
|
||||||
|
void setAvatarState(unsigned int p_state);
|
||||||
|
void setAvatarPath(const QString& path);
|
||||||
|
bool columnInvolvedInDisplay(int col) override;
|
||||||
|
const Account* getParentAccount() const override;
|
||||||
|
|
||||||
|
protected slots:
|
||||||
|
void onFeedUnreadMessagesCountChanged();
|
||||||
|
void onFeedUnnoticedMessage(const Shared::Message& msg);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
QString jid;
|
||||||
|
QString avatarPath;
|
||||||
|
Shared::Avatar avatarState;
|
||||||
|
|
||||||
|
const Account* account;
|
||||||
|
|
||||||
|
public:
|
||||||
|
MessageFeed* feed;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // ELEMENT_H
|
@ -283,6 +283,15 @@ Shared::ConnectionState Models::Item::getAccountConnectionState() const
|
|||||||
return acc->getState();
|
return acc->getState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString Models::Item::getAccountAvatarPath() const
|
||||||
|
{
|
||||||
|
const Account* acc = getParentAccount();
|
||||||
|
if (acc == nullptr) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return acc->getAvatarPath();
|
||||||
|
}
|
||||||
|
|
||||||
QString Models::Item::getDisplayedName() const
|
QString Models::Item::getDisplayedName() const
|
||||||
{
|
{
|
||||||
return name;
|
return name;
|
||||||
|
@ -80,6 +80,7 @@ class Item : public QObject{
|
|||||||
QString getAccountName() const;
|
QString getAccountName() const;
|
||||||
QString getAccountJid() const;
|
QString getAccountJid() const;
|
||||||
QString getAccountResource() const;
|
QString getAccountResource() const;
|
||||||
|
QString getAccountAvatarPath() const;
|
||||||
Shared::ConnectionState getAccountConnectionState() const;
|
Shared::ConnectionState getAccountConnectionState() const;
|
||||||
Shared::Availability getAccountAvailability() const;
|
Shared::Availability getAccountAvailability() const;
|
||||||
|
|
||||||
|
576
ui/models/messagefeed.cpp
Normal file
@ -0,0 +1,576 @@
|
|||||||
|
/*
|
||||||
|
* 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 "messagefeed.h"
|
||||||
|
#include "element.h"
|
||||||
|
#include "room.h"
|
||||||
|
|
||||||
|
#include <QDebug>
|
||||||
|
|
||||||
|
const QHash<int, QByteArray> Models::MessageFeed::roles = {
|
||||||
|
{Text, "text"},
|
||||||
|
{Sender, "sender"},
|
||||||
|
{Date, "date"},
|
||||||
|
{DeliveryState, "deliveryState"},
|
||||||
|
{Correction, "correction"},
|
||||||
|
{SentByMe,"sentByMe"},
|
||||||
|
{Avatar, "avatar"},
|
||||||
|
{Attach, "attach"},
|
||||||
|
{Id, "id"},
|
||||||
|
{Error, "error"},
|
||||||
|
{Bulk, "bulk"}
|
||||||
|
};
|
||||||
|
|
||||||
|
Models::MessageFeed::MessageFeed(const Element* ri, QObject* parent):
|
||||||
|
QAbstractListModel(parent),
|
||||||
|
storage(),
|
||||||
|
indexById(storage.get<id>()),
|
||||||
|
indexByTime(storage.get<time>()),
|
||||||
|
rosterItem(ri),
|
||||||
|
syncState(incomplete),
|
||||||
|
uploads(),
|
||||||
|
downloads(),
|
||||||
|
unreadMessages(new std::set<QString>()),
|
||||||
|
observersAmount(0)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Models::MessageFeed::~MessageFeed()
|
||||||
|
{
|
||||||
|
delete unreadMessages;
|
||||||
|
|
||||||
|
for (Shared::Message* message : storage) {
|
||||||
|
delete message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Models::MessageFeed::addMessage(const Shared::Message& msg)
|
||||||
|
{
|
||||||
|
QString id = msg.getId();
|
||||||
|
StorageById::const_iterator itr = indexById.find(id);
|
||||||
|
if (itr != indexById.end()) {
|
||||||
|
qDebug() << "received more then one message with the same id, skipping yet the new one";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Shared::Message* copy = new Shared::Message(msg);
|
||||||
|
StorageByTime::const_iterator tItr = indexByTime.upper_bound(msg.getTime());
|
||||||
|
int position;
|
||||||
|
if (tItr == indexByTime.end()) {
|
||||||
|
position = storage.size();
|
||||||
|
} else {
|
||||||
|
position = indexByTime.rank(tItr);
|
||||||
|
}
|
||||||
|
beginInsertRows(QModelIndex(), position, position);
|
||||||
|
storage.insert(copy);
|
||||||
|
endInsertRows();
|
||||||
|
|
||||||
|
emit newMessage(msg);
|
||||||
|
|
||||||
|
if (observersAmount == 0 && !msg.getForwarded()) { //not to notify when the message is delivered by the carbon copy
|
||||||
|
unreadMessages->insert(msg.getId()); //cuz it could be my own one or the one I read on another device
|
||||||
|
emit unreadMessagesCountChanged();
|
||||||
|
emit unnoticedMessage(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Models::MessageFeed::changeMessage(const QString& id, const QMap<QString, QVariant>& data)
|
||||||
|
{
|
||||||
|
StorageById::iterator itr = indexById.find(id);
|
||||||
|
if (itr == indexById.end()) {
|
||||||
|
qDebug() << "received a command to change a message, but the message couldn't be found, skipping";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Shared::Message* msg = *itr;
|
||||||
|
std::set<MessageRoles> changeRoles = detectChanges(*msg, data);
|
||||||
|
QModelIndex index = modelIndexByTime(id, msg->getTime());
|
||||||
|
Shared::Message::Change functor(data);
|
||||||
|
bool success = indexById.modify(itr, functor);
|
||||||
|
if (!success) {
|
||||||
|
qDebug() << "received a command to change a message, but something went wrong modifying message in the feed, throwing error";
|
||||||
|
throw 872;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (functor.hasIdBeenModified()) {
|
||||||
|
changeRoles.insert(MessageRoles::Id);
|
||||||
|
std::set<QString>::const_iterator umi = unreadMessages->find(id);
|
||||||
|
if (umi != unreadMessages->end()) {
|
||||||
|
unreadMessages->erase(umi);
|
||||||
|
unreadMessages->insert(msg->getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changeRoles.size() > 0) {
|
||||||
|
//change message is a final event in download/upload event train
|
||||||
|
//only after changeMessage we can consider the download is done
|
||||||
|
Progress::const_iterator dItr = downloads.find(id);
|
||||||
|
bool attachOrError = changeRoles.count(MessageRoles::Attach) > 0 || changeRoles.count(MessageRoles::Error);
|
||||||
|
if (dItr != downloads.end()) {
|
||||||
|
if (attachOrError) {
|
||||||
|
downloads.erase(dItr);
|
||||||
|
} else if (changeRoles.count(MessageRoles::Id) > 0) {
|
||||||
|
qreal progress = dItr->second;
|
||||||
|
downloads.erase(dItr);
|
||||||
|
downloads.insert(std::make_pair(msg->getId(), progress));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
dItr = uploads.find(id);
|
||||||
|
if (dItr != uploads.end()) {
|
||||||
|
if (attachOrError) {
|
||||||
|
uploads.erase(dItr);
|
||||||
|
} else if (changeRoles.count(MessageRoles::Id) > 0) {
|
||||||
|
qreal progress = dItr->second;
|
||||||
|
uploads.erase(dItr);
|
||||||
|
uploads.insert(std::make_pair(msg->getId(), progress));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QVector<int> cr;
|
||||||
|
for (MessageRoles role : changeRoles) {
|
||||||
|
cr.push_back(role);
|
||||||
|
}
|
||||||
|
|
||||||
|
emit dataChanged(index, index, cr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::set<Models::MessageFeed::MessageRoles> Models::MessageFeed::detectChanges(const Shared::Message& msg, const QMap<QString, QVariant>& data) const
|
||||||
|
{
|
||||||
|
std::set<MessageRoles> roles;
|
||||||
|
Shared::Message::State state = msg.getState();
|
||||||
|
QMap<QString, QVariant>::const_iterator itr = data.find("state");
|
||||||
|
if (itr != data.end() && static_cast<Shared::Message::State>(itr.value().toUInt()) != state) {
|
||||||
|
roles.insert(MessageRoles::DeliveryState);
|
||||||
|
}
|
||||||
|
|
||||||
|
itr = data.find("outOfBandUrl");
|
||||||
|
bool att = false;
|
||||||
|
if (itr != data.end() && itr.value().toString() != msg.getOutOfBandUrl()) {
|
||||||
|
roles.insert(MessageRoles::Attach);
|
||||||
|
att = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!att) {
|
||||||
|
itr = data.find("attachPath");
|
||||||
|
if (itr != data.end() && itr.value().toString() != msg.getAttachPath()) {
|
||||||
|
roles.insert(MessageRoles::Attach);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state == Shared::Message::State::error) {
|
||||||
|
itr = data.find("errorText");
|
||||||
|
if (itr != data.end() && itr.value().toString() != msg.getErrorText()) {
|
||||||
|
roles.insert(MessageRoles::Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
itr = data.find("body");
|
||||||
|
if (itr != data.end() && itr.value().toString() != msg.getBody()) {
|
||||||
|
QMap<QString, QVariant>::const_iterator dItr = data.find("stamp");
|
||||||
|
QDateTime correctionDate;
|
||||||
|
if (dItr != data.end()) {
|
||||||
|
correctionDate = dItr.value().toDateTime();
|
||||||
|
} else {
|
||||||
|
correctionDate = QDateTime::currentDateTimeUtc(); //in case there is no information about time of this correction it's applied
|
||||||
|
}
|
||||||
|
if (!msg.getEdited() || msg.getLastModified() < correctionDate) {
|
||||||
|
roles.insert(MessageRoles::Text);
|
||||||
|
roles.insert(MessageRoles::Correction);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
QMap<QString, QVariant>::const_iterator dItr = data.find("stamp");
|
||||||
|
if (dItr != data.end()) {
|
||||||
|
QDateTime ntime = dItr.value().toDateTime();
|
||||||
|
if (msg.getTime() != ntime) {
|
||||||
|
roles.insert(MessageRoles::Date);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return roles;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Models::MessageFeed::removeMessage(const QString& id)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant Models::MessageFeed::data(const QModelIndex& index, int role) const
|
||||||
|
{
|
||||||
|
int i = index.row();
|
||||||
|
QVariant answer;
|
||||||
|
|
||||||
|
StorageByTime::const_iterator itr = indexByTime.nth(i);
|
||||||
|
if (itr != indexByTime.end()) {
|
||||||
|
const Shared::Message* msg = *itr;
|
||||||
|
|
||||||
|
switch (role) {
|
||||||
|
case Qt::DisplayRole:
|
||||||
|
case Text: {
|
||||||
|
QString body = msg->getBody();
|
||||||
|
if (body != msg->getOutOfBandUrl()) {
|
||||||
|
answer = body;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Sender:
|
||||||
|
if (sentByMe(*msg)) {
|
||||||
|
answer = rosterItem->getAccountName();
|
||||||
|
} else {
|
||||||
|
if (rosterItem->isRoom()) {
|
||||||
|
answer = msg->getFromResource();
|
||||||
|
} else {
|
||||||
|
answer = rosterItem->getDisplayedName();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Date:
|
||||||
|
answer = msg->getTime();
|
||||||
|
break;
|
||||||
|
case DeliveryState:
|
||||||
|
answer = static_cast<unsigned int>(msg->getState());
|
||||||
|
break;
|
||||||
|
case Correction:
|
||||||
|
answer = msg->getEdited();
|
||||||
|
break;
|
||||||
|
case SentByMe:
|
||||||
|
answer = sentByMe(*msg);
|
||||||
|
break;
|
||||||
|
case Avatar: {
|
||||||
|
QString path;
|
||||||
|
if (sentByMe(*msg)) {
|
||||||
|
path = rosterItem->getAccountAvatarPath();
|
||||||
|
} else if (!rosterItem->isRoom()) {
|
||||||
|
if (rosterItem->getAvatarState() != Shared::Avatar::empty) {
|
||||||
|
path = rosterItem->getAvatarPath();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const Room* room = static_cast<const Room*>(rosterItem);
|
||||||
|
path = room->getParticipantIconPath(msg->getFromResource());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (path.size() == 0) {
|
||||||
|
answer = Shared::iconPath("user", true);
|
||||||
|
} else {
|
||||||
|
answer = path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Attach:
|
||||||
|
answer.setValue(fillAttach(*msg));
|
||||||
|
break;
|
||||||
|
case Id:
|
||||||
|
answer.setValue(msg->getId());
|
||||||
|
break;
|
||||||
|
break;
|
||||||
|
case Error:
|
||||||
|
answer.setValue(msg->getErrorText());
|
||||||
|
break;
|
||||||
|
case Bulk: {
|
||||||
|
FeedItem item;
|
||||||
|
item.id = msg->getId();
|
||||||
|
|
||||||
|
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 = msg->getEdited();
|
||||||
|
|
||||||
|
QString body = msg->getBody();
|
||||||
|
if (body != msg->getOutOfBandUrl()) {
|
||||||
|
item.text = body;
|
||||||
|
}
|
||||||
|
|
||||||
|
item.avatar.clear();
|
||||||
|
if (item.sentByMe) {
|
||||||
|
item.sender = rosterItem->getAccountName();
|
||||||
|
item.avatar = rosterItem->getAccountAvatarPath();
|
||||||
|
} else {
|
||||||
|
if (rosterItem->isRoom()) {
|
||||||
|
item.sender = msg->getFromResource();
|
||||||
|
const Room* room = static_cast<const Room*>(rosterItem);
|
||||||
|
item.avatar = room->getParticipantIconPath(msg->getFromResource());
|
||||||
|
} else {
|
||||||
|
item.sender = rosterItem->getDisplayedName();
|
||||||
|
if (rosterItem->getAvatarState() != Shared::Avatar::empty) {
|
||||||
|
item.avatar = rosterItem->getAvatarPath();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.avatar.size() == 0) {
|
||||||
|
item.avatar = Shared::iconPath("user", true);
|
||||||
|
}
|
||||||
|
item.attach = fillAttach(*msg);
|
||||||
|
answer.setValue(item);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return answer;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Models::MessageFeed::rowCount(const QModelIndex& parent) const
|
||||||
|
{
|
||||||
|
return storage.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int Models::MessageFeed::unreadMessagesCount() const
|
||||||
|
{
|
||||||
|
return unreadMessages->size();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Models::MessageFeed::canFetchMore(const QModelIndex& parent) const
|
||||||
|
{
|
||||||
|
return syncState == incomplete;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Models::MessageFeed::fetchMore(const QModelIndex& parent)
|
||||||
|
{
|
||||||
|
if (syncState == incomplete) {
|
||||||
|
syncState = syncing;
|
||||||
|
emit syncStateChange(syncState);
|
||||||
|
emit requestStateChange(true);
|
||||||
|
|
||||||
|
if (storage.size() == 0) {
|
||||||
|
emit requestArchive("");
|
||||||
|
} else {
|
||||||
|
emit requestArchive((*indexByTime.rbegin())->getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Models::MessageFeed::responseArchive(const std::list<Shared::Message> list, bool last)
|
||||||
|
{
|
||||||
|
Storage::size_type size = storage.size();
|
||||||
|
|
||||||
|
beginInsertRows(QModelIndex(), size, size + list.size() - 1);
|
||||||
|
for (const Shared::Message& msg : list) {
|
||||||
|
Shared::Message* copy = new Shared::Message(msg);
|
||||||
|
storage.insert(copy);
|
||||||
|
}
|
||||||
|
endInsertRows();
|
||||||
|
|
||||||
|
if (syncState == syncing) {
|
||||||
|
if (last) {
|
||||||
|
syncState = complete;
|
||||||
|
} else {
|
||||||
|
syncState = incomplete;
|
||||||
|
}
|
||||||
|
emit syncStateChange(syncState);
|
||||||
|
emit requestStateChange(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QModelIndex Models::MessageFeed::index(int row, int column, const QModelIndex& parent) const
|
||||||
|
{
|
||||||
|
if (!hasIndex(row, column, parent)) {
|
||||||
|
return QModelIndex();
|
||||||
|
}
|
||||||
|
|
||||||
|
StorageByTime::iterator itr = indexByTime.nth(row);
|
||||||
|
if (itr != indexByTime.end()) {
|
||||||
|
Shared::Message* msg = *itr;
|
||||||
|
|
||||||
|
return createIndex(row, column, msg);
|
||||||
|
} else {
|
||||||
|
return QModelIndex();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QHash<int, QByteArray> Models::MessageFeed::roleNames() const
|
||||||
|
{
|
||||||
|
return roles;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Models::MessageFeed::sentByMe(const Shared::Message& msg) const
|
||||||
|
{
|
||||||
|
if (rosterItem->isRoom()) {
|
||||||
|
const Room* room = static_cast<const Room*>(rosterItem);
|
||||||
|
return room->getNick().toLower() == msg.getFromResource().toLower();
|
||||||
|
} else {
|
||||||
|
return msg.getOutgoing();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Models::Attachment Models::MessageFeed::fillAttach(const Shared::Message& msg) const
|
||||||
|
{
|
||||||
|
::Models::Attachment att;
|
||||||
|
|
||||||
|
att.localPath = msg.getAttachPath();
|
||||||
|
att.remotePath = msg.getOutOfBandUrl();
|
||||||
|
|
||||||
|
if (att.remotePath.size() == 0) {
|
||||||
|
if (att.localPath.size() == 0) {
|
||||||
|
att.state = none;
|
||||||
|
} else {
|
||||||
|
Progress::const_iterator itr = uploads.find(msg.getId());
|
||||||
|
if (itr == uploads.end()) {
|
||||||
|
att.state = local;
|
||||||
|
} else {
|
||||||
|
att.state = uploading;
|
||||||
|
att.progress = itr->second;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (att.localPath.size() == 0) {
|
||||||
|
Progress::const_iterator itr = downloads.find(msg.getId());
|
||||||
|
if (itr == downloads.end()) {
|
||||||
|
att.state = remote;
|
||||||
|
} else {
|
||||||
|
att.state = downloading;
|
||||||
|
att.progress = itr->second;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
att.state = ready;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return att;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Models::MessageFeed::downloadAttachment(const QString& messageId)
|
||||||
|
{
|
||||||
|
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());
|
||||||
|
emit dataChanged(ind, ind, {MessageRoles::Attach});
|
||||||
|
emit fileDownloadRequest(msg->getOutOfBandUrl());
|
||||||
|
} else {
|
||||||
|
qDebug() << "Attachment download for message with id" << messageId << "is already in progress, skipping";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
qDebug() << "An attempt to download an attachment for the message that doesn't exist. ID:" << messageId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Models::MessageFeed::uploadAttachment(const QString& messageId)
|
||||||
|
{
|
||||||
|
qDebug() << "request to upload attachment of the message" << messageId;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Models::MessageFeed::registerUpload(const QString& messageId)
|
||||||
|
{
|
||||||
|
return uploads.insert(std::make_pair(messageId, 0)).second;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Models::MessageFeed::fileProgress(const QString& messageId, qreal value, bool up)
|
||||||
|
{
|
||||||
|
Progress* pr = 0;
|
||||||
|
if (up) {
|
||||||
|
pr = &uploads;
|
||||||
|
} else {
|
||||||
|
pr = &downloads;
|
||||||
|
}
|
||||||
|
|
||||||
|
Progress::iterator itr = pr->find(messageId);
|
||||||
|
if (itr != pr->end()) {
|
||||||
|
itr->second = value;
|
||||||
|
QModelIndex ind = modelIndexById(messageId);
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Models::MessageFeed::fileComplete(const QString& messageId, bool up)
|
||||||
|
{
|
||||||
|
fileProgress(messageId, 1, up);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Models::MessageFeed::fileError(const QString& messageId, const QString& error, bool up)
|
||||||
|
{
|
||||||
|
//TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
void Models::MessageFeed::incrementObservers()
|
||||||
|
{
|
||||||
|
++observersAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Models::MessageFeed::decrementObservers()
|
||||||
|
{
|
||||||
|
--observersAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
QModelIndex Models::MessageFeed::modelIndexById(const QString& id) const
|
||||||
|
{
|
||||||
|
StorageById::const_iterator itr = indexById.find(id);
|
||||||
|
if (itr != indexById.end()) {
|
||||||
|
Shared::Message* msg = *itr;
|
||||||
|
return modelIndexByTime(id, msg->getTime());
|
||||||
|
}
|
||||||
|
|
||||||
|
return QModelIndex();
|
||||||
|
}
|
||||||
|
|
||||||
|
QModelIndex Models::MessageFeed::modelIndexByTime(const QString& id, const QDateTime& time) const
|
||||||
|
{
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Models::MessageFeed::reportLocalPathInvalid(const QString& messageId)
|
||||||
|
{
|
||||||
|
StorageById::iterator itr = indexById.find(messageId);
|
||||||
|
if (itr == indexById.end()) {
|
||||||
|
qDebug() << "received a command to change a message, but the message couldn't be found, skipping";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Shared::Message* msg = *itr;
|
||||||
|
|
||||||
|
emit localPathInvalid(msg->getAttachPath());
|
||||||
|
|
||||||
|
//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("");
|
||||||
|
|
||||||
|
emit dataChanged(index, index, {MessageRoles::Attach});
|
||||||
|
}
|
||||||
|
|
||||||
|
Models::MessageFeed::SyncState Models::MessageFeed::getSyncState() const
|
||||||
|
{
|
||||||
|
return syncState;
|
||||||
|
}
|
195
ui/models/messagefeed.h
Normal file
@ -0,0 +1,195 @@
|
|||||||
|
/*
|
||||||
|
* 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 MESSAGEFEED_H
|
||||||
|
#define MESSAGEFEED_H
|
||||||
|
|
||||||
|
#include <QAbstractListModel>
|
||||||
|
#include <QDateTime>
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
#include <set>
|
||||||
|
|
||||||
|
#include <boost/multi_index_container.hpp>
|
||||||
|
#include <boost/multi_index/ordered_index.hpp>
|
||||||
|
#include <boost/multi_index/ranked_index.hpp>
|
||||||
|
#include <boost/multi_index/mem_fun.hpp>
|
||||||
|
|
||||||
|
#include <shared/message.h>
|
||||||
|
#include <shared/icons.h>
|
||||||
|
|
||||||
|
|
||||||
|
namespace Models {
|
||||||
|
class Element;
|
||||||
|
struct Attachment;
|
||||||
|
|
||||||
|
class MessageFeed : public QAbstractListModel
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
enum SyncState {
|
||||||
|
incomplete,
|
||||||
|
syncing,
|
||||||
|
complete
|
||||||
|
};
|
||||||
|
|
||||||
|
MessageFeed(const Element* rosterItem, QObject *parent = nullptr);
|
||||||
|
~MessageFeed();
|
||||||
|
|
||||||
|
void addMessage(const Shared::Message& msg);
|
||||||
|
void changeMessage(const QString& id, const QMap<QString, QVariant>& data);
|
||||||
|
void removeMessage(const QString& id);
|
||||||
|
|
||||||
|
QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const override;
|
||||||
|
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
|
||||||
|
|
||||||
|
bool canFetchMore(const QModelIndex & parent) const override;
|
||||||
|
void fetchMore(const QModelIndex & parent) override;
|
||||||
|
QHash<int, QByteArray> roleNames() const override;
|
||||||
|
QModelIndex index(int row, int column, const QModelIndex & parent) const override;
|
||||||
|
|
||||||
|
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;
|
||||||
|
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);
|
||||||
|
|
||||||
|
void incrementObservers();
|
||||||
|
void decrementObservers();
|
||||||
|
SyncState getSyncState() const;
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void requestArchive(const QString& before);
|
||||||
|
void requestStateChange(bool requesting);
|
||||||
|
void fileDownloadRequest(const QString& url);
|
||||||
|
void unreadMessagesCountChanged();
|
||||||
|
void newMessage(const Shared::Message& msg);
|
||||||
|
void unnoticedMessage(const Shared::Message& msg);
|
||||||
|
void localPathInvalid(const QString& path);
|
||||||
|
void syncStateChange(SyncState state);
|
||||||
|
|
||||||
|
public:
|
||||||
|
enum MessageRoles {
|
||||||
|
Text = Qt::UserRole + 1,
|
||||||
|
Sender,
|
||||||
|
Date,
|
||||||
|
DeliveryState,
|
||||||
|
Correction,
|
||||||
|
SentByMe,
|
||||||
|
Avatar,
|
||||||
|
Attach,
|
||||||
|
Id,
|
||||||
|
Error,
|
||||||
|
Bulk
|
||||||
|
};
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool sentByMe(const Shared::Message& msg) const;
|
||||||
|
Attachment fillAttach(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:
|
||||||
|
//tags
|
||||||
|
struct id {};
|
||||||
|
struct time {};
|
||||||
|
|
||||||
|
typedef boost::multi_index_container<
|
||||||
|
Shared::Message*,
|
||||||
|
boost::multi_index::indexed_by<
|
||||||
|
boost::multi_index::ordered_unique<
|
||||||
|
boost::multi_index::tag<id>,
|
||||||
|
boost::multi_index::const_mem_fun<
|
||||||
|
Shared::Message,
|
||||||
|
QString,
|
||||||
|
&Shared::Message::getId
|
||||||
|
>
|
||||||
|
>,
|
||||||
|
boost::multi_index::ranked_non_unique<
|
||||||
|
boost::multi_index::tag<time>,
|
||||||
|
boost::multi_index::const_mem_fun<
|
||||||
|
Shared::Message,
|
||||||
|
QDateTime,
|
||||||
|
&Shared::Message::getTime
|
||||||
|
>,
|
||||||
|
std::greater<QDateTime>
|
||||||
|
>
|
||||||
|
>
|
||||||
|
> Storage;
|
||||||
|
|
||||||
|
typedef Storage::index<id>::type StorageById;
|
||||||
|
typedef Storage::index<time>::type StorageByTime;
|
||||||
|
Storage storage;
|
||||||
|
StorageById& indexById;
|
||||||
|
StorageByTime& indexByTime;
|
||||||
|
|
||||||
|
const Element* rosterItem;
|
||||||
|
SyncState syncState;
|
||||||
|
|
||||||
|
typedef std::map<QString, qreal> Progress;
|
||||||
|
Progress uploads;
|
||||||
|
Progress downloads;
|
||||||
|
|
||||||
|
std::set<QString>* unreadMessages;
|
||||||
|
uint16_t observersAmount;
|
||||||
|
|
||||||
|
static const QHash<int, QByteArray> roles;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum AttachmentType {
|
||||||
|
none,
|
||||||
|
remote,
|
||||||
|
local,
|
||||||
|
downloading,
|
||||||
|
uploading,
|
||||||
|
errorDownload,
|
||||||
|
errorUpload,
|
||||||
|
ready
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Attachment {
|
||||||
|
AttachmentType state;
|
||||||
|
qreal progress;
|
||||||
|
QString localPath;
|
||||||
|
QString remotePath;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct FeedItem {
|
||||||
|
QString id;
|
||||||
|
QString text;
|
||||||
|
QString sender;
|
||||||
|
QString avatar;
|
||||||
|
QString error;
|
||||||
|
bool sentByMe;
|
||||||
|
bool correction;
|
||||||
|
QDateTime date;
|
||||||
|
Shared::Message::State state;
|
||||||
|
Attachment attach;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Q_DECLARE_METATYPE(Models::Attachment)
|
||||||
|
Q_DECLARE_METATYPE(Models::FeedItem)
|
||||||
|
|
||||||
|
#endif // MESSAGEFEED_H
|
@ -20,82 +20,15 @@
|
|||||||
#include "shared/icons.h"
|
#include "shared/icons.h"
|
||||||
|
|
||||||
Models::Presence::Presence(const QMap<QString, QVariant>& data, Item* parentItem):
|
Models::Presence::Presence(const QMap<QString, QVariant>& data, Item* parentItem):
|
||||||
AbstractParticipant(Item::presence, data, parentItem),
|
AbstractParticipant(Item::presence, data, parentItem)
|
||||||
messages()
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
Models::Presence::Presence(const Models::Presence& other):
|
|
||||||
AbstractParticipant(other),
|
|
||||||
messages(other.messages)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Models::Presence::~Presence()
|
Models::Presence::~Presence()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
int Models::Presence::columnCount() const
|
int Models::Presence::columnCount() const
|
||||||
{
|
{
|
||||||
return 5;
|
return 4;
|
||||||
}
|
|
||||||
|
|
||||||
QVariant Models::Presence::data(int column) const
|
|
||||||
{
|
|
||||||
switch (column) {
|
|
||||||
case 4:
|
|
||||||
return getMessagesCount();
|
|
||||||
default:
|
|
||||||
return AbstractParticipant::data(column);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned int Models::Presence::getMessagesCount() const
|
|
||||||
{
|
|
||||||
return messages.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Models::Presence::addMessage(const Shared::Message& data)
|
|
||||||
{
|
|
||||||
messages.emplace_back(data);
|
|
||||||
changed(4);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Models::Presence::changeMessage(const QString& id, const QMap<QString, QVariant>& data)
|
|
||||||
{
|
|
||||||
bool found = false;
|
|
||||||
for (Shared::Message& msg : messages) {
|
|
||||||
if (msg.getId() == id) {
|
|
||||||
msg.change(data);
|
|
||||||
found = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return found;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Models::Presence::dropMessages()
|
|
||||||
{
|
|
||||||
if (messages.size() > 0) {
|
|
||||||
messages.clear();
|
|
||||||
changed(4);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QIcon Models::Presence::getStatusIcon(bool big) const
|
|
||||||
{
|
|
||||||
if (getMessagesCount() > 0) {
|
|
||||||
return Shared::icon("mail-message", big);
|
|
||||||
} else {
|
|
||||||
return AbstractParticipant::getStatusIcon();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Models::Presence::getMessages(Models::Presence::Messages& container) const
|
|
||||||
{
|
|
||||||
for (Messages::const_iterator itr = messages.begin(), end = messages.end(); itr != end; ++itr) {
|
|
||||||
const Shared::Message& msg = *itr;
|
|
||||||
container.push_back(msg);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -32,25 +32,10 @@ class Presence : public Models::AbstractParticipant
|
|||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
typedef std::deque<Shared::Message> Messages;
|
|
||||||
explicit Presence(const QMap<QString, QVariant> &data, Item *parentItem = 0);
|
explicit Presence(const QMap<QString, QVariant> &data, Item *parentItem = 0);
|
||||||
Presence(const Presence& other);
|
|
||||||
~Presence();
|
~Presence();
|
||||||
|
|
||||||
int columnCount() const override;
|
int columnCount() const override;
|
||||||
QVariant data(int column) const override;
|
|
||||||
|
|
||||||
QIcon getStatusIcon(bool big = false) const override;
|
|
||||||
|
|
||||||
unsigned int getMessagesCount() const;
|
|
||||||
void dropMessages();
|
|
||||||
void addMessage(const Shared::Message& data);
|
|
||||||
bool changeMessage(const QString& id, const QMap<QString, QVariant>& data);
|
|
||||||
|
|
||||||
void getMessages(Messages& container) const;
|
|
||||||
|
|
||||||
private:
|
|
||||||
Messages messages;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -22,16 +22,12 @@
|
|||||||
#include <QIcon>
|
#include <QIcon>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
|
|
||||||
Models::Room::Room(const QString& p_jid, const QMap<QString, QVariant>& data, Models::Item* parentItem):
|
Models::Room::Room(const Account* acc, const QString& p_jid, const QMap<QString, QVariant>& data, Models::Item* parentItem):
|
||||||
Item(room, data, parentItem),
|
Element(room, acc, p_jid, data, parentItem),
|
||||||
autoJoin(false),
|
autoJoin(false),
|
||||||
joined(false),
|
joined(false),
|
||||||
jid(p_jid),
|
|
||||||
nick(""),
|
nick(""),
|
||||||
subject(""),
|
subject(""),
|
||||||
avatarState(Shared::Avatar::empty),
|
|
||||||
avatarPath(""),
|
|
||||||
messages(),
|
|
||||||
participants(),
|
participants(),
|
||||||
exParticipantAvatars()
|
exParticipantAvatars()
|
||||||
{
|
{
|
||||||
@ -55,16 +51,6 @@ Models::Room::Room(const QString& p_jid, const QMap<QString, QVariant>& data, Mo
|
|||||||
setSubject(itr.value().toString());
|
setSubject(itr.value().toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
itr = data.find("avatarState");
|
|
||||||
if (itr != data.end()) {
|
|
||||||
setAvatarState(itr.value().toUInt());
|
|
||||||
}
|
|
||||||
itr = data.find("avatarPath");
|
|
||||||
if (itr != data.end()) {
|
|
||||||
setAvatarPath(itr.value().toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
itr = data.find("avatars");
|
itr = data.find("avatars");
|
||||||
if (itr != data.end()) {
|
if (itr != data.end()) {
|
||||||
QMap<QString, QVariant> avs = itr.value().toMap();
|
QMap<QString, QVariant> avs = itr.value().toMap();
|
||||||
@ -78,21 +64,11 @@ Models::Room::~Room()
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned int Models::Room::getUnreadMessagesCount() const
|
|
||||||
{
|
|
||||||
return messages.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
int Models::Room::columnCount() const
|
int Models::Room::columnCount() const
|
||||||
{
|
{
|
||||||
return 7;
|
return 7;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString Models::Room::getJid() const
|
|
||||||
{
|
|
||||||
return jid;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Models::Room::getAutoJoin() const
|
bool Models::Room::getAutoJoin() const
|
||||||
{
|
{
|
||||||
return autoJoin;
|
return autoJoin;
|
||||||
@ -151,14 +127,6 @@ void Models::Room::setAutoJoin(bool p_autoJoin)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Models::Room::setJid(const QString& p_jid)
|
|
||||||
{
|
|
||||||
if (jid != p_jid) {
|
|
||||||
jid = p_jid;
|
|
||||||
changed(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Models::Room::setJoined(bool p_joined)
|
void Models::Room::setJoined(bool p_joined)
|
||||||
{
|
{
|
||||||
if (joined != p_joined) {
|
if (joined != p_joined) {
|
||||||
@ -182,8 +150,6 @@ void Models::Room::update(const QString& field, const QVariant& value)
|
|||||||
{
|
{
|
||||||
if (field == "name") {
|
if (field == "name") {
|
||||||
setName(value.toString());
|
setName(value.toString());
|
||||||
} else if (field == "jid") {
|
|
||||||
setJid(value.toString());
|
|
||||||
} else if (field == "joined") {
|
} else if (field == "joined") {
|
||||||
setJoined(value.toBool());
|
setJoined(value.toBool());
|
||||||
} else if (field == "autoJoin") {
|
} else if (field == "autoJoin") {
|
||||||
@ -192,16 +158,14 @@ void Models::Room::update(const QString& field, const QVariant& value)
|
|||||||
setNick(value.toString());
|
setNick(value.toString());
|
||||||
} else if (field == "subject") {
|
} else if (field == "subject") {
|
||||||
setSubject(value.toString());
|
setSubject(value.toString());
|
||||||
} else if (field == "avatarState") {
|
} else {
|
||||||
setAvatarState(value.toUInt());
|
Element::update(field, value);
|
||||||
} else if (field == "avatarPath") {
|
|
||||||
setAvatarPath(value.toString());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QIcon Models::Room::getStatusIcon(bool big) const
|
QIcon Models::Room::getStatusIcon(bool big) const
|
||||||
{
|
{
|
||||||
if (messages.size() > 0) {
|
if (getMessagesCount() > 0) {
|
||||||
return Shared::icon("mail-message", big);
|
return Shared::icon("mail-message", big);
|
||||||
} else {
|
} else {
|
||||||
if (autoJoin) {
|
if (autoJoin) {
|
||||||
@ -237,42 +201,6 @@ QString Models::Room::getStatusText() const
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned int Models::Room::getMessagesCount() const
|
|
||||||
{
|
|
||||||
return messages.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Models::Room::addMessage(const Shared::Message& data)
|
|
||||||
{
|
|
||||||
messages.emplace_back(data);
|
|
||||||
changed(5);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Models::Room::changeMessage(const QString& id, const QMap<QString, QVariant>& data)
|
|
||||||
{
|
|
||||||
for (Shared::Message& msg : messages) {
|
|
||||||
if (msg.getId() == id) {
|
|
||||||
msg.change(data);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Models::Room::dropMessages()
|
|
||||||
{
|
|
||||||
if (messages.size() > 0) {
|
|
||||||
messages.clear();
|
|
||||||
changed(5);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Models::Room::getMessages(Models::Room::Messages& container) const
|
|
||||||
{
|
|
||||||
for (Messages::const_iterator itr = messages.begin(), end = messages.end(); itr != end; ++itr) {
|
|
||||||
const Shared::Message& msg = *itr;
|
|
||||||
container.push_back(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Models::Room::toOfflineState()
|
void Models::Room::toOfflineState()
|
||||||
{
|
{
|
||||||
@ -367,47 +295,6 @@ QString Models::Room::getDisplayedName() const
|
|||||||
return getRoomName();
|
return getRoomName();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Models::Room::columnInvolvedInDisplay(int col)
|
|
||||||
{
|
|
||||||
return Item::columnInvolvedInDisplay(col) && col == 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString Models::Room::getAvatarPath() const
|
|
||||||
{
|
|
||||||
return avatarPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
Shared::Avatar Models::Room::getAvatarState() const
|
|
||||||
{
|
|
||||||
return avatarState;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Models::Room::setAvatarPath(const QString& path)
|
|
||||||
{
|
|
||||||
if (avatarPath != path) {
|
|
||||||
avatarPath = path;
|
|
||||||
changed(8);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Models::Room::setAvatarState(Shared::Avatar p_state)
|
|
||||||
{
|
|
||||||
if (avatarState != p_state) {
|
|
||||||
avatarState = p_state;
|
|
||||||
changed(7);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Models::Room::setAvatarState(unsigned int p_state)
|
|
||||||
{
|
|
||||||
if (p_state <= static_cast<quint8>(Shared::Avatar::valid)) {
|
|
||||||
Shared::Avatar state = static_cast<Shared::Avatar>(p_state);
|
|
||||||
setAvatarState(state);
|
|
||||||
} else {
|
|
||||||
qDebug() << "An attempt to set invalid avatar state" << p_state << "to the room" << jid << ", skipping";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::map<QString, const Models::Participant &> Models::Room::getParticipants() const
|
std::map<QString, const Models::Participant &> Models::Room::getParticipants() const
|
||||||
{
|
{
|
||||||
std::map<QString, const Models::Participant&> result;
|
std::map<QString, const Models::Participant&> result;
|
||||||
@ -423,8 +310,13 @@ QString Models::Room::getParticipantIconPath(const QString& name) const
|
|||||||
{
|
{
|
||||||
std::map<QString, Models::Participant*>::const_iterator itr = participants.find(name);
|
std::map<QString, Models::Participant*>::const_iterator itr = participants.find(name);
|
||||||
if (itr == participants.end()) {
|
if (itr == participants.end()) {
|
||||||
|
std::map<QString, QString>::const_iterator eitr = exParticipantAvatars.find(name);
|
||||||
|
if (eitr != exParticipantAvatars.end()) {
|
||||||
|
return eitr->second;
|
||||||
|
} else {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return itr->second->getAvatarPath();
|
return itr->second->getAvatarPath();
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
#ifndef MODELS_ROOM_H
|
#ifndef MODELS_ROOM_H
|
||||||
#define MODELS_ROOM_H
|
#define MODELS_ROOM_H
|
||||||
|
|
||||||
#include "item.h"
|
#include "element.h"
|
||||||
#include "participant.h"
|
#include "participant.h"
|
||||||
#include "shared/enums.h"
|
#include "shared/enums.h"
|
||||||
#include "shared/message.h"
|
#include "shared/message.h"
|
||||||
@ -29,21 +29,18 @@ namespace Models {
|
|||||||
/**
|
/**
|
||||||
* @todo write docs
|
* @todo write docs
|
||||||
*/
|
*/
|
||||||
class Room : public Models::Item
|
class Room : public Element
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
typedef std::deque<Shared::Message> Messages;
|
Room(const Account* acc, const QString& p_jid, const QMap<QString, QVariant> &data, Item *parentItem = 0);
|
||||||
Room(const QString& p_jid, const QMap<QString, QVariant> &data, Item *parentItem = 0);
|
|
||||||
~Room();
|
~Room();
|
||||||
|
|
||||||
int columnCount() const override;
|
int columnCount() const override;
|
||||||
QVariant data(int column) const override;
|
QVariant data(int column) const override;
|
||||||
|
|
||||||
unsigned int getUnreadMessagesCount() const;
|
|
||||||
bool getJoined() const;
|
bool getJoined() const;
|
||||||
bool getAutoJoin() const;
|
bool getAutoJoin() const;
|
||||||
QString getJid() const;
|
|
||||||
QString getNick() const;
|
QString getNick() const;
|
||||||
QString getRoomName() const;
|
QString getRoomName() const;
|
||||||
QString getSubject() const;
|
QString getSubject() const;
|
||||||
@ -53,17 +50,10 @@ public:
|
|||||||
|
|
||||||
void setJoined(bool p_joined);
|
void setJoined(bool p_joined);
|
||||||
void setAutoJoin(bool p_autoJoin);
|
void setAutoJoin(bool p_autoJoin);
|
||||||
void setJid(const QString& p_jid);
|
|
||||||
void setNick(const QString& p_nick);
|
void setNick(const QString& p_nick);
|
||||||
void setSubject(const QString& sub);
|
void setSubject(const QString& sub);
|
||||||
|
|
||||||
void update(const QString& field, const QVariant& value);
|
void update(const QString& field, const QVariant& value) override;
|
||||||
|
|
||||||
void addMessage(const Shared::Message& data);
|
|
||||||
void changeMessage(const QString& id, const QMap<QString, QVariant>& data);
|
|
||||||
unsigned int getMessagesCount() const;
|
|
||||||
void dropMessages();
|
|
||||||
void getMessages(Messages& container) const;
|
|
||||||
|
|
||||||
void addParticipant(const QString& name, const QMap<QString, QVariant>& data);
|
void addParticipant(const QString& name, const QMap<QString, QVariant>& data);
|
||||||
void changeParticipant(const QString& name, const QMap<QString, QVariant>& data);
|
void changeParticipant(const QString& name, const QMap<QString, QVariant>& data);
|
||||||
@ -71,8 +61,6 @@ public:
|
|||||||
|
|
||||||
void toOfflineState() override;
|
void toOfflineState() override;
|
||||||
QString getDisplayedName() const override;
|
QString getDisplayedName() const override;
|
||||||
Shared::Avatar getAvatarState() const;
|
|
||||||
QString getAvatarPath() const;
|
|
||||||
std::map<QString, const Participant&> getParticipants() const;
|
std::map<QString, const Participant&> getParticipants() const;
|
||||||
QString getParticipantIconPath(const QString& name) const;
|
QString getParticipantIconPath(const QString& name) const;
|
||||||
std::map<QString, QString> getExParticipantAvatars() const;
|
std::map<QString, QString> getExParticipantAvatars() const;
|
||||||
@ -84,24 +72,14 @@ signals:
|
|||||||
private:
|
private:
|
||||||
void handleParticipantUpdate(std::map<QString, Participant*>::const_iterator itr, const QMap<QString, QVariant>& data);
|
void handleParticipantUpdate(std::map<QString, Participant*>::const_iterator itr, const QMap<QString, QVariant>& data);
|
||||||
|
|
||||||
protected:
|
|
||||||
bool columnInvolvedInDisplay(int col) override;
|
|
||||||
void setAvatarState(Shared::Avatar p_state);
|
|
||||||
void setAvatarState(unsigned int p_state);
|
|
||||||
void setAvatarPath(const QString& path);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool autoJoin;
|
bool autoJoin;
|
||||||
bool joined;
|
bool joined;
|
||||||
QString jid;
|
QString jid;
|
||||||
QString nick;
|
QString nick;
|
||||||
QString subject;
|
QString subject;
|
||||||
Shared::Avatar avatarState;
|
|
||||||
QString avatarPath;
|
|
||||||
Messages messages;
|
|
||||||
std::map<QString, Participant*> participants;
|
std::map<QString, Participant*> participants;
|
||||||
std::map<QString, QString> exParticipantAvatars;
|
std::map<QString, QString> exParticipantAvatars;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -215,11 +215,7 @@ QVariant Models::Roster::data (const QModelIndex& index, int role) const
|
|||||||
break;
|
break;
|
||||||
case Item::presence: {
|
case Item::presence: {
|
||||||
Presence* contact = static_cast<Presence*>(item);
|
Presence* contact = static_cast<Presence*>(item);
|
||||||
QString str("");
|
QString str;
|
||||||
int mc = contact->getMessagesCount();
|
|
||||||
if (mc > 0) {
|
|
||||||
str += tr("New messages: ") + std::to_string(mc).c_str() + "\n";
|
|
||||||
}
|
|
||||||
Shared::Availability av = contact->getAvailability();
|
Shared::Availability av = contact->getAvailability();
|
||||||
str += tr("Availability: ") + Shared::Global::getName(av);
|
str += tr("Availability: ") + Shared::Global::getName(av);
|
||||||
QString s = contact->getStatus();
|
QString s = contact->getStatus();
|
||||||
@ -232,7 +228,7 @@ QVariant Models::Roster::data (const QModelIndex& index, int role) const
|
|||||||
break;
|
break;
|
||||||
case Item::participant: {
|
case Item::participant: {
|
||||||
Participant* p = static_cast<Participant*>(item);
|
Participant* p = static_cast<Participant*>(item);
|
||||||
QString str("");
|
QString str;
|
||||||
Shared::Availability av = p->getAvailability();
|
Shared::Availability av = p->getAvailability();
|
||||||
str += tr("Availability: ") + Shared::Global::getName(av) + "\n";
|
str += tr("Availability: ") + Shared::Global::getName(av) + "\n";
|
||||||
QString s = p->getStatus();
|
QString s = p->getStatus();
|
||||||
@ -260,7 +256,7 @@ QVariant Models::Roster::data (const QModelIndex& index, int role) const
|
|||||||
break;
|
break;
|
||||||
case Item::room: {
|
case Item::room: {
|
||||||
Room* rm = static_cast<Room*>(item);
|
Room* rm = static_cast<Room*>(item);
|
||||||
unsigned int count = rm->getUnreadMessagesCount();
|
unsigned int count = rm->getMessagesCount();
|
||||||
QString str("");
|
QString str("");
|
||||||
if (count > 0) {
|
if (count > 0) {
|
||||||
str += tr("New messages: ") + std::to_string(count).c_str() + "\n";
|
str += tr("New messages: ") + std::to_string(count).c_str() + "\n";
|
||||||
@ -450,6 +446,10 @@ void Models::Roster::addContact(const QString& account, const QString& jid, cons
|
|||||||
std::map<ElId, Contact*>::iterator itr = contacts.find(id);
|
std::map<ElId, Contact*>::iterator itr = contacts.find(id);
|
||||||
if (itr == contacts.end()) {
|
if (itr == contacts.end()) {
|
||||||
contact = new Contact(acc, jid, data);
|
contact = new Contact(acc, jid, data);
|
||||||
|
connect(contact, &Contact::requestArchive, this, &Roster::onElementRequestArchive);
|
||||||
|
connect(contact, &Contact::fileDownloadRequest, this, &Roster::fileDownloadRequest);
|
||||||
|
connect(contact, &Contact::unnoticedMessage, this, &Roster::unnoticedMessage);
|
||||||
|
connect(contact, &Contact::localPathInvalid, this, &Roster::localPathInvalid);
|
||||||
contacts.insert(std::make_pair(id, contact));
|
contacts.insert(std::make_pair(id, contact));
|
||||||
} else {
|
} else {
|
||||||
contact = itr->second;
|
contact = itr->second;
|
||||||
@ -535,35 +535,19 @@ void Models::Roster::removeGroup(const QString& account, const QString& name)
|
|||||||
|
|
||||||
void Models::Roster::changeContact(const QString& account, const QString& jid, const QMap<QString, QVariant>& data)
|
void Models::Roster::changeContact(const QString& account, const QString& jid, const QMap<QString, QVariant>& data)
|
||||||
{
|
{
|
||||||
ElId id(account, jid);
|
Element* el = getElement({account, jid});
|
||||||
std::map<ElId, Contact*>::iterator cItr = contacts.find(id);
|
if (el != NULL) {
|
||||||
|
|
||||||
if (cItr != contacts.end()) {
|
|
||||||
for (QMap<QString, QVariant>::const_iterator itr = data.begin(), end = data.end(); itr != end; ++itr) {
|
for (QMap<QString, QVariant>::const_iterator itr = data.begin(), end = data.end(); itr != end; ++itr) {
|
||||||
cItr->second->update(itr.key(), itr.value());
|
el->update(itr.key(), itr.value());
|
||||||
}
|
|
||||||
} else {
|
|
||||||
std::map<ElId, Room*>::iterator rItr = rooms.find(id);
|
|
||||||
if (rItr != rooms.end()) {
|
|
||||||
for (QMap<QString, QVariant>::const_iterator itr = data.begin(), end = data.end(); itr != end; ++itr) {
|
|
||||||
rItr->second->update(itr.key(), itr.value());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Models::Roster::changeMessage(const QString& account, const QString& jid, const QString& id, const QMap<QString, QVariant>& data)
|
void Models::Roster::changeMessage(const QString& account, const QString& jid, const QString& id, const QMap<QString, QVariant>& data)
|
||||||
{
|
{
|
||||||
ElId elid(account, jid);
|
Element* el = getElement({account, jid});
|
||||||
std::map<ElId, Contact*>::iterator cItr = contacts.find(elid);
|
if (el != NULL) {
|
||||||
|
el->changeMessage(id, data);
|
||||||
if (cItr != contacts.end()) {
|
|
||||||
cItr->second->changeMessage(id, data);
|
|
||||||
} else {
|
|
||||||
std::map<ElId, Room*>::iterator rItr = rooms.find(elid);
|
|
||||||
if (rItr != rooms.end()) {
|
|
||||||
rItr->second->changeMessage(id, data);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -627,7 +611,6 @@ void Models::Roster::removeContact(const QString& account, const QString& jid, c
|
|||||||
} else {
|
} else {
|
||||||
delete ref;
|
delete ref;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (gr->childCount() == 0) {
|
if (gr->childCount() == 0) {
|
||||||
removeGroup(account, group);
|
removeGroup(account, group);
|
||||||
}
|
}
|
||||||
@ -708,29 +691,9 @@ void Models::Roster::removePresence(const QString& account, const QString& jid,
|
|||||||
|
|
||||||
void Models::Roster::addMessage(const QString& account, const Shared::Message& data)
|
void Models::Roster::addMessage(const QString& account, const Shared::Message& data)
|
||||||
{
|
{
|
||||||
ElId id(account, data.getPenPalJid());
|
Element* el = getElement({account, data.getPenPalJid()});
|
||||||
std::map<ElId, Contact*>::iterator itr = contacts.find(id);
|
if (el != NULL) {
|
||||||
if (itr != contacts.end()) {
|
el->addMessage(data);
|
||||||
itr->second->addMessage(data);
|
|
||||||
} else {
|
|
||||||
std::map<ElId, Room*>::const_iterator rItr = rooms.find(id);
|
|
||||||
if (rItr != rooms.end()) {
|
|
||||||
rItr->second->addMessage(data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Models::Roster::dropMessages(const QString& account, const QString& jid)
|
|
||||||
{
|
|
||||||
ElId id(account, jid);
|
|
||||||
std::map<ElId, Contact*>::iterator itr = contacts.find(id);
|
|
||||||
if (itr != contacts.end()) {
|
|
||||||
itr->second->dropMessages();
|
|
||||||
} else {
|
|
||||||
std::map<ElId, Room*>::const_iterator rItr = rooms.find(id);
|
|
||||||
if (rItr != rooms.end()) {
|
|
||||||
rItr->second->dropMessages();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -821,7 +784,11 @@ void Models::Roster::addRoom(const QString& account, const QString jid, const QM
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Room* room = new Room(jid, data);
|
Room* room = new Room(acc, jid, data);
|
||||||
|
connect(room, &Contact::requestArchive, this, &Roster::onElementRequestArchive);
|
||||||
|
connect(room, &Contact::fileDownloadRequest, this, &Roster::fileDownloadRequest);
|
||||||
|
connect(room, &Contact::unnoticedMessage, this, &Roster::unnoticedMessage);
|
||||||
|
connect(room, &Contact::localPathInvalid, this, &Roster::localPathInvalid);
|
||||||
rooms.insert(std::make_pair(id, room));
|
rooms.insert(std::make_pair(id, room));
|
||||||
acc->appendChild(room);
|
acc->appendChild(room);
|
||||||
}
|
}
|
||||||
@ -974,3 +941,65 @@ QModelIndex Models::Roster::getGroupIndex(const QString& account, const QString&
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Models::Roster::onElementRequestArchive(const QString& before)
|
||||||
|
{
|
||||||
|
Element* el = static_cast<Element*>(sender());
|
||||||
|
emit requestArchive(el->getAccountName(), el->getJid(), before);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Models::Roster::responseArchive(const QString& account, const QString& jid, const std::list<Shared::Message>& list, bool last)
|
||||||
|
{
|
||||||
|
ElId id(account, jid);
|
||||||
|
Element* el = getElement(id);
|
||||||
|
if (el != NULL) {
|
||||||
|
el->responseArchive(list, last);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Models::Roster::fileProgress(const std::list<Shared::MessageInfo>& msgs, qreal value, bool up)
|
||||||
|
{
|
||||||
|
for (const Shared::MessageInfo& info : msgs) {
|
||||||
|
Element* el = getElement({info.account, info.jid});
|
||||||
|
if (el != NULL) {
|
||||||
|
el->fileProgress(info.messageId, value, up);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Models::Roster::fileComplete(const std::list<Shared::MessageInfo>& msgs, bool up)
|
||||||
|
{
|
||||||
|
for (const Shared::MessageInfo& info : msgs) {
|
||||||
|
Element* el = getElement({info.account, info.jid});
|
||||||
|
if (el != NULL) {
|
||||||
|
el->fileComplete(info.messageId, up);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Models::Roster::fileError(const std::list<Shared::MessageInfo>& msgs, const QString& err, bool up)
|
||||||
|
{
|
||||||
|
for (const Shared::MessageInfo& info : msgs) {
|
||||||
|
Element* el = getElement({info.account, info.jid});
|
||||||
|
if (el != NULL) {
|
||||||
|
el->fileError(info.messageId, err, up);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Models::Element * Models::Roster::getElement(const Models::Roster::ElId& id)
|
||||||
|
{
|
||||||
|
std::map<ElId, Contact*>::iterator cItr = contacts.find(id);
|
||||||
|
|
||||||
|
if (cItr != contacts.end()) {
|
||||||
|
return cItr->second;
|
||||||
|
} else {
|
||||||
|
std::map<ElId, Room*>::iterator rItr = rooms.find(id);
|
||||||
|
if (rItr != rooms.end()) {
|
||||||
|
return rItr->second;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -26,6 +26,7 @@
|
|||||||
|
|
||||||
#include "shared/message.h"
|
#include "shared/message.h"
|
||||||
#include "shared/global.h"
|
#include "shared/global.h"
|
||||||
|
#include "shared/messageinfo.h"
|
||||||
#include "accounts.h"
|
#include "accounts.h"
|
||||||
#include "item.h"
|
#include "item.h"
|
||||||
#include "account.h"
|
#include "account.h"
|
||||||
@ -58,7 +59,6 @@ public:
|
|||||||
void removePresence(const QString& account, const QString& jid, const QString& name);
|
void removePresence(const QString& account, const QString& jid, const QString& name);
|
||||||
void addMessage(const QString& account, const Shared::Message& data);
|
void addMessage(const QString& account, const Shared::Message& data);
|
||||||
void changeMessage(const QString& account, const QString& jid, const QString& id, const QMap<QString, QVariant>& data);
|
void changeMessage(const QString& account, const QString& jid, const QString& id, const QMap<QString, QVariant>& data);
|
||||||
void dropMessages(const QString& account, const QString& jid);
|
|
||||||
void addRoom(const QString& account, const QString jid, const QMap<QString, QVariant>& data);
|
void 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 changeRoom(const QString& account, const QString jid, const QMap<QString, QVariant>& data);
|
||||||
void removeRoom(const QString& account, const QString jid);
|
void removeRoom(const QString& account, const QString jid);
|
||||||
@ -81,15 +81,22 @@ public:
|
|||||||
Account* getAccount(const QString& name);
|
Account* getAccount(const QString& name);
|
||||||
QModelIndex getAccountIndex(const QString& name);
|
QModelIndex getAccountIndex(const QString& name);
|
||||||
QModelIndex getGroupIndex(const QString& account, const QString& name);
|
QModelIndex getGroupIndex(const QString& account, const QString& name);
|
||||||
|
void responseArchive(const QString& account, const QString& jid, const std::list<Shared::Message>& list, bool last);
|
||||||
|
|
||||||
|
void fileProgress(const std::list<Shared::MessageInfo>& msgs, qreal value, bool up);
|
||||||
|
void fileError(const std::list<Shared::MessageInfo>& msgs, const QString& err, bool up);
|
||||||
|
void fileComplete(const std::list<Shared::MessageInfo>& msgs, bool up);
|
||||||
|
|
||||||
Accounts* accountsModel;
|
Accounts* accountsModel;
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void requestArchive(const QString& account, const QString& jid, const QString& before);
|
||||||
|
void fileDownloadRequest(const QString& url);
|
||||||
|
void unnoticedMessage(const QString& account, const Shared::Message& msg);
|
||||||
|
void localPathInvalid(const QString& path);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Item* root;
|
Element* getElement(const ElId& id);
|
||||||
std::map<QString, Account*> accounts;
|
|
||||||
std::map<ElId, Group*> groups;
|
|
||||||
std::map<ElId, Contact*> contacts;
|
|
||||||
std::map<ElId, Room*> rooms;
|
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void onAccountDataChanged(const QModelIndex& tl, const QModelIndex& br, const QVector<int>& roles);
|
void onAccountDataChanged(const QModelIndex& tl, const QModelIndex& br, const QVector<int>& roles);
|
||||||
@ -100,6 +107,14 @@ private slots:
|
|||||||
void onChildRemoved();
|
void onChildRemoved();
|
||||||
void onChildIsAboutToBeMoved(Item* source, int first, int last, Item* destination, int newIndex);
|
void onChildIsAboutToBeMoved(Item* source, int first, int last, Item* destination, int newIndex);
|
||||||
void onChildMoved();
|
void onChildMoved();
|
||||||
|
void onElementRequestArchive(const QString& before);
|
||||||
|
|
||||||
|
private:
|
||||||
|
Item* root;
|
||||||
|
std::map<QString, Account*> accounts;
|
||||||
|
std::map<ElId, Group*> groups;
|
||||||
|
std::map<ElId, Contact*> contacts;
|
||||||
|
std::map<ElId, Room*> rooms;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
class ElId {
|
class ElId {
|
||||||
|
240
ui/squawk.cpp
@ -29,7 +29,6 @@ Squawk::Squawk(QWidget *parent) :
|
|||||||
conversations(),
|
conversations(),
|
||||||
contextMenu(new QMenu()),
|
contextMenu(new QMenu()),
|
||||||
dbus("org.freedesktop.Notifications", "/org/freedesktop/Notifications", "org.freedesktop.Notifications", QDBusConnection::sessionBus()),
|
dbus("org.freedesktop.Notifications", "/org/freedesktop/Notifications", "org.freedesktop.Notifications", QDBusConnection::sessionBus()),
|
||||||
requestedFiles(),
|
|
||||||
vCards(),
|
vCards(),
|
||||||
requestedAccountsForPasswords(),
|
requestedAccountsForPasswords(),
|
||||||
prompt(0),
|
prompt(0),
|
||||||
@ -60,8 +59,12 @@ Squawk::Squawk(QWidget *parent) :
|
|||||||
connect(m_ui->roster, &QTreeView::customContextMenuRequested, this, &Squawk::onRosterContextMenu);
|
connect(m_ui->roster, &QTreeView::customContextMenuRequested, this, &Squawk::onRosterContextMenu);
|
||||||
connect(m_ui->roster, &QTreeView::collapsed, this, &Squawk::onItemCollepsed);
|
connect(m_ui->roster, &QTreeView::collapsed, this, &Squawk::onItemCollepsed);
|
||||||
connect(m_ui->roster->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &Squawk::onRosterSelectionChanged);
|
connect(m_ui->roster->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &Squawk::onRosterSelectionChanged);
|
||||||
|
connect(&rosterModel, &Models::Roster::unnoticedMessage, this, &Squawk::onUnnoticedMessage);
|
||||||
|
|
||||||
connect(rosterModel.accountsModel, &Models::Accounts::sizeChanged, this, &Squawk::onAccountsSizeChanged);
|
connect(rosterModel.accountsModel, &Models::Accounts::sizeChanged, this, &Squawk::onAccountsSizeChanged);
|
||||||
|
connect(&rosterModel, &Models::Roster::requestArchive, this, &Squawk::onRequestArchive);
|
||||||
|
connect(&rosterModel, &Models::Roster::fileDownloadRequest, this, &Squawk::fileDownloadRequest);
|
||||||
|
connect(&rosterModel, &Models::Roster::localPathInvalid, this, &Squawk::localPathInvalid);
|
||||||
connect(contextMenu, &QMenu::aboutToHide, this, &Squawk::onContextAboutToHide);
|
connect(contextMenu, &QMenu::aboutToHide, this, &Squawk::onContextAboutToHide);
|
||||||
//m_ui->mainToolBar->addWidget(m_ui->comboBox);
|
//m_ui->mainToolBar->addWidget(m_ui->comboBox);
|
||||||
|
|
||||||
@ -336,17 +339,14 @@ void Squawk::onRosterItemDoubleClicked(const QModelIndex& item)
|
|||||||
Models::Account* acc = rosterModel.getAccount(id->account);
|
Models::Account* acc = rosterModel.getAccount(id->account);
|
||||||
Conversation* conv = 0;
|
Conversation* conv = 0;
|
||||||
bool created = false;
|
bool created = false;
|
||||||
Models::Contact::Messages deque;
|
|
||||||
if (itr != conversations.end()) {
|
if (itr != conversations.end()) {
|
||||||
conv = itr->second;
|
conv = itr->second;
|
||||||
} else if (contact != 0) {
|
} else if (contact != 0) {
|
||||||
created = true;
|
created = true;
|
||||||
conv = new Chat(acc, contact);
|
conv = new Chat(acc, contact);
|
||||||
contact->getMessages(deque);
|
|
||||||
} else if (room != 0) {
|
} else if (room != 0) {
|
||||||
created = true;
|
created = true;
|
||||||
conv = new Room(acc, room);
|
conv = new Room(acc, room);
|
||||||
room->getMessages(deque);
|
|
||||||
|
|
||||||
if (!room->getJoined()) {
|
if (!room->getJoined()) {
|
||||||
emit setRoomJoined(id->account, id->name, true);
|
emit setRoomJoined(id->account, id->name, true);
|
||||||
@ -358,12 +358,6 @@ void Squawk::onRosterItemDoubleClicked(const QModelIndex& item)
|
|||||||
conv->setAttribute(Qt::WA_DeleteOnClose);
|
conv->setAttribute(Qt::WA_DeleteOnClose);
|
||||||
subscribeConversation(conv);
|
subscribeConversation(conv);
|
||||||
conversations.insert(std::make_pair(*id, conv));
|
conversations.insert(std::make_pair(*id, conv));
|
||||||
|
|
||||||
if (created) {
|
|
||||||
for (Models::Contact::Messages::const_iterator itr = deque.begin(), end = deque.end(); itr != end; ++itr) {
|
|
||||||
conv->addMessage(*itr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
conv->show();
|
conv->show();
|
||||||
@ -380,12 +374,6 @@ void Squawk::onRosterItemDoubleClicked(const QModelIndex& item)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Squawk::onConversationShown()
|
|
||||||
{
|
|
||||||
Conversation* conv = static_cast<Conversation*>(sender());
|
|
||||||
rosterModel.dropMessages(conv->getAccount(), conv->getJid());
|
|
||||||
}
|
|
||||||
|
|
||||||
void Squawk::onConversationClosed(QObject* parent)
|
void Squawk::onConversationClosed(QObject* parent)
|
||||||
{
|
{
|
||||||
Conversation* conv = static_cast<Conversation*>(sender());
|
Conversation* conv = static_cast<Conversation*>(sender());
|
||||||
@ -402,164 +390,40 @@ void Squawk::onConversationClosed(QObject* parent)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Squawk::onConversationDownloadFile(const QString& messageId, const QString& url)
|
void Squawk::fileProgress(const std::list<Shared::MessageInfo> msgs, qreal value, bool up)
|
||||||
{
|
{
|
||||||
Conversation* conv = static_cast<Conversation*>(sender());
|
rosterModel.fileProgress(msgs, value, up);
|
||||||
std::map<QString, std::set<Models::Roster::ElId>>::iterator itr = requestedFiles.find(messageId);
|
|
||||||
bool created = false;
|
|
||||||
if (itr == requestedFiles.end()) {
|
|
||||||
itr = requestedFiles.insert(std::make_pair(messageId, std::set<Models::Roster::ElId>())).first;
|
|
||||||
created = true;
|
|
||||||
}
|
|
||||||
itr->second.insert(Models::Roster::ElId(conv->getAccount(), conv->getJid()));
|
|
||||||
if (created) {
|
|
||||||
emit downloadFileRequest(messageId, url);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Squawk::fileProgress(const QString& messageId, qreal value)
|
void Squawk::fileDownloadComplete(const std::list<Shared::MessageInfo> msgs, const QString& path)
|
||||||
{
|
{
|
||||||
std::map<QString, std::set<Models::Roster::ElId>>::const_iterator itr = requestedFiles.find(messageId);
|
rosterModel.fileComplete(msgs, false);
|
||||||
if (itr == requestedFiles.end()) {
|
|
||||||
qDebug() << "fileProgress in UI Squawk but there is nobody waiting for that id" << messageId << ", skipping";
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
const std::set<Models::Roster::ElId>& convs = itr->second;
|
|
||||||
for (std::set<Models::Roster::ElId>::const_iterator cItr = convs.begin(), cEnd = convs.end(); cItr != cEnd; ++cItr) {
|
|
||||||
const Models::Roster::ElId& id = *cItr;
|
|
||||||
Conversations::const_iterator c = conversations.find(id);
|
|
||||||
if (c != conversations.end()) {
|
|
||||||
c->second->responseFileProgress(messageId, value);
|
|
||||||
}
|
|
||||||
if (currentConversation != 0 && currentConversation->getId() == id) {
|
|
||||||
currentConversation->responseFileProgress(messageId, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Squawk::fileError(const QString& messageId, const QString& error)
|
void Squawk::fileError(const std::list<Shared::MessageInfo> msgs, const QString& error, bool up)
|
||||||
{
|
{
|
||||||
std::map<QString, std::set<Models::Roster::ElId>>::const_iterator itr = requestedFiles.find(messageId);
|
rosterModel.fileError(msgs, error, up);
|
||||||
if (itr == requestedFiles.end()) {
|
|
||||||
qDebug() << "fileError in UI Squawk but there is nobody waiting for that id" << messageId << ", skipping";
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
const std::set<Models::Roster::ElId>& convs = itr->second;
|
|
||||||
for (std::set<Models::Roster::ElId>::const_iterator cItr = convs.begin(), cEnd = convs.end(); cItr != cEnd; ++cItr) {
|
|
||||||
const Models::Roster::ElId& id = *cItr;
|
|
||||||
Conversations::const_iterator c = conversations.find(id);
|
|
||||||
if (c != conversations.end()) {
|
|
||||||
c->second->fileError(messageId, error);
|
|
||||||
}
|
|
||||||
if (currentConversation != 0 && currentConversation->getId() == id) {
|
|
||||||
currentConversation->fileError(messageId, error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
requestedFiles.erase(itr);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Squawk::fileLocalPathResponse(const QString& messageId, const QString& path)
|
void Squawk::fileUploadComplete(const std::list<Shared::MessageInfo> msgs, const QString& path)
|
||||||
{
|
{
|
||||||
std::map<QString, std::set<Models::Roster::ElId>>::const_iterator itr = requestedFiles.find(messageId);
|
rosterModel.fileComplete(msgs, true);
|
||||||
if (itr == requestedFiles.end()) {
|
|
||||||
qDebug() << "fileLocalPathResponse in UI Squawk but there is nobody waiting for that path, skipping";
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
const std::set<Models::Roster::ElId>& convs = itr->second;
|
|
||||||
for (std::set<Models::Roster::ElId>::const_iterator cItr = convs.begin(), cEnd = convs.end(); cItr != cEnd; ++cItr) {
|
|
||||||
const Models::Roster::ElId& id = *cItr;
|
|
||||||
Conversations::const_iterator c = conversations.find(id);
|
|
||||||
if (c != conversations.end()) {
|
|
||||||
c->second->responseLocalFile(messageId, path);
|
|
||||||
}
|
|
||||||
if (currentConversation != 0 && currentConversation->getId() == id) {
|
|
||||||
currentConversation->responseLocalFile(messageId, path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
requestedFiles.erase(itr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Squawk::onConversationRequestLocalFile(const QString& messageId, const QString& url)
|
|
||||||
{
|
|
||||||
Conversation* conv = static_cast<Conversation*>(sender());
|
|
||||||
std::map<QString, std::set<Models::Roster::ElId>>::iterator itr = requestedFiles.find(messageId);
|
|
||||||
bool created = false;
|
|
||||||
if (itr == requestedFiles.end()) {
|
|
||||||
itr = requestedFiles.insert(std::make_pair(messageId, std::set<Models::Roster::ElId>())).first;
|
|
||||||
created = true;
|
|
||||||
}
|
|
||||||
itr->second.insert(Models::Roster::ElId(conv->getAccount(), conv->getJid()));
|
|
||||||
if (created) {
|
|
||||||
emit fileLocalPathRequest(messageId, url);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Squawk::accountMessage(const QString& account, const Shared::Message& data)
|
void Squawk::accountMessage(const QString& account, const Shared::Message& data)
|
||||||
{
|
{
|
||||||
const QString& from = data.getPenPalJid();
|
|
||||||
Models::Roster::ElId id({account, from});
|
|
||||||
Conversations::iterator itr = conversations.find(id);
|
|
||||||
bool found = false;
|
|
||||||
|
|
||||||
if (currentConversation != 0 && currentConversation->getId() == id) {
|
|
||||||
currentConversation->addMessage(data);
|
|
||||||
QApplication::alert(this);
|
|
||||||
if (!isVisible() && !data.getForwarded()) {
|
|
||||||
notify(account, data);
|
|
||||||
}
|
|
||||||
found = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (itr != conversations.end()) {
|
|
||||||
Conversation* conv = itr->second;
|
|
||||||
conv->addMessage(data);
|
|
||||||
QApplication::alert(conv);
|
|
||||||
if (!found && conv->isMinimized()) {
|
|
||||||
rosterModel.addMessage(account, data);
|
rosterModel.addMessage(account, data);
|
||||||
}
|
}
|
||||||
if (!conv->isVisible() && !data.getForwarded()) {
|
|
||||||
notify(account, data);
|
|
||||||
}
|
|
||||||
found = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!found) {
|
void Squawk::onUnnoticedMessage(const QString& account, const Shared::Message& msg)
|
||||||
rosterModel.addMessage(account, data);
|
{
|
||||||
if (!data.getForwarded()) {
|
notify(account, msg); //Telegram does this way - notifies even if the app is visible
|
||||||
QApplication::alert(this);
|
QApplication::alert(this);
|
||||||
notify(account, data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Squawk::changeMessage(const QString& account, const QString& jid, const QString& id, const QMap<QString, QVariant>& data)
|
void Squawk::changeMessage(const QString& account, const QString& jid, const QString& id, const QMap<QString, QVariant>& data)
|
||||||
{
|
{
|
||||||
Models::Roster::ElId eid({account, jid});
|
|
||||||
bool found = false;
|
|
||||||
|
|
||||||
if (currentConversation != 0 && currentConversation->getId() == eid) {
|
|
||||||
currentConversation->changeMessage(id, data);
|
|
||||||
QApplication::alert(this);
|
|
||||||
found = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
Conversations::iterator itr = conversations.find(eid);
|
|
||||||
if (itr != conversations.end()) {
|
|
||||||
Conversation* conv = itr->second;
|
|
||||||
conv->changeMessage(id, data);
|
|
||||||
if (!found && conv->isMinimized()) {
|
|
||||||
rosterModel.changeMessage(account, jid, id, data);
|
rosterModel.changeMessage(account, jid, id, data);
|
||||||
}
|
|
||||||
found = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!found) {
|
|
||||||
rosterModel.changeMessage(account, jid, id, data);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Squawk::notify(const QString& account, const Shared::Message& msg)
|
void Squawk::notify(const QString& account, const Shared::Message& msg)
|
||||||
@ -596,60 +460,20 @@ void Squawk::notify(const QString& account, const Shared::Message& msg)
|
|||||||
void Squawk::onConversationMessage(const Shared::Message& msg)
|
void Squawk::onConversationMessage(const Shared::Message& msg)
|
||||||
{
|
{
|
||||||
Conversation* conv = static_cast<Conversation*>(sender());
|
Conversation* conv = static_cast<Conversation*>(sender());
|
||||||
emit sendMessage(conv->getAccount(), msg);
|
QString acc = conv->getAccount();
|
||||||
Models::Roster::ElId id = conv->getId();
|
|
||||||
|
|
||||||
if (currentConversation != 0 && currentConversation->getId() == id) {
|
rosterModel.addMessage(acc, msg);
|
||||||
if (conv == currentConversation) {
|
emit sendMessage(acc, msg);
|
||||||
Conversations::iterator itr = conversations.find(id);
|
|
||||||
if (itr != conversations.end()) {
|
|
||||||
itr->second->addMessage(msg);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
currentConversation->addMessage(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Squawk::onConversationMessage(const Shared::Message& msg, const QString& path)
|
void Squawk::onRequestArchive(const QString& account, const QString& jid, const QString& before)
|
||||||
{
|
{
|
||||||
Conversation* conv = static_cast<Conversation*>(sender());
|
emit requestArchive(account, jid, 20, before); //TODO amount as a settings value
|
||||||
Models::Roster::ElId id = conv->getId();
|
|
||||||
std::map<QString, std::set<Models::Roster::ElId>>::iterator itr = requestedFiles.insert(std::make_pair(msg.getId(), std::set<Models::Roster::ElId>())).first;
|
|
||||||
itr->second.insert(id);
|
|
||||||
|
|
||||||
if (currentConversation != 0 && currentConversation->getId() == id) {
|
|
||||||
if (conv == currentConversation) {
|
|
||||||
Conversations::iterator itr = conversations.find(id);
|
|
||||||
if (itr != conversations.end()) {
|
|
||||||
itr->second->appendMessageWithUpload(msg, path);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
currentConversation->appendMessageWithUpload(msg, path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
emit sendMessage(conv->getAccount(), msg, path);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Squawk::onConversationRequestArchive(const QString& before)
|
void Squawk::responseArchive(const QString& account, const QString& jid, const std::list<Shared::Message>& list, bool last)
|
||||||
{
|
{
|
||||||
Conversation* conv = static_cast<Conversation*>(sender());
|
rosterModel.responseArchive(account, jid, list, last);
|
||||||
requestArchive(conv->getAccount(), conv->getJid(), 20, before); //TODO amount as a settings value
|
|
||||||
}
|
|
||||||
|
|
||||||
void Squawk::responseArchive(const QString& account, const QString& jid, const std::list<Shared::Message>& list)
|
|
||||||
{
|
|
||||||
Models::Roster::ElId id(account, jid);
|
|
||||||
|
|
||||||
if (currentConversation != 0 && currentConversation->getId() == id) {
|
|
||||||
currentConversation->responseArchive(list);
|
|
||||||
}
|
|
||||||
|
|
||||||
Conversations::const_iterator itr = conversations.find(id);
|
|
||||||
if (itr != conversations.end()) {
|
|
||||||
itr->second->responseArchive(list);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Squawk::removeAccount(const QString& account)
|
void Squawk::removeAccount(const QString& account)
|
||||||
@ -661,8 +485,6 @@ void Squawk::removeAccount(const QString& account)
|
|||||||
++itr;
|
++itr;
|
||||||
Conversation* conv = lItr->second;
|
Conversation* conv = lItr->second;
|
||||||
disconnect(conv, &Conversation::destroyed, this, &Squawk::onConversationClosed);
|
disconnect(conv, &Conversation::destroyed, this, &Squawk::onConversationClosed);
|
||||||
disconnect(conv, &Conversation::requestArchive, this, &Squawk::onConversationRequestArchive);
|
|
||||||
disconnect(conv, &Conversation::shown, this, &Squawk::onConversationShown);
|
|
||||||
conv->close();
|
conv->close();
|
||||||
conversations.erase(lItr);
|
conversations.erase(lItr);
|
||||||
} else {
|
} else {
|
||||||
@ -926,7 +748,6 @@ void Squawk::onActivateVCard(const QString& account, const QString& jid, bool ed
|
|||||||
{
|
{
|
||||||
std::map<QString, VCard*>::const_iterator itr = vCards.find(jid);
|
std::map<QString, VCard*>::const_iterator itr = vCards.find(jid);
|
||||||
VCard* card;
|
VCard* card;
|
||||||
Models::Contact::Messages deque;
|
|
||||||
if (itr != vCards.end()) {
|
if (itr != vCards.end()) {
|
||||||
card = itr->second;
|
card = itr->second;
|
||||||
} else {
|
} else {
|
||||||
@ -1092,13 +913,8 @@ void Squawk::onPasswordPromptRejected()
|
|||||||
void Squawk::subscribeConversation(Conversation* conv)
|
void Squawk::subscribeConversation(Conversation* conv)
|
||||||
{
|
{
|
||||||
connect(conv, &Conversation::destroyed, this, &Squawk::onConversationClosed);
|
connect(conv, &Conversation::destroyed, this, &Squawk::onConversationClosed);
|
||||||
connect(conv, qOverload<const Shared::Message&>(&Conversation::sendMessage), this, qOverload<const Shared::Message&>(&Squawk::onConversationMessage));
|
connect(conv, &Conversation::sendMessage, this, &Squawk::onConversationMessage);
|
||||||
connect(conv, qOverload<const Shared::Message&, const QString&>(&Conversation::sendMessage),
|
connect(conv, &Conversation::notifyableMessage, this, &Squawk::notify);
|
||||||
this, qOverload<const Shared::Message&, const QString&>(&Squawk::onConversationMessage));
|
|
||||||
connect(conv, &Conversation::requestArchive, this, &Squawk::onConversationRequestArchive);
|
|
||||||
connect(conv, &Conversation::requestLocalFile, this, &Squawk::onConversationRequestLocalFile);
|
|
||||||
connect(conv, &Conversation::downloadFile, this, &Squawk::onConversationDownloadFile);
|
|
||||||
connect(conv, &Conversation::shown, this, &Squawk::onConversationShown);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Squawk::onRosterSelectionChanged(const QModelIndex& current, const QModelIndex& previous)
|
void Squawk::onRosterSelectionChanged(const QModelIndex& current, const QModelIndex& previous)
|
||||||
@ -1168,13 +984,10 @@ void Squawk::onRosterSelectionChanged(const QModelIndex& current, const QModelIn
|
|||||||
}
|
}
|
||||||
|
|
||||||
Models::Account* acc = rosterModel.getAccount(id->account);
|
Models::Account* acc = rosterModel.getAccount(id->account);
|
||||||
Models::Contact::Messages deque;
|
|
||||||
if (contact != 0) {
|
if (contact != 0) {
|
||||||
currentConversation = new Chat(acc, contact);
|
currentConversation = new Chat(acc, contact);
|
||||||
contact->getMessages(deque);
|
|
||||||
} else if (room != 0) {
|
} else if (room != 0) {
|
||||||
currentConversation = new Room(acc, room);
|
currentConversation = new Room(acc, room);
|
||||||
room->getMessages(deque);
|
|
||||||
|
|
||||||
if (!room->getJoined()) {
|
if (!room->getJoined()) {
|
||||||
emit setRoomJoined(id->account, id->name, true);
|
emit setRoomJoined(id->account, id->name, true);
|
||||||
@ -1185,9 +998,6 @@ void Squawk::onRosterSelectionChanged(const QModelIndex& current, const QModelIn
|
|||||||
}
|
}
|
||||||
|
|
||||||
subscribeConversation(currentConversation);
|
subscribeConversation(currentConversation);
|
||||||
for (Models::Contact::Messages::const_iterator itr = deque.begin(), end = deque.end(); itr != end; ++itr) {
|
|
||||||
currentConversation->addMessage(*itr);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (res.size() > 0) {
|
if (res.size() > 0) {
|
||||||
currentConversation->setPalResource(res);
|
currentConversation->setPalResource(res);
|
||||||
|
25
ui/squawk.h
@ -63,7 +63,6 @@ signals:
|
|||||||
void disconnectAccount(const QString&);
|
void disconnectAccount(const QString&);
|
||||||
void changeState(Shared::Availability state);
|
void changeState(Shared::Availability state);
|
||||||
void sendMessage(const QString& account, const Shared::Message& data);
|
void sendMessage(const QString& account, const Shared::Message& data);
|
||||||
void sendMessage(const QString& account, const Shared::Message& data, const QString& path);
|
|
||||||
void requestArchive(const QString& account, const QString& jid, int count, const QString& before);
|
void requestArchive(const QString& account, const QString& jid, int count, const QString& before);
|
||||||
void subscribeContact(const QString& account, const QString& jid, const QString& reason);
|
void subscribeContact(const QString& account, const QString& jid, const QString& reason);
|
||||||
void unsubscribeContact(const QString& account, const QString& jid, const QString& reason);
|
void unsubscribeContact(const QString& account, const QString& jid, const QString& reason);
|
||||||
@ -76,11 +75,11 @@ signals:
|
|||||||
void setRoomAutoJoin(const QString& account, const QString& jid, bool joined);
|
void setRoomAutoJoin(const QString& account, const QString& jid, bool joined);
|
||||||
void addRoomRequest(const QString& account, const QString& jid, const QString& nick, const QString& password, bool autoJoin);
|
void addRoomRequest(const QString& account, const QString& jid, const QString& nick, const QString& password, bool autoJoin);
|
||||||
void removeRoomRequest(const QString& account, const QString& jid);
|
void removeRoomRequest(const QString& account, const QString& jid);
|
||||||
void fileLocalPathRequest(const QString& messageId, const QString& url);
|
void fileDownloadRequest(const QString& url);
|
||||||
void downloadFileRequest(const QString& messageId, const QString& url);
|
|
||||||
void requestVCard(const QString& account, const QString& jid);
|
void requestVCard(const QString& account, const QString& jid);
|
||||||
void uploadVCard(const QString& account, const Shared::VCard& card);
|
void uploadVCard(const QString& account, const Shared::VCard& card);
|
||||||
void responsePassword(const QString& account, const QString& password);
|
void responsePassword(const QString& account, const QString& password);
|
||||||
|
void localPathInvalid(const QString& path);
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void readSettings();
|
void readSettings();
|
||||||
@ -97,16 +96,17 @@ public slots:
|
|||||||
void removePresence(const QString& account, const QString& jid, const QString& name);
|
void removePresence(const QString& account, const QString& jid, const QString& name);
|
||||||
void stateChanged(Shared::Availability state);
|
void stateChanged(Shared::Availability state);
|
||||||
void accountMessage(const QString& account, const Shared::Message& data);
|
void accountMessage(const QString& account, const Shared::Message& data);
|
||||||
void responseArchive(const QString& account, const QString& jid, const std::list<Shared::Message>& list);
|
void responseArchive(const QString& account, const QString& jid, const std::list<Shared::Message>& list, bool last);
|
||||||
void addRoom(const QString& account, const QString jid, const QMap<QString, QVariant>& data);
|
void 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 changeRoom(const QString& account, const QString jid, const QMap<QString, QVariant>& data);
|
||||||
void removeRoom(const QString& account, const QString jid);
|
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 addRoomParticipant(const QString& account, const QString& jid, const QString& name, const QMap<QString, QVariant>& data);
|
||||||
void changeRoomParticipant(const QString& account, const QString& jid, const QString& name, const QMap<QString, QVariant>& data);
|
void changeRoomParticipant(const QString& account, const QString& jid, const QString& name, const QMap<QString, QVariant>& data);
|
||||||
void removeRoomParticipant(const QString& account, const QString& jid, const QString& name);
|
void removeRoomParticipant(const QString& account, const QString& jid, const QString& name);
|
||||||
void fileLocalPathResponse(const QString& messageId, const QString& path);
|
void fileError(const std::list<Shared::MessageInfo> msgs, const QString& error, bool up);
|
||||||
void fileError(const QString& messageId, const QString& error);
|
void fileProgress(const std::list<Shared::MessageInfo> msgs, qreal value, bool up);
|
||||||
void fileProgress(const QString& messageId, qreal value);
|
void fileDownloadComplete(const std::list<Shared::MessageInfo> msgs, const QString& path);
|
||||||
|
void fileUploadComplete(const std::list<Shared::MessageInfo> msgs, const QString& path);
|
||||||
void responseVCard(const QString& jid, const Shared::VCard& card);
|
void responseVCard(const QString& jid, const Shared::VCard& card);
|
||||||
void changeMessage(const QString& account, const QString& jid, const QString& id, const QMap<QString, QVariant>& data);
|
void changeMessage(const QString& account, const QString& jid, const QString& id, const QMap<QString, QVariant>& data);
|
||||||
void requestPassword(const QString& account);
|
void requestPassword(const QString& account);
|
||||||
@ -120,7 +120,6 @@ private:
|
|||||||
Conversations conversations;
|
Conversations conversations;
|
||||||
QMenu* contextMenu;
|
QMenu* contextMenu;
|
||||||
QDBusInterface dbus;
|
QDBusInterface dbus;
|
||||||
std::map<QString, std::set<Models::Roster::ElId>> requestedFiles;
|
|
||||||
std::map<QString, VCard*> vCards;
|
std::map<QString, VCard*> vCards;
|
||||||
std::deque<QString> requestedAccountsForPasswords;
|
std::deque<QString> requestedAccountsForPasswords;
|
||||||
QInputDialog* prompt;
|
QInputDialog* prompt;
|
||||||
@ -130,6 +129,8 @@ private:
|
|||||||
|
|
||||||
protected:
|
protected:
|
||||||
void closeEvent(QCloseEvent * event) override;
|
void closeEvent(QCloseEvent * event) override;
|
||||||
|
|
||||||
|
protected slots:
|
||||||
void notify(const QString& account, const Shared::Message& msg);
|
void notify(const QString& account, const Shared::Message& msg);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
@ -147,18 +148,16 @@ private slots:
|
|||||||
void onComboboxActivated(int index);
|
void onComboboxActivated(int index);
|
||||||
void onRosterItemDoubleClicked(const QModelIndex& item);
|
void onRosterItemDoubleClicked(const QModelIndex& item);
|
||||||
void onConversationMessage(const Shared::Message& msg);
|
void onConversationMessage(const Shared::Message& msg);
|
||||||
void onConversationMessage(const Shared::Message& msg, const QString& path);
|
void onRequestArchive(const QString& account, const QString& jid, const QString& before);
|
||||||
void onConversationRequestArchive(const QString& before);
|
|
||||||
void onRosterContextMenu(const QPoint& point);
|
void onRosterContextMenu(const QPoint& point);
|
||||||
void onConversationShown();
|
|
||||||
void onConversationRequestLocalFile(const QString& messageId, const QString& url);
|
|
||||||
void onConversationDownloadFile(const QString& messageId, const QString& url);
|
|
||||||
void onItemCollepsed(const QModelIndex& index);
|
void onItemCollepsed(const QModelIndex& index);
|
||||||
void onPasswordPromptAccepted();
|
void onPasswordPromptAccepted();
|
||||||
void onPasswordPromptRejected();
|
void onPasswordPromptRejected();
|
||||||
void onRosterSelectionChanged(const QModelIndex& current, const QModelIndex& previous);
|
void onRosterSelectionChanged(const QModelIndex& current, const QModelIndex& previous);
|
||||||
void onContextAboutToHide();
|
void onContextAboutToHide();
|
||||||
|
|
||||||
|
void onUnnoticedMessage(const QString& account, const Shared::Message& msg);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void checkNextAccountForPassword();
|
void checkNextAccountForPassword();
|
||||||
void onPasswordPromptDone();
|
void onPasswordPromptDone();
|
||||||
|
32
ui/utils/CMakeLists.txt
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
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
|
||||||
|
# image.cpp
|
||||||
|
flowlayout.cpp
|
||||||
|
badge.cpp
|
||||||
|
progress.cpp
|
||||||
|
comboboxdelegate.cpp
|
||||||
|
feedview.cpp
|
||||||
|
messagedelegate.cpp
|
||||||
|
exponentialblur.cpp
|
||||||
|
shadowoverlay.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
# 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)
|
@ -37,7 +37,7 @@ QWidget* ComboboxDelegate::createEditor(QWidget *parent, const QStyleOptionViewI
|
|||||||
{
|
{
|
||||||
QComboBox *cb = new QComboBox(parent);
|
QComboBox *cb = new QComboBox(parent);
|
||||||
|
|
||||||
for (const std::pair<QString, QIcon> pair : entries) {
|
for (const std::pair<QString, QIcon>& pair : entries) {
|
||||||
cb->addItem(pair.second, pair.first);
|
cb->addItem(pair.second, pair.first);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,93 +0,0 @@
|
|||||||
/*
|
|
||||||
* Squawk messenger.
|
|
||||||
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef DROPSHADOWEFFECT_H
|
|
||||||
#define DROPSHADOWEFFECT_H
|
|
||||||
|
|
||||||
#include <QGraphicsEffect>
|
|
||||||
#include <QPainter>
|
|
||||||
#include <QPointF>
|
|
||||||
#include <QColor>
|
|
||||||
|
|
||||||
class PixmapFilter : public QObject
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
PixmapFilter(QObject *parent = nullptr);
|
|
||||||
virtual ~PixmapFilter() = 0;
|
|
||||||
|
|
||||||
virtual QRectF boundingRectFor(const QRectF &rect) const;
|
|
||||||
virtual void draw(QPainter *painter, const QPointF &p, const QPixmap &src, const QRectF &srcRect = QRectF()) const = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
class PixmapDropShadowFilter : public PixmapFilter
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
public:
|
|
||||||
PixmapDropShadowFilter(QObject *parent = nullptr);
|
|
||||||
~PixmapDropShadowFilter();
|
|
||||||
|
|
||||||
void draw(QPainter *p, const QPointF &pos, const QPixmap &px, const QRectF &src = QRectF()) const override;
|
|
||||||
|
|
||||||
qreal blurRadius() const;
|
|
||||||
void setBlurRadius(qreal radius);
|
|
||||||
|
|
||||||
QColor color() const;
|
|
||||||
void setColor(const QColor &color);
|
|
||||||
|
|
||||||
qreal thickness() const;
|
|
||||||
void setThickness(qreal thickness);
|
|
||||||
void setFrame(bool top, bool right, bool bottom, bool left);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
QColor mColor;
|
|
||||||
qreal mRadius;
|
|
||||||
qreal mThickness;
|
|
||||||
bool top;
|
|
||||||
bool right;
|
|
||||||
bool bottom;
|
|
||||||
bool left;
|
|
||||||
};
|
|
||||||
|
|
||||||
class DropShadowEffect : public QGraphicsEffect
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
qreal blurRadius() const;
|
|
||||||
QColor color() const;
|
|
||||||
void setFrame(bool top, bool right, bool bottom, bool left);
|
|
||||||
void setThickness(qreal thickness);
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void blurRadiusChanged(qreal blurRadius);
|
|
||||||
void colorChanged(const QColor &color);
|
|
||||||
|
|
||||||
public slots:
|
|
||||||
void setBlurRadius(qreal blurRadius);
|
|
||||||
void setColor(const QColor &color);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
void draw(QPainter * painter) override;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
PixmapDropShadowFilter filter;
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // DROPSHADOWEFFECT_H
|
|
@ -16,8 +16,7 @@
|
|||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "dropshadoweffect.h"
|
#include "exponentialblur.h"
|
||||||
#include "QtMath"
|
|
||||||
|
|
||||||
static const int tileSize = 32;
|
static const int tileSize = 32;
|
||||||
template <class T>
|
template <class T>
|
||||||
@ -574,128 +573,7 @@ void expblur(QImage &img, qreal radius, bool improvedQuality = false, int transp
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PixmapFilter::PixmapFilter(QObject* parent):QObject(parent) {}
|
void Utils::exponentialblur(QImage& img, qreal radius, bool improvedQuality, int transposed)
|
||||||
PixmapFilter::~PixmapFilter(){}
|
|
||||||
QRectF PixmapFilter::boundingRectFor(const QRectF &rect) const {return rect;}
|
|
||||||
|
|
||||||
PixmapDropShadowFilter::PixmapDropShadowFilter(QObject *parent):
|
|
||||||
PixmapFilter(parent),
|
|
||||||
mColor(63, 63, 63, 180),
|
|
||||||
mRadius(1),
|
|
||||||
mThickness(2),
|
|
||||||
top(true),
|
|
||||||
right(true),
|
|
||||||
bottom(true),
|
|
||||||
left(true){}
|
|
||||||
|
|
||||||
PixmapDropShadowFilter::~PixmapDropShadowFilter() {}
|
|
||||||
qreal PixmapDropShadowFilter::blurRadius() const {return mRadius;}
|
|
||||||
void PixmapDropShadowFilter::setBlurRadius(qreal radius) {mRadius = radius;}
|
|
||||||
QColor PixmapDropShadowFilter::color() const {return mColor;}
|
|
||||||
void PixmapDropShadowFilter::setColor(const QColor &color) {mColor = color;}
|
|
||||||
qreal PixmapDropShadowFilter::thickness() const {return mThickness;}
|
|
||||||
void PixmapDropShadowFilter::setThickness(qreal thickness) {mThickness = thickness;}
|
|
||||||
void PixmapDropShadowFilter::setFrame(bool ptop, bool pright, bool pbottom, bool pleft)
|
|
||||||
{
|
{
|
||||||
top = ptop;
|
expblur<12, 10, false>(img, radius, improvedQuality, transposed);
|
||||||
right = pright;
|
|
||||||
bottom = pbottom;
|
|
||||||
left = pleft;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DropShadowEffect::setThickness(qreal thickness)
|
|
||||||
{
|
|
||||||
if (filter.thickness() == thickness)
|
|
||||||
return;
|
|
||||||
|
|
||||||
filter.setThickness(thickness);
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void PixmapDropShadowFilter::draw(QPainter *p, const QPointF &pos, const QPixmap &px, const QRectF &src) const
|
|
||||||
{
|
|
||||||
if (px.isNull())
|
|
||||||
return;
|
|
||||||
|
|
||||||
QImage tmp({px.width(), px.height() + int(mThickness)}, QImage::Format_ARGB32_Premultiplied);
|
|
||||||
tmp.setDevicePixelRatio(px.devicePixelRatioF());
|
|
||||||
tmp.fill(0);
|
|
||||||
QPainter tmpPainter(&tmp);
|
|
||||||
tmpPainter.setCompositionMode(QPainter::CompositionMode_Source);
|
|
||||||
if (top) {
|
|
||||||
QRectF shadow(0, 0, px.width(), mThickness);
|
|
||||||
tmpPainter.fillRect(shadow, mColor);
|
|
||||||
}
|
|
||||||
if (right) {
|
|
||||||
QRectF shadow(px.width() - mThickness, 0, mThickness, px.height());
|
|
||||||
tmpPainter.fillRect(shadow, mColor);
|
|
||||||
}
|
|
||||||
if (bottom) {
|
|
||||||
QRectF shadow(0, px.height() - mThickness, px.width(), mThickness * 2); //i have no idea why, but it leaves some unpainted stripe without some spare space
|
|
||||||
tmpPainter.fillRect(shadow, mColor);
|
|
||||||
}
|
|
||||||
if (left) {
|
|
||||||
QRectF shadow(0, 0, mThickness, px.height());
|
|
||||||
tmpPainter.fillRect(shadow, mColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
expblur<12, 10, false>(tmp, mRadius, false, 0);
|
|
||||||
tmpPainter.end();
|
|
||||||
|
|
||||||
// Draw the actual pixmap...
|
|
||||||
p->drawPixmap(pos, px, src);
|
|
||||||
|
|
||||||
// draw the blurred drop shadow...
|
|
||||||
p->drawImage(pos, tmp);
|
|
||||||
}
|
|
||||||
|
|
||||||
qreal DropShadowEffect::blurRadius() const {return filter.blurRadius();}
|
|
||||||
void DropShadowEffect::setBlurRadius(qreal blurRadius)
|
|
||||||
{
|
|
||||||
if (qFuzzyCompare(filter.blurRadius(), blurRadius))
|
|
||||||
return;
|
|
||||||
|
|
||||||
filter.setBlurRadius(blurRadius);
|
|
||||||
updateBoundingRect();
|
|
||||||
emit blurRadiusChanged(blurRadius);
|
|
||||||
}
|
|
||||||
|
|
||||||
void DropShadowEffect::setFrame(bool top, bool right, bool bottom, bool left)
|
|
||||||
{
|
|
||||||
filter.setFrame(top, right, bottom, left);
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
QColor DropShadowEffect::color() const {return filter.color();}
|
|
||||||
void DropShadowEffect::setColor(const QColor &color)
|
|
||||||
{
|
|
||||||
if (filter.color() == color)
|
|
||||||
return;
|
|
||||||
|
|
||||||
filter.setColor(color);
|
|
||||||
update();
|
|
||||||
emit colorChanged(color);
|
|
||||||
}
|
|
||||||
|
|
||||||
void DropShadowEffect::draw(QPainter* painter)
|
|
||||||
{
|
|
||||||
if (filter.blurRadius() <= 0 && filter.thickness() == 0) {
|
|
||||||
drawSource(painter);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
PixmapPadMode mode = PadToEffectiveBoundingRect;
|
|
||||||
|
|
||||||
// Draw pixmap in device coordinates to avoid pixmap scaling.
|
|
||||||
QPoint offset;
|
|
||||||
const QPixmap pixmap = sourcePixmap(Qt::DeviceCoordinates, &offset, mode);
|
|
||||||
if (pixmap.isNull())
|
|
||||||
return;
|
|
||||||
|
|
||||||
QTransform restoreTransform = painter->worldTransform();
|
|
||||||
painter->setWorldTransform(QTransform());
|
|
||||||
filter.draw(painter, offset, pixmap);
|
|
||||||
painter->setWorldTransform(restoreTransform);
|
|
||||||
}
|
}
|
34
ui/utils/exponentialblur.h
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
* Squawk messenger.
|
||||||
|
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef EXPONENTIALBLUR_H
|
||||||
|
#define EXPONENTIALBLUR_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QImage>
|
||||||
|
#include <QtMath>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @todo write docs
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Utils {
|
||||||
|
void exponentialblur(QImage &img, qreal radius, bool improvedQuality = false, int transposed = 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // EXPONENTIALBLUR_H
|
439
ui/utils/feedview.cpp
Normal file
@ -0,0 +1,439 @@
|
|||||||
|
/*
|
||||||
|
* Squawk messenger.
|
||||||
|
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "feedview.h"
|
||||||
|
|
||||||
|
#include <QPaintEvent>
|
||||||
|
#include <QPainter>
|
||||||
|
#include <QScrollBar>
|
||||||
|
#include <QDebug>
|
||||||
|
|
||||||
|
#include "messagedelegate.h"
|
||||||
|
#include "ui/models/messagefeed.h"
|
||||||
|
|
||||||
|
constexpr int maxMessageHeight = 10000;
|
||||||
|
constexpr int approximateSingleMessageHeight = 20;
|
||||||
|
constexpr int progressSize = 70;
|
||||||
|
|
||||||
|
const std::set<int> FeedView::geometryChangingRoles = {
|
||||||
|
Models::MessageFeed::Attach,
|
||||||
|
Models::MessageFeed::Text,
|
||||||
|
Models::MessageFeed::Id,
|
||||||
|
Models::MessageFeed::Error,
|
||||||
|
Models::MessageFeed::Date
|
||||||
|
};
|
||||||
|
|
||||||
|
FeedView::FeedView(QWidget* parent):
|
||||||
|
QAbstractItemView(parent),
|
||||||
|
hints(),
|
||||||
|
vo(0),
|
||||||
|
specialDelegate(false),
|
||||||
|
specialModel(false),
|
||||||
|
clearWidgetsMode(false),
|
||||||
|
modelState(Models::MessageFeed::complete),
|
||||||
|
progress()
|
||||||
|
{
|
||||||
|
horizontalScrollBar()->setRange(0, 0);
|
||||||
|
verticalScrollBar()->setSingleStep(approximateSingleMessageHeight);
|
||||||
|
setMouseTracking(true);
|
||||||
|
setSelectionBehavior(SelectItems);
|
||||||
|
// viewport()->setAttribute(Qt::WA_Hover, true);
|
||||||
|
|
||||||
|
progress.setParent(viewport());
|
||||||
|
progress.resize(progressSize, progressSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
FeedView::~FeedView()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
QModelIndex FeedView::indexAt(const QPoint& point) const
|
||||||
|
{
|
||||||
|
int32_t vh = viewport()->height();
|
||||||
|
uint32_t y = vh - point.y() + vo;
|
||||||
|
|
||||||
|
for (std::deque<Hint>::size_type i = 0; i < hints.size(); ++i) {
|
||||||
|
const Hint& hint = hints[i];
|
||||||
|
if (y <= hint.offset + hint.height) {
|
||||||
|
if (y > hint.offset) {
|
||||||
|
return model()->index(i, 0, rootIndex());
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return QModelIndex();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FeedView::scrollTo(const QModelIndex& index, QAbstractItemView::ScrollHint hint)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
QRect FeedView::visualRect(const QModelIndex& index) const
|
||||||
|
{
|
||||||
|
unsigned int row = index.row();
|
||||||
|
if (!index.isValid() || row >= hints.size()) {
|
||||||
|
qDebug() << "visualRect for" << row;
|
||||||
|
return QRect();
|
||||||
|
} else {
|
||||||
|
const Hint& hint = hints.at(row);
|
||||||
|
const QWidget* vp = viewport();
|
||||||
|
return QRect(0, vp->height() - hint.height - hint.offset + vo, vp->width(), hint.height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int FeedView::horizontalOffset() const
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FeedView::isIndexHidden(const QModelIndex& index) const
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QModelIndex FeedView::moveCursor(QAbstractItemView::CursorAction cursorAction, Qt::KeyboardModifiers modifiers)
|
||||||
|
{
|
||||||
|
return QModelIndex();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FeedView::setSelection(const QRect& rect, QItemSelectionModel::SelectionFlags command)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
int FeedView::verticalOffset() const
|
||||||
|
{
|
||||||
|
return vo;
|
||||||
|
}
|
||||||
|
|
||||||
|
QRegion FeedView::visualRegionForSelection(const QItemSelection& selection) const
|
||||||
|
{
|
||||||
|
return QRegion();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FeedView::rowsInserted(const QModelIndex& parent, int start, int end)
|
||||||
|
{
|
||||||
|
QAbstractItemView::rowsInserted(parent, start, end);
|
||||||
|
|
||||||
|
scheduleDelayedItemsLayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FeedView::dataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight, const QVector<int>& roles)
|
||||||
|
{
|
||||||
|
if (specialDelegate) {
|
||||||
|
for (int role : roles) {
|
||||||
|
if (geometryChangingRoles.count(role) != 0) {
|
||||||
|
scheduleDelayedItemsLayout(); //to recalculate layout only if there are some geometry changing modifications
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
QAbstractItemView::dataChanged(topLeft, bottomRight, roles);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FeedView::updateGeometries()
|
||||||
|
{
|
||||||
|
qDebug() << "updateGeometries";
|
||||||
|
QScrollBar* bar = verticalScrollBar();
|
||||||
|
|
||||||
|
const QStyle* st = style();
|
||||||
|
const QAbstractItemModel* m = model();
|
||||||
|
QSize layoutBounds = maximumViewportSize();
|
||||||
|
QStyleOptionViewItem option = viewOptions();
|
||||||
|
option.rect.setHeight(maxMessageHeight);
|
||||||
|
option.rect.setWidth(layoutBounds.width());
|
||||||
|
int frameAroundContents = 0;
|
||||||
|
int verticalScrollBarExtent = st->pixelMetric(QStyle::PM_ScrollBarExtent, 0, bar);
|
||||||
|
|
||||||
|
bool layedOut = false;
|
||||||
|
if (verticalScrollBarExtent != 0 && verticalScrollBarPolicy() == Qt::ScrollBarAsNeeded && m->rowCount() * approximateSingleMessageHeight < layoutBounds.height()) {
|
||||||
|
hints.clear();
|
||||||
|
layedOut = tryToCalculateGeometriesWithNoScrollbars(option, m, layoutBounds.height());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (layedOut) {
|
||||||
|
bar->setRange(0, 0);
|
||||||
|
vo = 0;
|
||||||
|
} else {
|
||||||
|
int verticalMargin = 0;
|
||||||
|
if (st->styleHint(QStyle::SH_ScrollView_FrameOnlyAroundContents)) {
|
||||||
|
frameAroundContents = st->pixelMetric(QStyle::PM_DefaultFrameWidth) * 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (verticalScrollBarPolicy() == Qt::ScrollBarAsNeeded) {
|
||||||
|
verticalMargin = verticalScrollBarExtent + frameAroundContents;
|
||||||
|
}
|
||||||
|
|
||||||
|
layoutBounds.rwidth() -= verticalMargin;
|
||||||
|
|
||||||
|
option.features |= QStyleOptionViewItem::WrapText;
|
||||||
|
option.rect.setWidth(layoutBounds.width());
|
||||||
|
|
||||||
|
hints.clear();
|
||||||
|
uint32_t previousOffset = 0;
|
||||||
|
for (int i = 0, size = m->rowCount(); i < size; ++i) {
|
||||||
|
QModelIndex index = m->index(i, 0, rootIndex());
|
||||||
|
int height = itemDelegate(index)->sizeHint(option, index).height();
|
||||||
|
hints.emplace_back(Hint({
|
||||||
|
false,
|
||||||
|
previousOffset,
|
||||||
|
static_cast<uint32_t>(height)
|
||||||
|
}));
|
||||||
|
previousOffset += height;
|
||||||
|
}
|
||||||
|
|
||||||
|
int totalHeight = previousOffset - layoutBounds.height();
|
||||||
|
if (modelState != Models::MessageFeed::complete) {
|
||||||
|
totalHeight += progressSize;
|
||||||
|
}
|
||||||
|
vo = qMax(qMin(vo, totalHeight), 0);
|
||||||
|
bar->setRange(0, totalHeight);
|
||||||
|
bar->setPageStep(layoutBounds.height());
|
||||||
|
bar->setValue(totalHeight - vo);
|
||||||
|
}
|
||||||
|
|
||||||
|
positionProgress();
|
||||||
|
|
||||||
|
if (specialDelegate) {
|
||||||
|
clearWidgetsMode = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
QAbstractItemView::updateGeometries();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FeedView::tryToCalculateGeometriesWithNoScrollbars(const QStyleOptionViewItem& option, const QAbstractItemModel* m, uint32_t totalHeight)
|
||||||
|
{
|
||||||
|
uint32_t previousOffset = 0;
|
||||||
|
bool success = true;
|
||||||
|
for (int i = 0, size = m->rowCount(); i < size; ++i) {
|
||||||
|
QModelIndex index = m->index(i, 0, rootIndex());
|
||||||
|
int height = itemDelegate(index)->sizeHint(option, index).height();
|
||||||
|
|
||||||
|
if (previousOffset + height > totalHeight) {
|
||||||
|
success = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
hints.emplace_back(Hint({
|
||||||
|
false,
|
||||||
|
previousOffset,
|
||||||
|
static_cast<uint32_t>(height)
|
||||||
|
}));
|
||||||
|
previousOffset += height;
|
||||||
|
}
|
||||||
|
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void FeedView::paintEvent(QPaintEvent* event)
|
||||||
|
{
|
||||||
|
//qDebug() << "paint" << event->rect();
|
||||||
|
const QAbstractItemModel* m = model();
|
||||||
|
QWidget* vp = viewport();
|
||||||
|
QRect zone = event->rect().translated(0, -vo);
|
||||||
|
uint32_t vph = vp->height();
|
||||||
|
int32_t y1 = zone.y();
|
||||||
|
int32_t y2 = y1 + zone.height();
|
||||||
|
|
||||||
|
bool inZone = false;
|
||||||
|
std::deque<QModelIndex> toRener;
|
||||||
|
for (std::deque<Hint>::size_type i = 0; i < hints.size(); ++i) {
|
||||||
|
const Hint& hint = hints[i];
|
||||||
|
int32_t relativeY1 = vph - hint.offset - hint.height;
|
||||||
|
if (!inZone) {
|
||||||
|
if (y2 > relativeY1) {
|
||||||
|
inZone = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (inZone) {
|
||||||
|
toRener.emplace_back(m->index(i, 0, rootIndex()));
|
||||||
|
}
|
||||||
|
if (y1 > relativeY1) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QPainter painter(vp);
|
||||||
|
QStyleOptionViewItem option = viewOptions();
|
||||||
|
option.features = QStyleOptionViewItem::WrapText;
|
||||||
|
QPoint cursor = vp->mapFromGlobal(QCursor::pos());
|
||||||
|
|
||||||
|
if (specialDelegate) {
|
||||||
|
MessageDelegate* del = static_cast<MessageDelegate*>(itemDelegate());
|
||||||
|
if (clearWidgetsMode) {
|
||||||
|
del->beginClearWidgets();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const QModelIndex& index : toRener) {
|
||||||
|
option.rect = visualRect(index);
|
||||||
|
bool mouseOver = option.rect.contains(cursor) && vp->rect().contains(cursor);
|
||||||
|
option.state.setFlag(QStyle::State_MouseOver, mouseOver);
|
||||||
|
itemDelegate(index)->paint(&painter, option, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clearWidgetsMode && specialDelegate) {
|
||||||
|
MessageDelegate* del = static_cast<MessageDelegate*>(itemDelegate());
|
||||||
|
del->endClearWidgets();
|
||||||
|
clearWidgetsMode = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event->rect().height() == vp->height()) {
|
||||||
|
// draw the blurred drop shadow...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FeedView::verticalScrollbarValueChanged(int value)
|
||||||
|
{
|
||||||
|
vo = verticalScrollBar()->maximum() - value;
|
||||||
|
|
||||||
|
positionProgress();
|
||||||
|
|
||||||
|
if (specialDelegate) {
|
||||||
|
clearWidgetsMode = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (modelState == Models::MessageFeed::incomplete && value < progressSize) {
|
||||||
|
model()->fetchMore(rootIndex());
|
||||||
|
}
|
||||||
|
|
||||||
|
QAbstractItemView::verticalScrollbarValueChanged(vo);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FeedView::mouseMoveEvent(QMouseEvent* event)
|
||||||
|
{
|
||||||
|
if (!isVisible()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QAbstractItemView::mouseMoveEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FeedView::resizeEvent(QResizeEvent* event)
|
||||||
|
{
|
||||||
|
QAbstractItemView::resizeEvent(event);
|
||||||
|
|
||||||
|
positionProgress();
|
||||||
|
emit resized();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FeedView::positionProgress()
|
||||||
|
{
|
||||||
|
QSize layoutBounds = maximumViewportSize();
|
||||||
|
int progressPosition = layoutBounds.height() - progressSize;
|
||||||
|
std::deque<Hint>::size_type size = hints.size();
|
||||||
|
if (size > 0) {
|
||||||
|
const Hint& hint = hints[size - 1];
|
||||||
|
progressPosition -= hint.offset + hint.height;
|
||||||
|
}
|
||||||
|
progressPosition += vo;
|
||||||
|
progressPosition = qMin(progressPosition, 0);
|
||||||
|
|
||||||
|
progress.move((width() - progressSize) / 2, progressPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
QFont FeedView::getFont() const
|
||||||
|
{
|
||||||
|
return viewOptions().font;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FeedView::setItemDelegate(QAbstractItemDelegate* delegate)
|
||||||
|
{
|
||||||
|
if (specialDelegate) {
|
||||||
|
MessageDelegate* del = static_cast<MessageDelegate*>(itemDelegate());
|
||||||
|
disconnect(del, &MessageDelegate::buttonPushed, this, &FeedView::onMessageButtonPushed);
|
||||||
|
disconnect(del, &MessageDelegate::invalidPath, this, &FeedView::onMessageInvalidPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
QAbstractItemView::setItemDelegate(delegate);
|
||||||
|
|
||||||
|
MessageDelegate* del = dynamic_cast<MessageDelegate*>(delegate);
|
||||||
|
if (del) {
|
||||||
|
specialDelegate = true;
|
||||||
|
connect(del, &MessageDelegate::buttonPushed, this, &FeedView::onMessageButtonPushed);
|
||||||
|
connect(del, &MessageDelegate::invalidPath, this, &FeedView::onMessageInvalidPath);
|
||||||
|
} else {
|
||||||
|
specialDelegate = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FeedView::setModel(QAbstractItemModel* p_model)
|
||||||
|
{
|
||||||
|
if (specialModel) {
|
||||||
|
Models::MessageFeed* feed = static_cast<Models::MessageFeed*>(model());
|
||||||
|
disconnect(feed, &Models::MessageFeed::syncStateChange, this, &FeedView::onModelSyncStateChange);
|
||||||
|
}
|
||||||
|
|
||||||
|
QAbstractItemView::setModel(p_model);
|
||||||
|
|
||||||
|
Models::MessageFeed* feed = dynamic_cast<Models::MessageFeed*>(p_model);
|
||||||
|
if (feed) {
|
||||||
|
onModelSyncStateChange(feed->getSyncState());
|
||||||
|
specialModel = true;
|
||||||
|
connect(feed, &Models::MessageFeed::syncStateChange, this, &FeedView::onModelSyncStateChange);
|
||||||
|
} else {
|
||||||
|
onModelSyncStateChange(Models::MessageFeed::complete);
|
||||||
|
specialModel = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FeedView::onMessageButtonPushed(const QString& messageId, bool download)
|
||||||
|
{
|
||||||
|
if (specialModel) {
|
||||||
|
Models::MessageFeed* feed = static_cast<Models::MessageFeed*>(model());
|
||||||
|
|
||||||
|
if (download) {
|
||||||
|
feed->downloadAttachment(messageId);
|
||||||
|
} else {
|
||||||
|
feed->uploadAttachment(messageId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FeedView::onMessageInvalidPath(const QString& messageId)
|
||||||
|
{
|
||||||
|
if (specialModel) {
|
||||||
|
Models::MessageFeed* feed = static_cast<Models::MessageFeed*>(model());
|
||||||
|
feed->reportLocalPathInvalid(messageId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FeedView::onModelSyncStateChange(Models::MessageFeed::SyncState state)
|
||||||
|
{
|
||||||
|
bool needToUpdateGeometry = false;
|
||||||
|
if (modelState != state) {
|
||||||
|
if (state == Models::MessageFeed::complete || modelState == Models::MessageFeed::complete) {
|
||||||
|
needToUpdateGeometry = true;
|
||||||
|
}
|
||||||
|
modelState = state;
|
||||||
|
|
||||||
|
if (state == Models::MessageFeed::syncing) {
|
||||||
|
progress.show();
|
||||||
|
progress.start();
|
||||||
|
} else {
|
||||||
|
progress.stop();
|
||||||
|
progress.hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (needToUpdateGeometry) {
|
||||||
|
scheduleDelayedItemsLayout();
|
||||||
|
}
|
||||||
|
}
|
95
ui/utils/feedview.h
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
/*
|
||||||
|
* Squawk messenger.
|
||||||
|
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef FEEDVIEW_H
|
||||||
|
#define FEEDVIEW_H
|
||||||
|
|
||||||
|
#include <QAbstractItemView>
|
||||||
|
|
||||||
|
#include <deque>
|
||||||
|
#include <set>
|
||||||
|
|
||||||
|
#include <ui/models/messagefeed.h>
|
||||||
|
#include "progress.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @todo write docs
|
||||||
|
*/
|
||||||
|
class FeedView : public QAbstractItemView
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
FeedView(QWidget* parent = nullptr);
|
||||||
|
~FeedView();
|
||||||
|
|
||||||
|
QModelIndex indexAt(const QPoint & point) const override;
|
||||||
|
void scrollTo(const QModelIndex & index, QAbstractItemView::ScrollHint hint) override;
|
||||||
|
QRect visualRect(const QModelIndex & index) const override;
|
||||||
|
bool isIndexHidden(const QModelIndex & index) const override;
|
||||||
|
QModelIndex moveCursor(QAbstractItemView::CursorAction cursorAction, Qt::KeyboardModifiers modifiers) override;
|
||||||
|
void setSelection(const QRect & rect, QItemSelectionModel::SelectionFlags command) override;
|
||||||
|
QRegion visualRegionForSelection(const QItemSelection & selection) const override;
|
||||||
|
void setItemDelegate(QAbstractItemDelegate* delegate);
|
||||||
|
void setModel(QAbstractItemModel * model) override;
|
||||||
|
|
||||||
|
QFont getFont() const;
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void resized();
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
|
||||||
|
protected slots:
|
||||||
|
void rowsInserted(const QModelIndex & parent, int start, int end) override;
|
||||||
|
void verticalScrollbarValueChanged(int value) override;
|
||||||
|
void dataChanged(const QModelIndex & topLeft, const QModelIndex & bottomRight, const QVector<int> & roles) override;
|
||||||
|
void onMessageButtonPushed(const QString& messageId, bool download);
|
||||||
|
void onMessageInvalidPath(const QString& messageId);
|
||||||
|
void onModelSyncStateChange(Models::MessageFeed::SyncState state);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
int verticalOffset() const override;
|
||||||
|
int horizontalOffset() const override;
|
||||||
|
void paintEvent(QPaintEvent * event) override;
|
||||||
|
void updateGeometries() override;
|
||||||
|
void mouseMoveEvent(QMouseEvent * event) override;
|
||||||
|
void resizeEvent(QResizeEvent * event) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool tryToCalculateGeometriesWithNoScrollbars(const QStyleOptionViewItem& option, const QAbstractItemModel* model, uint32_t totalHeight);
|
||||||
|
void positionProgress();
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct Hint {
|
||||||
|
bool dirty;
|
||||||
|
uint32_t offset;
|
||||||
|
uint32_t height;
|
||||||
|
};
|
||||||
|
std::deque<Hint> hints;
|
||||||
|
int vo;
|
||||||
|
bool specialDelegate;
|
||||||
|
bool specialModel;
|
||||||
|
bool clearWidgetsMode;
|
||||||
|
Models::MessageFeed::SyncState modelState;
|
||||||
|
Progress progress;
|
||||||
|
|
||||||
|
static const std::set<int> geometryChangingRoles;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif //FEEDVIEW_H
|
549
ui/utils/messagedelegate.cpp
Normal file
@ -0,0 +1,549 @@
|
|||||||
|
/*
|
||||||
|
* Squawk messenger.
|
||||||
|
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <QDebug>
|
||||||
|
#include <QPainter>
|
||||||
|
#include <QApplication>
|
||||||
|
#include <QMouseEvent>
|
||||||
|
|
||||||
|
#include "messagedelegate.h"
|
||||||
|
#include "ui/models/messagefeed.h"
|
||||||
|
|
||||||
|
constexpr int avatarHeight = 50;
|
||||||
|
constexpr int margin = 6;
|
||||||
|
constexpr int textMargin = 2;
|
||||||
|
constexpr int statusIconSize = 16;
|
||||||
|
constexpr int maxAttachmentHeight = 500;
|
||||||
|
|
||||||
|
MessageDelegate::MessageDelegate(QObject* parent):
|
||||||
|
QStyledItemDelegate(parent),
|
||||||
|
bodyFont(),
|
||||||
|
nickFont(),
|
||||||
|
dateFont(),
|
||||||
|
bodyMetrics(bodyFont),
|
||||||
|
nickMetrics(nickFont),
|
||||||
|
dateMetrics(dateFont),
|
||||||
|
buttonHeight(0),
|
||||||
|
barHeight(0),
|
||||||
|
buttons(new std::map<QString, FeedButton*>()),
|
||||||
|
bars(new std::map<QString, QProgressBar*>()),
|
||||||
|
statusIcons(new std::map<QString, QLabel*>()),
|
||||||
|
bodies(new std::map<QString, QLabel*>()),
|
||||||
|
idsToKeep(new std::set<QString>()),
|
||||||
|
clearingWidgets(false)
|
||||||
|
{
|
||||||
|
QPushButton btn;
|
||||||
|
buttonHeight = btn.sizeHint().height();
|
||||||
|
|
||||||
|
QProgressBar bar;
|
||||||
|
barHeight = bar.sizeHint().height();
|
||||||
|
}
|
||||||
|
|
||||||
|
MessageDelegate::~MessageDelegate()
|
||||||
|
{
|
||||||
|
for (const std::pair<const QString, FeedButton*>& pair: *buttons){
|
||||||
|
delete pair.second;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const std::pair<const QString, QProgressBar*>& pair: *bars){
|
||||||
|
delete pair.second;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const std::pair<const QString, QLabel*>& pair: *statusIcons){
|
||||||
|
delete pair.second;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const std::pair<const QString, QLabel*>& pair: *bodies){
|
||||||
|
delete pair.second;
|
||||||
|
}
|
||||||
|
|
||||||
|
delete idsToKeep;
|
||||||
|
delete buttons;
|
||||||
|
delete bars;
|
||||||
|
delete bodies;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
|
||||||
|
{
|
||||||
|
QVariant vi = index.data(Models::MessageFeed::Bulk);
|
||||||
|
if (!vi.isValid()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Models::FeedItem data = qvariant_cast<Models::FeedItem>(vi);
|
||||||
|
painter->save();
|
||||||
|
painter->setRenderHint(QPainter::Antialiasing, true);
|
||||||
|
|
||||||
|
if (option.state & QStyle::State_MouseOver) {
|
||||||
|
painter->fillRect(option.rect, option.palette.brush(QPalette::Inactive, QPalette::Highlight));
|
||||||
|
}
|
||||||
|
|
||||||
|
QIcon icon(data.avatar);
|
||||||
|
|
||||||
|
if (data.sentByMe) {
|
||||||
|
painter->drawPixmap(option.rect.width() - avatarHeight - margin, option.rect.y() + margin / 2, icon.pixmap(avatarHeight, avatarHeight));
|
||||||
|
} else {
|
||||||
|
painter->drawPixmap(margin, option.rect.y() + margin / 2, icon.pixmap(avatarHeight, avatarHeight));
|
||||||
|
}
|
||||||
|
|
||||||
|
QStyleOptionViewItem opt = option;
|
||||||
|
QRect messageRect = option.rect.adjusted(margin, margin / 2, -(avatarHeight + 2 * margin), -margin / 2);
|
||||||
|
if (!data.sentByMe) {
|
||||||
|
opt.displayAlignment = Qt::AlignLeft | Qt::AlignTop;
|
||||||
|
messageRect.adjust(avatarHeight + margin, 0, avatarHeight + margin, 0);
|
||||||
|
} else {
|
||||||
|
opt.displayAlignment = Qt::AlignRight | Qt::AlignTop;
|
||||||
|
}
|
||||||
|
opt.rect = messageRect;
|
||||||
|
|
||||||
|
QSize messageSize(0, 0);
|
||||||
|
QSize bodySize(0, 0);
|
||||||
|
if (data.text.size() > 0) {
|
||||||
|
messageSize = bodyMetrics.boundingRect(messageRect, Qt::TextWordWrap, data.text).size();
|
||||||
|
bodySize = messageSize;
|
||||||
|
}
|
||||||
|
messageSize.rheight() += nickMetrics.lineSpacing();
|
||||||
|
messageSize.rheight() += dateMetrics.height();
|
||||||
|
if (messageSize.width() < opt.rect.width()) {
|
||||||
|
QSize senderSize = nickMetrics.boundingRect(messageRect, 0, data.sender).size();
|
||||||
|
if (senderSize.width() > messageSize.width()) {
|
||||||
|
messageSize.setWidth(senderSize.width());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
messageSize.setWidth(opt.rect.width());
|
||||||
|
}
|
||||||
|
|
||||||
|
QRect rect;
|
||||||
|
painter->setFont(nickFont);
|
||||||
|
painter->drawText(opt.rect, opt.displayAlignment, data.sender, &rect);
|
||||||
|
opt.rect.adjust(0, rect.height() + textMargin, 0, 0);
|
||||||
|
painter->save();
|
||||||
|
switch (data.attach.state) {
|
||||||
|
case Models::none:
|
||||||
|
clearHelperWidget(data); //i can't imagine the situation where it's gonna be needed
|
||||||
|
break; //but it's a possible performance problem
|
||||||
|
case Models::uploading:
|
||||||
|
case Models::downloading:
|
||||||
|
paintBar(getBar(data), painter, data.sentByMe, opt);
|
||||||
|
break;
|
||||||
|
case Models::remote:
|
||||||
|
case Models::local:
|
||||||
|
paintButton(getButton(data), painter, data.sentByMe, opt);
|
||||||
|
break;
|
||||||
|
case Models::ready:
|
||||||
|
clearHelperWidget(data);
|
||||||
|
paintPreview(data, painter, opt);
|
||||||
|
break;
|
||||||
|
case Models::errorDownload:
|
||||||
|
case Models::errorUpload:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
painter->restore();
|
||||||
|
|
||||||
|
int messageLeft = INT16_MAX;
|
||||||
|
QWidget* vp = static_cast<QWidget*>(painter->device());
|
||||||
|
if (data.text.size() > 0) {
|
||||||
|
QLabel* body = getBody(data);
|
||||||
|
body->setParent(vp);
|
||||||
|
body->setMaximumWidth(bodySize.width());
|
||||||
|
body->setMinimumWidth(bodySize.width());
|
||||||
|
body->setAlignment(opt.displayAlignment);
|
||||||
|
messageLeft = opt.rect.x();
|
||||||
|
if (data.sentByMe) {
|
||||||
|
messageLeft = opt.rect.topRight().x() - bodySize.width();
|
||||||
|
}
|
||||||
|
body->move(messageLeft, opt.rect.y());
|
||||||
|
body->show();
|
||||||
|
opt.rect.adjust(0, bodySize.height() + textMargin, 0, 0);
|
||||||
|
}
|
||||||
|
painter->setFont(dateFont);
|
||||||
|
QColor q = painter->pen().color();
|
||||||
|
q.setAlpha(180);
|
||||||
|
painter->setPen(q);
|
||||||
|
painter->drawText(opt.rect, opt.displayAlignment, data.date.toLocalTime().toString(), &rect);
|
||||||
|
if (data.sentByMe) {
|
||||||
|
if (messageLeft > rect.x() - statusIconSize - margin) {
|
||||||
|
messageLeft = rect.x() - statusIconSize - margin;
|
||||||
|
}
|
||||||
|
QLabel* statusIcon = getStatusIcon(data);
|
||||||
|
|
||||||
|
statusIcon->setParent(vp);
|
||||||
|
statusIcon->move(messageLeft, opt.rect.y());
|
||||||
|
statusIcon->show();
|
||||||
|
opt.rect.adjust(0, statusIconSize + textMargin, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
painter->restore();
|
||||||
|
|
||||||
|
if (clearingWidgets) {
|
||||||
|
idsToKeep->insert(data.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QSize MessageDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const
|
||||||
|
{
|
||||||
|
QRect messageRect = option.rect.adjusted(0, margin / 2, -(avatarHeight + 3 * margin), -margin / 2);
|
||||||
|
QStyleOptionViewItem opt = option;
|
||||||
|
opt.rect = messageRect;
|
||||||
|
QVariant va = index.data(Models::MessageFeed::Attach);
|
||||||
|
Models::Attachment attach = qvariant_cast<Models::Attachment>(va);
|
||||||
|
QString body = index.data(Models::MessageFeed::Text).toString();
|
||||||
|
QSize messageSize(0, 0);
|
||||||
|
if (body.size() > 0) {
|
||||||
|
messageSize = bodyMetrics.boundingRect(messageRect, Qt::TextWordWrap, body).size();
|
||||||
|
messageSize.rheight() += textMargin;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (attach.state) {
|
||||||
|
case Models::none:
|
||||||
|
break;
|
||||||
|
case Models::uploading:
|
||||||
|
case Models::downloading:
|
||||||
|
messageSize.rheight() += barHeight + textMargin;
|
||||||
|
break;
|
||||||
|
case Models::remote:
|
||||||
|
case Models::local:
|
||||||
|
messageSize.rheight() += buttonHeight + textMargin;
|
||||||
|
break;
|
||||||
|
case Models::ready:
|
||||||
|
messageSize.rheight() += calculateAttachSize(attach.localPath, messageRect).height() + textMargin;
|
||||||
|
break;
|
||||||
|
case Models::errorDownload:
|
||||||
|
case Models::errorUpload:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
messageSize.rheight() += nickMetrics.lineSpacing();
|
||||||
|
messageSize.rheight() += textMargin;
|
||||||
|
messageSize.rheight() += dateMetrics.height() > statusIconSize ? dateMetrics.height() : statusIconSize;
|
||||||
|
|
||||||
|
if (messageSize.height() < avatarHeight) {
|
||||||
|
messageSize.setHeight(avatarHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
messageSize.rheight() += margin;
|
||||||
|
|
||||||
|
return messageSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessageDelegate::initializeFonts(const QFont& font)
|
||||||
|
{
|
||||||
|
bodyFont = font;
|
||||||
|
nickFont = font;
|
||||||
|
dateFont = font;
|
||||||
|
|
||||||
|
nickFont.setBold(true);
|
||||||
|
|
||||||
|
float ndps = nickFont.pointSizeF();
|
||||||
|
if (ndps != -1) {
|
||||||
|
nickFont.setPointSizeF(ndps * 1.2);
|
||||||
|
} else {
|
||||||
|
nickFont.setPointSize(nickFont.pointSize() + 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
dateFont.setItalic(true);
|
||||||
|
float dps = dateFont.pointSizeF();
|
||||||
|
if (dps != -1) {
|
||||||
|
dateFont.setPointSizeF(dps * 0.8);
|
||||||
|
} else {
|
||||||
|
dateFont.setPointSize(dateFont.pointSize() - 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
bodyMetrics = QFontMetrics(bodyFont);
|
||||||
|
nickMetrics = QFontMetrics(nickFont);
|
||||||
|
dateMetrics = QFontMetrics(dateFont);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MessageDelegate::editorEvent(QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& index)
|
||||||
|
{
|
||||||
|
//qDebug() << event->type();
|
||||||
|
|
||||||
|
|
||||||
|
return QStyledItemDelegate::editorEvent(event, model, option, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessageDelegate::paintButton(QPushButton* btn, QPainter* painter, bool sentByMe, QStyleOptionViewItem& option) const
|
||||||
|
{
|
||||||
|
QPoint start;
|
||||||
|
if (sentByMe) {
|
||||||
|
start = {option.rect.width() - btn->width(), option.rect.top()};
|
||||||
|
} else {
|
||||||
|
start = option.rect.topLeft();
|
||||||
|
}
|
||||||
|
|
||||||
|
QWidget* vp = static_cast<QWidget*>(painter->device());
|
||||||
|
btn->setParent(vp);
|
||||||
|
btn->move(start);
|
||||||
|
btn->show();
|
||||||
|
|
||||||
|
option.rect.adjust(0, buttonHeight + textMargin, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessageDelegate::paintBar(QProgressBar* bar, QPainter* painter, bool sentByMe, QStyleOptionViewItem& option) const
|
||||||
|
{
|
||||||
|
QPoint start = option.rect.topLeft();
|
||||||
|
bar->resize(option.rect.width(), barHeight);
|
||||||
|
|
||||||
|
painter->translate(start);
|
||||||
|
bar->render(painter, QPoint(), QRegion(), QWidget::DrawChildren);
|
||||||
|
|
||||||
|
option.rect.adjust(0, barHeight + textMargin, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessageDelegate::paintPreview(const Models::FeedItem& data, QPainter* painter, QStyleOptionViewItem& option) const
|
||||||
|
{
|
||||||
|
Shared::Global::FileInfo info = Shared::Global::getFileInfo(data.attach.localPath);
|
||||||
|
if (info.preview == Shared::Global::FileInfo::Preview::picture) {
|
||||||
|
QSize size = constrainAttachSize(info.size, option.rect.size());
|
||||||
|
|
||||||
|
QPoint start;
|
||||||
|
if (data.sentByMe) {
|
||||||
|
start = {option.rect.width() - size.width(), option.rect.top()};
|
||||||
|
start.rx() += margin;
|
||||||
|
} else {
|
||||||
|
start = option.rect.topLeft();
|
||||||
|
}
|
||||||
|
QImage img(data.attach.localPath);
|
||||||
|
if (img.isNull()) {
|
||||||
|
emit invalidPath(data.id);
|
||||||
|
} else {
|
||||||
|
painter->drawImage(QRect(start, size), img);
|
||||||
|
}
|
||||||
|
|
||||||
|
option.rect.adjust(0, size.height() + textMargin, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QPushButton * MessageDelegate::getButton(const Models::FeedItem& data) const
|
||||||
|
{
|
||||||
|
std::map<QString, FeedButton*>::const_iterator itr = buttons->find(data.id);
|
||||||
|
FeedButton* result = 0;
|
||||||
|
if (itr != buttons->end()) {
|
||||||
|
if (
|
||||||
|
(data.attach.state == Models::remote && itr->second->download) ||
|
||||||
|
(data.attach.state == Models::local && !itr->second->download)
|
||||||
|
) {
|
||||||
|
result = itr->second;
|
||||||
|
} else {
|
||||||
|
delete itr->second;
|
||||||
|
buttons->erase(itr);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
std::map<QString, QProgressBar*>::const_iterator barItr = bars->find(data.id);
|
||||||
|
if (barItr != bars->end()) {
|
||||||
|
delete barItr->second;
|
||||||
|
bars->erase(barItr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result == 0) {
|
||||||
|
result = new FeedButton();
|
||||||
|
result->messageId = data.id;
|
||||||
|
if (data.attach.state == Models::remote) {
|
||||||
|
result->setText(QCoreApplication::translate("MessageLine", "Download"));
|
||||||
|
result->download = true;
|
||||||
|
} else {
|
||||||
|
result->setText(QCoreApplication::translate("MessageLine", "Upload"));
|
||||||
|
result->download = false;
|
||||||
|
}
|
||||||
|
buttons->insert(std::make_pair(data.id, result));
|
||||||
|
connect(result, &QPushButton::clicked, this, &MessageDelegate::onButtonPushed);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
QProgressBar * MessageDelegate::getBar(const Models::FeedItem& data) const
|
||||||
|
{
|
||||||
|
std::map<QString, QProgressBar*>::const_iterator barItr = bars->find(data.id);
|
||||||
|
QProgressBar* result = 0;
|
||||||
|
if (barItr != bars->end()) {
|
||||||
|
result = barItr->second;
|
||||||
|
} else {
|
||||||
|
std::map<QString, FeedButton*>::const_iterator itr = buttons->find(data.id);
|
||||||
|
if (itr != buttons->end()) {
|
||||||
|
delete itr->second;
|
||||||
|
buttons->erase(itr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result == 0) {
|
||||||
|
result = new QProgressBar();
|
||||||
|
result->setRange(0, 100);
|
||||||
|
bars->insert(std::make_pair(data.id, result));
|
||||||
|
}
|
||||||
|
|
||||||
|
result->setValue(data.attach.progress * 100);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
QLabel * MessageDelegate::getStatusIcon(const Models::FeedItem& data) const
|
||||||
|
{
|
||||||
|
std::map<QString, QLabel*>::const_iterator itr = statusIcons->find(data.id);
|
||||||
|
QLabel* result = 0;
|
||||||
|
|
||||||
|
QIcon q(Shared::icon(Shared::messageStateThemeIcons[static_cast<uint8_t>(data.state)]));
|
||||||
|
QString tt = Shared::Global::getName(data.state);
|
||||||
|
if (data.state == Shared::Message::State::error) {
|
||||||
|
if (data.error > 0) {
|
||||||
|
tt += ": " + data.error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (itr != statusIcons->end()) {
|
||||||
|
result = itr->second;
|
||||||
|
if (result->toolTip() != tt) { //If i just assign pixmap every time unconditionally
|
||||||
|
result->setPixmap(q.pixmap(statusIconSize)); //it involves into an infinite cycle of repaint
|
||||||
|
result->setToolTip(tt); //may be it's better to subclass and store last condition in int?
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result = new QLabel();
|
||||||
|
statusIcons->insert(std::make_pair(data.id, result));
|
||||||
|
result->setPixmap(q.pixmap(statusIconSize));
|
||||||
|
result->setToolTip(tt);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
result->setToolTip(tt);
|
||||||
|
//result->setText(std::to_string((int)data.state).c_str());
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
QLabel * MessageDelegate::getBody(const Models::FeedItem& data) const
|
||||||
|
{
|
||||||
|
std::map<QString, QLabel*>::const_iterator itr = bodies->find(data.id);
|
||||||
|
QLabel* result = 0;
|
||||||
|
|
||||||
|
if (itr != bodies->end()) {
|
||||||
|
result = itr->second;
|
||||||
|
} else {
|
||||||
|
result = new QLabel();
|
||||||
|
result->setFont(bodyFont);
|
||||||
|
result->setWordWrap(true);
|
||||||
|
result->setOpenExternalLinks(true);
|
||||||
|
result->setTextInteractionFlags(result->textInteractionFlags() | Qt::TextSelectableByMouse | Qt::LinksAccessibleByMouse);
|
||||||
|
bodies->insert(std::make_pair(data.id, result));
|
||||||
|
}
|
||||||
|
|
||||||
|
result->setText(Shared::processMessageBody(data.text));
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessageDelegate::beginClearWidgets()
|
||||||
|
{
|
||||||
|
idsToKeep->clear();
|
||||||
|
clearingWidgets = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessageDelegate::endClearWidgets()
|
||||||
|
{
|
||||||
|
if (clearingWidgets) {
|
||||||
|
std::set<QString> toRemoveButtons;
|
||||||
|
std::set<QString> toRemoveBars;
|
||||||
|
std::set<QString> toRemoveIcons;
|
||||||
|
std::set<QString> toRemoveBodies;
|
||||||
|
for (const std::pair<const QString, FeedButton*>& pair: *buttons) {
|
||||||
|
if (idsToKeep->find(pair.first) == idsToKeep->end()) {
|
||||||
|
delete pair.second;
|
||||||
|
toRemoveButtons.insert(pair.first);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const std::pair<const QString, QProgressBar*>& pair: *bars) {
|
||||||
|
if (idsToKeep->find(pair.first) == idsToKeep->end()) {
|
||||||
|
delete pair.second;
|
||||||
|
toRemoveBars.insert(pair.first);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const std::pair<const QString, QLabel*>& pair: *statusIcons) {
|
||||||
|
if (idsToKeep->find(pair.first) == idsToKeep->end()) {
|
||||||
|
delete pair.second;
|
||||||
|
toRemoveIcons.insert(pair.first);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const std::pair<const QString, QLabel*>& pair: *bodies) {
|
||||||
|
if (idsToKeep->find(pair.first) == idsToKeep->end()) {
|
||||||
|
delete pair.second;
|
||||||
|
toRemoveBodies.insert(pair.first);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const QString& key : toRemoveButtons) {
|
||||||
|
buttons->erase(key);
|
||||||
|
}
|
||||||
|
for (const QString& key : toRemoveBars) {
|
||||||
|
bars->erase(key);
|
||||||
|
}
|
||||||
|
for (const QString& key : toRemoveIcons) {
|
||||||
|
statusIcons->erase(key);
|
||||||
|
}
|
||||||
|
for (const QString& key : toRemoveBodies) {
|
||||||
|
bodies->erase(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
idsToKeep->clear();
|
||||||
|
clearingWidgets = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessageDelegate::onButtonPushed() const
|
||||||
|
{
|
||||||
|
FeedButton* btn = static_cast<FeedButton*>(sender());
|
||||||
|
emit buttonPushed(btn->messageId, btn->download);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessageDelegate::clearHelperWidget(const Models::FeedItem& data) const
|
||||||
|
{
|
||||||
|
std::map<QString, FeedButton*>::const_iterator itr = buttons->find(data.id);
|
||||||
|
if (itr != buttons->end()) {
|
||||||
|
delete itr->second;
|
||||||
|
buttons->erase(itr);
|
||||||
|
} else {
|
||||||
|
std::map<QString, QProgressBar*>::const_iterator barItr = bars->find(data.id);
|
||||||
|
if (barItr != bars->end()) {
|
||||||
|
delete barItr->second;
|
||||||
|
bars->erase(barItr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QSize MessageDelegate::calculateAttachSize(const QString& path, const QRect& bounds) const
|
||||||
|
{
|
||||||
|
Shared::Global::FileInfo info = Shared::Global::getFileInfo(path);
|
||||||
|
|
||||||
|
return constrainAttachSize(info.size, bounds.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
QSize MessageDelegate::constrainAttachSize(QSize src, QSize bounds) const
|
||||||
|
{
|
||||||
|
bounds.setHeight(maxAttachmentHeight);
|
||||||
|
|
||||||
|
if (src.width() > bounds.width() || src.height() > bounds.height()) {
|
||||||
|
src.scale(bounds, Qt::KeepAspectRatio);
|
||||||
|
}
|
||||||
|
|
||||||
|
return src;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// void MessageDelegate::setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const
|
||||||
|
// {
|
||||||
|
//
|
||||||
|
// }
|
102
ui/utils/messagedelegate.h
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
/*
|
||||||
|
* 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 MESSAGEDELEGATE_H
|
||||||
|
#define MESSAGEDELEGATE_H
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include <set>
|
||||||
|
|
||||||
|
#include <QStyledItemDelegate>
|
||||||
|
#include <QStyleOptionButton>
|
||||||
|
#include <QFont>
|
||||||
|
#include <QFontMetrics>
|
||||||
|
#include <QPushButton>
|
||||||
|
#include <QProgressBar>
|
||||||
|
#include <QLabel>
|
||||||
|
|
||||||
|
#include "shared/icons.h"
|
||||||
|
#include "shared/global.h"
|
||||||
|
#include "shared/utils.h"
|
||||||
|
|
||||||
|
namespace Models {
|
||||||
|
struct FeedItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
class MessageDelegate : public QStyledItemDelegate
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
MessageDelegate(QObject *parent = nullptr);
|
||||||
|
~MessageDelegate();
|
||||||
|
|
||||||
|
void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override;
|
||||||
|
QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const override;
|
||||||
|
//void setModelData(QWidget * editor, QAbstractItemModel * model, const QModelIndex & index) const override;
|
||||||
|
|
||||||
|
void initializeFonts(const QFont& font);
|
||||||
|
bool editorEvent(QEvent * event, QAbstractItemModel * model, const QStyleOptionViewItem & option, const QModelIndex & index) override;
|
||||||
|
void endClearWidgets();
|
||||||
|
void beginClearWidgets();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void buttonPushed(const QString& messageId, bool download) const;
|
||||||
|
void invalidPath(const QString& messageId) const;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
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* getBody(const Models::FeedItem& data) const;
|
||||||
|
void clearHelperWidget(const Models::FeedItem& data) const;
|
||||||
|
QSize calculateAttachSize(const QString& path, const QRect& bounds) const;
|
||||||
|
QSize constrainAttachSize(QSize src, QSize bounds) const;
|
||||||
|
|
||||||
|
protected slots:
|
||||||
|
void onButtonPushed() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
class FeedButton : public QPushButton {
|
||||||
|
public:
|
||||||
|
QString messageId;
|
||||||
|
bool download;
|
||||||
|
};
|
||||||
|
|
||||||
|
QFont bodyFont;
|
||||||
|
QFont nickFont;
|
||||||
|
QFont dateFont;
|
||||||
|
QFontMetrics bodyMetrics;
|
||||||
|
QFontMetrics nickMetrics;
|
||||||
|
QFontMetrics dateMetrics;
|
||||||
|
|
||||||
|
int buttonHeight;
|
||||||
|
int barHeight;
|
||||||
|
|
||||||
|
std::map<QString, FeedButton*>* buttons;
|
||||||
|
std::map<QString, QProgressBar*>* bars;
|
||||||
|
std::map<QString, QLabel*>* statusIcons;
|
||||||
|
std::map<QString, QLabel*>* bodies;
|
||||||
|
std::set<QString>* idsToKeep;
|
||||||
|
bool clearingWidgets;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // MESSAGEDELEGATE_H
|
91
ui/utils/shadowoverlay.cpp
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
/*
|
||||||
|
* Squawk messenger.
|
||||||
|
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "shadowoverlay.h"
|
||||||
|
|
||||||
|
ShadowOverlay::ShadowOverlay(unsigned int r, unsigned int t, const QColor& c, QWidget* parent):
|
||||||
|
QWidget(parent),
|
||||||
|
top(false),
|
||||||
|
right(false),
|
||||||
|
bottom(false),
|
||||||
|
left(false),
|
||||||
|
thickness(t),
|
||||||
|
radius(r),
|
||||||
|
color(c),
|
||||||
|
shadow(1, 1, QImage::Format_ARGB32_Premultiplied)
|
||||||
|
{
|
||||||
|
setAttribute(Qt::WA_NoSystemBackground);
|
||||||
|
setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShadowOverlay::paintEvent(QPaintEvent* event)
|
||||||
|
{
|
||||||
|
QWidget::paintEvent(event);
|
||||||
|
|
||||||
|
QPainter painter(this);
|
||||||
|
|
||||||
|
painter.drawImage(0, 0, shadow);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShadowOverlay::resizeEvent(QResizeEvent* event)
|
||||||
|
{
|
||||||
|
QWidget::resizeEvent(event);
|
||||||
|
|
||||||
|
updateImage();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShadowOverlay::updateImage()
|
||||||
|
{
|
||||||
|
int w = width();
|
||||||
|
int h = height();
|
||||||
|
shadow = QImage({w, h + int(thickness)}, QImage::Format_ARGB32_Premultiplied);
|
||||||
|
shadow.fill(0);
|
||||||
|
|
||||||
|
QPainter tmpPainter(&shadow);
|
||||||
|
tmpPainter.setCompositionMode(QPainter::CompositionMode_Source);
|
||||||
|
if (top) {
|
||||||
|
QRectF shadow(0, 0, w, thickness);
|
||||||
|
tmpPainter.fillRect(shadow, color);
|
||||||
|
}
|
||||||
|
if (right) {
|
||||||
|
QRectF shadow(w - thickness, 0, thickness, h);
|
||||||
|
tmpPainter.fillRect(shadow, color);
|
||||||
|
}
|
||||||
|
if (bottom) {
|
||||||
|
QRectF shadow(0, h - thickness, w, thickness * 2); //i have no idea why, but it leaves some unpainted stripe without some spare space
|
||||||
|
tmpPainter.fillRect(shadow, color);
|
||||||
|
}
|
||||||
|
if (left) {
|
||||||
|
QRectF shadow(0, 0, thickness, h);
|
||||||
|
tmpPainter.fillRect(shadow, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
Utils::exponentialblur(shadow, radius, false, 0);
|
||||||
|
tmpPainter.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShadowOverlay::setFrames(bool t, bool r, bool b, bool l)
|
||||||
|
{
|
||||||
|
top = t;
|
||||||
|
right = r;
|
||||||
|
bottom = b;
|
||||||
|
left = l;
|
||||||
|
|
||||||
|
updateImage();
|
||||||
|
update();
|
||||||
|
}
|
58
ui/utils/shadowoverlay.h
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
/*
|
||||||
|
* Squawk messenger.
|
||||||
|
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef SHADOWOVERLAY_H
|
||||||
|
#define SHADOWOVERLAY_H
|
||||||
|
|
||||||
|
#include <QWidget>
|
||||||
|
#include <QImage>
|
||||||
|
#include <QPainter>
|
||||||
|
#include <QColor>
|
||||||
|
#include <QPaintEvent>
|
||||||
|
#include <QResizeEvent>
|
||||||
|
|
||||||
|
#include <ui/utils/exponentialblur.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @todo write docs
|
||||||
|
*/
|
||||||
|
class ShadowOverlay : public QWidget {
|
||||||
|
|
||||||
|
public:
|
||||||
|
ShadowOverlay(unsigned int radius = 10, unsigned int thickness = 1, const QColor& color = Qt::black, QWidget* parent = nullptr);
|
||||||
|
|
||||||
|
void setFrames(bool top, bool right, bool bottom, bool left);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void updateImage();
|
||||||
|
|
||||||
|
void paintEvent(QPaintEvent * event) override;
|
||||||
|
void resizeEvent(QResizeEvent * event) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool top;
|
||||||
|
bool right;
|
||||||
|
bool bottom;
|
||||||
|
bool left;
|
||||||
|
unsigned int thickness;
|
||||||
|
unsigned int radius;
|
||||||
|
QColor color;
|
||||||
|
QImage shadow;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // SHADOWOVERLAY_H
|
@ -7,7 +7,7 @@ set(CMAKE_AUTOMOC ON)
|
|||||||
set(CMAKE_AUTOUIC ON)
|
set(CMAKE_AUTOUIC ON)
|
||||||
|
|
||||||
# Find the QtWidgets library
|
# Find the QtWidgets library
|
||||||
find_package(Qt5Widgets CONFIG REQUIRED)
|
find_package(Qt5Widgets CONFIG REQUIRED COMPONENTS Widgets Core)
|
||||||
|
|
||||||
add_subdirectory(vcard)
|
add_subdirectory(vcard)
|
||||||
|
|
||||||
@ -21,9 +21,11 @@ set(squawkWidgets_SRC
|
|||||||
joinconference.cpp
|
joinconference.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
# Tell CMake to create the helloworld executable
|
add_library(squawkWidgets STATIC ${squawkWidgets_SRC})
|
||||||
add_library(squawkWidgets ${squawkWidgets_SRC})
|
|
||||||
|
|
||||||
# Use the Widgets module from Qt 5.
|
# Use the Widgets module from Qt 5.
|
||||||
target_link_libraries(squawkWidgets vCardUI)
|
target_link_libraries(squawkWidgets vCardUI)
|
||||||
|
target_link_libraries(squawkWidgets squawkUIUtils)
|
||||||
target_link_libraries(squawkWidgets Qt5::Widgets)
|
target_link_libraries(squawkWidgets Qt5::Widgets)
|
||||||
|
|
||||||
|
qt5_use_modules(squawkWidgets Core Widgets)
|
||||||
|
@ -59,7 +59,7 @@ QMap<QString, QVariant> Account::value() const
|
|||||||
|
|
||||||
void Account::lockId()
|
void Account::lockId()
|
||||||
{
|
{
|
||||||
m_ui->name->setReadOnly(true);;
|
m_ui->name->setReadOnly(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Account::setData(const QMap<QString, QVariant>& data)
|
void Account::setData(const QMap<QString, QVariant>& data)
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
#include "chat.h"
|
#include "chat.h"
|
||||||
|
|
||||||
Chat::Chat(Models::Account* acc, Models::Contact* p_contact, QWidget* parent):
|
Chat::Chat(Models::Account* acc, Models::Contact* p_contact, QWidget* parent):
|
||||||
Conversation(false, acc, p_contact->getJid(), "", parent),
|
Conversation(false, acc, p_contact, p_contact->getJid(), "", parent),
|
||||||
contact(p_contact)
|
contact(p_contact)
|
||||||
{
|
{
|
||||||
setName(p_contact->getContactName());
|
setName(p_contact->getContactName());
|
||||||
@ -71,31 +71,14 @@ Shared::Message Chat::createMessage() const
|
|||||||
return msg;
|
return msg;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Chat::addMessage(const Shared::Message& data)
|
void Chat::onMessage(const Shared::Message& data)
|
||||||
{
|
{
|
||||||
Conversation::addMessage(data);
|
Conversation::onMessage(data);
|
||||||
|
|
||||||
if (!data.getOutgoing()) { //TODO need to check if that was the last message
|
if (!data.getOutgoing()) {
|
||||||
const QString& res = data.getPenPalResource();
|
const QString& res = data.getPenPalResource();
|
||||||
if (res.size() > 0) {
|
if (res.size() > 0) {
|
||||||
setPalResource(res);
|
setPalResource(res);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Chat::setName(const QString& name)
|
|
||||||
{
|
|
||||||
Conversation::setName(name);
|
|
||||||
line->setPalName(getJid(), name);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Chat::setAvatar(const QString& path)
|
|
||||||
{
|
|
||||||
Conversation::setAvatar(path);
|
|
||||||
|
|
||||||
if (path.size() == 0) {
|
|
||||||
line->dropPalAvatar(contact->getJid());
|
|
||||||
} else {
|
|
||||||
line->setPalAvatar(contact->getJid(), path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -35,15 +35,12 @@ public:
|
|||||||
Chat(Models::Account* acc, Models::Contact* p_contact, QWidget* parent = 0);
|
Chat(Models::Account* acc, Models::Contact* p_contact, QWidget* parent = 0);
|
||||||
~Chat();
|
~Chat();
|
||||||
|
|
||||||
void addMessage(const Shared::Message & data) override;
|
|
||||||
void setAvatar(const QString& path) override;
|
|
||||||
|
|
||||||
protected slots:
|
protected slots:
|
||||||
void onContactChanged(Models::Item* item, int row, int col);
|
void onContactChanged(Models::Item* item, int row, int col);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void setName(const QString & name) override;
|
|
||||||
Shared::Message createMessage() const override;
|
Shared::Message createMessage() const override;
|
||||||
|
void onMessage(const Shared::Message& msg) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void updateState();
|
void updateState();
|
||||||
|
@ -18,7 +18,6 @@
|
|||||||
|
|
||||||
#include "conversation.h"
|
#include "conversation.h"
|
||||||
#include "ui_conversation.h"
|
#include "ui_conversation.h"
|
||||||
#include "ui/utils/dropshadoweffect.h"
|
|
||||||
|
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QScrollBar>
|
#include <QScrollBar>
|
||||||
@ -29,31 +28,44 @@
|
|||||||
#include <QAbstractTextDocumentLayout>
|
#include <QAbstractTextDocumentLayout>
|
||||||
#include <QCoreApplication>
|
#include <QCoreApplication>
|
||||||
|
|
||||||
Conversation::Conversation(bool muc, Models::Account* acc, const QString pJid, const QString pRes, QWidget* parent):
|
Conversation::Conversation(bool muc, Models::Account* acc, Models::Element* el, const QString pJid, const QString pRes, QWidget* parent):
|
||||||
QWidget(parent),
|
QWidget(parent),
|
||||||
isMuc(muc),
|
isMuc(muc),
|
||||||
account(acc),
|
account(acc),
|
||||||
|
element(el),
|
||||||
palJid(pJid),
|
palJid(pJid),
|
||||||
activePalResource(pRes),
|
activePalResource(pRes),
|
||||||
line(new MessageLine(muc)),
|
|
||||||
m_ui(new Ui::Conversation()),
|
m_ui(new Ui::Conversation()),
|
||||||
ker(),
|
ker(),
|
||||||
scrollResizeCatcher(),
|
|
||||||
vis(),
|
|
||||||
thread(),
|
thread(),
|
||||||
statusIcon(0),
|
statusIcon(0),
|
||||||
statusLabel(0),
|
statusLabel(0),
|
||||||
filesLayout(0),
|
filesLayout(0),
|
||||||
overlay(new QWidget()),
|
overlay(new QWidget()),
|
||||||
filesToAttach(),
|
filesToAttach(),
|
||||||
scroll(down),
|
feed(new FeedView()),
|
||||||
|
delegate(new MessageDelegate(this)),
|
||||||
manualSliderChange(false),
|
manualSliderChange(false),
|
||||||
requestingHistory(false),
|
tsb(QApplication::style()->styleHint(QStyle::SH_ScrollBar_Transient) == 1),
|
||||||
everShown(false),
|
shadow(10, 1, Qt::black, this),
|
||||||
tsb(QApplication::style()->styleHint(QStyle::SH_ScrollBar_Transient) == 1)
|
contextMenu(new QMenu())
|
||||||
{
|
{
|
||||||
m_ui->setupUi(this);
|
m_ui->setupUi(this);
|
||||||
|
|
||||||
|
shadow.setFrames(true, false, true, false);
|
||||||
|
|
||||||
|
feed->setItemDelegate(delegate);
|
||||||
|
feed->setFrameShape(QFrame::NoFrame);
|
||||||
|
feed->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||||
|
delegate->initializeFonts(feed->getFont());
|
||||||
|
feed->setModel(el->feed);
|
||||||
|
el->feed->incrementObservers();
|
||||||
|
m_ui->widget->layout()->addWidget(feed);
|
||||||
|
|
||||||
|
connect(el->feed, &Models::MessageFeed::newMessage, this, &Conversation::onFeedMessage);
|
||||||
|
connect(feed, &FeedView::resized, this, &Conversation::positionShadow);
|
||||||
|
connect(feed, &FeedView::customContextMenuRequested, this, &Conversation::onFeedContext);
|
||||||
|
|
||||||
connect(acc, &Models::Account::childChanged, this, &Conversation::onAccountChanged);
|
connect(acc, &Models::Account::childChanged, this, &Conversation::onAccountChanged);
|
||||||
|
|
||||||
filesLayout = new FlowLayout(m_ui->filesPanel, 0);
|
filesLayout = new FlowLayout(m_ui->filesPanel, 0);
|
||||||
@ -63,14 +75,7 @@ Conversation::Conversation(bool muc, Models::Account* acc, const QString pJid, c
|
|||||||
statusLabel = m_ui->statusLabel;
|
statusLabel = m_ui->statusLabel;
|
||||||
|
|
||||||
connect(&ker, &KeyEnterReceiver::enterPressed, this, &Conversation::onEnterPressed);
|
connect(&ker, &KeyEnterReceiver::enterPressed, this, &Conversation::onEnterPressed);
|
||||||
connect(&scrollResizeCatcher, &Resizer::resized, this, &Conversation::onScrollResize);
|
|
||||||
connect(&vis, &VisibilityCatcher::shown, this, &Conversation::onScrollResize);
|
|
||||||
connect(&vis, &VisibilityCatcher::hidden, this, &Conversation::onScrollResize);
|
|
||||||
connect(m_ui->sendButton, &QPushButton::clicked, this, &Conversation::onEnterPressed);
|
connect(m_ui->sendButton, &QPushButton::clicked, this, &Conversation::onEnterPressed);
|
||||||
connect(line, &MessageLine::resize, this, &Conversation::onMessagesResize);
|
|
||||||
connect(line, &MessageLine::downloadFile, this, &Conversation::downloadFile);
|
|
||||||
connect(line, &MessageLine::uploadFile, this, qOverload<const Shared::Message&, const QString&>(&Conversation::sendMessage));
|
|
||||||
connect(line, &MessageLine::requestLocalFile, this, &Conversation::requestLocalFile);
|
|
||||||
connect(m_ui->attachButton, &QPushButton::clicked, this, &Conversation::onAttach);
|
connect(m_ui->attachButton, &QPushButton::clicked, this, &Conversation::onAttach);
|
||||||
connect(m_ui->clearButton, &QPushButton::clicked, this, &Conversation::onClearButton);
|
connect(m_ui->clearButton, &QPushButton::clicked, this, &Conversation::onClearButton);
|
||||||
connect(m_ui->messageEditor->document()->documentLayout(), &QAbstractTextDocumentLayout::documentSizeChanged,
|
connect(m_ui->messageEditor->document()->documentLayout(), &QAbstractTextDocumentLayout::documentSizeChanged,
|
||||||
@ -78,23 +83,43 @@ Conversation::Conversation(bool muc, Models::Account* acc, const QString pJid, c
|
|||||||
|
|
||||||
m_ui->messageEditor->installEventFilter(&ker);
|
m_ui->messageEditor->installEventFilter(&ker);
|
||||||
|
|
||||||
QScrollBar* vs = m_ui->scrollArea->verticalScrollBar();
|
|
||||||
m_ui->scrollArea->setWidget(line);
|
|
||||||
vs->installEventFilter(&vis);
|
|
||||||
|
|
||||||
line->setAutoFillBackground(false);
|
//line->setAutoFillBackground(false);
|
||||||
if (testAttribute(Qt::WA_TranslucentBackground)) {
|
//if (testAttribute(Qt::WA_TranslucentBackground)) {
|
||||||
m_ui->scrollArea->setAutoFillBackground(false);
|
//m_ui->scrollArea->setAutoFillBackground(false);
|
||||||
} else {
|
//} else {
|
||||||
m_ui->scrollArea->setBackgroundRole(QPalette::Base);
|
//m_ui->scrollArea->setBackgroundRole(QPalette::Base);
|
||||||
|
//}
|
||||||
|
|
||||||
|
//line->setMyAvatarPath(acc->getAvatarPath());
|
||||||
|
//line->setMyName(acc->getName());
|
||||||
|
|
||||||
|
initializeOverlay();
|
||||||
|
}
|
||||||
|
|
||||||
|
Conversation::~Conversation()
|
||||||
|
{
|
||||||
|
delete contextMenu;
|
||||||
|
|
||||||
|
element->feed->decrementObservers();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Conversation::onAccountChanged(Models::Item* item, int row, int col)
|
||||||
|
{
|
||||||
|
if (item == account) {
|
||||||
|
if (col == 2 && account->getState() == Shared::ConnectionState::connected) { //to request the history when we're back online after reconnect
|
||||||
|
//if (!requestingHistory) {
|
||||||
|
//requestingHistory = true;
|
||||||
|
//line->showBusyIndicator();
|
||||||
|
//emit requestArchive("");
|
||||||
|
//scroll = down;
|
||||||
|
//}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
connect(vs, &QScrollBar::valueChanged, this, &Conversation::onSliderValueChanged);
|
void Conversation::initializeOverlay()
|
||||||
m_ui->scrollArea->installEventFilter(&scrollResizeCatcher);
|
{
|
||||||
|
|
||||||
line->setMyAvatarPath(acc->getAvatarPath());
|
|
||||||
line->setMyName(acc->getName());
|
|
||||||
|
|
||||||
QGridLayout* gr = static_cast<QGridLayout*>(layout());
|
QGridLayout* gr = static_cast<QGridLayout*>(layout());
|
||||||
QLabel* progressLabel = new QLabel(tr("Drop files here to attach them to your message"));
|
QLabel* progressLabel = new QLabel(tr("Drop files here to attach them to your message"));
|
||||||
gr->addWidget(overlay, 0, 0, 2, 1);
|
gr->addWidget(overlay, 0, 0, 2, 1);
|
||||||
@ -115,36 +140,6 @@ Conversation::Conversation(bool muc, Models::Account* acc, const QString pJid, c
|
|||||||
nl->addWidget(progressLabel);
|
nl->addWidget(progressLabel);
|
||||||
nl->addStretch();
|
nl->addStretch();
|
||||||
overlay->hide();
|
overlay->hide();
|
||||||
|
|
||||||
applyVisualEffects();
|
|
||||||
}
|
|
||||||
|
|
||||||
Conversation::~Conversation()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void Conversation::onAccountChanged(Models::Item* item, int row, int col)
|
|
||||||
{
|
|
||||||
if (item == account) {
|
|
||||||
if (col == 2 && account->getState() == Shared::ConnectionState::connected) {
|
|
||||||
if (!requestingHistory) {
|
|
||||||
requestingHistory = true;
|
|
||||||
line->showBusyIndicator();
|
|
||||||
emit requestArchive("");
|
|
||||||
scroll = down;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Conversation::applyVisualEffects()
|
|
||||||
{
|
|
||||||
DropShadowEffect *e1 = new DropShadowEffect;
|
|
||||||
e1->setBlurRadius(10);
|
|
||||||
e1->setColor(Qt::black);
|
|
||||||
e1->setThickness(1);
|
|
||||||
e1->setFrame(true, false, true, false);
|
|
||||||
m_ui->scrollArea->setGraphicsEffect(e1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Conversation::setName(const QString& name)
|
void Conversation::setName(const QString& name)
|
||||||
@ -163,22 +158,6 @@ QString Conversation::getJid() const
|
|||||||
return palJid;
|
return palJid;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Conversation::addMessage(const Shared::Message& data)
|
|
||||||
{
|
|
||||||
int pos = m_ui->scrollArea->verticalScrollBar()->sliderPosition();
|
|
||||||
int max = m_ui->scrollArea->verticalScrollBar()->maximum();
|
|
||||||
|
|
||||||
MessageLine::Position place = line->message(data);
|
|
||||||
if (place == MessageLine::invalid) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Conversation::changeMessage(const QString& id, const QMap<QString, QVariant>& data)
|
|
||||||
{
|
|
||||||
line->changeMessage(id, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
KeyEnterReceiver::KeyEnterReceiver(QObject* parent): QObject(parent), ownEvent(false) {}
|
KeyEnterReceiver::KeyEnterReceiver(QObject* parent): QObject(parent), ownEvent(false) {}
|
||||||
|
|
||||||
bool KeyEnterReceiver::eventFilter(QObject* obj, QEvent* event)
|
bool KeyEnterReceiver::eventFilter(QObject* obj, QEvent* event)
|
||||||
@ -226,93 +205,19 @@ void Conversation::onEnterPressed()
|
|||||||
m_ui->messageEditor->clear();
|
m_ui->messageEditor->clear();
|
||||||
Shared::Message msg = createMessage();
|
Shared::Message msg = createMessage();
|
||||||
msg.setBody(body);
|
msg.setBody(body);
|
||||||
addMessage(msg);
|
|
||||||
emit sendMessage(msg);
|
emit sendMessage(msg);
|
||||||
}
|
}
|
||||||
if (filesToAttach.size() > 0) {
|
if (filesToAttach.size() > 0) {
|
||||||
for (Badge* badge : filesToAttach) {
|
for (Badge* badge : filesToAttach) {
|
||||||
Shared::Message msg = createMessage();
|
Shared::Message msg = createMessage();
|
||||||
line->appendMessageWithUpload(msg, badge->id);
|
msg.setAttachPath(badge->id);
|
||||||
usleep(1000); //this is required for the messages not to have equal time when appending into messageline
|
element->feed->registerUpload(msg.getId());
|
||||||
|
emit sendMessage(msg);
|
||||||
}
|
}
|
||||||
clearAttachedFiles();
|
clearAttachedFiles();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Conversation::appendMessageWithUpload(const Shared::Message& data, const QString& path)
|
|
||||||
{
|
|
||||||
line->appendMessageWithUploadNoSiganl(data, path);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Conversation::onMessagesResize(int amount)
|
|
||||||
{
|
|
||||||
manualSliderChange = true;
|
|
||||||
switch (scroll) {
|
|
||||||
case down:
|
|
||||||
m_ui->scrollArea->verticalScrollBar()->setValue(m_ui->scrollArea->verticalScrollBar()->maximum());
|
|
||||||
break;
|
|
||||||
case keep: {
|
|
||||||
int max = m_ui->scrollArea->verticalScrollBar()->maximum();
|
|
||||||
int value = m_ui->scrollArea->verticalScrollBar()->value() + amount;
|
|
||||||
m_ui->scrollArea->verticalScrollBar()->setValue(value);
|
|
||||||
|
|
||||||
if (value == max) {
|
|
||||||
scroll = down;
|
|
||||||
} else {
|
|
||||||
scroll = nothing;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
manualSliderChange = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Conversation::onSliderValueChanged(int value)
|
|
||||||
{
|
|
||||||
if (!manualSliderChange) {
|
|
||||||
if (value == m_ui->scrollArea->verticalScrollBar()->maximum()) {
|
|
||||||
scroll = down;
|
|
||||||
} else {
|
|
||||||
if (!requestingHistory && value == 0) {
|
|
||||||
requestingHistory = true;
|
|
||||||
line->showBusyIndicator();
|
|
||||||
emit requestArchive(line->firstMessageId());
|
|
||||||
scroll = keep;
|
|
||||||
} else {
|
|
||||||
scroll = nothing;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Conversation::responseArchive(const std::list<Shared::Message> list)
|
|
||||||
{
|
|
||||||
requestingHistory = false;
|
|
||||||
scroll = keep;
|
|
||||||
|
|
||||||
line->hideBusyIndicator();
|
|
||||||
for (std::list<Shared::Message>::const_iterator itr = list.begin(), end = list.end(); itr != end; ++itr) {
|
|
||||||
addMessage(*itr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Conversation::showEvent(QShowEvent* event)
|
|
||||||
{
|
|
||||||
if (!everShown) {
|
|
||||||
everShown = true;
|
|
||||||
line->showBusyIndicator();
|
|
||||||
requestingHistory = true;
|
|
||||||
scroll = keep;
|
|
||||||
emit requestArchive(line->firstMessageId());
|
|
||||||
}
|
|
||||||
emit shown();
|
|
||||||
|
|
||||||
QWidget::showEvent(event);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void Conversation::onAttach()
|
void Conversation::onAttach()
|
||||||
{
|
{
|
||||||
QFileDialog* d = new QFileDialog(this, tr("Chose a file to send"));
|
QFileDialog* d = new QFileDialog(this, tr("Chose a file to send"));
|
||||||
@ -340,34 +245,6 @@ void Conversation::setStatus(const QString& status)
|
|||||||
statusLabel->setText(Shared::processMessageBody(status));
|
statusLabel->setText(Shared::processMessageBody(status));
|
||||||
}
|
}
|
||||||
|
|
||||||
void Conversation::onScrollResize()
|
|
||||||
{
|
|
||||||
if (everShown) {
|
|
||||||
int size = m_ui->scrollArea->width();
|
|
||||||
QScrollBar* bar = m_ui->scrollArea->verticalScrollBar();
|
|
||||||
if (bar->isVisible() && !tsb) {
|
|
||||||
size -= bar->width();
|
|
||||||
|
|
||||||
}
|
|
||||||
line->setMaximumWidth(size);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Conversation::responseFileProgress(const QString& messageId, qreal progress)
|
|
||||||
{
|
|
||||||
line->fileProgress(messageId, progress);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Conversation::fileError(const QString& messageId, const QString& error)
|
|
||||||
{
|
|
||||||
line->fileError(messageId, error);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Conversation::responseLocalFile(const QString& messageId, const QString& path)
|
|
||||||
{
|
|
||||||
line->responseLocalFile(messageId, path);
|
|
||||||
}
|
|
||||||
|
|
||||||
Models::Roster::ElId Conversation::getId() const
|
Models::Roster::ElId Conversation::getId() const
|
||||||
{
|
{
|
||||||
return {getAccount(), getJid()};
|
return {getAccount(), getJid()};
|
||||||
@ -444,7 +321,7 @@ void Conversation::onTextEditDocSizeChanged(const QSizeF& size)
|
|||||||
|
|
||||||
void Conversation::setFeedFrames(bool top, bool right, bool bottom, bool left)
|
void Conversation::setFeedFrames(bool top, bool right, bool bottom, bool left)
|
||||||
{
|
{
|
||||||
static_cast<DropShadowEffect*>(m_ui->scrollArea->graphicsEffect())->setFrame(top, right, bottom, left);
|
shadow.setFrames(top, right, bottom, left);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Conversation::dragEnterEvent(QDragEnterEvent* event)
|
void Conversation::dragEnterEvent(QDragEnterEvent* event)
|
||||||
@ -504,21 +381,55 @@ Shared::Message Conversation::createMessage() const
|
|||||||
return msg;
|
return msg;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool VisibilityCatcher::eventFilter(QObject* obj, QEvent* event)
|
void Conversation::onFeedMessage(const Shared::Message& msg)
|
||||||
{
|
{
|
||||||
if (event->type() == QEvent::Show) {
|
this->onMessage(msg);
|
||||||
emit shown();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event->type() == QEvent::Hide) {
|
|
||||||
emit hidden();
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
VisibilityCatcher::VisibilityCatcher(QWidget* parent):
|
void Conversation::onMessage(const Shared::Message& msg)
|
||||||
QObject(parent)
|
|
||||||
{
|
{
|
||||||
|
if (!msg.getForwarded()) {
|
||||||
|
QApplication::alert(this);
|
||||||
|
if (window()->windowState().testFlag(Qt::WindowMinimized)) {
|
||||||
|
emit notifyableMessage(getAccount(), msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Conversation::positionShadow()
|
||||||
|
{
|
||||||
|
int w = width();
|
||||||
|
int h = feed->height();
|
||||||
|
|
||||||
|
shadow.resize(w, h);
|
||||||
|
shadow.move(feed->pos());
|
||||||
|
shadow.raise();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Conversation::onFeedContext(const QPoint& pos)
|
||||||
|
{
|
||||||
|
QModelIndex index = feed->indexAt(pos);
|
||||||
|
if (index.isValid()) {
|
||||||
|
Shared::Message* item = static_cast<Shared::Message*>(index.internalPointer());
|
||||||
|
|
||||||
|
contextMenu->clear();
|
||||||
|
bool showMenu = false;
|
||||||
|
QString path = item->getAttachPath();
|
||||||
|
if (path.size() > 0) {
|
||||||
|
showMenu = true;
|
||||||
|
QAction* open = contextMenu->addAction(Shared::icon("document-preview"), tr("Open"));
|
||||||
|
connect(open, &QAction::triggered, [path]() {
|
||||||
|
QDesktopServices::openUrl(QUrl::fromLocalFile(path));
|
||||||
|
});
|
||||||
|
|
||||||
|
QAction* show = contextMenu->addAction(Shared::icon("folder"), tr("Show in folder"));
|
||||||
|
connect(show, &QAction::triggered, [path]() {
|
||||||
|
Shared::Global::highlightInFileManager(path);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showMenu) {
|
||||||
|
contextMenu->popup(feed->viewport()->mapToGlobal(pos));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -24,15 +24,20 @@
|
|||||||
#include <QMap>
|
#include <QMap>
|
||||||
#include <QMimeData>
|
#include <QMimeData>
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
|
#include <QGraphicsOpacityEffect>
|
||||||
|
#include <QMenu>
|
||||||
|
#include <QAction>
|
||||||
|
#include <QDesktopServices>
|
||||||
|
|
||||||
#include "shared/message.h"
|
#include "shared/message.h"
|
||||||
#include "order.h"
|
#include "order.h"
|
||||||
#include "ui/models/account.h"
|
#include "ui/models/account.h"
|
||||||
#include "ui/models/roster.h"
|
#include "ui/models/roster.h"
|
||||||
#include "ui/utils/messageline.h"
|
|
||||||
#include "ui/utils/resizer.h"
|
|
||||||
#include "ui/utils/flowlayout.h"
|
#include "ui/utils/flowlayout.h"
|
||||||
#include "ui/utils/badge.h"
|
#include "ui/utils/badge.h"
|
||||||
|
#include "ui/utils/feedview.h"
|
||||||
|
#include "ui/utils/messagedelegate.h"
|
||||||
|
#include "ui/utils/shadowoverlay.h"
|
||||||
#include "shared/icons.h"
|
#include "shared/icons.h"
|
||||||
#include "shared/utils.h"
|
#include "shared/utils.h"
|
||||||
|
|
||||||
@ -54,54 +59,32 @@ signals:
|
|||||||
void enterPressed();
|
void enterPressed();
|
||||||
};
|
};
|
||||||
|
|
||||||
class VisibilityCatcher : public QObject {
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
VisibilityCatcher(QWidget* parent = nullptr);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
bool eventFilter(QObject* obj, QEvent* event) override;
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void hidden();
|
|
||||||
void shown();
|
|
||||||
};
|
|
||||||
|
|
||||||
class Conversation : public QWidget
|
class Conversation : public QWidget
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
Conversation(bool muc, Models::Account* acc, const QString pJid, const QString pRes, QWidget* parent = 0);
|
Conversation(bool muc, Models::Account* acc, Models::Element* el, const QString pJid, const QString pRes, QWidget* parent = 0);
|
||||||
~Conversation();
|
~Conversation();
|
||||||
|
|
||||||
QString getJid() const;
|
QString getJid() const;
|
||||||
QString getAccount() const;
|
QString getAccount() const;
|
||||||
QString getPalResource() const;
|
QString getPalResource() const;
|
||||||
Models::Roster::ElId getId() const;
|
Models::Roster::ElId getId() const;
|
||||||
virtual void addMessage(const Shared::Message& data);
|
|
||||||
|
|
||||||
void setPalResource(const QString& res);
|
void setPalResource(const QString& res);
|
||||||
void responseArchive(const std::list<Shared::Message> list);
|
|
||||||
void showEvent(QShowEvent * event) override;
|
|
||||||
void responseLocalFile(const QString& messageId, const QString& path);
|
|
||||||
void fileError(const QString& messageId, const QString& error);
|
|
||||||
void responseFileProgress(const QString& messageId, qreal progress);
|
|
||||||
virtual void setAvatar(const QString& path);
|
virtual void setAvatar(const QString& path);
|
||||||
void changeMessage(const QString& id, const QMap<QString, QVariant>& data);
|
|
||||||
void setFeedFrames(bool top, bool right, bool bottom, bool left);
|
void setFeedFrames(bool top, bool right, bool bottom, bool left);
|
||||||
virtual void appendMessageWithUpload(const Shared::Message& data, const QString& path);
|
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void sendMessage(const Shared::Message& message);
|
void sendMessage(const Shared::Message& message);
|
||||||
void sendMessage(const Shared::Message& message, const QString& path);
|
|
||||||
void requestArchive(const QString& before);
|
void requestArchive(const QString& before);
|
||||||
void shown();
|
void shown();
|
||||||
void requestLocalFile(const QString& messageId, const QString& url);
|
void requestLocalFile(const QString& messageId, const QString& url);
|
||||||
void downloadFile(const QString& messageId, const QString& url);
|
void downloadFile(const QString& messageId, const QString& url);
|
||||||
|
void notifyableMessage(const QString& account, const Shared::Message& msg);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void setName(const QString& name);
|
virtual void setName(const QString& name);
|
||||||
void applyVisualEffects();
|
|
||||||
virtual Shared::Message createMessage() const;
|
virtual Shared::Message createMessage() const;
|
||||||
void setStatus(const QString& status);
|
void setStatus(const QString& status);
|
||||||
void addAttachedFile(const QString& path);
|
void addAttachedFile(const QString& path);
|
||||||
@ -110,47 +93,44 @@ protected:
|
|||||||
void dragEnterEvent(QDragEnterEvent* event) override;
|
void dragEnterEvent(QDragEnterEvent* event) override;
|
||||||
void dragLeaveEvent(QDragLeaveEvent* event) override;
|
void dragLeaveEvent(QDragLeaveEvent* event) override;
|
||||||
void dropEvent(QDropEvent* event) override;
|
void dropEvent(QDropEvent* event) override;
|
||||||
|
void initializeOverlay();
|
||||||
|
virtual void onMessage(const Shared::Message& msg);
|
||||||
|
|
||||||
protected slots:
|
protected slots:
|
||||||
void onEnterPressed();
|
void onEnterPressed();
|
||||||
void onMessagesResize(int amount);
|
|
||||||
void onSliderValueChanged(int value);
|
|
||||||
void onAttach();
|
void onAttach();
|
||||||
void onFileSelected();
|
void onFileSelected();
|
||||||
void onScrollResize();
|
|
||||||
void onBadgeClose();
|
void onBadgeClose();
|
||||||
void onClearButton();
|
void onClearButton();
|
||||||
void onTextEditDocSizeChanged(const QSizeF& size);
|
void onTextEditDocSizeChanged(const QSizeF& size);
|
||||||
void onAccountChanged(Models::Item* item, int row, int col);
|
void onAccountChanged(Models::Item* item, int row, int col);
|
||||||
|
void onFeedMessage(const Shared::Message& msg);
|
||||||
|
void positionShadow();
|
||||||
|
void onFeedContext(const QPoint &pos);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
const bool isMuc;
|
const bool isMuc;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
enum Scroll {
|
|
||||||
nothing,
|
|
||||||
keep,
|
|
||||||
down
|
|
||||||
};
|
|
||||||
Models::Account* account;
|
Models::Account* account;
|
||||||
|
Models::Element* element;
|
||||||
QString palJid;
|
QString palJid;
|
||||||
QString activePalResource;
|
QString activePalResource;
|
||||||
MessageLine* line;
|
|
||||||
QScopedPointer<Ui::Conversation> m_ui;
|
QScopedPointer<Ui::Conversation> m_ui;
|
||||||
KeyEnterReceiver ker;
|
KeyEnterReceiver ker;
|
||||||
Resizer scrollResizeCatcher;
|
|
||||||
VisibilityCatcher vis;
|
|
||||||
QString thread;
|
QString thread;
|
||||||
QLabel* statusIcon;
|
QLabel* statusIcon;
|
||||||
QLabel* statusLabel;
|
QLabel* statusLabel;
|
||||||
FlowLayout* filesLayout;
|
FlowLayout* filesLayout;
|
||||||
QWidget* overlay;
|
QWidget* overlay;
|
||||||
W::Order<Badge*, Badge::Comparator> filesToAttach;
|
W::Order<Badge*, Badge::Comparator> filesToAttach;
|
||||||
Scroll scroll;
|
FeedView* feed;
|
||||||
|
MessageDelegate* delegate;
|
||||||
bool manualSliderChange;
|
bool manualSliderChange;
|
||||||
bool requestingHistory;
|
|
||||||
bool everShown;
|
|
||||||
bool tsb; //transient scroll bars
|
bool tsb; //transient scroll bars
|
||||||
|
|
||||||
|
ShadowOverlay shadow;
|
||||||
|
QMenu* contextMenu;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // CONVERSATION_H
|
#endif // CONVERSATION_H
|
||||||
|
@ -214,62 +214,8 @@
|
|||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
|
||||||
<widget class="QScrollArea" name="scrollArea">
|
|
||||||
<property name="autoFillBackground">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
<property name="frameShape">
|
|
||||||
<enum>QFrame::NoFrame</enum>
|
|
||||||
</property>
|
|
||||||
<property name="lineWidth">
|
|
||||||
<number>0</number>
|
|
||||||
</property>
|
|
||||||
<property name="midLineWidth">
|
|
||||||
<number>0</number>
|
|
||||||
</property>
|
|
||||||
<property name="horizontalScrollBarPolicy">
|
|
||||||
<enum>Qt::ScrollBarAlwaysOff</enum>
|
|
||||||
</property>
|
|
||||||
<property name="sizeAdjustPolicy">
|
|
||||||
<enum>QAbstractScrollArea::AdjustIgnored</enum>
|
|
||||||
</property>
|
|
||||||
<property name="widgetResizable">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
<widget class="QWidget" name="scrollAreaWidgetContents">
|
|
||||||
<property name="geometry">
|
|
||||||
<rect>
|
|
||||||
<x>0</x>
|
|
||||||
<y>0</y>
|
|
||||||
<width>520</width>
|
|
||||||
<height>385</height>
|
|
||||||
</rect>
|
|
||||||
</property>
|
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
|
||||||
<property name="spacing">
|
|
||||||
<number>0</number>
|
|
||||||
</property>
|
|
||||||
<property name="leftMargin">
|
|
||||||
<number>0</number>
|
|
||||||
</property>
|
|
||||||
<property name="topMargin">
|
|
||||||
<number>0</number>
|
|
||||||
</property>
|
|
||||||
<property name="rightMargin">
|
|
||||||
<number>0</number>
|
|
||||||
</property>
|
|
||||||
<property name="bottomMargin">
|
|
||||||
<number>0</number>
|
|
||||||
</property>
|
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
<zorder>scrollArea</zorder>
|
|
||||||
<zorder>widget_3</zorder>
|
|
||||||
</widget>
|
|
||||||
</item>
|
</item>
|
||||||
<item row="1" column="0">
|
<item row="1" column="0">
|
||||||
<widget class="QWidget" name="widget_2" native="true">
|
<widget class="QWidget" name="widget_2" native="true">
|
||||||
|
@ -19,27 +19,16 @@
|
|||||||
#include "room.h"
|
#include "room.h"
|
||||||
|
|
||||||
Room::Room(Models::Account* acc, Models::Room* p_room, QWidget* parent):
|
Room::Room(Models::Account* acc, Models::Room* p_room, QWidget* parent):
|
||||||
Conversation(true, acc, p_room->getJid(), "", parent),
|
Conversation(true, acc, p_room, p_room->getJid(), "", parent),
|
||||||
room(p_room)
|
room(p_room)
|
||||||
{
|
{
|
||||||
setName(p_room->getName());
|
setName(p_room->getName());
|
||||||
line->setMyName(room->getNick());
|
|
||||||
setStatus(room->getSubject());
|
setStatus(room->getSubject());
|
||||||
setAvatar(room->getAvatarPath());
|
setAvatar(room->getAvatarPath());
|
||||||
|
|
||||||
connect(room, &Models::Room::childChanged, this, &Room::onRoomChanged);
|
connect(room, &Models::Room::childChanged, this, &Room::onRoomChanged);
|
||||||
connect(room, &Models::Room::participantJoined, this, &Room::onParticipantJoined);
|
connect(room, &Models::Room::participantJoined, this, &Room::onParticipantJoined);
|
||||||
connect(room, &Models::Room::participantLeft, this, &Room::onParticipantLeft);
|
connect(room, &Models::Room::participantLeft, this, &Room::onParticipantLeft);
|
||||||
|
|
||||||
std::map<QString, const Models::Participant&> members = room->getParticipants();
|
|
||||||
for (std::pair<QString, const Models::Participant&> pair : members) {
|
|
||||||
QString aPath = pair.second.getAvatarPath();
|
|
||||||
if (aPath.size() > 0) {
|
|
||||||
line->setPalAvatar(pair.first, aPath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
line->setExPalAvatars(room->getExParticipantAvatars());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Room::~Room()
|
Room::~Room()
|
||||||
@ -75,30 +64,14 @@ void Room::onRoomChanged(Models::Item* item, int row, int col)
|
|||||||
setAvatar(room->getAvatarPath());
|
setAvatar(room->getAvatarPath());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
switch (col) {
|
|
||||||
case 7: {
|
|
||||||
Models::Participant* mem = static_cast<Models::Participant*>(item);
|
|
||||||
QString aPath = mem->getAvatarPath();
|
|
||||||
if (aPath.size() > 0) {
|
|
||||||
line->setPalAvatar(mem->getName(), aPath);
|
|
||||||
} else {
|
|
||||||
line->dropPalAvatar(mem->getName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Room::onParticipantJoined(const Models::Participant& participant)
|
void Room::onParticipantJoined(const Models::Participant& participant)
|
||||||
{
|
{
|
||||||
QString aPath = participant.getAvatarPath();
|
|
||||||
if (aPath.size() > 0) {
|
|
||||||
line->setPalAvatar(participant.getName(), aPath);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Room::onParticipantLeft(const QString& name)
|
void Room::onParticipantLeft(const QString& name)
|
||||||
{
|
{
|
||||||
line->movePalAvatarToEx(name);
|
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@ set(vCardUI_SRC
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Tell CMake to create the helloworld executable
|
# Tell CMake to create the helloworld executable
|
||||||
add_library(vCardUI ${vCardUI_SRC})
|
add_library(vCardUI STATIC ${vCardUI_SRC})
|
||||||
|
|
||||||
# Use the Widgets module from Qt 5.
|
# Use the Widgets module from Qt 5.
|
||||||
target_link_libraries(vCardUI Qt5::Widgets)
|
target_link_libraries(vCardUI Qt5::Widgets)
|
||||||
|