Browse Source

ValueAnnotation integration with example configuration

Neal Wilson 11 years ago
parent
commit
81afd9585f
3 changed files with 129 additions and 38 deletions
  1. 24
    0
      config/Windows/productionroaster.xml
  2. 26
    23
      src/typica.w
  3. 79
    15
      src/valueannotation.w

+ 24
- 0
config/Windows/productionroaster.xml View File

@@ -337,6 +337,30 @@
337 337
 					annotationPanel.addWidget(button);
338 338
 					tabControls.push(button);
339 339
 				}
340
+				else if(driverReference.driver == "valueannotation")
341
+				{
342
+					var checker = new ValueAnnotation;
343
+					var valuesSetting = driverReference.measuredValues;
344
+					var notesSetting = driverReference.annotations;
345
+					var valuesList = valuesSetting.slice(2, valuesSetting.length-2).split(",");
346
+					var notesList = notesSetting.slice(2, notesSetting.length-2).split(",");
347
+					if(valuesList.length > 1 && notesList.length == valuesList.length) {
348
+						for(var j = 0; j < valuesList.length; j++) {
349
+							checker.setAnnotation(Number(valueList[j]), notesList[j]);
350
+						}
351
+					}
352
+					if(driverReference.emitOnStart == "true") {
353
+						start.clicked.connect(checker.annotate);
354
+					}
355
+					var colname = driverReference.source;
356
+					for(var j = 0; j < columnNames.length; j++) {
357
+						if(columnNames[j] == colname) {
358
+							channels[j].newData.connect(checker.newMeasurement);
359
+							break;
360
+						}
361
+					}
362
+					annotationButtons.push(checker);
363
+				}
340 364
 				else if(driverReference.driver == "reconfigurablebutton")
341 365
 				{
342 366
 					var button = new AnnotationButton(driverReference.buttontext);

+ 26
- 23
src/typica.w View File

@@ -840,6 +840,8 @@ generated file empty.
840 840
 @<GraphSettingsWidget implementation@>@/
841 841
 @<DataqSdkDeviceConfWidget implementation@>@/
842 842
 @<SerialScaleConfWidget implementation@>@/
843
+@<ValueAnnotation implementation@>@/
844
+@<ValueAnnotationConfWidget implementation@>@/
843 845
 
844 846
 @ A few headers are required for various parts of \pn{}. These allow the use of
845 847
 various Qt modules.
@@ -10017,7 +10019,7 @@ oseconds = TIMETOINT(relative);
10017 10019
 r = cseconds - oseconds;
10018 10020
 
10019 10021
 @ The logic for a count down timer is very similar to the logic for a count up
10020
-timer. A key difference is that we don't want to continue counting down if the
10022
+timer. A key difference is that we don'@q'@>t want to continue counting down if the
10021 10023
 timer has already reached 0.
10022 10024
 
10023 10025
 @<Check for Timer Decrement@>=
@@ -10077,7 +10079,7 @@ void TimerDisplay::startTimer()@t\2\2@>@/
10077 10079
 }
10078 10080
 
10079 10081
 @ Stopping the timer is a little simpler. Remember to stop the clock so we
10080
-aren't updating senselessly.
10082
+aren'@q'@>t updating senselessly.
10081 10083
 
10082 10084
 @<TimerDisplay Implementation@>=
10083 10085
 void TimerDisplay::stopTimer()@t\2\2@>@/
@@ -11982,7 +11984,7 @@ the last row to increase the size of the table.
11982 11984
 
11983 11985
 The end of this function may seem a little strange. Why not simply look up the
11984 11986
 map and insert information directly into the model data? Well, as of this
11985
-writing, that doesn't work. There are two ways around that problem. One is to
11987
+writing, that doesn'@q'@>t work. There are two ways around that problem. One is to
11986 11988
 have the lists store references and dereference the real data. The other option
11987 11989
 is to obtain a copy of the row, then a copy of the cell, update the cell, then
