chatgui/chatgui-android: просмотр изображений с зумом + инфо о медиа
- chatgui: просмотрщик изображений (ImageViewerWindow) с зумом/поворотом - chatgui: отправка картинок как image с определением разрешения и типа - chatgui: у видео-пузыря строка info (res·size·bitrate) и бейдж только длительность - chatgui: контекстное меню медиа (сохранить в файл/скачать) - chatgui-android: большие emoji-only сообщения, битрейт/разрешение у видео - chatgui-android: сохранение неактивных интерфейсов как закомментированные секции - chatgui-android: отправка картинок по расширению (jpg/png/gif/bmp/webp) - photo_sender: content-type и расширение по типу файла
This commit is contained in:
1 parent
ca16b46404
commit
c474c37284
17 files changed
+854
-66
No files matched your search
@@ -22,6 +22,7 @@
|
||||
#include <QDesktopServices>
|
||||
#include <QUrl>
|
||||
#include <QDebug>
|
||||
#include <QImageReader>
|
||||
|
||||
extern "C" {
|
||||
#include "../transport/gui_bridge.h"
|
||||
@@ -106,6 +107,32 @@ bool MessageDelegate::editorEvent(QEvent *event, QAbstractItemModel *model,
|
||||
return true;
|
||||
}
|
||||
|
||||
if (L.isImage) {
|
||||
int dlState = index.data(MsgFileDownloadStateRole).toInt();
|
||||
qint64 msgId = index.data(MsgIdRole).toLongLong();
|
||||
QString chId = index.data(MsgChannelIdRole).toString();
|
||||
|
||||
if (dlState == 2) { /* скачано — открыть просмотрщик */
|
||||
QString fp = index.data(MsgVoiceFileRole).toString();
|
||||
if (!fp.isEmpty() && QFile::exists(fp) && m_imageOpenCb) {
|
||||
m_imageOpenCb(fp);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (dlState == 1) return false; /* скачивается */
|
||||
|
||||
if (chId.isEmpty() || msgId <= 0) return false;
|
||||
struct attachment_dl_req* req = (struct attachment_dl_req*)
|
||||
u_malloc(sizeof(struct attachment_dl_req));
|
||||
if (!req) return false;
|
||||
memset(req, 0, sizeof(*req));
|
||||
strncpy(req->channel_id, chId.toUtf8().constData(), sizeof(req->channel_id) - 1);
|
||||
req->msg_id = msgId;
|
||||
gui_bridge_post_uasync_fn(chat_core_attachment_download_trampoline, req);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (L.isFile) {
|
||||
int dlState = index.data(MsgFileDownloadStateRole).toInt();
|
||||
qint64 msgId = index.data(MsgIdRole).toLongLong();
|
||||
@@ -288,6 +315,13 @@ static QString formatFileSize(qint64 bytes) {
|
||||
return QString("%1GB").arg(gb, 0, 'f', 1);
|
||||
}
|
||||
|
||||
static QString formatBitrate(qint64 bytes, qint64 durMs) {
|
||||
if (bytes <= 0 || durMs <= 0) return QString();
|
||||
qint64 kbps = bytes * 8 / durMs;
|
||||
if (kbps >= 1000) return QString("%1Mbps").arg(kbps / 1000.0, 0, 'f', 1);
|
||||
return QString("%1kbps").arg(kbps);
|
||||
}
|
||||
|
||||
static int calcMaxLineWidth(const QFont &font, const QString &text, int availW) {
|
||||
int maxW = 0;
|
||||
int pos = 0;
|
||||
@@ -376,8 +410,9 @@ MessageDelegate::Layout MessageDelegate::calcLayout(
|
||||
if (L.isVideo) {
|
||||
int barW = 3, barPad = 4;
|
||||
int thW = 200, thH = 112; /* 16:9 превью */
|
||||
int infoH = 16; /* строка под пузырём: res·size·bitrate + время */
|
||||
L.bubbleWidth = thW;
|
||||
L.totalHeight = qMax(thH, kAvatar) + kItemGap;
|
||||
L.totalHeight = qMax(thH, kAvatar) + infoH + kItemGap;
|
||||
|
||||
int contentEdge = L.isOutgoing
|
||||
? L.viewWidth - (L.narrow ? kMarginH : (kMarginH + kAvatar + kGap))
|
||||
@@ -386,6 +421,7 @@ MessageDelegate::Layout MessageDelegate::calcLayout(
|
||||
L.bubbleRect = QRect(bx, 0, thW, thH);
|
||||
L.videoThumbRect = L.bubbleRect;
|
||||
L.mediaBarRect = QRect(L.isOutgoing ? bx + thW + 1 : bx - barW - 1, 0, barW, thH);
|
||||
L.statusRect = QRect(bx, thH + 2, thW, infoH - 2);
|
||||
|
||||
int dlState = index.data(MsgFileDownloadStateRole).toInt();
|
||||
int bd = index.data(MsgMediaBlocksDoneRole).toInt();
|
||||
@@ -395,7 +431,42 @@ MessageDelegate::Layout MessageDelegate::calcLayout(
|
||||
if (dlState == 1) { L.progressBarRect = QRect(bx, thH - 3, thW, 3); }
|
||||
|
||||
L.videoPlayBtnRect = QRect(bx + thW / 2 - 24, thH / 2 - 24, 48, 48);
|
||||
L.videoDurBadgeRect = QRect(bx + thW - 116, thH - 26, 108, 18);
|
||||
L.videoDurBadgeRect = QRect(bx + thW - 66, thH - 26, 58, 18);
|
||||
|
||||
if (!L.narrow) {
|
||||
int avatarY = L.bubbleRect.bottom() - kAvatar;
|
||||
if (!L.isOutgoing)
|
||||
L.avatarRect = QRect(kMarginH, avatarY, kAvatar, kAvatar);
|
||||
else
|
||||
L.avatarRect = QRect(L.viewWidth - kMarginH - kAvatar, avatarY, kAvatar, kAvatar);
|
||||
}
|
||||
return L;
|
||||
}
|
||||
|
||||
/* Image message detection */
|
||||
L.isImage = contentType.startsWith("image/");
|
||||
if (L.isImage) {
|
||||
int barW = 3, barPad = 4;
|
||||
int thW = 220, thH = 160;
|
||||
L.bubbleWidth = thW;
|
||||
L.totalHeight = qMax(thH, kAvatar) + kItemGap;
|
||||
|
||||
int contentEdge = L.isOutgoing
|
||||
? L.viewWidth - (L.narrow ? kMarginH : (kMarginH + kAvatar + kGap))
|
||||
: (L.narrow ? kMarginH : (kMarginH + kAvatar + kGap));
|
||||
int bx = L.isOutgoing ? (contentEdge - thW - barW - barPad) : contentEdge;
|
||||
L.bubbleRect = QRect(bx, 0, thW, thH);
|
||||
L.imageThumbRect = L.bubbleRect;
|
||||
L.mediaBarRect = QRect(L.isOutgoing ? bx + thW + 1 : bx - barW - 1, 0, barW, thH);
|
||||
|
||||
int dlState = index.data(MsgFileDownloadStateRole).toInt();
|
||||
int bd = index.data(MsgMediaBlocksDoneRole).toInt();
|
||||
int nb = index.data(MsgMediaNumBlocksRole).toInt();
|
||||
L.mediaBarColor = (dlState == 2) ? 2 : (dlState == 1 || (bd > 0 && nb > 0)) ? 1 : 0;
|
||||
if (dlState == 1 && nb > 0) L.progressFrac = (float)bd / nb;
|
||||
if (dlState == 1) { L.progressBarRect = QRect(bx, thH - 3, thW, 3); }
|
||||
|
||||
L.imageBadgeRect = QRect(bx + thW - 150, thH - 26, 142, 18);
|
||||
|
||||
if (!L.narrow) {
|
||||
int avatarY = L.bubbleRect.bottom() - kAvatar;
|
||||
@@ -790,15 +861,19 @@ void MessageDelegate::paint(QPainter *painter, const QStyleOptionViewItem &optio
|
||||
while (pos < trimmed.length()) {
|
||||
if (trimmed.at(pos).isSpace()) { ++pos; continue; }
|
||||
QString emojiStr;
|
||||
for (const auto &cat : builtinEmojiSet())
|
||||
bool found = false;
|
||||
for (const auto &cat : builtinEmojiSet()) {
|
||||
for (const auto &e : cat.items) {
|
||||
if (e.unicode.length() > trimmed.length() - pos) continue;
|
||||
if (QStringView(trimmed).mid(pos, e.unicode.length()) == e.unicode) {
|
||||
emojiStr = e.unicode;
|
||||
pos += e.unicode.length();
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (found) break;
|
||||
}
|
||||
if (emojiStr.isEmpty()) { ++pos; continue; }
|
||||
|
||||
bool animated = g_animatedEmojiMap.contains(emojiStr);
|
||||
@@ -1010,12 +1085,9 @@ void MessageDelegate::paint(QPainter *painter, const QStyleOptionViewItem &optio
|
||||
painter->drawPath(tri);
|
||||
}
|
||||
|
||||
/* бейдж: размер · длительность (m:ss) */
|
||||
qint64 fsz = index.data(MsgFileSizeRole).toLongLong();
|
||||
QString sizeStr = formatFileSize(fsz);
|
||||
/* бейдж: длительность (m:ss) */
|
||||
QString durStr = index.data(MsgVoiceDurationRole).toString();
|
||||
QString badge = durStr.isEmpty() ? sizeStr : sizeStr + " \xC2\xB7 " + durStr;
|
||||
if (!badge.isEmpty()) {
|
||||
if (!durStr.isEmpty()) {
|
||||
QFont durFont = option.font;
|
||||
durFont.setPointSize(durFont.pointSize() - 1);
|
||||
painter->setFont(durFont);
|
||||
@@ -1023,7 +1095,7 @@ void MessageDelegate::paint(QPainter *painter, const QStyleOptionViewItem &optio
|
||||
painter->setBrush(QColor(0, 0, 0, 160));
|
||||
painter->drawRoundedRect(L.videoDurBadgeRect, 5, 5);
|
||||
painter->setPen(Qt::white);
|
||||
painter->drawText(L.videoDurBadgeRect, Qt::AlignCenter, badge);
|
||||
painter->drawText(L.videoDurBadgeRect, Qt::AlignCenter, durStr);
|
||||
}
|
||||
painter->restore();
|
||||
|
||||
@@ -1049,6 +1121,112 @@ void MessageDelegate::paint(QPainter *painter, const QStyleOptionViewItem &optio
|
||||
if (pw > 0) { painter->setBrush(QColor(0x33, 0x90, 0xEC)); painter->drawRoundedRect(QRect(L.progressBarRect.left(), L.progressBarRect.top(), pw, 3), 2, 2); }
|
||||
}
|
||||
|
||||
/* строка под пузырём: res · size · bitrate + время */
|
||||
{
|
||||
qint64 fsz = index.data(MsgFileSizeRole).toLongLong();
|
||||
qint64 durMs = index.data(MsgVideoDurationMsRole).toLongLong();
|
||||
int vw = index.data(MsgVideoWidthRole).toInt();
|
||||
int vh = index.data(MsgVideoHeightRole).toInt();
|
||||
QStringList info;
|
||||
if (vw > 0 && vh > 0) info << QString("%1×%2").arg(vw).arg(vh);
|
||||
QString sz = formatFileSize(fsz);
|
||||
if (!sz.isEmpty()) info << sz;
|
||||
QString br = formatBitrate(fsz, durMs);
|
||||
if (!br.isEmpty()) info << br;
|
||||
|
||||
QFont smallFont = option.font;
|
||||
smallFont.setPointSize(smallFont.pointSize() - 2);
|
||||
painter->setFont(smallFont);
|
||||
painter->setPen(QColor(0x80, 0x80, 0x80));
|
||||
|
||||
if (!info.isEmpty())
|
||||
painter->drawText(L.statusRect, Qt::AlignLeft | Qt::AlignVCenter, info.join(" \xC2\xB7 "));
|
||||
painter->drawText(L.statusRect, Qt::AlignRight | Qt::AlignVCenter,
|
||||
index.data(MsgTimeRole).toString());
|
||||
}
|
||||
|
||||
painter->restore();
|
||||
return;
|
||||
}
|
||||
|
||||
if (L.isImage) {
|
||||
drawBubble(painter, L, option);
|
||||
if (!L.narrow) drawAvatar(painter, L, index);
|
||||
|
||||
int dlState = index.data(MsgFileDownloadStateRole).toInt();
|
||||
QString filePath = index.data(MsgVoiceFileRole).toString();
|
||||
|
||||
painter->save();
|
||||
painter->setRenderHint(QPainter::Antialiasing);
|
||||
QPainterPath clip;
|
||||
clip.addRoundedRect(L.imageThumbRect, kRadius, kRadius);
|
||||
painter->setClipPath(clip);
|
||||
|
||||
QPixmap img;
|
||||
if (!filePath.isEmpty() && QFile::exists(filePath)) {
|
||||
QImageReader reader(filePath);
|
||||
reader.setAutoTransform(true);
|
||||
reader.setScaledSize(L.imageThumbRect.size() * 2);
|
||||
QImage im = reader.read();
|
||||
if (!im.isNull()) img = QPixmap::fromImage(im);
|
||||
}
|
||||
|
||||
if (!img.isNull()) {
|
||||
painter->fillRect(L.imageThumbRect, QColor(20, 20, 24));
|
||||
QPixmap scaled = img.scaled(L.imageThumbRect.size(), Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||
int dx = (L.imageThumbRect.width() - scaled.width()) / 2;
|
||||
int dy = (L.imageThumbRect.height() - scaled.height()) / 2;
|
||||
painter->drawPixmap(L.imageThumbRect.left() + dx, L.imageThumbRect.top() + dy, scaled);
|
||||
} else {
|
||||
painter->fillRect(L.imageThumbRect, QColor(30, 30, 35));
|
||||
QFont iconFont = option.font;
|
||||
iconFont.setPointSize(iconFont.pointSize() + 10);
|
||||
painter->setFont(iconFont);
|
||||
painter->setPen(QColor(150, 150, 150));
|
||||
painter->drawText(L.imageThumbRect, Qt::AlignCenter, QString::fromUtf8("\xF0\x9F\x93\xB7"));
|
||||
}
|
||||
|
||||
/* бейдж: разрешение · размер */
|
||||
qint64 fsz = index.data(MsgFileSizeRole).toLongLong();
|
||||
int iw = index.data(MsgImageWidthRole).toInt();
|
||||
int ih = index.data(MsgImageHeightRole).toInt();
|
||||
QString res = (iw > 0 && ih > 0) ? QString("%1×%2").arg(iw).arg(ih) : QString();
|
||||
QString sizeStr = formatFileSize(fsz);
|
||||
QString badge = res.isEmpty() ? sizeStr : res + " \xC2\xB7 " + sizeStr;
|
||||
if (!badge.isEmpty()) {
|
||||
QFont durFont = option.font;
|
||||
durFont.setPointSize(durFont.pointSize() - 1);
|
||||
painter->setFont(durFont);
|
||||
painter->setPen(Qt::NoPen);
|
||||
painter->setBrush(QColor(0, 0, 0, 160));
|
||||
painter->drawRoundedRect(L.imageBadgeRect, 5, 5);
|
||||
painter->setPen(Qt::white);
|
||||
painter->drawText(L.imageBadgeRect, Qt::AlignCenter, badge);
|
||||
}
|
||||
painter->restore();
|
||||
|
||||
/* progress overlay (скачивание) */
|
||||
if (dlState == 1) {
|
||||
painter->setBrush(QColor(0, 0, 0, 140));
|
||||
painter->drawRect(L.imageThumbRect);
|
||||
QFont pfont = option.font;
|
||||
pfont.setPointSize(pfont.pointSize() + 2);
|
||||
painter->setFont(pfont);
|
||||
painter->setPen(Qt::white);
|
||||
painter->drawText(L.imageThumbRect, Qt::AlignCenter,
|
||||
QStringLiteral("%1%").arg((int)(L.progressFrac * 100)));
|
||||
}
|
||||
|
||||
/* media bar (левая кромка) */
|
||||
{ QColor barC = L.mediaBarColor == 2 ? QColor(0x40, 0xC0, 0x40) : L.mediaBarColor == 1 ? QColor(0xF0, 0xC0, 0x40) : QColor(0x80, 0x80, 0x80);
|
||||
painter->setPen(Qt::NoPen); painter->setBrush(barC); painter->drawRect(L.mediaBarRect); }
|
||||
if (L.progressBarRect.isValid()) {
|
||||
painter->setBrush(QColor(0x44, 0x44, 0x44));
|
||||
painter->drawRoundedRect(L.progressBarRect, 2, 2);
|
||||
int pw = (int)(L.progressBarRect.width() * L.progressFrac);
|
||||
if (pw > 0) { painter->setBrush(QColor(0x33, 0x90, 0xEC)); painter->drawRoundedRect(QRect(L.progressBarRect.left(), L.progressBarRect.top(), pw, 3), 2, 2); }
|
||||
}
|
||||
|
||||
painter->restore();
|
||||
return;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user