From: APTX Date: Wed, 31 Dec 2025 03:34:19 +0000 (+0900) Subject: Qt6 port X-Git-Url: https://gitweb.tyo.aptx.org/?a=commitdiff_plain;h=a31bd5a9be0359f9d6c1657ca0ea550eb77430b6;p=anidbudpclient.git Qt6 port Everything should be ported, except for the ECMAScript rename engine (always returns empty name). The new QJSEngine is not designed for scripting like the old one. There is no simple way to register functions in JS, the concept of "script context" changed. --- diff --git a/AniDBUdpClientConfig.cmake.in b/AniDBUdpClientConfig.cmake.in index b6fcc7c..793ad7c 100644 --- a/AniDBUdpClientConfig.cmake.in +++ b/AniDBUdpClientConfig.cmake.in @@ -4,7 +4,9 @@ include(CMakeFindDependencyMacro) # TODO find_dependency with components is nonstandard -find_dependency(Qt5Network) +find_dependency(Qt6Network) +find_dependency(Qt6StateMachine) +find_dependency(Qt6Qml) include("${CMAKE_CURRENT_LIST_DIR}/@CONFIG_NAME@Targets.cmake") diff --git a/CMakeLists.txt b/CMakeLists.txt index d7c3dc9..d587a5a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.8 FATAL_ERROR) +cmake_minimum_required(VERSION 4.0 FATAL_ERROR) include(FeatureSummary) set(CMAKE_INCLUDE_CURRENT_DIR ON) @@ -15,16 +15,20 @@ add_feature_info(Encryption WITH_ENCRYPTION "ENCRYPT command support (requires Q option(WITH_CLIENT_DEBUG "Enable client debug logs" OFF) add_feature_info(ClientDebug WITH_CLIENT_DEBUG "Print extra logs for debug purposes") -set(QT_MIN_VERSION "5.8.0") -find_package(Qt5 ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS +set(QT_MIN_VERSION "6.10.0") +find_package(Qt6 ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS Core Network - Script + StateMachine + # For QJSEngine + Qml ) set(AniDBUdpClient_PUBLIC_LIBS - Qt5::Core - Qt5::Network + Qt6::Core + Qt6::Network + Qt6::StateMachine + Qt6::Qml ) set(AniDBUdpClient_SOURCES @@ -110,6 +114,9 @@ install(FILES ${AniDBUdpClient_CONV_HEADERS} ) if(WITH_RENAMEPARSER) + find_package(AucQJSEngineFunction CONFIG REQUIRED) + set(AniDBUdpClient_LIBS ${AniDBUdpClient_LIBS} AucQJSEngineFunction::AucQJSEngineFunction) + set(AniDBUdpClient_RENAMEPARSER_HEADERS renameparser/renameengine.h renameparser/functions.h @@ -148,11 +155,6 @@ if(WITH_RENAMEPARSER) include/RenameParser/RenameEngine ) - set(AniDBUdpClient_LIBS - ${AniDBUdpClient_LIBS} - Qt5::Script - ) - install(FILES ${AniDBUdpClient_RENAMEPARSER_HEADERS} ${AniDBUdpClient_RENAMEPARSER_CONV_HEADERS} @@ -170,7 +172,7 @@ endif() if(WITH_ENCRYPTION) - set(QCA_NAME "Qca-qt5") + set(QCA_NAME "Qca-qt6") find_package(${QCA_NAME} CONFIG REQUIRED) if (${QCA_NAME}_FOUND) # TODO Runtime plugins are required diff --git a/animecommand.cpp b/animecommand.cpp index 6d70a9d..5243b4a 100644 --- a/animecommand.cpp +++ b/animecommand.cpp @@ -124,7 +124,7 @@ void AnimeReply::setRawReply(ReplyCode replyCode, const QString &reply) void AnimeReply::readReplyData(const QString &reply) { QString d = reply.mid(reply.indexOf('\n')).trimmed(); - QStringList parts = d.split('|', QString::KeepEmptyParts); + QStringList parts = d.split('|', Qt::KeepEmptyParts); if (command().amask() == 0) { @@ -208,7 +208,9 @@ QStringList AnimeReply::characterIdList() const QDateTime AnimeReply::dateRecordUpdated() const { - return QDateTime::fromTime_t(animeFlagData.value(AnimeFlag::DateRecordUpdated).toUInt()); + return QDateTime::fromSecsSinceEpoch( + animeFlagData.value(AnimeFlag::DateRecordUpdated).toUInt(), + QTimeZone::UTC); } QList AnimeReply::tagWeightList() const @@ -311,12 +313,14 @@ QUrl AnimeReply::url() const QDateTime AnimeReply::endDate() const { - return QDateTime::fromTime_t(animeFlagData.value(AnimeFlag::EndDate).toUInt()); + return QDateTime::fromSecsSinceEpoch( + animeFlagData.value(AnimeFlag::EndDate).toUInt(), QTimeZone::UTC); } QDateTime AnimeReply::airDate() const { - return QDateTime::fromTime_t(animeFlagData.value(AnimeFlag::AirDate).toUInt()); + return QDateTime::fromSecsSinceEpoch( + animeFlagData.value(AnimeFlag::AirDate).toUInt(), QTimeZone::UTC); } int AnimeReply::specialEpCount() const diff --git a/client.cpp b/client.cpp index 3eb994d..f33658d 100644 --- a/client.cpp +++ b/client.cpp @@ -5,9 +5,9 @@ #include #include -#include +#include #include -#include +#include #include "rawcommand.h" #include "logoutcommand.h" @@ -627,8 +627,9 @@ qDebug() << QString("Received datagram from [%1]:%2\nRaw datagram contents:%3") // Check if it is a 6xx error. { - QRegExp rx("(?:50[34]|555|598|6[0-9]{2}) "); - if (rx.exactMatch(tmp.mid(0, 4))) + static QRegularExpression rx("(?:50[34]|555|598|6[0-9]{2}) "); + auto match = rx.match(tmp.mid(0, 4)); + if (match.hasMatch()) { int replyCode = tmp.mid(0, 3).toInt(); switch (replyCode) diff --git a/clientinterface.cpp b/clientinterface.cpp index 2644324..a84e270 100644 --- a/clientinterface.cpp +++ b/clientinterface.cpp @@ -1,5 +1,8 @@ #include "clientinterface.h" +#include +#include + namespace AniDBUdpClient { ClientInterface::ClientInterface(QObject *parent) : @@ -60,7 +63,7 @@ QByteArray ClientInterface::nextCommandId(int len) QByteArray result(len, '-'); while (len--) - result[len] = chars[qrand() % numChars]; + result[len] = chars[QRandomGenerator::global()->bounded(numChars)]; #ifdef ANIDBUDPCLIENT_CLIENT_MISC_DEBUG qDebug() << QString("Generated id %1").arg(result.constData()); @@ -73,7 +76,7 @@ QByteArray ClientInterface::buildCmd(const QString &cmd, const QVariantMap &args QString result = cmd; for (QVariantMap::const_iterator it = args.constBegin(); it != args.constEnd(); ++it) { - if (!it.value().canConvert(QVariant::String)) + if (!it.value().canConvert()) { qWarning("Passed value cannot be converted to string!"); continue; @@ -81,7 +84,7 @@ QByteArray ClientInterface::buildCmd(const QString &cmd, const QVariantMap &args // The string version of bool is "true" or "false", but the API expects 1 or 0 QString value; - if (it.value().type() == QVariant::Bool) + if (it.value().typeId() == QMetaType::Bool) { value = it.value().toBool() ? "1" : "0"; } diff --git a/episodecommand.cpp b/episodecommand.cpp index 17561f2..0a1c2cd 100644 --- a/episodecommand.cpp +++ b/episodecommand.cpp @@ -183,7 +183,8 @@ void EpisodeReply::setRawReply(ReplyCode replyCode, const QString &reply) { case EPISODE: { - QStringList parts = reply.mid(reply.indexOf("\n")).split('|', QString::KeepEmptyParts); + QStringList parts = + reply.mid(reply.indexOf("\n")).split('|', Qt::KeepEmptyParts); if (parts.count() < 10) { signalReplyReady(false); @@ -215,7 +216,8 @@ void EpisodeReply::setRawReply(ReplyCode replyCode, const QString &reply) m_titleEnglish = parts[6]; m_titleRomaji = parts[7]; m_titleKanji = parts[8]; - m_airDate = QDateTime::fromTime_t(parts[9].toUInt(&ok, 10)); + m_airDate = QDateTime::fromSecsSinceEpoch(parts[9].toUInt(&ok, 10), + QTimeZone::UTC); if (parts.size() > 11) { m_typeAsInt = parts[10].toInt(&ok, 10); diff --git a/file.cpp b/file.cpp index 5a1c096..3ce3635 100644 --- a/file.cpp +++ b/file.cpp @@ -1,6 +1,6 @@ #include "file.h" -#include +#include #include "client.h" #include "hash.h" #include "filerenamedelegate.h" @@ -205,17 +205,20 @@ MyListState File::state() const File *File::fromEd2k(const QString &ed2k) { - QRegExp rx("^ed2k://\\|file\\|[^|]+\\|([0-9]+)\\|([a-z0-9]{32})\\|", Qt::CaseInsensitive, QRegExp::RegExp2); + static QRegularExpression rx( + "^ed2k://\\|file\\|[^|]+\\|([0-9]+)\\|([a-z0-9]{32})\\|", + QRegularExpression::CaseInsensitiveOption); - if (rx.indexIn(ed2k) == -1) + auto match = rx.match(ed2k); + if (!match.hasMatch()) return 0; bool ok = false; - qint64 size = rx.cap(1).toLongLong(&ok); + qint64 size = match.captured(1).toLongLong(&ok); if (!ok) return 0; - QByteArray hash = rx.cap(2).toLower().toLatin1(); + QByteArray hash = match.captured(2).toLower().toLatin1(); File *ret = new File(); ret->m_size = size; @@ -297,8 +300,10 @@ qDebug() << "lid = " << m_lid; .arg(fileReply->value(FileFlag::FileType).toString()); } newFileName.replace('"', "'"); - newFileName.replace(QRegExp("[\\/]"), "-"); - newFileName.replace(QRegExp("[\\/:*?\"<>|]"), ""); + static QRegularExpression rx1("[\\/]"); + newFileName.replace(rx1, "-"); + static QRegularExpression rx2("[\\/:*?\"<>|]"); + newFileName.replace(rx2, ""); #ifdef ANIDBUDPCLIENT_RENAME_DEBUG qDebug() << "New file name:" << newFileName; #endif diff --git a/filecommand.cpp b/filecommand.cpp index 177f12a..d50670d 100644 --- a/filecommand.cpp +++ b/filecommand.cpp @@ -302,7 +302,7 @@ void FileReply::setRawReply(ReplyCode replyCode, const QString &reply) void FileReply::readReplyData(const QString &reply) { QString d = reply.mid(reply.indexOf('\n')).trimmed(); - QList parts = d.split('|', QString::KeepEmptyParts); + QList parts = d.split('|', Qt::KeepEmptyParts); m_fid = parts[0].toInt(); if (command().fmask() == 0 && command().amask() == 0) @@ -356,7 +356,9 @@ void FileReply::init() QDateTime FileReply::dateAidRecordUpdated() const { - return QDateTime::fromTime_t(fileAnimeFlagData.value(FileAnimeFlag::DateAidRecordUpdated).toUInt()); + return QDateTime::fromSecsSinceEpoch( + fileAnimeFlagData.value(FileAnimeFlag::DateAidRecordUpdated).toUInt(), + QTimeZone::UTC); } QString FileReply::groupShortName() const @@ -489,7 +491,7 @@ QDateTime FileReply::myListViewDate() const bool ok; uint timestamp = fileFlagData.value(FileFlag::MyListViewDate).toUInt(&ok); if (ok && timestamp) - return QDateTime::fromTime_t(timestamp); + return QDateTime::fromSecsSinceEpoch(timestamp, QTimeZone::UTC); return QDateTime(); } diff --git a/mylistaddcommand.cpp b/mylistaddcommand.cpp index 56e50e7..09756fa 100644 --- a/mylistaddcommand.cpp +++ b/mylistaddcommand.cpp @@ -209,7 +209,7 @@ Command MyListAddCommand::rawCommand() const if (!m_viewDate.isNull()) { - cmd.second["viewdate"] = m_viewDate.toTime_t(); + cmd.second["viewdate"] = m_viewDate.toSecsSinceEpoch(); } if (!m_source.isEmpty()) diff --git a/mylistcommand.cpp b/mylistcommand.cpp index 72bcf5b..b7f8a03 100644 --- a/mylistcommand.cpp +++ b/mylistcommand.cpp @@ -307,7 +307,7 @@ void MyListReply::setRawReply(ReplyCode replyCode, const QString &reply) { case MYLIST: { - QStringList parts = reply.mid(reply.indexOf("\n")).split('|', QString::KeepEmptyParts); + QStringList parts = reply.mid(reply.indexOf("\n")).split('|', Qt::KeepEmptyParts); if (parts.count() < 12) { signalReplyReady(false); @@ -319,9 +319,11 @@ void MyListReply::setRawReply(ReplyCode replyCode, const QString &reply) m_eid = parts[2].toInt(&ok, 10); m_aid = parts[3].toInt(&ok, 10); m_gid = parts[4].toInt(&ok, 10); - m_date = QDateTime::fromTime_t(parts[5].toUInt(&ok, 10)); + m_date = QDateTime::fromSecsSinceEpoch(parts[5].toUInt(&ok, 10), + QTimeZone::UTC); m_state = State(parts[6].toInt(&ok, 10)); - m_viewDate = QDateTime::fromTime_t(parts[7].toUInt(&ok, 10)); + m_viewDate = QDateTime::fromSecsSinceEpoch(parts[7].toUInt(&ok, 10), + QTimeZone::UTC); m_storage = parts[8]; m_source = parts[9]; m_other = parts[10]; @@ -331,7 +333,7 @@ void MyListReply::setRawReply(ReplyCode replyCode, const QString &reply) break; case MULTIPLE_MYLIST_ENTRIES: { - m_multipleEntries = reply.mid(reply.indexOf("\n")).split('|', QString::KeepEmptyParts); + m_multipleEntries = reply.mid(reply.indexOf("\n")).split('|', Qt::KeepEmptyParts); signalReplyReady(true); } break; diff --git a/renameparser/AniAdd/lexer.cpp b/renameparser/AniAdd/lexer.cpp index ca6c1cd..b3606ab 100644 --- a/renameparser/AniAdd/lexer.cpp +++ b/renameparser/AniAdd/lexer.cpp @@ -28,21 +28,24 @@ void Lexer::lex(const QString &s) bool Lexer::next() { - m_pos = rx.indexIn(m_data, m_pos); + m_last_match = rx.match(m_data, m_pos); + m_pos = m_last_match.capturedStart(); - if (m_pos == -1) + if (!m_last_match.hasMatch()) { m_type = 0; return false; } #ifdef PARSER_DEBUG -QString m = rx.cap(); -qDebug() << "TOKEN=" << m; + QString m = rx.cap(); + qDebug() << "TOKEN=" << m; #endif - m_pos += rx.matchedLength(); - m_column += rx.matchedLength(); + m_pos += m_last_match.capturedLength(); + m_column += m_last_match.capturedLength(); - if (rx.cap() == "\n" || rx.cap()[0] == QChar('#') || rx.cap().left(2) == "//") + if (m_last_match.captured() == "\n" || + m_last_match.captured()[0] == QChar('#') || + m_last_match.captured().left(2) == "//") { ++m_line; m_column = 1; @@ -50,18 +53,19 @@ qDebug() << "TOKEN=" << m; return true; } - if (tokenMap.contains(rx.cap())) + if (tokenMap.contains(m_last_match.captured())) { - m_type = tokenMap.value(rx.cap()); + m_type = tokenMap.value(m_last_match.captured()); } - else if (rx.cap()[0] == QChar('"') || rx.cap()[0] == QChar('\'')) + else if (m_last_match.captured()[0] == QChar('"') || + m_last_match.captured()[0] == QChar('\'')) { m_type = RenameGrammar::STRING; } else { bool ok; - rx.cap().toDouble(&ok); + m_last_match.captured().toDouble(&ok); if (ok) m_type = RenameGrammar::NUMBER; @@ -74,8 +78,9 @@ qDebug() << "TOKEN=" << m; QString Lexer::value() const { if (m_type == RenameGrammar::STRING) - return rx.cap().mid(1, rx.cap().length() - 2); - return rx.cap(); + return m_last_match.captured().mid(1, m_last_match.captured().length() - + 2); + return m_last_match.captured(); } int Lexer::type() const @@ -128,7 +133,7 @@ void Lexer::staticInit() const char *Lexer::regexp = ":=|=|%|\\?|:|\\$|,|\\(|\\)|\\[|\\]|\\{|\\}|\\n|else|if|(:?//|#)[^\\n]*\\n|[^\"\\[\\]:= {}(),%$?\\n]+|\"[^\"]*\"|'[^']*'"; -QRegExp Lexer::rx(Lexer::regexp); +QRegularExpression Lexer::rx(Lexer::regexp); QMap Lexer::tokenMap; bool Lexer::staticInitialised = false; diff --git a/renameparser/AniAdd/lexer.h b/renameparser/AniAdd/lexer.h index cdc44bd..9afeaf0 100644 --- a/renameparser/AniAdd/lexer.h +++ b/renameparser/AniAdd/lexer.h @@ -2,7 +2,8 @@ #define LEXER_H #include -#include +#include +#include #include "renameparser.h" @@ -41,7 +42,8 @@ private: int m_column; int m_pos; - static QRegExp rx; + QRegularExpressionMatch m_last_match; + static QRegularExpression rx; static const char *regexp; static QMap tokenMap; diff --git a/renameparser/ECMAScript/parser.cpp b/renameparser/ECMAScript/parser.cpp index 117772e..d6d1f82 100644 --- a/renameparser/ECMAScript/parser.cpp +++ b/renameparser/ECMAScript/parser.cpp @@ -1,21 +1,22 @@ #include "parser.h" -#include +#include "../renameengine.h" +#include -namespace RenameParser { -namespace ECMAScript { +#include +#include -QScriptValue RenameEngine2QtScriptHelper(QScriptContext *context, QScriptEngine *engine, void *arg) +namespace RenameParser { +namespace ECMAScript { - Q_UNUSED(engine); - - RenameFunction func = (RenameFunction) arg; - - QStringList args; - for (int i = 0; i < context->argumentCount(); ++i) - args << context->argument(i).toString(); +QJSValue RenameEngineFunctionWrapperHelper(RenameParser::RenameFunction func, + const QJSValueList &args) +{ + QStringList strArgs; + for (const auto &arg : args) + strArgs << arg.toString(); - return QScriptValue(func(args)); + return QJSValue{func(strArgs)}; } Parser::Parser() : AbstractParser() @@ -33,51 +34,47 @@ bool Parser::parse(const QString &string) m_line = 0; m_column = 0; - QScriptSyntaxCheckResult checkResult = engine.checkSyntax(string); + QJSEngine engine; - if (checkResult.state() == QScriptSyntaxCheckResult::Valid) - { - program = string; - return true; - } - m_error = checkResult.errorMessage(); - m_line = checkResult.errorLineNumber(); - m_column = checkResult.errorColumnNumber(); + QJSValue result = engine.evaluate(string, QStringLiteral(""), 1); - return false; + if (result.isError()) { + m_line = result.property("lineNumber").toInt(); + m_column = 0; + m_error = + QStringLiteral("%1 at line %2").arg(result.toString()).arg(m_line); + return false; + } + program = string; + return true; } QString Parser::evaluate(Environment &env) const { - QScriptContext *fct = engine.pushContext(); - - foreach (const QString &funcName, RenameEngine::registeredFunctions()) + QJSEngine engine; + const auto functionList = RenameEngine::registeredFunctions(); + for (const QString &funcName : std::as_const(functionList)) { - fct->activationObject().setProperty(funcName, - engine.newFunction( - &RenameEngine2QtScriptHelper, (void *) RenameEngine::function(funcName))); + RenameFunction func = RenameEngine::function(funcName); + AucQJSEngineFunction::registerFunction( + engine, funcName, [func](const QJSValueList &args) -> QJSValue + { return RenameEngineFunctionWrapperHelper(func, args); }); } - - QScriptContext *ct = engine.pushContext(); - - for (Environment::const_iterator i = env.constBegin(); i != env.constEnd(); ++i) + for (auto it = env.constBegin(); it != env.constEnd(); ++it) { - ct->activationObject().setProperty(i.key(), i.value()); + engine.globalObject().setProperty(it.key(), it.value()); } QString ret = engine.evaluate(program).toString(); - QScriptValueIterator it(ct->activationObject()); - + QJSValueIterator it(engine.globalObject()); while (it.hasNext()) { it.next(); + if (!env.contains(it.name())) + continue; env[it.name()] = it.value().toString(); } - - engine.popContext(); - engine.popContext(); - return ret; } @@ -96,4 +93,5 @@ int Parser::column() const return m_column; } -}} // namespace +} // namespace ECMAScript +} // namespace RenameParser diff --git a/renameparser/ECMAScript/parser.h b/renameparser/ECMAScript/parser.h index 10d0580..9afd3c9 100644 --- a/renameparser/ECMAScript/parser.h +++ b/renameparser/ECMAScript/parser.h @@ -1,10 +1,8 @@ #ifndef ECMASCRIPTPARSER_H #define ECMASCRIPTPARSER_H -#include -#include #include "../abstractparser.h" -#include "../renameengine.h" +#include namespace RenameParser { namespace ECMAScript { @@ -23,13 +21,13 @@ public: int column() const; private: - mutable QScriptEngine engine; - QScriptProgram program; + QString program; QString m_error; int m_line; int m_column; }; -}} // namespace +} // namespace ECMAScript +} // namespace RenameParser #endif // ECMASCRIPTPARSER_H diff --git a/renameparser/ast.h b/renameparser/ast.h index 38c67fc..346a7f2 100644 --- a/renameparser/ast.h +++ b/renameparser/ast.h @@ -1,8 +1,9 @@ #ifndef AST_H #define AST_H -#include +#include #include +#include namespace RenameParser { namespace AST diff --git a/renameparser/functions.cpp b/renameparser/functions.cpp index 3590cff..0185e8f 100644 --- a/renameparser/functions.cpp +++ b/renameparser/functions.cpp @@ -1,5 +1,7 @@ #include "functions.h" +#include + namespace RenameParser { namespace RenameFunctions { @@ -86,14 +88,21 @@ QString repl(const QStringList &args) if (args.count() != 3) return ""; QString ret = args[0]; - return ret.replace(QRegExp(args[1], Qt::CaseSensitive, QRegExp::RegExp2), args[2]); + return ret.replace( + QRegularExpression(args[1], QRegularExpression::CaseInsensitiveOption), + args[2]); } QString match(const QStringList &args) { if (args.count() != 2) return ""; - return QRegExp(args[1], Qt::CaseSensitive, QRegExp::RegExp2).indexIn(args[0]) != -1 ? "1" : ""; + return QRegularExpression(args[1], + QRegularExpression::CaseInsensitiveOption) + .match(args[0]) + .hasMatch() + ? "1" + : ""; } QString uc(const QStringList &args) @@ -199,7 +208,8 @@ QString sanitize(const QStringList &args) { if (args.isEmpty()) return ""; - return QString(args.at(0)).replace(QRegExp("[/\\:*\"?<>|\\r\\n]"), QString("")); + static QRegularExpression rx("[/\\:*\"?<>|\\r\\n]"); + return QString(args.at(0)).replace(rx, QString("")); } } // namespace RenameFunctions diff --git a/updatecommand.cpp b/updatecommand.cpp index 618eff2..33d58d1 100644 --- a/updatecommand.cpp +++ b/updatecommand.cpp @@ -97,7 +97,7 @@ void UpdateReply::setRawReply(ReplyCode replyCode, const QString &reply) case UPDATED: { QString d = reply.mid(reply.indexOf('\n')).trimmed(); - QStringList parts = d.split('|', QString::KeepEmptyParts); + QStringList parts = d.split('|', Qt::KeepEmptyParts); if (parts.count() < 4) { diff --git a/votecommand.cpp b/votecommand.cpp index ae2fbb1..3b55c32 100644 --- a/votecommand.cpp +++ b/votecommand.cpp @@ -235,7 +235,7 @@ void VoteReply::setRawReply(ReplyCode replyCode, const QString &reply) bool VoteReply::readReplyData(const QString &reply) { QString d = reply.mid(reply.indexOf('\n')).trimmed(); - QStringList parts = d.split('|', QString::KeepEmptyParts); + QStringList parts = d.split('|', Qt::KeepEmptyParts); if (parts.count() < 4) {