#2 Various improvements on existing, albeit incomplete DCC implementation in Core. DCC Chat implementation. DCC request dialog.

master
Tomatix 3 years ago
parent c0c6a6eee1
commit 523e21d3db
  1. 91
      IRCClient/DCC.cpp
  2. 22
      IRCClient/DCC.h
  3. 5
      IRCClient/IRCBase.cpp
  4. 1
      IRCClient/IRCBase.h
  5. 4
      IRCClient/IRCBasePriv.h
  6. 2
      IWin/CMakeLists.txt
  7. 1
      IWin/IWin.cpp
  8. 1
      IWin/IWin.h
  9. 129
      IWin/IWinDCCChat.cpp
  10. 48
      IWin/IWinDCCChat.h
  11. 8
      IWin/IWinStatus.cpp
  12. 2
      IWin/IWinStatus.h
  13. 7
      IdealIRC/CMakeLists.txt
  14. 60
      IdealIRC/DCCQuery.cpp
  15. 43
      IdealIRC/DCCQuery.h
  16. 123
      IdealIRC/DCCQuery.ui
  17. 41
      IdealIRC/DCCQueryDelegate.cpp
  18. 29
      IdealIRC/DCCQueryDelegate.h
  19. 110
      IdealIRC/DCCQueryTableModel.cpp
  20. 53
      IdealIRC/DCCQueryTableModel.h
  21. 6
      IdealIRC/IRC.cpp
  22. 2
      IdealIRC/IRC.h
  23. 29
      IdealIRC/IdealIRC.cpp
  24. 6
      IdealIRC/IdealIRC.h
  25. 11
      IdealIRC/IdealIRC.ui
  26. 163
      IdealIRC/MdiManager.cpp
  27. 5
      IdealIRC/MdiManager.h
  28. BIN
      Resources/Icons/dccchat.png
  29. 1
      Resources/resources.qrc

@ -25,7 +25,9 @@ struct DCCPriv
, acceptor(ioctx_)
, endpoint(tcp::v4(), std::stoi(port_))
, socket(ioctx_)
{}
, readbuf(16384)
{
}
DCC& super;
IRCBase& ircctx;
@ -43,7 +45,7 @@ struct DCCPriv
asio::ip::tcp::endpoint endpoint;
tcp::socket socket;
asio::streambuf readbuf;
DCC::ByteString readbuf;
DCC::CallbackRead cbRead;
DCC::CallbackConnected cbCon;
@ -52,13 +54,7 @@ struct DCCPriv
void connected(const asio::error_code& ec)
{
super.onConnected();
asio::streambuf::mutable_buffers_type mutableBuf{ readbuf.prepare(16384) };
socket.async_receive(mutableBuf,
[this](const asio::error_code& ec, std::size_t rl){
readbuf.commit(rl);
read(ec);
});
read(ec);
}
void disconnected(const asio::error_code& ec)
@ -68,19 +64,16 @@ struct DCCPriv
void read(const asio::error_code& ec)
{
if (ec) {
disconnected(ec);
return;
}
std::istream is(&readbuf);
super.onRead(is);
asio::streambuf::mutable_buffers_type mutableBuf{ readbuf.prepare(16384) };
socket.async_receive(mutableBuf,
[this](const asio::error_code& ec, std::size_t rl){
readbuf.commit(rl);
read(ec);
socket.async_receive(asio::buffer(readbuf.data(), readbuf.size()),
[this](const asio::error_code& ec, std::size_t rl) {
if (!ec) {
std::cout << "DCC read " << rl << " bytes." << std::endl;
super.onRead(readbuf, rl);
read(ec);
}
else {
disconnected(ec);
}
});
}
@ -116,19 +109,7 @@ DCC::DCC(DCC&& other) noexcept
DCC::~DCC()
{
try {
if (mp->socket.is_open()) {
mp->socket.shutdown(asio::socket_base::shutdown_both);
mp->socket.close();
}
if (mp->direction == Direction::Initiator)
mp->acceptor.close();
}
catch(const asio::system_error& e) {
// Properly log e.what() or e.code() maybe?
std::cerr << fmt::format("~DCC() caught exception ({}): {}\n IP={}, Port={}, Direction={}",
e.code().value(), e.what(), mp->ip, mp->port, mp->direction) << std::endl;
}
disconnect();
}
bool DCC::isPending() const
@ -155,6 +136,23 @@ IRCError DCC::accept()
return IRCError::NoError;
}
void DCC::disconnect()
{
try {
if (mp->socket.is_open()) {
mp->socket.shutdown(asio::socket_base::shutdown_both);
mp->socket.close();
}
if (mp->direction == Direction::Initiator)
mp->acceptor.close();
}
catch(const asio::system_error& e) {
// Properly log e.what() or e.code() maybe?
std::cerr << fmt::format("~DCC() caught exception ({}): {}\n IP={}, Port={}, Direction={}",
e.code().value(), e.what(), mp->ip, mp->port, mp->direction) << std::endl;
}
}
DCC::Direction DCC::direction() const
{
return mp->direction;
@ -178,26 +176,26 @@ const IRCBase& DCC::context() const
return mp->ircctx;
}
void DCC::callbackRead(DCC::CallbackRead&& cb)
void DCC::setCallbackRead(DCC::CallbackRead&& cb)
{
mp->cbRead = std::move(cb);
}
void DCC::callbackConnected(DCC::CallbackConnected&& cb)
void DCC::setCallbackConnected(DCC::CallbackConnected&& cb)
{
mp->cbCon = std::move(cb);
}
void DCC::callbackDisconnected(DCC::CallbackDisconnected&& cb)
void DCC::setCallbackDisconnected(DCC::CallbackDisconnected&& cb)
{
mp->cbDiscon = std::move(cb);
}
void DCC::onRead(std::istream& is)
void DCC::onRead(const DCC::ByteString& data, std::size_t length)
{
if (mp->cbRead)
mp->cbRead(is);
mp->cbRead(data, length);
}
void DCC::onConnected()
@ -212,3 +210,16 @@ void DCC::onDisconnected(IRCError e)
mp->cbDiscon(e);
}
void DCC::resetCallbacks()
{
mp->cbRead = {};
mp->cbCon = {};
mp->cbDiscon = {};
}
DCC::ByteString DCC::stringToByteString(const std::string& input)
{
std::basic_string<std::byte> temp(reinterpret_cast<const std::byte*>(input.data()), input.size());
DCC::ByteString output(temp.begin(), temp.end());
return output;
}

