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