summaryrefslogtreecommitdiffstats
path: root/libcommuni/src/util
diff options
context:
space:
mode:
authorMarkus Mittendrein <git@maxmitti.tk>2014-10-06 15:03:54 +0200
committerMarkus Mittendrein <git@maxmitti.tk>2014-10-06 15:03:54 +0200
commit529f38bd8878b6b1bea2b5457031ce936aab8d80 (patch)
tree1193caefcad12f6a36f818048e4547e60add4398 /libcommuni/src/util
parent3b58b5536935adff242928ed9f30e1c0262fbd7c (diff)
downloadmanager-529f38bd8878b6b1bea2b5457031ce936aab8d80.tar.gz
manager-529f38bd8878b6b1bea2b5457031ce936aab8d80.zip
addedd communi
Diffstat (limited to 'libcommuni/src/util')
-rw-r--r--libcommuni/src/util/irccommandparser.cpp597
-rw-r--r--libcommuni/src/util/irccompleter.cpp405
-rw-r--r--libcommuni/src/util/irclagtimer.cpp243
-rw-r--r--libcommuni/src/util/ircpalette.cpp527
-rw-r--r--libcommuni/src/util/irctextformat.cpp550
-rw-r--r--libcommuni/src/util/irctoken.cpp125
-rw-r--r--libcommuni/src/util/ircutil.cpp61
-rw-r--r--libcommuni/src/util/util.pri39
-rw-r--r--libcommuni/src/util/util.pro11
9 files changed, 2558 insertions, 0 deletions
diff --git a/libcommuni/src/util/irccommandparser.cpp b/libcommuni/src/util/irccommandparser.cpp
new file mode 100644
index 0000000..e686a37
--- /dev/null
+++ b/libcommuni/src/util/irccommandparser.cpp
@@ -0,0 +1,597 @@
+/*
+ Copyright (C) 2008-2014 The Communi Project
+
+ You may use this file under the terms of BSD license as follows:
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ * Neither the name of the copyright holder nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR
+ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#include "irccommandparser.h"
+#include "irccommandparser_p.h"
+#include "irctoken_p.h"
+#include <climits>
+
+IRC_BEGIN_NAMESPACE
+
+/*!
+ \file irccommandparser.h
+ \brief \#include &lt;IrcCommandParser&gt;
+ */
+
+/*!
+ \class IrcCommandParser irccommandparser.h <IrcCommandParser>
+ \ingroup util
+ \brief Parses commands from user input.
+
+ \section syntax Syntax
+
+ Since the list of supported commands and the exact syntax for each
+ command is application specific, IrcCommandParser does not provide
+ any built-in command syntaxes. It is left up to the applications
+ to introduce the supported commands and syntaxes.
+ IrcCommandParser supports the following command syntax markup:
+
+ Syntax | Example | Description
+ -------------------|----------------------|------------
+ &lt;param&gt; | &lt;target&gt; | A required parameter.
+ (&lt;param&gt;) | (&lt;key&gt;) | An optional parameter.
+ &lt;param...&gt; | &lt;message...&gt; | A required parameter, multiple words accepted. (1)
+ (&lt;param...&gt;) | (&lt;message...&gt;) | An optional parameter, multiple words accepted. (1)
+ (&lt;\#param&gt;) | (&lt;\#channel&gt;) | An optional channel parameter. (2)
+ [param] | [target] | Inject the current target.
+
+ -# Multi-word parameters are only supported in the last parameter position.
+ -# An optional channel parameter is filled up with the current channel when absent.
+
+ The following example presents introducing some typical commands.
+ \code
+ IrcCommandParser* parser = new IrcCommandParser(this);
+ parser->addCommand(IrcCommand::Join, "JOIN <#channel> (<key>)");
+ parser->addCommand(IrcCommand::Part, "PART (<#channel>) (<message...>)");
+ parser->addCommand(IrcCommand::Kick, "KICK (<#channel>) <nick> (<reason...>)");
+ parser->addCommand(IrcCommand::CtcpAction, "ME [target] <message...>");
+ parser->addCommand(IrcCommand::CtcpAction, "ACTION <target> <message...>");
+ \endcode
+
+ \note The parameter names are insignificant, but descriptive
+ parameter names are recommended for the sake of readability.
+
+ \section context Context
+
+ Notice that commands are often context sensitive. While some command
+ may accept an optional parameter that is filled up with the current
+ target (channel/query) name when absent, another command may always
+ inject the current target name as a certain parameter. Therefore
+ IrcCommandParser must be kept up-to-date with the \ref target
+ "current target" and the \ref channels "list of channels".
+
+ \code
+ // currently in a query, and also present on some channels
+ parser->setTarget("jpnurmi");
+ parser->setChannels(QStringList() << "#communi" << "#freenode");
+ \endcode
+
+ \section command-triggers Command triggers
+
+ IrcCommandParser serves as a generic parser for typical IRC commands.
+ It can be utilized for parsing commands from user input in GUI clients,
+ and from messages from other clients when implementing IRC bots.
+
+ The command parsing behavior is controlled by setting up command
+ \ref triggers. Whilst a typical GUI client might use \c "/" as a command
+ trigger, an IRC bot might use \c "!" and the nick name of the bot. The
+ following snippet illustrates a typical GUI client usage.
+
+ \code
+ parser->setTarget("#communi");
+ parser->setTriggers(QStringList() << "/");
+ parser->parse(input);
+ \endcode
+
+ \p
+ Input | Result | Description
+ ------------------|---------------------|------------
+ "hello" | IrcCommand::Message | No matching command trigger => a message "hello" to \#communi
+ "/join #channel" | IrcCommand::Join | Matching command trigger => a command to join "#channel"
+
+ See the \ref bot "bot example" to see how the parser can be effectively utilized for IRC bots.
+
+ \section parse-custom-commands Custom commands
+
+ The parser also supports such custom client specific commands that
+ are not sent to the server. Since IrcCommand does not know how to
+ handle custom commands, the parser treats them as a special case
+ injecting the command as a first parameter.
+
+ \code
+ IrcParser parser;
+ parser.addCommand(IrcCommand::Custom, "QUERY <user>");
+ IrcCommand* command = parser.parse("/query jpnurmi");
+ Q_ASSERT(command->type() == IrcCommand::Custom);
+ qDebug() << command->parameters(); // ("QUERY", "jpnurmi")
+ \endcode
+ */
+
+/*!
+ \enum IrcCommandParser::Detail
+ This enum describes the available syntax details.
+ */
+
+/*!
+ \var IrcCommandParser::Full
+ \brief The syntax in full details
+ */
+
+/*!
+ \var IrcCommandParser::NoTarget
+ \brief The syntax has injected [target] removed
+ */
+
+/*!
+ \var IrcCommandParser::NoPrefix
+ \brief The syntax has \#channel prefixes removed
+ */
+
+/*!
+ \var IrcCommandParser::NoEllipsis
+ \brief The syntax has ellipsis... removed
+ */
+
+/*!
+ \var IrcCommandParser::NoParentheses
+ \brief The syntax has parentheses () removed
+ */
+
+/*!
+ \var IrcCommandParser::NoBrackets
+ \brief The syntax has brackets [] removed
+ */
+
+/*!
+ \var IrcCommandParser::NoAngles
+ \brief The syntax has angle brackets <> removed
+ */
+
+/*!
+ \var IrcCommandParser::Visual
+ \brief The syntax suitable for visual representation
+ */
+
+#ifndef IRC_DOXYGEN
+IrcCommandParserPrivate::IrcCommandParserPrivate() : tolerant(false)
+{
+}
+
+QList<IrcCommandInfo> IrcCommandParserPrivate::find(const QString& command) const
+{
+ QList<IrcCommandInfo> result;
+ foreach (const IrcCommandInfo& cmd, commands) {
+ if (cmd.command == command)
+ result += cmd;
+ }
+ return result;
+}
+
+static inline bool isOptional(const QString& token)
+{
+ return token.startsWith(QLatin1Char('(')) && token.endsWith(QLatin1Char(')'));
+}
+
+static inline bool isMultiWord(const QString& token)
+{
+ return token.contains(QLatin1String("..."));
+}
+
+static inline bool isChannel(const QString& token)
+{
+ return token.contains(QLatin1Char('#'));
+}
+
+static inline bool isCurrent(const QString& token)
+{
+ return token.startsWith(QLatin1Char('[')) && token.endsWith(QLatin1Char(']'));
+}
+
+IrcCommandInfo IrcCommandParserPrivate::parseSyntax(IrcCommand::Type type, const QString& syntax)
+{
+ IrcCommandInfo cmd;
+ QStringList tokens = syntax.split(QLatin1Char(' '), QString::SkipEmptyParts);
+ if (!tokens.isEmpty()) {
+ cmd.type = type;
+ cmd.command = tokens.takeFirst().toUpper();
+ cmd.syntax = tokens.join(QLatin1String(" "));
+ cmd.max = tokens.count();
+
+ IrcParameterInfo param;
+ for (int i = 0; i < tokens.count(); ++i) {
+ const QString& token = tokens.at(i);
+ param.optional = isOptional(token);
+ param.channel = isChannel(token);
+ param.current = isCurrent(token);
+ param.multi = isMultiWord(token);
+ if (!param.optional)
+ ++cmd.min;
+ if (param.optional && param.channel)
+ ++cmd.min;
+ const bool last = (i == tokens.count() - 1);
+ if (last && param.multi)
+ cmd.max = INT_MAX;
+ cmd.params += param;
+ }
+ }
+ return cmd;
+}
+
+IrcCommand* IrcCommandParserPrivate::parseCommand(const IrcCommandInfo& command, const QString& input) const
+{
+ IrcCommand* cmd = 0;
+ QStringList params;
+ if (processParameters(command, input, &params)) {
+ const int count = params.count();
+ if (count >= command.min && count <= command.max) {
+ cmd = new IrcCommand;
+ cmd->setType(command.type);
+ if (command.type == IrcCommand::Custom)
+ params.prepend(command.command);
+ cmd->setParameters(params);
+ }
+ }
+ return cmd;
+}
+
+bool IrcCommandParserPrivate::processParameters(const IrcCommandInfo& command, const QString& input, QStringList* params) const
+{
+ IrcTokenizer tokenizer(input);
+ for (int i = 0; i < command.params.count(); ++i) {
+ const IrcParameterInfo& info = command.params.at(i);
+ const IrcToken token = tokenizer.at(0);
+ if (info.optional && info.channel) {
+ if (onChannel()) {
+ if (!token.isValid() || !channels.contains(token.text(), Qt::CaseInsensitive)) {
+ params->append(target);
+ } else if (token.isValid()) {
+ tokenizer = tokenizer.mid(1);
+ params->append(token.text());
+ }
+ } else if (!channels.contains(token.text())) {
+ return false;
+ }
+ } else if (info.current) {
+ params->append(target);
+ } else if (info.multi) {
+ const QString multi = tokenizer.toString();
+ if (!multi.isEmpty()) {
+ params->append(multi);
+ tokenizer.clear();
+ }
+ } else {
+ tokenizer = tokenizer.mid(1);
+ if (token.isValid())
+ params->append(token.text());
+ }
+ }
+ return tokenizer.isEmpty();
+}
+
+bool IrcCommandParserPrivate::processCommand(QString* input, int* removed) const
+{
+ foreach (const QString& trigger, triggers) {
+ if (tolerant && trigger.length() == 1 && (input->startsWith(trigger.repeated(2)) || input->startsWith(trigger + QLatin1Char(' ')))) {
+ // treat "//cmd" and "/ /cmd" as message (-> "/cmd")
+ input->remove(0, 1);
+ if (removed)
+ *removed = 1;
+ return false;
+ } else if (input->startsWith(trigger)) {
+ input->remove(0, trigger.length());
+ if (removed)
+ *removed = trigger.length();
+ return true;
+ }
+ }
+ return false;
+}
+
+bool IrcCommandParserPrivate::processMessage(QString* input, int* removed) const
+{
+ if (input->isEmpty())
+ return false;
+ if (triggers.isEmpty())
+ return tolerant;
+ if (processCommand(input, removed))
+ return false;
+ return tolerant;
+}
+
+bool IrcCommandParserPrivate::onChannel() const
+{
+ return channels.contains(target, Qt::CaseInsensitive);
+}
+#endif // IRC_DOXYGEN
+
+/*!
+ Constructs a command parser with \a parent.
+ */
+IrcCommandParser::IrcCommandParser(QObject* parent) : QObject(parent), d_ptr(new IrcCommandParserPrivate)
+{
+}
+
+/*!
+ Destructs the command parser.
+ */
+IrcCommandParser::~IrcCommandParser()
+{
+}
+
+/*!
+ This property holds the known commands.
+
+ The commands are uppercased and in alphabetical order.
+
+ \par Access function:
+ \li QStringList <b>commands</b>() const
+
+ \par Notifier signal:
+ \li void <b>commandsChanged</b>(const QStringList& commands)
+
+ \sa addCommand(), removeCommand()
+ */
+QStringList IrcCommandParser::commands() const
+{
+ Q_D(const IrcCommandParser);
+ return d->commands.uniqueKeys();
+}
+
+/*!
+ Returns syntax for the given \a command in given \a details level.
+ */
+QString IrcCommandParser::syntax(const QString& command, Details details) const
+{
+ Q_D(const IrcCommandParser);
+ IrcCommandInfo info = d->find(command.toUpper()).value(0);
+ if (!info.command.isEmpty()) {
+ QString str = info.fullSyntax();
+ if (details != Full) {
+ if (details & NoTarget)
+ str.remove(QRegExp("\\[[^\\]]+\\]"));
+ if (details & NoPrefix)
+ str.remove("#");
+ if (details & NoEllipsis)
+ str.remove("...");
+ if (details & NoParentheses)
+ str.remove("(").remove(")");
+ if (details & NoBrackets)
+ str.remove("[").remove("]");
+ if (details & NoAngles)
+ str.remove("<").remove(">");
+ }
+ return str.simplified();
+ }
+ return QString();
+}
+
+/*!
+ Adds a command with \a type and \a syntax.
+ */
+void IrcCommandParser::addCommand(IrcCommand::Type type, const QString& syntax)
+{
+ Q_D(IrcCommandParser);
+ IrcCommandInfo cmd = d->parseSyntax(type, syntax);
+ if (!cmd.command.isEmpty()) {
+ const bool contains = d->commands.contains(cmd.command);
+ d->commands.insert(cmd.command, cmd);
+ if (!contains)
+ emit commandsChanged(commands());
+ }
+}
+
+/*!
+ Removes the command with \a type and \a syntax.
+ */
+void IrcCommandParser::removeCommand(IrcCommand::Type type, const QString& syntax)
+{
+ Q_D(IrcCommandParser);
+ bool changed = false;
+ QMutableMapIterator<QString, IrcCommandInfo> it(d->commands);
+ while (it.hasNext()) {
+ IrcCommandInfo cmd = it.next().value();
+ if (cmd.type == type && (syntax.isEmpty() || !syntax.compare(cmd.fullSyntax(), Qt::CaseInsensitive))) {
+ it.remove();
+ if (!d->commands.contains(cmd.command))
+ changed = true;
+ }
+ }
+ if (changed)
+ emit commandsChanged(commands());
+}
+
+/*!
+ This property holds the available channels.
+
+ \par Access functions:
+ \li QStringList <b>channels</b>() const
+ \li void <b>setChannels</b>(const QStringList& channels) [slot]
+
+ \par Notifier signal:
+ \li void <b>channelsChanged</b>(const QStringList& channels)
+
+ \sa IrcBufferModel::channels()
+ */
+QStringList IrcCommandParser::channels() const
+{
+ Q_D(const IrcCommandParser);
+ return d->channels;
+}
+
+void IrcCommandParser::setChannels(const QStringList& channels)
+{
+ Q_D(IrcCommandParser);
+ if (d->channels != channels) {
+ d->channels = channels;
+ emit channelsChanged(channels);
+ }
+}
+
+/*!
+ This property holds the current target.
+
+ \par Access functions:
+ \li QString <b>target</b>() const
+ \li void <b>setTarget</b>(const QString& target) [slot]
+
+ \par Notifier signal:
+ \li void <b>targetChanged</b>(const QString& target)
+ */
+QString IrcCommandParser::target() const
+{
+ Q_D(const IrcCommandParser);
+ return d->target;
+}
+
+void IrcCommandParser::setTarget(const QString& target)
+{
+ Q_D(IrcCommandParser);
+ if (d->target != target) {
+ d->target = target;
+ emit targetChanged(target);
+ }
+}
+
+/*!
+ This property holds the command triggers.
+
+ \par Access functions:
+ \li QStringList <b>triggers</b>() const
+ \li void <b>setTriggers</b>(const QStringList& triggers) [slot]
+
+ \par Notifier signal:
+ \li void <b>triggersChanged</b>(const QStringList& triggers)
+ */
+QStringList IrcCommandParser::triggers() const
+{
+ Q_D(const IrcCommandParser);
+ return d->triggers;
+}
+
+void IrcCommandParser::setTriggers(const QStringList& triggers)
+{
+ Q_D(IrcCommandParser);
+ if (d->triggers != triggers) {
+ d->triggers = triggers;
+ emit triggersChanged(triggers);
+ }
+}
+
+/*!
+ \property bool IrcCommandParser::tolerant
+
+ This property holds whether the parser is tolerant.
+
+ A tolerant parser creates message commands out of input that does not
+ start with a command trigger, and raw server commands when the input
+ starts with a command trigger but the command is unrecognized. Known
+ commands with invalid arguments are still considered invalid.
+
+ The default value is \c false.
+
+ \par Access functions:
+ \li bool <b>isTolerant</b>() const
+ \li void <b>setTolerant</b>(bool tolerant)
+
+ \par Notifier signal:
+ \li void <b>tolerancyChanged</b>(bool tolerant)
+
+ \sa IrcCommand::Quote
+ */
+bool IrcCommandParser::isTolerant() const
+{
+ Q_D(const IrcCommandParser);
+ return d->tolerant;
+}
+
+void IrcCommandParser::setTolerant(bool tolerant)
+{
+ Q_D(IrcCommandParser);
+ if (d->tolerant != tolerant) {
+ d->tolerant = tolerant;
+ emit tolerancyChanged(tolerant);
+ }
+}
+
+/*!
+ Parses and returns the command for \a input, or \c 0 if the input is not valid.
+ */
+IrcCommand* IrcCommandParser::parse(const QString& input) const
+{
+ Q_D(const IrcCommandParser);
+ QString message = input;
+ if (d->processMessage(&message)) {
+ return IrcCommand::createMessage(d->target, message.trimmed());
+ } else if (!message.isEmpty()) {
+ IrcTokenizer tokenizer(message);
+ const QString command = tokenizer.at(0).text().toUpper();
+ QString params = tokenizer.mid(1).toString();
+ const QList<IrcCommandInfo> commands = d->find(command);
+ if (!commands.isEmpty()) {
+ foreach (const IrcCommandInfo& c, commands) {
+ IrcCommand* cmd = d->parseCommand(c, params);
+ if (cmd)
+ return cmd;
+ }
+ } else if (d->tolerant) {
+ IrcCommandInfo custom = d->parseSyntax(IrcCommand::Quote, QString(QLatin1String("%1 (<parameters...>)")).arg(command));
+ params.prepend(custom.command + QLatin1Char(' '));
+ return d->parseCommand(custom, params);
+ }
+ }
+ return 0;
+}
+
+/*!
+ Clears the list of commands.
+
+ \sa reset()
+ */
+void IrcCommandParser::clear()
+{
+ Q_D(IrcCommandParser);
+ if (!d->commands.isEmpty()) {
+ d->commands.clear();
+ emit commandsChanged(QStringList());
+ }
+}
+
+/*!
+ Resets the channels and the current target.
+
+ \sa clear()
+ */
+void IrcCommandParser::reset()
+{
+ setChannels(QStringList());
+ setTarget(QString());
+}
+
+#include "moc_irccommandparser.cpp"
+
+IRC_END_NAMESPACE
diff --git a/libcommuni/src/util/irccompleter.cpp b/libcommuni/src/util/irccompleter.cpp
new file mode 100644
index 0000000..430167a
--- /dev/null
+++ b/libcommuni/src/util/irccompleter.cpp
@@ -0,0 +1,405 @@
+/*
+ Copyright (C) 2008-2014 The Communi Project
+
+ You may use this file under the terms of BSD license as follows:
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ * Neither the name of the copyright holder nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR
+ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#include "irccompleter.h"
+#include "irccommandparser.h"
+#include "irccommandparser_p.h"
+#include "ircbuffermodel.h"
+#include "ircusermodel.h"
+#include "ircnetwork.h"
+#include "ircchannel.h"
+#include "irctoken_p.h"
+#include "ircuser.h"
+
+#include <QTextBoundaryFinder>
+#include <QPointer>
+#include <QList>
+#include <QPair>
+
+IRC_BEGIN_NAMESPACE
+
+/*!
+ \file irccompleter.h
+ \brief \#include &lt;IrcCompleter&gt;
+ */
+
+/*!
+ \since 3.1
+ \class IrcCompleter irccompleter.h <IrcCompleter>
+ \ingroup util
+ \brief Provides command and name completion.
+
+ IrcCompleter provides command and name completion for a text input field. The completer
+ is made context aware by assigning a command \ref IrcCompleter::parser "parser" and a
+ \ref buffer that is currently active in the GUI. The parser is used for completing
+ commands, and the buffer is used for completing buffer and user names.
+
+ In order to perform a completion, call complete() with the current text input field
+ content and the cursor position. If a suitable completion is found, the completed()
+ signal is emitted with a suggestion for a new content and cursor position for the
+ text input field.
+
+ \code
+ TextField {
+ id: textField
+
+ Keys.onTabPressed: completer.complete(text, cursorPosition)
+
+ IrcCompleter {
+ id: completer
+
+ buffer: ...
+ parser: ...
+
+ onCompleted: {
+ textField.text = text
+ textField.cursorPosition = cursor
+ }
+ }
+ }
+ \endcode
+
+ \sa IrcCommandParser, IrcBuffer
+ */
+
+/*!
+ \fn void IrcCompleter::completed(const QString& text, int cursor)
+
+ This signal is emitted when a suitable completion with \a text and \a cursor position is found.
+ */
+
+#ifndef IRC_DOXYGEN
+
+static bool isPrefixed(const QString& text, int pos, const QStringList& prefixes, int* len)
+{
+ foreach (const QString& prefix, prefixes) {
+ const int ll = prefix.length();
+ if (text.mid(pos, ll) == prefix) {
+ if (len)
+ *len = 0;
+ return true;
+ } else if (text.mid(pos - ll, ll) == prefix) {
+ if (len)
+ *len = ll;
+ return true;
+ }
+ }
+ return false;
+}
+
+struct IrcCompletion
+{
+ IrcCompletion() : text(), cursor(-1) { }
+ IrcCompletion(const QString& txt, int pos) : text(txt), cursor(pos) { }
+ bool isValid() const { return !text.isNull() && cursor != -1; }
+ bool operator ==(const IrcCompletion& other) const { return text == other.text && cursor == other.cursor; }
+ bool operator !=(const IrcCompletion& other) const { return text != other.text || cursor != other.cursor; }
+ QString text;
+ int cursor;
+};
+
+class IrcCompleterPrivate
+{
+ Q_DECLARE_PUBLIC(IrcCompleter)
+
+public:
+ IrcCompleterPrivate();
+
+ void completeNext(IrcCompleter::Direction direction);
+ QList<IrcCompletion> completeCommands(const QString& text, int pos) const;
+ QList<IrcCompletion> completeWords(const QString& text, int pos) const;
+
+ IrcCompleter* q_ptr;
+
+ int index;
+ int cursor;
+ QString text;
+ QList<IrcCompletion> completions;
+
+ QString suffix;
+ QPointer<IrcBuffer> buffer;
+ QPointer<IrcCommandParser> parser;
+};
+
+IrcCompleterPrivate::IrcCompleterPrivate() : q_ptr(0), index(-1), cursor(-1), suffix(":"), buffer(0), parser(0)
+{
+}
+
+void IrcCompleterPrivate::completeNext(IrcCompleter::Direction direction)
+{
+ Q_Q(IrcCompleter);
+ Q_ASSERT(!completions.isEmpty());
+ if (direction == IrcCompleter::Forward) {
+ index = (index + 1) % completions.length();
+ } else {
+ if (--index < 0)
+ index = completions.length() - 1;
+ }
+ if (index >= 0 && index < completions.length()) {
+ const IrcCompletion completion = completions.at(index);
+ text = completion.text;
+ cursor = completion.cursor;
+ emit q->completed(text, cursor);
+ }
+}
+
+static IrcCompletion completeCommand(const QString& text, const QString& command)
+{
+ IrcTokenizer tokenizer(text);
+ tokenizer.replace(0, command);
+ QString completion = tokenizer.toString();
+ int next = command.length();
+ if (next >= completion.length() || completion.at(next) != QLatin1Char(' '))
+ completion.insert(next, QLatin1Char(' '));
+ return IrcCompletion(completion, ++next);
+}
+
+QList<IrcCompletion> IrcCompleterPrivate::completeCommands(const QString& text, int pos) const
+{
+ if (!parser)
+ return QList<IrcCompletion>();
+
+ QList<IrcCompletion> completions;
+
+ int removed = 0;
+ QString input = text;
+ IrcCommandParserPrivate* pp = IrcCommandParserPrivate::get(parser);
+ if (pp->processCommand(&input, &removed)) {
+ const QString command = input.split(QLatin1Char(' '), QString::SkipEmptyParts).value(0).toUpper();
+ if (!command.isEmpty()) {
+ foreach (const IrcCommandInfo& cmd, pp->commands) {
+ if (cmd.command == command)
+ return QList<IrcCompletion>() << completeCommand(text, text.left(removed) + cmd.command);
+ if (cmd.command.startsWith(command))
+ completions += completeCommand(text, text.left(removed) + cmd.command);
+ }
+ }
+ // TODO: context sensitive command parameter completion
+ Q_UNUSED(pos);
+ }
+ return completions;
+}
+
+static IrcCompletion completeWord(const QString& text, int from, int len, const QString& word)
+{
+ QString completion = QString(text).replace(from, len, word);
+ int next = from + word.length();
+ if (next >= completion.length() || completion.at(next) != QLatin1Char(' '))
+ completion.insert(next, QLatin1Char(' '));
+ return IrcCompletion(completion, ++next);
+}
+
+QList<IrcCompletion> IrcCompleterPrivate::completeWords(const QString& text, int pos) const
+{
+ if (!buffer || !buffer->network())
+ return QList<IrcCompletion>();
+
+ QList<IrcCompletion> completions;
+
+ const IrcToken token = IrcTokenizer(text).find(pos);
+ const QPair<int, int> bounds = qMakePair(token.position(), token.length());
+ if (bounds.first != -1 && bounds.second != -1) {
+ const QString word = text.mid(bounds.first, bounds.second);
+
+ int pfx = 0;
+ QString prefix;
+ bool isChannel = isPrefixed(text, bounds.first, buffer->network()->channelTypes(), &pfx);
+ if (isChannel && pfx > 0)
+ prefix = text.mid(bounds.first - pfx, pfx);
+
+ if (!isChannel) {
+ IrcUserModel userModel;
+ userModel.setSortMethod(Irc::SortByActivity);
+ userModel.setChannel(qobject_cast<IrcChannel*>(buffer));
+ foreach (IrcUser* user, userModel.users()) {
+ if (user->name().startsWith(word, Qt::CaseInsensitive)) {
+ QString name = user->name();
+ if (token.index() == 0)
+ name += suffix;
+ IrcCompletion completion = completeWord(text, bounds.first, bounds.second, name);
+ if (completion.isValid() && !completions.contains(completion))
+ completions += completion;
+ }
+ }
+ }
+
+ QList<IrcBuffer*> buffers = buffer->model()->buffers();
+ buffers.move(buffers.indexOf(buffer), 0); // promote the current buffer
+ foreach (IrcBuffer* buffer, buffers) {
+ QString title = buffer->title();
+ if (!isChannel && token.index() == 0)
+ title += suffix;
+ IrcCompletion completion;
+ if (title.startsWith(word, Qt::CaseInsensitive))
+ completion = completeWord(text, bounds.first, bounds.second, title);
+ else if (isChannel && !prefix.isEmpty() && title.startsWith(prefix + word, Qt::CaseInsensitive))
+ completion = completeWord(text, bounds.first - prefix.length(), bounds.second + prefix.length(), title);
+ if (completion.isValid() && !completions.contains(completion))
+ completions += completion;
+ }
+ }
+ return completions;
+}
+#endif // IRC_DOXYGEN
+
+/*!
+ Constructs a completer with \a parent.
+ */
+IrcCompleter::IrcCompleter(QObject* parent) : QObject(parent), d_ptr(new IrcCompleterPrivate)
+{
+ Q_D(IrcCompleter);
+ d->q_ptr = this;
+}
+
+/*!
+ Destructs the completer.
+ */
+IrcCompleter::~IrcCompleter()
+{
+}
+
+/*!
+ This property holds the completion suffix.
+
+ The suffix is appended to the end of a completed nick name, but
+ only when the nick name is in the beginning of completed text.
+
+ The default value is \c ":".
+
+ \par Access functions:
+ \li QString <b>suffix</b>() const
+ \li void <b>setSuffix</b>(const QString& suffix) [slot]
+
+ \par Notifier signal:
+ \li void <b>suffixChanged</b>(const QString& suffix)
+ */
+QString IrcCompleter::suffix() const
+{
+ Q_D(const IrcCompleter);
+ return d->suffix;
+}
+
+void IrcCompleter::setSuffix(const QString& suffix)
+{
+ Q_D(IrcCompleter);
+ if (d->suffix != suffix) {
+ d->suffix = suffix;
+ emit suffixChanged(suffix);
+ }
+}
+
+/*!
+ This property holds the buffer used for name completion.
+
+ \par Access functions:
+ \li \ref IrcBuffer* <b>buffer</b>() const
+ \li void <b>setBuffer</b>(\ref IrcBuffer* buffer) [slot]
+
+ \par Notifier signal:
+ \li void <b>bufferChanged</b>(\ref IrcBuffer* buffer)
+ */
+IrcBuffer* IrcCompleter::buffer() const
+{
+ Q_D(const IrcCompleter);
+ return d->buffer;
+}
+
+void IrcCompleter::setBuffer(IrcBuffer* buffer)
+{
+ Q_D(IrcCompleter);
+ if (d->buffer != buffer) {
+ d->buffer = buffer;
+ emit bufferChanged(buffer);
+ }
+}
+
+/*!
+ This property holds the parser used for command completion.
+
+ \par Access functions:
+ \li \ref IrcCommandParser* <b>parser</b>() const
+ \li void <b>setParser</b>(\ref IrcCommandParser* parser) [slot]
+
+ \par Notifier signal:
+ \li void <b>parserChanged</b>(\ref IrcCommandParser* parser)
+ */
+IrcCommandParser* IrcCompleter::parser() const
+{
+ Q_D(const IrcCompleter);
+ return d->parser;
+}
+
+void IrcCompleter::setParser(IrcCommandParser* parser)
+{
+ Q_D(IrcCompleter);
+ if (d->parser != parser) {
+ d->parser = parser;
+ emit parserChanged(parser);
+ }
+}
+
+/*!
+ Completes \a text at \a cursor position, iterating multiple
+ matches to the specified \a direction, and emits completed()
+ if a suitable completion is found.
+ */
+void IrcCompleter::complete(const QString& text, int cursor, Direction direction)
+{
+ Q_D(IrcCompleter);
+ if (!d->completions.isEmpty() && d->cursor == cursor && d->text == text) {
+ d->completeNext(direction);
+ return;
+ }
+
+ QList<IrcCompletion> completions = d->completeCommands(text, cursor);
+ if (completions.isEmpty() || IrcTokenizer(text).find(cursor).index() > 0)
+ completions = d->completeWords(text, cursor);
+
+ if (d->completions != completions) {
+ d->index = -1;
+ d->completions = completions;
+ }
+ if (!d->completions.isEmpty())
+ d->completeNext(direction);
+}
+
+/*!
+ Resets the completer state.
+ */
+void IrcCompleter::reset()
+{
+ Q_D(IrcCompleter);
+ d->index = -1;
+ d->cursor = -1;
+ d->text.clear();
+ d->completions.clear();
+}
+
+#include "moc_irccompleter.cpp"
+
+IRC_END_NAMESPACE
diff --git a/libcommuni/src/util/irclagtimer.cpp b/libcommuni/src/util/irclagtimer.cpp
new file mode 100644
index 0000000..b1612f7
--- /dev/null
+++ b/libcommuni/src/util/irclagtimer.cpp
@@ -0,0 +1,243 @@
+/*
+ Copyright (C) 2008-2014 The Communi Project
+
+ You may use this file under the terms of BSD license as follows:
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ * Neither the name of the copyright holder nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR
+ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#include "irclagtimer.h"
+#include "irclagtimer_p.h"
+#include "ircconnection.h"
+#include "ircmessage.h"
+#include "irccommand.h"
+#include <QDateTime>
+
+IRC_BEGIN_NAMESPACE
+
+static const int DEFAULT_INTERVAL = 60;
+
+/*!
+ \file irclagtimer.h
+ \brief \#include &lt;IrcLagTimer&gt;
+ */
+
+/*!
+ \class IrcLagTimer irclagtimer.h <IrcLagTimer>
+ \ingroup util
+ \brief Provides a timer for measuring lag.
+
+ \note IrcLagTimer relies on functionality introduced in Qt 4.7.0, and is
+ therefore not functional when built against earlier versions of Qt.
+ */
+
+/*!
+ \fn void IrcLagTimer::lagChanged(qint64 lag)
+
+ This signal is emitted when the \a lag has changed.
+ */
+
+#ifndef IRC_DOXYGEN
+IrcLagTimerPrivate::IrcLagTimerPrivate() : q_ptr(0), connection(0), interval(DEFAULT_INTERVAL), lag(-1)
+{
+}
+
+bool IrcLagTimerPrivate::messageFilter(IrcMessage* msg)
+{
+ if (msg->type() == IrcMessage::Pong)
+ return processPongReply(static_cast<IrcPongMessage*>(msg));
+ return false;
+}
+
+bool IrcLagTimerPrivate::processPongReply(IrcPongMessage* msg)
+{
+#if QT_VERSION >= 0x040700
+ // TODO: configurable format?
+ if (msg->argument().startsWith("communi/")) {
+ bool ok = false;
+ qint64 timestamp = msg->argument().mid(8).toLongLong(&ok);
+ if (ok) {
+ updateLag(QDateTime::currentMSecsSinceEpoch() - timestamp);
+ return true;
+ }
+ }
+#endif // QT_VERSION
+ return false;
+}
+
+void IrcLagTimerPrivate::_irc_connected()
+{
+#if QT_VERSION >= 0x040700
+ if (interval > 0)
+ timer.start();
+#endif // QT_VERSION
+}
+
+void IrcLagTimerPrivate::_irc_pingServer()
+{
+#if QT_VERSION >= 0x040700
+ // TODO: configurable format?
+ QString argument = QString("communi/%1").arg(QDateTime::currentMSecsSinceEpoch());
+ IrcCommand* cmd = IrcCommand::createPing(argument);
+ connection->sendCommand(cmd);
+#endif // QT_VERSION
+}
+
+void IrcLagTimerPrivate::_irc_disconnected()
+{
+#if QT_VERSION >= 0x040700
+ updateLag(-1);
+ if (timer.isActive())
+ timer.stop();
+#endif // QT_VERSION
+}
+
+void IrcLagTimerPrivate::updateTimer()
+{
+#if QT_VERSION >= 0x040700
+ if (connection && interval > 0) {
+ timer.setInterval(interval * 1000);
+ if (!timer.isActive() && connection->isConnected())
+ timer.start();
+ } else {
+ if (timer.isActive())
+ timer.stop();
+ updateLag(-1);
+ }
+#endif // QT_VERSION
+}
+
+void IrcLagTimerPrivate::updateLag(qint64 value)
+{
+ Q_Q(IrcLagTimer);
+ if (lag != value) {
+ lag = qMax(-1ll, value);
+ emit q->lagChanged(lag);
+ }
+}
+#endif // IRC_DOXYGEN
+
+/*!
+ Constructs a new lag timer with \a parent.
+
+ \note If \a parent is an instance of IrcConnection, it will be
+ automatically assigned to \ref IrcLagTimer::connection "connection".
+ */
+IrcLagTimer::IrcLagTimer(QObject* parent) : QObject(parent), d_ptr(new IrcLagTimerPrivate)
+{
+ Q_D(IrcLagTimer);
+ d->q_ptr = this;
+ connect(&d->timer, SIGNAL(timeout()), this, SLOT(_irc_pingServer()));
+ setConnection(qobject_cast<IrcConnection*>(parent));
+}
+
+/*!
+ Destructs the lag timer.
+ */
+IrcLagTimer::~IrcLagTimer()
+{
+}
+
+/*!
+ This property holds the associated connection.
+
+ \par Access functions:
+ \li IrcConnection* <b>connection</b>() const
+ \li void <b>setConnection</b>(IrcConnection* connection)
+ */
+IrcConnection* IrcLagTimer::connection() const
+{
+ Q_D(const IrcLagTimer);
+ return d->connection;
+}
+
+void IrcLagTimer::setConnection(IrcConnection* connection)
+{
+ Q_D(IrcLagTimer);
+ if (d->connection != connection) {
+ if (d->connection) {
+ d->connection->removeMessageFilter(d);
+ disconnect(d->connection, SIGNAL(connected()), this, SLOT(_irc_connected()));
+ disconnect(d->connection, SIGNAL(disconnected()), this, SLOT(_irc_disconnected()));
+ }
+ d->connection = connection;
+ if (connection) {
+ connection->installMessageFilter(d);
+ connect(connection, SIGNAL(connected()), this, SLOT(_irc_connected()));
+ connect(connection, SIGNAL(disconnected()), this, SLOT(_irc_disconnected()));
+ }
+ d->updateLag(-1);
+ d->updateTimer();
+ }
+}
+
+/*!
+ This property holds the current lag in milliseconds.
+
+ The value is \c -1 when
+ \li the connection is not connected,
+ \li the lag has not yet been measured,
+ \li the lag timer is disabled (interval <= 0s), or
+ \li the Qt version is too old (4.7.0 or later is required).
+
+ \par Access function:
+ \li qint64 <b>lag</b>() const
+
+ \par Notifier signal:
+ \li void <b>lagChanged</b>(qint64 lag)
+ */
+qint64 IrcLagTimer::lag() const
+{
+ Q_D(const IrcLagTimer);
+ return d->lag;
+}
+
+/*!
+ This property holds the lag measurement interval in seconds.
+
+ The default value is \c 60 seconds. A value equal to or
+ less than \c 0 seconds disables the lag measurement.
+
+ \par Access functions:
+ \li int <b>interval</b>() const
+ \li void <b>setInterval</b>(int seconds)
+ */
+int IrcLagTimer::interval() const
+{
+ Q_D(const IrcLagTimer);
+ return d->interval;
+}
+
+void IrcLagTimer::setInterval(int seconds)
+{
+ Q_D(IrcLagTimer);
+ if (d->interval != seconds) {
+ d->interval = seconds;
+ d->updateTimer();
+ }
+}
+
+#include "moc_irclagtimer.cpp"
+#include "moc_irclagtimer_p.cpp"
+
+IRC_END_NAMESPACE
diff --git a/libcommuni/src/util/ircpalette.cpp b/libcommuni/src/util/ircpalette.cpp
new file mode 100644
index 0000000..fe31035
--- /dev/null
+++ b/libcommuni/src/util/ircpalette.cpp
@@ -0,0 +1,527 @@
+/*
+ Copyright (C) 2008-2014 The Communi Project
+
+ You may use this file under the terms of BSD license as follows:
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ * Neither the name of the copyright holder nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR
+ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#include "ircpalette.h"
+#include "irc.h"
+
+IRC_BEGIN_NAMESPACE
+
+/*!
+ \file ircpalette.h
+ \brief \#include &lt;IrcPalette&gt;
+ */
+
+/*!
+ \class IrcPalette ircpalette.h <IrcPalette>
+ \ingroup util
+ \brief Specifies a palette of IRC colors.
+
+ IrcPalette is used to specify the desired IRC color palette when
+ converting IRC-style formatted messages to HTML using IrcTextFormat.
+
+ \code
+ IrcTextFormat format;
+ IrcPalette* palette = format.palette();
+ palette->setColorName(Irc::Red, "#ff3333");
+ palette->setColorName(Irc::Green, "#33ff33");
+ palette->setColorName(Irc::Blue, "#3333ff");
+ // ...
+
+ QString html = format.toHtml(message);
+ \endcode
+
+ \sa Irc::Color, <a href="http://www.mirc.com/colors.html">mIRC colors</a>, <a href="http://www.w3.org/TR/SVG/types.html#ColorKeywords">SVG color keyword names</a>
+ */
+
+class IrcPalettePrivate
+{
+public:
+ QMap<int, QString> colors;
+};
+
+static QMap<int, QString>& irc_default_colors()
+{
+ static QMap<int, QString> x;
+ if (x.isEmpty()) {
+ x.insert(Irc::White, QLatin1String("white"));
+ x.insert(Irc::Black, QLatin1String("black"));
+ x.insert(Irc::Blue, QLatin1String("blue"));
+ x.insert(Irc::Green, QLatin1String("green"));
+ x.insert(Irc::Red, QLatin1String("red"));
+ x.insert(Irc::Brown, QLatin1String("brown"));
+ x.insert(Irc::Purple, QLatin1String("purple"));
+ x.insert(Irc::Orange, QLatin1String("orange"));
+ x.insert(Irc::Yellow, QLatin1String("yellow"));
+ x.insert(Irc::LightGreen, QLatin1String("lightgreen"));
+ x.insert(Irc::Cyan, QLatin1String("cyan"));
+ x.insert(Irc::LightCyan, QLatin1String("lightcyan"));
+ x.insert(Irc::LightBlue, QLatin1String("lightblue"));
+ x.insert(Irc::Pink, QLatin1String("pink"));
+ x.insert(Irc::Gray, QLatin1String("gray"));
+ x.insert(Irc::LightGray, QLatin1String("lightgray"));
+ }
+ return x;
+}
+
+/*!
+ \internal
+ Constructs a new palette with \a parent.
+ */
+IrcPalette::IrcPalette(QObject* parent) : QObject(parent), d_ptr(new IrcPalettePrivate)
+{
+ Q_D(IrcPalette);
+ d->colors = irc_default_colors();
+}
+
+/*!
+ \internal
+ Destructs the palette.
+ */
+IrcPalette::~IrcPalette()
+{
+}
+
+/*!
+ This property holds the white color name.
+
+ The default value is \c "white".
+
+ \par Access functions:
+ \li QString <b>white</b>() const
+ \li void <b>setWhite</b>(const QString& color)
+
+ \sa Irc::White
+ */
+QString IrcPalette::white() const
+{
+ Q_D(const IrcPalette);
+ return d->colors.value(Irc::White);
+}
+
+void IrcPalette::setWhite(const QString& color)
+{
+ Q_D(IrcPalette);
+ d->colors.insert(Irc::White, color);
+}
+
+/*!
+ This property holds the black color name.
+
+ The default value is \c "black".
+
+ \par Access functions:
+ \li QString <b>black</b>() const
+ \li void <b>setBlack</b>(const QString& color)
+
+ \sa Irc::Black
+ */
+QString IrcPalette::black() const
+{
+ Q_D(const IrcPalette);
+ return d->colors.value(Irc::Black);
+}
+
+void IrcPalette::setBlack(const QString& color)
+{
+ Q_D(IrcPalette);
+ d->colors.insert(Irc::Black, color);
+}
+
+/*!
+ This property holds the blue color name.
+
+ The default value is \c "blue".
+
+ \par Access functions:
+ \li QString <b>blue</b>() const
+ \li void <b>setBlue</b>(const QString& color)
+
+ \sa Irc::Blue
+ */
+QString IrcPalette::blue() const
+{
+ Q_D(const IrcPalette);
+ return d->colors.value(Irc::Blue);
+}
+
+void IrcPalette::setBlue(const QString& color)
+{
+ Q_D(IrcPalette);
+ d->colors.insert(Irc::Blue, color);
+}
+
+/*!
+ This property holds the green color name.
+
+ The default value is \c "green".
+
+ \par Access functions:
+ \li QString <b>green</b>() const
+ \li void <b>setGreen</b>(const QString& color)
+
+ \sa Irc::Green
+ */
+QString IrcPalette::green() const
+{
+ Q_D(const IrcPalette);
+ return d->colors.value(Irc::Green);
+}
+
+void IrcPalette::setGreen(const QString& color)
+{
+ Q_D(IrcPalette);
+ d->colors.insert(Irc::Green, color);
+}
+
+/*!
+ This property holds the red color name.
+
+ The default value is \c "red".
+
+ \par Access functions:
+ \li QString <b>red</b>() const
+ \li void <b>setRed</b>(const QString& color)
+
+ \sa Irc::Red
+ */
+QString IrcPalette::red() const
+{
+ Q_D(const IrcPalette);
+ return d->colors.value(Irc::Red);
+}
+
+void IrcPalette::setRed(const QString& color)
+{
+ Q_D(IrcPalette);
+ d->colors.insert(Irc::Red, color);
+}
+
+/*!
+ This property holds the brown color name.
+
+ The default value is \c "brown".
+
+ \par Access functions:
+ \li QString <b>brown</b>() const
+ \li void <b>setBrown</b>(const QString& color)
+
+ \sa Irc::Brown
+ */
+QString IrcPalette::brown() const
+{
+ Q_D(const IrcPalette);
+ return d->colors.value(Irc::Brown);
+}
+
+void IrcPalette::setBrown(const QString& color)
+{
+ Q_D(IrcPalette);
+ d->colors.insert(Irc::Brown, color);
+}
+
+/*!
+ This property holds the purple color name.
+
+ The default value is \c "purple".
+
+ \par Access functions:
+ \li QString <b>purple</b>() const
+ \li void <b>setPurple</b>(const QString& color)
+
+ \sa Irc::Purple
+ */
+QString IrcPalette::purple() const
+{
+ Q_D(const IrcPalette);
+ return d->colors.value(Irc::Purple);
+}
+
+void IrcPalette::setPurple(const QString& color)
+{
+ Q_D(IrcPalette);
+ d->colors.insert(Irc::Purple, color);
+}
+
+/*!
+ This property holds the orange color name.
+
+ The default value is \c "orange".
+
+ \par Access functions:
+ \li QString <b>orange</b>() const
+ \li void <b>setOrange</b>(const QString& color)
+
+ \sa Irc::Orange
+ */
+QString IrcPalette::orange() const
+{
+ Q_D(const IrcPalette);
+ return d->colors.value(Irc::Orange);
+}
+
+void IrcPalette::setOrange(const QString& color)
+{
+ Q_D(IrcPalette);
+ d->colors.insert(Irc::Orange, color);
+}
+
+/*!
+ This property holds the yellow color name.
+
+ The default value is \c "yellow".
+
+ \par Access functions:
+ \li QString <b>yellow</b>() const
+ \li void <b>setYellow</b>(const QString& color)
+
+ \sa Irc::Yellow
+ */
+QString IrcPalette::yellow() const
+{
+ Q_D(const IrcPalette);
+ return d->colors.value(Irc::Yellow);
+}
+
+void IrcPalette::setYellow(const QString& color)
+{
+ Q_D(IrcPalette);
+ d->colors.insert(Irc::Yellow, color);
+}
+
+/*!
+ This property holds the light green color name.
+
+ The default value is \c "lightgreen".
+
+ \par Access functions:
+ \li QString <b>lightGreen</b>() const
+ \li void <b>setLightGreen</b>(const QString& color)
+
+ \sa Irc::LightGreen
+ */
+QString IrcPalette::lightGreen() const
+{
+ Q_D(const IrcPalette);
+ return d->colors.value(Irc::LightGreen);
+}
+
+void IrcPalette::setLightGreen(const QString& color)
+{
+ Q_D(IrcPalette);
+ d->colors.insert(Irc::LightGreen, color);
+}
+
+/*!
+ This property holds the cyan color name.
+
+ The default value is \c "cyan".
+
+ \par Access functions:
+ \li QString <b>cyan</b>() const
+ \li void <b>setCyan</b>(const QString& color)
+
+ \sa Irc::Cyan
+ */
+QString IrcPalette::cyan() const
+{
+ Q_D(const IrcPalette);
+ return d->colors.value(Irc::Cyan);
+}
+
+void IrcPalette::setCyan(const QString& color)
+{
+ Q_D(IrcPalette);
+ d->colors.insert(Irc::Cyan, color);
+}
+
+/*!
+ This property holds the light cyan color name.
+
+ The default value is \c "lightcyan".
+
+ \par Access functions:
+ \li QString <b>lightCyan</b>() const
+ \li void <b>setLightCyan</b>(const QString& color)
+
+ \sa Irc::LightCyan
+ */
+QString IrcPalette::lightCyan() const
+{
+ Q_D(const IrcPalette);
+ return d->colors.value(Irc::LightCyan);
+}
+
+void IrcPalette::setLightCyan(const QString& color)
+{
+ Q_D(IrcPalette);
+ d->colors.insert(Irc::LightCyan, color);
+}
+
+/*!
+ This property holds the light blue color name.
+
+ The default value is \c "lightblue".
+
+ \par Access functions:
+ \li QString <b>lightBlue</b>() const
+ \li void <b>setLightBlue</b>(const QString& color)
+
+ \sa Irc::LightBlue
+ */
+QString IrcPalette::lightBlue() const
+{
+ Q_D(const IrcPalette);
+ return d->colors.value(Irc::LightBlue);
+}
+
+void IrcPalette::setLightBlue(const QString& color)
+{
+ Q_D(IrcPalette);
+ d->colors.insert(Irc::LightBlue, color);
+}
+
+/*!
+ This property holds the pink color name.
+
+ The default value is \c "pink".
+
+ \par Access functions:
+ \li QString <b>pink</b>() const
+ \li void <b>setPink</b>(const QString& color)
+
+ \sa Irc::Pink
+ */
+QString IrcPalette::pink() const
+{
+ Q_D(const IrcPalette);
+ return d->colors.value(Irc::Pink);
+}
+
+void IrcPalette::setPink(const QString& color)
+{
+ Q_D(IrcPalette);
+ d->colors.insert(Irc::Pink, color);
+}
+
+/*!
+ This property holds the gray color name.
+
+ The default value is \c "gray".
+
+ \par Access functions:
+ \li QString <b>gray</b>() const
+ \li void <b>setGray</b>(const QString& color)
+
+ \sa Irc::Gray
+ */
+QString IrcPalette::gray() const
+{
+ Q_D(const IrcPalette);
+ return d->colors.value(Irc::Gray);
+}
+
+void IrcPalette::setGray(const QString& color)
+{
+ Q_D(IrcPalette);
+ d->colors.insert(Irc::Gray, color);
+}
+
+/*!
+ This property holds the light gray color name.
+
+ The default value is \c "lightgray".
+
+ \par Access functions:
+ \li QString <b>lightGray</b>() const
+ \li void <b>setLightGray</b>(const QString& color)
+
+ \sa Irc::LightGray
+ */
+QString IrcPalette::lightGray() const
+{
+ Q_D(const IrcPalette);
+ return d->colors.value(Irc::LightGray);
+}
+
+void IrcPalette::setLightGray(const QString& color)
+{
+ Q_D(IrcPalette);
+ d->colors.insert(Irc::LightGray, color);
+}
+
+/*!
+ Returns the map of color names.
+ */
+QMap<int, QString> IrcPalette::colorNames() const
+{
+ Q_D(const IrcPalette);
+ return d->colors;
+}
+
+/*!
+ Sets the map of color \a names.
+ */
+void IrcPalette::setColorNames(const QMap<int, QString>& names)
+{
+ Q_D(IrcPalette);
+ d->colors = names;
+}
+
+/*!
+ Converts a \a color code to a color name. If the \a color code
+ is unknown, the function returns the \a fallback color name.
+*/
+QString IrcPalette::colorName(int color, const QString& fallback) const
+{
+ Q_D(const IrcPalette);
+ return d->colors.value(color, fallback);
+}
+
+/*!
+ Assigns a \a name for \a color code.
+
+ The color \a name may be in one of these formats:
+
+ \li \#RGB (each of R, G, and B is a single hex digit)
+ \li \#RRGGBB
+ \li \#RRRGGGBBB
+ \li \#RRRRGGGGBBBB
+ \li A name from the list of colors defined in the list of <a href="http://www.w3.org/TR/SVG/types.html#ColorKeywords">SVG color keyword names</a>
+ provided by the World Wide Web Consortium; for example, "steelblue" or "gainsboro". These color names work on all platforms. Note that these
+ color names are not the same as defined by the Qt::GlobalColor enums, e.g. "green" and Qt::green does not refer to the same color.
+ \li transparent - representing the absence of a color.
+*/
+void IrcPalette::setColorName(int color, const QString& name)
+{
+ Q_D(IrcPalette);
+ d->colors.insert(color, name);
+}
+
+#include "moc_ircpalette.cpp"
+
+IRC_END_NAMESPACE
diff --git a/libcommuni/src/util/irctextformat.cpp b/libcommuni/src/util/irctextformat.cpp
new file mode 100644
index 0000000..072de47
--- /dev/null
+++ b/libcommuni/src/util/irctextformat.cpp
@@ -0,0 +1,550 @@
+/*
+ Copyright (C) 2008-2014 The Communi Project
+
+ You may use this file under the terms of BSD license as follows:
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ * Neither the name of the copyright holder nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR
+ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+/*
+ Parts of this code come from Konversation and are copyrighted to:
+ Copyright (C) 2002 Dario Abatianni <eisfuchs@tigress.com>
+ Copyright (C) 2004 Peter Simonsson <psn@linux.se>
+ Copyright (C) 2006-2008 Eike Hein <hein@kde.org>
+ Copyright (C) 2004-2009 Eli Mackenzie <argonel@gmail.com>
+*/
+
+#include "irctextformat.h"
+#include "ircpalette.h"
+#if QT_VERSION >= 0x050000
+#include <QRegularExpression>
+#endif
+#include <QStringList>
+#include <QRegExp>
+#include <QUrl>
+#include "irc.h"
+
+IRC_BEGIN_NAMESPACE
+
+/*!
+ \file irctextformat.h
+ \brief \#include &lt;IrcTextFormat&gt;
+ */
+
+/*!
+ \class IrcTextFormat irctextformat.h <IrcTextFormat>
+ \ingroup util
+ \brief Provides methods for text formatting.
+
+ IrcTextFormat is used to convert IRC-style formatted messages to either
+ plain text or HTML. When converting to plain text, the IRC-style formatting
+ (colors, bold, underline etc.) are simply stripped away. When converting
+ to HTML, the IRC-style formatting is converted to the corresponding HTML
+ formatting.
+
+ \code
+ IrcTextFormat format;
+ QString text = format.toPlainText(message);
+
+ format.palette()->setColorName(Irc::Red, "#ff3333");
+ format.palette()->setColorName(Irc::Green, "#33ff33");
+ format.palette()->setColorName(Irc::Blue, "#3333ff");
+ // ...
+ QString html = format.toHtml(message);
+ \endcode
+
+ \sa IrcPalette
+ */
+
+/*!
+ \enum IrcTextFormat::SpanFormat
+ This enum describes the supported formats for HTML span-elements.
+ */
+
+/*!
+ \var IrcTextFormat::SpanStyle
+ \brief HTML span-elements with style-attributes.
+ */
+
+/*!
+ \var IrcTextFormat::SpanClass
+ \brief HTML span-elements with class-attributes.
+ */
+
+class IrcTextFormatPrivate
+{
+public:
+ void parse(const QString& str, QString* text, QString* html, QList<QUrl>* urls) const;
+
+ QString plainText;
+ QString html;
+ QList<QUrl> urls;
+ QString urlPattern;
+ IrcPalette* palette;
+ IrcTextFormat::SpanFormat spanFormat;
+};
+
+static bool parseColors(const QString& message, int pos, int* len, int* fg = 0, int* bg = 0)
+{
+ // fg(,bg)
+ *len = 0;
+ if (fg)
+ *fg = -1;
+ if (bg)
+ *bg = -1;
+ QRegExp rx(QLatin1String("(\\d{1,2})(?:,(\\d{1,2}))?"));
+ int idx = rx.indexIn(message, pos);
+ if (idx == pos) {
+ *len = rx.matchedLength();
+ if (fg)
+ *fg = rx.cap(1).toInt();
+ if (bg) {
+ bool ok = false;
+ int tmp = rx.cap(2).toInt(&ok);
+ if (ok)
+ *bg = tmp;
+ }
+ }
+ return *len > 0;
+}
+
+static QString generateLink(const QString& protocol, const QString& href)
+{
+ const char* exclude = ":/?@%#=+&,";
+ const QByteArray url = QUrl::toPercentEncoding(href, exclude);
+ return QString(QLatin1String("<a href='%1%2'>%3</a>")).arg(protocol, url, href);
+}
+
+static QString parseLinks(const QString& message, const QString& pattern, QList<QUrl>* urls)
+{
+ QString processed = message;
+#if QT_VERSION >= 0x050000
+ int offset = 0;
+ QRegularExpression rx(pattern);
+ QRegularExpressionMatchIterator it = rx.globalMatch(message);
+ while (it.hasNext()) {
+ QRegularExpressionMatch match = it.next();
+ QString protocol;
+ if (match.capturedRef(2).isEmpty()) {
+ QStringRef link = match.capturedRef(1);
+ if (link.startsWith(QStringLiteral("ftp."), Qt::CaseInsensitive))
+ protocol = QStringLiteral("ftp://");
+ else if (link.contains(QStringLiteral("@")))
+ protocol = QStringLiteral("mailto:");
+ else
+ protocol = QStringLiteral("http://");
+ }
+
+ const int start = match.capturedStart();
+ const int len = match.capturedEnd() - start;
+ const QString href = match.captured();
+ const QString link = generateLink(protocol, href);
+ processed.replace(start + offset, len, link);
+ offset += link.length() - len;
+ if (urls)
+ urls->append(QUrl(protocol + href));
+ }
+#else
+ int pos = 0;
+ QRegExp rx(pattern);
+ while ((pos = rx.indexIn(processed, pos)) >= 0) {
+ int len = rx.matchedLength();
+ QString href = processed.mid(pos, len);
+
+ QString protocol;
+ if (rx.cap(2).isEmpty()) {
+ if (rx.cap(1).contains(QLatin1Char('@')))
+ protocol = QLatin1String("mailto:");
+ else if (rx.cap(1).startsWith(QLatin1String("ftp."), Qt::CaseInsensitive))
+ protocol = QLatin1String("ftp://");
+ else
+ protocol = QLatin1String("http://");
+ }
+
+ QString link = generateLink(protocol, href);
+ processed.replace(pos, len, link);
+ pos += link.length();
+ if (urls)
+ urls->append(QUrl(protocol + href));
+ }
+#endif
+ return processed;
+}
+
+void IrcTextFormatPrivate::parse(const QString& str, QString* text, QString* html, QList<QUrl>* urls) const
+{
+ QString processed = str;
+
+ // TODO:
+ //processed.replace(QLatin1Char('&'), QLatin1String("&amp;"));
+ processed.replace(QLatin1Char('<'), QLatin1String("&lt;"));
+ //processed.replace(QLatin1Char('>'), QLatin1String("&gt;"));
+ //processed.replace(QLatin1Char('"'), QLatin1String("&quot;"));
+ //processed.replace(QLatin1Char('\''), QLatin1String("&apos;"));
+ //processed.replace(QLatin1Char('\t'), QLatin1String("&nbsp;"));
+
+ enum {
+ None = 0x0,
+ Bold = 0x1,
+ Italic = 0x4,
+ LineThrough = 0x8,
+ Underline = 0x10,
+ Inverse = 0x20
+ };
+ int state = None;
+
+ int pos = 0;
+ int len = 0;
+ int fg = -1;
+ int bg = -1;
+ int depth = 0;
+ bool potentialUrl = false;
+ while (pos < processed.size()) {
+ QString replacement;
+ switch (processed.at(pos).unicode()) {
+ case '\x02': // bold
+ if (state & Bold) {
+ depth--;
+ replacement = QLatin1String("</span>");
+ } else {
+ depth++;
+ if (spanFormat == IrcTextFormat::SpanStyle)
+ replacement = QLatin1String("<span style='font-weight: bold'>");
+ else
+ replacement = QLatin1String("<span class='bold'>");
+ }
+ state ^= Bold;
+ break;
+
+ case '\x03': // color
+ if (parseColors(processed, pos + 1, &len, &fg, &bg)) {
+ depth++;
+ if (spanFormat == IrcTextFormat::SpanStyle) {
+ QStringList styles;
+ styles += QString(QLatin1String("color: %1")).arg(palette->colorName(fg, QLatin1String("black")));
+ if (bg != -1)
+ styles += QString(QLatin1String("background-color: %1")).arg(palette->colorName(bg, QLatin1String("transparent")));
+ replacement = QString(QLatin1String("<span style='%1'>")).arg(styles.join(QLatin1String("; ")));
+ } else {
+ QStringList classes;
+ classes += palette->colorName(fg, QLatin1String("black"));
+ if (bg != -1)
+ classes += palette->colorName(bg, QLatin1String("transparent")) + QLatin1String("-background");
+ replacement = QString(QLatin1String("<span class='%1'>")).arg(classes.join(QLatin1String(" ")));
+ }
+ // \x03FF(,BB)
+ processed.replace(pos, len + 1, replacement);
+ pos += replacement.length();
+ continue;
+ } else {
+ depth--;
+ replacement = QLatin1String("</span>");
+ }
+ break;
+
+ //case '\x09': // italic
+ case '\x1d': // italic
+ if (state & Italic) {
+ depth--;
+ replacement = QLatin1String("</span>");
+ } else {
+ depth++;
+ if (spanFormat == IrcTextFormat::SpanStyle)
+ replacement = QLatin1String("<span style='font-style: italic'>");
+ else
+ replacement = QLatin1String("<span class='italic'>");
+ }
+ state ^= Italic;
+ break;
+
+ case '\x13': // line-through
+ if (state & LineThrough) {
+ depth--;
+ replacement = QLatin1String("</span>");
+ } else {
+ depth++;
+ if (spanFormat == IrcTextFormat::SpanStyle)
+ replacement = QLatin1String("<span style='text-decoration: line-through'>");
+ else
+ replacement = QLatin1String("<span class='line-through'>");
+ }
+ state ^= LineThrough;
+ break;
+
+ case '\x15': // underline
+ case '\x1f': // underline
+ if (state & Underline) {
+ depth--;
+ replacement = QLatin1String("</span>");
+ } else {
+ depth++;
+ if (spanFormat == IrcTextFormat::SpanStyle)
+ replacement = QLatin1String("<span style='text-decoration: underline'>");
+ else
+ replacement = QLatin1String("<span class='underline'>");
+ }
+ state ^= Underline;
+ break;
+
+ case '\x16': // inverse
+ if (state & Inverse) {
+ depth--;
+ replacement = QLatin1String("</span>");
+ } else {
+ depth++;
+ if (spanFormat == IrcTextFormat::SpanStyle)
+ replacement = QLatin1String("<span style='text-decoration: inverse'>");
+ else
+ replacement = QLatin1String("<span class='inverse'>");
+ }
+ state ^= Inverse;
+ break;
+
+ case '\x0f': // none
+ if (depth > 0)
+ replacement = QString(QLatin1String("</span>")).repeated(depth);
+ else
+ processed.remove(pos--, 1); // must rewind back for ++pos below...
+ state = None;
+ depth = 0;
+ break;
+
+ case '.':
+ case '/':
+ case ':':
+ // a dot, slash or colon NOT surrounded by a space indicates a potential URL
+ if (!potentialUrl && pos > 0 && !processed.at(pos - 1).isSpace()
+ && pos < processed.length() - 1 && !processed.at(pos + 1).isSpace())
+ potentialUrl = true;
+ // flow through
+ default:
+ if (text)
+ *text += processed.at(pos);
+ break;
+ }
+
+ if (!replacement.isEmpty()) {
+ processed.replace(pos, 1, replacement);
+ pos += replacement.length();
+ } else {
+ ++pos;
+ }
+ }
+
+ if ((html || urls) && potentialUrl && !urlPattern.isEmpty())
+ processed = parseLinks(processed, urlPattern, urls);
+ if (html)
+ *html = processed;
+}
+
+/*!
+ Constructs a new text format with \a parent.
+ */
+IrcTextFormat::IrcTextFormat(QObject* parent) : QObject(parent), d_ptr(new IrcTextFormatPrivate)
+{
+ Q_D(IrcTextFormat);
+ d->palette = new IrcPalette(this);
+ d->urlPattern = QString("\\b((?:(?:([a-z][\\w\\.-]+:/{1,3})|www|ftp\\d{0,3}[.]|[a-z0-9.\\-]+[.][a-z]{2,4}/)(?:[^\\s()<>]+|\\(([^\\s()<>]+|(\\([^\\s()<>]+\\)))*\\))+(?:\\(([^\\s()<>]+|(\\([^\\s()<>]+\\)))*\\)|\\}\\]|[^\\s`!()\\[\\]{};:'\".,<>?%1%2%3%4%5%6])|[a-z0-9.\\-+_]+@[a-z0-9.\\-]+[.][a-z]{1,5}[^\\s/`!()\\[\\]{};:'\".,<>?%1%2%3%4%5%6]))").arg(QChar(0x00AB)).arg(QChar(0x00BB)).arg(QChar(0x201C)).arg(QChar(0x201D)).arg(QChar(0x2018)).arg(QChar(0x2019));
+ d->spanFormat = SpanStyle;
+}
+
+/*!
+ Destructs the text format.
+ */
+IrcTextFormat::~IrcTextFormat()
+{
+}
+
+/*!
+ This property holds the palette used for color formatting.
+
+ \par Access function:
+ \li \ref IrcPalette* <b>palette</b>() const
+ */
+IrcPalette* IrcTextFormat::palette() const
+{
+ Q_D(const IrcTextFormat);
+ return d->palette;
+}
+
+/*!
+ This property holds the regular expression pattern used for matching URLs.
+
+ \par Access functions:
+ \li QString <b>urlPattern</b>() const
+ \li void <b>setUrlPattern</b>(const QString& pattern)
+ */
+QString IrcTextFormat::urlPattern() const
+{
+ Q_D(const IrcTextFormat);
+ return d->urlPattern;
+}
+
+void IrcTextFormat::setUrlPattern(const QString& pattern)
+{
+ Q_D(IrcTextFormat);
+ d->urlPattern = pattern;
+}
+
+/*!
+ \since 3.1
+
+ This property holds the format used for HTML span-elements.
+
+ IrcTextFormat uses HTML span-elements for converting the IRC-style text
+ formatting to the corresponding HTML formatting. The \ref SpanStyle format
+ generates self contained span-elements with style-attributes, resulting to
+ HTML that is ready to be used with Qt's rich text classes without additional
+ styling. For more flexible styling, the \ref SpanClass generates span-elements
+ with class-attributes that can be styled with additional style sheets.
+
+ The default value is \ref SpanStyle. The following table illustrates the
+ difference between \ref SpanStyle and \ref SpanClass HTML formatting:
+
+ IRC format | SpanStyle | SpanClass
+ --------------------------------------- | ----------------------------------------------------------------------|----------
+ Bold ("\02...\0F") | &lt;span style='font-weight: bold'&gt;...&lt;/span&gt; | &lt;span class='bold'&gt;...&lt;/span&gt;
+ Color ("\03fg...\0F") | &lt;span style='color: fg;'&gt;...&lt;/span&gt; | &lt;span class='fg'&gt;...&lt;/span&gt;
+ Background ("\03fgbg...\0F") | &lt;span style='color: fg; background-color: bg'&gt;...&lt;/span&gt; | &lt;span class='fg bg-background'&gt;...&lt;/span&gt;
+ Italic ("\09...\0F") | &lt;span style='font-style: italic'&gt;...&lt;/span&gt; | &lt;span class='italic'&gt;...&lt;/span&gt;
+ Line-through ("\13...\0F") | &lt;span style='text-decoration: line-through'&gt;...&lt;/span&gt; | &lt;span class='line-through'&gt;...&lt;/span&gt;
+ Underline ("\15...\0F" or "\1F...\0F") | &lt;span style='text-decoration: underline'&gt;...&lt;/span&gt; | &lt;span class='underline'&gt;...&lt;/span&gt;
+ Inverse ("\16...\0F") | &lt;span style='text-decoration: inverse'&gt;...&lt;/span&gt; | &lt;span class='inverse'&gt;...&lt;/span&gt;
+
+ \par Access functions:
+ \li \ref SpanFormat <b>spanFormat</b>() const
+ \li void <b>setSpanFormat</b>(\ref SpanFormat format)
+ */
+IrcTextFormat::SpanFormat IrcTextFormat::spanFormat() const
+{
+ Q_D(const IrcTextFormat);
+ return d->spanFormat;
+}
+
+void IrcTextFormat::setSpanFormat(IrcTextFormat::SpanFormat format)
+{
+ Q_D(IrcTextFormat);
+ d->spanFormat = format;
+}
+
+
+/*!
+ Converts \a text to HTML. This function parses the text and replaces
+ IRC-style formatting (colors, bold, underline etc.) to the corresponding
+ HTML formatting. Furthermore, this function detects URLs and replaces
+ them with appropriate HTML hyperlinks.
+
+ \note URL detection can be disabled by setting an empty
+ regular expression pattern used for matching URLs.
+
+ \sa toPlainText(), parse(), palette, urlPattern, spanFormat
+*/
+QString IrcTextFormat::toHtml(const QString& text) const
+{
+ Q_D(const IrcTextFormat);
+ QString html;
+ d->parse(text, 0, &html, 0);
+ return html;
+}
+
+/*!
+ Converts \a text to plain text. This function parses the text and
+ strips away IRC-style formatting (colors, bold, underline etc.)
+
+ \sa toHtml(), parse()
+*/
+QString IrcTextFormat::toPlainText(const QString& text) const
+{
+ Q_D(const IrcTextFormat);
+ QString plain;
+ d->parse(text, &plain, 0, 0);
+ return plain;
+}
+
+/*!
+ \since 3.2
+
+ This property holds the current plain text content.
+
+ \par Access function:
+ \li QString <b>plainText</b>() const
+
+ \sa parse(), html, urls
+ */
+QString IrcTextFormat::plainText() const
+{
+ Q_D(const IrcTextFormat);
+ return d->plainText;
+}
+
+/*!
+ \since 3.2
+
+ This property holds the current HTML content.
+
+ \par Access function:
+ \li QString <b>html</b>() const
+
+ \sa parse(), plainText, urls
+ */
+QString IrcTextFormat::html() const
+{
+ Q_D(const IrcTextFormat);
+ return d->html;
+}
+
+/*!
+ \since 3.2
+
+ This property holds the current list of URLs.
+
+ \par Access function:
+ \li QList<QUrl> <b>urls</b>() const
+
+ \sa parse(), plainText, html
+ */
+QList<QUrl> IrcTextFormat::urls() const
+{
+ Q_D(const IrcTextFormat);
+ return d->urls;
+}
+
+/*!
+ \since 3.2
+
+ Parses \a text converting it to plain text and HTML and detects URLs.
+
+ \sa plainText, html, urls
+ */
+void IrcTextFormat::parse(const QString& text)
+{
+ Q_D(IrcTextFormat);
+ d->plainText.clear();
+ d->html.clear();
+ d->urls.clear();
+ d->parse(text, &d->plainText, &d->html, &d->urls);
+}
+
+#include "moc_irctextformat.cpp"
+
+IRC_END_NAMESPACE
diff --git a/libcommuni/src/util/irctoken.cpp b/libcommuni/src/util/irctoken.cpp
new file mode 100644
index 0000000..97f44a4
--- /dev/null
+++ b/libcommuni/src/util/irctoken.cpp
@@ -0,0 +1,125 @@
+/*
+ Copyright (C) 2008-2014 The Communi Project
+
+ You may use this file under the terms of BSD license as follows:
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ * Neither the name of the copyright holder nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR
+ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#include "irctoken_p.h"
+#include <QStringList>
+
+IRC_BEGIN_NAMESPACE
+
+#ifndef IRC_DOXYGEN
+static QList<IrcToken> tokenize(const QString& str)
+{
+ int idx = -1;
+ int pos = 0;
+ QList<IrcToken> tokens;
+ foreach (const QString& txt, str.split(QLatin1String(" "))) {
+ if (!txt.isEmpty())
+ tokens += IrcToken(++idx, pos, txt);
+ pos += txt.length() + 1;
+ }
+ return tokens;
+}
+
+IrcTokenizer::IrcTokenizer(const QString& str) : len(str.length()), t(tokenize(str))
+{
+}
+
+int IrcTokenizer::count() const
+{
+ return t.count();
+}
+
+bool IrcTokenizer::isEmpty() const
+{
+ return t.isEmpty();
+}
+
+QList<IrcToken> IrcTokenizer::tokens() const
+{
+ return t;
+}
+
+IrcToken IrcTokenizer::at(int index) const
+{
+ return t.value(index);
+}
+
+IrcTokenizer IrcTokenizer::mid(int index) const
+{
+ IrcTokenizer tt;
+ tt.t = t.mid(index);
+ if (!tt.isEmpty()) {
+ int d = tt.t.first().position();
+ tt.len = len - d;
+ for (int i = 0; i < tt.t.length(); ++i) {
+ tt.t[i].idx = i;
+ tt.t[i].pos -= d;
+ }
+ }
+ return tt;
+}
+
+void IrcTokenizer::clear()
+{
+ t.clear();
+}
+
+void IrcTokenizer::replace(int index, const QString& text)
+{
+ IrcToken token = t.value(index);
+ if (token.isValid()) {
+ int d = text.length() - token.length();
+ token = IrcToken(index, token.position(), text);
+ t.replace(index, token);
+ len += d;
+ for (int i = index + 1; i < t.length(); ++i)
+ t[i].pos += d;
+ }
+}
+
+IrcToken IrcTokenizer::find(int pos) const
+{
+ IrcToken token;
+ foreach (const IrcToken& tk, t) {
+ if (tk.position() > pos)
+ break;
+ token = tk;
+ }
+ return token;
+}
+
+QString IrcTokenizer::toString() const
+{
+ QString str(len, QLatin1Char(' '));
+ foreach (const IrcToken& token, t)
+ str.replace(token.position(), token.length(), token.text());
+ return str;
+}
+#endif // IRC_DOXYGEN
+
+IRC_END_NAMESPACE
diff --git a/libcommuni/src/util/ircutil.cpp b/libcommuni/src/util/ircutil.cpp
new file mode 100644
index 0000000..efcb591
--- /dev/null
+++ b/libcommuni/src/util/ircutil.cpp
@@ -0,0 +1,61 @@
+/*
+ Copyright (C) 2008-2014 The Communi Project
+
+ You may use this file under the terms of BSD license as follows:
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ * Neither the name of the copyright holder nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR
+ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#include "ircutil.h"
+
+IRC_BEGIN_NAMESPACE
+
+/*!
+ \file ircutil.h
+ \brief \#include &lt;IrcUtil&gt;
+ */
+
+/*!
+ \namespace IrcUtil
+ \ingroup util
+ \brief Module meta-type registration.
+ */
+
+namespace IrcUtil {
+
+ /*!
+ Registers IrcUtil types to the %Qt meta-system.
+
+ \sa IrcCore::registerMetaTypes(), IrcModel::registerMetaTypes(), qRegisterMetaType()
+ */
+ void registerMetaTypes()
+ {
+ qRegisterMetaType<IrcCommandParser*>("IrcCommandParser*");
+ qRegisterMetaType<IrcCompleter*>("IrcCompleter*");
+ qRegisterMetaType<IrcLagTimer*>("IrcLagTimer*");
+ qRegisterMetaType<IrcPalette*>("IrcPalette*");
+ qRegisterMetaType<IrcTextFormat*>("IrcTextFormat*");
+ }
+}
+
+IRC_END_NAMESPACE
diff --git a/libcommuni/src/util/util.pri b/libcommuni/src/util/util.pri
new file mode 100644
index 0000000..ad4b69f
--- /dev/null
+++ b/libcommuni/src/util/util.pri
@@ -0,0 +1,39 @@
+######################################################################
+# Communi
+######################################################################
+
+DEFINES += BUILD_IRC_UTIL
+
+INCDIR = $$PWD/../../include/IrcUtil
+
+DEPENDPATH += $$PWD $$INCDIR
+INCLUDEPATH += $$PWD $$INCDIR
+
+CONV_HEADERS += $$INCDIR/IrcCommandParser
+CONV_HEADERS += $$INCDIR/IrcCompleter
+CONV_HEADERS += $$INCDIR/IrcLagTimer
+CONV_HEADERS += $$INCDIR/IrcPalette
+CONV_HEADERS += $$INCDIR/IrcTextFormat
+CONV_HEADERS += $$INCDIR/IrcUtil
+
+PUB_HEADERS += $$INCDIR/irccommandparser.h
+PUB_HEADERS += $$INCDIR/irccompleter.h
+PUB_HEADERS += $$INCDIR/irclagtimer.h
+PUB_HEADERS += $$INCDIR/ircpalette.h
+PUB_HEADERS += $$INCDIR/irctextformat.h
+PUB_HEADERS += $$INCDIR/ircutil.h
+
+PRIV_HEADERS = $$INCDIR/irccommandparser_p.h
+PRIV_HEADERS += $$INCDIR/irclagtimer_p.h
+PRIV_HEADERS += $$INCDIR/irctoken_p.h
+
+HEADERS += $$PUB_HEADERS
+HEADERS += $$PRIV_HEADERS
+
+SOURCES += $$PWD/irccommandparser.cpp
+SOURCES += $$PWD/irccompleter.cpp
+SOURCES += $$PWD/irclagtimer.cpp
+SOURCES += $$PWD/ircpalette.cpp
+SOURCES += $$PWD/irctextformat.cpp
+SOURCES += $$PWD/irctoken.cpp
+SOURCES += $$PWD/ircutil.cpp
diff --git a/libcommuni/src/util/util.pro b/libcommuni/src/util/util.pro
new file mode 100644
index 0000000..bc55556
--- /dev/null
+++ b/libcommuni/src/util/util.pro
@@ -0,0 +1,11 @@
+######################################################################
+# Communi
+######################################################################
+
+IRC_MODULE = IrcUtil
+include(util.pri)
+include(../module_build.pri)
+include(../module_install.pri)
+
+IRC_MODULES = IrcCore IrcModel
+include(../module_deps.pri)