Browse Source

WIP: Manual Log Entry

Neal Wilson 6 years ago
parent
commit
4eee70d22a
4 changed files with 192 additions and 18 deletions
  1. 147
    7
      config/Windows/manuallogentry.xml
  2. 1
    0
      config/config.xml
  3. 5
    1
      src/graphsettings.w
  4. 39
    10
      src/typica.w

+ 147
- 7
config/Windows/manuallogentry.xml View File

@@ -4,6 +4,17 @@
4 4
 		<separator />
5 5
 		<item id="quitItem" shortcut="ctrl+Q">Quit</item>
6 6
 	</menu>
7
+	<menu name="Log">
8
+        <item id="clear" shortcut="Ctrl+L">Clear Log</item>
9
+        <separator />
10
+        <item id="ms">Millisecond View</item>
11
+        <item id="1s">1 Second View</item>
12
+        <item id="5s">5 Second View</item>
13
+        <item id="10s">10 Second View</item>
14
+        <item id="15s">15 Second View</item>
15
+        <item id="30s">30 Second View</item>
16
+        <item id="1m">1 Minute View</item>
17
+    </menu>
7 18
 	<layout type="vertical">
8 19
 		<tabbar id="tabs"/>
9 20
 		<layout type="stack" id="pages">
@@ -27,7 +38,7 @@
27 38
 									</row>
28 39
 									<row>
29 40
 										<column><label>Weight:</label></column>
30
-										<column><line id="sampleGreenWeight" /></column>
41
+										<column><line id="sampleGreenWeight" validator="numeric">0.0</line></column>
31 42
 										<column><sqldrop id="sampleGreenUnit" /></column>
32 43
 									</row>
33 44
 									<row>
@@ -36,7 +47,7 @@
36 47
 									</row>
37 48
 									<row>
38 49
 										<column><label>Arrival Date:</label></column>
39
-										<column><line id="sampleGreenArrivalDate" /></column>
50
+										<column><calendar id="sampleGreenArrivalDate" /></column>
40 51
 									</row>
41 52
 								</layout>
42 53
 								<label>Additional Details:</label>
@@ -75,7 +86,7 @@
75 86
 						</row>
76 87
 						<row>
77 88
 							<column><label>Weight:</label></column>
78
-							<column><line id="roastedWeight" validator="numeric" /></column>
89
+							<column><line id="roastedWeight" validator="numeric">0.0</line></column>
79 90
 						</row>
80 91
 						<row>
81 92
 							<column><label>Time:</label></column>
@@ -94,15 +105,25 @@
94 105
 			</page>
95 106
 			<page>
96 107
 				<layout type="vertical">
108
+					<layout type="horizontal">
109
+						<label>Time Increment (s):</label>
110
+						<line id="timeincrement" validator="numeric">30</line>
111
+						<stretch />
112
+						<label>Time:</label>
113
+						<timeedit id="currenttime" />
114
+						<stretch />
115
+						<label>Temperature:</label>
116
+						<line id="currenttemperature" validator="numeric" />
117
+						<label>Note:</label>
118
+						<line id="currentnote" />
119
+						<button name="Add Measurement" id="addmeasurement" type="push" />
120
+					</layout>
97 121
 					<splitter type="horizontal" id="roastdatasplit">
98 122
 						<measurementtable id="log" />
99 123
 						<graph id="graph" />
100 124
 					</splitter>
101 125
 				</layout>
102 126
 			</page>
103
-			<page>
104
-				
105
-			</page>
106 127
 		</layout>
107 128
 		<layout type="horizontal">
108 129
 			<stretch />
