single window mode

This commit is contained in:
Blue 2020-04-11 23:00:15 +03:00
parent b95028e33e
commit a77dfd191a
13 changed files with 345 additions and 74 deletions

View File

@ -2,6 +2,7 @@
## Squawk 0.1.4 (UNRELEASED)
### New features
- message line now is in the same window with roster (new window dialog is still able to opened on double click)
- several ways to manage your account password:
- store it in plain text with the config (like it always was)
- store it in config jammed (local hashing with the constant seed, not secure at all but might look like it is)

View File

@ -67,9 +67,6 @@ Here is the list of keys you can pass to configuration phase of `cmake ..`.
- `SYSTEM_QXMPP` - `True` tries to link against `qxmpp` installed in the system, `False` builds bundled `qxmpp` library (default is `True`)
- `WITH_KWALLET` - `True` builds the `KWallet` capability module if `KWallet` is installed and if not goes to `False`. `False` disables `KWallet` support (default is `True`)
Each key is supposed to be passed like that
## License
This project is licensed under the GPLv3 License - see the [LICENSE.md](LICENSE.md) file for details

View File

@ -1,7 +1,6 @@
cmake_minimum_required(VERSION 3.0)
project(pse)
if (WITH_KWALLET)
set(CMAKE_AUTOMOC ON)
@ -36,7 +35,3 @@ if (WITH_KWALLET)
install(TARGETS kwalletWrapper DESTINATION ${CMAKE_INSTALL_LIBDIR})
endif()

View File

@ -386,6 +386,16 @@ bool Models::Roster::ElId::operator <(const Models::Roster::ElId& other) const
}
}
bool Models::Roster::ElId::operator!=(const Models::Roster::ElId& other) const
{
return !(operator == (other));
}
bool Models::Roster::ElId::operator==(const Models::Roster::ElId& other) const
{
return (account == other.account) && (name == other.name);
}
void Models::Roster::onAccountDataChanged(const QModelIndex& tl, const QModelIndex& br, const QVector<int>& roles)
{
if (tl.column() == 0) {

View File

@ -109,6 +109,8 @@ public:
const QString name;
bool operator < (const ElId& other) const;
bool operator == (const ElId& other) const;
bool operator != (const ElId& other) const;
};
};

View File

