477 lines
18 KiB
C++
477 lines
18 KiB
C++
|
// Squawk messenger.
|
||
|
// Copyright (C) 2019 Yury Gubich <blue@macaw.me>
|
||
|
//
|
||
|
// This program is free software: you can redistribute it and/or modify
|
||
|
// it under the terms of the GNU General Public License as published by
|
||
|
// the Free Software Foundation, either version 3 of the License, or
|
||
|
// (at your option) any later version.
|
||
|
//
|
||
|
// This program is distributed in the hope that it will be useful,
|
||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
// GNU General Public License for more details.
|
||
|
//
|
||
|
// You should have received a copy of the GNU General Public License
|
||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||
|
|
||
|
#include "application.h"
|
||
|
|
||
|
Application::Application(Core::Squawk* p_core):
|
||
|
QObject(),
|
||
|
availability(Shared::Availability::offline),
|
||
|
core(p_core),
|
||
|
squawk(nullptr),
|
||
|
notifications("org.freedesktop.Notifications", "/org/freedesktop/Notifications", "org.freedesktop.Notifications", QDBusConnection::sessionBus()),
|
||
|
roster(),
|
||
|
conversations(),
|
||
|
dialogueQueue(roster),
|
||
|
nowQuitting(false),
|
||
|
destroyingSquawk(false)
|
||
|
{
|
||
|
connect(&roster, &Models::Roster::unnoticedMessage, this, &Application::notify);
|
||
|
|
||
|
|
||
|
//connecting myself to the backed
|
||
|
connect(this, &Application::changeState, core, &Core::Squawk::changeState);
|
||
|
connect(this, &Application::setRoomJoined, core, &Core::Squawk::setRoomJoined);
|
||
|
connect(this, &Application::setRoomAutoJoin, core, &Core::Squawk::setRoomAutoJoin);
|
||
|
connect(this, &Application::subscribeContact, core, &Core::Squawk::subscribeContact);
|
||
|
connect(this, &Application::unsubscribeContact, core, &Core::Squawk::unsubscribeContact);
|
||
|
connect(this, &Application::replaceMessage, core, &Core::Squawk::replaceMessage);
|
||
|
connect(this, &Application::sendMessage, core, &Core::Squawk::sendMessage);
|
||
|
connect(this, &Application::resendMessage, core, &Core::Squawk::resendMessage);
|
||
|
connect(&roster, &Models::Roster::requestArchive,
|
||
|
std::bind(&Core::Squawk::requestArchive, core, std::placeholders::_1, std::placeholders::_2, 20, std::placeholders::_3));
|
||
|
|
||
|
connect(&dialogueQueue, &DialogQueue::modifyAccountRequest, core, &Core::Squawk::modifyAccountRequest);
|
||
|
connect(&dialogueQueue, &DialogQueue::responsePassword, core, &Core::Squawk::responsePassword);
|
||
|
connect(&dialogueQueue, &DialogQueue::disconnectAccount, core, &Core::Squawk::disconnectAccount);
|
||
|
|
||
|
connect(&roster, &Models::Roster::fileDownloadRequest, core, &Core::Squawk::fileDownloadRequest);
|
||
|
connect(&roster, &Models::Roster::localPathInvalid, core, &Core::Squawk::onLocalPathInvalid);
|
||
|
|
||
|
|
||
|
//coonecting backend to myself
|
||
|
connect(core, &Core::Squawk::stateChanged, this, &Application::stateChanged);
|
||
|
|
||
|
connect(core, &Core::Squawk::accountMessage, &roster, &Models::Roster::addMessage);
|
||
|
connect(core, &Core::Squawk::responseArchive, &roster, &Models::Roster::responseArchive);
|
||
|
connect(core, &Core::Squawk::changeMessage, &roster, &Models::Roster::changeMessage);
|
||
|
|
||
|
connect(core, &Core::Squawk::newAccount, &roster, &Models::Roster::addAccount);
|
||
|
connect(core, &Core::Squawk::changeAccount, this, &Application::changeAccount);
|
||
|
connect(core, &Core::Squawk::removeAccount, this, &Application::removeAccount);
|
||
|
|
||
|
connect(core, &Core::Squawk::addContact, this, &Application::addContact);
|
||
|
connect(core, &Core::Squawk::addGroup, this, &Application::addGroup);
|
||
|
connect(core, &Core::Squawk::removeGroup, &roster, &Models::Roster::removeGroup);
|
||
|
connect(core, qOverload<const QString&, const QString&>(&Core::Squawk::removeContact),
|
||
|
&roster, qOverload<const QString&, const QString&>(&Models::Roster::removeContact));
|
||
|
connect(core, qOverload<const QString&, const QString&, const QString&>(&Core::Squawk::removeContact),
|
||
|
&roster, qOverload<const QString&, const QString&, const QString&>(&Models::Roster::removeContact));
|
||
|
connect(core, &Core::Squawk::changeContact, &roster, &Models::Roster::changeContact);
|
||
|
connect(core, &Core::Squawk::addPresence, &roster, &Models::Roster::addPresence);
|
||
|
connect(core, &Core::Squawk::removePresence, &roster, &Models::Roster::removePresence);
|
||
|
|
||
|
connect(core, &Core::Squawk::addRoom, &roster, &Models::Roster::addRoom);
|
||
|
connect(core, &Core::Squawk::changeRoom, &roster, &Models::Roster::changeRoom);
|
||
|
connect(core, &Core::Squawk::removeRoom, &roster, &Models::Roster::removeRoom);
|
||
|
connect(core, &Core::Squawk::addRoomParticipant, &roster, &Models::Roster::addRoomParticipant);
|
||
|
connect(core, &Core::Squawk::changeRoomParticipant, &roster, &Models::Roster::changeRoomParticipant);
|
||
|
connect(core, &Core::Squawk::removeRoomParticipant, &roster, &Models::Roster::removeRoomParticipant);
|
||
|
|
||
|
|
||
|
connect(core, &Core::Squawk::fileDownloadComplete, std::bind(&Models::Roster::fileComplete, &roster, std::placeholders::_1, false));
|
||
|
connect(core, &Core::Squawk::fileUploadComplete, std::bind(&Models::Roster::fileComplete, &roster, std::placeholders::_1, true));
|
||
|
connect(core, &Core::Squawk::fileProgress, &roster, &Models::Roster::fileProgress);
|
||
|
connect(core, &Core::Squawk::fileError, &roster, &Models::Roster::fileError);
|
||
|
|
||
|
connect(core, &Core::Squawk::requestPassword, this, &Application::requestPassword);
|
||
|
connect(core, &Core::Squawk::ready, this, &Application::readSettings);
|
||
|
|
||
|
}
|
||
|
|
||
|
Application::~Application() {}
|
||
|
|
||
|
void Application::quit()
|
||
|
{
|
||
|
if (!nowQuitting) {
|
||
|
nowQuitting = true;
|
||
|
emit quitting();
|
||
|
|
||
|
writeSettings();
|
||
|
for (Conversations::const_iterator itr = conversations.begin(), end = conversations.end(); itr != end; ++itr) {
|
||
|
disconnect(itr->second, &Conversation::destroyed, this, &Application::onConversationClosed);
|
||
|
itr->second->close();
|
||
|
}
|
||
|
conversations.clear();
|
||
|
dialogueQueue.quit();
|
||
|
|
||
|
if (squawk != nullptr) {
|
||
|
squawk->close();
|
||
|
}
|
||
|
if (!destroyingSquawk) {
|
||
|
checkForTheLastWindow();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void Application::checkForTheLastWindow()
|
||
|
{
|
||
|
if (QApplication::topLevelWidgets().size() > 0) {
|
||
|
emit readyToQuit();
|
||
|
} else {
|
||
|
connect(qApp, &QApplication::lastWindowClosed, this, &Application::readyToQuit);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void Application::createMainWindow()
|
||
|
{
|
||
|
if (squawk == nullptr) {
|
||
|
squawk = new Squawk(roster);
|
||
|
|
||
|
connect(squawk, &Squawk::notify, this, &Application::notify);
|
||
|
connect(squawk, &Squawk::changeSubscription, this, &Application::changeSubscription);
|
||
|
connect(squawk, &Squawk::openedConversation, this, &Application::onSquawkOpenedConversation);
|
||
|
connect(squawk, &Squawk::openConversation, this, &Application::openConversation);
|
||
|
connect(squawk, &Squawk::changeState, this, &Application::setState);
|
||
|
connect(squawk, &Squawk::closing, this, &Application::onSquawkClosing);
|
||
|
|
||
|
connect(squawk, &Squawk::modifyAccountRequest, core, &Core::Squawk::modifyAccountRequest);
|
||
|
connect(squawk, &Squawk::newAccountRequest, core, &Core::Squawk::newAccountRequest);
|
||
|
connect(squawk, &Squawk::removeAccountRequest, core, &Core::Squawk::removeAccountRequest);
|
||
|
connect(squawk, &Squawk::connectAccount, core, &Core::Squawk::connectAccount);
|
||
|
connect(squawk, &Squawk::disconnectAccount, core, &Core::Squawk::disconnectAccount);
|
||
|
|
||
|
connect(squawk, &Squawk::addContactRequest, core, &Core::Squawk::addContactRequest);
|
||
|
connect(squawk, &Squawk::removeContactRequest, core, &Core::Squawk::removeContactRequest);
|
||
|
connect(squawk, &Squawk::removeRoomRequest, core, &Core::Squawk::removeRoomRequest);
|
||
|
connect(squawk, &Squawk::addRoomRequest, core, &Core::Squawk::addRoomRequest);
|
||
|
connect(squawk, &Squawk::addContactToGroupRequest, core, &Core::Squawk::addContactToGroupRequest);
|
||
|
connect(squawk, &Squawk::removeContactFromGroupRequest, core, &Core::Squawk::removeContactFromGroupRequest);
|
||
|
connect(squawk, &Squawk::renameContactRequest, core, &Core::Squawk::renameContactRequest);
|
||
|
connect(squawk, &Squawk::requestVCard, core, &Core::Squawk::requestVCard);
|
||
|
connect(squawk, &Squawk::uploadVCard, core, &Core::Squawk::uploadVCard);
|
||
|
connect(squawk, &Squawk::changeDownloadsPath, core, &Core::Squawk::changeDownloadsPath);
|
||
|
|
||
|
connect(core, &Core::Squawk::responseVCard, squawk, &Squawk::responseVCard);
|
||
|
|
||
|
dialogueQueue.setParentWidnow(squawk);
|
||
|
squawk->stateChanged(availability);
|
||
|
squawk->show();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void Application::onSquawkClosing()
|
||
|
{
|
||
|
dialogueQueue.setParentWidnow(nullptr);
|
||
|
|
||
|
disconnect(core, &Core::Squawk::responseVCard, squawk, &Squawk::responseVCard);
|
||
|
|
||
|
destroyingSquawk = true;
|
||
|
squawk->deleteLater();
|
||
|
squawk = nullptr;
|
||
|
|
||
|
//for now
|
||
|
quit();
|
||
|
}
|
||
|
|
||
|
void Application::onSquawkDestroyed() {
|
||
|
destroyingSquawk = false;
|
||
|
if (nowQuitting) {
|
||
|
checkForTheLastWindow();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void Application::notify(const QString& account, const Shared::Message& msg)
|
||
|
{
|
||
|
QString name = QString(roster.getContactName(account, msg.getPenPalJid()));
|
||
|
QString path = QString(roster.getContactIconPath(account, msg.getPenPalJid(), msg.getPenPalResource()));
|
||
|
QVariantList args;
|
||
|
args << QString();
|
||
|
|
||
|
args << qHash(msg.getId());
|
||
|
if (path.size() > 0) {
|
||
|
args << path;
|
||
|
} else {
|
||
|
args << QString("mail-message"); //TODO should here better be unknown user icon?
|
||
|
}
|
||
|
if (msg.getType() == Shared::Message::groupChat) {
|
||
|
args << msg.getFromResource() + " from " + name;
|
||
|
} else {
|
||
|
args << name;
|
||
|
}
|
||
|
|
||
|
QString body(msg.getBody());
|
||
|
QString oob(msg.getOutOfBandUrl());
|
||
|
if (body == oob) {
|
||
|
body = tr("Attached file");
|
||
|
}
|
||
|
|
||
|
args << body;
|
||
|
args << QStringList();
|
||
|
args << QVariantMap({
|
||
|
{"desktop-entry", QString(QCoreApplication::applicationName())},
|
||
|
{"category", QString("message")},
|
||
|
// {"sound-file", "/path/to/macaw/squawk"},
|
||
|
{"sound-name", QString("message-new-instant")}
|
||
|
});
|
||
|
args << -1;
|
||
|
notifications.callWithArgumentList(QDBus::AutoDetect, "Notify", args);
|
||
|
|
||
|
if (squawk != nullptr) {
|
||
|
QApplication::alert(squawk);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void Application::setState(Shared::Availability p_availability)
|
||
|
{
|
||
|
if (availability != p_availability) {
|
||
|
availability = p_availability;
|
||
|
emit changeState(availability);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void Application::stateChanged(Shared::Availability state)
|
||
|
{
|
||
|
availability = state;
|
||
|
if (squawk != nullptr) {
|
||
|
squawk->stateChanged(state);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void Application::readSettings()
|
||
|
{
|
||
|
QSettings settings;
|
||
|
settings.beginGroup("ui");
|
||
|
int avail;
|
||
|
if (settings.contains("availability")) {
|
||
|
avail = settings.value("availability").toInt();
|
||
|
} else {
|
||
|
avail = static_cast<int>(Shared::Availability::online);
|
||
|
}
|
||
|
settings.endGroup();
|
||
|
|
||
|
setState(Shared::Global::fromInt<Shared::Availability>(avail));
|
||
|
createMainWindow();
|
||
|
}
|
||
|
|
||
|
void Application::writeSettings()
|
||
|
{
|
||
|
QSettings settings;
|
||
|
settings.setValue("availability", static_cast<int>(availability));
|
||
|
}
|
||
|
|
||
|
void Application::requestPassword(const QString& account, bool authenticationError) {
|
||
|
if (authenticationError) {
|
||
|
dialogueQueue.addAction(account, DialogQueue::askCredentials);
|
||
|
} else {
|
||
|
dialogueQueue.addAction(account, DialogQueue::askPassword);
|
||
|
}
|
||
|
|
||
|
}
|
||
|
void Application::onConversationClosed()
|
||
|
{
|
||
|
Conversation* conv = static_cast<Conversation*>(sender());
|
||
|
Models::Roster::ElId id(conv->getAccount(), conv->getJid());
|
||
|
Conversations::const_iterator itr = conversations.find(id);
|
||
|
if (itr != conversations.end()) {
|
||
|
conversations.erase(itr);
|
||
|
}
|
||
|
if (conv->isMuc) {
|
||
|
Room* room = static_cast<Room*>(conv);
|
||
|
if (!room->autoJoined()) {
|
||
|
emit setRoomJoined(id.account, id.name, false);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void Application::changeSubscription(const Models::Roster::ElId& id, bool subscribe)
|
||
|
{
|
||
|
Models::Item::Type type = roster.getContactType(id);
|
||
|
|
||
|
switch (type) {
|
||
|
case Models::Item::contact:
|
||
|
if (subscribe) {
|
||
|
emit subscribeContact(id.account, id.name, "");
|
||
|
} else {
|
||
|
emit unsubscribeContact(id.account, id.name, "");
|
||
|
}
|
||
|
break;
|
||
|
case Models::Item::room:
|
||
|
setRoomAutoJoin(id.account, id.name, subscribe);
|
||
|
if (!isConverstationOpened(id)) {
|
||
|
emit setRoomJoined(id.account, id.name, subscribe);
|
||
|
}
|
||
|
break;
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void Application::subscribeConversation(Conversation* conv)
|
||
|
{
|
||
|
connect(conv, &Conversation::destroyed, this, &Application::onConversationClosed);
|
||
|
connect(conv, &Conversation::sendMessage, this, &Application::onConversationMessage);
|
||
|
connect(conv, &Conversation::replaceMessage, this, &Application::onConversationReplaceMessage);
|
||
|
connect(conv, &Conversation::resendMessage, this, &Application::onConversationResend);
|
||
|
connect(conv, &Conversation::notifyableMessage, this, &Application::notify);
|
||
|
}
|
||
|
|
||
|
void Application::openConversation(const Models::Roster::ElId& id, const QString& resource)
|
||
|
{
|
||
|
Conversations::const_iterator itr = conversations.find(id);
|
||
|
Models::Account* acc = roster.getAccount(id.account);
|
||
|
Conversation* conv = nullptr;
|
||
|
bool created = false;
|
||
|
if (itr != conversations.end()) {
|
||
|
conv = itr->second;
|
||
|
} else {
|
||
|
Models::Element* el = roster.getElement(id);
|
||
|
if (el != NULL) {
|
||
|
if (el->type == Models::Item::room) {
|
||
|
created = true;
|
||
|
Models::Room* room = static_cast<Models::Room*>(el);
|
||
|
conv = new Room(acc, room);
|
||
|
if (!room->getJoined()) {
|
||
|
emit setRoomJoined(id.account, id.name, true);
|
||
|
}
|
||
|
} else if (el->type == Models::Item::contact) {
|
||
|
created = true;
|
||
|
conv = new Chat(acc, static_cast<Models::Contact*>(el));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (conv != nullptr) {
|
||
|
if (created) {
|
||
|
conv->setAttribute(Qt::WA_DeleteOnClose);
|
||
|
subscribeConversation(conv);
|
||
|
conversations.insert(std::make_pair(id, conv));
|
||
|
}
|
||
|
|
||
|
conv->show();
|
||
|
conv->raise();
|
||
|
conv->activateWindow();
|
||
|
|
||
|
if (resource.size() > 0) {
|
||
|
conv->setPalResource(resource);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void Application::onConversationMessage(const Shared::Message& msg)
|
||
|
{
|
||
|
Conversation* conv = static_cast<Conversation*>(sender());
|
||
|
QString acc = conv->getAccount();
|
||
|
|
||
|
roster.addMessage(acc, msg);
|
||
|
emit sendMessage(acc, msg);
|
||
|
}
|
||
|
|
||
|
void Application::onConversationReplaceMessage(const QString& originalId, const Shared::Message& msg)
|
||
|
{
|
||
|
Conversation* conv = static_cast<Conversation*>(sender());
|
||
|
QString acc = conv->getAccount();
|
||
|
|
||
|
roster.changeMessage(acc, msg.getPenPalJid(), originalId, {
|
||
|
{"state", static_cast<uint>(Shared::Message::State::pending)}
|
||
|
});
|
||
|
emit replaceMessage(acc, originalId, msg);
|
||
|
}
|
||
|
|
||
|
void Application::onConversationResend(const QString& id)
|
||
|
{
|
||
|
Conversation* conv = static_cast<Conversation*>(sender());
|
||
|
QString acc = conv->getAccount();
|
||
|
QString jid = conv->getJid();
|
||
|
|
||
|
emit resendMessage(acc, jid, id);
|
||
|
}
|
||
|
|
||
|
void Application::onSquawkOpenedConversation() {
|
||
|
subscribeConversation(squawk->currentConversation);
|
||
|
Models::Roster::ElId id = squawk->currentConversationId();
|
||
|
|
||
|
const Models::Element* el = roster.getElementConst(id);
|
||
|
if (el != NULL && el->isRoom() && !static_cast<const Models::Room*>(el)->getJoined()) {
|
||
|
emit setRoomJoined(id.account, id.name, true);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void Application::removeAccount(const QString& account)
|
||
|
{
|
||
|
Conversations::const_iterator itr = conversations.begin();
|
||
|
while (itr != conversations.end()) {
|
||
|
if (itr->first.account == account) {
|
||
|
Conversations::const_iterator lItr = itr;
|
||
|
++itr;
|
||
|
Conversation* conv = lItr->second;
|
||
|
disconnect(conv, &Conversation::destroyed, this, &Application::onConversationClosed);
|
||
|
conv->close();
|
||
|
conversations.erase(lItr);
|
||
|
} else {
|
||
|
++itr;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (squawk != nullptr && squawk->currentConversationId().account == account) {
|
||
|
squawk->closeCurrentConversation();
|
||
|
}
|
||
|
|
||
|
roster.removeAccount(account);
|
||
|
}
|
||
|
|
||
|
void Application::changeAccount(const QString& account, const QMap<QString, QVariant>& data)
|
||
|
{
|
||
|
for (QMap<QString, QVariant>::const_iterator itr = data.begin(), end = data.end(); itr != end; ++itr) {
|
||
|
QString attr = itr.key();
|
||
|
roster.updateAccount(account, attr, *itr);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void Application::addContact(const QString& account, const QString& jid, const QString& group, const QMap<QString, QVariant>& data)
|
||
|
{
|
||
|
roster.addContact(account, jid, group, data);
|
||
|
|
||
|
if (squawk != nullptr) {
|
||
|
QSettings settings;
|
||
|
settings.beginGroup("ui");
|
||
|
settings.beginGroup("roster");
|
||
|
settings.beginGroup(account);
|
||
|
if (settings.value("expanded", false).toBool()) {
|
||
|
QModelIndex ind = roster.getAccountIndex(account);
|
||
|
squawk->expand(ind);
|
||
|
}
|
||
|
settings.endGroup();
|
||
|
settings.endGroup();
|
||
|
settings.endGroup();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void Application::addGroup(const QString& account, const QString& name)
|
||
|
{
|
||
|
roster.addGroup(account, name);
|
||
|
|
||
|
if (squawk != nullptr) {
|
||
|
QSettings settings;
|
||
|
settings.beginGroup("ui");
|
||
|
settings.beginGroup("roster");
|
||
|
settings.beginGroup(account);
|
||
|
if (settings.value("expanded", false).toBool()) {
|
||
|
QModelIndex ind = roster.getAccountIndex(account);
|
||
|
squawk->expand(ind);
|
||
|
if (settings.value(name + "/expanded", false).toBool()) {
|
||
|
squawk->expand(roster.getGroupIndex(account, name));
|
||
|
}
|
||
|
}
|
||
|
settings.endGroup();
|
||
|
settings.endGroup();
|
||
|
settings.endGroup();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bool Application::isConverstationOpened(const Models::Roster::ElId& id) const {
|
||
|
return (conversations.count(id) > 0) || (squawk != nullptr && squawk->currentConversationId() == id);}
|