381 lines
11 KiB
C++
381 lines
11 KiB
C++
// Copyright 2010-2023 The Mumble Developers. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license
|
|
// that can be found in the LICENSE file at the root of the
|
|
// Mumble source tree or at <https://www.mumble.info/LICENSE>.
|
|
|
|
#include "OverlayUserGroup.h"
|
|
|
|
#include "Channel.h"
|
|
#include "ClientUser.h"
|
|
#include "Database.h"
|
|
#include "MainWindow.h"
|
|
#include "NetworkConfig.h"
|
|
#include "OverlayClient.h"
|
|
#include "OverlayEditor.h"
|
|
#include "OverlayText.h"
|
|
#include "OverlayUser.h"
|
|
#include "ServerHandler.h"
|
|
#include "User.h"
|
|
#include "Utils.h"
|
|
#include "Global.h"
|
|
#include "GlobalShortcut.h"
|
|
|
|
#include <QtGui/QImageReader>
|
|
#include <QtWidgets/QGraphicsSceneContextMenuEvent>
|
|
#include <QtWidgets/QGraphicsSceneWheelEvent>
|
|
#include <QtWidgets/QInputDialog>
|
|
|
|
template< typename T > QRectF OverlayGroup::boundingRect() const {
|
|
QRectF qr;
|
|
foreach (const QGraphicsItem *item, childItems())
|
|
if (item->isVisible() && (item->type() == T::Type))
|
|
qr |= item->boundingRect().translated(item->pos());
|
|
|
|
return qr;
|
|
}
|
|
|
|
OverlayUserGroup::OverlayUserGroup(OverlaySettings *osptr)
|
|
: OverlayGroup(), os(osptr), qgeiHandle(nullptr), bShowExamples(false) {
|
|
}
|
|
|
|
OverlayUserGroup::~OverlayUserGroup() {
|
|
reset();
|
|
}
|
|
|
|
void OverlayUserGroup::reset() {
|
|
foreach (OverlayUser *ou, qlExampleUsers)
|
|
delete ou;
|
|
qlExampleUsers.clear();
|
|
|
|
foreach (OverlayUser *ou, qmUsers)
|
|
delete ou;
|
|
qmUsers.clear();
|
|
|
|
delete qgeiHandle;
|
|
qgeiHandle = nullptr;
|
|
}
|
|
|
|
int OverlayUserGroup::type() const {
|
|
return Type;
|
|
}
|
|
|
|
void OverlayUserGroup::contextMenuEvent(QGraphicsSceneContextMenuEvent *e) {
|
|
e->accept();
|
|
|
|
#ifdef Q_OS_MAC
|
|
bool embed = Global::get().ocIntercept;
|
|
QMenu qm(embed ? nullptr : e->widget());
|
|
if (embed) {
|
|
QGraphicsScene *scene = Global::get().ocIntercept->qgv.scene();
|
|
scene->addWidget(&qm);
|
|
}
|
|
#else
|
|
QMenu qm(Global::get().ocIntercept ? Global::get().mw : e->widget());
|
|
#endif
|
|
|
|
QMenu *qmShow = qm.addMenu(OverlayClient::tr("Filter"));
|
|
|
|
QAction *qaShowTalking = qmShow->addAction(OverlayClient::tr("Only talking"));
|
|
qaShowTalking->setCheckable(true);
|
|
if (os->osShow == OverlaySettings::Talking)
|
|
qaShowTalking->setChecked(true);
|
|
|
|
QAction *qaShowActive = qmShow->addAction(OverlayClient::tr("Talking and recently active"));
|
|
qaShowActive->setCheckable(true);
|
|
if (os->osShow == OverlaySettings::Active)
|
|
qaShowActive->setChecked(true);
|
|
|
|
QAction *qaShowHome = qmShow->addAction(OverlayClient::tr("All in current channel"));
|
|
qaShowHome->setCheckable(true);
|
|
if (os->osShow == OverlaySettings::HomeChannel)
|
|
qaShowHome->setChecked(true);
|
|
|
|
QAction *qaShowLinked = qmShow->addAction(OverlayClient::tr("All in linked channels"));
|
|
qaShowLinked->setCheckable(true);
|
|
if (os->osShow == OverlaySettings::LinkedChannels)
|
|
qaShowLinked->setChecked(true);
|
|
|
|
qmShow->addSeparator();
|
|
|
|
QAction *qaShowSelf = qmShow->addAction(OverlayClient::tr("Always show yourself"));
|
|
qaShowSelf->setCheckable(true);
|
|
qaShowSelf->setEnabled(os->osShow == OverlaySettings::Talking || os->osShow == OverlaySettings::Active);
|
|
if (os->bAlwaysSelf)
|
|
qaShowSelf->setChecked(true);
|
|
|
|
qmShow->addSeparator();
|
|
|
|
QAction *qaConfigureRecentlyActiveTime =
|
|
qmShow->addAction(OverlayClient::tr("Configure recently active time (%1 seconds)...").arg(os->uiActiveTime));
|
|
qaConfigureRecentlyActiveTime->setEnabled(os->osShow == OverlaySettings::Active);
|
|
|
|
QMenu *qmColumns = qm.addMenu(OverlayClient::tr("Columns"));
|
|
QAction *qaColumns[6];
|
|
for (unsigned int i = 1; i <= 5; ++i) {
|
|
qaColumns[i] = qmColumns->addAction(QString::number(i));
|
|
qaColumns[i]->setCheckable(true);
|
|
qaColumns[i]->setChecked(i == os->uiColumns);
|
|
}
|
|
|
|
QMenu *qmSort = qm.addMenu(OverlayClient::tr("Sort"));
|
|
|
|
QAction *qaSortAlphabetically = qmSort->addAction(OverlayClient::tr("Alphabetically"));
|
|
qaSortAlphabetically->setCheckable(true);
|
|
if (os->osSort == OverlaySettings::Alphabetical)
|
|
qaSortAlphabetically->setChecked(true);
|
|
|
|
QAction *qaSortLastStateChange = qmSort->addAction(OverlayClient::tr("Last state change"));
|
|
qaSortLastStateChange->setCheckable(true);
|
|
if (os->osSort == OverlaySettings::LastStateChange)
|
|
qaSortLastStateChange->setChecked(true);
|
|
|
|
QAction *qaEdit = qm.addAction(OverlayClient::tr("Edit..."));
|
|
QAction *qaZoom = qm.addAction(OverlayClient::tr("Reset Zoom"));
|
|
|
|
QAction *act = qm.exec(e->screenPos());
|
|
|
|
if (!act)
|
|
return;
|
|
|
|
if (act == qaEdit) {
|
|
if (Global::get().ocIntercept) {
|
|
QMetaObject::invokeMethod(Global::get().ocIntercept, "openEditor", Qt::QueuedConnection);
|
|
} else {
|
|
OverlayEditor oe(qApp->activeModalWidget(), nullptr, os);
|
|
connect(&oe, SIGNAL(applySettings()), this, SLOT(updateLayout()));
|
|
oe.exec();
|
|
}
|
|
} else if (act == qaZoom) {
|
|
os->fZoom = 1.0f;
|
|
updateLayout();
|
|
} else if (act == qaShowTalking) {
|
|
os->osShow = OverlaySettings::Talking;
|
|
updateUsers();
|
|
} else if (act == qaShowActive) {
|
|
os->osShow = OverlaySettings::Active;
|
|
updateUsers();
|
|
} else if (act == qaShowHome) {
|
|
os->osShow = OverlaySettings::HomeChannel;
|
|
updateUsers();
|
|
} else if (act == qaShowLinked) {
|
|
os->osShow = OverlaySettings::LinkedChannels;
|
|
updateUsers();
|
|
} else if (act == qaShowSelf) {
|
|
os->bAlwaysSelf = !os->bAlwaysSelf;
|
|
updateUsers();
|
|
} else if (act == qaConfigureRecentlyActiveTime) {
|
|
// FIXME: This might not be the best place to configure this setting, but currently
|
|
// there's not really a suitable place to put this. In the future an additional tab
|
|
// might be added for some advanced overlay options, which could then include this
|
|
// setting.
|
|
bool ok;
|
|
int newValue = QInputDialog::getInt(qm.parentWidget(), OverlayClient::tr("Configure recently active time"),
|
|
OverlayClient::tr("Amount of seconds users remain active after talking:"),
|
|
os->uiActiveTime, 1, 2147483647, 1, &ok);
|
|
if (ok) {
|
|
os->uiActiveTime = newValue;
|
|
}
|
|
updateUsers();
|
|
} else if (act == qaSortAlphabetically) {
|
|
os->osSort = OverlaySettings::Alphabetical;
|
|
updateUsers();
|
|
} else if (act == qaSortLastStateChange) {
|
|
os->osSort = OverlaySettings::LastStateChange;
|
|
updateUsers();
|
|
} else {
|
|
for (unsigned int i = 1; i <= 5; ++i) {
|
|
if (act == qaColumns[i]) {
|
|
os->uiColumns = i;
|
|
updateLayout();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void OverlayUserGroup::wheelEvent(QGraphicsSceneWheelEvent *e) {
|
|
e->accept();
|
|
|
|
qreal scaleFactor = 0.875f;
|
|
|
|
if (e->delta() > 0)
|
|
scaleFactor = 1.0f / 0.875f;
|
|
|
|
if ((scaleFactor < 1.0f) && (os->fZoom <= (1.0f / 4.0f)))
|
|
return;
|
|
else if ((scaleFactor > 1.0f) && (os->fZoom >= 4.0f))
|
|
return;
|
|
|
|
os->fZoom *= scaleFactor;
|
|
|
|
updateLayout();
|
|
}
|
|
|
|
bool OverlayUserGroup::sceneEventFilter(QGraphicsItem *watched, QEvent *e) {
|
|
switch (e->type()) {
|
|
case QEvent::GraphicsSceneMouseMove:
|
|
case QEvent::GraphicsSceneMouseRelease:
|
|
QMetaObject::invokeMethod(this, "moveUsers", Qt::QueuedConnection);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return OverlayGroup::sceneEventFilter(watched, e);
|
|
}
|
|
|
|
void OverlayUserGroup::moveUsers() {
|
|
if (!qgeiHandle)
|
|
return;
|
|
|
|
const QRectF &sr = scene()->sceneRect();
|
|
const QPointF &p = qgeiHandle->pos();
|
|
|
|
os->fX = static_cast< float >(qBound(0.0, p.x() / sr.width(), 1.0));
|
|
os->fY = static_cast< float >(qBound(0.0, p.y() / sr.height(), 1.0));
|
|
|
|
qgeiHandle->setPos(os->fX * sr.width(), os->fY * sr.height());
|
|
updateUsers();
|
|
}
|
|
|
|
void OverlayUserGroup::updateLayout() {
|
|
prepareGeometryChange();
|
|
reset();
|
|
updateUsers();
|
|
}
|
|
|
|
void OverlayUserGroup::updateUsers() {
|
|
const QRectF &sr = scene()->sceneRect();
|
|
|
|
unsigned int uiHeight = static_cast< unsigned int >(sr.height() + 0.5f);
|
|
|
|
QList< QGraphicsItem * > items;
|
|
foreach (QGraphicsItem *qgi, childItems())
|
|
items << qgi;
|
|
|
|
QList< OverlayUser * > users;
|
|
if (bShowExamples) {
|
|
if (qlExampleUsers.isEmpty()) {
|
|
qlExampleUsers << new OverlayUser(Settings::Passive, uiHeight, os);
|
|
qlExampleUsers << new OverlayUser(Settings::Talking, uiHeight, os);
|
|
qlExampleUsers << new OverlayUser(Settings::MutedTalking, uiHeight, os);
|
|
qlExampleUsers << new OverlayUser(Settings::Whispering, uiHeight, os);
|
|
qlExampleUsers << new OverlayUser(Settings::Shouting, uiHeight, os);
|
|
}
|
|
|
|
users = qlExampleUsers;
|
|
foreach (OverlayUser *ou, users)
|
|
items.removeAll(ou);
|
|
|
|
if (!qgeiHandle) {
|
|
qgeiHandle = new QGraphicsEllipseItem(QRectF(-4.0f, -4.0f, 8.0f, 8.0f));
|
|
qgeiHandle->setPen(QPen(Qt::darkRed, 0.0f));
|
|
qgeiHandle->setBrush(Qt::red);
|
|
qgeiHandle->setZValue(0.5f);
|
|
qgeiHandle->setFlag(QGraphicsItem::ItemIsMovable);
|
|
qgeiHandle->setFlag(QGraphicsItem::ItemIsSelectable);
|
|
qgeiHandle->setPos(sr.width() * os->fX, sr.height() * os->fY);
|
|
scene()->addItem(qgeiHandle);
|
|
qgeiHandle->show();
|
|
qgeiHandle->installSceneEventFilter(this);
|
|
}
|
|
} else {
|
|
delete qgeiHandle;
|
|
qgeiHandle = nullptr;
|
|
}
|
|
|
|
ClientUser *self = ClientUser::get(Global::get().uiSession);
|
|
if (self) {
|
|
QList< ClientUser * > showusers;
|
|
Channel *home = ClientUser::get(Global::get().uiSession)->cChannel;
|
|
|
|
switch (os->osShow) {
|
|
case OverlaySettings::LinkedChannels:
|
|
foreach (Channel *c, home->allLinks())
|
|
foreach (User *p, c->qlUsers)
|
|
showusers << static_cast< ClientUser * >(p);
|
|
foreach (ClientUser *cu, ClientUser::getTalking())
|
|
if (!showusers.contains(cu))
|
|
showusers << cu;
|
|
break;
|
|
case OverlaySettings::HomeChannel:
|
|
foreach (User *p, home->qlUsers)
|
|
showusers << static_cast< ClientUser * >(p);
|
|
foreach (ClientUser *cu, ClientUser::getTalking())
|
|
if (!showusers.contains(cu))
|
|
showusers << cu;
|
|
break;
|
|
case OverlaySettings::Active:
|
|
showusers = ClientUser::getActive();
|
|
if (os->bAlwaysSelf && !showusers.contains(self))
|
|
showusers << self;
|
|
break;
|
|
default:
|
|
showusers = ClientUser::getTalking();
|
|
if (os->bAlwaysSelf && (self->tsState == Settings::Passive))
|
|
showusers << self;
|
|
break;
|
|
}
|
|
|
|
ClientUser::sortUsersOverlay(showusers);
|
|
|
|
foreach (ClientUser *cu, showusers) {
|
|
OverlayUser *ou = qmUsers.value(cu);
|
|
if (!ou) {
|
|
ou = new OverlayUser(cu, uiHeight, os);
|
|
connect(cu, SIGNAL(destroyed(QObject *)), this, SLOT(userDestroyed(QObject *)));
|
|
qmUsers.insert(cu, ou);
|
|
ou->hide();
|
|
} else {
|
|
items.removeAll(ou);
|
|
}
|
|
users << ou;
|
|
}
|
|
}
|
|
|
|
foreach (QGraphicsItem *qgi, items) {
|
|
scene()->removeItem(qgi);
|
|
qgi->hide();
|
|
}
|
|
|
|
QRectF childrenBounds = os->qrfAvatar | os->qrfChannel | os->qrfMutedDeafened | os->qrfUserName;
|
|
|
|
int pad = os->bBox ? static_cast< int >(uiHeight * os->fZoom * (os->fBoxPad + os->fBoxPenWidth) + 0.5f) : 0;
|
|
int width = static_cast< int >(childrenBounds.width() * uiHeight * os->fZoom + 0.5f) + 2 * pad;
|
|
int height = static_cast< int >(childrenBounds.height() * uiHeight * os->fZoom + 0.5f) + 2 * pad;
|
|
|
|
int xOffset = -static_cast< int >(childrenBounds.left() * uiHeight * os->fZoom + 0.5f) + pad;
|
|
int yOffset = -static_cast< int >(childrenBounds.top() * uiHeight * os->fZoom + 0.5f) + pad;
|
|
|
|
int yPos = 0;
|
|
int xPos = 0;
|
|
|
|
foreach (OverlayUser *ou, users) {
|
|
if (!ou->parentItem())
|
|
ou->setParentItem(this);
|
|
|
|
ou->setPos(xPos * (width + 4) + xOffset, yPos * (height + 4) + yOffset);
|
|
ou->updateUser();
|
|
ou->show();
|
|
|
|
if (static_cast< unsigned int >(xPos) >= (os->uiColumns - 1)) {
|
|
xPos = 0;
|
|
++yPos;
|
|
} else {
|
|
++xPos;
|
|
}
|
|
}
|
|
|
|
QRectF br = boundingRect< OverlayUser >();
|
|
|
|
int basex = qBound< int >(0, static_cast< int >(sr.width() * os->fX + 0.5f),
|
|
static_cast< int >(sr.width() - br.width() + 0.5f));
|
|
int basey = qBound< int >(0, static_cast< int >(sr.height() * os->fY + 0.5f),
|
|
static_cast< int >(sr.height() - br.height() + 0.5f));
|
|
|
|
setPos(basex, basey);
|
|
}
|
|
|
|
void OverlayUserGroup::userDestroyed(QObject *obj) {
|
|
OverlayUser *ou = qmUsers.take(obj);
|
|
delete ou;
|
|
}
|