/* Роли модели сообщений и отрисовка text/voice/file/image/video, статусов и progress. * Рассчитывает геометрию для ChatView и обрабатывает действия по media-элементам строки. */ #pragma once #include #include #include #include #include enum MessageDataRole { MsgAuthorRole = Qt::UserRole + 1, MsgTextRole = Qt::UserRole + 2, MsgTimeRole = Qt::UserRole + 3, MsgEditedRole = Qt::UserRole + 4, MsgAvatarRole = Qt::UserRole + 5, MsgHasQuoteRole = Qt::UserRole + 6, MsgQuoteAuthorRole = Qt::UserRole + 7, MsgQuoteTextRole = Qt::UserRole + 8, MsgReactionsRole = Qt::UserRole + 9, MsgIdRole = Qt::UserRole + 10, MsgIsOutgoingRole = Qt::UserRole + 11, MsgContentTypeRole = Qt::UserRole + 12, MsgVoiceDurationRole = Qt::UserRole + 13, MsgVoiceFileRole = Qt::UserRole + 14, MsgVoicePlayStateRole = Qt::UserRole + 15, MsgVoicePlayPosRole = Qt::UserRole + 16, MsgVoiceWaveformRole = Qt::UserRole + 17, MsgVoiceFileSizeRole = Qt::UserRole + 18, MsgVoiceBlockSizeRole = Qt::UserRole + 19, MsgAuthorNodeIdRole = Qt::UserRole + 20, MsgVoiceNumBlocksRole = Qt::UserRole + 21, MsgVoiceSigsRole = Qt::UserRole + 22, MsgFileDisplayNameRole = Qt::UserRole + 23, MsgFileSizeRole = Qt::UserRole + 24, MsgFileDownloadStateRole = Qt::UserRole + 25, MsgChannelIdRole = Qt::UserRole + 26, MsgFileDownloadProgressRole = Qt::UserRole + 27, MsgMediaBlocksDoneRole = Qt::UserRole + 28, MsgMediaNumBlocksRole = Qt::UserRole + 29, MsgVoiceDownloadPendingRole = Qt::UserRole + 30, MsgImageWidthRole = Qt::UserRole + 31, MsgImageHeightRole = Qt::UserRole + 32, MsgVideoWidthRole = Qt::UserRole + 33, MsgVideoHeightRole = Qt::UserRole + 34, MsgVideoDurationMsRole = Qt::UserRole + 35, MsgDeliveryStateRole = Qt::UserRole + 36, MsgInviteLinkRole = Qt::UserRole + 37, /* полная ссылка; MsgTextRole содержит локализованный заголовок и ссылку */ MsgMessageKeyRole = Qt::UserRole + 38, MsgRawTimestampRole = Qt::UserRole + 39, MsgReplyToRole = Qt::UserRole + 40, MsgHighlightRole = Qt::UserRole + 41, }; class MessageDelegate : public QStyledItemDelegate { Q_OBJECT public: static constexpr int kAvatar = 54; explicit MessageDelegate(QObject *parent = nullptr); void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override; bool editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index) override; QString linkAt(const QStyleOptionViewItem& option, const QModelIndex& index, const QPoint& point) const; static void startAutoPlay(const QString& filePath, float actualDurationSec, uint64_t playbackId); static void stopAllAutoPlay(); void setVideoOpenCallback(std::function cb) { m_videoOpenCb = std::move(cb); } void setImageOpenCallback(std::function cb) { m_imageOpenCb = std::move(cb); } void setDownloadCallback(std::function cb) { m_downloadCb = std::move(cb); } signals: void avatarRequested(const QString& channel, quint64 owner); void reactionRequested(const QModelIndex& index, int emoji); /* -1 открывает выбор реакции */ void pollRequested(const QString& messageId); void replyRequested(const QString& messageId); private: struct ReactionChip { QRect rect; int id; QString emoji; int count; bool mine; }; struct Layout { int viewWidth; int bubbleWidth; QRect authorRect; QRect avatarRect; QRect bubbleRect; QRect quoteRect; QRect quoteBarRect; QRect quoteAuthorRect; QRect quoteTextRect; QRect textRect; QRect statusRect; QRect deliveryRect; int totalHeight; bool narrow = false; bool emojiOnly = false; int emojiCount = 0; QRect emojiArea; bool isOutgoing = false; bool isVoice = false; bool isFile = false; bool isVideo = false; bool isImage = false; QRect voicePlayBtnRect; QRect voiceWaveformRect; QRect voiceDurationRect; QRect voiceTimeRect; QString voiceDuration; int mediaBarColor = 0; /* 0=gray, 1=yellow, 2=green */ QRect mediaBarRect; QRect progressBarRect; float progressFrac = 0.0f; QRect videoThumbRect; QRect videoPlayBtnRect; QRect videoDurBadgeRect; QRect imageThumbRect; QRect imageBadgeRect; QRect reactionHeart; QRect reactionTime; QString reactionTimeText; QVector reactionChips; }; std::function m_videoOpenCb; std::function m_imageOpenCb; QPersistentModelIndex m_avatarPress; QPoint m_avatarPressPosition; QPersistentModelIndex m_reactionPress; QPoint m_reactionPressPosition; int m_pressedReaction = -2; QPersistentModelIndex m_pollPress; QPoint m_pollPressPosition; QPersistentModelIndex m_quotePress; QPoint m_quotePressPosition; std::function m_downloadCb; Layout calcLayout(const QStyleOptionViewItem &option, const QModelIndex &index) const; Layout calcBodyLayout(const QStyleOptionViewItem& option, const QModelIndex& index) const; void paintBody(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index, const Layout& layout) const; void drawReactions(QPainter* painter, const Layout& layout, const QStyleOptionViewItem& option) const; void drawQuote(QPainter *p, const Layout &L, const QModelIndex &index, const QStyleOptionViewItem &opt) const; void drawBubble(QPainter *p, const Layout &L, const QStyleOptionViewItem &opt) const; void drawStatusBar(QPainter *p, const Layout &L, const QModelIndex &index, const QStyleOptionViewItem &opt) const; void drawAvatar(QPainter *p, const Layout &L, const QModelIndex &index) const; static constexpr int kGap = 8; static constexpr int kRadius = 10; static constexpr int kPadH = 12; static constexpr int kPadTop = 8; static constexpr int kPadBot = 6; static constexpr int kTail = 6; static constexpr int kMarginH = 8; static constexpr int kItemGap = 3; static constexpr int kQuoteGap = 4; static constexpr int kQuotePadH = 6; static constexpr int kQuotePadV = 6; static constexpr int kQuoteBarW = 2; static constexpr int kQuoteAv = 16; static constexpr int kStatusH = 18; };