| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026 |
- #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 <functional>
- #include <QAction>
- #include <QApplication>
- #include <QCheckBox>
- #include <QColor>
- #include <QContextMenuEvent>
- #include <QDate>
- #include <QDateTime>
- #include <QDesktopServices>
- #include <QEvent>
- #include <QFile>
- #include <QFileDialog>
- #include <QFontMetrics>
- #include <QFrame>
- #include <QGridLayout>
- #include <QHBoxLayout>
- #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 <QProgressBar>
- #include <QPushButton>
- #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
- };
- 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;
- QString name;
- QString preview;
- QString time;
- int unread = 0;
- int avatarVer = 0;
- bool online = false;
- };
- 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 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 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 void refreshRowFileAction(QWidget *row)
- {
- if (!row)
- return;
- if (auto *fb = row->findChild<FileBubble *>(QStringLiteral("fileBubble")))
- fb->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
- {
- 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.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;
- 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(AvatarCache::instance().pixmap(c.uid, c.avatarVer, c.name, 40));
- avatar->setScaledContents(false);
- 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.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 = 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
|