/* * Squawk messenger. * Copyright (C) 2019 Yury Gubich * * 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 . */ #include "squawk.h" #include "ui_squawk.h" #include #include #include Squawk::Squawk(QWidget *parent) : QMainWindow(parent), m_ui(new Ui::Squawk), accounts(0), rosterModel(), conversations(), contextMenu(new QMenu()), dbus("org.freedesktop.Notifications", "/org/freedesktop/Notifications", "org.freedesktop.Notifications", QDBusConnection::sessionBus()), requestedFiles(), vCards() { m_ui->setupUi(this); m_ui->roster->setModel(&rosterModel); m_ui->roster->setContextMenuPolicy(Qt::CustomContextMenu); m_ui->roster->setColumnWidth(1, 20); m_ui->roster->setIconSize(QSize(20, 20)); m_ui->roster->header()->setStretchLastSection(false); m_ui->roster->header()->setSectionResizeMode(0, QHeaderView::Stretch); for (unsigned int i = Shared::availabilityLowest; i < Shared::availabilityHighest + 1; ++i) { Shared::Availability av = static_cast(i); m_ui->comboBox->addItem(Shared::availabilityIcon(av), QCoreApplication::translate("Global", Shared::availabilityNames[av].toLatin1())); } m_ui->comboBox->setCurrentIndex(Shared::offline); connect(m_ui->actionAccounts, &QAction::triggered, this, &Squawk::onAccounts); connect(m_ui->actionAddContact, &QAction::triggered, this, &Squawk::onNewContact); connect(m_ui->actionAddConference, &QAction::triggered, this, &Squawk::onNewConference); connect(m_ui->comboBox, qOverload(&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(rosterModel.accountsModel, &Models::Accounts::sizeChanged, this, &Squawk::onAccountsSizeChanged); //m_ui->mainToolBar->addWidget(m_ui->comboBox); setWindowTitle(tr("Contact list")); } Squawk::~Squawk() { delete contextMenu; } void Squawk::onAccounts() { if (accounts == 0) { accounts = new Accounts(rosterModel.accountsModel, this); accounts->setAttribute(Qt::WA_DeleteOnClose); connect(accounts, &Accounts::destroyed, this, &Squawk::onAccountsClosed); connect(accounts, &Accounts::newAccount, this, &Squawk::newAccountRequest); connect(accounts, &Accounts::changeAccount, this, &Squawk::modifyAccountRequest); connect(accounts, &Accounts::connectAccount, this, &Squawk::connectAccount); connect(accounts, &Accounts::disconnectAccount, this, &Squawk::disconnectAccount); connect(accounts, &Accounts::removeAccount, this, &Squawk::removeAccountRequest); accounts->show(); } else { accounts->show(); accounts->raise(); accounts->activateWindow(); } } void Squawk::onAccountsSizeChanged(unsigned int size) { if (size > 0) { m_ui->actionAddContact->setEnabled(true); m_ui->actionAddConference->setEnabled(true); } else { m_ui->actionAddContact->setEnabled(false); m_ui->actionAddConference->setEnabled(false); } } void Squawk::onNewContact() { NewContact* nc = new NewContact(rosterModel.accountsModel, this); connect(nc, &NewContact::accepted, this, &Squawk::onNewContactAccepted); connect(nc, &NewContact::rejected, nc, &NewContact::deleteLater); nc->exec(); } void Squawk::onNewConference() { JoinConference* jc = new JoinConference(rosterModel.accountsModel, this); connect(jc, &JoinConference::accepted, this, &Squawk::onJoinConferenceAccepted); connect(jc, &JoinConference::rejected, jc, &JoinConference::deleteLater); jc->exec(); } void Squawk::onNewContactAccepted() { NewContact* nc = static_cast(sender()); NewContact::Data value = nc->value(); emit addContactRequest(value.account, value.jid, value.name, value.groups); nc->deleteLater(); } void Squawk::onJoinConferenceAccepted() { JoinConference* jc = static_cast(sender()); JoinConference::Data value = jc->value(); emit addRoomRequest(value.account, value.jid, value.nick, value.password, value.autoJoin); jc->deleteLater(); } void Squawk::closeEvent(QCloseEvent* event) { if (accounts != 0) { accounts->close(); } for (Conversations::const_iterator itr = conversations.begin(), end = conversations.end(); itr != end; ++itr) { disconnect(itr->second, &Conversation::destroyed, this, &Squawk::onConversationClosed); itr->second->close(); } conversations.clear(); for (std::map::const_iterator itr = vCards.begin(), end = vCards.end(); itr != end; ++itr) { disconnect(itr->second, &VCard::destroyed, this, &Squawk::onVCardClosed); itr->second->close(); } vCards.clear(); QMainWindow::closeEvent(event); } void Squawk::onAccountsClosed(QObject* parent) { accounts = 0; } void Squawk::newAccount(const QMap& account) { rosterModel.addAccount(account); } void Squawk::onComboboxActivated(int index) { if (index != Shared::offline) { int size = rosterModel.accountsModel->rowCount(QModelIndex()); if (size > 0) { emit changeState(index); for (int i = 0; i < size; ++i) { Models::Account* acc = rosterModel.accountsModel->getAccount(i); if (acc->getState() == Shared::disconnected) { emit connectAccount(acc->getName()); } } } else { m_ui->comboBox->setCurrentIndex(Shared::offline); } } else { emit changeState(index); int size = rosterModel.accountsModel->rowCount(QModelIndex()); for (int i = 0; i != size; ++i) { Models::Account* acc = rosterModel.accountsModel->getAccount(i); if (acc->getState() != Shared::disconnected) { emit disconnectAccount(acc->getName()); } } } } void Squawk::changeAccount(const QString& account, const QMap& data) { for (QMap::const_iterator itr = data.begin(), end = data.end(); itr != end; ++itr) { QString attr = itr.key(); rosterModel.updateAccount(account, attr, *itr); } } void Squawk::addContact(const QString& account, const QString& jid, const QString& group, const QMap& 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); qDebug() << "expanding account " << ind.data(); 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); qDebug() << "expanding account " << ind.data(); 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& 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& 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(int state) { m_ui->comboBox->setCurrentIndex(state); } void Squawk::onRosterItemDoubleClicked(const QModelIndex& item) { if (item.isValid()) { Models::Item* node = static_cast(item.internalPointer()); Models::Contact* contact = 0; Models::Room* room = 0; QString res; Models::Roster::ElId* id = 0; switch (node->type) { case Models::Item::contact: contact = static_cast(node); id = new Models::Roster::ElId(contact->getAccountName(), contact->getJid()); break; case Models::Item::presence: contact = static_cast(node->parentItem()); id = new Models::Roster::ElId(contact->getAccountName(), contact->getJid()); res = node->getName(); break; case Models::Item::room: room = static_cast(node); id = new 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; Models::Contact::Messages deque; if (itr != conversations.end()) { conv = itr->second; } else if (contact != 0) { created = true; conv = new Chat(acc, contact); contact->getMessages(deque); } else if (room != 0) { created = true; conv = new Room(acc, room); room->getMessages(deque); if (!room->getJoined()) { emit setRoomJoined(id->account, id->name, true); } } if (conv != 0) { if (created) { conv->setAttribute(Qt::WA_DeleteOnClose); connect(conv, &Conversation::destroyed, this, &Squawk::onConversationClosed); connect(conv, qOverload(&Conversation::sendMessage), this, qOverload(&Squawk::onConversationMessage)); connect(conv, qOverload(&Conversation::sendMessage), this, qOverload(&Squawk::onConversationMessage)); connect(conv, &Conversation::requestArchive, this, &Squawk::onConversationRequestArchive); connect(conv, &Conversation::requestLocalFile, this, &Squawk::onConversationRequestLocalFile); connect(conv, &Conversation::downloadFile, this, &Squawk::onConversationDownloadFile); connect(conv, &Conversation::shown, this, &Squawk::onConversationShown); conversations.insert(std::make_pair(*id, conv)); if (created) { for (Models::Contact::Messages::const_iterator itr = deque.begin(), end = deque.end(); itr != end; ++itr) { conv->addMessage(*itr); } } } conv->show(); conv->raise(); conv->activateWindow(); if (res.size() > 0) { conv->setPalResource(res); } } } } } void Squawk::onConversationShown() { Conversation* conv = static_cast(sender()); rosterModel.dropMessages(conv->getAccount(), conv->getJid()); } void Squawk::onConversationClosed(QObject* parent) { Conversation* conv = static_cast(sender()); Models::Roster::ElId id(conv->getAccount(), conv->getJid()); Conversations::const_iterator itr = conversations.find(id); if (itr == conversations.end()) { qDebug() << "Conversation has been closed but can not be found among other opened conversations, application is most probably going to crash"; return; } if (conv->isMuc) { Room* room = static_cast(conv); if (!room->autoJoined()) { emit setRoomJoined(id.account, id.name, false); } } conversations.erase(itr); } void Squawk::onConversationDownloadFile(const QString& messageId, const QString& url) { Conversation* conv = static_cast(sender()); std::map>::iterator itr = requestedFiles.find(messageId); bool created = false; if (itr == requestedFiles.end()) { itr = requestedFiles.insert(std::make_pair(messageId, std::set())).first; created = true; } itr->second.insert(Models::Roster::ElId(conv->getAccount(), conv->getJid())); if (created) { emit downloadFileRequest(messageId, url); } } void Squawk::fileProgress(const QString& messageId, qreal value) { std::map>::const_iterator itr = requestedFiles.find(messageId); if (itr == requestedFiles.end()) { qDebug() << "fileProgress in UI Squawk but there is nobody waiting for that id" << messageId << ", skipping"; return; } else { const std::set& convs = itr->second; for (std::set::const_iterator cItr = convs.begin(), cEnd = convs.end(); cItr != cEnd; ++cItr) { const Models::Roster::ElId& id = *cItr; Conversations::const_iterator c = conversations.find(id); if (c != conversations.end()) { c->second->responseFileProgress(messageId, value); } } } } void Squawk::fileError(const QString& messageId, const QString& error) { std::map>::const_iterator itr = requestedFiles.find(messageId); if (itr == requestedFiles.end()) { qDebug() << "fileError in UI Squawk but there is nobody waiting for that id" << messageId << ", skipping"; return; } else { const std::set& convs = itr->second; for (std::set::const_iterator cItr = convs.begin(), cEnd = convs.end(); cItr != cEnd; ++cItr) { const Models::Roster::ElId& id = *cItr; Conversations::const_iterator c = conversations.find(id); if (c != conversations.end()) { c->second->fileError(messageId, error); } } requestedFiles.erase(itr); } } void Squawk::fileLocalPathResponse(const QString& messageId, const QString& path) { std::map>::const_iterator itr = requestedFiles.find(messageId); if (itr == requestedFiles.end()) { qDebug() << "fileLocalPathResponse in UI Squawk but there is nobody waiting for that path, skipping"; return; } else { const std::set& convs = itr->second; for (std::set::const_iterator cItr = convs.begin(), cEnd = convs.end(); cItr != cEnd; ++cItr) { const Models::Roster::ElId& id = *cItr; Conversations::const_iterator c = conversations.find(id); if (c != conversations.end()) { c->second->responseLocalFile(messageId, path); } } requestedFiles.erase(itr); } } void Squawk::onConversationRequestLocalFile(const QString& messageId, const QString& url) { Conversation* conv = static_cast(sender()); std::map>::iterator itr = requestedFiles.find(messageId); bool created = false; if (itr == requestedFiles.end()) { itr = requestedFiles.insert(std::make_pair(messageId, std::set())).first; created = true; } itr->second.insert(Models::Roster::ElId(conv->getAccount(), conv->getJid())); if (created) { emit fileLocalPathRequest(messageId, url); } } void Squawk::accountMessage(const QString& account, const Shared::Message& data) { const QString& from = data.getPenPalJid(); Conversations::iterator itr = conversations.find({account, from}); if (itr != conversations.end()) { Conversation* conv = itr->second; conv->addMessage(data); QApplication::alert(conv); if (conv->isMinimized()) { rosterModel.addMessage(account, data); if (!data.getForwarded()) { notify(account, data); } } } else { rosterModel.addMessage(account, data); if (!data.getForwarded()) { QApplication::alert(this); notify(account, 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())); 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"); } if (msg.getType() == Shared::Message::groupChat) { args << msg.getFromResource() + " from " + name; } else { args << name; } args << QString(msg.getBody()); args << QStringList(); args << QVariantMap(); args << 3000; dbus.callWithArgumentList(QDBus::AutoDetect, "Notify", args); } void Squawk::onConversationMessage(const Shared::Message& msg) { Conversation* conv = static_cast(sender()); emit sendMessage(conv->getAccount(), msg); } void Squawk::onConversationMessage(const Shared::Message& msg, const QString& path) { Conversation* conv = static_cast(sender()); std::map>::iterator itr = requestedFiles.insert(std::make_pair(msg.getId(), std::set())).first; itr->second.insert(Models::Roster::ElId(conv->getAccount(), conv->getJid())); emit sendMessage(conv->getAccount(), msg, path); } void Squawk::onConversationRequestArchive(const QString& before) { Conversation* conv = static_cast(sender()); requestArchive(conv->getAccount(), conv->getJid(), 20, before); //TODO amount as a settings value } void Squawk::responseArchive(const QString& account, const QString& jid, const std::list& list) { Models::Roster::ElId id(account, jid); Conversations::const_iterator itr = conversations.find(id); if (itr != conversations.end()) { itr->second->responseArchive(list); } } 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); disconnect(conv, &Conversation::requestArchive, this, &Squawk::onConversationRequestArchive); disconnect(conv, &Conversation::shown, this, &Squawk::onConversationShown); conv->close(); conversations.erase(lItr); } else { ++itr; } } rosterModel.removeAccount(account); } void Squawk::onRosterContextMenu(const QPoint& point) { QModelIndex index = m_ui->roster->indexAt(point); if (index.isValid()) { Models::Item* item = static_cast(index.internalPointer()); contextMenu->clear(); bool hasMenu = false; bool active = item->getAccountConnectionState() == Shared::connected; switch (item->type) { case Models::Item::account: { Models::Account* acc = static_cast(item); hasMenu = true; QString name = acc->getName(); if (acc->getState() != Shared::disconnected) { QAction* con = contextMenu->addAction(Shared::icon("network-disconnect"), tr("Disconnect")); con->setEnabled(active); connect(con, &QAction::triggered, [this, name]() { emit disconnectAccount(name); }); } else { QAction* con = contextMenu->addAction(Shared::icon("network-connect"), tr("Connect")); connect(con, &QAction::triggered, [this, name]() { emit connectAccount(name); }); } QAction* card = contextMenu->addAction(Shared::icon("user-properties"), tr("VCard")); card->setEnabled(active); 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); }); } break; case Models::Item::contact: { Models::Contact* cnt = static_cast(item); hasMenu = true; QAction* dialog = contextMenu->addAction(Shared::icon("mail-message"), tr("Open dialog")); dialog->setEnabled(active); connect(dialog, &QAction::triggered, [this, index]() { onRosterItemDoubleClicked(index); }); Shared::SubscriptionState state = cnt->getState(); switch (state) { case Shared::both: case Shared::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(), ""); }); } break; case Shared::from: case Shared::unknown: case Shared::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(), ""); }); } } 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]() { QInputDialog* dialog = new QInputDialog(this); connect(dialog, &QDialog::accepted, [this, dialog, cntName, accName, cntJID]() { QString newName = dialog->textValue(); if (newName != cntName) { emit renameContactRequest(accName, cntJID, 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->setTextValue(cntName); dialog->exec(); }); QMenu* groupsMenu = contextMenu->addMenu(Shared::icon("group"), tr("Groups")); std::deque groupList = rosterModel.groupList(accName); for (QString groupName : groupList) { QAction* gr = groupsMenu->addAction(groupName); gr->setCheckable(true); gr->setChecked(rosterModel.groupHasContact(accName, groupName, cntJID)); gr->setEnabled(active); connect(gr, &QAction::toggled, [this, accName, groupName, cntJID](bool checked) { if (checked) { emit addContactToGroupRequest(accName, cntJID, groupName); } else { emit removeContactFromGroupRequest(accName, cntJID, groupName); } }); } QAction* newGroup = groupsMenu->addAction(Shared::icon("group-new"), tr("New group")); newGroup->setEnabled(active); connect(newGroup, &QAction::triggered, [this, accName, cntJID]() { QInputDialog* dialog = new QInputDialog(this); connect(dialog, &QDialog::accepted, [this, dialog, accName, cntJID]() { emit addContactToGroupRequest(accName, cntJID, 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->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)); 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()); }); } break; case Models::Item::room: { Models::Room* room = static_cast(item); hasMenu = true; QAction* dialog = contextMenu->addAction(Shared::icon("mail-message"), tr("Open conversation")); dialog->setEnabled(active); connect(dialog, &QAction::triggered, [this, index]() { onRosterItemDoubleClicked(index); }); Models::Roster::ElId id(room->getAccountName(), room->getJid()); 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()) { //to leave the room if it's not opened in a conversation window emit setRoomJoined(id.account, id.name, 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()) { //to join the room if it's not already joined emit setRoomJoined(id.account, id.name, true); } }); } QAction* remove = contextMenu->addAction(Shared::icon("edit-delete"), tr("Remove")); remove->setEnabled(active); connect(remove, &QAction::triggered, [this, id]() { emit removeRoomRequest(id.account, id.name); }); } break; default: break; } if (hasMenu) { contextMenu->popup(m_ui->roster->viewport()->mapToGlobal(point)); } } } void Squawk::addRoom(const QString& account, const QString jid, const QMap& data) { rosterModel.addRoom(account, jid, data); } void Squawk::changeRoom(const QString& account, const QString jid, const QMap& 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& data) { rosterModel.addRoomParticipant(account, jid, name, data); } void Squawk::changeRoomParticipant(const QString& account, const QString& jid, const QString& name, const QMap& 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::const_iterator itr = vCards.find(jid); if (itr != vCards.end()) { itr->second->setVCard(card); itr->second->hideProgress(); } } void Squawk::onVCardClosed() { VCard* vCard = static_cast(sender()); std::map::const_iterator itr = vCards.find(vCard->getJid()); if (itr == vCards.end()) { qDebug() << "VCard has been closed but can not be found among other opened vCards, application is most probably going to crash"; return; } vCards.erase(itr); } void Squawk::onActivateVCard(const QString& account, const QString& jid, bool edition) { std::map::const_iterator itr = vCards.find(jid); VCard* card; Models::Contact::Messages deque; if (itr != vCards.end()) { card = itr->second; } else { card = new VCard(jid, edition); if (edition) { card->setWindowTitle(tr("%1 account card").arg(account)); } else { card->setWindowTitle(tr("%1 contact card").arg(jid)); } card->setAttribute(Qt::WA_DeleteOnClose); vCards.insert(std::make_pair(jid, card)); connect(card, &VCard::destroyed, this, &Squawk::onVCardClosed); connect(card, &VCard::saveVCard, std::bind( &Squawk::onVCardSave, this, std::placeholders::_1, account)); } card->show(); card->raise(); card->activateWindow(); card->showProgress(tr("Downloading vCard")); emit requestVCard(account, jid); } void Squawk::onVCardSave(const Shared::VCard& card, const QString& account) { VCard* widget = static_cast(sender()); emit uploadVCard(account, card); widget->deleteLater(); } void Squawk::readSettings() { QSettings settings; settings.beginGroup("ui"); settings.beginGroup("window"); if (settings.contains("geometry")) { restoreGeometry(settings.value("geometry").toByteArray()); } if (settings.contains("state")) { restoreState(settings.value("state").toByteArray()); } settings.endGroup(); if (settings.contains("availability")) { int avail = settings.value("availability").toInt(); m_ui->comboBox->setCurrentIndex(avail); emit stateChanged(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("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::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 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(); } void Squawk::onItemCollepsed(const QModelIndex& index) { QSettings settings; Models::Item* item = static_cast(index.internalPointer()); switch (item->type) { case Models::Item::account: settings.setValue("ui/roster/" + item->getName() + "/expanded", false); break; case Models::Item::group: { QModelIndex accInd = rosterModel.parent(index); Models::Account* account = rosterModel.accountsModel->getAccount(accInd.row()); settings.setValue("ui/roster/" + account->getName() + "/" + item->getName() + "/expanded", false); } break; default: break; } }