Compare commits

...

81 Commits

Author SHA1 Message Date
Tomatix 48189c58a1 Version 1.1.0 2 years ago
Tomatix 4da5512456 #137 Added some popular servers to servers.json. Updated InnoSetup script to use servers.json. 2 years ago
Tomatix b8c2fd7d1b #143 Script engine: Renamed TokenType to STokenType yielding for some Windows API specific stuff. 3 years ago
Tomatix bb09217d7d #2 DCC with Windows OS specific implementation 3 years ago
Tomatix 433fdf747c #2 DCC with reverse initiation functionality and NAT integration. 3 years ago
Tomatix 29ea535243 #2 Inbound and outbound DCC chat, still need better NAT handling. 3 years ago
Tomatix 4bfc9b2380 #2 Added function to check if an IP is private (f.ex. 192.168.1.1) or not. Added functions to read the computer's own IP address and gateway IP address. Missing Windows implementation for that. 3 years ago
Tomatix 60768be2ce #2 Show a "not connected" message in the DCC Chat window if there is no connection while typing a message. 3 years ago
Tomatix 523e21d3db #2 Various improvements on existing, albeit incomplete DCC implementation in Core. DCC Chat implementation. DCC request dialog. 3 years ago
Tomatix c0c6a6eee1 Test release 1.1.0 dev8 3 years ago
Tomatix 3fae7faed0 #124 Use SASL credentials from config to Core client. 3 years ago
Tomatix 4c27c89967 #124 Correctly use PLAIN mechanism 3 years ago
Tomatix 9904699c43 #124 Added missing file from previous commit 3 years ago
Tomatix 5e159e2b9c #124 SASL PLAIN implemented in Core 3 years ago
Tomatix 7d955b7a8f #124 Base64 encoding implementation 3 years ago
Tomatix 8bae52c9e4 #124 SASL configuration. Client implementation not finished. 3 years ago
Tomatix 1bb74bf12c #134 Added checking of a modified ServerModel, raising a prompt if modified. If "No" is selected, the model is reset. If "Yes", model is saved to file as if by pressing the "Save" button. 3 years ago
Tomatix 8312170b71 #134 Saving servers complete, plus some small input checks with fallbacks. 3 years ago
Tomatix d7c744baa5 #134 Added a hack to the ServerModel that mitigates an issue with the server tree view where expanded networks won't show newly added servers, as well as expand/collapse decoration for empty networks doesn't show when adding a server to them. 3 years ago
Tomatix 50abd64932 #134 Delete server/network implemented 3 years ago
Tomatix ff81cabbb3 #134 When adding server without port in address, use defaults 6697 if SSL is checked, 6667 if not. 3 years ago
Tomatix 9185893037 #134 More work done on the new server config editing. 3 years ago
Tomatix c26715cf65 #134 Added an 'Add server' dialog, wip 3 years ago
Tomatix 2a600143d8 #133 Removed obsolete files for Server model 3 years ago
Tomatix f4a95a8eb1 #133 Better model for handling servers; using json (servers.json) for storing servers 3 years ago
Tomatix d032da0ce6 #127 Wrong logic, dangling member is when its channels _is_ empty. 4 years ago
Tomatix e5a8498e7c #132 Clear nickname list in channel when parting 4 years ago
Tomatix 8c8e85a263 #70 Check if subwindow is not nullptr 4 years ago
Tomatix b5dfe048a6 #70 Show QUIT message for user if their query window is open 4 years ago
Tomatix e81498fdaa Test release dev5 4 years ago
Tomatix 7c75677cec #127 Fixed removing and adding of members of channels 4 years ago
Tomatix cde36f9fea #125 ConfigMgr refactor. 4 years ago
Tomatix 1e3cd9a1a7 #121 Use specified IRCv3 features given from the configuration. 4 years ago
Tomatix 353b088dea #121 Instead of explicit enable/disable IRCv3 features, just specify which ones we need. 4 years ago
Tomatix 505231f9b2 #121 Added capability to enable/disable IRCv3 extensions for IRCClient. 4 years ago
Tomatix 34efc87086 #121 Connected ConfigMgr and the GUI frontend for IRCv3 configuration. 4 years ago
Tomatix 11c0d64c27 #121 Added configuration management for IRCv3 config 4 years ago
Tomatix 320cf68fff #121 Added GUI frontend for IRCv3 options. 4 years ago
Tomatix 9d19dc75ba #123 Hide "Background image" tab - it's useless for now. 4 years ago
Tomatix 3b3cdb860c Merge remote-tracking branch 'origin/1_1_0_dev' into 1_1_0_dev 4 years ago
Tomatix 9012607434 #91 Timestamp for chat history. 4 years ago
Tomatix 36c8fc20e7 #91 Timestamp for chat history / batch. 4 years ago
Tomatix 72de07fb30 #90 Added capability to print messages with custom timestamp 4 years ago
Tomatix d2974336de #90 Give read-only access to the current IRCMessage being parsed. 4 years ago
Tomatix 12158d9f10 #119 IRCv3 message tags. 4 years ago
Tomatix 02e09bccb6 #118 Fixed a bug in script engine with logical operators. 4 years ago
Tomatix 147d8b5905 #117 Fixed using /kick nickname while in a channel window. 4 years ago
Tomatix c417b137d6 #116 Reverting issue #106 - Mark connection as registered only upon receiving RPL_ENDOFMOTD or ERR_NOMOTD. 4 years ago
Tomatix 93e4840ec4 #17 Tabs to spaces 4 years ago
Tomatix fac1766fc5 #108 Removed unneccessary "undef" for Windows builds. 4 years ago
Tomatix 5dd821e475 #114 Added showing of tray messages 4 years ago
Tomatix b13547646a #114 Added tray messages 4 years ago
Tomatix 101e18429e #114 Highlight messages with our nickname in it and the corresponding window 4 years ago
Tomatix 94423c479b #114 Highlight incoming private message windows 4 years ago
Tomatix 8efebe04f5 #114 Added functionality to highlight window buttons upon creation. 4 years ago
Tomatix d41008283b #113 Always parse ISUPPORT message. 4 years ago
Tomatix 7cee1eb1cc #112 Added a tray icon menu. Double click on tray icon toggles visibility. 4 years ago
Tomatix 8a7579ac43 #106 Mark connection as registered upon receiving 001 4 years ago
Tomatix 5090761258 #95 Show error code and message in Unhandled exception error. Emit disconnected signal on connection error (affects connection button in toolbar.) 4 years ago
Tomatix d6ca35f084 #13 Reload the selected server item upon save 4 years ago
Tomatix c1f0055e5e #13 Added SSL option to Server editor. 4 years ago
Tomatix 040e63b625 #111 Use server password from configuration 4 years ago
Tomatix 7255df9077 #109 Use "raw" data to handle clearing of topic (/topic -c #channel). 4 years ago
Tomatix 909b6ff5f1 #110 Added script functions: strupper() strlower() 4 years ago
Tomatix cca07d10ed #108 ERROR is a macro in msvc :( 4 years ago
Tomatix de1ca627db #6 Option to hide the toolbar 4 years ago
Tomatix 80fe93184f #27 Disable compiler warnings about deprecated declarations. 4 years ago
Tomatix be0ca028fa #27 Compiler warning fixes 4 years ago
Tomatix 9d29452eb4 #27 Compiler warning fix 4 years ago
Tomatix 204513405a #27 Compiler warning fix 4 years ago
Tomatix 54bd4c268f #27 Compiler warning fixes 4 years ago
Tomatix 6c82c4bced #103 Refresh window title even if TOPIC is marked with "ignore verbosity". 4 years ago
Tomatix d3898e7b43 #103 Update internal topic storage upon change. 4 years ago
Tomatix e088cbb40a #107 Nickname and Channel lookup is now case-insensitive. 4 years ago
Tomatix 56a4f0f029 #102 Various cleanup from the previous refactor 4 years ago
Tomatix 05a9d50e04 #102 KILL command was treated as KICK... 4 years ago
Tomatix bba7227716 #102 Removed unneeded includes 4 years ago
Tomatix 8587e11c3c #102 Moved more logic for IRCClient to own source files 4 years ago
Tomatix 31be24eb7f #102 Added a simple sandbox-implementation of "IRCClient" - For testing stuff. 4 years ago
Tomatix b74969cef9 #102 Encapsulate inbound messages, handle tokenization in there as well. 4 years ago
Tomatix 54b6e79ce0 Version 1.1.0 development 4 years ago
  1. 85
      CHANGELOG
  2. 29
      CMakeLists.txt
  3. 3
      ICommand/CMakeLists.txt
  4. 60
      ICommand/CommandData.cpp
  5. 16
      ICommand/CommandData.h
  6. 21
      ICommand/Commands.h
  7. 20
      ICommand/External/kick.cpp
  8. 28
      ICommand/External/notice.cpp
  9. 30
      ICommand/External/privmsg.cpp
  10. 1127
      ICommand/ICommand.cpp
  11. 44
      ICommand/ICommand.h
  12. 6
      ICommand/ICommandPriv.cpp
  13. 16
      ICommand/ICommandPriv.h
  14. 52
      ICommand/Internal/dcc.cpp
  15. 26
      ICommand/Internal/me.cpp
  16. 98
      IConfig/AddServer.cpp
  17. 46
      IConfig/AddServer.h
  18. 183
      IConfig/AddServer.ui
  19. 12
      IConfig/CMakeLists.txt
  20. 552
      IConfig/ColorConfig.cpp
  21. 74
      IConfig/ColorConfig.h
  22. 178
      IConfig/IConfig.cpp
  23. 52
      IConfig/IConfig.h
  24. 100
      IConfig/IConfigLogging.cpp
  25. 30
      IConfig/IConfigLogging.h
  26. 200
      IConfig/IConfigOptions.cpp
  27. 73
      IConfig/IConfigOptions.h
  28. 897
      IConfig/IConfigOptions.ui
  29. 223
      IConfig/IConfigServers.cpp
  30. 51
      IConfig/IConfigServers.h
  31. 63
      IConfig/IConfigServers.ui
  32. 313
      IConfig/ServerEditor.cpp
  33. 59
      IConfig/ServerEditor.h
  34. 251
      IConfig/ServerEditor.ui
  35. 51
      IConfig/ServerItem.cpp
  36. 76
      IConfig/ServerItem.h
  37. 133
      IConfig/ServerMgr.cpp
  38. 66
      IConfig/ServerMgr.h
  39. 612
      IConfig/ServerModel.cpp
  40. 81
      IConfig/ServerModel.h
  41. 105
      IConfig/ServerOptionsDelegate.cpp
  42. 26
      IConfig/ServerOptionsDelegate.h
  43. 125
      IConfig/todo/ServerItem.cpp
  44. 67
      IConfig/todo/ServerItem.h
  45. 172
      IConfig/todo/ServerModel.cpp
  46. 54
      IConfig/todo/ServerModel.h
  47. 59
      IRCClient/CMakeLists.txt
  48. 109
      IRCClient/Commands.h
  49. 14
      IRCClient/Commands/Unhandled.cpp
  50. 14
      IRCClient/Commands/cmdError.cpp
  51. 14
      IRCClient/Commands/cmdInvite.cpp
  52. 43
      IRCClient/Commands/cmdJoin.cpp
  53. 16
      IRCClient/Commands/cmdKick.cpp
  54. 14
      IRCClient/Commands/cmdKill.cpp
  55. 31
      IRCClient/Commands/cmdMode.cpp
  56. 29
      IRCClient/Commands/cmdNick.cpp
  57. 23
      IRCClient/Commands/cmdNotice.cpp
  58. 16
      IRCClient/Commands/cmdPart.cpp
  59. 19
      IRCClient/Commands/cmdPing.cpp
  60. 14
      IRCClient/Commands/cmdPong.cpp
  61. 51
      IRCClient/Commands/cmdPrivmsg.cpp
  62. 32
      IRCClient/Commands/cmdQuit.cpp
  63. 17
      IRCClient/Commands/cmdTopic.cpp
  64. 18
      IRCClient/Commands/cmdV3Account.cpp
  65. 20
      IRCClient/Commands/cmdV3Authenticate.cpp
  66. 24
      IRCClient/Commands/cmdV3Away.cpp
  67. 64
      IRCClient/Commands/cmdV3Cap.cpp
  68. 14
      IRCClient/Commands/cmdWallops.cpp
  69. 135
      IRCClient/Commands/handleNumeric.cpp
  70. 373
      IRCClient/DCC.cpp
  71. 107
      IRCClient/DCC.h
  72. 1438
      IRCClient/IRCBase.cpp
  73. 222
      IRCClient/IRCBase.h
  74. 162
      IRCClient/IRCBasePriv.h
  75. 50
      IRCClient/IRCChannel.cpp
  76. 40
      IRCClient/IRCChannel.h
  77. 205
      IRCClient/IRCError.cpp
  78. 85
      IRCClient/IRCError.h
  79. 35
      IRCClient/IRCMember.cpp
  80. 22
      IRCClient/IRCMember.h
  81. 60
      IRCClient/IRCMemberEntry.cpp
  82. 18
      IRCClient/IRCMemberEntry.h
  83. 114
      IRCClient/IRCMessage.cpp
  84. 50
      IRCClient/IRCMessage.h
  85. 96
      IRCClient/IRCPrefix.cpp
  86. 58
      IRCClient/IRCPrefix.h
  87. 13
      IRCClient/Numeric.h
  88. 27
      IRCClient/Private/addMemberToChannel.cpp
  89. 30
      IRCClient/Private/channelModeGroup.cpp
  90. 51
      IRCClient/Private/connected.cpp
  91. 24
      IRCClient/Private/ctcp.cpp
  92. 63
      IRCClient/Private/delMemberFromChannel.cpp
  93. 24
      IRCClient/Private/disconnectHandler.cpp
  94. 14
      IRCClient/Private/isChannelSymbol.cpp
  95. 14
      IRCClient/Private/isMemberPrivilegeSymbol.cpp
  96. 21
      IRCClient/Private/keepaliveTimeout.cpp
  97. 17
      IRCClient/Private/memberPrivilegeSymbolToMode.cpp
  98. 56
      IRCClient/Private/parseChannelModeMessage.cpp
  99. 143
      IRCClient/Private/parseIncoming.cpp
  100. 34
      IRCClient/Private/read.cpp
  101. Some files were not shown because too many files have changed in this diff Show More

