Bläddra i källkod

Merge branch 'development' into sample-roasting

Neal Wilson 11 år sedan
förälder
incheckning
579dd2ad8e
9 ändrade filer med 1512 tillägg och 91 borttagningar
  1. 1
    1
      config/Windows/navigation.xml
  2. 41
    0
      config/Windows/newbatch.xml
  3. 152
    53
      config/Windows/productionroaster.xml
  4. 6
    2
      src/Typica.pro
  5. 205
    7
      src/dataqsdk.w
  6. 498
    0
      src/scales.w
  7. 104
    26
      src/typica.w
  8. 216
    2
      src/units.w
  9. 289
    0
      src/valueannotation.w

+ 1
- 1
config/Windows/navigation.xml Visa fil

@@ -160,7 +160,7 @@ type="push" />
160 160
             if(typeof(window.loggingWindow) == "undefined")
161 161
             {
162 162
                 window.loggingWindow = createWindow("basicWindow");
163
-                window.loggingWindow.windowTitle = "Typica";
163
+                window.loggingWindow.windowTitle = "Typica [*]";
164 164
 				window.loggingWindow.navigationWindow = window;
165 165
             }
166 166
             else

+ 41
- 0
config/Windows/newbatch.xml Visa fil

@@ -2,6 +2,7 @@
2 2
     <menu name="Batch">
3 3
         <item id="new" shortcut="Ctrl+N">New Batch…</item>
4 4
     </menu>
5
+	<layout type="horizontal">
5 6
     <layout type="vertical">
6 7
         <layout type="horizontal">
7 8
 			<label>Machine:</label>
@@ -53,6 +54,12 @@
53 54
             <button name="Save log as target profile" type="check" id="target" />
54 55
         </layout>
55 56
     </layout>
57
+	<layout type="vertical">
58
+		<label>Connected Scales</label>
59
+		<layout type="vertical" id="scales" />
60
+		<stretch />
61
+	</layout>
62
+	</layout>
56 63
     <program>
57 64
         <![CDATA[
58 65
 			var unitBox = findChildObject(this, 'unit');
@@ -77,6 +84,40 @@
77 84
             var roasted = findChildObject(this, 'roasted');
78 85
             var roastwt = findChildObject(this, 'roast');
79 86
             roastwt.maximumWidth = 80;
87
+			var scalesLayout = findChildObject(this, 'scales');
88
+			scalesLayout.spacing = 10;
89
+			if(navigationwindow.loggingWindow.scales.length > 0) {
90
+				for(var i = 0; i < navigationwindow.loggingWindow.scales.length; i++) {
91
+					var scale = navigationwindow.loggingWindow.scales[i];
92
+					var label = new DragLabel();
93
+					var weighButton = new QPushButton();
94
+					weighButton.text = "Weigh";
95
+					weighButton.clicked.connect(scale.weigh);
96
+					label.updateMeasurement = function(m, u) {
97
+						switch(unitBox.currentIndex) {
98
+							case 0:
99
+								this.text = Units.convertWeight(m, u, Units.Gram).toFixed(1);
100
+								break;
101
+							case 1:
102
+								this.text = Units.convertWeight(m, u, Units.Kilogram).toFixed(4);
103
+								break;
104
+							case 2:
105
+								this.text = Units.convertWeight(m, u, Units.Ounce).toFixed(3);
106
+								break;
107
+							case 3:
108
+								this.text = Units.convertWeight(m, u, Units.Pound).toFixed(4);
109
+								break;
110
+						}
111
+					};
112
+					scalesLayout.addWidget(label);
113
+					scalesLayout.addWidget(weighButton);
114
+					scale.newMeasurement.connect(function(m, u) {
115
+						label.updateMeasurement(m, u);
116
+					});
117
+					scale.weigh();
118
+					unitBox['currentIndexChanged(int)'].connect(scale.weigh);
119
+				}
120
+			}
80 121
 			model.dataChanged.connect(function() {
81 122
 				green.text = table.columnSum(1, 0);
82 123
 				table.resizeColumnToContents(0);

+ 152
- 53
config/Windows/productionroaster.xml Visa fil

@@ -75,6 +75,8 @@
75 75
 		var rateoffsets = new Array();
76 76
 		var ratezeros = new Array();
77 77
 		var channelType = new Array();
78
+		window.scales = new Array();
79
+		var channelVisibility = new Array();
78 80
 		var indicatorPanel = findChildObject(this, 'indicators');
79 81
 		var annotationPanel = findChildObject(this, 'controlpanel');
80 82
 		var log = findChildObject(this, 'log');
@@ -153,11 +155,16 @@
153 155
 											channelType.push("T");
154 156
 											columnNames.push(channelReference.columnname);
155 157
 											DAQChannels++;
156
-											var indicator = new TemperatureDisplay;
157
-											temperatureDisplays.push(indicator);
158
-											var decorator = new WidgetDecorator(indicator, configModel.data(channelIndex, 0), 2);
159
-											channel.newData.connect(indicator.setValue);
160
-											indicatorPanel.addWidget(decorator);
158
+											if(channelReference.hidden == "true") {
159
+												channelVisibility.push(false);
160
+											} else {
161
+												channelVisibility.push(true);
162
+												var indicator = new TemperatureDisplay;
163
+												temperatureDisplays.push(indicator);
164
+												var decorator = new WidgetDecorator(indicator, configModel.data(channelIndex, 0), 2);
165
+												channel.newData.connect(indicator.setValue);
166
+												indicatorPanel.addWidget(decorator);
167
+											}
161 168
 										}
162 169
 									}
163 170
 									switch(DAQChannels) {
@@ -216,11 +223,16 @@
216 223
 								channels.push(channel);
217 224
 								channelType.push("T");
218 225
 								columnNames.push(deviceReference.columnname);
219
-								var indicator = new TemperatureDisplay;
220
-								temperatureDisplays.push(indicator);
221
-								var decorator = new WidgetDecorator(indicator, configModel.data(deviceIndex, 0), 2);
222
-								channel.newData.connect(indicator.setValue);
223
-								indicatorPanel.addWidget(decorator);
226
+								if(deviceReference.hidden == "true") {
227
+									channelVisibility.push(false);
228
+								} else {
229
+									channelVisibility.push(true);
230
+									var indicator = new TemperatureDisplay;
231
+									temperatureDisplays.push(indicator);
232
+									var decorator = new WidgetDecorator(indicator, configModel.data(deviceIndex, 0), 2);
233
+									channel.newData.connect(indicator.setValue);
234
+									indicatorPanel.addWidget(decorator);
235
+								}
224 236
 								device.start();
225 237
 								nidevices.push(device);
226 238
 							}
@@ -267,9 +279,14 @@
267 279
 							channel.newData.connect(calibrator.newMeasurement)
268 280
 							channels.push(calibrator);
269 281
 							columnNames.push(channelReference.column);
270
-							var decorator = new WidgetDecorator(indicator, configModel.data(channelIndex, 0), 2);
271
-							calibrator.newData.connect(indicator.setValue);
272
-							indicatorPanel.addWidget(decorator);
282
+							if(channelReference.hidden == "true") {
283
+								channelVisibility.push(false);
284
+							} else {
285
+								channelVisibility.push(true);
286
+								var decorator = new WidgetDecorator(indicator, configModel.data(channelIndex, 0), 2);
287
+								calibrator.newData.connect(indicator.setValue);
288
+								indicatorPanel.addWidget(decorator);
289
+							}
273 290
 						}
274 291
 						sampleRate /= configModel.rowCount(driverIndex);
275 292
 					}
@@ -285,22 +302,32 @@
285 302
 					channels.push(pvchannel);
286 303
 					channelType.push("T");
287 304
 					columnNames.push(driverReference.pvcolname);
288
-					var indicator = new TemperatureDisplay;
289
-					temperatureDisplays.push(indicator);
290
-					var decorator = new WidgetDecorator(indicator, configModel.data(driverIndex, 0) + " PV", 2);
291
-					pvchannel.newData.connect(indicator.setValue);
292
-					indicatorPanel.addWidget(decorator);
305
+					if(driverReference.pvhidden == "true") {
306
+						channelVisibility.push(false);
307
+					} else {
308
+						channelVisibility.push(true);
309
+						var indicator = new TemperatureDisplay;
310
+						temperatureDisplays.push(indicator);
311
+						var decorator = new WidgetDecorator(indicator, configModel.data(driverIndex, 0) + " PV", 2);
312
+						pvchannel.newData.connect(indicator.setValue);
313
+						indicatorPanel.addWidget(decorator);
314
+					}
293 315
 					if(driverReference.sVEnabled == "true")
294 316
 					{
295 317
 						var svchannel = device.sVChannel();
296 318
 						channels.push(svchannel);
297 319
 						channelType.push("C");
298 320
 						columnNames.push(driverReference.svcolname);
299
-						var indicator = new TemperatureDisplay;
300
-						temperatureDisplays.push(indicator);
301
-						var decorator = new WidgetDecorator(indicator, configModel.data(driverIndex, 0) + " SV", 2);
302
-						svchannel.newData.connect(indicator.setValue);
303
-						indicatorPanel.addWidget(decorator);
321
+						if(driverReference.svhidden == "true") {
322
+							channelVisibility.push(false);
323
+						} else {
324
+							channelVisibility.push(true);
325
+							var indicator = new TemperatureDisplay;
326
+							temperatureDisplays.push(indicator);
327
+							var decorator = new WidgetDecorator(indicator, configModel.data(driverIndex, 0) + " SV", 2);
328
+							svchannel.newData.connect(indicator.setValue);
329
+							indicatorPanel.addWidget(decorator);
330
+						}
304 331
 					}
305 332
 					if(driverReference.sVWritable == "true")
306 333
 					{
@@ -336,6 +363,30 @@
336 363
 					annotationPanel.addWidget(button);
337 364
 					tabControls.push(button);
338 365
 				}
366
+				else if(driverReference.driver == "valueannotation")
367
+				{
368
+					var checker = new ValueAnnotation;
369
+					var valuesSetting = driverReference.measuredValues;
370
+					var notesSetting = driverReference.annotations;
371
+					var valuesList = valuesSetting.slice(2, valuesSetting.length-2).split(",");
372
+					var notesList = notesSetting.slice(2, notesSetting.length-2).split(",");
373
+					if(valuesList.length > 1 && notesList.length == valuesList.length) {
374
+						for(var j = 0; j < valuesList.length; j++) {
375
+							checker.setAnnotation(Number(valuesList[j]), notesList[j]);
376
+						}
377
+					}
378
+					if(driverReference.emitOnStart == "true") {
379
+						start.clicked.connect(checker.annotate);
380
+					}
381
+					var colname = driverReference.source;
382
+					for(var j = 0; j < columnNames.length; j++) {
383
+						if(columnNames[j] == colname) {
384
+							channels[j].newData.connect(checker.newMeasurement);
385
+							break;
386
+						}
387
+					}
388
+					annotationButtons.push(checker);
389
+				}
339 390
 				else if(driverReference.driver == "reconfigurablebutton")
340 391
 				{
341 392
 					var button = new AnnotationButton(driverReference.buttontext);
@@ -405,6 +456,10 @@
405 456
 								channels[j].newData.connect(calibrator.newMeasurement);
406 457
 								calibrator.newData.connect(indicator.setValue);
407 458
 								channels.push(calibrator);
459
+								// Channel hiding is not yet configurable for this.
460
+								// I'm not sure if there's any need to allow this
461
+								// to be hidden at present.
462
+								channelVisibility.push(true);
408 463
 								channelType.push(channelType[j]);
409 464
 								columnNames.push(driverReference.destination);
410 465
 								indicatorPanel.addWidget(decorator);
@@ -483,11 +538,25 @@
483 538
 							rate.newData.connect(indicator.setValue);
484 539
 							temperatureDisplays.push(indicator);
485 540
 							channels.push(rate);
541
+							// This cannot at present be configured to be
542
+							// hidden.
543
+							channelVisibility.push(true);
486 544
 							channelType.push("T");
487 545
 							columnNames.push(configModel.data(driverIndex, 0));
488 546
 						}
489 547
 					}
490 548
 				}
549
+				else if(driverReference.driver == "scale")
550
+				{
551
+					var scale = new SerialScale(driverReference.port);
552
+					scale.setDataBits(8);
553
+					scale.setBaudRate(driverReference.baudrate);
554
+					scale.setParity(driverReference.parity);
555
+					scale.setStopBits(driverReference.stopbits);
556
+					scale.setFlowControl(driverReference.flowcontrol);
557
+					scale.open(3);
558
+					window.scales.push(scale);
559
+				}
491 560
 			}
492 561
 		}
493 562
 		for(var i = 1; i < tabControls.length; i++)
@@ -495,13 +564,17 @@
495 564
 			setTabOrder(tabControls[i-1], tabControls[i]);
496 565
 		}
497 566
 		log.setHeaderData(0, "Time");
