From 49ee66d49c3b2798085ad772836dd9f892a92e8d Mon Sep 17 00:00:00 2001 From: evgeny Date: Fri, 9 Oct 2026 23:30:33 +0200 Subject: [PATCH] Accept file drops across the channel footer --- tools/chatgui/src/channellist.cpp | 50 +++++++++++++++++++++++++++++++ tools/chatgui/src/channellist.h | 8 +++++ tools/chatgui/src/mainwindow.cpp | 5 ++++ 3 files changed, 63 insertions(+) diff --git a/tools/chatgui/src/channellist.cpp b/tools/chatgui/src/channellist.cpp index 24c2040a..2666faf7 100644 --- a/tools/chatgui/src/channellist.cpp +++ b/tools/chatgui/src/channellist.cpp @@ -13,6 +13,9 @@ #include #include #include +#include +#include +#include extern "C" { #include "../../lib/debug_config.h" @@ -175,6 +178,7 @@ ChannelList::ChannelList(DbManager* db, QWidget *parent) layout->addWidget(m_listView); auto *footer = new QWidget(this); + m_footer = footer; m_footer->setObjectName("channelFooter"); auto *fl = new QHBoxLayout(footer); fl->setContentsMargins(6, 4, 6, 4); fl->setSpacing(6); @@ -240,6 +244,12 @@ ChannelList::ChannelList(DbManager* db, QWidget *parent) fl->addWidget(plusBtn); layout->addWidget(footer); + auto targets = footer->findChildren(); targets.append(footer); + for (auto* target : targets) { target->setAcceptDrops(true); target->installEventFilter(this); } + m_dropHint = new QLabel(footer); m_dropHint->setObjectName("ownFilesDropHint"); + m_dropHint->setAttribute(Qt::WA_TransparentForMouseEvents); m_dropHint->setAlignment(Qt::AlignCenter); + m_dropHint->setStyleSheet("QLabel { border: 2px dashed palette(highlight); background: palette(window); color: palette(text); }"); + m_dropHint->hide(); connect(m_listView->selectionModel(), &QItemSelectionModel::currentChanged, this, [this](const QModelIndex ¤t, const QModelIndex &) { @@ -266,6 +276,46 @@ ChannelList::ChannelList(DbManager* db, QWidget *parent) }); } +bool ChannelList::eventFilter(QObject* target, QEvent* event) { + if (event->type() == QEvent::DragLeave) { m_dropHint->hide(); return true; } + if (event->type() != QEvent::DragEnter && event->type() != QEvent::DragMove && event->type() != QEvent::Drop) + return QWidget::eventFilter(target, event); + auto* drop = static_cast(event); + QString channel = m_listView->currentIndex().data(ChannelChannelIdRole).toString(); + QString reason; + if (!channel.toULongLong()) reason = QCoreApplication::translate("ChatGui", "Select a group to upload files"); + else if (!m_db || !m_db->myNodeId() || !gui_bridge_is_uasync_ready()) + reason = QCoreApplication::translate("ChatGui", "Upload unavailable"); + else if (!drop->mimeData()->hasUrls() || drop->mimeData()->urls().isEmpty() || !(drop->possibleActions() & Qt::CopyAction)) + reason = QCoreApplication::translate("ChatGui", "Drop local files to copy"); + QStringList paths; + if (reason.isEmpty()) for (const auto& url : drop->mimeData()->urls()) { + if (!url.isLocalFile() || url.toLocalFile().isEmpty()) { + reason = QCoreApplication::translate("ChatGui", "Only local files can be uploaded"); break; + } + paths.append(url.toLocalFile()); + } + if (event->type() == QEvent::Drop) m_dropHint->hide(); + else { + m_dropHint->setGeometry(m_footer->rect()); + m_dropHint->setText(reason.isEmpty() ? QCoreApplication::translate("ChatGui", "Copy to my files") : reason); + m_dropHint->show(); m_dropHint->raise(); + } + if (!reason.isEmpty()) { + if (event->type() != QEvent::DragMove) + DEBUG_WARN(DEBUG_CATEGORY_MEDIA, "file drop: rejected channel=%s reason=%s", qPrintable(channel), qPrintable(reason)); + drop->ignore(); + // После отклонённого DragEnter Qt не гарантирует DragLeave. + m_dropHint->hide(); return true; + } + drop->setDropAction(Qt::CopyAction); drop->accept(); + if (event->type() == QEvent::Drop) { + DEBUG_INFO(DEBUG_CATEGORY_MEDIA, "file drop: accepted channel=%s files=%d", qPrintable(channel), int(paths.size())); + QTimer::singleShot(0, this, [this, channel, paths] { emit ownFilesDropped(channel, paths); }); + } + return true; +} + void ChannelList::refreshDmPresence() { if (!m_dmPresenceTimer->isActive()) m_dmPresenceTimer->start(); } diff --git a/tools/chatgui/src/channellist.h b/tools/chatgui/src/channellist.h index 61f9e602..c51fdc26 100644 --- a/tools/chatgui/src/channellist.h +++ b/tools/chatgui/src/channellist.h @@ -6,11 +6,13 @@ #include #include #include +#include #include #include class DbManager; class QPushButton; +class QLabel; class ChannelList : public QWidget { Q_OBJECT @@ -34,16 +36,22 @@ signals: void channelSettingsRequested(const QString& channelId); void filesRequested(const QString& channelId); void ownFilesRequested(const QString& channelId); + void ownFilesDropped(const QString& channelId, const QStringList& paths); void joinRequested(); void createGroupRequested(); void settingsRequested(); void connMonitorRequested(); +protected: + bool eventFilter(QObject* target, QEvent* event) override; + private: QListView *m_listView; QStandardItemModel *m_model; DbManager *m_db; QPushButton *m_ownFilesButton; + QWidget *m_footer; + QLabel *m_dropHint; bool m_autoConnectActive = false; QHash m_dmOnline; QTimer* m_dmPresenceTimer; diff --git a/tools/chatgui/src/mainwindow.cpp b/tools/chatgui/src/mainwindow.cpp index 73c1e496..198754b6 100644 --- a/tools/chatgui/src/mainwindow.cpp +++ b/tools/chatgui/src/mainwindow.cpp @@ -621,6 +621,11 @@ void MainWindow::setupConnects() { qPrintable(channel), (unsigned long long)owner); showMemberFiles(channel, owner); }); + connect(m_channelList, &ChannelList::ownFilesDropped, this, [this](const QString& channel, const QStringList& paths) { + if (!m_fileBrowser) m_fileBrowser = new MemberFileBrowser(m_db, this); + m_fileBrowser->show(); m_fileBrowser->raise(); m_fileBrowser->activateWindow(); + m_fileBrowser->uploadOwnFiles(channel, paths); + }); connect(m_accountList, &AccountList::callRequested, this, &MainWindow::onCallRequested); connect(m_messageList, &MessageList::callRequested, this, &MainWindow::onCallRequested); connect(m_accountList, &AccountList::pmRequested, this, &MainWindow::onPmRequested);