@ -2,6 +2,91 @@ IdealIRC changelog. http://www.idealirc.org/
Issues at https://git.idealirc.org/idealirc/idealirc/issues
Version 1.1.0 - January 5th 2023
#2 Implement DCC
* Added: DCC Chat.
#6 Consider removing or hiding the toolbar
* Added: GUI option to hide/show the toolbar.
#13 Improve the server editor
* Added: SSL option for individual servers.
* Added: SASL option for individual servers.
* Added: Checkable buttons for managing individual servers.
#70 Show QUIT message for user if their query window is open
* Added: Message in Query window if the user quits IRC.
#90 IRCv3 server-time
* Added: Support for timestamping chat history upon joining channels.
#95 Better error handling / display
* Improved: Show error code and message when SSL connections fail with "Unhandled exception".
#102 Improve the message parser
* Improved: Parser for inbound data from IRC servers is now more flexible at handling new features as well as IRCv3 extensions.
#103 Topic in channel's titlebar does not change
* Fixed: Refresh titlebar with new channel topic when it is changed.
#107 Looking up channels and nicknames isn't case sensitive
* Fixed: Case-insensitive lookup of the internal storage of channels and users.
#109 Topic doesn't show in the Channel Settings dialog
* Fixed: Corrected how topic clear command works (/topic -c #channel)
* Fixed: ChannelSettings.iis now handles channel names case-insensitively
#110 New script functions: strupper, strlower
* Added: Script finction strupper(string) that makes the input string all upper-case.
* Added: Script finction strlower(string) that makes the input string all lower-case.
#111 Server password isn't sent using the Options dialog
* Fixed: Use server password from configuration.
#112 Make use of the tray icon
* Added: Context menu when right-clicking the tray icon.
* Added: Double-clicking the icon toggles the program's visibility.
#114 Message highlighter doesn't work
* Fixed: Added missing functionality that prevented this to work.
#117 /KICK nickname in channel doesn't work
* Fixed: Missed check of channel parameter existence.
#118 ValueHolder seems to have breaks in some if..else if...else chains
* Fixed: Corrected an issue in the script engine related to logical operators.
#119 Support IRCv3 tags
* Added: Support for the message-tags IRCv3 extension.
#121 Add config options to enable/disable IRCv3 features
* Added: Own tab page in the Options dialog to enable/disable IRCv3 features.
#123 Remove the "Backround image" tab
* Removed: Background image option in Options dialog. This was only half-done and not working as intended anyway. Might see a come-back.
#124 SASL
* Added: SASL PLAIN authentication
* Added: Configuration option in the Options dialog to set SASL PLAIN credentials
#127 Re-joining seems to only populate a few of the actual users in channel
* Fixed: Corrected the clean-up procedures for the internal storage of members.
#132 Clear nickname list in channel when parting
* Improved: Clear the nickname list when leaving a channel (ie. by /part and the window is therefore still open)
#133 Replace servers.ini with servers.json
* Improved: Changed to use JSON format for servers due to how much information we need about each.
#134 Improve editing and adding servers
* Added: Double-clicking the columns in the editor allows editing on-the-fly (and auto saves).
* Added: More intuitive "add server" dialog box.
#137 Create a pre-populated servers.json with a few popular networks
* Improved: More servers and networks in servers.json.
Various source code cleanup (Issues #17, #27, #125 and #143)
Version 1.0.1 - July 8th 2021
#68 QUIT message isn't always sent
* Bugfix: Ensured that the configured QUIT message is used when closing status window, using tool buttons and when

@ -7,8 +7,8 @@ project(IdealIRC)
#
set(BUILD_TYPE "packaged")
set(VERSION_MAJOR 1)
set(VERSION_MINOR 0)
set(VERSION_PATCH 1)
set(VERSION_MINOR 1)
set(VERSION_PATCH 0)
set(VERSION_APPEND "")
#
@ -25,26 +25,36 @@ include_directories(${IIRC_SOURCE_DIR})
include_directories(${IIRC_SOURCE_DIR}/IdealIRC)
include_directories(${CMAKE_BINARY_DIR})
option(BUILD_DEV_STUFF "Build additional development stuff" OFF)
#
# Additional paths
#
set(IIRC_DEP_INCLUDE "" CACHE PATH "Additional include path (single path only)")
set(IIRC_DEP_LIBRARY "" CACHE PATH "Additional library path (single path only)")
if (NOT IIRC_DEP_INCLUDE STREQUAL "")
include_directories(${IIRC_DEP_INCLUDE})
message("Using additional include path: ${IIRC_DEP_INCLUDE}")
include_directories(${IIRC_DEP_INCLUDE})
message("Using additional include path: ${IIRC_DEP_INCLUDE}")
endif()
if (NOT IIRC_DEP_LIBRARY STREQUAL "")
link_directories(${IIRC_DEP_LIBRARY})
message("Using additional library path: ${IIRC_DEP_LIBRARY}")
link_directories(${IIRC_DEP_LIBRARY})
message("Using additional library path: ${IIRC_DEP_LIBRARY}")
endif()
# For GCC and Clang, basically.
if (UNIX)
# set(CMAKE_CXX_FLAGS "-Wall -Werror")
set(CMAKE_CXX_FLAGS "-pthread")
set(CMAKE_CXX_FLAGS "-pthread")
#
# Remove warnings about deprecated stuff.
# In order to change a statement using a deprecated API, it has to be investigated what it would affect.
# It is safe to temporarily disable this during development but should be enabled for release and test builds.
#
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-deprecated-declarations")
endif()
#
# Qt setup
#
@ -73,4 +83,9 @@ add_subdirectory(Script)
add_subdirectory(ScriptDialog)
add_subdirectory(ScriptFunctions)
add_subdirectory(Widgets)
add_subdirectory(NATUtils)
add_subdirectory(IdealIRC) # This one builds the binary.
if (BUILD_DEV_STUFF)
add_subdirectory(IRCClientSandbox)
endif()

@ -12,20 +12,19 @@ list(APPEND ${component}_SOURCES
list(APPEND ${component}_SOURCES_Internal
${CMAKE_CURRENT_SOURCE_DIR}/Internal/ctcp.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Internal/dcc.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Internal/ctcpreply.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Internal/me.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Internal/echo.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Internal/server.cpp
)
list(APPEND ${component}_SOURCES_External
${CMAKE_CURRENT_SOURCE_DIR}/External/kick.cpp
${CMAKE_CURRENT_SOURCE_DIR}/External/notice.cpp
${CMAKE_CURRENT_SOURCE_DIR}/External/privmsg.cpp
)
add_library(${component} STATIC ${${component}_SOURCES} ${${component}_SOURCES_Internal} ${${component}_SOURCES_External})
qt5_use_modules(${component} Widgets)
target_include_directories(${component} PRIVATE ${CMAKE_SOURCE_DIR})

@ -10,48 +10,48 @@
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;
}
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;
}
}
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);
while (m_l.size() < argp.size())
m_l.push_back(std::nullopt);
}
int CommandData::size() const
{
return m_l.size();
return m_l.size();
}
const std::optional<std::string>& CommandData::operator[](int idx)
{
return m_l.at(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;
std::string ret;
for (const auto& item : m_l) {
if (!ret.empty())
ret += sep;
if (item.has_value())
ret += *item;
}
return ret;
}

@ -15,22 +15,22 @@
#include <optional>
using PredicateList = std::vector< std::function<
std::optional<std::string>
(int& idx, const QString& line)
>>;
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;
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;
std::vector<std::optional<std::string>> m_l;
};
#endif // COMMANDDATA_H

