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"