| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600 |
- #pragma once
- // Shared widgets/helpers used by Client*.cpp. Not part of the public Client API.
- #include "AvatarCache.h"
- #include "FileCache.h"
- #include "FileTypeIcon.h"
- #include "LocalStore.h"
- #include "UiHelpers.h"
- #include "common/protocol_def.h"
- #include <functional>
- #include <QAction>
- #include <QApplication>
- #include <QCheckBox>
- #include <QColor>
- #include <QContextMenuEvent>
- #include <QDate>
- #include <QDateTime>
- #include <QDesktopServices>
- #include <QEvent>
- #include <QFile>
- #include <QFileDialog>
- #include <QFont>
- #include <QFontMetrics>
- #include <QFrame>
- #include <QGridLayout>
- #include <QHBoxLayout>
- #include <QHash>
- #include <QIcon>
- #include <QImage>
- #include <QLabel>
- #include <QListWidget>
- #include <QListWidgetItem>
- #include <QMenu>
- #include <QMouseEvent>
- #include <QObject>
- #include <QPaintEvent>
- #include <QPainter>
- #include <QPainterPath>
- #include <QPalette>
- #include <QPen>
- #include <QPixmap>
- #include <QPolygonF>
- #include <QPointer>
- #include <QProgressBar>
- #include <QPushButton>
- #include <QResizeEvent>
- #include <QScrollArea>
- #include <QScrollBar>
- #include <QSizePolicy>
- #include <QTextLayout>
- #include <QTextOption>
- #include <QTimer>
- #include <QUrl>
- #include <QVBoxLayout>
- #include <QWidget>
- #include <QtMath>
- namespace ClientInternal {
- enum {
- RolePeerUid = Qt::UserRole,
- RoleName = Qt::UserRole + 1,
- RoleUnread = Qt::UserRole + 2,
- RolePreview = Qt::UserRole + 3,
- RoleTs = Qt::UserRole + 4,
- RoleAccount = Qt::UserRole + 5,
- RoleSignature = Qt::UserRole + 6,
- RoleRemark = Qt::UserRole + 7,
- RoleNick = Qt::UserRole + 8,
- RoleAvatarVer = Qt::UserRole + 9,
- RoleOnline = Qt::UserRole + 10,
- RoleConvType = Qt::UserRole + 11,
- RoleMemberCount = Qt::UserRole + 12,
- RoleJoinMode = Qt::UserRole + 13
- };
- constexpr int ConvDm = im::ConvType::Direct;
- constexpr int ConvGroup = im::ConvType::Group;
- struct ConvId
- {
- int type = ConvDm;
- qint64 id = 0;
- bool isGroup() const { return type == ConvGroup; }
- bool isValid() const { return id > 0; }
- bool operator==(const ConvId &o) const { return type == o.type && id == o.id; }
- bool operator!=(const ConvId &o) const { return !(*this == o); }
- };
- #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
- inline uint qHash(const ConvId &c, uint seed = 0)
- {
- return ::qHash(static_cast<uint>(c.type), seed) ^ ::qHash(static_cast<quint64>(c.id));
- }
- #else
- inline size_t qHash(const ConvId &c, size_t seed = 0)
- {
- return ::qHash(static_cast<uint>(c.type), seed) ^ ::qHash(static_cast<quint64>(c.id), seed);
- }
- #endif
- inline QPixmap groupIcon(int size)
- {
- return UiHelpers::groupIconPixmap(size);
- }
- inline QString groupTitle(const QString &name, int memberCount)
- {
- if (memberCount > 0)
- return name + QStringLiteral(" (%1)").arg(memberCount);
- return name;
- }
- constexpr int kReconnectMaxTries = 3;
- constexpr int kReconnectIntervalSec = 10;
- inline QString fmtMs(qint64 nsecs)
- {
- return QString::number(nsecs / 1000000.0, 'f', 1);
- }
- class SpinRing : public QWidget
- {
- public:
- explicit SpinRing(QWidget *parent = nullptr)
- : QWidget(parent)
- {
- setFixedSize(36, 36);
- auto *t = new QTimer(this);
- connect(t, &QTimer::timeout, this, [this]() {
- m_angle = (m_angle + 30) % 360;
- update();
- });
- t->start(80);
- }
- protected:
- void paintEvent(QPaintEvent *) override
- {
- QPainter p(this);
- p.setRenderHint(QPainter::Antialiasing, true);
- const QRectF r = QRectF(rect()).adjusted(3.5, 3.5, -3.5, -3.5);
- QPen back(QColor(7, 193, 96, 40), 3, Qt::SolidLine, Qt::RoundCap);
- p.setPen(back);
- p.drawEllipse(r);
- QPen pen(QColor("#07C160"), 3, Qt::SolidLine, Qt::RoundCap);
- p.setPen(pen);
- p.drawArc(r, -m_angle * 16, 220 * 16);
- }
- private:
- int m_angle = 0;
- };
- struct Conv
- {
- qint64 uid = 0;
- int convType = ConvDm;
- QString name;
- QString preview;
- QString time;
- int unread = 0;
- int avatarVer = 0;
- bool online = false;
- int memberCount = 0;
- };
- inline QString formatTs(qint64 ms)
- {
- if (ms <= 0)
- return QString();
- const QDateTime dt = QDateTime::fromMSecsSinceEpoch(ms);
- const QDate today = QDate::currentDate();
- if (dt.date() == today)
- return dt.toString(QStringLiteral("HH:mm"));
- if (dt.date() == today.addDays(-1))
- return QStringLiteral("昨天");
- if (dt.date().year() == today.year())
- return dt.toString(QStringLiteral("MM-dd"));
- return dt.toString(QStringLiteral("yyyy-MM-dd"));
- }
- class BubbleText : public QWidget
- {
- public:
- explicit BubbleText(const QString &text, QWidget *parent = nullptr)
- : QWidget(parent)
- , m_text(text)
- {
- setObjectName(QStringLiteral("chatText"));
- setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
- setCursor(Qt::IBeamCursor);
- }
- QString text() const { return m_text; }
- void applyMaxWidth(int maxOuter)
- {
- const int ml = 10;
- const int mr = 10;
- const int mt = 8;
- const int mb = 8;
- const int innerMax = qMax(1, maxOuter - ml - mr);
- rebuild(innerMax);
- setFixedSize(m_size);
- if (QWidget *box = parentWidget())
- {
- if (auto *lay = qobject_cast<QVBoxLayout *>(box->layout()))
- lay->setContentsMargins(ml, mt, mr, mb);
- box->setFixedSize(m_size.width() + ml + mr, m_size.height() + mt + mb);
- }
- update();
- }
- protected:
- void paintEvent(QPaintEvent *) override
- {
- QPainter p(this);
- p.setRenderHint(QPainter::TextAntialiasing, true);
- p.setPen(QColor(QStringLiteral("#191919")));
- m_layout.draw(&p, QPointF(0, 0));
- }
- private:
- void rebuild(int innerMax)
- {
- m_layout.setText(m_text);
- m_layout.setFont(font());
- QTextOption opt;
- opt.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
- m_layout.setTextOption(opt);
- m_layout.setCacheEnabled(true);
- qreal y = 0;
- qreal maxLine = 0;
- m_layout.beginLayout();
- for (;;)
- {
- QTextLine line = m_layout.createLine();
- if (!line.isValid())
- break;
- line.setLineWidth(innerMax);
- line.setPosition(QPointF(0, y));
- y += line.height();
- maxLine = qMax(maxLine, line.naturalTextWidth());
- }
- m_layout.endLayout();
- if (y < 1)
- y = QFontMetrics(font()).height();
- m_size = QSize(qBound(1, qCeil(maxLine), innerMax), qCeil(y));
- }
- QString m_text;
- QTextLayout m_layout;
- QSize m_size;
- };
- inline void applyTextBubbleWidth(BubbleText *lab, int maxW)
- {
- if (lab)
- lab->applyMaxWidth(maxW);
- }
- class ClickWidget : public QFrame
- {
- public:
- std::function<void()> onClick;
- std::function<void()> onForward;
- std::function<void()> onSaveAs;
- std::function<void()> onFavorite;
- std::function<void()> onCopy;
- std::function<void()> onDelete;
- std::function<void()> onMultiSelect;
- explicit ClickWidget(QWidget *parent = nullptr) : QFrame(parent)
- {
- setCursor(Qt::PointingHandCursor);
- }
- protected:
- bool eventFilter(QObject *watched, QEvent *event) override
- {
- if (event->type() == QEvent::ContextMenu)
- {
- contextMenuEvent(static_cast<QContextMenuEvent *>(event));
- return true;
- }
- return QFrame::eventFilter(watched, event);
- }
- void mouseReleaseEvent(QMouseEvent *e) override
- {
- if (e->button() != Qt::LeftButton)
- return;
- QWidget *row = parentWidget();
- if (row && row->property("selecting").toBool())
- {
- if (auto *cb = row->findChild<QCheckBox *>(QStringLiteral("msgCheck")))
- cb->toggle();
- return;
- }
- if (onClick)
- onClick();
- }
- void contextMenuEvent(QContextMenuEvent *e) override
- {
- QWidget *row = parentWidget();
- const bool selecting = row && row->property("selecting").toBool();
- if (!onCopy && !onForward && !onSaveAs && !onFavorite && !onDelete && !onMultiSelect)
- {
- QFrame::contextMenuEvent(e);
- return;
- }
- QMenu menu;
- QAction *copy = onCopy ? menu.addAction(QStringLiteral("复制")) : nullptr;
- QAction *fav = onFavorite ? menu.addAction(QStringLiteral("收藏")) : nullptr;
- QAction *fwd = onForward ? menu.addAction(QStringLiteral("转发")) : nullptr;
- QAction *save = onSaveAs ? menu.addAction(QStringLiteral("保存")) : nullptr;
- if ((copy || fav || fwd || save) && (onDelete || onMultiSelect))
- menu.addSeparator();
- QAction *del = onDelete ? menu.addAction(QStringLiteral("删除")) : nullptr;
- QAction *multi = nullptr;
- if (onMultiSelect)
- multi = menu.addAction(selecting ? QStringLiteral("取消多选")
- : QStringLiteral("多选"));
- QAction *picked = menu.exec(e->globalPos());
- if (picked && picked == copy && onCopy)
- onCopy();
- else if (picked && picked == fav && onFavorite)
- onFavorite();
- else if (picked && picked == fwd && onForward)
- onForward();
- else if (picked && picked == save && onSaveAs)
- onSaveAs();
- else if (picked && picked == del && onDelete)
- onDelete();
- else if (picked && picked == multi && onMultiSelect)
- onMultiSelect();
- }
- };
- inline QSize chatMediaBox(int srcW, int srcH, bool video)
- {
- constexpr int kMax = 180;
- if (srcW <= 0 || srcH <= 0)
- return video ? QSize(180, 101) : QSize(160, 120);
- QSize s(srcW, srcH);
- s.scale(kMax, kMax, Qt::KeepAspectRatio);
- return QSize(qMax(48, s.width()), qMax(48, s.height()));
- }
- inline void lockMediaBox(QWidget *w, const QSize &box)
- {
- if (!w)
- return;
- w->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
- w->setMinimumSize(box);
- w->setMaximumSize(box);
- w->resize(box);
- w->updateGeometry();
- }
- inline void lockChatMediaRow(QWidget *row, QWidget *bubble, QWidget *nameCol = nullptr)
- {
- if (!row || !bubble)
- return;
- int h = bubble->minimumSize().height();
- if (h < 48)
- h = bubble->sizeHint().height();
- if (nameCol)
- {
- const int colH = h + 18;
- nameCol->setMinimumHeight(colH);
- nameCol->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Minimum);
- h = colH;
- }
- row->setMinimumHeight(qMax(36, h));
- row->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Minimum);
- row->updateGeometry();
- }
- inline void paintChatMedia(QPainter *p, const QRect &r, const QPixmap &pm, const QColor &fill)
- {
- p->setRenderHint(QPainter::Antialiasing);
- p->setRenderHint(QPainter::SmoothPixmapTransform);
- QPainterPath path;
- path.addRoundedRect(QRectF(r).adjusted(0.5, 0.5, -0.5, -0.5), 4, 4);
- p->setClipPath(path);
- p->fillRect(r, fill);
- if (pm.isNull())
- return;
- const qreal dpr = qMax(1.0, pm.devicePixelRatio());
- QSize pix(qMax(1, qRound(pm.width() / dpr)), qMax(1, qRound(pm.height() / dpr)));
- pix.scale(r.size(), Qt::KeepAspectRatio);
- QRect dest(QPoint(0, 0), pix);
- dest.moveCenter(r.center());
- p->drawPixmap(dest, pm);
- }
- inline void stickChatIfNearBottom(QWidget *w)
- {
- if (!w)
- return;
- for (QWidget *p = w->parentWidget(); p; p = p->parentWidget())
- {
- if (auto *sa = qobject_cast<QScrollArea *>(p))
- {
- QScrollBar *bar = sa->verticalScrollBar();
- if (bar && (bar->maximum() - bar->value()) <= 48)
- bar->setValue(bar->maximum());
- return;
- }
- }
- }
- inline QPixmap fileCtlIcon(bool pause, bool upload = false)
- {
- const int size = 22;
- const qreal dpr = qApp ? qApp->devicePixelRatio() : 1.0;
- QPixmap pm(qRound(size * dpr), qRound(size * dpr));
- pm.fill(Qt::transparent);
- pm.setDevicePixelRatio(dpr);
- QPainter p(&pm);
- p.setRenderHint(QPainter::Antialiasing);
- const QColor fg(QStringLiteral("#576B95"));
- p.setPen(QPen(fg, 1.4));
- p.setBrush(Qt::NoBrush);
- p.drawEllipse(QRectF(1.2, 1.2, size - 2.4, size - 2.4));
- if (pause)
- {
- p.setPen(Qt::NoPen);
- p.setBrush(fg);
- p.drawRoundedRect(QRectF(7.2, 6.4, 2.6, 9.2), 0.8, 0.8);
- p.drawRoundedRect(QRectF(12.2, 6.4, 2.6, 9.2), 0.8, 0.8);
- }
- else
- {
- p.setPen(QPen(fg, 1.6, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin));
- if (upload)
- {
- p.drawLine(QPointF(11.0, 14.4), QPointF(11.0, 8.0));
- p.drawLine(QPointF(7.8, 10.4), QPointF(11.0, 6.4));
- p.drawLine(QPointF(14.2, 10.4), QPointF(11.0, 6.4));
- }
- else
- {
- p.drawLine(QPointF(11.0, 6.2), QPointF(11.0, 12.6));
- p.drawLine(QPointF(7.8, 10.4), QPointF(11.0, 14.4));
- p.drawLine(QPointF(14.2, 10.4), QPointF(11.0, 14.4));
- }
- }
- return pm;
- }
- inline QPushButton *fileCtlBtn(QWidget *parent, bool pause, bool upload = false)
- {
- auto *btn = new QPushButton(parent);
- btn->setFixedSize(22, 22);
- btn->setCursor(Qt::PointingHandCursor);
- btn->setFocusPolicy(Qt::NoFocus);
- btn->setFlat(true);
- btn->setIcon(QIcon(fileCtlIcon(pause, upload)));
- btn->setIconSize(QSize(22, 22));
- btn->setStyleSheet(QStringLiteral(
- "QPushButton { background:transparent; border:none; padding:0; }"
- "QPushButton:disabled { opacity:0.35; }"));
- return btn;
- }
- class ImageBubble : public ClickWidget
- {
- public:
- ImageBubble(QWidget *parent, qint64 fileId, const QString &name, const QString &url,
- const QString &mime, int hintW = 0, int hintH = 0)
- : ClickWidget(parent)
- , m_fileId(fileId)
- , m_url(url)
- , m_mime(mime)
- , m_name(name)
- {
- setObjectName(QStringLiteral("imageBubble"));
- setFrameShape(QFrame::NoFrame);
- setStyleSheet(QStringLiteral("background:transparent; border:none;"));
- FileCache::instance().bindMeta(fileId, url, mime, name, 0);
- if (hintW > 0 && hintH > 0)
- FileCache::instance().rememberMediaSize(fileId, hintW, hintH);
- QSize src = FileCache::instance().probeImageSize(fileId);
- if (src.isEmpty())
- src = QSize(hintW, hintH);
- m_box = chatMediaBox(src.width(), src.height(), false);
- lockMediaBox(this, m_box);
- connect(&FileCache::instance(), &FileCache::thumbReady, this,
- [this](qint64 id, int edge) {
- if (id == m_fileId && edge == 180)
- applyThumb(FileCache::instance().cachedThumbnail(m_fileId, 180));
- });
- connect(&FileCache::instance(), &FileCache::ready, this, [this](qint64 id) {
- if (id != m_fileId || !m_asked)
- return;
- FileCache::instance().requestThumbnail(m_fileId, 180);
- });
- }
- QSize sizeHint() const override { return m_box; }
- QSize minimumSizeHint() const override { return m_box; }
- bool hasHeightForWidth() const override { return false; }
- void ensureMedia()
- {
- if (m_asked)
- return;
- m_asked = true;
- const QPixmap pm = FileCache::instance().cachedThumbnail(m_fileId, 180);
- if (!pm.isNull())
- {
- applyThumb(pm);
- return;
- }
- FileCache::instance().requestThumbnail(m_fileId, 180);
- FileCache::instance().ensure(m_fileId, m_url, m_mime, m_name);
- }
- protected:
- void paintEvent(QPaintEvent *) override
- {
- QPainter p(this);
- paintChatMedia(&p, rect(), m_pm, QColor(QStringLiteral("#F0F0F0")));
- }
- private:
- void applyThumb(const QPixmap &got)
- {
- if (got.isNull())
- return;
- m_pm = got;
- update();
- }
- qint64 m_fileId = 0;
- QString m_url;
- QString m_mime;
- QString m_name;
- bool m_asked = false;
- QSize m_box;
- QPixmap m_pm;
- };
- class FileBubble : public ClickWidget
- {
- public:
- FileBubble(QWidget *parent, qint64 fileId, const QString &name, qint64 size,
- const QString &url, const QString &mime, bool mine)
- : ClickWidget(parent)
- , m_fileId(fileId)
- , m_size(size)
- , m_url(url)
- , m_mime(mime)
- , m_name(name)
- , m_mine(mine)
- {
- setObjectName(QStringLiteral("fileBubble"));
- setStyleSheet(QStringLiteral("background:#FFFFFF; border:none; border-radius:4px;"));
- setFixedWidth(252);
- FileCache::instance().bindMeta(fileId, url, mime, name, size);
- auto *root = new QVBoxLayout(this);
- root->setContentsMargins(8, 8, 10, 8);
- root->setSpacing(6);
- auto *top = new QHBoxLayout();
- top->setContentsMargins(0, 0, 0, 0);
- top->setSpacing(8);
- auto *icon = new QLabel(this);
- icon->setFixedSize(40, 40);
- icon->setScaledContents(true);
- icon->setStyleSheet(QStringLiteral("background:transparent; border:none;"));
- if (FileCache::looksLikeImage(mime, name))
- {
- const QPixmap pm = FileCache::instance().thumbnail(fileId, 40);
- icon->setPixmap(pm.isNull() ? FileTypeIcon::pixmap(name, 40) : pm);
- }
- else
- {
- icon->setPixmap(FileTypeIcon::pixmap(name, 40));
- }
- auto *info = new QVBoxLayout();
- info->setContentsMargins(0, 0, 0, 0);
- info->setSpacing(2);
- auto *n = new QLabel(name, this);
- n->setWordWrap(true);
- n->setStyleSheet(QStringLiteral(
- "color:#191919; font-size:13px; background:transparent; border:none;"));
- m_statusLab = new QLabel(this);
- m_statusLab->setStyleSheet(QStringLiteral(
- "color:#888888; font-size:11px; background:transparent; border:none;"));
- info->addWidget(n);
- info->addWidget(m_statusLab);
- info->addStretch();
- top->addWidget(icon, 0, Qt::AlignTop);
- top->addLayout(info, 1);
- m_bar = new QProgressBar(this);
- m_bar->setRange(0, 1000);
- m_bar->setValue(0);
- m_bar->setTextVisible(false);
- m_bar->setToolTip(QString());
- m_bar->setFixedHeight(6);
- m_bar->setStyleSheet(QStringLiteral(
- "QProgressBar { background:rgba(0,0,0,0.08); border:none; border-radius:3px; }"
- "QProgressBar::chunk { background:#07C160; border-radius:3px; }"));
- auto *meta = new QHBoxLayout();
- meta->setContentsMargins(0, 0, 0, 0);
- meta->setSpacing(8);
- m_sizeLab = new QLabel(this);
- m_sizeLab->setStyleSheet(QStringLiteral(
- "color:#888888; font-size:11px; background:transparent; border:none;"));
- m_speedLab = new QLabel(this);
- m_speedLab->setStyleSheet(QStringLiteral(
- "color:#888888; font-size:11px; background:transparent; border:none;"));
- meta->addWidget(m_sizeLab, 1);
- meta->addWidget(m_speedLab, 0, Qt::AlignRight);
- root->addLayout(top);
- root->addWidget(m_bar);
- root->addLayout(meta);
- m_bar->hide();
- m_sizeLab->hide();
- m_speedLab->hide();
- connect(&FileCache::instance(), &FileCache::statusChanged, this, [this](qint64 id) {
- if (id == m_fileId)
- syncUi();
- });
- connect(&FileCache::instance(), &FileCache::progress, this,
- [this](qint64 id, qint64, qint64, double) {
- if (id == m_fileId)
- syncUi();
- });
- connect(&FileCache::instance(), &FileCache::failed, this,
- [this](qint64 id, const QString &error) {
- if (id != m_fileId)
- return;
- const bool uploading = FileCache::instance().needsUpload(m_fileId)
- || FileCache::instance().status(m_fileId) == FileCache::Uploading;
- if (!uploading)
- {
- UiHelpers::showToast(window(),
- error.isEmpty() ? QStringLiteral("下载失败") : error,
- true);
- }
- syncUi();
- });
- onClick = [this]() {
- const QString path = FileCache::instance().localPath(m_fileId);
- if (!path.isEmpty())
- {
- if (!QDesktopServices::openUrl(QUrl::fromLocalFile(path)))
- UiHelpers::showToast(window(), QStringLiteral("无法打开文件"), true);
- return;
- }
- if (FileCache::instance().needsUpload(m_fileId))
- {
- FileCache::instance().startUpload(m_fileId);
- return;
- }
- m_wantReceive = true;
- FileCache::instance().startDownload(m_fileId, m_url, m_mime, m_name, m_size);
- syncUi();
- };
- onSaveAs = [this]() {
- const QString src = FileCache::instance().localPath(m_fileId);
- if (src.isEmpty())
- {
- UiHelpers::showToast(window(), QStringLiteral("请先下载文件"), true);
- return;
- }
- const QString dest = QFileDialog::getSaveFileName(window(), QStringLiteral("保存文件"), m_name);
- if (dest.isEmpty())
- return;
- QFile::remove(dest);
- if (!QFile::copy(src, dest))
- UiHelpers::showToast(window(), QStringLiteral("保存失败"), true);
- else
- UiHelpers::showToast(window(), QStringLiteral("已保存"));
- };
- syncUi();
- }
- void bindActionSlot(QWidget *slot, QLabel *sendMark)
- {
- m_actionSlot = slot;
- m_sendMark = sendMark;
- if (!m_actionSlot)
- return;
- m_dl = fileCtlBtn(m_actionSlot, false, false);
- m_pause = fileCtlBtn(m_actionSlot, true);
- m_dl->setParent(m_actionSlot);
- m_pause->setParent(m_actionSlot);
- m_dl->move(0, 0);
- m_pause->move(0, 0);
- m_dl->raise();
- m_pause->raise();
- connect(m_dl, &QPushButton::clicked, this, [this]() {
- if (FileCache::instance().needsUpload(m_fileId))
- FileCache::instance().startUpload(m_fileId);
- else
- {
- m_wantReceive = true;
- FileCache::instance().startDownload(m_fileId, m_url, m_mime, m_name, m_size);
- }
- });
- connect(m_pause, &QPushButton::clicked, this, [this]() {
- if (FileCache::instance().needsUpload(m_fileId)
- || FileCache::instance().status(m_fileId) == FileCache::Uploading)
- FileCache::instance().cancelUpload(m_fileId);
- else
- FileCache::instance().cancelDownload(m_fileId);
- });
- syncUi();
- }
- void refreshActionSlot()
- {
- syncUi();
- }
- private:
- void syncUi()
- {
- const FileCache::Status st = FileCache::instance().status(m_fileId);
- const bool pendingUp = m_mine && FileCache::instance().needsUpload(m_fileId);
- qint64 rec = 0, tot = m_size;
- double bps = 0;
- FileCache::instance().progressOf(m_fileId, &rec, &tot, &bps);
- if (tot <= 0)
- tot = m_size;
- const bool received = (st == FileCache::Received) && !pendingUp;
- const bool transferring = (st == FileCache::Downloading) || (st == FileCache::Uploading);
- const bool transferStarted = transferring || (rec > 0 && !received);
- const bool sentOnServer = m_mine && !pendingUp && m_fileId > 0;
- const bool showAsSent = sentOnServer && !m_wantReceive && !transferStarted;
- QString status = FileCache::statusText(st);
- if (pendingUp && st != FileCache::Uploading)
- status = QStringLiteral("已暂停");
- else if (st == FileCache::NotReceived && rec > 0)
- status = QStringLiteral("已暂停");
- if (showAsSent)
- status = QStringLiteral("已发送");
- m_statusLab->setText(status);
- if (showAsSent)
- {
- m_bar->hide();
- m_sizeLab->hide();
- m_speedLab->hide();
- }
- else if (transferStarted)
- {
- m_bar->show();
- m_sizeLab->show();
- m_speedLab->show();
- if (tot > 0)
- m_bar->setValue(
- static_cast<int>(qBound(qint64(0), rec * 1000 / tot, qint64(1000))));
- else
- m_bar->setValue(0);
- m_sizeLab->setText(QStringLiteral("%1 / %2")
- .arg(FileCache::formatSize(rec),
- tot > 0 ? FileCache::formatSize(tot)
- : QStringLiteral("--")));
- if (transferring)
- m_speedLab->setText(FileCache::formatSpeed(bps));
- else
- m_speedLab->setText(QStringLiteral("已暂停"));
- }
- else
- {
- m_bar->hide();
- m_sizeLab->hide();
- m_speedLab->hide();
- }
- if (!m_actionSlot || !m_dl || !m_pause)
- return;
- const int sendState = parentWidget()
- ? parentWidget()->property("sendStatus").toInt()
- : LocalMsg::Ok;
- const bool sendBusy = m_mine
- && (sendState == LocalMsg::Sending || sendState == LocalMsg::Failed);
- if (sendBusy)
- {
- m_dl->hide();
- m_pause->hide();
- if (m_actionSlot)
- m_actionSlot->show();
- if (m_sendMark)
- m_sendMark->show();
- return;
- }
- if (received || showAsSent)
- {
- m_dl->hide();
- m_pause->hide();
- if (m_sendMark)
- m_sendMark->hide();
- if (m_actionSlot)
- m_actionSlot->hide();
- return;
- }
- if (m_actionSlot)
- m_actionSlot->show();
- if (m_sendMark)
- m_sendMark->hide();
- m_dl->setIcon(QIcon(fileCtlIcon(false, pendingUp)));
- m_dl->setVisible(!transferring);
- m_pause->setVisible(transferring);
- }
- qint64 m_fileId = 0;
- qint64 m_size = 0;
- QString m_url;
- QString m_mime;
- QString m_name;
- bool m_mine = false;
- bool m_wantReceive = false;
- QWidget *m_actionSlot = nullptr;
- QLabel *m_sendMark = nullptr;
- QPushButton *m_dl = nullptr;
- QPushButton *m_pause = nullptr;
- QLabel *m_statusLab = nullptr;
- QProgressBar *m_bar = nullptr;
- QLabel *m_sizeLab = nullptr;
- QLabel *m_speedLab = nullptr;
- };
- inline QPixmap videoPlayOverlay(int size)
- {
- const qreal dpr = qApp ? qApp->devicePixelRatio() : 1.0;
- QPixmap pm(qRound(size * dpr), qRound(size * dpr));
- pm.fill(Qt::transparent);
- pm.setDevicePixelRatio(dpr);
- QPainter p(&pm);
- p.setRenderHint(QPainter::Antialiasing, true);
- p.setBrush(QColor(0, 0, 0, 140));
- p.setPen(Qt::NoPen);
- p.drawEllipse(0, 0, size, size);
- QPainterPath tri;
- const qreal s = size;
- tri.moveTo(s * 0.38, s * 0.28);
- tri.lineTo(s * 0.38, s * 0.72);
- tri.lineTo(s * 0.74, s * 0.50);
- tri.closeSubpath();
- p.setBrush(QColor(255, 255, 255, 230));
- p.drawPath(tri);
- return pm;
- }
- class VideoBubble : public ClickWidget
- {
- public:
- VideoBubble(QWidget *parent, qint64 fileId, const QString &name, qint64 size,
- const QString &url, const QString &mime, bool mine, int hintW = 0,
- int hintH = 0)
- : ClickWidget(parent)
- , m_fileId(fileId)
- , m_size(size)
- , m_url(url)
- , m_mime(mime)
- , m_name(name)
- , m_mine(mine)
- , m_hintW(hintW)
- , m_hintH(hintH)
- {
- setObjectName(QStringLiteral("videoBubble"));
- setFrameShape(QFrame::NoFrame);
- setStyleSheet(QStringLiteral("background:transparent; border:none;"));
- FileCache::instance().bindMeta(fileId, url, mime, name, size);
- if (hintW > 0 && hintH > 0)
- FileCache::instance().rememberMediaSize(fileId, hintW, hintH);
- m_box = reservedSize();
- lockMediaBox(this, m_box);
- m_overlay = new QWidget(this);
- m_overlay->setAttribute(Qt::WA_TransparentForMouseEvents);
- m_overlay->setStyleSheet(QStringLiteral("background:transparent;"));
- auto *ovLay = new QVBoxLayout(m_overlay);
- ovLay->setContentsMargins(6, 0, 6, 6);
- ovLay->setSpacing(3);
- ovLay->addStretch();
- m_metaLab = new QLabel(m_overlay);
- m_metaLab->setStyleSheet(QStringLiteral(
- "color:#FFFFFF; font-size:11px; background:transparent; border:none;"));
- m_bar = new QProgressBar(m_overlay);
- m_bar->setRange(0, 1000);
- m_bar->setValue(0);
- m_bar->setTextVisible(false);
- m_bar->setFixedHeight(4);
- m_bar->setStyleSheet(QStringLiteral(
- "QProgressBar { background:rgba(255,255,255,0.25); border:none; border-radius:2px; }"
- "QProgressBar::chunk { background:#07C160; border-radius:2px; }"));
- ovLay->addWidget(m_metaLab);
- ovLay->addWidget(m_bar);
- m_overlay->setGeometry(QRect(QPoint(0, 0), m_box));
- connect(&FileCache::instance(), &FileCache::posterReady, this, [this](qint64 id) {
- if (id == m_fileId)
- applyPoster();
- });
- connect(&FileCache::instance(), &FileCache::thumbReady, this,
- [this](qint64 id, int) {
- if (id == m_fileId)
- applyPoster();
- });
- connect(&FileCache::instance(), &FileCache::ready, this, [this](qint64 id) {
- if (id != m_fileId || !m_asked)
- return;
- FileCache::instance().requestVideoPoster(m_fileId);
- applyPoster();
- syncUi();
- });
- connect(&FileCache::instance(), &FileCache::statusChanged, this, [this](qint64 id) {
- if (id == m_fileId)
- syncUi();
- });
- connect(&FileCache::instance(), &FileCache::progress, this,
- [this](qint64 id, qint64, qint64, double) {
- if (id == m_fileId)
- syncUi();
- });
- connect(&FileCache::instance(), &FileCache::failed, this,
- [this](qint64 id, const QString &error) {
- if (id != m_fileId)
- return;
- const bool uploading = FileCache::instance().needsUpload(m_fileId)
- || FileCache::instance().status(m_fileId) == FileCache::Uploading;
- if (!uploading)
- {
- UiHelpers::showToast(window(),
- error.isEmpty() ? QStringLiteral("下载失败") : error,
- true);
- }
- syncUi();
- });
- onClick = [this]() {
- const QString path = FileCache::instance().localPath(m_fileId);
- if (!path.isEmpty())
- return;
- if (FileCache::instance().needsUpload(m_fileId))
- {
- FileCache::instance().startUpload(m_fileId);
- return;
- }
- m_wantReceive = true;
- FileCache::instance().startDownload(m_fileId, m_url, m_mime, m_name, m_size);
- syncUi();
- };
- onSaveAs = [this]() {
- const QString src = FileCache::instance().localPath(m_fileId);
- if (src.isEmpty())
- {
- UiHelpers::showToast(window(), QStringLiteral("请先下载视频"), true);
- return;
- }
- const QString dest = QFileDialog::getSaveFileName(window(), QStringLiteral("保存视频"),
- m_name);
- if (dest.isEmpty())
- return;
- QFile::remove(dest);
- if (!QFile::copy(src, dest))
- UiHelpers::showToast(window(), QStringLiteral("保存失败"), true);
- else
- UiHelpers::showToast(window(), QStringLiteral("已保存"));
- };
- applyPoster();
- syncUi();
- }
- QSize sizeHint() const override { return m_box; }
- QSize minimumSizeHint() const override { return m_box; }
- bool hasHeightForWidth() const override { return false; }
- void ensureMedia()
- {
- if (m_asked)
- return;
- m_asked = true;
- applyPoster();
- if (FileCache::instance().localPath(m_fileId).isEmpty() && !m_mine)
- {
- m_wantReceive = true;
- FileCache::instance().startDownload(m_fileId, m_url, m_mime, m_name, m_size);
- }
- else
- {
- FileCache::instance().requestVideoPoster(m_fileId);
- }
- syncUi();
- }
- void bindActionSlot(QWidget *slot, QLabel *sendMark)
- {
- m_actionSlot = slot;
- m_sendMark = sendMark;
- if (!m_actionSlot)
- return;
- m_dl = fileCtlBtn(m_actionSlot, false, false);
- m_pause = fileCtlBtn(m_actionSlot, true);
- m_dl->setParent(m_actionSlot);
- m_pause->setParent(m_actionSlot);
- m_dl->move(0, 0);
- m_pause->move(0, 0);
- m_dl->raise();
- m_pause->raise();
- connect(m_dl, &QPushButton::clicked, this, [this]() {
- if (FileCache::instance().needsUpload(m_fileId))
- FileCache::instance().startUpload(m_fileId);
- else
- {
- m_wantReceive = true;
- FileCache::instance().startDownload(m_fileId, m_url, m_mime, m_name, m_size);
- }
- });
- connect(m_pause, &QPushButton::clicked, this, [this]() {
- if (FileCache::instance().needsUpload(m_fileId)
- || FileCache::instance().status(m_fileId) == FileCache::Uploading)
- FileCache::instance().cancelUpload(m_fileId);
- else
- FileCache::instance().cancelDownload(m_fileId);
- });
- syncUi();
- }
- void refreshActionSlot()
- {
- syncUi();
- }
- protected:
- void paintEvent(QPaintEvent *) override
- {
- QPainter p(this);
- paintChatMedia(&p, rect(), m_pm, QColor(QStringLiteral("#1A1A1A")));
- const QPixmap play = videoPlayOverlay(36);
- const qreal dpr = qMax(1.0, play.devicePixelRatio());
- const QSize ps(qRound(play.width() / dpr), qRound(play.height() / dpr));
- QRect pr(QPoint(0, 0), ps);
- pr.moveCenter(rect().center());
- p.drawPixmap(pr, play);
- }
- void resizeEvent(QResizeEvent *e) override
- {
- ClickWidget::resizeEvent(e);
- if (m_overlay)
- m_overlay->setGeometry(rect());
- }
- private:
- void applyPoster()
- {
- const QPixmap got = FileCache::instance().cachedThumbnail(m_fileId, 180);
- if (got.isNull())
- return;
- m_pm = got;
- update();
- }
- QSize reservedSize() const
- {
- QSize src = FileCache::instance().mediaSize(m_fileId);
- if (src.isEmpty())
- src = QSize(m_hintW, m_hintH);
- return chatMediaBox(src.width(), src.height(), true);
- }
- void syncUi()
- {
- const FileCache::Status st = FileCache::instance().status(m_fileId);
- const bool pendingUp = m_mine && FileCache::instance().needsUpload(m_fileId);
- qint64 rec = 0, tot = m_size;
- double bps = 0;
- FileCache::instance().progressOf(m_fileId, &rec, &tot, &bps);
- if (tot <= 0)
- tot = m_size;
- const bool received = (st == FileCache::Received) && !pendingUp;
- const bool transferring = (st == FileCache::Downloading) || (st == FileCache::Uploading);
- const bool transferStarted = transferring || (rec > 0 && !received);
- const bool sentOnServer = m_mine && !pendingUp && m_fileId > 0;
- const bool showAsSent = sentOnServer && !m_wantReceive && !transferStarted;
- if (showAsSent || received || !transferStarted)
- {
- m_bar->hide();
- m_metaLab->hide();
- }
- else
- {
- m_bar->show();
- m_metaLab->show();
- if (tot > 0)
- m_bar->setValue(
- static_cast<int>(qBound(qint64(0), rec * 1000 / tot, qint64(1000))));
- else
- m_bar->setValue(0);
- QString meta = QStringLiteral("%1 / %2")
- .arg(FileCache::formatSize(rec),
- tot > 0 ? FileCache::formatSize(tot)
- : QStringLiteral("--"));
- if (transferring)
- meta += QStringLiteral(" ") + FileCache::formatSpeed(bps);
- else
- meta += QStringLiteral(" 已暂停");
- m_metaLab->setText(meta);
- }
- if (!m_actionSlot || !m_dl || !m_pause)
- return;
- const int sendState = parentWidget()
- ? parentWidget()->property("sendStatus").toInt()
- : LocalMsg::Ok;
- const bool sendBusy = m_mine
- && (sendState == LocalMsg::Sending || sendState == LocalMsg::Failed);
- if (sendBusy)
- {
- m_dl->hide();
- m_pause->hide();
- if (m_actionSlot)
- m_actionSlot->show();
- if (m_sendMark)
- m_sendMark->show();
- return;
- }
- if (received || showAsSent)
- {
- m_dl->hide();
- m_pause->hide();
- if (m_sendMark)
- m_sendMark->hide();
- if (m_actionSlot)
- m_actionSlot->hide();
- return;
- }
- if (m_actionSlot)
- m_actionSlot->show();
- if (m_sendMark)
- m_sendMark->hide();
- m_dl->setIcon(QIcon(fileCtlIcon(false, pendingUp)));
- m_dl->setVisible(!transferring);
- m_pause->setVisible(transferring);
- }
- qint64 m_fileId = 0;
- qint64 m_size = 0;
- QString m_url;
- QString m_mime;
- QString m_name;
- bool m_mine = false;
- bool m_asked = false;
- bool m_wantReceive = false;
- int m_hintW = 0;
- int m_hintH = 0;
- QSize m_box;
- QPixmap m_pm;
- QWidget *m_actionSlot = nullptr;
- QLabel *m_sendMark = nullptr;
- QPushButton *m_dl = nullptr;
- QPushButton *m_pause = nullptr;
- QWidget *m_overlay = nullptr;
- QProgressBar *m_bar = nullptr;
- QLabel *m_metaLab = nullptr;
- };
- inline void refreshRowFileAction(QWidget *row)
- {
- if (!row)
- return;
- if (auto *fb = row->findChild<FileBubble *>(QStringLiteral("fileBubble")))
- fb->refreshActionSlot();
- if (auto *vb = row->findChild<VideoBubble *>(QStringLiteral("videoBubble")))
- vb->refreshActionSlot();
- }
- using ElideLabel = UiHelpers::ElideLabel;
- class TrayPreviewRow : public QWidget
- {
- public:
- std::function<void()> onActivate;
- explicit TrayPreviewRow(QWidget *parent = nullptr)
- : QWidget(parent)
- {
- setAttribute(Qt::WA_Hover);
- setCursor(Qt::PointingHandCursor);
- setAutoFillBackground(true);
- setMinimumHeight(48);
- applyHover(false);
- }
- protected:
- void enterEvent(QEvent *event) override
- {
- applyHover(true);
- QWidget::enterEvent(event);
- }
- void leaveEvent(QEvent *event) override
- {
- applyHover(false);
- QWidget::leaveEvent(event);
- }
- void mouseReleaseEvent(QMouseEvent *event) override
- {
- if (event->button() == Qt::LeftButton && rect().contains(event->pos()) && onActivate)
- onActivate();
- QWidget::mouseReleaseEvent(event);
- }
- private:
- void applyHover(bool on)
- {
- QPalette pal = palette();
- pal.setColor(QPalette::Window, on ? QColor("#F2F2F2") : QColor("#FFFFFF"));
- setPalette(pal);
- }
- };
- inline QPixmap navIcon(int kind, const QColor &color)
- {
- const int size = 22;
- QPixmap pm(size, size);
- pm.fill(Qt::transparent);
- QPainter p(&pm);
- p.setRenderHint(QPainter::Antialiasing, true);
- p.setPen(QPen(color, 1.6));
- p.setBrush(Qt::NoBrush);
- if (kind == 0)
- {
- p.drawRoundedRect(QRectF(2.5, 3.5, 17, 12.5), 2.5, 2.5);
- QPainterPath tail;
- tail.moveTo(6.5, 16);
- tail.lineTo(8.5, 20);
- tail.lineTo(12, 16);
- p.setBrush(color);
- p.setPen(Qt::NoPen);
- p.drawPath(tail);
- }
- else if (kind == 1)
- {
- p.drawEllipse(QRectF(6.5, 3.2, 9, 9));
- p.drawArc(QRect(3, 12, 16, 12), 20 * 16, 140 * 16);
- }
- else if (kind == 3)
- {
- QPolygonF star;
- for (int i = 0; i < 5; ++i)
- {
- const qreal a = -1.5707963 + i * 1.25663706;
- star << QPointF(11 + qCos(a) * 8.2, 11 + qSin(a) * 8.2);
- const qreal b = a + 0.62831853;
- star << QPointF(11 + qCos(b) * 3.4, 11 + qSin(b) * 3.4);
- }
- p.setBrush(color);
- p.setPen(Qt::NoPen);
- p.drawPolygon(star);
- }
- else
- {
- const QPointF c(11, 11);
- for (int i = 0; i < 6; ++i)
- {
- const qreal a = i * 3.14159265 / 3.0;
- const QPointF d(qCos(a), qSin(a));
- p.drawLine(c + d * 5.2, c + d * 8.6);
- }
- p.drawEllipse(QRectF(6.6, 6.6, 8.8, 8.8));
- p.drawEllipse(QRectF(8.6, 8.6, 4.8, 4.8));
- }
- return pm;
- }
- inline QPixmap chatToolIcon(int kind, const QColor &color, int size = 20)
- {
- const qreal dpr = qApp ? qApp->devicePixelRatio() : 1.0;
- QPixmap pm(qRound(size * dpr), qRound(size * dpr));
- pm.fill(Qt::transparent);
- pm.setDevicePixelRatio(dpr);
- QPainter p(&pm);
- p.setRenderHint(QPainter::Antialiasing, true);
- p.setPen(QPen(color, 1.5, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin));
- p.setBrush(Qt::NoBrush);
- const qreal s = size;
- if (kind == 0)
- {
- p.drawEllipse(QRectF(2.2, 3.2, s - 4.4, s - 5.2));
- p.drawArc(QRectF(5.5, 7.5, s - 11.0, s - 12.0), 200 * 16, 140 * 16);
- p.setBrush(color);
- p.setPen(Qt::NoPen);
- p.drawEllipse(QRectF(6.4, 7.2, 2.2, 2.2));
- p.drawEllipse(QRectF(s - 8.6, 7.2, 2.2, 2.2));
- }
- else if (kind == 1)
- {
- p.drawRoundedRect(QRectF(2.5, 4.2, s - 5.0, s - 8.0), 2.0, 2.0);
- p.setBrush(color);
- p.setPen(Qt::NoPen);
- p.drawEllipse(QRectF(5.2, 6.4, 3.0, 3.0));
- QPolygonF hill;
- hill << QPointF(3.6, s - 5.2) << QPointF(8.2, 10.2) << QPointF(11.2, 13.0)
- << QPointF(14.4, 9.4) << QPointF(s - 3.6, s - 5.2);
- p.drawPolygon(hill);
- }
- else if (kind == 3)
- {
- p.drawRoundedRect(QRectF(2.6, 4.6, s - 5.2, s - 9.0), 2.0, 2.0);
- p.setBrush(color);
- p.setPen(Qt::NoPen);
- QPolygonF tri;
- tri << QPointF(s * 0.40, s * 0.36) << QPointF(s * 0.40, s * 0.68)
- << QPointF(s * 0.68, s * 0.52);
- p.drawPolygon(tri);
- }
- else
- {
- p.drawRoundedRect(QRectF(5.0, 2.8, s - 9.2, s - 5.4), 1.2, 1.2);
- p.drawLine(QPointF(5.0, 6.4), QPointF(s - 4.2, 6.4));
- p.drawLine(QPointF(7.4, 9.4), QPointF(s - 7.2, 9.4));
- p.drawLine(QPointF(7.4, 12.2), QPointF(s - 7.2, 12.2));
- }
- return pm;
- }
- inline QPixmap settingsNavIcon()
- {
- static QPixmap cached;
- if (!cached.isNull())
- return cached;
- QPixmap src(QStringLiteral(":/icons/settings.png"));
- if (src.isNull())
- {
- cached = navIcon(2, QColor("#A6A6A6"));
- return cached;
- }
- QImage img = src.toImage().convertToFormat(QImage::Format_ARGB32);
- for (int y = 0; y < img.height(); ++y)
- {
- auto *line = reinterpret_cast<QRgb *>(img.scanLine(y));
- for (int x = 0; x < img.width(); ++x)
- {
- if (qRed(line[x]) < 40 && qGreen(line[x]) < 40 && qBlue(line[x]) < 40)
- line[x] = qRgba(0, 0, 0, 0);
- }
- }
- cached = QPixmap::fromImage(img).scaled(22, 22, Qt::KeepAspectRatio, Qt::SmoothTransformation);
- return cached;
- }
- inline QPushButton *navBtn(int kind, const QString &tip, QWidget *parent, bool checkable = true)
- {
- auto *btn = new QPushButton(parent);
- btn->setCheckable(checkable);
- btn->setFixedSize(56, 48);
- btn->setCursor(Qt::PointingHandCursor);
- btn->setIconSize(QSize(22, 22));
- btn->setIcon(QIcon(kind == 2 ? settingsNavIcon() : navIcon(kind, QColor("#A6A6A6"))));
- btn->setStyleSheet(QStringLiteral(
- "QPushButton { background:transparent; border:none; border-radius:0; padding:0; }"
- "QPushButton:hover { background:#3A3A3A; }"
- "QPushButton:checked { background:transparent; }"));
- if (checkable)
- {
- QObject::connect(btn, &QPushButton::toggled, btn, [btn, kind](bool on) {
- btn->setIcon(QIcon(navIcon(kind, on ? QColor("#07C160") : QColor("#A6A6A6"))));
- });
- }
- return btn;
- }
- inline QLabel *makeOnlineDot(QWidget *parent, bool online)
- {
- auto *dot = new QLabel(parent);
- dot->setFixedSize(8, 8);
- dot->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
- dot->setAttribute(Qt::WA_StyledBackground, true);
- dot->setStyleSheet(online
- ? QStringLiteral("background:#07C160; border:none; border-radius:4px;")
- : QStringLiteral("background:#C8C8C8; border:none; border-radius:4px;"));
- return dot;
- }
- inline Conv convFromItem(const QListWidgetItem *item)
- {
- Conv c;
- c.uid = item->data(RolePeerUid).toLongLong();
- c.convType = item->data(RoleConvType).toInt();
- c.name = item->data(RoleName).toString();
- c.preview = item->data(RolePreview).toString();
- c.time = formatTs(item->data(RoleTs).toLongLong());
- c.unread = item->data(RoleUnread).toInt();
- c.avatarVer = item->data(RoleAvatarVer).toInt();
- c.online = item->data(RoleOnline).toInt() == 1;
- c.memberCount = item->data(RoleMemberCount).toInt();
- return c;
- }
- inline bool listOnlineOf(const QListWidget *list, qint64 uid)
- {
- if (!list)
- return false;
- for (int i = 0; i < list->count(); ++i)
- {
- auto *item = list->item(i);
- if (item && item->data(RolePeerUid).toLongLong() == uid)
- return item->data(RoleOnline).toInt() == 1;
- }
- return false;
- }
- inline QIcon blankTrayIcon()
- {
- QPixmap pm(32, 32);
- pm.fill(Qt::transparent);
- return QIcon(pm);
- }
- inline QWidget *makeFriendRow(const QString &name, bool online, qint64 uid, int avatarVer, QWidget *parent)
- {
- auto *row = new QWidget(parent);
- row->setFixedHeight(48);
- row->setMinimumWidth(0);
- row->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed);
- row->setAttribute(Qt::WA_TranslucentBackground);
- row->setStyleSheet(QStringLiteral("background:transparent;"));
- auto *lay = new QHBoxLayout(row);
- lay->setContentsMargins(12, 8, 12, 8);
- lay->setSpacing(10);
- auto *avatar = new QLabel(row);
- avatar->setFixedSize(32, 32);
- avatar->setPixmap(AvatarCache::instance().pixmap(uid, avatarVer, name, 32));
- auto *nameLab = new ElideLabel(name, row);
- nameLab->setStyleSheet(QStringLiteral("color:#191919; font-size:14px; background:transparent;"));
- lay->addWidget(avatar, 0, Qt::AlignVCenter);
- lay->addWidget(nameLab, 1, Qt::AlignVCenter);
- if (online)
- lay->addWidget(makeOnlineDot(row, true), 0, Qt::AlignVCenter);
- return row;
- }
- inline QWidget *makeConvRow(const Conv &c, QWidget *parent)
- {
- auto *row = new QWidget(parent);
- row->setFixedHeight(68);
- row->setMinimumWidth(0);
- row->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed);
- row->setAttribute(Qt::WA_TranslucentBackground);
- row->setStyleSheet(QStringLiteral("background:transparent;"));
- auto *grid = new QGridLayout(row);
- grid->setContentsMargins(12, 10, 12, 10);
- grid->setHorizontalSpacing(10);
- grid->setVerticalSpacing(3);
- grid->setColumnStretch(1, 1);
- grid->setColumnMinimumWidth(1, 0);
- auto *avatarBox = new QWidget(row);
- avatarBox->setFixedSize(44, 44);
- auto *avatar = new QLabel(avatarBox);
- avatar->setGeometry(0, 0, 40, 40);
- avatar->setPixmap(c.convType == ConvGroup
- ? groupIcon(40)
- : AvatarCache::instance().pixmap(c.uid, c.avatarVer, c.name, 40));
- avatar->setScaledContents(false);
- if (c.convType == ConvGroup)
- AvatarCache::instance().applyToGroup(avatar, c.uid, c.avatarVer, c.name, 40);
- else
- AvatarCache::instance().applyTo(avatar, c.uid, c.avatarVer, c.name, 40);
- if (c.online)
- {
- auto *dot = makeOnlineDot(avatarBox, true);
- dot->setFixedSize(10, 10);
- dot->move(30, 30);
- dot->setStyleSheet(QStringLiteral(
- "background:#07C160; border:2px solid #FFFFFF; border-radius:5px;"));
- dot->raise();
- }
- if (c.unread > 0)
- {
- auto *badge = new QLabel(c.unread > 99 ? QStringLiteral("99") : QString::number(c.unread), avatarBox);
- badge->setObjectName(QStringLiteral("unreadBadge"));
- badge->setAlignment(Qt::AlignCenter);
- badge->setFixedSize(16, 16);
- badge->move(28, 0);
- badge->setStyleSheet(QStringLiteral(
- "background:#FA5151; color:#FFFFFF; font-size:10px; border:none; border-radius:8px;"));
- badge->raise();
- }
- auto *nameRow = new QWidget(row);
- nameRow->setMinimumWidth(0);
- nameRow->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Preferred);
- nameRow->setStyleSheet(QStringLiteral("background:transparent;"));
- auto *nameLay = new QHBoxLayout(nameRow);
- nameLay->setContentsMargins(0, 0, 0, 0);
- nameLay->setSpacing(6);
- auto *name = new ElideLabel(
- c.convType == ConvGroup ? groupTitle(c.name, c.memberCount) : c.name, nameRow);
- name->setStyleSheet(QStringLiteral("color:#191919; font-size:14px; background:transparent;"));
- nameLay->addWidget(name, 1, Qt::AlignVCenter);
- auto *prevWrap = new QWidget(row);
- prevWrap->setMinimumWidth(0);
- prevWrap->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Preferred);
- prevWrap->setStyleSheet(QStringLiteral("background:transparent;"));
- auto *prevLay = new QHBoxLayout(prevWrap);
- prevLay->setContentsMargins(0, 0, 0, 0);
- prevLay->setSpacing(4);
- QString previewText = c.preview;
- int iconKind = -1;
- if (previewText.startsWith(QStringLiteral("[表情]")))
- {
- iconKind = 0;
- previewText = previewText.mid(4);
- if (previewText.isEmpty())
- previewText = QStringLiteral("表情");
- }
- else if (previewText.startsWith(QStringLiteral("[图片]")))
- {
- iconKind = 1;
- previewText = previewText.mid(4);
- if (previewText.isEmpty())
- previewText = QStringLiteral("图片");
- }
- else if (previewText.startsWith(QStringLiteral("[视频]")))
- {
- iconKind = 3;
- previewText = previewText.mid(4);
- if (previewText.isEmpty())
- previewText = QStringLiteral("视频");
- }
- else if (previewText.startsWith(QStringLiteral("[文件]")))
- {
- iconKind = 2;
- previewText = previewText.mid(4).trimmed();
- if (previewText.isEmpty())
- previewText = QStringLiteral("文件");
- }
- if (iconKind >= 0)
- {
- auto *ic = new QLabel(prevWrap);
- ic->setFixedSize(14, 14);
- ic->setPixmap(chatToolIcon(iconKind, QColor("#9A9A9A"), 14));
- ic->setStyleSheet(QStringLiteral("background:transparent; border:none;"));
- prevLay->addWidget(ic, 0, Qt::AlignVCenter);
- }
- auto *preview = new ElideLabel(previewText, prevWrap);
- preview->setStyleSheet(QStringLiteral("color:#9A9A9A; font-size:12px; background:transparent;"));
- prevLay->addWidget(preview, 1);
- auto *time = new QLabel(c.time, row);
- time->setStyleSheet(QStringLiteral("color:#B2B2B2; font-size:11px; background:transparent;"));
- time->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
- time->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred);
- time->setMinimumWidth(time->fontMetrics().horizontalAdvance(
- c.time.isEmpty() ? QStringLiteral("00:00") : c.time));
- grid->addWidget(avatarBox, 0, 0, 2, 1, Qt::AlignVCenter);
- grid->addWidget(nameRow, 0, 1);
- grid->addWidget(time, 0, 2, Qt::AlignRight | Qt::AlignVCenter);
- grid->addWidget(prevWrap, 1, 1, 1, 2);
- return row;
- }
- } // namespace ClientInternal
|