@ -32,7 +32,8 @@ Squawk::Squawk(QWidget *parent) :
requestedFiles(),
vCards(),
requestedAccountsForPasswords(),
prompt(0)
prompt(0),
currentConversation(0)
{
m_ui->setupUi(this);
m_ui->roster->setModel(&rosterModel);
@ -55,6 +56,7 @@ Squawk::Squawk(QWidget *parent) :
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.accountsModel, &Models::Accounts::sizeChanged, this, &Squawk::onAccountsSizeChanged);
//m_ui->mainToolBar->addWidget(m_ui->comboBox);
@ -74,6 +76,10 @@ Squawk::Squawk(QWidget *parent) :
restoreState(settings.value("state").toByteArray());
}
settings.endGroup();
if (settings.contains("splitter")) {
m_ui->splitter->restoreState(settings.value("splitter").toByteArray());
}
settings.endGroup();
}
@ -344,16 +350,7 @@ void Squawk::onRosterItemDoubleClicked(const QModelIndex& item)
if (conv != 0) {
if (created) {
conv->setAttribute(Qt::WA_DeleteOnClose);
connect(conv, &Conversation::destroyed, this, &Squawk::onConversationClosed);
connect(conv, qOverload<const Shared::Message&>(&Conversation::sendMessage), this, qOverload<const Shared::Message&>(&Squawk::onConversationMessage));
connect(conv, qOverload<const Shared::Message&, const QString&>(&Conversation::sendMessage),
this, qOverload<const Shared::Message&, const QString&>(&Squawk::onConversationMessage));
connect(conv, &Conversation::requestArchive, this, &Squawk::onConversationRequestArchive);
connect(conv, &Conversation::requestLocalFile, this, &Squawk::onConversationRequestLocalFile);
connect(conv, &Conversation::downloadFile, this, &Squawk::onConversationDownloadFile);
connect(conv, &Conversation::shown, this, &Squawk::onConversationShown);
subscribeConversation(conv);
conversations.insert(std::make_pair(*id, conv));
if (created) {
@ -372,6 +369,7 @@ void Squawk::onRosterItemDoubleClicked(const QModelIndex& item)
}
}
delete id;
}
}
}
@ -387,9 +385,8 @@ void Squawk::onConversationClosed(QObject* parent)
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()) {
qDebug() << "Conversation has been closed but can not be found among other opened conversations, application is most probably going to crash";
return;
if (itr != conversations.end()) {
conversations.erase(itr);
}
if (conv->isMuc) {
Room* room = static_cast<Room*>(conv);
@ -397,7 +394,6 @@ void Squawk::onConversationClosed(QObject* parent)
emit setRoomJoined(id.account, id.name, false);
}
}
conversations.erase(itr);
}
void Squawk::onConversationDownloadFile(const QString& messageId, const QString& url)
@ -429,6 +425,9 @@ void Squawk::fileProgress(const QString& messageId, qreal value)
if (c != conversations.end()) {
c->second->responseFileProgress(messageId, value);
}
if (currentConversation != 0 && currentConversation->getId() == id) {
currentConversation->responseFileProgress(messageId, value);
}
}
}
}
@ -447,6 +446,9 @@ void Squawk::fileError(const QString& messageId, const QString& error)
if (c != conversations.end()) {
c->second->fileError(messageId, error);
}
if (currentConversation != 0 && currentConversation->getId() == id) {
currentConversation->fileError(messageId, error);
}
}
requestedFiles.erase(itr);
}
@ -466,6 +468,9 @@ void Squawk::fileLocalPathResponse(const QString& messageId, const QString& path
if (c != conversations.end()) {
c->second->responseLocalFile(messageId, path);
}
if (currentConversation != 0 && currentConversation->getId() == id) {
currentConversation->responseLocalFile(messageId, path);
}
}
requestedFiles.erase(itr);
@ -490,18 +495,33 @@ void Squawk::onConversationRequestLocalFile(const QString& messageId, const QStr
void Squawk::accountMessage(const QString& account, const Shared::Message& data)
{
const QString& from = data.getPenPalJid();
Conversations::iterator itr = conversations.find({account, from});
Models::Roster::ElId id({account, from});
Conversations::iterator itr = conversations.find(id);
bool found = false;
if (currentConversation != 0 && currentConversation->getId() == id) {
currentConversation->addMessage(data);
QApplication::alert(this);
if (!isVisible() && !data.getForwarded()) {
notify(account, data);
}
found = true;
}
if (itr != conversations.end()) {
Conversation* conv = itr->second;
conv->addMessage(data);
QApplication::alert(conv);
if (conv->isMinimized()) {
if (!found && conv->isMinimized()) {
rosterModel.addMessage(account, data);
}
if (!conv->isVisible() && !data.getForwarded()) {
notify(account, data);
}
} else {
found = true;
}
if (!found) {
rosterModel.addMessage(account, data);
if (!data.getForwarded()) {
QApplication::alert(this);
@ -512,14 +532,26 @@ void Squawk::accountMessage(const QString& account, const Shared::Message& data)
void Squawk::changeMessage(const QString& account, const QString& jid, const QString& id, const QMap<QString, QVariant>& data)
{
Conversations::iterator itr = conversations.find({account, jid});
Models::Roster::ElId eid({account, jid});
bool found = false;
if (currentConversation != 0 && currentConversation->getId() == eid) {
currentConversation->changeMessage(id, data);
QApplication::alert(this);
found = true;
}
Conversations::iterator itr = conversations.find(eid);
if (itr != conversations.end()) {
Conversation* conv = itr->second;
conv->changeMessage(id, data);
if (conv->isMinimized()) {
if (!found && conv->isMinimized()) {
rosterModel.changeMessage(account, jid, id, data);
}
} else {
found = true;
}
if (!found) {
rosterModel.changeMessage(account, jid, id, data);
}
}
@ -559,13 +591,37 @@ void Squawk::onConversationMessage(const Shared::Message& msg)
{
Conversation* conv = static_cast<Conversation*>(sender());
emit sendMessage(conv->getAccount(), msg);
Models::Roster::ElId id = conv->getId();
if (currentConversation != 0 && currentConversation->getId() == id) {
if (conv == currentConversation) {
Conversations::iterator itr = conversations.find(id);
if (itr != conversations.end()) {
itr->second->addMessage(msg);
}
} else {
currentConversation->addMessage(msg);
}
}
}
void Squawk::onConversationMessage(const Shared::Message& msg, const QString& path)
{
Conversation* conv = static_cast<Conversation*>(sender());
Models::Roster::ElId id = conv->getId();
std::map<QString, std::set<Models::Roster::ElId>>::iterator itr = requestedFiles.insert(std::make_pair(msg.getId(), std::set<Models::Roster::ElId>())).first;
itr->second.insert(Models::Roster::ElId(conv->getAccount(), conv->getJid()));
itr->second.insert(id);
if (currentConversation != 0 && currentConversation->getId() == id) {
if (conv == currentConversation) {
Conversations::iterator itr = conversations.find(id);
if (itr != conversations.end()) {
itr->second->appendMessageWithUpload(msg, path);
}
} else {
currentConversation->appendMessageWithUpload(msg, path);
}
}
emit sendMessage(conv->getAccount(), msg, path);
}
@ -580,6 +636,10 @@ void Squawk::responseArchive(const QString& account, const QString& jid, const s
{
Models::Roster::ElId id(account, jid);
if (currentConversation != 0 && currentConversation->getId() == id) {
currentConversation->responseArchive(list);
}
Conversations::const_iterator itr = conversations.find(id);
if (itr != conversations.end()) {
itr->second->responseArchive(list);
@ -603,6 +663,13 @@ void Squawk::removeAccount(const QString& account)
++itr;
}
}
if (currentConversation != 0 && currentConversation->getAccount() == account) {
currentConversation->deleteLater();
currentConversation = 0;
m_ui->filler->show();
}
rosterModel.removeAccount(account);
}
@ -761,7 +828,9 @@ void Squawk::onRosterContextMenu(const QPoint& point)
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
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);
}
});
@ -770,7 +839,9 @@ void Squawk::onRosterContextMenu(const QPoint& point)
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
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);
}
});
@ -897,7 +968,6 @@ void Squawk::readSettings()
} // need to fix that
settings.endArray();
}
settings.endGroup();
}
@ -910,6 +980,8 @@ void Squawk::writeSettings()
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());
@ -1008,3 +1080,93 @@ void Squawk::onPasswordPromptRejected()
emit responsePassword(requestedAccountsForPasswords.front(), prompt->textValue());
onPasswordPromptDone();
}
void Squawk::subscribeConversation(Conversation* conv)
{
connect(conv, &Conversation::destroyed, this, &Squawk::onConversationClosed);
connect(conv, qOverload<const Shared::Message&>(&Conversation::sendMessage), this, qOverload<const Shared::Message&>(&Squawk::onConversationMessage));
connect(conv, qOverload<const Shared::Message&, const QString&>(&Conversation::sendMessage),
this, qOverload<const Shared::Message&, const QString&>(&Squawk::onConversationMessage));
connect(conv, &Conversation::requestArchive, this, &Squawk::onConversationRequestArchive);
connect(conv, &Conversation::requestLocalFile, this, &Squawk::onConversationRequestLocalFile);
connect(conv, &Conversation::downloadFile, this, &Squawk::onConversationDownloadFile);
connect(conv, &Conversation::shown, this, &Squawk::onConversationShown);
}
void Squawk::onRosterSelectionChanged(const QModelIndex& current, const QModelIndex& previous)
{
if (current.isValid()) {
Models::Item* node = static_cast<Models::Item*>(current.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<Models::Contact*>(node);
id = new 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();
break;
case Models::Item::room:
room = static_cast<Models::Room*>(node);
id = new Models::Roster::ElId(room->getAccountName(), room->getJid());
break;
default:
break;
}
if (id != 0) {
if (currentConversation != 0) {
if (currentConversation->getJid() == id->name) {
if (contact != 0) {
currentConversation->setPalResource(res);
}
} else {
currentConversation->deleteLater();
}
} else {
m_ui->filler->hide();
}
Models::Account* acc = rosterModel.getAccount(id->account);
Models::Contact::Messages deque;
if (contact != 0) {
currentConversation = new Chat(acc, contact);
contact->getMessages(deque);
} else if (room != 0) {
currentConversation = new Room(acc, room);
room->getMessages(deque);
if (!room->getJoined()) {
emit setRoomJoined(id->account, id->name, true);
}
}
if (!testAttribute(Qt::WA_TranslucentBackground)) {
currentConversation->setFeedFrames(true, false, true, true);
}
subscribeConversation(currentConversation);
for (Models::Contact::Messages::const_iterator itr = deque.begin(), end = deque.end(); itr != end; ++itr) {
currentConversation->addMessage(*itr);
}
if (res.size() > 0) {
currentConversation->setPalResource(res);
}
m_ui->splitter->insertWidget(1, currentConversation);
delete id;
} else {
if (currentConversation != 0) {
currentConversation->deleteLater();
currentConversation = 0;
m_ui->filler->show();
}
}
}
}