567
+		var channelSkip = 0;
498 568
 		for(var i = 0; i < columnNames.length; i++) {
499
-			log.setHeaderData(i + 1, columnNames[i]);
500
-		}
501
-		log.setHeaderData(columnNames.length + 1, "Note");
502
-		for(var i = 0; i < channels.length; i++) {
503
-			log.addToCurrentColumnSet(i + 1);
569
+			if(channelVisibility[i]) {
570
+				log.setHeaderData(i + 1 - channelSkip, columnNames[i]);
571
+				log.addToCurrentColumnSet(i + 1 - channelSkip);
572
+			}
573
+			else {
574
+				channelSkip++;
575
+			}
504 576
 		}
577
+		log.setHeaderData(columnNames.length + 1 - channelSkip, "Note");
505 578
 		var timer = new TimerDisplay;
506 579
 		timer.displayFormat = "mm:ss";
507 580
 		timer.autoReset = true;
@@ -542,19 +615,33 @@
542 615
 		var offsets = new Array();
543 616
 		var zeroemitters = new Array();
544 617
 		var adapters = new Array();
618
+		channelSkip = 0;
545 619
 		for(var i = 0; i < channels.length; i++) {
546
-			var offset = new MeasurementTimeOffset(epoch);
547
-			offsets.push(offset);
548
-			channels[i].newData.connect(offset.newMeasurement);
549
-			var adapter = new MeasurementAdapter(i + 1);
550
-			adapters.push(adapter);
551
-			offset.measurement.connect(adapter.newMeasurement);
552
-			var emitter = new ZeroEmitter(i + 1);
553
-			zeroemitters.push(emitter);
554
-			channels[i].newData.connect(emitter.newMeasurement);
555
-			emitter.measurement.connect(log.newMeasurement);
556
-			emitter.measurement.connect(graph.newMeasurement);
620
+			if(channelVisibility[i]) {
621
+				var offset = new MeasurementTimeOffset(epoch);
622
+				offsets.push(offset);
623
+				channels[i].newData.connect(offset.newMeasurement);
624
+				var adapter = new MeasurementAdapter(i + 1 - channelSkip);
625
+				adapters.push(adapter);
626
+				offset.measurement.connect(adapter.newMeasurement);
627
+				var emitter = new ZeroEmitter(i + 1);
628
+				zeroemitters.push(emitter);
629
+				channels[i].newData.connect(emitter.newMeasurement);
630
+				emitter.measurement.connect(log.newMeasurement);
631
+				emitter.measurement.connect(graph.newMeasurement);
632
+			} else {
633
+				channelSkip++;
634
+			}
557 635
 		}
