diff --git a/CMakeLists.txt b/CMakeLists.txt index 90c68121b..d951ff2a1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -325,6 +325,8 @@ set(${PROJECT_NAME}_SOURCES src/widget/about/aboutfriendform.h src/widget/categorywidget.cpp src/widget/categorywidget.h + src/widget/chatformheader.cpp + src/widget/chatformheader.h src/widget/circlewidget.cpp src/widget/circlewidget.h src/widget/contentdialog.cpp diff --git a/src/widget/chatformheader.cpp b/src/widget/chatformheader.cpp new file mode 100644 index 000000000..b47674d64 --- /dev/null +++ b/src/widget/chatformheader.cpp @@ -0,0 +1,274 @@ +/* + Copyright © 2017 by The qTox Project Contributors + + This file is part of qTox, a Qt-based graphical interface for Tox. + + qTox is libre software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + qTox is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with qTox. If not, see . +*/ + +#include "chatformheader.h" + +#include "src/widget/maskablepixmapwidget.h" +#include "src/widget/style.h" +#include "src/widget/tool/callconfirmwidget.h" +#include "src/widget/tool/croppinglabel.h" +#include "src/widget/translator.h" + +#include +#include +#include +#include +#include +#include + +static const QSize AVATAR_SIZE{40, 40}; +static const QSize CALL_BUTTONS_SIZE{50, 40}; +static const QSize VOL_MIC_BUTTONS_SIZE{22, 18}; +static const short HEAD_LAYOUT_SPACING = 5; +static const short MIC_BUTTONS_LAYOUT_SPACING = 4; +static const short BUTTONS_LAYOUT_HOR_SPACING = 4; +static const QString Green = QStringLiteral("green"); + +#define STYLE_SHEET(x) Style::getStylesheet(":/ui/" #x "/" #x ".css") +#define SET_STYLESHEET(x) (x)->setStyleSheet(STYLE_SHEET(x)) + +ChatFormHeader::ChatFormHeader(QWidget* parent) + : QWidget(parent) + , mode{Mode::AV} +{ + QHBoxLayout* headLayout = new QHBoxLayout(); + avatar = new MaskablePixmapWidget(this, AVATAR_SIZE, ":/img/avatar_mask.svg"); + + nameLabel = new CroppingLabel(); + nameLabel->setObjectName("nameLabel"); + nameLabel->setMinimumHeight(Style::getFont(Style::Medium).pixelSize()); + nameLabel->setEditable(true); + nameLabel->setTextFormat(Qt::PlainText); + connect(nameLabel, &CroppingLabel::editFinished, this, &ChatFormHeader::onNameChanged); + + headTextLayout = new QVBoxLayout(); + headTextLayout->addStretch(); + headTextLayout->addWidget(nameLabel); + headTextLayout->addStretch(); + + micButton = new QToolButton(); + micButton->setFixedSize(VOL_MIC_BUTTONS_SIZE); + micButton->setAttribute(Qt::WA_LayoutUsesWidgetRect); + SET_STYLESHEET(micButton); + connect(micButton, &QPushButton::clicked, this, &ChatFormHeader::micMuteToggle); + + volButton = new QToolButton(); + volButton->setFixedSize(VOL_MIC_BUTTONS_SIZE); + volButton->setAttribute(Qt::WA_LayoutUsesWidgetRect); + SET_STYLESHEET(volButton); + connect(volButton, &QPushButton::clicked, this, &ChatFormHeader::volMuteToggle); + + callButton = new QPushButton(); + callButton->setFixedSize(CALL_BUTTONS_SIZE); + callButton->setAttribute(Qt::WA_LayoutUsesWidgetRect); + SET_STYLESHEET(callButton); + connect(callButton, &QPushButton::clicked, this, &ChatFormHeader::callTriggered); + + videoButton = new QPushButton(); + videoButton->setFixedSize(CALL_BUTTONS_SIZE); + videoButton->setAttribute(Qt::WA_LayoutUsesWidgetRect); + SET_STYLESHEET(videoButton); + connect(videoButton, &QPushButton::clicked, this, &ChatFormHeader::videoCallTriggered); + + QVBoxLayout* micButtonsLayout = new QVBoxLayout(); + micButtonsLayout->setSpacing(MIC_BUTTONS_LAYOUT_SPACING); + micButtonsLayout->addWidget(micButton, Qt::AlignTop | Qt::AlignRight); + micButtonsLayout->addWidget(volButton, Qt::AlignTop | Qt::AlignRight); + + QGridLayout* buttonsLayout = new QGridLayout(); + buttonsLayout->addLayout(micButtonsLayout, 0, 0, 2, 1, Qt::AlignTop | Qt::AlignRight); + buttonsLayout->addWidget(callButton, 0, 1, 2, 1, Qt::AlignTop); + buttonsLayout->addWidget(videoButton, 0, 2, 2, 1, Qt::AlignTop); + buttonsLayout->setVerticalSpacing(0); + buttonsLayout->setHorizontalSpacing(BUTTONS_LAYOUT_HOR_SPACING); + + headLayout->addWidget(avatar); + headLayout->addSpacing(HEAD_LAYOUT_SPACING); + headLayout->addLayout(headTextLayout); + headLayout->addLayout(buttonsLayout); + + setLayout(headLayout); + + updateCallButtons(false, false, false); + updateMuteMicButton(false, false); + updateMuteVolButton(false, false); + + retranslateUi(); + Translator::registerHandler(std::bind(&ChatFormHeader::retranslateUi, this), this); +} + +ChatFormHeader::~ChatFormHeader() +{ + delete callConfirm; +} + +void ChatFormHeader::setName(const QString& newName) +{ + nameLabel->setText(newName); + // for overlength names + nameLabel->setToolTip(Qt::convertFromPlainText(newName, Qt::WhiteSpaceNormal)); +} + +void ChatFormHeader::setMode(ChatFormHeader::Mode mode) +{ + this->mode = mode; + if (mode == Mode::None) { + callButton->hide(); + videoButton->hide(); + volButton->hide(); + micButton->hide(); + } +} + +void ChatFormHeader::retranslateUi() +{ + const QString callObjectName = callButton->objectName(); + const QString videoObjectName = videoButton->objectName(); + + if (callObjectName == QStringLiteral("green")) { + callButton->setToolTip(tr("Start audio call")); + } else if (callObjectName == QStringLiteral("yellow")) { + callButton->setToolTip(tr("Accept audio call")); + } else if (callObjectName == QStringLiteral("red")) { + callButton->setToolTip(tr("End audio call")); + } else if (callObjectName.isEmpty()) { + callButton->setToolTip(QString{}); + } + + if (videoObjectName == QStringLiteral("green")) { + videoButton->setToolTip(tr("Start video call")); + } else if (videoObjectName == QStringLiteral("yellow")) { + videoButton->setToolTip(tr("Accept video call")); + } else if (videoObjectName == QStringLiteral("red")) { + videoButton->setToolTip(tr("End video call")); + } else if (videoObjectName.isEmpty()) { + videoButton->setToolTip(QString{}); + } +} + +void ChatFormHeader::showOutgoingCall(bool video) +{ + QPushButton* btn = video ? videoButton : callButton; + btn->setObjectName("yellow"); + btn->setStyleSheet(video ? STYLE_SHEET(videoButton) : STYLE_SHEET(callButton)); + btn->setToolTip(video ? tr("Cancel video call") : tr("Cancel audio call")); +} + +void ChatFormHeader::showCallConfirm(bool video) +{ + callConfirm = new CallConfirmWidget(video ? videoButton : callButton); + callConfirm->show(); + CallConfirmWidget* confirmData = callConfirm.data(); + connect(confirmData, &CallConfirmWidget::accepted, this, [this, video]{ + emit callAccepted(video); + }); + connect(confirmData, &CallConfirmWidget::rejected, this, &ChatFormHeader::callRejected); +} + +void ChatFormHeader::removeCallConfirm() +{ + delete callConfirm; +} + +void ChatFormHeader::updateCallButtons(bool online, bool audio, bool video) +{ + callButton->setEnabled(audio && !video); + videoButton->setEnabled(video); + if (audio) { + callButton->setObjectName(!video ? "red" : ""); + callButton->setToolTip(!video ? tr("End audio call") : tr("Can't start audio call")); + + videoButton->setObjectName(video ? "red" : ""); + videoButton->setToolTip(video ? tr("End video call") : tr("Can't start video call")); + } else { + const bool audioAvaliable = online && (mode & Mode::Audio); + callButton->setEnabled(audioAvaliable); + callButton->setObjectName(audioAvaliable ? "green" : ""); + callButton->setToolTip(audioAvaliable ? tr("Start audio call") : tr("Can't start audio call")); + + const bool videoAvaliable = online && (mode & Mode::Video); + videoButton->setEnabled(videoAvaliable); + videoButton->setObjectName(videoAvaliable ? "green" : ""); + videoButton->setToolTip(videoAvaliable ? tr("Start video call") : tr("Can't start video call")); + } + + callButton->setStyleSheet(STYLE_SHEET(callButton)); + videoButton->setStyleSheet(STYLE_SHEET(videoButton)); +} + +void ChatFormHeader::updateMuteMicButton(bool active, bool inputMuted) +{ + micButton->setEnabled(active); + if (micButton->isEnabled()) { + micButton->setObjectName(inputMuted ? "red" : "green"); + micButton->setToolTip(inputMuted ? tr("Unmute microphone") : tr("Mute microphone")); + } else { + micButton->setObjectName(""); + micButton->setToolTip(tr("Microphone can be muted only during a call")); + } + + micButton->setStyleSheet(STYLE_SHEET(micButton)); +} + +void ChatFormHeader::updateMuteVolButton(bool active, bool outputMuted) +{ + volButton->setEnabled(active); + if (volButton->isEnabled()) { + volButton->setObjectName(outputMuted ? "red" : "green"); + volButton->setToolTip(outputMuted ? tr("Unmute call") : tr("Mute call")); + } else { + volButton->setObjectName(""); + volButton->setToolTip(tr("Sound can be disabled only during a call")); + } + + volButton->setStyleSheet(STYLE_SHEET(volButton)); +} + +void ChatFormHeader::setAvatar(const QPixmap &img) +{ + avatar->setPixmap(img); +} + +QSize ChatFormHeader::getAvatarSize() const +{ + return QSize{avatar->width(), avatar->height()}; +} + +void ChatFormHeader::addWidget(QWidget* widget, int stretch, Qt::Alignment alignment) +{ + headTextLayout->addWidget(widget, stretch, alignment); +} + +void ChatFormHeader::addLayout(QLayout* layout) +{ + headTextLayout->addLayout(layout); +} + +void ChatFormHeader::addStretch() +{ + headTextLayout->addStretch(); +} + +void ChatFormHeader::onNameChanged(const QString& name) +{ + if (!name.isEmpty()) { + nameLabel->setText(name); + emit nameChanged(name); + } +} diff --git a/src/widget/chatformheader.h b/src/widget/chatformheader.h new file mode 100644 index 000000000..21ec2f98a --- /dev/null +++ b/src/widget/chatformheader.h @@ -0,0 +1,96 @@ +/* + Copyright © 2017 by The qTox Project Contributors + + This file is part of qTox, a Qt-based graphical interface for Tox. + + qTox is libre software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + qTox is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with qTox. If not, see . +*/ + +#ifndef CHAT_FORM_HEADER +#define CHAT_FORM_HEADER + +#include +#include + +class MaskablePixmapWidget; +class QVBoxLayout; +class CroppingLabel; +class QPushButton; +class QToolButton; +class CallConfirmWidget; + +class ChatFormHeader : public QWidget +{ + Q_OBJECT +public: + enum Mode { + None = 0, + Audio = 1, + Video = 2, + AV = Audio | Video + }; + + ChatFormHeader(QWidget* parent = nullptr); + ~ChatFormHeader() override; + + void setName(const QString& newName); + void setMode(Mode mode); + + void showOutgoingCall(bool video); + void showCallConfirm(bool video); + void removeCallConfirm(); + + void updateCallButtons(bool online, bool audio, bool video = false); + void updateMuteMicButton(bool active, bool inputMuted); + void updateMuteVolButton(bool active, bool outputMuted); + + void setAvatar(const QPixmap& img); + QSize getAvatarSize() const; + + // TODO: Remove + void addWidget(QWidget* widget, int stretch = 0, Qt::Alignment alignment = Qt::Alignment()); + void addLayout(QLayout* layout); + void addStretch(); + +signals: + void callTriggered(); + void videoCallTriggered(); + void micMuteToggle(); + void volMuteToggle(); + + void nameChanged(const QString& name); + + void callAccepted(bool video); + void callRejected(); + +private slots: + void onNameChanged(const QString& name); + void retranslateUi(); + +private: + Mode mode; + MaskablePixmapWidget* avatar; + QVBoxLayout* headTextLayout; + CroppingLabel* nameLabel; + + QPushButton* callButton; + QPushButton* videoButton; + + QToolButton* volButton; + QToolButton* micButton; + + QPointer callConfirm; +}; + +#endif // CHAT_FORM_HEADER diff --git a/src/widget/form/chatform.cpp b/src/widget/form/chatform.cpp index 9d1b26564..739bbb218 100644 --- a/src/widget/form/chatform.cpp +++ b/src/widget/form/chatform.cpp @@ -33,6 +33,7 @@ #include "src/persistence/profile.h" #include "src/persistence/settings.h" #include "src/video/netcamview.h" +#include "src/widget/chatformheader.h" #include "src/widget/form/loadhistorydialog.h" #include "src/widget/maskablepixmapwidget.h" #include "src/widget/style.h" @@ -58,11 +59,6 @@ static const int DELIVER_OFFLINE_MESSAGES_DELAY = 250; static const int SCREENSHOT_GRABBER_OPENING_DELAY = 500; static const int TYPING_NOTIFICATION_DURATION = 3000; -static const QString CALL_BTN_STYLESHEET = QStringLiteral(":/ui/callButton/callButton.css"); -static const QString MIC_BTN_STYLESHEET = QStringLiteral(":/ui/micButton/micButton.css"); -static const QString VIDEO_BTN_STYLESHEET = QStringLiteral(":/ui/videoButton/videoButton.css"); -static const QString VOL_BTN_STYLESHEET = QStringLiteral(":/ui/volButton/volButton.css"); - const QString ChatForm::ACTION_PREFIX = QStringLiteral("/me "); QString statusToString(const Status status) @@ -118,9 +114,9 @@ ChatForm::ChatForm(Friend* chatFriend) , callDuration(new QLabel(this)) , isTyping(false) { - nameLabel->setText(f->getDisplayedName()); + setName(f->getDisplayedName()); - avatar->setPixmap(QPixmap(":/img/contact_dark.svg")); + headWidget->setAvatar(QPixmap(":/img/contact_dark.svg")); statusMessageLabel = new CroppingLabel(); statusMessageLabel->setObjectName("statusLabel"); @@ -129,7 +125,6 @@ ChatForm::ChatForm(Friend* chatFriend) statusMessageLabel->setTextFormat(Qt::PlainText); statusMessageLabel->setContextMenuPolicy(Qt::CustomContextMenu); - callConfirm = nullptr; offlineEngine = new OfflineMsgEngine(f); typingTimer.setSingleShot(true); @@ -139,10 +134,10 @@ ChatForm::ChatForm(Friend* chatFriend) chatWidget->setTypingNotification(ChatMessage::createTypingNotification()); chatWidget->setMinimumHeight(CHAT_WIDGET_MIN_HEIGHT); - headTextLayout->addWidget(statusMessageLabel); - headTextLayout->addStretch(); callDuration = new QLabel(); - headTextLayout->addWidget(callDuration, 1, Qt::AlignCenter); + headWidget->addWidget(statusMessageLabel); + headWidget->addStretch(); + headWidget->addWidget(callDuration, 1, Qt::AlignCenter); callDuration->hide(); loadHistoryAction = menu.addAction(QString(), this, SLOT(onLoadHistory())); @@ -171,10 +166,10 @@ ChatForm::ChatForm(Friend* chatFriend) connect(sendButton, &QPushButton::clicked, this, &ChatForm::onSendTriggered); connect(fileButton, &QPushButton::clicked, this, &ChatForm::onAttachClicked); connect(screenshotButton, &QPushButton::clicked, this, &ChatForm::onScreenshotClicked); - connect(callButton, &QAbstractButton::clicked, this, &ChatForm::onCallTriggered); - connect(videoButton, &QAbstractButton::clicked, this, &ChatForm::onVideoCallTriggered); - connect(micButton, &QAbstractButton::clicked, this, &ChatForm::onMicMuteToggle); - connect(volButton, &QAbstractButton::clicked, this, &ChatForm::onVolMuteToggle); + connect(headWidget, &ChatFormHeader::callTriggered, this, &ChatForm::onCallTriggered); + connect(headWidget, &ChatFormHeader::videoCallTriggered, this, &ChatForm::onVideoCallTriggered); + connect(headWidget, &ChatFormHeader::micMuteToggle, this, &ChatForm::onMicMuteToggle); + connect(headWidget, &ChatFormHeader::volMuteToggle, this, &ChatForm::onVolMuteToggle); connect(msgEdit, &ChatTextEdit::enterPressed, this, &ChatForm::onSendTriggered); connect(msgEdit, &ChatTextEdit::textChanged, this, &ChatForm::onTextEditChanged); @@ -192,9 +187,8 @@ ChatForm::ChatForm(Friend* chatFriend) isTyping = false; }); - connect(nameLabel, &CroppingLabel::editFinished, this, [=](const QString& newName) { - nameLabel->setText(newName); - emit aliasChanged(newName); + connect(headWidget, &ChatFormHeader::nameChanged, this, [=](const QString& newName) { + f->setAlias(newName); }); updateCallButtons(); @@ -335,7 +329,6 @@ void ChatForm::onAvInvite(uint32_t friendId, bool video) return; } - callConfirm = new CallConfirmWidget(video ? videoButton : callButton); QString displayedName = f->getDisplayedName(); insertChatMessage(ChatMessage::createChatInfoMessage(tr("%1 calling").arg(displayedName), ChatMessage::INFO, @@ -351,10 +344,9 @@ void ChatForm::onAvInvite(uint32_t friendId, bool video) Q_ARG(uint32_t, friendId), Q_ARG(bool, video)); onAvStart(friendId, video); } else { - callConfirm->show(); - CallConfirmWidget* confirmData = callConfirm.data(); - connect(confirmData, &CallConfirmWidget::accepted, this, [this, video]{ onAnswerCallTriggered(video); }); - connect(confirmData, &CallConfirmWidget::rejected, this, &ChatForm::onRejectCallTriggered); + headWidget->showCallConfirm(video); + connect(headWidget, &ChatFormHeader::callAccepted, this, &ChatForm::onAnswerCallTriggered); + connect(headWidget, &ChatFormHeader::callRejected, this, &ChatForm::onRejectCallTriggered); auto msg = ChatMessage::createChatInfoMessage(tr("%1 calling").arg(displayedName), ChatMessage::INFO, QDateTime::currentDateTime()); insertChatMessage(msg); @@ -385,8 +377,6 @@ void ChatForm::onAvEnd(uint32_t friendId, bool error) return; } - delete callConfirm; - // Fixes an OS X bug with ending a call while in full screen if (netcam && netcam->isFullScreen()) { netcam->showNormal(); @@ -399,10 +389,7 @@ void ChatForm::onAvEnd(uint32_t friendId, bool error) void ChatForm::showOutgoingCall(bool video) { - QPushButton* btn = video ? videoButton : callButton; - btn->setObjectName("yellow"); - btn->setStyleSheet(Style::getStylesheet(video ? VIDEO_BTN_STYLESHEET : CALL_BTN_STYLESHEET)); - btn->setToolTip(video ? tr("Cancel video call") : tr("Cancel audio call")); + headWidget->showOutgoingCall(video); addSystemInfoMessage(tr("Calling %1").arg(f->getDisplayedName()), ChatMessage::INFO, QDateTime::currentDateTime()); emit outgoingNotification(); @@ -411,7 +398,7 @@ void ChatForm::showOutgoingCall(bool video) void ChatForm::onAnswerCallTriggered(bool video) { - delete callConfirm; + headWidget->removeCallConfirm(); uint32_t friendId = f->getId(); emit acceptCall(friendId); @@ -429,7 +416,7 @@ void ChatForm::onAnswerCallTriggered(bool video) void ChatForm::onRejectCallTriggered() { - delete callConfirm; + headWidget->removeCallConfirm(); emit rejectCall(f->getId()); } @@ -461,32 +448,10 @@ void ChatForm::onVideoCallTriggered() void ChatForm::updateCallButtons() { CoreAV* av = Core::getInstance()->getAv(); - bool audio = av->isCallActive(f); - bool video = av->isCallVideoEnabled(f); - callButton->setEnabled(audio && !video); - videoButton->setEnabled(video); - if (audio) { - videoButton->setObjectName(video ? "red" : ""); - videoButton->setToolTip(video ? tr("End video call") : tr("Can't start video call")); - - callButton->setObjectName(!video ? "red" : ""); - callButton->setToolTip(!video ? tr("End audio call") : tr("Can't start audio call")); - } else { - const Status fs = f->getStatus(); - bool online = fs != Status::Offline; - callButton->setEnabled(online); - videoButton->setEnabled(online); - - QString color = online ? "green" : ""; - callButton->setObjectName(color); - callButton->setToolTip(online ? tr("Start audio call") : tr("Can't start audio call")); - - videoButton->setObjectName(color); - videoButton->setToolTip(online ? tr("Start video call") : tr("Can't start video call")); - } - - callButton->setStyleSheet(Style::getStylesheet(CALL_BTN_STYLESHEET)); - videoButton->setStyleSheet(Style::getStylesheet(VIDEO_BTN_STYLESHEET)); + const bool audio = av->isCallActive(f); + const bool video = av->isCallVideoEnabled(f); + const bool online = f->getStatus() != Status::Offline; + headWidget->updateCallButtons(online, audio, video); updateMuteMicButton(); updateMuteVolButton(); } @@ -584,7 +549,7 @@ void ChatForm::onAvatarChange(uint32_t friendId, const QPixmap& pic) return; } - avatar->setPixmap(pic); + headWidget->setAvatar(pic); } GenericNetCamView* ChatForm::createNetcam() @@ -653,7 +618,7 @@ void ChatForm::onAvatarRemoved(uint32_t friendId) return; } - avatar->setPixmap(QPixmap(":/img/contact_dark.svg")); + headWidget->setAvatar(QPixmap(":/img/contact_dark.svg")); } void ChatForm::clearChatArea(bool notInForm) @@ -849,35 +814,17 @@ void ChatForm::onCopyStatusMessage() void ChatForm::updateMuteMicButton() { const CoreAV* av = Core::getInstance()->getAv(); - - micButton->setEnabled(av->isCallActive(f)); - if (micButton->isEnabled()) { - bool inputMuted = av->isCallInputMuted(f); - micButton->setObjectName(inputMuted ? "red" : "green"); - micButton->setToolTip(inputMuted ? tr("Unmute microphone") : tr("Mute microphone")); - } else { - micButton->setObjectName(""); - micButton->setToolTip(tr("Microphone can be muted only during a call")); - } - - micButton->setStyleSheet(Style::getStylesheet(MIC_BTN_STYLESHEET)); + bool active = av->isCallActive(f); + bool inputMuted = av->isCallInputMuted(f); + headWidget->updateMuteMicButton(active, inputMuted); } void ChatForm::updateMuteVolButton() { const CoreAV* av = Core::getInstance()->getAv(); - - volButton->setEnabled(av->isCallActive(f)); - if (volButton->isEnabled()) { - bool outputMuted = av->isCallOutputMuted(f); - volButton->setObjectName(outputMuted ? "red" : "green"); - volButton->setToolTip(outputMuted ? tr("Unmute call") : tr("Mute call")); - } else { - volButton->setObjectName(""); - volButton->setToolTip(tr("Sound can be disabled only during a call")); - } - - volButton->setStyleSheet(Style::getStylesheet(VOL_BTN_STYLESHEET)); + bool active = av->isCallActive(f); + bool outputMuted = av->isCallOutputMuted(f); + headWidget->updateMuteVolButton(active, outputMuted); } void ChatForm::startCounter() @@ -929,26 +876,15 @@ void ChatForm::setFriendTyping(bool isTyping) void ChatForm::show(ContentLayout* contentLayout) { GenericChatForm::show(contentLayout); - if (callConfirm) { - callConfirm->show(); - } } void ChatForm::showEvent(QShowEvent* event) { - if (callConfirm) { - callConfirm->show(); - } - GenericChatForm::showEvent(event); } void ChatForm::hideEvent(QHideEvent* event) { - if (callConfirm) { - callConfirm->hide(); - } - GenericChatForm::hideEvent(event); } @@ -1006,8 +942,6 @@ void ChatForm::SendMessageStr(QString msg) void ChatForm::retranslateUi() { - QString volObjectName = volButton->objectName(); - QString micObjectName = micButton->objectName(); loadHistoryAction->setText(tr("Load chat history...")); copyStatusAction->setText(tr("Copy")); exportChatAction->setText(tr("Export to file")); diff --git a/src/widget/form/chatform.h b/src/widget/form/chatform.h index cf69f1a4d..c9420c23b 100644 --- a/src/widget/form/chatform.h +++ b/src/widget/form/chatform.h @@ -57,7 +57,6 @@ public: static const QString ACTION_PREFIX; signals: - void aliasChanged(const QString& alias); void incomingNotification(uint32_t friendId); void outgoingNotification(); void rejectCall(uint32_t friendId); @@ -134,7 +133,6 @@ private: QAction* exportChatAction; QHash ftransWidgets; - QPointer callConfirm; bool isTyping; }; diff --git a/src/widget/form/genericchatform.cpp b/src/widget/form/genericchatform.cpp index 9fae3fc7d..33275837d 100644 --- a/src/widget/form/genericchatform.cpp +++ b/src/widget/form/genericchatform.cpp @@ -29,6 +29,7 @@ #include "src/persistence/settings.h" #include "src/persistence/smileypack.h" #include "src/video/genericnetcamview.h" +#include "src/widget/chatformheader.h" #include "src/widget/contentdialog.h" #include "src/widget/contentlayout.h" #include "src/widget/emoticonswidget.h" @@ -57,16 +58,10 @@ #define SET_STYLESHEET(x) (x)->setStyleSheet(Style::getStylesheet(":/ui/" #x "/" #x ".css")) -static const QSize AVATAR_SIZE{40, 40}; -static const QSize CALL_BUTTONS_SIZE{50, 40}; -static const QSize VOL_MIC_BUTTONS_SIZE{22, 18}; static const QSize FILE_FLYOUT_SIZE{24, 24}; static const short FOOT_BUTTONS_SPACING = 2; static const short MESSAGE_EDIT_HEIGHT = 50; static const short MAIN_FOOT_LAYOUT_SPACING = 5; -static const short MIC_BUTTONS_LAYOUT_SPACING = 4; -static const short HEAD_LAYOUT_SPACING = 5; -static const short BUTTONS_LAYOUT_HOR_SPACING = 4; static const QString FONT_STYLE[]{"normal", "italic", "oblique"}; /** @@ -115,22 +110,12 @@ GenericChatForm::GenericChatForm(QWidget* parent) , audioOutputFlag(false) { curRow = 0; - headWidget = new QWidget(); + headWidget = new ChatFormHeader(); - nameLabel = new CroppingLabel(); - nameLabel->setObjectName("nameLabel"); - nameLabel->setMinimumHeight(Style::getFont(Style::Medium).pixelSize()); - nameLabel->setEditable(true); - nameLabel->setTextFormat(Qt::PlainText); + QHBoxLayout* mainFootLayout = new QHBoxLayout(); - avatar = new MaskablePixmapWidget(this, AVATAR_SIZE, ":/img/avatar_mask.svg"); - QHBoxLayout *mainFootLayout = new QHBoxLayout(), *headLayout = new QHBoxLayout(); - - QVBoxLayout *mainLayout = new QVBoxLayout(), *footButtonsSmall = new QVBoxLayout(), - *micButtonsLayout = new QVBoxLayout(); - headTextLayout = new QVBoxLayout(); - - QGridLayout* buttonsLayout = new QGridLayout(); + QVBoxLayout* mainLayout = new QVBoxLayout(); + QVBoxLayout* footButtonsSmall = new QVBoxLayout(); chatWidget = new ChatLog(this); chatWidget->setBusyNotification(ChatMessage::createBusyNotification()); @@ -149,16 +134,6 @@ GenericChatForm::GenericChatForm(QWidget* parent) fileButton = new QPushButton(); screenshotButton = new QPushButton; - callButton = new QPushButton(); - callButton->setFixedSize(CALL_BUTTONS_SIZE); - videoButton = new QPushButton(); - videoButton->setFixedSize(CALL_BUTTONS_SIZE); - - volButton = new QToolButton(); - volButton->setFixedSize(VOL_MIC_BUTTONS_SIZE); - micButton = new QToolButton(); - micButton->setFixedSize(VOL_MIC_BUTTONS_SIZE); - // TODO: Make updateCallButtons (see ChatForm) abstract // and call here to set tooltips. @@ -180,15 +155,6 @@ GenericChatForm::GenericChatForm(QWidget* parent) SET_STYLESHEET(fileButton); SET_STYLESHEET(screenshotButton); SET_STYLESHEET(emoteButton); - SET_STYLESHEET(callButton); - SET_STYLESHEET(videoButton); - SET_STYLESHEET(volButton); - SET_STYLESHEET(micButton); - - callButton->setObjectName("green"); - videoButton->setObjectName("green"); - volButton->setObjectName("grey"); - micButton->setObjectName("grey"); setLayout(mainLayout); @@ -213,37 +179,12 @@ GenericChatForm::GenericChatForm(QWidget* parent) mainFootLayout->addWidget(sendButton); mainFootLayout->setSpacing(0); - headTextLayout->addStretch(); - headTextLayout->addWidget(nameLabel); - headTextLayout->addStretch(); - - micButtonsLayout->setSpacing(MIC_BUTTONS_LAYOUT_SPACING); - micButtonsLayout->addWidget(micButton, Qt::AlignTop | Qt::AlignRight); - micButtonsLayout->addWidget(volButton, Qt::AlignTop | Qt::AlignRight); - - buttonsLayout->addLayout(micButtonsLayout, 0, 0, 2, 1, Qt::AlignTop | Qt::AlignRight); - buttonsLayout->addWidget(callButton, 0, 1, 2, 1, Qt::AlignTop); - buttonsLayout->addWidget(videoButton, 0, 2, 2, 1, Qt::AlignTop); - buttonsLayout->setVerticalSpacing(0); - buttonsLayout->setHorizontalSpacing(BUTTONS_LAYOUT_HOR_SPACING); - - headLayout->addWidget(avatar); - headLayout->addSpacing(HEAD_LAYOUT_SPACING); - headLayout->addLayout(headTextLayout); - headLayout->addLayout(buttonsLayout); - - headWidget->setLayout(headLayout); - // Fix for incorrect layouts on OS X as per // https://bugreports.qt-project.org/browse/QTBUG-14591 sendButton->setAttribute(Qt::WA_LayoutUsesWidgetRect); fileButton->setAttribute(Qt::WA_LayoutUsesWidgetRect); screenshotButton->setAttribute(Qt::WA_LayoutUsesWidgetRect); emoteButton->setAttribute(Qt::WA_LayoutUsesWidgetRect); - micButton->setAttribute(Qt::WA_LayoutUsesWidgetRect); - volButton->setAttribute(Qt::WA_LayoutUsesWidgetRect); - callButton->setAttribute(Qt::WA_LayoutUsesWidgetRect); - videoButton->setAttribute(Qt::WA_LayoutUsesWidgetRect); menu.addActions(chatWidget->actions()); menu.addSeparator(); @@ -324,9 +265,7 @@ QDate GenericChatForm::getLatestDate() const void GenericChatForm::setName(const QString& newName) { - nameLabel->setText(newName); - nameLabel->setToolTip( - Qt::convertFromPlainText(newName, Qt::WhiteSpaceNormal)); // for overlength names + headWidget->setName(newName); } void GenericChatForm::show(ContentLayout* contentLayout) @@ -709,23 +648,6 @@ void GenericChatForm::copyLink() void GenericChatForm::retranslateUi() { - QString callObjectName = callButton->objectName(); - QString videoObjectName = videoButton->objectName(); - - if (callObjectName == QStringLiteral("green")) - callButton->setToolTip(tr("Start audio call")); - else if (callObjectName == QStringLiteral("yellow")) - callButton->setToolTip(tr("Accept audio call")); - else if (callObjectName == QStringLiteral("red")) - callButton->setToolTip(tr("End audio call")); - - if (videoObjectName == QStringLiteral("green")) - videoButton->setToolTip(tr("Start video call")); - else if (videoObjectName == QStringLiteral("yellow")) - videoButton->setToolTip(tr("Accept video call")); - else if (videoObjectName == QStringLiteral("red")) - videoButton->setToolTip(tr("End video call")); - sendButton->setToolTip(tr("Send message")); emoteButton->setToolTip(tr("Smileys")); fileButton->setToolTip(tr("Send file(s)")); diff --git a/src/widget/form/genericchatform.h b/src/widget/form/genericchatform.h index f05acf773..b1b62dcef 100644 --- a/src/widget/form/genericchatform.h +++ b/src/widget/form/genericchatform.h @@ -32,6 +32,7 @@ * - Even a different font is not enough – TODO #1307 ~~zetok */ +class ChatFormHeader; class ChatLog; class ChatTextEdit; class ContentLayout; @@ -56,7 +57,7 @@ class GenericChatForm : public QWidget Q_OBJECT public: explicit GenericChatForm(QWidget* parent = nullptr); - ~GenericChatForm(); + ~GenericChatForm() override; void setName(const QString& newName); virtual void show() final @@ -138,28 +139,19 @@ protected: QMenu menu; - QPushButton* callButton; QPushButton* emoteButton; QPushButton* fileButton; QPushButton* screenshotButton; QPushButton* sendButton; - QPushButton* videoButton; QSplitter* bodySplitter; - QToolButton* volButton; - QToolButton* micButton; - - QVBoxLayout* headTextLayout; - - QWidget* headWidget; + ChatFormHeader* headWidget; ChatLog* chatWidget; ChatTextEdit* msgEdit; - CroppingLabel* nameLabel; FlyoutOverlayWidget* fileFlyout; GenericNetCamView* netcam; - MaskablePixmapWidget* avatar; Widget* parent; }; diff --git a/src/widget/form/groupchatform.cpp b/src/widget/form/groupchatform.cpp index 72061ce57..ab97496f4 100644 --- a/src/widget/form/groupchatform.cpp +++ b/src/widget/form/groupchatform.cpp @@ -26,6 +26,7 @@ #include "src/friendlist.h" #include "src/model/group.h" #include "src/video/groupnetcamview.h" +#include "src/widget/chatformheader.h" #include "src/widget/flowlayout.h" #include "src/widget/form/chatform.h" #include "src/widget/groupwidget.h" @@ -77,46 +78,40 @@ GroupChatForm::GroupChatForm(Group* chatGroup) tabber = new TabCompleter(msgEdit, group); fileButton->setEnabled(false); + ChatFormHeader::Mode mode = ChatFormHeader::Mode::None; if (group->isAvGroupchat()) { - videoButton->setEnabled(false); - videoButton->setObjectName("grey"); - } else { - videoButton->setVisible(false); - callButton->setVisible(false); - volButton->setVisible(false); - micButton->setVisible(false); + mode = ChatFormHeader::Mode::Audio; } - nameLabel->setText(group->getName()); + headWidget->setMode(mode); + setName(group->getName()); nusersLabel->setFont(Style::getFont(Style::Medium)); nusersLabel->setObjectName("statusLabel"); retranslateUi(); - avatar->setPixmap(Style::scaleSvgImage(":/img/group_dark.svg", avatar->width(), avatar->height())); + const QSize& size = headWidget->getAvatarSize(); + headWidget->setAvatar(Style::scaleSvgImage(":/img/group_dark.svg", size.width(), size.height())); msgEdit->setObjectName("group"); namesListLayout = new FlowLayout(0, 5, 0); - headTextLayout->addWidget(nusersLabel); - headTextLayout->addLayout(namesListLayout); - headTextLayout->addStretch(); + headWidget->addWidget(nusersLabel); + headWidget->addLayout(namesListLayout); + headWidget->addStretch(); - nameLabel->setMinimumHeight(12); + //nameLabel->setMinimumHeight(12); nusersLabel->setMinimumHeight(12); connect(sendButton, SIGNAL(clicked()), this, SLOT(onSendTriggered())); connect(msgEdit, SIGNAL(enterPressed()), this, SLOT(onSendTriggered())); connect(msgEdit, &ChatTextEdit::tabPressed, tabber, &TabCompleter::complete); connect(msgEdit, &ChatTextEdit::keyPressed, tabber, &TabCompleter::reset); - connect(callButton, &QPushButton::clicked, this, &GroupChatForm::onCallClicked); - connect(micButton, SIGNAL(clicked()), this, SLOT(onMicMuteToggle())); - connect(volButton, SIGNAL(clicked()), this, SLOT(onVolMuteToggle())); - connect(nameLabel, &CroppingLabel::editFinished, this, [=](const QString& newName) { - if (!newName.isEmpty()) { - nameLabel->setText(newName); - chatGroup->setName(newName); - } + connect(headWidget, &ChatFormHeader::callTriggered, this, &GroupChatForm::onCallClicked); + connect(headWidget, &ChatFormHeader::micMuteToggle, this, &GroupChatForm::onMicMuteToggle); + connect(headWidget, &ChatFormHeader::volMuteToggle, this, &GroupChatForm::onVolMuteToggle); + connect(headWidget, &ChatFormHeader::nameChanged, this, [=](const QString& newName) { + chatGroup->setName(newName); }); connect(group, &Group::userListChanged, this, &GroupChatForm::onUserListChanged); @@ -169,19 +164,9 @@ void GroupChatForm::onUserListChanged() // Enable or disable call button const int peersCount = group->getPeersCount(); - if (peersCount > 1 && group->isAvGroupchat()) { - // don't set button to green if call running - if (!inCall) { - callButton->setEnabled(true); - callButton->setObjectName("green"); - callButton->style()->polish(callButton); - callButton->setToolTip(tr("Start audio call")); - } - } else { - callButton->setEnabled(false); - callButton->setObjectName("grey"); - callButton->style()->polish(callButton); - callButton->setToolTip(""); + const bool online = peersCount > 1; + headWidget->updateCallButtons(online, inCall); + if (!online || !group->isAvGroupchat()) { Core::getInstance()->getAv()->leaveGroupCall(group->getId()); hideNetcam(); } @@ -294,17 +279,10 @@ void GroupChatForm::onMicMuteToggle() { if (audioInputFlag) { CoreAV* av = Core::getInstance()->getAv(); - if (micButton->objectName() == "red") { - av->muteCallInput(group, false); - micButton->setObjectName("green"); - micButton->setToolTip(tr("Mute microphone")); - } else { - av->muteCallInput(group, true); - micButton->setObjectName("red"); - micButton->setToolTip(tr("Unmute microphone")); - } - - Style::repolish(micButton); + const bool oldMuteState = av->isGroupCallInputMuted(group); + const bool newMute = !oldMuteState; + av->muteCallInput(group, newMute); + headWidget->updateMuteMicButton(inCall, newMute); } } @@ -312,53 +290,39 @@ void GroupChatForm::onVolMuteToggle() { if (audioOutputFlag) { CoreAV* av = Core::getInstance()->getAv(); - if (volButton->objectName() == "red") { - av->muteCallOutput(group, false); - volButton->setObjectName("green"); - volButton->setToolTip(tr("Mute call")); - } else { - av->muteCallOutput(group, true); - volButton->setObjectName("red"); - volButton->setToolTip(tr("Unmute call")); - } - - Style::repolish(volButton); + const bool oldMuteState = av->isGroupCallOutputMuted(group); + const bool newMute = !oldMuteState; + av->muteCallOutput(group, newMute); + headWidget->updateMuteVolButton(inCall, newMute); } } void GroupChatForm::onCallClicked() { + CoreAV* av = Core::getInstance()->getAv(); if (!inCall) { - Core::getInstance()->getAv()->joinGroupCall(group->getId()); + av->joinGroupCall(group->getId()); audioInputFlag = true; audioOutputFlag = true; - callButton->setObjectName("red"); - callButton->style()->polish(callButton); - callButton->setToolTip(tr("End audio call")); - micButton->setObjectName("green"); - micButton->style()->polish(micButton); - micButton->setToolTip(tr("Mute microphone")); - volButton->setObjectName("green"); - volButton->style()->polish(volButton); - volButton->setToolTip(tr("Mute call")); inCall = true; showNetcam(); } else { - Core::getInstance()->getAv()->leaveGroupCall(group->getId()); + av->leaveGroupCall(group->getId()); audioInputFlag = false; audioOutputFlag = false; - callButton->setObjectName("green"); - callButton->style()->polish(callButton); - callButton->setToolTip(tr("Start audio call")); - micButton->setObjectName("grey"); - micButton->style()->polish(micButton); - micButton->setToolTip(""); - volButton->setObjectName("grey"); - volButton->style()->polish(volButton); - volButton->setToolTip(""); inCall = false; hideNetcam(); } + + const int peersCount = group->getPeersCount(); + const bool online = peersCount > 1; + headWidget->updateCallButtons(online, inCall); + + const bool inMute = av->isGroupCallInputMuted(group); + headWidget->updateMuteMicButton(inCall, inMute); + + const bool outMute = av->isGroupCallOutputMuted(group); + headWidget->updateMuteVolButton(inCall, outMute); } GenericNetCamView* GroupChatForm::createNetcam() @@ -378,13 +342,7 @@ void GroupChatForm::keyPressEvent(QKeyEvent* ev) { // Push to talk (CTRL+P) if (ev->key() == Qt::Key_P && (ev->modifiers() & Qt::ControlModifier) && inCall) { - CoreAV* av = Core::getInstance()->getAv(); - if (!av->isGroupCallInputMuted(group)) { - av->muteCallInput(group, false); - micButton->setObjectName("green"); - micButton->style()->polish(micButton); - Style::repolish(micButton); - } + onMicMuteToggle(); } if (msgEdit->hasFocus()) @@ -395,13 +353,7 @@ void GroupChatForm::keyReleaseEvent(QKeyEvent* ev) { // Push to talk (CTRL+P) if (ev->key() == Qt::Key_P && (ev->modifiers() & Qt::ControlModifier) && inCall) { - CoreAV* av = Core::getInstance()->getAv(); - if (av->isGroupCallInputMuted(group)) { - av->muteCallInput(group, true); - micButton->setObjectName("red"); - micButton->style()->polish(micButton); - Style::repolish(micButton); - } + onMicMuteToggle(); } if (msgEdit->hasFocus())