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
 		<separator />
4
 		<separator />
5
 		<item id="quitItem" shortcut="ctrl+Q">Quit</item>
5
 		<item id="quitItem" shortcut="ctrl+Q">Quit</item>
6
 	</menu>
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
 	<layout type="vertical">
18
 	<layout type="vertical">
8
 		<tabbar id="tabs"/>
19
 		<tabbar id="tabs"/>
9
 		<layout type="stack" id="pages">
20
 		<layout type="stack" id="pages">
27
 									</row>
38
 									</row>
28
 									<row>
39
 									<row>
29
 										<column><label>Weight:</label></column>
40
 										<column><label>Weight:</label></column>
30
-										<column><line id="sampleGreenWeight" /></column>
41
+										<column><line id="sampleGreenWeight" validator="numeric">0.0</line></column>
31
 										<column><sqldrop id="sampleGreenUnit" /></column>
42
 										<column><sqldrop id="sampleGreenUnit" /></column>
32
 									</row>
43
 									</row>
33
 									<row>
44
 									<row>
36
 									</row>
47
 									</row>
37
 									<row>
48
 									<row>
38
 										<column><label>Arrival Date:</label></column>
49
 										<column><label>Arrival Date:</label></column>
39
-										<column><line id="sampleGreenArrivalDate" /></column>
50
+										<column><calendar id="sampleGreenArrivalDate" /></column>
40
 									</row>
51
 									</row>
41
 								</layout>
52
 								</layout>
42
 								<label>Additional Details:</label>
53
 								<label>Additional Details:</label>
75
 						</row>
86
 						</row>
76
 						<row>
87
 						<row>
77
 							<column><label>Weight:</label></column>
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
 						</row>
90
 						</row>
80
 						<row>
91
 						<row>
81
 							<column><label>Time:</label></column>
92
 							<column><label>Time:</label></column>
94
 			</page>
105
 			</page>
95
 			<page>
106
 			<page>
96
 				<layout type="vertical">
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
 					<splitter type="horizontal" id="roastdatasplit">
121
 					<splitter type="horizontal" id="roastdatasplit">
98
 						<measurementtable id="log" />
122
 						<measurementtable id="log" />
99
 						<graph id="graph" />
123
 						<graph id="graph" />
100
 					</splitter>
124
 					</splitter>
101
 				</layout>
125
 				</layout>
102
 			</page>
126
 			</page>
103
-			<page>
104
-				
105
-			</page>
106
 		</layout>
127
 		</layout>
107
 		<layout type="horizontal">
128
 		<layout type="horizontal">
108
 			<stretch />
129
 			<stretch />
