Compare commits
30 Commits
master
...
messageFee
Author | SHA1 | Date | |
---|---|---|---|
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
|
||||
- global availability (in drop down list) gets restored after reconnection
|
||||
- status icon in active chat changes when presence of the pen pal changes
|
||||
- infinite progress when open the dialogue with something that has no history to show
|
||||
|
||||
### Improvements
|
||||
- slightly reduced the traffic on the startup by not requesting history of all MUCs
|
||||
|
||||
- completely rewritten message feed, now it works way faster
|
||||
- OPTIONAL RUNTIME dependency: "KIO Widgets" that is supposed to allow you to open a file in your default file manager
|
||||
- show in folder now is supposed to try it's best to show file in folder, even you don't have KIO installed
|
||||
- once uploaded local files don't get second time uploaded - the remote URL is reused
|
||||
|
||||
## Squawk 0.1.5 (Jul 29, 2020)
|
||||
### Bug fixes
|
||||
|
@ -1,8 +1,8 @@
|
||||
cmake_minimum_required(VERSION 3.0)
|
||||
cmake_minimum_required(VERSION 3.4)
|
||||
project(squawk)
|
||||
|
||||
set(CMAKE_INCLUDE_CURRENT_DIR ON)
|
||||
set(CMAKE_CXX_STANDARD 14)
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
|
||||
set(CMAKE_AUTOMOC ON)
|
||||
set(CMAKE_AUTOUIC ON)
|
||||
@ -12,6 +12,7 @@ include(GNUInstallDirs)
|
||||
include_directories(.)
|
||||
|
||||
find_package(Qt5Widgets CONFIG REQUIRED)
|
||||
find_package(Qt5QuickCompiler CONFIG REQUIRED)
|
||||
find_package(Qt5LinguistTools)
|
||||
|
||||
if(NOT CMAKE_BUILD_TYPE)
|
||||
@ -32,6 +33,7 @@ set(squawk_SRC
|
||||
shared/message.cpp
|
||||
shared/vcard.cpp
|
||||
shared/icons.cpp
|
||||
shared/messageinfo.cpp
|
||||
)
|
||||
|
||||
set(squawk_HEAD
|
||||
@ -44,6 +46,7 @@ set(squawk_HEAD
|
||||
shared/utils.h
|
||||
shared/vcard.h
|
||||
shared/icons.h
|
||||
shared/messageinfo.h
|
||||
)
|
||||
|
||||
configure_file(resources/images/logo.svg squawk.svg COPYONLY)
|
||||
@ -64,6 +67,7 @@ qt5_add_resources(RCC resources/resources.qrc)
|
||||
|
||||
option(SYSTEM_QXMPP "Use system qxmpp lib" ON)
|
||||
option(WITH_KWALLET "Build KWallet support module" ON)
|
||||
option(WITH_KIO "Build KIO support module" ON)
|
||||
|
||||
if (SYSTEM_QXMPP)
|
||||
find_package(QXmpp CONFIG)
|
||||
@ -95,8 +99,21 @@ endif()
|
||||
add_executable(squawk ${squawk_SRC} ${squawk_HEAD} ${RCC})
|
||||
target_link_libraries(squawk Qt5::Widgets)
|
||||
|
||||
if (WITH_KIO)
|
||||
find_package(KF5KIO CONFIG)
|
||||
|
||||
if (NOT KF5KIO_FOUND)
|
||||
set(WITH_KIO OFF)
|
||||
message("KIO package wasn't found, KIO support modules wouldn't be built")
|
||||
else()
|
||||
add_definitions(-DWITH_KIO)
|
||||
message("Building with support of KIO")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
add_subdirectory(ui)
|
||||
add_subdirectory(core)
|
||||
add_subdirectory(plugins)
|
||||
|
||||
add_subdirectory(external/simpleCrypt)
|
||||
|
||||
@ -104,6 +121,8 @@ target_link_libraries(squawk squawkUI)
|
||||
target_link_libraries(squawk squawkCORE)
|
||||
target_link_libraries(squawk uuid)
|
||||
|
||||
|
||||
|
||||
add_dependencies(${CMAKE_PROJECT_NAME} translations)
|
||||
|
||||
# Install the executable
|
||||
|
@ -67,6 +67,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`)
|
||||
- `SYSTEM_QXMPP` - `True` tries to link against `qxmpp` installed in the system, `False` builds bundled `qxmpp` library (default is `True`)
|
||||
- `WITH_KWALLET` - `True` builds the `KWallet` capability module if `KWallet` is installed and if not goes to `False`. `False` disables `KWallet` support (default is `True`)
|
||||
- `WITH_KIO` - `True` builds the `KIO` capability module if `KIO` is installed and if not goes to `False`. `False` disables `KIO` support (default is `True`)
|
||||
|
||||
## License
|
||||
|
||||
|
@ -15,7 +15,7 @@ set(squawkCORE_SRC
|
||||
rosteritem.cpp
|
||||
contact.cpp
|
||||
conference.cpp
|
||||
storage.cpp
|
||||
urlstorage.cpp
|
||||
networkaccess.cpp
|
||||
adapterFuctions.cpp
|
||||
handlers/messagehandler.cpp
|
||||
|
@ -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::infoReceived, this, &Account::onDiscoveryInfoReceived);
|
||||
|
||||
QObject::connect(network, &NetworkAccess::uploadFileComplete, mh, &MessageHandler::onFileUploaded);
|
||||
QObject::connect(network, &NetworkAccess::uploadFileError, mh, &MessageHandler::onFileUploadError);
|
||||
QObject::connect(network, &NetworkAccess::uploadFileComplete, mh, &MessageHandler::onUploadFileComplete);
|
||||
QObject::connect(network, &NetworkAccess::downloadFileComplete, mh, &MessageHandler::onDownloadFileComplete);
|
||||
QObject::connect(network, &NetworkAccess::loadFileError, mh, &MessageHandler::onLoadFileError);
|
||||
|
||||
client.addExtension(rcpm);
|
||||
QObject::connect(rcpm, &QXmppMessageReceiptManager::messageDelivered, mh, &MessageHandler::onReceiptReceived);
|
||||
@ -155,8 +156,9 @@ Account::~Account()
|
||||
reconnectTimer->stop();
|
||||
}
|
||||
|
||||
QObject::disconnect(network, &NetworkAccess::uploadFileComplete, mh, &MessageHandler::onFileUploaded);
|
||||
QObject::disconnect(network, &NetworkAccess::uploadFileError, mh, &MessageHandler::onFileUploadError);
|
||||
QObject::disconnect(network, &NetworkAccess::uploadFileComplete, mh, &MessageHandler::onUploadFileComplete);
|
||||
QObject::disconnect(network, &NetworkAccess::downloadFileComplete, mh, &MessageHandler::onDownloadFileComplete);
|
||||
QObject::disconnect(network, &NetworkAccess::loadFileError, mh, &MessageHandler::onLoadFileError);
|
||||
|
||||
delete mh;
|
||||
delete rh;
|
||||
@ -402,9 +404,6 @@ QString Core::Account::getFullJid() const {
|
||||
void Core::Account::sendMessage(const Shared::Message& data) {
|
||||
mh->sendMessage(data);}
|
||||
|
||||
void Core::Account::sendMessage(const Shared::Message& data, const QString& path) {
|
||||
mh->sendMessage(data, path);}
|
||||
|
||||
void Core::Account::onMamMessageReceived(const QString& queryId, const QXmppMessage& msg)
|
||||
{
|
||||
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) {
|
||||
qDebug() << "An attempt to request archive for" << jid << "in account" << name << ", but the contact with such id wasn't found, skipping";
|
||||
emit responseArchive(jid, std::list<Shared::Message>());
|
||||
emit responseArchive(jid, std::list<Shared::Message>(), true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (state != Shared::ConnectionState::connected) {
|
||||
qDebug() << "An attempt to request archive for" << jid << "in account" << name << ", but the account is not online, skipping";
|
||||
emit responseArchive(contact->jid, std::list<Shared::Message>());
|
||||
emit responseArchive(contact->jid, std::list<Shared::Message>(), false);
|
||||
}
|
||||
|
||||
contact->requestHistory(count, before);
|
||||
@ -552,9 +551,11 @@ void Core::Account::onClientError(QXmppClient::Error err)
|
||||
case QXmppStanza::Error::NotAuthorized:
|
||||
errorText = "Authentication error";
|
||||
break;
|
||||
#if (QXMPP_VERSION) < QT_VERSION_CHECK(1, 3, 0)
|
||||
case QXmppStanza::Error::PaymentRequired:
|
||||
errorText = "Payment is required";
|
||||
break;
|
||||
#endif
|
||||
case QXmppStanza::Error::RecipientUnavailable:
|
||||
errorText = "Recipient is unavailable";
|
||||
break;
|
||||
@ -909,3 +910,16 @@ void Core::Account::handleDisconnection()
|
||||
ownVCardRequestInProgress = false;
|
||||
}
|
||||
|
||||
void Core::Account::onContactHistoryResponse(const std::list<Shared::Message>& list, bool last)
|
||||
{
|
||||
RosterItem* contact = static_cast<RosterItem*>(sender());
|
||||
|
||||
qDebug() << "Collected history for contact " << contact->jid << list.size() << "elements";
|
||||
if (last) {
|
||||
qDebug() << "The response contains the first accounted message";
|
||||
}
|
||||
emit responseArchive(contact->jid, list, last);
|
||||
}
|
||||
|
||||
void Core::Account::requestChangeMessage(const QString& jid, const QString& messageId, const QMap<QString, QVariant>& data){
|
||||
mh->requestChangeMessage(jid, messageId, data);}
|
||||
|
@ -88,7 +88,6 @@ public:
|
||||
void setPasswordType(Shared::AccountPassword pt);
|
||||
QString getFullJid() const;
|
||||
void sendMessage(const Shared::Message& data);
|
||||
void sendMessage(const Shared::Message& data, const QString& path);
|
||||
void requestArchive(const QString& jid, int count, const QString& before);
|
||||
void subscribeToContact(const QString& jid, const QString& reason);
|
||||
void unsubscribeFromContact(const QString& jid, const QString& reason);
|
||||
@ -97,6 +96,7 @@ public:
|
||||
void addContactToGroupRequest(const QString& jid, const QString& groupName);
|
||||
void removeContactFromGroupRequest(const QString& jid, const QString& groupName);
|
||||
void renameContactRequest(const QString& jid, const QString& newName);
|
||||
void requestChangeMessage(const QString& jid, const QString& messageId, const QMap<QString, QVariant>& data);
|
||||
|
||||
void setRoomJoined(const QString& jid, bool joined);
|
||||
void setRoomAutoJoin(const QString& jid, bool joined);
|
||||
@ -127,14 +127,14 @@ signals:
|
||||
void removePresence(const QString& jid, const QString& name);
|
||||
void message(const Shared::Message& data);
|
||||
void changeMessage(const QString& jid, const QString& id, const QMap<QString, QVariant>& data);
|
||||
void responseArchive(const QString& jid, const std::list<Shared::Message>& list);
|
||||
void responseArchive(const QString& jid, const std::list<Shared::Message>& list, bool last);
|
||||
void error(const QString& text);
|
||||
void addRoomParticipant(const QString& jid, const QString& nickName, const QMap<QString, QVariant>& data);
|
||||
void changeRoomParticipant(const QString& jid, const QString& nickName, const QMap<QString, QVariant>& data);
|
||||
void removeRoomParticipant(const QString& jid, const QString& nickName);
|
||||
void receivedVCard(const QString& jid, const Shared::VCard& card);
|
||||
void uploadFile(const QFileInfo& file, const QUrl& set, const QUrl& get, QMap<QString, QString> headers);
|
||||
void uploadFileError(const QString& messageId, const QString& error);
|
||||
void uploadFileError(const QString& jid, const QString& messageId, const QString& error);
|
||||
|
||||
private:
|
||||
QString name;
|
||||
@ -183,6 +183,7 @@ private slots:
|
||||
|
||||
void onDiscoveryItemsReceived (const QXmppDiscoveryIq& items);
|
||||
void onDiscoveryInfoReceived (const QXmppDiscoveryIq& info);
|
||||
void onContactHistoryResponse(const std::list<Shared::Message>& list, bool last);
|
||||
|
||||
private:
|
||||
void handleDisconnection();
|
||||
|
@ -271,6 +271,8 @@ void Core::Archive::changeMessage(const QString& id, const QMap<QString, QVarian
|
||||
bool hadStanzaId = msg.getStanzaId().size() > 0;
|
||||
QDateTime oTime = msg.getTime();
|
||||
bool idChange = msg.change(data);
|
||||
QDateTime nTime = msg.getTime();
|
||||
bool orderChange = oTime != nTime;
|
||||
|
||||
MDB_val lmdbKey, lmdbData;
|
||||
QByteArray ba;
|
||||
@ -280,15 +282,21 @@ void Core::Archive::changeMessage(const QString& id, const QMap<QString, QVarian
|
||||
lmdbKey.mv_size = strId.size();
|
||||
lmdbKey.mv_data = (char*)strId.c_str();
|
||||
int rc;
|
||||
if (idChange || orderChange) {
|
||||
if (idChange) {
|
||||
rc = mdb_del(txn, main, &lmdbKey, &lmdbData);
|
||||
} else {
|
||||
quint64 ostamp = oTime.toMSecsSinceEpoch();
|
||||
lmdbData.mv_data = (quint8*)&ostamp;
|
||||
lmdbData.mv_size = 8;
|
||||
rc = mdb_del(txn, order, &lmdbData, &lmdbKey);
|
||||
}
|
||||
if (rc == 0) {
|
||||
strId = msg.getId().toStdString();
|
||||
lmdbKey.mv_size = strId.size();
|
||||
lmdbKey.mv_data = (char*)strId.c_str();
|
||||
|
||||
|
||||
quint64 stamp = oTime.toMSecsSinceEpoch();
|
||||
quint64 stamp = nTime.toMSecsSinceEpoch();
|
||||
lmdbData.mv_data = (quint8*)&stamp;
|
||||
lmdbData.mv_size = 8;
|
||||
rc = mdb_put(txn, order, &lmdbData, &lmdbKey, 0);
|
||||
@ -502,8 +510,9 @@ long unsigned int Core::Archive::size() const
|
||||
mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn);
|
||||
MDB_stat stat;
|
||||
mdb_stat(txn, order, &stat);
|
||||
size_t amount = stat.ms_entries;
|
||||
mdb_txn_abort(txn);
|
||||
return stat.ms_entries;
|
||||
return amount;
|
||||
}
|
||||
|
||||
std::list<Shared::Message> Core::Archive::getBefore(int count, const QString& id)
|
||||
@ -603,10 +612,10 @@ void Core::Archive::setFromTheBeginning(bool is)
|
||||
MDB_txn *txn;
|
||||
mdb_txn_begin(environment, NULL, 0, &txn);
|
||||
bool success = setStatValue("beginning", is, txn);
|
||||
if (success != 0) {
|
||||
mdb_txn_abort(txn);
|
||||
} else {
|
||||
if (success) {
|
||||
mdb_txn_commit(txn);
|
||||
} else {
|
||||
mdb_txn_abort(txn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -23,7 +23,6 @@ Core::MessageHandler::MessageHandler(Core::Account* account):
|
||||
QObject(),
|
||||
acc(account),
|
||||
pendingStateMessages(),
|
||||
pendingMessages(),
|
||||
uploadingSlotsQueue()
|
||||
{
|
||||
}
|
||||
@ -168,14 +167,16 @@ void Core::MessageHandler::initializeMessage(Shared::Message& target, const QXmp
|
||||
id = source.id();
|
||||
#endif
|
||||
target.setId(id);
|
||||
if (target.getId().size() == 0) {
|
||||
QString messageId = target.getId();
|
||||
if (messageId.size() == 0) {
|
||||
target.generateRandomId(); //TODO out of desperation, I need at least a random ID
|
||||
messageId = target.getId();
|
||||
}
|
||||
target.setFrom(source.from());
|
||||
target.setTo(source.to());
|
||||
target.setBody(source.body());
|
||||
target.setForwarded(forwarded);
|
||||
target.setOutOfBandUrl(source.outOfBandUrl());
|
||||
|
||||
if (guessing) {
|
||||
if (target.getFromJid() == acc->getLogin() + "@" + acc->getServer()) {
|
||||
outgoing = true;
|
||||
@ -189,6 +190,12 @@ void Core::MessageHandler::initializeMessage(Shared::Message& target, const QXmp
|
||||
} else {
|
||||
target.setCurrentTime();
|
||||
}
|
||||
|
||||
QString oob = source.outOfBandUrl();
|
||||
if (oob.size() > 0) {
|
||||
target.setAttachPath(acc->network->addMessageAndCheckForPath(oob, acc->getName(), target.getPenPalJid(), messageId));
|
||||
}
|
||||
target.setOutOfBandUrl(oob);
|
||||
}
|
||||
|
||||
void Core::MessageHandler::logMessage(const QXmppMessage& msg, const QString& reason)
|
||||
@ -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 id = data.getId();
|
||||
QString oob = data.getOutOfBandUrl();
|
||||
RosterItem* ri = acc->rh->getRosterItem(jid);
|
||||
bool sent = false;
|
||||
QMap<QString, QVariant> changes;
|
||||
if (acc->state == Shared::ConnectionState::connected) {
|
||||
QXmppMessage msg(acc->getFullJid(), data.getTo(), data.getBody(), data.getThread());
|
||||
|
||||
@ -245,23 +264,16 @@ void Core::MessageHandler::sendMessage(Shared::Message data)
|
||||
#endif
|
||||
msg.setId(id);
|
||||
msg.setType(static_cast<QXmppMessage::Type>(data.getType())); //it is safe here, my type is compatible
|
||||
msg.setOutOfBandUrl(data.getOutOfBandUrl());
|
||||
msg.setOutOfBandUrl(oob);
|
||||
msg.setReceiptRequested(true);
|
||||
|
||||
bool sent = acc->client.sendPacket(msg);
|
||||
sent = acc->client.sendPacket(msg);
|
||||
|
||||
if (sent) {
|
||||
data.setState(Shared::Message::State::sent);
|
||||
} else {
|
||||
data.setState(Shared::Message::State::error);
|
||||
data.setErrorText("Couldn't send message via QXMPP library check out logs");
|
||||
}
|
||||
|
||||
if (ri != 0) {
|
||||
ri->appendMessageToArchive(data);
|
||||
if (sent) {
|
||||
pendingStateMessages.insert(std::make_pair(id, jid));
|
||||
}
|
||||
data.setErrorText("Couldn't send message: internal QXMPP library error, probably need to check out the logs");
|
||||
}
|
||||
|
||||
} else {
|
||||
@ -269,41 +281,74 @@ void Core::MessageHandler::sendMessage(Shared::Message data)
|
||||
data.setErrorText("You are is offline or reconnecting");
|
||||
}
|
||||
|
||||
emit acc->changeMessage(jid, id, {
|
||||
{"state", static_cast<uint>(data.getState())},
|
||||
{"errorText", data.getErrorText()}
|
||||
});
|
||||
Shared::Message::State mstate = data.getState();
|
||||
changes.insert("state", static_cast<uint>(mstate));
|
||||
if (mstate == Shared::Message::State::error) {
|
||||
changes.insert("errorText", data.getErrorText());
|
||||
}
|
||||
if (oob.size() > 0) {
|
||||
changes.insert("outOfBandUrl", oob);
|
||||
}
|
||||
if (!newMessage) {
|
||||
changes.insert("stamp", data.getTime());
|
||||
}
|
||||
|
||||
void Core::MessageHandler::sendMessage(const Shared::Message& data, const QString& path)
|
||||
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::prepareUpload(const Shared::Message& data)
|
||||
{
|
||||
if (acc->state == Shared::ConnectionState::connected) {
|
||||
QString jid = data.getPenPalJid();
|
||||
QString id = data.getId();
|
||||
RosterItem* ri = acc->rh->getRosterItem(jid);
|
||||
if (!ri) {
|
||||
qDebug() << "An attempt to initialize upload in" << acc->name << "for pal" << jid << "but the object for this pal wasn't found, something went terrebly wrong, skipping send";
|
||||
return;
|
||||
}
|
||||
QString path = data.getAttachPath();
|
||||
QString url = acc->network->getFileRemoteUrl(path);
|
||||
if (url.size() != 0) {
|
||||
sendMessageWithLocalUploadedFile(data, url);
|
||||
} else {
|
||||
if (acc->network->isUploading(path, data.getId())) {
|
||||
pendingMessages.emplace(data.getId(), data);
|
||||
if (acc->network->checkAndAddToUploading(acc->getName(), jid, id, path)) {
|
||||
ri->appendMessageToArchive(data);
|
||||
pendingStateMessages.insert(std::make_pair(id, jid));
|
||||
} else {
|
||||
if (acc->um->serviceFound()) {
|
||||
QFileInfo file(path);
|
||||
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) {
|
||||
acc->um->requestUploadSlot(file);
|
||||
}
|
||||
} else {
|
||||
onFileUploadError(data.getId(), "Uploading file no longer exists or your system user has no permission to read it");
|
||||
handleUploadError(jid, id, "Uploading file no longer exists or your system user has no permission to read it");
|
||||
qDebug() << "Requested upload slot in account" << acc->name << "for file" << path << "but the file doesn't exist or is not readable";
|
||||
}
|
||||
} else {
|
||||
onFileUploadError(data.getId(), "Your server doesn't support file upload service, or it's prohibited for your account");
|
||||
handleUploadError(jid, id, "Your server doesn't support file upload service, or it's prohibited for your account");
|
||||
qDebug() << "Requested upload slot in account" << acc->name << "for file" << path << "but upload manager didn't discover any upload services";
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
onFileUploadError(data.getId(), "Account is offline or reconnecting");
|
||||
handleUploadError(data.getPenPalJid(), data.getId(), "Account is offline or reconnecting");
|
||||
qDebug() << "An attempt to send message with not connected account " << acc->name << ", skipping";
|
||||
}
|
||||
}
|
||||
@ -314,12 +359,12 @@ void Core::MessageHandler::onUploadSlotReceived(const QXmppHttpUploadSlotIq& slo
|
||||
if (uploadingSlotsQueue.size() == 0) {
|
||||
qDebug() << "HTTP Upload manager of account" << acc->name << "reports about success requesting upload slot, but none was requested";
|
||||
} else {
|
||||
const std::pair<QString, Shared::Message>& pair = uploadingSlotsQueue.front();
|
||||
const QString& mId = pair.second.getId();
|
||||
acc->network->uploadFile(mId, pair.first, slot.putUrl(), slot.getUrl(), slot.putHeaders());
|
||||
pendingMessages.emplace(mId, pair.second);
|
||||
uploadingSlotsQueue.pop_front();
|
||||
const std::pair<QString, QString>& pair = uploadingSlotsQueue.front();
|
||||
const QString& mId = pair.second;
|
||||
QString palJid = pendingStateMessages.at(mId);
|
||||
acc->network->uploadFile({acc->name, palJid, mId}, pair.first, slot.putUrl(), slot.getUrl(), slot.putHeaders());
|
||||
|
||||
uploadingSlotsQueue.pop_front();
|
||||
if (uploadingSlotsQueue.size() > 0) {
|
||||
acc->um->requestUploadSlot(uploadingSlotsQueue.front().first);
|
||||
}
|
||||
@ -328,44 +373,111 @@ void Core::MessageHandler::onUploadSlotReceived(const QXmppHttpUploadSlotIq& slo
|
||||
|
||||
void Core::MessageHandler::onUploadSlotRequestFailed(const QXmppHttpUploadRequestIq& request)
|
||||
{
|
||||
QString err(request.error().text());
|
||||
if (uploadingSlotsQueue.size() == 0) {
|
||||
qDebug() << "HTTP Upload manager of account" << acc->name << "reports about an error requesting upload slot, but none was requested";
|
||||
qDebug() << request.error().text();
|
||||
qDebug() << err;
|
||||
} else {
|
||||
const std::pair<QString, Shared::Message>& pair = uploadingSlotsQueue.front();
|
||||
qDebug() << "Error requesting upload slot for file" << pair.first << "in account" << acc->name << ":" << request.error().text();
|
||||
emit acc->uploadFileError(pair.second.getId(), "Error requesting slot to upload file: " + request.error().text());
|
||||
const std::pair<QString, QString>& pair = uploadingSlotsQueue.front();
|
||||
qDebug() << "Error requesting upload slot for file" << pair.first << "in account" << acc->name << ":" << err;
|
||||
handleUploadError(pendingStateMessages.at(pair.second), pair.second, err);
|
||||
|
||||
uploadingSlotsQueue.pop_front();
|
||||
if (uploadingSlotsQueue.size() > 0) {
|
||||
acc->um->requestUploadSlot(uploadingSlotsQueue.front().first);
|
||||
}
|
||||
uploadingSlotsQueue.pop_front();
|
||||
}
|
||||
}
|
||||
|
||||
void Core::MessageHandler::onFileUploaded(const QString& messageId, const QString& url)
|
||||
void Core::MessageHandler::onDownloadFileComplete(const std::list<Shared::MessageInfo>& msgs, const QString& path)
|
||||
{
|
||||
std::map<QString, Shared::Message>::const_iterator itr = pendingMessages.find(messageId);
|
||||
if (itr != pendingMessages.end()) {
|
||||
sendMessageWithLocalUploadedFile(itr->second, url);
|
||||
pendingMessages.erase(itr);
|
||||
QMap<QString, QVariant> cData = {
|
||||
{"attachPath", path}
|
||||
};
|
||||
for (const Shared::MessageInfo& info : msgs) {
|
||||
if (info.account == acc->getName()) {
|
||||
RosterItem* cnt = acc->rh->getRosterItem(info.jid);
|
||||
if (cnt != 0) {
|
||||
if (cnt->changeMessage(info.messageId, cData)) {
|
||||
emit acc->changeMessage(info.jid, info.messageId, cData);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Core::MessageHandler::onFileUploadError(const QString& messageId, const QString& errMsg)
|
||||
void Core::MessageHandler::onLoadFileError(const std::list<Shared::MessageInfo>& msgs, const QString& text, bool up)
|
||||
{
|
||||
std::map<QString, Shared::Message>::const_iterator itr = pendingMessages.find(messageId);
|
||||
if (itr != pendingMessages.end()) {
|
||||
pendingMessages.erase(itr);
|
||||
if (up) {
|
||||
for (const Shared::MessageInfo& info : msgs) {
|
||||
if (info.account == acc->getName()) {
|
||||
handleUploadError(info.jid, info.messageId, text);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Core::MessageHandler::sendMessageWithLocalUploadedFile(Shared::Message msg, const QString& url)
|
||||
void Core::MessageHandler::handleUploadError(const QString& jid, const QString& messageId, const QString& errorText)
|
||||
{
|
||||
emit acc->uploadFileError(jid, messageId, "Error requesting slot to upload file: " + errorText);
|
||||
pendingStateMessages.erase(jid);
|
||||
requestChangeMessage(jid, messageId, {
|
||||
{"state", static_cast<uint>(Shared::Message::State::error)},
|
||||
{"errorText", errorText}
|
||||
});
|
||||
}
|
||||
|
||||
void Core::MessageHandler::onUploadFileComplete(const std::list<Shared::MessageInfo>& msgs, const QString& path)
|
||||
{
|
||||
for (const Shared::MessageInfo& info : msgs) {
|
||||
if (info.account == acc->getName()) {
|
||||
RosterItem* ri = acc->rh->getRosterItem(info.jid);
|
||||
if (ri != 0) {
|
||||
Shared::Message msg = ri->getMessage(info.messageId);
|
||||
sendMessageWithLocalUploadedFile(msg, path, false);
|
||||
} else {
|
||||
qDebug() << "A signal received about complete upload to" << acc->name << "for pal" << info.jid << "but the object for this pal wasn't found, something went terrebly wrong, skipping send";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Core::MessageHandler::sendMessageWithLocalUploadedFile(Shared::Message msg, const QString& url, bool newMessage)
|
||||
{
|
||||
msg.setOutOfBandUrl(url);
|
||||
if (msg.getBody().size() == 0) {
|
||||
msg.setBody(url);
|
||||
if (msg.getBody().size() == 0) { //not sure why, but most messages do that
|
||||
msg.setBody(url); //they duplicate oob in body, some of them wouldn't even show an attachment if you don't do that
|
||||
}
|
||||
sendMessage(msg);
|
||||
performSending(msg, newMessage);
|
||||
//TODO removal/progress update
|
||||
}
|
||||
|
||||
static const std::set<QString> allowerToChangeKeys({
|
||||
"attachPath",
|
||||
"outOfBandUrl",
|
||||
"state",
|
||||
"errorText"
|
||||
});
|
||||
|
||||
void Core::MessageHandler::requestChangeMessage(const QString& jid, const QString& messageId, const QMap<QString, QVariant>& data)
|
||||
{
|
||||
RosterItem* cnt = acc->rh->getRosterItem(jid);
|
||||
if (cnt != 0) {
|
||||
bool allSupported = true;
|
||||
QString unsupportedString;
|
||||
for (QMap<QString, QVariant>::const_iterator itr = data.begin(); itr != data.end(); ++itr) { //I need all this madness
|
||||
if (allowerToChangeKeys.count(itr.key()) != 1) { //to not allow this method
|
||||
allSupported = false; //to make a message to look like if it was edited
|
||||
unsupportedString = itr.key(); //basically I needed to control who exaclty calls this method
|
||||
break; //because the underlying tech assumes that the change is initiated by user
|
||||
} //not by system
|
||||
}
|
||||
if (allSupported) {
|
||||
cnt->changeMessage(messageId, data);
|
||||
emit acc->changeMessage(jid, messageId, data);
|
||||
} else {
|
||||
qDebug() << "A request to change message" << messageId << "of conversation" << jid << "with following data" << data;
|
||||
qDebug() << "only limited set of dataFields are supported yet here, and" << unsupportedString << "isn't one of them, skipping";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -28,6 +28,7 @@
|
||||
#include <QXmppHttpUploadIq.h>
|
||||
|
||||
#include <shared/message.h>
|
||||
#include <shared/messageinfo.h>
|
||||
|
||||
namespace Core {
|
||||
|
||||
@ -44,8 +45,7 @@ public:
|
||||
MessageHandler(Account* account);
|
||||
|
||||
public:
|
||||
void sendMessage(Shared::Message data);
|
||||
void sendMessage(const Shared::Message& data, const QString& path);
|
||||
void sendMessage(const Shared::Message& data);
|
||||
void initializeMessage(Shared::Message& target, const QXmppMessage& source, bool outgoing = false, bool forwarded = false, bool guessing = false) const;
|
||||
|
||||
public slots:
|
||||
@ -55,20 +55,24 @@ public slots:
|
||||
void onReceiptReceived(const QString& jid, const QString& id);
|
||||
void onUploadSlotReceived(const QXmppHttpUploadSlotIq& slot);
|
||||
void onUploadSlotRequestFailed(const QXmppHttpUploadRequestIq& request);
|
||||
void onFileUploaded(const QString& messageId, const QString& url);
|
||||
void onFileUploadError(const QString& messageId, const QString& errMsg);
|
||||
void onDownloadFileComplete(const std::list<Shared::MessageInfo>& msgs, const QString& path);
|
||||
void onUploadFileComplete(const std::list<Shared::MessageInfo>& msgs, const QString& path);
|
||||
void onLoadFileError(const std::list<Shared::MessageInfo>& msgs, const QString& path, bool up);
|
||||
void requestChangeMessage(const QString& jid, const QString& messageId, const QMap<QString, QVariant>& data);
|
||||
|
||||
private:
|
||||
bool handleChatMessage(const QXmppMessage& msg, bool outgoing = false, bool forwarded = false, bool guessing = false);
|
||||
bool handleGroupMessage(const QXmppMessage& msg, bool outgoing = false, bool forwarded = false, bool guessing = false);
|
||||
void logMessage(const QXmppMessage& msg, const QString& reason = "Message wasn't handled: ");
|
||||
void sendMessageWithLocalUploadedFile(Shared::Message msg, const QString& url);
|
||||
void sendMessageWithLocalUploadedFile(Shared::Message msg, const QString& url, bool newMessage = true);
|
||||
void performSending(Shared::Message data, bool newMessage = true);
|
||||
void prepareUpload(const Shared::Message& data);
|
||||
void handleUploadError(const QString& jid, const QString& messageId, const QString& errorText);
|
||||
|
||||
private:
|
||||
Account* acc;
|
||||
std::map<QString, QString> pendingStateMessages;
|
||||
std::map<QString, Shared::Message> pendingMessages;
|
||||
std::deque<std::pair<QString, Shared::Message>> uploadingSlotsQueue;
|
||||
std::map<QString, QString> pendingStateMessages; //key is message id, value is JID
|
||||
std::deque<std::pair<QString, QString>> uploadingSlotsQueue;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -190,7 +190,7 @@ void Core::RosterHandler::removeContactRequest(const QString& jid)
|
||||
void Core::RosterHandler::handleNewRosterItem(Core::RosterItem* contact)
|
||||
{
|
||||
connect(contact, &RosterItem::needHistory, this->acc, &Account::onContactNeedHistory);
|
||||
connect(contact, &RosterItem::historyResponse, this, &RosterHandler::onContactHistoryResponse);
|
||||
connect(contact, &RosterItem::historyResponse, this->acc, &Account::onContactHistoryResponse);
|
||||
connect(contact, &RosterItem::nameChanged, this, &RosterHandler::onContactNameChanged);
|
||||
connect(contact, &RosterItem::avatarChanged, this, &RosterHandler::onContactAvatarChanged);
|
||||
connect(contact, &RosterItem::requestVCard, this->acc, &Account::requestVCard);
|
||||
@ -315,14 +315,6 @@ void Core::RosterHandler::removeFromGroup(const QString& jid, const QString& gro
|
||||
}
|
||||
}
|
||||
|
||||
void Core::RosterHandler::onContactHistoryResponse(const std::list<Shared::Message>& list)
|
||||
{
|
||||
RosterItem* contact = static_cast<RosterItem*>(sender());
|
||||
|
||||
qDebug() << "Collected history for contact " << contact->jid << list.size() << "elements";
|
||||
emit acc->responseArchive(contact->jid, list);
|
||||
}
|
||||
|
||||
Core::RosterItem * Core::RosterHandler::getRosterItem(const QString& jid)
|
||||
{
|
||||
RosterItem* item = 0;
|
||||
|
@ -86,7 +86,6 @@ private slots:
|
||||
void onContactGroupRemoved(const QString& group);
|
||||
void onContactNameChanged(const QString& name);
|
||||
void onContactSubscriptionStateChanged(Shared::SubscriptionState state);
|
||||
void onContactHistoryResponse(const std::list<Shared::Message>& list);
|
||||
void onContactAvatarChanged(Shared::Avatar, const QString& path);
|
||||
|
||||
private:
|
||||
|
@ -16,13 +16,17 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#include <QtWidgets/QApplication>
|
||||
#include <QtCore/QDir>
|
||||
|
||||
#include "networkaccess.h"
|
||||
|
||||
Core::NetworkAccess::NetworkAccess(QObject* parent):
|
||||
QObject(parent),
|
||||
running(false),
|
||||
manager(0),
|
||||
files("files"),
|
||||
storage("fileURLStorage"),
|
||||
downloads(),
|
||||
uploads()
|
||||
{
|
||||
@ -33,60 +37,31 @@ Core::NetworkAccess::~NetworkAccess()
|
||||
stop();
|
||||
}
|
||||
|
||||
void Core::NetworkAccess::fileLocalPathRequest(const QString& messageId, const QString& url)
|
||||
void Core::NetworkAccess::downladFile(const QString& url)
|
||||
{
|
||||
std::map<QString, Transfer*>::iterator itr = downloads.find(url);
|
||||
if (itr != downloads.end()) {
|
||||
Transfer* dwn = itr->second;
|
||||
std::set<QString>::const_iterator mItr = dwn->messages.find(messageId);
|
||||
if (mItr == dwn->messages.end()) {
|
||||
dwn->messages.insert(messageId);
|
||||
}
|
||||
emit downloadFileProgress(messageId, dwn->progress);
|
||||
qDebug() << "NetworkAccess received a request to download a file" << url << ", but the file is currently downloading, skipping";
|
||||
} else {
|
||||
try {
|
||||
QString path = files.getRecord(url);
|
||||
QFileInfo info(path);
|
||||
std::pair<QString, std::list<Shared::MessageInfo>> p = storage.getPath(url);
|
||||
if (p.first.size() > 0) {
|
||||
QFileInfo info(p.first);
|
||||
if (info.exists() && info.isFile()) {
|
||||
emit fileLocalPathResponse(messageId, path);
|
||||
emit downloadFileComplete(p.second, p.first);
|
||||
} else {
|
||||
files.removeRecord(url);
|
||||
emit fileLocalPathResponse(messageId, "");
|
||||
startDownload(p.second, url);
|
||||
}
|
||||
} else {
|
||||
startDownload(p.second, url);
|
||||
}
|
||||
} catch (const Archive::NotFound& e) {
|
||||
emit fileLocalPathResponse(messageId, "");
|
||||
qDebug() << "NetworkAccess received a request to download a file" << url << ", but there is now record of which message uses that file, downloading anyway";
|
||||
storage.addFile(url);
|
||||
startDownload(std::list<Shared::MessageInfo>(), url);
|
||||
} catch (const Archive::Unknown& e) {
|
||||
qDebug() << "Error requesting file path:" << e.what();
|
||||
emit fileLocalPathResponse(messageId, "");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Core::NetworkAccess::downladFileRequest(const QString& messageId, const QString& url)
|
||||
{
|
||||
std::map<QString, Transfer*>::iterator itr = downloads.find(url);
|
||||
if (itr != downloads.end()) {
|
||||
Transfer* dwn = itr->second;
|
||||
std::set<QString>::const_iterator mItr = dwn->messages.find(messageId);
|
||||
if (mItr == dwn->messages.end()) {
|
||||
dwn->messages.insert(messageId);
|
||||
}
|
||||
emit downloadFileProgress(messageId, dwn->progress);
|
||||
} else {
|
||||
try {
|
||||
QString path = files.getRecord(url);
|
||||
QFileInfo info(path);
|
||||
if (info.exists() && info.isFile()) {
|
||||
emit fileLocalPathResponse(messageId, path);
|
||||
} else {
|
||||
files.removeRecord(url);
|
||||
startDownload(messageId, url);
|
||||
}
|
||||
} catch (const Archive::NotFound& e) {
|
||||
startDownload(messageId, url);
|
||||
} catch (const Archive::Unknown& e) {
|
||||
qDebug() << "Error requesting file path:" << e.what();
|
||||
emit downloadFileError(messageId, QString("Database error: ") + e.what());
|
||||
emit loadFileError(std::list<Shared::MessageInfo>(), QString("Database error: ") + e.what(), false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -95,7 +70,7 @@ void Core::NetworkAccess::start()
|
||||
{
|
||||
if (!running) {
|
||||
manager = new QNetworkAccessManager();
|
||||
files.open();
|
||||
storage.open();
|
||||
running = true;
|
||||
}
|
||||
}
|
||||
@ -103,7 +78,7 @@ void Core::NetworkAccess::start()
|
||||
void Core::NetworkAccess::stop()
|
||||
{
|
||||
if (running) {
|
||||
files.close();
|
||||
storage.close();
|
||||
manager->deleteLater();
|
||||
manager = 0;
|
||||
running = false;
|
||||
@ -128,9 +103,7 @@ void Core::NetworkAccess::onDownloadProgress(qint64 bytesReceived, qint64 bytesT
|
||||
qreal total = bytesTotal;
|
||||
qreal progress = received/total;
|
||||
dwn->progress = progress;
|
||||
for (std::set<QString>::const_iterator mItr = dwn->messages.begin(), end = dwn->messages.end(); mItr != end; ++mItr) {
|
||||
emit downloadFileProgress(*mItr, progress);
|
||||
}
|
||||
emit loadFileProgress(dwn->messages, progress, false);
|
||||
}
|
||||
}
|
||||
|
||||
@ -146,9 +119,7 @@ void Core::NetworkAccess::onDownloadError(QNetworkReply::NetworkError code)
|
||||
if (errorText.size() > 0) {
|
||||
itr->second->success = false;
|
||||
Transfer* dwn = itr->second;
|
||||
for (std::set<QString>::const_iterator mItr = dwn->messages.begin(), end = dwn->messages.end(); mItr != end; ++mItr) {
|
||||
emit downloadFileError(*mItr, errorText);
|
||||
}
|
||||
emit loadFileError(dwn->messages, errorText, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -276,50 +247,43 @@ QString Core::NetworkAccess::getErrorText(QNetworkReply::NetworkError code)
|
||||
|
||||
void Core::NetworkAccess::onDownloadFinished()
|
||||
{
|
||||
QString path("");
|
||||
QNetworkReply* rpl = static_cast<QNetworkReply*>(sender());
|
||||
QString url = rpl->url().toString();
|
||||
std::map<QString, Transfer*>::const_iterator itr = downloads.find(url);
|
||||
if (itr == downloads.end()) {
|
||||
qDebug() << "an error downloading" << url << ": the request is done but seems like noone is waiting for it, skipping";
|
||||
qDebug() << "an error downloading" << url << ": the request is done but there is no record of it being downloaded, ignoring";
|
||||
} else {
|
||||
Transfer* dwn = itr->second;
|
||||
if (dwn->success) {
|
||||
qDebug() << "download success for" << url;
|
||||
QStringList hops = url.split("/");
|
||||
QString fileName = hops.back();
|
||||
QStringList parts = fileName.split(".");
|
||||
path = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation) + "/";
|
||||
QString suffix("");
|
||||
QStringList::const_iterator sItr = parts.begin();
|
||||
QString realName = *sItr;
|
||||
++sItr;
|
||||
for (QStringList::const_iterator sEnd = parts.end(); sItr != sEnd; ++sItr) {
|
||||
suffix += "." + (*sItr);
|
||||
}
|
||||
QString postfix("");
|
||||
QFileInfo proposedName(path + realName + postfix + suffix);
|
||||
int counter = 0;
|
||||
while (proposedName.exists()) {
|
||||
postfix = QString("(") + std::to_string(++counter).c_str() + ")";
|
||||
proposedName = QFileInfo(path + realName + postfix + suffix);
|
||||
QString jid;
|
||||
if (dwn->messages.size() > 0) {
|
||||
jid = dwn->messages.front().jid;
|
||||
}
|
||||
QString path = prepareDirectory(jid);
|
||||
if (path.size() > 0) {
|
||||
path = checkFileName(fileName, path);
|
||||
|
||||
path = proposedName.absoluteFilePath();
|
||||
QFile file(path);
|
||||
if (file.open(QIODevice::WriteOnly)) {
|
||||
file.write(dwn->reply->readAll());
|
||||
file.close();
|
||||
files.addRecord(url, path);
|
||||
storage.setPath(url, path);
|
||||
qDebug() << "file" << path << "was successfully downloaded";
|
||||
} else {
|
||||
qDebug() << "couldn't save file" << path;
|
||||
path = "";
|
||||
path = QString();
|
||||
}
|
||||
}
|
||||
|
||||
for (std::set<QString>::const_iterator mItr = dwn->messages.begin(), end = dwn->messages.end(); mItr != end; ++mItr) {
|
||||
emit fileLocalPathResponse(*mItr, path);
|
||||
if (path.size() > 0) {
|
||||
emit downloadFileComplete(dwn->messages, path);
|
||||
} else {
|
||||
//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();
|
||||
@ -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);
|
||||
dwn->reply = manager->get(req);
|
||||
connect(dwn->reply, &QNetworkReply::downloadProgress, this, &NetworkAccess::onDownloadProgress);
|
||||
@ -341,7 +305,7 @@ void Core::NetworkAccess::startDownload(const QString& messageId, const QString&
|
||||
#endif
|
||||
connect(dwn->reply, &QNetworkReply::finished, this, &NetworkAccess::onDownloadFinished);
|
||||
downloads.insert(std::make_pair(url, dwn));
|
||||
emit downloadFileProgress(messageId, 0);
|
||||
emit loadFileProgress(dwn->messages, 0, false);
|
||||
}
|
||||
|
||||
void Core::NetworkAccess::onUploadError(QNetworkReply::NetworkError code)
|
||||
@ -350,16 +314,16 @@ void Core::NetworkAccess::onUploadError(QNetworkReply::NetworkError code)
|
||||
QString url = rpl->url().toString();
|
||||
std::map<QString, Transfer*>::const_iterator itr = uploads.find(url);
|
||||
if (itr == uploads.end()) {
|
||||
qDebug() << "an error uploading" << url << ": the request is reporting an error but seems like noone is waiting for it, skipping";
|
||||
qDebug() << "an error uploading" << url << ": the request is reporting an error but there is no record of it being uploading, ignoring";
|
||||
} else {
|
||||
QString errorText = getErrorText(code);
|
||||
if (errorText.size() > 0) {
|
||||
itr->second->success = false;
|
||||
Transfer* upl = itr->second;
|
||||
for (std::set<QString>::const_iterator mItr = upl->messages.begin(), end = upl->messages.end(); mItr != end; ++mItr) {
|
||||
emit uploadFileError(*mItr, errorText);
|
||||
}
|
||||
emit loadFileError(upl->messages, errorText, true);
|
||||
}
|
||||
|
||||
//TODO deletion?
|
||||
}
|
||||
}
|
||||
|
||||
@ -369,17 +333,14 @@ void Core::NetworkAccess::onUploadFinished()
|
||||
QString url = rpl->url().toString();
|
||||
std::map<QString, Transfer*>::const_iterator itr = uploads.find(url);
|
||||
if (itr == downloads.end()) {
|
||||
qDebug() << "an error uploading" << url << ": the request is done but seems like no one is waiting for it, skipping";
|
||||
qDebug() << "an error uploading" << url << ": the request is done there is no record of it being uploading, ignoring";
|
||||
} else {
|
||||
Transfer* upl = itr->second;
|
||||
if (upl->success) {
|
||||
qDebug() << "upload success for" << url;
|
||||
files.addRecord(upl->url, upl->path);
|
||||
|
||||
for (std::set<QString>::const_iterator mItr = upl->messages.begin(), end = upl->messages.end(); mItr != end; ++mItr) {
|
||||
emit fileLocalPathResponse(*mItr, upl->path);
|
||||
emit uploadFileComplete(*mItr, upl->url);
|
||||
}
|
||||
storage.addFile(upl->messages, upl->url, upl->path);
|
||||
emit uploadFileComplete(upl->messages, upl->url);
|
||||
}
|
||||
|
||||
upl->reply->deleteLater();
|
||||
@ -403,94 +364,29 @@ void Core::NetworkAccess::onUploadProgress(qint64 bytesReceived, qint64 bytesTot
|
||||
qreal total = bytesTotal;
|
||||
qreal progress = received/total;
|
||||
upl->progress = progress;
|
||||
for (std::set<QString>::const_iterator mItr = upl->messages.begin(), end = upl->messages.end(); mItr != end; ++mItr) {
|
||||
emit uploadFileProgress(*mItr, progress);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Core::NetworkAccess::startUpload(const QString& messageId, const QString& url, const QString& path)
|
||||
{
|
||||
Transfer* upl = new Transfer({{messageId}, 0, 0, true, path, url, 0});
|
||||
QNetworkRequest req(url);
|
||||
QFile* file = new QFile(path);
|
||||
if (file->open(QIODevice::ReadOnly)) {
|
||||
upl->reply = manager->put(req, file);
|
||||
|
||||
connect(upl->reply, &QNetworkReply::uploadProgress, this, &NetworkAccess::onUploadProgress);
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
|
||||
connect(upl->reply, qOverload<QNetworkReply::NetworkError>(&QNetworkReply::errorOccurred), this, &NetworkAccess::onUploadError);
|
||||
#else
|
||||
connect(upl->reply, qOverload<QNetworkReply::NetworkError>(&QNetworkReply::error), this, &NetworkAccess::onUploadError);
|
||||
#endif
|
||||
|
||||
connect(upl->reply, &QNetworkReply::finished, this, &NetworkAccess::onUploadFinished);
|
||||
uploads.insert(std::make_pair(url, upl));
|
||||
emit downloadFileProgress(messageId, 0);
|
||||
} else {
|
||||
qDebug() << "couldn't upload file" << path;
|
||||
emit uploadFileError(messageId, "Error opening file");
|
||||
delete file;
|
||||
}
|
||||
}
|
||||
|
||||
void Core::NetworkAccess::uploadFileRequest(const QString& messageId, const QString& url, const QString& path)
|
||||
{
|
||||
std::map<QString, Transfer*>::iterator itr = uploads.find(url);
|
||||
if (itr != uploads.end()) {
|
||||
Transfer* upl = itr->second;
|
||||
std::set<QString>::const_iterator mItr = upl->messages.find(messageId);
|
||||
if (mItr == upl->messages.end()) {
|
||||
upl->messages.insert(messageId);
|
||||
}
|
||||
emit uploadFileProgress(messageId, upl->progress);
|
||||
} else {
|
||||
try {
|
||||
QString ePath = files.getRecord(url);
|
||||
if (ePath == path) {
|
||||
QFileInfo info(path);
|
||||
if (info.exists() && info.isFile()) {
|
||||
emit fileLocalPathResponse(messageId, path);
|
||||
} else {
|
||||
files.removeRecord(url);
|
||||
startUpload(messageId, url, path);
|
||||
}
|
||||
} else {
|
||||
QFileInfo info(path);
|
||||
if (info.exists() && info.isFile()) {
|
||||
files.changeRecord(url, path);
|
||||
emit fileLocalPathResponse(messageId, path);
|
||||
} else {
|
||||
files.removeRecord(url);
|
||||
startUpload(messageId, url, path);
|
||||
}
|
||||
}
|
||||
} catch (const Archive::NotFound& e) {
|
||||
startUpload(messageId, url, path);
|
||||
} catch (const Archive::Unknown& e) {
|
||||
qDebug() << "Error requesting file path on upload:" << e.what();
|
||||
emit uploadFileError(messageId, QString("Database error: ") + e.what());
|
||||
}
|
||||
emit loadFileProgress(upl->messages, progress, true);
|
||||
}
|
||||
}
|
||||
|
||||
QString Core::NetworkAccess::getFileRemoteUrl(const QString& path)
|
||||
{
|
||||
return ""; //TODO this is a way not to upload some file more then 1 time, here I'm supposed to return that file GET url
|
||||
QString p;
|
||||
|
||||
try {
|
||||
p = storage.getUrl(path);
|
||||
} catch (const Archive::NotFound& err) {
|
||||
|
||||
} catch (...) {
|
||||
throw;
|
||||
}
|
||||
|
||||
bool Core::NetworkAccess::isUploading(const QString& path, const QString& messageId)
|
||||
{
|
||||
return false; //TODO this is a way to avoid parallel uploading of the same files by different chats
|
||||
// message is is supposed to be added to the uploading messageids list
|
||||
// the result should be true if there was an uploading file with this path
|
||||
// message id can be empty, then it's just to check and not to add
|
||||
return p;
|
||||
}
|
||||
|
||||
void Core::NetworkAccess::uploadFile(const QString& messageId, const QString& path, const QUrl& put, const QUrl& get, const QMap<QString, QString> headers)
|
||||
void Core::NetworkAccess::uploadFile(const Shared::MessageInfo& info, const QString& path, const QUrl& put, const QUrl& get, const QMap<QString, QString> headers)
|
||||
{
|
||||
QFile* file = new QFile(path);
|
||||
Transfer* upl = new Transfer({{messageId}, 0, 0, true, path, get.toString(), file});
|
||||
Transfer* upl = new Transfer({{info}, 0, 0, true, path, get.toString(), file});
|
||||
QNetworkRequest req(put);
|
||||
for (QMap<QString, QString>::const_iterator itr = headers.begin(), end = headers.end(); itr != end; itr++) {
|
||||
req.setRawHeader(itr.key().toUtf8(), itr.value().toUtf8());
|
||||
@ -506,10 +402,99 @@ void Core::NetworkAccess::uploadFile(const QString& messageId, const QString& pa
|
||||
#endif
|
||||
connect(upl->reply, &QNetworkReply::finished, this, &NetworkAccess::onUploadFinished);
|
||||
uploads.insert(std::make_pair(put.toString(), upl));
|
||||
emit downloadFileProgress(messageId, 0);
|
||||
emit loadFileProgress(upl->messages, 0, true);
|
||||
} else {
|
||||
qDebug() << "couldn't upload file" << path;
|
||||
emit uploadFileError(messageId, "Error opening file");
|
||||
emit loadFileError(upl->messages, "Error opening file", true);
|
||||
delete file;
|
||||
delete upl;
|
||||
}
|
||||
}
|
||||
|
||||
void Core::NetworkAccess::registerFile(const QString& url, const QString& account, const QString& jid, const QString& id)
|
||||
{
|
||||
storage.addFile(url, account, jid, id);
|
||||
std::map<QString, Transfer*>::iterator itr = downloads.find(url);
|
||||
if (itr != downloads.end()) {
|
||||
itr->second->messages.emplace_back(account, jid, id); //TODO notification is going to happen the next tick, is that okay?
|
||||
}
|
||||
}
|
||||
|
||||
void Core::NetworkAccess::registerFile(const QString& url, const QString& path, const QString& account, const QString& jid, const QString& id)
|
||||
{
|
||||
storage.addFile(url, path, account, jid, id);
|
||||
}
|
||||
|
||||
bool Core::NetworkAccess::checkAndAddToUploading(const QString& acc, const QString& jid, const QString id, const QString path)
|
||||
{
|
||||
for (const std::pair<const QString, Transfer*>& pair : uploads) {
|
||||
Transfer* info = pair.second;
|
||||
if (pair.second->path == path) {
|
||||
std::list<Shared::MessageInfo>& messages = info->messages;
|
||||
bool dup = false;
|
||||
for (const Shared::MessageInfo& info : messages) {
|
||||
if (info.account == acc && info.jid == jid && info.messageId == id) {
|
||||
dup = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!dup) {
|
||||
info->messages.emplace_back(acc, jid, id); //TODO notification is going to happen the next tick, is that okay?
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
QString Core::NetworkAccess::prepareDirectory(const QString& jid)
|
||||
{
|
||||
QString path = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation);
|
||||
path += "/" + QApplication::applicationName();
|
||||
if (jid.size() > 0) {
|
||||
path += "/" + jid;
|
||||
}
|
||||
QDir location(path);
|
||||
|
||||
if (!location.exists()) {
|
||||
bool res = location.mkpath(path);
|
||||
if (!res) {
|
||||
return "";
|
||||
} else {
|
||||
return path;
|
||||
}
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
QString Core::NetworkAccess::checkFileName(const QString& name, const QString& path)
|
||||
{
|
||||
QStringList parts = name.split(".");
|
||||
QString suffix("");
|
||||
QStringList::const_iterator sItr = parts.begin();
|
||||
QString realName = *sItr;
|
||||
++sItr;
|
||||
for (QStringList::const_iterator sEnd = parts.end(); sItr != sEnd; ++sItr) {
|
||||
suffix += "." + (*sItr);
|
||||
}
|
||||
QString postfix("");
|
||||
QFileInfo proposedName(path + "/" + realName + suffix);
|
||||
int counter = 0;
|
||||
while (proposedName.exists()) {
|
||||
QString count = QString("(") + std::to_string(++counter).c_str() + ")";
|
||||
proposedName = QFileInfo(path + "/" + realName + count + suffix);
|
||||
}
|
||||
|
||||
return proposedName.absoluteFilePath();
|
||||
}
|
||||
|
||||
QString Core::NetworkAccess::addMessageAndCheckForPath(const QString& url, const QString& account, const QString& jid, const QString& id)
|
||||
{
|
||||
return storage.addMessageAndCheckForPath(url, account, jid, id);
|
||||
}
|
||||
|
||||
std::list<Shared::MessageInfo> Core::NetworkAccess::reportPathInvalid(const QString& path)
|
||||
{
|
||||
return storage.deletedFile(path);
|
||||
}
|
||||
|
@ -29,13 +29,15 @@
|
||||
|
||||
#include <set>
|
||||
|
||||
#include "storage.h"
|
||||
#include "urlstorage.h"
|
||||
|
||||
namespace Core {
|
||||
|
||||
/**
|
||||
* @todo write docs
|
||||
*/
|
||||
|
||||
//TODO Need to describe how to get rid of records when file is no longer reachable;
|
||||
class NetworkAccess : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
@ -48,26 +50,27 @@ public:
|
||||
void stop();
|
||||
|
||||
QString getFileRemoteUrl(const QString& path);
|
||||
bool isUploading(const QString& path, const QString& messageId = "");
|
||||
void uploadFile(const QString& messageId, const QString& path, const QUrl& put, const QUrl& get, const QMap<QString, QString> headers);
|
||||
QString addMessageAndCheckForPath(const QString& url, const QString& account, const QString& jid, const QString& id);
|
||||
void uploadFile(const Shared::MessageInfo& info, const QString& path, const QUrl& put, const QUrl& get, const QMap<QString, QString> headers);
|
||||
bool checkAndAddToUploading(const QString& acc, const QString& jid, const QString id, const QString path);
|
||||
std::list<Shared::MessageInfo> reportPathInvalid(const QString& path);
|
||||
|
||||
signals:
|
||||
void fileLocalPathResponse(const QString& messageId, const QString& path);
|
||||
void downloadFileProgress(const QString& messageId, qreal value);
|
||||
void downloadFileError(const QString& messageId, const QString& path);
|
||||
void uploadFileProgress(const QString& messageId, qreal value);
|
||||
void uploadFileError(const QString& messageId, const QString& path);
|
||||
void uploadFileComplete(const QString& messageId, const QString& url);
|
||||
void loadFileProgress(const std::list<Shared::MessageInfo>& msgs, qreal value, bool up);
|
||||
void loadFileError(const std::list<Shared::MessageInfo>& msgs, const QString& text, bool up);
|
||||
void uploadFileComplete(const std::list<Shared::MessageInfo>& msgs, const QString& url);
|
||||
void downloadFileComplete(const std::list<Shared::MessageInfo>& msgs, const QString& path);
|
||||
|
||||
public slots:
|
||||
void fileLocalPathRequest(const QString& messageId, const QString& url);
|
||||
void downladFileRequest(const QString& messageId, const QString& url);
|
||||
void uploadFileRequest(const QString& messageId, const QString& url, const QString& path);
|
||||
void downladFile(const QString& url);
|
||||
void registerFile(const QString& url, const QString& account, const QString& jid, const QString& id);
|
||||
void registerFile(const QString& url, const QString& path, const QString& account, const QString& jid, const QString& id);
|
||||
|
||||
private:
|
||||
void startDownload(const QString& messageId, const QString& url);
|
||||
void startUpload(const QString& messageId, const QString& url, const QString& path);
|
||||
void startDownload(const std::list<Shared::MessageInfo>& msgs, const QString& url);
|
||||
QString getErrorText(QNetworkReply::NetworkError code);
|
||||
QString prepareDirectory(const QString& jid);
|
||||
QString checkFileName(const QString& name, const QString& path);
|
||||
|
||||
private slots:
|
||||
void onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal);
|
||||
@ -80,12 +83,12 @@ private slots:
|
||||
private:
|
||||
bool running;
|
||||
QNetworkAccessManager* manager;
|
||||
Storage files;
|
||||
UrlStorage storage;
|
||||
std::map<QString, Transfer*> downloads;
|
||||
std::map<QString, Transfer*> uploads;
|
||||
|
||||
struct Transfer {
|
||||
std::set<QString> messages;
|
||||
std::list<Shared::MessageInfo> messages;
|
||||
qreal progress;
|
||||
QNetworkReply* reply;
|
||||
bool success;
|
||||
|
@ -1,4 +1,4 @@
|
||||
cmake_minimum_required(VERSION 3.0)
|
||||
cmake_minimum_required(VERSION 3.3)
|
||||
project(pse)
|
||||
|
||||
if (WITH_KWALLET)
|
||||
|
@ -122,7 +122,22 @@ void Core::RosterItem::nextRequest()
|
||||
{
|
||||
if (syncronizing) {
|
||||
if (requestedCount != -1) {
|
||||
emit historyResponse(responseCache);
|
||||
bool last = false;
|
||||
if (archiveState == beginning || archiveState == complete) {
|
||||
QString firstId = archive->oldestId();
|
||||
if (responseCache.size() == 0) {
|
||||
if (requestedBefore == firstId) {
|
||||
last = true;
|
||||
}
|
||||
} else {
|
||||
if (responseCache.front().getId() == firstId) {
|
||||
last = true;
|
||||
}
|
||||
}
|
||||
} else if (archiveState == empty && responseCache.size() == 0) {
|
||||
last = true;
|
||||
}
|
||||
emit historyResponse(responseCache, last);
|
||||
}
|
||||
}
|
||||
if (requestCache.size() > 0) {
|
||||
@ -360,6 +375,11 @@ void Core::RosterItem::flushMessagesToArchive(bool finished, const QString& firs
|
||||
archiveState = complete;
|
||||
archive->setFromTheBeginning(true);
|
||||
}
|
||||
if (added == 0 && wasEmpty) {
|
||||
archiveState = empty;
|
||||
nextRequest();
|
||||
break;
|
||||
}
|
||||
if (requestedCount != -1) {
|
||||
QString before;
|
||||
if (responseCache.size() > 0) {
|
||||
@ -378,7 +398,7 @@ void Core::RosterItem::flushMessagesToArchive(bool finished, const QString& firs
|
||||
} catch (const Archive::Empty& e) {
|
||||
|
||||
}
|
||||
if (!found || requestedCount > responseCache.size()) {
|
||||
if (!found || requestedCount > int(responseCache.size())) {
|
||||
if (archiveState == complete) {
|
||||
nextRequest();
|
||||
} else {
|
||||
@ -529,7 +549,7 @@ void Core::RosterItem::clearArchiveRequests()
|
||||
requestedBefore = "";
|
||||
for (const std::pair<int, QString>& pair : requestCache) {
|
||||
if (pair.first != -1) {
|
||||
emit historyResponse(responseCache); //just to notify those who still waits with whatever happened to be left in caches yet
|
||||
emit historyResponse(responseCache, false); //just to notify those who still waits with whatever happened to be left in caches yet
|
||||
}
|
||||
responseCache.clear();
|
||||
}
|
||||
@ -549,3 +569,20 @@ void Core::RosterItem::downgradeDatabaseState()
|
||||
archiveState = ArchiveState::chunk;
|
||||
}
|
||||
}
|
||||
|
||||
Shared::Message Core::RosterItem::getMessage(const QString& id)
|
||||
{
|
||||
for (const Shared::Message& msg : appendCache) {
|
||||
if (msg.getId() == id) {
|
||||
return msg;
|
||||
}
|
||||
}
|
||||
|
||||
for (Shared::Message& msg : hisoryCache) {
|
||||
if (msg.getId() == id) {
|
||||
return msg;
|
||||
}
|
||||
}
|
||||
|
||||
return archive->getElement(id);
|
||||
}
|
||||
|
@ -78,10 +78,12 @@ public:
|
||||
void clearArchiveRequests();
|
||||
void downgradeDatabaseState();
|
||||
|
||||
Shared::Message getMessage(const QString& id);
|
||||
|
||||
signals:
|
||||
void nameChanged(const QString& name);
|
||||
void subscriptionStateChanged(Shared::SubscriptionState state);
|
||||
void historyResponse(const std::list<Shared::Message>& messages);
|
||||
void historyResponse(const std::list<Shared::Message>& messages, bool last);
|
||||
void needHistory(const QString& before, const QString& after, const QDateTime& afterTime = QDateTime());
|
||||
void avatarChanged(Shared::Avatar, const QString& path);
|
||||
void requestVCard(const QString& jid);
|
||||
|
@ -32,11 +32,10 @@ Core::Squawk::Squawk(QObject* parent):
|
||||
,kwallet()
|
||||
#endif
|
||||
{
|
||||
connect(&network, &NetworkAccess::fileLocalPathResponse, this, &Squawk::fileLocalPathResponse);
|
||||
connect(&network, &NetworkAccess::downloadFileProgress, this, &Squawk::downloadFileProgress);
|
||||
connect(&network, &NetworkAccess::downloadFileError, this, &Squawk::downloadFileError);
|
||||
connect(&network, &NetworkAccess::uploadFileProgress, this, &Squawk::uploadFileProgress);
|
||||
connect(&network, &NetworkAccess::uploadFileError, this, &Squawk::uploadFileError);
|
||||
connect(&network, &NetworkAccess::loadFileProgress, this, &Squawk::fileProgress);
|
||||
connect(&network, &NetworkAccess::loadFileError, this, &Squawk::fileError);
|
||||
connect(&network, &NetworkAccess::downloadFileComplete, this, &Squawk::fileDownloadComplete);
|
||||
connect(&network, &NetworkAccess::uploadFileComplete, this, &Squawk::fileUploadComplete);
|
||||
|
||||
#ifdef WITH_KWALLET
|
||||
if (kwallet.supportState() == PSE::KWallet::success) {
|
||||
@ -168,7 +167,7 @@ void Core::Squawk::addAccount(
|
||||
|
||||
connect(acc, &Account::receivedVCard, this, &Squawk::responseVCard);
|
||||
|
||||
connect(acc, &Account::uploadFileError, this, &Squawk::uploadFileError);
|
||||
connect(acc, &Account::uploadFileError, this, &Squawk::onAccountUploadFileError);
|
||||
|
||||
QMap<QString, QVariant> map = {
|
||||
{"login", login},
|
||||
@ -336,17 +335,6 @@ void Core::Squawk::sendMessage(const QString& account, const Shared::Message& da
|
||||
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)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
void Core::Squawk::onAccountResponseArchive(const QString& jid, const std::list<Shared::Message>& list)
|
||||
void Core::Squawk::onAccountResponseArchive(const QString& jid, const std::list<Shared::Message>& list, bool last)
|
||||
{
|
||||
Account* acc = static_cast<Account*>(sender());
|
||||
emit responseArchive(acc->getName(), jid, list);
|
||||
emit responseArchive(acc->getName(), jid, list, last);
|
||||
}
|
||||
|
||||
void Core::Squawk::modifyAccountRequest(const QString& name, const QMap<QString, QVariant>& map)
|
||||
@ -604,14 +592,9 @@ void Core::Squawk::addRoomRequest(const QString& account, const QString& jid, co
|
||||
itr->second->addRoomRequest(jid, nick, password, autoJoin);
|
||||
}
|
||||
|
||||
void Core::Squawk::fileLocalPathRequest(const QString& messageId, const QString& url)
|
||||
void Core::Squawk::fileDownloadRequest(const QString& url)
|
||||
{
|
||||
network.fileLocalPathRequest(messageId, url);
|
||||
}
|
||||
|
||||
void Core::Squawk::downloadFileRequest(const QString& messageId, const QString& url)
|
||||
{
|
||||
network.downladFileRequest(messageId, url);
|
||||
network.downladFile(url);
|
||||
}
|
||||
|
||||
void Core::Squawk::addContactToGroupRequest(const QString& account, const QString& jid, const QString& groupName)
|
||||
@ -762,3 +745,26 @@ void Core::Squawk::onWalletResponsePassword(const QString& login, const QString&
|
||||
emit changeAccount(login, {{"password", password}});
|
||||
accountReady();
|
||||
}
|
||||
|
||||
void Core::Squawk::onAccountUploadFileError(const QString& jid, const QString id, const QString& errorText)
|
||||
{
|
||||
Account* acc = static_cast<Account*>(sender());
|
||||
emit fileError({{acc->getName(), jid, id}}, errorText, true);
|
||||
}
|
||||
|
||||
void Core::Squawk::onLocalPathInvalid(const QString& path)
|
||||
{
|
||||
std::list<Shared::MessageInfo> list = network.reportPathInvalid(path);
|
||||
|
||||
QMap<QString, QVariant> data({
|
||||
{"attachPath", ""}
|
||||
});
|
||||
for (const Shared::MessageInfo& info : list) {
|
||||
AccountsMap::const_iterator itr = amap.find(info.account);
|
||||
if (itr != amap.end()) {
|
||||
itr->second->requestChangeMessage(info.jid, info.messageId, data);
|
||||
} else {
|
||||
qDebug() << "Reacting on failure to reach file" << path << "there was an attempt to change message in account" << info.account << "which doesn't exist, skipping";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -51,31 +51,39 @@ public:
|
||||
signals:
|
||||
void quit();
|
||||
void ready();
|
||||
|
||||
void newAccount(const QMap<QString, QVariant>&);
|
||||
void changeAccount(const QString& account, const QMap<QString, QVariant>& data);
|
||||
void removeAccount(const QString& account);
|
||||
|
||||
void addGroup(const QString& account, const QString& name);
|
||||
void removeGroup(const QString& account, const QString& name);
|
||||
|
||||
void addContact(const QString& account, const QString& jid, const QString& group, const QMap<QString, QVariant>& data);
|
||||
void removeContact(const QString& account, const QString& jid);
|
||||
void removeContact(const QString& account, const QString& jid, const QString& group);
|
||||
void changeContact(const QString& account, const QString& jid, const QMap<QString, QVariant>& data);
|
||||
|
||||
void addPresence(const QString& account, const QString& jid, const QString& name, const QMap<QString, QVariant>& data);
|
||||
void removePresence(const QString& account, const QString& jid, const QString& name);
|
||||
|
||||
void stateChanged(Shared::Availability state);
|
||||
|
||||
void accountMessage(const QString& account, const Shared::Message& data);
|
||||
void responseArchive(const QString& account, const QString& jid, const std::list<Shared::Message>& list);
|
||||
void responseArchive(const QString& account, const QString& jid, const std::list<Shared::Message>& list, bool last);
|
||||
|
||||
void addRoom(const QString& account, const QString jid, const QMap<QString, QVariant>& data);
|
||||
void changeRoom(const QString& account, const QString jid, const QMap<QString, QVariant>& data);
|
||||
void removeRoom(const QString& account, const QString jid);
|
||||
void addRoomParticipant(const QString& account, const QString& jid, const QString& name, const QMap<QString, QVariant>& data);
|
||||
void changeRoomParticipant(const QString& account, const QString& jid, const QString& name, const QMap<QString, QVariant>& data);
|
||||
void removeRoomParticipant(const QString& account, const QString& jid, const QString& name);
|
||||
void fileLocalPathResponse(const QString& messageId, const QString& path);
|
||||
void downloadFileError(const QString& messageId, const QString& error);
|
||||
void downloadFileProgress(const QString& messageId, qreal value);
|
||||
void uploadFileError(const QString& messageId, const QString& error);
|
||||
void uploadFileProgress(const QString& messageId, qreal value);
|
||||
|
||||
void fileError(const std::list<Shared::MessageInfo> msgs, const QString& error, bool up);
|
||||
void fileProgress(const std::list<Shared::MessageInfo> msgs, qreal value, bool up);
|
||||
void fileDownloadComplete(const std::list<Shared::MessageInfo> msgs, const QString& path);
|
||||
void fileUploadComplete(const std::list<Shared::MessageInfo> msgs, const QString& path);
|
||||
|
||||
void responseVCard(const QString& jid, const Shared::VCard& card);
|
||||
void changeMessage(const QString& account, const QString& jid, const QString& id, const QMap<QString, QVariant>& data);
|
||||
void requestPassword(const QString& account);
|
||||
@ -83,15 +91,18 @@ signals:
|
||||
public slots:
|
||||
void start();
|
||||
void stop();
|
||||
|
||||
void newAccountRequest(const QMap<QString, QVariant>& map);
|
||||
void modifyAccountRequest(const QString& name, const QMap<QString, QVariant>& map);
|
||||
void removeAccountRequest(const QString& name);
|
||||
void connectAccount(const QString& account);
|
||||
void disconnectAccount(const QString& account);
|
||||
|
||||
void changeState(Shared::Availability state);
|
||||
|
||||
void sendMessage(const QString& account, const Shared::Message& data);
|
||||
void sendMessage(const QString& account, const Shared::Message& data, const QString& path);
|
||||
void requestArchive(const QString& account, const QString& jid, int count, const QString& before);
|
||||
|
||||
void subscribeContact(const QString& account, const QString& jid, const QString& reason);
|
||||
void unsubscribeContact(const QString& account, const QString& jid, const QString& reason);
|
||||
void addContactToGroupRequest(const QString& account, const QString& jid, const QString& groupName);
|
||||
@ -99,15 +110,18 @@ public slots:
|
||||
void removeContactRequest(const QString& account, const QString& jid);
|
||||
void renameContactRequest(const QString& account, const QString& jid, const QString& newName);
|
||||
void addContactRequest(const QString& account, const QString& jid, const QString& name, const QSet<QString>& groups);
|
||||
|
||||
void setRoomJoined(const QString& account, const QString& jid, bool joined);
|
||||
void setRoomAutoJoin(const QString& account, const QString& jid, bool joined);
|
||||
void addRoomRequest(const QString& account, const QString& jid, const QString& nick, const QString& password, bool autoJoin);
|
||||
void removeRoomRequest(const QString& account, const QString& jid);
|
||||
void fileLocalPathRequest(const QString& messageId, const QString& url);
|
||||
void downloadFileRequest(const QString& messageId, const QString& url);
|
||||
|
||||
void fileDownloadRequest(const QString& url);
|
||||
|
||||
void requestVCard(const QString& account, const QString& jid);
|
||||
void uploadVCard(const QString& account, const Shared::VCard& card);
|
||||
void responsePassword(const QString& account, const QString& password);
|
||||
void onLocalPathInvalid(const QString& path);
|
||||
|
||||
private:
|
||||
typedef std::deque<Account*> Accounts;
|
||||
@ -146,7 +160,7 @@ private slots:
|
||||
void onAccountAddPresence(const QString& jid, const QString& name, const QMap<QString, QVariant>& data);
|
||||
void onAccountRemovePresence(const QString& jid, const QString& name);
|
||||
void onAccountMessage(const Shared::Message& data);
|
||||
void onAccountResponseArchive(const QString& jid, const std::list<Shared::Message>& list);
|
||||
void onAccountResponseArchive(const QString& jid, const std::list<Shared::Message>& list, bool last);
|
||||
void onAccountAddRoom(const QString jid, const QMap<QString, QVariant>& data);
|
||||
void onAccountChangeRoom(const QString jid, const QMap<QString, QVariant>& data);
|
||||
void onAccountRemoveRoom(const QString jid);
|
||||
@ -155,6 +169,8 @@ private slots:
|
||||
void onAccountRemoveRoomPresence(const QString& jid, const QString& nick);
|
||||
void onAccountChangeMessage(const QString& jid, const QString& id, const QMap<QString, QVariant>& data);
|
||||
|
||||
void onAccountUploadFileError(const QString& jid, const QString id, const QString& errorText);
|
||||
|
||||
void onWalletOpened(bool success);
|
||||
void onWalletResponsePassword(const QString& login, const QString& password);
|
||||
void onWalletRejectPassword(const QString& login);
|
||||
|
491
core/urlstorage.cpp
Normal file
@ -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
|
21
main.cpp
@ -20,6 +20,7 @@
|
||||
#include "core/squawk.h"
|
||||
#include "signalcatcher.h"
|
||||
#include "shared/global.h"
|
||||
#include "shared/messageinfo.h"
|
||||
#include <QtWidgets/QApplication>
|
||||
#include <QtCore/QThread>
|
||||
#include <QtCore/QObject>
|
||||
@ -31,8 +32,10 @@
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
qRegisterMetaType<Shared::Message>("Shared::Message");
|
||||
qRegisterMetaType<Shared::MessageInfo>("Shared::MessageInfo");
|
||||
qRegisterMetaType<Shared::VCard>("Shared::VCard");
|
||||
qRegisterMetaType<std::list<Shared::Message>>("std::list<Shared::Message>");
|
||||
qRegisterMetaType<std::list<Shared::MessageInfo>>("std::list<Shared::MessageInfo>");
|
||||
qRegisterMetaType<QSet<QString>>("QSet<QString>");
|
||||
qRegisterMetaType<Shared::ConnectionState>("Shared::ConnectionState");
|
||||
qRegisterMetaType<Shared::Availability>("Shared::Availability");
|
||||
@ -96,10 +99,7 @@ int main(int argc, char *argv[])
|
||||
QObject::connect(&w, &Squawk::connectAccount, squawk, &Core::Squawk::connectAccount);
|
||||
QObject::connect(&w, &Squawk::disconnectAccount, squawk, &Core::Squawk::disconnectAccount);
|
||||
QObject::connect(&w, &Squawk::changeState, squawk, &Core::Squawk::changeState);
|
||||
QObject::connect(&w, qOverload<const QString&, const Shared::Message&>(&Squawk::sendMessage),
|
||||
squawk, qOverload<const QString&, const Shared::Message&>(&Core::Squawk::sendMessage));
|
||||
QObject::connect(&w, qOverload<const QString&, const Shared::Message&, const QString&>(&Squawk::sendMessage),
|
||||
squawk, qOverload<const QString&, const Shared::Message&, const QString&>(&Core::Squawk::sendMessage));
|
||||
QObject::connect(&w, &Squawk::sendMessage, squawk,&Core::Squawk::sendMessage);
|
||||
QObject::connect(&w, &Squawk::requestArchive, squawk, &Core::Squawk::requestArchive);
|
||||
QObject::connect(&w, &Squawk::subscribeContact, squawk, &Core::Squawk::subscribeContact);
|
||||
QObject::connect(&w, &Squawk::unsubscribeContact, squawk, &Core::Squawk::unsubscribeContact);
|
||||
@ -109,14 +109,14 @@ int main(int argc, char *argv[])
|
||||
QObject::connect(&w, &Squawk::setRoomAutoJoin, squawk, &Core::Squawk::setRoomAutoJoin);
|
||||
QObject::connect(&w, &Squawk::removeRoomRequest, squawk, &Core::Squawk::removeRoomRequest);
|
||||
QObject::connect(&w, &Squawk::addRoomRequest, squawk, &Core::Squawk::addRoomRequest);
|
||||
QObject::connect(&w, &Squawk::fileLocalPathRequest, squawk, &Core::Squawk::fileLocalPathRequest);
|
||||
QObject::connect(&w, &Squawk::downloadFileRequest, squawk, &Core::Squawk::downloadFileRequest);
|
||||
QObject::connect(&w, &Squawk::fileDownloadRequest, squawk, &Core::Squawk::fileDownloadRequest);
|
||||
QObject::connect(&w, &Squawk::addContactToGroupRequest, squawk, &Core::Squawk::addContactToGroupRequest);
|
||||
QObject::connect(&w, &Squawk::removeContactFromGroupRequest, squawk, &Core::Squawk::removeContactFromGroupRequest);
|
||||
QObject::connect(&w, &Squawk::renameContactRequest, squawk, &Core::Squawk::renameContactRequest);
|
||||
QObject::connect(&w, &Squawk::requestVCard, squawk, &Core::Squawk::requestVCard);
|
||||
QObject::connect(&w, &Squawk::uploadVCard, squawk, &Core::Squawk::uploadVCard);
|
||||
QObject::connect(&w, &Squawk::responsePassword, squawk, &Core::Squawk::responsePassword);
|
||||
QObject::connect(&w, &Squawk::localPathInvalid, squawk, &Core::Squawk::onLocalPathInvalid);
|
||||
|
||||
QObject::connect(squawk, &Core::Squawk::newAccount, &w, &Squawk::newAccount);
|
||||
QObject::connect(squawk, &Core::Squawk::addContact, &w, &Squawk::addContact);
|
||||
@ -141,11 +141,10 @@ int main(int argc, char *argv[])
|
||||
QObject::connect(squawk, &Core::Squawk::addRoomParticipant, &w, &Squawk::addRoomParticipant);
|
||||
QObject::connect(squawk, &Core::Squawk::changeRoomParticipant, &w, &Squawk::changeRoomParticipant);
|
||||
QObject::connect(squawk, &Core::Squawk::removeRoomParticipant, &w, &Squawk::removeRoomParticipant);
|
||||
QObject::connect(squawk, &Core::Squawk::fileLocalPathResponse, &w, &Squawk::fileLocalPathResponse);
|
||||
QObject::connect(squawk, &Core::Squawk::downloadFileProgress, &w, &Squawk::fileProgress);
|
||||
QObject::connect(squawk, &Core::Squawk::downloadFileError, &w, &Squawk::fileError);
|
||||
QObject::connect(squawk, &Core::Squawk::uploadFileProgress, &w, &Squawk::fileProgress);
|
||||
QObject::connect(squawk, &Core::Squawk::uploadFileError, &w, &Squawk::fileError);
|
||||
QObject::connect(squawk, &Core::Squawk::fileDownloadComplete, &w, &Squawk::fileDownloadComplete);
|
||||
QObject::connect(squawk, &Core::Squawk::fileUploadComplete, &w, &Squawk::fileUploadComplete);
|
||||
QObject::connect(squawk, &Core::Squawk::fileProgress, &w, &Squawk::fileProgress);
|
||||
QObject::connect(squawk, &Core::Squawk::fileError, &w, &Squawk::fileError);
|
||||
QObject::connect(squawk, &Core::Squawk::responseVCard, &w, &Squawk::responseVCard);
|
||||
QObject::connect(squawk, &Core::Squawk::requestPassword, &w, &Squawk::requestPassword);
|
||||
QObject::connect(squawk, &Core::Squawk::ready, &w, &Squawk::readSettings);
|
||||
|
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/unfavorite.svg</file>
|
||||
<file>images/fallback/dark/big/add.svg</file>
|
||||
<file>images/fallback/dark/big/folder.svg</file>
|
||||
<file>images/fallback/dark/big/document-preview.svg</file>
|
||||
|
||||
|
||||
<file>images/fallback/dark/small/absent.svg</file>
|
||||
@ -80,6 +82,8 @@
|
||||
<file>images/fallback/dark/small/favorite.svg</file>
|
||||
<file>images/fallback/dark/small/unfavorite.svg</file>
|
||||
<file>images/fallback/dark/small/add.svg</file>
|
||||
<file>images/fallback/dark/small/folder.svg</file>
|
||||
<file>images/fallback/dark/small/document-preview.svg</file>
|
||||
|
||||
|
||||
<file>images/fallback/light/big/absent.svg</file>
|
||||
@ -120,6 +124,8 @@
|
||||
<file>images/fallback/light/big/favorite.svg</file>
|
||||
<file>images/fallback/light/big/unfavorite.svg</file>
|
||||
<file>images/fallback/light/big/add.svg</file>
|
||||
<file>images/fallback/light/big/folder.svg</file>
|
||||
<file>images/fallback/light/big/document-preview.svg</file>
|
||||
|
||||
|
||||
<file>images/fallback/light/small/absent.svg</file>
|
||||
@ -160,5 +166,7 @@
|
||||
<file>images/fallback/light/small/favorite.svg</file>
|
||||
<file>images/fallback/light/small/unfavorite.svg</file>
|
||||
<file>images/fallback/light/small/add.svg</file>
|
||||
<file>images/fallback/light/small/folder.svg</file>
|
||||
<file>images/fallback/light/small/document-preview.svg</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
1
shared.h
@ -25,5 +25,6 @@
|
||||
#include "shared/message.h"
|
||||
#include "shared/vcard.h"
|
||||
#include "shared/global.h"
|
||||
#include "shared/messageinfo.h"
|
||||
|
||||
#endif // SHARED_H
|
||||
|
@ -23,6 +23,11 @@
|
||||
Shared::Global* Shared::Global::instance = 0;
|
||||
const std::set<QString> Shared::Global::supportedImagesExts = {"png", "jpg", "webp", "jpeg", "gif", "svg"};
|
||||
|
||||
#ifdef WITH_KIO
|
||||
QLibrary Shared::Global::openFileManagerWindowJob("openFileManagerWindowJob");
|
||||
Shared::Global::HighlightInFileManager Shared::Global::hfm = 0;
|
||||
#endif
|
||||
|
||||
Shared::Global::Global():
|
||||
availability({
|
||||
tr("Online", "Availability"),
|
||||
@ -80,15 +85,60 @@ Shared::Global::Global():
|
||||
tr("Your password is going to be stored in KDE wallet storage (KWallet). You're going to be queried for permissions", "AccountPasswordDescription")
|
||||
}),
|
||||
pluginSupport({
|
||||
{"KWallet", false}
|
||||
})
|
||||
{"KWallet", false},
|
||||
{"openFileManagerWindowJob", false}
|
||||
}),
|
||||
fileCache()
|
||||
{
|
||||
if (instance != 0) {
|
||||
throw 551;
|
||||
}
|
||||
|
||||
instance = this;
|
||||
|
||||
#ifdef WITH_KIO
|
||||
openFileManagerWindowJob.load();
|
||||
if (openFileManagerWindowJob.isLoaded()) {
|
||||
hfm = (HighlightInFileManager) openFileManagerWindowJob.resolve("highlightInFileManager");
|
||||
if (hfm) {
|
||||
setSupported("openFileManagerWindowJob", true);
|
||||
qDebug() << "KIO::OpenFileManagerWindow support enabled";
|
||||
} else {
|
||||
qDebug() << "KIO::OpenFileManagerWindow support disabled: couldn't resolve required methods in the library";
|
||||
}
|
||||
} else {
|
||||
qDebug() << "KIO::OpenFileManagerWindow support disabled: couldn't load the library" << openFileManagerWindowJob.errorString();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
@ -152,6 +202,69 @@ QString Shared::Global::getDescription(Shared::AccountPassword ap)
|
||||
return instance->accountPasswordDescription[static_cast<int>(ap)];
|
||||
}
|
||||
|
||||
|
||||
static const QStringList query = {"query", "default", "inode/directory"};
|
||||
static const QRegularExpression dolphinReg("[Dd]olphin");
|
||||
static const QRegularExpression nautilusReg("[Nn]autilus");
|
||||
static const QRegularExpression cajaReg("[Cc]aja");
|
||||
static const QRegularExpression nemoReg("[Nn]emo");
|
||||
static const QRegularExpression konquerorReg("kfmclient");
|
||||
static const QRegularExpression pcmanfmQtReg("pcmanfm-qt");
|
||||
static const QRegularExpression pcmanfmReg("pcmanfm");
|
||||
static const QRegularExpression thunarReg("thunar");
|
||||
|
||||
void Shared::Global::highlightInFileManager(const QString& path)
|
||||
{
|
||||
#ifdef WITH_KIO
|
||||
if (supported("openFileManagerWindowJob")) {
|
||||
hfm(path);
|
||||
return;
|
||||
} else {
|
||||
qDebug() << "requested to highlight in file manager url" << path << "but it's not supported: KIO plugin isn't loaded, trying fallback";
|
||||
}
|
||||
#else
|
||||
qDebug() << "requested to highlight in file manager url" << path << "but it's not supported: squawk wasn't compiled to support it, trying fallback";
|
||||
#endif
|
||||
|
||||
QFileInfo info = path;
|
||||
if (info.exists()) {
|
||||
QProcess proc;
|
||||
proc.start("xdg-mime", query);
|
||||
proc.waitForFinished();
|
||||
QString output = proc.readLine().simplified();
|
||||
|
||||
QString folder;
|
||||
if (info.isDir()) {
|
||||
folder = info.canonicalFilePath();
|
||||
} else {
|
||||
folder = info.canonicalPath();
|
||||
}
|
||||
|
||||
if (output.contains(dolphinReg)) {
|
||||
//there is a bug on current (21.04.0) dolphin, it works correct only if you already have dolphin launched
|
||||
proc.startDetached("dolphin", QStringList() << "--select" << info.canonicalFilePath());
|
||||
//KIO::highlightInFileManager({QUrl(info.canonicalFilePath())});
|
||||
} else if (output.contains(nautilusReg)) {
|
||||
proc.startDetached("nautilus", QStringList() << "--select" << info.canonicalFilePath()); //this worked on nautilus
|
||||
} else if (output.contains(cajaReg)) {
|
||||
proc.startDetached("caja", QStringList() << folder); //caja doesn't seem to support file selection command line, gonna just open directory
|
||||
} else if (output.contains(nemoReg)) {
|
||||
proc.startDetached("nemo", QStringList() << info.canonicalFilePath()); //nemo supports selecting files without keys
|
||||
} else if (output.contains(konquerorReg)) {
|
||||
proc.startDetached("konqueror", QStringList() << "--select" << info.canonicalFilePath()); //this worked on konqueror
|
||||
} else if (output.contains(pcmanfmQtReg)) {
|
||||
proc.startDetached("pcmanfm-qt", QStringList() << folder); //pcmanfm-qt doesn't seem to support open with selection, gonna just open directory
|
||||
} else if (output.contains(pcmanfmReg)) {
|
||||
proc.startDetached("pcmanfm", QStringList() << folder); //pcmanfm also doesn't seem to support open with selection, gonna just open directory
|
||||
} else if (output.contains(thunarReg)) {
|
||||
proc.startDetached("thunar", QStringList() << folder); //thunar doesn't seem to support open with selection, gonna just open directory
|
||||
} else {
|
||||
QDesktopServices::openUrl(QUrl::fromLocalFile(folder));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#define FROM_INT_INPL(Enum) \
|
||||
template<> \
|
||||
Enum Shared::Global::fromInt(int src) \
|
||||
|
@ -29,6 +29,17 @@
|
||||
|
||||
#include <QCoreApplication>
|
||||
#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 {
|
||||
|
||||
@ -36,6 +47,19 @@ namespace Shared {
|
||||
Q_DECLARE_TR_FUNCTIONS(Global)
|
||||
|
||||
public:
|
||||
struct FileInfo {
|
||||
enum class Preview {
|
||||
none,
|
||||
picture,
|
||||
movie
|
||||
};
|
||||
|
||||
QString name;
|
||||
QSize size;
|
||||
QMimeType mime;
|
||||
Preview preview;
|
||||
};
|
||||
|
||||
Global();
|
||||
|
||||
static Global* getInstance();
|
||||
@ -64,6 +88,9 @@ namespace Shared {
|
||||
|
||||
static const std::set<QString> supportedImagesExts;
|
||||
|
||||
static FileInfo getFileInfo(const QString& path);
|
||||
static void highlightInFileManager(const QString& path);
|
||||
|
||||
template<typename T>
|
||||
static T fromInt(int src);
|
||||
|
||||
@ -87,6 +114,15 @@ namespace Shared {
|
||||
static Global* instance;
|
||||
|
||||
std::map<QString, bool> pluginSupport;
|
||||
std::map<QString, FileInfo> fileCache;
|
||||
|
||||
#ifdef WITH_KIO
|
||||
static QLibrary openFileManagerWindowJob;
|
||||
|
||||
typedef void (*HighlightInFileManager)(const QUrl &);
|
||||
|
||||
static HighlightInFileManager hfm;
|
||||
#endif
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -170,6 +170,8 @@ static const std::map<QString, std::pair<QString, QString>> icons = {
|
||||
{"favorite", {"favorite", "favorite"}},
|
||||
{"unfavorite", {"draw-star", "unfavorite"}},
|
||||
{"list-add", {"list-add", "add"}},
|
||||
{"folder", {"folder", "folder"}},
|
||||
{"document-preview", {"document-preview", "document-preview"}}
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -36,7 +36,8 @@ Shared::Message::Message(Shared::Message::Type p_type):
|
||||
errorText(),
|
||||
originalMessage(),
|
||||
lastModified(),
|
||||
stanzaId()
|
||||
stanzaId(),
|
||||
attachPath()
|
||||
{}
|
||||
|
||||
Shared::Message::Message():
|
||||
@ -56,7 +57,8 @@ Shared::Message::Message():
|
||||
errorText(),
|
||||
originalMessage(),
|
||||
lastModified(),
|
||||
stanzaId()
|
||||
stanzaId(),
|
||||
attachPath()
|
||||
{}
|
||||
|
||||
QString Shared::Message::getBody() const
|
||||
@ -311,6 +313,7 @@ void Shared::Message::serialize(QDataStream& data) const
|
||||
data << lastModified;
|
||||
}
|
||||
data << stanzaId;
|
||||
data << attachPath;
|
||||
}
|
||||
|
||||
void Shared::Message::deserialize(QDataStream& data)
|
||||
@ -341,6 +344,7 @@ void Shared::Message::deserialize(QDataStream& data)
|
||||
data >> lastModified;
|
||||
}
|
||||
data >> stanzaId;
|
||||
data >> attachPath;
|
||||
}
|
||||
|
||||
bool Shared::Message::change(const QMap<QString, QVariant>& data)
|
||||
@ -350,6 +354,16 @@ bool Shared::Message::change(const QMap<QString, QVariant>& data)
|
||||
setState(static_cast<State>(itr.value().toUInt()));
|
||||
}
|
||||
|
||||
itr = data.find("outOfBandUrl");
|
||||
if (itr != data.end()) {
|
||||
setOutOfBandUrl(itr.value().toString());
|
||||
}
|
||||
|
||||
itr = data.find("attachPath");
|
||||
if (itr != data.end()) {
|
||||
setAttachPath(itr.value().toString());
|
||||
}
|
||||
|
||||
if (state == State::error) {
|
||||
itr = data.find("errorText");
|
||||
if (itr != data.end()) {
|
||||
@ -380,6 +394,8 @@ bool Shared::Message::change(const QMap<QString, QVariant>& data)
|
||||
|
||||
itr = data.find("body");
|
||||
if (itr != data.end()) {
|
||||
QString b = itr.value().toString();
|
||||
if (body != b) {
|
||||
QMap<QString, QVariant>::const_iterator dItr = data.find("stamp");
|
||||
QDateTime correctionDate;
|
||||
if (dItr != data.end()) {
|
||||
@ -390,10 +406,19 @@ bool Shared::Message::change(const QMap<QString, QVariant>& data)
|
||||
if (!edited || lastModified < correctionDate) {
|
||||
originalMessage = body;
|
||||
lastModified = correctionDate;
|
||||
setBody(itr.value().toString());
|
||||
setBody(body);
|
||||
setEdited(true);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
QMap<QString, QVariant>::const_iterator dItr = data.find("stamp");
|
||||
if (dItr != data.end()) {
|
||||
QDateTime ntime = dItr.value().toDateTime();
|
||||
if (time != ntime) {
|
||||
setTime(ntime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return idChanged;
|
||||
}
|
||||
@ -420,7 +445,7 @@ void Shared::Message::setOutOfBandUrl(const QString& url)
|
||||
|
||||
bool Shared::Message::storable() const
|
||||
{
|
||||
return id.size() > 0 && (body.size() > 0 || oob.size()) > 0;
|
||||
return id.size() > 0 && (body.size() > 0 || oob.size() > 0 || attachPath.size() > 0);
|
||||
}
|
||||
|
||||
void Shared::Message::setStanzaId(const QString& sid)
|
||||
@ -432,3 +457,33 @@ QString Shared::Message::getStanzaId() const
|
||||
{
|
||||
return stanzaId;
|
||||
}
|
||||
|
||||
QString Shared::Message::getAttachPath() const
|
||||
{
|
||||
return attachPath;
|
||||
}
|
||||
|
||||
void Shared::Message::setAttachPath(const QString& path)
|
||||
{
|
||||
attachPath = path;
|
||||
}
|
||||
|
||||
Shared::Message::Change::Change(const QMap<QString, QVariant>& _data):
|
||||
data(_data),
|
||||
idModified(false) {}
|
||||
|
||||
void Shared::Message::Change::operator()(Shared::Message& msg)
|
||||
{
|
||||
idModified = msg.change(data);
|
||||
}
|
||||
|
||||
void Shared::Message::Change::operator()(Shared::Message* msg)
|
||||
{
|
||||
idModified = msg->change(data);
|
||||
}
|
||||
|
||||
bool Shared::Message::Change::hasIdBeenModified() const
|
||||
{
|
||||
return idModified;
|
||||
}
|
||||
|
||||
|
@ -16,15 +16,15 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef SHAPER_MESSAGE_H
|
||||
#define SHAPER_MESSAGE_H
|
||||
|
||||
#include <QString>
|
||||
#include <QDateTime>
|
||||
#include <QVariant>
|
||||
#include <QMap>
|
||||
#include <QDataStream>
|
||||
|
||||
#ifndef SHAPER_MESSAGE_H
|
||||
#define SHAPER_MESSAGE_H
|
||||
|
||||
namespace Shared {
|
||||
|
||||
/**
|
||||
@ -46,9 +46,22 @@ public:
|
||||
delivered,
|
||||
error
|
||||
};
|
||||
|
||||
static const State StateHighest = State::error;
|
||||
static const State StateLowest = State::pending;
|
||||
|
||||
struct Change //change functor, stores in idModified if ID has been modified during change
|
||||
{
|
||||
Change(const QMap<QString, QVariant>& _data);
|
||||
void operator() (Message& msg);
|
||||
void operator() (Message* msg);
|
||||
bool hasIdBeenModified() const;
|
||||
|
||||
private:
|
||||
const QMap<QString, QVariant>& data;
|
||||
bool idModified;
|
||||
};
|
||||
|
||||
Message(Type p_type);
|
||||
Message();
|
||||
|
||||
@ -72,6 +85,7 @@ public:
|
||||
void setErrorText(const QString& err);
|
||||
bool change(const QMap<QString, QVariant>& data);
|
||||
void setStanzaId(const QString& sid);
|
||||
void setAttachPath(const QString& path);
|
||||
|
||||
QString getFrom() const;
|
||||
QString getFromJid() const;
|
||||
@ -100,6 +114,7 @@ public:
|
||||
QDateTime getLastModified() const;
|
||||
QString getOriginalBody() const;
|
||||
QString getStanzaId() const;
|
||||
QString getAttachPath() const;
|
||||
|
||||
void serialize(QDataStream& data) const;
|
||||
void deserialize(QDataStream& data);
|
||||
@ -123,6 +138,7 @@ private:
|
||||
QString originalMessage;
|
||||
QDateTime lastModified;
|
||||
QString stanzaId;
|
||||
QString attachPath;
|
||||
};
|
||||
|
||||
}
|
||||
|
45
shared/messageinfo.cpp
Normal file
@ -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
|
||||
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <QColor>
|
||||
#include <QRegularExpression>
|
||||
|
||||
//#include "KIO/OpenFileManagerWindowJob"
|
||||
|
||||
#include <uuid/uuid.h>
|
||||
#include <vector>
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
cmake_minimum_required(VERSION 3.0)
|
||||
cmake_minimum_required(VERSION 3.3)
|
||||
project(squawkUI)
|
||||
|
||||
# Instruct CMake to run moc automatically when needed.
|
||||
@ -7,9 +7,14 @@ set(CMAKE_AUTOMOC ON)
|
||||
set(CMAKE_AUTOUIC ON)
|
||||
|
||||
# Find the QtWidgets library
|
||||
find_package(Qt5Widgets CONFIG REQUIRED)
|
||||
find_package(Qt5DBus CONFIG REQUIRED)
|
||||
find_package(Qt5 CONFIG REQUIRED COMPONENTS Widgets DBus Core)
|
||||
find_package(Boost 1.36.0 CONFIG REQUIRED COMPONENTS
|
||||
date_time filesystem iostreams)
|
||||
if(Boost_FOUND)
|
||||
include_directories(${Boost_INCLUDE_DIRS})
|
||||
endif()
|
||||
|
||||
add_subdirectory(utils)
|
||||
add_subdirectory(widgets)
|
||||
|
||||
set(squawkUI_SRC
|
||||
@ -25,15 +30,8 @@ set(squawkUI_SRC
|
||||
models/abstractparticipant.cpp
|
||||
models/participant.cpp
|
||||
models/reference.cpp
|
||||
utils/messageline.cpp
|
||||
utils//message.cpp
|
||||
utils/resizer.cpp
|
||||
utils/image.cpp
|
||||
utils/flowlayout.cpp
|
||||
utils/badge.cpp
|
||||
utils/progress.cpp
|
||||
utils/comboboxdelegate.cpp
|
||||
utils/dropshadoweffect.cpp
|
||||
models/messagefeed.cpp
|
||||
models/element.cpp
|
||||
)
|
||||
|
||||
# Tell CMake to create the helloworld executable
|
||||
@ -41,5 +39,6 @@ add_library(squawkUI ${squawkUI_SRC})
|
||||
|
||||
# Use the Widgets module from Qt 5.
|
||||
target_link_libraries(squawkUI squawkWidgets)
|
||||
target_link_libraries(squawkUI squawkUIUtils)
|
||||
target_link_libraries(squawkUI Qt5::Widgets)
|
||||
target_link_libraries(squawkUI Qt5::DBus)
|
||||
|
@ -231,7 +231,7 @@ void Models::Account::toOfflineState()
|
||||
Item::toOfflineState();
|
||||
}
|
||||
|
||||
QString Models::Account::getAvatarPath()
|
||||
QString Models::Account::getAvatarPath() const
|
||||
{
|
||||
return avatarPath;
|
||||
}
|
||||
|
@ -57,7 +57,7 @@ namespace Models {
|
||||
QString getError() const;
|
||||
|
||||
void setAvatarPath(const QString& path);
|
||||
QString getAvatarPath();
|
||||
QString getAvatarPath() const;
|
||||
|
||||
void setAvailability(Shared::Availability p_avail);
|
||||
void setAvailability(unsigned int p_avail);
|
||||
|
@ -17,55 +17,26 @@
|
||||
*/
|
||||
|
||||
#include "contact.h"
|
||||
#include "account.h"
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
Models::Contact::Contact(const Account* acc, const QString& p_jid ,const QMap<QString, QVariant> &data, Item *parentItem):
|
||||
Item(Item::contact, data, parentItem),
|
||||
jid(p_jid),
|
||||
Element(Item::contact, acc, p_jid, data, parentItem),
|
||||
availability(Shared::Availability::offline),
|
||||
state(Shared::SubscriptionState::none),
|
||||
avatarState(Shared::Avatar::empty),
|
||||
presences(),
|
||||
messages(),
|
||||
childMessages(0),
|
||||
status(),
|
||||
avatarPath(),
|
||||
account(acc)
|
||||
status()
|
||||
{
|
||||
QMap<QString, QVariant>::const_iterator itr = data.find("state");
|
||||
if (itr != data.end()) {
|
||||
setState(itr.value().toUInt());
|
||||
}
|
||||
|
||||
itr = data.find("avatarState");
|
||||
if (itr != data.end()) {
|
||||
setAvatarState(itr.value().toUInt());
|
||||
}
|
||||
itr = data.find("avatarPath");
|
||||
if (itr != data.end()) {
|
||||
setAvatarPath(itr.value().toString());
|
||||
}
|
||||
}
|
||||
|
||||
Models::Contact::~Contact()
|
||||
{
|
||||
}
|
||||
|
||||
QString Models::Contact::getJid() const
|
||||
{
|
||||
return jid;
|
||||
}
|
||||
|
||||
void Models::Contact::setJid(const QString p_jid)
|
||||
{
|
||||
if (jid != p_jid) {
|
||||
jid = p_jid;
|
||||
changed(1);
|
||||
}
|
||||
}
|
||||
|
||||
void Models::Contact::setAvailability(unsigned int p_state)
|
||||
{
|
||||
setAvailability(Shared::Global::fromInt<Shared::Availability>(p_state));
|
||||
@ -144,16 +115,12 @@ void Models::Contact::update(const QString& field, const QVariant& value)
|
||||
{
|
||||
if (field == "name") {
|
||||
setName(value.toString());
|
||||
} else if (field == "jid") {
|
||||
setJid(value.toString());
|
||||
} else if (field == "availability") {
|
||||
setAvailability(value.toUInt());
|
||||
} else if (field == "state") {
|
||||
setState(value.toUInt());
|
||||
} else if (field == "avatarState") {
|
||||
setAvatarState(value.toUInt());
|
||||
} else if (field == "avatarPath") {
|
||||
setAvatarPath(value.toString());
|
||||
} else {
|
||||
Element::update(field, value);
|
||||
}
|
||||
}
|
||||
|
||||
@ -192,11 +159,9 @@ void Models::Contact::refresh()
|
||||
{
|
||||
QDateTime lastActivity;
|
||||
Presence* presence = 0;
|
||||
unsigned int count = 0;
|
||||
for (QMap<QString, Presence*>::iterator itr = presences.begin(), end = presences.end(); itr != end; ++itr) {
|
||||
Presence* pr = itr.value();
|
||||
QDateTime la = pr->getLastActivity();
|
||||
count += pr->getMessagesCount();
|
||||
|
||||
if (la > lastActivity) {
|
||||
lastActivity = la;
|
||||
@ -211,11 +176,6 @@ void Models::Contact::refresh()
|
||||
setAvailability(Shared::Availability::offline);
|
||||
setStatus("");
|
||||
}
|
||||
|
||||
if (childMessages != count) {
|
||||
childMessages = count;
|
||||
changed(4);
|
||||
}
|
||||
}
|
||||
|
||||
void Models::Contact::_removeChild(int index)
|
||||
@ -257,81 +217,6 @@ QIcon Models::Contact::getStatusIcon(bool big) const
|
||||
}
|
||||
}
|
||||
|
||||
void Models::Contact::addMessage(const Shared::Message& data)
|
||||
{
|
||||
const QString& res = data.getPenPalResource();
|
||||
if (res.size() > 0) {
|
||||
QMap<QString, Presence*>::iterator itr = presences.find(res);
|
||||
if (itr == presences.end()) {
|
||||
// this is actually the place when I can spot someone's invisible presence, and there is nothing criminal in it, cuz the sender sent us a message
|
||||
// therefore he have revealed himself
|
||||
// the only issue is to find out when the sender is gone offline
|
||||
Presence* pr = new Presence({});
|
||||
pr->setName(res);
|
||||
pr->setAvailability(Shared::Availability::invisible);
|
||||
pr->setLastActivity(QDateTime::currentDateTimeUtc());
|
||||
presences.insert(res, pr);
|
||||
appendChild(pr);
|
||||
pr->addMessage(data);
|
||||
return;
|
||||
}
|
||||
itr.value()->addMessage(data);
|
||||
} else {
|
||||
messages.emplace_back(data);
|
||||
changed(4);
|
||||
}
|
||||
}
|
||||
|
||||
void Models::Contact::changeMessage(const QString& id, const QMap<QString, QVariant>& data)
|
||||
{
|
||||
|
||||
bool found = false;
|
||||
for (Shared::Message& msg : messages) {
|
||||
if (msg.getId() == id) {
|
||||
msg.change(data);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
for (Presence* pr : presences) {
|
||||
found = pr->changeMessage(id, data);
|
||||
if (found) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int Models::Contact::getMessagesCount() const
|
||||
{
|
||||
return messages.size() + childMessages;
|
||||
}
|
||||
|
||||
void Models::Contact::dropMessages()
|
||||
{
|
||||
if (messages.size() > 0) {
|
||||
messages.clear();
|
||||
changed(4);
|
||||
}
|
||||
|
||||
for (QMap<QString, Presence*>::iterator itr = presences.begin(), end = presences.end(); itr != end; ++itr) {
|
||||
itr.value()->dropMessages();
|
||||
}
|
||||
}
|
||||
|
||||
void Models::Contact::getMessages(Models::Contact::Messages& container) const
|
||||
{
|
||||
for (Messages::const_iterator itr = messages.begin(), end = messages.end(); itr != end; ++itr) {
|
||||
const Shared::Message& msg = *itr;
|
||||
container.push_back(msg);
|
||||
}
|
||||
|
||||
for (QMap<QString, Presence*>::const_iterator itr = presences.begin(), end = presences.end(); itr != end; ++itr) {
|
||||
itr.value()->getMessages(container);
|
||||
}
|
||||
}
|
||||
|
||||
void Models::Contact::toOfflineState()
|
||||
{
|
||||
std::deque<Item*>::size_type size = childItems.size();
|
||||
@ -355,75 +240,3 @@ QString Models::Contact::getDisplayedName() const
|
||||
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
|
||||
#define MODELS_CONTACT_H
|
||||
|
||||
#include "item.h"
|
||||
#include "element.h"
|
||||
#include "presence.h"
|
||||
#include "shared/enums.h"
|
||||
#include "shared/message.h"
|
||||
@ -31,49 +31,34 @@
|
||||
#include <deque>
|
||||
|
||||
namespace Models {
|
||||
class Account;
|
||||
|
||||
class Contact : public Item
|
||||
class Contact : public Element
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
typedef std::deque<Shared::Message> Messages;
|
||||
Contact(const Account* acc, const QString& p_jid, const QMap<QString, QVariant> &data, Item *parentItem = 0);
|
||||
Contact(const Contact& other);
|
||||
~Contact();
|
||||
|
||||
QString getJid() const;
|
||||
Shared::Availability getAvailability() const;
|
||||
Shared::SubscriptionState getState() const;
|
||||
Shared::Avatar getAvatarState() const;
|
||||
QString getAvatarPath() const;
|
||||
|
||||
QIcon getStatusIcon(bool big = false) const;
|
||||
|
||||
int columnCount() const override;
|
||||
QVariant data(int column) const override;
|
||||
|
||||
void update(const QString& field, const QVariant& value);
|
||||
void update(const QString& field, const QVariant& value) override;
|
||||
|
||||
void addPresence(const QString& name, const QMap<QString, QVariant>& data);
|
||||
void removePresence(const QString& name);
|
||||
|
||||
QString getContactName() const;
|
||||
QString getStatus() const;
|
||||
|
||||
void addMessage(const Shared::Message& data);
|
||||
void changeMessage(const QString& id, const QMap<QString, QVariant>& data);
|
||||
unsigned int getMessagesCount() const;
|
||||
void dropMessages();
|
||||
void getMessages(Messages& container) const;
|
||||
QString getDisplayedName() const override;
|
||||
|
||||
Contact* copy() const;
|
||||
|
||||
protected:
|
||||
void _removeChild(int index) override;
|
||||
void _appendChild(Models::Item * child) override;
|
||||
bool columnInvolvedInDisplay(int col) override;
|
||||
const Account* getParentAccount() const override;
|
||||
|
||||
protected slots:
|
||||
void refresh();
|
||||
@ -84,23 +69,13 @@ protected:
|
||||
void setAvailability(unsigned int p_state);
|
||||
void setState(Shared::SubscriptionState p_state);
|
||||
void setState(unsigned int p_state);
|
||||
void setAvatarState(Shared::Avatar p_state);
|
||||
void setAvatarState(unsigned int p_state);
|
||||
void setAvatarPath(const QString& path);
|
||||
void setJid(const QString p_jid);
|
||||
void setStatus(const QString& p_state);
|
||||
|
||||
private:
|
||||
QString jid;
|
||||
Shared::Availability availability;
|
||||
Shared::SubscriptionState state;
|
||||
Shared::Avatar avatarState;
|
||||
QMap<QString, Presence*> presences;
|
||||
Messages messages;
|
||||
unsigned int childMessages;
|
||||
QString status;
|
||||
QString avatarPath;
|
||||
const Account* account;
|
||||
};
|
||||
|
||||
}
|
||||
|
184
ui/models/element.cpp
Normal file
@ -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();
|
||||
}
|
||||
|
||||
QString Models::Item::getAccountAvatarPath() const
|
||||
{
|
||||
const Account* acc = getParentAccount();
|
||||
if (acc == nullptr) {
|
||||
return "";
|
||||
}
|
||||
return acc->getAvatarPath();
|
||||
}
|
||||
|
||||
QString Models::Item::getDisplayedName() const
|
||||
{
|
||||
return name;
|
||||
|
@ -80,6 +80,7 @@ class Item : public QObject{
|
||||
QString getAccountName() const;
|
||||
QString getAccountJid() const;
|
||||
QString getAccountResource() const;
|
||||
QString getAccountAvatarPath() const;
|
||||
Shared::ConnectionState getAccountConnectionState() const;
|
||||
Shared::Availability getAccountAvailability() const;
|
||||
|
||||
|
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"
|
||||
|
||||
Models::Presence::Presence(const QMap<QString, QVariant>& data, Item* parentItem):
|
||||
AbstractParticipant(Item::presence, data, parentItem),
|
||||
messages()
|
||||
AbstractParticipant(Item::presence, data, parentItem)
|
||||
{
|
||||
}
|
||||
|
||||
Models::Presence::Presence(const Models::Presence& other):
|
||||
AbstractParticipant(other),
|
||||
messages(other.messages)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
Models::Presence::~Presence()
|
||||
{
|
||||
}
|
||||
|
||||
int Models::Presence::columnCount() const
|
||||
{
|
||||
return 5;
|
||||
}
|
||||
|
||||
QVariant Models::Presence::data(int column) const
|
||||
{
|
||||
switch (column) {
|
||||
case 4:
|
||||
return getMessagesCount();
|
||||
default:
|
||||
return AbstractParticipant::data(column);
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int Models::Presence::getMessagesCount() const
|
||||
{
|
||||
return messages.size();
|
||||
}
|
||||
|
||||
void Models::Presence::addMessage(const Shared::Message& data)
|
||||
{
|
||||
messages.emplace_back(data);
|
||||
changed(4);
|
||||
}
|
||||
|
||||
bool Models::Presence::changeMessage(const QString& id, const QMap<QString, QVariant>& data)
|
||||
{
|
||||
bool found = false;
|
||||
for (Shared::Message& msg : messages) {
|
||||
if (msg.getId() == id) {
|
||||
msg.change(data);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
void Models::Presence::dropMessages()
|
||||
{
|
||||
if (messages.size() > 0) {
|
||||
messages.clear();
|
||||
changed(4);
|
||||
}
|
||||
}
|
||||
|
||||
QIcon Models::Presence::getStatusIcon(bool big) const
|
||||
{
|
||||
if (getMessagesCount() > 0) {
|
||||
return Shared::icon("mail-message", big);
|
||||
} else {
|
||||
return AbstractParticipant::getStatusIcon();
|
||||
}
|
||||
}
|
||||
|
||||
void Models::Presence::getMessages(Models::Presence::Messages& container) const
|
||||
{
|
||||
for (Messages::const_iterator itr = messages.begin(), end = messages.end(); itr != end; ++itr) {
|
||||
const Shared::Message& msg = *itr;
|
||||
container.push_back(msg);
|
||||
}
|
||||
return 4;
|
||||
}
|
||||
|
@ -32,25 +32,10 @@ class Presence : public Models::AbstractParticipant
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
typedef std::deque<Shared::Message> Messages;
|
||||
explicit Presence(const QMap<QString, QVariant> &data, Item *parentItem = 0);
|
||||
Presence(const Presence& other);
|
||||
~Presence();
|
||||
|
||||
int columnCount() const override;
|
||||
QVariant data(int column) const override;
|
||||
|
||||
QIcon getStatusIcon(bool big = false) const override;
|
||||
|
||||
unsigned int getMessagesCount() const;
|
||||
void dropMessages();
|
||||
void addMessage(const Shared::Message& data);
|
||||
bool changeMessage(const QString& id, const QMap<QString, QVariant>& data);
|
||||
|
||||
void getMessages(Messages& container) const;
|
||||
|
||||
private:
|
||||
Messages messages;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -22,16 +22,12 @@
|
||||
#include <QIcon>
|
||||
#include <QDebug>
|
||||
|
||||
Models::Room::Room(const QString& p_jid, const QMap<QString, QVariant>& data, Models::Item* parentItem):
|
||||
Item(room, data, parentItem),
|
||||
Models::Room::Room(const Account* acc, const QString& p_jid, const QMap<QString, QVariant>& data, Models::Item* parentItem):
|
||||
Element(room, acc, p_jid, data, parentItem),
|
||||
autoJoin(false),
|
||||
joined(false),
|
||||
jid(p_jid),
|
||||
nick(""),
|
||||
subject(""),
|
||||
avatarState(Shared::Avatar::empty),
|
||||
avatarPath(""),
|
||||
messages(),
|
||||
participants(),
|
||||
exParticipantAvatars()
|
||||
{
|
||||
@ -55,16 +51,6 @@ Models::Room::Room(const QString& p_jid, const QMap<QString, QVariant>& data, Mo
|
||||
setSubject(itr.value().toString());
|
||||
}
|
||||
|
||||
itr = data.find("avatarState");
|
||||
if (itr != data.end()) {
|
||||
setAvatarState(itr.value().toUInt());
|
||||
}
|
||||
itr = data.find("avatarPath");
|
||||
if (itr != data.end()) {
|
||||
setAvatarPath(itr.value().toString());
|
||||
}
|
||||
|
||||
|
||||
itr = data.find("avatars");
|
||||
if (itr != data.end()) {
|
||||
QMap<QString, QVariant> avs = itr.value().toMap();
|
||||
@ -78,21 +64,11 @@ Models::Room::~Room()
|
||||
{
|
||||
}
|
||||
|
||||
unsigned int Models::Room::getUnreadMessagesCount() const
|
||||
{
|
||||
return messages.size();
|
||||
}
|
||||
|
||||
int Models::Room::columnCount() const
|
||||
{
|
||||
return 7;
|
||||
}
|
||||
|
||||
QString Models::Room::getJid() const
|
||||
{
|
||||
return jid;
|
||||
}
|
||||
|
||||
bool Models::Room::getAutoJoin() const
|
||||
{
|
||||
return autoJoin;
|
||||
@ -151,14 +127,6 @@ void Models::Room::setAutoJoin(bool p_autoJoin)
|
||||
}
|
||||
}
|
||||
|
||||
void Models::Room::setJid(const QString& p_jid)
|
||||
{
|
||||
if (jid != p_jid) {
|
||||
jid = p_jid;
|
||||
changed(1);
|
||||
}
|
||||
}
|
||||
|
||||
void Models::Room::setJoined(bool p_joined)
|
||||
{
|
||||
if (joined != p_joined) {
|
||||
@ -182,8 +150,6 @@ void Models::Room::update(const QString& field, const QVariant& value)
|
||||
{
|
||||
if (field == "name") {
|
||||
setName(value.toString());
|
||||
} else if (field == "jid") {
|
||||
setJid(value.toString());
|
||||
} else if (field == "joined") {
|
||||
setJoined(value.toBool());
|
||||
} else if (field == "autoJoin") {
|
||||
@ -192,16 +158,14 @@ void Models::Room::update(const QString& field, const QVariant& value)
|
||||
setNick(value.toString());
|
||||
} else if (field == "subject") {
|
||||
setSubject(value.toString());
|
||||
} else if (field == "avatarState") {
|
||||
setAvatarState(value.toUInt());
|
||||
} else if (field == "avatarPath") {
|
||||
setAvatarPath(value.toString());
|
||||
} else {
|
||||
Element::update(field, value);
|
||||
}
|
||||
}
|
||||
|
||||
QIcon Models::Room::getStatusIcon(bool big) const
|
||||
{
|
||||
if (messages.size() > 0) {
|
||||
if (getMessagesCount() > 0) {
|
||||
return Shared::icon("mail-message", big);
|
||||
} else {
|
||||
if (autoJoin) {
|
||||
@ -237,42 +201,6 @@ QString Models::Room::getStatusText() const
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int Models::Room::getMessagesCount() const
|
||||
{
|
||||
return messages.size();
|
||||
}
|
||||
|
||||
void Models::Room::addMessage(const Shared::Message& data)
|
||||
{
|
||||
messages.emplace_back(data);
|
||||
changed(5);
|
||||
}
|
||||
|
||||
void Models::Room::changeMessage(const QString& id, const QMap<QString, QVariant>& data)
|
||||
{
|
||||
for (Shared::Message& msg : messages) {
|
||||
if (msg.getId() == id) {
|
||||
msg.change(data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Models::Room::dropMessages()
|
||||
{
|
||||
if (messages.size() > 0) {
|
||||
messages.clear();
|
||||
changed(5);
|
||||
}
|
||||
}
|
||||
|
||||
void Models::Room::getMessages(Models::Room::Messages& container) const
|
||||
{
|
||||
for (Messages::const_iterator itr = messages.begin(), end = messages.end(); itr != end; ++itr) {
|
||||
const Shared::Message& msg = *itr;
|
||||
container.push_back(msg);
|
||||
}
|
||||
}
|
||||
|
||||
void Models::Room::toOfflineState()
|
||||
{
|
||||
@ -367,47 +295,6 @@ QString Models::Room::getDisplayedName() const
|
||||
return getRoomName();
|
||||
}
|
||||
|
||||
bool Models::Room::columnInvolvedInDisplay(int col)
|
||||
{
|
||||
return Item::columnInvolvedInDisplay(col) && col == 1;
|
||||
}
|
||||
|
||||
QString Models::Room::getAvatarPath() const
|
||||
{
|
||||
return avatarPath;
|
||||
}
|
||||
|
||||
Shared::Avatar Models::Room::getAvatarState() const
|
||||
{
|
||||
return avatarState;
|
||||
}
|
||||
|
||||
void Models::Room::setAvatarPath(const QString& path)
|
||||
{
|
||||
if (avatarPath != path) {
|
||||
avatarPath = path;
|
||||
changed(8);
|
||||
}
|
||||
}
|
||||
|
||||
void Models::Room::setAvatarState(Shared::Avatar p_state)
|
||||
{
|
||||
if (avatarState != p_state) {
|
||||
avatarState = p_state;
|
||||
changed(7);
|
||||
}
|
||||
}
|
||||
|
||||
void Models::Room::setAvatarState(unsigned int p_state)
|
||||
{
|
||||
if (p_state <= static_cast<quint8>(Shared::Avatar::valid)) {
|
||||
Shared::Avatar state = static_cast<Shared::Avatar>(p_state);
|
||||
setAvatarState(state);
|
||||
} else {
|
||||
qDebug() << "An attempt to set invalid avatar state" << p_state << "to the room" << jid << ", skipping";
|
||||
}
|
||||
}
|
||||
|
||||
std::map<QString, const Models::Participant &> Models::Room::getParticipants() const
|
||||
{
|
||||
std::map<QString, const Models::Participant&> result;
|
||||
@ -423,8 +310,13 @@ QString Models::Room::getParticipantIconPath(const QString& name) const
|
||||
{
|
||||
std::map<QString, Models::Participant*>::const_iterator itr = participants.find(name);
|
||||
if (itr == participants.end()) {
|
||||
std::map<QString, QString>::const_iterator eitr = exParticipantAvatars.find(name);
|
||||
if (eitr != exParticipantAvatars.end()) {
|
||||
return eitr->second;
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
return itr->second->getAvatarPath();
|
||||
}
|
||||
|
@ -19,7 +19,7 @@
|
||||
#ifndef MODELS_ROOM_H
|
||||
#define MODELS_ROOM_H
|
||||
|
||||
#include "item.h"
|
||||
#include "element.h"
|
||||
#include "participant.h"
|
||||
#include "shared/enums.h"
|
||||
#include "shared/message.h"
|
||||
@ -29,21 +29,18 @@ namespace Models {
|
||||
/**
|
||||
* @todo write docs
|
||||
*/
|
||||
class Room : public Models::Item
|
||||
class Room : public Element
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
typedef std::deque<Shared::Message> Messages;
|
||||
Room(const QString& p_jid, const QMap<QString, QVariant> &data, Item *parentItem = 0);
|
||||
Room(const Account* acc, const QString& p_jid, const QMap<QString, QVariant> &data, Item *parentItem = 0);
|
||||
~Room();
|
||||
|
||||
int columnCount() const override;
|
||||
QVariant data(int column) const override;
|
||||
|
||||
unsigned int getUnreadMessagesCount() const;
|
||||
bool getJoined() const;
|
||||
bool getAutoJoin() const;
|
||||
QString getJid() const;
|
||||
QString getNick() const;
|
||||
QString getRoomName() const;
|
||||
QString getSubject() const;
|
||||
@ -53,17 +50,10 @@ public:
|
||||
|
||||
void setJoined(bool p_joined);
|
||||
void setAutoJoin(bool p_autoJoin);
|
||||
void setJid(const QString& p_jid);
|
||||
void setNick(const QString& p_nick);
|
||||
void setSubject(const QString& sub);
|
||||
|
||||
void update(const QString& field, const QVariant& value);
|
||||
|
||||
void addMessage(const Shared::Message& data);
|
||||
void changeMessage(const QString& id, const QMap<QString, QVariant>& data);
|
||||
unsigned int getMessagesCount() const;
|
||||
void dropMessages();
|
||||
void getMessages(Messages& container) const;
|
||||
void update(const QString& field, const QVariant& value) override;
|
||||
|
||||
void addParticipant(const QString& name, const QMap<QString, QVariant>& data);
|
||||
void changeParticipant(const QString& name, const QMap<QString, QVariant>& data);
|
||||
@ -71,8 +61,6 @@ public:
|
||||
|
||||
void toOfflineState() override;
|
||||
QString getDisplayedName() const override;
|
||||
Shared::Avatar getAvatarState() const;
|
||||
QString getAvatarPath() const;
|
||||
std::map<QString, const Participant&> getParticipants() const;
|
||||
QString getParticipantIconPath(const QString& name) const;
|
||||
std::map<QString, QString> getExParticipantAvatars() const;
|
||||
@ -84,24 +72,14 @@ signals:
|
||||
private:
|
||||
void handleParticipantUpdate(std::map<QString, Participant*>::const_iterator itr, const QMap<QString, QVariant>& data);
|
||||
|
||||
protected:
|
||||
bool columnInvolvedInDisplay(int col) override;
|
||||
void setAvatarState(Shared::Avatar p_state);
|
||||
void setAvatarState(unsigned int p_state);
|
||||
void setAvatarPath(const QString& path);
|
||||
|
||||
private:
|
||||
bool autoJoin;
|
||||
bool joined;
|
||||
QString jid;
|
||||
QString nick;
|
||||
QString subject;
|
||||
Shared::Avatar avatarState;
|
||||
QString avatarPath;
|
||||
Messages messages;
|
||||
std::map<QString, Participant*> participants;
|
||||
std::map<QString, QString> exParticipantAvatars;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -215,11 +215,7 @@ QVariant Models::Roster::data (const QModelIndex& index, int role) const
|
||||
break;
|
||||
case Item::presence: {
|
||||
Presence* contact = static_cast<Presence*>(item);
|
||||
QString str("");
|
||||
int mc = contact->getMessagesCount();
|
||||
if (mc > 0) {
|
||||
str += tr("New messages: ") + std::to_string(mc).c_str() + "\n";
|
||||
}
|
||||
QString str;
|
||||
Shared::Availability av = contact->getAvailability();
|
||||
str += tr("Availability: ") + Shared::Global::getName(av);
|
||||
QString s = contact->getStatus();
|
||||
@ -232,7 +228,7 @@ QVariant Models::Roster::data (const QModelIndex& index, int role) const
|
||||
break;
|
||||
case Item::participant: {
|
||||
Participant* p = static_cast<Participant*>(item);
|
||||
QString str("");
|
||||
QString str;
|
||||
Shared::Availability av = p->getAvailability();
|
||||
str += tr("Availability: ") + Shared::Global::getName(av) + "\n";
|
||||
QString s = p->getStatus();
|
||||
@ -260,7 +256,7 @@ QVariant Models::Roster::data (const QModelIndex& index, int role) const
|
||||
break;
|
||||
case Item::room: {
|
||||
Room* rm = static_cast<Room*>(item);
|
||||
unsigned int count = rm->getUnreadMessagesCount();
|
||||
unsigned int count = rm->getMessagesCount();
|
||||
QString str("");
|
||||
if (count > 0) {
|
||||
str += tr("New messages: ") + std::to_string(count).c_str() + "\n";
|
||||
@ -450,6 +446,10 @@ void Models::Roster::addContact(const QString& account, const QString& jid, cons
|
||||
std::map<ElId, Contact*>::iterator itr = contacts.find(id);
|
||||
if (itr == contacts.end()) {
|
||||
contact = new Contact(acc, jid, data);
|
||||
connect(contact, &Contact::requestArchive, this, &Roster::onElementRequestArchive);
|
||||
connect(contact, &Contact::fileDownloadRequest, this, &Roster::fileDownloadRequest);
|
||||
connect(contact, &Contact::unnoticedMessage, this, &Roster::unnoticedMessage);
|
||||
connect(contact, &Contact::localPathInvalid, this, &Roster::localPathInvalid);
|
||||
contacts.insert(std::make_pair(id, contact));
|
||||
} else {
|
||||
contact = itr->second;
|
||||
@ -535,35 +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)
|
||||
{
|
||||
ElId id(account, jid);
|
||||
std::map<ElId, Contact*>::iterator cItr = contacts.find(id);
|
||||
|
||||
if (cItr != contacts.end()) {
|
||||
Element* el = getElement({account, jid});
|
||||
if (el != NULL) {
|
||||
for (QMap<QString, QVariant>::const_iterator itr = data.begin(), end = data.end(); itr != end; ++itr) {
|
||||
cItr->second->update(itr.key(), itr.value());
|
||||
}
|
||||
} else {
|
||||
std::map<ElId, Room*>::iterator rItr = rooms.find(id);
|
||||
if (rItr != rooms.end()) {
|
||||
for (QMap<QString, QVariant>::const_iterator itr = data.begin(), end = data.end(); itr != end; ++itr) {
|
||||
rItr->second->update(itr.key(), itr.value());
|
||||
}
|
||||
el->update(itr.key(), itr.value());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Models::Roster::changeMessage(const QString& account, const QString& jid, const QString& id, const QMap<QString, QVariant>& data)
|
||||
{
|
||||
ElId elid(account, jid);
|
||||
std::map<ElId, Contact*>::iterator cItr = contacts.find(elid);
|
||||
|
||||
if (cItr != contacts.end()) {
|
||||
cItr->second->changeMessage(id, data);
|
||||
} else {
|
||||
std::map<ElId, Room*>::iterator rItr = rooms.find(elid);
|
||||
if (rItr != rooms.end()) {
|
||||
rItr->second->changeMessage(id, data);
|
||||
}
|
||||
Element* el = getElement({account, jid});
|
||||
if (el != NULL) {
|
||||
el->changeMessage(id, data);
|
||||
}
|
||||
}
|
||||
|
||||
@ -627,7 +611,6 @@ void Models::Roster::removeContact(const QString& account, const QString& jid, c
|
||||
} else {
|
||||
delete ref;
|
||||
}
|
||||
|
||||
if (gr->childCount() == 0) {
|
||||
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)
|
||||
{
|
||||
ElId id(account, data.getPenPalJid());
|
||||
std::map<ElId, Contact*>::iterator itr = contacts.find(id);
|
||||
if (itr != contacts.end()) {
|
||||
itr->second->addMessage(data);
|
||||
} else {
|
||||
std::map<ElId, Room*>::const_iterator rItr = rooms.find(id);
|
||||
if (rItr != rooms.end()) {
|
||||
rItr->second->addMessage(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Models::Roster::dropMessages(const QString& account, const QString& jid)
|
||||
{
|
||||
ElId id(account, jid);
|
||||
std::map<ElId, Contact*>::iterator itr = contacts.find(id);
|
||||
if (itr != contacts.end()) {
|
||||
itr->second->dropMessages();
|
||||
} else {
|
||||
std::map<ElId, Room*>::const_iterator rItr = rooms.find(id);
|
||||
if (rItr != rooms.end()) {
|
||||
rItr->second->dropMessages();
|
||||
}
|
||||
Element* el = getElement({account, data.getPenPalJid()});
|
||||
if (el != NULL) {
|
||||
el->addMessage(data);
|
||||
}
|
||||
}
|
||||
|
||||
@ -821,7 +784,11 @@ void Models::Roster::addRoom(const QString& account, const QString jid, const QM
|
||||
return;
|
||||
}
|
||||
|
||||
Room* room = new Room(jid, data);
|
||||
Room* room = new Room(acc, jid, data);
|
||||
connect(room, &Contact::requestArchive, this, &Roster::onElementRequestArchive);
|
||||
connect(room, &Contact::fileDownloadRequest, this, &Roster::fileDownloadRequest);
|
||||
connect(room, &Contact::unnoticedMessage, this, &Roster::unnoticedMessage);
|
||||
connect(room, &Contact::localPathInvalid, this, &Roster::localPathInvalid);
|
||||
rooms.insert(std::make_pair(id, room));
|
||||
acc->appendChild(room);
|
||||
}
|
||||
@ -974,3 +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/global.h"
|
||||
#include "shared/messageinfo.h"
|
||||
#include "accounts.h"
|
||||
#include "item.h"
|
||||
#include "account.h"
|
||||
@ -58,7 +59,6 @@ public:
|
||||
void removePresence(const QString& account, const QString& jid, const QString& name);
|
||||
void addMessage(const QString& account, const Shared::Message& data);
|
||||
void changeMessage(const QString& account, const QString& jid, const QString& id, const QMap<QString, QVariant>& data);
|
||||
void dropMessages(const QString& account, const QString& jid);
|
||||
void addRoom(const QString& account, const QString jid, const QMap<QString, QVariant>& data);
|
||||
void changeRoom(const QString& account, const QString jid, const QMap<QString, QVariant>& data);
|
||||
void removeRoom(const QString& account, const QString jid);
|
||||
@ -81,15 +81,22 @@ public:
|
||||
Account* getAccount(const QString& name);
|
||||
QModelIndex getAccountIndex(const QString& name);
|
||||
QModelIndex getGroupIndex(const QString& account, const QString& name);
|
||||
void responseArchive(const QString& account, const QString& jid, const std::list<Shared::Message>& list, bool last);
|
||||
|
||||
void fileProgress(const std::list<Shared::MessageInfo>& msgs, qreal value, bool up);
|
||||
void fileError(const std::list<Shared::MessageInfo>& msgs, const QString& err, bool up);
|
||||
void fileComplete(const std::list<Shared::MessageInfo>& msgs, bool up);
|
||||
|
||||
Accounts* accountsModel;
|
||||
|
||||
signals:
|
||||
void requestArchive(const QString& account, const QString& jid, const QString& before);
|
||||
void fileDownloadRequest(const QString& url);
|
||||
void unnoticedMessage(const QString& account, const Shared::Message& msg);
|
||||
void localPathInvalid(const QString& path);
|
||||
|
||||
private:
|
||||
Item* root;
|
||||
std::map<QString, Account*> accounts;
|
||||
std::map<ElId, Group*> groups;
|
||||
std::map<ElId, Contact*> contacts;
|
||||
std::map<ElId, Room*> rooms;
|
||||
Element* getElement(const ElId& id);
|
||||
|
||||
private slots:
|
||||
void onAccountDataChanged(const QModelIndex& tl, const QModelIndex& br, const QVector<int>& roles);
|
||||
@ -100,6 +107,14 @@ private slots:
|
||||
void onChildRemoved();
|
||||
void onChildIsAboutToBeMoved(Item* source, int first, int last, Item* destination, int newIndex);
|
||||
void onChildMoved();
|
||||
void onElementRequestArchive(const QString& before);
|
||||
|
||||
private:
|
||||
Item* root;
|
||||
std::map<QString, Account*> accounts;
|
||||
std::map<ElId, Group*> groups;
|
||||
std::map<ElId, Contact*> contacts;
|
||||
std::map<ElId, Room*> rooms;
|
||||
|
||||
public:
|
||||
class ElId {
|
||||
|
238
ui/squawk.cpp
@ -29,7 +29,6 @@ Squawk::Squawk(QWidget *parent) :
|
||||
conversations(),
|
||||
contextMenu(new QMenu()),
|
||||
dbus("org.freedesktop.Notifications", "/org/freedesktop/Notifications", "org.freedesktop.Notifications", QDBusConnection::sessionBus()),
|
||||
requestedFiles(),
|
||||
vCards(),
|
||||
requestedAccountsForPasswords(),
|
||||
prompt(0),
|
||||
@ -60,8 +59,12 @@ Squawk::Squawk(QWidget *parent) :
|
||||
connect(m_ui->roster, &QTreeView::customContextMenuRequested, this, &Squawk::onRosterContextMenu);
|
||||
connect(m_ui->roster, &QTreeView::collapsed, this, &Squawk::onItemCollepsed);
|
||||
connect(m_ui->roster->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &Squawk::onRosterSelectionChanged);
|
||||
connect(&rosterModel, &Models::Roster::unnoticedMessage, this, &Squawk::onUnnoticedMessage);
|
||||
|
||||
connect(rosterModel.accountsModel, &Models::Accounts::sizeChanged, this, &Squawk::onAccountsSizeChanged);
|
||||
connect(&rosterModel, &Models::Roster::requestArchive, this, &Squawk::onRequestArchive);
|
||||
connect(&rosterModel, &Models::Roster::fileDownloadRequest, this, &Squawk::fileDownloadRequest);
|
||||
connect(&rosterModel, &Models::Roster::localPathInvalid, this, &Squawk::localPathInvalid);
|
||||
connect(contextMenu, &QMenu::aboutToHide, this, &Squawk::onContextAboutToHide);
|
||||
//m_ui->mainToolBar->addWidget(m_ui->comboBox);
|
||||
|
||||
@ -336,17 +339,14 @@ void Squawk::onRosterItemDoubleClicked(const QModelIndex& item)
|
||||
Models::Account* acc = rosterModel.getAccount(id->account);
|
||||
Conversation* conv = 0;
|
||||
bool created = false;
|
||||
Models::Contact::Messages deque;
|
||||
if (itr != conversations.end()) {
|
||||
conv = itr->second;
|
||||
} else if (contact != 0) {
|
||||
created = true;
|
||||
conv = new Chat(acc, contact);
|
||||
contact->getMessages(deque);
|
||||
} else if (room != 0) {
|
||||
created = true;
|
||||
conv = new Room(acc, room);
|
||||
room->getMessages(deque);
|
||||
|
||||
if (!room->getJoined()) {
|
||||
emit setRoomJoined(id->account, id->name, true);
|
||||
@ -358,12 +358,6 @@ void Squawk::onRosterItemDoubleClicked(const QModelIndex& item)
|
||||
conv->setAttribute(Qt::WA_DeleteOnClose);
|
||||
subscribeConversation(conv);
|
||||
conversations.insert(std::make_pair(*id, conv));
|
||||
|
||||
if (created) {
|
||||
for (Models::Contact::Messages::const_iterator itr = deque.begin(), end = deque.end(); itr != end; ++itr) {
|
||||
conv->addMessage(*itr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
conv->show();
|
||||
@ -380,12 +374,6 @@ void Squawk::onRosterItemDoubleClicked(const QModelIndex& item)
|
||||
}
|
||||
}
|
||||
|
||||
void Squawk::onConversationShown()
|
||||
{
|
||||
Conversation* conv = static_cast<Conversation*>(sender());
|
||||
rosterModel.dropMessages(conv->getAccount(), conv->getJid());
|
||||
}
|
||||
|
||||
void Squawk::onConversationClosed(QObject* parent)
|
||||
{
|
||||
Conversation* conv = static_cast<Conversation*>(sender());
|
||||
@ -402,165 +390,41 @@ void Squawk::onConversationClosed(QObject* parent)
|
||||
}
|
||||
}
|
||||
|
||||
void Squawk::onConversationDownloadFile(const QString& messageId, const QString& url)
|
||||
void Squawk::fileProgress(const std::list<Shared::MessageInfo> msgs, qreal value, bool up)
|
||||
{
|
||||
Conversation* conv = static_cast<Conversation*>(sender());
|
||||
std::map<QString, std::set<Models::Roster::ElId>>::iterator itr = requestedFiles.find(messageId);
|
||||
bool created = false;
|
||||
if (itr == requestedFiles.end()) {
|
||||
itr = requestedFiles.insert(std::make_pair(messageId, std::set<Models::Roster::ElId>())).first;
|
||||
created = true;
|
||||
}
|
||||
itr->second.insert(Models::Roster::ElId(conv->getAccount(), conv->getJid()));
|
||||
if (created) {
|
||||
emit downloadFileRequest(messageId, url);
|
||||
}
|
||||
rosterModel.fileProgress(msgs, value, up);
|
||||
}
|
||||
|
||||
void Squawk::fileProgress(const QString& messageId, qreal value)
|
||||
void Squawk::fileDownloadComplete(const std::list<Shared::MessageInfo> msgs, const QString& path)
|
||||
{
|
||||
std::map<QString, std::set<Models::Roster::ElId>>::const_iterator itr = requestedFiles.find(messageId);
|
||||
if (itr == requestedFiles.end()) {
|
||||
qDebug() << "fileProgress in UI Squawk but there is nobody waiting for that id" << messageId << ", skipping";
|
||||
return;
|
||||
} else {
|
||||
const std::set<Models::Roster::ElId>& convs = itr->second;
|
||||
for (std::set<Models::Roster::ElId>::const_iterator cItr = convs.begin(), cEnd = convs.end(); cItr != cEnd; ++cItr) {
|
||||
const Models::Roster::ElId& id = *cItr;
|
||||
Conversations::const_iterator c = conversations.find(id);
|
||||
if (c != conversations.end()) {
|
||||
c->second->responseFileProgress(messageId, value);
|
||||
}
|
||||
if (currentConversation != 0 && currentConversation->getId() == id) {
|
||||
currentConversation->responseFileProgress(messageId, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
rosterModel.fileComplete(msgs, false);
|
||||
}
|
||||
|
||||
void Squawk::fileError(const QString& messageId, const QString& error)
|
||||
void Squawk::fileError(const std::list<Shared::MessageInfo> msgs, const QString& error, bool up)
|
||||
{
|
||||
std::map<QString, std::set<Models::Roster::ElId>>::const_iterator itr = requestedFiles.find(messageId);
|
||||
if (itr == requestedFiles.end()) {
|
||||
qDebug() << "fileError in UI Squawk but there is nobody waiting for that id" << messageId << ", skipping";
|
||||
return;
|
||||
} else {
|
||||
const std::set<Models::Roster::ElId>& convs = itr->second;
|
||||
for (std::set<Models::Roster::ElId>::const_iterator cItr = convs.begin(), cEnd = convs.end(); cItr != cEnd; ++cItr) {
|
||||
const Models::Roster::ElId& id = *cItr;
|
||||
Conversations::const_iterator c = conversations.find(id);
|
||||
if (c != conversations.end()) {
|
||||
c->second->fileError(messageId, error);
|
||||
}
|
||||
if (currentConversation != 0 && currentConversation->getId() == id) {
|
||||
currentConversation->fileError(messageId, error);
|
||||
}
|
||||
}
|
||||
requestedFiles.erase(itr);
|
||||
}
|
||||
rosterModel.fileError(msgs, error, up);
|
||||
}
|
||||
|
||||
void Squawk::fileLocalPathResponse(const QString& messageId, const QString& path)
|
||||
void Squawk::fileUploadComplete(const std::list<Shared::MessageInfo> msgs, const QString& path)
|
||||
{
|
||||
std::map<QString, std::set<Models::Roster::ElId>>::const_iterator itr = requestedFiles.find(messageId);
|
||||
if (itr == requestedFiles.end()) {
|
||||
qDebug() << "fileLocalPathResponse in UI Squawk but there is nobody waiting for that path, skipping";
|
||||
return;
|
||||
} else {
|
||||
const std::set<Models::Roster::ElId>& convs = itr->second;
|
||||
for (std::set<Models::Roster::ElId>::const_iterator cItr = convs.begin(), cEnd = convs.end(); cItr != cEnd; ++cItr) {
|
||||
const Models::Roster::ElId& id = *cItr;
|
||||
Conversations::const_iterator c = conversations.find(id);
|
||||
if (c != conversations.end()) {
|
||||
c->second->responseLocalFile(messageId, path);
|
||||
}
|
||||
if (currentConversation != 0 && currentConversation->getId() == id) {
|
||||
currentConversation->responseLocalFile(messageId, path);
|
||||
}
|
||||
}
|
||||
|
||||
requestedFiles.erase(itr);
|
||||
}
|
||||
}
|
||||
|
||||
void Squawk::onConversationRequestLocalFile(const QString& messageId, const QString& url)
|
||||
{
|
||||
Conversation* conv = static_cast<Conversation*>(sender());
|
||||
std::map<QString, std::set<Models::Roster::ElId>>::iterator itr = requestedFiles.find(messageId);
|
||||
bool created = false;
|
||||
if (itr == requestedFiles.end()) {
|
||||
itr = requestedFiles.insert(std::make_pair(messageId, std::set<Models::Roster::ElId>())).first;
|
||||
created = true;
|
||||
}
|
||||
itr->second.insert(Models::Roster::ElId(conv->getAccount(), conv->getJid()));
|
||||
if (created) {
|
||||
emit fileLocalPathRequest(messageId, url);
|
||||
}
|
||||
rosterModel.fileComplete(msgs, true);
|
||||
}
|
||||
|
||||
void Squawk::accountMessage(const QString& account, const Shared::Message& data)
|
||||
{
|
||||
const QString& from = data.getPenPalJid();
|
||||
Models::Roster::ElId id({account, from});
|
||||
Conversations::iterator itr = conversations.find(id);
|
||||
bool found = false;
|
||||
|
||||
if (currentConversation != 0 && currentConversation->getId() == id) {
|
||||
currentConversation->addMessage(data);
|
||||
QApplication::alert(this);
|
||||
if (!isVisible() && !data.getForwarded()) {
|
||||
notify(account, data);
|
||||
}
|
||||
found = true;
|
||||
}
|
||||
|
||||
if (itr != conversations.end()) {
|
||||
Conversation* conv = itr->second;
|
||||
conv->addMessage(data);
|
||||
QApplication::alert(conv);
|
||||
if (!found && conv->isMinimized()) {
|
||||
rosterModel.addMessage(account, data);
|
||||
}
|
||||
if (!conv->isVisible() && !data.getForwarded()) {
|
||||
notify(account, data);
|
||||
}
|
||||
found = true;
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
rosterModel.addMessage(account, data);
|
||||
if (!data.getForwarded()) {
|
||||
void Squawk::onUnnoticedMessage(const QString& account, const Shared::Message& msg)
|
||||
{
|
||||
notify(account, msg); //Telegram does this way - notifies even if the app is visible
|
||||
QApplication::alert(this);
|
||||
notify(account, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Squawk::changeMessage(const QString& account, const QString& jid, const QString& id, const QMap<QString, QVariant>& data)
|
||||
{
|
||||
Models::Roster::ElId eid({account, jid});
|
||||
bool found = false;
|
||||
|
||||
if (currentConversation != 0 && currentConversation->getId() == eid) {
|
||||
currentConversation->changeMessage(id, data);
|
||||
QApplication::alert(this);
|
||||
found = true;
|
||||
}
|
||||
|
||||
Conversations::iterator itr = conversations.find(eid);
|
||||
if (itr != conversations.end()) {
|
||||
Conversation* conv = itr->second;
|
||||
conv->changeMessage(id, data);
|
||||
if (!found && conv->isMinimized()) {
|
||||
rosterModel.changeMessage(account, jid, id, data);
|
||||
}
|
||||
found = true;
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
rosterModel.changeMessage(account, jid, id, data);
|
||||
}
|
||||
}
|
||||
|
||||
void Squawk::notify(const QString& account, const Shared::Message& msg)
|
||||
{
|
||||
@ -596,60 +460,20 @@ void Squawk::notify(const QString& account, const Shared::Message& msg)
|
||||
void Squawk::onConversationMessage(const Shared::Message& msg)
|
||||
{
|
||||
Conversation* conv = static_cast<Conversation*>(sender());
|
||||
emit sendMessage(conv->getAccount(), msg);
|
||||
Models::Roster::ElId id = conv->getId();
|
||||
QString acc = conv->getAccount();
|
||||
|
||||
if (currentConversation != 0 && currentConversation->getId() == id) {
|
||||
if (conv == currentConversation) {
|
||||
Conversations::iterator itr = conversations.find(id);
|
||||
if (itr != conversations.end()) {
|
||||
itr->second->addMessage(msg);
|
||||
}
|
||||
} else {
|
||||
currentConversation->addMessage(msg);
|
||||
}
|
||||
}
|
||||
rosterModel.addMessage(acc, msg);
|
||||
emit sendMessage(acc, msg);
|
||||
}
|
||||
|
||||
void Squawk::onConversationMessage(const Shared::Message& msg, const QString& path)
|
||||
void Squawk::onRequestArchive(const QString& account, const QString& jid, const QString& before)
|
||||
{
|
||||
Conversation* conv = static_cast<Conversation*>(sender());
|
||||
Models::Roster::ElId id = conv->getId();
|
||||
std::map<QString, std::set<Models::Roster::ElId>>::iterator itr = requestedFiles.insert(std::make_pair(msg.getId(), std::set<Models::Roster::ElId>())).first;
|
||||
itr->second.insert(id);
|
||||
|
||||
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 requestArchive(account, jid, 20, before); //TODO amount as a settings value
|
||||
}
|
||||
|
||||
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());
|
||||
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);
|
||||
}
|
||||
rosterModel.responseArchive(account, jid, list, last);
|
||||
}
|
||||
|
||||
void Squawk::removeAccount(const QString& account)
|
||||
@ -661,8 +485,6 @@ void Squawk::removeAccount(const QString& account)
|
||||
++itr;
|
||||
Conversation* conv = lItr->second;
|
||||
disconnect(conv, &Conversation::destroyed, this, &Squawk::onConversationClosed);
|
||||
disconnect(conv, &Conversation::requestArchive, this, &Squawk::onConversationRequestArchive);
|
||||
disconnect(conv, &Conversation::shown, this, &Squawk::onConversationShown);
|
||||
conv->close();
|
||||
conversations.erase(lItr);
|
||||
} else {
|
||||
@ -926,7 +748,6 @@ void Squawk::onActivateVCard(const QString& account, const QString& jid, bool ed
|
||||
{
|
||||
std::map<QString, VCard*>::const_iterator itr = vCards.find(jid);
|
||||
VCard* card;
|
||||
Models::Contact::Messages deque;
|
||||
if (itr != vCards.end()) {
|
||||
card = itr->second;
|
||||
} else {
|
||||
@ -1092,13 +913,8 @@ void Squawk::onPasswordPromptRejected()
|
||||
void Squawk::subscribeConversation(Conversation* conv)
|
||||
{
|
||||
connect(conv, &Conversation::destroyed, this, &Squawk::onConversationClosed);
|
||||
connect(conv, qOverload<const Shared::Message&>(&Conversation::sendMessage), this, qOverload<const Shared::Message&>(&Squawk::onConversationMessage));
|
||||
connect(conv, qOverload<const Shared::Message&, const QString&>(&Conversation::sendMessage),
|
||||
this, qOverload<const Shared::Message&, const QString&>(&Squawk::onConversationMessage));
|
||||
connect(conv, &Conversation::requestArchive, this, &Squawk::onConversationRequestArchive);
|
||||
connect(conv, &Conversation::requestLocalFile, this, &Squawk::onConversationRequestLocalFile);
|
||||
connect(conv, &Conversation::downloadFile, this, &Squawk::onConversationDownloadFile);
|
||||
connect(conv, &Conversation::shown, this, &Squawk::onConversationShown);
|
||||
connect(conv, &Conversation::sendMessage, this, &Squawk::onConversationMessage);
|
||||
connect(conv, &Conversation::notifyableMessage, this, &Squawk::notify);
|
||||
}
|
||||
|
||||
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::Contact::Messages deque;
|
||||
if (contact != 0) {
|
||||
currentConversation = new Chat(acc, contact);
|
||||
contact->getMessages(deque);
|
||||
} else if (room != 0) {
|
||||
currentConversation = new Room(acc, room);
|
||||
room->getMessages(deque);
|
||||
|
||||
if (!room->getJoined()) {
|
||||
emit setRoomJoined(id->account, id->name, true);
|
||||
@ -1185,9 +998,6 @@ void Squawk::onRosterSelectionChanged(const QModelIndex& current, const QModelIn
|
||||
}
|
||||
|
||||
subscribeConversation(currentConversation);
|
||||
for (Models::Contact::Messages::const_iterator itr = deque.begin(), end = deque.end(); itr != end; ++itr) {
|
||||
currentConversation->addMessage(*itr);
|
||||
}
|
||||
|
||||
if (res.size() > 0) {
|
||||
currentConversation->setPalResource(res);
|
||||
|
25
ui/squawk.h
@ -63,7 +63,6 @@ signals:
|
||||
void disconnectAccount(const QString&);
|
||||
void changeState(Shared::Availability state);
|
||||
void sendMessage(const QString& account, const Shared::Message& data);
|
||||
void sendMessage(const QString& account, const Shared::Message& data, const QString& path);
|
||||
void requestArchive(const QString& account, const QString& jid, int count, const QString& before);
|
||||
void subscribeContact(const QString& account, const QString& jid, const QString& reason);
|
||||
void unsubscribeContact(const QString& account, const QString& jid, const QString& reason);
|
||||
@ -76,11 +75,11 @@ signals:
|
||||
void setRoomAutoJoin(const QString& account, const QString& jid, bool joined);
|
||||
void addRoomRequest(const QString& account, const QString& jid, const QString& nick, const QString& password, bool autoJoin);
|
||||
void removeRoomRequest(const QString& account, const QString& jid);
|
||||
void fileLocalPathRequest(const QString& messageId, const QString& url);
|
||||
void downloadFileRequest(const QString& messageId, const QString& url);
|
||||
void fileDownloadRequest(const QString& url);
|
||||
void requestVCard(const QString& account, const QString& jid);
|
||||
void uploadVCard(const QString& account, const Shared::VCard& card);
|
||||
void responsePassword(const QString& account, const QString& password);
|
||||
void localPathInvalid(const QString& path);
|
||||
|
||||
public slots:
|
||||
void readSettings();
|
||||
@ -97,16 +96,17 @@ public slots:
|
||||
void removePresence(const QString& account, const QString& jid, const QString& name);
|
||||
void stateChanged(Shared::Availability state);
|
||||
void accountMessage(const QString& account, const Shared::Message& data);
|
||||
void responseArchive(const QString& account, const QString& jid, const std::list<Shared::Message>& list);
|
||||
void responseArchive(const QString& account, const QString& jid, const std::list<Shared::Message>& list, bool last);
|
||||
void addRoom(const QString& account, const QString jid, const QMap<QString, QVariant>& data);
|
||||
void changeRoom(const QString& account, const QString jid, const QMap<QString, QVariant>& data);
|
||||
void removeRoom(const QString& account, const QString jid);
|
||||
void addRoomParticipant(const QString& account, const QString& jid, const QString& name, const QMap<QString, QVariant>& data);
|
||||
void changeRoomParticipant(const QString& account, const QString& jid, const QString& name, const QMap<QString, QVariant>& data);
|
||||
void removeRoomParticipant(const QString& account, const QString& jid, const QString& name);
|
||||
void fileLocalPathResponse(const QString& messageId, const QString& path);
|
||||
void fileError(const QString& messageId, const QString& error);
|
||||
void fileProgress(const QString& messageId, qreal value);
|
||||
void fileError(const std::list<Shared::MessageInfo> msgs, const QString& error, bool up);
|
||||
void fileProgress(const std::list<Shared::MessageInfo> msgs, qreal value, bool up);
|
||||
void fileDownloadComplete(const std::list<Shared::MessageInfo> msgs, const QString& path);
|
||||
void fileUploadComplete(const std::list<Shared::MessageInfo> msgs, const QString& path);
|
||||
void responseVCard(const QString& jid, const Shared::VCard& card);
|
||||
void changeMessage(const QString& account, const QString& jid, const QString& id, const QMap<QString, QVariant>& data);
|
||||
void requestPassword(const QString& account);
|
||||
@ -120,7 +120,6 @@ private:
|
||||
Conversations conversations;
|
||||
QMenu* contextMenu;
|
||||
QDBusInterface dbus;
|
||||
std::map<QString, std::set<Models::Roster::ElId>> requestedFiles;
|
||||
std::map<QString, VCard*> vCards;
|
||||
std::deque<QString> requestedAccountsForPasswords;
|
||||
QInputDialog* prompt;
|
||||
@ -130,6 +129,8 @@ private:
|
||||
|
||||
protected:
|
||||
void closeEvent(QCloseEvent * event) override;
|
||||
|
||||
protected slots:
|
||||
void notify(const QString& account, const Shared::Message& msg);
|
||||
|
||||
private slots:
|
||||
@ -147,18 +148,16 @@ private slots:
|
||||
void onComboboxActivated(int index);
|
||||
void onRosterItemDoubleClicked(const QModelIndex& item);
|
||||
void onConversationMessage(const Shared::Message& msg);
|
||||
void onConversationMessage(const Shared::Message& msg, const QString& path);
|
||||
void onConversationRequestArchive(const QString& before);
|
||||
void onRequestArchive(const QString& account, const QString& jid, const QString& before);
|
||||
void onRosterContextMenu(const QPoint& point);
|
||||
void onConversationShown();
|
||||
void onConversationRequestLocalFile(const QString& messageId, const QString& url);
|
||||
void onConversationDownloadFile(const QString& messageId, const QString& url);
|
||||
void onItemCollepsed(const QModelIndex& index);
|
||||
void onPasswordPromptAccepted();
|
||||
void onPasswordPromptRejected();
|
||||
void onRosterSelectionChanged(const QModelIndex& current, const QModelIndex& previous);
|
||||
void onContextAboutToHide();
|
||||
|
||||
void onUnnoticedMessage(const QString& account, const Shared::Message& msg);
|
||||
|
||||
private:
|
||||
void checkNextAccountForPassword();
|
||||
void onPasswordPromptDone();
|
||||
|
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);
|
||||
|
||||
for (const std::pair<QString, QIcon> pair : entries) {
|
||||
for (const std::pair<QString, QIcon>& pair : entries) {
|
||||
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/>.
|
||||
*/
|
||||
|
||||
#include "dropshadoweffect.h"
|
||||
#include "QtMath"
|
||||
#include "exponentialblur.h"
|
||||
|
||||
static const int tileSize = 32;
|
||||
template <class T>
|
||||
@ -574,128 +573,7 @@ void expblur(QImage &img, qreal radius, bool improvedQuality = false, int transp
|
||||
}
|
||||
}
|
||||
|
||||
PixmapFilter::PixmapFilter(QObject* parent):QObject(parent) {}
|
||||
PixmapFilter::~PixmapFilter(){}
|
||||
QRectF PixmapFilter::boundingRectFor(const QRectF &rect) const {return rect;}
|
||||
|
||||
PixmapDropShadowFilter::PixmapDropShadowFilter(QObject *parent):
|
||||
PixmapFilter(parent),
|
||||
mColor(63, 63, 63, 180),
|
||||
mRadius(1),
|
||||
mThickness(2),
|
||||
top(true),
|
||||
right(true),
|
||||
bottom(true),
|
||||
left(true){}
|
||||
|
||||
PixmapDropShadowFilter::~PixmapDropShadowFilter() {}
|
||||
qreal PixmapDropShadowFilter::blurRadius() const {return mRadius;}
|
||||
void PixmapDropShadowFilter::setBlurRadius(qreal radius) {mRadius = radius;}
|
||||
QColor PixmapDropShadowFilter::color() const {return mColor;}
|
||||
void PixmapDropShadowFilter::setColor(const QColor &color) {mColor = color;}
|
||||
qreal PixmapDropShadowFilter::thickness() const {return mThickness;}
|
||||
void PixmapDropShadowFilter::setThickness(qreal thickness) {mThickness = thickness;}
|
||||
void PixmapDropShadowFilter::setFrame(bool ptop, bool pright, bool pbottom, bool pleft)
|
||||
void Utils::exponentialblur(QImage& img, qreal radius, bool improvedQuality, int transposed)
|
||||
{
|
||||
top = ptop;
|
||||
right = pright;
|
||||
bottom = pbottom;
|
||||
left = pleft;
|
||||
}
|
||||
|
||||
void DropShadowEffect::setThickness(qreal thickness)
|
||||
{
|
||||
if (filter.thickness() == thickness)
|
||||
return;
|
||||
|
||||
filter.setThickness(thickness);
|
||||
update();
|
||||
}
|
||||
|
||||
|
||||
void PixmapDropShadowFilter::draw(QPainter *p, const QPointF &pos, const QPixmap &px, const QRectF &src) const
|
||||
{
|
||||
if (px.isNull())
|
||||
return;
|
||||
|
||||
QImage tmp({px.width(), px.height() + int(mThickness)}, QImage::Format_ARGB32_Premultiplied);
|
||||
tmp.setDevicePixelRatio(px.devicePixelRatioF());
|
||||
tmp.fill(0);
|
||||
QPainter tmpPainter(&tmp);
|
||||
tmpPainter.setCompositionMode(QPainter::CompositionMode_Source);
|
||||
if (top) {
|
||||
QRectF shadow(0, 0, px.width(), mThickness);
|
||||
tmpPainter.fillRect(shadow, mColor);
|
||||
}
|
||||
if (right) {
|
||||
QRectF shadow(px.width() - mThickness, 0, mThickness, px.height());
|
||||
tmpPainter.fillRect(shadow, mColor);
|
||||
}
|
||||
if (bottom) {
|
||||
QRectF shadow(0, px.height() - mThickness, px.width(), mThickness * 2); //i have no idea why, but it leaves some unpainted stripe without some spare space
|
||||
tmpPainter.fillRect(shadow, mColor);
|
||||
}
|
||||
if (left) {
|
||||
QRectF shadow(0, 0, mThickness, px.height());
|
||||
tmpPainter.fillRect(shadow, mColor);
|
||||
}
|
||||
|
||||
expblur<12, 10, false>(tmp, mRadius, false, 0);
|
||||
tmpPainter.end();
|
||||
|
||||
// Draw the actual pixmap...
|
||||
p->drawPixmap(pos, px, src);
|
||||
|
||||
// draw the blurred drop shadow...
|
||||
p->drawImage(pos, tmp);
|
||||
}
|
||||
|
||||
qreal DropShadowEffect::blurRadius() const {return filter.blurRadius();}
|
||||
void DropShadowEffect::setBlurRadius(qreal blurRadius)
|
||||
{
|
||||
if (qFuzzyCompare(filter.blurRadius(), blurRadius))
|
||||
return;
|
||||
|
||||
filter.setBlurRadius(blurRadius);
|
||||
updateBoundingRect();
|
||||
emit blurRadiusChanged(blurRadius);
|
||||
}
|
||||
|
||||
void DropShadowEffect::setFrame(bool top, bool right, bool bottom, bool left)
|
||||
{
|
||||
filter.setFrame(top, right, bottom, left);
|
||||
update();
|
||||
}
|
||||
|
||||
|
||||
QColor DropShadowEffect::color() const {return filter.color();}
|
||||
void DropShadowEffect::setColor(const QColor &color)
|
||||
{
|
||||
if (filter.color() == color)
|
||||
return;
|
||||
|
||||
filter.setColor(color);
|
||||
update();
|
||||
emit colorChanged(color);
|
||||
}
|
||||
|
||||
void DropShadowEffect::draw(QPainter* painter)
|
||||
{
|
||||
if (filter.blurRadius() <= 0 && filter.thickness() == 0) {
|
||||
drawSource(painter);
|
||||
return;
|
||||
}
|
||||
|
||||
PixmapPadMode mode = PadToEffectiveBoundingRect;
|
||||
|
||||
// Draw pixmap in device coordinates to avoid pixmap scaling.
|
||||
QPoint offset;
|
||||
const QPixmap pixmap = sourcePixmap(Qt::DeviceCoordinates, &offset, mode);
|
||||
if (pixmap.isNull())
|
||||
return;
|
||||
|
||||
QTransform restoreTransform = painter->worldTransform();
|
||||
painter->setWorldTransform(QTransform());
|
||||
filter.draw(painter, offset, pixmap);
|
||||
painter->setWorldTransform(restoreTransform);
|
||||
expblur<12, 10, false>(img, radius, improvedQuality, transposed);
|
||||
}
|
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)
|
||||
|
||||
# Find the QtWidgets library
|
||||
find_package(Qt5Widgets CONFIG REQUIRED)
|
||||
find_package(Qt5Widgets CONFIG REQUIRED COMPONENTS Widgets Core)
|
||||
|
||||
add_subdirectory(vcard)
|
||||
|
||||
@ -21,9 +21,11 @@ set(squawkWidgets_SRC
|
||||
joinconference.cpp
|
||||
)
|
||||
|
||||
# Tell CMake to create the helloworld executable
|
||||
add_library(squawkWidgets ${squawkWidgets_SRC})
|
||||
add_library(squawkWidgets STATIC ${squawkWidgets_SRC})
|
||||
|
||||
# Use the Widgets module from Qt 5.
|
||||
target_link_libraries(squawkWidgets vCardUI)
|
||||
target_link_libraries(squawkWidgets squawkUIUtils)
|
||||
target_link_libraries(squawkWidgets Qt5::Widgets)
|
||||
|
||||
qt5_use_modules(squawkWidgets Core Widgets)
|
||||
|
@ -19,7 +19,7 @@
|
||||
#include "chat.h"
|
||||
|
||||
Chat::Chat(Models::Account* acc, Models::Contact* p_contact, QWidget* parent):
|
||||
Conversation(false, acc, p_contact->getJid(), "", parent),
|
||||
Conversation(false, acc, p_contact, p_contact->getJid(), "", parent),
|
||||
contact(p_contact)
|
||||
{
|
||||
setName(p_contact->getContactName());
|
||||
@ -71,31 +71,14 @@ Shared::Message Chat::createMessage() const
|
||||
return msg;
|
||||
}
|
||||
|
||||
void Chat::addMessage(const Shared::Message& data)
|
||||
void Chat::onMessage(const Shared::Message& data)
|
||||
{
|
||||
Conversation::addMessage(data);
|
||||
Conversation::onMessage(data);
|
||||
|
||||
if (!data.getOutgoing()) { //TODO need to check if that was the last message
|
||||
if (!data.getOutgoing()) {
|
||||
const QString& res = data.getPenPalResource();
|
||||
if (res.size() > 0) {
|
||||
setPalResource(res);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Chat::setName(const QString& name)
|
||||
{
|
||||
Conversation::setName(name);
|
||||
line->setPalName(getJid(), name);
|
||||
}
|
||||
|
||||
void Chat::setAvatar(const QString& path)
|
||||
{
|
||||
Conversation::setAvatar(path);
|
||||
|
||||
if (path.size() == 0) {
|
||||
line->dropPalAvatar(contact->getJid());
|
||||
} else {
|
||||
line->setPalAvatar(contact->getJid(), path);
|
||||
}
|
||||
}
|
||||
|
@ -35,15 +35,12 @@ public:
|
||||
Chat(Models::Account* acc, Models::Contact* p_contact, QWidget* parent = 0);
|
||||
~Chat();
|
||||
|
||||
void addMessage(const Shared::Message & data) override;
|
||||
void setAvatar(const QString& path) override;
|
||||
|
||||
protected slots:
|
||||
void onContactChanged(Models::Item* item, int row, int col);
|
||||
|
||||
protected:
|
||||
void setName(const QString & name) override;
|
||||
Shared::Message createMessage() const override;
|
||||
void onMessage(const Shared::Message& msg) override;
|
||||
|
||||
private:
|
||||
void updateState();
|
||||
|
@ -18,7 +18,6 @@
|
||||
|
||||
#include "conversation.h"
|
||||
#include "ui_conversation.h"
|
||||
#include "ui/utils/dropshadoweffect.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QScrollBar>
|
||||
@ -29,31 +28,44 @@
|
||||
#include <QAbstractTextDocumentLayout>
|
||||
#include <QCoreApplication>
|
||||
|
||||
Conversation::Conversation(bool muc, Models::Account* acc, const QString pJid, const QString pRes, QWidget* parent):
|
||||
Conversation::Conversation(bool muc, Models::Account* acc, Models::Element* el, const QString pJid, const QString pRes, QWidget* parent):
|
||||
QWidget(parent),
|
||||
isMuc(muc),
|
||||
account(acc),
|
||||
element(el),
|
||||
palJid(pJid),
|
||||
activePalResource(pRes),
|
||||
line(new MessageLine(muc)),
|
||||
m_ui(new Ui::Conversation()),
|
||||
ker(),
|
||||
scrollResizeCatcher(),
|
||||
vis(),
|
||||
thread(),
|
||||
statusIcon(0),
|
||||
statusLabel(0),
|
||||
filesLayout(0),
|
||||
overlay(new QWidget()),
|
||||
filesToAttach(),
|
||||
scroll(down),
|
||||
feed(new FeedView()),
|
||||
delegate(new MessageDelegate(this)),
|
||||
manualSliderChange(false),
|
||||
requestingHistory(false),
|
||||
everShown(false),
|
||||
tsb(QApplication::style()->styleHint(QStyle::SH_ScrollBar_Transient) == 1)
|
||||
tsb(QApplication::style()->styleHint(QStyle::SH_ScrollBar_Transient) == 1),
|
||||
shadow(10, 1, Qt::black, this),
|
||||
contextMenu(new QMenu())
|
||||
{
|
||||
m_ui->setupUi(this);
|
||||
|
||||
shadow.setFrames(true, false, true, false);
|
||||
|
||||
feed->setItemDelegate(delegate);
|
||||
feed->setFrameShape(QFrame::NoFrame);
|
||||
feed->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
delegate->initializeFonts(feed->getFont());
|
||||
feed->setModel(el->feed);
|
||||
el->feed->incrementObservers();
|
||||
m_ui->widget->layout()->addWidget(feed);
|
||||
|
||||
connect(el->feed, &Models::MessageFeed::newMessage, this, &Conversation::onFeedMessage);
|
||||
connect(feed, &FeedView::resized, this, &Conversation::positionShadow);
|
||||
connect(feed, &FeedView::customContextMenuRequested, this, &Conversation::onFeedContext);
|
||||
|
||||
connect(acc, &Models::Account::childChanged, this, &Conversation::onAccountChanged);
|
||||
|
||||
filesLayout = new FlowLayout(m_ui->filesPanel, 0);
|
||||
@ -63,14 +75,7 @@ Conversation::Conversation(bool muc, Models::Account* acc, const QString pJid, c
|
||||
statusLabel = m_ui->statusLabel;
|
||||
|
||||
connect(&ker, &KeyEnterReceiver::enterPressed, this, &Conversation::onEnterPressed);
|
||||
connect(&scrollResizeCatcher, &Resizer::resized, this, &Conversation::onScrollResize);
|
||||
connect(&vis, &VisibilityCatcher::shown, this, &Conversation::onScrollResize);
|
||||
connect(&vis, &VisibilityCatcher::hidden, this, &Conversation::onScrollResize);
|
||||
connect(m_ui->sendButton, &QPushButton::clicked, this, &Conversation::onEnterPressed);
|
||||
connect(line, &MessageLine::resize, this, &Conversation::onMessagesResize);
|
||||
connect(line, &MessageLine::downloadFile, this, &Conversation::downloadFile);
|
||||
connect(line, &MessageLine::uploadFile, this, qOverload<const Shared::Message&, const QString&>(&Conversation::sendMessage));
|
||||
connect(line, &MessageLine::requestLocalFile, this, &Conversation::requestLocalFile);
|
||||
connect(m_ui->attachButton, &QPushButton::clicked, this, &Conversation::onAttach);
|
||||
connect(m_ui->clearButton, &QPushButton::clicked, this, &Conversation::onClearButton);
|
||||
connect(m_ui->messageEditor->document()->documentLayout(), &QAbstractTextDocumentLayout::documentSizeChanged,
|
||||
@ -78,23 +83,43 @@ Conversation::Conversation(bool muc, Models::Account* acc, const QString pJid, c
|
||||
|
||||
m_ui->messageEditor->installEventFilter(&ker);
|
||||
|
||||
QScrollBar* vs = m_ui->scrollArea->verticalScrollBar();
|
||||
m_ui->scrollArea->setWidget(line);
|
||||
vs->installEventFilter(&vis);
|
||||
|
||||
line->setAutoFillBackground(false);
|
||||
if (testAttribute(Qt::WA_TranslucentBackground)) {
|
||||
m_ui->scrollArea->setAutoFillBackground(false);
|
||||
} else {
|
||||
m_ui->scrollArea->setBackgroundRole(QPalette::Base);
|
||||
//line->setAutoFillBackground(false);
|
||||
//if (testAttribute(Qt::WA_TranslucentBackground)) {
|
||||
//m_ui->scrollArea->setAutoFillBackground(false);
|
||||
//} else {
|
||||
//m_ui->scrollArea->setBackgroundRole(QPalette::Base);
|
||||
//}
|
||||
|
||||
//line->setMyAvatarPath(acc->getAvatarPath());
|
||||
//line->setMyName(acc->getName());
|
||||
|
||||
initializeOverlay();
|
||||
}
|
||||
|
||||
connect(vs, &QScrollBar::valueChanged, this, &Conversation::onSliderValueChanged);
|
||||
m_ui->scrollArea->installEventFilter(&scrollResizeCatcher);
|
||||
Conversation::~Conversation()
|
||||
{
|
||||
delete contextMenu;
|
||||
|
||||
line->setMyAvatarPath(acc->getAvatarPath());
|
||||
line->setMyName(acc->getName());
|
||||
element->feed->decrementObservers();
|
||||
}
|
||||
|
||||
void Conversation::onAccountChanged(Models::Item* item, int row, int col)
|
||||
{
|
||||
if (item == account) {
|
||||
if (col == 2 && account->getState() == Shared::ConnectionState::connected) { //to request the history when we're back online after reconnect
|
||||
//if (!requestingHistory) {
|
||||
//requestingHistory = true;
|
||||
//line->showBusyIndicator();
|
||||
//emit requestArchive("");
|
||||
//scroll = down;
|
||||
//}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Conversation::initializeOverlay()
|
||||
{
|
||||
QGridLayout* gr = static_cast<QGridLayout*>(layout());
|
||||
QLabel* progressLabel = new QLabel(tr("Drop files here to attach them to your message"));
|
||||
gr->addWidget(overlay, 0, 0, 2, 1);
|
||||
@ -115,36 +140,6 @@ Conversation::Conversation(bool muc, Models::Account* acc, const QString pJid, c
|
||||
nl->addWidget(progressLabel);
|
||||
nl->addStretch();
|
||||
overlay->hide();
|
||||
|
||||
applyVisualEffects();
|
||||
}
|
||||
|
||||
Conversation::~Conversation()
|
||||
{
|
||||
}
|
||||
|
||||
void Conversation::onAccountChanged(Models::Item* item, int row, int col)
|
||||
{
|
||||
if (item == account) {
|
||||
if (col == 2 && account->getState() == Shared::ConnectionState::connected) {
|
||||
if (!requestingHistory) {
|
||||
requestingHistory = true;
|
||||
line->showBusyIndicator();
|
||||
emit requestArchive("");
|
||||
scroll = down;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Conversation::applyVisualEffects()
|
||||
{
|
||||
DropShadowEffect *e1 = new DropShadowEffect;
|
||||
e1->setBlurRadius(10);
|
||||
e1->setColor(Qt::black);
|
||||
e1->setThickness(1);
|
||||
e1->setFrame(true, false, true, false);
|
||||
m_ui->scrollArea->setGraphicsEffect(e1);
|
||||
}
|
||||
|
||||
void Conversation::setName(const QString& name)
|
||||
@ -163,22 +158,6 @@ QString Conversation::getJid() const
|
||||
return palJid;
|
||||
}
|
||||
|
||||
void Conversation::addMessage(const Shared::Message& data)
|
||||
{
|
||||
int pos = m_ui->scrollArea->verticalScrollBar()->sliderPosition();
|
||||
int max = m_ui->scrollArea->verticalScrollBar()->maximum();
|
||||
|
||||
MessageLine::Position place = line->message(data);
|
||||
if (place == MessageLine::invalid) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void Conversation::changeMessage(const QString& id, const QMap<QString, QVariant>& data)
|
||||
{
|
||||
line->changeMessage(id, data);
|
||||
}
|
||||
|
||||
KeyEnterReceiver::KeyEnterReceiver(QObject* parent): QObject(parent), ownEvent(false) {}
|
||||
|
||||
bool KeyEnterReceiver::eventFilter(QObject* obj, QEvent* event)
|
||||
@ -226,93 +205,19 @@ void Conversation::onEnterPressed()
|
||||
m_ui->messageEditor->clear();
|
||||
Shared::Message msg = createMessage();
|
||||
msg.setBody(body);
|
||||
addMessage(msg);
|
||||
emit sendMessage(msg);
|
||||
}
|
||||
if (filesToAttach.size() > 0) {
|
||||
for (Badge* badge : filesToAttach) {
|
||||
Shared::Message msg = createMessage();
|
||||
line->appendMessageWithUpload(msg, badge->id);
|
||||
usleep(1000); //this is required for the messages not to have equal time when appending into messageline
|
||||
msg.setAttachPath(badge->id);
|
||||
element->feed->registerUpload(msg.getId());
|
||||
emit sendMessage(msg);
|
||||
}
|
||||
clearAttachedFiles();
|
||||
}
|
||||
}
|
||||
|
||||
void Conversation::appendMessageWithUpload(const Shared::Message& data, const QString& path)
|
||||
{
|
||||
line->appendMessageWithUploadNoSiganl(data, path);
|
||||
}
|
||||
|
||||
void Conversation::onMessagesResize(int amount)
|
||||
{
|
||||
manualSliderChange = true;
|
||||
switch (scroll) {
|
||||
case down:
|
||||
m_ui->scrollArea->verticalScrollBar()->setValue(m_ui->scrollArea->verticalScrollBar()->maximum());
|
||||
break;
|
||||
case keep: {
|
||||
int max = m_ui->scrollArea->verticalScrollBar()->maximum();
|
||||
int value = m_ui->scrollArea->verticalScrollBar()->value() + amount;
|
||||
m_ui->scrollArea->verticalScrollBar()->setValue(value);
|
||||
|
||||
if (value == max) {
|
||||
scroll = down;
|
||||
} else {
|
||||
scroll = nothing;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
manualSliderChange = false;
|
||||
}
|
||||
|
||||
void Conversation::onSliderValueChanged(int value)
|
||||
{
|
||||
if (!manualSliderChange) {
|
||||
if (value == m_ui->scrollArea->verticalScrollBar()->maximum()) {
|
||||
scroll = down;
|
||||
} else {
|
||||
if (!requestingHistory && value == 0) {
|
||||
requestingHistory = true;
|
||||
line->showBusyIndicator();
|
||||
emit requestArchive(line->firstMessageId());
|
||||
scroll = keep;
|
||||
} else {
|
||||
scroll = nothing;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Conversation::responseArchive(const std::list<Shared::Message> list)
|
||||
{
|
||||
requestingHistory = false;
|
||||
scroll = keep;
|
||||
|
||||
line->hideBusyIndicator();
|
||||
for (std::list<Shared::Message>::const_iterator itr = list.begin(), end = list.end(); itr != end; ++itr) {
|
||||
addMessage(*itr);
|
||||
}
|
||||
}
|
||||
|
||||
void Conversation::showEvent(QShowEvent* event)
|
||||
{
|
||||
if (!everShown) {
|
||||
everShown = true;
|
||||
line->showBusyIndicator();
|
||||
requestingHistory = true;
|
||||
scroll = keep;
|
||||
emit requestArchive(line->firstMessageId());
|
||||
}
|
||||
emit shown();
|
||||
|
||||
QWidget::showEvent(event);
|
||||
|
||||
}
|
||||
|
||||
void Conversation::onAttach()
|
||||
{
|
||||
QFileDialog* d = new QFileDialog(this, tr("Chose a file to send"));
|
||||
@ -340,34 +245,6 @@ void Conversation::setStatus(const QString& status)
|
||||
statusLabel->setText(Shared::processMessageBody(status));
|
||||
}
|
||||
|
||||
void Conversation::onScrollResize()
|
||||
{
|
||||
if (everShown) {
|
||||
int size = m_ui->scrollArea->width();
|
||||
QScrollBar* bar = m_ui->scrollArea->verticalScrollBar();
|
||||
if (bar->isVisible() && !tsb) {
|
||||
size -= bar->width();
|
||||
|
||||
}
|
||||
line->setMaximumWidth(size);
|
||||
}
|
||||
}
|
||||
|
||||
void Conversation::responseFileProgress(const QString& messageId, qreal progress)
|
||||
{
|
||||
line->fileProgress(messageId, progress);
|
||||
}
|
||||
|
||||
void Conversation::fileError(const QString& messageId, const QString& error)
|
||||
{
|
||||
line->fileError(messageId, error);
|
||||
}
|
||||
|
||||
void Conversation::responseLocalFile(const QString& messageId, const QString& path)
|
||||
{
|
||||
line->responseLocalFile(messageId, path);
|
||||
}
|
||||
|
||||
Models::Roster::ElId Conversation::getId() const
|
||||
{
|
||||
return {getAccount(), getJid()};
|
||||
@ -444,7 +321,7 @@ void Conversation::onTextEditDocSizeChanged(const QSizeF& size)
|
||||
|
||||
void Conversation::setFeedFrames(bool top, bool right, bool bottom, bool left)
|
||||
{
|
||||
static_cast<DropShadowEffect*>(m_ui->scrollArea->graphicsEffect())->setFrame(top, right, bottom, left);
|
||||
shadow.setFrames(top, right, bottom, left);
|
||||
}
|
||||
|
||||
void Conversation::dragEnterEvent(QDragEnterEvent* event)
|
||||
@ -504,21 +381,55 @@ Shared::Message Conversation::createMessage() const
|
||||
return msg;
|
||||
}
|
||||
|
||||
bool VisibilityCatcher::eventFilter(QObject* obj, QEvent* event)
|
||||
void Conversation::onFeedMessage(const Shared::Message& msg)
|
||||
{
|
||||
if (event->type() == QEvent::Show) {
|
||||
emit shown();
|
||||
this->onMessage(msg);
|
||||
}
|
||||
|
||||
if (event->type() == QEvent::Hide) {
|
||||
emit hidden();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
VisibilityCatcher::VisibilityCatcher(QWidget* parent):
|
||||
QObject(parent)
|
||||
void Conversation::onMessage(const Shared::Message& msg)
|
||||
{
|
||||
if (!msg.getForwarded()) {
|
||||
QApplication::alert(this);
|
||||
if (window()->windowState().testFlag(Qt::WindowMinimized)) {
|
||||
emit notifyableMessage(getAccount(), msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Conversation::positionShadow()
|
||||
{
|
||||
int w = width();
|
||||
int h = feed->height();
|
||||
|
||||
shadow.resize(w, h);
|
||||
shadow.move(feed->pos());
|
||||
shadow.raise();
|
||||
}
|
||||
|
||||
void Conversation::onFeedContext(const QPoint& pos)
|
||||
{
|
||||
QModelIndex index = feed->indexAt(pos);
|
||||
if (index.isValid()) {
|
||||
Shared::Message* item = static_cast<Shared::Message*>(index.internalPointer());
|
||||
|
||||
contextMenu->clear();
|
||||
bool showMenu = false;
|
||||
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 <QMimeData>
|
||||
#include <QFileInfo>
|
||||
#include <QGraphicsOpacityEffect>
|
||||
#include <QMenu>
|
||||
#include <QAction>
|
||||
#include <QDesktopServices>
|
||||
|
||||
#include "shared/message.h"
|
||||
#include "order.h"
|
||||
#include "ui/models/account.h"
|
||||
#include "ui/models/roster.h"
|
||||
#include "ui/utils/messageline.h"
|
||||
#include "ui/utils/resizer.h"
|
||||
#include "ui/utils/flowlayout.h"
|
||||
#include "ui/utils/badge.h"
|
||||
#include "ui/utils/feedview.h"
|
||||
#include "ui/utils/messagedelegate.h"
|
||||
#include "ui/utils/shadowoverlay.h"
|
||||
#include "shared/icons.h"
|
||||
#include "shared/utils.h"
|
||||
|
||||
@ -54,54 +59,32 @@ signals:
|
||||
void enterPressed();
|
||||
};
|
||||
|
||||
class VisibilityCatcher : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
VisibilityCatcher(QWidget* parent = nullptr);
|
||||
|
||||
protected:
|
||||
bool eventFilter(QObject* obj, QEvent* event) override;
|
||||
|
||||
signals:
|
||||
void hidden();
|
||||
void shown();
|
||||
};
|
||||
|
||||
class Conversation : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
Conversation(bool muc, Models::Account* acc, const QString pJid, const QString pRes, QWidget* parent = 0);
|
||||
Conversation(bool muc, Models::Account* acc, Models::Element* el, const QString pJid, const QString pRes, QWidget* parent = 0);
|
||||
~Conversation();
|
||||
|
||||
QString getJid() const;
|
||||
QString getAccount() const;
|
||||
QString getPalResource() const;
|
||||
Models::Roster::ElId getId() const;
|
||||
virtual void addMessage(const Shared::Message& data);
|
||||
|
||||
void setPalResource(const QString& res);
|
||||
void responseArchive(const std::list<Shared::Message> list);
|
||||
void showEvent(QShowEvent * event) override;
|
||||
void responseLocalFile(const QString& messageId, const QString& path);
|
||||
void fileError(const QString& messageId, const QString& error);
|
||||
void responseFileProgress(const QString& messageId, qreal progress);
|
||||
virtual void setAvatar(const QString& path);
|
||||
void changeMessage(const QString& id, const QMap<QString, QVariant>& data);
|
||||
void setFeedFrames(bool top, bool right, bool bottom, bool left);
|
||||
virtual void appendMessageWithUpload(const Shared::Message& data, const QString& path);
|
||||
|
||||
signals:
|
||||
void sendMessage(const Shared::Message& message);
|
||||
void sendMessage(const Shared::Message& message, const QString& path);
|
||||
void requestArchive(const QString& before);
|
||||
void shown();
|
||||
void requestLocalFile(const QString& messageId, const QString& url);
|
||||
void downloadFile(const QString& messageId, const QString& url);
|
||||
void notifyableMessage(const QString& account, const Shared::Message& msg);
|
||||
|
||||
protected:
|
||||
virtual void setName(const QString& name);
|
||||
void applyVisualEffects();
|
||||
virtual Shared::Message createMessage() const;
|
||||
void setStatus(const QString& status);
|
||||
void addAttachedFile(const QString& path);
|
||||
@ -110,47 +93,44 @@ protected:
|
||||
void dragEnterEvent(QDragEnterEvent* event) override;
|
||||
void dragLeaveEvent(QDragLeaveEvent* event) override;
|
||||
void dropEvent(QDropEvent* event) override;
|
||||
void initializeOverlay();
|
||||
virtual void onMessage(const Shared::Message& msg);
|
||||
|
||||
protected slots:
|
||||
void onEnterPressed();
|
||||
void onMessagesResize(int amount);
|
||||
void onSliderValueChanged(int value);
|
||||
void onAttach();
|
||||
void onFileSelected();
|
||||
void onScrollResize();
|
||||
void onBadgeClose();
|
||||
void onClearButton();
|
||||
void onTextEditDocSizeChanged(const QSizeF& size);
|
||||
void onAccountChanged(Models::Item* item, int row, int col);
|
||||
void onFeedMessage(const Shared::Message& msg);
|
||||
void positionShadow();
|
||||
void onFeedContext(const QPoint &pos);
|
||||
|
||||
public:
|
||||
const bool isMuc;
|
||||
|
||||
protected:
|
||||
enum Scroll {
|
||||
nothing,
|
||||
keep,
|
||||
down
|
||||
};
|
||||
Models::Account* account;
|
||||
Models::Element* element;
|
||||
QString palJid;
|
||||
QString activePalResource;
|
||||
MessageLine* line;
|
||||
QScopedPointer<Ui::Conversation> m_ui;
|
||||
KeyEnterReceiver ker;
|
||||
Resizer scrollResizeCatcher;
|
||||
VisibilityCatcher vis;
|
||||
QString thread;
|
||||
QLabel* statusIcon;
|
||||
QLabel* statusLabel;
|
||||
FlowLayout* filesLayout;
|
||||
QWidget* overlay;
|
||||
W::Order<Badge*, Badge::Comparator> filesToAttach;
|
||||
Scroll scroll;
|
||||
FeedView* feed;
|
||||
MessageDelegate* delegate;
|
||||
bool manualSliderChange;
|
||||
bool requestingHistory;
|
||||
bool everShown;
|
||||
bool tsb; //transient scroll bars
|
||||
|
||||
ShadowOverlay shadow;
|
||||
QMenu* contextMenu;
|
||||
};
|
||||
|
||||
#endif // CONVERSATION_H
|
||||
|
@ -214,62 +214,8 @@
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QScrollArea" name="scrollArea">
|
||||
<property name="autoFillBackground">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::NoFrame</enum>
|
||||
</property>
|
||||
<property name="lineWidth">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="midLineWidth">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="horizontalScrollBarPolicy">
|
||||
<enum>Qt::ScrollBarAlwaysOff</enum>
|
||||
</property>
|
||||
<property name="sizeAdjustPolicy">
|
||||
<enum>QAbstractScrollArea::AdjustIgnored</enum>
|
||||
</property>
|
||||
<property name="widgetResizable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<widget class="QWidget" name="scrollAreaWidgetContents">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>520</width>
|
||||
<height>385</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
<zorder>scrollArea</zorder>
|
||||
<zorder>widget_3</zorder>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QWidget" name="widget_2" native="true">
|
||||
|
@ -19,27 +19,16 @@
|
||||
#include "room.h"
|
||||
|
||||
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)
|
||||
{
|
||||
setName(p_room->getName());
|
||||
line->setMyName(room->getNick());
|
||||
setStatus(room->getSubject());
|
||||
setAvatar(room->getAvatarPath());
|
||||
|
||||
connect(room, &Models::Room::childChanged, this, &Room::onRoomChanged);
|
||||
connect(room, &Models::Room::participantJoined, this, &Room::onParticipantJoined);
|
||||
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()
|
||||
@ -75,30 +64,14 @@ void Room::onRoomChanged(Models::Item* item, int row, int col)
|
||||
setAvatar(room->getAvatarPath());
|
||||
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)
|
||||
{
|
||||
QString aPath = participant.getAvatarPath();
|
||||
if (aPath.size() > 0) {
|
||||
line->setPalAvatar(participant.getName(), aPath);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void Room::onParticipantLeft(const QString& name)
|
||||
{
|
||||
line->movePalAvatarToEx(name);
|
||||
}
|
||||
|