@ -9,9 +9,9 @@
#define DCC_H
#include "IRCError.h"
#include <string>
#include <memory>
#include <asio.hpp>
#include <vector>
#include <memory>
using asio::ip::tcp;
@ -24,10 +24,10 @@ class DCC
friend struct DCCPriv;
public:
using CallbackRead = std::function<void(std::istream&)>;
using ByteString = std::vector<std::byte>;
using CallbackRead = std::function<void(const ByteString&, std::size_t)>;
using CallbackConnected = std::function<void()>;
using CallbackDisconnected = std::function<void(IRCError e)>;
using ByteString = std::basic_string<std::byte>;
/*
* Initiator acts as the server, and the target is the client
@ -49,6 +49,8 @@ public:
[[nodiscard]] bool isPending() const;
IRCError accept();
void disconnect();
[[nodiscard]] Direction direction() const;
void write(const ByteString& data);
@ -59,9 +61,13 @@ public:
/*
* Callbacks used by 'users' of this class.
*/
void callbackRead(CallbackRead&& cb);
void callbackConnected(CallbackConnected&& cb);
void callbackDisconnected(CallbackDisconnected&& cb);
void setCallbackRead(CallbackRead&& cb);
void setCallbackConnected(CallbackConnected&& cb);
void setCallbackDisconnected(CallbackDisconnected&& cb);
void resetCallbacks();
static ByteString stringToByteString(const std::string& input);
protected:
/*
@ -69,7 +75,7 @@ protected:
* If you re-implement any of these, make sure to forward the
* calls to this base class at the end of your function.
*/
virtual void onRead(std::istream& is);
virtual void onRead(const ByteString& data, std::size_t length);
virtual void onConnected();
virtual void onDisconnected(IRCError e);