@ -9,16 +9,17 @@
#define ICOMMAND_COMMANDS_H
namespace Command::Internal {
constexpr auto* ACTION = "ACTION";
constexpr auto* CTCP = "CTCP";
constexpr auto* CTCPREPLY = "CTCPREPLY";
constexpr auto* ME = "ME";
constexpr auto* ECHO = "ECHO";
constexpr auto* QUERY = "QUERY";
constexpr auto* MUTERESP = "MUTERESP";
constexpr auto* UNMUTERESP = "UNMUTERESP";
constexpr auto* CLEAR = "CLEAR";
constexpr auto* MSG = "MSG";
constexpr auto* ACTION = "ACTION";
constexpr auto* CTCP = "CTCP";
constexpr auto* DCC = "DCC";
constexpr auto* CTCPREPLY = "CTCPREPLY";
constexpr auto* ME = "ME";
constexpr auto* ECHO = "ECHO";
constexpr auto* QUERY = "QUERY";
constexpr auto* MUTERESP = "MUTERESP";
constexpr auto* UNMUTERESP = "UNMUTERESP";
constexpr auto* CLEAR = "CLEAR";
constexpr auto* MSG = "MSG";
constexpr auto* QUOTE = "QUOTE";
constexpr auto* RAW = "RAW";
constexpr auto* SERVER = "SERVER";

@ -11,16 +11,16 @@ using namespace Command::IRC;
void ICommandPriv::cmd_kick(const std::string& channel, const std::string& nickname, const std::string& reason)
{
auto& mdi = MdiManager::instance();
std::string chan = channel;
auto& mdi = MdiManager::instance();
std::string chan = channel;
if (chan.empty()) {
if (mdi.currentWindow()->getType() != IWin::Type::Channel) {
mdi.currentWindow()->print(PrintType::ProgramInfo, "Not in a channel window");
return;
}
chan = mdi.currentWindow()->getButtonText().toStdString();
}
if (chan.empty()) {
if (mdi.currentWindow()->getType() != IWin::Type::Channel) {
mdi.currentWindow()->print(PrintType::ProgramInfo, "Not in a channel window");
return;
}
chan = mdi.currentWindow()->getButtonText().toStdString();
}
connection.command(KICK, { channel, nickname }, reason);
connection.command(KICK, { chan, nickname }, reason);
}

@ -11,21 +11,21 @@ using namespace Command::IRC;
void ICommandPriv::cmd_notice(const std::string& target, const std::string& message)
{
auto& mdi = MdiManager::instance();
auto& mdi = MdiManager::instance();
connection.command(NOTICE, { target }, message);
connection.command(NOTICE, { target }, message);
IWin* winTarget = mdi.findWindow(&status, target.c_str());
IWin* active = status.getActiveWindow();
IWin* winTarget = mdi.findWindow(&status, target.c_str());
IWin* active = status.getActiveWindow();
if (!target.empty() && !message.empty()) {
if (winTarget && winTarget != active)
winTarget->print(PrintType::OwnText, QStringLiteral("->%1<- %2")
.arg(target.c_str())
.arg(message.c_str()));
if (active && active != winTarget)
active->print(PrintType::OwnText, QStringLiteral("->%1<- %2")
.arg(target.c_str())
.arg(message.c_str()));
}
if (!target.empty() && !message.empty()) {
if (winTarget && winTarget != active)
winTarget->print(PrintType::OwnText, QStringLiteral("->%1<- %2")
.arg(target.c_str())
.arg(message.c_str()));
if (active && active != winTarget)
active->print(PrintType::OwnText, QStringLiteral("->%1<- %2")
.arg(target.c_str())
.arg(message.c_str()));
}
}

@ -11,22 +11,22 @@ using namespace Command::IRC;
void ICommandPriv::cmd_privmsg(const std::string& target, const std::string& message)
{
auto& mdi = MdiManager::instance();
auto& mdi = MdiManager::instance();
connection.command(PRIVMSG, { target }, message);
connection.command(PRIVMSG, { target }, message);
if (!target.empty() && !message.empty()) {
IWin* win = mdi.findWindow(&status, target.c_str());
if (win) {
const auto* myNick = connection.getNickname().c_str();
win->print(PrintType::OwnText, QStringLiteral("<%1> %2")
.arg(myNick)
.arg(message.c_str()));
}
else
status.print(PrintType::OwnText, QStringLiteral(">%1< %2")
.arg(target.c_str())
.arg(message.c_str()));
}
if (!target.empty() && !message.empty()) {
IWin* win = mdi.findWindow(&status, target.c_str());
if (win) {
const auto* myNick = connection.getNickname().c_str();
win->print(PrintType::OwnText, QStringLiteral("<%1> %2")
.arg(myNick)
.arg(message.c_str()));
}
else
status.print(PrintType::OwnText, QStringLiteral(">%1< %2")
.arg(target.c_str())
.arg(message.c_str()));
}
}

File diff suppressed because it is too large Load Diff

@ -20,34 +20,34 @@ struct ICommandPriv;
class ICommand : public QObject
{
Q_OBJECT
Q_OBJECT
friend struct ICommandPriv;
friend struct ICommandPriv;
public:
explicit ICommand(IRC& connection_);
~ICommand();
explicit ICommand(IRC& connection_);
~ICommand();
void parse(QString text);
IRC& getConnection();
void parse(QString text);
IRC& getConnection();
private:
bool tryParseIRC(QString cmdLine);
bool tryParseInternal(QString cmdLine);
void print_notEnoughParameters(const QString& command);
void print_invalidCommandSwitch(const QString& command);
void print_invalidWindowTypeForCommand(const QString& command);
void print_notConnectedToServer(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_AnyWordToUpper(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);
std::unique_ptr<ICommandPriv> mp;
bool tryParseIRC(QString cmdLine);
bool tryParseInternal(QString cmdLine);
void print_notEnoughParameters(const QString& command);
void print_invalidCommandSwitch(const QString& command);
void print_invalidWindowTypeForCommand(const QString& command);
void print_notConnectedToServer(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_AnyWordToUpper(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);
std::unique_ptr<ICommandPriv> mp;
};
#endif // ICOMMAND_H

@ -8,8 +8,8 @@
#include "ICommandPriv.h"
ICommandPriv::ICommandPriv(ICommand& super_, IRC& connection_, IWinStatus& status_)
: super(super_)
, connection(connection_)
, status(status_)
: super(super_)
, connection(connection_)
, status(status_)
{}

@ -12,26 +12,26 @@
#include "IWin/IWinStatus.h"
#include "MdiManager.h"
#include "IRCClient/Commands.h"
#include "IRCClient/Utilities.h"
#include "Widgets/IIRCView.h"
#include <string>
constexpr char CTCPflag { 0x01 };
struct ICommandPriv
{
ICommandPriv(ICommand& super_, IRC& connection_, IWinStatus& status_);
ICommandPriv(ICommand& super_, IRC& connection_, IWinStatus& status_);
ICommand& super;
IRC& connection;
IWinStatus& status;
void cmd_kick(const std::string& channel, const std::string& nickname, const std::string& reason);
void cmd_privmsg(const std::string& target, const std::string& message);
void cmd_notice(const std::string& target, const std::string& message);
void cmd_kick(const std::string& channel, const std::string& nickname, const std::string& reason);
void cmd_privmsg(const std::string& target, const std::string& message);
void cmd_notice(const std::string& target, const std::string& message);
void cmd_ctcp(const std::string& target, const std::string& command, std::string message);
void cmd_ctcp(const std::string& target, const std::string& command, std::string message);
void cmd_dcc(const std::string& command, const std::string& target, const std::string& message);
void cmd_ctcpreply(const std::string& target, const std::string& command, const std::string& message);
void cmd_me(const std::string& target, const std::string& message);
void cmd_me(const std::string& target, const std::string& message);
void cmd_echo(const std::string& arg1, const std::string& arg2, const std::string& text);
void cmd_server(bool ssl, bool newstatus, bool activate, const std::string& host, const std::string& port);
};

@ -0,0 +1,52 @@
/*
* IdealIRC - Internet Relay Chat client
* Copyright (c) 2022 Tom-Andre Barstad.
* This software is licensed under the Software Attribution License.
* See LICENSE for more information.
*/
#include "ICommand/ICommandPriv.h"
#include "IRCClient/DCC.h"
#include <fmt/format.h>
void ICommandPriv::cmd_dcc(const std::string& command, const std::string& target, const std::string& message)
{
auto& mdi = MdiManager::instance();
if (command == "CHAT") {
auto result = connection.initiateDCC();
if (result.second != IRCError::NoError) {
const auto code = static_cast<int>(result.second);
const auto errorStr = QString::fromStdString(IRCErrorToString(result.second));
mdi.currentStatus()->printToActive(PrintType::ProgramInfo, QObject::tr("/DCC: Unable to initiate DCC Chat to %1 [%2 (%3)]")
.arg( QString::fromStdString(target), errorStr, QString::number(code)) );
}
else {
auto dcc = result.first;
const auto portno = dcc->isReversed() ? 0
: dcc->port();
auto* window = mdi.createDCCSubwindow(mdi.currentStatus(), IWin::Type::DCCChat, result.first, IRCPrefix(target));
if (dcc->isReversed())
window->print(PrintType::ProgramInfo, QObject::tr("Initiating CHAT with %1, please wait...")
.arg( QString::fromStdString(target) ));
else
window->print(PrintType::ProgramInfo, QObject::tr("Initiating CHAT with %1 on port %2, please wait...")
.arg( QString::fromStdString(target) )
.arg(portno));
const auto myIp = findOwnIpAddress();
const auto msg = fmt::format( "CHAT chat {} {}",
normalIpToLong(myIp),
portno
);
cmd_ctcp(target, "DCC", msg);
}
}
else {
mdi.currentStatus()->printToActive(PrintType::ProgramInfo, QObject::tr("/DCC: Unknown DCC method: %1")
.arg( QString::fromStdString(command) ));
}
}

@ -19,19 +19,19 @@ void ICommandPriv::cmd_me(const std::string& target, const std::string& message)
IWin* cw = status.getActiveWindow();
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();
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]);
}
}
if (!mymodes.empty()) {
std::string pf = connection.toMemberPrefix(mymodes);
if (!pf.empty())
mynick.insert(mynick.begin(), pf[0]);
}
}
if (!target.empty() && !message.empty())
cw->print(PrintType::Action, QStringLiteral("%1 %2")
.arg(mynick.c_str())
.arg(message.c_str()));
if (!target.empty() && !message.empty())
cw->print(PrintType::Action, QStringLiteral("%1 %2")
.arg(mynick.c_str())
.arg(message.c_str()));
}

@ -0,0 +1,98 @@
/*
* IdealIRC - Internet Relay Chat client
* Copyright (C) 2022 Tom-Andre Barstad.
* This software is licensed under the Software Attribution License.
* See LICENSE for more information.
*/
#include "AddServer.h"
#include "ui_AddServer.h"
#include <QFile>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
AddServer::AddServer(const QStringList& networkList, QWidget *parent) :
QDialog(parent),
ui(new Ui::AddServer)
{
ui->setupUi(this);
setAttribute(Qt::WA_DeleteOnClose);
auto EnableOrDisableSave = [this](const QString&) {
auto isDisabled = ui->edName->text().isEmpty() || ui->edAddress->text().isEmpty();
ui->btnSave->setEnabled(!isDisabled);
};
connect(ui->edName, &QLineEdit::textChanged, EnableOrDisableSave);
connect(ui->edAddress, &QLineEdit::textChanged, EnableOrDisableSave);
for (const auto& networkName : networkList)
ui->edNetwork->addItem(networkName);
}
AddServer::~AddServer()
{
delete ui;
}
QString AddServer::name() const
{
return ui->edName->text();
}
QString AddServer::address() const
{
return ui->edAddress->text();
}
QString AddServer::password() const
{
return ui->edPassword->text();
}
QString AddServer::sasl() const
{
return ui->edSASL->text();
}
bool AddServer::ssl() const
{
return ui->chkSSL->isChecked();
}
bool AddServer::isServer() const
{
return ui->rdServer->isChecked();
}
int AddServer::networkIndex() const
{
// 0th index is the "No network" selection, make that into a "-1" index instead... and the rest must also follow.
return ui->edNetwork->currentIndex() - 1;
}
void AddServer::on_btnCancel_clicked()
{
close();
}
void AddServer::on_btnSave_clicked()
{
emit saved();
close();
}
void AddServer::on_rdNetwork_toggled(bool checked)
{
ui->edNetwork->setEnabled(!checked);
// Network servers (child servers) uses their parent server's SASL.
// The individual child servers will not have own SASL credentials.
ui->edSASL->setEnabled(!checked && ui->edNetwork->currentIndex() == 0 || checked);
}
void AddServer::on_edNetwork_currentIndexChanged(int index)
{
ui->edSASL->setEnabled(index == 0);
}

@ -0,0 +1,46 @@
/*
* IdealIRC - Internet Relay Chat client
* Copyright (C) 2022 Tom-Andre Barstad.
* This software is licensed under the Software Attribution License.
* See LICENSE for more information.
*/
#ifndef ADDSERVER_H
#define ADDSERVER_H
#include <QDialog>
namespace Ui {
class AddServer;
}
class AddServer : public QDialog
{
Q_OBJECT
public:
explicit AddServer(const QStringList& networkList, QWidget *parent = nullptr);
~AddServer();
QString name() const;
QString address() const;
QString password() const;
QString sasl() const;
bool ssl() const;
bool isServer() const;
int networkIndex() const;
signals:
void saved();
private slots:
void on_btnCancel_clicked();
void on_btnSave_clicked();
void on_rdNetwork_toggled(bool checked);
void on_edNetwork_currentIndexChanged(int index);
private:
Ui::AddServer *ui;
};
#endif // ADDSERVER_H

@ -0,0 +1,183 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>AddServer</class>
<widget class="QDialog" name="AddServer">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>200</height>
</rect>
</property>
<property name="windowTitle">
<string>Add server</string>
</property>
<property name="windowIcon">
<iconset resource="../Resources/resources.qrc">
<normaloff>:/Icons/options.png</normaloff>:/Icons/options.png</iconset>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QRadioButton" name="rdNetwork">
<property name="text">
<string>Network</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="rdServer">
<property name="text">
<string>Server</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="edNetwork">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="placeholderText">
<string/>
</property>
<item>
<property name="text">
<string>&lt;No network&gt;</string>
</property>
</item>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Name</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="edName"/>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>Address</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="edAddress"/>
</item>
<item>
<widget class="QCheckBox" name="chkSSL">
<property name="text">
<string>SSL</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>Password</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="edPassword">
<property name="echoMode">
<enum>QLineEdit::Password</enum>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_6">
<item>
<widget class="QLabel" name="label_4">
<property name="text">
<string>SASL credential</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="edSASL">
<property name="echoMode">
<enum>QLineEdit::Password</enum>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_5">
<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="QPushButton" name="btnCancel">
<property name="text">
<string>Cancel</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btnSave">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Save</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<tabstops>
<tabstop>rdNetwork</tabstop>
<tabstop>rdServer</tabstop>
<tabstop>edNetwork</tabstop>
<tabstop>edName</tabstop>
<tabstop>edAddress</tabstop>
<tabstop>chkSSL</tabstop>
<tabstop>edPassword</tabstop>
<tabstop>edSASL</tabstop>
<tabstop>btnCancel</tabstop>
<tabstop>btnSave</tabstop>
</tabstops>
<resources>
<include location="../Resources/resources.qrc"/>
</resources>
<connections/>
</ui>

@ -15,13 +15,15 @@ list(APPEND ${component}_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/IConfigServers.cpp
${CMAKE_CURRENT_SOURCE_DIR}/IConfigServers.h
${CMAKE_CURRENT_SOURCE_DIR}/IConfigServers.ui
${CMAKE_CURRENT_SOURCE_DIR}/ServerEditor.cpp
${CMAKE_CURRENT_SOURCE_DIR}/ServerEditor.h
${CMAKE_CURRENT_SOURCE_DIR}/ServerEditor.ui
${CMAKE_CURRENT_SOURCE_DIR}/ServerMgr.cpp
${CMAKE_CURRENT_SOURCE_DIR}/ServerMgr.h
${CMAKE_CURRENT_SOURCE_DIR}/AddServer.cpp
${CMAKE_CURRENT_SOURCE_DIR}/AddServer.h
${CMAKE_CURRENT_SOURCE_DIR}/AddServer.ui
${CMAKE_CURRENT_SOURCE_DIR}/ServerItem.cpp
${CMAKE_CURRENT_SOURCE_DIR}/ServerItem.h
${CMAKE_CURRENT_SOURCE_DIR}/ServerModel.cpp
${CMAKE_CURRENT_SOURCE_DIR}/ServerModel.h
${CMAKE_CURRENT_SOURCE_DIR}/ServerOptionsDelegate.cpp
${CMAKE_CURRENT_SOURCE_DIR}/ServerOptionsDelegate.h
)
add_library(${component} STATIC ${${component}_SOURCES})

