April 28, 2012

mjmvisser mjmvisser
Lab Rat
17 posts

QML syntax for QVariantMaps?

 

I’m using QML as a general scene language for a node-graph type of project I’m doing. This syntax works OK:

  1. Scene {
  2.     contents: [
  3.         SomeNode {
  4.             name: "node1"
  5.             values: [
  6.                 Value {
  7.                     name: "v1"
  8.                     value: 1
  9.                 },
  10.                 Value {
  11.                     name: "v2"
  12.                     value: 2
  13.                 }
  14.             ]
  15.         }
  16.     ]
  17. }

Where Scene.qml defines the “contents” property (as list), and SomeNode.qml defines the “name” (string) and “values” (list<Value>) property.

What I really want is that “contents” and “values” be of type map<Value> (which obviously doesn’t exist in QML). They would then be available in Javascript as an Object, and in C++ as a QVariantMap. In QML, it might look like this:

  1. Scene {
  2.     contents: {
  3.         "node1": SomeNode {
  4.             values: {
  5.                 "v1": Value {
  6.                     name: "v1"
  7.                     value: 1
  8.                 },
  9.                 "v2": Value {
  10.                     name: "v2"
  11.                     value: 2
  12.                 }
  13.             }
  14.         }
  15.     }
  16. }

This seems like a natural and useful extension to QML. Or am I smoking crack? :-)

cheers,
-Mark

6 replies

April 30, 2012

chriadam chriadam
Lab Rat
181 posts

JavaScript objects are converted to QVariantMaps when assigned to variant properties.
That is, if you have:

  1. Item {
  2.     property variant myMap: { "v1": { "name": "v1", "value": 1 }, "v2" : {"name": "v2", "value": 2} } // or whatever
  3. }

And then you access that property from C++ via QObject::property(), it will be a QVariantMap.

The problem (I believe) is that you’ve defined contents and values as a list property (I believe), and hence you are assigning those properties array values.

Or have I misunderstood your question?

Cheers,
Chris.

[Wrapped code in @ tags; mlong]

May 1, 2012

mjmvisser mjmvisser
Lab Rat
17 posts

Sorta. The difference is that in your example, myMap can only contain basic types, not QObject-derived types. Also, changing a value inside myMap won’t generate a notification signal.

I thought about it some more, and my conclusion is that I’ll need to think about it some more. :-) QtQuick is a weird and wonderful blend of static and dynamic-ness, and I’m still trying to wrap my head around it.

cheers,
-Mark

May 1, 2012

chriadam chriadam
Lab Rat
181 posts

Ah, I see.

Yeah, in QtQuick 2 a lot of those problems were solved with the addition of “var” properties (which store JS references). You can store basically anything in them (references to JS arrays, functions, objects, qobject-derived instance references, etc). But, they still don’t notify if one of the values changes – we had an implementation which allowed construction of a “bindable/notifying” js object, but in practice its performance was so bad that we decided against including it. The plan is to add support for it in the future (there is a task on the public bugtracker about bindable js objects) but no ETA on when it’ll be complete.

Cheers,
Chris.

May 2, 2012

mjmvisser mjmvisser
Lab Rat
17 posts

Well, here’s what I’m trying to do. Maybe there’s an idiomatic approach that I’m missing?

There are Nodes and Params. Nodes do things, like raise a number to a power, or compute the BRDF of a surface. Params define the inputs and outputs of Nodes, and have associated metadata for use by the UI presenting them.

For example, say an Pow raises “base” to “expo”:

  1. // Pow.qml
  2. import QtQuick 1.1
  3.  
  4. Node {
  5.     label: "Power"
  6.  
  7.     params: [
  8.         Param {
  9.             id: baseParam
  10.             name: "base"
  11.             label: "Base"
  12.         },
  13.         Param {
  14.             id: expoParam
  15.             name: "expo"
  16.             label: "Exponent"
  17.         },
  18.         Result {
  19.             id: result
  20.             name: "result"
  21.             display: "hidden"
  22.         }
  23.     ]
  24.     property alias base: baseParam.value
  25.     property alias expo: expoParam.value
  26.     property alias result: resultParam.value
  27. }

One would then instantiate a Pow object like so:

  1. Pow {
  2.     base: 10
  3.     expo: 2
  4. }

(I’m ignoring the actual computation of result for now. Insert hand-waving.)