636
+		var offsetForChannel = function(c) {
637
+			var retval = -1;
638
+			for(var i = 0; i <= c; i++) {
639
+				if(channelVisibility[i]) {
640
+					retval++;
641
+				}
642
+			}
643
+			return retval;
644
+		};
558 645
         start.clicked.connect(function() {
559 646
 			start.enabled = false;
560 647
 			hasTranslated = false;
@@ -585,7 +672,7 @@
585 672
             }
586 673
 			if(translationChannel >= 0)
587 674
 			{
588
-				offsets[translationChannel].measurement.connect(currentDetector.newMeasurement);
675
+				offsets[offsetForChannel(translationChannel)].measurement.connect(currentDetector.newMeasurement);
589 676
 			}
590 677
 			if(typeof(externtrans) != 'undefined') {
591 678
 				externtrans.display(0);
@@ -599,7 +686,7 @@
599 686
 			{
600 687
 				annotationButtons[i].annotation.connect(log.newAnnotation);
601 688
 				annotationButtons[i].setTemperatureColumn(1);
602
-				annotationButtons[i].setAnnotationColumn(channels.length + 1);
689
+				annotationButtons[i].setAnnotationColumn(channels.length - channelSkip + 1);
603 690
 				annotationButtons[i].annotation.connect(function(note, tcol, ncol) {
604 691
 					for(var i = tcol; i < ncol; i++) {
605 692
 						log.newAnnotation(note, i, ncol);
@@ -611,7 +698,7 @@
611 698
         stop.annotation.connect(log.newAnnotation);
612 699
         stop.clicked.connect(timer.stopTimer);
613 700
 		stop.setTemperatureColumn(1);
614
-		stop.setAnnotationColumn(channels.length + 1);
701
+		stop.setAnnotationColumn(channels.length - channelSkip + 1);
615 702
 		QSettings.setValue("liveColumn", 1);
616 703
         var lc = 1;
617 704
         stop.clicked.connect(function() {
@@ -647,7 +734,7 @@
647 734
             }
648 735
 			if(translationChannel >= 0)
649 736
 			{
650
-				offsets[translationChannel].measurement.disconnect(currentDetector.newMeasurement);
737
+				offsets[offsetForChannel(translationChannel)].measurement.disconnect(currentDetector.newMeasurement);
651 738
 			}
652 739
 			start.enabled = true;
653 740
 			window.windowModified = false;
@@ -764,27 +851,39 @@
764 851
 				log.newAnnotation("End", 1, lc);
765 852
             }
766 853
         });
854
+		var columnName = function(c) {
855
+			var visibleColumns = 0;
856
+			for(var i = 0; i < channels.length; i++) {
857
+				if(channelVisibility[i]) {
858
+					visibleColumns++;
859
+				}
860
+				if(c == visibleColumns - 1) {
861
+					return columnNames[i];
862
+				}
863
+			}
864
+			return "";
865
+		};
767 866
 		window.postLoadColumnSetup = function(c) {
768 867
 			for(var i = 0; i < adapters.length; i++)
769 868
 			{
770 869
 				adapters[i].setColumn(c + i + 1);
771 870
 				zeroemitters[i].setColumn(c + i + 1);
772
-				log.setHeaderData(c + i + 1, columnNames[i]);
871
+				log.setHeaderData(c + i + 1, columnName(i));
773 872
 			}
774
-			log.setHeaderData(c + columnNames.length + 1, "Note");
873
+			log.setHeaderData(c + columnNames.length + 1 - channelSkip, "Note");
775 874
             stop.setTemperatureColumn(c + 1);
776
-			stop.setAnnotationColumn(c + columnNames.length + 1);
875
+			stop.setAnnotationColumn(c + columnNames.length + 1 - channelSkip);
777 876
 			for(var i = 0; i < annotationButtons.length; i++)
778 877
 			{
779 878
 				annotationButtons[i].setTemperatureColumn(c + 1);
780
-				annotationButtons[i].setAnnotationColumn(c + columnNames.length + 1);
879
+				annotationButtons[i].setAnnotationColumn(c + columnNames.length + 1 - channelSkip);
781 880
 			}
782 881
 			log.clearCurrentColumnSet();
783
-			for(var i = 0; i < channels.length; i++) {
882
+			for(var i = 0; i < channels.length - channelSkip; i++) {
784 883
 				log.addToCurrentColumnSet(c + i + 1);
785 884
 			}
786 885
 			window.firstTempColumn = c + 1;
787
-			window.annotationColumn = c + columnNames.length + 1;
886
+			window.annotationColumn = c + columnNames.length - channelSkip + 1;
788 887
 		}
789 888
 		var saveMenu = findChildObject(this, 'save');
790 889
         saveMenu.triggered.connect(function() {
@@ -793,7 +892,7 @@
793 892
                 var lc = Number(QSettings.value("liveColumn"));
794 893
                 var file = new QFile(filename);
795 894
                 log.clearOutputColumns();
796
-				for(var i = 0; i < columnNames.length; i++)
895
+				for(var i = 0; i < columnNames.length - channelSkip; i++)
797 896
 				{
798 897
 					if(channelType[i] == "T") {
799 898
 						log.addOutputTemperatureColumn(lc + i);
@@ -802,7 +901,7 @@
802 901
 						log.addOutputControlColumn(lc + i);
803 902
 					}
804 903
 				}
805
-				log.addOutputAnnotationColumn(lc + columnNames.length);
904
+				log.addOutputAnnotationColumn(lc + columnNames.length - channelSkip);
806 905
                 log.saveXML(file);
807 906
                 QSettings.setValue("script/lastDir", dir(filename));
808 907
             }
@@ -814,11 +913,11 @@
814 913
                 var lc = Number(QSettings.value("liveColumn"));
815 914
                 var file = new QFile(filename);
816 915
                 log.clearOutputColumns();
817
-				for(var i = 0; i < columnNames.length; i++)
916
+				for(var i = 0; i < columnNames.length - channelSkip; i++)
818 917
 				{
819 918
 					log.addOutputTemperatureColumn(lc + i);
820 919
 				}
821
-				log.addOutputAnnotationColumn(lc + columnNames.length);
920
+				log.addOutputAnnotationColumn(lc + columnNames.length - channelSkip);
822 921
                 log.saveCSV(file);
823 922
                 QSettings.setValue("script/lastDir", dir(filename));
824 923
             }

+ 6
- 2
src/Typica.pro Visa fil

@@ -21,13 +21,17 @@ HEADERS += moc_typica.cpp \
21 21
     abouttypica.h \
22 22
     units.h \
23 23
     webview.h \
24
-    webelement.h
24
+    webelement.h \
25
+    scale.h \
26
+    draglabel.h
25 27
 SOURCES += typica.cpp \
26 28
     helpmenu.cpp \
27 29
     abouttypica.cpp \
28 30
     units.cpp \
29 31
     webview.cpp \
30
-    webelement.cpp
32
+    webelement.cpp \
33
+    scale.cpp \
34
+    draglabel.cpp
31 35
 
32 36
 RESOURCES += \
33 37
     resources.qrc

+ 205
- 7
src/dataqsdk.w Visa fil

@@ -825,16 +825,70 @@ class DataqSdkChannelConfWidget : public BasicDeviceConfigurationWidget
825 825
 		void startCalibration();
826 826
 		void stopCalibration();
827 827
 		void resetCalibration();
828
+		void updateInput(Measurement measure);
829
+		void updateOutput(Measurement measure);
830
+		void updateHidden(bool hidden);
831
+	private:
832
+		QPushButton *startButton;
833
+		QPushButton *resetButton;
834
+		QPushButton *stopButton;
835
+		@<DATAQ SDK device settings@>@;
836
+		DataqSdkDevice *calibrationDevice;
837
+		LinearCalibrator *calibrator;
838
+		QLineEdit *currentMeasurement;
839
+		QLineEdit *minimumMeasurement;
840
+		QLineEdit *maximumMeasurement;
841
+		QLineEdit *averageMeasurement;
842
+		QLineEdit *currentMapped;
843
+		QLineEdit *minimumMapped;
844
+		QLineEdit *maximumMapped;
845
+		QLineEdit *averageMapped;
846
+		int rmCount;
847
+		int cmCount;
848
+		double rmin;
849
+		double rmax;
850
+		double rmean;
851
+		double cmin;
852
+		double cmax;
853
+		double cmean;
828 854
 };
829 855
 
856
+@ Private members that hold minimum and maximum aggregate data for channel
857
+calibration will be initialized to the maximum and minimum values available for
858
+the |double| type respectively. This guarantees that the first measurement will
859
+overwrite these values. This is done with |std::numeric_limits| so we require a
860
+header to be included to gain access to this.
861
+
862
+@<Header files to include@>=
863
+#include <limits>
864
+
830 865
 @ The constructor sets up the interface. Calibration settings line edits need
831 866
 to have numeric validators added.
832 867
 
833 868
 @<DataqSdkDeviceConfWidget implementation@>=
834 869
 DataqSdkChannelConfWidget::DataqSdkChannelConfWidget(DeviceTreeModel *model,
835 870
                                                      const QModelIndex &index)
836
-	: BasicDeviceConfigurationWidget(model, index)
871
+	: BasicDeviceConfigurationWidget(model, index),
872
+	startButton(new QPushButton(tr("Start"))),
873
+	resetButton(new QPushButton(tr("Reset"))),
874
+	stopButton(new QPushButton(tr("Stop"))),
875
+	calibrator(new LinearCalibrator),
876
+	currentMeasurement(new QLineEdit), minimumMeasurement(new QLineEdit),
877
+	maximumMeasurement(new QLineEdit), averageMeasurement(new QLineEdit),
878
+	currentMapped(new QLineEdit), minimumMapped(new QLineEdit),
879
+	maximumMapped(new QLineEdit), averageMapped(new QLineEdit),
880
+	rmCount(0), cmCount(0),
881
+	rmin(std::numeric_limits<double>::max()),
882
+	rmax(std::numeric_limits<double>::min()), rmean(0),
883
+	cmin(std::numeric_limits<double>::max()),
884
+	cmax(std::numeric_limits<double>::min()), cmean(0)
837 885
 {
886
+	@<Find DATAQ SDK device settings from parent node@>@;
887
+	resetButton->setEnabled(false);
888
+	stopButton->setEnabled(false);
889
+	connect(startButton, SIGNAL(clicked()), this, SLOT(startCalibration()));
890
+	connect(resetButton, SIGNAL(clicked()), this, SLOT(resetCalibration()));
891
+	connect(stopButton, SIGNAL(clicked()), this, SLOT(stopCalibration()));
838 892
 	QVBoxLayout *layout = new QVBoxLayout;
839 893
 	QFormLayout *topLayout = new QFormLayout;
840 894
 	QLineEdit *columnEdit = new QLineEdit;
@@ -846,8 +900,11 @@ DataqSdkChannelConfWidget::DataqSdkChannelConfWidget(DeviceTreeModel *model,
846 900
 	QCheckBox *smoothingBox = new QCheckBox(tr("Enable smoothing"));
847 901
 	topLayout->addRow(smoothingBox);
848 902
 	layout->addLayout(topLayout);
903
+	QCheckBox *hideSeries = new QCheckBox(tr("Hide this channel"));
904
+	topLayout->addRow(hideSeries);
849 905
 	QLabel *calibrationLabel = new QLabel(tr("Calibration settings"));
850 906
 	layout->addWidget(calibrationLabel);
907
+	QHBoxLayout *calibrationLayout = new QHBoxLayout;
851 908
 	QFormLayout *calibrationControlsLayout = new QFormLayout;
852 909
 	QLineEdit *measuredLowerEdit = new QLineEdit;
853 910
 	measuredLowerEdit->setText("0");
@@ -866,10 +923,35 @@ DataqSdkChannelConfWidget::DataqSdkChannelConfWidget(DeviceTreeModel *model,
866 923
 	QLineEdit *sensitivityEdit = new QLineEdit;
867 924
 	sensitivityEdit->setText("0");
868 925
 	calibrationControlsLayout->addRow(tr("Discrete interval skip"), sensitivityEdit);
869
-	layout->addLayout(calibrationControlsLayout);
870
-	
871
-	// Insert another panel to assist in determining proper calibration values.
872
-	
926
+	QVBoxLayout *calibrationTestLayout = new QVBoxLayout;
927
+	QHBoxLayout *deviceControlLayout = new QHBoxLayout;
928
+	deviceControlLayout->addWidget(startButton);
929
+	deviceControlLayout->addWidget(resetButton);
930
+	deviceControlLayout->addWidget(stopButton);
931
+	QFormLayout *indicatorLayout = new QFormLayout;	
932
+	currentMeasurement->setReadOnly(true);
933
+	minimumMeasurement->setReadOnly(true);
934
+	maximumMeasurement->setReadOnly(true);
935
+	averageMeasurement->setReadOnly(true);
936
+	currentMapped->setReadOnly(true);
937
+	minimumMapped->setReadOnly(true);
938
+	maximumMapped->setReadOnly(true);
939
+	averageMapped->setReadOnly(true);
940
+	indicatorLayout->addRow(tr("Measured Values"), new QWidget);
941
+	indicatorLayout->addRow(tr("Current"), currentMeasurement);
942
+	indicatorLayout->addRow(tr("Minimum"), minimumMeasurement);
943
+	indicatorLayout->addRow(tr("Maximum"), maximumMeasurement);
944
+	indicatorLayout->addRow(tr("Mean"), averageMeasurement);
945
+	indicatorLayout->addRow(tr("Mapped Values"), new QWidget);
946
+	indicatorLayout->addRow(tr("Current Mapped"), currentMapped);
947
+	indicatorLayout->addRow(tr("Minimum Mapped"), minimumMapped);
948
+	indicatorLayout->addRow(tr("Maximum Mapped"), maximumMapped);
949
+	indicatorLayout->addRow(tr("Mean Mapped"), averageMapped);
950
+	calibrationTestLayout->addLayout(deviceControlLayout);
951
+	calibrationTestLayout->addLayout(indicatorLayout);
952
+	calibrationLayout->addLayout(calibrationControlsLayout);
953
+	calibrationLayout->addLayout(calibrationTestLayout);
954
+	layout->addLayout(calibrationLayout);
873 955
 	@<Get device configuration data for current node@>@;
874 956
 	for(int i = 0; i < configData.size(); i++)
875 957
 	{
@@ -910,6 +992,10 @@ DataqSdkChannelConfWidget::DataqSdkChannelConfWidget(DeviceTreeModel *model,
910 992
 		{
911 993
 			sensitivityEdit->setText(node.attribute("value"));
912 994
 		}
995
+		else if(node.attribute("name") == "hidden")
996
+		{
997
+			hideSeries->setChecked(node.attribute("value") == "true");
998
+		}
913 999
 	}
914 1000
 	updateColumnName(columnEdit->text());
915 1001
 	updateUnits(unitSelector->currentText());
@@ -920,6 +1006,7 @@ DataqSdkChannelConfWidget::DataqSdkChannelConfWidget(DeviceTreeModel *model,
920 1006
 	updateMappedUpper(mappedUpperEdit->text());
921 1007
 	updateClosedInterval(closedBox->isChecked());
922 1008
 	updateSensitivity(sensitivityEdit->text());
1009
+	updateHidden(hideSeries->isChecked());
923 1010
 	connect(columnEdit, SIGNAL(textChanged(QString)),
924 1011
 	        this, SLOT(updateColumnName(QString)));
925 1012
 	connect(unitSelector, SIGNAL(currentIndexChanged(QString)),
@@ -938,6 +1025,7 @@ DataqSdkChannelConfWidget::DataqSdkChannelConfWidget(DeviceTreeModel *model,
938 1025
 	        this, SLOT(updateClosedInterval(bool)));
939 1026
 	connect(sensitivityEdit, SIGNAL(textChanged(QString)),
940 1027
 	        this, SLOT(updateSensitivity(QString)));
1028
+	connect(hideSeries, SIGNAL(toggled(bool)), this, SLOT(updateHidden(bool)));
941 1029
 	setLayout(layout);
942 1030
 }
943 1031
 
@@ -960,26 +1048,31 @@ the |LinearCalibrator| used for calibration assistance.
960 1048
 void DataqSdkChannelConfWidget::updateMeasuredLower(const QString &value)
961 1049
 {
962 1050
 	updateAttribute("calibrationMeasuredLower", value);
1051
+	calibrator->setMeasuredLower(value.toDouble());
963 1052
 }
964 1053
 
965 1054
 void DataqSdkChannelConfWidget::updateMeasuredUpper(const QString &value)
966 1055
 {
967 1056
 	updateAttribute("calibrationMeasuredUpper", value);
1057
+	calibrator->setMeasuredUpper(value.toDouble());
968 1058
 }
969 1059
 
970 1060
 void DataqSdkChannelConfWidget::updateMappedLower(const QString &value)
971 1061
 {
972 1062
 	updateAttribute("calibrationMappedLower", value);
1063
+	calibrator->setMappedLower(value.toDouble());
973 1064
 }
974 1065
 
975 1066
 void DataqSdkChannelConfWidget::updateMappedUpper(const QString &value)
976 1067
 {
977 1068
 	updateAttribute("calibrationMappedUpper", value);
1069
+	calibrator->setMappedUpper(value.toDouble());
978 1070
 }
979 1071
 
980 1072
 void DataqSdkChannelConfWidget::updateClosedInterval(bool closed)
981 1073
 {
982 1074
 	updateAttribute("calibrationClosedInterval", closed ? "true" : "false");
1075
+	calibrator->setClosedRange(closed);
983 1076
 }
984 1077
 
985 1078
 void DataqSdkChannelConfWidget::updateSmoothingEnabled(bool enabled)
@@ -990,7 +1083,51 @@ void DataqSdkChannelConfWidget::updateSmoothingEnabled(bool enabled)
990 1083
 void DataqSdkChannelConfWidget::updateSensitivity(const QString &value)
991 1084
 {
992 1085
 	updateAttribute("calibrationSensitivity", value);
1086
+	calibrator->setSensitivity(value.toDouble());
1087
+}
1088
+
1089
+void DataqSdkChannelConfWidget::updateHidden(bool hidden)
1090
+{
1091
+	updateAttribute("hidden", hidden ? "true" : "false");
1092
+}
1093
+
1094
+@ When calibrating a device, we must know certain information to open a
1095
+connection to the appropriate hardware and know which channel we are interested
1096
+in.
1097
+
1098
+@<DATAQ SDK device settings@>=
1099
+bool autoSelect;
1100
+QString deviceID;
1101
+int channelOfInterest;
1102
+
1103
+@ This information is accessed through the reference element associated with
1104
+the parent node of the current configuration and from the row number of the
1105
+current node.
1106
+
1107
+@<Find DATAQ SDK device settings from parent node@>=
1108
+QDomElement parentReference = model->referenceElement(model->data(index.parent(), Qt::UserRole).toString());
1109
+QDomNodeList deviceConfigData = parentReference.elementsByTagName("attribute");
1110
+QDomElement deviceNode;
1111
+QString configPort;
1112
+QString configAuto;
1113
+for(int i = 0; i < deviceConfigData.size(); i++)
1114
+{
1115
+	deviceNode = deviceConfigData.at(i).toElement();
1116
+	if(deviceNode.attribute("name") == "autoSelect")
1117
+	{
1118
+		autoSelect = (deviceNode.attribute("value") == "true");
1119
+	}
1120
+	else if(deviceNode.attribute("name") == "deviceNumber")
1121
+	{
1122
+		configAuto = deviceNode.attribute("value");
1123
+	}
1124
+	else if(deviceNode.attribute("name") == "port")
1125
+	{
1126
+		configPort = deviceNode.attribute("value");
1127
+	}
993 1128
 }
1129
+deviceID = autoSelect ? configAuto : configPort;
1130
+channelOfInterest = index.row();
994 1131
 
995 1132
 @ It must be possible to perform calibration operations with the hardware not
996 1133
 connected. As such, the device should only be opened on request. Methods for
@@ -999,12 +1136,29 @@ opening and closing these connections to the hardware are provided.
999 1136
 @<DataqSdkDeviceConfWidget implementation@>=
1000 1137
 void DataqSdkChannelConfWidget::startCalibration()
1001 1138
 {
1002
-
1139
+	startButton->setEnabled(false);
1140
+	stopButton->setEnabled(true);
1141
+	resetButton->setEnabled(true);
1142
+	calibrationDevice = new DataqSdkDevice(deviceID);
1143
+	Channel *channel;
1144
+	for(int i = 0; i <= channelOfInterest; i++)
1145
+	{
1146
+		channel = calibrationDevice->newChannel(Units::Unitless);
1147
+	}
1148
+	connect(channel, SIGNAL(newData(Measurement)), this, SLOT(updateInput(Measurement)));
1149
+	connect(channel, SIGNAL(newData(Measurement)), calibrator, SLOT(newMeasurement(Measurement)));
1150
+	connect(calibrator, SIGNAL(newData(Measurement)), this, SLOT(updateOutput(Measurement)));
1151
+	calibrationDevice->setClockRate(6.0 / (1.0 + channelOfInterest));
1152
+	calibrationDevice->start();
1003 1153
 }
1004 1154
 
1005 1155
 void DataqSdkChannelConfWidget::stopCalibration()
1006 1156
 {
1007
-
1157
+	startButton->setEnabled(true);
1158
+	stopButton->setEnabled(false);
1159
+	resetButton->setEnabled(false);
1160
+	calibrationDevice->deleteLater();
1161
+	@<Reset DATAQ SDK channel calibration aggregates@>@;
1008 1162
 }
1009 1163
 
1010 1164
 @ When collecting calibration data it is useful to have a few types of
@@ -1018,7 +1172,51 @@ convenient testing in multiple parts of the range.
1018 1172
 @<DataqSdkDeviceConfWidget implementation@>=
1019 1173
 void DataqSdkChannelConfWidget::resetCalibration()
1020 1174
 {
1175
+	@<Reset DATAQ SDK channel calibration aggregates@>@;
1176
+}
1177
+
1178
+@ When calibration is stopped or reset, aggregate statistics are set to
1179
+their initial values;
1180
+
1181
+@<Reset DATAQ SDK channel calibration aggregates@>=
1182
+rmCount = 0;
1183
+cmCount = 0;
1184
+rmin = std::numeric_limits<double>::max();
1185
+rmax = std::numeric_limits<double>::min();
1186
+rmean = 0;
1187
+cmin = std::numeric_limits<double>::max();
1188
+cmax = std::numeric_limits<double>::min();
1189
+cmean = 0;
1190
+
1191
+@ Two methods are responsible for updating line edits with current and
1192
+aggregate data when calibrating a channel. One handles raw measurements from
1193
+the channel and the other handles output from the |LinearCalibrator|.
1194
+
1195
+@<DataqSdkDeviceConfWidget implementation@>=
1196
+void DataqSdkChannelConfWidget::updateInput(Measurement measure)
1197
+{
1198
+	double nv = measure.temperature();
1199
+	currentMeasurement->setText(QString("%1").arg(nv));
1200
+	rmin = qMin(nv, rmin);
1201
+	minimumMeasurement->setText(QString("%1").arg(rmin));
1202
+	rmax = qMax(nv, rmax);
1203
+	maximumMeasurement->setText(QString("%1").arg(rmax));
1204
+	rmean = ((rmean * rmCount) + nv) / (rmCount + 1);
1205
+	rmCount++;
1206
+	averageMeasurement->setText(QString("%1").arg(rmean));
1207
+}
1021 1208
 
1209
+void DataqSdkChannelConfWidget::updateOutput(Measurement measure)
1210
+{
1211
+	double nv = measure.temperature();
1212
+	currentMapped->setText(QString("%1").arg(nv));
1213
+	cmin = qMin(nv, cmin);
1214
+	minimumMapped->setText(QString("%1").arg(cmin));
1215
+	cmax = qMax(nv, cmax);
1216
+	maximumMapped->setText(QString("%1").arg(cmax));
1217
+	cmean = ((cmean * cmCount) + nv) / (cmCount + 1);
1218
+	cmCount++;
1219
+	averageMapped->setText(QString("%1").arg(cmean));
1022 1220
 }
1023 1221
 
1024 1222
 @ Column name is handled as usual.

+ 498
- 0
src/scales.w Visa fil

@@ -0,0 +1,498 @@
1
+@** Collecting Measurements from Scales.
2
+
3
+\noindent When a computer connected scale is available, it can be useful to
4
+eliminate manual transcription from the data entry for batches. In general it
5
+is difficult to determine where a measurement should go automatically, but
6
+there are a number of situations where the ability to drag a measurement from a
7
+label and drop it to whatever input widget is appropriate would be a useful
8
+operation to support.
9
+
10
+To support this, we need to subclass |QLabel| to allow it to initiate a drag
11
+and drop operation.
12
+
13
+@(draglabel.h@>=
14
+#ifndef TypicaDragLabelInclude
15
+#define TypicaDragLabelInclude
16
+
17
+#include <QLabel>
18
+
19
+class DragLabel : public QLabel
20
+{
21
+	Q_OBJECT
22
+	public:
23
+		explicit DragLabel(const QString &labelText, QWidget *parent = NULL);
24
+	protected:
25
+		void mousePressEvent(QMouseEvent *event);
26
+};
27
+
28
+#endif
29
+
30
+@ The font size of the label is increased by default to make it easier to
31
+manipulate on a touch screen. Otherwise, there is little to do in this class.
32
+
33
+@(draglabel.cpp@>=
34
+#include "draglabel.h"
35
+
36
+#include <QDrag>
37
+#include <QMouseEvent>
38
+
39
+DragLabel::DragLabel(const QString &labelText, QWidget *parent) :
40
+	QLabel(labelText, parent)
41
+{
42
+	QFont labelFont = font();
43
+	labelFont.setPointSize(14);
44
+	setFont(labelFont);
45
+}
46
+
47
+void DragLabel::mousePressEvent(QMouseEvent *event)
48
+{
49
+	if(event->button() == Qt::LeftButton)
50
+	{
51
+		QDrag *drag = new QDrag(this);
52
+		QMimeData *mimeData = new QMimeData;
53
+		mimeData->setText(text());
54
+		drag->setMimeData(mimeData);
55
+		drag->exec();
56
+	}
57
+}
58
+
59
+@ We require the ability to create these labels from the host environment.
60
+First we include the appropriate header.
61
+
62
+@<Header files to include@>=
63
+#include "draglabel.h"
64
+
65
+@ Next, a pair of function prototypes.
66
+
67
+@<Function prototypes for scripting@>=
68
+QScriptValue constructDragLabel(QScriptContext *context, QScriptEngine *engine);
69
+void setDragLabelProperties(QScriptValue value, QScriptEngine *engine);
70
+
71
+@ These are made known to the host environment as usual.
72
+
73
+@<Set up the scripting engine@>=
74
+constructor = engine->newFunction(constructDragLabel);
75
+value = engine->newQMetaObject(&DragLabel::staticMetaObject, constructor);
76
+engine->globalObject().setProperty("DragLabel", value);
77
+
78
+@ The implementation is trivial.
79
+
80
+@<Functions for scripting@>=
81
+QScriptValue constructDragLabel(QScriptContext *context, QScriptEngine *engine)
82
+{
83
+	QScriptValue object;
84
+	QString labelText = "";
85
+	if(context->argumentCount() == 1)
86
+	{
87
+		labelText = argument<QString>(0, context);
88
+	}
89
+	object = engine->newQObject(new DragLabel(labelText));
90
+	setDragLabelProperties(object, engine);
91
+	return object;
92
+}
93
+
94
+void setDragLabelProperties(QScriptValue value, QScriptEngine *engine)
95
+{
96
+	setQLabelProperties(value, engine);
97
+}
98
+
99
+@ An object is also required to communicate with a scale. This is responsible
100
+for setting up a connection over a serial port, sending commands out to the
101
+scale, buffering and interpreting the response, and signaling new measurements.
102
+
103
+@(scale.h@>=
104
+#ifndef TypicaScaleInclude
105
+#define TypicaScaleInclude
106
+
107
+#include "3rdparty/qextserialport/src/qextserialport.h"
108
+#include "units.h"
109
+
110
+class SerialScale : public QextSerialPort
111
+{
112
+	Q_OBJECT
113
+	public:
114
+		SerialScale(const QString &port);
115
+	public slots:
116
+		void tare();
117
+		void weigh();
118
+	signals:
119
+		void newMeasurement(double weight, Units::Unit unit);
120
+	private slots:
121
+		void dataAvailable();
122
+	private:
123
+		QByteArray responseBuffer;
124
+};
125
+
126
+#endif
127
+
128
+@ The constructor tells the port that this should be event driven and connects
129
+a signal to buffer data..
130
+
131
+@(scale.cpp@>=
132
+#include "scale.h"
133
+#include <QStringList>
134
+
135
+SerialScale::SerialScale(const QString &port) :
136
+	QextSerialPort(port, QextSerialPort::EventDriven)
137
+{
138
+	connect(this, SIGNAL(readyRead()), this, SLOT(dataAvailable()));
139
+}
140
+
141
+@ The |dataAvailable| method handles buffering incoming data and processing
142
+responses when they have come in. Serial port communications are likely to be
143
+very slow in comparison to everything else so it is likely that only one
144
+character will come in at a time.
145
+
146
+Note that this currently only understands single line output and a limited
147
+selection of units.
148
+
149
+@(scale.cpp@>=
150
+void SerialScale::dataAvailable()
151
+{
152
+	responseBuffer.append(readAll());
153
+	if(responseBuffer.contains("\x0D"))
154
+	{
155
+		if(responseBuffer.contains("!"))
156
+		{
157
+			responseBuffer.clear();
158
+		}
159
+		else
160
+		{
161
+			@<Process weight measurement@>@;
162
+			responseBuffer.clear();
163
+		}
164
+	}
165
+}
166
+
167
+@ Each line of data consists of an optional sign character possibly followed
168
+by a space followed by characters representing a number followed by a
169
+space followed by characters indicating a unit. This may be preceeded and
170
+followed by a variable amount of white space. To process a new measurement, we
171
+must remove the excess white space, split the number from the unit, prepend the
172
+sign to the number if it is present, convert the string representing the number
173
+to a numeric type, and determine which unit the measurement is in.
174
+
175
+\medskip
176
+
177
+\settabs 8 \columns
178
+\+&&&{\tt |"lb"|}&|Units::Pound|\cr
179
+\+&&&{\tt |"kg"|}&|Units::Kilogram|\cr
180
+\+&&&{\tt |"g"|}&|Units::Gram|\cr
181
+\+&&&{\tt |"oz"|}&|Units::Ounce|\cr
182
+
183
+\smallskip
184
+
185
+\centerline{Table \secno: Unit Strings and Representative Unit Enumeration}
186
+
187
+\medskip
188
+
189
+@<Process weight measurement@>=
190
+QStringList responseParts = QString(responseBuffer.simplified()).split(' ');
191
+if(responseParts.size() > 2)
192
+{
193
+	responseParts.removeFirst();
194
+	responseParts.replace(0, QString("-%1").arg(responseParts[0]));
195
+}
196
+double weight = responseParts[0].toDouble();
197
+Units::Unit unit = Units::Unitless;
198
+if(responseParts[1] == "lb")
199
+{
200
+	unit = Units::Pound;
201
+}
202
+else if(responseParts[1] == "kg")
203
+{
204
+	unit = Units::Kilogram;
205
+}
206
+else if(responseParts[1] == "g")
207
+{
208
+	unit = Units::Gram;
209
+}
210
+else if(responseParts[1] == "oz")
211
+{
212
+	unit = Units::Ounce;
213
+}
214
+emit newMeasurement(weight, unit);
215
+
216
+@ Two methods are used to send commands to the scale. I am unsure of how well
217
+standardized remote key operation of scales are. The class may need to be
218
+extended to support more devices.
219
+
220
+@(scale.cpp@>=
221
+void SerialScale::tare()
222
+{
223
+	write("!KT\x0D");
224
+}
225
+
226
+void SerialScale::weigh()
227
+{
228
+	write("!KP\x0D");
229
+}
230
+
231
+@ This must be available to the host environment.
232
+
233
+@<Function prototypes for scripting@>=
234
+QScriptValue constructSerialScale(QScriptContext *context, QScriptEngine *engine);
235
+void setSerialScaleProperties(QScriptValue value, QScriptEngine *engine);
236
+
237
+@ These functions are made known to the scripting engine in the usual way.
238
+
239
+@<Set up the scripting engine@>=
240
+constructor = engine->newFunction(constructSerialScale);
241
+value = engine->newQMetaObject(&SerialScale::staticMetaObject, constructor);
242
+engine->globalObject().setProperty("SerialScale", value);
243
+
244
+@ If we are to set up the serial ports from the host environment, a few
245
+enumerated types must be made known to the meta-object system.
246
+
247
+@<Class declarations@>=
248
+Q_DECLARE_METATYPE(BaudRateType)
249
+Q_DECLARE_METATYPE(DataBitsType)
250
+Q_DECLARE_METATYPE(ParityType)
251
+Q_DECLARE_METATYPE(StopBitsType)
252
+Q_DECLARE_METATYPE(FlowType)
253
+
254
+@ For each of these, a pair of functions converts values to script values and
255
+back. This is a very annoying aspect of the version of QextSerialPort currently
256
+used by \pn{}.
257
+
258
+@<Function prototypes for scripting@>=
259
+QScriptValue BaudRateType_toScriptValue(QScriptEngine *engine, const BaudRateType &value);
260
+void BaudRateType_fromScriptValue(const QScriptValue &sv, BaudRateType &value);
261
+QScriptValue DataBitsType_toScriptValue(QScriptEngine *engine, const DataBitsType &value);
262
+void DataBitsType_fromScriptValue(const QScriptValue &sv, DataBitsType &value);
263
+QScriptValue ParityType_toScriptValue(QScriptEngine *engine, const ParityType &value);
264
+void ParityType_fromScriptValue(const QScriptValue &sv, ParityType &value);
265
+QScriptValue StopBitsType_toScriptValue(QScriptEngine *engine, const StopBitsType &value);
266
+void StopBitsType_fromScriptValue(const QScriptValue &sv, StopBitsType &value);
267
+QScriptValue FlowType_toScriptValue(QScriptEngine *engine, const FlowType &value);
268
+void FlowType_fromScriptValue(const QScriptValue &sv, FlowType &value);
269
+
270
+@ These are implemented thusly.
271
+
272
+@<Functions for scripting@>=
273
+QScriptValue BaudRateType_toScriptValue(QScriptEngine *engine, const BaudRateType &value)
274
+{
275
+	return engine->newVariant(QVariant((int)(value)));
276
+}
277
+
278
+void BaudRateType_fromScriptValue(const QScriptValue &sv, BaudRateType &value)
279
+{
280
+	value = (BaudRateType)(sv.toVariant().toInt());
281
+}
282
+
283
+QScriptValue DataBitsType_toScriptValue(QScriptEngine *engine, const DataBitsType &value)
284
+{
285
+	return engine->newVariant(QVariant((int)(value)));
286
+}
287
+
288
+void DataBitsType_fromScriptValue(const QScriptValue &sv, DataBitsType &value)
289
+{
290
+	value = (DataBitsType)(sv.toVariant().toInt());
291
+}
292
+
293
+QScriptValue ParityType_toScriptValue(QScriptEngine *engine, const ParityType &value)
294
+{
295
+	return engine->newVariant(QVariant((int)(value)));
296
+}
297
+
298
+void ParityType_fromScriptValue(const QScriptValue &sv, ParityType &value)
299
+{
300
+	value = (ParityType)(sv.toVariant().toInt());
301
+}
302
+
303
+QScriptValue StopBitsType_toScriptValue(QScriptEngine *engine, const StopBitsType &value)
304
+{
305
+	return engine->newVariant(QVariant((int)(value)));
306
+}
307
+
308
+void StopBitsType_fromScriptValue(const QScriptValue &sv, StopBitsType &value)
309
+{
310
+	value = (StopBitsType)(sv.toVariant().toInt());
311
+}
312
+
313
+QScriptValue FlowType_toScriptValue(QScriptEngine *engine, const FlowType &value)
314
+{
315
+	return engine->newVariant(QVariant((int)(value)));
316
+}
317
+
318
+void FlowType_fromScriptValue(const QScriptValue &sv, FlowType &value)
319
+{
320
+	value = (FlowType)(sv.toVariant().toInt());
321
+}
322
+
323
+@ These conversion functions are then registered.
324
+
325
+@<Set up the scripting engine@>=
326
+qScriptRegisterMetaType(engine, BaudRateType_toScriptValue, BaudRateType_fromScriptValue);
327
+qScriptRegisterMetaType(engine, DataBitsType_toScriptValue, DataBitsType_fromScriptValue);
328
+qScriptRegisterMetaType(engine, ParityType_toScriptValue, ParityType_fromScriptValue);
329
+qScriptRegisterMetaType(engine, StopBitsType_toScriptValue, StopBitsType_fromScriptValue);
330
+qScriptRegisterMetaType(engine, FlowType_toScriptValue, FlowType_fromScriptValue);
331
+
332
+@ In order to make this class available to the host environment, we must also
333
+include the appropriate header file.
334
+
335
+@<Header files to include@>=
336
+#include "scale.h"
337
+
338
+@ Most of the properties of interest should be added automatically, however
339
+there are non-slot methods in |QIODevice| that we require.
340
+
341
+@<Functions for scripting@>=
342
+void setSerialScaleProperties(QScriptValue value, QScriptEngine *engine)
343
+{
344
+	setQIODeviceProperties(value, engine);
345
+}
346
+
347
+@ The script constructor should seem familiar.
348
+
349
+@<Functions for scripting@>=
350
+QScriptValue constructSerialScale(QScriptContext *context, QScriptEngine *engine)
351
+{
352
+	QScriptValue object;
353
+	if(context->argumentCount() == 1)
354
+	{
355
+		object = engine->newQObject(new SerialScale(argument<QString>(0, context)));
356
+		setSerialScaleProperties(object, engine);
357
+	}
358
+	else
359
+	{
360
+		context->throwError("Incorrect number of arguments passed to "
361
+		                    "SerialScale. The constructor takes one string "
362
+		                    "as an argument specifying a port name.");
363
+	}
364
+	return object;
365
+}
366
+
367
+@ In order to allow configuration of scales from within \pn{}, a configuration
368
+widget must be provided.
369
+
370
+@<Class declarations@>=
371
+class SerialScaleConfWidget : public BasicDeviceConfigurationWidget
372
+{
373
+	Q_OBJECT
374
+	public:
375
+		Q_INVOKABLE SerialScaleConfWidget(DeviceTreeModel *model,
376
+		                                  const QModelIndex &index);
377
+	private slots:
378
+		void updatePort(const QString &newPort);
379
+		void updateBaudRate(const QString &rate);
380
+		void updateParity(int index);
381
+		void updateFlowControl(int index);
382
+		void updateStopBits(int index);
383
+	private:
384
+		PortSelector *port;
385
+		BaudSelector *baud;
386
+		ParitySelector *parity;
387
+		FlowSelector *flow;
388
+		StopSelector *stop;
389
+};
390
+
391
+@ This is very similar to other configuration widgets.
392
+
393
+@<SerialScaleConfWidget implementation@>=
394
+SerialScaleConfWidget::SerialScaleConfWidget(DeviceTreeModel *model,
395
+                                             const QModelIndex &index)
396
+: BasicDeviceConfigurationWidget(model, index),
397
+  port(new PortSelector), baud(new BaudSelector), parity(new ParitySelector),
398
+  flow(new FlowSelector), stop(new StopSelector)
399
+{
400
+	QFormLayout *layout = new QFormLayout;
401
+	layout->addRow(tr("Port:"), port);
402
+	connect(port, SIGNAL(currentIndexChanged(QString)),
403
+	        this, SLOT(updatePort(QString)));
404
+	connect(port, SIGNAL(editTextChanged(QString)),
405
+	        this, SLOT(updatePort(QString)));
406
+	layout->addRow(tr("Baud:"), baud);
407
+	connect(baud, SIGNAL(currentIndexChanged(QString)),
408
+	        this, SLOT(updateBaudRate(QString)));
409
+	layout->addRow(tr("Parity:"), parity);
410
+	connect(parity, SIGNAL(currentIndexChanged(int)),
411
+	        this, SLOT(updateParity(int)));
412
+	layout->addRow(tr("Flow Control:"), flow);
413
+	connect(flow, SIGNAL(currentIndexChanged(int)),
414
+	        this, SLOT(updateFlowControl(int)));
415
+	layout->addRow(tr("Stop Bits:"), stop);
416
+	connect(stop, SIGNAL(currentIndexChanged(int)),
417
+	        this, SLOT(updateStopBits(int)));
418
+	@<Get device configuration data for current node@>@;
419
+	for(int i = 0; i < configData.size(); i++)
420
+	{
421
+		node = configData.at(i).toElement();
422
+		if(node.attribute("name") == "port")
423
+		{
424
+			int j = port->findText(node.attribute("value"));
425
+			if(j >= 0)
426
+			{
427
+				port->setCurrentIndex(j);
428
+			}
429
+			else
430
+			{
431
+				port->insertItem(0, node.attribute("value"));
432
+				port->setCurrentIndex(0);
433
+			}
434
+		}
435
+		else if(node.attribute("name") == "baudrate")
436
+		{
437
+			baud->setCurrentIndex(baud->findText(node.attribute("value")));
438
+		}
439
+		else if(node.attribute("name") == "parity")
440
+		{
441
+			parity->setCurrentIndex(parity->findData(node.attribute("value")));
442
+		}
443
+		else if(node.attribute("name") == "flowcontrol")
444
+		{
445
+			flow->setCurrentIndex(flow->findData(node.attribute("value")));
446
+		}
447
+		else if(node.attribute("name") == "stopbits")
448
+		{
449
+			stop->setCurrentIndex(stop->findData(node.attribute("value")));
450
+		}
451
+	}
452
+	updatePort(port->currentText());
453
+	updateBaudRate(baud->currentText());
454
+	updateParity(parity->currentIndex());
455
+	updateFlowControl(flow->currentIndex());
456
+	updateStopBits(stop->currentIndex());
457
+	setLayout(layout);
458
+}
459
+
460
+@ Update methods are the same as were used in |ModbusRtuPortConfWidget|.
461
+
462
+@<SerialScaleConfWidget implementation@>=
463
+void SerialScaleConfWidget::updatePort(const QString &newPort)
464
+{
465
+	updateAttribute("port", newPort);
466
+}
467
+
468
+void SerialScaleConfWidget::updateBaudRate(const QString &rate)
469
+{
470
+	updateAttribute("baudrate", rate);
471
+}
472
+
473
+void SerialScaleConfWidget::updateParity(int index)
474
+{
475
+	updateAttribute("parity", parity->itemData(index).toString());
476
+}
477
+
478
+void SerialScaleConfWidget::updateFlowControl(int index)
479
+{
480
+	updateAttribute("flowcontrol", flow->itemData(index).toString());
481
+}
482
+
483
+void SerialScaleConfWidget::updateStopBits(int index)
484
+{
485
+	updateAttribute("stopbits", stop->itemData(index).toString());
486
+}
487
+
488
+@ The configuration widget is registered with the configuration system.
489
+
490
+@<Register device configuration widgets@>=
491
+app.registerDeviceConfigurationWidget("scale", SerialScaleConfWidget::staticMetaObject);
492
+
493
+@ A |NodeInserter| is also added.
494
+
495
+@<Register top level device configuration nodes@>=
496
+inserter = new NodeInserter(tr("Serial Scale"), tr("Scale"), "scale", NULL);
497
+topLevelNodeInserters.append(inserter);
498
+

+ 104
- 26
src/typica.w Visa fil

@@ -839,6 +839,9 @@ generated file empty.
839 839
 @<SettingsWindow implementation@>@/
840 840
 @<GraphSettingsWidget implementation@>@/
841 841
 @<DataqSdkDeviceConfWidget implementation@>@/
842
+@<SerialScaleConfWidget implementation@>@/
843
+@<ValueAnnotation implementation@>@/
844
+@<ValueAnnotationConfWidget implementation@>@/
842 845
 
843 846
 @ A few headers are required for various parts of \pn{}. These allow the use of
844 847
 various Qt modules.
@@ -976,6 +979,16 @@ template<> QModelIndex argument(int arg, QScriptContext *context)
976 979
 	return qscriptvalue_cast<QModelIndex>(context->argument(arg));
977 980
 }
978 981
 
982
+template<> double argument(int arg, QScriptContext *context)
983
+{
984
+	return (double)(context->argument(arg).toNumber());
985
+}
986
+
987
+template<> Units::Unit argument(int arg, QScriptContext *context)
988
+{
989
+	return (Units::Unit)(context->argument(arg).toInt32());
990
+}
991
+
979 992
 @ The scripting engine is informed of a number of classes defined elsewhere in
980 993
 the program. Code related to scripting these classes is grouped with the code
981 994
 implementing the classes. Additionally, there are several classes from Qt which
@@ -10006,7 +10019,7 @@ oseconds = TIMETOINT(relative);
10006 10019
 r = cseconds - oseconds;
10007 10020
 
10008 10021
 @ The logic for a count down timer is very similar to the logic for a count up
10009
-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
10010 10023
 timer has already reached 0.
10011 10024
 
10012 10025
 @<Check for Timer Decrement@>=
@@ -10066,7 +10079,7 @@ void TimerDisplay::startTimer()@t\2\2@>@/
10066 10079
 }
10067 10080
 
10068 10081
 @ Stopping the timer is a little simpler. Remember to stop the clock so we
10069
-aren't updating senselessly.
10082
+aren'@q'@>t updating senselessly.
10070 10083
 
10071 10084
 @<TimerDisplay Implementation@>=
10072 10085
 void TimerDisplay::stopTimer()@t\2\2@>@/
@@ -11971,7 +11984,7 @@ the last row to increase the size of the table.
11971 11984
 
11972 11985
 The end of this function may seem a little strange. Why not simply look up the
11973 11986
 map and insert information directly into the model data? Well, as of this
11974
-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
11975 11988
 have the lists store references and dereference the real data. The other option
11976 11989
 is to obtain a copy of the row, then a copy of the cell, update the cell, then
11977 11990
 replace the old value of the cell in the copy of the row, then replace the old
@@ -12046,7 +12059,7 @@ SaltModel::SaltModel(int columns) : QAbstractItemModel(), colcount(columns)
12046 12059
 	@<Expand the SaltModel@>@;
12047 12060
 }
12048 12061
 
12049
-@ The destructor doesn't need to do anything.
12062
+@ The destructor doesn'@q'@>t need to do anything.
12050 12063
 
12051 12064
 @<SaltModel Implementation@>=
12052 12065
 SaltModel::~SaltModel()
@@ -12119,7 +12132,7 @@ Qt::ItemFlags SaltModel::flags(const QModelIndex &index) const
12119 12132
 	@<Check that the SaltModel index is valid@>@;
12120 12133
 	if(valid)
12121 12134
 	{
12122
-		return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable;
12135
+		return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsDropEnabled;
12123 12136
 	}
12124 12137
 	return 0;
12125 12138
 }
@@ -12465,7 +12478,7 @@ if(settings.value("database/exists", "false").toString() == "false")
12465 12478
 @ In order to connect to the database, we need five pieces of information: the
12466 12479
 name of a database driver (PostgreSQL is recommended for now), the host name of
12467 12480
 the computer running the database, the name of the database, the name of the
12468
-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
12469 12482
 be stored in the user settings for the application so that the database
12470 12483
 connection can be established without prompting the user next time. A class is
12471 12484
 provided to gather this information.
@@ -12658,7 +12671,7 @@ settings.setValue(QString("columnWidths/%1/%2/%3").
12658 12671
 				  QVariant(newsize));
12659 12672
 
12660 12673
 @ To determine which window a given table is in, we just follow
12661
-|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
12662 12675
 will also be the window, however this is not advised as it is easier for the
12663 12676
 settings key to be non-unique in such a case.
12664 12677
 
@@ -12960,7 +12973,7 @@ for(int j = 0; j < hierarchy.size() - 1; j++)
12960 12973
 in Qt. This brings several benefits, including making it easy to print reports
12961 12974
 or save reports as plain text or HTML.
12962 12975
 
12963
-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
12964 12977
 static elements and elements that are populated by external data such as the
12965 12978
 result of a SQL query.
12966 12979
 
@@ -13212,7 +13225,7 @@ do
13212 13225
 @ It is sometimes desirable to add fixed data such as column headers to a table.
13213 13226
 This is done with the {\tt <row>} element.
13214 13227
 
13215
-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
13216 13229
 {\tt <query>} element to select constant data, but this approach saves a trip to
13217 13230
 the database.
13218 13231
 
@@ -14199,7 +14212,7 @@ else
14199 14212
 	saveDeviceConfiguration();
14200 14213
 }
14201 14214
 
14202
-@ 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
14203 14216
 is corrupt, but an error message can be produced if the program happens to have
14204 14217
 access to a debugging console.
14205 14218
 
@@ -14589,7 +14602,7 @@ QDomElement DeviceTreeModel::referenceElement(const QString &id)
14589 14602
 	return QDomElement();
14590 14603
 }
14591 14604
 
14592
-@ We don't want any headers, so |headerData()| is very simple.
14605
+@ We don'@q'@>t want any headers, so |headerData()| is very simple.
14593 14606
 
14594 14607
 @<DeviceTreeModel implementation@>=
14595 14608
 QVariant DeviceTreeModel::headerData(int, Qt::Orientation, int) const
@@ -15217,6 +15230,7 @@ RoasterConfWidget::RoasterConfWidget(DeviceTreeModel *model, const QModelIndex &
15217 15230
 	        this, SLOT(insertChildNode(QString, QString)));
15218 15231
 	connect(freeAnnotationInserter, SIGNAL(triggered(QString, QString)),
15219 15232
 	        this, SLOT(insertChildNode(QString, QString)));
15233
+	@<Add annotation control node inserters@>@;
15220 15234
 	addAnnotationControlButton->setMenu(annotationMenu);
15221 15235
 	layout->addWidget(addAnnotationControlButton);
15222 15236
 	QPushButton *advancedButton = new QPushButton(tr("Advanced Features"));
@@ -15441,6 +15455,7 @@ class Ni9211TcConfWidget : public BasicDeviceConfigurationWidget
15441 15455
 	@[private slots@]:@/
15442 15456
 		void updateThermocoupleType(const QString &type);
15443 15457
 		void updateColumnName(const QString &name);
15458
+		void updateHidden(bool hidden);
15444 15459
 };
15445 15460
 
15446 15461
 @ This follows the same pattern of previous device configuration widgets. The
@@ -15466,6 +15481,8 @@ Ni9211TcConfWidget::Ni9211TcConfWidget(DeviceTreeModel *model,
15466 15481
 	typeSelector->addItem("R");
15467 15482
 	typeSelector->addItem("S");
15468 15483
 	layout->addRow(tr("Thermocouple Type:"), typeSelector);
15484
+	QCheckBox *hideSeries = new QCheckBox("Hide this channel");
15485
+	layout->addRow(hideSeries);
15469 15486
 	setLayout(layout);
15470 15487
 	@<Get device configuration data for current node@>@;
15471 15488
 	for(int i = 0; i < configData.size(); i++)
@@ -15480,12 +15497,18 @@ Ni9211TcConfWidget::Ni9211TcConfWidget(DeviceTreeModel *model,
15480 15497
 		{
15481 15498
 			columnName->setText(node.attribute("value"));
15482 15499
 		}
15500
+		else if(node.attribute("name") == "hidden")
15501
+		{
15502
+			hideSeries->setChecked(node.attribute("value") == "true");
15503
+		}
15483 15504
 	}
15484 15505
 	updateThermocoupleType(typeSelector->currentText());
15485 15506
 	updateColumnName(columnName->text());
15507
+	updateHidden(hideSeries->isChecked());
15486 15508
 	connect(typeSelector, SIGNAL(currentIndexChanged(QString)),
15487 15509
 	        this, SLOT(updateThermocoupleType(QString)));
15488 15510
 	connect(columnName, SIGNAL(textEdited(QString)), this, SLOT(updateColumnName(QString)));
15511
+	connect(hideSeries, SIGNAL(toggled(bool)), this, SLOT(updateHidden(bool)));
15489 15512
 }
15490 15513
 
15491 15514
 @ Two slots are used to pass configuration changes back to the underlying XML
@@ -15502,6 +15525,11 @@ void Ni9211TcConfWidget::updateColumnName(const QString &name)
15502 15525
 	updateAttribute("columnname", name);
15503 15526
 }
15504 15527
 
15528
+void Ni9211TcConfWidget::updateHidden(bool hidden)
15529
+{
15530
+	updateAttribute("hidden", hidden ? "true" : "false");
15531
+}
15532
+
15505 15533
 @ These three widgets need to be registered so the configuration widget can
15506 15534
 instantiate them when the nodes are selected.
15507 15535
 
@@ -15654,6 +15682,7 @@ class NiDaqMxTc01ConfWidget : public BasicDeviceConfigurationWidget
15654 15682
 		void updateDeviceId(const QString &newId);
15655 15683
 		void updateThermocoupleType(const QString &type);
15656 15684
 		void updateColumnName(const QString &name);
15685
+		void updateHidden(bool hidden);
15657 15686
 };
15658 15687
 
15659 15688
 @ The implementation is similar to the other configuration widgets.
@@ -15678,6 +15707,8 @@ NiDaqMxTc01ConfWidget::NiDaqMxTc01ConfWidget(DeviceTreeModel *model,
15678 15707
 	typeSelector->addItem("R");
15679 15708
 	typeSelector->addItem("S");
15680 15709
 	layout->addRow(tr("Thermocouple Type:"), typeSelector);
15710
+	QCheckBox *hideSeries = new QCheckBox(tr("Hide this channel"));
15711
+	layout->addRow(hideSeries);
15681 15712
 	@<Get device configuration data for current node@>@;
15682 15713
 	for(int i = 0; i < configData.size(); i++)
15683 15714
 	{
@@ -15694,14 +15725,20 @@ NiDaqMxTc01ConfWidget::NiDaqMxTc01ConfWidget(DeviceTreeModel *model,
15694 15725
 		{
15695 15726
 			columnName->setText(node.attribute("value"));
15696 15727
 		}
15728
+		else if(node.attribute("name") == "hidden")
15729
+		{
15730
+			hideSeries->setChecked(node.attribute("value") == "true");
15731
+		}
15697 15732
 	}
15698 15733
 	updateDeviceId(deviceId->text());
15699 15734
 	updateThermocoupleType(typeSelector->currentText());
15700 15735
 	updateColumnName(columnName->text());
15736
+	updateHidden(hideSeries->isChecked());
15701 15737
 	connect(deviceId, SIGNAL(textEdited(QString)), this, SLOT(updateDeviceId(QString)));
15702 15738
 	connect(typeSelector, SIGNAL(currentIndexChanged(QString)), this, SLOT(updateThermocoupleType(QString)));
15703 15739
 	connect(columnName, SIGNAL(textEdited(QString)), this, SLOT(updateColumnName(QString)));
15704 15740
 	setLayout(layout);
15741
+	connect(hideSeries, SIGNAL(toggled(bool)), this, SLOT(updateHidden(bool)));
15705 15742
 }
15706 15743
 
15707 15744
 void NiDaqMxTc01ConfWidget::updateDeviceId(const QString &newId)
@@ -15719,6 +15756,11 @@ void NiDaqMxTc01ConfWidget::updateColumnName(const QString &name)
15719 15756
 	updateAttribute("columnname", name);
15720 15757
 }
15721 15758
 
15759
+void NiDaqMxTc01ConfWidget::updateHidden(bool hidden)
15760
+{
15761
+	updateAttribute("hidden", hidden ? "true" : "false");
15762
+}
15763
+
15722 15764
 @ These configuration widgets need to be registered so they can be instantiated
15723 15765
 in response to node selections.
15724 15766
 
@@ -15782,7 +15824,11 @@ PortSelector::PortSelector(QWidget *parent) : QComboBox(parent),
15782 15824
 	QextPortInfo port;
15783 15825
 	foreach(port, ports)
15784 15826
 	{
15827
+#ifdef Q_OS_WIN32
15785 15828
 		addItem(port.portName);
15829
+#else
15830
+		addItem(port.physName);
15831
+#endif
15786 15832
 	}
15787 15833
 	lister->setUpNotifications();
15788 15834
 	connect(lister, SIGNAL(deviceDiscovered(QextPortInfo)),
@@ -15801,7 +15847,7 @@ by the current operating system are available to select.
15801 15847
 A later version of QextSerialPort than is used by \pn{} provides a helper
15802 15848
 class which can be used more conveniently to create this sort of control. As
15803 15849
 this is not yet available to \pn{}, we instead copy the |enum| specifying
15804
-the appropriate values into the class and use Qt's meta-object system to
15850
+the appropriate values into the class and use Qt'@q'@>s meta-object system to
15805 15851
 populate the combo box based on the values in that |enum|.
15806 15852
 
15807 15853
 @<Class declarations@>=
@@ -17289,7 +17335,7 @@ void ModbusRTUDevice::mResponse(QByteArray response)
17289 17335
 }
17290 17336
 
17291 17337
 @ There are two ways that we might request measurement data. All of the
17292
-devices I've seen documented provide function 0x4 addresses for PV and SV
17338
+devices I'@q'@>ve seen documented provide function 0x4 addresses for PV and SV
17293 17339
 such that SV can be obtained from the address immediately after the address
17294 17340
 from which we obtain PV. In this case we request both values at the same time.
17295 17341
 
@@ -17374,7 +17420,7 @@ more than one response in the buffer at a time. It is, however, likely that
17374 17420
 this buffer will have incomplete data. This means that we must determine when
17375 17421
 the full response is available before passing the complete response along to
17376 17422
 the appropriate method. If the response has not been received in full, nothing
17377
-is done. We'll be notified of more data shortly.
17423
+is done. We'@q'@>ll be notified of more data shortly.
17378 17424
 
17379 17425
 When the message we see the response for was queued, a callback was also
17380 17426
 registered to handle the response. Once we have the complete message, we pass
@@ -17547,7 +17593,7 @@ void ModbusRTUDevice::outputSV(double value)
17547 17593
 	queueMessage(message, this, "ignore(QByteArray)");
17548 17594
 }
17549 17595
 
17550
-@ We don't care about the response when sending a new SV.
17596
+@ We don'@q'@>t care about the response when sending a new SV.
17551 17597
 
17552 17598
 @<ModbusRTUDevice implementation@>=
17553 17599
 void ModbusRTUDevice::ignore(QByteArray)
@@ -17683,6 +17729,8 @@ class ModbusConfigurator : public BasicDeviceConfigurationWidget
17683 17729
 		void updateSVWriteAddress(int address);
17684 17730
 		void updatePVColumnName(const QString &name);
17685 17731
 		void updateSVColumnName(const QString &name);
17732
+		void updatePVHidden(bool hidden);
17733
+		void updateSVHidden(bool hidden);
17686 17734
 	private:@/
17687 17735
 		PortSelector *port;
17688 17736
 		BaudSelector *baud;
@@ -17774,6 +17822,8 @@ ModbusConfigurator::ModbusConfigurator(DeviceTreeModel *model, const QModelIndex
17774 17822
 	QFormLayout *pVSection = new QFormLayout;
17775 17823
 	pVSection->addRow(tr("Value relative address:"), pVAddress);
17776 17824
 	pVSection->addRow(tr("PV column name:"), pVColumnName);
17825
+	QCheckBox *pVHideBox = new QCheckBox(tr("Hide this channel"));
17826
+	pVSection->addRow(pVHideBox);
17777 17827
 	QGroupBox *processValueBox = new QGroupBox(tr("Process Value"));
17778 17828
 	processValueBox->setLayout(pVSection);
17779 17829
 	seriesLayout->addWidget(processValueBox);
@@ -17794,6 +17844,8 @@ ModbusConfigurator::ModbusConfigurator(DeviceTreeModel *model, const QModelIndex
17794 17844
 	sVSection->addRow(tr("Upper limit:"), sVUpper);
17795 17845
 	sVSection->addRow(tr("Output set value:"), sVWritable);
17796 17846
 	sVSection->addRow(tr("Output relative address:"), sVOutputAddr);
17847
+	QCheckBox *sVHideBox = new QCheckBox(tr("Hide this channel"));
17848
+	sVSection->addRow(sVHideBox);
17797 17849
 	QGroupBox *setValueBox = new QGroupBox(tr("Set Value"));
17798 17850
 	setValueBox->setLayout(sVSection);
17799 17851
 	seriesLayout->addWidget(setValueBox);
@@ -17952,6 +18004,14 @@ ModbusConfigurator::ModbusConfigurator(DeviceTreeModel *model, const QModelIndex
17952 18004
 		{
17953 18005
 			sVColumnName->setText(node.attribute("value"));
17954 18006
 		}
18007
+		else if(node.attribute("name") == "pvhidden")
18008
+		{
18009
+			pVHideBox->setChecked(node.attribute("value") == "true");
18010
+		}
18011
+		else if(node.attribute("name") == "svhidden")
18012
+		{
18013
+			sVHideBox->setChecked(node.attribute("value") == "true");
18014
+		}
17955 18015
 	}
17956 18016
 	updatePort(port->currentText());
17957 18017
 	updateBaudRate(baud->currentText());
@@ -17979,6 +18039,8 @@ ModbusConfigurator::ModbusConfigurator(DeviceTreeModel *model, const QModelIndex
17979 18039
 	updateSVWriteAddress(sVOutputAddr->value());
17980 18040
 	updatePVColumnName(pVColumnName->text());
17981 18041
 	updateSVColumnName(sVColumnName->text());
18042
+	updatePVHidden(pVHideBox->isChecked());
18043
+	updateSVHidden(sVHideBox->isChecked());
17982 18044
 	connect(port, SIGNAL(currentIndexChanged(QString)), this, SLOT(updatePort(QString)));
17983 18045
 	connect(port, SIGNAL(editTextChanged(QString)), this, SLOT(updatePort(QString)));
17984 18046
 	connect(baud, SIGNAL(currentIndexChanged(QString)), this, SLOT(updateBaudRate(QString)));
@@ -18006,6 +18068,8 @@ ModbusConfigurator::ModbusConfigurator(DeviceTreeModel *model, const QModelIndex
18006 18068
 	connect(sVOutputAddr, SIGNAL(valueChanged(int)), this, SLOT(updateSVWriteAddress(int)));
18007 18069
 	connect(pVColumnName, SIGNAL(textEdited(QString)), this, SLOT(updatePVColumnName(QString)));
18008 18070
 	connect(sVColumnName, SIGNAL(textEdited(QString)), this, SLOT(updateSVColumnName(QString)));
18071
+	connect(pVHideBox, SIGNAL(toggled(bool)), this, SLOT(updatePVHidden(bool)));
18072
+	connect(sVHideBox, SIGNAL(toggled(bool)), this, SLOT(updateSVHidden(bool)));
18009 18073
 	layout->addWidget(form);
18010 18074
 	setLayout(layout);
18011 18075
 }
@@ -18140,6 +18204,16 @@ void ModbusConfigurator::updateSVColumnName(const QString &name)
18140 18204
 	updateAttribute("svcolname", name);
18141 18205
 }
18142 18206
 
18207
+void ModbusConfigurator::updatePVHidden(bool hidden)
18208
+{
18209
+	updateAttribute("pvhidden", hidden ? "true" : "false");
18210
+}
18211
+
18212
+void ModbusConfigurator::updateSVHidden(bool hidden)
18213
+{
18214
+	updateAttribute("svhidden", hidden ? "true" : "false");
18215
+}
18216
+
18143 18217
 @ This is registered with the configuration system.
18144 18218
 
18145 18219
 @<Register device configuration widgets@>=
@@ -18168,7 +18242,7 @@ class LinearSplineInterpolationConfWidget : public BasicDeviceConfigurationWidge
18168 18242
 		void updateDestinationColumn(const QString &dest);
18169 18243
 		void updateKnots();
18170 18244
 	private:@/
18171
-		SaltModel *knotmodel;
18245
+		SaltModel *tablemodel;
18172 18246
 };
18173 18247
 
18174 18248
 @ This is configured by specifying a source column name, a destination column
@@ -18177,17 +18251,17 @@ the mapping data, we store each column of the table in its own attribute.
18177 18251
 
18178 18252
 @<LinearSplineInterpolationConfWidget implementation@>=
18179 18253
 LinearSplineInterpolationConfWidget::LinearSplineInterpolationConfWidget(DeviceTreeModel *model, const QModelIndex &index)
18180
-: BasicDeviceConfigurationWidget(model, index), knotmodel(new SaltModel(2))
18254
+: BasicDeviceConfigurationWidget(model, index), tablemodel(new SaltModel(2))
18181 18255
 {
18182 18256
 	QFormLayout *layout = new QFormLayout;
18183 18257
 	QLineEdit *source = new QLineEdit;
18184 18258
 	layout->addRow(tr("Source column name:"), source);
18185 18259
 	QLineEdit *destination = new QLineEdit;
18186 18260
 	layout->addRow(tr("Destination column name:"), destination);
18187
-	knotmodel->setHeaderData(0, Qt::Horizontal, "Input");
18188
-	knotmodel->setHeaderData(1, Qt::Horizontal, "Output");
18261
+	tablemodel->setHeaderData(0, Qt::Horizontal, "Input");
18262
+	tablemodel->setHeaderData(1, Qt::Horizontal, "Output");
18189 18263
 	QTableView *mappingTable = new QTableView;
18190
-	mappingTable->setModel(knotmodel);
18264
+	mappingTable->setModel(tablemodel);
18191 18265
 	NumericDelegate *delegate = new NumericDelegate;
18192 18266
 	mappingTable->setItemDelegate(delegate);
18193 18267
 	layout->addRow(tr("Mapping data:"), mappingTable);
@@ -18222,7 +18296,7 @@ LinearSplineInterpolationConfWidget::LinearSplineInterpolationConfWidget(DeviceT
18222 18296
 	updateKnots();
18223 18297
 	connect(source, SIGNAL(textEdited(QString)), this, SLOT(updateSourceColumn(QString)));
18224 18298
 	connect(destination, SIGNAL(textEdited(QString)), this, SLOT(updateDestinationColumn(QString)));
18225
-	connect(knotmodel, SIGNAL(dataChanged(QModelIndex, QModelIndex)), this, SLOT(updateKnots()));
18299
+	connect(tablemodel, SIGNAL(dataChanged(QModelIndex, QModelIndex)), this, SLOT(updateKnots()));
18226 18300
 	setLayout(layout);
18227 18301
 }
18228 18302
 
@@ -18245,9 +18319,9 @@ model.
18245 18319
 @<Populate model column from list@>=
18246 18320
 for(int i = 0; i < itemList.size(); i++)
18247 18321
 {
18248
-	knotmodel->setData(knotmodel->index(i, column),
18249
-	                   QVariant(itemList.at(i).toDouble()),
18250
-                       Qt::DisplayRole);
18322
+	tablemodel->setData(tablemodel->index(i, column),
18323
+	               QVariant(itemList.at(i).toDouble()),
18324
+                   Qt::DisplayRole);
18251 18325
 }
18252 18326
 
18253 18327
 @ When data in the table is changed we simply overwrite any previously saved
@@ -18256,8 +18330,8 @@ data with the current data.
18256 18330
 @<LinearSplineInterpolationConfWidget implementation@>=
18257 18331
 void LinearSplineInterpolationConfWidget::updateKnots()
18258 18332
 {
18259
-	updateAttribute("sourcevalues", knotmodel->arrayLiteral(0, Qt::DisplayRole));
18260
-	updateAttribute("destinationvalues", knotmodel->arrayLiteral(1, Qt::DisplayRole));
18333
+	updateAttribute("sourcevalues", tablemodel->arrayLiteral(0, Qt::DisplayRole));
18334
+	updateAttribute("destinationvalues", tablemodel->arrayLiteral(1, Qt::DisplayRole));
18261 18335
 }
18262 18336
 
18263 18337
 void LinearSplineInterpolationConfWidget::updateSourceColumn(const QString &source)
@@ -18368,6 +18442,10 @@ app.registerDeviceConfigurationWidget("translation", TranslationConfWidget::stat
18368 18442
 
18369 18443
 @i dataqsdk.w
18370 18444
 
18445
+@i scales.w
18446
+
18447
+@i valueannotation.w
18448
+
18371 18449
 @** Local changes.
18372 18450
 
18373 18451
 \noindent This is the end of \pn{} as distributed by its author. It is expected

+ 216
- 2
src/units.w Visa fil

@@ -22,11 +22,17 @@ class Units: public QObject
22 22
 			Fahrenheit = 10144,
23 23
 			Celsius = 10143,
24 24
 			Kelvin = 10325,
25
-			Rankine = 10145
25
+			Rankine = 10145,
26
+			Pound = 15876,
27
+			Kilogram = 15877,
28
+			Ounce = 1,
29
+			Gram = 2
26 30
 		};
27 31
 		static double convertTemperature(double value, Unit fromUnit, Unit toUnit);
28 32
 		static double convertRelativeTemperature(double value, Unit fromUnit, Unit toUnit);
29 33
 		static bool isTemperatureUnit(Unit unit);
34
+		static double convertWeight(double value, Unit fromUnit, Unit toUnit);
35
+		static bool isWeightUnit(Unit unit);
30 36
 };
31 37
 
32 38
 #endif
@@ -175,6 +181,9 @@ double Units::convertRelativeTemperature(double value, Unit fromUnit, Unit toUni
175 181
 				case Rankine:
176 182
 					return value;
177 183
 					break;
184
+				default:
185
+					return 0;
186
+					break;
178 187
 			}
179 188
 			break;
180 189
 		case Celsius:
@@ -192,6 +201,9 @@ double Units::convertRelativeTemperature(double value, Unit fromUnit, Unit toUni
192 201
 				case Rankine:
193 202
 					return value * (9.0 / 5.0);
194 203
 					break;
204
+				default:
205
+					return 0;
206
+					break;
195 207
 			}
196 208
 			break;
197 209
 		case Kelvin:
@@ -209,6 +221,9 @@ double Units::convertRelativeTemperature(double value, Unit fromUnit, Unit toUni
209 221
 				case Rankine:
210 222
 					return value * (9.0 / 5.0);
211 223
 					break;
224
+				default:
225
+					return 0;
226
+					break;
212 227
 			}
213 228
 			break;
214 229
 		case Rankine:
@@ -226,6 +241,9 @@ double Units::convertRelativeTemperature(double value, Unit fromUnit, Unit toUni
226 241
 				case Rankine:
227 242
 					return value;
228 243
 					break;
244
+				default:
245
+					return 0;
246
+					break;
229 247
 			}
230 248
 			break;
231 249
 		default:
@@ -235,11 +253,207 @@ double Units::convertRelativeTemperature(double value, Unit fromUnit, Unit toUni
235 253
 	return 0;
236 254
 }
237 255
 
256
+@ Units of force are handled similarly to units of temperature.
257
+
258
+@(units.cpp@>=
259
+double Units::convertWeight(double value, Unit fromUnit, Unit toUnit)
260
+{
261
+	if(isWeightUnit(fromUnit) && isWeightUnit(toUnit))
262
+	{
263
+		switch(fromUnit)
264
+		{
265
+			case Pound:
266
+				switch(toUnit)
267
+				{
268
+					case Pound:
269
+						return value;
270
+						break;
271
+					case Kilogram:
272
+						return value / 2.2;
273
+						break;
274
+					case Ounce:
275
+						return value * 16.0;
276
+						break;
277
+					case Gram:
278
+						return value / 0.0022;
279
+						break;
280
+					default:
281
+						return 0;
282
+						break;
283
+				}
284
+				break;
285
+			case Kilogram:
286
+				switch(toUnit)
287
+				{
288
+					case Pound:
289
+						return value * 2.2;
290
+						break;
291
+					case Kilogram:
292
+						return value;
293
+						break;
294
+					case Ounce:
295
+						return value * 35.2;
296
+						break;
297
+					case Gram:
298
+						return value * 1000.0;
299
+						break;
300
+					default:
301
+						return 0;
302
+						break;
303
+				}
304
+				break;
305
+			case Ounce:
306
+				switch(toUnit)
307
+				{
308
+					case Pound:
309
+						return value / 16.0;
310
+						break;
311
+					case Kilogram:
312
+						return value / 35.2;
313
+						break;
314
+					case Ounce:
315
+						return value;
316
+						break;
317
+					case Gram:
318
+						return value / 0.0352;
319
+						break;
320
+					default:
321
+						return 0;
322
+						break;
323
+				}
324
+				break;
325
+			case Gram:
326
+				switch(toUnit)
327
+				{
328
+					case Pound:
329
+						return value * 0.0022;
330
+						break;
331
+					case Kilogram:
332
+						return value / 1000.0;
333
+						break;
334
+					case Ounce:
335
+						return value * 0.0352;
336
+						break;
337
+					case Gram:
338
+						return value;
339
+						break;
340
+					default:
341
+						return 0;
342
+						break;
343
+				}
344
+				break;
345
+			default:
346
+				return 0;
347
+				break;
348
+		}
349
+	}
350
+	return 0;
351
+}
352
+
353
+bool Units::isWeightUnit(Unit unit)
354
+{
355
+	if(unit == Pound ||
356
+	   unit == Kilogram ||
357
+	   unit == Ounce ||
358
+	   unit == Gram)
359
+	{
360
+		return true;
361
+	}
362
+	return false;
363
+}
364
+
238 365
 @ This class is exposed to the host environment. Note the lack of constructor.
239 366
 We do not wish to create any instances, just have access to the |Unit|
240
-enumeration.
367
+enumeration and conversion methods.
368
+
369
+Unfortunately, marking a static method |Q_INVOKABLE| will not work as per Qt
370
+bug #18840. There seems to be no intention to correct this deficiency so
371
+instead of having something that just works, we must resort to the following
372
+hackery.
373
+
374
+@<Function prototypes for scripting@>=
375
+QScriptValue Units_convertTemperature(QScriptContext *context, QScriptEngine *engine);
376
+QScriptValue Units_convertRelativeTemperature(QScriptContext *context,
377
+                                              QScriptEngine *engine);
378
+QScriptValue Units_isTemperatureUnit(QScriptContext *context, QScriptEngine *engine);
379
+QScriptValue Units_convertWeight(QScriptContext *context, QScriptEngine *engine);
380
+QScriptValue Units_isWeightUnit(QScriptContext *context, QScriptEngine *engine);
381
+
382
+@ These functions are added as properties for the host environment.
241 383
 
242 384
 @<Set up the scripting engine@>=
243 385
 value = engine->newQMetaObject(&Units::staticMetaObject);
386
+value.setProperty("convertTemperature", engine->newFunction(Units_convertTemperature));
387
+value.setProperty("convertRelativeTemperature",
388
+                  engine->newFunction(Units_convertRelativeTemperature));
389
+value.setProperty("isTemperatureUnit", engine->newFunction(Units_isTemperatureUnit));
390
+value.setProperty("convertWeight", engine->newFunction(Units_convertWeight));
391
+value.setProperty("isWeightUnit", engine->newFunction(Units_isWeightUnit));
244 392
 engine->globalObject().setProperty("Units", value);
245 393
 
394
+@ The implementation of these functions is trivial.
395
+
396
+@<Functions for scripting@>=
397
+QScriptValue Units_convertTemperature(QScriptContext *context, QScriptEngine *engine)
398
+{
399
+	return QScriptValue(Units::convertTemperature(argument<double>(0, context),
400
+	                                              argument<Units::Unit>(1, context),
401
+	                                              argument<Units::Unit>(2, context)));
402
+}
403
+
404
+QScriptValue Units_convertRelativeTemperature(QScriptContext *context,
405
+                                              QScriptEngine *engine)
406
+{
407
+	return QScriptValue(Units::convertRelativeTemperature(
408
+	                         argument<double>(0, context),
409
+	                         argument<Units::Unit>(1, context),
410
+	                         argument<Units::Unit>(2, context)));
411
+}
412
+
413
+QScriptValue Units_isTemperatureUnit(QScriptContext *context, QScriptEngine *engine)
414
+{
415
+	return QScriptValue(Units::isTemperatureUnit(argument<Units::Unit>(0, context)));
416
+}
417
+
418
+QScriptValue Units_convertWeight(QScriptContext *context, QScriptEngine *engine)
419
+{
420
+	return QScriptValue(Units::convertWeight(argument<double>(0, context),
421
+	                                         argument<Units::Unit>(1, context),
422
+	                                         argument<Units::Unit>(2, context)));
423
+}
424
+
425
+QScriptValue Units_isWeightUnit(QScriptContext *context, QScriptEngine *engine)
426
+{
427
+	return QScriptValue(Units::isWeightUnit(argument<Units::Unit>(0, context)));
428
+}
429
+
430
+@ To pass unit data in some circumstances, the inner enumeration must be
431
+registered as a meta-type with script conversions.
432
+
433
+@<Class declarations@>=
434
+Q_DECLARE_METATYPE(Units::Unit)
435
+
436
+@ A pair of conversion methods is required.
437
+
438
+@<Function prototypes for scripting@>=
439
+QScriptValue Unit_toScriptValue(QScriptEngine *engine, const Units::Unit &value);
440
+void Unit_fromScriptValue(const QScriptValue &sv, Units::Unit &value);
441
+
442
+@ These are implemented thusly.
443
+
444
+@<Functions for scripting@>=
445
+QScriptValue Unit_toScriptValue(QScriptEngine *engine, const Units::Unit &value)
446
+{
447
+	return engine->newVariant(QVariant(value));
448
+}
449
+
450
+void Unit_fromScriptValue(const QScriptValue &sv, Units::Unit &value)
451
+{
452
+	value = sv.toVariant().value<Units::Unit>();
453
+}
454
+
455
+@ These conversion functions are registered.
456
+
457
+@<Set up the scripting engine@>=
458
+qScriptRegisterMetaType(engine, Unit_toScriptValue, Unit_fromScriptValue);
459
+

+ 289
- 0
src/valueannotation.w Visa fil

@@ -0,0 +1,289 @@
1
+@** Annotations for Values.
2
+
3
+\noindent In circumstances where a control setting is logged but this setting
4
+changes infrequently and has a small number of possible values, it is sometimes
5
+useful to not log the control values throughout the roast but rather simply add
6
+an annotation when the control value changes. It will commonly be desirable to
7
+also provide an annotation representing the current state at the start of every
8
+batch.
9
+
10
+To support this feature, there must be a configuration widget which can be used
11
+to identify which data series should be monitored and what annotations should
12
+be produced for what values.
13
+
14
+@<Class declarations@>=
15
+class ValueAnnotationConfWidget : public BasicDeviceConfigurationWidget
16
+{
17
+	Q_OBJECT
18
+	public:
19
+		Q_INVOKABLE ValueAnnotationConfWidget(DeviceTreeModel *model,
20
+                                              const QModelIndex &index);
21
+	private slots:
22
+		void updateSourceColumn(const QString &source);
23
+		void updateAnnotations();
24
+		void updateStart(bool noteOnStart);
25
+	private:
26
+		SaltModel *tablemodel;
27
+};
28
+
29
+@ The constructor sets up the configuration interface requesting a source
30
+column name, if an annotation should be emitted at the start of the batch, and
31
+what annotations should be produced for what values.
32
+
33
+@<ValueAnnotationConfWidget implementation@>=
34
+ValueAnnotationConfWidget::ValueAnnotationConfWidget(DeviceTreeModel *model,
35
+                                                     const QModelIndex &index)
36
+: BasicDeviceConfigurationWidget(model, index),
37
+  tablemodel(new SaltModel(2))
38
+{
39
+	QFormLayout *layout = new QFormLayout;
40
+	QLineEdit *source = new QLineEdit;
41
+	layout->addRow(tr("Source column name:"), source);
42
+	QCheckBox *noteOnStart = new QCheckBox(tr("Produce Start State Annotation"));
43
+	noteOnStart->setChecked(true);
44
+	layout->addRow(noteOnStart);
45
+	tablemodel->setHeaderData(0, Qt::Horizontal, "Value");
46
+	tablemodel->setHeaderData(1, Qt::Horizontal, "Annotation");
47
+	QTableView *annotationTable = new QTableView;
48
+	annotationTable->setModel(tablemodel);
49
+	NumericDelegate *delegate = new NumericDelegate;
50
+	annotationTable->setItemDelegateForColumn(0, delegate);
51
+	layout->addRow(tr("Annotations for values:"), annotationTable);
52
+	@<Get device configuration data for current node@>@;
53
+	for(int i = 0; i < configData.size(); i++)
54
+	{
55
+		node = configData.at(i).toElement();
56
+		if(node.attribute("name") == "source")
57
+		{
58
+			source->setText(node.attribute("value"));
59
+		}
60
+		else if(node.attribute("name") == "emitOnStart")
61
+		{
62
+			noteOnStart->setChecked(node.attribute("value") == "true" ? true : false);
63
+		}
64
+		else if(node.attribute("name") == "measuredValues")
65
+		{
66
+			@<Convert numeric array literal to list@>@;
67
+			int column = 0;
68
+			@<Populate model column from list@>@;
69
+		}
70
+		else if(node.attribute("name") == "annotations")
71
+		{
72
+			@<Convert string array literal to list@>@;
73
+			int column = 1;
74
+			@<Populate model column from string list@>@;
75
+		}
76
+	}
77
+	updateSourceColumn(source->text());
78
+	updateStart(noteOnStart->isChecked());
79
+	updateAnnotations();
80
+	connect(source, SIGNAL(textEdited(QString)), this, SLOT(updateSourceColumn(QString)));
81
+	connect(noteOnStart, SIGNAL(toggled(bool)), this, SLOT(updateStart(bool)));
82
+	connect(tablemodel, SIGNAL(dataChanged(QModelIndex, QModelIndex)), this, SLOT(updateAnnotations()));
83
+	setLayout(layout);
84
+}
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
+
112
+@ To update the table data, the measued values and annotations are saved in
113
+separate lists.
114
+
115
+@<ValueAnnotationConfWidget implementation@>=
116
+void ValueAnnotationConfWidget::updateAnnotations()
117
+{
118
+	updateAttribute("measuredValues", tablemodel->arrayLiteral(0, Qt::DisplayRole));
119
+	updateAttribute("annotations", tablemodel->arrayLiteral(1, Qt::DisplayRole));
120
+}
121
+
122
+@ The other settings are updated based on values passed through the parameter
123
+to the update method.
124
+
125
+@<ValueAnnotationConfWidget implementation@>=
126
+void ValueAnnotationConfWidget::updateSourceColumn(const QString &source)
127
+{
128
+	updateAttribute("source", source);
129
+}
130
+
131
+void ValueAnnotationConfWidget::updateStart(bool noteOnStart)
132
+{
133
+	updateAttribute("emitOnStart", noteOnStart ? "true" : "false");
134
+}
135
+
136
+@ The widget is registered with the configuration system.
137
+
138
+@<Register device configuration widgets@>=
139
+app.registerDeviceConfigurationWidget("valueannotation",
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)));
151
+
152
+@ While it is possible to implement this feature with |ThresholdDetector|
153
+objects, the code to handle these would be difficult to understand and there
154
+would be excessive overhead in moving measurements through all of these.
155
+Instead, we create a new class that watches for any sort of measurement
156
+change and produces the annotation signals directly.
157
+
158
+As measured values are represented as a |double|, a small value should be
159
+provided such that comparisons are not against the value directly but instead
160
+are against the value plus or minus this other small value.
161
+
162
+Method names have been chosen to be compatible with the |AnnotationButton|
163
+class.
164
+
165
+@<Class declarations@>=
166
+class ValueAnnotation : public QObject
167
+{
168
+	Q_OBJECT
169
+	public:
170
+		ValueAnnotation();
171
+		Q_INVOKABLE void setAnnotation(double value, const QString &annotation);
172
+	public slots:
173
+		void newMeasurement(Measurement measure);
174
+		void annotate();
175
+		void setAnnotationColumn(int column);
176
+		void setTemperatureColumn(int column);
177
+		void setTolerance(double epsilon);
178
+	signals:
179
+		void annotation(QString annotation, int tempcolumn, int notecolumn);
180
+	private:
181
+		int lastIndex;
182
+		int annotationColumn;
183
+		int measurementColumn;
184
+		QList<double> values;
185
+		QStringList annotations;
186
+		double tolerance;
187
+};
188
+
189
+@ Most of the work of this class happens in the |newMeasurement| method. This
190
+compares the latest measurement with every value that has an associated
191
+annotation. If the value is near enough to a value in the list, the index of
192
+that value is compared with the index of the previous annotation (if any) and
193
+if the indices are different, the appropriate annotation is emitted.
194
+
195
+@<ValueAnnotation implementation@>=
196
+void ValueAnnotation::newMeasurement(Measurement measure)
197
+{
198
+	for(int i = 0; i < values.size(); i++)
199
+	{
200
+		if(measure.temperature() > values.at(i) - tolerance &&
201
+		   measure.temperature() < values.at(i) + tolerance)
202
+		{
203
+			if(i != lastIndex)
204
+			{
205
+				lastIndex = i;
206
+				emit annotation(annotations.at(i), measurementColumn, annotationColumn);
207
+			}
208
+		}
209
+	}
210
+}
211
+
212
+@ Another method is used to emit an annotation matching the current state at
213
+the start of a batch if that is desired. This will not produce any output if
214
+no state has yet been matched.
215
+
216
+@<ValueAnnotation implementation@>=
217
+void ValueAnnotation::annotate()
218
+{
219
+	if(lastIndex > -1)
220
+	{
221
+		emit annotation(annotations.at(lastIndex), measurementColumn, annotationColumn);
222
+	}
223
+}
224
+
225
+@ Values and annotations are added to separate lists with new mappings always
226
+appended. Entries are never removed from these lists.
227
+
228
+@<ValueAnnotation implementation@>=
229
+void ValueAnnotation::setAnnotation(double value, const QString &annotation)
230
+{
231
+	values.append(value);
232
+	annotations.append(annotation);
233
+}
234
+
235
+@ The remaining setter methods are trivial similarly trivial.
236
+
237
+@<ValueAnnotation implementation@>=
238
+void ValueAnnotation::setAnnotationColumn(int column)
239
+{
240
+	annotationColumn = column;
241
+}
242
+
243
+void ValueAnnotation::setTemperatureColumn(int column)
244
+{
245
+	measurementColumn = column;
246
+}
247
+
248
+void ValueAnnotation::setTolerance(double epsilon)
249
+{
250
+	tolerance = epsilon;
251
+}
252
+
253
+@ This just leaves a trivial constructor.
254
+
255
+@<ValueAnnotation implementation@>=
256
+ValueAnnotation::ValueAnnotation() : QObject(),
257
+	lastIndex(-1), annotationColumn(2), measurementColumn(1), tolerance(0.05)
258
+{
259
+	/* Nothing needs to be done here. */
260
+}
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
+}

Laddar…
Avbryt
Spara