@ -21,360 +21,360 @@ constexpr auto* Text_AddPrefix_Short = "Add";
}
ColorConfig::ColorConfig(QWidget* parent)
: QWidget(parent)
, colorDlg(this)
: 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();
});
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;
ConfigMgr& conf = ConfigMgr::instance();
conf.setColorPalette(palette);
conf.setPrefixColorPalette(prefixColor);
m_isChanged = false;
}
bool ColorConfig::isChanged() const
{
return m_isChanged;
return m_isChanged;
}
void ColorConfig::reset()
{
ConfigMgr& conf = ConfigMgr::instance();
palette = conf.color();
prefixColor = conf.prefixColor();
m_isChanged = false;
repaint();
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();
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);
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;
paint.fillRect(0, 0, w, h, frame);
w -= 2;
h -= 2;
const int listboxW = 150;
const int inputH = 24;
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")));
/* 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
/* 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
/* 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);
ConfigMgr& conf = ConfigMgr::instance();
QFont font(conf.common("Font"));
paint.setFont(font);
createMessageBoxTexts(paint);
createMemberListTexts(paint, listboxW);
createInputBoxTexts(paint, inputH);
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");
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());
const int itemIncr = 19;
int itemTop = itemIncr;
QPen originalPen = paint.pen();
QFontMetrics fm(paint.fontMetrics());
textTypeBB.clear();
QHashIterator<QString,QString> it(textTypeMap);
QStringList sorted;
textTypeBB.clear();
QHashIterator<QString,QString> it(textTypeMap);
QStringList sorted;
while (it.hasNext())
sorted << it.next().key();
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;
});
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);
for (const QString& key : sorted) {
const QString val = descriptiveColorText(key);
paint.setPen(QColor(palette.value(key)));
paint.drawText(4, itemTop, val);
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);
QRect itemRect(4, itemTop - fm.ascent(), fm.width(val), fm.height());
textTypeBB.insert(key, itemRect);
itemTop += itemIncr;
}
itemTop += itemIncr;
}
paint.setPen(originalPen);
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);
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();
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(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);
paint.setPen(originalPen);
}
QString ColorConfig::descriptiveColorText(const QString& key) const
{
if (key.left(8) == "lbprefix")
return QStringLiteral("Prefix '%1'").arg(key[9]);
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;
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();
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();
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;
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;
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);
}
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();
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();
}

@ -21,51 +21,51 @@
class ColorConfig : public QWidget
{
Q_OBJECT
Q_OBJECT
public:
explicit ColorConfig(QWidget* parent = nullptr);
void save();
bool isChanged() const;
void reset();
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);
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;
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,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;
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;
QColorDialog colorDlg;
QString colorDlgItem;
QString originalColor;
bool m_isChanged{ false };
bool m_isChanged{ false };
};
#endif // COLORCONFIG_H

@ -11,75 +11,75 @@
#include <QMessageBox>
IConfig::IConfig(QWidget *parent) :
QDialog(parent),
ui(new Ui::IConfig)
QDialog(parent),
ui(new Ui::IConfig)
{
ui->setupUi(this);
ui->setupUi(this);
layout = new QHBoxLayout(this);
servers = new IConfigServers;
options = new IConfigOptions;
logging = new IConfigLogging;
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);
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); });
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);
showSubDialog(SubDialogType::Servers);
}
IConfig::~IConfig()
{
delete ui;
delete ui;
}
void IConfig::showDisconnectButton()
{
ui->btnDisconnect->show();
ui->btnDisconnect->show();
}
void IConfig::hideDisconnectButton()
{
ui->btnDisconnect->hide();
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;
}
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();
servers->save();
options->save();
logging->save();
ConfigMgr::instance().save();
ConfigMgr::instance().save();
}
bool IConfig::askForSave()
@ -87,70 +87,70 @@ bool IConfig::askForSave()
bool serversChanged = servers->isChanged();
bool optionsChanged = options->isChanged();
bool loggingChanged = logging->isChanged();
if (serversChanged || optionsChanged || loggingChanged) {
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;
if (serversChanged || optionsChanged || loggingChanged) {
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();
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;
}
if (!servers->connectToNewStatus())
emit disconnectFromServer();
emit connectToServer(servers->connectToNewStatus());
servers->unsetConnectToNewStatus();
close();
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;
}
if (!servers->connectToNewStatus())
emit disconnectFromServer();
emit connectToServer(servers->connectToNewStatus());
servers->unsetConnectToNewStatus();
close();
}
void IConfig::on_btnDisconnect_clicked()
{
ui->btnDisconnect->hide();
emit disconnectFromServer();
ui->btnDisconnect->hide();
emit disconnectFromServer();
}
void IConfig::on_btnClose_clicked()
{
servers->unsetConnectToNewStatus();
close();
servers->unsetConnectToNewStatus();
close();
}
void IConfig::closeEvent(QCloseEvent* evt)
{
if (askForSave())
evt->accept();
else
evt->ignore();
if (askForSave())
evt->accept();
else
evt->ignore();
}

@ -22,40 +22,40 @@ class IConfig;
class IConfig : public QDialog
{
Q_OBJECT
Q_OBJECT
public:
explicit IConfig(QWidget *parent = nullptr);
~IConfig();
void showDisconnectButton();
void hideDisconnectButton();
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();
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;
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();
void connectToServer(bool newServer);
void disconnectFromServer();
};
#endif // ICONFIG_H

@ -13,95 +13,95 @@
#include <QDebug>
IConfigLogging::IConfigLogging(QWidget *parent) :
QWidget(parent),
ui(new Ui::IConfigLogging)
QWidget(parent),
ui(new Ui::IConfigLogging)
{
ui->setupUi(this);
ui->splitter->setStretchFactor(0, 1);
ui->splitter->setStretchFactor(1, 4);
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");
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);
ui->chkChannels->setChecked(cf_Chnanels);
ui->chkPrivates->setChecked(cf_Privates);
ui->edPath->setText(cf_Path);
}
IConfigLogging::~IConfigLogging()
{
delete ui;
delete ui;
}
bool IConfigLogging::isChanged() const
{
return cf_Chnanels != ui->chkChannels->isChecked()
|| cf_Privates != ui->chkPrivates->isChecked()
|| cf_Path != ui->edPath->text();
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());
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");
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);
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());
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);
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);
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());
loadFiles(ui->edPath->text());
}
void IConfigLogging::on_fileList_itemClicked(QListWidgetItem *item)
{
if (!item)
return;
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);
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);
}

@ -18,27 +18,27 @@ class IConfigLogging;
class IConfigLogging : public QWidget
{
Q_OBJECT
Q_OBJECT
public:
explicit IConfigLogging(QWidget *parent = nullptr);
~IConfigLogging();
bool isChanged() const;
void save();
void reset();
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);
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;
void loadFiles(const QString& path);
Ui::IConfigLogging *ui;
bool cf_Chnanels;
bool cf_Privates;
QString cf_Path;
};
#endif // ICONFIGLOGGING_H

@ -13,46 +13,50 @@
#include <QFileDialog>
IConfigOptions::IConfigOptions(QWidget *parent) :
QWidget(parent),
ui(new Ui::IConfigOptions)
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();
cf_Font = ui->edFont->currentFont();
ui->setupUi(this);
// TODO remove this line whenever background images can be dealt with...
ui->tabWidget->setTabVisible(3, false);
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();
cf_Font = ui->edFont->currentFont();
}
IConfigOptions::~IConfigOptions()
{
delete ui;
delete ui;
}
bool IConfigOptions::isChanged() const
{
bool changed = cf_ShowOptions != ui->chkShowOptions->isChecked()
bool changed = cf_ShowOptions != ui->chkShowOptions->isChecked()
|| cf_Reconnect != ui->chkReconnect->isChecked()
|| cf_ReconnectDelay != ui->edReconnectDelay->value()
|| cf_RejoinChannelsOnConnect != ui->chkRejoinConnect->isChecked()
@ -73,12 +77,19 @@ bool IConfigOptions::isChanged() const
|| cf_BgImageOpacity != ui->hsImageOpacity->value()
|| cf_SSLSelfSigned != ui->chkSSLSelfsigned->isChecked()
|| cf_SSLExpired != ui->chkSSLExpired->isChecked()
|| cf_SSLCNMismatch != ui->chkSSLCNMismatch->isChecked();
|| cf_SSLCNMismatch != ui->chkSSLCNMismatch->isChecked()
|| cf_AccountNotify != ui->chkAccountNotify->isChecked()
|| cf_ExtendedJoin != ui->chkExtendedJoin->isChecked()
|| cf_AwayNotify != ui->chkAwayNotify->isChecked()
|| cf_InviteNotify != ui->chkInviteNotify->isChecked()
|| cf_MultiPrefix != ui->chkMutltiPrefix->isChecked()
|| cf_UserHostInNames != ui->chkUserhostInNames->isChecked()
|| cf_HistoryPlayback != ui->chkHistoryPlayback->isChecked();
if (changed)
return true;
else
return colorCfg->isChanged();
if (changed)
return true;
else
return colorCfg->isChanged();
}
void IConfigOptions::save()
@ -105,73 +116,96 @@ void IConfigOptions::save()
conf.setCommon("SSLSelfsigned", QString::number(ui->chkSSLSelfsigned->isChecked()));
conf.setCommon("SSLExpired", QString::number(ui->chkSSLExpired->isChecked()));
conf.setCommon("SSLCNMismatch", QString::number(ui->chkSSLCNMismatch->isChecked()));
conf.setIRCv3("account-notify", QString::number(ui->chkAccountNotify->isChecked()));
conf.setIRCv3("extended-join", QString::number(ui->chkExtendedJoin->isChecked()));
conf.setIRCv3("away-notify", QString::number(ui->chkAwayNotify->isChecked()));
conf.setIRCv3("invite-notify", QString::number(ui->chkInviteNotify->isChecked()));
conf.setIRCv3("multi-prefix", QString::number(ui->chkMutltiPrefix->isChecked()));
conf.setIRCv3("userhost-in-names", QString::number(ui->chkUserhostInNames->isChecked()));
conf.setIRCv3("message-tags", QString::number(ui->chkHistoryPlayback->isChecked()));
conf.setIRCv3("server-time", QString::number(ui->chkHistoryPlayback->isChecked()));
reload();
colorCfg->save();
}
void IConfigOptions::reset()
{
ui->chkShowOptions->setChecked(cf_ShowOptions);
ui->chkReconnect->setChecked(cf_Reconnect);
ui->edReconnectDelay->setValue(cf_ReconnectDelay);
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->chkManualKeepalive->setChecked(cf_ManualKeepaliveEnabled);
ui->edManualKeepalive->setValue(cf_ManualKeepalive);
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();
ui->chkShowOptions->setChecked(cf_ShowOptions);
ui->chkReconnect->setChecked(cf_Reconnect);
ui->edReconnectDelay->setValue(cf_ReconnectDelay);
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->chkManualKeepalive->setChecked(cf_ManualKeepaliveEnabled);
ui->edManualKeepalive->setValue(cf_ManualKeepalive);
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);
ui->chkAccountNotify->setChecked(cf_AccountNotify);
ui->chkExtendedJoin->setChecked(cf_ExtendedJoin);
ui->chkAwayNotify->setChecked(cf_AwayNotify);
ui->chkInviteNotify->setChecked(cf_InviteNotify);
ui->chkMutltiPrefix->setChecked(cf_MultiPrefix);
ui->chkUserhostInNames->setChecked(cf_UserHostInNames);
ui->chkHistoryPlayback->setChecked(cf_HistoryPlayback);
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."));
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_ReconnectDelay = conf.common("ReconnectDelay").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();
ConfigMgr& conf = ConfigMgr::instance();
cf_ShowOptions = conf.common("ShowOptions").toInt();
cf_Reconnect = conf.common("Reconnect").toInt();
cf_ReconnectDelay = conf.common("ReconnectDelay").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_ManualKeepaliveEnabled = conf.common("ManualKeepaliveEnabled").toInt();
cf_ManualKeepalive = conf.common("ManualKeepalive").toInt();
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();
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();
cf_AccountNotify = conf.IRCv3("account-notify").toInt();
cf_ExtendedJoin = conf.IRCv3("extended-join").toInt();
cf_AwayNotify = conf.IRCv3("away-notify").toInt();
cf_InviteNotify = conf.IRCv3("invite-notify").toInt();
cf_MultiPrefix = conf.IRCv3("multi-prefix").toInt();
cf_UserHostInNames = conf.IRCv3("userhost-in-names").toInt();
cf_HistoryPlayback = conf.IRCv3("message-tags").toInt() && conf.IRCv3("server-time").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);
QString path = QFileDialog::getOpenFileName(this, tr("Choose background image"), LOCAL_PATH, tr("Image Files (*.png *.jpg *.bmp)"));
ui->edImage->setText(path);
}

@ -19,46 +19,53 @@ class IConfigOptions;
class IConfigOptions : public QWidget
{
Q_OBJECT
Q_OBJECT
public:
explicit IConfigOptions(QWidget *parent = nullptr);
~IConfigOptions();
bool isChanged() const;
void save();
void reset();
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();
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;
int cf_ReconnectDelay;
bool cf_RejoinChannelsOnConnect;
bool cf_ShowWhoisActiveWindow;
bool cf_ShowModeInMessage;
bool cf_TrayNotify;
int cf_TrayNotifyDelay;
bool cf_ShowTimestamp;
QString cf_TimestampFormat;
void reload();
Ui::IConfigOptions *ui;
QVBoxLayout *layout;
ColorConfig *colorCfg;
bool cf_ShowOptions;
bool cf_Reconnect;
int cf_ReconnectDelay;
bool cf_RejoinChannelsOnConnect;
bool cf_ShowWhoisActiveWindow;
bool cf_ShowModeInMessage;
bool cf_TrayNotify;
int cf_TrayNotifyDelay;
bool cf_ShowTimestamp;
QString cf_TimestampFormat;
bool cf_ManualKeepaliveEnabled;
int cf_ManualKeepalive;
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;
int cf_ManualKeepalive;
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;
bool cf_AccountNotify;
bool cf_ExtendedJoin;
bool cf_AwayNotify;
bool cf_InviteNotify;
bool cf_MultiPrefix;
bool cf_UserHostInNames;
bool cf_HistoryPlayback;
};
#endif // ICONFIGOPTIONS_H

@ -13,417 +13,647 @@
<property name="windowTitle">
<string>Form</string>
</property>
<widget class="QTabWidget" name="tabWidget">
<property name="geometry">
<rect>
<x>9</x>
<y>9</y>
<width>591</width>
<height>561</height>
</rect>
</property>
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="tabCommon">
<attribute name="title">
<string>Common</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QCheckBox" name="chkShowOptions">
<property name="text">
<string>Show options upon start</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_10">
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<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">
<item>
<widget class="QCheckBox" name="chkReconnect">
<widget class="QCheckBox" name="chkShowOptions">
<property name="text">
<string>Automatic re-connect after lost connection, delay:</string>
<string>Show options upon start</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="edReconnectDelay">
<property name="suffix">
<string> sec</string>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>9999999</number>
<layout class="QHBoxLayout" name="horizontalLayout_10">
<item>
<widget class="QCheckBox" name="chkReconnect">
<property name="text">
<string>Automatic re-connect after lost connection, delay:</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="edReconnectDelay">
<property name="suffix">
<string> sec</string>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>9999999</number>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_6">
<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="QCheckBox" name="chkRejoinConnect">
<property name="text">
<string>Auto re-join channels after re-connect</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_6">
<property name="orientation">
<enum>Qt::Horizontal</enum>
<widget class="QCheckBox" name="chkWhoisActive">
<property name="text">
<string>Show /whois in active window</string>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</widget>
</item>
<item>
<widget class="QCheckBox" name="chkShowModeMsg">
<property name="text">
<string>Show modes in messages</string>
</property>
</spacer>
</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>
</layout>
</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">
<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>
<layout class="QHBoxLayout" name="horizontalLayout_8">
<item>
<widget class="QCheckBox" name="chkManualKeepalive">
<property name="text">
<string>Manual keep-alive</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="edManualKeepalive">
<property name="suffix">
<string> sec</string>
</property>
<property name="minimum">
<number>10</number>
</property>
<property name="maximum">
<number>120</number>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_5">
<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>Tray notify, delay:</string>
<string>Quit message</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="edTrayDelay">
<property name="suffix">
<string> sec</string>
<widget class="QLineEdit" name="edQuit"/>
</item>
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Font</string>
</property>
<property name="minimum">
<number>1</number>
<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="horizontalSpacer_2">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
<width>20</width>
<height>44</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>
</widget>
<widget class="QWidget" name="tabColors">
<attribute name="title">
<string>Colors</string>
</attribute>
</widget>
<widget class="QWidget" name="tabIRCv3">
<attribute name="title">
<string>IRCv3 extensions</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_3">
<property name="verticalSpacing">
<number>12</number>
</property>
<item row="0" column="0">
<layout class="QVBoxLayout" name="verticalLayout_3">
<property name="spacing">
<number>6</number>
</property>
</widget>
<item>
<widget class="QCheckBox" name="chkAccountNotify">
<property name="text">
<string>Account notification (account-notify)</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_7">
<property name="text">
<string>Receive notification when other members login to accounts.</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_17">
<property name="text">
<string>Handling of this notification is not dealt with in IdealIRC yet, but we do receive it from the server.</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QLineEdit" name="edTimestamp"/>
</item>
<item>
<spacer name="horizontalSpacer">
<item row="0" column="1">
<spacer name="horizontalSpacer_7">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<width>377</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_8">
<item>
<widget class="QCheckBox" name="chkManualKeepalive">
<property name="text">
<string>Manual keep-alive</string>
<item row="1" column="0">
<layout class="QVBoxLayout" name="verticalLayout_5">
<property name="spacing">
<number>6</number>
</property>
</widget>
<item>
<widget class="QCheckBox" name="chkExtendedJoin">
<property name="text">
<string>Extended join (extended-join)</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_8">
<property name="text">
<string>Receive account name for members joining a channel.</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QSpinBox" name="edManualKeepalive">
<property name="suffix">
<string> sec</string>
<item row="2" column="0">
<layout class="QVBoxLayout" name="verticalLayout_6">
<property name="spacing">
<number>6</number>
</property>
<property name="minimum">
<number>10</number>
<item>
<widget class="QCheckBox" name="chkAwayNotify">
<property name="text">
<string>Away notification (away-notify)</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_9">
<property name="text">
<string>Receive away messages from other members.</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="3" column="0">
<layout class="QVBoxLayout" name="verticalLayout_7">
<property name="spacing">
<number>6</number>
</property>
<property name="maximum">
<number>120</number>
<item>
<widget class="QCheckBox" name="chkInviteNotify">
<property name="text">
<string>Invite notification (invite-notify)</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_10">
<property name="text">
<string>Allows privileged channel users to see when someone is invited to their channel.</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="4" column="0">
<layout class="QVBoxLayout" name="verticalLayout_8">
<property name="spacing">
<number>6</number>
</property>
</widget>
<item>
<widget class="QCheckBox" name="chkMutltiPrefix">
<property name="text">
<string>Multiple prefixes (multi-prefix)</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_11">
<property name="text">
<string>Allows IdealIRC to see all the statuses (i.e. voice (+), chanop(@)) that other clients have in a channel</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_14">
<property name="text">
<string>rather than just the highest. This also mitigates an issue if someone has both voice (+) and chanop (@),</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_15">
<property name="text">
<string>and you join that channel, you only receive their chanop (@). When they de-op, you don't know about</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_16">
<property name="text">
<string>their voice (+) mode.</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="horizontalSpacer_5">
<item row="5" column="0">
<layout class="QVBoxLayout" name="verticalLayout_9">
<property name="spacing">
<number>6</number>
</property>
<item>
<widget class="QCheckBox" name="chkUserhostInNames">
<property name="text">
<string>Hostname in /NAMES (userhost-in-names)</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_12">
<property name="text">
<string>Allows IdealIRC to more easily see the user/hostnames of other clients when joining channels.</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="6" column="0">
<layout class="QVBoxLayout" name="verticalLayout_10">
<property name="spacing">
<number>6</number>
</property>
<item>
<widget class="QCheckBox" name="chkHistoryPlayback">
<property name="text">
<string>Server history playback (message-tags, server-time)</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_13">
<property name="text">
<string>This is a combination of the extensions &quot;message-tags&quot; and &quot;server-time&quot; which triggers this feature</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_18">
<property name="text">
<string>for channels that enable it.</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="7" column="0" colspan="2">
<spacer name="verticalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
<width>20</width>
<height>329</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>
</widget>
<widget class="QWidget" name="tabBgImg">
<attribute name="title">
<string>Background image</string>
</attribute>
<layout class="QGridLayout" name="gridLayout">
<item row="2" column="0">
<widget class="QCheckBox" name="chkEnableBgImage">
<property name="text">
<string>Opacity</string>
<string>Enable background image</string>
</property>
</widget>
</item>
<item>
<widget class="QSlider" name="hsImageOpacity">
<property name="maximum">
<number>100</number>
</property>
<property name="value">
<number>100</number>
</property>
<item row="5" 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="3" 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>
<item row="5" column="1">
<spacer name="horizontalSpacer_4">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="invertedAppearance">
<bool>false</bool>
</property>
<property name="invertedControls">
<bool>false</bool>
<property name="sizeHint" stdset="0">
<size>
<width>287</width>
<height>20</height>
</size>
</property>
</widget>
</spacer>
</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>
<item row="4" 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="7" column="0">
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="text">
<string>Scaling</string>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>301</height>
</size>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="edImageScaling"/>
</spacer>
</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>
<item row="1" column="0" colspan="2">
<widget class="QLabel" name="label_6">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="edImage"/>
</item>
<item>
<widget class="QPushButton" name="btnImageBrowse">
<property name="text">
<string>Browse...</string>
<string>This is trash, don't bother enabling this tab page.</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</widget>
</widget>
</widget>
</item>
</layout>
</widget>
<tabstops>
<tabstop>chkShowOptions</tabstop>
@ -449,7 +679,6 @@
<tabstop>btnImageBrowse</tabstop>
<tabstop>hsImageOpacity</tabstop>
<tabstop>edImageScaling</tabstop>
<tabstop>tabWidget</tabstop>
</tabstops>
<resources/>
<connections/>

@ -7,119 +7,200 @@
#include "IConfigServers.h"
#include "ui_IConfigServers.h"
#include "AddServer.h"
#include "ConfigMgr.h"
#include "ServerItem.h"
#include <QMessageBox>
#include <QDebug>
IConfigServers::IConfigServers(QWidget *parent) :
QWidget(parent),
ui(new Ui::IConfigServers)
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);
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_SASL = conf.connection("SASL");
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->edSASL->setText(cf_SASL);
ui->chkSSL->setChecked(cf_SSL);
ui->servers->setModel(&smodel);
ui->servers->setItemDelegateForColumn(2, &m_serverOptionsDelegate);
ui->servers->setItemDelegateForColumn(3, &m_serverOptionsDelegate);
ui->servers->setItemDelegateForColumn(4, &m_serverOptionsDelegate);
for (const auto& index : smodel.getEditorColumns()) {
ui->servers->openPersistentEditor(index);
}
connect(&smodel, &ServerModel::newEntry,
[this](const QModelIndex& sslIdx, const QModelIndex& passwordIdx, const QModelIndex& saslIdx) {
ui->servers->openPersistentEditor(sslIdx);
ui->servers->openPersistentEditor(passwordIdx);
ui->servers->openPersistentEditor(saslIdx);
serversModified = true;
});
ui->servers->setColumnWidth(2, 50); // SSL column
ui->servers->setColumnWidth(3, 100); // Password column
ui->servers->setColumnWidth(4, 50); // SASL column
}
IConfigServers::~IConfigServers()
{
delete ui;
delete ui;
}
bool IConfigServers::isChanged() const
{
return cf_Realname != ui->edRealName->text()
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();
|| cf_SASL != ui->edSASL->text()
|| cf_SSL != ui->chkSSL->isChecked()
|| serversModified;
}
bool IConfigServers::connectToNewStatus() const
{
return ui->chkNewStatus->isChecked();
return ui->chkNewStatus->isChecked();
}
void IConfigServers::unsetConnectToNewStatus()
{
ui->chkNewStatus->setChecked(false);
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();
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("SASL", ui->edSASL->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_SASL = conf.connection("SASL");
cf_SSL = conf.connection("SSL").toInt();
if (!smodel.saveToFile())
QMessageBox::warning(this, tr("Failed to save servers.json"), tr("Unable to open servers.json for writing, changes will be lost upon next restart!"));
else
serversModified = false;
}
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);
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->edSASL->setText(cf_SASL);
if (serversModified) {
smodel.reloadModel();
serversModified = false;
for (const auto& index : smodel.getEditorColumns()) {
ui->servers->openPersistentEditor(index);
}
}
}
void IConfigServers::on_btnShowPassword_toggled(bool checked)
{
ui->edServerPassword->setEchoMode(checked ? QLineEdit::Normal : QLineEdit::Password);
ui->edServerPassword->setEchoMode(checked ? QLineEdit::Normal : QLineEdit::Password);
}
void IConfigServers::on_btnShowSASL_toggled(bool checked)
{
ui->edSASL->setEchoMode(checked ? QLineEdit::Normal : QLineEdit::Password);
}
void IConfigServers::on_servers_clicked(const QModelIndex& index)
{
auto* item = static_cast<const ServerItem*>(index.internalPointer());
ui->edServer->setText( item->address() );
ui->edServerPassword->setText( item->password() );
ui->chkSSL->setChecked( item->ssl() );
if (item->parent())
ui->edSASL->setText(item->parent()->sasl());
else
ui->edSASL->setText(item->sasl());
}
void IConfigServers::on_btnEditServers_clicked()
void IConfigServers::on_btnAddServer_clicked()
{
editor->show();
if (!addServerDlg) {
addServerDlg = new AddServer(smodel.getNetworks(), this);
addServerDlg->show();
connect(addServerDlg, &QDialog::destroyed,
[this] {
addServerDlg = nullptr;
});
connect(addServerDlg, &AddServer::saved,
[this] {
if (addServerDlg->isServer())
smodel.addServer(addServerDlg->name(), addServerDlg->address(), addServerDlg->password(), addServerDlg->sasl(), addServerDlg->ssl(), addServerDlg->networkIndex());
else
smodel.addNetwork(addServerDlg->name(), addServerDlg->address(), addServerDlg->password(), addServerDlg->sasl(), addServerDlg->ssl());
});
}
}
void IConfigServers::on_servers_clicked(const QModelIndex &index)
void IConfigServers::on_btnDeleteSelected_clicked()
{
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);
auto index = ui->servers->currentIndex();
if (!index.isValid())
return;
const auto* item = static_cast<const ServerItem*>(index.internalPointer());
QMessageBox::StandardButton btn;
if (item->isNetwork())
btn = QMessageBox::question(this,
tr("Delete entire network"),
tr("You are about to delete an entire network!\nConfirm deletion of %1?").arg(item->name()));
else
btn = QMessageBox::question(this,
tr("Delete server"),
tr("Confirm deletion of %1?").arg(item->name()));
if (btn == QMessageBox::Yes) {
smodel.deleteEntry(index);
serversModified = true;
}
else {
return;
}
}

@ -8,43 +8,50 @@
#ifndef ICONFIGSERVERS_H
#define ICONFIGSERVERS_H
#include "ServerEditor.h"
#include "ServerModel.h"
#include "ServerOptionsDelegate.h"
#include <QWidget>
namespace Ui {
class IConfigServers;
}
class AddServer;
class IConfigServers : public QWidget
{
Q_OBJECT
Q_OBJECT
public:
explicit IConfigServers(QWidget *parent = nullptr);
~IConfigServers();
bool isChanged() const;
bool connectToNewStatus() const;
void unsetConnectToNewStatus();
void save();
void reset();
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);
void on_btnShowPassword_toggled(bool checked);
void on_btnShowSASL_toggled(bool checked);
void on_servers_clicked(const QModelIndex& index);
void on_btnAddServer_clicked();
void on_btnDeleteSelected_clicked();
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;
Ui::IConfigServers *ui;
AddServer* addServerDlg{};
ServerOptionsDelegate m_serverOptionsDelegate;
ServerModel smodel;
QString cf_Realname;
QString cf_Username;
QString cf_Nickname;
QString cf_AltNickame;
QString cf_Server;
QString cf_Password;
QString cf_SASL;
bool cf_SSL;
bool serversModified{ false };
};
#endif // ICONFIGSERVERS_H

@ -159,6 +159,40 @@
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_9">
<item>
<widget class="QLabel" name="label_7">
<property name="minimumSize">
<size>
<width>150</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>SASL credentials</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="edSASL">
<property name="echoMode">
<enum>QLineEdit::Password</enum>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="btnShowSASL">
<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">
@ -169,16 +203,35 @@
<item>
<widget class="QTreeView" name="servers">
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
<set>QAbstractItemView::DoubleClicked</set>
</property>
<property name="uniformRowHeights">
<bool>true</bool>
</property>
<property name="animated">
<bool>true</bool>
</property>
<attribute name="headerDefaultSectionSize">
<number>150</number>
</attribute>
<attribute name="headerStretchLastSection">
<bool>false</bool>
</attribute>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_6">
<item>
<widget class="QPushButton" name="btnEditServers">
<widget class="QPushButton" name="btnAddServer">
<property name="text">
<string>Add...</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btnDeleteSelected">
<property name="text">
<string>Edit servers...</string>
<string>Delete selected</string>
</property>
</widget>
</item>
@ -238,8 +291,10 @@
<tabstop>edServer</tabstop>
<tabstop>edServerPassword</tabstop>
<tabstop>btnShowPassword</tabstop>
<tabstop>chkSSL</tabstop>
<tabstop>servers</tabstop>
<tabstop>btnEditServers</tabstop>
<tabstop>btnAddServer</tabstop>
<tabstop>btnDeleteSelected</tabstop>
<tabstop>chkNewStatus</tabstop>
</tabstops>
<resources/>

@ -1,313 +0,0 @@
/*
* IdealIRC - Internet Relay Chat client
* Copyright (C) 2021 Tom-Andre Barstad.
* This software is licensed under the Software Attribution License.
* See LICENSE for more information.
*/
#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);
}