113
 	<![CDATA[
134
 	<![CDATA[
114
 		var window = this;
135
 		var window = this;
115
 		this.windowTitle = "Typica - Manual Log Entry";
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
 		quitItem = findChildObject(this, 'quitItem');
144
 		quitItem = findChildObject(this, 'quitItem');
117
 		quitItem.triggered.connect(function() {
145
 		quitItem.triggered.connect(function() {
118
 			Application.quit();
146
 			Application.quit();
119
 		});
147
 		});
120
 		pluginContext = {};
148
 		pluginContext = {};
121
 		pluginContext.table = findChildObject(this, 'log');
149
 		pluginContext.table = findChildObject(this, 'log');
150
+		pluginContext.table.setHeaderData(1, "Temp");
151
+		pluginContext.table.setHeaderData(2, "Note");
122
 		pluginContext.graph = findChildObject(this, 'graph');
152
 		pluginContext.graph = findChildObject(this, 'graph');
123
 		pluginContext.preRun = function() {
153
 		pluginContext.preRun = function() {
124
 			var filename = QFileDialog.getOpenFileName(window, TTR("manualLogEntry", "Import"), QSettings.value('script/lastDir', '') + '/');
154
 			var filename = QFileDialog.getOpenFileName(window, TTR("manualLogEntry", "Import"), QSettings.value('script/lastDir', '') + '/');
145
 		tabs = findChildObject(this, 'tabs');
175
 		tabs = findChildObject(this, 'tabs');
146
 		tabs.addTab("Batch Data");
176
 		tabs.addTab("Batch Data");
147
 		tabs.addTab("Roast Data");
177
 		tabs.addTab("Roast Data");
148
-		tabs.addTab("Roast Data Configuration");
149
 		pages = findChildObject(this, 'pages');
178
 		pages = findChildObject(this, 'pages');
150
 		tabs.currentChanged.connect(function(index) {
179
 		tabs.currentChanged.connect(function(index) {
151
 			pages.setCurrentIndex(index);
180
 			pages.setCurrentIndex(index);
188
 			QSettings.setValue("script/manual_unit", greenUnitIndex);
217
 			QSettings.setValue("script/manual_unit", greenUnitIndex);
189
 			sampleGreenUnit.setCurrentIndex(greenUnitIndex);
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
 	</program>
332
 	</program>
193
 </window>
333
 </window>

+ 1
- 0
config/config.xml View File

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

+ 5
- 1
src/graphsettings.w View File

62
 @ The constructor sets up the interface and restores any previous values from
62
 @ The constructor sets up the interface and restores any previous values from
63
 settings.
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
 @<GraphSettingsWidget implementation@>=
69
 @<GraphSettingsWidget implementation@>=
66
 GraphSettingsRelativeTab::GraphSettingsRelativeTab() : QWidget(NULL),
70
 GraphSettingsRelativeTab::GraphSettingsRelativeTab() : QWidget(NULL),
67
 	colorEdit(new QLineEdit)
71
 	colorEdit(new QLineEdit)
101
 	QHBoxLayout *axisLayout = new QHBoxLayout;
105
 	QHBoxLayout *axisLayout = new QHBoxLayout;
102
 	QLabel *axisLabel = new QLabel(tr("Grid line positions (comma separated):"));
106
 	QLabel *axisLabel = new QLabel(tr("Grid line positions (comma separated):"));
103
 	QLineEdit *axisEdit = new QLineEdit;
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
 	updateAxisSetting(axisEdit->text());
109
 	updateAxisSetting(axisEdit->text());
106
 	connect(axisEdit, SIGNAL(textChanged(QString)), this, SLOT(updateAxisSetting(QString)));
110
 	connect(axisEdit, SIGNAL(textChanged(QString)), this, SLOT(updateAxisSetting(QString)));
107
 	axisLayout->addWidget(axisLabel);
111
 	axisLayout->addWidget(axisLabel);

+ 39
- 10
src/typica.w View File

1039
 Version 1.6 adds a new property for handling the |windowModified| property
1039
 Version 1.6 adds a new property for handling the |windowModified| property
1040
 such that an appropriate prompt is provided to confirm or cancel close events.
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
 @<Class declarations@>=
1051
 @<Class declarations@>=
1043
 class ScriptQMainWindow : public QMainWindow@/
1052
 class ScriptQMainWindow : public QMainWindow@/
1044
 {@t\1@>@/
1053
 {@t\1@>@/
1052
         void saveSizeAndPosition(const QString &key);
1061
         void saveSizeAndPosition(const QString &key);
1053
         void restoreSizeAndPosition(const QString &key);
1062
         void restoreSizeAndPosition(const QString &key);
1054
         void displayStatus(const QString &message = QString());
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
     protected:@/
1069
     protected:@/
1057
         void closeEvent(QCloseEvent *event);
1070
         void closeEvent(QCloseEvent *event);
1058
         void showEvent(QShowEvent *event);@/
1071
         void showEvent(QShowEvent *event);@/
1059
-    signals:@/
1060
-        void aboutToClose(void);@/
1061
     private:@/
1072
     private:@/
1062
         QString cprompt;@t\2@>@/
1073
         QString cprompt;@t\2@>@/
1063
 }@t\kern-3pt@>;
1074
 }@t\kern-3pt@>;
1118
     QMainWindow::show();
1129
     QMainWindow::show();
1119
 }
1130
 }
1120
 
1131
 
1132
+void ScriptQMainWindow::setupFinished()
1133
+{
1134
+	emit windowReady();
1135
+}
1136
+
1121
 @ When a close event occurs, we check the |windowModified| property to
1137
 @ When a close event occurs, we check the |windowModified| property to
1122
 determine if closing the window could result in loss of data. If this is
1138
 determine if closing the window could result in loss of data. If this is
1123
 true, we allow the event to be cancelled. Otherwise, a signal is emitted which
1139
 true, we allow the event to be cancelled. Otherwise, a signal is emitted which
4706
 }
4722
 }
4707
 @<Insert help menu@>@;
4723
 @<Insert help menu@>@;
4708
 window->show();
4724
 window->show();
4725
+window->setupFinished();
4709
 
4726
 
4710
 @ Three element types make sense as top level children of a {\tt <window>}
4727
 @ Three element types make sense as top level children of a {\tt <window>}
4711
 element. An element representing a layout element can be used to apply that
4728
 element. An element representing a layout element can be used to apply that
9158
 Starting in version 1.4, column sizes are persisted automatically using the
9175
 Starting in version 1.4, column sizes are persisted automatically using the
9159
 same method as described in the section on |SqlQueryView|.
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
 @<Class declarations@>=
9181
 @<Class declarations@>=
9162
 class MeasurementModel;@/
9182
 class MeasurementModel;@/
9163
 class ZoomLog : public QTableView@/
9183
 class ZoomLog : public QTableView@/
9172
     public:@/
9192
     public:@/
9173
         ZoomLog();
9193
         ZoomLog();
9174
         QVariant data(int row, int column) const;
9194
         QVariant data(int row, int column) const;
9175
-        int rowCount();
9195
+        @[Q_INVOKABLE@,@, int rowCount();
9176
         bool saveXML(QIODevice *device);
9196
         bool saveXML(QIODevice *device);
9177
         bool saveCSV(QIODevice *device);
9197
         bool saveCSV(QIODevice *device);
9178
         QString lastTime(int series);
9198
         QString lastTime(int series);
9304
 measurements so that the |ZoomLog| thinks it gets at least one measurement
9324
 measurements so that the |ZoomLog| thinks it gets at least one measurement
9305
 every second.
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
 @<Synthesize measurements for slow hardware@>=
9333
 @<Synthesize measurements for slow hardware@>=
9314
 if(lastMeasurement.contains(tempcolumn))
9334
 if(lastMeasurement.contains(tempcolumn))
9316
     if(lastMeasurement[tempcolumn].time() < measure.time())
9336
     if(lastMeasurement[tempcolumn].time() < measure.time())
9317
     {
9337
     {
9318
         QList<QTime> timelist;
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
         for(QTime i = lastMeasurement.value(tempcolumn).time().addSecs(1); i < measure.time(); i = i.addSecs(1))
9345
         for(QTime i = lastMeasurement.value(tempcolumn).time().addSecs(1); i < measure.time(); i = i.addSecs(1))
9320
         {
9346
         {
9321
             timelist.append(i);
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
         for(int i = 0; i < timelist.size(); i++)
9351
         for(int i = 0; i < timelist.size(); i++)
9324
         {
9352
         {
9325
             Measurement synthesized = measure;
9353
             Measurement synthesized = measure;
9326
             synthesized.setTime(timelist[i]);
9354
             synthesized.setTime(timelist[i]);
9355
+            synthesized.setTemperature(templist[i]);
9327
             newMeasurement(synthesized, tempcolumn);
9356
             newMeasurement(synthesized, tempcolumn);
9328
         }
9357
         }
9329
     }
9358
     }
15536
         QModelIndex index(int row, int column,
15565
         QModelIndex index(int row, int column,
15537
                           const QModelIndex &parent = QModelIndex()) const;
15566
                           const QModelIndex &parent = QModelIndex()) const;
15538
         QModelIndex parent(const QModelIndex &child) const;
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
         int columnCount(const QModelIndex &parent = QModelIndex()) const;
15569
         int columnCount(const QModelIndex &parent = QModelIndex()) const;
15541
         bool setData(const QModelIndex &index, const QVariant &value,
15570
         bool setData(const QModelIndex &index, const QVariant &value,
15542
                      int role);
15571
                      int role);

Loading…
Cancel
Save