mumble-voip_mumble/src/mumble/LCD.cpp

431 lines
10 KiB
C++

// Copyright 2008-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 "LCD.h"
#include "Channel.h"
#include "ClientUser.h"
#include "ServerHandler.h"
#include "Utils.h"
#include "Global.h"
#include <QtGui/QPainter>
const QString LCDConfig::name = QLatin1String("LCDConfig");
QList< LCDEngineNew > *LCDEngineRegistrar::qlInitializers;
LCDEngineRegistrar::LCDEngineRegistrar(LCDEngineNew cons) {
if (!qlInitializers)
qlInitializers = new QList< LCDEngineNew >();
n = cons;
qlInitializers->append(n);
}
LCDEngineRegistrar::~LCDEngineRegistrar() {
qlInitializers->removeAll(n);
if (qlInitializers->isEmpty()) {
delete qlInitializers;
qlInitializers = nullptr;
}
}
static ConfigWidget *LCDConfigDialogNew(Settings &st) {
return new LCDConfig(st);
}
class LCDDeviceManager : public DeferInit {
protected:
ConfigRegistrar *crLCD;
public:
QList< LCDEngine * > qlEngines;
QList< LCDDevice * > qlDevices;
void initialize();
void destroy();
};
void LCDDeviceManager::initialize() {
if (LCDEngineRegistrar::qlInitializers) {
foreach (LCDEngineNew engine, *LCDEngineRegistrar::qlInitializers) {
LCDEngine *e = engine();
qlEngines.append(e);
foreach (LCDDevice *d, e->devices()) { qlDevices << d; }
}
}
if (qlDevices.count() > 0) {
crLCD = new ConfigRegistrar(5900, LCDConfigDialogNew);
} else {
crLCD = nullptr;
}
}
void LCDDeviceManager::destroy() {
qlDevices.clear();
foreach (LCDEngine *e, qlEngines) { delete e; }
delete crLCD;
}
static LCDDeviceManager devmgr;
/* --- */
LCDConfig::LCDConfig(Settings &st) : ConfigWidget(st) {
setupUi(this);
QTreeWidgetItem *qtwi;
foreach (LCDDevice *d, devmgr.qlDevices) {
qtwi = new QTreeWidgetItem(qtwDevices);
qtwi->setFlags(Qt::ItemIsEnabled | Qt::ItemIsUserCheckable);
qtwi->setText(0, d->name());
qtwi->setToolTip(0, d->name().toHtmlEscaped());
QSize lcdsize = d->size();
QString qsSize = QString::fromLatin1("%1x%2").arg(lcdsize.width()).arg(lcdsize.height());
qtwi->setText(1, qsSize);
qtwi->setToolTip(1, qsSize);
qtwi->setCheckState(2, Qt::Unchecked);
qtwi->setToolTip(2, tr("Enable this device"));
}
}
QString LCDConfig::title() const {
return tr("LCD");
}
const QString &LCDConfig::getName() const {
return LCDConfig::name;
}
QIcon LCDConfig::icon() const {
return QIcon(QLatin1String("skin:config_lcd.png"));
}
void LCDConfig::load(const Settings &r) {
QList< QTreeWidgetItem * > qlItems = qtwDevices->findItems(QString(), Qt::MatchContains);
foreach (QTreeWidgetItem *qtwi, qlItems) {
QString qsName = qtwi->text(0);
bool enabled = r.qmLCDDevices.contains(qsName) ? r.qmLCDDevices.value(qsName) : true;
qtwi->setCheckState(2, enabled ? Qt::Checked : Qt::Unchecked);
}
loadSlider(qsMinColWidth, r.iLCDUserViewMinColWidth);
loadSlider(qsSplitterWidth, r.iLCDUserViewSplitterWidth);
}
void LCDConfig::save() const {
QList< QTreeWidgetItem * > qlItems = qtwDevices->findItems(QString(), Qt::MatchContains);
foreach (QTreeWidgetItem *qtwi, qlItems) {
QString qsName = qtwi->text(0);
s.qmLCDDevices.insert(qsName, qtwi->checkState(2) == Qt::Checked);
}
s.iLCDUserViewMinColWidth = qsMinColWidth->value();
s.iLCDUserViewSplitterWidth = qsSplitterWidth->value();
}
void LCDConfig::accept() const {
foreach (LCDDevice *d, devmgr.qlDevices) {
bool enabled = s.qmLCDDevices.value(d->name());
d->setEnabled(enabled);
}
Global::get().lcd->updateUserView();
}
void LCDConfig::on_qsMinColWidth_valueChanged(int v) {
qlMinColWidth->setText(QString::number(v));
}
void LCDConfig::on_qsSplitterWidth_valueChanged(int v) {
qlSplitterWidth->setText(QString::number(v));
}
/* --- */
LCD::LCD() : QObject() {
#ifdef Q_OS_MAC
qfNormal.setStyleStrategy(QFont::NoAntialias);
qfNormal.setKerning(false);
qfNormal.setPointSize(10);
qfNormal.setFixedPitch(true);
qfNormal.setFamily(QString::fromLatin1("Andale Mono"));
#else
qfNormal = QFont(QString::fromLatin1("Arial"), 7);
#endif
qfItalic = qfNormal;
qfItalic.setItalic(true);
qfBold = qfNormal;
qfBold.setWeight(QFont::Black);
qfItalicBold = qfBold;
qfItalic.setItalic(true);
QFontMetrics qfm(qfNormal);
iFontHeight = 10;
initBuffers();
iFrameIndex = 0;
qtTimer = new QTimer(this);
connect(qtTimer, SIGNAL(timeout()), this, SLOT(tick()));
foreach (LCDDevice *d, devmgr.qlDevices) {
bool enabled =
Global::get().s.qmLCDDevices.contains(d->name()) ? Global::get().s.qmLCDDevices.value(d->name()) : true;
d->setEnabled(enabled);
}
qiLogo = QIcon(QLatin1String("skin:mumble.svg")).pixmap(48, 48).toImage().convertToFormat(QImage::Format_MonoLSB);
#if QT_VERSION >= 0x050600 && QT_VERSION <= 0x050601
// Don't invert the logo image when using Qt 5.6.
// See mumble-voip/mumble#2429
#else
qiLogo.invertPixels();
#endif
updateUserView();
}
void LCD::tick() {
iFrameIndex++;
updateUserView();
}
void LCD::initBuffers() {
foreach (LCDDevice *d, devmgr.qlDevices) {
QSize size = d->size();
if (!qhImageBuffers.contains(size)) {
size_t buflen = static_cast< std::size_t >(size.width() * size.height()) / 8;
qhImageBuffers[size] = new unsigned char[buflen];
qhImages[size] = new QImage(qhImageBuffers[size], size.width(), size.height(), QImage::Format_MonoLSB);
}
}
}
void LCD::destroyBuffers() {
foreach (QImage *img, qhImages)
delete img;
qhImages.clear();
foreach (unsigned char *buf, qhImageBuffers)
delete[] buf;
qhImageBuffers.clear();
}
struct ListEntry {
QString qsString;
bool bBold;
bool bItalic;
ListEntry(const QString &qs, bool bB, bool bI) : qsString(qs), bBold(bB), bItalic(bI){};
};
static bool entriesSort(const ListEntry &a, const ListEntry &b) {
return a.qsString < b.qsString;
}
void LCD::updateUserView() {
if (qhImages.count() == 0)
return;
QStringList qslTalking;
User *me = Global::get().uiSession ? ClientUser::get(Global::get().uiSession) : nullptr;
Channel *home = me ? me->cChannel : nullptr;
bool alert = false;
foreach (const QSize &size, qhImages.keys()) {
QImage *img = qhImages.value(size);
QPainter painter(img);
painter.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing, false);
#if QT_VERSION >= 0x050600 && QT_VERSION <= 0x050601
// Use Qt::white instead of Qt::color1 on Qt 5.6.
// See mumble-voip/mumble#2429
painter.setPen(Qt::white);
#else
painter.setPen(Qt::color1);
#endif
painter.setFont(qfNormal);
img->fill(Qt::color0);
if (!me) {
qmNew.clear();
qmOld.clear();
qmSpeaking.clear();
qmNameCache.clear();
painter.drawImage(0, 0, qiLogo);
painter.drawText(60, 20, tr("Not connected"));
continue;
}
foreach (User *p, me->cChannel->qlUsers) {
if (!qmNew.contains(p->uiSession)) {
qmNew.insert(p->uiSession, Timer());
qmNameCache.insert(p->uiSession, p->qsName);
qmOld.remove(p->uiSession);
}
}
foreach (unsigned int session, qmNew.keys()) {
User *p = ClientUser::get(session);
if (!p || (p->cChannel != me->cChannel)) {
qmNew.remove(session);
qmOld.insert(session, Timer());
}
}
QMap< unsigned int, Timer > old;
foreach (unsigned int session, qmOld.keys()) {
Timer t = qmOld.value(session);
if (t.elapsed() > 3000000) {
qmNameCache.remove(session);
} else {
old.insert(session, qmOld.value(session));
}
}
qmOld = old;
QList< struct ListEntry > entries;
entries << ListEntry(
QString::fromLatin1("[%1:%2]").arg(me->cChannel->qsName).arg(me->cChannel->qlUsers.count()), false, false);
bool hasnew = false;
QMap< unsigned int, Timer > speaking;
foreach (Channel *c, home->allLinks()) {
foreach (User *p, c->qlUsers) {
ClientUser *u = static_cast< ClientUser * >(p);
bool bTalk = (u->tsState != Settings::Passive);
if (bTalk) {
speaking.insert(p->uiSession, Timer());
} else if (qmSpeaking.contains(p->uiSession)) {
Timer t = qmSpeaking.value(p->uiSession);
if (t.elapsed() > 1000000)
qmSpeaking.remove(p->uiSession);
else {
speaking.insert(p->uiSession, t);
bTalk = true;
}
}
if (bTalk) {
alert = true;
entries << ListEntry(p->qsName, true, (p->cChannel != me->cChannel));
} else if (c == me->cChannel) {
if (qmNew.value(p->uiSession).elapsed() < 3000000) {
entries << ListEntry(QLatin1String("+") + p->qsName, false, false);
hasnew = true;
}
}
}
}
qmSpeaking = speaking;
foreach (unsigned int session, qmOld.keys()) {
entries << ListEntry(QLatin1String("-") + qmNameCache.value(session), false, false);
}
if (!qmOld.isEmpty() || hasnew || !qmSpeaking.isEmpty()) {
qtTimer->start(500);
} else {
qtTimer->stop();
}
std::sort(++entries.begin(), entries.end(), entriesSort);
const int iWidth = size.width();
const int iHeight = size.height();
const int iUsersPerColumn = iHeight / iFontHeight;
const int iSplitterWidth = Global::get().s.iLCDUserViewSplitterWidth;
const int iUserColumns = (entries.count() + iUsersPerColumn - 1) / iUsersPerColumn;
int iColumns = iUserColumns;
int iColumnWidth = 1;
while (iColumns >= 1) {
iColumnWidth = (iWidth - (iColumns - 1) * iSplitterWidth) / iColumns;
if (iColumnWidth >= Global::get().s.iLCDUserViewMinColWidth)
break;
--iColumns;
}
int row = 0, col = 0;
foreach (const ListEntry &le, entries) {
if (row >= iUsersPerColumn) {
row = 0;
++col;
}
if (col > iColumns)
break;
if (!le.qsString.isEmpty()) {
if (le.bBold && le.bItalic)
painter.setFont(qfItalicBold);
else if (le.bBold)
painter.setFont(qfBold);
else if (le.bItalic)
painter.setFont(qfItalic);
else
painter.setFont(qfNormal);
painter.drawText(
QRect(col * (iColumnWidth + iSplitterWidth), row * iFontHeight, iColumnWidth, iFontHeight + 2),
Qt::AlignLeft, le.qsString);
}
++row;
}
}
foreach (LCDDevice *d, devmgr.qlDevices) {
QImage *img = qhImages[d->size()];
if (!img)
continue;
d->blitImage(img, alert);
}
}
LCD::~LCD() {
destroyBuffers();
}
bool LCD::hasDevices() {
return (!devmgr.qlDevices.isEmpty());
}
/* --- */
LCDEngine::LCDEngine() : QObject() {
}
LCDEngine::~LCDEngine() {
foreach (LCDDevice *lcd, qlDevices)
delete lcd;
}
LCDDevice::LCDDevice() {
}
LCDDevice::~LCDDevice() {
}
/* --- */
uint qHash(const QSize &size) {
return static_cast< uint >((size.width() & 0xffff) << 16) | (size.height() & 0xffff);
}