@ -1,59 +0,0 @@
/*
* IdealIRC - Internet Relay Chat client
* Copyright (C) 2021 Tom-Andre Barstad.
* This software is licensed under the Software Attribution License.
* See LICENSE for more information.
*/
#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

@ -1,251 +0,0 @@
<?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,51 @@
/*
* IdealIRC - Internet Relay Chat client
* Copyright (C) 2022 Tom-Andre Barstad.
* This software is licensed under the Software Attribution License.
* See LICENSE for more information.
*/
#include "ServerItem.h"
namespace Key = ServerJsonKey;
ServerItem::ServerItem(const QJsonObject& data, int row, ServerItem* parentItem, bool isNetwork)
: m_data(data)
, m_row(row)
, m_parent(parentItem)
, m_isNetwork{ isNetwork }
{}
QString ServerItem::name() const
{
return m_data[Key::Name].toString();
}
QString ServerItem::address() const
{
auto host{ m_data[Key::Host].toString() };
auto port{ m_data[Key::Port].toInt() };
if (host.isEmpty())
host = "no.host";
if (port == 0)
port = ssl() ? 6697 : 6667;
return QStringLiteral("%1:%2").arg(host).arg(port);
}
QString ServerItem::password() const
{
return m_data[Key::Password].toString();
}
QString ServerItem::sasl() const
{
return m_data[Key::SASL].toString();
}
bool ServerItem::ssl() const
{
return m_data[Key::SSL].toBool();
}