11988 11990
 replace the old value of the cell in the copy of the row, then replace the old
@@ -12057,7 +12059,7 @@ SaltModel::SaltModel(int columns) : QAbstractItemModel(), colcount(columns)
12057 12059
 	@<Expand the SaltModel@>@;
12058 12060
 }
12059 12061
 
12060
-@ The destructor doesn't need to do anything.
12062
+@ The destructor doesn'@q'@>t need to do anything.
12061 12063
 
12062 12064
 @<SaltModel Implementation@>=
12063 12065
 SaltModel::~SaltModel()
@@ -12476,7 +12478,7 @@ if(settings.value("database/exists", "false").toString() == "false")
12476 12478
 @ In order to connect to the database, we need five pieces of information: the
12477 12479
 name of a database driver (PostgreSQL is recommended for now), the host name of
12478 12480
 the computer running the database, the name of the database, the name of the
12479
-user connecting to the database, and that user's password. This information will
12481
+user connecting to the database, and that user'@q'@>s password. This information will
12480 12482
 be stored in the user settings for the application so that the database
12481 12483
 connection can be established without prompting the user next time. A class is
12482 12484
 provided to gather this information.
@@ -12669,7 +12671,7 @@ settings.setValue(QString("columnWidths/%1/%2/%3").
12669 12671
 				  QVariant(newsize));
12670 12672
 
12671 12673
 @ To determine which window a given table is in, we just follow
12672
-|parentWidget()| until there isn't one. It is possible that the table view
12674
+|parentWidget()| until there isn'@q'@>t one. It is possible that the table view
12673 12675
 will also be the window, however this is not advised as it is easier for the
12674 12676
 settings key to be non-unique in such a case.
12675 12677
 
@@ -12971,7 +12973,7 @@ for(int j = 0; j < hierarchy.size() - 1; j++)
12971 12973
 in Qt. This brings several benefits, including making it easy to print reports
12972 12974
 or save reports as plain text or HTML.
12973 12975
 
12974
-Reports are specified in the \pn{}'s configuration document and can include both
12976
+Reports are specified in the \pn{}'@q'@>s configuration document and can include both
12975 12977
 static elements and elements that are populated by external data such as the
12976 12978
 result of a SQL query.
12977 12979
 
@@ -13223,7 +13225,7 @@ do
13223 13225
 @ It is sometimes desirable to add fixed data such as column headers to a table.
13224 13226
 This is done with the {\tt <row>} element.
13225 13227
 
13226
-Technically, this isn't needed. The same results can be produced by using a
13228
+Technically, this isn'@q'@>t needed. The same results can be produced by using a
13227 13229
 {\tt <query>} element to select constant data, but this approach saves a trip to
13228 13230
 the database.
13229 13231
 
@@ -14210,7 +14212,7 @@ else
14210 14212
 	saveDeviceConfiguration();
14211 14213
 }
14212 14214
 
14213
-@ There isn't really anything that can be done if the device configuration data
14215
+@ There isn'@q'@>t really anything that can be done if the device configuration data
14214 14216
 is corrupt, but an error message can be produced if the program happens to have
14215 14217
 access to a debugging console.
14216 14218
 
@@ -14600,7 +14602,7 @@ QDomElement DeviceTreeModel::referenceElement(const QString &id)
14600 14602
 	return QDomElement();
14601 14603
 }
14602 14604
 
14603
-@ We don't want any headers, so |headerData()| is very simple.
14605
+@ We don'@q'@>t want any headers, so |headerData()| is very simple.
14604 14606
 
14605 14607
 @<DeviceTreeModel implementation@>=
14606 14608
 QVariant DeviceTreeModel::headerData(int, Qt::Orientation, int) const
@@ -15228,6 +15230,7 @@ RoasterConfWidget::RoasterConfWidget(DeviceTreeModel *model, const QModelIndex &
15228 15230
 	        this, SLOT(insertChildNode(QString, QString)));
