mumble-voip_mumble/src/mumble/UserView.cpp

292 lines
9.9 KiB
C++

// Copyright 2009-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 "UserView.h"
#include "Channel.h"
#include "ClientUser.h"
#include "Log.h"
#include "MainWindow.h"
#include "ServerHandler.h"
#include "UserModel.h"
#include "Global.h"
#include <QtGui/QDesktopServices>
#include <QtGui/QHelpEvent>
#include <QtGui/QPainter>
#include <QtWidgets/QWhatsThis>
UserDelegate::UserDelegate(QObject *p) : QStyledItemDelegate(p) {
}
void UserDelegate::adjustIcons(int iconTotalDimension, int iconIconPadding, int iconIconDimension) {
m_iconTotalDimension = iconTotalDimension;
m_iconIconPadding = iconIconPadding;
m_iconIconDimension = iconIconDimension;
}
void UserDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const {
const QAbstractItemModel *m = index.model();
const QModelIndex idxc1 = index.sibling(index.row(), 1);
QVariant data = m->data(idxc1);
QList< QVariant > ql = data.toList();
// Allow a UserView's BackgroundRole to override the current theme's default color.
QVariant bg = index.data(Qt::BackgroundRole);
if (bg.isValid()) {
painter->fillRect(option.rect, bg.value< QBrush >());
}
painter->save();
QStyleOptionViewItem o = option;
initStyleOption(&o, index);
QStyle *style = o.widget->style();
QIcon::Mode iconMode = QIcon::Normal;
QPalette::ColorRole colorRole = ((o.state & QStyle::State_Selected) ? QPalette::HighlightedText : QPalette::Text);
#if defined(Q_OS_WIN)
// Qt's Vista Style has the wrong highlight color for treeview items
// We can't check for QStyleSheetStyle so we have to search the children list search for a QWindowsVistaStyle
QList< QObject * > hierarchy = style->findChildren< QObject * >();
hierarchy.insert(0, style);
foreach (QObject *obj, hierarchy) {
if (QString::fromUtf8(obj->metaObject()->className()) == QString::fromUtf8("QWindowsVistaStyle")) {
colorRole = QPalette::Text;
break;
}
}
#endif
// draw background
style->drawPrimitive(QStyle::PE_PanelItemViewItem, &o, painter, o.widget);
// resize rect to exclude the icons
o.rect = option.rect.adjusted(0, 0, -m_iconTotalDimension * ql.count(), 0);
// draw icon
QRect decorationRect = style->subElementRect(QStyle::SE_ItemViewItemDecoration, &o, o.widget);
o.icon.paint(painter, decorationRect, o.decorationAlignment, iconMode, QIcon::On);
// draw text
QRect textRect = style->subElementRect(QStyle::SE_ItemViewItemText, &o, o.widget);
QString itemText = o.fontMetrics.elidedText(o.text, o.textElideMode, textRect.width());
painter->setFont(o.font);
style->drawItemText(painter, textRect, static_cast< int >(o.displayAlignment), o.palette, true, itemText,
colorRole);
// draw icons to original rect
QRect ps = QRect(option.rect.right() - (ql.size() * m_iconTotalDimension), option.rect.y(),
ql.size() * m_iconTotalDimension, option.rect.height());
for (int i = 0; i < ql.size(); ++i) {
QRect r = ps;
r.setSize(QSize(m_iconIconDimension, m_iconIconDimension));
r.translate(i * m_iconTotalDimension + m_iconIconPadding, m_iconIconPadding);
QRect p = QStyle::alignedRect(option.direction, option.decorationAlignment, r.size(), r);
qvariant_cast< QIcon >(ql[i]).paint(painter, p, option.decorationAlignment, iconMode, QIcon::On);
}
painter->restore();
}
bool UserDelegate::helpEvent(QHelpEvent *evt, QAbstractItemView *view, const QStyleOptionViewItem &option,
const QModelIndex &index) {
if (index.isValid()) {
const QAbstractItemModel *m = index.model();
const QModelIndex firstColumnIdx = index.sibling(index.row(), 1);
QVariant data = m->data(firstColumnIdx);
QList< QVariant > iconList = data.toList();
const int offset = iconList.size() * -m_iconTotalDimension;
const int firstIconPos = option.rect.topRight().x() + offset;
if (evt->pos().x() >= firstIconPos) {
return QStyledItemDelegate::helpEvent(evt, view, option, firstColumnIdx);
}
}
return QStyledItemDelegate::helpEvent(evt, view, option, index);
}
UserView::UserView(QWidget *p) : QTreeView(p), m_userDelegate(make_qt_unique< UserDelegate >(this)) {
adjustIcons();
setItemDelegate(m_userDelegate.get());
// Because in Qt fonts take some time to initialize properly, we have to delay the call
// to adjustIcons a bit in order to give the fonts the necessary time (so we can read out
// the actual font details).
QTimer::singleShot(0, [this]() { adjustIcons(); });
connect(this, SIGNAL(doubleClicked(const QModelIndex &)), this, SLOT(nodeActivated(const QModelIndex &)));
}
void UserView::adjustIcons() {
// Calculate the icon size for status icons based on font size
// This should automaticially adjust size when the user has
// display scaling enabled
m_iconTotalDimension = QFontMetrics(font()).height();
int iconIconPadding = 1;
int iconIconDimension = m_iconTotalDimension - (2 * iconIconPadding);
m_userDelegate->adjustIcons(m_iconTotalDimension, iconIconPadding, iconIconDimension);
viewport()->update();
}
/**
* This implementation contains a special handler to display
* custom what's this entries for items. All other events are
* passed on.
*/
bool UserView::event(QEvent *evt) {
if (evt->type() == QEvent::WhatsThisClicked) {
QWhatsThisClickedEvent *qwtce = static_cast< QWhatsThisClickedEvent * >(evt);
QDesktopServices::openUrl(qwtce->href());
evt->accept();
return true;
}
return QTreeView::event(evt);
}
/**
* This function is used to create custom behaviour when clicking
* on user/channel icons (e.Global::get(). showing the comment)
*/
void UserView::mouseReleaseEvent(QMouseEvent *evt) {
QPoint clickPosition = evt->pos();
QModelIndex idx = indexAt(clickPosition);
if ((evt->button() == Qt::LeftButton) && idx.isValid()) {
UserModel *userModel = qobject_cast< UserModel * >(model());
ClientUser *clientUser = userModel->getUser(idx);
Channel *channel = userModel->getChannel(idx);
// This is the x offset of the _beginning_ of the comment icon starting from the
// right.
// Thus if the comment icon is the last icon that is displayed, this is equal to
// the negative width of a icon's width (which it is initialized to here). For
// every icon that is displayed to the right of the comment icon, we have to subtract
// m_iconTotalDimension once.
int commentIconPxOffset = -m_iconTotalDimension;
bool hasComment = false;
if (clientUser && !clientUser->qbaCommentHash.isEmpty()) {
hasComment = true;
if (clientUser->bLocalIgnore)
commentIconPxOffset -= m_iconTotalDimension;
if (clientUser->bRecording)
commentIconPxOffset -= m_iconTotalDimension;
if (clientUser->bPrioritySpeaker)
commentIconPxOffset -= m_iconTotalDimension;
if (clientUser->bMute)
commentIconPxOffset -= m_iconTotalDimension;
if (clientUser->bSuppress)
commentIconPxOffset -= m_iconTotalDimension;
if (clientUser->bSelfMute)
commentIconPxOffset -= m_iconTotalDimension;
if (clientUser->bLocalMute)
commentIconPxOffset -= m_iconTotalDimension;
if (clientUser->bSelfDeaf)
commentIconPxOffset -= m_iconTotalDimension;
if (clientUser->bDeaf)
commentIconPxOffset -= m_iconTotalDimension;
if (!clientUser->qsFriendName.isEmpty())
commentIconPxOffset -= m_iconTotalDimension;
if (clientUser->iId >= 0)
commentIconPxOffset -= m_iconTotalDimension;
} else if (channel && !channel->qbaDescHash.isEmpty()) {
hasComment = true;
switch (channel->m_filterMode) {
case ChannelFilterMode::PIN:
case ChannelFilterMode::HIDE:
commentIconPxOffset -= m_iconTotalDimension;
break;
case ChannelFilterMode::NORMAL:
// NOOP
break;
}
if (channel->hasEnterRestrictions) {
commentIconPxOffset -= m_iconTotalDimension;
}
}
if (hasComment) {
QRect r = visualRect(idx);
const int commentIconPxPos = r.topRight().x() + commentIconPxOffset;
if ((clickPosition.x() >= commentIconPxPos)
&& (clickPosition.x() <= (commentIconPxPos + m_iconTotalDimension))) {
// Clicked comment icon
QString str = userModel->data(idx, Qt::ToolTipRole).toString();
if (str.isEmpty()) {
userModel->bClicked = true;
} else {
QWhatsThis::showText(viewport()->mapToGlobal(r.bottomRight()), str, this);
userModel->seenComment(idx);
}
return;
}
}
}
QTreeView::mouseReleaseEvent(evt);
}
void UserView::keyPressEvent(QKeyEvent *ev) {
if (ev->key() == Qt::Key_Return || ev->key() == Qt::Key_Enter)
UserView::nodeActivated(currentIndex());
QTreeView::keyPressEvent(ev);
}
void UserView::nodeActivated(const QModelIndex &idx) {
UserModel *um = static_cast< UserModel * >(model());
ClientUser *p = um->getUser(idx);
if (p) {
Global::get().mw->openTextMessageDialog(p);
return;
}
Channel *c = um->getChannel(idx);
if (c) {
// if a channel is activated join it
Global::get().sh->joinChannel(Global::get().uiSession, c->iId);
}
}
void UserView::keyboardSearch(const QString &) {
// Disable keyboard search for the UserView in order to prevent jumping wildly through the
// UI just because the user has accidentally typed something on their keyboard.
return;
}
void UserView::updateChannel(const QModelIndex &idx) {
UserModel *um = static_cast< UserModel * >(model());
if (!idx.isValid()) {
return;
}
Channel *c = um->getChannel(idx);
for (int i = 0; idx.model()->index(i, 0, idx).isValid(); ++i) {
updateChannel(idx.model()->index(i, 0, idx));
}
if (c && idx.parent().isValid()) {
setRowHidden(idx.row(), idx.parent(), c->isFiltered());
}
}
void UserView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector< int > &) {
UserModel *um = static_cast< UserModel * >(model());
int nRowCount = um->rowCount();
int i;
for (i = 0; i < nRowCount; i++)
updateChannel(um->index(i, 0));
QTreeView::dataChanged(topLeft, bottomRight);
}