- etcp_metrics struct: 12-bucket RTT histogram, 11-bucket loss histogram, 10-min snapshots (>=200 pkts) - Total counters: packets/bytes sent/rcvd, losses (retrans + probe gap detection) - CSV append to <stats_dir>/<peer_id>.dat every 10 min - Ed25519 pubkey exchange in INIT handshake (V3), always present - Signed metrics exchange via ETCP_SECTION_METRICS (0x0A) - Received metrics saved as from_<peer_id>.dat with signature hex - on_metrics_rcvd callback in ETCP_BINDINGS - stats_dir derived from config file path, mkdir'd at instance init - Simplified versioning: ETCP_INIT_REQ_SIZE / ETCP_INIT_RESP_SIZE via sizeof
137 lines
4.8 KiB
C++
137 lines
4.8 KiB
C++
#include "channellist.h"
|
|
#include "channeldelegate.h"
|
|
#include "../db/db_manager.h"
|
|
#include <QVBoxLayout>
|
|
#include <QHBoxLayout>
|
|
#include <QLabel>
|
|
#include <QPushButton>
|
|
#include <QPainter>
|
|
#include <QPixmap>
|
|
#include <QMenu>
|
|
|
|
static QIcon makeChannelIcon(const QColor &color, const QString &letter) {
|
|
QPixmap pix(32, 32);
|
|
pix.fill(Qt::transparent);
|
|
QPainter p(&pix);
|
|
p.setRenderHint(QPainter::Antialiasing);
|
|
p.setBrush(color);
|
|
p.setPen(Qt::NoPen);
|
|
p.drawRoundedRect(0, 0, 32, 32, 8, 8);
|
|
p.setPen(Qt::white);
|
|
QFont f = p.font();
|
|
f.setBold(true);
|
|
f.setPointSize(14);
|
|
p.setFont(f);
|
|
p.drawText(QRect(0, 0, 32, 32), Qt::AlignCenter, letter);
|
|
p.end();
|
|
return QIcon(pix);
|
|
}
|
|
|
|
ChannelList::ChannelList(DbManager* db, QWidget *parent)
|
|
: QWidget(parent)
|
|
, m_listView(new QListView(this))
|
|
, m_model(new QStandardItemModel(this))
|
|
, m_db(db)
|
|
{
|
|
auto *layout = new QVBoxLayout(this);
|
|
layout->setContentsMargins(0, 0, 0, 0);
|
|
layout->setSpacing(0);
|
|
|
|
auto *header = new QLabel("Channels", this);
|
|
header->setAlignment(Qt::AlignCenter);
|
|
header->setStyleSheet("font-weight: bold; padding: 8px; background: palette(window);");
|
|
layout->addWidget(header);
|
|
|
|
m_listView->setModel(m_model);
|
|
m_listView->setItemDelegate(new ChannelDelegate(this));
|
|
m_listView->setEditTriggers(QAbstractItemView::NoEditTriggers);
|
|
m_listView->setSelectionMode(QAbstractItemView::SingleSelection);
|
|
m_listView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
|
m_listView->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
|
|
m_listView->setContextMenuPolicy(Qt::CustomContextMenu);
|
|
layout->addWidget(m_listView);
|
|
|
|
auto *footer = new QWidget(this);
|
|
auto *fl = new QHBoxLayout(footer);
|
|
fl->setContentsMargins(6, 4, 6, 4);
|
|
fl->setSpacing(6);
|
|
|
|
auto *gearBtn = new QPushButton(QString::fromUtf8("\xE2\x9A\x99"), footer);
|
|
gearBtn->setFixedSize(28, 28);
|
|
gearBtn->setStyleSheet(
|
|
"QPushButton { border: none; font-size: 18px; background: transparent; color: gray; }"
|
|
"QPushButton:hover { color: palette(text); }");
|
|
fl->addWidget(gearBtn);
|
|
|
|
connect(gearBtn, &QPushButton::clicked, this, [this] { emit settingsRequested(); });
|
|
|
|
fl->addStretch();
|
|
|
|
auto *plusBtn = new QPushButton("+", footer);
|
|
plusBtn->setFixedSize(28, 28);
|
|
plusBtn->setStyleSheet(
|
|
"QPushButton { border: 1.5px solid #3390EC; border-radius: 14px;"
|
|
" font-size: 18px; font-weight: bold; color: #3390EC; background: transparent; }"
|
|
"QPushButton:hover { background: #3390EC; color: white; }");
|
|
connect(plusBtn, &QPushButton::clicked, this, [this] { emit joinRequested(); });
|
|
fl->addWidget(plusBtn);
|
|
|
|
layout->addWidget(footer);
|
|
|
|
connect(m_listView->selectionModel(), &QItemSelectionModel::currentChanged,
|
|
this, [this](const QModelIndex ¤t, const QModelIndex &) {
|
|
if (current.isValid()) {
|
|
QString cid = current.data(ChannelChannelIdRole).toString();
|
|
emit channelSelected(cid);
|
|
}
|
|
});
|
|
connect(m_listView, &QListView::customContextMenuRequested,
|
|
this, &ChannelList::onContextMenu);
|
|
}
|
|
|
|
void ChannelList::loadChannels() {
|
|
m_model->clear();
|
|
if (!m_db || !m_db->isOpen()) return;
|
|
|
|
auto channels = m_db->getChannels();
|
|
static const QColor colors[] = {
|
|
QColor(74,144,226), QColor(126,211,33), QColor(245,166,35),
|
|
QColor(208,2,27), QColor(144,19,254), QColor(0,184,148)
|
|
};
|
|
int ci = 0;
|
|
|
|
for (const auto& ch : channels) {
|
|
auto letter = ch.name.mid(0, 1).toUpper();
|
|
if (letter.isEmpty()) letter = "#";
|
|
auto *item = new QStandardItem();
|
|
item->setData(ch.name, ChannelNameRole);
|
|
item->setData(ch.channelId, ChannelChannelIdRole);
|
|
item->setData(QString(), ChannelLastMessageRole);
|
|
item->setData(makeChannelIcon(colors[ci % 6], letter), ChannelIconRole);
|
|
m_model->appendRow(item);
|
|
ci++;
|
|
}
|
|
|
|
if (m_model->rowCount() > 0)
|
|
m_listView->setCurrentIndex(m_model->index(0, 0));
|
|
}
|
|
|
|
void ChannelList::selectChannel(const QString& channelId) {
|
|
for (int row = 0; row < m_model->rowCount(); row++) {
|
|
if (m_model->item(row)->data(ChannelChannelIdRole).toString() == channelId) {
|
|
m_listView->setCurrentIndex(m_model->index(row, 0));
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void ChannelList::onContextMenu(const QPoint& pos) {
|
|
QModelIndex index = m_listView->indexAt(pos);
|
|
if (!index.isValid()) return;
|
|
QString cid = index.data(ChannelChannelIdRole).toString();
|
|
QMenu menu(this);
|
|
QAction *inviteAction = menu.addAction("Пригласить");
|
|
if (menu.exec(m_listView->viewport()->mapToGlobal(pos)) == inviteAction)
|
|
emit inviteRequested(cid);
|
|
}
|