1
0
forked from blue/squawk

merge conflicts, text copying from context menu in message line

This commit is contained in:
Blue 2022-04-30 21:41:25 +03:00
commit c3a45ec58c
Signed by untrusted user: blue
GPG Key ID: 9B203B252A63EE38
38 changed files with 1221 additions and 798 deletions

View File

@ -4,6 +4,7 @@
### Bug fixes ### Bug fixes
- now when you remove an account it actually gets removed - now when you remove an account it actually gets removed
- segfault on unitialized Availability in some rare occesions - segfault on unitialized Availability in some rare occesions
- fixed crash when you open a dialog with someone that has only error messages in archive
### Improvements ### Improvements
- there is a way to disable an account and it wouldn't connect when you change availability - there is a way to disable an account and it wouldn't connect when you change availability
@ -15,6 +16,8 @@
### New features ### New features
- new "About" window with links, license, gratitudes - new "About" window with links, license, gratitudes
- if the authentication failed Squawk will ask againg for your password and login - if the authentication failed Squawk will ask againg for your password and login
- now there is an amount of unread messages showing on top of Squawk launcher icon
- notifications now have buttons to open a conversation or to mark that message as read
## Squawk 0.2.1 (Apr 02, 2022) ## Squawk 0.2.1 (Apr 02, 2022)
### Bug fixes ### Bug fixes

View File

@ -148,6 +148,7 @@ if(CMAKE_COMPILER_IS_GNUCXX)
target_compile_options(squawk PRIVATE ${COMPILE_OPTIONS}) target_compile_options(squawk PRIVATE ${COMPILE_OPTIONS})
endif(CMAKE_COMPILER_IS_GNUCXX) endif(CMAKE_COMPILER_IS_GNUCXX)
add_subdirectory(main)
add_subdirectory(core) add_subdirectory(core)
add_subdirectory(external/simpleCrypt) add_subdirectory(external/simpleCrypt)
add_subdirectory(packaging) add_subdirectory(packaging)

View File

@ -8,13 +8,10 @@ target_sources(squawk PRIVATE
account.h account.h
adapterfunctions.cpp adapterfunctions.cpp
adapterfunctions.h adapterfunctions.h
archive.cpp
archive.h
conference.cpp conference.cpp
conference.h conference.h
contact.cpp contact.cpp
contact.h contact.h
main.cpp
networkaccess.cpp networkaccess.cpp
networkaccess.h networkaccess.h
rosteritem.cpp rosteritem.cpp
@ -23,13 +20,10 @@ target_sources(squawk PRIVATE
signalcatcher.h signalcatcher.h
squawk.cpp squawk.cpp
squawk.h squawk.h
storage.cpp
storage.h
urlstorage.cpp
urlstorage.h
) )
target_include_directories(squawk PRIVATE ${LMDB_INCLUDE_DIRS}) target_include_directories(squawk PRIVATE ${LMDB_INCLUDE_DIRS})
add_subdirectory(handlers) add_subdirectory(handlers)
add_subdirectory(storage)
add_subdirectory(passwordStorageEngines) add_subdirectory(passwordStorageEngines)

View File

@ -1,201 +0,0 @@
/*
* Squawk messenger.
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "../shared/global.h"
#include "../shared/messageinfo.h"
#include "../shared/pathcheck.h"
#include "../ui/squawk.h"
#include "signalcatcher.h"
#include "squawk.h"
#include <QLibraryInfo>
#include <QSettings>
#include <QStandardPaths>
#include <QTranslator>
#include <QtCore/QObject>
#include <QtCore/QThread>
#include <QtWidgets/QApplication>
#include <QDir>
int main(int argc, char *argv[])
{
qRegisterMetaType<Shared::Message>("Shared::Message");
qRegisterMetaType<Shared::MessageInfo>("Shared::MessageInfo");
qRegisterMetaType<Shared::VCard>("Shared::VCard");
qRegisterMetaType<std::list<Shared::Message>>("std::list<Shared::Message>");
qRegisterMetaType<std::list<Shared::MessageInfo>>("std::list<Shared::MessageInfo>");
qRegisterMetaType<QSet<QString>>("QSet<QString>");
qRegisterMetaType<Shared::ConnectionState>("Shared::ConnectionState");
qRegisterMetaType<Shared::Availability>("Shared::Availability");
QApplication app(argc, argv);
SignalCatcher sc(&app);
QApplication::setApplicationName("squawk");
QApplication::setOrganizationName("macaw.me");
QApplication::setApplicationDisplayName("Squawk");
QApplication::setApplicationVersion("0.2.2");
QTranslator qtTranslator;
qtTranslator.load("qt_" + QLocale::system().name(), QLibraryInfo::location(QLibraryInfo::TranslationsPath));
app.installTranslator(&qtTranslator);
QTranslator myappTranslator;
QStringList shares = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation);
bool found = false;
for (QString share : shares) {
found = myappTranslator.load(QLocale(), QLatin1String("squawk"), ".", share + "/l10n");
if (found) {
break;
}
}
if (!found) {
myappTranslator.load(QLocale(), QLatin1String("squawk"), ".", QCoreApplication::applicationDirPath());
}
app.installTranslator(&myappTranslator);
QIcon icon;
icon.addFile(":images/logo.svg", QSize(16, 16));
icon.addFile(":images/logo.svg", QSize(24, 24));
icon.addFile(":images/logo.svg", QSize(32, 32));
icon.addFile(":images/logo.svg", QSize(48, 48));
icon.addFile(":images/logo.svg", QSize(64, 64));
icon.addFile(":images/logo.svg", QSize(96, 96));
icon.addFile(":images/logo.svg", QSize(128, 128));
icon.addFile(":images/logo.svg", QSize(256, 256));
icon.addFile(":images/logo.svg", QSize(512, 512));
QApplication::setWindowIcon(icon);
new Shared::Global(); //translates enums
QSettings settings;
QVariant vs = settings.value("style");
if (vs.isValid()) {
QString style = vs.toString().toLower();
if (style != "system") {
Shared::Global::setStyle(style);
}
}
if (Shared::Global::supported("colorSchemeTools")) {
QVariant vt = settings.value("theme");
if (vt.isValid()) {
QString theme = vt.toString();
if (theme.toLower() != "system") {
Shared::Global::setTheme(theme);
}
}
}
QString path = Shared::downloadsPathCheck();
if (path.size() > 0) {
settings.setValue("downloadsPath", path);
} else {
qDebug() << "couldn't initialize directory for downloads, quitting";
return -1;
}
Squawk w;
w.show();
Core::Squawk* squawk = new Core::Squawk();
QThread* coreThread = new QThread();
squawk->moveToThread(coreThread);
QObject::connect(&sc, &SignalCatcher::interrupt, &app, &QApplication::closeAllWindows);
QObject::connect(coreThread, &QThread::started, squawk, &Core::Squawk::start);
QObject::connect(&app, &QApplication::lastWindowClosed, squawk, &Core::Squawk::stop);
QObject::connect(&app, &QApplication::lastWindowClosed, &w, &Squawk::writeSettings);
//QObject::connect(&app, &QApplication::aboutToQuit, &w, &QMainWindow::close);
QObject::connect(squawk, &Core::Squawk::quit, squawk, &Core::Squawk::deleteLater);
QObject::connect(squawk, &Core::Squawk::quit, coreThread, &QThread::quit, Qt::QueuedConnection);
QObject::connect(coreThread, &QThread::finished, &app, &QApplication::quit, Qt::QueuedConnection);
QObject::connect(&w, &Squawk::newAccountRequest, squawk, &Core::Squawk::newAccountRequest);
QObject::connect(&w, &Squawk::modifyAccountRequest, squawk, &Core::Squawk::modifyAccountRequest);
QObject::connect(&w, &Squawk::removeAccountRequest, squawk, &Core::Squawk::removeAccountRequest);
QObject::connect(&w, &Squawk::connectAccount, squawk, &Core::Squawk::connectAccount);
QObject::connect(&w, &Squawk::disconnectAccount, squawk, &Core::Squawk::disconnectAccount);
QObject::connect(&w, &Squawk::changeState, squawk, &Core::Squawk::changeState);
QObject::connect(&w, &Squawk::sendMessage, squawk,&Core::Squawk::sendMessage);
QObject::connect(&w, &Squawk::replaceMessage, squawk,&Core::Squawk::replaceMessage);
QObject::connect(&w, &Squawk::resendMessage, squawk,&Core::Squawk::resendMessage);
QObject::connect(&w, &Squawk::requestArchive, squawk, &Core::Squawk::requestArchive);
QObject::connect(&w, &Squawk::subscribeContact, squawk, &Core::Squawk::subscribeContact);
QObject::connect(&w, &Squawk::unsubscribeContact, squawk, &Core::Squawk::unsubscribeContact);
QObject::connect(&w, &Squawk::addContactRequest, squawk, &Core::Squawk::addContactRequest);
QObject::connect(&w, &Squawk::removeContactRequest, squawk, &Core::Squawk::removeContactRequest);
QObject::connect(&w, &Squawk::setRoomJoined, squawk, &Core::Squawk::setRoomJoined);
QObject::connect(&w, &Squawk::setRoomAutoJoin, squawk, &Core::Squawk::setRoomAutoJoin);
QObject::connect(&w, &Squawk::removeRoomRequest, squawk, &Core::Squawk::removeRoomRequest);
QObject::connect(&w, &Squawk::addRoomRequest, squawk, &Core::Squawk::addRoomRequest);
QObject::connect(&w, &Squawk::fileDownloadRequest, squawk, &Core::Squawk::fileDownloadRequest);
QObject::connect(&w, &Squawk::addContactToGroupRequest, squawk, &Core::Squawk::addContactToGroupRequest);
QObject::connect(&w, &Squawk::removeContactFromGroupRequest, squawk, &Core::Squawk::removeContactFromGroupRequest);
QObject::connect(&w, &Squawk::renameContactRequest, squawk, &Core::Squawk::renameContactRequest);
QObject::connect(&w, &Squawk::requestVCard, squawk, &Core::Squawk::requestVCard);
QObject::connect(&w, &Squawk::uploadVCard, squawk, &Core::Squawk::uploadVCard);
QObject::connect(&w, &Squawk::responsePassword, squawk, &Core::Squawk::responsePassword);
QObject::connect(&w, &Squawk::localPathInvalid, squawk, &Core::Squawk::onLocalPathInvalid);
QObject::connect(&w, &Squawk::changeDownloadsPath, squawk, &Core::Squawk::changeDownloadsPath);
QObject::connect(squawk, &Core::Squawk::newAccount, &w, &Squawk::newAccount);
QObject::connect(squawk, &Core::Squawk::addContact, &w, &Squawk::addContact);
QObject::connect(squawk, &Core::Squawk::changeAccount, &w, &Squawk::changeAccount);
QObject::connect(squawk, &Core::Squawk::removeAccount, &w, &Squawk::removeAccount);
QObject::connect(squawk, &Core::Squawk::addGroup, &w, &Squawk::addGroup);
QObject::connect(squawk, &Core::Squawk::removeGroup, &w, &Squawk::removeGroup);
QObject::connect(squawk, qOverload<const QString&, const QString&>(&Core::Squawk::removeContact),
&w, qOverload<const QString&, const QString&>(&Squawk::removeContact));
QObject::connect(squawk, qOverload<const QString&, const QString&, const QString&>(&Core::Squawk::removeContact),
&w, qOverload<const QString&, const QString&, const QString&>(&Squawk::removeContact));
QObject::connect(squawk, &Core::Squawk::changeContact, &w, &Squawk::changeContact);
QObject::connect(squawk, &Core::Squawk::addPresence, &w, &Squawk::addPresence);
QObject::connect(squawk, &Core::Squawk::removePresence, &w, &Squawk::removePresence);
QObject::connect(squawk, &Core::Squawk::stateChanged, &w, &Squawk::stateChanged);
QObject::connect(squawk, &Core::Squawk::accountMessage, &w, &Squawk::accountMessage);
QObject::connect(squawk, &Core::Squawk::changeMessage, &w, &Squawk::changeMessage);
QObject::connect(squawk, &Core::Squawk::responseArchive, &w, &Squawk::responseArchive);
QObject::connect(squawk, &Core::Squawk::addRoom, &w, &Squawk::addRoom);
QObject::connect(squawk, &Core::Squawk::changeRoom, &w, &Squawk::changeRoom);
QObject::connect(squawk, &Core::Squawk::removeRoom, &w, &Squawk::removeRoom);
QObject::connect(squawk, &Core::Squawk::addRoomParticipant, &w, &Squawk::addRoomParticipant);
QObject::connect(squawk, &Core::Squawk::changeRoomParticipant, &w, &Squawk::changeRoomParticipant);
QObject::connect(squawk, &Core::Squawk::removeRoomParticipant, &w, &Squawk::removeRoomParticipant);
QObject::connect(squawk, &Core::Squawk::fileDownloadComplete, &w, &Squawk::fileDownloadComplete);
QObject::connect(squawk, &Core::Squawk::fileUploadComplete, &w, &Squawk::fileUploadComplete);
QObject::connect(squawk, &Core::Squawk::fileProgress, &w, &Squawk::fileProgress);
QObject::connect(squawk, &Core::Squawk::fileError, &w, &Squawk::fileError);
QObject::connect(squawk, &Core::Squawk::responseVCard, &w, &Squawk::responseVCard);
QObject::connect(squawk, &Core::Squawk::requestPassword, &w, &Squawk::requestPassword);
QObject::connect(squawk, &Core::Squawk::ready, &w, &Squawk::readSettings);
coreThread->start();
int result = app.exec();
if (coreThread->isRunning()) {
//coreThread->wait();
//todo if I uncomment that, the app will not quit if it has reconnected at least once
//it feels like a symptom of something badly desinged in the core thread
//need to investigate;
}
return result;
}

View File

@ -30,7 +30,7 @@
#include <set> #include <set>
#include "urlstorage.h" #include "storage/urlstorage.h"
#include "shared/pathcheck.h" #include "shared/pathcheck.h"
namespace Core { namespace Core {

View File

@ -124,15 +124,19 @@ void Core::RosterItem::nextRequest()
if (requestedCount != -1) { if (requestedCount != -1) {
bool last = false; bool last = false;
if (archiveState == beginning || archiveState == complete) { if (archiveState == beginning || archiveState == complete) {
QString firstId = archive->oldestId(); try {
if (responseCache.size() == 0) { QString firstId = archive->oldestId();
if (requestedBefore == firstId) { if (responseCache.size() == 0) {
last = true; if (requestedBefore == firstId) {
} last = true;
} else { }
if (responseCache.front().getId() == firstId) { } else {
last = true; if (responseCache.front().getId() == firstId) {
last = true;
}
} }
} catch (const Archive::Empty& e) {
last = true;
} }
} else if (archiveState == empty && responseCache.size() == 0) { } else if (archiveState == empty && responseCache.size() == 0) {
last = true; last = true;
@ -171,8 +175,12 @@ void Core::RosterItem::performRequest(int count, const QString& before)
requestCache.emplace_back(requestedCount, before); requestCache.emplace_back(requestedCount, before);
requestedCount = -1; requestedCount = -1;
} }
Shared::Message msg = archive->newest(); try {
emit needHistory("", getId(msg), msg.getTime()); Shared::Message msg = archive->newest();
emit needHistory("", getId(msg), msg.getTime());
} catch (const Archive::Empty& e) { //this can happen when the only message in archive is not server stored (error, for example)
emit needHistory(before, "");
}
} }
break; break;
case end: case end:

View File

@ -34,7 +34,7 @@
#include "shared/enums.h" #include "shared/enums.h"
#include "shared/message.h" #include "shared/message.h"
#include "shared/vcard.h" #include "shared/vcard.h"
#include "archive.h" #include "storage/archive.h"
#include "adapterfunctions.h" #include "adapterfunctions.h"
namespace Core { namespace Core {

View File

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

7
main/CMakeLists.txt Normal file
View File

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

564
main/application.cpp Normal file
View File

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

118
main/application.h Normal file
View File

@ -0,0 +1,118 @@
// Squawk messenger.
// Copyright (C) 2019 Yury Gubich <blue@macaw.me>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#ifndef APPLICATION_H
#define APPLICATION_H
#include <map>
#include <QObject>
#include <QDBusInterface>
#include "dialogqueue.h"
#include "core/squawk.h"
#include "ui/squawk.h"
#include "ui/models/roster.h"
#include "ui/widgets/conversation.h"
#include "shared/message.h"
#include "shared/enums.h"
/**
* @todo write docs
*/
class Application : public QObject
{
Q_OBJECT
public:
Application(Core::Squawk* core);
~Application();
bool isConverstationOpened(const Models::Roster::ElId& id) const;
signals:
void sendMessage(const QString& account, const Shared::Message& data);
void replaceMessage(const QString& account, const QString& originalId, const Shared::Message& data);
void resendMessage(const QString& account, const QString& jid, const QString& id);
void changeState(Shared::Availability state);
void setRoomJoined(const QString& account, const QString& jid, bool joined);
void setRoomAutoJoin(const QString& account, const QString& jid, bool joined);
void subscribeContact(const QString& account, const QString& jid, const QString& reason);
void unsubscribeContact(const QString& account, const QString& jid, const QString& reason);
void quitting();
void readyToQuit();
public slots:
void readSettings();
void quit();
protected slots:
void notify(const QString& account, const Shared::Message& msg);
void unreadMessagesCountChanged(int count);
void setState(Shared::Availability availability);
void changeAccount(const QString& account, const QMap<QString, QVariant>& data);
void removeAccount(const QString& account);
void openConversation(const Models::Roster::ElId& id, const QString& resource = "");
void addGroup(const QString& account, const QString& name);
void addContact(const QString& account, const QString& jid, const QString& group, const QMap<QString, QVariant>& data);
void requestPassword(const QString& account, bool authenticationError);
void writeSettings();
private slots:
void onConversationClosed();
void changeSubscription(const Models::Roster::ElId& id, bool subscribe);
void onSquawkOpenedConversation();
void onConversationMessage(const Shared::Message& msg);
void onConversationReplaceMessage(const QString& originalId, const Shared::Message& msg);
void onConversationResend(const QString& id);
void stateChanged(Shared::Availability state);
void onSquawkClosing();
void onSquawkDestroyed();
void onNotificationClosed(quint32 id, quint32 reason);
void onNotificationInvoked(quint32 id, const QString& action);
private:
void createMainWindow();
void subscribeConversation(Conversation* conv);
void checkForTheLastWindow();
void focusConversation(const Models::Roster::ElId& id, const QString& resource = "", const QString& messageId = "");
private:
typedef std::map<Models::Roster::ElId, Conversation*> Conversations;
typedef std::map<uint32_t, std::pair<Models::Roster::ElId, QString>> Notifications;
Shared::Availability availability;
Core::Squawk* core;
Squawk* squawk;
QDBusInterface notifications;
Models::Roster roster;
Conversations conversations;
DialogQueue dialogueQueue;
bool nowQuitting;
bool destroyingSquawk;
Notifications storage;
};
#endif // APPLICATION_H

