|
|
|
|
@ -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<CallVideoCanvas*>(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<CallVideoCanvas*>(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(); |
|
|
|
|
|