Browse Source

chatgui: кнопка + для invite в каналах, автокопирование ссылки, автоподключение из буфера

- Замена контекстного меню 'Пригласить' на кнопку + (18x18) справа снизу каждого канала с hover-эффектом
- InviteDialog: авто-копирование ссылки в буфер (Clipboard + Selection) при открытии
- JoinDialog: параметр autoJoinLink — если в буфере валидная utun:// ссылка, диалог сразу стартует подключение
topo_upd
Evgeny 2 months ago
parent
commit
e3286d409f
  1. 30
      tools/chatgui/src/channeldelegate.cpp
  2. 6
      tools/chatgui/src/channeldelegate.h
  3. 55
      tools/chatgui/src/channellist.cpp
  4. 3
      tools/chatgui/src/channellist.h
  5. 11
      tools/chatgui/src/invitedialog.cpp
  6. 8
      tools/chatgui/src/joindialog.cpp
  7. 3
      tools/chatgui/src/joindialog.h
  8. 18
      tools/chatgui/src/mainwindow.cpp

30
tools/chatgui/src/channeldelegate.cpp

@ -5,6 +5,13 @@
ChannelDelegate::ChannelDelegate(QObject *parent)
: QStyledItemDelegate(parent) {}
QRect ChannelDelegate::inviteButtonRect(const QRect &itemRect) {
const int margin = 6;
return QRect(itemRect.right() - margin - InviteBtnSize,
itemRect.bottom() - margin - InviteBtnSize,
InviteBtnSize, InviteBtnSize);
}
void ChannelDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &index) const {
painter->save();
@ -90,7 +97,8 @@ void ChannelDelegate::paint(QPainter *painter, const QStyleOptionViewItem &optio
if (option.state & QStyle::State_Selected) nickColor = nickColor.lighter(140);
QRect msgRect(textRect.left(), nameRect.bottom() + 4,
textRect.width(), option.rect.bottom() - nameRect.bottom() - margin);
textRect.right() - InviteBtnSize - 6 - textRect.left(),
option.rect.bottom() - nameRect.bottom() - margin);
QFontMetrics msgFm(msgFont);
int colon = lastMsg.indexOf(QString::fromUtf8(": "));
@ -117,6 +125,26 @@ void ChannelDelegate::paint(QPainter *painter, const QStyleOptionViewItem &optio
}
}
/* ── invite plus button (bottom-right) ── */
QRect plusRect = inviteButtonRect(option.rect);
bool plusHovered = index.data(ChannelInviteBtnHoveredRole).toInt() != 0;
painter->setRenderHint(QPainter::Antialiasing);
if (plusHovered) {
painter->setBrush(QColor(0x33, 0x90, 0xEC));
painter->setPen(Qt::NoPen);
painter->drawEllipse(plusRect);
painter->setPen(QPen(Qt::white, 2));
} else {
painter->setBrush(QColor(0x33, 0x90, 0xEC, 110));
painter->setPen(Qt::NoPen);
painter->drawEllipse(plusRect);
painter->setPen(QPen(QColor(0x33, 0x90, 0xEC), 2));
}
painter->drawLine(plusRect.center().x(), plusRect.top() + 5,
plusRect.center().x(), plusRect.bottom() - 5);
painter->drawLine(plusRect.left() + 5, plusRect.center().y(),
plusRect.right() - 5, plusRect.center().y());
painter->restore();
}

6
tools/chatgui/src/channeldelegate.h

@ -8,7 +8,8 @@ enum ChannelDataRole {
ChannelLastMessageRole = Qt::UserRole + 1,
ChannelChannelIdRole = Qt::UserRole + 2,
ChannelPeersOnlineRole = Qt::UserRole + 3, /* int: count of online peers */
ChannelAutoConnectActiveRole = Qt::UserRole + 4 /* bool: 1 if auto-connect in progress */
ChannelAutoConnectActiveRole = Qt::UserRole + 4, /* bool: 1 if auto-connect in progress */
ChannelInviteBtnHoveredRole = Qt::UserRole + 5 /* bool: 1 when mouse hovers invite button */
};
class ChannelDelegate : public QStyledItemDelegate {
@ -20,4 +21,7 @@ public:
const QModelIndex &index) const override;
QSize sizeHint(const QStyleOptionViewItem &option,
const QModelIndex &index) const override;
static constexpr int InviteBtnSize = 18;
static QRect inviteButtonRect(const QRect &itemRect);
};

