chatgui: local nicknames per channel member — edit via MemberPropsDialog, override display everywhere
This commit is contained in:
1 parent
373a2e4687
commit
85bdc00aaf
9 files changed
+87
-16
No files matched your search
@@ -214,7 +214,7 @@ int DbManager::getChannelMemberCount(const QString& chId) const {
|
||||
QList<ChannelMember> DbManager::getChannelMembers(const QString& chId, int offset, int limit) const {
|
||||
QList<ChannelMember> list;
|
||||
QString sql = QStringLiteral(
|
||||
"SELECT p.node_id, COALESCE(n.online,0), COALESCE(n.name,'')"
|
||||
"SELECT p.node_id, COALESCE(n.online,0), COALESCE(n.name,''), COALESCE(p.local_nick,'')"
|
||||
" FROM \"%1\" p LEFT JOIN nodes n ON p.node_id=n.node_id"
|
||||
" ORDER BY n.online DESC, p.node_id ASC LIMIT ? OFFSET ?"
|
||||
).arg(peersTableName(chId));
|
||||
@@ -227,12 +227,27 @@ QList<ChannelMember> DbManager::getChannelMembers(const QString& chId, int offse
|
||||
m.nodeId = (quint64)sqlite3_column_int64(stmt, 0);
|
||||
m.online = sqlite3_column_int(stmt, 1) != 0;
|
||||
m.name = colText(stmt, 2);
|
||||
m.localNick = colText(stmt, 3);
|
||||
list.append(m);
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
return list;
|
||||
}
|
||||
|
||||
QMap<quint64, QString> DbManager::getLocalNicknames(const QString& chId) const {
|
||||
QMap<quint64, QString> m;
|
||||
if (!m_db) return m;
|
||||
QString sql = QStringLiteral(
|
||||
"SELECT node_id, local_nick FROM \"%1\" WHERE local_nick IS NOT NULL AND local_nick != ''"
|
||||
).arg(peersTableName(chId));
|
||||
sqlite3_stmt* stmt = prepareOrNull(sql.toUtf8().constData());
|
||||
if (!stmt) return m;
|
||||
while (sqlite3_step(stmt) == SQLITE_ROW)
|
||||
m.insert((quint64)sqlite3_column_int64(stmt, 0), colText(stmt, 1));
|
||||
sqlite3_finalize(stmt);
|
||||
return m;
|
||||
}
|
||||
|
||||
void DbManager::cleanOrphanedPeers(const QString& chId) const {
|
||||
if (!m_db) return;
|
||||
QString sql = QStringLiteral(
|
||||
|
||||
@@ -49,6 +49,7 @@ struct ChannelMember {
|
||||
quint64 nodeId = 0;
|
||||
bool online = false;
|
||||
QString name; // from nodes.name, empty if not set
|
||||
QString localNick; // from peers.local_nick override, empty if not set
|
||||
};
|
||||
|
||||
struct NodeAddr {
|
||||
@@ -96,6 +97,7 @@ public:
|
||||
QList<ChannelMember> getChannelMembers(const QString& chId, int offset, int limit) const;
|
||||
void cleanOrphanedPeers(const QString& chId) const;
|
||||
QString getDisplayName(quint64 nodeId) const;
|
||||
QMap<quint64, QString> getLocalNicknames(const QString& chId) const;
|
||||
|
||||
/* ── Узлы ── */
|
||||
QByteArray loadNodeInfo(quint64 nodeId) const;
|
||||
|
||||
@@ -130,6 +130,21 @@ void AccountList::showMemberDetail(quint64 nodeId) {
|
||||
if (m_detailName.isEmpty())
|
||||
m_detailName = QString("0x%1").arg(nodeId, 8, 16, QChar('0')).right(4).toUpper();
|
||||
|
||||
/* prefer local nickname */
|
||||
QString tbl = m_db->peersTableName(m_channelId);
|
||||
sqlite3_stmt* nickSt = nullptr;
|
||||
QByteArray nickSql = QString(
|
||||
"SELECT local_nick FROM \"%1\" WHERE node_id=?"
|
||||
).arg(tbl).toUtf8();
|
||||
if (sqlite3_prepare_v2(d, nickSql.constData(), -1, &nickSt, nullptr) == SQLITE_OK) {
|
||||
sqlite3_bind_int64(nickSt, 1, (sqlite3_int64)nodeId);
|
||||
if (sqlite3_step(nickSt) == SQLITE_ROW) {
|
||||
QString n = DbManager::colText(nickSt, 0);
|
||||
if (!n.isEmpty()) m_detailName = n;
|
||||
}
|
||||
sqlite3_finalize(nickSt);
|
||||
}
|
||||
|
||||
sqlite3_stmt* na = nullptr;
|
||||
sqlite3_prepare_v2(d,
|
||||
"SELECT family, protocol, address, port FROM node_addresses WHERE node_id=? ORDER BY family, port",
|
||||
|
||||
@@ -18,7 +18,7 @@ QVariant MemberListModel::data(const QModelIndex& index, int role) const {
|
||||
|
||||
auto it = m_cache.find(index.row());
|
||||
if (it != m_cache.end()) {
|
||||
if (role == Qt::DisplayRole) return it->name;
|
||||
if (role == Qt::DisplayRole) return it->localNick.isEmpty() ? it->name : it->localNick;
|
||||
if (role == Qt::DecorationRole) return QVariant::fromValue(QIcon(it->avatar));
|
||||
if (role == Qt::UserRole + 1) {
|
||||
QString sub;
|
||||
@@ -93,6 +93,7 @@ void MemberListModel::loadBatch(int from, int count) {
|
||||
item.online = members[i].online;
|
||||
QString shortId = QString("%1").arg(members[i].nodeId, 8, 16, QChar('0')).right(4).toUpper();
|
||||
item.name = members[i].name.isEmpty() ? shortId : members[i].name;
|
||||
item.localNick = members[i].localNick;
|
||||
QColor c = members[i].online ? QColor("#4CAF50") : QColor("#9E9E9E");
|
||||
item.avatar = makeAvatar(c, item.name[0]);
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ struct MemberListItem {
|
||||
bool online = false;
|
||||
QPixmap avatar;
|
||||
QString name;
|
||||
QString localNick;
|
||||
int rtt = -1; // ms (min из node_addresses, -1 если нет)
|
||||
int natType = -1; // addr_type: 0=NET, 1=DIR, 2=EIM, 3=STR
|
||||
};
|
||||
|
||||
@@ -21,7 +21,6 @@ MemberPropsDialog::MemberPropsDialog(DbManager* db, const QString& channelId,
|
||||
setMinimumWidth(340);
|
||||
setModal(true);
|
||||
|
||||
// читаем ed25519_privkey канала
|
||||
auto chs = m_db->getChannels();
|
||||
for (const auto& ch : chs) {
|
||||
if (ch.channelId == channelId) {
|
||||
@@ -30,13 +29,14 @@ MemberPropsDialog::MemberPropsDialog(DbManager* db, const QString& channelId,
|
||||
}
|
||||
}
|
||||
bool haveKey = (m_edPrivkey.size() == 32);
|
||||
bool isOwn = (m_nodeId == m_db->myNodeId());
|
||||
|
||||
// читаем текущий adm_tags
|
||||
bool isAdmin = false, isSuper = false;
|
||||
QString curNick;
|
||||
if (m_db->m_db) {
|
||||
QString tbl = m_db->peersTableName(channelId);
|
||||
QByteArray sql = QString(
|
||||
"SELECT adm_tags FROM \"%1\" WHERE node_id=?"
|
||||
"SELECT adm_tags, local_nick 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) {
|
||||
@@ -47,10 +47,10 @@ MemberPropsDialog::MemberPropsDialog(DbManager* db, const QString& channelId,
|
||||
if (strstr(tags, "admin=yes")) isAdmin = true;
|
||||
if (strstr(tags, "supernode=yes")) isSuper = true;
|
||||
}
|
||||
curNick = DbManager::colText(st, 1);
|
||||
}
|
||||
sqlite3_finalize(st);
|
||||
}
|
||||
// страховка: добавить колонки если нет
|
||||
QByteArray alt = QString(
|
||||
"ALTER TABLE \"%1\" ADD COLUMN adm_tags TEXT"
|
||||
).arg(tbl).toUtf8();
|
||||
@@ -59,6 +59,10 @@ MemberPropsDialog::MemberPropsDialog(DbManager* db, const QString& channelId,
|
||||
"ALTER TABLE \"%1\" ADD COLUMN adm_tags_sig BLOB"
|
||||
).arg(tbl).toUtf8();
|
||||
sqlite3_exec(m_db->m_db, alt.constData(), nullptr, nullptr, nullptr);
|
||||
alt = QString(
|
||||
"ALTER TABLE \"%1\" ADD COLUMN local_nick TEXT"
|
||||
).arg(tbl).toUtf8();
|
||||
sqlite3_exec(m_db->m_db, alt.constData(), nullptr, nullptr, nullptr);
|
||||
}
|
||||
|
||||
auto* layout = new QVBoxLayout(this);
|
||||
@@ -72,6 +76,12 @@ MemberPropsDialog::MemberPropsDialog(DbManager* db, const QString& channelId,
|
||||
title->setStyleSheet("font-weight: bold; font-size: 13px;");
|
||||
layout->addWidget(title);
|
||||
|
||||
m_nickEdit = new QLineEdit(this);
|
||||
m_nickEdit->setPlaceholderText("Local nickname...");
|
||||
m_nickEdit->setText(curNick);
|
||||
m_nickEdit->setEnabled(!isOwn);
|
||||
layout->addWidget(m_nickEdit);
|
||||
|
||||
m_adminCb = new QCheckBox("Admin", this);
|
||||
m_adminCb->setChecked(isAdmin);
|
||||
m_adminCb->setEnabled(haveKey);
|
||||
@@ -83,7 +93,7 @@ MemberPropsDialog::MemberPropsDialog(DbManager* db, const QString& channelId,
|
||||
layout->addWidget(m_superCb);
|
||||
|
||||
if (!haveKey) {
|
||||
auto* hint = new QLabel("(requires channel private key)", this);
|
||||
auto* hint = new QLabel("(Admin/Super require channel private key)", this);
|
||||
hint->setStyleSheet("color: gray; font-size: 11px;");
|
||||
layout->addWidget(hint);
|
||||
}
|
||||
@@ -105,7 +115,6 @@ MemberPropsDialog::MemberPropsDialog(DbManager* db, const QString& channelId,
|
||||
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;"
|
||||
@@ -119,9 +128,26 @@ MemberPropsDialog::MemberPropsDialog(DbManager* db, const QString& channelId,
|
||||
}
|
||||
|
||||
void MemberPropsDialog::onSave() {
|
||||
if (m_edPrivkey.size() != 32 || !m_db->m_db) return;
|
||||
if (!m_db->m_db) return;
|
||||
QString tbl = m_db->peersTableName(m_channelId);
|
||||
|
||||
/* local_nick — always save (unsigned, local only) */
|
||||
QString nick = m_nickEdit->text().trimmed();
|
||||
QByteArray nickSql = QString(
|
||||
"UPDATE \"%1\" SET local_nick=? WHERE node_id=?"
|
||||
).arg(tbl).toUtf8();
|
||||
sqlite3_stmt* ns = nullptr;
|
||||
if (sqlite3_prepare_v2(m_db->m_db, nickSql.constData(), -1, &ns, nullptr) == SQLITE_OK) {
|
||||
sqlite3_bind_text(ns, 1, nick.isEmpty() ? nullptr : nick.toUtf8().constData(),
|
||||
-1, nick.isEmpty() ? SQLITE_STATIC : SQLITE_TRANSIENT);
|
||||
sqlite3_bind_int64(ns, 2, (sqlite3_int64)m_nodeId);
|
||||
sqlite3_step(ns);
|
||||
sqlite3_finalize(ns);
|
||||
}
|
||||
|
||||
/* signed fields — only if key available */
|
||||
if (m_edPrivkey.size() != 32) { accept(); return; }
|
||||
|
||||
// собираем adm_tags
|
||||
QString tags;
|
||||
if (m_adminCb->isChecked()) tags += "admin=yes";
|
||||
if (m_superCb->isChecked()) {
|
||||
@@ -129,7 +155,6 @@ void MemberPropsDialog::onSave() {
|
||||
tags += "supernode=yes";
|
||||
}
|
||||
|
||||
// подписываем: tags || node_id(8 LE)
|
||||
QByteArray msg = tags.toUtf8();
|
||||
uint64_t le = htole64(m_nodeId);
|
||||
msg.append((const char*)&le, 8);
|
||||
@@ -148,8 +173,6 @@ void MemberPropsDialog::onSave() {
|
||||
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();
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <QDialog>
|
||||
#include <QCheckBox>
|
||||
#include <QLineEdit>
|
||||
#include <QString>
|
||||
|
||||
class DbManager;
|
||||
@@ -22,6 +23,7 @@ private:
|
||||
quint64 m_nodeId;
|
||||
QByteArray m_edPrivkey;
|
||||
|
||||
QLineEdit* m_nickEdit;
|
||||
QCheckBox* m_adminCb;
|
||||
QCheckBox* m_superCb;
|
||||
};
|
||||
@@ -207,11 +207,13 @@ void MessageList::loadChannel(const QString& channelId) {
|
||||
m_readDebounce->stop();
|
||||
if (!m_db || !m_db->isOpen()) return;
|
||||
|
||||
m_localNicks = m_db->getLocalNicknames(channelId);
|
||||
|
||||
auto msgs = m_db->getMessages(channelId, 100);
|
||||
|
||||
for (const auto& m : msgs) {
|
||||
AccountRow acc = m_db->getAccount(m.authorNodeId);
|
||||
QString author = m_db->getDisplayName(m.authorNodeId);
|
||||
QString author = authorName(m.authorNodeId);
|
||||
QString letter = acc.avatarLetter.isEmpty() ? author.mid(0, 1) : acc.avatarLetter;
|
||||
QColor color(acc.avatarColor);
|
||||
|
||||
@@ -271,12 +273,14 @@ void MessageList::refresh() {
|
||||
auto newMsgs = m_db->getMessagesSince(m_currentChannelId, maxId);
|
||||
if (newMsgs.isEmpty()) return;
|
||||
|
||||
m_localNicks = m_db->getLocalNicknames(m_currentChannelId);
|
||||
|
||||
QScrollBar* sb = m_view->verticalScrollBar();
|
||||
bool wasAtBottom = sb && (sb->value() >= sb->maximum());
|
||||
|
||||
for (const auto& m : newMsgs) {
|
||||
AccountRow acc = m_db->getAccount(m.authorNodeId);
|
||||
QString author = m_db->getDisplayName(m.authorNodeId);
|
||||
QString author = authorName(m.authorNodeId);
|
||||
QString letter = acc.avatarLetter.isEmpty() ? author.mid(0, 1) : acc.avatarLetter;
|
||||
QColor color(acc.avatarColor);
|
||||
if (!m_avatarCache.contains(m.authorNodeId))
|
||||
@@ -302,7 +306,7 @@ void MessageList::addMessage(const QString& channelId, quint64 authorNodeId,
|
||||
if (channelId != m_currentChannelId) return;
|
||||
|
||||
AccountRow acc = m_db ? m_db->getAccount(authorNodeId) : AccountRow();
|
||||
QString author = m_db ? m_db->getDisplayName(authorNodeId) : QString("node_%1").arg(authorNodeId);
|
||||
QString author = m_db ? authorName(authorNodeId) : QString("node_%1").arg(authorNodeId);
|
||||
QString letter = acc.avatarLetter.isEmpty() ? author.mid(0, 1) : acc.avatarLetter;
|
||||
QColor color(acc.avatarColor);
|
||||
auto avatar = makeAvatar(color, letter);
|
||||
@@ -356,6 +360,12 @@ void MessageList::saveChannelState() {
|
||||
if (lastPosMsgId > 0) saveKey("_last_pos", lastPosMsgId);
|
||||
}
|
||||
|
||||
QString MessageList::authorName(quint64 nodeId) const {
|
||||
auto it = m_localNicks.find(nodeId);
|
||||
if (it != m_localNicks.end() && !it->isEmpty()) return *it;
|
||||
return m_db->getDisplayName(nodeId);
|
||||
}
|
||||
|
||||
void MessageList::restoreScrollPosition(const QString& channelId) {
|
||||
if (!m_db || m_model->rowCount() == 0) {
|
||||
return;
|
||||
|
||||
@@ -39,7 +39,9 @@ private:
|
||||
QTimer* m_readDebounce;
|
||||
qint64 m_savedTopMsgId = 0;
|
||||
bool m_wasAtBottom = false;
|
||||
QMap<quint64, QString> m_localNicks;
|
||||
|
||||
QString authorName(quint64 nodeId) const;
|
||||
void restoreScrollPosition(const QString& channelId);
|
||||
void updateReadPosition();
|
||||
};
|
||||
Reference in new issue
Block a user