Trying to populate a CandlestickSeries via JavaScript (data comes from a custom QObject dataProvider):
Connections {
target: dataProvider
function onDataChanged() {
for(var i = 0; i < dataProvider.data.length; i++) {
var x = dataProvider.data[i]
var jsobj = {timestamp: x.timestamp, open: x.open, high: x.high, low: x.low, close: x.close}
if(!serie1.append(jsobj))
console.log('append failed:', JSON.stringify(jsobj))
}
}
}
ChartView {
title: "Candlestick Series"
width: 400
height: 300
CandlestickSeries {
id: serie1
name: "Acme Ltd."
increasingColor: "green"
decreasingColor: "red"
}
}
The call to .append() fails for every data point:
qml: append failed: {"timestamp":1514764800,"open":11993.6,"high":11995.2,"low":11676,"close":11807.6}
qml: append failed: {"timestamp":1514768400,"open":11807.5,"high":11908.4,"low":11501,"close":11561.1}
qml: append failed: {"timestamp":1514772000,"open":11561.1,"high":11700,"low":11423.5,"close":11648.6}
qml: append failed: {"timestamp":1514775600,"open":11620.6,"high":11843.4,"low":11519.3,"close":11567.5}
qml: append failed: {"timestamp":1514779200,"open":11596.5,"high":11809.7,"low":11505.9,"close":11790.7}
qml: append failed: {"timestamp":1514782800,"open":11790.6,"high":11809.9,"low":11699.5,"close":11699.6}
qml: append failed: {"timestamp":1514786400,"open":11699.1,"high":11890.3,"low":11639.6,"close":11886.6}
qml: append failed: {"timestamp":1514790000,"open":11867.4,"high":11949.9,"low":11770.2,"close":11830.3}
qml: append failed: {"timestamp":1514793600,"open":11807.5,"high":11822.5,"low":11461.9,"close":11559.6}
qml: append failed: {"timestamp":1514797200,"open":11582.6,"high":11807.2,"low":11543.5,"close":11743.6}
qml: append failed: {"timestamp":1514800800,"open":11710.8,"high":11777,"low":11582.2,"close":11592.9}
qml: append failed: {"timestamp":1514804400,"open":11592.9,"high":11598,"low":11250,"close":11400.1}
qml: append failed: {"timestamp":1514808000,"open":11412.1,"high":11440,"low":11376.2,"close":11414.7}
qml: append failed: {"timestamp":1514811600,"open":11413.6,"high":11434.4,"low":11180.3,"close":11217.5}
qml: append failed: {"timestamp":1514815200,"open":11210.1,"high":11457.4,"low":11090,"close":11410}
qml: append failed: {"timestamp":1514818800,"open":11457.2,"high":11470,"low":11300.1,"close":11437.9}
qml: append failed: {"timestamp":1514822400,"open":11426.5,"high":11426.6,"low":11257.5,"close":11310.8}
qml: append failed: {"timestamp":1514826000,"open":11310.5,"high":11439.9,"low":11300,"close":11378.1}
qml: append failed: {"timestamp":1514829600,"open":11378,"high":11470,"low":11300,"close":11391.6}
qml: append failed: {"timestamp":1514833200,"open":11391.6,"high":11469.9,"low":11335,"close":11455.8}
qml: append failed: {"timestamp":1514836800,"open":11455.8,"high":11639,"low":11360,"close":11434.1}
qml: append failed: {"timestamp":1514840400,"open":11455,"high":11588,"low":11434,"close":11498.9}
qml: append failed: {"timestamp":1514844000,"open":11495.6,"high":11508,"low":11346.2,"close":11448.3}
qml: append failed: {"timestamp":1514847600,"open":11448.1,"high":11460,"low":11300,"close":11359}
How to populate the CandlestickSeries dynamically?
Note: I also tried an alternative approach using Repeater, and that failed too.
Tried to generate the whole QML code for the data I want to show and then use Qt.createQmlObject() to dynamically create it (yeah, I know, horrible solution):
function onDataChanged() {
var qml = 'import QtQuick 2.15\nimport QtCharts 2.15\n\nCandlestickSeries {\n name: "HelloWorld Ltd."\n increasingColor: "green"\n decreasingColor: "red"\n\n'
for(var i = 0; i < dataProvider.data.length; i++) {
var x = dataProvider.data[i]
qml += ' CandlestickSet { timestamp: ' + (x.timestamp * 1000) + '; open: ' + x.open + '; high: ' + x.high + '; low: ' + x.low + '; close: ' + x.close + ' }\n'
}
qml += '}\n'
var series = Qt.createQmlObject(qml, chart, 'dyn')
if(series == null) {
console.log("Error creating series")
return
}
}
and it doesn't work (no error).
But if I print the generated QML code, and then I modify my static QML code to add the generated QML, it works.
Really weird
You probably don't need this anymore but I have found a solution. What you need to do is create the entire object of CandlestickSet with Qt.createQmlObject, append it to CandlstickSeries and create and assign axes. The following code will work:
var newObject = Qt.createQmlObject(
"import QtQuick 2.0; " +
"import QtCharts 2.2; " +
"CandlestickSet {" +
"timestamp: " + timestamp +
"; open: " + open +
"; high: " + high +
"; low: " + low +
"; close: " + close +
"}", candlestickSeries);
qmlObjects.push(newObject); //Array holding created objects to destroy them when not needed anymore to prevent a memory leak
candlestickSeries.append(newObject);
candlestickSeries.axisX = Qt.createQmlObject(
"import QtQuick 2.0; " +
"import QtCharts 2.2; " +
"DateTimeAxis {" +
"min: new Date(" + x_min + ")" +
"; max: new Date(" + x_max + ")" +
"}", candlestickSeries);
candlestickSeries.axisY = Qt.createQmlObject(
"import QtQuick 2.0; " +
"import QtCharts 2.2; " +
"ValueAxis {" +
"min: " + y_min +
"; max: " + y_max +
"}", candlestickSeries);
Related
Is there any way to list all object members/properties in QML & Qt 5.1?
Such as:
var obj=myQObject;
console.log(obj)
// expected output:
// obj { x:123..... }
This would be very helpful for debugging.
Straight javascript offers what you are looking for:
JSON.stringify(anything)
It works on QML items such as Rectangle, and it also works on most arbitrary objects!
Converting an object to a string
With meta objects you can debug all properties of any QML obj (i.e. QQuickItem).
You need some C++ to get the meta object of a QML component and get back property names and values as text in QML.
First you create a QMLDebugger class in C++ with a properties method:
QString QMLDebugger::properties(QQuickItem *item, bool linebreak)
{
const QMetaObject *meta = item->metaObject();
QHash<QString, QVariant> list;
for (int i = 0; i < meta->propertyCount(); i++)
{
QMetaProperty property = meta->property(i);
const char* name = property.name();
QVariant value = item->property(name);
list[name] = value;
}
QString out;
QHashIterator<QString, QVariant> i(list);
while (i.hasNext()) {
i.next();
if (!out.isEmpty())
{
out += ", ";
if (linebreak) out += "\n";
}
out.append(i.key());
out.append(": ");
out.append(i.value().toString());
}
return out;
}
This function can be static or instanceiable, doesn't matter. QML does not support exporting static methods from C++ to QML anyway. I use the header:
public:
Q_INVOKABLE static QString properties(QQuickItem *item, bool linebreak = true);
Now you export the class to QML. In you main.cpp add
#include "qmldebugger.h"
and
qmlRegisterType<QMLDebugger>("MyDemoLibrary", 1, 0, "QMLDebugger");
In your QML file import you new library, create an instance of QMLDebugger and start happy debugging:
import QtQuick 2.0
import MyDemoLibrary 1.0
Rectangle {
id: mainRectangle
width: 360
height: 360
color: "silver"
Text {
id: textElement
color: "#d71f1f"
text: qsTr("Hello World")
font.bold: true
font.italic: true
font.underline: true
style: Text.Raised
horizontalAlignment: Text.AlignHCenter
font.pointSize: 16
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
}
QMLDebugger {
id: qmlDebugger
}
Component.onCompleted: {
console.log("Debug mainRectangle:");
console.log(qmlDebugger.properties(mainRectangle));
console.log("Debug textElement:");
console.log(qmlDebugger.properties(textElement, false));
}
}
The full source code is available as a minimal Qt Creator project on: https://github.com/webmaster128/QMLDebugger
Just convert the QML/C++ component/object into JavaScript var object, and use the for-each syntax to list all the property:
function listProperty(item)
{
for (var p in item)
console.log(p + ": " + item[p]);
}
in your QML file, just call
onClicked:
{
listProperty(ItemID)
//or with this to list self properties
listProperty(this)
}
in case of any one wants to list only properties of an object, no signals nor slots you can use this
function listProperty(item)
{
for (var p in item)
{
if( typeof item[p] != "function" )
if(p != "objectName")
console.log(p + ":" + item[p]);
}
}
If you are not only interested in console-debugging, there is a program called GammaRay by KDAB (link) that lets you introspect and change all properties during the runtime of a QWidgets or QtQuick based program. Pretty neat!
I did not see a solution to iterate all properties yet. But maybe this helps you as a first step.
For every Quick Item you can print out the properties of Item:
import QtQuick 2.0
Rectangle {
width: 360
height: 360
function debugQuickItem(object) {
var properties = {
'activeFocus': object.activeFocus,
'activeFocusOnTab': object.activeFocusOnTab,
'anchors.alignWhenCentered': object.anchors.alignWhenCentered,
'anchors.baseline': object.anchors.baseline,
'anchors.baselineOffset': object.anchors.baselineOffset,
'anchors.bottom': object.anchors.bottom,
'anchors.bottomMargin': object.anchors.bottomMargin,
'anchors.centerIn': object.anchors.centerIn,
'anchors.fill': object.anchors.fill,
'anchors.horizontalCenter': object.anchors.horizontalCenter,
'anchors.horizontalCenterOffset': object.anchors.horizontalCenterOffset,
'anchors.left': object.anchors.left,
'anchors.leftMargin': object.anchors.leftMargin,
'anchors.margins': object.anchors.margins,
'anchors.right': object.anchors.right,
'anchors.rightMargin': object.anchors.rightMargin,
'anchors.top': object.anchors.top,
'anchors.topMargin': object.anchors.topMargin,
'anchors.verticalCenter': object.anchors.verticalCenter,
'anchors.verticalCenterOffset': object.anchors.verticalCenterOffset,
'antialiasing': object.antialiasing,
'baselineOffset': object.baselineOffset,
'children': object.children,
'childrenRect.height': object.childrenRect.height,
'childrenRect.width': object.childrenRect.width,
'childrenRect.x': object.childrenRect.x,
'childrenRect.y': object.childrenRect.y,
'clip': object.clip,
'data': object.data,
'enabled': object.enabled,
'focus': object.focus,
'height': object.height,
'implicitHeight': object.implicitHeight,
'implicitWidth': object.implicitWidth,
'layer.effect': object.layer.effect,
'layer.enabled': object.layer.enabled,
'layer.format': object.layer.format,
'layer.mipmap': object.layer.mipmap,
'layer.samplerName': object.layer.samplerName,
'layer.smooth': object.layer.smooth,
'layer.sourceRect': object.layer.sourceRect,
'layer.textureSize': object.layer.textureSize,
'layer.wrapMode': object.layer.wrapMode,
'opacity': object.opacity,
'parent': object.parent,
'resources': object.resources,
'rotation': object.rotation,
'scale': object.scale,
'smooth': object.smooth,
'state': object.state,
'states': object.states,
'transform': object.transform,
'transformOrigin': object.transformOrigin,
'transitions': object.transitions,
'visible': object.visible,
'visibleChildren': object.visibleChildren,
'width': object.width,
'x': object.x,
'y': object.y,
'z': object.z,
}
var out = "{ "
for (var key in properties)
{
out += "'" + key + "': " + properties[key] + ", "
}
out += "}"
return out;
}
Text {
id: textObject
anchors.centerIn: parent
text: "Hello World"
}
Component.onCompleted: console.log(debugQuickItem(textObject));
}
Output:
{ 'activeFocus': false, 'activeFocusOnTab': false, 'anchors.alignWhenCentered': true, 'anchors.baseline': QVariant(QQuickAnchorLine), 'anchors.baselineOffset': 0, 'anchors.bottom': QVariant(QQuickAnchorLine), 'anchors.bottomMargin': 0, 'anchors.centerIn': QQuickRectangle_QML_0(0x29857d0), 'anchors.fill': null, 'anchors.horizontalCenter': QVariant(QQuickAnchorLine), 'anchors.horizontalCenterOffset': 0, 'anchors.left': QVariant(QQuickAnchorLine), 'anchors.leftMargin': 0, 'anchors.margins': 0, 'anchors.right': QVariant(QQuickAnchorLine), 'anchors.rightMargin': 0, 'anchors.top': QVariant(QQuickAnchorLine), 'anchors.topMargin': 0, 'anchors.verticalCenter': QVariant(QQuickAnchorLine), 'anchors.verticalCenterOffset': 0, 'antialiasing': false, 'baselineOffset': 14, 'children': [object Object], 'childrenRect.height': 0, 'childrenRect.width': 0, 'childrenRect.x': 0, 'childrenRect.y': 0, 'clip': false, 'data': [object Object], 'enabled': true, 'focus': false, 'height': 17, 'implicitHeight': 17, 'implicitWidth': 80.5625, 'layer.effect': null, 'layer.enabled': false, 'layer.format': 6408, 'layer.mipmap': false, 'layer.samplerName': source, 'layer.smooth': false, 'layer.sourceRect': QRectF(0, 0, 0, 0), 'layer.textureSize': QSize(-1, -1), 'layer.wrapMode': 0, 'opacity': 1, 'parent': QQuickRectangle_QML_0(0x29857d0), 'resources': [object Object], 'rotation': 0, 'scale': 1, 'smooth': true, 'state': , 'states': [object Object], 'transform': [object Object], 'transformOrigin': 4, 'transitions': [object Object], 'visible': true, 'visibleChildren': [object Object], 'width': 80.5625, 'x': 139.71875, 'y': 171, 'z': 0, }
I'm using Aframe-React and the Aframe Click Drag component.
This is working well, and I'm now trying to work out how to add events to the entity so I can update these calculations when one of my entities is dragged (the lines are a elbow connection between them - I want to update these as the item is dragged)
The entities are given the Click Drag attributes later on, however I'm assuming its best to add the listener here.
The library has an example for events
https://github.com/jesstelford/aframe-click-drag-component/blob/master/examples/events/index.html
And registers the events as such
event-set__1="_event: dragstart; material.opacity: 0.2"
However I can't seem to work out how to make that call a function in this class,
i.e. something like
event-set__1="_event: dragstart" should call the dragStart() function.
Any clues on how to do this?
const scalar = 0.2
const offsetX = 4
const offsetY = 4.5
config.scale = {x: scalar, y: scalar, z: scalar}
if (editingHotspot.shape) {
buttonConfig.position = {
x: config.position.x + offsetX,
y: config.position.y + offsetY,
z: config.position.z,
}
const shapeTop = {
x: config.position.x,
y: config.position.y + 1.9,
z: config.position.z,
}
const buttonEdge = {
x: buttonConfig.position.x - geometry.width * 0.5 * scalar,
y: buttonConfig.position.y,
z: buttonConfig.position.z,
}
const join = {
x: shapeTop.x,
y: buttonEdge.y,
z: (shapeTop.z + buttonEdge.z) / 2,
}
lineX = {
lineWidth: 10,
path: AFRAME.utils.coordinates.stringify(buttonEdge) + ',' + AFRAME.utils.coordinates.stringify(join),
color: '#fff',
}
lineY = {
lineWidth: 10,
path: AFRAME.utils.coordinates.stringify(shapeTop) + ',' + AFRAME.utils.coordinates.stringify(join),
color: '#fff',
}
}
}
let dragStart = (e) => {
console.log(e)
}
let params = {
'hotspots-button': 'text:' + (button.label != null ? button.label : '') + ';' + 'icon:' + (button.icon != null ? button.icon.preview : '') + ';',
draw: 'width:256; height:' + (button.icon != null ? '256' : '128') + ';',
}
return (
<Entity className='hotspot button' {...params} >
<Entity
className='hotspot button'
primitive='a-image'
look-at='[camera]'
{...{geometry}}
scale={config.scale}
position={editingHotspot.shape ? buttonConfig.position : config.position}
/>
{
editingHotspot.shape &&
<Entity>
<Shape config={config} editingHotspot={editingHotspot}/>
<Entity meshline={lineX}/>
<Entity meshline={lineY}/>
</Entity>
}
</Entity>
)
As far as i see, Kevin's event-set component sets the target / self attributes (line 121 of his non-minified dist), which means it can't call methods ( except for update, which is called whenever an attribute is changed)
I'd make my own listener, either in my component, or just a listener -> caller
AFRAME.registerComponent("listener", {
init: function() {
this.el.addEventListener("startDrag", (e)=> {
// call your function here.
// if you want to call another component's function, you can do
// this.el.components.componentWithMethod.method()
}
}
}
Something like this.
I've updated my meteor to 1.2, and I'm now trying to use the email attachment feature, but not sure how to.
Meteor's guide says refer to this, but it's not very helpful..
if(true == true){
var dataAttachments = attachment;
var dataText =
"Client Name: " + name +
"\rEmail: " + email +
"\rPhone: " + phone +
"\rCompany: " + company +
"\rDeliverables: " + deliverables +
"\rCopywriting: " + copywriting +
"\rPrint Services: " + print +
"\rIllustration: " + illustration +
"\rphotography: " + photography +
"\rTimelines: " + timelines +
"\rBudget: " + budget +
"\rDescription: " + description;
Meteor.call('sendEmail', dataText, dataAttachment);
//throwAlert is my helper method which creates popup with message
alert('Email sent');
}else{
alert('An error occurred. Sorry');
return false;
}
}
});
and
Meteor.methods({
sendEmail: function (text) {
check([text], [String]);
this.unblock();
Email.send({
to: 'jaeeun#antarcti.cc',
from: 'contact#myClientProject.com',
subject: 'New message from contact form',
text: text
});
Email.send().addAttachment(attachment);
}
});
I would suggest installing this package: https://atmospherejs.com/ashutosh/email-att
Then do:
var attachments = [];
attachments.push({filename: "xxx", filePath: "xxx"});
var email = {
from: "test#gmail.com",
to: "test2#gmail.com",
subject: "Test!",
text: "Text!"
attachmentOptions: attachments
};
Meteor.call('send_one_email_with_attachments', email, function(){});
Meteor.methods({
send_one_email_with_attachments: function(email){
this.unblock();
EmailAtt.send(email);
};
});
This made my life a lot easier after I fought Meteor's built-in email for a while. It even works side-by-side, so you can still use your old non-attachment email functions!
Is there any way to list all object members/properties in QML & Qt 5.1?
Such as:
var obj=myQObject;
console.log(obj)
// expected output:
// obj { x:123..... }
This would be very helpful for debugging.
Straight javascript offers what you are looking for:
JSON.stringify(anything)
It works on QML items such as Rectangle, and it also works on most arbitrary objects!
Converting an object to a string
With meta objects you can debug all properties of any QML obj (i.e. QQuickItem).
You need some C++ to get the meta object of a QML component and get back property names and values as text in QML.
First you create a QMLDebugger class in C++ with a properties method:
QString QMLDebugger::properties(QQuickItem *item, bool linebreak)
{
const QMetaObject *meta = item->metaObject();
QHash<QString, QVariant> list;
for (int i = 0; i < meta->propertyCount(); i++)
{
QMetaProperty property = meta->property(i);
const char* name = property.name();
QVariant value = item->property(name);
list[name] = value;
}
QString out;
QHashIterator<QString, QVariant> i(list);
while (i.hasNext()) {
i.next();
if (!out.isEmpty())
{
out += ", ";
if (linebreak) out += "\n";
}
out.append(i.key());
out.append(": ");
out.append(i.value().toString());
}
return out;
}
This function can be static or instanceiable, doesn't matter. QML does not support exporting static methods from C++ to QML anyway. I use the header:
public:
Q_INVOKABLE static QString properties(QQuickItem *item, bool linebreak = true);
Now you export the class to QML. In you main.cpp add
#include "qmldebugger.h"
and
qmlRegisterType<QMLDebugger>("MyDemoLibrary", 1, 0, "QMLDebugger");
In your QML file import you new library, create an instance of QMLDebugger and start happy debugging:
import QtQuick 2.0
import MyDemoLibrary 1.0
Rectangle {
id: mainRectangle
width: 360
height: 360
color: "silver"
Text {
id: textElement
color: "#d71f1f"
text: qsTr("Hello World")
font.bold: true
font.italic: true
font.underline: true
style: Text.Raised
horizontalAlignment: Text.AlignHCenter
font.pointSize: 16
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
}
QMLDebugger {
id: qmlDebugger
}
Component.onCompleted: {
console.log("Debug mainRectangle:");
console.log(qmlDebugger.properties(mainRectangle));
console.log("Debug textElement:");
console.log(qmlDebugger.properties(textElement, false));
}
}
The full source code is available as a minimal Qt Creator project on: https://github.com/webmaster128/QMLDebugger
Just convert the QML/C++ component/object into JavaScript var object, and use the for-each syntax to list all the property:
function listProperty(item)
{
for (var p in item)
console.log(p + ": " + item[p]);
}
in your QML file, just call
onClicked:
{
listProperty(ItemID)
//or with this to list self properties
listProperty(this)
}
in case of any one wants to list only properties of an object, no signals nor slots you can use this
function listProperty(item)
{
for (var p in item)
{
if( typeof item[p] != "function" )
if(p != "objectName")
console.log(p + ":" + item[p]);
}
}
If you are not only interested in console-debugging, there is a program called GammaRay by KDAB (link) that lets you introspect and change all properties during the runtime of a QWidgets or QtQuick based program. Pretty neat!
I did not see a solution to iterate all properties yet. But maybe this helps you as a first step.
For every Quick Item you can print out the properties of Item:
import QtQuick 2.0
Rectangle {
width: 360
height: 360
function debugQuickItem(object) {
var properties = {
'activeFocus': object.activeFocus,
'activeFocusOnTab': object.activeFocusOnTab,
'anchors.alignWhenCentered': object.anchors.alignWhenCentered,
'anchors.baseline': object.anchors.baseline,
'anchors.baselineOffset': object.anchors.baselineOffset,
'anchors.bottom': object.anchors.bottom,
'anchors.bottomMargin': object.anchors.bottomMargin,
'anchors.centerIn': object.anchors.centerIn,
'anchors.fill': object.anchors.fill,
'anchors.horizontalCenter': object.anchors.horizontalCenter,
'anchors.horizontalCenterOffset': object.anchors.horizontalCenterOffset,
'anchors.left': object.anchors.left,
'anchors.leftMargin': object.anchors.leftMargin,
'anchors.margins': object.anchors.margins,
'anchors.right': object.anchors.right,
'anchors.rightMargin': object.anchors.rightMargin,
'anchors.top': object.anchors.top,
'anchors.topMargin': object.anchors.topMargin,
'anchors.verticalCenter': object.anchors.verticalCenter,
'anchors.verticalCenterOffset': object.anchors.verticalCenterOffset,
'antialiasing': object.antialiasing,
'baselineOffset': object.baselineOffset,
'children': object.children,
'childrenRect.height': object.childrenRect.height,
'childrenRect.width': object.childrenRect.width,
'childrenRect.x': object.childrenRect.x,
'childrenRect.y': object.childrenRect.y,
'clip': object.clip,
'data': object.data,
'enabled': object.enabled,
'focus': object.focus,
'height': object.height,
'implicitHeight': object.implicitHeight,
'implicitWidth': object.implicitWidth,
'layer.effect': object.layer.effect,
'layer.enabled': object.layer.enabled,
'layer.format': object.layer.format,
'layer.mipmap': object.layer.mipmap,
'layer.samplerName': object.layer.samplerName,
'layer.smooth': object.layer.smooth,
'layer.sourceRect': object.layer.sourceRect,
'layer.textureSize': object.layer.textureSize,
'layer.wrapMode': object.layer.wrapMode,
'opacity': object.opacity,
'parent': object.parent,
'resources': object.resources,
'rotation': object.rotation,
'scale': object.scale,
'smooth': object.smooth,
'state': object.state,
'states': object.states,
'transform': object.transform,
'transformOrigin': object.transformOrigin,
'transitions': object.transitions,
'visible': object.visible,
'visibleChildren': object.visibleChildren,
'width': object.width,
'x': object.x,
'y': object.y,
'z': object.z,
}
var out = "{ "
for (var key in properties)
{
out += "'" + key + "': " + properties[key] + ", "
}
out += "}"
return out;
}
Text {
id: textObject
anchors.centerIn: parent
text: "Hello World"
}
Component.onCompleted: console.log(debugQuickItem(textObject));
}
Output:
{ 'activeFocus': false, 'activeFocusOnTab': false, 'anchors.alignWhenCentered': true, 'anchors.baseline': QVariant(QQuickAnchorLine), 'anchors.baselineOffset': 0, 'anchors.bottom': QVariant(QQuickAnchorLine), 'anchors.bottomMargin': 0, 'anchors.centerIn': QQuickRectangle_QML_0(0x29857d0), 'anchors.fill': null, 'anchors.horizontalCenter': QVariant(QQuickAnchorLine), 'anchors.horizontalCenterOffset': 0, 'anchors.left': QVariant(QQuickAnchorLine), 'anchors.leftMargin': 0, 'anchors.margins': 0, 'anchors.right': QVariant(QQuickAnchorLine), 'anchors.rightMargin': 0, 'anchors.top': QVariant(QQuickAnchorLine), 'anchors.topMargin': 0, 'anchors.verticalCenter': QVariant(QQuickAnchorLine), 'anchors.verticalCenterOffset': 0, 'antialiasing': false, 'baselineOffset': 14, 'children': [object Object], 'childrenRect.height': 0, 'childrenRect.width': 0, 'childrenRect.x': 0, 'childrenRect.y': 0, 'clip': false, 'data': [object Object], 'enabled': true, 'focus': false, 'height': 17, 'implicitHeight': 17, 'implicitWidth': 80.5625, 'layer.effect': null, 'layer.enabled': false, 'layer.format': 6408, 'layer.mipmap': false, 'layer.samplerName': source, 'layer.smooth': false, 'layer.sourceRect': QRectF(0, 0, 0, 0), 'layer.textureSize': QSize(-1, -1), 'layer.wrapMode': 0, 'opacity': 1, 'parent': QQuickRectangle_QML_0(0x29857d0), 'resources': [object Object], 'rotation': 0, 'scale': 1, 'smooth': true, 'state': , 'states': [object Object], 'transform': [object Object], 'transformOrigin': 4, 'transitions': [object Object], 'visible': true, 'visibleChildren': [object Object], 'width': 80.5625, 'x': 139.71875, 'y': 171, 'z': 0, }
When I catch an error in ExtendScript, I would like to be able to log its stack trace. It appears that errors do not contain stack traces in ExtendScript, so I'm playing around with the idea of adding stack traces to errors.
The only way I know of to get a stack trace is $.stack. The field $.stack contains the current stack trace at the moment that you access the field.
My first attempt was to create my own error object that includes the stack. The Error object is very special in that it can get the line and filename of the code that created it. For example,
try {
throw new Error("Houston, we have a problem.");
}
catch (e) {
$.writeln("Line: " + e.line);
$.writeln("File: " + e.fileName);
$.writeln("Message: " + e.message);
}
Will print:
Line: 2
File: ~/Desktop/Source1.jsx
Message: Houston, we have a problem.
I don't think it's possible to create your own object with this ability. The closest I can get is this:
function MyError(msg, file, line) {
this.message = msg;
this.fileName = file;
this.line = line;
this.stack = $.stack;
}
try {
throw new MyError("Houston, we have a problem.", $.fileName, $.line);
}
catch (e) {
$.writeln("Line: " + e.line);
$.writeln("File: " + e.fileName);
$.writeln("Message: " + e.message);
$.writeln("Stack: " + e.stack);
}
Which prints:
Line: 9
File: ~/Desktop/Source2.jsx
Message: Houston, we have a problem.
Stack: [Source3.jsx]
MyError("Houston, we have a p"...,"~/Desktop/Source2.js"...,9)
Here we can see that I'm creating my own error object and explicitly passing it the line and file name (since MyError can't figure that out on its own). I've also included the current stack when the error gets created.
This works fine when I call my own error object, but it doesn't work when other code calls the regular Error object or when an error is generated automatically (e.g. by illegal access). I want to be able to get the stack trace of any error, no matter how it is generated.
Other approaches might be to modify Error's constructor, modify Error's prototype, or replace the Error object entirely. I haven't been able to get any of these approaches to work.
Another idea would be to put a catch block in every single method of my code and add the current stack to the error if it doesn't already have one. I would like to avoid this option if possible.
I'm out of ideas. Is there any way to get the stack trace of errors?
It isn't perfect, but I found a partial solution.
Fact 1: Error.prototype is an Error object.
Fact 2: The method Error.prototype.toString is called whenever an error is created.
Fact 3: The field Error.prototype.toString can be modified.
That method typically just returns the string "Error", so we can replace it with our own method that stores the stack and then returns the string "Error".
Error.prototype.toString = function() {
if (typeof this.stack === "undefined" || this.stack === null) {
this.stack = "placeholder";
// The previous line is needed because the next line may indirectly call this method.
this.stack = $.stack;
}
return "Error";
}
try {
throw new Error("Houston, we have a problem.");
}
catch (e) {
$.writeln("Line: " + e.line);
$.writeln("File: " + e.fileName);
$.writeln("Message: " + e.message);
$.writeln("Stack: " + e.stack);
}
Result:
Line: 11
File: ~/Desktop/Source10.jsx
Message: Houston, we have a problem.
Stack: [Source10.jsx]
toString()
It works! The only problem is automatic errors.
Error.prototype.toString = function() {
if (typeof this.stack === "undefined" || this.stack === null) {
this.stack = "placeholder";
// The previous line is needed because the next line may indirectly call this method.
this.stack = $.stack;
}
return "Error";
}
try {
var foo = null;
foo.bar;
}
catch (e) {
$.writeln("Line: " + e.line);
$.writeln("File: " + e.fileName);
$.writeln("Message: " + e.message);
$.writeln("Stack: " + e.stack);
}
Result:
Line: 12
File: ~/Desktop/Source12.jsx
Message: null is not an object
Stack: undefined
So it doesn't work on all errors, but its progress.
I've come up with another solution, though this one requires you to change some of your code. Instead of calling methods as usual:
myObject.myMethod1("Hello", "world");
You'll need to switch to calling methods like this:
myObject.do("myMethod1", "Hello", "world");
Here's a complete example of how it works:
Object.prototype.do = function stackHelper() {
// Convert the arguments into an array.
var argumentArray = Array.prototype.slice.call(arguments);
// Remove the first argument, which is the function's name.
var functionString = argumentArray.shift();
try {
this[functionString].apply(this, argumentArray);
}
catch (e) {
if (typeof e.stack === "undefined" || e.stack === null) {
e.stack = $.stack;
}
throw e;
}
};
var myObject = {
myMethod1: function myMethod1(myArg1, myArg2){
this.do("myMethod2", myArg1, myArg2);
},
myMethod2: function myMethod2(myArg1, myArg2){
this.do("myMethod3", myArg1, myArg2);
},
myMethod3: function myMethod3(myArg1, myArg2){
$.writeln(myArg1 + ", " + myArg2 + "!");
var foo = null;
foo.bar; // Throws an error.
},
};
try {
myObject.do("myMethod1", "Hello", "world");
}
catch (e) {
$.writeln("Stack: " + e.stack);
}
The output looks like this:
Hello, world!
Stack: [do.jsx]
stackHelper("myMethod1","Hello","world")
myMethod1("Hello","world")
stackHelper("myMethod2","Hello","world")
myMethod2("Hello","world")
stackHelper("myMethod3","Hello","world")
It's not a great solution, but at least it works on all errors.
As far as I know you cannot modify the [native Code] of the Error.prototype.toString-function. So I came up with this solution:
function ReturnCustomErrorString(e, additionalMessage)
{
try {
var errorString = e.toString();
errorString = errorString.concat("\n", "additionalMessage: " + additionalMessage + "\n", "file: " + e.fileName + "\n", "line: " + e.line + "\n", "stack-trace: \n" + $.stack);
return errorString;
}
catch (e) {
alert("Error in : " + ReturnCustomErrorString.name + "(...)\n" + e);
exit();
}
}
Usage:
try {
// code that does throw an error
} catch (e) {
alert(ReturnCustomErrorString(e));
}
Before I wrote this function I often did something like this in the catch-block:
alert(e);
Now I'm doing alert(ReturnCustomErrorString(e));, but I get much more useful information. So at the moment I think this solution is pretty good.
If you need simply show a custom message, I wrote this code.
I think that solved, ... for me it's ok.
try
{
app.selection[0].contents = 1
}
catch (myError)
{
alert(myError.number); // For check the number error
if (myError.number == 30477)
{
alert("Mensagem Edu\n" + "Line: " + myError.line + "\n" + "File: " + myError.fileName + "\n" + "Message: " + myError.message + "\n" + "Stack: " + myError.stack);
exit();
}
else (myError);
{}
exit();
}
I also found that extending the Error object causes issues, it has a "special status" in ExtendScript, unfortunately.
The best I could come up with is the following:
function WrappedError(error) {
return {
name: 'ExtendScriptError',
message: error.message,
source: error.source,
line: error.line,
stack: $.stack,
}
}
Which is used like this:
throw WrappedError(new Error('Error Message Goes Here'))
The key to making it work is creating a real "Error" (object), on the actual line where the error is occurring, this way, we can get the correct line number in our wrapped error, and we also have access to the "err.source", which will be interesting for providing context later.
Next, when evaluating ExtendScript code from the "CEP" side, I wrap the call in a try / catch:
function safeRun(code) {
const safeCode = `(function () {
function errorToPretty (err) {
var stack = (err.stack && err.stack.split('\\n')) || []
var lines = (err.source && err.source.split('\\n')) || []
err.line--
stack.shift()
stack.shift()
stack.pop()
stack.reverse()
return {
name: err.name,
message: err.message,
line: err.line,
code: err.code,
context: [
lines[err.line - 2] || '',
lines[err.line - 1] || '',
'---> ' + lines[err.line] || '',
lines[err.line + 1] || '',
lines[err.line + 2] || ''
],
stack: stack
}
}
try {
return ${code}
} catch (err) {
return JSON.stringify(errorToPretty(err))
}
})()`
return evalExtendscript(safeCode).then((res) => {
if (typeof res === 'object' && res.stack && res.line && res.message && res.context) {
const e = new Error(res.message + '\n\n' + res.context.join('\n'))
e.name = res.name
e.stack = `${res.name}: ${res.message}
${res.stack.map((func) => `at ${func} (extendscript.js:0:0)`).join('\n ')}`
throw e
}
return res
})
}