14 changed files with 496 additions and 32 deletions
@ -0,0 +1,60 @@
|
||||
#include "accountdelegate.h" |
||||
#include "memberlistmodel.h" |
||||
#include <QPainter> |
||||
#include <QFont> |
||||
|
||||
AccountDelegate::AccountDelegate(QObject *parent) |
||||
: QStyledItemDelegate(parent) {} |
||||
|
||||
void AccountDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, |
||||
const QModelIndex &index) const { |
||||
painter->save(); |
||||
|
||||
if (option.state & QStyle::State_Selected) |
||||
painter->fillRect(option.rect, option.palette.highlight()); |
||||
else if (option.state & QStyle::State_MouseOver) |
||||
painter->fillRect(option.rect, QColor(240, 240, 240)); |
||||
|
||||
const int margin = 4; |
||||
const int iconSz = 24; |
||||
|
||||
QIcon icon = qvariant_cast<QIcon>(index.data(Qt::DecorationRole)); |
||||
if (!icon.isNull()) |
||||
icon.paint(painter, option.rect.left() + margin, option.rect.top() + (option.rect.height() - iconSz) / 2, iconSz, iconSz); |
||||
|
||||
const int x = option.rect.left() + iconSz + margin * 3; |
||||
const int w = option.rect.right() - x - margin; |
||||
const int yMid = option.rect.top() + option.rect.height() / 2; |
||||
|
||||
QFont nameFont = painter->font(); |
||||
nameFont.setBold(true); |
||||
nameFont.setPixelSize(13); |
||||
painter->setFont(nameFont); |
||||
|
||||
QColor textColor = option.palette.color(QPalette::Text); |
||||
if (option.state & QStyle::State_Selected) textColor = option.palette.color(QPalette::HighlightedText); |
||||
painter->setPen(textColor); |
||||
|
||||
QString name = index.data(Qt::DisplayRole).toString(); |
||||
painter->drawText(QRect(x, option.rect.top() + 2, w, yMid - option.rect.top() - 2), |
||||
Qt::AlignLeft | Qt::AlignBottom, name); |
||||
|
||||
QFont subFont = painter->font(); |
||||
subFont.setBold(false); |
||||
subFont.setPixelSize(10); |
||||
painter->setFont(subFont); |
||||
painter->setPen(QColor(130, 130, 130)); |
||||
|
||||
QString sub = index.data(Qt::UserRole + 1).toString(); |
||||
if (!sub.isEmpty()) |
||||
painter->drawText(QRect(x, yMid + 2, w, option.rect.bottom() - yMid - 2), |
||||
Qt::AlignLeft | Qt::AlignTop, sub); |
||||
|
||||
painter->restore(); |
||||
} |
||||
|
||||
QSize AccountDelegate::sizeHint(const QStyleOptionViewItem &option, |
||||
const QModelIndex &index) const { |
||||
(void)option; (void)index; |
||||
return QSize(160, 36); |
||||
} |
||||
@ -0,0 +1,14 @@
|
||||
#pragma once |
||||
|
||||
#include <QStyledItemDelegate> |
||||
|
||||
class AccountDelegate : public QStyledItemDelegate { |
||||
Q_OBJECT |
||||
public: |
||||
explicit AccountDelegate(QObject *parent = nullptr); |
||||
|
||||
void paint(QPainter *painter, const QStyleOptionViewItem &option, |
||||
const QModelIndex &index) const override; |
||||
QSize sizeHint(const QStyleOptionViewItem &option, |
||||
const QModelIndex &index) const override; |
||||
}; |
||||
@ -0,0 +1,168 @@
|
||||
#include "memberpropsdialog.h" |
||||
#include "../db/db_manager.h" |
||||
#include <QVBoxLayout> |
||||
#include <QHBoxLayout> |
||||
#include <QPushButton> |
||||
#include <QLabel> |
||||
#include <QMessageBox> |
||||
|
||||
#include <openssl/evp.h> |
||||
|
||||
MemberPropsDialog::MemberPropsDialog(DbManager* db, const QString& channelId, |
||||
quint64 nodeId, const QString& memberName, |
||||
QWidget* parent) |
||||
: QDialog(parent) |
||||
, m_db(db) |
||||
, m_channelId(channelId) |
||||
, m_nodeId(nodeId) |
||||
{ |
||||
setWindowTitle("Member Properties"); |
||||
setMinimumWidth(340); |
||||
setModal(true); |
||||
|
||||
// читаем ed25519_privkey канала
|
||||
auto chs = m_db->getChannels(); |
||||
for (const auto& ch : chs) { |
||||
if (ch.channelId == channelId) { |
||||
m_edPrivkey = ch.ed25519_privkey; |
||||
break; |
||||
} |
||||
} |
||||
bool haveKey = (m_edPrivkey.size() == 32); |
||||
|
||||
// читаем текущий adm_tags
|
||||
bool isAdmin = false, isSuper = false; |
||||
if (m_db->m_db) { |
||||
QString tbl = m_db->peersTableName(channelId); |
||||
QByteArray sql = QString( |
||||
"SELECT adm_tags FROM \"%1\" WHERE node_id=?" |
||||
).arg(tbl).toUtf8(); |
||||
sqlite3_stmt* st = nullptr; |
||||
if (sqlite3_prepare_v2(m_db->m_db, sql.constData(), -1, &st, nullptr) == SQLITE_OK) { |
||||
sqlite3_bind_int64(st, 1, (sqlite3_int64)nodeId); |
||||
if (sqlite3_step(st) == SQLITE_ROW) { |
||||
const char* tags = (const char*)sqlite3_column_text(st, 0); |
||||
if (tags) { |
||||
if (strstr(tags, "admin=yes")) isAdmin = true; |
||||
if (strstr(tags, "supernode=yes")) isSuper = true; |
||||
} |
||||
} |
||||
sqlite3_finalize(st); |
||||
} |
||||
// страховка: добавить колонки если нет
|
||||
QByteArray alt = QString( |
||||
"ALTER TABLE \"%1\" ADD COLUMN adm_tags TEXT" |
||||
).arg(tbl).toUtf8(); |
||||
sqlite3_exec(m_db->m_db, alt.constData(), nullptr, nullptr, nullptr); |
||||
alt = QString( |
||||
"ALTER TABLE \"%1\" ADD COLUMN adm_tags_sig BLOB" |
||||
).arg(tbl).toUtf8(); |
||||
sqlite3_exec(m_db->m_db, alt.constData(), nullptr, nullptr, nullptr); |
||||
} |
||||
|
||||
auto* layout = new QVBoxLayout(this); |
||||
layout->setSpacing(12); |
||||
layout->setContentsMargins(20, 16, 20, 16); |
||||
|
||||
auto* title = new QLabel( |
||||
QString("Node: 0x%1").arg(nodeId, 16, 16, QChar('0')) + |
||||
(memberName.isEmpty() ? "" : QString(" (%1)").arg(memberName)), |
||||
this); |
||||
title->setStyleSheet("font-weight: bold; font-size: 13px;"); |
||||
layout->addWidget(title); |
||||
|
||||
m_adminCb = new QCheckBox("Admin", this); |
||||
m_adminCb->setChecked(isAdmin); |
||||
m_adminCb->setEnabled(haveKey); |
||||
layout->addWidget(m_adminCb); |
||||
|
||||
m_superCb = new QCheckBox("Supernode", this); |
||||
m_superCb->setChecked(isSuper); |
||||
m_superCb->setEnabled(haveKey); |
||||
layout->addWidget(m_superCb); |
||||
|
||||
if (!haveKey) { |
||||
auto* hint = new QLabel("(requires channel private key)", this); |
||||
hint->setStyleSheet("color: gray; font-size: 11px;"); |
||||
layout->addWidget(hint); |
||||
} |
||||
|
||||
layout->addStretch(); |
||||
|
||||
auto* btnLayout = new QHBoxLayout(); |
||||
btnLayout->addStretch(); |
||||
|
||||
auto* cancelBtn = new QPushButton("Cancel", this); |
||||
cancelBtn->setFixedWidth(90); |
||||
cancelBtn->setStyleSheet( |
||||
"QPushButton { border: 1px solid palette(mid); border-radius: 4px;" |
||||
" padding: 6px 16px; font-size: 13px; background: palette(button); }" |
||||
"QPushButton:hover { background: palette(light); }"); |
||||
connect(cancelBtn, &QPushButton::clicked, this, &QDialog::reject); |
||||
btnLayout->addWidget(cancelBtn); |
||||
|
||||
auto* saveBtn = new QPushButton("Save", this); |
||||
saveBtn->setFixedWidth(90); |
||||
saveBtn->setDefault(true); |
||||
saveBtn->setEnabled(haveKey); |
||||
saveBtn->setStyleSheet( |
||||
"QPushButton { border: none; border-radius: 4px;" |
||||
" padding: 6px 16px; font-size: 13px; font-weight: bold;" |
||||
" background: #3390EC; color: white; }" |
||||
"QPushButton:hover { background: #2B7ED4; }" |
||||
"QPushButton:disabled { background: palette(mid); color: palette(midlight); }"); |
||||
connect(saveBtn, &QPushButton::clicked, this, &MemberPropsDialog::onSave); |
||||
btnLayout->addWidget(saveBtn); |
||||
|
||||
layout->addLayout(btnLayout); |
||||
} |
||||
|
||||
void MemberPropsDialog::onSave() { |
||||
if (m_edPrivkey.size() != 32 || !m_db->m_db) return; |
||||
|
||||
// собираем adm_tags
|
||||
QString tags; |
||||
if (m_adminCb->isChecked()) tags += "admin=yes"; |
||||
if (m_superCb->isChecked()) { |
||||
if (!tags.isEmpty()) tags += ","; |
||||
tags += "supernode=yes"; |
||||
} |
||||
|
||||
// подписываем: tags || node_id(8 LE)
|
||||
QByteArray msg = tags.toUtf8(); |
||||
uint64_t le = htole64(m_nodeId); |
||||
msg.append((const char*)&le, 8); |
||||
|
||||
uint8_t sig[64] = {}; |
||||
EVP_PKEY* pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519, nullptr, |
||||
(const uint8_t*)m_edPrivkey.constData(), 32); |
||||
if (pkey) { |
||||
EVP_MD_CTX* mdctx = EVP_MD_CTX_new(); |
||||
if (mdctx) { |
||||
size_t siglen = 64; |
||||
if (EVP_DigestSignInit(mdctx, nullptr, nullptr, nullptr, pkey) == 1) |
||||
EVP_DigestSign(mdctx, sig, &siglen, (const uint8_t*)msg.constData(), msg.size()); |
||||
EVP_MD_CTX_free(mdctx); |
||||
} |
||||
EVP_PKEY_free(pkey); |
||||
} |
||||
|
||||
// пишем в БД
|
||||
QString tbl = m_db->peersTableName(m_channelId); |
||||
QByteArray sql = QString( |
||||
"UPDATE \"%1\" SET adm_tags=?, adm_tags_sig=?, node_type=? WHERE node_id=?" |
||||
).arg(tbl).toUtf8(); |
||||
sqlite3_stmt* st = nullptr; |
||||
if (sqlite3_prepare_v2(m_db->m_db, sql.constData(), -1, &st, nullptr) == SQLITE_OK) { |
||||
sqlite3_bind_text(st, 1, tags.isEmpty() ? nullptr : tags.toUtf8().constData(), |
||||
-1, tags.isEmpty() ? SQLITE_STATIC : SQLITE_TRANSIENT); |
||||
sqlite3_bind_blob(st, 2, sig, 64, SQLITE_TRANSIENT); |
||||
int nt = m_superCb->isChecked() ? 4 : 1; |
||||
sqlite3_bind_int(st, 3, nt); |
||||
sqlite3_bind_int64(st, 4, (sqlite3_int64)m_nodeId); |
||||
sqlite3_step(st); |
||||
sqlite3_finalize(st); |
||||
} |
||||
|
||||
accept(); |
||||
} |
||||
@ -0,0 +1,27 @@
|
||||
#pragma once |
||||
|
||||
#include <QDialog> |
||||
#include <QCheckBox> |
||||
#include <QString> |
||||
|
||||
class DbManager; |
||||
|
||||
class MemberPropsDialog : public QDialog { |
||||
Q_OBJECT |
||||
public: |
||||
explicit MemberPropsDialog(DbManager* db, const QString& channelId, |
||||
quint64 nodeId, const QString& memberName, |
||||
QWidget* parent = nullptr); |
||||
|
||||
private slots: |
||||
void onSave(); |
||||
|
||||
private: |
||||
DbManager* m_db; |
||||
QString m_channelId; |
||||
quint64 m_nodeId; |
||||
QByteArray m_edPrivkey; |
||||
|
||||
QCheckBox* m_adminCb; |
||||
QCheckBox* m_superCb; |
||||
}; |
||||
Loading…
Reference in new issue