diff --git a/tools/chatgui/src/callwindow.cpp b/tools/chatgui/src/callwindow.cpp index 3f6247da..4cdd94df 100644 --- a/tools/chatgui/src/callwindow.cpp +++ b/tools/chatgui/src/callwindow.cpp @@ -35,12 +35,29 @@ enum class Glyph { Phone, PhoneDown, Mic, MicOff }; static QString callText(const char* text) { return QCoreApplication::translate("ChatGui", text); } -// Только изображение и миниатюра; подписи/настройки находятся в отдельной панели окна. -class CallVideoCanvas : public QWidget { +// Отдельное окно показа. Закрытие скрывает только изображение, не завершает звонок. +class CallVideoWindow : public QWidget { public: - explicit CallVideoCanvas(QWidget* parent) : QWidget(parent) { setMinimumSize(160, 120); } + explicit CallVideoWindow(QWidget* parent) : QWidget(parent, Qt::Window) { + setObjectName("callVideoWindow"); setMinimumSize(320, 240); setAttribute(Qt::WA_QuitOnClose, false); + QSize available = parent->screen() ? parent->screen()->availableGeometry().size() : QSize(1280, 900); + resize(qMin(1100, available.width() * 9 / 10), qMin(800, available.height() * 9 / 10)); + } QImage remote, local; protected: + void closeEvent(QCloseEvent* event) override { + DEBUG_INFO(DEBUG_CATEGORY_CALL, "call video window closed; call continues"); event->accept(); + } + void keyPressEvent(QKeyEvent* event) override { + if (event->key() == Qt::Key_F11 || (event->key() == Qt::Key_Escape && isFullScreen())) { + toggleFullScreen(); event->accept(); return; + } + QWidget::keyPressEvent(event); + } + void mouseDoubleClickEvent(QMouseEvent* event) override { + if (event->button() == Qt::LeftButton) { toggleFullScreen(); event->accept(); return; } + QWidget::mouseDoubleClickEvent(event); + } void paintEvent(QPaintEvent*) override { QPainter painter(this); painter.fillRect(rect(), Qt::black); painter.setRenderHint(QPainter::SmoothPixmapTransform); if (remote.isNull()) return; @@ -53,6 +70,12 @@ protected: painter.setPen(QPen(Qt::white, 2)); painter.drawRect(target.adjusted(-1, -1, 1, 1)); painter.drawImage(target, local); } } +private: + void toggleFullScreen() { + if (isFullScreen()) { showNormal(); if (!m_geometry.isEmpty()) restoreGeometry(m_geometry); } + else { m_geometry = saveGeometry(); showFullScreen(); } + } + QByteArray m_geometry; }; static QColor colorForName(const QString& name) { @@ -215,10 +238,10 @@ CallWindow::CallWindow(QWidget* parent) auto* root = new QVBoxLayout(this); root->setContentsMargins(12, 12, 12, 12); - m_videoArea = new CallVideoCanvas(this); m_videoArea->hide(); root->addWidget(m_videoArea, 1); + m_videoWindow = new CallVideoWindow(this); m_panel = new QWidget(this); m_panel->setObjectName("callPanel"); m_panel->setMaximumWidth(640); m_panel->setStyleSheet("QWidget#callPanel { background: palette(window); border-radius: 12px; }"); - root->addWidget(m_panel, 0, Qt::AlignHCenter | Qt::AlignBottom); + root->addWidget(m_panel, 0, Qt::AlignCenter); QVBoxLayout* lay = new QVBoxLayout(m_panel); lay->setContentsMargins(16, 20, 16, 16); lay->setSpacing(8); @@ -239,10 +262,14 @@ CallWindow::CallWindow(QWidget* parent) m_videoEnabled = new QCheckBox(callText("Send video"), this); actions->addWidget(m_videoEnabled); actions->addStretch(1); m_settingsBtn = new QPushButton(callText("Call settings"), this); m_settingsBtn->setCheckable(true); actions->addWidget(m_settingsBtn); - m_fullScreenBtn = new QPushButton(callText("Full screen"), this); actions->addWidget(m_fullScreenBtn); + m_showVideoBtn = new QPushButton(callText("Show video"), this); actions->addWidget(m_showVideoBtn); lay->addLayout(actions); connect(m_settingsBtn, &QPushButton::toggled, this, [this] { updateVideoPresentation(); }); - connect(m_fullScreenBtn, &QPushButton::clicked, this, &CallWindow::toggleFullScreen); + connect(m_showVideoBtn, &QPushButton::clicked, this, [this] { + if (m_videoWindow->isMinimized()) m_videoWindow->showNormal(); else m_videoWindow->show(); + m_videoWindow->raise(); m_videoWindow->activateWindow(); + DEBUG_INFO(DEBUG_CATEGORY_CALL, "call video window reopened"); + }); auto* modes = new QHBoxLayout(); m_camera = new QComboBox(this); for (const auto& camera : CallVideoEngine::cameras()) m_camera->addItem(camera.first, camera.second); @@ -267,13 +294,13 @@ CallWindow::CallWindow(QWidget* parent) }); connect(m_video, &CallVideoEngine::imageChanged, this, [this](const QImage& image) { bool changed = m_remoteImage.isNull() != image.isNull(); m_remoteImage = image; - static_cast(m_videoArea)->remote = image; m_videoArea->update(); + m_videoWindow->remote = image; m_videoWindow->update(); if (changed) updateVideoPresentation(); update(); }); connect(m_video, &CallVideoEngine::localImageChanged, this, [this](const QImage& image) { bool changed = m_localImage.isNull() != image.isNull(); m_localImage = image; - static_cast(m_videoArea)->local = image; m_videoArea->update(); + m_videoWindow->local = image; m_videoWindow->update(); if (!image.isNull()) { QSize size = image.size().scaled(200, 150, Qt::KeepAspectRatio); m_localPreview->setFixedSize(size); m_localPreview->setPixmap(QPixmap::fromImage(image).scaled(size, Qt::KeepAspectRatio, Qt::SmoothTransformation)); @@ -328,6 +355,7 @@ CallWindow::~CallWindow() { void CallWindow::setPeerName(const QString& name) { m_peerLabel->setText(name); m_avatarLabel->setPixmap(makeAvatar(name, 80)); + m_videoWindow->setWindowTitle(callText("Video — %1").arg(name)); } void CallWindow::setBgpPath(const QString& path) { @@ -401,58 +429,23 @@ void CallWindow::updateButtons() { updateVideoPresentation(); } -void CallWindow::leaveFullScreen() { - if (!isFullScreen()) return; - showNormal(); - if (!m_windowGeometry.isEmpty()) restoreGeometry(m_windowGeometry); -} - -void CallWindow::toggleFullScreen() { - if (m_remoteImage.isNull()) return; - if (isFullScreen()) leaveFullScreen(); - else { m_windowGeometry = saveGeometry(); showFullScreen(); } - m_fullScreenBtn->setText(isFullScreen() ? callText("Exit full screen") : callText("Full screen")); -} - void CallWindow::updateVideoPresentation() { bool receiving = m_state == Active && !m_remoteImage.isNull(); if (receiving != m_receivingVideo) { m_receivingVideo = receiving; - DEBUG_INFO(DEBUG_CATEGORY_CALL, "call video presentation remote=%d local=%d", receiving, !m_localImage.isNull()); - if (receiving) { - m_settingsBtn->setChecked(false); m_windowGeometry = saveGeometry(); - QSize available = screen() ? screen()->availableGeometry().size() : QSize(1280, 900); - resize(qMin(1100, available.width() * 9 / 10), qMin(800, available.height() * 9 / 10)); - } else { - leaveFullScreen(); - if (!m_windowGeometry.isEmpty()) restoreGeometry(m_windowGeometry); - } + DEBUG_INFO(DEBUG_CATEGORY_CALL, "call video separate window remote=%d local=%d", receiving, !m_localImage.isNull()); + if (receiving) m_videoWindow->show(); else m_videoWindow->hide(); } bool active = m_state == Active, settings = active && m_settingsBtn->isChecked(); - m_videoArea->setVisible(receiving); - layout()->setAlignment(m_panel, receiving ? Qt::AlignHCenter | Qt::AlignBottom : Qt::AlignCenter); m_localPreview->setVisible(active && !receiving && !m_localImage.isNull()); - m_avatarLabel->setVisible(!receiving); m_statusLabel->setVisible(!receiving); m_connectionLabel->setVisible(settings); m_bgpPathLabel->setVisible(settings && !m_bgpPathLabel->text().isEmpty()); m_statsLabel->setVisible(settings && !m_statsLabel->text().isEmpty()); m_videoEnabled->setVisible(active); m_settingsBtn->setVisible(active); - m_videoControls->setVisible(settings); m_fullScreenBtn->setVisible(receiving); - m_fullScreenBtn->setText(isFullScreen() ? callText("Exit full screen") : callText("Full screen")); + m_videoControls->setVisible(settings); m_showVideoBtn->setVisible(receiving); m_videoStatus->setVisible(active && !m_videoStatus->text().isEmpty()); update(); } -void CallWindow::keyPressEvent(QKeyEvent* event) { - if (event->key() == Qt::Key_Escape && isFullScreen()) { toggleFullScreen(); event->accept(); return; } - if (event->key() == Qt::Key_F11 && m_receivingVideo) { toggleFullScreen(); event->accept(); return; } - QWidget::keyPressEvent(event); -} - -void CallWindow::mouseDoubleClickEvent(QMouseEvent* event) { - if (event->button() == Qt::LeftButton && m_receivingVideo) { toggleFullScreen(); event->accept(); return; } - QWidget::mouseDoubleClickEvent(event); -} - void CallWindow::applyVideo() { if (!m_videoCall) return; QSize size = m_resolution->currentData().toSize(); diff --git a/tools/chatgui/src/callwindow.h b/tools/chatgui/src/callwindow.h index 4270a5f8..d7187c26 100644 --- a/tools/chatgui/src/callwindow.h +++ b/tools/chatgui/src/callwindow.h @@ -11,6 +11,7 @@ class QTimer; class QCheckBox; class QSpinBox; class CallVideoEngine; +class CallVideoWindow; /* Окно звонка: состояния Outgoing (пытаемся связаться), Ringing (звоним), * Incoming (принять/отклонить), Active (идёт разговор, таймер), Ended (завершён). */ @@ -37,8 +38,6 @@ signals: protected: void closeEvent(QCloseEvent* event) override; - void keyPressEvent(QKeyEvent* event) override; - void mouseDoubleClickEvent(QMouseEvent* event) override; private slots: void onAccept(); @@ -54,17 +53,15 @@ private: void postCallFn(void (*fn)(void*), uint64_t callId); void applyVideo(); void updateVideoPresentation(); - void toggleFullScreen(); - void leaveFullScreen(); CallVideoEngine* m_video; QCheckBox* m_videoEnabled; QComboBox *m_camera, *m_resolution, *m_codec; QSpinBox *m_videoMin, *m_videoMax; QLabel *m_localPreview, *m_videoStatus; - QWidget *m_videoControls, *m_panel, *m_videoArea; - QPushButton *m_settingsBtn, *m_fullScreenBtn; + QWidget *m_videoControls, *m_panel; + CallVideoWindow* m_videoWindow; + QPushButton *m_settingsBtn, *m_showVideoBtn; QImage m_remoteImage, m_localImage; - QByteArray m_windowGeometry; bool m_receivingVideo = false; quint64 m_videoCall = 0; bool m_videoRangeReady = false;