@ -0,0 +1,20 @@ |
||||
*.slo |
||||
*.lo |
||||
*.o |
||||
*.a |
||||
*.la |
||||
*.lai |
||||
*.so |
||||
*.dll |
||||
*.dylib |
||||
*.pro.user |
||||
*.pro.user.* |
||||
moc_*.cpp |
||||
qrc_*.cpp |
||||
Makefile |
||||
*-build-* |
||||
build |
||||
config.h |
||||
*.autosave |
||||
.idea |
||||
obsolete |
@ -0,0 +1,49 @@ |
||||
/*
|
||||
* IdealIRC - Internet Relay Chat client |
||||
* Copyright (C) 2019 Tom-Andre Barstad |
||||
* |
||||
* This program is free software; you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation; either version 2 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License along |
||||
* with this program; if not, write to the Free Software Foundation, Inc., |
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
||||
*/ |
||||
|
||||
#include "AboutIIRC.h" |
||||
#include "ui_AboutIIRC.h" |
||||
#include "config.h" |
||||
#include <QDateTime> |
||||
|
||||
AboutIIRC::AboutIIRC(QWidget *parent) : |
||||
QDialog(parent), |
||||
ui(new Ui::AboutIIRC) |
||||
{ |
||||
ui->setupUi(this); |
||||
|
||||
QDateTime cur = QDateTime::currentDateTime(); |
||||
QString year = QString::number(cur.date().year()); |
||||
|
||||
QString text = ui->textBrowser->toHtml(); |
||||
text = text.replace("{year}", year); |
||||
text = text.replace("{ver}", VERSION_STRING); |
||||
|
||||
if constexpr (BUILD_TYPE == BuildType::packaged) |
||||
text = text.replace("{buildtype}", QStringLiteral("Packaged")); |
||||
else if constexpr (BUILD_TYPE == BuildType::standalone) |
||||
text = text.replace("{buildtype}", QStringLiteral("Stand-alone")); |
||||
|
||||
ui->textBrowser->setHtml(text); |
||||
} |
||||
|
||||
AboutIIRC::~AboutIIRC() |
||||
{ |
||||
delete ui; |
||||
} |
@ -0,0 +1,41 @@ |
||||
/*
|
||||
* IdealIRC - Internet Relay Chat client |
||||
* Copyright (C) 2019 Tom-Andre Barstad |
||||
* |
||||
* This program is free software; you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation; either version 2 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License along |
||||
* with this program; if not, write to the Free Software Foundation, Inc., |
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
||||
*/ |
||||
|
||||
#ifndef ABOUTIIRC_H |
||||
#define ABOUTIIRC_H |
||||
|
||||
#include <QDialog> |
||||
|
||||
namespace Ui { |
||||
class AboutIIRC; |
||||
} |
||||
|
||||
class AboutIIRC : public QDialog |
||||
{ |
||||
Q_OBJECT |
||||
|
||||
public: |
||||
explicit AboutIIRC(QWidget *parent = nullptr); |
||||
~AboutIIRC(); |
||||
|
||||
private: |
||||
Ui::AboutIIRC *ui; |
||||
}; |
||||
|
||||
#endif // ABOUTIIRC_H
|
@ -0,0 +1,105 @@ |
||||
<?xml version="1.0" encoding="UTF-8"?> |
||||
<ui version="4.0"> |
||||
<class>AboutIIRC</class> |
||||
<widget class="QDialog" name="AboutIIRC"> |
||||
<property name="geometry"> |
||||
<rect> |
||||
<x>0</x> |
||||
<y>0</y> |
||||
<width>425</width> |
||||
<height>490</height> |
||||
</rect> |
||||
</property> |
||||
<property name="minimumSize"> |
||||
<size> |
||||
<width>425</width> |
||||
<height>490</height> |
||||
</size> |
||||
</property> |
||||
<property name="maximumSize"> |
||||
<size> |
||||
<width>425</width> |
||||
<height>490</height> |
||||
</size> |
||||
</property> |
||||
<property name="windowTitle"> |
||||
<string>About IdealIRC</string> |
||||
</property> |
||||
<property name="windowIcon"> |
||||
<iconset resource="resources.qrc"> |
||||
<normaloff>:/Icons/iconcutted.png</normaloff>:/Icons/iconcutted.png</iconset> |
||||
</property> |
||||
<layout class="QGridLayout" name="gridLayout"> |
||||
<item row="0" column="0" colspan="2"> |
||||
<widget class="QTextBrowser" name="textBrowser"> |
||||
<property name="html"> |
||||
<string><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> |
||||
<html><head><meta name="qrichtext" content="1" /><style type="text/css"> |
||||
p, li { white-space: pre-wrap; } |
||||
</style></head><body style=" font-family:'Sans'; font-size:9pt; font-weight:400; font-style:normal;"> |
||||
<p align="center" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><img src=":/Icons/icon.png" /></p> |
||||
<p align="center" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Noto Sans'; font-size:16pt;">IdealIRC {ver}</span></p> |
||||
<p align="center" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans';">Build type: {buildtype}</span></p> |
||||
<p align="center" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Noto Sans'; font-size:10pt;">©{year} Tom-Andre Barstad</span></p> |
||||
<p align="center" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Noto Sans'; font-size:10pt;">and contributors.</span></p> |
||||
<p align="center" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><a href="http://www.idealirc.org/"><span style=" font-family:'Sans'; font-size:8pt; text-decoration: underline; color:#007af4;">http://www.idealirc.org/</span></a></p> |
||||
<p align="center" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Noto Sans'; font-size:8pt;"><br /></p> |
||||
<p align="center" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Noto Sans'; font-size:8pt;">This program is free software; you can redistribute it and/or modify</span></p> |
||||
<p align="center" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Noto Sans'; font-size:8pt;">it under the terms of the GNU General Public License as published by</span></p> |
||||
<p align="center" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Noto Sans'; font-size:8pt;">the Free Software Foundation; either version 2 of the License, or</span></p> |
||||
<p align="center" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Noto Sans'; font-size:8pt;">(at your option) any later version.</span></p> |
||||
<p align="center" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Noto Sans'; font-size:8pt;"><br /></p> |
||||
<p align="center" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Noto Sans'; font-size:8pt;">This program is distributed in the hope that it will be useful,</span></p> |
||||
<p align="center" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Noto Sans'; font-size:8pt;">but WITHOUT ANY WARRANTY; without even the implied warranty of</span></p> |
||||
<p align="center" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Noto Sans'; font-size:8pt;">MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the</span></p> |
||||
<p align="center" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Noto Sans'; font-size:8pt;">GNU General Public License for more details.</span></p></body></html></string> |
||||
</property> |
||||
<property name="openExternalLinks"> |
||||
<bool>true</bool> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
<item row="1" column="0"> |
||||
<spacer name="horizontalSpacer"> |
||||
<property name="orientation"> |
||||
<enum>Qt::Horizontal</enum> |
||||
</property> |
||||
<property name="sizeHint" stdset="0"> |
||||
<size> |
||||
<width>439</width> |
||||
<height>20</height> |
||||
</size> |
||||
</property> |
||||
</spacer> |
||||
</item> |
||||
<item row="1" column="1"> |
||||
<widget class="QPushButton" name="pushButton"> |
||||
<property name="text"> |
||||
<string>Close</string> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
</layout> |
||||
</widget> |
||||
<resources> |
||||
<include location="resources.qrc"/> |
||||
</resources> |
||||
<connections> |
||||
<connection> |
||||
<sender>pushButton</sender> |
||||
<signal>clicked()</signal> |
||||
<receiver>AboutIIRC</receiver> |
||||
<slot>close()</slot> |
||||
<hints> |
||||
<hint type="sourcelabel"> |
||||
<x>466</x> |
||||
<y>452</y> |
||||
</hint> |
||||
<hint type="destinationlabel"> |
||||
<x>430</x> |
||||
<y>444</y> |
||||
</hint> |
||||
</hints> |
||||
</connection> |
||||
</connections> |
||||
</ui> |
@ -0,0 +1,274 @@ |
||||
#include "ButtonbarMgr.h" |
||||
|
||||
int ButtonbarMgr::WindowTypeToGroupPos(const IWin* subwin) |
||||
{ |
||||
switch (subwin->getType()) { |
||||
case IWin::Type::Status: |
||||
return 1; |
||||
case IWin::Type::Channel: |
||||
return 2; |
||||
case IWin::Type::Private: |
||||
return 3; |
||||
default: |
||||
return 0; |
||||
} |
||||
} |
||||
|
||||
ButtonbarMgr::ButtonbarMgr(QToolBar *parent) |
||||
: QObject(parent) |
||||
, m_buttonBar(*parent) |
||||
{ |
||||
m_buttonBar.setContextMenuPolicy(Qt::CustomContextMenu); |
||||
connect(&m_buttonBar, &QToolBar::customContextMenuRequested, this, &ButtonbarMgr::buttonBarContextMenu); |
||||
m_others_rightSep = m_buttonBar.addSeparator(); |
||||
} |
||||
|
||||
void ButtonbarMgr::addButton(IWin* subwin, QMdiSubWindow* mdiwin) |
||||
{ |
||||
QAction* nn = findNextNeighbour(subwin); |
||||
QAction *actn = new QAction(subwin->getButtonText(), &m_buttonBar); |
||||
m_buttonBar.insertAction(nn, actn); |
||||
m_winbtn.insert(subwin, actn); |
||||
actn->setCheckable(true); |
||||
buttons(subwin).push_back(Button(actn, mdiwin)); |
||||
connect(actn, &QAction::triggered, [this,actn,mdiwin](bool triggered){ |
||||
if (!triggered) { |
||||
actn->setChecked(true); |
||||
return; |
||||
} |
||||
emit changeWindow(mdiwin); |
||||
}); |
||||
} |
||||
|
||||
void ButtonbarMgr::delButton(IWin* subwin) |
||||
{ |
||||
auto clear = [this](Button& button){ |
||||
m_buttonBar.removeAction(button.button); |
||||
button.menu->deleteLater(); |
||||
button.button->deleteLater(); |
||||
}; |
||||
|
||||
m_winbtn.remove(subwin); |
||||
if (subwin->getType() != IWin::Type::Status |
||||
&& subwin->getType() != IWin::Type::Channel |
||||
&& subwin->getType() != IWin::Type::Private) |
||||
{ |
||||
int otherPos = buttonPosition(m_others, subwin); |
||||
if (otherPos > -1) { |
||||
clear(m_others[otherPos]); |
||||
m_others.removeAt(otherPos); |
||||
} |
||||
return; |
||||
} |
||||
|
||||
if (subwin->getType() == IWin::Type::Status) { |
||||
deleteGroup(subwin); |
||||
return; |
||||
} |
||||
|
||||
ButtonGroup* group = tryFindGroup(subwin); |
||||
if (!group) { |
||||
return; |
||||
} |
||||
|
||||
for (int i = 0; i < group->size; ++i) { |
||||
QList<Button>& buttons = (*group)[i]; |
||||
int buttonPos = buttonPosition(buttons, subwin); |
||||
if (buttonPos < 0) continue; |
||||
|
||||
Button& button = buttons[buttonPos]; |
||||
if (button.subwin->widget() == subwin) { |
||||
clear(button); |
||||
buttons.removeAt(buttonPos); |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
|
||||
void ButtonbarMgr::reloadButtonName(IWin* subwin, const QString& buttonText) |
||||
{ |
||||
Button* button = findButton(subwin); |
||||
if (button) { |
||||
button->button->setText(buttonText); |
||||
button->menuHead->setText(buttonText); |
||||
} |
||||
} |
||||
|
||||
void ButtonbarMgr::subwinActivated(IWin* prev, IWin* next) |
||||
{ |
||||
if (prev) { |
||||
Button* button{ findButton(prev) }; |
||||
if (button) |
||||
button->button->setChecked(false); |
||||
} |
||||
if (next) { |
||||
Button* button{ findButton(next) }; |
||||
if (button) |
||||
button->button->setChecked(true); |
||||
} |
||||
} |
||||
|
||||
void ButtonbarMgr::buttonBarContextMenu(const QPoint& pos) |
||||
{ |
||||
QAction* action = m_buttonBar.actionAt(pos); |
||||
if (!action) |
||||
return; |
||||
|
||||
IWin *subwin = m_winbtn.key(action); |
||||
if (!subwin) |
||||
return; |
||||
|
||||
Button* button = findButton(subwin); |
||||
if (!button) |
||||
return; |
||||
|
||||
QPoint gpos = m_buttonBar.mapToGlobal(pos); |
||||
button->menu->popup(gpos); |
||||
} |
||||
|
||||
ButtonbarMgr::ButtonGroup* ButtonbarMgr::tryFindGroup(IWin* subwin) |
||||
{ |
||||
auto lcontains = [](const QList<Button>& list, IWin* sw){ |
||||
for (const auto& item : list) { |
||||
if (item.subwin->widget() == sw) |
||||
return true; |
||||
} |
||||
return false; |
||||
}; |
||||
|
||||
ButtonGroup* null_grp{ nullptr }; |
||||
for (auto& group : m_groups) { |
||||
for (int i = 0; i < group.size; ++i) { |
||||
auto& list = group[i]; |
||||
if (lcontains(list, subwin)) |
||||
return &group; |
||||
} |
||||
} |
||||
return null_grp; |
||||
} |
||||
|
||||
QList<ButtonbarMgr::Button>& ButtonbarMgr::buttons(IWin* subwin) |
||||
{ |
||||
if (subwin->getType() != IWin::Type::Status |
||||
&& subwin->getType() != IWin::Type::Channel |
||||
&& subwin->getType() != IWin::Type::Private) |
||||
{ |
||||
return m_others; |
||||
} |
||||
|
||||
ButtonGroup* group = tryFindGroup(subwin); |
||||
if (group) |
||||
return (*group)[static_cast<int>(subwin->getType())-1]; |
||||
|
||||
if (subwin->getType() == IWin::Type::Status) { |
||||
m_groups.emplace_back(); |
||||
m_groups.back().rightSep = m_buttonBar.addSeparator(); |
||||
return m_groups.back().status; |
||||
} |
||||
|
||||
IWin* parent = subwin->getParent(); |
||||
group = tryFindGroup(parent); |
||||
if (!group) |
||||
return m_others; |
||||
|
||||
if (subwin->getType() == IWin::Type::Channel) |
||||
return group->channel; |
||||
|
||||
else // Implicit "Private"
|
||||
return group->privmsg; |
||||
} |
||||
|
||||
QAction* ButtonbarMgr::findNextNeighbour(IWin* subwin) |
||||
{ |
||||
if (subwin->getType() == IWin::Type::Status || subwin->getType() == IWin::Type::Private) { |
||||
ButtonGroup* group = tryFindGroup((subwin->getType() == IWin::Type::Status) ? subwin : subwin->getParent()); |
||||
return group ? group->rightSep : nullptr; |
||||
} |
||||
|
||||
else if (subwin->getType() == IWin::Type::Channel) { |
||||
ButtonGroup* group = tryFindGroup(subwin->getParent()); |
||||
if (!group) |
||||
return nullptr; |
||||
if (group->privmsg.isEmpty()) |
||||
return group->rightSep; |
||||
else |
||||
return group->privmsg[0].button; |
||||
} |
||||
else |
||||
return m_others_rightSep; |
||||
} |
||||
|
||||
ButtonbarMgr::Button* ButtonbarMgr::findButton(IWin* subwin) |
||||
{ |
||||
if (!subwin) |
||||
return nullptr; |
||||
else { |
||||
ButtonGroup* group = tryFindGroup(subwin); |
||||
if (group) { |
||||
for (int i = 0; i < group->size; ++i) { |
||||
QList<Button>& list = (*group)[i]; |
||||
int bpos = buttonPosition(list, subwin); |
||||
if (bpos > -1) |
||||
return &list[bpos]; |
||||
} |
||||
} |
||||
} |
||||
|
||||
int otherPos = buttonPosition(m_others, subwin); |
||||
if (otherPos < 0) |
||||
return nullptr; |
||||
else { |
||||
Button& button = m_others[otherPos]; |
||||
return &button; |
||||
} |
||||
} |
||||
|
||||
void ButtonbarMgr::deleteGroup(IWin* statuswin) |
||||
{ |
||||
auto it = m_groups.begin(); |
||||
for (; it != m_groups.end(); ++it) { |
||||
if ((*it).status[0].subwin->widget() == qobject_cast<QWidget*>(statuswin)) |
||||
break; |
||||
} |
||||
ButtonGroup& group = *it; |
||||
for (int i = 0; i < group.size; ++i) { |
||||
QList<Button>& list = group[i]; |
||||
for (auto& item : list) { |
||||
item.menuHead->deleteLater(); |
||||
item.menuSep->deleteLater(); |
||||
item.menuClose->deleteLater(); |
||||
item.menu->deleteLater(); |
||||
item.button->deleteLater(); |
||||
} |
||||
} |
||||
group.rightSep->deleteLater(); |
||||
m_groups.erase(it); |
||||
} |
||||
|
||||
int ButtonbarMgr::buttonPosition(QList<ButtonbarMgr::Button>& list, IWin* sw) |
||||
{ |
||||
for (int i = 0; i < list.count(); ++i) { |
||||
ButtonbarMgr::Button& item = list[i]; |
||||
if (item.subwin->widget() == sw) |
||||
return i; |
||||
} |
||||
return -1; |
||||
} |
||||
|
||||
ButtonbarMgr::Button::Button(QAction* button_, QMdiSubWindow* parent) |
||||
: subwin(parent) |
||||
, button(button_) |
||||
{ |
||||
const QString& buttonText = qobject_cast<IWin*>(parent->widget())->getButtonText(); |
||||
menu = new QMenu(parent); |
||||
menuHead = menu->addAction(buttonText); |
||||
QFont f = menuHead->font(); |
||||
f.setBold(true); |
||||
menuHead->setFont(f); |
||||
menuHead->setDisabled(true); |
||||
menuSep = menu->addSeparator(); |
||||
menuClose = menu->addAction(tr("Close")); |
||||
connect(menuClose, &QAction::triggered, [parent](bool){ |
||||
parent->close(); |
||||
}); |
||||
} |
@ -0,0 +1,77 @@ |
||||
#ifndef BUTTONBARMGR_H |
||||
#define BUTTONBARMGR_H |
||||
|
||||
#include <IWin/IWin.h> |
||||
#include <QObject> |
||||
#include <QHash> |
||||
#include <QMdiSubWindow> |
||||
#include <QMenu> |
||||
#include <QToolBar> |
||||
#include <QHash> |
||||
#include <list> |
||||
|
||||
class ButtonbarMgr : public QObject |
||||
{ |
||||
Q_OBJECT |
||||
|
||||
struct Button |
||||
{ |
||||
explicit Button(){} |
||||
Button(QAction* button_, QMdiSubWindow* parent); |
||||
QMdiSubWindow* subwin{ nullptr }; |
||||
QAction* button{ nullptr }; |
||||
QMenu* menu{ nullptr }; |
||||
QAction* menuHead{ nullptr }; |
||||
QAction* menuSep{ nullptr }; |
||||
QAction* menuClose{ nullptr }; |
||||
}; |
||||
|
||||
struct ButtonGroup |
||||
{ |
||||
QList<Button>& operator[](int i) { |
||||
switch (i) { |
||||
case 0: return status; |
||||
case 1: return channel; |
||||
case 2: return privmsg; |
||||
|
||||
default: return blank; |
||||
} |
||||
} |
||||
|
||||
QList<Button> status; |
||||
QList<Button> channel; |
||||
QList<Button> privmsg; |
||||
const int size = 3; |
||||
|
||||
QAction* rightSep; |
||||
QList<Button> blank; |
||||
}; |
||||
|
||||
static int WindowTypeToGroupPos(const IWin* subwin); |
||||
|
||||
public: |
||||
explicit ButtonbarMgr(QToolBar *parent); |
||||
void addButton(IWin* subwin, QMdiSubWindow* mdiwin); |
||||
void delButton(IWin* subwin); |
||||
void reloadButtonName(IWin* subwin, const QString& buttonText); |
||||
void subwinActivated(IWin* prev, IWin* next); |
||||
|
||||
private: |
||||
void buttonBarContextMenu(const QPoint& pos); |
||||
static int buttonPosition(QList<ButtonbarMgr::Button>& list, IWin* sw); |
||||
ButtonGroup* tryFindGroup(IWin* subwin); |
||||
QList<Button>& buttons(IWin* subwin); |
||||
QAction* findNextNeighbour(IWin* subwin); |
||||
Button* findButton(IWin* subwin); |
||||
void deleteGroup(IWin* statuswin); |
||||
QList<Button> m_others; |
||||
QAction* m_others_rightSep; |
||||
std::list<ButtonGroup> m_groups; |
||||
QHash<IWin*,QAction*> m_winbtn; |
||||
QToolBar& m_buttonBar; |
||||
|
||||
signals: |
||||
void changeWindow(QMdiSubWindow* mdiwin); |
||||
}; |
||||
|
||||
#endif // BUTTONBARMGR_H
|
@ -0,0 +1,93 @@ |
||||
/*
|
||||
* IdealIRC - Internet Relay Chat client |
||||
* Copyright (C) 2019 Tom-Andre Barstad |
||||
* |
||||
* This program is free software; you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation; either version 2 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License along |
||||
* with this program; if not, write to the Free Software Foundation, Inc., |
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
||||
*/ |
||||
|
||||
#ifndef COMMANDS_H |
||||
#define COMMANDS_H |
||||
|
||||
namespace Command { |
||||
namespace IRC { |
||||
constexpr auto* PASS = "PASS"; |
||||
constexpr auto* NICK = "NICK"; |
||||
constexpr auto* USER = "USER"; |
||||
constexpr auto* OPER = "OPER"; |
||||
constexpr auto* MODE = "MODE"; |
||||
constexpr auto* QUIT = "QUIT"; |
||||
constexpr auto* SQUIT = "SQUIT"; |
||||
constexpr auto* JOIN = "JOIN"; |
||||
constexpr auto* PART = "PART"; |
||||
constexpr auto* TOPIC = "TOPIC"; |
||||
constexpr auto* NAMES = "NAMES"; |
||||
constexpr auto* LIST = "LIST"; |
||||
constexpr auto* INVITE = "INVITE"; |
||||
constexpr auto* KICK = "KICK"; |
||||
constexpr auto* PRIVMSG = "PRIVMSG"; |
||||
constexpr auto* NOTICE = "NOTICE"; |
||||
constexpr auto* MOTD = "MOTD"; |
||||
constexpr auto* LUSERS = "LUSERS"; |
||||
constexpr auto* VERSION = "VERSION"; |
||||
constexpr auto* STATS = "STATS"; |
||||
constexpr auto* LINKS = "LINKS"; |
||||
constexpr auto* TIME = "TIME"; |
||||
constexpr auto* CONNECT = "CONNECT"; |
||||
constexpr auto* TRACE = "TRACE"; |
||||
constexpr auto* ADMIN = "ADMIN"; |
||||
constexpr auto* INFO = "INFO"; |
||||
constexpr auto* SERVLIST = "SERVLIST"; |
||||
constexpr auto* SQUERY = "SQUERY"; |
||||
constexpr auto* WHO = "WHO"; |
||||
constexpr auto* WHOIS = "WHOIS"; |
||||
constexpr auto* WHOWAS = "WHOWAS"; |
||||
constexpr auto* KILL = "KILL"; |
||||
constexpr auto* PING = "PING"; |
||||
constexpr auto* PONG = "PONG"; |
||||
constexpr auto* ERROR = "ERROR"; |
||||
constexpr auto* AWAY = "AWAY"; |
||||
constexpr auto* REHASH = "REHASH"; |
||||
constexpr auto* DIE = "DIE"; |
||||
constexpr auto* RESTART = "RESTART"; |
||||
constexpr auto* SUMMON = "SUMMON"; |
||||
constexpr auto* USERS = "USERS"; |
||||
constexpr auto* WALLOPS = "WALLOPS"; |
||||
constexpr auto* USERHOST = "USERHOST"; |
||||
constexpr auto* ISON = "ISON"; |
||||
} |
||||
|
||||
namespace Internal { |
||||
constexpr auto* CTCP = "CTCP"; |
||||
constexpr auto* ME = "ME"; |
||||
constexpr auto* ECHO = "ECHO"; |
||||
constexpr auto* QUERY = "QUERY"; |
||||
constexpr auto* MUTERESP = "MUTERESP"; |
||||
constexpr auto* UNMUTERESP = "UNMUTERESP"; |
||||
} |
||||
|
||||
namespace IRCv3 { |
||||
constexpr auto* CAP = "CAP"; |
||||
constexpr auto* LS = "LS"; |
||||
constexpr auto* LIST = "LIST"; |
||||
constexpr auto* REQ = "REQ"; |
||||
constexpr auto* ACK = "ACK"; |
||||
constexpr auto* NAK = "NAK"; |
||||
constexpr auto* END = "END"; |
||||
constexpr auto* NEW = "NEW"; |
||||
constexpr auto* DEL = "DEL"; |
||||
} // namespace IRCv3
|
||||
} // namespace command
|
||||
|
||||
#endif // COMMANDS_H
|
@ -0,0 +1,294 @@ |
||||
#include "ConfigMgr.h" |
||||
#include "config.h" |
||||
#include <QHashIterator> |
||||
|
||||
ConfigMgr& ConfigMgr::instance() |
||||
{ |
||||
static ConfigMgr inst; |
||||
return inst; |
||||
} |
||||
|
||||
// Constructor is private, use static instance()
|
||||
ConfigMgr::ConfigMgr() |
||||
{ |
||||
load(); |
||||
} |
||||
|
||||
void ConfigMgr::load() |
||||
{ |
||||
IniFile ini(LOCAL_PATH+"/iirc.ini"); |
||||
loadGeometry(ini); |
||||
loadConnection(ini); |
||||
loadCommon(ini); |
||||
loadColor(ini); |
||||
loadPrefixColor(ini); |
||||
loadLogging(ini); |
||||
loadScripts(ini); |
||||
} |
||||
|
||||
void ConfigMgr::save() |
||||
{ |
||||
IniFile ini(LOCAL_PATH+"/iirc.ini"); |
||||
saveGeometry(ini); |
||||
saveConnection(ini); |
||||
saveCommon(ini); |
||||
saveColor(ini); |
||||
savePrefixColor(ini); |
||||
saveLogging(ini); |
||||
saveScripts(ini); |
||||
emit saved(); |
||||
} |
||||
|
||||
QString ConfigMgr::geometry(const QString& key) const |
||||
{ |
||||
return geometryData.value(key, ""); |
||||
} |
||||
|
||||
QString ConfigMgr::connection(const QString& key) const |
||||
{ |
||||
return connectionData.value(key, ""); |
||||
} |
||||
|
||||
QString ConfigMgr::common(const QString& key) const |
||||
{ |
||||
return commonData.value(key, ""); |
||||
} |
||||
|
||||
QString ConfigMgr::color(const QString& key) const |
||||
{ |
||||
return colorData.value(key, ""); |
||||
} |
||||
|
||||
std::optional<QColor> ConfigMgr::prefixColor(const QChar prefix) const |
||||
{ |
||||
for (const auto& pair : prefixColorData) |
||||
if (pair.first == prefix) |
||||
return std::make_optional(pair.second); |
||||
return std::nullopt; |
||||
} |
||||
|
||||
QHash<QString, QString> ConfigMgr::color() const |
||||
{ |
||||
return colorData; |
||||
} |
||||
|
||||
QVector<std::pair<QChar, QColor> > ConfigMgr::prefixColor() const |
||||
{ |
||||
return prefixColorData; |
||||
} |
||||
|
||||
QString ConfigMgr::logging(const QString& key) const |
||||
{ |
||||
return loggingData.value(key, ""); |
||||
} |
||||
|
||||
const QStringList& ConfigMgr::scripts() const |
||||
{ |
||||
return scriptsData; |
||||
} |
||||
|
||||
void ConfigMgr::setGeometry(const QString& key, const QString& value) |
||||
{ |
||||
if (geometryData.contains(key)) |
||||
geometryData.insert(key, value); |
||||
} |
||||
|
||||
void ConfigMgr::setConnection(const QString& key, const QString& value) |
||||
{ |
||||
if (connectionData.contains(key)) |
||||
connectionData.insert(key, value); |
||||
} |
||||
|
||||
void ConfigMgr::setCommon(const QString& key, const QString& value) |
||||
{ |
||||
if (commonData.contains(key)) |
||||
commonData.insert(key, value); |
||||
} |
||||
|
||||
void ConfigMgr::setLogging(const QString& key, const QString& value) |
||||
{ |
||||
if (loggingData.contains(key)) |
||||
loggingData.insert(key, value); |
||||
} |
||||
|
||||
void ConfigMgr::setColorPalette(const QHash<QString, QString>& palette) |
||||
{ |
||||
colorData = palette; |
||||
} |
||||
|
||||
void ConfigMgr::setPrefixColorPalette(const QVector<std::pair<QChar, QColor>>& palette) |
||||
{ |
||||
prefixColorData = palette; |
||||
} |
||||
|
||||
void ConfigMgr::addScript(const QString& path) |
||||
{ |
||||
scriptsData << path; |
||||
} |
||||
|
||||
void ConfigMgr::delScript(const QString& path) |
||||
{ |
||||
scriptsData.removeAll(path); |
||||
} |
||||
|
||||
void ConfigMgr::loadGeometry(IniFile& ini) |
||||
{ |
||||
geometryData.insert("X", ini.value("Geometry", "X", "-1")); |
||||
geometryData.insert("Y", ini.value("Geometry", "Y", "-1")); |
||||
geometryData.insert("Width", ini.value("Geometry", "Width", "1000")); |
||||
geometryData.insert("Height", ini.value("Geometry", "Height", "768")); |
||||
} |
||||
|
||||
void ConfigMgr::loadConnection(IniFile& ini) |
||||
{ |
||||
connectionData.insert("Realname", ini.value("Connection", "Realname")); |
||||
connectionData.insert("Username", ini.value("Connection", "Username")); |
||||
connectionData.insert("Nickname", ini.value("Connection", "Nickname")); |
||||
connectionData.insert("AltNickname", ini.value("Connection", "AltNickname")); |
||||
connectionData.insert("Server", ini.value("Connection", "Server")); |
||||
connectionData.insert("Password", ini.value("Connection", "Password")); |
||||
connectionData.insert("SSL", ini.value("Connection", "SSL", "0")); |
||||
} |
||||
|
||||
void ConfigMgr::loadCommon(IniFile& ini) |
||||
{ |
||||
#if defined(Q_OS_WIN32) | defined(Q_OS_WIN64) |
||||
QString defaultFontName = "Fixedsys"; |
||||
#else |
||||
QString defaultFontName = "Monospace"; |
||||
#endif |
||||
commonData.insert("ShowOptions", ini.value("Common", "ShowOptions", "1")); |
||||
commonData.insert("Reconnect", ini.value("Common", "Reconnect", "0")); |
||||
commonData.insert("RejoinChannelsOnConnect", ini.value("Common", "RejoinChannelsOnConnect", "0")); |
||||
commonData.insert("ShowWhoisActiveWindow", ini.value("Common", "ShowWhoisActiveWindow", "1")); |
||||
commonData.insert("ShowModeInMessage", ini.value("Common", "ShowModeInMessage", "1")); |
||||
commonData.insert("TrayNotify", ini.value("Common", "TrayNotify", "1")); |
||||
commonData.insert("TrayNotifyDelay", ini.value("Common", "TrayNotifyDelay", "5")); |
||||
commonData.insert("ShowTimestamp", ini.value("Common", "ShowTimestamp", "1")); |
||||
commonData.insert("TimestampFormat", ini.value("Common", "TimestampFormat", "[HH:mm]")); |
||||
commonData.insert("QuitMessage", ini.value("Common", "QuitMessage")); |
||||
commonData.insert("Font", ini.value("Common", "Font", defaultFontName)); |
||||
commonData.insert("FontSize", ini.value("Common", "FontSize", "12")); |
||||
commonData.insert("BgImageEnabled", ini.value("Common", "BgImageEnabled", "0")); |
||||
commonData.insert("BgImagePath", ini.value("Common", "BgImagePath")); |
||||
commonData.insert("BgImageOpacity", ini.value("Common", "BgImageOpacity", "100")); |
||||
commonData.insert("BgImageScaling", ini.value("Common", "BgImageScaling")); |
||||
commonData.insert("SSLSelfsigned", ini.value("Common", "SSLSelfSigned", "0")); |
||||
commonData.insert("SSLExpired", "0"); |
||||
commonData.insert("SSLCNMismatch", ini.value("Common", "SSLCNMismatch", "0")); |
||||
} |
||||
|
||||
void ConfigMgr::loadColor(IniFile& ini) |
||||
{ |
||||
colorData.insert("Action", ini.value("Color", "Action", "#840084")); |
||||
colorData.insert("CTCP", ini.value("Color", "CTCP", "#FF0000")); |
||||
colorData.insert("Highlight", ini.value("Color", "Highlight", "#848400")); |
||||
colorData.insert("Invite", ini.value("Color", "Invite", "#008400")); |
||||
colorData.insert("Join", ini.value("Color", "Join", "#008400")); |
||||
colorData.insert("Kick", ini.value("Color", "Kick", "#008400")); |
||||
colorData.insert("Mode", ini.value("Color", "Mode", "#008400")); |
||||
colorData.insert("Nick", ini.value("Color", "Nick", "#008400")); |
||||
colorData.insert("Normal", ini.value("Color", "Normal", "#000000")); |
||||
colorData.insert("Notice", ini.value("Color", "Notice", "#840000")); |
||||
colorData.insert("OwnText", ini.value("Color", "OwnText", "#008484")); |
||||
colorData.insert("Part", ini.value("Color", "Part", "#008400")); |
||||
colorData.insert("ProgramInfo", ini.value("Color", "ProgramInfo", "#000084")); |
||||
colorData.insert("Quit", ini.value("Color", "Quit", "#000084")); |
||||
colorData.insert("ServerInfo", ini.value("Color", "ServerInfo", "#008400")); |
||||
colorData.insert("Topic", ini.value("Color", "Topic", "#008400")); |
||||
colorData.insert("Wallops", ini.value("Color", "Wallops", "#FF0000")); |
||||
colorData.insert("TextviewBackground", ini.value("Color", "TextviewBackground", "#FFFFFF")); |
||||
colorData.insert("InputBackground", ini.value("Color", "InputBackground", "#FFFFFF")); |
||||
colorData.insert("InputForeground", ini.value("Color", "InputForeground", "#000000")); |
||||
colorData.insert("ListboxBackground", ini.value("Color", "ListboxBackground", "#FFFFFF")); |
||||
colorData.insert("ListboxForeground", ini.value("Color", "ListboxForeground", "#000000")); |
||||
colorData.insert("Links", ini.value("Color", "Links", "#0000FF")); |
||||
} |
||||
|
||||
void ConfigMgr::loadPrefixColor(IniFile& ini) |
||||
{ |
||||
QString defaultColor = color("ListboxForeground"); |
||||
const int ItemCount = ini.countItems("PrefixColor"); |
||||
for (int i = 0; i < ItemCount; ++i) { |
||||
int key = ini.key("PrefixColor", i).toInt(); // Stored as ascii-value of given prefix
|
||||
QColor color(ini.value("PrefixColor", i, defaultColor)); |
||||
QChar prefix = static_cast<char>(key); |
||||
prefixColorData.push_back(std::make_pair(prefix, color)); |
||||
} |
||||
} |
||||
|
||||
void ConfigMgr::loadLogging(IniFile &ini) |
||||
{ |
||||
loggingData.insert("Channels", ini.value("Logging", "Channels", "0")); |
||||
loggingData.insert("Privates", ini.value("Logging", "Privates", "0")); |
||||
loggingData.insert("Path", ini.value("Logging", "Path")); |
||||
} |
||||
|
||||
void ConfigMgr::loadScripts(IniFile& ini) |
||||
{ |
||||
scriptsData.clear(); |
||||
const int slen = ini.countItems("Scripts"); |
||||
for (int i = 0; i < slen; ++i) { |
||||
scriptsData << ini.value("Scripts", i); |
||||
} |
||||
} |
||||
|
||||
void ConfigMgr::saveGeometry(IniFile& ini) |
||||
{ |
||||
QHashIterator<QString,QString> it(geometryData); |
||||
while (it.hasNext()) { |
||||
it.next(); |
||||
ini.write("Geometry", it.key(), it.value()); |
||||
} |
||||
} |
||||
|
||||
void ConfigMgr::saveConnection(IniFile& ini) |
||||
{ |
||||
QHashIterator<QString,QString> it(connectionData); |
||||
while (it.hasNext()) { |
||||
it.next(); |
||||
ini.write("Connection", it.key(), it.value()); |
||||
} |
||||
} |
||||
|
||||
void ConfigMgr::saveCommon(IniFile& ini) |
||||
{ |
||||
QHashIterator<QString,QString> it(commonData); |
||||
while (it.hasNext()) { |
||||
it.next(); |
||||
ini.write("Common", it.key(), it.value()); |
||||
} |
||||
} |
||||
|
||||
void ConfigMgr::saveColor(IniFile& ini) |
||||
{ |
||||
QHashIterator<QString,QString> it(colorData); |
||||
while (it.hasNext()) { |
||||
it.next(); |
||||
ini.write("Color", it.key(), it.value()); |
||||
} |
||||
} |
||||
|
||||
void ConfigMgr::savePrefixColor(IniFile& ini) |
||||
{ |
||||
for (const auto& pair : prefixColorData) { |
||||
int keynum = pair.first.toLatin1(); |
||||
ini.write("PrefixColor", QString::number(keynum), pair.second.name()); |
||||
} |
||||
} |
||||
|
||||
void ConfigMgr::saveLogging(IniFile& ini) |
||||
{ |
||||
QHashIterator<QString,QString> it(loggingData); |
||||
while (it.hasNext()) { |
||||
it.next(); |
||||
ini.write("Logging", it.key(), it.value()); |
||||
} |
||||
} |
||||
|
||||
void ConfigMgr::saveScripts(IniFile& ini) |
||||
{ |
||||
ini.delSection("Scripts"); |
||||
for (int i = 0; i < scriptsData.length(); ++i) |
||||
ini.write("Scripts", QString::number(i), scriptsData[i]); |
||||
} |
@ -0,0 +1,71 @@ |
||||
#ifndef CONFIGMGR_H |
||||
#define CONFIGMGR_H |
||||
|
||||
#include "IniFile.h" |
||||
#include <QObject> |
||||
#include <QString> |
||||
#include <QStringList> |
||||
#include <QHash> |
||||
#include <QVector> |
||||
#include <QColor> |
||||
#include <optional> |
||||
|
||||
class ConfigMgr : public QObject |
||||
{ |
||||
Q_OBJECT |
||||
|
||||
public: |
||||
static ConfigMgr& instance(); |
||||
void load(); |
||||
void save(); |
||||
|
||||
// One getter per section
|
||||
QString geometry(const QString& key) const; |
||||
QString connection(const QString& key) const; |
||||
QString common(const QString& key) const; |
||||
QString color(const QString& key) const; |
||||
std::optional<QColor> prefixColor(const QChar prefix) const; |
||||
QHash<QString,QString> color() const; |
||||
QVector<std::pair<QChar,QColor>> prefixColor() const; |
||||
QString logging(const QString& key) const; |
||||
const QStringList& scripts() const; |
||||
|
||||
void setGeometry(const QString& key, const QString& value); |
||||
void setConnection(const QString& key, const QString& value); |
||||
void setCommon(const QString& key, const QString& value); |
||||
void setLogging(const QString& key, const QString& value); |
||||
void setColorPalette(const QHash<QString,QString>& palette); |
||||
void setPrefixColorPalette(const QVector<std::pair<QChar,QColor>>& palette); |
||||
void addScript(const QString& path); |
||||
void delScript(const QString& path); |
||||
|
||||
private: |
||||
ConfigMgr(); |
||||
void loadGeometry(IniFile& ini); |
||||
void loadConnection(IniFile& ini); |
||||
void loadCommon(IniFile& ini); |
||||
void loadColor(IniFile& ini); |
||||
void loadPrefixColor(IniFile& ini); |
||||
void loadLogging(IniFile& ini); |
||||
void loadScripts(IniFile& ini); |
||||
void saveGeometry(IniFile& ini); |
||||
void saveConnection(IniFile& ini); |
||||
void saveCommon(IniFile& ini); |
||||
void saveColor(IniFile& ini); |
||||
void savePrefixColor(IniFile& ini); |
||||
void saveLogging(IniFile& ini); |
||||
void saveScripts(IniFile& ini); |
||||
|
||||
QHash<QString,QString> geometryData; |
||||
QHash<QString,QString> connectionData; |
||||
QHash<QString,QString> commonData; |
||||
QHash<QString,QString> colorData; |
||||
QVector<std::pair<QChar,QColor>> prefixColorData; |
||||
QHash<QString,QString> loggingData; |
||||
QStringList scriptsData; |
||||
|
||||
signals: |
||||
void saved(); |
||||
}; |
||||
|
||||
#endif // CONFIGMGR_H
|
@ -0,0 +1,599 @@ |
||||
#include "ICommand.h" |
||||
#include "Commands.h" |
||||
#include "IRC.h" |
||||
#include "IRCClient/IRCMember.h" |
||||
#include "IRCClient/IRCChannel.h" |
||||
#include "IWin/IWinStatus.h" |
||||
#include "MdiManager.h" |
||||
#include "Script/Manager.h" |
||||
#include "fmt/format.h" |
||||
|
||||
namespace { |
||||
constexpr char CTCPflag { 0x01 }; |
||||
} |
||||
|
||||
ICommand::ICommand(IRC& connection_) |
||||
: connection(connection_) |
||||
, status(connection_.getStatus()) |
||||
{} |
||||
|
||||
void ICommand::parse(QString text) |
||||
{ |
||||
if (text.isEmpty()) |
||||
return; |
||||
|
||||
if (text[0] == '/') |
||||
text = text.mid(1); |
||||
|
||||
if (tryParseIRC(text) || tryParseInternal(text)) |
||||
return; |
||||
else if (ScriptManager::instance()->runCommand(text)) |
||||
return; |
||||
else { |
||||
const int nextSpace = text.indexOf(' '); |
||||
if (nextSpace == -1) |
||||
connection.raw(text.toUpper().toStdString()); |
||||
else { |
||||
QString command = text.mid(0, nextSpace).toUpper(); |
||||
QString out = QString("%1 %2") |
||||
.arg(command) |
||||
.arg(text.mid(nextSpace + 1)); |
||||
connection.raw(out.toStdString()); |
||||
} |
||||
} |
||||
} |
||||
|
||||
IRC& ICommand::getConnection() |
||||
{ |
||||
return connection; |
||||
} |
||||
|
||||
bool ICommand::tryParseIRC(QString cmdLine) |
||||
{ |
||||
int spaceidx = cmdLine.indexOf(' ') > -1 ?: cmdLine.length(); |
||||
const QString command = cmdLine.mid(0, spaceidx).toUpper(); |
||||
cmdLine.remove(0, spaceidx); |
||||
|
||||
auto& mdi = MdiManager::instance(); |
||||
|
||||
using namespace Command::IRC; // For the string constants below.
|
||||
if (command == PASS) { |
||||
CommandData argv(cmdLine, { &ICommand::prd_Message }); |
||||
if (!argv[0]) |
||||
print_notEnoughParameters(command); |
||||
else |
||||
connection.command(PASS, *argv[0]); |
||||
return true; |
||||
} |
||||
|
||||
else if (command == NICK) { |
||||
CommandData argv(cmdLine, { &ICommand::prd_Message }); |
||||
if (!argv[0]) |
||||
print_notEnoughParameters(command); |
||||
else { |
||||
if (!connection.isOnline()) |
||||
connection.setNickname(*argv[0]); |
||||
connection.command(NICK, *argv[0]); |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
else if (command == OPER) { |
||||
CommandData argv(cmdLine, { &ICommand::prd_AnyWord, &ICommand::prd_AnyWord }); |
||||
if (argv.size() < 2) |
||||
print_notEnoughParameters(command); |
||||
else { |
||||
connection.command(OPER, { *argv[0], *argv[1] }); |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
else if (command == MODE) { |
||||
CommandData argv(cmdLine, { &ICommand::prd_AnyWord, &ICommand::prd_Message }); |
||||
connection.raw(fmt::format("{} {}", MODE, argv.joinString(' '))); |
||||
return true; |
||||
} |
||||
|
||||
else if (command == QUIT) { |
||||
CommandData argv(cmdLine, { &ICommand::prd_Message }); |
||||
connection.expectDisconnect(); |
||||
connection.command(QUIT, *argv[0]); |
||||
return true; |
||||
} |
||||
|
||||
else if (command == SQUIT) { |
||||
CommandData argv(cmdLine, { &ICommand::prd_AnyWord, &ICommand::prd_Message }); |
||||
if (!argv[0]) { |
||||
print_notEnoughParameters(command); |
||||
return true; |
||||
} |
||||
connection.command(SQUIT, { *argv[0] }, *argv[1]); |
||||
return true; |
||||
} |
||||
|
||||
else if (command == JOIN) { |
||||
CommandData argv(cmdLine, { &ICommand::prd_AnyWord, &ICommand::prd_AnyWord }); |
||||
if (!argv[0]) { |
||||
print_notEnoughParameters(command); |
||||
return true; |
||||
} |
||||
|
||||
connection.command(JOIN, { *argv[0], argv[1].value_or("") }); |
||||
|
||||
return true; |
||||
} |
||||
|
||||
else if (command == PART) { |
||||
CommandData argv(cmdLine, { &ICommand::prd_AnyWord, &ICommand::prd_Message }); |
||||
if (!argv[0]) { |
||||
print_notEnoughParameters(command); |
||||
return true; |
||||
} |
||||
|
||||
connection.command(PART, { *argv[0] }, argv[1].value_or("")); |
||||
|
||||
return true; |
||||
} |
||||
|
||||
else if (command == TOPIC) { |
||||
CommandData argv(cmdLine, { &ICommand::prd_Switch, &ICommand::prd_AnyWord, &ICommand::prd_Message }); |
||||
if (!argv[1]) { |
||||
print_notEnoughParameters(command); |
||||
return true; |
||||
} |
||||
|
||||
if (argv[0].value_or("") == "-c") { |
||||
connection.command(TOPIC, { *argv[1] }, ""); |
||||
return true; |
||||
} |
||||
|
||||
if (argv[2]) |
||||
connection.command(TOPIC, { *argv[1] }, *argv[2]); |
||||
else |
||||
connection.command(TOPIC, { *argv[1] }); |
||||
|
||||
return true; |
||||
} |
||||
|
||||
else if (command == NAMES) { |
||||
CommandData argv(cmdLine, { &ICommand::prd_AnyWord }); |
||||
if (!argv[0]) { |
||||
print_notEnoughParameters(command); |
||||
return true; |
||||
} |
||||
connection.command(NAMES, { *argv[0] }); |
||||
return true; |
||||
} |
||||
|
||||
else if (command == LIST) { |
||||
CommandData argv(cmdLine, { &ICommand::prd_AnyWord }); |
||||
connection.command(LIST, { argv[0].value_or("") }); |
||||
return true; |
||||
} |
||||
|
||||
else if (command == INVITE) { |
||||
CommandData argv(cmdLine, { &ICommand::prd_AnyWord, &ICommand::prd_AnyWord }); |
||||
if (!argv[0] || !argv[1]) { |
||||
print_notEnoughParameters(command); |
||||
return true; |
||||
} |
||||
connection.command(INVITE, { *argv[0], *argv[1] }); |
||||
return true; |
||||
} |
||||
|
||||
else if (command == KICK) { |
||||
CommandData argv(cmdLine, { &ICommand::prd_OptionalChannel, &ICommand::prd_AnyWord, &ICommand::prd_Message }); |
||||
if (!argv[1]) { |
||||
print_notEnoughParameters(command); |
||||
return true; |
||||
} |
||||
|
||||
std::string channel = argv[0].value_or(""); |
||||
if (!argv[0]) { |
||||
if (mdi.currentWindow()->getType() != IWin::Type::Channel) { |
||||
mdi.currentWindow()->print(PrintType::ProgramInfo, "Not in a channel window"); |
||||
return true; |
||||
} |
||||
channel = mdi.currentWindow()->getButtonText().toStdString(); |
||||
} |
||||
|
||||
connection.command(KICK, { channel, *argv[1] }, argv[2].value_or("")); |
||||
|
||||
return true; |
||||
} |
||||
|
||||
else if (command == PRIVMSG) { |
||||
CommandData argv(cmdLine, { &ICommand::prd_AnyWord, &ICommand::prd_Message }); |
||||
if (!argv[0]) { |
||||
print_notEnoughParameters(command); |
||||
return true; |
||||
} |
||||
|
||||
connection.command(PRIVMSG, { *argv[0] }, argv[1].value_or("")); |
||||
|
||||
IWin* win = mdi.findWindow(&status, argv[0]->c_str()); |
||||
if (win) { |
||||
const auto* myNick = connection.getNickname().c_str(); |
||||
const auto* msg = argv[1] ? argv[1]->c_str() : ""; |
||||
win->print(PrintType::OwnText, QStringLiteral("<%1> %2") |
||||
.arg(myNick) |
||||
.arg(msg)); |
||||
} |
||||
else { |
||||
const auto* msg = argv[1] ? argv[1]->c_str() : ""; |
||||
status.print(PrintType::OwnText, tr(">%1< %2") |
||||
.arg(argv[0]->c_str()) |
||||
.arg(msg)); |
||||
|
||||
} |
||||
|
||||
return true; |
||||
} |
||||
|
||||
else if (command == NOTICE) { |
||||
CommandData argv(cmdLine, { &ICommand::prd_AnyWord, &ICommand::prd_Message }); |
||||
if (!argv[0]) { |
||||
print_notEnoughParameters(command); |
||||
return true; |
||||
} |
||||
connection.command(NOTICE, { *argv[0] }, argv[1].value_or("")); |
||||
|
||||
IWin* win = mdi.findWindow(&status, argv[0]->c_str()); |
||||
if (!win) |
||||
win = &status; |
||||
const auto* msg = argv[1] ? argv[1]->c_str() : ""; |
||||
win->print(PrintType::OwnText, tr("->%1<- %2") |
||||
.arg(argv[0]->c_str()) |
||||
.arg(msg)); |
||||
|
||||
return true; |
||||
} |
||||
|
||||
else if (command == MOTD) { |
||||
CommandData argv(cmdLine, { &ICommand::prd_AnyWord }); |
||||
connection.command(MOTD, { argv[0].value_or("") }); |
||||
return true; |
||||
} |
||||
|
||||
else if (command == LUSERS) { |
||||
CommandData argv(cmdLine, { &ICommand::prd_AnyWord }); |
||||
connection.command(LUSERS, { argv[0].value_or("") }); |
||||
return true; |
||||
} |
||||
|
||||
else if (command == VERSION) { |
||||
CommandData argv(cmdLine, { &ICommand::prd_AnyWord }); |
||||
connection.command(VERSION, { argv[0].value_or("") }); |
||||
return true; |
||||
} |
||||
|
||||
else if (command == STATS) { |
||||
CommandData argv(cmdLine, { &ICommand::prd_AnyWord }); |
||||
connection.command(STATS, { argv[0].value_or("") }); |
||||
return true; |
||||
} |
||||
|
||||
else if (command == LINKS) { |
||||
CommandData argv(cmdLine, { &ICommand::prd_AnyWord }); |
||||
connection.command(LINKS, { argv[0].value_or("") }); |
||||
return true; |
||||
} |
||||
|
||||
else if (command == TIME) { |
||||
CommandData argv(cmdLine, { &ICommand::prd_AnyWord }); |
||||
connection.command(TIME, { argv[0].value_or("") }); |
||||
return true; |
||||
} |
||||
|
||||
else if (command == CONNECT) { |
||||
CommandData argv(cmdLine, { &ICommand::prd_AnyWord }); |
||||
connection.command(CONNECT, { argv[0].value_or("") }); |
||||
return true; |
||||
} |
||||
|
||||
else if (command == TRACE) { |
||||
CommandData argv(cmdLine, { &ICommand::prd_Message }); |
||||
connection.command(TRACE, { argv[0].value_or("") }); |
||||
return true; |
||||
} |
||||
|
||||
else if (command == ADMIN) { |
||||
CommandData argv(cmdLine, { &ICommand::prd_AnyWord }); |
||||
connection.command(ADMIN, { argv[0].value_or("") }); |
||||
return true; |
||||
} |
||||
|
||||
else if (command == INFO) { |
||||
CommandData argv(cmdLine, { &ICommand::prd_AnyWord }); |
||||
connection.command(INFO, { argv[0].value_or("") }); |
||||
return true; |
||||
} |
||||
|
||||
else if (command == SERVLIST) { |
||||
|
||||
} |
||||
|
||||
else if (command == SQUERY) { |
||||
|
||||
} |
||||
|
||||
else if (command == WHO) { |
||||
CommandData argv(cmdLine, { &ICommand::prd_Message }); |
||||
connection.command(WHO, { argv[0].value_or("") }); |
||||
return true; |
||||
} |
||||
|
||||
else if (command == WHOIS) { |
||||
CommandData argv(cmdLine, { &ICommand::prd_AnyWord }); |
||||
if (!argv[0]) { |
||||
print_notEnoughParameters(command); |
||||
return true; |
||||
} |
||||
connection.command(WHOIS, { *argv[0] }); |
||||
return true; |
||||
} |
||||
|
||||
else if (command == WHOWAS) { |
||||
CommandData argv(cmdLine, { &ICommand::prd_AnyWord }); |
||||
if (!argv[0]) { |
||||
print_notEnoughParameters(command); |
||||
return true; |
||||
} |
||||
connection.command(WHOWAS, { *argv[0] }); |
||||
return true; |
||||
} |
||||
|
||||
else if (command == KILL) { |
||||
CommandData argv(cmdLine, { &ICommand::prd_AnyWord, &ICommand::prd_Message }); |
||||
if (!argv[0]) { |
||||
print_notEnoughParameters(command); |
||||
return true; |
||||
} |
||||
connection.command(KILL, { *argv[0] }, argv[1].value_or("")); |
||||
return true; |
||||
} |
||||
|
||||
else if (command == PING) { |
||||
|
||||
} |
||||
|
||||
else if (command == PONG) { |
||||
|
||||
} |
||||
|
||||
else if (command == AWAY) { |
||||
CommandData argv(cmdLine, { &ICommand::prd_Message }); |
||||
if (argv[0]) |
||||
connection.command(AWAY, *argv[0]); |
||||
else |
||||
connection.raw(AWAY); |
||||
return true; |
||||
} |
||||
|
||||
else if (command == REHASH) { |
||||
|
||||
} |
||||
|
||||
else if (command == DIE) { |
||||
|
||||
} |
||||
|
||||
else if (command == RESTART) { |
||||
|
||||
} |
||||
|
||||
else if (command == SUMMON) { |
||||
|
||||
} |
||||
|
||||
else if (command == USERS) { |
||||
|
||||
} |
||||
|
||||
else if (command == WALLOPS) { |
||||
CommandData argv(cmdLine, { &ICommand::prd_Message }); |
||||
if (!argv[0]) { |
||||
print_notEnoughParameters(command); |
||||
return true; |
||||
} |
||||
connection.command(WALLOPS, *argv[0]); |
||||
return true; |
||||
} |
||||
|
||||
else if (command == USERHOST) { |
||||
|
||||
} |
||||
|
||||
else if (command == ISON) { |
||||
|
||||
} |
||||
|
||||
return false; |
||||
} |
||||
|
||||
bool ICommand::tryParseInternal(const QString& cmdLine) |
||||
{ |
||||
int spaceidx = cmdLine.indexOf(' '); |
||||
const QString command = cmdLine.mid(0, (spaceidx > -1) ? spaceidx : cmdLine.length()).toUpper(); |
||||
|
||||
using namespace Command::Internal; |
||||
using namespace Command::IRC; |
||||
if (command == CTCP) { |
||||
CommandData argv(cmdLine, { &ICommand::prd_AnyWord, &ICommand::prd_AnyWord, &ICommand::prd_Message }); |
||||
if (!argv[0] || !argv[1]) { |
||||
print_notEnoughParameters(command); |
||||
return true; |
||||
} |
||||
|
||||
QString target = QString::fromStdString(*argv[0]); |
||||
QString msg = QString::fromStdString(*argv[1]); |
||||
if (argv[2]) |
||||
msg += " " + QString::fromStdString(*argv[2]); |
||||
|
||||
status.printToActive(PrintType::CTCP, tr("Sending [CTCP %1] to %2") |
||||
.arg(msg) |
||||
.arg(target)); |
||||
|
||||
const std::string out = fmt::format("{} {} :{}{}{}", |
||||
PRIVMSG, |
||||
*argv[0], |
||||
CTCPflag, |
||||
msg.toStdString(), |
||||
CTCPflag); |
||||
connection.raw(out); |
||||
return true; |
||||
} |
||||
|
||||
else if (command == ME) { |
||||
IWin* cw = status.getActiveWindow(); |
||||
if (cw->getType() != IWin::Type::Channel && cw->getType() != IWin::Type::Private) { |
||||
print_invalidWindowTypeForCommand(command); |
||||
return true; |
||||
} |
||||
|
||||
CommandData argv(cmdLine, { &ICommand::prd_Message }); |
||||
if (!argv[0]) { |
||||
print_notEnoughParameters(command); |
||||
return true; |
||||
} |
||||
QString target = cw->getButtonText(); |
||||
std::string mynick = connection.getNickname(); |
||||
QString msg = argv[0]->c_str(); |
||||
if (cw->getType() == IWin::Type::Channel) { |
||||
auto channel = connection.getChannel( cw->getButtonText().toStdString() ); |
||||
auto& member = channel->getMember(mynick)->get(); |
||||
const std::string& mymodes = member.modes(); |
||||
|
||||
if (!mymodes.empty()) { |
||||
std::string pf = connection.toMemberPrefix(mymodes); |
||||
if (!pf.empty()) |
||||
mynick.insert(mynick.begin(), pf[0]); |
||||
} |
||||
} |
||||
const std::string out = fmt::format("{} {} :{}ACTION {}{}", |
||||
PRIVMSG, |
||||
*argv[0], |
||||
CTCPflag, |
||||
msg.toStdString(), |
||||
CTCPflag); |
||||
connection.raw(out); |
||||
cw->print(PrintType::Action, QStringLiteral("%1 %2") |
||||
.arg(mynick.c_str()) |
||||
.arg(msg)); |
||||
return true; |
||||
} |
||||
|
||||
else if (command == ECHO) { |
||||
CommandData argv(cmdLine, { &ICommand::prd_Message }); |
||||
if (!argv[0]) { |
||||
print_notEnoughParameters(command); |
||||
return true; |
||||
} |
||||
|
||||
auto* active = status.getActiveWindow(); |
||||
active->print(PrintType::Normal, argv[0]->c_str()); |
||||
return true; |
||||
} |
||||
|
||||
else if (command == QUERY) { |
||||
CommandData argv(cmdLine, { &ICommand::prd_AnyWord }); |
||||
if (!argv[0]) { |
||||
print_notEnoughParameters(command); |
||||
return true; |
||||
} |
||||
|
||||
status.createPrivateWindow(argv[0]->c_str()); |
||||
return true; |
||||
} |
||||
|
||||
else if (command == MUTERESP) { |
||||
QStringList list = cmdLine.split(' ', Qt::SkipEmptyParts); |
||||
list.pop_front(); |
||||
if (list.isEmpty()) { |
||||
print_notEnoughParameters(command); |
||||
return true; |
||||
} |
||||
connection.setIgnoreVerbosity(list); |
||||
return true; |
||||
} |
||||
|
||||
else if (command == UNMUTERESP) { |
||||
QStringList list = cmdLine.split(' ', Qt::SkipEmptyParts); |
||||
list.pop_front(); |
||||
if (list.isEmpty()) { |
||||
print_notEnoughParameters(command); |
||||
return true; |
||||
} |
||||
connection.unsetIgnoreVerbosity(list); |
||||
return true; |
||||
} |
||||
|
||||
return false; |
||||
} |
||||
|
||||
void ICommand::print_notEnoughParameters(const QString& command) |
||||
{ |
||||
const auto& mdi = MdiManager::instance(); |
||||
mdi.currentStatus()->printToActive(PrintType::ProgramInfo, tr("%1: Not enough parameters.").arg(command)); |
||||
} |
||||
|
||||
void ICommand::print_invalidCommandSwitch(const QString& command) |
||||
{ |
||||
const auto& mdi = MdiManager::instance(); |
||||
mdi.currentStatus()->printToActive(PrintType::ProgramInfo, tr("%1: Invalid command switch.").arg(command)); |
||||
} |
||||
|
||||
void ICommand::print_invalidWindowTypeForCommand(const QString& command) |
||||
{ |
||||
const auto& mdi = MdiManager::instance(); |
||||
mdi.currentStatus()->printToActive(PrintType::ProgramInfo, tr("%1: Invalid window type for that command.").arg(command)); |
||||
} |
||||
|
||||
std::optional<std::string> ICommand::prd_AnyWord(int& idx, const QString& line) |
||||
{ |
||||
std::string ret; |
||||
std::string stdline = line.toStdString(); |
||||
for (; idx < stdline.size(); ++idx) { |
||||
const char c = stdline[idx]; |
||||
if (c == ' ') |
||||
break; |
||||
ret += c; |
||||
} |
||||
|
||||
if (ret.empty()) |
||||
return {}; |
||||
return ret; |
||||
} |
||||
|
||||
std::optional<std::string> ICommand::prd_OptionalChannel(int& idx, const QString& line) |
||||
{ |
||||
const auto& mdi = MdiManager::instance(); |
||||
const auto& isupport = mdi.currentStatus()->getConnection().isupport(); |
||||
const auto& chantypes = isupport.at("CHANTYPES"); |
||||
|
||||
if (chantypes.find_first_of(line.toStdString()[idx]) == std::string::npos) |
||||
return std::nullopt; |
||||
return prd_AnyWord(idx, line); |
||||
} |
||||
|
||||
std::optional<std::string> ICommand::prd_NotSwitch(int& idx, const QString& line) |
||||
{ |
||||
if (line[idx] == '-' || line[idx] == '+') |
||||
return std::nullopt; |
||||
return prd_AnyWord(idx, line); |
||||
} |
||||
|
||||
std::optional<std::string> ICommand::prd_Switch(int& idx, const QString& line) |
||||
{ |
||||
if (line[idx] != '-' && line[idx] != '+') |
||||
return std::nullopt; |
||||
return prd_AnyWord(idx, line); |
||||
} |
||||
|
||||
std::optional<std::string> ICommand::prd_Message(int& idx, const QString& line) |
||||
{ |
||||
QString ret = line.mid(idx); |
||||
if (ret.isEmpty()) |
||||
return std::nullopt; |
||||
return ret.toStdString(); |
||||
} |
@ -0,0 +1,39 @@ |
||||
#ifndef ICOMMAND_H |
||||
#define ICOMMAND_H |
||||
|
||||
#include "ICommand/CommandData.h" |
||||
#include <QObject> |
||||
|
||||
class IRC; |
||||
class IWinStatus; |
||||
class MdiManager; |
||||
|
||||
class ICommand : public QObject |
||||
{ |
||||
Q_OBJECT |
||||
|
||||
public: |
||||
explicit ICommand(IRC& connection_); |
||||
|
||||
void parse(QString text); |
||||
IRC& getConnection(); |
||||
|
||||
private: |
||||
bool tryParseIRC(QString cmdLine); |
||||
bool tryParseInternal(const QString& cmdLine); |
||||
void print_notEnoughParameters(const QString& command); |
||||
void print_invalidCommandSwitch(const QString& command); |
||||
void print_invalidWindowTypeForCommand(const QString& command); |
||||
|
||||
/* Predicates for parsing commands */ |
||||
static std::optional<std::string> prd_AnyWord(int& idx, const QString& line); |
||||
static std::optional<std::string> prd_OptionalChannel(int& idx, const QString& line); |
||||
static std::optional<std::string> prd_NotSwitch(int& idx, const QString& line); |
||||
static std::optional<std::string> prd_Switch(int& idx, const QString& line); |
||||
static std::optional<std::string> prd_Message(int& idx, const QString& line); |
||||
|
||||
IRC& connection; |
||||
IWinStatus& status; |
||||
}; |
||||
|
||||
#endif // ICOMMAND_H
|
@ -0,0 +1,50 @@ |
||||
#include "CommandData.h" |
||||
#include "ICommand.h" |
||||
|
||||
CommandData::CommandData(const QString& line, PredicateList argp, bool repeatLastPredicate) |
||||
{ |
||||
for (int i = 0, ppos = 0; i < line.length(); ++i) { |
||||
QChar c = line[i]; |
||||
if (c == ' ') |
||||
continue; |
||||
else { |
||||
if (ppos >= static_cast<int>(argp.size()) && m_l.at(ppos-1)) { |
||||
if (repeatLastPredicate) |
||||
ppos = argp.size()-1; |
||||
else |
||||
break; |
||||
} |
||||
|
||||
const auto& result = argp[ppos](i, line); |
||||
m_l.push_back(result); |
||||
++ppos; |
||||
if (!result) |
||||
--i; |
||||
} |
||||
} |
||||
|
||||
while (m_l.size() < argp.size()) |
||||
m_l.push_back(std::nullopt); |
||||
} |
||||
|
||||
int CommandData::size() const |
||||
{ |
||||
return m_l.size(); |
||||
} |
||||
|
||||
const std::optional<std::string>& CommandData::operator[](int idx) |
||||
{ |
||||
return m_l.at(idx); |
||||
} |
||||
|
||||
std::string CommandData::joinString(char sep) const |
||||
{ |
||||
std::string ret; |
||||
for (const auto& item : m_l) { |
||||
if (!ret.empty()) |
||||
ret += sep; |
||||
if (item.has_value()) |
||||
ret += *item; |
||||
} |
||||
return ret; |
||||
} |
@ -0,0 +1,29 @@ |
||||
#ifndef PARSER_H |
||||
#define PARSER_H |
||||
|
||||
#include <QString> |
||||
#include <QVariant> |
||||
#include <vector> |
||||
#include <functional> |
||||
|
||||
|
||||
using PredicateList = std::vector< std::function< |
||||
std::optional<std::string> |
||||
(int& idx, const QString& line) |
||||
>>; |
||||
|
||||
class ICommand; |
||||
|
||||
class CommandData |
||||
{ |
||||
public: |
||||
CommandData(const QString& line, PredicateList argp, bool repeatLastPredicate = false); |
||||
int size() const; |
||||
const std::optional<std::string>& operator[](int idx); |
||||
std::string joinString(char sep) const; |
||||
|
||||
private: |
||||
std::vector<std::optional<std::string>> m_l; |
||||
}; |
||||
|
||||
#endif // PARSER_H
|
@ -0,0 +1,373 @@ |
||||
#include "ColorConfig.h" |
||||
#include "ConfigMgr.h" |
||||
#include <QAction> |
||||
#include <QPushButton> |
||||
#include <QPainter> |
||||
#include <QDebug> |
||||
#include <QFontMetrics> |
||||
#include <QHashIterator> |
||||
#include <QMessageBox> |
||||
|
||||
namespace { |
||||
constexpr auto* Text_AddPrefix_Full = "Add prefix"; |
||||
constexpr auto* Text_AddPrefix_Short = "Add"; |
||||
} |
||||
|
||||
ColorConfig::ColorConfig(QWidget* parent) |
||||
: QWidget(parent) |
||||
, colorDlg(this) |
||||
{ |
||||
addPrefixContainer = new QWidget(this); |
||||
addPrefixLayout = new QHBoxLayout; |
||||
btnAddPrefix = new QToolButton; |
||||
btnAddPrefix->setText(Text_AddPrefix_Full); |
||||
edAddPrefix = new QLineEdit; |
||||
edAddPrefix->setMaxLength(1); |
||||
edAddPrefix->setAlignment(Qt::AlignmentFlag::AlignCenter); |
||||
edAddPrefix->hide(); |
||||
addPrefixLayout->addWidget(edAddPrefix); |
||||
addPrefixLayout->addWidget(btnAddPrefix); |
||||
addPrefixContainer->setLayout(addPrefixLayout); |
||||
connect(btnAddPrefix, &QToolButton::clicked, this, &ColorConfig::addPrefixClicked); |
||||
connect(edAddPrefix, &QLineEdit::returnPressed, this, &ColorConfig::addPrefixClicked); |
||||
|
||||
ConfigMgr& conf = ConfigMgr::instance(); |
||||
palette = conf.color(); |
||||
prefixColor = conf.prefixColor(); |
||||
|
||||
textTypeMap = { |
||||
// Config key, Descriptive text
|
||||
{ "Action", "Action/role-play message" }, |
||||
{ "CTCP", "CTCP message" }, |
||||
{ "Highlight", "Highlighted message" }, |
||||
{ "Invite", "Invite message" }, |
||||
{ "Join", "Join message" }, |
||||
{ "Kick", "Kick message" }, |
||||
{ "Mode", "Mode message" }, |
||||
{ "Nick", "Nick message" }, |
||||
{ "Normal", "Normal message" }, |
||||
{ "Notice", "Notice message" }, |
||||
{ "OwnText", "Own message" }, |
||||
{ "Part", "Part message" }, |
||||
{ "ProgramInfo", "Program info" }, |
||||
{ "Quit", "Quit message" }, |
||||
{ "ServerInfo", "General server info" }, |
||||
{ "Topic", "Topic message" }, |
||||
{ "Wallops", "Wallops message" }, |
||||
{ "Links", "Links / anchors" } |
||||
}; |
||||
|
||||
colorTypeMap = { |
||||
{ "TextviewBackground", "Text view background" }, |
||||
{ "InputBackground", "Text input background" }, |
||||
{ "InputForeground", "Text input" }, |
||||
{ "ListboxBackground", "Member list background" }, |
||||
{ "ListboxForeground", "Member list default" } |
||||
}; |
||||
|
||||
connect(&colorDlg, &QColorDialog::currentColorChanged, |
||||
this, &ColorConfig::colorSelected); |
||||
|
||||
connect(&colorDlg, &QColorDialog::rejected, [this](){ |
||||
if (colorDlgItem.left(8) == "lbprefix") |
||||
setPrefixColor(colorDlgItem[9], originalColor); |
||||
else |
||||
palette.insert(colorDlgItem, originalColor); |
||||
repaint(); |
||||
}); |
||||
} |
||||
|
||||
void ColorConfig::save() |
||||
{ |
||||
ConfigMgr& conf = ConfigMgr::instance(); |
||||
conf.setColorPalette(palette); |
||||
conf.setPrefixColorPalette(prefixColor); |
||||
m_isChanged = false; |
||||
} |
||||
|
||||
bool ColorConfig::isChanged() const |
||||
{ |
||||
return m_isChanged; |
||||
} |
||||
|
||||
void ColorConfig::reset() |
||||
{ |
||||
ConfigMgr& conf = ConfigMgr::instance(); |
||||
palette = conf.color(); |
||||
prefixColor = conf.prefixColor(); |
||||
m_isChanged = false; |
||||
repaint(); |
||||
} |
||||
|
||||
void ColorConfig::paintEvent(QPaintEvent*) |
||||
{ |
||||
QPainter paint(this); |
||||
int w = width(); |
||||
int h = height(); |
||||
|
||||
QColor frame(palette.value("TextviewBackground")); |
||||
frame.setRed(frame.red() ^ 255); |
||||
frame.setGreen(frame.green() ^ 255); |
||||
frame.setBlue(frame.blue() ^ 255); |
||||
|
||||
paint.fillRect(0, 0, w, h, frame); |
||||
w -= 2; |
||||
h -= 2; |
||||
|
||||
const int listboxW = 150; |
||||
const int inputH = 24; |
||||
|
||||
/* Text-view background */ |
||||
textViewBB = { 1, 1, w - listboxW, h - inputH }; |
||||
paint.fillRect(textViewBB, QColor(palette.value("TextviewBackground"))); |
||||
|
||||
/* Member list background */ |
||||
listboxBB = { w - listboxW + 2, 1, listboxW - 1, h - inputH }; |
||||
paint.fillRect(listboxBB, QColor(palette.value("ListboxBackground"))); // Listbox
|
||||
|
||||
/* Input background */ |
||||
inputBB = { 1, h - inputH + 2, w, inputH - 1 }; |
||||
paint.fillRect(inputBB, QColor(palette.value("InputBackground"))); // Input
|
||||
|
||||
ConfigMgr& conf = ConfigMgr::instance(); |
||||
QFont font(conf.common("Font")); |
||||
paint.setFont(font); |
||||
|
||||
createMessageBoxTexts(paint); |
||||
createMemberListTexts(paint, listboxW); |
||||
createInputBoxTexts(paint, inputH); |
||||
} |
||||
|
||||
void ColorConfig::mouseReleaseEvent(QMouseEvent* evt) |
||||
{ |
||||
const int X = evt->x(); |
||||
const int Y = evt->y(); |
||||
|
||||
/* Check click for text item types */ |
||||
{ |
||||
QHashIterator<QString,QRect> it(textTypeBB); |
||||
while (it.hasNext()) { |
||||
it.next(); |
||||
const QString& key = it.key(); |
||||
const QRect& val = it.value(); |
||||
if (val.contains(X, Y)) { |
||||
chooseColorFor(key); |
||||
return; |
||||
} |
||||
} |
||||
} |
||||
|
||||
/* Check click for listbox items */ |
||||
{ |
||||
QHashIterator<QString,QRect> it(listboxItemBB); |
||||
while (it.hasNext()) { |
||||
it.next(); |
||||
const QString& key = it.key(); |
||||
const QRect& val = it.value(); |
||||
if (val.contains(X, Y)) { |
||||
chooseColorFor(key); |
||||
return; |
||||
} |
||||
} |
||||
} |
||||
|
||||
/* Check click for delete listbox item */ |
||||
{ |
||||
QHashIterator<QChar,QRect> it(prefixDeleteBB); |
||||
while (it.hasNext()) { |
||||
it.next(); |
||||
const QChar& key = it.key(); |
||||
const QRect& val = it.value(); |
||||
if (val.contains(X, Y)) { |
||||
askDeletePrefix(key); |
||||
return; |
||||
} |
||||
} |
||||
} |
||||
|
||||
if (inputTextBB.contains(X, Y)) |
||||
chooseColorFor("InputForeground"); |
||||
|
||||
else if (textViewBB.contains(X, Y)) |
||||
chooseColorFor("TextviewBackground"); |
||||
|
||||
else if (listboxBB.contains(X, Y)) |
||||
chooseColorFor("ListboxBackground"); |
||||
|
||||
else if (inputBB.contains(X, Y)) |
||||
chooseColorFor("InputBackground"); |
||||
} |
||||
|
||||
void ColorConfig::createMessageBoxTexts(QPainter& paint) |
||||
{ |
||||
const int itemIncr = 19; |
||||
int itemTop = itemIncr; |
||||
QPen originalPen = paint.pen(); |
||||
QFontMetrics fm(paint.fontMetrics()); |
||||
|
||||
textTypeBB.clear(); |
||||
QHashIterator<QString,QString> it(textTypeMap); |
||||
QStringList sorted; |
||||
|
||||
while (it.hasNext()) |
||||
sorted << it.next().key(); |
||||
|
||||
std::sort(sorted.begin(), sorted.end(), |
||||
[this](const QString& left, const QString& right) { |
||||
QString leftText = descriptiveColorText(left); |
||||
QString rightText = descriptiveColorText(right); |
||||
return leftText < rightText; |
||||
}); |
||||
|
||||
for (const QString& key : sorted) { |
||||
const QString val = descriptiveColorText(key); |
||||
|
||||
paint.setPen(QColor(palette.value(key))); |
||||
paint.drawText(4, itemTop, val); |
||||
|
||||
QRect itemRect(4, itemTop - fm.ascent(), fm.width(val), fm.height()); |
||||
textTypeBB.insert(key, itemRect); |
||||
|
||||
itemTop += itemIncr; |
||||
} |
||||
|
||||
paint.setPen(originalPen); |
||||
} |
||||
|
||||
void ColorConfig::createMemberListTexts(QPainter& paint, int listboxW) |
||||
{ |
||||
const int x = width() - listboxW + 4; |
||||
const int itemIncr = 20; |
||||
int itemTop = itemIncr; |
||||
QPen originalPen = paint.pen(); |
||||
|
||||
QFontMetrics fm(paint.fontMetrics()); |
||||
listboxItemBB.clear(); |
||||
prefixDeleteBB.clear(); |
||||
paint.setPen(QColor(palette.value("ListboxForeground"))); |
||||
paint.drawText(x, itemTop, "Default text"); |
||||
listboxItemBB.insert("ListboxForeground", QRect(x, itemTop - fm.ascent(), fm.width("Default text"), fm.height())); |
||||
itemTop += itemIncr; |
||||
|
||||
constexpr auto* DeleteLabel = "[X]"; |
||||
for (const auto& item : prefixColor) { |
||||
QString itemText = QStringLiteral("%1member").arg(item.first); |
||||
int itemTextWidth = fm.width(itemText); |
||||
paint.setPen(item.second); |
||||
paint.drawText(x, itemTop, itemText); |
||||
paint.drawText(x + itemTextWidth + 10, itemTop, DeleteLabel); |
||||
listboxItemBB.insert(QStringLiteral("lbprefix %1").arg(item.first), QRect(x, itemTop - fm.ascent(), itemTextWidth, fm.height())); |
||||
prefixDeleteBB.insert(item.first, QRect(x + itemTextWidth + 10, itemTop - fm.ascent(), fm.width(DeleteLabel), fm.height())); |
||||
|
||||
itemTop += itemIncr; |
||||
} |
||||
|
||||
paint.setPen(originalPen); |
||||
addPrefixContainer->move(x, itemTop); |
||||
} |
||||
|
||||
void ColorConfig::createInputBoxTexts(QPainter& paint, int inputH) |
||||
{ |
||||
QFontMetrics fm(paint.fontMetrics()); |
||||
const int fh = fm.height(); |
||||
const int y = height() - inputH + fh; |
||||
QPen originalPen = paint.pen(); |
||||
|
||||
paint.setPen(QColor(palette.value("InputForeground"))); |
||||
paint.drawText(4, y, "Input box text"); |
||||
inputTextBB = {4, y - fm.ascent(), fm.width("Input box text"), fm.height()}; |
||||
|
||||
paint.setPen(originalPen); |
||||
} |
||||
|
||||
QString ColorConfig::descriptiveColorText(const QString& key) const |
||||
{ |
||||
if (key.left(8) == "lbprefix") |
||||
return QStringLiteral("Prefix '%1'").arg(key[9]); |
||||
|
||||
QString text = textTypeMap.value(key); |
||||
if (text.isEmpty()) |
||||
text = colorTypeMap.value(key); |
||||
return text; |
||||
} |
||||
|
||||
void ColorConfig::chooseColorFor(const QString& item) |
||||
{ |
||||
colorDlgItem = item; |
||||
if (colorDlgItem.left(8) == "lbprefix") |
||||
colorDlg.setCurrentColor(*getPrefixColor(colorDlgItem[9])); |
||||
else |
||||
colorDlg.setCurrentColor(QColor(palette.value(item))); |
||||
originalColor = colorDlg.currentColor().name(); |
||||
|
||||
colorDlg.setWindowTitle(QStringLiteral("Choose color for: %1").arg(descriptiveColorText(item))); |
||||
if (!colorDlg.isVisible()) |
||||
colorDlg.show(); |
||||
} |
||||
|
||||
void ColorConfig::colorSelected(const QColor& color) |
||||
{ |
||||
if (colorDlgItem.left(8) == "lbprefix") |
||||
setPrefixColor(colorDlgItem[9], color); |
||||
else |
||||
palette.insert(colorDlgItem, color.name()); |
||||
m_isChanged = true; |
||||
repaint(); |
||||
} |
||||
|
||||
std::optional<QColor> ColorConfig::getPrefixColor(QChar prefix) |
||||
{ |
||||
for (const auto& item : prefixColor) |
||||
if (item.first == prefix) |
||||
return item.second; |
||||
return std::nullopt; |
||||
} |
||||
|
||||
void ColorConfig::setPrefixColor(QChar prefix, const QColor& color) |
||||
{ |
||||
for (auto& item : prefixColor) |
||||
if (item.first == prefix) |
||||
item.second = color; |
||||
} |
||||
|
||||
void ColorConfig::addPrefixClicked() |
||||
{ |
||||
if (edAddPrefix->isVisible()) { |
||||
if (!edAddPrefix->text().isEmpty()) { |
||||
const QChar prefix = edAddPrefix->text()[0]; |
||||
if (getPrefixColor(prefix)) { |
||||
QMessageBox::information(this, tr("Prefix exists"), tr("The given prefix '%1' is already defined.").arg(prefix)); |
||||
return; |
||||
} |
||||
if (!prefix.isSpace() && !prefix.isLetter() && !prefix.isNumber()) { |
||||
prefixColor.push_back(std::make_pair(prefix, palette.value("ListboxForeground"))); |
||||
m_isChanged = true; |
||||
repaint(); |
||||
} |
||||
} |
||||
edAddPrefix->clear(); |
||||
edAddPrefix->hide(); |
||||
btnAddPrefix->setText(Text_AddPrefix_Full); |
||||
} |
||||
else { |
||||
edAddPrefix->show(); |
||||
btnAddPrefix->setText(Text_AddPrefix_Short); |
||||
} |
||||
} |
||||
|
||||
void ColorConfig::askDeletePrefix(QChar prefix) |
||||
{ |
||||
const QString title = tr("Delete prefix"); |
||||
const QString question = tr("Are you sure you want to delete the prefix '%1'?").arg(prefix); |
||||
if (QMessageBox::question(this, title, question) == QMessageBox::No) |
||||
return; |
||||
|
||||
for (int i = 0; i < prefixColor.count(); ++i) { |
||||
QChar p = prefixColor[i].first; |
||||
if (p == prefix) { |
||||
prefixColor.removeAt(i); |
||||
break; |
||||
} |
||||
} |
||||
repaint(); |
||||
} |
@ -0,0 +1,64 @@ |
||||
#ifndef COLORCONFIG_H |
||||
#define COLORCONFIG_H |
||||
|
||||
#include <QWidget> |
||||
#include <QPaintEvent> |
||||
#include <QMouseEvent> |
||||
#include <QHBoxLayout> |
||||
#include <QToolButton> |
||||
#include <QLineEdit> |
||||
#include <QRect> |
||||
#include <QHash> |
||||
#include <QColorDialog> |
||||
#include <optional> |
||||
|
||||
class ColorConfig : public QWidget |
||||
{ |
||||
Q_OBJECT |
||||
public: |
||||
explicit ColorConfig(QWidget* parent = nullptr); |
||||
void save(); |
||||
bool isChanged() const; |
||||
void reset(); |
||||
|
||||
private: |
||||
void paintEvent(QPaintEvent*); |
||||
void mouseReleaseEvent(QMouseEvent *evt); |
||||
void createMessageBoxTexts(QPainter& paint); |
||||
void createMemberListTexts(QPainter& paint, int listboxW); |
||||
void createInputBoxTexts(QPainter& paint, int inputH); |
||||
void recalculateBB(); |
||||
QString descriptiveColorText(const QString& key) const; |
||||
void chooseColorFor(const QString& item); |
||||
void colorSelected(const QColor& color); |
||||
std::optional<QColor> getPrefixColor(QChar prefix); |
||||
void setPrefixColor(QChar prefix, const QColor& color); |
||||
void addPrefixClicked(); |
||||
void askDeletePrefix(QChar prefix); |
||||
|
||||
QWidget* addPrefixContainer; |
||||
QHBoxLayout* addPrefixLayout; |
||||
QToolButton* btnAddPrefix; |
||||
QLineEdit* edAddPrefix; |
||||
QHash<QString,QString> palette; |
||||
|
||||
QHash<QString,QString> textTypeMap; |
||||
QHash<QString,QString> colorTypeMap; |
||||
QVector<std::pair<QChar,QColor>> prefixColor; // Use vector for displaying prefixes in insertion order.
|
||||
|
||||
QHash<QString,QRect> textTypeBB; |
||||
QHash<QString,QRect> listboxItemBB; |
||||
QHash<QChar,QRect> prefixDeleteBB; |
||||
QRect textViewBB; |
||||
QRect inputBB; |
||||
QRect inputTextBB; |
||||
QRect listboxBB; |
||||
|
||||
QColorDialog colorDlg; |
||||
QString colorDlgItem; |
||||
QString originalColor; |
||||
|
||||
bool m_isChanged{ false }; |
||||
}; |
||||
|
||||
#endif // COLORCONFIG_H
|
@ -0,0 +1,165 @@ |
||||
/*
|
||||
* IdealIRC - Internet Relay Chat client |
||||
* Copyright (C) 2019 Tom-Andre Barstad |
||||
* |
||||
* This program is free software; you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation; either version 2 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License along |
||||
* with this program; if not, write to the Free Software Foundation, Inc., |
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
||||
*/ |
||||
|
||||
#include "IConfig.h" |
||||
#include "ui_IConfig.h" |
||||
#include "ConfigMgr.h" |
||||
#include <QMessageBox> |
||||
|
||||
IConfig::IConfig(QWidget *parent) : |
||||
QDialog(parent), |
||||
ui(new Ui::IConfig) |
||||
{ |
||||
ui->setupUi(this); |
||||
|
||||
layout = new QHBoxLayout(this); |
||||
servers = new IConfigServers; |
||||
options = new IConfigOptions; |
||||
logging = new IConfigLogging; |
||||
|
||||
layout->addWidget(servers); |
||||
layout->addWidget(options); |
||||
layout->addWidget(logging); |
||||
ui->frame->setLayout(layout); |
||||
|
||||
connect(ui->toolServers, &QAbstractButton::released, [this](){ showSubDialog(SubDialogType::Servers); }); |
||||
connect(ui->toolOptions, &QAbstractButton::released, [this](){ showSubDialog(SubDialogType::Options); }); |
||||
connect(ui->toolLogging, &QAbstractButton::released, [this](){ showSubDialog(SubDialogType::Logging); }); |
||||
|
||||
showSubDialog(SubDialogType::Servers); |
||||
} |
||||
|
||||
IConfig::~IConfig() |
||||
{ |
||||
delete ui; |
||||
} |
||||
|
||||
void IConfig::showDisconnectButton() |
||||
{ |
||||
ui->btnDisconnect->show(); |
||||
} |
||||
|
||||
void IConfig::hideDisconnectButton() |
||||
{ |
||||
ui->btnDisconnect->hide(); |
||||
} |
||||
|
||||
void IConfig::showSubDialog(IConfig::SubDialogType dlg) |
||||
{ |
||||
servers->hide(); |
||||
options->hide(); |
||||
logging->hide(); |
||||
ui->toolServers->setChecked(false); |
||||
ui->toolOptions->setChecked(false); |
||||
ui->toolLogging->setChecked(false); |
||||
|
||||
switch (dlg) { |
||||
case SubDialogType::Servers: |
||||
servers->show(); |
||||
ui->toolServers->setChecked(true); |
||||
break; |
||||
case SubDialogType::Options: |
||||
options->show(); |
||||
ui->toolOptions->setChecked(true); |
||||
break; |
||||
case SubDialogType::Logging: |
||||
logging->show(); |
||||
ui->toolLogging->setChecked(true); |
||||
break; |
||||
} |
||||
} |
||||
|
||||
void IConfig::saveAll() |
||||
{ |
||||
servers->save(); |
||||
options->save(); |
||||
logging->save(); |
||||
|
||||
ConfigMgr::instance().save(); |
||||
} |
||||
|
||||
bool IConfig::askForSave() |
||||
{ |
||||
bool changed = servers->isChanged() || options->isChanged() || logging->isChanged(); |
||||
if (changed) { |
||||
auto btn = QMessageBox::question(this, tr("Changes made"), tr("Do you want to save the changes?"), QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel); |
||||
if (btn == QMessageBox::Yes) { |
||||
saveAll(); |
||||
} |
||||
else if (btn == QMessageBox::No) { |
||||
servers->reset(); |
||||
options->reset(); |
||||
logging->reset(); |
||||
} |
||||
else if (btn == QMessageBox::Cancel) { |
||||
return false; |
||||
} |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
void IConfig::on_btnSave_clicked() |
||||
{ |
||||
saveAll(); |
||||
} |
||||
|
||||
void IConfig::on_btnSaveConnect_clicked() |
||||
{ |
||||
saveAll(); |
||||
ConfigMgr& conf = ConfigMgr::instance(); |
||||
if (conf.connection("Realname").isEmpty()) { |
||||
QMessageBox::information(this, tr("Missing field"), tr("Real name must be filled.")); |
||||
return; |
||||
} |
||||
|
||||
if (conf.connection("Username").isEmpty()) { |
||||
QMessageBox::information(this, tr("Missing field"), tr("Username/email must be filled.")); |
||||
return; |
||||
} |
||||
|
||||
if (conf.connection("Nickname").isEmpty()) { |
||||
QMessageBox::information(this, tr("Missing field"), tr("Nickname must be filled")); |
||||
return; |
||||
} |
||||
|
||||
emit connectToServer(servers->connectToNewStatus()); |
||||
servers->unsetConnectToNewStatus(); |
||||
close(); |
||||
} |
||||
|
||||
void IConfig::on_btnDisconnect_clicked() |
||||
{ |
||||
ui->btnDisconnect->hide(); |
||||
emit disconnectFromServer(); |
||||
} |
||||
|
||||
void IConfig::on_btnClose_clicked() |
||||
{ |
||||
servers->unsetConnectToNewStatus(); |
||||
if (askForSave()) |
||||
close(); |
||||
} |
||||
|
||||
void IConfig::closeEvent(QCloseEvent* evt) |
||||
{ |
||||
if (askForSave()) |
||||
evt->accept(); |
||||
else |
||||
evt->ignore(); |
||||
} |
@ -0,0 +1,73 @@ |
||||
/*
|
||||
* IdealIRC - Internet Relay Chat client |
||||
* Copyright (C) 2019 Tom-Andre Barstad |
||||
* |
||||
* This program is free software; you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation; either version 2 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License along |
||||
* with this program; if not, write to the Free Software Foundation, Inc., |
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
||||
*/ |
||||
|
||||
#ifndef ICONFIG_H |
||||
#define ICONFIG_H |
||||
|
||||
#include "IConfigServers.h" |
||||
#include "IConfigOptions.h" |
||||
#include "IConfigLogging.h" |
||||
#include <QDialog> |
||||
#include <QSignalMapper> |
||||
#include <QHBoxLayout> |
||||
#include <QCloseEvent> |
||||
|
||||
namespace Ui { |
||||
class IConfig; |
||||
} |
||||
|
||||
class IConfig : public QDialog |
||||
{ |
||||
Q_OBJECT |
||||
|
||||
public: |
||||
explicit IConfig(QWidget *parent = nullptr); |
||||
~IConfig(); |
||||
void showDisconnectButton(); |
||||
void hideDisconnectButton(); |
||||
|
||||
private slots: |
||||
void on_btnSave_clicked(); |
||||
void on_btnSaveConnect_clicked(); |
||||
void on_btnDisconnect_clicked(); |
||||
void on_btnClose_clicked(); |
||||
|
||||
private: |
||||
enum class SubDialogType { |
||||
Servers, |
||||
Options, |
||||
Logging |
||||
}; |
||||
void closeEvent(QCloseEvent* evt); |
||||
void showSubDialog(SubDialogType dlg); |
||||
void saveAll(); |
||||
bool askForSave(); |
||||
|
||||
Ui::IConfig* ui; |
||||
QHBoxLayout *layout; |
||||
IConfigServers* servers; |
||||
IConfigOptions* options; |
||||
IConfigLogging* logging; |
||||
|
||||
signals: |
||||
void connectToServer(bool newServer); |
||||
void disconnectFromServer(); |
||||
}; |
||||
|
||||
#endif // ICONFIG_H
|
@ -0,0 +1,225 @@ |
||||
<?xml version="1.0" encoding="UTF-8"?> |
||||
<ui version="4.0"> |
||||
<class>IConfig</class> |
||||
<widget class="QDialog" name="IConfig"> |
||||
<property name="windowModality"> |
||||
<enum>Qt::WindowModal</enum> |
||||
</property> |
||||
<property name="geometry"> |
||||
<rect> |
||||
<x>0</x> |
||||
<y>0</y> |
||||
<width>650</width> |
||||
<height>650</height> |
||||
</rect> |
||||
</property> |
||||
<property name="minimumSize"> |
||||
<size> |
||||
<width>650</width> |
||||
<height>650</height> |
||||
</size> |
||||
</property> |
||||
<property name="maximumSize"> |
||||
<size> |
||||
<width>650</width> |
||||
<height>650</height> |
||||
</size> |
||||
</property> |
||||
<property name="windowTitle"> |
||||
<string>Configure IdealIRC</string> |
||||
</property> |
||||
<layout class="QVBoxLayout" name="verticalLayout"> |
||||
<item> |
||||
<layout class="QHBoxLayout" name="horizontalLayout"> |
||||
<item> |
||||
<spacer name="horizontalSpacer"> |
||||
<property name="orientation"> |
||||
<enum>Qt::Horizontal</enum> |
||||
</property> |
||||
<property name="sizeHint" stdset="0"> |
||||
<size> |
||||
<width>40</width> |
||||
<height>20</height> |
||||
</size> |
||||
</property> |
||||
</spacer> |
||||
</item> |
||||
<item> |
||||
<widget class="QToolButton" name="toolServers"> |
||||
<property name="minimumSize"> |
||||
<size> |
||||
<width>100</width> |
||||
<height>80</height> |
||||
</size> |
||||
</property> |
||||
<property name="maximumSize"> |
||||
<size> |
||||
<width>100</width> |
||||
<height>80</height> |
||||
</size> |
||||
</property> |
||||
<property name="text"> |
||||
<string>Servers</string> |
||||
</property> |
||||
<property name="icon"> |
||||
<iconset resource="../resources.qrc"> |
||||
<normaloff>:/Icons/server.png</normaloff>:/Icons/server.png</iconset> |
||||
</property> |
||||
<property name="iconSize"> |
||||
<size> |
||||
<width>100</width> |
||||
<height>90</height> |
||||
</size> |
||||
</property> |
||||
<property name="checkable"> |
||||
<bool>true</bool> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
<item> |
||||
<widget class="QToolButton" name="toolOptions"> |
||||
<property name="minimumSize"> |
||||
<size> |
||||
<width>100</width> |
||||
<height>80</height> |
||||
</size> |
||||
</property> |
||||
<property name="maximumSize"> |
||||
<size> |
||||
<width>100</width> |
||||
<height>80</height> |
||||
</size> |
||||
</property> |
||||
<property name="text"> |
||||
<string>Options</string> |
||||
</property> |
||||
<property name="icon"> |
||||
<iconset resource="../resources.qrc"> |
||||
<normaloff>:/Icons/options.png</normaloff>:/Icons/options.png</iconset> |
||||
</property> |
||||
<property name="iconSize"> |
||||
<size> |
||||
<width>100</width> |
||||
<height>90</height> |
||||
</size> |
||||
</property> |
||||
<property name="checkable"> |
||||
<bool>true</bool> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
<item> |
||||
<widget class="QToolButton" name="toolLogging"> |
||||
<property name="minimumSize"> |
||||
<size> |
||||
<width>100</width> |
||||
<height>80</height> |
||||
</size> |
||||
</property> |
||||
<property name="maximumSize"> |
||||
<size> |
||||
<width>100</width> |
||||
<height>80</height> |
||||
</size> |
||||
</property> |
||||
<property name="text"> |
||||
<string>Logging</string> |
||||
</property> |
||||
<property name="icon"> |
||||
<iconset resource="../resources.qrc"> |
||||
<normaloff>:/Icons/log.png</normaloff>:/Icons/log.png</iconset> |
||||
</property> |
||||
<property name="iconSize"> |
||||
<size> |
||||
<width>100</width> |
||||
<height>90</height> |
||||
</size> |
||||
</property> |
||||
<property name="checkable"> |
||||
<bool>true</bool> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
<item> |
||||
<spacer name="horizontalSpacer_2"> |
||||
<property name="orientation"> |
||||
<enum>Qt::Horizontal</enum> |
||||
</property> |
||||
<property name="sizeHint" stdset="0"> |
||||
<size> |
||||
<width>40</width> |
||||
<height>20</height> |
||||
</size> |
||||
</property> |
||||
</spacer> |
||||
</item> |
||||
</layout> |
||||
</item> |
||||
<item> |
||||
<widget class="QFrame" name="frame"> |
||||
<property name="sizePolicy"> |
||||
<sizepolicy hsizetype="Preferred" vsizetype="Expanding"> |
||||
<horstretch>0</horstretch> |
||||
<verstretch>0</verstretch> |
||||
</sizepolicy> |
||||
</property> |
||||
<property name="frameShape"> |
||||
<enum>QFrame::StyledPanel</enum> |
||||
</property> |
||||
<property name="frameShadow"> |
||||
<enum>QFrame::Raised</enum> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
<item> |
||||
<layout class="QHBoxLayout" name="horizontalLayout_2"> |
||||
<item> |
||||
<spacer name="horizontalSpacer_3"> |
||||
<property name="orientation"> |
||||
<enum>Qt::Horizontal</enum> |
||||
</property> |
||||
<property name="sizeHint" stdset="0"> |
||||
<size> |
||||
<width>40</width> |
||||
<height>20</height> |
||||
</size> |
||||
</property> |
||||
</spacer> |
||||
</item> |
||||
<item> |
||||
<widget class="QPushButton" name="btnSaveConnect"> |
||||
<property name="text"> |
||||
<string>Save && Connect</string> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
<item> |
||||
<widget class="QPushButton" name="btnDisconnect"> |
||||
<property name="text"> |
||||
<string>Disconnect</string> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
<item> |
||||
<widget class="QPushButton" name="btnSave"> |
||||
<property name="text"> |
||||
<string>Save</string> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
<item> |
||||
<widget class="QPushButton" name="btnClose"> |
||||
<property name="text"> |
||||
<string>Close</string> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
</layout> |
||||
</item> |
||||
</layout> |
||||
</widget> |
||||
<resources> |
||||
<include location="../resources.qrc"/> |
||||
</resources> |
||||
<connections/> |
||||
</ui> |
@ -0,0 +1,119 @@ |
||||
/*
|
||||
* IdealIRC - Internet Relay Chat client |
||||
* Copyright (C) 2019 Tom-Andre Barstad |
||||
* |
||||
* This program is free software; you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation; either version 2 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License along |
||||
* with this program; if not, write to the Free Software Foundation, Inc., |
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
||||
*/ |
||||
|
||||
#include "IConfigLogging.h" |
||||
#include "ui_IConfigLogging.h" |
||||
#include "ConfigMgr.h" |
||||
#include "config.h" |
||||
#include <QFileDialog> |
||||
#include <QDebug> |
||||
|
||||
IConfigLogging::IConfigLogging(QWidget *parent) : |
||||
QWidget(parent), |
||||
ui(new Ui::IConfigLogging) |
||||
{ |
||||
ui->setupUi(this); |
||||
ui->splitter->setStretchFactor(0, 1); |
||||
ui->splitter->setStretchFactor(1, 4); |
||||
|
||||
ConfigMgr& conf = ConfigMgr::instance(); |
||||
cf_Chnanels = conf.logging("Channels").toInt(); |
||||
cf_Privates = conf.logging("Privates").toInt(); |
||||
cf_Path = conf.logging("Path"); |
||||
|
||||
ui->chkChannels->setChecked(cf_Chnanels); |
||||
ui->chkPrivates->setChecked(cf_Privates); |
||||
ui->edPath->setText(cf_Path); |
||||
} |
||||
|
||||
IConfigLogging::~IConfigLogging() |
||||
{ |
||||
delete ui; |
||||
} |
||||
|
||||
bool IConfigLogging::isChanged() const |
||||
{ |
||||
return cf_Chnanels != ui->chkChannels->isChecked() |
||||
|| cf_Privates != ui->chkPrivates->isChecked() |
||||
|| cf_Path != ui->edPath->text(); |
||||
} |
||||
|
||||
void IConfigLogging::save() |
||||
{ |
||||
ConfigMgr& conf = ConfigMgr::instance(); |
||||
conf.setLogging("Channels", QString::number(ui->chkChannels->isChecked())); |
||||
conf.setLogging("Privates", QString::number(ui->chkPrivates->isChecked())); |
||||
conf.setLogging("Path", ui->edPath->text()); |
||||
|
||||
cf_Chnanels = conf.logging("Channels").toInt(); |
||||
cf_Privates = conf.logging("Privates").toInt(); |
||||
cf_Path = conf.logging("Path"); |
||||
} |
||||
|
||||
void IConfigLogging::reset() |
||||
{ |
||||
ui->chkChannels->setChecked(cf_Chnanels); |
||||
ui->chkPrivates->setChecked(cf_Privates); |
||||
ui->edPath->setText(cf_Path); |
||||
} |
||||
|
||||
void IConfigLogging::showEvent(QShowEvent* evt) |
||||
{ |
||||
QWidget::showEvent(evt); |
||||
if (!ui->edPath->text().isEmpty()) |
||||
loadFiles(ui->edPath->text()); |
||||
} |
||||
|
||||
void IConfigLogging::on_btnBrowse_clicked() |
||||
{ |
||||
QString dirstr = QFileDialog::getExistingDirectory(this, tr("Log directory"), LOCAL_PATH); |
||||
loadFiles(dirstr); |
||||
} |
||||
|
||||
void IConfigLogging::loadFiles(const QString& path) |
||||
{ |
||||
ui->fileList->clear(); |
||||
ui->edPath->setText(path); |
||||
if (path.isEmpty()) |
||||
return; |
||||
QDir dir(path); |
||||
QStringList files = dir.entryList(QDir::Files, QDir::Name); |
||||
ui->fileList->addItems(files); |
||||
} |
||||
|
||||
void IConfigLogging::on_edPath_textChanged(const QString &arg1) |
||||
{ |
||||
loadFiles(ui->edPath->text()); |
||||
} |
||||
|
||||
void IConfigLogging::on_fileList_itemClicked(QListWidgetItem *item) |
||||
{ |
||||
if (!item) |
||||
return; |
||||
|
||||
QString filePath = ui->edPath->text() + "/" + item->text(); |
||||
QFile f(filePath); |
||||
if (!f.open(QIODevice::ReadOnly)) { |
||||
qWarning() << "Unable to open log file" << filePath; |
||||
return; |
||||
} |
||||
QByteArray data = f.readAll(); |
||||
f.close(); |
||||
ui->logView->setPlainText(data); |
||||
} |
@ -0,0 +1,56 @@ |
||||
/*
|
||||
* IdealIRC - Internet Relay Chat client |
||||
* Copyright (C) 2019 Tom-Andre Barstad |
||||
* |
||||
* This program is free software; you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation; either version 2 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License along |
||||
* with this program; if not, write to the Free Software Foundation, Inc., |
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
||||
*/ |
||||
|
||||
#ifndef ICONFIGLOGGING_H |
||||
#define ICONFIGLOGGING_H |
||||
|
||||
#include <QWidget> |
||||
#include <QListWidgetItem> |
||||
#include <QShowEvent> |
||||
|
||||
namespace Ui { |
||||
class IConfigLogging; |
||||
} |
||||
|
||||
class IConfigLogging : public QWidget |
||||
{ |
||||
Q_OBJECT |
||||
|
||||
public: |
||||
explicit IConfigLogging(QWidget *parent = nullptr); |
||||
~IConfigLogging(); |
||||
bool isChanged() const; |
||||
void save(); |
||||
void reset(); |
||||
|
||||
private slots: |
||||
void showEvent(QShowEvent* evt); |
||||
void on_btnBrowse_clicked(); |
||||
void on_edPath_textChanged(const QString &arg1); |
||||
void on_fileList_itemClicked(QListWidgetItem *item); |
||||
|
||||
private: |
||||
void loadFiles(const QString& path); |
||||
Ui::IConfigLogging *ui; |
||||
bool cf_Chnanels; |
||||
bool cf_Privates; |
||||
QString cf_Path; |
||||
}; |
||||
|
||||
#endif // ICONFIGLOGGING_H
|
@ -0,0 +1,85 @@ |
||||
<?xml version="1.0" encoding="UTF-8"?> |
||||
<ui version="4.0"> |
||||
<class>IConfigLogging</class> |
||||
<widget class="QWidget" name="IConfigLogging"> |
||||
<property name="geometry"> |
||||
<rect> |
||||
<x>0</x> |
||||
<y>0</y> |
||||
<width>559</width> |
||||
<height>463</height> |
||||
</rect> |
||||
</property> |
||||
<property name="windowTitle"> |
||||
<string>Form</string> |
||||
</property> |
||||
<layout class="QVBoxLayout" name="verticalLayout"> |
||||
<item> |
||||
<layout class="QVBoxLayout" name="verticalLayout_2"> |
||||
<item> |
||||
<widget class="QCheckBox" name="chkChannels"> |
||||
<property name="text"> |
||||
<string>Enable for channels</string> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
<item> |
||||
<widget class="QCheckBox" name="chkPrivates"> |
||||
<property name="text"> |
||||
<string>Enable for private messages</string> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
</layout> |
||||
</item> |
||||
<item> |
||||
<layout class="QHBoxLayout" name="horizontalLayout_2"> |
||||
<item> |
||||
<widget class="QLabel" name="label"> |
||||
<property name="text"> |
||||
<string>Path to logs</string> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
<item> |
||||
<widget class="QLineEdit" name="edPath"/> |
||||
</item> |
||||
<item> |
||||
<widget class="QPushButton" name="btnBrowse"> |
||||
<property name="text"> |
||||
<string>Browse...</string> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
</layout> |
||||
</item> |
||||
<item> |
||||
<widget class="QSplitter" name="splitter"> |
||||
<property name="sizePolicy"> |
||||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding"> |
||||
<horstretch>0</horstretch> |
||||
<verstretch>0</verstretch> |
||||
</sizepolicy> |
||||
</property> |
||||
<property name="orientation"> |
||||
<enum>Qt::Horizontal</enum> |
||||
</property> |
||||
<property name="childrenCollapsible"> |
||||
<bool>true</bool> |
||||
</property> |
||||
<widget class="QListWidget" name="fileList"/> |
||||
<widget class="QPlainTextEdit" name="logView"> |
||||
<property name="readOnly"> |
||||
<bool>true</bool> |
||||
</property> |
||||
<property name="placeholderText"> |
||||
<string>Select a log file on the left to view...</string> |
||||
</property> |
||||
</widget> |
||||
</widget> |
||||
</item> |
||||
</layout> |
||||
</widget> |
||||
<resources/> |
||||
<connections/> |
||||
</ui> |
@ -0,0 +1,175 @@ |
||||
/*
|
||||
* IdealIRC - Internet Relay Chat client |
||||
* Copyright (C) 2019 Tom-Andre Barstad |
||||
* |
||||
* This program is free software; you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation; either version 2 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License along |
||||
* with this program; if not, write to the Free Software Foundation, Inc., |
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
||||
*/ |
||||
|
||||
#include "IConfigOptions.h" |
||||
#include "ui_IConfigOptions.h" |
||||
#include "ConfigMgr.h" |
||||
#include "config.h" |
||||
#include <QMessageBox> |
||||
#include <QFileDialog> |
||||
|
||||
IConfigOptions::IConfigOptions(QWidget *parent) : |
||||
QWidget(parent), |
||||
ui(new Ui::IConfigOptions) |
||||
{ |
||||
ui->setupUi(this); |
||||
layout = new QVBoxLayout(ui->tabColors); |
||||
colorCfg = new ColorConfig; |
||||
{ |
||||
QLabel* bgcolorhintlabel = new QLabel("Click on a text type to change its color.\nClick anywhere on the backgrounds to change background color."); |
||||
QFont labelFont = font(); |
||||
bgcolorhintlabel->setAlignment(Qt::AlignCenter); |
||||
labelFont.setPointSize(8); |
||||
bgcolorhintlabel->setFont(labelFont); |
||||
layout->addWidget(bgcolorhintlabel); |
||||
} |
||||
layout->addWidget(colorCfg); |
||||
colorCfg->setSizePolicy(QSizePolicy::Policy::Expanding, QSizePolicy::Policy::Expanding); |
||||
ui->tabColors->setLayout(layout); |
||||
|
||||
/* Background opacity is not yet determined */ |
||||
ui->label_4->hide(); |
||||
ui->hsImageOpacity->hide(); |
||||
|
||||
/* Background scaling is not yet determined */ |
||||
ui->label_5->hide(); |
||||
ui->edImageScaling->hide(); |
||||
|
||||
reload(); |
||||
reset(); |
||||
} |
||||
|
||||
IConfigOptions::~IConfigOptions() |
||||
{ |
||||
delete ui; |
||||
} |
||||
|
||||
bool IConfigOptions::isChanged() const |
||||
{ |
||||
bool changed = cf_ShowOptions != ui->chkShowOptions->isChecked() |
||||
|| cf_Reconnect != ui->chkReconnect->isChecked() |
||||
|| cf_RejoinChannelsOnConnect != ui->chkRejoinConnect->isChecked() |
||||
|| cf_ShowWhoisActiveWindow != ui->chkWhoisActive->isChecked() |
||||
|| cf_ShowModeInMessage != ui->chkShowModeMsg->isChecked() |
||||
|| cf_TrayNotify != ui->chkTrayNotify->isChecked() |
||||
|| cf_TrayNotifyDelay != ui->edTrayDelay->value() |
||||
|| cf_ShowTimestamp != ui->chkTimestamp->isChecked() |
||||
|| cf_TimestampFormat != ui->edTimestamp->text() |
||||
|| cf_QuitMessage != ui->edQuit->text() |
||||
|| cf_Font.family() != ui->edFont->currentFont().family() |
||||
|| cf_FontSize != ui->edFontSize->value() |
||||
|| cf_BgImageEnabled != ui->chkEnableBgImage->isChecked() |
||||
|| cf_BgImagePath != ui->edImage->text() |
||||
// TODO background image scaling combo box
|
||||
|| cf_BgImageOpacity != ui->hsImageOpacity->value() |
||||
|| cf_SSLSelfSigned != ui->chkSSLSelfsigned->isChecked() |
||||
|| cf_SSLExpired != ui->chkSSLExpired->isChecked() |
||||
|| cf_SSLCNMismatch != ui->chkSSLCNMismatch->isChecked(); |
||||
|
||||
if (changed) |
||||
return true; |
||||
else |
||||
return colorCfg->isChanged(); |
||||
} |
||||
|
||||
void IConfigOptions::save() |
||||
{ |
||||
ConfigMgr& conf = ConfigMgr::instance(); |
||||
conf.setCommon("ShowOptions", QString::number(ui->chkShowOptions->isChecked())); |
||||
conf.setCommon("Reconnect", QString::number(ui->chkReconnect->isChecked())); |
||||
conf.setCommon("RejoinChannelsOnConnect", QString::number(ui->chkRejoinConnect->isChecked())); |
||||
conf.setCommon("ShowWhoisActiveWindow", QString::number(ui->chkWhoisActive->isChecked())); |
||||
conf.setCommon("ShowModeInMessage", QString::number(ui->chkShowModeMsg->isChecked())); |
||||
conf.setCommon("TrayNotify", QString::number(ui->chkTrayNotify->isChecked())); |
||||
conf.setCommon("TrayNotifyDelay", QString::number(ui->edTrayDelay->value())); |
||||
conf.setCommon("ShowTimestamp", QString::number(ui->chkTimestamp->isChecked())); |
||||
conf.setCommon("TimestampFormat", ui->edTimestamp->text()); |
||||
conf.setCommon("QuitMessage", ui->edQuit->text()); |
||||
conf.setCommon("Font", ui->edFont->currentFont().family()); |
||||
conf.setCommon("FontSize", QString::number(ui->edFontSize->value())); |
||||
conf.setCommon("BgImageEnabled", QString::number(ui->chkEnableBgImage->isChecked())); |
||||
conf.setCommon("BgImagePath", ui->edImage->text()); |
||||
conf.setCommon("BgImageOpacity", QString::number(ui->hsImageOpacity->value())); |
||||
conf.setCommon("SSLSelfsigned", QString::number(ui->chkSSLSelfsigned->isChecked())); |
||||
conf.setCommon("SSLExpired", QString::number(ui->chkSSLExpired->isChecked())); |
||||
conf.setCommon("SSLCNMismatch", QString::number(ui->chkSSLCNMismatch->isChecked())); |
||||
reload(); |
||||
colorCfg->save(); |
||||
} |
||||
|
||||
void IConfigOptions::reset() |
||||
{ |
||||
ui->chkShowOptions->setChecked(cf_ShowOptions); |
||||
ui->chkReconnect->setChecked(cf_Reconnect); |
||||
ui->chkRejoinConnect->setChecked(cf_RejoinChannelsOnConnect); |
||||
ui->chkWhoisActive->setChecked(cf_ShowWhoisActiveWindow); |
||||
ui->chkShowModeMsg->setChecked(cf_ShowModeInMessage); |
||||
ui->chkTrayNotify->setChecked(cf_TrayNotify); |
||||
ui->edTrayDelay->setValue(cf_TrayNotifyDelay); |
||||
ui->chkTimestamp->setChecked(cf_ShowTimestamp); |
||||
ui->edTimestamp->setText(cf_TimestampFormat); |
||||
ui->edQuit->setText(cf_QuitMessage); |
||||
ui->edFont->setCurrentFont(cf_Font); |
||||
ui->edFontSize->setValue(cf_FontSize); |
||||
ui->chkEnableBgImage->setChecked(cf_BgImageEnabled); |
||||
ui->edImage->setText(cf_BgImagePath); |
||||
ui->hsImageOpacity->setValue(cf_BgImageOpacity); |
||||
ui->chkSSLSelfsigned->setChecked(cf_SSLSelfSigned); |
||||
ui->chkSSLExpired->setChecked(cf_SSLExpired); |
||||
ui->chkSSLCNMismatch->setChecked(cf_SSLCNMismatch); |
||||
|
||||
colorCfg->reset(); |
||||
} |
||||
|
||||
void IConfigOptions::on_chkSSLExpired_toggled(bool checked) |
||||
{ |
||||
if (checked) |
||||
QMessageBox::warning(this, tr("Expired SSL certificates"), |
||||
tr("Allowing expired certificates is dangerous!\nThis option will revert after next connection.")); |
||||
} |
||||
|
||||
void IConfigOptions::reload() |
||||
{ |
||||
ConfigMgr& conf = ConfigMgr::instance(); |
||||
cf_ShowOptions = conf.common("ShowOptions").toInt(); |
||||
cf_Reconnect = conf.common("Reconnect").toInt(); |
||||
cf_RejoinChannelsOnConnect = conf.common("RejoinChannelsOnConnect").toInt(); |
||||
cf_ShowWhoisActiveWindow = conf.common("ShowWhoisActiveWindow").toInt(); |
||||
cf_ShowModeInMessage = conf.common("ShowModeInMessage").toInt(); |
||||
cf_TrayNotify = conf.common("TrayNotify").toInt(); |
||||
cf_TrayNotifyDelay = conf.common("TrayNotifyDelay").toInt(); |
||||
cf_ShowTimestamp = conf.common("ShowTimestamp").toInt(); |
||||
cf_TimestampFormat = conf.common("TimestampFormat"); |
||||
cf_QuitMessage = conf.common("QuitMessage"); |
||||
cf_Font = conf.common("Font"); |
||||
cf_FontSize = conf.common("FontSize").toInt(); |
||||
cf_BgImageEnabled = conf.common("BgImageEnabled").toInt(); |
||||
cf_BgImagePath = conf.common("BgImagePath"); |
||||
cf_BgImageOpacity = conf.common("BgImageOpacity").toInt(); |
||||
// TODO background image scaling combo box from config
|
||||
cf_SSLSelfSigned = conf.common("SSLSelfsigned").toInt(); |
||||
cf_SSLExpired = conf.common("SSLExpired").toInt(); |
||||
cf_SSLCNMismatch = conf.common("SSLCNMismatch").toInt(); |
||||
} |
||||
|
||||
void IConfigOptions::on_btnImageBrowse_clicked() |
||||
{ |
||||
QString path = QFileDialog::getOpenFileName(this, tr("Choose background image"), LOCAL_PATH, tr("Image Files (*.png *.jpg *.bmp)")); |
||||
ui->edImage->setText(path); |
||||
} |
@ -0,0 +1,74 @@ |
||||
/*
|
||||
* IdealIRC - Internet Relay Chat client |
||||
* Copyright (C) 2019 Tom-Andre Barstad |
||||
* |
||||
* This program is free software; you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation; either version 2 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License along |
||||
* with this program; if not, write to the Free Software Foundation, Inc., |
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
||||
*/ |
||||
|
||||
#ifndef ICONFIGOPTIONS_H |
||||
#define ICONFIGOPTIONS_H |
||||
|
||||
#include "ColorConfig.h" |
||||
#include <QWidget> |
||||
#include <QHBoxLayout> |
||||
#include <QVBoxLayout> |
||||
|
||||
namespace Ui { |
||||
class IConfigOptions; |
||||
} |
||||
|
||||
class IConfigOptions : public QWidget |
||||
{ |
||||
Q_OBJECT |
||||
|
||||
public: |
||||
explicit IConfigOptions(QWidget *parent = nullptr); |
||||
~IConfigOptions(); |
||||
bool isChanged() const; |
||||
void save(); |
||||
void reset(); |
||||
|
||||
private slots: |
||||
void on_chkSSLExpired_toggled(bool checked); |
||||
|
||||
void on_btnImageBrowse_clicked(); |
||||
|
||||
private: |
||||
void reload(); |
||||
Ui::IConfigOptions *ui; |
||||
QVBoxLayout *layout; |
||||
ColorConfig *colorCfg; |
||||
bool cf_ShowOptions; |
||||
bool cf_Reconnect; |
||||
bool cf_RejoinChannelsOnConnect; |
||||
bool cf_ShowWhoisActiveWindow; |
||||
bool cf_ShowModeInMessage; |
||||
bool cf_TrayNotify; |
||||
int cf_TrayNotifyDelay; |
||||
bool cf_ShowTimestamp; |
||||
QString cf_TimestampFormat; |
||||
QString cf_QuitMessage; |
||||
QFont cf_Font; |
||||
int cf_FontSize; |
||||
bool cf_BgImageEnabled; |
||||
QString cf_BgImagePath; |
||||
int cf_BgImageOpacity; |
||||
// TODO background image scaling combo box
|
||||
bool cf_SSLSelfSigned; |
||||
bool cf_SSLExpired; |
||||
bool cf_SSLCNMismatch; |
||||
}; |
||||
|
||||
#endif // ICONFIGOPTIONS_H
|
@ -0,0 +1,382 @@ |
||||
<?xml version="1.0" encoding="UTF-8"?> |
||||
<ui version="4.0"> |
||||
<class>IConfigOptions</class> |
||||
<widget class="QWidget" name="IConfigOptions"> |
||||
<property name="geometry"> |
||||
<rect> |
||||
<x>0</x> |
||||
<y>0</y> |
||||
<width>626</width> |
||||
<height>543</height> |
||||
</rect> |
||||
</property> |
||||
<property name="windowTitle"> |
||||
<string>Form</string> |
||||
</property> |
||||
<layout class="QVBoxLayout" name="verticalLayout"> |
||||
<item> |
||||
<widget class="QTabWidget" name="tabWidget"> |
||||
<property name="currentIndex"> |
||||
<number>0</number> |
||||
</property> |
||||
<widget class="QWidget" name="tabCommon"> |
||||
<attribute name="title"> |
||||
<string>Common</string> |
||||
</attribute> |
||||
<layout class="QVBoxLayout" name="verticalLayout_3"> |
||||
<item> |
||||
<widget class="QCheckBox" name="chkShowOptions"> |
||||
<property name="text"> |
||||
<string>Show options upon start</string> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
<item> |
||||
<widget class="QCheckBox" name="chkReconnect"> |
||||
<property name="text"> |
||||
<string>Automatic re-connect after lost connection</string> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
<item> |
||||
<widget class="QCheckBox" name="chkRejoinConnect"> |
||||
<property name="text"> |
||||
<string>Auto re-join channels after re-connect</string> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
<item> |
||||
<widget class="QCheckBox" name="chkWhoisActive"> |
||||
<property name="text"> |
||||
<string>Show /whois in active window</string> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
<item> |
||||
<widget class="QCheckBox" name="chkShowModeMsg"> |
||||
<property name="text"> |
||||
<string>Show modes in messages</string> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
<item> |
||||
<layout class="QHBoxLayout" name="horizontalLayout"> |
||||
<item> |
||||
<widget class="QCheckBox" name="chkTrayNotify"> |
||||
<property name="text"> |
||||
<string>Tray notify, delay:</string> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
<item> |
||||
<widget class="QSpinBox" name="edTrayDelay"> |
||||
<property name="suffix"> |
||||
<string> sec</string> |
||||
</property> |
||||
<property name="minimum"> |
||||
<number>1</number> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
<item> |
||||
<spacer name="horizontalSpacer_2"> |
||||
<property name="orientation"> |
||||
<enum>Qt::Horizontal</enum> |
||||
</property> |
||||
<property name="sizeHint" stdset="0"> |
||||
<size> |
||||
<width>40</width> |
||||
<height>20</height> |
||||
</size> |
||||
</property> |
||||
</spacer> |
||||
</item> |
||||
</layout> |
||||
</item> |
||||
<item> |
||||
<layout class="QHBoxLayout" name="horizontalLayout_2"> |
||||
<item> |
||||
<widget class="QCheckBox" name="chkTimestamp"> |
||||
<property name="text"> |
||||
<string>Display timestamp, format:</string> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
<item> |
||||
<widget class="QLineEdit" name="edTimestamp"/> |
||||
</item> |
||||
<item> |
||||
<spacer name="horizontalSpacer"> |
||||
<property name="orientation"> |
||||
<enum>Qt::Horizontal</enum> |
||||
</property> |
||||
<property name="sizeHint" stdset="0"> |
||||
<size> |
||||
<width>40</width> |
||||
<height>20</height> |
||||
</size> |
||||
</property> |
||||
</spacer> |
||||
</item> |
||||
</layout> |
||||
</item> |
||||
<item> |
||||
<widget class="QLabel" name="label"> |
||||
<property name="text"> |
||||
<string>Quit message</string> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
<item> |
||||
<widget class="QLineEdit" name="edQuit"/> |
||||
</item> |
||||
<item> |
||||
<widget class="QGroupBox" name="groupBox"> |
||||
<property name="title"> |
||||
<string>Font</string> |
||||
</property> |
||||
<layout class="QHBoxLayout" name="horizontalLayout_4"> |
||||
<item> |
||||
<layout class="QVBoxLayout" name="verticalLayout_2"> |
||||
<item> |
||||
<widget class="QFontComboBox" name="edFont"/> |
||||
</item> |
||||
<item> |
||||
<layout class="QHBoxLayout" name="horizontalLayout_3"> |
||||
<item> |
||||
<widget class="QLabel" name="label_2"> |
||||
<property name="text"> |
||||
<string>Size</string> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
<item> |
||||
<widget class="QSpinBox" name="edFontSize"> |
||||
<property name="suffix"> |
||||
<string> px</string> |
||||
</property> |
||||
<property name="minimum"> |
||||
<number>1</number> |
||||
</property> |
||||
<property name="maximum"> |
||||
<number>200</number> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
</layout> |
||||
</item> |
||||
</layout> |
||||
</item> |
||||
<item> |
||||
<spacer name="horizontalSpacer_3"> |
||||
<property name="orientation"> |
||||
<enum>Qt::Horizontal</enum> |
||||
</property> |
||||
<property name="sizeHint" stdset="0"> |
||||
<size> |
||||
<width>323</width> |
||||
<height>20</height> |
||||
</size> |
||||
</property> |
||||
</spacer> |
||||
</item> |
||||
</layout> |
||||
</widget> |
||||
</item> |
||||
<item> |
||||
<widget class="QGroupBox" name="groupBox_2"> |
||||
<property name="title"> |
||||
<string>SSL exceptions</string> |
||||
</property> |
||||
<layout class="QVBoxLayout" name="verticalLayout_4"> |
||||
<item> |
||||
<widget class="QCheckBox" name="chkSSLSelfsigned"> |
||||
<property name="text"> |
||||
<string>Allow self-signed certificates</string> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
<item> |
||||
<widget class="QCheckBox" name="chkSSLExpired"> |
||||
<property name="text"> |
||||
<string>Allow expired certificates</string> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
<item> |
||||
<widget class="QCheckBox" name="chkSSLCNMismatch"> |
||||
<property name="text"> |
||||
<string>Allow mismatching Common Name (host name)</string> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
</layout> |
||||
</widget> |
||||
</item> |
||||
<item> |
||||
<spacer name="verticalSpacer"> |
||||
<property name="orientation"> |
||||
<enum>Qt::Vertical</enum> |
||||
</property> |
||||
<property name="sizeHint" stdset="0"> |
||||
<size> |
||||
<width>20</width> |
||||
<height>44</height> |
||||
</size> |
||||
</property> |
||||
</spacer> |
||||
</item> |
||||
</layout> |
||||
</widget> |
||||
<widget class="QWidget" name="tabColors"> |
||||
<attribute name="title"> |
||||
<string>Colors</string> |
||||
</attribute> |
||||
</widget> |
||||
<widget class="QWidget" name="tabBgImg"> |
||||
<attribute name="title"> |
||||
<string>Background image</string> |
||||
</attribute> |
||||
<layout class="QGridLayout" name="gridLayout"> |
||||
<item row="3" column="0"> |
||||
<layout class="QHBoxLayout" name="horizontalLayout_6"> |
||||
<item> |
||||
<widget class="QLabel" name="label_4"> |
||||
<property name="sizePolicy"> |
||||
<sizepolicy hsizetype="Maximum" vsizetype="Preferred"> |
||||
<horstretch>0</horstretch> |
||||
<verstretch>0</verstretch> |
||||
</sizepolicy> |
||||
</property> |
||||
<property name="text"> |
||||
<string>Opacity</string> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
<item> |
||||
<widget class="QSlider" name="hsImageOpacity"> |
||||
<property name="maximum"> |
||||
<number>100</number> |
||||
</property> |
||||
<property name="value"> |
||||
<number>100</number> |
||||
</property> |
||||
<property name="orientation"> |
||||
<enum>Qt::Horizontal</enum> |
||||
</property> |
||||
<property name="invertedAppearance"> |
||||
<bool>false</bool> |
||||
</property> |
||||
<property name="invertedControls"> |
||||
<bool>false</bool> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
</layout> |
||||
</item> |
||||
<item row="4" column="1"> |
||||
<spacer name="horizontalSpacer_4"> |
||||
<property name="orientation"> |
||||
<enum>Qt::Horizontal</enum> |
||||
</property> |
||||
<property name="sizeHint" stdset="0"> |
||||
<size> |
||||
<width>287</width> |
||||
<height>20</height> |
||||
</size> |
||||
</property> |
||||
</spacer> |
||||
</item> |
||||
<item row="1" column="0"> |
||||
<widget class="QCheckBox" name="chkEnableBgImage"> |
||||
<property name="text"> |
||||
<string>Enable background image</string> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
<item row="4" column="0"> |
||||
<layout class="QHBoxLayout" name="horizontalLayout_7"> |
||||
<item> |
||||
<widget class="QLabel" name="label_5"> |
||||
<property name="sizePolicy"> |
||||
<sizepolicy hsizetype="Maximum" vsizetype="Preferred"> |
||||
<horstretch>0</horstretch> |
||||
<verstretch>0</verstretch> |
||||
</sizepolicy> |
||||
</property> |
||||
<property name="text"> |
||||
<string>Scaling</string> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
<item> |
||||
<widget class="QComboBox" name="edImageScaling"/> |
||||
</item> |
||||
</layout> |
||||
</item> |
||||
<item row="5" column="0"> |
||||
<spacer name="verticalSpacer_2"> |
||||
<property name="orientation"> |
||||
<enum>Qt::Vertical</enum> |
||||
</property> |
||||
<property name="sizeHint" stdset="0"> |
||||
<size> |
||||
<width>20</width> |
||||
<height>301</height> |
||||
</size> |
||||
</property> |
||||
</spacer> |
||||
</item> |
||||
<item row="2" column="0" colspan="2"> |
||||
<layout class="QHBoxLayout" name="horizontalLayout_5"> |
||||
<item> |
||||
<widget class="QLabel" name="label_3"> |
||||
<property name="text"> |
||||
<string>Image file</string> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
<item> |
||||
<widget class="QLineEdit" name="edImage"/> |
||||
</item> |
||||
<item> |
||||
<widget class="QPushButton" name="btnImageBrowse"> |
||||
<property name="text"> |
||||
<string>Browse...</string> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
</layout> |
||||
</item> |
||||
</layout> |
||||
</widget> |
||||
</widget> |
||||
</item> |
||||
</layout> |
||||
</widget> |
||||
<tabstops> |
||||
<tabstop>chkShowOptions</tabstop> |
||||
<tabstop>chkReconnect</tabstop> |
||||
<tabstop>chkRejoinConnect</tabstop> |
||||
<tabstop>chkWhoisActive</tabstop> |
||||
<tabstop>chkShowModeMsg</tabstop> |
||||
<tabstop>chkTrayNotify</tabstop> |
||||
<tabstop>edTrayDelay</tabstop> |
||||
<tabstop>chkTimestamp</tabstop> |
||||
<tabstop>edTimestamp</tabstop> |
||||
<tabstop>edQuit</tabstop> |
||||
<tabstop>edFont</tabstop> |
||||
<tabstop>edFontSize</tabstop> |
||||
<tabstop>chkSSLSelfsigned</tabstop> |
||||
<tabstop>chkSSLExpired</tabstop> |
||||
<tabstop>chkSSLCNMismatch</tabstop> |
||||
<tabstop>chkEnableBgImage</tabstop> |
||||
<tabstop>edImage</tabstop> |
||||
<tabstop>btnImageBrowse</tabstop> |
||||
<tabstop>hsImageOpacity</tabstop> |
||||
<tabstop>edImageScaling</tabstop> |
||||
<tabstop>tabWidget</tabstop> |
||||
</tabstops> |
||||
<resources/> |
||||
<connections/> |
||||
</ui> |
@ -0,0 +1,137 @@ |
||||
/*
|
||||
* IdealIRC - Internet Relay Chat client |
||||
* Copyright (C) 2019 Tom-Andre Barstad |
||||
* |
||||
* This program is free software; you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation; either version 2 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License along |
||||
* with this program; if not, write to the Free Software Foundation, Inc., |
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
||||
*/ |
||||
|
||||
#include "IConfigServers.h" |
||||
#include "ui_IConfigServers.h" |
||||
#include "ConfigMgr.h" |
||||
#include <QDebug> |
||||
|
||||
IConfigServers::IConfigServers(QWidget *parent) : |
||||
QWidget(parent), |
||||
ui(new Ui::IConfigServers) |
||||
{ |
||||
ui->setupUi(this); |
||||
|
||||
ConfigMgr& conf = ConfigMgr::instance(); |
||||
cf_Realname = conf.connection("Realname"); |
||||
cf_Username = conf.connection("Username"); |
||||
cf_Nickname = conf.connection("Nickname"); |
||||
cf_AltNickame = conf.connection("AltNickname"); |
||||
cf_Server = conf.connection("Server"); |
||||
cf_Password = conf.connection("Password"); |
||||
cf_SSL = conf.connection("SSL").toInt(); |
||||
|
||||
ui->edRealName->setText(cf_Realname); |
||||
ui->edUsername->setText(cf_Username); |
||||
ui->edNickname->setText(cf_Nickname); |
||||
ui->edAltNickname->setText(cf_AltNickame); |
||||
ui->edServer->setText(cf_Server); |
||||
ui->edServerPassword->setText(cf_Password); |
||||
ui->chkSSL->setChecked(cf_SSL); |
||||
|
||||
editor = new ServerEditor(smodel, this); |
||||
ui->servers->setModel(&smodel); |
||||
|
||||
QModelIndex sindex = smodel.indexFromHost(cf_Server); |
||||
if (sindex.isValid()) |
||||
ui->servers->selectionModel()->setCurrentIndex(sindex, QItemSelectionModel::Select | QItemSelectionModel::Rows); |
||||
} |
||||
|
||||
IConfigServers::~IConfigServers() |
||||
{ |
||||
delete ui; |
||||
} |
||||
|
||||
bool IConfigServers::isChanged() const |
||||
{ |
||||
return cf_Realname != ui->edRealName->text() |
||||
|| cf_Username != ui->edUsername->text() |
||||
|| cf_Nickname != ui->edNickname->text() |
||||
|| cf_AltNickame != ui->edAltNickname->text() |
||||
|| cf_Server != ui->edServer->text() |
||||
|| cf_Password != ui->edServerPassword->text() |
||||
|| cf_SSL != ui->chkSSL->isChecked(); |
||||
} |
||||
|
||||
bool IConfigServers::connectToNewStatus() const |
||||
{ |
||||
return ui->chkNewStatus->isChecked(); |
||||
} |
||||
|
||||
void IConfigServers::unsetConnectToNewStatus() |
||||
{ |
||||
ui->chkNewStatus->setChecked(false); |
||||
} |
||||
|
||||
void IConfigServers::save() |
||||
{ |
||||
ConfigMgr& conf = ConfigMgr::instance(); |
||||
conf.setConnection("Realname", ui->edRealName->text()); |
||||
conf.setConnection("Username", ui->edUsername->text()); |
||||
conf.setConnection("Nickname", ui->edNickname->text()); |
||||
conf.setConnection("AltNickname", ui->edAltNickname->text()); |
||||
conf.setConnection("Server", ui->edServer->text()); |
||||
conf.setConnection("Password", ui->edServerPassword->text()); |
||||
conf.setConnection("SSL", QString::number(ui->chkSSL->isChecked())); |
||||
|
||||
cf_Realname = conf.connection("Realname"); |
||||
cf_Username = conf.connection("Username"); |
||||
cf_Nickname = conf.connection("Nickname"); |
||||
cf_AltNickame = conf.connection("AltNickname"); |
||||
cf_Server = conf.connection("Server"); |
||||
cf_Password = conf.connection("Password"); |
||||
cf_SSL = conf.connection("SSL").toInt(); |
||||
} |
||||
|
||||
void IConfigServers::reset() |
||||
{ |
||||
ui->edRealName->setText(cf_Realname); |
||||
ui->edUsername->setText(cf_Username); |
||||
ui->edNickname->setText(cf_Nickname); |
||||
ui->edAltNickname->setText(cf_AltNickame); |
||||
ui->edServer->setText(cf_Server); |
||||
ui->edServerPassword->setText(cf_Password); |
||||
} |
||||
|
||||
|
||||
void IConfigServers::on_btnShowPassword_toggled(bool checked) |
||||
{ |
||||
ui->edServerPassword->setEchoMode(checked ? QLineEdit::Normal : QLineEdit::Password); |
||||
} |
||||
|
||||
void IConfigServers::on_btnEditServers_clicked() |
||||
{ |
||||
editor->show(); |
||||
} |
||||
|
||||
void IConfigServers::on_servers_clicked(const QModelIndex &index) |
||||
{ |
||||
auto spair = smodel.fromIndex(index); |
||||
QString details = smodel.details(spair.second, spair.first); |
||||
QString server; |
||||
QString password; |
||||
if (details.contains('|')) { |
||||
password = details.split('|')[1]; |
||||
details.remove("|"+password); |
||||
} |
||||
server = details; |
||||
|
||||
ui->edServer->setText(server); |
||||
ui->edServerPassword->setText(password); |
||||
} |
@ -0,0 +1,62 @@ |
||||
/*
|
||||
* IdealIRC - Internet Relay Chat client |
||||
* Copyright (C) 2019 Tom-Andre Barstad |
||||
* |
||||
* This program is free software; you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation; either version 2 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License along |
||||
* with this program; if not, write to the Free Software Foundation, Inc., |
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
||||
*/ |
||||
|
||||
#ifndef ICONFIGSERVERS_H |
||||
#define ICONFIGSERVERS_H |
||||
|
||||
#include "ServerEditor.h" |
||||
#include "ServerModel.h" |
||||
#include <QWidget> |
||||
|
||||
namespace Ui { |
||||
class IConfigServers; |
||||
} |
||||
|
||||
class IConfigServers : public QWidget |
||||
{ |
||||
Q_OBJECT |
||||
|
||||
public: |
||||
explicit IConfigServers(QWidget *parent = nullptr); |
||||
~IConfigServers(); |
||||
bool isChanged() const; |
||||
bool connectToNewStatus() const; |
||||
void unsetConnectToNewStatus(); |
||||
void save(); |
||||
void reset(); |
||||
|
||||
private slots: |
||||
void on_btnShowPassword_toggled(bool checked); |
||||
void on_btnEditServers_clicked(); |
||||
void on_servers_clicked(const QModelIndex &index); |
||||
|
||||
private: |
||||
Ui::IConfigServers *ui; |
||||
ServerModel smodel; |
||||
ServerEditor *editor; |
||||
QString cf_Realname; |
||||
QString cf_Username; |
||||
QString cf_Nickname; |
||||
QString cf_AltNickame; |
||||
QString cf_Server; |
||||
QString cf_Password; |
||||
bool cf_SSL; |
||||
}; |
||||
|
||||
#endif // ICONFIGSERVERS_H
|
@ -0,0 +1,247 @@ |
||||
<?xml version="1.0" encoding="UTF-8"?> |
||||
<ui version="4.0"> |
||||
<class>IConfigServers</class> |
||||
<widget class="QWidget" name="IConfigServers"> |
||||
<property name="geometry"> |
||||
<rect> |
||||
<x>0</x> |
||||
<y>0</y> |
||||
<width>556</width> |
||||
<height>527</height> |
||||
</rect> |
||||
</property> |
||||
<property name="windowTitle"> |
||||
<string>Form</string> |
||||
</property> |
||||
<layout class="QVBoxLayout" name="verticalLayout"> |
||||
<item> |
||||
<layout class="QHBoxLayout" name="horizontalLayout"> |
||||
<item> |
||||
<widget class="QLabel" name="label"> |
||||
<property name="minimumSize"> |
||||
<size> |
||||
<width>150</width> |
||||
<height>0</height> |
||||
</size> |
||||
</property> |
||||
<property name="text"> |
||||
<string>Real name</string> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
<item> |
||||
<widget class="QLineEdit" name="edRealName"/> |
||||
</item> |
||||
</layout> |
||||
</item> |
||||
<item> |
||||
<layout class="QHBoxLayout" name="horizontalLayout_2"> |
||||
<item> |
||||
<widget class="QLabel" name="label_2"> |
||||
<property name="minimumSize"> |
||||
<size> |
||||
<width>150</width> |
||||
<height>0</height> |
||||
</size> |
||||
</property> |
||||
<property name="text"> |
||||
<string>Username/email</string> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
<item> |
||||
<widget class="QLineEdit" name="edUsername"/> |
||||
</item> |
||||
</layout> |
||||
</item> |
||||
<item> |
||||
<layout class="QHBoxLayout" name="horizontalLayout_3"> |
||||
<item> |
||||
<widget class="QLabel" name="label_3"> |
||||
<property name="minimumSize"> |
||||
<size> |
||||
<width>150</width> |
||||
<height>0</height> |
||||
</size> |
||||
</property> |
||||
<property name="text"> |
||||
<string>Nickname</string> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
<item> |
||||
<widget class="QLineEdit" name="edNickname"/> |
||||
</item> |
||||
</layout> |
||||
</item> |
||||
<item> |
||||
<layout class="QHBoxLayout" name="horizontalLayout_4"> |
||||
<item> |
||||
<widget class="QLabel" name="label_4"> |
||||
<property name="minimumSize"> |
||||
<size> |
||||
<width>150</width> |
||||
<height>0</height> |
||||
</size> |
||||
</property> |
||||
<property name="text"> |
||||
<string>Alt.nickname</string> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
<item> |
||||
<widget class="QLineEdit" name="edAltNickname"/> |
||||
</item> |
||||
</layout> |
||||
</item> |
||||
<item> |
||||
<widget class="Line" name="line"> |
||||
<property name="orientation"> |
||||
<enum>Qt::Horizontal</enum> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
<item> |
||||
<layout class="QHBoxLayout" name="horizontalLayout_5"> |
||||
<item> |
||||
<widget class="QLabel" name="label_5"> |
||||
<property name="minimumSize"> |
||||
<size> |
||||
<width>150</width> |
||||
<height>0</height> |
||||
</size> |
||||
</property> |
||||
<property name="text"> |
||||
<string>Server address</string> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
<item> |
||||
<widget class="QLineEdit" name="edServer"> |
||||
<property name="readOnly"> |
||||
<bool>false</bool> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
</layout> |
||||
</item> |
||||
<item> |
||||
<layout class="QHBoxLayout" name="horizontalLayout_8"> |
||||
<item> |
||||
<widget class="QLabel" name="label_6"> |
||||
<property name="minimumSize"> |
||||
<size> |
||||
<width>150</width> |
||||
<height>0</height> |
||||
</size> |
||||
</property> |
||||
<property name="text"> |
||||
<string>Server password</string> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
<item> |
||||
<widget class="QLineEdit" name="edServerPassword"> |
||||
<property name="echoMode"> |
||||
<enum>QLineEdit::Password</enum> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
<item> |
||||
<widget class="QToolButton" name="btnShowPassword"> |
||||
<property name="text"> |
||||
<string>Show</string> |
||||
</property> |
||||
<property name="checkable"> |
||||
<bool>true</bool> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
</layout> |
||||
</item> |
||||
<item> |
||||
<widget class="QCheckBox" name="chkSSL"> |
||||
<property name="text"> |
||||
<string>Connect using SSL</string> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
<item> |
||||
<widget class="QTreeView" name="servers"> |
||||
<property name="editTriggers"> |
||||
<set>QAbstractItemView::NoEditTriggers</set> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
<item> |
||||
<layout class="QHBoxLayout" name="horizontalLayout_6"> |
||||
<item> |
||||
<widget class="QPushButton" name="btnEditServers"> |
||||
<property name="text"> |
||||
<string>Edit servers...</string> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
<item> |
||||
<spacer name="horizontalSpacer"> |
||||
<property name="orientation"> |
||||
<enum>Qt::Horizontal</enum> |
||||
</property> |
||||
<property name="sizeHint" stdset="0"> |
||||
<size> |
||||
<width>40</width> |
||||
<height>20</height> |
||||
</size> |
||||
</property> |
||||
</spacer> |
||||
</item> |
||||
</layout> |
||||
</item> |
||||
<item> |
||||
<widget class="Line" name="line_2"> |
||||
<property name="orientation"> |
||||
<enum>Qt::Horizontal</enum> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
<item> |
||||
<layout class="QHBoxLayout" name="horizontalLayout_7"> |
||||
<item> |
||||
<widget class="QCheckBox" name="chkNewStatus"> |
||||
<property name="text"> |
||||
<string>Connect in a new window</string> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
<item> |
||||
<spacer name="horizontalSpacer_2"> |
||||
<property name="orientation"> |
||||
<enum>Qt::Horizontal</enum> |
||||
</property> |
||||
<property name="sizeHint" stdset="0"> |
||||
<size> |
||||
<width>40</width> |
||||
<height>20</height> |
||||
</size> |
||||
</property> |
||||
</spacer> |
||||
</item> |
||||
</layout> |
||||
</item> |
||||
</layout> |
||||
</widget> |
||||
<tabstops> |
||||
<tabstop>edRealName</tabstop> |
||||
<tabstop>edUsername</tabstop> |
||||
<tabstop>edNickname</tabstop> |
||||
<tabstop>edAltNickname</tabstop> |
||||
<tabstop>edServer</tabstop> |
||||
<tabstop>edServerPassword</tabstop> |
||||
<tabstop>btnShowPassword</tabstop> |
||||
<tabstop>servers</tabstop> |
||||
<tabstop>btnEditServers</tabstop> |
||||
<tabstop>chkNewStatus</tabstop> |
||||
</tabstops> |
||||
<resources/> |
||||
<connections/> |
||||
</ui> |
@ -0,0 +1,325 @@ |
||||
/*
|
||||
* IdealIRC - Internet Relay Chat client |
||||
* Copyright (C) 2019 Tom-Andre Barstad |
||||
* |
||||
* This program is free software; you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation; either version 2 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License along |
||||
* with this program; if not, write to the Free Software Foundation, Inc., |
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
||||
*/ |
||||
|
||||
#include "ServerEditor.h" |
||||
#include "ui_ServerEditor.h" |
||||
#include <QDebug> |
||||
#include <QMessageBox> |
||||
#include <QInputDialog> |
||||
|
||||
ServerEditor::ServerEditor(ServerModel& model, QWidget* parent) |
||||
: QDialog(parent) |
||||
, ui(new Ui::ServerEditor) |
||||
, smodel(model) |
||||
{ |
||||
ui->setupUi(this); |
||||
addMenu = new QMenu(this); |
||||
addServerAction = addMenu->addAction(tr("Server"), this, &ServerEditor::on_addServerAction_triggered); |
||||
addNetworkAction = addMenu->addAction(tr("Network"), this, &ServerEditor::on_addNetworkAction_triggered); |
||||
ui->btnAdd->setMenu(addMenu); |
||||
ui->serverView->setModel(&smodel); |
||||
ui->serverView->header()->setSectionResizeMode(QHeaderView::Interactive); |
||||
|
||||
QStringList networks = smodel.networkList(); |
||||
ui->edNetwork->addItem(""); |
||||
for (const QString& network : networks) { |
||||
if (network == "NONE") |
||||
continue; |
||||
ui->edNetwork->addItem(network); |
||||
} |
||||
} |
||||
|
||||
ServerEditor::~ServerEditor() |
||||
{ |
||||
delete ui; |
||||
} |
||||
|
||||
void ServerEditor::on_addServerAction_triggered() |
||||
{ |
||||
QString network = "NONE"; |
||||
if (editMode == EditMode::Network) { |
||||
QModelIndex current = ui->serverView->currentIndex(); |
||||
auto spair = smodel.fromIndex(current); |
||||
network = spair.first; |
||||
} |
||||
else if (editMode == EditMode::Server) { |
||||
QModelIndex current = ui->serverView->currentIndex(); |
||||
if (current.parent().isValid()) { |
||||
auto spair = smodel.fromIndex(current.parent()); |
||||
network = spair.first; |
||||
} |
||||
} |
||||
|
||||
QString name; |
||||
QString host; |
||||
for (int i = 1 ;; ++i) { |
||||
name = QStringLiteral("New server %1").arg(i); |
||||
host = QStringLiteral("host%1.name:6667").arg(i); |
||||
QString det = smodel.details(name); |
||||
if (det.isEmpty()) |
||||
break; |
||||
} |
||||
QModelIndex idx = smodel.addServer(name, host, network); |
||||
selectItem(idx); |
||||
} |
||||
|
||||
void ServerEditor::on_addNetworkAction_triggered() |
||||
{ |
||||
QString name; |
||||
QString host; |
||||
for (int i = 1 ;; ++i) { |
||||
name = QStringLiteral("New network %1").arg(i); |
||||
host = QStringLiteral("irc%1.host.name:6667").arg(i); |
||||
QString det = smodel.details("DEFAULT", name); |
||||
if (det.isEmpty()) |
||||
break; |
||||
} |
||||
QModelIndex idx = smodel.addNetwork(name, host); |
||||
ui->edNetwork->addItem(name); |
||||
selectItem(idx); |
||||
} |
||||
|
||||
void ServerEditor::on_btnDel_clicked() |
||||
{ |
||||
if (editMode == EditMode::Off) |
||||
return; |
||||
|
||||
QModelIndex current = ui->serverView->currentIndex(); |
||||
auto spair = smodel.fromIndex(current); |
||||
QString network = spair.first; |
||||
QString name = spair.second; |
||||
|
||||
if (editMode == EditMode::Server) { |
||||
if (QMessageBox::question(this, tr("Delete server"), tr("Do you want to delete the server '%1'?").arg(name)) == QMessageBox::No) |
||||
return; |
||||
smodel.delServer(name, network); |
||||
} |
||||
|
||||
if (editMode == EditMode::Network) { |
||||
name = spair.first; |
||||
auto answer = QMessageBox::question(this, tr("Delete network"), tr("Do you want to delete the network '%1'?\nHitting 'Yes' will keep the servers as orphans.").arg(name), QMessageBox::Yes | QMessageBox::YesAll | QMessageBox::No); |
||||
if (answer == QMessageBox::No) |
||||
return; |
||||
bool keepServers = answer != QMessageBox::YesAll; |
||||
smodel.delNetwork(name, keepServers); |
||||
} |
||||
|
||||
ui->serverView->clearSelection(); |
||||
disableAll(); |
||||
} |
||||
|
||||
void ServerEditor::on_btnShowPassword_toggled(bool checked) |
||||
{ |
||||
ui->edPassword->setEchoMode(checked ? QLineEdit::Normal : QLineEdit::Password); |
||||
} |
||||
|
||||
void ServerEditor::on_btnSave_clicked() |
||||
{ |
||||
if (editMode == EditMode::Off) |
||||
return; |
||||
|
||||
if (ui->edName->text().isEmpty()) { |
||||
QMessageBox::information(this, tr("Field missing"), tr("No name is specified.")); |
||||
return; |
||||
} |
||||
|
||||
if (ui->edHostname->text().isEmpty()) { |
||||
QMessageBox::information(this, tr("Field missing"), tr("No hostname is specified.")); |
||||
return; |
||||
} |
||||
|
||||
if (editMode == EditMode::Server) |
||||
saveServerEdit(); |
||||
|
||||
else if (editMode == EditMode::Network) |
||||
saveNetworkEdit(); |
||||
} |
||||
|
||||
void ServerEditor::enableForServer() |
||||
{ |
||||
enableAll(); |
||||
ui->lbNetwork->show(); |
||||
ui->edNetwork->show(); |
||||
editMode = EditMode::Server; |
||||
} |
||||
|
||||
void ServerEditor::enableForNetwork() |
||||
{ |
||||
enableAll(); |
||||
ui->lbNetwork->hide(); |
||||
ui->edNetwork->hide(); |
||||
editMode = EditMode::Network; |
||||
} |
||||
|
||||
void ServerEditor::enableAll() |
||||
{ |
||||
ui->edName->setEnabled(true); |
||||
ui->edHostname->setEnabled(true); |
||||
ui->edPort->setEnabled(true); |
||||
ui->edPassword->setEnabled(true); |
||||
ui->btnShowPassword->setEnabled(true); |
||||
ui->btnShowPassword->setChecked(false); |
||||
ui->edNetwork->setEnabled(true); |
||||
ui->btnSave->setEnabled(true); |
||||
} |
||||
|
||||
void ServerEditor::disableAll() |
||||
{ |
||||
ui->edName->clear(); |
||||
ui->edHostname->clear(); |
||||
ui->edPort->setValue(6667); |
||||
ui->edPassword->clear(); |
||||
ui->btnShowPassword->setChecked(false); |
||||
ui->edNetwork->clearEditText(); |
||||
|
||||
ui->edName->setEnabled(false); |
||||
ui->edHostname->setEnabled(false); |
||||
ui->edPort->setEnabled(false); |
||||
ui->edPassword->setEnabled(false); |
||||
ui->btnShowPassword->setEnabled(false); |
||||
ui->edNetwork->setEnabled(false); |
||||
ui->btnSave->setEnabled(false); |
||||
ui->lbNetwork->show(); |
||||
ui->edNetwork->show(); |
||||
|
||||
editMode = EditMode::Off; |
||||
} |
||||
|
||||
void ServerEditor::saveNetworkEdit() |
||||
{ |
||||
qDebug() << "Save network"; |
||||
|
||||
QModelIndex current = ui->serverView->currentIndex(); |
||||
auto spair = smodel.fromIndex(current); |
||||
QString network = spair.first; |
||||
|
||||
/* Network name changed */ |
||||
if (network != ui->edName->text()) { |
||||
QString newNetwork = ui->edName->text(); |
||||
if (ui->edNetwork->findText(newNetwork) > -1) { |
||||
QMessageBox::information(this, tr("Duplicate network name"), tr("The specified network name already exist.")); |
||||
return; |
||||
} |
||||
smodel.renameNetwork(network, newNetwork); |
||||
int networkIdx = ui->edNetwork->findText(network); |
||||
ui->edNetwork->setItemText(networkIdx, newNetwork); |
||||
network = newNetwork; |
||||
} |
||||
|
||||
QString details = ui->edHostname->text() + ":" + QString::number(ui->edPort->value()); |
||||
QString password = ui->edPassword->text(); |
||||
if (!password.isEmpty()) |
||||
details += "|" + password; |
||||
smodel.setNetworkServer(network, details); |
||||
} |
||||
|
||||
void ServerEditor::saveServerEdit() |
||||
{ |
||||
qDebug() << "Save server"; |
||||
QModelIndex current = ui->serverView->currentIndex(); |
||||
auto spair = smodel.fromIndex(current); |
||||
|
||||
QString name = ui->edName->text(); |
||||
QString host = ui->edHostname->text(); |
||||
QString port = QString::number(ui->edPort->value()); |
||||
QString password = ui->edPassword->text(); |
||||
QString network = ui->edNetwork->currentText(); |
||||
if (ui->edNetwork->currentIndex() == 0) |
||||
network = "NONE"; |
||||
if (spair.first != network) { |
||||
/* New network */ |
||||
if (network != "NONE" && ui->edNetwork->findText(network) == -1) { |
||||
bool ok = false; |
||||
QString hostname = QInputDialog::getText(this, tr("New network"), tr("Set hostname:port for the new network %1").arg(network), QLineEdit::Normal, "", &ok); |
||||
if (!ok || hostname.isEmpty()) |
||||
return; |
||||
|
||||
smodel.addNetwork(network, hostname); |
||||
ui->edNetwork->addItem(network); |
||||
} |
||||
|
||||
QString details = smodel.details(spair.second, spair.first); |
||||
smodel.delServer(spair.second, spair.first); |
||||
QModelIndex idx = smodel.addServer(spair.second, details, network); |
||||
selectItem(idx); |
||||
} |
||||
|
||||
smodel.setServer(spair.second, host + ":" + port, password, network); |
||||
if (spair.second != name) { |
||||
QString details = smodel.details(spair.second, network); |
||||
smodel.delServer(spair.second, network); |
||||
smodel.resetModel(); |
||||
QModelIndex idx = smodel.addServer(name, details, network); |
||||
selectItem(idx); |
||||
} |
||||
} |
||||
|
||||
void ServerEditor::selectItem(const QModelIndex& index) |
||||
{ |
||||
ui->serverView->clearSelection(); |
||||
ui->serverView->selectionModel()->setCurrentIndex(index, QItemSelectionModel::Select | QItemSelectionModel::Rows); |
||||
on_serverView_clicked(index); |
||||
} |
||||
|
||||
void ServerEditor::on_serverView_clicked(const QModelIndex &index) |
||||
{ |
||||
if (!index.isValid()) { |
||||
disableAll(); |
||||
return; |
||||
} |
||||
|
||||
auto spair = smodel.fromIndex(index); |
||||
if (spair.first.isEmpty()) { |
||||
disableAll(); |
||||
return; |
||||
} |
||||
|
||||
QString name; |
||||
if (spair.second == "DEFAULT") { |
||||
name = spair.first; |
||||
enableForNetwork(); |
||||
} |
||||
else { |
||||
name = spair.second; |
||||
enableForServer(); |
||||
} |
||||
|
||||
QString details = smodel.details(spair.second, spair.first); |
||||
QString host, port, password; |
||||
// Value format: host:port|password
|
||||
if (details.contains('|')) { |
||||
password = details.split('|')[1]; |
||||
details = details.remove("|"+password); |
||||
} |
||||
if (details.contains(':')) { |
||||
port = details.split(':')[1]; |
||||
details = details.remove(":"+port); |
||||
} |
||||
host = details; |
||||
|
||||
|
||||
ui->edName->setText(name); |
||||
ui->edHostname->setText(host); |
||||
ui->edPort->setValue(port.toInt()); |
||||
ui->edPassword->setText(password); |
||||
int networkUiIndex = ui->edNetwork->findText(spair.first); |
||||
if (networkUiIndex == -1) |
||||
networkUiIndex = 0; |
||||
ui->edNetwork->setCurrentIndex(networkUiIndex); |
||||
} |
@ -0,0 +1,71 @@ |
||||
/*
|
||||
* IdealIRC - Internet Relay Chat client |
||||
* Copyright (C) 2019 Tom-Andre Barstad |
||||
* |
||||
* This program is free software; you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation; either version 2 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License along |
||||
* with this program; if not, write to the Free Software Foundation, Inc., |
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
||||
*/ |
||||
|
||||
#ifndef SERVEREDITOR_H |
||||
#define SERVEREDITOR_H |
||||
|
||||
#include "ServerModel.h" |
||||
#include <QDialog> |
||||
#include <QMenu> |
||||
|
||||
namespace Ui { |
||||
class ServerEditor; |
||||
} |
||||
|
||||
class ServerEditor : public QDialog |
||||
{ |
||||
Q_OBJECT |
||||
|
||||
public: |
||||
explicit ServerEditor(ServerModel& model, QWidget *parent = nullptr); |
||||
~ServerEditor(); |
||||
|
||||
private slots: |
||||
void on_addServerAction_triggered(); |
||||
void on_addNetworkAction_triggered(); |
||||
void on_btnDel_clicked(); |
||||
void on_btnShowPassword_toggled(bool checked); |
||||
void on_btnSave_clicked(); |
||||
void on_serverView_clicked(const QModelIndex &index); |
||||
|
||||
private: |
||||
enum class EditMode { |
||||
Off, |
||||
Server, |
||||
Network |
||||
} editMode = EditMode::Off; |
||||
|
||||
void enableForServer(); |
||||
void enableForNetwork(); |
||||
void enableAll(); |
||||
void disableAll(); |
||||
|
||||
void saveNetworkEdit(); |
||||
void saveServerEdit(); |
||||
|
||||
void selectItem(const QModelIndex& index); |
||||
|
||||
Ui::ServerEditor* ui; |
||||
QMenu* addMenu; |
||||
QAction* addServerAction; |
||||
QAction* addNetworkAction; |
||||
ServerModel& smodel; |
||||
}; |
||||
|
||||
#endif // SERVEREDITOR_H
|
@ -0,0 +1,251 @@ |
||||
<?xml version="1.0" encoding="UTF-8"?> |
||||
<ui version="4.0"> |
||||
<class>ServerEditor</class> |
||||
<widget class="QDialog" name="ServerEditor"> |
||||
<property name="windowModality"> |
||||
<enum>Qt::WindowModal</enum> |
||||
</property> |
||||
<property name="geometry"> |
||||
<rect> |
||||
<x>0</x> |
||||
<y>0</y> |
||||
<width>370</width> |
||||
<height>450</height> |
||||
</rect> |
||||
</property> |
||||
<property name="windowTitle"> |
||||
<string>Server editor</string> |
||||
</property> |
||||
<layout class="QGridLayout" name="gridLayout_2"> |
||||
<item row="0" column="0" rowspan="3" colspan="2"> |
||||
<widget class="QTreeView" name="serverView"> |
||||
<property name="editTriggers"> |
||||
<set>QAbstractItemView::NoEditTriggers</set> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
<item row="0" column="2"> |
||||
<widget class="QToolButton" name="btnAdd"> |
||||
<property name="text"> |
||||
<string>+</string> |
||||
</property> |
||||
<property name="popupMode"> |
||||
<enum>QToolButton::InstantPopup</enum> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
<item row="1" column="2"> |
||||
<widget class="QToolButton" name="btnDel"> |
||||
<property name="text"> |
||||
<string>-</string> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
<item row="2" column="2"> |
||||
<spacer name="verticalSpacer"> |
||||
<property name="orientation"> |
||||
<enum>Qt::Vertical</enum> |
||||
</property> |
||||
<property name="sizeHint" stdset="0"> |
||||
<size> |
||||
<width>20</width> |
||||
<height>178</height> |
||||
</size> |
||||
</property> |
||||
</spacer> |
||||
</item> |
||||
<item row="3" column="0" colspan="2"> |
||||
<widget class="QFrame" name="frame"> |
||||
<property name="frameShape"> |
||||
<enum>QFrame::StyledPanel</enum> |
||||
</property> |
||||
<property name="frameShadow"> |
||||
<enum>QFrame::Raised</enum> |
||||
</property> |
||||
<layout class="QGridLayout" name="gridLayout"> |
||||
<item row="0" column="0"> |
||||
<widget class="QLabel" name="label_4"> |
||||
<property name="text"> |
||||
<string>Name</string> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
<item row="1" column="0"> |
||||
<widget class="QLabel" name="label"> |
||||
<property name="text"> |
||||
<string>Hostname</string> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
<item row="2" column="0"> |
||||
<widget class="QLabel" name="label_2"> |
||||
<property name="text"> |
||||
<string>Port</string> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
<item row="3" column="0"> |
||||
<widget class="QLabel" name="label_3"> |
||||
<property name="text"> |
||||
<string>Password</string> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
<item row="3" column="1" colspan="2"> |
||||
<layout class="QHBoxLayout" name="horizontalLayout"> |
||||
<item> |
||||
<widget class="QLineEdit" name="edPassword"> |
||||
<property name="enabled"> |
||||
<bool>false</bool> |
||||
</property> |
||||
<property name="echoMode"> |
||||
<enum>QLineEdit::Password</enum> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
<item> |
||||
<widget class="QToolButton" name="btnShowPassword"> |
||||
<property name="enabled"> |
||||
<bool>false</bool> |
||||
</property> |
||||
<property name="text"> |
||||
<string>Show</string> |
||||
</property> |
||||
<property name="checkable"> |
||||
<bool>true</bool> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
</layout> |
||||
</item> |
||||
<item row="4" column="0"> |
||||
<widget class="QLabel" name="lbNetwork"> |
||||
<property name="text"> |
||||
<string>Network</string> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
<item row="5" column="1"> |
||||
<spacer name="horizontalSpacer"> |
||||
<property name="orientation"> |
||||
<enum>Qt::Horizontal</enum> |
||||
</property> |
||||
<property name="sizeHint" stdset="0"> |
||||
<size> |
||||
<width>40</width> |
||||
<height>20</height> |
||||
</size> |
||||
</property> |
||||
</spacer> |
||||
</item> |
||||
<item row="5" column="2"> |
||||
<widget class="QPushButton" name="btnSave"> |
||||
<property name="enabled"> |
||||
<bool>false</bool> |
||||
</property> |
||||
<property name="text"> |
||||
<string>Save</string> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
<item row="2" column="1" colspan="2"> |
||||
<widget class="QSpinBox" name="edPort"> |
||||
<property name="enabled"> |
||||
<bool>false</bool> |
||||
</property> |
||||
<property name="minimum"> |
||||
<number>1</number> |
||||
</property> |
||||
<property name="maximum"> |
||||
<number>65535</number> |
||||
</property> |
||||
<property name="value"> |
||||
<number>6667</number> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
<item row="1" column="1" colspan="2"> |
||||
<widget class="QLineEdit" name="edHostname"> |
||||
<property name="enabled"> |
||||
<bool>false</bool> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
<item row="0" column="1" colspan="2"> |
||||
<widget class="QLineEdit" name="edName"> |
||||
<property name="enabled"> |
||||
<bool>false</bool> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
<item row="4" column="1" colspan="2"> |
||||
<widget class="QComboBox" name="edNetwork"> |
||||
<property name="enabled"> |
||||
<bool>false</bool> |
||||
</property> |
||||
<property name="editable"> |
||||
<bool>true</bool> |
||||
</property> |
||||
<property name="insertPolicy"> |
||||
<enum>QComboBox::InsertAlphabetically</enum> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
</layout> |
||||
</widget> |
||||
</item> |
||||
<item row="4" column="0"> |
||||
<spacer name="horizontalSpacer_2"> |
||||
<property name="orientation"> |
||||
<enum>Qt::Horizontal</enum> |
||||
</property> |
||||
<property name="sizeHint" stdset="0"> |
||||
<size> |
||||
<width>230</width> |
||||
<height>20</height> |
||||
</size> |
||||
</property> |
||||
</spacer> |
||||
</item> |
||||
<item row="4" column="1"> |
||||
<widget class="QPushButton" name="btnClose"> |
||||
<property name="text"> |
||||
<string>Close</string> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
</layout> |
||||
</widget> |
||||
<tabstops> |
||||
<tabstop>serverView</tabstop> |
||||
<tabstop>btnAdd</tabstop> |
||||
<tabstop>btnDel</tabstop> |
||||
<tabstop>edName</tabstop> |
||||
<tabstop>edHostname</tabstop> |
||||
<tabstop>edPort</tabstop> |
||||
<tabstop>edPassword</tabstop> |
||||
<tabstop>edNetwork</tabstop> |
||||
<tabstop>btnSave</tabstop> |
||||
<tabstop>btnClose</tabstop> |
||||
<tabstop>btnShowPassword</tabstop> |
||||
</tabstops> |
||||
<resources/> |
||||
<connections> |
||||
<connection> |
||||
<sender>btnClose</sender> |
||||
<signal>clicked()</signal> |
||||
<receiver>ServerEditor</receiver> |
||||
<slot>close()</slot> |
||||
<hints> |
||||
<hint type="sourcelabel"> |
||||
<x>272</x> |
||||
<y>327</y> |
||||
</hint> |
||||
<hint type="destinationlabel"> |
||||
<x>358</x> |
||||
<y>288</y> |
||||
</hint> |
||||
</hints> |
||||
</connection> |
||||
</connections> |
||||
</ui> |
@ -0,0 +1,145 @@ |
||||
/*
|
||||
* IdealIRC - Internet Relay Chat client |
||||
* Copyright (C) 2014 Tom-Andre Barstad |
||||
* |
||||
* This program is free software; you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation; either version 2 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License along |
||||
* with this program; if not, write to the Free Software Foundation, Inc., |
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
||||
* |
||||
*/ |
||||
|
||||
#include "ServerMgr.h" |
||||
#include "config.h" |
||||
|
||||
ServerMgr::ServerMgr(QObject *parent) : |
||||
QObject(parent), |
||||
ini(LOCAL_PATH+"/servers.ini") |
||||
{ |
||||
} |
||||
|
||||
QStringList ServerMgr::networkList() |
||||
{ |
||||
int count = ini.countSections(); |
||||
QStringList r; |
||||
|
||||
for (int i = 0; i < count; i++) |
||||
r.push_back( ini.section(i) ); |
||||
|
||||
return r; |
||||
} |
||||
|
||||
QHash<QString,QString> ServerMgr::serverList(QString network) |
||||
{ |
||||
int count = ini.countItems(network); |
||||
QHash<QString,QString> r; |
||||
|
||||
for (int i = 0; i < count; i++) { |
||||
QString servername = ini.key(network, i); |
||||
QString serverdetails = ini.value(network, i); |
||||
|
||||
// Insert multi in case someone adds a server with same name in the list (f.ex. via editing servers.ini)
|
||||
r.insertMulti(servername, serverdetails); |
||||
} |
||||
|
||||
return r; |
||||
} |
||||
|
||||
QString ServerMgr::defaultServer(QString network) |
||||
{ |
||||
return ini.value(network, "DEFAULT"); |
||||
} |
||||
|
||||
bool ServerMgr::addNetwork(QString name) |
||||
{ |
||||
if (name == "NONE") |
||||
return false; |
||||
|
||||
if (! ini.appendSection(name)) |
||||
return false; |
||||
|
||||
return ini.write(name, "DEFAULT", "server.name"); |
||||
} |
||||
|
||||
bool ServerMgr::renameNetwork(QString o_name, QString n_name) |
||||
{ |
||||
if ((o_name == "NONE") || (n_name == "NONE")) |
||||
return false; |
||||
return ini.renameSection(o_name, n_name); |
||||
} |
||||
|
||||
bool ServerMgr::delNetwork(QString name, bool keep_servers) |
||||
{ |
||||
// If servers=true, we will keep the servers by moving them to the NONE section.
|
||||
// Any servers which got a name existing in the NONE, will be renamed to oldnetname_servername.
|
||||
|
||||
if (! ini.sectionExists(name)) |
||||
return false; |
||||
|
||||
if (keep_servers == true) { |
||||
int max = ini.countItems(name); |
||||
for (int i = 0; i < max; i++) { |
||||
QString item = ini.key(name, i); |
||||
QString value = ini.value(name, i); |
||||
|
||||
QString e_item = ini.value("NONE", item); |
||||
if (e_item.length() > 0) // item exists in NONE
|
||||
item.prepend(name+"_"); |
||||
|
||||
ini.write("NONE", item, value); |
||||
} |
||||
} |
||||
|
||||
ini.delSection(name); |
||||
|
||||
return true; |
||||
} |
||||
|
||||
bool ServerMgr::addServer(QString name, QString host, QString pw, QString network) |
||||
{ |
||||
if (pw.length() > 0) |
||||
pw.prepend('|'); |
||||
|
||||
QString detail = QString("%1%2") |
||||
.arg(host) |
||||
.arg(pw); |
||||
|
||||
if ((! ini.sectionExists(network)) && (network != "NONE")) |
||||
return false; |
||||
|
||||
ini.write(network, name, detail); |
||||
return true; |
||||
} |
||||
|
||||
bool ServerMgr::delServer(QString name, QString network) |
||||
{ |
||||
return ini.delItem(network, name); |
||||
} |
||||
|
||||
bool ServerMgr::hasNetwork(QString name) |
||||
{ |
||||
return ini.sectionExists(name); |
||||
} |
||||
|
||||
bool ServerMgr::hasServer(QString name, QString network) |
||||
{ |
||||
QString data = ini.value(network, name); |
||||
if (data.length() > 0) |
||||
return true; |
||||
else |
||||
return false; |
||||
} |
||||
|
||||
QString ServerMgr::getServerDetails(QString name, QString network) |
||||
{ |
||||
return ini.value(network, name); |
||||
} |
@ -0,0 +1,68 @@ |
||||
/*
|
||||
* IdealIRC - Internet Relay Chat client |
||||
* Copyright (C) 2014 Tom-Andre Barstad |
||||
* |
||||
* This program is free software; you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation; either version 2 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License along |
||||
* with this program; if not, write to the Free Software Foundation, Inc., |
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
||||
* |
||||
*/ |
||||
|
||||
#ifndef SERVERMGR_H |
||||
#define SERVERMGR_H |
||||
|
||||
#include <QObject> |
||||
#include <QStringList> |
||||
#include <QHash> |
||||
|
||||
#include "IniFile.h" |
||||
|
||||
/**
|
||||
* @brief Manages servers.ini |
||||
* @details |
||||
* This class is from IdealIRC 0.x series and is going to be removed/replaced at any time. |
||||
*/ |
||||
class ServerMgr : public QObject |
||||
{ |
||||
Q_OBJECT |
||||
|
||||
public: |
||||
explicit ServerMgr(QObject *parent = nullptr); |
||||
// All networks in a string list (Also counts in the NONE network)
|
||||
QStringList networkList(); |
||||
// All servers from a network in a hash map <"name","server:port|passwd">
|
||||
QHash<QString,QString> serverList(QString network = "NONE"); |
||||
// Return default server of given network (The "NONE" network have no default!) - returns empty if no default server is set.
|
||||
QString defaultServer(QString network); |
||||
// Add new network to servers.ini - returns false if network exist
|
||||
bool addNetwork(QString name); |
||||
// Rename a network - returns false if new network name already exist
|
||||
bool renameNetwork(QString o_name, QString n_name); |
||||
// Delete network - false if network didn't exsist (useless result?)
|
||||
bool delNetwork(QString name, bool keep_servers = false); |
||||
// Add (or update) a server to network - returns false if network doesn't exsist
|
||||
bool addServer(QString name, QString host /*host:port*/, QString pw = "", QString network = "NONE"); |
||||
// Delete a server from network - false if network or server didn't exist
|
||||
bool delServer(QString name, QString network = "NONE"); |
||||
// Check of we have the given network name
|
||||
bool hasNetwork(QString name); |
||||
// Check if we have the given server name inside the network
|
||||
bool hasServer(QString name, QString network = "NONE"); |
||||
// Get server details
|
||||
QString getServerDetails(QString name, QString network = "NONE"); |
||||
|
||||
private: |
||||
IniFile ini;
|
||||
}; |
||||
|
||||
#endif // SERVERMGR_H
|
@ -0,0 +1,371 @@ |
||||
/*
|
||||
* IdealIRC - Internet Relay Chat client |
||||
* Copyright (C) 2014 Tom-Andre Barstad |
||||
* |
||||
* This program is free software; you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation; either version 2 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License along |
||||
* with this program; if not, write to the Free Software Foundation, Inc., |
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
||||
* |
||||
*/ |
||||
|
||||
#include "ServerModel.h" |
||||
#include <QHashIterator> |
||||
#include <QDebug> |
||||
|
||||
ServerModel::ServerModel(QObject *parent) : |
||||
QStandardItemModel(parent) |
||||
{ |
||||
resetModel(); |
||||
} |
||||
|
||||
QModelIndex ServerModel::indexFromHost(QString hostname) |
||||
{ |
||||
return hostmap.value(hostname, QModelIndex()); |
||||
} |
||||
|
||||
QPair<QString, QString> ServerModel::fromIndex(const QModelIndex& index) |
||||
{ |
||||
/* Locate netmap */ |
||||
{ |
||||
QHashIterator<QString,QModelIndex> it(netmap); |
||||
while (it.hasNext()) { |
||||
const QString& network = it.next().key(); |
||||
const QModelIndex& iidx = it.value(); |
||||
if (index == iidx || index == iidx.siblingAtColumn(1)) |
||||
return { network, "DEFAULT" }; |
||||
|
||||
for (int i = 0 ;; ++i) { |
||||
QModelIndex cidx = iidx.child(i, 0); |
||||
if (!cidx.isValid()) |
||||
break; |
||||
QString name = cidx.data().toString(); |
||||
if (index == cidx || index == cidx.siblingAtColumn(1)) |
||||
return { network, name }; |
||||
} |
||||
} |
||||
} |
||||
|
||||
/* Locate nonemap */ |
||||
{ |
||||
QHashIterator<QString,QModelIndex> it(hostmap); |
||||
while (it.hasNext()) { |
||||
it.next(); |
||||
const QModelIndex& iidx = it.value(); |
||||
if (index == iidx || index == iidx.siblingAtColumn(1)) |
||||
return { "NONE", iidx.data().toString() }; |
||||
} |
||||
} |
||||
|
||||
return {}; |
||||
} |
||||
|
||||
QModelIndex ServerModel::addNetwork(QString name, QString server) |
||||
{ |
||||
QStandardItem *root = invisibleRootItem(); |
||||
|
||||
QStandardItem *pname = new QStandardItem(QIcon(":/options/gfx/network.png"), name); |
||||
QStandardItem *phost = new QStandardItem(server); |
||||
QList<QStandardItem*> list; |
||||
list << pname << phost; |
||||
|
||||
root->appendRow(list); |
||||
hostmap.insert(server, pname->index()); |
||||
netmap.insert(name, pname->index()); |
||||
|
||||
if (smgr.addNetwork(name)) |
||||
smgr.addServer("DEFAULT", server, "", name); |
||||
|
||||
return pname->index(); |
||||
} |
||||
|
||||
void ServerModel::setNetworkServer(QString name, QString server) |
||||
{ |
||||
QModelIndex current = netmap.value(name); |
||||
int row = current.row(); |
||||
QModelIndex serverIndex = index(row, 1, current.parent()); |
||||
|
||||
QStandardItem *item = itemFromIndex(serverIndex); |
||||
item->setText(server); |
||||
smgr.addServer("DEFAULT", server, "", name); |
||||
} |
||||
|
||||
void ServerModel::renameNetwork(QString name, QString newname) |
||||
{ |
||||
QModelIndex current = netmap.value(name); |
||||
int row = current.row(); |
||||
QModelIndex nameIndex = index(row, 0, current.parent()); |
||||
|
||||
QStandardItem *item = itemFromIndex(nameIndex); |
||||
item->setText(newname); |
||||
|
||||
netmap.remove(name); |
||||
netmap.insert(newname, current); |
||||
smgr.renameNetwork(name, newname); |
||||
} |
||||
|
||||
void ServerModel::delNetwork(QString name, bool keepServers) |
||||
{ |
||||
smgr.delNetwork(name, keepServers); |
||||
resetModel(); |
||||
} |
||||
|
||||
QModelIndex ServerModel::addServer(QString name, QString server, QString network) |
||||
{ |
||||
QStandardItem *parent; |
||||
|
||||
if (network.length() == 0) |
||||
network = "NONE"; |
||||
|
||||
if (network == "NONE") |
||||
parent = invisibleRootItem(); |
||||
else |
||||
parent = itemFromIndex( netmap.value(network) ); |
||||
|
||||
QStandardItem *sname = new QStandardItem(QIcon(":/options/gfx/server.png"), name); |
||||
QStandardItem *shost = new QStandardItem(server); |
||||
QList<QStandardItem*> list; |
||||
list << sname << shost; |
||||
|
||||
parent->appendRow(list); |
||||
hostmap.insert(server, indexFromItem(sname)); |
||||
if (network == "NONE") |
||||
nonemap.insert(name, indexFromItem(sname)); |
||||
|
||||
smgr.addServer(name, server, "", network); |
||||
|
||||
return indexFromItem(sname); |
||||
} |
||||
|
||||
void ServerModel::setServer(QString name, QString server, QString password, QString network) |
||||
{ |
||||
QStandardItem *parent; |
||||
|
||||
if (network.length() == 0) |
||||
network = "NONE"; |
||||
|
||||
if (network == "NONE") |
||||
parent = invisibleRootItem(); |
||||
else |
||||
parent = itemFromIndex( netmap.value(network) ); |
||||
|
||||
QModelIndex parentIdx = indexFromItem(parent); // Parent index
|
||||
QModelIndex current; // Item's index
|
||||
|
||||
if (network != "NONE") { |
||||
|
||||
for (int r = 0 ;; r++) { |
||||
QModelIndex idx = parentIdx.child(r, 0); |
||||
|
||||
if (! idx.isValid()) |
||||
return; // No relevant child found, stop.
|
||||
|
||||
if (idx.data().toString() == name) { |
||||
current = idx; |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
else { |
||||
current = nonemap.value(name); |
||||
} |
||||
|
||||
|
||||
int row = current.row(); |
||||
QModelIndex nameIndex = index(row, 0, current.parent()); |
||||
QModelIndex serverIndex = index(row, 1, current.parent()); |
||||
QStandardItem *serverItem = itemFromIndex(serverIndex); |
||||
serverItem->setText(server); |
||||
|
||||
hostmap.insert(server, nameIndex); |
||||
smgr.addServer(name, server, password, network); |
||||
} |
||||
|
||||
void ServerModel::renameServer(QString name, QString newname, QString network) |
||||
{ |
||||
QStandardItem *parent; |
||||
|
||||
if (network.length() == 0) |
||||
network = "NONE"; |
||||
|
||||
if (network == "NONE") |
||||
parent = invisibleRootItem(); |
||||
else |
||||
parent = itemFromIndex( netmap.value(network) ); |
||||
|
||||
QModelIndex parentIdx = indexFromItem(parent); // Parent index
|
||||
QModelIndex current; // Item's index
|
||||
|
||||
if (network != "NONE") { |
||||
|
||||
for (int r = 0 ;; r++) { |
||||
QModelIndex idx = parentIdx.child(r, 0); |
||||
|
||||
if (! idx.isValid()) |
||||
return; // No relevant child found, stop.
|
||||
|
||||
if (idx.data().toString() == name) { |
||||
current = idx; |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
else { |
||||
current = nonemap.value(name); |
||||
nonemap.remove(name); |
||||
nonemap.insert(newname, current); |
||||
} |
||||
|
||||
int row = current.row(); |
||||
QModelIndex serverIndex = index(row, 0, current.parent()); |
||||
QStandardItem *item = itemFromIndex(serverIndex); |
||||
item->setText(newname); |
||||
|
||||
QString details = smgr.getServerDetails(name, network); |
||||
smgr.delServer(name, network); |
||||
smgr.addServer(name, details, "", network); |
||||
} |
||||
|
||||
void ServerModel::delServer(QString name, QString network) |
||||
{ |
||||
QStandardItem *parent; |
||||
|
||||
if (network.length() == 0) |
||||
network = "NONE"; |
||||
|
||||
if (network == "NONE") |
||||
parent = invisibleRootItem(); |
||||
else |
||||
parent = itemFromIndex( netmap.value(network) ); |
||||
|
||||
QModelIndex parentIdx = indexFromItem(parent); // Parent index
|
||||
QModelIndex current; // Item's index
|
||||
|
||||
if (network != "NONE") { |
||||
|
||||
for (int r = 0 ;; r++) { |
||||
QModelIndex idx = parentIdx.child(r, 0); |
||||
|
||||
if (! idx.isValid()) |
||||
return; // No relevant child found, stop.
|
||||
|
||||
if (idx.data().toString() == name) { |
||||
current = idx; |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
else { |
||||
current = nonemap.value(name); |
||||
nonemap.remove(name); |
||||
} |
||||
|
||||
|
||||
int row = current.row(); |
||||
removeRow(row, current.parent()); |
||||
smgr.delServer(name, network); |
||||
} |
||||
|
||||
void ServerModel::resetModel() |
||||
{ |
||||
clear(); |
||||
|
||||
QStandardItem *root = invisibleRootItem(); |
||||
|
||||
QStandardItem *i = new QStandardItem(); |
||||
QStringList l; |
||||
l << tr("Name") << tr("Host"); |
||||
setColumnCount(2); |
||||
setHorizontalHeaderItem(0, i); |
||||
setHorizontalHeaderLabels(l); |
||||
|
||||
hostmap.clear(); |
||||
netmap.clear(); |
||||
nonemap.clear(); |
||||
|
||||
QStringList netlist = smgr.networkList(); |
||||
|
||||
if (netlist.contains("NONE")) { // "None" network is a section with servers not assigned to a network.
|
||||
QHash<QString,QString> sl = smgr.serverList("NONE"); |
||||
QHashIterator<QString,QString> i(sl); |
||||
while (i.hasNext()) { |
||||
i.next(); |
||||
// Key: Server name
|
||||
// Value: host:port|pass
|
||||
QString name = i.key(); |
||||
QString detail = i.value(); |
||||
|
||||
QString host; // hostname with port, e.g. irc.network.org:6667
|
||||
host = detail.split('|')[0]; |
||||
|
||||
QStandardItem *itemname = new QStandardItem(QIcon(":/options/gfx/server.png"), name); |
||||
QStandardItem *itemhost = new QStandardItem(host); |
||||
QList<QStandardItem*> list; |
||||
list << itemname << itemhost; |
||||
|
||||
root->appendRow(list); |
||||
hostmap.insert(host, indexFromItem(itemname)); |
||||
nonemap.insert(name, indexFromItem(itemname)); |
||||
|
||||
} |
||||
} |
||||
|
||||
for (int i = 0; i <= netlist.count()-1; ++i) { |
||||
|
||||
if (netlist[i] == "NONE") |
||||
continue; // The "None" network already taken care of - ignore.
|
||||
|
||||
QString data = smgr.defaultServer(netlist[i]); |
||||
QString host = data.split('|')[0]; |
||||
|
||||
QStandardItem *pname = new QStandardItem(QIcon(":/options/gfx/network.png"), netlist[i]); // parent name
|
||||
QStandardItem *phost = new QStandardItem(host); // parent host
|
||||
QList<QStandardItem*> list; |
||||
list << pname << phost; |
||||
|
||||
root->appendRow(list); |
||||
hostmap.insert(host, pname->index()); |
||||
netmap.insert(netlist[i], pname->index()); |
||||
|
||||
QHash<QString,QString> sl = smgr.serverList(netlist[i]); |
||||
QHashIterator<QString,QString> sli(sl); |
||||
while (sli.hasNext()) { |
||||
sli.next(); |
||||
// Key: Server name
|
||||
// Value: host:port|pass
|
||||
QString name = sli.key(); |
||||
if (name == "DEFAULT") |
||||
continue; // The default value already taken care of, it's the address of parent item.
|
||||
QString detail = sli.value(); |
||||
QString host; // hostname with port, e.g. irc.network.org:6667
|
||||
host = detail.split('|')[0]; |
||||
|
||||
QStandardItem *itemname = new QStandardItem(QIcon(":/options/gfx/server.png"), name); // parent name
|
||||
QStandardItem *itemhost = new QStandardItem(host); // parent host
|
||||
QList<QStandardItem*> list; |
||||
list << itemname << itemhost; |
||||
|
||||
pname->appendRow(list); |
||||
hostmap.insert(host, indexFromItem(itemname)); |
||||
} |
||||
} |
||||
} |
||||
|
||||
QStringList ServerModel::networkList() |
||||
{ |
||||
return smgr.networkList(); |
||||
} |
||||
|
||||
QString ServerModel::details(QString name, QString network) |
||||
{ |
||||
return smgr.getServerDetails(name, network); |
||||
} |
@ -0,0 +1,63 @@ |
||||
/*
|
||||
* IdealIRC - Internet Relay Chat client |
||||
* Copyright (C) 2014 Tom-Andre Barstad |
||||
* |
||||
* This program is free software; you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation; either version 2 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License along |
||||
* with this program; if not, write to the Free Software Foundation, Inc., |
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
||||
* |
||||
*/ |
||||
|
||||
#ifndef SERVERMODEL_H |
||||
#define SERVERMODEL_H |
||||
|
||||
#include <QStandardItemModel> |
||||
#include "ServerMgr.h" |
||||
#include <QHash> |
||||
#include <QPair> |
||||
|
||||
/**
|
||||
* @brief Model of servers.ini |
||||
* @details |
||||
* This class is from IdealIRC 0.x series and is going to be removed/replaced at any time. |
||||
*/ |
||||
class ServerModel : public QStandardItemModel |
||||
{ |
||||
Q_OBJECT |
||||
|
||||
public: |
||||
explicit ServerModel(QObject *parent = nullptr); |
||||
QModelIndex indexFromHost(QString hostname); // Hostname:Port
|
||||
QPair<QString,QString> fromIndex(const QModelIndex& index); |
||||
|
||||
QModelIndex addNetwork(QString name, QString server); |
||||
void setNetworkServer(QString name, QString server = ""); |
||||
void renameNetwork(QString name, QString newname); |
||||
void delNetwork(QString name, bool keepServers); |
||||
QModelIndex addServer(QString name, QString server, QString network = "NONE"); |
||||
void setServer(QString name, QString server, QString password, QString network = "NONE"); |
||||
void renameServer(QString name, QString newname, QString network = "NONE"); |
||||
void delServer(QString name, QString network = "NONE"); |
||||
void resetModel(); |
||||
QStringList networkList(); |
||||
QString details(QString name, QString network = "NONE"); |
||||
|
||||
private: |
||||
ServerMgr smgr; |
||||
|
||||
QHash<QString,QModelIndex> hostmap; // host:port to index
|
||||
QHash<QString,QModelIndex> netmap; // network to index
|
||||
QHash<QString,QModelIndex> nonemap; // All names (servers) in NONE to index
|
||||
}; |
||||
|
||||
#endif // SERVERMODEL_H
|
@ -0,0 +1,137 @@ |
||||
/*
|
||||
* IdealIRC - Internet Relay Chat client |
||||
* Copyright (C) 2019 Tom-Andre Barstad |
||||
* |
||||
* This program is free software; you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation; either version 2 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License along |
||||
* with this program; if not, write to the Free Software Foundation, Inc., |
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
||||
*/ |
||||
|
||||
#include "ServerItem.h" |
||||
#include <QDebug> |
||||
|
||||
ServerItem::ServerItem() |
||||
: m_type(Type::Root) |
||||
{} |
||||
|
||||
ServerItem::ServerItem(ServerItem* parentItem, const QString& serverName, const QString& host, const quint16 port, const QString& password) |
||||
: m_type(Type::Server) |
||||
, m_serverName(serverName) |
||||
, m_host(host) |
||||
, m_port(port) |
||||
, m_password(password) |
||||
, m_parent(parentItem) |
||||
{} |
||||
|
||||
ServerItem::ServerItem(const QString& networkName, const QString& host, const quint16 port, const QString& password) |
||||
: m_type(Type::Network) |
||||
, m_networkName(networkName) |
||||
, m_host(host) |
||||
, m_port(port) |
||||
, m_password(password) |
||||
{} |
||||
|
||||
ServerItem::~ServerItem() |
||||
{ |
||||
qDeleteAll(m_children); |
||||
} |
||||
|
||||
ServerItem::Type ServerItem::getType() const |
||||
{ |
||||
return m_type; |
||||
} |
||||
|
||||
const QString& ServerItem::getNetworkName() const |
||||
{ |
||||
return m_networkName; |
||||
} |
||||
|
||||
const QString& ServerItem::getServerName() const |
||||
{ |
||||
return m_serverName; |
||||
} |
||||
|
||||
const QString& ServerItem::getHost() const |
||||
{ |
||||
return m_host; |
||||
} |
||||
|
||||
quint16 ServerItem::getPort() const |
||||
{ |
||||
return m_port; |
||||
} |
||||
|
||||
const QString& ServerItem::getPassword() const |
||||
{ |
||||
return m_password; |
||||
} |
||||
|
||||
ServerItem*ServerItem::getParentItem() const |
||||
{ |
||||
return m_parent; |
||||
} |
||||
|
||||
bool ServerItem::isOrphan() const |
||||
{ |
||||
return m_parent == nullptr; |
||||
} |
||||
|
||||
bool ServerItem::addChild(ServerItem* child) |
||||
{ |
||||
if (m_type == Type::Server) { |
||||
qWarning() << "Tried to add a child item on a server entry!"; |
||||
return false; |
||||
} |
||||
m_children.push_back(child); |
||||
return true; |
||||
} |
||||
|
||||
bool ServerItem::delChild(ServerItem* child) |
||||
{ |
||||
if (m_type == Type::Server) { |
||||
qWarning() << "Tried to delete a child item on a server entry!"; |
||||
return false; |
||||
} |
||||
m_children.removeOne(child); |
||||
return true; |
||||
} |
||||
|
||||
ServerItem* ServerItem::getChild(int row) const |
||||
{ |
||||
if (m_type == Type::Server) { |
||||
qWarning() << "Tried to get a child item on a server entry!"; |
||||
return nullptr; |
||||
} |
||||
|
||||
if (row >= m_children.count()) { |
||||
qWarning() << "Tried to get child from row" << row << "- but it's out of index. Network:" << m_networkName; |
||||
return nullptr; |
||||
} |
||||
|
||||
return m_children[row]; |
||||
} |
||||
|
||||
int ServerItem::childCount() const |
||||
{ |
||||
return m_children.count(); |
||||
} |
||||
|
||||
int ServerItem::childRow(ServerItem* child) const |
||||
{ |
||||
return m_children.indexOf(child); |
||||
} |
||||
|
||||
ServerItem*ServerItem::getParent() const |
||||
{ |
||||
return m_parent; |
||||
} |
@ -0,0 +1,79 @@ |
||||
/*
|
||||
* IdealIRC - Internet Relay Chat client |
||||
* Copyright (C) 2019 Tom-Andre Barstad |
||||
* |
||||
* This program is free software; you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation; either version 2 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License along |
||||
* with this program; if not, write to the Free Software Foundation, Inc., |
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
||||
*/ |
||||
|
||||
#ifndef SERVERITEM_H |
||||
#define SERVERITEM_H |
||||
|
||||
#include <QString> |
||||
#include <QList> |
||||
|
||||
class ServerItem |
||||
{ |
||||
public: |
||||
enum class Type { |
||||
Root, |
||||
Server, |
||||
Network |
||||
}; |
||||
|
||||
/**
|
||||
* @brief Construct a root item. Should only be constructed once as the "invisible" root item in the tree. |
||||
*/ |
||||
explicit ServerItem(); |
||||
|
||||
/**
|
||||
* @brief Construct a server item. Set parentItem=nullptr for an orphaned server. |
||||
*/ |
||||
ServerItem(ServerItem* parentItem, const QString& serverName, const QString& host, const quint16 port, const QString& password); |
||||
|
||||
/**
|
||||
* @brief Construct a network item. |
||||
*/ |
||||
ServerItem(const QString& networkName, const QString& host, const quint16 port, const QString& password); |
||||
|
||||
~ServerItem(); |
||||
|
||||
Type getType() const; |
||||
const QString& getNetworkName() const; |
||||
const QString& getServerName() const; |
||||
const QString& getHost() const; |
||||
quint16 getPort() const; |
||||
const QString& getPassword() const; |
||||
ServerItem* getParentItem() const; |
||||
bool isOrphan() const; |
||||
|
||||
bool addChild(ServerItem* child); |
||||
bool delChild(ServerItem* child); |
||||
ServerItem* getChild(int row) const; |
||||
int childCount() const; |
||||
int childRow(ServerItem* child) const; |
||||
ServerItem* getParent() const; |
||||
|
||||
private: |
||||
Type m_type; |
||||
QString m_networkName; |
||||
QString m_serverName; |
||||
QString m_host; |
||||
quint16 m_port; |
||||
QString m_password; |
||||
ServerItem* m_parent{ nullptr }; |
||||
QList<ServerItem*> m_children; |
||||
}; |
||||
|
||||
#endif // SERVERITEM_H
|
@ -0,0 +1,184 @@ |
||||
/*
|
||||
* IdealIRC - Internet Relay Chat client |
||||
* Copyright (C) 2019 Tom-Andre Barstad |
||||
* |
||||
* This program is free software; you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation; either version 2 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License along |
||||
* with this program; if not, write to the Free Software Foundation, Inc., |
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
||||
*/ |
||||
|
||||
#include "ServerModel.h" |
||||
#include "config.h" |
||||
#include <QDebug> |
||||
|
||||
ServerModel::ServerModel(QObject *parent) |
||||
: QAbstractItemModel(parent) |
||||
{ |
||||
IniFile ini(LOCAL_PATH+"/servers.ini"); |
||||
buildToplevelItem(ini, "NONE"); // Orphaned servers. Always build first.
|
||||
const int sectionCount = ini.countSections(); |
||||
for (int i = 0; i < sectionCount; ++i) { |
||||
QString section = ini.section(i); |
||||
if (section == "NONE") |
||||
continue; |
||||
|
||||
buildToplevelItem(ini, section); |
||||
} |
||||
|
||||
qDebug() << "Built server tree model."; |
||||
} |
||||
|
||||
ServerModel::~ServerModel() |
||||
{ |
||||
qDeleteAll(toplevelItems); |
||||
} |
||||
|
||||
ServerModel::ItemIniValue::ItemIniValue(QString value) |
||||
{ |
||||
if (value.isEmpty()) |
||||
return; |
||||
|
||||
// Value format: host:port|password
|
||||
if (value.contains('|')) { |
||||
password = value.split('|')[1]; |
||||
value = value.remove("|"+password); |
||||
} |
||||
if (value.contains(':')) { |
||||
port = value.split(':')[1]; |
||||
value = value.remove(":"+port); |
||||
} |
||||
host = value; |
||||
} |
||||
|
||||
QModelIndex ServerModel::index(int row, int column, const QModelIndex &parent) const |
||||
{ |
||||
// TEST
|
||||
//if (!hasIndex(row, column, parent))
|
||||
// return QModelIndex();
|
||||
|
||||
ServerItem* item{ nullptr }; |
||||
if (!parent.isValid()) { |
||||
if (row < toplevelItems.count()) |
||||
item = toplevelItems[row]; |
||||
} |
||||
else |
||||
item = static_cast<ServerItem*>(parent.internalPointer()); |
||||
|
||||
if (item) |
||||
return createIndex(row, column, item); |
||||
else |
||||
return QModelIndex(); |
||||
} |
||||
|
||||
QModelIndex ServerModel::parent(const QModelIndex &index) const |
||||
{ |
||||
// FIXME: Implement me!
|
||||
if (!index.isValid()) |
||||
return QModelIndex(); |
||||
|
||||
ServerItem* childItem = static_cast<ServerItem*>(index.internalPointer()); |
||||
if (!childItem->isOrphan()) { |
||||
ServerItem* parentItem = childItem->getParentItem(); |
||||
return createIndex(parentItem->childRow(childItem), 0, parentItem); |
||||
} |
||||
else { |
||||
return QModelIndex(); |
||||
} |
||||
} |
||||
|
||||
int ServerModel::rowCount(const QModelIndex &parent) const |
||||
{ |
||||
if (!parent.isValid()) |
||||
return toplevelItems.count(); |
||||
|
||||
ServerItem* item = static_cast<ServerItem*>(parent.internalPointer()); |
||||
if (!item) |
||||
return 0; |
||||
else |
||||
return item->childCount(); |
||||
} |
||||
|
||||
int ServerModel::columnCount(const QModelIndex&) const |
||||
{ |
||||
return 1; |
||||
} |
||||
|
||||
QVariant ServerModel::data(const QModelIndex &index, int role) const |
||||
{ |
||||
if (!index.isValid()) |
||||
return QVariant(); |
||||
|
||||
// FIXME: Implement me!
|
||||
return "neger"; |
||||
} |
||||
|
||||
bool ServerModel::setData(const QModelIndex &index, const QVariant &value, int role) |
||||
{ |
||||
if (data(index, role) != value) { |
||||
// FIXME: Implement me!
|
||||
emit dataChanged(index, index, QVector<int>() << role); |
||||
return true; |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
Qt::ItemFlags ServerModel::flags(const QModelIndex &index) const |
||||
{ |
||||
if (!index.isValid()) |
||||
return Qt::NoItemFlags; |
||||
|
||||
return Qt::ItemIsEditable; // FIXME: Implement me!
|
||||
} |
||||
|
||||
bool ServerModel::insertRows(int row, int count, const QModelIndex &parent) |
||||
{ |
||||
beginInsertRows(parent, row, row + count - 1); |
||||
// FIXME: Implement me!
|
||||
endInsertRows(); |
||||
} |
||||
|
||||
bool ServerModel::removeRows(int row, int count, const QModelIndex &parent) |
||||
{ |
||||
beginRemoveRows(parent, row, row + count - 1); |
||||
// FIXME: Implement me!
|
||||
endRemoveRows(); |
||||
} |
||||
|
||||
void ServerModel::buildToplevelItem(IniFile& ini, const QString& section) |
||||
{ |
||||
if (!ini.sectionExists(section)) |
||||
return; |
||||
|
||||
ServerItem* parent{ nullptr }; |
||||
if (section != "NONE") { |
||||
ItemIniValue networkDefault(ini.value(section, "DEFAULT")); |
||||
parent = new ServerItem(section, networkDefault.host, |
||||
networkDefault.port.toUShort(), |
||||
networkDefault.password); |
||||
toplevelItems.push_back(parent); |
||||
} |
||||
|
||||
const int itemCount = ini.countItems(section); |
||||
for (int i = 0; i < itemCount; ++i) { |
||||
QString key = ini.key(section, i); |
||||
if (key == "DEFAULT") |
||||
continue; |
||||
|
||||
ItemIniValue val(ini.value(section, key)); |
||||
ServerItem* item = new ServerItem(parent, key, val.host, val.port.toUShort(), val.password); |
||||
if (parent) |
||||
parent->addChild(item); |
||||
else |
||||
toplevelItems.push_back(item); |
||||
} |
||||
} |
@ -0,0 +1,66 @@ |
||||
/*
|
||||
* IdealIRC - Internet Relay Chat client |
||||
* Copyright (C) 2019 Tom-Andre Barstad |
||||
* |
||||
* This program is free software; you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation; either version 2 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License along |
||||
* with this program; if not, write to the Free Software Foundation, Inc., |
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
||||
*/ |
||||
|
||||
#ifndef SERVERMODEL_H |
||||
#define SERVERMODEL_H |
||||
|
||||
#include "ServerItem.h" |
||||
#include "IniFile.h" |
||||
#include <QAbstractItemModel> |
||||
#include <QList> |
||||
|
||||
class ServerModel : public QAbstractItemModel |
||||
{ |
||||
Q_OBJECT |
||||
|
||||
public: |
||||
explicit ServerModel(QObject *parent = nullptr); |
||||
~ServerModel() override; |
||||
|
||||
// Basic functionality:
|
||||
QModelIndex index(int row, int column, |
||||
const QModelIndex &parent = QModelIndex()) const override; |
||||
QModelIndex parent(const QModelIndex &index) const override; |
||||
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override; |
||||
int columnCount(const QModelIndex &parent = QModelIndex()) const override; |
||||
|
||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; |
||||
|
||||
// Editable:
|
||||
bool setData(const QModelIndex &index, const QVariant &value, |
||||
int role = Qt::EditRole) override; |
||||
|
||||
Qt::ItemFlags flags(const QModelIndex& index) const override; |
||||
|
||||
bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex()) override; |
||||
bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) override; |
||||
|
||||
private: |
||||
struct ItemIniValue { |
||||
ItemIniValue(QString value); |
||||
QString host; |
||||
QString port; |
||||
QString password; |
||||
}; |
||||
void buildToplevelItem(IniFile& ini, const QString& section); |
||||
QList<ServerItem*> toplevelItems; |
||||
}; |
||||
|
||||
#endif // SERVERMODEL_H
|
@ -0,0 +1,435 @@ |
||||
#include "IRC.h" |
||||
#include "MdiManager.h" |
||||
#include "IRCClient/IRCMember.h" |
||||
#include "IRCClient/IRCChannel.h" |
||||
#include "IWin/IWinStatus.h" |
||||
#include "IWin/IWinChannel.h" |
||||
#include "MdiManager.h" |
||||
#include "ConfigMgr.h" |
||||
#include "Commands.h" |
||||
#include "Numeric.h" |
||||
#include "Script/Manager.h" |
||||
#include "ScriptEvent.h" |
||||
#include "Script/Builtin/ListUtils.h" // Needed for LIST_END_MAGIC |
||||
#include <fmt/format.h> |
||||
#include <QDebug> |
||||
|
||||
IRC::IRC(IWinStatus& status) |
||||
: m_status(status) |
||||
{ |
||||
static int idCount{ 0 }; |
||||
m_id = ++idCount; |
||||
} |
||||
|
||||
void IRC::disconnectForExit(const QString& quitMessage) |
||||
{ |
||||
m_disconnectForExit = true; |
||||
std::string quitMessageConv = quitMessage.toStdString(); |
||||
if (disconnectFromServer(quitMessageConv) == IRCError::NotConnected) |
||||
qWarning() << "Trying to disconnect from an already closed connection."; |
||||
} |
||||
|
||||
void IRC::setIgnoreVerbosity(const QStringList& commandsAndNumerics) |
||||
{ |
||||
for (const QString& entry : commandsAndNumerics) |
||||
m_ignoreVerbosity.push_back(entry.toUpper()); |
||||
m_ignoreVerbosity.removeDuplicates(); |
||||
} |
||||
|
||||
void IRC::unsetIgnoreVerbosity(const QStringList& commandsAndNumerics) |
||||
{ |
||||
for (const QString& entry : commandsAndNumerics) |
||||
m_ignoreVerbosity.removeOne(entry.toUpper()); |
||||
} |
||||
|
||||
bool IRC::ignoreVerbosity(const std::string& cmd) |
||||
{ |
||||
QString qcmd{ cmd.c_str() }; |
||||
return m_ignoreVerbosity.contains(qcmd.toUpper()); |
||||
} |
||||
|
||||
void IRC::rejoinChannels() |
||||
{ |
||||
QStringList targetList; |
||||
for (IWin* subwin : m_status.subWindows()) |
||||
targetList << subwin->getButtonText(); |
||||
if (targetList.isEmpty()) |
||||
return; |
||||
QString targets = targetList.join(','); |
||||
command(Command::IRC::JOIN, { targets.toStdString() }); |
||||
} |
||||
|
||||
void IRC::onRegistered() |
||||
{ |
||||
const auto& config = ConfigMgr::instance(); |
||||
|
||||
if (config.common("RejoinChannelsOnConnect") == "1") |
||||
rejoinChannels(); |
||||
} |
||||
|
||||
void IRC::onConnected() |
||||
{ |
||||
emit connected(); |
||||
contextualScriptEvent(&m_status, ScriptEvent::Connected); |
||||
} |
||||
|
||||
void IRC::onDisconnected() |
||||
{ |
||||
emit disconnected(); |
||||
emit memberListClearedForAll(); |
||||
if (m_disconnectForExit) |
||||
emit readyForExit(); |
||||
contextualScriptEvent(&m_status, ScriptEvent::Disconnected); |
||||
} |
||||
|
||||
void IRC::onConnectionError(IRCError e) |
||||
{ |
||||
m_status.print(PrintType::ProgramInfo, IRCErrorToString(e).c_str()); |
||||
} |
||||
|
||||
void IRC::onMsgNick(const IRCPrefix& sender, const std::string& newNickname, const std::vector<std::string>& channelsAffected) |
||||
{ |
||||
for (const auto& channelName : channelsAffected) { |
||||
auto channel = getChannel(channelName); |
||||
auto memberOpt = channel->getMember(sender.nickname()); |
||||
if (!memberOpt) |
||||
continue; |
||||
|
||||
if (!ignoreVerbosity(Command::IRC::NICK)) { |
||||
const std::string msg = fmt::format("{} is now known as {}", sender.nickname(), newNickname); |
||||
m_status.printTo(channelName.c_str(), PrintType::ServerInfo, msg.c_str()); |
||||
} |
||||
|
||||
const auto& member = memberOpt->get(); |
||||
emit memberChanged(sender.nickname().c_str(), member); |
||||
} |
||||
|
||||
contextualScriptEvent(&m_status, ScriptEvent::Nick, sender.nickname(), newNickname); |
||||
} |
||||
|
||||
void IRC::onMsgMode(const IRCPrefix& sender, const std::string& target, const std::string& modes, const std::vector<std::string>& args) |
||||
{ |
||||
auto modemsg = modes; |
||||
std::for_each(args.begin(), args.end(), |
||||
[&modemsg](const std::string& arg){ |
||||
modemsg += " " + arg; |
||||
}); |
||||
|
||||
if (!ignoreVerbosity(Command::IRC::MODE)) { |
||||
const auto& is = isupport(); |
||||
const auto& chantypes = is.at("CHANTYPES"); // Chantypes is always present.
|
||||
|
||||
/* Mode changed in a channel */ |
||||
if (chantypes.find_first_of(target[0]) != std::string::npos) { |
||||
const std::string msg = fmt::format("{} changed mode: {}", sender.toString(), modemsg); |
||||
getStatus().printTo(target.c_str(), PrintType::ServerInfo, msg.c_str()); |
||||
} |
||||
|
||||
/* We changed our own usermode */ |
||||
else if (sender.toString() == target && target == getNickname()) { |
||||
const std::string msg = fmt::format("Your usermode changed: {}", modemsg); |
||||
getStatus().print(PrintType::ServerInfo, msg.c_str()); |
||||
} |
||||
|
||||
/* Probably not possible, but someone set someone elses usermode and we got the message about it */ |
||||
else { |
||||
const std::string msg = fmt::format("{} changed usermode for {}: {}", sender.toString(), target, modemsg); |
||||
getStatus().print(PrintType::ServerInfo, msg.c_str()); |
||||
} |
||||
} |
||||
|
||||
contextualScriptEvent(&m_status, ScriptEvent::Mode, sender.toString(), target, modemsg); |
||||
} |
||||
|
||||
void IRC::onMsgQuit(const IRCPrefix& sender, const std::string& message, const std::vector<std::string>& channelsAffected) |
||||
{ |
||||
for (const auto& channelName : channelsAffected) { |
||||
auto channel = getChannel(channelName); |
||||
auto memberOpt = channel->getMember(sender.nickname()); |
||||
if (!memberOpt) |
||||
continue; |
||||
|
||||
if (!ignoreVerbosity(Command::IRC::QUIT)) { |
||||
std::string msg; |
||||
|
||||
if (message.empty()) |
||||
msg = fmt::format("Quit: {} ({}@{}) ({})", sender.nickname(), sender.user(), sender.host(), message); |
||||
else |
||||
msg = fmt::format("Quit: {} ({}@{})", sender.nickname(), sender.user(), sender.host()); |
||||
|
||||
m_status.printTo(channelName.c_str(), PrintType::ServerInfo, msg.c_str()); |
||||
} |
||||
|
||||
const auto& member = memberOpt->get(); |
||||
emit memberRemoved(channelName.c_str(), member); |
||||
} |
||||
|
||||
contextualScriptEvent(&m_status, ScriptEvent::Quit, sender.nickname(), message); |
||||
} |
||||
|
||||
void IRC::onMsgJoin(const IRCPrefix& sender, const std::string& target) |
||||
{ |
||||
/* We joined a channel */ |
||||
if (sender.nickname() == getNickname()) { |
||||
auto* subwin = getStatus().createChannelWindow(target.c_str()); |
||||
if (!ignoreVerbosity(Command::IRC::JOIN)) { |
||||
const std::string msg = fmt::format("Now talking in {}", target); |
||||
subwin->print(PrintType::ServerInfo, msg.c_str()); |
||||
} |
||||
subwin->refreshWindowTitle(); |
||||
} |
||||
|
||||
/* Someone else joined a channel */ |
||||
else { |
||||
if (!ignoreVerbosity(Command::IRC::JOIN)) { |
||||
const std::string msg = fmt::format("Join: {} ({}@{})", sender.nickname(), sender.user(), sender.host()); |
||||
getStatus().printTo(target.c_str(), PrintType::ServerInfo, msg.c_str()); |
||||
} |
||||
|
||||
auto channel = getChannel(target); |
||||
auto memberOpt = channel->getMember(sender.nickname()); |
||||
if (memberOpt) |
||||
emit memberAdded(target.c_str(), memberOpt->get()); |
||||
} |
||||
|
||||
contextualScriptEvent(&m_status, ScriptEvent::Join, sender.nickname(), target); |
||||
} |
||||
|
||||
void IRC::onMsgPart(const IRCPrefix& sender, const std::string& target, const std::string& message) |
||||
{ |
||||
if (!ignoreVerbosity(Command::IRC::PART)) { |
||||
std::string msg; |
||||
if (msg.empty()) |
||||
msg = fmt::format("Part: {} ({}@{})", sender.nickname(), sender.user(), sender.host()); |
||||
else |
||||
msg = fmt::format("Part: {} ({}@{}) ({})", sender.nickname(), sender.user(), sender.host(), message); |
||||
getStatus().printTo(target.c_str(), PrintType::ServerInfo, msg.c_str()); |
||||
} |
||||
|
||||
/* We left a channel */ |
||||
if (sender.nickname() == getNickname()) |
||||
emit memberListReloaded(target.c_str()); |
||||
|
||||
/* Someone else left a channel */ |
||||
else { |
||||
auto channel = getChannel(target); |
||||
auto memberOpt = channel->getMember(sender.nickname()); |
||||
if (memberOpt) |
||||
emit memberRemoved(target.c_str(), memberOpt->get()); |
||||
} |
||||
|
||||
contextualScriptEvent(&m_status, ScriptEvent::Part, sender.nickname(), target, message); |
||||
} |
||||
|
||||
void IRC::onMsgTopic(const IRCPrefix& sender, const std::string& target, const std::string& topic) |
||||
{ |
||||
if (!ignoreVerbosity(Command::IRC::TOPIC)) { |
||||
std::string msg; |
||||
if (topic.empty()) |
||||
msg = fmt::format("{} set the topic to: {}", sender.toString(), topic); |
||||
else |
||||
msg = fmt::format("{} removed the topic", sender.toString()); |
||||
getStatus().printTo(target.c_str(), PrintType::ServerInfo, msg.c_str()); |
||||
} |
||||
|
||||
contextualScriptEvent(&m_status, ScriptEvent::Topic, sender.toString(), target, topic); |
||||
} |
||||
|
||||
void IRC::onMsgInvite(const IRCPrefix& sender, const std::string& target) |
||||
{ |
||||
if (!ignoreVerbosity(Command::IRC::INVITE)) { |
||||
const std::string msg = fmt::format("{} invited you to join {}", sender.toString(), target); |
||||
getStatus().printToActive(PrintType::ServerInfo, msg.c_str()); |
||||
} |
||||
|
||||
contextualScriptEvent(&m_status, ScriptEvent::Invite, sender.toString(), target); |
||||
} |
||||
|
||||
void IRC::onMsgKick(const IRCPrefix& sender, const std::string& target, const std::string& who, const std::string& reason) |
||||
{ |
||||
std::string msg; |
||||
|
||||
/* We were kicked */ |
||||
if (who == getNickname()) { |
||||
msg = fmt::format("You were kicked out by {} ({})", sender.toString(), reason); |
||||
emit memberListReloaded(target.c_str()); |
||||
} |
||||
|
||||
/* Someone else were kicked */ |
||||
else { |
||||
msg = fmt::format("{} kicked out {} ({})", sender.toString(), who, reason); |
||||
|
||||
auto channel = getChannel(target); |
||||
auto memberOpt = channel->getMember(who); |
||||
if (memberOpt) |
||||
emit memberRemoved(target.c_str(), memberOpt->get()); |
||||
} |
||||
|
||||
if (!ignoreVerbosity(Command::IRC::KICK)) |
||||
getStatus().printTo(target.c_str(), PrintType::ServerInfo, msg.c_str()); |
||||
|
||||
contextualScriptEvent(&m_status, ScriptEvent::Kick, sender.toString(), who, target, reason); |
||||
} |
||||
|
||||
void IRC::onMsgPrivmsg(const IRCPrefix& sender, const std::string& target, const std::string& message) |
||||
{ |
||||
if (!ignoreVerbosity(Command::IRC::PRIVMSG)) { |
||||
const auto& is = isupport(); |
||||
const auto& chantypes = is.at("CHANTYPES"); // Chantypes is always present.
|
||||
|
||||
PrintType ptype = PrintType::Normal; |
||||
if (sender.nickname() == getNickname()) |
||||
ptype = PrintType::OwnText; |
||||
|
||||
const std::string msg = fmt::format("<{}> {}", sender.toString(), message); |
||||
|
||||
/* Message to a channel */ |
||||
if (chantypes.find_first_of(target[0]) != std::string::npos) |
||||
getStatus().printTo(target.c_str(), ptype, msg.c_str()); |
||||
|
||||
/* Private message */ |
||||
else { |
||||
auto* window = getStatus().createPrivateWindow(sender.toString().c_str(), false); |
||||
window->print(ptype, msg.c_str()); |
||||
} |
||||
} |
||||
|
||||
contextualScriptEvent(&m_status, ScriptEvent::Privmsg, sender.toString(), target, message); |
||||
} |
||||
|
||||
void IRC::onMsgNotice(const IRCPrefix& sender, const std::string& target, const std::string& message) |
||||
{ |
||||
if (!ignoreVerbosity(Command::IRC::NOTICE)) { |
||||
const std::string msg = fmt::format("-{}- {}", sender.toString(), message); |
||||
getStatus().printToActive(PrintType::Notice, msg.c_str()); |
||||
} |
||||
|
||||
contextualScriptEvent(&m_status, ScriptEvent::Notice, sender.toString(), target, message); |
||||
} |
||||
|
||||
void IRC::onMsgKill(const IRCPrefix& sender, const std::string& reason) |
||||
{ |
||||
if (!ignoreVerbosity(Command::IRC::KILL)) { |
||||
std::string msg; |
||||
|
||||
if (sender.type() == IRCPrefix::Type::user) |
||||
msg = fmt::format("You were forcibly disconnected by {} ({}@{}) ({})", |
||||
sender.nickname(), sender.user(), sender.host(), reason); |
||||
else |
||||
msg = fmt::format("You were forcibly disconnected by {} ({})", |
||||
sender.servername(), reason); |
||||
|
||||
getStatus().print(PrintType::ServerInfo, msg.c_str()); |
||||
} |
||||
|
||||
contextualScriptEvent(&m_status, ScriptEvent::Kill, sender.toString(), reason); |
||||
} |
||||
|
||||
void IRC::onMsgPing(const std::string& message) |
||||
{ |
||||
|
||||
} |
||||
|
||||
void IRC::onMsgPong(const std::string& message) |
||||
{ |
||||
|
||||
} |
||||
|
||||
void IRC::onMsgError(const std::string& message) |
||||
{ |
||||
if (!ignoreVerbosity(Command::IRC::ERROR)) |
||||
getStatus().print(PrintType::ServerInfo, message.c_str()); |
||||
|
||||
contextualScriptEvent(&m_status, ScriptEvent::Error, message); |
||||
} |
||||
|
||||
void IRC::onMsgWallops(const IRCPrefix& sender, const std::string& message) |
||||
{ |
||||
if (!ignoreVerbosity(Command::IRC::WALLOPS)) { |
||||
const std::string msg = fmt::format("!{}! {}", sender.toString(), message); |
||||
getStatus().print(PrintType::Wallops, msg.c_str()); |
||||
} |
||||
|
||||
contextualScriptEvent(&m_status, ScriptEvent::Wallops, sender.toString(), message); |
||||
} |
||||
|
||||
void IRC::onMsgNumeric(const IRCPrefix& /*sender*/, const std::string& num, const std::vector<std::string>& args, const std::string& message) |
||||
{ |
||||
std::string argmsg; |
||||
|
||||
/*
|
||||
* Skip showing our own nickname if it's the first argument received. |
||||
* It usually is. |
||||
*/ |
||||
size_t argsOffset = 0; |
||||
if (!args.empty() && args[0] == getNickname()) |
||||
argsOffset = 1; |
||||
|
||||
std::for_each(args.begin() + argsOffset, args.end(), |
||||
[&argmsg](const std::string& arg){ |
||||
if (!argmsg.empty()) |
||||
argmsg += ' '; |
||||
argmsg += arg; |
||||
}); |
||||
|
||||
if (!ignoreVerbosity(num)) { |
||||
if (message.empty()) |
||||
getStatus().print(PrintType::Normal, argmsg.c_str()); |
||||
else if (argmsg.empty()) |
||||
getStatus().print(PrintType::Normal, message.c_str()); |
||||
else { |
||||
std::string msg = fmt::format("{}: {}", argmsg, message); |
||||
getStatus().print(PrintType::Normal, msg.c_str()); |
||||
} |
||||
} |
||||
|
||||
if (num == Numeric::RPL_ENDOFNAMES) { |
||||
emit memberListReloaded(args[1].c_str()); |
||||
} |
||||
|
||||
/* Script event */ |
||||
{ |
||||
ScriptArray pval; |
||||
int i = 0; |
||||
for (const std::string& arg : args) { |
||||
const std::string key = std::to_string(i); |
||||
pval.emplace(key, arg); |
||||
++i; |
||||
} |
||||
pval.emplace(LIST_END_MAGIC, i); |
||||
contextualScriptEvent(&m_status, ScriptEvent::Numeric, num, pval, message); |
||||
} |
||||
} |
||||
|
||||
void IRC::onMsgCTCPRequest(const IRCPrefix& sender, const std::string& target, const std::string& command, const std::string& message) |
||||
{ |
||||
contextualScriptEvent(&m_status, ScriptEvent::CtcpRequest, sender.toString(), target, command, message); |
||||
} |
||||
|
||||
void IRC::onMsgCTCPResponse(const IRCPrefix& sender, const std::string& target, const std::string& command, const std::string& message) |
||||
{ |
||||
contextualScriptEvent(&m_status, ScriptEvent::CtcpReply, sender.toString(), target, command, message); |
||||
} |
||||
|
||||
void IRC::onMsgDCCRequest(std::shared_ptr<DCC> dcc, const IRCPrefix& sender, const std::string& target, const std::string& type, const std::string& message) |
||||
{ |
||||
|
||||
} |
||||
|
||||
void IRC::v3onMsgAway(const IRCPrefix& sender, const std::string& message, const std::vector<std::string>& channelsAffected) |
||||
{ |
||||
std::string msg; |
||||
if (message.empty()) { |
||||
msg = fmt::format("{} is now away: {}", sender.nickname(), message); |
||||
} |
||||
else { |
||||
msg = fmt::format("{} is no longer away", sender.nickname(), message); |
||||
} |
||||
for (const auto& channel : channelsAffected) |
||||
getStatus().printTo(channel.c_str(), PrintType::ServerInfo, msg.c_str()); |
||||
} |
||||
|
||||
void IRC::v3onMsgJoin(const IRCPrefix& sender, const std::string& channel, const std::string& useraccount, const std::string& realname) |
||||
{ |
||||
onMsgJoin(sender, channel); |
||||
} |
@ -0,0 +1,92 @@ |
||||
#ifndef IRC_H |
||||
#define IRC_H |
||||
|
||||
#include "IRCClient/IRCBase.h" |
||||
#include <QStringList> |
||||
#include <QObject> |
||||
|
||||
class IRCMemberEntry; |
||||
class MdiManager; |
||||
class IWinStatus; |
||||
|
||||
class IRC : public QObject, |
||||
public IRCBase |
||||
{ |
||||
Q_OBJECT |
||||
|
||||
public: |
||||
explicit IRC(IWinStatus& status); |
||||
|
||||
void disconnectForExit(const QString& quitMessage); |
||||
IWinStatus& getStatus() { return m_status; } |
||||
|
||||
void setIgnoreVerbosity(const QStringList& commandsAndNumerics); |
||||
void unsetIgnoreVerbosity(const QStringList& commandsAndNumerics); |
||||
|
||||
void expectDisconnect() |
||||
{ |
||||
/*
|
||||
m_expectDisconnect = true; |
||||
expectDisconnectTimer.singleShot(10000, [this]{ |
||||
m_expectDisconnect = false; |
||||
}); |
||||
*/ |
||||
} |
||||
|
||||
void dontExpectDisconnect() |
||||
{ |
||||
/*
|
||||
m_expectDisconnect = false; |
||||
expectDisconnectTimer.stop(); |
||||
*/ |
||||
} |
||||
|
||||
signals: |
||||
void readyForExit(); |
||||
void disconnected(); |
||||
void connected(); |
||||
|
||||
void memberListReloaded(const QString& channel); |
||||
void memberListClearedForAll(); |
||||
void memberAdded(const QString& channel, const IRCMemberEntry& entry); |
||||
void memberChanged(const QString& nickname, const IRCMemberEntry& entry); |
||||
void memberRemoved(const QString& channel, const IRCMemberEntry& entry); |
||||
|
||||
private: |
||||
bool m_disconnectForExit{ false }; |
||||
IWinStatus& m_status; |
||||
int m_id; //! Used mainly for scripts
|
||||
|
||||
QStringList m_ignoreVerbosity; //! Used to mute certain message types from the server
|
||||
bool ignoreVerbosity(const std::string& cmd); |
||||
|
||||
void rejoinChannels(); |
||||
|
||||
void onRegistered() override; |
||||
void onConnected() override; |
||||
void onDisconnected() override; |
||||
void onConnectionError(IRCError e) override; |
||||
void onMsgNick(const IRCPrefix& sender, const std::string& newNickname, const std::vector<std::string>& channelsAffected) override; |
||||
void onMsgMode(const IRCPrefix& sender, const std::string& target, const std::string& modes, const std::vector<std::string>& args) override; |
||||
void onMsgQuit(const IRCPrefix& sender, const std::string& message, const std::vector<std::string>& channelsAffected) override; |
||||
void onMsgJoin(const IRCPrefix& sender, const std::string& target) override; |
||||
void onMsgPart(const IRCPrefix& sender, const std::string& target, const std::string& message) override; |
||||
void onMsgTopic(const IRCPrefix& sender, const std::string& target, const std::string& topic) override; |
||||
void onMsgInvite(const IRCPrefix& sender, const std::string& target) override; |
||||
void onMsgKick(const IRCPrefix& sender, const std::string& target, const std::string& who, const std::string& reason) override; |
||||
void onMsgPrivmsg(const IRCPrefix& sender, const std::string& target, const std::string& message) override; |
||||
void onMsgNotice(const IRCPrefix& sender, const std::string& target, const std::string& message) override; |
||||
void onMsgKill(const IRCPrefix& sender, const std::string& reason) override; |
||||
void onMsgPing(const std::string& message) override; |
||||
void onMsgPong(const std::string& message) override; |
||||
void onMsgError(const std::string& message) override; |
||||
void onMsgWallops(const IRCPrefix& sender, const std::string& message) override; |
||||
void onMsgNumeric(const IRCPrefix& sender, const std::string& num, const std::vector<std::string>& args, const std::string& message) override; |
||||
void onMsgCTCPRequest(const IRCPrefix& sender, const std::string& target, const std::string& command, const std::string& message) override; |
||||
void onMsgCTCPResponse(const IRCPrefix& sender, const std::string& target, const std::string& command, const std::string& message) override; |
||||
void onMsgDCCRequest(std::shared_ptr<DCC> dcc, const IRCPrefix& sender, const std::string& target, const std::string& type, const std::string& message) override; |
||||
void v3onMsgAway(const IRCPrefix& sender, const std::string& message, const std::vector<std::string>& channelsAffected) override; |
||||
void v3onMsgJoin(const IRCPrefix& sender, const std::string& channel, const std::string& useraccount, const std::string& realname) override; |
||||
}; |
||||
|
||||
#endif // IRC_H
|
@ -0,0 +1,79 @@ |
||||
#ifndef COMMANDS_H |
||||
#define COMMANDS_H |
||||
|
||||
namespace Command { |
||||
namespace IRC { |
||||
constexpr auto* PASS = "PASS"; |
||||
constexpr auto* NICK = "NICK"; |
||||
constexpr auto* USER = "USER"; |
||||
constexpr auto* OPER = "OPER"; |
||||
constexpr auto* MODE = "MODE"; |
||||
constexpr auto* QUIT = "QUIT"; |
||||
constexpr auto* SQUIT = "SQUIT"; |
||||
constexpr auto* JOIN = "JOIN"; |
||||
constexpr auto* PART = "PART"; |
||||
constexpr auto* TOPIC = "TOPIC"; |
||||
constexpr auto* NAMES = "NAMES"; |
||||
constexpr auto* LIST = "LIST"; |
||||
constexpr auto* INVITE = "INVITE"; |
||||
constexpr auto* KICK = "KICK"; |
||||
constexpr auto* PRIVMSG = "PRIVMSG"; |
||||
constexpr auto* NOTICE = "NOTICE"; |
||||
constexpr auto* MOTD = "MOTD"; |
||||
constexpr auto* LUSERS = "LUSERS"; |
||||
constexpr auto* VERSION = "VERSION"; |
||||
constexpr auto* STATS = "STATS"; |
||||
constexpr auto* LINKS = "LINKS"; |
||||
constexpr auto* TIME = "TIME"; |
||||
constexpr auto* CONNECT = "CONNECT"; |
||||
constexpr auto* TRACE = "TRACE"; |
||||
constexpr auto* ADMIN = "ADMIN"; |
||||
constexpr auto* INFO = "INFO"; |
||||
constexpr auto* SERVLIST = "SERVLIST"; |
||||
constexpr auto* SQUERY = "SQUERY"; |
||||
constexpr auto* WHO = "WHO"; |
||||
constexpr auto* WHOIS = "WHOIS"; |
||||
constexpr auto* WHOWAS = "WHOWAS"; |
||||
constexpr auto* KILL = "KILL"; |
||||
constexpr auto* PING = "PING"; |
||||
constexpr auto* PONG = "PONG"; |
||||
constexpr auto* ERROR = "ERROR"; |
||||
constexpr auto* AWAY = "AWAY"; |
||||
constexpr auto* REHASH = "REHASH"; |
||||
constexpr auto* DIE = "DIE"; |
||||
constexpr auto* RESTART = "RESTART"; |
||||
constexpr auto* SUMMON = "SUMMON"; |
||||
constexpr auto* USERS = "USERS"; |
||||
constexpr auto* WALLOPS = "WALLOPS"; |
||||
constexpr auto* USERHOST = "USERHOST"; |
||||
constexpr auto* ISON = "ISON"; |
||||
} |
||||
|
||||
namespace Extension { |
||||
constexpr auto* ACCOUNT = "ACCOUNT"; |
||||
} |
||||
|
||||
// TODO move to client specific side
|
||||
namespace Internal { |
||||
constexpr auto* CTCP = "CTCP"; |
||||
constexpr auto* ME = "ME"; |
||||
constexpr auto* ECHO = "ECHO"; |
||||
constexpr auto* QUERY = "QUERY"; |
||||
constexpr auto* MUTERESP = "MUTERESP"; |
||||
constexpr auto* UNMUTERESP = "UNMUTERESP"; |
||||
} |
||||
|
||||
namespace IRCv3 { |
||||
constexpr auto* CAP = "CAP"; |
||||
constexpr auto* LS = "LS"; |
||||
constexpr auto* LIST = "LIST"; |
||||
constexpr auto* REQ = "REQ"; |
||||
constexpr auto* ACK = "ACK"; |
||||
constexpr auto* NAK = "NAK"; |
||||
constexpr auto* END = "END"; |
||||
constexpr auto* NEW = "NEW"; |
||||
constexpr auto* DEL = "DEL"; |
||||
} // namespace IRCv3
|
||||
} // namespace command
|
||||
|
||||
#endif // COMMANDS_H
|
@ -0,0 +1,207 @@ |
||||
#include "DCC.h" |
||||
#include "IRCBase.h" |
||||
#include <fmt/format.h> |
||||
#include <iostream> |
||||
|
||||
using asio::ip::tcp; |
||||
|
||||
struct DCCPriv |
||||
{ |
||||
DCCPriv(DCC& super_, IRCBase& ircctx_, DCC::Direction dir_, asio::io_context& ioctx_, const std::string& ip_, const std::string& port_) |
||||
: super(super_) |
||||
, ircctx(ircctx_) |
||||
, direction(dir_) |
||||
, ioctx(ioctx_) |
||||
, ip(ip_) |
||||
, port(port_) |
||||
, resolver(ioctx_) |
||||
, acceptor(ioctx_) |
||||
, endpoint(tcp::v4(), std::stoi(port_)) |
||||
, socket(ioctx_) |
||||
{} |
||||
|
||||
DCC& super; |
||||
IRCBase& ircctx; |
||||
|
||||
bool pending{ true }; |
||||
const DCC::Direction direction; |
||||
asio::io_context& ioctx; |
||||
std::string ip; // Only used when Direction=Target, and describes the Initiator's IP.
|
||||
std::string port; |
||||
|
||||
tcp::resolver resolver; // for outbound connection (we act as a Target)
|
||||
tcp::acceptor acceptor; // for inbound connection (we act as an Initiator)
|
||||
|
||||
tcp::resolver::results_type resolverResults; |
||||
asio::ip::tcp::endpoint endpoint; |
||||
tcp::socket socket; |
||||
|
||||
asio::streambuf readbuf; |
||||
|
||||
DCC::CallbackRead cbRead; |
||||
DCC::CallbackConnected cbCon; |
||||
DCC::CallbackDisconnected cbDiscon; |
||||
|
||||
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); |
||||
}); |
||||
} |
||||
|
||||
void disconnected(const asio::error_code& ec) |
||||
{ |
||||
super.onDisconnected(SystemErrorToIRCError(ec)); |
||||
} |
||||
|
||||
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); |
||||
}); |
||||
} |
||||
|
||||
void writeFinished(const asio::error_code& ec, std::size_t bytes_transferred) |
||||
{ |
||||
std::cout << "DCC wrote " << bytes_transferred << " bytes." << std::endl; |
||||
if (!ec) return; // No errors.
|
||||
std::cerr << fmt::format("'- DCC write error: {} ({})", SystemErrorToIRCError(ec), ec.message()) << std::endl; |
||||
} |
||||
}; |
||||
|
||||
DCC::DCC(IRCBase& ircctx, asio::io_context& ioctx, const std::string& port) |
||||
: mp(std::make_unique<DCCPriv>(*this, ircctx, Direction::Initiator, ioctx, "", port)) |
||||
{ |
||||
mp->acceptor.open(mp->endpoint.protocol()); |
||||
mp->acceptor.bind(mp->endpoint); |
||||
mp->acceptor.listen(1); |
||||
mp->acceptor.async_accept(mp->socket, [this](const asio::error_code& ec){ |
||||
if (ec) |
||||
mp->disconnected(ec); |
||||
else |
||||
mp->connected(ec); |
||||
}); |
||||
} |
||||
|
||||
DCC::DCC(IRCBase& ircctx, asio::io_context& ioctx, const std::string& ip, const std::string& port) |
||||
: mp(std::make_unique<DCCPriv>(*this, ircctx, Direction::Target, ioctx, ip, port)) |
||||
{} |
||||
|
||||
DCC::DCC(DCC&& other) noexcept |
||||
: mp(std::move(other.mp)) |
||||
{} |
||||
|
||||
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; |
||||
} |
||||
} |
||||
|
||||
bool DCC::isPending() const |
||||
{ |
||||
return mp->pending; |
||||
} |
||||
|
||||
IRCError DCC::accept() |
||||
{ |
||||
if (mp->direction == Direction::Initiator) |
||||
return IRCError::DCC_NotATarget; // Only usable if we are a Target...
|
||||
|
||||
mp->resolverResults = mp->resolver.resolve(mp->ip, mp->port); |
||||
|
||||
asio::async_connect(mp->socket, mp->resolverResults, |
||||
[this](const asio::error_code& ec, const tcp::endpoint&) { |
||||
if (ec) |
||||
mp->disconnected(ec); |
||||
else |
||||
mp->connected(ec); |
||||
}); |
||||
|
||||
mp->pending = false; |
||||
return IRCError::NoError; |
||||
} |
||||
|
||||
DCC::Direction DCC::direction() const |
||||
{ |
||||
return mp->direction; |
||||
} |
||||
|
||||
void DCC::write(const ByteString& data) |
||||
{ |
||||
mp->socket.async_send(asio::buffer(data), |
||||
[this](const asio::error_code& ec, std::size_t bytes_transferred){ |
||||
mp->writeFinished(ec, bytes_transferred); |
||||
}); |
||||
} |
||||
|
||||
IRCBase& DCC::context() |
||||
{ |
||||
return mp->ircctx; |
||||
} |
||||
|
||||
const IRCBase& DCC::context() const |
||||
{ |
||||
return mp->ircctx; |
||||
} |
||||
|
||||
void DCC::callbackRead(DCC::CallbackRead&& cb) |
||||
{ |
||||
mp->cbRead = std::move(cb); |
||||
} |
||||
|
||||
void DCC::callbackConnected(DCC::CallbackConnected&& cb) |
||||
{ |
||||
mp->cbCon = std::move(cb); |
||||
} |
||||
|
||||
void DCC::callbackDisconnected(DCC::CallbackDisconnected&& cb) |
||||
{ |
||||
mp->cbDiscon = std::move(cb); |
||||
} |
||||
|
||||
|
||||
void DCC::onRead(std::istream& is) |
||||
{ |
||||
if (mp->cbRead) |
||||
mp->cbRead(is); |
||||
} |
||||
|
||||
void DCC::onConnected() |
||||
{ |
||||
if (mp->cbCon) |
||||
mp->cbCon(); |
||||
} |
||||
|
||||
void DCC::onDisconnected(IRCError e) |
||||
{ |
||||
if (mp->cbDiscon) |
||||
mp->cbDiscon(e); |
||||
} |
||||
|
@ -0,0 +1,73 @@ |
||||
#ifndef DCC_H |
||||
#define DCC_H |
||||
|
||||
#include "IRCError.h" |
||||
#include <string> |
||||
#include <memory> |
||||
#include <asio.hpp> |
||||
|
||||
using asio::ip::tcp; |
||||
|
||||
class IRCBase; |
||||
|
||||
struct DCCPriv; |
||||
class DCC |
||||
{ |
||||
friend struct IRCBasePriv; |
||||
friend struct DCCPriv; |
||||
|
||||
public: |
||||
using CallbackRead = std::function<void(std::istream&)>; |
||||
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 |
||||
*/ |
||||
enum class Direction { |
||||
Initiator, |
||||
Target |
||||
}; |
||||
|
||||
~DCC(); |
||||
DCC(DCC&& other) noexcept; |
||||
|
||||
//! Constructs an Initiator (server)
|
||||
DCC(IRCBase& ircctx, asio::io_context& ioctx, const std::string& port); |
||||
|
||||
//! Constructs a Target (client)
|
||||
DCC(IRCBase& ircctx, asio::io_context& ioctx, const std::string& ip, const std::string& port); |
||||
|
||||
[[nodiscard]] bool isPending() const; |
||||
IRCError accept(); |
||||
|
||||
[[nodiscard]] Direction direction() const; |
||||
|
||||
void write(const ByteString& data); |
||||
|
||||
IRCBase& context(); |
||||
const IRCBase& context() const; |
||||
|
||||
/*
|
||||
* Callbacks used by 'users' of this class. |
||||
*/ |
||||
void callbackRead(CallbackRead&& cb); |
||||
void callbackConnected(CallbackConnected&& cb); |
||||
void callbackDisconnected(CallbackDisconnected&& cb); |
||||
|
||||
protected: |
||||
/*
|
||||
* Use these to read data in a custom derivative of DCC. |
||||
* 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 onConnected(); |
||||
virtual void onDisconnected(IRCError e); |
||||
|
||||
private: |
||||
std::unique_ptr<DCCPriv> mp; |
||||
}; |
||||
|
||||
#endif // DCC_H
|
@ -0,0 +1,152 @@ |
||||
#ifndef IRCBASE_H |
||||
#define IRCBASE_H |
||||
|
||||
#include "IRCError.h" |
||||
#include "IRCPrefix.h" |
||||
|
||||
#include <string> |
||||
#include <vector> |
||||
#include <memory> |
||||
#include <chrono> |
||||
#include <unordered_map> |
||||
|
||||
namespace asio { class io_context; } |
||||
class IRCChannel; |
||||
class IRCMember; |
||||
class DCC; |
||||
|
||||
struct IRCBasePriv; |
||||
class IRCBase |
||||
{ |
||||
friend struct IRCBasePriv; |
||||
|
||||
public: |
||||
IRCBase(); |
||||
~IRCBase(); |
||||
|
||||
IRCBase(IRCBase&& other); |
||||
IRCBase(const IRCBase&) = delete; |
||||
IRCBase& operator=(const IRCBase&) = delete; |
||||
|
||||
bool poll(); |
||||
|
||||
[[nodiscard]] const std::string& getHostname() const; |
||||
IRCError setHostname(const std::string& hostname, bool SSL = false); |
||||
|
||||
[[nodiscard]] const std::string& getPort() const; |
||||
IRCError setPort(const std::string& port); |
||||
|
||||
[[nodiscard]] const std::string& getRealname() const; |
||||
IRCError setRealname(const std::string& realname); |
||||
|
||||
[[nodiscard]] const std::string& getIdent() const; |
||||
IRCError setIdent(const std::string& ident); |
||||
|
||||
[[nodiscard]] const std::string& getNickname() const; |
||||
void setNickname(const std::string& nickname); |
||||
|
||||
[[nodiscard]] const std::string& getPassword() const; |
||||
void setPassword(const std::string& password); |
||||
|
||||
void exceptSSL_SelfSigned(bool except); |
||||
void exceptSSL_CNMismatch(bool except); |
||||
void exceptSSL_Expired(bool except); |
||||
|
||||
void command(const std::string& command, const std::vector<std::string>& args, const std::string& msg = ""); |
||||
void command(const std::string& command, const std::string& msg); |
||||
void raw(const std::string& data); |
||||
|
||||
void ctcp(const std::string& target, const std::string& command, const std::string& message = ""); |
||||
|
||||
//! Set to >0 to enable, =0 to disable.
|
||||
void setManualKeepalive(std::chrono::seconds freq = std::chrono::seconds(30)); |
||||
|
||||
[[nodiscard]] std::chrono::milliseconds getManualKeepaliveFreq() const; |
||||
|
||||
[[nodiscard]] asio::io_context& getIOCTX(); |
||||
|
||||
/**
|
||||
* Takes a string of modes (letters like ohv), in their intended order |
||||
* and converts each letter to its corresponding display prefix (ie. @%+). |
||||
* For mode (letters) that isn't valid, a colon ':' is placed in-stead (since |
||||
* those can't naturally exist with the IRC protocol.) |
||||
*/ |
||||
[[nodiscard]] std::string toMemberPrefix(const std::string& modes); |
||||
|
||||
[[nodiscard]] IRCError tryConnect(); |
||||
[[nodiscard]] IRCError disconnectFromServer(const std::string& quitMessage = ""); |
||||
[[nodiscard]] IRCError lastErrorCode() const; |
||||
|
||||
[[nodiscard]] bool isOnline() const; |
||||
[[nodiscard]] bool isConnected() const; |
||||
[[nodiscard]] bool isSSL() const; |
||||
|
||||
[[nodiscard]] static const std::vector<std::string>& clientV3Support(); |
||||
[[nodiscard]] const std::vector<std::string>& registeredV3Support() const; |
||||
[[nodiscard]] const std::vector<std::string>& serverV3Support() const; |
||||
[[nodiscard]] const std::unordered_map<std::string,std::string>& isupport() const; |
||||
|
||||
[[nodiscard]] const std::vector<std::shared_ptr<IRCChannel>>& channels() const; |
||||
[[nodiscard]] std::shared_ptr<IRCChannel> getChannel(const std::string& name) const; |
||||
[[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); |
||||
|
||||
protected: |
||||
virtual void onConnected() {} |
||||
virtual void onDisconnected() {} |
||||
virtual void onRegistered() {} |
||||
virtual void onConnectionError(IRCError e) {} |
||||
|
||||
/*
|
||||
* To correctly implement and get all data from a standard IRC server, |
||||
* the following virtuals must be implemented. |
||||
*/ |
||||
virtual void onMsgNick(const IRCPrefix& sender, const std::string& newNickname, const std::vector<std::string>& channelsAffected) = 0; |
||||
virtual void onMsgMode(const IRCPrefix& sender, const std::string& target, const std::string& modes, const std::vector<std::string>& args) = 0; |
||||
virtual void onMsgQuit(const IRCPrefix& sender, const std::string& message, const std::vector<std::string>& channelsAffected) = 0; |
||||
virtual void onMsgJoin(const IRCPrefix& sender, const std::string& target) = 0; |
||||
virtual void onMsgPart(const IRCPrefix& sender, const std::string& target, const std::string& message) = 0; |
||||
virtual void onMsgTopic(const IRCPrefix& sender, const std::string& target, const std::string& topic) = 0; |
||||
virtual void onMsgInvite(const IRCPrefix& sender, const std::string& target) = 0; |
||||
virtual void onMsgKick(const IRCPrefix& sender, const std::string& target, const std::string& who, const std::string& reason) = 0; |
||||
virtual void onMsgPrivmsg(const IRCPrefix& sender, const std::string& target, const std::string& message) = 0; |
||||
virtual void onMsgNotice(const IRCPrefix& sender, const std::string& target, const std::string& message) = 0; |
||||
virtual void onMsgKill(const IRCPrefix& sender, const std::string& reason) = 0; |
||||
virtual void onMsgPing(const std::string& message) = 0; |
||||
virtual void onMsgPong(const std::string& message) = 0; |
||||
virtual void onMsgError(const std::string& message) = 0; |
||||
virtual void onMsgWallops(const IRCPrefix& sender, const std::string& message) = 0; |
||||
virtual void onMsgNumeric(const IRCPrefix& sender, const std::string& num, const std::vector<std::string>& args, const std::string& message) = 0; |
||||
|
||||
/*
|
||||
* CTCP/DCC isn't really a part of the IRC standard so these are optional. |
||||
*/ |
||||
virtual void onMsgCTCPRequest(const IRCPrefix& sender, const std::string& target, const std::string& command, const std::string& message) {}; |
||||
virtual void onMsgCTCPResponse(const IRCPrefix& sender, const std::string& target, const std::string& command, const std::string& message) {}; |
||||
virtual void onMsgDCCRequest(std::shared_ptr<DCC> dcc, const IRCPrefix& sender, const std::string& target, const std::string& type, const std::string& message) {}; |
||||
|
||||
/*
|
||||
* Optional IRCv3 extensions to implement. |
||||
*/ |
||||
virtual void v3onMsgAway(const IRCPrefix& sender, const std::string& message, const std::vector<std::string>& channelsAffected) {}; |
||||
virtual void v3onMsgAccountLogin(const IRCPrefix& sender, const std::string& useraccount) {} |
||||
virtual void v3onMsgAccountLogout(const IRCPrefix& sender) {} |
||||
|
||||
/*
|
||||
* Required IRCv3 extensions to implement. |
||||
*/ |
||||
virtual void v3onMsgJoin(const IRCPrefix& sender, const std::string& channel, const std::string& useraccount, const std::string& realname) = 0; |
||||
|
||||
/*
|
||||
* Catch-all handler. If the message parser cannot match for any of the commands, it will turn up here. |
||||
* Optional to implement. |
||||
*/ |
||||
virtual void onMsgUnhandled(const IRCPrefix& sender, const std::string& command, const std::vector<std::string>& args, const std::string& message) {} |
||||
|
||||
private: |
||||
std::unique_ptr<IRCBasePriv> mp; |
||||
}; |
||||
|
||||
#endif // IRCBASE_H
|
@ -0,0 +1,85 @@ |
||||
#include "IRCChannel.h" |
||||
#include "IRCMember.h" |
||||
#include "IRCBase.h" |
||||
#include <algorithm> |
||||
|
||||
IRCChannel::IRCChannel(const std::string& name, IRCBase& owner) |
||||
: m_name(name) |
||||
, m_owner(&owner) |
||||
{} |
||||
|
||||
const std::string& IRCChannel::name() const |
||||
{ |
||||
return m_name; |
||||
} |
||||
|
||||
std::optional<IRCMemberEntryRef> IRCChannel::getMember(const std::string& nickname) |
||||
{ |
||||
auto it = std::find_if(m_members.begin(), m_members.end(), |
||||
[&nickname](const IRCMemberEntry& entry){ |
||||
return entry.member()->prefix().nickname() == nickname; |
||||
}); |
||||
|
||||
return it == m_members.end() ? std::nullopt |
||||
: std::make_optional<IRCMemberEntryRef>(*it); |
||||
} |
||||
|
||||
IRCMemberEntry& IRCChannel::addMember(std::shared_ptr<IRCMember> member) |
||||
{ |
||||
m_members.emplace_back(member, *m_owner); |
||||
return m_members.back(); |
||||
} |
||||
|
||||
void IRCChannel::delMember(std::shared_ptr<IRCMember> member) |
||||
{ |
||||
auto newEnd = std::remove_if(m_members.begin(), m_members.end(), [&member](const IRCMemberEntry& e){ |
||||
return member.get() == e.member().get(); |
||||
}); |
||||
|
||||
m_members.erase(newEnd, m_members.end()); |
||||
} |
||||
|
||||
const std::vector<IRCMemberEntry>& IRCChannel::members() const |
||||
{ |
||||
return m_members; |
||||
} |
||||
|
||||
void IRCChannel::setTopic(const std::string& topic) |
||||
{ |
||||
m_topic = topic; |
||||
} |
||||
|
||||
const std::string& IRCChannel::topic() const |
||||
{ |
||||
return m_topic; |
||||
} |
||||
|
||||
const std::unordered_map<char, std::string>& IRCChannel::modes() const |
||||
{ |
||||
return m_modes; |
||||
} |
||||
|
||||
void IRCChannel::setMode(char m, const std::string& parameter) |
||||
{ |
||||
/*
|
||||
* Note that if a mode requiring an argument is already set, |
||||
* the server will always send an 'unset' before setting a new value. |
||||
* But let's use insert_or_assign anyway. |
||||
*/ |
||||
m_modes.insert_or_assign(m, parameter); |
||||
} |
||||
|
||||
void IRCChannel::delMode(char m) |
||||
{ |
||||
m_modes.erase(m); |
||||
} |
||||
|
||||
bool IRCChannel::isPopulating() const |
||||
{ |
||||
return m_populating; |
||||
} |
||||
|
||||
void IRCChannel::donePopulating() |
||||
{ |
||||
m_populating = false; |
||||
} |
@ -0,0 +1,49 @@ |
||||
#ifndef IRCCHANNEL_H |
||||
#define IRCCHANNEL_H |
||||
|
||||
#include "IRCMemberEntry.h" |
||||
#include <memory> |
||||
#include <string> |
||||
#include <vector> |
||||
#include <unordered_map> |
||||
#include <optional> |
||||
|
||||
class IRCMember; |
||||
class IRCBase; |
||||
|
||||
using IRCMemberEntryRef = std::reference_wrapper<IRCMemberEntry>; |
||||
|
||||
class IRCChannel |
||||
{ |
||||
public: |
||||
IRCChannel(const std::string& name, IRCBase& owner); |
||||
~IRCChannel() = default; |
||||
|
||||
const std::string& name() const; |
||||
|
||||
std::optional<IRCMemberEntryRef> getMember(const std::string& nickname); |
||||
IRCMemberEntry& addMember(std::shared_ptr<IRCMember> member); |
||||
void delMember(std::shared_ptr<IRCMember> member); |
||||
const std::vector<IRCMemberEntry>& members() const; |
||||
|
||||
void setTopic(const std::string& topic); |
||||
const std::string& topic() const; |
||||
|
||||
const std::unordered_map<char, std::string>& modes() const; |
||||
void setMode(char m, const std::string& parameter = ""); |
||||
void delMode(char m); |
||||
|
||||
bool isPopulating() const; |
||||
void donePopulating(); |
||||
|
||||
private: |
||||
std::string m_name; |
||||
std::vector<IRCMemberEntry> m_members; |
||||
std::string m_topic; |
||||
std::unordered_map<char, std::string> m_modes; |
||||
bool m_populating{ true }; |
||||
IRCBase* m_owner; // TODO use reference?
|
||||
}; |
||||
|
||||
|
||||
#endif // IRCCHANNEL_H
|
@ -0,0 +1,105 @@ |
||||
#include "IRCError.h" |
||||
#include <iostream> |
||||
#include <fmt/format.h> |
||||
#include <asio/error.hpp> |
||||
|
||||
std::string IRCErrorToString(IRCError e) |
||||
{ |
||||
switch (e) { |
||||
case IRCError::NoError: |
||||
return "No error"; |
||||
case IRCError::NotConnected: |
||||
return "Not connected"; |
||||
case IRCError::AlreadyConnected: |
||||
return "Already connected"; |
||||
case IRCError::CannotResolveAddress: |
||||
return "Cannot resolve address"; |
||||
case IRCError::BrokenPipe: |
||||
return "Broken pipe"; |
||||
case IRCError::ConnectionAborted: |
||||
return "Connection aborted"; |
||||
case IRCError::ConnectionRefused: |
||||
return "Connection refused"; |
||||
case IRCError::ConnectionReset: |
||||
return "Connection reset"; |
||||
case IRCError::HostUnreachable: |
||||
return "Host unreachable"; |
||||
case IRCError::NetworkDown: |
||||
return "Network down"; |
||||
case IRCError::NetworkReset: |
||||
return "Network reset"; |
||||
case IRCError::NetworkUnreachable: |
||||
return "Network unreachable"; |
||||
case IRCError::NoDescriptors: |
||||
return "No file descriptors left"; |
||||
case IRCError::TimedOut: |
||||
return "Timed out"; |
||||
case IRCError::EndOfFile: |
||||
return "EOF"; |
||||
case IRCError::SSL_SelfSigned: |
||||
return "SSL: Self-signed certificate"; |
||||
case IRCError::SSL_CN_Mismatch: |
||||
return "SSL: (CN) Hostname mismatch"; |
||||
case IRCError::SSL_CN_Missing: |
||||
return "SSL: (CN) Hostname missing"; |
||||
case IRCError::SSL_CN_WildcardIllegal: |
||||
return "SSL: (CN) Illegal wildcard usage"; |
||||
case IRCError::SSL_NotYetValid: |
||||
return "SSL: Certificate not yet valid"; |
||||
case IRCError::SSL_Expired: |
||||
return "SSL: Certificate expired"; |
||||
case IRCError::CannotChangeWhenConnected: |
||||
return "Cannot change this setting when connected"; |
||||
case IRCError::HostNotSet: |
||||
return "Hostname is not set"; |
||||
case IRCError::PortNotSet: |
||||
return "Port number is not set"; |
||||
case IRCError::IdentNotSet: |
||||
return "Ident/username is not set"; |
||||
case IRCError::NicknameNotSet: |
||||
return "Nickname is not set"; |
||||
case IRCError::RealnameNotSet: |
||||
return "Real name is not set"; |
||||
case IRCError::DCC_NotATarget: |
||||
return "DCC: We are not a DCC target"; |
||||
case IRCError::DCC_TimedOut: |
||||
return "DCC: Request timed out"; |
||||
case IRCError::UnhandledException: |
||||
return "Unhandled exception"; |
||||
} |
||||
} |
||||
|
||||
IRCError SystemErrorToIRCError(const std::system_error& e) |
||||
{ |
||||
switch (e.code().value()) { |
||||
case 0: |
||||
return IRCError::NoError; |
||||
case asio::error::host_not_found: |
||||
return IRCError::CannotResolveAddress; |
||||
case asio::error::broken_pipe: |
||||
return IRCError::BrokenPipe; |
||||
case asio::error::connection_aborted: |
||||
return IRCError::ConnectionAborted; |
||||
case asio::error::connection_refused: |
||||
return IRCError::ConnectionRefused; |
||||
case asio::error::connection_reset: |
||||
return IRCError::ConnectionReset; |
||||
case asio::error::host_unreachable: |
||||
return IRCError::HostUnreachable; |
||||
case asio::error::network_down: |
||||
return IRCError::NetworkDown; |
||||
case asio::error::network_reset: |
||||
return IRCError::NetworkReset; |
||||
case asio::error::network_unreachable: |
||||
return IRCError::NetworkUnreachable; |
||||
case asio::error::no_descriptors: |
||||
return IRCError::NoDescriptors; |
||||
case asio::error::timed_out: |
||||
return IRCError::TimedOut; |
||||
case asio::error::eof: |
||||
return IRCError::EndOfFile; |
||||
default: |
||||
std::cerr << fmt::format("Unhandled exception {}: {}", e.code().value(), e.code().message()) << std::endl; |
||||
return IRCError::UnhandledException; |
||||
} |
||||
} |
@ -0,0 +1,50 @@ |
||||
#ifndef IRCERROR_H |
||||
#define IRCERROR_H |
||||
|
||||
#include <system_error> |
||||
#include <string> |
||||
|
||||
enum class IRCError |
||||
{ |
||||
NoError, |
||||
|
||||
NotConnected, |
||||
AlreadyConnected, |
||||
|
||||
CannotResolveAddress, |
||||
BrokenPipe, |
||||
ConnectionAborted, |
||||
ConnectionRefused, |
||||
ConnectionReset, |
||||
HostUnreachable, |
||||
NetworkDown, |
||||
NetworkReset, |
||||
NetworkUnreachable, |
||||
NoDescriptors, |
||||
TimedOut, |
||||
EndOfFile, |
||||
|
||||
SSL_SelfSigned, |
||||
SSL_CN_Mismatch, |
||||
SSL_CN_Missing, |
||||
SSL_CN_WildcardIllegal, |
||||
SSL_NotYetValid, |
||||
SSL_Expired, |
||||
|
||||
CannotChangeWhenConnected, |
||||
HostNotSet, |
||||
PortNotSet, |
||||
IdentNotSet, |
||||
NicknameNotSet, |
||||
RealnameNotSet, |
||||
|
||||
DCC_NotATarget, |
||||
DCC_TimedOut, |
||||
|
||||
UnhandledException |
||||
}; |
||||
|
||||
std::string IRCErrorToString(IRCError e); |
||||
IRCError SystemErrorToIRCError(const std::system_error& e); |
||||
|
||||
#endif // IRCERROR_H
|
@ -0,0 +1,43 @@ |
||||
#include "IRCMember.h" |
||||
#include <algorithm> |
||||
|
||||
IRCMember::IRCMember(const IRCPrefix& prefix) |
||||
: m_prefix(prefix) |
||||
{} |
||||
|
||||
IRCMember::IRCMember(const std::string& nickname) |
||||
: m_prefix(IRCPrefix::fromNickname(nickname)) |
||||
{} |
||||
|
||||
const IRCPrefix& IRCMember::prefix() const |
||||
{ |
||||
return m_prefix; |
||||
} |
||||
|
||||
void IRCMember::setPrefix(const IRCPrefix& prefix) |
||||
{ |
||||
m_prefix = prefix; |
||||
} |
||||
|
||||
const std::vector<std::shared_ptr<IRCChannel>>& IRCMember::channels() |
||||
{ |
||||
return m_channels; |
||||
} |
||||
|
||||
void IRCMember::addChannel(std::shared_ptr<IRCChannel> channel) |
||||
{ |
||||
auto it = std::find(m_channels.begin(), m_channels.end(), channel); |
||||
if (it == m_channels.end()) |
||||
m_channels.emplace_back(channel); |
||||
} |
||||
|
||||
void IRCMember::delChannel(std::shared_ptr<IRCChannel> channel) |
||||
{ |
||||
auto newEnd = std::remove(m_channels.begin(), m_channels.end(), channel); |
||||
m_channels.erase(newEnd, m_channels.end()); |
||||
} |
||||
|
||||
void IRCMember::setNickname(const std::string& nickname) |
||||
{ |
||||
m_prefix.setNickname(nickname); |
||||
} |
@ -0,0 +1,30 @@ |
||||
#ifndef IRCMEMBER_H |
||||
#define IRCMEMBER_H |
||||
|
||||
#include "IRCPrefix.h" |
||||
#include <vector> |
||||
#include <memory> |
||||
#include <string> |
||||
|
||||
class IRCChannel; |
||||
|
||||
class IRCMember |
||||
{ |
||||
public: |
||||
explicit IRCMember(const IRCPrefix& prefix); |
||||
explicit IRCMember(const std::string& nickname); |
||||
|
||||
const IRCPrefix& prefix() const; |
||||
void setPrefix(const IRCPrefix& prefix); |
||||
const std::vector<std::shared_ptr<IRCChannel>>& channels(); |
||||
void addChannel(std::shared_ptr<IRCChannel> channel); |
||||
void delChannel(std::shared_ptr<IRCChannel> channel); |
||||
|
||||
void setNickname(const std::string& nickname); |
||||
|
||||
private: |
||||
IRCPrefix m_prefix; |
||||
std::vector<std::shared_ptr<IRCChannel>> m_channels; |
||||
}; |
||||
|
||||
#endif // IRCMEMBER_H
|
@ -0,0 +1,55 @@ |
||||
#include "IRCMemberEntry.h" |
||||
#include "IRCBase.h" |
||||
#include <algorithm> |
||||
|
||||
IRCMemberEntry::IRCMemberEntry(std::shared_ptr<IRCMember> member, IRCBase& owner) |
||||
: m_member(member) |
||||
, m_owner(&owner) |
||||
{} |
||||
|
||||
std::shared_ptr<IRCMember> IRCMemberEntry::member() const |
||||
{ |
||||
return m_member; |
||||
} |
||||
|
||||
const std::string& IRCMemberEntry::modes() const |
||||
{ |
||||
return m_modes; |
||||
} |
||||
|
||||
void IRCMemberEntry::addMode(char m) |
||||
{ |
||||
// Note: "PREFIX" is always present.
|
||||
// Value example: (ohv)@%+
|
||||
// Value is ordered with most significant first.
|
||||
const std::string& prefix = m_owner->isupport().find("PREFIX")->second; |
||||
|
||||
// Just interested in the mode letters (ie. ohv)
|
||||
const std::string validModes(prefix.begin() + 1, std::find(prefix.begin(), prefix.end(), ')')); |
||||
auto order = [validModes](const char m) -> int { |
||||
auto ret = validModes.find(m); |
||||
return ret != std::string::npos ? static_cast<int>(ret) |
||||
: -1; |
||||
}; |
||||
|
||||
auto mOrd = order(m); |
||||
if (mOrd < 0) |
||||
return; |
||||
|
||||
auto it = m_modes.begin(); |
||||
for (auto& mode : m_modes) { |
||||
if (mode == m) |
||||
return; |
||||
else if (order(mode) >= mOrd) |
||||
break; |
||||
else |
||||
++it; |
||||
} |
||||
m_modes.insert(it, m); |
||||
} |
||||
|
||||
void IRCMemberEntry::delMode(char m) |
||||
{ |
||||
auto pos = m_modes.find_first_of(m); |
||||
m_modes.erase(pos); |
||||
} |
@ -0,0 +1,28 @@ |
||||
#ifndef IRCMEMBERENTRY_H |
||||
#define IRCMEMBERENTRY_H |
||||
|
||||
#include <memory> |
||||
#include <string> |
||||
|
||||
class IRCMember; |
||||
class IRCBase; |
||||
|
||||
class IRCMemberEntry |
||||
{ |
||||
public: |
||||
IRCMemberEntry(std::shared_ptr<IRCMember> member, IRCBase& owner); |
||||
~IRCMemberEntry() = default; |
||||
|
||||
[[nodiscard]] std::shared_ptr<IRCMember> member() const; |
||||
|
||||
[[nodiscard]] const std::string& modes() const; |
||||
void addMode(char m); |
||||
void delMode(char m); |
||||
|
||||
private: |
||||
std::shared_ptr<IRCMember> m_member; |
||||
std::string m_modes; |
||||
IRCBase* m_owner; // can't be reference since we store IRCMemberEntry in a vector, requiring assignment operator.
|
||||
}; |
||||
|
||||
#endif // IRCMEMBERENTRY_H
|
@ -0,0 +1,111 @@ |
||||
//
|
||||
// Created by tomatix on 23.05.2020.
|
||||
//
|
||||
|
||||
#include <fmt/format.h> |
||||
#include "IRCPrefix.h" |
||||
|
||||
IRCPrefix::IRCPrefix(const std::string& prefix) |
||||
{ |
||||
auto atIt = std::find(prefix.begin(), prefix.end(), '@'); |
||||
if (atIt != prefix.end()) { |
||||
// nickname!user@host
|
||||
m_type = Type::user; |
||||
auto exclmIt = std::find(prefix.begin(), prefix.end(), '!'); |
||||
m_nickname = std::string(prefix.begin(), exclmIt); |
||||
m_user = std::string(exclmIt + 1, atIt); |
||||
m_host = std::string(atIt + 1, prefix.end()); |
||||
} |
||||
else { |
||||
m_type = Type::server; |
||||
m_servername = prefix; |
||||
} |
||||
} |
||||
|
||||
IRCPrefix::IRCPrefix(const IRCPrefix& other) |
||||
: m_servername(other.m_servername) |
||||
, m_nickname(other.m_nickname) |
||||
, m_user(other.m_user) |
||||
, m_host(other.m_host) |
||||
, m_type(other.m_type) |
||||
{} |
||||
|
||||
IRCPrefix::IRCPrefix(IRCPrefix&& other) noexcept |
||||
: m_servername(std::move(other.m_servername)) |
||||
, m_nickname(std::move(other.m_nickname)) |
||||
, m_user(std::move(other.m_user)) |
||||
, m_host(std::move(other.m_host)) |
||||
, m_type(std::move(other.m_type)) |
||||
{} |
||||
|
||||
IRCPrefix::IRCPrefix(IRCPrefix::Type t) |
||||
: m_type(t) |
||||
{} |
||||
|
||||
IRCPrefix& IRCPrefix::operator=(const IRCPrefix& other) |
||||
{ |
||||
m_servername = other.m_servername; |
||||
m_nickname = other.m_nickname; |
||||
m_user = other.m_user; |
||||
m_host = other.m_host; |
||||
m_type = other.m_type; |
||||
return *this; |
||||
} |
||||
|
||||
const std::string& IRCPrefix::toString() const |
||||
{ |
||||
if (m_type == Type::server) |
||||
return m_servername; |
||||
else |
||||
return m_nickname; |
||||
} |
||||
|
||||
const std::string& IRCPrefix::servername() const |
||||
{ |
||||
return m_servername; |
||||
} |
||||
|
||||
const std::string& IRCPrefix::nickname() const |
||||
{ |
||||
return m_nickname; |
||||
} |
||||
|
||||
const std::string& IRCPrefix::user() const |
||||
{ |
||||
return m_user; |
||||
} |
||||
|
||||
const std::string& IRCPrefix::host() const |
||||
{ |
||||
return m_host; |
||||
} |
||||
|
||||
void IRCPrefix::setNickname(const std::string& nickname) |
||||
{ |
||||
m_nickname = nickname; |
||||
} |
||||
|
||||
void IRCPrefix::setHost(const std::string& host) |
||||
{ |
||||
m_host = host; |
||||
} |
||||
|
||||
std::string IRCPrefix::composite() const |
||||
{ |
||||
if (m_type == Type::server) |
||||
return m_servername; |
||||
else |
||||
return fmt::format("{}!{}@{}", m_nickname, m_user, m_host); |
||||
} |
||||
|
||||
IRCPrefix::Type IRCPrefix::type() const |
||||
{ |
||||
return m_type; |
||||
} |
||||
|
||||
IRCPrefix IRCPrefix::fromNickname(const std::string& nickname) |
||||
{ |
||||
IRCPrefix p(Type::user); |
||||
p.m_nickname = nickname; |
||||
return p; |
||||
} |
@ -0,0 +1,52 @@ |
||||
#ifndef IRCPREFIX_H |
||||
#define IRCPREFIX_H |
||||
|
||||
#include <string> |
||||
|
||||
class IRCPrefix |
||||
{ |
||||
public: |
||||
explicit IRCPrefix(const std::string& prefix); |
||||
IRCPrefix(const IRCPrefix& other); |
||||
IRCPrefix(IRCPrefix&& other) noexcept; |
||||
IRCPrefix() = delete; |
||||
~IRCPrefix() = default; |
||||
|
||||
IRCPrefix& operator=(const IRCPrefix& other); |
||||
|
||||
[[nodiscard]] const std::string& toString() const; //!< Returns either a servername or nickname.
|
||||
|
||||
[[nodiscard]] const std::string& servername() const; |
||||
[[nodiscard]] const std::string& nickname() const; |
||||
[[nodiscard]] const std::string& user() const; |
||||
[[nodiscard]] const std::string& host() const; |
||||
|
||||
void setNickname(const std::string& nickname); |
||||
void setHost(const std::string& host); |
||||
|
||||
[[nodiscard]] std::string composite() const; |
||||
|
||||
enum class Type |
||||
{ |
||||
server, |
||||
user |
||||
}; |
||||
|
||||
[[nodiscard]] Type type() const; |
||||
|
||||
[[nodiscard]] static IRCPrefix fromNickname(const std::string& nickname); |
||||
|
||||
private: |
||||
explicit IRCPrefix(Type t); |
||||
|
||||
// Used for :irc.server.name prefix
|
||||
std::string m_servername; |
||||
|
||||
/* Used for :nickname!user@host prefix */ |
||||
std::string m_nickname; |
||||
std::string m_user; |
||||
std::string m_host; |
||||
|
||||
Type m_type; |
||||
}; |
||||
#endif // IRCPREFIX_H
|
@ -0,0 +1,175 @@ |
||||
#ifndef NUMERICS_H |
||||
#define NUMERICS_H |
||||
|
||||
namespace Numeric { |
||||
|
||||
/*
|
||||
* Error reply numerics |
||||
*/ |
||||
constexpr auto* ERR_NOSUCHNICK = "401"; |
||||
constexpr auto* ERR_NOSUCHSERVER = "402"; |
||||
constexpr auto* ERR_NOSUCHCHANNEL = "403"; |
||||
constexpr auto* ERR_CANNOTSENDTOCHAN = "404"; |
||||
constexpr auto* ERR_TOOMANYCHANNELS = "405"; |
||||
constexpr auto* ERR_WASNOSUCHNICK = "406"; |
||||
constexpr auto* ERR_TOOMANYTARGETS = "407"; |
||||
constexpr auto* ERR_NOORIGIN = "409"; |
||||
constexpr auto* ERR_NORECIPIENT = "411"; |
||||
constexpr auto* ERR_NOTEXTTOSEND = "412"; |
||||
constexpr auto* ERR_NOTOPLEVEL = "413"; |
||||
constexpr auto* ERR_WILDTOPLEVEL = "414"; |
||||
constexpr auto* ERR_UNKNOWNCOMMAND = "421"; |
||||
constexpr auto* ERR_NOMOTD = "422"; |
||||
constexpr auto* ERR_NOADMININFO = "423"; |
||||
constexpr auto* ERR_FILEERROR = "424"; |
||||
constexpr auto* ERR_NONICKNAMEGIVEN = "431"; |
||||
constexpr auto* ERR_ERRORNEUSNICKNAME = "432"; |
||||
constexpr auto* ERR_NICKNAMEINUSE = "433"; |
||||
constexpr auto* ERR_NICKCOLLISION = "436"; |
||||
constexpr auto* ERR_USERNOTINCHANNEL = "441"; |
||||
constexpr auto* ERR_NOTONCHANNEL = "442"; |
||||
constexpr auto* ERR_USERONCHANNEL = "443"; |
||||
constexpr auto* ERR_NOLOGIN = "444"; |
||||
constexpr auto* ERR_SUMMONDISABLED = "445"; |
||||
constexpr auto* ERR_USERSDISABLED = "446"; |
||||
constexpr auto* ERR_NOTREGISTERED = "451"; |
||||
constexpr auto* ERR_NEEDMOREPARAMS = "461"; |
||||
constexpr auto* ERR_ALREADYREGISTERED = "462"; |
||||
constexpr auto* ERR_NOPERMFORHOST = "463"; |
||||
constexpr auto* ERR_PASSWDMISMATCH = "464"; |
||||
constexpr auto* ERR_YOUREBANNEDCREEP = "465"; |
||||
constexpr auto* ERR_KEYSET = "467"; |
||||
constexpr auto* ERR_CHANNELISFULL = "471"; |
||||
constexpr auto* ERR_UNKNOWNMODE = "472"; |
||||
constexpr auto* ERR_INVITEONLYCHAN = "473"; |
||||
constexpr auto* ERR_BANNEDFROMCHAN = "474"; |
||||
constexpr auto* ERR_BADCHANNELKEY = "475"; |
||||
constexpr auto* ERR_NOPRIVILEGES = "481"; |
||||
constexpr auto* ERR_CHANOPRIVSNEEDED = "482"; |
||||
constexpr auto* ERR_CANTKILLSERVER = "483"; |
||||
constexpr auto* ERR_NOOPERHOST = "491"; |
||||
constexpr auto* ERR_UMODEUNKNOWNFLAG = "501"; |
||||
constexpr auto* ERR_USERSDONTMATCH = "502"; |
||||
|
||||
/*
|
||||
* Command reply numerics |
||||
*/ |
||||
constexpr auto* RPL_NONE = "300"; |
||||
constexpr auto* RPL_USERHOST = "302"; |
||||
constexpr auto* RPL_ISON = "303"; |
||||
constexpr auto* RPL_AWAY = "301"; |
||||
constexpr auto* RPL_UNAWAY = "305"; |
||||
constexpr auto* RPL_NOWAWAY = "306"; |
||||
constexpr auto* RPL_WHOISUSER = "311"; |
||||
constexpr auto* RPL_WHOISSERVER = "312"; |
||||
constexpr auto* RPL_WHOISOPERATOR = "313"; |
||||
constexpr auto* RPL_WHOISIDLE = "317"; |
||||
constexpr auto* RPL_ENDOFWHOIS = "318"; |
||||
constexpr auto* RPL_WHOISCHANNELS = "319"; |
||||
constexpr auto* RPL_WHOWASUSER = "314"; |
||||
constexpr auto* RPL_ENDOFWHOWAS = "369"; |
||||
constexpr auto* RPL_LISTSTART = "321"; |
||||
constexpr auto* RPL_LIST = "322"; |
||||
constexpr auto* RPL_LISTEND = "323"; |
||||
constexpr auto* RPL_CHANNELMODEIS = "324"; |
||||
constexpr auto* RPL_NOTOPIC = "331"; |
||||
constexpr auto* RPL_TOPIC = "332"; |
||||
constexpr auto* RPL_INVITING = "341"; |
||||
constexpr auto* RPL_SUMMONING = "342"; |
||||
constexpr auto* RPL_VERSION = "351"; |
||||
constexpr auto* RPL_WHOREPLY = "352"; |
||||
constexpr auto* RPL_ENDOFWHO = "315"; |
||||
constexpr auto* RPL_NAMREPLY = "353"; |
||||
constexpr auto* RPL_ENDOFNAMES = "366"; |
||||
constexpr auto* RPL_LINKS = "364"; |
||||
constexpr auto* RPL_ENDOFLINKS = "365"; |
||||
constexpr auto* RPL_BANLIST = "367"; |
||||
constexpr auto* RPL_ENDOFBANLIST = "368"; |
||||
constexpr auto* RPL_INFO = "371"; |
||||
constexpr auto* RPL_ENDOFINFO = "374"; |
||||
constexpr auto* RPL_MOTDSTART = "375"; |
||||
constexpr auto* RPL_MOTD = "372"; |
||||
constexpr auto* RPL_ENDOFMOTD = "376"; |
||||
constexpr auto* RPL_YOUREOPER = "381"; |
||||
constexpr auto* RPL_REHASHING = "382"; |
||||
constexpr auto* RPL_TIME = "391"; |
||||
constexpr auto* RPL_USERSSTART = "392"; |
||||
constexpr auto* RPL_USERS = "393"; |
||||
constexpr auto* RPL_ENDOFUSERS = "394"; |
||||
constexpr auto* RPL_NOUSERS = "395"; |
||||
constexpr auto* RPL_TRACELINK = "200"; |
||||
constexpr auto* RPL_TRACECONNECTING = "201"; |
||||
constexpr auto* RPL_TRACEHANDSHAKE = "202"; |
||||
constexpr auto* RPL_TRACEUNKNOWN = "203"; |
||||
constexpr auto* RPL_TRACEOPERATOR = "204"; |
||||
constexpr auto* RPL_TRACEUSER = "205"; |
||||
constexpr auto* RPL_TRACESERVER = "206"; |
||||
constexpr auto* RPL_TRACENEWTYPE = "208"; |
||||
constexpr auto* RPL_TRACELOG = "261"; |
||||
constexpr auto* RPL_STATSLINKINFO = "211"; |
||||
constexpr auto* RPL_STATSCOMMANDS = "212"; |
||||
constexpr auto* RPL_STATSCLINE = "213"; |
||||
constexpr auto* RPL_STATSNLINE = "214"; |
||||
constexpr auto* RPL_STATSILINE = "215"; |
||||
constexpr auto* RPL_STATSKLINE = "216"; |
||||
constexpr auto* RPL_STATSYLINE = "218"; |
||||
constexpr auto* RPL_ENDOFSTATS = "219"; |
||||
constexpr auto* RPL_STATSLLINE = "241"; |
||||
constexpr auto* RPL_STATSUPTIME = "242"; |
||||
constexpr auto* RPL_STATSOLINE = "243"; |
||||
constexpr auto* RPL_STATSHLINE = "244"; |
||||
constexpr auto* RPL_UMODEIS = "221"; |
||||
constexpr auto* RPL_LUSERCLIENT = "251"; |
||||
constexpr auto* RPL_LUSEROP = "252"; |
||||
constexpr auto* RPL_LUSERUNKNOWN = "253"; |
||||
constexpr auto* RPL_LUSERCHANNELS = "254"; |
||||
constexpr auto* RPL_LUSERME = "255"; |
||||
constexpr auto* RPL_ADMINME = "256"; |
||||
constexpr auto* RPL_ADMINLOC1 = "257"; |
||||
constexpr auto* RPL_ADMINLOC2 = "258"; |
||||
constexpr auto* RPL_ADMINEMAIL = "259"; |
||||
constexpr auto* RPL_INVITELIST = "346"; |
||||
constexpr auto* RPL_ENDOFINVITELIST = "347"; |
||||
constexpr auto* RPL_EXCEPTLIST = "348"; |
||||
constexpr auto* RPL_ENDOFEXCEPTLIST = "349"; |
||||
|
||||
/*
|
||||
* Reserved reply numerics |
||||
* |
||||
* From RFC1459: These numerics are not described above since they fall into |
||||
* one of the following categories: \n |
||||
* 1. no longer in use \n |
||||
* 2. reserved for future planned use \n |
||||
* 3. in current use but are part of a non-generic 'feature' of the current IRC server. |
||||
*/ |
||||
constexpr auto* RPL_WELCOME = "001"; |
||||
constexpr auto* RPL_MYINFO = "004"; |
||||
constexpr auto* RPL_ISUPPORT = "005"; |
||||
constexpr auto* RPL_TRACECLASS = "209"; |
||||
constexpr auto* RPL_SERVICEINFO = "231"; |
||||
constexpr auto* RPL_SERVICE = "233"; |
||||
constexpr auto* RPL_SERVLISTEND = "235"; |
||||
constexpr auto* RPL_WHOISCHANOP = "316"; |
||||
constexpr auto* RPL_CLOSING = "362"; |
||||
constexpr auto* RPL_INFOSTART = "373"; |
||||
constexpr auto* ERR_YOUWILLBEBANNED = "466"; |
||||
constexpr auto* ERR_NOSERVICEHOST = "492"; |
||||
constexpr auto* RPL_STATSQLINE = "217"; |
||||
constexpr auto* RPL_ENDOFSERVICES = "232"; |
||||
constexpr auto* RPL_SERVLIST = "234"; |
||||
constexpr auto* RPL_KILLDONE = "361"; |
||||
constexpr auto* RPL_CLOSEEND = "363"; |
||||
constexpr auto* RPL_MYPORTIS = "384"; |
||||
constexpr auto* RPL_BADCHANMASK = "476"; |
||||
constexpr auto* RPL_CREATION = "329"; |
||||
constexpr auto* RPL_TOPICBY = "333"; |
||||
constexpr auto* RPL_WHOISACTUALHOST = "378"; |
||||
constexpr auto* RPL_WHOISMODES = "379"; |
||||
constexpr auto* RPL_WHOISIDENTIFIED = "307"; |
||||
constexpr auto* RPL_WHOISLOGGEDIN = "330"; |
||||
constexpr auto* RPL_WHOISHELP = "310"; |
||||
constexpr auto* RPL_DISPLAYEDHOST = "396"; |
||||
|
||||
} // namespace Numeric
|
||||
|
||||
#endif // NUMERICS_H
|
@ -0,0 +1,104 @@ |
||||
#include "Utilities.h" |
||||
#include <fmt/format.h> |
||||
#include <sstream> |
||||
|
||||
std::pair<std::string,std::string> FormatCTCPLine(std::string line) |
||||
{ |
||||
/* Clean away the CTCP byte flags */ |
||||
if (line[0] == CTCPflag) |
||||
line.erase(line.begin()); |
||||
|
||||
auto endIt = std::find(line.begin(), line.end(), CTCPflag); |
||||
if (endIt != line.end()) |
||||
line.pop_back(); |
||||
|
||||
auto cmdIt = std::find(line.begin(), line.end(), ' '); |
||||
std::string cmd(line.begin(), cmdIt); |
||||
std::string ctcpMsg; |
||||
if (cmdIt != line.end()) |
||||
ctcpMsg = std::string(cmdIt + 1, line.end()); |
||||
|
||||
return std::make_pair(cmd, ctcpMsg); |
||||
} |
||||
|
||||
/*!
|
||||
* @brief Simple wildcard match, supporting only a single wildcard. |
||||
* @returns true if string 'str' matches with the wildcard string 'wc'. |
||||
*/ |
||||
bool singleWildcardMatch(const std::string& str, const std::string& wc) |
||||
{ |
||||
if (str.empty() || wc.empty()) |
||||
return false; |
||||
|
||||
auto wcit = std::find(wc.begin(), wc.end(), '*'); |
||||
if (wcit == wc.end()) |
||||
return str == wc; |
||||
|
||||
/*
|
||||
* Since we only support a single wildcard we only need to match the left side and right side |
||||
* of the asterisk (wildcard) against the target string 'str'. |
||||
* Also if the "end" of left match is equal to the "begin" of right match, it's a mismatch. |
||||
*/ |
||||
|
||||
bool hasLeft, hasRight { false }; |
||||
auto leftEnd = str.end(); |
||||
auto rightBegin = str.end(); |
||||
|
||||
/* Left side */ |
||||
if (wcit != wc.begin()) { |
||||
std::string wildls(wc.begin(), wcit); |
||||
auto strmatch = str.substr(0, wildls.length()); |
||||
if (wildls != strmatch) |
||||
return false; |
||||
leftEnd = str.begin() + wildls.length(); |
||||
hasLeft = true; |
||||
} |
||||
|
||||
/* Right side */ |
||||
if (wcit != wc.end() - 1) { |
||||
std::string wildrs(wcit + 1, wc.end()); |
||||
auto mpos = str.find(wildrs); |
||||
if (mpos == std::string::npos) |
||||
return false; |
||||
auto strmatch = str.substr(mpos); |
||||
if (wildrs != strmatch) |
||||
return false; |
||||
rightBegin = str.begin() + mpos; |
||||
hasRight = true; |
||||
} |
||||
|
||||
// Avoid matching "left*right" with "leftright"
|
||||
return !(hasLeft && hasRight && leftEnd == rightBegin); |
||||
} |
||||
|
||||
std::string longIpToNormal(const std::string& longip) |
||||
{ |
||||
try { |
||||
unsigned ip = std::stoul(longip); |
||||
unsigned a = (ip & 0xFF000000u) >> 24u; |
||||
unsigned b = (ip & 0x00FF0000u) >> 16u; |
||||
unsigned c = (ip & 0x0000FF00u) >> 8u; |
||||
unsigned d = ip & 0x000000FFu; |
||||
return fmt::format("{}.{}.{}.{}", a, b, c, d); |
||||
} |
||||
catch (...) { |
||||
return "0.0.0.0"; |
||||
} |
||||
} |
||||
|
||||
std::string normalIpToLong(const std::string& normalip) |
||||
{ |
||||
std::stringstream ss(normalip); |
||||
std::string p; |
||||
std::vector<unsigned> parts; |
||||
while (getline(ss, p, '.')) |
||||
parts.emplace_back(std::stoul(p)); |
||||
|
||||
uint32_t ret = parts[0]; |
||||
for (int i = 1; i < 4; ++i) { |
||||
ret <<= 8u; |
||||
ret |= parts[i]; |
||||
} |
||||
|
||||
return std::to_string(ret); |
||||
} |
@ -0,0 +1,14 @@ |
||||
#ifndef IRCUTILITIES_H |
||||
#define IRCUTILITIES_H |
||||
|
||||
#include <string> |
||||
#include <algorithm> |
||||
|
||||
constexpr char CTCPflag { 0x01 }; |
||||
|
||||
std::pair<std::string,std::string> FormatCTCPLine(std::string line); |
||||
bool singleWildcardMatch(const std::string& str, const std::string& wc); |
||||
std::string longIpToNormal(const std::string& longip); |
||||
std::string normalIpToLong(const std::string& normalip); |
||||
|
||||
#endif //IRCUTILITIES_H
|
@ -0,0 +1,62 @@ |
||||
/*
|
||||
* IdealIRC - Internet Relay Chat client |
||||
* Copyright (C) 2019 Tom-Andre Barstad |
||||
* |
||||
* This program is free software; you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation; either version 2 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License along |
||||
* with this program; if not, write to the Free Software Foundation, Inc., |
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
||||
*/ |
||||
|
||||
#include "IWin.h" |
||||
#include <QDebug> |
||||
|
||||
const QString IWin::InvalidWindowTypeString { "Invalid" }; |
||||
|
||||
std::unordered_map<IWin::Type,QString> IWin::TypeString = { |
||||
{ IWin::Type::Status, "Status" }, |
||||
{ IWin::Type::Channel, "Channel" }, |
||||
{ IWin::Type::Private, "Private" }, |
||||
{ IWin::Type::Custom, "Custom" } |
||||
}; |
||||
|
||||
IWin::Type IWin::getType() const |
||||
{ |
||||
return m_type; |
||||
} |
||||
|
||||
IWin* IWin::getStatusParent() |
||||
{ |
||||
return (m_type == Type::Status) ? this : m_parent; |
||||
} |
||||
|
||||
const QString& IWin::getButtonText() const |
||||
{ |
||||
return m_buttonText; |
||||
} |
||||
|
||||
void IWin::setButtonText(const QString& text) |
||||
{ |
||||
m_buttonText = text; |
||||
} |
||||
|
||||
IWin::IWin(Type type, IWin* parentWindow) |
||||
: m_type(type) |
||||
, m_parent(parentWindow) |
||||
{ |
||||
|
||||
} |
||||
|
||||
void IWin::closeEvent(QCloseEvent*) |
||||
{ |
||||
emit aboutToClose(this); |
||||
} |
@ -0,0 +1,70 @@ |
||||
/*
|
||||
* IdealIRC - Internet Relay Chat client |
||||
* Copyright (C) 2019 Tom-Andre Barstad |
||||
* |
||||
* This program is free software; you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation; either version 2 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License along |
||||
* with this program; if not, write to the Free Software Foundation, Inc., |
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
||||
*/ |
||||
|
||||
#ifndef IWIN_H |
||||
#define IWIN_H |
||||
|
||||
#include "Widgets/IIRCView.h" |
||||
#include <QWidget> |
||||
#include <unordered_map> |
||||
#include <QCloseEvent> |
||||
#include <QShowEvent> |
||||
|
||||
class IWin : public QWidget |
||||
{ |
||||
Q_OBJECT |
||||
|
||||
public: |
||||
enum class Type |
||||
{ |
||||
Undefined, |
||||
Status, |
||||
Channel, |
||||
Private, |
||||
Custom |
||||
}; |
||||
static const QString InvalidWindowTypeString; |
||||
static std::unordered_map<Type,QString> TypeString; |
||||
|
||||
Type getType() const; |
||||
IWin* getParent() const { return m_parent; } |
||||
IWin* getStatusParent(); |
||||
|
||||
virtual bool print(const PrintType ptype, const QString& text) = 0; |
||||
virtual void refreshWindowTitle() = 0; |
||||
|
||||
const QString& getButtonText() const; |
||||
void setButtonText(const QString& text); |
||||
bool operator==(const IWin& other); |
||||
|
||||
protected: |
||||
explicit IWin(Type type, IWin* parentWindow = nullptr); |
||||
void closeEvent(QCloseEvent *) override; |
||||
|
||||
private: |
||||
Type m_type; |
||||
QString m_buttonText; |
||||
IWin* m_parent{ nullptr }; |
||||
bool m_firstShow{ true }; |
||||
|
||||
signals: |
||||
void aboutToClose(IWin* who); |
||||
}; |
||||
|
||||
#endif // IWIN_H
|
@ -0,0 +1,219 @@ |
||||
/*
|
||||
* IdealIRC - Internet Relay Chat client |
||||
* Copyright (C) 2019 Tom-Andre Barstad |
||||
* |
||||
* This program is free software; you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation; either version 2 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License along |
||||
* with this program; if not, write to the Free Software Foundation, Inc., |
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
||||
*/ |
||||
|
||||
#include "IWinChannel.h" |
||||
#include "IWinStatus.h" |
||||
#include "MdiManager.h" |
||||
#include "Commands.h" |
||||
#include "Script/Manager.h" |
||||
#include "IRCClient/IRCChannel.h" |
||||
#include <ConfigMgr.h> |
||||
#include <QHeaderView> |
||||
|
||||
IWinChannel::IWinChannel(IWinStatus* statusParent, const QString& channelName) |
||||
: IWin(IWin::Type::Channel, statusParent) |
||||
, status(statusParent) |
||||
, connection(statusParent->getConnection()) |
||||
{ |
||||
setButtonText(channelName); |
||||
|
||||
splitter = new QSplitter(); |
||||
v_layout = new QVBoxLayout(); |
||||
view = new IIRCView(); |
||||
listbox = new IListWidget(); |
||||
input = new ILineEdit(); |
||||
|
||||
v_layout->setMargin(0); |
||||
v_layout->setSpacing(2); |
||||
|
||||
splitter->addWidget(view); |
||||
splitter->addWidget(listbox); |
||||
v_layout->addWidget(splitter); |
||||
v_layout->addWidget(input); |
||||
setLayout(v_layout); |
||||
setTabOrder(input, view); |
||||
setTabOrder(view, listbox); |
||||
splitter->setStretchFactor(0, 8); |
||||
splitter->setStretchFactor(1, 1); |
||||
|
||||
view->setFocusPolicy(Qt::FocusPolicy::NoFocus); |
||||
listbox->setFocusPolicy(Qt::FocusPolicy::NoFocus); |
||||
listbox->setContextMenuPolicy(Qt::ContextMenuPolicy::CustomContextMenu); |
||||
listbox->setSelectionMode(QAbstractItemView::SelectionMode::ExtendedSelection); |
||||
|
||||
|
||||
|
||||
nlControl = new NicklistController(listbox, connection, channelName, this); |
||||
connect(&connection, &IRC::memberListReloaded, |
||||
this, &IWinChannel::memberListReloaded); |
||||
|
||||
connect(&connection, &IRC::memberListClearedForAll, |
||||
this, &IWinChannel::memberListCleared); |
||||
|
||||
connect(&connection, &IRC::memberAdded, |
||||
this, &IWinChannel::memberListAdd); |
||||
|
||||
connect(&connection, &IRC::memberChanged, |
||||
this, &IWinChannel::memberListUpdateMember); |
||||
|
||||
connect(&connection, &IRC::memberRemoved, |
||||
this, &IWinChannel::memberListRemoveMember); |
||||
|
||||
connect(input, &ILineEdit::returnPressed, |
||||
this, &IWinChannel::inputEnter); |
||||
|
||||
connect(listbox, &IListWidget::itemDoubleClicked, |
||||
this, &IWinChannel::listboxDoubleclick); |
||||
|
||||
connect(view, &IIRCView::customContextMenuRequested, [this,channelName](const QPoint& point){ |
||||
menuSymbols.clear(); |
||||
menuSymbols.set("channel", channelName.toStdString()); |
||||
ScriptManager::instance()->contextMenuPopup(ScriptMenuType::Channel, mapToGlobal(point), menuSymbols); |
||||
}); |
||||
|
||||
connect(listbox, &IListWidget::customContextMenuRequested, [this,channelName](const QPoint& pos){ |
||||
menuSymbols.clear(); |
||||
menuSymbols.set("channel", channelName.toStdString()); |
||||
|
||||
ValueArray members; |
||||
auto items = listbox->selectedItems(); |
||||
if (items.isEmpty()) return; |
||||
|
||||
int idx = 0; |
||||
for (auto* item : items) { |
||||
QString name = item->text(); |
||||
if (isUserModePrefix(name[0])) |
||||
name.remove(0, 1); |
||||
members.emplace(std::to_string(idx++), new ValueHolder(name.toStdString())); |
||||
} |
||||
menuSymbols.set("selected", std::move(members)); |
||||
|
||||
QPoint posAdj = mapToGlobal(pos); |
||||
posAdj.setX(posAdj.x() + view->width() + splitter->handleWidth()); |
||||
ScriptManager::instance()->contextMenuPopup(ScriptMenuType::Memberlist, posAdj, menuSymbols); |
||||
}); |
||||
} |
||||
|
||||
bool IWinChannel::print(const PrintType ptype, const QString& text) |
||||
{ |
||||
view->print(ptype, text); |
||||
ConfigMgr& conf = ConfigMgr::instance(); |
||||
if (conf.logging("Channels") == "1") { |
||||
QString path = conf.logging("Path"); |
||||
if (path.isEmpty()) |
||||
return true; |
||||
path += "/" + getButtonText() + ".log"; |
||||
QByteArray out; |
||||
out.append(IIRCView::formatType(ptype, text)); |
||||
out.append("\n"); |
||||
QFile f(path); |
||||
if (!f.open(QIODevice::WriteOnly | QIODevice::Append)) |
||||
return true; |
||||
f.write(out); |
||||
f.close(); |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
void IWinChannel::refreshWindowTitle() |
||||
{ |
||||
auto chan = connection.getChannel(getButtonText().toStdString()); |
||||
const auto& topic = chan->topic(); |
||||
|
||||
QString title; |
||||
if (topic.empty()) |
||||
title = getButtonText(); |
||||
else |
||||
title = QStringLiteral("%1 (%2)") |
||||
.arg(getButtonText()) |
||||
.arg(topic.c_str()); |
||||
|
||||
setWindowTitle(title); |
||||
} |
||||
|
||||
void IWinChannel::resetNicklist() |
||||
{ |
||||
nlControl->clear(); |
||||
} |
||||
|
||||
void IWinChannel::inputEnter() |
||||
{ |
||||
QString text = input->text(); |
||||
input->clear(); |
||||
|
||||
InputHandler inHndl(connection); |
||||
inHndl.parse(*this, text); |
||||
} |
||||
|
||||
void IWinChannel::listboxDoubleclick(QListWidgetItem* item) |
||||
{ |
||||
if (!item) |
||||
return; |
||||
|
||||
QString nickname = item->text(); |
||||
if (isUserModePrefix(nickname[0])) |
||||
nickname = nickname.mid(1); |
||||
|
||||
input->setFocus(); |
||||
IWin* subwin = status->createPrivateWindow(nickname); |
||||
subwin->refreshWindowTitle(); |
||||
} |
||||
|
||||
bool IWinChannel::isUserModePrefix(QChar c) |
||||
{ |
||||
const auto& isupport = connection.isupport(); |
||||
const auto& prefix = isupport.at("PREFIX"); // Always present in the isupport map.
|
||||
auto begin = std::find(prefix.begin(), prefix.end(), ')') + 1; |
||||
return std::find(begin, prefix.end(), c.toLatin1()) != prefix.end(); |
||||
} |
||||
|
||||
void IWinChannel::closeEvent(QCloseEvent* evt) |
||||
{ |
||||
connection.command(Command::IRC::PART, { getButtonText().toStdString() }, ""); |
||||
|
||||
IWin::closeEvent(evt); |
||||
} |
||||
|
||||
void IWinChannel::memberListReloaded(const QString& channel) |
||||
{ |
||||
if (channel != getButtonText()) return; |
||||
nlControl->reload(); |
||||
} |
||||
|
||||
void IWinChannel::memberListCleared() |
||||
{ |
||||
nlControl->clear(); |
||||
} |
||||
|
||||
void IWinChannel::memberListAdd(const QString& channel, const IRCMemberEntry& member) |
||||
{ |
||||
if (channel != getButtonText()) return; |
||||
nlControl->addMember(member); |
||||
} |
||||
|
||||
void IWinChannel::memberListUpdateMember(const QString& nickname, const IRCMemberEntry& member) |
||||
{ |
||||
nlControl->updateMember(nickname, member); |
||||
} |
||||
|
||||
void IWinChannel::memberListRemoveMember(const QString& channel, const IRCMemberEntry& member) |
||||
{ |
||||
if (channel != getButtonText()) return; |
||||
nlControl->removeMember(member); |
||||
} |
@ -0,0 +1,76 @@ |
||||
/*
|
||||
* IdealIRC - Internet Relay Chat client |
||||
* Copyright (C) 2019 Tom-Andre Barstad |
||||
* |
||||
* This program is free software; you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation; either version 2 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License along |
||||
* with this program; if not, write to the Free Software Foundation, Inc., |
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
||||
*/ |
||||
|
||||
#ifndef IWINCHANNEL_H |
||||
#define IWINCHANNEL_H |
||||
|
||||
#include "IWin.h" |
||||
#include "NicklistController.h" |
||||
#include "Widgets/ILineEdit.h" |
||||
#include "Widgets/IListWidget.h" |
||||
#include "Script/SymbolScope.h" |
||||
#include <QSplitter> |
||||
#include <QVBoxLayout> |
||||
#include <QListWidget> |
||||
#include <QTableView> |
||||
#include <QCloseEvent> |
||||
#include <memory> |
||||
|
||||
class IWinStatus; |
||||
|
||||
class IWinChannel : public IWin |
||||
{ |
||||
Q_OBJECT |
||||
|
||||
public: |
||||
IWinChannel(IWinStatus* statusParent, const QString& channelName); |
||||
|
||||
bool print(const PrintType ptype, const QString& text) override; |
||||
void refreshWindowTitle() override; |
||||
void resetNicklist(); |
||||
|
||||
private: |
||||
void inputEnter(); |
||||
void listboxDoubleclick(QListWidgetItem* item); |
||||
bool isUserModePrefix(QChar c); //! Returns true if the passed character is a prefix like @ + etc
|
||||
|
||||
void closeEvent(QCloseEvent* evt) override; |
||||
|
||||
QSplitter* splitter; |
||||
QVBoxLayout* v_layout; |
||||
IIRCView* view; |
||||
IListWidget* listbox; |
||||
ILineEdit* input; |
||||
|
||||
NicklistController* nlControl; |
||||
|
||||
IWinStatus* status; |
||||
IRC& connection; |
||||
|
||||
SymbolScope menuSymbols; |
||||
|
||||
private slots: |
||||
void memberListReloaded(const QString& channel); |
||||
void memberListCleared(); |
||||
void memberListAdd(const QString& channel, const IRCMemberEntry& member); |
||||
void memberListUpdateMember(const QString& nickname, const IRCMemberEntry& member); |
||||
void memberListRemoveMember(const QString& channel, const IRCMemberEntry& member); |
||||
}; |
||||
|
||||
#endif // IWINCHANNEL_H
|
@ -0,0 +1,101 @@ |
||||
/*
|
||||
* IdealIRC - Internet Relay Chat client |
||||
* Copyright (C) 2019 Tom-Andre Barstad |
||||
* |
||||
* This program is free software; you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation; either version 2 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License along |
||||
* with this program; if not, write to the Free Software Foundation, Inc., |
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
||||
*/ |
||||
|
||||
#include "IWinPrivate.h" |
||||
#include "IWinStatus.h" |
||||
#include "Script/Manager.h" |
||||
#include "IRCClient/IRCMember.h" |
||||
#include <ConfigMgr.h> |
||||
|
||||
IWinPrivate::IWinPrivate(IWinStatus* statusParent, const QString& targetName) |
||||
: IWin(IWin::Type::Private, statusParent) |
||||
, status(statusParent) |
||||
, connection(statusParent->getConnection()) |
||||
{ |
||||
setButtonText(targetName); |
||||
|
||||
layout = new QVBoxLayout(); |
||||
view = new IIRCView(); |
||||
input = new ILineEdit(); |
||||
|
||||
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::returnPressed, |
||||
this, &IWinPrivate::inputEnter); |
||||
|
||||
connect(view, &IIRCView::customContextMenuRequested, [this](const QPoint& point){ |
||||
menuSymbols.set("nickname", getButtonText().toStdString()); |
||||
ScriptManager::instance()->contextMenuPopup(ScriptMenuType::Private, mapToGlobal(point), menuSymbols); |
||||
}); |
||||
} |
||||
|
||||
bool IWinPrivate::print(const PrintType ptype, const QString& text) |
||||
{ |
||||
view->print(ptype, text); |
||||
ConfigMgr& conf = ConfigMgr::instance(); |
||||
if (conf.logging("Privates") == "1") { |
||||
QString path = conf.logging("Path"); |
||||
if (path.isEmpty()) |
||||
return true; |
||||
path += "/" + getButtonText() + ".log"; |
||||
QByteArray out; |
||||
out.append(IIRCView::formatType(ptype, text)); |
||||
out.append("\n"); |
||||
QFile f(path); |
||||
if (!f.open(QIODevice::WriteOnly | QIODevice::Append)) |
||||
return true; |
||||
f.write(out); |
||||
f.close(); |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
void IWinPrivate::refreshWindowTitle() |
||||
{ |
||||
QString target = getButtonText(); |
||||
auto ptr = connection.getMember(target.toStdString()); |
||||
if (!ptr) |
||||
setWindowTitle(target); |
||||
else { |
||||
const auto& prefix = ptr->prefix(); |
||||
if (prefix.host().empty()) |
||||
setWindowTitle(target); |
||||
else |
||||
setWindowTitle(QStringLiteral("%1 (%2@%3)") |
||||
.arg(target) |
||||
.arg(prefix.user().c_str()) |
||||
.arg(prefix.host().c_str())); |
||||
} |
||||
} |
||||
|
||||
void IWinPrivate::inputEnter() |
||||
{ |
||||
QString text = input->text(); |
||||
input->clear(); |
||||
|
||||
InputHandler inHndl(connection); |
||||
inHndl.parse(*this, text); |
||||
} |
@ -0,0 +1,54 @@ |
||||
/*
|
||||
* IdealIRC - Internet Relay Chat client |
||||
* Copyright (C) 2019 Tom-Andre Barstad |
||||
* |
||||
* This program is free software; you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation; either version 2 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License along |
||||
* with this program; if not, write to the Free Software Foundation, Inc., |
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
||||
*/ |
||||
|
||||
#ifndef IWINPRIVATE_H |
||||
#define IWINPRIVATE_H |
||||
|
||||
#include "IWin.h" |
||||
#include "Widgets/ILineEdit.h" |
||||
#include "Script/SymbolScope.h" |
||||
#include <QLineEdit> |
||||
#include <QVBoxLayout> |
||||
|
||||
class IWinStatus; |
||||
class IRC; |
||||
|
||||
class IWinPrivate : public IWin |
||||
{ |
||||
Q_OBJECT |
||||
|
||||
public: |
||||
IWinPrivate(IWinStatus* statusParent, const QString& targetName); |
||||
bool print(const PrintType ptype, const QString& text) override; |
||||
void refreshWindowTitle() override; |
||||
|
||||
private: |
||||
void inputEnter(); |
||||
|
||||
QVBoxLayout* layout; |
||||
IIRCView* view; |
||||
ILineEdit* input; |
||||
|
||||
IWinStatus* status; |
||||
IRC& connection; |
||||
|
||||
SymbolScope menuSymbols; |
||||
}; |
||||
|
||||
#endif // IWINPRIVATE_H
|
@ -0,0 +1,205 @@ |
||||
/*
|
||||
* IdealIRC - Internet Relay Chat client |
||||
* Copyright (C) 2019 Tom-Andre Barstad |
||||
* |
||||
* This program is free software; you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation; either version 2 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License along |
||||
* with this program; if not, write to the Free Software Foundation, Inc., |
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
||||
*/ |
||||
|
||||
#include "IWinStatus.h" |
||||
#include "IWinChannel.h" |
||||
#include "MdiManager.h" |
||||
#include "Script/Manager.h" |
||||
#include "IRC.h" |
||||
#include <QMessageBox> |
||||
|
||||
int IWinStatus::StatusWindowCount = 0; |
||||
|
||||
IWinStatus::IWinStatus(const QString& buttonText) |
||||
: IWin(Type::Status) |
||||
, connection(*this) |
||||
{ |
||||
setButtonText(buttonText); |
||||
++StatusWindowCount; |
||||
|
||||
layout = new QVBoxLayout(); |
||||
view = new IIRCView(); |
||||
input = new ILineEdit(); |
||||
|
||||
static int connectionIdCount{ 0 }; |
||||
connect(view, &IIRCView::customContextMenuRequested, [this](const QPoint& point){ |
||||
menuSymbols.set("cid", ++connectionIdCount); |
||||
ScriptManager::instance()->contextMenuPopup(ScriptMenuType::Status, mapToGlobal(point), menuSymbols); |
||||
}); |
||||
|
||||
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::returnPressed, |
||||
this, &IWinStatus::inputEnter); |
||||
|
||||
connect(&connection, &IRC::connected, |
||||
&MdiManager::instance(), &MdiManager::connectionStateChange); |
||||
|
||||
connect(&connection, &IRC::disconnected, [this] { |
||||
disconnectedFromServer(); |
||||
MdiManager::instance().connectionStateChange(); |
||||
}); |
||||
|
||||
Qt_Eventloop_Hook(); |
||||
} |
||||
|
||||
bool IWinStatus::print(const PrintType ptype, const QString& text) |
||||
{ |
||||
view->print(ptype, text); |
||||
return true; |
||||
} |
||||
|
||||
void IWinStatus::refreshWindowTitle() |
||||
{ |
||||
const auto& isupport = connection.isupport(); |
||||
const auto it = isupport.find("NETWORK"); |
||||
|
||||
if (it == isupport.end()) |
||||
setWindowTitle(QStringLiteral("Status")); |
||||
else |
||||
setWindowTitle(QStringLiteral("Status (%1)") |
||||
.arg( it->second.c_str() )); |
||||
} |
||||
|
||||
void IWinStatus::printTo(const QString& target, const PrintType ptype, const QString& text) |
||||
{ |
||||
MdiManager::instance().print(this, target, ptype, text); |
||||
} |
||||
|
||||
void IWinStatus::printToTypes(const IWin::Type toType, const PrintType ptype, const QString& text) |
||||
{ |
||||
MdiManager::instance().printToTypes(this, toType, ptype, text); |
||||
} |
||||
|
||||
void IWinStatus::printToActive(const PrintType ptype, const QString& text) |
||||
{ |
||||
MdiManager::instance().printToActive(this, ptype, text); |
||||
} |
||||
|
||||
void IWinStatus::printToAll(const PrintType ptype, const QString& text) |
||||
{ |
||||
MdiManager::instance().printToAll(this, ptype, text); |
||||
} |
||||
|
||||
IWinChannel* IWinStatus::createChannelWindow(const QString& name) |
||||
{ |
||||
auto& mdiManager = MdiManager::instance(); |
||||
IWin* subwinBase = mdiManager.findWindow(this, name); |
||||
if (!subwinBase) |
||||
subwinBase = mdiManager.createSubwindow(this, name, IWin::Type::Channel); |
||||
|
||||
return dynamic_cast<IWinChannel*>(subwinBase); |
||||
} |
||||
|
||||
IWin* IWinStatus::createPrivateWindow(const QString& target, bool setFocus) |
||||
{ |
||||
auto& mdiManager = MdiManager::instance(); |
||||
IWin* subwinBase = mdiManager.findWindow(this, target); |
||||
if (!subwinBase) |
||||
subwinBase = mdiManager.createSubwindow(this, target, IWin::Type::Private, setFocus); |
||||
|
||||
return subwinBase; |
||||
} |
||||
|
||||
IWin* IWinStatus::getActiveWindow() |
||||
{ |
||||
auto& mdiManager = MdiManager::instance(); |
||||
if (mdiManager.currentWindow()->getStatusParent() != this) |
||||
return this; |
||||
return mdiManager.currentWindow(); |
||||
} |
||||
|
||||
QList<IWin*> IWinStatus::subWindows() const |
||||
{ |
||||
return MdiManager::instance().childrenOf(this); |
||||
} |
||||
|
||||
void IWinStatus::closeEvent(QCloseEvent* evt) |
||||
{ |
||||
auto& mdiManager = MdiManager::instance(); |
||||
|
||||
if (StatusWindowCount == 1 || |
||||
(connection.isConnected() && QMessageBox::question(this, tr("Close status window"), tr("This status window has a connection. Close?")) |
||||
== QMessageBox::No)) { |
||||
evt->ignore(); |
||||
return; |
||||
} |
||||
|
||||
if (connection.isConnected()) { |
||||
connect(&connection, &IRC::disconnected, [&] { closeAllChildren(); mdiManager.toMdiwin(this)->close(); }); |
||||
connection.disconnectFromServer(); |
||||
evt->ignore(); |
||||
return; |
||||
} |
||||
|
||||
--StatusWindowCount; |
||||
|
||||
IWin::closeEvent(evt); |
||||
} |
||||
|
||||
void IWinStatus::inputEnter() |
||||
{ |
||||
QString text = input->text(); |
||||
input->clear(); |
||||
|
||||
InputHandler inHndl(connection); |
||||
inHndl.parse(*this, text); |
||||
} |
||||
|
||||
void IWinStatus::closeAllChildren() |
||||
{ |
||||
auto& mdiManager = MdiManager::instance(); |
||||
QList<QMdiSubWindow*> subwinList = mdiManager.mdiChildrenOf(this); |
||||
for (QMdiSubWindow* subwin : subwinList) |
||||
subwin->close(); |
||||
} |
||||
|
||||
void IWinStatus::Qt_Eventloop_Hook() |
||||
{ |
||||
const int nextInterval = connection.poll() ? 0 : 10; |
||||
QTimer::singleShot(nextInterval, this, std::bind(&IWinStatus::Qt_Eventloop_Hook, this)); |
||||
} |
||||
|
||||
void IWinStatus::disconnectedFromServer() |
||||
{ |
||||
const auto printDisconnected = [this](IWin* sw){ |
||||
sw->print(PrintType::ProgramInfo, tr("Disconnected")); |
||||
}; |
||||
|
||||
printDisconnected(this); |
||||
|
||||
auto subwins = subWindows(); |
||||
for (auto* sw : subwins) { |
||||
if (sw->getType() == IWin::Type::Channel) { |
||||
auto* chan = dynamic_cast<IWinChannel*>(sw); |
||||
chan->resetNicklist(); |
||||
printDisconnected(chan); |
||||
} |
||||
else if (sw->getType() == IWin::Type::Private) { |
||||
printDisconnected(sw); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,72 @@ |
||||
/*
|
||||
* IdealIRC - Internet Relay Chat client |
||||
* Copyright (C) 2019 Tom-Andre Barstad |
||||
* |
||||
* This program is free software; you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation; either version 2 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License along |
||||
* with this program; if not, write to the Free Software Foundation, Inc., |
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
||||
*/ |
||||
|
||||
#ifndef IWINSTATUS_H |
||||
#define IWINSTATUS_H |
||||
|
||||
#include "IWin.h" |
||||
#include "IRC.h" |
||||
#include "InputHandler.h" |
||||
#include "Widgets/ILineEdit.h" |
||||
#include "Script/SymbolScope.h" |
||||
#include <QLineEdit> |
||||
#include <QVBoxLayout> |
||||
#include <QTimer> |
||||
|
||||
class MdiManager; |
||||
class IWinChannel; |
||||
|
||||
class IWinStatus : public IWin |
||||
{ |
||||
Q_OBJECT |
||||
|
||||
public: |
||||
explicit IWinStatus(const QString& buttonText); |
||||
|
||||
bool print(const PrintType ptype, const QString& text) override; |
||||
void refreshWindowTitle() override; |
||||
void printTo(const QString& target, const PrintType ptype, const QString& text); |
||||
void printToTypes(const IWin::Type toType, const PrintType ptype, const QString& text); |
||||
void printToActive(const PrintType ptype, const QString& text); // TODO test
|
||||
void printToAll(const PrintType ptype, const QString& text); |
||||
IWinChannel* createChannelWindow(const QString& name); |
||||
IWin* createPrivateWindow(const QString& target, bool setFocus = true); |
||||
IWin* getActiveWindow(); |
||||
QList<IWin*> subWindows() const; |
||||
IRC& getConnection() { return connection; } |
||||
ILineEdit& getInputBox() { return *input; } |
||||
|
||||
private: |
||||
void closeEvent(QCloseEvent* evt) override; |
||||
void inputEnter(); |
||||
void closeAllChildren(); |
||||
void Qt_Eventloop_Hook(); |
||||
void disconnectedFromServer(); |
||||
|
||||
static int StatusWindowCount; |
||||
QVBoxLayout* layout; |
||||
IIRCView* view; |
||||
ILineEdit* input; |
||||
|
||||
IRC connection; |
||||
|
||||
SymbolScope menuSymbols; |
||||
}; |
||||
|
||||
#endif // IWINSTATUS_H
|
@ -0,0 +1,214 @@ |
||||
/*
|
||||
* IdealIRC - Internet Relay Chat client |
||||
* Copyright (C) 2019 Tom-Andre Barstad |
||||
* |
||||
* This program is free software; you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation; either version 2 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License along |
||||
* with this program; if not, write to the Free Software Foundation, Inc., |
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
||||
*/ |
||||
|
||||
#include "NicklistController.h" |
||||
#include "ConfigMgr.h" |
||||
#include "IRC.h" |
||||
#include "IRCClient/IRCChannel.h" |
||||
#include "IRCClient/IRCMember.h" |
||||
#include <algorithm> |
||||
#include <QDebug> |
||||
|
||||
NicklistController::NicklistController(QListWidget* listbox, IRC& server, const QString& channel, QObject* parent) |
||||
: QObject(parent) |
||||
, m_listbox(listbox) |
||||
, m_server(server) |
||||
, m_channel(channel) |
||||
{ |
||||
ConfigMgr& conf = ConfigMgr::instance(); |
||||
connect(&conf, &ConfigMgr::saved, this, &NicklistController::newConfiguration); |
||||
|
||||
/* Precalculate m_prefixOrdered */ |
||||
const auto& isupport = m_server.isupport(); |
||||
const auto& prefix = isupport.at("PREFIX"); // PREFIX is always present
|
||||
auto start = prefix.find_last_of(')'); |
||||
if (start != std::string::npos) { |
||||
++start; |
||||
m_prefixOrdered = prefix.substr(start).c_str(); |
||||
} |
||||
} |
||||
|
||||
void NicklistController::reload() |
||||
{ |
||||
m_listbox->clear(); |
||||
m_items.clear(); |
||||
|
||||
auto chan = m_server.getChannel(m_channel.toStdString()); |
||||
if (!chan) |
||||
return; |
||||
|
||||
const auto& members = chan->members(); |
||||
|
||||
for (const auto& mem : members) |
||||
m_items << makeItemText(mem); |
||||
|
||||
std::sort(m_items.begin(), m_items.end(), |
||||
[this](const QString& left, const QString& right) { |
||||
return lessThan(left, right); |
||||
}); |
||||
|
||||
m_listbox->addItems(m_items); |
||||
newConfiguration(); |
||||
} |
||||
|
||||
void NicklistController::clear() |
||||
{ |
||||
m_listbox->clear(); |
||||
} |
||||
|
||||
bool NicklistController::lessThan(const QString& left, const QString& right) |
||||
{ |
||||
auto li = m_prefixOrdered.indexOf(left[0]); |
||||
auto ri = m_prefixOrdered.indexOf(right[0]); |
||||
|
||||
/* Both sides got a prefix */ |
||||
if (li != -1 && ri != -1) { |
||||
if (left[0] == right[0]) |
||||
return left.mid(1).compare(right.mid(1), Qt::CaseInsensitive) < 0; |
||||
else |
||||
return li < ri; |
||||
} |
||||
|
||||
/* Neither side got a prefix */ |
||||
else if (li == -1 && ri == -1) |
||||
return left.compare(right, Qt::CaseInsensitive) < 0; |
||||
|
||||
/* One side got a prefix */ |
||||
else |
||||
return ri == -1; |
||||
} |
||||
|
||||
void NicklistController::addMember(const IRCMemberEntry& member) |
||||
{ |
||||
QString newItem = makeItemText(member); |
||||
int pos = findNewItemPosition(newItem); |
||||
m_items.insert(pos, newItem); |
||||
m_listbox->insertItem(pos, newItem); |
||||
|
||||
QListWidgetItem* item = m_listbox->item(pos); |
||||
updatePrefixColor(item, std::nullopt); |
||||
} |
||||
|
||||
void NicklistController::updateMember(const QString& nickname, const IRCMemberEntry& member) |
||||
{ |
||||
// Find position of the item in the list.
|
||||
// TODO use this loop also to find new item position.
|
||||
int currPos = -1; |
||||
for (int i = 0; i < m_items.count(); ++i) { |
||||
QString item = m_items[i]; |
||||
if (m_prefixOrdered.indexOf(item[0]) > -1) |
||||
item = item.mid(1); |
||||
if (item == nickname) { |
||||
currPos = i; |
||||
break; |
||||
} |
||||
} |
||||
|
||||
// We don't have this nickname in this channel, and it is safe to ignore it silently.
|
||||
// This is because when a nickname changes, that user might not be on all channels we're on.
|
||||
if (currPos == -1) return; |
||||
|
||||
QListWidgetItem* lbitem = m_listbox->item(currPos); |
||||
bool isSelected = lbitem->isSelected(); |
||||
m_items.removeAt(currPos); |
||||
m_listbox->removeItemWidget(lbitem); |
||||
delete lbitem; |
||||
|
||||
QString newItemText = makeItemText(member); |
||||
int newPos = findNewItemPosition(newItemText); |
||||
|
||||
lbitem = new QListWidgetItem(newItemText); |
||||
m_listbox->insertItem(newPos, lbitem); |
||||
m_items.insert(newPos, newItemText); |
||||
lbitem->setSelected(isSelected); |
||||
updatePrefixColor(lbitem, std::nullopt); |
||||
} |
||||
|
||||
void NicklistController::removeMember(const IRCMemberEntry& member) |
||||
{ |
||||
QString itemText = makeItemText(member); |
||||
int pos = 0; |
||||
int delpos = -1; |
||||
while (pos < m_items.count()) { |
||||
const QString& item = m_items[pos]; |
||||
if (item == itemText) { |
||||
delpos = pos; |
||||
break; |
||||
} |
||||
++pos; |
||||
} |
||||
|
||||
if (delpos == -1) { |
||||
qWarning() << "Got request to delete" << itemText << "from" << m_channel << "but it was not found!"; |
||||
return; |
||||
} |
||||
|
||||
m_items.removeAt(delpos); |
||||
QListWidgetItem* lbitem = m_listbox->item(delpos); |
||||
m_listbox->removeItemWidget(lbitem); |
||||
delete lbitem; |
||||
} |
||||
|
||||
QString NicklistController::makeItemText(const IRCMemberEntry& member) |
||||
{ |
||||
QString item; |
||||
auto modes = member.modes(); |
||||
if (!modes.empty()) { |
||||
auto prefixes = m_server.toMemberPrefix(modes).c_str(); |
||||
item += prefixes[0]; |
||||
} |
||||
item += member.member()->prefix().nickname().c_str(); |
||||
return item; |
||||
} |
||||
|
||||
int NicklistController::findNewItemPosition(const QString& text) |
||||
{ |
||||
int pos = 0; |
||||
while (pos < m_items.count()) { |
||||
const QString& item = m_items[pos]; |
||||
if (lessThan(text, item)) |
||||
break; |
||||
|
||||
++pos; |
||||
} |
||||
|
||||
return pos; |
||||
} |
||||
|
||||
void NicklistController::updatePrefixColor(QListWidgetItem* item, std::optional<QColor> color) |
||||
{ |
||||
if (!color && m_prefixOrdered.indexOf(item->text()[0]) == -1) |
||||
return; |
||||
|
||||
ConfigMgr& conf = ConfigMgr::instance(); |
||||
if (!color) { |
||||
color = conf.prefixColor(item->text()[0]); |
||||
if (!color) return; |
||||
} |
||||
item->setForeground(*color); |
||||
} |
||||
|
||||
void NicklistController::newConfiguration() |
||||
{ |
||||
ConfigMgr& conf = ConfigMgr::instance(); |
||||
for (int i = 0; i < m_listbox->count(); ++i) { |
||||
updatePrefixColor(m_listbox->item(i) , QColor(conf.color("ListboxForeground"))); |
||||
updatePrefixColor(m_listbox->item(i) , std::nullopt); |
||||
} |
||||
} |
@ -0,0 +1,56 @@ |
||||
/*
|
||||
* IdealIRC - Internet Relay Chat client |
||||
* Copyright (C) 2019 Tom-Andre Barstad |
||||
* |
||||
* This program is free software; you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation; either version 2 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License along |
||||
* with this program; if not, write to the Free Software Foundation, Inc., |
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
||||
*/ |
||||
|
||||
#ifndef NICKLISTCONTROLLER_H |
||||
#define NICKLISTCONTROLLER_H |
||||
|
||||
#include <QObject> |
||||
#include <QListWidget> |
||||
#include <memory> |
||||
|
||||
class IRC; |
||||
class IRCChannel; |
||||
class IRCMemberEntry; |
||||
|
||||
class NicklistController : public QObject |
||||
{ |
||||
Q_OBJECT |
||||
|
||||
public: |
||||
NicklistController(QListWidget* listbox, IRC& server, const QString& channel, QObject* parent = nullptr); |
||||
void reload(); |
||||
void clear(); |
||||
void addMember(const IRCMemberEntry& member); |
||||
void updateMember(const QString& nickname, const IRCMemberEntry& member); |
||||
void removeMember(const IRCMemberEntry& member); |
||||
|
||||
private: |
||||
bool lessThan(const QString& left, const QString& right); |
||||
QString makeItemText(const IRCMemberEntry& member); |
||||
int findNewItemPosition(const QString& text); |
||||
void updatePrefixColor(QListWidgetItem* item, std::optional<QColor> color); |
||||
void newConfiguration(); |
||||
QListWidget* m_listbox; |
||||
IRC& m_server; |
||||
const QString m_channel; |
||||
QStringList m_items; |
||||
QString m_prefixOrdered; //! Nickname prefix (@%+) in order
|
||||
}; |
||||
|
||||
#endif // NICKLISTCONTROLLER_H
|
After Width: | Height: | Size: 2.3 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 40 KiB |
After Width: | Height: | Size: 44 KiB |
After Width: | Height: | Size: 33 KiB |
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 6.9 KiB |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 13 KiB |
@ -0,0 +1,282 @@ |
||||
/*
|
||||
* IdealIRC - Internet Relay Chat client |
||||
* Copyright (C) 2019 Tom-Andre Barstad |
||||
* |
||||
* This program is free software; you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation; either version 2 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License along |
||||
* with this program; if not, write to the Free Software Foundation, Inc., |
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
||||
*/ |
||||
|
||||
#include "IdealIRC.h" |
||||
#include "ui_IdealIRC.h" |
||||
#include "config.h" |
||||
#include "IWin/IWinStatus.h" |
||||
#include <ConfigMgr.h> |
||||
#include <QDebug> |
||||
#include <QDir> |
||||
#include <QMessageBox> |
||||
|
||||
// Used externally. Initialized in the IdealIRC constructor.
|
||||
IdealIRC* IIRC_MainWindow_Ptr{ nullptr }; |
||||
|
||||
namespace { |
||||
void copyRecursively(const QString& from, const QString& to) |
||||
{ |
||||
QDir srcDir(from); |
||||
if (!srcDir.exists()) { |
||||
qWarning() << "Unable to copy" << from << "to" << to; |
||||
return; |
||||
} |
||||
|
||||
QDir destDir(to); |
||||
if (!destDir.exists()) |
||||
destDir.mkdir(to); |
||||
|
||||
auto sep = QDir::separator(); |
||||
QStringList files = srcDir.entryList(QDir::Files); |
||||
for (const QString& fileName : files) { |
||||
QString srcName = from + sep + fileName; |
||||
QString destName = to + sep + fileName; |
||||
if (!QFile::copy(srcName, destName)) |
||||
qWarning() << "Unable to copy" << srcName << "to" << destName; |
||||
} |
||||
|
||||
files = srcDir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot); |
||||
for (const QString& fileName : files) { |
||||
QString srcName = from + sep + fileName; |
||||
QString destName = to + sep + fileName; |
||||
copyRecursively(srcName, destName); |
||||
} |
||||
} |
||||
|
||||
void runtimeEnvironmentSetup() |
||||
{ |
||||
if constexpr (BUILD_TYPE == BuildType::packaged) { |
||||
QDir skelDir(GLOBAL_PATH+"/skel"); |
||||
if (!skelDir.exists()) |
||||
return; // Nothing more to do.
|
||||
|
||||
QDir destination(LOCAL_PATH); |
||||
if (!destination.exists()) { |
||||
destination.mkdir(LOCAL_PATH); |
||||
copyRecursively(skelDir.path(), destination.path()); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
IdealIRC::IdealIRC(QWidget* parent) |
||||
: QMainWindow(parent) |
||||
, ui(new Ui::IdealIRC) |
||||
, confDlg(this) |
||||
, aboutDlg(this) |
||||
{ |
||||
ui->setupUi(this); |
||||
IIRC_MainWindow_Ptr = this; |
||||
|
||||
qInfo() << "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"; |
||||
qInfo() << " IdealIRC version" << VERSION_STRING << "started."; |
||||
qInfo() << "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"; |
||||
runtimeEnvironmentSetup(); |
||||
qDebug() << "Global path:" << GLOBAL_PATH; |
||||
qDebug() << "Local path:" << LOCAL_PATH; |
||||
|
||||
const QString title = QString("%1 %2") |
||||
.arg(windowTitle()) |
||||
.arg(VERSION_STRING_NO_BUILD); |
||||
setWindowTitle(title); |
||||
trayIcon = new QSystemTrayIcon(QIcon(QStringLiteral(":/Icons/iconcutted.png")), this); |
||||
mdiManager = new MdiManager(*ui->mdiArea, *ui->windowButtons, *trayIcon); |
||||
trayIcon->show(); |
||||
trayIcon->setToolTip(title); |
||||
|
||||
connect(mdiManager, &MdiManager::readyForExit, this, &QMainWindow::close); |
||||
connect(mdiManager, &MdiManager::subwindowSwitched, this, &IdealIRC::subwindowSwitched); |
||||
connect(mdiManager, &MdiManager::connectionStateChange, this, &IdealIRC::updateConnectButtonState); |
||||
connect(&confDlg, &IConfig::connectToServer, this, &IdealIRC::connectToServer); |
||||
connect(&confDlg, &IConfig::disconnectFromServer, this, &IdealIRC::disconnectFromServer); |
||||
setConnectButtonState(ConnectButtonState::Connect); |
||||
|
||||
IWin* statusw = mdiManager->createSubwindow(nullptr, "Status", IWin::Type::Status); |
||||
statusw->refreshWindowTitle(); |
||||
|
||||
ScriptManager::init(mdiManager, this); |
||||
QTimer::singleShot(10, std::bind(&IdealIRC::startup, this)); |
||||
} |
||||
|
||||
IdealIRC::~IdealIRC() |
||||
{ |
||||
delete mdiManager; |
||||
delete trayIcon; |
||||
delete ui; |
||||
} |
||||
|
||||
void IdealIRC::closeEvent(QCloseEvent* evt) |
||||
{ |
||||
int serversWaiting = mdiManager->connectionsOnlineCount(); |
||||
/* Initialize closing */ |
||||
if (serversWaiting > 0) { |
||||
auto btn = QMessageBox::question(this, tr("Close IdealIRC"), tr("There are %1 connections active.\nDo you really want to exit IdealIRC?").arg(serversWaiting)); |
||||
if (btn == QMessageBox::Yes) |
||||
mdiManager->broadcastProgramExit(); |
||||
evt->ignore(); |
||||
} |
||||
else { |
||||
ConfigMgr::instance().save(); |
||||
ScriptManager::event("exit"); |
||||
ScriptManager::instance()->unloadAllScripts(); |
||||
emit aboutToClose(); |
||||
evt->accept(); |
||||
} |
||||
} |
||||
|
||||
void IdealIRC::startup() |
||||
{ |
||||
const ConfigMgr& conf = ConfigMgr::instance(); |
||||
if (conf.common("ShowOptions") == "1") |
||||
confDlg.show(); |
||||
|
||||
mdiManager->currentStatus()->createChannelWindow("#Svett"); |
||||
scriptEvent("start"); |
||||
} |
||||
|
||||
void IdealIRC::subwindowSwitched() |
||||
{ |
||||
updateConnectButtonState(); |
||||
ScriptManager::setContext(mdiManager->currentStatus()); |
||||
} |
||||
|
||||
void IdealIRC::updateConnectButtonState() |
||||
{ |
||||
IWinStatus* status = mdiManager->currentStatus(); |
||||
bool isConnected = status->getConnection().isConnected(); |
||||
setConnectButtonState(isConnected ? ConnectButtonState::Disconnect : ConnectButtonState::Connect); |
||||
} |
||||
|
||||
void IdealIRC::setConnectButtonState(IdealIRC::ConnectButtonState state) |
||||
{ |
||||
switch (state) { |
||||
case ConnectButtonState::Connect: |
||||
ui->actionConnect->setText(tr("Connect")); |
||||
break; |
||||
case ConnectButtonState::Disconnect: |
||||
ui->actionConnect->setText(tr("Disconnect")); |
||||
break; |
||||
} |
||||
ui->actionConnect->setData(static_cast<int>(state)); |
||||
} |
||||
|
||||
void IdealIRC::connectToServer(bool newStatus) |
||||
{ |
||||
IWinStatus* status{ nullptr }; |
||||
if (newStatus) |
||||
status = static_cast<IWinStatus*>(mdiManager->createSubwindow(nullptr, "Status", IWin::Type::Status)); |
||||
else |
||||
status = mdiManager->currentStatus(); |
||||
const ConfigMgr& conf = ConfigMgr::instance(); |
||||
QString server = conf.connection("Server"); |
||||
QString nickname = conf.connection("Nickname"); |
||||
QString realname = conf.connection("Realname"); |
||||
QString username = conf.connection("Username"); |
||||
|
||||
if (server.isEmpty() || nickname.isEmpty() || realname.isEmpty() || username.isEmpty()) { |
||||
confDlg.show(); |
||||
return; |
||||
} |
||||
|
||||
bool ssl = conf.connection("SSL").toInt(); |
||||
QString host; |
||||
quint16 port; |
||||
if (server.contains(':')) { |
||||
QString strport = server.split(':')[1]; |
||||
port = strport.toUShort(); |
||||
server = server.remove(":"+strport); |
||||
} |
||||
else |
||||
port = 6667; |
||||
host = server; |
||||
|
||||
IRC& con = status->getConnection(); |
||||
con.setHostname(host.toStdString(), ssl); |
||||
con.setPort(std::to_string(port)); |
||||
|
||||
con.setRealname(realname.toStdString()); |
||||
con.setIdent(username.toStdString()); |
||||
con.setNickname(nickname.toStdString()); |
||||
|
||||
const auto cfgSSLExcept_Expired = conf.common("SSLExpired"); |
||||
const auto cfgSSLExcept_Selfsigned = conf.common("SSLSelfsigned"); |
||||
const auto cfgSSLExcept_CNMismatch = conf.common("SSLCNMismatch"); |
||||
con.exceptSSL_Expired(cfgSSLExcept_Expired == "1"); |
||||
con.exceptSSL_SelfSigned(cfgSSLExcept_Selfsigned == "1"); |
||||
con.exceptSSL_CNMismatch(cfgSSLExcept_CNMismatch == "1"); |
||||
|
||||
if (con.isConnected()) { |
||||
if (con.disconnectFromServer() == IRCError::NotConnected) |
||||
qWarning() << "Trying to disconnect from an already closed connection."; |
||||
|
||||
auto e = con.tryConnect(); |
||||
if (e != IRCError::NoError) |
||||
status->print(PrintType::ProgramInfo, IRCErrorToString(e).c_str()); |
||||
} |
||||
else { |
||||
setConnectButtonState(ConnectButtonState::Disconnect); |
||||
auto e = con.tryConnect(); |
||||
if (e != IRCError::NoError) |
||||
status->print(PrintType::ProgramInfo, IRCErrorToString(e).c_str()); |
||||
} |
||||
} |
||||
|
||||
void IdealIRC::disconnectFromServer() |
||||
{ |
||||
IWinStatus* status = mdiManager->currentStatus(); |
||||
if (status->getConnection().disconnectFromServer() == IRCError::NotConnected) |
||||
qWarning() << "Trying to disconnect from an already closed connection."; |
||||
} |
||||
|
||||
void IdealIRC::on_actionConnect_triggered() |
||||
{ |
||||
ConnectButtonState state = static_cast<ConnectButtonState>(ui->actionConnect->data().toInt()); |
||||
if (state == ConnectButtonState::Connect) |
||||
connectToServer(false); |
||||
|
||||
else if (state == ConnectButtonState::Disconnect) { |
||||
IRC& connection = mdiManager->currentStatus()->getConnection(); |
||||
const ConfigMgr& conf = ConfigMgr::instance(); |
||||
if (!connection.isOnline()) |
||||
setConnectButtonState(ConnectButtonState::Connect); |
||||
|
||||
auto e = connection.disconnectFromServer(conf.common("QuitMessage").toStdString()); |
||||
if (e == IRCError::NotConnected) |
||||
qWarning() << "Trying to disconnect from an already closed connection."; |
||||
} |
||||
} |
||||
|
||||
void IdealIRC::on_actionOptions_triggered() |
||||
{ |
||||
confDlg.show(); |
||||
if (mdiManager->currentStatus()->getConnection().isConnected()) |
||||
confDlg.showDisconnectButton(); |
||||
else |
||||
confDlg.hideDisconnectButton(); |
||||
} |
||||
|
||||
void IdealIRC::on_actionAbout_IdealIRC_triggered() |
||||
{ |
||||
aboutDlg.show(); |
||||
} |
||||
|
||||
void IdealIRC::on_actionScripts_triggered() |
||||
{ |
||||
ScriptManager::instance()->show(); |
||||
} |
@ -0,0 +1,74 @@ |
||||
/*
|
||||
* IdealIRC - Internet Relay Chat client |
||||
* Copyright (C) 2019 Tom-Andre Barstad |
||||
* |
||||
* This program is free software; you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation; either version 2 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License along |
||||
* with this program; if not, write to the Free Software Foundation, Inc., |
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
||||
*/ |
||||
|
||||
#ifndef IDEALIRC_H |
||||
#define IDEALIRC_H |
||||
|
||||
#include "MdiManager.h" |
||||
#include "IConfig/IConfig.h" |
||||
#include "AboutIIRC.h" |
||||
#include "Script/Manager.h" |
||||
#include <QMainWindow> |
||||
#include <QCloseEvent> |
||||
#include <QSystemTrayIcon> |
||||
#include <QTimer> |
||||
|
||||
namespace Ui { |
||||
class IdealIRC; |
||||
} |
||||
|
||||
class IdealIRC : public QMainWindow |
||||
{ |
||||
Q_OBJECT |
||||
|
||||
public: |
||||
explicit IdealIRC(QWidget* parent = nullptr); |
||||
~IdealIRC(); |
||||
|
||||
private slots: |
||||
void on_actionConnect_triggered(); |
||||
void on_actionOptions_triggered(); |
||||
void on_actionAbout_IdealIRC_triggered(); |
||||
void on_actionScripts_triggered(); |
||||
|
||||
private: |
||||
enum class ConnectButtonState { |
||||
Connect, |
||||
Disconnect |
||||
}; |
||||
|
||||
void closeEvent(QCloseEvent* evt); |
||||
void startup(); |
||||
void subwindowSwitched(); |
||||
void updateConnectButtonState(); |
||||
void setConnectButtonState(ConnectButtonState state); |
||||
void connectToServer(bool newStatus); |
||||
void disconnectFromServer(); |
||||
|
||||
Ui::IdealIRC* ui; |
||||
MdiManager* mdiManager; |
||||
QSystemTrayIcon* trayIcon; |
||||
IConfig confDlg; |
||||
AboutIIRC aboutDlg; |
||||
|
||||
signals: |
||||
void aboutToClose(); |
||||
}; |
||||
|
||||
#endif // IDEALIRC_H
|
@ -0,0 +1,196 @@ |
||||
# IdealIRC - Internet Relay Chat client |
||||
# Copyright (C) 2019 Tom-Andre Barstad |
||||
# |
||||
# This program is free software; you can redistribute it and/or modify |
||||
# it under the terms of the GNU General Public License as published by |
||||
# the Free Software Foundation; either version 2 of the License, or |
||||
# (at your option) any later version. |
||||
# |
||||
# This program is distributed in the hope that it will be useful, |
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
# GNU General Public License for more details. |
||||
# |
||||
# You should have received a copy of the GNU General Public License along |
||||
# with this program; if not, write to the Free Software Foundation, Inc., |
||||
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
||||
|
||||
QT += core gui widgets |
||||
|
||||
TARGET = IdealIRC |
||||
TEMPLATE = app |
||||
|
||||
VERSION_MAJOR = 1 |
||||
VERSION_MINOR = 0 |
||||
VERSION_PATCH = 0 |
||||
VERSION_BUILD = 001 |
||||
|
||||
# Two different "build types". |
||||
# standalone: |
||||
# Read config and skeleton from same folder as the executable. |
||||
# packaged: |
||||
# Used with installers, and reads config and skeleton from a more system-friendly path. |
||||
BUILD_TYPE = packaged |
||||
|
||||
QMAKE_SUBSTITUTES += config.h.in |
||||
DEFINES += QT_DEPRECATED_WARNINGS |
||||
CONFIG += c++17 |
||||
unix { QMAKE_CXXFLAGS += -std=c++17 } |
||||
win32 | win64 { QMAKE_CXXFLAGS += /std:c++17 } |
||||
|
||||
QMAKE_LFLAGS += -lm -lfmt -lcrypto -lssl |
||||
|
||||
SOURCES += \ |
||||
IRC.cpp \ |
||||
Script/Builtin/Builtin.cpp \ |
||||
Script/Builtin/DialogUtils.cpp \ |
||||
Script/Builtin/Error.cpp \ |
||||
Script/Builtin/GeneralUtils.cpp \ |
||||
Script/Builtin/ListUtils.cpp \ |
||||
Script/Builtin/MapUtils.cpp \ |
||||
Script/Builtin/Mathematics.cpp \ |
||||
Script/Builtin/StringUtils.cpp \ |
||||
Script/Dialog.cpp \ |
||||
Script/ManagerListModel.cpp \ |
||||
Script/Menu.cpp \ |
||||
Script/ParserToken.cpp \ |
||||
Script/Script.cpp \ |
||||
Script/ScriptException.cpp \ |
||||
Script/SymbolScope.cpp \ |
||||
Script/Tokenizer.cpp \ |
||||
Script/Tokens.cpp \ |
||||
Script/ValueExtract.cpp \ |
||||
Script/ValueHolder.cpp \ |
||||
ScriptDialog/ScriptDialog.cpp \ |
||||
ScriptFunctions/RegisterScriptFunctions.cpp \ |
||||
ScriptFunctions/ScriptGeneralUtils.cpp \ |
||||
ScriptFunctions/ScriptIRCUtils.cpp \ |
||||
main.cpp \ |
||||
IdealIRC.cpp \ |
||||
# IMember.cpp \ |
||||
# IConnection/IConnection.cpp \ |
||||
IWin/IWin.cpp \ |
||||
# IChannel.cpp \ |
||||
# IServer.cpp \ |
||||
MdiManager.cpp \ |
||||
IWin/IWinStatus.cpp \ |
||||
Widgets/IIRCView.cpp \ |
||||
# IRCReader.cpp \ |
||||
# IRCPrefix.cpp \ |
||||
InputHandler.cpp \ |
||||
ICommand.cpp \ |
||||
IWin/IWinChannel.cpp \ |
||||
IWin/IWinPrivate.cpp \ |
||||
IWin/NicklistController.cpp \ |
||||
IConfig/IConfig.cpp \ |
||||
IConfig/IConfigServers.cpp \ |
||||
IConfig/IConfigOptions.cpp \ |
||||
IConfig/IConfigLogging.cpp \ |
||||
AboutIIRC.cpp \ |
||||
IniFile.cpp \ |
||||
IConfig/ColorConfig.cpp \ |
||||
ConfigMgr.cpp \ |
||||
Widgets/ILineEdit.cpp \ |
||||
Widgets/IListWidget.cpp \ |
||||
IConfig/ServerEditor.cpp \ |
||||
IConfig/ServerMgr.cpp \ |
||||
IConfig/ServerModel.cpp \ |
||||
ButtonbarMgr.cpp \ |
||||
ICommand/CommandData.cpp \ |
||||
Script/Manager.cpp \ |
||||
IRCClient/DCC.cpp \ |
||||
IRCClient/IRCBase.cpp \ |
||||
IRCClient/IRCChannel.cpp \ |
||||
IRCClient/IRCError.cpp \ |
||||
IRCClient/IRCMember.cpp \ |
||||
IRCClient/IRCMemberEntry.cpp \ |
||||
IRCClient/IRCPrefix.cpp \ |
||||
IRCClient/Utilities.cpp |
||||
|
||||
HEADERS += \ |
||||
IRC.h \ |
||||
IdealIRC.h \ |
||||
# IMember.h \ |
||||
# IConnection/IConnection.h \ |
||||
IWin/IWin.h \ |
||||
# IChannel.h \ |
||||
# IServer.h \ |
||||
MdiManager.h \ |
||||
IWin/IWinStatus.h \ |
||||
Script/Builtin/Builtin.h \ |
||||
Script/Builtin/DialogUtils.h \ |
||||
Script/Builtin/Error.h \ |
||||
Script/Builtin/GeneralUtils.h \ |
||||
Script/Builtin/ListUtils.h \ |
||||
Script/Builtin/MapUtils.h \ |
||||
Script/Builtin/Mathematics.h \ |
||||
Script/Builtin/StringUtils.h \ |
||||
Script/Dialog.h \ |
||||
Script/ManagerListModel.h \ |
||||
Script/Menu.h \ |
||||
Script/ParserOperator.h \ |
||||
Script/ParserToken.h \ |
||||
Script/Script.h \ |
||||
Script/ScriptException.h \ |
||||
Script/SymbolScope.h \ |
||||
Script/Tokenizer.h \ |
||||
Script/Tokens.h \ |
||||
Script/ValueExtract.h \ |
||||
Script/ValueHolder.h \ |
||||
ScriptDialog/ScriptDialog.h \ |
||||
ScriptEvent.h \ |
||||
ScriptFunctions/RegisterScriptFunctions.h \ |
||||
ScriptFunctions/ScriptGeneralUtils.h \ |
||||
ScriptFunctions/ScriptIRCUtils.h \ |
||||
Widgets/IIRCView.h \ |
||||
# IRCReader.h \ |
||||
# IRCPrefix.h \ |
||||
# Numeric.h \ |
||||
# Commands.h \ |
||||
InputHandler.h \ |
||||
ICommand.h \ |
||||
IWin/IWinChannel.h \ |
||||
IWin/IWinPrivate.h \ |
||||
IWin/NicklistController.h \ |
||||
IConfig/IConfig.h \ |
||||
IConfig/IConfigServers.h \ |
||||
IConfig/IConfigOptions.h \ |
||||
IConfig/IConfigLogging.h \ |
||||
AboutIIRC.h \ |
||||
IniFile.h \ |
||||
IConfig/ColorConfig.h \ |
||||
ConfigMgr.h \ |
||||
Widgets/ILineEdit.h \ |
||||
Widgets/IListWidget.h \ |
||||
IConfig/ServerEditor.h \ |
||||
IConfig/ServerMgr.h \ |
||||
IConfig/ServerModel.h \ |
||||
ButtonbarMgr.h \ |
||||
ICommand/CommandData.h \ |
||||
Script/Manager.h \ |
||||
IRCClient/Commands.h \ |
||||
IRCClient/DCC.h \ |
||||
IRCClient/IRCBase.h \ |
||||
IRCClient/IRCChannel.h \ |
||||
IRCClient/IRCError.h \ |
||||
IRCClient/IRCMember.h \ |
||||
IRCClient/IRCMemberEntry.h \ |
||||
IRCClient/IRCPrefix.h \ |
||||
IRCClient/Numeric.h \ |
||||
IRCClient/Utilities.h |
||||
|
||||
FORMS += \ |
||||
IdealIRC.ui \ |
||||
IConfig/IConfig.ui \ |
||||
IConfig/IConfigServers.ui \ |
||||
IConfig/IConfigOptions.ui \ |
||||
IConfig/IConfigLogging.ui \ |
||||
AboutIIRC.ui \ |
||||
IConfig/ServerEditor.ui \ |
||||
Script/Manager.ui |
||||
|
||||
RESOURCES += \ |
||||
resources.qrc |
||||
|
||||
DISTFILES += \ |
||||
events.txt |
@ -0,0 +1,175 @@ |
||||
<?xml version="1.0" encoding="UTF-8"?> |
||||
<ui version="4.0"> |
||||
<class>IdealIRC</class> |
||||
<widget class="QMainWindow" name="IdealIRC"> |
||||
<property name="geometry"> |
||||
<rect> |
||||
<x>0</x> |
||||
<y>0</y> |
||||
<width>973</width> |
||||
<height>691</height> |
||||
</rect> |
||||
</property> |
||||
<property name="windowTitle"> |
||||
<string>IdealIRC</string> |
||||
</property> |
||||
<property name="windowIcon"> |
||||
<iconset resource="resources.qrc"> |
||||
<normaloff>:/Icons/iconcutted.png</normaloff>:/Icons/iconcutted.png</iconset> |
||||
</property> |
||||
<widget class="QWidget" name="centralWidget"> |
||||
<layout class="QVBoxLayout" name="verticalLayout"> |
||||
<property name="spacing"> |
||||
<number>0</number> |
||||
</property> |
||||
<property name="leftMargin"> |
||||
<number>0</number> |
||||
</property> |
||||
<property name="topMargin"> |
||||
<number>0</number> |
||||
</property> |
||||
<property name="rightMargin"> |
||||
<number>0</number> |
||||
</property> |
||||
<property name="bottomMargin"> |
||||
<number>0</number> |
||||
</property> |
||||
<item> |
||||
<widget class="QMdiArea" name="mdiArea"> |
||||
<property name="activationOrder"> |
||||
<enum>QMdiArea::ActivationHistoryOrder</enum> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
</layout> |
||||
</widget> |
||||
<widget class="QMenuBar" name="menuBar"> |
||||
<property name="geometry"> |
||||
<rect> |
||||
<x>0</x> |
||||
<y>0</y> |
||||
<width>973</width> |
||||
<height>20</height> |
||||
</rect> |
||||
</property> |
||||
<property name="contextMenuPolicy"> |
||||
<enum>Qt::CustomContextMenu</enum> |
||||
</property> |
||||
<widget class="QMenu" name="menuIIRC"> |
||||
<property name="title"> |
||||
<string>IIRC</string> |
||||
</property> |
||||
<addaction name="separator"/> |
||||
<addaction name="actionConnect"/> |
||||
<addaction name="actionOptions"/> |
||||
<addaction name="actionScripts"/> |
||||
<addaction name="separator"/> |
||||
<addaction name="actionExit"/> |
||||
</widget> |
||||
<widget class="QMenu" name="menuHelp"> |
||||
<property name="title"> |
||||
<string>Help</string> |
||||
</property> |
||||
<addaction name="actionOnline_wiki"/> |
||||
<addaction name="separator"/> |
||||
<addaction name="actionAbout_IdealIRC"/> |
||||
</widget> |
||||
<addaction name="menuIIRC"/> |
||||
<addaction name="menuHelp"/> |
||||
</widget> |
||||
<widget class="QToolBar" name="toolBar"> |
||||
<property name="contextMenuPolicy"> |
||||
<enum>Qt::CustomContextMenu</enum> |
||||
</property> |
||||
<property name="windowTitle"> |
||||
<string>Toolbar buttons</string> |
||||
</property> |
||||
<property name="iconSize"> |
||||
<size> |
||||
<width>24</width> |
||||
<height>24</height> |
||||
</size> |
||||
</property> |
||||
<attribute name="toolBarArea"> |
||||
<enum>TopToolBarArea</enum> |
||||
</attribute> |
||||
<attribute name="toolBarBreak"> |
||||
<bool>false</bool> |
||||
</attribute> |
||||
<addaction name="actionConnect"/> |
||||
<addaction name="actionOptions"/> |
||||
<addaction name="actionScripts"/> |
||||
</widget> |
||||
<widget class="QToolBar" name="windowButtons"> |
||||
<property name="windowTitle"> |
||||
<string>Window buttons</string> |
||||
</property> |
||||
<attribute name="toolBarArea"> |
||||
<enum>TopToolBarArea</enum> |
||||
</attribute> |
||||
<attribute name="toolBarBreak"> |
||||
<bool>true</bool> |
||||
</attribute> |
||||
</widget> |
||||
<action name="actionConnect"> |
||||
<property name="text"> |
||||
<string>Connect</string> |
||||
</property> |
||||
</action> |
||||
<action name="actionOptions"> |
||||
<property name="icon"> |
||||
<iconset resource="resources.qrc"> |
||||
<normaloff>:/Icons/options.png</normaloff>:/Icons/options.png</iconset> |
||||
</property> |
||||
<property name="text"> |
||||
<string>Options...</string> |
||||
</property> |
||||
</action> |
||||
<action name="actionScripts"> |
||||
<property name="icon"> |
||||
<iconset resource="resources.qrc"> |
||||
<normaloff>:/Icons/script.png</normaloff>:/Icons/script.png</iconset> |
||||
</property> |
||||
<property name="text"> |
||||
<string>Scripts...</string> |
||||
</property> |
||||
</action> |
||||
<action name="actionExit"> |
||||
<property name="text"> |
||||
<string>Exit</string> |
||||
</property> |
||||
</action> |
||||
<action name="actionOnline_wiki"> |
||||
<property name="text"> |
||||
<string>Online wiki</string> |
||||
</property> |
||||
</action> |
||||
<action name="actionAbout_IdealIRC"> |
||||
<property name="text"> |
||||
<string>About IdealIRC</string> |
||||
</property> |
||||
</action> |
||||
</widget> |
||||
<layoutdefault spacing="6" margin="11"/> |
||||
<resources> |
||||
<include location="resources.qrc"/> |
||||
</resources> |
||||
<connections> |
||||
<connection> |
||||
<sender>actionExit</sender> |
||||
<signal>triggered()</signal> |
||||
<receiver>IdealIRC</receiver> |
||||
<slot>close()</slot> |
||||
<hints> |
||||
<hint type="sourcelabel"> |
||||
<x>-1</x> |
||||
<y>-1</y> |
||||
</hint> |
||||
<hint type="destinationlabel"> |
||||
<x>486</x> |
||||
<y>345</y> |
||||
</hint> |
||||
</hints> |
||||
</connection> |
||||
</connections> |
||||
</ui> |
@ -0,0 +1,539 @@ |
||||
/*
|
||||
* IdealIRC - Internet Relay Chat client |
||||
* Copyright (C) 2019 Tom-Andre Barstad |
||||
* |
||||
* This program is free software; you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation; either version 2 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License along |
||||
* with this program; if not, write to the Free Software Foundation, Inc., |
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
||||
* |
||||
*/ |
||||
|
||||
#include <QStringList> |
||||
#include <iostream> |
||||
#include "IniFile.h" |
||||
|
||||
IniFile::IniFile(QString filename) |
||||
{ |
||||
file = new QFile(filename); |
||||
if (QFile::exists(filename) == false) { |
||||
file->open(QIODevice::WriteOnly); |
||||
file->close(); |
||||
} |
||||
} |
||||
|
||||
void IniFile::clearNewline(char *data) |
||||
{ |
||||
int i = 0; |
||||
while (true) { |
||||
if (data[i] == '\0') |
||||
break; |
||||
|
||||
if (data[i] == '\n') { |
||||
data[i] = '\0'; |
||||
break; |
||||
} |
||||
i++; |
||||
} |
||||
} |
||||
|
||||
QString IniFile::value(QString Section, QString Item, QString defaultValue) |
||||
{ |
||||
if (! file->open(QIODevice::ReadOnly | QIODevice::Text)) |
||||
return defaultValue; |
||||
|
||||
Section = QStringLiteral("[%1]").arg(Section); |
||||
|
||||
Item.append('='); |
||||
|
||||
char buf[1024]; |
||||
memset(buf, 0, sizeof(buf)); |
||||
bool sectFound = false; |
||||
|
||||
while (file->readLine(buf, sizeof(buf)) != -1) { |
||||
clearNewline(buf); |
||||
QString line(buf); |
||||
|
||||
if (line.isEmpty()) |
||||
continue; |
||||
|
||||
if (line.left(Section.length()).toUpper() == Section.toUpper()) { |
||||
sectFound = true; |
||||
continue; |
||||
} |
||||
if ((sectFound) && (line.left(Item.length()).toUpper() == Item.toUpper())) { |
||||
file->close(); |
||||
return line.mid(Item.length()); |
||||
} |
||||
} |
||||
|
||||
file->close(); |
||||
return defaultValue; |
||||
} |
||||
|
||||
QString IniFile::value(QString Section, int ItemPos, QString defaultValue) |
||||
{ |
||||
if (! file->open(QIODevice::ReadOnly | QIODevice::Text)) |
||||
return defaultValue; |
||||
|
||||
Section = QStringLiteral("[%1]").arg(Section); |
||||
|
||||
char buf[1024]; |
||||
memset(buf, 0, sizeof(buf)); |
||||
bool sectFound = false; |
||||
int i = 0; |
||||
|
||||
while (file->readLine(buf, sizeof(buf)) != -1) { |
||||
clearNewline(buf); |
||||
QString line(buf); |
||||
|
||||
if (line.isEmpty()) |
||||
continue; |
||||
|
||||
if (line.left(Section.length()).toUpper() == Section.toUpper()) { |
||||
sectFound = true; |
||||
continue; |
||||
} |
||||
|
||||
if ((sectFound) && (line.contains(QChar('=')))) { |
||||
if (i == ItemPos) { |
||||
// Read out value.
|
||||
i = line.indexOf('='); |
||||
file->close(); |
||||
return line.mid(i+1); |
||||
} |
||||
i++; |
||||
|
||||
} |
||||
} |
||||
|
||||
file->close(); |
||||
return defaultValue; |
||||
} |
||||
|
||||
QString IniFile::section(int SectionPos, QString defaultSectionName) |
||||
{ |
||||
if (! file->open(QIODevice::ReadOnly | QIODevice::Text)) |
||||
return defaultSectionName; |
||||
|
||||
char buf[1024]; |
||||
memset(buf, 0, sizeof(buf)); |
||||
|
||||
int i = 0; |
||||
|
||||
while (file->readLine(buf, sizeof(buf)) != -1) { |
||||
clearNewline(buf); |
||||
QString line(buf); |
||||
|
||||
if (line == "") |
||||
continue; |
||||
|
||||
|
||||
if (line.startsWith('[') && line.endsWith(']')) { |
||||
if (i == SectionPos) { |
||||
file->close(); |
||||
return line.mid(1, line.length()-2); |
||||
} |
||||
|
||||
i++; |
||||
} |
||||
} |
||||
|
||||
file->close(); |
||||
return defaultSectionName; |
||||
} |
||||
|
||||
QString IniFile::key(QString Section, int ItemPos, QString defaultKey) |
||||
{ |
||||
if (! file->open(QIODevice::ReadOnly | QIODevice::Text)) |
||||
return defaultKey; |
||||
|
||||
Section = QStringLiteral("[%1]").arg(Section); |
||||
|
||||
char buf[1024]; |
||||
memset(buf, 0, sizeof(buf)); |
||||
bool sectFound = false; |
||||
int i = 0; |
||||
|
||||
while (file->readLine(buf, sizeof(buf)) != -1) { |
||||
clearNewline(buf); |
||||
QString line(buf); |
||||
|
||||
if (line.isEmpty()) |
||||
continue; |
||||
|
||||
if (line.left(Section.length()).toUpper() == Section.toUpper()) { |
||||
sectFound = true; |
||||
continue; |
||||
} |
||||
if ((sectFound) && (line.contains(QChar('=')))) { |
||||
if (i == ItemPos) { |
||||
// Read out item.
|
||||
i = line.indexOf('='); |
||||
file->close(); |
||||
return line.mid(0,i); |
||||
} |
||||
i++; |
||||
} |
||||
} |
||||
|
||||
file->close(); |
||||
return defaultKey; |
||||
} |
||||
|
||||
bool IniFile::write(QString Section, QString Item, QString Value) |
||||
{ |
||||
Section = QStringLiteral("[%1]").arg(Section); |
||||
|
||||
Item.append('='); |
||||
|
||||
QStringList sl; |
||||
char buf[1024]; |
||||
memset(buf, 0, sizeof(buf)); |
||||
bool sectFound = false; |
||||
bool finished = false; |
||||
|
||||
if (! file->open(QIODevice::ReadOnly | QIODevice::Text)) |
||||
return false; |
||||
|
||||
while (file->readLine(buf, sizeof(buf)) != -1) { |
||||
clearNewline(buf); |
||||
QString line(buf); |
||||
|
||||
if (line.isEmpty()) |
||||
continue; |
||||
|
||||
sl.append(line); |
||||
|
||||
if (finished) |
||||
continue; |
||||
|
||||
if (line.left(Section.length()).toUpper() == Section.toUpper()) { |
||||
sectFound = true; |
||||
continue; |
||||
} |
||||
|
||||
// Found existing item, overwriting.
|
||||
if ((sectFound) && (line.left(Item.length()).toUpper() == Item.toUpper())) { |
||||
sl.removeAt(sl.count()-1); // Remove the last insertion, we get a new one here...
|
||||
sl.append(Item + Value); |
||||
finished = true; |
||||
continue; |
||||
} |
||||
|
||||
if ((sectFound) && (! finished) && (line.left(1)) == "[") { |
||||
// We have found our section, but not the item, as we reached end of the section.
|
||||
sl.removeAt(sl.count()-1); // Remove the last insertion, we get a new one here...
|
||||
sl.append(Item + Value); |
||||
sl.append(line); |
||||
finished = true; |
||||
continue; |
||||
} |
||||
} |
||||
|
||||
if ((sectFound == true) && (finished == false)) |
||||
sl.append(Item + Value); // We have found our section, but we reached EOF. Insert new item.
|
||||
|
||||
if (sectFound == false) { |
||||
// Section weren't found, we make a new one at the end, and our item there.
|
||||
sl.append(Section); |
||||
sl.append(Item + Value); |
||||
} |
||||
|
||||
file->close(); |
||||
if (! file->open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) |
||||
return false; |
||||
|
||||
for (int i = 0; i <= sl.count()-1; i++) { |
||||
QByteArray out; |
||||
out.append(sl[i]); |
||||
out.append('\n'); |
||||
file->write(out); |
||||
} |
||||
file->close(); |
||||
return true; |
||||
} |
||||
|
||||
int IniFile::countItems(QString Section) |
||||
{ |
||||
if (! file->open(QIODevice::ReadOnly | QIODevice::Text)) |
||||
return 0; |
||||
|
||||
Section = QStringLiteral("[%1]").arg(Section); |
||||
|
||||
char buf[1024]; |
||||
memset(buf, 0, sizeof(buf)); |
||||
bool sectFound = false; |
||||
int count = 0; |
||||
|
||||
while (file->readLine(buf, sizeof(buf)) != -1) { |
||||
clearNewline(buf); |
||||
QString line(buf); |
||||
|
||||
if (line.isEmpty()) |
||||
continue; |
||||
|
||||
if (line.left(Section.length()).toUpper() == Section.toUpper()) { |
||||
sectFound = true; |
||||
continue; |
||||
} |
||||
|
||||
if (sectFound) { |
||||
if (line.left(1) == "[") |
||||
break; |
||||
if (line.contains('=')) |
||||
count++; |
||||
} |
||||
} |
||||
|
||||
file->close(); |
||||
return count; |
||||
} |
||||
|
||||
int IniFile::countSections() |
||||
{ |
||||
if (! file->open(QIODevice::ReadOnly | QIODevice::Text)) |
||||
return 0; |
||||
|
||||
char buf[1024]; |
||||
memset(buf, 0, sizeof(buf)); |
||||
int count = 0; |
||||
|
||||
while (file->readLine(buf, sizeof(buf)) != -1) { |
||||
clearNewline(buf); |
||||
QString line(buf); |
||||
if (line.isEmpty()) |
||||
continue; |
||||
|
||||
if (line.startsWith('[') && line.endsWith(']')) |
||||
count++; |
||||
} |
||||
|
||||
file->close(); |
||||
return count; |
||||
} |
||||
|
||||
bool IniFile::delSection(QString Section) |
||||
{ |
||||
Section = QStringLiteral("[%1]").arg(Section); |
||||
|
||||
QStringList sl; |
||||
char buf[1024]; |
||||
memset(buf, 0, sizeof(buf)); |
||||
bool sectFound = false; |
||||
|
||||
if (! file->open(QIODevice::ReadOnly | QIODevice::Text)) |
||||
return false; |
||||
|
||||
while (file->readLine(buf, sizeof(buf)) != -1) { |
||||
clearNewline(buf); |
||||
QString line(buf); |
||||
|
||||
if (line.isEmpty()) |
||||
continue; |
||||
|
||||
if (line.left(Section.length()).toUpper() == Section.toUpper()) { |
||||
sectFound = true; |
||||
continue; |
||||
} |
||||
|
||||
if (sectFound) { |
||||
if (line.left(1) == "[") |
||||
sectFound = false; |
||||
} |
||||
|
||||
if (! sectFound) |
||||
sl.push_back(line); |
||||
} |
||||
|
||||
file->close(); |
||||
if (! file->open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) |
||||
return false; |
||||
|
||||
for (int i = 0; i <= sl.count()-1; i++) { |
||||
QByteArray out; |
||||
out.append(sl[i]); |
||||
out.append('\n'); |
||||
file->write(out); |
||||
} |
||||
file->close(); |
||||
return true; |
||||
} |
||||
|
||||
bool IniFile::delItem(QString Section, QString Item) |
||||
{ |
||||
Section = QStringLiteral("[%1]").arg(Section); |
||||
|
||||
Item.append('='); |
||||
|
||||
QStringList sl; |
||||
char buf[1024]; |
||||
memset(buf, 0, sizeof(buf)); |
||||
bool sectFound = false; |
||||
bool finished = false; |
||||
|
||||
if (! file->open(QIODevice::ReadOnly | QIODevice::Text)) |
||||
return false; |
||||
|
||||
while (file->readLine(buf, sizeof(buf)) != -1) { |
||||
clearNewline(buf); |
||||
QString line(buf); |
||||
|
||||
if (line.isEmpty()) |
||||
continue; |
||||
|
||||
sl.append(line); |
||||
|
||||
if (finished) |
||||
continue; |
||||
|
||||
if (line.left(Section.length()).toUpper() == Section.toUpper()) { |
||||
sectFound = true; |
||||
continue; |
||||
} |
||||
|
||||
// Found item
|
||||
if ((sectFound) && (line.left(Item.length()).toUpper() == Item.toUpper())) { |
||||
sl.removeAt(sl.count()-1); // Remove the last insertion
|
||||
finished = true; |
||||
continue; |
||||
} |
||||
|
||||
if ((sectFound) && (! finished) && (line.left(1)) == "[") { |
||||
// We have found our section, but not the item, as we reached end of the section.
|
||||
// Just close file reading and do not touch the file at all.
|
||||
file->close(); |
||||
return false; // False because we didn't do anything
|
||||
} |
||||
} |
||||
|
||||
if ((sectFound == true) && (finished == false)) { |
||||
// We have found our section, but we reached EOF. Don't do anything
|
||||
file->close(); |
||||
return false; |
||||
} |
||||
|
||||
if (sectFound == false) { |
||||
// Section weren't found, just stop.
|
||||
file->close(); |
||||
return false; |
||||
} |
||||
|
||||
file->close(); |
||||
if (! file->open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) |
||||
return false; |
||||
|
||||
for (int i = 0; i <= sl.count()-1; i++) { |
||||
QByteArray out; |
||||
out.append(sl[i]); |
||||
out.append('\n'); |
||||
file->write(out); |
||||
} |
||||
file->close(); |
||||
return true; |
||||
} |
||||
|
||||
bool IniFile::sectionExists(QString section) |
||||
{ |
||||
section = QStringLiteral("[%1]").arg(section); |
||||
|
||||
if (! file->open(QIODevice::ReadOnly | QIODevice::Text)) |
||||
return false; |
||||
|
||||
char d[64]; |
||||
memset(d, 0, 64); |
||||
while (file->readLine(d, 64) > -1) { |
||||
clearNewline(d); |
||||
QString ln(d); |
||||
if (section.toUpper() == ln.toUpper()) { |
||||
file->close(); |
||||
return true; |
||||
} |
||||
} |
||||
|
||||
file->close(); |
||||
return false; |
||||
} |
||||
|
||||
bool IniFile::appendSection(QString Section) |
||||
{ |
||||
if (sectionExists(Section)) |
||||
return false; |
||||
|
||||
if (! file->open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Append)) |
||||
return false; |
||||
|
||||
QString out = QStringLiteral("[%1]\n") |
||||
.arg(Section); |
||||
|
||||
file->write(out.toLocal8Bit()); |
||||
file->close(); |
||||
|
||||
return true; |
||||
} |
||||
|
||||
bool IniFile::renameSection(QString OldName, QString NewName) |
||||
{ |
||||
OldName = QStringLiteral("[%1]").arg(OldName); |
||||
|
||||
NewName = QStringLiteral("[%1]").arg(NewName); |
||||
|
||||
QStringList sl; |
||||
|
||||
char buf[1024]; |
||||
memset(buf, 0, sizeof(buf)); |
||||
bool finished = false; |
||||
|
||||
if (! file->open(QIODevice::ReadOnly | QIODevice::Text)) |
||||
return false; |
||||
|
||||
while (file->readLine(buf, sizeof(buf)) != -1) { |
||||
clearNewline(buf); |
||||
QString line(buf); |
||||
|
||||
if (line.isEmpty()) |
||||
continue; |
||||
|
||||
sl.append(line); |
||||
|
||||
if (finished) |
||||
continue; |
||||
|
||||
if (line.toUpper() == OldName.toUpper()) { |
||||
sl.pop_back(); // The very last item inserted is actually OldName. Remove.
|
||||
sl.append(NewName); |
||||
finished = true; |
||||
} |
||||
|
||||
} |
||||
|
||||
if (finished == false) { |
||||
// Section weren't found, just stop.
|
||||
file->close(); |
||||
return false; |
||||
} |
||||
|
||||
|
||||
|
||||
file->close(); |
||||
if (! file->open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) |
||||
return false; |
||||
|
||||
for (int i = 0; i <= sl.count()-1; i++) { |
||||
QByteArray out; |
||||
out.append(sl[i]); |
||||
out.append('\n'); |
||||
file->write(out); |
||||
} |
||||
file->close(); |
||||
return true; |
||||
} |
@ -0,0 +1,57 @@ |
||||
/*
|
||||
* IdealIRC - Internet Relay Chat client |
||||
* Copyright (C) 2019 Tom-Andre Barstad |
||||
* |
||||
* This program is free software; you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation; either version 2 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License along |
||||
* with this program; if not, write to the Free Software Foundation, Inc., |
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
||||
* |
||||
* *** |
||||
* This class is from the 0.x version and is quite ready for re-writing. |
||||
* TODO |
||||
* Refactor to use const-references |
||||
* Refactor to use tabs instead of spaces |
||||
* -> In fact, rewrite this entire class. There's so much wrong with it! <- |
||||
* *** |
||||
*/ |
||||
|
||||
#ifndef INIFILE_H |
||||
#define INIFILE_H |
||||
|
||||
#include <QFile> |
||||
|
||||
class IniFile |
||||
{ |
||||
public: |
||||
explicit IniFile(QString filename); |
||||
~IniFile() { file->deleteLater(); } |
||||
QString value(QString Section, QString Item, QString defaultValue = ""); |
||||
QString value(QString Section, int ItemPos, QString defaultValue = ""); |
||||
QString section(int SectionPos, QString defaultSectionName = ""); |
||||
QString key(QString Section, int ItemPos, QString defaultKey = ""); |
||||
bool write(QString Section, QString Item, QString Value); |
||||
int countItems(QString section); |
||||
int countSections(); |
||||
bool delSection(QString Section); |
||||
bool delItem(QString Section, QString Item); |
||||
bool sectionExists(QString section); |
||||
bool appendSection(QString Section); |
||||
bool renameSection(QString OldName, QString NewName); |
||||
|
||||
private: |
||||
void clearNewline(char *data); |
||||
QFile *file; |
||||
|
||||
}; |
||||
|
||||
#endif // INIFILE_H
|
@ -0,0 +1,80 @@ |
||||
/*
|
||||
* IdealIRC - Internet Relay Chat client |
||||
* Copyright (C) 2019 Tom-Andre Barstad |
||||
* |
||||
* This program is free software; you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation; either version 2 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License along |
||||
* with this program; if not, write to the Free Software Foundation, Inc., |
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
||||
*/ |
||||
|
||||
#include "InputHandler.h" |
||||
#include "IWin/IWin.h" |
||||
#include "ICommand.h" |
||||
#include "IRC.h" |
||||
#include "Commands.h" |
||||
#include "ConfigMgr.h" |
||||
#include "IRCClient/IRCMember.h" |
||||
#include "IRCClient/IRCChannel.h" |
||||
#include <QDebug> |
||||
|
||||
InputHandler::InputHandler(IRC& connection_) |
||||
: connection(connection_) |
||||
, cmdHndl(connection_) |
||||
{} |
||||
|
||||
void InputHandler::parse(IWin& sender, const QString& text, bool handleCommand) |
||||
{ |
||||
if (text.isEmpty()) |
||||
return; |
||||
|
||||
if (text[0] == '/' && handleCommand) |
||||
cmdHndl.parse(text); |
||||
else { |
||||
if (sender.getType() == IWin::Type::Status) { |
||||
sender.print(PrintType::ProgramInfo, "You're not in a chat window!"); |
||||
return; |
||||
} |
||||
else { |
||||
const std::string target = sender.getButtonText().toStdString(); |
||||
std::string me = connection.getNickname(); |
||||
connection.command(Command::IRC::PRIVMSG, { target }, text.toStdString()); |
||||
|
||||
if (ConfigMgr::instance().common("ShowModeInMessage") == "1") do { |
||||
const auto& myChannels = connection.channels(); |
||||
auto chanIt = std::find_if(myChannels.begin(), myChannels.end(), |
||||
[&target](std::shared_ptr<IRCChannel> ptr) { |
||||
return target == ptr->name(); |
||||
}); |
||||
|
||||
if (chanIt == myChannels.end()) { |
||||
qWarning() << "Could not find channel '" << target.c_str() << "' internally, although it seems we're on it anyway."; |
||||
break; |
||||
} |
||||
|
||||
const auto& member = (*chanIt)->getMember(me)->get(); |
||||
const auto prefixes = connection.toMemberPrefix(member.modes()); |
||||
if (!prefixes.empty()) |
||||
me.insert(me.begin(), prefixes[0]); |
||||
} while(0); |
||||
|
||||
sender.print(PrintType::OwnText, QStringLiteral("<%1> %2") |
||||
.arg(me.c_str()) |
||||
.arg(text)); |
||||
} |
||||
} |
||||
} |
||||
|
||||
void InputHandler::parseCommand(const QString& text) |
||||
{ |
||||
cmdHndl.parse(text); |
||||
} |
@ -0,0 +1,44 @@ |
||||
/*
|
||||
* IdealIRC - Internet Relay Chat client |
||||
* Copyright (C) 2019 Tom-Andre Barstad |
||||
* |
||||
* This program is free software; you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation; either version 2 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License along |
||||
* with this program; if not, write to the Free Software Foundation, Inc., |
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
||||
*/ |
||||
|
||||
#ifndef INPUTHANDLER_H |
||||
#define INPUTHANDLER_H |
||||
|
||||
#include "ICommand.h" |
||||
#include <QObject> |
||||
|
||||
class IWin; |
||||
class IRC; |
||||
class MdiManager; |
||||
|
||||
class InputHandler |
||||
{ |
||||
public: |
||||
explicit InputHandler(IRC& connection_); |
||||
|
||||
void parse(IWin& sender, const QString& text, bool handleCommand = true); |
||||
void parseCommand(const QString& text); |
||||
ICommand* getHandler() { return &cmdHndl; } |
||||
|
||||
private: |
||||
IRC& connection; |
||||
ICommand cmdHndl; |
||||
}; |
||||
|
||||
#endif // INPUTHANDLER_H
|
@ -0,0 +1,339 @@ |
||||
GNU GENERAL PUBLIC LICENSE |
||||
Version 2, June 1991 |
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc., |
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
||||
Everyone is permitted to copy and distribute verbatim copies |
||||
of this license document, but changing it is not allowed. |
||||
|
||||
Preamble |
||||
|
||||
The licenses for most software are designed to take away your |
||||
freedom to share and change it. By contrast, the GNU General Public |
||||
License is intended to guarantee your freedom to share and change free |
||||
software--to make sure the software is free for all its users. This |
||||
General Public License applies to most of the Free Software |
||||
Foundation's software and to any other program whose authors commit to |
||||
using it. (Some other Free Software Foundation software is covered by |
||||
the GNU Lesser General Public License instead.) You can apply it to |
||||
your programs, too. |
||||
|
||||
When we speak of free software, we are referring to freedom, not |
||||
price. Our General Public Licenses are designed to make sure that you |
||||
have the freedom to distribute copies of free software (and charge for |
||||
this service if you wish), that you receive source code or can get it |
||||
if you want it, that you can change the software or use pieces of it |
||||
in new free programs; and that you know you can do these things. |
||||
|
||||
To protect your rights, we need to make restrictions that forbid |
||||
anyone to deny you these rights or to ask you to surrender the rights. |
||||
These restrictions translate to certain responsibilities for you if you |
||||
distribute copies of the software, or if you modify it. |
||||
|
||||
For example, if you distribute copies of such a program, whether |
||||
gratis or for a fee, you must give the recipients all the rights that |
||||
you have. You must make sure that they, too, receive or can get the |
||||
source code. And you must show them these terms so they know their |
||||
rights. |
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and |
||||
(2) offer you this license which gives you legal permission to copy, |
||||
distribute and/or modify the software. |
||||
|
||||
Also, for each author's protection and ours, we want to make certain |
||||
that everyone understands that there is no warranty for this free |
||||
software. If the software is modified by someone else and passed on, we |
||||
want its recipients to know that what they have is not the original, so |
||||
that any problems introduced by others will not reflect on the original |
||||
authors' reputations. |
||||
|
||||
Finally, any free program is threatened constantly by software |
||||
patents. We wish to avoid the danger that redistributors of a free |
||||
program will individually obtain patent licenses, in effect making the |
||||
program proprietary. To prevent this, we have made it clear that any |
||||
patent must be licensed for everyone's free use or not licensed at all. |
||||
|
||||
The precise terms and conditions for copying, distribution and |
||||
modification follow. |
||||
|
||||
GNU GENERAL PUBLIC LICENSE |
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION |
||||
|
||||
0. This License applies to any program or other work which contains |
||||
a notice placed by the copyright holder saying it may be distributed |
||||
under the terms of this General Public License. The "Program", below, |
||||
refers to any such program or work, and a "work based on the Program" |
||||
means either the Program or any derivative work under copyright law: |
||||
that is to say, a work containing the Program or a portion of it, |
||||
either verbatim or with modifications and/or translated into another |
||||
language. (Hereinafter, translation is included without limitation in |
||||
the term "modification".) Each licensee is addressed as "you". |
||||
|
||||
Activities other than copying, distribution and modification are not |
||||
covered by this License; they are outside its scope. The act of |
||||
running the Program is not restricted, and the output from the Program |
||||
is covered only if its contents constitute a work based on the |
||||
Program (independent of having been made by running the Program). |
||||
Whether that is true depends on what the Program does. |
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's |
||||
source code as you receive it, in any medium, provided that you |
||||
conspicuously and appropriately publish on each copy an appropriate |
||||
copyright notice and disclaimer of warranty; keep intact all the |
||||
notices that refer to this License and to the absence of any warranty; |
||||
and give any other recipients of the Program a copy of this License |
||||
along with the Program. |
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and |
||||
you may at your option offer warranty protection in exchange for a fee. |
||||
|
||||
2. You may modify your copy or copies of the Program or any portion |
||||
of it, thus forming a work based on the Program, and copy and |
||||
distribute such modifications or work under the terms of Section 1 |
||||
above, provided that you also meet all of these conditions: |
||||
|
||||
a) You must cause the modified files to carry prominent notices |
||||
stating that you changed the files and the date of any change. |
||||
|
||||
b) You must cause any work that you distribute or publish, that in |
||||
whole or in part contains or is derived from the Program or any |
||||
part thereof, to be licensed as a whole at no charge to all third |
||||
parties under the terms of this License. |
||||
|
||||
c) If the modified program normally reads commands interactively |
||||
when run, you must cause it, when started running for such |
||||
interactive use in the most ordinary way, to print or display an |
||||
announcement including an appropriate copyright notice and a |
||||
notice that there is no warranty (or else, saying that you provide |
||||
a warranty) and that users may redistribute the program under |
||||
these conditions, and telling the user how to view a copy of this |
||||
License. (Exception: if the Program itself is interactive but |
||||
does not normally print such an announcement, your work based on |
||||
the Program is not required to print an announcement.) |
||||
|
||||
These requirements apply to the modified work as a whole. If |
||||
identifiable sections of that work are not derived from the Program, |
||||
and can be reasonably considered independent and separate works in |
||||
themselves, then this License, and its terms, do not apply to those |
||||
sections when you distribute them as separate works. But when you |
||||
distribute the same sections as part of a whole which is a work based |
||||
on the Program, the distribution of the whole must be on the terms of |
||||
this License, whose permissions for other licensees extend to the |
||||
entire whole, and thus to each and every part regardless of who wrote it. |
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest |
||||
your rights to work written entirely by you; rather, the intent is to |
||||
exercise the right to control the distribution of derivative or |
||||
collective works based on the Program. |
||||
|
||||
In addition, mere aggregation of another work not based on the Program |
||||
with the Program (or with a work based on the Program) on a volume of |
||||
a storage or distribution medium does not bring the other work under |
||||
the scope of this License. |
||||
|
||||
3. You may copy and distribute the Program (or a work based on it, |
||||
under Section 2) in object code or executable form under the terms of |
||||
Sections 1 and 2 above provided that you also do one of the following: |
||||
|
||||
a) Accompany it with the complete corresponding machine-readable |
||||
source code, which must be distributed under the terms of Sections |
||||
1 and 2 above on a medium customarily used for software interchange; or, |
||||
|
||||
b) Accompany it with a written offer, valid for at least three |
||||
years, to give any third party, for a charge no more than your |
||||
cost of physically performing source distribution, a complete |
||||
machine-readable copy of the corresponding source code, to be |
||||
distributed under the terms of Sections 1 and 2 above on a medium |
||||
customarily used for software interchange; or, |
||||
|
||||
c) Accompany it with the information you received as to the offer |
||||
to distribute corresponding source code. (This alternative is |
||||
allowed only for noncommercial distribution and only if you |
||||
received the program in object code or executable form with such |
||||
an offer, in accord with Subsection b above.) |
||||
|
||||
The source code for a work means the preferred form of the work for |
||||
making modifications to it. For an executable work, complete source |
||||
code means all the source code for all modules it contains, plus any |
||||
associated interface definition files, plus the scripts used to |
||||
control compilation and installation of the executable. However, as a |
||||
special exception, the source code distributed need not include |
||||
anything that is normally distributed (in either source or binary |
||||
form) with the major components (compiler, kernel, and so on) of the |
||||
operating system on which the executable runs, unless that component |
||||
itself accompanies the executable. |
||||
|
||||
If distribution of executable or object code is made by offering |
||||
access to copy from a designated place, then offering equivalent |
||||
access to copy the source code from the same place counts as |
||||
distribution of the source code, even though third parties are not |
||||
compelled to copy the source along with the object code. |
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program |
||||
except as expressly provided under this License. Any attempt |
||||
otherwise to copy, modify, sublicense or distribute the Program is |
||||
void, and will automatically terminate your rights under this License. |
||||
However, parties who have received copies, or rights, from you under |
||||
this License will not have their licenses terminated so long as such |
||||
parties remain in full compliance. |
||||
|
||||
5. You are not required to accept this License, since you have not |
||||
signed it. However, nothing else grants you permission to modify or |
||||
distribute the Program or its derivative works. These actions are |
||||
prohibited by law if you do not accept this License. Therefore, by |
||||
modifying or distributing the Program (or any work based on the |
||||
Program), you indicate your acceptance of this License to do so, and |
||||
all its terms and conditions for copying, distributing or modifying |
||||
the Program or works based on it. |
||||
|
||||
6. Each time you redistribute the Program (or any work based on the |
||||
Program), the recipient automatically receives a license from the |
||||
original licensor to copy, distribute or modify the Program subject to |
||||
these terms and conditions. You may not impose any further |
||||
restrictions on the recipients' exercise of the rights granted herein. |
||||
You are not responsible for enforcing compliance by third parties to |
||||
this License. |
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent |
||||
infringement or for any other reason (not limited to patent issues), |
||||
conditions are imposed on you (whether by court order, agreement or |
||||
otherwise) that contradict the conditions of this License, they do not |
||||
excuse you from the conditions of this License. If you cannot |
||||
distribute so as to satisfy simultaneously your obligations under this |
||||
License and any other pertinent obligations, then as a consequence you |
||||
may not distribute the Program at all. For example, if a patent |
||||
license would not permit royalty-free redistribution of the Program by |
||||
all those who receive copies directly or indirectly through you, then |
||||
the only way you could satisfy both it and this License would be to |
||||
refrain entirely from distribution of the Program. |
||||
|
||||
If any portion of this section is held invalid or unenforceable under |
||||
any particular circumstance, the balance of the section is intended to |
||||
apply and the section as a whole is intended to apply in other |
||||
circumstances. |
||||
|
||||
It is not the purpose of this section to induce you to infringe any |
||||
patents or other property right claims or to contest validity of any |
||||
such claims; this section has the sole purpose of protecting the |
||||
integrity of the free software distribution system, which is |
||||
implemented by public license practices. Many people have made |
||||
generous contributions to the wide range of software distributed |
||||
through that system in reliance on consistent application of that |
||||
system; it is up to the author/donor to decide if he or she is willing |
||||
to distribute software through any other system and a licensee cannot |
||||
impose that choice. |
||||
|
||||
This section is intended to make thoroughly clear what is believed to |
||||
be a consequence of the rest of this License. |
||||
|
||||
8. If the distribution and/or use of the Program is restricted in |
||||
certain countries either by patents or by copyrighted interfaces, the |
||||
original copyright holder who places the Program under this License |
||||
may add an explicit geographical distribution limitation excluding |
||||
those countries, so that distribution is permitted only in or among |
||||
countries not thus excluded. In such case, this License incorporates |
||||
the limitation as if written in the body of this License. |
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions |
||||
of the General Public License from time to time. Such new versions will |
||||
be similar in spirit to the present version, but may differ in detail to |
||||
address new problems or concerns. |
||||
|
||||
Each version is given a distinguishing version number. If the Program |
||||
specifies a version number of this License which applies to it and "any |
||||
later version", you have the option of following the terms and conditions |
||||
either of that version or of any later version published by the Free |
||||
Software Foundation. If the Program does not specify a version number of |
||||
this License, you may choose any version ever published by the Free Software |
||||
Foundation. |
||||
|
||||
10. If you wish to incorporate parts of the Program into other free |
||||
programs whose distribution conditions are different, write to the author |
||||
to ask for permission. For software which is copyrighted by the Free |
||||
Software Foundation, write to the Free Software Foundation; we sometimes |
||||
make exceptions for this. Our decision will be guided by the two goals |
||||
of preserving the free status of all derivatives of our free software and |
||||
of promoting the sharing and reuse of software generally. |
||||
|
||||
NO WARRANTY |
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY |
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN |
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES |
||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED |
||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF |
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS |
||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE |
||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, |
||||
REPAIR OR CORRECTION. |
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING |
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR |
||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, |
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING |
||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED |
||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY |
||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER |
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE |
||||
POSSIBILITY OF SUCH DAMAGES. |
||||
|
||||
END OF TERMS AND CONDITIONS |
||||
|
||||
How to Apply These Terms to Your New Programs |
||||
|
||||
If you develop a new program, and you want it to be of the greatest |
||||
possible use to the public, the best way to achieve this is to make it |
||||
free software which everyone can redistribute and change under these terms. |
||||
|
||||
To do so, attach the following notices to the program. It is safest |
||||
to attach them to the start of each source file to most effectively |
||||
convey the exclusion of warranty; and each file should have at least |
||||
the "copyright" line and a pointer to where the full notice is found. |
||||
|
||||
<one line to give the program's name and a brief idea of what it does.> |
||||
Copyright (C) <year> <name of author> |
||||
|
||||
This program is free software; you can redistribute it and/or modify |
||||
it under the terms of the GNU General Public License as published by |
||||
the Free Software Foundation; either version 2 of the License, or |
||||
(at your option) any later version. |
||||
|
||||
This program is distributed in the hope that it will be useful, |
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
GNU General Public License for more details. |
||||
|
||||
You should have received a copy of the GNU General Public License along |
||||
with this program; if not, write to the Free Software Foundation, Inc., |
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
||||
|
||||
Also add information on how to contact you by electronic and paper mail. |
||||
|
||||
If the program is interactive, make it output a short notice like this |
||||
when it starts in an interactive mode: |
||||
|
||||
Gnomovision version 69, Copyright (C) year name of author |
||||
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. |
||||
This is free software, and you are welcome to redistribute it |
||||
under certain conditions; type `show c' for details. |
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate |
||||
parts of the General Public License. Of course, the commands you use may |
||||
be called something other than `show w' and `show c'; they could even be |
||||
mouse-clicks or menu items--whatever suits your program. |
||||
|
||||
You should also get your employer (if you work as a programmer) or your |
||||
school, if any, to sign a "copyright disclaimer" for the program, if |
||||
necessary. Here is a sample; alter the names: |
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program |
||||
`Gnomovision' (which makes passes at compilers) written by James Hacker. |
||||
|
||||
<signature of Ty Coon>, 1 April 1989 |
||||
Ty Coon, President of Vice |
||||
|
||||
This General Public License does not permit incorporating your program into |
||||
proprietary programs. If your program is a subroutine library, you may |
||||
consider it more useful to permit linking proprietary applications with the |
||||
library. If this is what you want to do, use the GNU Lesser General |
||||
Public License instead of this License. |
@ -0,0 +1,329 @@ |
||||
/*
|
||||
* IdealIRC - Internet Relay Chat client |
||||
* Copyright (C) 2019 Tom-Andre Barstad |
||||
* |
||||
* This program is free software; you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation; either version 2 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License along |
||||
* with this program; if not, write to the Free Software Foundation, Inc., |
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
||||
*/ |
||||
|
||||
#include "IWin/IWinStatus.h" |
||||
#include "IWin/IWinChannel.h" |
||||
#include "IWin/IWinPrivate.h" |
||||
#include "MdiManager.h" |
||||
#include "ConfigMgr.h" |
||||
#include <QApplication> |
||||
#include <QDebug> |
||||
|
||||
namespace { |
||||
constexpr int WINDOW_DEFAULT_WIDTH = 800; |
||||
constexpr int WINDOW_DEFAULT_HEIGHT = 600; |
||||
constexpr int WINDOW_STEP = 30; |
||||
constexpr int WINDOW_STEP_RESET_POSITION = 400; |
||||
constexpr int WINDOW_STEP_RESET_ADJUST = 60; |
||||
} |
||||
|
||||
MdiManager* MdiManager::m_instance{ nullptr }; |
||||
|
||||
MdiManager::MdiManager(QMdiArea& mdiArea, QToolBar& buttonBar, QSystemTrayIcon& trayIcon) |
||||
: m_mdiArea(mdiArea) |
||||
, trayIcon(trayIcon) |
||||
, m_bbMgr(&buttonBar) |
||||
{ |
||||
m_instance = this; |
||||
connect(&m_mdiArea, &QMdiArea::subWindowActivated, this, &MdiManager::subwinActivated); |
||||
connect(&m_bbMgr, &ButtonbarMgr::changeWindow, &m_mdiArea, &QMdiArea::setActiveSubWindow); |
||||
} |
||||
|
||||
MdiManager& MdiManager::instance() |
||||
{ |
||||
return *m_instance; |
||||
} |
||||
|
||||
MdiManager::ButtonBarEntry::ButtonBarEntry(QAction* button_, const QString& buttonText, QMdiSubWindow* parent) |
||||
: subwin(parent) |
||||
, button(button_) |
||||
{ |
||||
menu = new QMenu(parent); |
||||
menuHead = menu->addAction(buttonText); |
||||
QFont f = menuHead->font(); |
||||
f.setBold(true); |
||||
menuHead->setFont(f); |
||||
menuHead->setDisabled(true); |
||||
menuSep = menu->addSeparator(); |
||||
menuClose = menu->addAction(tr("Close")); |
||||
connect(menuClose, &QAction::triggered, [&](bool){ |
||||
subwin->close(); |
||||
}); |
||||
} |
||||
|
||||
IWin* MdiManager::createSubwindow(IWin* parent, const QString& buttonText, IWin::Type windowType, bool activate) |
||||
{ |
||||
IWin* basePtr{ nullptr }; |
||||
IWinStatus* statusParent{ nullptr }; |
||||
QMdiSubWindow* mdiwin{ nullptr }; |
||||
|
||||
if (parent && parent->getType() == IWin::Type::Status) |
||||
statusParent = dynamic_cast<IWinStatus*>(parent); |
||||
|
||||
switch (windowType) { |
||||
case IWin::Type::Status: |
||||
basePtr = new IWinStatus(buttonText); |
||||
m_activeStatus = dynamic_cast<IWinStatus*>(basePtr); |
||||
break; |
||||
|
||||
case IWin::Type::Channel: |
||||
basePtr = new IWinChannel(statusParent, buttonText); |
||||
break; |
||||
|
||||
case IWin::Type::Private: |
||||
basePtr = new IWinPrivate(statusParent, buttonText); |
||||
break; |
||||
|
||||
case IWin::Type::Custom: |
||||
[[fallthrough]]; // Use createCustomSubwindow; treat this as an error.
|
||||
|
||||
case IWin::Type::Undefined: |
||||
break; |
||||
} |
||||
|
||||
if (!basePtr) { |
||||
qWarning() << "Failed to create 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); |
||||
mdiwin->setAttribute(Qt::WA_DeleteOnClose); |
||||
m_bbMgr.addButton(basePtr, mdiwin); |
||||
|
||||
QRect spawnCoord = generateSpawnCoordinates(); |
||||
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(); |
||||
} |
||||
|
||||
return basePtr; |
||||
} |
||||
|
||||
IWin* MdiManager::currentWindow() const |
||||
{ |
||||
return m_active; |
||||
} |
||||
|
||||
IWinStatus* MdiManager::currentStatus() const |
||||
{ |
||||
return m_activeStatus; |
||||
} |
||||
|
||||
IWin* MdiManager::findWindow(IWin* statusParent, const QString& buttonText) |
||||
{ |
||||
for (QMdiSubWindow* mdiwin : m_mdiArea.subWindowList()) { |
||||
IWin* subwin = dynamic_cast<IWin*>(mdiwin->widget()); |
||||
if (subwin->getParent() == statusParent && buttonText.compare(subwin->getButtonText(), Qt::CaseInsensitive) == 0) { |
||||
subwin->setButtonText(buttonText); |
||||
renameWindowButton(subwin, buttonText); |
||||
return subwin; |
||||
} |
||||
} |
||||
return nullptr; |
||||
} |
||||
|
||||
QMdiSubWindow* MdiManager::toMdiwin(IWin* win) |
||||
{ |
||||
for (QMdiSubWindow* mdiwin : m_mdiArea.subWindowList()) { |
||||
if (mdiwin->widget() == win) |
||||
return mdiwin; |
||||
} |
||||
return nullptr; |
||||
} |
||||
|
||||
QList<QMdiSubWindow*> MdiManager::mdiChildrenOf(const IWin* statusParent, IWin::Type type) const |
||||
{ |
||||
QList<QMdiSubWindow*> retlist; |
||||
for (QMdiSubWindow* mdiwin : m_mdiArea.subWindowList()) { |
||||
IWin* subwin = dynamic_cast<IWin*>(mdiwin->widget()); |
||||
if (type != IWin::Type::Undefined && subwin->getType() != type) |
||||
continue; |
||||
|
||||
if (subwin->getParent() == statusParent) |
||||
retlist.push_back(mdiwin); |
||||
} |
||||
return retlist; |
||||
} |
||||
|
||||
QList<IWin*> MdiManager::childrenOf(const IWin* statusParent, IWin::Type type) const |
||||
{ |
||||
QList<IWin*> retlist; |
||||
QList<QMdiSubWindow*> subwinList = mdiChildrenOf(statusParent, type); |
||||
for (QMdiSubWindow* subwin : subwinList) |
||||
retlist.push_back(qobject_cast<IWin*>(subwin->widget())); |
||||
return retlist; |
||||
} |
||||
|
||||
void MdiManager::showTrayInfo(const QString& title, const QString& message) |
||||
{ |
||||
showTray(title, message, QSystemTrayIcon::MessageIcon::Information); |
||||
} |
||||
|
||||
void MdiManager::showTrayWarn(const QString& title, const QString& message) |
||||
{ |
||||
showTray(title, message, QSystemTrayIcon::MessageIcon::Warning); |
||||
} |
||||
|
||||
int MdiManager::connectionsOnlineCount() const |
||||
{ |
||||
int c = 0; |
||||
|
||||
for (QMdiSubWindow* mdiwin : m_mdiArea.subWindowList()) { |
||||
IWin* subwin = dynamic_cast<IWin*>(mdiwin->widget()); |
||||
if (subwin->getType() != IWin::Type::Status) |
||||
continue; |
||||
IWinStatus* status = dynamic_cast<IWinStatus*>(subwin); |
||||
if (status->getConnection().isOnline()) |
||||
++c; |
||||
} |
||||
return c; |
||||
} |
||||
|
||||
void MdiManager::broadcastProgramExit() |
||||
{ |
||||
for (QMdiSubWindow* mdiwin : m_mdiArea.subWindowList()) { |
||||
IWin* subwin = dynamic_cast<IWin*>(mdiwin->widget()); |
||||
if (subwin->getType() != IWin::Type::Status) |
||||
continue; |
||||
IWinStatus* status = dynamic_cast<IWinStatus*>(subwin); |
||||
if (status->getConnection().isOnline()) { |
||||
connect(&status->getConnection(), &IRC::readyForExit, [this](){ |
||||
if (connectionsOnlineCount() == 0) |
||||
emit readyForExit(); |
||||
}); |
||||
|
||||
ConfigMgr& conf = ConfigMgr::instance(); |
||||
status->getConnection().disconnectForExit(conf.common("QuitMessage")); |
||||
} |
||||
} |
||||
} |
||||
|
||||
void MdiManager::renameWindowButton(IWin* window, const QString& text) |
||||
{ |
||||
m_bbMgr.reloadButtonName(window, text); |
||||
} |
||||
|
||||
void MdiManager::print(IWinStatus* parent, const QString& target, const PrintType ptype, const QString& text) |
||||
{ |
||||
IWin* subwin = findWindow(parent, target); |
||||
if (!subwin || !subwin->print(ptype, text)) { |
||||
if (ptype != PrintType::CTCP) |
||||
parent->print(ptype, QStringLiteral("[%1] %2").arg(target).arg(text)); |
||||
else |
||||
parent->print(ptype, text); |
||||
} |
||||
} |
||||
|
||||
void MdiManager::print(const QString& target, const PrintType ptype, const QString& text) |
||||
{ |
||||
print(m_activeStatus, target, ptype, text); |
||||
} |
||||
|
||||
void MdiManager::printToActive(IWinStatus* parent, const PrintType ptype, const QString& text) |
||||
{ |
||||
if (!m_active) |
||||
parent->print(ptype, text); |
||||
else if (m_active->getParent() != parent) |
||||
parent->print(ptype, text); |
||||
else |
||||
m_active->print(ptype, text); |
||||
} |
||||
|
||||
void MdiManager::printToTypes(IWinStatus* parent, const IWin::Type toType, const PrintType ptype, const QString& text) |
||||
{ |
||||
for (QMdiSubWindow* mdiwin : m_mdiArea.subWindowList()) { |
||||
IWin* subwin = qobject_cast<IWin*>(mdiwin->widget()); |
||||
if (subwin->getParent() == parent && subwin->getType() == toType) |
||||
subwin->print(ptype, text); |
||||
} |
||||
} |
||||
|
||||
void MdiManager::printToAll(IWinStatus* parent, const PrintType ptype, const QString& text) |
||||
{ |
||||
for (QMdiSubWindow* mdiwin : m_mdiArea.subWindowList()) { |
||||
IWin* subwin = qobject_cast<IWin*>(mdiwin->widget()); |
||||
if (subwin->getParent() == parent || subwin == parent) |
||||
subwin->print(ptype, text); |
||||
} |
||||
} |
||||
|
||||
void MdiManager::showTray(const QString& title, const QString& message, QSystemTrayIcon::MessageIcon icon) |
||||
{ |
||||
if (QApplication::activeWindow() || QApplication::focusWidget()) return; |
||||
|
||||
ConfigMgr& conf = ConfigMgr::instance(); |
||||
if (conf.common("TrayNotify") != "1") return; |
||||
int delay = conf.common("TrayNotifyDelay").toInt(); |
||||
trayIcon.showMessage(title, message, icon, delay*1000); |
||||
} |
||||
|
||||
void MdiManager::subwinAboutToClose(IWin* who) |
||||
{ |
||||
qInfo() << "Closing" << who; |
||||
m_bbMgr.delButton(who); |
||||
} |
||||
|
||||
void MdiManager::subwinActivated(QMdiSubWindow* window) |
||||
{ |
||||
if (!window) return; |
||||
|
||||
IWin* cw = dynamic_cast<IWin*>(window->widget()); |
||||
qInfo() << "Activated:" << cw; |
||||
if (!cw) { |
||||
qWarning() << "NULL window!"; |
||||
return; |
||||
} |
||||
m_bbMgr.subwinActivated(m_active, cw); |
||||
m_active = cw; |
||||
if (cw->getParent() && cw->getParent()->getType() == IWin::Type::Status) |
||||
m_activeStatus = dynamic_cast<IWinStatus*>(cw->getParent()); |
||||
else if (cw->getType() == IWin::Type::Status) |
||||
m_activeStatus = qobject_cast<IWinStatus*>(cw); |
||||
|
||||
emit subwindowSwitched(); |
||||
} |
||||
|
||||
QRect MdiManager::generateSpawnCoordinates() |
||||
{ |
||||
QRect rectangle(nextXY, nextXY, WINDOW_DEFAULT_WIDTH, WINDOW_DEFAULT_HEIGHT); |
||||
nextXY += WINDOW_STEP; |
||||
|
||||
if (nextXY > WINDOW_STEP_RESET_POSITION) { |
||||
nextXYadjust += WINDOW_STEP_RESET_ADJUST; |
||||
if (nextXYadjust > WINDOW_STEP_RESET_POSITION) |
||||
nextXYadjust = 0; |
||||
|
||||
nextXY = nextXYadjust; |
||||
} |
||||
|
||||
return rectangle; |
||||
} |
||||
|
@ -0,0 +1,99 @@ |
||||
/*
|
||||
* IdealIRC - Internet Relay Chat client |
||||
* Copyright (C) 2019 Tom-Andre Barstad |
||||
* |
||||
* This program is free software; you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation; either version 2 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License along |
||||
* with this program; if not, write to the Free Software Foundation, Inc., |
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
||||
*/ |
||||
|
||||
#ifndef MDIMANAGER_H |
||||
#define MDIMANAGER_H |
||||
|
||||
#include "IWin/IWin.h" |
||||
#include "ButtonbarMgr.h" |
||||
#include <QObject> |
||||
#include <QToolBar> |
||||
#include <QTreeView> |
||||
#include <QMdiArea> |
||||
#include <QMdiSubWindow> |
||||
#include <QSystemTrayIcon> |
||||
#include <QList> |
||||
#include <QMenu> |
||||
#include <memory> |
||||
|
||||
class IWinStatus; |
||||
|
||||
class MdiManager : public QObject |
||||
{ |
||||
Q_OBJECT |
||||
|
||||
public: |
||||
MdiManager(QMdiArea& mdiArea, QToolBar& buttonBar, QSystemTrayIcon& trayIcon); |
||||
|
||||
static MdiManager& instance(); |
||||
|
||||
IWin* createSubwindow(IWin* parent, const QString& buttonText, IWin::Type windowType, bool activate = true); |
||||
IWin* currentWindow() const; |
||||
IWinStatus* currentStatus() const; |
||||
|
||||
IWin* findWindow(IWin* statusParent, const QString& buttonText); |
||||
QMdiSubWindow* toMdiwin(IWin* win); |
||||
QList<QMdiSubWindow*> mdiChildrenOf(const IWin* statusParent, IWin::Type type = IWin::Type::Undefined) const; |
||||
QList<IWin*> childrenOf(const IWin* statusParent, IWin::Type type = IWin::Type::Undefined) const; |
||||
void showTrayInfo(const QString& title, const QString& message); |
||||
void showTrayWarn(const QString& title, const QString& message); |
||||
int connectionsOnlineCount() const; |
||||
void broadcastProgramExit(); |
||||
void renameWindowButton(IWin* window, const QString& text); |
||||
|
||||
void print(IWinStatus* parent, const QString& target, const PrintType ptype, const QString& text); |
||||
void print(const QString& target, const PrintType ptype, const QString& text); |
||||
void printToActive(IWinStatus* parent, const PrintType ptype, const QString& text); |
||||
void printToTypes(IWinStatus* parent, const IWin::Type toType, const PrintType ptype, const QString& text); |
||||
void printToAll(IWinStatus* parent, const PrintType ptype, const QString& text); |
||||
|
||||
private: |
||||
struct ButtonBarEntry { |
||||
ButtonBarEntry(){} |
||||
ButtonBarEntry(QAction* button_, const QString& buttonText, QMdiSubWindow* parent); |
||||
QMdiSubWindow* subwin{ nullptr }; |
||||
QAction* button{ nullptr }; |
||||
QMenu* menu{ nullptr }; |
||||
QAction* menuHead{ nullptr }; |
||||
QAction* menuSep{ nullptr }; |
||||
QAction* menuClose{ nullptr }; |
||||
}; |
||||
QMdiArea& m_mdiArea; |
||||
QSystemTrayIcon& trayIcon; |
||||
IWinStatus* m_activeStatus{ nullptr }; |
||||
IWin* m_active{ nullptr }; |
||||
ButtonbarMgr m_bbMgr; |
||||
|
||||
void showTray(const QString& title, const QString& message, QSystemTrayIcon::MessageIcon icon); |
||||
void subwinAboutToClose(IWin* who); |
||||
void subwinActivated(QMdiSubWindow *window); |
||||
|
||||
int nextXY{ 0 }; |
||||
int nextXYadjust{ 0 }; |
||||
QRect generateSpawnCoordinates(); |
||||
|
||||
static MdiManager* m_instance; |
||||
|
||||
signals: |
||||
void readyForExit(); |
||||
void subwindowSwitched(); |
||||
void connectionStateChange(); |
||||
}; |
||||
|
||||
#endif // MDIMANAGER_H
|
@ -0,0 +1,194 @@ |
||||
/*
|
||||
* IdealIRC - Internet Relay Chat client |
||||
* Copyright (C) 2019 Tom-Andre Barstad |
||||
* |
||||
* This program is free software; you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation; either version 2 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License along |
||||
* with this program; if not, write to the Free Software Foundation, Inc., |
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
||||
*/ |
||||
|
||||
#ifndef NUMERICS_H |
||||
#define NUMERICS_H |
||||
|
||||
namespace Numeric { |
||||
|
||||
/*
|
||||
* Error reply numerics |
||||
*/ |
||||
constexpr auto* ERR_NOSUCHNICK = "401"; |
||||
constexpr auto* ERR_NOSUCHSERVER = "402"; |
||||
constexpr auto* ERR_NOSUCHCHANNEL = "403"; |
||||
constexpr auto* ERR_CANNOTSENDTOCHAN = "404"; |
||||
constexpr auto* ERR_TOOMANYCHANNELS = "405"; |
||||
constexpr auto* ERR_WASNOSUCHNICK = "406"; |
||||
constexpr auto* ERR_TOOMANYTARGETS = "407"; |
||||
constexpr auto* ERR_NOORIGIN = "409"; |
||||
constexpr auto* ERR_NORECIPIENT = "411"; |
||||
constexpr auto* ERR_NOTEXTTOSEND = "412"; |
||||
constexpr auto* ERR_NOTOPLEVEL = "413"; |
||||
constexpr auto* ERR_WILDTOPLEVEL = "414"; |
||||
constexpr auto* ERR_UNKNOWNCOMMAND = "421"; |
||||
constexpr auto* ERR_NOMOTD = "422"; |
||||
constexpr auto* ERR_NOADMININFO = "423"; |
||||
constexpr auto* ERR_FILEERROR = "424"; |
||||
constexpr auto* ERR_NONICKNAMEGIVEN = "431"; |
||||
constexpr auto* ERR_ERRORNEUSNICKNAME = "432"; |
||||
constexpr auto* ERR_NICKNAMEINUSE = "433"; |
||||
constexpr auto* ERR_NICKCOLLISION = "436"; |
||||
constexpr auto* ERR_USERNOTINCHANNEL = "441"; |
||||
constexpr auto* ERR_NOTONCHANNEL = "442"; |
||||
constexpr auto* ERR_USERONCHANNEL = "443"; |
||||
constexpr auto* ERR_NOLOGIN = "444"; |
||||
constexpr auto* ERR_SUMMONDISABLED = "445"; |
||||
constexpr auto* ERR_USERSDISABLED = "446"; |
||||
constexpr auto* ERR_NOTREGISTERED = "451"; |
||||
constexpr auto* ERR_NEEDMOREPARAMS = "461"; |
||||
constexpr auto* ERR_ALREADYREGISTERED = "462"; |
||||
constexpr auto* ERR_NOPERMFORHOST = "463"; |
||||
constexpr auto* ERR_PASSWDMISMATCH = "464"; |
||||
constexpr auto* ERR_YOUREBANNEDCREEP = "465"; |
||||
constexpr auto* ERR_KEYSET = "467"; |
||||
constexpr auto* ERR_CHANNELISFULL = "471"; |
||||
constexpr auto* ERR_UNKNOWNMODE = "472"; |
||||
constexpr auto* ERR_INVITEONLYCHAN = "473"; |
||||
constexpr auto* ERR_BANNEDFROMCHAN = "474"; |
||||
constexpr auto* ERR_BADCHANNELKEY = "475"; |
||||
constexpr auto* ERR_NOPRIVILEGES = "481"; |
||||
constexpr auto* ERR_CHANOPRIVSNEEDED = "482"; |
||||
constexpr auto* ERR_CANTKILLSERVER = "483"; |
||||
constexpr auto* ERR_NOOPERHOST = "491"; |
||||
constexpr auto* ERR_UMODEUNKNOWNFLAG = "501"; |
||||
constexpr auto* ERR_USERSDONTMATCH = "502"; |
||||
|
||||
/*
|
||||
* Command reply numerics |
||||
*/ |
||||
constexpr auto* RPL_NONE = "300"; |
||||
constexpr auto* RPL_USERHOST = "302"; |
||||
constexpr auto* RPL_ISON = "303"; |
||||
constexpr auto* RPL_AWAY = "301"; |
||||
constexpr auto* RPL_UNAWAY = "305"; |
||||
constexpr auto* RPL_NOWAWAY = "306"; |
||||
constexpr auto* RPL_WHOISUSER = "311"; |
||||
constexpr auto* RPL_WHOISSERVER = "312"; |
||||
constexpr auto* RPL_WHOISOPERATOR = "313"; |
||||
constexpr auto* RPL_WHOISIDLE = "317"; |
||||
constexpr auto* RPL_ENDOFWHOIS = "318"; |
||||
constexpr auto* RPL_WHOISCHANNELS = "319"; |
||||
constexpr auto* RPL_WHOWASUSER = "314"; |
||||
constexpr auto* RPL_ENDOFWHOWAS = "369"; |
||||
constexpr auto* RPL_LISTSTART = "321"; |
||||
constexpr auto* RPL_LIST = "322"; |
||||
constexpr auto* RPL_LISTEND = "323"; |
||||
constexpr auto* RPL_CHANNELMODEIS = "324"; |
||||
constexpr auto* RPL_NOTOPIC = "331"; |
||||
constexpr auto* RPL_TOPIC = "332"; |
||||
constexpr auto* RPL_INVITING = "341"; |
||||
constexpr auto* RPL_SUMMONING = "342"; |
||||
constexpr auto* RPL_VERSION = "351"; |
||||
constexpr auto* RPL_WHOREPLY = "352"; |
||||
constexpr auto* RPL_ENDOFWHO = "315"; |
||||
constexpr auto* RPL_NAMREPLY = "353"; |
||||
constexpr auto* RPL_ENDOFNAMES = "366"; |
||||
constexpr auto* RPL_LINKS = "364"; |
||||
constexpr auto* RPL_ENDOFLINKS = "365"; |
||||
constexpr auto* RPL_BANLIST = "367"; |
||||
constexpr auto* RPL_ENDOFBANLIST = "368"; |
||||
constexpr auto* RPL_INFO = "371"; |
||||
constexpr auto* RPL_ENDOFINFO = "374"; |
||||
constexpr auto* RPL_MOTDSTART = "375"; |
||||
constexpr auto* RPL_MOTD = "372"; |
||||
constexpr auto* RPL_ENDOFMOTD = "376"; |
||||
constexpr auto* RPL_YOUREOPER = "381"; |
||||
constexpr auto* RPL_REHASHING = "382"; |
||||
constexpr auto* RPL_TIME = "391"; |
||||
constexpr auto* RPL_USERSSTART = "392"; |
||||
constexpr auto* RPL_USERS = "393"; |
||||
constexpr auto* RPL_ENDOFUSERS = "394"; |
||||
constexpr auto* RPL_NOUSERS = "395"; |
||||
constexpr auto* RPL_TRACELINK = "200"; |
||||
constexpr auto* RPL_TRACECONNECTING = "201"; |
||||
constexpr auto* RPL_TRACEHANDSHAKE = "202"; |
||||
constexpr auto* RPL_TRACEUNKNOWN = "203"; |
||||
constexpr auto* RPL_TRACEOPERATOR = "204"; |
||||
constexpr auto* RPL_TRACEUSER = "205"; |
||||
constexpr auto* RPL_TRACESERVER = "206"; |
||||
constexpr auto* RPL_TRACENEWTYPE = "208"; |
||||
constexpr auto* RPL_TRACELOG = "261"; |
||||
constexpr auto* RPL_STATSLINKINFO = "211"; |
||||
constexpr auto* RPL_STATSCOMMANDS = "212"; |
||||
constexpr auto* RPL_STATSCLINE = "213"; |
||||
constexpr auto* RPL_STATSNLINE = "214"; |
||||
constexpr auto* RPL_STATSILINE = "215"; |
||||
constexpr auto* RPL_STATSKLINE = "216"; |
||||
constexpr auto* RPL_STATSYLINE = "218"; |
||||
constexpr auto* RPL_ENDOFSTATS = "219"; |
||||
constexpr auto* RPL_STATSLLINE = "241"; |
||||
constexpr auto* RPL_STATSUPTIME = "242"; |
||||
constexpr auto* RPL_STATSOLINE = "243"; |
||||
constexpr auto* RPL_STATSHLINE = "244"; |
||||
constexpr auto* RPL_UMODEIS = "221"; |
||||
constexpr auto* RPL_LUSERCLIENT = "251"; |
||||
constexpr auto* RPL_LUSEROP = "252"; |
||||
constexpr auto* RPL_LUSERUNKNOWN = "253"; |
||||
constexpr auto* RPL_LUSERCHANNELS = "254"; |
||||
constexpr auto* RPL_LUSERME = "255"; |
||||
constexpr auto* RPL_ADMINME = "256"; |
||||
constexpr auto* RPL_ADMINLOC1 = "257"; |
||||
constexpr auto* RPL_ADMINLOC2 = "258"; |
||||
constexpr auto* RPL_ADMINEMAIL = "259"; |
||||
constexpr auto* RPL_INVITELIST = "346"; |
||||
constexpr auto* RPL_ENDOFINVITELIST = "347"; |
||||
constexpr auto* RPL_EXCEPTLIST = "348"; |
||||
constexpr auto* RPL_ENDOFEXCEPTLIST = "349"; |
||||
|
||||
/*
|
||||
* Reserved reply numerics |
||||
* |
||||
* From RFC1459: These numerics are not described above since they fall into |
||||
* one of the following categories: \n |
||||
* 1. no longer in use \n |
||||
* 2. reserved for future planned use \n |
||||
* 3. in current use but are part of a non-generic 'feature' of the current IRC server. |
||||
*/ |
||||
constexpr auto* RPL_WELCOME = "001"; |
||||
constexpr auto* RPL_MYINFO = "004"; |
||||
constexpr auto* RPL_ISUPPORT = "005"; |
||||
constexpr auto* RPL_TRACECLASS = "209"; |
||||
constexpr auto* RPL_SERVICEINFO = "231"; |
||||
constexpr auto* RPL_SERVICE = "233"; |
||||
constexpr auto* RPL_SERVLISTEND = "235"; |
||||
constexpr auto* RPL_WHOISCHANOP = "316"; |
||||
constexpr auto* RPL_CLOSING = "362"; |
||||
constexpr auto* RPL_INFOSTART = "373"; |
||||
constexpr auto* ERR_YOUWILLBEBANNED = "466"; |
||||
constexpr auto* ERR_NOSERVICEHOST = "492"; |
||||
constexpr auto* RPL_STATSQLINE = "217"; |
||||
constexpr auto* RPL_ENDOFSERVICES = "232"; |
||||
constexpr auto* RPL_SERVLIST = "234"; |
||||
constexpr auto* RPL_KILLDONE = "361"; |
||||
constexpr auto* RPL_CLOSEEND = "363"; |
||||
constexpr auto* RPL_MYPORTIS = "384"; |
||||
constexpr auto* RPL_BADCHANMASK = "476"; |
||||
constexpr auto* RPL_CREATION = "329"; |
||||
constexpr auto* RPL_TOPICBY = "333"; |
||||
constexpr auto* RPL_WHOISACTUALHOST = "378"; |
||||
constexpr auto* RPL_WHOISMODES = "379"; |
||||
constexpr auto* RPL_WHOISIDENTIFIED = "307"; |
||||
constexpr auto* RPL_WHOISLOGGEDIN = "330"; |
||||
constexpr auto* RPL_WHOISHELP = "310"; |
||||
constexpr auto* RPL_DISPLAYEDHOST = "396"; |
||||
|
||||
} // namespace Numeric
|
||||
|
||||
#endif // NUMERICS_H
|
@ -0,0 +1,30 @@ |
||||
IdealIRC - Internet Relay Chat client |
||||
(c) 2019 Tom-Andre Barstad and contributors. |
||||
http://www.idealirc.org/ |
||||
|
||||
Licensed under the GNU General Public License version 2. |
||||
See LICENSE for more licensing information. |
||||
|
||||
|
||||
INTRODUCTION |
||||
|
||||
The goal for IdealIRC is to provide a modern compliance of the IRC protocol. |
||||
It supports features from IRCv3 that fixes some of the issues of the protocol. |
||||
IdealIRC also supports connecting with SSL. |
||||
|
||||
IdealIRC aims to be a simple IRC client with only the bare minimum implemented. |
||||
The intention is to extend the client using scripts. |
||||
|
||||
|
||||
COMPILATION |
||||
|
||||
IdealIRC is written using Qt Creator. You should be able to build the software |
||||
purely using Qt Creator's build system. |
||||
|
||||
From command line (Linux) |
||||
First navigate to the source folder. |
||||
$ mkdir build |
||||
$ cd build |
||||
$ qmake .. |
||||
$ make -j `nproc` |
||||
After building is done, the executable "IdealIRC" is present in this directory. |
@ -0,0 +1,108 @@ |
||||
#include "Builtin.h" |
||||
#include "Script/Script.h" |
||||
|
||||
#include "Mathematics.h" |
||||
#include "StringUtils.h" |
||||
#include "GeneralUtils.h" |
||||
#include "MapUtils.h" |
||||
#include "ListUtils.h" |
||||
#include "DialogUtils.h" |
||||
|
||||
namespace { |
||||
bool alreadyRegistered = false; |
||||
} |
||||
|
||||
void registerBuiltinFunctions() |
||||
{ |
||||
if (alreadyRegistered) return; |
||||
alreadyRegistered = true; |
||||
|
||||
/*
|
||||
* Mathematical functions |
||||
*/ |
||||
Script::registerUniversalFunction("pow", &Builtin::Mathematics::pow); |
||||
Script::registerUniversalFunction("sqrt", &Builtin::Mathematics::sqrt); |
||||
Script::registerUniversalFunction("cbrt", &Builtin::Mathematics::cbrt); |
||||
Script::registerUniversalFunction("floor", &Builtin::Mathematics::floor); |
||||
Script::registerUniversalFunction("ceil", &Builtin::Mathematics::ceil); |
||||
Script::registerUniversalFunction("abs", &Builtin::Mathematics::abs); |
||||
Script::registerUniversalFunction("clamp", &Builtin::Mathematics::clamp); |
||||
Script::registerUniversalFunction("clamp01", &Builtin::Mathematics::clamp01); |
||||
Script::registerUniversalFunction("rand", &Builtin::Mathematics::rand); |
||||
Script::registerUniversalFunction("scale", &Builtin::Mathematics::scale); |
||||
Script::registerUniversalFunction("log", &Builtin::Mathematics::log); |
||||
Script::registerUniversalFunction("log10", &Builtin::Mathematics::log10); |
||||
Script::registerUniversalFunction("sin", &Builtin::Mathematics::sin); |
||||
Script::registerUniversalFunction("asin", &Builtin::Mathematics::asin); |
||||
Script::registerUniversalFunction("cos", &Builtin::Mathematics::cos); |
||||
Script::registerUniversalFunction("acos", &Builtin::Mathematics::acos); |
||||
Script::registerUniversalFunction("tan", &Builtin::Mathematics::tan); |
||||
Script::registerUniversalFunction("atan", &Builtin::Mathematics::atan); |
||||
|
||||
/*
|
||||
* String utilities |
||||
*/ |
||||
Script::registerUniversalFunction("strlen", &Builtin::StringUtils::strlen); |
||||
Script::registerUniversalFunction("strmid", &Builtin::StringUtils::strmid); |
||||
Script::registerUniversalFunction("strsplit", &Builtin::StringUtils::strsplit); |
||||
Script::registerUniversalFunction("strfind", &Builtin::StringUtils::strfind); |
||||
Script::registerUniversalFunction("strleft", &Builtin::StringUtils::strleft); |
||||
Script::registerUniversalFunction("strright", &Builtin::StringUtils::strright); |
||||
Script::registerUniversalFunction("strrep", &Builtin::StringUtils::strrep); |
||||
Script::registerUniversalFunction("resemblesint", &Builtin::StringUtils::resemblesint); |
||||
Script::registerUniversalFunction("resemblesreal", &Builtin::StringUtils::resemblesreal); |
||||
Script::registerUniversalFunction("resemblesbool", &Builtin::StringUtils::resemblesbool); |
||||
|
||||
/*
|
||||
* General utilities |
||||
*/ |
||||
Script::registerUniversalFunction("toint", &Builtin::GeneralUtils::toint); |
||||
Script::registerUniversalFunction("tostr", &Builtin::GeneralUtils::tostr); |
||||
Script::registerUniversalFunction("todouble", &Builtin::GeneralUtils::toreal); |
||||
Script::registerUniversalFunction("tobool", &Builtin::GeneralUtils::tobool); |
||||
Script::registerUniversalFunction("isvoid", &Builtin::GeneralUtils::isvoid); |
||||
Script::registerUniversalFunction("isint", &Builtin::GeneralUtils::isint); |
||||
Script::registerUniversalFunction("isreal", &Builtin::GeneralUtils::isreal); |
||||
Script::registerUniversalFunction("isbool", &Builtin::GeneralUtils::isbool); |
||||
Script::registerUniversalFunction("isstr", &Builtin::GeneralUtils::isstr); |
||||
Script::registerUniversalFunction("ismap", &Builtin::GeneralUtils::ismap); |
||||
Script::registerUniversalFunction("decimalsep", &Builtin::GeneralUtils::decimalsep); |
||||
|
||||
/*
|
||||
* Map utilities |
||||
*/ |
||||
Script::registerUniversalFunction("map", &Builtin::MapUtils::map); |
||||
Script::registerUniversalFunction("maplen", &Builtin::MapUtils::maplen); |
||||
Script::registerUniversalFunction("mapremove", &Builtin::MapUtils::mapremove); |
||||
Script::registerUniversalFunction("mapdump", &Builtin::MapUtils::mapdump); |
||||
|
||||
/*
|
||||
* List utilities |
||||
*/ |
||||
Script::registerUniversalFunction("list", &Builtin::ListUtils::list); |
||||
Script::registerUniversalFunction("listpush", &Builtin::ListUtils::listpush); |
||||
Script::registerUniversalFunction("listpop", &Builtin::ListUtils::listpop); |
||||
Script::registerUniversalFunction("listlen", &Builtin::ListUtils::listlen); |
||||
Script::registerUniversalFunction("listremove", &Builtin::ListUtils::listremove); |
||||
Script::registerUniversalFunction("listjoin", &Builtin::ListUtils::listjoin); |
||||
Script::registerUniversalFunction("listsplice", &Builtin::ListUtils::listsplice); |
||||
|
||||
/*
|
||||
* Dialog utilities |
||||
*/ |
||||
Script::registerUniversalFunction("dialog", &Builtin::DialogUtils::dialog); |
||||
Script::registerUniversalFunction("dlgvalidhndl", &Builtin::DialogUtils::dlgvalidhndl); |
||||
Script::registerUniversalFunction("dlgclose", &Builtin::DialogUtils::dlgclose); |
||||
Script::registerUniversalFunction("dlghide", &Builtin::DialogUtils::dlghide); |
||||
Script::registerUniversalFunction("dlgshow", &Builtin::DialogUtils::dlgshow); |
||||
Script::registerUniversalFunction("dlglist", &Builtin::DialogUtils::dlglist); |
||||
Script::registerUniversalFunction("dlgsetattr", &Builtin::DialogUtils::dlgsetattr); |
||||
Script::registerUniversalFunction("dlggetattr", &Builtin::DialogUtils::dlggetattr); |
||||
Script::registerUniversalFunction("dlgsetwidgetattr", &Builtin::DialogUtils::dlgsetwidgetattr); |
||||
Script::registerUniversalFunction("dlggetwidgetattr", &Builtin::DialogUtils::dlggetwidgetattr); |
||||
Script::registerUniversalFunction("dlgtablecount", &Builtin::DialogUtils::dlgtablecount); |
||||
Script::registerUniversalFunction("dlgtableinsert", &Builtin::DialogUtils::dlgtableinsert); |
||||
Script::registerUniversalFunction("dlgtableremove", &Builtin::DialogUtils::dlgtableremove); |
||||
Script::registerUniversalFunction("dlgtableselected", &Builtin::DialogUtils::dlgtableselected); |
||||
Script::registerUniversalFunction("dlgtablerow", &Builtin::DialogUtils::dlgtablerow); |
||||
} |
@ -0,0 +1,6 @@ |
||||
#ifndef BUILTIN_H |
||||
#define BUILTIN_H |
||||
|
||||
void registerBuiltinFunctions(); |
||||
|
||||
#endif // BUILTIN_H
|
@ -0,0 +1,247 @@ |
||||
#include "DialogUtils.h" |
||||
#include "Script/ValueExtract.h" |
||||
#include "Script/ScriptException.h" |
||||
#include "Error.h" |
||||
#include "Script/Script.h" |
||||
|
||||
using namespace Builtin::Error; |
||||
|
||||
namespace Builtin::DialogUtils { |
||||
|
||||
ValueHolder dialog(Script& script, std::vector<ValueHolder>& args) |
||||
{ |
||||
if (args.size() != 1) |
||||
throwInsufficientParameters("dialog", args.size(), 1); |
||||
|
||||
if (args[0].getType() != ValueHolder::Type::String) |
||||
throwNotAString("dialog", 1); |
||||
|
||||
return script.dialogConstruct(ValueExtract(args[0]).toString()); |
||||
} |
||||
|
||||
ValueHolder dlgvalidhndl(Script& script, std::vector<ValueHolder>& args) |
||||
{ |
||||
if (args.size() != 1) |
||||
throwInsufficientParameters("dlgvalidhndl", args.size(), 1); |
||||
|
||||
if (args[0].getType() != ValueHolder::Type::Integer) |
||||
return false; |
||||
|
||||
return script.dialogIsValidHandle(ValueExtract(args[0]).toInt()); |
||||
} |
||||
|
||||
ValueHolder dlgclose(Script& script, std::vector<ValueHolder>& args) |
||||
{ |
||||
if (args.size() != 1) |
||||
throwInsufficientParameters("dlgclose", args.size(), 1); |
||||
|
||||
if (args[0].getType() != ValueHolder::Type::Integer) |
||||
throwNotADialogHandle("dlgclose", 1); |
||||
|
||||
script.dialogClose(ValueExtract(args[0]).toInt()); |
||||
return ValueHolder(); |
||||
} |
||||
|
||||
ValueHolder dlghide(Script& script, std::vector<ValueHolder>& args) |
||||
{ |
||||
if (args.size() != 1) |
||||
throwInsufficientParameters("dlghide", args.size(), 1); |
||||
|
||||
if (args[0].getType() != ValueHolder::Type::Integer) |
||||
throwNotADialogHandle("dlghide", 1); |
||||
|
||||
script.dialogHide(ValueExtract(args[0]).toInt()); |
||||
return ValueHolder(); |
||||
} |
||||
|
||||
ValueHolder dlgshow(Script& script, std::vector<ValueHolder>& args) |
||||
{ |
||||
if (args.size() != 1) |
||||
throwInsufficientParameters("dlgshow", args.size(), 1); |
||||
|
||||
if (args[0].getType() != ValueHolder::Type::Integer) |
||||
throwNotADialogHandle("dlgshow", 1); |
||||
|
||||
script.dialogShow(ValueExtract(args[0]).toInt()); |
||||
return ValueHolder(); |
||||
} |
||||
|
||||
ValueHolder dlglist(Script& script, std::vector<ValueHolder>& args) |
||||
{ |
||||
if (args.size() != 1) |
||||
throwInsufficientParameters("dlglist", args.size(), 1); |
||||
|
||||
if (args[0].getType() != ValueHolder::Type::String) |
||||
throwNotAString("dlglist", 1); |
||||
|
||||
return script.dialogList(ValueExtract(args[0]).toString()); |
||||
} |
||||
|
||||
ValueHolder dlgsetattr(Script& script, std::vector<ValueHolder>& args) |
||||
{ |
||||
if (args.size() != 3) |
||||
throwInsufficientParameters("dlgsetattr", args.size(), 3); |
||||
|
||||
if (args[0].getType() != ValueHolder::Type::Integer) |
||||
throwNotADialogHandle("dlgsetattr", 1); |
||||
|
||||
if (args[1].getType() != ValueHolder::Type::String) |
||||
throwNotAString("dlgsetattr", 2); |
||||
|
||||
int handle = ValueExtract(args[0]).toInt(); |
||||
const auto& attribute = ValueExtract(args[1]).toString(); |
||||
ValueHolder& value = args[2]; |
||||
script.dialogSetAttr(handle, attribute, value); |
||||
|
||||
return ValueHolder(); |
||||
} |
||||
|
||||
ValueHolder dlggetattr(Script& script, std::vector<ValueHolder>& args) |
||||
{ |
||||
if (args.size() != 2) |
||||
throwInsufficientParameters("dlggetattr", args.size(), 2); |
||||
|
||||
if (args[0].getType() != ValueHolder::Type::Integer) |
||||
throwNotADialogHandle("dlggetattr", 1); |
||||
|
||||
int handle = ValueExtract(args[0]).toInt(); |
||||
const auto& attribute = ValueExtract(args[1]).toString(); |
||||
|
||||
return script.dialogGetAttr(handle, attribute); |
||||
} |
||||
|
||||
ValueHolder dlgsetwidgetattr(Script& script, std::vector<ValueHolder>& args) |
||||
{ |
||||
if (args.size() != 4) |
||||
throwInsufficientParameters("dlgsetwidgetattr", args.size(), 4); |
||||
|
||||
if (args[0].getType() != ValueHolder::Type::Integer) |
||||
throwNotADialogHandle("dlgsetwidgetattr", 1); |
||||
|
||||
if (args[1].getType() != ValueHolder::Type::String) |
||||
throwNotAString("dlgsetwidgetattr", 2); |
||||
|
||||
if (args[2].getType() != ValueHolder::Type::String) |
||||
throwNotAString("dlgsetwidgetattr", 3); |
||||
|
||||
int handle = ValueExtract(args[0]).toInt(); |
||||
const auto& widgetName = ValueExtract(args[1]).toString(); |
||||
const auto& attribute = ValueExtract(args[2]).toString(); |
||||
ValueHolder& value = args[3]; |
||||
script.dialogSetWidgetAttr(handle, widgetName, attribute, value); |
||||
|
||||
return ValueHolder(); |
||||
} |
||||
|
||||
ValueHolder dlggetwidgetattr(Script& script, std::vector<ValueHolder>& args) |
||||
{ |
||||
if (args.size() != 3) |
||||
throwInsufficientParameters("dlggetwidgetattr", args.size(), 3); |
||||
|
||||
if (args[0].getType() != ValueHolder::Type::Integer) |
||||
throwNotADialogHandle("dlggetwidgetattr", 1); |
||||
|
||||
if (args[1].getType() != ValueHolder::Type::String) |
||||
throwNotAString("dlgsetwidgetattr", 2); |
||||
|
||||
int handle = ValueExtract(args[0]).toInt(); |
||||
const auto& widgetName = ValueExtract(args[1]).toString(); |
||||
const auto& attribute = ValueExtract(args[2]).toString(); |
||||
|
||||
return script.dialogGetWidgetAttr(handle, widgetName, attribute); |
||||
} |
||||
|
||||
ValueHolder dlgtablecount(Script& script, std::vector<ValueHolder>& args) |
||||
{ |
||||
if (args.size() != 2) |
||||
throwInsufficientParameters("dlgtablecount", args.size(), 2); |
||||
|
||||
if (args[0].getType() != ValueHolder::Type::Integer) |
||||
throwNotADialogHandle("dlgtablecount", 1); |
||||
|
||||
if (args[1].getType() != ValueHolder::Type::String) |
||||
throwNotAString("dlgtablecount", 2); |
||||
|
||||
int handle = ValueExtract(args[0]).toInt(); |
||||
const auto& widgetName = ValueExtract(args[1]).toString(); |
||||
|
||||
return script.dialogTableWidgetCount(handle, widgetName); |
||||
} |
||||
|
||||
ValueHolder dlgtableinsert(Script& script, std::vector<ValueHolder>& args) |
||||
{ |
||||
if (args.size() != 3) |
||||
throwInsufficientParameters("dlgtableinsert", args.size(), 3); |
||||
|
||||
if (args[0].getType() != ValueHolder::Type::Integer) |
||||
throwNotADialogHandle("dlgtableinsert", 1); |
||||
|
||||
if (args[1].getType() != ValueHolder::Type::String) |
||||
throwNotAString("dlgtableinsert", 2); |
||||
|
||||
if (args[2].getType() != ValueHolder::Type::Map) |
||||
throwNotAMap("dlgtableinsert", 3); |
||||
|
||||
int handle = ValueExtract(args[0]).toInt(); |
||||
const auto& widgetName = ValueExtract(args[1]).toString(); |
||||
const auto& cols = ValueExtract(args[2]).toMap(); |
||||
|
||||
script.dialogTableWidgetInsert(handle, widgetName, cols); |
||||
return ValueHolder(); |
||||
} |
||||
|
||||
ValueHolder dlgtableremove(Script& script, std::vector<ValueHolder>& args) |
||||
{ |
||||
if (args.size() != 3) |
||||
throwInsufficientParameters("dlgtableremove", args.size(), 3); |
||||
|
||||
if (args[0].getType() != ValueHolder::Type::Integer) |
||||
throwNotADialogHandle("dlgtableremove", 1); |
||||
|
||||
if (args[1].getType() != ValueHolder::Type::String) |
||||
throwNotAString("dlgtableremove", 2); |
||||
|
||||
if (args[2].getType() != ValueHolder::Type::Integer) |
||||
throwNotANumber("dlgtableremove", 3); |
||||
|
||||
int handle = ValueExtract(args[0]).toInt(); |
||||
const auto& widgetName = ValueExtract(args[1]).toString(); |
||||
const auto& index = ValueExtract(args[2]).toInt(); |
||||
|
||||
script.dialogTableWidgetRemove(handle, widgetName, index); |
||||
return ValueHolder(); |
||||
} |
||||
|
||||
ValueHolder dlgtableselected(Script& script, std::vector<ValueHolder>& args) |
||||
{ |
||||
if (args.size() != 2) |
||||
throwInsufficientParameters("dlgtableselected", args.size(), 2); |
||||
|
||||
if (args[1].getType() != ValueHolder::Type::String) |
||||
throwNotAString("dlgtableselected", 2); |
||||
|
||||
int handle = ValueExtract(args[0]).toInt(); |
||||
const auto& widgetName = ValueExtract(args[1]).toString(); |
||||
|
||||
return script.dialogTableWidgetSelected(handle, widgetName); |
||||
} |
||||
|
||||
ValueHolder dlgtablerow(Script& script, std::vector<ValueHolder>& args) |
||||
{ |
||||
if (args.size() != 3) |
||||
throwInsufficientParameters("dlgtablerow", args.size(), 3); |
||||
|
||||
if (args[1].getType() != ValueHolder::Type::String) |
||||
throwNotAString("dlgtablerow", 2); |
||||
|
||||
if (args[2].getType() != ValueHolder::Type::Integer) |
||||
throwNotANumber("dlgtablerow", 3); |
||||
|
||||
int handle = ValueExtract(args[0]).toInt(); |
||||
const std::string& widgetName = ValueExtract(args[1]).toString(); |
||||
int row = ValueExtract(args[2]).toInt(); |
||||
|
||||
return script.dialogTableWidgetRow(handle, widgetName, row); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,28 @@ |
||||
#ifndef DIALOGUTILS_H |
||||
#define DIALOGUTILS_H |
||||
|
||||
#include "Script/ValueHolder.h" |
||||
#include <vector> |
||||
|
||||
class Script; |
||||
|
||||
namespace Builtin::DialogUtils { |
||||
ValueHolder dialog(Script& script, std::vector<ValueHolder>& args); |
||||
ValueHolder dlgvalidhndl(Script& script, std::vector<ValueHolder>& args); |
||||
ValueHolder dlgclose(Script& script, std::vector<ValueHolder>& args); |
||||
ValueHolder dlghide(Script& script, std::vector<ValueHolder>& args); |
||||
ValueHolder dlgshow(Script& script, std::vector<ValueHolder>& args); |
||||
ValueHolder dlglist(Script& script, std::vector<ValueHolder>& args); |
||||
ValueHolder dlgsetattr(Script& script, std::vector<ValueHolder>& args); |
||||
ValueHolder dlggetattr(Script& script, std::vector<ValueHolder>& args); |
||||
ValueHolder dlgsetwidgetattr(Script& script, std::vector<ValueHolder>& args); |
||||
ValueHolder dlggetwidgetattr(Script& script, std::vector<ValueHolder>& args); |
||||
|
||||
ValueHolder dlgtablecount(Script& script, std::vector<ValueHolder>& args); |
||||
ValueHolder dlgtableinsert(Script& script, std::vector<ValueHolder>& args); |
||||
ValueHolder dlgtableremove(Script& script, std::vector<ValueHolder>& args); |
||||
ValueHolder dlgtableselected(Script& script, std::vector<ValueHolder>& args); |
||||
ValueHolder dlgtablerow(Script& script, std::vector<ValueHolder>& args); |
||||
} |
||||
|
||||
#endif // DIALOGUTILS_H
|
@ -0,0 +1,94 @@ |
||||
#include "Error.h" |
||||
#include "Script/ScriptException.h" |
||||
#include <sstream> |
||||
|
||||
namespace { |
||||
//! Add the "st"/"nd"/"rd"/"th" suffixes. Only supports values up to 20.
|
||||
std::string ith(int i) |
||||
{ |
||||
std::string ret = std::to_string(i); |
||||
switch (i) { |
||||
case -1: [[fallthrough]]; |
||||
case 1: |
||||
ret += "st"; |
||||
break; |
||||
|
||||
case -2: [[fallthrough]]; |
||||
case 2: |
||||
ret += "nd"; |
||||
break; |
||||
|
||||
case -3: [[fallthrough]]; |
||||
case 3: |
||||
ret += "rd"; |
||||
break; |
||||
|
||||
default: |
||||
ret += "th"; |
||||
} |
||||
return ret; |
||||
} |
||||
} |
||||
|
||||
namespace Builtin::Error { |
||||
|
||||
void throwInsufficientParameters(const std::string& fname, size_t given, size_t required) |
||||
{ |
||||
std::stringstream ss; |
||||
ss << fname << ": Insufficient parameter count. " |
||||
<< required << " required, " << given << " given"; |
||||
throw ScriptException(ss.str()); |
||||
} |
||||
|
||||
void throwNotAString(const std::string& fname, int paramPos) |
||||
{ |
||||
throw ScriptException(fname + ": " + ith(paramPos) + " parameter must be a string"); |
||||
} |
||||
|
||||
void throwNotANumber(const std::string& fname, int paramPos) |
||||
{ |
||||
throw ScriptException(fname + ": " + ith(paramPos) + " parameter must be a number"); |
||||
} |
||||
|
||||
void throwNotAWholeNumber(const std::string& fname, int paramPos) |
||||
{ |
||||
throw ScriptException(fname + ": " + ith(paramPos) + " parameter must be a whole number"); |
||||
} |
||||
|
||||
void throwNotADialogHandle(const std::string& fname, int paramPos) |
||||
{ |
||||
throw ScriptException(fname + ": " + ith(paramPos) + " parameter must be a dialog handle (a whole number)"); |
||||
} |
||||
|
||||
void throwNotAMap(const std::string& fname, int paramPos) |
||||
{ |
||||
throw ScriptException(fname + ": " + ith(paramPos) + " parameter must be a map"); |
||||
} |
||||
|
||||
void throwNotAList(const std::string& fname, int paramPos) |
||||
{ |
||||
throw ScriptException(fname + ": " + ith(paramPos) + " parameter must be a list"); |
||||
} |
||||
|
||||
void throwIndexOutOfRange(const std::string& fname, int paramPos) |
||||
{ |
||||
throw ScriptException(fname + ": " + ith(paramPos) + " parameter is out of range"); |
||||
} |
||||
|
||||
|
||||
void throwNotAReference(const std::string& fname, int paramPos) |
||||
{ |
||||
throw ScriptException(fname + ": " + ith(paramPos) + " parameter must be a reference"); |
||||
} |
||||
|
||||
void throwNegativeInputUndefined(const std::string& fname) |
||||
{ |
||||
throw ScriptException(fname + ": Negative input is undefined"); |
||||
} |
||||
|
||||
void throwNumberMustBePositive(const std::string& fname, int paramPos) |
||||
{ |
||||
throw ScriptException(fname + ": " + ith(paramPos) + " parameter must be a positive number"); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,20 @@ |
||||
#ifndef ERROR_H |
||||
#define ERROR_H |
||||
|
||||
#include <string> |
||||
|
||||
namespace Builtin::Error { |
||||
[[noreturn]] void throwInsufficientParameters(const std::string& fname, size_t given, size_t required); |
||||
[[noreturn]] void throwNotAString(const std::string& fname, int paramPos); |
||||
[[noreturn]] void throwNotANumber(const std::string& fname, int paramPos); |
||||
[[noreturn]] void throwNotAWholeNumber(const std::string& fname, int paramPos); |
||||
[[noreturn]] void throwNotADialogHandle(const std::string& fname, int paramPos); |
||||
[[noreturn]] void throwNotAMap(const std::string& fname, int paramPos); |
||||
[[noreturn]] void throwNotAList(const std::string& fname, int paramPos); |
||||
[[noreturn]] void throwIndexOutOfRange(const std::string& fname, int paramPos); |
||||
[[noreturn]] void throwNotAReference(const std::string& fname, int paramPos); |
||||
[[noreturn]] void throwNegativeInputUndefined(const std::string& fname); |
||||
[[noreturn]] void throwNumberMustBePositive(const std::string& fname, int paramPos); |
||||
} |
||||
|
||||
#endif // ERROR_H
|
@ -0,0 +1,167 @@ |
||||
#include "GeneralUtils.h" |
||||
#include "Script/ValueExtract.h" |
||||
#include "Script/ScriptException.h" |
||||
#include "Error.h" |
||||
#include <locale> |
||||
#include <iostream> |
||||
|
||||
using namespace Builtin::Error; |
||||
|
||||
namespace { |
||||
inline std::string decimalSeparator() |
||||
{ |
||||
std::string ret; |
||||
ret += std::use_facet<std::numpunct<char>>(std::cout.getloc()).decimal_point(); |
||||
return ret; |
||||
} |
||||
} |
||||
|
||||
namespace Builtin::GeneralUtils { |
||||
|
||||
ValueHolder toint(Script&, std::vector<ValueHolder>& args) |
||||
{ |
||||
if (args.size() != 1) |
||||
throwInsufficientParameters("toint", args.size(), 1); |
||||
|
||||
using VType = ValueHolder::Type; |
||||
switch (args[0].getType()) { |
||||
case VType::Void: |
||||
throw ScriptException("toint: A void value cannot convert to int"); |
||||
case VType::Map: |
||||
throw ScriptException("toint: A map cannot convert to int"); |
||||
case VType::Integer: [[fallthrough]]; |
||||
case VType::Real: [[fallthrough]]; |
||||
case VType::Bool: |
||||
return ValueExtract(args[0]).toInt(); |
||||
case VType::String: |
||||
{ |
||||
const std::string& val = ValueExtract(args[0]).toString(); |
||||
if (val == "false") |
||||
return 0; |
||||
if (val == "true") |
||||
return 1; |
||||
return std::atoi(val.c_str()); |
||||
} |
||||
} |
||||
} |
||||
|
||||
ValueHolder tostr(Script&, std::vector<ValueHolder>& args) |
||||
{ |
||||
if (args.size() != 1) |
||||
throwInsufficientParameters("tostr", args.size(), 1); |
||||
|
||||
return args[0].toStdString(); |
||||
} |
||||
|
||||
ValueHolder toreal(Script&, std::vector<ValueHolder>& args) |
||||
{ |
||||
if (args.size() != 1) |
||||
throwInsufficientParameters("toreal", args.size(), 1); |
||||
|
||||
using VType = ValueHolder::Type; |
||||
switch (args[0].getType()) { |
||||
case VType::Void: |
||||
throw ScriptException("toint: A void value cannot convert to real"); |
||||
case VType::Map: |
||||
throw ScriptException("toint: A map cannot convert to double"); |
||||
case VType::Integer: [[fallthrough]]; |
||||
case VType::Real: [[fallthrough]]; |
||||
case VType::Bool: |
||||
return ValueExtract(args[0]).toReal(); |
||||
case VType::String: |
||||
{ |
||||
std::string val = ValueExtract(args[0]).toString(); |
||||
if (val == "false") |
||||
return 0.0; |
||||
if (val == "true") |
||||
return 1.0; |
||||
auto dotpos = val.find('.'); |
||||
if (dotpos != val.npos) |
||||
val.replace(dotpos, 1, decimalSeparator()); |
||||
return std::stod(val.c_str()); |
||||
} |
||||
} |
||||
} |
||||
|
||||
ValueHolder tobool(Script&, std::vector<ValueHolder>& args) |
||||
{ |
||||
if (args.size() != 1) |
||||
throwInsufficientParameters("tobool", args.size(), 1); |
||||
|
||||
using VType = ValueHolder::Type; |
||||
switch (args[0].getType()) { |
||||
case VType::Void: |
||||
throw ScriptException("toint: A void value cannot convert to bool"); |
||||
case VType::Map: |
||||
throw ScriptException("toint: A map cannot convert to bool"); |
||||
case VType::Integer: [[fallthrough]]; |
||||
case VType::Real: [[fallthrough]]; |
||||
case VType::Bool: |
||||
return ValueExtract(args[0]).toBool(); |
||||
case VType::String: |
||||
{ |
||||
std::string val = ValueExtract(args[0]).toString(); |
||||
if (val == "true" || val == "1") |
||||
return true; |
||||
else |
||||
return false; |
||||
} |
||||
} |
||||
} |
||||
|
||||
ValueHolder isvoid(Script&, std::vector<ValueHolder>& args) |
||||
{ |
||||
if (args.size() != 1) |
||||
throwInsufficientParameters("isvoid", args.size(), 1); |
||||
|
||||
return args[0].getType() == ValueHolder::Type::Void; |
||||
} |
||||
|
||||
ValueHolder isint(Script&, std::vector<ValueHolder>& args) |
||||
{ |
||||
if (args.size() != 1) |
||||
throwInsufficientParameters("isint", args.size(), 1); |
||||
|
||||
return args[0].getType() == ValueHolder::Type::Integer; |
||||
} |
||||
|
||||
ValueHolder isreal(Script&, std::vector<ValueHolder>& args) |
||||
{ |
||||
if (args.size() != 1) |
||||
throwInsufficientParameters("isreal", args.size(), 1); |
||||
|
||||
return args[0].getType() == ValueHolder::Type::Real; |
||||
} |
||||
|
||||
ValueHolder isbool(Script&, std::vector<ValueHolder>& args) |
||||
{ |
||||
if (args.size() != 1) |
||||
throwInsufficientParameters("isbool", args.size(), 1); |
||||
|
||||
return args[0].getType() == ValueHolder::Type::Bool; |
||||
} |
||||
|
||||
ValueHolder isstr(Script&, std::vector<ValueHolder>& args) |
||||
{ |
||||
if (args.size() != 1) |
||||
throwInsufficientParameters("isstring", args.size(), 1); |
||||
|
||||
return args[0].getType() == ValueHolder::Type::String; |
||||
} |
||||
|
||||
ValueHolder ismap(Script&, std::vector<ValueHolder>& args) |
||||
{ |
||||
if (args.size() != 1) |
||||
throwInsufficientParameters("ismap", args.size(), 1); |
||||
|
||||
return args[0].getType() == ValueHolder::Type::Map; |
||||
} |
||||
|
||||
ValueHolder decimalsep(Script&, std::vector<ValueHolder>& args) |
||||
{ |
||||
if (args.size() != 0) |
||||
throwInsufficientParameters("decimalsep", 0, 1); |
||||
return decimalSeparator(); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,23 @@ |
||||
#ifndef GENERALUTILS_H |
||||
#define GENERALUTILS_H |
||||
|
||||
#include "Script/ValueHolder.h" |
||||
#include <vector> |
||||
|
||||
class Script; |
||||
|
||||
namespace Builtin::GeneralUtils { |
||||
ValueHolder toint(Script&, std::vector<ValueHolder>& args); |
||||
ValueHolder tostr(Script&, std::vector<ValueHolder>& args); |
||||
ValueHolder toreal(Script&, std::vector<ValueHolder>& args); |
||||
ValueHolder tobool(Script&, std::vector<ValueHolder>& args); |
||||
ValueHolder isvoid(Script&, std::vector<ValueHolder>& args); |
||||
ValueHolder isint(Script&, std::vector<ValueHolder>& args); |
||||
ValueHolder isreal(Script&, std::vector<ValueHolder>& args); |
||||
ValueHolder isbool(Script&, std::vector<ValueHolder>& args); |
||||
ValueHolder isstr(Script&, std::vector<ValueHolder>& args); |
||||
ValueHolder ismap(Script&, std::vector<ValueHolder>& args); |
||||
ValueHolder decimalsep(Script&, std::vector<ValueHolder>& args); |
||||
} |
||||
|
||||
#endif // GENERALUTILS_H
|
@ -0,0 +1,196 @@ |
||||
#include "ListUtils.h" |
||||
#include "Error.h" |
||||
#include "Script/ValueExtract.h" |
||||
#include "Script/ScriptException.h" |
||||
#include <stdexcept> |
||||
|
||||
using namespace Builtin::Error; |
||||
|
||||
namespace Builtin::ListUtils { |
||||
|
||||
ValueHolder list(Script&, std::vector<ValueHolder>& args) |
||||
{ |
||||
ValueArray ret; |
||||
int idx = 0; |
||||
for (auto& val : args) |
||||
ret.emplace(std::to_string(idx++), new ValueHolder(std::move(val))); |
||||
ret.emplace(LIST_END_MAGIC, new ValueHolder(idx)); |
||||
return ValueHolder(std::move(ret)); |
||||
} |
||||
|
||||
ValueHolder listpush(Script&, std::vector<ValueHolder>& args) |
||||
{ |
||||
if (args.size() != 2) |
||||
throwInsufficientParameters("listpush", args.size(), 2); |
||||
|
||||
if (!args[0].isReference()) |
||||
throwNotAReference("listpush", 1); |
||||
|
||||
if (args[0].getType() != ValueHolder::Type::Map) |
||||
throwNotAList("listpush", 1); |
||||
|
||||
try { |
||||
ValueArray& arr = ValueExtract(args[0]).toMap(); |
||||
ValueHolder& endVal = *arr[LIST_END_MAGIC]; |
||||
arr.emplace(std::to_string(ValueExtract(endVal).toInt()), new ValueHolder(args[1])); |
||||
endVal = endVal + 1; |
||||
} catch (const std::out_of_range&) { |
||||
throwNotAList("listpush", 1); |
||||
} |
||||
return ValueHolder(); |
||||
} |
||||
|
||||
ValueHolder listpop(Script&, std::vector<ValueHolder>& args) |
||||
{ |
||||
if (args.size() != 1) |
||||
throwInsufficientParameters("listpop", args.size(), 1); |
||||
|
||||
if (!args[0].isReference()) |
||||
throwNotAReference("listpop", 1); |
||||
|
||||
if (args[0].getType() != ValueHolder::Type::Map) |
||||
throwNotAList("listpop", 1); |
||||
|
||||
try { |
||||
ValueArray& arr = ValueExtract(args[0]).toMap(); |
||||
ValueHolder& endVal = *arr[LIST_END_MAGIC]; |
||||
int end = ValueExtract(endVal).toInt(); |
||||
if (arr.size() == 0) |
||||
throw ScriptException("Cannot pop an empty list"); |
||||
ValueHolder ret = *arr.at(std::to_string(end - 1)); |
||||
arr.erase(std::to_string(end - 1)); |
||||
endVal = end - 1; |
||||
return ret; |
||||
} catch (const std::out_of_range&) { |
||||
throwNotAList("listpop", 1); |
||||
} |
||||
} |
||||
|
||||
ValueHolder listlen(Script&, std::vector<ValueHolder>& args) |
||||
{ |
||||
if (args.size() != 1) |
||||
throwInsufficientParameters("listlen", args.size(), 1); |
||||
|
||||
if (!args[0].isReference()) |
||||
throwNotAReference("listlen", 1); |
||||
|
||||
if (args[0].getType() != ValueHolder::Type::Map) |
||||
throwNotAList("listlen", 1); |
||||
|
||||
ValueArray& arr = ValueExtract(args[0]).toMap(); |
||||
if (arr.count(LIST_END_MAGIC) < 1) |
||||
throwNotAList("listlen", 1); |
||||
|
||||
return *arr[LIST_END_MAGIC]; |
||||
} |
||||
|
||||
ValueHolder listremove(Script&, std::vector<ValueHolder>& args) |
||||
{ |
||||
if (args.size() != 2) |
||||
throwInsufficientParameters("listremove", args.size(), 2); |
||||
|
||||
if (!args[0].isReference()) |
||||
throwNotAReference("listremove", 1); |
||||
|
||||
if (args[0].getType() != ValueHolder::Type::Map) |
||||
throwNotAList("listremove", 1); |
||||
|
||||
if (args[1].getType() != ValueHolder::Type::Integer) |
||||
throwNotAWholeNumber("listremove", 2); |
||||
|
||||
int index = ValueExtract(args[1]).toInt(); |
||||
ValueArray& arr = ValueExtract(args[0]).toMap(); |
||||
ValueHolder& endVal = *arr[LIST_END_MAGIC]; |
||||
int end = ValueExtract(endVal).toInt(); |
||||
|
||||
if (index < 0 || index >= end) |
||||
throwIndexOutOfRange("listremove", 2); |
||||
|
||||
arr.erase(std::to_string(index)); |
||||
for (int i = index + 1; i < end; ++i) { |
||||
auto node = arr.extract(std::to_string(i)); |
||||
node.key() = std::to_string(i - 1); |
||||
arr.insert(std::move(node)); |
||||
} |
||||
endVal = end - 1; |
||||
|
||||
return ValueHolder(); |
||||
} |
||||
|
||||
ValueHolder listjoin(Script&, std::vector<ValueHolder>& args) |
||||
{ |
||||
if (args.size() != 2) |
||||
throwInsufficientParameters("listjoin", args.size(), 2); |
||||
|
||||
if (!args[0].isReference()) |
||||
throwNotAReference("listjoin", 1); |
||||
|
||||
if (args[0].getType() != ValueHolder::Type::Map) |
||||
throwNotAList("listjoin", 1); |
||||
|
||||
if (!args[1].isReference()) |
||||
throwNotAReference("listjoin", 2); |
||||
|
||||
if (args[1].getType() != ValueHolder::Type::Map) |
||||
throwNotAList("listjoin", 2); |
||||
|
||||
ValueArray& M1arr = ValueExtract(args[0]).toMap(); |
||||
ValueHolder& M1endVal = *M1arr[LIST_END_MAGIC]; |
||||
int M1end = ValueExtract(M1endVal).toInt(); |
||||
|
||||
ValueArray& M2arr = ValueExtract(args[1]).toMap(); |
||||
ValueHolder& M2endVal = *M2arr[LIST_END_MAGIC]; |
||||
int M2end = ValueExtract(M2endVal).toInt(); |
||||
|
||||
for (int i = 0; i < M2end; ++i) { |
||||
const int newIdx = i + M1end; |
||||
auto& vh = *M2arr[std::to_string(i)]; |
||||
M1arr.emplace(std::to_string(newIdx), new ValueHolder(vh)); |
||||
} |
||||
M1endVal = M1end + M2end; |
||||
return ValueHolder(); |
||||
} |
||||
|
||||
ValueHolder listsplice(Script&, std::vector<ValueHolder>& args) |
||||
{ |
||||
if (args.size() != 2) |
||||
throwInsufficientParameters("listsplice", args.size(), 2); |
||||
|
||||
if (!args[0].isReference()) |
||||
throwNotAReference("listsplice", 1); |
||||
|
||||
if (args[0].getType() != ValueHolder::Type::Map) |
||||
throwNotAList("listsplice", 1); |
||||
|
||||
if (!args[1].isReference()) |
||||
throwNotAReference("listsplice", 2); |
||||
|
||||
if (args[1].getType() != ValueHolder::Type::Map) |
||||
throwNotAList("listsplice", 2); |
||||
|
||||
ValueArray ret; |
||||
|
||||
ValueArray& M1arr = ValueExtract(args[0]).toMap(); |
||||
ValueHolder& M1endVal = *M1arr[LIST_END_MAGIC]; |
||||
int M1end = ValueExtract(M1endVal).toInt(); |
||||
|
||||
for (int i = 0; i < M1end; ++i) { |
||||
auto& vh = *M1arr[std::to_string(i)]; |
||||
ret.emplace(std::to_string(i), new ValueHolder(vh)); |
||||
} |
||||
|
||||
ValueArray& M2arr = ValueExtract(args[1]).toMap(); |
||||
ValueHolder& M2endVal = *M2arr[LIST_END_MAGIC]; |
||||
int M2end = ValueExtract(M2endVal).toInt(); |
||||
|
||||
for (int i = 0; i < M2end; ++i) { |
||||
const int newIdx = i + M1end; |
||||
auto& vh = *M2arr[std::to_string(i)]; |
||||
ret.emplace(std::to_string(newIdx), new ValueHolder(vh)); |
||||
} |
||||
ret.emplace(LIST_END_MAGIC, new ValueHolder(M1end + M2end)); |
||||
|
||||
return ValueHolder(std::move(ret)); |
||||
} |
||||
|
||||
} |