@ -427,11 +427,6 @@ std::pair<std::shared_ptr<DCC>, IRCError> IRCBase::initiateDCC(const std::string
return std::pair<std::shared_ptr<DCC>, IRCError>();
}
IRCError IRCBase::declineDCC(std::shared_ptr<DCC> /*dcc*/)
{
return IRCError::NetworkUnreachable;
}
const IRCMessage* IRCBase::getCurrentMessageRaw() const
{
return mp->currentMessage;

@ -120,7 +120,6 @@ public:
[[nodiscard]] std::shared_ptr<IRCMember> getMember(const std::string& nickname) const;
std::pair<std::shared_ptr<DCC>, IRCError> initiateDCC(const std::string& port); // Creates a DCC initiator (tcp server)
IRCError declineDCC(std::shared_ptr<DCC> dcc);
[[nodiscard]] const IRCMessage* getCurrentMessageRaw() const;

@ -24,8 +24,8 @@
namespace {
/* Debug/development options */
constexpr bool DumpReadData = false;
constexpr bool DumpWriteData = false;
constexpr bool DumpReadData = true;
constexpr bool DumpWriteData = true;
const std::vector<std::string> V3Support {
"account-notify",

@ -9,6 +9,8 @@ list(APPEND ${component}_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/IWinPrivate.h
${CMAKE_CURRENT_SOURCE_DIR}/IWinStatus.cpp
${CMAKE_CURRENT_SOURCE_DIR}/IWinStatus.h
${CMAKE_CURRENT_SOURCE_DIR}/IWinDCCChat.cpp
${CMAKE_CURRENT_SOURCE_DIR}/IWinDCCChat.h
${CMAKE_CURRENT_SOURCE_DIR}/NicklistController.cpp
${CMAKE_CURRENT_SOURCE_DIR}/NicklistController.h
)

@ -15,6 +15,7 @@ std::unordered_map<IWin::Type,QString> IWin::TypeString = {
{ IWin::Type::Status, "Status" },
{ IWin::Type::Channel, "Channel" },
{ IWin::Type::Private, "Private" },
{ IWin::Type::DCCChat, "DCC Chat" },
{ IWin::Type::Custom, "Custom" }
};

@ -25,6 +25,7 @@ public:
Status,
Channel,
Private,
DCCChat,
Custom
};
static const QString InvalidWindowTypeString;

@ -0,0 +1,129 @@
/*
* IdealIRC - Internet Relay Chat client
* Copyright (C) 2022 Tom-Andre Barstad.
* This software is licensed under the Software Attribution License.
* See LICENSE for more information.
*/
#include "IWinDCCChat.h"
#include <IRCClient/IRCBase.h>
#include <QMessageBox>
#include <iostream>
IWinDCCChat::IWinDCCChat(std::shared_ptr<DCC> dcc_, const QString& targetNickname)
: IWin(IWin::Type::DCCChat, nullptr)
, dcc(std::move(dcc_))
{
setButtonText(targetNickname);
layout = new QVBoxLayout();
view = new IIRCView();
input = new ILineEdit([this](int a, QString& b){ return tabComplete(a,b); });
layout->setMargin(0);
layout->setSpacing(2);
layout->addWidget(view);
layout->addWidget(input);
setLayout(layout);
setTabOrder(input, view);
view->setFocusPolicy(Qt::FocusPolicy::NoFocus);
connect(input, &ILineEdit::newLine,
this, &IWinDCCChat::newLine);
dcc->setCallbackConnected([this]{ onConnected(); });
dcc->setCallbackDisconnected([this](IRCError e){ onDisconnected(e); });
dcc->setCallbackRead([this](const DCC::ByteString& data, std::size_t length){ onRead(data, length); });
dcc->accept();
}
bool IWinDCCChat::printWithCustomTime(const QDateTime& timestamp, PrintType ptype, const QString& text)
{
view->print(timestamp, ptype, text);
return true;
}
void IWinDCCChat::refreshWindowTitle()
{
setWindowTitle(tr("DCC Chat with: %1").arg(getButtonText()));
}
void IWinDCCChat::closeEvent(QCloseEvent* evt)
{
auto btn = QMessageBox::question(this, tr("Disconnect DCC Chat"),
tr("Closing this window will also disconnect the DCC session, and no more messages can be received over this conversation.\nProceed?"));
if (btn == QMessageBox::Yes) {
dcc->resetCallbacks();
dcc->disconnect();
evt->accept();
IWin::closeEvent(evt);
}
else {
evt->ignore();
}
}
void IWinDCCChat::onConnected()
{
print(PrintType::ProgramInfo, tr("Connected to remote peer"));
}
void IWinDCCChat::onDisconnected(IRCError e)
{
print(PrintType::ProgramInfo, tr("Disconnected from remote peer"));
}
void IWinDCCChat::newLine(const QString& line)
{
const auto nickname{ QString::fromStdString(dcc->context().getNickname()) };
print(PrintType::OwnText, tr("<%1> %2").arg(nickname, line));
auto data = DCC::stringToByteString(line.toStdString());
data.push_back(std::byte('\r'));
data.push_back(std::byte('\n'));
dcc->write(data);
}
void IWinDCCChat::onRead(const DCC::ByteString& data, std::size_t length)
{
auto beginIt = data.cbegin();
auto end = data.cbegin() + static_cast<int>(length);
while (true) {
auto endIt = std::find_if(beginIt, end,
[](std::byte byte) { return byte == std::byte('\n'); });
std::basic_string<std::byte> basictemp(beginIt, endIt);
std::string temp(reinterpret_cast<char*>(basictemp.data()), basictemp.size());
if (endIt == end) {
lineBuffer += QString::fromStdString(temp);
break;
}
lineBuffer += QString::fromStdString(temp);
if (!lineBuffer.isEmpty() && lineBuffer.back() == '\r')
lineBuffer.remove(lineBuffer.length()-1, 1);
print(PrintType::Normal, tr("<%1> %2").arg(getButtonText(), lineBuffer));
lineBuffer.clear();
beginIt = ++endIt;
}
}
int IWinDCCChat::tabComplete(int index, QString& pattern)
{
return 0;
}
void IWinDCCChat::disconnectFromDCC()
{
dcc->disconnect();
}

@ -0,0 +1,48 @@
/*
* IdealIRC - Internet Relay Chat client
* Copyright (C) 2022 Tom-Andre Barstad.
* This software is licensed under the Software Attribution License.
* See LICENSE for more information.
*/
#ifndef IWINDCCCHAT_H
#define IWINDCCCHAT_H
#include "IWin.h"
#include "IRCClient/DCC.h"
#include "Widgets/ILineEdit.h"
#include <QVBoxLayout>
#include <QCloseEvent>
#include <memory>
class IWinDCCChat : public IWin
{
Q_OBJECT
public:
IWinDCCChat(std::shared_ptr<DCC> dcc_, const QString& targetNickname);
bool printWithCustomTime(const QDateTime& timestamp, PrintType ptype, const QString& text) override;
void refreshWindowTitle() override;
void clear() override { view->resetView(); }
void disconnectFromDCC();
private:
void closeEvent(QCloseEvent* evt) override;
void onConnected();
void onDisconnected(IRCError e);
void newLine(const QString& line);
void onRead(const DCC::ByteString& data, std::size_t length);
int tabComplete(int index, QString& pattern);
QVBoxLayout* layout;
IIRCView* view;
ILineEdit* input;
QString lineBuffer;
std::shared_ptr<DCC> dcc;
};
#endif // IWINDCCCHAT_H

@ -7,6 +7,7 @@
#include "IWinStatus.h"
#include "IWinChannel.h"
#include "IWinDCCChat.h"
#include "MdiManager.h"
#include "Script/Manager.h"
#include "IRC.h"
@ -133,6 +134,13 @@ IWin* IWinStatus::createPrivateWindow(const QString& target, bool setFocus, High
return subwinBase;
}
IWinDCCChat* IWinStatus::createDCCChatWindow(std::shared_ptr<DCC> dcc, const IRCPrefix& sender)
{
auto& mdiManager = MdiManager::instance();
auto* win = mdiManager.createDCCSubwindow(this, IWin::Type::DCCChat, std::move(dcc), sender);
return dynamic_cast<IWinDCCChat*>(win);
}
IWin* IWinStatus::getActiveWindow()
{
auto& mdiManager = MdiManager::instance();

@ -20,6 +20,7 @@
class MdiManager;
class IWinChannel;
class IWinDCCChat;
class IWinStatus : public IWin
{
@ -37,6 +38,7 @@ public:
void printToAll(const PrintType ptype, const QString& text);
IWinChannel* createChannelWindow(const QString& name);
IWin* createPrivateWindow(const QString& target, bool setFocus = true, Highlight buttonHighlight = HL_None);
IWinDCCChat* createDCCChatWindow(std::shared_ptr<DCC> dcc, const IRCPrefix& sender);
IWin* getActiveWindow();
QList<IWin*> subWindows() const;
IRC& getConnection() { return connection; }

@ -6,6 +6,13 @@ if (MSVC)
endif()
list(APPEND ${component}_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/DCCQuery.cpp
${CMAKE_CURRENT_SOURCE_DIR}/DCCQuery.h
${CMAKE_CURRENT_SOURCE_DIR}/DCCQuery.ui
${CMAKE_CURRENT_SOURCE_DIR}/DCCQueryTableModel.cpp
${CMAKE_CURRENT_SOURCE_DIR}/DCCQueryTableModel.h
${CMAKE_CURRENT_SOURCE_DIR}/DCCQueryDelegate.cpp
${CMAKE_CURRENT_SOURCE_DIR}/DCCQueryDelegate.h
${CMAKE_CURRENT_SOURCE_DIR}/AboutIIRC.cpp
${CMAKE_CURRENT_SOURCE_DIR}/AboutIIRC.h
${CMAKE_CURRENT_SOURCE_DIR}/AboutIIRC.ui

@ -0,0 +1,60 @@
/*
* IdealIRC - Internet Relay Chat client
* Copyright (C) 2022 Tom-Andre Barstad.
* This software is licensed under the Software Attribution License.
* See LICENSE for more information.
*/
#include "DCCQuery.h"
#include "ui_DCCQuery.h"
#include "MdiManager.h"
#include "IWin/IWinStatus.h"
DCCQuery::DCCQuery(QWidget *parent)
: QDialog(parent)
, ui(new Ui::DCCQuery)
{
ui->setupUi(this);
ui->queryTable->setModel(&m_model);
ui->queryTable->setItemDelegateForColumn(2, &m_delegate);
ui->queryTable->setItemDelegateForColumn(3, &m_delegate);
connect(&m_delegate, &DCCQueryDelegate::accept, this, &DCCQuery::accepted);
connect(&m_delegate, &DCCQueryDelegate::reject, this, &DCCQuery::rejected);
}
DCCQuery::~DCCQuery()
{
delete ui;
}
void DCCQuery::on_btnRejectAll_clicked()
{
m_model.removeAll();
}
void DCCQuery::accepted(const QModelIndex& index)
{
auto& mdi = MdiManager::instance();
auto dcc = m_model.getDCCPtr(index);
auto sender = m_model.getSender(index);
auto& context = dynamic_cast<IRC&>(dcc->context());
// TODO check window type.
mdi.createDCCSubwindow(&(context.getStatus()), IWin::Type::DCCChat, dcc, *sender);
m_model.removeEntry(index);
}
void DCCQuery::rejected(const QModelIndex& index)
{
m_model.removeEntry(index);
}
void DCCQuery::addQuery(const IRCPrefix& sender, const QString& description, const std::shared_ptr<DCC>& dcc)
{
auto buttonPair = m_model.addQuery(sender, description, dcc);
ui->queryTable->openPersistentEditor(buttonPair.first);
ui->queryTable->openPersistentEditor(buttonPair.second);
ui->queryTable->resizeColumnToContents(1); // Always make second column view the contents; for DCC SEND, showing of filename, etc.
}

@ -0,0 +1,43 @@
/*
* IdealIRC - Internet Relay Chat client
* Copyright (C) 2022 Tom-Andre Barstad.
* This software is licensed under the Software Attribution License.
* See LICENSE for more information.
*/
#ifndef DCCQUERY_H
#define DCCQUERY_H
#include "DCCQueryTableModel.h"
#include "DCCQueryDelegate.h"
#include "IRC.h"
#include <QDialog>
namespace Ui {
class DCCQuery;
}
class DCCQuery : public QDialog
{
Q_OBJECT
public:
explicit DCCQuery(QWidget *parent = nullptr);
~DCCQuery();
void addQuery(const IRCPrefix& sender, const QString& description, const std::shared_ptr<DCC>& dcc);
private slots:
void on_btnRejectAll_clicked();
private:
Ui::DCCQuery *ui;
DCCQueryTableModel m_model;
DCCQueryDelegate m_delegate;
void accepted(const QModelIndex& index);
void rejected(const QModelIndex& index);
};
#endif // DCCQUERY_H

@ -0,0 +1,123 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>DCCQuery</class>
<widget class="QDialog" name="DCCQuery">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>450</width>
<height>250</height>
</rect>
</property>
<property name="focusPolicy">
<enum>Qt::StrongFocus</enum>
</property>
<property name="windowTitle">
<string>DCC Queries</string>
</property>
<property name="windowIcon">
<iconset resource="../Resources/resources.qrc">
<normaloff>:/Icons/dccchat.png</normaloff>:/Icons/dccchat.png</iconset>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="2" column="1">
<widget class="QPushButton" name="btnRejectAll">
<property name="text">
<string>Reject all</string>
</property>
<property name="autoDefault">
<bool>false</bool>
</property>
</widget>
</item>
<item row="1" column="0" colspan="3">
<widget class="QTableView" name="queryTable">
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<attribute name="horizontalHeaderVisible">
<bool>false</bool>
</attribute>
<attribute name="horizontalHeaderDefaultSectionSize">
<number>100</number>
</attribute>
<attribute name="horizontalHeaderHighlightSections">
<bool>false</bool>
</attribute>
<attribute name="verticalHeaderVisible">
<bool>false</bool>
</attribute>
<attribute name="verticalHeaderHighlightSections">
<bool>false</bool>
</attribute>
</widget>
</item>
<item row="2" column="2">
<widget class="QPushButton" name="btnClose">
<property name="text">
<string>Close</string>
</property>
<property name="autoDefault">
<bool>false</bool>
</property>
<property name="default">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="0">
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>293</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="0" column="0" colspan="3">
<widget class="QLabel" name="label">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Inbound DCC queries&lt;br/&gt;&lt;/span&gt;Note: Accepting will expose your IP address to the requester!&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
</layout>
</widget>
<tabstops>
<tabstop>queryTable</tabstop>
<tabstop>btnRejectAll</tabstop>
<tabstop>btnClose</tabstop>
</tabstops>
<resources>
<include location="../Resources/resources.qrc"/>
</resources>
<connections>
<connection>
<sender>btnClose</sender>
<signal>clicked()</signal>
<receiver>DCCQuery</receiver>
<slot>close()</slot>
<hints>
<hint type="sourcelabel">
<x>350</x>
<y>178</y>
</hint>
<hint type="destinationlabel">
<x>199</x>
<y>99</y>
</hint>
</hints>
</connection>
</connections>
</ui>

@ -0,0 +1,41 @@
/*
* IdealIRC - Internet Relay Chat client
* Copyright (c) 2022 Tom-Andre Barstad.
* This software is licensed under the Software Attribution License.
* See LICENSE for more information.
*/
#include "DCCQueryDelegate.h"
#include <QPushButton>
DCCQueryDelegate::DCCQueryDelegate(QObject* parent)
: QStyledItemDelegate(parent)
{}
QWidget* DCCQueryDelegate::createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const
{
/* Accept */
if (index.column() == 2) {
auto* btn = new QPushButton(tr("Accept"), parent);
auto idx = index;
connect(btn, &QPushButton::clicked, [this, idx]{ emit accept(idx); });
return btn;
}
/* Reject */
else if (index.column() == 3) {
auto* btn = new QPushButton(tr("Reject"), parent);
auto idx = index;
connect(btn, &QPushButton::clicked, [this, idx]{ emit reject(idx); });
return btn;
}
else {
return nullptr;
}
}
void DCCQueryDelegate::updateEditorGeometry(QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& index) const
{
editor->setGeometry(option.rect);
}

@ -0,0 +1,29 @@
/*
* IdealIRC - Internet Relay Chat client
* Copyright (c) 2022 Tom-Andre Barstad.
* This software is licensed under the Software Attribution License.
* See LICENSE for more information.
*/
#ifndef DCCQUERYDELEGATE_H
#define DCCQUERYDELEGATE_H
#include <QStyledItemDelegate>
class DCCQueryDelegate : public QStyledItemDelegate
{
Q_OBJECT
public:
explicit DCCQueryDelegate(QObject* parent = nullptr);
QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const override;
void updateEditorGeometry(QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& index) const override;
signals:
void accept(const QModelIndex& index) const;
void reject(const QModelIndex& index) const;
};
#endif //DCCQUERYDELEGATE_H

@ -0,0 +1,110 @@
/*
* IdealIRC - Internet Relay Chat client
* Copyright (c) 2022 Tom-Andre Barstad.
* This software is licensed under the Software Attribution License.
* See LICENSE for more information.
*/
#include "DCCQueryTableModel.h"
#include <stdexcept>
DCCQueryTableModel::DCCQueryTableModel(QObject* parent)
: QAbstractTableModel(parent)
{
m_items.reserve(20);
}
QModelIndex DCCQueryTableModel::index(int row, int column, const QModelIndex& parent) const
{
return row < m_items.size() ? createIndex(row, column, nullptr)
: QModelIndex{};
}
int DCCQueryTableModel::rowCount(const QModelIndex& parent) const
{
return static_cast<int>(m_items.size());
}
QVariant DCCQueryTableModel::data(const QModelIndex& index, int role) const
{
if (role == Qt::DisplayRole) {
const Item* item{};
try {
item = &(m_items.at(index.row()));
}
catch (const std::out_of_range&) {
item = nullptr;
}
if (!item)
return {};
switch (index.column()) {
case 0: return QString::fromStdString(item->sender.toString());
case 1: return item->description;
case 2: return "accept";
case 3: return "decline";
}
}
return {};
}
std::pair<QModelIndex,QModelIndex> DCCQueryTableModel::addQuery(const IRCPrefix& sender, const QString& description, const std::shared_ptr<DCC>& dcc)
{
const auto pos = static_cast<int>(m_items.size());
beginInsertRows(QModelIndex{}, pos, pos);
m_items.emplace_back( Item{sender, description, dcc} );
endInsertRows();
return std::make_pair( createIndex(pos, 2, nullptr),
createIndex(pos, 3, nullptr)
);
}
void DCCQueryTableModel::removeAll()
{
beginResetModel();
m_items.clear();
endResetModel();
}
void DCCQueryTableModel::removeEntry(const QModelIndex& index)
{
if (!index.isValid() || (index.isValid() && index.row() > m_items.size()))
return;
beginRemoveRows(QModelIndex{}, index.row(), index.row());
m_items.erase(m_items.begin() + index.row());
endRemoveRows();
}
std::shared_ptr<DCC> DCCQueryTableModel::getDCCPtr(const QModelIndex& index)
{
if (!index.isValid() || (index.isValid() && index.row() > m_items.size()))
return nullptr;
auto it = m_items.begin() + index.row();
return it->dcc;
}
std::optional<IRCPrefix> DCCQueryTableModel::getSender(const QModelIndex& index)
{
if (!index.isValid() || (index.isValid() && index.row() > m_items.size()))
return {};
auto it = m_items.begin() + index.row();
return std::make_optional(it->sender);
}
Qt::ItemFlags DCCQueryTableModel::flags(const QModelIndex& index) const
{
if (!index.isValid())
return Qt::NoItemFlags;
if (index.row() > 1)
return Qt::ItemIsEnabled | Qt::ItemIsEditable;
else
return Qt::ItemIsEnabled;
}

@ -0,0 +1,53 @@
/*
* IdealIRC - Internet Relay Chat client
* Copyright (c) 2022 Tom-Andre Barstad.
* This software is licensed under the Software Attribution License.
* See LICENSE for more information.
*/
#ifndef DCCQUERYTABLEMODEL_H
#define DCCQUERYTABLEMODEL_H
#include "IRC.h"
#include "IRCClient/DCC.h"
#include <QAbstractTableModel>
#include <vector>
#include <memory>
#include <optional>
#include <utility>
class DCCQueryTableModel : public QAbstractTableModel
{
Q_OBJECT
public:
explicit DCCQueryTableModel(QObject* parent = nullptr);
QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const override;
QModelIndex parent(const QModelIndex& /*child*/) const override { return QModelIndex{}; };
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
int columnCount(const QModelIndex& parent = QModelIndex()) const override { return 4; }; // Sender, Description, Accept, Reject
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
Qt::ItemFlags flags(const QModelIndex &index) const override;
std::pair<QModelIndex,QModelIndex> addQuery(const IRCPrefix& sender, const QString& description, const std::shared_ptr<DCC>& dcc);
void removeAll();
void removeEntry(const QModelIndex& index);
std::shared_ptr<DCC> getDCCPtr(const QModelIndex& index);
std::optional<IRCPrefix> getSender(const QModelIndex& index);
private:
struct Item {
IRCPrefix sender;
QString description;
std::shared_ptr<DCC> dcc;
};
std::vector<Item> m_items;
};
#endif //DCCQUERYTABLEMODEL_H

@ -907,7 +907,11 @@ void IRC::onMsgCTCPResponse(const IRCPrefix& sender, const std::string& target,
void IRC::onMsgDCCRequest(std::shared_ptr<DCC> dcc, const IRCPrefix& sender, const std::string& target, const std::string& type, const std::string& message)
{
emit DCCRequested( dcc,
sender,
QString::fromStdString(type).toUpper(),
QString::fromStdString(message)
);
}
void IRC::v3onMsgAway(const IRCPrefix& sender, const std::string& message, const std::vector<std::string>& channelsAffected)

@ -53,6 +53,8 @@ signals:
void showTrayMessage(const QString& title, const QString& message);
void DCCRequested(const std::shared_ptr<DCC>& dcc, const IRCPrefix& sender, const QString& type, const QString& message);
private:
struct ReconnectDetails {
bool ssl;

@ -24,6 +24,7 @@ IdealIRC::IdealIRC(QWidget* parent)
, ui(new Ui::IdealIRC)
, confDlg(this)
, aboutDlg(this)
, dccQueryDlg(this)
{
ui->setupUi(this);
IIRC_MainWindow_Ptr = this;
@ -357,6 +358,25 @@ void IdealIRC::disconnectFromServer()
qWarning() << "Trying to disconnect from an already closed connection.";
}
void IdealIRC::onStatusWindowCreated(IWinStatus* window)
{
connect(&(window->getConnection()), &IRC::showTrayMessage, [this] (const QString& title, const QString& message) {
if (!isVisible() || !isActiveWindow()) {
auto& conf = ConfigMgr::instance();
trayIcon->showMessage(title, message, QSystemTrayIcon::Information, 1000 * conf.common("TrayNotifyDelay").toInt());
}
});
connect(&(window->getConnection()), &IRC::DCCRequested, this, &IdealIRC::onDCCRequested);
}
void IdealIRC::onDCCRequested(const std::shared_ptr<DCC>& dcc, const IRCPrefix& sender, const QString& type, const QString& message)
{
// TODO check type and message together... DCC SEND would use 'message' to hold filename and size
dccQueryDlg.addQuery(sender, type, dcc);
dccQueryDlg.show();
}
void IdealIRC::on_actionConnect_triggered()
{
ConnectButtonState state = static_cast<ConnectButtonState>(ui->actionConnect->data().toInt());
@ -408,12 +428,7 @@ void IdealIRC::on_actionHide_toolbar_triggered()
ui->toolBar->setVisible(!ui->actionHide_toolbar->isChecked());
}
void IdealIRC::onStatusWindowCreated(IWinStatus* window)
void IdealIRC::on_actionDCC_queries_triggered()
{
connect(&(window->getConnection()), &IRC::showTrayMessage, [this] (const QString& title, const QString& message) {
if (!isVisible() || !isActiveWindow()) {
auto& conf = ConfigMgr::instance();
trayIcon->showMessage(title, message, QSystemTrayIcon::Information, 1000 * conf.common("TrayNotifyDelay").toInt());
}
});
dccQueryDlg.show();
}

@ -11,6 +11,7 @@
#include "MdiManager.h"
#include "IConfig/IConfig.h"
#include "AboutIIRC.h"
#include "DCCQuery.h"
#include "Script/Manager.h"
#include <QMainWindow>
#include <QCloseEvent>
@ -33,13 +34,15 @@ public:
~IdealIRC();
private slots:
void onStatusWindowCreated(IWinStatus* window);
void onDCCRequested(const std::shared_ptr<DCC>& dcc, const IRCPrefix& sender, const QString& type, const QString& message);
void on_actionConnect_triggered();
void on_actionOptions_triggered();
void on_actionAbout_IdealIRC_triggered();
void on_actionScripts_triggered();
void on_actionOnline_wiki_triggered();
void on_actionHide_toolbar_triggered();
void onStatusWindowCreated(IWinStatus* window);
void on_actionDCC_queries_triggered();
private:
enum class ConnectButtonState {
@ -64,6 +67,7 @@ private:
QAction* trayIconMenuExit;
IConfig confDlg;
AboutIIRC aboutDlg;
DCCQuery dccQueryDlg;
signals:
void aboutToClose();

@ -62,6 +62,7 @@
<addaction name="separator"/>
<addaction name="actionConnect"/>
<addaction name="actionOptions"/>
<addaction name="actionDCC_queries"/>
<addaction name="actionScripts"/>
<addaction name="separator"/>
<addaction name="actionHide_toolbar"/>
@ -106,6 +107,7 @@
</attribute>
<addaction name="actionConnect"/>
<addaction name="actionOptions"/>
<addaction name="actionDCC_queries"/>
<addaction name="actionScripts"/>
</widget>
<widget class="QToolBar" name="windowButtons">
@ -169,6 +171,15 @@
<string>Hide toolbar</string>
</property>
</action>
<action name="actionDCC_queries">
<property name="icon">
<iconset resource="../Resources/resources.qrc">
<normaloff>:/Icons/dccchat.png</normaloff>:/Icons/dccchat.png</iconset>
</property>
<property name="text">
<string>DCC queries</string>
</property>
</action>
</widget>
<layoutdefault spacing="6" margin="11"/>
<resources>

@ -8,6 +8,7 @@
#include "IWin/IWinStatus.h"
#include "IWin/IWinChannel.h"
#include "IWin/IWinPrivate.h"
#include "IWin/IWinDCCChat.h"
#include "MdiManager.h"
#include "ConfigMgr.h"
#include <QApplication>
@ -33,6 +34,20 @@ MdiManager::MdiManager(QMdiArea& mdiArea, QToolBar& buttonBar, QSystemTrayIcon&
connect(&m_bbMgr, &ButtonbarMgr::changeWindow, &m_mdiArea, &QMdiArea::setActiveSubWindow);
}
MdiManager::~MdiManager()
{
for (auto subwin : m_mdiArea.subWindowList()) {
auto win = qobject_cast<IWin*>(subwin->widget());
if (!win) {
qWarning() << "During destruction of MdiManager; subwindow not part of IWin!" << subwin->widget();
}
auto dccchat = qobject_cast<IWinDCCChat*>(win);
if (dccchat)
dccchat->disconnectFromDCC();
}
}
MdiManager& MdiManager::instance()
{
return *m_instance;
@ -59,85 +74,63 @@ IWin* MdiManager::createSubwindow(IWin* parent, const QString& buttonText, IWin:
{
IWin* basePtr{ nullptr };
IWinStatus* statusParent{ nullptr };
QMdiSubWindow* mdiwin{ nullptr };
QString iconPath;
if (parent && parent->getType() == IWin::Type::Status)
statusParent = dynamic_cast<IWinStatus*>(parent);
switch (windowType) {
case IWin::Type::Status:
basePtr = new IWinStatus(buttonText);
if (!m_activeStatus || activate)
m_activeStatus = dynamic_cast<IWinStatus*>(basePtr);
iconPath = ":/Icons/serverwindow.png";
break;
case IWin::Type::Channel:
basePtr = new IWinChannel(statusParent, buttonText);
iconPath = ":/Icons/channel.png";
break;
case IWin::Type::Private:
basePtr = new IWinPrivate(statusParent, buttonText);
iconPath = ":/Icons/private.png";
break;
case IWin::Type::Custom:
[[fallthrough]]; // Use createCustomSubwindow; treat this as an error.
case IWin::Type::Undefined:
break;
case IWin::Type::Status:
basePtr = new IWinStatus(buttonText);
if (!m_activeStatus || activate)
m_activeStatus = dynamic_cast<IWinStatus*>(basePtr);
iconPath = ":/Icons/serverwindow.png";
break;
case IWin::Type::Channel:
basePtr = new IWinChannel(statusParent, buttonText);
iconPath = ":/Icons/channel.png";
break;
case IWin::Type::Private:
basePtr = new IWinPrivate(statusParent, buttonText);
iconPath = ":/Icons/private.png";
break;
default:
break;
}
if (!basePtr) {
qWarning() << "Failed to create subwindow of type" << IWin::TypeString[windowType] << "with button text" << buttonText;
qWarning() << "Failed to create regular subwindow of type" << IWin::TypeString[windowType] << "with button text" << buttonText;
return nullptr;
}
connect(basePtr, &IWin::aboutToClose,
this, &MdiManager::subwinAboutToClose);
mdiwin = m_mdiArea.addSubWindow(basePtr, Qt::SubWindow);
if (!iconPath.isEmpty())
mdiwin->setWindowIcon(QIcon(iconPath));
mdiwin->setAttribute(Qt::WA_DeleteOnClose);
m_bbMgr.addButton(basePtr, mdiwin);
m_bbMgr.highlight(basePtr, buttonHighlight);
return createSubwindowCommons(parent, basePtr, buttonText, iconPath, activate, buttonHighlight);
}
QString networkKey, windowKey;
if (windowType == IWin::Type::Channel || windowType == IWin::Type::Private) {
const auto& connection = dynamic_cast<IWinStatus*>(parent)->getConnection();
IWin* MdiManager::createDCCSubwindow(IWin* parent, IWin::Type windowType, std::shared_ptr<DCC> dcc, const IRCPrefix& sender)
{
IWin* basePtr{ nullptr };
QString iconPath;
auto targetNickname{ QString::fromStdString(sender.toString()) };
try {
networkKey = QString::fromStdString(connection.isupport().at("NETWORK"));
}
catch (...) {}
switch (windowType) {
case IWin::Type::DCCChat:
basePtr = new IWinDCCChat(std::move(dcc), targetNickname);
iconPath = ":/Icons/private.png";
break;
windowKey = buttonText;
default:
break;
}
QRect spawnCoord = generateSpawnCoordinates(networkKey, windowKey);
mdiwin->setGeometry(spawnCoord);
qInfo() << "Created a subwindow of type" << IWin::TypeString[windowType] << "with button text" << buttonText;
if (!m_active || m_active->isMaximized())
mdiwin->showMaximized();
else
mdiwin->show();
if (!activate) {
m_mdiArea.activatePreviousSubWindow();
mdiwin->lower();
if (!basePtr) {
qWarning() << "Failed to create DCC subwindow of type" << IWin::TypeString[windowType] << "with target" << QString::fromStdString(sender.toString());
return nullptr;
}
if (windowType == IWin::Type::Status)
emit statusWindowCreated(dynamic_cast<IWinStatus*>(basePtr));
return basePtr;
return createSubwindowCommons(parent, basePtr, QStringLiteral("DCC: %1").arg(targetNickname), iconPath, true, HL_None);
}
IWin* MdiManager::currentWindow() const
@ -290,6 +283,55 @@ void MdiManager::printToAll(IWinStatus* parent, const PrintType ptype, const QSt
}
}
IWin* MdiManager::createSubwindowCommons(IWin* parent, IWin* window, const QString& buttonText, const QString& iconPath, bool activate, Highlight buttonHighlight)
{
const auto windowType = window->getType();
connect(window, &IWin::aboutToClose,
this, &MdiManager::subwinAboutToClose);
QMdiSubWindow* mdiwin{ m_mdiArea.addSubWindow(window, Qt::SubWindow) };
if (!iconPath.isEmpty())
mdiwin->setWindowIcon(QIcon(iconPath));
mdiwin->setAttribute(Qt::WA_DeleteOnClose);
m_bbMgr.addButton(window, mdiwin);
m_bbMgr.highlight(window, buttonHighlight);
QString networkKey, windowKey;
if (windowType == IWin::Type::Channel || windowType == IWin::Type::Private) {
const auto& connection = dynamic_cast<IWinStatus*>(parent)->getConnection();
try {
networkKey = QString::fromStdString(connection.isupport().at("NETWORK"));
}
catch (...) {}
windowKey = buttonText;
}
QRect spawnCoord = generateSpawnCoordinates(networkKey, windowKey);
mdiwin->setGeometry(spawnCoord);
qInfo() << "Created a subwindow of type" << IWin::TypeString[windowType] << "with button text" << buttonText;
if (!m_active || m_active->isMaximized())
mdiwin->showMaximized();
else
mdiwin->show();
if (!activate) {
m_mdiArea.activatePreviousSubWindow();
mdiwin->lower();
}
if (windowType == IWin::Type::Status)
emit statusWindowCreated(dynamic_cast<IWinStatus*>(window));
return window;
}
void MdiManager::showTray(const QString& title, const QString& message, QSystemTrayIcon::MessageIcon icon)
{
if (QApplication::activeWindow() || QApplication::focusWidget()) return;
@ -386,4 +428,3 @@ QList<QMdiSubWindow*> MdiManager::mdiChildren() const
ret << mdiwin;
return ret;
}

@ -9,6 +9,7 @@
#define MDIMANAGER_H
#include "IWin/IWin.h"
#include "IRC.h"
#include "ButtonbarMgr.h"
#include <QObject>
#include <QToolBar>
@ -28,10 +29,12 @@ class MdiManager : public QObject
public:
MdiManager(QMdiArea& mdiArea, QToolBar& buttonBar, QSystemTrayIcon& trayIcon);
~MdiManager();
static MdiManager& instance();
IWin* createSubwindow(IWin* parent, const QString& buttonText, IWin::Type windowType, bool activate = true, Highlight buttonHighlight = HL_None);
IWin* createDCCSubwindow(IWin* parent, IWin::Type windowType, std::shared_ptr<DCC> dcc, const IRCPrefix& sender);
IWin* currentWindow() const;
IWinStatus* currentStatus() const;
@ -71,6 +74,8 @@ private:
IWin* m_active{ nullptr };
ButtonbarMgr m_bbMgr;
IWin* createSubwindowCommons(IWin* parent, IWin* window, const QString& buttonText, const QString& iconPath, bool activate, Highlight buttonHighlight);
void showTray(const QString& title, const QString& message, QSystemTrayIcon::MessageIcon icon);
void subwinAboutToClose(IWin* who);
void subwinActivated(QMdiSubWindow *window);

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

@ -15,5 +15,6 @@
<file>Icons/connect.png</file>
<file>Icons/disconnect.png</file>
<file>Icons/network.png</file>
<file>Icons/dccchat.png</file>
</qresource>
</RCC>

Loading…
Cancel
Save