diff --git a/src/apps/qml/main.cpp b/src/apps/qml/main.cpp --- a/src/apps/qml/main.cpp +++ b/src/apps/qml/main.cpp @@ -112,7 +112,7 @@ (void)Ruqola::self()->notification(); #endif if (!loadAccount.isEmpty()) { - Ruqola::self()->setCurrentAccount(loadAccount); + Ruqola::self()->setCurrentAccount(loadAccount, true /*showLastRoom*/); } // For desktop we'd like to use native text rendering diff --git a/src/apps/widget/main.cpp b/src/apps/widget/main.cpp --- a/src/apps/widget/main.cpp +++ b/src/apps/widget/main.cpp @@ -117,7 +117,7 @@ #endif if (!loadAccount.isEmpty()) { - Ruqola::self()->setCurrentAccount(loadAccount); + Ruqola::self()->setCurrentAccount(loadAccount, false /*showLastRoom*/); } KDBusService service(KDBusService::Unique); diff --git a/src/core/accountmanager.h b/src/core/accountmanager.h --- a/src/core/accountmanager.h +++ b/src/core/accountmanager.h @@ -48,8 +48,7 @@ RocketChatAccountFilterProxyModel *rocketChatAccountProxyModel() const; void addAccount(RocketChatAccount *account); - - void setCurrentAccount(const QString &accountName); + void setCurrentAccount(const QString &accountName, bool showLastRoom = true); Q_REQUIRED_RESULT QString currentAccount() const; void selectAccount(const QString &accountName); @@ -60,7 +59,7 @@ Q_SIGNALS: void logoutAccountDone(const QString &accountName); void updateNotification(bool hasAlert, int nbUnread, const QString &accountName); - void currentAccountChanged(); + void currentAccountChanged(bool showLastRoom); private: Q_DISABLE_COPY(AccountManager) diff --git a/src/core/accountmanager.cpp b/src/core/accountmanager.cpp --- a/src/core/accountmanager.cpp +++ b/src/core/accountmanager.cpp @@ -63,7 +63,7 @@ void AccountManager::slotSwitchToAccountAndRoomName(const QString &accountName, const QString &roomName, const QString &channelType) { - setCurrentAccount(accountName); + setCurrentAccount(accountName, false /*showLastRoom*/); QString linkRoom; if (channelType == QLatin1Char('c')) { linkRoom = QStringLiteral("ruqola:/room/%1").arg(roomName); @@ -174,16 +174,16 @@ } } -void AccountManager::setCurrentAccount(const QString &accountName) +void AccountManager::setCurrentAccount(const QString &accountName, bool showLastRoom) { RocketChatAccount *account = mRocketChatAccountModel->account(accountName); if (account) { if (mCurrentAccount != account) { QSettings settings; settings.setValue(QStringLiteral("currentAccount"), accountName); settings.sync(); mCurrentAccount = account; - Q_EMIT currentAccountChanged(); + Q_EMIT currentAccountChanged(showLastRoom); } } else { qCWarning(RUQOLA_LOG) << "AccountName " << accountName << " is not found on system. Fallback to default one."; @@ -203,7 +203,7 @@ } else { //TODO create new dummy account ! } - Q_EMIT currentAccountChanged(); + Q_EMIT currentAccountChanged(false /*showLastRoom*/); } RocketChatAccountModel *AccountManager::rocketChatAccountModel() const diff --git a/src/core/autotests/accountschannelsmodeltest.cpp b/src/core/autotests/accountschannelsmodeltest.cpp --- a/src/core/autotests/accountschannelsmodeltest.cpp +++ b/src/core/autotests/accountschannelsmodeltest.cpp @@ -61,7 +61,7 @@ acct->setAccountName(newAcctName); QCOMPARE(model.data(newAcctIndex).toString(), newAcctName); - Ruqola::self()->setCurrentAccount(newAcctName); + Ruqola::self()->setCurrentAccount(newAcctName, false /*showLastRoom*/); const auto newRoomId = QStringLiteral("RoomId"); const auto newRoomName = QStringLiteral("Room Name"); diff --git a/src/core/model/accountschannelsmodel.h b/src/core/model/accountschannelsmodel.h --- a/src/core/model/accountschannelsmodel.h +++ b/src/core/model/accountschannelsmodel.h @@ -30,15 +30,28 @@ class LIBRUQOLACORE_EXPORT AccountsChannelsModel : public QAbstractItemModel { + Q_OBJECT public: explicit AccountsChannelsModel(QObject *parent = nullptr); + void setFilterString(const QString &filter); + bool isFiltered() const; + QModelIndex index(int row, int column, const QModelIndex &parent = {}) const override; QModelIndex parent(const QModelIndex &child) const override; int rowCount(const QModelIndex &parent = {}) const override; int columnCount(const QModelIndex &parent = {}) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + QModelIndex findRoomById(const QString &roomId) const; + QModelIndex findRoomByName(const QString &roomName) const; + QModelIndex findRoomByRole(int role, const QVariant &value) const; + + QString accountForIndex(const QModelIndex &index) const; + +Q_SIGNALS: + void modelChanged(); + private: QModelIndex modelRoot(QAbstractItemModel *model) const; QAbstractItemModel *rootModel(const QModelIndex &root) const; @@ -51,6 +64,23 @@ std::function root; }; QVector mProxied; + bool mFiltered = false; + + enum Visit + { + Continue, + Abort + }; + template + void visitProxied(const Fn &fn) const + { + for (const auto &proxied: mProxied) + { + if (auto model = qobject_cast(proxied.model)) + if (fn(model, proxied.root()) == Abort) + return; + } + } }; #endif // ACCOUNTSCHANNELSMODEL_H diff --git a/src/core/model/accountschannelsmodel.cpp b/src/core/model/accountschannelsmodel.cpp --- a/src/core/model/accountschannelsmodel.cpp +++ b/src/core/model/accountschannelsmodel.cpp @@ -82,9 +82,24 @@ } } +void AccountsChannelsModel::setFilterString(const QString &filter) +{ + mFiltered = !filter.isEmpty(); + visitProxied([&](RoomFilterProxyModel *m, const QModelIndex &){ + m->setFilterString(filter); + return Continue; + }); +} + +bool AccountsChannelsModel::isFiltered() const +{ + return mFiltered; +} + QModelIndex AccountsChannelsModel::index(int row, int column, const QModelIndex &parent) const { - if (auto model = rootModel(parent)) { + const auto model = rootModel(parent); + if (model && column == 0 && row >= 0 && row < model->rowCount()) { return createIndex(row, column, model); } return {}; @@ -130,6 +145,43 @@ return model->index(index.row(), index.column()).data(role); } +QModelIndex AccountsChannelsModel::findRoomById(const QString &roomId) const +{ + return findRoomByRole(RoomModel::RoomID, roomId); +} + +QModelIndex AccountsChannelsModel::findRoomByName(const QString &roomName) const +{ + return findRoomByRole(RoomModel::RoomName, roomName); +} + +QModelIndex AccountsChannelsModel::findRoomByRole(int role, const QVariant &value) const +{ + QModelIndex found; + visitProxied([&](RoomFilterProxyModel *m, const QModelIndex &root){ + for (int i = 0, count = m->rowCount(); i < count; ++i) + { + if (m->index(i, 0).data(role) == value) + { + found = index(i, 0, root); + return Abort; + } + } + return Continue; + }); + return found; +} + +QString AccountsChannelsModel::accountForIndex(const QModelIndex &index) const +{ + if (!index.isValid()) + return {}; + + const auto parent = index.parent(); + const auto accountIndex = parent.isValid() ? parent : index; + return accountIndex.data(RocketChatAccountModel::Name).toString(); +} + QModelIndex AccountsChannelsModel::modelRoot(QAbstractItemModel *model) const { const auto find = [model](const ProxyIndex &i){ @@ -156,27 +208,32 @@ beginInsertRows(modelRoot(model), first, last); }); connect(model, &QAbstractItemModel::rowsInserted, this, &AccountsChannelsModel::endInsertRows); + connect(model, &QAbstractItemModel::rowsInserted, this, &AccountsChannelsModel::modelChanged); connect(model, &QAbstractItemModel::rowsAboutToBeRemoved, this, [this, model](const QModelIndex &parent, int first, int last) { Q_ASSERT(!parent.isValid()); beginRemoveRows(modelRoot(model), first, last); }); connect(model, &QAbstractItemModel::rowsRemoved, this, &AccountsChannelsModel::endRemoveRows); + connect(model, &QAbstractItemModel::rowsRemoved, this, &AccountsChannelsModel::modelChanged); connect(model, &QAbstractItemModel::rowsAboutToBeMoved, this, [this, model](const QModelIndex &src, int sf, int sl, const QModelIndex &dst, int df) { Q_ASSERT(!src.isValid() && !dst.isValid()); const auto idx = modelRoot(model); beginMoveRows(idx, sf, sl, idx, df); }); connect(model, &QAbstractItemModel::rowsMoved, this, &AccountsChannelsModel::endMoveRows); + connect(model, &QAbstractItemModel::rowsMoved, this, &AccountsChannelsModel::modelChanged); connect(model, &QAbstractItemModel::modelAboutToBeReset, this, &AccountsChannelsModel::beginResetModel); connect(model, &QAbstractItemModel::modelReset, this, &AccountsChannelsModel::endResetModel); + connect(model, &QAbstractItemModel::modelReset, this, &AccountsChannelsModel::modelChanged); connect(model, &QAbstractItemModel::layoutAboutToBeChanged, this, &AccountsChannelsModel::layoutAboutToBeChanged); connect(model, &QAbstractItemModel::layoutChanged, this, &AccountsChannelsModel::layoutChanged); + connect(model, &QAbstractItemModel::layoutChanged, this, &AccountsChannelsModel::modelChanged); connect(model, &QAbstractItemModel::dataChanged, this, [this, model](const QModelIndex &tl, const QModelIndex &br) { diff --git a/src/core/model/rocketchataccountmodel.h b/src/core/model/rocketchataccountmodel.h --- a/src/core/model/rocketchataccountmodel.h +++ b/src/core/model/rocketchataccountmodel.h @@ -33,6 +33,7 @@ Name = Qt::UserRole + 1, SiteUrl, UserName, + Account }; Q_ENUM(AccountRoles) diff --git a/src/core/model/rocketchataccountmodel.cpp b/src/core/model/rocketchataccountmodel.cpp --- a/src/core/model/rocketchataccountmodel.cpp +++ b/src/core/model/rocketchataccountmodel.cpp @@ -20,9 +20,12 @@ #include "rocketchataccountmodel.h" #include "rocketchataccount.h" +#include "roommodel.h" #include "ruqolaserverconfig.h" #include "ruqola_debug.h" +#include + RocketChatAccountModel::RocketChatAccountModel(QObject *parent) : QAbstractListModel(parent) { @@ -48,12 +51,21 @@ { if (rowCount() != 0) { beginRemoveRows(QModelIndex(), 0, mRocketChatAccount.count() - 1); + for (auto acct: accounts) + acct->roomModel()->disconnect(this); mRocketChatAccount.clear(); endRemoveRows(); } if (!accounts.isEmpty()) { beginInsertRows(QModelIndex(), 0, accounts.count() - 1); mRocketChatAccount = accounts; + for (auto acct: accounts) + { + connect(acct->roomModel(), &RoomModel::needToUpdateNotification, this, [this, acct]{ + const auto idx = index(mRocketChatAccount.indexOf(acct)); + Q_EMIT dataChanged(idx, idx); + }); + } endInsertRows(); } Q_EMIT accountNumberChanged(); @@ -113,13 +125,26 @@ const int idx = index.row(); RocketChatAccount *account = mRocketChatAccount.at(idx); switch (role) { + case Qt::DecorationRole: + { + bool hasAlert = false; + int nbUnread = 0; + account->roomModel()->getUnreadAlertFromAccount(hasAlert, nbUnread); + if (hasAlert) + return QIcon::fromTheme(QStringLiteral("flag-red")); + if (nbUnread) + return QIcon::fromTheme(QStringLiteral("flag-green")); + return QIcon::fromTheme(QStringLiteral("flag-black")); + } case Qt::DisplayRole: case Name: return account->accountName(); case SiteUrl: return account->ruqolaServerConfig()->siteUrl(); case UserName: return account->userName(); + case Account: + return QVariant::fromValue(account); } //Add icon ??? return {}; diff --git a/src/core/ruqola.h b/src/core/ruqola.h --- a/src/core/ruqola.h +++ b/src/core/ruqola.h @@ -65,7 +65,7 @@ explicit Ruqola(QObject *parent = nullptr); - void setCurrentAccount(const QString &accountName); + void setCurrentAccount(const QString &accountName, bool showLastRoom); private: Q_DISABLE_COPY(Ruqola) void updateNotification(bool hasAlert, int nbUnread, const QString &accountName); diff --git a/src/core/ruqola.cpp b/src/core/ruqola.cpp --- a/src/core/ruqola.cpp +++ b/src/core/ruqola.cpp @@ -1,4 +1,4 @@ -/* +/* * Copyright 2016 Riccardo Iaconelli * @@ -57,9 +57,9 @@ connect(mAccountManager, &AccountManager::logoutAccountDone, this, &Ruqola::logout); } -void Ruqola::setCurrentAccount(const QString &accountName) +void Ruqola::setCurrentAccount(const QString &accountName, bool showLastRoom) { - mAccountManager->setCurrentAccount(accountName); + mAccountManager->setCurrentAccount(accountName, showLastRoom); } AccountManager *Ruqola::accountManager() const diff --git a/src/widgets/CMakeLists.txt b/src/widgets/CMakeLists.txt --- a/src/widgets/CMakeLists.txt +++ b/src/widgets/CMakeLists.txt @@ -124,7 +124,6 @@ set(Ruqola_misc_widget_SRCS misc/accountmenu.cpp - misc/accountsoverviewwidget.cpp misc/emoticonmenuwidget.cpp misc/emoticonselectorwidget.cpp misc/emoticonrecentusedfilterproxymodel.cpp diff --git a/src/widgets/channellist/channellistview.h b/src/widgets/channellist/channellistview.h --- a/src/widgets/channellist/channellistview.h +++ b/src/widgets/channellist/channellistview.h @@ -21,39 +21,48 @@ #ifndef CHANNELLISTVIEW_H #define CHANNELLISTVIEW_H -#include +#include #include "libruqolawidgets_private_export.h" -class RoomFilterProxyModel; +class AccountsChannelsModel; +class RocketChatAccount; -class LIBRUQOLAWIDGETS_TESTS_EXPORT ChannelListView : public QListView +class LIBRUQOLAWIDGETS_TESTS_EXPORT ChannelListView : public QTreeView { Q_OBJECT public: explicit ChannelListView(QWidget *parent = nullptr); ~ChannelListView() override; - RoomFilterProxyModel *model() const; - void setModel(QAbstractItemModel *model) override; + void setModel(QAbstractItemModel *) override; + void model() const = delete; - void selectChannelRequested(const QString &channelId); + void activateChannel(const QModelIndex &index); + void activateChannelById(const QString &channelId); + Q_REQUIRED_RESULT bool activateChannelByRoomName(const QString &selectedRoomName); - Q_REQUIRED_RESULT bool selectChannelByRoomNameRequested(const QString &selectedRoomName); + void moveSelectionDown(); + void moveSelectionUp(); - void channelSelected(const QModelIndex &index); + void setFilterString(const QString &filter); Q_SIGNALS: - void roomSelected(const QString &roomId, const QString &roomType); + void channelActivated(const QString &acct, const QString &roomId, const QString &roomType); protected: void contextMenuEvent(QContextMenuEvent *event) override; private: + AccountsChannelsModel *rooms() const; + void slotClicked(const QModelIndex &index); void slotHideChannel(const QModelIndex &index, const QString &roomType); void slotLeaveChannel(const QModelIndex &index, const QString &roomType); void slotChangeFavorite(const QModelIndex &index, bool isFavorite); + + // TODO: Move this state out of this widget + QString mCurrentChannelId; }; #endif // CHANNELLISTVIEW_H diff --git a/src/widgets/channellist/channellistview.cpp b/src/widgets/channellist/channellistview.cpp --- a/src/widgets/channellist/channellistview.cpp +++ b/src/widgets/channellist/channellistview.cpp @@ -23,48 +23,48 @@ #include "ruqolawidgets_debug.h" #include "rocketchataccount.h" #include "channellistdelegate.h" -#include "model/roomfilterproxymodel.h" +#include "model/accountschannelsmodel.h" +#include "model/rocketchataccountmodel.h" #include #include #include #include ChannelListView::ChannelListView(QWidget *parent) - : QListView(parent) + : QTreeView(parent) { + setUniformRowHeights(true); + setHeaderHidden(true); + auto *delegate = new ChannelListDelegate(this); setItemDelegate(delegate); setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - connect(this, &ChannelListView::clicked, this, &ChannelListView::slotClicked); -} + connect(this, &ChannelListView::clicked, this, &ChannelListView::activateChannel); -ChannelListView::~ChannelListView() -{ -} + auto model = new AccountsChannelsModel(this); + auto syncCurrentIndex = [this, model]{ + if (model->isFiltered()) + return; // We don't keep in sync when filtering -RoomFilterProxyModel *ChannelListView::model() const -{ - return qobject_cast(QListView::model()); + const auto index = model->findRoomById(mCurrentChannelId); + selectionModel()->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); + }; + + connect(model, &AccountsChannelsModel::modelChanged, this, syncCurrentIndex); + connect(this, &ChannelListView::channelActivated, this, syncCurrentIndex); + QTreeView::setModel(model); } -void ChannelListView::setModel(QAbstractItemModel *model) +ChannelListView::~ChannelListView() { - if (!qobject_cast(model)) { - qCWarning(RUQOLAWIDGETS_LOG) << "Need to pass a RoomFilterProxyModel instance!"; - return; - } - - QListView::setModel(model); } -void ChannelListView::slotClicked(const QModelIndex &index) +void ChannelListView::setModel(QAbstractItemModel *) { - if (index.isValid()) { - channelSelected(index); - } + Q_ASSERT_X(false, "", "Cannot change the channel list view model"); } void ChannelListView::contextMenuEvent(QContextMenuEvent *event) @@ -105,11 +105,30 @@ } } -void ChannelListView::channelSelected(const QModelIndex &index) +AccountsChannelsModel *ChannelListView::rooms() const { + Q_ASSERT(qobject_cast(QTreeView::model())); + return static_cast(QTreeView::model()); +} + +void ChannelListView::activateChannel(const QModelIndex &index) +{ + if (!index.isValid()) + return; + + if (!index.parent().isValid()) + { + // This is an account index, activate last selected room + if (auto account = index.data(RocketChatAccountModel::Account).value()) + activateChannelById(account->settings()->lastSelectedRoom()); + return; + } + const QString roomId = index.data(RoomModel::RoomID).toString(); const QString roomType = index.data(RoomModel::RoomType).toString(); - Q_EMIT roomSelected(roomId, roomType); + const QString acct = rooms()->accountForIndex(index); + mCurrentChannelId = roomId; + Q_EMIT channelActivated(acct, roomId, roomType); } void ChannelListView::slotHideChannel(const QModelIndex &index, const QString &roomType) @@ -133,43 +152,91 @@ rcAccount->changeFavorite(roomId, !isFavorite); } -void ChannelListView::selectChannelRequested(const QString &channelId) +void ChannelListView::activateChannelById(const QString &channelId) { - if (channelId.isEmpty()) { - return; - } - RoomFilterProxyModel *filterModel = model(); - Q_ASSERT(filterModel); - const int nRooms = filterModel->rowCount(); - if (nRooms == 0) { - return; // too early, next chance when accountInitialized is emitted - } - for (int roomIdx = 0; roomIdx < nRooms; ++roomIdx) { - const auto roomModelIndex = filterModel->index(roomIdx, 0); - const auto roomId = roomModelIndex.data(RoomModel::RoomID).toString(); - if (roomId == channelId) { - channelSelected(roomModelIndex); - selectionModel()->setCurrentIndex(filterModel->index(roomIdx, 0), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); - return; - } - } - qCWarning(RUQOLAWIDGETS_LOG) << "Room not found:" << channelId; + activateChannel(rooms()->findRoomById(channelId)); } -bool ChannelListView::selectChannelByRoomNameRequested(const QString &selectedRoomName) +bool ChannelListView::activateChannelByRoomName(const QString &selectedRoomName) { - if (selectedRoomName.isEmpty()) { + const auto roomIdx = rooms()->findRoomByName(selectedRoomName); + if (!roomIdx.isValid()) return false; - } - RoomFilterProxyModel *filterModel = model(); - for (int roomIdx = 0, nRooms = filterModel->rowCount(); roomIdx < nRooms; ++roomIdx) { - const auto roomModelIndex = filterModel->index(roomIdx, 0); - const auto roomName = roomModelIndex.data(RoomModel::RoomName).toString(); - if (roomName == selectedRoomName) { - channelSelected(roomModelIndex); - selectionModel()->setCurrentIndex(filterModel->index(roomIdx, 0), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); - return true; + + activateChannel(roomIdx); + return true; +} + +void ChannelListView::moveSelectionDown() +{ + const auto index = [&]() -> QModelIndex { + const auto current = currentIndex().isValid() ? currentIndex() : rooms()->index(0, 0); + if (!current.isValid()) + return {}; + + const auto useParent = current.parent(); + if (useParent.isValid()) + { + const auto room = current.sibling(current.row() + 1, 0); + if (room.isValid()) + return room; } - } - return false; + + auto account = useParent.isValid() ? useParent.sibling(useParent.row() + 1, 0) : current; + while (account.isValid()) + { + if (rooms()->hasChildren(account)) + return rooms()->index(0, 0, account); + account = account.sibling(account.row() + 1, account.column()); + } + return {}; + }(); + + if (index.isValid()) + selectionModel()->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect); +} + +void ChannelListView::moveSelectionUp() +{ + const auto index = [&]() -> QModelIndex { + const auto current = [&]{ + if (currentIndex().isValid()) + return currentIndex(); + + const auto lastParent = rooms()->index(rooms()->rowCount() - 1, 0); + if (!lastParent.isValid()) + return lastParent; + + const auto lastChild = rooms()->index(rooms()->rowCount(lastParent) - 1, 0, lastParent); + return lastChild.isValid() ? lastChild : lastParent; + }(); + if (!current.isValid()) + return {}; + + const auto useParent = current.parent(); + if (useParent.isValid()) + { + const auto room = current.sibling(current.row() - 1, 0); + if (room.isValid()) + return room; + } + + auto account = useParent.isValid() ? useParent.sibling(useParent.row() - 1, 0) : current; + while (account.isValid()) + { + if (rooms()->hasChildren(account)) + return rooms()->index(rooms()->rowCount(account) - 1, 0, account); + account = account.sibling(account.row() - 1, account.column()); + } + + return {}; + }(); + + if (index.isValid()) + selectionModel()->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect); +} + +void ChannelListView::setFilterString(const QString &filter) +{ + rooms()->setFilterString(filter); } diff --git a/src/widgets/channellist/channellistwidget.h b/src/widgets/channellist/channellistwidget.h --- a/src/widgets/channellist/channellistwidget.h +++ b/src/widgets/channellist/channellistwidget.h @@ -41,7 +41,7 @@ void setCurrentRocketChatAccount(RocketChatAccount *account); Q_SIGNALS: - void roomSelected(const QString &roomId, const QString &roomType); + void channelActivated(const QString &acct, const QString &roomId, const QString &roomType); protected: bool eventFilter(QObject *object, QEvent *event) override; @@ -51,7 +51,6 @@ void setUserStatusUpdated(User::PresenceStatus status, const QString &accountName); void slotStatusChanged(); void slotSearchRoomTextChanged(); - void clearFilterChannel(); void slotOpenLinkRequested(const QString &link); StatusCombobox *mStatusComboBox = nullptr; diff --git a/src/widgets/channellist/channellistwidget.cpp b/src/widgets/channellist/channellistwidget.cpp --- a/src/widgets/channellist/channellistwidget.cpp +++ b/src/widgets/channellist/channellistwidget.cpp @@ -54,12 +54,12 @@ mSearchRoom->setClearButtonEnabled(true); mSearchRoom->installEventFilter(this); mainLayout->addWidget(mSearchRoom); - connect(mSearchRoom, &QLineEdit::textChanged, this, &ChannelListWidget::slotSearchRoomTextChanged); mChannelView = new ChannelListView(this); mChannelView->setObjectName(QStringLiteral("mChannelView")); mainLayout->addWidget(mChannelView); - connect(mChannelView, &ChannelListView::roomSelected, this, &ChannelListWidget::roomSelected); + connect(mChannelView, &ChannelListView::channelActivated, this, &ChannelListWidget::channelActivated); + connect(mSearchRoom, &QLineEdit::textChanged, mChannelView, &ChannelListView::setFilterString); auto *statusComboBoxLayout = new QHBoxLayout; mainLayout->addLayout(statusComboBoxLayout); @@ -86,28 +86,18 @@ { } -void ChannelListWidget::clearFilterChannel() -{ - if (auto *model = mChannelView->model()) { - model->setFilterString(QString()); - mSearchRoom->clear(); - } -} - void ChannelListWidget::setCurrentRocketChatAccount(RocketChatAccount *account) { - clearFilterChannel(); if (mCurrentRocketChatAccount) { disconnect(mCurrentRocketChatAccount, nullptr, this, nullptr); } mCurrentRocketChatAccount = account; connect(mCurrentRocketChatAccount, &RocketChatAccount::accountInitialized, this, &ChannelListWidget::slotAccountInitialized); connect(mCurrentRocketChatAccount, &RocketChatAccount::userStatusUpdated, this, &ChannelListWidget::setUserStatusUpdated); connect(mCurrentRocketChatAccount, &RocketChatAccount::openLinkRequested, this, &ChannelListWidget::slotOpenLinkRequested); - connect(mCurrentRocketChatAccount, &RocketChatAccount::selectRoomByRoomNameRequested, mChannelView, &ChannelListView::selectChannelByRoomNameRequested); - connect(mCurrentRocketChatAccount, &RocketChatAccount::selectRoomByRoomIdRequested, mChannelView, &ChannelListView::selectChannelRequested); + connect(mCurrentRocketChatAccount, &RocketChatAccount::selectRoomByRoomNameRequested, mChannelView, &ChannelListView::activateChannelByRoomName); + connect(mCurrentRocketChatAccount, &RocketChatAccount::selectRoomByRoomIdRequested, mChannelView, &ChannelListView::activateChannelById); - mChannelView->setModel(mCurrentRocketChatAccount->roomFilterProxyModel()); mStatusComboBox->blockSignals(true); mStatusComboBox->setStatus(mCurrentRocketChatAccount->presenceStatus()); mStatusComboBox->blockSignals(false); @@ -121,45 +111,35 @@ bool ChannelListWidget::eventFilter(QObject *object, QEvent *event) { if (object == mSearchRoom && event->type() == QEvent::KeyPress) { - const auto *model = mChannelView->model(); const auto *keyEvent = static_cast(event); const int keyValue = keyEvent->key(); - if (keyValue == Qt::Key_Return || keyValue == Qt::Key_Enter) { - const auto selectedIndex = mChannelView->selectionModel()->currentIndex(); - if (selectedIndex.isValid()) { - mChannelView->channelSelected(selectedIndex); - mSearchRoom->setText({}); - } - } else if (keyValue == Qt::Key_Up || keyValue == Qt::Key_Down) { - const QModelIndex currentIndex = mChannelView->selectionModel()->currentIndex(); - int selectRow = -1; - if (keyValue == Qt::Key_Up) { - if (!currentIndex.isValid()) { - selectRow = model->rowCount() - 1; - } else if (currentIndex.row()-1 >= 0) { - selectRow = currentIndex.row() - 1; - } - } else { // Qt::Key_Down - if (!currentIndex.isValid()) { - selectRow = 0; - } else if (currentIndex.row()+1 < model->rowCount()) { - selectRow = currentIndex.row() + 1; + switch (keyValue) + { + case Qt::Key_Return: + case Qt::Key_Enter: + { + const auto selectedIndex = mChannelView->selectionModel()->currentIndex(); + if (selectedIndex.isValid()) { + mChannelView->activateChannel(selectedIndex); + mSearchRoom->setText({}); } } - - if (selectRow != -1) { - mChannelView->selectionModel()->setCurrentIndex(model->index(selectRow, 0), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); - } - return true; // eat event + break; + case Qt::Key_Up: + mChannelView->moveSelectionUp(); + return true; + case Qt::Key_Down: + mChannelView->moveSelectionDown(); + return true; } } return QWidget::eventFilter(object, event); } void ChannelListWidget::slotAccountInitialized() { - mChannelView->selectChannelRequested(mCurrentRocketChatAccount->settings()->lastSelectedRoom()); + mChannelView->activateChannelById(mCurrentRocketChatAccount->settings()->lastSelectedRoom()); } void ChannelListWidget::setUserStatusUpdated(User::PresenceStatus status, const QString &accountName) @@ -192,7 +172,7 @@ void ChannelListWidget::slotSearchRoomTextChanged() { - mChannelView->model()->setFilterString(mSearchRoom->text()); + mChannelView->setFilterString(mSearchRoom->text()); } void ChannelListWidget::slotOpenLinkRequested(const QString &link) @@ -207,14 +187,14 @@ } } if (link.startsWith(QLatin1String("ruqola:/room/"))) { - if (!mChannelView->selectChannelByRoomNameRequested(roomOrUser)) { + if (!mChannelView->activateChannelByRoomName(roomOrUser)) { mCurrentRocketChatAccount->openChannel(roomOrUser, RocketChatAccount::ChannelTypeInfo::RoomName); } } else if (link.startsWith(QLatin1String("ruqola:/user/"))) { if (roomOrUser == QLatin1String("here") || roomOrUser == QLatin1String("all")) { return; } - if (!mChannelView->selectChannelByRoomNameRequested(roomOrUser)) { + if (!mChannelView->activateChannelByRoomName(roomOrUser)) { if (roomOrUser != mCurrentRocketChatAccount->userName()) { if (KMessageBox::Yes == KMessageBox::questionYesNo(this, i18n("Do you want to open direct conversation with %1", roomOrUser))) { mCurrentRocketChatAccount->openDirectChannel(roomOrUser); diff --git a/src/widgets/misc/accountsoverviewwidget.h b/src/widgets/misc/accountsoverviewwidget.h deleted file mode 100644 --- a/src/widgets/misc/accountsoverviewwidget.h +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright 2020 Olivier de Gaalon - * - * 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 2 of - * the License or (at your option) version 3 or any later version - * accepted by the membership of KDE e.V. (or its successor approved - * by the membership of KDE e.V.), which shall act as a proxy - * defined in Section 14 of version 3 of the license. - * - * 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 . - * - */ - -#pragma once - -#include -#include - -class AccountButton; - -class AccountsOverviewWidget : public QWidget -{ - Q_OBJECT -public: - explicit AccountsOverviewWidget(QWidget *parent = nullptr); - void updateButtons(); - -private: - QVector mAccounts; -}; diff --git a/src/widgets/misc/accountsoverviewwidget.cpp b/src/widgets/misc/accountsoverviewwidget.cpp deleted file mode 100644 --- a/src/widgets/misc/accountsoverviewwidget.cpp +++ /dev/null @@ -1,236 +0,0 @@ -/* - * Copyright 2020 Olivier de Gaalon - * - * 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 2 of - * the License or (at your option) version 3 or any later version - * accepted by the membership of KDE e.V. (or its successor approved - * by the membership of KDE e.V.), which shall act as a proxy - * defined in Section 14 of version 3 of the license. - * - * 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 "accountsoverviewwidget.h" - -#include "accountmanager.h" -#include "model/rocketchataccountmodel.h" -#include "rocketchataccount.h" -#include "ruqola.h" - -#include - -#include -#include -#include -#include -#include -#include - -constexpr const double PAD = 0.2; - -class AccountButton : public QAbstractButton -{ - struct UnreadAlert - { - int unread; - bool alert; - }; - -public: - explicit AccountButton(QWidget *parent = nullptr) - : QAbstractButton(parent) - , mAccount(nullptr) - { - connect(Ruqola::self()->accountManager(), &AccountManager::currentAccountChanged, - this, QOverload<>::of(&AccountButton::update)); - - setMouseTracking(true); - setFocusPolicy(Qt::NoFocus); - setAttribute(Qt::WA_Hover); - } - - void setAccount(RocketChatAccount *acct) - { - if (mAccount) { - mAccount->disconnect(this); - mAccount->roomModel()->disconnect(this); - this->disconnect(acct); - } - - mAccount = acct; - - if (mAccount) { - auto updateFont = [this] { - QFont f = font(); - f.setBold(currentUnreadAlert().alert); - setFont(f); - updateGeometry(); - }; - connect(acct, &RocketChatAccount::accountNameChanged, this, &AccountButton::updateGeometry); - connect(acct, &RocketChatAccount::loginStatusChanged, this, &AccountButton::updateGeometry); - connect(acct, &RocketChatAccount::loginStatusChanged, this, &AccountButton::updateTooltip); - connect(acct->roomModel(), &RoomModel::needToUpdateNotification, this, updateFont); - connect(this, &AccountButton::clicked, acct, [acct] { - Ruqola::self()->accountManager()->setCurrentAccount(acct->accountName()); - }); - - updateFont(); - } - update(); - } - - QSize sizeHint() const override - { - const auto mngr = Ruqola::self()->accountManager(); - const bool singleAccount = mngr->rocketChatAccountModel()->rowCount() == 1; - const double height = fontMetrics().height(); - const double padding = singleAccount ? 0 : height * PAD; - const QSize textSize = fontMetrics().size(Qt::TextSingleLine, currentText()); - return QSize(textSize.width() + padding * 2, height + padding * 2); - } - -protected: - void enterEvent(QEvent *event) override - { - if (isEnabled()) { - update(); - } - QAbstractButton::enterEvent(event); - } - - void leaveEvent(QEvent *event) override - { - if (isEnabled()) { - update(); - } - QAbstractButton::leaveEvent(event); - } - - void paintEvent(QPaintEvent *) override - { - if (!mAccount) { - return; - } - - QPainter p(this); - - const auto mngr = Ruqola::self()->accountManager(); - if (mngr->rocketChatAccountModel()->rowCount() > 1) { - const bool isCurrent = mngr->currentAccount() == mAccount->accountName(); - - QStyleOption opt; - opt.init(this); - if (isDown() || isCurrent) { - opt.state |= QStyle::State_Sunken; - } - style()->drawPrimitive(QStyle::PE_PanelButtonTool, &opt, &p, this); - } - - p.setPen(palette().color(QPalette::WindowText)); - p.setFont(font()); - p.drawText(rect(), Qt::AlignCenter, currentText()); - } - -private: - Q_REQUIRED_RESULT QString currentText() const - { - QString text = mAccount ? mAccount->accountName() : QString(); - if (text.isEmpty()) { - text = i18n("(Unnamed)"); - } - - if (mAccount) { - if (mAccount->loginStatus() != DDPAuthenticationManager::LoggedIn) { - text += QStringLiteral(": %1").arg(currentLoginStatusText()); - } else if (int unread = currentUnreadAlert().unread) { - text += QStringLiteral(" (%1)").arg(unread); - } - } - - return text; - } - - Q_REQUIRED_RESULT UnreadAlert currentUnreadAlert() const - { - UnreadAlert ua = {0, false}; - mAccount->roomModel()->getUnreadAlertFromAccount(ua.alert, ua.unread); - return ua; - } - - Q_REQUIRED_RESULT QString currentLoginStatusText() const - { - if (mAccount) { - if (!mAccount->ddp()->isConnected()) { - return i18n("Not connected"); - } - switch (mAccount->loginStatus()) { - case DDPAuthenticationManager::LoginOtpAuthOngoing: - return i18n("Login OTP code required"); - case DDPAuthenticationManager::LoginFailedInvalidUserOrPassword: - return i18n("Login failed: invalid username or password"); - case DDPAuthenticationManager::LoginOngoing: - return i18n("Logging in"); - case DDPAuthenticationManager::LoggedIn: - return i18n("Logged in"); - case DDPAuthenticationManager::LoggedOut: - return i18n("Logged out"); - case DDPAuthenticationManager::FailedToLoginPluginProblem: - return i18n("Failed to login due to plugin problem"); - case DDPAuthenticationManager::GenericError: - return i18n("Login failed: generic error"); - case DDPAuthenticationManager::LoginOtpRequired: - case DDPAuthenticationManager::LoginFailedInvalidOtp: - case DDPAuthenticationManager::LogoutOngoing: - case DDPAuthenticationManager::LogoutCleanUpOngoing: - case DDPAuthenticationManager::LoggedOutAndCleanedUp: - break; - } - } - return i18n("Unknown state"); - } - - void updateTooltip() - { - setToolTip(currentLoginStatusText()); - } - - QPointer mAccount; -}; - -AccountsOverviewWidget::AccountsOverviewWidget(QWidget *parent) - : QWidget(parent) -{ - setLayout(new QHBoxLayout); - const auto model = Ruqola::self()->accountManager()->rocketChatAccountModel(); - connect(model, &RocketChatAccountModel::accountNumberChanged, this, &AccountsOverviewWidget::updateButtons); - updateButtons(); -} - -void AccountsOverviewWidget::updateButtons() -{ - const auto model = Ruqola::self()->accountManager()->rocketChatAccountModel(); - const auto count = model->rowCount(); - for (int i = 0; i < count; ++i) { - RocketChatAccount *account = model->account(i); - if (i >= mAccounts.size()) { - auto *button = new AccountButton(this); - mAccounts.append(button); - layout()->addWidget(mAccounts.last()); - } - mAccounts[i]->setVisible(account->accountEnabled()); - mAccounts[i]->setAccount(model->account(i)); - } - for (int i = count; i < mAccounts.size(); ++i) { - mAccounts[i]->deleteLater(); - } - mAccounts.resize(count); -} diff --git a/src/widgets/ruqolacentralwidget.h b/src/widgets/ruqolacentralwidget.h --- a/src/widgets/ruqolacentralwidget.h +++ b/src/widgets/ruqolacentralwidget.h @@ -39,7 +39,7 @@ ~RuqolaCentralWidget() override; Q_REQUIRED_RESULT QString roomId() const; - void setCurrentRocketChatAccount(RocketChatAccount *account); + void setCurrentRocketChatAccount(RocketChatAccount *account, bool showLastRoom); Q_REQUIRED_RESULT QString roomType() const; RoomWrapper *roomWrapper() const; Q_SIGNALS: diff --git a/src/widgets/ruqolacentralwidget.cpp b/src/widgets/ruqolacentralwidget.cpp --- a/src/widgets/ruqolacentralwidget.cpp +++ b/src/widgets/ruqolacentralwidget.cpp @@ -83,16 +83,16 @@ return mRuqolaMainWidget->roomType(); } -void RuqolaCentralWidget::setCurrentRocketChatAccount(RocketChatAccount *account) +void RuqolaCentralWidget::setCurrentRocketChatAccount(RocketChatAccount *account, bool showLastRoom) { if (mCurrentRocketChatAccount) { disconnect(mCurrentRocketChatAccount, nullptr, this, nullptr); } mCurrentRocketChatAccount = account; connect(mCurrentRocketChatAccount, &RocketChatAccount::loginStatusChanged, this, &RuqolaCentralWidget::slotLoginStatusChanged); connect(mCurrentRocketChatAccount, &RocketChatAccount::socketError, this, &RuqolaCentralWidget::slotSocketError); connect(mCurrentRocketChatAccount, &RocketChatAccount::jobFailed, this, &RuqolaCentralWidget::slotJobFailedInfo); - mRuqolaMainWidget->setCurrentRocketChatAccount(account); + mRuqolaMainWidget->setCurrentRocketChatAccount(account, showLastRoom); //Check if account is connected or not. slotLoginStatusChanged(); } diff --git a/src/widgets/ruqolamainwidget.h b/src/widgets/ruqolamainwidget.h --- a/src/widgets/ruqolamainwidget.h +++ b/src/widgets/ruqolamainwidget.h @@ -36,14 +36,16 @@ explicit RuqolaMainWidget(QWidget *parent = nullptr); ~RuqolaMainWidget() override; Q_REQUIRED_RESULT QString roomId() const; - void setCurrentRocketChatAccount(RocketChatAccount *account); + void setCurrentRocketChatAccount(RocketChatAccount *account, bool showLastRoom); Q_REQUIRED_RESULT QString roomType() const; RoomWrapper *roomWrapper() const; - void selectChannelRoom(const QString &roomId, const QString &roomType); Q_SIGNALS: void channelSelected(); + private: + void selectChannelRoom(const QString &acct, const QString &roomId, const QString &roomType); + ChannelListWidget *mChannelList = nullptr; RoomWidget *mRoomWidget = nullptr; QSplitter *mSplitter = nullptr; diff --git a/src/widgets/ruqolamainwidget.cpp b/src/widgets/ruqolamainwidget.cpp --- a/src/widgets/ruqolamainwidget.cpp +++ b/src/widgets/ruqolamainwidget.cpp @@ -18,6 +18,7 @@ Boston, MA 02110-1301, USA. */ +#include "ruqola.h" #include "ruqolamainwidget.h" #include "channellist/channellistwidget.h" #include "room/roomwidget.h" @@ -60,16 +61,16 @@ mRoomWidget->setObjectName(QStringLiteral("mRoomWidget")); mStackedRoomWidget->addWidget(mRoomWidget); connect(mRoomWidget, &RoomWidget::selectChannelRequested, this, [this](const QString &channelId) { - mChannelList->channelListView()->selectChannelRequested(channelId); + mChannelList->channelListView()->activateChannelById(channelId); }); mEmptyRoomWidget = new QWidget(this); mEmptyRoomWidget->setObjectName(QStringLiteral("mEmptyRoomWidget")); mStackedRoomWidget->addWidget(mEmptyRoomWidget); mStackedRoomWidget->setCurrentWidget(mEmptyRoomWidget); - connect(mChannelList, &ChannelListWidget::roomSelected, this, &RuqolaMainWidget::selectChannelRoom); + connect(mChannelList, &ChannelListWidget::channelActivated, this, &RuqolaMainWidget::selectChannelRoom); KConfigGroup group(KSharedConfig::openConfig(), myConfigGroupName); mSplitter->restoreState(group.readEntry("SplitterSizes", QByteArray())); @@ -84,8 +85,9 @@ } } -void RuqolaMainWidget::selectChannelRoom(const QString &roomId, const QString &roomType) +void RuqolaMainWidget::selectChannelRoom(const QString &acct, const QString &roomId, const QString &roomType) { + Ruqola::self()->setCurrentAccount(acct, false); mRoomWidget->setChannelSelected(roomId, roomType); mStackedRoomWidget->setCurrentWidget(mRoomWidget); Q_EMIT channelSelected(); @@ -106,7 +108,7 @@ return mRoomWidget->roomType(); } -void RuqolaMainWidget::setCurrentRocketChatAccount(RocketChatAccount *account) +void RuqolaMainWidget::setCurrentRocketChatAccount(RocketChatAccount *account, bool showLastRoom) { if (mCurrentRocketChatAccount) { mCurrentRocketChatAccount->settings()->setLastSelectedRoom(mRoomWidget->roomId()); @@ -116,7 +118,6 @@ mRoomWidget->setCurrentRocketChatAccount(account); mStackedRoomWidget->setCurrentWidget(mEmptyRoomWidget); - // This is for switching between already-loaded accounts - // On startup it's too early - mChannelList->channelListView()->selectChannelRequested(mCurrentRocketChatAccount->settings()->lastSelectedRoom()); + if (showLastRoom) + mChannelList->channelListView()->activateChannelById(mCurrentRocketChatAccount->settings()->lastSelectedRoom()); } diff --git a/src/widgets/ruqolamainwindow.h b/src/widgets/ruqolamainwindow.h --- a/src/widgets/ruqolamainwindow.h +++ b/src/widgets/ruqolamainwindow.h @@ -51,7 +51,7 @@ void slotCreateNewChannel(); void slotTypingNotificationChanged(const QString &roomId, const QString ¬ificationStr); void slotClearNotification(); - void slotAccountChanged(); + void slotAccountChanged(bool showLastRoom); void slotUnreadOnTop(bool checked); void updateActions(); void slotMissingChannelPassword(const RocketChatRestApi::ChannelBaseJob::ChannelInfo &channelInfo); @@ -71,7 +71,6 @@ AccountMenu *mAccountMenu = nullptr; QPointer mCurrentRocketChatAccount; QLabel *mStatusBarTypingMessage = nullptr; - AccountsOverviewWidget *mAccountOverviewWidget = nullptr; }; #endif // RUQOLAMAINWINDOW_H diff --git a/src/widgets/ruqolamainwindow.cpp b/src/widgets/ruqolamainwindow.cpp --- a/src/widgets/ruqolamainwindow.cpp +++ b/src/widgets/ruqolamainwindow.cpp @@ -29,7 +29,6 @@ #include "receivetypingnotificationmanager.h" #include "ruqolacentralwidget.h" #include "misc/accountmenu.h" -#include "misc/accountsoverviewwidget.h" #include "dialogs/serverinfodialog.h" #include "dialogs/searchchanneldialog.h" #include "dialogs/createnewchanneldialog.h" @@ -71,7 +70,7 @@ setupGUI(/*QStringLiteral(":/kxmlgui5/ruqola/ruqolaui.rc")*/); readConfig(); connect(Ruqola::self()->accountManager(), &AccountManager::currentAccountChanged, this, &RuqolaMainWindow::slotAccountChanged); - slotAccountChanged(); + slotAccountChanged(false /*showLastRoom*/); #if HAVE_KUSERFEEDBACK KUserFeedback::NotificationPopup *userFeedBackNotificationPopup = new KUserFeedback::NotificationPopup(this); userFeedBackNotificationPopup->setFeedbackProvider(UserFeedBackManager::self()->userFeedbackProvider()); @@ -95,11 +94,9 @@ mStatusBarTypingMessage->setTextFormat(Qt::RichText); mStatusBarTypingMessage->setObjectName(QStringLiteral("mStatusBarTypingMessage")); statusBar()->addPermanentWidget(mStatusBarTypingMessage); - mAccountOverviewWidget = new AccountsOverviewWidget(this); - statusBar()->addPermanentWidget(mAccountOverviewWidget); } -void RuqolaMainWindow::slotAccountChanged() +void RuqolaMainWindow::slotAccountChanged(bool showLastRoom) { if (mCurrentRocketChatAccount) { disconnect(mCurrentRocketChatAccount, nullptr, this, nullptr); @@ -115,7 +112,7 @@ updateActions(); changeActionStatus(false); //Disable actions when switching. slotClearNotification(); //Clear notification when we switch too. - mMainWidget->setCurrentRocketChatAccount(mCurrentRocketChatAccount); + mMainWidget->setCurrentRocketChatAccount(mCurrentRocketChatAccount, showLastRoom); } void RuqolaMainWindow::slotRaiseWindow() @@ -232,9 +229,7 @@ void RuqolaMainWindow::slotConfigure() { QPointer dlg = new ConfigureSettingsDialog(this); - if (dlg->exec()) { - mAccountOverviewWidget->updateButtons(); - } + dlg->exec(); delete dlg; }