15229 15231
 	connect(freeAnnotationInserter, SIGNAL(triggered(QString, QString)),
15230 15232
 	        this, SLOT(insertChildNode(QString, QString)));
15233
+	@<Add annotation control node inserters@>@;
15231 15234
 	addAnnotationControlButton->setMenu(annotationMenu);
15232 15235
 	layout->addWidget(addAnnotationControlButton);
15233 15236
 	QPushButton *advancedButton = new QPushButton(tr("Advanced Features"));
@@ -15816,7 +15819,7 @@ by the current operating system are available to select.
15816 15819
 A later version of QextSerialPort than is used by \pn{} provides a helper
15817 15820
 class which can be used more conveniently to create this sort of control. As
15818 15821
 this is not yet available to \pn{}, we instead copy the |enum| specifying
15819
-the appropriate values into the class and use Qt's meta-object system to
15822
+the appropriate values into the class and use Qt'@q'@>s meta-object system to
15820 15823
 populate the combo box based on the values in that |enum|.
15821 15824
 
15822 15825
 @<Class declarations@>=
@@ -17304,7 +17307,7 @@ void ModbusRTUDevice::mResponse(QByteArray response)
17304 17307
 }
17305 17308
 
17306 17309
 @ There are two ways that we might request measurement data. All of the
17307
-devices I've seen documented provide function 0x4 addresses for PV and SV
17310
+devices I'@q'@>ve seen documented provide function 0x4 addresses for PV and SV
17308 17311
 such that SV can be obtained from the address immediately after the address
17309 17312
 from which we obtain PV. In this case we request both values at the same time.
17310 17313
 
@@ -17389,7 +17392,7 @@ more than one response in the buffer at a time. It is, however, likely that
17389 17392
 this buffer will have incomplete data. This means that we must determine when
17390 17393
 the full response is available before passing the complete response along to
17391 17394
 the appropriate method. If the response has not been received in full, nothing
17392
-is done. We'll be notified of more data shortly.
17395
+is done. We'@q'@>ll be notified of more data shortly.
17393 17396
 
17394 17397
 When the message we see the response for was queued, a callback was also
17395 17398
 registered to handle the response. Once we have the complete message, we pass
@@ -17562,7 +17565,7 @@ void ModbusRTUDevice::outputSV(double value)
17562 17565
 	queueMessage(message, this, "ignore(QByteArray)");
17563 17566
 }
17564 17567
 
17565
-@ We don't care about the response when sending a new SV.
17568
+@ We don'@q'@>t care about the response when sending a new SV.
17566 17569
 
17567 17570
 @<ModbusRTUDevice implementation@>=
17568 17571
 void ModbusRTUDevice::ignore(QByteArray)
@@ -18183,7 +18186,7 @@ class LinearSplineInterpolationConfWidget : public BasicDeviceConfigurationWidge
18183 18186
 		void updateDestinationColumn(const QString &dest);
18184 18187
 		void updateKnots();
18185 18188
 	private:@/
18186
-		SaltModel *model;
18189
+		SaltModel *tablemodel;
18187 18190
 };
18188 18191
 
18189 18192
 @ This is configured by specifying a source column name, a destination column
@@ -18192,17 +18195,17 @@ the mapping data, we store each column of the table in its own attribute.
18192 18195
 
18193 18196
 @<LinearSplineInterpolationConfWidget implementation@>=
18194 18197
 LinearSplineInterpolationConfWidget::LinearSplineInterpolationConfWidget(DeviceTreeModel *model, const QModelIndex &index)
