Initial commit, pre 1.0.0

master
Tomatix 4 years ago
commit 10ec77f70a
  1. 20
      .gitignore
  2. 49
      AboutIIRC.cpp
  3. 41
      AboutIIRC.h
  4. 105
      AboutIIRC.ui
  5. 274
      ButtonbarMgr.cpp
  6. 77
      ButtonbarMgr.h
  7. 93
      Commands.h
  8. 294
      ConfigMgr.cpp
  9. 71
      ConfigMgr.h
  10. 599
      ICommand.cpp
  11. 39
      ICommand.h
  12. 50
      ICommand/CommandData.cpp
  13. 29
      ICommand/CommandData.h
  14. 373
      IConfig/ColorConfig.cpp
  15. 64
      IConfig/ColorConfig.h
  16. 165
      IConfig/IConfig.cpp
  17. 73
      IConfig/IConfig.h
  18. 225
      IConfig/IConfig.ui
  19. 119
      IConfig/IConfigLogging.cpp
  20. 56
      IConfig/IConfigLogging.h
  21. 85
      IConfig/IConfigLogging.ui
  22. 175
      IConfig/IConfigOptions.cpp
  23. 74
      IConfig/IConfigOptions.h
  24. 382
      IConfig/IConfigOptions.ui
  25. 137
      IConfig/IConfigServers.cpp
  26. 62
      IConfig/IConfigServers.h
  27. 247
      IConfig/IConfigServers.ui
  28. 325
      IConfig/ServerEditor.cpp
  29. 71
      IConfig/ServerEditor.h
  30. 251
      IConfig/ServerEditor.ui
  31. 145
      IConfig/ServerMgr.cpp
  32. 68
      IConfig/ServerMgr.h
  33. 371
      IConfig/ServerModel.cpp
  34. 63
      IConfig/ServerModel.h
  35. 137
      IConfig/todo/ServerItem.cpp
  36. 79
      IConfig/todo/ServerItem.h
  37. 184
      IConfig/todo/ServerModel.cpp
  38. 66
      IConfig/todo/ServerModel.h
  39. 435
      IRC.cpp
  40. 92
      IRC.h
  41. 79
      IRCClient/Commands.h
  42. 207
      IRCClient/DCC.cpp
  43. 73
      IRCClient/DCC.h
  44. 1261
      IRCClient/IRCBase.cpp
  45. 152
      IRCClient/IRCBase.h
  46. 85
      IRCClient/IRCChannel.cpp
  47. 49
      IRCClient/IRCChannel.h
  48. 105
      IRCClient/IRCError.cpp
  49. 50
      IRCClient/IRCError.h
  50. 43
      IRCClient/IRCMember.cpp
  51. 30
      IRCClient/IRCMember.h
  52. 55
      IRCClient/IRCMemberEntry.cpp
  53. 28
      IRCClient/IRCMemberEntry.h
  54. 111
      IRCClient/IRCPrefix.cpp
  55. 52
      IRCClient/IRCPrefix.h
  56. 175
      IRCClient/Numeric.h
  57. 104
      IRCClient/Utilities.cpp
  58. 14
      IRCClient/Utilities.h
  59. 62
      IWin/IWin.cpp
  60. 70
      IWin/IWin.h
  61. 219
      IWin/IWinChannel.cpp
  62. 76
      IWin/IWinChannel.h
  63. 101
      IWin/IWinPrivate.cpp
  64. 54
      IWin/IWinPrivate.h
  65. 205
      IWin/IWinStatus.cpp
  66. 72
      IWin/IWinStatus.h
  67. 214
      IWin/NicklistController.cpp
  68. 56
      IWin/NicklistController.h
  69. BIN
      Icons/add.png
  70. BIN
      Icons/exclamation.png
  71. BIN
      Icons/icon.png
  72. BIN
      Icons/iconcutted.png
  73. BIN
      Icons/log.png
  74. BIN
      Icons/options.png
  75. BIN
      Icons/refresh.png
  76. BIN
      Icons/remove.png
  77. BIN
      Icons/script.png
  78. BIN
      Icons/server.png
  79. 282
      IdealIRC.cpp
  80. 74
      IdealIRC.h
  81. 196
      IdealIRC.pro
  82. 175
      IdealIRC.ui
  83. 539
      IniFile.cpp
  84. 57
      IniFile.h
  85. 80
      InputHandler.cpp
  86. 44
      InputHandler.h
  87. 339
      LICENSE
  88. 329
      MdiManager.cpp
  89. 99
      MdiManager.h
  90. 194
      Numeric.h
  91. 30
      README
  92. 108
      Script/Builtin/Builtin.cpp
  93. 6
      Script/Builtin/Builtin.h
  94. 247
      Script/Builtin/DialogUtils.cpp
  95. 28
      Script/Builtin/DialogUtils.h
  96. 94
      Script/Builtin/Error.cpp
  97. 20
      Script/Builtin/Error.h
  98. 167
      Script/Builtin/GeneralUtils.cpp
  99. 23
      Script/Builtin/GeneralUtils.h
  100. 196
      Script/Builtin/ListUtils.cpp
  101. Some files were not shown because too many files have changed in this diff Show More

