Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/mumble/MainWindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2127,6 +2127,7 @@ void MainWindow::on_qaUserTextureReset_triggered() {
QMessageBox::No);
if (ret == QMessageBox::Yes) {
Global::get().sh->setUserTexture(session, QByteArray());
qtvUsers->triggerUpdate();
}
}

Expand Down Expand Up @@ -4108,6 +4109,7 @@ void MainWindow::changeServerTexture() {

void MainWindow::removeServerTexture() {
Global::get().sh->setUserTexture(Global::get().uiSession, QByteArray());
qtvUsers->triggerUpdate();
}

void MainWindow::selfRegister() {
Expand Down
216 changes: 130 additions & 86 deletions src/mumble/UserModel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -235,13 +235,17 @@ QString ModelItem::hash() const {
}

UserModel::UserModel(QObject *p) : QAbstractItemModel(p) {
qiTalkingOff = QIcon(QLatin1String("skin:talking_off.svg"));
qiTalkingOn = QIcon(QLatin1String("skin:talking_on.svg"));
qiTalkingMuted = QIcon(QLatin1String("skin:talking_muted.svg"));
qiTalkingShout = QIcon(QLatin1String("skin:talking_alt.svg"));
qiTalkingWhisper = QIcon(QLatin1String("skin:talking_whisper.svg"));
qiPrioritySpeaker = QIcon(QLatin1String("skin:priority_speaker.svg"));
qiRecording = QIcon(QLatin1String("skin:actions/media-record.svg"));
qiTalkingOff = QIcon(QLatin1String("skin:talking_off.svg"));
qiTalkingOn = QIcon(QLatin1String("skin:talking_on.svg"));
qiTalkingMuted = QIcon(QLatin1String("skin:talking_muted.svg"));
qiTalkingShout = QIcon(QLatin1String("skin:talking_alt.svg"));
qiTalkingWhisper = QIcon(QLatin1String("skin:talking_whisper.svg"));
qiPrioritySpeaker = QIcon(QLatin1String("skin:priority_speaker.svg"));
qiCustomTalkingOn = QIcon(QLatin1String("skin:custom_talking_on.svg"));
qiCustomTalkingMuted = QIcon(QLatin1String("skin:custom_talking_muted.svg"));
qiCustomTalkingShout = QIcon(QLatin1String("skin:custom_talking_alt.svg"));
qiCustomTalkingWhisper = QIcon(QLatin1String("skin:custom_talking_whisper.svg"));
qiRecording = QIcon(QLatin1String("skin:actions/media-record.svg"));
qiMutedPushToMute.addFile(QLatin1String("skin:muted_pushtomute.svg"));
qiMutedSelf = QIcon(QLatin1String("skin:muted_self.svg"));
qiMutedServer = QIcon(QLatin1String("skin:muted_server.svg"));
Expand Down Expand Up @@ -279,6 +283,24 @@ UserModel::~UserModel() {
delete miRoot;
}

QString UserModel::mergeIconsToImg(const QList< QPair< QIcon, QRect > > &iconsWithRect, const QSize &fullSize,
const QString &format) {
QImage image = QImage(fullSize, QImage::Format_ARGB32);
image.fill(Qt::transparent);
QPainter painter(&image);
painter.setRenderHint(QPainter::Antialiasing);
for (auto [icon, rect] : iconsWithRect) {
icon.paint(&painter, rect);
}

QByteArray mergedIconBa;
QBuffer buffer(&mergedIconBa);
buffer.open(QIODevice::WriteOnly);
image.save(&buffer, qvariant_cast< QByteArray >(format));
QString base64 = qvariant_cast< QString >(mergedIconBa.toBase64());
return QLatin1String("<img src=\"data:image/%2;base64,%1\" />").arg(base64, format);
}


int UserModel::columnCount(const QModelIndex &) const {
return 1;
Expand Down Expand Up @@ -438,29 +460,29 @@ QVariant UserModel::data(const QModelIndex &idx, int role) const {
if (idx.column() == 0) {
if (item->isListener) {
return qiEar;
} else {
// Select the talking-state symbol to display
if (p == pSelf && p->bSelfMute) {
// This is a workaround for a bug that can lead to the user having muted him/herself but
// the talking icon is stuck at qiTalkingOn for some reason.
// Until someone figures out how to fix the root of the problem, we'll have this workaround
// to cure the symptoms of the bug.
return qiTalkingOff;
}
}
// Select the talking-state symbol to display
if (p == pSelf && p->bSelfMute) {
// This is a workaround for a bug that can lead to the user having muted him/herself but
// the talking icon is stuck at qiTalkingOn for some reason.
// Until someone figures out how to fix the root of the problem, we'll have this workaround
// to cure the symptoms of the bug.
return qiTalkingOff;
}

switch (p->tsState) {
case Settings::Talking:
return qiTalkingOn;
case Settings::MutedTalking:
return qiTalkingMuted;
case Settings::Whispering:
return qiTalkingWhisper;
case Settings::Shouting:
return qiTalkingShout;
case Settings::Passive:
default:
return qiTalkingOff;
}
bool isUserProvidedAvatar = !p->qbaTextureHash.isEmpty() && !p->qbaTexture.isEmpty();
switch (p->tsState) {
case Settings::Talking:
return isUserProvidedAvatar ? qiCustomTalkingOn : qiTalkingOn;
case Settings::MutedTalking:
return isUserProvidedAvatar ? qiCustomTalkingMuted : qiTalkingMuted;
case Settings::Whispering:
return isUserProvidedAvatar ? qiCustomTalkingWhisper : qiTalkingWhisper;
case Settings::Shouting:
return isUserProvidedAvatar ? qiCustomTalkingShout : qiTalkingShout;
case Settings::Passive:
default:
return qiTalkingOff;
}
}
break;
Expand Down Expand Up @@ -717,46 +739,68 @@ QVariant UserModel::otherRoles(const QModelIndex &idx, int role) const {
case Qt::WhatsThisRole:
switch (section) {
case 0:
if (isUser)
return QString::fromLatin1("%1"
"<table>"
"<tr><td><img src=\"skin:talking_on.svg\" height=64 /></td><td "
"valign=\"middle\">%2</td></tr>"
"<tr><td><img src=\"skin:talking_alt.svg\" height=64 /></td><td "
"valign=\"middle\">%3</td></tr>"
"<tr><td><img src=\"skin:talking_whisper.svg\" height=64 /></td><td "
"valign=\"middle\">%4</td></tr>"
"<tr><td><img src=\"skin:talking_off.svg\" height=64 /></td><td "
"valign=\"middle\">%5</td></tr>"
"<tr><td><img src=\"skin:talking_muted.svg\" height=64 /></td><td "
"valign=\"middle\">%6</td></tr>"
"<tr><td><img src=\"skin:ear.svg\" height=64 /></td><td "
"valign=\"middle\">%7</td></tr>"
"</table>")
if (isUser) {
QIcon placeholderIcon("skin:mimetypes/image-x-generic.svg");
QRect rect(0, 0, 88, 88);
const QSize &fullSize = rect.size();
QList< QPair< QIcon, QRect > > iconsWithRect = { { placeholderIcon, QRect(22, 22, 44, 44) },
{ qiCustomTalkingOn, rect } };
QString customTalkingOnWithPlaceholderImg = mergeIconsToImg(iconsWithRect, fullSize);
iconsWithRect.replace(1, { qiCustomTalkingShout, rect });
QString customTalkingShoutWithPlaceholderImg = mergeIconsToImg(iconsWithRect, fullSize);
iconsWithRect.replace(1, { qiCustomTalkingWhisper, rect });
QString customTalkingWhisperWithPlaceholderImg = mergeIconsToImg(iconsWithRect, fullSize);
iconsWithRect.replace(1, { qiCustomTalkingMuted, rect });
QString customTalkingMutedWithPlaceholderImg = mergeIconsToImg(iconsWithRect, fullSize);
iconsWithRect.removeLast();
QString placeholderImg = mergeIconsToImg(iconsWithRect, fullSize);
return QString::fromLatin1(
"%1"
"<table>"
"<tr><td valign=\"middle\"><h1 style=\"font-weight: 900;\">"
"<img src=\"skin:talking_on.svg\" height=64 /> / "
"%2 &nbsp;&nbsp;</h1></td><td valign=\"middle\">%3</td></tr>"
"<tr><td valign=\"middle\"><h1 style=\"font-weight: 900;\">"
"<img src=\"skin:talking_alt.svg\" height=64 /> / "
"%4 &nbsp;&nbsp;</h1></td><td valign=\"middle\">%5</td></tr>"
"<tr><td valign=\"middle\"><h1 style=\"font-weight: 900;\">"
"<img src=\"skin:talking_whisper.svg\" height=64 /> / "
"%6 &nbsp;&nbsp;</h1></td><td valign=\"middle\">%7</td></tr>"
"<tr><td valign=\"middle\"><h1 style=\"font-weight: 900;\">"
"<img src=\"skin:talking_off.svg\" height=64 /> / "
"%8 &nbsp;&nbsp;</h1></td><td valign=\"middle\">%9</td></tr>"
"<tr><td valign=\"middle\"><h1 style=\"font-weight: 900;\">"
"<img src=\"skin:talking_muted.svg\" height=64 /> / "
"%10 &nbsp;&nbsp;</h1></td><td valign=\"middle\">%11</td></tr>"
"<tr><td valign=\"middle\"><img src=\"skin:ear.svg\" height=64 /></td>"
"<td valign=\"middle\">%12</td></tr>"
"</table>")
.arg(tr("This is a user connected to the server. The icon to the left of the user "
"indicates whether or not they are talking:"),
tr("Talking to your channel."), tr("Shouting directly to your channel."),
tr("Whispering directly to you."), tr("Not talking."),
customTalkingOnWithPlaceholderImg, tr("Talking to your channel."),
customTalkingShoutWithPlaceholderImg, tr("Shouting directly to your channel."),
customTalkingWhisperWithPlaceholderImg, tr("Whispering directly to you."),
placeholderImg, tr("Not talking."), customTalkingMutedWithPlaceholderImg,
tr("Talking while being muted on your end"),
tr("This is a channel listener. The corresponding user hears everything you say in "
"this channel."));
else
return QString::fromLatin1("%1"
"<table>"
"<tr><td><img src=\"skin:channel_active.svg\" height=64 /></td><td "
"valign=\"middle\">%2</td></tr>"
"<tr><td><img src=\"skin:channel_linked.svg\" height=64 /></td><td "
"valign=\"middle\">%3</td></tr>"
"<tr><td><img src=\"skin:channel.svg\" height=64 /></td><td "
"valign=\"middle\">%4</td></tr>"
"</table>")
.arg(tr("This is a channel on the server. The icon indicates the state of the channel:"),
tr("Your current channel."),
tr("A channel that is linked with your channel. Linked channels can talk to each "
"other."),
tr("A channel on the server that you are not linked to."));
}
return QString::fromLatin1("%1"
"<table>"
"<tr><td><img src=\"skin:channel_active.svg\" height=64 /></td><td "
"valign=\"middle\">%2</td></tr>"
"<tr><td><img src=\"skin:channel_linked.svg\" height=64 /></td><td "
"valign=\"middle\">%3</td></tr>"
"<tr><td><img src=\"skin:channel.svg\" height=64 /></td><td "
"valign=\"middle\">%4</td></tr>"
"</table>")
.arg(tr("This is a channel on the server. The icon indicates the state of the channel:"),
tr("Your current channel."),
tr("A channel that is linked with your channel. Linked channels can talk to each "
"other."),
tr("A channel on the server that you are not linked to."));
case 1:
if (isUser)
if (isUser) {
return QString::fromLatin1("%1"
"<table>"
"<tr><td><img src=\"skin:emblems/emblem-favorite.svg\" height=64 "
Expand Down Expand Up @@ -793,29 +837,29 @@ QVariant UserModel::otherRoles(const QModelIndex &idx, int role) const {
tr("User has a new comment set (click to show)"),
tr("User has a comment set, which you've already seen. (click to show)"),
tr("Ignoring Text Messages"));
else
return QString::fromLatin1("%1"
"<table>"
"<tr><td><img src=\"skin:comment.svg\" height=64 /></td><td "
"valign=\"middle\">%10</td></tr>"
"<tr><td><img src=\"skin:comment_seen.svg\" height=64 /></td><td "
"valign=\"middle\">%11</td></tr>"
"<tr><td><img src=\"skin:filter.svg\" height=64 /></td><td "
"valign=\"middle\">%12</td></tr>"
"<tr><td><img src=\"skin:pin.svg\" height=64 /></td><td "
"valign=\"middle\">%13</td></tr>"
"<tr><td><img src=\"skin:lock_locked.svg\" height=64 /></td><td "
"valign=\"middle\">%14</td></tr>"
"<tr><td><img src=\"skin:lock_unlocked.svg\" height=64 /></td><td "
"valign=\"middle\">%15</td></tr>"
"</table>")
.arg(tr("This shows the flags the channel has, if any:"),
tr("Channel has a new comment set (click to show)"),
tr("Channel has a comment set, which you've already seen. (click to show)"),
tr("Channel will be hidden when filtering is enabled"),
tr("Channel will be pinned when filtering is enabled"),
tr("Channel has access restrictions so that you can't enter it"),
tr("Channel has access restrictions but you can enter nonetheless"));
}
return QString::fromLatin1("%1"
"<table>"
"<tr><td><img src=\"skin:comment.svg\" height=64 /></td><td "
"valign=\"middle\">%10</td></tr>"
"<tr><td><img src=\"skin:comment_seen.svg\" height=64 /></td><td "
"valign=\"middle\">%11</td></tr>"
"<tr><td><img src=\"skin:filter.svg\" height=64 /></td><td "
"valign=\"middle\">%12</td></tr>"
"<tr><td><img src=\"skin:pin.svg\" height=64 /></td><td "
"valign=\"middle\">%13</td></tr>"
"<tr><td><img src=\"skin:lock_locked.svg\" height=64 /></td><td "
"valign=\"middle\">%14</td></tr>"
"<tr><td><img src=\"skin:lock_unlocked.svg\" height=64 /></td><td "
"valign=\"middle\">%15</td></tr>"
"</table>")
.arg(tr("This shows the flags the channel has, if any:"),
tr("Channel has a new comment set (click to show)"),
tr("Channel has a comment set, which you've already seen. (click to show)"),
tr("Channel will be hidden when filtering is enabled"),
tr("Channel will be pinned when filtering is enabled"),
tr("Channel has access restrictions so that you can't enter it"),
tr("Channel has access restrictions but you can enter nonetheless"));
}
break;
}
Expand Down
4 changes: 4 additions & 0 deletions src/mumble/UserModel.h
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ class UserModel : public QAbstractItemModel {
Q_DISABLE_COPY(UserModel)
protected:
QIcon qiTalkingOn, qiTalkingMuted, qiTalkingWhisper, qiTalkingShout, qiTalkingOff;
QIcon qiCustomTalkingOn, qiCustomTalkingMuted, qiCustomTalkingWhisper, qiCustomTalkingShout;
QIcon qiMutedPushToMute, qiMutedSelf, qiMutedServer, qiMutedLocal, qiIgnoredLocal, qiMutedSuppressed;
QIcon qiPrioritySpeaker;
QIcon qiRecording;
Expand Down Expand Up @@ -107,6 +108,9 @@ class UserModel : public QAbstractItemModel {
UserModel(QObject *parent = 0);
~UserModel() Q_DECL_OVERRIDE;

static QString mergeIconsToImg(const QList< QPair< QIcon, QRect > > &iconsWithRect, const QSize &fullSize,
const QString &format = "png");

QModelIndex index(ClientUser *, int column = 0) const;
QModelIndex index(Channel *, int column = 0) const;
QModelIndex index(ModelItem *) const;
Expand Down
34 changes: 33 additions & 1 deletion src/mumble/UserView.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,24 @@
UserDelegate::UserDelegate(QObject *p) : QStyledItemDelegate(p) {
}

bool UserDelegate::drawAvatarIcon(QPainter &painter, const QRect &rect, const ClientUser *user,
const QIcon &talkIndicator) {
if (user == nullptr || user->qbaTextureHash.isEmpty() || user->qbaTexture.isEmpty()) {
return false;
}

QPixmap avatar;
avatar.loadFromData(user->qbaTexture);
painter.save();
painter.setRenderHint(QPainter::Antialiasing);
painter.drawPixmap(rect, avatar);
if (user->tsState != Settings::TalkState::Passive) {
talkIndicator.paint(&painter, rect.adjusted(-5, -5, 5, 5));
}
painter.restore();
return true;
}

void UserDelegate::adjustIcons(int iconTotalDimension, int iconIconPadding, int iconIconDimension) {
m_iconTotalDimension = iconTotalDimension;
m_iconIconPadding = iconIconPadding;
Expand Down Expand Up @@ -68,8 +86,13 @@ void UserDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
o.rect = option.rect.adjusted(0, 0, static_cast< int >(-m_iconTotalDimension * ql.count()), 0);

// draw icon
ModelItem *item = static_cast< ModelItem * >(index.internalPointer());
ClientUser *user = item->pUser;
const QIcon &icon = o.icon;
QRect decorationRect = style->subElementRect(QStyle::SE_ItemViewItemDecoration, &o, o.widget);
o.icon.paint(painter, decorationRect, o.decorationAlignment, iconMode, QIcon::On);
if (item->isListener || !drawAvatarIcon(*painter, decorationRect.adjusted(-3, 0, -3, 0), user, icon)) {
icon.paint(painter, decorationRect, o.decorationAlignment, iconMode, QIcon::On);
}

// draw text
QRect textRect = style->subElementRect(QStyle::SE_ItemViewItemText, &o, o.widget);
Expand Down Expand Up @@ -281,6 +304,15 @@ void UserView::updateChannel(const QModelIndex &idx) {
}
}

void UserView::triggerUpdate() {
QPalette previousPalette = palette();
QPalette palette;
palette.setColor(QPalette::WindowText, previousPalette.color(QPalette::WindowText).darker(101));
setPalette(palette);
QApplication::processEvents();
setPalette(previousPalette);
}

void UserView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector< int > &) {
UserModel *um = static_cast< UserModel * >(model());
int nRowCount = um->rowCount();
Expand Down
4 changes: 4 additions & 0 deletions src/mumble/UserView.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include <QtWidgets/QStyledItemDelegate>
#include <QtWidgets/QTreeView>

#include "ClientUser.h"
#include "QtUtils.h"
#include "Timer.h"

Expand All @@ -24,6 +25,8 @@ class UserDelegate : public QStyledItemDelegate {

public:
UserDelegate(QObject *parent);
static bool drawAvatarIcon(QPainter &painter, const QRect &rect, const ClientUser *user,
const QIcon &talkIndicator);
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const Q_DECL_OVERRIDE;
void adjustIcons(int iconTotalDimension, int iconIconPadding, int iconIconDimension);

Expand All @@ -48,6 +51,7 @@ class UserView : public QTreeView {

public:
UserView(QWidget *);
void triggerUpdate();
void keyboardSearch(const QString &search) Q_DECL_OVERRIDE;
void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight,
const QVector< int > &roles = QVector< int >()) Q_DECL_OVERRIDE;
Expand Down
7 changes: 7 additions & 0 deletions themes/Default/custom_talking_alt.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 7 additions & 0 deletions themes/Default/custom_talking_muted.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 7 additions & 0 deletions themes/Default/custom_talking_on.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading