diff options
| author | Markus Mittendrein <git@maxmitti.tk> | 2014-10-06 15:03:54 +0200 |
|---|---|---|
| committer | Markus Mittendrein <git@maxmitti.tk> | 2014-10-06 15:03:54 +0200 |
| commit | 529f38bd8878b6b1bea2b5457031ce936aab8d80 (patch) | |
| tree | 1193caefcad12f6a36f818048e4547e60add4398 /libcommuni/src/util | |
| parent | 3b58b5536935adff242928ed9f30e1c0262fbd7c (diff) | |
| download | manager-529f38bd8878b6b1bea2b5457031ce936aab8d80.tar.gz manager-529f38bd8878b6b1bea2b5457031ce936aab8d80.zip | |
addedd communi
Diffstat (limited to 'libcommuni/src/util')
| -rw-r--r-- | libcommuni/src/util/irccommandparser.cpp | 597 | ||||
| -rw-r--r-- | libcommuni/src/util/irccompleter.cpp | 405 | ||||
| -rw-r--r-- | libcommuni/src/util/irclagtimer.cpp | 243 | ||||
| -rw-r--r-- | libcommuni/src/util/ircpalette.cpp | 527 | ||||
| -rw-r--r-- | libcommuni/src/util/irctextformat.cpp | 550 | ||||
| -rw-r--r-- | libcommuni/src/util/irctoken.cpp | 125 | ||||
| -rw-r--r-- | libcommuni/src/util/ircutil.cpp | 61 | ||||
| -rw-r--r-- | libcommuni/src/util/util.pri | 39 | ||||
| -rw-r--r-- | libcommuni/src/util/util.pro | 11 |
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 <IrcCommandParser> + */ + +/*! + \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 + -------------------|----------------------|------------ + <param> | <target> | A required parameter. + (<param>) | (<key>) | An optional parameter. + <param...> | <message...> | A required parameter, multiple words accepted. (1) + (<param...>) | (<message...>) | An optional parameter, multiple words accepted. (1) + (<\#param>) | (<\#channel>) | 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, ¶ms)) { + 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 <IrcCompleter> + */ + +/*! + \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 <IrcLagTimer> + */ + +/*! + \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 <IrcPalette> + */ + +/*! + \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 <IrcTextFormat> + */ + +/*! + \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("&")); + processed.replace(QLatin1Char('<'), QLatin1String("<")); + //processed.replace(QLatin1Char('>'), QLatin1String(">")); + //processed.replace(QLatin1Char('"'), QLatin1String(""")); + //processed.replace(QLatin1Char('\''), QLatin1String("'")); + //processed.replace(QLatin1Char('\t'), QLatin1String(" ")); + + 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") | <span style='font-weight: bold'>...</span> | <span class='bold'>...</span> + Color ("\03fg...\0F") | <span style='color: fg;'>...</span> | <span class='fg'>...</span> + Background ("\03fgbg...\0F") | <span style='color: fg; background-color: bg'>...</span> | <span class='fg bg-background'>...</span> + Italic ("\09...\0F") | <span style='font-style: italic'>...</span> | <span class='italic'>...</span> + Line-through ("\13...\0F") | <span style='text-decoration: line-through'>...</span> | <span class='line-through'>...</span> + Underline ("\15...\0F" or "\1F...\0F") | <span style='text-decoration: underline'>...</span> | <span class='underline'>...</span> + Inverse ("\16...\0F") | <span style='text-decoration: inverse'>...</span> | <span class='inverse'>...</span> + + \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 <IrcUtil> + */ + +/*! + \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) |
