From cb203dc49cb2943f69ba549fb369f5d5f89878e7 Mon Sep 17 00:00:00 2001 From: Christian Kandeler Date: Wed, 13 Jan 2021 14:23:25 +0100 Subject: [PATCH 1/8] CppTools: Do not wait for QFuture in ~BuiltinEditorDocumentProcessor() This seems safe, as the arguments to the async function that the future results from are values and shared pointers, so there does not appear to be a need for this object to stay around while it finishes. (cherry-picked from commit fd22787a69c579a03bd114f98930ccadaaed5f71) Fixes: QTCREATORBUG-25121 Change-Id: Ib498551856942bf7c3d05c3013e12ad6d90fd762 Reviewed-by: Cristian Adam --- src/plugins/cpptools/builtineditordocumentprocessor.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/plugins/cpptools/builtineditordocumentprocessor.cpp b/src/plugins/cpptools/builtineditordocumentprocessor.cpp index ce35d95b77c..69ae2969b9f 100644 --- a/src/plugins/cpptools/builtineditordocumentprocessor.cpp +++ b/src/plugins/cpptools/builtineditordocumentprocessor.cpp @@ -202,7 +202,6 @@ BuiltinEditorDocumentProcessor::BuiltinEditorDocumentProcessor( BuiltinEditorDocumentProcessor::~BuiltinEditorDocumentProcessor() { m_parserFuture.cancel(); - m_parserFuture.waitForFinished(); } void BuiltinEditorDocumentProcessor::runImpl( From 18d73f132e059c1b32a8180d8a69bc38d4e1f02a Mon Sep 17 00:00:00 2001 From: Christian Stenger Date: Tue, 15 Sep 2020 15:28:21 +0200 Subject: [PATCH 2/8] Squish: Make helper tool python 3 ready Change-Id: I42f59a6819467209d46e3830aff6897861c548ef Reviewed-by: Robert Loehning --- tests/system/tools/findUnusedObjects.py | 52 +++++++++++++++++++------ tests/system/tools/toolfunctions.py | 6 ++- 2 files changed, 44 insertions(+), 14 deletions(-) diff --git a/tests/system/tools/findUnusedObjects.py b/tests/system/tools/findUnusedObjects.py index 1b2869bf9e6..258674a4aa6 100755 --- a/tests/system/tools/findUnusedObjects.py +++ b/tests/system/tools/findUnusedObjects.py @@ -33,10 +33,12 @@ from optparse import OptionParser from toolfunctions import checkDirectory from toolfunctions import getFileContent + objMap = None lastToken = [None, None] stopTokens = ('OP', 'NAME', 'NUMBER', 'ENDMARKER') + def parseCommandLine(): global directory, onlyRemovable, sharedFolders, deleteObjects parser = OptionParser("\n%prog [OPTIONS] [DIRECTORY]") @@ -54,7 +56,7 @@ def parseCommandLine(): elif len(args) == 1: directory = os.path.abspath(args[0]) else: - print "\nERROR: Too many arguments\n" + print("\nERROR: Too many arguments\n") parser.print_help() sys.exit(1) onlyRemovable = options.onlyRemovable @@ -70,6 +72,7 @@ def collectObjects(): data = getFileContent(objMap) return map(lambda x: x.strip().split("\t", 1)[0], data.strip().splitlines()) + def handleStringsWithTrailingBackSlash(origStr): try: while True: @@ -78,8 +81,11 @@ def handleStringsWithTrailingBackSlash(origStr): except: return origStr -def handle_token(tokenType, token, (startRow, startCol), (endRow, endCol), line): + +def handle_token(tokenType, token, startPos, endPos, line): global useCounts, lastToken, stopTokens + (startRow, startCol) = startPos + (endRow, endCol) = endPos if tokenize.tok_name[tokenType] == 'STRING': # concatenate strings followed directly by other strings @@ -87,7 +93,7 @@ def handle_token(tokenType, token, (startRow, startCol), (endRow, endCol), line) token = "'" + lastToken[1][1:-1] + str(token)[1:-1] + "'" # store the new string as lastToken after removing potential trailing backslashes # (including their following indentation) - lastToken = ['STRING' , handleStringsWithTrailingBackSlash(str(token))] + lastToken = ['STRING', handleStringsWithTrailingBackSlash(str(token))] # if a stop token occurs check the potential string before it elif tokenize.tok_name[tokenType] in stopTokens: if lastToken[0] == 'STRING': @@ -97,17 +103,22 @@ def handle_token(tokenType, token, (startRow, startCol), (endRow, endCol), line) # store the stop token as lastToken lastToken = [tokenize.tok_name[tokenType], str(token)] + def handleDataFiles(openFile, separator): global useCounts # ignore header line - openFile.readline() + first = True for line in openFile: + if first: + first = False + continue currentTokens = line.split(separator) for token in currentTokens: stripped = token.strip().strip('"') if stripped in useCounts: useCounts[stripped] = useCounts[stripped] + 1 + def findUsages(): global directory, objMap, sharedFolders suffixes = (".py", ".csv", ".tsv") @@ -130,36 +141,51 @@ def findUsages(): for directory in directories: for root, dirnames, filenames in os.walk(directory): for filename in filter(lambda x: x.endswith(suffixes), filenames): - currentFile = open(os.path.join(root, filename)) + if sys.version_info.major == 2: + currentFile = open(os.path.join(root, filename), "r") + else: + currentFile = open(os.path.join(root, filename), "r", encoding="utf8") if filename.endswith(".py"): - tokenize.tokenize(currentFile.readline, handle_token) + if sys.version_info.major == 2: + tokenize.tokenize(currentFile.readline, handle_token) + else: + tokens = tokenize.generate_tokens(currentFile.readline) + for token in tokens: + handle_token(token.type, token.string, token.start, token.end, token.line) elif filename.endswith(".csv"): handleDataFiles(currentFile, ",") elif filename.endswith(".tsv"): handleDataFiles(currentFile, "\t") currentFile.close() currentFile = open(objMap) - tokenize.tokenize(currentFile.readline, handle_token) + if sys.version_info.major == 2: + tokenize.tokenize(currentFile.readline, handle_token) + else: + tokens = tokenize.generate_tokens(currentFile.readline) + for token in tokens: + handle_token(token.type, token.string, token.start, token.end, token.line) currentFile.close() + def printResult(): global useCounts, onlyRemovable print if onlyRemovable: if min(useCounts.values()) > 0: - print "All objects are used once at least.\n" + print("All objects are used once at least.\n") return False - print "Unused objects:\n" + print("Unused objects:\n") for obj in filter(lambda x: useCounts[x] == 0, useCounts): - print "%s" % obj + print("%s" % obj) return True else: outFormat = "%3d %s" - for obj,useCount in useCounts.iteritems(): - print outFormat % (useCount, obj) + for obj, useCount in useCounts.items(): + print(outFormat % (useCount, obj)) print return None + def deleteRemovable(): global useCounts, objMap @@ -192,6 +218,7 @@ def deleteRemovable(): print("Deleted %d items, old objects.map has been moved to objects.map~" % count) return count > 0 + def main(): global useCounts, objMap, deleteObjects objMap = checkDirectory(directory) @@ -209,6 +236,7 @@ def main(): print(mssg + "to find objects that might have been referenced only by removed objects.\n") return 0 + if __name__ == '__main__': parseCommandLine() sys.exit(main()) diff --git a/tests/system/tools/toolfunctions.py b/tests/system/tools/toolfunctions.py index 91cff2b59be..ff9cd6c5751 100644 --- a/tests/system/tools/toolfunctions.py +++ b/tests/system/tools/toolfunctions.py @@ -26,16 +26,18 @@ import os import sys + def checkDirectory(directory): if not os.path.exists(directory): - print "Given path '%s' does not exist" % directory + print("Given path '%s' does not exist" % directory) sys.exit(1) objMap = os.path.join(directory, "objects.map") if not os.path.exists(objMap): - print "Given path '%s' does not contain an objects.map file" % directory + print("Given path '%s' does not contain an objects.map file" % directory) sys.exit(1) return objMap + def getFileContent(filePath): if os.path.isfile(filePath): f = open(filePath, "r") From 122fdf6e5aab22a8241ca9cff6a04adf24c0cb71 Mon Sep 17 00:00:00 2001 From: Christian Stenger Date: Mon, 28 Sep 2020 08:54:12 +0200 Subject: [PATCH 3/8] Squish: Make another helper tool compatible Change-Id: I1fc4e268e311c628b541b76ae3a948854635a4a5 Reviewed-by: Robert Loehning --- tests/system/tools/objectsToTable.py | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/tests/system/tools/objectsToTable.py b/tests/system/tools/objectsToTable.py index 430e3cee2d8..bce82ef4a8d 100755 --- a/tests/system/tools/objectsToTable.py +++ b/tests/system/tools/objectsToTable.py @@ -30,6 +30,9 @@ import sys from optparse import OptionParser from toolfunctions import checkDirectory from toolfunctions import getFileContent +if sys.version_info.major == 3: + from functools import reduce + def parseCommandLine(): global directory, tsv @@ -43,11 +46,12 @@ def parseCommandLine(): elif len(args) == 1: directory = os.path.abspath(args[0]) else: - print "\nERROR: Too many arguments\n" + print("\nERROR: Too many arguments\n") parser.print_help() sys.exit(1) tsv = options.tsv + def readProperties(line): def readOneProperty(rawProperties): name, rawProperties = rawProperties.split("=", 1) @@ -64,6 +68,7 @@ def readProperties(line): properties[name] = value return objectName, properties + def main(): global directory, tsv objMap = checkDirectory(directory) @@ -71,23 +76,24 @@ def main(): # Which properties have been used at least once? eachObjectsProperties = [set(properties.keys()) for properties in objects.values()] - usedProperties = list(reduce(lambda x,y: x | y, eachObjectsProperties)) + usedProperties = list(reduce(lambda x, y: x | y, eachObjectsProperties)) if tsv: - print "\t".join(["Squish internal name"] + usedProperties) + print("\t".join(["Squish internal name"] + usedProperties)) for name, properties in objects.items(): values = [name] + map(lambda x: properties.setdefault(x, ""), usedProperties) - print "\t".join(values) + print("\t".join(values)) else: maxPropertyLength = max(map(len, usedProperties)) for name, properties in objects.items(): - print "Squish internal name: %s" % name - print "Properties:" + print("Squish internal name: %s" % name) + print("Properties:") for key, val in properties.items(): - print "%s: %s" % (key.rjust(maxPropertyLength + 4), val) - print + print("%s: %s" % (key.rjust(maxPropertyLength + 4), val)) + print() return 0 + if __name__ == '__main__': parseCommandLine() sys.exit(main()) From aa404ca5c935afc1a313e2d9b168a6f53fee7667 Mon Sep 17 00:00:00 2001 From: Tim Jenssen Date: Mon, 18 Jan 2021 14:32:05 +0100 Subject: [PATCH 4/8] doc: fix menu entry MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Change-Id: I21d1ba77b2f4a39756f6408e6911192237e7e54c Reviewed-by: Henning Gründl Reviewed-by: Leena Miettinen --- doc/qtcreator/src/qtquick/qt-design-viewer.qdoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/qtcreator/src/qtquick/qt-design-viewer.qdoc b/doc/qtcreator/src/qtquick/qt-design-viewer.qdoc index 0bd0f8a2e96..cdc8891b56e 100644 --- a/doc/qtcreator/src/qtquick/qt-design-viewer.qdoc +++ b/doc/qtcreator/src/qtquick/qt-design-viewer.qdoc @@ -46,7 +46,7 @@ indistinguishable from the same application running on the desktop. \if defined(qtdesignstudio) - To create a resource file out of your project, select \uicontrol Tools > + To create a resource file out of your project, select \uicontrol Build > \uicontrol {Generate Resource File} in \QC. Then upload the package into \QDV. \else From 80a187029246d950f9c0523cee8b9bd687b45d25 Mon Sep 17 00:00:00 2001 From: David Schulz Date: Fri, 15 Jan 2021 14:16:05 +0100 Subject: [PATCH 5/8] Debugger: log the debugger exitcode also in hex Change-Id: Ia9742bfa9c36c7ff4fdd965f2a32ea36a4239422 Reviewed-by: hjk --- src/plugins/debugger/debuggerengine.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/plugins/debugger/debuggerengine.cpp b/src/plugins/debugger/debuggerengine.cpp index 6d7ec190bac..2f62093cb08 100644 --- a/src/plugins/debugger/debuggerengine.cpp +++ b/src/plugins/debugger/debuggerengine.cpp @@ -1773,8 +1773,11 @@ void DebuggerEngine::showMessage(const QString &msg, int channel, int timeout) c void DebuggerEngine::notifyDebuggerProcessFinished(int exitCode, QProcess::ExitStatus exitStatus, const QString &backendName) { - showMessage(QString("%1 PROCESS FINISHED, status %2, exit code %3") - .arg(backendName).arg(exitStatus).arg(exitCode)); + showMessage(QString("%1 PROCESS FINISHED, status %2, exit code %3 (0x%4)") + .arg(backendName) + .arg(exitStatus) + .arg(exitCode) + .arg(QString::number(exitCode, 16))); switch (state()) { case DebuggerFinished: From 41ff60a6932c89a32d5e4213ba3c600d939ba7fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kama=20W=C3=B3jcik?= Date: Wed, 23 Dec 2020 16:33:08 +0100 Subject: [PATCH 6/8] QmlDesigner: Paint single event id label * Paint red label on the arrow with event id if only one event is connected * Cleanup label painting for multiple events * General code cleanup Task-number: QDS-3481 Change-Id: I9652c1767ccaeb07b03c35e0fd4fec41d496c210 Reviewed-by: Thomas Hartmann --- .../components/formeditor/formeditoritem.cpp | 283 ++++++++++++++---- .../components/formeditor/formeditoritem.h | 7 + 2 files changed, 226 insertions(+), 64 deletions(-) diff --git a/src/plugins/qmldesigner/components/formeditor/formeditoritem.cpp b/src/plugins/qmldesigner/components/formeditor/formeditoritem.cpp index 09c19a7a84c..807029aacbd 100644 --- a/src/plugins/qmldesigner/components/formeditor/formeditoritem.cpp +++ b/src/plugins/qmldesigner/components/formeditor/formeditoritem.cpp @@ -56,7 +56,10 @@ const int flowBlockSize = 200; const int blockRadius = 18; const int blockAdjust = 40; const int startItemOffset = 96; -const int labelFontSize = 16; + +const qreal fontSize = 10; // points +const qreal zoomLevelLabel = 0.5; // Everything lower than that will hide all labels +const qreal defaultDpi = 96.0; void drawIcon(QPainter *painter, int x, @@ -882,7 +885,6 @@ public: , bezier(50) , type(ConnectionType::Default) , label() - , fontSize(labelFontSize / scaleFactor) , labelOffset(14 / scaleFactor) , labelPosition(50.0) , labelFlags(Qt::AlignHCenter | Qt::AlignVCenter | Qt::TextDontClip) @@ -898,7 +900,7 @@ public: if (node.modelNode().isSelected()) width += 2; if (hitTest) - width = width * 8 / scaleFactor; + width = width * 8 / scaleFactor; // color if (resolveConnection.isStartLine) color = QColor("blue"); @@ -944,8 +946,6 @@ public: label = node.modelNode().bindingProperty("condition").expression(); if (node.modelNode().hasVariantProperty("question")) label = node.modelNode().variantProperty("question").value().toString(); - // font size - // label offset // label position @@ -960,7 +960,6 @@ public: if (node.modelNode().hasVariantProperty(eventsName)) events = node.modelNode().variantProperty(eventsName).value().toString(); - } qreal width; @@ -980,7 +979,6 @@ public: int bezier; ConnectionType type; QString label; - int fontSize; qreal labelOffset; qreal labelPosition; int labelFlags; @@ -1284,7 +1282,7 @@ public: const bool boolExitRight = fromRect.right() < toRect.center().x(); const bool boolExitBottom = fromRect.bottom() < toRect.center().y(); - const int padding = 4 * config.adjustedWidth; + const qreal padding = 8; if (horizontalFirst) { const qreal startX = boolExitRight ? fromRect.right() + padding : fromRect.x() - padding; @@ -1404,11 +1402,14 @@ void FormEditorTransitionItem::updateGeometry() QPixmap pixmap(640, 480); QPainter localPainter(&pixmap); QFont font = localPainter.font(); - font.setPixelSize(config.fontSize); + font.setPointSizeF(getFontSize(&localPainter) / getScaleFactor()); localPainter.setFont(font); - for (const auto &from : resolved.from) { - for (const auto &to : resolved.to) { + const auto fromNodes = resolved.from; + const auto toNodes = resolved.to; + + for (const auto &from : fromNodes) { + for (const auto &to : toNodes) { Connection connection(resolved, pos(), from, to, config); // Just add the configured transition width to the bounding box to make sure the BB is not cutting @@ -1434,31 +1435,13 @@ QPointF FormEditorTransitionItem::instancePosition() const void FormEditorTransitionItem::drawLabels(QPainter *painter, const Connection &connection) { - // draw label with event ids - if (connection.config.isSelected && !connection.config.events.isEmpty()) - { - qreal offset = connection.config.labelOffset; - QStringList events = connection.config.events.split(','); - int fontSize = connection.config.fontSize; - QString outputText; - qreal minWidth = offset * 12.0; - qreal letterWidth = fontSize * 0.6; // assumption on max letter width - int eventCount = events.size(); - std::for_each(events.begin(), events.end(), [&](auto id) { - outputText.append(id.trimmed()); - outputText.append('\n'); - if (minWidth < id.size() * letterWidth) minWidth = id.size() * letterWidth; - }); - const QPointF pos = connection.path.pointAtPercent(0.0); - painter->save(); - painter->setBrush(QColor(70, 70, 70, 200)); - painter->setPen(Qt::lightGray); - painter->drawRoundedRect(pos.x(), pos.y() + offset, minWidth, 1.5 * fontSize * eventCount + offset * 4.0, offset / 2.0, offset / 2.0); - painter->drawText(pos.x(), pos.y() + 2.0 * offset, minWidth, offset * 2.0, Qt::AlignHCenter, QObject::tr("Connected Events")); - painter->drawLine(pos.x() + offset, pos.y() + 4.0 * offset, pos.x() + minWidth - offset, pos.y() + offset * 4.0); - painter->drawText(pos.x() + offset, pos.y() + 4.0 * offset, minWidth - offset, 1.5 * fontSize * eventCount, Qt::AlignLeft, outputText); - painter->restore(); - } + drawSingleEventIdLabel(painter, connection); + drawEventIdsLabel(painter, connection); + drawGeneralLabel(painter, connection); +} + +void FormEditorTransitionItem::drawGeneralLabel(QPainter *painter, const Connection &connection) +{ if (connection.config.label.isEmpty()) return; @@ -1481,11 +1464,166 @@ void FormEditorTransitionItem::drawLabels(QPainter *painter, const Connection &c painter->restore(); } +void FormEditorTransitionItem::drawSingleEventIdLabel(QPainter *painter, const Connection &connection) +{ + if (connection.config.events.isEmpty() || connection.config.events.split(",").size() != 1) + return; + + QPointF position; + qreal angle; + + const qreal hMargin = 10.0 / getScaleFactor(); + QFontMetrics metric(painter->font()); + QRectF textRect = metric.boundingRect(connection.config.events); + textRect.adjust(-hMargin, 0.0, hMargin, 0.0); + const qreal halfHeight = textRect.height() / 2.0; + + if (connection.config.type == ConnectionType::Bezier) { + position = connection.path.pointAtPercent(0.5); + angle = connection.path.angleAtPercent(0.5); + textRect.moveCenter(position); + } else { + const QLineF start(connection.start, connection.mid1); + const QLineF mid(connection.mid1, connection.mid2); + const QLineF end(connection.mid2, connection.end); + QVector pathLines = {start, mid, end}; + + std::sort(pathLines.begin(), pathLines.end(), [](const QLineF &a, const QLineF &b) { + return a.length() > b.length() && (a.angle() == 0 || a.angle() == 180); + }); + + // Calculate font bounding box without taking into account the cale factor + QFont originalFont = painter->font(); + originalFont.setPointSizeF(getFontSize(painter)); + QFontMetrics originalMetric(originalFont); + QRectF originalTextRect = originalMetric.boundingRect(connection.config.events); + originalTextRect.adjust(-10.0, 0.0, 10.0, 0.0); + + const qreal width = originalTextRect.width(); // original width + + QVector candidates; + candidates.reserve(3); + std::copy_if(pathLines.begin(), pathLines.end(), + std::back_inserter(candidates), + [width](const QLineF &line) { return line.length() > width; }); + + const QLineF tmp = candidates.empty() ? start : candidates.first(); + angle = tmp.angle(); + + if (tmp.p1() == start.p1()) { + if (angle == 0) { + position = tmp.p2() + QPointF(-connection.config.radius, halfHeight); + textRect.moveBottomRight(position); + } else if (angle == 90) { + position = tmp.p2() + QPointF(halfHeight, connection.config.radius); + textRect.moveBottomRight(position); + } else if (angle == 180) { + position = tmp.p2() + QPointF(connection.config.radius, halfHeight); + textRect.moveBottomLeft(position); + } else if (angle == 270) { + position = tmp.p2() + QPointF(halfHeight, -connection.config.radius); + textRect.moveBottomLeft(position); + } + } else if (tmp.p2() == end.p2()) { + if (angle == 0) { + position = tmp.p1() + QPointF(connection.config.radius, halfHeight); + textRect.moveBottomLeft(position); + } else if (angle == 90) { + position = tmp.p1() + QPointF(halfHeight, -connection.config.radius); + textRect.moveBottomLeft(position); + } else if (angle == 180) { + position = tmp.p1() + QPointF(-connection.config.radius, halfHeight); + textRect.moveBottomRight(position); + } else if (angle == 270) { + position = tmp.p1() + QPointF(halfHeight, connection.config.radius); + textRect.moveBottomRight(position); + } + } else { + position = tmp.center(); + textRect.moveCenter(position); + } + } + + painter->save(); + + painter->setBrush(Qt::red); + painter->translate(position); + painter->rotate(-normalizeAngle(angle)); + painter->translate(-position); + painter->drawRoundedRect(textRect, halfHeight, halfHeight); + + painter->setPen(Qt::white); + painter->drawText(textRect, Qt::AlignCenter, connection.config.events); + + painter->restore(); +} + +void FormEditorTransitionItem::drawEventIdsLabel(QPainter *painter, const Connection &connection) +{ + if (!connection.config.isSelected || connection.config.events.isEmpty()) + return; + + // draw label with event ids + const QStringList events = connection.config.events.split(','); + const int eventCount = events.size(); + + const qreal scaleFactor = getScaleFactor(); + const qreal radius = 7.0 / scaleFactor; + const qreal hMargin = 10.0 / scaleFactor; + + QFontMetrics metric(painter->font()); + const QString title = QObject::tr("Connected Events"); + const QRect titleRect = metric.boundingRect(title); + const qreal lineHeight = titleRect.height(); + + qreal minWidth = titleRect.width() + (2 * hMargin); + // Get the width for the widest event label + for (const QString &event : events) + minWidth = std::max(minWidth, metric.boundingRect(event.trimmed()).width() + (2 * hMargin)); + + const qreal offset = 10.0 / scaleFactor; + const QLineF line(connection.start, connection.mid1); + QPointF pos = line.p1(); + + if (line.angle() == 0) + pos += QPointF(0.0, offset); + else if (line.angle() == 90) + pos += QPointF(offset, -(eventCount + 1) * lineHeight); + else if (line.angle() == 180) + pos += QPointF(-minWidth, offset); + else if (line.angle() == 270) + pos += QPointF(offset, 0.0); + + const QRectF tmpRect(pos, QSize(minWidth, lineHeight)); + + painter->save(); + painter->setBrush(QColor(70, 70, 70, 200)); + painter->setPen(Qt::NoPen); + + // Draw background rect + painter->drawRoundedRect(tmpRect.adjusted(0, 0, 0, eventCount * lineHeight), radius, radius); + // Draw title background rect + painter->drawRoundedRect(tmpRect, radius, radius); + + painter->setPen(Qt::lightGray); + // Draw title + painter->drawText(tmpRect, Qt::AlignHCenter | Qt::TextDontClip, title); + // Draw events + int i = 1; + for (const QString &event : events) { + painter->drawText(tmpRect.translated(hMargin, lineHeight * i++), + Qt::AlignLeft | Qt::TextDontClip, + event.trimmed()); + } + + painter->restore(); +} + static void drawArrow(QPainter *painter, const QPointF &point, const qreal &angle, - int arrowLength, - int arrowWidth) + const qreal &arrowLength, + const qreal &arrowWidth) { const QPointF peakP(0, 0); const QPointF leftP(-arrowLength, -arrowWidth * 0.5); @@ -1503,8 +1641,8 @@ static void drawArrow(QPainter *painter, void FormEditorTransitionItem::paintConnection(QPainter *painter, const Connection &connection) { - const int arrowLength = 4 * connection.config.adjustedWidth; - const int arrowWidth = 8 * connection.config.adjustedWidth; + const qreal arrowLength = 4 * connection.config.adjustedWidth; + const qreal arrowWidth = 8 * connection.config.adjustedWidth; painter->save(); painter->setRenderHint(QPainter::Antialiasing); @@ -1550,11 +1688,12 @@ void FormEditorTransitionItem::paintConnection(QPainter *painter, const Connecti // Draw start ellipse if (connection.config.drawStart) { painter->setBrush(Qt::white); - painter->drawEllipse(connection.start, arrowLength / 3, arrowLength / 3); + painter->drawEllipse(connection.start, arrowLength / 2.0, arrowLength / 2.0); } // Draw labels - drawLabels(painter, connection); + if (viewportTransform().m11() >= zoomLevelLabel) + drawLabels(painter, connection); painter->restore(); } @@ -1580,12 +1719,15 @@ void FormEditorTransitionItem::paint(QPainter *painter, const QStyleOptionGraphi ConnectionConfiguration config(qmlItemNode(), resolved, viewportTransform().m11(), m_hitTest); - QFont font = painter->font(); - font.setPixelSize(config.fontSize); - painter->setFont(font); + QFont f = painter->font(); + f.setPointSizeF(getFontSize(painter) / getScaleFactor()); + painter->setFont(f); - for (const auto &f : resolved.from) { - for (const auto &t : resolved.to) { + const auto fromNodes = resolved.from; + const auto toNodes = resolved.to; + + for (const auto &f : fromNodes) { + for (const auto &t : toNodes) { Connection connection(resolved, pos(), f, t, config); if (!config.hitTesting) { // The following if statement is a special treatment for n-to-n, 1-to-n and n-to-1 @@ -1650,11 +1792,11 @@ void FormEditorTransitionItem::paint(QPainter *painter, const QStyleOptionGraphi pen.setColor(config.color); painter->setPen(pen); - const int iconAdjust = 48; - const int size = connection.fromRect.width(); - const int iconSize = size - iconAdjust; - const int x = connection.fromRect.topRight().x() - startItemOffset; - const int y = connection.fromRect.topRight().y(); + const qreal iconAdjust = 48; + const qreal size = connection.fromRect.width(); + const qreal iconSize = size - iconAdjust; + const qreal x = connection.fromRect.topRight().x() - startItemOffset; + const qreal y = connection.fromRect.topRight().y(); painter->drawRoundedRect(x, y , size - 10, size, size / 2, iconSize / 2); drawIcon(painter, x + iconAdjust / 2, y + iconAdjust / 2, icon, iconSize, iconSize, config.color); } @@ -1675,7 +1817,7 @@ bool FormEditorTransitionItem::flowHitTest(const QPointF &point) const const_cast(this)->paint(&p, nullptr, nullptr); m_hitTest = false; - QPoint pos = mapFromScene(point).toPoint(); + const QPoint pos = mapFromScene(point).toPoint(); return image.pixelColor(pos).value() > 0; } @@ -1687,6 +1829,20 @@ QTransform FormEditorItem::viewportTransform() const return scene()->views().first()->viewportTransform(); } +qreal FormEditorItem::getFontSize(QPainter *painter) const +{ + const int dpi = std::max(painter->device()->logicalDpiX(), + painter->device()->logicalDpiY()); + + return fontSize * (dpi / defaultDpi); +} + +qreal FormEditorItem::getScaleFactor() const +{ + // Cap scaling at 100% zoom + return (viewportTransform().m11() >= 1.0) ? viewportTransform().m11() : 1.0; +} + void FormEditorFlowDecisionItem::updateGeometry() { prepareGeometryChange(); @@ -1704,7 +1860,7 @@ void FormEditorFlowDecisionItem::updateGeometry() // If drawing the dialog title is requested we need to add it to the bounding rect. QRectF labelBoundingRect; - int showDialogLabel = false; + bool showDialogLabel = false; if (qmlItemNode().modelNode().hasAuxiliaryData("showDialogLabel")) showDialogLabel = qmlItemNode().modelNode().auxiliaryData("showDialogLabel").toBool(); @@ -1718,10 +1874,10 @@ void FormEditorFlowDecisionItem::updateGeometry() QPixmap pixmap(640, 480); QPainter localPainter(&pixmap); QFont font = localPainter.font(); - font.setPixelSize(labelFontSize / viewportTransform().m11()); + font.setPointSizeF(getFontSize(&localPainter) / getScaleFactor()); localPainter.setFont(font); - int margin = blockAdjust * 0.5; + const qreal margin = blockAdjust * 0.5; const QRectF adjustedRect = boundingRect.adjusted(margin, margin, -margin, -margin); QRectF textRect(0, 0, 100, 20); @@ -1791,7 +1947,6 @@ void FormEditorFlowDecisionItem::paint(QPainter *painter, const QStyleOptionGrap if (qmlItemNode().modelNode().hasAuxiliaryData("color")) flowColor = qmlItemNode().modelNode().auxiliaryData("color").value(); - const qreal scaleFactor = viewportTransform().m11(); qreal width = 2; if (qmlItemNode().modelNode().hasAuxiliaryData("width")) @@ -1832,7 +1987,7 @@ void FormEditorFlowDecisionItem::paint(QPainter *painter, const QStyleOptionGrap QRectF boundingRect(0, 0, size, size); QTransform transform; - int margin = blockAdjust; + qreal margin = blockAdjust; if (m_iconType == DecisionIcon) { transform.translate(boundingRect.center().x(), boundingRect.center().y()); transform.rotate(45); @@ -1845,18 +2000,18 @@ void FormEditorFlowDecisionItem::paint(QPainter *painter, const QStyleOptionGrap painter->setTransform(transform, true); painter->drawRoundedRect(adjustedRect, radius, radius); - const int iconDecrement = 32; - const int iconSize = adjustedRect.width() - iconDecrement; - const int offset = iconDecrement / 2 + margin; + const qreal iconDecrement = 32.0; + const qreal iconSize = adjustedRect.width() - iconDecrement; + const qreal offset = iconDecrement / 2.0 + margin; painter->restore(); // Draw the dialog title inside the form view if requested. Decision item only. - int showDialogLabel = false; + bool showDialogLabel = false; if (qmlItemNode().modelNode().hasAuxiliaryData("showDialogLabel")) showDialogLabel = qmlItemNode().modelNode().auxiliaryData("showDialogLabel").toBool(); - if (showDialogLabel) { + if (showDialogLabel && viewportTransform().m11() >= zoomLevelLabel) { QString dialogTitle; if (qmlItemNode().modelNode().hasVariantProperty("dialogTitle")) dialogTitle = qmlItemNode().modelNode().variantProperty("dialogTitle").value().toString(); @@ -1864,7 +2019,7 @@ void FormEditorFlowDecisionItem::paint(QPainter *painter, const QStyleOptionGrap if (!dialogTitle.isEmpty()) { QFont font = painter->font(); - font.setPixelSize(labelFontSize / scaleFactor); + font.setPointSizeF(getFontSize(painter) / getScaleFactor()); painter->setFont(font); QRectF textRect(0, 0, 100, 20); diff --git a/src/plugins/qmldesigner/components/formeditor/formeditoritem.h b/src/plugins/qmldesigner/components/formeditor/formeditoritem.h index 58ffff8393c..b37f6aedbd5 100644 --- a/src/plugins/qmldesigner/components/formeditor/formeditoritem.h +++ b/src/plugins/qmldesigner/components/formeditor/formeditoritem.h @@ -128,6 +128,8 @@ protected: QList offspringFormEditorItemsRecursive(const FormEditorItem *formEditorItem) const; FormEditorItem(const QmlItemNode &qmlItemNode, FormEditorScene* scene); QTransform viewportTransform() const; + qreal getFontSize(QPainter *painter) const; + qreal getScaleFactor() const; QRectF m_boundingRect; QRectF m_paintedBoundingRect; @@ -204,6 +206,11 @@ protected: {} void paintConnection(QPainter *painter, const Connection &connection); void drawLabels(QPainter *painter, const Connection &connection); + + void drawGeneralLabel(QPainter *painter, const Connection &connection); + void drawSingleEventIdLabel(QPainter *painter, const Connection &connection); + void drawEventIdsLabel(QPainter *painter, const Connection &connection); + private: mutable bool m_hitTest = false; }; From e2c808f576eac08f68af6b37e9108f6c605c36dd Mon Sep 17 00:00:00 2001 From: Thomas Hartmann Date: Fri, 8 Jan 2021 14:00:36 +0100 Subject: [PATCH 7/8] AdvancedDockingSystem: Fix crash on shutdown In some cases the last floatingWidget seems to be partially destructed. Checking for the parent checks for this case. Change-Id: I27cb750d846e71b9ab4423700120e2c659a02adc Reviewed-by: Thomas Hartmann --- src/libs/advanceddockingsystem/dockmanager.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/libs/advanceddockingsystem/dockmanager.cpp b/src/libs/advanceddockingsystem/dockmanager.cpp index dd67af205a2..adfc23bbf4d 100644 --- a/src/libs/advanceddockingsystem/dockmanager.cpp +++ b/src/libs/advanceddockingsystem/dockmanager.cpp @@ -358,7 +358,9 @@ namespace ADS saveStartupWorkspace(); for (auto floatingWidget : d->m_floatingWidgets) { - if (floatingWidget) + /* There have been crashes with partially destructed widgets in + m_floatingWidgets. Those do not have a parent. */ + if (floatingWidget && floatingWidget->parent() == this) delete floatingWidget.data(); } d->m_floatingWidgets.clear(); From 55cbe717a9b71fa57b404338dbfbb0f024ca5713 Mon Sep 17 00:00:00 2001 From: Vikas Pachdha Date: Tue, 12 Jan 2021 19:06:05 +0100 Subject: [PATCH 8/8] AssetExport: Export reference assets of components Task-number: QDS-2868 Change-Id: Ib5d3875e009bde972a30b0a90216bad6ef6e38ea Reviewed-by: Thomas Hartmann --- .../assetexporterplugin/assetexporter.cpp | 43 +++++++++++++++---- .../assetexporterplugin/assetexporter.h | 7 ++- .../assetexportpluginconstants.h | 1 + .../assetexporterplugin/componentexporter.cpp | 34 +++++++++++++++ .../assetexporterplugin/componentexporter.h | 2 + .../parsers/assetnodeparser.cpp | 6 ++- 6 files changed, 81 insertions(+), 12 deletions(-) diff --git a/src/plugins/qmldesigner/assetexporterplugin/assetexporter.cpp b/src/plugins/qmldesigner/assetexporterplugin/assetexporter.cpp index 856ff32390d..74498f185b0 100644 --- a/src/plugins/qmldesigner/assetexporterplugin/assetexporter.cpp +++ b/src/plugins/qmldesigner/assetexporterplugin/assetexporter.cpp @@ -23,6 +23,7 @@ ** ****************************************************************************/ #include "assetexporter.h" +#include "assetexportpluginconstants.h" #include "componentexporter.h" #include "exportnotification.h" @@ -154,21 +155,47 @@ bool AssetExporter::isBusy() const m_currentState == AssetExporter::ParsingState::WritingJson; } -Utils::FilePath AssetExporter::exportAsset(const QmlObjectNode &node, const Component *component, - const QString &uuid) +const QPixmap &AssetExporter::generateAsset(const ModelNode &node) { + static QPixmap nullPixmap; if (m_cancelled) + return nullPixmap; + + const QString uuid = node.auxiliaryData(Constants::UuidAuxTag).toString(); + QTC_ASSERT(!uuid.isEmpty(), return nullPixmap); + + if (!m_assets.contains(uuid)) { + // Generate asset. + QmlObjectNode objectNode(node); + QPixmap asset = objectNode.toQmlItemNode().instanceRenderPixmap(); + m_assets[uuid] = asset; + } + return m_assets[uuid]; +} + +Utils::FilePath AssetExporter::assetPath(const ModelNode &node, const Component *component, + const QString &suffix) const +{ + const QString uuid = node.auxiliaryData(Constants::UuidAuxTag).toString(); + if (!component || uuid.isEmpty()) return {}; - const Utils::FilePath assetExportDir = m_perComponentExport ? componentExportDir(component) : - m_exportPath; - const QString fileName = uuid + ".png"; - const Utils::FilePath assetPath = assetExportDir.pathAppended("assets").pathAppended(fileName); - if (m_assetDumper) - m_assetDumper->dumpAsset(node.toQmlItemNode().instanceRenderPixmap(), assetPath); + + const Utils::FilePath assetExportDir = + m_perComponentExport ? componentExportDir(component) : m_exportPath; + const Utils::FilePath assetPath = assetExportDir.pathAppended("assets") + .pathAppended(uuid + suffix + ".png"); return assetPath; } +void AssetExporter::exportAsset(const QPixmap &asset, const Utils::FilePath &path) +{ + if (m_cancelled || !m_assetDumper) + return; + + m_assetDumper->dumpAsset(asset, path); +} + void AssetExporter::exportComponent(const ModelNode &rootNode) { qCDebug(loggerInfo) << "Exporting component" << rootNode.id(); diff --git a/src/plugins/qmldesigner/assetexporterplugin/assetexporter.h b/src/plugins/qmldesigner/assetexporterplugin/assetexporter.h index cd5b302e166..55b1432d223 100644 --- a/src/plugins/qmldesigner/assetexporterplugin/assetexporter.h +++ b/src/plugins/qmldesigner/assetexporterplugin/assetexporter.h @@ -71,8 +71,10 @@ public: void cancel(); bool isBusy() const; - Utils::FilePath exportAsset(const QmlObjectNode& node, const Component *component, - const QString &uuid); + const QPixmap &generateAsset(const ModelNode &node); + Utils::FilePath assetPath(const ModelNode &node, const Component *component, + const QString &suffix = {}) const; + void exportAsset(const QPixmap &asset, const Utils::FilePath &path); QByteArray generateUuid(const ModelNode &node); signals: @@ -108,6 +110,7 @@ private: bool m_perComponentExport = false; std::vector> m_components; QSet m_usedHashes; + QHash m_assets; std::unique_ptr m_assetDumper; bool m_cancelled = false; }; diff --git a/src/plugins/qmldesigner/assetexporterplugin/assetexportpluginconstants.h b/src/plugins/qmldesigner/assetexporterplugin/assetexportpluginconstants.h index 5f09b748603..36033a5ea2e 100644 --- a/src/plugins/qmldesigner/assetexporterplugin/assetexportpluginconstants.h +++ b/src/plugins/qmldesigner/assetexporterplugin/assetexportpluginconstants.h @@ -63,6 +63,7 @@ const char ImportsTag[] = "extraImports"; const char UuidTag[] = "uuid"; const char ClipTag[] = "clip"; const char AssetDataTag[] = "assetData"; +const char ReferenceAssetTag[] = "referenceAsset"; const char AssetPathTag[] = "assetPath"; const char AssetBoundsTag[] = "assetBounds"; const char OpacityTag[] = "opacity"; diff --git a/src/plugins/qmldesigner/assetexporterplugin/componentexporter.cpp b/src/plugins/qmldesigner/assetexporterplugin/componentexporter.cpp index a84cfe72c3e..d3e2edfc82b 100644 --- a/src/plugins/qmldesigner/assetexporterplugin/componentexporter.cpp +++ b/src/plugins/qmldesigner/assetexporterplugin/componentexporter.cpp @@ -31,6 +31,7 @@ #include "model.h" #include "nodeabstractproperty.h" #include "nodemetainfo.h" +#include "qmlitemnode.h" #include "rewriterview.h" #include "utils/qtcassert.h" @@ -38,6 +39,7 @@ #include #include #include +#include namespace { Q_LOGGING_CATEGORY(loggerInfo, "qtc.designer.assetExportPlugin.modelExporter", QtInfoMsg) @@ -86,6 +88,7 @@ void Component::exportComponent() // Change the export type to component QJsonObject metadata = m_json.value(MetadataTag).toObject(); metadata.insert(ExportTypeTag, ExportTypeComponent); + addReferenceAsset(metadata); m_json.insert(MetadataTag, metadata); addImports(); } @@ -152,6 +155,37 @@ QJsonObject Component::nodeToJson(const ModelNode &node) return jsonObject; } +void Component::addReferenceAsset(QJsonObject &metadataObject) const +{ + QPixmap refAsset = m_exporter.generateAsset(m_rootNode); + stichChildrendAssets(m_rootNode, refAsset); + Utils::FilePath refAssetPath = m_exporter.assetPath(m_rootNode, this, "_ref"); + m_exporter.exportAsset(refAsset, refAssetPath); + QJsonObject assetData; + if (metadataObject.contains(AssetDataTag)) + assetData = metadataObject[AssetDataTag].toObject(); + assetData.insert(ReferenceAssetTag, refAssetPath.toString()); + metadataObject.insert(AssetDataTag, assetData); +} + +void Component::stichChildrendAssets(const ModelNode &node, QPixmap &parentPixmap) const +{ + if (!node.hasAnySubModelNodes()) + return; + + QPainter painter(&parentPixmap); + for (const ModelNode &child : node.directSubModelNodes()) { + QPixmap childPixmap = m_exporter.generateAsset(child); + if (childPixmap.isNull()) + continue; + stichChildrendAssets(child, childPixmap); + QTransform cTransform = QmlObjectNode(child).toQmlItemNode().instanceTransform(); + painter.setTransform(cTransform); + painter.drawPixmap(QPoint(0, 0), childPixmap); + } + painter.end(); +} + void Component::addImports() { QJsonArray importsArray; diff --git a/src/plugins/qmldesigner/assetexporterplugin/componentexporter.h b/src/plugins/qmldesigner/assetexporterplugin/componentexporter.h index 3f2fa6ee8a9..533586c3809 100644 --- a/src/plugins/qmldesigner/assetexporterplugin/componentexporter.h +++ b/src/plugins/qmldesigner/assetexporterplugin/componentexporter.h @@ -88,6 +88,8 @@ public: private: ModelNodeParser* createNodeParser(const ModelNode &node) const; QJsonObject nodeToJson(const ModelNode &node); + void addReferenceAsset(QJsonObject &metadataObject) const; + void stichChildrendAssets(const ModelNode &node, QPixmap &parentPixmap) const; void addImports(); private: diff --git a/src/plugins/qmldesigner/assetexporterplugin/parsers/assetnodeparser.cpp b/src/plugins/qmldesigner/assetexporterplugin/parsers/assetnodeparser.cpp index b498efbd35a..75111848156 100644 --- a/src/plugins/qmldesigner/assetexporterplugin/parsers/assetnodeparser.cpp +++ b/src/plugins/qmldesigner/assetexporterplugin/parsers/assetnodeparser.cpp @@ -54,10 +54,12 @@ QJsonObject AssetNodeParser::json(Component &component) const { QJsonObject jsonObject = ItemNodeParser::json(component); - Utils::FilePath assetPath = component.exporter().exportAsset(objectNode(), &component, uuid()); + AssetExporter &exporter = component.exporter(); + const Utils::FilePath assetPath = exporter.assetPath(m_node, &component); + exporter.exportAsset(exporter.generateAsset(m_node), assetPath); + QJsonObject assetData; assetData.insert(AssetPathTag, assetPath.toString()); - QJsonObject metadata = jsonObject.value(MetadataTag).toObject(); metadata.insert(AssetDataTag, assetData); jsonObject.insert(MetadataTag, metadata);