55
tools/chatgui/src/channellist.cpp

@ -7,17 +7,43 @@
#include <QPushButton>
#include <QPainter>
#include <QPixmap>
#include <QMenu>
#include <QMouseEvent>
/* QListView that prevents deselection */
/* QListView that prevents deselection, handles invite plus-button clicks */
class ChannelListView : public QListView {
Q_OBJECT
public:
using QListView::QListView;
signals:
void inviteClicked(const QString& channelId);
protected:
void mousePressEvent(QMouseEvent* e) override {
if (indexAt(e->pos()).isValid())
QListView::mousePressEvent(e);
QModelIndex idx = indexAt(e->pos());
if (!idx.isValid()) return;
QRect plusRect = ChannelDelegate::inviteButtonRect(visualRect(idx));
if (plusRect.contains(e->pos())) {
emit inviteClicked(idx.data(ChannelChannelIdRole).toString());
return;
}
QListView::mousePressEvent(e);
}
void mouseMoveEvent(QMouseEvent* e) override {
int prevRow = m_hoveredInviteRow;
m_hoveredInviteRow = -1;
QModelIndex idx = indexAt(e->pos());
if (idx.isValid()) {
QRect plusRect = ChannelDelegate::inviteButtonRect(visualRect(idx));
if (plusRect.contains(e->pos()))
m_hoveredInviteRow = idx.row();
}
if (m_hoveredInviteRow != prevRow) {
model()->setData(model()->index(prevRow, 0), 0, ChannelInviteBtnHoveredRole);
model()->setData(model()->index(m_hoveredInviteRow, 0), 1, ChannelInviteBtnHoveredRole);
}
setCursor(m_hoveredInviteRow >= 0 ? Qt::PointingHandCursor : Qt::ArrowCursor);
QListView::mouseMoveEvent(e);
}
QItemSelectionModel::SelectionFlags selectionCommand(const QModelIndex& idx, const QEvent* e) const override {
if (idx.isValid())
@ -29,6 +55,9 @@ protected:
if (selectionModel() && !selectionModel()->hasSelection() && currentIndex().isValid())
selectionModel()->select(currentIndex(), QItemSelectionModel::ClearAndSelect);
}
private:
int m_hoveredInviteRow = -1;
};
static QIcon makeChannelIcon(const QColor &color, const QString &letter) {
@ -70,7 +99,7 @@ ChannelList::ChannelList(DbManager* db, QWidget *parent)
m_listView->setSelectionMode(QAbstractItemView::SingleSelection);
m_listView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
m_listView->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
m_listView->setContextMenuPolicy(Qt::CustomContextMenu);
m_listView->setMouseTracking(true);
layout->addWidget(m_listView);
auto *footer = new QWidget(this);
@ -119,8 +148,8 @@ ChannelList::ChannelList(DbManager* db, QWidget *parent)
emit channelSelected(cid);
}
});
connect(m_listView, &QListView::customContextMenuRequested,
this, &ChannelList::onContextMenu);
connect(static_cast<ChannelListView*>(m_listView), &ChannelListView::inviteClicked,
this, &ChannelList::inviteRequested);
}
void ChannelList::loadChannels() {
@ -174,16 +203,6 @@ void ChannelList::selectChannel(const QString& channelId) {
}
}
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);
}
void ChannelList::setChannelPeersOnline(const QString& channelId, int count) {
for (int row = 0; row < m_model->rowCount(); row++) {
QStandardItem* item = m_model->item(row);
@ -202,3 +221,5 @@ void ChannelList::setAutoConnectActive(bool active) {
if (item) item->setData(active ? 1 : 0, ChannelAutoConnectActiveRole);
}
}
#include "channellist.moc"

3
tools/chatgui/src/channellist.h

@ -24,9 +24,6 @@ signals:
void createGroupRequested();
void settingsRequested();
private slots:
void onContextMenu(const QPoint& pos);
private:
QListView *m_listView;
QStandardItemModel *m_model;

11
tools/chatgui/src/invitedialog.cpp

@ -7,6 +7,7 @@
#include <QLabel>
#include <QClipboard>
#include <QApplication>
#include <QTimer>
InviteDialog::InviteDialog(const QString& link, QWidget* parent)
: QDialog(parent), m_link(link)
@ -50,4 +51,14 @@ InviteDialog::InviteDialog(const QString& link, QWidget* parent)
auto *closeBtn = new QPushButton("OK", this);
connect(closeBtn, &QPushButton::clicked, this, &QDialog::accept);
layout->addWidget(closeBtn, 0, Qt::AlignRight);
QTimer::singleShot(0, this, [this]() {
QClipboard* cb = QApplication::clipboard();
cb->clear(QClipboard::Clipboard);
cb->setText(m_link, QClipboard::Clipboard);
if (cb->supportsSelection()) {
cb->clear(QClipboard::Selection);
cb->setText(m_link, QClipboard::Selection);
}
});
}