18195
-: BasicDeviceConfigurationWidget(model, index), model(new SaltModel(2))
18198
+: BasicDeviceConfigurationWidget(model, index), tablemodel(new SaltModel(2))
18196 18199
 {
18197 18200
 	QFormLayout *layout = new QFormLayout;
18198 18201
 	QLineEdit *source = new QLineEdit;
18199 18202
 	layout->addRow(tr("Source column name:"), source);
18200 18203
 	QLineEdit *destination = new QLineEdit;
18201 18204
 	layout->addRow(tr("Destination column name:"), destination);
18202
-	model->setHeaderData(0, Qt::Horizontal, "Input");
18203
-	model->setHeaderData(1, Qt::Horizontal, "Output");
18205
+	tablemodel->setHeaderData(0, Qt::Horizontal, "Input");
18206
+	tablemodel->setHeaderData(1, Qt::Horizontal, "Output");
18204 18207
 	QTableView *mappingTable = new QTableView;
18205
-	mappingTable->setModel(model);
18208
+	mappingTable->setModel(tablemodel);
18206 18209
 	NumericDelegate *delegate = new NumericDelegate;
18207 18210
 	mappingTable->setItemDelegate(delegate);
18208 18211
 	layout->addRow(tr("Mapping data:"), mappingTable);
@@ -18237,7 +18240,7 @@ LinearSplineInterpolationConfWidget::LinearSplineInterpolationConfWidget(DeviceT
18237 18240
 	updateKnots();
18238 18241
 	connect(source, SIGNAL(textEdited(QString)), this, SLOT(updateSourceColumn(QString)));
18239 18242
 	connect(destination, SIGNAL(textEdited(QString)), this, SLOT(updateDestinationColumn(QString)));
18240
-	connect(model, SIGNAL(dataChanged(QModelIndex, QModelIndex)), this, SLOT(updateKnots()));
18243
+	connect(tablemodel, SIGNAL(dataChanged(QModelIndex, QModelIndex)), this, SLOT(updateKnots()));
18241 18244
 	setLayout(layout);
18242 18245
 }
18243 18246
 
@@ -18260,7 +18263,7 @@ model.
18260 18263
 @<Populate model column from list@>=
18261 18264
 for(int i = 0; i < itemList.size(); i++)
18262 18265
 {
18263
-	model->setData(model->index(i, column),
18266
+	tablemodel->setData(tablemodel->index(i, column),
18264 18267
 	               QVariant(itemList.at(i).toDouble()),
18265 18268
                    Qt::DisplayRole);
18266 18269
 }
@@ -18271,8 +18274,8 @@ data with the current data.
18271 18274
 @<LinearSplineInterpolationConfWidget implementation@>=
18272 18275
 void LinearSplineInterpolationConfWidget::updateKnots()
18273 18276
 {
18274
-	updateAttribute("sourcevalues", model->arrayLiteral(0, Qt::DisplayRole));
18275
-	updateAttribute("destinationvalues", model->arrayLiteral(1, Qt::DisplayRole));
18277
+	updateAttribute("sourcevalues", tablemodel->arrayLiteral(0, Qt::DisplayRole));
18278
+	updateAttribute("destinationvalues", tablemodel->arrayLiteral(1, Qt::DisplayRole));
18276 18279
 }
18277 18280
 
18278 18281
 void LinearSplineInterpolationConfWidget::updateSourceColumn(const QString &source)

+ 79
- 15
src/valueannotation.w View File

@@ -11,7 +11,7 @@ To support this feature, there must be a configuration widget which can be used
11 11
 to identify which data series should be monitored and what annotations should
12 12
 be produced for what values.
13 13
 
14
-@<Class declrations@>=
14
+@<Class declarations@>=
15 15
 class ValueAnnotationConfWidget : public BasicDeviceConfigurationWidget
16 16
 {
17 17
 	Q_OBJECT
@@ -23,7 +23,7 @@ class ValueAnnotationConfWidget : public BasicDeviceConfigurationWidget
23 23
 		void updateAnnotations();
24 24
 		void updateStart(bool noteOnStart);
25 25
 	private:
26
-		SaltModel *model;
26
+		SaltModel *tablemodel;
27 27
 };
28 28
 
29 29
 @ The constructor sets up the configuration interface requesting a source
@@ -34,7 +34,7 @@ what annotations should be produced for what values.
34 34
 ValueAnnotationConfWidget::ValueAnnotationConfWidget(DeviceTreeModel *model,
35 35
                                                      const QModelIndex &index)
36 36
 : BasicDeviceConfigurationWidget(model, index),
37
-  model(new SaltModel(2))
37
+  tablemodel(new SaltModel(2))
38 38
 {
39 39
 	QFormLayout *layout = new QFormLayout;
40 40
 	QLineEdit *source = new QLineEdit;
@@ -42,14 +42,14 @@ ValueAnnotationConfWidget::ValueAnnotationConfWidget(DeviceTreeModel *model,
42 42
 	QCheckBox *noteOnStart = new QCheckBox(tr("Produce Start State Annotation"));
43 43
 	noteOnStart->setChecked(true);
44 44
 	layout->addRow(noteOnStart);
45
-	model->setHeaderData(0, Qt::Horizontal, "Value");
46
-	model->setHeaderData(1, Qt::Horizontal, "Annotation");
45
+	tablemodel->setHeaderData(0, Qt::Horizontal, "Value");
46
+	tablemodel->setHeaderData(1, Qt::Horizontal, "Annotation");
47 47
 	QTableView *annotationTable = new QTableView;
48
-	annotationTable->setModel(model);
48
+	annotationTable->setModel(tablemodel);
49 49
 	NumericDelegate *delegate = new NumericDelegate;
50 50
 	annotationTable->setItemDelegateForColumn(0, delegate);
51 51
 	layout->addRow(tr("Annotations for values:"), annotationTable);
52
-	@<Get device configuration data for current node@>=
52
+	@<Get device configuration data for current node@>@;
53 53
 	for(int i = 0; i < configData.size(); i++)
54 54
 	{
55 55
 		node = configData.at(i).toElement();
@@ -69,9 +69,9 @@ ValueAnnotationConfWidget::ValueAnnotationConfWidget(DeviceTreeModel *model,
69 69
 		}
70 70
 		else if(node.attribute("name") == "annotations")
71 71
 		{
72
-			@<Convert numeric array literal to list@>@;
72
+			@<Convert string array literal to list@>@;
73 73
 			int column = 1;
74
-			@<Populate model column from list@>@;
74
+			@<Populate model column from string list@>@;
75 75
 		}
76 76
 	}
77 77
 	updateSourceColumn(source->text());
@@ -79,18 +79,44 @@ ValueAnnotationConfWidget::ValueAnnotationConfWidget(DeviceTreeModel *model,
79 79
 	updateAnnotations();
80 80
 	connect(source, SIGNAL(textEdited(QString)), this, SLOT(updateSourceColumn(QString)));
81 81
 	connect(noteOnStart, SIGNAL(toggled(bool)), this, SLOT(updateStart(bool)));
82
-	connect(model, SIGNAL(dataChanged(QModelIndex, QModelIndex)), this, SLOT(updateAnnotations()));
82
+	connect(tablemodel, SIGNAL(dataChanged(QModelIndex, QModelIndex)), this, SLOT(updateAnnotations()));
83 83
 	setLayout(layout);
84 84
 }
85 85
 
86
+@ While we can re-use code for handling numeric array literals, string array
87
+literals need to be handled a little differently.
88
+
89
+@<Convert string array literal to list@>=
90
+QString data = node.attribute("value");
91
+if(data.length() > 3)
92
+{
93
+	data.chop(2);
94
+	data = data.remove(0, 2);
95
+}
96
+QStringList itemList = data.split(",");
97
+for(int i = 0; i < itemList.size(); i++)
98
+{
99
+	itemList[i] = itemList[i].simplified();
100
+}
101
+
102
+@ Populating the model must also be done a little differently.
103
+
104
+@<Populate model column from string list@>=
105
+for(int i = 0; i < itemList.size(); i++)
106
+{
107
+	tablemodel->setData(tablemodel->index(i, column),
108
+	                    QVariant(itemList.at(i)),
109
+	                    Qt::DisplayRole);
110
+}
111
+
86 112
 @ To update the table data, the measued values and annotations are saved in
87 113
 separate lists.
88 114
 
89 115
 @<ValueAnnotationConfWidget implementation@>=
90 116
 void ValueAnnotationConfWidget::updateAnnotations()
91 117
 {
92
-	updateAttribute("measuredValues", model->arrayLiteral(0, Qt::DisplayRole));
93
-	updateAttribute("annotations", model->arrayLiteral(1, Qt::DisplayRole));
118
+	updateAttribute("measuredValues", tablemodel->arrayLiteral(0, Qt::DisplayRole));
119
+	updateAttribute("annotations", tablemodel->arrayLiteral(1, Qt::DisplayRole));
94 120
 }
95 121
 
96 122
 @ The other settings are updated based on values passed through the parameter
@@ -104,14 +130,24 @@ void ValueAnnotationConfWidget::updateSourceColumn(const QString &source)
104 130
 
105 131
 void ValueAnnotationConfWidget::updateStart(bool noteOnStart)
106 132
 {
107
-	updateAttribute("emitOnStart", noteOnStart);
133
+	updateAttribute("emitOnStart", noteOnStart ? "true" : "false");
108 134
 }
109 135
 
110 136
 @ The widget is registered with the configuration system.
111 137
 
112 138
 @<Register device configuration widgets@>=
113 139
 app.registerDeviceConfigurationWidget("valueannotation",
114
-	ValueAnnotationConfWidget::staticMetaObjet);
140
+	ValueAnnotationConfWidget::staticMetaObject);
141
+
142
+@ A NodeInserter is needed to make this widget available.
143
+
144
+@<Add annotation control node inserters@>=
145
+NodeInserter *valueAnnotationInserter = new NodeInserter(tr("Value Annotation"),
146
+                                                         tr("Value Annotation"),
147
+                                                         "valueannotation");
148
+annotationMenu->addAction(valueAnnotationInserter);
149
+connect(valueAnnotationInserter, SIGNAL(triggered(QString, QString)),
150
+        this, SLOT(insertChildNode(QString, QString)));
115 151
 
116 152
 @ While it is possible to implement this feature with |ThresholdDetector|
117 153
 objects, the code to handle these would be difficult to understand and there
@@ -148,7 +184,7 @@ class ValueAnnotation : public QObject
148 184
 		QList<double> values;
149 185
 		QStringList annotations;
150 186
 		double tolerance;
151
-}
187
+};
152 188
 
153 189
 @ Most of the work of this class happens in the |newMeasurement| method. This
154 190
 compares the latest measurement with every value that has an associated
@@ -223,3 +259,31 @@ ValueAnnotation::ValueAnnotation() : QObject(),
223 259
 	/* Nothing needs to be done here. */
224 260
 }
