The complete source code of IdealIRC
http://www.idealirc.org/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
368 lines
9.8 KiB
368 lines
9.8 KiB
/*
|
|
* 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 "ServerModel.h"
|
|
#include "config.h"
|
|
#include <QJsonArray>
|
|
#include <QFile>
|
|
#include <QDebug>
|
|
#include <QTimer>
|
|
#include <QIcon>
|
|
|
|
namespace {
|
|
QJsonObject createJsonObject(const QString& name, const QString& address, const QString& password, bool isSsl)
|
|
{
|
|
namespace Key = ServerJsonKey;
|
|
|
|
const auto hostport{ address.split(':') };
|
|
const auto host{ hostport[0] };
|
|
const auto port{ hostport.count() > 1 ? hostport[1].toShort()
|
|
: (isSsl ? 6697 : 6667) };
|
|
QJsonObject obj;
|
|
|
|
obj[Key::Name] = name;
|
|
obj[Key::Host] = host;
|
|
obj[Key::Port] = port;
|
|
obj[Key::SSL] = isSsl;
|
|
obj[Key::Password] = password;
|
|
|
|
return obj;
|
|
}
|
|
}
|
|
|
|
ServerModel::ServerModel(QObject* parent) :
|
|
QAbstractItemModel(parent)
|
|
{
|
|
QJsonDocument jsondoc;
|
|
|
|
const auto path{ QStringLiteral("%1/servers.json").arg(LOCAL_PATH) };
|
|
QFile f{ path };
|
|
if (f.open(QIODevice::ReadOnly)) {
|
|
const auto jsonData{ f.readAll() };
|
|
qDebug() << "Read" << jsonData.size() << "bytes from" << path;
|
|
jsondoc = QJsonDocument::fromJson(jsonData);
|
|
|
|
f.close();
|
|
|
|
if (!jsondoc.isObject())
|
|
qWarning() << "Parse error for servers.json";
|
|
else
|
|
createItems(jsondoc.object());
|
|
}
|
|
else
|
|
qWarning() << "Unable to open servers.json:" << path;
|
|
|
|
}
|
|
|
|
void ServerModel::saveToJsonFile(const QString& fileName) const
|
|
{
|
|
|
|
}
|
|
|
|
Qt::ItemFlags ServerModel::flags(const QModelIndex& index) const
|
|
{
|
|
/* Don't care about invalid indexes */
|
|
if (!index.isValid())
|
|
return Qt::NoItemFlags;
|
|
|
|
/* Columns containing widgets for editing */
|
|
if (index.column() > 1)
|
|
return Qt::ItemIsEnabled | Qt::ItemIsEditable;
|
|
|
|
/* Directly editable columns */
|
|
Qt::ItemFlags flags{ Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsEnabled };
|
|
|
|
const auto* item = static_cast<const ServerItem*>(index.internalPointer());
|
|
if (!item->isNetwork())
|
|
flags |= Qt::ItemNeverHasChildren;
|
|
|
|
return flags;
|
|
}
|
|
|
|
QVariant ServerModel::data(const QModelIndex& index, int role) const
|
|
{
|
|
if (!index.isValid())
|
|
return {};
|
|
|
|
if (role == Qt::DisplayRole || role == Qt::EditRole) {
|
|
const auto* item = static_cast<const ServerItem*>(index.internalPointer());
|
|
|
|
if (index.column() == 0)
|
|
return item->name();
|
|
|
|
else if (index.column() == 1)
|
|
return item->address();
|
|
|
|
else
|
|
return {};
|
|
}
|
|
|
|
if (role == Qt::DecorationRole && index.column() == 0) {
|
|
const auto* item = static_cast<const ServerItem*>(index.internalPointer());
|
|
if (item->isNetwork())
|
|
return QIcon(":/Icons/network.png");
|
|
else
|
|
return QIcon(":/Icons/serverwindow.png");
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
QVariant ServerModel::headerData(int section, Qt::Orientation orientation, int role) const
|
|
{
|
|
if (role == Qt::DisplayRole && orientation == Qt::Horizontal) {
|
|
QStringList labels {
|
|
tr("Name"),
|
|
tr("Address"),
|
|
"",
|
|
""
|
|
};
|
|
|
|
return labels[section];
|
|
}
|
|
else {
|
|
return {};
|
|
}
|
|
}
|
|
|
|
bool ServerModel::setData(const QModelIndex& index, const QVariant& value, int role)
|
|
{
|
|
if (!index.isValid() || role != Qt::EditRole)
|
|
return false;
|
|
|
|
auto* item = static_cast<ServerItem*>(index.internalPointer());
|
|
|
|
bool ret{ true };
|
|
switch (index.column()) {
|
|
case 0:
|
|
if (value.toString().isEmpty())
|
|
ret = false;
|
|
else
|
|
item->setName(value.toString());
|
|
break;
|
|
|
|
case 1:
|
|
if (value.toString().isEmpty())
|
|
ret = false;
|
|
else
|
|
item->setAddress(value.toString());
|
|
break;
|
|
|
|
default:
|
|
ret = false;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int ServerModel::rowCount(const QModelIndex& parent) const
|
|
{
|
|
if (!parent.isValid())
|
|
return m_items.count();
|
|
else {
|
|
auto* parentItem = static_cast<ServerItem*>(parent.internalPointer());
|
|
return parentItem->children().count();
|
|
}
|
|
}
|
|
|
|
QModelIndex ServerModel::index(int row, int column, const QModelIndex& parent) const
|
|
{
|
|
if (!hasIndex(row, column, parent))
|
|
return {};
|
|
|
|
ServerItem* item{};
|
|
|
|
if (!parent.isValid())
|
|
item = &m_items[row];
|
|
else {
|
|
auto* parentItem = static_cast<ServerItem*>(parent.internalPointer());
|
|
item = &parentItem->children()[row];
|
|
}
|
|
|
|
return createIndex(row, column, item);
|
|
}
|
|
|
|
QModelIndex ServerModel::parent(const QModelIndex& index) const
|
|
{
|
|
if (!index.isValid())
|
|
return {};
|
|
|
|
auto* parentItem = static_cast<ServerItem*>(index.internalPointer())->parent();
|
|
if (!parentItem)
|
|
return {};
|
|
|
|
return createIndex(parentItem->row(), 0, parentItem);
|
|
}
|
|
|
|
void ServerModel::addServer(const QString& name, const QString& address, const QString& password, bool isSsl, int networkIdx)
|
|
{
|
|
QList<ServerItem>* items{};
|
|
ServerItem* parentNetworkItem{};
|
|
int pos{};
|
|
|
|
QModelIndex modelIdx;
|
|
|
|
/* Network server */
|
|
if (networkIdx > -1) {
|
|
modelIdx = networkIndex(networkIdx);
|
|
parentNetworkItem = static_cast<ServerItem*>(modelIdx.internalPointer());
|
|
items = &(parentNetworkItem->children());
|
|
pos = items->count();
|
|
}
|
|
|
|
/* Non-network server */
|
|
else {
|
|
items = &m_items;
|
|
|
|
for (auto& item : m_items) {
|
|
if (item.isNetwork())
|
|
item.incRow(); // Increment row number for each network, since any non-network server is inserted before these.
|
|
else
|
|
++pos; // Finds the end position of non-network servers. Add server there (bottom of non-networks; before networks are listed.)
|
|
}
|
|
}
|
|
|
|
const auto obj{ createJsonObject(name, address, password, isSsl) };
|
|
beginInsertRows(modelIdx, pos, pos);
|
|
items->insert(pos, ServerItem(obj, pos, parentNetworkItem, false));
|
|
endInsertRows();
|
|
|
|
emit newEntry(
|
|
createIndex(pos, 2, &((*items)[pos])), // SSL
|
|
createIndex(pos, 3, &((*items)[pos])) // Password
|
|
);
|
|
|
|
/*
|
|
* A hack to make new child items appear if parent's expanded.
|
|
* This also "solves" when a new/empty network gets its first entry, the network item (parent) won't get the "expand/collapse" decoration.
|
|
* Doing this should not be necessary, so there is an issue with this ServerModel, somewhere...
|
|
*/
|
|
QTimer::singleShot(1, [this] {
|
|
auto pos = m_items.size();
|
|
beginInsertRows(QModelIndex{}, pos, pos);
|
|
m_items << ServerItem({}, pos, nullptr, false);
|
|
endInsertRows();
|
|
|
|
QTimer::singleShot(1, [this] {
|
|
auto pos = m_items.size() - 1;
|
|
beginRemoveRows(QModelIndex{}, pos, pos);
|
|
m_items.removeLast();
|
|
endRemoveRows();
|
|
});
|
|
});
|
|
}
|
|
|
|
void ServerModel::addNetwork(const QString& name, const QString& address, const QString& password, bool isSsl)
|
|
{
|
|
const auto obj{ createJsonObject(name, address, password, isSsl) };
|
|
|
|
const auto idx{ m_items.count() };
|
|
beginInsertRows(QModelIndex{}, idx, idx);
|
|
m_items << ServerItem(obj, idx, nullptr, true);
|
|
endInsertRows();
|
|
|
|
emit newEntry(
|
|
createIndex(idx, 2, &(m_items.back())), // SSL
|
|
createIndex(idx, 3, &(m_items.back())) // Password
|
|
);
|
|
}
|
|
|
|
void ServerModel::deleteEntry(const QModelIndex& index)
|
|
{
|
|
auto* item = static_cast<ServerItem*>(index.internalPointer());
|
|
auto* parent = item->parent();
|
|
QList<ServerItem>* items{};
|
|
|
|
if (parent == nullptr)
|
|
items = &m_items;
|
|
else
|
|
items = &(parent->children());
|
|
|
|
beginRemoveRows(index.parent(), index.row(), index.row());
|
|
|
|
items->removeAt(item->row());
|
|
for (int i = item->row(); i < items->count(); ++i)
|
|
(*items)[i].decRow();
|
|
|
|
endRemoveRows();
|
|
}
|
|
|
|
QStringList ServerModel::getNetworks() const
|
|
{
|
|
QStringList networks;
|
|
|
|
for (const auto& item : m_items) {
|
|
if (item.isNetwork())
|
|
networks << item.name();
|
|
}
|
|
|
|
return networks;
|
|
}
|
|
|
|
QList<QModelIndex> ServerModel::getEditorColumns()
|
|
{
|
|
QList<QModelIndex> indexList;
|
|
|
|
for (auto& topItem : m_items) {
|
|
indexList << createIndex(topItem.row(), 2, &topItem);
|
|
indexList << createIndex(topItem.row(), 3, &topItem);
|
|
|
|
if (topItem.isNetwork()) {
|
|
auto& items = topItem.children();
|
|
for (auto& item : items) {
|
|
indexList << createIndex(item.row(), 2, &item);
|
|
indexList << createIndex(item.row(), 3, &item);
|
|
}
|
|
}
|
|
}
|
|
|
|
return indexList;
|
|
}
|
|
|
|
QModelIndex ServerModel::networkIndex(int row, int col)
|
|
{
|
|
int rc{ 0 };
|
|
for (auto& item : m_items) {
|
|
if (!item.isNetwork())
|
|
continue;
|
|
|
|
if (rc == row)
|
|
return createIndex(row, col, &item);
|
|
else
|
|
++rc;
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
void ServerModel::createItems(const QJsonObject& json)
|
|
{
|
|
namespace Key = ServerJsonKey;
|
|
|
|
const auto servers = json[Key::Servers].toArray();
|
|
const auto networks = json[Key::Networks].toArray();
|
|
|
|
int rootRow{ 0 };
|
|
|
|
for (const auto& server : servers) {
|
|
m_items << ServerItem(server.toObject(), rootRow++, nullptr, false);
|
|
}
|
|
|
|
for (const auto& network : networks) {
|
|
const auto networkObj = network.toObject();
|
|
const auto childServers = networkObj[Key::Servers].toArray();
|
|
|
|
m_items << ServerItem(networkObj, rootRow++, nullptr, true);
|
|
auto& networkItem = m_items.back();
|
|
|
|
int childRow{ 0 };
|
|
for (const auto& cs : childServers) {
|
|
auto& networkServers = networkItem.children();
|
|
networkServers << ServerItem(cs.toObject(), childRow++, &networkItem, false);
|
|
}
|
|
}
|
|
}
|
|
|