You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
193 lines
8.9 KiB
193 lines
8.9 KiB
#include "accountdelegate.h" |
|
#include "flagpainter.h" |
|
#include "memberlistmodel.h" |
|
#include "debug_ui.h" |
|
#include <QPainter> |
|
#include <QFont> |
|
#include <QMouseEvent> |
|
#include <QHelpEvent> |
|
#include <QToolTip> |
|
#include <QPainterPath> |
|
#include <QCoreApplication> |
|
#include <QAbstractItemView> |
|
|
|
extern "C" { |
|
#include "chat/chat_member.h" |
|
} |
|
|
|
static QRect inviteButtonRect(const QRect& row) { |
|
return QRect(row.right() - 31, row.center().y() - 14, 28, 28); |
|
} |
|
|
|
static QRect radioMuteButtonRect(const QRect& row) { |
|
return QRect(row.right() - 63, row.center().y() - 14, 28, 28); |
|
} |
|
|
|
// Волны показывают передачу независимо от локального mute; перечёркивание сохраняется при речи. |
|
static void drawRadioMuteButton(QPainter* painter, const QRect& button, bool muted, bool speaking) { |
|
painter->save(); |
|
painter->setRenderHint(QPainter::Antialiasing); |
|
painter->translate(button.center()); |
|
QColor color(muted ? "#EF5350" : speaking ? "#4CAF50" : "#9E9E9E"); |
|
QPainterPath speaker; |
|
speaker.moveTo(-8, -3); speaker.lineTo(-4, -3); speaker.lineTo(1, -7); |
|
speaker.lineTo(1, 7); speaker.lineTo(-4, 3); speaker.lineTo(-8, 3); speaker.closeSubpath(); |
|
painter->fillPath(speaker, color); |
|
painter->setPen(QPen(color, 1.8, Qt::SolidLine, Qt::RoundCap)); painter->setBrush(Qt::NoBrush); |
|
if (muted) painter->drawLine(QPointF(-10, 10), QPointF(10, -10)); |
|
if (speaking) { |
|
painter->drawArc(QRectF(-3, -5, 10, 10), -60 * 16, 120 * 16); |
|
painter->drawArc(QRectF(-6, -8, 16, 16), -60 * 16, 120 * 16); |
|
} |
|
painter->restore(); |
|
} |
|
|
|
static QRect avatarRect(const QRect& row) { |
|
const int size = MemberListModel::kAvatarSize; |
|
return QRect(row.left() + 4, row.top() + (row.height() - size) / 2, size, size); |
|
} |
|
|
|
AccountDelegate::AccountDelegate(QObject *parent) |
|
: QStyledItemDelegate(parent) { |
|
GUI_DEBUG("AccountDelegate: row height=%dpx avatar=%dpx gap=%dpx", MemberListModel::kRowHeight, |
|
MemberListModel::kAvatarSize, MemberListModel::kRowHeight - MemberListModel::kAvatarSize); |
|
} |
|
|
|
void AccountDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, |
|
const QModelIndex &index) const { |
|
painter->save(); |
|
|
|
if (option.state & QStyle::State_Selected) |
|
painter->fillRect(option.rect, option.palette.highlight()); |
|
else if (option.state & QStyle::State_MouseOver) |
|
painter->fillRect(option.rect, option.palette.alternateBase().color().lighter(115)); |
|
else |
|
painter->fillRect(option.rect, option.palette.alternateBase()); |
|
|
|
const int margin = 4; |
|
const int iconSz = MemberListModel::kAvatarSize; |
|
|
|
QIcon icon = qvariant_cast<QIcon>(index.data(Qt::DecorationRole)); |
|
if (!icon.isNull()) |
|
icon.paint(painter, avatarRect(option.rect)); |
|
|
|
const int x = option.rect.left() + iconSz + margin * 3; |
|
const bool canInvite = !index.data(Qt::UserRole + 2).toBool(); |
|
const int w = option.rect.right() - x - margin - 64; |
|
const int yMid = option.rect.top() + option.rect.height() / 2; |
|
|
|
uint8_t flags = (uint8_t)(index.data(Qt::UserRole + 3).toInt() & 0xFF); |
|
|
|
QFont nameFont = painter->font(); |
|
nameFont.setBold(true); |
|
nameFont.setPixelSize(13); |
|
if (flags & MEMBER_FLAG_DELETED) nameFont.setStrikeOut(true); |
|
painter->setFont(nameFont); |
|
|
|
QColor textColor = option.palette.color(QPalette::Text); |
|
if (option.state & QStyle::State_Selected) textColor = option.palette.color(QPalette::HighlightedText); |
|
if (index.data(Qt::UserRole + 2).toBool()) textColor = QColor("#4CAF50"); |
|
painter->setPen(textColor); |
|
|
|
const uint8_t visualFlags = flags & (MEMBER_FLAG_SUPERNODE | MEMBER_FLAG_ADMIN | MEMBER_FLAG_MODER); |
|
int flagX = x; |
|
if (visualFlags) { |
|
FlagPainter::draw(painter, flags, flagX, option.rect.top() + (option.rect.height() - nameFont.pixelSize()) / 2, nameFont.pixelSize()); |
|
int n = __builtin_popcount(visualFlags); |
|
flagX += nameFont.pixelSize() * n + (n - 1) * 2; |
|
} |
|
if (index.data(Qt::UserRole + 4).toBool()) { |
|
FlagPainter::drawStorage(painter, flagX, option.rect.top() + (option.rect.height() - nameFont.pixelSize()) / 2, nameFont.pixelSize()); |
|
flagX += nameFont.pixelSize() + 2; |
|
} |
|
if (flags & MEMBER_FLAG_RADIO) { |
|
FlagPainter::drawRadio(painter, flagX, option.rect.top() + (option.rect.height() - nameFont.pixelSize()) / 2, |
|
nameFont.pixelSize(), textColor); |
|
flagX += nameFont.pixelSize() + 2; |
|
} |
|
|
|
QString name = index.data(Qt::DisplayRole).toString(); |
|
painter->drawText(QRect(flagX, option.rect.top() + 2, w - (flagX - x), yMid - option.rect.top() - 2), |
|
Qt::AlignLeft | Qt::AlignBottom, name); |
|
|
|
QFont subFont = painter->font(); |
|
subFont.setBold(false); |
|
subFont.setPixelSize(10); |
|
painter->setFont(subFont); |
|
painter->setPen(QColor(130, 130, 130)); |
|
|
|
QString sub = index.data(Qt::UserRole + 1).toString(); |
|
if (!sub.isEmpty()) |
|
painter->drawText(QRect(flagX, yMid + 2, w - (flagX - x), option.rect.bottom() - yMid - 2), |
|
Qt::AlignLeft | Qt::AlignTop, sub); |
|
|
|
drawRadioMuteButton(painter, radioMuteButtonRect(option.rect), flags & CHAT_MEMBER_FLAG_RADIO_MUTED, |
|
index.data(MemberListModel::RadioSpeakingRole).toBool()); |
|
if (canInvite) { |
|
QFont plusFont = nameFont; plusFont.setPixelSize(22); plusFont.setStrikeOut(false); |
|
painter->setFont(plusFont); |
|
painter->setPen(option.palette.color(QPalette::Link)); |
|
painter->drawText(inviteButtonRect(option.rect), Qt::AlignCenter, QStringLiteral("+")); |
|
} |
|
painter->restore(); |
|
} |
|
|
|
bool AccountDelegate::editorEvent(QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option, |
|
const QModelIndex& index) { |
|
Q_UNUSED(model); |
|
if (event->type() != QEvent::MouseButtonPress && event->type() != QEvent::MouseButtonRelease && |
|
event->type() != QEvent::MouseButtonDblClick) return false; |
|
auto* mouse = static_cast<QMouseEvent*>(event); |
|
if (mouse->button() != Qt::LeftButton) return false; |
|
const bool onMute = !index.data(Qt::UserRole + 2).toBool() && radioMuteButtonRect(option.rect).contains(mouse->pos()); |
|
if (event->type() == QEvent::MouseButtonPress) { |
|
m_radioMutePress = onMute ? index : QModelIndex(); m_radioMutePosition = mouse->pos(); |
|
} |
|
if (onMute || m_radioMutePress.isValid()) { |
|
if (event->type() == QEvent::MouseButtonRelease) { |
|
const bool activate = onMute && m_radioMutePress == index && (mouse->pos() - m_radioMutePosition).manhattanLength() < 5; |
|
m_radioMutePress = QPersistentModelIndex(); |
|
if (activate) emit radioMuteRequested(index); |
|
} |
|
return true; |
|
} |
|
const bool onAvatar = avatarRect(option.rect).contains(mouse->pos()); |
|
if (event->type()==QEvent::MouseButtonPress) { |
|
m_avatarPress=onAvatar ? index : QModelIndex(); m_avatarPosition=mouse->pos(); |
|
} |
|
if (onAvatar) { |
|
if (event->type()==QEvent::MouseButtonRelease && m_avatarPress==index && (mouse->pos()-m_avatarPosition).manhattanLength()<5) |
|
emit avatarRequested(index); |
|
if (event->type()==QEvent::MouseButtonRelease) m_avatarPress=QPersistentModelIndex(); |
|
return true; |
|
} |
|
if (index.data(Qt::UserRole + 2).toBool()) return false; |
|
const bool inside = inviteButtonRect(option.rect).contains(mouse->pos()); |
|
if (event->type() == QEvent::MouseButtonPress) { m_invitePress = inside ? index : QModelIndex(); return inside; } |
|
if (event->type() == QEvent::MouseButtonDblClick) return inside; |
|
const bool activate = inside && m_invitePress == index; |
|
m_invitePress = QPersistentModelIndex(); |
|
if (activate) emit inviteRequested(index); |
|
return activate; |
|
} |
|
|
|
bool AccountDelegate::helpEvent(QHelpEvent* event, QAbstractItemView* view, const QStyleOptionViewItem& option, |
|
const QModelIndex& index) { |
|
if (event && radioMuteButtonRect(option.rect).contains(event->pos())) { |
|
const bool muted = index.data(Qt::UserRole + 3).toInt() & CHAT_MEMBER_FLAG_RADIO_MUTED; |
|
const bool speaking = index.data(MemberListModel::RadioSpeakingRole).toBool(); |
|
QString text = QCoreApplication::translate("ChatGui", speaking ? "Radio: speaking" : "Radio: silent"); |
|
if (muted) text += " · " + QCoreApplication::translate("ChatGui", "Radio: muted"); |
|
if (!index.data(Qt::UserRole + 2).toBool()) text += '\n' + QCoreApplication::translate("ChatGui", |
|
muted ? "Listen to this member in radio" : "Mute this member in radio"); |
|
QToolTip::showText(event->globalPos(), text, view); |
|
return true; |
|
} |
|
return QStyledItemDelegate::helpEvent(event, view, option, index); |
|
} |
|
|
|
QSize AccountDelegate::sizeHint(const QStyleOptionViewItem &option, |
|
const QModelIndex &index) const { |
|
(void)option; (void)index; |
|
return QSize(160, MemberListModel::kRowHeight); |
|
}
|
|
|