225 261
 
262
+@ This class is exposed to the host environment in the usual way. First with
263
+function prototypes.
264
+
265
+@<Function prototypes for scripting@>=
266
+QScriptValue constructValueAnnotation(QScriptContext *context, QScriptEngine *engine);
267
+void setValueAnnotationProperties(QScriptValue value, QScriptEngine *engine);
268
+
269
+@ Then setting up a new property of the global object.
270
+
271
+@<Set up the scripting engine@>=
272
+constructor = engine->newFunction(constructValueAnnotation);
273
+value = engine->newQMetaObject(&ValueAnnotation::staticMetaObject, constructor);
274
+engine->globalObject().setProperty("ValueAnnotation", value);
275
+
276
+@ The implementation of the functions also proceeds as usual.
277
+
278
+@<Functions for scripting@>=
279
+QScriptValue constructValueAnnotation(QScriptContext *context, QScriptEngine *engine)
280
+{
281
+	QScriptValue object = engine->newQObject(new ValueAnnotation);
282
+	setValueAnnotationProperties(object, engine);
283
+	return object;
284
+}
285
+
286
+void setValueAnnotationProperties(QScriptValue value, QScriptEngine *engine)
287
+{
288
+	setQObjectProperties(value, engine);
289
+}

Loading…
Cancel
Save