@ -0,0 +1,76 @@
/*
* IdealIRC - Internet Relay Chat client
* Copyright (C) 2022 Tom-Andre Barstad.
* This software is licensed under the Software Attribution License.
* See LICENSE for more information.
*/
#ifndef SERVERITEM_H
#define SERVERITEM_H
#include <QList>
#include <QString>
#include <QJsonObject>
namespace ServerJsonKey
{
constexpr auto Servers = "Servers";
constexpr auto Networks = "Networks";
constexpr auto Name = "Name";
constexpr auto Host = "Host";
constexpr auto Port = "Port";
constexpr auto Password = "Password";
constexpr auto SASL = "SASL";
constexpr auto SSL = "SSL";
}
class ServerItem
{
public:
ServerItem(const QJsonObject& data, int row, ServerItem* parentItem, bool isNetwork);
QString name() const;
void setName(const QString& name) { m_data[ServerJsonKey::Name] = name; }
QString address() const;
void setAddress(const QString& address)
{
auto hostport = address.split(':');
if (hostport.size() == 1)
hostport << (ssl() ? "6697" : "6667");
if (hostport[0].isEmpty())
hostport[0] = "server.name";
m_data[ServerJsonKey::Host] = hostport[0];
m_data[ServerJsonKey::Port] = hostport[1].toUShort();
}
QString password() const;
void setPassword(const QString& password) { m_data[ServerJsonKey::Password] = password; }
QString sasl() const;
void setSasl(const QString& credentials) { m_data[ServerJsonKey::SASL] = credentials; }
bool ssl() const;
void setSsl(bool enable) { m_data[ServerJsonKey::SSL] = enable; }
ServerItem* parent() const { return m_parent; }
int row() const { return m_row; }
void incRow() { ++m_row; }
void decRow() { --m_row; }
QList<ServerItem>& children() { return m_children; }
bool isNetwork() const { return m_isNetwork; }
private:
QJsonObject m_data;
QList<ServerItem> m_children;
int m_row;
ServerItem* m_parent;
bool m_isNetwork;
};
#endif // SERVERITEM_H

@ -1,133 +0,0 @@
/*
* IdealIRC - Internet Relay Chat client
* Copyright (C) 2021 Tom-Andre Barstad.
* This software is licensed under the Software Attribution License.
* See LICENSE for more information.
*/
#include "ServerMgr.h"
#include "config.h"
#include <fmt/format.h>
ServerMgr::ServerMgr(QObject *parent) :
QObject(parent),
ini( QString(LOCAL_PATH+"/servers.ini").toStdString() )
{
}
QStringList ServerMgr::networkList()
{
int count = ini.count();
QStringList r;
for (int i = 0; i < count; i++) {
const auto name = QString::fromStdString( ini.section(i) );
r.push_back(name);
}
return r;
}
QHash<QString,QString> ServerMgr::serverList(const QString& network)
{
const auto networkStr = network.toStdString();
int count = ini.count(networkStr);
QHash<QString,QString> r;
for (int i = 0; i < count; i++) {
const auto servername = QString::fromStdString( ini.item(networkStr, i) );
const auto serverdetails = QString::fromStdString( ini.read(networkStr, i) );
r.insert(servername, serverdetails);
}
return r;
}
QString ServerMgr::defaultServer(const QString& network)
{
return QString::fromStdString( ini.read(network.toStdString(), "DEFAULT") );
}
bool ServerMgr::addNetwork(const QString& name)
{
const auto nameStr = name.toStdString();
if (nameStr == "NONE" || ini.exist(nameStr))
return false;
ini.write(nameStr, "DEFAULT", "server.name");
return true;
}
bool ServerMgr::renameNetwork(const QString& o_name, const QString& n_name)
{
if ((o_name == "NONE") || (n_name == "NONE"))
return false;
auto err = ini.rename(o_name.toStdString(), n_name.toStdString());
return err == IniFile::Error::NoError;
}
void ServerMgr::delNetwork(const QString& name, bool keep_servers)
{
// If servers=true, we will keep the servers by moving them to the NONE section.
const auto nameStr = name.toStdString();
if (! ini.exist(nameStr))
return;
if (keep_servers) {
int max = ini.count(nameStr);
for (int i = 0; i < max; i++) {
auto item = fmt::format("{}_{}", name.toStdString(), ini.item(nameStr, i));
auto value = ini.read(nameStr, i);
ini.write("NONE", item, value);
}
}
ini.remove(nameStr);
}
bool ServerMgr::addServer(const QString& name, const QString& host, const QString& pw, const QString& network)
{
QString details;
if (pw.isEmpty()) {
details = host;
}
else {
details = QString("%1|%2")
.arg(host)
.arg(pw);
}
const auto networkStr = network.toStdString();
if (!ini.exist(networkStr) && networkStr != "NONE")
return false;
ini.write(networkStr, name.toStdString(), details.toStdString());
return true;
}
void ServerMgr::delServer(const QString& name, const QString& network)
{
ini.remove(network.toStdString(), name.toStdString());
}
bool ServerMgr::hasNetwork(const QString& name)
{
return ini.exist(name.toStdString());
}
bool ServerMgr::hasServer(const QString& name, const QString& network)
{
return ini.exist(network.toStdString(), name.toStdString());
}
QString ServerMgr::getServerDetails(const QString& name, const QString& network)
{
return QString::fromStdString( ini.read(network.toStdString(), name.toStdString()) );
}

@ -1,66 +0,0 @@
/*
* IdealIRC - Internet Relay Chat client
* Copyright (C) 2021 Tom-Andre Barstad.
* This software is licensed under the Software Attribution License.
* See LICENSE for more information.
*/
#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(const 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(const QString& network);
// Add new network to servers.ini - returns false if network exist
bool addNetwork(const QString& name);
// Rename a network - returns false if new network name already exist
bool renameNetwork(const QString& o_name, const QString& n_name);
// Delete network
void delNetwork(const QString& name, bool keep_servers = false);
// Add (or update) a server to network - returns false if network doesn't exsist
bool addServer(const QString& name, const QString& host /*host:port*/, const QString& pw = "", const QString& network = "NONE");
// Delete a server from network
void delServer(const QString& name, const QString& network = "NONE");
// Check of we have the given network name
bool hasNetwork(const QString& name);
// Check if we have the given server name inside the network
bool hasServer(const QString& name, const QString& network = "NONE");
// Get server details
QString getServerDetails(const QString& name, const QString& network = "NONE");
private:
IniFile ini;
};
#endif // SERVERMGR_H

