Main goal is make ContextProperty for QML like QVariantMap with access operator []:
For example cpp file:
...
QVariantMap qvm; qvm["zero"] = "it's OK";
view.rootContext()->setContextProperty("qvm",qvm);
...
qml file:
console.log("qvm[zero] === "+qvm["zero"]); // output: qvm[zero] === it's OK
but with my class:
class MyStore : public QObject
{
Q_OBJECT
public:
MyStore(QObject* parent=nullptr):QObject(parent){
}
MyStore(const MyStore &other,QObject* parent=nullptr):QObject(parent){ store = other.store; }
~MyStore(){}
inline const QVariant &operator[](QString i) const {
static const QVariant val(store[i]);
return val;
}
QVariantMap store;
};
Q_DECLARE_METATYPE(MyStore);
cpp file:
store.store["zero"] = "OK only in cpp";
qDebug("main: store[\"zero\"] == %s", store["zero"].toString().toAscii().data()); // output: main: store["zero"] == "OK only in cpp"
view.rootContext()->setContextProperty("mystore",&store);
qml file:
console.log("qml: mystore[\"zero\"] === "+mystore["zero"]); // output: qml: mystore["zero"] === undefined
How to implement for output:
qml: mystore["zero"] === "OK only in cpp"
?
Related
I am currently working on a bokeh application with continuous AND categorical data in the same Dataframe. Since the PointDrawTool of bokeh only comes with support for continuous data, I need to write a custom PointDrawTool myself. I followed the custom tools tutorial (https://docs.bokeh.org/en/2.4.1/docs/user_guide/extensions_gallery/tool.html) and changed the TypeScript code. I just copied the source code of the PointDrawTool (https://github.com/bokeh/bokeh/blob/branch-3.0/bokehjs/src/lib/models/tools/edit/point_draw_tool.ts) and fixed all the imports so that I do not run into any TypeScript Errors anymore. However, I always get a weird console error when loading the page. Additionally, the page remains completely blank and none of my widgets are showing.
Error in console: "Failed to repull session TypeError: this.properties[e] is undefined"
Code in draw_tool.py:
from bokeh.core.properties import Instance
from bokeh.io import output_file, show
from bokeh.models import ColumnDataSource, Tool
from bokeh.plotting import figure
from bokeh.util.compiler import TypeScript
output_file('tool.html')
TS_CODE = """
import {Keys} from "core/dom"
import {PanEvent, TapEvent, KeyEvent} from "core/ui_events"
import * as p from "core/properties"
import {GlyphRenderer} from "models/renderers/glyph_renderer"
import {EditTool, EditToolView, HasXYGlyph} from "models/tools/edit/edit_tool"
import {tool_icon_point_draw} from "styles/icons.css"
export class CustomDrawToolView extends EditToolView {
override model: CustomDrawTool
override _tap(ev: TapEvent): void {
const renderers = this._select_event(ev, this._select_mode(ev), this.model.renderers)
if (renderers.length || !this.model.add) {
return
}
const renderer = this.model.renderers[0]
const point = this._map_drag(ev.sx, ev.sy, renderer)
if (point == null)
return
// Type once dataspecs are typed
const glyph: any = renderer.glyph
const cds = renderer.data_source
const [xkey, ykey] = [glyph.x.field, glyph.y.field]
const [x, y] = point
this._pop_glyphs(cds, this.model.num_objects)
if (xkey) cds.get_array(xkey).push(x)
if (ykey) cds.get_array(ykey).push(y)
this._pad_empty_columns(cds, [xkey, ykey])
const {data} = cds
cds.setv({data}, {check_eq: false}) // XXX: inplace updates
}
override _keyup(ev: KeyEvent): void {
if (!this.model.active || !this._mouse_in_frame)
return
for (const renderer of this.model.renderers) {
if (ev.keyCode === Keys.Backspace) {
this._delete_selected(renderer)
} else if (ev.keyCode == Keys.Esc) {
renderer.data_source.selection_manager.clear()
}
}
}
override _pan_start(ev: PanEvent): void {
if (!this.model.drag)
return
this._select_event(ev, "append", this.model.renderers)
this._basepoint = [ev.sx, ev.sy]
}
override _pan(ev: PanEvent): void {
if (!this.model.drag || this._basepoint == null)
return
this._drag_points(ev, this.model.renderers)
}
override _pan_end(ev: PanEvent): void {
if (!this.model.drag)
return
this._pan(ev)
for (const renderer of this.model.renderers)
this._emit_cds_changes(renderer.data_source, false, true, true)
this._basepoint = null
}
}
export namespace CustomDrawTool {
export type Attrs = p.AttrsOf<Props>
export type Props = EditTool.Props & {
add: p.Property<boolean>
drag: p.Property<boolean>
num_objects: p.Property<number>
renderers: p.Property<(GlyphRenderer & HasXYGlyph)[]>
}
}
export interface CustomDrawTool extends CustomDrawTool.Attrs {}
export class CustomDrawTool extends EditTool {
override properties: CustomDrawTool.Props
override __view_type__: CustomDrawToolView
override renderers: (GlyphRenderer & HasXYGlyph)[]
constructor(attrs?: Partial<CustomDrawTool.Attrs>) {
super(attrs)
}
static {
this.prototype.default_view = CustomDrawToolView
this.define<CustomDrawTool.Props>(({Boolean, Int}) => ({
add: [ Boolean, true ],
drag: [ Boolean, true ],
num_objects: [ Int, 0 ],
}))
}
override tool_name = "Point Draw Tool XX"
tool_icon = tool_icon_point_draw
override event_type = ["tap" as "tap", "pan" as "pan", "move" as "move"]
override default_order = 2
}
"""
class CustomDrawTool(Tool):
__implementation__ = TypeScript(TS_CODE)
source = Instance(ColumnDataSource)
Usage in main.py
from custom_tools.draw_tool import CustomDrawTool
tools = config.TOOLS + [CustomDrawTool(source=data_model.data_X)]
p = figure(height=config.PLOT_HEIGHT, width=config.PLOT_WIDTH, tools=tools, output_backend="webgl",
**kw_figure)
Note: data_model.data_X is just a ColumnDataSource.
Hopefully someone can give me a hint on how to fix the problem and continue with writing my own Tool.
Thanks & best regards
MyObj is the following component:
Item {
id:root
signal foo()
property string bar: "bar"
Component.onCompleted: root.foo()
}
When creating it dynamically via Qt.createComponent(...) -> comp.createObject(...) it is possible to connect JS functions to all signals, except destruction
Following code:
var comp = Qt.createComponent('MyObj.qml');
var finish = () => {
if(comp.status === Component.Error) {
console.log("Error loading component:", comp.errorString())
return
}
if(comp.status !== Component.Ready) {
console.log("Component not ready")
return
}
var obj = comp.createObject(mainWindow, {})
if(obj === null) {
console.log('Error creating object')
return
}
obj.foo.connect(() => console.log('foo!'))
obj.barChanged.connect(() => console.log('barChanged!'))
obj.destruction.connect(() => console.log('destruction!'))
}
if(comp.status !== Component.Loading)
finish();
else
comp.statusChanged.connect(finish);
produces the error:
qrc:/main.qml:32: TypeError: Cannot call method 'connect' of undefined
exactly at the line with obj.destruction.connect(...)
The documentation doesn't mention any such restriction.
What's wrong?
Putting:
Component.onDestruction: console.log("#destruction")
in MyObj works as usual, but that's not what I need.
I didn't realize JS needs the same syntax as QML for accessing properties of the superclass.
The following works just fine:
obj.Component.destruction.connect(() => console.log('destruction!'))
I'm currently using a typescript transformer api, and I found that the node.parent is undefined.
My code is:
const transformerFactory: ts.TransformerFactory<ts.Node> = (
context: ts.TransformationContext
) => {
return (rootNode) => {
function visit(node: ts.Node): ts.Node {
node = ts.visitEachChild(node, visit, context);
// HERE node.parent IS UNDEFINED !
return filterFn(node, context);
}
return ts.visitNode(rootNode, visit);
};
};
const transformationResult = ts.transform(
sourceFile, [transformerFactory]
);
How can I find the parent of the node?
You can parse specifying to set the parent nodes:
const sourceFile = ts.createSourceFile(
"fileName.ts",
"class Test {}",
ts.ScriptTarget.Latest,
/* setParentNodes */ true, // specify this as true
);
Or do some operation on the node to get it to set its parent nodes (ex. type check the program... IIRC during binding it ensures the parent nodes are set).
Update based on comment
If you are creating these from a program, then you can do the following:
const options: ts.CompilerOptions = { allowJs: true };
const compilerHost = ts.createCompilerHost(options, /* setParentNodes */ true);
const program = ts.createProgram([this.filePath], options, compilerHost);
Supposing we have a generic class or an interface:
interface A<T> {
prop1: T;
func2(): T;
}
interface B extends A<C> {
}
interface C {
}
We need to get the return type of the B.func2 method (not T but C).
The way described here works OK for props, but I can't figure out how to modify it for methods:
for (const statement of sourceFile.statements) {
if (!isInterface(statement))
continue;
if (!statement.heritageClauses?.length)
continue;
for (const heritageClause of statement.heritageClauses) {
for (const exprWithTypeArgs of heritageClause.types) {
const baseType = checker.getTypeAtLocation(exprWithTypeArgs);
for (const propSymbol of baseType.getProperties()) {
const resolvedType = checker.getTypeOfSymbolAtLocation(propSymbol, exprWithTypeArgs);
console.log(`${propSymbol.name} has type: ${resolvedType.symbol?.name}`);
// prints
// prop1 has type: C
// func2 has type: func1
for (const propDeclaration of propSymbol.declarations) {
if (!isSignatureDeclaration(propDeclaration))
continue;
const signature = checker.getSignatureFromDeclaration(propDeclaration);
const returnTypeSymbol = checker.getReturnTypeOfSignature(signature)?.symbol;
const resolvedReturnType = checker.getTypeOfSymbolAtLocation(returnTypeSymbol, exprWithTypeArgs);
console.log(`${propSymbol.name} return type: ${resolvedReturnType.symbol?.name}`);
// prints
// func2 return type: undefined
}
}
}
}
}
What is the correct way of getting resolved return type of a method?
The TypeChecker#getSignaturesOfType method allows for getting the signature of a type.
const bDecl = sourceFile.statements[1]; // obviously, improve this
const bType = typeChecker.getTypeAtLocation(bDecl);
const func2Symbol = bType.getProperty("func2")!;
const func2Type = typeChecker.getTypeOfSymbolAtLocation(func2Symbol, func2Symbol.valueDeclaration);
const func2Signature = checker.getSignaturesOfType(func2Type, ts.SignatureKind.Call)[0];
checker.typeToString(func2Signature.getReturnType()); // C
I have this signal
class SystemUICfgScanner
{
/*code here*/
signals:
void error(QString desc);
/*more code*/
};
In QML I use an InfoBanner this way:
InfoBanner
{
property string infodetails: ""
id: systemuicfgErrorBanner
text: "Error: " + infodetails
Connections
{
target: cfgScanner
onError: infodetails = desc
}
}
When error(QString) signal is emitted, I'm getting this error
Invalid write to global property "infodetails"
What am I doing wrong?
Thanks in advance
Try to reference InfoBanner instance by id:
InfoBanner
{
property string infodetails: ""
id: systemuicfgErrorBanner
text: "Error: " + infodetails
Connections
{
target: cfgScanner
onError: systemuicfgErrorBanner.infodetails = desc
}
}