mumble-voip_mumble/src/mumble/OverlayUserGroup.cpp

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;
}