feat/tray_pictogram #70
29
CHANGELOG.md
29
CHANGELOG.md
@ -1,6 +1,28 @@
|
||||
# Changelog
|
||||
|
||||
## Squawk 0.2.1 (UNRELEASED)
|
||||
## Squawk 0.2.2 (May 05, 2022)
|
||||
### Bug fixes
|
||||
- now when you remove an account it actually gets removed
|
||||
- segfault on unitialized Availability in some rare occesions
|
||||
- fixed crash when you open a dialog with someone that has only error messages in archive
|
||||
- message height is now calculated correctly on Chinese and Japanese paragraphs
|
||||
- the app doesn't crash on SIGINT anymore
|
||||
|
||||
### Improvements
|
||||
- there is a way to disable an account and it wouldn't connect when you change availability
|
||||
- if you cancel password query an account becomes inactive and doesn't annoy you anymore
|
||||
- if you filled password field and chose KWallet as a storage Squawk wouldn't ask you again for the same password
|
||||
- if left the password field empty and chose KWallet as a storage Squawk will try to get that passord from KWallet before asking you to input it
|
||||
- accounts now connect to the server asyncronously - if one is stopped on password prompt another is connecting
|
||||
- actualized translations, added English localization file
|
||||
|
||||
### New features
|
||||
- new "About" window with links, license, gratitudes
|
||||
- if the authentication failed Squawk will ask againg for your password and login
|
||||
- now there is an amount of unread messages showing on top of Squawk launcher icon
|
||||
- notifications now have buttons to open a conversation or to mark that message as read
|
||||
|
||||
## Squawk 0.2.1 (Apr 02, 2022)
|
||||
### Bug fixes
|
||||
- build in release mode now no longer spams warnings
|
||||
- build now correctly installs all build plugin libs
|
||||
@ -11,12 +33,13 @@
|
||||
### Improvements
|
||||
- reduced amount of places where platform specific path separator is used
|
||||
- now message input is automatically focused when you open a dialog or a room
|
||||
- what() method on unhandled exception now actually tells what happened
|
||||
|
||||
### New features
|
||||
- the settings are here! You con config different stuff from there
|
||||
- now it's possible to set up different qt styles from settings
|
||||
- if you have KConfig nad KConfigWidgets packages installed - you can chose from global color schemes
|
||||
- it's possible now to chose a folder where squawk is going to store downloaded files
|
||||
- if you have KConfig nad KConfigWidgets packages installed - you can choose from global color schemes
|
||||
- it's possible now to choose a folder where squawk is going to store downloaded files
|
||||
- now you can correct your message
|
||||
|
||||
## Squawk 0.2.0 (Jan 10, 2022)
|
||||
|
@ -1,5 +1,5 @@
|
||||
cmake_minimum_required(VERSION 3.4)
|
||||
project(squawk VERSION 0.2.1 LANGUAGES CXX)
|
||||
project(squawk VERSION 0.2.2 LANGUAGES CXX)
|
||||
|
||||
cmake_policy(SET CMP0076 NEW)
|
||||
cmake_policy(SET CMP0079 NEW)
|
||||
@ -148,6 +148,7 @@ if(CMAKE_COMPILER_IS_GNUCXX)
|
||||
target_compile_options(squawk PRIVATE ${COMPILE_OPTIONS})
|
||||
endif(CMAKE_COMPILER_IS_GNUCXX)
|
||||
|
||||
add_subdirectory(main)
|
||||
add_subdirectory(core)
|
||||
add_subdirectory(external/simpleCrypt)
|
||||
add_subdirectory(packaging)
|
||||
@ -159,6 +160,8 @@ add_subdirectory(ui)
|
||||
|
||||
# Install the executable
|
||||
install(TARGETS squawk DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||
install(FILES README.md DESTINATION ${CMAKE_INSTALL_DATADIR}/macaw.me/squawk)
|
||||
install(FILES LICENSE.md DESTINATION ${CMAKE_INSTALL_DATADIR}/macaw.me/squawk)
|
||||
|
||||
if (CMAKE_BUILD_TYPE STREQUAL "Release")
|
||||
if (APPLE)
|
||||
|
@ -4,7 +4,7 @@
|
||||
[![AUR version](https://img.shields.io/aur/version/squawk?style=flat-square)](https://aur.archlinux.org/packages/squawk/)
|
||||
[![Liberapay patrons](https://img.shields.io/liberapay/patrons/macaw.me?logo=liberapay&style=flat-square)](https://liberapay.com/macaw.me)
|
||||
|
||||
![Squawk screenshot](https://macaw.me/images/squawk/0.2.0.png)
|
||||
![Squawk screenshot](https://macaw.me/images/squawk/0.2.2.png)
|
||||
|
||||
### Prerequisites
|
||||
|
||||
|
@ -6,14 +6,12 @@ endif(WIN32)
|
||||
target_sources(squawk PRIVATE
|
||||
account.cpp
|
||||
account.h
|
||||
adapterFuctions.cpp
|
||||
archive.cpp
|
||||
archive.h
|
||||
adapterfunctions.cpp
|
||||
adapterfunctions.h
|
||||
conference.cpp
|
||||
conference.h
|
||||
contact.cpp
|
||||
contact.h
|
||||
main.cpp
|
||||
networkaccess.cpp
|
||||
networkaccess.h
|
||||
rosteritem.cpp
|
||||
@ -22,13 +20,10 @@ target_sources(squawk PRIVATE
|
||||
signalcatcher.h
|
||||
squawk.cpp
|
||||
squawk.h
|
||||
storage.cpp
|
||||
storage.h
|
||||
urlstorage.cpp
|
||||
urlstorage.h
|
||||
)
|
||||
|
||||
target_include_directories(squawk PRIVATE ${LMDB_INCLUDE_DIRS})
|
||||
|
||||
add_subdirectory(handlers)
|
||||
add_subdirectory(storage)
|
||||
add_subdirectory(passwordStorageEngines)
|
||||
|
582
core/account.cpp
582
core/account.cpp
@ -22,7 +22,7 @@
|
||||
|
||||
using namespace Core;
|
||||
|
||||
Account::Account(const QString& p_login, const QString& p_server, const QString& p_password, const QString& p_name, NetworkAccess* p_net, QObject* parent):
|
||||
Account::Account(const QString& p_login, const QString& p_server, const QString& p_password, const QString& p_name, bool p_active, NetworkAccess* p_net, QObject* parent):
|
||||
QObject(parent),
|
||||
name(p_name),
|
||||
archiveQueries(),
|
||||
@ -41,13 +41,15 @@ Account::Account(const QString& p_login, const QString& p_server, const QString&
|
||||
rcpm(new QXmppMessageReceiptManager()),
|
||||
reconnectScheduled(false),
|
||||
reconnectTimer(new QTimer),
|
||||
avatarHash(),
|
||||
avatarType(),
|
||||
ownVCardRequestInProgress(false),
|
||||
network(p_net),
|
||||
passwordType(Shared::AccountPassword::plain),
|
||||
lastError(Error::none),
|
||||
pepSupport(false),
|
||||
active(p_active),
|
||||
notReadyPassword(false),
|
||||
mh(new MessageHandler(this)),
|
||||
rh(new RosterHandler(this))
|
||||
rh(new RosterHandler(this)),
|
||||
vh(new VCardHandler(this))
|
||||
{
|
||||
config.setUser(p_login);
|
||||
config.setDomain(p_server);
|
||||
@ -73,10 +75,6 @@ Account::Account(const QString& p_login, const QString& p_server, const QString&
|
||||
|
||||
client.addExtension(mm);
|
||||
client.addExtension(bm);
|
||||
|
||||
QObject::connect(vm, &QXmppVCardManager::vCardReceived, this, &Account::onVCardReceived);
|
||||
//QObject::connect(&vm, &QXmppVCardManager::clientVCardReceived, this, &Account::onOwnVCardReceived); //for some reason it doesn't work, launching from common handler
|
||||
|
||||
client.addExtension(um);
|
||||
QObject::connect(um, &QXmppUploadRequestManager::slotReceived, mh, &MessageHandler::onUploadSlotReceived);
|
||||
QObject::connect(um, &QXmppUploadRequestManager::requestFailed, mh, &MessageHandler::onUploadSlotRequestFailed);
|
||||
@ -91,62 +89,18 @@ Account::Account(const QString& p_login, const QString& p_server, const QString&
|
||||
client.addExtension(rcpm);
|
||||
QObject::connect(rcpm, &QXmppMessageReceiptManager::messageDelivered, mh, &MessageHandler::onReceiptReceived);
|
||||
|
||||
|
||||
QString path(QStandardPaths::writableLocation(QStandardPaths::CacheLocation));
|
||||
path += "/" + name;
|
||||
QDir dir(path);
|
||||
|
||||
if (!dir.exists()) {
|
||||
bool res = dir.mkpath(path);
|
||||
if (!res) {
|
||||
qDebug() << "Couldn't create a cache directory for account" << name;
|
||||
throw 22;
|
||||
}
|
||||
}
|
||||
|
||||
QFile* avatar = new QFile(path + "/avatar.png");
|
||||
QString type = "png";
|
||||
if (!avatar->exists()) {
|
||||
delete avatar;
|
||||
avatar = new QFile(path + "/avatar.jpg");
|
||||
type = "jpg";
|
||||
if (!avatar->exists()) {
|
||||
delete avatar;
|
||||
avatar = new QFile(path + "/avatar.jpeg");
|
||||
type = "jpeg";
|
||||
if (!avatar->exists()) {
|
||||
delete avatar;
|
||||
avatar = new QFile(path + "/avatar.gif");
|
||||
type = "gif";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (avatar->exists()) {
|
||||
if (avatar->open(QFile::ReadOnly)) {
|
||||
QCryptographicHash sha1(QCryptographicHash::Sha1);
|
||||
sha1.addData(avatar);
|
||||
avatarHash = sha1.result();
|
||||
avatarType = type;
|
||||
}
|
||||
}
|
||||
if (avatarType.size() != 0) {
|
||||
presence.setVCardUpdateType(QXmppPresence::VCardUpdateValidPhoto);
|
||||
presence.setPhotoHash(avatarHash.toUtf8());
|
||||
} else {
|
||||
presence.setVCardUpdateType(QXmppPresence::VCardUpdateNotReady);
|
||||
}
|
||||
|
||||
reconnectTimer->setSingleShot(true);
|
||||
QObject::connect(reconnectTimer, &QTimer::timeout, this, &Account::onReconnectTimer);
|
||||
|
||||
// QXmppLogger* logger = new QXmppLogger(this);
|
||||
// logger->setLoggingType(QXmppLogger::SignalLogging);
|
||||
// client.setLogger(logger);
|
||||
//
|
||||
// QObject::connect(logger, &QXmppLogger::message, this, [](QXmppLogger::MessageType type, const QString& text){
|
||||
// qDebug() << text;
|
||||
// });
|
||||
if (name == "Test") {
|
||||
QXmppLogger* logger = new QXmppLogger(this);
|
||||
logger->setLoggingType(QXmppLogger::SignalLogging);
|
||||
client.setLogger(logger);
|
||||
|
||||
QObject::connect(logger, &QXmppLogger::message, this, [](QXmppLogger::MessageType type, const QString& text){
|
||||
qDebug() << text;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Account::~Account()
|
||||
@ -160,6 +114,7 @@ Account::~Account()
|
||||
QObject::disconnect(network, &NetworkAccess::downloadFileComplete, mh, &MessageHandler::onDownloadFileComplete);
|
||||
QObject::disconnect(network, &NetworkAccess::loadFileError, mh, &MessageHandler::onLoadFileError);
|
||||
|
||||
delete vh;
|
||||
delete mh;
|
||||
delete rh;
|
||||
|
||||
@ -185,7 +140,12 @@ void Core::Account::connect()
|
||||
reconnectTimer->stop();
|
||||
}
|
||||
if (state == Shared::ConnectionState::disconnected) {
|
||||
client.connectToServer(config, presence);
|
||||
if (notReadyPassword) {
|
||||
emit needPassword();
|
||||
} else {
|
||||
client.connectToServer(config, presence);
|
||||
}
|
||||
|
||||
} else {
|
||||
qDebug("An attempt to connect an account which is already connected, skipping");
|
||||
}
|
||||
@ -224,6 +184,7 @@ void Core::Account::onClientStateChange(QXmppClient::State st)
|
||||
dm->requestItems(getServer());
|
||||
dm->requestInfo(getServer());
|
||||
}
|
||||
lastError = Error::none;
|
||||
emit connectionStateChanged(state);
|
||||
}
|
||||
} else {
|
||||
@ -255,45 +216,17 @@ void Core::Account::onClientStateChange(QXmppClient::State st)
|
||||
|
||||
void Core::Account::reconnect()
|
||||
{
|
||||
if (state == Shared::ConnectionState::connected && !reconnectScheduled) {
|
||||
reconnectScheduled = true;
|
||||
reconnectTimer->start(500);
|
||||
client.disconnectFromServer();
|
||||
} else {
|
||||
qDebug() << "An attempt to reconnect account" << getName() << "which was not connected";
|
||||
if (!reconnectScheduled) { //TODO define behavior if It was connection or disconnecting
|
||||
if (state == Shared::ConnectionState::connected) {
|
||||
reconnectScheduled = true;
|
||||
reconnectTimer->start(500);
|
||||
client.disconnectFromServer();
|
||||
} else {
|
||||
qDebug() << "An attempt to reconnect account" << getName() << "which was not connected";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QString Core::Account::getName() const {
|
||||
return name;}
|
||||
|
||||
QString Core::Account::getLogin() const {
|
||||
return config.user();}
|
||||
|
||||
QString Core::Account::getPassword() const {
|
||||
return config.password();}
|
||||
|
||||
QString Core::Account::getServer() const {
|
||||
return config.domain();}
|
||||
|
||||
Shared::AccountPassword Core::Account::getPasswordType() const {
|
||||
return passwordType;}
|
||||
|
||||
void Core::Account::setPasswordType(Shared::AccountPassword pt) {
|
||||
passwordType = pt; }
|
||||
|
||||
void Core::Account::setLogin(const QString& p_login) {
|
||||
config.setUser(p_login);}
|
||||
|
||||
void Core::Account::setName(const QString& p_name) {
|
||||
name = p_name;}
|
||||
|
||||
void Core::Account::setPassword(const QString& p_password) {
|
||||
config.setPassword(p_password);}
|
||||
|
||||
void Core::Account::setServer(const QString& p_server) {
|
||||
config.setDomain(p_server);}
|
||||
|
||||
Shared::Availability Core::Account::getAvailability() const
|
||||
{
|
||||
if (state == Shared::ConnectionState::connected) {
|
||||
@ -325,32 +258,11 @@ void Core::Account::onPresenceReceived(const QXmppPresence& p_presence)
|
||||
QString jid = comps.front().toLower();
|
||||
QString resource = comps.back();
|
||||
|
||||
QString myJid = getLogin() + "@" + getServer();
|
||||
|
||||
if (jid == myJid) {
|
||||
if (jid == getBareJid()) {
|
||||
if (resource == getResource()) {
|
||||
emit availabilityChanged(static_cast<Shared::Availability>(p_presence.availableStatusType()));
|
||||
} else {
|
||||
if (!ownVCardRequestInProgress) {
|
||||
switch (p_presence.vCardUpdateType()) {
|
||||
case QXmppPresence::VCardUpdateNone: //this presence has nothing to do with photo
|
||||
break;
|
||||
case QXmppPresence::VCardUpdateNotReady: //let's say the photo didn't change here
|
||||
break;
|
||||
case QXmppPresence::VCardUpdateNoPhoto: //there is no photo, need to drop if any
|
||||
if (avatarType.size() > 0) {
|
||||
vm->requestClientVCard();
|
||||
ownVCardRequestInProgress = true;
|
||||
}
|
||||
break;
|
||||
case QXmppPresence::VCardUpdateValidPhoto: //there is a photo, need to load
|
||||
if (avatarHash != p_presence.photoHash()) {
|
||||
vm->requestClientVCard();
|
||||
ownVCardRequestInProgress = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
vh->handleOtherPresenceOfMyAccountChange(p_presence);
|
||||
}
|
||||
} else {
|
||||
RosterItem* item = rh->getRosterItem(jid);
|
||||
@ -392,18 +304,6 @@ void Core::Account::onPresenceReceived(const QXmppPresence& p_presence)
|
||||
}
|
||||
}
|
||||
|
||||
QString Core::Account::getResource() const {
|
||||
return config.resource();}
|
||||
|
||||
void Core::Account::setResource(const QString& p_resource) {
|
||||
config.setResource(p_resource);}
|
||||
|
||||
QString Core::Account::getFullJid() const {
|
||||
return getLogin() + "@" + getServer() + "/" + getResource();}
|
||||
|
||||
void Core::Account::sendMessage(const Shared::Message& data) {
|
||||
mh->sendMessage(data);}
|
||||
|
||||
void Core::Account::onMamMessageReceived(const QString& queryId, const QXmppMessage& msg)
|
||||
{
|
||||
if (msg.id().size() > 0 && (msg.body().size() > 0 || msg.outOfBandUrl().size() > 0)) {
|
||||
@ -517,6 +417,7 @@ void Core::Account::onClientError(QXmppClient::Error err)
|
||||
qDebug() << "Error";
|
||||
QString errorText;
|
||||
QString errorType;
|
||||
lastError = Error::other;
|
||||
switch (err) {
|
||||
case QXmppClient::SocketError:
|
||||
errorText = client.socketErrorString();
|
||||
@ -558,6 +459,7 @@ void Core::Account::onClientError(QXmppClient::Error err)
|
||||
break;
|
||||
case QXmppStanza::Error::NotAuthorized:
|
||||
errorText = "Authentication error";
|
||||
lastError = Error::authentication;
|
||||
break;
|
||||
#if (QXMPP_VERSION) < QT_VERSION_CHECK(1, 3, 0)
|
||||
case QXmppStanza::Error::PaymentRequired:
|
||||
@ -667,6 +569,166 @@ void Core::Account::setRoomJoined(const QString& jid, bool joined)
|
||||
conf->setJoined(joined);
|
||||
}
|
||||
|
||||
void Core::Account::onDiscoveryItemsReceived(const QXmppDiscoveryIq& items)
|
||||
{
|
||||
if (items.from() == getServer()) {
|
||||
std::set<QString> needToRequest;
|
||||
qDebug() << "Server items list received for account " << name << ":";
|
||||
for (QXmppDiscoveryIq::Item item : items.items()) {
|
||||
QString jid = item.jid();
|
||||
if (jid != getServer()) {
|
||||
qDebug() << " Node" << jid;
|
||||
needToRequest.insert(jid);
|
||||
} else {
|
||||
qDebug() << " " << item.node().toStdString().c_str();
|
||||
}
|
||||
}
|
||||
|
||||
for (const QString& jid : needToRequest) {
|
||||
dm->requestInfo(jid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Core::Account::onDiscoveryInfoReceived(const QXmppDiscoveryIq& info)
|
||||
{
|
||||
if (info.from() == getServer()) {
|
||||
bool enableCC = false;
|
||||
qDebug() << "Server info received for account" << name;
|
||||
QStringList features = info.features();
|
||||
qDebug() << "List of supported features of the server " << getServer() << ":";
|
||||
for (const QString& feature : features) {
|
||||
qDebug() << " " << feature.toStdString().c_str();
|
||||
if (feature == "urn:xmpp:carbons:2") {
|
||||
enableCC = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (enableCC) {
|
||||
qDebug() << "Enabling carbon copies for account" << name;
|
||||
cm->setCarbonsEnabled(true);
|
||||
}
|
||||
|
||||
qDebug() << "Requesting account" << name << "capabilities";
|
||||
dm->requestInfo(getBareJid());
|
||||
} else if (info.from() == getBareJid()) {
|
||||
qDebug() << "Received capabilities for account" << name << ":";
|
||||
QList<QXmppDiscoveryIq::Identity> identities = info.identities();
|
||||
bool pepSupported = false;
|
||||
for (const QXmppDiscoveryIq::Identity& identity : identities) {
|
||||
QString type = identity.type();
|
||||
qDebug() << " " << identity.category() << type;
|
||||
if (type == "pep") {
|
||||
pepSupported = true;
|
||||
}
|
||||
}
|
||||
rh->setPepSupport(pepSupported);
|
||||
} else {
|
||||
qDebug() << "Received info for account" << name << "about" << info.from();
|
||||
QList<QXmppDiscoveryIq::Identity> identities = info.identities();
|
||||
for (const QXmppDiscoveryIq::Identity& identity : identities) {
|
||||
qDebug() << " " << identity.name() << identity.category() << identity.type();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Core::Account::handleDisconnection()
|
||||
{
|
||||
cm->setCarbonsEnabled(false);
|
||||
rh->handleOffline();
|
||||
vh->handleOffline();
|
||||
archiveQueries.clear();
|
||||
}
|
||||
|
||||
void Core::Account::onContactHistoryResponse(const std::list<Shared::Message>& list, bool last)
|
||||
{
|
||||
RosterItem* contact = static_cast<RosterItem*>(sender());
|
||||
|
||||
qDebug() << "Collected history for contact " << contact->jid << list.size() << "elements";
|
||||
if (last) {
|
||||
qDebug() << "The response contains the first accounted message";
|
||||
}
|
||||
emit responseArchive(contact->jid, list, last);
|
||||
}
|
||||
|
||||
bool Core::Account::getActive() const {
|
||||
return active;}
|
||||
|
||||
void Core::Account::setActive(bool p_active) {
|
||||
if (active != p_active) {
|
||||
active = p_active;
|
||||
|
||||
emit changed({
|
||||
{"active", active}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
QString Core::Account::getResource() const {
|
||||
return config.resource();}
|
||||
|
||||
void Core::Account::setResource(const QString& p_resource) {
|
||||
config.setResource(p_resource);}
|
||||
|
||||
QString Core::Account::getBareJid() const {
|
||||
return getLogin() + "@" + getServer();}
|
||||
|
||||
QString Core::Account::getFullJid() const {
|
||||
return getBareJid() + "/" + getResource();}
|
||||
|
||||
QString Core::Account::getName() const {
|
||||
return name;}
|
||||
|
||||
QString Core::Account::getLogin() const {
|
||||
return config.user();}
|
||||
|
||||
QString Core::Account::getPassword() const {
|
||||
return config.password();}
|
||||
|
||||
QString Core::Account::getServer() const {
|
||||
return config.domain();}
|
||||
|
||||
Shared::AccountPassword Core::Account::getPasswordType() const {
|
||||
return passwordType;}
|
||||
|
||||
void Core::Account::setPasswordType(Shared::AccountPassword pt) {
|
||||
passwordType = pt; }
|
||||
|
||||
void Core::Account::setLogin(const QString& p_login) {
|
||||
config.setUser(p_login);}
|
||||
|
||||
void Core::Account::setName(const QString& p_name) {
|
||||
name = p_name;}
|
||||
|
||||
void Core::Account::setPassword(const QString& p_password) {
|
||||
config.setPassword(p_password);
|
||||
notReadyPassword = false;
|
||||
}
|
||||
|
||||
void Core::Account::setServer(const QString& p_server) {
|
||||
config.setDomain(p_server);}
|
||||
|
||||
void Core::Account::sendMessage(const Shared::Message& data) {
|
||||
mh->sendMessage(data);}
|
||||
|
||||
void Core::Account::requestChangeMessage(const QString& jid, const QString& messageId, const QMap<QString, QVariant>& data){
|
||||
mh->requestChangeMessage(jid, messageId, data);}
|
||||
|
||||
void Core::Account::resendMessage(const QString& jid, const QString& id) {
|
||||
mh->resendMessage(jid, id);}
|
||||
|
||||
void Core::Account::replaceMessage(const QString& originalId, const Shared::Message& data) {
|
||||
mh->sendMessage(data, false, originalId);}
|
||||
|
||||
void Core::Account::requestVCard(const QString& jid) {
|
||||
vh->requestVCard(jid);}
|
||||
|
||||
void Core::Account::uploadVCard(const Shared::VCard& card) {
|
||||
vh->uploadVCard(card);}
|
||||
|
||||
QString Core::Account::getAvatarPath() const {
|
||||
return vh->getAvatarPath();}
|
||||
|
||||
void Core::Account::removeRoomRequest(const QString& jid){
|
||||
rh->removeRoomRequest(jid);}
|
||||
|
||||
@ -689,253 +751,9 @@ void Core::Account::renameContactRequest(const QString& jid, const QString& newN
|
||||
}
|
||||
}
|
||||
|
||||
void Core::Account::onVCardReceived(const QXmppVCardIq& card)
|
||||
{
|
||||
QString id = card.from();
|
||||
QStringList comps = id.split("/");
|
||||
QString jid = comps.front().toLower();
|
||||
QString resource("");
|
||||
if (comps.size() > 1) {
|
||||
resource = comps.back();
|
||||
}
|
||||
pendingVCardRequests.erase(id);
|
||||
RosterItem* item = rh->getRosterItem(jid);
|
||||
void Core::Account::invalidatePassword() {
|
||||
notReadyPassword = true;}
|
||||
|
||||
if (item == 0) {
|
||||
if (jid == getLogin() + "@" + getServer()) {
|
||||
onOwnVCardReceived(card);
|
||||
} else {
|
||||
qDebug() << "received vCard" << jid << "doesn't belong to any of known contacts or conferences, skipping";
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
Shared::VCard vCard = item->handleResponseVCard(card, resource);
|
||||
|
||||
emit receivedVCard(jid, vCard);
|
||||
}
|
||||
|
||||
void Core::Account::onOwnVCardReceived(const QXmppVCardIq& card)
|
||||
{
|
||||
QByteArray ava = card.photo();
|
||||
bool avaChanged = false;
|
||||
QString path = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/" + name + "/";
|
||||
if (ava.size() > 0) {
|
||||
QCryptographicHash sha1(QCryptographicHash::Sha1);
|
||||
sha1.addData(ava);
|
||||
QString newHash(sha1.result());
|
||||
QMimeDatabase db;
|
||||
QMimeType newType = db.mimeTypeForData(ava);
|
||||
if (avatarType.size() > 0) {
|
||||
if (avatarHash != newHash) {
|
||||
QString oldPath = path + "avatar." + avatarType;
|
||||
QFile oldAvatar(oldPath);
|
||||
bool oldToRemove = false;
|
||||
if (oldAvatar.exists()) {
|
||||
if (oldAvatar.rename(oldPath + ".bak")) {
|
||||
oldToRemove = true;
|
||||
} else {
|
||||
qDebug() << "Received new avatar for account" << name << "but can't get rid of the old one, doing nothing";
|
||||
}
|
||||
}
|
||||
QFile newAvatar(path + "avatar." + newType.preferredSuffix());
|
||||
if (newAvatar.open(QFile::WriteOnly)) {
|
||||
newAvatar.write(ava);
|
||||
newAvatar.close();
|
||||
avatarHash = newHash;
|
||||
avatarType = newType.preferredSuffix();
|
||||
avaChanged = true;
|
||||
} else {
|
||||
qDebug() << "Received new avatar for account" << name << "but can't save it";
|
||||
if (oldToRemove) {
|
||||
qDebug() << "rolling back to the old avatar";
|
||||
if (!oldAvatar.rename(oldPath)) {
|
||||
qDebug() << "Couldn't roll back to the old avatar in account" << name;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
QFile newAvatar(path + "avatar." + newType.preferredSuffix());
|
||||
if (newAvatar.open(QFile::WriteOnly)) {
|
||||
newAvatar.write(ava);
|
||||
newAvatar.close();
|
||||
avatarHash = newHash;
|
||||
avatarType = newType.preferredSuffix();
|
||||
avaChanged = true;
|
||||
} else {
|
||||
qDebug() << "Received new avatar for account" << name << "but can't save it";
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (avatarType.size() > 0) {
|
||||
QFile oldAvatar(path + "avatar." + avatarType);
|
||||
if (!oldAvatar.remove()) {
|
||||
qDebug() << "Received vCard for account" << name << "without avatar, but can't get rid of the file, doing nothing";
|
||||
} else {
|
||||
avatarType = "";
|
||||
avatarHash = "";
|
||||
avaChanged = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (avaChanged) {
|
||||
QMap<QString, QVariant> change;
|
||||
if (avatarType.size() > 0) {
|
||||
presence.setPhotoHash(avatarHash.toUtf8());
|
||||
presence.setVCardUpdateType(QXmppPresence::VCardUpdateValidPhoto);
|
||||
change.insert("avatarPath", path + "avatar." + avatarType);
|
||||
} else {
|
||||
presence.setPhotoHash("");
|
||||
presence.setVCardUpdateType(QXmppPresence::VCardUpdateNoPhoto);
|
||||
change.insert("avatarPath", "");
|
||||
}
|
||||
client.setClientPresence(presence);
|
||||
emit changed(change);
|
||||
}
|
||||
|
||||
ownVCardRequestInProgress = false;
|
||||
|
||||
Shared::VCard vCard;
|
||||
initializeVCard(vCard, card);
|
||||
|
||||
if (avatarType.size() > 0) {
|
||||
vCard.setAvatarType(Shared::Avatar::valid);
|
||||
vCard.setAvatarPath(path + "avatar." + avatarType);
|
||||
} else {
|
||||
vCard.setAvatarType(Shared::Avatar::empty);
|
||||
}
|
||||
|
||||
emit receivedVCard(getLogin() + "@" + getServer(), vCard);
|
||||
}
|
||||
|
||||
QString Core::Account::getAvatarPath() const
|
||||
{
|
||||
if (avatarType.size() == 0) {
|
||||
return "";
|
||||
} else {
|
||||
return QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/" + name + "/" + "avatar." + avatarType;
|
||||
}
|
||||
}
|
||||
|
||||
void Core::Account::requestVCard(const QString& jid)
|
||||
{
|
||||
if (pendingVCardRequests.find(jid) == pendingVCardRequests.end()) {
|
||||
qDebug() << "requesting vCard" << jid;
|
||||
if (jid == getLogin() + "@" + getServer()) {
|
||||
if (!ownVCardRequestInProgress) {
|
||||
vm->requestClientVCard();
|
||||
ownVCardRequestInProgress = true;
|
||||
}
|
||||
} else {
|
||||
vm->requestVCard(jid);
|
||||
pendingVCardRequests.insert(jid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Core::Account::uploadVCard(const Shared::VCard& card)
|
||||
{
|
||||
QXmppVCardIq iq;
|
||||
initializeQXmppVCard(iq, card);
|
||||
|
||||
if (card.getAvatarType() != Shared::Avatar::empty) {
|
||||
QString newPath = card.getAvatarPath();
|
||||
QString oldPath = getAvatarPath();
|
||||
QByteArray data;
|
||||
QString type;
|
||||
if (newPath != oldPath) {
|
||||
QFile avatar(newPath);
|
||||
if (!avatar.open(QFile::ReadOnly)) {
|
||||
qDebug() << "An attempt to upload new vCard to account" << name
|
||||
<< "but it wasn't possible to read file" << newPath
|
||||
<< "which was supposed to be new avatar, uploading old avatar";
|
||||
if (avatarType.size() > 0) {
|
||||
QFile oA(oldPath);
|
||||
if (!oA.open(QFile::ReadOnly)) {
|
||||
qDebug() << "Couldn't read old avatar of account" << name << ", uploading empty avatar";
|
||||
} else {
|
||||
data = oA.readAll();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
data = avatar.readAll();
|
||||
}
|
||||
} else {
|
||||
if (avatarType.size() > 0) {
|
||||
QFile oA(oldPath);
|
||||
if (!oA.open(QFile::ReadOnly)) {
|
||||
qDebug() << "Couldn't read old avatar of account" << name << ", uploading empty avatar";
|
||||
} else {
|
||||
data = oA.readAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (data.size() > 0) {
|
||||
QMimeDatabase db;
|
||||
type = db.mimeTypeForData(data).name();
|
||||
iq.setPhoto(data);
|
||||
iq.setPhotoType(type);
|
||||
}
|
||||
}
|
||||
|
||||
vm->setClientVCard(iq);
|
||||
onOwnVCardReceived(iq);
|
||||
}
|
||||
|
||||
void Core::Account::onDiscoveryItemsReceived(const QXmppDiscoveryIq& items)
|
||||
{
|
||||
for (QXmppDiscoveryIq::Item item : items.items()) {
|
||||
if (item.jid() != getServer()) {
|
||||
dm->requestInfo(item.jid());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Core::Account::onDiscoveryInfoReceived(const QXmppDiscoveryIq& info)
|
||||
{
|
||||
qDebug() << "Discovery info received for account" << name;
|
||||
if (info.from() == getServer()) {
|
||||
if (info.features().contains("urn:xmpp:carbons:2")) {
|
||||
qDebug() << "Enabling carbon copies for account" << name;
|
||||
cm->setCarbonsEnabled(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Core::Account::handleDisconnection()
|
||||
{
|
||||
cm->setCarbonsEnabled(false);
|
||||
rh->handleOffline();
|
||||
archiveQueries.clear();
|
||||
pendingVCardRequests.clear();
|
||||
Shared::VCard vCard; //just to show, that there is now more pending request
|
||||
for (const QString& jid : pendingVCardRequests) {
|
||||
emit receivedVCard(jid, vCard); //need to show it better in the future, like with an error
|
||||
}
|
||||
pendingVCardRequests.clear();
|
||||
ownVCardRequestInProgress = false;
|
||||
}
|
||||
|
||||
void Core::Account::onContactHistoryResponse(const std::list<Shared::Message>& list, bool last)
|
||||
{
|
||||
RosterItem* contact = static_cast<RosterItem*>(sender());
|
||||
|
||||
qDebug() << "Collected history for contact " << contact->jid << list.size() << "elements";
|
||||
if (last) {
|
||||
qDebug() << "The response contains the first accounted message";
|
||||
}
|
||||
emit responseArchive(contact->jid, list, last);
|
||||
}
|
||||
|
||||
void Core::Account::requestChangeMessage(const QString& jid, const QString& messageId, const QMap<QString, QVariant>& data){
|
||||
mh->requestChangeMessage(jid, messageId, data);}
|
||||
|
||||
void Core::Account::resendMessage(const QString& jid, const QString& id) {
|
||||
mh->resendMessage(jid, id);}
|
||||
|
||||
void Core::Account::replaceMessage(const QString& originalId, const Shared::Message& data) {
|
||||
mh->sendMessage(data, false, originalId);}
|
||||
Core::Account::Error Core::Account::getLastError() const {
|
||||
return lastError;}
|
||||
|
||||
|
@ -39,7 +39,6 @@
|
||||
#include <QXmppBookmarkManager.h>
|
||||
#include <QXmppBookmarkSet.h>
|
||||
#include <QXmppUploadRequestManager.h>
|
||||
#include <QXmppVCardIq.h>
|
||||
#include <QXmppVCardManager.h>
|
||||
#include <QXmppMessageReceiptManager.h>
|
||||
|
||||
@ -50,6 +49,7 @@
|
||||
|
||||
#include "handlers/messagehandler.h"
|
||||
#include "handlers/rosterhandler.h"
|
||||
#include "handlers/vcardhandler.h"
|
||||
|
||||
namespace Core
|
||||
{
|
||||
@ -59,12 +59,20 @@ class Account : public QObject
|
||||
Q_OBJECT
|
||||
friend class MessageHandler;
|
||||
friend class RosterHandler;
|
||||
friend class VCardHandler;
|
||||
public:
|
||||
enum class Error {
|
||||
authentication,
|
||||
other,
|
||||
none
|
||||
};
|
||||
|
||||
Account(
|
||||
const QString& p_login,
|
||||
const QString& p_server,
|
||||
const QString& p_password,
|
||||
const QString& p_name,
|
||||
bool p_active,
|
||||
NetworkAccess* p_net,
|
||||
QObject* parent = 0);
|
||||
~Account();
|
||||
@ -76,8 +84,12 @@ public:
|
||||
QString getPassword() const;
|
||||
QString getResource() const;
|
||||
QString getAvatarPath() const;
|
||||
QString getBareJid() const;
|
||||
QString getFullJid() const;
|
||||
Shared::Availability getAvailability() const;
|
||||
Shared::AccountPassword getPasswordType() const;
|
||||
Error getLastError() const;
|
||||
bool getActive() const;
|
||||
|
||||
void setName(const QString& p_name);
|
||||
void setLogin(const QString& p_login);
|
||||
@ -86,8 +98,8 @@ public:
|
||||
void setResource(const QString& p_resource);
|
||||
void setAvailability(Shared::Availability avail);
|
||||
void setPasswordType(Shared::AccountPassword pt);
|
||||
QString getFullJid() const;
|
||||
void sendMessage(const Shared::Message& data);
|
||||
void setActive(bool p_active);
|
||||
void requestArchive(const QString& jid, int count, const QString& before);
|
||||
void subscribeToContact(const QString& jid, const QString& reason);
|
||||
void unsubscribeFromContact(const QString& jid, const QString& reason);
|
||||
@ -105,6 +117,7 @@ public:
|
||||
void uploadVCard(const Shared::VCard& card);
|
||||
void resendMessage(const QString& jid, const QString& id);
|
||||
void replaceMessage(const QString& originalId, const Shared::Message& data);
|
||||
void invalidatePassword();
|
||||
|
||||
public slots:
|
||||
void connect();
|
||||
@ -137,6 +150,7 @@ signals:
|
||||
void receivedVCard(const QString& jid, const Shared::VCard& card);
|
||||
void uploadFile(const QFileInfo& file, const QUrl& set, const QUrl& get, QMap<QString, QString> headers);
|
||||
void uploadFileError(const QString& jid, const QString& messageId, const QString& error);
|
||||
void needPassword();
|
||||
|
||||
private:
|
||||
QString name;
|
||||
@ -157,16 +171,16 @@ private:
|
||||
bool reconnectScheduled;
|
||||
QTimer* reconnectTimer;
|
||||
|
||||
std::set<QString> pendingVCardRequests;
|
||||
|
||||
QString avatarHash;
|
||||
QString avatarType;
|
||||
bool ownVCardRequestInProgress;
|
||||
NetworkAccess* network;
|
||||
Shared::AccountPassword passwordType;
|
||||
Error lastError;
|
||||
bool pepSupport;
|
||||
bool active;
|
||||
bool notReadyPassword;
|
||||
|
||||
MessageHandler* mh;
|
||||
RosterHandler* rh;
|
||||
VCardHandler* vh;
|
||||
|
||||
private slots:
|
||||
void onClientStateChange(QXmppClient::State state);
|
||||
@ -180,9 +194,6 @@ private slots:
|
||||
|
||||
void onMamLog(QXmppLogger::MessageType type, const QString &msg);
|
||||
|
||||
void onVCardReceived(const QXmppVCardIq& card);
|
||||
void onOwnVCardReceived(const QXmppVCardIq& card);
|
||||
|
||||
void onDiscoveryItemsReceived (const QXmppDiscoveryIq& items);
|
||||
void onDiscoveryInfoReceived (const QXmppDiscoveryIq& info);
|
||||
void onContactHistoryResponse(const std::list<Shared::Message>& list, bool last);
|
||||
@ -191,9 +202,6 @@ private:
|
||||
void handleDisconnection();
|
||||
void onReconnectTimer();
|
||||
};
|
||||
|
||||
void initializeVCard(Shared::VCard& vCard, const QXmppVCardIq& card);
|
||||
void initializeQXmppVCard(QXmppVCardIq& card, const Shared::VCard& vCard);
|
||||
}
|
||||
|
||||
|
||||
|
@ -15,10 +15,8 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifndef CORE_ADAPTER_FUNCTIONS_H
|
||||
#define CORE_ADAPTER_FUNCTIONS_H
|
||||
|
||||
#include "account.h"
|
||||
#include "adapterfunctions.h"
|
||||
|
||||
void Core::initializeVCard(Shared::VCard& vCard, const QXmppVCardIq& card)
|
||||
{
|
||||
@ -271,5 +269,3 @@ void Core::initializeQXmppVCard(QXmppVCardIq& iq, const Shared::VCard& card) {
|
||||
iq.setEmails(emails);
|
||||
iq.setPhones(phs);
|
||||
}
|
||||
|
||||
#endif // CORE_ADAPTER_FUNCTIONS_H
|
32
core/adapterfunctions.h
Normal file
32
core/adapterfunctions.h
Normal file
@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Squawk messenger.
|
||||
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifndef CORE_ADAPTER_FUNCTIONS_H
|
||||
#define CORE_ADAPTER_FUNCTIONS_H
|
||||
|
||||
#include <QXmppVCardIq.h>
|
||||
#include <shared/vcard.h>
|
||||
|
||||
namespace Core {
|
||||
|
||||
void initializeVCard(Shared::VCard& vCard, const QXmppVCardIq& card);
|
||||
void initializeQXmppVCard(QXmppVCardIq& card, const Shared::VCard& vCard);
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // CORE_ADAPTER_FUNCTIONS_H
|
@ -3,4 +3,6 @@ target_sources(squawk PRIVATE
|
||||
messagehandler.h
|
||||
rosterhandler.cpp
|
||||
rosterhandler.h
|
||||
vcardhandler.cpp
|
||||
vcardhandler.h
|
||||
)
|
||||
|
@ -176,7 +176,7 @@ void Core::MessageHandler::initializeMessage(Shared::Message& target, const QXmp
|
||||
target.setForwarded(forwarded);
|
||||
|
||||
if (guessing) {
|
||||
if (target.getFromJid() == acc->getLogin() + "@" + acc->getServer()) {
|
||||
if (target.getFromJid() == acc->getBareJid()) {
|
||||
outgoing = true;
|
||||
} else {
|
||||
outgoing = false;
|
||||
|
@ -26,7 +26,8 @@ Core::RosterHandler::RosterHandler(Core::Account* account):
|
||||
conferences(),
|
||||
groups(),
|
||||
queuedContacts(),
|
||||
outOfRosterContacts()
|
||||
outOfRosterContacts(),
|
||||
pepSupport(false)
|
||||
{
|
||||
connect(acc->rm, &QXmppRosterManager::rosterReceived, this, &RosterHandler::onRosterReceived);
|
||||
connect(acc->rm, &QXmppRosterManager::itemAdded, this, &RosterHandler::onRosterItemAdded);
|
||||
@ -51,8 +52,7 @@ Core::RosterHandler::~RosterHandler()
|
||||
|
||||
void Core::RosterHandler::onRosterReceived()
|
||||
{
|
||||
acc->vm->requestClientVCard(); //TODO need to make sure server actually supports vCards
|
||||
acc->ownVCardRequestInProgress = true;
|
||||
acc->requestVCard(acc->getBareJid()); //TODO need to make sure server actually supports vCards
|
||||
|
||||
QStringList bj = acc->rm->getRosterBareJids();
|
||||
for (int i = 0; i < bj.size(); ++i) {
|
||||
@ -588,4 +588,13 @@ void Core::RosterHandler::handleOffline()
|
||||
pair.second->clearArchiveRequests();
|
||||
pair.second->downgradeDatabaseState();
|
||||
}
|
||||
setPepSupport(false);
|
||||
}
|
||||
|
||||
|
||||
void Core::RosterHandler::setPepSupport(bool support)
|
||||
{
|
||||
if (pepSupport != support) {
|
||||
pepSupport = support;
|
||||
}
|
||||
}
|
||||
|
@ -64,6 +64,7 @@ public:
|
||||
|
||||
void storeConferences();
|
||||
void clearConferences();
|
||||
void setPepSupport(bool support);
|
||||
|
||||
private slots:
|
||||
void onRosterReceived();
|
||||
@ -107,6 +108,7 @@ private:
|
||||
std::map<QString, std::set<QString>> groups;
|
||||
std::map<QString, QString> queuedContacts;
|
||||
std::set<QString> outOfRosterContacts;
|
||||
bool pepSupport;
|
||||
};
|
||||
|
||||
}
|
||||
|
312
core/handlers/vcardhandler.cpp
Normal file
312
core/handlers/vcardhandler.cpp
Normal file
@ -0,0 +1,312 @@
|
||||
// Squawk messenger.
|
||||
// Copyright (C) 2019 Yury Gubich <blue@macaw.me>
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
#include "vcardhandler.h"
|
||||
#include "core/account.h"
|
||||
|
||||
Core::VCardHandler::VCardHandler(Account* account):
|
||||
QObject(),
|
||||
acc(account),
|
||||
ownVCardRequestInProgress(false),
|
||||
pendingVCardRequests(),
|
||||
avatarHash(),
|
||||
avatarType()
|
||||
{
|
||||
connect(acc->vm, &QXmppVCardManager::vCardReceived, this, &VCardHandler::onVCardReceived);
|
||||
//for some reason it doesn't work, launching from common handler
|
||||
//connect(acc->vm, &QXmppVCardManager::clientVCardReceived, this, &VCardHandler::onOwnVCardReceived);
|
||||
|
||||
QString path(QStandardPaths::writableLocation(QStandardPaths::CacheLocation));
|
||||
path += "/" + acc->name;
|
||||
QDir dir(path);
|
||||
|
||||
if (!dir.exists()) {
|
||||
bool res = dir.mkpath(path);
|
||||
if (!res) {
|
||||
qDebug() << "Couldn't create a cache directory for account" << acc->name;
|
||||
throw 22;
|
||||
}
|
||||
}
|
||||
|
||||
QFile* avatar = new QFile(path + "/avatar.png");
|
||||
QString type = "png";
|
||||
if (!avatar->exists()) {
|
||||
delete avatar;
|
||||
avatar = new QFile(path + "/avatar.jpg");
|
||||
type = "jpg";
|
||||
if (!avatar->exists()) {
|
||||
delete avatar;
|
||||
avatar = new QFile(path + "/avatar.jpeg");
|
||||
type = "jpeg";
|
||||
if (!avatar->exists()) {
|
||||
delete avatar;
|
||||
avatar = new QFile(path + "/avatar.gif");
|
||||
type = "gif";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (avatar->exists()) {
|
||||
if (avatar->open(QFile::ReadOnly)) {
|
||||
QCryptographicHash sha1(QCryptographicHash::Sha1);
|
||||
sha1.addData(avatar);
|
||||
avatarHash = sha1.result();
|
||||
avatarType = type;
|
||||
}
|
||||
}
|
||||
if (avatarType.size() != 0) {
|
||||
acc->presence.setVCardUpdateType(QXmppPresence::VCardUpdateValidPhoto);
|
||||
acc->presence.setPhotoHash(avatarHash.toUtf8());
|
||||
} else {
|
||||
acc->presence.setVCardUpdateType(QXmppPresence::VCardUpdateNotReady);
|
||||
}
|
||||
}
|
||||
|
||||
Core::VCardHandler::~VCardHandler()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void Core::VCardHandler::onVCardReceived(const QXmppVCardIq& card)
|
||||
{
|
||||
QString id = card.from();
|
||||
QStringList comps = id.split("/");
|
||||
QString jid = comps.front().toLower();
|
||||
QString resource("");
|
||||
if (comps.size() > 1) {
|
||||
resource = comps.back();
|
||||
}
|
||||
pendingVCardRequests.erase(id);
|
||||
RosterItem* item = acc->rh->getRosterItem(jid);
|
||||
|
||||
if (item == 0) {
|
||||
if (jid == acc->getBareJid()) {
|
||||
onOwnVCardReceived(card);
|
||||
} else {
|
||||
qDebug() << "received vCard" << jid << "doesn't belong to any of known contacts or conferences, skipping";
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
Shared::VCard vCard = item->handleResponseVCard(card, resource);
|
||||
|
||||
emit acc->receivedVCard(jid, vCard);
|
||||
}
|
||||
|
||||
void Core::VCardHandler::onOwnVCardReceived(const QXmppVCardIq& card)
|
||||
{
|
||||
QByteArray ava = card.photo();
|
||||
bool avaChanged = false;
|
||||
QString path = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/" + acc->name + "/";
|
||||
if (ava.size() > 0) {
|
||||
QCryptographicHash sha1(QCryptographicHash::Sha1);
|
||||
sha1.addData(ava);
|
||||
QString newHash(sha1.result());
|
||||
QMimeDatabase db;
|
||||
QMimeType newType = db.mimeTypeForData(ava);
|
||||
if (avatarType.size() > 0) {
|
||||
if (avatarHash != newHash) {
|
||||
QString oldPath = path + "avatar." + avatarType;
|
||||
QFile oldAvatar(oldPath);
|
||||
bool oldToRemove = false;
|
||||
if (oldAvatar.exists()) {
|
||||
if (oldAvatar.rename(oldPath + ".bak")) {
|
||||
oldToRemove = true;
|
||||
} else {
|
||||
qDebug() << "Received new avatar for account" << acc->name << "but can't get rid of the old one, doing nothing";
|
||||
}
|
||||
}
|
||||
QFile newAvatar(path + "avatar." + newType.preferredSuffix());
|
||||
if (newAvatar.open(QFile::WriteOnly)) {
|
||||
newAvatar.write(ava);
|
||||
newAvatar.close();
|
||||
avatarHash = newHash;
|
||||
avatarType = newType.preferredSuffix();
|
||||
avaChanged = true;
|
||||
} else {
|
||||
qDebug() << "Received new avatar for account" << acc->name << "but can't save it";
|
||||
if (oldToRemove) {
|
||||
qDebug() << "rolling back to the old avatar";
|
||||
if (!oldAvatar.rename(oldPath)) {
|
||||
qDebug() << "Couldn't roll back to the old avatar in account" << acc->name;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
QFile newAvatar(path + "avatar." + newType.preferredSuffix());
|
||||
if (newAvatar.open(QFile::WriteOnly)) {
|
||||
newAvatar.write(ava);
|
||||
newAvatar.close();
|
||||
avatarHash = newHash;
|
||||
avatarType = newType.preferredSuffix();
|
||||
avaChanged = true;
|
||||
} else {
|
||||
qDebug() << "Received new avatar for account" << acc->name << "but can't save it";
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (avatarType.size() > 0) {
|
||||
QFile oldAvatar(path + "avatar." + avatarType);
|
||||
if (!oldAvatar.remove()) {
|
||||
qDebug() << "Received vCard for account" << acc->name << "without avatar, but can't get rid of the file, doing nothing";
|
||||
} else {
|
||||
avatarType = "";
|
||||
avatarHash = "";
|
||||
avaChanged = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (avaChanged) {
|
||||
QMap<QString, QVariant> change;
|
||||
if (avatarType.size() > 0) {
|
||||
acc->presence.setPhotoHash(avatarHash.toUtf8());
|
||||
acc->presence.setVCardUpdateType(QXmppPresence::VCardUpdateValidPhoto);
|
||||
change.insert("avatarPath", path + "avatar." + avatarType);
|
||||
} else {
|
||||
acc->presence.setPhotoHash("");
|
||||
acc->presence.setVCardUpdateType(QXmppPresence::VCardUpdateNoPhoto);
|
||||
change.insert("avatarPath", "");
|
||||
}
|
||||
acc->client.setClientPresence(acc->presence);
|
||||
emit acc->changed(change);
|
||||
}
|
||||
|
||||
ownVCardRequestInProgress = false;
|
||||
|
||||
Shared::VCard vCard;
|
||||
initializeVCard(vCard, card);
|
||||
|
||||
if (avatarType.size() > 0) {
|
||||
vCard.setAvatarType(Shared::Avatar::valid);
|
||||
vCard.setAvatarPath(path + "avatar." + avatarType);
|
||||
} else {
|
||||
vCard.setAvatarType(Shared::Avatar::empty);
|
||||
}
|
||||
|
||||
emit acc->receivedVCard(acc->getBareJid(), vCard);
|
||||
}
|
||||
|
||||
void Core::VCardHandler::handleOffline()
|
||||
{
|
||||
pendingVCardRequests.clear();
|
||||
Shared::VCard vCard; //just to show, that there is now more pending request
|
||||
for (const QString& jid : pendingVCardRequests) {
|
||||
emit acc->receivedVCard(jid, vCard); //need to show it better in the future, like with an error
|
||||
}
|
||||
pendingVCardRequests.clear();
|
||||
ownVCardRequestInProgress = false;
|
||||
}
|
||||
|
||||
void Core::VCardHandler::requestVCard(const QString& jid)
|
||||
{
|
||||
if (pendingVCardRequests.find(jid) == pendingVCardRequests.end()) {
|
||||
qDebug() << "requesting vCard" << jid;
|
||||
if (jid == acc->getBareJid()) {
|
||||
if (!ownVCardRequestInProgress) {
|
||||
acc->vm->requestClientVCard();
|
||||
ownVCardRequestInProgress = true;
|
||||
}
|
||||
} else {
|
||||
acc->vm->requestVCard(jid);
|
||||
pendingVCardRequests.insert(jid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Core::VCardHandler::handleOtherPresenceOfMyAccountChange(const QXmppPresence& p_presence)
|
||||
{
|
||||
if (!ownVCardRequestInProgress) {
|
||||
switch (p_presence.vCardUpdateType()) {
|
||||
case QXmppPresence::VCardUpdateNone: //this presence has nothing to do with photo
|
||||
break;
|
||||
case QXmppPresence::VCardUpdateNotReady: //let's say the photo didn't change here
|
||||
break;
|
||||
case QXmppPresence::VCardUpdateNoPhoto: //there is no photo, need to drop if any
|
||||
if (avatarType.size() > 0) {
|
||||
acc->vm->requestClientVCard();
|
||||
ownVCardRequestInProgress = true;
|
||||
}
|
||||
break;
|
||||
case QXmppPresence::VCardUpdateValidPhoto: //there is a photo, need to load
|
||||
if (avatarHash != p_presence.photoHash()) {
|
||||
acc->vm->requestClientVCard();
|
||||
ownVCardRequestInProgress = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Core::VCardHandler::uploadVCard(const Shared::VCard& card)
|
||||
{
|
||||
QXmppVCardIq iq;
|
||||
initializeQXmppVCard(iq, card);
|
||||
|
||||
if (card.getAvatarType() != Shared::Avatar::empty) {
|
||||
QString newPath = card.getAvatarPath();
|
||||
QString oldPath = getAvatarPath();
|
||||
QByteArray data;
|
||||
QString type;
|
||||
if (newPath != oldPath) {
|
||||
QFile avatar(newPath);
|
||||
if (!avatar.open(QFile::ReadOnly)) {
|
||||
qDebug() << "An attempt to upload new vCard to account" << acc->name
|
||||
<< "but it wasn't possible to read file" << newPath
|
||||
<< "which was supposed to be new avatar, uploading old avatar";
|
||||
if (avatarType.size() > 0) {
|
||||
QFile oA(oldPath);
|
||||
if (!oA.open(QFile::ReadOnly)) {
|
||||
qDebug() << "Couldn't read old avatar of account" << acc->name << ", uploading empty avatar";
|
||||
} else {
|
||||
data = oA.readAll();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
data = avatar.readAll();
|
||||
}
|
||||
} else {
|
||||
if (avatarType.size() > 0) {
|
||||
QFile oA(oldPath);
|
||||
if (!oA.open(QFile::ReadOnly)) {
|
||||
qDebug() << "Couldn't read old avatar of account" << acc->name << ", uploading empty avatar";
|
||||
} else {
|
||||
data = oA.readAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (data.size() > 0) {
|
||||
QMimeDatabase db;
|
||||
type = db.mimeTypeForData(data).name();
|
||||
iq.setPhoto(data);
|
||||
iq.setPhotoType(type);
|
||||
}
|
||||
}
|
||||
|
||||
acc->vm->setClientVCard(iq);
|
||||
onOwnVCardReceived(iq);
|
||||
}
|
||||
|
||||
QString Core::VCardHandler::getAvatarPath() const
|
||||
{
|
||||
if (avatarType.size() == 0) {
|
||||
return "";
|
||||
} else {
|
||||
return QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/" + acc->name + "/" + "avatar." + avatarType;
|
||||
}
|
||||
}
|
65
core/handlers/vcardhandler.h
Normal file
65
core/handlers/vcardhandler.h
Normal file
@ -0,0 +1,65 @@
|
||||
// Squawk messenger.
|
||||
// Copyright (C) 2019 Yury Gubich <blue@macaw.me>
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
#ifndef CORE_VCARDHANDLER_H
|
||||
#define CORE_VCARDHANDLER_H
|
||||
|
||||
#include <set>
|
||||
|
||||
#include <QXmppVCardIq.h>
|
||||
#include <QXmppPresence.h>
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include <shared/vcard.h>
|
||||
#include <core/adapterfunctions.h>
|
||||
|
||||
/**
|
||||
* @todo write docs
|
||||
*/
|
||||
|
||||
namespace Core {
|
||||
|
||||
class Account;
|
||||
|
||||
class VCardHandler : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
VCardHandler(Account* account);
|
||||
~VCardHandler();
|
||||
|
||||
void handleOffline();
|
||||
void requestVCard(const QString& jid);
|
||||
void handleOtherPresenceOfMyAccountChange(const QXmppPresence& p_presence);
|
||||
void uploadVCard(const Shared::VCard& card);
|
||||
QString getAvatarPath() const;
|
||||
|
||||
private slots:
|
||||
void onVCardReceived(const QXmppVCardIq& card);
|
||||
void onOwnVCardReceived(const QXmppVCardIq& card);
|
||||
|
||||
private:
|
||||
Account* acc;
|
||||
|
||||
bool ownVCardRequestInProgress;
|
||||
std::set<QString> pendingVCardRequests;
|
||||
QString avatarHash;
|
||||
QString avatarType;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // CORE_VCARDHANDLER_H
|
209
core/main.cpp
209
core/main.cpp
@ -1,209 +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);
|
||||
#ifdef Q_OS_WIN
|
||||
// Windows need an organization name for QSettings to work
|
||||
// https://doc.qt.io/qt-5/qsettings.html#basic-usage
|
||||
{
|
||||
const QString& orgName = QApplication::organizationName();
|
||||
if (orgName.isNull() || orgName.isEmpty()) {
|
||||
QApplication::setOrganizationName("squawk");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
QApplication::setApplicationName("squawk");
|
||||
QApplication::setApplicationDisplayName("Squawk");
|
||||
QApplication::setApplicationVersion("0.2.1");
|
||||
|
||||
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 no quit if it has reconnected at least once
|
||||
//it feels like a symptom of something badly desinged in the core coreThread
|
||||
//need to investigate;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
@ -30,7 +30,7 @@
|
||||
|
||||
#include <set>
|
||||
|
||||
#include "urlstorage.h"
|
||||
#include "storage/urlstorage.h"
|
||||
#include "shared/pathcheck.h"
|
||||
|
||||
namespace Core {
|
||||
|
@ -124,15 +124,19 @@ void Core::RosterItem::nextRequest()
|
||||
if (requestedCount != -1) {
|
||||
bool last = false;
|
||||
if (archiveState == beginning || archiveState == complete) {
|
||||
QString firstId = archive->oldestId();
|
||||
if (responseCache.size() == 0) {
|
||||
if (requestedBefore == firstId) {
|
||||
last = true;
|
||||
}
|
||||
} else {
|
||||
if (responseCache.front().getId() == firstId) {
|
||||
last = true;
|
||||
try {
|
||||
QString firstId = archive->oldestId();
|
||||
if (responseCache.size() == 0) {
|
||||
if (requestedBefore == firstId) {
|
||||
last = true;
|
||||
}
|
||||
} else {
|
||||
if (responseCache.front().getId() == firstId) {
|
||||
last = true;
|
||||
}
|
||||
}
|
||||
} catch (const Archive::Empty& e) {
|
||||
last = true;
|
||||
}
|
||||
} else if (archiveState == empty && responseCache.size() == 0) {
|
||||
last = true;
|
||||
@ -171,8 +175,12 @@ void Core::RosterItem::performRequest(int count, const QString& before)
|
||||
requestCache.emplace_back(requestedCount, before);
|
||||
requestedCount = -1;
|
||||
}
|
||||
Shared::Message msg = archive->newest();
|
||||
emit needHistory("", getId(msg), msg.getTime());
|
||||
try {
|
||||
Shared::Message msg = archive->newest();
|
||||
emit needHistory("", getId(msg), msg.getTime());
|
||||
} catch (const Archive::Empty& e) { //this can happen when the only message in archive is not server stored (error, for example)
|
||||
emit needHistory(before, "");
|
||||
}
|
||||
}
|
||||
break;
|
||||
case end:
|
||||
|
@ -34,7 +34,8 @@
|
||||
#include "shared/enums.h"
|
||||
#include "shared/message.h"
|
||||
#include "shared/vcard.h"
|
||||
#include "archive.h"
|
||||
#include "storage/archive.h"
|
||||
#include "adapterfunctions.h"
|
||||
|
||||
namespace Core {
|
||||
|
||||
|
273
core/squawk.cpp
273
core/squawk.cpp
@ -26,8 +26,8 @@ Core::Squawk::Squawk(QObject* parent):
|
||||
QObject(parent),
|
||||
accounts(),
|
||||
amap(),
|
||||
state(Shared::Availability::offline),
|
||||
network(),
|
||||
waitingForAccounts(0),
|
||||
isInitialized(false)
|
||||
#ifdef WITH_KWALLET
|
||||
,kwallet()
|
||||
@ -42,7 +42,7 @@ Core::Squawk::Squawk(QObject* parent):
|
||||
if (kwallet.supportState() == PSE::KWallet::success) {
|
||||
connect(&kwallet, &PSE::KWallet::opened, this, &Squawk::onWalletOpened);
|
||||
connect(&kwallet, &PSE::KWallet::rejectPassword, this, &Squawk::onWalletRejectPassword);
|
||||
connect(&kwallet, &PSE::KWallet::responsePassword, this, &Squawk::onWalletResponsePassword);
|
||||
connect(&kwallet, &PSE::KWallet::responsePassword, this, &Squawk::responsePassword);
|
||||
|
||||
Shared::Global::setSupported("KWallet", true);
|
||||
}
|
||||
@ -97,6 +97,7 @@ void Core::Squawk::stop()
|
||||
settings.setValue("password", password);
|
||||
settings.setValue("resource", acc->getResource());
|
||||
settings.setValue("passwordType", static_cast<int>(ap));
|
||||
settings.setValue("active", acc->getActive());
|
||||
}
|
||||
settings.endArray();
|
||||
settings.endGroup();
|
||||
@ -124,8 +125,9 @@ void Core::Squawk::newAccountRequest(const QMap<QString, QVariant>& map)
|
||||
QString password = map.value("password").toString();
|
||||
QString resource = map.value("resource").toString();
|
||||
int passwordType = map.value("passwordType").toInt();
|
||||
bool active = map.value("active").toBool();
|
||||
|
||||
addAccount(login, server, password, name, resource, Shared::Global::fromInt<Shared::AccountPassword>(passwordType));
|
||||
addAccount(login, server, password, name, resource, active, Shared::Global::fromInt<Shared::AccountPassword>(passwordType));
|
||||
}
|
||||
|
||||
void Core::Squawk::addAccount(
|
||||
@ -134,12 +136,14 @@ void Core::Squawk::addAccount(
|
||||
const QString& password,
|
||||
const QString& name,
|
||||
const QString& resource,
|
||||
Shared::AccountPassword passwordType
|
||||
)
|
||||
bool active,
|
||||
Shared::AccountPassword passwordType)
|
||||
{
|
||||
QSettings settings;
|
||||
|
||||
Account* acc = new Account(login, server, password, name, &network);
|
||||
if (amap.count(name) > 0) {
|
||||
qDebug() << "An attempt to add account" << name << "but an account with such name already exist, ignoring";
|
||||
return;
|
||||
}
|
||||
Account* acc = new Account(login, server, password, name, active, &network);
|
||||
acc->setResource(resource);
|
||||
acc->setPasswordType(passwordType);
|
||||
accounts.push_back(acc);
|
||||
@ -148,6 +152,8 @@ void Core::Squawk::addAccount(
|
||||
connect(acc, &Account::connectionStateChanged, this, &Squawk::onAccountConnectionStateChanged);
|
||||
connect(acc, &Account::changed, this, &Squawk::onAccountChanged);
|
||||
connect(acc, &Account::error, this, &Squawk::onAccountError);
|
||||
connect(acc, &Account::needPassword, this, &Squawk::onAccountNeedPassword);
|
||||
|
||||
connect(acc, &Account::availabilityChanged, this, &Squawk::onAccountAvailabilityChanged);
|
||||
connect(acc, &Account::addContact, this, &Squawk::onAccountAddContact);
|
||||
connect(acc, &Account::addGroup, this, &Squawk::onAccountAddGroup);
|
||||
@ -185,20 +191,44 @@ void Core::Squawk::addAccount(
|
||||
{"offline", QVariant::fromValue(Shared::Availability::offline)},
|
||||
{"error", ""},
|
||||
{"avatarPath", acc->getAvatarPath()},
|
||||
{"passwordType", QVariant::fromValue(passwordType)}
|
||||
{"passwordType", QVariant::fromValue(passwordType)},
|
||||
{"active", active}
|
||||
};
|
||||
|
||||
emit newAccount(map);
|
||||
|
||||
switch (passwordType) {
|
||||
case Shared::AccountPassword::alwaysAsk:
|
||||
case Shared::AccountPassword::kwallet:
|
||||
if (password == "") {
|
||||
acc->invalidatePassword();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (state != Shared::Availability::offline) {
|
||||
acc->setAvailability(state);
|
||||
if (acc->getActive()) {
|
||||
acc->connect();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Core::Squawk::changeState(Shared::Availability p_state)
|
||||
{
|
||||
if (state != p_state) {
|
||||
for (std::deque<Account*>::iterator itr = accounts.begin(), end = accounts.end(); itr != end; ++itr) {
|
||||
Account* acc = *itr;
|
||||
acc->setAvailability(p_state);
|
||||
if (state == Shared::Availability::offline && acc->getActive()) {
|
||||
acc->connect();
|
||||
}
|
||||
}
|
||||
state = p_state;
|
||||
}
|
||||
|
||||
for (std::deque<Account*>::iterator itr = accounts.begin(), end = accounts.end(); itr != end; ++itr) {
|
||||
(*itr)->setAvailability(state);
|
||||
emit stateChanged(p_state);
|
||||
}
|
||||
}
|
||||
|
||||
@ -209,7 +239,10 @@ void Core::Squawk::connectAccount(const QString& account)
|
||||
qDebug("An attempt to connect non existing account, skipping");
|
||||
return;
|
||||
}
|
||||
itr->second->connect();
|
||||
itr->second->setActive(true);
|
||||
if (state != Shared::Availability::offline) {
|
||||
itr->second->connect();
|
||||
}
|
||||
}
|
||||
|
||||
void Core::Squawk::disconnectAccount(const QString& account)
|
||||
@ -220,13 +253,17 @@ void Core::Squawk::disconnectAccount(const QString& account)
|
||||
return;
|
||||
}
|
||||
|
||||
itr->second->setActive(false);
|
||||
itr->second->disconnect();
|
||||
}
|
||||
|
||||
void Core::Squawk::onAccountConnectionStateChanged(Shared::ConnectionState p_state)
|
||||
{
|
||||
Account* acc = static_cast<Account*>(sender());
|
||||
emit changeAccount(acc->getName(), {{"state", QVariant::fromValue(p_state)}});
|
||||
emit changeAccount(acc->getName(), {
|
||||
{"state", QVariant::fromValue(p_state)},
|
||||
{"error", ""}
|
||||
});
|
||||
|
||||
#ifdef WITH_KWALLET
|
||||
if (p_state == Shared::ConnectionState::connected) {
|
||||
@ -235,33 +272,6 @@ void Core::Squawk::onAccountConnectionStateChanged(Shared::ConnectionState p_sta
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
Accounts::const_iterator itr = accounts.begin();
|
||||
bool es = true;
|
||||
bool ea = true;
|
||||
Shared::ConnectionState cs = (*itr)->getState();
|
||||
Shared::Availability av = (*itr)->getAvailability();
|
||||
itr++;
|
||||
for (Accounts::const_iterator end = accounts.end(); itr != end; itr++) {
|
||||
Account* item = *itr;
|
||||
if (item->getState() != cs) {
|
||||
es = false;
|
||||
}
|
||||
if (item->getAvailability() != av) {
|
||||
ea = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (es) {
|
||||
if (cs == Shared::ConnectionState::disconnected) {
|
||||
state = Shared::Availability::offline;
|
||||
emit stateChanged(state);
|
||||
} else if (ea) {
|
||||
state = av;
|
||||
emit stateChanged(state);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void Core::Squawk::onAccountAddContact(const QString& jid, const QString& group, const QMap<QString, QVariant>& data)
|
||||
@ -391,6 +401,7 @@ void Core::Squawk::modifyAccountRequest(const QString& name, const QMap<QString,
|
||||
Shared::ConnectionState st = acc->getState();
|
||||
QMap<QString, QVariant>::const_iterator mItr;
|
||||
bool needToReconnect = false;
|
||||
bool wentReconnecting = false;
|
||||
|
||||
mItr = map.find("login");
|
||||
if (mItr != map.end()) {
|
||||
@ -416,8 +427,16 @@ void Core::Squawk::modifyAccountRequest(const QString& name, const QMap<QString,
|
||||
}
|
||||
}
|
||||
|
||||
if (needToReconnect && st != Shared::ConnectionState::disconnected) {
|
||||
acc->reconnect();
|
||||
bool activeChanged = false;
|
||||
mItr = map.find("active");
|
||||
if (mItr == map.end() || mItr->toBool() == acc->getActive()) {
|
||||
if (needToReconnect && st != Shared::ConnectionState::disconnected) {
|
||||
acc->reconnect();
|
||||
wentReconnecting = true;
|
||||
}
|
||||
} else {
|
||||
acc->setActive(mItr->toBool());
|
||||
activeChanged = true;
|
||||
}
|
||||
|
||||
mItr = map.find("login");
|
||||
@ -454,6 +473,14 @@ void Core::Squawk::modifyAccountRequest(const QString& name, const QMap<QString,
|
||||
}
|
||||
#endif
|
||||
|
||||
if (state != Shared::Availability::offline) {
|
||||
if (activeChanged && acc->getActive()) {
|
||||
acc->connect();
|
||||
} else if (!wentReconnecting && acc->getActive() && acc->getLastError() == Account::Error::authentication) {
|
||||
acc->connect();
|
||||
}
|
||||
}
|
||||
|
||||
emit changeAccount(name, map);
|
||||
}
|
||||
|
||||
@ -461,6 +488,10 @@ void Core::Squawk::onAccountError(const QString& text)
|
||||
{
|
||||
Account* acc = static_cast<Account*>(sender());
|
||||
emit changeAccount(acc->getName(), {{"error", text}});
|
||||
|
||||
if (acc->getLastError() == Account::Error::authentication) {
|
||||
emit requestPassword(acc->getName(), true);
|
||||
}
|
||||
}
|
||||
|
||||
void Core::Squawk::removeAccountRequest(const QString& name)
|
||||
@ -675,6 +706,70 @@ void Core::Squawk::uploadVCard(const QString& account, const Shared::VCard& card
|
||||
itr->second->uploadVCard(card);
|
||||
}
|
||||
|
||||
void Core::Squawk::readSettings()
|
||||
{
|
||||
QSettings settings;
|
||||
settings.beginGroup("core");
|
||||
int size = settings.beginReadArray("accounts");
|
||||
for (int i = 0; i < size; ++i) {
|
||||
settings.setArrayIndex(i);
|
||||
Shared::AccountPassword passwordType =
|
||||
Shared::Global::fromInt<Shared::AccountPassword>(
|
||||
settings.value("passwordType", static_cast<int>(Shared::AccountPassword::plain)).toInt()
|
||||
);
|
||||
|
||||
QString password = settings.value("password", "").toString();
|
||||
if (passwordType == Shared::AccountPassword::jammed) {
|
||||
SimpleCrypt crypto(passwordHash);
|
||||
password = crypto.decryptToString(password);
|
||||
}
|
||||
|
||||
addAccount(
|
||||
settings.value("login").toString(),
|
||||
settings.value("server").toString(),
|
||||
password,
|
||||
settings.value("name").toString(),
|
||||
settings.value("resource").toString(),
|
||||
settings.value("active").toBool(),
|
||||
passwordType
|
||||
);
|
||||
}
|
||||
settings.endArray();
|
||||
settings.endGroup();
|
||||
|
||||
qDebug() << "Squawk core is ready";
|
||||
emit ready();
|
||||
}
|
||||
|
||||
void Core::Squawk::onAccountNeedPassword()
|
||||
{
|
||||
Account* acc = static_cast<Account*>(sender());
|
||||
switch (acc->getPasswordType()) {
|
||||
case Shared::AccountPassword::alwaysAsk:
|
||||
emit requestPassword(acc->getName(), false);
|
||||
break;
|
||||
case Shared::AccountPassword::kwallet: {
|
||||
#ifdef WITH_KWALLET
|
||||
if (kwallet.supportState() == PSE::KWallet::success) {
|
||||
kwallet.requestReadPassword(acc->getName());
|
||||
} else {
|
||||
#endif
|
||||
emit requestPassword(acc->getName(), false);
|
||||
#ifdef WITH_KWALLET
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break; //should never happen;
|
||||
}
|
||||
}
|
||||
|
||||
void Core::Squawk::onWalletRejectPassword(const QString& login)
|
||||
{
|
||||
emit requestPassword(login, false);
|
||||
}
|
||||
|
||||
void Core::Squawk::responsePassword(const QString& account, const QString& password)
|
||||
{
|
||||
AccountsMap::const_iterator itr = amap.find(account);
|
||||
@ -682,96 +777,12 @@ void Core::Squawk::responsePassword(const QString& account, const QString& passw
|
||||
qDebug() << "An attempt to set password to non existing account" << account << ", skipping";
|
||||
return;
|
||||
}
|
||||
itr->second->setPassword(password);
|
||||
Account* acc = itr->second;
|
||||
acc->setPassword(password);
|
||||
emit changeAccount(account, {{"password", password}});
|
||||
accountReady();
|
||||
}
|
||||
|
||||
void Core::Squawk::readSettings()
|
||||
{
|
||||
QSettings settings;
|
||||
settings.beginGroup("core");
|
||||
int size = settings.beginReadArray("accounts");
|
||||
waitingForAccounts = size;
|
||||
for (int i = 0; i < size; ++i) {
|
||||
settings.setArrayIndex(i);
|
||||
parseAccount(
|
||||
settings.value("login").toString(),
|
||||
settings.value("server").toString(),
|
||||
settings.value("password", "").toString(),
|
||||
settings.value("name").toString(),
|
||||
settings.value("resource").toString(),
|
||||
Shared::Global::fromInt<Shared::AccountPassword>(settings.value("passwordType", static_cast<int>(Shared::AccountPassword::plain)).toInt())
|
||||
);
|
||||
if (state != Shared::Availability::offline && acc->getActive()) {
|
||||
acc->connect();
|
||||
}
|
||||
settings.endArray();
|
||||
settings.endGroup();
|
||||
}
|
||||
|
||||
void Core::Squawk::accountReady()
|
||||
{
|
||||
--waitingForAccounts;
|
||||
|
||||
if (waitingForAccounts == 0) {
|
||||
emit ready();
|
||||
}
|
||||
}
|
||||
|
||||
void Core::Squawk::parseAccount(
|
||||
const QString& login,
|
||||
const QString& server,
|
||||
const QString& password,
|
||||
const QString& name,
|
||||
const QString& resource,
|
||||
Shared::AccountPassword passwordType
|
||||
)
|
||||
{
|
||||
switch (passwordType) {
|
||||
case Shared::AccountPassword::plain:
|
||||
addAccount(login, server, password, name, resource, passwordType);
|
||||
accountReady();
|
||||
break;
|
||||
case Shared::AccountPassword::jammed: {
|
||||
SimpleCrypt crypto(passwordHash);
|
||||
QString decrypted = crypto.decryptToString(password);
|
||||
addAccount(login, server, decrypted, name, resource, passwordType);
|
||||
accountReady();
|
||||
}
|
||||
break;
|
||||
case Shared::AccountPassword::alwaysAsk:
|
||||
addAccount(login, server, QString(), name, resource, passwordType);
|
||||
emit requestPassword(name);
|
||||
break;
|
||||
case Shared::AccountPassword::kwallet: {
|
||||
addAccount(login, server, QString(), name, resource, passwordType);
|
||||
#ifdef WITH_KWALLET
|
||||
if (kwallet.supportState() == PSE::KWallet::success) {
|
||||
kwallet.requestReadPassword(name);
|
||||
} else {
|
||||
#endif
|
||||
emit requestPassword(name);
|
||||
#ifdef WITH_KWALLET
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Core::Squawk::onWalletRejectPassword(const QString& login)
|
||||
{
|
||||
emit requestPassword(login);
|
||||
}
|
||||
|
||||
void Core::Squawk::onWalletResponsePassword(const QString& login, const QString& password)
|
||||
{
|
||||
AccountsMap::const_iterator itr = amap.find(login);
|
||||
if (itr == amap.end()) {
|
||||
qDebug() << "An attempt to set password to non existing account" << login << ", skipping";
|
||||
return;
|
||||
}
|
||||
itr->second->setPassword(password);
|
||||
emit changeAccount(login, {{"password", password}});
|
||||
accountReady();
|
||||
}
|
||||
|
||||
void Core::Squawk::onAccountUploadFileError(const QString& jid, const QString id, const QString& errorText)
|
||||
|
@ -86,7 +86,7 @@ signals:
|
||||
|
||||
void responseVCard(const QString& jid, const Shared::VCard& card);
|
||||
void changeMessage(const QString& account, const QString& jid, const QString& id, const QMap<QString, QVariant>& data);
|
||||
void requestPassword(const QString& account);
|
||||
void requestPassword(const QString& account, bool authernticationError);
|
||||
|
||||
public slots:
|
||||
void start();
|
||||
@ -134,7 +134,6 @@ private:
|
||||
AccountsMap amap;
|
||||
Shared::Availability state;
|
||||
NetworkAccess network;
|
||||
uint8_t waitingForAccounts;
|
||||
bool isInitialized;
|
||||
|
||||
#ifdef WITH_KWALLET
|
||||
@ -148,6 +147,7 @@ private slots:
|
||||
const QString& password,
|
||||
const QString& name,
|
||||
const QString& resource,
|
||||
bool active,
|
||||
Shared::AccountPassword passwordType
|
||||
);
|
||||
|
||||
@ -172,22 +172,22 @@ private slots:
|
||||
void onAccountChangeRoomPresence(const QString& jid, const QString& nick, const QMap<QString, QVariant>& data);
|
||||
void onAccountRemoveRoomPresence(const QString& jid, const QString& nick);
|
||||
void onAccountChangeMessage(const QString& jid, const QString& id, const QMap<QString, QVariant>& data);
|
||||
void onAccountNeedPassword();
|
||||
|
||||
void onAccountUploadFileError(const QString& jid, const QString id, const QString& errorText);
|
||||
|
||||
void onWalletOpened(bool success);
|
||||
void onWalletResponsePassword(const QString& login, const QString& password);
|
||||
void onWalletRejectPassword(const QString& login);
|
||||
|
||||
private:
|
||||
void readSettings();
|
||||
void accountReady();
|
||||
void parseAccount(
|
||||
const QString& login,
|
||||
const QString& server,
|
||||
const QString& password,
|
||||
const QString& name,
|
||||
const QString& resource,
|
||||
bool active,
|
||||
Shared::AccountPassword passwordType
|
||||
);
|
||||
|
||||
|
8
core/storage/CMakeLists.txt
Normal file
8
core/storage/CMakeLists.txt
Normal 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
7
main/CMakeLists.txt
Normal file
@ -0,0 +1,7 @@
|
||||
target_sources(squawk PRIVATE
|
||||
main.cpp
|
||||
application.cpp
|
||||
application.h
|
||||
dialogqueue.cpp
|
||||
dialogqueue.h
|
||||
)
|
566
main/application.cpp
Normal file
566
main/application.cpp
Normal file
@ -0,0 +1,566 @@
|
||||
// Squawk messenger.
|
||||
// Copyright (C) 2019 Yury Gubich <blue@macaw.me>
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
#include "application.h"
|
||||
|
||||
Application::Application(Core::Squawk* p_core):
|
||||
QObject(),
|
||||
availability(Shared::Availability::offline),
|
||||
core(p_core),
|
||||
squawk(nullptr),
|
||||
notifications("org.freedesktop.Notifications", "/org/freedesktop/Notifications", "org.freedesktop.Notifications"),
|
||||
roster(),
|
||||
conversations(),
|
||||
dialogueQueue(roster),
|
||||
nowQuitting(false),
|
||||
destroyingSquawk(false),
|
||||
storage()
|
||||
{
|
||||
connect(&roster, &Models::Roster::unnoticedMessage, this, &Application::notify);
|
||||
connect(&roster, &Models::Roster::unreadMessagesCountChanged, this, &Application::unreadMessagesCountChanged);
|
||||
|
||||
|
||||
//connecting myself to the backed
|
||||
connect(this, &Application::changeState, core, &Core::Squawk::changeState);
|
||||
connect(this, &Application::setRoomJoined, core, &Core::Squawk::setRoomJoined);
|
||||
connect(this, &Application::setRoomAutoJoin, core, &Core::Squawk::setRoomAutoJoin);
|
||||
connect(this, &Application::subscribeContact, core, &Core::Squawk::subscribeContact);
|
||||
connect(this, &Application::unsubscribeContact, core, &Core::Squawk::unsubscribeContact);
|
||||
connect(this, &Application::replaceMessage, core, &Core::Squawk::replaceMessage);
|
||||
connect(this, &Application::sendMessage, core, &Core::Squawk::sendMessage);
|
||||
connect(this, &Application::resendMessage, core, &Core::Squawk::resendMessage);
|
||||
connect(&roster, &Models::Roster::requestArchive,
|
||||
std::bind(&Core::Squawk::requestArchive, core, std::placeholders::_1, std::placeholders::_2, 20, std::placeholders::_3));
|
||||
|
||||
connect(&dialogueQueue, &DialogQueue::modifyAccountRequest, core, &Core::Squawk::modifyAccountRequest);
|
||||
connect(&dialogueQueue, &DialogQueue::responsePassword, core, &Core::Squawk::responsePassword);
|
||||
connect(&dialogueQueue, &DialogQueue::disconnectAccount, core, &Core::Squawk::disconnectAccount);
|
||||
|
||||
connect(&roster, &Models::Roster::fileDownloadRequest, core, &Core::Squawk::fileDownloadRequest);
|
||||
connect(&roster, &Models::Roster::localPathInvalid, core, &Core::Squawk::onLocalPathInvalid);
|
||||
|
||||
|
||||
//coonecting backend to myself
|
||||
connect(core, &Core::Squawk::stateChanged, this, &Application::stateChanged);
|
||||
|
||||
connect(core, &Core::Squawk::accountMessage, &roster, &Models::Roster::addMessage);
|
||||
connect(core, &Core::Squawk::responseArchive, &roster, &Models::Roster::responseArchive);
|
||||
connect(core, &Core::Squawk::changeMessage, &roster, &Models::Roster::changeMessage);
|
||||
|
||||
connect(core, &Core::Squawk::newAccount, &roster, &Models::Roster::addAccount);
|
||||
connect(core, &Core::Squawk::changeAccount, this, &Application::changeAccount);
|
||||
connect(core, &Core::Squawk::removeAccount, this, &Application::removeAccount);
|
||||
|
||||
connect(core, &Core::Squawk::addContact, this, &Application::addContact);
|
||||
connect(core, &Core::Squawk::addGroup, this, &Application::addGroup);
|
||||
connect(core, &Core::Squawk::removeGroup, &roster, &Models::Roster::removeGroup);
|
||||
connect(core, qOverload<const QString&, const QString&>(&Core::Squawk::removeContact),
|
||||
&roster, qOverload<const QString&, const QString&>(&Models::Roster::removeContact));
|
||||
connect(core, qOverload<const QString&, const QString&, const QString&>(&Core::Squawk::removeContact),
|
||||
&roster, qOverload<const QString&, const QString&, const QString&>(&Models::Roster::removeContact));
|
||||
connect(core, &Core::Squawk::changeContact, &roster, &Models::Roster::changeContact);
|
||||
connect(core, &Core::Squawk::addPresence, &roster, &Models::Roster::addPresence);
|
||||
connect(core, &Core::Squawk::removePresence, &roster, &Models::Roster::removePresence);
|
||||
|
||||
connect(core, &Core::Squawk::addRoom, &roster, &Models::Roster::addRoom);
|
||||
connect(core, &Core::Squawk::changeRoom, &roster, &Models::Roster::changeRoom);
|
||||
connect(core, &Core::Squawk::removeRoom, &roster, &Models::Roster::removeRoom);
|
||||
connect(core, &Core::Squawk::addRoomParticipant, &roster, &Models::Roster::addRoomParticipant);
|
||||
connect(core, &Core::Squawk::changeRoomParticipant, &roster, &Models::Roster::changeRoomParticipant);
|
||||
connect(core, &Core::Squawk::removeRoomParticipant, &roster, &Models::Roster::removeRoomParticipant);
|
||||
|
||||
|
||||
connect(core, &Core::Squawk::fileDownloadComplete, std::bind(&Models::Roster::fileComplete, &roster, std::placeholders::_1, false));
|
||||
connect(core, &Core::Squawk::fileUploadComplete, std::bind(&Models::Roster::fileComplete, &roster, std::placeholders::_1, true));
|
||||
connect(core, &Core::Squawk::fileProgress, &roster, &Models::Roster::fileProgress);
|
||||
connect(core, &Core::Squawk::fileError, &roster, &Models::Roster::fileError);
|
||||
|
||||
connect(core, &Core::Squawk::requestPassword, this, &Application::requestPassword);
|
||||
connect(core, &Core::Squawk::ready, this, &Application::readSettings);
|
||||
|
||||
QDBusConnection sys = QDBusConnection::sessionBus();
|
||||
sys.connect(
|
||||
"org.freedesktop.Notifications",
|
||||
"/org/freedesktop/Notifications",
|
||||
"org.freedesktop.Notifications",
|
||||
"NotificationClosed",
|
||||
this,
|
||||
SLOT(onNotificationClosed(quint32, quint32))
|
||||
);
|
||||
sys.connect(
|
||||
"org.freedesktop.Notifications",
|
||||
"/org/freedesktop/Notifications",
|
||||
"org.freedesktop.Notifications",
|
||||
"ActionInvoked",
|
||||
this,
|
||||
SLOT(onNotificationInvoked(quint32, const QString&))
|
||||
);
|
||||
}
|
||||
|
||||
Application::~Application() {}
|
||||
|
||||
void Application::quit()
|
||||
{
|
||||
if (!nowQuitting) {
|
||||
nowQuitting = true;
|
||||
emit quitting();
|
||||
|
||||
writeSettings();
|
||||
unreadMessagesCountChanged(0); //this notification persist in the desktop, for now I'll zero it on quit not to confuse people
|
||||
for (Conversations::const_iterator itr = conversations.begin(), end = conversations.end(); itr != end; ++itr) {
|
||||
disconnect(itr->second, &Conversation::destroyed, this, &Application::onConversationClosed);
|
||||
itr->second->close();
|
||||
}
|
||||
conversations.clear();
|
||||
dialogueQueue.quit();
|
||||
|
||||
if (squawk != nullptr) {
|
||||
squawk->close();
|
||||
}
|
||||
if (!destroyingSquawk) {
|
||||
checkForTheLastWindow();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Application::checkForTheLastWindow()
|
||||
{
|
||||
if (QApplication::topLevelWidgets().size() > 0) {
|
||||
emit readyToQuit();
|
||||
} else {
|
||||
connect(qApp, &QApplication::lastWindowClosed, this, &Application::readyToQuit);
|
||||
}
|
||||
}
|
||||
|
||||
void Application::createMainWindow()
|
||||
{
|
||||
if (squawk == nullptr) {
|
||||
squawk = new Squawk(roster);
|
||||
|
||||
connect(squawk, &Squawk::notify, this, &Application::notify);
|
||||
connect(squawk, &Squawk::changeSubscription, this, &Application::changeSubscription);
|
||||
connect(squawk, &Squawk::openedConversation, this, &Application::onSquawkOpenedConversation);
|
||||
connect(squawk, &Squawk::openConversation, this, &Application::openConversation);
|
||||
connect(squawk, &Squawk::changeState, this, &Application::setState);
|
||||
connect(squawk, &Squawk::closing, this, &Application::onSquawkClosing);
|
||||
|
||||
connect(squawk, &Squawk::modifyAccountRequest, core, &Core::Squawk::modifyAccountRequest);
|
||||
connect(squawk, &Squawk::newAccountRequest, core, &Core::Squawk::newAccountRequest);
|
||||
connect(squawk, &Squawk::removeAccountRequest, core, &Core::Squawk::removeAccountRequest);
|
||||
connect(squawk, &Squawk::connectAccount, core, &Core::Squawk::connectAccount);
|
||||
connect(squawk, &Squawk::disconnectAccount, core, &Core::Squawk::disconnectAccount);
|
||||
|
||||
connect(squawk, &Squawk::addContactRequest, core, &Core::Squawk::addContactRequest);
|
||||
connect(squawk, &Squawk::removeContactRequest, core, &Core::Squawk::removeContactRequest);
|
||||
connect(squawk, &Squawk::removeRoomRequest, core, &Core::Squawk::removeRoomRequest);
|
||||
connect(squawk, &Squawk::addRoomRequest, core, &Core::Squawk::addRoomRequest);
|
||||
connect(squawk, &Squawk::addContactToGroupRequest, core, &Core::Squawk::addContactToGroupRequest);
|
||||
connect(squawk, &Squawk::removeContactFromGroupRequest, core, &Core::Squawk::removeContactFromGroupRequest);
|
||||
connect(squawk, &Squawk::renameContactRequest, core, &Core::Squawk::renameContactRequest);
|
||||
connect(squawk, &Squawk::requestVCard, core, &Core::Squawk::requestVCard);
|
||||
connect(squawk, &Squawk::uploadVCard, core, &Core::Squawk::uploadVCard);
|
||||
connect(squawk, &Squawk::changeDownloadsPath, core, &Core::Squawk::changeDownloadsPath);
|
||||
|
||||
connect(core, &Core::Squawk::responseVCard, squawk, &Squawk::responseVCard);
|
||||
|
||||
dialogueQueue.setParentWidnow(squawk);
|
||||
squawk->stateChanged(availability);
|
||||
squawk->show();
|
||||
}
|
||||
}
|
||||
|
||||
void Application::onSquawkClosing()
|
||||
{
|
||||
dialogueQueue.setParentWidnow(nullptr);
|
||||
|
||||
if (!nowQuitting) {
|
||||
disconnect(core, &Core::Squawk::responseVCard, squawk, &Squawk::responseVCard);
|
||||
}
|
||||
|
||||
destroyingSquawk = true;
|
||||
squawk->deleteLater();
|
||||
squawk = nullptr;
|
||||
|
||||
//for now
|
||||
quit();
|
||||
}
|
||||
|
||||
void Application::onSquawkDestroyed() {
|
||||
destroyingSquawk = false;
|
||||
if (nowQuitting) {
|
||||
checkForTheLastWindow();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Application::notify(const QString& account, const Shared::Message& msg)
|
||||
{
|
||||
QString jid = msg.getPenPalJid();
|
||||
QString name = QString(roster.getContactName(account, jid));
|
||||
QString path = QString(roster.getContactIconPath(account, jid, msg.getPenPalResource()));
|
||||
QVariantList args;
|
||||
args << QString();
|
||||
|
||||
uint32_t notificationId = qHash(msg.getId());
|
||||
args << notificationId;
|
||||
if (path.size() > 0) {
|
||||
args << path;
|
||||
} else {
|
||||
args << QString("mail-message"); //TODO should here better be unknown user icon?
|
||||
}
|
||||
if (msg.getType() == Shared::Message::groupChat) {
|
||||
args << msg.getFromResource() + tr(" from ") + name;
|
||||
} else {
|
||||
args << name;
|
||||
}
|
||||
|
||||
QString body(msg.getBody());
|
||||
QString oob(msg.getOutOfBandUrl());
|
||||
if (body == oob) {
|
||||
body = tr("Attached file");
|
||||
}
|
||||
|
||||
args << body;
|
||||
args << QStringList({
|
||||
"markAsRead", tr("Mark as Read"),
|
||||
"openConversation", tr("Open conversation")
|
||||
});
|
||||
args << QVariantMap({
|
||||
{"desktop-entry", qApp->desktopFileName()},
|
||||
{"category", QString("message")},
|
||||
{"urgency", 1},
|
||||
// {"sound-file", "/path/to/macaw/squawk"},
|
||||
{"sound-name", QString("message-new-instant")}
|
||||
});
|
||||
args << -1;
|
||||
notifications.callWithArgumentList(QDBus::AutoDetect, "Notify", args);
|
||||
|
||||
storage.insert(std::make_pair(notificationId, std::make_pair(Models::Roster::ElId(account, name), msg.getId())));
|
||||
|
||||
if (squawk != nullptr) {
|
||||
QApplication::alert(squawk);
|
||||
}
|
||||
}
|
||||
|
||||
void Application::onNotificationClosed(quint32 id, quint32 reason)
|
||||
{
|
||||
Notifications::const_iterator itr = storage.find(id);
|
||||
if (itr != storage.end()) {
|
||||
if (reason == 2) { //dissmissed by user (https://specifications.freedesktop.org/notification-spec/latest/ar01s09.html)
|
||||
//TODO may ba also mark as read?
|
||||
}
|
||||
if (reason != 1) { //just expired, can be activated again from history, so no removing for now
|
||||
storage.erase(id);
|
||||
qDebug() << "Notification" << id << "was closed";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Application::onNotificationInvoked(quint32 id, const QString& action)
|
||||
{
|
||||
qDebug() << "Notification" << id << action << "request";
|
||||
Notifications::const_iterator itr = storage.find(id);
|
||||
if (itr != storage.end()) {
|
||||
if (action == "markAsRead") {
|
||||
roster.markMessageAsRead(itr->second.first, itr->second.second);
|
||||
} else if (action == "openConversation") {
|
||||
focusConversation(itr->second.first, "", itr->second.second);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Application::unreadMessagesCountChanged(int count)
|
||||
{
|
||||
QDBusMessage signal = QDBusMessage::createSignal("/", "com.canonical.Unity.LauncherEntry", "Update");
|
||||
signal << qApp->desktopFileName() + QLatin1String(".desktop");
|
||||
signal << QVariantMap ({
|
||||
{"count-visible", count != 0},
|
||||
{"count", count}
|
||||
});
|
||||
QDBusConnection::sessionBus().send(signal);
|
||||
}
|
||||
|
||||
void Application::focusConversation(const Models::Roster::ElId& id, const QString& resource, const QString& messageId)
|
||||
{
|
||||
if (squawk != nullptr) {
|
||||
if (squawk->currentConversationId() != id) {
|
||||
QModelIndex index = roster.getContactIndex(id.account, id.name, resource);
|
||||
squawk->select(index);
|
||||
}
|
||||
|
||||
if (squawk->isMinimized()) {
|
||||
squawk->showNormal();
|
||||
} else {
|
||||
squawk->show();
|
||||
}
|
||||
squawk->raise();
|
||||
squawk->activateWindow();
|
||||
} else {
|
||||
openConversation(id, resource);
|
||||
}
|
||||
|
||||
//TODO focus messageId;
|
||||
}
|
||||
|
||||
void Application::setState(Shared::Availability p_availability)
|
||||
{
|
||||
if (availability != p_availability) {
|
||||
availability = p_availability;
|
||||
emit changeState(availability);
|
||||
}
|
||||
}
|
||||
|
||||
void Application::stateChanged(Shared::Availability state)
|
||||
{
|
||||
availability = state;
|
||||
if (squawk != nullptr) {
|
||||
squawk->stateChanged(state);
|
||||
}
|
||||
}
|
||||
|
||||
void Application::readSettings()
|
||||
{
|
||||
QSettings settings;
|
||||
settings.beginGroup("ui");
|
||||
int avail;
|
||||
if (settings.contains("availability")) {
|
||||
avail = settings.value("availability").toInt();
|
||||
} else {
|
||||
avail = static_cast<int>(Shared::Availability::online);
|
||||
}
|
||||
settings.endGroup();
|
||||
|
||||
setState(Shared::Global::fromInt<Shared::Availability>(avail));
|
||||
createMainWindow();
|
||||
}
|
||||
|
||||
void Application::writeSettings()
|
||||
{
|
||||
QSettings settings;
|
||||
settings.setValue("availability", static_cast<int>(availability));
|
||||
}
|
||||
|
||||
void Application::requestPassword(const QString& account, bool authenticationError) {
|
||||
if (authenticationError) {
|
||||
dialogueQueue.addAction(account, DialogQueue::askCredentials);
|
||||
} else {
|
||||
dialogueQueue.addAction(account, DialogQueue::askPassword);
|
||||
}
|
||||
|
||||
}
|
||||
void Application::onConversationClosed()
|
||||
{
|
||||
Conversation* conv = static_cast<Conversation*>(sender());
|
||||
Models::Roster::ElId id(conv->getAccount(), conv->getJid());
|
||||
Conversations::const_iterator itr = conversations.find(id);
|
||||
if (itr != conversations.end()) {
|
||||
conversations.erase(itr);
|
||||
}
|
||||
if (conv->isMuc) {
|
||||
Room* room = static_cast<Room*>(conv);
|
||||
if (!room->autoJoined()) {
|
||||
emit setRoomJoined(id.account, id.name, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Application::changeSubscription(const Models::Roster::ElId& id, bool subscribe)
|
||||
{
|
||||
Models::Item::Type type = roster.getContactType(id);
|
||||
|
||||
switch (type) {
|
||||
case Models::Item::contact:
|
||||
if (subscribe) {
|
||||
emit subscribeContact(id.account, id.name, "");
|
||||
} else {
|
||||
emit unsubscribeContact(id.account, id.name, "");
|
||||
}
|
||||
break;
|
||||
case Models::Item::room:
|
||||
setRoomAutoJoin(id.account, id.name, subscribe);
|
||||
if (!isConverstationOpened(id)) {
|
||||
emit setRoomJoined(id.account, id.name, subscribe);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Application::subscribeConversation(Conversation* conv)
|
||||
{
|
||||
connect(conv, &Conversation::destroyed, this, &Application::onConversationClosed);
|
||||
connect(conv, &Conversation::sendMessage, this, &Application::onConversationMessage);
|
||||
connect(conv, &Conversation::replaceMessage, this, &Application::onConversationReplaceMessage);
|
||||
connect(conv, &Conversation::resendMessage, this, &Application::onConversationResend);
|
||||
connect(conv, &Conversation::notifyableMessage, this, &Application::notify);
|
||||
}
|
||||
|
||||
void Application::openConversation(const Models::Roster::ElId& id, const QString& resource)
|
||||
{
|
||||
Conversations::const_iterator itr = conversations.find(id);
|
||||
Models::Account* acc = roster.getAccount(id.account);
|
||||
Conversation* conv = nullptr;
|
||||
bool created = false;
|
||||
if (itr != conversations.end()) {
|
||||
conv = itr->second;
|
||||
} else {
|
||||
Models::Element* el = roster.getElement(id);
|
||||
if (el != nullptr) {
|
||||
if (el->type == Models::Item::room) {
|
||||
created = true;
|
||||
Models::Room* room = static_cast<Models::Room*>(el);
|
||||
conv = new Room(acc, room);
|
||||
if (!room->getJoined()) {
|
||||
emit setRoomJoined(id.account, id.name, true);
|
||||
}
|
||||
} else if (el->type == Models::Item::contact) {
|
||||
created = true;
|
||||
conv = new Chat(acc, static_cast<Models::Contact*>(el));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (conv != nullptr) {
|
||||
if (created) {
|
||||
conv->setAttribute(Qt::WA_DeleteOnClose);
|
||||
subscribeConversation(conv);
|
||||
conversations.insert(std::make_pair(id, conv));
|
||||
}
|
||||
|
||||
conv->show();
|
||||
conv->raise();
|
||||
conv->activateWindow();
|
||||
|
||||
if (resource.size() > 0) {
|
||||
conv->setPalResource(resource);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Application::onConversationMessage(const Shared::Message& msg)
|
||||
{
|
||||
Conversation* conv = static_cast<Conversation*>(sender());
|
||||
QString acc = conv->getAccount();
|
||||
|
||||
roster.addMessage(acc, msg);
|
||||
emit sendMessage(acc, msg);
|
||||
}
|
||||
|
||||
void Application::onConversationReplaceMessage(const QString& originalId, const Shared::Message& msg)
|
||||
{
|
||||
Conversation* conv = static_cast<Conversation*>(sender());
|
||||
QString acc = conv->getAccount();
|
||||
|
||||
roster.changeMessage(acc, msg.getPenPalJid(), originalId, {
|
||||
{"state", static_cast<uint>(Shared::Message::State::pending)}
|
||||
});
|
||||
emit replaceMessage(acc, originalId, msg);
|
||||
}
|
||||
|
||||
void Application::onConversationResend(const QString& id)
|
||||
{
|
||||
Conversation* conv = static_cast<Conversation*>(sender());
|
||||
QString acc = conv->getAccount();
|
||||
QString jid = conv->getJid();
|
||||
|
||||
emit resendMessage(acc, jid, id);
|
||||
}
|
||||
|
||||
void Application::onSquawkOpenedConversation() {
|
||||
subscribeConversation(squawk->currentConversation);
|
||||
Models::Roster::ElId id = squawk->currentConversationId();
|
||||
|
||||
const Models::Element* el = roster.getElementConst(id);
|
||||
if (el != nullptr && el->isRoom() && !static_cast<const Models::Room*>(el)->getJoined()) {
|
||||
emit setRoomJoined(id.account, id.name, true);
|
||||
}
|
||||
}
|
||||
|
||||
void Application::removeAccount(const QString& account)
|
||||
{
|
||||
Conversations::const_iterator itr = conversations.begin();
|
||||
while (itr != conversations.end()) {
|
||||
if (itr->first.account == account) {
|
||||
Conversations::const_iterator lItr = itr;
|
||||
++itr;
|
||||
Conversation* conv = lItr->second;
|
||||
disconnect(conv, &Conversation::destroyed, this, &Application::onConversationClosed);
|
||||
conv->close();
|
||||
conversations.erase(lItr);
|
||||
} else {
|
||||
++itr;
|
||||
}
|
||||
}
|
||||
|
||||
if (squawk != nullptr && squawk->currentConversationId().account == account) {
|
||||
squawk->closeCurrentConversation();
|
||||
}
|
||||
|
||||
roster.removeAccount(account);
|
||||
}
|
||||
|
||||
void Application::changeAccount(const QString& account, const QMap<QString, QVariant>& data)
|
||||
{
|
||||
for (QMap<QString, QVariant>::const_iterator itr = data.begin(), end = data.end(); itr != end; ++itr) {
|
||||
QString attr = itr.key();
|
||||
roster.updateAccount(account, attr, *itr);
|
||||
}
|
||||
}
|
||||
|
||||
void Application::addContact(const QString& account, const QString& jid, const QString& group, const QMap<QString, QVariant>& data)
|
||||
{
|
||||
roster.addContact(account, jid, group, data);
|
||||
|
||||
if (squawk != nullptr) {
|
||||
QSettings settings;
|
||||
settings.beginGroup("ui");
|
||||
settings.beginGroup("roster");
|
||||
settings.beginGroup(account);
|
||||
if (settings.value("expanded", false).toBool()) {
|
||||
QModelIndex ind = roster.getAccountIndex(account);
|
||||
squawk->expand(ind);
|
||||
}
|
||||
settings.endGroup();
|
||||
settings.endGroup();
|
||||
settings.endGroup();
|
||||
}
|
||||
}
|
||||
|
||||
void Application::addGroup(const QString& account, const QString& name)
|
||||
{
|
||||
roster.addGroup(account, name);
|
||||
|
||||
if (squawk != nullptr) {
|
||||
QSettings settings;
|
||||
settings.beginGroup("ui");
|
||||
settings.beginGroup("roster");
|
||||
settings.beginGroup(account);
|
||||
if (settings.value("expanded", false).toBool()) {
|
||||
QModelIndex ind = roster.getAccountIndex(account);
|
||||
squawk->expand(ind);
|
||||
if (settings.value(name + "/expanded", false).toBool()) {
|
||||
squawk->expand(roster.getGroupIndex(account, name));
|
||||
}
|
||||
}
|
||||
settings.endGroup();
|
||||
settings.endGroup();
|
||||
settings.endGroup();
|
||||
}
|
||||
}
|
||||
|
||||
bool Application::isConverstationOpened(const Models::Roster::ElId& id) const {
|
||||
return (conversations.count(id) > 0) || (squawk != nullptr && squawk->currentConversationId() == id);}
|
118
main/application.h
Normal file
118
main/application.h
Normal 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
|
187
main/dialogqueue.cpp
Normal file
187
main/dialogqueue.cpp
Normal file
@ -0,0 +1,187 @@
|
||||
// Squawk messenger.
|
||||
// Copyright (C) 2019 Yury Gubich <blue@macaw.me>
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
#include "dialogqueue.h"
|
||||
#include <QDebug>
|
||||
|
||||
DialogQueue::DialogQueue(const Models::Roster& p_roster):
|
||||
QObject(),
|
||||
currentSource(),
|
||||
currentAction(none),
|
||||
queue(),
|
||||
collection(queue.get<0>()),
|
||||
sequence(queue.get<1>()),
|
||||
prompt(nullptr),
|
||||
parent(nullptr),
|
||||
roster(p_roster)
|
||||
{
|
||||
}
|
||||
|
||||
DialogQueue::~DialogQueue()
|
||||
{
|
||||
}
|
||||
|
||||
void DialogQueue::quit()
|
||||
{
|
||||
queue.clear();
|
||||
if (currentAction != none) {
|
||||
actionDone();
|
||||
}
|
||||
}
|
||||
|
||||
void DialogQueue::setParentWidnow(QMainWindow* p_parent)
|
||||
{
|
||||
parent = p_parent;
|
||||
}
|
||||
|
||||
bool DialogQueue::addAction(const QString& source, DialogQueue::Action action)
|
||||
{
|
||||
if (action == none) {
|
||||
return false;
|
||||
}
|
||||
if (currentAction == none) {
|
||||
currentAction = action;
|
||||
currentSource = source;
|
||||
performNextAction();
|
||||
return true;
|
||||
} else {
|
||||
if (currentAction != action || currentSource != source) {
|
||||
std::pair<Queue::iterator, bool> result = queue.emplace(source, action);
|
||||
return result.second;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool DialogQueue::cancelAction(const QString& source, DialogQueue::Action action)
|
||||
{
|
||||
if (source == currentSource && action == currentAction) {
|
||||
actionDone();
|
||||
return true;
|
||||
} else {
|
||||
Collection::iterator itr = collection.find(ActionId{source, action});
|
||||
if (itr != collection.end()) {
|
||||
collection.erase(itr);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DialogQueue::performNextAction()
|
||||
{
|
||||
switch (currentAction) {
|
||||
case none:
|
||||
actionDone();
|
||||
break;
|
||||
case askPassword: {
|
||||
QInputDialog* dialog = new QInputDialog(parent);
|
||||
prompt = dialog;
|
||||
connect(dialog, &QDialog::accepted, this, &DialogQueue::onPropmtAccepted);
|
||||
connect(dialog, &QDialog::rejected, this, &DialogQueue::onPropmtRejected);
|
||||
dialog->setInputMode(QInputDialog::TextInput);
|
||||
dialog->setTextEchoMode(QLineEdit::Password);
|
||||
dialog->setLabelText(tr("Input the password for account %1").arg(currentSource));
|
||||
dialog->setWindowTitle(tr("Password for account %1").arg(currentSource));
|
||||
dialog->setTextValue("");
|
||||
dialog->exec();
|
||||
}
|
||||
break;
|
||||
case askCredentials: {
|
||||
CredentialsPrompt* dialog = new CredentialsPrompt(parent);
|
||||
prompt = dialog;
|
||||
connect(dialog, &QDialog::accepted, this, &DialogQueue::onPropmtAccepted);
|
||||
connect(dialog, &QDialog::rejected, this, &DialogQueue::onPropmtRejected);
|
||||
const Models::Account* acc = roster.getAccountConst(currentSource);
|
||||
dialog->setAccount(currentSource);
|
||||
dialog->setLogin(acc->getLogin());
|
||||
dialog->setPassword(acc->getPassword());
|
||||
dialog->exec();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void DialogQueue::onPropmtAccepted()
|
||||
{
|
||||
switch (currentAction) {
|
||||
case none:
|
||||
break;
|
||||
case askPassword: {
|
||||
QInputDialog* dialog = static_cast<QInputDialog*>(prompt);
|
||||
emit responsePassword(currentSource, dialog->textValue());
|
||||
}
|
||||
break;
|
||||
case askCredentials: {
|
||||
CredentialsPrompt* dialog = static_cast<CredentialsPrompt*>(prompt);
|
||||
emit modifyAccountRequest(currentSource, {
|
||||
{"login", dialog->getLogin()},
|
||||
{"password", dialog->getPassword()}
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
actionDone();
|
||||
}
|
||||
|
||||
void DialogQueue::onPropmtRejected()
|
||||
{
|
||||
switch (currentAction) {
|
||||
case none:
|
||||
break;
|
||||
case askPassword:
|
||||
case askCredentials:
|
||||
emit disconnectAccount(currentSource);
|
||||
break;
|
||||
}
|
||||
actionDone();
|
||||
}
|
||||
|
||||
void DialogQueue::actionDone()
|
||||
{
|
||||
prompt->deleteLater();
|
||||
prompt = nullptr;
|
||||
|
||||
if (queue.empty()) {
|
||||
currentAction = none;
|
||||
currentSource = "";
|
||||
} else {
|
||||
Sequence::iterator itr = sequence.begin();
|
||||
currentAction = itr->action;
|
||||
currentSource = itr->source;
|
||||
sequence.erase(itr);
|
||||
performNextAction();
|
||||
}
|
||||
}
|
||||
|
||||
DialogQueue::ActionId::ActionId(const QString& p_source, DialogQueue::Action p_action):
|
||||
source(p_source),
|
||||
action(p_action) {}
|
||||
|
||||
bool DialogQueue::ActionId::operator < (const DialogQueue::ActionId& other) const
|
||||
{
|
||||
if (action == other.action) {
|
||||
return source < other.source;
|
||||
} else {
|
||||
return action < other.action;
|
||||
}
|
||||
}
|
||||
|
||||
DialogQueue::ActionId::ActionId(const DialogQueue::ActionId& other):
|
||||
source(other.source),
|
||||
action(other.action) {}
|
101
main/dialogqueue.h
Normal file
101
main/dialogqueue.h
Normal file
@ -0,0 +1,101 @@
|
||||
// Squawk messenger.
|
||||
// Copyright (C) 2019 Yury Gubich <blue@macaw.me>
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
#ifndef DIALOGQUEUE_H
|
||||
#define DIALOGQUEUE_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QInputDialog>
|
||||
#include <QMainWindow>
|
||||
|
||||
#include <boost/multi_index_container.hpp>
|
||||
#include <boost/multi_index/ordered_index.hpp>
|
||||
#include <boost/multi_index/sequenced_index.hpp>
|
||||
|
||||
#include <ui/widgets/accounts/credentialsprompt.h>
|
||||
#include <ui/models/roster.h>
|
||||
|
||||
class DialogQueue : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum Action {
|
||||
none,
|
||||
askPassword,
|
||||
askCredentials
|
||||
};
|
||||
|
||||
DialogQueue(const Models::Roster& roster);
|
||||
~DialogQueue();
|
||||
|
||||
bool addAction(const QString& source, Action action);
|
||||
bool cancelAction(const QString& source, Action action);
|
||||
|
||||
signals:
|
||||
void modifyAccountRequest(const QString&, const QMap<QString, QVariant>&);
|
||||
void responsePassword(const QString& account, const QString& password);
|
||||
void disconnectAccount(const QString&);
|
||||
|
||||
public:
|
||||
void setParentWidnow(QMainWindow* parent);
|
||||
void quit();
|
||||
|
||||
private:
|
||||
void performNextAction();
|
||||
void actionDone();
|
||||
|
||||
private slots:
|
||||
void onPropmtAccepted();
|
||||
void onPropmtRejected();
|
||||
|
||||
private:
|
||||
QString currentSource;
|
||||
Action currentAction;
|
||||
|
||||
struct ActionId {
|
||||
public:
|
||||
ActionId(const QString& p_source, Action p_action);
|
||||
ActionId(const ActionId& other);
|
||||
|
||||
const QString source;
|
||||
const Action action;
|
||||
|
||||
bool operator < (const ActionId& other) const;
|
||||
};
|
||||
|
||||
typedef boost::multi_index_container <
|
||||
ActionId,
|
||||
boost::multi_index::indexed_by <
|
||||
boost::multi_index::ordered_unique <
|
||||
boost::multi_index::identity <ActionId>
|
||||
>,
|
||||
boost::multi_index::sequenced<>
|
||||
>
|
||||
> Queue;
|
||||
|
||||
typedef Queue::nth_index<0>::type Collection;
|
||||
typedef Queue::nth_index<1>::type Sequence;
|
||||
|
||||
Queue queue;
|
||||
Collection& collection;
|
||||
Sequence& sequence;
|
||||
|
||||
QDialog* prompt;
|
||||
QMainWindow* parent;
|
||||
const Models::Roster& roster;
|
||||
};
|
||||
|
||||
#endif // DIALOGQUEUE_H
|
140
main/main.cpp
Normal file
140
main/main.cpp
Normal 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;
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
# Maintainer: Yury Gubich <blue@macaw.me>
|
||||
pkgname=squawk
|
||||
pkgver=0.2.0
|
||||
pkgver=0.2.2
|
||||
pkgrel=1
|
||||
pkgdesc="An XMPP desktop messenger, written on pure c++ (qt)"
|
||||
arch=('i686' 'x86_64')
|
||||
@ -14,7 +14,7 @@ optdepends=('kwallet: secure password storage (requires rebuild)'
|
||||
'kio: better show in folder action (requires rebuild)')
|
||||
|
||||
source=("$pkgname-$pkgver.tar.gz")
|
||||
sha256sums=('8e93d3dbe1fc35cfecb7783af409c6a264244d11609b2241d4fe77d43d068419')
|
||||
sha256sums=('e4fa2174a3ba95159cc3b0bac3f00550c9e0ce971c55334e2662696a4543fc7e')
|
||||
build() {
|
||||
cd "$srcdir/squawk"
|
||||
cmake . -D CMAKE_INSTALL_PREFIX=/usr -D CMAKE_BUILD_TYPE=Release
|
||||
|
@ -19,6 +19,7 @@
|
||||
#include "global.h"
|
||||
|
||||
#include "enums.h"
|
||||
#include "ui/models/roster.h"
|
||||
|
||||
Shared::Global* Shared::Global::instance = 0;
|
||||
const std::set<QString> Shared::Global::supportedImagesExts = {"png", "jpg", "webp", "jpeg", "gif", "svg"};
|
||||
|
@ -47,7 +47,6 @@ namespace Shared {
|
||||
|
||||
class Global {
|
||||
Q_DECLARE_TR_FUNCTIONS(Global)
|
||||
|
||||
public:
|
||||
struct FileInfo {
|
||||
enum class Preview {
|
||||
|
@ -40,5 +40,5 @@ QString Shared::processMessageBody(const QString& msg)
|
||||
{
|
||||
QString processed = msg.toHtmlEscaped();
|
||||
processed.replace(urlReg, "<a href=\"\\1\">\\1</a>");
|
||||
return "<p style=\"white-space: pre-wrap;\">" + processed + "</p>";
|
||||
return "<p style=\"white-space: pre-wrap; line-height: 1em;\">" + processed + "</p>";
|
||||
}
|
||||
|
@ -69,6 +69,12 @@ static const std::vector<QColor> colorPalette = {
|
||||
QColor(17, 17, 80),
|
||||
QColor(54, 54, 94)
|
||||
};
|
||||
|
||||
enum class Hover {
|
||||
nothing,
|
||||
text,
|
||||
anchor
|
||||
};
|
||||
}
|
||||
|
||||
#endif // SHARED_UTILS_H
|
||||
|
@ -1,11 +1,12 @@
|
||||
find_package(Qt5LinguistTools)
|
||||
|
||||
set(TS_FILES
|
||||
squawk.en.ts
|
||||
squawk.ru.ts
|
||||
squawk.pt_BR.ts
|
||||
)
|
||||
qt5_add_translation(QM_FILES ${TS_FILES})
|
||||
add_custom_target(translations ALL DEPENDS ${QM_FILES})
|
||||
install(FILES ${QM_FILES} DESTINATION ${CMAKE_INSTALL_DATADIR}/squawk/l10n)
|
||||
install(FILES ${QM_FILES} DESTINATION ${CMAKE_INSTALL_DATADIR}/macaw.me/squawk/l10n)
|
||||
|
||||
add_dependencies(${CMAKE_PROJECT_NAME} translations)
|
||||
|
1420
translations/squawk.en.ts
Normal file
1420
translations/squawk.en.ts
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,108 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE TS>
|
||||
<TS version="2.1" language="pt_BR">
|
||||
<context>
|
||||
<name>About</name>
|
||||
<message>
|
||||
<source>About Squawk</source>
|
||||
<translation>Sorbe Squawk</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Squawk</source>
|
||||
<translation>Squawk</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>About</source>
|
||||
<translatorcomment>Tab title</translatorcomment>
|
||||
<translation>Sobre</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>XMPP (jabber) messenger</source>
|
||||
<translation>XMPP (jabber) mensageiro</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>(c) 2019 - 2022, Yury Gubich</source>
|
||||
<translation>(c) 2019 - 2022, Yury Gubich</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source><a href="https://git.macaw.me/blue/squawk">Project site</a></source>
|
||||
<translation><a href="https://git.macaw.me/blue/squawk">Site do projeto</a></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source><a href="https://git.macaw.me/blue/squawk/src/branch/master/LICENSE.md">License: GNU General Public License version 3</a></source>
|
||||
<translation><a href="https://git.macaw.me/blue/squawk/src/branch/master/LICENSE.md">Licença: GNU General Public License versão 3</a></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Components</source>
|
||||
<translation>Componentes</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Version</source>
|
||||
<translation>Versão</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>0.0.0</source>
|
||||
<translation>0.0.0</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Report Bugs</source>
|
||||
<translation>Relatório de erros</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Please report any bug you find!
|
||||
To report bugs you can use:</source>
|
||||
<translation>Por favor reportar qualquer erro que você encontrar!
|
||||
Para relatar bugs você pode usar:</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source><a href="https://git.macaw.me/blue/squawk/issues">Project bug tracker</></source>
|
||||
<translation><a href="https://git.macaw.me/blue/squawk/issues">Rastreador de bugs do projeto</></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>XMPP (<a href="xmpp:blue@macaw.me">blue@macaw.me</a>)</source>
|
||||
<translation>XMPP (<a href="xmpp:blue@macaw.me">blue@macaw.me</a>)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>E-Mail (<a href="mailto:blue@macaw.me">blue@macaw.me</a>)</source>
|
||||
<translation>E-Mail (<a href="mailto:blue@macaw.me">blue@macaw.me</a>)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Thanks To</source>
|
||||
<translation>Graças ao</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Vae</source>
|
||||
<translation>Vae</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Major refactoring, bug fixes, constructive criticism</source>
|
||||
<translation>Refatoração importante, correção de erros, críticas construtivas</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Shunf4</source>
|
||||
<translation>Shunf4</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Major refactoring, bug fixes, build adaptations for Windows and MacOS</source>
|
||||
<translation>Refatoração importante, correção de erros, adaptações de construção para Windows e MacOS</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Bruno F. Fontes</source>
|
||||
<translation>Bruno F. Fontes</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Brazilian Portuguese translation</source>
|
||||
<translation>Tradução para o português do Brasil</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>(built against %1)</source>
|
||||
<translation>(Versão durante a compilação %1)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>License</source>
|
||||
<translation>Licença</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Account</name>
|
||||
<message>
|
||||
@ -10,10 +112,12 @@
|
||||
</message>
|
||||
<message>
|
||||
<source>Your account login</source>
|
||||
<translatorcomment>Tooltip</translatorcomment>
|
||||
<translation>Suas informações de login</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>john_smith1987</source>
|
||||
<translatorcomment>Login placeholder</translatorcomment>
|
||||
<translation>josé_silva1987</translation>
|
||||
</message>
|
||||
<message>
|
||||
@ -22,10 +126,12 @@
|
||||
</message>
|
||||
<message>
|
||||
<source>A server address of your account. Like 404.city or macaw.me</source>
|
||||
<translatorcomment>Tooltip</translatorcomment>
|
||||
<translation>O endereço do servidor da sua conta, como o 404.city ou o macaw.me</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>macaw.me</source>
|
||||
<translatorcomment>Placeholder</translatorcomment>
|
||||
<translation>macaw.me</translation>
|
||||
</message>
|
||||
<message>
|
||||
@ -38,6 +144,7 @@
|
||||
</message>
|
||||
<message>
|
||||
<source>Password of your account</source>
|
||||
<translatorcomment>Tooltip</translatorcomment>
|
||||
<translation>Senha da sua conta</translation>
|
||||
</message>
|
||||
<message>
|
||||
@ -46,10 +153,11 @@
|
||||
</message>
|
||||
<message>
|
||||
<source>Just a name how would you call this account, doesn't affect anything</source>
|
||||
<translation>Apenas um nome para identificar esta conta. Não influencia em nada</translation>
|
||||
<translation>Apenas um nome para identificar esta conta. Não influencia em nada (não pode ser mudado)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>John</source>
|
||||
<translatorcomment>Placeholder</translatorcomment>
|
||||
<translation>José</translation>
|
||||
</message>
|
||||
<message>
|
||||
@ -58,6 +166,7 @@
|
||||
</message>
|
||||
<message>
|
||||
<source>A resource name like "Home" or "Work"</source>
|
||||
<translatorcomment>Tooltip</translatorcomment>
|
||||
<translation>Um nome de recurso como "Casa" ou "Trabalho"</translation>
|
||||
</message>
|
||||
<message>
|
||||
@ -69,6 +178,14 @@
|
||||
<source>Password storage</source>
|
||||
<translation>Armazenamento de senha</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Active</source>
|
||||
<translation>Ativo</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>enable</source>
|
||||
<translation>habilitar</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Accounts</name>
|
||||
@ -98,30 +215,135 @@
|
||||
</message>
|
||||
<message>
|
||||
<source>Disconnect</source>
|
||||
<translation>Desconectar</translation>
|
||||
<translation type="vanished">Desconectar</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Deactivate</source>
|
||||
<translation>Desativar</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Activate</source>
|
||||
<translation>Ativar</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Application</name>
|
||||
<message>
|
||||
<source> from </source>
|
||||
<translation> de </translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Attached file</source>
|
||||
<translation>Arquivo anexado</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Mark as Read</source>
|
||||
<translation>Marcar como lido</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Open conversation</source>
|
||||
<translation>Abrir conversa</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Conversation</name>
|
||||
<message>
|
||||
<source>Type your message here...</source>
|
||||
<translatorcomment>Placeholder</translatorcomment>
|
||||
<translation>Digite sua mensagem aqui...</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Chose a file to send</source>
|
||||
<translation>Escolha um arquivo para enviar</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Drop files here to attach them to your message</source>
|
||||
<translation>Arraste seus arquivos aqui para anexá-los a sua mensagem</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
|
||||
<html><head><meta name="qrichtext" content="1" /><style type="text/css">
|
||||
p, li { white-space: pre-wrap; }
|
||||
</style></head><body style=" font-family:'Liberation Sans'; font-size:10pt; font-weight:400; font-style:normal;">
|
||||
</style></head><body style=" font-family:'Noto Sans'; font-size:8pt; font-weight:400; font-style:normal;">
|
||||
<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html></source>
|
||||
<translation></translation>
|
||||
<translation><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
|
||||
<html><head><meta name="qrichtext" content="1" /><style type="text/css">
|
||||
p, li { white-space: pre-wrap; }
|
||||
</style></head><body style=" font-family:'Noto Sans'; font-size:8pt; font-weight:400; font-style:normal;">
|
||||
<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Drop files here to attach them to your message</source>
|
||||
<translation>Arraste seus arquivos aqui para anexá-los a sua mensagem</translation>
|
||||
<source>Paste Image</source>
|
||||
<translation>Colar imagem</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Try sending again</source>
|
||||
<translation>Tente enviar de novo</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Copy selected</source>
|
||||
<translation>Copiar selecionado</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Copy message</source>
|
||||
<translation>Copiar mensagem</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Open</source>
|
||||
<translation>Abrir</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Show in folder</source>
|
||||
<translation>Show in explorer</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Edit</source>
|
||||
<translation>Editar</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Editing message...</source>
|
||||
<translation>Messae está sendo editado...</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>CredentialsPrompt</name>
|
||||
<message>
|
||||
<source>Authentication error: %1</source>
|
||||
<translatorcomment>Window title</translatorcomment>
|
||||
<translation>Erro de autenticação: %1</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Couldn't authenticate account %1: login or password is icorrect.
|
||||
Would you like to check them and try again?</source>
|
||||
<translation>Não foi possível autenticar a conta %1: login ou senha incorretos.
|
||||
Deseja verificá-los e tentar novamente?</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Login</source>
|
||||
<translation>Usuário</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Your account login (without @server.domain)</source>
|
||||
<translation>Suas informações de login (sem @server.domain)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Password</source>
|
||||
<translation>Senha</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Your password</source>
|
||||
<translation>Senha da sua conta</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>DialogQueue</name>
|
||||
<message>
|
||||
<source>Input the password for account %1</source>
|
||||
<translation>Digite a senha para a conta %1</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Password for account %1</source>
|
||||
<translation>Senha para a conta %1</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@ -370,14 +592,14 @@ p, li { white-space: pre-wrap; }
|
||||
<name>Message</name>
|
||||
<message>
|
||||
<source>Open</source>
|
||||
<translation>Abrir</translation>
|
||||
<translation type="vanished">Abrir</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>MessageLine</name>
|
||||
<message>
|
||||
<source>Downloading...</source>
|
||||
<translation>Baixando...</translation>
|
||||
<translation type="vanished">Baixando...</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Download</source>
|
||||
@ -386,28 +608,28 @@ p, li { white-space: pre-wrap; }
|
||||
<message>
|
||||
<source>Error uploading file: %1
|
||||
You can try again</source>
|
||||
<translation>Error fazendo upload do arquivo:
|
||||
<translation type="vanished">Error fazendo upload do arquivo:
|
||||
%1
|
||||
Você pode tentar novamente</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Upload</source>
|
||||
<translation>Upload</translation>
|
||||
<translation type="vanished">Upload</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Error downloading file: %1
|
||||
You can try again</source>
|
||||
<translation>Erro baixando arquivo:
|
||||
<translation type="vanished">Erro baixando arquivo:
|
||||
%1
|
||||
Você pode tentar novamente</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Uploading...</source>
|
||||
<translation>Fazendo upload...</translation>
|
||||
<translation type="vanished">Fazendo upload...</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Push the button to download the file</source>
|
||||
<translation>Pressione o botão para baixar o arquivo</translation>
|
||||
<translation type="vanished">Pressione o botão para baixar o arquivo</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@ -481,7 +703,7 @@ Você pode tentar novamente</translation>
|
||||
<name>NewContact</name>
|
||||
<message>
|
||||
<source>Add new contact</source>
|
||||
<translatorcomment>Заголовок окна</translatorcomment>
|
||||
<translatorcomment>Window title</translatorcomment>
|
||||
<translation>Adicionar novo contato</translation>
|
||||
</message>
|
||||
<message>
|
||||
@ -502,7 +724,7 @@ Você pode tentar novamente</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>name@server.dmn</source>
|
||||
<translatorcomment>Placeholder поля ввода JID</translatorcomment>
|
||||
<translatorcomment>Placeholder</translatorcomment>
|
||||
<translation>nome@servidor.com.br</translation>
|
||||
</message>
|
||||
<message>
|
||||
@ -518,6 +740,66 @@ Você pode tentar novamente</translation>
|
||||
<translation>José Silva</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>PageAppearance</name>
|
||||
<message>
|
||||
<source>Theme</source>
|
||||
<translation>Estilo</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Color scheme</source>
|
||||
<translation>Esquema de cores</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>System</source>
|
||||
<translation>Do sistema</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>PageGeneral</name>
|
||||
<message>
|
||||
<source>Downloads path</source>
|
||||
<translation>Pasta de downloads</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Browse</source>
|
||||
<translation>6 / 5,000
|
||||
Translation results
|
||||
Navegar</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Select where downloads folder is going to be</source>
|
||||
<translation>Selecione onde a pasta de downloads ficará</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Settings</name>
|
||||
<message>
|
||||
<source>Preferences</source>
|
||||
<translatorcomment>Window title</translatorcomment>
|
||||
<translation>Preferências</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>General</source>
|
||||
<translation>Geral</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Appearance</source>
|
||||
<translation>Aparência</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Apply</source>
|
||||
<translation>Aplicar</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cancel</source>
|
||||
<translation>Cancelar</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Ok</source>
|
||||
<translation>Feito</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Squawk</name>
|
||||
<message>
|
||||
@ -530,6 +812,7 @@ Você pode tentar novamente</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Squawk</source>
|
||||
<translatorcomment>Menu bar entry</translatorcomment>
|
||||
<translation>Squawk</translation>
|
||||
</message>
|
||||
<message>
|
||||
@ -550,11 +833,11 @@ Você pode tentar novamente</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Disconnect</source>
|
||||
<translation>Desconectar</translation>
|
||||
<translation type="vanished">Desconectar</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Connect</source>
|
||||
<translation>Conectar</translation>
|
||||
<translation type="vanished">Conectar</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>VCard</source>
|
||||
@ -627,26 +910,46 @@ ser exibido com
|
||||
</message>
|
||||
<message>
|
||||
<source>Attached file</source>
|
||||
<translation>Arquivo anexado</translation>
|
||||
<translation type="vanished">Arquivo anexado</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Input the password for account %1</source>
|
||||
<translation>Digite a senha para a conta %1</translation>
|
||||
<translation type="vanished">Digite a senha para a conta %1</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Password for account %1</source>
|
||||
<translation>Senha para a conta %1</translation>
|
||||
<translation type="vanished">Senha para a conta %1</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Please select a contact to start chatting</source>
|
||||
<translation>Por favor selecione um contato para começar a conversar</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Help</source>
|
||||
<translation>Ajuda</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Preferences</source>
|
||||
<translation>Preferências</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>About Squawk</source>
|
||||
<translation>Sorbe Squawk</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Deactivate</source>
|
||||
<translation>Desativar</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Activate</source>
|
||||
<translation>Ativar</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>VCard</name>
|
||||
<message>
|
||||
<source>Received 12.07.2007 at 17.35</source>
|
||||
<translation>Recebido 12/07/2007 às 17:35</translation>
|
||||
<translation>Nunca atualizado</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>General</source>
|
||||
@ -682,7 +985,7 @@ ser exibido com
|
||||
</message>
|
||||
<message>
|
||||
<source>Unit / Department</source>
|
||||
<translation>Unidade/Departamento</translation>
|
||||
<translation>Unidade / Departamento</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Role / Profession</source>
|
||||
|
@ -1,6 +1,108 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE TS>
|
||||
<TS version="2.1" language="ru_RU">
|
||||
<context>
|
||||
<name>About</name>
|
||||
<message>
|
||||
<source>About Squawk</source>
|
||||
<translation>О Программе Squawk</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Squawk</source>
|
||||
<translation>Squawk</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>About</source>
|
||||
<translatorcomment>Tab title</translatorcomment>
|
||||
<translation>Общее</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>XMPP (jabber) messenger</source>
|
||||
<translation>XMPP (jabber) мессенджер</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>(c) 2019 - 2022, Yury Gubich</source>
|
||||
<translation>(c) 2019 - 2022, Юрий Губич</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source><a href="https://git.macaw.me/blue/squawk">Project site</a></source>
|
||||
<translation><a href="https://git.macaw.me/blue/squawk">Сайт проекта</a></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source><a href="https://git.macaw.me/blue/squawk/src/branch/master/LICENSE.md">License: GNU General Public License version 3</a></source>
|
||||
<translation><a href="https://git.macaw.me/blue/squawk/src/branch/master/LICENSE.md">Лицензия: GNU General Public License версия 3</a></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Components</source>
|
||||
<translation>Компоненты</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Version</source>
|
||||
<translation>Версия</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>0.0.0</source>
|
||||
<translation>0.0.0</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Report Bugs</source>
|
||||
<translation>Сообщать об ошибках</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Please report any bug you find!
|
||||
To report bugs you can use:</source>
|
||||
<translation>Пожалуйста, сообщайте о любых ошибках!
|
||||
Способы сообщить об ошибках:</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source><a href="https://git.macaw.me/blue/squawk/issues">Project bug tracker</></source>
|
||||
<translation><a href="https://git.macaw.me/blue/squawk/issues">Баг-трекер проекта</></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>XMPP (<a href="xmpp:blue@macaw.me">blue@macaw.me</a>)</source>
|
||||
<translation>XMPP (<a href="xmpp:blue@macaw.me">blue@macaw.me</a>)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>E-Mail (<a href="mailto:blue@macaw.me">blue@macaw.me</a>)</source>
|
||||
<translation>E-Mail (<a href="mailto:blue@macaw.me">blue@macaw.me</a>)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Thanks To</source>
|
||||
<translation>Благодарности</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Vae</source>
|
||||
<translation>Vae</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Major refactoring, bug fixes, constructive criticism</source>
|
||||
<translation>Крупный рефакторинг, исправление ошибок, конструктивная критика</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Shunf4</source>
|
||||
<translation>Shunf4</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Major refactoring, bug fixes, build adaptations for Windows and MacOS</source>
|
||||
<translation>Крупный рефакторинг, исправление ошибок, адаптация сборки под Windows and MacOS</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Bruno F. Fontes</source>
|
||||
<translation>Bruno F. Fontes</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Brazilian Portuguese translation</source>
|
||||
<translation>Перевод на Португальский (Бразилия)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>(built against %1)</source>
|
||||
<translation>(версия при сборке %1)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>License</source>
|
||||
<translation>Лицензия</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Account</name>
|
||||
<message>
|
||||
@ -10,10 +112,12 @@
|
||||
</message>
|
||||
<message>
|
||||
<source>Your account login</source>
|
||||
<translatorcomment>Tooltip</translatorcomment>
|
||||
<translation>Имя пользователя Вашей учетной записи</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>john_smith1987</source>
|
||||
<translatorcomment>Login placeholder</translatorcomment>
|
||||
<translation>ivan_ivanov1987</translation>
|
||||
</message>
|
||||
<message>
|
||||
@ -22,10 +126,12 @@
|
||||
</message>
|
||||
<message>
|
||||
<source>A server address of your account. Like 404.city or macaw.me</source>
|
||||
<translatorcomment>Tooltip</translatorcomment>
|
||||
<translation>Адресс сервера вашей учетной записи (выглядит как 404.city или macaw.me)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>macaw.me</source>
|
||||
<translatorcomment>Placeholder</translatorcomment>
|
||||
<translation>macaw.me</translation>
|
||||
</message>
|
||||
<message>
|
||||
@ -38,6 +144,7 @@
|
||||
</message>
|
||||
<message>
|
||||
<source>Password of your account</source>
|
||||
<translatorcomment>Tooltip</translatorcomment>
|
||||
<translation>Пароль вашей учетной записи</translation>
|
||||
</message>
|
||||
<message>
|
||||
@ -46,10 +153,11 @@
|
||||
</message>
|
||||
<message>
|
||||
<source>Just a name how would you call this account, doesn't affect anything</source>
|
||||
<translation>Просто имя, то как Вы называете свою учетную запись, может быть любым</translation>
|
||||
<translation>Просто имя, то как Вы называете свою учетную запись, может быть любым (нельзя поменять)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>John</source>
|
||||
<translatorcomment>Placeholder</translatorcomment>
|
||||
<translation>Иван</translation>
|
||||
</message>
|
||||
<message>
|
||||
@ -58,6 +166,7 @@
|
||||
</message>
|
||||
<message>
|
||||
<source>A resource name like "Home" or "Work"</source>
|
||||
<translatorcomment>Tooltip</translatorcomment>
|
||||
<translation>Имя этой программы для ваших контактов, может быть "Home" или "Phone"</translation>
|
||||
</message>
|
||||
<message>
|
||||
@ -69,6 +178,14 @@
|
||||
<source>Password storage</source>
|
||||
<translation>Хранение пароля</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Active</source>
|
||||
<translation>Активен</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>enable</source>
|
||||
<translation>включен</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Accounts</name>
|
||||
@ -98,30 +215,139 @@
|
||||
</message>
|
||||
<message>
|
||||
<source>Disconnect</source>
|
||||
<translation>Отключить</translation>
|
||||
<translation type="vanished">Отключить</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Deactivate</source>
|
||||
<translation>Деактивировать</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Activate</source>
|
||||
<translation>Активировать</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Application</name>
|
||||
<message>
|
||||
<source> from </source>
|
||||
<translation> от </translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Attached file</source>
|
||||
<translation>Прикрепленный файл</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Mark as Read</source>
|
||||
<translation>Пометить прочитанным</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Open conversation</source>
|
||||
<translation>Открыть окно беседы</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Conversation</name>
|
||||
<message>
|
||||
<source>Type your message here...</source>
|
||||
<translatorcomment>Placeholder</translatorcomment>
|
||||
<translation>Введите сообщение...</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Chose a file to send</source>
|
||||
<translation>Выберите файл для отправки</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Drop files here to attach them to your message</source>
|
||||
<translation>Бросьте файлы сюда для того что бы прикрепить их к сообщению</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
|
||||
<html><head><meta name="qrichtext" content="1" /><style type="text/css">
|
||||
p, li { white-space: pre-wrap; }
|
||||
</style></head><body style=" font-family:'Liberation Sans'; font-size:10pt; font-weight:400; font-style:normal;">
|
||||
</style></head><body style=" font-family:'Noto Sans'; font-size:8pt; font-weight:400; font-style:normal;">
|
||||
<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html></source>
|
||||
<translation></translation>
|
||||
<translation><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
|
||||
<html><head><meta name="qrichtext" content="1" /><style type="text/css">
|
||||
p, li { white-space: pre-wrap; }
|
||||
</style></head><body style=" font-family:'Noto Sans'; font-size:8pt; font-weight:400; font-style:normal;">
|
||||
<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Drop files here to attach them to your message</source>
|
||||
<translation>Бросьте файлы сюда для того что бы прикрепить их к сообщению</translation>
|
||||
<source>Paste Image</source>
|
||||
<translation>Вставить изображение</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Try sending again</source>
|
||||
<translation>Отправить снова</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Copy selected</source>
|
||||
<translation>Скопировать выделенное</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Copy message</source>
|
||||
<translation>Скопировать сообщение</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Open</source>
|
||||
<translation>Открыть</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Show in folder</source>
|
||||
<translation>Показать в проводнике</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Edit</source>
|
||||
<translation>Редактировать</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Editing message...</source>
|
||||
<translation>Сообщение редактируется...</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>CredentialsPrompt</name>
|
||||
<message>
|
||||
<source>Authentication error: %1</source>
|
||||
<translatorcomment>Window title</translatorcomment>
|
||||
<translation>Ошибка аутентификации: %1</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Couldn't authenticate account %1: login or password is icorrect.
|
||||
Would you like to check them and try again?</source>
|
||||
<translation>Не получилось аутентифицировать
|
||||
учетную запись %1:
|
||||
имя пользователя или пароль введены неверно.
|
||||
Желаете ли проверить их и
|
||||
попробовать аутентифицироваться еще раз?</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Login</source>
|
||||
<translation>Имя учетной записи</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Your account login (without @server.domain)</source>
|
||||
<translatorcomment>Tooltip</translatorcomment>
|
||||
<translation>Имя вашей учтетной записи (без @server.domain)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Password</source>
|
||||
<translation>Пароль</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Your password</source>
|
||||
<translation>Ваш пароль</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>DialogQueue</name>
|
||||
<message>
|
||||
<source>Input the password for account %1</source>
|
||||
<translation>Введите пароль для учетной записи %1</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Password for account %1</source>
|
||||
<translation>Пароль для учетной записи %1</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@ -370,14 +596,14 @@ p, li { white-space: pre-wrap; }
|
||||
<name>Message</name>
|
||||
<message>
|
||||
<source>Open</source>
|
||||
<translation>Открыть</translation>
|
||||
<translation type="vanished">Открыть</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>MessageLine</name>
|
||||
<message>
|
||||
<source>Downloading...</source>
|
||||
<translation>Скачивается...</translation>
|
||||
<translation type="vanished">Скачивается...</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Download</source>
|
||||
@ -386,28 +612,28 @@ p, li { white-space: pre-wrap; }
|
||||
<message>
|
||||
<source>Error uploading file: %1
|
||||
You can try again</source>
|
||||
<translation>Ошибка загрузки файла на сервер:
|
||||
<translation type="vanished">Ошибка загрузки файла на сервер:
|
||||
%1
|
||||
Для того, что бы попробовать снова нажмите на кнопку</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Upload</source>
|
||||
<translation>Загрузить</translation>
|
||||
<translation type="vanished">Загрузить</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Error downloading file: %1
|
||||
You can try again</source>
|
||||
<translation>Ошибка скачивания файла:
|
||||
<translation type="vanished">Ошибка скачивания файла:
|
||||
%1
|
||||
Вы можете попробовать снова</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Uploading...</source>
|
||||
<translation>Загружается...</translation>
|
||||
<translation type="vanished">Загружается...</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Push the button to download the file</source>
|
||||
<translation>Нажмите на кнопку что бы загрузить файл</translation>
|
||||
<translation type="vanished">Нажмите на кнопку что бы загрузить файл</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@ -481,7 +707,7 @@ You can try again</source>
|
||||
<name>NewContact</name>
|
||||
<message>
|
||||
<source>Add new contact</source>
|
||||
<translatorcomment>Заголовок окна</translatorcomment>
|
||||
<translatorcomment>Window title</translatorcomment>
|
||||
<translation>Добавление нового контакта</translation>
|
||||
</message>
|
||||
<message>
|
||||
@ -502,7 +728,7 @@ You can try again</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>name@server.dmn</source>
|
||||
<translatorcomment>Placeholder поля ввода JID</translatorcomment>
|
||||
<translatorcomment>Placeholder</translatorcomment>
|
||||
<translation>name@server.dmn</translation>
|
||||
</message>
|
||||
<message>
|
||||
@ -518,6 +744,64 @@ You can try again</source>
|
||||
<translation>Иван Иванов</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>PageAppearance</name>
|
||||
<message>
|
||||
<source>Theme</source>
|
||||
<translation>Оформление</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Color scheme</source>
|
||||
<translation>Цветовая схема</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>System</source>
|
||||
<translation>Системная</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>PageGeneral</name>
|
||||
<message>
|
||||
<source>Downloads path</source>
|
||||
<translation>Папка для сохраненных файлов</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Browse</source>
|
||||
<translation>Выбрать</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Select where downloads folder is going to be</source>
|
||||
<translation>Выберете папку, в которую будут сохраняться файлы</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Settings</name>
|
||||
<message>
|
||||
<source>Preferences</source>
|
||||
<translatorcomment>Window title</translatorcomment>
|
||||
<translation>Настройки</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>General</source>
|
||||
<translation>Общее</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Appearance</source>
|
||||
<translation>Внешний вид</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Apply</source>
|
||||
<translation>Применить</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cancel</source>
|
||||
<translation>Отменить</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Ok</source>
|
||||
<translation>Готово</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Squawk</name>
|
||||
<message>
|
||||
@ -530,6 +814,7 @@ You can try again</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>Squawk</source>
|
||||
<translatorcomment>Menu bar entry</translatorcomment>
|
||||
<translation>Squawk</translation>
|
||||
</message>
|
||||
<message>
|
||||
@ -550,11 +835,11 @@ You can try again</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>Disconnect</source>
|
||||
<translation>Отключить</translation>
|
||||
<translation type="vanished">Отключить</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Connect</source>
|
||||
<translation>Подключить</translation>
|
||||
<translation type="vanished">Подключить</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>VCard</source>
|
||||
@ -627,20 +912,40 @@ to be displayed as %1</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>Attached file</source>
|
||||
<translation>Прикрепленный файл</translation>
|
||||
<translation type="vanished">Прикрепленный файл</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Input the password for account %1</source>
|
||||
<translation>Введите пароль для учетной записи %1</translation>
|
||||
<translation type="vanished">Введите пароль для учетной записи %1</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Password for account %1</source>
|
||||
<translation>Пароль для учетной записи %1</translation>
|
||||
<translation type="vanished">Пароль для учетной записи %1</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Please select a contact to start chatting</source>
|
||||
<translation>Выберите контакт или группу что бы начать переписку</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Help</source>
|
||||
<translation>Помощь</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Preferences</source>
|
||||
<translation>Настройки</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>About Squawk</source>
|
||||
<translation>О Программе Squawk</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Deactivate</source>
|
||||
<translation>Деактивировать</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Activate</source>
|
||||
<translation>Активировать</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>VCard</name>
|
||||
|
@ -1,4 +1,8 @@
|
||||
target_sources(squawk PRIVATE squawk.cpp squawk.h squawk.ui)
|
||||
target_sources(squawk PRIVATE
|
||||
squawk.cpp
|
||||
squawk.h
|
||||
squawk.ui
|
||||
)
|
||||
|
||||
add_subdirectory(models)
|
||||
add_subdirectory(utils)
|
||||
|
@ -32,7 +32,8 @@ Models::Account::Account(const QMap<QString, QVariant>& data, Models::Item* pare
|
||||
state(Shared::ConnectionState::disconnected),
|
||||
availability(Shared::Availability::offline),
|
||||
passwordType(Shared::AccountPassword::plain),
|
||||
wasEverConnected(false)
|
||||
wasEverConnected(false),
|
||||
active(false)
|
||||
{
|
||||
QMap<QString, QVariant>::const_iterator sItr = data.find("state");
|
||||
if (sItr != data.end()) {
|
||||
@ -46,6 +47,10 @@ Models::Account::Account(const QMap<QString, QVariant>& data, Models::Item* pare
|
||||
if (pItr != data.end()) {
|
||||
setPasswordType(pItr.value().toUInt());
|
||||
}
|
||||
QMap<QString, QVariant>::const_iterator acItr = data.find("active");
|
||||
if (acItr != data.end()) {
|
||||
setActive(acItr.value().toBool());
|
||||
}
|
||||
}
|
||||
|
||||
Models::Account::~Account()
|
||||
@ -176,6 +181,8 @@ QVariant Models::Account::data(int column) const
|
||||
return avatarPath;
|
||||
case 9:
|
||||
return Shared::Global::getName(passwordType);
|
||||
case 10:
|
||||
return active;
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
@ -183,7 +190,7 @@ QVariant Models::Account::data(int column) const
|
||||
|
||||
int Models::Account::columnCount() const
|
||||
{
|
||||
return 10;
|
||||
return 11;
|
||||
}
|
||||
|
||||
void Models::Account::update(const QString& field, const QVariant& value)
|
||||
@ -208,6 +215,8 @@ void Models::Account::update(const QString& field, const QVariant& value)
|
||||
setAvatarPath(value.toString());
|
||||
} else if (field == "passwordType") {
|
||||
setPasswordType(value.toUInt());
|
||||
} else if (field == "active") {
|
||||
setActive(value.toBool());
|
||||
}
|
||||
}
|
||||
|
||||
@ -281,3 +290,16 @@ void Models::Account::setPasswordType(unsigned int pt)
|
||||
{
|
||||
setPasswordType(Shared::Global::fromInt<Shared::AccountPassword>(pt));
|
||||
}
|
||||
|
||||
bool Models::Account::getActive() const
|
||||
{
|
||||
return active;
|
||||
}
|
||||
|
||||
void Models::Account::setActive(bool p_active)
|
||||
{
|
||||
if (active != p_active) {
|
||||
active = p_active;
|
||||
changed(10);
|
||||
}
|
||||
}
|
||||
|
@ -59,6 +59,9 @@ namespace Models {
|
||||
void setAvatarPath(const QString& path);
|
||||
QString getAvatarPath() const;
|
||||
|
||||
void setActive(bool active);
|
||||
bool getActive() const;
|
||||
|
||||
void setAvailability(Shared::Availability p_avail);
|
||||
void setAvailability(unsigned int p_avail);
|
||||
Shared::Availability getAvailability() const;
|
||||
@ -91,6 +94,7 @@ namespace Models {
|
||||
Shared::Availability availability;
|
||||
Shared::AccountPassword passwordType;
|
||||
bool wasEverConnected;
|
||||
bool active;
|
||||
|
||||
protected slots:
|
||||
void toOfflineState() override;
|
||||
|
@ -48,6 +48,10 @@ QVariant Models::Accounts::data (const QModelIndex& index, int role) const
|
||||
answer = Shared::connectionStateIcon(accs[index.row()]->getState());
|
||||
}
|
||||
break;
|
||||
case Qt::ForegroundRole:
|
||||
if (!accs[index.row()]->getActive()) {
|
||||
answer = qApp->palette().brush(QPalette::Disabled, QPalette::Text);
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -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()
|
||||
{
|
||||
QDateTime lastActivity;
|
||||
|
@ -51,6 +51,7 @@ public:
|
||||
|
||||
void addPresence(const QString& name, const QMap<QString, QVariant>& data);
|
||||
void removePresence(const QString& name);
|
||||
Presence* getPresence(const QString& name);
|
||||
|
||||
QString getContactName() const;
|
||||
QString getStatus() const;
|
||||
|
@ -134,6 +134,11 @@ unsigned int Models::Element::getMessagesCount() const
|
||||
return feed->unreadMessagesCount();
|
||||
}
|
||||
|
||||
bool Models::Element::markMessageAsRead(const QString& id) const
|
||||
{
|
||||
return feed->markMessageAsRead(id);
|
||||
}
|
||||
|
||||
void Models::Element::addMessage(const Shared::Message& data)
|
||||
{
|
||||
feed->addMessage(data);
|
||||
@ -171,6 +176,7 @@ void Models::Element::fileError(const QString& messageId, const QString& error,
|
||||
|
||||
void Models::Element::onFeedUnreadMessagesCountChanged()
|
||||
{
|
||||
emit unreadMessagesCountChanged();
|
||||
if (type == contact) {
|
||||
changed(4);
|
||||
} else if (type == room) {
|
||||
|
@ -42,6 +42,7 @@ public:
|
||||
void addMessage(const Shared::Message& data);
|
||||
void changeMessage(const QString& id, const QMap<QString, QVariant>& data);
|
||||
unsigned int getMessagesCount() const;
|
||||
bool markMessageAsRead(const QString& id) const;
|
||||
void responseArchive(const std::list<Shared::Message> list, bool last);
|
||||
bool isRoom() const;
|
||||
void fileProgress(const QString& messageId, qreal value, bool up);
|
||||
@ -52,6 +53,7 @@ signals:
|
||||
void requestArchive(const QString& before);
|
||||
void fileDownloadRequest(const QString& url);
|
||||
void unnoticedMessage(const QString& account, const Shared::Message& msg);
|
||||
void unreadMessagesCountChanged();
|
||||
void localPathInvalid(const QString& path);
|
||||
|
||||
protected:
|
||||
|
@ -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)
|
||||
{
|
||||
Participant* part = itr->second;
|
||||
|
@ -58,6 +58,7 @@ public:
|
||||
void addParticipant(const QString& name, const QMap<QString, QVariant>& data);
|
||||
void changeParticipant(const QString& name, const QMap<QString, QVariant>& data);
|
||||
void removeParticipant(const QString& name);
|
||||
Participant* getParticipant(const QString& name);
|
||||
|
||||
void toOfflineState() override;
|
||||
QString getDisplayedName() const override;
|
||||
|
@ -276,6 +276,18 @@ QVariant Models::Roster::data (const QModelIndex& index, int role) const
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case Qt::ForegroundRole:
|
||||
switch (item->type) {
|
||||
case Item::account: {
|
||||
Account* acc = static_cast<Account*>(item);
|
||||
if (!acc->getActive()) {
|
||||
result = qApp->palette().brush(QPalette::Disabled, QPalette::Text);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@ -451,6 +463,7 @@ void Models::Roster::addContact(const QString& account, const QString& jid, cons
|
||||
connect(contact, &Contact::fileDownloadRequest, this, &Roster::fileDownloadRequest);
|
||||
connect(contact, &Contact::unnoticedMessage, this, &Roster::unnoticedMessage);
|
||||
connect(contact, &Contact::localPathInvalid, this, &Roster::localPathInvalid);
|
||||
connect(contact, &Contact::unreadMessagesCountChanged, this, &Roster::recalculateUnreadMessages);
|
||||
contacts.insert(std::make_pair(id, contact));
|
||||
} else {
|
||||
contact = itr->second;
|
||||
@ -536,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)
|
||||
{
|
||||
Element* el = getElement({account, jid});
|
||||
if (el != NULL) {
|
||||
Element* el = getElement(ElId(account, jid));
|
||||
if (el != nullptr) {
|
||||
for (QMap<QString, QVariant>::const_iterator itr = data.begin(), end = data.end(); itr != end; ++itr) {
|
||||
el->update(itr.key(), itr.value());
|
||||
}
|
||||
@ -546,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)
|
||||
{
|
||||
Element* el = getElement({account, jid});
|
||||
if (el != NULL) {
|
||||
Element* el = getElement(ElId(account, jid));
|
||||
if (el != nullptr) {
|
||||
el->changeMessage(id, data);
|
||||
} else {
|
||||
qDebug() << "A request to change a message of the contact " << jid << " in the account " << account << " but it wasn't found";
|
||||
@ -694,8 +707,8 @@ void Models::Roster::removePresence(const QString& account, const QString& jid,
|
||||
|
||||
void Models::Roster::addMessage(const QString& account, const Shared::Message& data)
|
||||
{
|
||||
Element* el = getElement({account, data.getPenPalJid()});
|
||||
if (el != NULL) {
|
||||
Element* el = getElement(ElId(account, data.getPenPalJid()));
|
||||
if (el != nullptr) {
|
||||
el->addMessage(data);
|
||||
}
|
||||
}
|
||||
@ -751,7 +764,7 @@ void Models::Roster::removeAccount(const QString& account)
|
||||
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);
|
||||
std::map<ElId, Contact*>::const_iterator cItr = contacts.find(id);
|
||||
@ -789,10 +802,11 @@ void Models::Roster::addRoom(const QString& account, const QString jid, const QM
|
||||
}
|
||||
|
||||
Room* room = new Room(acc, jid, data);
|
||||
connect(room, &Contact::requestArchive, this, &Roster::onElementRequestArchive);
|
||||
connect(room, &Contact::fileDownloadRequest, this, &Roster::fileDownloadRequest);
|
||||
connect(room, &Contact::unnoticedMessage, this, &Roster::unnoticedMessage);
|
||||
connect(room, &Contact::localPathInvalid, this, &Roster::localPathInvalid);
|
||||
connect(room, &Room::requestArchive, this, &Roster::onElementRequestArchive);
|
||||
connect(room, &Room::fileDownloadRequest, this, &Roster::fileDownloadRequest);
|
||||
connect(room, &Room::unnoticedMessage, this, &Roster::unnoticedMessage);
|
||||
connect(room, &Room::localPathInvalid, this, &Roster::localPathInvalid);
|
||||
connect(room, &Room::unreadMessagesCountChanged, this, &Roster::recalculateUnreadMessages);
|
||||
rooms.insert(std::make_pair(id, room));
|
||||
acc->appendChild(room);
|
||||
}
|
||||
@ -895,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);
|
||||
std::map<ElId, Contact*>::const_iterator cItr = contacts.find(id);
|
||||
@ -915,9 +929,36 @@ QString Models::Roster::getContactIconPath(const QString& account, const QString
|
||||
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)
|
||||
@ -936,7 +977,7 @@ QModelIndex Models::Roster::getGroupIndex(const QString& account, const QString&
|
||||
if (itr == accounts.end()) {
|
||||
return QModelIndex();
|
||||
} else {
|
||||
std::map<ElId, Group*>::const_iterator gItr = groups.find({account, name});
|
||||
std::map<ElId, Group*>::const_iterator gItr = groups.find(ElId(account, name));
|
||||
if (gItr == groups.end()) {
|
||||
return QModelIndex();
|
||||
} else {
|
||||
@ -946,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)
|
||||
{
|
||||
Element* el = static_cast<Element*>(sender());
|
||||
@ -956,7 +1039,7 @@ void Models::Roster::responseArchive(const QString& account, const QString& jid,
|
||||
{
|
||||
ElId id(account, jid);
|
||||
Element* el = getElement(id);
|
||||
if (el != NULL) {
|
||||
if (el != nullptr) {
|
||||
el->responseArchive(list, last);
|
||||
}
|
||||
}
|
||||
@ -964,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)
|
||||
{
|
||||
for (const Shared::MessageInfo& info : msgs) {
|
||||
Element* el = getElement({info.account, info.jid});
|
||||
if (el != NULL) {
|
||||
Element* el = getElement(ElId(info.account, info.jid));
|
||||
if (el != nullptr) {
|
||||
el->fileProgress(info.messageId, value, up);
|
||||
}
|
||||
}
|
||||
@ -974,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)
|
||||
{
|
||||
for (const Shared::MessageInfo& info : msgs) {
|
||||
Element* el = getElement({info.account, info.jid});
|
||||
if (el != NULL) {
|
||||
Element* el = getElement(ElId(info.account, info.jid));
|
||||
if (el != nullptr) {
|
||||
el->fileComplete(info.messageId, up);
|
||||
}
|
||||
}
|
||||
@ -984,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)
|
||||
{
|
||||
for (const Shared::MessageInfo& info : msgs) {
|
||||
Element* el = getElement({info.account, info.jid});
|
||||
if (el != NULL) {
|
||||
Element* el = getElement(ElId(info.account, info.jid));
|
||||
if (el != nullptr) {
|
||||
el->fileError(info.messageId, err, up);
|
||||
}
|
||||
}
|
||||
@ -993,20 +1076,20 @@ void Models::Roster::fileError(const std::list<Shared::MessageInfo>& msgs, const
|
||||
|
||||
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()) {
|
||||
return cItr->second;
|
||||
} else {
|
||||
std::map<ElId, Room*>::iterator rItr = rooms.find(id);
|
||||
if (rItr != rooms.end()) {
|
||||
return rItr->second;
|
||||
}
|
||||
Models::Item::Type Models::Roster::getContactType(const Models::Roster::ElId& id) const
|
||||
{
|
||||
const Models::Element* el = getElementConst(id);
|
||||
if (el == nullptr) {
|
||||
return Item::root;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
return el->type;
|
||||
}
|
||||
|
||||
|
||||
void Models::Roster::onAccountReconnected()
|
||||
{
|
||||
Account* acc = static_cast<Account*>(sender());
|
||||
@ -1019,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);
|
||||
}
|
||||
|
@ -46,6 +46,7 @@ public:
|
||||
Roster(QObject* parent = 0);
|
||||
~Roster();
|
||||
|
||||
public slots:
|
||||
void addAccount(const QMap<QString, QVariant> &data);
|
||||
void updateAccount(const QString& account, const QString& field, const QVariant& value);
|
||||
void removeAccount(const QString& account);
|
||||
@ -65,7 +66,12 @@ public:
|
||||
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);
|
||||
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;
|
||||
Qt::ItemFlags flags(const QModelIndex &index) const override;
|
||||
@ -77,10 +83,13 @@ public:
|
||||
|
||||
std::deque<QString> groupList(const QString& account) const;
|
||||
bool groupHasContact(const QString& account, const QString& group, const QString& contactJID) const;
|
||||
QString getContactIconPath(const QString& account, const QString& jid, const QString& resource);
|
||||
QString getContactIconPath(const QString& account, const QString& jid, const QString& resource) const;
|
||||
Account* getAccount(const QString& name);
|
||||
const Account* getAccountConst(const QString& name) const;
|
||||
QModelIndex getAccountIndex(const QString& name);
|
||||
QModelIndex getGroupIndex(const QString& account, const QString& name);
|
||||
QModelIndex getContactIndex(const QString& account, const QString& jid, const QString& resource = "");
|
||||
bool markMessageAsRead(const ElId& elementId, const QString& messageId);
|
||||
void responseArchive(const QString& account, const QString& jid, const std::list<Shared::Message>& list, bool last);
|
||||
|
||||
void fileProgress(const std::list<Shared::MessageInfo>& msgs, qreal value, bool up);
|
||||
@ -92,12 +101,10 @@ public:
|
||||
signals:
|
||||
void requestArchive(const QString& account, const QString& jid, const QString& before);
|
||||
void fileDownloadRequest(const QString& url);
|
||||
void unreadMessagesCountChanged(int count);
|
||||
void unnoticedMessage(const QString& account, const Shared::Message& msg);
|
||||
void localPathInvalid(const QString& path);
|
||||
|
||||
private:
|
||||
Element* getElement(const ElId& id);
|
||||
|
||||
private slots:
|
||||
void onAccountDataChanged(const QModelIndex& tl, const QModelIndex& br, const QVector<int>& roles);
|
||||
void onAccountReconnected();
|
||||
@ -109,6 +116,7 @@ private slots:
|
||||
void onChildIsAboutToBeMoved(Item* source, int first, int last, Item* destination, int newIndex);
|
||||
void onChildMoved();
|
||||
void onElementRequestArchive(const QString& before);
|
||||
void recalculateUnreadMessages();
|
||||
|
||||
private:
|
||||
Item* root;
|
||||
|
744
ui/squawk.cpp
744
ui/squawk.cpp
@ -21,19 +21,16 @@
|
||||
#include <QDebug>
|
||||
#include <QIcon>
|
||||
|
||||
Squawk::Squawk(QWidget *parent) :
|
||||
Squawk::Squawk(Models::Roster& p_rosterModel, QWidget *parent) :
|
||||
QMainWindow(parent),
|
||||
m_ui(new Ui::Squawk),
|
||||
accounts(0),
|
||||
preferences(0),
|
||||
rosterModel(),
|
||||
conversations(),
|
||||
accounts(nullptr),
|
||||
preferences(nullptr),
|
||||
about(nullptr),
|
||||
rosterModel(p_rosterModel),
|
||||
contextMenu(new QMenu()),
|
||||
dbus("org.freedesktop.Notifications", "/org/freedesktop/Notifications", "org.freedesktop.Notifications", QDBusConnection::sessionBus()),
|
||||
vCards(),
|
||||
requestedAccountsForPasswords(),
|
||||
prompt(0),
|
||||
currentConversation(0),
|
||||
currentConversation(nullptr),
|
||||
restoreSelection(),
|
||||
needToRestore(false)
|
||||
{
|
||||
@ -54,24 +51,22 @@ Squawk::Squawk(QWidget *parent) :
|
||||
m_ui->comboBox->addItem(Shared::availabilityIcon(av), Shared::Global::getName(av));
|
||||
}
|
||||
m_ui->comboBox->setCurrentIndex(static_cast<int>(Shared::Availability::offline));
|
||||
createTrayIcon();
|
||||
|
||||
connect(m_ui->actionAccounts, &QAction::triggered, this, &Squawk::onAccounts);
|
||||
connect(m_ui->actionPreferences, &QAction::triggered, this, &Squawk::onPreferences);
|
||||
connect(m_ui->actionAddContact, &QAction::triggered, this, &Squawk::onNewContact);
|
||||
connect(m_ui->actionAddConference, &QAction::triggered, this, &Squawk::onNewConference);
|
||||
connect(m_ui->actionQuit, &QAction::triggered, this, &Squawk::close);
|
||||
connect(m_ui->actionQuit, &QAction::triggered, [this]() { hide(); close(); }); // Actually closing
|
||||
connect(m_ui->comboBox, qOverload<int>(&QComboBox::activated), this, &Squawk::onComboboxActivated);
|
||||
//connect(m_ui->roster, &QTreeView::doubleClicked, this, &Squawk::onRosterItemDoubleClicked);
|
||||
connect(m_ui->roster, &QTreeView::customContextMenuRequested, this, &Squawk::onRosterContextMenu);
|
||||
connect(m_ui->roster, &QTreeView::collapsed, this, &Squawk::onItemCollepsed);
|
||||
connect(m_ui->roster->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &Squawk::onRosterSelectionChanged);
|
||||
connect(&rosterModel, &Models::Roster::unnoticedMessage, this, &Squawk::onUnnoticedMessage);
|
||||
|
||||
connect(rosterModel.accountsModel, &Models::Accounts::sizeChanged, this, &Squawk::onAccountsSizeChanged);
|
||||
connect(&rosterModel, &Models::Roster::requestArchive, this, &Squawk::onRequestArchive);
|
||||
connect(&rosterModel, &Models::Roster::fileDownloadRequest, this, &Squawk::fileDownloadRequest);
|
||||
connect(&rosterModel, &Models::Roster::localPathInvalid, this, &Squawk::localPathInvalid);
|
||||
connect(contextMenu, &QMenu::aboutToHide, this, &Squawk::onContextAboutToHide);
|
||||
connect(m_ui->actionAboutSquawk, &QAction::triggered, this, &Squawk::onAboutSquawkCalled);
|
||||
//m_ui->mainToolBar->addWidget(m_ui->comboBox);
|
||||
|
||||
if (testAttribute(Qt::WA_TranslucentBackground)) {
|
||||
@ -95,13 +90,38 @@ Squawk::Squawk(QWidget *parent) :
|
||||
settings.endGroup();
|
||||
}
|
||||
|
||||
QSystemTrayIcon* Squawk::trayIcon;
|
||||
|
||||
Squawk::~Squawk() {
|
||||
delete contextMenu;
|
||||
delete trayIcon;
|
||||
}
|
||||
|
||||
void Squawk::createTrayIcon()
|
||||
{
|
||||
QSettings settings;
|
||||
trayIcon = new QSystemTrayIcon(this);
|
||||
trayIcon->setIcon(QApplication::windowIcon());
|
||||
|
||||
QMenu * menu = new QMenu(this);
|
||||
QAction * viewWindow = new QAction("Open Main Window", this);//TODO add translations
|
||||
QAction * quitAction = new QAction("Quit", this);
|
||||
|
||||
connect(viewWindow, SIGNAL(triggered()), this, SLOT(show()));
|
||||
connect(quitAction, &QAction::triggered, [this]() { hide(); close(); }); // Actually closing
|
||||
|
||||
menu->addAction(viewWindow);
|
||||
menu->addAction(quitAction);
|
||||
|
||||
trayIcon->setContextMenu(menu);
|
||||
if(settings.value("trayIconCheckbox").toBool())
|
||||
trayIcon->show();
|
||||
}
|
||||
|
||||
|
||||
void Squawk::onAccounts()
|
||||
{
|
||||
if (accounts == 0) {
|
||||
if (accounts == nullptr) {
|
||||
accounts = new Accounts(rosterModel.accountsModel);
|
||||
accounts->setAttribute(Qt::WA_DeleteOnClose);
|
||||
connect(accounts, &Accounts::destroyed, this, &Squawk::onAccountsClosed);
|
||||
@ -121,7 +141,7 @@ void Squawk::onAccounts()
|
||||
|
||||
void Squawk::onPreferences()
|
||||
{
|
||||
if (preferences == 0) {
|
||||
if (preferences == nullptr) {
|
||||
preferences = new Settings();
|
||||
preferences->setAttribute(Qt::WA_DeleteOnClose);
|
||||
connect(preferences, &Settings::destroyed, this, &Squawk::onPreferencesClosed);
|
||||
@ -189,151 +209,54 @@ void Squawk::onJoinConferenceAccepted()
|
||||
|
||||
void Squawk::closeEvent(QCloseEvent* event)
|
||||
{
|
||||
if (accounts != 0) {
|
||||
accounts->close();
|
||||
}
|
||||
if (preferences != 0) {
|
||||
preferences->close();
|
||||
}
|
||||
QSettings settings;
|
||||
if(this->isVisible() && settings.value("trayIconCheckbox").toBool()){
|
||||
event->ignore();
|
||||
this->hide();
|
||||
|
||||
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();
|
||||
} else {
|
||||
if (accounts != nullptr) {
|
||||
accounts->close();
|
||||
}
|
||||
if (preferences != nullptr) {
|
||||
preferences->close();
|
||||
}
|
||||
if (about != nullptr) {
|
||||
about->close();
|
||||
}
|
||||
|
||||
for (std::map<QString, VCard*>::const_iterator itr = vCards.begin(), end = vCards.end(); itr != end; ++itr) {
|
||||
disconnect(itr->second, &VCard::destroyed, this, &Squawk::onVCardClosed);
|
||||
itr->second->close();
|
||||
}
|
||||
vCards.clear();
|
||||
for (std::map<QString, VCard*>::const_iterator itr = vCards.begin(), end = vCards.end(); itr != end; ++itr) {
|
||||
disconnect(itr->second, &VCard::destroyed, this, &Squawk::onVCardClosed);
|
||||
itr->second->close();
|
||||
}
|
||||
vCards.clear();
|
||||
writeSettings();
|
||||
emit closing();
|
||||
|
||||
QMainWindow::closeEvent(event);
|
||||
QMainWindow::closeEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
void Squawk::onAccountsClosed() {
|
||||
accounts = nullptr;}
|
||||
|
||||
void Squawk::onAccountsClosed()
|
||||
{
|
||||
accounts = 0;
|
||||
}
|
||||
void Squawk::onPreferencesClosed() {
|
||||
preferences = nullptr;}
|
||||
|
||||
void Squawk::onPreferencesClosed()
|
||||
{
|
||||
preferences = 0;
|
||||
}
|
||||
|
||||
void Squawk::newAccount(const QMap<QString, QVariant>& account)
|
||||
{
|
||||
rosterModel.addAccount(account);
|
||||
}
|
||||
void Squawk::onAboutSquawkClosed() {
|
||||
about = nullptr;}
|
||||
|
||||
void Squawk::onComboboxActivated(int index)
|
||||
{
|
||||
Shared::Availability av = Shared::Global::fromInt<Shared::Availability>(index);
|
||||
if (av != Shared::Availability::offline) {
|
||||
int size = rosterModel.accountsModel->rowCount(QModelIndex());
|
||||
if (size > 0) {
|
||||
emit changeState(av);
|
||||
for (int i = 0; i < size; ++i) {
|
||||
Models::Account* acc = rosterModel.accountsModel->getAccount(i);
|
||||
if (acc->getState() == Shared::ConnectionState::disconnected) {
|
||||
emit connectAccount(acc->getName());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
m_ui->comboBox->setCurrentIndex(static_cast<int>(Shared::Availability::offline));
|
||||
}
|
||||
} else {
|
||||
emit changeState(av);
|
||||
int size = rosterModel.accountsModel->rowCount(QModelIndex());
|
||||
for (int i = 0; i != size; ++i) {
|
||||
Models::Account* acc = rosterModel.accountsModel->getAccount(i);
|
||||
if (acc->getState() != Shared::ConnectionState::disconnected) {
|
||||
emit disconnectAccount(acc->getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
emit changeState(av);
|
||||
}
|
||||
|
||||
void Squawk::changeAccount(const QString& account, const QMap<QString, QVariant>& data)
|
||||
{
|
||||
for (QMap<QString, QVariant>::const_iterator itr = data.begin(), end = data.end(); itr != end; ++itr) {
|
||||
QString attr = itr.key();
|
||||
rosterModel.updateAccount(account, attr, *itr);
|
||||
}
|
||||
}
|
||||
void Squawk::expand(const QModelIndex& index) {
|
||||
m_ui->roster->expand(index);}
|
||||
|
||||
void Squawk::addContact(const QString& account, const QString& jid, const QString& group, const QMap<QString, QVariant>& data)
|
||||
{
|
||||
rosterModel.addContact(account, jid, group, data);
|
||||
|
||||
QSettings settings;
|
||||
settings.beginGroup("ui");
|
||||
settings.beginGroup("roster");
|
||||
settings.beginGroup(account);
|
||||
if (settings.value("expanded", false).toBool()) {
|
||||
QModelIndex ind = rosterModel.getAccountIndex(account);
|
||||
m_ui->roster->expand(ind);
|
||||
}
|
||||
settings.endGroup();
|
||||
settings.endGroup();
|
||||
settings.endGroup();
|
||||
}
|
||||
|
||||
void Squawk::addGroup(const QString& account, const QString& name)
|
||||
{
|
||||
rosterModel.addGroup(account, name);
|
||||
|
||||
QSettings settings;
|
||||
settings.beginGroup("ui");
|
||||
settings.beginGroup("roster");
|
||||
settings.beginGroup(account);
|
||||
if (settings.value("expanded", false).toBool()) {
|
||||
QModelIndex ind = rosterModel.getAccountIndex(account);
|
||||
m_ui->roster->expand(ind);
|
||||
if (settings.value(name + "/expanded", false).toBool()) {
|
||||
m_ui->roster->expand(rosterModel.getGroupIndex(account, name));
|
||||
}
|
||||
}
|
||||
settings.endGroup();
|
||||
settings.endGroup();
|
||||
settings.endGroup();
|
||||
}
|
||||
|
||||
void Squawk::removeGroup(const QString& account, const QString& name)
|
||||
{
|
||||
rosterModel.removeGroup(account, name);
|
||||
}
|
||||
|
||||
void Squawk::changeContact(const QString& account, const QString& jid, const QMap<QString, QVariant>& data)
|
||||
{
|
||||
rosterModel.changeContact(account, jid, data);
|
||||
}
|
||||
|
||||
void Squawk::removeContact(const QString& account, const QString& jid)
|
||||
{
|
||||
rosterModel.removeContact(account, jid);
|
||||
}
|
||||
|
||||
void Squawk::removeContact(const QString& account, const QString& jid, const QString& group)
|
||||
{
|
||||
rosterModel.removeContact(account, jid, group);
|
||||
}
|
||||
|
||||
void Squawk::addPresence(const QString& account, const QString& jid, const QString& name, const QMap<QString, QVariant>& data)
|
||||
{
|
||||
rosterModel.addPresence(account, jid, name, data);
|
||||
}
|
||||
|
||||
void Squawk::removePresence(const QString& account, const QString& jid, const QString& name)
|
||||
{
|
||||
rosterModel.removePresence(account, jid, name);
|
||||
}
|
||||
|
||||
void Squawk::stateChanged(Shared::Availability state)
|
||||
{
|
||||
m_ui->comboBox->setCurrentIndex(static_cast<int>(state));
|
||||
}
|
||||
void Squawk::stateChanged(Shared::Availability state) {
|
||||
m_ui->comboBox->setCurrentIndex(static_cast<int>(state));}
|
||||
|
||||
void Squawk::onRosterItemDoubleClicked(const QModelIndex& item)
|
||||
{
|
||||
@ -342,214 +265,35 @@ void Squawk::onRosterItemDoubleClicked(const QModelIndex& item)
|
||||
if (node->type == Models::Item::reference) {
|
||||
node = static_cast<Models::Reference*>(node)->dereference();
|
||||
}
|
||||
Models::Contact* contact = 0;
|
||||
Models::Room* room = 0;
|
||||
QString res;
|
||||
Models::Roster::ElId* id = 0;
|
||||
Models::Contact* contact = nullptr;
|
||||
Models::Room* room = nullptr;
|
||||
switch (node->type) {
|
||||
case Models::Item::contact:
|
||||
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;
|
||||
case Models::Item::presence:
|
||||
contact = static_cast<Models::Contact*>(node->parentItem());
|
||||
id = new Models::Roster::ElId(contact->getAccountName(), contact->getJid());
|
||||
res = node->getName();
|
||||
emit openConversation(Models::Roster::ElId(contact->getAccountName(), contact->getJid()), node->getName());
|
||||
break;
|
||||
case Models::Item::room:
|
||||
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;
|
||||
default:
|
||||
m_ui->roster->expand(item);
|
||||
break;
|
||||
}
|
||||
|
||||
if (id != 0) {
|
||||
Conversations::const_iterator itr = conversations.find(*id);
|
||||
Models::Account* acc = rosterModel.getAccount(id->account);
|
||||
Conversation* conv = 0;
|
||||
bool created = false;
|
||||
if (itr != conversations.end()) {
|
||||
conv = itr->second;
|
||||
} else if (contact != 0) {
|
||||
created = true;
|
||||
conv = new Chat(acc, contact);
|
||||
} else if (room != 0) {
|
||||
created = true;
|
||||
conv = new Room(acc, room);
|
||||
|
||||
if (!room->getJoined()) {
|
||||
emit setRoomJoined(id->account, id->name, true);
|
||||
}
|
||||
}
|
||||
|
||||
if (conv != 0) {
|
||||
if (created) {
|
||||
conv->setAttribute(Qt::WA_DeleteOnClose);
|
||||
subscribeConversation(conv);
|
||||
conversations.insert(std::make_pair(*id, conv));
|
||||
}
|
||||
|
||||
conv->show();
|
||||
conv->raise();
|
||||
conv->activateWindow();
|
||||
|
||||
if (res.size() > 0) {
|
||||
conv->setPalResource(res);
|
||||
}
|
||||
}
|
||||
|
||||
delete id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Squawk::onConversationClosed(QObject* parent)
|
||||
void Squawk::closeCurrentConversation()
|
||||
{
|
||||
Conversation* conv = static_cast<Conversation*>(sender());
|
||||
Models::Roster::ElId id(conv->getAccount(), conv->getJid());
|
||||
Conversations::const_iterator itr = conversations.find(id);
|
||||
if (itr != conversations.end()) {
|
||||
conversations.erase(itr);
|
||||
}
|
||||
if (conv->isMuc) {
|
||||
Room* room = static_cast<Room*>(conv);
|
||||
if (!room->autoJoined()) {
|
||||
emit setRoomJoined(id.account, id.name, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Squawk::fileProgress(const std::list<Shared::MessageInfo> msgs, qreal value, bool up)
|
||||
{
|
||||
rosterModel.fileProgress(msgs, value, up);
|
||||
}
|
||||
|
||||
void Squawk::fileDownloadComplete(const std::list<Shared::MessageInfo> msgs, const QString& path)
|
||||
{
|
||||
rosterModel.fileComplete(msgs, false);
|
||||
}
|
||||
|
||||
void Squawk::fileError(const std::list<Shared::MessageInfo> msgs, const QString& error, bool up)
|
||||
{
|
||||
rosterModel.fileError(msgs, error, up);
|
||||
}
|
||||
|
||||
void Squawk::fileUploadComplete(const std::list<Shared::MessageInfo> msgs, const QString& 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 != 0 && currentConversation->getAccount() == account) {
|
||||
if (currentConversation != nullptr) {
|
||||
currentConversation->deleteLater();
|
||||
currentConversation = 0;
|
||||
currentConversation = nullptr;
|
||||
m_ui->filler->show();
|
||||
}
|
||||
|
||||
rosterModel.removeAccount(account);
|
||||
}
|
||||
|
||||
void Squawk::onRosterContextMenu(const QPoint& point)
|
||||
@ -569,17 +313,12 @@ void Squawk::onRosterContextMenu(const QPoint& point)
|
||||
hasMenu = true;
|
||||
QString name = acc->getName();
|
||||
|
||||
if (acc->getState() != Shared::ConnectionState::disconnected) {
|
||||
QAction* con = contextMenu->addAction(Shared::icon("network-disconnect"), tr("Disconnect"));
|
||||
con->setEnabled(active);
|
||||
connect(con, &QAction::triggered, [this, name]() {
|
||||
emit disconnectAccount(name);
|
||||
});
|
||||
if (acc->getActive()) {
|
||||
QAction* con = contextMenu->addAction(Shared::icon("network-disconnect"), tr("Deactivate"));
|
||||
connect(con, &QAction::triggered, std::bind(&Squawk::disconnectAccount, this, name));
|
||||
} else {
|
||||
QAction* con = contextMenu->addAction(Shared::icon("network-connect"), tr("Connect"));
|
||||
connect(con, &QAction::triggered, [this, name]() {
|
||||
emit connectAccount(name);
|
||||
});
|
||||
QAction* con = contextMenu->addAction(Shared::icon("network-connect"), tr("Activate"));
|
||||
connect(con, &QAction::triggered, std::bind(&Squawk::connectAccount, this, name));
|
||||
}
|
||||
|
||||
QAction* card = contextMenu->addAction(Shared::icon("user-properties"), tr("VCard"));
|
||||
@ -587,22 +326,18 @@ void Squawk::onRosterContextMenu(const QPoint& point)
|
||||
connect(card, &QAction::triggered, std::bind(&Squawk::onActivateVCard, this, name, acc->getBareJid(), true));
|
||||
|
||||
QAction* remove = contextMenu->addAction(Shared::icon("edit-delete"), tr("Remove"));
|
||||
remove->setEnabled(active);
|
||||
connect(remove, &QAction::triggered, [this, name]() {
|
||||
emit removeAccount(name);
|
||||
});
|
||||
|
||||
connect(remove, &QAction::triggered, std::bind(&Squawk::removeAccountRequest, this, name));
|
||||
}
|
||||
break;
|
||||
case Models::Item::contact: {
|
||||
Models::Contact* cnt = static_cast<Models::Contact*>(item);
|
||||
Models::Roster::ElId id(cnt->getAccountName(), cnt->getJid());
|
||||
QString cntName = cnt->getName();
|
||||
hasMenu = true;
|
||||
|
||||
QAction* dialog = contextMenu->addAction(Shared::icon("mail-message"), tr("Open dialog"));
|
||||
dialog->setEnabled(active);
|
||||
connect(dialog, &QAction::triggered, [this, index]() {
|
||||
onRosterItemDoubleClicked(index);
|
||||
});
|
||||
connect(dialog, &QAction::triggered, std::bind(&Squawk::onRosterItemDoubleClicked, this, index));
|
||||
|
||||
Shared::SubscriptionState state = cnt->getState();
|
||||
switch (state) {
|
||||
@ -610,9 +345,7 @@ void Squawk::onRosterContextMenu(const QPoint& point)
|
||||
case Shared::SubscriptionState::to: {
|
||||
QAction* unsub = contextMenu->addAction(Shared::icon("news-unsubscribe"), tr("Unsubscribe"));
|
||||
unsub->setEnabled(active);
|
||||
connect(unsub, &QAction::triggered, [this, cnt]() {
|
||||
emit unsubscribeContact(cnt->getAccountName(), cnt->getJid(), "");
|
||||
});
|
||||
connect(unsub, &QAction::triggered, std::bind(&Squawk::changeSubscription, this, id, false));
|
||||
}
|
||||
break;
|
||||
case Shared::SubscriptionState::from:
|
||||
@ -620,75 +353,68 @@ void Squawk::onRosterContextMenu(const QPoint& point)
|
||||
case Shared::SubscriptionState::none: {
|
||||
QAction* sub = contextMenu->addAction(Shared::icon("news-subscribe"), tr("Subscribe"));
|
||||
sub->setEnabled(active);
|
||||
connect(sub, &QAction::triggered, [this, cnt]() {
|
||||
emit subscribeContact(cnt->getAccountName(), cnt->getJid(), "");
|
||||
});
|
||||
connect(sub, &QAction::triggered, std::bind(&Squawk::changeSubscription, this, id, true));
|
||||
}
|
||||
}
|
||||
QString accName = cnt->getAccountName();
|
||||
QString cntJID = cnt->getJid();
|
||||
QString cntName = cnt->getName();
|
||||
|
||||
QAction* rename = contextMenu->addAction(Shared::icon("edit-rename"), tr("Rename"));
|
||||
rename->setEnabled(active);
|
||||
connect(rename, &QAction::triggered, [this, cntName, accName, cntJID]() {
|
||||
connect(rename, &QAction::triggered, [this, cntName, id]() {
|
||||
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();
|
||||
if (newName != cntName) {
|
||||
emit renameContactRequest(accName, cntJID, newName);
|
||||
emit renameContactRequest(id.account, id.name, newName);
|
||||
}
|
||||
dialog->deleteLater();
|
||||
});
|
||||
connect(dialog, &QDialog::rejected, dialog, &QObject::deleteLater);
|
||||
dialog->setInputMode(QInputDialog::TextInput);
|
||||
dialog->setLabelText(tr("Input new name for %1\nor leave it empty for the contact \nto be displayed as %1").arg(cntJID));
|
||||
dialog->setWindowTitle(tr("Renaming %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(id.name));
|
||||
dialog->setTextValue(cntName);
|
||||
dialog->exec();
|
||||
});
|
||||
|
||||
|
||||
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) {
|
||||
QAction* gr = groupsMenu->addAction(groupName);
|
||||
gr->setCheckable(true);
|
||||
gr->setChecked(rosterModel.groupHasContact(accName, groupName, cntJID));
|
||||
gr->setChecked(rosterModel.groupHasContact(id.account, groupName, id.name));
|
||||
gr->setEnabled(active);
|
||||
connect(gr, &QAction::toggled, [this, accName, groupName, cntJID](bool checked) {
|
||||
connect(gr, &QAction::toggled, [this, groupName, id](bool checked) {
|
||||
if (checked) {
|
||||
emit addContactToGroupRequest(accName, cntJID, groupName);
|
||||
emit addContactToGroupRequest(id.account, id.name, groupName);
|
||||
} else {
|
||||
emit removeContactFromGroupRequest(accName, cntJID, groupName);
|
||||
emit removeContactFromGroupRequest(id.account, id.name, groupName);
|
||||
}
|
||||
});
|
||||
}
|
||||
QAction* newGroup = groupsMenu->addAction(Shared::icon("group-new"), tr("New group"));
|
||||
newGroup->setEnabled(active);
|
||||
connect(newGroup, &QAction::triggered, [this, accName, cntJID]() {
|
||||
connect(newGroup, &QAction::triggered, [this, id]() {
|
||||
QInputDialog* dialog = new QInputDialog(this);
|
||||
connect(dialog, &QDialog::accepted, [this, dialog, accName, cntJID]() {
|
||||
emit addContactToGroupRequest(accName, cntJID, dialog->textValue());
|
||||
connect(dialog, &QDialog::accepted, [this, dialog, id]() {
|
||||
emit addContactToGroupRequest(id.account, id.name, dialog->textValue());
|
||||
dialog->deleteLater();
|
||||
});
|
||||
connect(dialog, &QDialog::rejected, dialog, &QObject::deleteLater);
|
||||
dialog->setInputMode(QInputDialog::TextInput);
|
||||
dialog->setLabelText(tr("New group name"));
|
||||
dialog->setWindowTitle(tr("Add %1 to a new group").arg(cntJID));
|
||||
dialog->setWindowTitle(tr("Add %1 to a new group").arg(id.name));
|
||||
dialog->exec();
|
||||
});
|
||||
|
||||
|
||||
QAction* card = contextMenu->addAction(Shared::icon("user-properties"), tr("VCard"));
|
||||
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"));
|
||||
remove->setEnabled(active);
|
||||
connect(remove, &QAction::triggered, [this, cnt]() {
|
||||
emit removeContactRequest(cnt->getAccountName(), cnt->getJid());
|
||||
});
|
||||
connect(remove, &QAction::triggered, std::bind(&Squawk::removeContactRequest, this, id.account, id.name));
|
||||
|
||||
}
|
||||
break;
|
||||
@ -707,32 +433,16 @@ void Squawk::onRosterContextMenu(const QPoint& point)
|
||||
if (room->getAutoJoin()) {
|
||||
QAction* unsub = contextMenu->addAction(Shared::icon("news-unsubscribe"), tr("Unsubscribe"));
|
||||
unsub->setEnabled(active);
|
||||
connect(unsub, &QAction::triggered, [this, id]() {
|
||||
emit setRoomAutoJoin(id.account, id.name, false);
|
||||
if (conversations.find(id) == conversations.end()
|
||||
&& (currentConversation == 0 || currentConversation->getId() != id)
|
||||
) { //to leave the room if it's not opened in a conversation window
|
||||
emit setRoomJoined(id.account, id.name, false);
|
||||
}
|
||||
});
|
||||
connect(unsub, &QAction::triggered, std::bind(&Squawk::changeSubscription, this, id, false));
|
||||
} else {
|
||||
QAction* unsub = contextMenu->addAction(Shared::icon("news-subscribe"), tr("Subscribe"));
|
||||
unsub->setEnabled(active);
|
||||
connect(unsub, &QAction::triggered, [this, id]() {
|
||||
emit setRoomAutoJoin(id.account, id.name, true);
|
||||
if (conversations.find(id) == conversations.end()
|
||||
&& (currentConversation == 0 || currentConversation->getId() != id)
|
||||
) { //to join the room if it's not already joined
|
||||
emit setRoomJoined(id.account, id.name, true);
|
||||
}
|
||||
});
|
||||
QAction* sub = contextMenu->addAction(Shared::icon("news-subscribe"), tr("Subscribe"));
|
||||
sub->setEnabled(active);
|
||||
connect(sub, &QAction::triggered, std::bind(&Squawk::changeSubscription, this, id, true));
|
||||
}
|
||||
|
||||
QAction* remove = contextMenu->addAction(Shared::icon("edit-delete"), tr("Remove"));
|
||||
remove->setEnabled(active);
|
||||
connect(remove, &QAction::triggered, [this, id]() {
|
||||
emit removeRoomRequest(id.account, id.name);
|
||||
});
|
||||
connect(remove, &QAction::triggered, std::bind(&Squawk::removeRoomRequest, this, id.account, id.name));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
@ -744,36 +454,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)
|
||||
{
|
||||
std::map<QString, VCard*>::const_iterator itr = vCards.find(jid);
|
||||
@ -831,74 +511,40 @@ void Squawk::onVCardSave(const Shared::VCard& card, const QString& account)
|
||||
widget->deleteLater();
|
||||
}
|
||||
|
||||
void Squawk::readSettings()
|
||||
{
|
||||
QSettings settings;
|
||||
settings.beginGroup("ui");
|
||||
|
||||
if (settings.contains("availability")) {
|
||||
int avail = settings.value("availability").toInt();
|
||||
m_ui->comboBox->setCurrentIndex(avail);
|
||||
emit stateChanged(Shared::Global::fromInt<Shared::Availability>(avail));
|
||||
|
||||
int size = settings.beginReadArray("connectedAccounts");
|
||||
for (int i = 0; i < size; ++i) {
|
||||
settings.setArrayIndex(i);
|
||||
emit connectAccount(settings.value("name").toString()); //TODO this is actually not needed, stateChanged event already connects everything you have
|
||||
} // need to fix that
|
||||
settings.endArray();
|
||||
}
|
||||
settings.endGroup();
|
||||
}
|
||||
|
||||
void Squawk::writeSettings()
|
||||
{
|
||||
QSettings settings;
|
||||
settings.beginGroup("ui");
|
||||
settings.beginGroup("window");
|
||||
settings.setValue("geometry", saveGeometry());
|
||||
settings.setValue("state", saveState());
|
||||
settings.endGroup();
|
||||
|
||||
settings.setValue("splitter", m_ui->splitter->saveState());
|
||||
|
||||
settings.setValue("availability", m_ui->comboBox->currentIndex());
|
||||
settings.beginWriteArray("connectedAccounts");
|
||||
int size = rosterModel.accountsModel->rowCount(QModelIndex());
|
||||
for (int i = 0; i < size; ++i) {
|
||||
Models::Account* acc = rosterModel.accountsModel->getAccount(i);
|
||||
if (acc->getState() != Shared::ConnectionState::disconnected) {
|
||||
settings.setArrayIndex(i);
|
||||
settings.setValue("name", acc->getName());
|
||||
}
|
||||
}
|
||||
settings.endArray();
|
||||
|
||||
settings.remove("roster");
|
||||
settings.beginGroup("roster");
|
||||
for (int i = 0; i < size; ++i) {
|
||||
QModelIndex acc = rosterModel.index(i, 0, QModelIndex());
|
||||
Models::Account* account = rosterModel.accountsModel->getAccount(i);
|
||||
QString accName = account->getName();
|
||||
settings.beginGroup(accName);
|
||||
|
||||
settings.setValue("expanded", m_ui->roster->isExpanded(acc));
|
||||
std::deque<QString> groups = rosterModel.groupList(accName);
|
||||
for (const QString& groupName : groups) {
|
||||
settings.beginGroup(groupName);
|
||||
QModelIndex gIndex = rosterModel.getGroupIndex(accName, groupName);
|
||||
settings.setValue("expanded", m_ui->roster->isExpanded(gIndex));
|
||||
settings.endGroup();
|
||||
}
|
||||
|
||||
settings.beginGroup("window");
|
||||
settings.setValue("geometry", saveGeometry());
|
||||
settings.setValue("state", saveState());
|
||||
settings.endGroup();
|
||||
|
||||
settings.setValue("splitter", m_ui->splitter->saveState());
|
||||
settings.remove("roster");
|
||||
settings.beginGroup("roster");
|
||||
int size = rosterModel.accountsModel->rowCount(QModelIndex());
|
||||
for (int i = 0; i < size; ++i) {
|
||||
QModelIndex acc = rosterModel.index(i, 0, QModelIndex());
|
||||
Models::Account* account = rosterModel.accountsModel->getAccount(i);
|
||||
QString accName = account->getName();
|
||||
settings.beginGroup(accName);
|
||||
|
||||
settings.setValue("expanded", m_ui->roster->isExpanded(acc));
|
||||
std::deque<QString> groups = rosterModel.groupList(accName);
|
||||
for (const QString& groupName : groups) {
|
||||
settings.beginGroup(groupName);
|
||||
QModelIndex gIndex = rosterModel.getGroupIndex(accName, groupName);
|
||||
settings.setValue("expanded", m_ui->roster->isExpanded(gIndex));
|
||||
settings.endGroup();
|
||||
}
|
||||
|
||||
settings.endGroup();
|
||||
}
|
||||
settings.endGroup();
|
||||
}
|
||||
settings.endGroup();
|
||||
settings.endGroup();
|
||||
|
||||
settings.sync();
|
||||
|
||||
qDebug() << "Saved settings";
|
||||
}
|
||||
|
||||
void Squawk::onItemCollepsed(const QModelIndex& index)
|
||||
@ -920,60 +566,6 @@ void Squawk::onItemCollepsed(const QModelIndex& index)
|
||||
}
|
||||
}
|
||||
|
||||
void Squawk::requestPassword(const QString& account)
|
||||
{
|
||||
requestedAccountsForPasswords.push_back(account);
|
||||
checkNextAccountForPassword();
|
||||
}
|
||||
|
||||
void Squawk::checkNextAccountForPassword()
|
||||
{
|
||||
if (prompt == 0 && requestedAccountsForPasswords.size() > 0) {
|
||||
prompt = new QInputDialog(this);
|
||||
QString accName = requestedAccountsForPasswords.front();
|
||||
connect(prompt, &QDialog::accepted, this, &Squawk::onPasswordPromptAccepted);
|
||||
connect(prompt, &QDialog::rejected, this, &Squawk::onPasswordPromptRejected);
|
||||
prompt->setInputMode(QInputDialog::TextInput);
|
||||
prompt->setTextEchoMode(QLineEdit::Password);
|
||||
prompt->setLabelText(tr("Input the password for account %1").arg(accName));
|
||||
prompt->setWindowTitle(tr("Password for account %1").arg(accName));
|
||||
prompt->setTextValue("");
|
||||
prompt->exec();
|
||||
}
|
||||
}
|
||||
|
||||
void Squawk::onPasswordPromptAccepted()
|
||||
{
|
||||
emit responsePassword(requestedAccountsForPasswords.front(), prompt->textValue());
|
||||
onPasswordPromptDone();
|
||||
}
|
||||
|
||||
void Squawk::onPasswordPromptDone()
|
||||
{
|
||||
prompt->deleteLater();
|
||||
prompt = 0;
|
||||
requestedAccountsForPasswords.pop_front();
|
||||
checkNextAccountForPassword();
|
||||
}
|
||||
|
||||
void Squawk::onPasswordPromptRejected()
|
||||
{
|
||||
//for now it's the same on reject and on accept, but one day I'm gonna make
|
||||
//"Asking for the password again on the authentication failure" feature
|
||||
//and here I'll be able to break the circle of password requests
|
||||
emit responsePassword(requestedAccountsForPasswords.front(), prompt->textValue());
|
||||
onPasswordPromptDone();
|
||||
}
|
||||
|
||||
void Squawk::subscribeConversation(Conversation* conv)
|
||||
{
|
||||
connect(conv, &Conversation::destroyed, this, &Squawk::onConversationClosed);
|
||||
connect(conv, &Conversation::sendMessage, this, &Squawk::onConversationMessage);
|
||||
connect(conv, &Conversation::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)
|
||||
{
|
||||
if (restoreSelection.isValid() && restoreSelection == current) {
|
||||
@ -986,10 +578,10 @@ void Squawk::onRosterSelectionChanged(const QModelIndex& current, const QModelIn
|
||||
if (node->type == Models::Item::reference) {
|
||||
node = static_cast<Models::Reference*>(node)->dereference();
|
||||
}
|
||||
Models::Contact* contact = 0;
|
||||
Models::Room* room = 0;
|
||||
Models::Contact* contact = nullptr;
|
||||
Models::Room* room = nullptr;
|
||||
QString res;
|
||||
Models::Roster::ElId* id = 0;
|
||||
Models::Roster::ElId* id = nullptr;
|
||||
bool hasContext = true;
|
||||
switch (node->type) {
|
||||
case Models::Item::contact:
|
||||
@ -1018,7 +610,7 @@ void Squawk::onRosterSelectionChanged(const QModelIndex& current, const QModelIn
|
||||
}
|
||||
|
||||
if (hasContext && QGuiApplication::mouseButtons() & Qt::RightButton) {
|
||||
if (id != 0) {
|
||||
if (id != nullptr) {
|
||||
delete id;
|
||||
}
|
||||
needToRestore = true;
|
||||
@ -1026,10 +618,10 @@ void Squawk::onRosterSelectionChanged(const QModelIndex& current, const QModelIn
|
||||
return;
|
||||
}
|
||||
|
||||
if (id != 0) {
|
||||
if (currentConversation != 0) {
|
||||
if (id != nullptr) {
|
||||
if (currentConversation != nullptr) {
|
||||
if (currentConversation->getId() == *id) {
|
||||
if (contact != 0) {
|
||||
if (contact != nullptr) {
|
||||
currentConversation->setPalResource(res);
|
||||
}
|
||||
return;
|
||||
@ -1041,20 +633,16 @@ void Squawk::onRosterSelectionChanged(const QModelIndex& current, const QModelIn
|
||||
}
|
||||
|
||||
Models::Account* acc = rosterModel.getAccount(id->account);
|
||||
if (contact != 0) {
|
||||
if (contact != nullptr) {
|
||||
currentConversation = new Chat(acc, contact);
|
||||
} else if (room != 0) {
|
||||
} else if (room != nullptr) {
|
||||
currentConversation = new Room(acc, room);
|
||||
|
||||
if (!room->getJoined()) {
|
||||
emit setRoomJoined(id->account, id->name, true);
|
||||
}
|
||||
}
|
||||
if (!testAttribute(Qt::WA_TranslucentBackground)) {
|
||||
currentConversation->setFeedFrames(true, false, true, true);
|
||||
}
|
||||
|
||||
subscribeConversation(currentConversation);
|
||||
emit openedConversation();
|
||||
|
||||
if (res.size() > 0) {
|
||||
currentConversation->setPalResource(res);
|
||||
@ -1064,18 +652,10 @@ void Squawk::onRosterSelectionChanged(const QModelIndex& current, const QModelIn
|
||||
|
||||
delete id;
|
||||
} else {
|
||||
if (currentConversation != 0) {
|
||||
currentConversation->deleteLater();
|
||||
currentConversation = 0;
|
||||
m_ui->filler->show();
|
||||
}
|
||||
closeCurrentConversation();
|
||||
}
|
||||
} else {
|
||||
if (currentConversation != 0) {
|
||||
currentConversation->deleteLater();
|
||||
currentConversation = 0;
|
||||
m_ui->filler->show();
|
||||
}
|
||||
closeCurrentConversation();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1086,3 +666,31 @@ void Squawk::onContextAboutToHide()
|
||||
m_ui->roster->selectionModel()->setCurrentIndex(restoreSelection, QItemSelectionModel::ClearAndSelect);
|
||||
}
|
||||
}
|
||||
|
||||
void Squawk::onAboutSquawkCalled()
|
||||
{
|
||||
if (about == nullptr) {
|
||||
about = new About();
|
||||
about->setAttribute(Qt::WA_DeleteOnClose);
|
||||
connect(about, &Settings::destroyed, this, &Squawk::onAboutSquawkClosed);
|
||||
} else {
|
||||
about->raise();
|
||||
about->activateWindow();
|
||||
}
|
||||
about->show();
|
||||
}
|
||||
|
||||
Models::Roster::ElId Squawk::currentConversationId() const
|
||||
{
|
||||
if (currentConversation == nullptr) {
|
||||
return Models::Roster::ElId();
|
||||
} else {
|
||||
return Models::Roster::ElId(currentConversation->getAccount(), currentConversation->getJid());
|
||||
}
|
||||
}
|
||||
|
||||
void Squawk::select(QModelIndex index)
|
||||
{
|
||||
m_ui->roster->scrollTo(index, QAbstractItemView::EnsureVisible);
|
||||
m_ui->roster->selectionModel()->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect);
|
||||
}
|
||||
|
94
ui/squawk.h
94
ui/squawk.h
@ -22,16 +22,16 @@
|
||||
#include <QMainWindow>
|
||||
#include <QScopedPointer>
|
||||
#include <QCloseEvent>
|
||||
#include <QtDBus/QDBusInterface>
|
||||
#include <QSettings>
|
||||
#include <QInputDialog>
|
||||
#include <QSystemTrayIcon>
|
||||
|
||||
#include <deque>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <list>
|
||||
|
||||
#include "widgets/accounts.h"
|
||||
#include "widgets/accounts/accounts.h"
|
||||
#include "widgets/chat.h"
|
||||
#include "widgets/room.h"
|
||||
#include "widgets/newcontact.h"
|
||||
@ -39,103 +39,79 @@
|
||||
#include "models/roster.h"
|
||||
#include "widgets/vcard/vcard.h"
|
||||
#include "widgets/settings/settings.h"
|
||||
#include "widgets/about.h"
|
||||
|
||||
#include "shared/shared.h"
|
||||
#include "shared/global.h"
|
||||
|
||||
namespace Ui {
|
||||
class Squawk;
|
||||
}
|
||||
|
||||
class Application;
|
||||
|
||||
class Squawk : public QMainWindow
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
friend class Application;
|
||||
public:
|
||||
explicit Squawk(QWidget *parent = nullptr);
|
||||
explicit Squawk(Models::Roster& rosterModel, QWidget *parent = nullptr);
|
||||
~Squawk() override;
|
||||
|
||||
signals:
|
||||
void closing();
|
||||
void newAccountRequest(const QMap<QString, QVariant>&);
|
||||
void modifyAccountRequest(const QString&, const QMap<QString, QVariant>&);
|
||||
void removeAccountRequest(const QString&);
|
||||
void connectAccount(const QString&);
|
||||
void disconnectAccount(const QString&);
|
||||
void changeState(Shared::Availability state);
|
||||
void sendMessage(const QString& account, const Shared::Message& data);
|
||||
void 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 addContactRequest(const QString& account, const QString& jid, const QString& name, const QSet<QString>& groups);
|
||||
void addContactToGroupRequest(const QString& account, const QString& jid, const QString& groupName);
|
||||
void removeContactFromGroupRequest(const QString& account, const QString& jid, const QString& groupName);
|
||||
void renameContactRequest(const QString& account, const QString& jid, const QString& newName);
|
||||
void setRoomJoined(const QString& account, const QString& jid, bool joined);
|
||||
void setRoomAutoJoin(const QString& account, const QString& jid, bool joined);
|
||||
void addRoomRequest(const QString& account, const QString& jid, const QString& nick, const QString& password, bool autoJoin);
|
||||
void removeRoomRequest(const QString& account, const QString& jid);
|
||||
void fileDownloadRequest(const QString& url);
|
||||
void requestVCard(const QString& account, const QString& jid);
|
||||
void uploadVCard(const QString& account, const Shared::VCard& card);
|
||||
void responsePassword(const QString& account, const QString& password);
|
||||
void localPathInvalid(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();
|
||||
static QSystemTrayIcon *trayIcon;
|
||||
|
||||
public slots:
|
||||
void writeSettings();
|
||||
void readSettings();
|
||||
void newAccount(const QMap<QString, QVariant>& account);
|
||||
void changeAccount(const QString& account, const QMap<QString, QVariant>& data);
|
||||
void removeAccount(const QString& account);
|
||||
void addGroup(const QString& account, const QString& name);
|
||||
void removeGroup(const QString& account, const QString& name);
|
||||
void addContact(const QString& account, const QString& jid, const QString& group, const QMap<QString, QVariant>& data);
|
||||
void removeContact(const QString& account, const QString& jid, const QString& group);
|
||||
void removeContact(const QString& account, const QString& jid);
|
||||
void changeContact(const QString& account, const QString& jid, const QMap<QString, QVariant>& data);
|
||||
void addPresence(const QString& account, const QString& jid, const QString& name, const QMap<QString, QVariant>& data);
|
||||
void removePresence(const QString& account, const QString& jid, const QString& name);
|
||||
void stateChanged(Shared::Availability state);
|
||||
void accountMessage(const QString& account, const Shared::Message& data);
|
||||
void responseArchive(const QString& account, const QString& jid, const std::list<Shared::Message>& list, bool last);
|
||||
void addRoom(const QString& account, const QString jid, const QMap<QString, QVariant>& data);
|
||||
void changeRoom(const QString& account, const QString jid, const QMap<QString, QVariant>& data);
|
||||
void removeRoom(const QString& account, const QString jid);
|
||||
void addRoomParticipant(const QString& account, const QString& jid, const QString& name, const QMap<QString, QVariant>& data);
|
||||
void changeRoomParticipant(const QString& account, const QString& jid, const QString& name, const QMap<QString, QVariant>& data);
|
||||
void removeRoomParticipant(const QString& account, const QString& jid, const QString& name);
|
||||
void fileError(const std::list<Shared::MessageInfo> msgs, const QString& error, bool up);
|
||||
void fileProgress(const std::list<Shared::MessageInfo> msgs, qreal value, bool up);
|
||||
void fileDownloadComplete(const std::list<Shared::MessageInfo> msgs, const QString& path);
|
||||
void fileUploadComplete(const std::list<Shared::MessageInfo> msgs, const QString& url, const QString& path);
|
||||
void responseVCard(const QString& jid, const Shared::VCard& card);
|
||||
void changeMessage(const QString& account, const QString& jid, const QString& id, const QMap<QString, QVariant>& data);
|
||||
void requestPassword(const QString& account);
|
||||
void select(QModelIndex index);
|
||||
|
||||
private:
|
||||
typedef std::map<Models::Roster::ElId, Conversation*> Conversations;
|
||||
void createTrayIcon();
|
||||
|
||||
QScopedPointer<Ui::Squawk> m_ui;
|
||||
|
||||
Accounts* accounts;
|
||||
Settings* preferences;
|
||||
Models::Roster rosterModel;
|
||||
Conversations conversations;
|
||||
About* about;
|
||||
Models::Roster& rosterModel;
|
||||
QMenu* contextMenu;
|
||||
QDBusInterface dbus;
|
||||
std::map<QString, VCard*> vCards;
|
||||
std::deque<QString> requestedAccountsForPasswords;
|
||||
QInputDialog* prompt;
|
||||
Conversation* currentConversation;
|
||||
QModelIndex restoreSelection;
|
||||
bool needToRestore;
|
||||
|
||||
protected:
|
||||
void closeEvent(QCloseEvent * event) override;
|
||||
|
||||
protected slots:
|
||||
void notify(const QString& account, const Shared::Message& msg);
|
||||
void expand(const QModelIndex& index);
|
||||
|
||||
private slots:
|
||||
void onAccounts();
|
||||
@ -147,29 +123,17 @@ private slots:
|
||||
void onAccountsSizeChanged(unsigned int size);
|
||||
void onAccountsClosed();
|
||||
void onPreferencesClosed();
|
||||
void onConversationClosed(QObject* parent = 0);
|
||||
void onVCardClosed();
|
||||
void onVCardSave(const Shared::VCard& card, const QString& account);
|
||||
void onActivateVCard(const QString& account, const QString& jid, bool edition = false);
|
||||
void onComboboxActivated(int index);
|
||||
void onRosterItemDoubleClicked(const QModelIndex& item);
|
||||
void onConversationMessage(const Shared::Message& msg);
|
||||
void 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 onItemCollepsed(const QModelIndex& index);
|
||||
void onPasswordPromptAccepted();
|
||||
void onPasswordPromptRejected();
|
||||
void onRosterSelectionChanged(const QModelIndex& current, const QModelIndex& previous);
|
||||
void onContextAboutToHide();
|
||||
|
||||
void onUnnoticedMessage(const QString& account, const Shared::Message& msg);
|
||||
|
||||
private:
|
||||
void checkNextAccountForPassword();
|
||||
void onPasswordPromptDone();
|
||||
void subscribeConversation(Conversation* conv);
|
||||
void onAboutSquawkCalled();
|
||||
void onAboutSquawkClosed();
|
||||
};
|
||||
|
||||
#endif // SQUAWK_H
|
||||
|
15
ui/squawk.ui
15
ui/squawk.ui
@ -201,8 +201,15 @@
|
||||
<addaction name="actionAddConference"/>
|
||||
<addaction name="actionQuit"/>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menuHelp">
|
||||
<property name="title">
|
||||
<string>Help</string>
|
||||
</property>
|
||||
<addaction name="actionAboutSquawk"/>
|
||||
</widget>
|
||||
<addaction name="menuFile"/>
|
||||
<addaction name="menuSettings"/>
|
||||
<addaction name="menuHelp"/>
|
||||
</widget>
|
||||
<action name="actionAccounts">
|
||||
<property name="icon">
|
||||
@ -248,12 +255,18 @@
|
||||
</action>
|
||||
<action name="actionPreferences">
|
||||
<property name="icon">
|
||||
<iconset theme="settings-configure"/>
|
||||
<iconset theme="settings-configure">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Preferences</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionAboutSquawk">
|
||||
<property name="text">
|
||||
<string>About Squawk</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<resources>
|
||||
<include location="../resources/resources.qrc"/>
|
||||
|
@ -1,10 +1,4 @@
|
||||
target_sources(squawk PRIVATE
|
||||
account.cpp
|
||||
account.h
|
||||
account.ui
|
||||
accounts.cpp
|
||||
accounts.h
|
||||
accounts.ui
|
||||
chat.cpp
|
||||
chat.h
|
||||
conversation.cpp
|
||||
@ -18,8 +12,12 @@ target_sources(squawk PRIVATE
|
||||
newcontact.ui
|
||||
room.cpp
|
||||
room.h
|
||||
about.cpp
|
||||
about.h
|
||||
about.ui
|
||||
)
|
||||
|
||||
add_subdirectory(vcard)
|
||||
add_subdirectory(messageline)
|
||||
add_subdirectory(settings)
|
||||
add_subdirectory(accounts)
|
||||
|
108
ui/widgets/about.cpp
Normal file
108
ui/widgets/about.cpp
Normal file
@ -0,0 +1,108 @@
|
||||
// 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 "about.h"
|
||||
#include "ui_about.h"
|
||||
#include <QXmppGlobal.h>
|
||||
#include <QDebug>
|
||||
|
||||
static const std::string QXMPP_VERSION_PATCH(std::to_string(QXMPP_VERSION & 0xff));
|
||||
static const std::string QXMPP_VERSION_MINOR(std::to_string((QXMPP_VERSION & 0xff00) >> 8));
|
||||
static const std::string QXMPP_VERSION_MAJOR(std::to_string(QXMPP_VERSION >> 16));
|
||||
static const QString QXMPP_VERSION_STRING = QString::fromStdString(QXMPP_VERSION_MAJOR + "." + QXMPP_VERSION_MINOR + "." + QXMPP_VERSION_PATCH);
|
||||
|
||||
About::About(QWidget* parent):
|
||||
QWidget(parent),
|
||||
m_ui(new Ui::About),
|
||||
license(nullptr)
|
||||
{
|
||||
m_ui->setupUi(this);
|
||||
m_ui->versionValue->setText(QApplication::applicationVersion());
|
||||
m_ui->qtVersionValue->setText(qVersion());
|
||||
m_ui->qtBuiltAgainstVersion->setText(tr("(built against %1)").arg(QT_VERSION_STR));
|
||||
|
||||
m_ui->qxmppVersionValue->setText(QXmppVersion());
|
||||
m_ui->qxmppBuiltAgainstVersion->setText(tr("(built against %1)").arg(QXMPP_VERSION_STRING));
|
||||
|
||||
setWindowFlag(Qt::Tool);
|
||||
|
||||
connect(m_ui->licenceLink, &QLabel::linkActivated, this, &About::onLicenseActivated);
|
||||
}
|
||||
|
||||
About::~About() {
|
||||
if (license != nullptr) {
|
||||
license->deleteLater();
|
||||
}
|
||||
};
|
||||
|
||||
void About::onLicenseActivated()
|
||||
{
|
||||
if (license == nullptr) {
|
||||
QFile file;
|
||||
bool found = false;
|
||||
QStringList shares = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation);
|
||||
for (const QString& path : shares) {
|
||||
file.setFileName(path + "/LICENSE.md");
|
||||
|
||||
if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
qDebug() << "couldn't read license file, bailing";
|
||||
return;
|
||||
}
|
||||
|
||||
license = new QWidget();
|
||||
license->setWindowTitle(tr("License"));
|
||||
QVBoxLayout* layout = new QVBoxLayout(license);
|
||||
QLabel* text = new QLabel(license);
|
||||
QScrollArea* area = new QScrollArea(license);
|
||||
text->setTextFormat(Qt::MarkdownText);
|
||||
text->setWordWrap(true);
|
||||
text->setOpenExternalLinks(true);
|
||||
text->setMargin(5);
|
||||
area->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
|
||||
|
||||
layout->addWidget(area);
|
||||
license->setAttribute(Qt::WA_DeleteOnClose);
|
||||
connect(license, &QWidget::destroyed, this, &About::onLicenseClosed);
|
||||
|
||||
QTextStream in(&file);
|
||||
QString line;
|
||||
QString licenseText("");
|
||||
while (!in.atEnd()) {
|
||||
line = in.readLine();
|
||||
licenseText.append(line + "\n");
|
||||
}
|
||||
text->setText(licenseText);
|
||||
file.close();
|
||||
|
||||
area->setWidget(text);
|
||||
|
||||
} else {
|
||||
license->raise();
|
||||
license->activateWindow();
|
||||
}
|
||||
|
||||
license->show();
|
||||
}
|
||||
|
||||
void About::onLicenseClosed()
|
||||
{
|
||||
license = nullptr;
|
||||
}
|
51
ui/widgets/about.h
Normal file
51
ui/widgets/about.h
Normal file
@ -0,0 +1,51 @@
|
||||
// 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 ABOUT_H
|
||||
#define ABOUT_H
|
||||
|
||||
#include <QWidget>
|
||||
#include <QScopedPointer>
|
||||
#include <QApplication>
|
||||
#include <QFile>
|
||||
#include <QTextStream>
|
||||
#include <QStandardPaths>
|
||||
|
||||
namespace Ui
|
||||
{
|
||||
class About;
|
||||
}
|
||||
|
||||
/**
|
||||
* @todo write docs
|
||||
*/
|
||||
class About : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
About(QWidget* parent = nullptr);
|
||||
~About();
|
||||
|
||||
protected slots:
|
||||
void onLicenseActivated();
|
||||
void onLicenseClosed();
|
||||
|
||||
private:
|
||||
QScopedPointer<Ui::About> m_ui;
|
||||
QWidget* license;
|
||||
};
|
||||
|
||||
#endif // ABOUT_H
|
680
ui/widgets/about.ui
Normal file
680
ui/widgets/about.ui
Normal file
@ -0,0 +1,680 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>About</class>
|
||||
<widget class="QWidget" name="About">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>375</width>
|
||||
<height>290</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>About Squawk</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<property name="verticalSpacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item row="2" column="0" colspan="4">
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Fixed</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>10</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="0" column="1" colspan="2">
|
||||
<widget class="QLabel" name="header">
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>12</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Squawk</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0" rowspan="2">
|
||||
<widget class="QLabel" name="squawkIcon">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>50</width>
|
||||
<height>50</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="pixmap">
|
||||
<pixmap resource="../../resources/resources.qrc">:/images/logo.svg</pixmap>
|
||||
</property>
|
||||
<property name="scaledContents">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="3" rowspan="2">
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="4" column="0" colspan="4">
|
||||
<widget class="QTabWidget" name="tabs">
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="usesScrollButtons">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<widget class="QWidget" name="aboutTab">
|
||||
<attribute name="title">
|
||||
<string>About</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<spacer name="verticalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="description">
|
||||
<property name="text">
|
||||
<string>XMPP (jabber) messenger</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="copyright">
|
||||
<property name="text">
|
||||
<string>(c) 2019 - 2022, Yury Gubich</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="siteLink">
|
||||
<property name="text">
|
||||
<string><a href="https://git.macaw.me/blue/squawk">Project site</a></string>
|
||||
</property>
|
||||
<property name="textFormat">
|
||||
<enum>Qt::RichText</enum>
|
||||
</property>
|
||||
<property name="openExternalLinks">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="licenceLink">
|
||||
<property name="text">
|
||||
<string><a href="https://git.macaw.me/blue/squawk/src/branch/master/LICENSE.md">License: GNU General Public License version 3</a></string>
|
||||
</property>
|
||||
<property name="textFormat">
|
||||
<enum>Qt::RichText</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_3">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QScrollArea" name="componentsTab">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::NoFrame</enum>
|
||||
</property>
|
||||
<property name="horizontalScrollBarPolicy">
|
||||
<enum>Qt::ScrollBarAlwaysOff</enum>
|
||||
</property>
|
||||
<property name="widgetResizable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<attribute name="title">
|
||||
<string>Components</string>
|
||||
</attribute>
|
||||
<widget class="QWidget" name="scrollAreaWidgetContents">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>355</width>
|
||||
<height>181</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_1">
|
||||
<item>
|
||||
<widget class="QWidget" name="widget" native="true">
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="horizontalSpacing">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<property name="verticalSpacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="font">
|
||||
<font>
|
||||
<italic>true</italic>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Version</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLabel" name="qtVersionValue">
|
||||
<property name="font">
|
||||
<font>
|
||||
<italic>true</italic>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>0.0.0</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<widget class="QLabel" name="qtBuiltAgainstVersion">
|
||||
<property name="font">
|
||||
<font>
|
||||
<italic>true</italic>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string notr="true">(built against 0.0.0)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0" colspan="3">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="font">
|
||||
<font>
|
||||
<weight>75</weight>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string notr="true">Qt</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="3" rowspan="2">
|
||||
<spacer name="horizontalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="2" column="0" colspan="4">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string notr="true"><a href="https://www.qt.io/">www.qt.io</a></string>
|
||||
</property>
|
||||
<property name="openExternalLinks">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QWidget" name="widget_2" native="true">
|
||||
<layout class="QGridLayout" name="gridLayout_3">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="horizontalSpacing">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<property name="verticalSpacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="font">
|
||||
<font>
|
||||
<italic>true</italic>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Version</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLabel" name="qxmppVersionValue">
|
||||
<property name="font">
|
||||
<font>
|
||||
<italic>true</italic>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>0.0.0</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<widget class="QLabel" name="qxmppBuiltAgainstVersion">
|
||||
<property name="font">
|
||||
<font>
|
||||
<italic>true</italic>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string notr="true">(built against 0.0.0)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0" colspan="3">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="font">
|
||||
<font>
|
||||
<weight>75</weight>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string notr="true">QXmpp</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="3" rowspan="2">
|
||||
<spacer name="horizontalSpacer_3">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="2" column="0" colspan="4">
|
||||
<widget class="QLabel" name="label_6">
|
||||
<property name="text">
|
||||
<string notr="true"><a href="https://github.com/qxmpp-project/qxmpp">github.com/qxmpp-project/qxmpp</a></string>
|
||||
</property>
|
||||
<property name="openExternalLinks">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_4">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
<widget class="QWidget" name="reportTab">
|
||||
<attribute name="title">
|
||||
<string>Report Bugs</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<spacer name="verticalSpacer_6">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_8">
|
||||
<property name="text">
|
||||
<string>Please report any bug you find!
|
||||
To report bugs you can use:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_7">
|
||||
<property name="text">
|
||||
<string><a href="https://git.macaw.me/blue/squawk/issues">Project bug tracker</></string>
|
||||
</property>
|
||||
<property name="openExternalLinks">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_10">
|
||||
<property name="text">
|
||||
<string>XMPP (<a href="xmpp:blue@macaw.me">blue@macaw.me</a>)</string>
|
||||
</property>
|
||||
<property name="openExternalLinks">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_9">
|
||||
<property name="text">
|
||||
<string>E-Mail (<a href="mailto:blue@macaw.me">blue@macaw.me</a>)</string>
|
||||
</property>
|
||||
<property name="openExternalLinks">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_5">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QScrollArea" name="thanksTab">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::NoFrame</enum>
|
||||
</property>
|
||||
<property name="horizontalScrollBarPolicy">
|
||||
<enum>Qt::ScrollBarAlwaysOff</enum>
|
||||
</property>
|
||||
<property name="widgetResizable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<attribute name="title">
|
||||
<string>Thanks To</string>
|
||||
</attribute>
|
||||
<widget class="QWidget" name="thanksTabContent">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>355</width>
|
||||
<height>181</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<property name="spacing">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QWidget" name="widget_3" native="true">
|
||||
<layout class="QGridLayout" name="gridLayout_4">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_11">
|
||||
<property name="font">
|
||||
<font>
|
||||
<weight>75</weight>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Vae</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_12">
|
||||
<property name="font">
|
||||
<font>
|
||||
<italic>true</italic>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Major refactoring, bug fixes, constructive criticism</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QWidget" name="widget_4" native="true">
|
||||
<layout class="QGridLayout" name="gridLayout_5">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_13">
|
||||
<property name="font">
|
||||
<font>
|
||||
<weight>75</weight>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Shunf4</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_14">
|
||||
<property name="font">
|
||||
<font>
|
||||
<italic>true</italic>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Major refactoring, bug fixes, build adaptations for Windows and MacOS</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QWidget" name="widget_5" native="true">
|
||||
<layout class="QGridLayout" name="gridLayout_6">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_15">
|
||||
<property name="font">
|
||||
<font>
|
||||
<weight>75</weight>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Bruno F. Fontes</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_16">
|
||||
<property name="font">
|
||||
<font>
|
||||
<italic>true</italic>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Brazilian Portuguese translation</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_7">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLabel" name="versionLabel">
|
||||
<property name="text">
|
||||
<string>Version</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<widget class="QLabel" name="versionValue">
|
||||
<property name="text">
|
||||
<string>0.0.0</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources>
|
||||
<include location="../../resources/resources.qrc"/>
|
||||
</resources>
|
||||
<connections/>
|
||||
</ui>
|
11
ui/widgets/accounts/CMakeLists.txt
Normal file
11
ui/widgets/accounts/CMakeLists.txt
Normal file
@ -0,0 +1,11 @@
|
||||
target_sources(squawk PRIVATE
|
||||
account.cpp
|
||||
account.h
|
||||
account.ui
|
||||
accounts.cpp
|
||||
accounts.h
|
||||
accounts.ui
|
||||
credentialsprompt.cpp
|
||||
credentialsprompt.h
|
||||
credentialsprompt.ui
|
||||
)
|
@ -53,6 +53,7 @@ QMap<QString, QVariant> Account::value() const
|
||||
map["name"] = m_ui->name->text();
|
||||
map["resource"] = m_ui->resource->text();
|
||||
map["passwordType"] = m_ui->passwordType->currentIndex();
|
||||
map["active"] = m_ui->active->isChecked();
|
||||
|
||||
return map;
|
||||
}
|
@ -7,7 +7,7 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>438</width>
|
||||
<height>342</height>
|
||||
<height>345</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
@ -34,7 +34,7 @@
|
||||
<property name="verticalSpacing">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<item row="1" column="1">
|
||||
<item row="2" column="1">
|
||||
<widget class="QLineEdit" name="login">
|
||||
<property name="toolTip">
|
||||
<string>Your account login</string>
|
||||
@ -44,14 +44,14 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Server</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<item row="3" column="1">
|
||||
<widget class="QLineEdit" name="server">
|
||||
<property name="toolTip">
|
||||
<string>A server address of your account. Like 404.city or macaw.me</string>
|
||||
@ -61,21 +61,21 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Login</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Password</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<item row="4" column="1">
|
||||
<widget class="QLineEdit" name="password">
|
||||
<property name="toolTip">
|
||||
<string>Password of your account</string>
|
||||
@ -97,14 +97,14 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>Name</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="name">
|
||||
<property name="toolTip">
|
||||
<string>Just a name how would you call this account, doesn't affect anything</string>
|
||||
@ -114,14 +114,14 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<item row="7" column="0">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string>Resource</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="1">
|
||||
<item row="7" column="1">
|
||||
<widget class="QLineEdit" name="resource">
|
||||
<property name="toolTip">
|
||||
<string>A resource name like "Home" or "Work"</string>
|
||||
@ -131,17 +131,17 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="label_6">
|
||||
<property name="text">
|
||||
<string>Password storage</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<item row="5" column="1">
|
||||
<widget class="QComboBox" name="passwordType"/>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<item row="6" column="1">
|
||||
<widget class="QLabel" name="comment">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
@ -157,6 +157,23 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_7">
|
||||
<property name="text">
|
||||
<string>Active</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QCheckBox" name="active">
|
||||
<property name="text">
|
||||
<string>enable</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
@ -83,7 +83,8 @@ void Accounts::onEditButton()
|
||||
{"server", mAcc->getServer()},
|
||||
{"name", mAcc->getName()},
|
||||
{"resource", mAcc->getResource()},
|
||||
{"passwordType", QVariant::fromValue(mAcc->getPasswordType())}
|
||||
{"passwordType", QVariant::fromValue(mAcc->getPasswordType())},
|
||||
{"active", mAcc->getActive()}
|
||||
});
|
||||
acc->lockId();
|
||||
connect(acc, &Account::accepted, this, &Accounts::onAccountAccepted);
|
||||
@ -118,17 +119,17 @@ void Accounts::updateConnectButton()
|
||||
bool allConnected = true;
|
||||
for (int i = 0; i < selectionSize && allConnected; ++i) {
|
||||
const Models::Account* mAcc = model->getAccount(sm->selectedRows().at(i).row());
|
||||
allConnected = mAcc->getState() == Shared::ConnectionState::connected;
|
||||
allConnected = allConnected && mAcc->getActive();
|
||||
}
|
||||
if (allConnected) {
|
||||
toDisconnect = true;
|
||||
m_ui->connectButton->setText(tr("Disconnect"));
|
||||
m_ui->connectButton->setText(tr("Deactivate"));
|
||||
} else {
|
||||
toDisconnect = false;
|
||||
m_ui->connectButton->setText(tr("Connect"));
|
||||
m_ui->connectButton->setText(tr("Activate"));
|
||||
}
|
||||
} else {
|
||||
m_ui->connectButton->setText(tr("Connect"));
|
||||
m_ui->connectButton->setText(tr("Activate"));
|
||||
toDisconnect = false;
|
||||
m_ui->connectButton->setEnabled(false);
|
||||
}
|
@ -24,7 +24,7 @@
|
||||
#include <QItemSelection>
|
||||
|
||||
#include "account.h"
|
||||
#include "../models/accounts.h"
|
||||
#include "ui/models/accounts.h"
|
||||
|
||||
namespace Ui
|
||||
{
|
60
ui/widgets/accounts/credentialsprompt.cpp
Normal file
60
ui/widgets/accounts/credentialsprompt.cpp
Normal file
@ -0,0 +1,60 @@
|
||||
// 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 "credentialsprompt.h"
|
||||
#include "ui_credentialsprompt.h"
|
||||
|
||||
CredentialsPrompt::CredentialsPrompt(QWidget* parent):
|
||||
QDialog(parent),
|
||||
m_ui(new Ui::CredentialsPrompt),
|
||||
title(),
|
||||
message()
|
||||
{
|
||||
m_ui->setupUi(this);
|
||||
|
||||
title = windowTitle();
|
||||
message = m_ui->message->text();
|
||||
}
|
||||
|
||||
CredentialsPrompt::~CredentialsPrompt()
|
||||
{
|
||||
}
|
||||
|
||||
void CredentialsPrompt::setAccount(const QString& account)
|
||||
{
|
||||
m_ui->message->setText(message.arg(account));
|
||||
setWindowTitle(title.arg(account));
|
||||
}
|
||||
|
||||
QString CredentialsPrompt::getLogin() const
|
||||
{
|
||||
return m_ui->login->text();
|
||||
}
|
||||
|
||||
QString CredentialsPrompt::getPassword() const
|
||||
{
|
||||
return m_ui->password->text();
|
||||
}
|
||||
|
||||
void CredentialsPrompt::setLogin(const QString& login)
|
||||
{
|
||||
m_ui->login->setText(login);
|
||||
}
|
||||
|
||||
void CredentialsPrompt::setPassword(const QString& password)
|
||||
{
|
||||
m_ui->password->setText(password);
|
||||
}
|
52
ui/widgets/accounts/credentialsprompt.h
Normal file
52
ui/widgets/accounts/credentialsprompt.h
Normal file
@ -0,0 +1,52 @@
|
||||
// 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 CREDENTIALSPROMPT_H
|
||||
#define CREDENTIALSPROMPT_H
|
||||
|
||||
#include <qdialog.h>
|
||||
#include <QScopedPointer>
|
||||
|
||||
namespace Ui
|
||||
{
|
||||
class CredentialsPrompt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @todo write docs
|
||||
*/
|
||||
class CredentialsPrompt : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
CredentialsPrompt(QWidget* parent = nullptr);
|
||||
~CredentialsPrompt();
|
||||
|
||||
void setAccount(const QString& account);
|
||||
void setLogin(const QString& login);
|
||||
void setPassword(const QString& password);
|
||||
|
||||
QString getLogin() const;
|
||||
QString getPassword() const;
|
||||
|
||||
private:
|
||||
QScopedPointer<Ui::CredentialsPrompt> m_ui;
|
||||
QString title;
|
||||
QString message;
|
||||
};
|
||||
|
||||
#endif // CREDENTIALSPROMPT_H
|
144
ui/widgets/accounts/credentialsprompt.ui
Normal file
144
ui/widgets/accounts/credentialsprompt.ui
Normal file
@ -0,0 +1,144 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>CredentialsPrompt</class>
|
||||
<widget class="QDialog" name="CredentialsPrompt">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>318</width>
|
||||
<height>229</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Authentication error: %1</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout" stretch="0,0,0">
|
||||
<item>
|
||||
<widget class="QLabel" name="message">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Couldn't authenticate account %1: login or password is icorrect.
|
||||
Would you like to check them and try again?</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="loginLabel">
|
||||
<property name="text">
|
||||
<string>Login</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="login">
|
||||
<property name="toolTip">
|
||||
<string>Your account login (without @server.domain)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="passwordLabel">
|
||||
<property name="text">
|
||||
<string>Password</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="password">
|
||||
<property name="toolTip">
|
||||
<string>Your password</string>
|
||||
</property>
|
||||
<property name="inputMethodHints">
|
||||
<set>Qt::ImhHiddenText|Qt::ImhNoAutoUppercase|Qt::ImhNoPredictiveText|Qt::ImhSensitiveData</set>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="echoMode">
|
||||
<enum>QLineEdit::Password</enum>
|
||||
</property>
|
||||
<property name="clearButtonEnabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>CredentialsPrompt</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>20</x>
|
||||
<y>20</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>20</x>
|
||||
<y>20</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>CredentialsPrompt</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>20</x>
|
||||
<y>20</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>20</x>
|
||||
<y>20</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
@ -26,7 +26,7 @@
|
||||
#include <QFileDialog>
|
||||
#include <QMimeDatabase>
|
||||
#include <QAbstractTextDocumentLayout>
|
||||
#include <QCoreApplication>
|
||||
#include <QApplication>
|
||||
#include <QTemporaryFile>
|
||||
#include <QDir>
|
||||
#include <QMenu>
|
||||
@ -499,6 +499,26 @@ void Conversation::onFeedContext(const QPoint& pos)
|
||||
});
|
||||
}
|
||||
|
||||
QString selected = feed->getSelectedText();
|
||||
if (selected.size() > 0) {
|
||||
showMenu = true;
|
||||
QAction* copy = contextMenu->addAction(Shared::icon("edit-copy"), tr("Copy selected"));
|
||||
connect(copy, &QAction::triggered, [selected] () {
|
||||
QClipboard* cb = QApplication::clipboard();
|
||||
cb->setText(selected);
|
||||
});
|
||||
}
|
||||
|
||||
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());
|
||||
if (path.size() > 0) {
|
||||
showMenu = true;
|
||||
|
@ -21,6 +21,8 @@
|
||||
#include <QPaintEvent>
|
||||
#include <QPainter>
|
||||
#include <QScrollBar>
|
||||
#include <QApplication>
|
||||
#include <QClipboard>
|
||||
#include <QDebug>
|
||||
|
||||
#include "messagedelegate.h"
|
||||
@ -50,7 +52,13 @@ FeedView::FeedView(QWidget* parent):
|
||||
modelState(Models::MessageFeed::complete),
|
||||
progress(),
|
||||
dividerFont(),
|
||||
dividerMetrics(dividerFont)
|
||||
dividerMetrics(dividerFont),
|
||||
mousePressed(false),
|
||||
dragging(false),
|
||||
hovered(Shared::Hover::nothing),
|
||||
dragStartPoint(),
|
||||
dragEndPoint(),
|
||||
selectedText()
|
||||
{
|
||||
horizontalScrollBar()->setRange(0, 0);
|
||||
verticalScrollBar()->setSingleStep(approximateSingleMessageHeight);
|
||||
@ -162,7 +170,7 @@ void FeedView::dataChanged(const QModelIndex& topLeft, const QModelIndex& bottom
|
||||
|
||||
void FeedView::updateGeometries()
|
||||
{
|
||||
qDebug() << "updateGeometries";
|
||||
//qDebug() << "updateGeometries";
|
||||
QScrollBar* bar = verticalScrollBar();
|
||||
|
||||
const QStyle* st = style();
|
||||
@ -343,6 +351,7 @@ void FeedView::paintEvent(QPaintEvent* event)
|
||||
|
||||
QDateTime lastDate;
|
||||
bool first = true;
|
||||
QRect viewportRect = vp->rect();
|
||||
for (const QModelIndex& index : toRener) {
|
||||
QDateTime currentDate = index.data(Models::MessageFeed::Date).toDateTime();
|
||||
option.rect = visualRect(index);
|
||||
@ -356,7 +365,10 @@ void FeedView::paintEvent(QPaintEvent* event)
|
||||
}
|
||||
first = false;
|
||||
}
|
||||
bool mouseOver = option.rect.contains(cursor) && vp->rect().contains(cursor);
|
||||
QRect stripe = option.rect;
|
||||
stripe.setLeft(0);
|
||||
stripe.setWidth(viewportRect.width());
|
||||
bool mouseOver = stripe.contains(cursor) && viewportRect.contains(cursor);
|
||||
option.state.setFlag(QStyle::State_MouseOver, mouseOver);
|
||||
itemDelegate(index)->paint(&painter, option, index);
|
||||
|
||||
@ -403,13 +415,151 @@ void FeedView::verticalScrollbarValueChanged(int value)
|
||||
QAbstractItemView::verticalScrollbarValueChanged(vo);
|
||||
}
|
||||
|
||||
void FeedView::setAnchorHovered(Shared::Hover type)
|
||||
{
|
||||
if (hovered != type) {
|
||||
hovered = type;
|
||||
switch (hovered) {
|
||||
case Shared::Hover::nothing:
|
||||
setCursor(Qt::ArrowCursor);
|
||||
break;
|
||||
case Shared::Hover::text:
|
||||
setCursor(Qt::IBeamCursor);
|
||||
break;
|
||||
case Shared::Hover::anchor:
|
||||
setCursor(Qt::PointingHandCursor);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FeedView::mouseMoveEvent(QMouseEvent* event)
|
||||
{
|
||||
if (!isVisible()) {
|
||||
return;
|
||||
}
|
||||
|
||||
dragEndPoint = event->localPos().toPoint();
|
||||
if (mousePressed) {
|
||||
QPoint distance = dragStartPoint - dragEndPoint;
|
||||
if (distance.manhattanLength() > 5) {
|
||||
dragging = true;
|
||||
}
|
||||
}
|
||||
|
||||
QAbstractItemView::mouseMoveEvent(event);
|
||||
|
||||
if (specialDelegate) {
|
||||
MessageDelegate* del = static_cast<MessageDelegate*>(itemDelegate());
|
||||
if (dragging) {
|
||||
QModelIndex index = indexAt(dragStartPoint);
|
||||
if (index.isValid()) {
|
||||
QRect rect = visualRect(index);
|
||||
if (rect.contains(dragStartPoint)) {
|
||||
QString selected = del->mouseDrag(dragStartPoint, dragEndPoint, index, rect);
|
||||
if (selectedText != selected) {
|
||||
selectedText = selected;
|
||||
setDirtyRegion(rect);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
QModelIndex index = indexAt(dragEndPoint);
|
||||
if (index.isValid()) {
|
||||
QRect rect = visualRect(index);
|
||||
if (rect.contains(dragEndPoint)) {
|
||||
setAnchorHovered(del->hoverType(dragEndPoint, index, rect));
|
||||
} else {
|
||||
setAnchorHovered(Shared::Hover::nothing);
|
||||
}
|
||||
} else {
|
||||
setAnchorHovered(Shared::Hover::nothing);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FeedView::mousePressEvent(QMouseEvent* event)
|
||||
{
|
||||
QAbstractItemView::mousePressEvent(event);
|
||||
|
||||
mousePressed = event->button() == Qt::LeftButton;
|
||||
if (mousePressed) {
|
||||
dragStartPoint = event->localPos().toPoint();
|
||||
if (specialDelegate && specialModel) {
|
||||
MessageDelegate* del = static_cast<MessageDelegate*>(itemDelegate());
|
||||
QString lastSelectedId = del->clearSelection();
|
||||
if (lastSelectedId.size()) {
|
||||
Models::MessageFeed* feed = static_cast<Models::MessageFeed*>(model());
|
||||
QModelIndex index = feed->modelIndexById(lastSelectedId);
|
||||
if (index.isValid()) {
|
||||
setDirtyRegion(visualRect(index));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FeedView::mouseDoubleClickEvent(QMouseEvent* event)
|
||||
{
|
||||
QAbstractItemView::mouseDoubleClickEvent(event);
|
||||
mousePressed = event->button() == Qt::LeftButton;
|
||||
if (mousePressed) {
|
||||
dragStartPoint = event->localPos().toPoint();
|
||||
if (specialDelegate && specialModel) {
|
||||
MessageDelegate* del = static_cast<MessageDelegate*>(itemDelegate());
|
||||
QString lastSelectedId = del->clearSelection();
|
||||
selectedText = "";
|
||||
if (lastSelectedId.size()) {
|
||||
Models::MessageFeed* feed = static_cast<Models::MessageFeed*>(model());
|
||||
QModelIndex index = feed->modelIndexById(lastSelectedId);
|
||||
if (index.isValid()) {
|
||||
setDirtyRegion(visualRect(index));
|
||||
}
|
||||
}
|
||||
|
||||
QModelIndex index = indexAt(dragStartPoint);
|
||||
QRect rect = visualRect(index);
|
||||
if (rect.contains(dragStartPoint)) {
|
||||
selectedText = del->leftDoubleClick(dragStartPoint, index, rect);
|
||||
if (selectedText.size() > 0) {
|
||||
setDirtyRegion(rect);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FeedView::mouseReleaseEvent(QMouseEvent* event)
|
||||
{
|
||||
QAbstractItemView::mouseReleaseEvent(event);
|
||||
|
||||
if (mousePressed) {
|
||||
if (!dragging && specialDelegate) {
|
||||
QPoint point = event->localPos().toPoint();
|
||||
QModelIndex index = indexAt(point);
|
||||
if (index.isValid()) {
|
||||
QRect rect = visualRect(index);
|
||||
MessageDelegate* del = static_cast<MessageDelegate*>(itemDelegate());
|
||||
if (rect.contains(point)) {
|
||||
del->leftClick(point, index, rect);
|
||||
}
|
||||
}
|
||||
}
|
||||
dragging = false;
|
||||
mousePressed = false;
|
||||
}
|
||||
}
|
||||
|
||||
void FeedView::keyPressEvent(QKeyEvent* event)
|
||||
{
|
||||
QKeyEvent *key_event = static_cast<QKeyEvent*>(event);
|
||||
if (key_event->matches(QKeySequence::Copy)) {
|
||||
if (selectedText.size() > 0) {
|
||||
QClipboard* cb = QApplication::clipboard();
|
||||
cb->setText(selectedText);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FeedView::resizeEvent(QResizeEvent* event)
|
||||
@ -456,6 +606,7 @@ void FeedView::setItemDelegate(QAbstractItemDelegate* delegate)
|
||||
elementMargin = MessageDelegate::margin;
|
||||
connect(del, &MessageDelegate::buttonPushed, this, &FeedView::onMessageButtonPushed);
|
||||
connect(del, &MessageDelegate::invalidPath, this, &FeedView::onMessageInvalidPath);
|
||||
connect(del, &MessageDelegate::openLink, &QDesktopServices::openUrl);
|
||||
} else {
|
||||
specialDelegate = false;
|
||||
elementMargin = 0;
|
||||
@ -520,3 +671,8 @@ void FeedView::onModelSyncStateChange(Models::MessageFeed::SyncState state)
|
||||
scheduleDelayedItemsLayout();
|
||||
}
|
||||
}
|
||||
|
||||
QString FeedView::getSelectedText() const
|
||||
{
|
||||
return selectedText;
|
||||
}
|
||||
|
@ -20,12 +20,15 @@
|
||||
#define FEEDVIEW_H
|
||||
|
||||
#include <QAbstractItemView>
|
||||
#include <QDesktopServices>
|
||||
#include <QUrl>
|
||||
|
||||
#include <deque>
|
||||
#include <set>
|
||||
|
||||
#include <ui/widgets/messageline/messagefeed.h>
|
||||
#include <ui/utils/progress.h>
|
||||
#include <shared/utils.h>
|
||||
|
||||
/**
|
||||
* @todo write docs
|
||||
@ -48,6 +51,7 @@ public:
|
||||
void setModel(QAbstractItemModel * model) override;
|
||||
|
||||
QFont getFont() const;
|
||||
QString getSelectedText() const;
|
||||
|
||||
signals:
|
||||
void resized();
|
||||
@ -68,12 +72,17 @@ protected:
|
||||
void paintEvent(QPaintEvent * event) override;
|
||||
void updateGeometries() override;
|
||||
void mouseMoveEvent(QMouseEvent * event) override;
|
||||
void mousePressEvent(QMouseEvent * event) override;
|
||||
void mouseReleaseEvent(QMouseEvent * event) override;
|
||||
void mouseDoubleClickEvent(QMouseEvent * event) override;
|
||||
void keyPressEvent(QKeyEvent * event) override;
|
||||
void resizeEvent(QResizeEvent * event) override;
|
||||
|
||||
private:
|
||||
bool tryToCalculateGeometriesWithNoScrollbars(const QStyleOptionViewItem& option, const QAbstractItemModel* model, uint32_t totalHeight);
|
||||
void positionProgress();
|
||||
void drawDateDevider(int top, const QDateTime& date, QPainter& painter);
|
||||
void setAnchorHovered(Shared::Hover type);
|
||||
|
||||
private:
|
||||
struct Hint {
|
||||
@ -93,6 +102,12 @@ private:
|
||||
Progress progress;
|
||||
QFont dividerFont;
|
||||
QFontMetrics dividerMetrics;
|
||||
bool mousePressed;
|
||||
bool dragging;
|
||||
Shared::Hover hovered;
|
||||
QPoint dragStartPoint;
|
||||
QPoint dragEndPoint;
|
||||
QString selectedText;
|
||||
|
||||
static const std::set<int> geometryChangingRoles;
|
||||
|
||||
|
@ -22,6 +22,9 @@
|
||||
#include <QApplication>
|
||||
#include <QMouseEvent>
|
||||
#include <QAbstractItemView>
|
||||
#include <QAbstractTextDocumentLayout>
|
||||
#include <QTextBlock>
|
||||
#include <cmath>
|
||||
|
||||
#include "messagedelegate.h"
|
||||
#include "messagefeed.h"
|
||||
@ -41,7 +44,7 @@ MessageDelegate::MessageDelegate(QObject* parent):
|
||||
bodyFont(),
|
||||
nickFont(),
|
||||
dateFont(),
|
||||
bodyMetrics(bodyFont),
|
||||
bodyRenderer(new QTextDocument()),
|
||||
nickMetrics(nickFont),
|
||||
dateMetrics(dateFont),
|
||||
buttonHeight(0),
|
||||
@ -51,11 +54,14 @@ MessageDelegate::MessageDelegate(QObject* parent):
|
||||
bars(new std::map<QString, QProgressBar*>()),
|
||||
statusIcons(new std::map<QString, QLabel*>()),
|
||||
pencilIcons(new std::map<QString, QLabel*>()),
|
||||
bodies(new std::map<QString, QLabel*>()),
|
||||
previews(new std::map<QString, Preview*>()),
|
||||
idsToKeep(new std::set<QString>()),
|
||||
clearingWidgets(false)
|
||||
clearingWidgets(false),
|
||||
currentId(""),
|
||||
selection(0, 0)
|
||||
{
|
||||
bodyRenderer->setDocumentMargin(0);
|
||||
|
||||
QPushButton btn(QCoreApplication::translate("MessageLine", "Download"));
|
||||
buttonHeight = btn.sizeHint().height();
|
||||
buttonWidth = btn.sizeHint().width();
|
||||
@ -82,10 +88,6 @@ MessageDelegate::~MessageDelegate()
|
||||
delete pair.second;
|
||||
}
|
||||
|
||||
for (const std::pair<const QString, QLabel*>& pair: *bodies){
|
||||
delete pair.second;
|
||||
}
|
||||
|
||||
for (const std::pair<const QString, Preview*>& pair: *previews){
|
||||
delete pair.second;
|
||||
}
|
||||
@ -95,8 +97,8 @@ MessageDelegate::~MessageDelegate()
|
||||
delete idsToKeep;
|
||||
delete buttons;
|
||||
delete bars;
|
||||
delete bodies;
|
||||
delete previews;
|
||||
delete bodyRenderer;
|
||||
}
|
||||
|
||||
void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
|
||||
@ -123,11 +125,6 @@ void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio
|
||||
opt.displayAlignment = Qt::AlignRight | Qt::AlignTop;
|
||||
}
|
||||
|
||||
QSize bodySize(0, 0);
|
||||
if (data.text.size() > 0) {
|
||||
bodySize = bodyMetrics.boundingRect(opt.rect, Qt::TextWordWrap, data.text).size();
|
||||
}
|
||||
|
||||
QRect rect;
|
||||
if (ntds) {
|
||||
painter->setFont(nickFont);
|
||||
@ -170,15 +167,7 @@ void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio
|
||||
painter->restore();
|
||||
|
||||
QWidget* vp = static_cast<QWidget*>(painter->device());
|
||||
if (data.text.size() > 0) {
|
||||
QLabel* body = getBody(data);
|
||||
body->setParent(vp);
|
||||
body->setMinimumSize(bodySize);
|
||||
body->setMaximumSize(bodySize);
|
||||
body->move(opt.rect.left(), opt.rect.y());
|
||||
body->show();
|
||||
opt.rect.adjust(0, bodySize.height() + textMargin, 0, 0);
|
||||
}
|
||||
paintBody(data, painter, opt);
|
||||
painter->setFont(dateFont);
|
||||
QColor q = painter->pen().color();
|
||||
QString dateString = data.date.toLocalTime().toString("hh:mm");
|
||||
@ -306,7 +295,12 @@ QSize MessageDelegate::sizeHint(const QStyleOptionViewItem& option, const QModel
|
||||
Models::FeedItem data = qvariant_cast<Models::FeedItem>(vi);
|
||||
QSize messageSize(0, 0);
|
||||
if (data.text.size() > 0) {
|
||||
messageSize = bodyMetrics.boundingRect(messageRect, Qt::TextWordWrap, data.text).size();
|
||||
bodyRenderer->setPlainText(data.text);
|
||||
bodyRenderer->setTextWidth(messageRect.size().width());
|
||||
|
||||
QSizeF size = bodyRenderer->size();
|
||||
size.setWidth(bodyRenderer->idealWidth());
|
||||
messageSize = QSize(std::ceil(size.width()), std::ceil(size.height()));
|
||||
messageSize.rheight() += textMargin;
|
||||
}
|
||||
|
||||
@ -367,6 +361,183 @@ QSize MessageDelegate::sizeHint(const QStyleOptionViewItem& option, const QModel
|
||||
return messageSize;
|
||||
}
|
||||
|
||||
QRect MessageDelegate::getHoveredMessageBodyRect(const QModelIndex& index, const Models::FeedItem& data, const QRect& sizeHint) const
|
||||
{
|
||||
QRect localHint = sizeHint.adjusted(bubbleMargin, bubbleMargin + margin, -bubbleMargin, -bubbleMargin / 2);
|
||||
if (needToDrawSender(index, data)) {
|
||||
localHint.adjust(0, nickMetrics.lineSpacing() + textMargin, 0, 0);
|
||||
}
|
||||
|
||||
int attachHeight = 0;
|
||||
switch (data.attach.state) {
|
||||
case Models::none:
|
||||
break;
|
||||
case Models::uploading:
|
||||
attachHeight += Preview::calculateAttachSize(Shared::resolvePath(data.attach.localPath), localHint).height() + textMargin;
|
||||
[[fallthrough]];
|
||||
case Models::downloading:
|
||||
attachHeight += barHeight + textMargin;
|
||||
break;
|
||||
case Models::remote:
|
||||
attachHeight += buttonHeight + textMargin;
|
||||
break;
|
||||
case Models::ready:
|
||||
case Models::local: {
|
||||
QSize aSize = Preview::calculateAttachSize(Shared::resolvePath(data.attach.localPath), localHint);
|
||||
attachHeight += aSize.height() + textMargin;
|
||||
}
|
||||
break;
|
||||
case Models::errorDownload: {
|
||||
QSize commentSize = dateMetrics.boundingRect(localHint, Qt::TextWordWrap, data.attach.error).size();
|
||||
attachHeight += commentSize.height() + buttonHeight + textMargin * 2;
|
||||
}
|
||||
break;
|
||||
case Models::errorUpload: {
|
||||
QSize aSize = Preview::calculateAttachSize(Shared::resolvePath(data.attach.localPath), localHint);
|
||||
QSize commentSize = dateMetrics.boundingRect(localHint, Qt::TextWordWrap, data.attach.error).size();
|
||||
attachHeight += aSize.height() + commentSize.height() + textMargin * 2;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
int bottomSize = std::max(dateMetrics.lineSpacing(), statusIconSize);
|
||||
localHint.adjust(0, attachHeight, 0, -(bottomSize + textMargin));
|
||||
|
||||
return localHint;
|
||||
}
|
||||
|
||||
QString MessageDelegate::getAnchor(const QPoint& point, const QModelIndex& index, const QRect& sizeHint) const
|
||||
{
|
||||
QVariant vi = index.data(Models::MessageFeed::Bulk);
|
||||
Models::FeedItem data = qvariant_cast<Models::FeedItem>(vi);
|
||||
if (data.text.size() > 0) {
|
||||
QRect localHint = getHoveredMessageBodyRect(index, data, sizeHint);
|
||||
|
||||
if (localHint.contains(point)) {
|
||||
QPoint translated = point - localHint.topLeft();
|
||||
|
||||
bodyRenderer->setHtml(Shared::processMessageBody(data.text));
|
||||
bodyRenderer->setTextWidth(localHint.size().width());
|
||||
|
||||
return bodyRenderer->documentLayout()->anchorAt(translated);
|
||||
}
|
||||
}
|
||||
|
||||
return QString();
|
||||
}
|
||||
|
||||
void MessageDelegate::leftClick(const QPoint& point, const QModelIndex& index, const QRect& sizeHint) const
|
||||
{
|
||||
QString anchor = getAnchor(point, index, sizeHint);
|
||||
if (anchor.size() > 0) {
|
||||
emit openLink(anchor);
|
||||
}
|
||||
}
|
||||
|
||||
QString MessageDelegate::leftDoubleClick(const QPoint& point, const QModelIndex& index, const QRect& sizeHint)
|
||||
{
|
||||
QVariant vi = index.data(Models::MessageFeed::Bulk);
|
||||
Models::FeedItem data = qvariant_cast<Models::FeedItem>(vi);
|
||||
if (data.text.size() > 0) {
|
||||
QRect localHint = getHoveredMessageBodyRect(index, data, sizeHint);
|
||||
|
||||
if (localHint.contains(point)) {
|
||||
QPoint translated = point - localHint.topLeft();
|
||||
|
||||
bodyRenderer->setHtml(Shared::processMessageBody(data.text));
|
||||
bodyRenderer->setTextWidth(localHint.size().width());
|
||||
|
||||
QAbstractTextDocumentLayout* lay = bodyRenderer->documentLayout();
|
||||
|
||||
int position = lay->hitTest(translated, Qt::HitTestAccuracy::FuzzyHit);
|
||||
QTextCursor cursor(bodyRenderer);
|
||||
cursor.setPosition(position, QTextCursor::MoveAnchor);
|
||||
cursor.movePosition(QTextCursor::StartOfWord, QTextCursor::MoveAnchor);
|
||||
cursor.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor);
|
||||
|
||||
selection.first = cursor.anchor();
|
||||
selection.second = cursor.position();
|
||||
currentId = data.id;
|
||||
|
||||
if (selection.first != selection.second) {
|
||||
return cursor.selectedText();
|
||||
}
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
Shared::Hover MessageDelegate::hoverType(const QPoint& point, const QModelIndex& index, const QRect& sizeHint) const
|
||||
{
|
||||
QVariant vi = index.data(Models::MessageFeed::Bulk);
|
||||
Models::FeedItem data = qvariant_cast<Models::FeedItem>(vi);
|
||||
if (data.text.size() > 0) {
|
||||
QRect localHint = getHoveredMessageBodyRect(index, data, sizeHint);
|
||||
|
||||
if (localHint.contains(point)) {
|
||||
QPoint translated = point - localHint.topLeft();
|
||||
|
||||
bodyRenderer->setHtml(Shared::processMessageBody(data.text));
|
||||
bodyRenderer->setTextWidth(localHint.size().width());
|
||||
|
||||
QAbstractTextDocumentLayout* lay = bodyRenderer->documentLayout();
|
||||
QString anchor = lay->anchorAt(translated);
|
||||
|
||||
if (anchor.size() > 0) {
|
||||
return Shared::Hover::anchor;
|
||||
} else {
|
||||
int position = lay->hitTest(translated, Qt::HitTestAccuracy::ExactHit);
|
||||
if (position != -1) {
|
||||
return Shared::Hover::text;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return Shared::Hover::nothing;
|
||||
}
|
||||
|
||||
QString MessageDelegate::mouseDrag(const QPoint& start, const QPoint& end, const QModelIndex& index, const QRect& sizeHint)
|
||||
{
|
||||
QVariant vi = index.data(Models::MessageFeed::Bulk);
|
||||
Models::FeedItem data = qvariant_cast<Models::FeedItem>(vi);
|
||||
if (data.text.size() > 0) {
|
||||
QRect localHint = getHoveredMessageBodyRect(index, data, sizeHint);
|
||||
|
||||
if (localHint.contains(start)) {
|
||||
QPoint tl = localHint.topLeft();
|
||||
QPoint first = start - tl;
|
||||
QPoint last = end - tl;
|
||||
last.setX(std::max(last.x(), 0));
|
||||
last.setX(std::min(last.x(), localHint.width() - 1));
|
||||
last.setY(std::max(last.y(), 0));
|
||||
last.setY(std::min(last.y(), localHint.height()));
|
||||
|
||||
bodyRenderer->setHtml(Shared::processMessageBody(data.text));
|
||||
bodyRenderer->setTextWidth(localHint.size().width());
|
||||
selection.first = bodyRenderer->documentLayout()->hitTest(first, Qt::HitTestAccuracy::FuzzyHit);
|
||||
selection.second = bodyRenderer->documentLayout()->hitTest(last, Qt::HitTestAccuracy::FuzzyHit);
|
||||
|
||||
currentId = data.id;
|
||||
|
||||
if (selection.first != selection.second) {
|
||||
QTextCursor cursor(bodyRenderer);
|
||||
cursor.setPosition(selection.first, QTextCursor::MoveAnchor);
|
||||
cursor.setPosition(selection.second, QTextCursor::KeepAnchor);
|
||||
return cursor.selectedText();
|
||||
}
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
QString MessageDelegate::clearSelection()
|
||||
{
|
||||
QString lastSelectedId = currentId;
|
||||
currentId = "";
|
||||
selection = std::pair(0, 0);
|
||||
return lastSelectedId;
|
||||
}
|
||||
|
||||
void MessageDelegate::initializeFonts(const QFont& font)
|
||||
{
|
||||
bodyFont = font;
|
||||
@ -390,10 +561,12 @@ void MessageDelegate::initializeFonts(const QFont& font)
|
||||
dateFont.setPointSize(dateFont.pointSize() * dateFontMultiplier);
|
||||
}
|
||||
|
||||
bodyMetrics = QFontMetrics(bodyFont);
|
||||
bodyFont.setKerning(false);
|
||||
nickMetrics = QFontMetrics(nickFont);
|
||||
dateMetrics = QFontMetrics(dateFont);
|
||||
|
||||
bodyRenderer->setDefaultFont(bodyFont);
|
||||
|
||||
Preview::initializeFont(bodyFont);
|
||||
}
|
||||
|
||||
@ -572,34 +745,6 @@ QLabel * MessageDelegate::getPencilIcon(const Models::FeedItem& data) const
|
||||
return result;
|
||||
}
|
||||
|
||||
QLabel * MessageDelegate::getBody(const Models::FeedItem& data) const
|
||||
{
|
||||
std::map<QString, QLabel*>::const_iterator itr = bodies->find(data.id);
|
||||
QLabel* result = 0;
|
||||
|
||||
if (itr != bodies->end()) {
|
||||
result = itr->second;
|
||||
} else {
|
||||
result = new QLabel();
|
||||
result->setFont(bodyFont);
|
||||
result->setContextMenuPolicy(Qt::NoContextMenu);
|
||||
result->setWordWrap(true);
|
||||
result->setOpenExternalLinks(true);
|
||||
result->setTextInteractionFlags(result->textInteractionFlags() | Qt::TextSelectableByMouse | Qt::LinksAccessibleByMouse);
|
||||
bodies->insert(std::make_pair(data.id, result));
|
||||
}
|
||||
|
||||
result->setText(Shared::processMessageBody(data.text));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void MessageDelegate::beginClearWidgets()
|
||||
{
|
||||
idsToKeep->clear();
|
||||
clearingWidgets = true;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void removeElements(std::map<QString, T*>* elements, std::set<QString>* idsToKeep) {
|
||||
std::set<QString> toRemove;
|
||||
@ -614,6 +759,41 @@ void removeElements(std::map<QString, T*>* elements, std::set<QString>* idsToKee
|
||||
}
|
||||
}
|
||||
|
||||
int MessageDelegate::paintBody(const Models::FeedItem& data, QPainter* painter, QStyleOptionViewItem& option) const
|
||||
{
|
||||
if (data.text.size() > 0) {
|
||||
bodyRenderer->setHtml(Shared::processMessageBody(data.text));
|
||||
bodyRenderer->setTextWidth(option.rect.size().width());
|
||||
painter->save();
|
||||
painter->translate(option.rect.topLeft());
|
||||
|
||||
if (data.id == currentId) {
|
||||
QTextCursor cursor(bodyRenderer);
|
||||
cursor.setPosition(selection.first, QTextCursor::MoveAnchor);
|
||||
cursor.setPosition(selection.second, QTextCursor::KeepAnchor);
|
||||
QTextCharFormat format = cursor.charFormat();
|
||||
format.setBackground(option.palette.color(QPalette::Active, QPalette::Highlight));
|
||||
format.setForeground(option.palette.color(QPalette::Active, QPalette::HighlightedText));
|
||||
cursor.setCharFormat(format);
|
||||
}
|
||||
|
||||
bodyRenderer->drawContents(painter);
|
||||
|
||||
painter->restore();
|
||||
QSize bodySize(std::ceil(bodyRenderer->idealWidth()), std::ceil(bodyRenderer->size().height()));
|
||||
|
||||
option.rect.adjust(0, bodySize.height() + textMargin, 0, 0);
|
||||
return bodySize.width();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void MessageDelegate::beginClearWidgets()
|
||||
{
|
||||
idsToKeep->clear();
|
||||
clearingWidgets = true;
|
||||
}
|
||||
|
||||
void MessageDelegate::endClearWidgets()
|
||||
{
|
||||
if (clearingWidgets) {
|
||||
@ -621,7 +801,6 @@ void MessageDelegate::endClearWidgets()
|
||||
removeElements(bars, idsToKeep);
|
||||
removeElements(statusIcons, idsToKeep);
|
||||
removeElements(pencilIcons, idsToKeep);
|
||||
removeElements(bodies, idsToKeep);
|
||||
removeElements(previews, idsToKeep);
|
||||
|
||||
idsToKeep->clear();
|
||||
@ -649,8 +828,3 @@ void MessageDelegate::clearHelperWidget(const Models::FeedItem& data) const
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// void MessageDelegate::setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const
|
||||
// {
|
||||
//
|
||||
// }
|
||||
|
@ -29,6 +29,7 @@
|
||||
#include <QPushButton>
|
||||
#include <QProgressBar>
|
||||
#include <QLabel>
|
||||
#include <QTextDocument>
|
||||
|
||||
#include "shared/icons.h"
|
||||
#include "shared/global.h"
|
||||
@ -56,6 +57,11 @@ public:
|
||||
bool editorEvent(QEvent * event, QAbstractItemModel * model, const QStyleOptionViewItem & option, const QModelIndex & index) override;
|
||||
void endClearWidgets();
|
||||
void beginClearWidgets();
|
||||
void leftClick(const QPoint& point, const QModelIndex& index, const QRect& sizeHint) const;
|
||||
QString leftDoubleClick(const QPoint& point, const QModelIndex& index, const QRect& sizeHint);
|
||||
Shared::Hover hoverType(const QPoint& point, const QModelIndex& index, const QRect& sizeHint) const;
|
||||
QString mouseDrag(const QPoint& start, const QPoint& end, const QModelIndex& index, const QRect& sizeHint);
|
||||
QString clearSelection();
|
||||
|
||||
static int avatarHeight;
|
||||
static int margin;
|
||||
@ -63,24 +69,29 @@ public:
|
||||
signals:
|
||||
void buttonPushed(const QString& messageId) const;
|
||||
void invalidPath(const QString& messageId) const;
|
||||
void openLink(const QString& href) const;
|
||||
|
||||
protected:
|
||||
int paintButton(QPushButton* btn, QPainter* painter, bool sentByMe, QStyleOptionViewItem& option) const;
|
||||
int paintBar(QProgressBar* bar, QPainter* painter, bool sentByMe, QStyleOptionViewItem& option) const;
|
||||
int paintPreview(const Models::FeedItem& data, QPainter* painter, QStyleOptionViewItem& option) const;
|
||||
int paintComment(const Models::FeedItem& data, QPainter* painter, QStyleOptionViewItem& option) const;
|
||||
int paintBody(const Models::FeedItem& data, QPainter* painter, QStyleOptionViewItem& option) const;
|
||||
void paintAvatar(const Models::FeedItem& data, const QModelIndex& index, const QStyleOptionViewItem& option, QPainter* painter) const;
|
||||
void paintBubble(const Models::FeedItem& data, QPainter* painter, const QStyleOptionViewItem& option) const;
|
||||
|
||||
QPushButton* getButton(const Models::FeedItem& data) const;
|
||||
QProgressBar* getBar(const Models::FeedItem& data) const;
|
||||
QLabel* getStatusIcon(const Models::FeedItem& data) const;
|
||||
QLabel* getPencilIcon(const Models::FeedItem& data) const;
|
||||
QLabel* getBody(const Models::FeedItem& data) const;
|
||||
void clearHelperWidget(const Models::FeedItem& data) const;
|
||||
|
||||
bool needToDrawAvatar(const QModelIndex& index, const Models::FeedItem& data, const QStyleOptionViewItem& option) const;
|
||||
bool needToDrawSender(const QModelIndex& index, const Models::FeedItem& data) const;
|
||||
|
||||
QRect getHoveredMessageBodyRect(const QModelIndex& index, const Models::FeedItem& data, const QRect& sizeHint) const;
|
||||
QString getAnchor(const QPoint& point, const QModelIndex& index, const QRect& sizeHint) const;
|
||||
|
||||
protected slots:
|
||||
void onButtonPushed() const;
|
||||
|
||||
@ -93,7 +104,7 @@ private:
|
||||
QFont bodyFont;
|
||||
QFont nickFont;
|
||||
QFont dateFont;
|
||||
QFontMetrics bodyMetrics;
|
||||
QTextDocument* bodyRenderer;
|
||||
QFontMetrics nickMetrics;
|
||||
QFontMetrics dateMetrics;
|
||||
|
||||
@ -105,11 +116,11 @@ private:
|
||||
std::map<QString, QProgressBar*>* bars;
|
||||
std::map<QString, QLabel*>* statusIcons;
|
||||
std::map<QString, QLabel*>* pencilIcons;
|
||||
std::map<QString, QLabel*>* bodies;
|
||||
std::map<QString, Preview*>* previews;
|
||||
std::set<QString>* idsToKeep;
|
||||
bool clearingWidgets;
|
||||
|
||||
QString currentId;
|
||||
std::pair<int, int> selection;
|
||||
};
|
||||
|
||||
#endif // MESSAGEDELEGATE_H
|
||||
|
@ -163,6 +163,12 @@ void Models::MessageFeed::changeMessage(const QString& id, const QMap<QString, Q
|
||||
}
|
||||
|
||||
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: {
|
||||
FeedItem item;
|
||||
item.id = msg->getId();
|
||||
|
||||
std::set<QString>::const_iterator umi = unreadMessages->find(item.id);
|
||||
if (umi != unreadMessages->end()) {
|
||||
unreadMessages->erase(umi);
|
||||
emit unreadMessagesCountChanged();
|
||||
}
|
||||
markMessageAsRead(item.id);
|
||||
|
||||
item.sentByMe = sentByMe(*msg);
|
||||
item.date = msg->getTime();
|
||||
@ -367,6 +368,17 @@ int Models::MessageFeed::rowCount(const QModelIndex& parent) const
|
||||
return storage.size();
|
||||
}
|
||||
|
||||
bool Models::MessageFeed::markMessageAsRead(const QString& id) const
|
||||
{
|
||||
std::set<QString>::const_iterator umi = unreadMessages->find(id);
|
||||
if (umi != unreadMessages->end()) {
|
||||
unreadMessages->erase(umi);
|
||||
emit unreadMessagesCountChanged();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
unsigned int Models::MessageFeed::unreadMessagesCount() const
|
||||
{
|
||||
return unreadMessages->size();
|
||||
|
@ -57,6 +57,8 @@ public:
|
||||
void changeMessage(const QString& id, const QMap<QString, QVariant>& data);
|
||||
void removeMessage(const QString& id);
|
||||
Shared::Message getMessage(const QString& id);
|
||||
QModelIndex modelIndexById(const QString& id) const;
|
||||
QModelIndex modelIndexByTime(const QString& id, const QDateTime& time) const;
|
||||
|
||||
QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const override;
|
||||
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
|
||||
@ -72,6 +74,7 @@ public:
|
||||
void reportLocalPathInvalid(const QString& messageId);
|
||||
|
||||
unsigned int unreadMessagesCount() const;
|
||||
bool markMessageAsRead(const QString& id) const;
|
||||
void fileProgress(const QString& messageId, qreal value, bool up);
|
||||
void fileError(const QString& messageId, const QString& error, bool up);
|
||||
void fileComplete(const QString& messageId, bool up);
|
||||
@ -125,8 +128,6 @@ protected:
|
||||
bool sentByMe(const Shared::Message& msg) const;
|
||||
Attachment fillAttach(const Shared::Message& msg) const;
|
||||
Edition fillCorrection(const Shared::Message& msg) const;
|
||||
QModelIndex modelIndexById(const QString& id) const;
|
||||
QModelIndex modelIndexByTime(const QString& id, const QDateTime& time) const;
|
||||
std::set<MessageRoles> detectChanges(const Shared::Message& msg, const QMap<QString, QVariant>& data) const;
|
||||
|
||||
private:
|
||||
|
@ -18,6 +18,7 @@
|
||||
|
||||
#include "pagegeneral.h"
|
||||
#include "ui_pagegeneral.h"
|
||||
#include "ui/squawk.h"
|
||||
|
||||
PageGeneral::PageGeneral(QWidget* parent):
|
||||
QWidget(parent),
|
||||
@ -28,7 +29,10 @@ PageGeneral::PageGeneral(QWidget* parent):
|
||||
|
||||
QSettings settings;
|
||||
m_ui->downloadsPathInput->setText(settings.value("downloadsPath").toString());
|
||||
m_ui->trayIconCheckbox->setChecked(settings.value("trayIconCheckbox").toBool());
|
||||
|
||||
connect(m_ui->downloadsPathButton, &QPushButton::clicked, this, &PageGeneral::onBrowseButtonClicked);
|
||||
connect(m_ui->trayIconCheckbox, &QCheckBox::stateChanged, this, &PageGeneral::onTrayIconCheckboxChecked);
|
||||
}
|
||||
|
||||
PageGeneral::~PageGeneral()
|
||||
@ -76,3 +80,9 @@ void PageGeneral::onDialogDestroyed()
|
||||
{
|
||||
dialog = nullptr;
|
||||
}
|
||||
|
||||
void PageGeneral::onTrayIconCheckboxChecked(){
|
||||
QSettings settings;
|
||||
settings.setValue("trayIconCheckbox", m_ui->trayIconCheckbox->isChecked());
|
||||
Squawk::trayIcon->setVisible(m_ui->trayIconCheckbox->isChecked());
|
||||
}
|
||||
|
@ -47,6 +47,7 @@ private slots:
|
||||
void onBrowseButtonClicked();
|
||||
void onDialogAccepted();
|
||||
void onDialogDestroyed();
|
||||
void onTrayIconCheckboxChecked();
|
||||
|
||||
private:
|
||||
QScopedPointer<Ui::PageGeneral> m_ui;
|
||||
|
@ -39,6 +39,19 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="2" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="trayIconCheckbox">
|
||||
<property name="layoutDirection">
|
||||
<enum>Qt::LeftToRight</enum>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Hide Squawk to tray</string>
|
||||
</property>
|
||||
<property name="tristate">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
|
Loading…
Reference in New Issue
Block a user