forked from qt-creator/qt-creator
GCC parser: Create fewer and better issues
Consider the following compiler warning:
In file included from qgroundcontrol/libs/mavlink/include/mavlink/v2.0/
ardupilotmega/ardupilotmega.h:946,
from qgroundcontrol/libs/mavlink/include/mavlink/v2.0/
ardupilotmega/mavlink.h:32,
from qgroundcontrol/src/comm/QGCMAVLink.h:24,
from qgroundcontrol/src/comm/LinkInterface.h:21,
from qgroundcontrol/src/comm/LinkManager.h:21,
from qgroundcontrol/src/QGCApplication.h:27,
from qgroundcontrol/src/QtLocationPlugin/
QGCMapUrlEngine.cpp:19:
qgroundcontrol/libs/mavlink/include/mavlink/v2.0/ardupilotmega/./
mavlink_msg_vision_position_delta.h: In function ‘uint16_t
mavlink_msg_vision_position_delta_encode(uint8_t, uint8_t,
mavlink_message_t*, const mavlink_vision_position_delta_t*)’:
qgroundcontrol/libs/mavlink/include/mavlink/v2.0/ardupilotmega/./
mavlink_msg_vision_position_delta.h:138:178: warning: taking address of
packed member of ‘__mavlink_vision_position_delta_t’ may result in an
unaligned pointer value [-Waddress-of-packed-member]
138 | return mavlink_msg_vision_position_delta_pack(system_id,
component_id, msg, vision_position_delta->time_usec,
vision_position_delta->time_delta_usec, vision_position_delta-
>angle_delta, vision_position_delta->position_delta,
vision_position_delta->confidence);
|
~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~
Before this patch, this output resulted in nine entries in the issues
pane, which defeats the purpose: The user is supposed to get a quick
overview of the build problems, but instead we basically just copied
over the contents of the compile window, which is of little help and
also slows things down by overloading the task model.
We now try harder to identify output lines that belong to the same issue
and create just one task for them. File paths are now linkified in the
detailed issue view, so that users can still navigate quickly to all
involved files.
Task-number: QTCREATORBUG-22914
Change-Id: I1aed2ffac7d363c02073ef318cb863754379cf6d
Reviewed-by: hjk <hjk@qt.io>
This commit is contained in:
@@ -374,6 +374,7 @@ const QList<FormattedText> OutputFormatter::linkifiedText(
|
||||
int nextLinkSpecIndex = 0;
|
||||
|
||||
for (const FormattedText &t : text) {
|
||||
const int totalPreviousTextLength = totalTextLengthSoFar;
|
||||
|
||||
// There is no more linkification work to be done. Just copy the text as-is.
|
||||
if (nextLinkSpecIndex >= linkSpecs.size()) {
|
||||
@@ -391,7 +392,7 @@ const QList<FormattedText> OutputFormatter::linkifiedText(
|
||||
}
|
||||
|
||||
const OutputLineParser::LinkSpec &linkSpec = linkSpecs.at(nextLinkSpecIndex);
|
||||
const int localLinkStartPos = linkSpec.startPos - totalTextLengthSoFar;
|
||||
const int localLinkStartPos = linkSpec.startPos - totalPreviousTextLength;
|
||||
++nextLinkSpecIndex;
|
||||
|
||||
// We ignore links that would cross format boundaries.
|
||||
|
||||
@@ -159,6 +159,9 @@ public:
|
||||
|
||||
bool hasFatalErrors() const;
|
||||
|
||||
static const QList<Utils::FormattedText> linkifiedText(const QList<FormattedText> &text,
|
||||
const OutputLineParser::LinkSpecs &linkSpecs);
|
||||
|
||||
#ifdef WITH_TESTS
|
||||
void overrideTextCharFormat(const QTextCharFormat &fmt);
|
||||
QList<OutputLineParser *> lineParsers() const;
|
||||
@@ -185,8 +188,6 @@ private:
|
||||
void dumpIncompleteLine(const QString &line, OutputFormat format);
|
||||
void clearLastLine();
|
||||
QList<FormattedText> parseAnsi(const QString &text, const QTextCharFormat &format);
|
||||
const QList<Utils::FormattedText> linkifiedText(const QList<FormattedText> &text,
|
||||
const OutputLineParser::LinkSpecs &linkSpecs);
|
||||
OutputFormat outputTypeForParser(const OutputLineParser *parser, OutputFormat type) const;
|
||||
void setupLineParser(OutputLineParser *parser);
|
||||
|
||||
|
||||
@@ -231,7 +231,7 @@ void IarParser::flush()
|
||||
m_lastTask.details = m_snippets;
|
||||
m_snippets.clear();
|
||||
m_lines += m_lastTask.details.count();
|
||||
setMonospacedDetailsFormat(m_lastTask);
|
||||
setDetailsFormat(m_lastTask);
|
||||
amendFilePath();
|
||||
|
||||
m_expectSnippet = true;
|
||||
|
||||
@@ -258,7 +258,7 @@ void KeilParser::flush()
|
||||
m_lastTask.details = m_snippets;
|
||||
m_snippets.clear();
|
||||
m_lines += m_lastTask.details.count();
|
||||
setMonospacedDetailsFormat(m_lastTask);
|
||||
setDetailsFormat(m_lastTask);
|
||||
Task t = m_lastTask;
|
||||
m_lastTask.clear();
|
||||
scheduleTask(t, m_lines, 1);
|
||||
|
||||
@@ -152,7 +152,7 @@ void SdccParser::flush()
|
||||
if (m_lastTask.isNull())
|
||||
return;
|
||||
|
||||
setMonospacedDetailsFormat(m_lastTask);
|
||||
setDetailsFormat(m_lastTask);
|
||||
Task t = m_lastTask;
|
||||
m_lastTask.clear();
|
||||
scheduleTask(t, m_lines, 1);
|
||||
|
||||
@@ -75,7 +75,7 @@ OutputLineParser::Result ClangParser::handleLine(const QString &line, OutputForm
|
||||
match = m_commandRegExp.match(lne);
|
||||
if (match.hasMatch()) {
|
||||
m_expectSnippet = true;
|
||||
newTask(CompileTask(taskType(match.captured(3)), match.captured(4)));
|
||||
createOrAmendTask(taskType(match.captured(3)), match.captured(4), lne);
|
||||
return Status::InProgress;
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ OutputLineParser::Result ClangParser::handleLine(const QString &line, OutputForm
|
||||
const int lineNo = match.captured(3).toInt();
|
||||
LinkSpecs linkSpecs;
|
||||
addLinkSpecForAbsoluteFilePath(linkSpecs, filePath, lineNo, match, 2);
|
||||
newTask(CompileTask(Task::Unknown, lne.trimmed(), filePath, lineNo));
|
||||
createOrAmendTask(Task::Unknown, lne.trimmed(), lne, false, filePath, lineNo, linkSpecs);
|
||||
return {Status::InProgress, linkSpecs};
|
||||
}
|
||||
|
||||
@@ -100,19 +100,20 @@ OutputLineParser::Result ClangParser::handleLine(const QString &line, OutputForm
|
||||
const FilePath filePath = absoluteFilePath(FilePath::fromUserInput(match.captured(1)));
|
||||
LinkSpecs linkSpecs;
|
||||
addLinkSpecForAbsoluteFilePath(linkSpecs, filePath, lineNo, match, 1);
|
||||
newTask(CompileTask(taskType(match.captured(7)), match.captured(8), filePath, lineNo));
|
||||
createOrAmendTask(taskType(match.captured(7)), match.captured(8), lne, false,
|
||||
filePath, lineNo, linkSpecs);
|
||||
return {Status::InProgress, linkSpecs};
|
||||
}
|
||||
|
||||
match = m_codesignRegExp.match(lne);
|
||||
if (match.hasMatch()) {
|
||||
m_expectSnippet = true;
|
||||
newTask(CompileTask(Task::Error, match.captured(1)));
|
||||
createOrAmendTask(Task::Error, match.captured(1), lne, false);
|
||||
return Status::InProgress;
|
||||
}
|
||||
|
||||
if (m_expectSnippet) {
|
||||
amendDescription(lne);
|
||||
createOrAmendTask(Task::Unknown, lne, lne, true);
|
||||
return Status::InProgress;
|
||||
}
|
||||
|
||||
@@ -178,17 +179,15 @@ void ProjectExplorerPlugin::testClangOutputParser_data()
|
||||
" ^")
|
||||
<< OutputParserTester::STDERR
|
||||
<< QString() << QString()
|
||||
<< (Tasks()
|
||||
<< CompileTask(Task::Unknown,
|
||||
"In file included from ..\\..\\..\\QtSDK1.1\\Desktop\\Qt\\4.7.3\\mingw\\include/QtCore/qnamespace.h:45:",
|
||||
FilePath::fromUserInput("..\\..\\..\\QtSDK1.1\\Desktop\\Qt\\4.7.3\\mingw\\include/QtCore/qnamespace.h"),
|
||||
45)
|
||||
<< CompileTask(Task::Warning,
|
||||
"unknown attribute 'dllimport' ignored [-Wunknown-attributes]\n"
|
||||
"class Q_CORE_EXPORT QSysInfo {\n"
|
||||
" ^",
|
||||
FilePath::fromUserInput("..\\..\\..\\QtSDK1.1\\Desktop\\Qt\\4.7.3\\mingw\\include/QtCore/qglobal.h"),
|
||||
1425))
|
||||
<< Tasks{CompileTask(
|
||||
Task::Warning,
|
||||
"unknown attribute 'dllimport' ignored [-Wunknown-attributes]\n"
|
||||
"In file included from ..\\..\\..\\QtSDK1.1\\Desktop\\Qt\\4.7.3\\mingw\\include/QtCore/qnamespace.h:45:\n"
|
||||
"..\\..\\..\\QtSDK1.1\\Desktop\\Qt\\4.7.3\\mingw\\include/QtCore/qglobal.h(1425) : warning: unknown attribute 'dllimport' ignored [-Wunknown-attributes]\n"
|
||||
"class Q_CORE_EXPORT QSysInfo {\n"
|
||||
" ^",
|
||||
FilePath::fromUserInput("..\\..\\..\\QtSDK1.1\\Desktop\\Qt\\4.7.3\\mingw\\include/QtCore/qglobal.h"),
|
||||
1425)}
|
||||
<< QString();
|
||||
|
||||
QTest::newRow("note")
|
||||
@@ -200,6 +199,7 @@ void ProjectExplorerPlugin::testClangOutputParser_data()
|
||||
<< (Tasks()
|
||||
<< CompileTask(Task::Unknown,
|
||||
"instantiated from:\n"
|
||||
"..\\..\\..\\QtSDK1.1\\Desktop\\Qt\\4.7.3\\mingw\\include/QtCore/qglobal.h:1289:27: note: instantiated from:\n"
|
||||
"# define Q_CORE_EXPORT Q_DECL_IMPORT\n"
|
||||
" ^",
|
||||
FilePath::fromUserInput("..\\..\\..\\QtSDK1.1\\Desktop\\Qt\\4.7.3\\mingw\\include/QtCore/qglobal.h"),
|
||||
@@ -215,6 +215,7 @@ void ProjectExplorerPlugin::testClangOutputParser_data()
|
||||
<< (Tasks()
|
||||
<< CompileTask(Task::Error,
|
||||
"'bits/c++config.h' file not found\n"
|
||||
"/usr/include/c++/4.6/utility:68:10: fatal error: 'bits/c++config.h' file not found\n"
|
||||
"#include <bits/c++config.h>\n"
|
||||
" ^",
|
||||
FilePath::fromUserInput("/usr/include/c++/4.6/utility"),
|
||||
@@ -230,6 +231,7 @@ void ProjectExplorerPlugin::testClangOutputParser_data()
|
||||
<< (Tasks()
|
||||
<< CompileTask(Task::Warning,
|
||||
"?: has lower precedence than +; + will be evaluated first [-Wparentheses]\n"
|
||||
"/home/code/src/creator/src/plugins/coreplugin/manhattanstyle.cpp:567:51: warning: ?: has lower precedence than +; + will be evaluated first [-Wparentheses]\n"
|
||||
" int x = option->rect.x() + horizontal ? 2 : 6;\n"
|
||||
" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ^",
|
||||
FilePath::fromUserInput("/home/code/src/creator/src/plugins/coreplugin/manhattanstyle.cpp"),
|
||||
|
||||
@@ -32,6 +32,8 @@
|
||||
|
||||
#include <utils/qtcassert.h>
|
||||
|
||||
#include <numeric>
|
||||
|
||||
using namespace ProjectExplorer;
|
||||
using namespace Utils;
|
||||
|
||||
@@ -69,11 +71,45 @@ QList<OutputLineParser *> GccParser::gccParserSuite()
|
||||
return {new GccParser, new Internal::LldParser, new LdParser};
|
||||
}
|
||||
|
||||
void GccParser::newTask(const Task &task)
|
||||
void GccParser::createOrAmendTask(
|
||||
Task::TaskType type,
|
||||
const QString &description,
|
||||
const QString &originalLine,
|
||||
bool forceAmend,
|
||||
const FilePath &file,
|
||||
int line,
|
||||
const LinkSpecs &linkSpecs
|
||||
)
|
||||
{
|
||||
flush();
|
||||
m_currentTask = task;
|
||||
m_lines = 1;
|
||||
const bool amend = !m_currentTask.isNull() && (forceAmend || isContinuation(originalLine));
|
||||
if (!amend) {
|
||||
flush();
|
||||
m_currentTask = CompileTask(type, description, file, line);
|
||||
m_currentTask.details.append(originalLine);
|
||||
m_linkSpecs = linkSpecs;
|
||||
m_lines = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
LinkSpecs adaptedLinkSpecs = linkSpecs;
|
||||
const int offset = std::accumulate(m_currentTask.details.cbegin(), m_currentTask.details.cend(),
|
||||
0, [](int total, const QString &line) { return total + line.length() + 1;});
|
||||
for (LinkSpec &ls : adaptedLinkSpecs)
|
||||
ls.startPos += offset;
|
||||
m_linkSpecs << adaptedLinkSpecs;
|
||||
m_currentTask.details.append(originalLine);
|
||||
|
||||
// Check whether the new line is more relevant than the previous ones.
|
||||
if ((m_currentTask.type != Task::Error && type == Task::Error)
|
||||
|| (m_currentTask.type == Task::Unknown && type != Task::Unknown)) {
|
||||
m_currentTask.type = type;
|
||||
m_currentTask.summary = description;
|
||||
if (!file.isEmpty()) {
|
||||
m_currentTask.setFile(file);
|
||||
m_currentTask.line = line;
|
||||
}
|
||||
}
|
||||
++m_lines;
|
||||
}
|
||||
|
||||
void GccParser::flush()
|
||||
@@ -81,22 +117,19 @@ void GccParser::flush()
|
||||
if (m_currentTask.isNull())
|
||||
return;
|
||||
|
||||
setMonospacedDetailsFormat(m_currentTask);
|
||||
// If there is only one line of details, then it is the line that we generated
|
||||
// the summary from. Remove it, because it does not add any information.
|
||||
if (m_currentTask.details.count() == 1)
|
||||
m_currentTask.details.clear();
|
||||
|
||||
setDetailsFormat(m_currentTask, m_linkSpecs);
|
||||
Task t = m_currentTask;
|
||||
m_currentTask.clear();
|
||||
m_linkSpecs.clear();
|
||||
scheduleTask(t, m_lines, 1);
|
||||
m_lines = 0;
|
||||
}
|
||||
|
||||
void GccParser::amendDescription(const QString &desc)
|
||||
{
|
||||
if (m_currentTask.isNull())
|
||||
return;
|
||||
m_currentTask.details.append(desc);
|
||||
++m_lines;
|
||||
return;
|
||||
}
|
||||
|
||||
OutputLineParser::Result GccParser::handleLine(const QString &line, OutputFormat type)
|
||||
{
|
||||
if (type == StdOutFormat) {
|
||||
@@ -114,7 +147,7 @@ OutputLineParser::Result GccParser::handleLine(const QString &line, OutputFormat
|
||||
|
||||
// Handle misc issues:
|
||||
if (lne.startsWith(QLatin1String("ERROR:")) || lne == QLatin1String("* cpp failed")) {
|
||||
newTask(CompileTask(Task::Error, lne /* description */));
|
||||
createOrAmendTask(Task::Error, lne, lne);
|
||||
return Status::InProgress;
|
||||
}
|
||||
|
||||
@@ -128,10 +161,20 @@ OutputLineParser::Result GccParser::handleLine(const QString &line, OutputFormat
|
||||
} else if (description.startsWith(QLatin1String("fatal: "))) {
|
||||
description = description.mid(7);
|
||||
}
|
||||
newTask(CompileTask(type, description));
|
||||
createOrAmendTask(type, description, lne);
|
||||
return Status::InProgress;
|
||||
}
|
||||
|
||||
match = m_regExpIncluded.match(lne);
|
||||
if (match.hasMatch()) {
|
||||
const FilePath filePath = absoluteFilePath(FilePath::fromUserInput(match.captured(1)));
|
||||
const int lineNo = match.captured(3).toInt();
|
||||
LinkSpecs linkSpecs;
|
||||
addLinkSpecForAbsoluteFilePath(linkSpecs, filePath, lineNo, match, 1);
|
||||
createOrAmendTask(Task::Unknown, lne.trimmed(), lne, false, filePath, lineNo, linkSpecs);
|
||||
return {Status::InProgress, linkSpecs};
|
||||
}
|
||||
|
||||
match = m_regExp.match(lne);
|
||||
if (match.hasMatch()) {
|
||||
int lineno = match.captured(3).toInt();
|
||||
@@ -151,20 +194,12 @@ OutputLineParser::Result GccParser::handleLine(const QString &line, OutputFormat
|
||||
const FilePath filePath = absoluteFilePath(FilePath::fromUserInput(match.captured(1)));
|
||||
LinkSpecs linkSpecs;
|
||||
addLinkSpecForAbsoluteFilePath(linkSpecs, filePath, lineno, match, 1);
|
||||
newTask(CompileTask(type, description, filePath, lineno));
|
||||
createOrAmendTask(type, description, lne, false, filePath, lineno, linkSpecs);
|
||||
return {Status::InProgress, linkSpecs};
|
||||
}
|
||||
|
||||
match = m_regExpIncluded.match(lne);
|
||||
if (match.hasMatch()) {
|
||||
const FilePath filePath = absoluteFilePath(FilePath::fromUserInput(match.captured(1)));
|
||||
const int lineNo = match.captured(3).toInt();
|
||||
LinkSpecs linkSpecs;
|
||||
addLinkSpecForAbsoluteFilePath(linkSpecs, filePath, lineNo, match, 1);
|
||||
newTask(CompileTask(Task::Unknown, lne.trimmed() /* description */, filePath, lineNo));
|
||||
return {Status::InProgress, linkSpecs};
|
||||
} else if (lne.startsWith(' ') && !m_currentTask.isNull()) {
|
||||
amendDescription(lne);
|
||||
if ((lne.startsWith(' ') && !m_currentTask.isNull()) || isContinuation(lne)) {
|
||||
createOrAmendTask(Task::Unknown, lne, lne, true);
|
||||
return Status::InProgress;
|
||||
}
|
||||
|
||||
@@ -172,6 +207,15 @@ OutputLineParser::Result GccParser::handleLine(const QString &line, OutputFormat
|
||||
return Status::NotHandled;
|
||||
}
|
||||
|
||||
bool GccParser::isContinuation(const QString &newLine) const
|
||||
{
|
||||
return !m_currentTask.isNull()
|
||||
&& (m_currentTask.details.last().endsWith(':')
|
||||
|| m_currentTask.details.last().endsWith(',')
|
||||
|| newLine.contains("within this context")
|
||||
|| newLine.contains("note:"));
|
||||
}
|
||||
|
||||
// Unit tests:
|
||||
|
||||
#ifdef WITH_TESTS
|
||||
@@ -590,22 +634,14 @@ void ProjectExplorerPlugin::testGccOutputParsers_data()
|
||||
"C:/Symbian_SDK/epoc32/include/e32cmn.inl:7094: warning: returning reference to temporary")
|
||||
<< OutputParserTester::STDERR
|
||||
<< QString() << QString()
|
||||
<< (Tasks()
|
||||
<< CompileTask(Task::Unknown,
|
||||
"In file included from C:/Symbian_SDK/epoc32/include/e32cmn.h:6792,",
|
||||
FilePath::fromUserInput("C:/Symbian_SDK/epoc32/include/e32cmn.h"),
|
||||
6792)
|
||||
<< CompileTask(Task::Unknown,
|
||||
"from C:/Symbian_SDK/epoc32/include/e32std.h:25,",
|
||||
FilePath::fromUserInput("C:/Symbian_SDK/epoc32/include/e32std.h"),
|
||||
25)
|
||||
<< CompileTask(Task::Unknown,
|
||||
"In member function 'SSecureId::operator const TSecureId&() const':",
|
||||
FilePath::fromUserInput("C:/Symbian_SDK/epoc32/include/e32cmn.inl"))
|
||||
<< CompileTask(Task::Warning,
|
||||
"returning reference to temporary",
|
||||
FilePath::fromUserInput("C:/Symbian_SDK/epoc32/include/e32cmn.inl"),
|
||||
7094))
|
||||
<< Tasks{CompileTask(Task::Warning,
|
||||
"returning reference to temporary\n"
|
||||
"In file included from C:/Symbian_SDK/epoc32/include/e32cmn.h:6792,\n"
|
||||
" from C:/Symbian_SDK/epoc32/include/e32std.h:25,\n"
|
||||
"C:/Symbian_SDK/epoc32/include/e32cmn.inl: In member function 'SSecureId::operator const TSecureId&() const':\n"
|
||||
"C:/Symbian_SDK/epoc32/include/e32cmn.inl:7094: warning: returning reference to temporary",
|
||||
FilePath::fromUserInput("C:/Symbian_SDK/epoc32/include/e32cmn.inl"),
|
||||
7094)}
|
||||
<< QString();
|
||||
|
||||
QTest::newRow("QTCREATORBUG-2206")
|
||||
@@ -624,19 +660,14 @@ void ProjectExplorerPlugin::testGccOutputParsers_data()
|
||||
"/Symbian/SDK/epoc32/include/variant/Symbian_OS.hrh:1134:26: warning: no newline at end of file")
|
||||
<< OutputParserTester::STDERR
|
||||
<< QString() << QString()
|
||||
<< (Tasks()
|
||||
<< CompileTask(Task::Unknown,
|
||||
"In file included from /Symbian/SDK/EPOC32/INCLUDE/GCCE/GCCE.h:15,",
|
||||
FilePath::fromUserInput("/Symbian/SDK/EPOC32/INCLUDE/GCCE/GCCE.h"),
|
||||
15)
|
||||
<< CompileTask(Task::Unknown,
|
||||
"from <command line>:26:",
|
||||
FilePath::fromUserInput("<command line>"),
|
||||
26)
|
||||
<< CompileTask(Task::Warning,
|
||||
"no newline at end of file",
|
||||
FilePath::fromUserInput("/Symbian/SDK/epoc32/include/variant/Symbian_OS.hrh"),
|
||||
1134))
|
||||
<< Tasks{CompileTask(
|
||||
Task::Warning,
|
||||
"no newline at end of file\n"
|
||||
"In file included from /Symbian/SDK/EPOC32/INCLUDE/GCCE/GCCE.h:15,\n"
|
||||
" from <command line>:26:\n"
|
||||
"/Symbian/SDK/epoc32/include/variant/Symbian_OS.hrh:1134:26: warning: no newline at end of file",
|
||||
FilePath::fromUserInput("/Symbian/SDK/epoc32/include/variant/Symbian_OS.hrh"),
|
||||
1134)}
|
||||
<< QString();
|
||||
|
||||
QTest::newRow("Linker fail (release build)")
|
||||
@@ -669,14 +700,12 @@ void ProjectExplorerPlugin::testGccOutputParsers_data()
|
||||
"./mw.h:4:0: warning: \"STUPID_DEFINE\" redefined")
|
||||
<< OutputParserTester::STDERR
|
||||
<< QString() << QString()
|
||||
<< (Tasks()
|
||||
<< CompileTask(Task::Unknown,
|
||||
"In file included from <command-line>:0:0:",
|
||||
FilePath::fromUserInput("<command-line>"))
|
||||
<< CompileTask(Task::Warning,
|
||||
"\"STUPID_DEFINE\" redefined",
|
||||
FilePath::fromUserInput("./mw.h"),
|
||||
4))
|
||||
<< Tasks{CompileTask(
|
||||
Task::Warning,
|
||||
"\"STUPID_DEFINE\" redefined\n"
|
||||
"In file included from <command-line>:0:0:\n"
|
||||
"./mw.h:4:0: warning: \"STUPID_DEFINE\" redefined",
|
||||
FilePath::fromUserInput("./mw.h"), 4)}
|
||||
<< QString();
|
||||
|
||||
QTest::newRow("instanciation with line:column info")
|
||||
@@ -736,17 +765,14 @@ void ProjectExplorerPlugin::testGccOutputParsers_data()
|
||||
" ^")
|
||||
<< OutputParserTester::STDERR
|
||||
<< QString() << QString()
|
||||
<< (Tasks()
|
||||
<< CompileTask(Task::Unknown,
|
||||
"In file included from /home/code/src/creator/src/libs/extensionsystem/pluginerrorview.cpp:31:0:",
|
||||
FilePath::fromUserInput("/home/code/src/creator/src/libs/extensionsystem/pluginerrorview.cpp"),
|
||||
31)
|
||||
<< CompileTask(Task::Error,
|
||||
"QtGui/QAction: No such file or directory\n"
|
||||
" #include <QtGui/QAction>\n"
|
||||
" ^",
|
||||
FilePath::fromUserInput(".uic/ui_pluginerrorview.h"),
|
||||
14))
|
||||
<< Tasks{CompileTask(
|
||||
Task::Error,
|
||||
"QtGui/QAction: No such file or directory\n"
|
||||
"In file included from /home/code/src/creator/src/libs/extensionsystem/pluginerrorview.cpp:31:0:\n"
|
||||
".uic/ui_pluginerrorview.h:14:25: fatal error: QtGui/QAction: No such file or directory\n"
|
||||
" #include <QtGui/QAction>\n"
|
||||
" ^",
|
||||
FilePath::fromUserInput(".uic/ui_pluginerrorview.h"), 14)}
|
||||
<< QString();
|
||||
|
||||
QTest::newRow("qtcreatorbug-9195")
|
||||
@@ -757,26 +783,15 @@ void ProjectExplorerPlugin::testGccOutputParsers_data()
|
||||
"main.cpp:7:22: error: within this context")
|
||||
<< OutputParserTester::STDERR
|
||||
<< QString() << QString()
|
||||
<< (Tasks()
|
||||
<< CompileTask(Task::Unknown,
|
||||
"In file included from /usr/include/qt4/QtCore/QString:1:0,",
|
||||
FilePath::fromUserInput("/usr/include/qt4/QtCore/QString"),
|
||||
1)
|
||||
<< CompileTask(Task::Unknown,
|
||||
"from main.cpp:3:",
|
||||
FilePath::fromUserInput("main.cpp"),
|
||||
3)
|
||||
<< CompileTask(Task::Unknown,
|
||||
"In function 'void foo()':",
|
||||
FilePath::fromUserInput("/usr/include/qt4/QtCore/qstring.h"))
|
||||
<< CompileTask(Task::Error,
|
||||
"'QString::QString(const char*)' is private",
|
||||
FilePath::fromUserInput("/usr/include/qt4/QtCore/qstring.h"),
|
||||
597)
|
||||
<< CompileTask(Task::Error,
|
||||
"within this context",
|
||||
FilePath::fromUserInput("main.cpp"),
|
||||
7))
|
||||
<< Tasks{CompileTask(
|
||||
Task::Error,
|
||||
"'QString::QString(const char*)' is private\n"
|
||||
"In file included from /usr/include/qt4/QtCore/QString:1:0,\n"
|
||||
" from main.cpp:3:\n"
|
||||
"/usr/include/qt4/QtCore/qstring.h: In function 'void foo()':\n"
|
||||
"/usr/include/qt4/QtCore/qstring.h:597:5: error: 'QString::QString(const char*)' is private\n"
|
||||
"main.cpp:7:22: error: within this context",
|
||||
FilePath::fromUserInput("/usr/include/qt4/QtCore/qstring.h"), 597)}
|
||||
<< QString();
|
||||
|
||||
QTest::newRow("ld: Multiple definition error")
|
||||
@@ -1041,71 +1056,55 @@ void ProjectExplorerPlugin::testGccOutputParsers_data()
|
||||
)
|
||||
<< OutputParserTester::STDERR
|
||||
<< QString() << QString()
|
||||
<< Tasks({CompileTask(Task::Unknown,
|
||||
"In file included from /usr/include/qt/QtCore/qlocale.h:43,",
|
||||
FilePath::fromUserInput("/usr/include/qt/QtCore/qlocale.h"), 43),
|
||||
CompileTask(Task::Unknown,
|
||||
"from /usr/include/qt/QtCore/qtextstream.h:46,",
|
||||
FilePath::fromUserInput("/usr/include/qt/QtCore/qtextstream.h"), 46),
|
||||
CompileTask(Task::Unknown,
|
||||
"from /qtc/src/shared/proparser/proitems.cpp:31:",
|
||||
FilePath::fromUserInput("/qtc/src/shared/proparser/proitems.cpp"), 31),
|
||||
CompileTask(Task::Unknown,
|
||||
"In constructor ‘QVariant::QVariant(QVariant&&)’:",
|
||||
FilePath::fromUserInput("/usr/include/qt/QtCore/qvariant.h"), -1),
|
||||
CompileTask(Task::Warning,
|
||||
"implicitly-declared ‘constexpr QVariant::Private& QVariant::Private::operator=(const QVariant::Private&)’ is deprecated [-Wdeprecated-copy]\n"
|
||||
" 273 | { other.d = Private(); }\n"
|
||||
" | ^",
|
||||
FilePath::fromUserInput("/usr/include/qt/QtCore/qvariant.h"), 273),
|
||||
CompileTask(Task::Unknown,
|
||||
"because ‘QVariant::Private’ has user-provided ‘QVariant::Private::Private(const QVariant::Private&)’\n"
|
||||
" 399 | inline Private(const Private &other) Q_DECL_NOTHROW\n"
|
||||
" | ^~~~~~~)",
|
||||
FilePath::fromUserInput("/usr/include/qt/QtCore/qvariant.h"), 399),
|
||||
CompileTask(Task::Unknown,
|
||||
"In function ‘int test(const shape&, const shape&)’:",
|
||||
FilePath::fromUserInput("t.cc"), -1),
|
||||
CompileTask(Task::Error,
|
||||
"no match for ‘operator+’ (operand types are ‘boxed_value<double>’ and ‘boxed_value<double>’)\n"
|
||||
" 14 | return (width(s1) * height(s1)\n"
|
||||
" | ~~~~~~~~~~~~~~~~~~~~~~\n"
|
||||
" | |\n"
|
||||
" | boxed_value<[...]>\n"
|
||||
" 15 | + width(s2) * height(s2));\n"
|
||||
" | ^ ~~~~~~~~~~~~~~~~~~~~~~\n"
|
||||
" | |\n"
|
||||
" | boxed_value<[...]>",
|
||||
FilePath::fromUserInput("t.cc"),
|
||||
15),
|
||||
<< Tasks{CompileTask(Task::Warning,
|
||||
"implicitly-declared ‘constexpr QVariant::Private& QVariant::Private::operator=(const QVariant::Private&)’ is deprecated [-Wdeprecated-copy]\n"
|
||||
"In file included from /usr/include/qt/QtCore/qlocale.h:43,\n"
|
||||
" from /usr/include/qt/QtCore/qtextstream.h:46,\n"
|
||||
" from /qtc/src/shared/proparser/proitems.cpp:31:\n"
|
||||
"/usr/include/qt/QtCore/qvariant.h: In constructor ‘QVariant::QVariant(QVariant&&)’:\n"
|
||||
"/usr/include/qt/QtCore/qvariant.h:273:25: warning: implicitly-declared ‘constexpr QVariant::Private& QVariant::Private::operator=(const QVariant::Private&)’ is deprecated [-Wdeprecated-copy]\n"
|
||||
" 273 | { other.d = Private(); }\n"
|
||||
" | ^\n"
|
||||
"/usr/include/qt/QtCore/qvariant.h:399:16: note: because ‘QVariant::Private’ has user-provided ‘QVariant::Private::Private(const QVariant::Private&)’\n"
|
||||
" 399 | inline Private(const Private &other) Q_DECL_NOTHROW\n"
|
||||
" | ^~~~~~~)",
|
||||
FilePath::fromUserInput("/usr/include/qt/QtCore/qvariant.h"), 273),
|
||||
CompileTask(Task::Unknown, "In function ‘int test(const shape&, const shape&)’:", FilePath::fromUserInput("t.cc")), // TODO: should be matched by GccParser, rather than LdParser
|
||||
CompileTask(Task::Error,
|
||||
"no match for ‘operator+’ (operand types are ‘boxed_value<double>’ and ‘boxed_value<double>’)\n"
|
||||
"t.cc:15:4: error: no match for ‘operator+’ (operand types are ‘boxed_value<double>’ and ‘boxed_value<double>’)\n"
|
||||
" 14 | return (width(s1) * height(s1)\n"
|
||||
" | ~~~~~~~~~~~~~~~~~~~~~~\n"
|
||||
" | |\n"
|
||||
" | boxed_value<[...]>\n"
|
||||
" 15 | + width(s2) * height(s2));\n"
|
||||
" | ^ ~~~~~~~~~~~~~~~~~~~~~~\n"
|
||||
" | |\n"
|
||||
" | boxed_value<[...]>",
|
||||
FilePath::fromUserInput("t.cc"),
|
||||
15),
|
||||
CompileTask(Task::Error,
|
||||
"‘string’ in namespace ‘std’ does not name a type\n"
|
||||
"incomplete.c:1:6: error: ‘string’ in namespace ‘std’ does not name a type\n"
|
||||
" 1 | std::string test(void)\n"
|
||||
" | ^~~~~~",
|
||||
FilePath::fromUserInput("incomplete.c"),
|
||||
1),
|
||||
CompileTask(Task::Unknown,
|
||||
"‘std::string’ is defined in header ‘<string>’; did you forget to ‘#include <string>’?\n"
|
||||
" | ^~~~~~\n"
|
||||
"incomplete.c:1:1: note: ‘std::string’ is defined in header ‘<string>’; did you forget to ‘#include <string>’?\n"
|
||||
" +++ |+#include <string>\n"
|
||||
" 1 | std::string test(void)",
|
||||
FilePath::fromUserInput("incomplete.c"),
|
||||
1),
|
||||
CompileTask(Task::Unknown,
|
||||
"In function ‘caller’:",
|
||||
FilePath::fromUserInput("param-type-mismatch.c"),
|
||||
-1),
|
||||
CompileTask(Task::Unknown, "In function ‘caller’:", FilePath::fromUserInput("param-type-mismatch.c")), // TODO: should be matched by GccParser, rather than LdParser
|
||||
CompileTask(Task::Warning,
|
||||
"passing argument 2 of ‘callee’ makes pointer from integer without a cast [-Wint-conversion]\n"
|
||||
"param-type-mismatch.c:5:24: warning: passing argument 2 of ‘callee’ makes pointer from integer without a cast [-Wint-conversion]\n"
|
||||
" 5 | return callee(first, second, third);\n"
|
||||
" | ^~~~~~\n"
|
||||
" | |\n"
|
||||
" | int",
|
||||
FilePath::fromUserInput("param-type-mismatch.c"), 5),
|
||||
CompileTask(Task::Unknown,
|
||||
"expected ‘const char *’ but argument is of type ‘int’\n"
|
||||
" | int\n"
|
||||
"param-type-mismatch.c:1:40: note: expected ‘const char *’ but argument is of type ‘int’\n"
|
||||
" 1 | extern int callee(int one, const char *two, float three);\n"
|
||||
" | ~~~~~~~~~~~~^~~",
|
||||
FilePath::fromUserInput("param-type-mismatch.c"), 1)})
|
||||
FilePath::fromUserInput("param-type-mismatch.c"), 5)}
|
||||
<< QString();
|
||||
}
|
||||
|
||||
|
||||
@@ -45,19 +45,28 @@ public:
|
||||
static QList<OutputLineParser *> gccParserSuite();
|
||||
|
||||
protected:
|
||||
void newTask(const Task &task);
|
||||
void createOrAmendTask(
|
||||
Task::TaskType type,
|
||||
const QString &description,
|
||||
const QString &originalLine,
|
||||
bool forceAmend = false,
|
||||
const Utils::FilePath &file = {},
|
||||
int line = -1,
|
||||
const LinkSpecs &linkSpecs = {}
|
||||
);
|
||||
void flush() override;
|
||||
|
||||
void amendDescription(const QString &desc);
|
||||
|
||||
private:
|
||||
Result handleLine(const QString &line, Utils::OutputFormat type) override;
|
||||
|
||||
bool isContinuation(const QString &newLine) const;
|
||||
|
||||
QRegularExpression m_regExp;
|
||||
QRegularExpression m_regExpIncluded;
|
||||
QRegularExpression m_regExpGccNames;
|
||||
|
||||
Task m_currentTask;
|
||||
LinkSpecs m_linkSpecs;
|
||||
int m_lines = 0;
|
||||
};
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
|
||||
#include <texteditor/fontsettings.h>
|
||||
#include <texteditor/texteditorsettings.h>
|
||||
#include <utils/ansiescapecodehandler.h>
|
||||
|
||||
|
||||
/*!
|
||||
@@ -94,16 +95,22 @@ void OutputTaskParser::scheduleTask(const Task &task, int outputLines, int skipp
|
||||
QTC_CHECK(d->scheduledTasks.size() <= 2);
|
||||
}
|
||||
|
||||
void OutputTaskParser::setMonospacedDetailsFormat(Task &task)
|
||||
void OutputTaskParser::setDetailsFormat(Task &task, const LinkSpecs &linkSpecs)
|
||||
{
|
||||
if (task.details.isEmpty())
|
||||
return;
|
||||
QTextLayout::FormatRange fr;
|
||||
fr.start = task.summary.length() + 1;
|
||||
fr.length = task.details.join('\n').length();
|
||||
fr.format.setFont(TextEditor::TextEditorSettings::fontSettings().font());
|
||||
fr.format.setFontStyleHint(QFont::Monospace);
|
||||
task.formats = {fr};
|
||||
|
||||
Utils::FormattedText monospacedText(task.details.join('\n'));
|
||||
monospacedText.format.setFont(TextEditor::TextEditorSettings::fontSettings().font());
|
||||
monospacedText.format.setFontStyleHint(QFont::Monospace);
|
||||
const QList<Utils::FormattedText> linkifiedText =
|
||||
Utils::OutputFormatter::linkifiedText({monospacedText}, linkSpecs);
|
||||
task.formats.clear();
|
||||
int offset = task.summary.length() + 1;
|
||||
for (const Utils::FormattedText &ft : linkifiedText) {
|
||||
task.formats << QTextLayout::FormatRange{offset, ft.text.length(), ft.format};
|
||||
offset += ft.text.length();
|
||||
}
|
||||
}
|
||||
|
||||
void OutputTaskParser::runPostPrintActions()
|
||||
|
||||
@@ -54,7 +54,7 @@ public:
|
||||
|
||||
protected:
|
||||
void scheduleTask(const Task &task, int outputLines, int skippedLines = 0);
|
||||
void setMonospacedDetailsFormat(Task &task);
|
||||
void setDetailsFormat(Task &task, const LinkSpecs &linkSpecs = {});
|
||||
|
||||
private:
|
||||
void runPostPrintActions() override;
|
||||
|
||||
@@ -125,7 +125,7 @@ void LinuxIccParser::flush()
|
||||
if (m_temporary.isNull())
|
||||
return;
|
||||
|
||||
setMonospacedDetailsFormat(m_temporary);
|
||||
setDetailsFormat(m_temporary);
|
||||
Task t = m_temporary;
|
||||
m_temporary.clear();
|
||||
scheduleTask(t, m_lines, 1);
|
||||
|
||||
@@ -176,7 +176,7 @@ void MsvcParser::flush()
|
||||
if (m_lastTask.isNull())
|
||||
return;
|
||||
|
||||
setMonospacedDetailsFormat(m_lastTask);
|
||||
setDetailsFormat(m_lastTask);
|
||||
Task t = m_lastTask;
|
||||
m_lastTask.clear();
|
||||
scheduleTask(t, m_lines, 1);
|
||||
|
||||
@@ -34,13 +34,15 @@
|
||||
|
||||
#include <coreplugin/actionmanager/actionmanager.h>
|
||||
#include <coreplugin/actionmanager/command.h>
|
||||
#include <coreplugin/editormanager/editormanager.h>
|
||||
#include <coreplugin/icore.h>
|
||||
#include <coreplugin/icontext.h>
|
||||
|
||||
#include <utils/algorithm.h>
|
||||
#include <utils/fileinprojectfinder.h>
|
||||
#include <utils/qtcassert.h>
|
||||
#include <utils/itemviews.h>
|
||||
#include <utils/outputformatter.h>
|
||||
#include <utils/qtcassert.h>
|
||||
#include <utils/utilsicons.h>
|
||||
|
||||
#include <QDir>
|
||||
@@ -77,7 +79,23 @@ class TaskView : public Utils::ListView
|
||||
public:
|
||||
TaskView(QWidget *parent = nullptr);
|
||||
~TaskView() override;
|
||||
|
||||
private:
|
||||
void resizeEvent(QResizeEvent *e) override;
|
||||
void mousePressEvent(QMouseEvent *e) override;
|
||||
void mouseReleaseEvent(QMouseEvent *e) override;
|
||||
void mouseMoveEvent(QMouseEvent *e) override;
|
||||
|
||||
class Location {
|
||||
public:
|
||||
Utils::FilePath file;
|
||||
int line;
|
||||
int column;
|
||||
};
|
||||
Location locationForPos(const QPoint &pos);
|
||||
|
||||
bool m_linksActive = true;
|
||||
Qt::MouseButton m_mouseButtonPressed = Qt::NoButton;
|
||||
};
|
||||
|
||||
class TaskWindowContext : public Core::IContext
|
||||
@@ -103,11 +121,14 @@ public:
|
||||
|
||||
void currentChanged(const QModelIndex ¤t, const QModelIndex &previous);
|
||||
|
||||
QString hrefForPos(const QPointF &pos);
|
||||
|
||||
private:
|
||||
void generateGradientPixmap(int width, int height, QColor color, bool selected) const;
|
||||
|
||||
mutable int m_cachedHeight = 0;
|
||||
mutable QFont m_cachedFont;
|
||||
mutable QList<QPair<QRectF, QString>> m_hrefs;
|
||||
|
||||
/*
|
||||
Collapsed:
|
||||
@@ -186,6 +207,7 @@ TaskView::TaskView(QWidget *parent)
|
||||
{
|
||||
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
|
||||
setMouseTracking(true);
|
||||
|
||||
QFontMetrics fm(font());
|
||||
int vStepSize = fm.height() + 3;
|
||||
@@ -203,6 +225,54 @@ void TaskView::resizeEvent(QResizeEvent *e)
|
||||
static_cast<TaskDelegate *>(itemDelegate())->emitSizeHintChanged(selectionModel()->currentIndex());
|
||||
}
|
||||
|
||||
void TaskView::mousePressEvent(QMouseEvent *e)
|
||||
{
|
||||
m_mouseButtonPressed = e->button();
|
||||
ListView::mousePressEvent(e);
|
||||
}
|
||||
|
||||
void TaskView::mouseReleaseEvent(QMouseEvent *e)
|
||||
{
|
||||
if (m_linksActive && m_mouseButtonPressed == Qt::LeftButton) {
|
||||
const Location loc = locationForPos(e->pos());
|
||||
if (!loc.file.isEmpty())
|
||||
Core::EditorManager::openEditorAt(loc.file.toString(), loc.line, loc.column);
|
||||
}
|
||||
|
||||
// Mouse was released, activate links again
|
||||
m_linksActive = true;
|
||||
m_mouseButtonPressed = Qt::NoButton;
|
||||
ListView::mouseReleaseEvent(e);
|
||||
}
|
||||
|
||||
void TaskView::mouseMoveEvent(QMouseEvent *e)
|
||||
{
|
||||
// Cursor was dragged, deactivate links
|
||||
if (m_mouseButtonPressed != Qt::NoButton)
|
||||
m_linksActive = false;
|
||||
|
||||
viewport()->setCursor(m_linksActive && !locationForPos(e->pos()).file.isEmpty()
|
||||
? Qt::PointingHandCursor : Qt::ArrowCursor);
|
||||
ListView::mouseMoveEvent(e);
|
||||
}
|
||||
|
||||
TaskView::Location TaskView::locationForPos(const QPoint &pos)
|
||||
{
|
||||
const auto delegate = qobject_cast<TaskDelegate *>(itemDelegate(indexAt(pos)));
|
||||
if (!delegate)
|
||||
return {};
|
||||
Utils::OutputFormatter formatter;
|
||||
Location loc;
|
||||
connect(&formatter, &Utils::OutputFormatter::openInEditorRequested, this,
|
||||
[&loc](const Utils::FilePath &fp, int line, int column) {
|
||||
loc.file = fp;
|
||||
loc.line = line;
|
||||
loc.column = column;
|
||||
});
|
||||
formatter.handleLink(delegate->hrefForPos(pos));
|
||||
return loc;
|
||||
}
|
||||
|
||||
/////
|
||||
// TaskWindow
|
||||
/////
|
||||
@@ -764,6 +834,15 @@ void TaskDelegate::currentChanged(const QModelIndex ¤t, const QModelIndex
|
||||
emit sizeHintChanged(previous);
|
||||
}
|
||||
|
||||
QString TaskDelegate::hrefForPos(const QPointF &pos)
|
||||
{
|
||||
for (const auto &link : m_hrefs) {
|
||||
if (link.first.contains(pos))
|
||||
return link.second;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
void TaskDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
|
||||
{
|
||||
QStyleOptionViewItem opt = option;
|
||||
@@ -825,7 +904,10 @@ void TaskDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
|
||||
int height = 0;
|
||||
description.replace(QLatin1Char('\n'), QChar::LineSeparator);
|
||||
QTextLayout tl(description);
|
||||
tl.setFormats(index.data(TaskModel::Task_t).value<Task>().formats);
|
||||
QVector<QTextLayout::FormatRange> formats = index.data(TaskModel::Task_t).value<Task>().formats;
|
||||
for (QTextLayout::FormatRange &format : formats)
|
||||
format.format.setForeground(opt.palette.highlightedText());
|
||||
tl.setFormats(formats);
|
||||
tl.beginLayout();
|
||||
while (true) {
|
||||
QTextLine line = tl.createLine();
|
||||
@@ -837,7 +919,33 @@ void TaskDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
|
||||
height += static_cast<int>(line.height());
|
||||
}
|
||||
tl.endLayout();
|
||||
const QPoint indexPos = view->visualRect(index).topLeft();
|
||||
tl.draw(painter, QPoint(positions.textAreaLeft(), positions.top()));
|
||||
m_hrefs.clear();
|
||||
for (const auto &range : tl.formats()) {
|
||||
if (!range.format.isAnchor())
|
||||
continue;
|
||||
const QTextLine &firstLinkLine = tl.lineForTextPosition(range.start);
|
||||
const QTextLine &lastLinkLine = tl.lineForTextPosition(range.start + range.length - 1);
|
||||
for (int i = firstLinkLine.lineNumber(); i <= lastLinkLine.lineNumber(); ++i) {
|
||||
const QTextLine &linkLine = tl.lineAt(i);
|
||||
if (!linkLine.isValid())
|
||||
break;
|
||||
const QPointF linePos = linkLine.position();
|
||||
const int linkStartPos = i == firstLinkLine.lineNumber()
|
||||
? range.start : linkLine.textStart();
|
||||
const qreal startOffset = linkLine.cursorToX(linkStartPos);
|
||||
const int linkEndPos = i == lastLinkLine.lineNumber()
|
||||
? range.start + range.length
|
||||
: linkLine.textStart() + linkLine.textLength();
|
||||
const qreal endOffset = linkLine.cursorToX(linkEndPos);
|
||||
const QPointF linkPos(indexPos.x() + positions.textAreaLeft() + linePos.x()
|
||||
+ startOffset, positions.top() + linePos.y());
|
||||
const QSize linkSize(endOffset - startOffset, linkLine.height());
|
||||
const QRectF linkRect(linkPos, linkSize);
|
||||
m_hrefs << qMakePair(linkRect, range.format.anchorHref());
|
||||
}
|
||||
}
|
||||
|
||||
QColor mix;
|
||||
mix.setRgb( static_cast<int>(0.7 * textColor.red() + 0.3 * backgroundColor.red()),
|
||||
|
||||
Reference in New Issue
Block a user