View File

@ -124,6 +124,7 @@ private:
std::map<QString, VCard*> vCards;
std::deque<QString> requestedAccountsForPasswords;
QInputDialog* prompt;
Conversation* currentConversation;
protected:
void closeEvent(QCloseEvent * event) override;
@ -153,10 +154,12 @@ private slots:
void onItemCollepsed(const QModelIndex& index);
void onPasswordPromptAccepted();
void onPasswordPromptRejected();
void onRosterSelectionChanged(const QModelIndex& current, const QModelIndex& previous);
private:
void checkNextAccountForPassword();
void onPasswordPromptDone();
void subscribeConversation(Conversation* conv);
};
#endif // SQUAWK_H

View File

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>385</width>
<height>508</height>
<width>718</width>
<height>720</height>
</rect>
</property>
<property name="windowTitle">
@ -17,7 +17,7 @@
<enum>Qt::ToolButtonFollowStyle</enum>
</property>
<widget class="QWidget" name="centralWidget">
<layout class="QGridLayout" name="gridLayout">
<layout class="QVBoxLayout" name="verticalLayout">
<property name="leftMargin">
<number>0</number>
</property>
@ -30,42 +30,112 @@
<property name="bottomMargin">
<number>0</number>
</property>
<item row="0" column="0">
<widget class="QComboBox" name="comboBox">
<property name="editable">
<item>
<widget class="QSplitter" name="splitter">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="handleWidth">
<number>1</number>
</property>
<property name="childrenCollapsible">
<bool>false</bool>
</property>
<property name="currentText">
<string/>
</property>
<property name="currentIndex">
<number>-1</number>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QTreeView" name="roster">
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Sunken</enum>
</property>
<property name="uniformRowHeights">
<bool>true</bool>
</property>
<property name="animated">
<bool>true</bool>
</property>
<property name="allColumnsShowFocus">
<bool>true</bool>
</property>
<property name="expandsOnDoubleClick">
<bool>false</bool>
</property>
<attribute name="headerVisible">
<bool>false</bool>
</attribute>
<widget class="QWidget" name="widget" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>200</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>400</width>
<height>16777215</height>
</size>
</property>
<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>
<item row="2" column="1">
<widget class="QTreeView" name="roster">
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Sunken</enum>
</property>
<property name="uniformRowHeights">
<bool>true</bool>
</property>
<property name="animated">
<bool>true</bool>
</property>
<property name="allColumnsShowFocus">
<bool>true</bool>
</property>
<property name="expandsOnDoubleClick">
<bool>false</bool>
</property>
<attribute name="headerVisible">
<bool>false</bool>
</attribute>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="comboBox">
<property name="editable">
<bool>false</bool>
</property>
<property name="currentText">
<string/>
</property>
<property name="currentIndex">
<number>-1</number>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="filler" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>2</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:26pt;&quot;&gt;Please select a contact to start chatting&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
@ -75,8 +145,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>385</width>
<height>22</height>
<width>718</width>
<height>27</height>
</rect>
</property>
<widget class="QMenu" name="menuSettings">

