File indexing completed on 2024-05-19 16:00:00

0001 /*
0002    SPDX-FileCopyrightText: 2020-2024 Laurent Montel <montel@kde.org>
0003 
0004    SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "channellistwidget.h"
0008 #include "channellistview.h"
0009 #include "model/roomfilterproxymodel.h"
0010 #include "room/roomutil.h"
0011 #include "ruqolawidgets_debug.h"
0012 
0013 #include "accountmanager.h"
0014 #include "rocketchataccount.h"
0015 #include "ruqola.h"
0016 #include "ruqolautils.h"
0017 
0018 #include <KLocalizedString>
0019 #include <QAction>
0020 #include <QKeyEvent>
0021 #include <QLineEdit>
0022 #include <QVBoxLayout>
0023 
0024 ChannelListWidget::ChannelListWidget(QWidget *parent)
0025     : QWidget(parent)
0026     , mChannelView(new ChannelListView(this))
0027     , mSearchRoomLineEdit(new QLineEdit(this))
0028 {
0029     auto mainLayout = new QVBoxLayout(this);
0030     mainLayout->setObjectName(QStringLiteral("mainlayout"));
0031 #if QT_VERSION > QT_VERSION_CHECK(6, 0, 0)
0032     mainLayout->setSpacing(0);
0033 #endif
0034     mainLayout->setContentsMargins({});
0035 
0036     mChannelView->setObjectName(QStringLiteral("mChannelView"));
0037 #if QT_VERSION > QT_VERSION_CHECK(6, 0, 0)
0038     mChannelView->setProperty("_breeze_force_frame", false);
0039 #endif
0040     mainLayout->addWidget(mChannelView);
0041     connect(mChannelView, &ChannelListView::selectMessageIdRequested, this, &ChannelListWidget::selectMessageIdRequested);
0042     connect(mChannelView, &ChannelListView::roomPressed, this, &ChannelListWidget::roomPressed);
0043     connect(mChannelView, &ChannelListView::roomSelected, this, [this](const ChannelListView::ChannelSelectedInfo &roomInfo) {
0044         // retain focus on the search line edit when this is triggering the room change
0045         const auto wasFocused = mSearchRoomLineEdit->hasFocus();
0046 
0047         Q_EMIT roomSelected(roomInfo);
0048 
0049         if (wasFocused) {
0050             mSearchRoomLineEdit->setFocus();
0051         }
0052     });
0053 
0054     // dummy action just for getting the icon)
0055     mSearchRoomLineEdit->addAction(QIcon::fromTheme(QStringLiteral("view-filter")), QLineEdit::LeadingPosition);
0056     mSearchRoomLineEdit->setObjectName(QStringLiteral("mSearchRoom"));
0057     mSearchRoomLineEdit->setPlaceholderText(i18n("Filter channels (CTRL + K)"));
0058     mSearchRoomLineEdit->setClearButtonEnabled(true);
0059     mSearchRoomLineEdit->installEventFilter(this);
0060 #if QT_VERSION > QT_VERSION_CHECK(6, 0, 0)
0061     mSearchRoomLineEdit->setProperty("_breeze_borders_sides", QVariant::fromValue(QFlags{Qt::TopEdge}));
0062     mSearchRoomLineEdit->setMinimumHeight(36); // match the default size of the message text field
0063 #endif
0064     mainLayout->addWidget(mSearchRoomLineEdit);
0065     connect(mSearchRoomLineEdit, &QLineEdit::textChanged, this, &ChannelListWidget::slotSearchRoomTextChanged);
0066 
0067     // BEGIN: Actions
0068     auto searchRoomAction = new QAction(i18n("Search Channels"), this);
0069     searchRoomAction->setShortcut(Qt::CTRL | Qt::Key_K);
0070     connect(searchRoomAction, &QAction::triggered, this, [this]() {
0071         mSearchRoomLineEdit->setFocus();
0072     });
0073     addAction(searchRoomAction); // TODO: Add to MainWindow's action collection instead?
0074 
0075     auto previousRoomAction = new QAction(i18n("Previous Channel"), this);
0076     previousRoomAction->setShortcut(Qt::CTRL | Qt::Key_Up);
0077     connect(previousRoomAction, &QAction::triggered, this, [this]() {
0078         mChannelView->selectNextChannel(ChannelListView::Direction::Up);
0079         mSearchRoomLineEdit->clear();
0080     });
0081     addAction(previousRoomAction); // TODO: Add to MainWindow's action collection instead?
0082 
0083     auto nextRoomAction = new QAction(i18n("Next Channel"), this);
0084     nextRoomAction->setShortcut(Qt::CTRL | Qt::Key_Down);
0085     connect(nextRoomAction, &QAction::triggered, this, [this]() {
0086         mChannelView->selectNextChannel(ChannelListView::Direction::Down);
0087         mSearchRoomLineEdit->clear();
0088     });
0089     addAction(nextRoomAction); // TODO: Add to MainWindow's action collection instead?
0090     // END: Actions
0091 }
0092 
0093 ChannelListWidget::~ChannelListWidget() = default;
0094 
0095 void ChannelListWidget::clearFilterChannel()
0096 {
0097     if (auto *model = mChannelView->filterModel()) {
0098         model->setFilterString(QString());
0099         mSearchRoomLineEdit->clear();
0100     }
0101 }
0102 
0103 void ChannelListWidget::setCurrentRocketChatAccount(RocketChatAccount *account)
0104 {
0105     clearFilterChannel();
0106     if (mCurrentRocketChatAccount) {
0107         disconnect(mCurrentRocketChatAccount, nullptr, this, nullptr);
0108     }
0109     mCurrentRocketChatAccount = account;
0110     connect(mCurrentRocketChatAccount, &RocketChatAccount::accountInitialized, this, &ChannelListWidget::slotAccountInitialized);
0111     connect(mCurrentRocketChatAccount, &RocketChatAccount::openLinkRequested, this, &ChannelListWidget::slotOpenLinkRequested);
0112     connect(mCurrentRocketChatAccount, &RocketChatAccount::openTeamNameRequested, this, &ChannelListWidget::slotOpenTeamRequested);
0113     connect(mCurrentRocketChatAccount, &RocketChatAccount::selectRoomByRoomNameRequested, mChannelView, &ChannelListView::selectChannelByRoomNameRequested);
0114     connect(mCurrentRocketChatAccount, &RocketChatAccount::selectRoomByRoomIdRequested, mChannelView, &ChannelListView::selectChannelRequested);
0115     connect(mCurrentRocketChatAccount, &RocketChatAccount::selectChannelAndMessage, this, &ChannelListWidget::slotSelectMessageRequested);
0116 
0117     mChannelView->setCurrentRocketChatAccount(account);
0118 }
0119 
0120 ChannelListView *ChannelListWidget::channelListView() const
0121 {
0122     return mChannelView;
0123 }
0124 
0125 bool ChannelListWidget::eventFilter(QObject *object, QEvent *event)
0126 {
0127     if (object == mSearchRoomLineEdit && event->type() == QEvent::KeyPress) {
0128         const auto keyEvent = static_cast<QKeyEvent *>(event);
0129         const int keyValue = keyEvent->key();
0130         if (keyValue == Qt::Key_Return || keyValue == Qt::Key_Enter) {
0131             // The search line edit wants to restore focus to itself, but Enter is supposed to
0132             // explicitly switch to the message line edit, so distract it from doing that
0133             setFocus();
0134             applyChannelSelection();
0135         } else if (keyValue == Qt::Key_Up || keyValue == Qt::Key_Down) {
0136             mChannelView->selectNextChannel(keyValue == Qt::Key_Up ? ChannelListView::Direction::Up : ChannelListView::Direction::Down);
0137             return true; // eat event
0138         } else if (keyValue == Qt::Key_Escape) {
0139             mSearchRoomLineEdit->clear();
0140         }
0141     }
0142 
0143     return QWidget::eventFilter(object, event);
0144 }
0145 
0146 void ChannelListWidget::slotAccountInitialized()
0147 {
0148     mChannelView->selectChannelRequested(mCurrentRocketChatAccount->settings()->lastSelectedRoom(), QString());
0149 }
0150 
0151 void ChannelListWidget::slotSearchRoomTextChanged()
0152 {
0153     mChannelView->filterModel()->setFilterString(mSearchRoomLineEdit->text());
0154 }
0155 
0156 void ChannelListWidget::slotOpenTeamRequested(const QString &identifier)
0157 {
0158     const QModelIndex selectedIndex = mChannelView->selectionModel()->currentIndex();
0159     if (selectedIndex.isValid()) {
0160         const QString currentRoomId = selectedIndex.data(RoomModel::RoomId).toString();
0161         if (identifier == currentRoomId) {
0162             return;
0163         }
0164     }
0165     if (!mChannelView->selectChannelByRoomIdRequested(identifier)) {
0166         mCurrentRocketChatAccount->openChannel(identifier, RocketChatAccount::ChannelTypeInfo::RoomId);
0167     }
0168 }
0169 
0170 void ChannelListWidget::slotSelectMessageRequested(const QString &messageId,
0171                                                    const QString &roomId,
0172                                                    ParseMessageUrlUtils::RoomIdType roomType,
0173                                                    ParseMessageUrlUtils::ChannelType channelType)
0174 {
0175     switch (roomType) {
0176     case ParseMessageUrlUtils::RoomIdType::Unknown:
0177         qCWarning(RUQOLAWIDGETS_LOG) << "Room type undefined!";
0178         break;
0179     case ParseMessageUrlUtils::RoomIdType::RoomId: {
0180         const QModelIndex selectedIndex = mChannelView->selectionModel()->currentIndex();
0181         if (selectedIndex.isValid()) {
0182             const QString currentRoomId = selectedIndex.data(RoomModel::RoomId).toString();
0183             if (roomId == currentRoomId) {
0184                 Q_EMIT selectMessageIdRequested(messageId);
0185                 return;
0186             }
0187             switch (channelType) {
0188             case ParseMessageUrlUtils::ChannelType::Channel: {
0189                 if (!mChannelView->selectChannelByRoomIdRequested(roomId)) {
0190                     mCurrentRocketChatAccount->openChannel(roomId, RocketChatAccount::ChannelTypeInfo::RoomId);
0191                     // TODO implement scroll to message
0192                 } else {
0193                     Q_EMIT selectMessageIdRequested(messageId);
0194                 }
0195                 break;
0196             }
0197             case ParseMessageUrlUtils::ChannelType::Group: {
0198                 // TODO ?
0199                 if (!mChannelView->selectChannelByRoomIdRequested(roomId)) {
0200                     mCurrentRocketChatAccount->openChannel(roomId, RocketChatAccount::ChannelTypeInfo::RoomId);
0201                     // TODO implement scroll to message
0202                 } else {
0203                     Q_EMIT selectMessageIdRequested(messageId);
0204                 }
0205                 break;
0206             }
0207             case ParseMessageUrlUtils::ChannelType::Direct: {
0208                 if (!mChannelView->selectChannelByRoomIdRequested(roomId)) {
0209                     // TODO add support for roomId or roomName
0210                     // mCurrentRocketChatAccount->openDirectChannel(roomId /*, RocketChatAccount::ChannelTypeInfo::RoomId*/);
0211                     // Workaround RC 4.7.x where openDirectChannel doesn't accept userId as direct open channel REST API
0212                     mCurrentRocketChatAccount->ddp()->openDirectChannel(roomId);
0213                     // TODO implement scroll to message
0214                 } else {
0215                     Q_EMIT selectMessageIdRequested(messageId);
0216                 }
0217                 break;
0218             }
0219             case ParseMessageUrlUtils::ChannelType::Unknown: {
0220                 qCWarning(RUQOLAWIDGETS_LOG) << "ChannelType undefined!";
0221                 break;
0222             }
0223             }
0224         }
0225         break;
0226     }
0227     case ParseMessageUrlUtils::RoomIdType::RoomName: {
0228         const QModelIndex selectedIndex = mChannelView->selectionModel()->currentIndex();
0229         if (selectedIndex.isValid()) {
0230             const QString currentRoomName = selectedIndex.data(RoomModel::RoomName).toString();
0231             if (roomId == currentRoomName) {
0232                 Q_EMIT selectMessageIdRequested(messageId);
0233                 return;
0234             }
0235             switch (channelType) {
0236             case ParseMessageUrlUtils::ChannelType::Channel: {
0237                 if (!mChannelView->selectChannelByRoomNameRequested(roomId)) {
0238                     mCurrentRocketChatAccount->openChannel(roomId, RocketChatAccount::ChannelTypeInfo::RoomName);
0239                     // TODO implement scroll to message
0240                 } else {
0241                     Q_EMIT selectMessageIdRequested(messageId);
0242                 }
0243                 break;
0244             }
0245             case ParseMessageUrlUtils::ChannelType::Direct: {
0246                 if (!mChannelView->selectChannelByRoomNameRequested(roomId)) {
0247                     // TODO add support for roomId or roomName
0248                     mCurrentRocketChatAccount->openDirectChannel(roomId /*, RocketChatAccount::ChannelTypeInfo::RoomName*/);
0249                 } else {
0250                     Q_EMIT selectMessageIdRequested(messageId);
0251                 }
0252                 break;
0253             }
0254             case ParseMessageUrlUtils::ChannelType::Group: {
0255                 if (!mChannelView->selectChannelByRoomNameRequested(roomId)) {
0256                     // TODO add support for roomId or roomName
0257                     mCurrentRocketChatAccount->openDirectChannel(roomId /*, RocketChatAccount::ChannelTypeInfo::RoomName*/);
0258                 } else {
0259                     Q_EMIT selectMessageIdRequested(messageId);
0260                 }
0261                 break;
0262             }
0263             case ParseMessageUrlUtils::ChannelType::Unknown: {
0264                 qCWarning(RUQOLAWIDGETS_LOG) << "ChannelType undefined!";
0265                 break;
0266             }
0267             }
0268         }
0269         break;
0270     }
0271     }
0272 }
0273 
0274 void ChannelListWidget::slotOpenLinkRequested(const QString &link)
0275 {
0276     // qDebug() << " void ChannelListWidget::slotOpenLinkRequested(const QString &link)" << link;
0277     if (link.startsWith(QLatin1String("ruqola:"))) {
0278         const QString roomOrUserId = RuqolaUtils::self()->extractRoomUserFromUrl(link);
0279         const QModelIndex selectedIndex = mChannelView->selectionModel()->currentIndex();
0280         if (selectedIndex.isValid()) {
0281             const QString currentRoomId = selectedIndex.data(RoomModel::RoomId).toString();
0282             if (roomOrUserId == currentRoomId) {
0283                 return;
0284             }
0285         }
0286         if (link.startsWith(QLatin1String("ruqola:/room/"))) {
0287             if (!mChannelView->selectChannelByRoomIdRequested(roomOrUserId)) {
0288                 mCurrentRocketChatAccount->openChannel(roomOrUserId, RocketChatAccount::ChannelTypeInfo::RoomId);
0289             }
0290         } else if (link.startsWith(QLatin1String("ruqola:/user/"))) {
0291             if (!RoomUtil::validUser(roomOrUserId)) {
0292                 return;
0293             }
0294             if (!mChannelView->selectChannelByRoomIdRequested(roomOrUserId)) {
0295                 if (roomOrUserId != mCurrentRocketChatAccount->userName()) {
0296                     if (mCurrentRocketChatAccount->hasPermission(QStringLiteral("create-d"))) {
0297                         // Workaround RC 4.7.x where openDirectChannel doesn't accept userId as direct open channel REST API
0298                         mCurrentRocketChatAccount->ddp()->openDirectChannel(roomOrUserId);
0299                     }
0300 
0301                     // mCurrentRocketChatAccount->openDirectChannel(roomOrUserId);
0302                 }
0303             }
0304         } else if (link == QLatin1String("ruqola:/jitsicall/")) {
0305             const QModelIndex jitsiSelectedIndex = mChannelView->selectionModel()->currentIndex();
0306             if (jitsiSelectedIndex.isValid()) {
0307                 const QString roomId = jitsiSelectedIndex.data(RoomModel::RoomId).toString();
0308                 mCurrentRocketChatAccount->joinJitsiConfCall(roomId);
0309             }
0310         }
0311     } else {
0312         ParseMessageUrlUtils parseUrl;
0313         if (parseUrl.parseUrl(link)) {
0314             if (Ruqola::self()->accountManager()->showMessage(parseUrl)) {
0315                 return;
0316             }
0317         }
0318         RuqolaUtils::self()->openUrl(link);
0319     }
0320 }
0321 
0322 void ChannelListWidget::setLayoutSpacing(int spacing)
0323 {
0324     layout()->setSpacing(spacing);
0325 }
0326 
0327 void ChannelListWidget::applyChannelSelection()
0328 {
0329     const auto selectedIndex = mChannelView->selectionModel()->currentIndex();
0330     if (selectedIndex.isValid()) {
0331         mChannelView->channelSelected(selectedIndex);
0332         mSearchRoomLineEdit->clear();
0333     }
0334 }
0335 
0336 #include "moc_channellistwidget.cpp"