Initial commit, pre 1.0.0

master
Tomatix 5 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)