Warning, file /network/ruqola/src/widgets/room/roomwidget.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).
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 "roomwidget.h" 0008 #include "accountroomsettings.h" 0009 #include "conferencecalldialog/conferencecalldialog.h" 0010 #include "conferencecalldialog/conferencedirectcalldialog.h" 0011 #include "connection.h" 0012 #include "dialogs/addusersinroomdialog.h" 0013 #include "dialogs/autotranslateconfiguredialog.h" 0014 #include "dialogs/channelinfodialog.h" 0015 #include "dialogs/configurenotificationdialog.h" 0016 #include "dialogs/directchannelinfodialog.h" 0017 #include "dialogs/inviteusersdialog.h" 0018 #include "dialogs/searchmessagedialog.h" 0019 #include "dialogs/showattachmentdialog.h" 0020 #include "dialogs/showmentionsmessagesdialog.h" 0021 #include "dialogs/showpinnedmessagesdialog.h" 0022 #include "dialogs/showstarredmessagesdialog.h" 0023 #include "dialogs/showthreadsdialog.h" 0024 #include "discussions/showdiscussionsdialog.h" 0025 #include "exportmessages/exportmessagesdialog.h" 0026 #include "messagelinewidget.h" 0027 #include "messagelistview.h" 0028 #include "plugintextmessagewidget.h" 0029 #include "prunemessages/prunemessagesdialog.h" 0030 #include "rocketchataccount.h" 0031 #include "rocketchatbackend.h" 0032 #include "roomutil.h" 0033 #include "ruqolawidgets_debug.h" 0034 #include "usersinroomflowwidget.h" 0035 0036 #include "offlinewidget/offlinewidget.h" 0037 0038 #include "otr/otrwidget.h" 0039 #include "reconnectinfowidget.h" 0040 #include "roomcounterinfowidget.h" 0041 #include "roomwidgetbase.h" 0042 #include "ruqola_thread_message_widgets_debug.h" 0043 #include "teams/teamchannelsdialog.h" 0044 #include "teams/teaminfo.h" 0045 #include "threadwidget/threadmessagedialog.h" 0046 #include "video-conference/videoconferencejoinjob.h" 0047 #include "video-conference/videoconferencestartjob.h" 0048 #include "videoconference/videoconferencemessageinfomanager.h" 0049 0050 #include <KLocalizedString> 0051 #include <KMessageBox> 0052 0053 #include <QDesktopServices> 0054 #include <QDragEnterEvent> 0055 #include <QDropEvent> 0056 #include <QMimeData> 0057 #include <QPushButton> 0058 #include <QScrollBar> 0059 0060 #include "config-ruqola.h" 0061 #include "video-conference/videoconferencecapabilitiesjob.h" 0062 0063 #if HAVE_TEXT_TO_SPEECH 0064 #include <TextEditTextToSpeech/TextToSpeechContainerWidget> 0065 #endif 0066 0067 RoomWidget::RoomWidget(QWidget *parent) 0068 : QWidget(parent) 0069 , mRoomWidgetBase(new RoomWidgetBase(MessageListView::Mode::Editing, this)) 0070 , mRoomHeaderWidget(new RoomHeaderWidget(this)) 0071 , mUsersInRoomFlowWidget(new UsersInRoomFlowWidget(this)) 0072 , mRoomCounterInfoWidget(new RoomCounterInfoWidget(this)) 0073 0074 #if HAVE_TEXT_TO_SPEECH 0075 , mTextToSpeechWidget(new TextEditTextToSpeech::TextToSpeechContainerWidget(this)) 0076 #endif 0077 { 0078 auto mainLayout = new QVBoxLayout(this); 0079 mainLayout->setObjectName(QStringLiteral("mainLayout")); 0080 mainLayout->setContentsMargins({}); 0081 0082 mRoomHeaderWidget->setObjectName(QStringLiteral("mRoomHeaderWidget")); 0083 mainLayout->addWidget(mRoomHeaderWidget); 0084 0085 auto roomWidget = new QWidget(this); 0086 mainLayout->addWidget(roomWidget); 0087 mRoomWidgetLayout = new QVBoxLayout(roomWidget); 0088 mRoomWidgetLayout->setObjectName(QStringLiteral("roomWidgetLayout")); 0089 mRoomWidgetLayout->setContentsMargins({}); 0090 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) 0091 mRoomWidgetLayout->setSpacing(0); 0092 #endif 0093 0094 mUsersInRoomFlowWidget->setObjectName(QStringLiteral("mUsersInRoomFlowWidget")); 0095 mRoomWidgetLayout->addWidget(mUsersInRoomFlowWidget); 0096 mUsersInRoomFlowWidget->setVisible(false); 0097 0098 mRoomCounterInfoWidget->setObjectName(QStringLiteral("mRoomCounterInfoWidget")); 0099 0100 connect(mRoomWidgetBase, &RoomWidgetBase::errorMessage, this, [this](const QString &message) { 0101 if (!mPluginTextMessageWidget) { 0102 createPluginTextMessateWidget(); 0103 } 0104 mPluginTextMessageWidget->slotShareError(message); 0105 }); 0106 connect(mRoomWidgetBase, &RoomWidgetBase::successMessage, this, [this](const QString &message) { 0107 if (!mPluginTextMessageWidget) { 0108 createPluginTextMessateWidget(); 0109 } 0110 mPluginTextMessageWidget->slotShareSuccess(message); 0111 }); 0112 0113 mRoomWidgetLayout->addWidget(mRoomCounterInfoWidget); 0114 0115 #if HAVE_TEXT_TO_SPEECH 0116 mTextToSpeechWidget->setObjectName(QStringLiteral("mTextToSpeechWidget")); 0117 mRoomWidgetLayout->addWidget(mTextToSpeechWidget); 0118 connect(mRoomWidgetBase, &RoomWidgetBase::textToSpeech, mTextToSpeechWidget, &TextEditTextToSpeech::TextToSpeechContainerWidget::say); 0119 #endif 0120 0121 mRoomWidgetLayout->addWidget(mRoomWidgetBase); 0122 connect(mRoomCounterInfoWidget, &RoomCounterInfoWidget::markAsRead, this, &RoomWidget::slotClearNotification); 0123 connect(mRoomCounterInfoWidget, &RoomCounterInfoWidget::jumpToUnreadMessage, this, &RoomWidget::slotJumpToUnreadMessage); 0124 connect(mRoomHeaderWidget, &RoomHeaderWidget::favoriteChanged, this, &RoomWidget::slotChangeFavorite); 0125 connect(mRoomHeaderWidget, &RoomHeaderWidget::encryptedChanged, this, &RoomWidget::slotEncryptedChanged); 0126 connect(mRoomHeaderWidget, &RoomHeaderWidget::goBackToRoom, this, &RoomWidget::slotGoBackToRoom); 0127 connect(mRoomHeaderWidget, &RoomHeaderWidget::listOfUsersChanged, this, &RoomWidget::slotShowListOfUsersInRoom); 0128 connect(mRoomHeaderWidget, &RoomHeaderWidget::searchMessageRequested, this, &RoomWidget::slotSearchMessages); 0129 connect(mRoomHeaderWidget, &RoomHeaderWidget::actionRequested, this, &RoomWidget::slotActionRequested); 0130 connect(mRoomHeaderWidget, &RoomHeaderWidget::channelInfoRequested, this, &RoomWidget::slotChannelInfoRequested); 0131 connect(mRoomWidgetBase, &RoomWidgetBase::loadHistory, this, &RoomWidget::slotLoadHistory); 0132 connect(mRoomWidgetBase, &RoomWidgetBase::createNewDiscussion, this, &RoomWidget::slotCreateNewDiscussion); 0133 connect(mRoomHeaderWidget, &RoomHeaderWidget::teamChannelsRequested, this, &RoomWidget::slotTeamChannelsRequested); 0134 connect(mRoomHeaderWidget, &RoomHeaderWidget::openTeam, this, &RoomWidget::slotOpenTeamRequested); 0135 connect(mRoomHeaderWidget, &RoomHeaderWidget::callRequested, this, &RoomWidget::slotCallRequested); 0136 setAcceptDrops(true); 0137 } 0138 0139 RoomWidget::~RoomWidget() 0140 { 0141 delete mRoom; 0142 } 0143 0144 void RoomWidget::createPluginTextMessateWidget() 0145 { 0146 mPluginTextMessageWidget = new PluginTextMessageWidget(this); 0147 mPluginTextMessageWidget->setObjectName(QStringLiteral("mPluginTextMessageWidget")); 0148 mRoomWidgetLayout->insertWidget(1, mPluginTextMessageWidget); 0149 } 0150 0151 // TODO using it 0152 void RoomWidget::createRoomReconnectInfoWidget() 0153 { 0154 mRoomReconnectInfoWidget = new ReconnectInfoWidget(this); 0155 mRoomReconnectInfoWidget->setObjectName(QStringLiteral("mRoomReconnectInfoWidget")); 0156 connect(mRoomReconnectInfoWidget, &ReconnectInfoWidget::tryReconnect, this, &RoomWidget::slotTryReconnect); 0157 // After mUsersInRoomFlowWidget 0158 mRoomWidgetLayout->insertWidget(1, mRoomReconnectInfoWidget); 0159 } 0160 0161 // TODO using it. 0162 void RoomWidget::createOffLineWidget() 0163 { 0164 mOffLineWidget = new OffLineWidget(this); 0165 mOffLineWidget->setObjectName(QStringLiteral("mOffLineWidget")); 0166 // After mUsersInRoomFlowWidget 0167 mRoomWidgetLayout->insertWidget(1, mOffLineWidget); 0168 } 0169 0170 // TODO using it. 0171 void RoomWidget::createOtrWidget() 0172 { 0173 mOtrWidget = new OtrWidget(this); 0174 mOtrWidget->setObjectName(QStringLiteral("mOtrWidget")); 0175 connect(mOtrWidget, &OtrWidget::closeOtr, this, &RoomWidget::slotCloseOtr); 0176 connect(mOtrWidget, &OtrWidget::refreshKeys, this, &RoomWidget::slotRefreshOtrKeys); 0177 // After mUsersInRoomFlowWidget 0178 mRoomWidgetLayout->insertWidget(1, mOtrWidget); 0179 } 0180 0181 void RoomWidget::slotLoadHistory() 0182 { 0183 mCurrentRocketChatAccount->loadHistory(mRoomWidgetBase->roomId()); 0184 } 0185 0186 void RoomWidget::slotChannelInfoRequested() 0187 { 0188 if (!mRoom) { 0189 return; 0190 } 0191 if (mRoomType == Room::RoomType::Direct) { 0192 DirectChannelInfoDialog dlg(mCurrentRocketChatAccount, this); 0193 dlg.setUserName(mRoom->name()); 0194 dlg.exec(); 0195 } else { 0196 QPointer<ChannelInfoDialog> dlg = new ChannelInfoDialog(mRoom, mCurrentRocketChatAccount, this); 0197 if (dlg->exec()) { 0198 const RocketChatRestApi::SaveRoomSettingsJob::SaveRoomSettingsInfo info = dlg->saveRoomSettingsInfo(); 0199 if (info.isValid()) { 0200 auto saveRoomSettingsJob = new RocketChatRestApi::SaveRoomSettingsJob(this); 0201 saveRoomSettingsJob->setSaveRoomSettingsInfo(info); 0202 mCurrentRocketChatAccount->restApi()->initializeRestApiJob(saveRoomSettingsJob); 0203 if (!saveRoomSettingsJob->start()) { 0204 qCDebug(RUQOLAWIDGETS_LOG) << "Impossible to start saveRoomSettingsJob"; 0205 } 0206 } 0207 } 0208 delete dlg; 0209 } 0210 } 0211 0212 void RoomWidget::slotActionRequested(RoomHeaderWidget::ChannelActionType type) 0213 { 0214 switch (type) { 0215 case RoomHeaderWidget::ShowMentions: 0216 slotShowMentions(); 0217 break; 0218 case RoomHeaderWidget::ShowPinned: 0219 slotPinnedMessages(); 0220 break; 0221 case RoomHeaderWidget::ShowStarred: 0222 slotStarredMessages(); 0223 break; 0224 case RoomHeaderWidget::ShowDiscussions: 0225 slotShowDiscussions(); 0226 break; 0227 case RoomHeaderWidget::ShowThreads: 0228 slotShowThreads(); 0229 break; 0230 case RoomHeaderWidget::ShowAttachment: 0231 slotShowFileAttachments(); 0232 break; 0233 case RoomHeaderWidget::Notification: 0234 slotConfigureNotification(); 0235 break; 0236 case RoomHeaderWidget::AutoTranslate: 0237 slotConfigureAutoTranslate(); 0238 break; 0239 case RoomHeaderWidget::InviteUsers: 0240 slotInviteUsers(); 0241 break; 0242 case RoomHeaderWidget::AddUsersInRoom: 0243 slotAddUsersInRoom(); 0244 break; 0245 case RoomHeaderWidget::VideoChat: 0246 slotVideoChat(); 0247 break; 0248 case RoomHeaderWidget::PruneMessages: 0249 slotPruneMessages(); 0250 break; 0251 case RoomHeaderWidget::ExportMessages: 0252 slotExportMessages(); 0253 break; 0254 case RoomHeaderWidget::OtrMessages: 0255 // TODO 0256 break; 0257 case RoomHeaderWidget::EncryptMessages: 0258 // TODO 0259 break; 0260 } 0261 } 0262 0263 void RoomWidget::slotPruneMessages() 0264 { 0265 if (!mRoom) { 0266 return; 0267 } 0268 QPointer<PruneMessagesDialog> dlg = new PruneMessagesDialog(mCurrentRocketChatAccount, this); 0269 dlg->setRoomName(mRoom->name()); 0270 if (dlg->exec()) { 0271 RocketChatRestApi::RoomsCleanHistoryJob::CleanHistoryInfo info = dlg->cleanHistoryInfo(); 0272 info.roomId = mRoomWidgetBase->roomId(); 0273 if (KMessageBox::ButtonCode::PrimaryAction 0274 == KMessageBox::questionTwoActions(this, 0275 i18n("Do you want really remove history?"), 0276 i18nc("@title:window", "Remove History"), 0277 KStandardGuiItem::remove(), 0278 KStandardGuiItem::cancel())) { 0279 mCurrentRocketChatAccount->cleanChannelHistory(info); 0280 } 0281 } 0282 delete dlg; 0283 } 0284 0285 void RoomWidget::slotExportMessages() 0286 { 0287 QPointer<ExportMessagesDialog> dlg = new ExportMessagesDialog(this); 0288 if (dlg->exec()) { 0289 RocketChatRestApi::RoomsExportJob::RoomsExportInfo info = dlg->roomExportInfo(); 0290 info.roomId = mRoomWidgetBase->roomId(); 0291 mCurrentRocketChatAccount->exportMessages(info); 0292 } 0293 delete dlg; 0294 } 0295 0296 void RoomWidget::slotVideoChat() 0297 { 0298 mCurrentRocketChatAccount->createJitsiConfCall(mRoomWidgetBase->roomId()); 0299 } 0300 0301 void RoomWidget::slotAddUsersInRoom() 0302 { 0303 QPointer<AddUsersInRoomDialog> dlg = new AddUsersInRoomDialog(mCurrentRocketChatAccount, this); 0304 if (dlg->exec()) { 0305 const QStringList users = dlg->userIds(); 0306 for (const QString &user : users) { 0307 mCurrentRocketChatAccount->addUserToRoom(user, mRoomWidgetBase->roomId(), mRoomType); 0308 } 0309 } 0310 delete dlg; 0311 } 0312 0313 void RoomWidget::slotInviteUsers() 0314 { 0315 InviteUsersDialog dlg(mCurrentRocketChatAccount, this); 0316 dlg.setRoomId(mRoomWidgetBase->roomId()); 0317 dlg.generateLink(); 0318 dlg.exec(); 0319 } 0320 0321 void RoomWidget::updateListView() 0322 { 0323 mRoomWidgetBase->updateListView(); 0324 } 0325 0326 void RoomWidget::slotConfigureAutoTranslate() 0327 { 0328 if (!mRoom) { 0329 return; 0330 } 0331 AutoTranslateConfigureDialog dlg(mCurrentRocketChatAccount, this); 0332 dlg.setRoom(mRoom); 0333 dlg.exec(); 0334 } 0335 0336 void RoomWidget::slotConfigureNotification() 0337 { 0338 if (!mRoom) { 0339 return; 0340 } 0341 ConfigureNotificationDialog dlg(mCurrentRocketChatAccount, this); 0342 dlg.setRoom(mRoom); 0343 dlg.exec(); 0344 } 0345 0346 void RoomWidget::slotStarredMessages() 0347 { 0348 if (!mRoom) { 0349 return; 0350 } 0351 QPointer<ShowStarredMessagesDialog> dlg = new ShowStarredMessagesDialog(mCurrentRocketChatAccount, this); 0352 dlg->setRoomId(mRoomWidgetBase->roomId()); 0353 dlg->setModel(mCurrentRocketChatAccount->listMessagesFilterProxyModel()); 0354 dlg->setRoom(mRoom); 0355 mCurrentRocketChatAccount->getListMessages(mRoomWidgetBase->roomId(), ListMessagesModel::StarredMessages); 0356 connect(dlg, &ShowListMessageBaseDialog::goToMessageRequested, this, &RoomWidget::slotGotoMessage); 0357 dlg->exec(); 0358 delete dlg; 0359 } 0360 0361 void RoomWidget::slotPinnedMessages() 0362 { 0363 if (!mRoom) { 0364 return; 0365 } 0366 QPointer<ShowPinnedMessagesDialog> dlg = new ShowPinnedMessagesDialog(mCurrentRocketChatAccount, this); 0367 dlg->setRoomId(mRoomWidgetBase->roomId()); 0368 dlg->setRoom(mRoom); 0369 dlg->setModel(mCurrentRocketChatAccount->listMessagesFilterProxyModel()); 0370 mCurrentRocketChatAccount->getListMessages(mRoomWidgetBase->roomId(), ListMessagesModel::PinnedMessages); 0371 connect(dlg, &ShowListMessageBaseDialog::goToMessageRequested, this, &RoomWidget::slotGotoMessage); 0372 dlg->exec(); 0373 delete dlg; 0374 } 0375 0376 void RoomWidget::slotShowMentions() 0377 { 0378 if (!mRoom) { 0379 return; 0380 } 0381 QPointer<ShowMentionsMessagesDialog> dlg = new ShowMentionsMessagesDialog(mCurrentRocketChatAccount, this); 0382 dlg->setRoomId(mRoomWidgetBase->roomId()); 0383 dlg->setModel(mCurrentRocketChatAccount->listMessagesFilterProxyModel()); 0384 dlg->setRoom(mRoom); 0385 mCurrentRocketChatAccount->getListMessages(mRoomWidgetBase->roomId(), ListMessagesModel::MentionsMessages); 0386 connect(dlg, &ShowListMessageBaseDialog::goToMessageRequested, this, &RoomWidget::slotGotoMessage); 0387 dlg->exec(); 0388 delete dlg; 0389 } 0390 0391 void RoomWidget::slotShowThreads() 0392 { 0393 if (!mRoom) { 0394 return; 0395 } 0396 QPointer<ShowThreadsDialog> dlg = new ShowThreadsDialog(mCurrentRocketChatAccount, this); 0397 dlg->setModel(mCurrentRocketChatAccount->listMessagesFilterProxyModel()); 0398 dlg->setRoomId(mRoomWidgetBase->roomId()); 0399 dlg->setRoom(mRoom); 0400 mCurrentRocketChatAccount->getListMessages(mRoomWidgetBase->roomId(), ListMessagesModel::ThreadsMessages); 0401 connect(dlg, &ShowListMessageBaseDialog::goToMessageRequested, this, &RoomWidget::slotGotoMessage); 0402 dlg->exec(); 0403 delete dlg; 0404 } 0405 0406 void RoomWidget::slotShowDiscussions() 0407 { 0408 auto dlg = new ShowDiscussionsDialog(mCurrentRocketChatAccount, this); 0409 dlg->setModel(mCurrentRocketChatAccount->discussionsFilterProxyModel()); 0410 dlg->setRoomId(mRoomWidgetBase->roomId()); 0411 mCurrentRocketChatAccount->discussionsInRoom(mRoomWidgetBase->roomId()); 0412 dlg->show(); 0413 } 0414 0415 void RoomWidget::slotShowFileAttachments() 0416 { 0417 auto dlg = new ShowAttachmentDialog(mCurrentRocketChatAccount, this); 0418 mCurrentRocketChatAccount->roomFiles(mRoomWidgetBase->roomId(), mRoomType); 0419 dlg->setModel(mCurrentRocketChatAccount->filesForRoomFilterProxyModel()); 0420 dlg->setRoomId(mRoomWidgetBase->roomId()); 0421 dlg->setRoomType(mRoomType); 0422 dlg->show(); 0423 } 0424 0425 void RoomWidget::slotSearchMessages() 0426 { 0427 if (!mRoom) { 0428 return; 0429 } 0430 SearchMessageDialog dlg(mCurrentRocketChatAccount, this); 0431 dlg.setRoomId(mRoomWidgetBase->roomId()); 0432 dlg.setRoom(mRoom); 0433 dlg.setModel(mCurrentRocketChatAccount->searchMessageFilterProxyModel()); 0434 connect(&dlg, &SearchMessageDialog::goToMessageRequested, this, &RoomWidget::slotGotoMessage); 0435 dlg.exec(); 0436 } 0437 0438 void RoomWidget::slotCallRequested() 0439 { 0440 if (!mRoom) { 0441 return; 0442 } 0443 if (mRoom->channelType() == Room::RoomType::Direct && mRoom->userNames().count() == 2) { 0444 QPointer<ConferenceDirectCallDialog> dlg = new ConferenceDirectCallDialog(mCurrentRocketChatAccount, this); 0445 dlg->setRoomId(mRoomWidgetBase->roomId()); 0446 dlg->setAllowRinging(mRoom->hasPermission(QStringLiteral("videoconf-ring-users"))); 0447 if (dlg->exec()) { 0448 #if 0 0449 auto conferenceInfoJob = new RocketChatRestApi::VideoConferenceInfoJob(this); 0450 conferenceInfoJob->setCallId(obj[QLatin1String("callId")].toString()); 0451 mCurrentRocketChatAccount->restApi()->initializeRestApiJob(conferenceInfoJob); 0452 connect(conferenceInfoJob, &RocketChatRestApi::VideoConferenceInfoJob::videoConferenceInfoDone, this, [this, callInfo](const QJsonObject &obj) { 0453 qDebug() << " info " << obj; 0454 VideoConferenceInfo info; 0455 info.parse(obj); 0456 }); 0457 if (!conferenceInfoJob->start()) { 0458 qCWarning(RUQOLAWIDGETS_LOG) << "Impossible to start VideoConferenceInfoJob job"; 0459 } 0460 #endif 0461 // TODO show conf call info 0462 } 0463 delete dlg; 0464 } else { 0465 auto job = new RocketChatRestApi::VideoConferenceCapabilitiesJob(this); 0466 mCurrentRocketChatAccount->restApi()->initializeRestApiJob(job); 0467 connect(job, &RocketChatRestApi::VideoConferenceCapabilitiesJob::noVideoConferenceProviderApps, this, [this] { 0468 KMessageBox::information(this, i18n("A workspace admin needs to install and configure a conference call apps."), i18n("Video Conference")); 0469 }); 0470 connect(job, &RocketChatRestApi::VideoConferenceCapabilitiesJob::videoConferenceCapabilitiesDone, this, [this](const QJsonObject &obj) { 0471 // qDebug() << "obj " << obj; 0472 // {"capabilities":{"cam":true,"mic":true,"title":true},"providerName":"jitsi","success":true} 0473 const QJsonObject capabilitiesObj = obj[QLatin1String("capabilities")].toObject(); 0474 const bool useCam = capabilitiesObj[QLatin1String("cam")].toBool(); 0475 const bool useMic = capabilitiesObj[QLatin1String("mic")].toBool(); 0476 ConferenceCallWidget::ConferenceCallStart callInfo; 0477 callInfo.useCamera = useCam; 0478 callInfo.useMic = useMic; 0479 0480 QPointer<ConferenceCallDialog> dlg = new ConferenceCallDialog(mCurrentRocketChatAccount, this); 0481 dlg->setConferenceCallInfo(callInfo); 0482 if (dlg->exec()) { 0483 const ConferenceCallWidget::ConferenceCallStart conferenceCallInfo = dlg->conferenceCallInfo(); 0484 0485 auto job = new RocketChatRestApi::VideoConferenceStartJob(this); 0486 RocketChatRestApi::VideoConferenceStartJob::VideoConferenceStartInfo startInfo; 0487 startInfo.roomId = mRoomWidgetBase->roomId(); 0488 startInfo.allowRinging = mRoom->hasPermission(QStringLiteral("videoconf-ring-users")); 0489 job->setInfo(startInfo); 0490 mCurrentRocketChatAccount->restApi()->initializeRestApiJob(job); 0491 connect(job, &RocketChatRestApi::VideoConferenceStartJob::videoConferenceStartDone, this, [this, conferenceCallInfo](const QJsonObject &obj) { 0492 // qDebug() << "obj " << obj; 0493 // {"data":{"callId":"63949ea24ef3f3baa9658f25","providerName":"jitsi","rid":"hE6RS3iv5ND5EGWC6","type":"videoconference"},"success":true} 0494 const QString callId{obj[QLatin1String("callId")].toString()}; 0495 mCurrentRocketChatAccount->videoConferenceMessageInfoManager()->addCallId(callId); 0496 auto conferenceJoinJob = new RocketChatRestApi::VideoConferenceJoinJob(this); 0497 RocketChatRestApi::VideoConferenceJoinJob::VideoConferenceJoinInfo joinInfo; 0498 joinInfo.callId = callId; 0499 joinInfo.useCamera = conferenceCallInfo.useCamera; 0500 joinInfo.useMicro = conferenceCallInfo.useMic; 0501 conferenceJoinJob->setInfo(joinInfo); 0502 mCurrentRocketChatAccount->restApi()->initializeRestApiJob(conferenceJoinJob); 0503 connect(conferenceJoinJob, &RocketChatRestApi::VideoConferenceJoinJob::videoConferenceJoinDone, this, [](const QJsonObject &joinObject) { 0504 // qDebug() << " join info " << obj; 0505 QDesktopServices::openUrl(QUrl(joinObject[QLatin1String("url")].toString())); 0506 }); 0507 if (!conferenceJoinJob->start()) { 0508 qCWarning(RUQOLAWIDGETS_LOG) << "Impossible to start VideoConferenceJoinJob job"; 0509 } 0510 }); 0511 if (!job->start()) { 0512 qCWarning(RUQOLAWIDGETS_LOG) << "Impossible to start VideoConferenceCapabilitiesJob job"; 0513 } 0514 } 0515 delete dlg; 0516 }); 0517 if (!job->start()) { 0518 qCWarning(RUQOLAWIDGETS_LOG) << "Impossible to start VideoConferenceCapabilitiesJob job"; 0519 } 0520 } 0521 } 0522 0523 void RoomWidget::slotOpenTeamRequested(const QString &teamId) 0524 { 0525 Q_EMIT mCurrentRocketChatAccount->openTeamNameRequested(teamId); 0526 } 0527 0528 void RoomWidget::slotTeamChannelsRequested() 0529 { 0530 if (!mRoom) { 0531 return; 0532 } 0533 TeamChannelsDialog dlg(mCurrentRocketChatAccount, this); 0534 dlg.setRoom(mRoom); 0535 dlg.exec(); 0536 } 0537 0538 void RoomWidget::slotCreateNewDiscussion(const QString &messageId, const QString &originalMessage) 0539 { 0540 mRoomWidgetBase->slotCreateNewDiscussion(messageId, originalMessage, mRoomHeaderWidget->roomName()); 0541 } 0542 0543 void RoomWidget::slotCreatePrivateDiscussion(const QString &userName) 0544 { 0545 Q_EMIT mCurrentRocketChatAccount->openLinkRequested(RoomUtil::generateUserLink(userName)); 0546 } 0547 0548 void RoomWidget::dragEnterEvent(QDragEnterEvent *event) 0549 { 0550 // Don't allow to drop element when it's blocked 0551 if (mRoom && mRoom->roomIsBlocked()) { 0552 return; 0553 } 0554 const QMimeData *mimeData = event->mimeData(); 0555 if (mimeData->hasUrls()) { 0556 event->accept(); 0557 } 0558 } 0559 0560 void RoomWidget::dropEvent(QDropEvent *event) 0561 { 0562 const QMimeData *mimeData = event->mimeData(); 0563 if (mimeData->hasUrls()) { 0564 mRoomWidgetBase->messageLineWidget()->handleMimeData(mimeData); 0565 } 0566 } 0567 0568 void RoomWidget::storeRoomSettings() 0569 { 0570 if (mCurrentRocketChatAccount) { 0571 if (mRoomWidgetBase->messageLineWidget()->text().isEmpty()) { 0572 auto *vbar = mRoomWidgetBase->messageListView()->verticalScrollBar(); 0573 if (vbar->value() != vbar->maximum()) { 0574 AccountRoomSettings::PendingTypedInfo info; 0575 info.scrollbarPosition = mRoomWidgetBase->messageListView()->verticalScrollBar()->value(); 0576 mCurrentRocketChatAccount->accountRoomSettings()->add(mRoomWidgetBase->roomId(), info); 0577 } else { 0578 mCurrentRocketChatAccount->accountRoomSettings()->remove(mRoomWidgetBase->roomId()); 0579 } 0580 } else { 0581 AccountRoomSettings::PendingTypedInfo info; 0582 info.text = mRoomWidgetBase->messageLineWidget()->text(); 0583 info.messageIdBeingEdited = mRoomWidgetBase->messageLineWidget()->messageIdBeingEdited(); 0584 info.scrollbarPosition = mRoomWidgetBase->messageListView()->verticalScrollBar()->value(); 0585 info.threadMessageId = mRoomWidgetBase->messageLineWidget()->threadMessageId(); 0586 info.quotePermalink = mRoomWidgetBase->messageLineWidget()->quotePermalink(); 0587 info.quoteText = mRoomWidgetBase->messageLineWidget()->quoteText(); 0588 mCurrentRocketChatAccount->accountRoomSettings()->add(mRoomWidgetBase->roomId(), info); 0589 } 0590 } 0591 } 0592 0593 void RoomWidget::clearBeforeSwitching() 0594 { 0595 mRoomWidgetBase->messageLineWidget()->setThreadMessageId({}); 0596 mRoomWidgetBase->messageLineWidget()->setQuoteMessage({}, {}); 0597 } 0598 0599 void RoomWidget::forceLineEditFocus() 0600 { 0601 mRoomWidgetBase->messageLineWidget()->setFocus(); 0602 } 0603 0604 void RoomWidget::setChannelSelected(const QString &roomId, Room::RoomType roomType) 0605 { 0606 storeRoomSettings(); 0607 setRoomId(roomId); 0608 setRoomType(roomType); 0609 clearBeforeSwitching(); 0610 const AccountRoomSettings::PendingTypedInfo currentPendingInfo = mCurrentRocketChatAccount->accountRoomSettings()->value(roomId); 0611 if (currentPendingInfo.isValid()) { 0612 mRoomWidgetBase->messageLineWidget()->setQuoteMessage(currentPendingInfo.quotePermalink, currentPendingInfo.quoteText); 0613 mRoomWidgetBase->messageLineWidget()->setThreadMessageId(currentPendingInfo.threadMessageId); 0614 mRoomWidgetBase->messageLineWidget()->setText(currentPendingInfo.text); 0615 mRoomWidgetBase->messageLineWidget()->setMessageIdBeingEdited(currentPendingInfo.messageIdBeingEdited); 0616 if (currentPendingInfo.scrollbarPosition != -1) { 0617 mRoomWidgetBase->messageListView()->verticalScrollBar()->setValue(currentPendingInfo.scrollbarPosition); 0618 } 0619 } else { 0620 mRoomWidgetBase->messageLineWidget()->setText(QString()); 0621 } 0622 mRoomWidgetBase->messageLineWidget()->setMode(mRoomWidgetBase->messageLineWidget()->messageIdBeingEdited().isEmpty() 0623 ? MessageLineWidget::EditingMode::NewMessage 0624 : MessageLineWidget::EditingMode::EditMessage); 0625 0626 mRoomWidgetBase->messageLineWidget()->setFocus(); 0627 } 0628 0629 void RoomWidget::slotUpdateRoomCounterInfoWidget() 0630 { 0631 if (mRoom) { 0632 mRoomCounterInfoWidget->setChannelCounterInfo(mRoom->channelCounterInfo()); 0633 } 0634 } 0635 0636 void RoomWidget::updateRoomHeader() 0637 { 0638 if (mRoom) { 0639 mRoomHeaderWidget->setRoomName(mRoom->displayRoomName()); 0640 mRoomHeaderWidget->setRoomAnnouncement(mRoom->displayAnnouncement()); 0641 mRoomHeaderWidget->setRoomTopic(mRoom->displayTopic()); 0642 mRoomHeaderWidget->setFavoriteStatus(mRoom->favorite()); 0643 mRoomHeaderWidget->setEncypted(mRoom->encrypted() && mRoom->hasPermission(QStringLiteral("edit-room"))); 0644 mRoomHeaderWidget->setIsDiscussion(mRoom->isDiscussionRoom()); 0645 mRoomHeaderWidget->setIsMainTeam(mRoom->teamInfo().mainTeam()); 0646 mRoomHeaderWidget->setTeamRoomInfo(mRoom->teamRoomInfo()); 0647 mRoomHeaderWidget->setIsDirectGroup((mRoom->channelType() == Room::RoomType::Direct) && mRoom->userNames().count() > 2); 0648 if (mRoom->roomId() == QLatin1String("%1%1").arg(mCurrentRocketChatAccount->userId())) { 0649 mRoomHeaderWidget->setCallEnabled(false); 0650 } else { 0651 mRoomHeaderWidget->setCallEnabled(true); 0652 } 0653 // TODO Description ? 0654 0655 mRoomWidgetBase->updateRoomReadOnly(mRoom); 0656 if (mRoom->channelCounterInfo().isValid() && mRoom->channelCounterInfo().unreadMessages() > 0) { 0657 mRoomCounterInfoWidget->animatedShow(); 0658 } else { 0659 mRoomCounterInfoWidget->animatedHide(); 0660 } 0661 } 0662 } 0663 0664 QString RoomWidget::roomId() const 0665 { 0666 return mRoomWidgetBase->roomId(); 0667 } 0668 0669 void RoomWidget::setRoomType(Room::RoomType roomType) 0670 { 0671 mRoomType = roomType; 0672 } 0673 0674 Room *RoomWidget::room() const 0675 { 0676 return mRoom; 0677 } 0678 0679 void RoomWidget::slotShowListOfUsersInRoom(bool checked) 0680 { 0681 mUsersInRoomFlowWidget->setVisible(checked); 0682 } 0683 0684 void RoomWidget::setRoomId(const QString &roomId) 0685 { 0686 mRoomWidgetBase->setRoomId(roomId); 0687 mRoom = mCurrentRocketChatAccount->room(mRoomWidgetBase->roomId()); 0688 if (mRoom) { 0689 connectRoom(); 0690 mRoomWidgetBase->messageLineWidget()->setRoomId(roomId); 0691 mRoomWidgetBase->messageListView()->setChannelSelected(mRoom); 0692 mUsersInRoomFlowWidget->setRoom(mRoom); 0693 mRoomHeaderWidget->setRoom(mRoom); 0694 } else { 0695 qCWarning(RUQOLAWIDGETS_LOG) << " Impossible to find room " << roomId; 0696 } 0697 } 0698 0699 void RoomWidget::connectRoom() 0700 { 0701 if (mRoom) { 0702 connect(mRoom, &Room::announcementChanged, this, [this]() { 0703 mRoomHeaderWidget->setRoomAnnouncement(mRoom->displayAnnouncement()); 0704 }); 0705 connect(mRoom, &Room::topicChanged, this, [this]() { 0706 mRoomHeaderWidget->setRoomTopic(mRoom->displayTopic()); 0707 }); 0708 connect(mRoom, &Room::nameChanged, this, [this]() { 0709 mRoomHeaderWidget->setRoomName(mRoom->displayRoomName()); 0710 }); 0711 connect(mRoom, &Room::fnameChanged, this, [this]() { 0712 mRoomHeaderWidget->setRoomName(mRoom->displayRoomName()); 0713 }); 0714 connect(mRoom, &Room::favoriteChanged, this, [this]() { 0715 mRoomHeaderWidget->setFavoriteStatus(mRoom->favorite()); 0716 }); 0717 connect(mRoom, &Room::encryptedChanged, this, [this]() { 0718 mRoomHeaderWidget->setEncypted(mRoom->encrypted()); 0719 }); 0720 // TODO verify it. 0721 connect(mRoom, &Room::teamInfoChanged, this, [this]() { 0722 mRoomHeaderWidget->setIsMainTeam(mRoom->teamInfo().mainTeam()); 0723 }); 0724 connect(mRoom, &Room::autoTranslateLanguageChanged, this, &RoomWidget::updateListView); 0725 connect(mRoom, &Room::autoTranslateChanged, this, &RoomWidget::updateListView); 0726 connect(mRoom, &Room::ignoredUsersChanged, this, &RoomWidget::updateListView); 0727 connect(mRoom, &Room::channelCounterInfoChanged, this, &RoomWidget::slotUpdateRoomCounterInfoWidget); 0728 connect(mRoom, &Room::highlightsWordChanged, this, &RoomWidget::updateListView); 0729 } 0730 slotUpdateRoomCounterInfoWidget(); 0731 updateRoomHeader(); 0732 } 0733 0734 void RoomWidget::slotJumpToUnreadMessage(qint64 numberOfMessage) 0735 { 0736 MessagesModel *roomMessageModel = mCurrentRocketChatAccount->messageModelForRoom(mRoomWidgetBase->roomId()); 0737 if (roomMessageModel->rowCount() >= numberOfMessage) { 0738 const QString messageId = roomMessageModel->messageIdFromIndex(roomMessageModel->rowCount() - numberOfMessage); 0739 mRoomWidgetBase->messageListView()->goToMessage(messageId); 0740 } else { 0741 RocketChatRestApi::ChannelHistoryJob::ChannelHistoryInfo info; 0742 switch (mRoomType) { 0743 case Room::RoomType::Channel: 0744 info.channelType = RocketChatRestApi::ChannelHistoryJob::Channel; 0745 break; 0746 case Room::RoomType::Direct: 0747 info.channelType = RocketChatRestApi::ChannelHistoryJob::Direct; 0748 break; 0749 case Room::RoomType::Private: 0750 info.channelType = RocketChatRestApi::ChannelHistoryJob::Groups; 0751 break; 0752 case Room::RoomType::Unknown: 0753 qCWarning(RUQOLAWIDGETS_LOG) << " Problem with room type "; 0754 break; 0755 } 0756 if (mRoomType == Room::RoomType::Unknown) { 0757 return; 0758 } 0759 auto job = new RocketChatRestApi::ChannelHistoryJob(this); 0760 info.count = numberOfMessage - roomMessageModel->rowCount() + 1; 0761 info.roomId = mRoomWidgetBase->roomId(); 0762 const qint64 endDateTime = roomMessageModel->lastTimestamp(); 0763 info.latestMessage = QDateTime::fromMSecsSinceEpoch(endDateTime, Qt::UTC).toString(Qt::ISODateWithMs); 0764 // qDebug() << " info.latestMessage " << info.latestMessage; 0765 job->setChannelHistoryInfo(info); 0766 mCurrentRocketChatAccount->restApi()->initializeRestApiJob(job); 0767 connect(job, &RocketChatRestApi::ChannelHistoryJob::channelHistoryDone, this, [this, numberOfMessage, roomMessageModel](const QJsonObject &obj) { 0768 mCurrentRocketChatAccount->rocketChatBackend()->processIncomingMessages(obj.value(QLatin1String("messages")).toArray(), true, true); 0769 // qDebug() << " obj " << obj; 0770 // qDebug() << " roomMessageModel->rowCount() " << roomMessageModel->rowCount(); 0771 // qDebug() << " numberOfMessage " << numberOfMessage; 0772 // qDebug() << " initialRowCount " << (roomMessageModel->rowCount() - numberOfMessage); 0773 0774 const QString messageId = roomMessageModel->messageIdFromIndex(roomMessageModel->rowCount() - numberOfMessage); 0775 mRoomWidgetBase->messageListView()->goToMessage(messageId); 0776 }); 0777 if (!job->start()) { 0778 qCDebug(RUQOLAWIDGETS_LOG) << "Impossible to start ChannelHistoryJob"; 0779 } 0780 } 0781 } 0782 0783 void RoomWidget::scrollToMessageId(const QString &messageId) 0784 { 0785 slotGotoMessage(messageId, {}); 0786 } 0787 0788 void RoomWidget::slotGotoMessage(const QString &messageId, const QString &messageDateTimeUtc) 0789 { 0790 MessageListView *messageListView = mRoomWidgetBase->messageListView(); 0791 auto messageModel = qobject_cast<MessagesModel *>(messageListView->model()); 0792 Q_ASSERT(messageModel); 0793 const QModelIndex index = messageModel->indexForMessage(messageId); 0794 if (index.isValid()) { 0795 messageListView->scrollTo(index); 0796 } else /* if (!messageDateTimeUtc.isEmpty())*/ { 0797 RocketChatRestApi::ChannelHistoryJob::ChannelHistoryInfo info; 0798 switch (mRoomType) { 0799 case Room::RoomType::Channel: 0800 info.channelType = RocketChatRestApi::ChannelHistoryJob::Channel; 0801 break; 0802 case Room::RoomType::Direct: 0803 info.channelType = RocketChatRestApi::ChannelHistoryJob::Direct; 0804 break; 0805 case Room::RoomType::Private: 0806 info.channelType = RocketChatRestApi::ChannelHistoryJob::Groups; 0807 break; 0808 case Room::RoomType::Unknown: 0809 qCWarning(RUQOLAWIDGETS_LOG) << " Problem with room type "; 0810 break; 0811 } 0812 if (mRoomType == Room::RoomType::Unknown) { 0813 return; 0814 } 0815 auto job = new RocketChatRestApi::ChannelHistoryJob(this); 0816 info.roomId = mRoomWidgetBase->roomId(); 0817 const qint64 endDateTime = messageModel->lastTimestamp(); 0818 info.latestMessage = QDateTime::fromMSecsSinceEpoch(endDateTime, Qt::UTC).toString(Qt::ISODateWithMs); 0819 info.oldestMessage = messageDateTimeUtc; 0820 info.inclusive = true; 0821 info.count = 5000; 0822 // qDebug() << " info " << info; 0823 job->setChannelHistoryInfo(info); 0824 mCurrentRocketChatAccount->restApi()->initializeRestApiJob(job); 0825 connect(job, &RocketChatRestApi::ChannelHistoryJob::channelHistoryDone, this, [messageId, messageModel, messageListView, this](const QJsonObject &obj) { 0826 mCurrentRocketChatAccount->rocketChatBackend()->processIncomingMessages(obj.value(QLatin1String("messages")).toArray(), true, true); 0827 const QModelIndex index = messageModel->indexForMessage(messageId); 0828 if (index.isValid()) { 0829 messageListView->scrollTo(index); 0830 } else { 0831 qCWarning(RUQOLAWIDGETS_LOG) << "Message not found:" << messageId; 0832 } 0833 }); 0834 if (!job->start()) { 0835 qCDebug(RUQOLAWIDGETS_LOG) << "Impossible to start ChannelHistoryJob"; 0836 } 0837 } 0838 } 0839 0840 void RoomWidget::slotClearNotification() 0841 { 0842 mCurrentRocketChatAccount->markRoomAsRead(mRoomWidgetBase->roomId()); 0843 } 0844 0845 void RoomWidget::slotEncryptedChanged(bool b) 0846 { 0847 RocketChatRestApi::SaveRoomSettingsJob::SaveRoomSettingsInfo info; 0848 info.encrypted = b; 0849 info.roomId = mRoomWidgetBase->roomId(); 0850 info.roomType = mRoom ? mRoom->roomFromRoomType(mRoom->channelType()) : QString(); 0851 info.mSettingsWillBeChanged |= RocketChatRestApi::SaveRoomSettingsJob::SaveRoomSettingsInfo::Encrypted; 0852 auto saveRoomSettingsJob = new RocketChatRestApi::SaveRoomSettingsJob(this); 0853 saveRoomSettingsJob->setSaveRoomSettingsInfo(info); 0854 mCurrentRocketChatAccount->restApi()->initializeRestApiJob(saveRoomSettingsJob); 0855 if (!saveRoomSettingsJob->start()) { 0856 qCDebug(RUQOLAWIDGETS_LOG) << "Impossible to start saveRoomSettingsJob"; 0857 } 0858 } 0859 0860 void RoomWidget::slotChangeFavorite(bool b) 0861 { 0862 mCurrentRocketChatAccount->changeFavorite(mRoomWidgetBase->roomId(), b); 0863 } 0864 0865 Room::RoomType RoomWidget::roomType() const 0866 { 0867 return mRoomType; 0868 } 0869 0870 void RoomWidget::setCurrentRocketChatAccount(RocketChatAccount *account) 0871 { 0872 if (mCurrentRocketChatAccount) { 0873 disconnect(mCurrentRocketChatAccount, &RocketChatAccount::openThreadRequested, this, &RoomWidget::slotOpenThreadRequested); 0874 disconnect(mCurrentRocketChatAccount, &RocketChatAccount::displayReconnectWidget, this, &RoomWidget::slotDisplayReconnectWidget); 0875 disconnect(mCurrentRocketChatAccount, &RocketChatAccount::loginStatusChanged, this, &RoomWidget::slotLoginStatusChanged); 0876 disconnect(mCurrentRocketChatAccount, &RocketChatAccount::needUpdateMessageView, this, &RoomWidget::updateListView); 0877 } 0878 0879 mCurrentRocketChatAccount = account; 0880 mRoomWidgetBase->setCurrentRocketChatAccount(account); 0881 connect(mCurrentRocketChatAccount, &RocketChatAccount::openThreadRequested, this, &RoomWidget::slotOpenThreadRequested); 0882 connect(mCurrentRocketChatAccount, &RocketChatAccount::displayReconnectWidget, this, &RoomWidget::slotDisplayReconnectWidget); 0883 connect(mCurrentRocketChatAccount, &RocketChatAccount::loginStatusChanged, this, &RoomWidget::slotLoginStatusChanged); 0884 connect(mCurrentRocketChatAccount, &RocketChatAccount::needUpdateMessageView, this, &RoomWidget::updateListView); 0885 // TODO verify if we need to show or not reconnect widget 0886 mRoomHeaderWidget->setCurrentRocketChatAccount(account); 0887 mUsersInRoomFlowWidget->setCurrentRocketChatAccount(account); 0888 } 0889 0890 void RoomWidget::slotLoginStatusChanged() 0891 { 0892 const auto loginStatus = mCurrentRocketChatAccount->loginStatus(); 0893 if (loginStatus == DDPAuthenticationManager::LoggedIn) { 0894 if (mRoomReconnectInfoWidget) { 0895 mRoomReconnectInfoWidget->hide(); 0896 } 0897 } 0898 } 0899 0900 void RoomWidget::slotGoBackToRoom() 0901 { 0902 if (mRoom) { 0903 Q_EMIT selectChannelRequested(mRoom->parentRid()); 0904 } 0905 } 0906 0907 void RoomWidget::slotOpenThreadRequested(const QString &threadMessageId, 0908 const QString &threadMessagePreview, 0909 bool threadIsFollowing, 0910 const Message &threadMessage) 0911 { 0912 qCDebug(RUQOLA_THREAD_MESSAGE_WIDGETS_LOG) << "threadMessageId: " << threadMessageId; 0913 auto dlg = new ThreadMessageDialog(mCurrentRocketChatAccount, this); 0914 ThreadMessageWidget::ThreadMessageInfo info; 0915 info.threadMessageId = threadMessageId; 0916 info.threadMessagePreview = threadMessagePreview; 0917 info.threadIsFollowing = threadIsFollowing; 0918 info.room = mRoom; 0919 info.messageThread = threadMessage; 0920 dlg->setThreadMessageInfo(info); 0921 dlg->show(); 0922 } 0923 0924 void RoomWidget::setLayoutSpacing(int spacing) 0925 { 0926 mRoomWidgetBase->layout()->setSpacing(spacing); 0927 } 0928 0929 void RoomWidget::slotTryReconnect() 0930 { 0931 mCurrentRocketChatAccount->reconnectToServer(); 0932 } 0933 0934 void RoomWidget::slotDisplayReconnectWidget(int seconds) 0935 { 0936 // Disable for the moment it seems to create some problems 0937 // FIXME mRoomReconnectInfoWidget->setReconnectSecondDelay(seconds); 0938 } 0939 0940 void RoomWidget::slotCloseOtr() 0941 { 0942 mCurrentRocketChatAccount->ddp()->streamNotifyUserOtrEnd(roomId(), mCurrentRocketChatAccount->userId()); 0943 } 0944 0945 void RoomWidget::slotRefreshOtrKeys() 0946 { 0947 // TODO 0948 } 0949 0950 #include "moc_roomwidget.cpp"