Merge remote-tracking branch 'origin/4.14'

Change-Id: Ib343adc6b761d15410d357137e4fc38040ce8d48
This commit is contained in:
Eike Ziller
2021-01-20 11:13:11 +01:00
14 changed files with 374 additions and 102 deletions

View File

@@ -46,7 +46,7 @@
indistinguishable from the same application running on the desktop. indistinguishable from the same application running on the desktop.
\if defined(qtdesignstudio) \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 \uicontrol {Generate Resource File} in \QC. Then upload the package into
\QDV. \QDV.
\else \else

View File

@@ -358,7 +358,9 @@ namespace ADS
saveStartupWorkspace(); saveStartupWorkspace();
for (auto floatingWidget : d->m_floatingWidgets) { 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(); delete floatingWidget.data();
} }
d->m_floatingWidgets.clear(); d->m_floatingWidgets.clear();

View File

@@ -1779,8 +1779,11 @@ void DebuggerEngine::showMessage(const QString &msg, int channel, int timeout) c
void DebuggerEngine::notifyDebuggerProcessFinished(int exitCode, void DebuggerEngine::notifyDebuggerProcessFinished(int exitCode,
QProcess::ExitStatus exitStatus, const QString &backendName) QProcess::ExitStatus exitStatus, const QString &backendName)
{ {
showMessage(QString("%1 PROCESS FINISHED, status %2, exit code %3") showMessage(QString("%1 PROCESS FINISHED, status %2, exit code %3 (0x%4)")
.arg(backendName).arg(exitStatus).arg(exitCode)); .arg(backendName)
.arg(exitStatus)
.arg(exitCode)
.arg(QString::number(exitCode, 16)));
switch (state()) { switch (state()) {
case DebuggerFinished: case DebuggerFinished:

View File

@@ -23,6 +23,7 @@
** **
****************************************************************************/ ****************************************************************************/
#include "assetexporter.h" #include "assetexporter.h"
#include "assetexportpluginconstants.h"
#include "componentexporter.h" #include "componentexporter.h"
#include "exportnotification.h" #include "exportnotification.h"
@@ -154,21 +155,47 @@ bool AssetExporter::isBusy() const
m_currentState == AssetExporter::ParsingState::WritingJson; m_currentState == AssetExporter::ParsingState::WritingJson;
} }
Utils::FilePath AssetExporter::exportAsset(const QmlObjectNode &node, const Component *component, const QPixmap &AssetExporter::generateAsset(const ModelNode &node)
const QString &uuid)
{ {
static QPixmap nullPixmap;
if (m_cancelled) 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 {}; return {};
const Utils::FilePath assetExportDir = m_perComponentExport ? componentExportDir(component) :
m_exportPath; const Utils::FilePath assetExportDir =
const QString fileName = uuid + ".png"; m_perComponentExport ? componentExportDir(component) : m_exportPath;
const Utils::FilePath assetPath = assetExportDir.pathAppended("assets").pathAppended(fileName); const Utils::FilePath assetPath = assetExportDir.pathAppended("assets")
if (m_assetDumper) .pathAppended(uuid + suffix + ".png");
m_assetDumper->dumpAsset(node.toQmlItemNode().instanceRenderPixmap(), assetPath);
return assetPath; 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) void AssetExporter::exportComponent(const ModelNode &rootNode)
{ {
qCDebug(loggerInfo) << "Exporting component" << rootNode.id(); qCDebug(loggerInfo) << "Exporting component" << rootNode.id();

View File

@@ -71,8 +71,10 @@ public:
void cancel(); void cancel();
bool isBusy() const; bool isBusy() const;
Utils::FilePath exportAsset(const QmlObjectNode& node, const Component *component, const QPixmap &generateAsset(const ModelNode &node);
const QString &uuid); 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); QByteArray generateUuid(const ModelNode &node);
signals: signals:
@@ -108,6 +110,7 @@ private:
bool m_perComponentExport = false; bool m_perComponentExport = false;
std::vector<std::unique_ptr<Component>> m_components; std::vector<std::unique_ptr<Component>> m_components;
QSet<QByteArray> m_usedHashes; QSet<QByteArray> m_usedHashes;
QHash<QString, QPixmap> m_assets;
std::unique_ptr<AssetDumper> m_assetDumper; std::unique_ptr<AssetDumper> m_assetDumper;
bool m_cancelled = false; bool m_cancelled = false;
}; };

View File

@@ -63,6 +63,7 @@ const char ImportsTag[] = "extraImports";
const char UuidTag[] = "uuid"; const char UuidTag[] = "uuid";
const char ClipTag[] = "clip"; const char ClipTag[] = "clip";
const char AssetDataTag[] = "assetData"; const char AssetDataTag[] = "assetData";
const char ReferenceAssetTag[] = "referenceAsset";
const char AssetPathTag[] = "assetPath"; const char AssetPathTag[] = "assetPath";
const char AssetBoundsTag[] = "assetBounds"; const char AssetBoundsTag[] = "assetBounds";
const char OpacityTag[] = "opacity"; const char OpacityTag[] = "opacity";

View File

@@ -31,6 +31,7 @@
#include "model.h" #include "model.h"
#include "nodeabstractproperty.h" #include "nodeabstractproperty.h"
#include "nodemetainfo.h" #include "nodemetainfo.h"
#include "qmlitemnode.h"
#include "rewriterview.h" #include "rewriterview.h"
#include "utils/qtcassert.h" #include "utils/qtcassert.h"
@@ -38,6 +39,7 @@
#include <QJsonArray> #include <QJsonArray>
#include <QJsonObject> #include <QJsonObject>
#include <QLoggingCategory> #include <QLoggingCategory>
#include <QPainter>
namespace { namespace {
Q_LOGGING_CATEGORY(loggerInfo, "qtc.designer.assetExportPlugin.modelExporter", QtInfoMsg) Q_LOGGING_CATEGORY(loggerInfo, "qtc.designer.assetExportPlugin.modelExporter", QtInfoMsg)
@@ -86,6 +88,7 @@ void Component::exportComponent()
// Change the export type to component // Change the export type to component
QJsonObject metadata = m_json.value(MetadataTag).toObject(); QJsonObject metadata = m_json.value(MetadataTag).toObject();
metadata.insert(ExportTypeTag, ExportTypeComponent); metadata.insert(ExportTypeTag, ExportTypeComponent);
addReferenceAsset(metadata);
m_json.insert(MetadataTag, metadata); m_json.insert(MetadataTag, metadata);
addImports(); addImports();
} }
@@ -152,6 +155,37 @@ QJsonObject Component::nodeToJson(const ModelNode &node)
return jsonObject; 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() void Component::addImports()
{ {
QJsonArray importsArray; QJsonArray importsArray;

View File

@@ -88,6 +88,8 @@ public:
private: private:
NodeDumper* createNodeDumper(const ModelNode &node) const; NodeDumper* createNodeDumper(const ModelNode &node) const;
QJsonObject nodeToJson(const ModelNode &node); QJsonObject nodeToJson(const ModelNode &node);
void addReferenceAsset(QJsonObject &metadataObject) const;
void stichChildrendAssets(const ModelNode &node, QPixmap &parentPixmap) const;
void addImports(); void addImports();
private: private:

View File

@@ -54,10 +54,12 @@ QJsonObject AssetNodeDumper::json(Component &component) const
{ {
QJsonObject jsonObject = ItemNodeDumper::json(component); 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; QJsonObject assetData;
assetData.insert(AssetPathTag, assetPath.toString()); assetData.insert(AssetPathTag, assetPath.toString());
QJsonObject metadata = jsonObject.value(MetadataTag).toObject(); QJsonObject metadata = jsonObject.value(MetadataTag).toObject();
metadata.insert(AssetDataTag, assetData); metadata.insert(AssetDataTag, assetData);
jsonObject.insert(MetadataTag, metadata); jsonObject.insert(MetadataTag, metadata);

View File

@@ -56,7 +56,10 @@ const int flowBlockSize = 200;
const int blockRadius = 18; const int blockRadius = 18;
const int blockAdjust = 40; const int blockAdjust = 40;
const int startItemOffset = 96; 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, void drawIcon(QPainter *painter,
int x, int x,
@@ -882,7 +885,6 @@ public:
, bezier(50) , bezier(50)
, type(ConnectionType::Default) , type(ConnectionType::Default)
, label() , label()
, fontSize(labelFontSize / scaleFactor)
, labelOffset(14 / scaleFactor) , labelOffset(14 / scaleFactor)
, labelPosition(50.0) , labelPosition(50.0)
, labelFlags(Qt::AlignHCenter | Qt::AlignVCenter | Qt::TextDontClip) , labelFlags(Qt::AlignHCenter | Qt::AlignVCenter | Qt::TextDontClip)
@@ -944,8 +946,6 @@ public:
label = node.modelNode().bindingProperty("condition").expression(); label = node.modelNode().bindingProperty("condition").expression();
if (node.modelNode().hasVariantProperty("question")) if (node.modelNode().hasVariantProperty("question"))
label = node.modelNode().variantProperty("question").value().toString(); label = node.modelNode().variantProperty("question").value().toString();
// font size
// label offset // label offset
// label position // label position
@@ -960,7 +960,6 @@ public:
if (node.modelNode().hasVariantProperty(eventsName)) if (node.modelNode().hasVariantProperty(eventsName))
events = node.modelNode().variantProperty(eventsName).value().toString(); events = node.modelNode().variantProperty(eventsName).value().toString();
} }
qreal width; qreal width;
@@ -980,7 +979,6 @@ public:
int bezier; int bezier;
ConnectionType type; ConnectionType type;
QString label; QString label;
int fontSize;
qreal labelOffset; qreal labelOffset;
qreal labelPosition; qreal labelPosition;
int labelFlags; int labelFlags;
@@ -1284,7 +1282,7 @@ public:
const bool boolExitRight = fromRect.right() < toRect.center().x(); const bool boolExitRight = fromRect.right() < toRect.center().x();
const bool boolExitBottom = fromRect.bottom() < toRect.center().y(); const bool boolExitBottom = fromRect.bottom() < toRect.center().y();
const int padding = 4 * config.adjustedWidth; const qreal padding = 8;
if (horizontalFirst) { if (horizontalFirst) {
const qreal startX = boolExitRight ? fromRect.right() + padding : fromRect.x() - padding; const qreal startX = boolExitRight ? fromRect.right() + padding : fromRect.x() - padding;
@@ -1404,11 +1402,14 @@ void FormEditorTransitionItem::updateGeometry()
QPixmap pixmap(640, 480); QPixmap pixmap(640, 480);
QPainter localPainter(&pixmap); QPainter localPainter(&pixmap);
QFont font = localPainter.font(); QFont font = localPainter.font();
font.setPixelSize(config.fontSize); font.setPointSizeF(getFontSize(&localPainter) / getScaleFactor());
localPainter.setFont(font); localPainter.setFont(font);
for (const auto &from : resolved.from) { const auto fromNodes = resolved.from;
for (const auto &to : resolved.to) { const auto toNodes = resolved.to;
for (const auto &from : fromNodes) {
for (const auto &to : toNodes) {
Connection connection(resolved, pos(), from, to, config); 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 // 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) void FormEditorTransitionItem::drawLabels(QPainter *painter, const Connection &connection)
{ {
// draw label with event ids drawSingleEventIdLabel(painter, connection);
if (connection.config.isSelected && !connection.config.events.isEmpty()) drawEventIdsLabel(painter, connection);
{ drawGeneralLabel(painter, connection);
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();
} }
void FormEditorTransitionItem::drawGeneralLabel(QPainter *painter, const Connection &connection)
{
if (connection.config.label.isEmpty()) if (connection.config.label.isEmpty())
return; return;
@@ -1481,11 +1464,166 @@ void FormEditorTransitionItem::drawLabels(QPainter *painter, const Connection &c
painter->restore(); 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<QLineF> 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<QLineF> 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, static void drawArrow(QPainter *painter,
const QPointF &point, const QPointF &point,
const qreal &angle, const qreal &angle,
int arrowLength, const qreal &arrowLength,
int arrowWidth) const qreal &arrowWidth)
{ {
const QPointF peakP(0, 0); const QPointF peakP(0, 0);
const QPointF leftP(-arrowLength, -arrowWidth * 0.5); const QPointF leftP(-arrowLength, -arrowWidth * 0.5);
@@ -1503,8 +1641,8 @@ static void drawArrow(QPainter *painter,
void FormEditorTransitionItem::paintConnection(QPainter *painter, const Connection &connection) void FormEditorTransitionItem::paintConnection(QPainter *painter, const Connection &connection)
{ {
const int arrowLength = 4 * connection.config.adjustedWidth; const qreal arrowLength = 4 * connection.config.adjustedWidth;
const int arrowWidth = 8 * connection.config.adjustedWidth; const qreal arrowWidth = 8 * connection.config.adjustedWidth;
painter->save(); painter->save();
painter->setRenderHint(QPainter::Antialiasing); painter->setRenderHint(QPainter::Antialiasing);
@@ -1550,10 +1688,11 @@ void FormEditorTransitionItem::paintConnection(QPainter *painter, const Connecti
// Draw start ellipse // Draw start ellipse
if (connection.config.drawStart) { if (connection.config.drawStart) {
painter->setBrush(Qt::white); painter->setBrush(Qt::white);
painter->drawEllipse(connection.start, arrowLength / 3, arrowLength / 3); painter->drawEllipse(connection.start, arrowLength / 2.0, arrowLength / 2.0);
} }
// Draw labels // Draw labels
if (viewportTransform().m11() >= zoomLevelLabel)
drawLabels(painter, connection); drawLabels(painter, connection);
painter->restore(); painter->restore();
@@ -1580,12 +1719,15 @@ void FormEditorTransitionItem::paint(QPainter *painter, const QStyleOptionGraphi
ConnectionConfiguration config(qmlItemNode(), resolved, viewportTransform().m11(), m_hitTest); ConnectionConfiguration config(qmlItemNode(), resolved, viewportTransform().m11(), m_hitTest);
QFont font = painter->font(); QFont f = painter->font();
font.setPixelSize(config.fontSize); f.setPointSizeF(getFontSize(painter) / getScaleFactor());
painter->setFont(font); painter->setFont(f);
for (const auto &f : resolved.from) { const auto fromNodes = resolved.from;
for (const auto &t : resolved.to) { const auto toNodes = resolved.to;
for (const auto &f : fromNodes) {
for (const auto &t : toNodes) {
Connection connection(resolved, pos(), f, t, config); Connection connection(resolved, pos(), f, t, config);
if (!config.hitTesting) { if (!config.hitTesting) {
// The following if statement is a special treatment for n-to-n, 1-to-n and n-to-1 // 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); pen.setColor(config.color);
painter->setPen(pen); painter->setPen(pen);
const int iconAdjust = 48; const qreal iconAdjust = 48;
const int size = connection.fromRect.width(); const qreal size = connection.fromRect.width();
const int iconSize = size - iconAdjust; const qreal iconSize = size - iconAdjust;
const int x = connection.fromRect.topRight().x() - startItemOffset; const qreal x = connection.fromRect.topRight().x() - startItemOffset;
const int y = connection.fromRect.topRight().y(); const qreal y = connection.fromRect.topRight().y();
painter->drawRoundedRect(x, y , size - 10, size, size / 2, iconSize / 2); painter->drawRoundedRect(x, y , size - 10, size, size / 2, iconSize / 2);
drawIcon(painter, x + iconAdjust / 2, y + iconAdjust / 2, icon, iconSize, iconSize, config.color); 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<FormEditorTransitionItem *>(this)->paint(&p, nullptr, nullptr); const_cast<FormEditorTransitionItem *>(this)->paint(&p, nullptr, nullptr);
m_hitTest = false; m_hitTest = false;
QPoint pos = mapFromScene(point).toPoint(); const QPoint pos = mapFromScene(point).toPoint();
return image.pixelColor(pos).value() > 0; return image.pixelColor(pos).value() > 0;
} }
@@ -1687,6 +1829,20 @@ QTransform FormEditorItem::viewportTransform() const
return scene()->views().first()->viewportTransform(); 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() void FormEditorFlowDecisionItem::updateGeometry()
{ {
prepareGeometryChange(); prepareGeometryChange();
@@ -1704,7 +1860,7 @@ void FormEditorFlowDecisionItem::updateGeometry()
// If drawing the dialog title is requested we need to add it to the bounding rect. // If drawing the dialog title is requested we need to add it to the bounding rect.
QRectF labelBoundingRect; QRectF labelBoundingRect;
int showDialogLabel = false; bool showDialogLabel = false;
if (qmlItemNode().modelNode().hasAuxiliaryData("showDialogLabel")) if (qmlItemNode().modelNode().hasAuxiliaryData("showDialogLabel"))
showDialogLabel = qmlItemNode().modelNode().auxiliaryData("showDialogLabel").toBool(); showDialogLabel = qmlItemNode().modelNode().auxiliaryData("showDialogLabel").toBool();
@@ -1718,10 +1874,10 @@ void FormEditorFlowDecisionItem::updateGeometry()
QPixmap pixmap(640, 480); QPixmap pixmap(640, 480);
QPainter localPainter(&pixmap); QPainter localPainter(&pixmap);
QFont font = localPainter.font(); QFont font = localPainter.font();
font.setPixelSize(labelFontSize / viewportTransform().m11()); font.setPointSizeF(getFontSize(&localPainter) / getScaleFactor());
localPainter.setFont(font); localPainter.setFont(font);
int margin = blockAdjust * 0.5; const qreal margin = blockAdjust * 0.5;
const QRectF adjustedRect = boundingRect.adjusted(margin, margin, -margin, -margin); const QRectF adjustedRect = boundingRect.adjusted(margin, margin, -margin, -margin);
QRectF textRect(0, 0, 100, 20); QRectF textRect(0, 0, 100, 20);
@@ -1791,7 +1947,6 @@ void FormEditorFlowDecisionItem::paint(QPainter *painter, const QStyleOptionGrap
if (qmlItemNode().modelNode().hasAuxiliaryData("color")) if (qmlItemNode().modelNode().hasAuxiliaryData("color"))
flowColor = qmlItemNode().modelNode().auxiliaryData("color").value<QColor>(); flowColor = qmlItemNode().modelNode().auxiliaryData("color").value<QColor>();
const qreal scaleFactor = viewportTransform().m11();
qreal width = 2; qreal width = 2;
if (qmlItemNode().modelNode().hasAuxiliaryData("width")) if (qmlItemNode().modelNode().hasAuxiliaryData("width"))
@@ -1832,7 +1987,7 @@ void FormEditorFlowDecisionItem::paint(QPainter *painter, const QStyleOptionGrap
QRectF boundingRect(0, 0, size, size); QRectF boundingRect(0, 0, size, size);
QTransform transform; QTransform transform;
int margin = blockAdjust; qreal margin = blockAdjust;
if (m_iconType == DecisionIcon) { if (m_iconType == DecisionIcon) {
transform.translate(boundingRect.center().x(), boundingRect.center().y()); transform.translate(boundingRect.center().x(), boundingRect.center().y());
transform.rotate(45); transform.rotate(45);
@@ -1845,18 +2000,18 @@ void FormEditorFlowDecisionItem::paint(QPainter *painter, const QStyleOptionGrap
painter->setTransform(transform, true); painter->setTransform(transform, true);
painter->drawRoundedRect(adjustedRect, radius, radius); painter->drawRoundedRect(adjustedRect, radius, radius);
const int iconDecrement = 32; const qreal iconDecrement = 32.0;
const int iconSize = adjustedRect.width() - iconDecrement; const qreal iconSize = adjustedRect.width() - iconDecrement;
const int offset = iconDecrement / 2 + margin; const qreal offset = iconDecrement / 2.0 + margin;
painter->restore(); painter->restore();
// Draw the dialog title inside the form view if requested. Decision item only. // Draw the dialog title inside the form view if requested. Decision item only.
int showDialogLabel = false; bool showDialogLabel = false;
if (qmlItemNode().modelNode().hasAuxiliaryData("showDialogLabel")) if (qmlItemNode().modelNode().hasAuxiliaryData("showDialogLabel"))
showDialogLabel = qmlItemNode().modelNode().auxiliaryData("showDialogLabel").toBool(); showDialogLabel = qmlItemNode().modelNode().auxiliaryData("showDialogLabel").toBool();
if (showDialogLabel) { if (showDialogLabel && viewportTransform().m11() >= zoomLevelLabel) {
QString dialogTitle; QString dialogTitle;
if (qmlItemNode().modelNode().hasVariantProperty("dialogTitle")) if (qmlItemNode().modelNode().hasVariantProperty("dialogTitle"))
dialogTitle = qmlItemNode().modelNode().variantProperty("dialogTitle").value().toString(); dialogTitle = qmlItemNode().modelNode().variantProperty("dialogTitle").value().toString();
@@ -1864,7 +2019,7 @@ void FormEditorFlowDecisionItem::paint(QPainter *painter, const QStyleOptionGrap
if (!dialogTitle.isEmpty()) { if (!dialogTitle.isEmpty()) {
QFont font = painter->font(); QFont font = painter->font();
font.setPixelSize(labelFontSize / scaleFactor); font.setPointSizeF(getFontSize(painter) / getScaleFactor());
painter->setFont(font); painter->setFont(font);
QRectF textRect(0, 0, 100, 20); QRectF textRect(0, 0, 100, 20);

View File

@@ -128,6 +128,8 @@ protected:
QList<FormEditorItem*> offspringFormEditorItemsRecursive(const FormEditorItem *formEditorItem) const; QList<FormEditorItem*> offspringFormEditorItemsRecursive(const FormEditorItem *formEditorItem) const;
FormEditorItem(const QmlItemNode &qmlItemNode, FormEditorScene* scene); FormEditorItem(const QmlItemNode &qmlItemNode, FormEditorScene* scene);
QTransform viewportTransform() const; QTransform viewportTransform() const;
qreal getFontSize(QPainter *painter) const;
qreal getScaleFactor() const;
QRectF m_boundingRect; QRectF m_boundingRect;
QRectF m_paintedBoundingRect; QRectF m_paintedBoundingRect;
@@ -204,6 +206,11 @@ protected:
{} {}
void paintConnection(QPainter *painter, const Connection &connection); void paintConnection(QPainter *painter, const Connection &connection);
void drawLabels(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: private:
mutable bool m_hitTest = false; mutable bool m_hitTest = false;
}; };

View File

@@ -33,10 +33,12 @@ from optparse import OptionParser
from toolfunctions import checkDirectory from toolfunctions import checkDirectory
from toolfunctions import getFileContent from toolfunctions import getFileContent
objMap = None objMap = None
lastToken = [None, None] lastToken = [None, None]
stopTokens = ('OP', 'NAME', 'NUMBER', 'ENDMARKER') stopTokens = ('OP', 'NAME', 'NUMBER', 'ENDMARKER')
def parseCommandLine(): def parseCommandLine():
global directory, onlyRemovable, sharedFolders, deleteObjects global directory, onlyRemovable, sharedFolders, deleteObjects
parser = OptionParser("\n%prog [OPTIONS] [DIRECTORY]") parser = OptionParser("\n%prog [OPTIONS] [DIRECTORY]")
@@ -54,7 +56,7 @@ def parseCommandLine():
elif len(args) == 1: elif len(args) == 1:
directory = os.path.abspath(args[0]) directory = os.path.abspath(args[0])
else: else:
print "\nERROR: Too many arguments\n" print("\nERROR: Too many arguments\n")
parser.print_help() parser.print_help()
sys.exit(1) sys.exit(1)
onlyRemovable = options.onlyRemovable onlyRemovable = options.onlyRemovable
@@ -70,6 +72,7 @@ def collectObjects():
data = getFileContent(objMap) data = getFileContent(objMap)
return map(lambda x: x.strip().split("\t", 1)[0], data.strip().splitlines()) return map(lambda x: x.strip().split("\t", 1)[0], data.strip().splitlines())
def handleStringsWithTrailingBackSlash(origStr): def handleStringsWithTrailingBackSlash(origStr):
try: try:
while True: while True:
@@ -78,8 +81,11 @@ def handleStringsWithTrailingBackSlash(origStr):
except: except:
return origStr return origStr
def handle_token(tokenType, token, (startRow, startCol), (endRow, endCol), line):
def handle_token(tokenType, token, startPos, endPos, line):
global useCounts, lastToken, stopTokens global useCounts, lastToken, stopTokens
(startRow, startCol) = startPos
(endRow, endCol) = endPos
if tokenize.tok_name[tokenType] == 'STRING': if tokenize.tok_name[tokenType] == 'STRING':
# concatenate strings followed directly by other strings # concatenate strings followed directly by other strings
@@ -97,17 +103,22 @@ def handle_token(tokenType, token, (startRow, startCol), (endRow, endCol), line)
# store the stop token as lastToken # store the stop token as lastToken
lastToken = [tokenize.tok_name[tokenType], str(token)] lastToken = [tokenize.tok_name[tokenType], str(token)]
def handleDataFiles(openFile, separator): def handleDataFiles(openFile, separator):
global useCounts global useCounts
# ignore header line # ignore header line
openFile.readline() first = True
for line in openFile: for line in openFile:
if first:
first = False
continue
currentTokens = line.split(separator) currentTokens = line.split(separator)
for token in currentTokens: for token in currentTokens:
stripped = token.strip().strip('"') stripped = token.strip().strip('"')
if stripped in useCounts: if stripped in useCounts:
useCounts[stripped] = useCounts[stripped] + 1 useCounts[stripped] = useCounts[stripped] + 1
def findUsages(): def findUsages():
global directory, objMap, sharedFolders global directory, objMap, sharedFolders
suffixes = (".py", ".csv", ".tsv") suffixes = (".py", ".csv", ".tsv")
@@ -130,36 +141,51 @@ def findUsages():
for directory in directories: for directory in directories:
for root, dirnames, filenames in os.walk(directory): for root, dirnames, filenames in os.walk(directory):
for filename in filter(lambda x: x.endswith(suffixes), filenames): 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"): if filename.endswith(".py"):
if sys.version_info.major == 2:
tokenize.tokenize(currentFile.readline, handle_token) 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"): elif filename.endswith(".csv"):
handleDataFiles(currentFile, ",") handleDataFiles(currentFile, ",")
elif filename.endswith(".tsv"): elif filename.endswith(".tsv"):
handleDataFiles(currentFile, "\t") handleDataFiles(currentFile, "\t")
currentFile.close() currentFile.close()
currentFile = open(objMap) currentFile = open(objMap)
if sys.version_info.major == 2:
tokenize.tokenize(currentFile.readline, handle_token) 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() currentFile.close()
def printResult(): def printResult():
global useCounts, onlyRemovable global useCounts, onlyRemovable
print print
if onlyRemovable: if onlyRemovable:
if min(useCounts.values()) > 0: 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 return False
print "Unused objects:\n" print("Unused objects:\n")
for obj in filter(lambda x: useCounts[x] == 0, useCounts): for obj in filter(lambda x: useCounts[x] == 0, useCounts):
print "%s" % obj print("%s" % obj)
return True return True
else: else:
outFormat = "%3d %s" outFormat = "%3d %s"
for obj,useCount in useCounts.iteritems(): for obj, useCount in useCounts.items():
print outFormat % (useCount, obj) print(outFormat % (useCount, obj))
print print
return None return None
def deleteRemovable(): def deleteRemovable():
global useCounts, objMap global useCounts, objMap
@@ -192,6 +218,7 @@ def deleteRemovable():
print("Deleted %d items, old objects.map has been moved to objects.map~" % count) print("Deleted %d items, old objects.map has been moved to objects.map~" % count)
return count > 0 return count > 0
def main(): def main():
global useCounts, objMap, deleteObjects global useCounts, objMap, deleteObjects
objMap = checkDirectory(directory) objMap = checkDirectory(directory)
@@ -209,6 +236,7 @@ def main():
print(mssg + "to find objects that might have been referenced only by removed objects.\n") print(mssg + "to find objects that might have been referenced only by removed objects.\n")
return 0 return 0
if __name__ == '__main__': if __name__ == '__main__':
parseCommandLine() parseCommandLine()
sys.exit(main()) sys.exit(main())

View File

@@ -30,6 +30,9 @@ import sys
from optparse import OptionParser from optparse import OptionParser
from toolfunctions import checkDirectory from toolfunctions import checkDirectory
from toolfunctions import getFileContent from toolfunctions import getFileContent
if sys.version_info.major == 3:
from functools import reduce
def parseCommandLine(): def parseCommandLine():
global directory, tsv global directory, tsv
@@ -43,11 +46,12 @@ def parseCommandLine():
elif len(args) == 1: elif len(args) == 1:
directory = os.path.abspath(args[0]) directory = os.path.abspath(args[0])
else: else:
print "\nERROR: Too many arguments\n" print("\nERROR: Too many arguments\n")
parser.print_help() parser.print_help()
sys.exit(1) sys.exit(1)
tsv = options.tsv tsv = options.tsv
def readProperties(line): def readProperties(line):
def readOneProperty(rawProperties): def readOneProperty(rawProperties):
name, rawProperties = rawProperties.split("=", 1) name, rawProperties = rawProperties.split("=", 1)
@@ -64,6 +68,7 @@ def readProperties(line):
properties[name] = value properties[name] = value
return objectName, properties return objectName, properties
def main(): def main():
global directory, tsv global directory, tsv
objMap = checkDirectory(directory) objMap = checkDirectory(directory)
@@ -74,20 +79,21 @@ def main():
usedProperties = list(reduce(lambda x, y: x | y, eachObjectsProperties)) usedProperties = list(reduce(lambda x, y: x | y, eachObjectsProperties))
if tsv: if tsv:
print "\t".join(["Squish internal name"] + usedProperties) print("\t".join(["Squish internal name"] + usedProperties))
for name, properties in objects.items(): for name, properties in objects.items():
values = [name] + map(lambda x: properties.setdefault(x, ""), usedProperties) values = [name] + map(lambda x: properties.setdefault(x, ""), usedProperties)
print "\t".join(values) print("\t".join(values))
else: else:
maxPropertyLength = max(map(len, usedProperties)) maxPropertyLength = max(map(len, usedProperties))
for name, properties in objects.items(): for name, properties in objects.items():
print "Squish internal name: %s" % name print("Squish internal name: %s" % name)
print "Properties:" print("Properties:")
for key, val in properties.items(): for key, val in properties.items():
print "%s: %s" % (key.rjust(maxPropertyLength + 4), val) print("%s: %s" % (key.rjust(maxPropertyLength + 4), val))
print print()
return 0 return 0
if __name__ == '__main__': if __name__ == '__main__':
parseCommandLine() parseCommandLine()
sys.exit(main()) sys.exit(main())

View File

@@ -26,16 +26,18 @@
import os import os
import sys import sys
def checkDirectory(directory): def checkDirectory(directory):
if not os.path.exists(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) sys.exit(1)
objMap = os.path.join(directory, "objects.map") objMap = os.path.join(directory, "objects.map")
if not os.path.exists(objMap): 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) sys.exit(1)
return objMap return objMap
def getFileContent(filePath): def getFileContent(filePath):
if os.path.isfile(filePath): if os.path.isfile(filePath):
f = open(filePath, "r") f = open(filePath, "r")