@@ -113,12 +134,21 @@
113 134
 	<![CDATA[
114 135
 		var window = this;
115 136
 		this.windowTitle = "Typica - Manual Log Entry";
137
+		window.windowReady.connect(function() {
138
+			if(machineModel.rowCount() == 0) {
139
+				displayError(TTR("manualLogEntry", "Configuration Required"),
140
+				TTR("manualLogEntry", "Please configure a roaster."));
141
+				window.close();
142
+			}
143
+		});
116 144
 		quitItem = findChildObject(this, 'quitItem');
117 145
 		quitItem.triggered.connect(function() {
118 146
 			Application.quit();
119 147
 		});
120 148
 		pluginContext = {};
121 149
 		pluginContext.table = findChildObject(this, 'log');
150
+		pluginContext.table.setHeaderData(1, "Temp");
151
+		pluginContext.table.setHeaderData(2, "Note");
122 152
 		pluginContext.graph = findChildObject(this, 'graph');
123 153
 		pluginContext.preRun = function() {
124 154
 			var filename = QFileDialog.getOpenFileName(window, TTR("manualLogEntry", "Import"), QSettings.value('script/lastDir', '') + '/');
@@ -145,7 +175,6 @@
145 175
 		tabs = findChildObject(this, 'tabs');
146 176
 		tabs.addTab("Batch Data");
147 177
 		tabs.addTab("Roast Data");
148
-		tabs.addTab("Roast Data Configuration");
149 178
 		pages = findChildObject(this, 'pages');
150 179
 		tabs.currentChanged.connect(function(index) {
151 180
 			pages.setCurrentIndex(index);
@@ -188,6 +217,117 @@
188 217
 			QSettings.setValue("script/manual_unit", greenUnitIndex);
189 218
 			sampleGreenUnit.setCurrentIndex(greenUnitIndex);
190 219
 		});
220
+		timeincrement = findChildObject(this, 'timeincrement');
221
+		currenttime = findChildObject(this, 'currenttime');
222
+		currenttemperature = findChildObject(this, 'currenttemperature');
223
+		currentnote = findChildObject(this, 'currentnote');
224
+		addmeasurement = findChildObject(this, 'addmeasurement');
225
+		addmeasurement.clicked.connect(function() {
226
+			pluginContext.newMeasurement(new Measurement(Number(currenttemperature.text), currenttime.time), 1);
227
+			if(currentnote.text.length > 0) {
228
+				pluginContext.table.newAnnotation(currentnote.text, 1, 2);
229
+			}
230
+			currentnote.text = "";
231
+			var t = QTime();
232
+			t = t.fromString(currenttime.time, "hh:mm:ss");
233
+			t = t.addSecs(30);
234
+			currenttime.time = t;
235
+			currenttemperature.text = "";
236
+		});
237
+		currenttemperature.returnPressed.connect(addmeasurement.clicked);
238
+		currentnote.returnPressed.connect(addmeasurement.clicked);
239
+		var v1 = findChildObject(this, 'ms');
240
+        v1.triggered.connect(pluginContext.table.LOD_ms);
241
+        var v2 = findChildObject(this, '1s');
242
+        v2.triggered.connect(pluginContext.table.LOD_1s);
243
+        var v3 = findChildObject(this, '5s');
244
+        v3.triggered.connect(pluginContext.table.LOD_5s);
245
+        var v4 = findChildObject(this, '10s');
246
+        v4.triggered.connect(pluginContext.table.LOD_10s);
247
+        var v5 = findChildObject(this, '15s');
248
+        v5.triggered.connect(pluginContext.table.LOD_15s);
249
+        var v6 = findChildObject(this, '30s');
250
+        v6.triggered.connect(pluginContext.table.LOD_30s);
251
+        var v7 = findChildObject(this, '1m');
252
+        v7.triggered.connect(pluginContext.table.LOD_1m);
253
+		var clear = findChildObject(this, 'clear');
254
+        clear.triggered.connect(pluginContext.table.clear);
255
+        clear.triggered.connect(pluginContext.graph.clear);
256
+		clear.triggered.connect(function() {
257
+			currenttime.time = QTime(0, 0, 0, 0);
258
+			currenttemperature.text = "";
259
+			currentnote.text = "";
260
+		});
261
+		var sampleGreenName = findChildObject(this, 'sampleGreenName');
262
+		var sampleGreenWeight = findChildObject(this, 'sampleGreenWeight');
263
+		var productionGreenTable = findChildObject(this, 'productionGreenTable');
264
+		var greenModel = productionGreenTable.model();
265
+		var greenTotal = 0.0;
266
+		var updateGreenTable = function() {
267
+			var deleteRow = -1;
268
+			while((deleteRow = productionGreenTable.findData("delete", 0)) > -1) {
269
+				if(productionGreenTable.data(deleteRow, 0, 0) == "Delete") {
270
+					productionGreenTable.removeRow(productionGreenTable.findData("delete", 0));
271
+				} else {
272
+					break;
273
+				}
274
+			}
275
+			greenTotal = productionGreenTable.columnSum(1, 0);
276
+			productionGreenTable.resizeColumnToContents(0);
277
+		};
278
+		greenModel.dataChanged.connect(updateGreenTable);
279
+		var validateInputs = function() {
280
+			if(batchType.currentIndex == 0) {
281
+				/* Sample batch */
282
+				if(sampleGreenName.text.length == 0) {
283
+					tabs.setCurrentIndex(0);
284
+					displayError(TTR("manualLogEntry", "Data Entry Error"),
285
+					TTR("manualLogEntry", "Please enter a green coffee name."));
286
+					return false;
287
+				}
288
+				if(Number(sampleGreenWeight.text) <= 0 || isNaN(sampleGreenWeight.text)) {
289
+					tabs.setCurrentIndex(0);
290
+					displayError(TTR("manualLogEntry", "Data Entry Error"),
291
+					TTR("manualLogEntry", "Green coffee weight must be a number greater than 0."));
292
+					return false;
293
+				}
294
+			} else {
295
+				/* Production batch */
296
+				var itemArray = productionGreenTable.columnArray(0, 32).split("\\s*,\\s*");
297
+				var weightArray = productionGreenTable.columnArray(1, 0).split("\\s*,\\s*");
298
+				if((itemArray.length != weightArray.length) || (itemArray.length == 0)) {
299
+					tabs.setCurrentIndex(0);
300
+					displayError(TTR("manualLogEntry", "Data Entry Error"),
301
+					TTR("manualLogEntry", "Please check that at least one green coffee has been selected and each green coffee has a valid weight"));
302
+					return false;
303
+				}
304
+				if(Number(greenTotal) <= 0) {
305
+					tabs.setCurrentIndex(0);
306
+					displayError(TTR("manualLogEntry", "DataEntryError"),
307
+					TTR("manualLogEntry", "Total green coffee weight must be a number greater than 0."));
308
+					return false;
309
+				}
310
+				if(roastedItem.currentIndex == 0) {
311
+					tabs.setCurrentIndex(0);
312
+					displayError(TTR("manualLogEntry", "DataEntryError"),
313
+					TTR("manualLogEntry", "Please select a roasted coffee item."));
314
+					return false;
315
+				}
316
+			}
317
+			return true;
318
+		};
319
+		var roastDataExists = function() {
320
+			return (pluginContext.table.rowCount() > 0);
321
+		}
322
+		var doSubmit = function() {
323
+			window.close();
324
+		}
325
+		var submit = findChildObject(this, 'submit');
326
+		submit.clicked.connect(function() {
327
+			if(validateInputs()) {
328
+				doSubmit();
329
+			}
330
+		});
191 331
 	]]>
192 332
 	</program>
193 333
 </window>

+ 1
- 0
config/config.xml View File

@@ -33,6 +33,7 @@
33 33
     <include src="Windows/newsamplebatch.xml" />
34 34
     <include src="Windows/editreminder.xml" />
35 35
     <include src="Windows/roastspec.xml" />
36
+	<include src="Windows/manuallogentry.xml" />
36 37
     <program>
37 38
         <![CDATA[
38 39
             Windows = new Object();

+ 5
- 1
src/graphsettings.w View File

@@ -62,6 +62,10 @@ class GraphSettingsRelativeTab : public QWidget@/
62 62
 @ The constructor sets up the interface and restores any previous values from
63 63
 settings.
64 64
 
65
+The default grid line position has been updated since version 1.8 to match the
66
+number of grid lines present when viewing the graph in Fahrenheit and to
67
+present a slightly wider range where most measurements are expected.
68
+
65 69
 @<GraphSettingsWidget implementation@>=
66 70
 GraphSettingsRelativeTab::GraphSettingsRelativeTab() : QWidget(NULL),
67 71
 	colorEdit(new QLineEdit)
@@ -101,7 +105,7 @@ GraphSettingsRelativeTab::GraphSettingsRelativeTab() : QWidget(NULL),
101 105
 	QHBoxLayout *axisLayout = new QHBoxLayout;
102 106
 	QLabel *axisLabel = new QLabel(tr("Grid line positions (comma separated):"));
103 107
 	QLineEdit *axisEdit = new QLineEdit;
104
-	axisEdit->setText(settings.value("settings/graph/relative/grid", "-300, -100, -10, 0, 10, 30, 50").toString());
108
+	axisEdit->setText(settings.value("settings/graph/relative/grid", "-300, -100, 0, 30, 65, 100").toString());
105 109
 	updateAxisSetting(axisEdit->text());
106 110
 	connect(axisEdit, SIGNAL(textChanged(QString)), this, SLOT(updateAxisSetting(QString)));
107 111
 	axisLayout->addWidget(axisLabel);

+ 39
- 10
src/typica.w View File

@@ -1039,6 +1039,15 @@ considered depreciated.
1039 1039
 Version 1.6 adds a new property for handling the |windowModified| property
1040 1040
 such that an appropriate prompt is provided to confirm or cancel close events.
1041 1041
 
1042
+Version 1.8 adds a new |setupFinished()| slot which is called after the
1043
+initial |show()| at the end of window creation. This emits a |windowReady()|
1044
+signal. Scripts can connect to this signal to perform tasks that must happen
1045
+after the window has fully finished opening. The initial use for this is
1046
+validating that all required configuration has been performed for a given
1047
+window to be useful and, if not, immediately closing that. Without this, a
1048
+call to |close()| in the script is reversed when the function creating the
1049
+window calls |show()|.
1050
+
1042 1051
 @<Class declarations@>=
1043 1052
 class ScriptQMainWindow : public QMainWindow@/
1044 1053
 {@t\1@>@/
@@ -1052,12 +1061,14 @@ class ScriptQMainWindow : public QMainWindow@/
1052 1061
         void saveSizeAndPosition(const QString &key);
1053 1062
         void restoreSizeAndPosition(const QString &key);
1054 1063
         void displayStatus(const QString &message = QString());
1055
-        void setClosePrompt(QString prompt);@/
1064
+        void setClosePrompt(QString prompt);
1065
+        void setupFinished();@/
1066
+    signals:@/
1067
+        void aboutToClose(void);
1068
+        void windowReady(void);@/
1056 1069
     protected:@/
1057 1070
         void closeEvent(QCloseEvent *event);
1058 1071
         void showEvent(QShowEvent *event);@/
1059
-    signals:@/
1060
-        void aboutToClose(void);@/
1061 1072
     private:@/
1062 1073
         QString cprompt;@t\2@>@/
1063 1074
 }@t\kern-3pt@>;
@@ -1118,6 +1129,11 @@ void ScriptQMainWindow::show()
1118 1129
     QMainWindow::show();
1119 1130
 }
1120 1131
 
1132
+void ScriptQMainWindow::setupFinished()
1133
+{
1134
+	emit windowReady();
1135
+}
1136
+
1121 1137
 @ When a close event occurs, we check the |windowModified| property to
1122 1138
 determine if closing the window could result in loss of data. If this is
1123 1139
 true, we allow the event to be cancelled. Otherwise, a signal is emitted which
@@ -4706,6 +4722,7 @@ if(element.hasChildNodes())
4706 4722
 }
4707 4723
 @<Insert help menu@>@;
4708 4724
 window->show();
4725
+window->setupFinished();
4709 4726
 
4710 4727
 @ Three element types make sense as top level children of a {\tt <window>}
4711 4728
 element. An element representing a layout element can be used to apply that
@@ -9158,6 +9175,9 @@ space, but it is very fast and simple to code.
9158 9175
 Starting in version 1.4, column sizes are persisted automatically using the
9159 9176
 same method as described in the section on |SqlQueryView|.
9160 9177
 
9178
+Starting in version 1.8, |rowCount()| is |Q_INVOKABLE|. This allows the manual
9179
+log entry interface to easily determine if any roasting data exists to save.
9180
+
9161 9181
 @<Class declarations@>=
9162 9182
 class MeasurementModel;@/
9163 9183
 class ZoomLog : public QTableView@/
@@ -9172,7 +9192,7 @@ class ZoomLog : public QTableView@/
9172 9192
     public:@/
9173 9193
         ZoomLog();
9174 9194
         QVariant data(int row, int column) const;
9175
-        int rowCount();
9195
+        @[Q_INVOKABLE@,@, int rowCount();
9176 9196
         bool saveXML(QIODevice *device);
9177 9197
         bool saveCSV(QIODevice *device);
9178 9198
         QString lastTime(int series);
@@ -9304,11 +9324,11 @@ annotation associated with it. The solution in this case is to synthesize
9304 9324
 measurements so that the |ZoomLog| thinks it gets at least one measurement
9305 9325
 every second.
9306 9326
 
9307
-The current approach simply replicates the last measurement every second until
9308
-the time for the most recent measurement is reached, however it would likely be
9309
-better to interpolate values between the two most recent real measurements as
9310
-this would match the graphic representation rather than altering it when later
9311
-reviewing the batch.
9327
+Prior to version 1.8 this simply replicated the last measurement every second
9328
+until the time for the most recent measurement was reached, however this yields
9329
+problematic results when loading saved data or attempting to use this view for
9330
+manual data entry. The current behavior performs a linear interpolation which
9331
+will match the graph.
9312 9332
 
9313 9333
 @<Synthesize measurements for slow hardware@>=
9314 9334
 if(lastMeasurement.contains(tempcolumn))
@@ -9316,14 +9336,23 @@ if(lastMeasurement.contains(tempcolumn))
9316 9336
     if(lastMeasurement[tempcolumn].time() < measure.time())
9317 9337
     {
9318 9338
         QList<QTime> timelist;
9339
+        QList<double> templist;
9340
+        QTime z = QTime(0, 0, 0, 0);
9341
+        double ptime = (double)(z.secsTo(lastMeasurement[tempcolumn].time()));
9342
+        double ptemp = lastMeasurement[tempcolumn].temperature();
9343
+        double ctime = (double)(z.secsTo(measure.time()));
9344
+        double ctemp = measure.temperature();
9319 9345
         for(QTime i = lastMeasurement.value(tempcolumn).time().addSecs(1); i < measure.time(); i = i.addSecs(1))
9320 9346
         {
9321 9347
             timelist.append(i);
9348
+            double v = ((ptemp * (ctime - z.secsTo(i))) + (ctemp * (z.secsTo(i) - ptime))) / (ctime - ptime);
9349
+            templist.append(v);
9322 9350
         }
9323 9351
         for(int i = 0; i < timelist.size(); i++)
9324 9352
         {
9325 9353
             Measurement synthesized = measure;
9326 9354
             synthesized.setTime(timelist[i]);
9355
+            synthesized.setTemperature(templist[i]);
9327 9356
             newMeasurement(synthesized, tempcolumn);
9328 9357
         }
9329 9358
     }
@@ -15536,7 +15565,7 @@ class DeviceTreeModel : public QAbstractItemModel@/
15536 15565
         QModelIndex index(int row, int column,
15537 15566
                           const QModelIndex &parent = QModelIndex()) const;
15538 15567
         QModelIndex parent(const QModelIndex &child) const;
15539
-        int rowCount(const QModelIndex &parent = QModelIndex()) const;
15568
+        Q_INVOKABLE int rowCount(const QModelIndex &parent = QModelIndex()) const;
15540 15569
         int columnCount(const QModelIndex &parent = QModelIndex()) const;
15541 15570
         bool setData(const QModelIndex &index, const QVariant &value,
15542 15571
                      int role);

Loading…
Cancel
Save