Querying the “result” property would give you 10^2. Pretty useless, but if you have multiple nodes, you can connect them together:

  1. Pow {
  2.     id: pow1
  3.     base: 10
  4.     expo: 2
  5. }
  6. Pow {
  7.     id: pow2
  8.     base: pow1.result
  9.     expo: pow1.result
  10. }

A rather useless example, but you can see that pow2.result will magically evaluate (10^2)^(10^2).

My problem is that I want Params to be somewhat dynamic, for example:

  1. // Add.qml
  2. Node {
  3.     label: "Add Numbers"
  4.  
  5.     params: [
  6.         Repeater {
  7.             id: vParam
  8.             model: 1
  9.             Param {
  10.                 name: "v" + index
  11.                 label: "Value " + index
  12.             }
  13.         },
  14.         Param {
  15.             id: resultParam
  16.             name: result
  17.             display: "hidden"
  18.         }
  19.     ]
  20.  
  21.     property alias count: vParam.model
  22.     property alias v1: vParam.????
  23.     property alias v2: vParam.????
  24.     ...
  25.     property alias vN: vParam.????
  26.     property alias result: resultParam.value
  27. }

And after instantiation:

  1. // ...
  2.  
  3. Add {
  4.     id: add1
  5.     count: 3
  6.     v1: someOtherNode.result
  7.     v2: etc.result
  8.     v3: 25
  9. }

This dynamism is a recurring motif in the project I’m planning, so I’m trying to find a good idiom to represent it using QML. I’m not ruling out writing my own QtDeclarative classes with custom parsers (a la ListElement) if that’s what it takes. But even if that’s necessary, I’d rather build on top of best-practices than beat QML into submission with a hammer. :-)

May 3, 2012

chriadam chriadam
Lab Rat
181 posts

I spoke to Michael Brasser about this briefly today. I’m not sure if I fully understood what you’re trying to achieve, but basically I came up with the following:

  1. // Add.qml - provides the "Add" node implementation
  2. import QtQuick 2.0
  3. Item {
  4.     id: addElement
  5.  
  6.     // all node types must have the following three properties
  7.     property bool isANode: true
  8.     property var inputs
  9.     property var result
  10.  
  11.     // this function will be the same for all node/operation types.
  12.     onInputsChanged: {
  13.         // disconnect all current connections.
  14.         onAnyInputResultChanged.disconnectEverything(); // not sure what the "actual" api is to do this...
  15.  
  16.         // create new connections.
  17.         for (var i = 0; i < inputs.length; ++i) {
  18.             var currInput = inputs[i];
  19.             if (currInput.isObject()) {
  20.                 if (currInput.isANode) {
  21.                     currInput.onResultChanged.connect(inputResultChanged);
  22.                 }
  23.             }
  24.         }
  25.  
  26.         // update result.
  27.         inputResultChanged();
  28.     }
  29.  
  30.     // this function will be different for each node/operation type.
  31.     // the Add node simply adds up the various inputs' results / values,
  32.     // and exposes the result via its result property.
  33.     function inputResultChanged() {
  34.         var tmp = 0;
  35.         for (var i = 0; i < inputs.length; ++i) {
  36.             var currInput = inputs[i];
  37.             if (currInput.isObject()) {
  38.                 if (currInput.isANode) {
  39.                     tmp += currInput.result.valueOf(); // grab the node's result as a number.
  40.                 } else {
  41.                     tmp += currInput.valueOf(); // convert random object to number.
  42.                 }
  43.             } else {
  44.                 tmp += currInput; // must be a number or primitive already.
  45.             }
  46.         }
  47.  
  48.         result = tmp; // will cause notify signal to be emitted.
  49.     }
  50. }

That way, at run-time, you can dynamically create Add nodes, and modify the input nodes as required. When the input nodes change, or when the result property of any input nodes change, the result of the node will be automatically updated.

The one “tricky” bit is the “disconnect all current connects to the auto-update function on change” (prior to rebuilding it) — I’m not sure what the API is for that, but I’m fairly certain it’s possible (there’s a notify list or signal connection list there somewhere, which can be updated).

Cheers,
Chris.

May 8, 2012

mjmvisser mjmvisser
Lab Rat
17 posts

Thanks Chris. I haven’t had a chance to build QtQuick 2.0 yet, but from your example, it looks like the new var type is exactly what I was looking for.

cheers,
-Mark

 
  ‹‹ PathView currentItem      Problem with itemAt function in repeater element ››

You must log in to post a reply. Not a member yet? Register here!