20
.gitignore vendored

@ -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>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Sans'; font-size:9pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p align=&quot;center&quot; style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;img src=&quot;:/Icons/icon.png&quot; /&gt;&lt;/p&gt;
&lt;p align=&quot;center&quot; style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Noto Sans'; font-size:16pt;&quot;&gt;IdealIRC {ver}&lt;/span&gt;&lt;/p&gt;
&lt;p align=&quot;center&quot; style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Sans';&quot;&gt;Build type: {buildtype}&lt;/span&gt;&lt;/p&gt;
&lt;p align=&quot;center&quot; style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Noto Sans'; font-size:10pt;&quot;&gt;©{year} Tom-Andre Barstad&lt;/span&gt;&lt;/p&gt;
&lt;p align=&quot;center&quot; style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Noto Sans'; font-size:10pt;&quot;&gt;and contributors.&lt;/span&gt;&lt;/p&gt;
&lt;p align=&quot;center&quot; style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;a href=&quot;http://www.idealirc.org/&quot;&gt;&lt;span style=&quot; font-family:'Sans'; font-size:8pt; text-decoration: underline; color:#007af4;&quot;&gt;http://www.idealirc.org/&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p align=&quot;center&quot; style=&quot;-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;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p align=&quot;center&quot; style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Noto Sans'; font-size:8pt;&quot;&gt;This program is free software; you can redistribute it and/or modify&lt;/span&gt;&lt;/p&gt;
&lt;p align=&quot;center&quot; style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Noto Sans'; font-size:8pt;&quot;&gt;it under the terms of the GNU General Public License as published by&lt;/span&gt;&lt;/p&gt;
&lt;p align=&quot;center&quot; style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Noto Sans'; font-size:8pt;&quot;&gt;the Free Software Foundation; either version 2 of the License, or&lt;/span&gt;&lt;/p&gt;
&lt;p align=&quot;center&quot; style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Noto Sans'; font-size:8pt;&quot;&gt;(at your option) any later version.&lt;/span&gt;&lt;/p&gt;
&lt;p align=&quot;center&quot; style=&quot;-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;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p align=&quot;center&quot; style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Noto Sans'; font-size:8pt;&quot;&gt;This program is distributed in the hope that it will be useful,&lt;/span&gt;&lt;/p&gt;
&lt;p align=&quot;center&quot; style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Noto Sans'; font-size:8pt;&quot;&gt;but WITHOUT ANY WARRANTY; without even the implied warranty of&lt;/span&gt;&lt;/p&gt;
&lt;p align=&quot;center&quot; style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Noto Sans'; font-size:8pt;&quot;&gt;MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the&lt;/span&gt;&lt;/p&gt;
&lt;p align=&quot;center&quot; style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Noto Sans'; font-size:8pt;&quot;&gt;GNU General Public License for more details.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</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 &amp;&amp; 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);
}

92
IRC.h

@ -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

File diff suppressed because it is too large Load Diff

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

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));
}
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save