View File

@ -15,10 +15,9 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "dialogqueue.h" #include "dialogqueue.h"
#include "squawk.h"
#include <QDebug> #include <QDebug>
DialogQueue::DialogQueue(Squawk* p_squawk): DialogQueue::DialogQueue(const Models::Roster& p_roster):
QObject(), QObject(),
currentSource(), currentSource(),
currentAction(none), currentAction(none),
@ -26,7 +25,8 @@ DialogQueue::DialogQueue(Squawk* p_squawk):
collection(queue.get<0>()), collection(queue.get<0>()),
sequence(queue.get<1>()), sequence(queue.get<1>()),
prompt(nullptr), prompt(nullptr),
squawk(p_squawk) parent(nullptr),
roster(p_roster)
{ {
} }
@ -34,6 +34,19 @@ DialogQueue::~DialogQueue()
{ {
} }
void DialogQueue::quit()
{
queue.clear();
if (currentAction != none) {
actionDone();
}
}
void DialogQueue::setParentWidnow(QMainWindow* p_parent)
{
parent = p_parent;
}
bool DialogQueue::addAction(const QString& source, DialogQueue::Action action) bool DialogQueue::addAction(const QString& source, DialogQueue::Action action)
{ {
if (action == none) { if (action == none) {
@ -77,7 +90,7 @@ void DialogQueue::performNextAction()
actionDone(); actionDone();
break; break;
case askPassword: { case askPassword: {
QInputDialog* dialog = new QInputDialog(squawk); QInputDialog* dialog = new QInputDialog(parent);
prompt = dialog; prompt = dialog;
connect(dialog, &QDialog::accepted, this, &DialogQueue::onPropmtAccepted); connect(dialog, &QDialog::accepted, this, &DialogQueue::onPropmtAccepted);
connect(dialog, &QDialog::rejected, this, &DialogQueue::onPropmtRejected); connect(dialog, &QDialog::rejected, this, &DialogQueue::onPropmtRejected);
@ -90,11 +103,11 @@ void DialogQueue::performNextAction()
} }
break; break;
case askCredentials: { case askCredentials: {
CredentialsPrompt* dialog = new CredentialsPrompt(squawk); CredentialsPrompt* dialog = new CredentialsPrompt(parent);
prompt = dialog; prompt = dialog;
connect(dialog, &QDialog::accepted, this, &DialogQueue::onPropmtAccepted); connect(dialog, &QDialog::accepted, this, &DialogQueue::onPropmtAccepted);
connect(dialog, &QDialog::rejected, this, &DialogQueue::onPropmtRejected); connect(dialog, &QDialog::rejected, this, &DialogQueue::onPropmtRejected);
Models::Account* acc = squawk->rosterModel.getAccount(currentSource); const Models::Account* acc = roster.getAccountConst(currentSource);
dialog->setAccount(currentSource); dialog->setAccount(currentSource);
dialog->setLogin(acc->getLogin()); dialog->setLogin(acc->getLogin());
dialog->setPassword(acc->getPassword()); dialog->setPassword(acc->getPassword());
@ -111,12 +124,12 @@ void DialogQueue::onPropmtAccepted()
break; break;
case askPassword: { case askPassword: {
QInputDialog* dialog = static_cast<QInputDialog*>(prompt); QInputDialog* dialog = static_cast<QInputDialog*>(prompt);
emit squawk->responsePassword(currentSource, dialog->textValue()); emit responsePassword(currentSource, dialog->textValue());
} }
break; break;
case askCredentials: { case askCredentials: {
CredentialsPrompt* dialog = static_cast<CredentialsPrompt*>(prompt); CredentialsPrompt* dialog = static_cast<CredentialsPrompt*>(prompt);
emit squawk->modifyAccountRequest(currentSource, { emit modifyAccountRequest(currentSource, {
{"login", dialog->getLogin()}, {"login", dialog->getLogin()},
{"password", dialog->getPassword()} {"password", dialog->getPassword()}
}); });
@ -133,7 +146,7 @@ void DialogQueue::onPropmtRejected()
break; break;
case askPassword: case askPassword:
case askCredentials: case askCredentials:
emit squawk->disconnectAccount(currentSource); emit disconnectAccount(currentSource);
break; break;
} }
actionDone(); actionDone();

View File

@ -19,14 +19,14 @@
#include <QObject> #include <QObject>
#include <QInputDialog> #include <QInputDialog>
#include <QMainWindow>
#include <boost/multi_index_container.hpp> #include <boost/multi_index_container.hpp>
#include <boost/multi_index/ordered_index.hpp> #include <boost/multi_index/ordered_index.hpp>
#include <boost/multi_index/sequenced_index.hpp> #include <boost/multi_index/sequenced_index.hpp>
#include <ui/widgets/accounts/credentialsprompt.h> #include <ui/widgets/accounts/credentialsprompt.h>
#include <ui/models/roster.h>
class Squawk;
class DialogQueue : public QObject class DialogQueue : public QObject
{ {
@ -38,12 +38,21 @@ public:
askCredentials askCredentials
}; };
DialogQueue(Squawk* squawk); DialogQueue(const Models::Roster& roster);
~DialogQueue(); ~DialogQueue();
bool addAction(const QString& source, Action action); bool addAction(const QString& source, Action action);
bool cancelAction(const QString& source, Action action); bool cancelAction(const QString& source, Action action);
signals:
void modifyAccountRequest(const QString&, const QMap<QString, QVariant>&);
void responsePassword(const QString& account, const QString& password);
void disconnectAccount(const QString&);
public:
void setParentWidnow(QMainWindow* parent);
void quit();
private: private:
void performNextAction(); void performNextAction();
void actionDone(); void actionDone();
@ -85,7 +94,8 @@ private:
Sequence& sequence; Sequence& sequence;
QDialog* prompt; QDialog* prompt;
Squawk* squawk; QMainWindow* parent;
const Models::Roster& roster;
}; };
#endif // DIALOGQUEUE_H #endif // DIALOGQUEUE_H

140
main/main.cpp Normal file
View File

@ -0,0 +1,140 @@
/*
* Squawk messenger.
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "shared/global.h"
#include "shared/messageinfo.h"
#include "shared/pathcheck.h"
#include "main/application.h"
#include "core/signalcatcher.h"
#include "core/squawk.h"
#include <QLibraryInfo>
#include <QSettings>
#include <QStandardPaths>
#include <QTranslator>
#include <QtCore/QObject>
#include <QtCore/QThread>
#include <QtWidgets/QApplication>
#include <QDir>
int main(int argc, char *argv[])
{
qRegisterMetaType<Shared::Message>("Shared::Message");
qRegisterMetaType<Shared::MessageInfo>("Shared::MessageInfo");
qRegisterMetaType<Shared::VCard>("Shared::VCard");
qRegisterMetaType<std::list<Shared::Message>>("std::list<Shared::Message>");
qRegisterMetaType<std::list<Shared::MessageInfo>>("std::list<Shared::MessageInfo>");
qRegisterMetaType<QSet<QString>>("QSet<QString>");
qRegisterMetaType<Shared::ConnectionState>("Shared::ConnectionState");
qRegisterMetaType<Shared::Availability>("Shared::Availability");
QApplication app(argc, argv);
SignalCatcher sc(&app);
QApplication::setApplicationName("squawk");
QApplication::setOrganizationName("macaw.me");
QApplication::setApplicationDisplayName("Squawk");
QApplication::setApplicationVersion("0.2.2");
app.setDesktopFileName("squawk");
QTranslator qtTranslator;
qtTranslator.load("qt_" + QLocale::system().name(), QLibraryInfo::location(QLibraryInfo::TranslationsPath));
app.installTranslator(&qtTranslator);
QTranslator myappTranslator;
QStringList shares = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation);
bool found = false;
for (QString share : shares) {
found = myappTranslator.load(QLocale(), QLatin1String("squawk"), ".", share + "/l10n");
if (found) {
break;
}
}
if (!found) {
myappTranslator.load(QLocale(), QLatin1String("squawk"), ".", QCoreApplication::applicationDirPath());
}
app.installTranslator(&myappTranslator);
QIcon icon;
icon.addFile(":images/logo.svg", QSize(16, 16));
icon.addFile(":images/logo.svg", QSize(24, 24));
icon.addFile(":images/logo.svg", QSize(32, 32));
icon.addFile(":images/logo.svg", QSize(48, 48));
icon.addFile(":images/logo.svg", QSize(64, 64));
icon.addFile(":images/logo.svg", QSize(96, 96));
icon.addFile(":images/logo.svg", QSize(128, 128));
icon.addFile(":images/logo.svg", QSize(256, 256));
icon.addFile(":images/logo.svg", QSize(512, 512));
QApplication::setWindowIcon(icon);
new Shared::Global(); //translates enums
QSettings settings;
QVariant vs = settings.value("style");
if (vs.isValid()) {
QString style = vs.toString().toLower();
if (style != "system") {
Shared::Global::setStyle(style);
}
}
if (Shared::Global::supported("colorSchemeTools")) {
QVariant vt = settings.value("theme");
if (vt.isValid()) {
QString theme = vt.toString();
if (theme.toLower() != "system") {
Shared::Global::setTheme(theme);
}
}
}
QString path = Shared::downloadsPathCheck();
if (path.size() > 0) {
settings.setValue("downloadsPath", path);
} else {
qDebug() << "couldn't initialize directory for downloads, quitting";
return -1;
}
Core::Squawk* squawk = new Core::Squawk();
QThread* coreThread = new QThread();
squawk->moveToThread(coreThread);
Application application(squawk);
QObject::connect(&sc, &SignalCatcher::interrupt, &application, &Application::quit);
QObject::connect(coreThread, &QThread::started, squawk, &Core::Squawk::start);
QObject::connect(&application, &Application::quitting, squawk, &Core::Squawk::stop);
//QObject::connect(&app, &QApplication::aboutToQuit, &w, &QMainWindow::close);
QObject::connect(squawk, &Core::Squawk::quit, squawk, &Core::Squawk::deleteLater);
QObject::connect(squawk, &Core::Squawk::destroyed, coreThread, &QThread::quit, Qt::QueuedConnection);
QObject::connect(coreThread, &QThread::finished, &app, &QApplication::quit, Qt::QueuedConnection);
coreThread->start();
int result = app.exec();
if (coreThread->isRunning()) {
//coreThread->wait();
//todo if I uncomment that, the app will not quit if it has reconnected at least once
//it feels like a symptom of something badly desinged in the core thread
//need to investigate;
}
return result;
}

View File

@ -19,6 +19,7 @@
#include "global.h" #include "global.h"
#include "enums.h" #include "enums.h"
#include "ui/models/roster.h"
Shared::Global* Shared::Global::instance = 0; Shared::Global* Shared::Global::instance = 0;
const std::set<QString> Shared::Global::supportedImagesExts = {"png", "jpg", "webp", "jpeg", "gif", "svg"}; const std::set<QString> Shared::Global::supportedImagesExts = {"png", "jpg", "webp", "jpeg", "gif", "svg"};

View File

@ -47,7 +47,6 @@ namespace Shared {
class Global { class Global {
Q_DECLARE_TR_FUNCTIONS(Global) Q_DECLARE_TR_FUNCTIONS(Global)
public: public:
struct FileInfo { struct FileInfo {
enum class Preview { enum class Preview {

View File

@ -2,8 +2,6 @@ target_sources(squawk PRIVATE
squawk.cpp squawk.cpp
squawk.h squawk.h
squawk.ui squawk.ui
dialogqueue.cpp
dialogqueue.h
) )
add_subdirectory(models) add_subdirectory(models)

View File

@ -155,6 +155,16 @@ void Models::Contact::removePresence(const QString& name)
} }
} }
Models::Presence * Models::Contact::getPresence(const QString& name)
{
QMap<QString, Presence*>::iterator itr = presences.find(name);
if (itr == presences.end()) {
return nullptr;
} else {
return itr.value();
}
}
void Models::Contact::refresh() void Models::Contact::refresh()
{ {
QDateTime lastActivity; QDateTime lastActivity;

View File

@ -51,6 +51,7 @@ public:
void addPresence(const QString& name, const QMap<QString, QVariant>& data); void addPresence(const QString& name, const QMap<QString, QVariant>& data);
void removePresence(const QString& name); void removePresence(const QString& name);
Presence* getPresence(const QString& name);
QString getContactName() const; QString getContactName() const;
QString getStatus() const; QString getStatus() const;

View File

@ -134,6 +134,11 @@ unsigned int Models::Element::getMessagesCount() const
return feed->unreadMessagesCount(); return feed->unreadMessagesCount();
} }
bool Models::Element::markMessageAsRead(const QString& id) const
{
return feed->markMessageAsRead(id);
}
void Models::Element::addMessage(const Shared::Message& data) void Models::Element::addMessage(const Shared::Message& data)
{ {
feed->addMessage(data); feed->addMessage(data);
@ -171,6 +176,7 @@ void Models::Element::fileError(const QString& messageId, const QString& error,
void Models::Element::onFeedUnreadMessagesCountChanged() void Models::Element::onFeedUnreadMessagesCountChanged()
{ {
emit unreadMessagesCountChanged();
if (type == contact) { if (type == contact) {
changed(4); changed(4);
} else if (type == room) { } else if (type == room) {

View File

@ -42,6 +42,7 @@ public:
void addMessage(const Shared::Message& data); void addMessage(const Shared::Message& data);
void changeMessage(const QString& id, const QMap<QString, QVariant>& data); void changeMessage(const QString& id, const QMap<QString, QVariant>& data);
unsigned int getMessagesCount() const; unsigned int getMessagesCount() const;
bool markMessageAsRead(const QString& id) const;
void responseArchive(const std::list<Shared::Message> list, bool last); void responseArchive(const std::list<Shared::Message> list, bool last);
bool isRoom() const; bool isRoom() const;
void fileProgress(const QString& messageId, qreal value, bool up); void fileProgress(const QString& messageId, qreal value, bool up);
@ -52,6 +53,7 @@ signals:
void requestArchive(const QString& before); void requestArchive(const QString& before);
void fileDownloadRequest(const QString& url); void fileDownloadRequest(const QString& url);
void unnoticedMessage(const QString& account, const Shared::Message& msg); void unnoticedMessage(const QString& account, const Shared::Message& msg);
void unreadMessagesCountChanged();
void localPathInvalid(const QString& path); void localPathInvalid(const QString& path);
protected: protected:

View File

@ -264,6 +264,16 @@ void Models::Room::removeParticipant(const QString& p_name)
} }
} }
Models::Participant * Models::Room::getParticipant(const QString& p_name)
{
std::map<QString, Participant*>::const_iterator itr = participants.find(p_name);
if (itr == participants.end()) {
return nullptr;
} else {
return itr->second;
}
}
void Models::Room::handleParticipantUpdate(std::map<QString, Participant*>::const_iterator itr, const QMap<QString, QVariant>& data) void Models::Room::handleParticipantUpdate(std::map<QString, Participant*>::const_iterator itr, const QMap<QString, QVariant>& data)
{ {
Participant* part = itr->second; Participant* part = itr->second;

View File

@ -58,6 +58,7 @@ public:
void addParticipant(const QString& name, const QMap<QString, QVariant>& data); void addParticipant(const QString& name, const QMap<QString, QVariant>& data);
void changeParticipant(const QString& name, const QMap<QString, QVariant>& data); void changeParticipant(const QString& name, const QMap<QString, QVariant>& data);
void removeParticipant(const QString& name); void removeParticipant(const QString& name);
Participant* getParticipant(const QString& name);
void toOfflineState() override; void toOfflineState() override;
QString getDisplayedName() const override; QString getDisplayedName() const override;

View File

@ -463,6 +463,7 @@ void Models::Roster::addContact(const QString& account, const QString& jid, cons
connect(contact, &Contact::fileDownloadRequest, this, &Roster::fileDownloadRequest); connect(contact, &Contact::fileDownloadRequest, this, &Roster::fileDownloadRequest);
connect(contact, &Contact::unnoticedMessage, this, &Roster::unnoticedMessage); connect(contact, &Contact::unnoticedMessage, this, &Roster::unnoticedMessage);
connect(contact, &Contact::localPathInvalid, this, &Roster::localPathInvalid); connect(contact, &Contact::localPathInvalid, this, &Roster::localPathInvalid);
connect(contact, &Contact::unreadMessagesCountChanged, this, &Roster::recalculateUnreadMessages);
contacts.insert(std::make_pair(id, contact)); contacts.insert(std::make_pair(id, contact));
} else { } else {
contact = itr->second; contact = itr->second;
@ -548,8 +549,8 @@ void Models::Roster::removeGroup(const QString& account, const QString& name)
void Models::Roster::changeContact(const QString& account, const QString& jid, const QMap<QString, QVariant>& data) void Models::Roster::changeContact(const QString& account, const QString& jid, const QMap<QString, QVariant>& data)
{ {
Element* el = getElement({account, jid}); Element* el = getElement(ElId(account, jid));
if (el != NULL) { if (el != nullptr) {
for (QMap<QString, QVariant>::const_iterator itr = data.begin(), end = data.end(); itr != end; ++itr) { for (QMap<QString, QVariant>::const_iterator itr = data.begin(), end = data.end(); itr != end; ++itr) {
el->update(itr.key(), itr.value()); el->update(itr.key(), itr.value());
} }
@ -558,8 +559,8 @@ void Models::Roster::changeContact(const QString& account, const QString& jid, c
void Models::Roster::changeMessage(const QString& account, const QString& jid, const QString& id, const QMap<QString, QVariant>& data) void Models::Roster::changeMessage(const QString& account, const QString& jid, const QString& id, const QMap<QString, QVariant>& data)
{ {
Element* el = getElement({account, jid}); Element* el = getElement(ElId(account, jid));
if (el != NULL) { if (el != nullptr) {
el->changeMessage(id, data); el->changeMessage(id, data);
} else { } else {
qDebug() << "A request to change a message of the contact " << jid << " in the account " << account << " but it wasn't found"; qDebug() << "A request to change a message of the contact " << jid << " in the account " << account << " but it wasn't found";
@ -706,8 +707,8 @@ void Models::Roster::removePresence(const QString& account, const QString& jid,
void Models::Roster::addMessage(const QString& account, const Shared::Message& data) void Models::Roster::addMessage(const QString& account, const Shared::Message& data)
{ {
Element* el = getElement({account, data.getPenPalJid()}); Element* el = getElement(ElId(account, data.getPenPalJid()));
if (el != NULL) { if (el != nullptr) {
el->addMessage(data); el->addMessage(data);
} }
} }
@ -763,7 +764,7 @@ void Models::Roster::removeAccount(const QString& account)
acc->deleteLater(); acc->deleteLater();
} }
QString Models::Roster::getContactName(const QString& account, const QString& jid) QString Models::Roster::getContactName(const QString& account, const QString& jid) const
{ {
ElId id(account, jid); ElId id(account, jid);
std::map<ElId, Contact*>::const_iterator cItr = contacts.find(id); std::map<ElId, Contact*>::const_iterator cItr = contacts.find(id);
@ -801,10 +802,11 @@ void Models::Roster::addRoom(const QString& account, const QString jid, const QM
} }
Room* room = new Room(acc, jid, data); Room* room = new Room(acc, jid, data);
connect(room, &Contact::requestArchive, this, &Roster::onElementRequestArchive); connect(room, &Room::requestArchive, this, &Roster::onElementRequestArchive);
connect(room, &Contact::fileDownloadRequest, this, &Roster::fileDownloadRequest); connect(room, &Room::fileDownloadRequest, this, &Roster::fileDownloadRequest);
connect(room, &Contact::unnoticedMessage, this, &Roster::unnoticedMessage); connect(room, &Room::unnoticedMessage, this, &Roster::unnoticedMessage);
connect(room, &Contact::localPathInvalid, this, &Roster::localPathInvalid); connect(room, &Room::localPathInvalid, this, &Roster::localPathInvalid);
connect(room, &Room::unreadMessagesCountChanged, this, &Roster::recalculateUnreadMessages);
rooms.insert(std::make_pair(id, room)); rooms.insert(std::make_pair(id, room));
acc->appendChild(room); acc->appendChild(room);
} }
@ -907,7 +909,7 @@ bool Models::Roster::groupHasContact(const QString& account, const QString& grou
} }
} }
QString Models::Roster::getContactIconPath(const QString& account, const QString& jid, const QString& resource) QString Models::Roster::getContactIconPath(const QString& account, const QString& jid, const QString& resource) const
{ {
ElId id(account, jid); ElId id(account, jid);
std::map<ElId, Contact*>::const_iterator cItr = contacts.find(id); std::map<ElId, Contact*>::const_iterator cItr = contacts.find(id);
@ -927,9 +929,36 @@ QString Models::Roster::getContactIconPath(const QString& account, const QString
return path; return path;
} }
Models::Account * Models::Roster::getAccount(const QString& name) Models::Account * Models::Roster::getAccount(const QString& name) {
return const_cast<Models::Account*>(getAccountConst(name));}
const Models::Account * Models::Roster::getAccountConst(const QString& name) const {
return accounts.at(name);}
const Models::Element * Models::Roster::getElementConst(const Models::Roster::ElId& id) const
{ {
return accounts.find(name)->second; std::map<ElId, Contact*>::const_iterator cItr = contacts.find(id);
if (cItr != contacts.end()) {
return cItr->second;
} else {
std::map<ElId, Room*>::const_iterator rItr = rooms.find(id);
if (rItr != rooms.end()) {
return rItr->second;
}
}
return nullptr;
}
bool Models::Roster::markMessageAsRead(const Models::Roster::ElId& elementId, const QString& messageId)
{
const Element* el = getElementConst(elementId);
if (el != nullptr) {
return el->markMessageAsRead(messageId);
} else {
return false;
}
} }
QModelIndex Models::Roster::getAccountIndex(const QString& name) QModelIndex Models::Roster::getAccountIndex(const QString& name)
@ -948,7 +977,7 @@ QModelIndex Models::Roster::getGroupIndex(const QString& account, const QString&
if (itr == accounts.end()) { if (itr == accounts.end()) {
return QModelIndex(); return QModelIndex();
} else { } else {
std::map<ElId, Group*>::const_iterator gItr = groups.find({account, name}); std::map<ElId, Group*>::const_iterator gItr = groups.find(ElId(account, name));
if (gItr == groups.end()) { if (gItr == groups.end()) {
return QModelIndex(); return QModelIndex();
} else { } else {
@ -958,6 +987,48 @@ QModelIndex Models::Roster::getGroupIndex(const QString& account, const QString&
} }
} }
QModelIndex Models::Roster::getContactIndex(const QString& account, const QString& jid, const QString& resource)
{
std::map<QString, Account*>::const_iterator itr = accounts.find(account);
if (itr == accounts.end()) {
return QModelIndex();
} else {
Account* acc = itr->second;
QModelIndex accIndex = index(acc->row(), 0, QModelIndex());
std::map<ElId, Contact*>::const_iterator cItr = contacts.find(ElId(account, jid));
if (cItr != contacts.end()) {
QModelIndex contactIndex = index(acc->getContact(jid), 0, accIndex);
if (resource.size() == 0) {
return contactIndex;
} else {
Presence* pres = cItr->second->getPresence(resource);
if (pres != nullptr) {
return index(pres->row(), 0, contactIndex);
} else {
return contactIndex;
}
}
} else {
std::map<ElId, Room*>::const_iterator rItr = rooms.find(ElId(account, jid));
if (rItr != rooms.end()) {
QModelIndex roomIndex = index(rItr->second->row(), 0, accIndex);
if (resource.size() == 0) {
return roomIndex;
} else {
Participant* part = rItr->second->getParticipant(resource);
if (part != nullptr) {
return index(part->row(), 0, roomIndex);
} else {
return roomIndex;
}
}
} else {
return QModelIndex();
}
}
}
}
void Models::Roster::onElementRequestArchive(const QString& before) void Models::Roster::onElementRequestArchive(const QString& before)
{ {
Element* el = static_cast<Element*>(sender()); Element* el = static_cast<Element*>(sender());
@ -968,7 +1039,7 @@ void Models::Roster::responseArchive(const QString& account, const QString& jid,
{ {
ElId id(account, jid); ElId id(account, jid);
Element* el = getElement(id); Element* el = getElement(id);
if (el != NULL) { if (el != nullptr) {
el->responseArchive(list, last); el->responseArchive(list, last);
} }
} }
@ -976,8 +1047,8 @@ void Models::Roster::responseArchive(const QString& account, const QString& jid,
void Models::Roster::fileProgress(const std::list<Shared::MessageInfo>& msgs, qreal value, bool up) void Models::Roster::fileProgress(const std::list<Shared::MessageInfo>& msgs, qreal value, bool up)
{ {
for (const Shared::MessageInfo& info : msgs) { for (const Shared::MessageInfo& info : msgs) {
Element* el = getElement({info.account, info.jid}); Element* el = getElement(ElId(info.account, info.jid));
if (el != NULL) { if (el != nullptr) {
el->fileProgress(info.messageId, value, up); el->fileProgress(info.messageId, value, up);
} }
} }
@ -986,8 +1057,8 @@ void Models::Roster::fileProgress(const std::list<Shared::MessageInfo>& msgs, qr
void Models::Roster::fileComplete(const std::list<Shared::MessageInfo>& msgs, bool up) void Models::Roster::fileComplete(const std::list<Shared::MessageInfo>& msgs, bool up)
{ {
for (const Shared::MessageInfo& info : msgs) { for (const Shared::MessageInfo& info : msgs) {
Element* el = getElement({info.account, info.jid}); Element* el = getElement(ElId(info.account, info.jid));
if (el != NULL) { if (el != nullptr) {
el->fileComplete(info.messageId, up); el->fileComplete(info.messageId, up);
} }
} }
@ -996,8 +1067,8 @@ void Models::Roster::fileComplete(const std::list<Shared::MessageInfo>& msgs, bo
void Models::Roster::fileError(const std::list<Shared::MessageInfo>& msgs, const QString& err, bool up) void Models::Roster::fileError(const std::list<Shared::MessageInfo>& msgs, const QString& err, bool up)
{ {
for (const Shared::MessageInfo& info : msgs) { for (const Shared::MessageInfo& info : msgs) {
Element* el = getElement({info.account, info.jid}); Element* el = getElement(ElId(info.account, info.jid));
if (el != NULL) { if (el != nullptr) {
el->fileError(info.messageId, err, up); el->fileError(info.messageId, err, up);
} }
} }
@ -1005,20 +1076,20 @@ void Models::Roster::fileError(const std::list<Shared::MessageInfo>& msgs, const
Models::Element * Models::Roster::getElement(const Models::Roster::ElId& id) Models::Element * Models::Roster::getElement(const Models::Roster::ElId& id)
{ {
std::map<ElId, Contact*>::iterator cItr = contacts.find(id); return const_cast<Models::Element*>(getElementConst(id));
}
if (cItr != contacts.end()) { Models::Item::Type Models::Roster::getContactType(const Models::Roster::ElId& id) const
return cItr->second; {
} else { const Models::Element* el = getElementConst(id);
std::map<ElId, Room*>::iterator rItr = rooms.find(id); if (el == nullptr) {
if (rItr != rooms.end()) { return Item::root;
return rItr->second;
}
} }
return NULL; return el->type;
} }
void Models::Roster::onAccountReconnected() void Models::Roster::onAccountReconnected()
{ {
Account* acc = static_cast<Account*>(sender()); Account* acc = static_cast<Account*>(sender());
@ -1031,3 +1102,14 @@ void Models::Roster::onAccountReconnected()
} }
} }
void Models::Roster::recalculateUnreadMessages()
{
int count(0);
for (const std::pair<const ElId, Contact*>& pair : contacts) {
count += pair.second->getMessagesCount();
}
for (const std::pair<const ElId, Room*>& pair : rooms) {
count += pair.second->getMessagesCount();
}
emit unreadMessagesCountChanged(count);
}

View File

@ -46,6 +46,7 @@ public:
Roster(QObject* parent = 0); Roster(QObject* parent = 0);
~Roster(); ~Roster();
public slots:
void addAccount(const QMap<QString, QVariant> &data); void addAccount(const QMap<QString, QVariant> &data);
void updateAccount(const QString& account, const QString& field, const QVariant& value); void updateAccount(const QString& account, const QString& field, const QVariant& value);
void removeAccount(const QString& account); void removeAccount(const QString& account);
@ -65,7 +66,12 @@ public:
void addRoomParticipant(const QString& account, const QString& jid, const QString& name, const QMap<QString, QVariant>& data); void addRoomParticipant(const QString& account, const QString& jid, const QString& name, const QMap<QString, QVariant>& data);
void changeRoomParticipant(const QString& account, const QString& jid, const QString& name, const QMap<QString, QVariant>& data); void changeRoomParticipant(const QString& account, const QString& jid, const QString& name, const QMap<QString, QVariant>& data);
void removeRoomParticipant(const QString& account, const QString& jid, const QString& name); void removeRoomParticipant(const QString& account, const QString& jid, const QString& name);
QString getContactName(const QString& account, const QString& jid);
public:
QString getContactName(const QString& account, const QString& jid) const;
Item::Type getContactType(const Models::Roster::ElId& id) const;
const Element* getElementConst(const ElId& id) const;
Element* getElement(const ElId& id);
QVariant data ( const QModelIndex& index, int role ) const override; QVariant data ( const QModelIndex& index, int role ) const override;
Qt::ItemFlags flags(const QModelIndex &index) const override; Qt::ItemFlags flags(const QModelIndex &index) const override;
@ -77,10 +83,13 @@ public:
std::deque<QString> groupList(const QString& account) const; std::deque<QString> groupList(const QString& account) const;
bool groupHasContact(const QString& account, const QString& group, const QString& contactJID) const; bool groupHasContact(const QString& account, const QString& group, const QString& contactJID) const;
QString getContactIconPath(const QString& account, const QString& jid, const QString& resource); QString getContactIconPath(const QString& account, const QString& jid, const QString& resource) const;
Account* getAccount(const QString& name); Account* getAccount(const QString& name);
const Account* getAccountConst(const QString& name) const;
QModelIndex getAccountIndex(const QString& name); QModelIndex getAccountIndex(const QString& name);
QModelIndex getGroupIndex(const QString& account, const QString& name); QModelIndex getGroupIndex(const QString& account, const QString& name);
QModelIndex getContactIndex(const QString& account, const QString& jid, const QString& resource = "");
bool markMessageAsRead(const ElId& elementId, const QString& messageId);
void responseArchive(const QString& account, const QString& jid, const std::list<Shared::Message>& list, bool last); void 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 fileProgress(const std::list<Shared::MessageInfo>& msgs, qreal value, bool up);
@ -92,12 +101,10 @@ public:
signals: signals:
void requestArchive(const QString& account, const QString& jid, const QString& before); void requestArchive(const QString& account, const QString& jid, const QString& before);
void fileDownloadRequest(const QString& url); void fileDownloadRequest(const QString& url);
void unreadMessagesCountChanged(int count);
void unnoticedMessage(const QString& account, const Shared::Message& msg); void unnoticedMessage(const QString& account, const Shared::Message& msg);
void localPathInvalid(const QString& path); void localPathInvalid(const QString& path);
private:
Element* getElement(const ElId& id);
private slots: private slots:
void onAccountDataChanged(const QModelIndex& tl, const QModelIndex& br, const QVector<int>& roles); void onAccountDataChanged(const QModelIndex& tl, const QModelIndex& br, const QVector<int>& roles);
void onAccountReconnected(); void onAccountReconnected();
@ -109,6 +116,7 @@ private slots:
void onChildIsAboutToBeMoved(Item* source, int first, int last, Item* destination, int newIndex); void onChildIsAboutToBeMoved(Item* source, int first, int last, Item* destination, int newIndex);
void onChildMoved(); void onChildMoved();
void onElementRequestArchive(const QString& before); void onElementRequestArchive(const QString& before);
void recalculateUnreadMessages();
private: private:
Item* root; Item* root;

View File

@ -21,17 +21,14 @@
#include <QDebug> #include <QDebug>
#include <QIcon> #include <QIcon>
Squawk::Squawk(QWidget *parent) : Squawk::Squawk(Models::Roster& p_rosterModel, QWidget *parent) :
QMainWindow(parent), QMainWindow(parent),
m_ui(new Ui::Squawk), m_ui(new Ui::Squawk),
accounts(nullptr), accounts(nullptr),
preferences(nullptr), preferences(nullptr),
about(nullptr), about(nullptr),
dialogueQueue(this), rosterModel(p_rosterModel),
rosterModel(),
conversations(),
contextMenu(new QMenu()), contextMenu(new QMenu()),
dbus("org.freedesktop.Notifications", "/org/freedesktop/Notifications", "org.freedesktop.Notifications", QDBusConnection::sessionBus()),
vCards(), vCards(),
currentConversation(nullptr), currentConversation(nullptr),
restoreSelection(), restoreSelection(),
@ -65,12 +62,8 @@ Squawk::Squawk(QWidget *parent) :
connect(m_ui->roster, &QTreeView::customContextMenuRequested, this, &Squawk::onRosterContextMenu); connect(m_ui->roster, &QTreeView::customContextMenuRequested, this, &Squawk::onRosterContextMenu);
connect(m_ui->roster, &QTreeView::collapsed, this, &Squawk::onItemCollepsed); connect(m_ui->roster, &QTreeView::collapsed, this, &Squawk::onItemCollepsed);
connect(m_ui->roster->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &Squawk::onRosterSelectionChanged); connect(m_ui->roster->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &Squawk::onRosterSelectionChanged);
connect(&rosterModel, &Models::Roster::unnoticedMessage, this, &Squawk::onUnnoticedMessage);
connect(rosterModel.accountsModel, &Models::Accounts::sizeChanged, this, &Squawk::onAccountsSizeChanged); connect(rosterModel.accountsModel, &Models::Accounts::sizeChanged, this, &Squawk::onAccountsSizeChanged);
connect(&rosterModel, &Models::Roster::requestArchive, this, &Squawk::onRequestArchive);
connect(&rosterModel, &Models::Roster::fileDownloadRequest, this, &Squawk::fileDownloadRequest);
connect(&rosterModel, &Models::Roster::localPathInvalid, this, &Squawk::localPathInvalid);
connect(contextMenu, &QMenu::aboutToHide, this, &Squawk::onContextAboutToHide); connect(contextMenu, &QMenu::aboutToHide, this, &Squawk::onContextAboutToHide);
connect(m_ui->actionAboutSquawk, &QAction::triggered, this, &Squawk::onAboutSquawkCalled); connect(m_ui->actionAboutSquawk, &QAction::triggered, this, &Squawk::onAboutSquawkCalled);
//m_ui->mainToolBar->addWidget(m_ui->comboBox); //m_ui->mainToolBar->addWidget(m_ui->comboBox);
@ -200,36 +193,25 @@ void Squawk::closeEvent(QCloseEvent* event)
about->close(); about->close();
} }
for (Conversations::const_iterator itr = conversations.begin(), end = conversations.end(); itr != end; ++itr) {
disconnect(itr->second, &Conversation::destroyed, this, &Squawk::onConversationClosed);
itr->second->close();
}
conversations.clear();
for (std::map<QString, VCard*>::const_iterator itr = vCards.begin(), end = vCards.end(); itr != end; ++itr) { for (std::map<QString, VCard*>::const_iterator itr = vCards.begin(), end = vCards.end(); itr != end; ++itr) {
disconnect(itr->second, &VCard::destroyed, this, &Squawk::onVCardClosed); disconnect(itr->second, &VCard::destroyed, this, &Squawk::onVCardClosed);
itr->second->close(); itr->second->close();
} }
vCards.clear(); vCards.clear();
writeSettings();
emit closing();;
QMainWindow::closeEvent(event); QMainWindow::closeEvent(event);
} }
void Squawk::onAccountsClosed() {
accounts = nullptr;}
void Squawk::onAccountsClosed() void Squawk::onPreferencesClosed() {
{ preferences = nullptr;}
accounts = nullptr;
}
void Squawk::onPreferencesClosed() void Squawk::onAboutSquawkClosed() {
{ about = nullptr;}
preferences = nullptr;
}
void Squawk::newAccount(const QMap<QString, QVariant>& account)
{
rosterModel.addAccount(account);
}
void Squawk::onComboboxActivated(int index) void Squawk::onComboboxActivated(int index)
{ {
@ -237,85 +219,11 @@ void Squawk::onComboboxActivated(int index)
emit changeState(av); emit changeState(av);
} }
void Squawk::changeAccount(const QString& account, const QMap<QString, QVariant>& data) void Squawk::expand(const QModelIndex& index) {
{ m_ui->roster->expand(index);}
for (QMap<QString, QVariant>::const_iterator itr = data.begin(), end = data.end(); itr != end; ++itr) {
QString attr = itr.key();
rosterModel.updateAccount(account, attr, *itr);
}
}
void Squawk::addContact(const QString& account, const QString& jid, const QString& group, const QMap<QString, QVariant>& data) void Squawk::stateChanged(Shared::Availability state) {
{ m_ui->comboBox->setCurrentIndex(static_cast<int>(state));}
rosterModel.addContact(account, jid, group, data);
QSettings settings;
settings.beginGroup("ui");
settings.beginGroup("roster");
settings.beginGroup(account);
if (settings.value("expanded", false).toBool()) {
QModelIndex ind = rosterModel.getAccountIndex(account);
m_ui->roster->expand(ind);
}
settings.endGroup();
settings.endGroup();
settings.endGroup();
}
void Squawk::addGroup(const QString& account, const QString& name)
{
rosterModel.addGroup(account, name);
QSettings settings;
settings.beginGroup("ui");
settings.beginGroup("roster");
settings.beginGroup(account);
if (settings.value("expanded", false).toBool()) {
QModelIndex ind = rosterModel.getAccountIndex(account);
m_ui->roster->expand(ind);
if (settings.value(name + "/expanded", false).toBool()) {
m_ui->roster->expand(rosterModel.getGroupIndex(account, name));
}
}
settings.endGroup();
settings.endGroup();
settings.endGroup();
}
void Squawk::removeGroup(const QString& account, const QString& name)
{
rosterModel.removeGroup(account, name);
}
void Squawk::changeContact(const QString& account, const QString& jid, const QMap<QString, QVariant>& data)
{
rosterModel.changeContact(account, jid, data);
}
void Squawk::removeContact(const QString& account, const QString& jid)
{
rosterModel.removeContact(account, jid);
}
void Squawk::removeContact(const QString& account, const QString& jid, const QString& group)
{
rosterModel.removeContact(account, jid, group);
}
void Squawk::addPresence(const QString& account, const QString& jid, const QString& name, const QMap<QString, QVariant>& data)
{
rosterModel.addPresence(account, jid, name, data);
}
void Squawk::removePresence(const QString& account, const QString& jid, const QString& name)
{
rosterModel.removePresence(account, jid, name);
}
void Squawk::stateChanged(Shared::Availability state)
{
m_ui->comboBox->setCurrentIndex(static_cast<int>(state));
}
void Squawk::onRosterItemDoubleClicked(const QModelIndex& item) void Squawk::onRosterItemDoubleClicked(const QModelIndex& item)
{ {
@ -326,212 +234,33 @@ void Squawk::onRosterItemDoubleClicked(const QModelIndex& item)
} }
Models::Contact* contact = nullptr; Models::Contact* contact = nullptr;
Models::Room* room = nullptr; Models::Room* room = nullptr;
QString res;
Models::Roster::ElId* id = nullptr;
switch (node->type) { switch (node->type) {
case Models::Item::contact: case Models::Item::contact:
contact = static_cast<Models::Contact*>(node); contact = static_cast<Models::Contact*>(node);
id = new Models::Roster::ElId(contact->getAccountName(), contact->getJid()); emit openConversation(Models::Roster::ElId(contact->getAccountName(), contact->getJid()));
break; break;
case Models::Item::presence: case Models::Item::presence:
contact = static_cast<Models::Contact*>(node->parentItem()); contact = static_cast<Models::Contact*>(node->parentItem());
id = new Models::Roster::ElId(contact->getAccountName(), contact->getJid()); emit openConversation(Models::Roster::ElId(contact->getAccountName(), contact->getJid()), node->getName());
res = node->getName();
break; break;
case Models::Item::room: case Models::Item::room:
room = static_cast<Models::Room*>(node); room = static_cast<Models::Room*>(node);
id = new Models::Roster::ElId(room->getAccountName(), room->getJid()); emit openConversation(Models::Roster::ElId(room->getAccountName(), room->getJid()));
break; break;
default: default:
m_ui->roster->expand(item); m_ui->roster->expand(item);
break; break;
} }
if (id != nullptr) {
Conversations::const_iterator itr = conversations.find(*id);
Models::Account* acc = rosterModel.getAccount(id->account);
Conversation* conv = nullptr;
bool created = false;
if (itr != conversations.end()) {
conv = itr->second;
} else if (contact != nullptr) {
created = true;
conv = new Chat(acc, contact);
} else if (room != nullptr) {
created = true;
conv = new Room(acc, room);
if (!room->getJoined()) {
emit setRoomJoined(id->account, id->name, true);
}
}
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 (res.size() > 0) {
conv->setPalResource(res);
}
}
delete id;
}
} }
} }
void Squawk::onConversationClosed(QObject* parent) void Squawk::closeCurrentConversation()
{ {
Conversation* conv = static_cast<Conversation*>(sender()); if (currentConversation != nullptr) {
Models::Roster::ElId id(conv->getAccount(), conv->getJid());
Conversations::const_iterator itr = conversations.find(id);
if (itr != conversations.end()) {
conversations.erase(itr);
}
if (conv->isMuc) {
Room* room = static_cast<Room*>(conv);
if (!room->autoJoined()) {
emit setRoomJoined(id.account, id.name, false);
}
}
}
void Squawk::fileProgress(const std::list<Shared::MessageInfo> msgs, qreal value, bool up)
{
rosterModel.fileProgress(msgs, value, up);
}
void Squawk::fileDownloadComplete(const std::list<Shared::MessageInfo> msgs, const QString& path)
{
rosterModel.fileComplete(msgs, false);
}
void Squawk::fileError(const std::list<Shared::MessageInfo> msgs, const QString& error, bool up)
{
rosterModel.fileError(msgs, error, up);
}
void Squawk::fileUploadComplete(const std::list<Shared::MessageInfo> msgs, const QString& url, const QString& path)
{
rosterModel.fileComplete(msgs, true);
}
void Squawk::accountMessage(const QString& account, const Shared::Message& data)
{
rosterModel.addMessage(account, data);
}
void Squawk::onUnnoticedMessage(const QString& account, const Shared::Message& msg)
{
notify(account, msg); //Telegram does this way - notifies even if the app is visible
QApplication::alert(this);
}
void Squawk::changeMessage(const QString& account, const QString& jid, const QString& id, const QMap<QString, QVariant>& data)
{
rosterModel.changeMessage(account, jid, id, data);
}
void Squawk::notify(const QString& account, const Shared::Message& msg)
{
QString name = QString(rosterModel.getContactName(account, msg.getPenPalJid()));
QString path = QString(rosterModel.getContactIconPath(account, msg.getPenPalJid(), msg.getPenPalResource()));
QVariantList args;
args << QString(QCoreApplication::applicationName());
args << QVariant(QVariant::UInt); //TODO some normal id
if (path.size() > 0) {
args << path;
} else {
args << QString("mail-message"); //TODO should here better be unknown user icon?
}
if (msg.getType() == Shared::Message::groupChat) {
args << msg.getFromResource() + " from " + name;
} else {
args << name;
}
QString body(msg.getBody());
QString oob(msg.getOutOfBandUrl());
if (body == oob) {
body = tr("Attached file");
}
args << body;
args << QStringList();
args << QVariantMap();
args << 3000;
dbus.callWithArgumentList(QDBus::AutoDetect, "Notify", args);
}
void Squawk::onConversationMessage(const Shared::Message& msg)
{
Conversation* conv = static_cast<Conversation*>(sender());
QString acc = conv->getAccount();
rosterModel.addMessage(acc, msg);
emit sendMessage(acc, msg);
}
void Squawk::onConversationReplaceMessage(const QString& originalId, const Shared::Message& msg)
{
Conversation* conv = static_cast<Conversation*>(sender());
QString acc = conv->getAccount();
rosterModel.changeMessage(acc, msg.getPenPalJid(), originalId, {
{"state", static_cast<uint>(Shared::Message::State::pending)}
});
emit replaceMessage(acc, originalId, msg);
}
void Squawk::onConversationResend(const QString& id)
{
Conversation* conv = static_cast<Conversation*>(sender());
QString acc = conv->getAccount();
QString jid = conv->getJid();
emit resendMessage(acc, jid, id);
}
void Squawk::onRequestArchive(const QString& account, const QString& jid, const QString& before)
{
emit requestArchive(account, jid, 20, before); //TODO amount as a settings value
}
void Squawk::responseArchive(const QString& account, const QString& jid, const std::list<Shared::Message>& list, bool last)
{
rosterModel.responseArchive(account, jid, list, last);
}
void Squawk::removeAccount(const QString& account)
{
Conversations::const_iterator itr = conversations.begin();
while (itr != conversations.end()) {
if (itr->first.account == account) {
Conversations::const_iterator lItr = itr;
++itr;
Conversation* conv = lItr->second;
disconnect(conv, &Conversation::destroyed, this, &Squawk::onConversationClosed);
conv->close();
conversations.erase(lItr);
} else {
++itr;
}
}
if (currentConversation != nullptr && currentConversation->getAccount() == account) {
currentConversation->deleteLater(); currentConversation->deleteLater();
currentConversation = nullptr; currentConversation = nullptr;
m_ui->filler->show(); m_ui->filler->show();
} }
rosterModel.removeAccount(account);
} }
void Squawk::onRosterContextMenu(const QPoint& point) void Squawk::onRosterContextMenu(const QPoint& point)
@ -569,13 +298,13 @@ void Squawk::onRosterContextMenu(const QPoint& point)
break; break;
case Models::Item::contact: { case Models::Item::contact: {
Models::Contact* cnt = static_cast<Models::Contact*>(item); Models::Contact* cnt = static_cast<Models::Contact*>(item);
Models::Roster::ElId id(cnt->getAccountName(), cnt->getJid());
QString cntName = cnt->getName();
hasMenu = true; hasMenu = true;
QAction* dialog = contextMenu->addAction(Shared::icon("mail-message"), tr("Open dialog")); QAction* dialog = contextMenu->addAction(Shared::icon("mail-message"), tr("Open dialog"));
dialog->setEnabled(active); dialog->setEnabled(active);
connect(dialog, &QAction::triggered, [this, index]() { connect(dialog, &QAction::triggered, std::bind(&Squawk::onRosterItemDoubleClicked, this, index));
onRosterItemDoubleClicked(index);
});
Shared::SubscriptionState state = cnt->getState(); Shared::SubscriptionState state = cnt->getState();
switch (state) { switch (state) {
@ -583,9 +312,7 @@ void Squawk::onRosterContextMenu(const QPoint& point)
case Shared::SubscriptionState::to: { case Shared::SubscriptionState::to: {
QAction* unsub = contextMenu->addAction(Shared::icon("news-unsubscribe"), tr("Unsubscribe")); QAction* unsub = contextMenu->addAction(Shared::icon("news-unsubscribe"), tr("Unsubscribe"));
unsub->setEnabled(active); unsub->setEnabled(active);
connect(unsub, &QAction::triggered, [this, cnt]() { connect(unsub, &QAction::triggered, std::bind(&Squawk::changeSubscription, this, id, false));
emit unsubscribeContact(cnt->getAccountName(), cnt->getJid(), "");
});
} }
break; break;
case Shared::SubscriptionState::from: case Shared::SubscriptionState::from:
@ -593,75 +320,68 @@ void Squawk::onRosterContextMenu(const QPoint& point)
case Shared::SubscriptionState::none: { case Shared::SubscriptionState::none: {
QAction* sub = contextMenu->addAction(Shared::icon("news-subscribe"), tr("Subscribe")); QAction* sub = contextMenu->addAction(Shared::icon("news-subscribe"), tr("Subscribe"));
sub->setEnabled(active); sub->setEnabled(active);
connect(sub, &QAction::triggered, [this, cnt]() { connect(sub, &QAction::triggered, std::bind(&Squawk::changeSubscription, this, id, true));
emit subscribeContact(cnt->getAccountName(), cnt->getJid(), "");
});
} }
} }
QString accName = cnt->getAccountName();
QString cntJID = cnt->getJid();
QString cntName = cnt->getName();
QAction* rename = contextMenu->addAction(Shared::icon("edit-rename"), tr("Rename")); QAction* rename = contextMenu->addAction(Shared::icon("edit-rename"), tr("Rename"));
rename->setEnabled(active); rename->setEnabled(active);
connect(rename, &QAction::triggered, [this, cntName, accName, cntJID]() { connect(rename, &QAction::triggered, [this, cntName, id]() {
QInputDialog* dialog = new QInputDialog(this); QInputDialog* dialog = new QInputDialog(this);
connect(dialog, &QDialog::accepted, [this, dialog, cntName, accName, cntJID]() { connect(dialog, &QDialog::accepted, [this, dialog, cntName, id]() {
QString newName = dialog->textValue(); QString newName = dialog->textValue();
if (newName != cntName) { if (newName != cntName) {
emit renameContactRequest(accName, cntJID, newName); emit renameContactRequest(id.account, id.name, newName);
} }
dialog->deleteLater(); dialog->deleteLater();
}); });
connect(dialog, &QDialog::rejected, dialog, &QObject::deleteLater); connect(dialog, &QDialog::rejected, dialog, &QObject::deleteLater);
dialog->setInputMode(QInputDialog::TextInput); dialog->setInputMode(QInputDialog::TextInput);
dialog->setLabelText(tr("Input new name for %1\nor leave it empty for the contact \nto be displayed as %1").arg(cntJID)); dialog->setLabelText(tr("Input new name for %1\nor leave it empty for the contact \nto be displayed as %1").arg(id.name));
dialog->setWindowTitle(tr("Renaming %1").arg(cntJID)); dialog->setWindowTitle(tr("Renaming %1").arg(id.name));
dialog->setTextValue(cntName); dialog->setTextValue(cntName);
dialog->exec(); dialog->exec();
}); });
QMenu* groupsMenu = contextMenu->addMenu(Shared::icon("group"), tr("Groups")); QMenu* groupsMenu = contextMenu->addMenu(Shared::icon("group"), tr("Groups"));
std::deque<QString> groupList = rosterModel.groupList(accName); std::deque<QString> groupList = rosterModel.groupList(id.account);
for (QString groupName : groupList) { for (QString groupName : groupList) {
QAction* gr = groupsMenu->addAction(groupName); QAction* gr = groupsMenu->addAction(groupName);
gr->setCheckable(true); gr->setCheckable(true);
gr->setChecked(rosterModel.groupHasContact(accName, groupName, cntJID)); gr->setChecked(rosterModel.groupHasContact(id.account, groupName, id.name));
gr->setEnabled(active); gr->setEnabled(active);
connect(gr, &QAction::toggled, [this, accName, groupName, cntJID](bool checked) { connect(gr, &QAction::toggled, [this, groupName, id](bool checked) {
if (checked) { if (checked) {
emit addContactToGroupRequest(accName, cntJID, groupName); emit addContactToGroupRequest(id.account, id.name, groupName);
} else { } else {
emit removeContactFromGroupRequest(accName, cntJID, groupName); emit removeContactFromGroupRequest(id.account, id.name, groupName);
} }
}); });
} }
QAction* newGroup = groupsMenu->addAction(Shared::icon("group-new"), tr("New group")); QAction* newGroup = groupsMenu->addAction(Shared::icon("group-new"), tr("New group"));
newGroup->setEnabled(active); newGroup->setEnabled(active);
connect(newGroup, &QAction::triggered, [this, accName, cntJID]() { connect(newGroup, &QAction::triggered, [this, id]() {
QInputDialog* dialog = new QInputDialog(this); QInputDialog* dialog = new QInputDialog(this);
connect(dialog, &QDialog::accepted, [this, dialog, accName, cntJID]() { connect(dialog, &QDialog::accepted, [this, dialog, id]() {
emit addContactToGroupRequest(accName, cntJID, dialog->textValue()); emit addContactToGroupRequest(id.account, id.name, dialog->textValue());
dialog->deleteLater(); dialog->deleteLater();
}); });
connect(dialog, &QDialog::rejected, dialog, &QObject::deleteLater); connect(dialog, &QDialog::rejected, dialog, &QObject::deleteLater);
dialog->setInputMode(QInputDialog::TextInput); dialog->setInputMode(QInputDialog::TextInput);
dialog->setLabelText(tr("New group name")); dialog->setLabelText(tr("New group name"));
dialog->setWindowTitle(tr("Add %1 to a new group").arg(cntJID)); dialog->setWindowTitle(tr("Add %1 to a new group").arg(id.name));
dialog->exec(); dialog->exec();
}); });
QAction* card = contextMenu->addAction(Shared::icon("user-properties"), tr("VCard")); QAction* card = contextMenu->addAction(Shared::icon("user-properties"), tr("VCard"));
card->setEnabled(active); card->setEnabled(active);
connect(card, &QAction::triggered, std::bind(&Squawk::onActivateVCard, this, accName, cnt->getJid(), false)); connect(card, &QAction::triggered, std::bind(&Squawk::onActivateVCard, this, id.account, id.name, false));
QAction* remove = contextMenu->addAction(Shared::icon("edit-delete"), tr("Remove")); QAction* remove = contextMenu->addAction(Shared::icon("edit-delete"), tr("Remove"));
remove->setEnabled(active); remove->setEnabled(active);
connect(remove, &QAction::triggered, [this, cnt]() { connect(remove, &QAction::triggered, std::bind(&Squawk::removeContactRequest, this, id.account, id.name));
emit removeContactRequest(cnt->getAccountName(), cnt->getJid());
});
} }
break; break;
@ -680,32 +400,16 @@ void Squawk::onRosterContextMenu(const QPoint& point)
if (room->getAutoJoin()) { if (room->getAutoJoin()) {
QAction* unsub = contextMenu->addAction(Shared::icon("news-unsubscribe"), tr("Unsubscribe")); QAction* unsub = contextMenu->addAction(Shared::icon("news-unsubscribe"), tr("Unsubscribe"));
unsub->setEnabled(active); unsub->setEnabled(active);
connect(unsub, &QAction::triggered, [this, id]() { connect(unsub, &QAction::triggered, std::bind(&Squawk::changeSubscription, this, id, false));
emit setRoomAutoJoin(id.account, id.name, false);
if (conversations.find(id) == conversations.end()
&& (currentConversation == nullptr || currentConversation->getId() != id)
) { //to leave the room if it's not opened in a conversation window
emit setRoomJoined(id.account, id.name, false);
}
});
} else { } else {
QAction* unsub = contextMenu->addAction(Shared::icon("news-subscribe"), tr("Subscribe")); QAction* sub = contextMenu->addAction(Shared::icon("news-subscribe"), tr("Subscribe"));
unsub->setEnabled(active); sub->setEnabled(active);
connect(unsub, &QAction::triggered, [this, id]() { connect(sub, &QAction::triggered, std::bind(&Squawk::changeSubscription, this, id, true));
emit setRoomAutoJoin(id.account, id.name, true);
if (conversations.find(id) == conversations.end()
&& (currentConversation == nullptr || currentConversation->getId() != id)
) { //to join the room if it's not already joined
emit setRoomJoined(id.account, id.name, true);
}
});
} }
QAction* remove = contextMenu->addAction(Shared::icon("edit-delete"), tr("Remove")); QAction* remove = contextMenu->addAction(Shared::icon("edit-delete"), tr("Remove"));
remove->setEnabled(active); remove->setEnabled(active);
connect(remove, &QAction::triggered, [this, id]() { connect(remove, &QAction::triggered, std::bind(&Squawk::removeRoomRequest, this, id.account, id.name));
emit removeRoomRequest(id.account, id.name);
});
} }
break; break;
default: default:
@ -717,36 +421,6 @@ void Squawk::onRosterContextMenu(const QPoint& point)
} }
} }
void Squawk::addRoom(const QString& account, const QString jid, const QMap<QString, QVariant>& data)
{
rosterModel.addRoom(account, jid, data);
}
void Squawk::changeRoom(const QString& account, const QString jid, const QMap<QString, QVariant>& data)
{
rosterModel.changeRoom(account, jid, data);
}
void Squawk::removeRoom(const QString& account, const QString jid)
{
rosterModel.removeRoom(account, jid);
}
void Squawk::addRoomParticipant(const QString& account, const QString& jid, const QString& name, const QMap<QString, QVariant>& data)
{
rosterModel.addRoomParticipant(account, jid, name, data);
}
void Squawk::changeRoomParticipant(const QString& account, const QString& jid, const QString& name, const QMap<QString, QVariant>& data)
{
rosterModel.changeRoomParticipant(account, jid, name, data);
}
void Squawk::removeRoomParticipant(const QString& account, const QString& jid, const QString& name)
{
rosterModel.removeRoomParticipant(account, jid, name);
}
void Squawk::responseVCard(const QString& jid, const Shared::VCard& card) void Squawk::responseVCard(const QString& jid, const Shared::VCard& card)
{ {
std::map<QString, VCard*>::const_iterator itr = vCards.find(jid); std::map<QString, VCard*>::const_iterator itr = vCards.find(jid);
@ -804,61 +478,40 @@ void Squawk::onVCardSave(const Shared::VCard& card, const QString& account)
widget->deleteLater(); widget->deleteLater();
} }
void Squawk::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();
m_ui->comboBox->setCurrentIndex(avail);
emit changeState(Shared::Global::fromInt<Shared::Availability>(avail));
}
void Squawk::writeSettings() void Squawk::writeSettings()
{ {
QSettings settings; QSettings settings;
settings.beginGroup("ui"); settings.beginGroup("ui");
settings.beginGroup("window"); settings.beginGroup("window");
settings.setValue("geometry", saveGeometry()); settings.setValue("geometry", saveGeometry());
settings.setValue("state", saveState()); settings.setValue("state", saveState());
settings.endGroup(); settings.endGroup();
settings.setValue("splitter", m_ui->splitter->saveState()); settings.setValue("splitter", m_ui->splitter->saveState());
settings.remove("roster");
settings.setValue("availability", m_ui->comboBox->currentIndex()); settings.beginGroup("roster");
int size = rosterModel.accountsModel->rowCount(QModelIndex());
settings.remove("roster"); for (int i = 0; i < size; ++i) {
settings.beginGroup("roster"); QModelIndex acc = rosterModel.index(i, 0, QModelIndex());
int size = rosterModel.accountsModel->rowCount(QModelIndex()); Models::Account* account = rosterModel.accountsModel->getAccount(i);
for (int i = 0; i < size; ++i) { QString accName = account->getName();
QModelIndex acc = rosterModel.index(i, 0, QModelIndex()); settings.beginGroup(accName);
Models::Account* account = rosterModel.accountsModel->getAccount(i);
QString accName = account->getName(); settings.setValue("expanded", m_ui->roster->isExpanded(acc));
settings.beginGroup(accName); std::deque<QString> groups = rosterModel.groupList(accName);
for (const QString& groupName : groups) {
settings.setValue("expanded", m_ui->roster->isExpanded(acc)); settings.beginGroup(groupName);
std::deque<QString> groups = rosterModel.groupList(accName); QModelIndex gIndex = rosterModel.getGroupIndex(accName, groupName);
for (const QString& groupName : groups) { settings.setValue("expanded", m_ui->roster->isExpanded(gIndex));
settings.beginGroup(groupName); settings.endGroup();
QModelIndex gIndex = rosterModel.getGroupIndex(accName, groupName); }
settings.setValue("expanded", m_ui->roster->isExpanded(gIndex));
settings.endGroup(); settings.endGroup();
} }
settings.endGroup(); settings.endGroup();
}
settings.endGroup();
settings.endGroup(); settings.endGroup();
settings.sync(); settings.sync();
qDebug() << "Saved settings";
} }
void Squawk::onItemCollepsed(const QModelIndex& index) void Squawk::onItemCollepsed(const QModelIndex& index)
@ -880,24 +533,6 @@ void Squawk::onItemCollepsed(const QModelIndex& index)
} }
} }
void Squawk::requestPassword(const QString& account, bool authenticationError) {
if (authenticationError) {
dialogueQueue.addAction(account, DialogQueue::askCredentials);
} else {
dialogueQueue.addAction(account, DialogQueue::askPassword);
}
}
void Squawk::subscribeConversation(Conversation* conv)
{
connect(conv, &Conversation::destroyed, this, &Squawk::onConversationClosed);
connect(conv, &Conversation::sendMessage, this, &Squawk::onConversationMessage);
connect(conv, &Conversation::replaceMessage, this, &Squawk::onConversationReplaceMessage);
connect(conv, &Conversation::resendMessage, this, &Squawk::onConversationResend);
connect(conv, &Conversation::notifyableMessage, this, &Squawk::notify);
}
void Squawk::onRosterSelectionChanged(const QModelIndex& current, const QModelIndex& previous) void Squawk::onRosterSelectionChanged(const QModelIndex& current, const QModelIndex& previous)
{ {
if (restoreSelection.isValid() && restoreSelection == current) { if (restoreSelection.isValid() && restoreSelection == current) {
@ -969,16 +604,12 @@ void Squawk::onRosterSelectionChanged(const QModelIndex& current, const QModelIn
currentConversation = new Chat(acc, contact); currentConversation = new Chat(acc, contact);
} else if (room != nullptr) { } else if (room != nullptr) {
currentConversation = new Room(acc, room); currentConversation = new Room(acc, room);
if (!room->getJoined()) {
emit setRoomJoined(id->account, id->name, true);
}
} }
if (!testAttribute(Qt::WA_TranslucentBackground)) { if (!testAttribute(Qt::WA_TranslucentBackground)) {
currentConversation->setFeedFrames(true, false, true, true); currentConversation->setFeedFrames(true, false, true, true);
} }
subscribeConversation(currentConversation); emit openedConversation();
if (res.size() > 0) { if (res.size() > 0) {
currentConversation->setPalResource(res); currentConversation->setPalResource(res);
@ -988,18 +619,10 @@ void Squawk::onRosterSelectionChanged(const QModelIndex& current, const QModelIn
delete id; delete id;
} else { } else {
if (currentConversation != nullptr) { closeCurrentConversation();
currentConversation->deleteLater();
currentConversation = nullptr;
m_ui->filler->show();
}
} }
} else { } else {
if (currentConversation != nullptr) { closeCurrentConversation();
currentConversation->deleteLater();
currentConversation = nullptr;
m_ui->filler->show();
}
} }
} }
@ -1024,7 +647,17 @@ void Squawk::onAboutSquawkCalled()
about->show(); about->show();
} }
void Squawk::onAboutSquawkClosed() Models::Roster::ElId Squawk::currentConversationId() const
{ {
about = nullptr; if (currentConversation == nullptr) {
return Models::Roster::ElId();
} else {
return Models::Roster::ElId(currentConversation->getAccount(), currentConversation->getJid());
}
}
void Squawk::select(QModelIndex index)
{
m_ui->roster->scrollTo(index, QAbstractItemView::EnsureVisible);
m_ui->roster->selectionModel()->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect);
} }

View File

@ -22,7 +22,6 @@
#include <QMainWindow> #include <QMainWindow>
#include <QScopedPointer> #include <QScopedPointer>
#include <QCloseEvent> #include <QCloseEvent>
#include <QtDBus/QDBusInterface>
#include <QSettings> #include <QSettings>
#include <QInputDialog> #include <QInputDialog>
@ -40,94 +39,67 @@
#include "widgets/vcard/vcard.h" #include "widgets/vcard/vcard.h"
#include "widgets/settings/settings.h" #include "widgets/settings/settings.h"
#include "widgets/about.h" #include "widgets/about.h"
#include "dialogqueue.h"
#include "shared/shared.h" #include "shared/shared.h"
#include "shared/global.h"
namespace Ui { namespace Ui {
class Squawk; class Squawk;
} }
class Application;
class Squawk : public QMainWindow class Squawk : public QMainWindow
{ {
Q_OBJECT Q_OBJECT
friend class DialogQueue; friend class Application;
public: public:
explicit Squawk(QWidget *parent = nullptr); explicit Squawk(Models::Roster& rosterModel, QWidget *parent = nullptr);
~Squawk() override; ~Squawk() override;
signals: signals:
void closing();
void newAccountRequest(const QMap<QString, QVariant>&); void newAccountRequest(const QMap<QString, QVariant>&);
void modifyAccountRequest(const QString&, const QMap<QString, QVariant>&);
void removeAccountRequest(const QString&); void removeAccountRequest(const QString&);
void connectAccount(const QString&); void connectAccount(const QString&);
void disconnectAccount(const QString&); void disconnectAccount(const QString&);
void changeState(Shared::Availability state); void changeState(Shared::Availability state);
void sendMessage(const QString& account, const Shared::Message& data);
void replaceMessage(const QString& account, const QString& originalId, const Shared::Message& data);
void resendMessage(const QString& account, const QString& jid, const QString& id);
void requestArchive(const QString& account, const QString& jid, int count, const QString& before);
void subscribeContact(const QString& account, const QString& jid, const QString& reason);
void unsubscribeContact(const QString& account, const QString& jid, const QString& reason);
void removeContactRequest(const QString& account, const QString& jid); void removeContactRequest(const QString& account, const QString& jid);
void addContactRequest(const QString& account, const QString& jid, const QString& name, const QSet<QString>& groups); void addContactRequest(const QString& account, const QString& jid, const QString& name, const QSet<QString>& groups);
void addContactToGroupRequest(const QString& account, const QString& jid, const QString& groupName); void addContactToGroupRequest(const QString& account, const QString& jid, const QString& groupName);
void removeContactFromGroupRequest(const QString& account, const QString& jid, const QString& groupName); void removeContactFromGroupRequest(const QString& account, const QString& jid, const QString& groupName);
void renameContactRequest(const QString& account, const QString& jid, const QString& newName); void renameContactRequest(const QString& account, const QString& jid, const QString& newName);
void setRoomJoined(const QString& account, const QString& jid, bool joined);
void setRoomAutoJoin(const QString& account, const QString& jid, bool joined);
void addRoomRequest(const QString& account, const QString& jid, const QString& nick, const QString& password, bool autoJoin); void addRoomRequest(const QString& account, const QString& jid, const QString& nick, const QString& password, bool autoJoin);
void removeRoomRequest(const QString& account, const QString& jid); void removeRoomRequest(const QString& account, const QString& jid);
void fileDownloadRequest(const QString& url);
void requestVCard(const QString& account, const QString& jid); void requestVCard(const QString& account, const QString& jid);
void uploadVCard(const QString& account, const Shared::VCard& card); void uploadVCard(const QString& account, const Shared::VCard& card);
void responsePassword(const QString& account, const QString& password);
void localPathInvalid(const QString& path);
void changeDownloadsPath(const QString& path); void changeDownloadsPath(const QString& path);
void notify(const QString& account, const Shared::Message& msg);
void changeSubscription(const Models::Roster::ElId& id, bool subscribe);
void openedConversation();
void openConversation(const Models::Roster::ElId& id, const QString& resource = "");
void modifyAccountRequest(const QString&, const QMap<QString, QVariant>&);
public:
Models::Roster::ElId currentConversationId() const;
void closeCurrentConversation();
public slots: public slots:
void writeSettings(); void writeSettings();
void readSettings();
void newAccount(const QMap<QString, QVariant>& account);
void changeAccount(const QString& account, const QMap<QString, QVariant>& data);
void removeAccount(const QString& account);
void addGroup(const QString& account, const QString& name);
void removeGroup(const QString& account, const QString& name);
void addContact(const QString& account, const QString& jid, const QString& group, const QMap<QString, QVariant>& data);
void removeContact(const QString& account, const QString& jid, const QString& group);
void removeContact(const QString& account, const QString& jid);
void changeContact(const QString& account, const QString& jid, const QMap<QString, QVariant>& data);
void addPresence(const QString& account, const QString& jid, const QString& name, const QMap<QString, QVariant>& data);
void removePresence(const QString& account, const QString& jid, const QString& name);
void stateChanged(Shared::Availability state); void stateChanged(Shared::Availability state);
void accountMessage(const QString& account, const Shared::Message& data);
void responseArchive(const QString& account, const QString& jid, const std::list<Shared::Message>& list, bool last);
void addRoom(const QString& account, const QString jid, const QMap<QString, QVariant>& data);
void changeRoom(const QString& account, const QString jid, const QMap<QString, QVariant>& data);
void removeRoom(const QString& account, const QString jid);
void addRoomParticipant(const QString& account, const QString& jid, const QString& name, const QMap<QString, QVariant>& data);
void changeRoomParticipant(const QString& account, const QString& jid, const QString& name, const QMap<QString, QVariant>& data);
void removeRoomParticipant(const QString& account, const QString& jid, const QString& name);
void fileError(const std::list<Shared::MessageInfo> msgs, const QString& error, bool up);
void fileProgress(const std::list<Shared::MessageInfo> msgs, qreal value, bool up);
void fileDownloadComplete(const std::list<Shared::MessageInfo> msgs, const QString& path);
void fileUploadComplete(const std::list<Shared::MessageInfo> msgs, const QString& url, const QString& path);
void responseVCard(const QString& jid, const Shared::VCard& card); void responseVCard(const QString& jid, const Shared::VCard& card);
void changeMessage(const QString& account, const QString& jid, const QString& id, const QMap<QString, QVariant>& data); void select(QModelIndex index);
void requestPassword(const QString& account, bool authenticationError);
private: private:
typedef std::map<Models::Roster::ElId, Conversation*> Conversations;
QScopedPointer<Ui::Squawk> m_ui; QScopedPointer<Ui::Squawk> m_ui;
Accounts* accounts; Accounts* accounts;
Settings* preferences; Settings* preferences;
About* about; About* about;
DialogQueue dialogueQueue; Models::Roster& rosterModel;
Models::Roster rosterModel;
Conversations conversations;
QMenu* contextMenu; QMenu* contextMenu;
QDBusInterface dbus;
std::map<QString, VCard*> vCards; std::map<QString, VCard*> vCards;
Conversation* currentConversation; Conversation* currentConversation;
QModelIndex restoreSelection; QModelIndex restoreSelection;
@ -135,9 +107,7 @@ private:
protected: protected:
void closeEvent(QCloseEvent * event) override; void closeEvent(QCloseEvent * event) override;
void expand(const QModelIndex& index);
protected slots:
void notify(const QString& account, const Shared::Message& msg);
private slots: private slots:
void onAccounts(); void onAccounts();
@ -149,27 +119,17 @@ private slots:
void onAccountsSizeChanged(unsigned int size); void onAccountsSizeChanged(unsigned int size);
void onAccountsClosed(); void onAccountsClosed();
void onPreferencesClosed(); void onPreferencesClosed();
void onConversationClosed(QObject* parent = 0);
void onVCardClosed(); void onVCardClosed();
void onVCardSave(const Shared::VCard& card, const QString& account); void onVCardSave(const Shared::VCard& card, const QString& account);
void onActivateVCard(const QString& account, const QString& jid, bool edition = false); void onActivateVCard(const QString& account, const QString& jid, bool edition = false);
void onComboboxActivated(int index); void onComboboxActivated(int index);
void onRosterItemDoubleClicked(const QModelIndex& item); void onRosterItemDoubleClicked(const QModelIndex& item);
void onConversationMessage(const Shared::Message& msg);
void onConversationReplaceMessage(const QString& originalId, const Shared::Message& msg);
void onConversationResend(const QString& id);
void onRequestArchive(const QString& account, const QString& jid, const QString& before);
void onRosterContextMenu(const QPoint& point); void onRosterContextMenu(const QPoint& point);
void onItemCollepsed(const QModelIndex& index); void onItemCollepsed(const QModelIndex& index);
void onRosterSelectionChanged(const QModelIndex& current, const QModelIndex& previous); void onRosterSelectionChanged(const QModelIndex& current, const QModelIndex& previous);
void onContextAboutToHide(); void onContextAboutToHide();
void onAboutSquawkCalled(); void onAboutSquawkCalled();
void onAboutSquawkClosed(); void onAboutSquawkClosed();
void onUnnoticedMessage(const QString& account, const Shared::Message& msg);
private:
void subscribeConversation(Conversation* conv);
}; };
#endif // SQUAWK_H #endif // SQUAWK_H

View File

@ -26,7 +26,7 @@
#include <QFileDialog> #include <QFileDialog>
#include <QMimeDatabase> #include <QMimeDatabase>
#include <QAbstractTextDocumentLayout> #include <QAbstractTextDocumentLayout>
#include <QCoreApplication> #include <QApplication>
#include <QTemporaryFile> #include <QTemporaryFile>
#include <QDir> #include <QDir>
#include <QMenu> #include <QMenu>
@ -499,6 +499,16 @@ void Conversation::onFeedContext(const QPoint& pos)
}); });
} }
QString body = item->getBody();
if (body.size() > 0) {
showMenu = true;
QAction* copy = contextMenu->addAction(Shared::icon("edit-copy"), tr("Copy message"));
connect(copy, &QAction::triggered, [body] () {
QClipboard* cb = QApplication::clipboard();
cb->setText(body);
});
}
QString path = Shared::resolvePath(item->getAttachPath()); QString path = Shared::resolvePath(item->getAttachPath());
if (path.size() > 0) { if (path.size() > 0) {
showMenu = true; showMenu = true;

View File

@ -664,9 +664,32 @@ int MessageDelegate::paintBody(const Models::FeedItem& data, QPainter* painter,
if (data.text.size() > 0) { if (data.text.size() > 0) {
bodyRenderer->setHtml(Shared::processMessageBody(data.text)); bodyRenderer->setHtml(Shared::processMessageBody(data.text));
bodyRenderer->setTextWidth(option.rect.size().width()); bodyRenderer->setTextWidth(option.rect.size().width());
painter->setBackgroundMode(Qt::BGMode::OpaqueMode);
painter->save(); painter->save();
// QTextCursor cursor(bodyRenderer);
// cursor.setPosition(2, QTextCursor::KeepAnchor);
painter->translate(option.rect.topLeft()); painter->translate(option.rect.topLeft());
// QTextFrameFormat format = bodyRenderer->rootFrame()->frameFormat();
// format.setBackground(option.palette.brush(QPalette::Active, QPalette::Highlight));
// bodyRenderer->rootFrame()->setFrameFormat(format);
bodyRenderer->drawContents(painter); bodyRenderer->drawContents(painter);
// QColor c = option.palette.color(QPalette::Active, QPalette::Highlight);
// QTextBlock b = bodyRenderer->begin();
// QTextBlockFormat format = b.blockFormat();
// format.setBackground(option.palette.brush(QPalette::Active, QPalette::Highlight));
// format.setProperty(QTextFormat::BackgroundBrush, option.palette.brush(QPalette::Active, QPalette::Highlight));
// QTextCursor cursor(bodyRenderer);
// cursor.setBlockFormat(format);
// b = bodyRenderer->begin();
// while (b.isValid() > 0) {
// QTextLayout* lay = b.layout();
// QTextLayout::FormatRange range;
// range.format = b.charFormat();
// range.start = 0;
// range.length = 2;
// lay->draw(painter, option.rect.topLeft(), {range});
// b = b.next();
// }
painter->restore(); painter->restore();
QSize bodySize(std::ceil(bodyRenderer->idealWidth()), std::ceil(bodyRenderer->size().height())); QSize bodySize(std::ceil(bodyRenderer->idealWidth()), std::ceil(bodyRenderer->size().height()));

View File

@ -29,6 +29,7 @@
#include <QPushButton> #include <QPushButton>
#include <QProgressBar> #include <QProgressBar>
#include <QLabel> #include <QLabel>
#include <QTextDocument>
#include "shared/icons.h" #include "shared/icons.h"
#include "shared/global.h" #include "shared/global.h"

View File

@ -163,6 +163,12 @@ void Models::MessageFeed::changeMessage(const QString& id, const QMap<QString, Q
} }
emit dataChanged(index, index, cr); emit dataChanged(index, index, cr);
if (observersAmount == 0 && !msg->getForwarded() && changeRoles.count(MessageRoles::Text) > 0) {
unreadMessages->insert(id);
emit unreadMessagesCountChanged();
emit unnoticedMessage(*msg);
}
} }
} }
@ -312,12 +318,7 @@ QVariant Models::MessageFeed::data(const QModelIndex& index, int role) const
case Bulk: { case Bulk: {
FeedItem item; FeedItem item;
item.id = msg->getId(); item.id = msg->getId();
markMessageAsRead(item.id);
std::set<QString>::const_iterator umi = unreadMessages->find(item.id);
if (umi != unreadMessages->end()) {
unreadMessages->erase(umi);
emit unreadMessagesCountChanged();
}
item.sentByMe = sentByMe(*msg); item.sentByMe = sentByMe(*msg);
item.date = msg->getTime(); item.date = msg->getTime();
@ -367,6 +368,17 @@ int Models::MessageFeed::rowCount(const QModelIndex& parent) const
return storage.size(); return storage.size();
} }
bool Models::MessageFeed::markMessageAsRead(const QString& id) const
{
std::set<QString>::const_iterator umi = unreadMessages->find(id);
if (umi != unreadMessages->end()) {
unreadMessages->erase(umi);
emit unreadMessagesCountChanged();
return true;
}
return false;
}
unsigned int Models::MessageFeed::unreadMessagesCount() const unsigned int Models::MessageFeed::unreadMessagesCount() const
{ {
return unreadMessages->size(); return unreadMessages->size();

View File

@ -72,6 +72,7 @@ public:
void reportLocalPathInvalid(const QString& messageId); void reportLocalPathInvalid(const QString& messageId);
unsigned int unreadMessagesCount() const; unsigned int unreadMessagesCount() const;
bool markMessageAsRead(const QString& id) const;
void fileProgress(const QString& messageId, qreal value, bool up); void fileProgress(const QString& messageId, qreal value, bool up);
void fileError(const QString& messageId, const QString& error, bool up); void fileError(const QString& messageId, const QString& error, bool up);
void fileComplete(const QString& messageId, bool up); void fileComplete(const QString& messageId, bool up);