View File

@ -427,6 +427,12 @@ void MessageLine::fileError(const QString& messageId, const QString& error)
}
void MessageLine::appendMessageWithUpload(const Shared::Message& msg, const QString& path)
{
appendMessageWithUploadNoSiganl(msg, path);
emit uploadFile(msg, path);
}
void MessageLine::appendMessageWithUploadNoSiganl(const Shared::Message& msg, const QString& path)
{
message(msg, true);
QString id = msg.getId();
@ -436,9 +442,9 @@ void MessageLine::appendMessageWithUpload(const Shared::Message& msg, const QStr
ui->showComment(tr("Uploading..."));
uploading.insert(std::make_pair(id, ui));
uploadPaths.insert(std::make_pair(id, path));
emit uploadFile(msg, path);
}
void MessageLine::onUpload()
{
//TODO retry

View File

@ -53,6 +53,7 @@ public:
void fileError(const QString& messageId, const QString& error);
void fileProgress(const QString& messageId, qreal progress);
void appendMessageWithUpload(const Shared::Message& msg, const QString& path);
void appendMessageWithUploadNoSiganl(const Shared::Message& msg, const QString& path);
void removeMessage(const QString& messageId);
void setMyAvatarPath(const QString& p_path);
void setPalAvatar(const QString& jid, const QString& path);

View File

@ -210,6 +210,11 @@ void Conversation::onEnterPressed()
}
}
void Conversation::appendMessageWithUpload(const Shared::Message& data, const QString& path)
{
line->appendMessageWithUploadNoSiganl(data, path);
}
void Conversation::onMessagesResize(int amount)
{
manualSliderChange = true;
@ -334,6 +339,11 @@ void Conversation::responseLocalFile(const QString& messageId, const QString& pa
line->responseLocalFile(messageId, path);
}
Models::Roster::ElId Conversation::getId() const
{
return {getAccount(), getJid()};
}
void Conversation::addAttachedFile(const QString& path)
{
QMimeDatabase db;
@ -397,6 +407,10 @@ void Conversation::onTextEditDocSizeChanged(const QSizeF& size)
m_ui->messageEditor->setMaximumHeight(int(size.height()));
}
void Conversation::setFeedFrames(bool top, bool right, bool bottom, bool left)
{
static_cast<DropShadowEffect*>(m_ui->scrollArea->graphicsEffect())->setFrame(top, right, bottom, left);
}
bool VisibilityCatcher::eventFilter(QObject* obj, QEvent* event)
{

View File

@ -26,6 +26,7 @@
#include "shared/message.h"
#include "order.h"
#include "ui/models/account.h"
#include "ui/models/roster.h"
#include "ui/utils/messageline.h"
#include "ui/utils/resizer.h"
#include "ui/utils/flowlayout.h"
@ -72,6 +73,7 @@ public:
QString getJid() const;
QString getAccount() const;
QString getPalResource() const;
Models::Roster::ElId getId() const;
virtual void addMessage(const Shared::Message& data);
void setPalResource(const QString& res);
@ -82,6 +84,8 @@ public:
void responseFileProgress(const QString& messageId, qreal progress);
virtual void setAvatar(const QString& path);
void changeMessage(const QString& id, const QMap<QString, QVariant>& data);
void setFeedFrames(bool top, bool right, bool bottom, bool left);
virtual void appendMessageWithUpload(const Shared::Message& data, const QString& path);
signals:
void sendMessage(const Shared::Message& message);

View File

@ -10,6 +10,12 @@
<height>658</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>2</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QVBoxLayout" name="horizontalLayout">
<property name="spacing">
<number>0</number>
@ -206,7 +212,7 @@
<x>0</x>
<y>0</y>
<width>520</width>
<height>389</height>
<height>392</height>
</rect>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_2">