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 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(); diff --git a/src/plugins/debugger/debuggerengine.cpp b/src/plugins/debugger/debuggerengine.cpp index 70e49c35e23..2c2b80dce1e 100644 --- a/src/plugins/debugger/debuggerengine.cpp +++ b/src/plugins/debugger/debuggerengine.cpp @@ -1779,8 +1779,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: 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 b56c648126e..ef931bd0999 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 07f59c62c63..d2f6c720c8a 100644 --- a/src/plugins/qmldesigner/assetexporterplugin/componentexporter.h +++ b/src/plugins/qmldesigner/assetexporterplugin/componentexporter.h @@ -88,6 +88,8 @@ public: private: NodeDumper* createNodeDumper(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/dumpers/assetnodedumper.cpp b/src/plugins/qmldesigner/assetexporterplugin/dumpers/assetnodedumper.cpp index c2b906337b0..a80cbc81013 100644 --- a/src/plugins/qmldesigner/assetexporterplugin/dumpers/assetnodedumper.cpp +++ b/src/plugins/qmldesigner/assetexporterplugin/dumpers/assetnodedumper.cpp @@ -54,10 +54,12 @@ QJsonObject AssetNodeDumper::json(Component &component) const { QJsonObject jsonObject = ItemNodeDumper::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); 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; }; 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/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()) 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")