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"