#2 Various improvements on existing, albeit incomplete DCC implementation in Core. DCC Chat implementation. DCC request dialog.
parent
c0c6a6eee1
commit
523e21d3db
@ -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
|
@ -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><html><head/><body><p><span style=" font-weight:600;">Inbound DCC queries<br/></span>Note: Accepting will expose your IP address to the requester!</p></body></html></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
|
After Width: | Height: | Size: 7.1 KiB |
Loading…
Reference in new issue