8
tools/chatgui/src/joindialog.cpp

@ -18,7 +18,8 @@ extern "C" {
#include "../../../lib/sha256.h"
}
JoinDialog::JoinDialog(UtunNode* node, DbManager* db, QWidget* parent)
JoinDialog::JoinDialog(UtunNode* node, DbManager* db, QWidget* parent,
const QString& autoJoinLink)
: QDialog(parent)
, m_node(node)
, m_db(db)
@ -115,6 +116,11 @@ JoinDialog::JoinDialog(UtunNode* node, DbManager* db, QWidget* parent)
JoinDialog* dlg = qobject_cast<JoinDialog*>(QApplication::activeModalWidget());
if (dlg) dlg->onConnectResult(nodeId, channelId, result);
});
if (!autoJoinLink.isEmpty()) {
m_linkEdit->setText(autoJoinLink);
QTimer::singleShot(0, this, &JoinDialog::onConnectClicked);
}
}
JoinDialog::~JoinDialog() {

3
tools/chatgui/src/joindialog.h

@ -13,7 +13,8 @@ class DbManager;
class JoinDialog : public QDialog {
Q_OBJECT
public:
explicit JoinDialog(UtunNode* node, DbManager* db, QWidget* parent = nullptr);
explicit JoinDialog(UtunNode* node, DbManager* db, QWidget* parent = nullptr,
const QString& autoJoinLink = QString());
~JoinDialog() override;
signals:

18
tools/chatgui/src/mainwindow.cpp

@ -13,6 +13,7 @@
#include <QSplitter>
#include <QCloseEvent>
#include <QApplication>
#include <QClipboard>
#include <QStyle>
#include <QMessageBox>
#include <QDebug>
@ -253,10 +254,21 @@ void MainWindow::showJoinDialog() {
QMessageBox::warning(this, "Error", "Node is not running.");
return;
}
JoinDialog dlg(m_node, m_db, this);
if (dlg.exec() == QDialog::Accepted) {
m_channelList->loadChannels();
QString autoLink;
QClipboard* cb = QApplication::clipboard();
QString clipText = cb->text(QClipboard::Clipboard).trimmed();
if (clipText.startsWith("utun://") && decodeInviteLink(clipText).error.isEmpty())
autoLink = clipText;
else if (cb->supportsSelection()) {
QString selText = cb->text(QClipboard::Selection).trimmed();
if (selText.startsWith("utun://") && decodeInviteLink(selText).error.isEmpty())
autoLink = selText;
}
JoinDialog dlg(m_node, m_db, this, autoLink);
if (dlg.exec() == QDialog::Accepted)
m_channelList->loadChannels();
}
void MainWindow::showSettings() {

Loading…
Cancel
Save