@ -1,358 +1,430 @@
/*
* IdealIRC - Internet Relay Chat client
* Copyright (C) 2021 Tom-Andre Barstad.
* Copyright (C) 2022 Tom-Andre Barstad.
* This software is licensed under the Software Attribution License.
* See LICENSE for more information.
*/
#include "ServerModel.h"
#include <QHashIterator>
#include "config.h"
#include <QJsonArray>
#include <QFile>
#include <QDebug>
#include <QTimer>
#include <QIcon>
#include <QFile>
ServerModel::ServerModel(QObject *parent) :
QStandardItemModel(parent)
namespace {
QJsonObject createJsonObject(const QString& name, const QString& address, const QString& password, const QString& sasl, bool isSsl)
{
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 {};
}
namespace Key = ServerJsonKey;
QModelIndex ServerModel::addNetwork(QString name, QString server)
{
QStandardItem *root = invisibleRootItem();
auto hostport{ address.split(':') };
if (hostport.size() == 1) {
hostport[0] = "no.host";
hostport << (isSsl ? "6697" : "6667");
}
QStandardItem *pname = new QStandardItem(QIcon(":/options/gfx/network.png"), name);
QStandardItem *phost = new QStandardItem(server);
QList<QStandardItem*> list;
list << pname << phost;
const auto host{ hostport[0] };
const auto port{ hostport.count() > 1 ? hostport[1].toShort()
: (isSsl ? 6697 : 6667) };
QJsonObject obj;
root->appendRow(list);
hostmap.insert(server, pname->index());
netmap.insert(name, pname->index());
obj[Key::Name] = name;
obj[Key::Host] = host;
obj[Key::Port] = port;
obj[Key::SSL] = isSsl;
obj[Key::Password] = password;
if (smgr.addNetwork(name))
smgr.addServer("DEFAULT", server, "", name);
if (!sasl.isEmpty())
obj[Key::SASL] = sasl;
return pname->index();
return obj;
}
}
void ServerModel::setNetworkServer(QString name, QString server)
ServerModel::ServerModel(QObject* parent) :
QAbstractItemModel(parent)
{
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);
loadFromFile();
}
void ServerModel::renameNetwork(QString name, QString newname)
bool ServerModel::saveToFile()
{
QModelIndex current = netmap.value(name);
int row = current.row();
QModelIndex nameIndex = index(row, 0, current.parent());
namespace Key = ServerJsonKey;
QJsonArray servers, networks;
for (auto& item : m_items) {
if (item.isNetwork()) {
QJsonArray networkServers;
for (auto& child : item.children()) {
QJsonObject server{ createJsonObject(child.name(), child.address(), child.password(), "", child.ssl()) };
networkServers << server;
}
QJsonObject network{ createJsonObject(item.name(), item.address(), item.password(), item.sasl(), item.ssl()) };
network[Key::Servers] = networkServers;
networks << network;
}
else {
QJsonObject server{ createJsonObject(item.name(), item.address(), item.password(), item.sasl(), item.ssl()) };
servers << server;
}
}
QJsonObject root;
root[Key::Servers] = servers;
root[Key::Networks] = networks;
QJsonDocument doc(root);
QStandardItem *item = itemFromIndex(nameIndex);
item->setText(newname);
const auto path{ QStringLiteral("%1/servers.json").arg(LOCAL_PATH) };
QFile f{ path };
if (!f.open(QIODevice::WriteOnly))
return false;
netmap.remove(name);
netmap.insert(newname, current);
smgr.renameNetwork(name, newname);
f.write(doc.toJson(QJsonDocument::Indented));
f.flush();
f.close();
return true;
}
void ServerModel::delNetwork(QString name, bool keepServers)
void ServerModel::reloadModel()
{
smgr.delNetwork(name, keepServers);
resetModel();
beginResetModel();
m_items.clear();
loadFromFile();
endResetModel();
}
QModelIndex ServerModel::addServer(QString name, QString server, QString network)
Qt::ItemFlags ServerModel::flags(const QModelIndex& index) const
{
QStandardItem *parent;
if (network.length() == 0)
network = "NONE";
/* Don't care about invalid indexes */
if (!index.isValid())
return Qt::NoItemFlags;
if (network == "NONE")
parent = invisibleRootItem();
else
parent = itemFromIndex( netmap.value(network) );
/* Columns containing widgets for editing */
if (index.column() > 1)
return Qt::ItemIsEnabled | Qt::ItemIsEditable;
QStandardItem *sname = new QStandardItem(QIcon(":/options/gfx/server.png"), name);
QStandardItem *shost = new QStandardItem(server);
QList<QStandardItem*> list;
list << sname << shost;
/* Directly editable columns */
Qt::ItemFlags flags{ Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsEnabled };
parent->appendRow(list);
hostmap.insert(server, indexFromItem(sname));
if (network == "NONE")
nonemap.insert(name, indexFromItem(sname));
const auto* item = static_cast<const ServerItem*>(index.internalPointer());
if (!item->isNetwork())
flags |= Qt::ItemNeverHasChildren;
smgr.addServer(name, server, "", network);
return indexFromItem(sname);
return flags;
}
void ServerModel::setServer(QString name, QString server, QString password, QString network)
QVariant ServerModel::data(const QModelIndex& index, int role) const
{
QStandardItem *parent;
if (!index.isValid())
return {};
if (network.length() == 0)
network = "NONE";
if (role == Qt::DisplayRole || role == Qt::EditRole) {
const auto* item = static_cast<const ServerItem*>(index.internalPointer());
if (network == "NONE")
parent = invisibleRootItem();
else
parent = itemFromIndex( netmap.value(network) );
if (index.column() == 0)
return item->name();
QModelIndex parentIdx = indexFromItem(parent); // Parent index
QModelIndex current; // Item's index
else if (index.column() == 1)
return item->address();
if (network != "NONE") {
else
return {};
}
for (int r = 0 ;; r++) {
QModelIndex idx = parentIdx.child(r, 0);
if (role == Qt::DecorationRole && index.column() == 0) {
const auto* item = static_cast<const ServerItem*>(index.internalPointer());
if (item->isNetwork())
return QIcon(":/Icons/network.png");
else
return QIcon(":/Icons/serverwindow.png");
}
if (! idx.isValid())
return; // No relevant child found, stop.
return {};
}
if (idx.data().toString() == name) {
current = idx;
break;
}
}
QVariant ServerModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (role == Qt::DisplayRole && orientation == Qt::Horizontal) {
QStringList labels {
tr("Name"),
tr("Address"),
"",
"",
""
};
return labels[section];
}
else {
current = nonemap.value(name);
return {};
}
}
bool ServerModel::setData(const QModelIndex& index, const QVariant& value, int role)
{
if (!index.isValid() || role != Qt::EditRole)
return false;
auto* item = static_cast<ServerItem*>(index.internalPointer());
bool ret{ true };
switch (index.column()) {
case 0:
if (value.toString().isEmpty())
ret = false;
else
item->setName(value.toString());
break;
case 1:
if (value.toString().isEmpty())
ret = false;
else
item->setAddress(value.toString());
break;
default:
ret = false;
}
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);
return ret;
}
hostmap.insert(server, nameIndex);
smgr.addServer(name, server, password, network);
int ServerModel::rowCount(const QModelIndex& parent) const
{
if (!parent.isValid())
return m_items.count();
else {
auto* parentItem = static_cast<ServerItem*>(parent.internalPointer());
return parentItem->children().count();
}
}
void ServerModel::renameServer(QString name, QString newname, QString network)
QModelIndex ServerModel::index(int row, int column, const QModelIndex& parent) const
{
QStandardItem *parent;
if (!hasIndex(row, column, parent))
return {};
if (network.length() == 0)
network = "NONE";
ServerItem* item{};
if (network == "NONE")
parent = invisibleRootItem();
else
parent = itemFromIndex( netmap.value(network) );
if (!parent.isValid())
item = &m_items[row];
else {
auto* parentItem = static_cast<ServerItem*>(parent.internalPointer());
item = &parentItem->children()[row];
}
QModelIndex parentIdx = indexFromItem(parent); // Parent index
QModelIndex current; // Item's index
return createIndex(row, column, item);
}
if (network != "NONE") {
QModelIndex ServerModel::parent(const QModelIndex& index) const
{
if (!index.isValid())
return {};
for (int r = 0 ;; r++) {
QModelIndex idx = parentIdx.child(r, 0);
auto* parentItem = static_cast<ServerItem*>(index.internalPointer())->parent();
if (!parentItem)
return {};
if (! idx.isValid())
return; // No relevant child found, stop.
return createIndex(parentItem->row(), 0, parentItem);
}
if (idx.data().toString() == name) {
current = idx;
break;
}
}
void ServerModel::addServer(const QString& name, const QString& address, const QString& password, const QString& sasl, bool isSsl, int networkIdx)
{
QList<ServerItem>* items{};
ServerItem* parentNetworkItem{};
int pos{};
QModelIndex modelIdx;
/* Network server */
if (networkIdx > -1) {
modelIdx = networkIndex(networkIdx);
parentNetworkItem = static_cast<ServerItem*>(modelIdx.internalPointer());
items = &(parentNetworkItem->children());
pos = items->count();
}
/* Non-network server */
else {
current = nonemap.value(name);
nonemap.remove(name);
nonemap.insert(newname, current);
}
items = &m_items;
int row = current.row();
QModelIndex serverIndex = index(row, 0, current.parent());
QStandardItem *item = itemFromIndex(serverIndex);
item->setText(newname);
for (auto& item : m_items) {
if (item.isNetwork())
item.incRow(); // Increment row number for each network, since any non-network server is inserted before these.
else
++pos; // Finds the end position of non-network servers. Add server there (bottom of non-networks; before networks are listed.)
}
}
QString details = smgr.getServerDetails(name, network);
smgr.delServer(name, network);
smgr.addServer(name, details, "", network);
const auto obj{ createJsonObject(name, address, password, sasl, isSsl) };
beginInsertRows(modelIdx, pos, pos);
items->insert(pos, ServerItem(obj, pos, parentNetworkItem, false));
endInsertRows();
emit newEntry(
createIndex(pos, 2, &((*items)[pos])), // SSL
createIndex(pos, 3, &((*items)[pos])), // Password
createIndex(pos, 4, &((*items)[pos])) // SASL
);
/*
* A hack to make new child items appear if parent's expanded.
* This also "solves" when a new/empty network gets its first entry, the network item (parent) won't get the "expand/collapse" decoration.
* Doing this should not be necessary, so there is an issue with this ServerModel, somewhere...
*/
QTimer::singleShot(1, [this] {
auto pos = m_items.size();
beginInsertRows(QModelIndex{}, pos, pos);
m_items << ServerItem({}, pos, nullptr, false);
endInsertRows();
QTimer::singleShot(1, [this] {
auto pos = m_items.size() - 1;
beginRemoveRows(QModelIndex{}, pos, pos);
m_items.removeLast();
endRemoveRows();
});
});
}
void ServerModel::delServer(QString name, QString network)
void ServerModel::addNetwork(const QString& name, const QString& address, const QString& password, const QString& sasl, bool isSsl)
{
QStandardItem *parent;
const auto obj{ createJsonObject(name, address, password, sasl, isSsl) };
const auto idx{ m_items.count() };
beginInsertRows(QModelIndex{}, idx, idx);
m_items << ServerItem(obj, idx, nullptr, true);
endInsertRows();
emit newEntry(
createIndex(idx, 2, &(m_items.back())), // SSL
createIndex(idx, 3, &(m_items.back())), // Password
createIndex(idx, 4, &(m_items.back())) // SASL
);
}
if (network.length() == 0)
network = "NONE";
void ServerModel::deleteEntry(const QModelIndex& index)
{
auto* item = static_cast<ServerItem*>(index.internalPointer());
auto* parent = item->parent();
QList<ServerItem>* items{};
if (network == "NONE")
parent = invisibleRootItem();
if (parent == nullptr)
items = &m_items;
else
parent = itemFromIndex( netmap.value(network) );
items = &(parent->children());
QModelIndex parentIdx = indexFromItem(parent); // Parent index
QModelIndex current; // Item's index
beginRemoveRows(index.parent(), index.row(), index.row());
if (network != "NONE") {
items->removeAt(item->row());
for (int i = item->row(); i < items->count(); ++i)
(*items)[i].decRow();
for (int r = 0 ;; r++) {
QModelIndex idx = parentIdx.child(r, 0);
endRemoveRows();
}
if (! idx.isValid())
return; // No relevant child found, stop.
QStringList ServerModel::getNetworks() const
{
QStringList networks;
for (const auto& item : m_items) {
if (item.isNetwork())
networks << item.name();
}
if (idx.data().toString() == name) {
current = idx;
break;
return networks;
}
QList<QModelIndex> ServerModel::getEditorColumns()
{
QList<QModelIndex> indexList;
for (auto& topItem : m_items) {
indexList << createIndex(topItem.row(), 2, &topItem);
indexList << createIndex(topItem.row(), 3, &topItem);
indexList << createIndex(topItem.row(), 4, &topItem);
if (topItem.isNetwork()) {
auto& items = topItem.children();
for (auto& item : items) {
indexList << createIndex(item.row(), 2, &item);
indexList << createIndex(item.row(), 3, &item);
}
}
}
else {
current = nonemap.value(name);
nonemap.remove(name);
}
int row = current.row();
removeRow(row, current.parent());
smgr.delServer(name, network);
return indexList;
}
void ServerModel::resetModel()
QModelIndex ServerModel::networkIndex(int row, int col)
{
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));
}
}
int rc{ 0 };
for (auto& item : m_items) {
if (!item.isNetwork())
continue;
if (rc == row)
return createIndex(row, col, &item);
else
++rc;
}
return {};
}
QStringList ServerModel::networkList()
void ServerModel::createItems(const QJsonObject& json)
{
return smgr.networkList();
namespace Key = ServerJsonKey;
const auto servers = json[Key::Servers].toArray();
const auto networks = json[Key::Networks].toArray();
int rootRow{ 0 };
for (const auto& server : servers) {
m_items << ServerItem(server.toObject(), rootRow++, nullptr, false);
}
for (const auto& network : networks) {
const auto networkObj = network.toObject();
const auto childServers = networkObj[Key::Servers].toArray();
m_items << ServerItem(networkObj, rootRow++, nullptr, true);
auto& networkItem = m_items.back();
int childRow{ 0 };
for (const auto& cs : childServers) {
auto& networkServers = networkItem.children();
networkServers << ServerItem(cs.toObject(), childRow++, &networkItem, false);
}
}
}
QString ServerModel::details(QString name, QString network)
void ServerModel::loadFromFile()
{
return smgr.getServerDetails(name, network);
QJsonDocument jsondoc;
const auto path{ QStringLiteral("%1/servers.json").arg(LOCAL_PATH) };
QFile f{ path };
if (f.open(QIODevice::ReadOnly)) {
const auto jsonData{ f.readAll() };
qDebug() << "Read" << jsonData.size() << "bytes from" << path;
jsondoc = QJsonDocument::fromJson(jsonData);
f.close();
if (!jsondoc.isObject())
qWarning() << "Parse error for servers.json";
else
createItems(jsondoc.object());
}
else
qWarning() << "Unable to open servers.json:" << path;
}

@ -1,6 +1,6 @@
/*
* IdealIRC - Internet Relay Chat client
* Copyright (C) 2021 Tom-Andre Barstad.
* Copyright (C) 2022 Tom-Andre Barstad.
* This software is licensed under the Software Attribution License.
* See LICENSE for more information.
*/
@ -8,43 +8,68 @@
#ifndef SERVERMODEL_H
#define SERVERMODEL_H
#include <QStandardItemModel>
#include "ServerMgr.h"
#include <QHash>
#include <QPair>
#include "ServerItem.h"
#include <QAbstractItemModel>
#include <QList>
#include <QJsonDocument>
#include <QJsonObject>
/**
* @brief Model of servers.ini
* @details
* This class is from IdealIRC 0.x series and is going to be removed/replaced at any time.
* @brief Model of servers.json
*/
class ServerModel : public QStandardItemModel
class ServerModel : public QAbstractItemModel
{
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");
explicit ServerModel(QObject* parent = nullptr);
/// Save model to servers.json
bool saveToFile();
/// Resets and reloads entire model
void reloadModel();
Qt::ItemFlags flags(const QModelIndex &index) const;
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
bool setData(const QModelIndex &index, const QVariant& value, int role = Qt::EditRole) override;
int rowCount(const QModelIndex& parent = {}) const override;
int columnCount(const QModelIndex& parent = {}) const override
{
return 5;
};
QModelIndex index(int row, int column, const QModelIndex& parent = {}) const override;
QModelIndex parent(const QModelIndex& index) const override;
/// Add a server to the model. networkIdx = -1 means server is not part of a network.
void addServer(const QString& name, const QString& address, const QString& password, const QString& sasl, bool isSsl, int networkIdx);
/// Add a network to the model.
void addNetwork(const QString& name, const QString& address, const QString& password, const QString& sasl, bool isSsl);
void deleteEntry(const QModelIndex& index);
QStringList getNetworks() const;
QList<QModelIndex> getEditorColumns();
signals:
void newEntry(const QModelIndex& sslIdx, const QModelIndex& passwordIdx, const QModelIndex& saslIdx);
private:
ServerMgr smgr;
QModelIndex networkIndex(int row, int col = 0);
void createItems(const QJsonObject& json);
void loadFromFile();
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
// I don't like this being mutable, but the nature of Qt's item model seems to have forced my hand.
// Or I've probably not done the correct implementation design. In any way, it works, but smells a bit...
// This is the top-level items as shown in the UI, same order.
mutable QList<ServerItem> m_items;
};
#endif // SERVERMODEL_H

@ -0,0 +1,105 @@
/*
* IdealIRC - Internet Relay Chat client
* Copyright (C) 2022 Tom-Andre Barstad.
* This software is licensed under the Software Attribution License.
* See LICENSE for more information.
*/
#include "ServerOptionsDelegate.h"
#include "ServerItem.h"
#include <QInputDialog>
#include <QPushButton>
#include <QDebug>
ServerOptionsDelegate::ServerOptionsDelegate(QObject* parent)
: QStyledItemDelegate(parent)
{}
QWidget* ServerOptionsDelegate::createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const
{
if (!index.isValid())
return nullptr;
if (index.column() == 2) {
auto* btn = new QPushButton(tr("SSL"), parent);
btn->setCheckable(true);
return btn;
}
else if (index.column() == 3) {
auto* item = static_cast<ServerItem*>(index.internalPointer());
auto* btn = new QPushButton(tr("Password"), parent);
connect(btn, &QPushButton::pressed,
[item, parent] {
auto password = QInputDialog::getText(
parent,
tr("Enter password"),
tr("Password for %1:").arg(item->name()),
QLineEdit::Password,
item->password()
);
item->setPassword(password);
});
return btn;
}
else if (index.column() == 4) {
auto* item = static_cast<ServerItem*>(index.internalPointer());
if (item->parent()) // Only items with a parent are network servers / child servers. No SASL for them.
return nullptr;
auto* btn = new QPushButton(tr("SASL"), parent);
connect(btn, &QPushButton::pressed,
[item, parent] {
auto password = QInputDialog::getText(
parent,
tr("SASL credentials"),
tr("SASL credentials for %1:").arg(item->name()),
QLineEdit::Password,
item->sasl()
);
item->setSasl(password);
});
return btn;
}
else {
return nullptr;
}
}
void ServerOptionsDelegate::setEditorData(QWidget* editor, const QModelIndex& index) const
{
const auto* item = static_cast<const ServerItem*>(index.internalPointer());
if (index.column() == 2) {
auto* btn = qobject_cast<QPushButton*>(editor);
if (btn)
btn->setChecked(item->ssl());
else
qCritical() << "SSL button is not a QPushButton?!";
}
}
void ServerOptionsDelegate::setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const
{
auto* item = static_cast<ServerItem*>(index.internalPointer());
if (index.column() == 2) {
auto* btn = qobject_cast<QPushButton*>(editor);
if (btn)
item->setSsl(btn->isChecked());
else
qCritical() << "SSL button is not a QPushButton?!";
}
}
void ServerOptionsDelegate::updateEditorGeometry(QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& /*index*/) const
{
editor->setGeometry(option.rect);
}

@ -0,0 +1,26 @@
/*
* IdealIRC - Internet Relay Chat client
* Copyright (C) 2022 Tom-Andre Barstad.
* This software is licensed under the Software Attribution License.
* See LICENSE for more information.
*/
#ifndef SERVEROPTIONSDELEGATE_H
#define SERVEROPTIONSDELEGATE_H
#include <QStyledItemDelegate>
class ServerOptionsDelegate : public QStyledItemDelegate
{
Q_OBJECT
public:
explicit ServerOptionsDelegate(QObject* parent = nullptr);
QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const override;
void setEditorData(QWidget* editor, const QModelIndex& index) const override;
void setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const override;
void updateEditorGeometry(QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& index) const override;
};
#endif // SERVEROPTIONSDELEGATE_H

@ -1,125 +0,0 @@
/*
* IdealIRC - Internet Relay Chat client
* Copyright (C) 2021 Tom-Andre Barstad.
* This software is licensed under the Software Attribution License.
* See LICENSE for more information.
*/
#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;
}

@ -1,67 +0,0 @@
/*
* IdealIRC - Internet Relay Chat client
* Copyright (C) 2021 Tom-Andre Barstad.
* This software is licensed under the Software Attribution License.
* See LICENSE for more information.
*/
#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

@ -1,172 +0,0 @@
/*
* IdealIRC - Internet Relay Chat client
* Copyright (C) 2021 Tom-Andre Barstad.
* This software is licensed under the Software Attribution License.
* See LICENSE for more information.
*/
#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);
}
}

@ -1,54 +0,0 @@
/*
* IdealIRC - Internet Relay Chat client
* Copyright (C) 2021 Tom-Andre Barstad.
* This software is licensed under the Software Attribution License.
* See LICENSE for more information.
*/
#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

@ -6,6 +6,8 @@ list(APPEND ${component}_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/DCC.h
${CMAKE_CURRENT_SOURCE_DIR}/IRCBase.cpp
${CMAKE_CURRENT_SOURCE_DIR}/IRCBase.h
${CMAKE_CURRENT_SOURCE_DIR}/IRCMessage.cpp
${CMAKE_CURRENT_SOURCE_DIR}/IRCMessage.h
${CMAKE_CURRENT_SOURCE_DIR}/IRCChannel.cpp
${CMAKE_CURRENT_SOURCE_DIR}/IRCChannel.h
${CMAKE_CURRENT_SOURCE_DIR}/IRCError.cpp
@ -21,6 +23,59 @@ list(APPEND ${component}_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/Utilities.h
)
add_library(${component} STATIC ${${component}_SOURCES})
target_link_libraries(${component} fmt crypto ssl)
list(APPEND ${component}_COMMANDS
${CMAKE_CURRENT_SOURCE_DIR}/Commands/cmdError.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Commands/cmdInvite.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Commands/cmdJoin.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Commands/cmdKick.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Commands/cmdKill.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Commands/cmdMode.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Commands/cmdNick.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Commands/cmdNotice.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Commands/cmdPart.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Commands/cmdPing.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Commands/cmdPong.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Commands/cmdPrivmsg.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Commands/cmdQuit.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Commands/cmdTopic.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Commands/cmdV3Account.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Commands/cmdV3Authenticate.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Commands/cmdV3Away.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Commands/cmdV3Cap.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Commands/cmdWallops.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Commands/handleNumeric.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Commands/Unhandled.cpp
)
list(APPEND ${component}_PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/Private/addMemberToChannel.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Private/channelModeGroup.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Private/connected.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Private/ctcp.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Private/delMemberFromChannel.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Private/disconnectHandler.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Private/isChannelSymbol.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Private/isMemberPrivilegeSymbol.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Private/keepaliveTimeout.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Private/memberPrivilegeSymbolToMode.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Private/parseChannelModeMessage.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Private/parseIncoming.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Private/read.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Private/setDefaults.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Private/startKeepaliveTimer.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Private/stopKeepaliveTimer.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Private/verify_X509.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Private/write.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Private/writeNoMsg.cpp
)
add_library(${component} STATIC ${${component}_SOURCES} ${${component}_COMMANDS} ${${component}_PRIVATE})
target_link_libraries(${component}
NATUtils
fmt
crypto
ssl
)
target_include_directories(${component} PRIVATE ${CMAKE_SOURCE_DIR})

@ -10,66 +10,67 @@
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";
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"; // Environment for msvc already defines ERROR as a macro of sorts...
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";
constexpr auto* ACCOUNT = "ACCOUNT";
constexpr auto* AUTHENTICATE = "AUTHENTICATE";
}
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";
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

@ -0,0 +1,14 @@
/*
* IdealIRC Core - Internet Relay Chat API
* Copyright (C) 2021 Tom-Andre Barstad.
* This software is licensed under the Software Attribution License.
* See LICENSE for more information.
*/
#include "../IRCBase.h"
#include "../IRCBasePriv.h"
void IRCBasePriv::handleUnhandled(const IRCMessage& ircmessage)
{
super.onMsgUnhandled(ircmessage.getSender(), ircmessage.getCommand(), ircmessage.getArgs(), ircmessage.getMessage());
}

@ -0,0 +1,14 @@
/*
* IdealIRC Core - Internet Relay Chat API
* Copyright (C) 2021 Tom-Andre Barstad.
* This software is licensed under the Software Attribution License.
* See LICENSE for more information.
*/
#include "../IRCBase.h"
#include "../IRCBasePriv.h"
void IRCBasePriv::cmdError(const IRCMessage& ircmessage)
{
super.onMsgError(ircmessage.getMessage());
}